Adds trunk/talk folder of revision 359 from libjingles google code to
trunk/talk


git-svn-id: http://webrtc.googlecode.com/svn/trunk@4318 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/talk/OWNERS b/talk/OWNERS
new file mode 100644
index 0000000..f124b68
--- /dev/null
+++ b/talk/OWNERS
@@ -0,0 +1 @@
+henrike@webrtc.org
\ No newline at end of file
diff --git a/talk/app/webrtc/audiotrack.cc b/talk/app/webrtc/audiotrack.cc
new file mode 100644
index 0000000..5bfca42
--- /dev/null
+++ b/talk/app/webrtc/audiotrack.cc
@@ -0,0 +1,53 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "talk/app/webrtc/audiotrack.h"
+
+#include <string>
+
+namespace webrtc {
+
+static const char kAudioTrackKind[] = "audio";
+
+AudioTrack::AudioTrack(const std::string& label,
+                       AudioSourceInterface* audio_source)
+    : MediaStreamTrack<AudioTrackInterface>(label),
+      audio_source_(audio_source),
+      renderer_(new AudioTrackRenderer()) {
+}
+
+std::string AudioTrack::kind() const {
+  return kAudioTrackKind;
+}
+
+talk_base::scoped_refptr<AudioTrack> AudioTrack::Create(
+    const std::string& id, AudioSourceInterface* source) {
+  talk_base::RefCountedObject<AudioTrack>* track =
+      new talk_base::RefCountedObject<AudioTrack>(id, source);
+  return track;
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/audiotrack.h b/talk/app/webrtc/audiotrack.h
new file mode 100644
index 0000000..48098f5
--- /dev/null
+++ b/talk/app/webrtc/audiotrack.h
@@ -0,0 +1,66 @@
+/*
+ * 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_AUDIOTRACK_H_
+#define TALK_APP_WEBRTC_AUDIOTRACK_H_
+
+#include "talk/app/webrtc/audiotrackrenderer.h"
+#include "talk/app/webrtc/mediastreaminterface.h"
+#include "talk/app/webrtc/mediastreamtrack.h"
+#include "talk/app/webrtc/notifier.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/scoped_ref_ptr.h"
+
+namespace webrtc {
+
+class AudioTrack : public MediaStreamTrack<AudioTrackInterface> {
+ public:
+  static talk_base::scoped_refptr<AudioTrack> Create(
+      const std::string& id, AudioSourceInterface* source);
+
+  virtual AudioSourceInterface* GetSource() const {
+    return audio_source_.get();
+  }
+
+  virtual cricket::AudioRenderer* FrameInput() {
+    return renderer_.get();
+  }
+
+  // Implement MediaStreamTrack
+  virtual std::string kind() const;
+
+ protected:
+  AudioTrack(const std::string& label, AudioSourceInterface* audio_source);
+
+ private:
+  talk_base::scoped_refptr<AudioSourceInterface> audio_source_;
+  talk_base::scoped_ptr<AudioTrackRenderer> renderer_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_AUDIOTRACK_H_
diff --git a/talk/app/webrtc/audiotrackrenderer.cc b/talk/app/webrtc/audiotrackrenderer.cc
new file mode 100644
index 0000000..c8ad522
--- /dev/null
+++ b/talk/app/webrtc/audiotrackrenderer.cc
@@ -0,0 +1,48 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/audiotrackrenderer.h"
+#include "talk/base/common.h"
+
+namespace webrtc {
+
+AudioTrackRenderer::AudioTrackRenderer() : channel_id_(-1) {
+}
+
+AudioTrackRenderer::~AudioTrackRenderer() {
+}
+
+void AudioTrackRenderer::SetChannelId(int channel_id) {
+  ASSERT(channel_id_ == -1);
+  channel_id_ = channel_id;
+}
+
+int AudioTrackRenderer::GetChannelId() const {
+  return channel_id_;
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/audiotrackrenderer.h b/talk/app/webrtc/audiotrackrenderer.h
new file mode 100644
index 0000000..55de04e
--- /dev/null
+++ b/talk/app/webrtc/audiotrackrenderer.h
@@ -0,0 +1,55 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_AUDIOTRACKRENDERER_H_
+#define TALK_APP_WEBRTC_AUDIOTRACKRENDERER_H_
+
+#include "talk/media/base/audiorenderer.h"
+
+namespace webrtc {
+
+// Class used for AudioTrack to get the ID of WebRtc voice channel that
+// the AudioTrack is connecting to.
+// Each AudioTrack owns a AudioTrackRenderer instance.
+// SetChannelID() should be called only when a AudioTrack is added to a
+// MediaStream and should not be changed afterwards.
+class AudioTrackRenderer : public cricket::AudioRenderer {
+ public:
+  AudioTrackRenderer();
+  ~AudioTrackRenderer();
+
+  // Implements cricket::AudioRenderer.
+  virtual void SetChannelId(int channel_id);
+  virtual int GetChannelId() const;
+
+ private:
+  int channel_id_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_AUDIOTRACKRENDERER_H_
diff --git a/talk/app/webrtc/datachannel.cc b/talk/app/webrtc/datachannel.cc
new file mode 100644
index 0000000..345cd5f
--- /dev/null
+++ b/talk/app/webrtc/datachannel.cc
@@ -0,0 +1,295 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/datachannel.h"
+
+#include <string>
+
+#include "talk/app/webrtc/webrtcsession.h"
+#include "talk/base/logging.h"
+#include "talk/base/refcount.h"
+
+namespace webrtc {
+
+static size_t kMaxQueuedDataPackets = 100;
+
+talk_base::scoped_refptr<DataChannel> DataChannel::Create(
+    WebRtcSession* session,
+    const std::string& label,
+    const DataChannelInit* config) {
+  talk_base::scoped_refptr<DataChannel> channel(
+      new talk_base::RefCountedObject<DataChannel>(session, label));
+  if (!channel->Init(config)) {
+    return NULL;
+  }
+  return channel;
+}
+
+DataChannel::DataChannel(WebRtcSession* session, const std::string& label)
+    : label_(label),
+      observer_(NULL),
+      state_(kConnecting),
+      was_ever_writable_(false),
+      session_(session),
+      data_session_(NULL),
+      send_ssrc_set_(false),
+      send_ssrc_(0),
+      receive_ssrc_set_(false),
+      receive_ssrc_(0) {
+}
+
+bool DataChannel::Init(const DataChannelInit* config) {
+  if (config) {
+    if (session_->data_channel_type() == cricket::DCT_RTP &&
+        (config->reliable ||
+         config->id != -1 ||
+         config->maxRetransmits != -1 ||
+         config->maxRetransmitTime != -1)) {
+      LOG(LS_ERROR) << "Failed to initialize the RTP data channel due to "
+                    << "invalid DataChannelInit.";
+      return false;
+    } else if (session_->data_channel_type() == cricket::DCT_SCTP) {
+      if (config->id < -1 ||
+          config->maxRetransmits < -1 ||
+          config->maxRetransmitTime < -1) {
+        LOG(LS_ERROR) << "Failed to initialize the SCTP data channel due to "
+                      << "invalid DataChannelInit.";
+        return false;
+      }
+      if (config->maxRetransmits != -1 && config->maxRetransmitTime != -1) {
+        LOG(LS_ERROR) <<
+            "maxRetransmits and maxRetransmitTime should not be both set.";
+        return false;
+      }
+    }
+    config_ = *config;
+  }
+  return true;
+}
+
+bool DataChannel::HasNegotiationCompleted() {
+  return send_ssrc_set_ == receive_ssrc_set_;
+}
+
+DataChannel::~DataChannel() {
+  ClearQueuedData();
+}
+
+void DataChannel::RegisterObserver(DataChannelObserver* observer) {
+  observer_ = observer;
+  DeliverQueuedData();
+}
+
+void DataChannel::UnregisterObserver() {
+  observer_ = NULL;
+}
+
+bool DataChannel::reliable() const {
+  if (session_->data_channel_type() == cricket::DCT_RTP) {
+    return false;
+  } else {
+    return config_.maxRetransmits == -1 &&
+           config_.maxRetransmitTime == -1;
+  }
+}
+
+uint64 DataChannel::buffered_amount() const {
+  return 0;
+}
+
+void DataChannel::Close() {
+  if (state_ == kClosed)
+    return;
+  send_ssrc_ = 0;
+  send_ssrc_set_ = false;
+  SetState(kClosing);
+  UpdateState();
+}
+
+bool DataChannel::Send(const DataBuffer& buffer) {
+  if (state_ != kOpen) {
+    return false;
+  }
+  cricket::SendDataParams send_params;
+
+  send_params.ssrc = send_ssrc_;
+  if (session_->data_channel_type() == cricket::DCT_SCTP) {
+    send_params.ordered = config_.ordered;
+    send_params.max_rtx_count = config_.maxRetransmits;
+    send_params.max_rtx_ms = config_.maxRetransmitTime;
+  }
+  send_params.type = buffer.binary ? cricket::DMT_BINARY : cricket::DMT_TEXT;
+
+  cricket::SendDataResult send_result;
+  // TODO(pthatcher): Use send_result.would_block for buffering.
+  return session_->data_channel()->SendData(
+      send_params, buffer.data, &send_result);
+}
+
+void DataChannel::SetReceiveSsrc(uint32 receive_ssrc) {
+  if (receive_ssrc_set_) {
+    ASSERT(session_->data_channel_type() == cricket::DCT_RTP ||
+        receive_ssrc_ == send_ssrc_);
+    return;
+  }
+  receive_ssrc_ = receive_ssrc;
+  receive_ssrc_set_ = true;
+  UpdateState();
+}
+
+// The remote peer request that this channel shall be closed.
+void DataChannel::RemotePeerRequestClose() {
+  DoClose();
+}
+
+void DataChannel::SetSendSsrc(uint32 send_ssrc) {
+  if (send_ssrc_set_) {
+    ASSERT(session_->data_channel_type() == cricket::DCT_RTP ||
+        receive_ssrc_ == send_ssrc_);
+    return;
+  }
+  send_ssrc_ = send_ssrc;
+  send_ssrc_set_ = true;
+  UpdateState();
+}
+
+// The underlaying data engine is closing.
+// This function make sure the DataChannel is disconneced and change state to
+// kClosed.
+void DataChannel::OnDataEngineClose() {
+  DoClose();
+}
+
+void DataChannel::DoClose() {
+  receive_ssrc_set_ = false;
+  send_ssrc_set_ = false;
+  SetState(kClosing);
+  UpdateState();
+}
+
+void DataChannel::UpdateState() {
+  switch (state_) {
+    case kConnecting: {
+      if (HasNegotiationCompleted()) {
+        if (!IsConnectedToDataSession()) {
+          ConnectToDataSession();
+        }
+        if (was_ever_writable_) {
+          SetState(kOpen);
+          // If we have received buffers before the channel got writable.
+          // Deliver them now.
+          DeliverQueuedData();
+        }
+      }
+      break;
+    }
+    case kOpen: {
+      break;
+    }
+    case kClosing: {
+      if (IsConnectedToDataSession()) {
+        DisconnectFromDataSession();
+      }
+      if (HasNegotiationCompleted()) {
+        SetState(kClosed);
+      }
+      break;
+    }
+    case kClosed:
+      break;
+  }
+}
+
+void DataChannel::SetState(DataState state) {
+  state_ = state;
+  if (observer_) {
+    observer_->OnStateChange();
+  }
+}
+
+void DataChannel::ConnectToDataSession() {
+  ASSERT(session_->data_channel() != NULL);
+  if (!session_->data_channel()) {
+    LOG(LS_ERROR) << "The DataEngine does not exist.";
+    return;
+  }
+
+  data_session_ = session_->data_channel();
+  data_session_->SignalReadyToSendData.connect(this,
+                                               &DataChannel::OnChannelReady);
+  data_session_->SignalDataReceived.connect(this, &DataChannel::OnDataReceived);
+}
+
+void DataChannel::DisconnectFromDataSession() {
+  data_session_->SignalReadyToSendData.disconnect(this);
+  data_session_->SignalDataReceived.disconnect(this);
+  data_session_ = NULL;
+}
+
+void DataChannel::DeliverQueuedData() {
+  if (was_ever_writable_ && observer_) {
+    while (!queued_data_.empty()) {
+      DataBuffer* buffer = queued_data_.front();
+      observer_->OnMessage(*buffer);
+      queued_data_.pop();
+      delete buffer;
+    }
+  }
+}
+
+void DataChannel::ClearQueuedData() {
+  while (!queued_data_.empty()) {
+    DataBuffer* buffer = queued_data_.front();
+    queued_data_.pop();
+    delete buffer;
+  }
+}
+
+void DataChannel::OnDataReceived(cricket::DataChannel* channel,
+                                 const cricket::ReceiveDataParams& params,
+                                 const talk_base::Buffer& payload) {
+  if (params.ssrc == receive_ssrc_) {
+    bool binary = false;
+    talk_base::scoped_ptr<DataBuffer> buffer(new DataBuffer(payload, binary));
+    if (was_ever_writable_ && observer_) {
+      observer_->OnMessage(*buffer.get());
+    } else {
+      if (queued_data_.size() > kMaxQueuedDataPackets) {
+        ClearQueuedData();
+      }
+      queued_data_.push(buffer.release());
+    }
+  }
+}
+
+void DataChannel::OnChannelReady(bool writable) {
+  if (!was_ever_writable_ && writable) {
+    was_ever_writable_ = true;
+    UpdateState();
+  }
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/datachannel.h b/talk/app/webrtc/datachannel.h
new file mode 100644
index 0000000..c79c491
--- /dev/null
+++ b/talk/app/webrtc/datachannel.h
@@ -0,0 +1,154 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_DATACHANNEL_H_
+#define TALK_APP_WEBRTC_DATACHANNEL_H_
+
+#include <string>
+#include <queue>
+
+#include "talk/app/webrtc/datachannelinterface.h"
+#include "talk/app/webrtc/proxy.h"
+#include "talk/base/scoped_ref_ptr.h"
+#include "talk/base/sigslot.h"
+#include "talk/session/media/channel.h"
+
+namespace webrtc {
+
+class WebRtcSession;
+
+// DataChannel is a an implementation of the DataChannelInterface based on
+// libjingle's data engine. It provides an implementation of unreliable data
+// channels. Currently this class is specifically designed to use RtpDataEngine,
+// and will changed to use SCTP in the future.
+
+// DataChannel states:
+// kConnecting: The channel has been created but SSRC for sending and receiving
+//              has not yet been set and the transport might not yet be ready.
+// kOpen: The channel have a local SSRC set by a call to UpdateSendSsrc
+//        and a remote SSRC set by call to UpdateReceiveSsrc and the transport
+//        has been writable once.
+// kClosing: DataChannelInterface::Close has been called or UpdateReceiveSsrc
+//           has been called with SSRC==0
+// kClosed: Both UpdateReceiveSsrc and UpdateSendSsrc has been called with
+//          SSRC==0.
+class DataChannel : public DataChannelInterface,
+                    public sigslot::has_slots<> {
+ public:
+  static talk_base::scoped_refptr<DataChannel> Create(
+      WebRtcSession* session,
+      const std::string& label,
+      const DataChannelInit* config);
+
+  virtual void RegisterObserver(DataChannelObserver* observer);
+  virtual void UnregisterObserver();
+
+  virtual std::string label() const  { return label_; }
+  virtual bool reliable() const;
+  virtual int id() const { return config_.id; }
+  virtual uint64 buffered_amount() const;
+  virtual void Close();
+  virtual DataState state() const { return state_; }
+  virtual bool Send(const DataBuffer& buffer);
+
+  // Set the SSRC this channel should use to receive data from the
+  // underlying data engine.
+  void SetReceiveSsrc(uint32 receive_ssrc);
+  // The remote peer request that this channel should be closed.
+  void RemotePeerRequestClose();
+
+  // Set the SSRC this channel should use to send data on the
+  // underlying data engine. |send_ssrc| == 0 means that the channel is no
+  // longer part of the session negotiation.
+  void SetSendSsrc(uint32 send_ssrc);
+
+  // Called if the underlying data engine is closing.
+  void OnDataEngineClose();
+
+ protected:
+  DataChannel(WebRtcSession* session, const std::string& label);
+  virtual ~DataChannel();
+
+  bool Init(const DataChannelInit* config);
+  bool HasNegotiationCompleted();
+
+  // Sigslots from cricket::DataChannel
+  void OnDataReceived(cricket::DataChannel* channel,
+                      const cricket::ReceiveDataParams& params,
+                      const talk_base::Buffer& payload);
+  void OnChannelReady(bool writable);
+
+ private:
+  void DoClose();
+  void UpdateState();
+  void SetState(DataState state);
+  void ConnectToDataSession();
+  void DisconnectFromDataSession();
+  bool IsConnectedToDataSession() { return data_session_ != NULL; }
+  void DeliverQueuedData();
+  void ClearQueuedData();
+
+  std::string label_;
+  DataChannelInit config_;
+  DataChannelObserver* observer_;
+  DataState state_;
+  bool was_ever_writable_;
+  WebRtcSession* session_;
+  cricket::DataChannel* data_session_;
+  bool send_ssrc_set_;
+  uint32 send_ssrc_;
+  bool receive_ssrc_set_;
+  uint32 receive_ssrc_;
+  std::queue<DataBuffer*> queued_data_;
+};
+
+class DataChannelFactory {
+ public:
+  virtual talk_base::scoped_refptr<DataChannel> CreateDataChannel(
+      const std::string& label,
+      const DataChannelInit* config) = 0;
+
+ protected:
+  virtual ~DataChannelFactory() {}
+};
+
+// Define proxy for DataChannelInterface.
+BEGIN_PROXY_MAP(DataChannel)
+  PROXY_METHOD1(void, RegisterObserver, DataChannelObserver*)
+  PROXY_METHOD0(void, UnregisterObserver)
+  PROXY_CONSTMETHOD0(std::string, label)
+  PROXY_CONSTMETHOD0(bool, reliable)
+  PROXY_CONSTMETHOD0(int, id)
+  PROXY_CONSTMETHOD0(DataState, state)
+  PROXY_CONSTMETHOD0(uint64, buffered_amount)
+  PROXY_METHOD0(void, Close)
+  PROXY_METHOD1(bool, Send, const DataBuffer&)
+END_PROXY()
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_DATACHANNEL_H_
diff --git a/talk/app/webrtc/datachannelinterface.h b/talk/app/webrtc/datachannelinterface.h
new file mode 100644
index 0000000..9c66a50
--- /dev/null
+++ b/talk/app/webrtc/datachannelinterface.h
@@ -0,0 +1,127 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 DataChannels
+// http://dev.w3.org/2011/webrtc/editor/webrtc.html#rtcdatachannel
+
+#ifndef TALK_APP_WEBRTC_DATACHANNELINTERFACE_H_
+#define TALK_APP_WEBRTC_DATACHANNELINTERFACE_H_
+
+#include <string>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/buffer.h"
+#include "talk/base/refcount.h"
+
+
+namespace webrtc {
+
+struct DataChannelInit {
+  DataChannelInit()
+      : reliable(false),
+        ordered(true),
+        maxRetransmitTime(-1),
+        maxRetransmits(-1),
+        negotiated(false),
+        id(-1) {
+  }
+
+  bool reliable;           // Deprecated.
+  bool ordered;            // True if ordered delivery is required.
+  int maxRetransmitTime;   // The max period of time in milliseconds in which
+                           // retransmissions will be sent.  After this time, no
+                           // more retransmissions will be sent. -1 if unset.
+  int maxRetransmits;      // The max number of retransmissions. -1 if unset.
+  std::string protocol;    // This is set by the application and opaque to the
+                           // WebRTC implementation.
+  bool negotiated;         // True if the channel has been externally negotiated
+                           // and we do not send an in-band signalling in the
+                           // form of an "open" message.
+  int id;                  // The stream id, or SID, for SCTP data channels. -1
+                           // if unset.
+};
+
+struct DataBuffer {
+  DataBuffer(const talk_base::Buffer& data, bool binary)
+      : data(data),
+        binary(binary) {
+  }
+  // For convenience for unit tests.
+  explicit DataBuffer(const std::string& text)
+      : data(text.data(), text.length()),
+        binary(false) {
+  }
+  talk_base::Buffer data;
+  // Indicates if the received data contains UTF-8 or binary data.
+  // Note that the upper layers are left to verify the UTF-8 encoding.
+  // TODO(jiayl): prefer to use an enum instead of a bool.
+  bool binary;
+};
+
+class DataChannelObserver {
+ public:
+  // The data channel state have changed.
+  virtual void OnStateChange() = 0;
+  //  A data buffer was successfully received.
+  virtual void OnMessage(const DataBuffer& buffer) = 0;
+
+ protected:
+  virtual ~DataChannelObserver() {}
+};
+
+class DataChannelInterface : public talk_base::RefCountInterface {
+ public:
+  enum DataState {
+    kConnecting,
+    kOpen,  // The DataChannel is ready to send data.
+    kClosing,
+    kClosed
+  };
+
+  virtual void RegisterObserver(DataChannelObserver* observer) = 0;
+  virtual void UnregisterObserver() = 0;
+  // The label attribute represents a label that can be used to distinguish this
+  // DataChannel object from other DataChannel objects.
+  virtual std::string label() const = 0;
+  virtual bool reliable() const = 0;
+  virtual int id() const = 0;
+  virtual DataState state() const = 0;
+  // The buffered_amount returns the number of bytes of application data
+  // (UTF-8 text and binary data) that have been queued using SendBuffer but
+  // have not yet been transmitted to the network.
+  virtual uint64 buffered_amount() const = 0;
+  virtual void Close() = 0;
+  // Sends |data| to the remote peer.
+  virtual bool Send(const DataBuffer& buffer) = 0;
+
+ protected:
+  virtual ~DataChannelInterface() {}
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_DATACHANNELINTERFACE_H_
diff --git a/talk/app/webrtc/dtmfsender.cc b/talk/app/webrtc/dtmfsender.cc
new file mode 100644
index 0000000..6556acd
--- /dev/null
+++ b/talk/app/webrtc/dtmfsender.cc
@@ -0,0 +1,257 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/dtmfsender.h"
+
+#include <ctype.h>
+
+#include <string>
+
+#include "talk/base/logging.h"
+#include "talk/base/thread.h"
+
+namespace webrtc {
+
+enum {
+  MSG_DO_INSERT_DTMF = 0,
+};
+
+// RFC4733
+//  +-------+--------+------+---------+
+//  | Event | Code   | Type | Volume? |
+//  +-------+--------+------+---------+
+//  | 0--9  | 0--9   | tone | yes     |
+//  | *     | 10     | tone | yes     |
+//  | #     | 11     | tone | yes     |
+//  | A--D  | 12--15 | tone | yes     |
+//  +-------+--------+------+---------+
+// The "," is a special event defined by the WebRTC spec. It means to delay for
+// 2 seconds before processing the next tone. We use -1 as its code.
+static const int kDtmfCodeTwoSecondDelay = -1;
+static const int kDtmfTwoSecondInMs = 2000;
+static const char kDtmfValidTones[] = ",0123456789*#ABCDabcd";
+static const char kDtmfTonesTable[] = ",0123456789*#ABCD";
+// The duration cannot be more than 6000ms or less than 70ms. The gap between
+// tones must be at least 50 ms.
+static const int kDtmfDefaultDurationMs = 100;
+static const int kDtmfMinDurationMs = 70;
+static const int kDtmfMaxDurationMs = 6000;
+static const int kDtmfDefaultGapMs = 50;
+static const int kDtmfMinGapMs = 50;
+
+// Get DTMF code from the DTMF event character.
+bool GetDtmfCode(char tone, int* code) {
+  // Convert a-d to A-D.
+  char event = toupper(tone);
+  const char* p = strchr(kDtmfTonesTable, event);
+  if (!p) {
+    return false;
+  }
+  *code = p - kDtmfTonesTable - 1;
+  return true;
+}
+
+talk_base::scoped_refptr<DtmfSender> DtmfSender::Create(
+    AudioTrackInterface* track,
+    talk_base::Thread* signaling_thread,
+    DtmfProviderInterface* provider) {
+  if (!track || !signaling_thread) {
+    return NULL;
+  }
+  talk_base::scoped_refptr<DtmfSender> dtmf_sender(
+      new talk_base::RefCountedObject<DtmfSender>(track, signaling_thread,
+                                                  provider));
+  return dtmf_sender;
+}
+
+DtmfSender::DtmfSender(AudioTrackInterface* track,
+                       talk_base::Thread* signaling_thread,
+                       DtmfProviderInterface* provider)
+    : track_(track),
+      observer_(NULL),
+      signaling_thread_(signaling_thread),
+      provider_(provider),
+      duration_(kDtmfDefaultDurationMs),
+      inter_tone_gap_(kDtmfDefaultGapMs) {
+  ASSERT(track_ != NULL);
+  ASSERT(signaling_thread_ != NULL);
+  if (provider_) {
+    ASSERT(provider_->GetOnDestroyedSignal() != NULL);
+    provider_->GetOnDestroyedSignal()->connect(
+        this, &DtmfSender::OnProviderDestroyed);
+  }
+}
+
+DtmfSender::~DtmfSender() {
+  if (provider_) {
+    ASSERT(provider_->GetOnDestroyedSignal() != NULL);
+    provider_->GetOnDestroyedSignal()->disconnect(this);
+  }
+  StopSending();
+}
+
+void DtmfSender::RegisterObserver(DtmfSenderObserverInterface* observer) {
+  observer_ = observer;
+}
+
+void DtmfSender::UnregisterObserver() {
+  observer_ = NULL;
+}
+
+bool DtmfSender::CanInsertDtmf() {
+  ASSERT(signaling_thread_->IsCurrent());
+  if (!provider_) {
+    return false;
+  }
+  return provider_->CanInsertDtmf(track_->id());
+}
+
+bool DtmfSender::InsertDtmf(const std::string& tones, int duration,
+                            int inter_tone_gap) {
+  ASSERT(signaling_thread_->IsCurrent());
+
+  if (duration > kDtmfMaxDurationMs ||
+      duration < kDtmfMinDurationMs ||
+      inter_tone_gap < kDtmfMinGapMs) {
+    LOG(LS_ERROR) << "InsertDtmf is called with invalid duration or tones gap. "
+        << "The duration cannot be more than " << kDtmfMaxDurationMs
+        << "ms or less than " << kDtmfMinDurationMs << "ms. "
+        << "The gap between tones must be at least " << kDtmfMinGapMs << "ms.";
+    return false;
+  }
+
+  if (!CanInsertDtmf()) {
+    LOG(LS_ERROR)
+        << "InsertDtmf is called on DtmfSender that can't send DTMF.";
+    return false;
+  }
+
+  tones_ = tones;
+  duration_ = duration;
+  inter_tone_gap_ = inter_tone_gap;
+  // Clear the previous queue.
+  signaling_thread_->Clear(this, MSG_DO_INSERT_DTMF);
+  // Kick off a new DTMF task queue.
+  signaling_thread_->Post(this, MSG_DO_INSERT_DTMF);
+  return true;
+}
+
+const AudioTrackInterface* DtmfSender::track() const {
+  return track_;
+}
+
+std::string DtmfSender::tones() const {
+  return tones_;
+}
+
+int DtmfSender::duration() const {
+  return duration_;
+}
+
+int DtmfSender::inter_tone_gap() const {
+  return inter_tone_gap_;
+}
+
+void DtmfSender::OnMessage(talk_base::Message* msg) {
+  switch (msg->message_id) {
+    case MSG_DO_INSERT_DTMF: {
+      DoInsertDtmf();
+      break;
+    }
+    default: {
+      ASSERT(false);
+      break;
+    }
+  }
+}
+
+void DtmfSender::DoInsertDtmf() {
+  ASSERT(signaling_thread_->IsCurrent());
+
+  // Get the first DTMF tone from the tone buffer. Unrecognized characters will
+  // be ignored and skipped.
+  size_t first_tone_pos = tones_.find_first_of(kDtmfValidTones);
+  int code = 0;
+  if (first_tone_pos == std::string::npos) {
+    tones_.clear();
+    // Fire a “OnToneChange” event with an empty string and stop.
+    if (observer_) {
+      observer_->OnToneChange(std::string());
+    }
+    return;
+  } else {
+    char tone = tones_[first_tone_pos];
+    if (!GetDtmfCode(tone, &code)) {
+      // The find_first_of(kDtmfValidTones) should have guarantee |tone| is
+      // a valid DTMF tone.
+      ASSERT(false);
+    }
+  }
+
+  int tone_gap = inter_tone_gap_;
+  if (code == kDtmfCodeTwoSecondDelay) {
+    // Special case defined by WebRTC - The character',' indicates a delay of 2
+    // seconds before processing the next character in the tones parameter.
+    tone_gap = kDtmfTwoSecondInMs;
+  } else {
+    if (!provider_) {
+      LOG(LS_ERROR) << "The DtmfProvider has been destroyed.";
+      return;
+    }
+    // The provider starts playout of the given tone on the
+    // associated RTP media stream, using the appropriate codec.
+    if (!provider_->InsertDtmf(track_->id(), code, duration_)) {
+      LOG(LS_ERROR) << "The DtmfProvider can no longer send DTMF.";
+      return;
+    }
+    // Wait for the number of milliseconds specified by |duration_|.
+    tone_gap += duration_;
+  }
+
+  // Fire a “OnToneChange” event with the tone that's just processed.
+  if (observer_) {
+    observer_->OnToneChange(tones_.substr(first_tone_pos, 1));
+  }
+
+  // Erase the unrecognized characters plus the tone that's just processed.
+  tones_.erase(0, first_tone_pos + 1);
+
+  // Continue with the next tone.
+  signaling_thread_->PostDelayed(tone_gap, this, MSG_DO_INSERT_DTMF);
+}
+
+void DtmfSender::OnProviderDestroyed() {
+  LOG(LS_INFO) << "The Dtmf provider is deleted. Clear the sending queue.";
+  StopSending();
+  provider_ = NULL;
+}
+
+void DtmfSender::StopSending() {
+  signaling_thread_->Clear(this);
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/dtmfsender.h b/talk/app/webrtc/dtmfsender.h
new file mode 100644
index 0000000..f2bebde
--- /dev/null
+++ b/talk/app/webrtc/dtmfsender.h
@@ -0,0 +1,138 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_DTMFSENDER_H_
+#define TALK_APP_WEBRTC_DTMFSENDER_H_
+
+#include <string>
+
+#include "talk/app/webrtc/dtmfsenderinterface.h"
+#include "talk/app/webrtc/mediastreaminterface.h"
+#include "talk/app/webrtc/proxy.h"
+#include "talk/base/common.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/refcount.h"
+
+// DtmfSender is the native implementation of the RTCDTMFSender defined by
+// the WebRTC W3C Editor's Draft.
+// http://dev.w3.org/2011/webrtc/editor/webrtc.html
+
+namespace talk_base {
+class Thread;
+}
+
+namespace webrtc {
+
+// This interface is called by DtmfSender to talk to the actual audio channel
+// to send DTMF.
+class DtmfProviderInterface {
+ public:
+  // Returns true if the audio track with given id (|track_id|) is capable
+  // of sending DTMF. Otherwise returns false.
+  virtual bool CanInsertDtmf(const std::string& track_id) = 0;
+  // Sends DTMF |code| via the audio track with given id (|track_id|).
+  // The |duration| indicates the length of the DTMF tone in ms.
+  // Returns true on success and false on failure.
+  virtual bool InsertDtmf(const std::string& track_id,
+                          int code, int duration) = 0;
+  // Returns a |sigslot::signal0<>| signal. The signal should fire before
+  // the provider is destroyed.
+  virtual sigslot::signal0<>* GetOnDestroyedSignal() = 0;
+
+ protected:
+  virtual ~DtmfProviderInterface() {}
+};
+
+class DtmfSender
+    : public DtmfSenderInterface,
+      public sigslot::has_slots<>,
+      public talk_base::MessageHandler {
+ public:
+  static talk_base::scoped_refptr<DtmfSender> Create(
+      AudioTrackInterface* track,
+      talk_base::Thread* signaling_thread,
+      DtmfProviderInterface* provider);
+
+  // Implements DtmfSenderInterface.
+  virtual void RegisterObserver(DtmfSenderObserverInterface* observer) OVERRIDE;
+  virtual void UnregisterObserver() OVERRIDE;
+  virtual bool CanInsertDtmf() OVERRIDE;
+  virtual bool InsertDtmf(const std::string& tones, int duration,
+                          int inter_tone_gap) OVERRIDE;
+  virtual const AudioTrackInterface* track() const OVERRIDE;
+  virtual std::string tones() const OVERRIDE;
+  virtual int duration() const OVERRIDE;
+  virtual int inter_tone_gap() const OVERRIDE;
+
+ protected:
+  DtmfSender(AudioTrackInterface* track,
+             talk_base::Thread* signaling_thread,
+             DtmfProviderInterface* provider);
+  virtual ~DtmfSender();
+
+ private:
+  DtmfSender();
+
+  // Implements MessageHandler.
+  virtual void OnMessage(talk_base::Message* msg);
+
+  // The DTMF sending task.
+  void DoInsertDtmf();
+
+  void OnProviderDestroyed();
+
+  void StopSending();
+
+  talk_base::scoped_refptr<AudioTrackInterface> track_;
+  DtmfSenderObserverInterface* observer_;
+  talk_base::Thread* signaling_thread_;
+  DtmfProviderInterface* provider_;
+  std::string tones_;
+  int duration_;
+  int inter_tone_gap_;
+
+  DISALLOW_COPY_AND_ASSIGN(DtmfSender);
+};
+
+// Define proxy for DtmfSenderInterface.
+BEGIN_PROXY_MAP(DtmfSender)
+  PROXY_METHOD1(void, RegisterObserver, DtmfSenderObserverInterface*)
+  PROXY_METHOD0(void, UnregisterObserver)
+  PROXY_METHOD0(bool, CanInsertDtmf)
+  PROXY_METHOD3(bool, InsertDtmf, const std::string&, int, int)
+  PROXY_CONSTMETHOD0(const AudioTrackInterface*, track)
+  PROXY_CONSTMETHOD0(std::string, tones)
+  PROXY_CONSTMETHOD0(int, duration)
+  PROXY_CONSTMETHOD0(int, inter_tone_gap)
+END_PROXY()
+
+// Get DTMF code from the DTMF event character.
+bool GetDtmfCode(char tone, int* code);
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_DTMFSENDER_H_
diff --git a/talk/app/webrtc/dtmfsender_unittest.cc b/talk/app/webrtc/dtmfsender_unittest.cc
new file mode 100644
index 0000000..e1c3be9
--- /dev/null
+++ b/talk/app/webrtc/dtmfsender_unittest.cc
@@ -0,0 +1,356 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/dtmfsender.h"
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "talk/app/webrtc/audiotrack.h"
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/timeutils.h"
+
+using webrtc::AudioTrackInterface;
+using webrtc::AudioTrack;
+using webrtc::DtmfProviderInterface;
+using webrtc::DtmfSender;
+using webrtc::DtmfSenderObserverInterface;
+
+static const char kTestAudioLabel[] = "test_audio_track";
+static const int kMaxWaitMs = 3000;
+
+class FakeDtmfObserver : public DtmfSenderObserverInterface {
+ public:
+  FakeDtmfObserver() : completed_(false) {}
+
+  // Implements DtmfSenderObserverInterface.
+  virtual void OnToneChange(const std::string& tone) OVERRIDE {
+    LOG(LS_VERBOSE) << "FakeDtmfObserver::OnToneChange '" << tone << "'.";
+    tones_.push_back(tone);
+    if (tone.empty()) {
+      completed_ = true;
+    }
+  }
+
+  // getters
+  const std::vector<std::string>& tones() const {
+    return tones_;
+  }
+  bool completed() const {
+    return completed_;
+  }
+
+ private:
+  std::vector<std::string> tones_;
+  bool completed_;
+};
+
+class FakeDtmfProvider : public DtmfProviderInterface {
+ public:
+  struct DtmfInfo {
+    DtmfInfo(int code, int duration, int gap)
+      : code(code),
+        duration(duration),
+        gap(gap) {}
+    int code;
+    int duration;
+    int gap;
+  };
+
+  FakeDtmfProvider() : last_insert_dtmf_call_(0) {}
+
+  ~FakeDtmfProvider() {
+    SignalDestroyed();
+  }
+
+  // Implements DtmfProviderInterface.
+  virtual bool CanInsertDtmf(const std::string&  track_label) OVERRIDE {
+    return (can_insert_dtmf_tracks_.count(track_label) != 0);
+  }
+
+  virtual bool InsertDtmf(const std::string& track_label,
+                          int code, int duration) OVERRIDE {
+    int gap = 0;
+    // TODO(ronghuawu): Make the timer (basically the talk_base::TimeNanos)
+    // mockable and use a fake timer in the unit tests.
+    if (last_insert_dtmf_call_ > 0) {
+      gap = static_cast<int>(talk_base::Time() - last_insert_dtmf_call_);
+    }
+    last_insert_dtmf_call_ = talk_base::Time();
+
+    LOG(LS_VERBOSE) << "FakeDtmfProvider::InsertDtmf code=" << code
+                    << " duration=" << duration
+                    << " gap=" << gap << ".";
+    dtmf_info_queue_.push_back(DtmfInfo(code, duration, gap));
+    return true;
+  }
+
+  virtual sigslot::signal0<>* GetOnDestroyedSignal() {
+    return &SignalDestroyed;
+  }
+
+  // getter and setter
+  const std::vector<DtmfInfo>& dtmf_info_queue() const {
+    return dtmf_info_queue_;
+  }
+
+  // helper functions
+  void AddCanInsertDtmfTrack(const std::string& label) {
+    can_insert_dtmf_tracks_.insert(label);
+  }
+  void RemoveCanInsertDtmfTrack(const std::string& label) {
+    can_insert_dtmf_tracks_.erase(label);
+  }
+
+ private:
+  std::set<std::string> can_insert_dtmf_tracks_;
+  std::vector<DtmfInfo> dtmf_info_queue_;
+  int64 last_insert_dtmf_call_;
+  sigslot::signal0<> SignalDestroyed;
+};
+
+class DtmfSenderTest : public testing::Test {
+ protected:
+  DtmfSenderTest()
+      : track_(AudioTrack::Create(kTestAudioLabel, NULL)),
+        observer_(new talk_base::RefCountedObject<FakeDtmfObserver>()),
+        provider_(new FakeDtmfProvider()) {
+    provider_->AddCanInsertDtmfTrack(kTestAudioLabel);
+    dtmf_ = DtmfSender::Create(track_, talk_base::Thread::Current(),
+                               provider_.get());
+    dtmf_->RegisterObserver(observer_.get());
+  }
+
+  ~DtmfSenderTest() {
+    if (dtmf_.get()) {
+      dtmf_->UnregisterObserver();
+    }
+  }
+
+  // Constructs a list of DtmfInfo from |tones|, |duration| and
+  // |inter_tone_gap|.
+  void GetDtmfInfoFromString(const std::string& tones, int duration,
+                             int inter_tone_gap,
+                             std::vector<FakeDtmfProvider::DtmfInfo>* dtmfs) {
+    // Init extra_delay as -inter_tone_gap - duration to ensure the first
+    // DtmfInfo's gap field will be 0.
+    int extra_delay = -1 * (inter_tone_gap + duration);
+
+    std::string::const_iterator it = tones.begin();
+    for (; it != tones.end(); ++it) {
+      char tone = *it;
+      int code = 0;
+      webrtc::GetDtmfCode(tone, &code);
+      if (tone == ',') {
+        extra_delay = 2000;  // 2 seconds
+      } else {
+        dtmfs->push_back(FakeDtmfProvider::DtmfInfo(code, duration,
+                         duration + inter_tone_gap + extra_delay));
+        extra_delay = 0;
+      }
+    }
+  }
+
+  void VerifyExpectedState(AudioTrackInterface* track,
+                          const std::string& tones,
+                          int duration, int inter_tone_gap) {
+    EXPECT_EQ(track, dtmf_->track());
+    EXPECT_EQ(tones, dtmf_->tones());
+    EXPECT_EQ(duration, dtmf_->duration());
+    EXPECT_EQ(inter_tone_gap, dtmf_->inter_tone_gap());
+  }
+
+  // Verify the provider got all the expected calls.
+  void VerifyOnProvider(const std::string& tones, int duration,
+                        int inter_tone_gap) {
+    std::vector<FakeDtmfProvider::DtmfInfo> dtmf_queue_ref;
+    GetDtmfInfoFromString(tones, duration, inter_tone_gap, &dtmf_queue_ref);
+    VerifyOnProvider(dtmf_queue_ref);
+  }
+
+  void VerifyOnProvider(
+      const std::vector<FakeDtmfProvider::DtmfInfo>& dtmf_queue_ref) {
+    const std::vector<FakeDtmfProvider::DtmfInfo>& dtmf_queue =
+        provider_->dtmf_info_queue();
+    ASSERT_EQ(dtmf_queue_ref.size(), dtmf_queue.size());
+    std::vector<FakeDtmfProvider::DtmfInfo>::const_iterator it_ref =
+        dtmf_queue_ref.begin();
+    std::vector<FakeDtmfProvider::DtmfInfo>::const_iterator it =
+        dtmf_queue.begin();
+    while (it_ref != dtmf_queue_ref.end() && it != dtmf_queue.end()) {
+      EXPECT_EQ(it_ref->code, it->code);
+      EXPECT_EQ(it_ref->duration, it->duration);
+      // Allow ~20ms error.
+      EXPECT_GE(it_ref->gap, it->gap - 20);
+      EXPECT_LE(it_ref->gap, it->gap + 20);
+      ++it_ref;
+      ++it;
+    }
+  }
+
+  // Verify the observer got all the expected callbacks.
+  void VerifyOnObserver(const std::string& tones_ref) {
+    const std::vector<std::string>& tones = observer_->tones();
+    // The observer will get an empty string at the end.
+    EXPECT_EQ(tones_ref.size() + 1, tones.size());
+    EXPECT_TRUE(tones.back().empty());
+    std::string::const_iterator it_ref = tones_ref.begin();
+    std::vector<std::string>::const_iterator it = tones.begin();
+    while (it_ref != tones_ref.end() && it != tones.end()) {
+      EXPECT_EQ(*it_ref, it->at(0));
+      ++it_ref;
+      ++it;
+    }
+  }
+
+  talk_base::scoped_refptr<AudioTrackInterface> track_;
+  talk_base::scoped_ptr<FakeDtmfObserver> observer_;
+  talk_base::scoped_ptr<FakeDtmfProvider> provider_;
+  talk_base::scoped_refptr<DtmfSender> dtmf_;
+};
+
+TEST_F(DtmfSenderTest, CanInsertDtmf) {
+  EXPECT_TRUE(dtmf_->CanInsertDtmf());
+  provider_->RemoveCanInsertDtmfTrack(kTestAudioLabel);
+  EXPECT_FALSE(dtmf_->CanInsertDtmf());
+}
+
+TEST_F(DtmfSenderTest, InsertDtmf) {
+  std::string tones = "@1%a&*$";
+  int duration = 100;
+  int inter_tone_gap = 50;
+  EXPECT_TRUE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap));
+  EXPECT_TRUE_WAIT(observer_->completed(), kMaxWaitMs);
+
+  // The unrecognized characters should be ignored.
+  std::string known_tones = "1a*";
+  VerifyOnProvider(known_tones, duration, inter_tone_gap);
+  VerifyOnObserver(known_tones);
+}
+
+TEST_F(DtmfSenderTest, InsertDtmfTwice) {
+  std::string tones1 = "12";
+  std::string tones2 = "ab";
+  int duration = 100;
+  int inter_tone_gap = 50;
+  EXPECT_TRUE(dtmf_->InsertDtmf(tones1, duration, inter_tone_gap));
+  VerifyExpectedState(track_, tones1, duration, inter_tone_gap);
+  // Wait until the first tone got sent.
+  EXPECT_TRUE_WAIT(observer_->tones().size() == 1, kMaxWaitMs);
+  VerifyExpectedState(track_, "2", duration, inter_tone_gap);
+  // Insert with another tone buffer.
+  EXPECT_TRUE(dtmf_->InsertDtmf(tones2, duration, inter_tone_gap));
+  VerifyExpectedState(track_, tones2, duration, inter_tone_gap);
+  // Wait until it's completed.
+  EXPECT_TRUE_WAIT(observer_->completed(), kMaxWaitMs);
+
+  std::vector<FakeDtmfProvider::DtmfInfo> dtmf_queue_ref;
+  GetDtmfInfoFromString("1", duration, inter_tone_gap, &dtmf_queue_ref);
+  GetDtmfInfoFromString("ab", duration, inter_tone_gap, &dtmf_queue_ref);
+  VerifyOnProvider(dtmf_queue_ref);
+  VerifyOnObserver("1ab");
+}
+
+TEST_F(DtmfSenderTest, InsertDtmfWhileProviderIsDeleted) {
+  std::string tones = "@1%a&*$";
+  int duration = 100;
+  int inter_tone_gap = 50;
+  EXPECT_TRUE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap));
+  // Wait until the first tone got sent.
+  EXPECT_TRUE_WAIT(observer_->tones().size() == 1, kMaxWaitMs);
+  // Delete provider.
+  provider_.reset();
+  // The queue should be discontinued so no more tone callbacks.
+  WAIT(false, 200);
+  EXPECT_EQ(1U, observer_->tones().size());
+}
+
+TEST_F(DtmfSenderTest, InsertDtmfWhileSenderIsDeleted) {
+  std::string tones = "@1%a&*$";
+  int duration = 100;
+  int inter_tone_gap = 50;
+  EXPECT_TRUE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap));
+  // Wait until the first tone got sent.
+  EXPECT_TRUE_WAIT(observer_->tones().size() == 1, kMaxWaitMs);
+  // Delete the sender.
+  dtmf_ = NULL;
+  // The queue should be discontinued so no more tone callbacks.
+  WAIT(false, 200);
+  EXPECT_EQ(1U, observer_->tones().size());
+}
+
+TEST_F(DtmfSenderTest, InsertEmptyTonesToCancelPreviousTask) {
+  std::string tones1 = "12";
+  std::string tones2 = "";
+  int duration = 100;
+  int inter_tone_gap = 50;
+  EXPECT_TRUE(dtmf_->InsertDtmf(tones1, duration, inter_tone_gap));
+  // Wait until the first tone got sent.
+  EXPECT_TRUE_WAIT(observer_->tones().size() == 1, kMaxWaitMs);
+  // Insert with another tone buffer.
+  EXPECT_TRUE(dtmf_->InsertDtmf(tones2, duration, inter_tone_gap));
+  // Wait until it's completed.
+  EXPECT_TRUE_WAIT(observer_->completed(), kMaxWaitMs);
+
+  std::vector<FakeDtmfProvider::DtmfInfo> dtmf_queue_ref;
+  GetDtmfInfoFromString("1", duration, inter_tone_gap, &dtmf_queue_ref);
+  VerifyOnProvider(dtmf_queue_ref);
+  VerifyOnObserver("1");
+}
+
+TEST_F(DtmfSenderTest, InsertDtmfWithCommaAsDelay) {
+  std::string tones = "3,4";
+  int duration = 100;
+  int inter_tone_gap = 50;
+  EXPECT_TRUE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap));
+  EXPECT_TRUE_WAIT(observer_->completed(), kMaxWaitMs);
+
+  VerifyOnProvider(tones, duration, inter_tone_gap);
+  VerifyOnObserver(tones);
+}
+
+TEST_F(DtmfSenderTest, TryInsertDtmfWhenItDoesNotWork) {
+  std::string tones = "3,4";
+  int duration = 100;
+  int inter_tone_gap = 50;
+  provider_->RemoveCanInsertDtmfTrack(kTestAudioLabel);
+  EXPECT_FALSE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap));
+}
+
+TEST_F(DtmfSenderTest, InsertDtmfWithInvalidDurationOrGap) {
+  std::string tones = "3,4";
+  int duration = 100;
+  int inter_tone_gap = 50;
+
+  EXPECT_FALSE(dtmf_->InsertDtmf(tones, 6001, inter_tone_gap));
+  EXPECT_FALSE(dtmf_->InsertDtmf(tones, 69, inter_tone_gap));
+  EXPECT_FALSE(dtmf_->InsertDtmf(tones, duration, 49));
+
+  EXPECT_TRUE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap));
+}
diff --git a/talk/app/webrtc/dtmfsenderinterface.h b/talk/app/webrtc/dtmfsenderinterface.h
new file mode 100644
index 0000000..46f3924
--- /dev/null
+++ b/talk/app/webrtc/dtmfsenderinterface.h
@@ -0,0 +1,105 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_DTMFSENDERINTERFACE_H_
+#define TALK_APP_WEBRTC_DTMFSENDERINTERFACE_H_
+
+#include <string>
+
+#include "talk/app/webrtc/mediastreaminterface.h"
+#include "talk/base/common.h"
+#include "talk/base/refcount.h"
+
+// This file contains interfaces for DtmfSender.
+
+namespace webrtc {
+
+// DtmfSender callback interface. Application should implement this interface
+// to get notifications from the DtmfSender.
+class DtmfSenderObserverInterface {
+ public:
+  // Triggered when DTMF |tone| is sent.
+  // If |tone| is empty that means the DtmfSender has sent out all the given
+  // tones.
+  virtual void OnToneChange(const std::string& tone) = 0;
+
+ protected:
+  virtual ~DtmfSenderObserverInterface() {}
+};
+
+// The interface of native implementation of the RTCDTMFSender defined by the
+// WebRTC W3C Editor's Draft.
+class DtmfSenderInterface : public talk_base::RefCountInterface {
+ public:
+  virtual void RegisterObserver(DtmfSenderObserverInterface* observer) = 0;
+  virtual void UnregisterObserver() = 0;
+
+  // Returns true if this DtmfSender is capable of sending DTMF.
+  // Otherwise returns false.
+  virtual bool CanInsertDtmf() = 0;
+
+  // Queues a task that sends the DTMF |tones|. The |tones| parameter is treated
+  // as a series of characters. The characters 0 through 9, A through D, #, and
+  // * generate the associated DTMF tones. The characters a to d are equivalent
+  // to A to D. The character ',' indicates a delay of 2 seconds before
+  // processing the next character in the tones parameter.
+  // Unrecognized characters are ignored.
+  // The |duration| parameter indicates the duration in ms to use for each
+  // character passed in the |tones| parameter.
+  // The duration cannot be more than 6000 or less than 70.
+  // The |inter_tone_gap| parameter indicates the gap between tones in ms.
+  // The |inter_tone_gap| must be at least 50 ms but should be as short as
+  // possible.
+  // If InsertDtmf is called on the same object while an existing task for this
+  // object to generate DTMF is still running, the previous task is canceled.
+  // Returns true on success and false on failure.
+  virtual bool InsertDtmf(const std::string& tones, int duration,
+                          int inter_tone_gap) = 0;
+
+  // Returns the track given as argument to the constructor.
+  virtual const AudioTrackInterface* track() const = 0;
+
+  // Returns the tones remaining to be played out.
+  virtual std::string tones() const = 0;
+
+  // Returns the current tone duration value in ms.
+  // This value will be the value last set via the InsertDtmf() method, or the
+  // default value of 100 ms if InsertDtmf() was never called.
+  virtual int duration() const = 0;
+
+  // Returns the current value of the between-tone gap in ms.
+  // This value will be the value last set via the InsertDtmf() method, or the
+  // default value of 50 ms if InsertDtmf() was never called.
+  virtual int inter_tone_gap() const = 0;
+
+ protected:
+  virtual ~DtmfSenderInterface() {}
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_DTMFSENDERINTERFACE_H_
diff --git a/talk/app/webrtc/fakeportallocatorfactory.h b/talk/app/webrtc/fakeportallocatorfactory.h
new file mode 100644
index 0000000..c1727ae
--- /dev/null
+++ b/talk/app/webrtc/fakeportallocatorfactory.h
@@ -0,0 +1,74 @@
+/*
+ * 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/peerconnectioninterface.h"
+#include "talk/p2p/client/fakeportallocator.h"
+
+namespace webrtc {
+
+class FakePortAllocatorFactory : public PortAllocatorFactoryInterface {
+ public:
+  static FakePortAllocatorFactory* 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) {
+    stun_configs_ = stun_configurations;
+    turn_configs_ = turn_configurations;
+    return new cricket::FakePortAllocator(talk_base::Thread::Current(), NULL);
+  }
+
+  const std::vector<StunConfiguration>& stun_configs() const {
+    return stun_configs_;
+  }
+
+  const std::vector<TurnConfiguration>& turn_configs() const {
+    return turn_configs_;
+  }
+
+ protected:
+  FakePortAllocatorFactory() {}
+  ~FakePortAllocatorFactory() {}
+
+ private:
+  std::vector<PortAllocatorFactoryInterface::StunConfiguration> stun_configs_;
+  std::vector<PortAllocatorFactoryInterface::TurnConfiguration> turn_configs_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_FAKEPORTALLOCATORFACTORY_H_
diff --git a/talk/app/webrtc/java/README b/talk/app/webrtc/java/README
new file mode 100644
index 0000000..454046c
--- /dev/null
+++ b/talk/app/webrtc/java/README
@@ -0,0 +1,23 @@
+This directory holds a Java implementation of the webrtc::PeerConnection API, as
+well as the JNI glue C++ code that lets the Java implementation reuse the C++
+implementation of the same API.
+
+To build the Java API and related tests, build with 
+OS=linux or OS=android and include
+build_with_libjingle=1 build_with_chromium=0
+in $GYP_DEFINES.
+
+To use the Java API, start by looking at the public interface of
+org.webrtc.PeerConnection{,Factory} and the org.webrtc.PeerConnectionTest.
+
+To understand the implementation of the API, see the native code in jni/.
+
+An example command-line to build & run the unittest:
+cd path/to/trunk
+GYP_DEFINES="build_with_libjingle=1 build_with_chromium=0 java_home=path/to/JDK" gclient runhooks && \
+    ninja -C out/Debug libjingle_peerconnection_java_unittest && \
+    ./out/Debug/libjingle_peerconnection_java_unittest
+(where path/to/JDK should contain include/jni.h)
+
+During development it can be helpful to run the JVM with the -Xcheck:jni flag.
+
diff --git a/talk/app/webrtc/java/jni/peerconnection_jni.cc b/talk/app/webrtc/java/jni/peerconnection_jni.cc
new file mode 100644
index 0000000..6b5a6a4
--- /dev/null
+++ b/talk/app/webrtc/java/jni/peerconnection_jni.cc
@@ -0,0 +1,1359 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Hints for future visitors:
+// This entire file is an implementation detail of the org.webrtc Java package,
+// the most interesting bits of which are org.webrtc.PeerConnection{,Factory}.
+// The layout of this file is roughly:
+// - various helper C++ functions & classes that wrap Java counterparts and
+//   expose a C++ interface that can be passed to the C++ PeerConnection APIs
+// - implementations of methods declared "static" in the Java package (named
+//   things like Java_org_webrtc_OMG_Can_This_Name_Be_Any_Longer, prescribed by
+//   the JNI spec).
+//
+// Lifecycle notes: objects are owned where they will be called; in other words
+// FooObservers are owned by C++-land, and user-callable objects (e.g.
+// PeerConnection and VideoTrack) are owned by Java-land.
+// When this file allocates C++ RefCountInterfaces it AddRef()s an artificial
+// ref simulating the jlong held in Java-land, and then Release()s the ref in
+// the respective free call.  Sometimes this AddRef is implicit in the
+// construction of a scoped_refptr<> which is then .release()d.
+// Any persistent (non-local) references from C++ to Java must be global or weak
+// (in which case they must be checked before use)!
+//
+// Exception notes: pretty much all JNI calls can throw Java exceptions, so each
+// call through a JNIEnv* pointer needs to be followed by an ExceptionCheck()
+// call.  In this file this is done in CHECK_EXCEPTION, making for much easier
+// debugging in case of failure (the alternative is to wait for control to
+// return to the Java frame that called code in this file, at which point it's
+// impossible to tell which JNI call broke).
+
+#include <jni.h>
+#undef JNIEXPORT
+#define JNIEXPORT __attribute__((visibility("default")))
+
+#include <map>
+
+#include "talk/app/webrtc/mediaconstraintsinterface.h"
+#include "talk/app/webrtc/peerconnectioninterface.h"
+#include "talk/app/webrtc/videosourceinterface.h"
+#include "talk/base/logging.h"
+#include "talk/base/ssladapter.h"
+#include "talk/media/base/videocapturer.h"
+#include "talk/media/base/videorenderer.h"
+#include "talk/media/devices/videorendererfactory.h"
+#include "talk/media/webrtc/webrtcvideocapturer.h"
+#include "third_party/icu/public/common/unicode/unistr.h"
+#include "third_party/webrtc/system_wrappers/interface/trace.h"
+#include "third_party/webrtc/video_engine/include/vie_base.h"
+#include "third_party/webrtc/voice_engine/include/voe_base.h"
+
+using icu::UnicodeString;
+using webrtc::AudioSourceInterface;
+using webrtc::AudioTrackInterface;
+using webrtc::AudioTrackVector;
+using webrtc::CreateSessionDescriptionObserver;
+using webrtc::IceCandidateInterface;
+using webrtc::MediaConstraintsInterface;
+using webrtc::MediaSourceInterface;
+using webrtc::MediaStreamInterface;
+using webrtc::MediaStreamTrackInterface;
+using webrtc::PeerConnectionFactoryInterface;
+using webrtc::PeerConnectionInterface;
+using webrtc::PeerConnectionObserver;
+using webrtc::SessionDescriptionInterface;
+using webrtc::SetSessionDescriptionObserver;
+using webrtc::StatsObserver;
+using webrtc::StatsReport;
+using webrtc::VideoRendererInterface;
+using webrtc::VideoSourceInterface;
+using webrtc::VideoTrackInterface;
+using webrtc::VideoTrackVector;
+using webrtc::VideoRendererInterface;
+
+// Abort the process if |x| is false, emitting |msg|.
+#define CHECK(x, msg)                                                          \
+  if (x) {} else {                                                             \
+    LOG(LS_ERROR) << __FILE__ << ":" << __LINE__ << ": " << msg;               \
+    abort();                                                                   \
+  }
+// Abort the process if |jni| has a Java exception pending, emitting |msg|.
+#define CHECK_EXCEPTION(jni, msg)                                              \
+  if (0) {} else {                                                             \
+    if (jni->ExceptionCheck()) {                                               \
+      jni->ExceptionDescribe();                                                \
+      jni->ExceptionClear();                                                   \
+      CHECK(0, msg);                                                           \
+    }                                                                          \
+  }
+
+namespace {
+
+static JavaVM* g_jvm = NULL;  // Set in JNI_OnLoad().
+
+static pthread_once_t g_jni_ptr_once = PTHREAD_ONCE_INIT;
+static pthread_key_t g_jni_ptr;  // Key for per-thread JNIEnv* data.
+
+static void ThreadDestructor(void* unused) {
+  jint status = g_jvm->DetachCurrentThread();
+  CHECK(status == JNI_OK, "Failed to detach thread: " << status);
+}
+
+static void CreateJNIPtrKey() {
+  CHECK(!pthread_key_create(&g_jni_ptr, &ThreadDestructor),
+        "pthread_key_create");
+}
+
+// Deal with difference in signatures between Oracle's jni.h and Android's.
+static JNIEnv* AttachCurrentThreadIfNeeded() {
+  CHECK(!pthread_once(&g_jni_ptr_once, &CreateJNIPtrKey),
+        "pthread_once");
+  JNIEnv* jni = reinterpret_cast<JNIEnv*>(pthread_getspecific(g_jni_ptr));
+  if (jni == NULL) {
+#ifdef _JAVASOFT_JNI_H_  // Oracle's jni.h violates the JNI spec!
+    void* env;
+#else
+    JNIEnv* env;
+#endif
+    CHECK(!g_jvm->AttachCurrentThread(&env, NULL), "Failed to attach thread");
+    CHECK(env, "AttachCurrentThread handed back NULL!");
+    jni = reinterpret_cast<JNIEnv*>(env);
+    CHECK(!pthread_setspecific(g_jni_ptr, jni), "pthread_setspecific");
+  }
+  return jni;
+}
+
+// Android's FindClass() is trickier than usual because the app-specific
+// ClassLoader is not consulted when there is no app-specific frame on the
+// stack.  Consequently, we only look up classes once in JNI_OnLoad.
+// http://developer.android.com/training/articles/perf-jni.html#faq_FindClass
+class ClassReferenceHolder {
+ public:
+  explicit ClassReferenceHolder(JNIEnv* jni) {
+    LoadClass(jni, "java/nio/ByteBuffer");
+    LoadClass(jni, "org/webrtc/AudioTrack");
+    LoadClass(jni, "org/webrtc/IceCandidate");
+    LoadClass(jni, "org/webrtc/MediaSource$State");
+    LoadClass(jni, "org/webrtc/MediaStream");
+    LoadClass(jni, "org/webrtc/MediaStreamTrack$State");
+    LoadClass(jni, "org/webrtc/PeerConnection$SignalingState");
+    LoadClass(jni, "org/webrtc/PeerConnection$IceConnectionState");
+    LoadClass(jni, "org/webrtc/PeerConnection$IceGatheringState");
+    LoadClass(jni, "org/webrtc/SessionDescription");
+    LoadClass(jni, "org/webrtc/SessionDescription$Type");
+    LoadClass(jni, "org/webrtc/StatsReport");
+    LoadClass(jni, "org/webrtc/StatsReport$Value");
+    LoadClass(jni, "org/webrtc/VideoRenderer$I420Frame");
+    LoadClass(jni, "org/webrtc/VideoTrack");
+  }
+
+  ~ClassReferenceHolder() {
+    CHECK(classes_.empty(), "Must call FreeReferences() before dtor!");
+  }
+
+  void FreeReferences(JNIEnv* jni) {
+    for (std::map<std::string, jclass>::const_iterator it = classes_.begin();
+         it != classes_.end(); ++it) {
+      jni->DeleteGlobalRef(it->second);
+    }
+    classes_.clear();
+  }
+
+  jclass GetClass(const std::string& name) {
+    std::map<std::string, jclass>::iterator it = classes_.find(name);
+    CHECK(it != classes_.end(), "Unexpected GetClass() call for: " << name);
+    return it->second;
+  }
+
+ private:
+  void LoadClass(JNIEnv* jni, const std::string& name) {
+    jclass localRef = jni->FindClass(name.c_str());
+    CHECK_EXCEPTION(jni, "error during FindClass: " << name);
+    CHECK(localRef, name);
+    jclass globalRef = reinterpret_cast<jclass>(jni->NewGlobalRef(localRef));
+    CHECK_EXCEPTION(jni, "error during NewGlobalRef: " << name);
+    CHECK(globalRef, name);
+    bool inserted = classes_.insert(std::make_pair(name, globalRef)).second;
+    CHECK(inserted, "Duplicate class name: " << name);
+  }
+
+  std::map<std::string, jclass> classes_;
+};
+
+// Allocated in JNI_OnLoad(), freed in JNI_OnUnLoad().
+static ClassReferenceHolder* g_class_reference_holder = NULL;
+
+// JNIEnv-helper methods that CHECK success: no Java exception thrown and found
+// object/class/method/field is non-null.
+jmethodID GetMethodID(
+    JNIEnv* jni, jclass c, const std::string& name, const char* signature) {
+  jmethodID m = jni->GetMethodID(c, name.c_str(), signature);
+  CHECK_EXCEPTION(jni,
+                  "error during GetMethodID: " << name << ", " << signature);
+  CHECK(m, name << ", " << signature);
+  return m;
+}
+
+jmethodID GetStaticMethodID(
+    JNIEnv* jni, jclass c, const char* name, const char* signature) {
+  jmethodID m = jni->GetStaticMethodID(c, name, signature);
+  CHECK_EXCEPTION(jni,
+                  "error during GetStaticMethodID: "
+                  << name << ", " << signature);
+  CHECK(m, name << ", " << signature);
+  return m;
+}
+
+jfieldID GetFieldID(
+    JNIEnv* jni, jclass c, const char* name, const char* signature) {
+  jfieldID f = jni->GetFieldID(c, name, signature);
+  CHECK_EXCEPTION(jni, "error during GetFieldID");
+  CHECK(f, name << ", " << signature);
+  return f;
+}
+
+jclass FindClass(JNIEnv* jni, const char* name) {
+  return g_class_reference_holder->GetClass(name);
+}
+
+jclass GetObjectClass(JNIEnv* jni, jobject object) {
+  jclass c = jni->GetObjectClass(object);
+  CHECK_EXCEPTION(jni, "error during GetObjectClass");
+  CHECK(c, "");
+  return c;
+}
+
+jobject GetObjectField(JNIEnv* jni, jobject object, jfieldID id) {
+  jobject o = jni->GetObjectField(object, id);
+  CHECK_EXCEPTION(jni, "error during GetObjectField");
+  CHECK(o, "");
+  return o;
+}
+
+jlong GetLongField(JNIEnv* jni, jobject object, jfieldID id) {
+  jlong l = jni->GetLongField(object, id);
+  CHECK_EXCEPTION(jni, "error during GetLongField");
+  CHECK(l, "");
+  return l;
+}
+
+jobject NewGlobalRef(JNIEnv* jni, jobject o) {
+  jobject ret = jni->NewGlobalRef(o);
+  CHECK_EXCEPTION(jni, "error during NewGlobalRef");
+  CHECK(ret, "");
+  return ret;
+}
+
+void DeleteGlobalRef(JNIEnv* jni, jobject o) {
+  jni->DeleteGlobalRef(o);
+  CHECK_EXCEPTION(jni, "error during DeleteGlobalRef");
+}
+
+// Given a jweak reference, allocate a (strong) local reference scoped to the
+// lifetime of this object if the weak reference is still valid, or NULL
+// otherwise.
+class WeakRef {
+ public:
+  WeakRef(JNIEnv* jni, jweak ref)
+      : jni_(jni), obj_(jni_->NewLocalRef(ref)) {
+    CHECK_EXCEPTION(jni, "error during NewLocalRef");
+  }
+  ~WeakRef() {
+    if (obj_) {
+      jni_->DeleteLocalRef(obj_);
+      CHECK_EXCEPTION(jni_, "error during DeleteLocalRef");
+    }
+  }
+  jobject obj() { return obj_; }
+
+ private:
+  JNIEnv* const jni_;
+  jobject const obj_;
+};
+
+// Given a local ref, take ownership of it and delete the ref when this goes out
+// of scope.
+template<class T>  // T is jclass, jobject, jintArray, etc.
+class ScopedLocalRef {
+ public:
+  ScopedLocalRef(JNIEnv* jni, T obj)
+      : jni_(jni), obj_(obj) {}
+  ~ScopedLocalRef() {
+    jni_->DeleteLocalRef(obj_);
+  }
+  T operator*() const {
+    return obj_;
+  }
+ private:
+  JNIEnv* jni_;
+  T obj_;
+};
+
+// Scoped holder for global Java refs.
+class ScopedGlobalRef {
+ public:
+  explicit ScopedGlobalRef(JNIEnv* jni, jobject obj)
+      : obj_(jni->NewGlobalRef(obj)) {}
+  ~ScopedGlobalRef() {
+    DeleteGlobalRef(AttachCurrentThreadIfNeeded(), obj_);
+  }
+  jobject operator*() const {
+    return obj_;
+  }
+ private:
+  jobject obj_;
+};
+
+// Return the (singleton) Java Enum object corresponding to |index|;
+// |state_class_fragment| is something like "MediaSource$State".
+jobject JavaEnumFromIndex(
+    JNIEnv* jni, const std::string& state_class_fragment, int index) {
+  std::string state_class_name = "org/webrtc/" + state_class_fragment;
+  jclass state_class = FindClass(jni, state_class_name.c_str());
+  jmethodID state_values_id = GetStaticMethodID(
+      jni, state_class, "values", ("()[L" + state_class_name  + ";").c_str());
+  ScopedLocalRef<jobjectArray> state_values(
+      jni,
+      (jobjectArray)jni->CallStaticObjectMethod(state_class, state_values_id));
+  CHECK_EXCEPTION(jni, "error during CallStaticObjectMethod");
+  jobject ret = jni->GetObjectArrayElement(*state_values, index);
+  CHECK_EXCEPTION(jni, "error during GetObjectArrayElement");
+  return ret;
+}
+
+// Given a UTF-8 encoded |native| string return a new (UTF-16) jstring.
+static jstring JavaStringFromStdString(JNIEnv* jni, const std::string& native) {
+  UnicodeString ustr(UnicodeString::fromUTF8(native));
+  jstring jstr = jni->NewString(ustr.getBuffer(), ustr.length());
+  CHECK_EXCEPTION(jni, "error during NewString");
+  return jstr;
+}
+
+// Given a (UTF-16) jstring return a new UTF-8 native string.
+static std::string JavaToStdString(JNIEnv* jni, const jstring& j_string) {
+  const jchar* jchars = jni->GetStringChars(j_string, NULL);
+  CHECK_EXCEPTION(jni, "Error during GetStringChars");
+  UnicodeString ustr(jchars, jni->GetStringLength(j_string));
+  CHECK_EXCEPTION(jni, "Error during GetStringLength");
+  jni->ReleaseStringChars(j_string, jchars);
+  CHECK_EXCEPTION(jni, "Error during ReleaseStringChars");
+  std::string ret;
+  return ustr.toUTF8String(ret);
+}
+
+class ConstraintsWrapper;
+
+// Adapter between the C++ PeerConnectionObserver interface and the Java
+// PeerConnection.Observer interface.  Wraps an instance of the Java interface
+// and dispatches C++ callbacks to Java.
+class PCOJava : public PeerConnectionObserver {
+ public:
+  PCOJava(JNIEnv* jni, jobject j_observer)
+      : j_observer_global_(jni, j_observer),
+        j_observer_class_((jclass)NewGlobalRef(
+            jni, GetObjectClass(jni, *j_observer_global_))),
+        j_media_stream_class_((jclass)NewGlobalRef(
+            jni, FindClass(jni, "org/webrtc/MediaStream"))),
+        j_media_stream_ctor_(GetMethodID(jni,
+            j_media_stream_class_, "<init>", "(J)V")),
+        j_audio_track_class_((jclass)NewGlobalRef(
+            jni, FindClass(jni, "org/webrtc/AudioTrack"))),
+        j_audio_track_ctor_(GetMethodID(
+            jni, j_audio_track_class_, "<init>", "(J)V")),
+        j_video_track_class_((jclass)NewGlobalRef(
+            jni, FindClass(jni, "org/webrtc/VideoTrack"))),
+        j_video_track_ctor_(GetMethodID(jni,
+            j_video_track_class_, "<init>", "(J)V")) {
+  }
+
+  virtual ~PCOJava() {}
+
+  virtual void OnIceCandidate(const IceCandidateInterface* candidate) {
+    std::string sdp;
+    CHECK(candidate->ToString(&sdp), "got so far: " << sdp);
+    jclass candidate_class = FindClass(jni(), "org/webrtc/IceCandidate");
+    jmethodID ctor = GetMethodID(jni(), candidate_class,
+        "<init>", "(Ljava/lang/String;ILjava/lang/String;)V");
+    ScopedLocalRef<jstring> j_mid(
+        jni(), JavaStringFromStdString(jni(), candidate->sdp_mid()));
+    ScopedLocalRef<jstring> j_sdp(jni(), JavaStringFromStdString(jni(), sdp));
+    ScopedLocalRef<jobject> j_candidate(jni(), jni()->NewObject(
+        candidate_class, ctor, *j_mid, candidate->sdp_mline_index(), *j_sdp));
+    CHECK_EXCEPTION(jni(), "error during NewObject");
+    jmethodID m = GetMethodID(jni(), j_observer_class_,
+                              "onIceCandidate", "(Lorg/webrtc/IceCandidate;)V");
+    jni()->CallVoidMethod(*j_observer_global_, m, *j_candidate);
+    CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
+  }
+
+  virtual void OnError() {
+    jmethodID m = GetMethodID(jni(), j_observer_class_, "onError", "(V)V");
+    jni()->CallVoidMethod(*j_observer_global_, m);
+    CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
+  }
+
+  virtual void OnSignalingChange(
+      PeerConnectionInterface::SignalingState new_state) {
+    jmethodID m = GetMethodID(
+        jni(), j_observer_class_, "onSignalingChange",
+        "(Lorg/webrtc/PeerConnection$SignalingState;)V");
+    ScopedLocalRef<jobject> new_state_enum(jni(), JavaEnumFromIndex(
+        jni(), "PeerConnection$SignalingState", new_state));
+    jni()->CallVoidMethod(*j_observer_global_, m, *new_state_enum);
+    CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
+  }
+
+  virtual void OnIceConnectionChange(
+      PeerConnectionInterface::IceConnectionState new_state) {
+    jmethodID m = GetMethodID(
+        jni(), j_observer_class_, "onIceConnectionChange",
+        "(Lorg/webrtc/PeerConnection$IceConnectionState;)V");
+    ScopedLocalRef<jobject> new_state_enum(jni(), JavaEnumFromIndex(
+        jni(), "PeerConnection$IceConnectionState", new_state));
+    jni()->CallVoidMethod(*j_observer_global_, m, *new_state_enum);
+    CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
+  }
+
+  virtual void OnIceGatheringChange(
+      PeerConnectionInterface::IceGatheringState new_state) {
+    jmethodID m = GetMethodID(
+        jni(), j_observer_class_, "onIceGatheringChange",
+        "(Lorg/webrtc/PeerConnection$IceGatheringState;)V");
+    ScopedLocalRef<jobject> new_state_enum(jni(), JavaEnumFromIndex(
+        jni(), "PeerConnection$IceGatheringState", new_state));
+    jni()->CallVoidMethod(*j_observer_global_, m, *new_state_enum);
+    CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
+  }
+
+  virtual void OnAddStream(MediaStreamInterface* stream) {
+    ScopedLocalRef<jobject> j_stream(jni(), jni()->NewObject(
+        j_media_stream_class_, j_media_stream_ctor_, (jlong)stream));
+    CHECK_EXCEPTION(jni(), "error during NewObject");
+
+    AudioTrackVector audio_tracks = stream->GetAudioTracks();
+    for (size_t i = 0; i < audio_tracks.size(); ++i) {
+      AudioTrackInterface* track = audio_tracks[i];
+      ScopedLocalRef<jstring> id(
+          jni(), JavaStringFromStdString(jni(), track->id()));
+      ScopedLocalRef<jobject> j_track(jni(), jni()->NewObject(
+          j_audio_track_class_, j_audio_track_ctor_, (jlong)track, *id));
+      CHECK_EXCEPTION(jni(), "error during NewObject");
+      jfieldID audio_tracks_id = GetFieldID(jni(),
+          j_media_stream_class_, "audioTracks", "Ljava/util/List;");
+      ScopedLocalRef<jobject> audio_tracks(jni(), GetObjectField(
+          jni(), *j_stream, audio_tracks_id));
+      jmethodID add = GetMethodID(jni(),
+          GetObjectClass(jni(), *audio_tracks), "add", "(Ljava/lang/Object;)Z");
+      jboolean added = jni()->CallBooleanMethod(*audio_tracks, add, *j_track);
+      CHECK_EXCEPTION(jni(), "error during CallBooleanMethod");
+      CHECK(added, "");
+    }
+
+    VideoTrackVector video_tracks = stream->GetVideoTracks();
+    for (size_t i = 0; i < video_tracks.size(); ++i) {
+      VideoTrackInterface* track = video_tracks[i];
+      ScopedLocalRef<jstring> id(
+          jni(), JavaStringFromStdString(jni(), track->id()));
+      ScopedLocalRef<jobject> j_track(jni(), jni()->NewObject(
+          j_video_track_class_, j_video_track_ctor_, (jlong)track, *id));
+      CHECK_EXCEPTION(jni(), "error during NewObject");
+      jfieldID video_tracks_id = GetFieldID(jni(),
+          j_media_stream_class_, "videoTracks", "Ljava/util/List;");
+      ScopedLocalRef<jobject> video_tracks(jni(), GetObjectField(
+          jni(), *j_stream, video_tracks_id));
+      jmethodID add = GetMethodID(jni(),
+          GetObjectClass(jni(), *video_tracks), "add", "(Ljava/lang/Object;)Z");
+      jboolean added = jni()->CallBooleanMethod(*video_tracks, add, *j_track);
+      CHECK_EXCEPTION(jni(), "error during CallBooleanMethod");
+      CHECK(added, "");
+    }
+    streams_[stream] = jni()->NewWeakGlobalRef(*j_stream);
+    CHECK_EXCEPTION(jni(), "error during NewWeakGlobalRef");
+
+    jmethodID m = GetMethodID(jni(),
+        j_observer_class_, "onAddStream", "(Lorg/webrtc/MediaStream;)V");
+    jni()->CallVoidMethod(*j_observer_global_, m, *j_stream);
+    CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
+  }
+
+  virtual void OnRemoveStream(MediaStreamInterface* stream) {
+    NativeToJavaStreamsMap::iterator it = streams_.find(stream);
+    CHECK(it != streams_.end(), "unexpected stream: " << std::hex << stream);
+
+    WeakRef s(jni(), it->second);
+    streams_.erase(it);
+    if (!s.obj())
+      return;
+
+    jmethodID m = GetMethodID(jni(),
+        j_observer_class_, "onRemoveStream", "(Lorg/webrtc/MediaStream;)V");
+    jni()->CallVoidMethod(*j_observer_global_, m, s.obj());
+    CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
+  }
+
+  void SetConstraints(ConstraintsWrapper* constraints) {
+    CHECK(!constraints_.get(), "constraints already set!");
+    constraints_.reset(constraints);
+  }
+
+  const ConstraintsWrapper* constraints() { return constraints_.get(); }
+
+ private:
+  JNIEnv* jni() {
+    return AttachCurrentThreadIfNeeded();
+  }
+
+  const ScopedGlobalRef j_observer_global_;
+  const jclass j_observer_class_;
+  const jclass j_media_stream_class_;
+  const jmethodID j_media_stream_ctor_;
+  const jclass j_audio_track_class_;
+  const jmethodID j_audio_track_ctor_;
+  const jclass j_video_track_class_;
+  const jmethodID j_video_track_ctor_;
+  typedef std::map<void*, jweak> NativeToJavaStreamsMap;
+  NativeToJavaStreamsMap streams_;  // C++ -> Java streams.
+  talk_base::scoped_ptr<ConstraintsWrapper> constraints_;
+};
+
+// Wrapper for a Java MediaConstraints object.  Copies all needed data so when
+// the constructor returns the Java object is no longer needed.
+class ConstraintsWrapper : public MediaConstraintsInterface {
+ public:
+  ConstraintsWrapper(JNIEnv* jni, jobject j_constraints) {
+    PopulateConstraintsFromJavaPairList(
+        jni, j_constraints, "mandatory", &mandatory_);
+    PopulateConstraintsFromJavaPairList(
+        jni, j_constraints, "optional", &optional_);
+  }
+
+  virtual ~ConstraintsWrapper() {}
+
+  // MediaConstraintsInterface.
+  virtual const Constraints& GetMandatory() const { return mandatory_; }
+  virtual const Constraints& GetOptional() const { return optional_; }
+
+ private:
+  // Helper for translating a List<Pair<String, String>> to a Constraints.
+  static void PopulateConstraintsFromJavaPairList(
+      JNIEnv* jni, jobject j_constraints,
+      const char* field_name, Constraints* field) {
+    jfieldID j_id = GetFieldID(jni,
+        GetObjectClass(jni, j_constraints), field_name, "Ljava/util/List;");
+    jobject j_list = GetObjectField(jni, j_constraints, j_id);
+    jmethodID j_iterator_id = GetMethodID(jni,
+        GetObjectClass(jni, j_list), "iterator", "()Ljava/util/Iterator;");
+    jobject j_iterator = jni->CallObjectMethod(j_list, j_iterator_id);
+    CHECK_EXCEPTION(jni, "error during CallObjectMethod");
+    jmethodID j_has_next = GetMethodID(jni,
+        GetObjectClass(jni, j_iterator), "hasNext", "()Z");
+    jmethodID j_next = GetMethodID(jni,
+        GetObjectClass(jni, j_iterator), "next", "()Ljava/lang/Object;");
+    while (jni->CallBooleanMethod(j_iterator, j_has_next)) {
+      CHECK_EXCEPTION(jni, "error during CallBooleanMethod");
+      jobject entry = jni->CallObjectMethod(j_iterator, j_next);
+      CHECK_EXCEPTION(jni, "error during CallObjectMethod");
+      jmethodID get_key = GetMethodID(jni,
+          GetObjectClass(jni, entry), "getKey", "()Ljava/lang/String;");
+      jstring j_key = reinterpret_cast<jstring>(
+          jni->CallObjectMethod(entry, get_key));
+      CHECK_EXCEPTION(jni, "error during CallObjectMethod");
+      jmethodID get_value = GetMethodID(jni,
+          GetObjectClass(jni, entry), "getValue", "()Ljava/lang/String;");
+      jstring j_value = reinterpret_cast<jstring>(
+          jni->CallObjectMethod(entry, get_value));
+      CHECK_EXCEPTION(jni, "error during CallObjectMethod");
+      field->push_back(Constraint(JavaToStdString(jni, j_key),
+                                  JavaToStdString(jni, j_value)));
+    }
+    CHECK_EXCEPTION(jni, "error during CallBooleanMethod");
+  }
+
+  Constraints mandatory_;
+  Constraints optional_;
+};
+
+static jobject JavaSdpFromNativeSdp(
+    JNIEnv* jni, const SessionDescriptionInterface* desc) {
+  std::string sdp;
+  CHECK(desc->ToString(&sdp), "got so far: " << sdp);
+  ScopedLocalRef<jstring> j_description(jni, JavaStringFromStdString(jni, sdp));
+
+  jclass j_type_class = FindClass(
+      jni, "org/webrtc/SessionDescription$Type");
+  jmethodID j_type_from_canonical = GetStaticMethodID(
+      jni, j_type_class, "fromCanonicalForm",
+      "(Ljava/lang/String;)Lorg/webrtc/SessionDescription$Type;");
+  ScopedLocalRef<jstring> j_type_string(
+      jni, JavaStringFromStdString(jni, desc->type()));
+  jobject j_type = jni->CallStaticObjectMethod(
+      j_type_class, j_type_from_canonical, *j_type_string);
+  CHECK_EXCEPTION(jni, "error during CallObjectMethod");
+
+  jclass j_sdp_class = FindClass(jni, "org/webrtc/SessionDescription");
+  jmethodID j_sdp_ctor = GetMethodID(
+      jni, j_sdp_class, "<init>",
+      "(Lorg/webrtc/SessionDescription$Type;Ljava/lang/String;)V");
+  jobject j_sdp = jni->NewObject(
+      j_sdp_class, j_sdp_ctor, j_type, *j_description);
+  CHECK_EXCEPTION(jni, "error during NewObject");
+  return j_sdp;
+}
+
+template <class T>  // T is one of {Create,Set}SessionDescriptionObserver.
+class SdpObserverWrapper : public T {
+ public:
+  SdpObserverWrapper(JNIEnv* jni, jobject j_observer,
+                     ConstraintsWrapper* constraints)
+      : constraints_(constraints),
+        j_observer_global_(NewGlobalRef(jni, j_observer)),
+        j_observer_class_((jclass)NewGlobalRef(
+            jni, GetObjectClass(jni, j_observer))) {
+  }
+
+  virtual ~SdpObserverWrapper() {
+    DeleteGlobalRef(jni(), j_observer_global_);
+    DeleteGlobalRef(jni(), j_observer_class_);
+  }
+
+  virtual void OnSuccess() {
+    jmethodID m = GetMethodID(jni(), j_observer_class_, "onSetSuccess", "()V");
+    jni()->CallVoidMethod(j_observer_global_, m);
+    CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
+  }
+
+  virtual void OnSuccess(SessionDescriptionInterface* desc) {
+    jmethodID m = GetMethodID(
+        jni(), j_observer_class_, "onCreateSuccess",
+        "(Lorg/webrtc/SessionDescription;)V");
+    ScopedLocalRef<jobject> j_sdp(jni(), JavaSdpFromNativeSdp(jni(), desc));
+    jni()->CallVoidMethod(j_observer_global_, m, *j_sdp);
+    CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
+  }
+
+ protected:
+  // Common implementation for failure of Set & Create types, distinguished by
+  // |op| being "Set" or "Create".
+  void OnFailure(const std::string& op, const std::string& error) {
+    jmethodID m = GetMethodID(jni(),
+        j_observer_class_, "on" + op + "Failure", "(Ljava/lang/String;)V");
+    ScopedLocalRef<jstring> j_error_string(
+        jni(), JavaStringFromStdString(jni(), error));
+    jni()->CallVoidMethod(j_observer_global_, m, *j_error_string);
+    CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
+  }
+
+ private:
+  JNIEnv* jni() {
+    return AttachCurrentThreadIfNeeded();
+  }
+
+  talk_base::scoped_ptr<ConstraintsWrapper> constraints_;
+  const jobject j_observer_global_;
+  const jclass j_observer_class_;
+};
+
+class CreateSdpObserverWrapper
+    : public SdpObserverWrapper<CreateSessionDescriptionObserver> {
+ public:
+  CreateSdpObserverWrapper(JNIEnv* jni, jobject j_observer,
+                           ConstraintsWrapper* constraints)
+      : SdpObserverWrapper(jni, j_observer, constraints) {}
+
+  virtual void OnFailure(const std::string& error) {
+    SdpObserverWrapper::OnFailure(std::string("Create"), error);
+  }
+};
+
+class SetSdpObserverWrapper
+    : public SdpObserverWrapper<SetSessionDescriptionObserver> {
+ public:
+  SetSdpObserverWrapper(JNIEnv* jni, jobject j_observer,
+                        ConstraintsWrapper* constraints)
+      : SdpObserverWrapper(jni, j_observer, constraints) {}
+
+  virtual void OnFailure(const std::string& error) {
+    SdpObserverWrapper::OnFailure(std::string("Set"), error);
+  }
+};
+
+// Adapter for a Java StatsObserver presenting a C++ StatsObserver and
+// dispatching the callback from C++ back to Java.
+class StatsObserverWrapper : public StatsObserver {
+ public:
+  StatsObserverWrapper(JNIEnv* jni, jobject j_observer)
+      : j_observer_global_(NewGlobalRef(jni, j_observer)),
+        j_observer_class_((jclass)NewGlobalRef(
+            jni, GetObjectClass(jni, j_observer))),
+        j_stats_report_class_(FindClass(jni, "org/webrtc/StatsReport")),
+        j_stats_report_ctor_(GetMethodID(
+            jni, j_stats_report_class_, "<init>",
+            "(Ljava/lang/String;Ljava/lang/String;D"
+            "[Lorg/webrtc/StatsReport$Value;)V")),
+        j_value_class_(FindClass(
+            jni, "org/webrtc/StatsReport$Value")),
+        j_value_ctor_(GetMethodID(
+            jni, j_value_class_, "<init>",
+            "(Ljava/lang/String;Ljava/lang/String;)V")) {
+  }
+
+  virtual ~StatsObserverWrapper() {
+    DeleteGlobalRef(jni(), j_observer_global_);
+    DeleteGlobalRef(jni(), j_observer_class_);
+  }
+
+  virtual void OnComplete(const std::vector<StatsReport>& reports) {
+    ScopedLocalRef<jobjectArray> j_reports(jni(),
+                                           ReportsToJava(jni(), reports));
+    jmethodID m = GetMethodID(
+        jni(), j_observer_class_, "onComplete", "([Lorg/webrtc/StatsReport;)V");
+    jni()->CallVoidMethod(j_observer_global_, m, *j_reports);
+    CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
+  }
+
+ private:
+  jobjectArray ReportsToJava(
+      JNIEnv* jni, const std::vector<StatsReport>& reports) {
+    jobjectArray reports_array = jni->NewObjectArray(
+        reports.size(), j_stats_report_class_, NULL);
+    for (int i = 0; i < reports.size(); ++i) {
+      const StatsReport& report = reports[i];
+      ScopedLocalRef<jstring> j_id(
+          jni, JavaStringFromStdString(jni, report.id));
+      ScopedLocalRef<jstring> j_type(
+          jni, JavaStringFromStdString(jni, report.type));
+      ScopedLocalRef<jobjectArray> j_values(
+          jni, ValuesToJava(jni, report.values));
+      ScopedLocalRef<jobject> j_report(jni, jni->NewObject(
+          j_stats_report_class_, j_stats_report_ctor_, *j_id, *j_type,
+          report.timestamp, *j_values));
+      jni->SetObjectArrayElement(reports_array, i, *j_report);
+    }
+    return reports_array;
+  }
+
+  jobjectArray ValuesToJava(JNIEnv* jni, const StatsReport::Values& values) {
+    jobjectArray j_values = jni->NewObjectArray(
+        values.size(), j_value_class_, NULL);
+    for (int i = 0; i < values.size(); ++i) {
+      const StatsReport::Value& value = values[i];
+      ScopedLocalRef<jstring> j_name(
+          jni, JavaStringFromStdString(jni, value.name));
+      ScopedLocalRef<jstring> j_value(
+          jni, JavaStringFromStdString(jni, value.value));
+      ScopedLocalRef<jobject> j_element_value(jni, jni->NewObject(
+          j_value_class_, j_value_ctor_, *j_name, *j_value));
+      jni->SetObjectArrayElement(j_values, i, *j_element_value);
+    }
+    return j_values;
+  }
+
+  JNIEnv* jni() {
+    return AttachCurrentThreadIfNeeded();
+  }
+
+  const jobject j_observer_global_;
+  const jclass j_observer_class_;
+  const jclass j_stats_report_class_;
+  const jmethodID j_stats_report_ctor_;
+  const jclass j_value_class_;
+  const jmethodID j_value_ctor_;
+};
+
+// Adapter presenting a cricket::VideoRenderer as a
+// webrtc::VideoRendererInterface.
+class VideoRendererWrapper : public VideoRendererInterface {
+ public:
+  static VideoRendererWrapper* Create(cricket::VideoRenderer* renderer) {
+    if (renderer)
+      return new VideoRendererWrapper(renderer);
+    return NULL;
+  }
+
+  virtual ~VideoRendererWrapper() {}
+
+  virtual void SetSize(int width, int height) {
+    const bool kNotReserved = false;  // What does this param mean??
+    renderer_->SetSize(width, height, kNotReserved);
+  }
+
+  virtual void RenderFrame(const cricket::VideoFrame* frame) {
+    renderer_->RenderFrame(frame);
+  }
+
+ private:
+  explicit VideoRendererWrapper(cricket::VideoRenderer* renderer)
+      : renderer_(renderer) {}
+
+  talk_base::scoped_ptr<cricket::VideoRenderer> renderer_;
+};
+
+// Wrapper dispatching webrtc::VideoRendererInterface to a Java VideoRenderer
+// instance.
+class JavaVideoRendererWrapper : public VideoRendererInterface {
+ public:
+  JavaVideoRendererWrapper(JNIEnv* jni, jobject j_callbacks)
+      : j_callbacks_(jni, j_callbacks) {
+    j_set_size_id_ = GetMethodID(
+        jni, GetObjectClass(jni, j_callbacks), "setSize", "(II)V");
+    j_render_frame_id_ = GetMethodID(
+        jni, GetObjectClass(jni, j_callbacks), "renderFrame",
+        "(Lorg/webrtc/VideoRenderer$I420Frame;)V");
+    j_frame_class_ = FindClass(jni, "org/webrtc/VideoRenderer$I420Frame");
+    j_frame_ctor_id_ = GetMethodID(
+        jni, j_frame_class_, "<init>", "(II[I[Ljava/nio/ByteBuffer;)V");
+    j_byte_buffer_class_ = FindClass(jni, "java/nio/ByteBuffer");
+    CHECK_EXCEPTION(jni, "");
+  }
+
+  virtual void SetSize(int width, int height) {
+    jni()->CallVoidMethod(*j_callbacks_, j_set_size_id_, width, height);
+    CHECK_EXCEPTION(jni(), "");
+  }
+
+  virtual void RenderFrame(const cricket::VideoFrame* frame) {
+    ScopedLocalRef<jobject> j_frame(jni(), CricketToJavaFrame(frame));
+    jni()->CallVoidMethod(*j_callbacks_, j_render_frame_id_, *j_frame);
+    CHECK_EXCEPTION(jni(), "");
+  }
+
+ private:
+  // Return a VideoRenderer.I420Frame referring to the data in |frame|.
+  jobject CricketToJavaFrame(const cricket::VideoFrame* frame) {
+    ScopedLocalRef<jintArray> strides(jni(), jni()->NewIntArray(3));
+    jint* strides_array = jni()->GetIntArrayElements(*strides, NULL);
+    strides_array[0] = frame->GetYPitch();
+    strides_array[1] = frame->GetUPitch();
+    strides_array[2] = frame->GetVPitch();
+    jni()->ReleaseIntArrayElements(*strides, strides_array, 0);
+    ScopedLocalRef<jobjectArray> planes(
+        jni(), jni()->NewObjectArray(3, j_byte_buffer_class_, NULL));
+    ScopedLocalRef<jobject> y_buffer(jni(), jni()->NewDirectByteBuffer(
+        const_cast<uint8*>(frame->GetYPlane()),
+        frame->GetYPitch() * frame->GetHeight()));
+    ScopedLocalRef<jobject> u_buffer(jni(), jni()->NewDirectByteBuffer(
+        const_cast<uint8*>(frame->GetUPlane()), frame->GetChromaSize()));
+    ScopedLocalRef<jobject> v_buffer(jni(), jni()->NewDirectByteBuffer(
+        const_cast<uint8*>(frame->GetVPlane()), frame->GetChromaSize()));
+    jni()->SetObjectArrayElement(*planes, 0, *y_buffer);
+    jni()->SetObjectArrayElement(*planes, 1, *u_buffer);
+    jni()->SetObjectArrayElement(*planes, 2, *v_buffer);
+    return jni()->NewObject(
+        j_frame_class_, j_frame_ctor_id_,
+        frame->GetWidth(), frame->GetHeight(), *strides, *planes);
+  }
+
+  JNIEnv* jni() {
+    return AttachCurrentThreadIfNeeded();
+  }
+
+  ScopedGlobalRef j_callbacks_;
+  jmethodID j_set_size_id_;
+  jmethodID j_render_frame_id_;
+  jclass j_frame_class_;
+  jmethodID j_frame_ctor_id_;
+  jclass j_byte_buffer_class_;
+};
+
+}  // anonymous namespace
+
+
+// Convenience macro defining JNI-accessible methods in the org.webrtc package.
+// Eliminates unnecessary boilerplate and line-wraps, reducing visual clutter.
+#define JOW(rettype, name) extern "C" rettype JNIEXPORT JNICALL \
+  Java_org_webrtc_##name
+
+extern "C" jint JNIEXPORT JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
+  CHECK(!g_jvm, "JNI_OnLoad called more than once!");
+  g_jvm = jvm;
+  CHECK(g_jvm, "JNI_OnLoad handed NULL?");
+
+  CHECK(talk_base::InitializeSSL(), "Failed to InitializeSSL()");
+
+  JNIEnv* jni;
+  if (jvm->GetEnv(reinterpret_cast<void**>(&jni), JNI_VERSION_1_6) != JNI_OK)
+    return -1;
+  g_class_reference_holder = new ClassReferenceHolder(jni);
+
+#ifdef ANDROID
+  webrtc::Trace::CreateTrace();
+  CHECK(!webrtc::Trace::SetTraceFile("/sdcard/trace.txt", false),
+        "SetTraceFile failed");
+  CHECK(!webrtc::Trace::SetLevelFilter(webrtc::kTraceAll),
+        "SetLevelFilter failed");
+#endif  // ANDROID
+
+  // Uncomment to get sensitive logs emitted (to stderr or logcat).
+  // talk_base::LogMessage::LogToDebug(talk_base::LS_SENSITIVE);
+
+  return JNI_VERSION_1_6;
+}
+
+extern "C" void JNIEXPORT JNICALL JNI_OnUnLoad(JavaVM *jvm, void *reserved) {
+  webrtc::Trace::ReturnTrace();
+  delete g_class_reference_holder;
+  g_class_reference_holder = NULL;
+  CHECK(talk_base::CleanupSSL(), "Failed to CleanupSSL()");
+}
+
+JOW(void, PeerConnection_freePeerConnection)(JNIEnv*, jclass, jlong j_p) {
+  reinterpret_cast<PeerConnectionInterface*>(j_p)->Release();
+}
+
+JOW(void, PeerConnection_freeObserver)(JNIEnv*, jclass, jlong j_p) {
+  PCOJava* p = reinterpret_cast<PCOJava*>(j_p);
+  delete p;
+}
+
+JOW(void, MediaSource_free)(JNIEnv*, jclass, jlong j_p) {
+  reinterpret_cast<MediaSourceInterface*>(j_p)->Release();
+}
+
+JOW(void, VideoCapturer_free)(JNIEnv*, jclass, jlong j_p) {
+  delete reinterpret_cast<cricket::VideoCapturer*>(j_p);
+}
+
+JOW(void, VideoRenderer_free)(JNIEnv*, jclass, jlong j_p) {
+  delete reinterpret_cast<VideoRendererWrapper*>(j_p);
+}
+
+JOW(void, MediaStreamTrack_free)(JNIEnv*, jclass, jlong j_p) {
+  reinterpret_cast<MediaStreamTrackInterface*>(j_p)->Release();
+}
+
+JOW(jboolean, MediaStream_nativeAddAudioTrack)(
+    JNIEnv* jni, jclass, jlong pointer, jlong j_audio_track_pointer) {
+  talk_base::scoped_refptr<MediaStreamInterface> stream(
+      reinterpret_cast<MediaStreamInterface*>(pointer));
+  talk_base::scoped_refptr<AudioTrackInterface> track(
+      reinterpret_cast<AudioTrackInterface*>(j_audio_track_pointer));
+  return stream->AddTrack(track);
+}
+
+JOW(jboolean, MediaStream_nativeAddVideoTrack)(
+    JNIEnv* jni, jclass, jlong pointer, jlong j_video_track_pointer) {
+  talk_base::scoped_refptr<MediaStreamInterface> stream(
+      reinterpret_cast<MediaStreamInterface*>(pointer));
+  talk_base::scoped_refptr<VideoTrackInterface> track(
+      reinterpret_cast<VideoTrackInterface*>(j_video_track_pointer));
+  return stream->AddTrack(track);
+}
+
+JOW(jboolean, MediaStream_nativeRemoveAudioTrack)(
+    JNIEnv* jni, jclass, jlong pointer, jlong j_audio_track_pointer) {
+  talk_base::scoped_refptr<MediaStreamInterface> stream(
+      reinterpret_cast<MediaStreamInterface*>(pointer));
+  talk_base::scoped_refptr<AudioTrackInterface> track(
+      reinterpret_cast<AudioTrackInterface*>(j_audio_track_pointer));
+  return stream->RemoveTrack(track);
+}
+
+JOW(jboolean, MediaStream_nativeRemoveVideoTrack)(
+    JNIEnv* jni, jclass, jlong pointer, jlong j_video_track_pointer) {
+  talk_base::scoped_refptr<MediaStreamInterface> stream(
+      reinterpret_cast<MediaStreamInterface*>(pointer));
+  talk_base::scoped_refptr<VideoTrackInterface> track(
+      reinterpret_cast<VideoTrackInterface*>(j_video_track_pointer));
+  return stream->RemoveTrack(track);
+}
+
+JOW(jstring, MediaStream_nativeLabel)(JNIEnv* jni, jclass, jlong j_p) {
+  return JavaStringFromStdString(
+      jni, reinterpret_cast<MediaStreamInterface*>(j_p)->label());
+}
+
+JOW(void, MediaStream_free)(JNIEnv*, jclass, jlong j_p) {
+  reinterpret_cast<MediaStreamInterface*>(j_p)->Release();
+}
+
+JOW(jlong, PeerConnectionFactory_nativeCreateObserver)(
+    JNIEnv * jni, jclass, jobject j_observer) {
+  return (jlong)new PCOJava(jni, j_observer);
+}
+
+#ifdef ANDROID
+JOW(jboolean, PeerConnectionFactory_initializeAndroidGlobals)(
+    JNIEnv* jni, jclass, jobject context) {
+  CHECK(g_jvm, "JNI_OnLoad failed to run?");
+  bool failure = false;
+  failure |= webrtc::VideoEngine::SetAndroidObjects(g_jvm, context);
+  failure |= webrtc::VoiceEngine::SetAndroidObjects(g_jvm, jni, context);
+  return !failure;
+}
+#endif  // ANDROID
+
+JOW(jlong, PeerConnectionFactory_nativeCreatePeerConnectionFactory)(
+    JNIEnv* jni, jclass) {
+  talk_base::scoped_refptr<PeerConnectionFactoryInterface> factory(
+      webrtc::CreatePeerConnectionFactory());
+  return (jlong)factory.release();
+}
+
+JOW(void, PeerConnectionFactory_freeFactory)(JNIEnv*, jclass, jlong j_p) {
+  reinterpret_cast<PeerConnectionFactoryInterface*>(j_p)->Release();
+}
+
+JOW(jlong, PeerConnectionFactory_nativeCreateLocalMediaStream)(
+    JNIEnv* jni, jclass, jlong native_factory, jstring label) {
+  talk_base::scoped_refptr<PeerConnectionFactoryInterface> factory(
+      reinterpret_cast<PeerConnectionFactoryInterface*>(native_factory));
+  talk_base::scoped_refptr<MediaStreamInterface> stream(
+      factory->CreateLocalMediaStream(JavaToStdString(jni, label)));
+  return (jlong)stream.release();
+}
+
+JOW(jlong, PeerConnectionFactory_nativeCreateVideoSource)(
+    JNIEnv* jni, jclass, jlong native_factory, jlong native_capturer,
+    jobject j_constraints) {
+  talk_base::scoped_ptr<ConstraintsWrapper> constraints(
+      new ConstraintsWrapper(jni, j_constraints));
+  talk_base::scoped_refptr<PeerConnectionFactoryInterface> factory(
+      reinterpret_cast<PeerConnectionFactoryInterface*>(native_factory));
+  talk_base::scoped_refptr<VideoSourceInterface> source(
+      factory->CreateVideoSource(
+          reinterpret_cast<cricket::VideoCapturer*>(native_capturer),
+          constraints.get()));
+  return (jlong)source.release();
+}
+
+JOW(jlong, PeerConnectionFactory_nativeCreateVideoTrack)(
+    JNIEnv* jni, jclass, jlong native_factory, jstring id,
+    jlong native_source) {
+  talk_base::scoped_refptr<PeerConnectionFactoryInterface> factory(
+      reinterpret_cast<PeerConnectionFactoryInterface*>(native_factory));
+  talk_base::scoped_refptr<VideoTrackInterface> track(
+      factory->CreateVideoTrack(
+          JavaToStdString(jni, id),
+          reinterpret_cast<VideoSourceInterface*>(native_source)));
+  return (jlong)track.release();
+}
+
+JOW(jlong, PeerConnectionFactory_nativeCreateAudioTrack)(
+    JNIEnv* jni, jclass, jlong native_factory, jstring id) {
+  talk_base::scoped_refptr<PeerConnectionFactoryInterface> factory(
+      reinterpret_cast<PeerConnectionFactoryInterface*>(native_factory));
+  talk_base::scoped_refptr<AudioTrackInterface> track(
+      factory->CreateAudioTrack(JavaToStdString(jni, id), NULL));
+  return (jlong)track.release();
+}
+
+static void JavaIceServersToJsepIceServers(
+    JNIEnv* jni, jobject j_ice_servers,
+    PeerConnectionInterface::IceServers* ice_servers) {
+  jclass list_class = GetObjectClass(jni, j_ice_servers);
+  jmethodID iterator_id = GetMethodID(
+      jni, list_class, "iterator", "()Ljava/util/Iterator;");
+  jobject iterator = jni->CallObjectMethod(j_ice_servers, iterator_id);
+  CHECK_EXCEPTION(jni, "error during CallObjectMethod");
+  jmethodID iterator_has_next = GetMethodID(
+      jni, GetObjectClass(jni, iterator), "hasNext", "()Z");
+  jmethodID iterator_next = GetMethodID(
+      jni, GetObjectClass(jni, iterator), "next", "()Ljava/lang/Object;");
+  while (jni->CallBooleanMethod(iterator, iterator_has_next)) {
+    CHECK_EXCEPTION(jni, "error during CallBooleanMethod");
+    jobject j_ice_server = jni->CallObjectMethod(iterator, iterator_next);
+    CHECK_EXCEPTION(jni, "error during CallObjectMethod");
+    jclass j_ice_server_class = GetObjectClass(jni, j_ice_server);
+    jfieldID j_ice_server_uri_id =
+        GetFieldID(jni, j_ice_server_class, "uri", "Ljava/lang/String;");
+    jfieldID j_ice_server_username_id =
+        GetFieldID(jni, j_ice_server_class, "username", "Ljava/lang/String;");
+    jfieldID j_ice_server_password_id =
+        GetFieldID(jni, j_ice_server_class, "password", "Ljava/lang/String;");
+    jstring uri = reinterpret_cast<jstring>(
+        GetObjectField(jni, j_ice_server, j_ice_server_uri_id));
+    jstring username = reinterpret_cast<jstring>(
+        GetObjectField(jni, j_ice_server, j_ice_server_username_id));
+    jstring password = reinterpret_cast<jstring>(
+        GetObjectField(jni, j_ice_server, j_ice_server_password_id));
+    PeerConnectionInterface::IceServer server;
+    server.uri = JavaToStdString(jni, uri);
+    server.username = JavaToStdString(jni, username);
+    server.password = JavaToStdString(jni, password);
+    ice_servers->push_back(server);
+  }
+  CHECK_EXCEPTION(jni, "error during CallBooleanMethod");
+}
+
+JOW(jlong, PeerConnectionFactory_nativeCreatePeerConnection)(
+    JNIEnv *jni, jclass, jlong factory, jobject j_ice_servers,
+    jobject j_constraints, jlong observer_p) {
+  talk_base::scoped_refptr<PeerConnectionFactoryInterface> f(
+      reinterpret_cast<PeerConnectionFactoryInterface*>(factory));
+  PeerConnectionInterface::IceServers servers;
+  JavaIceServersToJsepIceServers(jni, j_ice_servers, &servers);
+  PCOJava* observer = reinterpret_cast<PCOJava*>(observer_p);
+  observer->SetConstraints(new ConstraintsWrapper(jni, j_constraints));
+  talk_base::scoped_refptr<PeerConnectionInterface> pc(f->CreatePeerConnection(
+      servers, observer->constraints(), NULL, observer));
+  return (jlong)pc.release();
+}
+
+static talk_base::scoped_refptr<PeerConnectionInterface> ExtractNativePC(
+    JNIEnv* jni, jobject j_pc) {
+  jfieldID native_pc_id = GetFieldID(jni,
+      GetObjectClass(jni, j_pc), "nativePeerConnection", "J");
+  jlong j_p = GetLongField(jni, j_pc, native_pc_id);
+  return talk_base::scoped_refptr<PeerConnectionInterface>(
+      reinterpret_cast<PeerConnectionInterface*>(j_p));
+}
+
+JOW(jobject, PeerConnection_getLocalDescription)(JNIEnv* jni, jobject j_pc) {
+  const SessionDescriptionInterface* sdp =
+      ExtractNativePC(jni, j_pc)->local_description();
+  return sdp ? JavaSdpFromNativeSdp(jni, sdp) : NULL;
+}
+
+JOW(jobject, PeerConnection_getRemoteDescription)(JNIEnv* jni, jobject j_pc) {
+  const SessionDescriptionInterface* sdp =
+      ExtractNativePC(jni, j_pc)->remote_description();
+  return sdp ? JavaSdpFromNativeSdp(jni, sdp) : NULL;
+}
+
+JOW(void, PeerConnection_createOffer)(
+    JNIEnv* jni, jobject j_pc, jobject j_observer, jobject j_constraints) {
+  ConstraintsWrapper* constraints =
+      new ConstraintsWrapper(jni, j_constraints);
+  talk_base::scoped_refptr<CreateSdpObserverWrapper> observer(
+      new talk_base::RefCountedObject<CreateSdpObserverWrapper>(
+          jni, j_observer, constraints));
+  ExtractNativePC(jni, j_pc)->CreateOffer(observer, constraints);
+}
+
+JOW(void, PeerConnection_createAnswer)(
+    JNIEnv* jni, jobject j_pc, jobject j_observer, jobject j_constraints) {
+  ConstraintsWrapper* constraints =
+      new ConstraintsWrapper(jni, j_constraints);
+  talk_base::scoped_refptr<CreateSdpObserverWrapper> observer(
+      new talk_base::RefCountedObject<CreateSdpObserverWrapper>(
+          jni, j_observer, constraints));
+  ExtractNativePC(jni, j_pc)->CreateAnswer(observer, constraints);
+}
+
+// Helper to create a SessionDescriptionInterface from a SessionDescription.
+static SessionDescriptionInterface* JavaSdpToNativeSdp(
+    JNIEnv* jni, jobject j_sdp) {
+  jfieldID j_type_id = GetFieldID(
+      jni, GetObjectClass(jni, j_sdp), "type",
+      "Lorg/webrtc/SessionDescription$Type;");
+  jobject j_type = GetObjectField(jni, j_sdp, j_type_id);
+  jmethodID j_canonical_form_id = GetMethodID(
+      jni, GetObjectClass(jni, j_type), "canonicalForm",
+      "()Ljava/lang/String;");
+  jstring j_type_string = (jstring)jni->CallObjectMethod(
+      j_type, j_canonical_form_id);
+  CHECK_EXCEPTION(jni, "error during CallObjectMethod");
+  std::string std_type = JavaToStdString(jni, j_type_string);
+
+  jfieldID j_description_id = GetFieldID(
+      jni, GetObjectClass(jni, j_sdp), "description", "Ljava/lang/String;");
+  jstring j_description = (jstring)GetObjectField(jni, j_sdp, j_description_id);
+  std::string std_description = JavaToStdString(jni, j_description);
+
+  return webrtc::CreateSessionDescription(
+      std_type, std_description, NULL);
+}
+
+JOW(void, PeerConnection_setLocalDescription)(
+    JNIEnv* jni, jobject j_pc,
+    jobject j_observer, jobject j_sdp) {
+  talk_base::scoped_refptr<SetSdpObserverWrapper> observer(
+      new talk_base::RefCountedObject<SetSdpObserverWrapper>(
+          jni, j_observer, reinterpret_cast<ConstraintsWrapper*>(NULL)));
+  ExtractNativePC(jni, j_pc)->SetLocalDescription(
+      observer, JavaSdpToNativeSdp(jni, j_sdp));
+}
+
+JOW(void, PeerConnection_setRemoteDescription)(
+    JNIEnv* jni, jobject j_pc,
+    jobject j_observer, jobject j_sdp) {
+  talk_base::scoped_refptr<SetSdpObserverWrapper> observer(
+      new talk_base::RefCountedObject<SetSdpObserverWrapper>(
+          jni, j_observer, reinterpret_cast<ConstraintsWrapper*>(NULL)));
+  ExtractNativePC(jni, j_pc)->SetRemoteDescription(
+      observer, JavaSdpToNativeSdp(jni, j_sdp));
+}
+
+JOW(jboolean, PeerConnection_updateIce)(
+    JNIEnv* jni, jobject j_pc, jobject j_ice_servers, jobject j_constraints) {
+  PeerConnectionInterface::IceServers ice_servers;
+  JavaIceServersToJsepIceServers(jni, j_ice_servers, &ice_servers);
+  talk_base::scoped_ptr<ConstraintsWrapper> constraints(
+      new ConstraintsWrapper(jni, j_constraints));
+  return ExtractNativePC(jni, j_pc)->UpdateIce(ice_servers, constraints.get());
+}
+
+JOW(jboolean, PeerConnection_nativeAddIceCandidate)(
+    JNIEnv* jni, jobject j_pc, jstring j_sdp_mid,
+    jint j_sdp_mline_index, jstring j_candidate_sdp) {
+  std::string sdp_mid = JavaToStdString(jni, j_sdp_mid);
+  std::string sdp = JavaToStdString(jni, j_candidate_sdp);
+  talk_base::scoped_ptr<IceCandidateInterface> candidate(
+      webrtc::CreateIceCandidate(sdp_mid, j_sdp_mline_index, sdp, NULL));
+  return ExtractNativePC(jni, j_pc)->AddIceCandidate(candidate.get());
+}
+
+JOW(jboolean, PeerConnection_nativeAddLocalStream)(
+    JNIEnv* jni, jobject j_pc, jlong native_stream, jobject j_constraints) {
+  talk_base::scoped_ptr<ConstraintsWrapper> constraints(
+      new ConstraintsWrapper(jni, j_constraints));
+  return ExtractNativePC(jni, j_pc)->AddStream(
+      reinterpret_cast<MediaStreamInterface*>(native_stream),
+      constraints.get());
+}
+
+JOW(void, PeerConnection_nativeRemoveLocalStream)(
+    JNIEnv* jni, jobject j_pc, jlong native_stream) {
+  ExtractNativePC(jni, j_pc)->RemoveStream(
+      reinterpret_cast<MediaStreamInterface*>(native_stream));
+}
+
+JOW(bool, PeerConnection_nativeGetStats)(
+    JNIEnv* jni, jobject j_pc, jobject j_observer, jlong native_track) {
+  talk_base::scoped_refptr<StatsObserverWrapper> observer(
+      new talk_base::RefCountedObject<StatsObserverWrapper>(jni, j_observer));
+  return ExtractNativePC(jni, j_pc)->GetStats(
+      observer, reinterpret_cast<MediaStreamTrackInterface*>(native_track));
+}
+
+JOW(jobject, PeerConnection_signalingState)(JNIEnv* jni, jobject j_pc) {
+  PeerConnectionInterface::SignalingState state =
+      ExtractNativePC(jni, j_pc)->signaling_state();
+  return JavaEnumFromIndex(jni, "PeerConnection$SignalingState", state);
+}
+
+JOW(jobject, PeerConnection_iceConnectionState)(JNIEnv* jni, jobject j_pc) {
+  PeerConnectionInterface::IceConnectionState state =
+      ExtractNativePC(jni, j_pc)->ice_connection_state();
+  return JavaEnumFromIndex(jni, "PeerConnection$IceConnectionState", state);
+}
+
+JOW(jobject, PeerGathering_iceGatheringState)(JNIEnv* jni, jobject j_pc) {
+  PeerConnectionInterface::IceGatheringState state =
+      ExtractNativePC(jni, j_pc)->ice_gathering_state();
+  return JavaEnumFromIndex(jni, "PeerGathering$IceGatheringState", state);
+}
+
+JOW(void, PeerConnection_close)(JNIEnv* jni, jobject j_pc) {
+  ExtractNativePC(jni, j_pc)->Close();
+  return;
+}
+
+JOW(jobject, MediaSource_nativeState)(JNIEnv* jni, jclass, jlong j_p) {
+  talk_base::scoped_refptr<MediaSourceInterface> p(
+      reinterpret_cast<MediaSourceInterface*>(j_p));
+  return JavaEnumFromIndex(jni, "MediaSource$State", p->state());
+}
+
+JOW(jlong, VideoCapturer_nativeCreateVideoCapturer)(
+    JNIEnv* jni, jclass, jstring j_device_name) {
+  std::string device_name = JavaToStdString(jni, j_device_name);
+  talk_base::scoped_ptr<cricket::DeviceManagerInterface> device_manager(
+      cricket::DeviceManagerFactory::Create());
+  CHECK(device_manager->Init(), "DeviceManager::Init() failed");
+  cricket::Device device;
+  if (!device_manager->GetVideoCaptureDevice(device_name, &device)) {
+    LOG(LS_ERROR) << "GetVideoCaptureDevice failed";
+    return 0;
+  }
+  talk_base::scoped_ptr<cricket::VideoCapturer> capturer(
+      device_manager->CreateVideoCapturer(device));
+  return (jlong)capturer.release();
+}
+
+JOW(jlong, VideoRenderer_nativeCreateGuiVideoRenderer)(
+    JNIEnv* jni, jclass, int x, int y) {
+  talk_base::scoped_ptr<VideoRendererWrapper> renderer(
+      VideoRendererWrapper::Create(
+          cricket::VideoRendererFactory::CreateGuiVideoRenderer(x, y)));
+  return (jlong)renderer.release();
+}
+
+JOW(jlong, VideoRenderer_nativeWrapVideoRenderer)(
+    JNIEnv* jni, jclass, jobject j_callbacks) {
+  talk_base::scoped_ptr<JavaVideoRendererWrapper> renderer(
+      new JavaVideoRendererWrapper(jni, j_callbacks));
+  return (jlong)renderer.release();
+}
+
+JOW(jstring, MediaStreamTrack_nativeId)(JNIEnv* jni, jclass, jlong j_p) {
+  talk_base::scoped_refptr<MediaStreamTrackInterface> p(
+      reinterpret_cast<MediaStreamTrackInterface*>(j_p));
+  return JavaStringFromStdString(jni, p->id());
+}
+
+JOW(jstring, MediaStreamTrack_nativeKind)(JNIEnv* jni, jclass, jlong j_p) {
+  talk_base::scoped_refptr<MediaStreamTrackInterface> p(
+      reinterpret_cast<MediaStreamTrackInterface*>(j_p));
+  return JavaStringFromStdString(jni, p->kind());
+}
+
+JOW(jboolean, MediaStreamTrack_nativeEnabled)(JNIEnv* jni, jclass, jlong j_p) {
+  talk_base::scoped_refptr<MediaStreamTrackInterface> p(
+      reinterpret_cast<MediaStreamTrackInterface*>(j_p));
+  return p->enabled();
+}
+
+JOW(jobject, MediaStreamTrack_nativeState)(JNIEnv* jni, jclass, jlong j_p) {
+  talk_base::scoped_refptr<MediaStreamTrackInterface> p(
+      reinterpret_cast<MediaStreamTrackInterface*>(j_p));
+  return JavaEnumFromIndex(jni, "MediaStreamTrack$State", p->state());
+}
+
+JOW(jboolean, MediaStreamTrack_nativeSetState)(
+    JNIEnv* jni, jclass, jlong j_p, jint j_new_state) {
+  talk_base::scoped_refptr<MediaStreamTrackInterface> p(
+      reinterpret_cast<MediaStreamTrackInterface*>(j_p));
+  MediaStreamTrackInterface::TrackState new_state =
+      (MediaStreamTrackInterface::TrackState)j_new_state;
+  return p->set_state(new_state);
+}
+
+JOW(jboolean, MediaStreamTrack_nativeSetEnabled)(
+    JNIEnv* jni, jclass, jlong j_p, jboolean enabled) {
+  talk_base::scoped_refptr<MediaStreamTrackInterface> p(
+      reinterpret_cast<MediaStreamTrackInterface*>(j_p));
+  return p->set_enabled(enabled);
+}
+
+JOW(void, VideoTrack_nativeAddRenderer)(
+    JNIEnv* jni, jclass,
+    jlong j_video_track_pointer, jlong j_renderer_pointer) {
+  talk_base::scoped_refptr<VideoTrackInterface> track(
+      reinterpret_cast<VideoTrackInterface*>(j_video_track_pointer));
+  track->AddRenderer(
+      reinterpret_cast<VideoRendererInterface*>(j_renderer_pointer));
+}
+
+JOW(void, VideoTrack_nativeRemoveRenderer)(
+    JNIEnv* jni, jclass,
+    jlong j_video_track_pointer, jlong j_renderer_pointer) {
+  talk_base::scoped_refptr<VideoTrackInterface> track(
+      reinterpret_cast<VideoTrackInterface*>(j_video_track_pointer));
+  track->RemoveRenderer(
+      reinterpret_cast<VideoRendererInterface*>(j_renderer_pointer));
+}
diff --git a/talk/app/webrtc/java/src/org/webrtc/AudioSource.java b/talk/app/webrtc/java/src/org/webrtc/AudioSource.java
new file mode 100644
index 0000000..8b7a8f7
--- /dev/null
+++ b/talk/app/webrtc/java/src/org/webrtc/AudioSource.java
@@ -0,0 +1,38 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.webrtc;
+
+/**
+ * Java wrapper for a C++ AudioSourceInterface.  Used as the source for one or
+ * more {@code AudioTrack} objects.
+ */
+public class AudioSource extends MediaSource {
+  public AudioSource(long nativeSource) {
+    super(nativeSource);
+  }
+}
diff --git a/talk/app/webrtc/java/src/org/webrtc/AudioTrack.java b/talk/app/webrtc/java/src/org/webrtc/AudioTrack.java
new file mode 100644
index 0000000..35d7c41
--- /dev/null
+++ b/talk/app/webrtc/java/src/org/webrtc/AudioTrack.java
@@ -0,0 +1,35 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.webrtc;
+
+/** Java wrapper for a C++ AudioTrackInterface */
+public class AudioTrack extends MediaStreamTrack {
+  public AudioTrack(long nativeTrack) {
+    super(nativeTrack);
+  }
+}
diff --git a/talk/app/webrtc/java/src/org/webrtc/IceCandidate.java b/talk/app/webrtc/java/src/org/webrtc/IceCandidate.java
new file mode 100644
index 0000000..b5d2dc9
--- /dev/null
+++ b/talk/app/webrtc/java/src/org/webrtc/IceCandidate.java
@@ -0,0 +1,48 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.webrtc;
+
+/**
+ * Representation of a single ICE Candidate, mirroring
+ * {@code IceCandidateInterface} in the C++ API.
+ */
+public class IceCandidate {
+  public final String sdpMid;
+  public final int sdpMLineIndex;
+  public final String sdp;
+
+  public IceCandidate(String sdpMid, int sdpMLineIndex, String sdp) {
+    this.sdpMid = sdpMid;
+    this.sdpMLineIndex = sdpMLineIndex;
+    this.sdp = sdp;
+  }
+
+  public String toString() {
+    return sdpMid + ":" + sdpMLineIndex + ":" + sdp;
+  }
+}
diff --git a/talk/app/webrtc/java/src/org/webrtc/MediaConstraints.java b/talk/app/webrtc/java/src/org/webrtc/MediaConstraints.java
new file mode 100644
index 0000000..ef30301
--- /dev/null
+++ b/talk/app/webrtc/java/src/org/webrtc/MediaConstraints.java
@@ -0,0 +1,85 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.webrtc;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Description of media constraints for {@code MediaStream} and
+ * {@code PeerConnection}.
+ */
+public class MediaConstraints {
+  /** Simple String key/value pair. */
+  public static class KeyValuePair {
+    private final String key;
+    private final String value;
+
+    public KeyValuePair(String key, String value) {
+      this.key = key;
+      this.value = value;
+    }
+
+    public String getKey() {
+      return key;
+    }
+
+    public String getValue() {
+      return value;
+    }
+
+    public String toString() {
+      return key + ": " + value;
+    }
+  }
+
+
+  public final List<KeyValuePair> mandatory;
+  public final List<KeyValuePair> optional;
+
+  public MediaConstraints() {
+    mandatory = new LinkedList<KeyValuePair>();
+    optional = new LinkedList<KeyValuePair>();
+  }
+
+  private static String stringifyKeyValuePairList(List<KeyValuePair> list) {
+    StringBuilder builder = new StringBuilder("[");
+    for (KeyValuePair pair : list) {
+      if (builder.length() > 1) {
+        builder.append(", ");
+      }
+      builder.append(pair.toString());
+    }
+    return builder.append("]").toString();
+  }
+
+  public String toString() {
+    return "mandatory: " + stringifyKeyValuePairList(mandatory) +
+        ", optional: " + stringifyKeyValuePairList(optional);
+  }
+}
diff --git a/talk/app/webrtc/java/src/org/webrtc/MediaSource.java b/talk/app/webrtc/java/src/org/webrtc/MediaSource.java
new file mode 100644
index 0000000..2949049
--- /dev/null
+++ b/talk/app/webrtc/java/src/org/webrtc/MediaSource.java
@@ -0,0 +1,55 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+package org.webrtc;
+
+/** Java wrapper for a C++ MediaSourceInterface. */
+public class MediaSource {
+  /** Tracks MediaSourceInterface.SourceState */
+  public enum State {
+    INITIALIZING, LIVE, ENDED, MUTED
+  }
+
+  final long nativeSource;  // Package-protected for PeerConnectionFactory.
+
+  public MediaSource(long nativeSource) {
+    this.nativeSource = nativeSource;
+  }
+
+  public State state() {
+    return nativeState(nativeSource);
+  }
+
+  void dispose() {
+    free(nativeSource);
+  }
+
+  private static native State nativeState(long pointer);
+
+  private static native void free(long nativeSource);
+}
diff --git a/talk/app/webrtc/java/src/org/webrtc/MediaStream.java b/talk/app/webrtc/java/src/org/webrtc/MediaStream.java
new file mode 100644
index 0000000..431c561
--- /dev/null
+++ b/talk/app/webrtc/java/src/org/webrtc/MediaStream.java
@@ -0,0 +1,114 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.webrtc;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/** Java wrapper for a C++ MediaStreamInterface. */
+public class MediaStream {
+  public final List<AudioTrack> audioTracks;
+  public final List<VideoTrack> videoTracks;
+  // Package-protected for LocalMediaStream and PeerConnection.
+  final long nativeStream;
+
+  public MediaStream(long nativeStream) {
+    audioTracks = new LinkedList<AudioTrack>();
+    videoTracks = new LinkedList<VideoTrack>();
+    this.nativeStream = nativeStream;
+  }
+
+  public boolean addTrack(AudioTrack track) {
+    if (nativeAddAudioTrack(nativeStream, track.nativeTrack)) {
+      audioTracks.add(track);
+      return true;
+    }
+    return false;
+  }
+
+  public boolean addTrack(VideoTrack track) {
+    if (nativeAddVideoTrack(nativeStream, track.nativeTrack)) {
+      videoTracks.add(track);
+      return true;
+    }
+    return false;
+  }
+
+  public boolean removeTrack(AudioTrack track) {
+    if (nativeRemoveAudioTrack(nativeStream, track.nativeTrack)) {
+      audioTracks.remove(track);
+      return true;
+    }
+    return false;
+  }
+
+  public boolean removeTrack(VideoTrack track) {
+    if (nativeRemoveVideoTrack(nativeStream, track.nativeTrack)) {
+      videoTracks.remove(track);
+      return true;
+    }
+    return false;
+  }
+
+  public void dispose() {
+    for (AudioTrack track : audioTracks) {
+      track.dispose();
+    }
+    audioTracks.clear();
+    for (VideoTrack track : videoTracks) {
+      track.dispose();
+    }
+    videoTracks.clear();
+    free(nativeStream);
+  }
+
+  public String label() {
+    return nativeLabel(nativeStream);
+  }
+
+  public String toString() {
+    return "[" + label() + ":A=" + audioTracks.size() +
+        ":V=" + videoTracks.size() + "]";
+  }
+
+  private static native boolean nativeAddAudioTrack(
+      long nativeStream, long nativeAudioTrack);
+
+  private static native boolean nativeAddVideoTrack(
+      long nativeStream, long nativeVideoTrack);
+
+  private static native boolean nativeRemoveAudioTrack(
+      long nativeStream, long nativeAudioTrack);
+
+  private static native boolean nativeRemoveVideoTrack(
+      long nativeStream, long nativeVideoTrack);
+
+  private static native String nativeLabel(long nativeStream);
+
+  private static native void free(long nativeStream);
+}
diff --git a/talk/app/webrtc/java/src/org/webrtc/MediaStreamTrack.java b/talk/app/webrtc/java/src/org/webrtc/MediaStreamTrack.java
new file mode 100644
index 0000000..5cd2f4c
--- /dev/null
+++ b/talk/app/webrtc/java/src/org/webrtc/MediaStreamTrack.java
@@ -0,0 +1,86 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.webrtc;
+
+/** Java wrapper for a C++ MediaStreamTrackInterface. */
+public class MediaStreamTrack {
+  /** Tracks MediaStreamTrackInterface.TrackState */
+  public enum State {
+    INITIALIZING, LIVE, ENDED, FAILED
+  }
+
+  final long nativeTrack;
+
+  public MediaStreamTrack(long nativeTrack) {
+    this.nativeTrack = nativeTrack;
+  }
+
+  public String id() {
+    return nativeId(nativeTrack);
+  }
+
+  public String kind() {
+    return nativeKind(nativeTrack);
+  }
+
+  public boolean enabled() {
+    return nativeEnabled(nativeTrack);
+  }
+
+  public boolean setEnabled(boolean enable) {
+    return nativeSetEnabled(nativeTrack, enable);
+  }
+
+  public State state() {
+    return nativeState(nativeTrack);
+  }
+
+  public boolean setState(State newState) {
+    return nativeSetState(nativeTrack, newState.ordinal());
+  }
+
+  public void dispose() {
+    free(nativeTrack);
+  }
+
+  private static native String nativeId(long nativeTrack);
+
+  private static native String nativeKind(long nativeTrack);
+
+  private static native boolean nativeEnabled(long nativeTrack);
+
+  private static native boolean nativeSetEnabled(
+      long nativeTrack, boolean enabled);
+
+  private static native State nativeState(long nativeTrack);
+
+  private static native boolean nativeSetState(
+      long nativeTrack, int newState);
+
+  private static native void free(long nativeTrack);
+}
diff --git a/talk/app/webrtc/java/src/org/webrtc/PeerConnection.java b/talk/app/webrtc/java/src/org/webrtc/PeerConnection.java
new file mode 100644
index 0000000..5d08c04
--- /dev/null
+++ b/talk/app/webrtc/java/src/org/webrtc/PeerConnection.java
@@ -0,0 +1,194 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+package org.webrtc;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Java-land version of the PeerConnection APIs; wraps the C++ API
+ * http://www.webrtc.org/reference/native-apis, which in turn is inspired by the
+ * JS APIs: http://dev.w3.org/2011/webrtc/editor/webrtc.html and
+ * http://www.w3.org/TR/mediacapture-streams/
+ */
+public class PeerConnection {
+  static {
+    System.loadLibrary("jingle_peerconnection_so");
+  }
+
+  /** Tracks PeerConnectionInterface::IceGatheringState */
+  public enum IceGatheringState { NEW, GATHERING, COMPLETE };
+
+
+  /** Tracks PeerConnectionInterface::IceConnectionState */
+  public enum IceConnectionState {
+    NEW, CHECKING, CONNECTED, COMPLETED, FAILED, DISCONNECTED, CLOSED
+  };
+
+  /** Tracks PeerConnectionInterface::SignalingState */
+  public enum SignalingState {
+    STABLE, HAVE_LOCAL_OFFER, HAVE_LOCAL_PRANSWER, HAVE_REMOTE_OFFER,
+    HAVE_REMOTE_PRANSWER, CLOSED
+  };
+
+  /** Java version of PeerConnectionObserver. */
+  public static interface Observer {
+    /** Triggered when the SignalingState changes. */
+    public void onSignalingChange(SignalingState newState);
+
+    /** Triggered when the IceConnectionState changes. */
+    public void onIceConnectionChange(IceConnectionState newState);
+
+    /** Triggered when the IceGatheringState changes. */
+    public void onIceGatheringChange(IceGatheringState newState);
+
+    /** Triggered when a new ICE candidate has been found. */
+    public void onIceCandidate(IceCandidate candidate);
+
+    /** Triggered on any error. */
+    public void onError();
+
+    /** Triggered when media is received on a new stream from remote peer. */
+    public void onAddStream(MediaStream stream);
+
+    /** Triggered when a remote peer close a stream. */
+    public void onRemoveStream(MediaStream stream);
+  }
+
+  /** Java version of PeerConnectionInterface.IceServer. */
+  public static class IceServer {
+    public final String uri;
+    public final String username;
+    public final String password;
+
+    /** Convenience constructor for STUN servers. */
+    public IceServer(String uri) {
+      this(uri, "", "");
+    }
+
+    public IceServer(String uri, String username, String password) {
+      this.uri = uri;
+      this.username = username;
+      this.password = password;
+    }
+
+    public String toString() {
+      return uri + "[" + username + ":" + password + "]";
+    }
+  }
+
+  private final List<MediaStream> localStreams;
+  private final long nativePeerConnection;
+  private final long nativeObserver;
+
+  PeerConnection(long nativePeerConnection, long nativeObserver) {
+    this.nativePeerConnection = nativePeerConnection;
+    this.nativeObserver = nativeObserver;
+    localStreams = new LinkedList<MediaStream>();
+  }
+
+  // JsepInterface.
+  public native SessionDescription getLocalDescription();
+
+  public native SessionDescription getRemoteDescription();
+
+  public native void createOffer(
+      SdpObserver observer, MediaConstraints constraints);
+
+  public native void createAnswer(
+      SdpObserver observer, MediaConstraints constraints);
+
+  public native void setLocalDescription(
+      SdpObserver observer, SessionDescription sdp);
+
+  public native void setRemoteDescription(
+      SdpObserver observer, SessionDescription sdp);
+
+  public native boolean updateIce(
+      List<IceServer> iceServers, MediaConstraints constraints);
+
+  public boolean addIceCandidate(IceCandidate candidate) {
+    return nativeAddIceCandidate(
+        candidate.sdpMid, candidate.sdpMLineIndex, candidate.sdp);
+  }
+
+  public boolean addStream(
+      MediaStream stream, MediaConstraints constraints) {
+    boolean ret = nativeAddLocalStream(stream.nativeStream, constraints);
+    if (!ret) {
+      return false;
+    }
+    localStreams.add(stream);
+    return true;
+  }
+
+  public void removeStream(MediaStream stream) {
+    nativeRemoveLocalStream(stream.nativeStream);
+    localStreams.remove(stream);
+  }
+
+  public boolean getStats(StatsObserver observer, MediaStreamTrack track) {
+    return nativeGetStats(observer, (track == null) ? 0 : track.nativeTrack);
+  }
+
+  // TODO(fischman): add support for DTMF-related methods once that API
+  // stabilizes.
+  public native SignalingState signalingState();
+
+  public native IceConnectionState iceConnectionState();
+
+  public native IceGatheringState iceGatheringState();
+
+  public native void close();
+
+  public void dispose() {
+    close();
+    for (MediaStream stream : localStreams) {
+      stream.dispose();
+    }
+    localStreams.clear();
+    freePeerConnection(nativePeerConnection);
+    freeObserver(nativeObserver);
+  }
+
+  private static native void freePeerConnection(long nativePeerConnection);
+
+  private static native void freeObserver(long nativeObserver);
+
+  private native boolean nativeAddIceCandidate(
+      String sdpMid, int sdpMLineIndex, String iceCandidateSdp);
+
+  private native boolean nativeAddLocalStream(
+      long nativeStream, MediaConstraints constraints);
+
+  private native void nativeRemoveLocalStream(long nativeStream);
+
+  private native boolean nativeGetStats(
+      StatsObserver observer, long nativeTrack);
+}
diff --git a/talk/app/webrtc/java/src/org/webrtc/PeerConnectionFactory.java b/talk/app/webrtc/java/src/org/webrtc/PeerConnectionFactory.java
new file mode 100644
index 0000000..03ed03f
--- /dev/null
+++ b/talk/app/webrtc/java/src/org/webrtc/PeerConnectionFactory.java
@@ -0,0 +1,119 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+package org.webrtc;
+
+import java.util.List;
+
+/**
+ * Java wrapper for a C++ PeerConnectionFactoryInterface.  Main entry point to
+ * the PeerConnection API for clients.
+ */
+public class PeerConnectionFactory {
+  static {
+    System.loadLibrary("jingle_peerconnection_so");
+  }
+
+  private final long nativeFactory;
+
+  // |context| is an android.content.Context object, but we keep it untyped here
+  // to allow building on non-Android platforms.
+  public static native boolean initializeAndroidGlobals(Object context);
+
+  public PeerConnectionFactory() {
+    nativeFactory = nativeCreatePeerConnectionFactory();
+    if (nativeFactory == 0) {
+      throw new RuntimeException("Failed to initialize PeerConnectionFactory!");
+    }
+  }
+
+
+  public PeerConnection createPeerConnection(
+      List<PeerConnection.IceServer> iceServers,
+      MediaConstraints constraints,
+      PeerConnection.Observer observer) {
+    long nativeObserver = nativeCreateObserver(observer);
+    if (nativeObserver == 0) {
+      return null;
+    }
+    long nativePeerConnection = nativeCreatePeerConnection(
+        nativeFactory, iceServers, constraints, nativeObserver);
+    if (nativePeerConnection == 0) {
+      return null;
+    }
+    return new PeerConnection(nativePeerConnection, nativeObserver);
+  }
+
+  public MediaStream createLocalMediaStream(String label) {
+    return new MediaStream(
+        nativeCreateLocalMediaStream(nativeFactory, label));
+  }
+
+  public VideoSource createVideoSource(
+      VideoCapturer capturer, MediaConstraints constraints) {
+    return new VideoSource(nativeCreateVideoSource(
+        nativeFactory, capturer.nativeVideoCapturer, constraints));
+  }
+
+  public VideoTrack createVideoTrack(String id, VideoSource source) {
+    return new VideoTrack(nativeCreateVideoTrack(
+        nativeFactory, id, source.nativeSource));
+  }
+
+  public AudioTrack createAudioTrack(String id) {
+    return new AudioTrack(nativeCreateAudioTrack(nativeFactory, id));
+  }
+
+  public void dispose() {
+    freeFactory(nativeFactory);
+  }
+
+  private static native long nativeCreatePeerConnectionFactory();
+
+  private static native long nativeCreateObserver(
+      PeerConnection.Observer observer);
+
+  private static native long nativeCreatePeerConnection(
+      long nativeFactory, List<PeerConnection.IceServer> iceServers,
+      MediaConstraints constraints, long nativeObserver);
+
+  private static native long nativeCreateLocalMediaStream(
+      long nativeFactory, String label);
+
+  private static native long nativeCreateVideoSource(
+      long nativeFactory, long nativeVideoCapturer,
+      MediaConstraints constraints);
+
+  private static native long nativeCreateVideoTrack(
+      long nativeFactory, String id, long nativeVideoSource);
+
+  private static native long nativeCreateAudioTrack(
+      long nativeFactory, String id);
+
+  private static native void freeFactory(long nativeFactory);
+}
diff --git a/talk/app/webrtc/java/src/org/webrtc/SdpObserver.java b/talk/app/webrtc/java/src/org/webrtc/SdpObserver.java
new file mode 100644
index 0000000..c9eb14a
--- /dev/null
+++ b/talk/app/webrtc/java/src/org/webrtc/SdpObserver.java
@@ -0,0 +1,43 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.webrtc;
+
+/** Interface for observing SDP-related events. */
+public interface SdpObserver {
+  /** Called on success of Create{Offer,Answer}(). */
+  public void onCreateSuccess(SessionDescription sdp);
+
+  /** Called on success of Set{Local,Remote}Description(). */
+  public void onSetSuccess();
+
+  /** Called on error of Create{Offer,Answer}(). */
+  public void onCreateFailure(String error);
+
+  /** Called on error of Set{Local,Remote}Description(). */
+  public void onSetFailure(String error);
+}
diff --git a/talk/app/webrtc/java/src/org/webrtc/SessionDescription.java b/talk/app/webrtc/java/src/org/webrtc/SessionDescription.java
new file mode 100644
index 0000000..982db8f
--- /dev/null
+++ b/talk/app/webrtc/java/src/org/webrtc/SessionDescription.java
@@ -0,0 +1,57 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+package org.webrtc;
+
+/**
+ * Description of an RFC 4566 Session.
+ * SDPs are passed as serialized Strings in Java-land and are materialized
+ * to SessionDescriptionInterface as appropriate in the JNI layer.
+ */
+public class SessionDescription {
+  /** Java-land enum version of SessionDescriptionInterface's type() string. */
+  public static enum Type {
+    OFFER, PRANSWER, ANSWER;
+
+    public String canonicalForm() {
+      return name().toLowerCase();
+    }
+
+    public static Type fromCanonicalForm(String canonical) {
+      return Type.valueOf(Type.class, canonical.toUpperCase());
+    }
+  }
+
+  public final Type type;
+  public final String description;
+
+  public SessionDescription(Type type, String description) {
+    this.type = type;
+    this.description = description;
+  }
+}
diff --git a/talk/app/webrtc/java/src/org/webrtc/StatsObserver.java b/talk/app/webrtc/java/src/org/webrtc/StatsObserver.java
new file mode 100644
index 0000000..e61d8f7
--- /dev/null
+++ b/talk/app/webrtc/java/src/org/webrtc/StatsObserver.java
@@ -0,0 +1,34 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.webrtc;
+
+/** Interface for observing Stats reports (see webrtc::StatsObservers). */
+public interface StatsObserver {
+  /** Called when the reports are ready.*/
+  public void onComplete(StatsReport[] reports);
+}
diff --git a/talk/app/webrtc/java/src/org/webrtc/StatsReport.java b/talk/app/webrtc/java/src/org/webrtc/StatsReport.java
new file mode 100644
index 0000000..8285ba2
--- /dev/null
+++ b/talk/app/webrtc/java/src/org/webrtc/StatsReport.java
@@ -0,0 +1,72 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.webrtc;
+
+/** Java version of webrtc::StatsReport. */
+public class StatsReport {
+
+  /** Java version of webrtc::StatsReport::Value. */
+  public static class Value {
+    public final String name;
+    public final String value;
+
+    public Value(String name, String value) {
+      this.name = name;
+      this.value = value;
+    }
+
+    public String toString() {
+      StringBuilder builder = new StringBuilder();
+      builder.append("[").append(name).append(": ").append(value).append("]");
+      return builder.toString();
+    }
+  }
+
+  public final String id;
+  public final String type;
+  // Time since 1970-01-01T00:00:00Z in milliseconds.
+  public final double timestamp;
+  public final Value[] values;
+
+  public StatsReport(String id, String type, double timestamp, Value[] values) {
+    this.id = id;
+    this.type = type;
+    this.timestamp = timestamp;
+    this.values = values;
+  }
+
+  public String toString() {
+    StringBuilder builder = new StringBuilder();
+    builder.append("id: ").append(id).append(", type: ").append(type)
+        .append(", timestamp: ").append(timestamp).append(", values: ");
+    for (int i = 0; i < values.length; ++i) {
+      builder.append(values[i].toString()).append(", ");
+    }
+    return builder.toString();
+  }
+}
diff --git a/talk/app/webrtc/java/src/org/webrtc/VideoCapturer.java b/talk/app/webrtc/java/src/org/webrtc/VideoCapturer.java
new file mode 100644
index 0000000..eab5797
--- /dev/null
+++ b/talk/app/webrtc/java/src/org/webrtc/VideoCapturer.java
@@ -0,0 +1,53 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.webrtc;
+
+/** Java version of VideoCapturerInterface. */
+public class VideoCapturer {
+  final long nativeVideoCapturer;
+
+  private VideoCapturer(long nativeVideoCapturer) {
+    this.nativeVideoCapturer = nativeVideoCapturer;
+  }
+
+  public static VideoCapturer create(String deviceName) {
+    long nativeVideoCapturer = nativeCreateVideoCapturer(deviceName);
+    if (nativeVideoCapturer == 0) {
+      return null;
+    }
+    return new VideoCapturer(nativeVideoCapturer);
+  }
+
+  public void dispose() {
+    free(nativeVideoCapturer);
+  }
+
+  private static native long nativeCreateVideoCapturer(String deviceName);
+
+  private static native void free(long nativeVideoCapturer);
+}
diff --git a/talk/app/webrtc/java/src/org/webrtc/VideoRenderer.java b/talk/app/webrtc/java/src/org/webrtc/VideoRenderer.java
new file mode 100644
index 0000000..4cc341a
--- /dev/null
+++ b/talk/app/webrtc/java/src/org/webrtc/VideoRenderer.java
@@ -0,0 +1,136 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.webrtc;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/**
+ * Java version of VideoRendererInterface.  In addition to allowing clients to
+ * define their own rendering behavior (by passing in a Callbacks object), this
+ * class also provides a createGui() method for creating a GUI-rendering window
+ * on various platforms.
+ */
+public class VideoRenderer {
+
+  /** Java version of cricket::VideoFrame. */
+  public static class I420Frame {
+    public final int width;
+    public final int height;
+    public final int[] yuvStrides;
+    public final ByteBuffer[] yuvPlanes;
+
+    /**
+     * Construct a frame of the given dimensions with the specified planar
+     * data.  If |yuvPlanes| is null, new planes of the appropriate sizes are
+     * allocated.
+     */
+    public I420Frame(
+        int width, int height, int[] yuvStrides, ByteBuffer[] yuvPlanes) {
+      this.width = width;
+      this.height = height;
+      this.yuvStrides = yuvStrides;
+      if (yuvPlanes == null) {
+        yuvPlanes = new ByteBuffer[3];
+        yuvPlanes[0] = ByteBuffer.allocateDirect(yuvStrides[0] * height);
+        yuvPlanes[1] = ByteBuffer.allocateDirect(yuvStrides[1] * height);
+        yuvPlanes[2] = ByteBuffer.allocateDirect(yuvStrides[2] * height);
+      }
+      this.yuvPlanes = yuvPlanes;
+    }
+
+    /**
+     * Copy the planes out of |source| into |this| and return |this|.  Calling
+     * this with mismatched frame dimensions is a programming error and will
+     * likely crash.
+     */
+    public I420Frame copyFrom(I420Frame source) {
+      if (!Arrays.equals(yuvStrides, source.yuvStrides) ||
+          width != source.width || height != source.height) {
+        throw new RuntimeException("Mismatched dimensions!  Source: " +
+            source.toString() + ", destination: " + toString());
+      }
+      copyPlane(source.yuvPlanes[0], yuvPlanes[0]);
+      copyPlane(source.yuvPlanes[1], yuvPlanes[1]);
+      copyPlane(source.yuvPlanes[2], yuvPlanes[2]);
+      return this;
+    }
+
+    @Override
+    public String toString() {
+      return width + "x" + height + ":" + yuvStrides[0] + ":" + yuvStrides[1] +
+          ":" + yuvStrides[2];
+    }
+
+    // Copy the bytes out of |src| and into |dst|, ignoring and overwriting
+    // positon & limit in both buffers.
+    private void copyPlane(ByteBuffer src, ByteBuffer dst) {
+      src.position(0).limit(src.capacity());
+      dst.put(src);
+      dst.position(0).limit(dst.capacity());
+    }
+}
+
+  /** The real meat of VideoRendererInterface. */
+  public static interface Callbacks {
+    public void setSize(int width, int height);
+    public void renderFrame(I420Frame frame);
+  }
+
+  // |this| either wraps a native (GUI) renderer or a client-supplied Callbacks
+  // (Java) implementation; so exactly one of these will be non-0/null.
+  final long nativeVideoRenderer;
+  private final Callbacks callbacks;
+
+  public static VideoRenderer createGui(int x, int y) {
+    long nativeVideoRenderer = nativeCreateGuiVideoRenderer(x, y);
+    if (nativeVideoRenderer == 0) {
+      return null;
+    }
+    return new VideoRenderer(nativeVideoRenderer);
+  }
+
+  public VideoRenderer(Callbacks callbacks) {
+    nativeVideoRenderer = nativeWrapVideoRenderer(callbacks);
+    this.callbacks = callbacks;
+  }
+
+  private VideoRenderer(long nativeVideoRenderer) {
+    this.nativeVideoRenderer = nativeVideoRenderer;
+    callbacks = null;
+  }
+
+  public void dispose() {
+    free(nativeVideoRenderer);
+  }
+
+  private static native long nativeCreateGuiVideoRenderer(int x, int y);
+  private static native long nativeWrapVideoRenderer(Callbacks callbacks);
+
+  private static native void free(long nativeVideoRenderer);
+}
diff --git a/talk/app/webrtc/java/src/org/webrtc/VideoSource.java b/talk/app/webrtc/java/src/org/webrtc/VideoSource.java
new file mode 100644
index 0000000..f29f312
--- /dev/null
+++ b/talk/app/webrtc/java/src/org/webrtc/VideoSource.java
@@ -0,0 +1,36 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+package org.webrtc;
+
+/** Java version of VideoSourceInterface. */
+public class VideoSource extends MediaSource {
+  public VideoSource(long nativeSource) {
+    super(nativeSource);
+  }
+}
diff --git a/talk/app/webrtc/java/src/org/webrtc/VideoTrack.java b/talk/app/webrtc/java/src/org/webrtc/VideoTrack.java
new file mode 100644
index 0000000..90e5c95
--- /dev/null
+++ b/talk/app/webrtc/java/src/org/webrtc/VideoTrack.java
@@ -0,0 +1,65 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.webrtc;
+
+import java.util.LinkedList;
+
+/** Java version of VideoTrackInterface. */
+public class VideoTrack extends MediaStreamTrack {
+  private final LinkedList<VideoRenderer> renderers;
+
+  public VideoTrack(long nativeTrack) {
+    super(nativeTrack);
+    renderers = new LinkedList<VideoRenderer>();
+  }
+
+  public void addRenderer(VideoRenderer renderer) {
+    renderers.add(renderer);
+    nativeAddRenderer(nativeTrack, renderer.nativeVideoRenderer);
+  }
+
+  public void removeRenderer(VideoRenderer renderer) {
+    if (!renderers.remove(renderer)) {
+      return;
+    }
+    nativeRemoveRenderer(nativeTrack, renderer.nativeVideoRenderer);
+    renderer.dispose();
+  }
+
+  public void dispose() {
+    while (!renderers.isEmpty()) {
+      removeRenderer(renderers.getFirst());
+    }
+  }
+
+  private static native void nativeAddRenderer(
+      long nativeTrack, long nativeRenderer);
+
+  private static native void nativeRemoveRenderer(
+      long nativeTrack, long nativeRenderer);
+}
diff --git a/talk/app/webrtc/javatests/libjingle_peerconnection_java_unittest.sh b/talk/app/webrtc/javatests/libjingle_peerconnection_java_unittest.sh
new file mode 100644
index 0000000..0ecb730
--- /dev/null
+++ b/talk/app/webrtc/javatests/libjingle_peerconnection_java_unittest.sh
@@ -0,0 +1,47 @@
+#!/bin/bash
+#
+# libjingle
+# Copyright 2013, Google Inc.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#  1. Redistributions of source code must retain the above copyright notice,
+#     this list of conditions and the following disclaimer.
+#  2. Redistributions in binary form must reproduce the above copyright notice,
+#     this list of conditions and the following disclaimer in the documentation
+#     and/or other materials provided with the distribution.
+#  3. The name of the author may not be used to endorse or promote products
+#     derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# Wrapper script for running the Java tests under this directory.
+
+# Exit with error immediately if any subcommand fails.
+set -e
+
+# Change directory to the PRODUCT_DIR (e.g. out/Debug).
+cd -P $(dirname $0)
+
+export CLASSPATH=`pwd`/junit-4.11.jar
+CLASSPATH=$CLASSPATH:`pwd`/libjingle_peerconnection_test.jar
+CLASSPATH=$CLASSPATH:`pwd`/libjingle_peerconnection.jar
+
+export LD_LIBRARY_PATH=`pwd`
+
+# The RHS value is replaced by the build action that copies this script to
+# <(PRODUCT_DIR).
+export JAVA_HOME=GYP_JAVA_HOME
+
+${JAVA_HOME}/bin/java -Xcheck:jni -classpath $CLASSPATH \
+    junit.textui.TestRunner org.webrtc.PeerConnectionTest
diff --git a/talk/app/webrtc/javatests/src/org/webrtc/PeerConnectionTest.java b/talk/app/webrtc/javatests/src/org/webrtc/PeerConnectionTest.java
new file mode 100644
index 0000000..cdd8c73
--- /dev/null
+++ b/talk/app/webrtc/javatests/src/org/webrtc/PeerConnectionTest.java
@@ -0,0 +1,532 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.webrtc;
+
+import junit.framework.TestCase;
+
+import org.junit.Test;
+import org.webrtc.PeerConnection.IceConnectionState;
+import org.webrtc.PeerConnection.IceGatheringState;
+import org.webrtc.PeerConnection.SignalingState;
+
+import java.lang.ref.WeakReference;
+import java.util.IdentityHashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/** End-to-end tests for PeerConnection.java. */
+public class PeerConnectionTest extends TestCase {
+  // Set to true to render video.
+  private static final boolean RENDER_TO_GUI = false;
+
+  private static class ObserverExpectations implements PeerConnection.Observer,
+                                            VideoRenderer.Callbacks,
+                                            StatsObserver {
+    private int expectedIceCandidates = 0;
+    private int expectedErrors = 0;
+    private LinkedList<Integer> expectedSetSizeDimensions =
+        new LinkedList<Integer>();  // Alternating width/height.
+    private int expectedFramesDelivered = 0;
+    private LinkedList<SignalingState> expectedSignalingChanges =
+        new LinkedList<SignalingState>();
+    private LinkedList<IceConnectionState> expectedIceConnectionChanges =
+        new LinkedList<IceConnectionState>();
+    private LinkedList<IceGatheringState> expectedIceGatheringChanges =
+        new LinkedList<IceGatheringState>();
+    private LinkedList<String> expectedAddStreamLabels =
+        new LinkedList<String>();
+    private LinkedList<String> expectedRemoveStreamLabels =
+        new LinkedList<String>();
+    public LinkedList<IceCandidate> gotIceCandidates =
+        new LinkedList<IceCandidate>();
+    private Map<MediaStream, WeakReference<VideoRenderer>> renderers =
+        new IdentityHashMap<MediaStream, WeakReference<VideoRenderer>>();
+    private int expectedStatsCallbacks = 0;
+    private LinkedList<StatsReport[]> gotStatsReports =
+        new LinkedList<StatsReport[]>();
+
+    public synchronized void expectIceCandidates(int count) {
+      expectedIceCandidates += count;
+    }
+
+    public synchronized void onIceCandidate(IceCandidate candidate) {
+      --expectedIceCandidates;
+      // We don't assert expectedIceCandidates >= 0 because it's hard to know
+      // how many to expect, in general.  We only use expectIceCandidates to
+      // assert a minimal count.
+      gotIceCandidates.add(candidate);
+    }
+
+    public synchronized void expectError() {
+      ++expectedErrors;
+    }
+
+    public synchronized void onError() {
+      assertTrue(--expectedErrors >= 0);
+    }
+
+    public synchronized void expectSetSize(int width, int height) {
+      expectedSetSizeDimensions.add(width);
+      expectedSetSizeDimensions.add(height);
+    }
+
+    @Override
+    public synchronized void setSize(int width, int height) {
+      assertEquals(width, expectedSetSizeDimensions.removeFirst().intValue());
+      assertEquals(height, expectedSetSizeDimensions.removeFirst().intValue());
+    }
+
+    public synchronized void expectFramesDelivered(int count) {
+      expectedFramesDelivered += count;
+    }
+
+    @Override
+    public synchronized void renderFrame(VideoRenderer.I420Frame frame) {
+      --expectedFramesDelivered;
+    }
+
+    public synchronized void expectSignalingChange(SignalingState newState) {
+      expectedSignalingChanges.add(newState);
+    }
+
+    @Override
+    public synchronized void onSignalingChange(SignalingState newState) {
+      assertEquals(expectedSignalingChanges.removeFirst(), newState);
+    }
+
+    public synchronized void expectIceConnectionChange(
+        IceConnectionState newState) {
+      expectedIceConnectionChanges.add(newState);
+    }
+
+    @Override
+    public void onIceConnectionChange(IceConnectionState newState) {
+      assertEquals(expectedIceConnectionChanges.removeFirst(), newState);
+    }
+
+    public synchronized void expectIceGatheringChange(
+        IceGatheringState newState) {
+      expectedIceGatheringChanges.add(newState);
+    }
+
+    @Override
+    public void onIceGatheringChange(IceGatheringState newState) {
+      // It's fine to get a variable number of GATHERING messages before
+      // COMPLETE fires (depending on how long the test runs) so we don't assert
+      // any particular count.
+      if (newState == IceGatheringState.GATHERING) {
+        return;
+      }
+      assertEquals(expectedIceGatheringChanges.removeFirst(), newState);
+    }
+
+    public synchronized void expectAddStream(String label) {
+      expectedAddStreamLabels.add(label);
+    }
+
+    public synchronized void onAddStream(MediaStream stream) {
+      assertEquals(expectedAddStreamLabels.removeFirst(), stream.label());
+      assertEquals(1, stream.videoTracks.size());
+      assertEquals(1, stream.audioTracks.size());
+      assertTrue(stream.videoTracks.get(0).id().endsWith("LMSv0"));
+      assertTrue(stream.audioTracks.get(0).id().endsWith("LMSa0"));
+      assertEquals("video", stream.videoTracks.get(0).kind());
+      assertEquals("audio", stream.audioTracks.get(0).kind());
+      VideoRenderer renderer = createVideoRenderer(this);
+      stream.videoTracks.get(0).addRenderer(renderer);
+      assertNull(renderers.put(
+          stream, new WeakReference<VideoRenderer>(renderer)));
+    }
+
+    public synchronized void expectRemoveStream(String label) {
+      expectedRemoveStreamLabels.add(label);
+    }
+
+    public synchronized void onRemoveStream(MediaStream stream) {
+      assertEquals(expectedRemoveStreamLabels.removeFirst(), stream.label());
+      WeakReference<VideoRenderer> renderer = renderers.remove(stream);
+      assertNotNull(renderer);
+      assertNotNull(renderer.get());
+      assertEquals(1, stream.videoTracks.size());
+      stream.videoTracks.get(0).removeRenderer(renderer.get());
+    }
+
+    @Override
+    public synchronized void onComplete(StatsReport[] reports) {
+      if (--expectedStatsCallbacks < 0) {
+        throw new RuntimeException("Unexpected stats report: " + reports);
+      }
+      gotStatsReports.add(reports);
+    }
+
+    public synchronized void expectStatsCallback() {
+      ++expectedStatsCallbacks;
+    }
+
+    public synchronized LinkedList<StatsReport[]> takeStatsReports() {
+      LinkedList<StatsReport[]> got = gotStatsReports;
+      gotStatsReports = new LinkedList<StatsReport[]>();
+      return got;
+    }
+
+    public synchronized boolean areAllExpectationsSatisfied() {
+      return expectedIceCandidates <= 0 &&  // See comment in onIceCandidate.
+          expectedErrors == 0 &&
+          expectedSignalingChanges.size() == 0 &&
+          expectedIceConnectionChanges.size() == 0 &&
+          expectedIceGatheringChanges.size() == 0 &&
+          expectedAddStreamLabels.size() == 0 &&
+          expectedRemoveStreamLabels.size() == 0 &&
+          expectedSetSizeDimensions.isEmpty() &&
+          expectedFramesDelivered <= 0 &&
+          expectedStatsCallbacks == 0;
+    }
+
+    public void waitForAllExpectationsToBeSatisfied() {
+      // TODO(fischman): problems with this approach:
+      // - come up with something better than a poll loop
+      // - avoid serializing expectations explicitly; the test is not as robust
+      //   as it could be because it must place expectations between wait
+      //   statements very precisely (e.g. frame must not arrive before its
+      //   expectation, and expectation must not be registered so early as to
+      //   stall a wait).  Use callbacks to fire off dependent steps instead of
+      //   explicitly waiting, so there can be just a single wait at the end of
+      //   the test.
+      while (!areAllExpectationsSatisfied()) {
+        try {
+          Thread.sleep(10);
+        } catch (InterruptedException e) {
+          throw new RuntimeException(e);
+        }
+      }
+    }
+  }
+
+  private static class SdpObserverLatch implements SdpObserver {
+    private boolean success = false;
+    private SessionDescription sdp = null;
+    private String error = null;
+    private CountDownLatch latch = new CountDownLatch(1);
+
+    public SdpObserverLatch() {}
+
+    public void onCreateSuccess(SessionDescription sdp) {
+      this.sdp = sdp;
+      onSetSuccess();
+    }
+
+    public void onSetSuccess() {
+      success = true;
+      latch.countDown();
+    }
+
+    public void onCreateFailure(String error) {
+      onSetFailure(error);
+    }
+
+    public void onSetFailure(String error) {
+      this.error = error;
+      latch.countDown();
+    }
+
+    public boolean await() {
+      try {
+        assertTrue(latch.await(1000, TimeUnit.MILLISECONDS));
+        return getSuccess();
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    public boolean getSuccess() {
+      return success;
+    }
+
+    public SessionDescription getSdp() {
+      return sdp;
+    }
+
+    public String getError() {
+      return error;
+    }
+  }
+
+  static int videoWindowsMapped = -1;
+
+  private static class TestRenderer implements VideoRenderer.Callbacks {
+    public int width = -1;
+    public int height = -1;
+    public int numFramesDelivered = 0;
+
+    public void setSize(int width, int height) {
+      assertEquals(this.width, -1);
+      assertEquals(this.height, -1);
+      this.width = width;
+      this.height = height;
+    }
+
+    public void renderFrame(VideoRenderer.I420Frame frame) {
+      ++numFramesDelivered;
+    }
+  }
+
+  private static VideoRenderer createVideoRenderer(
+      ObserverExpectations observer) {
+    if (!RENDER_TO_GUI) {
+      return new VideoRenderer(observer);
+    }
+    ++videoWindowsMapped;
+    assertTrue(videoWindowsMapped < 4);
+    int x = videoWindowsMapped % 2 != 0 ? 700 : 0;
+    int y = videoWindowsMapped >= 2 ? 0 : 500;
+    return VideoRenderer.createGui(x, y);
+  }
+
+  // Return a weak reference to test that ownership is correctly held by
+  // PeerConnection, not by test code.
+  private static WeakReference<MediaStream> addTracksToPC(
+      PeerConnectionFactory factory, PeerConnection pc,
+      VideoSource videoSource,
+      String streamLabel, String videoTrackId, String audioTrackId,
+      ObserverExpectations observer) {
+    MediaStream lMS = factory.createLocalMediaStream(streamLabel);
+    VideoTrack videoTrack =
+        factory.createVideoTrack(videoTrackId, videoSource);
+    assertNotNull(videoTrack);
+    VideoRenderer videoRenderer = createVideoRenderer(observer);
+    assertNotNull(videoRenderer);
+    videoTrack.addRenderer(videoRenderer);
+    lMS.addTrack(videoTrack);
+    // Just for fun, let's remove and re-add the track.
+    lMS.removeTrack(videoTrack);
+    lMS.addTrack(videoTrack);
+    lMS.addTrack(factory.createAudioTrack(audioTrackId));
+    pc.addStream(lMS, new MediaConstraints());
+    return new WeakReference<MediaStream>(lMS);
+  }
+
+  private static void assertEquals(
+      SessionDescription lhs, SessionDescription rhs) {
+    assertEquals(lhs.type, rhs.type);
+    assertEquals(lhs.description, rhs.description);
+  }
+
+  @Test
+  public void testCompleteSession() throws Exception {
+    CountDownLatch testDone = new CountDownLatch(1);
+
+    PeerConnectionFactory factory = new PeerConnectionFactory();
+    MediaConstraints constraints = new MediaConstraints();
+
+    LinkedList<PeerConnection.IceServer> iceServers =
+        new LinkedList<PeerConnection.IceServer>();
+    iceServers.add(new PeerConnection.IceServer(
+        "stun:stun.l.google.com:19302"));
+    iceServers.add(new PeerConnection.IceServer(
+        "turn:fake.example.com", "fakeUsername", "fakePassword"));
+    ObserverExpectations offeringExpectations = new ObserverExpectations();
+    PeerConnection offeringPC = factory.createPeerConnection(
+        iceServers, constraints, offeringExpectations);
+    assertNotNull(offeringPC);
+
+    ObserverExpectations answeringExpectations = new ObserverExpectations();
+    PeerConnection answeringPC = factory.createPeerConnection(
+        iceServers, constraints, answeringExpectations);
+    assertNotNull(answeringPC);
+
+    // We want to use the same camera for offerer & answerer, so create it here
+    // instead of in addTracksToPC.
+    VideoSource videoSource = factory.createVideoSource(
+        VideoCapturer.create(""), new MediaConstraints());
+
+    // TODO(fischman): the track ids here and in the addTracksToPC() call
+    // below hard-code the <mediaStreamLabel>[av]<index> scheme used in the
+    // serialized SDP, because the C++ API doesn't auto-translate.
+    // Drop |label| params from {Audio,Video}Track-related APIs once
+    // https://code.google.com/p/webrtc/issues/detail?id=1253 is fixed.
+    WeakReference<MediaStream> oLMS = addTracksToPC(
+        factory, offeringPC, videoSource, "oLMS", "oLMSv0", "oLMSa0",
+        offeringExpectations);
+
+    SdpObserverLatch sdpLatch = new SdpObserverLatch();
+    offeringPC.createOffer(sdpLatch, constraints);
+    assertTrue(sdpLatch.await());
+    SessionDescription offerSdp = sdpLatch.getSdp();
+    assertEquals(offerSdp.type, SessionDescription.Type.OFFER);
+    assertFalse(offerSdp.description.isEmpty());
+
+    sdpLatch = new SdpObserverLatch();
+    answeringExpectations.expectSignalingChange(
+        SignalingState.HAVE_REMOTE_OFFER);
+    answeringExpectations.expectAddStream("oLMS");
+    answeringPC.setRemoteDescription(sdpLatch, offerSdp);
+    answeringExpectations.waitForAllExpectationsToBeSatisfied();
+    assertEquals(
+        PeerConnection.SignalingState.STABLE, offeringPC.signalingState());
+    assertTrue(sdpLatch.await());
+    assertNull(sdpLatch.getSdp());
+
+    WeakReference<MediaStream> aLMS = addTracksToPC(
+        factory, answeringPC, videoSource, "aLMS", "aLMSv0", "aLMSa0",
+        answeringExpectations);
+
+    sdpLatch = new SdpObserverLatch();
+    answeringPC.createAnswer(sdpLatch, constraints);
+    assertTrue(sdpLatch.await());
+    SessionDescription answerSdp = sdpLatch.getSdp();
+    assertEquals(answerSdp.type, SessionDescription.Type.ANSWER);
+    assertFalse(answerSdp.description.isEmpty());
+
+    offeringExpectations.expectIceCandidates(2);
+    answeringExpectations.expectIceCandidates(2);
+
+    sdpLatch = new SdpObserverLatch();
+    answeringExpectations.expectSignalingChange(SignalingState.STABLE);
+    answeringPC.setLocalDescription(sdpLatch, answerSdp);
+    assertTrue(sdpLatch.await());
+    assertNull(sdpLatch.getSdp());
+
+    sdpLatch = new SdpObserverLatch();
+    offeringExpectations.expectSignalingChange(SignalingState.HAVE_LOCAL_OFFER);
+    offeringPC.setLocalDescription(sdpLatch, offerSdp);
+    assertTrue(sdpLatch.await());
+    assertNull(sdpLatch.getSdp());
+    sdpLatch = new SdpObserverLatch();
+    offeringExpectations.expectSignalingChange(SignalingState.STABLE);
+    offeringExpectations.expectAddStream("aLMS");
+    offeringPC.setRemoteDescription(sdpLatch, answerSdp);
+    assertTrue(sdpLatch.await());
+    assertNull(sdpLatch.getSdp());
+
+    offeringExpectations.waitForAllExpectationsToBeSatisfied();
+    answeringExpectations.waitForAllExpectationsToBeSatisfied();
+
+    assertEquals(offeringPC.getLocalDescription().type, offerSdp.type);
+    assertEquals(offeringPC.getRemoteDescription().type, answerSdp.type);
+    assertEquals(answeringPC.getLocalDescription().type, answerSdp.type);
+    assertEquals(answeringPC.getRemoteDescription().type, offerSdp.type);
+
+    if (!RENDER_TO_GUI) {
+      offeringExpectations.expectSetSize(640, 480);
+      offeringExpectations.expectSetSize(640, 480);
+      answeringExpectations.expectSetSize(640, 480);
+      answeringExpectations.expectSetSize(640, 480);
+      // Wait for at least some frames to be delivered at each end (number
+      // chosen arbitrarily).
+      offeringExpectations.expectFramesDelivered(10);
+      answeringExpectations.expectFramesDelivered(10);
+    }
+
+    offeringExpectations.expectIceConnectionChange(
+        IceConnectionState.CHECKING);
+    offeringExpectations.expectIceConnectionChange(
+        IceConnectionState.CONNECTED);
+    answeringExpectations.expectIceConnectionChange(
+        IceConnectionState.CHECKING);
+    answeringExpectations.expectIceConnectionChange(
+        IceConnectionState.CONNECTED);
+
+    offeringExpectations.expectIceGatheringChange(IceGatheringState.COMPLETE);
+    answeringExpectations.expectIceGatheringChange(IceGatheringState.COMPLETE);
+
+    for (IceCandidate candidate : offeringExpectations.gotIceCandidates) {
+      answeringPC.addIceCandidate(candidate);
+    }
+    offeringExpectations.gotIceCandidates.clear();
+    for (IceCandidate candidate : answeringExpectations.gotIceCandidates) {
+      offeringPC.addIceCandidate(candidate);
+    }
+    answeringExpectations.gotIceCandidates.clear();
+
+    offeringExpectations.waitForAllExpectationsToBeSatisfied();
+    answeringExpectations.waitForAllExpectationsToBeSatisfied();
+
+    assertEquals(
+        PeerConnection.SignalingState.STABLE, offeringPC.signalingState());
+    assertEquals(
+        PeerConnection.SignalingState.STABLE, answeringPC.signalingState());
+
+    if (RENDER_TO_GUI) {
+      try {
+        Thread.sleep(3000);
+      } catch (Throwable t) {
+        throw new RuntimeException(t);
+      }
+    }
+
+    // TODO(fischman) MOAR test ideas:
+    // - Test that PC.removeStream() works; requires a second
+    //   createOffer/createAnswer dance.
+    // - audit each place that uses |constraints| for specifying non-trivial
+    //   constraints (and ensure they're honored).
+    // - test error cases
+    // - ensure reasonable coverage of _jni.cc is achieved.  Coverage is
+    //   extra-important because of all the free-text (class/method names, etc)
+    //   in JNI-style programming; make sure no typos!
+    // - Test that shutdown mid-interaction is crash-free.
+
+    // Free the Java-land objects, collect them, and sleep a bit to make sure we
+    // don't get late-arrival crashes after the Java-land objects have been
+    // freed.
+    shutdownPC(offeringPC, offeringExpectations);
+    offeringPC = null;
+    shutdownPC(answeringPC, answeringExpectations);
+    answeringPC = null;
+    System.gc();
+    Thread.sleep(100);
+  }
+
+  private static void shutdownPC(
+      PeerConnection pc, ObserverExpectations expectations) {
+    expectations.expectStatsCallback();
+    assertTrue(pc.getStats(expectations, null));
+    expectations.waitForAllExpectationsToBeSatisfied();
+    expectations.expectIceConnectionChange(IceConnectionState.CLOSED);
+    expectations.expectSignalingChange(SignalingState.CLOSED);
+    pc.close();
+    expectations.waitForAllExpectationsToBeSatisfied();
+    expectations.expectStatsCallback();
+    assertTrue(pc.getStats(expectations, null));
+    expectations.waitForAllExpectationsToBeSatisfied();
+
+    System.out.println("FYI stats: ");
+    int reportIndex = -1;
+    for (StatsReport[] reports : expectations.takeStatsReports()) {
+      System.out.println(" Report #" + (++reportIndex));
+      for (int i = 0; i < reports.length; ++i) {
+        System.out.println("  " + reports[i].toString());
+      }
+    }
+    assertEquals(1, reportIndex);
+    System.out.println("End stats.");
+
+    pc.dispose();
+  }
+}
diff --git a/talk/app/webrtc/jsep.h b/talk/app/webrtc/jsep.h
new file mode 100644
index 0000000..5f28fc8
--- /dev/null
+++ b/talk/app/webrtc/jsep.h
@@ -0,0 +1,164 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Interfaces matching the draft-ietf-rtcweb-jsep-01.
+
+#ifndef TALK_APP_WEBRTC_JSEP_H_
+#define TALK_APP_WEBRTC_JSEP_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/refcount.h"
+
+namespace cricket {
+class SessionDescription;
+class Candidate;
+}  // namespace cricket
+
+namespace webrtc {
+
+struct SdpParseError {
+ public:
+  // The sdp line that causes the error.
+  std::string line;
+  // Explains the error.
+  std::string description;
+};
+
+// Class representation of an ICE candidate.
+// An instance of this interface is supposed to be owned by one class at
+// a time and is therefore not expected to be thread safe.
+class IceCandidateInterface {
+ public:
+  virtual ~IceCandidateInterface() {}
+  /// If present, this contains the identierfier of the "media stream
+  // identification" as defined in [RFC 3388] for m-line this candidate is
+  // assocated with.
+  virtual std::string sdp_mid() const = 0;
+  // This indeicates the index (starting at zero) of m-line in the SDP this
+  // candidate is assocated with.
+  virtual int sdp_mline_index() const = 0;
+  virtual const cricket::Candidate& candidate() const = 0;
+  // Creates a SDP-ized form of this candidate.
+  virtual bool ToString(std::string* out) const = 0;
+};
+
+// Creates a IceCandidateInterface based on SDP string.
+// Returns NULL if the sdp string can't be parsed.
+// TODO(ronghuawu): Deprecated.
+IceCandidateInterface* CreateIceCandidate(const std::string& sdp_mid,
+                                          int sdp_mline_index,
+                                          const std::string& sdp);
+
+// |error| can be NULL if doesn't care about the failure reason.
+IceCandidateInterface* CreateIceCandidate(const std::string& sdp_mid,
+                                          int sdp_mline_index,
+                                          const std::string& sdp,
+                                          SdpParseError* error);
+
+// This class represents a collection of candidates for a specific m-line.
+// This class is used in SessionDescriptionInterface to represent all known
+// candidates for a certain m-line.
+class IceCandidateCollection {
+ public:
+  virtual ~IceCandidateCollection() {}
+  virtual size_t count() const = 0;
+  // Returns true if an equivalent |candidate| exist in the collection.
+  virtual bool HasCandidate(const IceCandidateInterface* candidate) const = 0;
+  virtual const IceCandidateInterface* at(size_t index) const = 0;
+};
+
+// Class representation of a Session description.
+// An instance of this interface is supposed to be owned by one class at
+// a time and is therefore not expected to be thread safe.
+class SessionDescriptionInterface {
+ public:
+  // Supported types:
+  static const char kOffer[];
+  static const char kPrAnswer[];
+  static const char kAnswer[];
+
+  virtual ~SessionDescriptionInterface() {}
+  virtual cricket::SessionDescription* description() = 0;
+  virtual const cricket::SessionDescription* description() const = 0;
+  // Get the session id and session version, which are defined based on
+  // RFC 4566 for the SDP o= line.
+  virtual std::string session_id() const = 0;
+  virtual std::string session_version() const = 0;
+  virtual std::string type() const = 0;
+  // Adds the specified candidate to the description.
+  // Ownership is not transferred.
+  // Returns false if the session description does not have a media section that
+  // corresponds to the |candidate| label.
+  virtual bool AddCandidate(const IceCandidateInterface* candidate) = 0;
+  // Returns the number of m- lines in the session description.
+  virtual size_t number_of_mediasections() const = 0;
+  // Returns a collection of all candidates that belong to a certain m-line
+  virtual const IceCandidateCollection* candidates(
+      size_t mediasection_index) const = 0;
+  // Serializes the description to SDP.
+  virtual bool ToString(std::string* out) const = 0;
+};
+
+// Creates a SessionDescriptionInterface based on SDP string and the type.
+// Returns NULL if the sdp string can't be parsed or the type is unsupported.
+// TODO(ronghuawu): Deprecated.
+SessionDescriptionInterface* CreateSessionDescription(const std::string& type,
+                                                      const std::string& sdp);
+
+// |error| can be NULL if doesn't care about the failure reason.
+SessionDescriptionInterface* CreateSessionDescription(const std::string& type,
+                                                      const std::string& sdp,
+                                                      SdpParseError* error);
+
+// Jsep CreateOffer and CreateAnswer callback interface.
+class CreateSessionDescriptionObserver : public talk_base::RefCountInterface {
+ public:
+  // The implementation of the CreateSessionDescriptionObserver takes
+  // the ownership of the |desc|.
+  virtual void OnSuccess(SessionDescriptionInterface* desc) = 0;
+  virtual void OnFailure(const std::string& error) = 0;
+
+ protected:
+  ~CreateSessionDescriptionObserver() {}
+};
+
+// Jsep SetLocalDescription and SetRemoteDescription callback interface.
+class SetSessionDescriptionObserver : public talk_base::RefCountInterface {
+ public:
+  virtual void OnSuccess() = 0;
+  virtual void OnFailure(const std::string& error) = 0;
+
+ protected:
+  ~SetSessionDescriptionObserver() {}
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_JSEP_H_
diff --git a/talk/app/webrtc/jsepicecandidate.cc b/talk/app/webrtc/jsepicecandidate.cc
new file mode 100644
index 0000000..13cc812
--- /dev/null
+++ b/talk/app/webrtc/jsepicecandidate.cc
@@ -0,0 +1,105 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/jsepicecandidate.h"
+
+#include <vector>
+
+#include "talk/app/webrtc/webrtcsdp.h"
+#include "talk/base/stringencode.h"
+
+namespace webrtc {
+
+IceCandidateInterface* CreateIceCandidate(const std::string& sdp_mid,
+                                          int sdp_mline_index,
+                                          const std::string& sdp) {
+  return CreateIceCandidate(sdp_mid, sdp_mline_index, sdp, NULL);
+}
+
+IceCandidateInterface* CreateIceCandidate(const std::string& sdp_mid,
+                                          int sdp_mline_index,
+                                          const std::string& sdp,
+                                          SdpParseError* error) {
+  JsepIceCandidate* jsep_ice = new JsepIceCandidate(sdp_mid, sdp_mline_index);
+  if (!jsep_ice->Initialize(sdp, error)) {
+    delete jsep_ice;
+    return NULL;
+  }
+  return jsep_ice;
+}
+
+JsepIceCandidate::JsepIceCandidate(const std::string& sdp_mid,
+                                   int sdp_mline_index)
+    : sdp_mid_(sdp_mid),
+      sdp_mline_index_(sdp_mline_index) {
+}
+
+JsepIceCandidate::JsepIceCandidate(const std::string& sdp_mid,
+                                   int sdp_mline_index,
+                                   const cricket::Candidate& candidate)
+    : sdp_mid_(sdp_mid),
+      sdp_mline_index_(sdp_mline_index),
+      candidate_(candidate) {
+}
+
+JsepIceCandidate::~JsepIceCandidate() {
+}
+
+bool JsepIceCandidate::Initialize(const std::string& sdp, SdpParseError* err) {
+  return SdpDeserializeCandidate(sdp, this, err);
+}
+
+bool JsepIceCandidate::ToString(std::string* out) const {
+  if (!out)
+    return false;
+  *out = SdpSerializeCandidate(*this);
+  return !out->empty();
+}
+
+JsepCandidateCollection::~JsepCandidateCollection() {
+  for (std::vector<JsepIceCandidate*>::iterator it = candidates_.begin();
+       it != candidates_.end(); ++it) {
+    delete *it;
+  }
+}
+
+bool JsepCandidateCollection::HasCandidate(
+    const IceCandidateInterface* candidate) const {
+  bool ret = false;
+  for (std::vector<JsepIceCandidate*>::const_iterator it = candidates_.begin();
+      it != candidates_.end(); ++it) {
+    if ((*it)->sdp_mid() == candidate->sdp_mid() &&
+        (*it)->sdp_mline_index() == candidate->sdp_mline_index() &&
+        (*it)->candidate().IsEquivalent(candidate->candidate())) {
+      ret = true;
+      break;
+    }
+  }
+  return ret;
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/jsepicecandidate.h b/talk/app/webrtc/jsepicecandidate.h
new file mode 100644
index 0000000..54de950
--- /dev/null
+++ b/talk/app/webrtc/jsepicecandidate.h
@@ -0,0 +1,92 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Implements the IceCandidateInterface.
+
+#ifndef TALK_APP_WEBRTC_JSEPICECANDIDATE_H_
+#define TALK_APP_WEBRTC_JSEPICECANDIDATE_H_
+
+#include <string>
+
+#include "talk/app/webrtc/jsep.h"
+#include "talk/base/constructormagic.h"
+#include "talk/p2p/base/candidate.h"
+
+namespace webrtc {
+
+class JsepIceCandidate : public IceCandidateInterface {
+ public:
+  JsepIceCandidate(const std::string& sdp_mid, int sdp_mline_index);
+  JsepIceCandidate(const std::string& sdp_mid, int sdp_mline_index,
+                   const cricket::Candidate& candidate);
+  ~JsepIceCandidate();
+  // |error| can be NULL if don't care about the failure reason.
+  bool Initialize(const std::string& sdp, SdpParseError* err);
+  void SetCandidate(const cricket::Candidate& candidate) {
+    candidate_ = candidate;
+  }
+
+  virtual std::string sdp_mid() const { return sdp_mid_; }
+  virtual int sdp_mline_index() const { return sdp_mline_index_; }
+  virtual const cricket::Candidate& candidate() const {
+    return candidate_;
+  }
+
+  virtual bool ToString(std::string* out) const;
+
+ private:
+  std::string sdp_mid_;
+  int sdp_mline_index_;
+  cricket::Candidate candidate_;
+
+  DISALLOW_COPY_AND_ASSIGN(JsepIceCandidate);
+};
+
+// Implementation of IceCandidateCollection.
+// This implementation stores JsepIceCandidates.
+class JsepCandidateCollection : public IceCandidateCollection {
+ public:
+  ~JsepCandidateCollection();
+  virtual size_t count() const {
+    return candidates_.size();
+  }
+  virtual bool HasCandidate(const IceCandidateInterface* candidate) const;
+  // Adds and takes ownership of the JsepIceCandidate.
+  virtual void add(JsepIceCandidate* candidate) {
+    candidates_.push_back(candidate);
+  }
+  virtual const IceCandidateInterface* at(size_t index) const {
+    return candidates_[index];
+  }
+
+ private:
+  std::vector<JsepIceCandidate*> candidates_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_JSEPICECANDIDATE_H_
diff --git a/talk/app/webrtc/jsepsessiondescription.cc b/talk/app/webrtc/jsepsessiondescription.cc
new file mode 100644
index 0000000..bc65ca5
--- /dev/null
+++ b/talk/app/webrtc/jsepsessiondescription.cc
@@ -0,0 +1,193 @@
+/* libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/jsepsessiondescription.h"
+
+#include "talk/app/webrtc/webrtcsdp.h"
+#include "talk/base/stringencode.h"
+#include "talk/session/media/mediasession.h"
+
+using talk_base::scoped_ptr;
+using cricket::SessionDescription;
+
+namespace webrtc {
+
+static const char* kSupportedTypes[] = {
+    JsepSessionDescription::kOffer,
+    JsepSessionDescription::kPrAnswer,
+    JsepSessionDescription::kAnswer
+};
+
+static bool IsTypeSupported(const std::string& type) {
+  bool type_supported = false;
+  for (size_t i = 0; i < ARRAY_SIZE(kSupportedTypes); ++i) {
+    if (kSupportedTypes[i] == type) {
+      type_supported = true;
+      break;
+    }
+  }
+  return type_supported;
+}
+
+const char SessionDescriptionInterface::kOffer[] = "offer";
+const char SessionDescriptionInterface::kPrAnswer[] = "pranswer";
+const char SessionDescriptionInterface::kAnswer[] = "answer";
+
+const int JsepSessionDescription::kDefaultVideoCodecId = 100;
+const int JsepSessionDescription::kDefaultVideoCodecFramerate = 30;
+const char JsepSessionDescription::kDefaultVideoCodecName[] = "VP8";
+const int JsepSessionDescription::kMaxVideoCodecWidth = 1280;
+const int JsepSessionDescription::kMaxVideoCodecHeight = 720;
+const int JsepSessionDescription::kDefaultVideoCodecPreference = 1;
+
+SessionDescriptionInterface* CreateSessionDescription(const std::string& type,
+                                                      const std::string& sdp) {
+  return CreateSessionDescription(type, sdp, NULL);
+}
+
+SessionDescriptionInterface* CreateSessionDescription(const std::string& type,
+                                                      const std::string& sdp,
+                                                      SdpParseError* error) {
+  if (!IsTypeSupported(type)) {
+    return NULL;
+  }
+
+  JsepSessionDescription* jsep_desc = new JsepSessionDescription(type);
+  if (!jsep_desc->Initialize(sdp, error)) {
+    delete jsep_desc;
+    return NULL;
+  }
+  return jsep_desc;
+}
+
+JsepSessionDescription::JsepSessionDescription(const std::string& type)
+    : type_(type) {
+}
+
+JsepSessionDescription::~JsepSessionDescription() {}
+
+bool JsepSessionDescription::Initialize(
+    cricket::SessionDescription* description,
+    const std::string& session_id,
+    const std::string& session_version) {
+  if (!description)
+    return false;
+
+  session_id_ = session_id;
+  session_version_ = session_version;
+  description_.reset(description);
+  candidate_collection_.resize(number_of_mediasections());
+  return true;
+}
+
+bool JsepSessionDescription::Initialize(const std::string& sdp,
+                                        SdpParseError* error) {
+  return SdpDeserialize(sdp, this, error);
+}
+
+bool JsepSessionDescription::AddCandidate(
+    const IceCandidateInterface* candidate) {
+  if (!candidate || candidate->sdp_mline_index() < 0)
+    return false;
+  size_t mediasection_index = 0;
+  if (!GetMediasectionIndex(candidate, &mediasection_index)) {
+    return false;
+  }
+  if (mediasection_index >= number_of_mediasections())
+    return false;
+  if (candidate_collection_[mediasection_index].HasCandidate(candidate)) {
+    return true;  // Silently ignore this candidate if we already have it.
+  }
+  const std::string content_name =
+      description_->contents()[mediasection_index].name;
+  const cricket::TransportInfo* transport_info =
+      description_->GetTransportInfoByName(content_name);
+  if (!transport_info) {
+    return false;
+  }
+
+  cricket::Candidate updated_candidate = candidate->candidate();
+  if (updated_candidate.username().empty()) {
+    updated_candidate.set_username(transport_info->description.ice_ufrag);
+  }
+  if (updated_candidate.password().empty()) {
+    updated_candidate.set_password(transport_info->description.ice_pwd);
+  }
+
+  candidate_collection_[mediasection_index].add(
+       new JsepIceCandidate(candidate->sdp_mid(),
+                            mediasection_index,
+                            updated_candidate));
+  return true;
+}
+
+size_t JsepSessionDescription::number_of_mediasections() const {
+  if (!description_)
+    return 0;
+  return description_->contents().size();
+}
+
+const IceCandidateCollection* JsepSessionDescription::candidates(
+    size_t mediasection_index) const {
+  if (mediasection_index >= candidate_collection_.size())
+    return NULL;
+  return &candidate_collection_[mediasection_index];
+}
+
+bool JsepSessionDescription::ToString(std::string* out) const {
+  if (!description_ || !out)
+    return false;
+  *out = SdpSerialize(*this);
+  return !out->empty();
+}
+
+bool JsepSessionDescription::GetMediasectionIndex(
+    const IceCandidateInterface* candidate,
+    size_t* index) {
+  if (!candidate || !index) {
+    return false;
+  }
+  *index = static_cast<size_t>(candidate->sdp_mline_index());
+  if (description_ && !candidate->sdp_mid().empty()) {
+    bool found = false;
+    // Try to match the sdp_mid with content name.
+    for (size_t i = 0; i < description_->contents().size(); ++i) {
+      if (candidate->sdp_mid() == description_->contents().at(i).name) {
+        *index = i;
+        found = true;
+        break;
+      }
+    }
+    if (!found) {
+      // If the sdp_mid is presented but we can't find a match, we consider
+      // this as an error.
+      return false;
+    }
+  }
+  return true;
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/jsepsessiondescription.h b/talk/app/webrtc/jsepsessiondescription.h
new file mode 100644
index 0000000..7ca7a22
--- /dev/null
+++ b/talk/app/webrtc/jsepsessiondescription.h
@@ -0,0 +1,106 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Implements the SessionDescriptionInterface.
+
+#ifndef TALK_APP_WEBRTC_JSEPSESSIONDESCRIPTION_H_
+#define TALK_APP_WEBRTC_JSEPSESSIONDESCRIPTION_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/app/webrtc/jsep.h"
+#include "talk/app/webrtc/jsepicecandidate.h"
+#include "talk/base/scoped_ptr.h"
+
+namespace cricket {
+class SessionDescription;
+}
+
+namespace webrtc {
+
+class JsepSessionDescription : public SessionDescriptionInterface {
+ public:
+  explicit JsepSessionDescription(const std::string& type);
+  virtual ~JsepSessionDescription();
+
+  // |error| can be NULL if don't care about the failure reason.
+  bool Initialize(const std::string& sdp, SdpParseError* error);
+
+  // Takes ownership of |description|.
+  bool Initialize(cricket::SessionDescription* description,
+      const std::string& session_id,
+      const std::string& session_version);
+
+  virtual cricket::SessionDescription* description() {
+    return description_.get();
+  }
+  virtual const cricket::SessionDescription* description() const {
+    return description_.get();
+  }
+  virtual std::string session_id() const {
+    return session_id_;
+  }
+  virtual std::string session_version() const {
+    return session_version_;
+  }
+  virtual std::string type() const {
+    return type_;
+  }
+  // Allow changing the type. Used for testing.
+  void set_type(const std::string& type) { type_ = type; }
+  virtual bool AddCandidate(const IceCandidateInterface* candidate);
+  virtual size_t number_of_mediasections() const;
+  virtual const IceCandidateCollection* candidates(
+      size_t mediasection_index) const;
+  virtual bool ToString(std::string* out) const;
+
+  // Default video encoder settings. The resolution is the max resolution.
+  // TODO(perkj): Implement proper negotiation of video resolution.
+  static const int kDefaultVideoCodecId;
+  static const int kDefaultVideoCodecFramerate;
+  static const char kDefaultVideoCodecName[];
+  static const int kMaxVideoCodecWidth;
+  static const int kMaxVideoCodecHeight;
+  static const int kDefaultVideoCodecPreference;
+
+ private:
+  talk_base::scoped_ptr<cricket::SessionDescription> description_;
+  std::string session_id_;
+  std::string session_version_;
+  std::string type_;
+  std::vector<JsepCandidateCollection> candidate_collection_;
+
+  bool GetMediasectionIndex(const IceCandidateInterface* candidate,
+                            size_t* index);
+
+  DISALLOW_COPY_AND_ASSIGN(JsepSessionDescription);
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_JSEPSESSIONDESCRIPTION_H_
diff --git a/talk/app/webrtc/jsepsessiondescription_unittest.cc b/talk/app/webrtc/jsepsessiondescription_unittest.cc
new file mode 100644
index 0000000..83f67cb
--- /dev/null
+++ b/talk/app/webrtc/jsepsessiondescription_unittest.cc
@@ -0,0 +1,223 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/jsepicecandidate.h"
+#include "talk/app/webrtc/jsepsessiondescription.h"
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stringencode.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/sessiondescription.h"
+#include "talk/session/media/mediasession.h"
+
+using webrtc::IceCandidateCollection;
+using webrtc::IceCandidateInterface;
+using webrtc::JsepIceCandidate;
+using webrtc::JsepSessionDescription;
+using webrtc::SessionDescriptionInterface;
+using talk_base::scoped_ptr;
+
+static const char kCandidateUfrag[] = "ufrag";
+static const char kCandidatePwd[] = "pwd";
+static const char kCandidateUfragVoice[] = "ufrag_voice";
+static const char kCandidatePwdVoice[] = "pwd_voice";
+static const char kCandidateUfragVideo[] = "ufrag_video";
+static const char kCandidatePwdVideo[] = "pwd_video";
+
+// This creates a session description with both audio and video media contents.
+// In SDP this is described by two m lines, one audio and one video.
+static cricket::SessionDescription* CreateCricketSessionDescription() {
+  cricket::SessionDescription* desc(new cricket::SessionDescription());
+  // AudioContentDescription
+  scoped_ptr<cricket::AudioContentDescription> audio(
+      new cricket::AudioContentDescription());
+
+  // VideoContentDescription
+  scoped_ptr<cricket::VideoContentDescription> video(
+      new cricket::VideoContentDescription());
+
+  audio->AddCodec(cricket::AudioCodec(103, "ISAC", 16000, 0, 0, 0));
+  desc->AddContent(cricket::CN_AUDIO, cricket::NS_JINGLE_RTP,
+                   audio.release());
+
+  video->AddCodec(cricket::VideoCodec(120, "VP8", 640, 480, 30, 0));
+  desc->AddContent(cricket::CN_VIDEO, cricket::NS_JINGLE_RTP,
+                   video.release());
+
+  EXPECT_TRUE(desc->AddTransportInfo(
+      cricket::TransportInfo(
+                             cricket::CN_AUDIO,
+                             cricket::TransportDescription(
+                                 cricket::NS_GINGLE_P2P,
+                                 std::vector<std::string>(),
+                                 kCandidateUfragVoice, kCandidatePwdVoice,
+                                 cricket::ICEMODE_FULL, NULL,
+                                 cricket::Candidates()))));
+  EXPECT_TRUE(desc->AddTransportInfo(
+      cricket::TransportInfo(cricket::CN_VIDEO,
+                             cricket::TransportDescription(
+                                 cricket::NS_GINGLE_P2P,
+                                 std::vector<std::string>(),
+                                 kCandidateUfragVideo, kCandidatePwdVideo,
+                                 cricket::ICEMODE_FULL, NULL,
+                                 cricket::Candidates()))));
+  return desc;
+}
+
+class JsepSessionDescriptionTest : public testing::Test {
+ protected:
+  virtual void SetUp() {
+    int port = 1234;
+    talk_base::SocketAddress address("127.0.0.1", port++);
+    cricket::Candidate candidate("rtp", cricket::ICE_CANDIDATE_COMPONENT_RTP,
+                                 "udp", address, 1, "",
+                                 "", "local", "eth0", 0, "1");
+    candidate_ = candidate;
+    const std::string session_id =
+        talk_base::ToString(talk_base::CreateRandomId64());
+    const std::string session_version =
+        talk_base::ToString(talk_base::CreateRandomId());
+    jsep_desc_.reset(new JsepSessionDescription("dummy"));
+    ASSERT_TRUE(jsep_desc_->Initialize(CreateCricketSessionDescription(),
+        session_id, session_version));
+  }
+
+  std::string Serialize(const SessionDescriptionInterface* desc) {
+    std::string sdp;
+    EXPECT_TRUE(desc->ToString(&sdp));
+    EXPECT_FALSE(sdp.empty());
+    return sdp;
+  }
+
+  SessionDescriptionInterface* DeSerialize(const std::string& sdp) {
+    JsepSessionDescription* desc(new JsepSessionDescription("dummy"));
+    EXPECT_TRUE(desc->Initialize(sdp, NULL));
+    return desc;
+  }
+
+  cricket::Candidate candidate_;
+  talk_base::scoped_ptr<JsepSessionDescription> jsep_desc_;
+};
+
+// Test that number_of_mediasections() returns the number of media contents in
+// a session description.
+TEST_F(JsepSessionDescriptionTest, CheckSessionDescription) {
+  EXPECT_EQ(2u, jsep_desc_->number_of_mediasections());
+}
+
+// Test that we can add a candidate to a session description.
+TEST_F(JsepSessionDescriptionTest, AddCandidateWithoutMid) {
+  JsepIceCandidate jsep_candidate("", 0, candidate_);
+  EXPECT_TRUE(jsep_desc_->AddCandidate(&jsep_candidate));
+  const IceCandidateCollection* ice_candidates = jsep_desc_->candidates(0);
+  ASSERT_TRUE(ice_candidates != NULL);
+  EXPECT_EQ(1u, ice_candidates->count());
+  const IceCandidateInterface* ice_candidate = ice_candidates->at(0);
+  ASSERT_TRUE(ice_candidate != NULL);
+  candidate_.set_username(kCandidateUfragVoice);
+  candidate_.set_password(kCandidatePwdVoice);
+  EXPECT_TRUE(ice_candidate->candidate().IsEquivalent(candidate_));
+  EXPECT_EQ(0, ice_candidate->sdp_mline_index());
+  EXPECT_EQ(0u, jsep_desc_->candidates(1)->count());
+}
+
+TEST_F(JsepSessionDescriptionTest, AddCandidateWithMid) {
+  // mid and m-line index don't match, in this case mid is preferred.
+  JsepIceCandidate jsep_candidate("video", 0, candidate_);
+  EXPECT_TRUE(jsep_desc_->AddCandidate(&jsep_candidate));
+  EXPECT_EQ(0u, jsep_desc_->candidates(0)->count());
+  const IceCandidateCollection* ice_candidates = jsep_desc_->candidates(1);
+  ASSERT_TRUE(ice_candidates != NULL);
+  EXPECT_EQ(1u, ice_candidates->count());
+  const IceCandidateInterface* ice_candidate = ice_candidates->at(0);
+  ASSERT_TRUE(ice_candidate != NULL);
+  candidate_.set_username(kCandidateUfragVideo);
+  candidate_.set_password(kCandidatePwdVideo);
+  EXPECT_TRUE(ice_candidate->candidate().IsEquivalent(candidate_));
+  // The mline index should have been updated according to mid.
+  EXPECT_EQ(1, ice_candidate->sdp_mline_index());
+}
+
+TEST_F(JsepSessionDescriptionTest, AddCandidateAlreadyHasUfrag) {
+  candidate_.set_username(kCandidateUfrag);
+  candidate_.set_password(kCandidatePwd);
+  JsepIceCandidate jsep_candidate("audio", 0, candidate_);
+  EXPECT_TRUE(jsep_desc_->AddCandidate(&jsep_candidate));
+  const IceCandidateCollection* ice_candidates = jsep_desc_->candidates(0);
+  ASSERT_TRUE(ice_candidates != NULL);
+  EXPECT_EQ(1u, ice_candidates->count());
+  const IceCandidateInterface* ice_candidate = ice_candidates->at(0);
+  ASSERT_TRUE(ice_candidate != NULL);
+  candidate_.set_username(kCandidateUfrag);
+  candidate_.set_password(kCandidatePwd);
+  EXPECT_TRUE(ice_candidate->candidate().IsEquivalent(candidate_));
+
+  EXPECT_EQ(0u, jsep_desc_->candidates(1)->count());
+}
+
+// Test that we can not add a candidate if there is no corresponding media
+// content in the session description.
+TEST_F(JsepSessionDescriptionTest, AddBadCandidate) {
+  JsepIceCandidate bad_candidate1("", 55, candidate_);
+  EXPECT_FALSE(jsep_desc_->AddCandidate(&bad_candidate1));
+
+  JsepIceCandidate bad_candidate2("some weird mid", 0, candidate_);
+  EXPECT_FALSE(jsep_desc_->AddCandidate(&bad_candidate2));
+}
+
+// Test that we can serialize a JsepSessionDescription and deserialize it again.
+TEST_F(JsepSessionDescriptionTest, SerializeDeserialize) {
+  std::string sdp = Serialize(jsep_desc_.get());
+
+  scoped_ptr<SessionDescriptionInterface> parsed_jsep_desc(DeSerialize(sdp));
+  EXPECT_EQ(2u, parsed_jsep_desc->number_of_mediasections());
+
+  std::string parsed_sdp = Serialize(parsed_jsep_desc.get());
+  EXPECT_EQ(sdp, parsed_sdp);
+}
+
+// Tests that we can serialize and deserialize a JsepSesssionDescription
+// with candidates.
+TEST_F(JsepSessionDescriptionTest, SerializeDeserializeWithCandidates) {
+  std::string sdp = Serialize(jsep_desc_.get());
+
+  // Add a candidate and check that the serialized result is different.
+  JsepIceCandidate jsep_candidate("audio", 0, candidate_);
+  EXPECT_TRUE(jsep_desc_->AddCandidate(&jsep_candidate));
+  std::string sdp_with_candidate = Serialize(jsep_desc_.get());
+  EXPECT_NE(sdp, sdp_with_candidate);
+
+  scoped_ptr<SessionDescriptionInterface> parsed_jsep_desc(
+      DeSerialize(sdp_with_candidate));
+  std::string parsed_sdp_with_candidate = Serialize(parsed_jsep_desc.get());
+
+  EXPECT_EQ(sdp_with_candidate, parsed_sdp_with_candidate);
+}
diff --git a/talk/app/webrtc/localaudiosource.cc b/talk/app/webrtc/localaudiosource.cc
new file mode 100644
index 0000000..9706c07
--- /dev/null
+++ b/talk/app/webrtc/localaudiosource.cc
@@ -0,0 +1,127 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/localaudiosource.h"
+
+#include <vector>
+
+#include "talk/media/base/mediaengine.h"
+#include "talk/app/webrtc/mediaconstraintsinterface.h"
+
+using webrtc::MediaConstraintsInterface;
+using webrtc::MediaSourceInterface;
+
+namespace webrtc {
+
+// Constraint keys.
+// They are declared as static members in mediaconstraintsinterface.h
+const char MediaConstraintsInterface::kEchoCancellation[] =
+    "googEchoCancellation";
+const char MediaConstraintsInterface::kExperimentalEchoCancellation[] =
+    "googEchoCancellation2";
+const char MediaConstraintsInterface::kAutoGainControl[] =
+    "googAutoGainControl";
+const char MediaConstraintsInterface::kExperimentalAutoGainControl[] =
+    "googAutoGainControl2";
+const char MediaConstraintsInterface::kNoiseSuppression[] =
+    "googNoiseSuppression";
+const char MediaConstraintsInterface::kHighpassFilter[] =
+    "googHighpassFilter";
+const char MediaConstraintsInterface::kInternalAecDump[] = "internalAecDump";
+
+namespace {
+
+// Convert constraints to audio options. Return false if constraints are
+// invalid.
+bool FromConstraints(const MediaConstraintsInterface::Constraints& constraints,
+                     cricket::AudioOptions* options) {
+  bool success = true;
+  MediaConstraintsInterface::Constraints::const_iterator iter;
+
+  // This design relies on the fact that all the audio constraints are actually
+  // "options", i.e. boolean-valued and always satisfiable.  If the constraints
+  // are extended to include non-boolean values or actual format constraints,
+  // a different algorithm will be required.
+  for (iter = constraints.begin(); iter != constraints.end(); ++iter) {
+    bool value = false;
+
+    if (!talk_base::FromString(iter->value, &value)) {
+      success = false;
+      continue;
+    }
+
+    if (iter->key == MediaConstraintsInterface::kEchoCancellation)
+      options->echo_cancellation.Set(value);
+    else if (iter->key ==
+        MediaConstraintsInterface::kExperimentalEchoCancellation)
+      options->experimental_aec.Set(value);
+    else if (iter->key == MediaConstraintsInterface::kAutoGainControl)
+      options->auto_gain_control.Set(value);
+    else if (iter->key ==
+        MediaConstraintsInterface::kExperimentalAutoGainControl)
+      options->experimental_agc.Set(value);
+    else if (iter->key == MediaConstraintsInterface::kNoiseSuppression)
+      options->noise_suppression.Set(value);
+    else if (iter->key == MediaConstraintsInterface::kHighpassFilter)
+      options->highpass_filter.Set(value);
+    else if (iter->key == MediaConstraintsInterface::kInternalAecDump)
+      options->aec_dump.Set(value);
+    else
+      success = false;
+  }
+  return success;
+}
+
+}  // namespace
+
+talk_base::scoped_refptr<LocalAudioSource> LocalAudioSource::Create(
+    const MediaConstraintsInterface* constraints) {
+  talk_base::scoped_refptr<LocalAudioSource> source(
+      new talk_base::RefCountedObject<LocalAudioSource>());
+  source->Initialize(constraints);
+  return source;
+}
+
+void LocalAudioSource::Initialize(
+    const MediaConstraintsInterface* constraints) {
+  if (!constraints)
+    return;
+
+  // Apply optional constraints first, they will be overwritten by mandatory
+  // constraints.
+  FromConstraints(constraints->GetOptional(), &options_);
+
+  cricket::AudioOptions options;
+  if (!FromConstraints(constraints->GetMandatory(), &options)) {
+    source_state_ = kEnded;
+    return;
+  }
+  options_.SetAll(options);
+  source_state_ = kLive;
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/localaudiosource.h b/talk/app/webrtc/localaudiosource.h
new file mode 100644
index 0000000..e0fda03
--- /dev/null
+++ b/talk/app/webrtc/localaudiosource.h
@@ -0,0 +1,69 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_LOCALAUDIOSOURCE_H_
+#define TALK_APP_WEBRTC_LOCALAUDIOSOURCE_H_
+
+#include "talk/app/webrtc/mediastreaminterface.h"
+#include "talk/app/webrtc/notifier.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/media/base/mediachannel.h"
+
+// LocalAudioSource implements AudioSourceInterface.
+// This contains settings for switching audio processing on and off.
+
+namespace webrtc {
+
+class MediaConstraintsInterface;
+
+class LocalAudioSource : public Notifier<AudioSourceInterface> {
+ public:
+  // Creates an instance of LocalAudioSource.
+  static talk_base::scoped_refptr<LocalAudioSource> Create(
+      const MediaConstraintsInterface* constraints);
+
+  virtual SourceState state() const { return source_state_; }
+  virtual const cricket::AudioOptions& options() const { return options_; }
+
+ protected:
+  LocalAudioSource()
+      : source_state_(kInitializing) {
+  }
+
+  ~LocalAudioSource() {
+  }
+
+ private:
+  void Initialize(const MediaConstraintsInterface* constraints);
+
+  cricket::AudioOptions options_;
+  SourceState source_state_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_LOCALAUDIOSOURCE_H_
diff --git a/talk/app/webrtc/localaudiosource_unittest.cc b/talk/app/webrtc/localaudiosource_unittest.cc
new file mode 100644
index 0000000..ae07787
--- /dev/null
+++ b/talk/app/webrtc/localaudiosource_unittest.cc
@@ -0,0 +1,118 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/localaudiosource.h"
+
+#include <string>
+#include <vector>
+
+#include "talk/app/webrtc/test/fakeconstraints.h"
+#include "talk/base/gunit.h"
+#include "talk/media/base/fakemediaengine.h"
+#include "talk/media/base/fakevideorenderer.h"
+#include "talk/media/devices/fakedevicemanager.h"
+
+using webrtc::LocalAudioSource;
+using webrtc::MediaConstraintsInterface;
+using webrtc::MediaSourceInterface;
+
+TEST(LocalAudioSourceTest, SetValidOptions) {
+  webrtc::FakeConstraints constraints;
+  constraints.AddMandatory(MediaConstraintsInterface::kEchoCancellation, false);
+  constraints.AddOptional(
+      MediaConstraintsInterface::kExperimentalEchoCancellation, true);
+  constraints.AddOptional(MediaConstraintsInterface::kAutoGainControl, true);
+  constraints.AddOptional(
+      MediaConstraintsInterface::kExperimentalAutoGainControl, true);
+  constraints.AddMandatory(MediaConstraintsInterface::kNoiseSuppression, false);
+  constraints.AddOptional(MediaConstraintsInterface::kHighpassFilter, true);
+
+  talk_base::scoped_refptr<LocalAudioSource> source =
+      LocalAudioSource::Create(&constraints);
+
+  bool value;
+  EXPECT_TRUE(source->options().echo_cancellation.Get(&value));
+  EXPECT_FALSE(value);
+  EXPECT_TRUE(source->options().experimental_aec.Get(&value));
+  EXPECT_TRUE(value);
+  EXPECT_TRUE(source->options().auto_gain_control.Get(&value));
+  EXPECT_TRUE(value);
+  EXPECT_TRUE(source->options().experimental_agc.Get(&value));
+  EXPECT_TRUE(value);
+  EXPECT_TRUE(source->options().noise_suppression.Get(&value));
+  EXPECT_FALSE(value);
+  EXPECT_TRUE(source->options().highpass_filter.Get(&value));
+  EXPECT_TRUE(value);
+}
+
+TEST(LocalAudioSourceTest, OptionNotSet) {
+  webrtc::FakeConstraints constraints;
+  talk_base::scoped_refptr<LocalAudioSource> source =
+      LocalAudioSource::Create(&constraints);
+  bool value;
+  EXPECT_FALSE(source->options().highpass_filter.Get(&value));
+}
+
+TEST(LocalAudioSourceTest, MandatoryOverridesOptional) {
+  webrtc::FakeConstraints constraints;
+  constraints.AddMandatory(MediaConstraintsInterface::kEchoCancellation, false);
+  constraints.AddOptional(MediaConstraintsInterface::kEchoCancellation, true);
+
+  talk_base::scoped_refptr<LocalAudioSource> source =
+      LocalAudioSource::Create(&constraints);
+
+  bool value;
+  EXPECT_TRUE(source->options().echo_cancellation.Get(&value));
+  EXPECT_FALSE(value);
+}
+
+TEST(LocalAudioSourceTest, InvalidOptional) {
+  webrtc::FakeConstraints constraints;
+  constraints.AddOptional(MediaConstraintsInterface::kHighpassFilter, false);
+  constraints.AddOptional("invalidKey", false);
+
+  talk_base::scoped_refptr<LocalAudioSource> source =
+      LocalAudioSource::Create(&constraints);
+
+  EXPECT_EQ(MediaSourceInterface::kLive, source->state());
+  bool value;
+  EXPECT_TRUE(source->options().highpass_filter.Get(&value));
+  EXPECT_FALSE(value);
+}
+
+TEST(LocalAudioSourceTest, InvalidMandatory) {
+  webrtc::FakeConstraints constraints;
+  constraints.AddMandatory(MediaConstraintsInterface::kHighpassFilter, false);
+  constraints.AddMandatory("invalidKey", false);
+
+  talk_base::scoped_refptr<LocalAudioSource> source =
+      LocalAudioSource::Create(&constraints);
+
+  EXPECT_EQ(MediaSourceInterface::kEnded, source->state());
+  bool value;
+  EXPECT_FALSE(source->options().highpass_filter.Get(&value));
+}
diff --git a/talk/app/webrtc/localvideosource.cc b/talk/app/webrtc/localvideosource.cc
new file mode 100644
index 0000000..2d43885
--- /dev/null
+++ b/talk/app/webrtc/localvideosource.cc
@@ -0,0 +1,442 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/localvideosource.h"
+
+#include <vector>
+
+#include "talk/app/webrtc/mediaconstraintsinterface.h"
+#include "talk/session/media/channelmanager.h"
+
+using cricket::CaptureState;
+using webrtc::MediaConstraintsInterface;
+using webrtc::MediaSourceInterface;
+
+namespace webrtc {
+
+// Constraint keys. Specified by draft-alvestrand-constraints-resolution-00b
+// They are declared as static members in mediastreaminterface.h
+const char MediaConstraintsInterface::kMinAspectRatio[] = "minAspectRatio";
+const char MediaConstraintsInterface::kMaxAspectRatio[] = "maxAspectRatio";
+const char MediaConstraintsInterface::kMaxWidth[] = "maxWidth";
+const char MediaConstraintsInterface::kMinWidth[] = "minWidth";
+const char MediaConstraintsInterface::kMaxHeight[] = "maxHeight";
+const char MediaConstraintsInterface::kMinHeight[] = "minHeight";
+const char MediaConstraintsInterface::kMaxFrameRate[] = "maxFrameRate";
+const char MediaConstraintsInterface::kMinFrameRate[] = "minFrameRate";
+
+// Google-specific keys
+const char MediaConstraintsInterface::kNoiseReduction[] = "googNoiseReduction";
+const char MediaConstraintsInterface::kLeakyBucket[] = "googLeakyBucket";
+const char MediaConstraintsInterface::kTemporalLayeredScreencast[] =
+    "googTemporalLayeredScreencast";
+
+}  // namespace webrtc
+
+namespace {
+
+const double kRoundingTruncation = 0.0005;
+
+enum {
+  MSG_VIDEOCAPTURESTATECONNECT,
+  MSG_VIDEOCAPTURESTATEDISCONNECT,
+  MSG_VIDEOCAPTURESTATECHANGE,
+};
+
+// Default resolution. If no constraint is specified, this is the resolution we
+// will use.
+static const cricket::VideoFormatPod kDefaultResolution =
+    {640, 480, FPS_TO_INTERVAL(30), cricket::FOURCC_ANY};
+
+// List of formats used if the camera doesn't support capability enumeration.
+static const cricket::VideoFormatPod kVideoFormats[] = {
+  {1920, 1080, FPS_TO_INTERVAL(30), cricket::FOURCC_ANY},
+  {1280, 720, FPS_TO_INTERVAL(30), cricket::FOURCC_ANY},
+  {960, 720, FPS_TO_INTERVAL(30), cricket::FOURCC_ANY},
+  {640, 360, FPS_TO_INTERVAL(30), cricket::FOURCC_ANY},
+  {640, 480, FPS_TO_INTERVAL(30), cricket::FOURCC_ANY},
+  {320, 240, FPS_TO_INTERVAL(30), cricket::FOURCC_ANY},
+  {320, 180, FPS_TO_INTERVAL(30), cricket::FOURCC_ANY}
+};
+
+MediaSourceInterface::SourceState
+GetReadyState(cricket::CaptureState state) {
+  switch (state) {
+    case cricket::CS_STARTING:
+      return MediaSourceInterface::kInitializing;
+    case cricket::CS_RUNNING:
+      return MediaSourceInterface::kLive;
+    case cricket::CS_FAILED:
+    case cricket::CS_NO_DEVICE:
+    case cricket::CS_STOPPED:
+      return MediaSourceInterface::kEnded;
+    case cricket::CS_PAUSED:
+      return MediaSourceInterface::kMuted;
+    default:
+      ASSERT(false && "GetReadyState unknown state");
+  }
+  return MediaSourceInterface::kEnded;
+}
+
+void SetUpperLimit(int new_limit, int* original_limit) {
+  if (*original_limit < 0 || new_limit < *original_limit)
+    *original_limit = new_limit;
+}
+
+// Updates |format_upper_limit| from |constraint|.
+// If constraint.maxFoo is smaller than format_upper_limit.foo,
+// set format_upper_limit.foo to constraint.maxFoo.
+void SetUpperLimitFromConstraint(
+    const MediaConstraintsInterface::Constraint& constraint,
+    cricket::VideoFormat* format_upper_limit) {
+  if (constraint.key == MediaConstraintsInterface::kMaxWidth) {
+    int value = talk_base::FromString<int>(constraint.value);
+    SetUpperLimit(value, &(format_upper_limit->width));
+  } else if (constraint.key == MediaConstraintsInterface::kMaxHeight) {
+    int value = talk_base::FromString<int>(constraint.value);
+    SetUpperLimit(value, &(format_upper_limit->height));
+  }
+}
+
+// Fills |format_out| with the max width and height allowed by |constraints|.
+void FromConstraintsForScreencast(
+    const MediaConstraintsInterface::Constraints& constraints,
+    cricket::VideoFormat* format_out) {
+  typedef MediaConstraintsInterface::Constraints::const_iterator
+      ConstraintsIterator;
+
+  cricket::VideoFormat upper_limit(-1, -1, 0, 0);
+  for (ConstraintsIterator constraints_it = constraints.begin();
+       constraints_it != constraints.end(); ++constraints_it)
+    SetUpperLimitFromConstraint(*constraints_it, &upper_limit);
+
+  if (upper_limit.width >= 0)
+    format_out->width = upper_limit.width;
+  if (upper_limit.height >= 0)
+    format_out->height = upper_limit.height;
+}
+
+// Returns true if |constraint| is fulfilled. |format_out| can differ from
+// |format_in| if the format is changed by the constraint. Ie - the frame rate
+// can be changed by setting maxFrameRate.
+bool NewFormatWithConstraints(
+    const MediaConstraintsInterface::Constraint& constraint,
+    const cricket::VideoFormat& format_in,
+    bool mandatory,
+    cricket::VideoFormat* format_out) {
+  ASSERT(format_out != NULL);
+  *format_out = format_in;
+
+  if (constraint.key == MediaConstraintsInterface::kMinWidth) {
+    int value = talk_base::FromString<int>(constraint.value);
+    return (value <= format_in.width);
+  } else if (constraint.key == MediaConstraintsInterface::kMaxWidth) {
+    int value = talk_base::FromString<int>(constraint.value);
+    return (value >= format_in.width);
+  } else if (constraint.key == MediaConstraintsInterface::kMinHeight) {
+    int value = talk_base::FromString<int>(constraint.value);
+    return (value <= format_in.height);
+  } else if (constraint.key == MediaConstraintsInterface::kMaxHeight) {
+    int value = talk_base::FromString<int>(constraint.value);
+    return (value >= format_in.height);
+  } else if (constraint.key == MediaConstraintsInterface::kMinFrameRate) {
+    int value = talk_base::FromString<int>(constraint.value);
+    return (value <= cricket::VideoFormat::IntervalToFps(format_in.interval));
+  } else if (constraint.key == MediaConstraintsInterface::kMaxFrameRate) {
+    int value = talk_base::FromString<int>(constraint.value);
+    if (value == 0) {
+      if (mandatory) {
+        // TODO(ronghuawu): Convert the constraint value to float when sub-1fps
+        // is supported by the capturer.
+        return false;
+      } else {
+        value = 1;
+      }
+    }
+    if (value <= cricket::VideoFormat::IntervalToFps(format_in.interval)) {
+      format_out->interval = cricket::VideoFormat::FpsToInterval(value);
+      return true;
+    } else {
+      return false;
+    }
+  } else if (constraint.key == MediaConstraintsInterface::kMinAspectRatio) {
+    double value = talk_base::FromString<double>(constraint.value);
+    // The aspect ratio in |constraint.value| has been converted to a string and
+    // back to a double, so it may have a rounding error.
+    // E.g if the value 1/3 is converted to a string, the string will not have
+    // infinite length.
+    // We add a margin of 0.0005 which is high enough to detect the same aspect
+    // ratio but small enough to avoid matching wrong aspect ratios.
+    double ratio = static_cast<double>(format_in.width) / format_in.height;
+    return  (value <= ratio + kRoundingTruncation);
+  } else if (constraint.key == MediaConstraintsInterface::kMaxAspectRatio) {
+    double value = talk_base::FromString<double>(constraint.value);
+    double ratio = static_cast<double>(format_in.width) / format_in.height;
+    // Subtract 0.0005 to avoid rounding problems. Same as above.
+    const double kRoundingTruncation = 0.0005;
+    return  (value >= ratio - kRoundingTruncation);
+  } else if (constraint.key == MediaConstraintsInterface::kNoiseReduction ||
+             constraint.key == MediaConstraintsInterface::kLeakyBucket ||
+             constraint.key ==
+                 MediaConstraintsInterface::kTemporalLayeredScreencast) {
+    // These are actually options, not constraints, so they can be satisfied
+    // regardless of the format.
+    return true;
+  }
+  LOG(LS_WARNING) << "Found unknown MediaStream constraint. Name:"
+      <<  constraint.key << " Value:" << constraint.value;
+  return false;
+}
+
+// Removes cricket::VideoFormats from |formats| that don't meet |constraint|.
+void FilterFormatsByConstraint(
+    const MediaConstraintsInterface::Constraint& constraint,
+    bool mandatory,
+    std::vector<cricket::VideoFormat>* formats) {
+  std::vector<cricket::VideoFormat>::iterator format_it =
+      formats->begin();
+  while (format_it != formats->end()) {
+    // Modify the format_it to fulfill the constraint if possible.
+    // Delete it otherwise.
+    if (!NewFormatWithConstraints(constraint, (*format_it),
+                                  mandatory, &(*format_it))) {
+      format_it = formats->erase(format_it);
+    } else {
+      ++format_it;
+    }
+  }
+}
+
+// Returns a vector of cricket::VideoFormat that best match |constraints|.
+std::vector<cricket::VideoFormat> FilterFormats(
+    const MediaConstraintsInterface::Constraints& mandatory,
+    const MediaConstraintsInterface::Constraints& optional,
+    const std::vector<cricket::VideoFormat>& supported_formats) {
+  typedef MediaConstraintsInterface::Constraints::const_iterator
+      ConstraintsIterator;
+  std::vector<cricket::VideoFormat> candidates = supported_formats;
+
+  for (ConstraintsIterator constraints_it = mandatory.begin();
+       constraints_it != mandatory.end(); ++constraints_it)
+    FilterFormatsByConstraint(*constraints_it, true, &candidates);
+
+  if (candidates.size() == 0)
+    return candidates;
+
+  // Ok - all mandatory checked and we still have a candidate.
+  // Let's try filtering using the optional constraints.
+  for (ConstraintsIterator  constraints_it = optional.begin();
+       constraints_it != optional.end(); ++constraints_it) {
+    std::vector<cricket::VideoFormat> current_candidates = candidates;
+    FilterFormatsByConstraint(*constraints_it, false, &current_candidates);
+    if (current_candidates.size() > 0) {
+      candidates = current_candidates;
+    }
+  }
+
+  // We have done as good as we can to filter the supported resolutions.
+  return candidates;
+}
+
+// Find the format that best matches the default video size.
+// Constraints are optional and since the performance of a video call
+// might be bad due to bitrate limitations, CPU, and camera performance,
+// it is better to select a resolution that is as close as possible to our
+// default and still meets the contraints.
+const cricket::VideoFormat& GetBestCaptureFormat(
+    const std::vector<cricket::VideoFormat>& formats) {
+  ASSERT(formats.size() > 0);
+
+  int default_area = kDefaultResolution.width * kDefaultResolution.height;
+
+  std::vector<cricket::VideoFormat>::const_iterator it = formats.begin();
+  std::vector<cricket::VideoFormat>::const_iterator best_it = formats.begin();
+  int best_diff = abs(default_area - it->width* it->height);
+  for (; it != formats.end(); ++it) {
+    int diff = abs(default_area - it->width* it->height);
+    if (diff < best_diff) {
+      best_diff = diff;
+      best_it = it;
+    }
+  }
+  return *best_it;
+}
+
+// Set |option| to the highest-priority value of |key| in the constraints.
+// Return false if the key is mandatory, and the value is invalid.
+bool ExtractOption(const MediaConstraintsInterface* all_constraints,
+    const std::string& key, cricket::Settable<bool>* option) {
+  size_t mandatory = 0;
+  bool value;
+  if (FindConstraint(all_constraints, key, &value, &mandatory)) {
+    option->Set(value);
+    return true;
+  }
+
+  return mandatory == 0;
+}
+
+// Search |all_constraints| for known video options.  Apply all options that are
+// found with valid values, and return false if any mandatory video option was
+// found with an invalid value.
+bool ExtractVideoOptions(const MediaConstraintsInterface* all_constraints,
+                         cricket::VideoOptions* options) {
+  bool all_valid = true;
+
+  all_valid &= ExtractOption(all_constraints,
+      MediaConstraintsInterface::kNoiseReduction,
+      &(options->video_noise_reduction));
+  all_valid &= ExtractOption(all_constraints,
+      MediaConstraintsInterface::kLeakyBucket,
+      &(options->video_leaky_bucket));
+  all_valid &= ExtractOption(all_constraints,
+      MediaConstraintsInterface::kTemporalLayeredScreencast,
+      &(options->video_temporal_layer_screencast));
+
+  return all_valid;
+}
+
+}  // anonymous namespace
+
+namespace webrtc {
+
+talk_base::scoped_refptr<LocalVideoSource> LocalVideoSource::Create(
+    cricket::ChannelManager* channel_manager,
+    cricket::VideoCapturer* capturer,
+    const webrtc::MediaConstraintsInterface* constraints) {
+  ASSERT(channel_manager != NULL);
+  ASSERT(capturer != NULL);
+  talk_base::scoped_refptr<LocalVideoSource> source(
+      new talk_base::RefCountedObject<LocalVideoSource>(channel_manager,
+                                                        capturer));
+  source->Initialize(constraints);
+  return source;
+}
+
+LocalVideoSource::LocalVideoSource(cricket::ChannelManager* channel_manager,
+                                   cricket::VideoCapturer* capturer)
+    : channel_manager_(channel_manager),
+      video_capturer_(capturer),
+      state_(kInitializing) {
+  channel_manager_->SignalVideoCaptureStateChange.connect(
+      this, &LocalVideoSource::OnStateChange);
+}
+
+LocalVideoSource::~LocalVideoSource() {
+  channel_manager_->StopVideoCapture(video_capturer_.get(), format_);
+  channel_manager_->SignalVideoCaptureStateChange.disconnect(this);
+}
+
+void LocalVideoSource::Initialize(
+    const webrtc::MediaConstraintsInterface* constraints) {
+
+  std::vector<cricket::VideoFormat> formats;
+  if (video_capturer_->GetSupportedFormats() &&
+      video_capturer_->GetSupportedFormats()->size() > 0) {
+    formats = *video_capturer_->GetSupportedFormats();
+  } else if (video_capturer_->IsScreencast()) {
+    // The screen capturer can accept any resolution and we will derive the
+    // format from the constraints if any.
+    // Note that this only affects tab capturing, not desktop capturing,
+    // since desktop capturer does not respect the VideoFormat passed in.
+    formats.push_back(cricket::VideoFormat(kDefaultResolution));
+  } else {
+    // The VideoCapturer implementation doesn't support capability enumeration.
+    // We need to guess what the camera support.
+    for (int i = 0; i < ARRAY_SIZE(kVideoFormats); ++i) {
+      formats.push_back(cricket::VideoFormat(kVideoFormats[i]));
+    }
+  }
+
+  if (constraints) {
+    MediaConstraintsInterface::Constraints mandatory_constraints =
+        constraints->GetMandatory();
+    MediaConstraintsInterface::Constraints optional_constraints;
+    optional_constraints = constraints->GetOptional();
+
+    if (video_capturer_->IsScreencast()) {
+      // Use the maxWidth and maxHeight allowed by constraints for screencast.
+      FromConstraintsForScreencast(mandatory_constraints, &(formats[0]));
+    }
+
+    formats = FilterFormats(mandatory_constraints, optional_constraints,
+                            formats);
+  }
+
+  if (formats.size() == 0) {
+    LOG(LS_WARNING) << "Failed to find a suitable video format.";
+    SetState(kEnded);
+    return;
+  }
+
+  cricket::VideoOptions options;
+  if (!ExtractVideoOptions(constraints, &options)) {
+    LOG(LS_WARNING) << "Could not satisfy mandatory options.";
+    SetState(kEnded);
+    return;
+  }
+  options_.SetAll(options);
+
+  format_ = GetBestCaptureFormat(formats);
+  // Start the camera with our best guess.
+  // TODO(perkj): Should we try again with another format it it turns out that
+  // the camera doesn't produce frames with the correct format? Or will
+  // cricket::VideCapturer be able to re-scale / crop to the requested
+  // resolution?
+  if (!channel_manager_->StartVideoCapture(video_capturer_.get(), format_)) {
+    SetState(kEnded);
+    return;
+  }
+  // Initialize hasn't succeeded until a successful state change has occurred.
+}
+
+void LocalVideoSource::AddSink(cricket::VideoRenderer* output) {
+  channel_manager_->AddVideoRenderer(video_capturer_.get(), output);
+}
+
+void LocalVideoSource::RemoveSink(cricket::VideoRenderer* output) {
+  channel_manager_->RemoveVideoRenderer(video_capturer_.get(), output);
+}
+
+// OnStateChange listens to the ChannelManager::SignalVideoCaptureStateChange.
+// This signal is triggered for all video capturers. Not only the one we are
+// interested in.
+void LocalVideoSource::OnStateChange(cricket::VideoCapturer* capturer,
+                                     cricket::CaptureState capture_state) {
+  if (capturer == video_capturer_.get()) {
+    SetState(GetReadyState(capture_state));
+  }
+}
+
+void LocalVideoSource::SetState(SourceState new_state) {
+  if (VERIFY(state_ != new_state)) {
+    state_ = new_state;
+    FireOnChanged();
+  }
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/localvideosource.h b/talk/app/webrtc/localvideosource.h
new file mode 100644
index 0000000..0a3bac0
--- /dev/null
+++ b/talk/app/webrtc/localvideosource.h
@@ -0,0 +1,100 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_LOCALVIDEOSOURCE_H_
+#define TALK_APP_WEBRTC_LOCALVIDEOSOURCE_H_
+
+#include "talk/app/webrtc/mediastreaminterface.h"
+#include "talk/app/webrtc/notifier.h"
+#include "talk/app/webrtc/videosourceinterface.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#include "talk/media/base/videocapturer.h"
+#include "talk/media/base/videocommon.h"
+
+// LocalVideoSource implements VideoSourceInterface. It owns a
+// cricket::VideoCapturer and make sure the camera is started at a resolution
+// that honors the constraints.
+// The state is set depending on the result of starting the capturer.
+// If the constraint can't be met or the capturer fails to start, the state
+// transition to kEnded, otherwise it transitions to kLive.
+
+namespace cricket {
+
+class ChannelManager;
+
+}  // namespace cricket
+
+namespace webrtc {
+
+class MediaConstraintsInterface;
+
+class LocalVideoSource : public Notifier<VideoSourceInterface>,
+                         public sigslot::has_slots<> {
+ public:
+  // Creates an instance of LocalVideoSource.
+  // LocalVideoSource take ownership of |capturer|.
+  // |constraints| can be NULL and in that case the camera is opened using a
+  // default resolution.
+  static talk_base::scoped_refptr<LocalVideoSource> Create(
+      cricket::ChannelManager* channel_manager,
+      cricket::VideoCapturer* capturer,
+      const webrtc::MediaConstraintsInterface* constraints);
+
+  virtual SourceState state() const { return state_; }
+  virtual const cricket::VideoOptions* options() const { return &options_; }
+
+  virtual cricket::VideoCapturer* GetVideoCapturer() {
+    return video_capturer_.get();
+  }
+  // |output| will be served video frames as long as the underlying capturer
+  // is running video frames.
+  virtual void AddSink(cricket::VideoRenderer* output);
+  virtual void RemoveSink(cricket::VideoRenderer* output);
+
+ protected:
+  LocalVideoSource(cricket::ChannelManager* channel_manager,
+                   cricket::VideoCapturer* capturer);
+  ~LocalVideoSource();
+
+ private:
+  void Initialize(const webrtc::MediaConstraintsInterface* constraints);
+  void OnStateChange(cricket::VideoCapturer* capturer,
+                     cricket::CaptureState capture_state);
+  void SetState(SourceState new_state);
+
+  cricket::ChannelManager* channel_manager_;
+  talk_base::scoped_ptr<cricket::VideoCapturer> video_capturer_;
+
+  cricket::VideoFormat format_;
+  cricket::VideoOptions options_;
+  SourceState state_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_LOCALVIDEOSOURCE_H_
diff --git a/talk/app/webrtc/localvideosource_unittest.cc b/talk/app/webrtc/localvideosource_unittest.cc
new file mode 100644
index 0000000..24a8588
--- /dev/null
+++ b/talk/app/webrtc/localvideosource_unittest.cc
@@ -0,0 +1,523 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/localvideosource.h"
+
+#include <string>
+#include <vector>
+
+#include "talk/app/webrtc/test/fakeconstraints.h"
+#include "talk/base/gunit.h"
+#include "talk/media/base/fakemediaengine.h"
+#include "talk/media/base/fakevideorenderer.h"
+#include "talk/media/devices/fakedevicemanager.h"
+#include "talk/session/media/channelmanager.h"
+
+using webrtc::FakeConstraints;
+using webrtc::LocalVideoSource;
+using webrtc::MediaConstraintsInterface;
+using webrtc::MediaSourceInterface;
+using webrtc::ObserverInterface;
+using webrtc::VideoSourceInterface;
+
+namespace {
+
+// Max wait time for a test.
+const int kMaxWaitMs = 100;
+
+}  // anonymous namespace
+
+
+// TestVideoCapturer extends cricket::FakeVideoCapturer so it can be used for
+// testing without known camera formats.
+// It keeps its own lists of cricket::VideoFormats for the unit tests in this
+// file.
+class TestVideoCapturer : public cricket::FakeVideoCapturer {
+ public:
+  TestVideoCapturer() : test_without_formats_(false) {
+    std::vector<cricket::VideoFormat> formats;
+    formats.push_back(cricket::VideoFormat(1280, 720,
+        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(640, 400,
+            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(352, 288,
+            cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+    ResetSupportedFormats(formats);
+  }
+
+  // This function is used for resetting the supported capture formats and
+  // simulating a cricket::VideoCapturer implementation that don't support
+  // capture format enumeration. This is used to simulate the current
+  // Chrome implementation.
+  void TestWithoutCameraFormats() {
+    test_without_formats_ = true;
+    std::vector<cricket::VideoFormat> formats;
+    ResetSupportedFormats(formats);
+  }
+
+  virtual cricket::CaptureState Start(
+      const cricket::VideoFormat& capture_format) {
+    if (test_without_formats_) {
+      std::vector<cricket::VideoFormat> formats;
+      formats.push_back(capture_format);
+      ResetSupportedFormats(formats);
+    }
+    return FakeVideoCapturer::Start(capture_format);
+  }
+
+  virtual bool GetBestCaptureFormat(const cricket::VideoFormat& desired,
+                                    cricket::VideoFormat* best_format) {
+    if (test_without_formats_) {
+      *best_format = desired;
+      return true;
+    }
+    return FakeVideoCapturer::GetBestCaptureFormat(desired,
+                                                   best_format);
+  }
+
+ private:
+  bool test_without_formats_;
+};
+
+class StateObserver : public ObserverInterface {
+ public:
+  explicit StateObserver(VideoSourceInterface* source)
+     : state_(source->state()),
+       source_(source) {
+  }
+  virtual void OnChanged() {
+    state_ = source_->state();
+  }
+  MediaSourceInterface::SourceState state() const { return state_; }
+
+ private:
+  MediaSourceInterface::SourceState state_;
+  talk_base::scoped_refptr<VideoSourceInterface> source_;
+};
+
+class LocalVideoSourceTest : public testing::Test {
+ protected:
+  LocalVideoSourceTest()
+      : channel_manager_(new cricket::ChannelManager(
+          new cricket::FakeMediaEngine(),
+          new cricket::FakeDeviceManager(), talk_base::Thread::Current())) {
+  }
+
+  void SetUp() {
+    ASSERT_TRUE(channel_manager_->Init());
+    capturer_ = new TestVideoCapturer();
+  }
+
+  void CreateLocalVideoSource() {
+    CreateLocalVideoSource(NULL);
+  }
+
+  void CreateLocalVideoSource(
+      const webrtc::MediaConstraintsInterface* constraints) {
+    // VideoSource take ownership of |capturer_|
+    local_source_ = LocalVideoSource::Create(channel_manager_.get(),
+                                             capturer_,
+                                             constraints);
+
+    ASSERT_TRUE(local_source_.get() != NULL);
+    EXPECT_EQ(capturer_, local_source_->GetVideoCapturer());
+
+    state_observer_.reset(new StateObserver(local_source_));
+    local_source_->RegisterObserver(state_observer_.get());
+    local_source_->AddSink(&renderer_);
+  }
+
+  TestVideoCapturer* capturer_;  // Raw pointer. Owned by local_source_.
+  cricket::FakeVideoRenderer renderer_;
+  talk_base::scoped_ptr<cricket::ChannelManager> channel_manager_;
+  talk_base::scoped_ptr<StateObserver> state_observer_;
+  talk_base::scoped_refptr<LocalVideoSource> local_source_;
+};
+
+
+// Test that a LocalVideoSource transition to kLive state when the capture
+// device have started and kEnded if it is stopped.
+// It also test that an output can receive video frames.
+TEST_F(LocalVideoSourceTest, StartStop) {
+  // Initialize without constraints.
+  CreateLocalVideoSource();
+  EXPECT_EQ_WAIT(MediaSourceInterface::kLive, state_observer_->state(),
+                 kMaxWaitMs);
+
+  ASSERT_TRUE(capturer_->CaptureFrame());
+  EXPECT_EQ(1, renderer_.num_rendered_frames());
+
+  capturer_->Stop();
+  EXPECT_EQ_WAIT(MediaSourceInterface::kEnded, state_observer_->state(),
+                 kMaxWaitMs);
+}
+
+// Test that a LocalVideoSource transition to kEnded if the capture device
+// fails.
+TEST_F(LocalVideoSourceTest, CameraFailed) {
+  CreateLocalVideoSource();
+  EXPECT_EQ_WAIT(MediaSourceInterface::kLive, state_observer_->state(),
+                 kMaxWaitMs);
+
+  capturer_->SignalStateChange(capturer_, cricket::CS_FAILED);
+  EXPECT_EQ_WAIT(MediaSourceInterface::kEnded, state_observer_->state(),
+                 kMaxWaitMs);
+}
+
+// Test that the capture output is CIF if we set max constraints to CIF.
+// and the capture device support CIF.
+TEST_F(LocalVideoSourceTest, MandatoryConstraintCif5Fps) {
+  FakeConstraints constraints;
+  constraints.AddMandatory(MediaConstraintsInterface::kMaxWidth, 352);
+  constraints.AddMandatory(MediaConstraintsInterface::kMaxHeight, 288);
+  constraints.AddMandatory(MediaConstraintsInterface::kMaxFrameRate, 5);
+
+  CreateLocalVideoSource(&constraints);
+  EXPECT_EQ_WAIT(MediaSourceInterface::kLive, state_observer_->state(),
+                 kMaxWaitMs);
+  const cricket::VideoFormat* format = capturer_->GetCaptureFormat();
+  ASSERT_TRUE(format != NULL);
+  EXPECT_EQ(352, format->width);
+  EXPECT_EQ(288, format->height);
+  EXPECT_EQ(5, format->framerate());
+}
+
+// Test that the capture output is 720P if the camera support it and the
+// optional constraint is set to 720P.
+TEST_F(LocalVideoSourceTest, MandatoryMinVgaOptional720P) {
+  FakeConstraints constraints;
+  constraints.AddMandatory(MediaConstraintsInterface::kMinWidth, 640);
+  constraints.AddMandatory(MediaConstraintsInterface::kMinHeight, 480);
+  constraints.AddOptional(MediaConstraintsInterface::kMinWidth, 1280);
+  constraints.AddOptional(MediaConstraintsInterface::kMinAspectRatio,
+                          1280.0 / 720);
+
+  CreateLocalVideoSource(&constraints);
+  EXPECT_EQ_WAIT(MediaSourceInterface::kLive, state_observer_->state(),
+                 kMaxWaitMs);
+  const cricket::VideoFormat* format = capturer_->GetCaptureFormat();
+  ASSERT_TRUE(format != NULL);
+  EXPECT_EQ(1280, format->width);
+  EXPECT_EQ(720, format->height);
+  EXPECT_EQ(30, format->framerate());
+}
+
+// Test that the capture output have aspect ratio 4:3 if a mandatory constraint
+// require it even if an optional constraint request a higher resolution
+// that don't have this aspect ratio.
+TEST_F(LocalVideoSourceTest, MandatoryAspectRatio4To3) {
+  FakeConstraints constraints;
+  constraints.AddMandatory(MediaConstraintsInterface::kMinWidth, 640);
+  constraints.AddMandatory(MediaConstraintsInterface::kMinHeight, 480);
+  constraints.AddMandatory(MediaConstraintsInterface::kMaxAspectRatio,
+                           640.0 / 480);
+  constraints.AddOptional(MediaConstraintsInterface::kMinWidth, 1280);
+
+  CreateLocalVideoSource(&constraints);
+  EXPECT_EQ_WAIT(MediaSourceInterface::kLive, state_observer_->state(),
+                 kMaxWaitMs);
+  const cricket::VideoFormat* format = capturer_->GetCaptureFormat();
+  ASSERT_TRUE(format != NULL);
+  EXPECT_EQ(640, format->width);
+  EXPECT_EQ(480, format->height);
+  EXPECT_EQ(30, format->framerate());
+}
+
+
+// Test that the source state transition to kEnded if the mandatory aspect ratio
+// is set higher than supported.
+TEST_F(LocalVideoSourceTest, MandatoryAspectRatioTooHigh) {
+  FakeConstraints constraints;
+  constraints.AddMandatory(MediaConstraintsInterface::kMinAspectRatio, 2);
+  CreateLocalVideoSource(&constraints);
+  EXPECT_EQ_WAIT(MediaSourceInterface::kEnded, state_observer_->state(),
+                 kMaxWaitMs);
+}
+
+// Test that the source ignores an optional aspect ratio that is higher than
+// supported.
+TEST_F(LocalVideoSourceTest, OptionalAspectRatioTooHigh) {
+  FakeConstraints constraints;
+  constraints.AddOptional(MediaConstraintsInterface::kMinAspectRatio, 2);
+  CreateLocalVideoSource(&constraints);
+  EXPECT_EQ_WAIT(MediaSourceInterface::kLive, state_observer_->state(),
+                 kMaxWaitMs);
+  const cricket::VideoFormat* format = capturer_->GetCaptureFormat();
+  ASSERT_TRUE(format != NULL);
+  double aspect_ratio = static_cast<double>(format->width) / format->height;
+  EXPECT_LT(aspect_ratio, 2);
+}
+
+// Test that the source starts video with the default resolution if the
+// camera doesn't support capability enumeration and there are no constraints.
+TEST_F(LocalVideoSourceTest, NoCameraCapability) {
+  capturer_->TestWithoutCameraFormats();
+
+  CreateLocalVideoSource();
+  EXPECT_EQ_WAIT(MediaSourceInterface::kLive, state_observer_->state(),
+                 kMaxWaitMs);
+  const cricket::VideoFormat* format = capturer_->GetCaptureFormat();
+  ASSERT_TRUE(format != NULL);
+  EXPECT_EQ(640, format->width);
+  EXPECT_EQ(480, format->height);
+  EXPECT_EQ(30, format->framerate());
+}
+
+// Test that the source can start the video and get the requested aspect ratio
+// if the camera doesn't support capability enumeration and the aspect ratio is
+// set.
+TEST_F(LocalVideoSourceTest, NoCameraCapability16To9Ratio) {
+  capturer_->TestWithoutCameraFormats();
+
+  FakeConstraints constraints;
+  double requested_aspect_ratio = 640.0 / 360;
+  constraints.AddMandatory(MediaConstraintsInterface::kMinWidth, 640);
+  constraints.AddMandatory(MediaConstraintsInterface::kMinAspectRatio,
+                           requested_aspect_ratio);
+
+  CreateLocalVideoSource(&constraints);
+  EXPECT_EQ_WAIT(MediaSourceInterface::kLive, state_observer_->state(),
+                 kMaxWaitMs);
+  const cricket::VideoFormat* format = capturer_->GetCaptureFormat();
+  double aspect_ratio = static_cast<double>(format->width) / format->height;
+  EXPECT_LE(requested_aspect_ratio, aspect_ratio);
+}
+
+// Test that the source state transitions to kEnded if an unknown mandatory
+// constraint is found.
+TEST_F(LocalVideoSourceTest, InvalidMandatoryConstraint) {
+  FakeConstraints constraints;
+  constraints.AddMandatory("weird key", 640);
+
+  CreateLocalVideoSource(&constraints);
+  EXPECT_EQ_WAIT(MediaSourceInterface::kEnded, state_observer_->state(),
+                 kMaxWaitMs);
+}
+
+// Test that the source ignores an unknown optional constraint.
+TEST_F(LocalVideoSourceTest, InvalidOptionalConstraint) {
+  FakeConstraints constraints;
+  constraints.AddOptional("weird key", 640);
+
+  CreateLocalVideoSource(&constraints);
+  EXPECT_EQ_WAIT(MediaSourceInterface::kLive, state_observer_->state(),
+                 kMaxWaitMs);
+}
+
+TEST_F(LocalVideoSourceTest, SetValidOptionValues) {
+  FakeConstraints constraints;
+  constraints.AddMandatory(MediaConstraintsInterface::kNoiseReduction, "false");
+  constraints.AddMandatory(
+      MediaConstraintsInterface::kTemporalLayeredScreencast, "false");
+  constraints.AddOptional(
+      MediaConstraintsInterface::kLeakyBucket, "true");
+
+  CreateLocalVideoSource(&constraints);
+
+  bool value = true;
+  EXPECT_TRUE(local_source_->options()->video_noise_reduction.Get(&value));
+  EXPECT_FALSE(value);
+  EXPECT_TRUE(local_source_->options()->
+      video_temporal_layer_screencast.Get(&value));
+  EXPECT_FALSE(value);
+  EXPECT_TRUE(local_source_->options()->video_leaky_bucket.Get(&value));
+  EXPECT_TRUE(value);
+}
+
+TEST_F(LocalVideoSourceTest, OptionNotSet) {
+  FakeConstraints constraints;
+  CreateLocalVideoSource(&constraints);
+  bool value;
+  EXPECT_FALSE(local_source_->options()->video_noise_reduction.Get(&value));
+}
+
+TEST_F(LocalVideoSourceTest, MandatoryOptionOverridesOptional) {
+  FakeConstraints constraints;
+  constraints.AddMandatory(
+      MediaConstraintsInterface::kNoiseReduction, true);
+  constraints.AddOptional(
+      MediaConstraintsInterface::kNoiseReduction, false);
+
+  CreateLocalVideoSource(&constraints);
+
+  bool value = false;
+  EXPECT_TRUE(local_source_->options()->video_noise_reduction.Get(&value));
+  EXPECT_TRUE(value);
+  EXPECT_FALSE(local_source_->options()->video_leaky_bucket.Get(&value));
+}
+
+TEST_F(LocalVideoSourceTest, InvalidOptionKeyOptional) {
+  FakeConstraints constraints;
+  constraints.AddOptional(
+      MediaConstraintsInterface::kNoiseReduction, false);
+  constraints.AddOptional("invalidKey", false);
+
+  CreateLocalVideoSource(&constraints);
+
+  EXPECT_EQ_WAIT(MediaSourceInterface::kLive, state_observer_->state(),
+      kMaxWaitMs);
+  bool value = true;
+  EXPECT_TRUE(local_source_->options()->video_noise_reduction.Get(&value));
+  EXPECT_FALSE(value);
+}
+
+TEST_F(LocalVideoSourceTest, InvalidOptionKeyMandatory) {
+  FakeConstraints constraints;
+  constraints.AddMandatory(
+      MediaConstraintsInterface::kNoiseReduction, false);
+  constraints.AddMandatory("invalidKey", false);
+
+  CreateLocalVideoSource(&constraints);
+
+  EXPECT_EQ_WAIT(MediaSourceInterface::kEnded, state_observer_->state(),
+      kMaxWaitMs);
+  bool value;
+  EXPECT_FALSE(local_source_->options()->video_noise_reduction.Get(&value));
+}
+
+TEST_F(LocalVideoSourceTest, InvalidOptionValueOptional) {
+  FakeConstraints constraints;
+  constraints.AddOptional(
+      MediaConstraintsInterface::kNoiseReduction, "true");
+  constraints.AddOptional(
+      MediaConstraintsInterface::kLeakyBucket, "not boolean");
+
+  CreateLocalVideoSource(&constraints);
+
+  EXPECT_EQ_WAIT(MediaSourceInterface::kLive, state_observer_->state(),
+      kMaxWaitMs);
+  bool value = false;
+  EXPECT_TRUE(local_source_->options()->video_noise_reduction.Get(&value));
+  EXPECT_TRUE(value);
+  EXPECT_FALSE(local_source_->options()->video_leaky_bucket.Get(&value));
+}
+
+TEST_F(LocalVideoSourceTest, InvalidOptionValueMandatory) {
+  FakeConstraints constraints;
+  // Optional constraints should be ignored if the mandatory constraints fail.
+  constraints.AddOptional(
+      MediaConstraintsInterface::kNoiseReduction, "false");
+  // Values are case-sensitive and must be all lower-case.
+  constraints.AddMandatory(
+      MediaConstraintsInterface::kLeakyBucket, "True");
+
+  CreateLocalVideoSource(&constraints);
+
+  EXPECT_EQ_WAIT(MediaSourceInterface::kEnded, state_observer_->state(),
+      kMaxWaitMs);
+  bool value;
+  EXPECT_FALSE(local_source_->options()->video_noise_reduction.Get(&value));
+}
+
+TEST_F(LocalVideoSourceTest, MixedOptionsAndConstraints) {
+  FakeConstraints constraints;
+  constraints.AddMandatory(MediaConstraintsInterface::kMaxWidth, 352);
+  constraints.AddMandatory(MediaConstraintsInterface::kMaxHeight, 288);
+  constraints.AddOptional(MediaConstraintsInterface::kMaxFrameRate, 5);
+
+  constraints.AddMandatory(
+      MediaConstraintsInterface::kNoiseReduction, false);
+  constraints.AddOptional(
+      MediaConstraintsInterface::kNoiseReduction, true);
+
+  CreateLocalVideoSource(&constraints);
+  EXPECT_EQ_WAIT(MediaSourceInterface::kLive, state_observer_->state(),
+                 kMaxWaitMs);
+  const cricket::VideoFormat* format = capturer_->GetCaptureFormat();
+  ASSERT_TRUE(format != NULL);
+  EXPECT_EQ(352, format->width);
+  EXPECT_EQ(288, format->height);
+  EXPECT_EQ(5, format->framerate());
+
+  bool value = true;
+  EXPECT_TRUE(local_source_->options()->video_noise_reduction.Get(&value));
+  EXPECT_FALSE(value);
+  EXPECT_FALSE(local_source_->options()->video_leaky_bucket.Get(&value));
+}
+
+// Tests that the source starts video with the default resolution for
+// screencast if no constraint is set.
+TEST_F(LocalVideoSourceTest, ScreencastResolutionNoConstraint) {
+  capturer_->TestWithoutCameraFormats();
+  capturer_->SetScreencast(true);
+
+  CreateLocalVideoSource();
+  EXPECT_EQ_WAIT(MediaSourceInterface::kLive, state_observer_->state(),
+                 kMaxWaitMs);
+  const cricket::VideoFormat* format = capturer_->GetCaptureFormat();
+  ASSERT_TRUE(format != NULL);
+  EXPECT_EQ(640, format->width);
+  EXPECT_EQ(480, format->height);
+  EXPECT_EQ(30, format->framerate());
+}
+
+// Tests that the source starts video with the max width and height set by
+// constraints for screencast.
+TEST_F(LocalVideoSourceTest, ScreencastResolutionWithConstraint) {
+  FakeConstraints constraints;
+  constraints.AddMandatory(MediaConstraintsInterface::kMaxWidth, 480);
+  constraints.AddMandatory(MediaConstraintsInterface::kMaxHeight, 270);
+
+  capturer_->TestWithoutCameraFormats();
+  capturer_->SetScreencast(true);
+
+  CreateLocalVideoSource(&constraints);
+  EXPECT_EQ_WAIT(MediaSourceInterface::kLive, state_observer_->state(),
+                 kMaxWaitMs);
+  const cricket::VideoFormat* format = capturer_->GetCaptureFormat();
+  ASSERT_TRUE(format != NULL);
+  EXPECT_EQ(480, format->width);
+  EXPECT_EQ(270, format->height);
+  EXPECT_EQ(30, format->framerate());
+}
+
+TEST_F(LocalVideoSourceTest, MandatorySubOneFpsConstraints) {
+  FakeConstraints constraints;
+  constraints.AddMandatory(MediaConstraintsInterface::kMaxFrameRate, 0.5);
+
+  CreateLocalVideoSource(&constraints);
+  EXPECT_EQ_WAIT(MediaSourceInterface::kEnded, state_observer_->state(),
+                 kMaxWaitMs);
+  ASSERT_TRUE(capturer_->GetCaptureFormat() == NULL);
+}
+
+TEST_F(LocalVideoSourceTest, OptionalSubOneFpsConstraints) {
+  FakeConstraints constraints;
+  constraints.AddOptional(MediaConstraintsInterface::kMaxFrameRate, 0.5);
+
+  CreateLocalVideoSource(&constraints);
+  EXPECT_EQ_WAIT(MediaSourceInterface::kLive, state_observer_->state(),
+                 kMaxWaitMs);
+  const cricket::VideoFormat* format = capturer_->GetCaptureFormat();
+  ASSERT_TRUE(format != NULL);
+  EXPECT_EQ(1, format->framerate());
+}
+
diff --git a/talk/app/webrtc/mediaconstraintsinterface.cc b/talk/app/webrtc/mediaconstraintsinterface.cc
new file mode 100644
index 0000000..2e6af77
--- /dev/null
+++ b/talk/app/webrtc/mediaconstraintsinterface.cc
@@ -0,0 +1,78 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/mediaconstraintsinterface.h"
+
+#include "talk/base/stringencode.h"
+
+namespace webrtc {
+
+const char MediaConstraintsInterface::kValueTrue[] = "true";
+const char MediaConstraintsInterface::kValueFalse[] = "false";
+
+// Set |value| to the value associated with the first appearance of |key|, or
+// return false if |key| is not found.
+bool MediaConstraintsInterface::Constraints::FindFirst(
+    const std::string& key, std::string* value) const {
+  for (Constraints::const_iterator iter = begin(); iter != end(); ++iter) {
+    if (iter->key == key) {
+      *value = iter->value;
+      return true;
+    }
+  }
+  return false;
+}
+
+// Find the highest-priority instance of the boolean-valued constraint) named by
+// |key| and return its value as |value|. |constraints| can be null.
+// If |mandatory_constraints| is non-null, it is incremented if the key appears
+// among the mandatory constraints.
+// Returns true if the key was found and has a valid boolean value.
+// If the key appears multiple times as an optional constraint, appearances
+// after the first are ignored.
+// Note: Because this uses FindFirst, repeated optional constraints whose
+// first instance has an unrecognized value are not handled precisely in
+// accordance with the specification.
+bool FindConstraint(const MediaConstraintsInterface* constraints,
+                    const std::string& key, bool* value,
+                    size_t* mandatory_constraints) {
+  std::string string_value;
+  if (!constraints) {
+    return false;
+  }
+  if (constraints->GetMandatory().FindFirst(key, &string_value)) {
+    if (mandatory_constraints)
+      ++*mandatory_constraints;
+    return talk_base::FromString(string_value, value);
+  }
+  if (constraints->GetOptional().FindFirst(key, &string_value)) {
+    return talk_base::FromString(string_value, value);
+  }
+  return false;
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/mediaconstraintsinterface.h b/talk/app/webrtc/mediaconstraintsinterface.h
new file mode 100644
index 0000000..a6b23c6
--- /dev/null
+++ b/talk/app/webrtc/mediaconstraintsinterface.h
@@ -0,0 +1,129 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 interface for MediaConstraints, corresponding to
+// the definition at
+// http://www.w3.org/TR/mediacapture-streams/#mediastreamconstraints and also
+// used in WebRTC: http://dev.w3.org/2011/webrtc/editor/webrtc.html#constraints.
+
+#ifndef TALK_APP_WEBRTC_MEDIACONSTRAINTSINTERFACE_H_
+#define TALK_APP_WEBRTC_MEDIACONSTRAINTSINTERFACE_H_
+
+#include <string>
+#include <vector>
+
+namespace webrtc {
+
+// MediaConstraintsInterface
+// Interface used for passing arguments about media constraints
+// to the MediaStream and PeerConnection implementation.
+class MediaConstraintsInterface {
+ public:
+  struct Constraint {
+    Constraint() {}
+    Constraint(const std::string& key, const std::string value)
+        : key(key), value(value) {
+    }
+    std::string key;
+    std::string value;
+  };
+
+  class Constraints : public std::vector<Constraint> {
+   public:
+    bool FindFirst(const std::string& key, std::string* value) const;
+  };
+
+  virtual const Constraints& GetMandatory() const = 0;
+  virtual const Constraints& GetOptional() const = 0;
+
+
+  // Constraint keys used by a local video source.
+  // Specified by draft-alvestrand-constraints-resolution-00b
+  static const char kMinAspectRatio[];  // minAspectRatio
+  static const char kMaxAspectRatio[];  // maxAspectRatio
+  static const char kMaxWidth[];  // maxWidth
+  static const char kMinWidth[];  // minWidth
+  static const char kMaxHeight[];  // maxHeight
+  static const char kMinHeight[];  // minHeight
+  static const char kMaxFrameRate[];  // maxFrameRate
+  static const char kMinFrameRate[];  // minFrameRate
+
+  // Constraint keys used by a local audio source.
+  // These keys are google specific.
+  static const char kEchoCancellation[];  // googEchoCancellation
+  static const char kExperimentalEchoCancellation[];  // googEchoCancellation2
+  static const char kAutoGainControl[];  // googAutoGainControl
+  static const char kExperimentalAutoGainControl[];  // googAutoGainControl2
+  static const char kNoiseSuppression[];  // googNoiseSuppression
+  static const char kHighpassFilter[];  // googHighpassFilter
+
+  // Google-specific constraint keys for a local video source
+  static const char kNoiseReduction[];  // googNoiseReduction
+  static const char kLeakyBucket[];  // googLeakyBucket
+  // googTemporalLayeredScreencast
+  static const char kTemporalLayeredScreencast[];
+
+  // Constraint keys for CreateOffer / CreateAnswer
+  // Specified by the W3C PeerConnection spec
+  static const char kOfferToReceiveVideo[];  // OfferToReceiveVideo
+  static const char kOfferToReceiveAudio[];  // OfferToReceiveAudio
+  static const char kVoiceActivityDetection[];  // VoiceActivityDetection
+  static const char kIceRestart[];  // IceRestart
+  // These keys are google specific.
+  static const char kUseRtpMux[];  // googUseRtpMUX
+
+  // Constraints values.
+  static const char kValueTrue[];  // true
+  static const char kValueFalse[];  // false
+
+  // Temporary pseudo-constraints used to enable DTLS-SRTP
+  static const char kEnableDtlsSrtp[];  // Enable DTLS-SRTP
+  // Temporary pseudo-constraints used to enable DataChannels
+  static const char kEnableRtpDataChannels[];  // Enable RTP DataChannels
+  static const char kEnableSctpDataChannels[];  // Enable SCTP DataChannels
+
+  // The prefix of internal-only constraints whose JS set values should be
+  // stripped by Chrome before passed down to Libjingle.
+  static const char kInternalConstraintPrefix[];
+
+  // This constraint is for internal use only, representing the Chrome command
+  // line flag. So it is prefixed with "internal" so JS values will be removed.
+  // Used by a local audio source.
+  static const char kInternalAecDump[];  // internalAecDump
+
+ protected:
+  // Dtor protected as objects shouldn't be deleted via this interface
+  virtual ~MediaConstraintsInterface() {}
+};
+
+bool FindConstraint(const MediaConstraintsInterface* constraints,
+                    const std::string& key, bool* value,
+                    size_t* mandatory_constraints);
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_MEDIACONSTRAINTSINTERFACE_H_
diff --git a/talk/app/webrtc/mediastream.cc b/talk/app/webrtc/mediastream.cc
new file mode 100644
index 0000000..aad8e85
--- /dev/null
+++ b/talk/app/webrtc/mediastream.cc
@@ -0,0 +1,112 @@
+/*
+ * 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/mediastream.h"
+#include "talk/base/logging.h"
+
+namespace webrtc {
+
+template <class V>
+static typename V::iterator FindTrack(V* vector,
+                                      const std::string& track_id) {
+  typename V::iterator it = vector->begin();
+  for (; it != vector->end(); ++it) {
+    if ((*it)->id() == track_id) {
+      break;
+    }
+  }
+  return it;
+};
+
+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) {
+}
+
+bool MediaStream::AddTrack(AudioTrackInterface* track) {
+  return AddTrack<AudioTrackVector, AudioTrackInterface>(&audio_tracks_, track);
+}
+
+bool MediaStream::AddTrack(VideoTrackInterface* track) {
+  return AddTrack<VideoTrackVector, VideoTrackInterface>(&video_tracks_, track);
+}
+
+bool MediaStream::RemoveTrack(AudioTrackInterface* track) {
+  return RemoveTrack<AudioTrackVector>(&audio_tracks_, track);
+}
+
+bool MediaStream::RemoveTrack(VideoTrackInterface* track) {
+  return RemoveTrack<VideoTrackVector>(&video_tracks_, track);
+}
+
+talk_base::scoped_refptr<AudioTrackInterface>
+MediaStream::FindAudioTrack(const std::string& track_id) {
+  AudioTrackVector::iterator it = FindTrack(&audio_tracks_, track_id);
+  if (it == audio_tracks_.end())
+    return NULL;
+  return *it;
+}
+
+talk_base::scoped_refptr<VideoTrackInterface>
+MediaStream::FindVideoTrack(const std::string& track_id) {
+  VideoTrackVector::iterator it = FindTrack(&video_tracks_, track_id);
+  if (it == video_tracks_.end())
+    return NULL;
+  return *it;
+}
+
+template <typename TrackVector, typename Track>
+bool MediaStream::AddTrack(TrackVector* tracks, Track* track) {
+  typename TrackVector::iterator it = FindTrack(tracks, track->id());
+  if (it != tracks->end())
+    return false;
+  tracks->push_back(track);
+  FireOnChanged();
+  return true;
+}
+
+template <typename TrackVector>
+bool MediaStream::RemoveTrack(TrackVector* tracks,
+                              MediaStreamTrackInterface* track) {
+  ASSERT(tracks != NULL);
+  if (!track)
+    return false;
+  typename TrackVector::iterator it = FindTrack(tracks, track->id());
+  if (it == tracks->end())
+    return false;
+  tracks->erase(it);
+  FireOnChanged();
+  return true;
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/mediastream.h b/talk/app/webrtc/mediastream.h
new file mode 100644
index 0000000..e5ac6eb
--- /dev/null
+++ b/talk/app/webrtc/mediastream.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.
+ */
+
+// This file contains the implementation of MediaStreamInterface interface.
+
+#ifndef TALK_APP_WEBRTC_MEDIASTREAM_H_
+#define TALK_APP_WEBRTC_MEDIASTREAM_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/app/webrtc/mediastreaminterface.h"
+#include "talk/app/webrtc/notifier.h"
+
+namespace webrtc {
+
+class MediaStream : public Notifier<MediaStreamInterface> {
+ public:
+  static talk_base::scoped_refptr<MediaStream> Create(const std::string& label);
+
+  virtual std::string label() const OVERRIDE { return label_; }
+
+  virtual bool AddTrack(AudioTrackInterface* track) OVERRIDE;
+  virtual bool AddTrack(VideoTrackInterface* track) OVERRIDE;
+  virtual bool RemoveTrack(AudioTrackInterface* track) OVERRIDE;
+  virtual bool RemoveTrack(VideoTrackInterface* track) OVERRIDE;
+  virtual talk_base::scoped_refptr<AudioTrackInterface>
+      FindAudioTrack(const std::string& track_id);
+  virtual talk_base::scoped_refptr<VideoTrackInterface>
+      FindVideoTrack(const std::string& track_id);
+
+  virtual AudioTrackVector GetAudioTracks() OVERRIDE { return audio_tracks_; }
+  virtual VideoTrackVector GetVideoTracks() OVERRIDE { return video_tracks_; }
+
+ protected:
+  explicit MediaStream(const std::string& label);
+
+ private:
+  template <typename TrackVector, typename Track>
+  bool AddTrack(TrackVector* Tracks, Track* track);
+  template <typename TrackVector>
+  bool RemoveTrack(TrackVector* Tracks, MediaStreamTrackInterface* track);
+
+  std::string label_;
+  AudioTrackVector audio_tracks_;
+  VideoTrackVector video_tracks_;
+};
+
+}  // 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..bb2d50e
--- /dev/null
+++ b/talk/app/webrtc/mediastream_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 "talk/app/webrtc/audiotrack.h"
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/app/webrtc/videotrack.h"
+#include "talk/base/refcount.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/gunit.h"
+#include "testing/base/public/gmock.h"
+
+static const char kStreamLabel1[] = "local_stream_1";
+static const char kVideoTrackId[] = "dummy_video_cam_1";
+static const char kAudioTrackId[] = "dummy_microphone_1";
+
+using talk_base::scoped_refptr;
+using ::testing::Exactly;
+
+namespace webrtc {
+
+// Helper class to test Observer.
+class MockObserver : public ObserverInterface {
+ public:
+  MockObserver() {}
+
+  MOCK_METHOD0(OnChanged, void());
+};
+
+class MediaStreamTest: public testing::Test {
+ protected:
+  virtual void SetUp() {
+    stream_ = MediaStream::Create(kStreamLabel1);
+    ASSERT_TRUE(stream_.get() != NULL);
+
+    video_track_ = VideoTrack::Create(kVideoTrackId, NULL);
+    ASSERT_TRUE(video_track_.get() != NULL);
+    EXPECT_EQ(MediaStreamTrackInterface::kInitializing, video_track_->state());
+
+    audio_track_ = AudioTrack::Create(kAudioTrackId, NULL);
+
+    ASSERT_TRUE(audio_track_.get() != NULL);
+    EXPECT_EQ(MediaStreamTrackInterface::kInitializing, audio_track_->state());
+
+    EXPECT_TRUE(stream_->AddTrack(video_track_));
+    EXPECT_FALSE(stream_->AddTrack(video_track_));
+    EXPECT_TRUE(stream_->AddTrack(audio_track_));
+    EXPECT_FALSE(stream_->AddTrack(audio_track_));
+  }
+
+  void ChangeTrack(MediaStreamTrackInterface* track) {
+    MockObserver observer;
+    track->RegisterObserver(&observer);
+
+    EXPECT_CALL(observer, OnChanged())
+        .Times(Exactly(1));
+    track->set_enabled(false);
+    EXPECT_FALSE(track->enabled());
+
+    EXPECT_CALL(observer, OnChanged())
+        .Times(Exactly(1));
+    track->set_state(MediaStreamTrackInterface::kLive);
+    EXPECT_EQ(MediaStreamTrackInterface::kLive, track->state());
+  }
+
+  scoped_refptr<MediaStreamInterface> stream_;
+  scoped_refptr<AudioTrackInterface> audio_track_;
+  scoped_refptr<VideoTrackInterface> video_track_;
+};
+
+TEST_F(MediaStreamTest, GetTrackInfo) {
+  ASSERT_EQ(1u, stream_->GetVideoTracks().size());
+  ASSERT_EQ(1u, stream_->GetAudioTracks().size());
+
+  // Verify the video track.
+  scoped_refptr<webrtc::MediaStreamTrackInterface> video_track(
+      stream_->GetVideoTracks()[0]);
+  EXPECT_EQ(0, video_track->id().compare(kVideoTrackId));
+  EXPECT_TRUE(video_track->enabled());
+
+  ASSERT_EQ(1u, stream_->GetVideoTracks().size());
+  EXPECT_TRUE(stream_->GetVideoTracks()[0].get() == video_track.get());
+  EXPECT_TRUE(stream_->FindVideoTrack(video_track->id()).get()
+              == video_track.get());
+  video_track = stream_->GetVideoTracks()[0];
+  EXPECT_EQ(0, video_track->id().compare(kVideoTrackId));
+  EXPECT_TRUE(video_track->enabled());
+
+  // Verify the audio track.
+  scoped_refptr<webrtc::MediaStreamTrackInterface> audio_track(
+      stream_->GetAudioTracks()[0]);
+  EXPECT_EQ(0, audio_track->id().compare(kAudioTrackId));
+  EXPECT_TRUE(audio_track->enabled());
+  ASSERT_EQ(1u, stream_->GetAudioTracks().size());
+  EXPECT_TRUE(stream_->GetAudioTracks()[0].get() == audio_track.get());
+  EXPECT_TRUE(stream_->FindAudioTrack(audio_track->id()).get()
+              == audio_track.get());
+  audio_track = stream_->GetAudioTracks()[0];
+  EXPECT_EQ(0, audio_track->id().compare(kAudioTrackId));
+  EXPECT_TRUE(audio_track->enabled());
+}
+
+TEST_F(MediaStreamTest, RemoveTrack) {
+  MockObserver observer;
+  stream_->RegisterObserver(&observer);
+
+  EXPECT_CALL(observer, OnChanged())
+      .Times(Exactly(2));
+
+  EXPECT_TRUE(stream_->RemoveTrack(audio_track_));
+  EXPECT_FALSE(stream_->RemoveTrack(audio_track_));
+  EXPECT_EQ(0u, stream_->GetAudioTracks().size());
+  EXPECT_EQ(0u, stream_->GetAudioTracks().size());
+
+  EXPECT_TRUE(stream_->RemoveTrack(video_track_));
+  EXPECT_FALSE(stream_->RemoveTrack(video_track_));
+
+  EXPECT_EQ(0u, stream_->GetVideoTracks().size());
+  EXPECT_EQ(0u, stream_->GetVideoTracks().size());
+
+  EXPECT_FALSE(stream_->RemoveTrack(static_cast<AudioTrackInterface*>(NULL)));
+  EXPECT_FALSE(stream_->RemoveTrack(static_cast<VideoTrackInterface*>(NULL)));
+}
+
+TEST_F(MediaStreamTest, ChangeVideoTrack) {
+  scoped_refptr<webrtc::VideoTrackInterface> video_track(
+      stream_->GetVideoTracks()[0]);
+  ChangeTrack(video_track.get());
+}
+
+TEST_F(MediaStreamTest, ChangeAudioTrack) {
+  scoped_refptr<webrtc::AudioTrackInterface> audio_track(
+      stream_->GetAudioTracks()[0]);
+  ChangeTrack(audio_track.get());
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/mediastreamhandler.cc b/talk/app/webrtc/mediastreamhandler.cc
new file mode 100644
index 0000000..a6a45b2
--- /dev/null
+++ b/talk/app/webrtc/mediastreamhandler.cc
@@ -0,0 +1,440 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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"
+
+#include "talk/app/webrtc/localaudiosource.h"
+#include "talk/app/webrtc/localvideosource.h"
+#include "talk/app/webrtc/videosourceinterface.h"
+
+namespace webrtc {
+
+TrackHandler::TrackHandler(MediaStreamTrackInterface* track, uint32 ssrc)
+    : track_(track),
+      ssrc_(ssrc),
+      state_(track->state()),
+      enabled_(track->enabled()) {
+  track_->RegisterObserver(this);
+}
+
+TrackHandler::~TrackHandler() {
+  track_->UnregisterObserver(this);
+}
+
+void TrackHandler::OnChanged() {
+  if (state_ != track_->state()) {
+    state_ = track_->state();
+    OnStateChanged();
+  }
+  if (enabled_ != track_->enabled()) {
+    enabled_ = track_->enabled();
+    OnEnabledChanged();
+  }
+}
+
+LocalAudioTrackHandler::LocalAudioTrackHandler(
+    AudioTrackInterface* track,
+    uint32 ssrc,
+    AudioProviderInterface* provider)
+    : TrackHandler(track, ssrc),
+      audio_track_(track),
+      provider_(provider) {
+  OnEnabledChanged();
+}
+
+LocalAudioTrackHandler::~LocalAudioTrackHandler() {
+}
+
+void LocalAudioTrackHandler::OnStateChanged() {
+  // TODO(perkj): What should happen when the state change?
+}
+
+void LocalAudioTrackHandler::Stop() {
+  cricket::AudioOptions options;
+  provider_->SetAudioSend(ssrc(), false, options);
+}
+
+void LocalAudioTrackHandler::OnEnabledChanged() {
+  cricket::AudioOptions options;
+  if (audio_track_->enabled() && audio_track_->GetSource()) {
+    options = static_cast<LocalAudioSource*>(
+        audio_track_->GetSource())->options();
+  }
+  provider_->SetAudioSend(ssrc(), audio_track_->enabled(), options);
+}
+
+RemoteAudioTrackHandler::RemoteAudioTrackHandler(
+    AudioTrackInterface* track,
+    uint32 ssrc,
+    AudioProviderInterface* provider)
+    : TrackHandler(track, ssrc),
+      audio_track_(track),
+      provider_(provider) {
+  OnEnabledChanged();
+  provider_->SetAudioRenderer(ssrc, audio_track_->FrameInput());
+}
+
+RemoteAudioTrackHandler::~RemoteAudioTrackHandler() {
+}
+
+void RemoteAudioTrackHandler::Stop() {
+  provider_->SetAudioPlayout(ssrc(), false);
+}
+
+void RemoteAudioTrackHandler::OnStateChanged() {
+}
+
+void RemoteAudioTrackHandler::OnEnabledChanged() {
+  provider_->SetAudioPlayout(ssrc(), audio_track_->enabled());
+}
+
+LocalVideoTrackHandler::LocalVideoTrackHandler(
+    VideoTrackInterface* track,
+    uint32 ssrc,
+    VideoProviderInterface* provider)
+    : TrackHandler(track, ssrc),
+      local_video_track_(track),
+      provider_(provider) {
+  VideoSourceInterface* source = local_video_track_->GetSource();
+  if (source)
+    provider_->SetCaptureDevice(ssrc, source->GetVideoCapturer());
+  OnEnabledChanged();
+}
+
+LocalVideoTrackHandler::~LocalVideoTrackHandler() {
+}
+
+void LocalVideoTrackHandler::OnStateChanged() {
+}
+
+void LocalVideoTrackHandler::Stop() {
+  provider_->SetCaptureDevice(ssrc(), NULL);
+  provider_->SetVideoSend(ssrc(), false, NULL);
+}
+
+void LocalVideoTrackHandler::OnEnabledChanged() {
+  const cricket::VideoOptions* options = NULL;
+  VideoSourceInterface* source = local_video_track_->GetSource();
+  if (local_video_track_->enabled() && source) {
+    options = source->options();
+  }
+  provider_->SetVideoSend(ssrc(), local_video_track_->enabled(), options);
+}
+
+RemoteVideoTrackHandler::RemoteVideoTrackHandler(
+    VideoTrackInterface* track,
+    uint32 ssrc,
+    VideoProviderInterface* provider)
+    : TrackHandler(track, ssrc),
+      remote_video_track_(track),
+      provider_(provider) {
+  OnEnabledChanged();
+}
+
+RemoteVideoTrackHandler::~RemoteVideoTrackHandler() {
+}
+
+void RemoteVideoTrackHandler::Stop() {
+  // Since cricket::VideoRenderer is not reference counted
+  // we need to remove the renderer before we are deleted.
+  provider_->SetVideoPlayout(ssrc(), false, NULL);
+}
+
+void RemoteVideoTrackHandler::OnStateChanged() {
+}
+
+void RemoteVideoTrackHandler::OnEnabledChanged() {
+  provider_->SetVideoPlayout(ssrc(),
+                             remote_video_track_->enabled(),
+                             remote_video_track_->FrameInput());
+}
+
+MediaStreamHandler::MediaStreamHandler(MediaStreamInterface* stream,
+                                       AudioProviderInterface* audio_provider,
+                                       VideoProviderInterface* video_provider)
+    : stream_(stream),
+      audio_provider_(audio_provider),
+      video_provider_(video_provider) {
+}
+
+MediaStreamHandler::~MediaStreamHandler() {
+  for (TrackHandlers::iterator it = track_handlers_.begin();
+       it != track_handlers_.end(); ++it) {
+    delete *it;
+  }
+}
+
+void MediaStreamHandler::RemoveTrack(MediaStreamTrackInterface* track) {
+  for (TrackHandlers::iterator it = track_handlers_.begin();
+       it != track_handlers_.end(); ++it) {
+    if ((*it)->track() == track) {
+      TrackHandler* track = *it;
+      track->Stop();
+      delete track;
+      track_handlers_.erase(it);
+      break;
+    }
+  }
+}
+
+TrackHandler* MediaStreamHandler::FindTrackHandler(
+    MediaStreamTrackInterface* track) {
+  TrackHandlers::iterator it = track_handlers_.begin();
+  for (; it != track_handlers_.end(); ++it) {
+    if ((*it)->track() == track) {
+      return *it;
+      break;
+    }
+  }
+  return NULL;
+}
+
+MediaStreamInterface* MediaStreamHandler::stream() {
+  return stream_.get();
+}
+
+void MediaStreamHandler::OnChanged() {
+}
+
+void MediaStreamHandler::Stop() {
+  for (TrackHandlers::const_iterator it = track_handlers_.begin();
+      it != track_handlers_.end(); ++it) {
+    (*it)->Stop();
+  }
+}
+
+LocalMediaStreamHandler::LocalMediaStreamHandler(
+    MediaStreamInterface* stream,
+    AudioProviderInterface* audio_provider,
+    VideoProviderInterface* video_provider)
+    : MediaStreamHandler(stream, audio_provider, video_provider) {
+}
+
+LocalMediaStreamHandler::~LocalMediaStreamHandler() {
+}
+
+void LocalMediaStreamHandler::AddAudioTrack(AudioTrackInterface* audio_track,
+                                            uint32 ssrc) {
+  ASSERT(!FindTrackHandler(audio_track));
+
+  TrackHandler* handler(new LocalAudioTrackHandler(audio_track, ssrc,
+                                                   audio_provider_));
+  track_handlers_.push_back(handler);
+}
+
+void LocalMediaStreamHandler::AddVideoTrack(VideoTrackInterface* video_track,
+                                            uint32 ssrc) {
+  ASSERT(!FindTrackHandler(video_track));
+
+  TrackHandler* handler(new LocalVideoTrackHandler(video_track, ssrc,
+                                                   video_provider_));
+  track_handlers_.push_back(handler);
+}
+
+RemoteMediaStreamHandler::RemoteMediaStreamHandler(
+    MediaStreamInterface* stream,
+    AudioProviderInterface* audio_provider,
+    VideoProviderInterface* video_provider)
+    : MediaStreamHandler(stream, audio_provider, video_provider) {
+}
+
+RemoteMediaStreamHandler::~RemoteMediaStreamHandler() {
+}
+
+void RemoteMediaStreamHandler::AddAudioTrack(AudioTrackInterface* audio_track,
+                                             uint32 ssrc) {
+  ASSERT(!FindTrackHandler(audio_track));
+  TrackHandler* handler(
+      new RemoteAudioTrackHandler(audio_track, ssrc, audio_provider_));
+  track_handlers_.push_back(handler);
+}
+
+void RemoteMediaStreamHandler::AddVideoTrack(VideoTrackInterface* video_track,
+                                             uint32 ssrc) {
+  ASSERT(!FindTrackHandler(video_track));
+  TrackHandler* handler(
+      new RemoteVideoTrackHandler(video_track, ssrc, video_provider_));
+  track_handlers_.push_back(handler);
+}
+
+MediaStreamHandlerContainer::MediaStreamHandlerContainer(
+    AudioProviderInterface* audio_provider,
+    VideoProviderInterface* video_provider)
+    : audio_provider_(audio_provider),
+      video_provider_(video_provider) {
+}
+
+MediaStreamHandlerContainer::~MediaStreamHandlerContainer() {
+  ASSERT(remote_streams_handlers_.empty());
+  ASSERT(local_streams_handlers_.empty());
+}
+
+void MediaStreamHandlerContainer::TearDown() {
+  for (StreamHandlerList::iterator it = remote_streams_handlers_.begin();
+       it != remote_streams_handlers_.end(); ++it) {
+    (*it)->Stop();
+    delete *it;
+  }
+  remote_streams_handlers_.clear();
+  for (StreamHandlerList::iterator it = local_streams_handlers_.begin();
+       it != local_streams_handlers_.end(); ++it) {
+    (*it)->Stop();
+    delete *it;
+  }
+  local_streams_handlers_.clear();
+}
+
+void MediaStreamHandlerContainer::RemoveRemoteStream(
+    MediaStreamInterface* stream) {
+  DeleteStreamHandler(&remote_streams_handlers_, stream);
+}
+
+void MediaStreamHandlerContainer::AddRemoteAudioTrack(
+    MediaStreamInterface* stream,
+    AudioTrackInterface* audio_track,
+    uint32 ssrc) {
+  MediaStreamHandler* handler = FindStreamHandler(remote_streams_handlers_,
+                                                  stream);
+  if (handler == NULL) {
+    handler = CreateRemoteStreamHandler(stream);
+  }
+  handler->AddAudioTrack(audio_track, ssrc);
+}
+
+void MediaStreamHandlerContainer::AddRemoteVideoTrack(
+    MediaStreamInterface* stream,
+    VideoTrackInterface* video_track,
+    uint32 ssrc) {
+  MediaStreamHandler* handler = FindStreamHandler(remote_streams_handlers_,
+                                                  stream);
+  if (handler == NULL) {
+    handler = CreateRemoteStreamHandler(stream);
+  }
+  handler->AddVideoTrack(video_track, ssrc);
+}
+
+void MediaStreamHandlerContainer::RemoveRemoteTrack(
+    MediaStreamInterface* stream,
+    MediaStreamTrackInterface* track) {
+  MediaStreamHandler* handler = FindStreamHandler(remote_streams_handlers_,
+                                                  stream);
+  if (!VERIFY(handler != NULL)) {
+    LOG(LS_WARNING) << "Local MediaStreamHandler for stream  with id "
+                    << stream->label() << "doesnt't exist.";
+    return;
+  }
+  handler->RemoveTrack(track);
+}
+
+void MediaStreamHandlerContainer::RemoveLocalStream(
+    MediaStreamInterface* stream) {
+  DeleteStreamHandler(&local_streams_handlers_, stream);
+}
+
+void MediaStreamHandlerContainer::AddLocalAudioTrack(
+    MediaStreamInterface* stream,
+    AudioTrackInterface* audio_track,
+    uint32 ssrc) {
+  MediaStreamHandler* handler = FindStreamHandler(local_streams_handlers_,
+                                                  stream);
+  if (handler == NULL) {
+    handler = CreateLocalStreamHandler(stream);
+  }
+  handler->AddAudioTrack(audio_track, ssrc);
+}
+
+void MediaStreamHandlerContainer::AddLocalVideoTrack(
+    MediaStreamInterface* stream,
+    VideoTrackInterface* video_track,
+    uint32 ssrc) {
+  MediaStreamHandler* handler = FindStreamHandler(local_streams_handlers_,
+                                                  stream);
+  if (handler == NULL) {
+    handler = CreateLocalStreamHandler(stream);
+  }
+  handler->AddVideoTrack(video_track, ssrc);
+}
+
+void MediaStreamHandlerContainer::RemoveLocalTrack(
+    MediaStreamInterface* stream,
+    MediaStreamTrackInterface* track) {
+  MediaStreamHandler* handler = FindStreamHandler(local_streams_handlers_,
+                                                  stream);
+  if (!VERIFY(handler != NULL)) {
+    LOG(LS_WARNING) << "Remote MediaStreamHandler for stream with id "
+                    << stream->label() << "doesnt't exist.";
+    return;
+  }
+  handler->RemoveTrack(track);
+}
+
+MediaStreamHandler* MediaStreamHandlerContainer::CreateRemoteStreamHandler(
+    MediaStreamInterface* stream) {
+  ASSERT(!FindStreamHandler(remote_streams_handlers_, stream));
+
+  RemoteMediaStreamHandler* handler =
+      new RemoteMediaStreamHandler(stream, audio_provider_, video_provider_);
+  remote_streams_handlers_.push_back(handler);
+  return handler;
+}
+
+MediaStreamHandler* MediaStreamHandlerContainer::CreateLocalStreamHandler(
+    MediaStreamInterface* stream) {
+  ASSERT(!FindStreamHandler(local_streams_handlers_, stream));
+
+  LocalMediaStreamHandler* handler =
+      new LocalMediaStreamHandler(stream, audio_provider_, video_provider_);
+  local_streams_handlers_.push_back(handler);
+  return handler;
+}
+
+MediaStreamHandler* MediaStreamHandlerContainer::FindStreamHandler(
+    const StreamHandlerList& handlers,
+    MediaStreamInterface* stream) {
+  StreamHandlerList::const_iterator it = handlers.begin();
+  for (; it != handlers.end(); ++it) {
+    if ((*it)->stream() == stream) {
+      return *it;
+    }
+  }
+  return NULL;
+}
+
+void MediaStreamHandlerContainer::DeleteStreamHandler(
+    StreamHandlerList* streamhandlers, MediaStreamInterface* stream) {
+  StreamHandlerList::iterator it = streamhandlers->begin();
+  for (; it != streamhandlers->end(); ++it) {
+    if ((*it)->stream() == stream) {
+      (*it)->Stop();
+      delete *it;
+      streamhandlers->erase(it);
+      break;
+    }
+  }
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/mediastreamhandler.h b/talk/app/webrtc/mediastreamhandler.h
new file mode 100644
index 0000000..0cd34d6
--- /dev/null
+++ b/talk/app/webrtc/mediastreamhandler.h
@@ -0,0 +1,264 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 that are connected to a certain PeerConnection.
+// Example: If a user sets a rendererer on a remote video track the renderer is
+// connected to the appropriate remote video stream.
+
+#ifndef TALK_APP_WEBRTC_MEDIASTREAMHANDLER_H_
+#define TALK_APP_WEBRTC_MEDIASTREAMHANDLER_H_
+
+#include <list>
+#include <vector>
+
+#include "talk/app/webrtc/mediastreaminterface.h"
+#include "talk/app/webrtc/mediastreamprovider.h"
+#include "talk/app/webrtc/peerconnectioninterface.h"
+#include "talk/base/thread.h"
+
+namespace webrtc {
+
+// TrackHandler listen to events on a MediaStreamTrackInterface that is
+// connected to a certain PeerConnection.
+class TrackHandler : public ObserverInterface {
+ public:
+  TrackHandler(MediaStreamTrackInterface* track, uint32 ssrc);
+  virtual ~TrackHandler();
+  virtual void OnChanged();
+  // Stop using |track_| on this PeerConnection.
+  virtual void Stop() = 0;
+
+  MediaStreamTrackInterface*  track() { return track_; }
+  uint32 ssrc() const { return ssrc_; }
+
+ protected:
+  virtual void OnStateChanged() = 0;
+  virtual void OnEnabledChanged() = 0;
+
+ private:
+  talk_base::scoped_refptr<MediaStreamTrackInterface> track_;
+  uint32 ssrc_;
+  MediaStreamTrackInterface::TrackState state_;
+  bool enabled_;
+};
+
+// LocalAudioTrackHandler listen to events on a local AudioTrack instance
+// connected to a PeerConnection and orders the |provider| to executes the
+// requested change.
+class LocalAudioTrackHandler : public TrackHandler {
+ public:
+  LocalAudioTrackHandler(AudioTrackInterface* track,
+                         uint32 ssrc,
+                         AudioProviderInterface* provider);
+  virtual ~LocalAudioTrackHandler();
+
+  virtual void Stop() OVERRIDE;
+
+ protected:
+  virtual void OnStateChanged() OVERRIDE;
+  virtual void OnEnabledChanged() OVERRIDE;
+
+ private:
+  AudioTrackInterface* audio_track_;
+  AudioProviderInterface* provider_;
+};
+
+// RemoteAudioTrackHandler listen to events on a remote AudioTrack instance
+// connected to a PeerConnection and orders the |provider| to executes the
+// requested change.
+class RemoteAudioTrackHandler : public TrackHandler {
+ public:
+  RemoteAudioTrackHandler(AudioTrackInterface* track,
+                          uint32 ssrc,
+                          AudioProviderInterface* provider);
+  virtual ~RemoteAudioTrackHandler();
+  virtual void Stop() OVERRIDE;
+
+ protected:
+  virtual void OnStateChanged() OVERRIDE;
+  virtual void OnEnabledChanged() OVERRIDE;
+
+ private:
+  AudioTrackInterface* audio_track_;
+  AudioProviderInterface* provider_;
+};
+
+// LocalVideoTrackHandler listen to events on a local VideoTrack instance
+// connected to a PeerConnection and orders the |provider| to executes the
+// requested change.
+class LocalVideoTrackHandler : public TrackHandler {
+ public:
+  LocalVideoTrackHandler(VideoTrackInterface* track,
+                         uint32 ssrc,
+                         VideoProviderInterface* provider);
+  virtual ~LocalVideoTrackHandler();
+  virtual void Stop() OVERRIDE;
+
+ protected:
+  virtual void OnStateChanged() OVERRIDE;
+  virtual void OnEnabledChanged() OVERRIDE;
+
+ private:
+  VideoTrackInterface* local_video_track_;
+  VideoProviderInterface* provider_;
+};
+
+// RemoteVideoTrackHandler listen to events on a remote VideoTrack instance
+// connected to a PeerConnection and orders the |provider| to execute
+// requested changes.
+class RemoteVideoTrackHandler : public TrackHandler {
+ public:
+  RemoteVideoTrackHandler(VideoTrackInterface* track,
+                          uint32 ssrc,
+                          VideoProviderInterface* provider);
+  virtual ~RemoteVideoTrackHandler();
+  virtual void Stop() OVERRIDE;
+
+ protected:
+  virtual void OnStateChanged() OVERRIDE;
+  virtual void OnEnabledChanged() OVERRIDE;
+
+ private:
+  VideoTrackInterface* remote_video_track_;
+  VideoProviderInterface* provider_;
+};
+
+class MediaStreamHandler : public ObserverInterface {
+ public:
+  MediaStreamHandler(MediaStreamInterface* stream,
+                     AudioProviderInterface* audio_provider,
+                     VideoProviderInterface* video_provider);
+  ~MediaStreamHandler();
+  MediaStreamInterface* stream();
+  void Stop();
+
+  virtual void AddAudioTrack(AudioTrackInterface* audio_track, uint32 ssrc) = 0;
+  virtual void AddVideoTrack(VideoTrackInterface* video_track, uint32 ssrc) = 0;
+
+  virtual void RemoveTrack(MediaStreamTrackInterface* track);
+  virtual void OnChanged() OVERRIDE;
+
+ protected:
+  TrackHandler* FindTrackHandler(MediaStreamTrackInterface* track);
+  talk_base::scoped_refptr<MediaStreamInterface> stream_;
+  AudioProviderInterface* audio_provider_;
+  VideoProviderInterface* video_provider_;
+  typedef std::vector<TrackHandler*> TrackHandlers;
+  TrackHandlers track_handlers_;
+};
+
+class LocalMediaStreamHandler : public MediaStreamHandler {
+ public:
+  LocalMediaStreamHandler(MediaStreamInterface* stream,
+                          AudioProviderInterface* audio_provider,
+                          VideoProviderInterface* video_provider);
+  ~LocalMediaStreamHandler();
+
+  virtual void AddAudioTrack(AudioTrackInterface* audio_track,
+                             uint32 ssrc) OVERRIDE;
+  virtual void AddVideoTrack(VideoTrackInterface* video_track,
+                             uint32 ssrc) OVERRIDE;
+};
+
+class RemoteMediaStreamHandler : public MediaStreamHandler {
+ public:
+  RemoteMediaStreamHandler(MediaStreamInterface* stream,
+                           AudioProviderInterface* audio_provider,
+                           VideoProviderInterface* video_provider);
+  ~RemoteMediaStreamHandler();
+  virtual void AddAudioTrack(AudioTrackInterface* audio_track,
+                             uint32 ssrc) OVERRIDE;
+  virtual void AddVideoTrack(VideoTrackInterface* video_track,
+                             uint32 ssrc) OVERRIDE;
+};
+
+// Container for MediaStreamHandlers of currently known local and remote
+// MediaStreams.
+class MediaStreamHandlerContainer {
+ public:
+  MediaStreamHandlerContainer(AudioProviderInterface* audio_provider,
+                              VideoProviderInterface* video_provider);
+  ~MediaStreamHandlerContainer();
+
+  // Notify all referenced objects that MediaStreamHandlerContainer will be
+  // destroyed. This method must be called prior to the dtor and prior to the
+  // |audio_provider| and |video_provider| is destroyed.
+  void TearDown();
+
+  // Remove all TrackHandlers for tracks in |stream| and make sure
+  // the audio_provider and video_provider is notified that the tracks has been
+  // removed.
+  void RemoveRemoteStream(MediaStreamInterface* stream);
+
+  // Create a RemoteAudioTrackHandler and associate |audio_track| with |ssrc|.
+  void AddRemoteAudioTrack(MediaStreamInterface* stream,
+                           AudioTrackInterface* audio_track,
+                           uint32 ssrc);
+  // Create a RemoteVideoTrackHandler and associate |video_track| with |ssrc|.
+  void AddRemoteVideoTrack(MediaStreamInterface* stream,
+                           VideoTrackInterface* video_track,
+                           uint32 ssrc);
+  // Remove the TrackHandler for |track|.
+  void RemoveRemoteTrack(MediaStreamInterface* stream,
+                         MediaStreamTrackInterface* track);
+
+  // Remove all TrackHandlers for tracks in |stream| and make sure
+  // the audio_provider and video_provider is notified that the tracks has been
+  // removed.
+  void RemoveLocalStream(MediaStreamInterface* stream);
+
+  // Create a LocalAudioTrackHandler and associate |audio_track| with |ssrc|.
+  void AddLocalAudioTrack(MediaStreamInterface* stream,
+                          AudioTrackInterface* audio_track,
+                          uint32 ssrc);
+  // Create a LocalVideoTrackHandler and associate |video_track| with |ssrc|.
+  void AddLocalVideoTrack(MediaStreamInterface* stream,
+                          VideoTrackInterface* video_track,
+                          uint32 ssrc);
+  // Remove the TrackHandler for |track|.
+  void RemoveLocalTrack(MediaStreamInterface* stream,
+                        MediaStreamTrackInterface* track);
+
+ private:
+  typedef std::list<MediaStreamHandler*> StreamHandlerList;
+  MediaStreamHandler* FindStreamHandler(const StreamHandlerList& handlers,
+                                        MediaStreamInterface* stream);
+  MediaStreamHandler* CreateRemoteStreamHandler(MediaStreamInterface* stream);
+  MediaStreamHandler* CreateLocalStreamHandler(MediaStreamInterface* stream);
+  void DeleteStreamHandler(StreamHandlerList* streamhandlers,
+                           MediaStreamInterface* stream);
+
+  StreamHandlerList local_streams_handlers_;
+  StreamHandlerList remote_streams_handlers_;
+  AudioProviderInterface* audio_provider_;
+  VideoProviderInterface* video_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..bc4189bf
--- /dev/null
+++ b/talk/app/webrtc/mediastreamhandler_unittest.cc
@@ -0,0 +1,297 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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"
+
+#include <string>
+
+#include "talk/app/webrtc/audiotrack.h"
+#include "talk/app/webrtc/localvideosource.h"
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/app/webrtc/streamcollection.h"
+#include "talk/app/webrtc/videotrack.h"
+#include "talk/base/gunit.h"
+#include "talk/media/base/fakevideocapturer.h"
+#include "talk/media/base/mediachannel.h"
+#include "testing/base/public/gmock.h"
+
+using ::testing::_;
+using ::testing::Exactly;
+
+static const char kStreamLabel1[] = "local_stream_1";
+static const char kVideoTrackId[] = "video_1";
+static const char kAudioTrackId[] = "audio_1";
+static const uint32 kVideoSsrc = 98;
+static const uint32 kAudioSsrc = 99;
+
+namespace webrtc {
+
+// Helper class to test MediaStreamHandler.
+class MockAudioProvider : public AudioProviderInterface {
+ public:
+  virtual ~MockAudioProvider() {}
+  MOCK_METHOD2(SetAudioPlayout, void(uint32 ssrc, bool enable));
+  MOCK_METHOD3(SetAudioSend, void(uint32 ssrc, bool enable,
+                                  const cricket::AudioOptions& options));
+  MOCK_METHOD2(SetAudioRenderer, bool(uint32, cricket::AudioRenderer*));
+};
+
+// Helper class to test MediaStreamHandler.
+class MockVideoProvider : public VideoProviderInterface {
+ public:
+  virtual ~MockVideoProvider() {}
+  MOCK_METHOD2(SetCaptureDevice, bool(uint32 ssrc,
+                                      cricket::VideoCapturer* camera));
+  MOCK_METHOD3(SetVideoPlayout, void(uint32 ssrc,
+                                     bool enable,
+                                     cricket::VideoRenderer* renderer));
+  MOCK_METHOD3(SetVideoSend, void(uint32 ssrc, bool enable,
+                                  const cricket::VideoOptions* options));
+};
+
+class FakeVideoSource : public Notifier<VideoSourceInterface> {
+ public:
+  static talk_base::scoped_refptr<FakeVideoSource> Create() {
+    return new talk_base::RefCountedObject<FakeVideoSource>();
+  }
+  virtual cricket::VideoCapturer* GetVideoCapturer() {
+    return &fake_capturer_;
+  }
+  virtual void AddSink(cricket::VideoRenderer* output) {}
+  virtual void RemoveSink(cricket::VideoRenderer* output) {}
+  virtual SourceState state() const { return state_; }
+  virtual const cricket::VideoOptions* options() const { return &options_; }
+
+ protected:
+  FakeVideoSource() : state_(kLive) {}
+  ~FakeVideoSource() {}
+
+ private:
+  cricket::FakeVideoCapturer fake_capturer_;
+  SourceState state_;
+  cricket::VideoOptions options_;
+};
+
+class MediaStreamHandlerTest : public testing::Test {
+ public:
+  MediaStreamHandlerTest()
+      : handlers_(&audio_provider_, &video_provider_) {
+  }
+
+  virtual void SetUp() {
+    stream_ = MediaStream::Create(kStreamLabel1);
+    talk_base::scoped_refptr<VideoSourceInterface> source(
+        FakeVideoSource::Create());
+    video_track_ = VideoTrack::Create(kVideoTrackId, source);
+    EXPECT_TRUE(stream_->AddTrack(video_track_));
+    audio_track_ = AudioTrack::Create(kAudioTrackId,
+                                           NULL);
+    EXPECT_TRUE(stream_->AddTrack(audio_track_));
+  }
+
+  void AddLocalAudioTrack() {
+    EXPECT_CALL(audio_provider_, SetAudioSend(kAudioSsrc, true, _));
+    handlers_.AddLocalAudioTrack(stream_, stream_->GetAudioTracks()[0],
+                                 kAudioSsrc);
+  }
+
+  void AddLocalVideoTrack() {
+    EXPECT_CALL(video_provider_, SetCaptureDevice(
+        kVideoSsrc, video_track_->GetSource()->GetVideoCapturer()));
+    EXPECT_CALL(video_provider_, SetVideoSend(kVideoSsrc, true, _));
+    handlers_.AddLocalVideoTrack(stream_, stream_->GetVideoTracks()[0],
+                                 kVideoSsrc);
+  }
+
+  void RemoveLocalAudioTrack() {
+    EXPECT_CALL(audio_provider_, SetAudioSend(kAudioSsrc, false, _))
+        .Times(1);
+    handlers_.RemoveLocalTrack(stream_, audio_track_);
+  }
+
+  void RemoveLocalVideoTrack() {
+    EXPECT_CALL(video_provider_, SetCaptureDevice(kVideoSsrc, NULL))
+        .Times(1);
+    EXPECT_CALL(video_provider_, SetVideoSend(kVideoSsrc, false, _))
+        .Times(1);
+    handlers_.RemoveLocalTrack(stream_, video_track_);
+  }
+
+  void AddRemoteAudioTrack() {
+    EXPECT_CALL(audio_provider_, SetAudioRenderer(kAudioSsrc, _));
+    EXPECT_CALL(audio_provider_, SetAudioPlayout(kAudioSsrc, true));
+    handlers_.AddRemoteAudioTrack(stream_, stream_->GetAudioTracks()[0],
+                                  kAudioSsrc);
+  }
+
+  void AddRemoteVideoTrack() {
+    EXPECT_CALL(video_provider_, SetVideoPlayout(kVideoSsrc, true,
+                                                 video_track_->FrameInput()));
+    handlers_.AddRemoteVideoTrack(stream_, stream_->GetVideoTracks()[0],
+                                  kVideoSsrc);
+  }
+
+  void RemoveRemoteAudioTrack() {
+    EXPECT_CALL(audio_provider_, SetAudioPlayout(kAudioSsrc, false));
+    handlers_.RemoveRemoteTrack(stream_, stream_->GetAudioTracks()[0]);
+  }
+
+  void RemoveRemoteVideoTrack() {
+    EXPECT_CALL(video_provider_, SetVideoPlayout(kVideoSsrc, false, NULL));
+    handlers_.RemoveRemoteTrack(stream_, stream_->GetVideoTracks()[0]);
+  }
+
+ protected:
+  MockAudioProvider audio_provider_;
+  MockVideoProvider video_provider_;
+  MediaStreamHandlerContainer handlers_;
+  talk_base::scoped_refptr<MediaStreamInterface> stream_;
+  talk_base::scoped_refptr<VideoTrackInterface> video_track_;
+  talk_base::scoped_refptr<AudioTrackInterface> audio_track_;
+};
+
+// Test that |audio_provider_| is notified when an audio track is associated
+// and disassociated with a MediaStreamHandler.
+TEST_F(MediaStreamHandlerTest, AddAndRemoveLocalAudioTrack) {
+  AddLocalAudioTrack();
+  RemoveLocalAudioTrack();
+
+  handlers_.RemoveLocalStream(stream_);
+}
+
+// Test that |video_provider_| is notified when a video track is associated and
+// disassociated with a MediaStreamHandler.
+TEST_F(MediaStreamHandlerTest, AddAndRemoveLocalVideoTrack) {
+  AddLocalVideoTrack();
+  RemoveLocalVideoTrack();
+
+  handlers_.RemoveLocalStream(stream_);
+}
+
+// Test that |video_provider_| and |audio_provider_| is notified when an audio
+// and video track is disassociated with a MediaStreamHandler by calling
+// RemoveLocalStream.
+TEST_F(MediaStreamHandlerTest, RemoveLocalStream) {
+  AddLocalAudioTrack();
+  AddLocalVideoTrack();
+
+  EXPECT_CALL(video_provider_, SetCaptureDevice(kVideoSsrc, NULL))
+      .Times(1);
+  EXPECT_CALL(video_provider_, SetVideoSend(kVideoSsrc, false, _))
+      .Times(1);
+  EXPECT_CALL(audio_provider_, SetAudioSend(kAudioSsrc, false, _))
+      .Times(1);
+  handlers_.RemoveLocalStream(stream_);
+}
+
+
+// Test that |audio_provider_| is notified when a remote audio and track is
+// associated and disassociated with a MediaStreamHandler.
+TEST_F(MediaStreamHandlerTest, AddAndRemoveRemoteAudioTrack) {
+  AddRemoteAudioTrack();
+  RemoveRemoteAudioTrack();
+
+  handlers_.RemoveRemoteStream(stream_);
+}
+
+// Test that |video_provider_| is notified when a remote
+// video track is associated and disassociated with a MediaStreamHandler.
+TEST_F(MediaStreamHandlerTest, AddAndRemoveRemoteVideoTrack) {
+  AddRemoteVideoTrack();
+  RemoveRemoteVideoTrack();
+
+  handlers_.RemoveRemoteStream(stream_);
+}
+
+// Test that |audio_provider_| and |video_provider_| is notified when an audio
+// and video track is disassociated with a MediaStreamHandler by calling
+// RemoveRemoveStream.
+TEST_F(MediaStreamHandlerTest, RemoveRemoteStream) {
+  AddRemoteAudioTrack();
+  AddRemoteVideoTrack();
+
+  EXPECT_CALL(video_provider_, SetVideoPlayout(kVideoSsrc, false, NULL))
+      .Times(1);
+  EXPECT_CALL(audio_provider_, SetAudioPlayout(kAudioSsrc, false))
+      .Times(1);
+  handlers_.RemoveRemoteStream(stream_);
+}
+
+TEST_F(MediaStreamHandlerTest, LocalAudioTrackDisable) {
+  AddLocalAudioTrack();
+
+  EXPECT_CALL(audio_provider_, SetAudioSend(kAudioSsrc, false, _));
+  audio_track_->set_enabled(false);
+
+  EXPECT_CALL(audio_provider_, SetAudioSend(kAudioSsrc, true, _));
+  audio_track_->set_enabled(true);
+
+  RemoveLocalAudioTrack();
+  handlers_.TearDown();
+}
+
+TEST_F(MediaStreamHandlerTest, RemoteAudioTrackDisable) {
+  AddRemoteAudioTrack();
+
+  EXPECT_CALL(audio_provider_, SetAudioPlayout(kAudioSsrc, false));
+  audio_track_->set_enabled(false);
+
+  EXPECT_CALL(audio_provider_, SetAudioPlayout(kAudioSsrc, true));
+  audio_track_->set_enabled(true);
+
+  RemoveRemoteAudioTrack();
+  handlers_.TearDown();
+}
+
+TEST_F(MediaStreamHandlerTest, LocalVideoTrackDisable) {
+  AddLocalVideoTrack();
+
+  EXPECT_CALL(video_provider_, SetVideoSend(kVideoSsrc, false, _));
+  video_track_->set_enabled(false);
+
+  EXPECT_CALL(video_provider_, SetVideoSend(kVideoSsrc, true, _));
+  video_track_->set_enabled(true);
+
+  RemoveLocalVideoTrack();
+  handlers_.TearDown();
+}
+
+TEST_F(MediaStreamHandlerTest, RemoteVideoTrackDisable) {
+  AddRemoteVideoTrack();
+
+  EXPECT_CALL(video_provider_, SetVideoPlayout(kVideoSsrc, false, _));
+  video_track_->set_enabled(false);
+
+  EXPECT_CALL(video_provider_, SetVideoPlayout(kVideoSsrc, true,
+                                               video_track_->FrameInput()));
+  video_track_->set_enabled(true);
+
+  RemoveRemoteVideoTrack();
+  handlers_.TearDown();
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/mediastreaminterface.h b/talk/app/webrtc/mediastreaminterface.h
new file mode 100644
index 0000000..6f834d2
--- /dev/null
+++ b/talk/app/webrtc/mediastreaminterface.h
@@ -0,0 +1,196 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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, MediaTrack and MediaSource.
+// 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_MEDIASTREAMINTERFACE_H_
+#define TALK_APP_WEBRTC_MEDIASTREAMINTERFACE_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/refcount.h"
+#include "talk/base/scoped_ref_ptr.h"
+
+namespace cricket {
+
+class AudioRenderer;
+class VideoCapturer;
+class VideoRenderer;
+class VideoFrame;
+
+}  // namespace cricket
+
+namespace webrtc {
+
+// 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() {}
+};
+
+// Base class for sources. A MediaStreamTrack have an underlying source that
+// provide media. A source can be shared with multiple tracks.
+// TODO(perkj): Implement sources for local and remote audio tracks and
+// remote video tracks.
+class MediaSourceInterface : public talk_base::RefCountInterface,
+                             public NotifierInterface {
+ public:
+  enum SourceState {
+    kInitializing,
+    kLive,
+    kEnded,
+    kMuted
+  };
+
+  virtual SourceState state() const = 0;
+
+ protected:
+  virtual ~MediaSourceInterface() {}
+};
+
+// 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 id() 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;
+};
+
+// Interface for rendering VideoFrames from a VideoTrack
+class VideoRendererInterface {
+ public:
+  virtual void SetSize(int width, int height) = 0;
+  virtual void RenderFrame(const cricket::VideoFrame* frame) = 0;
+
+ protected:
+  // The destructor is protected to prevent deletion via the interface.
+  // This is so that we allow reference counted classes, where the destructor
+  // should never be public, to implement the interface.
+  virtual ~VideoRendererInterface() {}
+};
+
+class VideoSourceInterface;
+
+class VideoTrackInterface : public MediaStreamTrackInterface {
+ public:
+  // Register a renderer that will render all frames received on this track.
+  virtual void AddRenderer(VideoRendererInterface* renderer) = 0;
+  // Deregister a renderer.
+  virtual void RemoveRenderer(VideoRendererInterface* renderer) = 0;
+
+  // Gets a pointer to the frame input of this VideoTrack.
+  // The pointer is valid for the lifetime of this VideoTrack.
+  // VideoFrames rendered to the cricket::VideoRenderer will be rendered on all
+  // registered renderers.
+  virtual cricket::VideoRenderer* FrameInput() = 0;
+
+  virtual VideoSourceInterface* GetSource() const = 0;
+
+ protected:
+  virtual ~VideoTrackInterface() {}
+};
+
+// AudioSourceInterface is a reference counted source used for AudioTracks.
+// The same source can be used in multiple AudioTracks.
+// TODO(perkj): Extend this class with necessary methods to allow separate
+// sources for each audio track.
+class AudioSourceInterface : public MediaSourceInterface {
+};
+
+class AudioTrackInterface : public MediaStreamTrackInterface {
+ public:
+  // TODO(xians): Figure out if the following interface should be const or not.
+  virtual AudioSourceInterface* GetSource() const =  0;
+
+  // Gets a pointer to the frame input of this AudioTrack.
+  // The pointer is valid for the lifetime of this AudioTrack.
+  // TODO(xians): Make the following interface pure virtual once Chrome has its
+  // implementation.
+  virtual cricket::AudioRenderer* FrameInput() { return NULL; }
+
+ protected:
+  virtual ~AudioTrackInterface() {}
+};
+
+typedef std::vector<talk_base::scoped_refptr<AudioTrackInterface> >
+    AudioTrackVector;
+typedef std::vector<talk_base::scoped_refptr<VideoTrackInterface> >
+    VideoTrackVector;
+
+class MediaStreamInterface : public talk_base::RefCountInterface,
+                             public NotifierInterface {
+ public:
+  virtual std::string label() const = 0;
+
+  virtual AudioTrackVector GetAudioTracks() = 0;
+  virtual VideoTrackVector GetVideoTracks() = 0;
+  virtual talk_base::scoped_refptr<AudioTrackInterface>
+      FindAudioTrack(const std::string& track_id) = 0;
+  virtual talk_base::scoped_refptr<VideoTrackInterface>
+      FindVideoTrack(const std::string& track_id) = 0;
+
+  virtual bool AddTrack(AudioTrackInterface* track) = 0;
+  virtual bool AddTrack(VideoTrackInterface* track) = 0;
+  virtual bool RemoveTrack(AudioTrackInterface* track) = 0;
+  virtual bool RemoveTrack(VideoTrackInterface* track) = 0;
+
+ protected:
+  virtual ~MediaStreamInterface() {}
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_MEDIASTREAMINTERFACE_H_
diff --git a/talk/app/webrtc/mediastreamprovider.h b/talk/app/webrtc/mediastreamprovider.h
new file mode 100644
index 0000000..4c77fd0
--- /dev/null
+++ b/talk/app/webrtc/mediastreamprovider.h
@@ -0,0 +1,81 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_
+
+namespace cricket {
+
+class AudioRenderer;
+class VideoCapturer;
+class VideoRenderer;
+struct AudioOptions;
+struct VideoOptions;
+
+}  // namespace cricket
+
+namespace webrtc {
+
+// This interface is called by AudioTrackHandler classes in mediastreamhandler.h
+// to change the settings of an audio track connected to certain PeerConnection.
+class AudioProviderInterface {
+ public:
+  // Enable/disable the audio playout of a remote audio track with |ssrc|.
+  virtual void SetAudioPlayout(uint32 ssrc, bool enable) = 0;
+  // Enable/disable sending audio on the local audio track with |ssrc|.
+  // When |enable| is true |options| should be applied to the audio track.
+  virtual void SetAudioSend(uint32 ssrc, bool enable,
+                            const cricket::AudioOptions& options) = 0;
+  // Sets the renderer to be used for the specified |ssrc|.
+  virtual bool SetAudioRenderer(uint32 ssrc,
+                                cricket::AudioRenderer* renderer) = 0;
+
+ protected:
+  virtual ~AudioProviderInterface() {}
+};
+
+// This interface is called by VideoTrackHandler classes in mediastreamhandler.h
+// to change the settings of a video track connected to a certain
+// PeerConnection.
+class VideoProviderInterface {
+ public:
+  virtual bool SetCaptureDevice(uint32 ssrc,
+                                cricket::VideoCapturer* camera) = 0;
+  // Enable/disable the video playout of a remote video track with |ssrc|.
+  virtual void SetVideoPlayout(uint32 ssrc, bool enable,
+                               cricket::VideoRenderer* renderer) = 0;
+  // Enable sending video on the local video track with |ssrc|.
+  virtual void SetVideoSend(uint32 ssrc, bool enable,
+                            const cricket::VideoOptions* options) = 0;
+
+ protected:
+  virtual ~VideoProviderInterface() {}
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_MEDIASTREAMPROVIDER_H_
diff --git a/talk/app/webrtc/mediastreamproxy.h b/talk/app/webrtc/mediastreamproxy.h
new file mode 100644
index 0000000..7d018d5
--- /dev/null
+++ b/talk/app/webrtc/mediastreamproxy.h
@@ -0,0 +1,54 @@
+/*
+ * 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 "talk/app/webrtc/mediastreaminterface.h"
+#include "talk/app/webrtc/proxy.h"
+
+namespace webrtc {
+
+BEGIN_PROXY_MAP(MediaStream)
+  PROXY_CONSTMETHOD0(std::string, label)
+  PROXY_METHOD0(AudioTrackVector, GetAudioTracks)
+  PROXY_METHOD0(VideoTrackVector, GetVideoTracks)
+  PROXY_METHOD1(talk_base::scoped_refptr<AudioTrackInterface>,
+                FindAudioTrack, const std::string&)
+  PROXY_METHOD1(talk_base::scoped_refptr<VideoTrackInterface>,
+                FindVideoTrack, const std::string&)
+  PROXY_METHOD1(bool, AddTrack, AudioTrackInterface*)
+  PROXY_METHOD1(bool, AddTrack, VideoTrackInterface*)
+  PROXY_METHOD1(bool, RemoveTrack, AudioTrackInterface*)
+  PROXY_METHOD1(bool, RemoveTrack, VideoTrackInterface*)
+  PROXY_METHOD1(void, RegisterObserver, ObserverInterface*)
+  PROXY_METHOD1(void, UnregisterObserver, ObserverInterface*)
+END_PROXY()
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_MEDIASTREAMPROXY_H_
diff --git a/talk/app/webrtc/mediastreamsignaling.cc b/talk/app/webrtc/mediastreamsignaling.cc
new file mode 100644
index 0000000..1397a7f
--- /dev/null
+++ b/talk/app/webrtc/mediastreamsignaling.cc
@@ -0,0 +1,883 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/mediastreamsignaling.h"
+
+#include <vector>
+
+#include "talk/app/webrtc/audiotrack.h"
+#include "talk/app/webrtc/mediastreamproxy.h"
+#include "talk/app/webrtc/mediaconstraintsinterface.h"
+#include "talk/app/webrtc/mediastreamtrackproxy.h"
+#include "talk/app/webrtc/videotrack.h"
+
+static const char kDefaultStreamLabel[] = "default";
+static const char kDefaultAudioTrackLabel[] = "defaulta0";
+static const char kDefaultVideoTrackLabel[] = "defaultv0";
+
+namespace webrtc {
+
+using talk_base::scoped_ptr;
+using talk_base::scoped_refptr;
+
+// Supported MediaConstraints.
+const char MediaConstraintsInterface::kOfferToReceiveAudio[] =
+    "OfferToReceiveAudio";
+const char MediaConstraintsInterface::kOfferToReceiveVideo[] =
+    "OfferToReceiveVideo";
+const char MediaConstraintsInterface::kIceRestart[] =
+    "IceRestart";
+const char MediaConstraintsInterface::kUseRtpMux[] =
+    "googUseRtpMUX";
+const char MediaConstraintsInterface::kVoiceActivityDetection[] =
+    "VoiceActivityDetection";
+
+static bool ParseConstraints(
+    const MediaConstraintsInterface* constraints,
+    cricket::MediaSessionOptions* options, bool is_answer) {
+  bool value;
+  size_t mandatory_constraints_satisfied = 0;
+
+  if (FindConstraint(constraints,
+                     MediaConstraintsInterface::kOfferToReceiveAudio,
+                     &value, &mandatory_constraints_satisfied)) {
+    // |options-|has_audio| can only change from false to
+    // true, but never change from true to false. This is to make sure
+    // CreateOffer / CreateAnswer doesn't remove a media content
+    // description that has been created.
+    options->has_audio |= value;
+  } else {
+    // kOfferToReceiveAudio defaults to true according to spec.
+    options->has_audio = true;
+  }
+
+  if (FindConstraint(constraints,
+                     MediaConstraintsInterface::kOfferToReceiveVideo,
+                     &value, &mandatory_constraints_satisfied)) {
+    // |options->has_video| can only change from false to
+    // true, but never change from true to false. This is to make sure
+    // CreateOffer / CreateAnswer doesn't remove a media content
+    // description that has been created.
+    options->has_video |= value;
+  } else {
+    // kOfferToReceiveVideo defaults to false according to spec. But
+    // if it is an answer and video is offered, we should still accept video
+    // per default.
+    options->has_video |= is_answer;
+  }
+
+  if (FindConstraint(constraints,
+                     MediaConstraintsInterface::kVoiceActivityDetection,
+                     &value, &mandatory_constraints_satisfied)) {
+    options->vad_enabled = value;
+  }
+
+  if (FindConstraint(constraints,
+                     MediaConstraintsInterface::kUseRtpMux,
+                     &value, &mandatory_constraints_satisfied)) {
+    options->bundle_enabled = value;
+  } else {
+    // kUseRtpMux defaults to true according to spec.
+    options->bundle_enabled = true;
+  }
+  if (FindConstraint(constraints,
+                     MediaConstraintsInterface::kIceRestart,
+                     &value, &mandatory_constraints_satisfied)) {
+    options->transport_options.ice_restart = value;
+  } else {
+    // kIceRestart defaults to false according to spec.
+    options->transport_options.ice_restart = false;
+  }
+
+  if (!constraints) {
+    return true;
+  }
+  return mandatory_constraints_satisfied == constraints->GetMandatory().size();
+}
+
+// Returns true if if at least one media content is present and
+// |options.bundle_enabled| is true.
+// Bundle will be enabled  by default if at least one media content is present
+// and the constraint kUseRtpMux has not disabled bundle.
+static bool EvaluateNeedForBundle(const cricket::MediaSessionOptions& options) {
+  return options.bundle_enabled &&
+      (options.has_audio || options.has_video || options.has_data());
+}
+
+// Factory class for creating remote MediaStreams and MediaStreamTracks.
+class RemoteMediaStreamFactory {
+ public:
+  explicit RemoteMediaStreamFactory(talk_base::Thread* signaling_thread)
+      : signaling_thread_(signaling_thread) {
+  }
+
+  talk_base::scoped_refptr<MediaStreamInterface> CreateMediaStream(
+      const std::string& stream_label) {
+    return MediaStreamProxy::Create(
+        signaling_thread_, MediaStream::Create(stream_label));
+  }
+
+  AudioTrackInterface* AddAudioTrack(webrtc::MediaStreamInterface* stream,
+                                     const std::string& track_id) {
+    return AddTrack<AudioTrackInterface, AudioTrack, AudioTrackProxy>(stream,
+                                                                      track_id);
+  }
+
+  VideoTrackInterface* AddVideoTrack(webrtc::MediaStreamInterface* stream,
+                                     const std::string& track_id) {
+    return AddTrack<VideoTrackInterface, VideoTrack, VideoTrackProxy>(stream,
+                                                                      track_id);
+  }
+
+ private:
+  template <typename TI, typename T, typename TP>
+  TI* AddTrack(MediaStreamInterface* stream, const std::string& track_id) {
+    talk_base::scoped_refptr<TI> track(
+        TP::Create(signaling_thread_, T::Create(track_id, NULL)));
+    track->set_state(webrtc::MediaStreamTrackInterface::kLive);
+    if (stream->AddTrack(track)) {
+      return track;
+    }
+    return NULL;
+  }
+
+  talk_base::Thread* signaling_thread_;
+};
+
+MediaStreamSignaling::MediaStreamSignaling(
+    talk_base::Thread* signaling_thread,
+    MediaStreamSignalingObserver* stream_observer)
+    : signaling_thread_(signaling_thread),
+      data_channel_factory_(NULL),
+      stream_observer_(stream_observer),
+      local_streams_(StreamCollection::Create()),
+      remote_streams_(StreamCollection::Create()),
+      remote_stream_factory_(new RemoteMediaStreamFactory(signaling_thread)),
+      last_allocated_sctp_id_(0) {
+  options_.has_video = false;
+  options_.has_audio = false;
+}
+
+MediaStreamSignaling::~MediaStreamSignaling() {
+}
+
+void MediaStreamSignaling::TearDown() {
+  OnAudioChannelClose();
+  OnVideoChannelClose();
+  OnDataChannelClose();
+}
+
+bool MediaStreamSignaling::IsSctpIdAvailable(int id) const {
+  if (id < 0 || id > static_cast<int>(cricket::kMaxSctpSid))
+    return false;
+  for (DataChannels::const_iterator iter = data_channels_.begin();
+       iter != data_channels_.end();
+       ++iter) {
+    if (iter->second->id() == id) {
+      return false;
+    }
+  }
+  return true;
+}
+
+// Gets the first id that has not been taken by existing data
+// channels. Starting from 1.
+// Returns false if no id can be allocated.
+// TODO(jiayl): Update to some kind of even/odd random number selection when the
+// rules are fully standardized.
+bool MediaStreamSignaling::AllocateSctpId(int* id) {
+  do {
+    last_allocated_sctp_id_++;
+  } while (last_allocated_sctp_id_ <= static_cast<int>(cricket::kMaxSctpSid) &&
+           !IsSctpIdAvailable(last_allocated_sctp_id_));
+
+  if (last_allocated_sctp_id_ > static_cast<int>(cricket::kMaxSctpSid)) {
+    last_allocated_sctp_id_ = cricket::kMaxSctpSid;
+    return false;
+  }
+
+  *id = last_allocated_sctp_id_;
+  return true;
+}
+
+bool MediaStreamSignaling::AddDataChannel(DataChannel* data_channel) {
+  ASSERT(data_channel != NULL);
+  if (data_channels_.find(data_channel->label()) != data_channels_.end()) {
+    LOG(LS_ERROR) << "DataChannel with label " << data_channel->label()
+                  << " already exists.";
+    return false;
+  }
+  data_channels_[data_channel->label()] = data_channel;
+  return true;
+}
+
+bool MediaStreamSignaling::AddLocalStream(MediaStreamInterface* local_stream) {
+  if (local_streams_->find(local_stream->label()) != NULL) {
+    LOG(LS_WARNING) << "MediaStream with label " << local_stream->label()
+                    << "already exist.";
+    return false;
+  }
+  local_streams_->AddStream(local_stream);
+
+  // Find tracks that has already been configured in SDP. This can occur if a
+  // local session description that contains the MSID of these tracks is set
+  // before AddLocalStream is called. It can also occur if the local session
+  // description is not changed and RemoveLocalStream
+  // is called and later AddLocalStream is called again with the same stream.
+  AudioTrackVector audio_tracks = local_stream->GetAudioTracks();
+  for (AudioTrackVector::const_iterator it = audio_tracks.begin();
+       it != audio_tracks.end(); ++it) {
+    TrackInfos::const_iterator track_info_it =
+        local_audio_tracks_.find((*it)->id());
+    if (track_info_it != local_audio_tracks_.end()) {
+      const TrackInfo& info = track_info_it->second;
+      OnLocalTrackSeen(info.stream_label, info.track_id, info.ssrc,
+                       cricket::MEDIA_TYPE_AUDIO);
+    }
+  }
+
+  VideoTrackVector video_tracks = local_stream->GetVideoTracks();
+  for (VideoTrackVector::const_iterator it = video_tracks.begin();
+       it != video_tracks.end(); ++it) {
+    TrackInfos::const_iterator track_info_it =
+        local_video_tracks_.find((*it)->id());
+    if (track_info_it != local_video_tracks_.end()) {
+      const TrackInfo& info = track_info_it->second;
+      OnLocalTrackSeen(info.stream_label, info.track_id, info.ssrc,
+                       cricket::MEDIA_TYPE_VIDEO);
+    }
+  }
+  return true;
+}
+
+void MediaStreamSignaling::RemoveLocalStream(
+    MediaStreamInterface* local_stream) {
+  local_streams_->RemoveStream(local_stream);
+  stream_observer_->OnRemoveLocalStream(local_stream);
+}
+
+bool MediaStreamSignaling::GetOptionsForOffer(
+    const MediaConstraintsInterface* constraints,
+    cricket::MediaSessionOptions* options) {
+  UpdateSessionOptions();
+  if (!ParseConstraints(constraints, &options_, false)) {
+    return false;
+  }
+  options_.bundle_enabled = EvaluateNeedForBundle(options_);
+  *options = options_;
+  return true;
+}
+
+bool MediaStreamSignaling::GetOptionsForAnswer(
+    const MediaConstraintsInterface* constraints,
+    cricket::MediaSessionOptions* options) {
+  UpdateSessionOptions();
+
+  // Copy the |options_| to not let the flag MediaSessionOptions::has_audio and
+  // MediaSessionOptions::has_video affect subsequent offers.
+  cricket::MediaSessionOptions current_options = options_;
+  if (!ParseConstraints(constraints, &current_options, true)) {
+    return false;
+  }
+  current_options.bundle_enabled = EvaluateNeedForBundle(current_options);
+  *options = current_options;
+  return true;
+}
+
+// Updates or creates remote MediaStream objects given a
+// remote SessionDesription.
+// If the remote SessionDesription contains new remote MediaStreams
+// the observer OnAddStream method is called. If a remote MediaStream is missing
+// from the remote SessionDescription OnRemoveStream is called.
+void MediaStreamSignaling::OnRemoteDescriptionChanged(
+    const SessionDescriptionInterface* desc) {
+  const cricket::SessionDescription* remote_desc = desc->description();
+  talk_base::scoped_refptr<StreamCollection> new_streams(
+      StreamCollection::Create());
+
+  // Find all audio rtp streams and create corresponding remote AudioTracks
+  // and MediaStreams.
+  const cricket::ContentInfo* audio_content = GetFirstAudioContent(remote_desc);
+  if (audio_content) {
+    const cricket::AudioContentDescription* desc =
+        static_cast<const cricket::AudioContentDescription*>(
+            audio_content->description);
+    UpdateRemoteStreamsList(desc->streams(), desc->type(), new_streams);
+    remote_info_.default_audio_track_needed =
+        desc->direction() == cricket::MD_SENDRECV && desc->streams().empty();
+  }
+
+  // Find all video rtp streams and create corresponding remote VideoTracks
+  // and MediaStreams.
+  const cricket::ContentInfo* video_content = GetFirstVideoContent(remote_desc);
+  if (video_content) {
+    const cricket::VideoContentDescription* desc =
+        static_cast<const cricket::VideoContentDescription*>(
+            video_content->description);
+    UpdateRemoteStreamsList(desc->streams(), desc->type(), new_streams);
+    remote_info_.default_video_track_needed =
+        desc->direction() == cricket::MD_SENDRECV && desc->streams().empty();
+  }
+
+  // Update the DataChannels with the information from the remote peer.
+  const cricket::ContentInfo* data_content = GetFirstDataContent(remote_desc);
+  if (data_content) {
+    const cricket::DataContentDescription* data_desc =
+        static_cast<const cricket::DataContentDescription*>(
+            data_content->description);
+    if (data_desc->protocol() == cricket::kMediaProtocolDtlsSctp) {
+      UpdateRemoteSctpDataChannels();
+    } else {
+      UpdateRemoteRtpDataChannels(data_desc->streams());
+    }
+  }
+
+  // Iterate new_streams and notify the observer about new MediaStreams.
+  for (size_t i = 0; i < new_streams->count(); ++i) {
+    MediaStreamInterface* new_stream = new_streams->at(i);
+    stream_observer_->OnAddRemoteStream(new_stream);
+  }
+
+  // Find removed MediaStreams.
+  if (remote_info_.IsDefaultMediaStreamNeeded() &&
+      remote_streams_->find(kDefaultStreamLabel) != NULL) {
+    // The default media stream already exists. No need to do anything.
+  } else {
+    UpdateEndedRemoteMediaStreams();
+    remote_info_.msid_supported |= remote_streams_->count() > 0;
+  }
+  MaybeCreateDefaultStream();
+}
+
+void MediaStreamSignaling::OnLocalDescriptionChanged(
+    const SessionDescriptionInterface* desc) {
+  const cricket::ContentInfo* audio_content =
+      GetFirstAudioContent(desc->description());
+  if (audio_content) {
+    if (audio_content->rejected) {
+      RejectRemoteTracks(cricket::MEDIA_TYPE_AUDIO);
+    }
+    const cricket::AudioContentDescription* audio_desc =
+        static_cast<const cricket::AudioContentDescription*>(
+            audio_content->description);
+    UpdateLocalTracks(audio_desc->streams(), audio_desc->type());
+  }
+
+  const cricket::ContentInfo* video_content =
+      GetFirstVideoContent(desc->description());
+  if (video_content) {
+    if (video_content->rejected) {
+      RejectRemoteTracks(cricket::MEDIA_TYPE_VIDEO);
+    }
+    const cricket::VideoContentDescription* video_desc =
+        static_cast<const cricket::VideoContentDescription*>(
+            video_content->description);
+    UpdateLocalTracks(video_desc->streams(), video_desc->type());
+  }
+
+  const cricket::ContentInfo* data_content =
+      GetFirstDataContent(desc->description());
+  if (data_content) {
+    const cricket::DataContentDescription* data_desc =
+        static_cast<const cricket::DataContentDescription*>(
+            data_content->description);
+    if (data_desc->protocol() == cricket::kMediaProtocolDtlsSctp) {
+      UpdateLocalSctpDataChannels();
+    } else {
+      UpdateLocalRtpDataChannels(data_desc->streams());
+    }
+  }
+}
+
+void MediaStreamSignaling::OnAudioChannelClose() {
+  RejectRemoteTracks(cricket::MEDIA_TYPE_AUDIO);
+}
+
+void MediaStreamSignaling::OnVideoChannelClose() {
+  RejectRemoteTracks(cricket::MEDIA_TYPE_VIDEO);
+}
+
+void MediaStreamSignaling::OnDataChannelClose() {
+  DataChannels::iterator it = data_channels_.begin();
+  for (; it != data_channels_.end(); ++it) {
+    DataChannel* data_channel = it->second;
+    data_channel->OnDataEngineClose();
+  }
+}
+
+bool MediaStreamSignaling::GetRemoteAudioTrackSsrc(
+    const std::string& track_id, uint32* ssrc) const {
+  TrackInfos::const_iterator it = remote_audio_tracks_.find(track_id);
+  if (it == remote_audio_tracks_.end()) {
+    return false;
+  }
+
+  *ssrc = it->second.ssrc;
+  return true;
+}
+
+bool MediaStreamSignaling::GetRemoteVideoTrackSsrc(
+    const std::string& track_id, uint32* ssrc) const {
+  TrackInfos::const_iterator it = remote_video_tracks_.find(track_id);
+  if (it == remote_video_tracks_.end()) {
+    return false;
+  }
+
+  *ssrc = it->second.ssrc;
+  return true;
+}
+
+void MediaStreamSignaling::UpdateSessionOptions() {
+  options_.streams.clear();
+  if (local_streams_ != NULL) {
+    for (size_t i = 0; i < local_streams_->count(); ++i) {
+      MediaStreamInterface* stream = local_streams_->at(i);
+
+      AudioTrackVector audio_tracks(stream->GetAudioTracks());
+      if (!audio_tracks.empty()) {
+        options_.has_audio = true;
+      }
+
+      // For each audio track in the stream, add it to the MediaSessionOptions.
+      for (size_t j = 0; j < audio_tracks.size(); ++j) {
+        scoped_refptr<MediaStreamTrackInterface> track(audio_tracks[j]);
+        options_.AddStream(cricket::MEDIA_TYPE_AUDIO, track->id(),
+                           stream->label());
+      }
+
+      VideoTrackVector video_tracks(stream->GetVideoTracks());
+      if (!video_tracks.empty()) {
+        options_.has_video = true;
+      }
+      // For each video track in the stream, add it to the MediaSessionOptions.
+      for (size_t j = 0; j < video_tracks.size(); ++j) {
+        scoped_refptr<MediaStreamTrackInterface> track(video_tracks[j]);
+        options_.AddStream(cricket::MEDIA_TYPE_VIDEO, track->id(),
+                           stream->label());
+      }
+    }
+  }
+
+  // Check for data channels.
+  DataChannels::const_iterator data_channel_it = data_channels_.begin();
+  for (; data_channel_it != data_channels_.end(); ++data_channel_it) {
+    const DataChannel* channel = data_channel_it->second;
+    if (channel->state() == DataChannel::kConnecting ||
+        channel->state() == DataChannel::kOpen) {
+      // |streamid| and |sync_label| are both set to the DataChannel label
+      // here so they can be signaled the same way as MediaStreams and Tracks.
+      // For MediaStreams, the sync_label is the MediaStream label and the
+      // track label is the same as |streamid|.
+      const std::string& streamid = channel->label();
+      const std::string& sync_label = channel->label();
+      options_.AddStream(cricket::MEDIA_TYPE_DATA, streamid, sync_label);
+    }
+  }
+}
+
+void MediaStreamSignaling::UpdateRemoteStreamsList(
+    const cricket::StreamParamsVec& streams,
+    cricket::MediaType media_type,
+    StreamCollection* new_streams) {
+  TrackInfos* current_tracks = GetRemoteTracks(media_type);
+
+  // Find removed tracks. Ie tracks where the track id or ssrc don't match the
+  // new StreamParam.
+  TrackInfos::iterator track_it = current_tracks->begin();
+  while (track_it != current_tracks->end()) {
+    TrackInfo info = track_it->second;
+    cricket::StreamParams params;
+    if (!cricket::GetStreamBySsrc(streams, info.ssrc, &params) ||
+        params.id != info.track_id) {
+      OnRemoteTrackRemoved(info.stream_label, info.track_id, media_type);
+      current_tracks->erase(track_it++);
+    } else {
+      ++track_it;
+    }
+  }
+
+  // Find new and active tracks.
+  for (cricket::StreamParamsVec::const_iterator it = streams.begin();
+       it != streams.end(); ++it) {
+    // The sync_label is the MediaStream label and the |stream.id| is the
+    // track id.
+    const std::string& stream_label = it->sync_label;
+    const std::string& track_id = it->id;
+    uint32 ssrc = it->first_ssrc();
+
+    talk_base::scoped_refptr<MediaStreamInterface> stream =
+        remote_streams_->find(stream_label);
+    if (!stream) {
+      // This is a new MediaStream. Create a new remote MediaStream.
+      stream = remote_stream_factory_->CreateMediaStream(stream_label);
+      remote_streams_->AddStream(stream);
+      new_streams->AddStream(stream);
+    }
+
+    TrackInfos::iterator track_it = current_tracks->find(track_id);
+    if (track_it == current_tracks->end()) {
+      (*current_tracks)[track_id] =
+          TrackInfo(stream_label, track_id, ssrc);
+      OnRemoteTrackSeen(stream_label, track_id, it->first_ssrc(), media_type);
+    }
+  }
+}
+
+void MediaStreamSignaling::OnRemoteTrackSeen(const std::string& stream_label,
+                                             const std::string& track_id,
+                                             uint32 ssrc,
+                                             cricket::MediaType media_type) {
+  MediaStreamInterface* stream = remote_streams_->find(stream_label);
+
+  if (media_type == cricket::MEDIA_TYPE_AUDIO) {
+    AudioTrackInterface* audio_track =
+        remote_stream_factory_->AddAudioTrack(stream, track_id);
+    stream_observer_->OnAddRemoteAudioTrack(stream, audio_track, ssrc);
+  } else if (media_type == cricket::MEDIA_TYPE_VIDEO) {
+    VideoTrackInterface* video_track =
+        remote_stream_factory_->AddVideoTrack(stream, track_id);
+    stream_observer_->OnAddRemoteVideoTrack(stream, video_track, ssrc);
+  } else {
+    ASSERT(false && "Invalid media type");
+  }
+}
+
+void MediaStreamSignaling::OnRemoteTrackRemoved(
+    const std::string& stream_label,
+    const std::string& track_id,
+    cricket::MediaType media_type) {
+  MediaStreamInterface* stream = remote_streams_->find(stream_label);
+
+  if (media_type == cricket::MEDIA_TYPE_AUDIO) {
+    talk_base::scoped_refptr<AudioTrackInterface> audio_track =
+        stream->FindAudioTrack(track_id);
+    audio_track->set_state(webrtc::MediaStreamTrackInterface::kEnded);
+    stream->RemoveTrack(audio_track);
+    stream_observer_->OnRemoveRemoteAudioTrack(stream, audio_track);
+  } else if (media_type == cricket::MEDIA_TYPE_VIDEO) {
+    talk_base::scoped_refptr<VideoTrackInterface> video_track =
+        stream->FindVideoTrack(track_id);
+    video_track->set_state(webrtc::MediaStreamTrackInterface::kEnded);
+    stream->RemoveTrack(video_track);
+    stream_observer_->OnRemoveRemoteVideoTrack(stream, video_track);
+  } else {
+    ASSERT(false && "Invalid media type");
+  }
+}
+
+void MediaStreamSignaling::RejectRemoteTracks(cricket::MediaType media_type) {
+  TrackInfos* current_tracks = GetRemoteTracks(media_type);
+  for (TrackInfos::iterator track_it = current_tracks->begin();
+       track_it != current_tracks->end(); ++track_it) {
+    TrackInfo info = track_it->second;
+    MediaStreamInterface* stream = remote_streams_->find(info.stream_label);
+    if (media_type == cricket::MEDIA_TYPE_AUDIO) {
+      AudioTrackInterface* track = stream->FindAudioTrack(info.track_id);
+      track->set_state(webrtc::MediaStreamTrackInterface::kEnded);
+    }
+    if (media_type == cricket::MEDIA_TYPE_VIDEO) {
+      VideoTrackInterface* track = stream->FindVideoTrack(info.track_id);
+      track->set_state(webrtc::MediaStreamTrackInterface::kEnded);
+    }
+  }
+}
+
+void MediaStreamSignaling::UpdateEndedRemoteMediaStreams() {
+  std::vector<scoped_refptr<MediaStreamInterface> > streams_to_remove;
+  for (size_t i = 0; i < remote_streams_->count(); ++i) {
+    MediaStreamInterface*stream = remote_streams_->at(i);
+    if (stream->GetAudioTracks().empty() && stream->GetVideoTracks().empty()) {
+      streams_to_remove.push_back(stream);
+    }
+  }
+
+  std::vector<scoped_refptr<MediaStreamInterface> >::const_iterator it;
+  for (it = streams_to_remove.begin(); it != streams_to_remove.end(); ++it) {
+    remote_streams_->RemoveStream(*it);
+    stream_observer_->OnRemoveRemoteStream(*it);
+  }
+}
+
+void MediaStreamSignaling::MaybeCreateDefaultStream() {
+  if (!remote_info_.IsDefaultMediaStreamNeeded())
+    return;
+
+  bool default_created = false;
+
+  scoped_refptr<MediaStreamInterface> default_remote_stream =
+      remote_streams_->find(kDefaultStreamLabel);
+  if (default_remote_stream == NULL) {
+    default_created = true;
+    default_remote_stream =
+        remote_stream_factory_->CreateMediaStream(kDefaultStreamLabel);
+    remote_streams_->AddStream(default_remote_stream);
+  }
+  if (remote_info_.default_audio_track_needed &&
+      default_remote_stream->GetAudioTracks().size() == 0) {
+    remote_audio_tracks_[kDefaultAudioTrackLabel] =
+        TrackInfo(kDefaultStreamLabel, kDefaultAudioTrackLabel, 0);
+    OnRemoteTrackSeen(kDefaultStreamLabel, kDefaultAudioTrackLabel, 0,
+                       cricket::MEDIA_TYPE_AUDIO);
+  }
+  if (remote_info_.default_video_track_needed &&
+      default_remote_stream->GetVideoTracks().size() == 0) {
+    remote_video_tracks_[kDefaultVideoTrackLabel] =
+        TrackInfo(kDefaultStreamLabel, kDefaultVideoTrackLabel, 0);
+    OnRemoteTrackSeen(kDefaultStreamLabel, kDefaultVideoTrackLabel, 0,
+                       cricket::MEDIA_TYPE_VIDEO);
+  }
+  if (default_created) {
+    stream_observer_->OnAddRemoteStream(default_remote_stream);
+  }
+}
+
+MediaStreamSignaling::TrackInfos* MediaStreamSignaling::GetRemoteTracks(
+    cricket::MediaType type) {
+  if (type == cricket::MEDIA_TYPE_AUDIO)
+    return &remote_audio_tracks_;
+  else if (type == cricket::MEDIA_TYPE_VIDEO)
+    return &remote_video_tracks_;
+  ASSERT(false && "Unknown MediaType");
+  return NULL;
+}
+
+MediaStreamSignaling::TrackInfos* MediaStreamSignaling::GetLocalTracks(
+    cricket::MediaType media_type) {
+  ASSERT(media_type == cricket::MEDIA_TYPE_AUDIO ||
+         media_type == cricket::MEDIA_TYPE_VIDEO);
+
+  return (media_type == cricket::MEDIA_TYPE_AUDIO) ?
+      &local_audio_tracks_ : &local_video_tracks_;
+}
+
+void MediaStreamSignaling::UpdateLocalTracks(
+    const std::vector<cricket::StreamParams>& streams,
+    cricket::MediaType media_type) {
+  TrackInfos* current_tracks = GetLocalTracks(media_type);
+
+  // Find removed tracks. Ie tracks where the track id or ssrc don't match the
+  // new StreamParam.
+  TrackInfos::iterator track_it = current_tracks->begin();
+  while (track_it != current_tracks->end()) {
+    TrackInfo info = track_it->second;
+    cricket::StreamParams params;
+    if (!cricket::GetStreamBySsrc(streams, info.ssrc, &params) ||
+        params.id != info.track_id) {
+      OnLocalTrackRemoved(info.stream_label, info.track_id, media_type);
+      current_tracks->erase(track_it++);
+    } else {
+      ++track_it;
+    }
+  }
+
+  // Find new and active tracks.
+  for (cricket::StreamParamsVec::const_iterator it = streams.begin();
+       it != streams.end(); ++it) {
+    // The sync_label is the MediaStream label and the |stream.id| is the
+    // track id.
+    const std::string& stream_label = it->sync_label;
+    const std::string& track_id = it->id;
+    uint32 ssrc = it->first_ssrc();
+    TrackInfos::iterator track_it =  current_tracks->find(track_id);
+    if (track_it == current_tracks->end()) {
+      (*current_tracks)[track_id] =
+          TrackInfo(stream_label, track_id, ssrc);
+      OnLocalTrackSeen(stream_label, track_id, it->first_ssrc(),
+                       media_type);
+    }
+  }
+}
+
+void MediaStreamSignaling::OnLocalTrackSeen(
+    const std::string& stream_label,
+    const std::string& track_id,
+    uint32 ssrc,
+    cricket::MediaType media_type) {
+  MediaStreamInterface* stream = local_streams_->find(stream_label);
+  if (!stream) {
+    LOG(LS_WARNING) << "An unknown local MediaStream with label "
+                    << stream_label <<  " has been configured.";
+    return;
+  }
+
+  if (media_type == cricket::MEDIA_TYPE_AUDIO) {
+    AudioTrackInterface* audio_track = stream->FindAudioTrack(track_id);
+    if (!audio_track) {
+      LOG(LS_WARNING) << "An unknown local AudioTrack with id , "
+                      << track_id <<  " has been configured.";
+      return;
+    }
+    stream_observer_->OnAddLocalAudioTrack(stream, audio_track, ssrc);
+  } else if (media_type == cricket::MEDIA_TYPE_VIDEO) {
+    VideoTrackInterface* video_track = stream->FindVideoTrack(track_id);
+    if (!video_track) {
+      LOG(LS_WARNING) << "An unknown local VideoTrack with id , "
+                      << track_id <<  " has been configured.";
+      return;
+    }
+    stream_observer_->OnAddLocalVideoTrack(stream, video_track, ssrc);
+  } else {
+    ASSERT(false && "Invalid media type");
+  }
+}
+
+void MediaStreamSignaling::OnLocalTrackRemoved(
+    const std::string& stream_label,
+    const std::string& track_id,
+    cricket::MediaType media_type) {
+  MediaStreamInterface* stream = local_streams_->find(stream_label);
+  if (!stream) {
+    // This is the normal case. Ie RemoveLocalStream has been called and the
+    // SessionDescriptions has been renegotiated.
+    return;
+  }
+  // A track has been removed from the SessionDescription but the MediaStream
+  // is still associated with MediaStreamSignaling. This only occurs if the SDP
+  // doesn't match with the calls to AddLocalStream and RemoveLocalStream.
+
+  if (media_type == cricket::MEDIA_TYPE_AUDIO) {
+    AudioTrackInterface* audio_track = stream->FindAudioTrack(track_id);
+    if (!audio_track) {
+      return;
+    }
+    stream_observer_->OnRemoveLocalAudioTrack(stream, audio_track);
+  } else if (media_type == cricket::MEDIA_TYPE_VIDEO) {
+    VideoTrackInterface* video_track = stream->FindVideoTrack(track_id);
+    if (!video_track) {
+      return;
+    }
+    stream_observer_->OnRemoveLocalVideoTrack(stream, video_track);
+  } else {
+    ASSERT(false && "Invalid media type.");
+  }
+}
+
+void MediaStreamSignaling::UpdateLocalRtpDataChannels(
+    const cricket::StreamParamsVec& streams) {
+  std::vector<std::string> existing_channels;
+
+  // Find new and active data channels.
+  for (cricket::StreamParamsVec::const_iterator it =streams.begin();
+       it != streams.end(); ++it) {
+    // |it->sync_label| is actually the data channel label. The reason is that
+    // we use the same naming of data channels as we do for
+    // MediaStreams and Tracks.
+    // For MediaStreams, the sync_label is the MediaStream label and the
+    // track label is the same as |streamid|.
+    const std::string& channel_label = it->sync_label;
+    DataChannels::iterator data_channel_it = data_channels_.find(channel_label);
+    if (!VERIFY(data_channel_it != data_channels_.end())) {
+      continue;
+    }
+    // Set the SSRC the data channel should use for sending.
+    data_channel_it->second->SetSendSsrc(it->first_ssrc());
+    existing_channels.push_back(data_channel_it->first);
+  }
+
+  UpdateClosingDataChannels(existing_channels, true);
+}
+
+void MediaStreamSignaling::UpdateRemoteRtpDataChannels(
+    const cricket::StreamParamsVec& streams) {
+  std::vector<std::string> existing_channels;
+
+  // Find new and active data channels.
+  for (cricket::StreamParamsVec::const_iterator it = streams.begin();
+       it != streams.end(); ++it) {
+    // The data channel label is either the mslabel or the SSRC if the mslabel
+    // does not exist. Ex a=ssrc:444330170 mslabel:test1.
+    std::string label = it->sync_label.empty() ?
+        talk_base::ToString(it->first_ssrc()) : it->sync_label;
+    DataChannels::iterator data_channel_it =
+        data_channels_.find(label);
+    if (data_channel_it == data_channels_.end()) {
+      // This is a new data channel.
+      CreateRemoteDataChannel(label, it->first_ssrc());
+    } else {
+      data_channel_it->second->SetReceiveSsrc(it->first_ssrc());
+    }
+    existing_channels.push_back(label);
+  }
+
+  UpdateClosingDataChannels(existing_channels, false);
+}
+
+void MediaStreamSignaling::UpdateClosingDataChannels(
+    const std::vector<std::string>& active_channels, bool is_local_update) {
+  DataChannels::iterator it = data_channels_.begin();
+  while (it != data_channels_.end()) {
+    DataChannel* data_channel = it->second;
+    if (std::find(active_channels.begin(), active_channels.end(),
+                  data_channel->label()) != active_channels.end()) {
+      ++it;
+      continue;
+    }
+
+    if (is_local_update)
+      data_channel->SetSendSsrc(0);
+    else
+      data_channel->RemotePeerRequestClose();
+
+    if (data_channel->state() == DataChannel::kClosed) {
+      data_channels_.erase(it);
+      it = data_channels_.begin();
+    } else {
+      ++it;
+    }
+  }
+}
+
+void MediaStreamSignaling::CreateRemoteDataChannel(const std::string& label,
+                                                   uint32 remote_ssrc) {
+  if (!data_channel_factory_) {
+    LOG(LS_WARNING) << "Remote peer requested a DataChannel but DataChannels "
+                    << "are not supported.";
+    return;
+  }
+  scoped_refptr<DataChannel> channel(
+      data_channel_factory_->CreateDataChannel(label, NULL));
+  channel->SetReceiveSsrc(remote_ssrc);
+  stream_observer_->OnAddDataChannel(channel);
+}
+
+void MediaStreamSignaling::UpdateLocalSctpDataChannels() {
+  DataChannels::iterator it = data_channels_.begin();
+  for (; it != data_channels_.end(); ++it) {
+    DataChannel* data_channel = it->second;
+    data_channel->SetSendSsrc(data_channel->id());
+  }
+}
+
+void MediaStreamSignaling::UpdateRemoteSctpDataChannels() {
+  DataChannels::iterator it = data_channels_.begin();
+  for (; it != data_channels_.end(); ++it) {
+    DataChannel* data_channel = it->second;
+    data_channel->SetReceiveSsrc(data_channel->id());
+  }
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/mediastreamsignaling.h b/talk/app/webrtc/mediastreamsignaling.h
new file mode 100644
index 0000000..9ead8b0
--- /dev/null
+++ b/talk/app/webrtc/mediastreamsignaling.h
@@ -0,0 +1,385 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_MEDIASTREAMSIGNALING_H_
+#define TALK_APP_WEBRTC_MEDIASTREAMSIGNALING_H_
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include "talk/app/webrtc/datachannel.h"
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/app/webrtc/peerconnectioninterface.h"
+#include "talk/app/webrtc/streamcollection.h"
+#include "talk/base/scoped_ref_ptr.h"
+#include "talk/session/media/mediasession.h"
+
+namespace talk_base {
+class Thread;
+}  // namespace talk_base
+
+namespace webrtc {
+
+class RemoteMediaStreamFactory;
+
+// A MediaStreamSignalingObserver is notified when events happen to
+// MediaStreams, MediaStreamTracks or DataChannels associated with the observed
+// MediaStreamSignaling object. The notifications identify the stream, track or
+// channel.
+class MediaStreamSignalingObserver {
+ public:
+  // Triggered when the remote SessionDescription has a new stream.
+  virtual void OnAddRemoteStream(MediaStreamInterface* stream) = 0;
+
+  // Triggered when the remote SessionDescription removes a stream.
+  virtual void OnRemoveRemoteStream(MediaStreamInterface* stream) = 0;
+
+  // Triggered when the remote SessionDescription has a new data channel.
+  virtual void OnAddDataChannel(DataChannelInterface* data_channel) = 0;
+
+  // Triggered when the remote SessionDescription has a new audio track.
+  virtual void OnAddRemoteAudioTrack(MediaStreamInterface* stream,
+                                     AudioTrackInterface* audio_track,
+                                     uint32 ssrc) = 0;
+
+  // Triggered when the remote SessionDescription has a new video track.
+  virtual void OnAddRemoteVideoTrack(MediaStreamInterface* stream,
+                                     VideoTrackInterface* video_track,
+                                     uint32 ssrc) = 0;
+
+  // Triggered when the remote SessionDescription has removed an audio track.
+  virtual void OnRemoveRemoteAudioTrack(MediaStreamInterface* stream,
+                                        AudioTrackInterface* audio_track)  = 0;
+
+  // Triggered when the remote SessionDescription has removed a video track.
+  virtual void OnRemoveRemoteVideoTrack(MediaStreamInterface* stream,
+                                        VideoTrackInterface* video_track) = 0;
+
+  // Triggered when the local SessionDescription has a new audio track.
+  virtual void OnAddLocalAudioTrack(MediaStreamInterface* stream,
+                                    AudioTrackInterface* audio_track,
+                                    uint32 ssrc) = 0;
+
+  // Triggered when the local SessionDescription has a new video track.
+  virtual void OnAddLocalVideoTrack(MediaStreamInterface* stream,
+                                    VideoTrackInterface* video_track,
+                                    uint32 ssrc) = 0;
+
+  // Triggered when the local SessionDescription has removed an audio track.
+  virtual void OnRemoveLocalAudioTrack(MediaStreamInterface* stream,
+                                       AudioTrackInterface* audio_track) = 0;
+
+  // Triggered when the local SessionDescription has removed a video track.
+  virtual void OnRemoveLocalVideoTrack(MediaStreamInterface* stream,
+                                       VideoTrackInterface* video_track) = 0;
+
+  // Triggered when RemoveLocalStream is called. |stream| is no longer used
+  // when negotiating and all tracks in |stream| should stop providing data to
+  // this PeerConnection. This doesn't mean that the local session description
+  // has changed and OnRemoveLocalAudioTrack and OnRemoveLocalVideoTrack is not
+  // called for each individual track.
+  virtual void OnRemoveLocalStream(MediaStreamInterface* stream) = 0;
+
+ protected:
+  ~MediaStreamSignalingObserver() {}
+};
+
+// MediaStreamSignaling works as a glue between MediaStreams and a cricket
+// classes for SessionDescriptions.
+// It is used for creating cricket::MediaSessionOptions given the local
+// MediaStreams and data channels.
+//
+// It is responsible for creating remote MediaStreams given a remote
+// SessionDescription and creating cricket::MediaSessionOptions given
+// local MediaStreams.
+//
+// To signal that a DataChannel should be established:
+// 1. Call AddDataChannel with the new DataChannel. Next time
+//    GetMediaSessionOptions will include the description of the DataChannel.
+// 2. When a local session description is set, call UpdateLocalStreams with the
+//    session description. This will set the SSRC used for sending data on
+//    this DataChannel.
+// 3. When remote session description is set, call UpdateRemoteStream with the
+//    session description. If the DataChannel label and a SSRC is included in
+//    the description, the DataChannel is updated with SSRC that will be used
+//    for receiving data.
+// 4. When both the local and remote SSRC of a DataChannel is set the state of
+//    the DataChannel change to kOpen.
+//
+// To setup a DataChannel initialized by the remote end.
+// 1. When remote session description is set, call UpdateRemoteStream with the
+//    session description. If a label and a SSRC of a new DataChannel is found
+//    MediaStreamSignalingObserver::OnAddDataChannel with the label and SSRC is
+//    triggered.
+// 2. Create a DataChannel instance with the label and set the remote SSRC.
+// 3. Call AddDataChannel with this new DataChannel.  GetMediaSessionOptions
+//    will include the description of the DataChannel.
+// 4. Create a local session description and call UpdateLocalStreams. This will
+//    set the local SSRC used by the DataChannel.
+// 5. When both the local and remote SSRC of a DataChannel is set the state of
+//    the DataChannel change to kOpen.
+//
+// To close a DataChannel:
+// 1. Call DataChannel::Close. This will change the state of the DataChannel to
+//    kClosing. GetMediaSessionOptions will not
+//    include the description of the DataChannel.
+// 2. When a local session description is set, call UpdateLocalStreams with the
+//    session description. The description will no longer contain the
+//    DataChannel label or SSRC.
+// 3. When remote session description is set, call UpdateRemoteStream with the
+//    session description. The description will no longer contain the
+//    DataChannel label or SSRC. The DataChannel SSRC is updated with SSRC=0.
+//    The DataChannel change state to kClosed.
+
+class MediaStreamSignaling {
+ public:
+  MediaStreamSignaling(talk_base::Thread* signaling_thread,
+                       MediaStreamSignalingObserver* stream_observer);
+  virtual ~MediaStreamSignaling();
+
+  // Notify all referenced objects that MediaStreamSignaling will be teared
+  // down. This method must be called prior to the dtor.
+  void TearDown();
+
+  // Set a factory for creating data channels that are initiated by the remote
+  // peer.
+  void SetDataChannelFactory(DataChannelFactory* data_channel_factory) {
+    data_channel_factory_ = data_channel_factory;
+  }
+
+  // Checks if |id| is available to be assigned to a new SCTP data channel.
+  bool IsSctpIdAvailable(int id) const;
+
+  // Gets the first available SCTP id that is not assigned to any existing
+  // data channels.
+  bool AllocateSctpId(int* id);
+
+  // Adds |local_stream| to the collection of known MediaStreams that will be
+  // offered in a SessionDescription.
+  bool AddLocalStream(MediaStreamInterface* local_stream);
+
+  // Removes |local_stream| from the collection of known MediaStreams that will
+  // be offered in a SessionDescription.
+  void RemoveLocalStream(MediaStreamInterface* local_stream);
+
+  // Adds |data_channel| to the collection of DataChannels that will be
+  // be offered in a SessionDescription.
+  bool AddDataChannel(DataChannel* data_channel);
+
+  // Returns a MediaSessionOptions struct with options decided by |constraints|,
+  // the local MediaStreams and DataChannels.
+  virtual bool GetOptionsForOffer(
+      const MediaConstraintsInterface* constraints,
+      cricket::MediaSessionOptions* options);
+
+  // Returns a MediaSessionOptions struct with options decided by
+  // |constraints|, the local MediaStreams and DataChannels.
+  virtual bool GetOptionsForAnswer(
+      const MediaConstraintsInterface* constraints,
+      cricket::MediaSessionOptions* options);
+
+  // Called when the remote session description has changed. The purpose is to
+  // update remote MediaStreams and DataChannels with the current
+  // session state.
+  // If the remote SessionDescription contain information about a new remote
+  // MediaStreams a new remote MediaStream is created and
+  // MediaStreamSignalingObserver::OnAddStream is called.
+  // If a remote MediaStream is missing from
+  // the remote SessionDescription MediaStreamSignalingObserver::OnRemoveStream
+  // is called.
+  // If the SessionDescription contains information about a new DataChannel,
+  // MediaStreamSignalingObserver::OnAddDataChannel is called with the
+  // DataChannel.
+  void OnRemoteDescriptionChanged(const SessionDescriptionInterface* desc);
+
+  // Called when the local session description has changed. The purpose is to
+  // update local and remote MediaStreams and DataChannels with the current
+  // session state.
+  // If |desc| indicates that the media type should be rejected, the method
+  // ends the remote MediaStreamTracks.
+  // It also updates local DataChannels with information about its local SSRC.
+  void OnLocalDescriptionChanged(const SessionDescriptionInterface* desc);
+
+  // Called when the audio channel closes.
+  void OnAudioChannelClose();
+  // Called when the video channel closes.
+  void OnVideoChannelClose();
+  // Called when the data channel closes.
+  void OnDataChannelClose();
+
+  // Returns the SSRC for a given track.
+  bool GetRemoteAudioTrackSsrc(const std::string& track_id, uint32* ssrc) const;
+  bool GetRemoteVideoTrackSsrc(const std::string& track_id, uint32* ssrc) const;
+
+  // Returns all current known local MediaStreams.
+  StreamCollectionInterface* local_streams() const { return local_streams_;}
+
+  // Returns all current remote MediaStreams.
+  StreamCollectionInterface* remote_streams() const {
+    return remote_streams_.get();
+  }
+
+ private:
+  struct RemotePeerInfo {
+    RemotePeerInfo()
+        : msid_supported(false),
+          default_audio_track_needed(false),
+          default_video_track_needed(false) {
+    }
+    // True if it has been discovered that the remote peer support MSID.
+    bool msid_supported;
+    // The remote peer indicates in the session description that audio will be
+    // sent but no MSID is given.
+    bool default_audio_track_needed;
+    // The remote peer indicates in the session description that video will be
+    // sent but no MSID is given.
+    bool default_video_track_needed;
+
+    bool IsDefaultMediaStreamNeeded() {
+      return !msid_supported && (default_audio_track_needed ||
+          default_video_track_needed);
+    }
+  };
+
+  struct TrackInfo {
+    TrackInfo() : ssrc(0) {}
+    TrackInfo(const std::string& stream_label,
+              const std::string track_id,
+              uint32 ssrc)
+        : stream_label(stream_label),
+          track_id(track_id),
+          ssrc(ssrc) {
+    }
+    std::string stream_label;
+    std::string track_id;
+    uint32 ssrc;
+  };
+  typedef std::map<std::string, TrackInfo> TrackInfos;
+
+  void UpdateSessionOptions();
+
+  // Makes sure a MediaStream Track is created for each StreamParam in
+  // |streams|. |media_type| is the type of the |streams| and can be either
+  // audio or video.
+  // If a new MediaStream is created it is added to |new_streams|.
+  void UpdateRemoteStreamsList(
+      const std::vector<cricket::StreamParams>& streams,
+      cricket::MediaType media_type,
+      StreamCollection* new_streams);
+
+  // Triggered when a remote track has been seen for the first time in a remote
+  // session description. It creates a remote MediaStreamTrackInterface
+  // implementation and triggers MediaStreamSignaling::OnAddRemoteAudioTrack or
+  // MediaStreamSignaling::OnAddRemoteVideoTrack.
+  void OnRemoteTrackSeen(const std::string& stream_label,
+                         const std::string& track_id,
+                         uint32 ssrc,
+                         cricket::MediaType media_type);
+
+  // Triggered when a remote track has been removed from a remote session
+  // description. It removes the remote track with id |track_id| from a remote
+  // MediaStream and triggers MediaStreamSignaling::OnRemoveRemoteAudioTrack or
+  // MediaStreamSignaling::OnRemoveRemoteVideoTrack.
+  void OnRemoteTrackRemoved(const std::string& stream_label,
+                            const std::string& track_id,
+                            cricket::MediaType media_type);
+
+  // Set the MediaStreamTrackInterface::TrackState to |kEnded| on all remote
+  // tracks of type |media_type|.
+  void RejectRemoteTracks(cricket::MediaType media_type);
+
+  // Finds remote MediaStreams without any tracks and removes them from
+  // |remote_streams_| and notifies the observer that the MediaStream no longer
+  // exist.
+  void UpdateEndedRemoteMediaStreams();
+  void MaybeCreateDefaultStream();
+  TrackInfos* GetRemoteTracks(cricket::MediaType type);
+
+  // Returns a map of currently negotiated LocalTrackInfo of type |type|.
+  TrackInfos* GetLocalTracks(cricket::MediaType type);
+  bool FindLocalTrack(const std::string& track_id, cricket::MediaType type);
+
+  // Loops through the vector of |streams| and finds added and removed
+  // StreamParams since last time this method was called.
+  // For each new or removed StreamParam NotifyLocalTrackAdded or
+  // NotifyLocalTrackRemoved in invoked.
+  void UpdateLocalTracks(const std::vector<cricket::StreamParams>& streams,
+                         cricket::MediaType media_type);
+
+  // Triggered when a local track has been seen for the first time in a local
+  // session description.
+  // This method triggers MediaStreamSignaling::OnAddLocalAudioTrack or
+  // MediaStreamSignaling::OnAddLocalVideoTrack if the rtp streams in the local
+  // SessionDescription can be mapped to a MediaStreamTrack in a MediaStream in
+  // |local_streams_|
+  void OnLocalTrackSeen(const std::string& stream_label,
+                        const std::string& track_id,
+                        uint32 ssrc,
+                        cricket::MediaType media_type);
+
+  // Triggered when a local track has been removed from a local session
+  // description.
+  // This method triggers MediaStreamSignaling::OnRemoveLocalAudioTrack or
+  // MediaStreamSignaling::OnRemoveLocalVideoTrack if a stream has been removed
+  // from the local SessionDescription and the stream can be mapped to a
+  // MediaStreamTrack in a MediaStream in |local_streams_|.
+  void OnLocalTrackRemoved(const std::string& stream_label,
+                           const std::string& track_id,
+                           cricket::MediaType media_type);
+
+  void UpdateLocalRtpDataChannels(const cricket::StreamParamsVec& streams);
+  void UpdateRemoteRtpDataChannels(const cricket::StreamParamsVec& streams);
+  void UpdateClosingDataChannels(
+      const std::vector<std::string>& active_channels, bool is_local_update);
+  void CreateRemoteDataChannel(const std::string& label, uint32 remote_ssrc);
+  void UpdateLocalSctpDataChannels();
+  void UpdateRemoteSctpDataChannels();
+
+  RemotePeerInfo remote_info_;
+  talk_base::Thread* signaling_thread_;
+  DataChannelFactory* data_channel_factory_;
+  cricket::MediaSessionOptions options_;
+  MediaStreamSignalingObserver* stream_observer_;
+  talk_base::scoped_refptr<StreamCollection> local_streams_;
+  talk_base::scoped_refptr<StreamCollection> remote_streams_;
+  talk_base::scoped_ptr<RemoteMediaStreamFactory> remote_stream_factory_;
+
+  TrackInfos remote_audio_tracks_;
+  TrackInfos remote_video_tracks_;
+  TrackInfos local_audio_tracks_;
+  TrackInfos local_video_tracks_;
+
+  int last_allocated_sctp_id_;
+  typedef std::map<std::string, talk_base::scoped_refptr<DataChannel> >
+      DataChannels;
+  DataChannels data_channels_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_MEDIASTREAMSIGNALING_H_
diff --git a/talk/app/webrtc/mediastreamsignaling_unittest.cc b/talk/app/webrtc/mediastreamsignaling_unittest.cc
new file mode 100644
index 0000000..7f87454
--- /dev/null
+++ b/talk/app/webrtc/mediastreamsignaling_unittest.cc
@@ -0,0 +1,949 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/audiotrack.h"
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/app/webrtc/mediastreamsignaling.h"
+#include "talk/app/webrtc/streamcollection.h"
+#include "talk/app/webrtc/test/fakeconstraints.h"
+#include "talk/app/webrtc/videotrack.h"
+#include "talk/base/gunit.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/sessiondescription.h"
+
+static const char kStreams[][8] = {"stream1", "stream2"};
+static const char kAudioTracks[][32] = {"audiotrack0", "audiotrack1"};
+static const char kVideoTracks[][32] = {"videotrack0", "videotrack1"};
+
+using webrtc::AudioTrack;
+using webrtc::AudioTrackInterface;
+using webrtc::AudioTrackVector;
+using webrtc::VideoTrack;
+using webrtc::VideoTrackInterface;
+using webrtc::VideoTrackVector;
+using webrtc::DataChannelInterface;
+using webrtc::FakeConstraints;
+using webrtc::IceCandidateInterface;
+using webrtc::MediaConstraintsInterface;
+using webrtc::MediaStreamInterface;
+using webrtc::MediaStreamTrackInterface;
+using webrtc::SdpParseError;
+using webrtc::SessionDescriptionInterface;
+using webrtc::StreamCollection;
+using webrtc::StreamCollectionInterface;
+
+// Reference SDP with a MediaStream with label "stream1" and audio track with
+// id "audio_1" and a video track with id "video_1;
+static const char kSdpStringWithStream1[] =
+    "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 1 RTP/AVPF 103\r\n"
+    "a=mid:audio\r\n"
+    "a=rtpmap:103 ISAC/16000\r\n"
+    "a=ssrc:1 cname:stream1\r\n"
+    "a=ssrc:1 mslabel:stream1\r\n"
+    "a=ssrc:1 label:audiotrack0\r\n"
+    "m=video 1 RTP/AVPF 120\r\n"
+    "a=mid:video\r\n"
+    "a=rtpmap:120 VP8/90000\r\n"
+    "a=ssrc:2 cname:stream1\r\n"
+    "a=ssrc:2 mslabel:stream1\r\n"
+    "a=ssrc:2 label:videotrack0\r\n";
+
+// Reference SDP with two MediaStreams with label "stream1" and "stream2. Each
+// MediaStreams have one audio track and one video track.
+// This uses MSID.
+static const char kSdpStringWith2Stream[] =
+    "v=0\r\n"
+    "o=- 0 0 IN IP4 127.0.0.1\r\n"
+    "s=-\r\n"
+    "t=0 0\r\n"
+    "a=msid-semantic: WMS stream1 stream2\r\n"
+    "m=audio 1 RTP/AVPF 103\r\n"
+    "a=mid:audio\r\n"
+    "a=rtpmap:103 ISAC/16000\r\n"
+    "a=ssrc:1 cname:stream1\r\n"
+    "a=ssrc:1 msid:stream1 audiotrack0\r\n"
+    "a=ssrc:3 cname:stream2\r\n"
+    "a=ssrc:3 msid:stream2 audiotrack1\r\n"
+    "m=video 1 RTP/AVPF 120\r\n"
+    "a=mid:video\r\n"
+    "a=rtpmap:120 VP8/0\r\n"
+    "a=ssrc:2 cname:stream1\r\n"
+    "a=ssrc:2 msid:stream1 videotrack0\r\n"
+    "a=ssrc:4 cname:stream2\r\n"
+    "a=ssrc:4 msid:stream2 videotrack1\r\n";
+
+// Reference SDP without MediaStreams. Msid is not supported.
+static const char kSdpStringWithoutStreams[] =
+    "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 1 RTP/AVPF 103\r\n"
+    "a=mid:audio\r\n"
+    "a=rtpmap:103 ISAC/16000\r\n"
+    "m=video 1 RTP/AVPF 120\r\n"
+    "a=mid:video\r\n"
+    "a=rtpmap:120 VP8/90000\r\n";
+
+// Reference SDP without MediaStreams. Msid is supported.
+static const char kSdpStringWithMsidWithoutStreams[] =
+    "v=0\r\n"
+    "o=- 0 0 IN IP4 127.0.0.1\r\n"
+    "s=-\r\n"
+    "t=0 0\r\n"
+    "a:msid-semantic: WMS\r\n"
+    "m=audio 1 RTP/AVPF 103\r\n"
+    "a=mid:audio\r\n"
+    "a=rtpmap:103 ISAC/16000\r\n"
+    "m=video 1 RTP/AVPF 120\r\n"
+    "a=mid:video\r\n"
+    "a=rtpmap:120 VP8/90000\r\n";
+
+// Reference SDP without MediaStreams and audio only.
+static const char kSdpStringWithoutStreamsAudioOnly[] =
+    "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 1 RTP/AVPF 103\r\n"
+    "a=mid:audio\r\n"
+    "a=rtpmap:103 ISAC/16000\r\n";
+
+static const char kSdpStringInit[] =
+    "v=0\r\n"
+    "o=- 0 0 IN IP4 127.0.0.1\r\n"
+    "s=-\r\n"
+    "t=0 0\r\n"
+    "a=msid-semantic: WMS\r\n";
+
+static const char kSdpStringAudio[] =
+    "m=audio 1 RTP/AVPF 103\r\n"
+    "a=mid:audio\r\n"
+    "a=rtpmap:103 ISAC/16000\r\n";
+
+static const char kSdpStringVideo[] =
+    "m=video 1 RTP/AVPF 120\r\n"
+    "a=mid:video\r\n"
+    "a=rtpmap:120 VP8/90000\r\n";
+
+static const char kSdpStringMs1Audio0[] =
+    "a=ssrc:1 cname:stream1\r\n"
+    "a=ssrc:1 msid:stream1 audiotrack0\r\n";
+
+static const char kSdpStringMs1Video0[] =
+    "a=ssrc:2 cname:stream1\r\n"
+    "a=ssrc:2 msid:stream1 videotrack0\r\n";
+
+static const char kSdpStringMs1Audio1[] =
+    "a=ssrc:3 cname:stream1\r\n"
+    "a=ssrc:3 msid:stream1 audiotrack1\r\n";
+
+static const char kSdpStringMs1Video1[] =
+    "a=ssrc:4 cname:stream1\r\n"
+    "a=ssrc:4 msid:stream1 videotrack1\r\n";
+
+// Verifies that |options| contain all tracks in |collection| and that
+// the |options| has set the the has_audio and has_video flags correct.
+static void VerifyMediaOptions(StreamCollectionInterface* collection,
+                               const cricket::MediaSessionOptions& options) {
+  if (!collection) {
+    return;
+  }
+
+  size_t stream_index = 0;
+  for (size_t i = 0; i < collection->count(); ++i) {
+    MediaStreamInterface* stream = collection->at(i);
+    AudioTrackVector audio_tracks = stream->GetAudioTracks();
+    ASSERT_GE(options.streams.size(), stream_index + audio_tracks.size());
+    for (size_t j = 0; j < audio_tracks.size(); ++j) {
+      webrtc::AudioTrackInterface* audio = audio_tracks[j];
+      EXPECT_EQ(options.streams[stream_index].sync_label, stream->label());
+      EXPECT_EQ(options.streams[stream_index++].id, audio->id());
+      EXPECT_TRUE(options.has_audio);
+    }
+    VideoTrackVector video_tracks = stream->GetVideoTracks();
+    ASSERT_GE(options.streams.size(), stream_index + video_tracks.size());
+    for (size_t j = 0; j < video_tracks.size(); ++j) {
+      webrtc::VideoTrackInterface* video = video_tracks[j];
+      EXPECT_EQ(options.streams[stream_index].sync_label, stream->label());
+      EXPECT_EQ(options.streams[stream_index++].id, video->id());
+      EXPECT_TRUE(options.has_video);
+    }
+  }
+}
+
+static bool CompareStreamCollections(StreamCollectionInterface* s1,
+                                     StreamCollectionInterface* s2) {
+  if (s1 == NULL || s2 == NULL || s1->count() != s2->count())
+    return false;
+
+  for (size_t i = 0; i != s1->count(); ++i) {
+    if (s1->at(i)->label() != s2->at(i)->label())
+      return false;
+    webrtc::AudioTrackVector audio_tracks1 = s1->at(i)->GetAudioTracks();
+    webrtc::AudioTrackVector audio_tracks2 = s2->at(i)->GetAudioTracks();
+    webrtc::VideoTrackVector video_tracks1 = s1->at(i)->GetVideoTracks();
+    webrtc::VideoTrackVector video_tracks2 = s2->at(i)->GetVideoTracks();
+
+    if (audio_tracks1.size() != audio_tracks2.size())
+      return false;
+    for (size_t j = 0; j != audio_tracks1.size(); ++j) {
+       if (audio_tracks1[j]->id() != audio_tracks2[j]->id())
+         return false;
+    }
+    if (video_tracks1.size() != video_tracks2.size())
+      return false;
+    for (size_t j = 0; j != video_tracks1.size(); ++j) {
+      if (video_tracks1[j]->id() != video_tracks2[j]->id())
+        return false;
+    }
+  }
+  return true;
+}
+
+class MockSignalingObserver : public webrtc::MediaStreamSignalingObserver {
+ public:
+  MockSignalingObserver()
+      : remote_media_streams_(StreamCollection::Create()) {
+  }
+
+  virtual ~MockSignalingObserver() {
+  }
+
+  // New remote stream have been discovered.
+  virtual void OnAddRemoteStream(MediaStreamInterface* remote_stream) {
+    remote_media_streams_->AddStream(remote_stream);
+  }
+
+  // Remote stream is no longer available.
+  virtual void OnRemoveRemoteStream(MediaStreamInterface* remote_stream) {
+    remote_media_streams_->RemoveStream(remote_stream);
+  }
+
+  virtual void OnAddDataChannel(DataChannelInterface* data_channel) {
+  }
+
+  virtual void OnAddLocalAudioTrack(MediaStreamInterface* stream,
+                                    AudioTrackInterface* audio_track,
+                                    uint32 ssrc) {
+    AddTrack(&local_audio_tracks_, stream, audio_track, ssrc);
+  }
+
+  virtual void OnAddLocalVideoTrack(MediaStreamInterface* stream,
+                                    VideoTrackInterface* video_track,
+                                    uint32 ssrc) {
+    AddTrack(&local_video_tracks_, stream, video_track, ssrc);
+  }
+
+  virtual void OnRemoveLocalAudioTrack(MediaStreamInterface* stream,
+                                       AudioTrackInterface* audio_track) {
+    RemoveTrack(&local_audio_tracks_, stream, audio_track);
+  }
+
+  virtual void OnRemoveLocalVideoTrack(MediaStreamInterface* stream,
+                                       VideoTrackInterface* video_track) {
+    RemoveTrack(&local_video_tracks_, stream, video_track);
+  }
+
+  virtual void OnAddRemoteAudioTrack(MediaStreamInterface* stream,
+                                     AudioTrackInterface* audio_track,
+                                     uint32 ssrc) {
+    AddTrack(&remote_audio_tracks_, stream, audio_track, ssrc);
+  }
+
+  virtual void OnAddRemoteVideoTrack(MediaStreamInterface* stream,
+                                    VideoTrackInterface* video_track,
+                                    uint32 ssrc) {
+    AddTrack(&remote_video_tracks_, stream, video_track, ssrc);
+  }
+
+  virtual void OnRemoveRemoteAudioTrack(MediaStreamInterface* stream,
+                                       AudioTrackInterface* audio_track) {
+    RemoveTrack(&remote_audio_tracks_, stream, audio_track);
+  }
+
+  virtual void OnRemoveRemoteVideoTrack(MediaStreamInterface* stream,
+                                        VideoTrackInterface* video_track) {
+    RemoveTrack(&remote_video_tracks_, stream, video_track);
+  }
+
+  virtual void OnRemoveLocalStream(MediaStreamInterface* stream) {
+  }
+
+  MediaStreamInterface* RemoteStream(const std::string& label) {
+    return remote_media_streams_->find(label);
+  }
+
+  StreamCollectionInterface* remote_streams() const {
+    return remote_media_streams_;
+  }
+
+  size_t NumberOfRemoteAudioTracks() { return remote_audio_tracks_.size(); }
+
+  void  VerifyRemoteAudioTrack(const std::string& stream_label,
+                               const std::string& track_id,
+                               uint32 ssrc) {
+    VerifyTrack(remote_audio_tracks_, stream_label, track_id, ssrc);
+  }
+
+  size_t NumberOfRemoteVideoTracks() { return remote_video_tracks_.size(); }
+
+  void  VerifyRemoteVideoTrack(const std::string& stream_label,
+                               const std::string& track_id,
+                               uint32 ssrc) {
+    VerifyTrack(remote_video_tracks_, stream_label, track_id, ssrc);
+  }
+
+  size_t NumberOfLocalAudioTracks() { return local_audio_tracks_.size(); }
+  void  VerifyLocalAudioTrack(const std::string& stream_label,
+                              const std::string& track_id,
+                              uint32 ssrc) {
+    VerifyTrack(local_audio_tracks_, stream_label, track_id, ssrc);
+  }
+
+  size_t NumberOfLocalVideoTracks() { return local_video_tracks_.size(); }
+
+  void  VerifyLocalVideoTrack(const std::string& stream_label,
+                              const std::string& track_id,
+                              uint32 ssrc) {
+    VerifyTrack(local_video_tracks_, stream_label, track_id, ssrc);
+  }
+
+ private:
+  struct TrackInfo {
+    TrackInfo() {}
+    TrackInfo(const std::string& stream_label, const std::string track_id,
+              uint32 ssrc)
+        : stream_label(stream_label),
+          track_id(track_id),
+          ssrc(ssrc) {
+    }
+    std::string stream_label;
+    std::string track_id;
+    uint32 ssrc;
+  };
+  typedef std::map<std::string, TrackInfo> TrackInfos;
+
+  void AddTrack(TrackInfos* track_infos, MediaStreamInterface* stream,
+                MediaStreamTrackInterface* track,
+                uint32 ssrc) {
+    (*track_infos)[track->id()] = TrackInfo(stream->label(), track->id(),
+                                            ssrc);
+  }
+
+  void RemoveTrack(TrackInfos* track_infos, MediaStreamInterface* stream,
+                   MediaStreamTrackInterface* track) {
+    TrackInfos::iterator it = track_infos->find(track->id());
+    ASSERT_TRUE(it != track_infos->end());
+    ASSERT_EQ(it->second.stream_label, stream->label());
+    track_infos->erase(it);
+  }
+
+  void VerifyTrack(const TrackInfos& track_infos,
+                   const std::string& stream_label,
+                   const std::string& track_id,
+                   uint32 ssrc) {
+    TrackInfos::const_iterator it = track_infos.find(track_id);
+    ASSERT_TRUE(it != track_infos.end());
+    EXPECT_EQ(stream_label, it->second.stream_label);
+    EXPECT_EQ(ssrc, it->second.ssrc);
+  }
+
+  TrackInfos remote_audio_tracks_;
+  TrackInfos remote_video_tracks_;
+  TrackInfos local_audio_tracks_;
+  TrackInfos local_video_tracks_;
+
+  talk_base::scoped_refptr<StreamCollection> remote_media_streams_;
+};
+
+class MediaStreamSignalingForTest : public webrtc::MediaStreamSignaling {
+ public:
+  explicit MediaStreamSignalingForTest(MockSignalingObserver* observer)
+      : webrtc::MediaStreamSignaling(talk_base::Thread::Current(), observer) {
+  };
+
+  using webrtc::MediaStreamSignaling::GetOptionsForOffer;
+  using webrtc::MediaStreamSignaling::GetOptionsForAnswer;
+  using webrtc::MediaStreamSignaling::OnRemoteDescriptionChanged;
+  using webrtc::MediaStreamSignaling::remote_streams;
+};
+
+class MediaStreamSignalingTest: public testing::Test {
+ protected:
+  virtual void SetUp() {
+    observer_.reset(new MockSignalingObserver());
+    signaling_.reset(new MediaStreamSignalingForTest(observer_.get()));
+  }
+
+  // Create a collection of streams.
+  // CreateStreamCollection(1) creates a collection that
+  // correspond to kSdpString1.
+  // CreateStreamCollection(2) correspond to kSdpString2.
+  talk_base::scoped_refptr<StreamCollection>
+  CreateStreamCollection(int number_of_streams) {
+    talk_base::scoped_refptr<StreamCollection> local_collection(
+        StreamCollection::Create());
+
+    for (int i = 0; i < number_of_streams; ++i) {
+      talk_base::scoped_refptr<webrtc::MediaStreamInterface> stream(
+          webrtc::MediaStream::Create(kStreams[i]));
+
+      // Add a local audio track.
+      talk_base::scoped_refptr<webrtc::AudioTrackInterface> audio_track(
+          webrtc::AudioTrack::Create(kAudioTracks[i], NULL));
+      stream->AddTrack(audio_track);
+
+      // Add a local video track.
+      talk_base::scoped_refptr<webrtc::VideoTrackInterface> video_track(
+          webrtc::VideoTrack::Create(kVideoTracks[i], NULL));
+      stream->AddTrack(video_track);
+
+      local_collection->AddStream(stream);
+    }
+    return local_collection;
+  }
+
+  // This functions Creates a MediaStream with label kStreams[0] and
+  // |number_of_audio_tracks| and |number_of_video_tracks| tracks and the
+  // corresponding SessionDescriptionInterface. The SessionDescriptionInterface
+  // is returned in |desc| and the MediaStream is stored in
+  // |reference_collection_|
+  void CreateSessionDescriptionAndReference(
+      size_t number_of_audio_tracks,
+      size_t number_of_video_tracks,
+      SessionDescriptionInterface** desc) {
+    ASSERT_TRUE(desc != NULL);
+    ASSERT_LE(number_of_audio_tracks, 2u);
+    ASSERT_LE(number_of_video_tracks, 2u);
+
+    reference_collection_ = StreamCollection::Create();
+    std::string sdp_ms1 = std::string(kSdpStringInit);
+
+    std::string mediastream_label = kStreams[0];
+
+    talk_base::scoped_refptr<webrtc::MediaStreamInterface> stream(
+            webrtc::MediaStream::Create(mediastream_label));
+    reference_collection_->AddStream(stream);
+
+    if (number_of_audio_tracks > 0) {
+      sdp_ms1 += std::string(kSdpStringAudio);
+      sdp_ms1 += std::string(kSdpStringMs1Audio0);
+      AddAudioTrack(kAudioTracks[0], stream);
+    }
+    if (number_of_audio_tracks > 1) {
+      sdp_ms1 += kSdpStringMs1Audio1;
+      AddAudioTrack(kAudioTracks[1], stream);
+    }
+
+    if (number_of_video_tracks > 0) {
+      sdp_ms1 += std::string(kSdpStringVideo);
+      sdp_ms1 += std::string(kSdpStringMs1Video0);
+      AddVideoTrack(kVideoTracks[0], stream);
+    }
+    if (number_of_video_tracks > 1) {
+      sdp_ms1 += kSdpStringMs1Video1;
+      AddVideoTrack(kVideoTracks[1], stream);
+    }
+
+    *desc = webrtc::CreateSessionDescription(
+        SessionDescriptionInterface::kOffer, sdp_ms1, NULL);
+  }
+
+  void AddAudioTrack(const std::string& track_id,
+                     MediaStreamInterface* stream) {
+    talk_base::scoped_refptr<webrtc::AudioTrackInterface> audio_track(
+        webrtc::AudioTrack::Create(track_id, NULL));
+    ASSERT_TRUE(stream->AddTrack(audio_track));
+  }
+
+  void AddVideoTrack(const std::string& track_id,
+                     MediaStreamInterface* stream) {
+    talk_base::scoped_refptr<webrtc::VideoTrackInterface> video_track(
+        webrtc::VideoTrack::Create(track_id, NULL));
+    ASSERT_TRUE(stream->AddTrack(video_track));
+  }
+
+  talk_base::scoped_refptr<StreamCollection> reference_collection_;
+  talk_base::scoped_ptr<MockSignalingObserver> observer_;
+  talk_base::scoped_ptr<MediaStreamSignalingForTest> signaling_;
+};
+
+// Test that a MediaSessionOptions is created for an offer if
+// kOfferToReceiveAudio and kOfferToReceiveVideo constraints are set but no
+// MediaStreams are sent.
+TEST_F(MediaStreamSignalingTest, GetMediaSessionOptionsForOfferWithAudioVideo) {
+  FakeConstraints constraints;
+  constraints.SetMandatoryReceiveAudio(true);
+  constraints.SetMandatoryReceiveVideo(true);
+  cricket::MediaSessionOptions options;
+  EXPECT_TRUE(signaling_->GetOptionsForOffer(&constraints, &options));
+  EXPECT_TRUE(options.has_audio);
+  EXPECT_TRUE(options.has_video);
+  EXPECT_TRUE(options.bundle_enabled);
+}
+
+// Test that a correct MediaSessionOptions is created for an offer if
+// kOfferToReceiveAudio constraints is set but no MediaStreams are sent.
+TEST_F(MediaStreamSignalingTest, GetMediaSessionOptionsForOfferWithAudio) {
+  FakeConstraints constraints;
+  constraints.SetMandatoryReceiveAudio(true);
+  cricket::MediaSessionOptions options;
+  EXPECT_TRUE(signaling_->GetOptionsForOffer(&constraints, &options));
+  EXPECT_TRUE(options.has_audio);
+  EXPECT_FALSE(options.has_video);
+  EXPECT_TRUE(options.bundle_enabled);
+}
+
+// Test that a correct MediaSessionOptions is created for an offer if
+// no constraints or MediaStreams are sent.
+TEST_F(MediaStreamSignalingTest, GetDefaultMediaSessionOptionsForOffer) {
+  cricket::MediaSessionOptions options;
+  EXPECT_TRUE(signaling_->GetOptionsForOffer(NULL, &options));
+  EXPECT_TRUE(options.has_audio);
+  EXPECT_FALSE(options.has_video);
+  EXPECT_TRUE(options.bundle_enabled);
+}
+
+// Test that a correct MediaSessionOptions is created for an offer if
+// kOfferToReceiveVideo constraints is set but no MediaStreams are sent.
+TEST_F(MediaStreamSignalingTest, GetMediaSessionOptionsForOfferWithVideo) {
+  FakeConstraints constraints;
+  constraints.SetMandatoryReceiveAudio(false);
+  constraints.SetMandatoryReceiveVideo(true);
+  cricket::MediaSessionOptions options;
+  EXPECT_TRUE(signaling_->GetOptionsForOffer(&constraints, &options));
+  EXPECT_FALSE(options.has_audio);
+  EXPECT_TRUE(options.has_video);
+  EXPECT_TRUE(options.bundle_enabled);
+}
+
+// Test that a correct MediaSessionOptions is created for an offer if
+// kUseRtpMux constraints is set to false.
+TEST_F(MediaStreamSignalingTest,
+       GetMediaSessionOptionsForOfferWithBundleDisabled) {
+  FakeConstraints constraints;
+  constraints.SetMandatoryReceiveAudio(true);
+  constraints.SetMandatoryReceiveVideo(true);
+  constraints.SetMandatoryUseRtpMux(false);
+  cricket::MediaSessionOptions options;
+  EXPECT_TRUE(signaling_->GetOptionsForOffer(&constraints, &options));
+  EXPECT_TRUE(options.has_audio);
+  EXPECT_TRUE(options.has_video);
+  EXPECT_FALSE(options.bundle_enabled);
+}
+
+// Test that a correct MediaSessionOptions is created to restart ice if
+// kIceRestart constraints is set. It also tests that subsequent
+// MediaSessionOptions don't have |transport_options.ice_restart| set.
+TEST_F(MediaStreamSignalingTest,
+       GetMediaSessionOptionsForOfferWithIceRestart) {
+  FakeConstraints constraints;
+  constraints.SetMandatoryIceRestart(true);
+  cricket::MediaSessionOptions options;
+  EXPECT_TRUE(signaling_->GetOptionsForOffer(&constraints, &options));
+  EXPECT_TRUE(options.transport_options.ice_restart);
+
+  EXPECT_TRUE(signaling_->GetOptionsForOffer(NULL, &options));
+  EXPECT_FALSE(options.transport_options.ice_restart);
+}
+
+// Test that GetMediaSessionOptionsForOffer and GetOptionsForAnswer work as
+// expected if unknown constraints are used.
+TEST_F(MediaStreamSignalingTest, GetMediaSessionOptionsWithBadConstraints) {
+  FakeConstraints mandatory;
+  mandatory.AddMandatory("bad_key", "bad_value");
+  cricket::MediaSessionOptions options;
+  EXPECT_FALSE(signaling_->GetOptionsForOffer(&mandatory, &options));
+  EXPECT_FALSE(signaling_->GetOptionsForAnswer(&mandatory, &options));
+
+  FakeConstraints optional;
+  optional.AddOptional("bad_key", "bad_value");
+  EXPECT_TRUE(signaling_->GetOptionsForOffer(&optional, &options));
+  EXPECT_TRUE(signaling_->GetOptionsForAnswer(&optional, &options));
+}
+
+// Test that a correct MediaSessionOptions are created for an offer if
+// a MediaStream is sent and later updated with a new track.
+// MediaConstraints are not used.
+TEST_F(MediaStreamSignalingTest, AddTrackToLocalMediaStream) {
+  talk_base::scoped_refptr<StreamCollection> local_streams(
+      CreateStreamCollection(1));
+  MediaStreamInterface* local_stream = local_streams->at(0);
+  EXPECT_TRUE(signaling_->AddLocalStream(local_stream));
+  cricket::MediaSessionOptions options;
+  EXPECT_TRUE(signaling_->GetOptionsForOffer(NULL, &options));
+  VerifyMediaOptions(local_streams, options);
+
+  cricket::MediaSessionOptions updated_options;
+  local_stream->AddTrack(AudioTrack::Create(kAudioTracks[1], NULL));
+  EXPECT_TRUE(signaling_->GetOptionsForOffer(NULL, &options));
+  VerifyMediaOptions(local_streams, options);
+}
+
+// Test that the MediaConstraints in an answer don't affect if audio and video
+// is offered in an offer but that if kOfferToReceiveAudio or
+// kOfferToReceiveVideo constraints are true in an offer, the media type will be
+// included in subsequent answers.
+TEST_F(MediaStreamSignalingTest, MediaConstraintsInAnswer) {
+  FakeConstraints answer_c;
+  answer_c.SetMandatoryReceiveAudio(true);
+  answer_c.SetMandatoryReceiveVideo(true);
+
+  cricket::MediaSessionOptions answer_options;
+  EXPECT_TRUE(signaling_->GetOptionsForAnswer(&answer_c, &answer_options));
+  EXPECT_TRUE(answer_options.has_audio);
+  EXPECT_TRUE(answer_options.has_video);
+
+  FakeConstraints offer_c;
+  offer_c.SetMandatoryReceiveAudio(false);
+  offer_c.SetMandatoryReceiveVideo(false);
+
+  cricket::MediaSessionOptions offer_options;
+  EXPECT_TRUE(signaling_->GetOptionsForOffer(&offer_c, &offer_options));
+  EXPECT_FALSE(offer_options.has_audio);
+  EXPECT_FALSE(offer_options.has_video);
+
+  FakeConstraints updated_offer_c;
+  updated_offer_c.SetMandatoryReceiveAudio(true);
+  updated_offer_c.SetMandatoryReceiveVideo(true);
+
+  cricket::MediaSessionOptions updated_offer_options;
+  EXPECT_TRUE(signaling_->GetOptionsForOffer(&updated_offer_c,
+                                             &updated_offer_options));
+  EXPECT_TRUE(updated_offer_options.has_audio);
+  EXPECT_TRUE(updated_offer_options.has_video);
+
+  // Since an offer has been created with both audio and video, subsequent
+  // offers and answers should contain both audio and video.
+  // Answers will only contain the media types that exist in the offer
+  // regardless of the value of |updated_answer_options.has_audio| and
+  // |updated_answer_options.has_video|.
+  FakeConstraints updated_answer_c;
+  answer_c.SetMandatoryReceiveAudio(false);
+  answer_c.SetMandatoryReceiveVideo(false);
+
+  cricket::MediaSessionOptions updated_answer_options;
+  EXPECT_TRUE(signaling_->GetOptionsForAnswer(&updated_answer_c,
+                                              &updated_answer_options));
+  EXPECT_TRUE(updated_answer_options.has_audio);
+  EXPECT_TRUE(updated_answer_options.has_video);
+
+  EXPECT_TRUE(signaling_->GetOptionsForOffer(NULL,
+                                             &updated_offer_options));
+  EXPECT_TRUE(updated_offer_options.has_audio);
+  EXPECT_TRUE(updated_offer_options.has_video);
+}
+
+// This test verifies that the remote MediaStreams corresponding to a received
+// SDP string is created. In this test the two separate MediaStreams are
+// signaled.
+TEST_F(MediaStreamSignalingTest, UpdateRemoteStreams) {
+  talk_base::scoped_ptr<SessionDescriptionInterface> desc(
+      webrtc::CreateSessionDescription(SessionDescriptionInterface::kOffer,
+                                       kSdpStringWithStream1, NULL));
+  EXPECT_TRUE(desc != NULL);
+  signaling_->OnRemoteDescriptionChanged(desc.get());
+
+  talk_base::scoped_refptr<StreamCollection> reference(
+      CreateStreamCollection(1));
+  EXPECT_TRUE(CompareStreamCollections(signaling_->remote_streams(),
+                                       reference.get()));
+  EXPECT_TRUE(CompareStreamCollections(observer_->remote_streams(),
+                                       reference.get()));
+  EXPECT_EQ(1u, observer_->NumberOfRemoteAudioTracks());
+  observer_->VerifyRemoteAudioTrack(kStreams[0], kAudioTracks[0], 1);
+  EXPECT_EQ(1u, observer_->NumberOfRemoteVideoTracks());
+  observer_->VerifyRemoteVideoTrack(kStreams[0], kVideoTracks[0], 2);
+
+  // Create a session description based on another SDP with another
+  // MediaStream.
+  talk_base::scoped_ptr<SessionDescriptionInterface> update_desc(
+      webrtc::CreateSessionDescription(SessionDescriptionInterface::kOffer,
+                                       kSdpStringWith2Stream, NULL));
+  EXPECT_TRUE(update_desc != NULL);
+  signaling_->OnRemoteDescriptionChanged(update_desc.get());
+
+  talk_base::scoped_refptr<StreamCollection> reference2(
+      CreateStreamCollection(2));
+  EXPECT_TRUE(CompareStreamCollections(signaling_->remote_streams(),
+                                       reference2.get()));
+  EXPECT_TRUE(CompareStreamCollections(observer_->remote_streams(),
+                                       reference2.get()));
+
+  EXPECT_EQ(2u, observer_->NumberOfRemoteAudioTracks());
+  observer_->VerifyRemoteAudioTrack(kStreams[0], kAudioTracks[0], 1);
+  observer_->VerifyRemoteAudioTrack(kStreams[1], kAudioTracks[1], 3);
+  EXPECT_EQ(2u, observer_->NumberOfRemoteVideoTracks());
+  observer_->VerifyRemoteVideoTrack(kStreams[0], kVideoTracks[0], 2);
+  observer_->VerifyRemoteVideoTrack(kStreams[1], kVideoTracks[1], 4);
+}
+
+// This test verifies that the remote MediaStreams corresponding to a received
+// SDP string is created. In this test the same remote MediaStream is signaled
+// but MediaStream tracks are added and removed.
+TEST_F(MediaStreamSignalingTest, AddRemoveTrackFromExistingRemoteMediaStream) {
+  talk_base::scoped_ptr<SessionDescriptionInterface> desc_ms1;
+  CreateSessionDescriptionAndReference(1, 1, desc_ms1.use());
+  signaling_->OnRemoteDescriptionChanged(desc_ms1.get());
+  EXPECT_TRUE(CompareStreamCollections(signaling_->remote_streams(),
+                                       reference_collection_));
+
+  // Add extra audio and video tracks to the same MediaStream.
+  talk_base::scoped_ptr<SessionDescriptionInterface> desc_ms1_two_tracks;
+  CreateSessionDescriptionAndReference(2, 2, desc_ms1_two_tracks.use());
+  signaling_->OnRemoteDescriptionChanged(desc_ms1_two_tracks.get());
+  EXPECT_TRUE(CompareStreamCollections(signaling_->remote_streams(),
+                                       reference_collection_));
+  EXPECT_TRUE(CompareStreamCollections(observer_->remote_streams(),
+                                       reference_collection_));
+
+  // Remove the extra audio and video tracks again.
+  CreateSessionDescriptionAndReference(1, 1, desc_ms1.use());
+  signaling_->OnRemoteDescriptionChanged(desc_ms1.get());
+  EXPECT_TRUE(CompareStreamCollections(signaling_->remote_streams(),
+                                       reference_collection_));
+  EXPECT_TRUE(CompareStreamCollections(observer_->remote_streams(),
+                                       reference_collection_));
+}
+
+// This test that remote tracks are ended if a
+// local session description is set that rejects the media content type.
+TEST_F(MediaStreamSignalingTest, RejectMediaContent) {
+  talk_base::scoped_ptr<SessionDescriptionInterface> desc(
+      webrtc::CreateSessionDescription(SessionDescriptionInterface::kOffer,
+                                       kSdpStringWithStream1, NULL));
+  EXPECT_TRUE(desc != NULL);
+  signaling_->OnRemoteDescriptionChanged(desc.get());
+
+  ASSERT_EQ(1u, observer_->remote_streams()->count());
+  MediaStreamInterface* remote_stream =  observer_->remote_streams()->at(0);
+  ASSERT_EQ(1u, remote_stream->GetVideoTracks().size());
+  ASSERT_EQ(1u, remote_stream->GetAudioTracks().size());
+
+  talk_base::scoped_refptr<webrtc::VideoTrackInterface> remote_video =
+      remote_stream->GetVideoTracks()[0];
+  EXPECT_EQ(webrtc::MediaStreamTrackInterface::kLive, remote_video->state());
+  talk_base::scoped_refptr<webrtc::AudioTrackInterface> remote_audio =
+      remote_stream->GetAudioTracks()[0];
+  EXPECT_EQ(webrtc::MediaStreamTrackInterface::kLive, remote_audio->state());
+
+  cricket::ContentInfo* video_info =
+      desc->description()->GetContentByName("video");
+  ASSERT_TRUE(video_info != NULL);
+  video_info->rejected = true;
+  signaling_->OnLocalDescriptionChanged(desc.get());
+  EXPECT_EQ(webrtc::MediaStreamTrackInterface::kEnded, remote_video->state());
+  EXPECT_EQ(webrtc::MediaStreamTrackInterface::kLive, remote_audio->state());
+
+  cricket::ContentInfo* audio_info =
+      desc->description()->GetContentByName("audio");
+  ASSERT_TRUE(audio_info != NULL);
+  audio_info->rejected = true;
+  signaling_->OnLocalDescriptionChanged(desc.get());
+  EXPECT_EQ(webrtc::MediaStreamTrackInterface::kEnded, remote_audio->state());
+}
+
+// This tests that a default MediaStream is created if a remote session
+// description doesn't contain any streams and no MSID support.
+// It also tests that the default stream is updated if a video m-line is added
+// in a subsequent session description.
+TEST_F(MediaStreamSignalingTest, SdpWithoutMsidCreatesDefaultStream) {
+  talk_base::scoped_ptr<SessionDescriptionInterface> desc_audio_only(
+      webrtc::CreateSessionDescription(SessionDescriptionInterface::kOffer,
+                                       kSdpStringWithoutStreamsAudioOnly,
+                                       NULL));
+  ASSERT_TRUE(desc_audio_only != NULL);
+  signaling_->OnRemoteDescriptionChanged(desc_audio_only.get());
+
+  EXPECT_EQ(1u, signaling_->remote_streams()->count());
+  ASSERT_EQ(1u, observer_->remote_streams()->count());
+  MediaStreamInterface* remote_stream = observer_->remote_streams()->at(0);
+
+  EXPECT_EQ(1u, remote_stream->GetAudioTracks().size());
+  EXPECT_EQ(0u, remote_stream->GetVideoTracks().size());
+  EXPECT_EQ("default", remote_stream->label());
+
+  talk_base::scoped_ptr<SessionDescriptionInterface> desc(
+      webrtc::CreateSessionDescription(SessionDescriptionInterface::kOffer,
+                                       kSdpStringWithoutStreams, NULL));
+  ASSERT_TRUE(desc != NULL);
+  signaling_->OnRemoteDescriptionChanged(desc.get());
+  EXPECT_EQ(1u, signaling_->remote_streams()->count());
+  ASSERT_EQ(1u, remote_stream->GetAudioTracks().size());
+  EXPECT_EQ("defaulta0", remote_stream->GetAudioTracks()[0]->id());
+  ASSERT_EQ(1u, remote_stream->GetVideoTracks().size());
+  EXPECT_EQ("defaultv0", remote_stream->GetVideoTracks()[0]->id());
+  observer_->VerifyRemoteAudioTrack("default", "defaulta0", 0);
+  observer_->VerifyRemoteVideoTrack("default", "defaultv0", 0);
+}
+
+// This tests that a default MediaStream is created if the remote session
+// description doesn't contain any streams and don't contain an indication if
+// MSID is supported.
+TEST_F(MediaStreamSignalingTest,
+       SdpWithoutMsidAndStreamsCreatesDefaultStream) {
+  talk_base::scoped_ptr<SessionDescriptionInterface> desc(
+      webrtc::CreateSessionDescription(SessionDescriptionInterface::kOffer,
+                                       kSdpStringWithoutStreams,
+                                       NULL));
+  ASSERT_TRUE(desc != NULL);
+  signaling_->OnRemoteDescriptionChanged(desc.get());
+
+  ASSERT_EQ(1u, observer_->remote_streams()->count());
+  MediaStreamInterface* remote_stream = observer_->remote_streams()->at(0);
+  EXPECT_EQ(1u, remote_stream->GetAudioTracks().size());
+  EXPECT_EQ(1u, remote_stream->GetVideoTracks().size());
+}
+
+// This tests that a default MediaStream is not created if the remote session
+// description doesn't contain any streams but does support MSID.
+TEST_F(MediaStreamSignalingTest, SdpWitMsidDontCreatesDefaultStream) {
+  talk_base::scoped_ptr<SessionDescriptionInterface> desc_msid_without_streams(
+      webrtc::CreateSessionDescription(SessionDescriptionInterface::kOffer,
+                                       kSdpStringWithMsidWithoutStreams,
+                                       NULL));
+  signaling_->OnRemoteDescriptionChanged(desc_msid_without_streams.get());
+  EXPECT_EQ(0u, observer_->remote_streams()->count());
+}
+
+// This test that a default MediaStream is not created if a remote session
+// description is updated to not have any MediaStreams.
+TEST_F(MediaStreamSignalingTest, VerifyDefaultStreamIsNotCreated) {
+  talk_base::scoped_ptr<SessionDescriptionInterface> desc(
+      webrtc::CreateSessionDescription(SessionDescriptionInterface::kOffer,
+                                       kSdpStringWithStream1,
+                                       NULL));
+  ASSERT_TRUE(desc != NULL);
+  signaling_->OnRemoteDescriptionChanged(desc.get());
+  talk_base::scoped_refptr<StreamCollection> reference(
+      CreateStreamCollection(1));
+  EXPECT_TRUE(CompareStreamCollections(observer_->remote_streams(),
+                                       reference.get()));
+
+  talk_base::scoped_ptr<SessionDescriptionInterface> desc_without_streams(
+      webrtc::CreateSessionDescription(SessionDescriptionInterface::kOffer,
+                                       kSdpStringWithoutStreams,
+                                       NULL));
+  signaling_->OnRemoteDescriptionChanged(desc_without_streams.get());
+  EXPECT_EQ(0u, observer_->remote_streams()->count());
+}
+
+// This test that the correct MediaStreamSignalingObserver methods are called
+// when MediaStreamSignaling::OnLocalDescriptionChanged is called with an
+// updated local session description.
+TEST_F(MediaStreamSignalingTest, LocalDescriptionChanged) {
+  talk_base::scoped_ptr<SessionDescriptionInterface> desc_1;
+  CreateSessionDescriptionAndReference(2, 2, desc_1.use());
+
+  signaling_->AddLocalStream(reference_collection_->at(0));
+  signaling_->OnLocalDescriptionChanged(desc_1.get());
+  EXPECT_EQ(2u, observer_->NumberOfLocalAudioTracks());
+  EXPECT_EQ(2u, observer_->NumberOfLocalVideoTracks());
+  observer_->VerifyLocalAudioTrack(kStreams[0], kAudioTracks[0], 1);
+  observer_->VerifyLocalVideoTrack(kStreams[0], kVideoTracks[0], 2);
+  observer_->VerifyLocalAudioTrack(kStreams[0], kAudioTracks[1], 3);
+  observer_->VerifyLocalVideoTrack(kStreams[0], kVideoTracks[1], 4);
+
+  // Remove an audio and video track.
+  talk_base::scoped_ptr<SessionDescriptionInterface> desc_2;
+  CreateSessionDescriptionAndReference(1, 1, desc_2.use());
+  signaling_->OnLocalDescriptionChanged(desc_2.get());
+  EXPECT_EQ(1u, observer_->NumberOfLocalAudioTracks());
+  EXPECT_EQ(1u, observer_->NumberOfLocalVideoTracks());
+  observer_->VerifyLocalAudioTrack(kStreams[0], kAudioTracks[0], 1);
+  observer_->VerifyLocalVideoTrack(kStreams[0], kVideoTracks[0], 2);
+}
+
+// This test that the correct MediaStreamSignalingObserver methods are called
+// when MediaStreamSignaling::AddLocalStream is called after
+// MediaStreamSignaling::OnLocalDescriptionChanged is called.
+TEST_F(MediaStreamSignalingTest, AddLocalStreamAfterLocalDescriptionChanged) {
+  talk_base::scoped_ptr<SessionDescriptionInterface> desc_1;
+  CreateSessionDescriptionAndReference(2, 2, desc_1.use());
+
+  signaling_->OnLocalDescriptionChanged(desc_1.get());
+  EXPECT_EQ(0u, observer_->NumberOfLocalAudioTracks());
+  EXPECT_EQ(0u, observer_->NumberOfLocalVideoTracks());
+
+  signaling_->AddLocalStream(reference_collection_->at(0));
+  EXPECT_EQ(2u, observer_->NumberOfLocalAudioTracks());
+  EXPECT_EQ(2u, observer_->NumberOfLocalVideoTracks());
+  observer_->VerifyLocalAudioTrack(kStreams[0], kAudioTracks[0], 1);
+  observer_->VerifyLocalVideoTrack(kStreams[0], kVideoTracks[0], 2);
+  observer_->VerifyLocalAudioTrack(kStreams[0], kAudioTracks[1], 3);
+  observer_->VerifyLocalVideoTrack(kStreams[0], kVideoTracks[1], 4);
+}
+
+// This test that the correct MediaStreamSignalingObserver methods are called
+// if the ssrc on a local track is changed when
+// MediaStreamSignaling::OnLocalDescriptionChanged is called.
+TEST_F(MediaStreamSignalingTest, ChangeSsrcOnTrackInLocalSessionDescription) {
+  talk_base::scoped_ptr<SessionDescriptionInterface> desc;
+  CreateSessionDescriptionAndReference(1, 1, desc.use());
+
+  signaling_->AddLocalStream(reference_collection_->at(0));
+  signaling_->OnLocalDescriptionChanged(desc.get());
+  EXPECT_EQ(1u, observer_->NumberOfLocalAudioTracks());
+  EXPECT_EQ(1u, observer_->NumberOfLocalVideoTracks());
+  observer_->VerifyLocalAudioTrack(kStreams[0], kAudioTracks[0], 1);
+  observer_->VerifyLocalVideoTrack(kStreams[0], kVideoTracks[0], 2);
+
+  // Change the ssrc of the audio and video track.
+  std::string sdp;
+  desc->ToString(&sdp);
+  std::string ssrc_org = "a=ssrc:1";
+  std::string ssrc_to = "a=ssrc:97";
+  talk_base::replace_substrs(ssrc_org.c_str(), ssrc_org.length(),
+                             ssrc_to.c_str(), ssrc_to.length(),
+                             &sdp);
+  ssrc_org = "a=ssrc:2";
+  ssrc_to = "a=ssrc:98";
+  talk_base::replace_substrs(ssrc_org.c_str(), ssrc_org.length(),
+                             ssrc_to.c_str(), ssrc_to.length(),
+                             &sdp);
+  talk_base::scoped_ptr<SessionDescriptionInterface> updated_desc(
+      webrtc::CreateSessionDescription(SessionDescriptionInterface::kOffer,
+                                       sdp, NULL));
+
+  signaling_->OnLocalDescriptionChanged(updated_desc.get());
+  EXPECT_EQ(1u, observer_->NumberOfLocalAudioTracks());
+  EXPECT_EQ(1u, observer_->NumberOfLocalVideoTracks());
+  observer_->VerifyLocalAudioTrack(kStreams[0], kAudioTracks[0], 97);
+  observer_->VerifyLocalVideoTrack(kStreams[0], kVideoTracks[0], 98);
+}
+
+
diff --git a/talk/app/webrtc/mediastreamtrack.h b/talk/app/webrtc/mediastreamtrack.h
new file mode 100644
index 0000000..6055e51
--- /dev/null
+++ b/talk/app/webrtc/mediastreamtrack.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_MEDIASTREAMTRACK_H_
+#define TALK_APP_WEBRTC_MEDIASTREAMTRACK_H_
+
+#include <string>
+
+#include "talk/app/webrtc/mediastreaminterface.h"
+#include "talk/app/webrtc/notifier.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 id() const { return id_; }
+  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& id)
+      : enabled_(true),
+        id_(id),
+        state_(MediaStreamTrackInterface::kInitializing) {
+  }
+
+ private:
+  bool enabled_;
+  std::string id_;
+  MediaStreamTrackInterface::TrackState state_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_MEDIASTREAMTRACK_H_
diff --git a/talk/app/webrtc/mediastreamtrackproxy.h b/talk/app/webrtc/mediastreamtrackproxy.h
new file mode 100644
index 0000000..954874b
--- /dev/null
+++ b/talk/app/webrtc/mediastreamtrackproxy.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.
+ */
+
+// 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 "talk/app/webrtc/mediastreaminterface.h"
+#include "talk/app/webrtc/proxy.h"
+
+namespace webrtc {
+
+BEGIN_PROXY_MAP(AudioTrack)
+  PROXY_CONSTMETHOD0(std::string, kind)
+  PROXY_CONSTMETHOD0(std::string, id)
+  PROXY_CONSTMETHOD0(TrackState, state)
+  PROXY_CONSTMETHOD0(bool, enabled)
+  PROXY_CONSTMETHOD0(AudioSourceInterface*, GetSource)
+  PROXY_METHOD0(cricket::AudioRenderer*, FrameInput)
+
+  PROXY_METHOD1(bool, set_enabled, bool)
+  PROXY_METHOD1(bool, set_state, TrackState)
+
+  PROXY_METHOD1(void, RegisterObserver, ObserverInterface*)
+  PROXY_METHOD1(void, UnregisterObserver, ObserverInterface*)
+END_PROXY()
+
+BEGIN_PROXY_MAP(VideoTrack)
+  PROXY_CONSTMETHOD0(std::string, kind)
+  PROXY_CONSTMETHOD0(std::string, id)
+  PROXY_CONSTMETHOD0(TrackState, state)
+  PROXY_CONSTMETHOD0(bool, enabled)
+  PROXY_METHOD1(bool, set_enabled, bool)
+  PROXY_METHOD1(bool, set_state, TrackState)
+
+  PROXY_METHOD1(void, AddRenderer, VideoRendererInterface*)
+  PROXY_METHOD1(void, RemoveRenderer, VideoRendererInterface*)
+  PROXY_METHOD0(cricket::VideoRenderer*, FrameInput)
+  PROXY_CONSTMETHOD0(VideoSourceInterface*, GetSource)
+
+  PROXY_METHOD1(void, RegisterObserver, ObserverInterface*)
+  PROXY_METHOD1(void, UnregisterObserver, ObserverInterface*)
+END_PROXY()
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_MEDIASTREAMTRACKPROXY_H_
diff --git a/talk/app/webrtc/notifier.h b/talk/app/webrtc/notifier.h
new file mode 100644
index 0000000..eaa0063
--- /dev/null
+++ b/talk/app/webrtc/notifier.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_APP_WEBRTC_NOTIFIER_H_
+#define TALK_APP_WEBRTC_NOTIFIER_H_
+
+#include <list>
+
+#include "talk/base/common.h"
+#include "talk/app/webrtc/mediastreaminterface.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() {
+    // Copy the list of observers to avoid a crash if the observer object
+    // unregisters as a result of the OnChanged() call. If the same list is used
+    // UnregisterObserver will affect the list make the iterator invalid.
+    std::list<ObserverInterface*> observers = observers_;
+    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_NOTIFIER_H_
diff --git a/talk/app/webrtc/objc/README b/talk/app/webrtc/objc/README
new file mode 100644
index 0000000..cea2aae
--- /dev/null
+++ b/talk/app/webrtc/objc/README
@@ -0,0 +1,45 @@
+This directory contains the ObjectiveC implementation of the
+webrtc::PeerConnection API.  This can be built for Mac or iOS.
+
+Prerequisites:
+- Make sure gclient is checking out tools necessary to target iOS: your
+  .gclient file should contain a line like:
+  target_os = ['ios', 'mac']
+  Make sure to re-run gclient sync after adding this to download the tools.
+- Set up webrtc-related GYP variables:
+- For Mac:
+  export GYP_DEFINES="build_with_libjingle=1 build_with_chromium=0 OS=mac
+  target_arch=x64 libjingle_objc=1 libpeer_target_type=static_library
+  $GYP_DEFINES"
+- For iOS:
+  export GYP_DEFINES="build_with_libjingle=1 build_with_chromium=0 OS=ios
+  libjingle_enable_video=0 libjingle_objc=1 enable_video=0 $GYP_DEFINES"
+- Finally, run "gclient runhooks" to generate iOS or Mac targeting Xcode
+    projects.
+
+Example of building & using the app:
+
+cd <path/to/libjingle>/trunk/talk
+- Open libjingle.xcproj.  Select iPhone or iPad simulator and build everything.
+  Then switch to iOS device and build everything.  This creates x86 and ARM
+  archives.
+cd examples/ios
+./makeLibs.sh
+- This will generate fat archives containing both targets and copy them to
+  ./libs.
+- This step must be rerun every time you run gclient sync or build the API
+  libraries.
+- Open AppRTCDemo.xcodeproj, select your device or simulator and run.
+- If you have any problems deploying for the first time, check the project
+  properties to ensure that the Bundle Identifier matches your phone
+  provisioning profile.  Or use the simulator as it doesn't require a profile.
+
+- In desktop chrome, navigate to http://apprtc.appspot.com and note the r=<NNN>
+  room number in the resulting URL.
+
+- Enter that number into the text field on the phone.
+
+- Alternatively, you can background the app and launch Safari.  In Safari, open
+  the url apprtc://apprtc.appspot.com/?r=<NNN> where <NNN> is the room name.
+  Other options are to put the link in an email and send it to your self.
+  Clicking on it will launch AppRTCDemo and navigate to the room.
diff --git a/talk/app/webrtc/objc/RTCAudioTrack+Internal.h b/talk/app/webrtc/objc/RTCAudioTrack+Internal.h
new file mode 100644
index 0000000..17d2723
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCAudioTrack+Internal.h
@@ -0,0 +1,37 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCAudioTrack.h"
+
+#include "talk/app/webrtc/mediastreaminterface.h"
+
+@interface RTCAudioTrack (Internal)
+
+@property(nonatomic, assign, readonly)
+    talk_base::scoped_refptr<webrtc::AudioTrackInterface> audioTrack;
+
+@end
diff --git a/talk/app/webrtc/objc/RTCAudioTrack.mm b/talk/app/webrtc/objc/RTCAudioTrack.mm
new file mode 100644
index 0000000..8a56986
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCAudioTrack.mm
@@ -0,0 +1,45 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "RTCAudioTrack+internal.h"
+
+#import "RTCMediaStreamTrack+internal.h"
+
+@implementation RTCAudioTrack
+@end
+
+@implementation RTCAudioTrack (Internal)
+
+- (talk_base::scoped_refptr<webrtc::AudioTrackInterface>)audioTrack {
+  return static_cast<webrtc::AudioTrackInterface *>(self.mediaTrack.get());
+}
+
+@end
diff --git a/talk/app/webrtc/objc/RTCEnumConverter.h b/talk/app/webrtc/objc/RTCEnumConverter.h
new file mode 100644
index 0000000..0e83719
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCEnumConverter.h
@@ -0,0 +1,54 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCTypes.h"
+
+#include "talk/app/webrtc/peerconnectioninterface.h"
+
+@interface RTCEnumConverter : NSObject
+
++ (RTCICEConnectionState)convertIceConnectionStateToObjC:
+        (webrtc::PeerConnectionInterface::IceConnectionState)nativeState;
+
++ (RTCICEGatheringState)convertIceGatheringStateToObjC:
+        (webrtc::PeerConnectionInterface::IceGatheringState)nativeState;
+
++ (RTCSignalingState)convertSignalingStateToObjC:
+        (webrtc::PeerConnectionInterface::SignalingState)nativeState;
+
++ (RTCSourceState)convertSourceStateToObjC:
+        (webrtc::MediaSourceInterface::SourceState)nativeState;
+
++ (webrtc::MediaStreamTrackInterface::TrackState)convertTrackStateToNative:
+        (RTCTrackState)state;
+
++ (RTCTrackState)convertTrackStateToObjC:
+        (webrtc::MediaStreamTrackInterface::TrackState)nativeState;
+
+@end
diff --git a/talk/app/webrtc/objc/RTCEnumConverter.mm b/talk/app/webrtc/objc/RTCEnumConverter.mm
new file mode 100644
index 0000000..7c81c8d
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCEnumConverter.mm
@@ -0,0 +1,126 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCEnumConverter.h"
+
+#include "talk/app/webrtc/peerconnectioninterface.h"
+
+@implementation RTCEnumConverter
+
++ (RTCICEConnectionState)convertIceConnectionStateToObjC:
+    (webrtc::PeerConnectionInterface::IceConnectionState)nativeState {
+  switch (nativeState) {
+    case webrtc::PeerConnectionInterface::kIceConnectionNew:
+      return RTCICEConnectionNew;
+    case webrtc::PeerConnectionInterface::kIceConnectionChecking:
+      return RTCICEConnectionChecking;
+    case webrtc::PeerConnectionInterface::kIceConnectionConnected:
+      return RTCICEConnectionConnected;
+    case webrtc::PeerConnectionInterface::kIceConnectionCompleted:
+      return RTCICEConnectionCompleted;
+    case webrtc::PeerConnectionInterface::kIceConnectionFailed:
+      return RTCICEConnectionFailed;
+    case webrtc::PeerConnectionInterface::kIceConnectionDisconnected:
+      return RTCICEConnectionDisconnected;
+    case webrtc::PeerConnectionInterface::kIceConnectionClosed:
+      return RTCICEConnectionClosed;
+  }
+}
+
++ (RTCICEGatheringState)convertIceGatheringStateToObjC:
+    (webrtc::PeerConnectionInterface::IceGatheringState)nativeState {
+  switch (nativeState) {
+    case webrtc::PeerConnectionInterface::kIceGatheringNew:
+      return RTCICEGatheringNew;
+    case webrtc::PeerConnectionInterface::kIceGatheringGathering:
+      return RTCICEGatheringGathering;
+    case webrtc::PeerConnectionInterface::kIceGatheringComplete:
+      return RTCICEGatheringComplete;
+  }
+}
+
++ (RTCSignalingState)convertSignalingStateToObjC:
+    (webrtc::PeerConnectionInterface::SignalingState)nativeState {
+  switch (nativeState) {
+    case webrtc::PeerConnectionInterface::kStable:
+      return RTCSignalingStable;
+    case webrtc::PeerConnectionInterface::kHaveLocalOffer:
+      return RTCSignalingHaveLocalOffer;
+    case webrtc::PeerConnectionInterface::kHaveLocalPrAnswer:
+      return RTCSignalingHaveLocalPrAnswer;
+    case webrtc::PeerConnectionInterface::kHaveRemoteOffer:
+      return RTCSignalingHaveRemoteOffer;
+    case webrtc::PeerConnectionInterface::kHaveRemotePrAnswer:
+      return RTCSignalingHaveRemotePrAnswer;
+    case webrtc::PeerConnectionInterface::kClosed:
+      return RTCSignalingClosed;
+  }
+}
+
++ (RTCSourceState)convertSourceStateToObjC:
+    (webrtc::MediaSourceInterface::SourceState)nativeState {
+  switch (nativeState) {
+    case webrtc::MediaSourceInterface::kInitializing:
+      return RTCSourceStateInitializing;
+    case webrtc::MediaSourceInterface::kLive:
+      return RTCSourceStateLive;
+    case webrtc::MediaSourceInterface::kEnded:
+      return RTCSourceStateEnded;
+    case webrtc::MediaSourceInterface::kMuted:
+      return RTCSourceStateMuted;
+  }
+}
+
++ (webrtc::MediaStreamTrackInterface::TrackState)
+    convertTrackStateToNative:(RTCTrackState)state {
+  switch (state) {
+    case RTCTrackStateInitializing:
+      return webrtc::MediaStreamTrackInterface::kInitializing;
+    case RTCTrackStateLive:
+      return webrtc::MediaStreamTrackInterface::kLive;
+    case RTCTrackStateEnded:
+      return webrtc::MediaStreamTrackInterface::kEnded;
+    case RTCTrackStateFailed:
+      return webrtc::MediaStreamTrackInterface::kFailed;
+  }
+}
+
++ (RTCTrackState)convertTrackStateToObjC:
+    (webrtc::MediaStreamTrackInterface::TrackState)nativeState {
+  switch (nativeState) {
+    case webrtc::MediaStreamTrackInterface::kInitializing:
+      return RTCTrackStateInitializing;
+    case webrtc::MediaStreamTrackInterface::kLive:
+      return RTCTrackStateLive;
+    case webrtc::MediaStreamTrackInterface::kEnded:
+      return RTCTrackStateEnded;
+    case webrtc::MediaStreamTrackInterface::kFailed:
+      return RTCTrackStateFailed;
+  }
+}
+
+@end
diff --git a/talk/app/webrtc/objc/RTCI420Frame.mm b/talk/app/webrtc/objc/RTCI420Frame.mm
new file mode 100644
index 0000000..df84fc1
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCI420Frame.mm
@@ -0,0 +1,34 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCI420Frame.h"
+
+@implementation RTCI420Frame
+
+// TODO(hughv): Should this just be a cricket::VideoFrame wrapper object?
+
+@end
diff --git a/talk/app/webrtc/objc/RTCIceCandidate+Internal.h b/talk/app/webrtc/objc/RTCIceCandidate+Internal.h
new file mode 100644
index 0000000..e4964d4
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCIceCandidate+Internal.h
@@ -0,0 +1,39 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCICECandidate.h"
+
+#include "talk/app/webrtc/peerconnectioninterface.h"
+
+@interface RTCICECandidate (Internal)
+
+@property(nonatomic, assign, readonly) const
+    webrtc::IceCandidateInterface *candidate;
+
+- (id)initWithCandidate:(const webrtc::IceCandidateInterface *)candidate;
+
+@end
diff --git a/talk/app/webrtc/objc/RTCIceCandidate.mm b/talk/app/webrtc/objc/RTCIceCandidate.mm
new file mode 100644
index 0000000..63eac1d
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCIceCandidate.mm
@@ -0,0 +1,86 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "RTCICECandidate+internal.h"
+
+@implementation RTCICECandidate {
+  NSString *_sdpMid;
+  NSInteger _sdpMLineIndex;
+  NSString *_sdp;
+}
+
+- (id)initWithMid:(NSString *)sdpMid
+            index:(NSInteger)sdpMLineIndex
+              sdp:(NSString *)sdp {
+  if (!sdpMid || !sdp) {
+    NSAssert(NO, @"nil arguments not allowed");
+    return nil;
+  }
+  if ((self = [super init])) {
+    _sdpMid = [sdpMid copy];
+    _sdpMLineIndex = sdpMLineIndex;
+    _sdp = [sdp copy];
+  }
+  return self;
+}
+
+- (NSString *)description {
+  return [NSString stringWithFormat:@"%@:%ld:%@",
+          self.sdpMid,
+          (long)self.sdpMLineIndex,
+          self.sdp];
+}
+
+@end
+
+@implementation RTCICECandidate (Internal)
+
+- (id)initWithCandidate:(const webrtc::IceCandidateInterface *)candidate {
+  if ((self = [super init])) {
+    std::string sdp;
+    if (candidate->ToString(&sdp)) {
+      _sdpMid = @(candidate->sdp_mid().c_str());
+      _sdpMLineIndex = candidate->sdp_mline_index();
+      _sdp = @(sdp.c_str());
+    } else {
+      self = nil;
+      NSAssert(NO, @"ICECandidateInterface->ToString failed");
+    }
+  }
+  return self;
+}
+
+- (const webrtc::IceCandidateInterface *)candidate {
+  return webrtc::CreateIceCandidate(
+      [self.sdpMid UTF8String], self.sdpMLineIndex, [self.sdp UTF8String]);
+}
+
+@end
diff --git a/talk/app/webrtc/objc/RTCIceServer+Internal.h b/talk/app/webrtc/objc/RTCIceServer+Internal.h
new file mode 100644
index 0000000..c074294
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCIceServer+Internal.h
@@ -0,0 +1,37 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCICEServer.h"
+
+#include "talk/app/webrtc/peerconnectioninterface.h"
+
+@interface RTCICEServer (Internal)
+
+@property(nonatomic, assign, readonly)
+    webrtc::PeerConnectionInterface::IceServer iceServer;
+
+@end
diff --git a/talk/app/webrtc/objc/RTCIceServer.mm b/talk/app/webrtc/objc/RTCIceServer.mm
new file mode 100644
index 0000000..cb32673
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCIceServer.mm
@@ -0,0 +1,65 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "RTCICEServer+internal.h"
+
+@implementation RTCICEServer
+
+- (id)initWithURI:(NSURL *)URI password:(NSString *)password {
+  if (!URI || !password) {
+    NSAssert(NO, @"nil arguments not allowed");
+    self = nil;
+    return nil;
+  }
+  if ((self = [super init])) {
+    _URI = URI;
+    _password = [password copy];
+  }
+  return self;
+}
+
+- (NSString *)description {
+  return [NSString stringWithFormat:@"Server: [%@]\nPassword: [%@]",
+          [self.URI absoluteString], self.password];
+}
+
+@end
+
+@implementation RTCICEServer (Internal)
+
+- (webrtc::PeerConnectionInterface::IceServer)iceServer {
+  webrtc::PeerConnectionInterface::IceServer iceServer;
+  iceServer.uri = [[self.URI absoluteString] UTF8String];
+  iceServer.password = [self.password UTF8String];
+  return iceServer;
+}
+
+@end
diff --git a/talk/app/webrtc/objc/RTCMediaConstraints+Internal.h b/talk/app/webrtc/objc/RTCMediaConstraints+Internal.h
new file mode 100644
index 0000000..71a10c7
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCMediaConstraints+Internal.h
@@ -0,0 +1,40 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCMediaConstraints.h"
+
+#import "RTCMediaConstraintsNative.h"
+
+#include "talk/app/webrtc/mediastreaminterface.h"
+
+@interface RTCMediaConstraints (Internal)
+
+// Ownership is retained for the lifetime of this object.
+@property(nonatomic, assign, readonly) const
+    webrtc::RTCMediaConstraintsNative *constraints;
+
+@end
diff --git a/talk/app/webrtc/objc/RTCMediaConstraints.mm b/talk/app/webrtc/objc/RTCMediaConstraints.mm
new file mode 100644
index 0000000..fcb3b52
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCMediaConstraints.mm
@@ -0,0 +1,76 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "RTCMediaConstraints+internal.h"
+
+#import "RTCPair.h"
+
+#include "talk/base/scoped_ptr.h"
+
+// TODO(hughv):  Add accessors for mandatory and optional constraints.
+// TODO(hughv):  Add description.
+
+@implementation RTCMediaConstraints {
+  talk_base::scoped_ptr<webrtc::RTCMediaConstraintsNative> _constraints;
+  webrtc::MediaConstraintsInterface::Constraints _mandatory;
+  webrtc::MediaConstraintsInterface::Constraints _optional;
+}
+
+- (id)initWithMandatoryConstraints:(NSArray *)mandatory
+               optionalConstraints:(NSArray *)optional {
+  if ((self = [super init])) {
+    _mandatory = [[self class] constraintsFromArray:mandatory];
+    _optional = [[self class] constraintsFromArray:optional];
+    _constraints.reset(
+        new webrtc::RTCMediaConstraintsNative(_mandatory, _optional));
+  }
+  return self;
+}
+
++ (webrtc::MediaConstraintsInterface::Constraints)
+    constraintsFromArray:(NSArray *)array {
+  webrtc::MediaConstraintsInterface::Constraints constraints;
+  for (RTCPair *pair in array) {
+    constraints.push_back(webrtc::MediaConstraintsInterface::Constraint(
+        [pair.key UTF8String], [pair.value UTF8String]));
+  }
+  return constraints;
+}
+
+@end
+
+@implementation RTCMediaConstraints (internal)
+
+- (const webrtc::RTCMediaConstraintsNative *)constraints {
+  return _constraints.get();
+}
+
+@end
diff --git a/talk/app/webrtc/objc/RTCMediaConstraintsNative.cc b/talk/app/webrtc/objc/RTCMediaConstraintsNative.cc
new file mode 100644
index 0000000..ed06d18
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCMediaConstraintsNative.cc
@@ -0,0 +1,51 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/objc/RTCMediaConstraintsNative.h"
+
+namespace webrtc {
+
+RTCMediaConstraintsNative::~RTCMediaConstraintsNative() {}
+
+RTCMediaConstraintsNative::RTCMediaConstraintsNative() {}
+
+RTCMediaConstraintsNative::RTCMediaConstraintsNative(
+    const MediaConstraintsInterface::Constraints& mandatory,
+    const MediaConstraintsInterface::Constraints& optional)
+    : mandatory_(mandatory), optional_(optional) {}
+
+const MediaConstraintsInterface::Constraints&
+RTCMediaConstraintsNative::GetMandatory() const {
+  return mandatory_;
+}
+
+const MediaConstraintsInterface::Constraints&
+RTCMediaConstraintsNative::GetOptional() const {
+  return optional_;
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/objc/RTCMediaConstraintsNative.h b/talk/app/webrtc/objc/RTCMediaConstraintsNative.h
new file mode 100644
index 0000000..a5cd266
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCMediaConstraintsNative.h
@@ -0,0 +1,50 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_OBJC_RTCMEDIACONSTRAINTSNATIVE_H_
+#define TALK_APP_WEBRTC_OBJC_RTCMEDIACONSTRAINTSNATIVE_H_
+
+#include "talk/app/webrtc/mediaconstraintsinterface.h"
+
+namespace webrtc {
+class RTCMediaConstraintsNative : public MediaConstraintsInterface {
+ public:
+  virtual ~RTCMediaConstraintsNative();
+  RTCMediaConstraintsNative();
+  RTCMediaConstraintsNative(
+      const MediaConstraintsInterface::Constraints& mandatory,
+      const MediaConstraintsInterface::Constraints& optional);
+  virtual const Constraints& GetMandatory() const;
+  virtual const Constraints& GetOptional() const;
+
+ private:
+  MediaConstraintsInterface::Constraints mandatory_;
+  MediaConstraintsInterface::Constraints optional_;
+};
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_OBJC_RTCMEDIACONSTRAINTSNATIVE_H_
diff --git a/talk/app/webrtc/objc/RTCMediaSource+Internal.h b/talk/app/webrtc/objc/RTCMediaSource+Internal.h
new file mode 100644
index 0000000..98f8e9c
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCMediaSource+Internal.h
@@ -0,0 +1,40 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCMediaSource.h"
+
+#include "talk/app/webrtc/mediastreaminterface.h"
+
+@interface RTCMediaSource (Internal)
+
+@property(nonatomic, assign, readonly)
+    talk_base::scoped_refptr<webrtc::MediaSourceInterface> mediaSource;
+
+- (id)initWithMediaSource:
+        (talk_base::scoped_refptr<webrtc::MediaSourceInterface>)mediaSource;
+
+@end
diff --git a/talk/app/webrtc/objc/RTCMediaSource.mm b/talk/app/webrtc/objc/RTCMediaSource.mm
new file mode 100644
index 0000000..9331fd7
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCMediaSource.mm
@@ -0,0 +1,65 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "RTCMediaSource+internal.h"
+
+#import "RTCEnumConverter.h"
+
+@implementation RTCMediaSource {
+  talk_base::scoped_refptr<webrtc::MediaSourceInterface> _mediaSource;
+}
+
+- (RTCSourceState)state {
+  return [RTCEnumConverter convertSourceStateToObjC:self.mediaSource->state()];
+}
+
+@end
+
+@implementation RTCMediaSource (Internal)
+
+- (id)initWithMediaSource:
+        (talk_base::scoped_refptr<webrtc::MediaSourceInterface>)mediaSource {
+  if (!mediaSource) {
+    NSAssert(NO, @"nil arguments not allowed");
+    self = nil;
+    return nil;
+  }
+  if ((self = [super init])) {
+    _mediaSource = mediaSource;
+  }
+  return self;
+}
+
+- (talk_base::scoped_refptr<webrtc::MediaSourceInterface>)mediaSource {
+  return _mediaSource;
+}
+
+@end
diff --git a/talk/app/webrtc/objc/RTCMediaStream+Internal.h b/talk/app/webrtc/objc/RTCMediaStream+Internal.h
new file mode 100644
index 0000000..2123c2d
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCMediaStream+Internal.h
@@ -0,0 +1,40 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCMediaStream.h"
+
+#include "talk/app/webrtc/mediastreamtrack.h"
+
+@interface RTCMediaStream (Internal)
+
+@property(nonatomic, assign, readonly)
+    talk_base::scoped_refptr<webrtc::MediaStreamInterface> mediaStream;
+
+- (id)initWithMediaStream:
+        (talk_base::scoped_refptr<webrtc::MediaStreamInterface>)mediaStream;
+
+@end
diff --git a/talk/app/webrtc/objc/RTCMediaStream.mm b/talk/app/webrtc/objc/RTCMediaStream.mm
new file mode 100644
index 0000000..dd4aab6
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCMediaStream.mm
@@ -0,0 +1,145 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "RTCMediaStream+internal.h"
+
+#import "RTCAudioTrack+internal.h"
+#import "RTCMediaStreamTrack+internal.h"
+#import "RTCVideoTrack+internal.h"
+
+#include "talk/app/webrtc/mediastreaminterface.h"
+
+@implementation RTCMediaStream {
+  NSMutableArray *_audioTracks;
+  NSMutableArray *_videoTracks;
+  talk_base::scoped_refptr<webrtc::MediaStreamInterface> _mediaStream;
+}
+
+- (NSString *)description {
+  return [NSString stringWithFormat:@"[%@:A=%lu:V=%lu]",
+          [self label],
+          (unsigned long)[self.audioTracks count],
+          (unsigned long)[self.videoTracks count]];
+}
+
+- (NSArray *)audioTracks {
+  return [_audioTracks copy];
+}
+
+- (NSArray *)videoTracks {
+  return [_videoTracks copy];
+}
+
+- (NSString *)label {
+  return @(self.mediaStream->label().c_str());
+}
+
+- (BOOL)addAudioTrack:(RTCAudioTrack *)track {
+  if (self.mediaStream->AddTrack(track.audioTrack)) {
+    [_audioTracks addObject:track];
+    return YES;
+  }
+  return NO;
+}
+
+- (BOOL)addVideoTrack:(RTCVideoTrack *)track {
+  if (self.mediaStream->AddTrack(track.videoTrack)) {
+    [_videoTracks addObject:track];
+    return YES;
+  }
+  return NO;
+}
+
+- (BOOL)removeAudioTrack:(RTCAudioTrack *)track {
+  NSUInteger index = [_audioTracks indexOfObjectIdenticalTo:track];
+  NSAssert(index != NSNotFound,
+           @"|removeAudioTrack| called on unexpected RTCAudioTrack");
+  if (index != NSNotFound && self.mediaStream->RemoveTrack(track.audioTrack)) {
+    [_audioTracks removeObjectAtIndex:index];
+    return YES;
+  }
+  return NO;
+}
+
+- (BOOL)removeVideoTrack:(RTCVideoTrack *)track {
+  NSUInteger index = [_videoTracks indexOfObjectIdenticalTo:track];
+  NSAssert(index != NSNotFound,
+           @"|removeAudioTrack| called on unexpected RTCVideoTrack");
+  if (index != NSNotFound && self.mediaStream->RemoveTrack(track.videoTrack)) {
+    [_videoTracks removeObjectAtIndex:index];
+    return YES;
+  }
+  return NO;
+}
+
+@end
+
+@implementation RTCMediaStream (Internal)
+
+- (id)initWithMediaStream:
+        (talk_base::scoped_refptr<webrtc::MediaStreamInterface>)mediaStream {
+  if (!mediaStream) {
+    NSAssert(NO, @"nil arguments not allowed");
+    self = nil;
+    return nil;
+  }
+  if ((self = [super init])) {
+    webrtc::AudioTrackVector audio_tracks = mediaStream->GetAudioTracks();
+    webrtc::VideoTrackVector video_tracks = mediaStream->GetVideoTracks();
+
+    _audioTracks = [NSMutableArray arrayWithCapacity:audio_tracks.size()];
+    _videoTracks = [NSMutableArray arrayWithCapacity:video_tracks.size()];
+    _mediaStream = mediaStream;
+
+    for (size_t i = 0; i < audio_tracks.size(); ++i) {
+      talk_base::scoped_refptr<webrtc::AudioTrackInterface> track =
+          audio_tracks[i];
+      RTCAudioTrack *audioTrack =
+          [[RTCAudioTrack alloc] initWithMediaTrack:track];
+      [_audioTracks addObject:audioTrack];
+    }
+      // TODO(hughv): Add video.
+//    for (size_t i = 0; i < video_tracks.size(); ++i) {
+//      talk_base::scoped_refptr<webrtc::VideoTrackInterface> track =
+//          video_tracks[i];
+//      RTCVideoTrack *videoTrack =
+//          [[RTCVideoTrack alloc] initWithMediaTrack:track];
+//      [_videoTracks addObject:videoTrack];
+//    }
+  }
+  return self;
+}
+
+- (talk_base::scoped_refptr<webrtc::MediaStreamInterface>)mediaStream {
+  return _mediaStream;
+}
+
+@end
diff --git a/talk/app/webrtc/objc/RTCMediaStreamTrack+Internal.h b/talk/app/webrtc/objc/RTCMediaStreamTrack+Internal.h
new file mode 100644
index 0000000..9a0cab3
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCMediaStreamTrack+Internal.h
@@ -0,0 +1,40 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCMediaStreamTrack.h"
+
+#include "talk/app/webrtc/mediastreaminterface.h"
+
+@interface RTCMediaStreamTrack (Internal)
+
+@property(nonatomic, assign, readonly)
+    talk_base::scoped_refptr<webrtc::MediaStreamTrackInterface> mediaTrack;
+
+- (id)initWithMediaTrack:
+        (talk_base::scoped_refptr<webrtc::MediaStreamTrackInterface>)mediaTrack;
+
+@end
diff --git a/talk/app/webrtc/objc/RTCMediaStreamTrack.mm b/talk/app/webrtc/objc/RTCMediaStreamTrack.mm
new file mode 100644
index 0000000..6c8f715
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCMediaStreamTrack.mm
@@ -0,0 +1,103 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "RTCMediaStreamTrack+internal.h"
+#import "RTCEnumConverter.h"
+
+@implementation RTCMediaStreamTrack {
+  talk_base::scoped_refptr<webrtc::MediaStreamTrackInterface> _mediaTrack;
+}
+
+@synthesize label;
+
+- (BOOL)isEqual:(id)other {
+  // Equality is purely based on the label just like the C++ implementation.
+  if (self == other) return YES;
+  if (![other isKindOfClass:[self class]] ||
+      ![self isKindOfClass:[other class]]) {
+    return NO;
+  }
+  RTCMediaStreamTrack *otherMediaStream = (RTCMediaStreamTrack *)other;
+  return [self.label isEqual:otherMediaStream.label];
+}
+
+- (NSUInteger)hash {
+  return [self.label hash];
+}
+
+- (NSString *)kind {
+  return @(self.mediaTrack->kind().c_str());
+}
+
+- (NSString *)label {
+  return @(self.mediaTrack->id().c_str());
+}
+
+- (BOOL)isEnabled {
+  return self.mediaTrack->enabled();
+}
+
+- (BOOL)setEnabled:(BOOL)enabled {
+  return self.mediaTrack->set_enabled(enabled);
+}
+
+- (RTCTrackState)state {
+  return [RTCEnumConverter convertTrackStateToObjC:self.mediaTrack->state()];
+}
+
+- (BOOL)setState:(RTCTrackState)state {
+  return self.mediaTrack->set_state(
+      [RTCEnumConverter convertTrackStateToNative:state]);
+}
+
+@end
+
+@implementation RTCMediaStreamTrack (Internal)
+
+- (id)initWithMediaTrack:(
+    talk_base::scoped_refptr<webrtc::MediaStreamTrackInterface>)mediaTrack {
+  if (!mediaTrack) {
+    NSAssert(NO, @"nil arguments not allowed");
+    self = nil;
+    return nil;
+  }
+  if ((self = [super init])) {
+    _mediaTrack = mediaTrack;
+    label = @(mediaTrack->id().c_str());
+  }
+  return self;
+}
+
+- (talk_base::scoped_refptr<webrtc::MediaStreamTrackInterface>)mediaTrack {
+  return _mediaTrack;
+}
+
+@end
diff --git a/talk/app/webrtc/objc/RTCPair.m b/talk/app/webrtc/objc/RTCPair.m
new file mode 100644
index 0000000..ee2ba1b
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCPair.m
@@ -0,0 +1,40 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCPair.h"
+
+@implementation RTCPair
+
+- (id)initWithKey:(NSString *)key value:(NSString *)value {
+  if ((self = [super init])) {
+    _key = [key copy];
+    _value = [value copy];
+  }
+  return self;
+}
+
+@end
diff --git a/talk/app/webrtc/objc/RTCPeerConnection+Internal.h b/talk/app/webrtc/objc/RTCPeerConnection+Internal.h
new file mode 100644
index 0000000..d1b4639
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCPeerConnection+Internal.h
@@ -0,0 +1,44 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCPeerConnection.h"
+
+#import "RTCPeerConnectionDelegate.h"
+#import "RTCPeerConnectionObserver.h"
+
+#include "talk/app/webrtc/peerconnectioninterface.h"
+
+@interface RTCPeerConnection (Internal)
+
+@property(nonatomic, assign, readonly)
+    talk_base::scoped_refptr<webrtc::PeerConnectionInterface> peerConnection;
+
+- (id)initWithPeerConnection:(
+    talk_base::scoped_refptr<webrtc::PeerConnectionInterface>)peerConnection
+                    observer:(webrtc::RTCPeerConnectionObserver *)observer;
+
+@end
diff --git a/talk/app/webrtc/objc/RTCPeerConnection.mm b/talk/app/webrtc/objc/RTCPeerConnection.mm
new file mode 100644
index 0000000..73dce36
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCPeerConnection.mm
@@ -0,0 +1,247 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "RTCPeerConnection+internal.h"
+
+#import "RTCEnumConverter.h"
+#import "RTCICECandidate+internal.h"
+#import "RTCICEServer+internal.h"
+#import "RTCMediaConstraints+internal.h"
+#import "RTCMediaStream+internal.h"
+#import "RTCSessionDescription+internal.h"
+#import "RTCSessionDescriptonDelegate.h"
+#import "RTCSessionDescription.h"
+
+#include "talk/app/webrtc/jsep.h"
+
+NSString* const kRTCSessionDescriptionDelegateErrorDomain = @"RTCSDPError";
+int const kRTCSessionDescriptionDelegateErrorCode = -1;
+
+namespace webrtc {
+
+class RTCCreateSessionDescriptionObserver
+    : public CreateSessionDescriptionObserver {
+ public:
+  RTCCreateSessionDescriptionObserver(id<RTCSessionDescriptonDelegate> delegate,
+                                      RTCPeerConnection *peerConnection) {
+    _delegate = delegate;
+    _peerConnection = peerConnection;
+  }
+
+  virtual void OnSuccess(SessionDescriptionInterface *desc) OVERRIDE {
+    RTCSessionDescription *session =
+        [[RTCSessionDescription alloc] initWithSessionDescription:desc];
+    [_delegate peerConnection:_peerConnection
+        didCreateSessionDescription:session
+        error:nil];
+  }
+
+  virtual void OnFailure(const std::string &error) OVERRIDE {
+    NSString *str = @(error.c_str());
+    NSError *err =
+        [NSError errorWithDomain:kRTCSessionDescriptionDelegateErrorDomain
+                            code:kRTCSessionDescriptionDelegateErrorCode
+                        userInfo:@{ @"error" : str }];
+    [_delegate peerConnection:_peerConnection
+        didCreateSessionDescription:nil
+        error:err];
+  }
+
+ private:
+  id<RTCSessionDescriptonDelegate> _delegate;
+  RTCPeerConnection *_peerConnection;
+};
+
+class RTCSetSessionDescriptionObserver : public SetSessionDescriptionObserver {
+ public:
+  RTCSetSessionDescriptionObserver(id<RTCSessionDescriptonDelegate> delegate,
+                                   RTCPeerConnection *peerConnection) {
+    _delegate = delegate;
+    _peerConnection = peerConnection;
+  }
+
+  virtual void OnSuccess() OVERRIDE {
+    [_delegate peerConnection:_peerConnection
+        didSetSessionDescriptionWithError:nil];
+  }
+
+  virtual void OnFailure(const std::string &error) OVERRIDE {
+    NSString *str = @(error.c_str());
+    NSError *err =
+        [NSError errorWithDomain:kRTCSessionDescriptionDelegateErrorDomain
+                            code:kRTCSessionDescriptionDelegateErrorCode
+                        userInfo:@{ @"error" : str }];
+    [_delegate peerConnection:_peerConnection
+        didSetSessionDescriptionWithError:err];
+  }
+
+ private:
+  id<RTCSessionDescriptonDelegate> _delegate;
+  RTCPeerConnection *_peerConnection;
+};
+
+}
+
+@implementation RTCPeerConnection {
+  NSMutableArray *_localStreams;
+  talk_base::scoped_ptr<webrtc::RTCPeerConnectionObserver>_observer;
+  talk_base::scoped_refptr<webrtc::PeerConnectionInterface> _peerConnection;
+}
+
+- (BOOL)addICECandidate:(RTCICECandidate *)candidate {
+  const webrtc::IceCandidateInterface *iceCandidate = candidate.candidate;
+  return self.peerConnection->AddIceCandidate(iceCandidate);
+  delete iceCandidate;
+}
+
+- (BOOL)addStream:(RTCMediaStream *)stream
+      constraints:(RTCMediaConstraints *)constraints {
+  BOOL ret = self.peerConnection->AddStream(stream.mediaStream,
+                                            constraints.constraints);
+  if (!ret) {
+    return NO;
+  }
+  [_localStreams addObject:stream];
+  return YES;
+}
+
+- (void)createAnswerWithDelegate:(id<RTCSessionDescriptonDelegate>)delegate
+                     constraints:(RTCMediaConstraints *)constraints {
+  talk_base::scoped_refptr<webrtc::RTCCreateSessionDescriptionObserver>
+      observer(new talk_base::RefCountedObject<
+          webrtc::RTCCreateSessionDescriptionObserver>(delegate, self));
+  self.peerConnection->CreateAnswer(observer, constraints.constraints);
+}
+
+- (void)createOfferWithDelegate:(id<RTCSessionDescriptonDelegate>)delegate
+                    constraints:(RTCMediaConstraints *)constraints {
+  talk_base::scoped_refptr<webrtc::RTCCreateSessionDescriptionObserver>
+      observer(new talk_base::RefCountedObject<
+          webrtc::RTCCreateSessionDescriptionObserver>(delegate, self));
+  self.peerConnection->CreateOffer(observer, constraints.constraints);
+}
+
+- (void)removeStream:(RTCMediaStream *)stream {
+  self.peerConnection->RemoveStream(stream.mediaStream);
+  [_localStreams removeObject:stream];
+}
+
+- (void)
+    setLocalDescriptionWithDelegate:(id<RTCSessionDescriptonDelegate>)delegate
+                 sessionDescription:(RTCSessionDescription *)sdp {
+  talk_base::scoped_refptr<webrtc::RTCSetSessionDescriptionObserver> observer(
+      new talk_base::RefCountedObject<webrtc::RTCSetSessionDescriptionObserver>(
+          delegate, self));
+  self.peerConnection->SetLocalDescription(observer, sdp.sessionDescription);
+}
+
+- (void)
+    setRemoteDescriptionWithDelegate:(id<RTCSessionDescriptonDelegate>)delegate
+                  sessionDescription:(RTCSessionDescription *)sdp {
+  talk_base::scoped_refptr<webrtc::RTCSetSessionDescriptionObserver> observer(
+      new talk_base::RefCountedObject<webrtc::RTCSetSessionDescriptionObserver>(
+          delegate, self));
+  self.peerConnection->SetRemoteDescription(observer, sdp.sessionDescription);
+}
+
+- (BOOL)updateICEServers:(NSArray *)servers
+             constraints:(RTCMediaConstraints *)constraints {
+  webrtc::PeerConnectionInterface::IceServers iceServers;
+  for (RTCICEServer *server in servers) {
+    iceServers.push_back(server.iceServer);
+  }
+  return self.peerConnection->UpdateIce(iceServers, constraints.constraints);
+}
+
+- (RTCSessionDescription *)localDescription {
+  const webrtc::SessionDescriptionInterface *sdi =
+      self.peerConnection->local_description();
+  return sdi ?
+      [[RTCSessionDescription alloc] initWithSessionDescription:sdi] :
+      nil;
+}
+
+- (NSArray *)localStreams {
+  return [_localStreams copy];
+}
+
+- (RTCSessionDescription *)remoteDescription {
+  const webrtc::SessionDescriptionInterface *sdi =
+      self.peerConnection->remote_description();
+  return sdi ?
+      [[RTCSessionDescription alloc] initWithSessionDescription:sdi] :
+      nil;
+}
+
+- (RTCICEConnectionState)iceConnectionState {
+  return [RTCEnumConverter convertIceConnectionStateToObjC:
+      self.peerConnection->ice_connection_state()];
+}
+
+- (RTCICEGatheringState)iceGatheringState {
+  return [RTCEnumConverter convertIceGatheringStateToObjC:
+      self.peerConnection->ice_gathering_state()];
+}
+
+- (RTCSignalingState)signalingState {
+  return [RTCEnumConverter
+      convertSignalingStateToObjC:self.peerConnection->signaling_state()];
+}
+
+- (void)close {
+  self.peerConnection->Close();
+}
+
+@end
+
+@implementation RTCPeerConnection (Internal)
+
+- (id)initWithPeerConnection:(
+    talk_base::scoped_refptr<webrtc::PeerConnectionInterface>)peerConnection
+                    observer:(webrtc::RTCPeerConnectionObserver *)observer {
+  if (!peerConnection || !observer) {
+    NSAssert(NO, @"nil arguments not allowed");
+    self = nil;
+    return nil;
+  }
+  if ((self = [super init])) {
+    _peerConnection = peerConnection;
+    _localStreams = [[NSMutableArray alloc] init];
+    _observer.reset(observer);
+  }
+  return self;
+}
+
+- (talk_base::scoped_refptr<webrtc::PeerConnectionInterface>)peerConnection {
+  return _peerConnection;
+}
+
+@end
diff --git a/talk/app/webrtc/objc/RTCPeerConnectionFactory.mm b/talk/app/webrtc/objc/RTCPeerConnectionFactory.mm
new file mode 100644
index 0000000..b12af9d
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCPeerConnectionFactory.mm
@@ -0,0 +1,127 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "RTCPeerConnectionFactory.h"
+
+#include <vector>
+
+#import "RTCAudioTrack+internal.h"
+#import "RTCICEServer+internal.h"
+#import "RTCMediaConstraints+internal.h"
+#import "RTCMediaSource+internal.h"
+#import "RTCMediaStream+internal.h"
+#import "RTCMediaStreamTrack+internal.h"
+#import "RTCPeerConnection+internal.h"
+#import "RTCPeerConnectionDelegate.h"
+#import "RTCPeerConnectionObserver.h"
+#import "RTCVideoCapturer+internal.h"
+#import "RTCVideoSource+internal.h"
+#import "RTCVideoTrack+internal.h"
+
+#include "talk/app/webrtc/audiotrack.h"
+#include "talk/app/webrtc/mediastreaminterface.h"
+#include "talk/app/webrtc/peerconnectionfactory.h"
+#include "talk/app/webrtc/peerconnectioninterface.h"
+#include "talk/app/webrtc/videosourceinterface.h"
+#include "talk/app/webrtc/videotrack.h"
+#include "talk/base/logging.h"
+
+@interface RTCPeerConnectionFactory ()
+
+@property(nonatomic, assign) talk_base::scoped_refptr<
+    webrtc::PeerConnectionFactoryInterface> nativeFactory;
+
+@end
+
+@implementation RTCPeerConnectionFactory
+
+- (id)init {
+  if ((self = [super init])) {
+    _nativeFactory = webrtc::CreatePeerConnectionFactory();
+    NSAssert(_nativeFactory, @"Failed to initialize PeerConnectionFactory!");
+    // Uncomment to get sensitive logs emitted (to stderr or logcat).
+    // talk_base::LogMessage::LogToDebug(talk_base::LS_SENSITIVE);
+  }
+  return self;
+}
+
+- (RTCPeerConnection *)
+    peerConnectionWithICEServers:(NSArray *)servers
+                     constraints:(RTCMediaConstraints *)constraints
+                        delegate:(id<RTCPeerConnectionDelegate>)delegate {
+  webrtc::PeerConnectionInterface::IceServers iceServers;
+  for (RTCICEServer *server in servers) {
+    iceServers.push_back(server.iceServer);
+  }
+  webrtc::RTCPeerConnectionObserver *observer =
+      new webrtc::RTCPeerConnectionObserver(delegate);
+  talk_base::scoped_refptr<webrtc::PeerConnectionInterface> peerConnection =
+      self.nativeFactory->CreatePeerConnection(
+          iceServers, constraints.constraints, observer);
+  RTCPeerConnection *pc =
+      [[RTCPeerConnection alloc] initWithPeerConnection:peerConnection
+                                               observer:observer];
+  observer->SetPeerConnection(pc);
+  return pc;
+}
+
+- (RTCMediaStream *)mediaStreamWithLabel:(NSString *)label {
+  talk_base::scoped_refptr<webrtc::MediaStreamInterface> nativeMediaStream =
+      self.nativeFactory->CreateLocalMediaStream([label UTF8String]);
+  return [[RTCMediaStream alloc] initWithMediaStream:nativeMediaStream];
+}
+
+- (RTCVideoSource *)videoSourceWithCapturer:(RTCVideoCapturer *)capturer
+                                constraints:(RTCMediaConstraints *)constraints {
+  if (!capturer) {
+    return nil;
+  }
+  talk_base::scoped_refptr<webrtc::VideoSourceInterface> source =
+      self.nativeFactory->CreateVideoSource(capturer.capturer.get(),
+                                            constraints.constraints);
+  return [[RTCVideoSource alloc] initWithMediaSource:source];
+}
+
+- (RTCVideoTrack *)videoTrackWithID:(NSString *)videoId
+                             source:(RTCVideoSource *)source {
+  talk_base::scoped_refptr<webrtc::VideoTrackInterface> track =
+      self.nativeFactory->CreateVideoTrack([videoId UTF8String],
+                                           source.videoSource);
+  return [[RTCVideoTrack alloc] initWithMediaTrack:track];
+}
+
+- (RTCAudioTrack *)audioTrackWithID:(NSString *)audioId {
+  talk_base::scoped_refptr<webrtc::AudioTrackInterface> track =
+      self.nativeFactory->CreateAudioTrack([audioId UTF8String], NULL);
+  return [[RTCAudioTrack alloc] initWithMediaTrack:track];
+}
+
+@end
diff --git a/talk/app/webrtc/objc/RTCPeerConnectionObserver.h b/talk/app/webrtc/objc/RTCPeerConnectionObserver.h
new file mode 100644
index 0000000..c7d1ef8
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCPeerConnectionObserver.h
@@ -0,0 +1,79 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/peerconnectioninterface.h"
+
+#import "RTCPeerConnection.h"
+#import "RTCPeerConnectionDelegate.h"
+
+// These objects are created by RTCPeerConnectionFactory to wrap an
+// id<RTCPeerConnectionDelegate> and call methods on that interface.
+
+namespace webrtc {
+
+class RTCPeerConnectionObserver : public PeerConnectionObserver {
+
+ public:
+  explicit RTCPeerConnectionObserver(id<RTCPeerConnectionDelegate> delegate);
+
+  void SetPeerConnection(RTCPeerConnection *peerConnection);
+
+  virtual void OnError() OVERRIDE;
+
+  // Triggered when the SignalingState changed.
+  virtual void OnSignalingChange(
+      PeerConnectionInterface::SignalingState new_state) OVERRIDE;
+
+  // Triggered when media is received on a new stream from remote peer.
+  virtual void OnAddStream(MediaStreamInterface* stream) OVERRIDE;
+
+  // Triggered when a remote peer close a stream.
+  virtual void OnRemoveStream(MediaStreamInterface* stream) OVERRIDE;
+
+  // Triggered when a remote peer open a data channel.
+  virtual void OnDataChannel(DataChannelInterface* data_channel) OVERRIDE;
+
+  // Triggered when renegotation is needed, for example the ICE has restarted.
+  virtual void OnRenegotiationNeeded() OVERRIDE;
+
+  // Called any time the ICEConnectionState changes
+  virtual void OnIceConnectionChange(
+      PeerConnectionInterface::IceConnectionState new_state) OVERRIDE;
+
+  // Called any time the ICEGatheringState changes
+  virtual void OnIceGatheringChange(
+      PeerConnectionInterface::IceGatheringState new_state) OVERRIDE;
+
+  // New Ice candidate have been found.
+  virtual void OnIceCandidate(const IceCandidateInterface* candidate) OVERRIDE;
+
+ private:
+  id<RTCPeerConnectionDelegate> _delegate;
+  RTCPeerConnection *_peerConnection;
+};
+
+} // namespace webrtc
diff --git a/talk/app/webrtc/objc/RTCPeerConnectionObserver.mm b/talk/app/webrtc/objc/RTCPeerConnectionObserver.mm
new file mode 100644
index 0000000..e102bb9
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCPeerConnectionObserver.mm
@@ -0,0 +1,103 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "RTCPeerConnectionObserver.h"
+
+#import "RTCICECandidate+internal.h"
+#import "RTCMediaStream+internal.h"
+#import "RTCEnumConverter.h"
+
+namespace webrtc {
+
+RTCPeerConnectionObserver::RTCPeerConnectionObserver(
+    id<RTCPeerConnectionDelegate> delegate) {
+  _delegate = delegate;
+}
+
+void RTCPeerConnectionObserver::SetPeerConnection(
+    RTCPeerConnection *peerConnection) {
+  _peerConnection = peerConnection;
+}
+
+void RTCPeerConnectionObserver::OnError() {
+  [_delegate peerConnectionOnError:_peerConnection];
+}
+
+void RTCPeerConnectionObserver::OnSignalingChange(
+    PeerConnectionInterface::SignalingState new_state) {
+  [_delegate peerConnection:_peerConnection
+      signalingStateChanged:
+          [RTCEnumConverter convertSignalingStateToObjC:new_state]];
+}
+
+void RTCPeerConnectionObserver::OnAddStream(MediaStreamInterface* stream) {
+  RTCMediaStream* mediaStream =
+      [[RTCMediaStream alloc] initWithMediaStream:stream];
+  [_delegate peerConnection:_peerConnection addedStream:mediaStream];
+}
+
+void RTCPeerConnectionObserver::OnRemoveStream(MediaStreamInterface* stream) {
+  RTCMediaStream* mediaStream =
+      [[RTCMediaStream alloc] initWithMediaStream:stream];
+  [_delegate peerConnection:_peerConnection removedStream:mediaStream];
+}
+
+void RTCPeerConnectionObserver::OnDataChannel(
+    DataChannelInterface* data_channel) {
+  // TODO(hughv): Implement for future version.
+}
+
+void RTCPeerConnectionObserver::OnRenegotiationNeeded() {
+  [_delegate peerConnectionOnRenegotiationNeeded:_peerConnection];
+}
+
+void RTCPeerConnectionObserver::OnIceConnectionChange(
+    PeerConnectionInterface::IceConnectionState new_state) {
+  [_delegate peerConnection:_peerConnection
+       iceConnectionChanged:
+           [RTCEnumConverter convertIceConnectionStateToObjC:new_state]];
+}
+
+void RTCPeerConnectionObserver::OnIceGatheringChange(
+    PeerConnectionInterface::IceGatheringState new_state) {
+  [_delegate peerConnection:_peerConnection
+        iceGatheringChanged:
+            [RTCEnumConverter convertIceGatheringStateToObjC:new_state]];
+}
+
+void RTCPeerConnectionObserver::OnIceCandidate(
+    const IceCandidateInterface* candidate) {
+  RTCICECandidate* iceCandidate =
+      [[RTCICECandidate alloc] initWithCandidate:candidate];
+  [_delegate peerConnection:_peerConnection gotICECandidate:iceCandidate];
+}
+
+} // namespace webrtc
diff --git a/talk/app/webrtc/objc/RTCSessionDescription+Internal.h b/talk/app/webrtc/objc/RTCSessionDescription+Internal.h
new file mode 100644
index 0000000..261a176
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCSessionDescription+Internal.h
@@ -0,0 +1,41 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCSessionDescription.h"
+
+#include "talk/app/webrtc/jsep.h"
+#include "talk/app/webrtc/webrtcsession.h"
+
+@interface RTCSessionDescription (Internal)
+
+// Caller assumes ownership of this object!
+- (webrtc::SessionDescriptionInterface *)sessionDescription;
+
+- (id)initWithSessionDescription:
+    (const webrtc::SessionDescriptionInterface*)sessionDescription;
+
+@end
diff --git a/talk/app/webrtc/objc/RTCSessionDescription.mm b/talk/app/webrtc/objc/RTCSessionDescription.mm
new file mode 100644
index 0000000..4bd9b14
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCSessionDescription.mm
@@ -0,0 +1,81 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "RTCSessionDescription+internal.h"
+
+@implementation RTCSessionDescription {
+  NSString *_description;
+  NSString *_type;
+}
+
+- (id)initWithType:(NSString *)type sdp:(NSString *)sdp {
+  if (!type || !sdp) {
+    NSAssert(NO, @"nil arguments not allowed");
+    return nil;
+  }
+  if ((self = [super init])) {
+    _description = sdp;
+    _type = type;
+  }
+  return self;
+}
+
+@end
+
+@implementation RTCSessionDescription (Internal)
+
+- (id)initWithSessionDescription:
+        (const webrtc::SessionDescriptionInterface *)sessionDescription {
+  if (!sessionDescription) {
+    NSAssert(NO, @"nil arguments not allowed");
+    self = nil;
+    return nil;
+  }
+  if ((self = [super init])) {
+    const std::string &type = sessionDescription->type();
+    std::string sdp;
+    if (!sessionDescription->ToString(&sdp)) {
+      NSAssert(NO, @"Invalid SessionDescriptionInterface.");
+      self = nil;
+    } else {
+      _description = @(sdp.c_str());
+      _type = @(type.c_str());
+    }
+  }
+  return self;
+}
+
+- (webrtc::SessionDescriptionInterface *)sessionDescription {
+  return webrtc::CreateSessionDescription(
+      [self.type UTF8String], [self.description UTF8String], NULL);
+}
+
+@end
diff --git a/talk/app/webrtc/objc/RTCVideoCapturer+Internal.h b/talk/app/webrtc/objc/RTCVideoCapturer+Internal.h
new file mode 100644
index 0000000..d0d685b
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCVideoCapturer+Internal.h
@@ -0,0 +1,38 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCVideoCapturer.h"
+
+#include "talk/app/webrtc/videosourceinterface.h"
+
+@interface RTCVideoCapturer (Internal)
+
+@property(nonatomic, assign, readonly) const talk_base::scoped_ptr<cricket::VideoCapturer> &capturer;
+
+- (id)initWithCapturer:(cricket::VideoCapturer*)capturer;
+
+@end
diff --git a/talk/app/webrtc/objc/RTCVideoCapturer.mm b/talk/app/webrtc/objc/RTCVideoCapturer.mm
new file mode 100644
index 0000000..f7282c5
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCVideoCapturer.mm
@@ -0,0 +1,76 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "RTCVideoCapturer+internal.h"
+
+#include "talk/media/base/videocapturer.h"
+#include "talk/media/devices/devicemanager.h"
+
+@implementation RTCVideoCapturer {
+  talk_base::scoped_ptr<cricket::VideoCapturer>_capturer;
+}
+
++ (RTCVideoCapturer *)capturerWithDeviceName:(NSString *)deviceName {
+  const std::string &device_name = std::string([deviceName UTF8String]);
+  talk_base::scoped_ptr<cricket::DeviceManagerInterface> device_manager(
+      cricket::DeviceManagerFactory::Create());
+  bool initialized = device_manager->Init();
+  NSAssert(initialized, @"DeviceManager::Init() failed");
+  cricket::Device device;
+  if (!device_manager->GetVideoCaptureDevice(device_name, &device)) {
+    LOG(LS_ERROR) << "GetVideoCaptureDevice failed";
+    return 0;
+  }
+  talk_base::scoped_ptr<cricket::VideoCapturer> capturer(
+      device_manager->CreateVideoCapturer(device));
+  RTCVideoCapturer *rtcCapturer =
+      [[RTCVideoCapturer alloc] initWithCapturer:capturer.release()];
+  return rtcCapturer;
+}
+
+@end
+
+@implementation RTCVideoCapturer (Internal)
+
+- (id)initWithCapturer:(cricket::VideoCapturer *)capturer {
+  if ((self = [super init])) {
+    _capturer.reset(capturer);
+  }
+  return self;
+}
+
+// TODO(hughv):  When capturer is implemented, this needs to return
+// _capturer.release() instead.  For now, this isn't used.
+- (const talk_base::scoped_ptr<cricket::VideoCapturer> &)capturer {
+  return _capturer;
+}
+
+@end
diff --git a/talk/app/webrtc/objc/RTCVideoRenderer+Internal.h b/talk/app/webrtc/objc/RTCVideoRenderer+Internal.h
new file mode 100644
index 0000000..8854ed7
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCVideoRenderer+Internal.h
@@ -0,0 +1,40 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCVideoRenderer.h"
+
+#include "talk/app/webrtc/mediastreaminterface.h"
+
+@interface RTCVideoRenderer (Internal)
+
+// TODO(hughv): Use smart pointer.
+@property(nonatomic, assign, readonly)
+    webrtc::VideoRendererInterface *videoRenderer;
+
+- (id)initWithVideoRenderer:(webrtc::VideoRendererInterface *)videoRenderer;
+
+@end
diff --git a/talk/app/webrtc/objc/RTCVideoRenderer.mm b/talk/app/webrtc/objc/RTCVideoRenderer.mm
new file mode 100644
index 0000000..3d3b10e
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCVideoRenderer.mm
@@ -0,0 +1,72 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "RTCVideoRenderer+internal.h"
+
+#if TARGET_OS_IPHONE
+#import <UIKit/UIKit.h>
+#endif
+
+#import "RTCI420Frame.h"
+#import "RTCVideoRendererDelegate.h"
+
+@implementation RTCVideoRenderer
+
++ (RTCVideoRenderer *)videoRenderGUIWithFrame:(CGRect)frame {
+  // TODO (hughv): Implement.
+  return nil;
+}
+
+- (id)initWithDelegate:(id<RTCVideoRendererDelegate>)delegate {
+  if ((self = [super init])) {
+    _delegate = delegate;
+    // TODO (hughv): Create video renderer.
+  }
+  return self;
+}
+
+@end
+
+@implementation RTCVideoRenderer (Internal)
+
+- (id)initWithVideoRenderer:(webrtc::VideoRendererInterface *)videoRenderer {
+  if ((self = [super init])) {
+    // TODO (hughv): Implement.
+  }
+  return self;
+}
+
+- (webrtc::VideoRendererInterface *)videoRenderer {
+  // TODO (hughv): Implement.
+  return NULL;
+}
+
+@end
diff --git a/talk/app/webrtc/objc/RTCVideoSource+Internal.h b/talk/app/webrtc/objc/RTCVideoSource+Internal.h
new file mode 100644
index 0000000..1d3c4c9
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCVideoSource+Internal.h
@@ -0,0 +1,37 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCVideoSource.h"
+
+#include "talk/app/webrtc/videosourceinterface.h"
+
+@interface RTCVideoSource (Internal)
+
+@property(nonatomic, assign, readonly)
+    talk_base::scoped_refptr<webrtc::VideoSourceInterface>videoSource;
+
+@end
diff --git a/talk/app/webrtc/objc/RTCVideoSource.mm b/talk/app/webrtc/objc/RTCVideoSource.mm
new file mode 100644
index 0000000..c28fa9b
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCVideoSource.mm
@@ -0,0 +1,44 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "RTCVideoSource+internal.h"
+#import "RTCMediaSource+internal.h"
+
+@implementation RTCVideoSource
+@end
+
+@implementation RTCVideoSource (Internal)
+
+- (talk_base::scoped_refptr<webrtc::VideoSourceInterface>)videoSource {
+  return static_cast<webrtc::VideoSourceInterface *>(self.mediaSource.get());
+}
+
+@end
diff --git a/talk/app/webrtc/objc/RTCVideoTrack+Internal.h b/talk/app/webrtc/objc/RTCVideoTrack+Internal.h
new file mode 100644
index 0000000..b5da54b
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCVideoTrack+Internal.h
@@ -0,0 +1,40 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCVideoTrack.h"
+
+#include "talk/app/webrtc/mediastreaminterface.h"
+#include "talk/app/webrtc/peerconnectioninterface.h"
+
+@class RTCVideoRenderer;
+
+@interface RTCVideoTrack (Internal)
+
+@property(nonatomic, assign, readonly)
+    talk_base::scoped_refptr<webrtc::VideoTrackInterface> videoTrack;
+
+@end
diff --git a/talk/app/webrtc/objc/RTCVideoTrack.mm b/talk/app/webrtc/objc/RTCVideoTrack.mm
new file mode 100644
index 0000000..88f7226
--- /dev/null
+++ b/talk/app/webrtc/objc/RTCVideoTrack.mm
@@ -0,0 +1,77 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "RTCVideoTrack+internal.h"
+
+#import "RTCMediaStreamTrack+internal.h"
+#import "RTCVideoRenderer+internal.h"
+
+@implementation RTCVideoTrack {
+  NSMutableArray *_rendererArray;
+}
+
+- (id)initWithMediaTrack:(
+    talk_base::scoped_refptr<webrtc::MediaStreamTrackInterface>)mediaTrack {
+  if (self = [super initWithMediaTrack:mediaTrack]) {
+    _rendererArray = [NSMutableArray array];
+  }
+  return self;
+}
+
+- (void)addRenderer:(RTCVideoRenderer *)renderer {
+  NSAssert1(![self.renderers containsObject:renderer],
+            @"renderers already contains object [%@]",
+            [renderer description]);
+  [_rendererArray addObject:renderer];
+  self.videoTrack->AddRenderer(renderer.videoRenderer);
+}
+
+- (void)removeRenderer:(RTCVideoRenderer *)renderer {
+  NSUInteger index = [self.renderers indexOfObjectIdenticalTo:renderer];
+  if (index != NSNotFound) {
+    [_rendererArray removeObjectAtIndex:index];
+    self.videoTrack->RemoveRenderer(renderer.videoRenderer);
+  }
+}
+
+- (NSArray *)renderers {
+  return [_rendererArray copy];
+}
+
+@end
+
+@implementation RTCVideoTrack (Internal)
+
+- (talk_base::scoped_refptr<webrtc::VideoTrackInterface>)videoTrack {
+  return static_cast<webrtc::VideoTrackInterface *>(self.mediaTrack.get());
+}
+
+@end
diff --git a/talk/app/webrtc/objc/public/RTCAudioSource.h b/talk/app/webrtc/objc/public/RTCAudioSource.h
new file mode 100644
index 0000000..e357620
--- /dev/null
+++ b/talk/app/webrtc/objc/public/RTCAudioSource.h
@@ -0,0 +1,40 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCMediaSource.h"
+
+// RTCAudioSource is an ObjectiveC wrapper for AudioSourceInterface.  It is
+// used as the source for one or more RTCAudioTrack objects.
+@interface RTCAudioSource : RTCMediaSource
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+// Disallow init and don't add to documentation
+- (id)init __attribute__(
+    (unavailable("init is not a supported initializer for this class.")));
+#endif /* DOXYGEN_SHOULD_SKIP_THIS */
+
+@end
diff --git a/talk/app/webrtc/objc/public/RTCAudioTrack.h b/talk/app/webrtc/objc/public/RTCAudioTrack.h
new file mode 100644
index 0000000..e6aae13
--- /dev/null
+++ b/talk/app/webrtc/objc/public/RTCAudioTrack.h
@@ -0,0 +1,39 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCMediaStreamTrack.h"
+
+// RTCAudioTrack is an ObjectiveC wrapper for AudioTrackInterface.
+@interface RTCAudioTrack : RTCMediaStreamTrack
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+// Disallow init and don't add to documentation
+- (id)init __attribute__(
+    (unavailable("init is not a supported initializer for this class.")));
+#endif /* DOXYGEN_SHOULD_SKIP_THIS */
+
+@end
diff --git a/talk/app/webrtc/objc/public/RTCI420Frame.h b/talk/app/webrtc/objc/public/RTCI420Frame.h
new file mode 100644
index 0000000..bf58085
--- /dev/null
+++ b/talk/app/webrtc/objc/public/RTCI420Frame.h
@@ -0,0 +1,36 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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>
+
+// RTCI420Frame is an ObjectiveC version of cricket::VideoFrame.
+@interface RTCI420Frame : NSObject
+
+// TODO(hughv): Implement this when iOS VP8 is ready.
+
+@end
+
diff --git a/talk/app/webrtc/objc/public/RTCIceCandidate.h b/talk/app/webrtc/objc/public/RTCIceCandidate.h
new file mode 100644
index 0000000..f3f2c161
--- /dev/null
+++ b/talk/app/webrtc/objc/public/RTCIceCandidate.h
@@ -0,0 +1,56 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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>
+
+// RTCICECandidate contains an instance of ICECandidateInterface.
+@interface RTCICECandidate : NSObject
+
+// If present, this contains the identifier of the "media stream
+// identification" as defined in [RFC 3388] for m-line this candidate is
+// associated with.
+@property(nonatomic, copy, readonly) NSString *sdpMid;
+
+// This indicates the index (starting at zero) of m-line in the SDP this
+// candidate is associated with.
+@property(nonatomic, assign, readonly) NSInteger sdpMLineIndex;
+
+// Creates an SDP-ized form of this candidate.
+@property(nonatomic, copy, readonly) NSString *sdp;
+
+// Creates an ICECandidateInterface based on SDP string.
+- (id)initWithMid:(NSString *)sdpMid
+            index:(NSInteger)sdpMLineIndex
+              sdp:(NSString *)sdp;
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+// Disallow init and don't add to documentation
+- (id)init __attribute__(
+    (unavailable("init is not a supported initializer for this class.")));
+#endif /* DOXYGEN_SHOULD_SKIP_THIS */
+
+@end
diff --git a/talk/app/webrtc/objc/public/RTCIceServer.h b/talk/app/webrtc/objc/public/RTCIceServer.h
new file mode 100644
index 0000000..01ad9b5
--- /dev/null
+++ b/talk/app/webrtc/objc/public/RTCIceServer.h
@@ -0,0 +1,48 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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>
+
+// RTCICEServer allows for the creation of ICEServer structs.
+@interface RTCICEServer : NSObject
+
+// The server URI.
+@property(nonatomic, strong, readonly) NSURL *URI;
+
+// The server password.
+@property(nonatomic, copy, readonly) NSString *password;
+
+// Initializer for RTCICEServer taking uri and password.
+- (id)initWithURI:(NSString *)URI password:(NSString *)password;
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+// Disallow init and don't add to documentation
+- (id)init __attribute__(
+    (unavailable("init is not a supported initializer for this class.")));
+#endif /* DOXYGEN_SHOULD_SKIP_THIS */
+
+@end
diff --git a/talk/app/webrtc/objc/public/RTCMediaConstraints.h b/talk/app/webrtc/objc/public/RTCMediaConstraints.h
new file mode 100644
index 0000000..89d2c3b
--- /dev/null
+++ b/talk/app/webrtc/objc/public/RTCMediaConstraints.h
@@ -0,0 +1,39 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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>
+
+// RTCMediaConstraints contains the media constraints to be used in
+// RTCPeerConnection and RTCMediaStream.
+@interface RTCMediaConstraints : NSObject
+
+// Initializer for RTCMediaConstraints.  The parameters mandatory and optional
+// contain RTCPair objects with key/value for each constrant.
+- (id)initWithMandatoryConstraints:(NSArray *)mandatory
+               optionalConstraints:(NSArray *)optional;
+
+@end
diff --git a/talk/app/webrtc/objc/public/RTCMediaSource.h b/talk/app/webrtc/objc/public/RTCMediaSource.h
new file mode 100644
index 0000000..be3ad32
--- /dev/null
+++ b/talk/app/webrtc/objc/public/RTCMediaSource.h
@@ -0,0 +1,44 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCTypes.h"
+
+// RTCMediaSource is an ObjectiveC wrapper for MediaSourceInterface
+@interface RTCMediaSource : NSObject
+
+// The current state of the RTCMediaSource.
+@property (nonatomic, assign, readonly)RTCSourceState state;
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+// Disallow init and don't add to documentation
+- (id)init __attribute__(
+    (unavailable("init is not a supported initializer for this class.")));
+#endif /* DOXYGEN_SHOULD_SKIP_THIS */
+
+@end
diff --git a/talk/app/webrtc/objc/public/RTCMediaStream.h b/talk/app/webrtc/objc/public/RTCMediaStream.h
new file mode 100644
index 0000000..cd10321
--- /dev/null
+++ b/talk/app/webrtc/objc/public/RTCMediaStream.h
@@ -0,0 +1,51 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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>
+
+@class RTCAudioTrack;
+@class RTCVideoTrack;
+
+// RTCMediaStream is an ObjectiveC wrapper for MediaStreamInterface.
+@interface RTCMediaStream : NSObject
+
+@property(nonatomic, strong, readonly) NSArray *audioTracks;
+@property(nonatomic, strong, readonly) NSArray *videoTracks;
+@property(nonatomic, strong, readonly) NSString *label;
+
+- (BOOL)addAudioTrack:(RTCAudioTrack *)track;
+- (BOOL)addVideoTrack:(RTCVideoTrack *)track;
+- (BOOL)removeAudioTrack:(RTCAudioTrack *)track;
+- (BOOL)removeVideoTrack:(RTCVideoTrack *)track;
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+// Disallow init and don't add to documentation
+- (id)init __attribute__(
+    (unavailable("init is not a supported initializer for this class.")));
+#endif /* DOXYGEN_SHOULD_SKIP_THIS */
+
+@end
diff --git a/talk/app/webrtc/objc/public/RTCMediaStreamTrack.h b/talk/app/webrtc/objc/public/RTCMediaStreamTrack.h
new file mode 100644
index 0000000..f8f9369
--- /dev/null
+++ b/talk/app/webrtc/objc/public/RTCMediaStreamTrack.h
@@ -0,0 +1,51 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCTypes.h"
+
+// RTCMediaStreamTrack implements the interface common to RTCAudioTrack and
+// RTCVideoTrack.  Do not create an instance of this class, rather create one
+// of the derived classes.
+@interface RTCMediaStreamTrack : NSObject
+
+@property(nonatomic, assign, readonly) NSString *kind;
+@property(nonatomic, assign, readonly) NSString *label;
+
+- (BOOL)isEnabled;
+- (BOOL)setEnabled:(BOOL)enabled;
+- (RTCTrackState)state;
+- (BOOL)setState:(RTCTrackState)state;
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+// Disallow init and don't add to documentation
+- (id)init __attribute__(
+    (unavailable("init is not a supported initializer for this class.")));
+#endif /* DOXYGEN_SHOULD_SKIP_THIS */
+
+@end
diff --git a/talk/app/webrtc/objc/public/RTCPair.h b/talk/app/webrtc/objc/public/RTCPair.h
new file mode 100644
index 0000000..bb57e02
--- /dev/null
+++ b/talk/app/webrtc/objc/public/RTCPair.h
@@ -0,0 +1,45 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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>
+
+// A class to hold a key and value.
+@interface RTCPair : NSObject
+
+@property(nonatomic, strong, readonly) NSString *key;
+@property(nonatomic, strong, readonly) NSString *value;
+
+// Initialize a RTCPair object with a key and value.
+- (id)initWithKey:(NSString *)key value:(NSString *)value;
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+// Disallow init and don't add to documentation
+- (id)init __attribute__(
+    (unavailable("init is not a supported initializer for this class.")));
+#endif /* DOXYGEN_SHOULD_SKIP_THIS */
+
+@end
diff --git a/talk/app/webrtc/objc/public/RTCPeerConnection.h b/talk/app/webrtc/objc/public/RTCPeerConnection.h
new file mode 100644
index 0000000..c66bac8
--- /dev/null
+++ b/talk/app/webrtc/objc/public/RTCPeerConnection.h
@@ -0,0 +1,110 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCPeerConnectionDelegate.h"
+
+@class RTCICECandidate;
+@class RTCICEServers;
+@class RTCMediaConstraints;
+@class RTCMediaStream;
+@class RTCSessionDescription;
+@protocol RTCSessionDescriptonDelegate;
+
+// RTCPeerConnection is an ObjectiveC friendly wrapper around a PeerConnection
+// object.  See the documentation in talk/app/webrtc/peerconnectioninterface.h.
+// or http://www.webrtc.org/reference/native-apis, which in turn is inspired by
+// the JS APIs: http://dev.w3.org/2011/webrtc/editor/webrtc.html and
+// http://www.w3.org/TR/mediacapture-streams/
+@interface RTCPeerConnection : NSObject
+
+// Accessor methods to active local streams.
+@property(nonatomic, strong, readonly) NSArray *localStreams;
+
+// The local description.
+@property(nonatomic, assign, readonly) RTCSessionDescription *localDescription;
+
+// The remote description.
+@property(nonatomic, assign, readonly) RTCSessionDescription *remoteDescription;
+
+// The current signaling state.
+@property(nonatomic, assign, readonly) RTCSignalingState signalingState;
+@property(nonatomic, assign, readonly) RTCICEConnectionState iceConnectionState;
+@property(nonatomic, assign, readonly) RTCICEGatheringState iceGatheringState;
+
+// Add a new MediaStream to be sent on this PeerConnection.
+// Note that a SessionDescription negotiation is needed before the
+// remote peer can receive the stream.
+- (BOOL)addStream:(RTCMediaStream *)stream
+      constraints:(RTCMediaConstraints *)constraints;
+
+// Remove a MediaStream from this PeerConnection.
+// Note that a SessionDescription negotiation is need before the
+// remote peer is notified.
+- (void)removeStream:(RTCMediaStream *)stream;
+
+// Create a new offer.
+// Success or failure will be reported via RTCSessionDescriptonDelegate.
+- (void)createOfferWithDelegate:(id<RTCSessionDescriptonDelegate>)delegate
+                    constraints:(RTCMediaConstraints *)constraints;
+
+// Create an answer to an offer.
+// Success or failure will be reported via RTCSessionDescriptonDelegate.
+- (void)createAnswerWithDelegate:(id<RTCSessionDescriptonDelegate>)delegate
+                     constraints:(RTCMediaConstraints *)constraints;
+
+// Sets the local session description.
+// Success or failure will be reported via RTCSessionDescriptonDelegate.
+- (void)
+    setLocalDescriptionWithDelegate:(id<RTCSessionDescriptonDelegate>)delegate
+                 sessionDescription:(RTCSessionDescription *)sdp;
+
+// Sets the remote session description.
+// Success or failure will be reported via RTCSessionDescriptonDelegate.
+- (void)
+    setRemoteDescriptionWithDelegate:(id<RTCSessionDescriptonDelegate>)delegate
+                  sessionDescription:(RTCSessionDescription *)sdp;
+
+// Restarts or updates the ICE Agent process of gathering local candidates
+// and pinging remote candidates.
+- (BOOL)updateICEServers:(NSArray *)servers
+             constraints:(RTCMediaConstraints *)constraints;
+
+// Provides a remote candidate to the ICE Agent.
+- (BOOL)addICECandidate:(RTCICECandidate *)candidate;
+
+// Terminates all media and closes the transport.
+- (void)close;
+
+// TODO(hughv): Implement GetStats.
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+// Disallow init and don't add to documentation
+- (id)init __attribute__(
+    (unavailable("init is not a supported initializer for this class.")));
+#endif /* DOXYGEN_SHOULD_SKIP_THIS */
+
+@end
diff --git a/talk/app/webrtc/objc/public/RTCPeerConnectionDelegate.h b/talk/app/webrtc/objc/public/RTCPeerConnectionDelegate.h
new file mode 100644
index 0000000..b3bb881
--- /dev/null
+++ b/talk/app/webrtc/objc/public/RTCPeerConnectionDelegate.h
@@ -0,0 +1,70 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCTypes.h"
+
+@class RTCICECandidate;
+@class RTCMediaStream;
+@class RTCPeerConnection;
+
+// RTCPeerConnectionDelegate is a protocol for an object that must be
+// implemented to get messages from PeerConnection.
+@protocol RTCPeerConnectionDelegate<NSObject>
+
+// Triggered when there is an error.
+- (void)peerConnectionOnError:(RTCPeerConnection *)peerConnection;
+
+// Triggered when the SignalingState changed.
+- (void)peerConnection:(RTCPeerConnection *)peerConnection
+ signalingStateChanged:(RTCSignalingState)stateChanged;
+
+// Triggered when media is received on a new stream from remote peer.
+- (void)peerConnection:(RTCPeerConnection *)peerConnection
+           addedStream:(RTCMediaStream *)stream;
+
+// Triggered when a remote peer close a stream.
+- (void)peerConnection:(RTCPeerConnection *)peerConnection
+        removedStream:(RTCMediaStream *)stream;
+
+// Triggered when renegotation is needed, for example the ICE has restarted.
+- (void)peerConnectionOnRenegotiationNeeded:(RTCPeerConnection *)peerConnection;
+
+// Called any time the ICEConnectionState changes.
+- (void)peerConnection:(RTCPeerConnection *)peerConnection
+  iceConnectionChanged:(RTCICEConnectionState)newState;
+
+// Called any time the ICEGatheringState changes.
+- (void)peerConnection:(RTCPeerConnection *)peerConnection
+   iceGatheringChanged:(RTCICEGatheringState)newState;
+
+// New Ice candidate have been found.
+- (void)peerConnection:(RTCPeerConnection *)peerConnection
+        gotICECandidate:(RTCICECandidate *)candidate;
+
+@end
diff --git a/talk/app/webrtc/objc/public/RTCPeerConnectionFactory.h b/talk/app/webrtc/objc/public/RTCPeerConnectionFactory.h
new file mode 100644
index 0000000..0f48299
--- /dev/null
+++ b/talk/app/webrtc/objc/public/RTCPeerConnectionFactory.h
@@ -0,0 +1,67 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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>
+
+@class RTCAudioTrack;
+@class RTCMediaConstraints;
+@class RTCMediaStream;
+@class RTCPeerConnection;
+@class RTCVideoCapturer;
+@class RTCVideoSource;
+@class RTCVideoTrack;
+@protocol RTCPeerConnectionDelegate;
+
+// RTCPeerConnectionFactory is an ObjectiveC wrapper for PeerConnectionFactory.
+// It is the main entry point to the PeerConnection API for clients.
+@interface RTCPeerConnectionFactory : NSObject
+
+// Create an RTCPeerConnection object.   RTCPeerConnectionFactory will create
+// required libjingle threads, socket and network manager factory classes for
+// networking.
+- (RTCPeerConnection *)
+    peerConnectionWithICEServers:(NSArray *)servers
+                     constraints:(RTCMediaConstraints *)constraints
+                        delegate:(id<RTCPeerConnectionDelegate>)delegate;
+
+// Create an RTCMediaStream named |label|.
+- (RTCMediaStream *)mediaStreamWithLabel:(NSString *)label;
+
+// Creates a RTCVideoSource. The new source takes ownership of |capturer|.
+// |constraints| decides video resolution and frame rate but can be NULL.
+- (RTCVideoSource *)videoSourceWithCapturer:(RTCVideoCapturer *)capturer
+                                constraints:(RTCMediaConstraints *)constraints;
+
+// Creates a new local VideoTrack. The same |source| can be used in several
+// tracks.
+- (RTCVideoTrack *)videoTrackWithID:(NSString *)videoId
+                             source:(RTCVideoSource *)source;
+
+// Creates an new AudioTrack.
+- (RTCAudioTrack *)audioTrackWithID:(NSString *)audioId;
+
+@end
diff --git a/talk/app/webrtc/objc/public/RTCSessionDescription.h b/talk/app/webrtc/objc/public/RTCSessionDescription.h
new file mode 100644
index 0000000..ffe8fbe
--- /dev/null
+++ b/talk/app/webrtc/objc/public/RTCSessionDescription.h
@@ -0,0 +1,50 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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>
+
+// Description of an RFC 4566 Session.
+// RTCSessionDescription is an ObjectiveC wrapper for
+// SessionDescriptionInterface.
+@interface RTCSessionDescription : NSObject
+
+// The SDP description.
+@property(nonatomic, copy, readonly) NSString *description;
+
+// The session type.
+@property(nonatomic, copy, readonly) NSString *type;
+
+- (id)initWithType:(NSString *)type sdp:(NSString *)sdp;
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+// Disallow init and don't add to documentation
+- (id)init __attribute__(
+    (unavailable("init is not a supported initializer for this class.")));
+#endif /* DOXYGEN_SHOULD_SKIP_THIS */
+
+@end
+
diff --git a/talk/app/webrtc/objc/public/RTCSessionDescriptonDelegate.h b/talk/app/webrtc/objc/public/RTCSessionDescriptonDelegate.h
new file mode 100644
index 0000000..409aaee
--- /dev/null
+++ b/talk/app/webrtc/objc/public/RTCSessionDescriptonDelegate.h
@@ -0,0 +1,49 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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>
+
+@class RTCPeerConnection;
+@class RTCSessionDescription;
+
+extern NSString* const kRTCSessionDescriptionDelegateErrorDomain;
+extern int const kRTCSessionDescriptionDelegateErrorCode;
+
+// RTCSessionDescriptonDelegate is a protocol for listening to callback messages
+// when RTCSessionDescriptions are created or set.
+@protocol RTCSessionDescriptonDelegate<NSObject>
+
+// Called when creating a session.
+- (void)peerConnection:(RTCPeerConnection *)peerConnection
+    didCreateSessionDescription:(RTCSessionDescription *)sdp
+                          error:(NSError *)error;
+
+// Called when setting a local or remote description.
+- (void)peerConnection:(RTCPeerConnection *)peerConnection
+    didSetSessionDescriptionWithError:(NSError *)error;
+
+@end
diff --git a/talk/app/webrtc/objc/public/RTCTypes.h b/talk/app/webrtc/objc/public/RTCTypes.h
new file mode 100644
index 0000000..8ff8bf4
--- /dev/null
+++ b/talk/app/webrtc/objc/public/RTCTypes.h
@@ -0,0 +1,72 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Enums that are common to the ObjectiveC version of the PeerConnection API.
+
+// RTCICEConnectionState correspond to the states in webrtc::ICEConnectionState.
+typedef enum {
+  RTCICEConnectionNew,
+  RTCICEConnectionChecking,
+  RTCICEConnectionConnected,
+  RTCICEConnectionCompleted,
+  RTCICEConnectionFailed,
+  RTCICEConnectionDisconnected,
+  RTCICEConnectionClosed,
+} RTCICEConnectionState;
+
+// RTCICEGatheringState the states in webrtc::ICEGatheringState.
+typedef enum {
+  RTCICEGatheringNew,
+  RTCICEGatheringGathering,
+  RTCICEGatheringComplete,
+} RTCICEGatheringState;
+
+// RTCSignalingState correspond to the states in webrtc::SignalingState.
+typedef enum {
+  RTCSignalingStable,
+  RTCSignalingHaveLocalOffer,
+  RTCSignalingHaveLocalPrAnswer,
+  RTCSignalingHaveRemoteOffer,
+  RTCSignalingHaveRemotePrAnswer,
+  RTCSignalingClosed,
+} RTCSignalingState;
+
+// RTCSourceState corresponds to the states in webrtc::SourceState.
+typedef enum {
+  RTCSourceStateInitializing,
+  RTCSourceStateLive,
+  RTCSourceStateEnded,
+  RTCSourceStateMuted,
+} RTCSourceState;
+
+// RTCTrackState corresponds to the states in webrtc::TrackState.
+typedef enum {
+  RTCTrackStateInitializing,
+  RTCTrackStateLive,
+  RTCTrackStateEnded,
+  RTCTrackStateFailed,
+} RTCTrackState;
diff --git a/talk/app/webrtc/objc/public/RTCVideoCapturer.h b/talk/app/webrtc/objc/public/RTCVideoCapturer.h
new file mode 100644
index 0000000..7321d57
--- /dev/null
+++ b/talk/app/webrtc/objc/public/RTCVideoCapturer.h
@@ -0,0 +1,42 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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>
+
+// RTCVideoCapturer is an ObjectiveC wrapper for VideoCapturerInterface.
+@interface RTCVideoCapturer : NSObject
+
+// Create a new video capturer using the specified device.
++ (RTCVideoCapturer *)capturerWithDeviceName:(NSString *)deviceName;
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+// Disallow init and don't add to documentation
+- (id)init __attribute__(
+    (unavailable("init is not a supported initializer for this class.")));
+#endif /* DOXYGEN_SHOULD_SKIP_THIS */
+
+@end
diff --git a/talk/app/webrtc/objc/public/RTCVideoRenderer.h b/talk/app/webrtc/objc/public/RTCVideoRenderer.h
new file mode 100644
index 0000000..cc7ba71
--- /dev/null
+++ b/talk/app/webrtc/objc/public/RTCVideoRenderer.h
@@ -0,0 +1,52 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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>
+
+@protocol RTCVideoRendererDelegate;
+struct CGRect;
+
+// Interface for rendering VideoFrames from a VideoTrack
+@interface RTCVideoRenderer : NSObject
+
+@property(nonatomic, strong) id<RTCVideoRendererDelegate> delegate;
+
+// A convenience method to create a renderer and window and render frames into
+// that window.
++ (RTCVideoRenderer *)videoRenderGUIWithFrame:(CGRect)frame;
+
+// Initialize the renderer.  Requires a delegate which does the actual drawing
+// of frames.
+- (id)initWithDelegate:(id<RTCVideoRendererDelegate>)delegate;
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+// Disallow init and don't add to documentation
+- (id)init __attribute__(
+    (unavailable("init is not a supported initializer for this class.")));
+#endif /* DOXYGEN_SHOULD_SKIP_THIS */
+
+@end
diff --git a/talk/app/webrtc/objc/public/RTCVideoRendererDelegate.h b/talk/app/webrtc/objc/public/RTCVideoRendererDelegate.h
new file mode 100644
index 0000000..af72bde
--- /dev/null
+++ b/talk/app/webrtc/objc/public/RTCVideoRendererDelegate.h
@@ -0,0 +1,44 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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>
+
+@class RTCI420Frame;
+@class RTCVideoRenderer;
+
+// RTCVideoRendererDelegate is a protocol for an object that must be
+// implemented to get messages when rendering.
+@protocol RTCVideoRendererDelegate<NSObject>
+
+// The size of the frame.
+- (void)videoRenderer:(RTCVideoRenderer *)videoRenderer setSize:(CGSize)size;
+
+// The frame to be displayed.
+- (void)videoRenderer:(RTCVideoRenderer *)videoRenderer
+          renderFrame:(RTCI420Frame *)frame;
+
+@end
diff --git a/talk/app/webrtc/objc/public/RTCVideoSource.h b/talk/app/webrtc/objc/public/RTCVideoSource.h
new file mode 100644
index 0000000..8de8068
--- /dev/null
+++ b/talk/app/webrtc/objc/public/RTCVideoSource.h
@@ -0,0 +1,39 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCMediaSource.h"
+
+// RTCVideoSource is an ObjectiveC wrapper for VideoSourceInterface.
+@interface RTCVideoSource : RTCMediaSource
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+// Disallow init and don't add to documentation
+- (id)init __attribute__(
+    (unavailable("init is not a supported initializer for this class.")));
+#endif /* DOXYGEN_SHOULD_SKIP_THIS */
+
+@end
diff --git a/talk/app/webrtc/objc/public/RTCVideoTrack.h b/talk/app/webrtc/objc/public/RTCVideoTrack.h
new file mode 100644
index 0000000..291c923
--- /dev/null
+++ b/talk/app/webrtc/objc/public/RTCVideoTrack.h
@@ -0,0 +1,50 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCMediaStreamTrack.h"
+
+@class RTCVideoRenderer;
+
+// RTCVideoTrack is an ObjectiveC wrapper for VideoTrackInterface.
+@interface RTCVideoTrack : RTCMediaStreamTrack
+
+// The currently registered renderers.
+@property(nonatomic, strong, readonly) NSArray *renderers;
+
+// Register a renderer that will render all frames received on this track.
+- (void)addRenderer:(RTCVideoRenderer *)renderer;
+
+// Deregister a renderer.
+- (void)removeRenderer:(RTCVideoRenderer *)renderer;
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+// Disallow init and don't add to documentation
+- (id)init __attribute__(
+    (unavailable("init is not a supported initializer for this class.")));
+#endif /* DOXYGEN_SHOULD_SKIP_THIS */
+
+@end
diff --git a/talk/app/webrtc/objctests/Info.plist b/talk/app/webrtc/objctests/Info.plist
new file mode 100644
index 0000000..0b1583e
--- /dev/null
+++ b/talk/app/webrtc/objctests/Info.plist
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleDisplayName</key>
+	<string>${PRODUCT_NAME}</string>
+	<key>CFBundleExecutable</key>
+	<string>${EXECUTABLE_NAME}</string>
+	<key>CFBundleIdentifier</key>
+	<string>com.Google.${PRODUCT_NAME:rfc1034identifier}</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>${PRODUCT_NAME}</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleSignature</key>
+	<string>????</string>
+	<key>CFBundleVersion</key>
+	<string>1.0</string>
+	<key>LSRequiresIPhoneOS</key>
+	<true/>
+	<key>UIRequiredDeviceCapabilities</key>
+	<array>
+		<string>armv7</string>
+	</array>
+	<key>UISupportedInterfaceOrientations</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+</dict>
+</plist>
diff --git a/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.h b/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.h
new file mode 100644
index 0000000..db97816
--- /dev/null
+++ b/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.h
@@ -0,0 +1,53 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCPeerConnectionDelegate.h"
+
+// Observer of PeerConnection events, used by RTCPeerConnectionTest to check
+// expectations.
+@interface RTCPeerConnectionSyncObserver : NSObject<RTCPeerConnectionDelegate>
+// TODO(hughv): Add support for RTCVideoRendererDelegate when Video is enabled.
+
+// Transfer received ICE candidates to the caller.
+- (NSArray*)releaseReceivedICECandidates;
+
+// Register expectations for events that this observer should see before it can
+// be considered satisfied (see below).
+- (void)expectError;
+- (void)expectSignalingChange:(RTCSignalingState)state;
+- (void)expectAddStream:(NSString *)label;
+- (void)expectRemoveStream:(NSString *)label;
+- (void)expectICECandidates:(int)count;
+- (void)expectICEConnectionChange:(RTCICEConnectionState)state;
+- (void)expectICEGatheringChange:(RTCICEGatheringState)state;
+
+// Wait until all registered expectations above have been observed.
+- (void)waitForAllExpectationsToBeSatisfied;
+
+@end
diff --git a/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.m b/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.m
new file mode 100644
index 0000000..0f33bac
--- /dev/null
+++ b/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.m
@@ -0,0 +1,190 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "RTCPeerConnectionSyncObserver.h"
+
+#import "RTCMediaStream.h"
+
+@implementation RTCPeerConnectionSyncObserver {
+  int _expectedErrors;
+  NSMutableArray *_expectedSignalingChanges;
+  NSMutableArray *_expectedAddStreamLabels;
+  NSMutableArray *_expectedRemoveStreamLabels;
+  int _expectedICECandidates;
+  NSMutableArray *_receivedICECandidates;
+  NSMutableArray *_expectedICEConnectionChanges;
+  NSMutableArray *_expectedICEGatheringChanges;
+}
+
+- (id)init {
+  self = [super init];
+  if (self) {
+    _expectedSignalingChanges = [NSMutableArray array];
+    _expectedSignalingChanges = [NSMutableArray array];
+    _expectedAddStreamLabels = [NSMutableArray array];
+    _expectedRemoveStreamLabels = [NSMutableArray array];
+    _receivedICECandidates = [NSMutableArray array];
+    _expectedICEConnectionChanges = [NSMutableArray array];
+    _expectedICEGatheringChanges = [NSMutableArray array];
+  }
+  return self;
+}
+
+- (int)popFirstElementAsInt:(NSMutableArray *)array {
+  NSAssert([array count] > 0, @"Empty array");
+  NSNumber *boxedState = [array objectAtIndex:0];
+  [array removeObjectAtIndex:0];
+  return [boxedState intValue];
+}
+
+- (NSString *)popFirstElementAsNSString:(NSMutableArray *)array {
+  NSAssert([array count] > 0, @"Empty expectation array");
+  NSString *string = [array objectAtIndex:0];
+  [array removeObjectAtIndex:0];
+  return string;
+}
+
+- (BOOL)areAllExpectationsSatisfied {
+  return _expectedICECandidates <= 0 &&  // See comment in gotICECandidate.
+         _expectedErrors == 0 &&
+         [_expectedSignalingChanges count] == 0 &&
+         [_expectedICEConnectionChanges count] == 0 &&
+         [_expectedICEGatheringChanges count] == 0 &&
+         [_expectedAddStreamLabels count] == 0 &&
+         [_expectedRemoveStreamLabels count] == 0;
+  // TODO(hughv): Test video state here too.
+}
+
+- (NSArray *)releaseReceivedICECandidates {
+  NSArray* ret = _receivedICECandidates;
+  _receivedICECandidates = [NSMutableArray array];
+  return ret;
+}
+
+- (void)expectError {
+  ++_expectedErrors;
+}
+
+- (void)expectSignalingChange:(RTCSignalingState)state {
+  [_expectedSignalingChanges addObject:@((int)state)];
+}
+
+- (void)expectAddStream:(NSString *)label {
+  [_expectedAddStreamLabels addObject:label];
+}
+
+- (void)expectRemoveStream:(NSString *)label {
+  [_expectedRemoveStreamLabels addObject:label];
+}
+
+- (void)expectICECandidates:(int)count {
+  _expectedICECandidates += count;
+}
+
+- (void)expectICEConnectionChange:(RTCICEConnectionState)state {
+  [_expectedICEConnectionChanges addObject:@((int)state)];
+}
+
+- (void)expectICEGatheringChange:(RTCICEGatheringState)state {
+  [_expectedICEGatheringChanges addObject:@((int)state)];
+}
+
+- (void)waitForAllExpectationsToBeSatisfied {
+  // TODO (fischman):  Revisit.  Keeping in sync with the Java version, but
+  // polling is not optimal.
+  // https://code.google.com/p/libjingle/source/browse/trunk/talk/app/webrtc/javatests/src/org/webrtc/PeerConnectionTest.java?line=212#212
+  while (![self areAllExpectationsSatisfied]) {
+    [[NSRunLoop currentRunLoop]
+        runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
+  }
+}
+
+#pragma mark - RTCPeerConnectionDelegate methods
+
+- (void)peerConnectionOnError:(RTCPeerConnection *)peerConnection {
+  NSLog(@"RTCPeerConnectionDelegate::onError");
+  NSAssert(--_expectedErrors >= 0, @"Unexpected error");
+}
+
+- (void)peerConnection:(RTCPeerConnection *)peerConnection
+    signalingStateChanged:(RTCSignalingState)stateChanged {
+  int expectedState = [self popFirstElementAsInt:_expectedSignalingChanges];
+  NSString *message = [NSString stringWithFormat: @"RTCPeerConnectionDelegate::"
+      @"onSignalingStateChange [%d] expected[%d]", stateChanged, expectedState];
+  NSAssert(expectedState == (int) stateChanged, message);
+}
+
+- (void)peerConnection:(RTCPeerConnection *)peerConnection
+           addedStream:(RTCMediaStream *)stream {
+  NSString *expectedLabel =
+      [self popFirstElementAsNSString:_expectedAddStreamLabels];
+  NSAssert([expectedLabel isEqual:stream.label], @"Stream not expected");
+}
+
+- (void)peerConnection:(RTCPeerConnection *)peerConnection
+        removedStream:(RTCMediaStream *)stream {
+  NSString *expectedLabel =
+      [self popFirstElementAsNSString:_expectedRemoveStreamLabels];
+  NSAssert([expectedLabel isEqual:stream.label], @"Stream not expected");
+}
+
+- (void)peerConnectionOnRenegotiationNeeded:
+    (RTCPeerConnection *)peerConnection {
+}
+
+- (void)peerConnection:(RTCPeerConnection *)peerConnection
+       gotICECandidate:(RTCICECandidate *)candidate {
+  --_expectedICECandidates;
+  // We don't assert expectedICECandidates >= 0 because it's hard to know
+  // how many to expect, in general.  We only use expectICECandidates to
+  // assert a minimal count.
+  [_receivedICECandidates addObject:candidate];
+}
+
+- (void)peerConnection:(RTCPeerConnection *)peerConnection
+    iceGatheringChanged:(RTCICEGatheringState)newState {
+  // It's fine to get a variable number of GATHERING messages before
+  // COMPLETE fires (depending on how long the test runs) so we don't assert
+  // any particular count.
+  if (newState == RTCICEGatheringGathering) {
+    return;
+  }
+  int expectedState = [self popFirstElementAsInt:_expectedICEGatheringChanges];
+  NSAssert(expectedState == (int)newState, @"Empty expectation array");
+}
+
+- (void)peerConnection:(RTCPeerConnection *)peerConnection
+    iceConnectionChanged:(RTCICEConnectionState)newState {
+  int expectedState = [self popFirstElementAsInt:_expectedICEConnectionChanges];
+  NSAssert(expectedState == (int)newState, @"Empty expectation array");
+}
+
+@end
diff --git a/talk/app/webrtc/objctests/RTCPeerConnectionTest.mm b/talk/app/webrtc/objctests/RTCPeerConnectionTest.mm
new file mode 100644
index 0000000..826409f5
--- /dev/null
+++ b/talk/app/webrtc/objctests/RTCPeerConnectionTest.mm
@@ -0,0 +1,235 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCICEServer.h"
+#import "RTCMediaConstraints.h"
+#import "RTCMediaStream.h"
+#import "RTCPeerConnection.h"
+#import "RTCPeerConnectionFactory.h"
+#import "RTCPeerConnectionSyncObserver.h"
+#import "RTCSessionDescription.h"
+#import "RTCSessionDescriptionSyncObserver.h"
+#import "RTCVideoRenderer.h"
+#import "RTCVideoTrack.h"
+
+#include "talk/base/gunit.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@interface RTCPeerConnectionTest : NSObject
+
+// Returns whether the two sessions are of the same type.
++ (BOOL)isSession:(RTCSessionDescription *)session1
+    ofSameTypeAsSession:(RTCSessionDescription *)session2;
+
+// Create and add tracks to pc, with the given source, label, and IDs
+- (RTCMediaStream *)
+    addTracksToPeerConnection:(RTCPeerConnection *)pc
+                  withFactory:(RTCPeerConnectionFactory *)factory
+                  videoSource:(RTCVideoSource *)videoSource
+                  streamLabel:(NSString *)streamLabel
+                 videoTrackID:(NSString *)videoTrackID
+                 audioTrackID:(NSString *)audioTrackID;
+
+- (void)testCompleteSession;
+
+@end
+
+@implementation RTCPeerConnectionTest
+
++ (BOOL)isSession:(RTCSessionDescription *)session1
+    ofSameTypeAsSession:(RTCSessionDescription *)session2 {
+  return [session1.type isEqual:session2.type];
+}
+
+- (RTCMediaStream *)
+    addTracksToPeerConnection:(RTCPeerConnection *)pc
+                  withFactory:(RTCPeerConnectionFactory *)factory
+                  videoSource:(RTCVideoSource *)videoSource
+                  streamLabel:(NSString *)streamLabel
+                 videoTrackID:(NSString *)videoTrackID
+                 audioTrackID:(NSString *)audioTrackID {
+  RTCMediaStream *localMediaStream = [factory mediaStreamWithLabel:streamLabel];
+  RTCVideoTrack *videoTrack =
+      [factory videoTrackWithID:videoTrackID source:videoSource];
+  RTCVideoRenderer *videoRenderer =
+      [[RTCVideoRenderer alloc] initWithDelegate:nil];
+  [videoTrack addRenderer:videoRenderer];
+  [localMediaStream addVideoTrack:videoTrack];
+  // Test that removal/re-add works.
+  [localMediaStream removeVideoTrack:videoTrack];
+  [localMediaStream addVideoTrack:videoTrack];
+  RTCAudioTrack *audioTrack = [factory audioTrackWithID:audioTrackID];
+  [localMediaStream addAudioTrack:audioTrack];
+  RTCMediaConstraints *constraints = [[RTCMediaConstraints alloc] init];
+  [pc addStream:localMediaStream constraints:constraints];
+  return localMediaStream;
+}
+
+- (void)testCompleteSession {
+  RTCPeerConnectionFactory *factory = [[RTCPeerConnectionFactory alloc] init];
+  NSString *stunURL = @"stun:stun.l.google.com:19302";
+  RTCICEServer *stunServer =
+      [[RTCICEServer alloc] initWithURI:[NSURL URLWithString:stunURL]
+                               password:@""];
+  NSArray *iceServers = @[stunServer];
+
+  RTCMediaConstraints *constraints = [[RTCMediaConstraints alloc] init];
+  RTCPeerConnectionSyncObserver *offeringExpectations =
+      [[RTCPeerConnectionSyncObserver alloc] init];
+  RTCPeerConnection *pcOffer =
+      [factory peerConnectionWithICEServers:iceServers
+                                constraints:constraints
+                                   delegate:offeringExpectations];
+
+  RTCPeerConnectionSyncObserver *answeringExpectations =
+      [[RTCPeerConnectionSyncObserver alloc] init];
+  RTCPeerConnection *pcAnswer =
+      [factory peerConnectionWithICEServers:iceServers
+                                constraints:constraints
+                                   delegate:answeringExpectations];
+
+  // TODO(hughv): Create video capturer
+  RTCVideoCapturer *capturer = nil;
+  RTCVideoSource *videoSource =
+      [factory videoSourceWithCapturer:capturer constraints:constraints];
+
+  // Here and below, "oLMS" refers to offerer's local media stream, and "aLMS"
+  // refers to the answerer's local media stream, with suffixes of "a0" and "v0"
+  // for audio and video tracks, resp.  These mirror chrome historical naming.
+  RTCMediaStream *oLMSUnused =
+      [self addTracksToPeerConnection:pcOffer
+                          withFactory:factory
+                          videoSource:videoSource
+                          streamLabel:@"oLMS"
+                         videoTrackID:@"oLMSv0"
+                         audioTrackID:@"oLMSa0"];
+  RTCSessionDescriptionSyncObserver *sdpObserver =
+      [[RTCSessionDescriptionSyncObserver alloc] init];
+  [pcOffer createOfferWithDelegate:sdpObserver constraints:constraints];
+  [sdpObserver wait];
+  EXPECT_TRUE(sdpObserver.success);
+  RTCSessionDescription *offerSDP = sdpObserver.sessionDescription;
+  EXPECT_EQ([@"offer" compare:offerSDP.type options:NSCaseInsensitiveSearch],
+            NSOrderedSame);
+  EXPECT_GT([offerSDP.description length], 0);
+
+  sdpObserver = [[RTCSessionDescriptionSyncObserver alloc] init];
+  [answeringExpectations
+      expectSignalingChange:RTCSignalingHaveRemoteOffer];
+  [answeringExpectations expectAddStream:@"oLMS"];
+  [pcAnswer setRemoteDescriptionWithDelegate:sdpObserver
+                          sessionDescription:offerSDP];
+  [sdpObserver wait];
+
+  RTCMediaStream *aLMSUnused =
+      [self addTracksToPeerConnection:pcAnswer
+                          withFactory:factory
+                          videoSource:videoSource
+                          streamLabel:@"aLMS"
+                         videoTrackID:@"aLMSv0"
+                         audioTrackID:@"aLMSa0"];
+
+  sdpObserver = [[RTCSessionDescriptionSyncObserver alloc] init];
+  [pcAnswer createAnswerWithDelegate:sdpObserver constraints:constraints];
+  [sdpObserver wait];
+  EXPECT_TRUE(sdpObserver.success);
+  RTCSessionDescription *answerSDP = sdpObserver.sessionDescription;
+  EXPECT_EQ([@"answer" compare:answerSDP.type options:NSCaseInsensitiveSearch],
+            NSOrderedSame);
+  EXPECT_GT([answerSDP.description length], 0);
+
+  [offeringExpectations expectICECandidates:2];
+  [answeringExpectations expectICECandidates:2];
+
+  sdpObserver = [[RTCSessionDescriptionSyncObserver alloc] init];
+  [answeringExpectations expectSignalingChange:RTCSignalingStable];
+  [pcAnswer setLocalDescriptionWithDelegate:sdpObserver
+                         sessionDescription:answerSDP];
+  [sdpObserver wait];
+  EXPECT_TRUE(sdpObserver.sessionDescription == NULL);
+
+  sdpObserver = [[RTCSessionDescriptionSyncObserver alloc] init];
+  [offeringExpectations expectSignalingChange:RTCSignalingHaveLocalOffer];
+  [pcOffer setLocalDescriptionWithDelegate:sdpObserver
+                        sessionDescription:offerSDP];
+  [sdpObserver wait];
+  EXPECT_TRUE(sdpObserver.sessionDescription == NULL);
+
+  [offeringExpectations expectICEConnectionChange:RTCICEConnectionChecking];
+  [offeringExpectations expectICEConnectionChange:RTCICEConnectionConnected];
+  [answeringExpectations expectICEConnectionChange:RTCICEConnectionChecking];
+  [answeringExpectations expectICEConnectionChange:RTCICEConnectionConnected];
+
+  [offeringExpectations expectICEGatheringChange:RTCICEGatheringComplete];
+  [answeringExpectations expectICEGatheringChange:RTCICEGatheringComplete];
+
+  sdpObserver = [[RTCSessionDescriptionSyncObserver alloc] init];
+  [offeringExpectations expectSignalingChange:RTCSignalingStable];
+  [offeringExpectations expectAddStream:@"aLMS"];
+  [pcOffer setRemoteDescriptionWithDelegate:sdpObserver
+                         sessionDescription:answerSDP];
+  [sdpObserver wait];
+  EXPECT_TRUE(sdpObserver.sessionDescription == NULL);
+
+  EXPECT_TRUE([offerSDP.type isEqual:pcOffer.localDescription.type]);
+  EXPECT_TRUE([answerSDP.type isEqual:pcOffer.remoteDescription.type]);
+  EXPECT_TRUE([offerSDP.type isEqual:pcAnswer.remoteDescription.type]);
+  EXPECT_TRUE([answerSDP.type isEqual:pcAnswer.localDescription.type]);
+
+  for (RTCICECandidate *candidate in
+       offeringExpectations.releaseReceivedICECandidates) {
+    [pcAnswer addICECandidate:candidate];
+  }
+  for (RTCICECandidate *candidate in
+       answeringExpectations.releaseReceivedICECandidates) {
+    [pcOffer addICECandidate:candidate];
+  }
+
+  [offeringExpectations waitForAllExpectationsToBeSatisfied];
+  [answeringExpectations waitForAllExpectationsToBeSatisfied];
+
+  // Let the audio feedback run for 10s to allow human testing and to ensure
+  // things stabilize.  TODO(fischman): replace seconds with # of video frames,
+  // when we have video flowing.
+  [[NSRunLoop currentRunLoop]
+      runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];
+
+  // TODO(hughv): Implement orderly shutdown.
+}
+
+@end
+
+
+TEST(RTCPeerConnectionTest, SessionTest) {
+  RTCPeerConnectionTest *pcTest = [[RTCPeerConnectionTest alloc] init];
+  [pcTest testCompleteSession];
+}
diff --git a/talk/app/webrtc/objctests/RTCSessionDescriptionSyncObserver.h b/talk/app/webrtc/objctests/RTCSessionDescriptionSyncObserver.h
new file mode 100644
index 0000000..18d7902
--- /dev/null
+++ b/talk/app/webrtc/objctests/RTCSessionDescriptionSyncObserver.h
@@ -0,0 +1,49 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "RTCSessionDescriptonDelegate.h"
+
+@class RTCSessionDescription;
+
+// Observer of SDP-related events, used by RTCPeerConnectionTest to check
+// expectations.
+@interface RTCSessionDescriptionSyncObserver : NSObject<
+    RTCSessionDescriptonDelegate>
+
+// Error string.  May be nil.
+@property(atomic, copy) NSString *error;
+// Created session description.  May be nil.
+@property(atomic, strong) RTCSessionDescription *sessionDescription;
+// Whether an SDP-related callback reported success.
+@property(atomic, assign) BOOL success;
+
+// Wait for an SDP-related callback to fire.
+- (void)wait;
+
+@end
diff --git a/talk/app/webrtc/objctests/RTCSessionDescriptionSyncObserver.m b/talk/app/webrtc/objctests/RTCSessionDescriptionSyncObserver.m
new file mode 100644
index 0000000..c04c1c3
--- /dev/null
+++ b/talk/app/webrtc/objctests/RTCSessionDescriptionSyncObserver.m
@@ -0,0 +1,97 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "RTCSessionDescriptionSyncObserver.h"
+
+#import "RTCSessionDescription.h"
+
+@interface RTCSessionDescriptionSyncObserver()
+
+// CondVar used to wait for, and signal arrival of, an SDP-related callback.
+@property(nonatomic, strong) NSCondition *condition;
+// Whether an SDP-related callback has fired; cleared before wait returns.
+@property(atomic, assign) BOOL signaled;
+
+@end
+
+@implementation RTCSessionDescriptionSyncObserver
+
+- (id)init {
+  if ((self = [super init])) {
+    if (!(_condition = [[NSCondition alloc] init]))
+      self = nil;
+  }
+  return self;
+}
+
+- (void)signal {
+  self.signaled = YES;
+  [self.condition signal];
+}
+
+- (void)wait {
+  [self.condition lock];
+  if (!self.signaled)
+    [self.condition wait];
+  self.signaled = NO;
+  [self.condition unlock];
+}
+
+#pragma mark - RTCSessionDescriptonDelegate methods
+- (void)peerConnection:(RTCPeerConnection *)peerConnection
+    didCreateSessionDescription:(RTCSessionDescription *)sdp
+                          error:(NSError *)error {
+  [self.condition lock];
+  if (error) {
+    self.success = NO;
+    self.error = error.description;
+  } else {
+    self.success = YES;
+    self.sessionDescription = sdp;
+  }
+  [self signal];
+  [self.condition unlock];
+}
+
+- (void)peerConnection:(RTCPeerConnection *)peerConnection
+    didSetSessionDescriptionWithError:(NSError *)error {
+  [self.condition lock];
+  if (error) {
+    self.success = NO;
+    self.error = error.description;
+  } else {
+    self.success = YES;
+  }
+  [self signal];
+  [self.condition unlock];
+}
+
+@end
diff --git a/talk/app/webrtc/objctests/mac/main.mm b/talk/app/webrtc/objctests/mac/main.mm
new file mode 100644
index 0000000..3fb24f3
--- /dev/null
+++ b/talk/app/webrtc/objctests/mac/main.mm
@@ -0,0 +1,33 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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"
+
+int main(int argc, char *argv[]) {
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/talk/app/webrtc/peerconnection.cc b/talk/app/webrtc/peerconnection.cc
new file mode 100644
index 0000000..6d3417a
--- /dev/null
+++ b/talk/app/webrtc/peerconnection.cc
@@ -0,0 +1,755 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/peerconnection.h"
+
+#include <vector>
+
+#include "talk/app/webrtc/dtmfsender.h"
+#include "talk/app/webrtc/jsepicecandidate.h"
+#include "talk/app/webrtc/jsepsessiondescription.h"
+#include "talk/app/webrtc/mediastreamhandler.h"
+#include "talk/app/webrtc/streamcollection.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringencode.h"
+#include "talk/session/media/channelmanager.h"
+
+namespace {
+
+using webrtc::PeerConnectionInterface;
+
+// The min number of tokens in the ice uri.
+static const size_t kMinIceUriTokens = 2;
+// The min number of tokens must present in Turn host uri.
+// e.g. user@turn.example.org
+static const size_t kTurnHostTokensNum = 2;
+// Number of tokens must be preset when TURN uri has transport param.
+static const size_t kTurnTransportTokensNum = 2;
+// The default stun port.
+static const int kDefaultPort = 3478;
+static const char kTransport[] = "transport";
+static const char kDefaultTransportType[] = "udp";
+
+// NOTE: Must be in the same order as the ServiceType enum.
+static const char* kValidIceServiceTypes[] = {
+    "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_CREATE_SESSIONDESCRIPTION_SUCCESS = 0,
+  MSG_CREATE_SESSIONDESCRIPTION_FAILED,
+  MSG_SET_SESSIONDESCRIPTION_SUCCESS,
+  MSG_SET_SESSIONDESCRIPTION_FAILED,
+  MSG_GETSTATS,
+  MSG_ICECONNECTIONCHANGE,
+  MSG_ICEGATHERINGCHANGE,
+  MSG_ICECANDIDATE,
+  MSG_ICECOMPLETE,
+};
+
+struct CandidateMsg : public talk_base::MessageData {
+  explicit CandidateMsg(const webrtc::JsepIceCandidate* candidate)
+      : candidate(candidate) {
+  }
+  talk_base::scoped_ptr<const webrtc::JsepIceCandidate> candidate;
+};
+
+struct CreateSessionDescriptionMsg : public talk_base::MessageData {
+  explicit CreateSessionDescriptionMsg(
+      webrtc::CreateSessionDescriptionObserver* observer)
+      : observer(observer) {
+  }
+
+  talk_base::scoped_refptr<webrtc::CreateSessionDescriptionObserver> observer;
+  std::string error;
+  talk_base::scoped_ptr<webrtc::SessionDescriptionInterface> description;
+};
+
+struct SetSessionDescriptionMsg : public talk_base::MessageData {
+  explicit SetSessionDescriptionMsg(
+      webrtc::SetSessionDescriptionObserver* observer)
+      : observer(observer) {
+  }
+
+  talk_base::scoped_refptr<webrtc::SetSessionDescriptionObserver> observer;
+  std::string error;
+};
+
+struct GetStatsMsg : public talk_base::MessageData {
+  explicit GetStatsMsg(webrtc::StatsObserver* observer)
+      : observer(observer) {
+  }
+  webrtc::StatsReports reports;
+  talk_base::scoped_refptr<webrtc::StatsObserver> observer;
+};
+
+typedef webrtc::PortAllocatorFactoryInterface::StunConfiguration
+    StunConfiguration;
+typedef webrtc::PortAllocatorFactoryInterface::TurnConfiguration
+    TurnConfiguration;
+
+bool ParseIceServers(const PeerConnectionInterface::IceServers& configuration,
+                     std::vector<StunConfiguration>* stun_config,
+                     std::vector<TurnConfiguration>* turn_config) {
+  // draft-nandakumar-rtcweb-stun-uri-01
+  // stunURI       = scheme ":" stun-host [ ":" stun-port ]
+  // scheme        = "stun" / "stuns"
+  // stun-host     = IP-literal / IPv4address / reg-name
+  // stun-port     = *DIGIT
+
+  // draft-petithuguenin-behave-turn-uris-01
+  // turnURI       = scheme ":" turn-host [ ":" turn-port ]
+  //                 [ "?transport=" transport ]
+  // scheme        = "turn" / "turns"
+  // transport     = "udp" / "tcp" / transport-ext
+  // transport-ext = 1*unreserved
+  // turn-host     = IP-literal / IPv4address / reg-name
+  // turn-port     = *DIGIT
+
+  // TODO(ronghuawu): Handle IPV6 address
+  for (size_t i = 0; i < configuration.size(); ++i) {
+    webrtc::PeerConnectionInterface::IceServer server = configuration[i];
+    if (server.uri.empty()) {
+      LOG(WARNING) << "Empty uri.";
+      continue;
+    }
+    std::vector<std::string> tokens;
+    std::string turn_transport_type = kDefaultTransportType;
+    talk_base::tokenize(server.uri, '?', &tokens);
+    std::string uri_without_transport = tokens[0];
+    // Let's look into transport= param, if it exists.
+    if (tokens.size() == kTurnTransportTokensNum) {  // ?transport= is present.
+      std::string uri_transport_param = tokens[1];
+      talk_base::tokenize(uri_transport_param, '=', &tokens);
+      if (tokens[0] == kTransport) {
+        turn_transport_type = tokens[1];
+      }
+    }
+
+    tokens.clear();
+    talk_base::tokenize(uri_without_transport, ':', &tokens);
+    if (tokens.size() < kMinIceUriTokens) {
+      LOG(WARNING) << "Invalid uri: " << server.uri;
+      continue;
+    }
+    ServiceType service_type = INVALID;
+    const std::string& type = tokens[0];
+    for (size_t i = 0; i < ARRAY_SIZE(kValidIceServiceTypes); ++i) {
+      if (type.compare(kValidIceServiceTypes[i]) == 0) {
+        service_type = static_cast<ServiceType>(i);
+        break;
+      }
+    }
+    if (service_type == INVALID) {
+      LOG(WARNING) << "Invalid service type: " << type;
+      continue;
+    }
+    std::string address = tokens[1];
+    int port = kDefaultPort;
+    if (tokens.size() > kMinIceUriTokens) {
+      if (!talk_base::FromString(tokens[2], &port)) {
+        LOG(LS_WARNING)  << "Failed to parse port string: " << tokens[2];
+        continue;
+      }
+
+      if (port <= 0 || port > 0xffff) {
+        LOG(WARNING) << "Invalid port: " << port;
+        continue;
+      }
+    }
+
+    switch (service_type) {
+      case STUN:
+      case STUNS:
+        stun_config->push_back(StunConfiguration(address, port));
+        break;
+      case TURN: {
+        if (server.username.empty()) {
+          // Turn url example from the spec |url:"turn:user@turn.example.org"|.
+          std::vector<std::string> turn_tokens;
+          talk_base::tokenize(address, '@', &turn_tokens);
+          if (turn_tokens.size() == kTurnHostTokensNum) {
+            server.username = talk_base::s_url_decode(turn_tokens[0]);
+            address = turn_tokens[1];
+          }
+        }
+        turn_config->push_back(TurnConfiguration(address, port,
+                                                 server.username,
+                                                 server.password,
+                                                 turn_transport_type));
+        // STUN functionality is part of TURN.
+        stun_config->push_back(StunConfiguration(address, port));
+        break;
+      }
+      case TURNS:
+      case INVALID:
+      default:
+        LOG(WARNING) << "Configuration not supported: " << server.uri;
+        return false;
+    }
+  }
+  return true;
+}
+
+// Check if we can send |new_stream| on a PeerConnection.
+// Currently only one audio but multiple video track is supported per
+// PeerConnection.
+bool CanAddLocalMediaStream(webrtc::StreamCollectionInterface* current_streams,
+                            webrtc::MediaStreamInterface* new_stream) {
+  if (!new_stream || !current_streams)
+    return false;
+  if (current_streams->find(new_stream->label()) != NULL) {
+    LOG(LS_ERROR) << "MediaStream with label " << new_stream->label()
+                  << " is already added.";
+    return false;
+  }
+
+  bool audio_track_exist = false;
+  for (size_t j = 0; j < current_streams->count(); ++j) {
+    if (!audio_track_exist) {
+      audio_track_exist = current_streams->at(j)->GetAudioTracks().size() > 0;
+    }
+  }
+  if (audio_track_exist && (new_stream->GetAudioTracks().size() > 0)) {
+    LOG(LS_ERROR) << "AddStream - Currently only one audio track is supported"
+                  << "per PeerConnection.";
+    return false;
+  }
+  return true;
+}
+
+}  // namespace
+
+namespace webrtc {
+
+PeerConnection::PeerConnection(PeerConnectionFactory* factory)
+    : factory_(factory),
+      observer_(NULL),
+      signaling_state_(kStable),
+      ice_state_(kIceNew),
+      ice_connection_state_(kIceConnectionNew),
+      ice_gathering_state_(kIceGatheringNew) {
+}
+
+PeerConnection::~PeerConnection() {
+  if (mediastream_signaling_)
+    mediastream_signaling_->TearDown();
+  if (stream_handler_container_)
+    stream_handler_container_->TearDown();
+}
+
+bool PeerConnection::Initialize(
+    const PeerConnectionInterface::IceServers& configuration,
+    const MediaConstraintsInterface* constraints,
+    webrtc::PortAllocatorFactoryInterface* allocator_factory,
+    PeerConnectionObserver* observer) {
+  std::vector<PortAllocatorFactoryInterface::StunConfiguration> stun_config;
+  std::vector<PortAllocatorFactoryInterface::TurnConfiguration> turn_config;
+  if (!ParseIceServers(configuration, &stun_config, &turn_config)) {
+    return false;
+  }
+
+  return DoInitialize(stun_config, turn_config, constraints,
+                      allocator_factory, observer);
+}
+
+bool PeerConnection::DoInitialize(
+    const StunConfigurations& stun_config,
+    const TurnConfigurations& turn_config,
+    const MediaConstraintsInterface* constraints,
+    webrtc::PortAllocatorFactoryInterface* allocator_factory,
+    PeerConnectionObserver* observer) {
+  ASSERT(observer != NULL);
+  if (!observer)
+    return false;
+  observer_ = observer;
+  port_allocator_.reset(
+      allocator_factory->CreatePortAllocator(stun_config, turn_config));
+  // To handle both internal and externally created port allocator, we will
+  // enable BUNDLE here. Also enabling TURN and disable legacy relay service.
+  port_allocator_->set_flags(cricket::PORTALLOCATOR_ENABLE_BUNDLE |
+                             cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG |
+                             cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET);
+  // No step delay is used while allocating ports.
+  port_allocator_->set_step_delay(cricket::kMinimumStepDelay);
+
+  mediastream_signaling_.reset(new MediaStreamSignaling(
+      factory_->signaling_thread(), this));
+
+  session_.reset(new WebRtcSession(factory_->channel_manager(),
+                                   factory_->signaling_thread(),
+                                   factory_->worker_thread(),
+                                   port_allocator_.get(),
+                                   mediastream_signaling_.get()));
+  stream_handler_container_.reset(new MediaStreamHandlerContainer(
+      session_.get(), session_.get()));
+  stats_.set_session(session_.get());
+
+  // Initialize the WebRtcSession. It creates transport channels etc.
+  if (!session_->Initialize(constraints))
+    return false;
+
+
+  // Register PeerConnection as receiver of local ice candidates.
+  // All the callbacks will be posted to the application from PeerConnection.
+  session_->RegisterIceObserver(this);
+  session_->SignalState.connect(this, &PeerConnection::OnSessionStateChange);
+  return true;
+}
+
+talk_base::scoped_refptr<StreamCollectionInterface>
+PeerConnection::local_streams() {
+  return mediastream_signaling_->local_streams();
+}
+
+talk_base::scoped_refptr<StreamCollectionInterface>
+PeerConnection::remote_streams() {
+  return mediastream_signaling_->remote_streams();
+}
+
+bool PeerConnection::AddStream(MediaStreamInterface* local_stream,
+                               const MediaConstraintsInterface* constraints) {
+  if (IsClosed()) {
+    return false;
+  }
+  if (!CanAddLocalMediaStream(mediastream_signaling_->local_streams(),
+                              local_stream))
+    return false;
+
+  // TODO(perkj): Implement support for MediaConstraints in AddStream.
+  if (!mediastream_signaling_->AddLocalStream(local_stream)) {
+    return false;
+  }
+  stats_.AddStream(local_stream);
+  observer_->OnRenegotiationNeeded();
+  return true;
+}
+
+void PeerConnection::RemoveStream(MediaStreamInterface* local_stream) {
+  if (IsClosed()) {
+    return;
+  }
+  mediastream_signaling_->RemoveLocalStream(local_stream);
+  observer_->OnRenegotiationNeeded();
+}
+
+talk_base::scoped_refptr<DtmfSenderInterface> PeerConnection::CreateDtmfSender(
+    AudioTrackInterface* track) {
+  if (!track) {
+    LOG(LS_ERROR) << "CreateDtmfSender - track is NULL.";
+    return NULL;
+  }
+  if (!mediastream_signaling_->local_streams()->FindAudioTrack(track->id())) {
+    LOG(LS_ERROR) << "CreateDtmfSender is called with a non local audio track.";
+    return NULL;
+  }
+
+  talk_base::scoped_refptr<DtmfSenderInterface> sender(
+      DtmfSender::Create(track, signaling_thread(), session_.get()));
+  if (!sender.get()) {
+    LOG(LS_ERROR) << "CreateDtmfSender failed on DtmfSender::Create.";
+    return NULL;
+  }
+  return DtmfSenderProxy::Create(signaling_thread(), sender.get());
+}
+
+bool PeerConnection::GetStats(StatsObserver* observer,
+                              MediaStreamTrackInterface* track) {
+  if (!VERIFY(observer != NULL)) {
+    LOG(LS_ERROR) << "GetStats - observer is NULL.";
+    return false;
+  }
+
+  stats_.UpdateStats();
+  talk_base::scoped_ptr<GetStatsMsg> msg(new GetStatsMsg(observer));
+  if (!stats_.GetStats(track, &(msg->reports))) {
+    return false;
+  }
+  signaling_thread()->Post(this, MSG_GETSTATS, msg.release());
+  return true;
+}
+
+PeerConnectionInterface::SignalingState PeerConnection::signaling_state() {
+  return signaling_state_;
+}
+
+PeerConnectionInterface::IceState PeerConnection::ice_state() {
+  return ice_state_;
+}
+
+PeerConnectionInterface::IceConnectionState
+PeerConnection::ice_connection_state() {
+  return ice_connection_state_;
+}
+
+PeerConnectionInterface::IceGatheringState
+PeerConnection::ice_gathering_state() {
+  return ice_gathering_state_;
+}
+
+talk_base::scoped_refptr<DataChannelInterface>
+PeerConnection::CreateDataChannel(
+    const std::string& label,
+    const DataChannelInit* config) {
+  talk_base::scoped_refptr<DataChannelInterface> channel(
+      session_->CreateDataChannel(label, config));
+  if (!channel.get())
+    return NULL;
+
+  observer_->OnRenegotiationNeeded();
+  return DataChannelProxy::Create(signaling_thread(), channel.get());
+}
+
+void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer,
+                                 const MediaConstraintsInterface* constraints) {
+  if (!VERIFY(observer != NULL)) {
+    LOG(LS_ERROR) << "CreateOffer - observer is NULL.";
+    return;
+  }
+  CreateSessionDescriptionMsg* msg = new CreateSessionDescriptionMsg(observer);
+  msg->description.reset(
+      session_->CreateOffer(constraints));
+
+  if (!msg->description) {
+    msg->error = "CreateOffer failed.";
+    signaling_thread()->Post(this, MSG_CREATE_SESSIONDESCRIPTION_FAILED, msg);
+    return;
+  }
+
+  signaling_thread()->Post(this, MSG_CREATE_SESSIONDESCRIPTION_SUCCESS, msg);
+}
+
+void PeerConnection::CreateAnswer(
+    CreateSessionDescriptionObserver* observer,
+    const MediaConstraintsInterface* constraints) {
+  if (!VERIFY(observer != NULL)) {
+    LOG(LS_ERROR) << "CreateAnswer - observer is NULL.";
+    return;
+  }
+  CreateSessionDescriptionMsg* msg = new CreateSessionDescriptionMsg(observer);
+  msg->description.reset(session_->CreateAnswer(constraints));
+  if (!msg->description) {
+    msg->error = "CreateAnswer failed.";
+    signaling_thread()->Post(this, MSG_CREATE_SESSIONDESCRIPTION_FAILED, msg);
+    return;
+  }
+
+  signaling_thread()->Post(this, MSG_CREATE_SESSIONDESCRIPTION_SUCCESS, msg);
+}
+
+void PeerConnection::SetLocalDescription(
+    SetSessionDescriptionObserver* observer,
+    SessionDescriptionInterface* desc) {
+  if (!VERIFY(observer != NULL)) {
+    LOG(LS_ERROR) << "SetLocalDescription - observer is NULL.";
+    return;
+  }
+  if (!desc) {
+    PostSetSessionDescriptionFailure(observer, "SessionDescription is NULL.");
+    return;
+  }
+
+  // Update stats here so that we have the most recent stats for tracks and
+  // streams that might be removed by updating the session description.
+  stats_.UpdateStats();
+  std::string error;
+  if (!session_->SetLocalDescription(desc, &error)) {
+    PostSetSessionDescriptionFailure(observer, error);
+    return;
+  }
+  SetSessionDescriptionMsg* msg =  new SetSessionDescriptionMsg(observer);
+  signaling_thread()->Post(this, MSG_SET_SESSIONDESCRIPTION_SUCCESS, msg);
+}
+
+void PeerConnection::SetRemoteDescription(
+    SetSessionDescriptionObserver* observer,
+    SessionDescriptionInterface* desc) {
+  if (!VERIFY(observer != NULL)) {
+    LOG(LS_ERROR) << "SetRemoteDescription - observer is NULL.";
+    return;
+  }
+
+  if (!desc) {
+    PostSetSessionDescriptionFailure(observer, "SessionDescription is NULL.");
+    return;
+  }
+  // Update stats here so that we have the most recent stats for tracks and
+  // streams that might be removed by updating the session description.
+  stats_.UpdateStats();
+  std::string error;
+  if (!session_->SetRemoteDescription(desc, &error)) {
+    PostSetSessionDescriptionFailure(observer, error);
+    return;
+  }
+  SetSessionDescriptionMsg* msg  = new SetSessionDescriptionMsg(observer);
+  signaling_thread()->Post(this, MSG_SET_SESSIONDESCRIPTION_SUCCESS, msg);
+}
+
+void PeerConnection::PostSetSessionDescriptionFailure(
+    SetSessionDescriptionObserver* observer,
+    const std::string& error) {
+  SetSessionDescriptionMsg* msg  = new SetSessionDescriptionMsg(observer);
+  msg->error = error;
+  signaling_thread()->Post(this, MSG_SET_SESSIONDESCRIPTION_FAILED, msg);
+}
+
+bool PeerConnection::UpdateIce(const IceServers& configuration,
+                               const MediaConstraintsInterface* constraints) {
+  // TODO(ronghuawu): Implement UpdateIce.
+  LOG(LS_ERROR) << "UpdateIce is not implemented.";
+  return false;
+}
+
+bool PeerConnection::AddIceCandidate(
+    const IceCandidateInterface* ice_candidate) {
+  return session_->ProcessIceMessage(ice_candidate);
+}
+
+const SessionDescriptionInterface* PeerConnection::local_description() const {
+  return session_->local_description();
+}
+
+const SessionDescriptionInterface* PeerConnection::remote_description() const {
+  return session_->remote_description();
+}
+
+void PeerConnection::Close() {
+  // Update stats here so that we have the most recent stats for tracks and
+  // streams before the channels are closed.
+  stats_.UpdateStats();
+
+  session_->Terminate();
+}
+
+void PeerConnection::OnSessionStateChange(cricket::BaseSession* /*session*/,
+                                          cricket::BaseSession::State state) {
+  switch (state) {
+    case cricket::BaseSession::STATE_INIT:
+      ChangeSignalingState(PeerConnectionInterface::kStable);
+    case cricket::BaseSession::STATE_SENTINITIATE:
+      ChangeSignalingState(PeerConnectionInterface::kHaveLocalOffer);
+      break;
+    case cricket::BaseSession::STATE_SENTPRACCEPT:
+      ChangeSignalingState(PeerConnectionInterface::kHaveLocalPrAnswer);
+      break;
+    case cricket::BaseSession::STATE_RECEIVEDINITIATE:
+      ChangeSignalingState(PeerConnectionInterface::kHaveRemoteOffer);
+      break;
+    case cricket::BaseSession::STATE_RECEIVEDPRACCEPT:
+      ChangeSignalingState(PeerConnectionInterface::kHaveRemotePrAnswer);
+      break;
+    case cricket::BaseSession::STATE_SENTACCEPT:
+    case cricket::BaseSession::STATE_RECEIVEDACCEPT:
+      ChangeSignalingState(PeerConnectionInterface::kStable);
+      break;
+    case cricket::BaseSession::STATE_RECEIVEDTERMINATE:
+      ChangeSignalingState(PeerConnectionInterface::kClosed);
+      break;
+    default:
+      break;
+  }
+}
+
+void PeerConnection::OnMessage(talk_base::Message* msg) {
+  switch (msg->message_id) {
+    case MSG_CREATE_SESSIONDESCRIPTION_SUCCESS: {
+      CreateSessionDescriptionMsg* param =
+          static_cast<CreateSessionDescriptionMsg*>(msg->pdata);
+      param->observer->OnSuccess(param->description.release());
+      delete param;
+      break;
+    }
+    case MSG_CREATE_SESSIONDESCRIPTION_FAILED: {
+      CreateSessionDescriptionMsg* param =
+          static_cast<CreateSessionDescriptionMsg*>(msg->pdata);
+      param->observer->OnFailure(param->error);
+      delete param;
+      break;
+    }
+    case MSG_SET_SESSIONDESCRIPTION_SUCCESS: {
+      SetSessionDescriptionMsg* param =
+          static_cast<SetSessionDescriptionMsg*>(msg->pdata);
+      param->observer->OnSuccess();
+      delete param;
+      break;
+    }
+    case MSG_SET_SESSIONDESCRIPTION_FAILED: {
+      SetSessionDescriptionMsg* param =
+          static_cast<SetSessionDescriptionMsg*>(msg->pdata);
+      param->observer->OnFailure(param->error);
+      delete param;
+      break;
+    }
+    case MSG_GETSTATS: {
+      GetStatsMsg* param = static_cast<GetStatsMsg*>(msg->pdata);
+      param->observer->OnComplete(param->reports);
+      delete param;
+      break;
+    }
+    case MSG_ICECONNECTIONCHANGE: {
+      observer_->OnIceConnectionChange(ice_connection_state_);
+      break;
+    }
+    case MSG_ICEGATHERINGCHANGE: {
+      observer_->OnIceGatheringChange(ice_gathering_state_);
+      break;
+    }
+    case MSG_ICECANDIDATE: {
+      CandidateMsg* data = static_cast<CandidateMsg*>(msg->pdata);
+      observer_->OnIceCandidate(data->candidate.get());
+      delete data;
+      break;
+    }
+    case MSG_ICECOMPLETE: {
+      observer_->OnIceComplete();
+      break;
+    }
+    default:
+      ASSERT(false && "Not implemented");
+      break;
+  }
+}
+
+void PeerConnection::OnAddRemoteStream(MediaStreamInterface* stream) {
+  stats_.AddStream(stream);
+  observer_->OnAddStream(stream);
+}
+
+void PeerConnection::OnRemoveRemoteStream(MediaStreamInterface* stream) {
+  stream_handler_container_->RemoveRemoteStream(stream);
+  observer_->OnRemoveStream(stream);
+}
+
+void PeerConnection::OnAddDataChannel(DataChannelInterface* data_channel) {
+  observer_->OnDataChannel(DataChannelProxy::Create(signaling_thread(),
+                                                    data_channel));
+}
+
+void PeerConnection::OnAddRemoteAudioTrack(MediaStreamInterface* stream,
+                                           AudioTrackInterface* audio_track,
+                                           uint32 ssrc) {
+  stream_handler_container_->AddRemoteAudioTrack(stream, audio_track, ssrc);
+}
+
+void PeerConnection::OnAddRemoteVideoTrack(MediaStreamInterface* stream,
+                                           VideoTrackInterface* video_track,
+                                           uint32 ssrc) {
+  stream_handler_container_->AddRemoteVideoTrack(stream, video_track, ssrc);
+}
+
+void PeerConnection::OnRemoveRemoteAudioTrack(
+    MediaStreamInterface* stream,
+    AudioTrackInterface* audio_track) {
+  stream_handler_container_->RemoveRemoteTrack(stream, audio_track);
+}
+
+void PeerConnection::OnRemoveRemoteVideoTrack(
+    MediaStreamInterface* stream,
+    VideoTrackInterface* video_track) {
+  stream_handler_container_->RemoveRemoteTrack(stream, video_track);
+}
+void PeerConnection::OnAddLocalAudioTrack(MediaStreamInterface* stream,
+                                          AudioTrackInterface* audio_track,
+                                          uint32 ssrc) {
+  stream_handler_container_->AddLocalAudioTrack(stream, audio_track, ssrc);
+}
+void PeerConnection::OnAddLocalVideoTrack(MediaStreamInterface* stream,
+                                          VideoTrackInterface* video_track,
+                                          uint32 ssrc) {
+  stream_handler_container_->AddLocalVideoTrack(stream, video_track, ssrc);
+}
+
+void PeerConnection::OnRemoveLocalAudioTrack(MediaStreamInterface* stream,
+                                             AudioTrackInterface* audio_track) {
+  stream_handler_container_->RemoveLocalTrack(stream, audio_track);
+}
+
+void PeerConnection::OnRemoveLocalVideoTrack(MediaStreamInterface* stream,
+                                             VideoTrackInterface* video_track) {
+  stream_handler_container_->RemoveLocalTrack(stream, video_track);
+}
+
+void PeerConnection::OnRemoveLocalStream(MediaStreamInterface* stream) {
+  stream_handler_container_->RemoveLocalStream(stream);
+}
+
+void PeerConnection::OnIceConnectionChange(
+    PeerConnectionInterface::IceConnectionState new_state) {
+  ice_connection_state_ = new_state;
+  signaling_thread()->Post(this, MSG_ICECONNECTIONCHANGE);
+}
+
+void PeerConnection::OnIceGatheringChange(
+    PeerConnectionInterface::IceGatheringState new_state) {
+  if (IsClosed()) {
+    return;
+  }
+  ice_gathering_state_ = new_state;
+  signaling_thread()->Post(this, MSG_ICEGATHERINGCHANGE);
+}
+
+void PeerConnection::OnIceCandidate(const IceCandidateInterface* candidate) {
+  JsepIceCandidate* candidate_copy = NULL;
+  if (candidate) {
+    // TODO(ronghuawu): Make IceCandidateInterface reference counted instead
+    // of making a copy.
+    candidate_copy = new JsepIceCandidate(candidate->sdp_mid(),
+                                          candidate->sdp_mline_index(),
+                                          candidate->candidate());
+  }
+  // The Post takes the ownership of the |candidate_copy|.
+  signaling_thread()->Post(this, MSG_ICECANDIDATE,
+                           new CandidateMsg(candidate_copy));
+}
+
+void PeerConnection::OnIceComplete() {
+  signaling_thread()->Post(this, MSG_ICECOMPLETE);
+}
+
+void PeerConnection::ChangeSignalingState(
+    PeerConnectionInterface::SignalingState signaling_state) {
+  signaling_state_ = signaling_state;
+  if (signaling_state == kClosed) {
+    ice_connection_state_ = kIceConnectionClosed;
+    observer_->OnIceConnectionChange(ice_connection_state_);
+    if (ice_gathering_state_ != kIceGatheringComplete) {
+      ice_gathering_state_ = kIceGatheringComplete;
+      observer_->OnIceGatheringChange(ice_gathering_state_);
+    }
+  }
+  observer_->OnSignalingChange(signaling_state_);
+  observer_->OnStateChange(PeerConnectionObserver::kSignalingState);
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/peerconnection.h b/talk/app/webrtc/peerconnection.h
new file mode 100644
index 0000000..28aa9d8
--- /dev/null
+++ b/talk/app/webrtc/peerconnection.h
@@ -0,0 +1,192 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_
+
+#include <string>
+
+#include "talk/app/webrtc/mediastreamsignaling.h"
+#include "talk/app/webrtc/peerconnectioninterface.h"
+#include "talk/app/webrtc/peerconnectionfactory.h"
+#include "talk/app/webrtc/statscollector.h"
+#include "talk/app/webrtc/streamcollection.h"
+#include "talk/app/webrtc/webrtcsession.h"
+#include "talk/base/scoped_ptr.h"
+
+namespace webrtc {
+class MediaStreamHandlerContainer;
+
+typedef std::vector<PortAllocatorFactoryInterface::StunConfiguration>
+    StunConfigurations;
+typedef std::vector<PortAllocatorFactoryInterface::TurnConfiguration>
+    TurnConfigurations;
+
+// PeerConnectionImpl implements the PeerConnection interface.
+// It uses MediaStreamSignaling and WebRtcSession to implement
+// the PeerConnection functionality.
+class PeerConnection : public PeerConnectionInterface,
+                       public MediaStreamSignalingObserver,
+                       public IceObserver,
+                       public talk_base::MessageHandler,
+                       public sigslot::has_slots<> {
+ public:
+  explicit PeerConnection(PeerConnectionFactory* factory);
+
+  bool Initialize(const PeerConnectionInterface::IceServers& configuration,
+                  const MediaConstraintsInterface* constraints,
+                  webrtc::PortAllocatorFactoryInterface* allocator_factory,
+                  PeerConnectionObserver* observer);
+  virtual talk_base::scoped_refptr<StreamCollectionInterface> local_streams();
+  virtual talk_base::scoped_refptr<StreamCollectionInterface> remote_streams();
+  virtual bool AddStream(MediaStreamInterface* local_stream,
+                         const MediaConstraintsInterface* constraints);
+  virtual void RemoveStream(MediaStreamInterface* local_stream);
+
+  virtual talk_base::scoped_refptr<DtmfSenderInterface> CreateDtmfSender(
+      AudioTrackInterface* track);
+
+  virtual talk_base::scoped_refptr<DataChannelInterface> CreateDataChannel(
+      const std::string& label,
+      const DataChannelInit* config);
+  virtual bool GetStats(StatsObserver* observer,
+                        webrtc::MediaStreamTrackInterface* track);
+
+  virtual SignalingState signaling_state();
+
+  // TODO(bemasc): Remove ice_state() when callers are removed.
+  virtual IceState ice_state();
+  virtual IceConnectionState ice_connection_state();
+  virtual IceGatheringState ice_gathering_state();
+
+  virtual const SessionDescriptionInterface* local_description() const;
+  virtual const SessionDescriptionInterface* remote_description() const;
+
+  // JSEP01
+  virtual void CreateOffer(CreateSessionDescriptionObserver* observer,
+                           const MediaConstraintsInterface* constraints);
+  virtual void CreateAnswer(CreateSessionDescriptionObserver* observer,
+                            const MediaConstraintsInterface* constraints);
+  virtual void SetLocalDescription(SetSessionDescriptionObserver* observer,
+                                   SessionDescriptionInterface* desc);
+  virtual void SetRemoteDescription(SetSessionDescriptionObserver* observer,
+                                    SessionDescriptionInterface* desc);
+  virtual bool UpdateIce(const IceServers& configuration,
+                         const MediaConstraintsInterface* constraints);
+  virtual bool AddIceCandidate(const IceCandidateInterface* candidate);
+
+  virtual void Close();
+
+ protected:
+  virtual ~PeerConnection();
+
+ private:
+  // Implements MessageHandler.
+  virtual void OnMessage(talk_base::Message* msg);
+
+  // Implements MediaStreamSignalingObserver.
+  virtual void OnAddRemoteStream(MediaStreamInterface* stream) OVERRIDE;
+  virtual void OnRemoveRemoteStream(MediaStreamInterface* stream) OVERRIDE;
+  virtual void OnAddDataChannel(DataChannelInterface* data_channel) OVERRIDE;
+  virtual void OnAddRemoteAudioTrack(MediaStreamInterface* stream,
+                                     AudioTrackInterface* audio_track,
+                                     uint32 ssrc) OVERRIDE;
+  virtual void OnAddRemoteVideoTrack(MediaStreamInterface* stream,
+                                     VideoTrackInterface* video_track,
+                                     uint32 ssrc) OVERRIDE;
+  virtual void OnRemoveRemoteAudioTrack(
+      MediaStreamInterface* stream,
+      AudioTrackInterface* audio_track) OVERRIDE;
+  virtual void OnRemoveRemoteVideoTrack(
+      MediaStreamInterface* stream,
+      VideoTrackInterface* video_track) OVERRIDE;
+  virtual void OnAddLocalAudioTrack(MediaStreamInterface* stream,
+                                    AudioTrackInterface* audio_track,
+                                    uint32 ssrc) OVERRIDE;
+  virtual void OnAddLocalVideoTrack(MediaStreamInterface* stream,
+                                    VideoTrackInterface* video_track,
+                                    uint32 ssrc) OVERRIDE;
+  virtual void OnRemoveLocalAudioTrack(
+      MediaStreamInterface* stream,
+      AudioTrackInterface* audio_track) OVERRIDE;
+  virtual void OnRemoveLocalVideoTrack(
+      MediaStreamInterface* stream,
+      VideoTrackInterface* video_track) OVERRIDE;
+  virtual void OnRemoveLocalStream(MediaStreamInterface* stream);
+
+  // Implements IceObserver
+  virtual void OnIceConnectionChange(IceConnectionState new_state);
+  virtual void OnIceGatheringChange(IceGatheringState new_state);
+  virtual void OnIceCandidate(const IceCandidateInterface* candidate);
+  virtual void OnIceComplete();
+
+  // Signals from WebRtcSession.
+  void OnSessionStateChange(cricket::BaseSession* session,
+                            cricket::BaseSession::State state);
+  void ChangeSignalingState(SignalingState signaling_state);
+
+  bool DoInitialize(const StunConfigurations& stun_config,
+                    const TurnConfigurations& turn_config,
+                    const MediaConstraintsInterface* constraints,
+                    webrtc::PortAllocatorFactoryInterface* allocator_factory,
+                    PeerConnectionObserver* observer);
+
+  talk_base::Thread* signaling_thread() const {
+    return factory_->signaling_thread();
+  }
+
+  void PostSetSessionDescriptionFailure(SetSessionDescriptionObserver* observer,
+                                        const std::string& error);
+
+  bool IsClosed() const {
+    return signaling_state_ == PeerConnectionInterface::kClosed;
+  }
+
+  // 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_;
+  SignalingState signaling_state_;
+  // TODO(bemasc): Remove ice_state_.
+  IceState ice_state_;
+  IceConnectionState ice_connection_state_;
+  IceGatheringState ice_gathering_state_;
+
+  talk_base::scoped_ptr<cricket::PortAllocator> port_allocator_;
+  talk_base::scoped_ptr<WebRtcSession> session_;
+  talk_base::scoped_ptr<MediaStreamSignaling> mediastream_signaling_;
+  talk_base::scoped_ptr<MediaStreamHandlerContainer> stream_handler_container_;
+  StatsCollector stats_;
+};
+
+}  // 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..96a9c1c
--- /dev/null
+++ b/talk/app/webrtc/peerconnection_unittest.cc
@@ -0,0 +1,1374 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 <algorithm>
+#include <list>
+#include <map>
+#include <vector>
+
+#include "talk/app/webrtc/dtmfsender.h"
+#include "talk/app/webrtc/fakeportallocatorfactory.h"
+#include "talk/app/webrtc/localaudiosource.h"
+#include "talk/app/webrtc/mediastreaminterface.h"
+#include "talk/app/webrtc/peerconnectionfactory.h"
+#include "talk/app/webrtc/peerconnectioninterface.h"
+#include "talk/app/webrtc/test/fakeaudiocapturemodule.h"
+#include "talk/app/webrtc/test/fakeconstraints.h"
+#include "talk/app/webrtc/test/fakevideotrackrenderer.h"
+#include "talk/app/webrtc/test/fakeperiodicvideocapturer.h"
+#include "talk/app/webrtc/test/mockpeerconnectionobservers.h"
+#include "talk/app/webrtc/videosourceinterface.h"
+#include "talk/base/gunit.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/ssladapter.h"
+#include "talk/base/sslstreamadapter.h"
+#include "talk/base/thread.h"
+#include "talk/media/webrtc/fakewebrtcvideoengine.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/sessiondescription.h"
+#include "talk/session/media/mediasession.h"
+
+#define MAYBE_SKIP_TEST(feature)                    \
+  if (!(feature())) {                               \
+    LOG(LS_INFO) << "Feature disabled... skipping"; \
+    return;                                         \
+  }
+
+using cricket::ContentInfo;
+using cricket::FakeWebRtcVideoDecoder;
+using cricket::FakeWebRtcVideoDecoderFactory;
+using cricket::FakeWebRtcVideoEncoder;
+using cricket::FakeWebRtcVideoEncoderFactory;
+using cricket::MediaContentDescription;
+using webrtc::DataBuffer;
+using webrtc::DataChannelInterface;
+using webrtc::DtmfSender;
+using webrtc::DtmfSenderInterface;
+using webrtc::DtmfSenderObserverInterface;
+using webrtc::FakeConstraints;
+using webrtc::MediaConstraintsInterface;
+using webrtc::MediaStreamTrackInterface;
+using webrtc::MockCreateSessionDescriptionObserver;
+using webrtc::MockDataChannelObserver;
+using webrtc::MockSetSessionDescriptionObserver;
+using webrtc::MockStatsObserver;
+using webrtc::SessionDescriptionInterface;
+using webrtc::StreamCollectionInterface;
+
+static const int kMaxWaitMs = 1000;
+static const int kMaxWaitForStatsMs = 3000;
+static const int kMaxWaitForFramesMs = 5000;
+static const int kEndAudioFrameCount = 3;
+static const int kEndVideoFrameCount = 3;
+
+static const char kStreamLabelBase[] = "stream_label";
+static const char kVideoTrackLabelBase[] = "video_track";
+static const char kAudioTrackLabelBase[] = "audio_track";
+static const char kDataChannelLabel[] = "data_channel";
+
+static void RemoveLinesFromSdp(const std::string& line_start,
+                               std::string* sdp) {
+  const char kSdpLineEnd[] = "\r\n";
+  size_t ssrc_pos = 0;
+  while ((ssrc_pos = sdp->find(line_start, ssrc_pos)) !=
+      std::string::npos) {
+    size_t end_ssrc = sdp->find(kSdpLineEnd, ssrc_pos);
+    sdp->erase(ssrc_pos, end_ssrc - ssrc_pos + strlen(kSdpLineEnd));
+  }
+}
+
+class SignalingMessageReceiver {
+ public:
+ protected:
+  SignalingMessageReceiver() {}
+  virtual ~SignalingMessageReceiver() {}
+};
+
+class JsepMessageReceiver : public SignalingMessageReceiver {
+ public:
+  virtual void ReceiveSdpMessage(const std::string& type,
+                                 std::string& msg) = 0;
+  virtual void ReceiveIceMessage(const std::string& sdp_mid,
+                                 int sdp_mline_index,
+                                 const std::string& msg) = 0;
+
+ protected:
+  JsepMessageReceiver() {}
+  virtual ~JsepMessageReceiver() {}
+};
+
+template <typename MessageReceiver>
+class PeerConnectionTestClientBase
+    : public webrtc::PeerConnectionObserver,
+      public MessageReceiver {
+ public:
+  ~PeerConnectionTestClientBase() {
+    while (!fake_video_renderers_.empty()) {
+      RenderMap::iterator it = fake_video_renderers_.begin();
+      delete it->second;
+      fake_video_renderers_.erase(it);
+    }
+  }
+
+  virtual void Negotiate()  = 0;
+
+  virtual void Negotiate(bool audio, bool video)  = 0;
+
+  virtual void SetVideoConstraints(
+      const webrtc::FakeConstraints& video_constraint) {
+    video_constraints_ = video_constraint;
+  }
+
+  void AddMediaStream(bool audio, bool video) {
+    std::string label = kStreamLabelBase +
+        talk_base::ToString<int>(peer_connection_->local_streams()->count());
+    talk_base::scoped_refptr<webrtc::MediaStreamInterface> stream =
+        peer_connection_factory_->CreateLocalMediaStream(label);
+
+    if (audio && can_receive_audio()) {
+      FakeConstraints constraints;
+      // Disable highpass filter so that we can get all the test audio frames.
+      constraints.AddMandatory(
+          MediaConstraintsInterface::kHighpassFilter, false);
+      talk_base::scoped_refptr<webrtc::LocalAudioSource> source =
+          webrtc::LocalAudioSource::Create(&constraints);
+      // TODO(perkj): Test audio source when it is implemented. Currently audio
+      // always use the default input.
+      talk_base::scoped_refptr<webrtc::AudioTrackInterface> audio_track(
+          peer_connection_factory_->CreateAudioTrack(kAudioTrackLabelBase,
+                                                     source));
+      stream->AddTrack(audio_track);
+    }
+    if (video && can_receive_video()) {
+      stream->AddTrack(CreateLocalVideoTrack(label));
+    }
+
+    EXPECT_TRUE(peer_connection_->AddStream(stream, NULL));
+  }
+
+  size_t NumberOfLocalMediaStreams() {
+    return peer_connection_->local_streams()->count();
+  }
+
+  bool SessionActive() {
+    return peer_connection_->signaling_state() ==
+        webrtc::PeerConnectionInterface::kStable;
+  }
+
+  void set_signaling_message_receiver(
+      MessageReceiver* signaling_message_receiver) {
+    signaling_message_receiver_ = signaling_message_receiver;
+  }
+
+  void EnableVideoDecoderFactory() {
+    video_decoder_factory_enabled_ = true;
+    fake_video_decoder_factory_->AddSupportedVideoCodecType(
+        webrtc::kVideoCodecVP8);
+  }
+
+  bool AudioFramesReceivedCheck(int number_of_frames) const {
+    return number_of_frames <= fake_audio_capture_module_->frames_received();
+  }
+
+  bool VideoFramesReceivedCheck(int number_of_frames) {
+    if (video_decoder_factory_enabled_) {
+      const std::vector<FakeWebRtcVideoDecoder*>& decoders
+          = fake_video_decoder_factory_->decoders();
+      if (decoders.empty()) {
+        return number_of_frames <= 0;
+      }
+
+      for (std::vector<FakeWebRtcVideoDecoder*>::const_iterator
+           it = decoders.begin(); it != decoders.end(); ++it) {
+        if (number_of_frames > (*it)->GetNumFramesReceived()) {
+          return false;
+        }
+      }
+      return true;
+    } else {
+      if (fake_video_renderers_.empty()) {
+        return number_of_frames <= 0;
+      }
+
+      for (RenderMap::const_iterator it = fake_video_renderers_.begin();
+           it != fake_video_renderers_.end(); ++it) {
+        if (number_of_frames > it->second->num_rendered_frames()) {
+          return false;
+        }
+      }
+      return true;
+    }
+  }
+  // Verify the CreateDtmfSender interface
+  void VerifyDtmf() {
+    talk_base::scoped_ptr<DummyDtmfObserver> observer(new DummyDtmfObserver());
+    talk_base::scoped_refptr<DtmfSenderInterface> dtmf_sender;
+
+    // We can't create a DTMF sender with an invalid audio track or a non local
+    // track.
+    EXPECT_TRUE(peer_connection_->CreateDtmfSender(NULL) == NULL);
+    talk_base::scoped_refptr<webrtc::AudioTrackInterface> non_localtrack(
+        peer_connection_factory_->CreateAudioTrack("dummy_track",
+                                                   NULL));
+    EXPECT_TRUE(peer_connection_->CreateDtmfSender(non_localtrack) == NULL);
+
+    // We should be able to create a DTMF sender from a local track.
+    webrtc::AudioTrackInterface* localtrack =
+        peer_connection_->local_streams()->at(0)->GetAudioTracks()[0];
+    dtmf_sender = peer_connection_->CreateDtmfSender(localtrack);
+    EXPECT_TRUE(dtmf_sender.get() != NULL);
+    dtmf_sender->RegisterObserver(observer.get());
+
+    // Test the DtmfSender object just created.
+    EXPECT_TRUE(dtmf_sender->CanInsertDtmf());
+    EXPECT_TRUE(dtmf_sender->InsertDtmf("1a", 100, 50));
+
+    // We don't need to verify that the DTMF tones are actually sent out because
+    // that is already covered by the tests of the lower level components.
+
+    EXPECT_TRUE_WAIT(observer->completed(), kMaxWaitMs);
+    std::vector<std::string> tones;
+    tones.push_back("1");
+    tones.push_back("a");
+    tones.push_back("");
+    observer->Verify(tones);
+
+    dtmf_sender->UnregisterObserver();
+  }
+
+  // Verifies that the SessionDescription have rejected the appropriate media
+  // content.
+  void VerifyRejectedMediaInSessionDescription() {
+    ASSERT_TRUE(peer_connection_->remote_description() != NULL);
+    ASSERT_TRUE(peer_connection_->local_description() != NULL);
+    const cricket::SessionDescription* remote_desc =
+        peer_connection_->remote_description()->description();
+    const cricket::SessionDescription* local_desc =
+        peer_connection_->local_description()->description();
+
+    const ContentInfo* remote_audio_content = GetFirstAudioContent(remote_desc);
+    if (remote_audio_content) {
+      const ContentInfo* audio_content =
+          GetFirstAudioContent(local_desc);
+      EXPECT_EQ(can_receive_audio(), !audio_content->rejected);
+    }
+
+    const ContentInfo* remote_video_content = GetFirstVideoContent(remote_desc);
+    if (remote_video_content) {
+      const ContentInfo* video_content =
+          GetFirstVideoContent(local_desc);
+      EXPECT_EQ(can_receive_video(), !video_content->rejected);
+    }
+  }
+
+  void SetExpectIceRestart(bool expect_restart) {
+    expect_ice_restart_ = expect_restart;
+  }
+
+  bool ExpectIceRestart() const { return expect_ice_restart_; }
+
+  void VerifyLocalIceUfragAndPassword() {
+    ASSERT_TRUE(peer_connection_->local_description() != NULL);
+    const cricket::SessionDescription* desc =
+        peer_connection_->local_description()->description();
+    const cricket::ContentInfos& contents = desc->contents();
+
+    for (size_t index = 0; index < contents.size(); ++index) {
+      if (contents[index].rejected)
+        continue;
+      const cricket::TransportDescription* transport_desc =
+          desc->GetTransportDescriptionByName(contents[index].name);
+
+      std::map<int, IceUfragPwdPair>::const_iterator ufragpair_it =
+          ice_ufrag_pwd_.find(index);
+      if (ufragpair_it == ice_ufrag_pwd_.end()) {
+        ASSERT_FALSE(ExpectIceRestart());
+        ice_ufrag_pwd_[index] = IceUfragPwdPair(transport_desc->ice_ufrag,
+                                                transport_desc->ice_pwd);
+      } else if (ExpectIceRestart()) {
+        const IceUfragPwdPair& ufrag_pwd = ufragpair_it->second;
+        EXPECT_NE(ufrag_pwd.first, transport_desc->ice_ufrag);
+        EXPECT_NE(ufrag_pwd.second, transport_desc->ice_pwd);
+      } else {
+        const IceUfragPwdPair& ufrag_pwd = ufragpair_it->second;
+        EXPECT_EQ(ufrag_pwd.first, transport_desc->ice_ufrag);
+        EXPECT_EQ(ufrag_pwd.second, transport_desc->ice_pwd);
+      }
+    }
+  }
+
+  int GetAudioOutputLevelStats(webrtc::MediaStreamTrackInterface* track) {
+    talk_base::scoped_refptr<MockStatsObserver>
+        observer(new talk_base::RefCountedObject<MockStatsObserver>());
+    EXPECT_TRUE(peer_connection_->GetStats(observer, track));
+    EXPECT_TRUE_WAIT(observer->called(), kMaxWaitMs);
+    return observer->AudioOutputLevel();
+  }
+
+  int GetAudioInputLevelStats() {
+    talk_base::scoped_refptr<MockStatsObserver>
+        observer(new talk_base::RefCountedObject<MockStatsObserver>());
+    EXPECT_TRUE(peer_connection_->GetStats(observer, NULL));
+    EXPECT_TRUE_WAIT(observer->called(), kMaxWaitMs);
+    return observer->AudioInputLevel();
+  }
+
+  int GetBytesReceivedStats(webrtc::MediaStreamTrackInterface* track) {
+    talk_base::scoped_refptr<MockStatsObserver>
+    observer(new talk_base::RefCountedObject<MockStatsObserver>());
+    EXPECT_TRUE(peer_connection_->GetStats(observer, track));
+    EXPECT_TRUE_WAIT(observer->called(), kMaxWaitMs);
+    return observer->BytesReceived();
+  }
+
+  int GetBytesSentStats(webrtc::MediaStreamTrackInterface* track) {
+    talk_base::scoped_refptr<MockStatsObserver>
+    observer(new talk_base::RefCountedObject<MockStatsObserver>());
+    EXPECT_TRUE(peer_connection_->GetStats(observer, track));
+    EXPECT_TRUE_WAIT(observer->called(), kMaxWaitMs);
+    return observer->BytesSent();
+  }
+
+  int rendered_width() {
+    EXPECT_FALSE(fake_video_renderers_.empty());
+    return fake_video_renderers_.empty() ? 1 :
+        fake_video_renderers_.begin()->second->width();
+  }
+
+  int rendered_height() {
+    EXPECT_FALSE(fake_video_renderers_.empty());
+    return fake_video_renderers_.empty() ? 1 :
+        fake_video_renderers_.begin()->second->height();
+  }
+
+  size_t number_of_remote_streams() {
+    if (!pc())
+      return 0;
+    return pc()->remote_streams()->count();
+  }
+
+  StreamCollectionInterface* remote_streams() {
+    if (!pc()) {
+      ADD_FAILURE();
+      return NULL;
+    }
+    return pc()->remote_streams();
+  }
+
+  StreamCollectionInterface* local_streams() {
+    if (!pc()) {
+      ADD_FAILURE();
+      return NULL;
+    }
+    return pc()->local_streams();
+  }
+
+  webrtc::PeerConnectionInterface::SignalingState signaling_state() {
+    return pc()->signaling_state();
+  }
+
+  webrtc::PeerConnectionInterface::IceConnectionState ice_connection_state() {
+    return pc()->ice_connection_state();
+  }
+
+  webrtc::PeerConnectionInterface::IceGatheringState ice_gathering_state() {
+    return pc()->ice_gathering_state();
+  }
+
+  // PeerConnectionObserver callbacks.
+  virtual void OnError() {}
+  virtual void OnMessage(const std::string&) {}
+  virtual void OnSignalingMessage(const std::string& /*msg*/) {}
+  virtual void OnSignalingChange(
+      webrtc::PeerConnectionInterface::SignalingState new_state) {
+    EXPECT_EQ(peer_connection_->signaling_state(), new_state);
+  }
+  virtual void OnAddStream(webrtc::MediaStreamInterface* media_stream) {
+    for (size_t i = 0; i < media_stream->GetVideoTracks().size(); ++i) {
+      const std::string id = media_stream->GetVideoTracks()[i]->id();
+      ASSERT_TRUE(fake_video_renderers_.find(id) ==
+          fake_video_renderers_.end());
+      fake_video_renderers_[id] = new webrtc::FakeVideoTrackRenderer(
+          media_stream->GetVideoTracks()[i]);
+    }
+  }
+  virtual void OnRemoveStream(webrtc::MediaStreamInterface* media_stream) {}
+  virtual void OnRenegotiationNeeded() {}
+  virtual void OnIceConnectionChange(
+      webrtc::PeerConnectionInterface::IceConnectionState new_state) {
+    EXPECT_EQ(peer_connection_->ice_connection_state(), new_state);
+  }
+  virtual void OnIceGatheringChange(
+      webrtc::PeerConnectionInterface::IceGatheringState new_state) {
+    EXPECT_EQ(peer_connection_->ice_gathering_state(), new_state);
+  }
+  virtual void OnIceCandidate(
+      const webrtc::IceCandidateInterface* /*candidate*/) {}
+
+  webrtc::PeerConnectionInterface* pc() {
+    return peer_connection_.get();
+  }
+
+ protected:
+  explicit PeerConnectionTestClientBase(const std::string& id)
+      : id_(id),
+        expect_ice_restart_(false),
+        fake_video_decoder_factory_(NULL),
+        fake_video_encoder_factory_(NULL),
+        video_decoder_factory_enabled_(false),
+        signaling_message_receiver_(NULL) {
+  }
+  bool Init(const MediaConstraintsInterface* constraints) {
+    EXPECT_TRUE(!peer_connection_);
+    EXPECT_TRUE(!peer_connection_factory_);
+    allocator_factory_ = webrtc::FakePortAllocatorFactory::Create();
+    if (!allocator_factory_) {
+      return false;
+    }
+    audio_thread_.Start();
+    fake_audio_capture_module_ = FakeAudioCaptureModule::Create(
+        &audio_thread_);
+
+    if (fake_audio_capture_module_ == NULL) {
+      return false;
+    }
+    fake_video_decoder_factory_ = new FakeWebRtcVideoDecoderFactory();
+    fake_video_encoder_factory_ = new FakeWebRtcVideoEncoderFactory();
+    peer_connection_factory_ = webrtc::CreatePeerConnectionFactory(
+        talk_base::Thread::Current(), talk_base::Thread::Current(),
+        fake_audio_capture_module_, fake_video_encoder_factory_,
+        fake_video_decoder_factory_);
+    if (!peer_connection_factory_) {
+      return false;
+    }
+    peer_connection_ = CreatePeerConnection(allocator_factory_.get(),
+                                            constraints);
+    return peer_connection_.get() != NULL;
+  }
+  virtual talk_base::scoped_refptr<webrtc::PeerConnectionInterface>
+      CreatePeerConnection(webrtc::PortAllocatorFactoryInterface* factory,
+                           const MediaConstraintsInterface* constraints) = 0;
+  MessageReceiver* signaling_message_receiver() {
+    return signaling_message_receiver_;
+  }
+  webrtc::PeerConnectionFactoryInterface* peer_connection_factory() {
+    return peer_connection_factory_.get();
+  }
+
+  virtual bool can_receive_audio() = 0;
+  virtual bool can_receive_video() = 0;
+  const std::string& id() const { return id_; }
+
+ private:
+  class DummyDtmfObserver : public DtmfSenderObserverInterface {
+   public:
+    DummyDtmfObserver() : completed_(false) {}
+
+    // Implements DtmfSenderObserverInterface.
+    void OnToneChange(const std::string& tone) {
+      tones_.push_back(tone);
+      if (tone.empty()) {
+        completed_ = true;
+      }
+    }
+
+    void Verify(const std::vector<std::string>& tones) const {
+      ASSERT_TRUE(tones_.size() == tones.size());
+      EXPECT_TRUE(std::equal(tones.begin(), tones.end(), tones_.begin()));
+    }
+
+    bool completed() const { return completed_; }
+
+   private:
+    bool completed_;
+    std::vector<std::string> tones_;
+  };
+
+  talk_base::scoped_refptr<webrtc::VideoTrackInterface>
+  CreateLocalVideoTrack(const std::string stream_label) {
+    // Set max frame rate to 10fps to reduce the risk of the tests to be flaky.
+    FakeConstraints source_constraints = video_constraints_;
+    source_constraints.SetMandatoryMaxFrameRate(10);
+
+    talk_base::scoped_refptr<webrtc::VideoSourceInterface> source =
+        peer_connection_factory_->CreateVideoSource(
+            new webrtc::FakePeriodicVideoCapturer(),
+            &source_constraints);
+    std::string label = stream_label + kVideoTrackLabelBase;
+    return peer_connection_factory_->CreateVideoTrack(label, source);
+  }
+
+  std::string id_;
+  // Separate thread for executing |fake_audio_capture_module_| tasks. Audio
+  // processing must not be performed on the same thread as signaling due to
+  // signaling time constraints and relative complexity of the audio pipeline.
+  // This is consistent with the video pipeline that us a a separate thread for
+  // encoding and decoding.
+  talk_base::Thread audio_thread_;
+
+  talk_base::scoped_refptr<webrtc::PortAllocatorFactoryInterface>
+      allocator_factory_;
+  talk_base::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection_;
+  talk_base::scoped_refptr<webrtc::PeerConnectionFactoryInterface>
+      peer_connection_factory_;
+
+  typedef std::pair<std::string, std::string> IceUfragPwdPair;
+  std::map<int, IceUfragPwdPair> ice_ufrag_pwd_;
+  bool expect_ice_restart_;
+
+  // Needed to keep track of number of frames send.
+  talk_base::scoped_refptr<FakeAudioCaptureModule> fake_audio_capture_module_;
+  // Needed to keep track of number of frames received.
+  typedef std::map<std::string, webrtc::FakeVideoTrackRenderer*> RenderMap;
+  RenderMap fake_video_renderers_;
+  // Needed to keep track of number of frames received when external decoder
+  // used.
+  FakeWebRtcVideoDecoderFactory* fake_video_decoder_factory_;
+  FakeWebRtcVideoEncoderFactory* fake_video_encoder_factory_;
+  bool video_decoder_factory_enabled_;
+  webrtc::FakeConstraints video_constraints_;
+
+  // For remote peer communication.
+  MessageReceiver* signaling_message_receiver_;
+};
+
+class JsepTestClient
+    : public PeerConnectionTestClientBase<JsepMessageReceiver> {
+ public:
+  static JsepTestClient* CreateClient(
+      const std::string& id,
+      const MediaConstraintsInterface* constraints) {
+    JsepTestClient* client(new JsepTestClient(id));
+    if (!client->Init(constraints)) {
+      delete client;
+      return NULL;
+    }
+    return client;
+  }
+  ~JsepTestClient() {}
+
+  virtual void Negotiate() {
+    Negotiate(true, true);
+  }
+  virtual void Negotiate(bool audio, bool video) {
+    talk_base::scoped_ptr<SessionDescriptionInterface> offer;
+    EXPECT_TRUE(DoCreateOffer(offer.use()));
+
+    if (offer->description()->GetContentByName("audio")) {
+      offer->description()->GetContentByName("audio")->rejected = !audio;
+    }
+    if (offer->description()->GetContentByName("video")) {
+      offer->description()->GetContentByName("video")->rejected = !video;
+    }
+
+    std::string sdp;
+    EXPECT_TRUE(offer->ToString(&sdp));
+    EXPECT_TRUE(DoSetLocalDescription(offer.release()));
+    signaling_message_receiver()->ReceiveSdpMessage(
+        webrtc::SessionDescriptionInterface::kOffer, sdp);
+  }
+  // JsepMessageReceiver callback.
+  virtual void ReceiveSdpMessage(const std::string& type,
+                                 std::string& msg) {
+    FilterIncomingSdpMessage(&msg);
+    if (type == webrtc::SessionDescriptionInterface::kOffer) {
+      HandleIncomingOffer(msg);
+    } else {
+      HandleIncomingAnswer(msg);
+    }
+  }
+  // JsepMessageReceiver callback.
+  virtual void ReceiveIceMessage(const std::string& sdp_mid,
+                                 int sdp_mline_index,
+                                 const std::string& msg) {
+    LOG(INFO) << id() << "ReceiveIceMessage";
+    talk_base::scoped_ptr<webrtc::IceCandidateInterface> candidate(
+        webrtc::CreateIceCandidate(sdp_mid, sdp_mline_index, msg, NULL));
+    EXPECT_TRUE(pc()->AddIceCandidate(candidate.get()));
+  }
+  // Implements PeerConnectionObserver functions needed by Jsep.
+  virtual void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) {
+    LOG(INFO) << id() << "OnIceCandidate";
+
+    std::string ice_sdp;
+    EXPECT_TRUE(candidate->ToString(&ice_sdp));
+    if (signaling_message_receiver() == NULL) {
+      // Remote party may be deleted.
+      return;
+    }
+    signaling_message_receiver()->ReceiveIceMessage(candidate->sdp_mid(),
+        candidate->sdp_mline_index(), ice_sdp);
+  }
+
+  void IceRestart() {
+    session_description_constraints_.SetMandatoryIceRestart(true);
+    SetExpectIceRestart(true);
+  }
+
+  void SetReceiveAudioVideo(bool audio, bool video) {
+    session_description_constraints_.SetMandatoryReceiveAudio(audio);
+    session_description_constraints_.SetMandatoryReceiveVideo(video);
+    ASSERT_EQ(audio, can_receive_audio());
+    ASSERT_EQ(video, can_receive_video());
+  }
+
+  void RemoveMsidFromReceivedSdp(bool remove) {
+    remove_msid_ = remove;
+  }
+
+  void RemoveSdesCryptoFromReceivedSdp(bool remove) {
+    remove_sdes_ = remove;
+  }
+
+  void RemoveBundleFromReceivedSdp(bool remove) {
+    remove_bundle_ = remove;
+  }
+
+  virtual bool can_receive_audio() {
+    bool value;
+    if (webrtc::FindConstraint(&session_description_constraints_,
+        MediaConstraintsInterface::kOfferToReceiveAudio, &value, NULL)) {
+      return value;
+    }
+    return true;
+  }
+
+  virtual bool can_receive_video() {
+    bool value;
+    if (webrtc::FindConstraint(&session_description_constraints_,
+        MediaConstraintsInterface::kOfferToReceiveVideo, &value, NULL)) {
+      return value;
+    }
+    return true;
+  }
+
+  virtual void OnIceComplete() {
+    LOG(INFO) << id() << "OnIceComplete";
+  }
+
+  virtual void OnDataChannel(DataChannelInterface* data_channel) {
+    LOG(INFO) << id() << "OnDataChannel";
+    data_channel_ = data_channel;
+    data_observer_.reset(new MockDataChannelObserver(data_channel));
+  }
+
+  void CreateDataChannel() {
+    data_channel_ = pc()->CreateDataChannel(kDataChannelLabel,
+                                                         NULL);
+    ASSERT_TRUE(data_channel_.get() != NULL);
+    data_observer_.reset(new MockDataChannelObserver(data_channel_));
+  }
+
+  DataChannelInterface* data_channel() { return data_channel_; }
+  const MockDataChannelObserver* data_observer() const {
+    return data_observer_.get();
+  }
+
+ protected:
+  explicit JsepTestClient(const std::string& id)
+      : PeerConnectionTestClientBase<JsepMessageReceiver>(id),
+        remove_msid_(false),
+        remove_bundle_(false),
+        remove_sdes_(false) {
+  }
+
+  virtual talk_base::scoped_refptr<webrtc::PeerConnectionInterface>
+      CreatePeerConnection(webrtc::PortAllocatorFactoryInterface* factory,
+                           const MediaConstraintsInterface* constraints) {
+    // CreatePeerConnection with IceServers.
+    webrtc::PeerConnectionInterface::IceServers ice_servers;
+    webrtc::PeerConnectionInterface::IceServer ice_server;
+    ice_server.uri = "stun:stun.l.google.com:19302";
+    ice_servers.push_back(ice_server);
+    return peer_connection_factory()->CreatePeerConnection(
+        ice_servers, constraints, factory, NULL, this);
+  }
+
+  void HandleIncomingOffer(const std::string& msg) {
+    LOG(INFO) << id() << "HandleIncomingOffer ";
+    if (NumberOfLocalMediaStreams() == 0) {
+      // If we are not sending any streams ourselves it is time to add some.
+      AddMediaStream(true, true);
+    }
+    talk_base::scoped_ptr<SessionDescriptionInterface> desc(
+         webrtc::CreateSessionDescription("offer", msg, NULL));
+    EXPECT_TRUE(DoSetRemoteDescription(desc.release()));
+    talk_base::scoped_ptr<SessionDescriptionInterface> answer;
+    EXPECT_TRUE(DoCreateAnswer(answer.use()));
+    std::string sdp;
+    EXPECT_TRUE(answer->ToString(&sdp));
+    EXPECT_TRUE(DoSetLocalDescription(answer.release()));
+    if (signaling_message_receiver()) {
+      signaling_message_receiver()->ReceiveSdpMessage(
+          webrtc::SessionDescriptionInterface::kAnswer, sdp);
+    }
+  }
+
+  void HandleIncomingAnswer(const std::string& msg) {
+    LOG(INFO) << id() << "HandleIncomingAnswer";
+    talk_base::scoped_ptr<SessionDescriptionInterface> desc(
+         webrtc::CreateSessionDescription("answer", msg, NULL));
+    EXPECT_TRUE(DoSetRemoteDescription(desc.release()));
+  }
+
+  bool DoCreateOfferAnswer(SessionDescriptionInterface** desc,
+                           bool offer) {
+    talk_base::scoped_refptr<MockCreateSessionDescriptionObserver>
+        observer(new talk_base::RefCountedObject<
+            MockCreateSessionDescriptionObserver>());
+    if (offer) {
+      pc()->CreateOffer(observer, &session_description_constraints_);
+    } else {
+      pc()->CreateAnswer(observer, &session_description_constraints_);
+    }
+    EXPECT_EQ_WAIT(true, observer->called(), kMaxWaitMs);
+    *desc = observer->release_desc();
+    if (observer->result() && ExpectIceRestart()) {
+      EXPECT_EQ(0u, (*desc)->candidates(0)->count());
+    }
+    return observer->result();
+  }
+
+  bool DoCreateOffer(SessionDescriptionInterface** desc) {
+    return DoCreateOfferAnswer(desc, true);
+  }
+
+  bool DoCreateAnswer(SessionDescriptionInterface** desc) {
+    return DoCreateOfferAnswer(desc, false);
+  }
+
+  bool DoSetLocalDescription(SessionDescriptionInterface* desc) {
+    talk_base::scoped_refptr<MockSetSessionDescriptionObserver>
+            observer(new talk_base::RefCountedObject<
+                MockSetSessionDescriptionObserver>());
+    LOG(INFO) << id() << "SetLocalDescription ";
+    pc()->SetLocalDescription(observer, desc);
+    // Ignore the observer result. If we wait for the result with
+    // EXPECT_TRUE_WAIT, local ice candidates might be sent to the remote peer
+    // before the offer which is an error.
+    // The reason is that EXPECT_TRUE_WAIT uses
+    // talk_base::Thread::Current()->ProcessMessages(1);
+    // ProcessMessages waits at least 1ms but processes all messages before
+    // returning. Since this test is synchronous and send messages to the remote
+    // peer whenever a callback is invoked, this can lead to messages being
+    // sent to the remote peer in the wrong order.
+    // TODO(perkj): Find a way to check the result without risking that the
+    // order of sent messages are changed. Ex- by posting all messages that are
+    // sent to the remote peer.
+    return true;
+  }
+
+  bool DoSetRemoteDescription(SessionDescriptionInterface* desc) {
+    talk_base::scoped_refptr<MockSetSessionDescriptionObserver>
+        observer(new talk_base::RefCountedObject<
+            MockSetSessionDescriptionObserver>());
+    LOG(INFO) << id() << "SetRemoteDescription ";
+    pc()->SetRemoteDescription(observer, desc);
+    EXPECT_TRUE_WAIT(observer->called(), kMaxWaitMs);
+    return observer->result();
+  }
+
+  // This modifies all received SDP messages before they are processed.
+  void FilterIncomingSdpMessage(std::string* sdp) {
+    if (remove_msid_) {
+      const char kSdpSsrcAttribute[] = "a=ssrc:";
+      RemoveLinesFromSdp(kSdpSsrcAttribute, sdp);
+      const char kSdpMsidSupportedAttribute[] = "a=msid-semantic:";
+      RemoveLinesFromSdp(kSdpMsidSupportedAttribute, sdp);
+    }
+    if (remove_bundle_) {
+      const char kSdpBundleAttribute[] = "a=group:BUNDLE";
+      RemoveLinesFromSdp(kSdpBundleAttribute, sdp);
+    }
+    if (remove_sdes_) {
+      const char kSdpSdesCryptoAttribute[] = "a=crypto";
+      RemoveLinesFromSdp(kSdpSdesCryptoAttribute, sdp);
+    }
+  }
+
+ private:
+  webrtc::FakeConstraints session_description_constraints_;
+  bool remove_msid_;  // True if MSID should be removed in received SDP.
+  bool remove_bundle_;  // True if bundle should be removed in received SDP.
+  bool remove_sdes_;  // True if a=crypto should be removed in received SDP.
+
+  talk_base::scoped_refptr<DataChannelInterface> data_channel_;
+  talk_base::scoped_ptr<MockDataChannelObserver> data_observer_;
+};
+
+template <typename SignalingClass>
+class P2PTestConductor : public testing::Test {
+ public:
+  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 audio_frames_to_receive,
+                        int video_frames_to_receive) {
+    return VideoFramesReceivedCheck(video_frames_to_receive) &&
+        AudioFramesReceivedCheck(audio_frames_to_receive);
+  }
+  bool AudioFramesReceivedCheck(int frames_received) {
+    return initiating_client_->AudioFramesReceivedCheck(frames_received) &&
+        receiving_client_->AudioFramesReceivedCheck(frames_received);
+  }
+  bool VideoFramesReceivedCheck(int frames_received) {
+    return initiating_client_->VideoFramesReceivedCheck(frames_received) &&
+        receiving_client_->VideoFramesReceivedCheck(frames_received);
+  }
+  void VerifyDtmf() {
+    initiating_client_->VerifyDtmf();
+    receiving_client_->VerifyDtmf();
+  }
+
+  void TestUpdateOfferWithRejectedContent() {
+    initiating_client_->Negotiate(true, false);
+    EXPECT_TRUE_WAIT(
+        FramesNotPending(kEndAudioFrameCount * 2, kEndVideoFrameCount),
+        kMaxWaitForFramesMs);
+    // There shouldn't be any more video frame after the new offer is
+    // negotiated.
+    EXPECT_FALSE(VideoFramesReceivedCheck(kEndVideoFrameCount + 1));
+  }
+
+  void VerifyRenderedSize(int width, int height) {
+    EXPECT_EQ(width, receiving_client()->rendered_width());
+    EXPECT_EQ(height, receiving_client()->rendered_height());
+    EXPECT_EQ(width, initializing_client()->rendered_width());
+    EXPECT_EQ(height, initializing_client()->rendered_height());
+  }
+
+  void VerifySessionDescriptions() {
+    initiating_client_->VerifyRejectedMediaInSessionDescription();
+    receiving_client_->VerifyRejectedMediaInSessionDescription();
+    initiating_client_->VerifyLocalIceUfragAndPassword();
+    receiving_client_->VerifyLocalIceUfragAndPassword();
+  }
+
+  P2PTestConductor() {
+    talk_base::InitializeSSL(NULL);
+  }
+  ~P2PTestConductor() {
+    if (initiating_client_) {
+      initiating_client_->set_signaling_message_receiver(NULL);
+    }
+    if (receiving_client_) {
+      receiving_client_->set_signaling_message_receiver(NULL);
+    }
+  }
+
+  bool CreateTestClients() {
+    return CreateTestClients(NULL, NULL);
+  }
+
+  bool CreateTestClients(MediaConstraintsInterface* init_constraints,
+                         MediaConstraintsInterface* recv_constraints) {
+    initiating_client_.reset(SignalingClass::CreateClient("Caller: ",
+                                                          init_constraints));
+    receiving_client_.reset(SignalingClass::CreateClient("Callee: ",
+                                                         recv_constraints));
+    if (!initiating_client_ || !receiving_client_) {
+      return false;
+    }
+    initiating_client_->set_signaling_message_receiver(receiving_client_.get());
+    receiving_client_->set_signaling_message_receiver(initiating_client_.get());
+    return true;
+  }
+
+  void SetVideoConstraints(const webrtc::FakeConstraints& init_constraints,
+                           const webrtc::FakeConstraints& recv_constraints) {
+    initiating_client_->SetVideoConstraints(init_constraints);
+    receiving_client_->SetVideoConstraints(recv_constraints);
+  }
+
+  void EnableVideoDecoderFactory() {
+    initiating_client_->EnableVideoDecoderFactory();
+    receiving_client_->EnableVideoDecoderFactory();
+  }
+
+  // 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.
+  void LocalP2PTest() {
+    if (initiating_client_->NumberOfLocalMediaStreams() == 0) {
+      initiating_client_->AddMediaStream(true, true);
+    }
+    initiating_client_->Negotiate();
+    const int kMaxWaitForActivationMs = 5000;
+    // Assert true is used here since next tests are guaranteed to fail and
+    // would eat up 5 seconds.
+    ASSERT_TRUE_WAIT(SessionActive(), kMaxWaitForActivationMs);
+    VerifySessionDescriptions();
+
+
+    int audio_frame_count = kEndAudioFrameCount;
+    // TODO(ronghuawu): Add test to cover the case of sendonly and recvonly.
+    if (!initiating_client_->can_receive_audio() ||
+        !receiving_client_->can_receive_audio()) {
+      audio_frame_count = -1;
+    }
+    int video_frame_count = kEndVideoFrameCount;
+    if (!initiating_client_->can_receive_video() ||
+        !receiving_client_->can_receive_video()) {
+      video_frame_count = -1;
+    }
+
+    if (audio_frame_count != -1 || video_frame_count != -1) {
+      // Audio or video is expected to flow, so both sides should get to the
+      // Connected state.
+      // Note: These tests have been observed to fail under heavy load at
+      // shorter timeouts, so they may be flaky.
+      EXPECT_EQ_WAIT(
+          webrtc::PeerConnectionInterface::kIceConnectionConnected,
+          initiating_client_->ice_connection_state(),
+          kMaxWaitForFramesMs);
+      EXPECT_EQ_WAIT(
+          webrtc::PeerConnectionInterface::kIceConnectionConnected,
+          receiving_client_->ice_connection_state(),
+          kMaxWaitForFramesMs);
+    }
+
+    if (initiating_client_->can_receive_audio() ||
+        initiating_client_->can_receive_video()) {
+      // The initiating client can receive media, so it must produce candidates
+      // that will serve as destinations for that media.
+      // TODO(bemasc): Understand why the state is not already Complete here, as
+      // seems to be the case for the receiving client. This may indicate a bug
+      // in the ICE gathering system.
+      EXPECT_NE(webrtc::PeerConnectionInterface::kIceGatheringNew,
+                initiating_client_->ice_gathering_state());
+    }
+    if (receiving_client_->can_receive_audio() ||
+        receiving_client_->can_receive_video()) {
+      EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceGatheringComplete,
+                     receiving_client_->ice_gathering_state(),
+                     kMaxWaitForFramesMs);
+    }
+
+    EXPECT_TRUE_WAIT(FramesNotPending(audio_frame_count, video_frame_count),
+                     kMaxWaitForFramesMs);
+  }
+
+  SignalingClass* initializing_client() { return initiating_client_.get(); }
+  SignalingClass* receiving_client() { return receiving_client_.get(); }
+
+ private:
+  talk_base::scoped_ptr<SignalingClass> initiating_client_;
+  talk_base::scoped_ptr<SignalingClass> receiving_client_;
+};
+typedef P2PTestConductor<JsepTestClient> JsepPeerConnectionP2PTestClient;
+
+// This test sets up a Jsep call between two parties and test Dtmf.
+TEST_F(JsepPeerConnectionP2PTestClient, LocalP2PTestDtmf) {
+  ASSERT_TRUE(CreateTestClients());
+  LocalP2PTest();
+  VerifyDtmf();
+}
+
+// This test sets up a Jsep call between two parties and test that we can get a
+// video aspect ratio of 16:9.
+TEST_F(JsepPeerConnectionP2PTestClient, LocalP2PTest16To9) {
+  ASSERT_TRUE(CreateTestClients());
+  FakeConstraints constraint;
+  double requested_ratio = 640.0/360;
+  constraint.SetMandatoryMinAspectRatio(requested_ratio);
+  SetVideoConstraints(constraint, constraint);
+  LocalP2PTest();
+
+  ASSERT_LE(0, initializing_client()->rendered_height());
+  double initiating_video_ratio =
+      static_cast<double> (initializing_client()->rendered_width()) /
+      initializing_client()->rendered_height();
+  EXPECT_LE(requested_ratio, initiating_video_ratio);
+
+  ASSERT_LE(0, receiving_client()->rendered_height());
+  double receiving_video_ratio =
+      static_cast<double> (receiving_client()->rendered_width()) /
+      receiving_client()->rendered_height();
+  EXPECT_LE(requested_ratio, receiving_video_ratio);
+}
+
+// This test sets up a Jsep call between two parties and test that the
+// received video has a resolution of 1280*720.
+// TODO(mallinath): Enable when
+// http://code.google.com/p/webrtc/issues/detail?id=981 is fixed.
+TEST_F(JsepPeerConnectionP2PTestClient, DISABLED_LocalP2PTest1280By720) {
+  ASSERT_TRUE(CreateTestClients());
+  FakeConstraints constraint;
+  constraint.SetMandatoryMinWidth(1280);
+  constraint.SetMandatoryMinHeight(720);
+  SetVideoConstraints(constraint, constraint);
+  LocalP2PTest();
+  VerifyRenderedSize(1280, 720);
+}
+
+// This test sets up a call between two endpoints that are configured to use
+// DTLS key agreement. As a result, DTLS is negotiated and used for transport.
+TEST_F(JsepPeerConnectionP2PTestClient, LocalP2PTestDtls) {
+  MAYBE_SKIP_TEST(talk_base::SSLStreamAdapter::HaveDtlsSrtp);
+  FakeConstraints setup_constraints;
+  setup_constraints.AddMandatory(MediaConstraintsInterface::kEnableDtlsSrtp,
+                                 true);
+  ASSERT_TRUE(CreateTestClients(&setup_constraints, &setup_constraints));
+  LocalP2PTest();
+  VerifyRenderedSize(640, 480);
+}
+
+// This test sets up a call between an endpoint configured to use either SDES or
+// DTLS (the offerer) and just SDES (the answerer). As a result, SDES is used
+// instead of DTLS.
+TEST_F(JsepPeerConnectionP2PTestClient, LocalP2PTestOfferDtlsToSdes) {
+  MAYBE_SKIP_TEST(talk_base::SSLStreamAdapter::HaveDtlsSrtp);
+  FakeConstraints setup_constraints;
+  setup_constraints.AddMandatory(MediaConstraintsInterface::kEnableDtlsSrtp,
+                                 true);
+  ASSERT_TRUE(CreateTestClients(&setup_constraints, NULL));
+  LocalP2PTest();
+  VerifyRenderedSize(640, 480);
+}
+
+// This test sets up a call between an endpoint configured to use SDES
+// (the offerer) and either SDES or DTLS (the answerer). As a result, SDES is
+// used instead of DTLS.
+TEST_F(JsepPeerConnectionP2PTestClient, LocalP2PTestOfferSdesToDtls) {
+  MAYBE_SKIP_TEST(talk_base::SSLStreamAdapter::HaveDtlsSrtp);
+  FakeConstraints setup_constraints;
+  setup_constraints.AddMandatory(MediaConstraintsInterface::kEnableDtlsSrtp,
+                                 true);
+  ASSERT_TRUE(CreateTestClients(NULL, &setup_constraints));
+  LocalP2PTest();
+  VerifyRenderedSize(640, 480);
+}
+
+// This test sets up a call between two endpoints that are configured to use
+// DTLS key agreement. The offerer don't support SDES. As a result, DTLS is
+// negotiated and used for transport.
+TEST_F(JsepPeerConnectionP2PTestClient, LocalP2PTestOfferDtlsButNotSdes) {
+  MAYBE_SKIP_TEST(talk_base::SSLStreamAdapter::HaveDtlsSrtp);
+  FakeConstraints setup_constraints;
+  setup_constraints.AddMandatory(MediaConstraintsInterface::kEnableDtlsSrtp,
+                                 true);
+  ASSERT_TRUE(CreateTestClients(&setup_constraints, &setup_constraints));
+  receiving_client()->RemoveSdesCryptoFromReceivedSdp(true);
+  LocalP2PTest();
+  VerifyRenderedSize(640, 480);
+}
+
+// This test sets up a Jsep call between two parties, and the callee only
+// accept to receive video.
+TEST_F(JsepPeerConnectionP2PTestClient, LocalP2PTestAnswerVideo) {
+  ASSERT_TRUE(CreateTestClients());
+  receiving_client()->SetReceiveAudioVideo(false, true);
+  LocalP2PTest();
+}
+
+// This test sets up a Jsep call between two parties, and the callee only
+// accept to receive audio.
+TEST_F(JsepPeerConnectionP2PTestClient, LocalP2PTestAnswerAudio) {
+  ASSERT_TRUE(CreateTestClients());
+  receiving_client()->SetReceiveAudioVideo(true, false);
+  LocalP2PTest();
+}
+
+// This test sets up a Jsep call between two parties, and the callee reject both
+// audio and video.
+TEST_F(JsepPeerConnectionP2PTestClient, LocalP2PTestAnswerNone) {
+  ASSERT_TRUE(CreateTestClients());
+  receiving_client()->SetReceiveAudioVideo(false, false);
+  LocalP2PTest();
+}
+
+// This test sets up an audio and video call between two parties. After the call
+// runs for a while (10 frames), the caller sends an update offer with video
+// being rejected. Once the re-negotiation is done, the video flow should stop
+// and the audio flow should continue.
+TEST_F(JsepPeerConnectionP2PTestClient, UpdateOfferWithRejectedContent) {
+  ASSERT_TRUE(CreateTestClients());
+  LocalP2PTest();
+  TestUpdateOfferWithRejectedContent();
+}
+
+// This test sets up a Jsep call between two parties. The MSID is removed from
+// the SDP strings from the caller.
+TEST_F(JsepPeerConnectionP2PTestClient, LocalP2PTestWithoutMsid) {
+  ASSERT_TRUE(CreateTestClients());
+  receiving_client()->RemoveMsidFromReceivedSdp(true);
+  // TODO(perkj): Currently there is a bug that cause audio to stop playing if
+  // audio and video is muxed when MSID is disabled. Remove
+  // SetRemoveBundleFromSdp once
+  // https://code.google.com/p/webrtc/issues/detail?id=1193 is fixed.
+  receiving_client()->RemoveBundleFromReceivedSdp(true);
+  LocalP2PTest();
+}
+
+// This test sets up a Jsep call between two parties and the initiating peer
+// sends two steams.
+// TODO(perkj): Disabled due to
+// https://code.google.com/p/webrtc/issues/detail?id=1454
+TEST_F(JsepPeerConnectionP2PTestClient, DISABLED_LocalP2PTestTwoStreams) {
+  ASSERT_TRUE(CreateTestClients());
+  // Set optional video constraint to max 320pixels to decrease CPU usage.
+  FakeConstraints constraint;
+  constraint.SetOptionalMaxWidth(320);
+  SetVideoConstraints(constraint, constraint);
+  initializing_client()->AddMediaStream(true, true);
+  initializing_client()->AddMediaStream(false, true);
+  ASSERT_EQ(2u, initializing_client()->NumberOfLocalMediaStreams());
+  LocalP2PTest();
+  EXPECT_EQ(2u, receiving_client()->number_of_remote_streams());
+}
+
+// Test that we can receive the audio output level from a remote audio track.
+TEST_F(JsepPeerConnectionP2PTestClient, GetAudioOutputLevelStats) {
+  ASSERT_TRUE(CreateTestClients());
+  LocalP2PTest();
+
+  StreamCollectionInterface* remote_streams =
+      initializing_client()->remote_streams();
+  ASSERT_GT(remote_streams->count(), 0u);
+  ASSERT_GT(remote_streams->at(0)->GetAudioTracks().size(), 0u);
+  MediaStreamTrackInterface* remote_audio_track =
+      remote_streams->at(0)->GetAudioTracks()[0];
+
+  // Get the audio output level stats. Note that the level is not available
+  // until a RTCP packet has been received.
+  EXPECT_TRUE_WAIT(
+      initializing_client()->GetAudioOutputLevelStats(remote_audio_track) > 0,
+      kMaxWaitForStatsMs);
+}
+
+// Test that an audio input level is reported.
+TEST_F(JsepPeerConnectionP2PTestClient, GetAudioInputLevelStats) {
+  ASSERT_TRUE(CreateTestClients());
+  LocalP2PTest();
+
+  // Get the audio input level stats.  The level should be available very
+  // soon after the test starts.
+  EXPECT_TRUE_WAIT(initializing_client()->GetAudioInputLevelStats() > 0,
+      kMaxWaitForStatsMs);
+}
+
+// Test that we can get incoming byte counts from both audio and video tracks.
+TEST_F(JsepPeerConnectionP2PTestClient, GetBytesReceivedStats) {
+  ASSERT_TRUE(CreateTestClients());
+  LocalP2PTest();
+
+  StreamCollectionInterface* remote_streams =
+      initializing_client()->remote_streams();
+  ASSERT_GT(remote_streams->count(), 0u);
+  ASSERT_GT(remote_streams->at(0)->GetAudioTracks().size(), 0u);
+  MediaStreamTrackInterface* remote_audio_track =
+      remote_streams->at(0)->GetAudioTracks()[0];
+  EXPECT_TRUE_WAIT(
+      initializing_client()->GetBytesReceivedStats(remote_audio_track) > 0,
+      kMaxWaitForStatsMs);
+
+  MediaStreamTrackInterface* remote_video_track =
+      remote_streams->at(0)->GetVideoTracks()[0];
+  EXPECT_TRUE_WAIT(
+      initializing_client()->GetBytesReceivedStats(remote_video_track) > 0,
+      kMaxWaitForStatsMs);
+}
+
+// Test that we can get outgoing byte counts from both audio and video tracks.
+TEST_F(JsepPeerConnectionP2PTestClient, GetBytesSentStats) {
+  ASSERT_TRUE(CreateTestClients());
+  LocalP2PTest();
+
+  StreamCollectionInterface* local_streams =
+      initializing_client()->local_streams();
+  ASSERT_GT(local_streams->count(), 0u);
+  ASSERT_GT(local_streams->at(0)->GetAudioTracks().size(), 0u);
+  MediaStreamTrackInterface* local_audio_track =
+      local_streams->at(0)->GetAudioTracks()[0];
+  EXPECT_TRUE_WAIT(
+      initializing_client()->GetBytesSentStats(local_audio_track) > 0,
+      kMaxWaitForStatsMs);
+
+  MediaStreamTrackInterface* local_video_track =
+      local_streams->at(0)->GetVideoTracks()[0];
+  EXPECT_TRUE_WAIT(
+      initializing_client()->GetBytesSentStats(local_video_track) > 0,
+      kMaxWaitForStatsMs);
+}
+
+// This test sets up a call between two parties with audio, video and data.
+TEST_F(JsepPeerConnectionP2PTestClient, LocalP2PTestDataChannel) {
+  FakeConstraints setup_constraints;
+  setup_constraints.SetAllowRtpDataChannels();
+  ASSERT_TRUE(CreateTestClients(&setup_constraints, &setup_constraints));
+  initializing_client()->CreateDataChannel();
+  LocalP2PTest();
+  ASSERT_TRUE(initializing_client()->data_channel() != NULL);
+  ASSERT_TRUE(receiving_client()->data_channel() != NULL);
+  EXPECT_TRUE_WAIT(initializing_client()->data_observer()->IsOpen(),
+                   kMaxWaitMs);
+  EXPECT_TRUE_WAIT(receiving_client()->data_observer()->IsOpen(),
+                   kMaxWaitMs);
+
+  std::string data = "hello world";
+  initializing_client()->data_channel()->Send(DataBuffer(data));
+  EXPECT_EQ_WAIT(data, receiving_client()->data_observer()->last_message(),
+                 kMaxWaitMs);
+  receiving_client()->data_channel()->Send(DataBuffer(data));
+  EXPECT_EQ_WAIT(data, initializing_client()->data_observer()->last_message(),
+                 kMaxWaitMs);
+
+  receiving_client()->data_channel()->Close();
+  // Send new offer and answer.
+  receiving_client()->Negotiate();
+  EXPECT_FALSE(initializing_client()->data_observer()->IsOpen());
+  EXPECT_FALSE(receiving_client()->data_observer()->IsOpen());
+}
+
+// This test sets up a call between two parties and creates a data channel.
+// The test tests that received data is buffered unless an observer has been
+// registered.
+// Rtp data channels can receive data before the underlying
+// transport has detected that a channel is writable and thus data can be
+// received before the data channel state changes to open. That is hard to test
+// but the same buffering is used in that case.
+TEST_F(JsepPeerConnectionP2PTestClient, RegisterDataChannelObserver) {
+  FakeConstraints setup_constraints;
+  setup_constraints.SetAllowRtpDataChannels();
+  ASSERT_TRUE(CreateTestClients(&setup_constraints, &setup_constraints));
+  initializing_client()->CreateDataChannel();
+  initializing_client()->Negotiate();
+
+  ASSERT_TRUE(initializing_client()->data_channel() != NULL);
+  ASSERT_TRUE(receiving_client()->data_channel() != NULL);
+  EXPECT_TRUE_WAIT(initializing_client()->data_observer()->IsOpen(),
+                   kMaxWaitMs);
+  EXPECT_EQ_WAIT(DataChannelInterface::kOpen,
+                 receiving_client()->data_channel()->state(), kMaxWaitMs);
+
+  // Unregister the existing observer.
+  receiving_client()->data_channel()->UnregisterObserver();
+  std::string data = "hello world";
+  initializing_client()->data_channel()->Send(DataBuffer(data));
+  // Wait a while to allow the sent data to arrive before an observer is
+  // registered..
+  talk_base::Thread::Current()->ProcessMessages(100);
+
+  MockDataChannelObserver new_observer(receiving_client()->data_channel());
+  EXPECT_EQ_WAIT(data, new_observer.last_message(), kMaxWaitMs);
+}
+
+// This test sets up a call between two parties with audio, video and but only
+// the initiating client support data.
+TEST_F(JsepPeerConnectionP2PTestClient, LocalP2PTestReceiverDoesntSupportData) {
+  FakeConstraints setup_constraints;
+  setup_constraints.SetAllowRtpDataChannels();
+  ASSERT_TRUE(CreateTestClients(&setup_constraints, NULL));
+  initializing_client()->CreateDataChannel();
+  LocalP2PTest();
+  EXPECT_TRUE(initializing_client()->data_channel() != NULL);
+  EXPECT_FALSE(receiving_client()->data_channel());
+  EXPECT_FALSE(initializing_client()->data_observer()->IsOpen());
+}
+
+// This test sets up a call between two parties with audio, video. When audio
+// and video is setup and flowing and data channel is negotiated.
+TEST_F(JsepPeerConnectionP2PTestClient, AddDataChannelAfterRenegotiation) {
+  FakeConstraints setup_constraints;
+  setup_constraints.SetAllowRtpDataChannels();
+  ASSERT_TRUE(CreateTestClients(&setup_constraints, &setup_constraints));
+  LocalP2PTest();
+  initializing_client()->CreateDataChannel();
+  // Send new offer and answer.
+  initializing_client()->Negotiate();
+  ASSERT_TRUE(initializing_client()->data_channel() != NULL);
+  ASSERT_TRUE(receiving_client()->data_channel() != NULL);
+  EXPECT_TRUE_WAIT(initializing_client()->data_observer()->IsOpen(),
+                   kMaxWaitMs);
+  EXPECT_TRUE_WAIT(receiving_client()->data_observer()->IsOpen(),
+                   kMaxWaitMs);
+}
+
+// This test sets up a call between two parties with audio, and video.
+// During the call, the initializing side restart ice and the test verifies that
+// new ice candidates are generated and audio and video still can flow.
+TEST_F(JsepPeerConnectionP2PTestClient, IceRestart) {
+  ASSERT_TRUE(CreateTestClients());
+
+  // Negotiate and wait for ice completion and make sure audio and video plays.
+  LocalP2PTest();
+
+  // Create a SDP string of the first audio candidate for both clients.
+  const webrtc::IceCandidateCollection* audio_candidates_initiator =
+      initializing_client()->pc()->local_description()->candidates(0);
+  const webrtc::IceCandidateCollection* audio_candidates_receiver =
+      receiving_client()->pc()->local_description()->candidates(0);
+  ASSERT_GT(audio_candidates_initiator->count(), 0u);
+  ASSERT_GT(audio_candidates_receiver->count(), 0u);
+  std::string initiator_candidate;
+  EXPECT_TRUE(
+      audio_candidates_initiator->at(0)->ToString(&initiator_candidate));
+  std::string receiver_candidate;
+  EXPECT_TRUE(audio_candidates_receiver->at(0)->ToString(&receiver_candidate));
+
+  // Restart ice on the initializing client.
+  receiving_client()->SetExpectIceRestart(true);
+  initializing_client()->IceRestart();
+
+  // Negotiate and wait for ice completion again and make sure audio and video
+  // plays.
+  LocalP2PTest();
+
+  // Create a SDP string of the first audio candidate for both clients again.
+  const webrtc::IceCandidateCollection* audio_candidates_initiator_restart =
+      initializing_client()->pc()->local_description()->candidates(0);
+  const webrtc::IceCandidateCollection* audio_candidates_reciever_restart =
+      receiving_client()->pc()->local_description()->candidates(0);
+  ASSERT_GT(audio_candidates_initiator_restart->count(), 0u);
+  ASSERT_GT(audio_candidates_reciever_restart->count(), 0u);
+  std::string initiator_candidate_restart;
+  EXPECT_TRUE(audio_candidates_initiator_restart->at(0)->ToString(
+      &initiator_candidate_restart));
+  std::string receiver_candidate_restart;
+  EXPECT_TRUE(audio_candidates_reciever_restart->at(0)->ToString(
+      &receiver_candidate_restart));
+
+  // Verify that the first candidates in the local session descriptions has
+  // changed.
+  EXPECT_NE(initiator_candidate, initiator_candidate_restart);
+  EXPECT_NE(receiver_candidate, receiver_candidate_restart);
+}
+
+
+// This test sets up a Jsep call between two parties with external
+// VideoDecoderFactory.
+TEST_F(JsepPeerConnectionP2PTestClient, LocalP2PTestWithVideoDecoderFactory) {
+  ASSERT_TRUE(CreateTestClients());
+  EnableVideoDecoderFactory();
+  LocalP2PTest();
+}
diff --git a/talk/app/webrtc/peerconnectionfactory.cc b/talk/app/webrtc/peerconnectionfactory.cc
new file mode 100644
index 0000000..7ae5a3b
--- /dev/null
+++ b/talk/app/webrtc/peerconnectionfactory.cc
@@ -0,0 +1,369 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/app/webrtc/peerconnectionfactory.h"
+
+#include "talk/app/webrtc/audiotrack.h"
+#include "talk/app/webrtc/localaudiosource.h"
+#include "talk/app/webrtc/localvideosource.h"
+#include "talk/app/webrtc/mediastreamproxy.h"
+#include "talk/app/webrtc/mediastreamtrackproxy.h"
+#include "talk/app/webrtc/peerconnection.h"
+#include "talk/app/webrtc/peerconnectionproxy.h"
+#include "talk/app/webrtc/portallocatorfactory.h"
+#include "talk/app/webrtc/videosourceproxy.h"
+#include "talk/app/webrtc/videotrack.h"
+#include "talk/media/devices/dummydevicemanager.h"
+#include "talk/media/webrtc/webrtcmediaengine.h"
+#include "talk/media/webrtc/webrtcvideodecoderfactory.h"
+#include "talk/media/webrtc/webrtcvideoencoderfactory.h"
+#include "webrtc/modules/audio_device/include/audio_device.h"
+
+using talk_base::scoped_refptr;
+
+namespace {
+
+typedef talk_base::TypedMessageData<bool> InitMessageData;
+
+struct CreatePeerConnectionParams : public talk_base::MessageData {
+  CreatePeerConnectionParams(
+      const webrtc::PeerConnectionInterface::IceServers& configuration,
+      const webrtc::MediaConstraintsInterface* constraints,
+      webrtc::PortAllocatorFactoryInterface* allocator_factory,
+      webrtc::PeerConnectionObserver* observer)
+      : configuration(configuration),
+        constraints(constraints),
+        allocator_factory(allocator_factory),
+        observer(observer) {
+  }
+  scoped_refptr<webrtc::PeerConnectionInterface> peerconnection;
+  const webrtc::PeerConnectionInterface::IceServers& configuration;
+  const webrtc::MediaConstraintsInterface* constraints;
+  scoped_refptr<webrtc::PortAllocatorFactoryInterface> allocator_factory;
+  webrtc::PeerConnectionObserver* observer;
+};
+
+struct CreatePeerConnectionParamsDeprecated : public talk_base::MessageData {
+  CreatePeerConnectionParamsDeprecated(
+      const std::string& configuration,
+      webrtc::PortAllocatorFactoryInterface* allocator_factory,
+        webrtc::PeerConnectionObserver* observer)
+      : configuration(configuration),
+        allocator_factory(allocator_factory),
+        observer(observer) {
+  }
+  scoped_refptr<webrtc::PeerConnectionInterface> peerconnection;
+  const std::string& configuration;
+  scoped_refptr<webrtc::PortAllocatorFactoryInterface> allocator_factory;
+  webrtc::PeerConnectionObserver* observer;
+};
+
+struct CreateAudioSourceParams : public talk_base::MessageData {
+  explicit CreateAudioSourceParams(
+      const webrtc::MediaConstraintsInterface* constraints)
+      : constraints(constraints) {
+  }
+  const webrtc::MediaConstraintsInterface* constraints;
+  scoped_refptr<webrtc::AudioSourceInterface> source;
+};
+
+struct CreateVideoSourceParams : public talk_base::MessageData {
+  CreateVideoSourceParams(cricket::VideoCapturer* capturer,
+                          const webrtc::MediaConstraintsInterface* constraints)
+      : capturer(capturer),
+        constraints(constraints) {
+  }
+  cricket::VideoCapturer* capturer;
+  const webrtc::MediaConstraintsInterface* constraints;
+  scoped_refptr<webrtc::VideoSourceInterface> source;
+};
+
+enum {
+  MSG_INIT_FACTORY = 1,
+  MSG_TERMINATE_FACTORY,
+  MSG_CREATE_PEERCONNECTION,
+  MSG_CREATE_AUDIOSOURCE,
+  MSG_CREATE_VIDEOSOURCE,
+};
+
+}  // namespace
+
+namespace webrtc {
+
+scoped_refptr<PeerConnectionFactoryInterface>
+CreatePeerConnectionFactory() {
+  scoped_refptr<PeerConnectionFactory> pc_factory(
+      new talk_base::RefCountedObject<PeerConnectionFactory>());
+
+  if (!pc_factory->Initialize()) {
+    return NULL;
+  }
+  return pc_factory;
+}
+
+scoped_refptr<PeerConnectionFactoryInterface>
+CreatePeerConnectionFactory(
+    talk_base::Thread* worker_thread,
+    talk_base::Thread* signaling_thread,
+    AudioDeviceModule* default_adm,
+    cricket::WebRtcVideoEncoderFactory* encoder_factory,
+    cricket::WebRtcVideoDecoderFactory* decoder_factory) {
+  scoped_refptr<PeerConnectionFactory> pc_factory(
+      new talk_base::RefCountedObject<PeerConnectionFactory>(
+          worker_thread, signaling_thread, default_adm,
+          encoder_factory, decoder_factory));
+  if (!pc_factory->Initialize()) {
+    return 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,
+    AudioDeviceModule* default_adm,
+    cricket::WebRtcVideoEncoderFactory* video_encoder_factory,
+    cricket::WebRtcVideoDecoderFactory* video_decoder_factory)
+    : owns_ptrs_(false),
+      signaling_thread_(signaling_thread),
+      worker_thread_(worker_thread),
+      default_adm_(default_adm),
+      video_encoder_factory_(video_encoder_factory),
+      video_decoder_factory_(video_decoder_factory) {
+  ASSERT(worker_thread != NULL);
+  ASSERT(signaling_thread != 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->constraints,
+                                                     pdata->allocator_factory,
+                                                     pdata->observer);
+      break;
+    }
+    case MSG_CREATE_AUDIOSOURCE: {
+      CreateAudioSourceParams* pdata =
+          static_cast<CreateAudioSourceParams*>(msg->pdata);
+      pdata->source = CreateAudioSource_s(pdata->constraints);
+      break;
+    }
+    case MSG_CREATE_VIDEOSOURCE: {
+      CreateVideoSourceParams* pdata =
+          static_cast<CreateVideoSourceParams*> (msg->pdata);
+      pdata->source = CreateVideoSource_s(pdata->capturer, pdata->constraints);
+      break;
+    }
+  }
+}
+
+bool PeerConnectionFactory::Initialize_s() {
+  talk_base::InitRandom(talk_base::Time());
+
+  allocator_factory_ = PortAllocatorFactory::Create(worker_thread_);
+  if (!allocator_factory_)
+    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.
+                                     video_encoder_factory_.get(),
+                                     video_decoder_factory_.get()));
+
+  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);
+  allocator_factory_ = NULL;
+}
+
+talk_base::scoped_refptr<AudioSourceInterface>
+PeerConnectionFactory::CreateAudioSource_s(
+    const MediaConstraintsInterface* constraints) {
+  talk_base::scoped_refptr<LocalAudioSource> source(
+      LocalAudioSource::Create(constraints));
+  return source;
+}
+
+talk_base::scoped_refptr<VideoSourceInterface>
+PeerConnectionFactory::CreateVideoSource_s(
+    cricket::VideoCapturer* capturer,
+    const MediaConstraintsInterface* constraints) {
+  talk_base::scoped_refptr<LocalVideoSource> source(
+      LocalVideoSource::Create(channel_manager_.get(), capturer,
+                               constraints));
+  return VideoSourceProxy::Create(signaling_thread_, source);
+}
+
+scoped_refptr<PeerConnectionInterface>
+PeerConnectionFactory::CreatePeerConnection(
+    const PeerConnectionInterface::IceServers& configuration,
+    const MediaConstraintsInterface* constraints,
+    PortAllocatorFactoryInterface* allocator_factory,
+    DTLSIdentityServiceInterface* dtls_identity_service,
+    PeerConnectionObserver* observer) {
+  CreatePeerConnectionParams params(configuration, constraints,
+                                    allocator_factory, observer);
+  signaling_thread_->Send(this, MSG_CREATE_PEERCONNECTION, &params);
+  return params.peerconnection;
+}
+
+scoped_refptr<PeerConnectionInterface>
+PeerConnectionFactory::CreatePeerConnection(
+    const PeerConnectionInterface::IceServers& configuration,
+    const MediaConstraintsInterface* constraints,
+    DTLSIdentityServiceInterface* dtls_identity_service,
+    PeerConnectionObserver* observer) {
+  return CreatePeerConnection(
+      configuration, constraints, NULL, dtls_identity_service, observer);
+}
+
+talk_base::scoped_refptr<PeerConnectionInterface>
+PeerConnectionFactory::CreatePeerConnection_s(
+    const PeerConnectionInterface::IceServers& configuration,
+    const MediaConstraintsInterface* constraints,
+    PortAllocatorFactoryInterface* allocator_factory,
+    PeerConnectionObserver* observer) {
+  ASSERT(allocator_factory || allocator_factory_);
+  talk_base::scoped_refptr<PeerConnection> pc(
+      new talk_base::RefCountedObject<PeerConnection>(this));
+  if (!pc->Initialize(
+      configuration,
+      constraints,
+      allocator_factory ? allocator_factory : allocator_factory_.get(),
+      observer)) {
+    return NULL;
+  }
+  return PeerConnectionProxy::Create(signaling_thread(), pc);
+}
+
+scoped_refptr<MediaStreamInterface>
+PeerConnectionFactory::CreateLocalMediaStream(const std::string& label) {
+  return MediaStreamProxy::Create(signaling_thread_,
+                                  MediaStream::Create(label));
+}
+
+talk_base::scoped_refptr<AudioSourceInterface>
+PeerConnectionFactory::CreateAudioSource(
+    const MediaConstraintsInterface* constraints) {
+  CreateAudioSourceParams params(constraints);
+  signaling_thread_->Send(this, MSG_CREATE_AUDIOSOURCE, &params);
+  return params.source;
+}
+
+talk_base::scoped_refptr<VideoSourceInterface>
+PeerConnectionFactory::CreateVideoSource(
+    cricket::VideoCapturer* capturer,
+    const MediaConstraintsInterface* constraints) {
+
+  CreateVideoSourceParams params(capturer,
+                                 constraints);
+  signaling_thread_->Send(this, MSG_CREATE_VIDEOSOURCE, &params);
+  return params.source;
+}
+
+talk_base::scoped_refptr<VideoTrackInterface>
+PeerConnectionFactory::CreateVideoTrack(
+    const std::string& id,
+    VideoSourceInterface* source) {
+  talk_base::scoped_refptr<VideoTrackInterface> track(
+      VideoTrack::Create(id, source));
+  return VideoTrackProxy::Create(signaling_thread_, track);
+}
+
+scoped_refptr<AudioTrackInterface> PeerConnectionFactory::CreateAudioTrack(
+    const std::string& id,
+    AudioSourceInterface* source) {
+  talk_base::scoped_refptr<AudioTrackInterface> track(
+      AudioTrack::Create(id, source));
+  return AudioTrackProxy::Create(signaling_thread_, track);
+}
+
+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_;
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/peerconnectionfactory.h b/talk/app/webrtc/peerconnectionfactory.h
new file mode 100644
index 0000000..c0e15e3
--- /dev/null
+++ b/talk/app/webrtc/peerconnectionfactory.h
@@ -0,0 +1,127 @@
+/*
+ * 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_PEERCONNECTIONFACTORY_H_
+#define TALK_APP_WEBRTC_PEERCONNECTIONFACTORY_H_
+
+#include <string>
+
+#include "talk/app/webrtc/mediastreaminterface.h"
+#include "talk/app/webrtc/peerconnectioninterface.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/thread.h"
+#include "talk/session/media/channelmanager.h"
+
+namespace webrtc {
+
+class PeerConnectionFactory : public PeerConnectionFactoryInterface,
+                              public talk_base::MessageHandler {
+ public:
+  virtual talk_base::scoped_refptr<PeerConnectionInterface>
+      CreatePeerConnection(
+          const PeerConnectionInterface::IceServers& configuration,
+          const MediaConstraintsInterface* constraints,
+          DTLSIdentityServiceInterface* dtls_identity_service,
+          PeerConnectionObserver* observer);
+
+  virtual talk_base::scoped_refptr<PeerConnectionInterface>
+      CreatePeerConnection(
+          const PeerConnectionInterface::IceServers& configuration,
+          const MediaConstraintsInterface* constraints,
+          PortAllocatorFactoryInterface* allocator_factory,
+          DTLSIdentityServiceInterface* dtls_identity_service,
+          PeerConnectionObserver* observer);
+  bool Initialize();
+
+  virtual talk_base::scoped_refptr<MediaStreamInterface>
+      CreateLocalMediaStream(const std::string& label);
+
+  virtual talk_base::scoped_refptr<AudioSourceInterface> CreateAudioSource(
+      const MediaConstraintsInterface* constraints);
+
+  virtual talk_base::scoped_refptr<VideoSourceInterface> CreateVideoSource(
+      cricket::VideoCapturer* capturer,
+      const MediaConstraintsInterface* constraints);
+
+  virtual talk_base::scoped_refptr<VideoTrackInterface>
+      CreateVideoTrack(const std::string& id,
+                       VideoSourceInterface* video_source);
+
+  virtual talk_base::scoped_refptr<AudioTrackInterface>
+      CreateAudioTrack(const std::string& id,
+                       AudioSourceInterface* audio_source);
+
+  virtual cricket::ChannelManager* channel_manager();
+  virtual talk_base::Thread* signaling_thread();
+  virtual talk_base::Thread* worker_thread();
+
+ protected:
+  PeerConnectionFactory();
+  PeerConnectionFactory(
+      talk_base::Thread* worker_thread,
+      talk_base::Thread* signaling_thread,
+      AudioDeviceModule* default_adm,
+      cricket::WebRtcVideoEncoderFactory* video_encoder_factory,
+      cricket::WebRtcVideoDecoderFactory* video_decoder_factory);
+  virtual ~PeerConnectionFactory();
+
+
+ private:
+  bool Initialize_s();
+  void Terminate_s();
+  talk_base::scoped_refptr<AudioSourceInterface> CreateAudioSource_s(
+      const MediaConstraintsInterface* constraints);
+  talk_base::scoped_refptr<VideoSourceInterface> CreateVideoSource_s(
+      cricket::VideoCapturer* capturer,
+      const MediaConstraintsInterface* constraints);
+  talk_base::scoped_refptr<PeerConnectionInterface> CreatePeerConnection_s(
+      const PeerConnectionInterface::IceServers& configuration,
+      const MediaConstraintsInterface* constraints,
+      PortAllocatorFactoryInterface* allocator_factory,
+      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_;
+  // External Video encoder factory. This can be NULL if the client has not
+  // injected any. In that case, video engine will use the internal SW encoder.
+  talk_base::scoped_ptr<cricket::WebRtcVideoEncoderFactory>
+      video_encoder_factory_;
+  // External Video decoder factory. This can be NULL if the client has not
+  // injected any. In that case, video engine will use the internal SW decoder.
+  talk_base::scoped_ptr<cricket::WebRtcVideoDecoderFactory>
+      video_decoder_factory_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_PEERCONNECTIONFACTORY_H_
diff --git a/talk/app/webrtc/peerconnectionfactory_unittest.cc b/talk/app/webrtc/peerconnectionfactory_unittest.cc
new file mode 100644
index 0000000..6d54204
--- /dev/null
+++ b/talk/app/webrtc/peerconnectionfactory_unittest.cc
@@ -0,0 +1,270 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 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/fakeportallocatorfactory.h"
+#include "talk/app/webrtc/mediastreaminterface.h"
+#include "talk/app/webrtc/peerconnectionfactory.h"
+#include "talk/app/webrtc/videosourceinterface.h"
+#include "talk/app/webrtc/test/fakevideotrackrenderer.h"
+#include "talk/base/gunit.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/thread.h"
+#include "talk/media/base/fakevideocapturer.h"
+#include "talk/media/webrtc/webrtccommon.h"
+#include "talk/media/webrtc/webrtcvoe.h"
+
+using webrtc::FakeVideoTrackRenderer;
+using webrtc::MediaStreamInterface;
+using webrtc::PeerConnectionFactoryInterface;
+using webrtc::PeerConnectionInterface;
+using webrtc::PeerConnectionObserver;
+using webrtc::PortAllocatorFactoryInterface;
+using webrtc::VideoSourceInterface;
+using webrtc::VideoTrackInterface;
+
+namespace {
+
+typedef std::vector<PortAllocatorFactoryInterface::StunConfiguration>
+    StunConfigurations;
+typedef std::vector<PortAllocatorFactoryInterface::TurnConfiguration>
+    TurnConfigurations;
+
+static const char kStunIceServer[] = "stun:stun.l.google.com:19302";
+static const char kTurnIceServer[] = "turn:test%40hello.com@test.com:1234";
+static const char kTurnIceServerWithTransport[] =
+    "turn:test@hello.com?transport=tcp";
+static const char kSecureTurnIceServer[] =
+    "turns:test@hello.com?transport=tcp";
+static const char kTurnIceServerWithNoUsernameInUri[] =
+    "turn:test.com:1234";
+static const char kTurnPassword[] = "turnpassword";
+static const int kDefaultPort = 3478;
+static const char kTurnUsername[] = "test";
+
+class NullPeerConnectionObserver : public PeerConnectionObserver {
+ public:
+  virtual void OnError() {}
+  virtual void OnMessage(const std::string& msg) {}
+  virtual void OnSignalingMessage(const std::string& msg) {}
+  virtual void OnSignalingChange(
+      PeerConnectionInterface::SignalingState new_state) {}
+  virtual void OnAddStream(MediaStreamInterface* stream) {}
+  virtual void OnRemoveStream(MediaStreamInterface* stream) {}
+  virtual void OnRenegotiationNeeded() {}
+  virtual void OnIceConnectionChange(
+      PeerConnectionInterface::IceConnectionState new_state) {}
+  virtual void OnIceGatheringChange(
+      PeerConnectionInterface::IceGatheringState new_state) {}
+  virtual void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) {}
+};
+
+}  // namespace
+
+class PeerConnectionFactoryTest : public testing::Test {
+  void SetUp() {
+    factory_ = webrtc::CreatePeerConnectionFactory(talk_base::Thread::Current(),
+                                                   talk_base::Thread::Current(),
+                                                   NULL,
+                                                   NULL,
+                                                   NULL);
+
+    ASSERT_TRUE(factory_.get() != NULL);
+    allocator_factory_ =  webrtc::FakePortAllocatorFactory::Create();
+  }
+
+ protected:
+  void VerifyStunConfigurations(StunConfigurations stun_config) {
+    webrtc::FakePortAllocatorFactory* allocator =
+        static_cast<webrtc::FakePortAllocatorFactory*>(
+            allocator_factory_.get());
+    ASSERT_TRUE(allocator != NULL);
+    EXPECT_EQ(stun_config.size(), allocator->stun_configs().size());
+    for (size_t i = 0; i < stun_config.size(); ++i) {
+      EXPECT_EQ(stun_config[i].server.ToString(),
+                allocator->stun_configs()[i].server.ToString());
+    }
+  }
+
+  void VerifyTurnConfigurations(TurnConfigurations turn_config) {
+    webrtc::FakePortAllocatorFactory* allocator =
+        static_cast<webrtc::FakePortAllocatorFactory*>(
+            allocator_factory_.get());
+    ASSERT_TRUE(allocator != NULL);
+    EXPECT_EQ(turn_config.size(), allocator->turn_configs().size());
+    for (size_t i = 0; i < turn_config.size(); ++i) {
+      EXPECT_EQ(turn_config[i].server.ToString(),
+                allocator->turn_configs()[i].server.ToString());
+      EXPECT_EQ(turn_config[i].username, allocator->turn_configs()[i].username);
+      EXPECT_EQ(turn_config[i].password, allocator->turn_configs()[i].password);
+      EXPECT_EQ(turn_config[i].transport_type,
+                allocator->turn_configs()[i].transport_type);
+    }
+  }
+
+  talk_base::scoped_refptr<PeerConnectionFactoryInterface> factory_;
+  NullPeerConnectionObserver observer_;
+  talk_base::scoped_refptr<PortAllocatorFactoryInterface> allocator_factory_;
+};
+
+// Verify creation of PeerConnection using internal ADM, video factory and
+// internal libjingle threads.
+TEST(PeerConnectionFactoryTestInternal, CreatePCUsingInternalModules) {
+  talk_base::scoped_refptr<PeerConnectionFactoryInterface> factory(
+      webrtc::CreatePeerConnectionFactory());
+
+  NullPeerConnectionObserver observer;
+  webrtc::PeerConnectionInterface::IceServers servers;
+
+  talk_base::scoped_refptr<PeerConnectionInterface> pc(
+      factory->CreatePeerConnection(servers, NULL, NULL, &observer));
+
+  EXPECT_TRUE(pc.get() != NULL);
+}
+
+// This test verifies creation of PeerConnection with valid STUN and TURN
+// configuration. Also verifies the URL's parsed correctly as expected.
+TEST_F(PeerConnectionFactoryTest, CreatePCUsingIceServers) {
+  webrtc::PeerConnectionInterface::IceServers ice_servers;
+  webrtc::PeerConnectionInterface::IceServer ice_server;
+  ice_server.uri = kStunIceServer;
+  ice_servers.push_back(ice_server);
+  ice_server.uri = kTurnIceServer;
+  ice_server.password = kTurnPassword;
+  ice_servers.push_back(ice_server);
+  talk_base::scoped_refptr<PeerConnectionInterface> pc(
+      factory_->CreatePeerConnection(ice_servers, NULL,
+                                     allocator_factory_.get(),
+                                     NULL,
+                                     &observer_));
+  EXPECT_TRUE(pc.get() != NULL);
+  StunConfigurations stun_configs;
+  webrtc::PortAllocatorFactoryInterface::StunConfiguration stun(
+      "stun.l.google.com", 19302);
+  stun_configs.push_back(stun);
+  webrtc::PortAllocatorFactoryInterface::StunConfiguration stun1(
+        "test.com", 1234);
+  stun_configs.push_back(stun1);
+  VerifyStunConfigurations(stun_configs);
+  TurnConfigurations turn_configs;
+  webrtc::PortAllocatorFactoryInterface::TurnConfiguration turn(
+      "test.com", 1234, "test@hello.com", kTurnPassword, "udp");
+  turn_configs.push_back(turn);
+  VerifyTurnConfigurations(turn_configs);
+}
+
+TEST_F(PeerConnectionFactoryTest, CreatePCUsingNoUsernameInUri) {
+  webrtc::PeerConnectionInterface::IceServers ice_servers;
+  webrtc::PeerConnectionInterface::IceServer ice_server;
+  ice_server.uri = kStunIceServer;
+  ice_servers.push_back(ice_server);
+  ice_server.uri = kTurnIceServerWithNoUsernameInUri;
+  ice_server.username = kTurnUsername;
+  ice_server.password = kTurnPassword;
+  ice_servers.push_back(ice_server);
+  talk_base::scoped_refptr<PeerConnectionInterface> pc(
+      factory_->CreatePeerConnection(ice_servers, NULL,
+                                     allocator_factory_.get(),
+                                     NULL,
+                                     &observer_));
+  EXPECT_TRUE(pc.get() != NULL);
+  TurnConfigurations turn_configs;
+  webrtc::PortAllocatorFactoryInterface::TurnConfiguration turn(
+      "test.com", 1234, kTurnUsername, kTurnPassword, "udp");
+  turn_configs.push_back(turn);
+  VerifyTurnConfigurations(turn_configs);
+}
+
+// This test verifies the PeerConnection created properly with TURN url which
+// has transport parameter in it.
+TEST_F(PeerConnectionFactoryTest, CreatePCUsingTurnUrlWithTransportParam) {
+  webrtc::PeerConnectionInterface::IceServers ice_servers;
+  webrtc::PeerConnectionInterface::IceServer ice_server;
+  ice_server.uri = kTurnIceServerWithTransport;
+  ice_server.password = kTurnPassword;
+  ice_servers.push_back(ice_server);
+  talk_base::scoped_refptr<PeerConnectionInterface> pc(
+      factory_->CreatePeerConnection(ice_servers, NULL,
+                                     allocator_factory_.get(),
+                                     NULL,
+                                     &observer_));
+  EXPECT_TRUE(pc.get() != NULL);
+  TurnConfigurations turn_configs;
+  webrtc::PortAllocatorFactoryInterface::TurnConfiguration turn(
+      "hello.com", kDefaultPort, "test", kTurnPassword, "tcp");
+  turn_configs.push_back(turn);
+  VerifyTurnConfigurations(turn_configs);
+  StunConfigurations stun_configs;
+  webrtc::PortAllocatorFactoryInterface::StunConfiguration stun(
+        "hello.com", kDefaultPort);
+  stun_configs.push_back(stun);
+  VerifyStunConfigurations(stun_configs);
+}
+
+// This test verifies factory failed to create a peerconneciton object when
+// a valid secure TURN url passed. Connecting to a secure TURN server is not
+// supported currently.
+TEST_F(PeerConnectionFactoryTest, CreatePCUsingSecureTurnUrl) {
+  webrtc::PeerConnectionInterface::IceServers ice_servers;
+  webrtc::PeerConnectionInterface::IceServer ice_server;
+  ice_server.uri = kSecureTurnIceServer;
+  ice_server.password = kTurnPassword;
+  ice_servers.push_back(ice_server);
+  talk_base::scoped_refptr<PeerConnectionInterface> pc(
+      factory_->CreatePeerConnection(ice_servers, NULL,
+                                     allocator_factory_.get(),
+                                     NULL,
+                                     &observer_));
+  EXPECT_TRUE(pc.get() == NULL);
+  TurnConfigurations turn_configs;
+  VerifyTurnConfigurations(turn_configs);
+}
+
+// This test verifies the captured stream is rendered locally using a
+// local video track.
+TEST_F(PeerConnectionFactoryTest, LocalRendering) {
+  cricket::FakeVideoCapturer* capturer = new cricket::FakeVideoCapturer();
+  // The source take ownership of |capturer|.
+  talk_base::scoped_refptr<VideoSourceInterface> source(
+      factory_->CreateVideoSource(capturer, NULL));
+  ASSERT_TRUE(source.get() != NULL);
+  talk_base::scoped_refptr<VideoTrackInterface> track(
+      factory_->CreateVideoTrack("testlabel", source));
+  ASSERT_TRUE(track.get() != NULL);
+  FakeVideoTrackRenderer local_renderer(track);
+
+  EXPECT_EQ(0, local_renderer.num_rendered_frames());
+  EXPECT_TRUE(capturer->CaptureFrame());
+  EXPECT_EQ(1, local_renderer.num_rendered_frames());
+
+  track->set_enabled(false);
+  EXPECT_TRUE(capturer->CaptureFrame());
+  EXPECT_EQ(1, local_renderer.num_rendered_frames());
+
+  track->set_enabled(true);
+  EXPECT_TRUE(capturer->CaptureFrame());
+  EXPECT_EQ(2, local_renderer.num_rendered_frames());
+}
diff --git a/talk/app/webrtc/peerconnectioninterface.h b/talk/app/webrtc/peerconnectioninterface.h
new file mode 100644
index 0000000..9a7cdd0
--- /dev/null
+++ b/talk/app/webrtc/peerconnectioninterface.h
@@ -0,0 +1,451 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 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 using Jsep.
+// 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.
+// 3. Create local MediaStream and MediaTracks using the PeerConnectionFactory
+// and add it to PeerConnection by calling AddStream.
+// 4. Create an offer and serialize it and send it to the remote peer.
+// 5. Once an ice candidate have been found PeerConnection will call the
+// observer function OnIceCandidate. The candidates must also be serialized and
+// sent to the remote peer.
+// 6. Once an answer is received from the remote peer, call
+// SetLocalSessionDescription with the offer and SetRemoteSessionDescription
+// with the remote answer.
+// 7. Once a remote candidate is received from the remote peer, provide it to
+// the peerconnection by calling AddIceCandidate.
+
+
+// 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 a new PeerConnection.
+// 3. Provide the remote offer to the new PeerConnection object by calling
+// SetRemoteSessionDescription.
+// 4. Generate an answer to the remote offer by calling CreateAnswer and send it
+// back to the remote peer.
+// 5. Provide the local answer to the new PeerConnection by calling
+// SetLocalSessionDescription with the answer.
+// 6. Provide the remote ice candidates by calling AddIceCandidate.
+// 7. Once a candidate have been found PeerConnection will call the observer
+// function OnIceCandidate. Send these candidates to the remote peer.
+
+#ifndef TALK_APP_WEBRTC_PEERCONNECTIONINTERFACE_H_
+#define TALK_APP_WEBRTC_PEERCONNECTIONINTERFACE_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/app/webrtc/datachannelinterface.h"
+#include "talk/app/webrtc/dtmfsenderinterface.h"
+#include "talk/app/webrtc/jsep.h"
+#include "talk/app/webrtc/mediastreaminterface.h"
+#include "talk/app/webrtc/statstypes.h"
+#include "talk/base/socketaddress.h"
+
+namespace talk_base {
+class Thread;
+}
+
+namespace cricket {
+class PortAllocator;
+class WebRtcVideoDecoderFactory;
+class WebRtcVideoEncoderFactory;
+}
+
+namespace webrtc {
+class AudioDeviceModule;
+class MediaConstraintsInterface;
+
+// MediaStream container interface.
+class StreamCollectionInterface : public talk_base::RefCountInterface {
+ public:
+  // TODO(ronghuawu): Update the function names to c++ style, e.g. find -> Find.
+  virtual size_t count() = 0;
+  virtual MediaStreamInterface* at(size_t index) = 0;
+  virtual MediaStreamInterface* find(const std::string& label) = 0;
+  virtual MediaStreamTrackInterface* FindAudioTrack(
+      const std::string& id) = 0;
+  virtual MediaStreamTrackInterface* FindVideoTrack(
+      const std::string& id) = 0;
+
+ protected:
+  // Dtor protected as objects shouldn't be deleted via this interface.
+  ~StreamCollectionInterface() {}
+};
+
+class StatsObserver : public talk_base::RefCountInterface {
+ public:
+  virtual void OnComplete(const std::vector<StatsReport>& reports) = 0;
+
+ protected:
+  virtual ~StatsObserver() {}
+};
+
+class PeerConnectionInterface : public talk_base::RefCountInterface {
+ public:
+  // See http://dev.w3.org/2011/webrtc/editor/webrtc.html#state-definitions .
+  enum SignalingState {
+    kStable,
+    kHaveLocalOffer,
+    kHaveLocalPrAnswer,
+    kHaveRemoteOffer,
+    kHaveRemotePrAnswer,
+    kClosed,
+  };
+
+  // TODO(bemasc): Remove IceState when callers are changed to
+  // IceConnection/GatheringState.
+  enum IceState {
+    kIceNew,
+    kIceGathering,
+    kIceWaiting,
+    kIceChecking,
+    kIceConnected,
+    kIceCompleted,
+    kIceFailed,
+    kIceClosed,
+  };
+
+  enum IceGatheringState {
+    kIceGatheringNew,
+    kIceGatheringGathering,
+    kIceGatheringComplete
+  };
+
+  enum IceConnectionState {
+    kIceConnectionNew,
+    kIceConnectionChecking,
+    kIceConnectionConnected,
+    kIceConnectionCompleted,
+    kIceConnectionFailed,
+    kIceConnectionDisconnected,
+    kIceConnectionClosed,
+  };
+
+  struct IceServer {
+    std::string uri;
+    std::string username;
+    std::string password;
+  };
+  typedef std::vector<IceServer> IceServers;
+
+  // Accessor methods to active local streams.
+  virtual talk_base::scoped_refptr<StreamCollectionInterface>
+      local_streams() = 0;
+
+  // Accessor methods to remote streams.
+  virtual talk_base::scoped_refptr<StreamCollectionInterface>
+      remote_streams() = 0;
+
+  // Add a new MediaStream to be sent on this PeerConnection.
+  // Note that a SessionDescription negotiation is needed before the
+  // remote peer can receive the stream.
+  virtual bool AddStream(MediaStreamInterface* stream,
+                         const MediaConstraintsInterface* constraints) = 0;
+
+  // Remove a MediaStream from this PeerConnection.
+  // Note that a SessionDescription negotiation is need before the
+  // remote peer is notified.
+  virtual void RemoveStream(MediaStreamInterface* stream) = 0;
+
+  // Returns pointer to the created DtmfSender on success.
+  // Otherwise returns NULL.
+  virtual talk_base::scoped_refptr<DtmfSenderInterface> CreateDtmfSender(
+      AudioTrackInterface* track) = 0;
+
+  virtual bool GetStats(StatsObserver* observer,
+                        MediaStreamTrackInterface* track) = 0;
+
+  virtual talk_base::scoped_refptr<DataChannelInterface> CreateDataChannel(
+      const std::string& label,
+      const DataChannelInit* config) = 0;
+
+  virtual const SessionDescriptionInterface* local_description() const = 0;
+  virtual const SessionDescriptionInterface* remote_description() const = 0;
+
+  // Create a new offer.
+  // The CreateSessionDescriptionObserver callback will be called when done.
+  virtual void CreateOffer(CreateSessionDescriptionObserver* observer,
+                           const MediaConstraintsInterface* constraints) = 0;
+  // Create an answer to an offer.
+  // The CreateSessionDescriptionObserver callback will be called when done.
+  virtual void CreateAnswer(CreateSessionDescriptionObserver* observer,
+                            const MediaConstraintsInterface* constraints) = 0;
+  // Sets the local session description.
+  // JsepInterface takes the ownership of |desc| even if it fails.
+  // The |observer| callback will be called when done.
+  virtual void SetLocalDescription(SetSessionDescriptionObserver* observer,
+                                   SessionDescriptionInterface* desc) = 0;
+  // Sets the remote session description.
+  // JsepInterface takes the ownership of |desc| even if it fails.
+  // The |observer| callback will be called when done.
+  virtual void SetRemoteDescription(SetSessionDescriptionObserver* observer,
+                                    SessionDescriptionInterface* desc) = 0;
+  // Restarts or updates the ICE Agent process of gathering local candidates
+  // and pinging remote candidates.
+  virtual bool UpdateIce(const IceServers& configuration,
+                         const MediaConstraintsInterface* constraints) = 0;
+  // Provides a remote candidate to the ICE Agent.
+  // A copy of the |candidate| will be created and added to the remote
+  // description. So the caller of this method still has the ownership of the
+  // |candidate|.
+  // TODO(ronghuawu): Consider to change this so that the AddIceCandidate will
+  // take the ownership of the |candidate|.
+  virtual bool AddIceCandidate(const IceCandidateInterface* candidate) = 0;
+
+  // Returns the current SignalingState.
+  virtual SignalingState signaling_state() = 0;
+
+  // TODO(bemasc): Remove ice_state when callers are changed to
+  // IceConnection/GatheringState.
+  // Returns the current IceState.
+  virtual IceState ice_state() = 0;
+  virtual IceConnectionState ice_connection_state() = 0;
+  virtual IceGatheringState ice_gathering_state() = 0;
+
+  // Terminates all media and closes the transport.
+  virtual void Close() = 0;
+
+ protected:
+  // Dtor protected as objects shouldn't be deleted via this interface.
+  ~PeerConnectionInterface() {}
+};
+
+// PeerConnection callback interface. Application should implement these
+// methods.
+class PeerConnectionObserver {
+ public:
+  enum StateType {
+    kSignalingState,
+    kIceState,
+  };
+
+  virtual void OnError() = 0;
+
+  // Triggered when the SignalingState changed.
+  virtual void OnSignalingChange(
+     PeerConnectionInterface::SignalingState new_state) {}
+
+  // Triggered when SignalingState or IceState have changed.
+  // TODO(bemasc): Remove once callers transition to OnSignalingChange.
+  virtual void OnStateChange(StateType state_changed) {}
+
+  // 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;
+
+  // Triggered when a remote peer open a data channel.
+  // TODO(perkj): Make pure virtual.
+  virtual void OnDataChannel(DataChannelInterface* data_channel) {}
+
+  // Triggered when renegotation is needed, for example the ICE has restarted.
+  virtual void OnRenegotiationNeeded() {}
+
+  // Called any time the IceConnectionState changes
+  virtual void OnIceConnectionChange(
+      PeerConnectionInterface::IceConnectionState new_state) {}
+
+  // Called any time the IceGatheringState changes
+  virtual void OnIceGatheringChange(
+      PeerConnectionInterface::IceGatheringState new_state) {}
+
+  // New Ice candidate have been found.
+  virtual void OnIceCandidate(const IceCandidateInterface* candidate) = 0;
+
+  // TODO(bemasc): Remove this once callers transition to OnIceGatheringChange.
+  // All Ice candidates have been found.
+  virtual void OnIceComplete() {}
+
+ protected:
+  // Dtor protected as objects shouldn't be deleted via this interface.
+  ~PeerConnectionObserver() {}
+};
+
+// 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,
+                      const std::string& transport_type)
+        : server(address, port),
+          username(username),
+          password(password),
+          transport_type(transport_type) {}
+    talk_base::SocketAddress server;
+    std::string username;
+    std::string password;
+    std::string transport_type;
+  };
+
+  virtual cricket::PortAllocator* CreatePortAllocator(
+      const std::vector<StunConfiguration>& stun_servers,
+      const std::vector<TurnConfiguration>& turn_configurations) = 0;
+
+ protected:
+  PortAllocatorFactoryInterface() {}
+  ~PortAllocatorFactoryInterface() {}
+};
+
+// Used to receive callbacks of DTLS identity requests.
+class DTLSIdentityRequestObserver : public talk_base::RefCountInterface {
+ public:
+  virtual void OnFailure(int error) = 0;
+  virtual void OnSuccess(const std::string& certificate,
+                         const std::string& private_key) = 0;
+ protected:
+  virtual ~DTLSIdentityRequestObserver() {}
+};
+
+class DTLSIdentityServiceInterface {
+ public:
+  // Asynchronously request a DTLS identity, including a self-signed certificate
+  // and the private key used to sign the certificate, from the identity store
+  // for the given identity name.
+  // DTLSIdentityRequestObserver::OnSuccess will be called with the identity if
+  // the request succeeded; DTLSIdentityRequestObserver::OnFailure will be
+  // called with an error code if the request failed.
+  //
+  // Only one request can be made at a time. If a second request is called
+  // before the first one completes, RequestIdentity will abort and return
+  // false.
+  //
+  // |identity_name| is an internal name selected by the client to identify an
+  // identity within an origin. E.g. an web site may cache the certificates used
+  // to communicate with differnent peers under different identity names.
+  //
+  // |common_name| is the common name used to generate the certificate. If the
+  // certificate already exists in the store, |common_name| is ignored.
+  //
+  // |observer| is the object to receive success or failure callbacks.
+  //
+  // Returns true if either OnFailure or OnSuccess will be called.
+  virtual bool RequestIdentity(
+      const std::string& identity_name,
+      const std::string& common_name,
+      DTLSIdentityRequestObserver* observer) = 0;
+};
+
+// 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 an application decides to provide its own threads and network
+// implementation of these classes it should use the alternate
+// CreatePeerConnectionFactory method which accepts threads as input and use the
+// CreatePeerConnection version that takes a PortAllocatorFactoryInterface as
+// argument.
+class PeerConnectionFactoryInterface : public talk_base::RefCountInterface {
+ public:
+  virtual talk_base::scoped_refptr<PeerConnectionInterface>
+     CreatePeerConnection(
+         const PeerConnectionInterface::IceServers& configuration,
+         const MediaConstraintsInterface* constraints,
+         DTLSIdentityServiceInterface* dtls_identity_service,
+         PeerConnectionObserver* observer) = 0;
+  virtual talk_base::scoped_refptr<PeerConnectionInterface>
+      CreatePeerConnection(
+          const PeerConnectionInterface::IceServers& configuration,
+          const MediaConstraintsInterface* constraints,
+          PortAllocatorFactoryInterface* allocator_factory,
+          DTLSIdentityServiceInterface* dtls_identity_service,
+          PeerConnectionObserver* observer) = 0;
+  virtual talk_base::scoped_refptr<MediaStreamInterface>
+      CreateLocalMediaStream(const std::string& label) = 0;
+
+  // Creates a AudioSourceInterface.
+  // |constraints| decides audio processing settings but can be NULL.
+  virtual talk_base::scoped_refptr<AudioSourceInterface> CreateAudioSource(
+      const MediaConstraintsInterface* constraints) = 0;
+
+  // Creates a VideoSourceInterface. The new source take ownership of
+  // |capturer|. |constraints| decides video resolution and frame rate but can
+  // be NULL.
+  virtual talk_base::scoped_refptr<VideoSourceInterface> CreateVideoSource(
+      cricket::VideoCapturer* capturer,
+      const MediaConstraintsInterface* constraints) = 0;
+
+  // Creates a new local VideoTrack. The same |source| can be used in several
+  // tracks.
+  virtual talk_base::scoped_refptr<VideoTrackInterface>
+      CreateVideoTrack(const std::string& label,
+                       VideoSourceInterface* source) = 0;
+
+  // Creates an new AudioTrack. At the moment |source| can be NULL.
+  virtual talk_base::scoped_refptr<AudioTrackInterface>
+      CreateAudioTrack(const std::string& label,
+                       AudioSourceInterface* source) = 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 |factory|, |default_adm|, and optionally |encoder_factory| and
+// |decoder_factory| transferred to the returned factory.
+talk_base::scoped_refptr<PeerConnectionFactoryInterface>
+CreatePeerConnectionFactory(
+    talk_base::Thread* worker_thread,
+    talk_base::Thread* signaling_thread,
+    AudioDeviceModule* default_adm,
+    cricket::WebRtcVideoEncoderFactory* encoder_factory,
+    cricket::WebRtcVideoDecoderFactory* decoder_factory);
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_PEERCONNECTIONINTERFACE_H_
diff --git a/talk/app/webrtc/peerconnectioninterface_unittest.cc b/talk/app/webrtc/peerconnectioninterface_unittest.cc
new file mode 100644
index 0000000..782bba1
--- /dev/null
+++ b/talk/app/webrtc/peerconnectioninterface_unittest.cc
@@ -0,0 +1,1220 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/jsepsessiondescription.h"
+#include "talk/app/webrtc/localvideosource.h"
+#include "talk/app/webrtc/mediastreaminterface.h"
+#include "talk/app/webrtc/peerconnectioninterface.h"
+#include "talk/app/webrtc/test/fakeconstraints.h"
+#include "talk/app/webrtc/test/mockpeerconnectionobservers.h"
+#include "talk/app/webrtc/test/testsdpstrings.h"
+#include "talk/base/gunit.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sslstreamadapter.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+#include "talk/media/base/fakevideocapturer.h"
+#include "talk/session/media/mediasession.h"
+
+static const char kStreamLabel1[] = "local_stream_1";
+static const char kStreamLabel2[] = "local_stream_2";
+static const char kStreamLabel3[] = "local_stream_3";
+static const int kDefaultStunPort = 3478;
+static const char kStunAddressOnly[] = "stun:address";
+static const char kStunInvalidPort[] = "stun:address:-1";
+static const char kStunAddressPortAndMore1[] = "stun:address:port:more";
+static const char kStunAddressPortAndMore2[] = "stun:address:port more";
+static const char kTurnIceServerUri[] = "turn:user@turn.example.org";
+static const char kTurnUsername[] = "user";
+static const char kTurnPassword[] = "password";
+static const char kTurnHostname[] = "turn.example.org";
+static const uint32 kTimeout = 5000U;
+
+#define MAYBE_SKIP_TEST(feature)                    \
+  if (!(feature())) {                               \
+    LOG(LS_INFO) << "Feature disabled... skipping"; \
+    return;                                         \
+  }
+
+using talk_base::scoped_ptr;
+using talk_base::scoped_refptr;
+using webrtc::AudioSourceInterface;
+using webrtc::AudioTrackInterface;
+using webrtc::DataBuffer;
+using webrtc::DataChannelInterface;
+using webrtc::FakeConstraints;
+using webrtc::FakePortAllocatorFactory;
+using webrtc::IceCandidateInterface;
+using webrtc::MediaStreamInterface;
+using webrtc::MediaStreamTrackInterface;
+using webrtc::MockCreateSessionDescriptionObserver;
+using webrtc::MockDataChannelObserver;
+using webrtc::MockSetSessionDescriptionObserver;
+using webrtc::MockStatsObserver;
+using webrtc::PeerConnectionInterface;
+using webrtc::PeerConnectionObserver;
+using webrtc::PortAllocatorFactoryInterface;
+using webrtc::SdpParseError;
+using webrtc::SessionDescriptionInterface;
+using webrtc::VideoSourceInterface;
+using webrtc::VideoTrackInterface;
+
+namespace {
+
+// Gets the first ssrc of given content type from the ContentInfo.
+bool GetFirstSsrc(const cricket::ContentInfo* content_info, int* ssrc) {
+  if (!content_info || !ssrc) {
+    return false;
+  }
+  const cricket::MediaContentDescription* media_desc =
+      static_cast<const cricket::MediaContentDescription*> (
+          content_info->description);
+  if (!media_desc || media_desc->streams().empty()) {
+    return false;
+  }
+  *ssrc = media_desc->streams().begin()->first_ssrc();
+  return true;
+}
+
+void SetSsrcToZero(std::string* sdp) {
+  const char kSdpSsrcAtribute[] = "a=ssrc:";
+  const char kSdpSsrcAtributeZero[] = "a=ssrc:0";
+  size_t ssrc_pos = 0;
+  while ((ssrc_pos = sdp->find(kSdpSsrcAtribute, ssrc_pos)) !=
+      std::string::npos) {
+    size_t end_ssrc = sdp->find(" ", ssrc_pos);
+    sdp->replace(ssrc_pos, end_ssrc - ssrc_pos, kSdpSsrcAtributeZero);
+    ssrc_pos = end_ssrc;
+  }
+}
+
+class MockPeerConnectionObserver : public PeerConnectionObserver {
+ public:
+  MockPeerConnectionObserver()
+      : renegotiation_needed_(false),
+        ice_complete_(false) {
+  }
+  ~MockPeerConnectionObserver() {
+  }
+  void SetPeerConnectionInterface(PeerConnectionInterface* pc) {
+    pc_ = pc;
+    if (pc) {
+      state_ = pc_->signaling_state();
+    }
+  }
+  virtual void OnError() {}
+  virtual void OnSignalingChange(
+      PeerConnectionInterface::SignalingState new_state) {
+    EXPECT_EQ(pc_->signaling_state(), new_state);
+    state_ = new_state;
+  }
+  // TODO(bemasc): Remove this once callers transition to OnIceGatheringChange.
+  virtual void OnStateChange(StateType state_changed) {
+    if (pc_.get() == NULL)
+      return;
+    switch (state_changed) {
+      case kSignalingState:
+        // OnSignalingChange and OnStateChange(kSignalingState) should always
+        // be called approximately simultaneously.  To ease testing, we require
+        // that they always be called in that order.  This check verifies
+        // that OnSignalingChange has just been called.
+        EXPECT_EQ(pc_->signaling_state(), 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;
+  }
+  virtual void OnRenegotiationNeeded() {
+    renegotiation_needed_ = true;
+  }
+  virtual void OnDataChannel(DataChannelInterface* data_channel) {
+    last_datachannel_ = data_channel;
+  }
+
+  virtual void OnIceConnectionChange(
+      PeerConnectionInterface::IceConnectionState new_state) {
+    EXPECT_EQ(pc_->ice_connection_state(), new_state);
+  }
+  virtual void OnIceGatheringChange(
+      PeerConnectionInterface::IceGatheringState new_state) {
+    EXPECT_EQ(pc_->ice_gathering_state(), new_state);
+  }
+  virtual void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) {
+    EXPECT_NE(PeerConnectionInterface::kIceGatheringNew,
+              pc_->ice_gathering_state());
+
+    std::string sdp;
+    EXPECT_TRUE(candidate->ToString(&sdp));
+    EXPECT_LT(0u, sdp.size());
+    last_candidate_.reset(webrtc::CreateIceCandidate(candidate->sdp_mid(),
+        candidate->sdp_mline_index(), sdp, NULL));
+    EXPECT_TRUE(last_candidate_.get() != NULL);
+  }
+  // TODO(bemasc): Remove this once callers transition to OnSignalingChange.
+  virtual void OnIceComplete() {
+    ice_complete_ = true;
+    // OnIceGatheringChange(IceGatheringCompleted) and OnIceComplete() should
+    // be called approximately simultaneously.  For ease of testing, this
+    // check additionally requires that they be called in the above order.
+    EXPECT_EQ(PeerConnectionInterface::kIceGatheringComplete,
+      pc_->ice_gathering_state());
+  }
+
+  // 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 "";
+  }
+
+  scoped_refptr<PeerConnectionInterface> pc_;
+  PeerConnectionInterface::SignalingState state_;
+  scoped_ptr<IceCandidateInterface> last_candidate_;
+  scoped_refptr<DataChannelInterface> last_datachannel_;
+  bool renegotiation_needed_;
+  bool ice_complete_;
+
+ private:
+  scoped_refptr<MediaStreamInterface> last_added_stream_;
+  scoped_refptr<MediaStreamInterface> last_removed_stream_;
+};
+
+}  // namespace
+class PeerConnectionInterfaceTest : public testing::Test {
+ protected:
+  virtual void SetUp() {
+    pc_factory_ = webrtc::CreatePeerConnectionFactory(
+        talk_base::Thread::Current(), talk_base::Thread::Current(), NULL, NULL,
+        NULL);
+    ASSERT_TRUE(pc_factory_.get() != NULL);
+  }
+
+  void CreatePeerConnection() {
+    CreatePeerConnection("", "", NULL);
+  }
+
+  void CreatePeerConnection(webrtc::MediaConstraintsInterface* constraints) {
+    CreatePeerConnection("", "", constraints);
+  }
+
+  void CreatePeerConnection(const std::string& uri,
+                            const std::string& password,
+                            webrtc::MediaConstraintsInterface* constraints) {
+    PeerConnectionInterface::IceServer server;
+    PeerConnectionInterface::IceServers servers;
+    server.uri = uri;
+    server.password = password;
+    servers.push_back(server);
+
+    port_allocator_factory_ = FakePortAllocatorFactory::Create();
+    pc_ = pc_factory_->CreatePeerConnection(servers, constraints,
+                                            port_allocator_factory_.get(),
+                                            NULL,
+                                            &observer_);
+    ASSERT_TRUE(pc_.get() != NULL);
+    observer_.SetPeerConnectionInterface(pc_.get());
+    EXPECT_EQ(PeerConnectionInterface::kStable, observer_.state_);
+  }
+
+  void CreatePeerConnectionWithDifferentConfigurations() {
+    CreatePeerConnection(kStunAddressOnly, "", NULL);
+    EXPECT_EQ(1u, port_allocator_factory_->stun_configs().size());
+    EXPECT_EQ(0u, port_allocator_factory_->turn_configs().size());
+    EXPECT_EQ("address",
+        port_allocator_factory_->stun_configs()[0].server.hostname());
+    EXPECT_EQ(kDefaultStunPort,
+        port_allocator_factory_->stun_configs()[0].server.port());
+
+    CreatePeerConnection(kStunInvalidPort, "", NULL);
+    EXPECT_EQ(0u, port_allocator_factory_->stun_configs().size());
+    EXPECT_EQ(0u, port_allocator_factory_->turn_configs().size());
+
+    CreatePeerConnection(kStunAddressPortAndMore1, "", NULL);
+    EXPECT_EQ(0u, port_allocator_factory_->stun_configs().size());
+    EXPECT_EQ(0u, port_allocator_factory_->turn_configs().size());
+
+    CreatePeerConnection(kStunAddressPortAndMore2, "", NULL);
+    EXPECT_EQ(0u, port_allocator_factory_->stun_configs().size());
+    EXPECT_EQ(0u, port_allocator_factory_->turn_configs().size());
+
+    CreatePeerConnection(kTurnIceServerUri, kTurnPassword, NULL);
+    EXPECT_EQ(1u, port_allocator_factory_->stun_configs().size());
+    EXPECT_EQ(1u, port_allocator_factory_->turn_configs().size());
+    EXPECT_EQ(kTurnUsername,
+              port_allocator_factory_->turn_configs()[0].username);
+    EXPECT_EQ(kTurnPassword,
+              port_allocator_factory_->turn_configs()[0].password);
+    EXPECT_EQ(kTurnHostname,
+              port_allocator_factory_->turn_configs()[0].server.hostname());
+    EXPECT_EQ(kTurnHostname,
+              port_allocator_factory_->stun_configs()[0].server.hostname());
+  }
+
+  void ReleasePeerConnection() {
+    pc_ = NULL;
+    observer_.SetPeerConnectionInterface(NULL);
+  }
+
+  void AddStream(const std::string& label) {
+    // Create a local stream.
+    scoped_refptr<MediaStreamInterface> stream(
+        pc_factory_->CreateLocalMediaStream(label));
+    scoped_refptr<VideoSourceInterface> video_source(
+        pc_factory_->CreateVideoSource(new cricket::FakeVideoCapturer(), NULL));
+    scoped_refptr<VideoTrackInterface> video_track(
+        pc_factory_->CreateVideoTrack(label + "v0", video_source));
+    stream->AddTrack(video_track.get());
+    EXPECT_TRUE(pc_->AddStream(stream, NULL));
+    EXPECT_TRUE_WAIT(observer_.renegotiation_needed_, kTimeout);
+    observer_.renegotiation_needed_ = false;
+  }
+
+  void AddVoiceStream(const std::string& label) {
+    // Create a local stream.
+    scoped_refptr<MediaStreamInterface> stream(
+        pc_factory_->CreateLocalMediaStream(label));
+    scoped_refptr<AudioTrackInterface> audio_track(
+        pc_factory_->CreateAudioTrack(label + "a0", NULL));
+    stream->AddTrack(audio_track.get());
+    EXPECT_TRUE(pc_->AddStream(stream, NULL));
+    EXPECT_TRUE_WAIT(observer_.renegotiation_needed_, kTimeout);
+    observer_.renegotiation_needed_ = false;
+  }
+
+  void AddAudioVideoStream(const std::string& stream_label,
+                           const std::string& audio_track_label,
+                           const std::string& video_track_label) {
+    // Create a local stream.
+    scoped_refptr<MediaStreamInterface> stream(
+        pc_factory_->CreateLocalMediaStream(stream_label));
+    scoped_refptr<AudioTrackInterface> audio_track(
+        pc_factory_->CreateAudioTrack(
+            audio_track_label, static_cast<AudioSourceInterface*>(NULL)));
+    stream->AddTrack(audio_track.get());
+    scoped_refptr<VideoTrackInterface> video_track(
+        pc_factory_->CreateVideoTrack(video_track_label, NULL));
+    stream->AddTrack(video_track.get());
+    EXPECT_TRUE(pc_->AddStream(stream, NULL));
+    EXPECT_TRUE_WAIT(observer_.renegotiation_needed_, kTimeout);
+    observer_.renegotiation_needed_ = false;
+  }
+
+  bool DoCreateOfferAnswer(SessionDescriptionInterface** desc, bool offer) {
+    talk_base::scoped_refptr<MockCreateSessionDescriptionObserver>
+        observer(new talk_base::RefCountedObject<
+            MockCreateSessionDescriptionObserver>());
+    if (offer) {
+      pc_->CreateOffer(observer, NULL);
+    } else {
+      pc_->CreateAnswer(observer, NULL);
+    }
+    EXPECT_EQ_WAIT(true, observer->called(), kTimeout);
+    *desc = observer->release_desc();
+    return observer->result();
+  }
+
+  bool DoCreateOffer(SessionDescriptionInterface** desc) {
+    return DoCreateOfferAnswer(desc, true);
+  }
+
+  bool DoCreateAnswer(SessionDescriptionInterface** desc) {
+    return DoCreateOfferAnswer(desc, false);
+  }
+
+  bool DoSetSessionDescription(SessionDescriptionInterface* desc, bool local) {
+    talk_base::scoped_refptr<MockSetSessionDescriptionObserver>
+        observer(new talk_base::RefCountedObject<
+            MockSetSessionDescriptionObserver>());
+    if (local) {
+      pc_->SetLocalDescription(observer, desc);
+    } else {
+      pc_->SetRemoteDescription(observer, desc);
+    }
+    EXPECT_EQ_WAIT(true, observer->called(), kTimeout);
+    return observer->result();
+  }
+
+  bool DoSetLocalDescription(SessionDescriptionInterface* desc) {
+    return DoSetSessionDescription(desc, true);
+  }
+
+  bool DoSetRemoteDescription(SessionDescriptionInterface* desc) {
+    return DoSetSessionDescription(desc, false);
+  }
+
+  // Calls PeerConnection::GetStats and check the return value.
+  // It does not verify the values in the StatReports since a RTCP packet might
+  // be required.
+  bool DoGetStats(MediaStreamTrackInterface* track) {
+    talk_base::scoped_refptr<MockStatsObserver> observer(
+        new talk_base::RefCountedObject<MockStatsObserver>());
+    if (!pc_->GetStats(observer, track))
+      return false;
+    EXPECT_TRUE_WAIT(observer->called(), kTimeout);
+    return observer->called();
+  }
+
+  void InitiateCall() {
+    CreatePeerConnection();
+    // Create a local stream with audio&video tracks.
+    AddAudioVideoStream(kStreamLabel1, "audio_label", "video_label");
+    CreateOfferReceiveAnswer();
+  }
+
+  // Verify that RTP Header extensions has been negotiated for audio and video.
+  void VerifyRemoteRtpHeaderExtensions() {
+    const cricket::MediaContentDescription* desc =
+        cricket::GetFirstAudioContentDescription(
+            pc_->remote_description()->description());
+    ASSERT_TRUE(desc != NULL);
+    EXPECT_GT(desc->rtp_header_extensions().size(), 0u);
+
+    desc = cricket::GetFirstVideoContentDescription(
+        pc_->remote_description()->description());
+    ASSERT_TRUE(desc != NULL);
+    EXPECT_GT(desc->rtp_header_extensions().size(), 0u);
+  }
+
+  void CreateOfferAsRemoteDescription() {
+    talk_base::scoped_ptr<SessionDescriptionInterface> offer;
+    EXPECT_TRUE(DoCreateOffer(offer.use()));
+    std::string sdp;
+    EXPECT_TRUE(offer->ToString(&sdp));
+    SessionDescriptionInterface* remote_offer =
+        webrtc::CreateSessionDescription(SessionDescriptionInterface::kOffer,
+                                         sdp, NULL);
+    EXPECT_TRUE(DoSetRemoteDescription(remote_offer));
+    EXPECT_EQ(PeerConnectionInterface::kHaveRemoteOffer, observer_.state_);
+  }
+
+  void CreateAnswerAsLocalDescription() {
+    scoped_ptr<SessionDescriptionInterface> answer;
+    EXPECT_TRUE(DoCreateAnswer(answer.use()));
+
+    // TODO(perkj): Currently SetLocalDescription fails if any parameters in an
+    // audio codec change, even if the parameter has nothing to do with
+    // receiving. Not all parameters are serialized to SDP.
+    // Since CreatePrAnswerAsLocalDescription serialize/deserialize
+    // the SessionDescription, it is necessary to do that here to in order to
+    // get ReceiveOfferCreatePrAnswerAndAnswer and RenegotiateAudioOnly to pass.
+    // https://code.google.com/p/webrtc/issues/detail?id=1356
+    std::string sdp;
+    EXPECT_TRUE(answer->ToString(&sdp));
+    SessionDescriptionInterface* new_answer =
+        webrtc::CreateSessionDescription(SessionDescriptionInterface::kAnswer,
+                                         sdp, NULL);
+    EXPECT_TRUE(DoSetLocalDescription(new_answer));
+    EXPECT_EQ(PeerConnectionInterface::kStable, observer_.state_);
+  }
+
+  void CreatePrAnswerAsLocalDescription() {
+    scoped_ptr<SessionDescriptionInterface> answer;
+    EXPECT_TRUE(DoCreateAnswer(answer.use()));
+
+    std::string sdp;
+    EXPECT_TRUE(answer->ToString(&sdp));
+    SessionDescriptionInterface* pr_answer =
+        webrtc::CreateSessionDescription(SessionDescriptionInterface::kPrAnswer,
+                                         sdp, NULL);
+    EXPECT_TRUE(DoSetLocalDescription(pr_answer));
+    EXPECT_EQ(PeerConnectionInterface::kHaveLocalPrAnswer, observer_.state_);
+  }
+
+  void CreateOfferReceiveAnswer() {
+    CreateOfferAsLocalDescription();
+    std::string sdp;
+    EXPECT_TRUE(pc_->local_description()->ToString(&sdp));
+    CreateAnswerAsRemoteDescription(sdp);
+  }
+
+  void CreateOfferAsLocalDescription() {
+    talk_base::scoped_ptr<SessionDescriptionInterface> offer;
+    ASSERT_TRUE(DoCreateOffer(offer.use()));
+    // TODO(perkj): Currently SetLocalDescription fails if any parameters in an
+    // audio codec change, even if the parameter has nothing to do with
+    // receiving. Not all parameters are serialized to SDP.
+    // Since CreatePrAnswerAsLocalDescription serialize/deserialize
+    // the SessionDescription, it is necessary to do that here to in order to
+    // get ReceiveOfferCreatePrAnswerAndAnswer and RenegotiateAudioOnly to pass.
+    // https://code.google.com/p/webrtc/issues/detail?id=1356
+    std::string sdp;
+    EXPECT_TRUE(offer->ToString(&sdp));
+    SessionDescriptionInterface* new_offer =
+            webrtc::CreateSessionDescription(
+                SessionDescriptionInterface::kOffer,
+                sdp, NULL);
+
+    EXPECT_TRUE(DoSetLocalDescription(new_offer));
+    EXPECT_EQ(PeerConnectionInterface::kHaveLocalOffer, observer_.state_);
+  }
+
+  void CreateAnswerAsRemoteDescription(const std::string& offer) {
+    webrtc::JsepSessionDescription* answer = new webrtc::JsepSessionDescription(
+        SessionDescriptionInterface::kAnswer);
+    EXPECT_TRUE(answer->Initialize(offer, NULL));
+    EXPECT_TRUE(DoSetRemoteDescription(answer));
+    EXPECT_EQ(PeerConnectionInterface::kStable, observer_.state_);
+  }
+
+  void CreatePrAnswerAndAnswerAsRemoteDescription(const std::string& offer) {
+    webrtc::JsepSessionDescription* pr_answer =
+        new webrtc::JsepSessionDescription(
+            SessionDescriptionInterface::kPrAnswer);
+    EXPECT_TRUE(pr_answer->Initialize(offer, NULL));
+    EXPECT_TRUE(DoSetRemoteDescription(pr_answer));
+    EXPECT_EQ(PeerConnectionInterface::kHaveRemotePrAnswer, observer_.state_);
+    webrtc::JsepSessionDescription* answer =
+        new webrtc::JsepSessionDescription(
+            SessionDescriptionInterface::kAnswer);
+    EXPECT_TRUE(answer->Initialize(offer, NULL));
+    EXPECT_TRUE(DoSetRemoteDescription(answer));
+    EXPECT_EQ(PeerConnectionInterface::kStable, observer_.state_);
+  }
+
+  // Help function used for waiting until a the last signaled remote stream has
+  // the same label as |stream_label|. In a few of the tests in this file we
+  // answer with the same session description as we offer and thus we can
+  // check if OnAddStream have been called with the same stream as we offer to
+  // send.
+  void WaitAndVerifyOnAddStream(const std::string& stream_label) {
+    EXPECT_EQ_WAIT(stream_label, observer_.GetLastAddedStreamLabel(), kTimeout);
+  }
+
+  // Creates an offer and applies it as a local session description.
+  // Creates an answer with the same SDP an the offer but removes all lines
+  // that start with a:ssrc"
+  void CreateOfferReceiveAnswerWithoutSsrc() {
+    CreateOfferAsLocalDescription();
+    std::string sdp;
+    EXPECT_TRUE(pc_->local_description()->ToString(&sdp));
+    SetSsrcToZero(&sdp);
+    CreateAnswerAsRemoteDescription(sdp);
+  }
+
+  scoped_refptr<FakePortAllocatorFactory> port_allocator_factory_;
+  scoped_refptr<webrtc::PeerConnectionFactoryInterface> pc_factory_;
+  scoped_refptr<PeerConnectionInterface> pc_;
+  MockPeerConnectionObserver observer_;
+};
+
+TEST_F(PeerConnectionInterfaceTest,
+       CreatePeerConnectionWithDifferentConfigurations) {
+  CreatePeerConnectionWithDifferentConfigurations();
+}
+
+TEST_F(PeerConnectionInterfaceTest, AddStreams) {
+  CreatePeerConnection();
+  AddStream(kStreamLabel1);
+  AddVoiceStream(kStreamLabel2);
+  ASSERT_EQ(2u, pc_->local_streams()->count());
+
+  // Fail to add another stream with audio since we already have an audio track.
+  scoped_refptr<MediaStreamInterface> stream(
+      pc_factory_->CreateLocalMediaStream(kStreamLabel3));
+  scoped_refptr<AudioTrackInterface> audio_track(
+      pc_factory_->CreateAudioTrack(
+          kStreamLabel3, static_cast<AudioSourceInterface*>(NULL)));
+  stream->AddTrack(audio_track.get());
+  EXPECT_FALSE(pc_->AddStream(stream, NULL));
+
+  // Remove the stream with the audio track.
+  pc_->RemoveStream(pc_->local_streams()->at(1));
+
+  // Test that we now can add the stream with the audio track.
+  EXPECT_TRUE(pc_->AddStream(stream, NULL));
+}
+
+TEST_F(PeerConnectionInterfaceTest, RemoveStream) {
+  CreatePeerConnection();
+  AddStream(kStreamLabel1);
+  ASSERT_EQ(1u, pc_->local_streams()->count());
+  pc_->RemoveStream(pc_->local_streams()->at(0));
+  EXPECT_EQ(0u, pc_->local_streams()->count());
+}
+
+TEST_F(PeerConnectionInterfaceTest, CreateOfferReceiveAnswer) {
+  InitiateCall();
+  WaitAndVerifyOnAddStream(kStreamLabel1);
+  VerifyRemoteRtpHeaderExtensions();
+}
+
+TEST_F(PeerConnectionInterfaceTest, CreateOfferReceivePrAnswerAndAnswer) {
+  CreatePeerConnection();
+  AddStream(kStreamLabel1);
+  CreateOfferAsLocalDescription();
+  std::string offer;
+  EXPECT_TRUE(pc_->local_description()->ToString(&offer));
+  CreatePrAnswerAndAnswerAsRemoteDescription(offer);
+  WaitAndVerifyOnAddStream(kStreamLabel1);
+}
+
+TEST_F(PeerConnectionInterfaceTest, ReceiveOfferCreateAnswer) {
+  CreatePeerConnection();
+  AddStream(kStreamLabel1);
+
+  CreateOfferAsRemoteDescription();
+  CreateAnswerAsLocalDescription();
+
+  WaitAndVerifyOnAddStream(kStreamLabel1);
+}
+
+TEST_F(PeerConnectionInterfaceTest, ReceiveOfferCreatePrAnswerAndAnswer) {
+  CreatePeerConnection();
+  AddStream(kStreamLabel1);
+
+  CreateOfferAsRemoteDescription();
+  CreatePrAnswerAsLocalDescription();
+  CreateAnswerAsLocalDescription();
+
+  WaitAndVerifyOnAddStream(kStreamLabel1);
+}
+
+TEST_F(PeerConnectionInterfaceTest, Renegotiate) {
+  InitiateCall();
+  ASSERT_EQ(1u, pc_->remote_streams()->count());
+  pc_->RemoveStream(pc_->local_streams()->at(0));
+  CreateOfferReceiveAnswer();
+  EXPECT_EQ(0u, pc_->remote_streams()->count());
+  AddStream(kStreamLabel1);
+  CreateOfferReceiveAnswer();
+}
+
+// Tests that after negotiating an audio only call, the respondent can perform a
+// renegotiation that removes the audio stream.
+TEST_F(PeerConnectionInterfaceTest, RenegotiateAudioOnly) {
+  CreatePeerConnection();
+  AddVoiceStream(kStreamLabel1);
+  CreateOfferAsRemoteDescription();
+  CreateAnswerAsLocalDescription();
+
+  ASSERT_EQ(1u, pc_->remote_streams()->count());
+  pc_->RemoveStream(pc_->local_streams()->at(0));
+  CreateOfferReceiveAnswer();
+  EXPECT_EQ(0u, pc_->remote_streams()->count());
+}
+
+// Test that candidates are generated and that we can parse our own candidates.
+TEST_F(PeerConnectionInterfaceTest, IceCandidates) {
+  CreatePeerConnection();
+
+  EXPECT_FALSE(pc_->AddIceCandidate(observer_.last_candidate_.get()));
+  // SetRemoteDescription takes ownership of offer.
+  SessionDescriptionInterface* offer = NULL;
+  AddStream(kStreamLabel1);
+  EXPECT_TRUE(DoCreateOffer(&offer));
+  EXPECT_TRUE(DoSetRemoteDescription(offer));
+
+  // SetLocalDescription takes ownership of answer.
+  SessionDescriptionInterface* answer = NULL;
+  EXPECT_TRUE(DoCreateAnswer(&answer));
+  EXPECT_TRUE(DoSetLocalDescription(answer));
+
+  EXPECT_TRUE_WAIT(observer_.last_candidate_.get() != NULL, kTimeout);
+  EXPECT_TRUE_WAIT(observer_.ice_complete_, kTimeout);
+
+  EXPECT_TRUE(pc_->AddIceCandidate(observer_.last_candidate_.get()));
+}
+
+// Test that the CreateOffer and CreatAnswer will fail if the track labels are
+// not unique.
+TEST_F(PeerConnectionInterfaceTest, CreateOfferAnswerWithInvalidStream) {
+  CreatePeerConnection();
+  // Create a regular offer for the CreateAnswer test later.
+  SessionDescriptionInterface* offer = NULL;
+  EXPECT_TRUE(DoCreateOffer(&offer));
+  EXPECT_TRUE(offer != NULL);
+  delete offer;
+  offer = NULL;
+
+  // Create a local stream with audio&video tracks having same label.
+  AddAudioVideoStream(kStreamLabel1, "track_label", "track_label");
+
+  // Test CreateOffer
+  EXPECT_FALSE(DoCreateOffer(&offer));
+
+  // Test CreateAnswer
+  SessionDescriptionInterface* answer = NULL;
+  EXPECT_FALSE(DoCreateAnswer(&answer));
+}
+
+// Test that we will get different SSRCs for each tracks in the offer and answer
+// we created.
+TEST_F(PeerConnectionInterfaceTest, SsrcInOfferAnswer) {
+  CreatePeerConnection();
+  // Create a local stream with audio&video tracks having different labels.
+  AddAudioVideoStream(kStreamLabel1, "audio_label", "video_label");
+
+  // Test CreateOffer
+  scoped_ptr<SessionDescriptionInterface> offer;
+  EXPECT_TRUE(DoCreateOffer(offer.use()));
+  int audio_ssrc = 0;
+  int video_ssrc = 0;
+  EXPECT_TRUE(GetFirstSsrc(GetFirstAudioContent(offer->description()),
+                           &audio_ssrc));
+  EXPECT_TRUE(GetFirstSsrc(GetFirstVideoContent(offer->description()),
+                           &video_ssrc));
+  EXPECT_NE(audio_ssrc, video_ssrc);
+
+  // Test CreateAnswer
+  EXPECT_TRUE(DoSetRemoteDescription(offer.release()));
+  scoped_ptr<SessionDescriptionInterface> answer;
+  EXPECT_TRUE(DoCreateAnswer(answer.use()));
+  audio_ssrc = 0;
+  video_ssrc = 0;
+  EXPECT_TRUE(GetFirstSsrc(GetFirstAudioContent(answer->description()),
+                           &audio_ssrc));
+  EXPECT_TRUE(GetFirstSsrc(GetFirstVideoContent(answer->description()),
+                           &video_ssrc));
+  EXPECT_NE(audio_ssrc, video_ssrc);
+}
+
+// Test that we can specify a certain track that we want statistics about.
+TEST_F(PeerConnectionInterfaceTest, GetStatsForSpecificTrack) {
+  InitiateCall();
+  ASSERT_LT(0u, pc_->remote_streams()->count());
+  ASSERT_LT(0u, pc_->remote_streams()->at(0)->GetAudioTracks().size());
+  scoped_refptr<MediaStreamTrackInterface> remote_audio =
+      pc_->remote_streams()->at(0)->GetAudioTracks()[0];
+  EXPECT_TRUE(DoGetStats(remote_audio));
+
+  // Remove the stream. Since we are sending to our selves the local
+  // and the remote stream is the same.
+  pc_->RemoveStream(pc_->local_streams()->at(0));
+  // Do a re-negotiation.
+  CreateOfferReceiveAnswer();
+
+  ASSERT_EQ(0u, pc_->remote_streams()->count());
+
+  // Test that we still can get statistics for the old track. Even if it is not
+  // sent any longer.
+  EXPECT_TRUE(DoGetStats(remote_audio));
+}
+
+// Test that we can get stats on a video track.
+TEST_F(PeerConnectionInterfaceTest, GetStatsForVideoTrack) {
+  InitiateCall();
+  ASSERT_LT(0u, pc_->remote_streams()->count());
+  ASSERT_LT(0u, pc_->remote_streams()->at(0)->GetVideoTracks().size());
+  scoped_refptr<MediaStreamTrackInterface> remote_video =
+      pc_->remote_streams()->at(0)->GetVideoTracks()[0];
+  EXPECT_TRUE(DoGetStats(remote_video));
+}
+
+// Test that we don't get statistics for an invalid track.
+TEST_F(PeerConnectionInterfaceTest, GetStatsForInvalidTrack) {
+  InitiateCall();
+  scoped_refptr<AudioTrackInterface> unknown_audio_track(
+      pc_factory_->CreateAudioTrack("unknown track", NULL));
+  EXPECT_FALSE(DoGetStats(unknown_audio_track));
+}
+
+// This test setup two RTP data channels in loop back.
+#ifdef WIN32
+// TODO(perkj): Investigate why the transport channel sometimes don't become
+// writable on Windows when we try to connect in loop back.
+TEST_F(PeerConnectionInterfaceTest, DISABLED_TestDataChannel) {
+#else
+TEST_F(PeerConnectionInterfaceTest, TestDataChannel) {
+#endif
+  FakeConstraints constraints;
+  constraints.SetAllowRtpDataChannels();
+  CreatePeerConnection(&constraints);
+  scoped_refptr<DataChannelInterface> data1  =
+      pc_->CreateDataChannel("test1", NULL);
+  scoped_refptr<DataChannelInterface> data2  =
+      pc_->CreateDataChannel("test2", NULL);
+  ASSERT_TRUE(data1 != NULL);
+  talk_base::scoped_ptr<MockDataChannelObserver> observer1(
+      new MockDataChannelObserver(data1));
+  talk_base::scoped_ptr<MockDataChannelObserver> observer2(
+      new MockDataChannelObserver(data2));
+
+  EXPECT_EQ(DataChannelInterface::kConnecting, data1->state());
+  EXPECT_EQ(DataChannelInterface::kConnecting, data2->state());
+  std::string data_to_send1 = "testing testing";
+  std::string data_to_send2 = "testing something else";
+  EXPECT_FALSE(data1->Send(DataBuffer(data_to_send1)));
+
+  CreateOfferReceiveAnswer();
+  EXPECT_TRUE_WAIT(observer1->IsOpen(), kTimeout);
+  EXPECT_TRUE_WAIT(observer2->IsOpen(), kTimeout);
+
+  EXPECT_EQ(DataChannelInterface::kOpen, data1->state());
+  EXPECT_EQ(DataChannelInterface::kOpen, data2->state());
+  EXPECT_TRUE(data1->Send(DataBuffer(data_to_send1)));
+  EXPECT_TRUE(data2->Send(DataBuffer(data_to_send2)));
+
+  EXPECT_EQ_WAIT(data_to_send1, observer1->last_message(), kTimeout);
+  EXPECT_EQ_WAIT(data_to_send2, observer2->last_message(), kTimeout);
+
+  data1->Close();
+  EXPECT_EQ(DataChannelInterface::kClosing, data1->state());
+  CreateOfferReceiveAnswer();
+  EXPECT_FALSE(observer1->IsOpen());
+  EXPECT_EQ(DataChannelInterface::kClosed, data1->state());
+  EXPECT_TRUE(observer2->IsOpen());
+
+  data_to_send2 = "testing something else again";
+  EXPECT_TRUE(data2->Send(DataBuffer(data_to_send2)));
+
+  EXPECT_EQ_WAIT(data_to_send2, observer2->last_message(), kTimeout);
+}
+
+// This test verifies that sendnig binary data over RTP data channels should
+// fail.
+#ifdef WIN32
+// TODO(perkj): Investigate why the transport channel sometimes don't become
+// writable on Windows when we try to connect in loop back.
+TEST_F(PeerConnectionInterfaceTest, DISABLED_TestSendBinaryOnRtpDataChannel) {
+#else
+TEST_F(PeerConnectionInterfaceTest, TestSendBinaryOnRtpDataChannel) {
+#endif
+  FakeConstraints constraints;
+  constraints.SetAllowRtpDataChannels();
+  CreatePeerConnection(&constraints);
+  scoped_refptr<DataChannelInterface> data1  =
+      pc_->CreateDataChannel("test1", NULL);
+  scoped_refptr<DataChannelInterface> data2  =
+      pc_->CreateDataChannel("test2", NULL);
+  ASSERT_TRUE(data1 != NULL);
+  talk_base::scoped_ptr<MockDataChannelObserver> observer1(
+      new MockDataChannelObserver(data1));
+  talk_base::scoped_ptr<MockDataChannelObserver> observer2(
+      new MockDataChannelObserver(data2));
+
+  EXPECT_EQ(DataChannelInterface::kConnecting, data1->state());
+  EXPECT_EQ(DataChannelInterface::kConnecting, data2->state());
+
+  CreateOfferReceiveAnswer();
+  EXPECT_TRUE_WAIT(observer1->IsOpen(), kTimeout);
+  EXPECT_TRUE_WAIT(observer2->IsOpen(), kTimeout);
+
+  EXPECT_EQ(DataChannelInterface::kOpen, data1->state());
+  EXPECT_EQ(DataChannelInterface::kOpen, data2->state());
+
+  talk_base::Buffer buffer("test", 4);
+  EXPECT_FALSE(data1->Send(DataBuffer(buffer, true)));
+}
+
+// This test setup a RTP data channels in loop back and test that a channel is
+// opened even if the remote end answer with a zero SSRC.
+#ifdef WIN32
+// TODO(perkj): Investigate why the transport channel sometimes don't become
+// writable on Windows when we try to connect in loop back.
+TEST_F(PeerConnectionInterfaceTest, DISABLED_TestSendOnlyDataChannel) {
+#else
+TEST_F(PeerConnectionInterfaceTest, TestSendOnlyDataChannel) {
+#endif
+  FakeConstraints constraints;
+  constraints.SetAllowRtpDataChannels();
+  CreatePeerConnection(&constraints);
+  scoped_refptr<DataChannelInterface> data1  =
+      pc_->CreateDataChannel("test1", NULL);
+  talk_base::scoped_ptr<MockDataChannelObserver> observer1(
+      new MockDataChannelObserver(data1));
+
+  CreateOfferReceiveAnswerWithoutSsrc();
+
+  EXPECT_TRUE_WAIT(observer1->IsOpen(), kTimeout);
+
+  data1->Close();
+  EXPECT_EQ(DataChannelInterface::kClosing, data1->state());
+  CreateOfferReceiveAnswerWithoutSsrc();
+  EXPECT_EQ(DataChannelInterface::kClosed, data1->state());
+  EXPECT_FALSE(observer1->IsOpen());
+}
+
+// This test that if a data channel is added in an answer a receive only channel
+// channel is created.
+TEST_F(PeerConnectionInterfaceTest, TestReceiveOnlyDataChannel) {
+  FakeConstraints constraints;
+  constraints.SetAllowRtpDataChannels();
+  CreatePeerConnection(&constraints);
+
+  std::string offer_label = "offer_channel";
+  scoped_refptr<DataChannelInterface> offer_channel  =
+      pc_->CreateDataChannel(offer_label, NULL);
+
+  CreateOfferAsLocalDescription();
+
+  // Replace the data channel label in the offer and apply it as an answer.
+  std::string receive_label = "answer_channel";
+  std::string sdp;
+  EXPECT_TRUE(pc_->local_description()->ToString(&sdp));
+  talk_base::replace_substrs(offer_label.c_str(), offer_label.length(),
+                             receive_label.c_str(), receive_label.length(),
+                             &sdp);
+  CreateAnswerAsRemoteDescription(sdp);
+
+  // Verify that a new incoming data channel has been created and that
+  // it is open but can't we written to.
+  ASSERT_TRUE(observer_.last_datachannel_ != NULL);
+  DataChannelInterface* received_channel = observer_.last_datachannel_;
+  EXPECT_EQ(DataChannelInterface::kConnecting, received_channel->state());
+  EXPECT_EQ(receive_label, received_channel->label());
+  EXPECT_FALSE(received_channel->Send(DataBuffer("something")));
+
+  // Verify that the channel we initially offered has been rejected.
+  EXPECT_EQ(DataChannelInterface::kClosed, offer_channel->state());
+
+  // Do another offer / answer exchange and verify that the data channel is
+  // opened.
+  CreateOfferReceiveAnswer();
+  EXPECT_EQ_WAIT(DataChannelInterface::kOpen, received_channel->state(),
+                 kTimeout);
+}
+
+// This test that no data channel is returned if a reliable channel is
+// requested.
+// TODO(perkj): Remove this test once reliable channels are implemented.
+TEST_F(PeerConnectionInterfaceTest, CreateReliableRtpDataChannelShouldFail) {
+  FakeConstraints constraints;
+  constraints.SetAllowRtpDataChannels();
+  CreatePeerConnection(&constraints);
+
+  std::string label = "test";
+  webrtc::DataChannelInit config;
+  config.reliable = true;
+  scoped_refptr<DataChannelInterface> channel  =
+      pc_->CreateDataChannel(label, &config);
+  EXPECT_TRUE(channel == NULL);
+}
+
+// This tests that a SCTP data channel is returned using different
+// DataChannelInit configurations.
+TEST_F(PeerConnectionInterfaceTest, CreateSctpDataChannel) {
+  FakeConstraints constraints;
+  constraints.SetAllowDtlsSctpDataChannels();
+  CreatePeerConnection(&constraints);
+
+  webrtc::DataChannelInit config;
+
+  scoped_refptr<DataChannelInterface> channel =
+      pc_->CreateDataChannel("1", &config);
+  EXPECT_TRUE(channel != NULL);
+  EXPECT_TRUE(channel->reliable());
+
+  config.ordered = false;
+  channel = pc_->CreateDataChannel("2", &config);
+  EXPECT_TRUE(channel != NULL);
+  EXPECT_TRUE(channel->reliable());
+
+  config.ordered = true;
+  config.maxRetransmits = 0;
+  channel = pc_->CreateDataChannel("3", &config);
+  EXPECT_TRUE(channel != NULL);
+  EXPECT_FALSE(channel->reliable());
+
+  config.maxRetransmits = -1;
+  config.maxRetransmitTime = 0;
+  channel = pc_->CreateDataChannel("4", &config);
+  EXPECT_TRUE(channel != NULL);
+  EXPECT_FALSE(channel->reliable());
+}
+
+// This tests that no data channel is returned if both maxRetransmits and
+// maxRetransmitTime are set for SCTP data channels.
+TEST_F(PeerConnectionInterfaceTest,
+       CreateSctpDataChannelShouldFailForInvalidConfig) {
+  FakeConstraints constraints;
+  constraints.SetAllowDtlsSctpDataChannels();
+  CreatePeerConnection(&constraints);
+
+  std::string label = "test";
+  webrtc::DataChannelInit config;
+  config.maxRetransmits = 0;
+  config.maxRetransmitTime = 0;
+
+  scoped_refptr<DataChannelInterface> channel =
+      pc_->CreateDataChannel(label, &config);
+  EXPECT_TRUE(channel == NULL);
+}
+
+// The test verifies that the first id not used by existing data channels is
+// assigned to a new data channel if no id is specified.
+TEST_F(PeerConnectionInterfaceTest, AssignSctpDataChannelId) {
+  FakeConstraints constraints;
+  constraints.SetAllowDtlsSctpDataChannels();
+  CreatePeerConnection(&constraints);
+
+  webrtc::DataChannelInit config;
+
+  scoped_refptr<DataChannelInterface> channel =
+      pc_->CreateDataChannel("1", &config);
+  EXPECT_TRUE(channel != NULL);
+  EXPECT_EQ(1, channel->id());
+
+  config.id = 4;
+  channel = pc_->CreateDataChannel("4", &config);
+  EXPECT_TRUE(channel != NULL);
+  EXPECT_EQ(config.id, channel->id());
+
+  config.id = -1;
+  channel = pc_->CreateDataChannel("2", &config);
+  EXPECT_TRUE(channel != NULL);
+  EXPECT_EQ(2, channel->id());
+}
+
+// The test verifies that creating a SCTP data channel with an id already in use
+// or out of range should fail.
+TEST_F(PeerConnectionInterfaceTest,
+       CreateSctpDataChannelWithInvalidIdShouldFail) {
+  FakeConstraints constraints;
+  constraints.SetAllowDtlsSctpDataChannels();
+  CreatePeerConnection(&constraints);
+
+  webrtc::DataChannelInit config;
+
+  scoped_refptr<DataChannelInterface> channel =
+      pc_->CreateDataChannel("1", &config);
+  EXPECT_TRUE(channel != NULL);
+  EXPECT_EQ(1, channel->id());
+
+  config.id = 1;
+  channel = pc_->CreateDataChannel("x", &config);
+  EXPECT_TRUE(channel == NULL);
+
+  config.id = cricket::kMaxSctpSid;
+  channel = pc_->CreateDataChannel("max", &config);
+  EXPECT_TRUE(channel != NULL);
+  EXPECT_EQ(config.id, channel->id());
+
+  config.id = cricket::kMaxSctpSid + 1;
+  channel = pc_->CreateDataChannel("x", &config);
+  EXPECT_TRUE(channel == NULL);
+}
+
+// This test that a data channel closes when a PeerConnection is deleted/closed.
+#ifdef WIN32
+// TODO(perkj): Investigate why the transport channel sometimes don't become
+// writable on Windows when we try to connect in loop back.
+TEST_F(PeerConnectionInterfaceTest,
+       DISABLED_DataChannelCloseWhenPeerConnectionClose) {
+#else
+TEST_F(PeerConnectionInterfaceTest, DataChannelCloseWhenPeerConnectionClose) {
+#endif
+  FakeConstraints constraints;
+  constraints.SetAllowRtpDataChannels();
+  CreatePeerConnection(&constraints);
+
+  scoped_refptr<DataChannelInterface> data1  =
+      pc_->CreateDataChannel("test1", NULL);
+  scoped_refptr<DataChannelInterface> data2  =
+      pc_->CreateDataChannel("test2", NULL);
+  ASSERT_TRUE(data1 != NULL);
+  talk_base::scoped_ptr<MockDataChannelObserver> observer1(
+      new MockDataChannelObserver(data1));
+  talk_base::scoped_ptr<MockDataChannelObserver> observer2(
+      new MockDataChannelObserver(data2));
+
+  CreateOfferReceiveAnswer();
+  EXPECT_TRUE_WAIT(observer1->IsOpen(), kTimeout);
+  EXPECT_TRUE_WAIT(observer2->IsOpen(), kTimeout);
+
+  ReleasePeerConnection();
+  EXPECT_EQ(DataChannelInterface::kClosed, data1->state());
+  EXPECT_EQ(DataChannelInterface::kClosed, data2->state());
+}
+
+// This test that data channels can be rejected in an answer.
+TEST_F(PeerConnectionInterfaceTest, TestRejectDataChannelInAnswer) {
+  FakeConstraints constraints;
+  constraints.SetAllowRtpDataChannels();
+  CreatePeerConnection(&constraints);
+
+  scoped_refptr<DataChannelInterface> offer_channel(
+      pc_->CreateDataChannel("offer_channel", NULL));
+
+  CreateOfferAsLocalDescription();
+
+  // Create an answer where the m-line for data channels are rejected.
+  std::string sdp;
+  EXPECT_TRUE(pc_->local_description()->ToString(&sdp));
+  webrtc::JsepSessionDescription* answer = new webrtc::JsepSessionDescription(
+      SessionDescriptionInterface::kAnswer);
+  EXPECT_TRUE(answer->Initialize(sdp, NULL));
+  cricket::ContentInfo* data_info =
+      answer->description()->GetContentByName("data");
+  data_info->rejected = true;
+
+  DoSetRemoteDescription(answer);
+  EXPECT_EQ(DataChannelInterface::kClosed, offer_channel->state());
+}
+
+// Test that we can create a session description from an SDP string from
+// FireFox, use it as a remote session description, generate an answer and use
+// the answer as a local description.
+TEST_F(PeerConnectionInterfaceTest, ReceiveFireFoxOffer) {
+  MAYBE_SKIP_TEST(talk_base::SSLStreamAdapter::HaveDtlsSrtp);
+  FakeConstraints constraints;
+  constraints.AddMandatory(webrtc::MediaConstraintsInterface::kEnableDtlsSrtp,
+                           true);
+  CreatePeerConnection(&constraints);
+  AddAudioVideoStream(kStreamLabel1, "audio_label", "video_label");
+  SessionDescriptionInterface* desc =
+      webrtc::CreateSessionDescription(SessionDescriptionInterface::kOffer,
+                                       webrtc::kFireFoxSdpOffer);
+  EXPECT_TRUE(DoSetSessionDescription(desc, false));
+  CreateAnswerAsLocalDescription();
+  ASSERT_TRUE(pc_->local_description() != NULL);
+  ASSERT_TRUE(pc_->remote_description() != NULL);
+
+  const cricket::ContentInfo* content =
+      cricket::GetFirstAudioContent(pc_->local_description()->description());
+  ASSERT_TRUE(content != NULL);
+  EXPECT_FALSE(content->rejected);
+
+  content =
+      cricket::GetFirstVideoContent(pc_->local_description()->description());
+  ASSERT_TRUE(content != NULL);
+  EXPECT_FALSE(content->rejected);
+
+  content =
+      cricket::GetFirstDataContent(pc_->local_description()->description());
+  ASSERT_TRUE(content != NULL);
+  EXPECT_TRUE(content->rejected);
+}
+
+// Test that we can create an audio only offer and receive an answer with a
+// limited set of audio codecs and receive an updated offer with more audio
+// codecs, where the added codecs are not supported.
+TEST_F(PeerConnectionInterfaceTest, ReceiveUpdatedAudioOfferWithBadCodecs) {
+  CreatePeerConnection();
+  AddVoiceStream("audio_label");
+  CreateOfferAsLocalDescription();
+
+  SessionDescriptionInterface* answer =
+      webrtc::CreateSessionDescription(SessionDescriptionInterface::kAnswer,
+                                       webrtc::kAudioSdp);
+  EXPECT_TRUE(DoSetSessionDescription(answer, false));
+
+  SessionDescriptionInterface* updated_offer =
+      webrtc::CreateSessionDescription(SessionDescriptionInterface::kOffer,
+                                       webrtc::kAudioSdpWithUnsupportedCodecs);
+  EXPECT_TRUE(DoSetSessionDescription(updated_offer, false));
+  CreateAnswerAsLocalDescription();
+}
+
+// Test that PeerConnection::Close changes the states to closed and all remote
+// tracks change state to ended.
+TEST_F(PeerConnectionInterfaceTest, CloseAndTestStreamsAndStates) {
+  // Initialize a PeerConnection and negotiate local and remote session
+  // description.
+  InitiateCall();
+  ASSERT_EQ(1u, pc_->local_streams()->count());
+  ASSERT_EQ(1u, pc_->remote_streams()->count());
+
+  pc_->Close();
+
+  EXPECT_EQ(PeerConnectionInterface::kClosed, pc_->signaling_state());
+  EXPECT_EQ(PeerConnectionInterface::kIceConnectionClosed,
+            pc_->ice_connection_state());
+  EXPECT_EQ(PeerConnectionInterface::kIceGatheringComplete,
+            pc_->ice_gathering_state());
+
+  EXPECT_EQ(1u, pc_->local_streams()->count());
+  EXPECT_EQ(1u, pc_->remote_streams()->count());
+
+  scoped_refptr<MediaStreamInterface> remote_stream =
+          pc_->remote_streams()->at(0);
+  EXPECT_EQ(MediaStreamTrackInterface::kEnded,
+            remote_stream->GetVideoTracks()[0]->state());
+  EXPECT_EQ(MediaStreamTrackInterface::kEnded,
+            remote_stream->GetAudioTracks()[0]->state());
+}
+
+// Test that PeerConnection methods fails gracefully after
+// PeerConnection::Close has been called.
+TEST_F(PeerConnectionInterfaceTest, CloseAndTestMethods) {
+  CreatePeerConnection();
+  AddAudioVideoStream(kStreamLabel1, "audio_label", "video_label");
+  CreateOfferAsRemoteDescription();
+  CreateAnswerAsLocalDescription();
+
+  ASSERT_EQ(1u, pc_->local_streams()->count());
+  scoped_refptr<MediaStreamInterface> local_stream =
+      pc_->local_streams()->at(0);
+
+  pc_->Close();
+
+  pc_->RemoveStream(local_stream);
+  EXPECT_FALSE(pc_->AddStream(local_stream, NULL));
+
+  ASSERT_FALSE(local_stream->GetAudioTracks().empty());
+  talk_base::scoped_refptr<webrtc::DtmfSenderInterface> dtmf_sender(
+      pc_->CreateDtmfSender(local_stream->GetAudioTracks()[0]));
+  EXPECT_FALSE(dtmf_sender->CanInsertDtmf());
+
+  EXPECT_TRUE(pc_->CreateDataChannel("test", NULL) == NULL);
+
+  EXPECT_TRUE(pc_->local_description() != NULL);
+  EXPECT_TRUE(pc_->remote_description() != NULL);
+
+  talk_base::scoped_ptr<SessionDescriptionInterface> offer;
+  EXPECT_TRUE(DoCreateOffer(offer.use()));
+  talk_base::scoped_ptr<SessionDescriptionInterface> answer;
+  EXPECT_TRUE(DoCreateAnswer(answer.use()));
+
+  std::string sdp;
+  ASSERT_TRUE(pc_->remote_description()->ToString(&sdp));
+  SessionDescriptionInterface* remote_offer =
+      webrtc::CreateSessionDescription(SessionDescriptionInterface::kOffer,
+                                       sdp, NULL);
+  EXPECT_FALSE(DoSetRemoteDescription(remote_offer));
+
+  ASSERT_TRUE(pc_->local_description()->ToString(&sdp));
+  SessionDescriptionInterface* local_offer =
+        webrtc::CreateSessionDescription(SessionDescriptionInterface::kOffer,
+                                         sdp, NULL);
+  EXPECT_FALSE(DoSetLocalDescription(local_offer));
+}
+
+// Test that GetStats can still be called after PeerConnection::Close.
+TEST_F(PeerConnectionInterfaceTest, CloseAndGetStats) {
+  InitiateCall();
+  pc_->Close();
+  DoGetStats(NULL);
+}
diff --git a/talk/app/webrtc/peerconnectionproxy.h b/talk/app/webrtc/peerconnectionproxy.h
new file mode 100644
index 0000000..f07416d
--- /dev/null
+++ b/talk/app/webrtc/peerconnectionproxy.h
@@ -0,0 +1,72 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_PEERCONNECTIONPROXY_H_
+#define TALK_APP_WEBRTC_PEERCONNECTIONPROXY_H_
+
+#include "talk/app/webrtc/peerconnectioninterface.h"
+#include "talk/app/webrtc/proxy.h"
+
+namespace webrtc {
+
+// Define proxy for PeerConnectionInterface.
+BEGIN_PROXY_MAP(PeerConnection)
+  PROXY_METHOD0(talk_base::scoped_refptr<StreamCollectionInterface>,
+                local_streams)
+  PROXY_METHOD0(talk_base::scoped_refptr<StreamCollectionInterface>,
+                remote_streams)
+  PROXY_METHOD2(bool, AddStream, MediaStreamInterface*,
+                const MediaConstraintsInterface*)
+  PROXY_METHOD1(void, RemoveStream, MediaStreamInterface*)
+  PROXY_METHOD1(talk_base::scoped_refptr<DtmfSenderInterface>,
+                CreateDtmfSender, AudioTrackInterface*)
+  PROXY_METHOD2(bool, GetStats, StatsObserver*, MediaStreamTrackInterface*)
+  PROXY_METHOD2(talk_base::scoped_refptr<DataChannelInterface>,
+                CreateDataChannel, const std::string&, const DataChannelInit*)
+  PROXY_CONSTMETHOD0(const SessionDescriptionInterface*, local_description)
+  PROXY_CONSTMETHOD0(const SessionDescriptionInterface*, remote_description)
+  PROXY_METHOD2(void, CreateOffer, CreateSessionDescriptionObserver*,
+                const MediaConstraintsInterface*)
+  PROXY_METHOD2(void, CreateAnswer, CreateSessionDescriptionObserver*,
+                const MediaConstraintsInterface*)
+  PROXY_METHOD2(void, SetLocalDescription, SetSessionDescriptionObserver*,
+                SessionDescriptionInterface*)
+  PROXY_METHOD2(void, SetRemoteDescription, SetSessionDescriptionObserver*,
+                SessionDescriptionInterface*)
+  PROXY_METHOD2(bool, UpdateIce, const IceServers&,
+                const MediaConstraintsInterface*)
+  PROXY_METHOD1(bool, AddIceCandidate, const IceCandidateInterface*)
+  PROXY_METHOD0(SignalingState, signaling_state)
+  PROXY_METHOD0(IceState, ice_state)
+  PROXY_METHOD0(IceConnectionState, ice_connection_state)
+  PROXY_METHOD0(IceGatheringState, ice_gathering_state)
+  PROXY_METHOD0(void, Close)
+END_PROXY()
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_PEERCONNECTIONPROXY_H_
diff --git a/talk/app/webrtc/portallocatorfactory.cc b/talk/app/webrtc/portallocatorfactory.cc
new file mode 100644
index 0000000..59ac9fb
--- /dev/null
+++ b/talk/app/webrtc/portallocatorfactory.cc
@@ -0,0 +1,92 @@
+/*
+ * 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/thread.h"
+#include "talk/p2p/base/basicpacketsocketfactory.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) {
+  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);
+  }
+
+  talk_base::SocketAddress stun_addr;
+  if (!stun_hosts.empty()) {
+    stun_addr = stun_hosts.front();
+  }
+  scoped_ptr<cricket::BasicPortAllocator> allocator(
+      new cricket::BasicPortAllocator(
+          network_manager_.get(), socket_factory_.get(), stun_addr));
+
+  if (turn.size() > 0) {
+    cricket::RelayCredentials credentials(turn[0].username, turn[0].password);
+    cricket::RelayServerConfig relay_server(cricket::RELAY_TURN);
+    cricket::ProtocolType protocol;
+    if (cricket::StringToProto(turn[0].transport_type.c_str(), &protocol)) {
+      relay_server.ports.push_back(cricket::ProtocolAddress(
+          turn[0].server, protocol));
+      relay_server.credentials = credentials;
+      allocator->AddRelay(relay_server);
+    } else {
+      LOG(LS_WARNING) << "Ignoring TURN server " << turn[0].server << ". "
+                      << "Reason= Incorrect " << turn[0].transport_type
+                      << " transport parameter.";
+    }
+  }
+  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..e30024c
--- /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/peerconnectioninterface.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/proxy.h b/talk/app/webrtc/proxy.h
new file mode 100644
index 0000000..4db4bef
--- /dev/null
+++ b/talk/app/webrtc/proxy.h
@@ -0,0 +1,287 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 Macros for creating proxies for webrtc MediaStream and
+// PeerConnection classes.
+
+//
+// Example usage:
+//
+// class TestInterface : public talk_base::RefCountInterface {
+//  public:
+//   std::string FooA() = 0;
+//   std::string FooB(bool arg1) const = 0;
+//   std::string FooC(bool arg1)= 0;
+//  };
+//
+// Note that return types can not be a const reference.
+//
+// class Test : public TestInterface {
+// ... implementation of the interface.
+// };
+//
+// BEGIN_PROXY_MAP(Test)
+//   PROXY_METHOD0(std::string, FooA)
+//   PROXY_CONSTMETHOD1(std::string, FooB, arg1)
+//   PROXY_METHOD1(std::string, FooC, arg1)
+// END_PROXY()
+//
+// The proxy can be created using TestProxy::Create(Thread*, TestInterface*).
+
+#ifndef TALK_APP_WEBRTC_PROXY_H_
+#define TALK_APP_WEBRTC_PROXY_H_
+
+#include "talk/base/thread.h"
+
+namespace webrtc {
+
+template <typename R>
+class ReturnType {
+ public:
+  template<typename C, typename M>
+  void Invoke(C* c, M m) { r_ = (c->*m)(); }
+  template<typename C, typename M, typename T1>
+  void Invoke(C* c, M m, T1 a1) { r_ = (c->*m)(a1); }
+  template<typename C, typename M, typename T1, typename T2>
+  void Invoke(C* c, M m, T1 a1, T2 a2) { r_ = (c->*m)(a1, a2); }
+  template<typename C, typename M, typename T1, typename T2, typename T3>
+  void Invoke(C* c, M m, T1 a1, T2 a2, T3 a3) { r_ = (c->*m)(a1, a2, a3); }
+
+  R value() { return r_; }
+
+ private:
+  R r_;
+};
+
+template <>
+class ReturnType<void> {
+ public:
+  template<typename C, typename M>
+  void Invoke(C* c, M m) { (c->*m)(); }
+  template<typename C, typename M, typename T1>
+  void Invoke(C* c, M m, T1 a1) { (c->*m)(a1); }
+  template<typename C, typename M, typename T1, typename T2>
+  void Invoke(C* c, M m, T1 a1, T2 a2) { (c->*m)(a1, a2); }
+  template<typename C, typename M, typename T1, typename T2, typename T3>
+  void Invoke(C* c, M m, T1 a1, T2 a2, T3 a3) { (c->*m)(a1, a2, a3); }
+
+  void value() {}
+};
+
+template <typename C, typename R>
+class MethodCall0 : public talk_base::Message,
+                    public talk_base::MessageHandler {
+ public:
+  typedef R (C::*Method)();
+  MethodCall0(C* c, Method m) : c_(c), m_(m) {}
+
+  R Marshal(talk_base::Thread* t) {
+    t->Send(this, 0);
+    return r_.value();
+  }
+
+ private:
+  void OnMessage(talk_base::Message*) {  r_.Invoke(c_, m_);}
+
+  C* c_;
+  Method m_;
+  ReturnType<R> r_;
+};
+
+template <typename C, typename R>
+class ConstMethodCall0 : public talk_base::Message,
+                         public talk_base::MessageHandler {
+ public:
+  typedef R (C::*Method)() const;
+  ConstMethodCall0(C* c, Method m) : c_(c), m_(m) {}
+
+  R Marshal(talk_base::Thread* t) {
+    t->Send(this, 0);
+    return r_.value();
+  }
+
+ private:
+  void OnMessage(talk_base::Message*) { r_.Invoke(c_, m_); }
+
+  C* c_;
+  Method m_;
+  ReturnType<R> r_;
+};
+
+template <typename C, typename R,  typename T1>
+class MethodCall1 : public talk_base::Message,
+                    public talk_base::MessageHandler {
+ public:
+  typedef R (C::*Method)(T1 a1);
+  MethodCall1(C* c, Method m, T1 a1) : c_(c), m_(m), a1_(a1) {}
+
+  R Marshal(talk_base::Thread* t) {
+    t->Send(this, 0);
+    return r_.value();
+  }
+
+ private:
+  void OnMessage(talk_base::Message*) { r_.Invoke(c_, m_, a1_); }
+
+  C* c_;
+  Method m_;
+  ReturnType<R> r_;
+  T1 a1_;
+};
+
+template <typename C, typename R,  typename T1>
+class ConstMethodCall1 : public talk_base::Message,
+                         public talk_base::MessageHandler {
+ public:
+  typedef R (C::*Method)(T1 a1) const;
+  ConstMethodCall1(C* c, Method m, T1 a1) : c_(c), m_(m), a1_(a1) {}
+
+  R Marshal(talk_base::Thread* t) {
+    t->Send(this, 0);
+    return r_.value();
+  }
+
+ private:
+  void OnMessage(talk_base::Message*) { r_.Invoke(c_, m_, a1_); }
+
+  C* c_;
+  Method m_;
+  ReturnType<R> r_;
+  T1 a1_;
+};
+
+template <typename C, typename R, typename T1, typename T2>
+class MethodCall2 : public talk_base::Message,
+                    public talk_base::MessageHandler {
+ public:
+  typedef R (C::*Method)(T1 a1, T2 a2);
+  MethodCall2(C* c, Method m, T1 a1, T2 a2) : c_(c), m_(m), a1_(a1), a2_(a2) {}
+
+  R Marshal(talk_base::Thread* t) {
+    t->Send(this, 0);
+    return r_.value();
+  }
+
+ private:
+  void OnMessage(talk_base::Message*) { r_.Invoke(c_, m_, a1_, a2_); }
+
+  C* c_;
+  Method m_;
+  ReturnType<R> r_;
+  T1 a1_;
+  T2 a2_;
+};
+
+template <typename C, typename R, typename T1, typename T2, typename T3>
+class MethodCall3 : public talk_base::Message,
+                    public talk_base::MessageHandler {
+ public:
+  typedef R (C::*Method)(T1 a1, T2 a2, T3 a3);
+  MethodCall3(C* c, Method m, T1 a1, T2 a2, T3 a3)
+      : c_(c), m_(m), a1_(a1), a2_(a2), a3_(a3) {}
+
+  R Marshal(talk_base::Thread* t) {
+    t->Send(this, 0);
+    return r_.value();
+  }
+
+ private:
+  void OnMessage(talk_base::Message*) { r_.Invoke(c_, m_, a1_, a2_, a3_); }
+
+  C* c_;
+  Method m_;
+  ReturnType<R> r_;
+  T1 a1_;
+  T2 a2_;
+  T3 a3_;
+};
+
+#define BEGIN_PROXY_MAP(c) \
+  class c##Proxy : public c##Interface {\
+   protected:\
+    typedef c##Interface C;\
+    c##Proxy(talk_base::Thread* thread, C* c)\
+      : owner_thread_(thread), \
+        c_(c)  {}\
+    ~c##Proxy() {\
+      MethodCall0<c##Proxy, void> call(this, &c##Proxy::Release_s);\
+      call.Marshal(owner_thread_);\
+    }\
+   public:\
+    static talk_base::scoped_refptr<C> Create(talk_base::Thread* thread, \
+                                              C* c) {\
+      return new talk_base::RefCountedObject<c##Proxy>(thread, c);\
+    }\
+
+#define PROXY_METHOD0(r, method)\
+    r method() OVERRIDE {\
+      MethodCall0<C, r> call(c_.get(), &C::method);\
+      return call.Marshal(owner_thread_);\
+    }\
+
+#define PROXY_CONSTMETHOD0(r, method)\
+    r method() const OVERRIDE {\
+      ConstMethodCall0<C, r> call(c_.get(), &C::method);\
+      return call.Marshal(owner_thread_);\
+     }\
+
+#define PROXY_METHOD1(r, method, t1)\
+    r method(t1 a1) OVERRIDE {\
+      MethodCall1<C, r, t1> call(c_.get(), &C::method, a1);\
+      return call.Marshal(owner_thread_);\
+    }\
+
+#define PROXY_CONSTMETHOD1(r, method, t1)\
+    r method(t1 a1) const OVERRIDE {\
+      ConstMethodCall1<C, r, t1> call(c_.get(), &C::method, a1);\
+      return call.Marshal(owner_thread_);\
+    }\
+
+#define PROXY_METHOD2(r, method, t1, t2)\
+    r method(t1 a1, t2 a2) OVERRIDE {\
+      MethodCall2<C, r, t1, t2> call(c_.get(), &C::method, a1, a2);\
+      return call.Marshal(owner_thread_);\
+    }\
+
+#define PROXY_METHOD3(r, method, t1, t2, t3)\
+    r method(t1 a1, t2 a2, t3 a3) OVERRIDE {\
+      MethodCall3<C, r, t1, t2, t3> call(c_.get(), &C::method, a1, a2, a3);\
+      return call.Marshal(owner_thread_);\
+    }\
+
+#define END_PROXY() \
+   private:\
+    void Release_s() {\
+      c_ = NULL;\
+    }\
+    mutable talk_base::Thread* owner_thread_;\
+    talk_base::scoped_refptr<C> c_;\
+  };\
+
+}  // namespace webrtc
+
+#endif  //  TALK_APP_WEBRTC_PROXY_H_
diff --git a/talk/app/webrtc/proxy_unittest.cc b/talk/app/webrtc/proxy_unittest.cc
new file mode 100644
index 0000000..71a583c
--- /dev/null
+++ b/talk/app/webrtc/proxy_unittest.cc
@@ -0,0 +1,170 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/proxy.h"
+
+#include <string>
+
+#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"
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::Exactly;
+using ::testing::InvokeWithoutArgs;
+using ::testing::Return;
+
+namespace webrtc {
+
+// Interface used for testing here.
+class FakeInterface : public talk_base::RefCountInterface {
+ public:
+  virtual void VoidMethod0() = 0;
+  virtual std::string Method0() = 0;
+  virtual std::string ConstMethod0() const = 0;
+  virtual std::string Method1(std::string s) = 0;
+  virtual std::string ConstMethod1(std::string s) const = 0;
+  virtual std::string Method2(std::string s1, std::string s2) = 0;
+
+ protected:
+  ~FakeInterface() {}
+};
+
+// Proxy for the test interface.
+BEGIN_PROXY_MAP(Fake)
+  PROXY_METHOD0(void, VoidMethod0)
+  PROXY_METHOD0(std::string, Method0)
+  PROXY_CONSTMETHOD0(std::string, ConstMethod0)
+  PROXY_METHOD1(std::string, Method1, std::string)
+  PROXY_CONSTMETHOD1(std::string, ConstMethod1, std::string)
+  PROXY_METHOD2(std::string, Method2, std::string, std::string)
+END_PROXY()
+
+// Implementation of the test interface.
+class Fake : public FakeInterface {
+ public:
+  static talk_base::scoped_refptr<Fake> Create() {
+    return new talk_base::RefCountedObject<Fake>();
+  }
+
+  MOCK_METHOD0(VoidMethod0, void());
+  MOCK_METHOD0(Method0, std::string());
+  MOCK_CONST_METHOD0(ConstMethod0, std::string());
+
+  MOCK_METHOD1(Method1, std::string(std::string));
+  MOCK_CONST_METHOD1(ConstMethod1, std::string(std::string));
+
+  MOCK_METHOD2(Method2, std::string(std::string, std::string));
+
+ protected:
+  Fake() {}
+  ~Fake() {}
+};
+
+class ProxyTest: public testing::Test {
+ public:
+  // Checks that the functions is called on the |signaling_thread_|.
+  void CheckThread() {
+    EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_.get());
+  }
+
+ protected:
+  virtual void SetUp() {
+    signaling_thread_.reset(new talk_base::Thread());
+    ASSERT_TRUE(signaling_thread_->Start());
+    fake_ = Fake::Create();
+    fake_proxy_ = FakeProxy::Create(signaling_thread_.get(), fake_.get());
+  }
+
+ protected:
+  talk_base::scoped_ptr<talk_base::Thread> signaling_thread_;
+  talk_base::scoped_refptr<FakeInterface> fake_proxy_;
+  talk_base::scoped_refptr<Fake> fake_;
+};
+
+TEST_F(ProxyTest, VoidMethod0) {
+  EXPECT_CALL(*fake_, VoidMethod0())
+            .Times(Exactly(1))
+            .WillOnce(InvokeWithoutArgs(this, &ProxyTest::CheckThread));
+  fake_proxy_->VoidMethod0();
+}
+
+TEST_F(ProxyTest, Method0) {
+  EXPECT_CALL(*fake_, Method0())
+            .Times(Exactly(1))
+            .WillOnce(
+                DoAll(InvokeWithoutArgs(this, &ProxyTest::CheckThread),
+                      Return("Method0")));
+  EXPECT_EQ("Method0",
+            fake_proxy_->Method0());
+}
+
+TEST_F(ProxyTest, ConstMethod0) {
+  EXPECT_CALL(*fake_, ConstMethod0())
+            .Times(Exactly(1))
+            .WillOnce(
+                DoAll(InvokeWithoutArgs(this, &ProxyTest::CheckThread),
+                      Return("ConstMethod0")));
+  EXPECT_EQ("ConstMethod0",
+            fake_proxy_->ConstMethod0());
+}
+
+TEST_F(ProxyTest, Method1) {
+  const std::string arg1 = "arg1";
+  EXPECT_CALL(*fake_, Method1(arg1))
+            .Times(Exactly(1))
+            .WillOnce(
+                DoAll(InvokeWithoutArgs(this, &ProxyTest::CheckThread),
+                      Return("Method1")));
+  EXPECT_EQ("Method1", fake_proxy_->Method1(arg1));
+}
+
+TEST_F(ProxyTest, ConstMethod1) {
+  const std::string arg1 = "arg1";
+  EXPECT_CALL(*fake_, ConstMethod1(arg1))
+            .Times(Exactly(1))
+            .WillOnce(
+                DoAll(InvokeWithoutArgs(this, &ProxyTest::CheckThread),
+                      Return("ConstMethod1")));
+  EXPECT_EQ("ConstMethod1", fake_proxy_->ConstMethod1(arg1));
+}
+
+TEST_F(ProxyTest, Method2) {
+  const std::string arg1 = "arg1";
+  const std::string arg2 = "arg2";
+  EXPECT_CALL(*fake_, Method2(arg1, arg2))
+            .Times(Exactly(1))
+            .WillOnce(
+                DoAll(InvokeWithoutArgs(this, &ProxyTest::CheckThread),
+                      Return("Method2")));
+  EXPECT_EQ("Method2", fake_proxy_->Method2(arg1, arg2));
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/statscollector.cc b/talk/app/webrtc/statscollector.cc
new file mode 100644
index 0000000..b994f2f
--- /dev/null
+++ b/talk/app/webrtc/statscollector.cc
@@ -0,0 +1,571 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/statscollector.h"
+
+#include <utility>
+#include <vector>
+
+#include "talk/session/media/channel.h"
+
+namespace webrtc {
+
+// The items below are in alphabetical order.
+const char StatsReport::kStatsValueNameActiveConnection[] =
+    "googActiveConnection";
+const char StatsReport::kStatsValueNameActualEncBitrate[] =
+    "googActualEncBitrate";
+const char StatsReport::kStatsValueNameAudioOutputLevel[] = "audioOutputLevel";
+const char StatsReport::kStatsValueNameAudioInputLevel[] = "audioInputLevel";
+const char StatsReport::kStatsValueNameAvailableReceiveBandwidth[] =
+    "googAvailableReceiveBandwidth";
+const char StatsReport::kStatsValueNameAvailableSendBandwidth[] =
+    "googAvailableSendBandwidth";
+const char StatsReport::kStatsValueNameBucketDelay[] = "googBucketDelay";
+const char StatsReport::kStatsValueNameBytesReceived[] = "bytesReceived";
+const char StatsReport::kStatsValueNameBytesSent[] = "bytesSent";
+const char StatsReport::kStatsValueNameChannelId[] = "googChannelId";
+const char StatsReport::kStatsValueNameCodecName[] = "googCodecName";
+const char StatsReport::kStatsValueNameComponent[] = "googComponent";
+const char StatsReport::kStatsValueNameContentName[] = "googContentName";
+// Echo metrics from the audio processing module.
+const char StatsReport::kStatsValueNameEchoCancellationQualityMin[] =
+    "googEchoCancellationQualityMin";
+const char StatsReport::kStatsValueNameEchoDelayMedian[] =
+    "googEchoCancellationEchoDelayMedian";
+const char StatsReport::kStatsValueNameEchoDelayStdDev[] =
+    "googEchoCancellationEchoDelayStdDev";
+const char StatsReport::kStatsValueNameEchoReturnLoss[] =
+    "googEchoCancellationReturnLoss";
+const char StatsReport::kStatsValueNameEchoReturnLossEnhancement[] =
+    "googEchoCancellationReturnLossEnhancement";
+
+const char StatsReport::kStatsValueNameFirsReceived[] = "googFirsReceived";
+const char StatsReport::kStatsValueNameFirsSent[] = "googFirsSent";
+const char StatsReport::kStatsValueNameFrameHeightReceived[] =
+    "googFrameHeightReceived";
+const char StatsReport::kStatsValueNameFrameHeightSent[] =
+    "googFrameHeightSent";
+const char StatsReport::kStatsValueNameFrameRateReceived[] =
+    "googFrameRateReceived";
+const char StatsReport::kStatsValueNameFrameRateDecoded[] =
+    "googFrameRateDecoded";
+const char StatsReport::kStatsValueNameFrameRateOutput[] =
+    "googFrameRateOutput";
+const char StatsReport::kStatsValueNameFrameRateInput[] = "googFrameRateInput";
+const char StatsReport::kStatsValueNameFrameRateSent[] = "googFrameRateSent";
+const char StatsReport::kStatsValueNameFrameWidthReceived[] =
+    "googFrameWidthReceived";
+const char StatsReport::kStatsValueNameFrameWidthSent[] = "googFrameWidthSent";
+const char StatsReport::kStatsValueNameInitiator[] = "googInitiator";
+const char StatsReport::kStatsValueNameJitterReceived[] = "googJitterReceived";
+const char StatsReport::kStatsValueNameLocalAddress[] = "googLocalAddress";
+const char StatsReport::kStatsValueNameNacksReceived[] = "googNacksReceived";
+const char StatsReport::kStatsValueNameNacksSent[] = "googNacksSent";
+const char StatsReport::kStatsValueNamePacketsReceived[] = "packetsReceived";
+const char StatsReport::kStatsValueNamePacketsSent[] = "packetsSent";
+const char StatsReport::kStatsValueNamePacketsLost[] = "packetsLost";
+const char StatsReport::kStatsValueNameReadable[] = "googReadable";
+const char StatsReport::kStatsValueNameRemoteAddress[] = "googRemoteAddress";
+const char StatsReport::kStatsValueNameRetransmitBitrate[] =
+    "googRetransmitBitrate";
+const char StatsReport::kStatsValueNameRtt[] = "googRtt";
+const char StatsReport::kStatsValueNameTargetEncBitrate[] =
+    "googTargetEncBitrate";
+const char StatsReport::kStatsValueNameTransmitBitrate[] =
+    "googTransmitBitrate";
+const char StatsReport::kStatsValueNameTransportId[] = "transportId";
+const char StatsReport::kStatsValueNameTransportType[] = "googTransportType";
+const char StatsReport::kStatsValueNameTrackId[] = "googTrackId";
+const char StatsReport::kStatsValueNameSsrc[] = "ssrc";
+const char StatsReport::kStatsValueNameWritable[] = "googWritable";
+
+const char StatsReport::kStatsReportTypeSession[] = "googLibjingleSession";
+const char StatsReport::kStatsReportTypeBwe[] = "VideoBwe";
+const char StatsReport::kStatsReportTypeSsrc[] = "ssrc";
+const char StatsReport::kStatsReportTypeTrack[] = "googTrack";
+const char StatsReport::kStatsReportTypeIceCandidate[] = "iceCandidate";
+const char StatsReport::kStatsReportTypeTransport[] = "googTransport";
+const char StatsReport::kStatsReportTypeComponent[] = "googComponent";
+const char StatsReport::kStatsReportTypeCandidatePair[] = "googCandidatePair";
+
+const char StatsReport::kStatsReportVideoBweId[] = "bweforvideo";
+
+// Implementations of functions in statstypes.h
+void StatsReport::AddValue(const std::string& name, const std::string& value) {
+  Value temp;
+  temp.name = name;
+  temp.value = value;
+  values.push_back(temp);
+}
+
+void StatsReport::AddValue(const std::string& name, int64 value) {
+  AddValue(name, talk_base::ToString<int64>(value));
+}
+
+void StatsReport::AddBoolean(const std::string& name, bool value) {
+  AddValue(name, value ? "true" : "false");
+}
+
+namespace {
+typedef std::map<std::string, StatsReport> StatsMap;
+
+std::string StatsId(const std::string& type, const std::string& id) {
+  return type + "_" + id;
+}
+
+bool ExtractValueFromReport(
+    const StatsReport& report,
+    const std::string& name,
+    std::string* value) {
+  StatsReport::Values::const_iterator it = report.values.begin();
+  for (; it != report.values.end(); ++it) {
+    if (it->name == name) {
+      *value = it->value;
+      return true;
+    }
+  }
+  return false;
+}
+
+template <class TrackVector>
+void CreateTrackReports(const TrackVector& tracks, StatsMap* reports) {
+  for (size_t j = 0; j < tracks.size(); ++j) {
+    webrtc::MediaStreamTrackInterface* track = tracks[j];
+    // Adds an empty track report.
+    StatsReport report;
+    report.type = StatsReport::kStatsReportTypeTrack;
+    report.id = StatsId(StatsReport::kStatsReportTypeTrack, track->id());
+    report.AddValue(StatsReport::kStatsValueNameTrackId,
+                    track->id());
+    (*reports)[report.id] = report;
+  }
+}
+
+void ExtractStats(const cricket::VoiceReceiverInfo& info, StatsReport* report) {
+  report->AddValue(StatsReport::kStatsValueNameAudioOutputLevel,
+                   info.audio_level);
+  report->AddValue(StatsReport::kStatsValueNameBytesReceived,
+                   info.bytes_rcvd);
+  report->AddValue(StatsReport::kStatsValueNameJitterReceived,
+                   info.jitter_ms);
+  report->AddValue(StatsReport::kStatsValueNamePacketsReceived,
+                   info.packets_rcvd);
+  report->AddValue(StatsReport::kStatsValueNamePacketsLost,
+                   info.packets_lost);
+}
+
+void ExtractStats(const cricket::VoiceSenderInfo& info, StatsReport* report) {
+  report->AddValue(StatsReport::kStatsValueNameAudioInputLevel,
+                   info.audio_level);
+  report->AddValue(StatsReport::kStatsValueNameBytesSent,
+                   info.bytes_sent);
+  report->AddValue(StatsReport::kStatsValueNamePacketsSent,
+                   info.packets_sent);
+  report->AddValue(StatsReport::kStatsValueNameJitterReceived,
+                   info.jitter_ms);
+  report->AddValue(StatsReport::kStatsValueNameRtt, info.rtt_ms);
+  report->AddValue(StatsReport::kStatsValueNameEchoCancellationQualityMin,
+                   talk_base::ToString<float>(info.aec_quality_min));
+  report->AddValue(StatsReport::kStatsValueNameEchoDelayMedian,
+                   info.echo_delay_median_ms);
+  report->AddValue(StatsReport::kStatsValueNameEchoDelayStdDev,
+                   info.echo_delay_std_ms);
+  report->AddValue(StatsReport::kStatsValueNameEchoReturnLoss,
+                   info.echo_return_loss);
+  report->AddValue(StatsReport::kStatsValueNameEchoReturnLossEnhancement,
+                   info.echo_return_loss_enhancement);
+  report->AddValue(StatsReport::kStatsValueNameCodecName, info.codec_name);
+}
+
+void ExtractStats(const cricket::VideoReceiverInfo& info, StatsReport* report) {
+  report->AddValue(StatsReport::kStatsValueNameBytesReceived,
+                   info.bytes_rcvd);
+  report->AddValue(StatsReport::kStatsValueNamePacketsReceived,
+                   info.packets_rcvd);
+  report->AddValue(StatsReport::kStatsValueNamePacketsLost,
+                   info.packets_lost);
+
+  report->AddValue(StatsReport::kStatsValueNameFirsSent,
+                   info.firs_sent);
+  report->AddValue(StatsReport::kStatsValueNameNacksSent,
+                   info.nacks_sent);
+  report->AddValue(StatsReport::kStatsValueNameFrameWidthReceived,
+                   info.frame_width);
+  report->AddValue(StatsReport::kStatsValueNameFrameHeightReceived,
+                   info.frame_height);
+  report->AddValue(StatsReport::kStatsValueNameFrameRateReceived,
+                   info.framerate_rcvd);
+  report->AddValue(StatsReport::kStatsValueNameFrameRateDecoded,
+                   info.framerate_decoded);
+  report->AddValue(StatsReport::kStatsValueNameFrameRateOutput,
+                   info.framerate_output);
+}
+
+void ExtractStats(const cricket::VideoSenderInfo& info, StatsReport* report) {
+  report->AddValue(StatsReport::kStatsValueNameBytesSent,
+                   info.bytes_sent);
+  report->AddValue(StatsReport::kStatsValueNamePacketsSent,
+                   info.packets_sent);
+
+  report->AddValue(StatsReport::kStatsValueNameFirsReceived,
+                   info.firs_rcvd);
+  report->AddValue(StatsReport::kStatsValueNameNacksReceived,
+                   info.nacks_rcvd);
+  report->AddValue(StatsReport::kStatsValueNameFrameWidthSent,
+                   info.frame_width);
+  report->AddValue(StatsReport::kStatsValueNameFrameHeightSent,
+                   info.frame_height);
+  report->AddValue(StatsReport::kStatsValueNameFrameRateInput,
+                   info.framerate_input);
+  report->AddValue(StatsReport::kStatsValueNameFrameRateSent,
+                   info.framerate_sent);
+  report->AddValue(StatsReport::kStatsValueNameRtt, info.rtt_ms);
+  report->AddValue(StatsReport::kStatsValueNameCodecName, info.codec_name);
+}
+
+void ExtractStats(const cricket::BandwidthEstimationInfo& info,
+                  double stats_gathering_started,
+                  StatsReport* report) {
+  report->id = StatsReport::kStatsReportVideoBweId;
+  report->type = StatsReport::kStatsReportTypeBwe;
+
+  // Clear out stats from previous GatherStats calls if any.
+  if (report->timestamp != stats_gathering_started) {
+    report->values.clear();
+    report->timestamp = stats_gathering_started;
+  }
+
+  report->AddValue(StatsReport::kStatsValueNameAvailableSendBandwidth,
+                   info.available_send_bandwidth);
+  report->AddValue(StatsReport::kStatsValueNameAvailableReceiveBandwidth,
+                   info.available_recv_bandwidth);
+  report->AddValue(StatsReport::kStatsValueNameTargetEncBitrate,
+                   info.target_enc_bitrate);
+  report->AddValue(StatsReport::kStatsValueNameActualEncBitrate,
+                   info.actual_enc_bitrate);
+  report->AddValue(StatsReport::kStatsValueNameRetransmitBitrate,
+                   info.retransmit_bitrate);
+  report->AddValue(StatsReport::kStatsValueNameTransmitBitrate,
+                   info.transmit_bitrate);
+  report->AddValue(StatsReport::kStatsValueNameBucketDelay,
+                   info.bucket_delay);
+}
+
+uint32 ExtractSsrc(const cricket::VoiceReceiverInfo& info) {
+  return info.ssrc;
+}
+
+uint32 ExtractSsrc(const cricket::VoiceSenderInfo& info) {
+  return info.ssrc;
+}
+
+uint32 ExtractSsrc(const cricket::VideoReceiverInfo& info) {
+  return info.ssrcs[0];
+}
+
+uint32 ExtractSsrc(const cricket::VideoSenderInfo& info) {
+  return info.ssrcs[0];
+}
+
+// Template to extract stats from a data vector.
+// ExtractSsrc and ExtractStats must be defined and overloaded for each type.
+template<typename T>
+void ExtractStatsFromList(const std::vector<T>& data,
+                          const std::string& transport_id,
+                          StatsCollector* collector) {
+  typename std::vector<T>::const_iterator it = data.begin();
+  for (; it != data.end(); ++it) {
+    std::string id;
+    uint32 ssrc = ExtractSsrc(*it);
+    StatsReport* report = collector->PrepareReport(ssrc, transport_id);
+    if (!report) {
+      continue;
+    }
+    ExtractStats(*it, report);
+  }
+};
+
+}  // namespace
+
+StatsCollector::StatsCollector()
+    : session_(NULL), stats_gathering_started_(0) {
+}
+
+// Adds a MediaStream with tracks that can be used as a |selector| in a call
+// to GetStats.
+void StatsCollector::AddStream(MediaStreamInterface* stream) {
+  ASSERT(stream != NULL);
+
+  CreateTrackReports<AudioTrackVector>(stream->GetAudioTracks(),
+                                       &reports_);
+  CreateTrackReports<VideoTrackVector>(stream->GetVideoTracks(),
+                                       &reports_);
+}
+
+bool StatsCollector::GetStats(MediaStreamTrackInterface* track,
+                              StatsReports* reports) {
+  ASSERT(reports != NULL);
+  reports->clear();
+
+  StatsMap::iterator it;
+  if (!track) {
+    for (it = reports_.begin(); it != reports_.end(); ++it) {
+      reports->push_back(it->second);
+    }
+    return true;
+  }
+
+  it = reports_.find(StatsId(StatsReport::kStatsReportTypeSession,
+                             session_->id()));
+  if (it != reports_.end()) {
+    reports->push_back(it->second);
+  }
+
+  it = reports_.find(StatsId(StatsReport::kStatsReportTypeTrack, track->id()));
+
+  if (it == reports_.end()) {
+    LOG(LS_WARNING) << "No StatsReport is available for "<< track->id();
+    return false;
+  }
+
+  reports->push_back(it->second);
+
+  std::string track_id;
+  for (it = reports_.begin(); it != reports_.end(); ++it) {
+    if (it->second.type != StatsReport::kStatsReportTypeSsrc) {
+      continue;
+    }
+    if (ExtractValueFromReport(it->second,
+                               StatsReport::kStatsValueNameTrackId,
+                               &track_id)) {
+      if (track_id == track->id()) {
+        reports->push_back(it->second);
+      }
+    }
+  }
+
+  return true;
+}
+
+void StatsCollector::UpdateStats() {
+  double time_now = GetTimeNow();
+  // Calls to UpdateStats() that occur less than kMinGatherStatsPeriod number of
+  // ms apart will be ignored.
+  const double kMinGatherStatsPeriod = 50;
+  if (stats_gathering_started_ + kMinGatherStatsPeriod > time_now) {
+    return;
+  }
+  stats_gathering_started_ = time_now;
+
+  if (session_) {
+    ExtractSessionInfo();
+    ExtractVoiceInfo();
+    ExtractVideoInfo();
+  }
+}
+
+StatsReport* StatsCollector::PrepareReport(uint32 ssrc,
+                                           const std::string& transport_id) {
+  std::string ssrc_id = talk_base::ToString<uint32>(ssrc);
+  StatsMap::iterator it = reports_.find(StatsId(
+      StatsReport::kStatsReportTypeSsrc, ssrc_id));
+
+  std::string track_id;
+  if (it == reports_.end()) {
+    if (!session()->GetTrackIdBySsrc(ssrc, &track_id)) {
+      LOG(LS_ERROR) << "The SSRC " << ssrc
+                    << " is not associated with a track";
+      return NULL;
+    }
+  } else {
+    // Keeps the old track id since we want to report the stats for inactive
+    // tracks.
+    ExtractValueFromReport(it->second,
+                           StatsReport::kStatsValueNameTrackId,
+                           &track_id);
+  }
+
+  StatsReport* report = &reports_[
+      StatsId(StatsReport::kStatsReportTypeSsrc, ssrc_id)];
+  report->id = StatsId(StatsReport::kStatsReportTypeSsrc, ssrc_id);
+  report->type = StatsReport::kStatsReportTypeSsrc;
+
+  // Clear out stats from previous GatherStats calls if any.
+  if (report->timestamp != stats_gathering_started_) {
+    report->values.clear();
+    report->timestamp = stats_gathering_started_;
+  }
+
+  report->AddValue(StatsReport::kStatsValueNameSsrc, ssrc_id);
+  report->AddValue(StatsReport::kStatsValueNameTrackId, track_id);
+  // Add the mapping of SSRC to transport.
+  report->AddValue(StatsReport::kStatsValueNameTransportId,
+                   transport_id);
+  return report;
+}
+
+void StatsCollector::ExtractSessionInfo() {
+  // Extract information from the base session.
+  StatsReport report;
+  report.id = StatsId(StatsReport::kStatsReportTypeSession, session_->id());
+  report.type = StatsReport::kStatsReportTypeSession;
+  report.timestamp = stats_gathering_started_;
+  report.values.clear();
+  report.AddBoolean(StatsReport::kStatsValueNameInitiator,
+                    session_->initiator());
+
+  reports_[report.id] = report;
+
+  cricket::SessionStats stats;
+  if (session_->GetStats(&stats)) {
+    // Store the proxy map away for use in SSRC reporting.
+    proxy_to_transport_ = stats.proxy_to_transport;
+
+    for (cricket::TransportStatsMap::iterator transport_iter
+             = stats.transport_stats.begin();
+         transport_iter != stats.transport_stats.end(); ++transport_iter) {
+      for (cricket::TransportChannelStatsList::iterator channel_iter
+               = transport_iter->second.channel_stats.begin();
+           channel_iter != transport_iter->second.channel_stats.end();
+           ++channel_iter) {
+        StatsReport channel_report;
+        std::ostringstream ostc;
+        ostc << "Channel-" << transport_iter->second.content_name
+             << "-" << channel_iter->component;
+        channel_report.id = ostc.str();
+        channel_report.type = StatsReport::kStatsReportTypeComponent;
+        channel_report.timestamp = stats_gathering_started_;
+        channel_report.AddValue(StatsReport::kStatsValueNameComponent,
+                                channel_iter->component);
+        reports_[channel_report.id] = channel_report;
+        for (size_t i = 0;
+             i < channel_iter->connection_infos.size();
+             ++i) {
+          StatsReport report;
+          const cricket::ConnectionInfo& info
+              = channel_iter->connection_infos[i];
+          std::ostringstream ost;
+          ost << "Conn-" << transport_iter->first << "-"
+              << channel_iter->component << "-" << i;
+          report.id = ost.str();
+          report.type = StatsReport::kStatsReportTypeCandidatePair;
+          report.timestamp = stats_gathering_started_;
+          // Link from connection to its containing channel.
+          report.AddValue(StatsReport::kStatsValueNameChannelId,
+                          channel_report.id);
+          report.AddValue(StatsReport::kStatsValueNameBytesSent,
+                          info.sent_total_bytes);
+          report.AddValue(StatsReport::kStatsValueNameBytesReceived,
+                          info.recv_total_bytes);
+          report.AddBoolean(StatsReport::kStatsValueNameWritable,
+                            info.writable);
+          report.AddBoolean(StatsReport::kStatsValueNameReadable,
+                            info.readable);
+          report.AddBoolean(StatsReport::kStatsValueNameActiveConnection,
+                            info.best_connection);
+          report.AddValue(StatsReport::kStatsValueNameLocalAddress,
+                          info.local_candidate.address().ToString());
+          report.AddValue(StatsReport::kStatsValueNameRemoteAddress,
+                          info.remote_candidate.address().ToString());
+          reports_[report.id] = report;
+        }
+      }
+    }
+  }
+}
+
+void StatsCollector::ExtractVoiceInfo() {
+  if (!session_->voice_channel()) {
+    return;
+  }
+  cricket::VoiceMediaInfo voice_info;
+  if (!session_->voice_channel()->GetStats(&voice_info)) {
+    LOG(LS_ERROR) << "Failed to get voice channel stats.";
+    return;
+  }
+  std::string transport_id;
+  if (!GetTransportIdFromProxy(session_->voice_channel()->content_name(),
+                               &transport_id)) {
+    LOG(LS_ERROR) << "Failed to get transport name for proxy "
+                  << session_->voice_channel()->content_name();
+    return;
+  }
+  ExtractStatsFromList(voice_info.receivers, transport_id, this);
+  ExtractStatsFromList(voice_info.senders, transport_id, this);
+}
+
+void StatsCollector::ExtractVideoInfo() {
+  if (!session_->video_channel()) {
+    return;
+  }
+  cricket::VideoMediaInfo video_info;
+  if (!session_->video_channel()->GetStats(&video_info)) {
+    LOG(LS_ERROR) << "Failed to get video channel stats.";
+    return;
+  }
+  std::string transport_id;
+  if (!GetTransportIdFromProxy(session_->video_channel()->content_name(),
+                               &transport_id)) {
+    LOG(LS_ERROR) << "Failed to get transport name for proxy "
+                  << session_->video_channel()->content_name();
+    return;
+  }
+  ExtractStatsFromList(video_info.receivers, transport_id, this);
+  ExtractStatsFromList(video_info.senders, transport_id, this);
+  if (video_info.bw_estimations.size() != 1) {
+    LOG(LS_ERROR) << "BWEs count: " << video_info.bw_estimations.size();
+  } else {
+    StatsReport* report = &reports_[StatsReport::kStatsReportVideoBweId];
+    ExtractStats(
+        video_info.bw_estimations[0], stats_gathering_started_, report);
+  }
+}
+
+double StatsCollector::GetTimeNow() {
+  return timing_.WallTimeNow() * talk_base::kNumMillisecsPerSec;
+}
+
+bool StatsCollector::GetTransportIdFromProxy(const std::string& proxy,
+                                             std::string* transport) {
+  // TODO(hta): Remove handling of empty proxy name once tests do not use it.
+  if (proxy.empty()) {
+    transport->clear();
+    return true;
+  }
+  if (proxy_to_transport_.find(proxy) == proxy_to_transport_.end()) {
+    LOG(LS_ERROR) << "No transport ID mapping for " << proxy;
+    return false;
+  }
+  std::ostringstream ost;
+  // Component 1 is always used for RTP.
+  ost << "Channel-" << proxy_to_transport_[proxy] << "-1";
+  *transport = ost.str();
+  return true;
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/statscollector.h b/talk/app/webrtc/statscollector.h
new file mode 100644
index 0000000..03a32c4
--- /dev/null
+++ b/talk/app/webrtc/statscollector.h
@@ -0,0 +1,95 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 gathering statistics from an ongoing
+// libjingle PeerConnection.
+
+#ifndef TALK_APP_WEBRTC_STATSCOLLECTOR_H_
+#define TALK_APP_WEBRTC_STATSCOLLECTOR_H_
+
+#include <string>
+#include <map>
+
+#include "talk/app/webrtc/mediastreaminterface.h"
+#include "talk/app/webrtc/statstypes.h"
+#include "talk/app/webrtc/webrtcsession.h"
+
+#include "talk/base/timing.h"
+
+namespace webrtc {
+
+class StatsCollector {
+ public:
+  StatsCollector();
+
+  // Register the session Stats should operate on.
+  // Set to NULL if the session has ended.
+  void set_session(WebRtcSession* session) {
+    session_ = session;
+  }
+
+  // Adds a MediaStream with tracks that can be used as a |selector| in a call
+  // to GetStats.
+  void AddStream(MediaStreamInterface* stream);
+
+  // Gather statistics from the session and store them for future use.
+  void UpdateStats();
+
+  // Gets a StatsReports of the last collected stats. Note that UpdateStats must
+  // be called before this function to get the most recent stats. |selector| is
+  // a track label or empty string. The most recent reports are stored in
+  // |reports|.
+  bool GetStats(MediaStreamTrackInterface* track, StatsReports* reports);
+
+  WebRtcSession* session() { return session_; }
+  // Prepare an SSRC report for the given ssrc. Used internally.
+  StatsReport* PrepareReport(uint32 ssrc, const std::string& transport);
+  // Extracts the ID of a Transport belonging to an SSRC. Used internally.
+  bool GetTransportIdFromProxy(const std::string& proxy,
+                               std::string* transport_id);
+
+ private:
+  bool CopySelectedReports(const std::string& selector, StatsReports* reports);
+
+  void ExtractSessionInfo();
+  void ExtractVoiceInfo();
+  void ExtractVideoInfo();
+  double GetTimeNow();
+  void BuildSsrcToTransportId();
+
+  // A map from the report id to the report.
+  std::map<std::string, webrtc::StatsReport> reports_;
+  // Raw pointer to the session the statistics are gathered from.
+  WebRtcSession* session_;
+  double stats_gathering_started_;
+  talk_base::Timing timing_;
+  cricket::ProxyTransportMap proxy_to_transport_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_STATSCOLLECTOR_H_
diff --git a/talk/app/webrtc/statscollector_unittest.cc b/talk/app/webrtc/statscollector_unittest.cc
new file mode 100644
index 0000000..cce1645bc
--- /dev/null
+++ b/talk/app/webrtc/statscollector_unittest.cc
@@ -0,0 +1,442 @@
+/*
+ * libjingle
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/app/webrtc/statscollector.h"
+
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/app/webrtc/videotrack.h"
+#include "talk/base/gunit.h"
+#include "talk/media/base/fakemediaengine.h"
+#include "talk/media/devices/fakedevicemanager.h"
+#include "talk/p2p/base/fakesession.h"
+#include "talk/session/media/channelmanager.h"
+#include "testing/base/public/gmock.h"
+
+using testing::_;
+using testing::DoAll;
+using testing::Return;
+using testing::ReturnNull;
+using testing::SetArgPointee;
+
+namespace cricket {
+
+class ChannelManager;
+class FakeDeviceManager;
+
+}  // namespace cricket
+
+namespace {
+
+// Error return values
+const char kNotFound[] = "NOT FOUND";
+const char kNoReports[] = "NO REPORTS";
+
+class MockWebRtcSession : public webrtc::WebRtcSession {
+ public:
+  explicit MockWebRtcSession(cricket::ChannelManager* channel_manager)
+    : WebRtcSession(channel_manager, talk_base::Thread::Current(),
+                    NULL, NULL, NULL) {
+  }
+  MOCK_METHOD0(video_channel, cricket::VideoChannel*());
+  MOCK_METHOD2(GetTrackIdBySsrc, bool(uint32, std::string*));
+  MOCK_METHOD1(GetStats, bool(cricket::SessionStats*));
+};
+
+class MockVideoMediaChannel : public cricket::FakeVideoMediaChannel {
+ public:
+  MockVideoMediaChannel()
+    : cricket::FakeVideoMediaChannel(NULL) {
+  }
+  // MOCK_METHOD0(transport_channel, cricket::TransportChannel*());
+  MOCK_METHOD1(GetStats, bool(cricket::VideoMediaInfo*));
+};
+
+std::string ExtractStatsValue(const std::string& type,
+                              webrtc::StatsReports reports,
+                              const std::string name) {
+  if (reports.empty()) {
+    return kNoReports;
+  }
+  for (size_t i = 0; i < reports.size(); ++i) {
+    if (reports[i].type != type)
+      continue;
+    webrtc::StatsReport::Values::const_iterator it =
+        reports[i].values.begin();
+    for (; it != reports[i].values.end(); ++it) {
+      if (it->name == name) {
+        return it->value;
+      }
+    }
+  }
+
+  return kNotFound;
+}
+
+// Finds the |n|-th report of type |type| in |reports|.
+// |n| starts from 1 for finding the first report.
+const webrtc::StatsReport* FindNthReportByType(webrtc::StatsReports reports,
+                                               const std::string& type,
+                                               int n) {
+  for (size_t i = 0; i < reports.size(); ++i) {
+    if (reports[i].type == type) {
+      n--;
+      if (n == 0)
+        return &reports[i];
+    }
+  }
+  return NULL;
+}
+
+const webrtc::StatsReport* FindReportById(webrtc::StatsReports reports,
+                                          const std::string& id) {
+  for (size_t i = 0; i < reports.size(); ++i) {
+    if (reports[i].id == id) {
+      return &reports[i];
+    }
+  }
+  return NULL;
+}
+
+std::string ExtractSsrcStatsValue(webrtc::StatsReports reports,
+                                  const std::string& name) {
+  return ExtractStatsValue(
+      webrtc::StatsReport::kStatsReportTypeSsrc, reports, name);
+}
+
+std::string ExtractBweStatsValue(webrtc::StatsReports reports,
+                                  const std::string& name) {
+  return ExtractStatsValue(
+      webrtc::StatsReport::kStatsReportTypeBwe, reports, name);
+}
+
+class StatsCollectorTest : public testing::Test {
+ protected:
+  StatsCollectorTest()
+    : media_engine_(new cricket::FakeMediaEngine),
+      channel_manager_(
+          new cricket::ChannelManager(media_engine_,
+                                      new cricket::FakeDeviceManager(),
+                                      talk_base::Thread::Current())),
+      session_(channel_manager_.get()) {
+    // By default, we ignore session GetStats calls.
+    EXPECT_CALL(session_, GetStats(_)).WillRepeatedly(Return(false));
+  }
+
+  cricket::FakeMediaEngine* media_engine_;
+  talk_base::scoped_ptr<cricket::ChannelManager> channel_manager_;
+  MockWebRtcSession session_;
+};
+
+// This test verifies that 64-bit counters are passed successfully.
+TEST_F(StatsCollectorTest, BytesCounterHandles64Bits) {
+  webrtc::StatsCollector stats;  // Implementation under test.
+  MockVideoMediaChannel* media_channel = new MockVideoMediaChannel;
+  cricket::VideoChannel video_channel(talk_base::Thread::Current(),
+      media_engine_, media_channel, &session_, "", false, NULL);
+  webrtc::StatsReports reports;  // returned values.
+  cricket::VideoSenderInfo video_sender_info;
+  cricket::VideoMediaInfo stats_read;
+  const uint32 kSsrcOfTrack = 1234;
+  const std::string kNameOfTrack("somename");
+  // The number of bytes must be larger than 0xFFFFFFFF for this test.
+  const int64 kBytesSent = 12345678901234LL;
+  const std::string kBytesSentString("12345678901234");
+
+  stats.set_session(&session_);
+  talk_base::scoped_refptr<webrtc::MediaStream> stream(
+      webrtc::MediaStream::Create("streamlabel"));
+  stream->AddTrack(webrtc::VideoTrack::Create(kNameOfTrack, NULL));
+  stats.AddStream(stream);
+
+  // Construct a stats value to read.
+  video_sender_info.ssrcs.push_back(1234);
+  video_sender_info.bytes_sent = kBytesSent;
+  stats_read.senders.push_back(video_sender_info);
+
+  EXPECT_CALL(session_, video_channel())
+    .WillRepeatedly(Return(&video_channel));
+  EXPECT_CALL(*media_channel, GetStats(_))
+    .WillOnce(DoAll(SetArgPointee<0>(stats_read),
+                    Return(true)));
+  EXPECT_CALL(session_, GetTrackIdBySsrc(kSsrcOfTrack, _))
+    .WillOnce(DoAll(SetArgPointee<1>(kNameOfTrack),
+                    Return(true)));
+  stats.UpdateStats();
+  stats.GetStats(NULL, &reports);
+  std::string result = ExtractSsrcStatsValue(reports, "bytesSent");
+  EXPECT_EQ(kBytesSentString, result);
+}
+
+// Test that BWE information is reported via stats.
+TEST_F(StatsCollectorTest, BandwidthEstimationInfoIsReported) {
+  webrtc::StatsCollector stats;  // Implementation under test.
+  MockVideoMediaChannel* media_channel = new MockVideoMediaChannel;
+  cricket::VideoChannel video_channel(talk_base::Thread::Current(),
+      media_engine_, media_channel, &session_, "", false, NULL);
+  webrtc::StatsReports reports;  // returned values.
+  cricket::VideoSenderInfo video_sender_info;
+  cricket::VideoMediaInfo stats_read;
+  // Set up an SSRC just to test that we get both kinds of stats back: SSRC and
+  // BWE.
+  const uint32 kSsrcOfTrack = 1234;
+  const std::string kNameOfTrack("somename");
+  const int64 kBytesSent = 12345678901234LL;
+  const std::string kBytesSentString("12345678901234");
+
+  stats.set_session(&session_);
+  talk_base::scoped_refptr<webrtc::MediaStream> stream(
+      webrtc::MediaStream::Create("streamlabel"));
+  stream->AddTrack(webrtc::VideoTrack::Create(kNameOfTrack, NULL));
+  stats.AddStream(stream);
+
+  // Construct a stats value to read.
+  video_sender_info.ssrcs.push_back(1234);
+  video_sender_info.bytes_sent = kBytesSent;
+  stats_read.senders.push_back(video_sender_info);
+  cricket::BandwidthEstimationInfo bwe;
+  const int kTargetEncBitrate = 123456;
+  const std::string kTargetEncBitrateString("123456");
+  bwe.target_enc_bitrate = kTargetEncBitrate;
+  stats_read.bw_estimations.push_back(bwe);
+
+  EXPECT_CALL(session_, video_channel())
+    .WillRepeatedly(Return(&video_channel));
+  EXPECT_CALL(*media_channel, GetStats(_))
+    .WillOnce(DoAll(SetArgPointee<0>(stats_read),
+                    Return(true)));
+  EXPECT_CALL(session_, GetTrackIdBySsrc(kSsrcOfTrack, _))
+    .WillOnce(DoAll(SetArgPointee<1>(kNameOfTrack),
+                    Return(true)));
+  stats.UpdateStats();
+  stats.GetStats(NULL, &reports);
+  std::string result = ExtractSsrcStatsValue(reports, "bytesSent");
+  EXPECT_EQ(kBytesSentString, result);
+  result = ExtractBweStatsValue(reports, "googTargetEncBitrate");
+  EXPECT_EQ(kTargetEncBitrateString, result);
+}
+
+// This test verifies that an object of type "googSession" always
+// exists in the returned stats.
+TEST_F(StatsCollectorTest, SessionObjectExists) {
+  webrtc::StatsCollector stats;  // Implementation under test.
+  webrtc::StatsReports reports;  // returned values.
+  stats.set_session(&session_);
+  EXPECT_CALL(session_, video_channel())
+    .WillRepeatedly(ReturnNull());
+  stats.UpdateStats();
+  stats.GetStats(NULL, &reports);
+  const webrtc::StatsReport* session_report = FindNthReportByType(
+      reports, webrtc::StatsReport::kStatsReportTypeSession, 1);
+  EXPECT_FALSE(session_report == NULL);
+}
+
+// This test verifies that only one object of type "googSession" exists
+// in the returned stats.
+TEST_F(StatsCollectorTest, OnlyOneSessionObjectExists) {
+  webrtc::StatsCollector stats;  // Implementation under test.
+  webrtc::StatsReports reports;  // returned values.
+  stats.set_session(&session_);
+  EXPECT_CALL(session_, video_channel())
+    .WillRepeatedly(ReturnNull());
+  stats.UpdateStats();
+  stats.UpdateStats();
+  stats.GetStats(NULL, &reports);
+  const webrtc::StatsReport* session_report = FindNthReportByType(
+      reports, webrtc::StatsReport::kStatsReportTypeSession, 1);
+  EXPECT_FALSE(session_report == NULL);
+  session_report = FindNthReportByType(
+      reports, webrtc::StatsReport::kStatsReportTypeSession, 2);
+  EXPECT_EQ(NULL, session_report);
+}
+
+// This test verifies that the empty track report exists in the returned stats
+// without calling StatsCollector::UpdateStats.
+TEST_F(StatsCollectorTest, TrackObjectExistsWithoutUpdateStats) {
+  webrtc::StatsCollector stats;  // Implementation under test.
+  MockVideoMediaChannel* media_channel = new MockVideoMediaChannel;
+  cricket::VideoChannel video_channel(talk_base::Thread::Current(),
+      media_engine_, media_channel, &session_, "", false, NULL);
+  const std::string kTrackId("somename");
+  talk_base::scoped_refptr<webrtc::MediaStream> stream(
+      webrtc::MediaStream::Create("streamlabel"));
+  talk_base::scoped_refptr<webrtc::VideoTrack> track =
+      webrtc::VideoTrack::Create(kTrackId, NULL);
+  stream->AddTrack(track);
+  stats.AddStream(stream);
+
+  stats.set_session(&session_);
+
+  webrtc::StatsReports reports;
+
+  // Verfies the existence of the track report.
+  stats.GetStats(NULL, &reports);
+  EXPECT_EQ((size_t)1, reports.size());
+  EXPECT_EQ(std::string(webrtc::StatsReport::kStatsReportTypeTrack),
+            reports[0].type);
+
+  std::string trackValue =
+      ExtractStatsValue(webrtc::StatsReport::kStatsReportTypeTrack,
+                        reports,
+                        webrtc::StatsReport::kStatsValueNameTrackId);
+  EXPECT_EQ(kTrackId, trackValue);
+}
+
+// This test verifies that the empty track report exists in the returned stats
+// when StatsCollector::UpdateStats is called with ssrc stats.
+TEST_F(StatsCollectorTest, TrackAndSsrcObjectExistAfterUpdateSsrcStats) {
+  webrtc::StatsCollector stats;  // Implementation under test.
+  MockVideoMediaChannel* media_channel = new MockVideoMediaChannel;
+  cricket::VideoChannel video_channel(talk_base::Thread::Current(),
+      media_engine_, media_channel, &session_, "", false, NULL);
+  const std::string kTrackId("somename");
+  talk_base::scoped_refptr<webrtc::MediaStream> stream(
+      webrtc::MediaStream::Create("streamlabel"));
+  talk_base::scoped_refptr<webrtc::VideoTrack> track =
+      webrtc::VideoTrack::Create(kTrackId, NULL);
+  stream->AddTrack(track);
+  stats.AddStream(stream);
+
+  stats.set_session(&session_);
+
+  webrtc::StatsReports reports;
+
+  // Constructs an ssrc stats update.
+  cricket::VideoSenderInfo video_sender_info;
+  cricket::VideoMediaInfo stats_read;
+  const uint32 kSsrcOfTrack = 1234;
+  const int64 kBytesSent = 12345678901234LL;
+
+  // Construct a stats value to read.
+  video_sender_info.ssrcs.push_back(1234);
+  video_sender_info.bytes_sent = kBytesSent;
+  stats_read.senders.push_back(video_sender_info);
+
+  EXPECT_CALL(session_, video_channel())
+    .WillRepeatedly(Return(&video_channel));
+  EXPECT_CALL(*media_channel, GetStats(_))
+    .WillOnce(DoAll(SetArgPointee<0>(stats_read),
+                    Return(true)));
+  EXPECT_CALL(session_, GetTrackIdBySsrc(kSsrcOfTrack, _))
+    .WillOnce(DoAll(SetArgPointee<1>(kTrackId),
+                    Return(true)));
+
+  stats.UpdateStats();
+  stats.GetStats(NULL, &reports);
+  // |reports| should contain one session report, one track report, and one ssrc
+  // report.
+  EXPECT_EQ((size_t)3, reports.size());
+  const webrtc::StatsReport* track_report = FindNthReportByType(
+      reports, webrtc::StatsReport::kStatsReportTypeTrack, 1);
+  EXPECT_FALSE(track_report == NULL);
+
+  stats.GetStats(track, &reports);
+  // |reports| should contain one session report, one track report, and one ssrc
+  // report.
+  EXPECT_EQ((size_t)3, reports.size());
+  track_report = FindNthReportByType(
+      reports, webrtc::StatsReport::kStatsReportTypeTrack, 1);
+  EXPECT_FALSE(track_report == NULL);
+
+  std::string ssrc_id = ExtractSsrcStatsValue(
+      reports, webrtc::StatsReport::kStatsValueNameSsrc);
+  EXPECT_EQ(talk_base::ToString<uint32>(kSsrcOfTrack), ssrc_id);
+
+  std::string track_id = ExtractSsrcStatsValue(
+      reports, webrtc::StatsReport::kStatsValueNameTrackId);
+  EXPECT_EQ(kTrackId, track_id);
+}
+
+// This test verifies that an SSRC object has the identifier of a Transport
+// stats object, and that this transport stats object exists in stats.
+TEST_F(StatsCollectorTest, TransportObjectLinkedFromSsrcObject) {
+  webrtc::StatsCollector stats;  // Implementation under test.
+  MockVideoMediaChannel* media_channel = new MockVideoMediaChannel;
+  // The content_name known by the video channel.
+  const std::string kVcName("vcname");
+  cricket::VideoChannel video_channel(talk_base::Thread::Current(),
+      media_engine_, media_channel, &session_, kVcName, false, NULL);
+  const std::string kTrackId("somename");
+  talk_base::scoped_refptr<webrtc::MediaStream> stream(
+      webrtc::MediaStream::Create("streamlabel"));
+  talk_base::scoped_refptr<webrtc::VideoTrack> track =
+      webrtc::VideoTrack::Create(kTrackId, NULL);
+  stream->AddTrack(track);
+  stats.AddStream(stream);
+
+  stats.set_session(&session_);
+
+  webrtc::StatsReports reports;
+
+  // Constructs an ssrc stats update.
+  cricket::VideoSenderInfo video_sender_info;
+  cricket::VideoMediaInfo stats_read;
+  const uint32 kSsrcOfTrack = 1234;
+  const int64 kBytesSent = 12345678901234LL;
+
+  // Construct a stats value to read.
+  video_sender_info.ssrcs.push_back(1234);
+  video_sender_info.bytes_sent = kBytesSent;
+  stats_read.senders.push_back(video_sender_info);
+
+  EXPECT_CALL(session_, video_channel())
+    .WillRepeatedly(Return(&video_channel));
+  EXPECT_CALL(*media_channel, GetStats(_))
+    .WillRepeatedly(DoAll(SetArgPointee<0>(stats_read),
+                          Return(true)));
+  EXPECT_CALL(session_, GetTrackIdBySsrc(kSsrcOfTrack, _))
+    .WillOnce(DoAll(SetArgPointee<1>(kTrackId),
+                    Return(true)));
+
+  // Instruct the session to return stats containing the transport channel.
+  const std::string kTransportName("trspname");
+  cricket::SessionStats session_stats;
+  cricket::TransportStats transport_stats;
+  cricket::TransportChannelStats channel_stats;
+  channel_stats.component = 1;
+  transport_stats.content_name = kTransportName;
+  transport_stats.channel_stats.push_back(channel_stats);
+
+  session_stats.transport_stats[kTransportName] = transport_stats;
+  session_stats.proxy_to_transport[kVcName] = kTransportName;
+  EXPECT_CALL(session_, GetStats(_))
+    .WillRepeatedly(DoAll(SetArgPointee<0>(session_stats),
+                          Return(true)));
+
+  stats.UpdateStats();
+  stats.GetStats(NULL, &reports);
+  std::string transport_id = ExtractStatsValue(
+      webrtc::StatsReport::kStatsReportTypeSsrc,
+      reports,
+      webrtc::StatsReport::kStatsValueNameTransportId);
+  ASSERT_NE(kNotFound, transport_id);
+  const webrtc::StatsReport* transport_report = FindReportById(reports,
+                                                               transport_id);
+  ASSERT_FALSE(transport_report == NULL);
+}
+
+}  // namespace
diff --git a/talk/app/webrtc/statstypes.h b/talk/app/webrtc/statstypes.h
new file mode 100644
index 0000000..62f8781
--- /dev/null
+++ b/talk/app/webrtc/statstypes.h
@@ -0,0 +1,160 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 used for retrieving statistics from an ongoing
+// libjingle session.
+
+#ifndef TALK_APP_WEBRTC_STATSTYPES_H_
+#define TALK_APP_WEBRTC_STATSTYPES_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/stringencode.h"
+
+namespace webrtc {
+
+class StatsReport {
+ public:
+  StatsReport() : timestamp(0) { }
+
+  std::string id;  // See below for contents.
+  std::string type;  // See below for contents.
+
+  struct Value {
+    std::string name;
+    std::string value;
+  };
+
+  void AddValue(const std::string& name, const std::string& value);
+  void AddValue(const std::string& name, int64 value);
+  void AddBoolean(const std::string& name, bool value);
+
+  double timestamp;  // Time since 1970-01-01T00:00:00Z in milliseconds.
+  typedef std::vector<Value> Values;
+  Values values;
+
+  // StatsReport types.
+  // A StatsReport of |type| = "googSession" contains overall information
+  // about the thing libjingle calls a session (which may contain one
+  // or more RTP sessions.
+  static const char kStatsReportTypeSession[];
+
+  // A StatsReport of |type| = "googTransport" contains information
+  // about a libjingle "transport".
+  static const char kStatsReportTypeTransport[];
+
+  // A StatsReport of |type| = "googComponent" contains information
+  // about a libjingle "channel" (typically, RTP or RTCP for a transport).
+  // This is intended to be the same thing as an ICE "Component".
+  static const char kStatsReportTypeComponent[];
+
+  // A StatsReport of |type| = "googCandidatePair" contains information
+  // about a libjingle "connection" - a single source/destination port pair.
+  // This is intended to be the same thing as an ICE "candidate pair".
+  static const char kStatsReportTypeCandidatePair[];
+
+  // StatsReport of |type| = "VideoBWE" is statistics for video Bandwidth
+  // Estimation, which is global per-session.  The |id| field is "bweforvideo"
+  // (will probably change in the future).
+  static const char kStatsReportTypeBwe[];
+
+  // StatsReport of |type| = "ssrc" is statistics for a specific rtp stream.
+  // The |id| field is the SSRC in decimal form of the rtp stream.
+  static const char kStatsReportTypeSsrc[];
+
+  // StatsReport of |type| = "googTrack" is statistics for a specific media
+  // track. The |id| field is the track id.
+  static const char kStatsReportTypeTrack[];
+
+  // StatsReport of |type| = "iceCandidate" is statistics on a specific
+  // ICE Candidate. It links to its transport.
+  static const char kStatsReportTypeIceCandidate[];
+
+  // The id of StatsReport of type VideoBWE.
+  static const char kStatsReportVideoBweId[];
+
+  // StatsValue names
+  static const char kStatsValueNameAudioOutputLevel[];
+  static const char kStatsValueNameAudioInputLevel[];
+  static const char kStatsValueNameBytesSent[];
+  static const char kStatsValueNamePacketsSent[];
+  static const char kStatsValueNameBytesReceived[];
+  static const char kStatsValueNamePacketsReceived[];
+  static const char kStatsValueNamePacketsLost[];
+  static const char kStatsValueNameTransportId[];
+  static const char kStatsValueNameLocalAddress[];
+  static const char kStatsValueNameRemoteAddress[];
+  static const char kStatsValueNameWritable[];
+  static const char kStatsValueNameReadable[];
+  static const char kStatsValueNameActiveConnection[];
+
+
+  // Internal StatsValue names
+  static const char kStatsValueNameCodecName[];
+  static const char kStatsValueNameEchoCancellationQualityMin[];
+  static const char kStatsValueNameEchoDelayMedian[];
+  static const char kStatsValueNameEchoDelayStdDev[];
+  static const char kStatsValueNameEchoReturnLoss[];
+  static const char kStatsValueNameEchoReturnLossEnhancement[];
+  static const char kStatsValueNameFirsReceived[];
+  static const char kStatsValueNameFirsSent[];
+  static const char kStatsValueNameFrameHeightReceived[];
+  static const char kStatsValueNameFrameHeightSent[];
+  static const char kStatsValueNameFrameRateReceived[];
+  static const char kStatsValueNameFrameRateDecoded[];
+  static const char kStatsValueNameFrameRateOutput[];
+  static const char kStatsValueNameFrameRateInput[];
+  static const char kStatsValueNameFrameRateSent[];
+  static const char kStatsValueNameFrameWidthReceived[];
+  static const char kStatsValueNameFrameWidthSent[];
+  static const char kStatsValueNameJitterReceived[];
+  static const char kStatsValueNameNacksReceived[];
+  static const char kStatsValueNameNacksSent[];
+  static const char kStatsValueNameRtt[];
+  static const char kStatsValueNameAvailableSendBandwidth[];
+  static const char kStatsValueNameAvailableReceiveBandwidth[];
+  static const char kStatsValueNameTargetEncBitrate[];
+  static const char kStatsValueNameActualEncBitrate[];
+  static const char kStatsValueNameRetransmitBitrate[];
+  static const char kStatsValueNameTransmitBitrate[];
+  static const char kStatsValueNameBucketDelay[];
+  static const char kStatsValueNameInitiator[];
+  static const char kStatsValueNameTransportType[];
+  static const char kStatsValueNameContentName[];
+  static const char kStatsValueNameComponent[];
+  static const char kStatsValueNameChannelId[];
+  static const char kStatsValueNameTrackId[];
+  static const char kStatsValueNameSsrc[];
+};
+
+typedef std::vector<StatsReport> StatsReports;
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_STATSTYPES_H_
diff --git a/talk/app/webrtc/streamcollection.h b/talk/app/webrtc/streamcollection.h
new file mode 100644
index 0000000..7796b42
--- /dev/null
+++ b/talk/app/webrtc/streamcollection.h
@@ -0,0 +1,125 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_APP_WEBRTC_STREAMCOLLECTION_H_
+#define TALK_APP_WEBRTC_STREAMCOLLECTION_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/app/webrtc/peerconnectioninterface.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;
+  }
+
+  virtual MediaStreamTrackInterface* FindAudioTrack(
+      const std::string& id) {
+    for (size_t i = 0; i < media_streams_.size(); ++i) {
+      MediaStreamTrackInterface* track = media_streams_[i]->FindAudioTrack(id);
+      if (track) {
+        return track;
+      }
+    }
+    return NULL;
+  }
+
+  virtual MediaStreamTrackInterface* FindVideoTrack(
+      const std::string& id) {
+    for (size_t i = 0; i < media_streams_.size(); ++i) {
+      MediaStreamTrackInterface* track = media_streams_[i]->FindVideoTrack(id);
+      if (track) {
+        return track;
+      }
+    }
+    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_STREAMCOLLECTION_H_
diff --git a/talk/app/webrtc/test/fakeaudiocapturemodule.cc b/talk/app/webrtc/test/fakeaudiocapturemodule.cc
new file mode 100644
index 0000000..4bdaf89
--- /dev/null
+++ b/talk/app/webrtc/test/fakeaudiocapturemodule.cc
@@ -0,0 +1,716 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/test/fakeaudiocapturemodule.h"
+
+#include "talk/base/common.h"
+#include "talk/base/refcount.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+
+// Audio sample value that is high enough that it doesn't occur naturally when
+// frames are being faked. E.g. NetEq will not generate this large sample value
+// unless it has received an audio frame containing a sample of this value.
+// Even simpler buffers would likely just contain audio sample values of 0.
+static const int kHighSampleValue = 10000;
+
+// Same value as src/modules/audio_device/main/source/audio_device_config.h in
+// https://code.google.com/p/webrtc/
+static const uint32 kAdmMaxIdleTimeProcess = 1000;
+
+// Constants here are derived by running VoE using a real ADM.
+// The constants correspond to 10ms of mono audio at 44kHz.
+static const int kTimePerFrameMs = 10;
+static const int kNumberOfChannels = 1;
+static const int kSamplesPerSecond = 44000;
+static const int kTotalDelayMs = 0;
+static const int kClockDriftMs = 0;
+static const uint32_t kMaxVolume = 14392;
+
+enum {
+  MSG_RUN_PROCESS,
+  MSG_STOP_PROCESS,
+};
+
+FakeAudioCaptureModule::FakeAudioCaptureModule(
+    talk_base::Thread* process_thread)
+    : last_process_time_ms_(0),
+      audio_callback_(NULL),
+      recording_(false),
+      playing_(false),
+      play_is_initialized_(false),
+      rec_is_initialized_(false),
+      current_mic_level_(kMaxVolume),
+      started_(false),
+      next_frame_time_(0),
+      process_thread_(process_thread),
+      frames_received_(0) {
+}
+
+FakeAudioCaptureModule::~FakeAudioCaptureModule() {
+  // Ensure that thread stops calling ProcessFrame().
+  process_thread_->Send(this, MSG_STOP_PROCESS);
+}
+
+talk_base::scoped_refptr<FakeAudioCaptureModule> FakeAudioCaptureModule::Create(
+    talk_base::Thread* process_thread) {
+  if (process_thread == NULL) return NULL;
+
+  talk_base::scoped_refptr<FakeAudioCaptureModule> capture_module(
+      new talk_base::RefCountedObject<FakeAudioCaptureModule>(process_thread));
+  if (!capture_module->Initialize()) {
+    return NULL;
+  }
+  return capture_module;
+}
+
+int FakeAudioCaptureModule::frames_received() const {
+  return frames_received_;
+}
+
+int32_t FakeAudioCaptureModule::Version(char* /*version*/,
+                                        uint32_t& /*remaining_buffer_in_bytes*/,
+                                        uint32_t& /*position*/) const {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::TimeUntilNextProcess() {
+  const uint32 current_time = talk_base::Time();
+  if (current_time < last_process_time_ms_) {
+    // TODO: wraparound could be handled more gracefully.
+    return 0;
+  }
+  const uint32 elapsed_time = current_time - last_process_time_ms_;
+  if (kAdmMaxIdleTimeProcess < elapsed_time) {
+    return 0;
+  }
+  return kAdmMaxIdleTimeProcess - elapsed_time;
+}
+
+int32_t FakeAudioCaptureModule::Process() {
+  last_process_time_ms_ = talk_base::Time();
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::ChangeUniqueId(const int32_t /*id*/) {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::ActiveAudioLayer(
+    AudioLayer* /*audio_layer*/) const {
+  ASSERT(false);
+  return 0;
+}
+
+webrtc::AudioDeviceModule::ErrorCode FakeAudioCaptureModule::LastError() const {
+  ASSERT(false);
+  return webrtc::AudioDeviceModule::kAdmErrNone;
+}
+
+int32_t FakeAudioCaptureModule::RegisterEventObserver(
+    webrtc::AudioDeviceObserver* /*event_callback*/) {
+  // Only used to report warnings and errors. This fake implementation won't
+  // generate any so discard this callback.
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::RegisterAudioCallback(
+    webrtc::AudioTransport* audio_callback) {
+  audio_callback_ = audio_callback;
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::Init() {
+  // Initialize is called by the factory method. Safe to ignore this Init call.
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::Terminate() {
+  // Clean up in the destructor. No action here, just success.
+  return 0;
+}
+
+bool FakeAudioCaptureModule::Initialized() const {
+  ASSERT(false);
+  return 0;
+}
+
+int16_t FakeAudioCaptureModule::PlayoutDevices() {
+  ASSERT(false);
+  return 0;
+}
+
+int16_t FakeAudioCaptureModule::RecordingDevices() {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::PlayoutDeviceName(
+    uint16_t /*index*/,
+    char /*name*/[webrtc::kAdmMaxDeviceNameSize],
+    char /*guid*/[webrtc::kAdmMaxGuidSize]) {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::RecordingDeviceName(
+    uint16_t /*index*/,
+    char /*name*/[webrtc::kAdmMaxDeviceNameSize],
+    char /*guid*/[webrtc::kAdmMaxGuidSize]) {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::SetPlayoutDevice(uint16_t /*index*/) {
+  // No playout device, just playing from file. Return success.
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::SetPlayoutDevice(WindowsDeviceType /*device*/) {
+  if (play_is_initialized_) {
+    return -1;
+  }
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::SetRecordingDevice(uint16_t /*index*/) {
+  // No recording device, just dropping audio. Return success.
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::SetRecordingDevice(
+    WindowsDeviceType /*device*/) {
+  if (rec_is_initialized_) {
+    return -1;
+  }
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::PlayoutIsAvailable(bool* /*available*/) {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::InitPlayout() {
+  play_is_initialized_ = true;
+  return 0;
+}
+
+bool FakeAudioCaptureModule::PlayoutIsInitialized() const {
+  return play_is_initialized_;
+}
+
+int32_t FakeAudioCaptureModule::RecordingIsAvailable(bool* /*available*/) {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::InitRecording() {
+  rec_is_initialized_ = true;
+  return 0;
+}
+
+bool FakeAudioCaptureModule::RecordingIsInitialized() const {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::StartPlayout() {
+  if (!play_is_initialized_) {
+    return -1;
+  }
+  playing_ = true;
+  UpdateProcessing();
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::StopPlayout() {
+  playing_ = false;
+  UpdateProcessing();
+  return 0;
+}
+
+bool FakeAudioCaptureModule::Playing() const {
+  return playing_;
+}
+
+int32_t FakeAudioCaptureModule::StartRecording() {
+  if (!rec_is_initialized_) {
+    return -1;
+  }
+  recording_ = true;
+  UpdateProcessing();
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::StopRecording() {
+  recording_ = false;
+  UpdateProcessing();
+  return 0;
+}
+
+bool FakeAudioCaptureModule::Recording() const {
+  return recording_;
+}
+
+int32_t FakeAudioCaptureModule::SetAGC(bool /*enable*/) {
+  // No AGC but not needed since audio is pregenerated. Return success.
+  return 0;
+}
+
+bool FakeAudioCaptureModule::AGC() const {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::SetWaveOutVolume(uint16_t /*volume_left*/,
+                                                 uint16_t /*volume_right*/) {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::WaveOutVolume(
+    uint16_t* /*volume_left*/,
+    uint16_t* /*volume_right*/) const {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::SpeakerIsAvailable(bool* available) {
+  // No speaker, just dropping audio. Return success.
+  *available = true;
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::InitSpeaker() {
+  // No speaker, just playing from file. Return success.
+  return 0;
+}
+
+bool FakeAudioCaptureModule::SpeakerIsInitialized() const {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::MicrophoneIsAvailable(bool* available) {
+  // No microphone, just playing from file. Return success.
+  *available = true;
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::InitMicrophone() {
+  // No microphone, just playing from file. Return success.
+  return 0;
+}
+
+bool FakeAudioCaptureModule::MicrophoneIsInitialized() const {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::SpeakerVolumeIsAvailable(bool* /*available*/) {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::SetSpeakerVolume(uint32_t /*volume*/) {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::SpeakerVolume(uint32_t* /*volume*/) const {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::MaxSpeakerVolume(
+    uint32_t* /*max_volume*/) const {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::MinSpeakerVolume(
+    uint32_t* /*min_volume*/) const {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::SpeakerVolumeStepSize(
+    uint16_t* /*step_size*/) const {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::MicrophoneVolumeIsAvailable(
+    bool* /*available*/) {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::SetMicrophoneVolume(uint32_t /*volume*/) {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::MicrophoneVolume(uint32_t* volume) const {
+  *volume = current_mic_level_;
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::MaxMicrophoneVolume(
+    uint32_t* max_volume) const {
+  *max_volume = kMaxVolume;
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::MinMicrophoneVolume(
+    uint32_t* /*min_volume*/) const {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::MicrophoneVolumeStepSize(
+    uint16_t* /*step_size*/) const {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::SpeakerMuteIsAvailable(bool* /*available*/) {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::SetSpeakerMute(bool /*enable*/) {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::SpeakerMute(bool* /*enabled*/) const {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::MicrophoneMuteIsAvailable(bool* /*available*/) {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::SetMicrophoneMute(bool /*enable*/) {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::MicrophoneMute(bool* /*enabled*/) const {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::MicrophoneBoostIsAvailable(
+    bool* /*available*/) {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::SetMicrophoneBoost(bool /*enable*/) {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::MicrophoneBoost(bool* /*enabled*/) const {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::StereoPlayoutIsAvailable(
+    bool* available) const {
+  // No recording device, just dropping audio. Stereo can be dropped just
+  // as easily as mono.
+  *available = true;
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::SetStereoPlayout(bool /*enable*/) {
+  // No recording device, just dropping audio. Stereo can be dropped just
+  // as easily as mono.
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::StereoPlayout(bool* /*enabled*/) const {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::StereoRecordingIsAvailable(
+    bool* available) const {
+  // Keep thing simple. No stereo recording.
+  *available = false;
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::SetStereoRecording(bool enable) {
+  if (!enable) {
+    return 0;
+  }
+  return -1;
+}
+
+int32_t FakeAudioCaptureModule::StereoRecording(bool* /*enabled*/) const {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::SetRecordingChannel(
+    const ChannelType channel) {
+  if (channel != AudioDeviceModule::kChannelBoth) {
+    // There is no right or left in mono. I.e. kChannelBoth should be used for
+    // mono.
+    ASSERT(false);
+    return -1;
+  }
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::RecordingChannel(ChannelType* channel) const {
+  // Stereo recording not supported. However, WebRTC ADM returns kChannelBoth
+  // in that case. Do the same here.
+  *channel = AudioDeviceModule::kChannelBoth;
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::SetPlayoutBuffer(const BufferType /*type*/,
+                                                 uint16_t /*size_ms*/) {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::PlayoutBuffer(BufferType* /*type*/,
+                                              uint16_t* /*size_ms*/) const {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::PlayoutDelay(uint16_t* delay_ms) const {
+  // No delay since audio frames are dropped.
+  *delay_ms = 0;
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::RecordingDelay(uint16_t* /*delay_ms*/) const {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::CPULoad(uint16_t* /*load*/) const {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::StartRawOutputFileRecording(
+    const char /*pcm_file_name_utf8*/[webrtc::kAdmMaxFileNameSize]) {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::StopRawOutputFileRecording() {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::StartRawInputFileRecording(
+    const char /*pcm_file_name_utf8*/[webrtc::kAdmMaxFileNameSize]) {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::StopRawInputFileRecording() {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::SetRecordingSampleRate(
+    const uint32_t /*samples_per_sec*/) {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::RecordingSampleRate(
+    uint32_t* /*samples_per_sec*/) const {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::SetPlayoutSampleRate(
+    const uint32_t /*samples_per_sec*/) {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::PlayoutSampleRate(
+    uint32_t* /*samples_per_sec*/) const {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::ResetAudioDevice() {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::SetLoudspeakerStatus(bool /*enable*/) {
+  ASSERT(false);
+  return 0;
+}
+
+int32_t FakeAudioCaptureModule::GetLoudspeakerStatus(bool* /*enabled*/) const {
+  ASSERT(false);
+  return 0;
+}
+
+void FakeAudioCaptureModule::OnMessage(talk_base::Message* msg) {
+  switch (msg->message_id) {
+    case MSG_RUN_PROCESS:
+      ProcessFrameP();
+      break;
+    case MSG_STOP_PROCESS:
+      StopProcessP();
+      break;
+    default:
+      // All existing messages should be caught. Getting here should never
+      // happen.
+      ASSERT(false);
+  }
+}
+
+bool FakeAudioCaptureModule::Initialize() {
+  // Set the send buffer samples high enough that it would not occur on the
+  // remote side unless a packet containing a sample of that magnitude has been
+  // sent to it. Note that the audio processing pipeline will likely distort the
+  // original signal.
+  SetSendBuffer(kHighSampleValue);
+  last_process_time_ms_ = talk_base::Time();
+  return true;
+}
+
+void FakeAudioCaptureModule::SetSendBuffer(int value) {
+  Sample* buffer_ptr = reinterpret_cast<Sample*>(send_buffer_);
+  const int buffer_size_in_samples = sizeof(send_buffer_) /
+      kNumberBytesPerSample;
+  for (int i = 0; i < buffer_size_in_samples; ++i) {
+    buffer_ptr[i] = value;
+  }
+}
+
+void FakeAudioCaptureModule::ResetRecBuffer() {
+  memset(rec_buffer_, 0, sizeof(rec_buffer_));
+}
+
+bool FakeAudioCaptureModule::CheckRecBuffer(int value) {
+  const Sample* buffer_ptr = reinterpret_cast<const Sample*>(rec_buffer_);
+  const int buffer_size_in_samples = sizeof(rec_buffer_) /
+      kNumberBytesPerSample;
+  for (int i = 0; i < buffer_size_in_samples; ++i) {
+    if (buffer_ptr[i] >= value) return true;
+  }
+  return false;
+}
+
+void FakeAudioCaptureModule::UpdateProcessing() {
+  const bool process = recording_ || playing_;
+  if (process) {
+    if (started_) {
+      // Already started.
+      return;
+    }
+    process_thread_->Post(this, MSG_RUN_PROCESS);
+  } else {
+    process_thread_->Send(this, MSG_STOP_PROCESS);
+  }
+}
+
+void FakeAudioCaptureModule::ProcessFrameP() {
+  ASSERT(talk_base::Thread::Current() == process_thread_);
+  if (!started_) {
+    next_frame_time_ = talk_base::Time();
+    started_ = true;
+  }
+  // Receive and send frames every kTimePerFrameMs.
+  if (audio_callback_ != NULL) {
+    if (playing_) {
+      ReceiveFrameP();
+    }
+    if (recording_) {
+      SendFrameP();
+    }
+  }
+
+  next_frame_time_ += kTimePerFrameMs;
+  const uint32 current_time = talk_base::Time();
+  const uint32 wait_time = (next_frame_time_ > current_time) ?
+      next_frame_time_ - current_time : 0;
+  process_thread_->PostDelayed(wait_time, this, MSG_RUN_PROCESS);
+}
+
+void FakeAudioCaptureModule::ReceiveFrameP() {
+  ASSERT(talk_base::Thread::Current() == process_thread_);
+  ResetRecBuffer();
+  uint32_t nSamplesOut = 0;
+  if (audio_callback_->NeedMorePlayData(kNumberSamples, kNumberBytesPerSample,
+                                       kNumberOfChannels, kSamplesPerSecond,
+                                       rec_buffer_, nSamplesOut) != 0) {
+    ASSERT(false);
+  }
+  ASSERT(nSamplesOut == kNumberSamples);
+  // The SetBuffer() function ensures that after decoding, the audio buffer
+  // should contain samples of similar magnitude (there is likely to be some
+  // distortion due to the audio pipeline). If one sample is detected to
+  // have the same or greater magnitude somewhere in the frame, an actual frame
+  // has been received from the remote side (i.e. faked frames are not being
+  // pulled).
+  if (CheckRecBuffer(kHighSampleValue)) ++frames_received_;
+}
+
+void FakeAudioCaptureModule::SendFrameP() {
+  ASSERT(talk_base::Thread::Current() == process_thread_);
+  bool key_pressed = false;
+  if (audio_callback_->RecordedDataIsAvailable(send_buffer_, kNumberSamples,
+                                              kNumberBytesPerSample,
+                                              kNumberOfChannels,
+                                              kSamplesPerSecond, kTotalDelayMs,
+                                              kClockDriftMs, current_mic_level_,
+                                              key_pressed,
+                                              current_mic_level_) != 0) {
+    ASSERT(false);
+  }
+}
+
+void FakeAudioCaptureModule::StopProcessP() {
+  ASSERT(talk_base::Thread::Current() == process_thread_);
+  started_ = false;
+  process_thread_->Clear(this);
+}
diff --git a/talk/app/webrtc/test/fakeaudiocapturemodule.h b/talk/app/webrtc/test/fakeaudiocapturemodule.h
new file mode 100644
index 0000000..c32fa1f
--- /dev/null
+++ b/talk/app/webrtc/test/fakeaudiocapturemodule.h
@@ -0,0 +1,280 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 class implements an AudioCaptureModule that can be used to detect if
+// audio is being received properly if it is fed by another AudioCaptureModule
+// in some arbitrary audio pipeline where they are connected. It does not play
+// out or record any audio so it does not need access to any hardware and can
+// therefore be used in the gtest testing framework.
+
+// Note P postfix of a function indicates that it should only be called by the
+// processing thread.
+
+#ifndef TALK_APP_WEBRTC_TEST_FAKEAUDIOCAPTUREMODULE_H_
+#define TALK_APP_WEBRTC_TEST_FAKEAUDIOCAPTUREMODULE_H_
+
+#include "talk/base/basictypes.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/scoped_ref_ptr.h"
+#include "webrtc/common_types.h"
+#include "webrtc/modules/audio_device/include/audio_device.h"
+
+namespace talk_base {
+
+class Thread;
+
+}  // namespace talk_base
+
+class FakeAudioCaptureModule
+    : public webrtc::AudioDeviceModule,
+      public talk_base::MessageHandler {
+ public:
+  typedef uint16 Sample;
+
+  // The value for the following constants have been derived by running VoE
+  // using a real ADM. The constants correspond to 10ms of mono audio at 44kHz.
+  enum{kNumberSamples = 440};
+  enum{kNumberBytesPerSample = sizeof(Sample)};
+
+  // Creates a FakeAudioCaptureModule or returns NULL on failure.
+  // |process_thread| is used to push and pull audio frames to and from the
+  // returned instance. Note: ownership of |process_thread| is not handed over.
+  static talk_base::scoped_refptr<FakeAudioCaptureModule> Create(
+      talk_base::Thread* process_thread);
+
+  // Returns the number of frames that have been successfully pulled by the
+  // instance. Note that correctly detecting success can only be done if the
+  // pulled frame was generated/pushed from a FakeAudioCaptureModule.
+  int frames_received() const;
+
+  // Following functions are inherited from webrtc::AudioDeviceModule.
+  // Only functions called by PeerConnection are implemented, the rest do
+  // nothing and return success. If a function is not expected to be called by
+  // PeerConnection an assertion is triggered if it is in fact called.
+  virtual int32_t Version(char* version,
+                          uint32_t& remaining_buffer_in_bytes,
+                          uint32_t& position) const;
+  virtual int32_t TimeUntilNextProcess();
+  virtual int32_t Process();
+  virtual int32_t ChangeUniqueId(const int32_t id);
+
+  virtual int32_t ActiveAudioLayer(AudioLayer* audio_layer) const;
+
+  virtual ErrorCode LastError() const;
+  virtual int32_t RegisterEventObserver(
+      webrtc::AudioDeviceObserver* event_callback);
+
+  virtual int32_t RegisterAudioCallback(webrtc::AudioTransport* audio_callback);
+
+  virtual int32_t Init();
+  virtual int32_t Terminate();
+  virtual bool Initialized() const;
+
+  virtual int16_t PlayoutDevices();
+  virtual int16_t RecordingDevices();
+  virtual int32_t PlayoutDeviceName(uint16_t index,
+                                    char name[webrtc::kAdmMaxDeviceNameSize],
+                                    char guid[webrtc::kAdmMaxGuidSize]);
+  virtual int32_t RecordingDeviceName(uint16_t index,
+                                      char name[webrtc::kAdmMaxDeviceNameSize],
+                                      char guid[webrtc::kAdmMaxGuidSize]);
+
+  virtual int32_t SetPlayoutDevice(uint16_t index);
+  virtual int32_t SetPlayoutDevice(WindowsDeviceType device);
+  virtual int32_t SetRecordingDevice(uint16_t index);
+  virtual int32_t SetRecordingDevice(WindowsDeviceType device);
+
+  virtual int32_t PlayoutIsAvailable(bool* available);
+  virtual int32_t InitPlayout();
+  virtual bool PlayoutIsInitialized() const;
+  virtual int32_t RecordingIsAvailable(bool* available);
+  virtual int32_t InitRecording();
+  virtual bool RecordingIsInitialized() const;
+
+  virtual int32_t StartPlayout();
+  virtual int32_t StopPlayout();
+  virtual bool Playing() const;
+  virtual int32_t StartRecording();
+  virtual int32_t StopRecording();
+  virtual bool Recording() const;
+
+  virtual int32_t SetAGC(bool enable);
+  virtual bool AGC() const;
+
+  virtual int32_t SetWaveOutVolume(uint16_t volume_left,
+                                   uint16_t volume_right);
+  virtual int32_t WaveOutVolume(uint16_t* volume_left,
+                                uint16_t* volume_right) const;
+
+  virtual int32_t SpeakerIsAvailable(bool* available);
+  virtual int32_t InitSpeaker();
+  virtual bool SpeakerIsInitialized() const;
+  virtual int32_t MicrophoneIsAvailable(bool* available);
+  virtual int32_t InitMicrophone();
+  virtual bool MicrophoneIsInitialized() const;
+
+  virtual int32_t SpeakerVolumeIsAvailable(bool* available);
+  virtual int32_t SetSpeakerVolume(uint32_t volume);
+  virtual int32_t SpeakerVolume(uint32_t* volume) const;
+  virtual int32_t MaxSpeakerVolume(uint32_t* max_volume) const;
+  virtual int32_t MinSpeakerVolume(uint32_t* min_volume) const;
+  virtual int32_t SpeakerVolumeStepSize(uint16_t* step_size) const;
+
+  virtual int32_t MicrophoneVolumeIsAvailable(bool* available);
+  virtual int32_t SetMicrophoneVolume(uint32_t volume);
+  virtual int32_t MicrophoneVolume(uint32_t* volume) const;
+  virtual int32_t MaxMicrophoneVolume(uint32_t* max_volume) const;
+
+  virtual int32_t MinMicrophoneVolume(uint32_t* min_volume) const;
+  virtual int32_t MicrophoneVolumeStepSize(uint16_t* step_size) const;
+
+  virtual int32_t SpeakerMuteIsAvailable(bool* available);
+  virtual int32_t SetSpeakerMute(bool enable);
+  virtual int32_t SpeakerMute(bool* enabled) const;
+
+  virtual int32_t MicrophoneMuteIsAvailable(bool* available);
+  virtual int32_t SetMicrophoneMute(bool enable);
+  virtual int32_t MicrophoneMute(bool* enabled) const;
+
+  virtual int32_t MicrophoneBoostIsAvailable(bool* available);
+  virtual int32_t SetMicrophoneBoost(bool enable);
+  virtual int32_t MicrophoneBoost(bool* enabled) const;
+
+  virtual int32_t StereoPlayoutIsAvailable(bool* available) const;
+  virtual int32_t SetStereoPlayout(bool enable);
+  virtual int32_t StereoPlayout(bool* enabled) const;
+  virtual int32_t StereoRecordingIsAvailable(bool* available) const;
+  virtual int32_t SetStereoRecording(bool enable);
+  virtual int32_t StereoRecording(bool* enabled) const;
+  virtual int32_t SetRecordingChannel(const ChannelType channel);
+  virtual int32_t RecordingChannel(ChannelType* channel) const;
+
+  virtual int32_t SetPlayoutBuffer(const BufferType type,
+                                   uint16_t size_ms = 0);
+  virtual int32_t PlayoutBuffer(BufferType* type,
+                                uint16_t* size_ms) const;
+  virtual int32_t PlayoutDelay(uint16_t* delay_ms) const;
+  virtual int32_t RecordingDelay(uint16_t* delay_ms) const;
+
+  virtual int32_t CPULoad(uint16_t* load) const;
+
+  virtual int32_t StartRawOutputFileRecording(
+      const char pcm_file_name_utf8[webrtc::kAdmMaxFileNameSize]);
+  virtual int32_t StopRawOutputFileRecording();
+  virtual int32_t StartRawInputFileRecording(
+      const char pcm_file_name_utf8[webrtc::kAdmMaxFileNameSize]);
+  virtual int32_t StopRawInputFileRecording();
+
+  virtual int32_t SetRecordingSampleRate(const uint32_t samples_per_sec);
+  virtual int32_t RecordingSampleRate(uint32_t* samples_per_sec) const;
+  virtual int32_t SetPlayoutSampleRate(const uint32_t samples_per_sec);
+  virtual int32_t PlayoutSampleRate(uint32_t* samples_per_sec) const;
+
+  virtual int32_t ResetAudioDevice();
+  virtual int32_t SetLoudspeakerStatus(bool enable);
+  virtual int32_t GetLoudspeakerStatus(bool* enabled) const;
+  // End of functions inherited from webrtc::AudioDeviceModule.
+
+  // The following function is inherited from talk_base::MessageHandler.
+  virtual void OnMessage(talk_base::Message* msg);
+
+ protected:
+  // The constructor is protected because the class needs to be created as a
+  // reference counted object (for memory managment reasons). It could be
+  // exposed in which case the burden of proper instantiation would be put on
+  // the creator of a FakeAudioCaptureModule instance. To create an instance of
+  // this class use the Create(..) API.
+  explicit FakeAudioCaptureModule(talk_base::Thread* process_thread);
+  // The destructor is protected because it is reference counted and should not
+  // be deleted directly.
+  virtual ~FakeAudioCaptureModule();
+
+ private:
+  // Initializes the state of the FakeAudioCaptureModule. This API is called on
+  // creation by the Create() API.
+  bool Initialize();
+  // SetBuffer() sets all samples in send_buffer_ to |value|.
+  void SetSendBuffer(int value);
+  // Resets rec_buffer_. I.e., sets all rec_buffer_ samples to 0.
+  void ResetRecBuffer();
+  // Returns true if rec_buffer_ contains one or more sample greater than or
+  // equal to |value|.
+  bool CheckRecBuffer(int value);
+
+  // Starts or stops the pushing and pulling of audio frames depending on if
+  // recording or playback has been enabled/started.
+  void UpdateProcessing();
+
+  // Periodcally called function that ensures that frames are pulled and pushed
+  // periodically if enabled/started.
+  void ProcessFrameP();
+  // Pulls frames from the registered webrtc::AudioTransport.
+  void ReceiveFrameP();
+  // Pushes frames to the registered webrtc::AudioTransport.
+  void SendFrameP();
+  // Stops the periodic calling of ProcessFrame() in a thread safe way.
+  void StopProcessP();
+
+  // The time in milliseconds when Process() was last called or 0 if no call
+  // has been made.
+  uint32 last_process_time_ms_;
+
+  // Callback for playout and recording.
+  webrtc::AudioTransport* audio_callback_;
+
+  bool recording_; // True when audio is being pushed from the instance.
+  bool playing_; // True when audio is being pulled by the instance.
+
+  bool play_is_initialized_; // True when the instance is ready to pull audio.
+  bool rec_is_initialized_; // True when the instance is ready to push audio.
+
+  // Input to and output from RecordedDataIsAvailable(..) makes it possible to
+  // modify the current mic level. The implementation does not care about the
+  // mic level so it just feeds back what it receives.
+  uint32_t current_mic_level_;
+
+  // next_frame_time_ is updated in a non-drifting manner to indicate the next
+  // wall clock time the next frame should be generated and received. started_
+  // ensures that next_frame_time_ can be initialized properly on first call.
+  bool started_;
+  uint32 next_frame_time_;
+
+  // User provided thread context.
+  talk_base::Thread* process_thread_;
+
+  // Buffer for storing samples received from the webrtc::AudioTransport.
+  char rec_buffer_[kNumberSamples * kNumberBytesPerSample];
+  // Buffer for samples to send to the webrtc::AudioTransport.
+  char send_buffer_[kNumberSamples * kNumberBytesPerSample];
+
+  // Counter of frames received that have samples of high enough amplitude to
+  // indicate that the frames are not faked somewhere in the audio pipeline
+  // (e.g. by a jitter buffer).
+  int frames_received_;
+};
+
+#endif  // TALK_APP_WEBRTC_TEST_FAKEAUDIOCAPTUREMODULE_H_
diff --git a/talk/app/webrtc/test/fakeaudiocapturemodule_unittest.cc b/talk/app/webrtc/test/fakeaudiocapturemodule_unittest.cc
new file mode 100644
index 0000000..5738955
--- /dev/null
+++ b/talk/app/webrtc/test/fakeaudiocapturemodule_unittest.cc
@@ -0,0 +1,212 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/test/fakeaudiocapturemodule.h"
+
+#include <algorithm>
+
+#include "talk/base/gunit.h"
+#include "talk/base/scoped_ref_ptr.h"
+#include "talk/base/thread.h"
+
+using std::min;
+
+class FakeAdmTest : public testing::Test,
+                    public webrtc::AudioTransport {
+ protected:
+  static const int kMsInSecond = 1000;
+
+  FakeAdmTest()
+      : push_iterations_(0),
+        pull_iterations_(0),
+        rec_buffer_bytes_(0) {
+    memset(rec_buffer_, 0, sizeof(rec_buffer_));
+  }
+
+  virtual void SetUp() {
+    fake_audio_capture_module_ = FakeAudioCaptureModule::Create(
+        talk_base::Thread::Current());
+    EXPECT_TRUE(fake_audio_capture_module_.get() != NULL);
+  }
+
+  // Callbacks inherited from webrtc::AudioTransport.
+  // ADM is pushing data.
+  virtual int32_t RecordedDataIsAvailable(const void* audioSamples,
+                                          const uint32_t nSamples,
+                                          const uint8_t nBytesPerSample,
+                                          const uint8_t nChannels,
+                                          const uint32_t samplesPerSec,
+                                          const uint32_t totalDelayMS,
+                                          const int32_t clockDrift,
+                                          const uint32_t currentMicLevel,
+                                          const bool keyPressed,
+                                          uint32_t& newMicLevel) {
+    rec_buffer_bytes_ = nSamples * nBytesPerSample;
+    if ((rec_buffer_bytes_ <= 0) ||
+        (rec_buffer_bytes_ > FakeAudioCaptureModule::kNumberSamples *
+         FakeAudioCaptureModule::kNumberBytesPerSample)) {
+      ADD_FAILURE();
+      return -1;
+    }
+    memcpy(rec_buffer_, audioSamples, rec_buffer_bytes_);
+    ++push_iterations_;
+    newMicLevel = currentMicLevel;
+    return 0;
+  }
+
+  // ADM is pulling data.
+  virtual int32_t NeedMorePlayData(const uint32_t nSamples,
+                                   const uint8_t nBytesPerSample,
+                                   const uint8_t nChannels,
+                                   const uint32_t samplesPerSec,
+                                   void* audioSamples,
+                                   uint32_t& nSamplesOut) {
+    ++pull_iterations_;
+    const uint32_t audio_buffer_size = nSamples * nBytesPerSample;
+    const uint32_t bytes_out = RecordedDataReceived() ?
+        CopyFromRecBuffer(audioSamples, audio_buffer_size):
+        GenerateZeroBuffer(audioSamples, audio_buffer_size);
+    nSamplesOut = bytes_out / nBytesPerSample;
+    return 0;
+  }
+
+  int push_iterations() const { return push_iterations_; }
+  int pull_iterations() const { return pull_iterations_; }
+
+  talk_base::scoped_refptr<FakeAudioCaptureModule> fake_audio_capture_module_;
+
+ private:
+  bool RecordedDataReceived() const {
+    return rec_buffer_bytes_ != 0;
+  }
+  int32_t GenerateZeroBuffer(void* audio_buffer, uint32_t audio_buffer_size) {
+    memset(audio_buffer, 0, audio_buffer_size);
+    return audio_buffer_size;
+  }
+  int32_t CopyFromRecBuffer(void* audio_buffer, uint32_t audio_buffer_size) {
+    EXPECT_EQ(audio_buffer_size, rec_buffer_bytes_);
+    const uint32_t min_buffer_size = min(audio_buffer_size, rec_buffer_bytes_);
+    memcpy(audio_buffer, rec_buffer_, min_buffer_size);
+    return min_buffer_size;
+  }
+
+  int push_iterations_;
+  int pull_iterations_;
+
+  char rec_buffer_[FakeAudioCaptureModule::kNumberSamples *
+                   FakeAudioCaptureModule::kNumberBytesPerSample];
+  uint32_t rec_buffer_bytes_;
+};
+
+TEST_F(FakeAdmTest, TestProccess) {
+  // Next process call must be some time in the future (or now).
+  EXPECT_LE(0, fake_audio_capture_module_->TimeUntilNextProcess());
+  // Process call updates TimeUntilNextProcess() but there are no guarantees on
+  // timing so just check that Process can ba called successfully.
+  EXPECT_LE(0, fake_audio_capture_module_->Process());
+}
+
+TEST_F(FakeAdmTest, PlayoutTest) {
+  EXPECT_EQ(0, fake_audio_capture_module_->RegisterAudioCallback(this));
+
+  bool speaker_available = false;
+  EXPECT_EQ(0, fake_audio_capture_module_->SpeakerIsAvailable(
+      &speaker_available));
+  EXPECT_TRUE(speaker_available);
+
+  bool stereo_available = false;
+  EXPECT_EQ(0,
+            fake_audio_capture_module_->StereoPlayoutIsAvailable(
+                &stereo_available));
+  EXPECT_TRUE(stereo_available);
+
+  EXPECT_NE(0, fake_audio_capture_module_->StartPlayout());
+  EXPECT_FALSE(fake_audio_capture_module_->PlayoutIsInitialized());
+  EXPECT_FALSE(fake_audio_capture_module_->Playing());
+  EXPECT_EQ(0, fake_audio_capture_module_->StopPlayout());
+
+  EXPECT_EQ(0, fake_audio_capture_module_->InitPlayout());
+  EXPECT_TRUE(fake_audio_capture_module_->PlayoutIsInitialized());
+  EXPECT_FALSE(fake_audio_capture_module_->Playing());
+
+  EXPECT_EQ(0, fake_audio_capture_module_->StartPlayout());
+  EXPECT_TRUE(fake_audio_capture_module_->Playing());
+
+  uint16_t delay_ms = 10;
+  EXPECT_EQ(0, fake_audio_capture_module_->PlayoutDelay(&delay_ms));
+  EXPECT_EQ(0, delay_ms);
+
+  EXPECT_TRUE_WAIT(pull_iterations() > 0, kMsInSecond);
+  EXPECT_GE(0, push_iterations());
+
+  EXPECT_EQ(0, fake_audio_capture_module_->StopPlayout());
+  EXPECT_FALSE(fake_audio_capture_module_->Playing());
+}
+
+TEST_F(FakeAdmTest, RecordTest) {
+  EXPECT_EQ(0, fake_audio_capture_module_->RegisterAudioCallback(this));
+
+  bool microphone_available = false;
+  EXPECT_EQ(0, fake_audio_capture_module_->MicrophoneIsAvailable(
+      &microphone_available));
+  EXPECT_TRUE(microphone_available);
+
+  bool stereo_available = false;
+  EXPECT_EQ(0, fake_audio_capture_module_->StereoRecordingIsAvailable(
+      &stereo_available));
+  EXPECT_FALSE(stereo_available);
+
+  EXPECT_NE(0, fake_audio_capture_module_->StartRecording());
+  EXPECT_FALSE(fake_audio_capture_module_->Recording());
+  EXPECT_EQ(0, fake_audio_capture_module_->StopRecording());
+
+  EXPECT_EQ(0, fake_audio_capture_module_->InitRecording());
+  EXPECT_EQ(0, fake_audio_capture_module_->StartRecording());
+  EXPECT_TRUE(fake_audio_capture_module_->Recording());
+
+  EXPECT_TRUE_WAIT(push_iterations() > 0, kMsInSecond);
+  EXPECT_GE(0, pull_iterations());
+
+  EXPECT_EQ(0, fake_audio_capture_module_->StopRecording());
+  EXPECT_FALSE(fake_audio_capture_module_->Recording());
+}
+
+TEST_F(FakeAdmTest, DuplexTest) {
+  EXPECT_EQ(0, fake_audio_capture_module_->RegisterAudioCallback(this));
+
+  EXPECT_EQ(0, fake_audio_capture_module_->InitPlayout());
+  EXPECT_EQ(0, fake_audio_capture_module_->StartPlayout());
+
+  EXPECT_EQ(0, fake_audio_capture_module_->InitRecording());
+  EXPECT_EQ(0, fake_audio_capture_module_->StartRecording());
+
+  EXPECT_TRUE_WAIT(push_iterations() > 0, kMsInSecond);
+  EXPECT_TRUE_WAIT(pull_iterations() > 0, kMsInSecond);
+
+  EXPECT_EQ(0, fake_audio_capture_module_->StopPlayout());
+  EXPECT_EQ(0, fake_audio_capture_module_->StopRecording());
+}
diff --git a/talk/app/webrtc/test/fakeconstraints.h b/talk/app/webrtc/test/fakeconstraints.h
new file mode 100644
index 0000000..0299afa
--- /dev/null
+++ b/talk/app/webrtc/test/fakeconstraints.h
@@ -0,0 +1,118 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_TEST_FAKECONSTRAINTS_H_
+#define TALK_APP_WEBRTC_TEST_FAKECONSTRAINTS_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/app/webrtc/mediaconstraintsinterface.h"
+#include "talk/base/stringencode.h"
+
+namespace webrtc {
+
+class FakeConstraints : public webrtc::MediaConstraintsInterface {
+ public:
+  FakeConstraints() { }
+  virtual ~FakeConstraints() { }
+
+  virtual const Constraints& GetMandatory() const {
+    return mandatory_;
+  }
+
+  virtual const Constraints& GetOptional() const {
+    return optional_;
+  }
+
+  template <class T>
+  void AddMandatory(const std::string& key, const T& value) {
+    mandatory_.push_back(Constraint(key, talk_base::ToString<T>(value)));
+  }
+
+  template <class T>
+  void AddOptional(const std::string& key, const T& value) {
+    optional_.push_back(Constraint(key, talk_base::ToString<T>(value)));
+  }
+
+  void SetMandatoryMinAspectRatio(double ratio) {
+    AddMandatory(MediaConstraintsInterface::kMinAspectRatio, ratio);
+  }
+
+  void SetMandatoryMinWidth(int width) {
+    AddMandatory(MediaConstraintsInterface::kMinWidth, width);
+  }
+
+  void SetMandatoryMinHeight(int height) {
+    AddMandatory(MediaConstraintsInterface::kMinHeight, height);
+  }
+
+  void SetOptionalMaxWidth(int width) {
+    AddOptional(MediaConstraintsInterface::kMaxWidth, width);
+  }
+
+  void SetMandatoryMaxFrameRate(int frame_rate) {
+    AddMandatory(MediaConstraintsInterface::kMaxFrameRate, frame_rate);
+  }
+
+  void SetMandatoryReceiveAudio(bool enable) {
+    AddMandatory(MediaConstraintsInterface::kOfferToReceiveAudio, enable);
+  }
+
+  void SetMandatoryReceiveVideo(bool enable) {
+    AddMandatory(MediaConstraintsInterface::kOfferToReceiveVideo, enable);
+  }
+
+  void SetMandatoryUseRtpMux(bool enable) {
+    AddMandatory(MediaConstraintsInterface::kUseRtpMux, enable);
+  }
+
+  void SetMandatoryIceRestart(bool enable) {
+    AddMandatory(MediaConstraintsInterface::kIceRestart, enable);
+  }
+
+  void SetAllowRtpDataChannels() {
+    AddMandatory(MediaConstraintsInterface::kEnableRtpDataChannels, true);
+  }
+
+  void SetOptionalVAD(bool enable) {
+    AddOptional(MediaConstraintsInterface::kVoiceActivityDetection, enable);
+  }
+
+  void SetAllowDtlsSctpDataChannels() {
+    AddMandatory(MediaConstraintsInterface::kEnableSctpDataChannels, true);
+    AddMandatory(MediaConstraintsInterface::kEnableDtlsSrtp, true);
+  }
+
+ private:
+  Constraints mandatory_;
+  Constraints optional_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_TEST_FAKECONSTRAINTS_H_
diff --git a/talk/app/webrtc/test/fakeperiodicvideocapturer.h b/talk/app/webrtc/test/fakeperiodicvideocapturer.h
new file mode 100644
index 0000000..7f70ae2
--- /dev/null
+++ b/talk/app/webrtc/test/fakeperiodicvideocapturer.h
@@ -0,0 +1,89 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// FakePeriodicVideoCapturer implements a fake cricket::VideoCapturer that
+// creates video frames periodically after it has been started.
+
+#ifndef TALK_APP_WEBRTC_TEST_FAKEPERIODICVIDEOCAPTURER_H_
+#define TALK_APP_WEBRTC_TEST_FAKEPERIODICVIDEOCAPTURER_H_
+
+#include "talk/base/thread.h"
+#include "talk/media/base/fakevideocapturer.h"
+
+namespace webrtc {
+
+class FakePeriodicVideoCapturer : public cricket::FakeVideoCapturer {
+ public:
+  FakePeriodicVideoCapturer() {
+    std::vector<cricket::VideoFormat> formats;
+    formats.push_back(cricket::VideoFormat(1280, 720,
+            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(640, 360,
+            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);
+  };
+
+  virtual cricket::CaptureState Start(const cricket::VideoFormat& format) {
+    cricket::CaptureState state = FakeVideoCapturer::Start(format);
+    if (state != cricket::CS_FAILED) {
+      talk_base::Thread::Current()->Post(this, MSG_CREATEFRAME);
+    }
+    return state;
+  }
+  virtual void Stop() {
+    talk_base::Thread::Current()->Clear(this);
+  }
+  // Inherited from MesageHandler.
+  virtual void OnMessage(talk_base::Message* msg) {
+    if (msg->message_id == MSG_CREATEFRAME) {
+      if (IsRunning()) {
+        CaptureFrame();
+        talk_base::Thread::Current()->PostDelayed(static_cast<int>(
+            GetCaptureFormat()->interval / talk_base::kNumNanosecsPerMillisec),
+            this, MSG_CREATEFRAME);
+        }
+    } else {
+      FakeVideoCapturer::OnMessage(msg);
+    }
+  }
+
+ private:
+  enum {
+    // Offset  0xFF to make sure this don't collide with base class messages.
+    MSG_CREATEFRAME = 0xFF
+  };
+};
+
+}  // namespace webrtc
+
+#endif  //  TALK_APP_WEBRTC_TEST_FAKEPERIODICVIDEOCAPTURER_H_
diff --git a/talk/app/webrtc/test/fakevideotrackrenderer.h b/talk/app/webrtc/test/fakevideotrackrenderer.h
new file mode 100644
index 0000000..0030a0c
--- /dev/null
+++ b/talk/app/webrtc/test/fakevideotrackrenderer.h
@@ -0,0 +1,70 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_TEST_FAKEVIDEOTRACKRENDERER_H_
+#define TALK_APP_WEBRTC_TEST_FAKEVIDEOTRACKRENDERER_H_
+
+#include "talk/app/webrtc/mediastreaminterface.h"
+#include "talk/media/base/fakevideorenderer.h"
+
+namespace webrtc {
+
+class FakeVideoTrackRenderer : public VideoRendererInterface {
+ public:
+  explicit FakeVideoTrackRenderer(VideoTrackInterface* video_track)
+      : video_track_(video_track) {
+    video_track_->AddRenderer(this);
+  }
+  ~FakeVideoTrackRenderer() {
+    video_track_->RemoveRenderer(this);
+  }
+
+  // Implements VideoRendererInterface
+  virtual void SetSize(int width, int height) {
+    fake_renderer_.SetSize(width, height, 0);
+  }
+
+  virtual void RenderFrame(const cricket::VideoFrame* frame) {
+    fake_renderer_.RenderFrame(frame);
+  }
+
+  int errors() const { return fake_renderer_.errors(); }
+  int width() const { return fake_renderer_.width(); }
+  int height() const { return fake_renderer_.height(); }
+  int num_set_sizes() const { return fake_renderer_.num_set_sizes(); }
+  int num_rendered_frames() const {
+    return fake_renderer_.num_rendered_frames();
+  }
+
+ private:
+  cricket::FakeVideoRenderer fake_renderer_;
+  talk_base::scoped_refptr<VideoTrackInterface> video_track_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_TEST_FAKEVIDEOTRACKRENDERER_H_
diff --git a/talk/app/webrtc/test/mockpeerconnectionobservers.h b/talk/app/webrtc/test/mockpeerconnectionobservers.h
new file mode 100644
index 0000000..e2de379
--- /dev/null
+++ b/talk/app/webrtc/test/mockpeerconnectionobservers.h
@@ -0,0 +1,172 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 mock implementations of observers used in PeerConnection.
+
+#ifndef TALK_APP_WEBRTC_TEST_MOCKPEERCONNECTIONOBSERVERS_H_
+#define TALK_APP_WEBRTC_TEST_MOCKPEERCONNECTIONOBSERVERS_H_
+
+#include <string>
+
+#include "talk/app/webrtc/datachannelinterface.h"
+
+namespace webrtc {
+
+class MockCreateSessionDescriptionObserver
+    : public webrtc::CreateSessionDescriptionObserver {
+ public:
+  MockCreateSessionDescriptionObserver()
+      : called_(false),
+        result_(false) {}
+  virtual ~MockCreateSessionDescriptionObserver() {}
+  virtual void OnSuccess(SessionDescriptionInterface* desc) {
+    called_ = true;
+    result_ = true;
+    desc_.reset(desc);
+  }
+  virtual void OnFailure(const std::string& error) {
+    called_ = true;
+    result_ = false;
+  }
+  bool called() const { return called_; }
+  bool result() const { return result_; }
+  SessionDescriptionInterface* release_desc() {
+    return desc_.release();
+  }
+
+ private:
+  bool called_;
+  bool result_;
+  talk_base::scoped_ptr<SessionDescriptionInterface> desc_;
+};
+
+class MockSetSessionDescriptionObserver
+    : public webrtc::SetSessionDescriptionObserver {
+ public:
+  MockSetSessionDescriptionObserver()
+      : called_(false),
+        result_(false) {}
+  virtual ~MockSetSessionDescriptionObserver() {}
+  virtual void OnSuccess() {
+    called_ = true;
+    result_ = true;
+  }
+  virtual void OnFailure(const std::string& error) {
+    called_ = true;
+    result_ = false;
+  }
+  bool called() const { return called_; }
+  bool result() const { return result_; }
+
+ private:
+  bool called_;
+  bool result_;
+};
+
+class MockDataChannelObserver : public webrtc::DataChannelObserver {
+ public:
+  explicit MockDataChannelObserver(webrtc::DataChannelInterface* channel)
+     : channel_(channel) {
+    channel_->RegisterObserver(this);
+    state_ = channel_->state();
+  }
+  virtual ~MockDataChannelObserver() {
+    channel_->UnregisterObserver();
+  }
+
+  virtual void OnStateChange() { state_ = channel_->state(); }
+  virtual void OnMessage(const DataBuffer& buffer) {
+    last_message_.assign(buffer.data.data(), buffer.data.length());
+  }
+
+  bool IsOpen() const { return state_ == DataChannelInterface::kOpen; }
+  const std::string& last_message() const { return last_message_; }
+
+ private:
+  talk_base::scoped_refptr<webrtc::DataChannelInterface> channel_;
+  DataChannelInterface::DataState state_;
+  std::string last_message_;
+};
+
+class MockStatsObserver : public webrtc::StatsObserver {
+ public:
+  MockStatsObserver()
+      : called_(false) {}
+  virtual ~MockStatsObserver() {}
+  virtual void OnComplete(const std::vector<webrtc::StatsReport>& reports) {
+    called_ = true;
+    reports_ = reports;
+  }
+
+  bool called() const { return called_; }
+  size_t number_of_reports() const { return reports_.size(); }
+
+  int AudioOutputLevel() {
+    return GetSsrcStatsValue(
+        webrtc::StatsReport::kStatsValueNameAudioOutputLevel);
+  }
+
+  int AudioInputLevel() {
+    return GetSsrcStatsValue(
+        webrtc::StatsReport::kStatsValueNameAudioInputLevel);
+  }
+
+  int BytesReceived() {
+    return GetSsrcStatsValue(
+        webrtc::StatsReport::kStatsValueNameBytesReceived);
+  }
+
+  int BytesSent() {
+    return GetSsrcStatsValue(webrtc::StatsReport::kStatsValueNameBytesSent);
+  }
+
+ private:
+  int GetSsrcStatsValue(const std::string name) {
+    if (reports_.empty()) {
+      return 0;
+    }
+    for (size_t i = 0; i < reports_.size(); ++i) {
+      if (reports_[i].type != StatsReport::kStatsReportTypeSsrc)
+        continue;
+      webrtc::StatsReport::Values::const_iterator it =
+          reports_[i].values.begin();
+      for (; it != reports_[i].values.end(); ++it) {
+        if (it->name == name) {
+          return talk_base::FromString<int>(it->value);
+        }
+      }
+    }
+    return 0;
+  }
+
+  bool called_;
+  std::vector<webrtc::StatsReport> reports_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_TEST_MOCKPEERCONNECTIONOBSERVERS_H_
diff --git a/talk/app/webrtc/test/testsdpstrings.h b/talk/app/webrtc/test/testsdpstrings.h
new file mode 100644
index 0000000..9f95d36
--- /dev/null
+++ b/talk/app/webrtc/test/testsdpstrings.h
@@ -0,0 +1,144 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 SDP strings used for testing.
+
+#ifndef TALK_APP_WEBRTC_TEST_TESTSDPSTRINGS_H_
+#define TALK_APP_WEBRTC_TEST_TESTSDPSTRINGS_H_
+
+namespace webrtc {
+
+// SDP offer string from a Nightly FireFox build.
+static const char kFireFoxSdpOffer[] =
+    "v=0\r\n"
+    "o=Mozilla-SIPUA 23551 0 IN IP4 0.0.0.0\r\n"
+    "s=SIP Call\r\n"
+    "t=0 0\r\n"
+    "a=ice-ufrag:e5785931\r\n"
+    "a=ice-pwd:36fb7878390db89481c1d46daa4278d8\r\n"
+    "a=fingerprint:sha-256 A7:24:72:CA:6E:02:55:39:BA:66:DF:6E:CC:4C:D8:B0:1A:"
+    "BF:1A:56:65:7D:F4:03:AD:7E:77:43:2A:29:EC:93\r\n"
+    "m=audio 36993 RTP/SAVPF 109 0 8 101\r\n"
+    "c=IN IP4 74.95.2.170\r\n"
+    "a=rtpmap:109 opus/48000/2\r\n"
+    "a=ptime:20\r\n"
+    "a=rtpmap:0 PCMU/8000\r\n"
+    "a=rtpmap:8 PCMA/8000\r\n"
+    "a=rtpmap:101 telephone-event/8000\r\n"
+    "a=fmtp:101 0-15\r\n"
+    "a=sendrecv\r\n"
+    "a=candidate:0 1 UDP 2112946431 172.16.191.1 61725 typ host\r\n"
+    "a=candidate:2 1 UDP 2112487679 172.16.131.1 58798 typ host\r\n"
+    "a=candidate:4 1 UDP 2113667327 10.0.254.2 58122 typ host\r\n"
+    "a=candidate:5 1 UDP 1694302207 74.95.2.170 36993 typ srflx raddr "
+    "10.0.254.2 rport 58122\r\n"
+    "a=candidate:0 2 UDP 2112946430 172.16.191.1 55025 typ host\r\n"
+    "a=candidate:2 2 UDP 2112487678 172.16.131.1 63576 typ host\r\n"
+    "a=candidate:4 2 UDP 2113667326 10.0.254.2 50962 typ host\r\n"
+    "a=candidate:5 2 UDP 1694302206 74.95.2.170 41028 typ srflx raddr"
+    " 10.0.254.2 rport 50962\r\n"
+    "m=video 38826 RTP/SAVPF 120\r\n"
+    "c=IN IP4 74.95.2.170\r\n"
+    "a=rtpmap:120 VP8/90000\r\n"
+    "a=sendrecv\r\n"
+    "a=candidate:0 1 UDP 2112946431 172.16.191.1 62017 typ host\r\n"
+    "a=candidate:2 1 UDP 2112487679 172.16.131.1 59741 typ host\r\n"
+    "a=candidate:4 1 UDP 2113667327 10.0.254.2 62652 typ host\r\n"
+    "a=candidate:5 1 UDP 1694302207 74.95.2.170 38826 typ srflx raddr"
+    " 10.0.254.2 rport 62652\r\n"
+    "a=candidate:0 2 UDP 2112946430 172.16.191.1 63440 typ host\r\n"
+    "a=candidate:2 2 UDP 2112487678 172.16.131.1 51847 typ host\r\n"
+    "a=candidate:4 2 UDP 2113667326 10.0.254.2 58890 typ host\r\n"
+    "a=candidate:5 2 UDP 1694302206 74.95.2.170 33611 typ srflx raddr"
+    " 10.0.254.2 rport 58890\r\n"
+    "m=application 45536 SCTP/DTLS 5000\r\n"
+    "c=IN IP4 74.95.2.170\r\n"
+    "a=fmtp:5000 protocol=webrtc-datachannel;streams=16\r\n"
+    "a=sendrecv\r\n"
+    "a=candidate:0 1 UDP 2112946431 172.16.191.1 60248 typ host\r\n"
+    "a=candidate:2 1 UDP 2112487679 172.16.131.1 55925 typ host\r\n"
+    "a=candidate:4 1 UDP 2113667327 10.0.254.2 65268 typ host\r\n"
+    "a=candidate:5 1 UDP 1694302207 74.95.2.170 45536 typ srflx raddr"
+    " 10.0.254.2 rport 65268\r\n"
+    "a=candidate:0 2 UDP 2112946430 172.16.191.1 49162 typ host\r\n"
+    "a=candidate:2 2 UDP 2112487678 172.16.131.1 59635 typ host\r\n"
+    "a=candidate:4 2 UDP 2113667326 10.0.254.2 61232 typ host\r\n"
+    "a=candidate:5 2 UDP 1694302206 74.95.2.170 45468 typ srflx raddr"
+    " 10.0.254.2 rport 61232\r\n";
+
+// Audio SDP with a limited set of audio codecs.
+static const char kAudioSdp[] =
+    "v=0\r\n"
+    "o=- 7859371131 2 IN IP4 192.168.30.208\r\n"
+    "s=-\r\n"
+    "c=IN IP4 192.168.30.208\r\n"
+    "t=0 0\r\n"
+    "m=audio 16000 RTP/SAVPF 0 8 126\r\n"
+    "a=rtpmap:0 PCMU/8000\r\n"
+    "a=rtpmap:8 PCMA/8000\r\n"
+    "a=rtpmap:126 telephone-event/8000\r\n"
+    "a=sendrecv\r\n"
+    "a=rtcp:16000 IN IP4 192.168.30.208\r\n"
+    "a=rtcp-mux\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
+    "inline:tvKIFjbMQ7W0/C2RzhwN0oQglj/7GJg+frdsNRxt\r\n"
+    "a=ice-ufrag:AI2sRT3r\r\n"
+    "a=ice-pwd:lByS9z2RSQlSE9XurlvjYmEm\r\n"
+    "a=ssrc:4227871655 cname:GeAAgb6XCPNLVMX5\r\n"
+    "a=ssrc:4227871655 msid:1NFAV3iD08ioO2339rQS9pfOI9mDf6GeG9F4 a0\r\n"
+    "a=ssrc:4227871655 mslabel:1NFAV3iD08ioO2339rQS9pfOI9mDf6GeG9F4\r\n"
+    "a=ssrc:4227871655 label:1NFAV3iD08ioO2339rQS9pfOI9mDf6GeG9F4a0\r\n"
+    "a=mid:audio\r\n";
+
+static const char kAudioSdpWithUnsupportedCodecs[] =
+    "v=0\r\n"
+    "o=- 6858750541 2 IN IP4 192.168.30.208\r\n"
+    "s=-\r\n"
+    "c=IN IP4 192.168.30.208\r\n"
+    "t=0 0\r\n"
+    "m=audio 16000 RTP/SAVPF 0 8 18 110 126\r\n"
+    "a=rtpmap:0 PCMU/8000\r\n"
+    "a=rtpmap:8 PCMA/8000\r\n"
+    "a=rtpmap:18 WeirdCodec1/8000\r\n"
+    "a=rtpmap:110 WeirdCodec2/8000\r\n"
+    "a=rtpmap:126 telephone-event/8000\r\n"
+    "a=sendonly\r\n"
+    "a=rtcp:16000 IN IP4 192.168.30.208\r\n"
+    "a=rtcp-mux\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
+    "inline:tvKIFjbMQ7W0/C2RzhwN0oQglj/7GJg+frdsNRxt\r\n"
+    "a=ice-ufrag:AI2sRT3r\r\n"
+    "a=ice-pwd:lByS9z2RSQlSE9XurlvjYmEm\r\n"
+    "a=ssrc:4227871655 cname:TsmD02HRfhkJBm4m\r\n"
+    "a=ssrc:4227871655 msid:7nU0TApbB-n4dfPlCplWT9QTEsbBDS1IlpW3 a0\r\n"
+    "a=ssrc:4227871655 mslabel:7nU0TApbB-n4dfPlCplWT9QTEsbBDS1IlpW3\r\n"
+    "a=ssrc:4227871655 label:7nU0TApbB-n4dfPlCplWT9QTEsbBDS1IlpW3a0\r\n"
+    "a=mid:audio\r\n";
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_TEST_TESTSDPSTRINGS_H_
diff --git a/talk/app/webrtc/videosourceinterface.h b/talk/app/webrtc/videosourceinterface.h
new file mode 100644
index 0000000..ae968f7
--- /dev/null
+++ b/talk/app/webrtc/videosourceinterface.h
@@ -0,0 +1,57 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_VIDEOSOURCEINTERFACE_H_
+#define TALK_APP_WEBRTC_VIDEOSOURCEINTERFACE_H_
+
+#include "talk/app/webrtc/mediastreaminterface.h"
+#include "talk/media/base/mediachannel.h"
+
+namespace webrtc {
+
+// VideoSourceInterface is a reference counted source used for VideoTracks.
+// The same source can be used in multiple VideoTracks.
+// The methods are only supposed to be called by the PeerConnection
+// implementation.
+class VideoSourceInterface : public MediaSourceInterface {
+ public:
+  // Get access to the source implementation of cricket::VideoCapturer.
+  // This can be used for receiving frames and state notifications.
+  // But it should not be used for starting or stopping capturing.
+  virtual cricket::VideoCapturer* GetVideoCapturer() = 0;
+  // Adds |output| to the source to receive frames.
+  virtual void AddSink(cricket::VideoRenderer* output) = 0;
+  virtual void RemoveSink(cricket::VideoRenderer* output) = 0;
+  virtual const cricket::VideoOptions* options() const = 0;
+
+ protected:
+  virtual ~VideoSourceInterface() {}
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_VIDEOSOURCEINTERFACE_H_
diff --git a/talk/app/webrtc/videosourceproxy.h b/talk/app/webrtc/videosourceproxy.h
new file mode 100644
index 0000000..be80077
--- /dev/null
+++ b/talk/app/webrtc/videosourceproxy.h
@@ -0,0 +1,51 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_VIDEOSOURCEPROXY_H_
+#define TALK_APP_WEBRTC_VIDEOSOURCEPROXY_H_
+
+#include "talk/app/webrtc/proxy.h"
+#include "talk/app/webrtc/videosourceinterface.h"
+
+namespace webrtc {
+
+// VideoSourceProxy makes sure the real VideoSourceInterface implementation is
+// destroyed on the signaling thread and marshals all method calls to the
+// signaling thread.
+BEGIN_PROXY_MAP(VideoSource)
+  PROXY_CONSTMETHOD0(SourceState, state)
+  PROXY_METHOD0(cricket::VideoCapturer*, GetVideoCapturer)
+  PROXY_METHOD1(void, AddSink, cricket::VideoRenderer*)
+  PROXY_METHOD1(void, RemoveSink, cricket::VideoRenderer*)
+  PROXY_CONSTMETHOD0(const cricket::VideoOptions*, options)
+  PROXY_METHOD1(void, RegisterObserver, ObserverInterface*)
+  PROXY_METHOD1(void, UnregisterObserver, ObserverInterface*)
+END_PROXY()
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_VIDEOSOURCEPROXY_H_
diff --git a/talk/app/webrtc/videotrack.cc b/talk/app/webrtc/videotrack.cc
new file mode 100644
index 0000000..ec17ec7
--- /dev/null
+++ b/talk/app/webrtc/videotrack.cc
@@ -0,0 +1,78 @@
+/*
+ * 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/videotrack.h"
+
+#include <string>
+
+#include "talk/media/webrtc/webrtcvideocapturer.h"
+
+namespace webrtc {
+
+static const char kVideoTrackKind[] = "video";
+
+VideoTrack::VideoTrack(const std::string& label,
+                       VideoSourceInterface* video_source)
+    : MediaStreamTrack<VideoTrackInterface>(label),
+      video_source_(video_source) {
+  if (video_source_)
+    video_source_->AddSink(FrameInput());
+}
+
+VideoTrack::~VideoTrack() {
+  if (video_source_)
+    video_source_->RemoveSink(FrameInput());
+}
+
+std::string VideoTrack::kind() const {
+  return kVideoTrackKind;
+}
+
+void VideoTrack::AddRenderer(VideoRendererInterface* renderer) {
+  renderers_.AddRenderer(renderer);
+}
+
+void VideoTrack::RemoveRenderer(VideoRendererInterface* renderer) {
+  renderers_.RemoveRenderer(renderer);
+}
+
+cricket::VideoRenderer* VideoTrack::FrameInput() {
+  return &renderers_;
+}
+
+bool VideoTrack::set_enabled(bool enable) {
+  renderers_.SetEnabled(enable);
+  return MediaStreamTrack<VideoTrackInterface>::set_enabled(enable);
+}
+
+talk_base::scoped_refptr<VideoTrack> VideoTrack::Create(
+    const std::string& id, VideoSourceInterface* source) {
+  talk_base::RefCountedObject<VideoTrack>* track =
+      new talk_base::RefCountedObject<VideoTrack>(id, source);
+  return track;
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/videotrack.h b/talk/app/webrtc/videotrack.h
new file mode 100644
index 0000000..aefeb50
--- /dev/null
+++ b/talk/app/webrtc/videotrack.h
@@ -0,0 +1,65 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_VIDEOTRACK_H_
+#define TALK_APP_WEBRTC_VIDEOTRACK_H_
+
+#include <string>
+
+#include "talk/app/webrtc/mediastreamtrack.h"
+#include "talk/app/webrtc/videosourceinterface.h"
+#include "talk/app/webrtc/videotrackrenderers.h"
+#include "talk/base/scoped_ref_ptr.h"
+
+namespace webrtc {
+
+class VideoTrack : public MediaStreamTrack<VideoTrackInterface> {
+ public:
+  static talk_base::scoped_refptr<VideoTrack> Create(
+      const std::string& label, VideoSourceInterface* source);
+
+  virtual void AddRenderer(VideoRendererInterface* renderer);
+  virtual void RemoveRenderer(VideoRendererInterface* renderer);
+  virtual cricket::VideoRenderer* FrameInput();
+  virtual VideoSourceInterface* GetSource() const {
+    return video_source_.get();
+  }
+  virtual bool set_enabled(bool enable);
+  virtual std::string kind() const;
+
+ protected:
+  VideoTrack(const std::string& id, VideoSourceInterface* video_source);
+  ~VideoTrack();
+
+ private:
+  VideoTrackRenderers renderers_;
+  talk_base::scoped_refptr<VideoSourceInterface> video_source_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_VIDEOTRACK_H_
diff --git a/talk/app/webrtc/videotrack_unittest.cc b/talk/app/webrtc/videotrack_unittest.cc
new file mode 100644
index 0000000..671e360
--- /dev/null
+++ b/talk/app/webrtc/videotrack_unittest.cc
@@ -0,0 +1,84 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/test/fakevideotrackrenderer.h"
+#include "talk/app/webrtc/videotrack.h"
+#include "talk/base/gunit.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/media/webrtc/webrtcvideoframe.h"
+
+using webrtc::FakeVideoTrackRenderer;
+using webrtc::VideoTrack;
+using webrtc::VideoTrackInterface;
+
+// Test adding renderers to a video track and render to them by providing
+// VideoFrames to the track frame input.
+TEST(VideoTrack, RenderVideo) {
+  static const char kVideoTrackId[] = "track_id";
+  talk_base::scoped_refptr<VideoTrackInterface> video_track(
+      VideoTrack::Create(kVideoTrackId, NULL));
+  // FakeVideoTrackRenderer register itself to |video_track|
+  talk_base::scoped_ptr<FakeVideoTrackRenderer> renderer_1(
+      new FakeVideoTrackRenderer(video_track.get()));
+
+  cricket::VideoRenderer* render_input = video_track->FrameInput();
+  ASSERT_FALSE(render_input == NULL);
+
+  render_input->SetSize(123, 123, 0);
+  EXPECT_EQ(1, renderer_1->num_set_sizes());
+  EXPECT_EQ(123, renderer_1->width());
+  EXPECT_EQ(123, renderer_1->height());
+
+  cricket::WebRtcVideoFrame frame;
+  frame.InitToBlack(123, 123, 1, 1, 0, 0);
+  render_input->RenderFrame(&frame);
+  EXPECT_EQ(1, renderer_1->num_rendered_frames());
+
+  // FakeVideoTrackRenderer register itself to |video_track|
+  talk_base::scoped_ptr<FakeVideoTrackRenderer> renderer_2(
+      new FakeVideoTrackRenderer(video_track.get()));
+
+  render_input->RenderFrame(&frame);
+
+  EXPECT_EQ(1, renderer_1->num_set_sizes());
+  EXPECT_EQ(123, renderer_1->width());
+  EXPECT_EQ(123, renderer_1->height());
+  EXPECT_EQ(1, renderer_2->num_set_sizes());
+  EXPECT_EQ(123, renderer_2->width());
+  EXPECT_EQ(123, renderer_2->height());
+
+  EXPECT_EQ(2, renderer_1->num_rendered_frames());
+  EXPECT_EQ(1, renderer_2->num_rendered_frames());
+
+  video_track->RemoveRenderer(renderer_1.get());
+  render_input->RenderFrame(&frame);
+
+  EXPECT_EQ(2, renderer_1->num_rendered_frames());
+  EXPECT_EQ(2, renderer_2->num_rendered_frames());
+}
diff --git a/talk/app/webrtc/videotrackrenderers.cc b/talk/app/webrtc/videotrackrenderers.cc
new file mode 100644
index 0000000..b0e0c1f
--- /dev/null
+++ b/talk/app/webrtc/videotrackrenderers.cc
@@ -0,0 +1,94 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/videotrackrenderers.h"
+
+namespace webrtc {
+
+VideoTrackRenderers::VideoTrackRenderers()
+    : width_(0),
+      height_(0),
+      enabled_(true) {
+}
+
+VideoTrackRenderers::~VideoTrackRenderers() {
+}
+
+void VideoTrackRenderers::AddRenderer(VideoRendererInterface* renderer) {
+  talk_base::CritScope cs(&critical_section_);
+  std::vector<RenderObserver>::iterator it =  renderers_.begin();
+  for (; it != renderers_.end(); ++it) {
+    if (it->renderer_ == renderer)
+      return;
+  }
+  renderers_.push_back(RenderObserver(renderer));
+}
+
+void VideoTrackRenderers::RemoveRenderer(VideoRendererInterface* renderer) {
+  talk_base::CritScope cs(&critical_section_);
+  std::vector<RenderObserver>::iterator it =  renderers_.begin();
+  for (; it != renderers_.end(); ++it) {
+    if (it->renderer_ == renderer) {
+      renderers_.erase(it);
+      return;
+    }
+  }
+}
+
+void VideoTrackRenderers::SetEnabled(bool enable) {
+  talk_base::CritScope cs(&critical_section_);
+  enabled_ = enable;
+}
+
+bool VideoTrackRenderers::SetSize(int width, int height, int reserved) {
+  talk_base::CritScope cs(&critical_section_);
+  width_ = width;
+  height_ = height;
+  std::vector<RenderObserver>::iterator it = renderers_.begin();
+  for (; it != renderers_.end(); ++it) {
+    it->renderer_->SetSize(width, height);
+    it->size_set_ = true;
+  }
+  return true;
+}
+
+bool VideoTrackRenderers::RenderFrame(const cricket::VideoFrame* frame) {
+  talk_base::CritScope cs(&critical_section_);
+  if (!enabled_) {
+    return true;
+  }
+  std::vector<RenderObserver>::iterator it = renderers_.begin();
+  for (; it != renderers_.end(); ++it) {
+    if (!it->size_set_) {
+      it->renderer_->SetSize(width_, height_);
+      it->size_set_ = true;
+    }
+    it->renderer_->RenderFrame(frame);
+  }
+  return true;
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/videotrackrenderers.h b/talk/app/webrtc/videotrackrenderers.h
new file mode 100644
index 0000000..4bcf6a3
--- /dev/null
+++ b/talk/app/webrtc/videotrackrenderers.h
@@ -0,0 +1,77 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_VIDEOTRACKRENDERERS_H_
+#define TALK_APP_WEBRTC_VIDEOTRACKRENDERERS_H_
+
+#include <vector>
+
+#include "talk/app/webrtc/mediastreaminterface.h"
+#include "talk/base/criticalsection.h"
+#include "talk/media/base/videorenderer.h"
+
+namespace webrtc {
+
+// Class used for rendering cricket::VideoFrames to multiple renderers of type
+// VideoRendererInterface.
+// Each VideoTrack owns a VideoTrackRenderers instance.
+// The class is thread safe. Rendering to the added VideoRendererInterfaces is
+// done on the same thread as the cricket::VideoRenderer.
+class VideoTrackRenderers : public cricket::VideoRenderer {
+ public:
+  VideoTrackRenderers();
+  ~VideoTrackRenderers();
+
+  // Implements cricket::VideoRenderer
+  virtual bool SetSize(int width, int height, int reserved);
+  virtual bool RenderFrame(const cricket::VideoFrame* frame);
+
+  void AddRenderer(VideoRendererInterface* renderer);
+  void RemoveRenderer(VideoRendererInterface* renderer);
+  void SetEnabled(bool enable);
+
+ private:
+  struct RenderObserver {
+    explicit RenderObserver(VideoRendererInterface* renderer)
+        : renderer_(renderer),
+          size_set_(false) {
+    }
+    VideoRendererInterface* renderer_;
+    bool size_set_;
+  };
+
+  int width_;
+  int height_;
+  bool enabled_;
+  std::vector<RenderObserver> renderers_;
+
+  talk_base::CriticalSection critical_section_;  // Protects the above variables
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_VIDEOTRACKRENDERERS_H_
diff --git a/talk/app/webrtc/webrtc.scons b/talk/app/webrtc/webrtc.scons
new file mode 100644
index 0000000..0cbe756
--- /dev/null
+++ b/talk/app/webrtc/webrtc.scons
@@ -0,0 +1,88 @@
+# -*- Python -*-
+import talk
+
+Import('env')
+
+# For peerconnection, we need additional flags only for GCC 4.6+.
+peerconnection_lin_ccflags = []
+
+if env.Bit('linux'):
+  # Detect the GCC version and update peerconnection flags.
+  (major, minor, rev) = env.GetGccVersion()
+  if major > 4 or (major == 4 and minor >= 6):
+    peerconnection_lin_ccflags = ['-Wno-error=unused-but-set-variable']
+
+
+if env.Bit('have_webrtc_voice') and env.Bit('have_webrtc_video'):
+  # local sources
+  talk.Library(
+    env,
+    name = 'peerconnection',
+    srcs = [
+      'audiotrack.cc',
+      'jsepicecandidate.cc',
+      'jsepsessiondescription.cc',
+      'mediaconstraintsinterface.cc',
+      'mediastream.cc',
+      'mediastreamhandler.cc',
+      'mediastreamproxy.cc',
+      'mediastreamsignaling.cc',
+      'mediastreamtrackproxy.cc',
+      'peerconnectionfactory.cc',
+      'peerconnection.cc',
+      'portallocatorfactory.cc',
+      'roapmessages.cc',
+      'roapsession.cc',
+      'roapsignaling.cc',
+      'videorendererimpl.cc',
+      'videotrack.cc',
+      'webrtcsdp.cc',
+      'webrtcsession.cc',
+    ],
+    lin_ccflags = peerconnection_lin_ccflags
+  )
+
+  talk.Unittest(
+    env,
+    name = 'peerconnection',
+    srcs = [
+      'test/fakeaudiocapturemodule.cc',
+      'test/fakeaudiocapturemodule_unittest.cc',
+      'test/fakevideocapturemodule.cc',
+      'test/fileframesource.cc',
+      'test/i420framesource.cc',
+      'test/staticframesource.cc',
+      'jsepsessiondescription_unittest.cc',
+      'mediastream_unittest.cc',
+      'mediastreamhandler_unittest.cc',
+      'mediastreamsignaling_unittest.cc',
+      'peerconnectioninterface_unittest.cc',
+      'peerconnection_unittest.cc',
+      'peerconnectionfactory_unittest.cc',
+      'roapmessages_unittest.cc',
+      'roapsession_unittest.cc',
+      'roapsignaling_unittest.cc',
+      'webrtcsdp_unittest.cc',
+      'webrtcsession_unittest.cc',
+    ],
+    libs = [
+      'base',
+      'expat',
+      '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/webrtcsdp.cc b/talk/app/webrtc/webrtcsdp.cc
new file mode 100644
index 0000000..f91db8d
--- /dev/null
+++ b/talk/app/webrtc/webrtcsdp.cc
@@ -0,0 +1,2885 @@
+/*
+ * 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 <limits.h>
+#include <stdio.h>
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "talk/app/webrtc/jsepicecandidate.h"
+#include "talk/app/webrtc/jsepsessiondescription.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/messagedigest.h"
+#include "talk/base/stringutils.h"
+#include "talk/media/base/codec.h"
+#include "talk/media/base/constants.h"
+#include "talk/media/base/cryptoparams.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/port.h"
+#include "talk/session/media/mediasession.h"
+#include "talk/session/media/mediasessionclient.h"
+
+using cricket::AudioContentDescription;
+using cricket::Candidate;
+using cricket::Candidates;
+using cricket::ContentDescription;
+using cricket::ContentInfo;
+using cricket::CryptoParams;
+using cricket::DataContentDescription;
+using cricket::ICE_CANDIDATE_COMPONENT_RTP;
+using cricket::ICE_CANDIDATE_COMPONENT_RTCP;
+using cricket::kCodecParamMaxBitrate;
+using cricket::kCodecParamMaxPTime;
+using cricket::kCodecParamMaxQuantization;
+using cricket::kCodecParamMinBitrate;
+using cricket::kCodecParamMinPTime;
+using cricket::kCodecParamPTime;
+using cricket::kCodecParamSPropStereo;
+using cricket::kCodecParamStereo;
+using cricket::kCodecParamUseInbandFec;
+using cricket::kCodecParamSctpProtocol;
+using cricket::kCodecParamSctpStreams;
+using cricket::kWildcardPayloadType;
+using cricket::MediaContentDescription;
+using cricket::MediaType;
+using cricket::NS_JINGLE_ICE_UDP;
+using cricket::RtpHeaderExtension;
+using cricket::SsrcGroup;
+using cricket::StreamParams;
+using cricket::StreamParamsVec;
+using cricket::TransportDescription;
+using cricket::TransportInfo;
+using cricket::VideoContentDescription;
+using talk_base::SocketAddress;
+
+typedef std::vector<RtpHeaderExtension> RtpHeaderExtensions;
+
+namespace cricket {
+class SessionDescription;
+}
+
+namespace webrtc {
+
+// Line type
+// RFC 4566
+// An SDP session description consists of a number of lines of text of
+// the form:
+// <type>=<value>
+// where <type> MUST be exactly one case-significant character.
+static const int kLinePrefixLength = 2;  // Lenght of <type>=
+static const char kLineTypeVersion = 'v';
+static const char kLineTypeOrigin = 'o';
+static const char kLineTypeSessionName = 's';
+static const char kLineTypeSessionInfo = 'i';
+static const char kLineTypeSessionUri = 'u';
+static const char kLineTypeSessionEmail = 'e';
+static const char kLineTypeSessionPhone = 'p';
+static const char kLineTypeSessionBandwidth = 'b';
+static const char kLineTypeTiming = 't';
+static const char kLineTypeRepeatTimes = 'r';
+static const char kLineTypeTimeZone = 'z';
+static const char kLineTypeEncryptionKey = 'k';
+static const char kLineTypeMedia = 'm';
+static const char kLineTypeConnection = 'c';
+static const char kLineTypeAttributes = 'a';
+
+// Attributes
+static const char kAttributeGroup[] = "group";
+static const char kAttributeMid[] = "mid";
+static const char kAttributeRtcpMux[] = "rtcp-mux";
+static const char kAttributeSsrc[] = "ssrc";
+static const char kSsrcAttributeCname[] = "cname";
+static const char kAttributeExtmap[] = "extmap";
+// draft-alvestrand-mmusic-msid-01
+// a=msid-semantic: WMS
+static const char kAttributeMsidSemantics[] = "msid-semantic";
+static const char kMediaStreamSemantic[] = "WMS";
+static const char kSsrcAttributeMsid[] = "msid";
+static const char kDefaultMsid[] = "default";
+static const char kMsidAppdataAudio[] = "a";
+static const char kMsidAppdataVideo[] = "v";
+static const char kMsidAppdataData[] = "d";
+static const char kSsrcAttributeMslabel[] = "mslabel";
+static const char kSSrcAttributeLabel[] = "label";
+static const char kAttributeSsrcGroup[] = "ssrc-group";
+static const char kAttributeCrypto[] = "crypto";
+static const char kAttributeCandidate[] = "candidate";
+static const char kAttributeCandidateTyp[] = "typ";
+static const char kAttributeCandidateRaddr[] = "raddr";
+static const char kAttributeCandidateRport[] = "rport";
+static const char kAttributeCandidateUsername[] = "username";
+static const char kAttributeCandidatePassword[] = "password";
+static const char kAttributeCandidateGeneration[] = "generation";
+static const char kAttributeFingerprint[] = "fingerprint";
+static const char kAttributeFmtp[] = "fmtp";
+static const char kAttributeRtpmap[] = "rtpmap";
+static const char kAttributeRtcp[] = "rtcp";
+static const char kAttributeIceUfrag[] = "ice-ufrag";
+static const char kAttributeIcePwd[] = "ice-pwd";
+static const char kAttributeIceLite[] = "ice-lite";
+static const char kAttributeIceOption[] = "ice-options";
+static const char kAttributeSendOnly[] = "sendonly";
+static const char kAttributeRecvOnly[] = "recvonly";
+static const char kAttributeRtcpFb[] = "rtcp-fb";
+static const char kAttributeSendRecv[] = "sendrecv";
+static const char kAttributeInactive[] = "inactive";
+
+// Experimental flags
+static const char kAttributeXGoogleFlag[] = "x-google-flag";
+static const char kValueConference[] = "conference";
+static const char kAttributeXGoogleBufferLatency[] =
+    "x-google-buffer-latency";
+
+// 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 kSdpDelimiterEqual = '=';
+static const char kSdpDelimiterSpace = ' ';
+static const char kSdpDelimiterColon = ':';
+static const char kSdpDelimiterSemicolon = ';';
+static const char kSdpDelimiterSlash = '/';
+static const char kNewLine = '\n';
+static const char kReturn = '\r';
+static const char kLineBreak[] = "\r\n";
+
+// TODO: Generate the Session and Time description
+// instead of hardcoding.
+static const char kSessionVersion[] = "v=0";
+// RFC 4566
+static const char kSessionOriginUsername[] = "-";
+static const char kSessionOriginSessionId[] = "0";
+static const char kSessionOriginSessionVersion[] = "0";
+static const char kSessionOriginNettype[] = "IN";
+static const char kSessionOriginAddrtype[] = "IP4";
+static const char kSessionOriginAddress[] = "127.0.0.1";
+static const char kSessionName[] = "s=-";
+static const char kTimeDescription[] = "t=0 0";
+static const char kAttrGroup[] = "a=group:BUNDLE";
+static const char kConnectionNettype[] = "IN";
+static const char kConnectionAddrtype[] = "IP4";
+static const char kMediaTypeVideo[] = "video";
+static const char kMediaTypeAudio[] = "audio";
+static const char kMediaTypeData[] = "application";
+static const char kMediaPortRejected[] = "0";
+static const char kDefaultAddress[] = "0.0.0.0";
+static const char kDefaultPort[] = "1";
+// RFC 3556
+static const char kApplicationSpecificMaximum[] = "AS";
+
+static const int kDefaultVideoClockrate = 90000;
+
+// ISAC special-case.
+static const char kIsacCodecName[] = "ISAC";  // From webrtcvoiceengine.cc
+static const int kIsacWbDefaultRate = 32000;  // From acm_common_defs.h
+static const int kIsacSwbDefaultRate = 56000;  // From acm_common_defs.h
+
+static const int kDefaultSctpFmt = 5000;
+static const char kDefaultSctpFmtProtocol[] = "webrtc-datachannel";
+
+struct SsrcInfo {
+  SsrcInfo()
+      : msid_identifier(kDefaultMsid),
+        // TODO(ronghuawu): What should we do if the appdata doesn't appear?
+        // Create random string (which will be used as track label later)?
+        msid_appdata(talk_base::CreateRandomString(8)) {
+  }
+  uint32 ssrc_id;
+  std::string cname;
+  std::string msid_identifier;
+  std::string msid_appdata;
+
+  // For backward compatibility.
+  // TODO(ronghuawu): Remove below 2 fields once all the clients support msid.
+  std::string label;
+  std::string mslabel;
+};
+typedef std::vector<SsrcInfo> SsrcInfoVec;
+typedef std::vector<SsrcGroup> SsrcGroupVec;
+
+// Serializes the passed in SessionDescription to a SDP string.
+// desc - The SessionDescription object to be serialized.
+static std::string SdpSerializeSessionDescription(
+    const JsepSessionDescription& jdesc);
+template <class T>
+static void AddFmtpLine(const T& codec, std::string* message);
+static void BuildMediaDescription(const ContentInfo* content_info,
+                                  const TransportInfo* transport_info,
+                                  const MediaType media_type,
+                                  std::string* message);
+static void BuildSctpContentAttributes(std::string* message);
+static void BuildRtpContentAttributes(
+    const MediaContentDescription* media_desc,
+    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,
+                           std::string* message);
+static void BuildIceOptions(const std::vector<std::string>& transport_options,
+                            std::string* message);
+
+static bool ParseSessionDescription(const std::string& message, size_t* pos,
+                                    std::string* session_id,
+                                    std::string* session_version,
+                                    bool* supports_msid,
+                                    TransportDescription* session_td,
+                                    RtpHeaderExtensions* session_extmaps,
+                                    cricket::SessionDescription* desc,
+                                    SdpParseError* error);
+static bool ParseGroupAttribute(const std::string& line,
+                                cricket::SessionDescription* desc,
+                                SdpParseError* error);
+static bool ParseMediaDescription(
+    const std::string& message,
+    const TransportDescription& session_td,
+    const RtpHeaderExtensions& session_extmaps,
+    bool supports_msid,
+    size_t* pos, cricket::SessionDescription* desc,
+    std::vector<JsepIceCandidate*>* candidates,
+    SdpParseError* error);
+static bool ParseContent(const std::string& message,
+                         const MediaType media_type,
+                         int mline_index,
+                         const std::string& protocol,
+                         const std::vector<int>& codec_preference,
+                         size_t* pos,
+                         std::string* content_name,
+                         MediaContentDescription* media_desc,
+                         TransportDescription* transport,
+                         std::vector<JsepIceCandidate*>* candidates,
+                         SdpParseError* error);
+static bool ParseSsrcAttribute(const std::string& line,
+                               SsrcInfoVec* ssrc_infos,
+                               SdpParseError* error);
+static bool ParseSsrcGroupAttribute(const std::string& line,
+                                    SsrcGroupVec* ssrc_groups,
+                                    SdpParseError* error);
+static bool ParseCryptoAttribute(const std::string& line,
+                                 MediaContentDescription* media_desc,
+                                 SdpParseError* error);
+static bool ParseRtpmapAttribute(const std::string& line,
+                                 const MediaType media_type,
+                                 const std::vector<int>& codec_preference,
+                                 MediaContentDescription* media_desc,
+                                 SdpParseError* error);
+static bool ParseFmtpAttributes(const std::string& line,
+                                const MediaType media_type,
+                                MediaContentDescription* media_desc,
+                                SdpParseError* error);
+static bool ParseFmtpParam(const std::string& line, std::string* parameter,
+                           std::string* value, SdpParseError* error);
+static bool ParseCandidate(const std::string& message, Candidate* candidate,
+                           SdpParseError* error, bool is_raw);
+static bool ParseRtcpFbAttribute(const std::string& line,
+                                 const MediaType media_type,
+                                 MediaContentDescription* media_desc,
+                                 SdpParseError* error);
+static bool ParseIceOptions(const std::string& line,
+                            std::vector<std::string>* transport_options,
+                            SdpParseError* error);
+static bool ParseExtmap(const std::string& line,
+                        RtpHeaderExtension* extmap,
+                        SdpParseError* error);
+static bool ParseFingerprintAttribute(const std::string& line,
+                                      talk_base::SSLFingerprint** fingerprint,
+                                      SdpParseError* error);
+
+// Helper functions
+
+// Below ParseFailed*** functions output the line that caused the parsing
+// failure and the detailed reason (|description|) of the failure to |error|.
+// The functions always return false so that they can be used directly in the
+// following way when error happens:
+// "return ParseFailed***(...);"
+
+// The line starting at |line_start| of |message| is the failing line.
+// The reason for the failure should be provided in the |description|.
+// An example of a description could be "unknown character".
+static bool ParseFailed(const std::string& message,
+                        size_t line_start,
+                        const std::string& description,
+                        SdpParseError* error) {
+  // Get the first line of |message| from |line_start|.
+  std::string first_line = message;
+  size_t line_end = message.find(kNewLine, line_start);
+  if (line_end != std::string::npos) {
+    if (line_end > 0 && (message.at(line_end - 1) == kReturn)) {
+      --line_end;
+    }
+    first_line = message.substr(line_start, (line_end - line_start));
+  }
+
+  if (error) {
+    error->line = first_line;
+    error->description = description;
+  }
+  LOG(LS_ERROR) << "Failed to parse: \"" << first_line
+                << "\". Reason: " << description;
+  return false;
+}
+
+// |line| is the failing line. The reason for the failure should be
+// provided in the |description|.
+static bool ParseFailed(const std::string& line,
+                        const std::string& description,
+                        SdpParseError* error) {
+  return ParseFailed(line, 0, description, error);
+}
+
+// Parses failure where the failing SDP line isn't know or there are multiple
+// failing lines.
+static bool ParseFailed(const std::string& description,
+                        SdpParseError* error) {
+  return ParseFailed("", description, error);
+}
+
+// |line| is the failing line. The failure is due to the fact that |line|
+// doesn't have |expected_fields| fields.
+static bool ParseFailedExpectFieldNum(const std::string& line,
+                                      int expected_fields,
+                                      SdpParseError* error) {
+  std::ostringstream description;
+  description << "Expects " << expected_fields << " fields.";
+  return ParseFailed(line, description.str(), error);
+}
+
+// |line| is the failing line. The failure is due to the fact that |line| has
+// less than |expected_min_fields| fields.
+static bool ParseFailedExpectMinFieldNum(const std::string& line,
+                                         int expected_min_fields,
+                                         SdpParseError* error) {
+  std::ostringstream description;
+  description << "Expects at least " << expected_min_fields << " fields.";
+  return ParseFailed(line, description.str(), error);
+}
+
+// |line| is the failing line. The failure is due to the fact that it failed to
+// get the value of |attribute|.
+static bool ParseFailedGetValue(const std::string& line,
+                                const std::string& attribute,
+                                SdpParseError* error) {
+  std::ostringstream description;
+  description << "Failed to get the value of attribute: " << attribute;
+  return ParseFailed(line, description.str(), error);
+}
+
+// The line starting at |line_start| of |message| is the failing line. The
+// failure is due to the line type (e.g. the "m" part of the "m-line")
+// not matching what is expected. The expected line type should be
+// provided as |line_type|.
+static bool ParseFailedExpectLine(const std::string& message,
+                                  size_t line_start,
+                                  const char line_type,
+                                  const std::string& line_value,
+                                  SdpParseError* error) {
+  std::ostringstream description;
+  description << "Expect line: " << line_type << "=" << line_value;
+  return ParseFailed(message, line_start, description.str(), error);
+}
+
+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(kNewLine, 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) == kReturn)) {
+    --line_end;
+  }
+  *line = message.substr(line_begin, (line_end - line_begin));
+  const char* cline = line->c_str();
+  // RFC 4566
+  // An SDP session description consists of a number of lines of text of
+  // the form:
+  // <type>=<value>
+  // where <type> MUST be exactly one case-significant character and
+  // <value> is structured text whose format depends on <type>.
+  // Whitespace MUST NOT be used on either side of the "=" sign.
+  if (cline[0] == kSdpDelimiterSpace ||
+      cline[1] != kSdpDelimiterEqual ||
+      cline[2] == kSdpDelimiterSpace) {
+    *pos = line_begin;
+    return false;
+  }
+  return true;
+}
+
+// Init |os| to "|type|=|value|".
+static void InitLine(const char type,
+                     const std::string& value,
+                     std::ostringstream* os) {
+  os->str("");
+  *os << type << kSdpDelimiterEqual << value;
+}
+
+// Init |os| to "a=|attribute|".
+static void InitAttrLine(const std::string& attribute, std::ostringstream* os) {
+  InitLine(kLineTypeAttributes, attribute, os);
+}
+
+// Writes a SDP attribute line based on |attribute| and |value| to |message|.
+static void AddAttributeLine(const std::string& attribute, int value,
+                             std::string* message) {
+  std::ostringstream os;
+  InitAttrLine(attribute, &os);
+  os << kSdpDelimiterColon << value;
+  AddLine(os.str(), message);
+}
+
+// Returns the first line of the message without the line breaker.
+static bool GetFirstLine(const std::string& message, std::string* line) {
+  size_t pos = 0;
+  if (!GetLine(message, &pos, line)) {
+    // If GetLine failed, just return the full |message|.
+    *line = message;
+  }
+  return true;
+}
+
+static bool IsLineType(const std::string& message,
+                       const char type,
+                       size_t line_start) {
+  if (message.size() < line_start + kLinePrefixLength) {
+    return false;
+  }
+  const char* cmessage = message.c_str();
+  return (cmessage[line_start] == type &&
+          cmessage[line_start + 1] == kSdpDelimiterEqual);
+}
+
+static bool IsLineType(const std::string& line,
+                       const char type) {
+  return IsLineType(line, type, 0);
+}
+
+static bool GetLineWithType(const std::string& message, size_t* pos,
+                            std::string* line, const char type) {
+  if (!IsLineType(message, type, *pos)) {
+    return false;
+  }
+
+  if (!GetLine(message, pos, line))
+    return false;
+
+  return true;
+}
+
+static bool HasAttribute(const std::string& line,
+                         const std::string& attribute) {
+  return (line.compare(kLinePrefixLength, attribute.size(), attribute) == 0);
+}
+
+// Verifies the candiate to be of the format candidate:<blah>
+static bool IsRawCandidate(const std::string& line) {
+  // Checking candiadte-attribute is starting with "candidate" str.
+  if (line.compare(0, strlen(kAttributeCandidate), kAttributeCandidate) != 0) {
+    return false;
+  }
+  const size_t first_candidate = line.find(kSdpDelimiterColon);
+  if (first_candidate == std::string::npos)
+    return false;
+  // In this format we only expecting one candiate. If any additional
+  // candidates present, whole string will be discared.
+  const size_t any_other = line.find(kSdpDelimiterColon, first_candidate + 1);
+  return (any_other == std::string::npos);
+}
+
+static bool AddSsrcLine(uint32 ssrc_id, const std::string& attribute,
+                        const std::string& value, std::string* message) {
+  // RFC 5576
+  // a=ssrc:<ssrc-id> <attribute>:<value>
+  std::ostringstream os;
+  InitAttrLine(kAttributeSsrc, &os);
+  os << kSdpDelimiterColon << ssrc_id << kSdpDelimiterSpace
+     << attribute << kSdpDelimiterColon << value;
+  return AddLine(os.str(), message);
+}
+
+// Split the message into two parts by the first delimiter.
+static bool SplitByDelimiter(const std::string& message,
+                             const char delimiter,
+                             std::string* field1,
+                             std::string* field2) {
+  // Find the first delimiter
+  size_t pos = message.find(delimiter);
+  if (pos == std::string::npos) {
+    return false;
+  }
+  *field1 = message.substr(0, pos);
+  // The rest is the value.
+  *field2 = message.substr(pos + 1);
+  return true;
+}
+
+// Get value only from <attribute>:<value>.
+static bool GetValue(const std::string& message, const std::string& attribute,
+                     std::string* value, SdpParseError* error) {
+  std::string leftpart;
+  if (!SplitByDelimiter(message, kSdpDelimiterColon, &leftpart, value)) {
+    return ParseFailedGetValue(message, attribute, error);
+  }
+  // The left part should end with the expected attribute.
+  if (leftpart.length() < attribute.length() ||
+      leftpart.compare(leftpart.length() - attribute.length(),
+                       attribute.length(), attribute) != 0) {
+    return ParseFailedGetValue(message, attribute, error);
+  }
+  return true;
+}
+
+static bool CaseInsensitiveFind(std::string str1, std::string str2) {
+  std::transform(str1.begin(), str1.end(), str1.begin(),
+                 ::tolower);
+  std::transform(str2.begin(), str2.end(), str2.begin(),
+                 ::tolower);
+  return str1.find(str2) != std::string::npos;
+}
+
+void CreateTracksFromSsrcInfos(const SsrcInfoVec& ssrc_infos,
+                               StreamParamsVec* tracks) {
+  ASSERT(tracks != NULL);
+  for (SsrcInfoVec::const_iterator ssrc_info = ssrc_infos.begin();
+       ssrc_info != ssrc_infos.end(); ++ssrc_info) {
+    if (ssrc_info->cname.empty()) {
+      continue;
+    }
+
+    std::string sync_label;
+    std::string track_id;
+    if (ssrc_info->msid_identifier == kDefaultMsid &&
+        !ssrc_info->mslabel.empty()) {
+      // If there's no msid and there's mslabel, we consider this is a sdp from
+      // a older version of client that doesn't support msid.
+      // In that case, we use the mslabel and label to construct the track.
+      sync_label = ssrc_info->mslabel;
+      track_id = ssrc_info->label;
+    } else {
+      sync_label = ssrc_info->msid_identifier;
+      // The appdata consists of the "id" attribute of a MediaStreamTrack, which
+      // is corresponding to the "id" attribute of StreamParams.
+      track_id = ssrc_info->msid_appdata;
+    }
+    if (sync_label.empty() || track_id.empty()) {
+      ASSERT(false);
+      continue;
+    }
+
+    StreamParamsVec::iterator track = tracks->begin();
+    for (; track != tracks->end(); ++track) {
+      if (track->id == track_id) {
+        break;
+      }
+    }
+    if (track == tracks->end()) {
+      // If we don't find an existing track, create a new one.
+      tracks->push_back(StreamParams());
+      track = tracks->end() - 1;
+    }
+    track->add_ssrc(ssrc_info->ssrc_id);
+    track->cname = ssrc_info->cname;
+    track->sync_label = sync_label;
+    track->id = track_id;
+  }
+}
+
+void GetMediaStreamLabels(const ContentInfo* content,
+                          std::set<std::string>* labels) {
+  const MediaContentDescription* media_desc =
+      static_cast<const MediaContentDescription*> (
+          content->description);
+  const cricket::StreamParamsVec& streams =  media_desc->streams();
+  for (cricket::StreamParamsVec::const_iterator it = streams.begin();
+       it != streams.end(); ++it) {
+    labels->insert(it->sync_label);
+  }
+}
+
+// RFC 5245
+// It is RECOMMENDED that default candidates be chosen based on the
+// likelihood of those candidates to work with the peer that is being
+// contacted.  It is RECOMMENDED that relayed > reflexive > host.
+static const int kPreferenceUnknown = 0;
+static const int kPreferenceHost = 1;
+static const int kPreferenceReflexive = 2;
+static const int kPreferenceRelayed = 3;
+
+static int GetCandidatePreferenceFromType(const std::string& type) {
+  int preference = kPreferenceUnknown;
+  if (type == cricket::LOCAL_PORT_TYPE) {
+    preference = kPreferenceHost;
+  } else if (type == cricket::STUN_PORT_TYPE) {
+    preference = kPreferenceReflexive;
+  } else if (type == cricket::RELAY_PORT_TYPE) {
+    preference = kPreferenceRelayed;
+  } else {
+    ASSERT(false);
+  }
+  return preference;
+}
+
+// Get ip and port of the default destination from the |candidates| with
+// the given value of |component_id|.
+// RFC 5245
+// The value of |component_id| currently supported are 1 (RTP) and 2 (RTCP).
+// TODO: Decide the default destination in webrtcsession and
+// pass it down via SessionDescription.
+static bool GetDefaultDestination(const std::vector<Candidate>& candidates,
+    int component_id, std::string* port, std::string* ip) {
+  *port = kDefaultPort;
+  *ip = kDefaultAddress;
+  int current_preference = kPreferenceUnknown;
+  for (std::vector<Candidate>::const_iterator it = candidates.begin();
+       it != candidates.end(); ++it) {
+    if (it->component() != component_id) {
+      continue;
+    }
+    const int preference = GetCandidatePreferenceFromType(it->type());
+    // See if this candidate is more preferable then the current one.
+    if (preference <= current_preference) {
+      continue;
+    }
+    current_preference = preference;
+    *port = it->address().PortAsString();
+    *ip = it->address().ipaddr().ToString();
+  }
+  return true;
+}
+
+// Update the media default destination.
+static void UpdateMediaDefaultDestination(
+    const std::vector<Candidate>& candidates, std::string* mline) {
+  // RFC 4566
+  // m=<media> <port> <proto> <fmt> ...
+  std::vector<std::string> fields;
+  talk_base::split(*mline, kSdpDelimiterSpace, &fields);
+  if (fields.size() < 3) {
+    return;
+  }
+
+  bool is_rtp =
+      fields[2].empty() ||
+      talk_base::starts_with(fields[2].data(),
+                             cricket::kMediaProtocolRtpPrefix);
+
+  std::ostringstream os;
+  std::string rtp_port, rtp_ip;
+  if (GetDefaultDestination(candidates, ICE_CANDIDATE_COMPONENT_RTP,
+                            &rtp_port, &rtp_ip)) {
+    // Found default RTP candidate.
+    // RFC 5245
+    // The default candidates are added to the SDP as the default
+    // destination for media.  For streams based on RTP, this is done by
+    // placing the IP address and port of the RTP candidate into the c and m
+    // lines, respectively.
+
+    // Update the port in the m line.
+    // If this is a m-line with port equal to 0, we don't change it.
+    if (fields[1] != kMediaPortRejected) {
+      mline->replace(fields[0].size() + 1,
+                     fields[1].size(),
+                     rtp_port);
+    }
+    // Add the c line.
+    // RFC 4566
+    // c=<nettype> <addrtype> <connection-address>
+    InitLine(kLineTypeConnection, kConnectionNettype, &os);
+    os << " " << kConnectionAddrtype << " " << rtp_ip;
+    AddLine(os.str(), mline);
+  }
+
+  if (is_rtp) {
+    std::string rtcp_port, rtcp_ip;
+    if (GetDefaultDestination(candidates, ICE_CANDIDATE_COMPONENT_RTCP,
+                              &rtcp_port, &rtcp_ip)) {
+      // Found default RTCP candidate.
+      // RFC 5245
+      // If the agent is utilizing RTCP, it MUST encode the RTCP candidate
+      // using the a=rtcp attribute as defined in RFC 3605.
+
+      // RFC 3605
+      // rtcp-attribute =  "a=rtcp:" port  [nettype space addrtype space
+      // connection-address] CRLF
+      InitAttrLine(kAttributeRtcp, &os);
+      os << kSdpDelimiterColon
+         << rtcp_port << " "
+         << kConnectionNettype << " "
+         << kConnectionAddrtype << " "
+         << rtcp_ip;
+      AddLine(os.str(), mline);
+    }
+  }
+}
+
+// Get candidates according to the mline index from SessionDescriptionInterface.
+static void GetCandidatesByMindex(const SessionDescriptionInterface& desci,
+                                  int mline_index,
+                                  std::vector<Candidate>* candidates) {
+  if (!candidates) {
+    return;
+  }
+  const IceCandidateCollection* cc = desci.candidates(mline_index);
+  for (size_t i = 0; i < cc->count(); ++i) {
+    const IceCandidateInterface* candidate = cc->at(i);
+    candidates->push_back(candidate->candidate());
+  }
+}
+
+std::string SdpSerialize(const JsepSessionDescription& jdesc) {
+  std::string sdp = SdpSerializeSessionDescription(jdesc);
+
+  std::string sdp_with_candidates;
+  size_t pos = 0;
+  std::string line;
+  int mline_index = -1;
+  while (GetLine(sdp, &pos, &line)) {
+    if (IsLineType(line, kLineTypeMedia)) {
+      ++mline_index;
+      std::vector<Candidate> candidates;
+      GetCandidatesByMindex(jdesc, mline_index, &candidates);
+      // Media line may append other lines inside the
+      // UpdateMediaDefaultDestination call, so add the kLineBreak here first.
+      line.append(kLineBreak);
+      UpdateMediaDefaultDestination(candidates, &line);
+      sdp_with_candidates.append(line);
+      // Build the a=candidate lines.
+      BuildCandidate(candidates, &sdp_with_candidates);
+    } else {
+      // Copy old line to new sdp without change.
+      AddLine(line, &sdp_with_candidates);
+    }
+  }
+  sdp = sdp_with_candidates;
+
+  return sdp;
+}
+
+std::string SdpSerializeSessionDescription(
+    const JsepSessionDescription& jdesc) {
+  const cricket::SessionDescription* desc = jdesc.description();
+  if (!desc) {
+    return "";
+  }
+
+  std::string message;
+
+  // Session Description.
+  AddLine(kSessionVersion, &message);
+  // Session Origin
+  // RFC 4566
+  // o=<username> <sess-id> <sess-version> <nettype> <addrtype>
+  // <unicast-address>
+  std::ostringstream os;
+  InitLine(kLineTypeOrigin, kSessionOriginUsername, &os);
+  const std::string session_id = jdesc.session_id().empty() ?
+      kSessionOriginSessionId : jdesc.session_id();
+  const std::string session_version = jdesc.session_version().empty() ?
+      kSessionOriginSessionVersion : jdesc.session_version();
+  os << " " << session_id << " " << session_version << " "
+     << kSessionOriginNettype << " " << kSessionOriginAddrtype << " "
+     << kSessionOriginAddress;
+  AddLine(os.str(), &message);
+  AddLine(kSessionName, &message);
+
+  // Time Description.
+  AddLine(kTimeDescription, &message);
+
+  // Group
+  if (desc->HasGroup(cricket::GROUP_TYPE_BUNDLE)) {
+    std::string group_line = kAttrGroup;
+    const cricket::ContentGroup* group =
+        desc->GetGroupByName(cricket::GROUP_TYPE_BUNDLE);
+    ASSERT(group != NULL);
+    const cricket::ContentNames& content_names = group->content_names();
+    for (cricket::ContentNames::const_iterator it = content_names.begin();
+         it != content_names.end(); ++it) {
+      group_line.append(" ");
+      group_line.append(*it);
+    }
+    AddLine(group_line, &message);
+  }
+
+  // MediaStream semantics
+  InitAttrLine(kAttributeMsidSemantics, &os);
+  os << kSdpDelimiterColon << " " << kMediaStreamSemantic;
+  std::set<std::string> media_stream_labels;
+  const ContentInfo* audio_content = GetFirstAudioContent(desc);
+  if (audio_content)
+    GetMediaStreamLabels(audio_content, &media_stream_labels);
+  const ContentInfo* video_content = GetFirstVideoContent(desc);
+  if (video_content)
+    GetMediaStreamLabels(video_content, &media_stream_labels);
+  for (std::set<std::string>::const_iterator it =
+      media_stream_labels.begin(); it != media_stream_labels.end(); ++it) {
+    os << " " << *it;
+  }
+  AddLine(os.str(), &message);
+
+  if (audio_content) {
+    BuildMediaDescription(audio_content,
+                          desc->GetTransportInfoByName(audio_content->name),
+                          cricket::MEDIA_TYPE_AUDIO, &message);
+  }
+
+
+  if (video_content) {
+    BuildMediaDescription(video_content,
+                          desc->GetTransportInfoByName(video_content->name),
+                          cricket::MEDIA_TYPE_VIDEO, &message);
+  }
+
+  const ContentInfo* data_content = GetFirstDataContent(desc);
+  if (data_content) {
+    BuildMediaDescription(data_content,
+                          desc->GetTransportInfoByName(data_content->name),
+                          cricket::MEDIA_TYPE_DATA, &message);
+  }
+
+
+  return message;
+}
+
+// Serializes the passed in IceCandidateInterface to a SDP string.
+// candidate - The candidate to be serialized.
+std::string SdpSerializeCandidate(
+    const IceCandidateInterface& candidate) {
+  std::string message;
+  std::vector<cricket::Candidate> candidates;
+  candidates.push_back(candidate.candidate());
+  BuildCandidate(candidates, &message);
+  return message;
+}
+
+bool SdpDeserialize(const std::string& message,
+                    JsepSessionDescription* jdesc,
+                    SdpParseError* error) {
+  std::string session_id;
+  std::string session_version;
+  TransportDescription session_td(NS_JINGLE_ICE_UDP, Candidates());
+  RtpHeaderExtensions session_extmaps;
+  cricket::SessionDescription* desc = new cricket::SessionDescription();
+  std::vector<JsepIceCandidate*> candidates;
+  size_t current_pos = 0;
+  bool supports_msid = false;
+
+  // Session Description
+  if (!ParseSessionDescription(message, &current_pos, &session_id,
+                               &session_version, &supports_msid, &session_td,
+                               &session_extmaps, desc, error)) {
+    delete desc;
+    return false;
+  }
+
+  // Media Description
+  if (!ParseMediaDescription(message, session_td, session_extmaps,
+                             supports_msid, &current_pos, desc, &candidates,
+                             error)) {
+    delete desc;
+    for (std::vector<JsepIceCandidate*>::const_iterator
+         it = candidates.begin(); it != candidates.end(); ++it) {
+      delete *it;
+    }
+    return false;
+  }
+
+  jdesc->Initialize(desc, session_id, session_version);
+
+  for (std::vector<JsepIceCandidate*>::const_iterator
+       it = candidates.begin(); it != candidates.end(); ++it) {
+    jdesc->AddCandidate(*it);
+    delete *it;
+  }
+  return true;
+}
+
+bool SdpDeserializeCandidate(const std::string& message,
+                             JsepIceCandidate* jcandidate,
+                             SdpParseError* error) {
+  ASSERT(jcandidate != NULL);
+  Candidate candidate;
+  if (!ParseCandidate(message, &candidate, error, true)) {
+    return false;
+  }
+  jcandidate->SetCandidate(candidate);
+  return true;
+}
+
+bool ParseCandidate(const std::string& message, Candidate* candidate,
+                    SdpParseError* error, bool is_raw) {
+  ASSERT(candidate != NULL);
+
+  // Get the first line from |message|.
+  std::string first_line;
+  GetFirstLine(message, &first_line);
+
+  size_t start_pos = kLinePrefixLength;  // Starting position to parse.
+  if (IsRawCandidate(first_line)) {
+    // From WebRTC draft section 4.8.1.1 candidate-attribute will be
+    // just candidate:<candidate> not a=candidate:<blah>CRLF
+    start_pos = 0;
+  } else if (!IsLineType(first_line, kLineTypeAttributes) ||
+             !HasAttribute(first_line, kAttributeCandidate)) {
+    // Must start with a=candidate line.
+    // Expecting to be of the format a=candidate:<blah>CRLF.
+    if (is_raw) {
+      std::ostringstream description;
+      description << "Expect line: "
+                  << kAttributeCandidate
+                  << ":" << "<candidate-str>";
+      return ParseFailed(first_line, 0, description.str(), error);
+    } else {
+      return ParseFailedExpectLine(first_line, 0, kLineTypeAttributes,
+                                   kAttributeCandidate, error);
+    }
+  }
+
+  std::vector<std::string> fields;
+  talk_base::split(first_line.substr(start_pos),
+                   kSdpDelimiterSpace, &fields);
+  // RFC 5245
+  // 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)
+  const size_t expected_min_fields = 8;
+  if (fields.size() < expected_min_fields ||
+      (fields[6] != kAttributeCandidateTyp)) {
+    return ParseFailedExpectMinFieldNum(first_line, expected_min_fields, error);
+  }
+  std::string foundation;
+  if (!GetValue(fields[0], kAttributeCandidate, &foundation, error)) {
+    return false;
+  }
+  const int component_id = talk_base::FromString<int>(fields[1]);
+  const std::string transport = fields[2];
+  const uint32 priority = talk_base::FromString<uint32>(fields[3]);
+  const std::string connection_address = fields[4];
+  const int port = talk_base::FromString<int>(fields[5]);
+  SocketAddress address(connection_address, port);
+
+  cricket::ProtocolType protocol;
+  if (!StringToProto(transport.c_str(), &protocol)) {
+    return ParseFailed(first_line, "Unsupported transport type.", error);
+  }
+
+  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 {
+    return ParseFailed(first_line, "Unsupported candidate type.", error);
+  }
+
+  size_t current_position = expected_min_fields;
+  SocketAddress related_address;
+  // The 2 optional fields for related address
+  // [raddr <connection-address>] [rport <port>]
+  if (fields.size() >= (current_position + 2) &&
+      fields[current_position] == kAttributeCandidateRaddr) {
+    related_address.SetIP(fields[++current_position]);
+    ++current_position;
+  }
+  if (fields.size() >= (current_position + 2) &&
+      fields[current_position] == kAttributeCandidateRport) {
+    related_address.SetPort(
+        talk_base::FromString<int>(fields[++current_position]));
+    ++current_position;
+  }
+
+  // Extension
+  // Empty string as the candidate username and password.
+  // Will be updated later with the ice-ufrag and ice-pwd.
+  // TODO: Remove the username/password extension, which is currently
+  // kept for backwards compatibility.
+  std::string username;
+  std::string password;
+  uint32 generation = 0;
+  for (size_t i = current_position; i + 1 < fields.size(); ++i) {
+    // RFC 5245
+    // *(SP extension-att-name SP extension-att-value)
+    if (fields[i] == kAttributeCandidateGeneration) {
+      generation = talk_base::FromString<uint32>(fields[++i]);
+    } else if (fields[i] == kAttributeCandidateUsername) {
+      username = fields[++i];
+    } else if (fields[i] == kAttributeCandidatePassword) {
+      password = fields[++i];
+    } else {
+      // Skip the unknown extension.
+      ++i;
+    }
+  }
+
+  // Empty string as the candidate id and network name.
+  const std::string id;
+  const std::string network_name;
+  *candidate = Candidate(id, component_id, cricket::ProtoToString(protocol),
+      address, priority, username, password, candidate_type, network_name,
+      generation, foundation);
+  candidate->set_related_address(related_address);
+  return true;
+}
+
+bool ParseIceOptions(const std::string& line,
+                     std::vector<std::string>* transport_options,
+                     SdpParseError* error) {
+  std::string ice_options;
+  if (!GetValue(line, kAttributeIceOption, &ice_options, error)) {
+    return false;
+  }
+  std::vector<std::string> fields;
+  talk_base::split(ice_options, kSdpDelimiterSpace, &fields);
+  for (size_t i = 0; i < fields.size(); ++i) {
+    transport_options->push_back(fields[i]);
+  }
+  return true;
+}
+
+bool ParseExtmap(const std::string& line, RtpHeaderExtension* extmap,
+                 SdpParseError* error) {
+  // RFC 5285
+  // a=extmap:<value>["/"<direction>] <URI> <extensionattributes>
+  std::vector<std::string> fields;
+  talk_base::split(line.substr(kLinePrefixLength),
+                   kSdpDelimiterSpace, &fields);
+  const size_t expected_min_fields = 2;
+  if (fields.size() < expected_min_fields) {
+    return ParseFailedExpectMinFieldNum(line, expected_min_fields, error);
+  }
+  std::string uri = fields[1];
+
+  std::string value_direction;
+  if (!GetValue(fields[0], kAttributeExtmap, &value_direction, error)) {
+    return false;
+  }
+  std::vector<std::string> sub_fields;
+  talk_base::split(value_direction, kSdpDelimiterSlash, &sub_fields);
+  int value = talk_base::FromString<int>(sub_fields[0]);
+
+  *extmap = RtpHeaderExtension(uri, value);
+  return true;
+}
+
+void BuildMediaDescription(const ContentInfo* content_info,
+                           const TransportInfo* transport_info,
+                           const MediaType media_type,
+                           std::string* message) {
+  ASSERT(message != NULL);
+  if (content_info == NULL || message == NULL) {
+    return;
+  }
+  // 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);
+
+  bool is_sctp = (media_desc->protocol() == cricket::kMediaProtocolDtlsSctp);
+
+  // RFC 4566
+  // 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 if (media_type == cricket::MEDIA_TYPE_DATA)
+    type = kMediaTypeData;
+  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));
+    }
+  } else if (media_type == cricket::MEDIA_TYPE_DATA) {
+    if (is_sctp) {
+      fmt.append(" ");
+      // TODO(jiayl): Replace the hard-coded string with the fmt read out of the
+      // ContentDescription.
+      fmt.append(talk_base::ToString<int>(kDefaultSctpFmt));
+    } else {
+      const DataContentDescription* data_desc =
+          static_cast<const DataContentDescription*>(media_desc);
+      for (std::vector<cricket::DataCodec>::const_iterator it =
+           data_desc->codecs().begin();
+           it != data_desc->codecs().end(); ++it) {
+        fmt.append(" ");
+        fmt.append(talk_base::ToString<int>(it->id));
+      }
+    }
+  }
+  // The fmt must never be empty. If no codecs are found, set the fmt attribute
+  // to 0.
+  if (fmt.empty()) {
+    fmt = " 0";
+  }
+
+  // The port number in the m line will be updated later when associate with
+  // the candidates.
+  // RFC 3264
+  // To reject an offered stream, the port number in the corresponding stream in
+  // the answer MUST be set to zero.
+  const std::string port = content_info->rejected ?
+      kMediaPortRejected : kDefaultPort;
+
+  talk_base::SSLFingerprint* fp = (transport_info) ?
+      transport_info->description.identity_fingerprint.get() : NULL;
+
+  InitLine(kLineTypeMedia, type, &os);
+  os << " " << port << " " << media_desc->protocol() << fmt;
+  AddLine(os.str(), message);
+
+  // Use the transport_info to build the media level ice-ufrag and ice-pwd.
+  if (transport_info) {
+    // RFC 5245
+    // ice-pwd-att           = "ice-pwd" ":" password
+    // ice-ufrag-att         = "ice-ufrag" ":" ufrag
+    // ice-ufrag
+    InitAttrLine(kAttributeIceUfrag, &os);
+    os << kSdpDelimiterColon << transport_info->description.ice_ufrag;
+    AddLine(os.str(), message);
+    // ice-pwd
+    InitAttrLine(kAttributeIcePwd, &os);
+    os << kSdpDelimiterColon << transport_info->description.ice_pwd;
+    AddLine(os.str(), message);
+
+    // draft-petithuguenin-mmusic-ice-attributes-level-03
+    BuildIceOptions(transport_info->description.transport_options, message);
+
+    // RFC 4572
+    // fingerprint-attribute  =
+    //   "fingerprint" ":" hash-func SP fingerprint
+    if (fp) {
+      // Insert the fingerprint attribute.
+      InitAttrLine(kAttributeFingerprint, &os);
+      os << kSdpDelimiterColon
+         << fp->algorithm << kSdpDelimiterSpace
+         << fp->GetRfc4572Fingerprint();
+
+      AddLine(os.str(), message);
+    }
+  }
+
+  // RFC 3388
+  // mid-attribute      = "a=mid:" identification-tag
+  // identification-tag = token
+  // Use the content name as the mid identification-tag.
+  InitAttrLine(kAttributeMid, &os);
+  os << kSdpDelimiterColon << content_info->name;
+  AddLine(os.str(), message);
+
+  if (is_sctp) {
+    BuildSctpContentAttributes(message);
+  } else {
+    BuildRtpContentAttributes(media_desc, media_type, message);
+  }
+}
+
+void BuildSctpContentAttributes(std::string* message) {
+  cricket::DataCodec sctp_codec(kDefaultSctpFmt, kDefaultSctpFmtProtocol, 0);
+  sctp_codec.SetParam(kCodecParamSctpProtocol, kDefaultSctpFmtProtocol);
+  sctp_codec.SetParam(kCodecParamSctpStreams, cricket::kMaxSctpSid + 1);
+  AddFmtpLine(sctp_codec, message);
+}
+
+void BuildRtpContentAttributes(
+    const MediaContentDescription* media_desc,
+    const MediaType media_type,
+    std::string* message) {
+  std::ostringstream os;
+  // RFC 5285
+  // a=extmap:<value>["/"<direction>] <URI> <extensionattributes>
+  // The definitions MUST be either all session level or all media level. This
+  // implementation uses all media level.
+  for (size_t i = 0; i < media_desc->rtp_header_extensions().size(); ++i) {
+    InitAttrLine(kAttributeExtmap, &os);
+    os << kSdpDelimiterColon << media_desc->rtp_header_extensions()[i].id
+       << kSdpDelimiterSpace << media_desc->rtp_header_extensions()[i].uri;
+    AddLine(os.str(), message);
+  }
+
+  // RFC 3264
+  // a=sendrecv || a=sendonly || a=sendrecv || a=inactive
+
+  cricket::MediaContentDirection direction = media_desc->direction();
+  if (media_desc->streams().empty() && direction == cricket::MD_SENDRECV) {
+    direction = cricket::MD_RECVONLY;
+  }
+
+  switch (direction) {
+    case cricket::MD_INACTIVE:
+      InitAttrLine(kAttributeInactive, &os);
+      break;
+    case cricket::MD_SENDONLY:
+      InitAttrLine(kAttributeSendOnly, &os);
+      break;
+    case cricket::MD_RECVONLY:
+      InitAttrLine(kAttributeRecvOnly, &os);
+      break;
+    case cricket::MD_SENDRECV:
+    default:
+      InitAttrLine(kAttributeSendRecv, &os);
+      break;
+  }
+  AddLine(os.str(), message);
+
+  // RFC 4566
+  // b=AS:<bandwidth>
+  if (media_desc->bandwidth() >= 1000) {
+    InitLine(kLineTypeSessionBandwidth, kApplicationSpecificMaximum, &os);
+    os << kSdpDelimiterColon << (media_desc->bandwidth() / 1000);
+    AddLine(os.str(), message);
+  }
+
+  // RFC 5761
+  // a=rtcp-mux
+  if (media_desc->rtcp_mux()) {
+    InitAttrLine(kAttributeRtcpMux, &os);
+    AddLine(os.str(), message);
+  }
+
+  // RFC 4568
+  // 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) {
+    InitAttrLine(kAttributeCrypto, &os);
+    os << kSdpDelimiterColon << it->tag << " " << it->cipher_suite << " "
+       << it->key_params;
+    if (!it->session_params.empty()) {
+      os << " " << it->session_params;
+    }
+    AddLine(os.str(), message);
+  }
+
+  // RFC 4566
+  // a=rtpmap:<payload type> <encoding name>/<clock rate>
+  // [/<encodingparameters>]
+  BuildRtpMap(media_desc, media_type, message);
+
+  // Specify latency for buffered mode.
+  // a=x-google-buffer-latency:<value>
+  if (media_desc->buffered_mode_latency() != cricket::kBufferedModeDisabled) {
+    std::ostringstream os;
+    InitAttrLine(kAttributeXGoogleBufferLatency, &os);
+    os << kSdpDelimiterColon << media_desc->buffered_mode_latency();
+    AddLine(os.str(), message);
+  }
+
+  for (StreamParamsVec::const_iterator track = media_desc->streams().begin();
+       track != media_desc->streams().end(); ++track) {
+    // 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 (track->sync_label.empty()) continue;
+
+    // Build the ssrc-group lines.
+    for (size_t i = 0; i < track->ssrc_groups.size(); ++i) {
+      // RFC 5576
+      // a=ssrc-group:<semantics> <ssrc-id> ...
+      if (track->ssrc_groups[i].ssrcs.empty()) {
+        continue;
+      }
+      std::ostringstream os;
+      InitAttrLine(kAttributeSsrcGroup, &os);
+      os << kSdpDelimiterColon << track->ssrc_groups[i].semantics;
+      std::vector<uint32>::const_iterator ssrc =
+          track->ssrc_groups[i].ssrcs.begin();
+      for (; ssrc != track->ssrc_groups[i].ssrcs.end(); ++ssrc) {
+        os << kSdpDelimiterSpace << talk_base::ToString<uint32>(*ssrc);
+      }
+      AddLine(os.str(), message);
+    }
+    // Build the ssrc lines for each ssrc.
+    for (size_t i = 0; i < track->ssrcs.size(); ++i) {
+      uint32 ssrc = track->ssrcs[i];
+      // RFC 5576
+      // a=ssrc:<ssrc-id> cname:<value>
+      AddSsrcLine(ssrc, kSsrcAttributeCname,
+                  track->cname, message);
+
+      // draft-alvestrand-mmusic-msid-00
+      // a=ssrc:<ssrc-id> msid:identifier [appdata]
+      // The appdata consists of the "id" attribute of a MediaStreamTrack, which
+      // is corresponding to the "name" attribute of StreamParams.
+      std::string appdata = track->id;
+      std::ostringstream os;
+      InitAttrLine(kAttributeSsrc, &os);
+      os << kSdpDelimiterColon << ssrc << kSdpDelimiterSpace
+         << kSsrcAttributeMsid << kSdpDelimiterColon << track->sync_label
+         << kSdpDelimiterSpace << appdata;
+      AddLine(os.str(), message);
+
+      // TODO(ronghuawu): Remove below code which is for backward compatibility.
+      // draft-alvestrand-rtcweb-mid-01
+      // a=ssrc:<ssrc-id> mslabel:<value>
+      // The label isn't yet defined.
+      // a=ssrc:<ssrc-id> label:<value>
+      AddSsrcLine(ssrc, kSsrcAttributeMslabel, track->sync_label, message);
+      AddSsrcLine(ssrc, kSSrcAttributeLabel, track->id, message);
+    }
+  }
+}
+
+void WriteFmtpHeader(int payload_type, std::ostringstream* os) {
+  // fmtp header: a=fmtp:|payload_type| <parameters>
+  // Add a=fmtp
+  InitAttrLine(kAttributeFmtp, os);
+  // Add :|payload_type|
+  *os << kSdpDelimiterColon << payload_type;
+}
+
+void WriteRtcpFbHeader(int payload_type, std::ostringstream* os) {
+  // rtcp-fb header: a=rtcp-fb:|payload_type|
+  // <parameters>/<ccm <ccm_parameters>>
+  // Add a=rtcp-fb
+  InitAttrLine(kAttributeRtcpFb, os);
+  // Add :
+  *os << kSdpDelimiterColon;
+  if (payload_type == kWildcardPayloadType) {
+    *os << "*";
+  } else {
+    *os << payload_type;
+  }
+}
+
+void WriteFmtpParameter(const std::string& parameter_name,
+                        const std::string& parameter_value,
+                        std::ostringstream* os) {
+  // fmtp parameters: |parameter_name|=|parameter_value|
+  *os << parameter_name << kSdpDelimiterEqual << parameter_value;
+}
+
+void WriteFmtpParameters(const cricket::CodecParameterMap& parameters,
+                         std::ostringstream* os) {
+  for (cricket::CodecParameterMap::const_iterator fmtp = parameters.begin();
+       fmtp != parameters.end(); ++fmtp) {
+    // Each new parameter, except the first one starts with ";" and " ".
+    if (fmtp != parameters.begin()) {
+      *os << kSdpDelimiterSemicolon;
+    }
+    *os << kSdpDelimiterSpace;
+    WriteFmtpParameter(fmtp->first, fmtp->second, os);
+  }
+}
+
+bool IsFmtpParam(const std::string& name) {
+  const char* kFmtpParams[] = {
+    kCodecParamMinPTime, kCodecParamSPropStereo,
+    kCodecParamStereo, kCodecParamUseInbandFec,
+    kCodecParamMaxBitrate, kCodecParamMinBitrate, kCodecParamMaxQuantization,
+    kCodecParamSctpProtocol, kCodecParamSctpStreams
+  };
+  for (size_t i = 0; i < ARRAY_SIZE(kFmtpParams); ++i) {
+    if (_stricmp(name.c_str(), kFmtpParams[i]) == 0) {
+      return true;
+    }
+  }
+  return false;
+}
+
+// Retreives fmtp parameters from |params|, which may contain other parameters
+// as well, and puts them in |fmtp_parameters|.
+void GetFmtpParams(const cricket::CodecParameterMap& params,
+                   cricket::CodecParameterMap* fmtp_parameters) {
+  for (cricket::CodecParameterMap::const_iterator iter = params.begin();
+       iter != params.end(); ++iter) {
+    if (IsFmtpParam(iter->first)) {
+      (*fmtp_parameters)[iter->first] = iter->second;
+    }
+  }
+}
+
+template <class T>
+void AddFmtpLine(const T& codec, std::string* message) {
+  cricket::CodecParameterMap fmtp_parameters;
+  GetFmtpParams(codec.params, &fmtp_parameters);
+  if (fmtp_parameters.empty()) {
+    // No need to add an fmtp if it will have no (optional) parameters.
+    return;
+  }
+  std::ostringstream os;
+  WriteFmtpHeader(codec.id, &os);
+  WriteFmtpParameters(fmtp_parameters, &os);
+  AddLine(os.str(), message);
+  return;
+}
+
+template <class T>
+void AddRtcpFbLines(const T& codec, std::string* message) {
+  for (std::vector<cricket::FeedbackParam>::const_iterator iter =
+           codec.feedback_params.params().begin();
+       iter != codec.feedback_params.params().end(); ++iter) {
+    std::ostringstream os;
+    WriteRtcpFbHeader(codec.id, &os);
+    os << " " << iter->id();
+    if (!iter->param().empty()) {
+      os << " " << iter->param();
+    }
+    AddLine(os.str(), message);
+  }
+}
+
+bool GetMinValue(const std::vector<int>& values, int* value) {
+  if (values.empty()) {
+    return false;
+  }
+  std::vector<int>::const_iterator found =
+      std::min_element(values.begin(), values.end());
+  *value = *found;
+  return true;
+}
+
+bool GetParameter(const std::string& name,
+                  const cricket::CodecParameterMap& params, int* value) {
+  std::map<std::string, std::string>::const_iterator found =
+      params.find(name);
+  if (found == params.end()) {
+    return false;
+  }
+  *value = talk_base::FromString<int>(found->second);
+  return true;
+}
+
+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) {
+      // RFC 4566
+      // a=rtpmap:<payload type> <encoding name>/<clock rate>
+      // [/<encodingparameters>]
+      if (it->id != kWildcardPayloadType) {
+        InitAttrLine(kAttributeRtpmap, &os);
+        os << kSdpDelimiterColon << it->id << " " << it->name
+         << "/" << kDefaultVideoClockrate;
+        AddLine(os.str(), message);
+      }
+      AddRtcpFbLines(*it, message);
+      AddFmtpLine(*it, message);
+    }
+  } else if (media_type == cricket::MEDIA_TYPE_AUDIO) {
+    const AudioContentDescription* audio_desc =
+        static_cast<const AudioContentDescription*>(media_desc);
+    std::vector<int> ptimes;
+    std::vector<int> maxptimes;
+    int max_minptime = 0;
+    for (std::vector<cricket::AudioCodec>::const_iterator it =
+             audio_desc->codecs().begin();
+         it != audio_desc->codecs().end(); ++it) {
+      ASSERT(!it->name.empty());
+      // RFC 4566
+      // a=rtpmap:<payload type> <encoding name>/<clock rate>
+      // [/<encodingparameters>]
+      InitAttrLine(kAttributeRtpmap, &os);
+      os << kSdpDelimiterColon << it->id << " ";
+      os << it->name << "/" << it->clockrate;
+      if (it->channels != 1) {
+        os << "/" << it->channels;
+      }
+      AddLine(os.str(), message);
+      AddRtcpFbLines(*it, message);
+      AddFmtpLine(*it, message);
+      int minptime = 0;
+      if (GetParameter(kCodecParamMinPTime, it->params, &minptime)) {
+        max_minptime = std::max(minptime, max_minptime);
+      }
+      int ptime;
+      if (GetParameter(kCodecParamPTime, it->params, &ptime)) {
+        ptimes.push_back(ptime);
+      }
+      int maxptime;
+      if (GetParameter(kCodecParamMaxPTime, it->params, &maxptime)) {
+        maxptimes.push_back(maxptime);
+      }
+    }
+    // Populate the maxptime attribute with the smallest maxptime of all codecs
+    // under the same m-line.
+    int min_maxptime = INT_MAX;
+    if (GetMinValue(maxptimes, &min_maxptime)) {
+      AddAttributeLine(kCodecParamMaxPTime, min_maxptime, message);
+    }
+    ASSERT(min_maxptime > max_minptime);
+    // Populate the ptime attribute with the smallest ptime or the largest
+    // minptime, whichever is the largest, for all codecs under the same m-line.
+    int ptime = INT_MAX;
+    if (GetMinValue(ptimes, &ptime)) {
+      ptime = std::min(ptime, min_maxptime);
+      ptime = std::max(ptime, max_minptime);
+      AddAttributeLine(kCodecParamPTime, ptime, message);
+    }
+  } else if (media_type == cricket::MEDIA_TYPE_DATA) {
+    const DataContentDescription* data_desc =
+        static_cast<const DataContentDescription*>(media_desc);
+    for (std::vector<cricket::DataCodec>::const_iterator it =
+         data_desc->codecs().begin();
+         it != data_desc->codecs().end(); ++it) {
+      // RFC 4566
+      // a=rtpmap:<payload type> <encoding name>/<clock rate>
+      // [/<encodingparameters>]
+      InitAttrLine(kAttributeRtpmap, &os);
+      os << kSdpDelimiterColon << it->id << " "
+         << it->name << "/" << it->clockrate;
+      AddLine(os.str(), message);
+    }
+  }
+}
+
+void BuildCandidate(const std::vector<Candidate>& candidates,
+                    std::string* message) {
+  std::ostringstream os;
+
+  for (std::vector<Candidate>::const_iterator it = candidates.begin();
+       it != candidates.end(); ++it) {
+    // RFC 5245
+    // 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)
+    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);
+    }
+
+    InitAttrLine(kAttributeCandidate, &os);
+    os << kSdpDelimiterColon
+       << it->foundation() << " " << it->component() << " "
+       << it->protocol() << " " << it->priority() << " "
+       << it->address().ipaddr().ToString() << " "
+       << it->address().PortAsString() << " "
+       << kAttributeCandidateTyp << " " << type << " ";
+
+    // Related address
+    if (!it->related_address().IsNil()) {
+      os << kAttributeCandidateRaddr << " "
+         << it->related_address().ipaddr().ToString() << " "
+         << kAttributeCandidateRport << " "
+         << it->related_address().PortAsString() << " ";
+    }
+
+    // Extensions
+    os << kAttributeCandidateGeneration << " " << it->generation();
+
+    AddLine(os.str(), message);
+  }
+}
+
+void BuildIceOptions(const std::vector<std::string>& transport_options,
+                     std::string* message) {
+  if (!transport_options.empty()) {
+    std::ostringstream os;
+    InitAttrLine(kAttributeIceOption, &os);
+    os << kSdpDelimiterColon << transport_options[0];
+    for (size_t i = 1; i < transport_options.size(); ++i) {
+      os << kSdpDelimiterSpace << transport_options[i];
+    }
+    AddLine(os.str(), message);
+  }
+}
+
+bool ParseSessionDescription(const std::string& message, size_t* pos,
+                             std::string* session_id,
+                             std::string* session_version,
+                             bool* supports_msid,
+                             TransportDescription* session_td,
+                             RtpHeaderExtensions* session_extmaps,
+                             cricket::SessionDescription* desc,
+                             SdpParseError* error) {
+  std::string line;
+
+  // RFC 4566
+  // v=  (protocol version)
+  if (!GetLineWithType(message, pos, &line, kLineTypeVersion)) {
+    return ParseFailedExpectLine(message, *pos, kLineTypeVersion,
+                                 std::string(), error);
+  }
+  // RFC 4566
+  // o=<username> <sess-id> <sess-version> <nettype> <addrtype>
+  // <unicast-address>
+  if (!GetLineWithType(message, pos, &line, kLineTypeOrigin)) {
+    return ParseFailedExpectLine(message, *pos, kLineTypeOrigin,
+                                 std::string(), error);
+  }
+  std::vector<std::string> fields;
+  talk_base::split(line.substr(kLinePrefixLength),
+                   kSdpDelimiterSpace, &fields);
+  const size_t expected_fields = 6;
+  if (fields.size() != expected_fields) {
+    return ParseFailedExpectFieldNum(line, expected_fields, error);
+  }
+  *session_id = fields[1];
+  *session_version = fields[2];
+
+  // RFC 4566
+  // s=  (session name)
+  if (!GetLineWithType(message, pos, &line, kLineTypeSessionName)) {
+    return ParseFailedExpectLine(message, *pos, kLineTypeSessionName,
+                                 std::string(), error);
+  }
+
+  // Optional lines
+  // Those are the optional lines, so shouldn't return false if not present.
+  // RFC 4566
+  // i=* (session information)
+  GetLineWithType(message, pos, &line, kLineTypeSessionInfo);
+
+  // RFC 4566
+  // u=* (URI of description)
+  GetLineWithType(message, pos, &line, kLineTypeSessionUri);
+
+  // RFC 4566
+  // e=* (email address)
+  GetLineWithType(message, pos, &line, kLineTypeSessionEmail);
+
+  // RFC 4566
+  // p=* (phone number)
+  GetLineWithType(message, pos, &line, kLineTypeSessionPhone);
+
+  // RFC 4566
+  // c=* (connection information -- not required if included in
+  //      all media)
+  GetLineWithType(message, pos, &line, kLineTypeConnection);
+
+  // RFC 4566
+  // b=* (zero or more bandwidth information lines)
+  while (GetLineWithType(message, pos, &line, kLineTypeSessionBandwidth)) {
+    // By pass zero or more b lines.
+  }
+
+  // RFC 4566
+  // 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 (!GetLineWithType(message, pos, &line, kLineTypeTiming)) {
+    return ParseFailedExpectLine(message, *pos, kLineTypeTiming, std::string(),
+                                 error);
+  }
+
+  while (GetLineWithType(message, pos, &line, kLineTypeRepeatTimes)) {
+    // By pass zero or more r lines.
+  }
+
+  // Go through the rest of the time descriptions
+  while (GetLineWithType(message, pos, &line, kLineTypeTiming)) {
+    while (GetLineWithType(message, pos, &line, kLineTypeRepeatTimes)) {
+      // By pass zero or more r lines.
+    }
+  }
+
+  // RFC 4566
+  // z=* (time zone adjustments)
+  GetLineWithType(message, pos, &line, kLineTypeTimeZone);
+
+  // RFC 4566
+  // k=* (encryption key)
+  GetLineWithType(message, pos, &line, kLineTypeEncryptionKey);
+
+  // RFC 4566
+  // a=* (zero or more session attribute lines)
+  while (GetLineWithType(message, pos, &line, kLineTypeAttributes)) {
+    if (HasAttribute(line, kAttributeGroup)) {
+      if (!ParseGroupAttribute(line, desc, error)) {
+        return false;
+      }
+    } else if (HasAttribute(line, kAttributeIceUfrag)) {
+      if (!GetValue(line, kAttributeIceUfrag,
+                    &(session_td->ice_ufrag), error)) {
+        return false;
+      }
+    } else if (HasAttribute(line, kAttributeIcePwd)) {
+      if (!GetValue(line, kAttributeIcePwd, &(session_td->ice_pwd), error)) {
+        return false;
+      }
+    } else if (HasAttribute(line, kAttributeIceLite)) {
+      session_td->ice_mode = cricket::ICEMODE_LITE;
+    } else if (HasAttribute(line, kAttributeIceOption)) {
+      if (!ParseIceOptions(line, &(session_td->transport_options), error)) {
+        return false;
+      }
+    } else if (HasAttribute(line, kAttributeFingerprint)) {
+      if (session_td->identity_fingerprint.get()) {
+        return ParseFailed(
+            line,
+            "Can't have multiple fingerprint attributes at the same level.",
+            error);
+      }
+      talk_base::SSLFingerprint* fingerprint = NULL;
+      if (!ParseFingerprintAttribute(line, &fingerprint, error)) {
+        return false;
+      }
+      session_td->identity_fingerprint.reset(fingerprint);
+    } else if (HasAttribute(line, kAttributeMsidSemantics)) {
+      std::string semantics;
+      if (!GetValue(line, kAttributeMsidSemantics, &semantics, error)) {
+        return false;
+      }
+      *supports_msid = CaseInsensitiveFind(semantics, kMediaStreamSemantic);
+    } else if (HasAttribute(line, kAttributeExtmap)) {
+      RtpHeaderExtension extmap;
+      if (!ParseExtmap(line, &extmap, error)) {
+        return false;
+      }
+      session_extmaps->push_back(extmap);
+    }
+  }
+
+  return true;
+}
+
+bool ParseGroupAttribute(const std::string& line,
+                         cricket::SessionDescription* desc,
+                         SdpParseError* error) {
+  ASSERT(desc != NULL);
+
+  // RFC 5888 and draft-holmberg-mmusic-sdp-bundle-negotiation-00
+  // a=group:BUNDLE video voice
+  std::vector<std::string> fields;
+  talk_base::split(line.substr(kLinePrefixLength),
+                   kSdpDelimiterSpace, &fields);
+  std::string semantics;
+  if (!GetValue(fields[0], kAttributeGroup, &semantics, error)) {
+    return false;
+  }
+  cricket::ContentGroup group(semantics);
+  for (size_t i = 1; i < fields.size(); ++i) {
+    group.AddContentName(fields[i]);
+  }
+  desc->AddGroup(group);
+  return true;
+}
+
+static bool ParseFingerprintAttribute(const std::string& line,
+                                      talk_base::SSLFingerprint** fingerprint,
+                                      SdpParseError* error) {
+  if (!IsLineType(line, kLineTypeAttributes) ||
+      !HasAttribute(line, kAttributeFingerprint)) {
+    return ParseFailedExpectLine(line, 0, kLineTypeAttributes,
+                                 kAttributeFingerprint, error);
+  }
+
+  std::vector<std::string> fields;
+  talk_base::split(line.substr(kLinePrefixLength),
+                   kSdpDelimiterSpace, &fields);
+  const size_t expected_fields = 2;
+  if (fields.size() != expected_fields) {
+    return ParseFailedExpectFieldNum(line, expected_fields, error);
+  }
+
+  // The first field here is "fingerprint:<hash>.
+  std::string algorithm;
+  if (!GetValue(fields[0], kAttributeFingerprint, &algorithm, error)) {
+    return false;
+  }
+
+  // Downcase the algorithm. Note that we don't need to downcase the
+  // fingerprint because hex_decode can handle upper-case.
+  std::transform(algorithm.begin(), algorithm.end(), algorithm.begin(),
+                 ::tolower);
+
+  // The second field is the digest value. De-hexify it.
+  *fingerprint = talk_base::SSLFingerprint::CreateFromRfc4572(
+      algorithm, fields[1]);
+  if (!*fingerprint) {
+    return ParseFailed(line,
+                       "Failed to create fingerprint from the digest.",
+                       error);
+  }
+
+  return true;
+}
+
+// RFC 3551
+//  PT   encoding    media type  clock rate   channels
+//                      name                    (Hz)
+//  0    PCMU        A            8,000       1
+//  1    reserved    A
+//  2    reserved    A
+//  3    GSM         A            8,000       1
+//  4    G723        A            8,000       1
+//  5    DVI4        A            8,000       1
+//  6    DVI4        A           16,000       1
+//  7    LPC         A            8,000       1
+//  8    PCMA        A            8,000       1
+//  9    G722        A            8,000       1
+//  10   L16         A           44,100       2
+//  11   L16         A           44,100       1
+//  12   QCELP       A            8,000       1
+//  13   CN          A            8,000       1
+//  14   MPA         A           90,000       (see text)
+//  15   G728        A            8,000       1
+//  16   DVI4        A           11,025       1
+//  17   DVI4        A           22,050       1
+//  18   G729        A            8,000       1
+struct StaticPayloadAudioCodec {
+  const char* name;
+  int clockrate;
+  int channels;
+};
+static const StaticPayloadAudioCodec kStaticPayloadAudioCodecs[] = {
+  { "PCMU", 8000, 1 },
+  { "reserved", 0, 0 },
+  { "reserved", 0, 0 },
+  { "GSM", 8000, 1 },
+  { "G723", 8000, 1 },
+  { "DVI4", 8000, 1 },
+  { "DVI4", 16000, 1 },
+  { "LPC", 8000, 1 },
+  { "PCMA", 8000, 1 },
+  { "G722", 8000, 1 },
+  { "L16", 44100, 2 },
+  { "L16", 44100, 1 },
+  { "QCELP", 8000, 1 },
+  { "CN", 8000, 1 },
+  { "MPA", 90000, 1 },
+  { "G728", 8000, 1 },
+  { "DVI4", 11025, 1 },
+  { "DVI4", 22050, 1 },
+  { "G729", 8000, 1 },
+};
+
+void MaybeCreateStaticPayloadAudioCodecs(
+    const std::vector<int>& fmts, AudioContentDescription* media_desc) {
+  if (!media_desc) {
+    return;
+  }
+  int preference = fmts.size();
+  std::vector<int>::const_iterator it = fmts.begin();
+  bool add_new_codec = false;
+  for (; it != fmts.end(); ++it) {
+    int payload_type = *it;
+    if (!media_desc->HasCodec(payload_type) &&
+        payload_type >= 0 &&
+        payload_type < ARRAY_SIZE(kStaticPayloadAudioCodecs)) {
+      std::string encoding_name = kStaticPayloadAudioCodecs[payload_type].name;
+      int clock_rate = kStaticPayloadAudioCodecs[payload_type].clockrate;
+      int channels = kStaticPayloadAudioCodecs[payload_type].channels;
+      media_desc->AddCodec(cricket::AudioCodec(payload_type, encoding_name,
+                                               clock_rate, 0, channels,
+                                               preference));
+      add_new_codec = true;
+    }
+    --preference;
+  }
+  if (add_new_codec) {
+    media_desc->SortCodecs();
+  }
+}
+
+template <class C>
+static C* ParseContentDescription(const std::string& message,
+                                  const MediaType media_type,
+                                  int mline_index,
+                                  const std::string& protocol,
+                                  const std::vector<int>& codec_preference,
+                                  size_t* pos,
+                                  std::string* content_name,
+                                  TransportDescription* transport,
+                                  std::vector<JsepIceCandidate*>* candidates,
+                                  webrtc::SdpParseError* error) {
+  C* media_desc = new C();
+  switch (media_type) {
+    case cricket::MEDIA_TYPE_AUDIO:
+      *content_name = cricket::CN_AUDIO;
+      break;
+    case cricket::MEDIA_TYPE_VIDEO:
+      *content_name = cricket::CN_VIDEO;
+      break;
+    case cricket::MEDIA_TYPE_DATA:
+      *content_name = cricket::CN_DATA;
+      break;
+    default:
+      ASSERT(false);
+      break;
+  }
+  if (!ParseContent(message, media_type, mline_index, protocol,
+                    codec_preference, pos, content_name,
+                    media_desc, transport, candidates, error)) {
+    delete media_desc;
+    return NULL;
+  }
+  // Sort the codecs according to the m-line fmt list.
+  media_desc->SortCodecs();
+  return media_desc;
+}
+
+bool ParseMediaDescription(const std::string& message,
+                           const TransportDescription& session_td,
+                           const RtpHeaderExtensions& session_extmaps,
+                           bool supports_msid,
+                           size_t* pos,
+                           cricket::SessionDescription* desc,
+                           std::vector<JsepIceCandidate*>* candidates,
+                           SdpParseError* error) {
+  ASSERT(desc != NULL);
+  std::string line;
+  int mline_index = -1;
+
+  // Zero or more media descriptions
+  // RFC 4566
+  // m=<media> <port> <proto> <fmt>
+  while (GetLineWithType(message, pos, &line, kLineTypeMedia)) {
+    ++mline_index;
+
+    std::vector<std::string> fields;
+    talk_base::split(line.substr(kLinePrefixLength),
+                     kSdpDelimiterSpace, &fields);
+    const size_t expected_min_fields = 4;
+    if (fields.size() < expected_min_fields) {
+      return ParseFailedExpectMinFieldNum(line, expected_min_fields, error);
+    }
+    bool rejected = false;
+    // RFC 3264
+    // To reject an offered stream, the port number in the corresponding stream
+    // in the answer MUST be set to zero.
+    if (fields[1] == kMediaPortRejected) {
+      rejected = true;
+    }
+
+    std::string protocol = fields[2];
+    bool is_sctp = (protocol == cricket::kMediaProtocolDtlsSctp);
+
+    // <fmt>
+    std::vector<int> codec_preference;
+    for (size_t j = 3 ; j < fields.size(); ++j) {
+      codec_preference.push_back(talk_base::FromString<int>(fields[j]));
+    }
+
+    // Make a temporary TransportDescription based on |session_td|.
+    // Some of this gets overwritten by ParseContent.
+    TransportDescription transport(NS_JINGLE_ICE_UDP,
+                                   session_td.transport_options,
+                                   session_td.ice_ufrag,
+                                   session_td.ice_pwd,
+                                   session_td.ice_mode,
+                                   session_td.identity_fingerprint.get(),
+                                   Candidates());
+
+    talk_base::scoped_ptr<MediaContentDescription> content;
+    std::string content_name;
+    if (HasAttribute(line, kMediaTypeVideo)) {
+      content.reset(ParseContentDescription<VideoContentDescription>(
+                    message, cricket::MEDIA_TYPE_VIDEO, mline_index, protocol,
+                    codec_preference, pos, &content_name,
+                    &transport, candidates, error));
+    } else if (HasAttribute(line, kMediaTypeAudio)) {
+      content.reset(ParseContentDescription<AudioContentDescription>(
+                    message, cricket::MEDIA_TYPE_AUDIO, mline_index, protocol,
+                    codec_preference, pos, &content_name,
+                    &transport, candidates, error));
+      MaybeCreateStaticPayloadAudioCodecs(
+          codec_preference,
+          static_cast<AudioContentDescription*>(content.get()));
+    } else if (HasAttribute(line, kMediaTypeData)) {
+      content.reset(ParseContentDescription<DataContentDescription>(
+                    message, cricket::MEDIA_TYPE_DATA, mline_index, protocol,
+                    codec_preference, pos, &content_name,
+                    &transport, candidates, error));
+    } else {
+      LOG(LS_WARNING) << "Unsupported media type: " << line;
+      continue;
+    }
+    if (!content.get()) {
+      // ParseContentDescription returns NULL if failed.
+      return false;
+    }
+
+    if (!is_sctp) {
+      // Make sure to set the media direction correctly. If the direction is not
+      // MD_RECVONLY or Inactive and no streams are parsed,
+      // a default MediaStream will be created to prepare for receiving media.
+      if (supports_msid && content->streams().empty() &&
+          content->direction() == cricket::MD_SENDRECV) {
+        content->set_direction(cricket::MD_RECVONLY);
+      }
+
+      // Set the extmap.
+      if (!session_extmaps.empty() &&
+          !content->rtp_header_extensions().empty()) {
+        return ParseFailed("",
+                           "The a=extmap MUST be either all session level or "
+                           "all media level.",
+                           error);
+      }
+      for (size_t i = 0; i < session_extmaps.size(); ++i) {
+        content->AddRtpHeaderExtension(session_extmaps[i]);
+      }
+    }
+    content->set_protocol(protocol);
+    desc->AddContent(content_name,
+                     is_sctp ? cricket::NS_JINGLE_DRAFT_SCTP :
+                               cricket::NS_JINGLE_RTP,
+                     rejected,
+                     content.release());
+    // Create TransportInfo with the media level "ice-pwd" and "ice-ufrag".
+    TransportInfo transport_info(content_name, transport);
+
+    if (!desc->AddTransportInfo(transport_info)) {
+      std::ostringstream description;
+      description << "Failed to AddTransportInfo with content name: "
+                  << content_name;
+      return ParseFailed("", description.str(), error);
+    }
+  }
+  return true;
+}
+
+bool VerifyCodec(const cricket::Codec& codec) {
+  // Codec has not been populated correctly unless the name has been set. This
+  // can happen if an SDP has an fmtp or rtcp-fb with a payload type but doesn't
+  // have a corresponding "rtpmap" line.
+  cricket::Codec default_codec;
+  return default_codec.name != codec.name;
+}
+
+bool VerifyAudioCodecs(const AudioContentDescription* audio_desc) {
+  const std::vector<cricket::AudioCodec>& codecs = audio_desc->codecs();
+  for (std::vector<cricket::AudioCodec>::const_iterator iter = codecs.begin();
+       iter != codecs.end(); ++iter) {
+    if (!VerifyCodec(*iter)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool VerifyVideoCodecs(const VideoContentDescription* video_desc) {
+  const std::vector<cricket::VideoCodec>& codecs = video_desc->codecs();
+  for (std::vector<cricket::VideoCodec>::const_iterator iter = codecs.begin();
+       iter != codecs.end(); ++iter) {
+    if (!VerifyCodec(*iter)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+void AddParameters(const cricket::CodecParameterMap& parameters,
+                   cricket::Codec* codec) {
+  for (cricket::CodecParameterMap::const_iterator iter =
+           parameters.begin(); iter != parameters.end(); ++iter) {
+    codec->SetParam(iter->first, iter->second);
+  }
+}
+
+void AddFeedbackParameter(const cricket::FeedbackParam& feedback_param,
+                          cricket::Codec* codec) {
+  codec->AddFeedbackParam(feedback_param);
+}
+
+void AddFeedbackParameters(const cricket::FeedbackParams& feedback_params,
+                           cricket::Codec* codec) {
+  for (std::vector<cricket::FeedbackParam>::const_iterator iter =
+           feedback_params.params().begin();
+       iter != feedback_params.params().end(); ++iter) {
+    codec->AddFeedbackParam(*iter);
+  }
+}
+
+// Gets the current codec setting associated with |payload_type|. If there
+// is no AudioCodec associated with that payload type it returns an empty codec
+// with that payload type.
+template <class T>
+T GetCodec(const std::vector<T>& codecs, int payload_type) {
+  for (typename std::vector<T>::const_iterator codec = codecs.begin();
+       codec != codecs.end(); ++codec) {
+    if (codec->id == payload_type) {
+      return *codec;
+    }
+  }
+  T ret_val = T();
+  ret_val.id = payload_type;
+  return ret_val;
+}
+
+// Updates or creates a new codec entry in the audio description.
+template <class T, class U>
+void AddOrReplaceCodec(MediaContentDescription* content_desc, const U& codec) {
+  T* desc = static_cast<T*>(content_desc);
+  std::vector<U> codecs = desc->codecs();
+  bool found = false;
+
+  typename std::vector<U>::iterator iter;
+  for (iter = codecs.begin(); iter != codecs.end(); ++iter) {
+    if (iter->id == codec.id) {
+      *iter = codec;
+      found = true;
+      break;
+    }
+  }
+  if (!found) {
+    desc->AddCodec(codec);
+    return;
+  }
+  desc->set_codecs(codecs);
+}
+
+// Adds or updates existing codec corresponding to |payload_type| according
+// to |parameters|.
+template <class T, class U>
+void UpdateCodec(MediaContentDescription* content_desc, int payload_type,
+                 const cricket::CodecParameterMap& parameters) {
+  // Codec might already have been populated (from rtpmap).
+  U new_codec = GetCodec(static_cast<T*>(content_desc)->codecs(), payload_type);
+  AddParameters(parameters, &new_codec);
+  AddOrReplaceCodec<T, U>(content_desc, new_codec);
+}
+
+// Adds or updates existing codec corresponding to |payload_type| according
+// to |feedback_param|.
+template <class T, class U>
+void UpdateCodec(MediaContentDescription* content_desc, int payload_type,
+                 const cricket::FeedbackParam& feedback_param) {
+  // Codec might already have been populated (from rtpmap).
+  U new_codec = GetCodec(static_cast<T*>(content_desc)->codecs(), payload_type);
+  AddFeedbackParameter(feedback_param, &new_codec);
+  AddOrReplaceCodec<T, U>(content_desc, new_codec);
+}
+
+bool PopWildcardCodec(std::vector<cricket::VideoCodec>* codecs,
+                      cricket::VideoCodec* wildcard_codec) {
+  for (std::vector<cricket::VideoCodec>::iterator iter = codecs->begin();
+       iter != codecs->end(); ++iter) {
+    if (iter->id == kWildcardPayloadType) {
+      *wildcard_codec = *iter;
+      codecs->erase(iter);
+      return true;
+    }
+  }
+  return false;
+}
+
+void UpdateFromWildcardVideoCodecs(VideoContentDescription* video_desc) {
+  std::vector<cricket::VideoCodec> codecs = video_desc->codecs();
+  cricket::VideoCodec wildcard_codec;
+  if (!PopWildcardCodec(&codecs, &wildcard_codec)) {
+    return;
+  }
+  for (std::vector<cricket::VideoCodec>::iterator iter = codecs.begin();
+       iter != codecs.end(); ++iter) {
+    cricket::VideoCodec& codec = *iter;
+    AddFeedbackParameters(wildcard_codec.feedback_params, &codec);
+  }
+  video_desc->set_codecs(codecs);
+}
+
+void AddAudioAttribute(const std::string& name, const std::string& value,
+                       AudioContentDescription* audio_desc) {
+  if (value.empty()) {
+    return;
+  }
+  std::vector<cricket::AudioCodec> codecs = audio_desc->codecs();
+  for (std::vector<cricket::AudioCodec>::iterator iter = codecs.begin();
+       iter != codecs.end(); ++iter) {
+    iter->params[name] = value;
+  }
+  audio_desc->set_codecs(codecs);
+}
+
+bool ParseContent(const std::string& message,
+                  const MediaType media_type,
+                  int mline_index,
+                  const std::string& protocol,
+                  const std::vector<int>& codec_preference,
+                  size_t* pos,
+                  std::string* content_name,
+                  MediaContentDescription* media_desc,
+                  TransportDescription* transport,
+                  std::vector<JsepIceCandidate*>* candidates,
+                  SdpParseError* error) {
+  ASSERT(media_desc != NULL);
+  ASSERT(content_name != NULL);
+  ASSERT(transport != NULL);
+
+  // The media level "ice-ufrag" and "ice-pwd".
+  // The candidates before update the media level "ice-pwd" and "ice-ufrag".
+  Candidates candidates_orig;
+  std::string line;
+  std::string mline_id;
+  // Tracks created out of the ssrc attributes.
+  StreamParamsVec tracks;
+  SsrcInfoVec ssrc_infos;
+  SsrcGroupVec ssrc_groups;
+  std::string maxptime_as_string;
+  std::string ptime_as_string;
+
+  bool is_rtp =
+      protocol.empty() ||
+      talk_base::starts_with(protocol.data(),
+                             cricket::kMediaProtocolRtpPrefix);
+
+  // Loop until the next m line
+  while (!IsLineType(message, kLineTypeMedia, *pos)) {
+    if (!GetLine(message, pos, &line)) {
+      if (*pos >= message.size()) {
+        break;  // Done parsing
+      } else {
+        return ParseFailed(message, *pos, "Can't find valid SDP line.", error);
+      }
+    }
+
+    if (IsLineType(line, kLineTypeSessionBandwidth)) {
+      std::string bandwidth;
+      if (HasAttribute(line, kApplicationSpecificMaximum)) {
+        if (!GetValue(line, kApplicationSpecificMaximum, &bandwidth, error)) {
+          return false;
+        } else {
+          media_desc->set_bandwidth(
+              talk_base::FromString<int>(bandwidth) * 1000);
+        }
+      }
+      continue;
+    }
+
+    // RFC 4566
+    // b=* (zero or more bandwidth information lines)
+    if (IsLineType(line, kLineTypeSessionBandwidth)) {
+      std::string bandwidth;
+      if (HasAttribute(line, kApplicationSpecificMaximum)) {
+        if (!GetValue(line, kApplicationSpecificMaximum, &bandwidth, error)) {
+          return false;
+        } else {
+          media_desc->set_bandwidth(
+              talk_base::FromString<int>(bandwidth) * 1000);
+        }
+      }
+      continue;
+    }
+
+    if (!IsLineType(line, kLineTypeAttributes)) {
+      // TODO: Handle other lines if needed.
+      LOG(LS_INFO) << "Ignored line: " << line;
+      continue;
+    }
+
+    // Handle attributes common to SCTP and RTP.
+    if (HasAttribute(line, kAttributeMid)) {
+      // RFC 3388
+      // mid-attribute      = "a=mid:" identification-tag
+      // identification-tag = token
+      // Use the mid identification-tag as the content name.
+      if (!GetValue(line, kAttributeMid, &mline_id, error)) {
+        return false;
+      }
+      *content_name = mline_id;
+    } else if (HasAttribute(line, kAttributeCandidate)) {
+      Candidate candidate;
+      if (!ParseCandidate(line, &candidate, error, false)) {
+        return false;
+      }
+      candidates_orig.push_back(candidate);
+    } else if (HasAttribute(line, kAttributeIceUfrag)) {
+      if (!GetValue(line, kAttributeIceUfrag, &transport->ice_ufrag, error)) {
+        return false;
+      }
+    } else if (HasAttribute(line, kAttributeIcePwd)) {
+      if (!GetValue(line, kAttributeIcePwd, &transport->ice_pwd, error)) {
+        return false;
+      }
+    } else if (HasAttribute(line, kAttributeIceOption)) {
+      if (!ParseIceOptions(line, &transport->transport_options, error)) {
+        return false;
+      }
+    } else if (HasAttribute(line, kAttributeFmtp)) {
+      if (!ParseFmtpAttributes(line, media_type, media_desc, error)) {
+        return false;
+      }
+    } else if (HasAttribute(line, kAttributeFingerprint)) {
+      talk_base::SSLFingerprint* fingerprint = NULL;
+
+      if (!ParseFingerprintAttribute(line, &fingerprint, error)) {
+        return false;
+      }
+      transport->identity_fingerprint.reset(fingerprint);
+    } else if (is_rtp) {
+      //
+      // RTP specific attrubtes
+      //
+      if (HasAttribute(line, kAttributeRtcpMux)) {
+        media_desc->set_rtcp_mux(true);
+      } else if (HasAttribute(line, kAttributeSsrcGroup)) {
+        if (!ParseSsrcGroupAttribute(line, &ssrc_groups, error)) {
+          return false;
+        }
+      } else if (HasAttribute(line, kAttributeSsrc)) {
+        if (!ParseSsrcAttribute(line, &ssrc_infos, error)) {
+          return false;
+        }
+      } else if (HasAttribute(line, kAttributeCrypto)) {
+        if (!ParseCryptoAttribute(line, media_desc, error)) {
+          return false;
+        }
+      } else if (HasAttribute(line, kAttributeRtpmap)) {
+        if (!ParseRtpmapAttribute(line, media_type, codec_preference,
+                                  media_desc, error)) {
+          return false;
+        }
+      } else if (HasAttribute(line, kCodecParamMaxPTime)) {
+        if (!GetValue(line, kCodecParamMaxPTime, &maxptime_as_string, error)) {
+          return false;
+        }
+      } else if (HasAttribute(line, kAttributeRtcpFb)) {
+        if (!ParseRtcpFbAttribute(line, media_type, media_desc, error)) {
+          return false;
+        }
+      } else if (HasAttribute(line, kCodecParamPTime)) {
+        if (!GetValue(line, kCodecParamPTime, &ptime_as_string, error)) {
+          return false;
+        }
+      } else if (HasAttribute(line, kAttributeSendOnly)) {
+        media_desc->set_direction(cricket::MD_SENDONLY);
+      } else if (HasAttribute(line, kAttributeRecvOnly)) {
+        media_desc->set_direction(cricket::MD_RECVONLY);
+      } else if (HasAttribute(line, kAttributeInactive)) {
+        media_desc->set_direction(cricket::MD_INACTIVE);
+      } else if (HasAttribute(line, kAttributeSendRecv)) {
+        media_desc->set_direction(cricket::MD_SENDRECV);
+      } else if (HasAttribute(line, kAttributeExtmap)) {
+        RtpHeaderExtension extmap;
+        if (!ParseExtmap(line, &extmap, error)) {
+          return false;
+        }
+        media_desc->AddRtpHeaderExtension(extmap);
+      } else if (HasAttribute(line, kAttributeXGoogleFlag)) {
+        // Experimental attribute.  Conference mode activates more aggressive
+        // AEC and NS settings.
+        // TODO: expose API to set these directly.
+        std::string flag_value;
+        if (!GetValue(line, kAttributeXGoogleFlag, &flag_value, error)) {
+          return false;
+        }
+        if (flag_value.compare(kValueConference) == 0)
+          media_desc->set_conference_mode(true);
+      } else if (HasAttribute(line, kAttributeXGoogleBufferLatency)) {
+        // Experimental attribute.
+        // TODO: expose API to set this directly.
+        std::string flag_value;
+        if (!GetValue(line, kAttributeXGoogleBufferLatency, &flag_value,
+                      error)) {
+          return false;
+        }
+        int buffer_latency = 0;
+        if (!talk_base::FromString(flag_value, &buffer_latency) ||
+            buffer_latency < 0) {
+          return ParseFailed(message, "Invalid buffer latency.", error);
+        }
+        media_desc->set_buffered_mode_latency(buffer_latency);
+      }
+    } else {
+      // Only parse lines that we are interested of.
+      LOG(LS_INFO) << "Ignored line: " << line;
+      continue;
+    }
+  }
+
+  // Create tracks from the |ssrc_infos|.
+  CreateTracksFromSsrcInfos(ssrc_infos, &tracks);
+
+  // Add the ssrc group to the track.
+  for (SsrcGroupVec::iterator ssrc_group = ssrc_groups.begin();
+       ssrc_group != ssrc_groups.end(); ++ssrc_group) {
+    if (ssrc_group->ssrcs.empty()) {
+      continue;
+    }
+    uint32 ssrc = ssrc_group->ssrcs.front();
+    for (StreamParamsVec::iterator track = tracks.begin();
+         track != tracks.end(); ++track) {
+      if (track->has_ssrc(ssrc)) {
+        track->ssrc_groups.push_back(*ssrc_group);
+      }
+    }
+  }
+
+  // Add the new tracks to the |media_desc|.
+  for (StreamParamsVec::iterator track = tracks.begin();
+       track != tracks.end(); ++track) {
+    media_desc->AddStream(*track);
+  }
+
+  if (media_type == cricket::MEDIA_TYPE_AUDIO) {
+    AudioContentDescription* audio_desc =
+        static_cast<AudioContentDescription*>(media_desc);
+    // Verify audio codec ensures that no audio codec has been populated with
+    // only fmtp.
+    if (!VerifyAudioCodecs(audio_desc)) {
+      return ParseFailed("Failed to parse audio codecs correctly.", error);
+    }
+    AddAudioAttribute(kCodecParamMaxPTime, maxptime_as_string, audio_desc);
+    AddAudioAttribute(kCodecParamPTime, ptime_as_string, audio_desc);
+  }
+
+  if (media_type == cricket::MEDIA_TYPE_VIDEO) {
+      VideoContentDescription* video_desc =
+          static_cast<VideoContentDescription*>(media_desc);
+      UpdateFromWildcardVideoCodecs(video_desc);
+      // Verify video codec ensures that no video codec has been populated with
+      // only rtcp-fb.
+      if (!VerifyVideoCodecs(video_desc)) {
+        return ParseFailed("Failed to parse video codecs correctly.", error);
+      }
+  }
+
+  // RFC 5245
+  // Update the candidates with the media level "ice-pwd" and "ice-ufrag".
+  for (Candidates::iterator it = candidates_orig.begin();
+       it != candidates_orig.end(); ++it) {
+    ASSERT((*it).username().empty());
+    (*it).set_username(transport->ice_ufrag);
+    ASSERT((*it).password().empty());
+    (*it).set_password(transport->ice_pwd);
+    candidates->push_back(
+        new JsepIceCandidate(mline_id, mline_index, *it));
+  }
+  return true;
+}
+
+bool ParseSsrcAttribute(const std::string& line, SsrcInfoVec* ssrc_infos,
+                        SdpParseError* error) {
+  ASSERT(ssrc_infos != NULL);
+  // RFC 5576
+  // a=ssrc:<ssrc-id> <attribute>
+  // a=ssrc:<ssrc-id> <attribute>:<value>
+  std::string field1, field2;
+  if (!SplitByDelimiter(line.substr(kLinePrefixLength),
+                        kSdpDelimiterSpace,
+                        &field1,
+                        &field2)) {
+    const size_t expected_fields = 2;
+    return ParseFailedExpectFieldNum(line, expected_fields, error);
+  }
+
+  // ssrc:<ssrc-id>
+  std::string ssrc_id_s;
+  if (!GetValue(field1, kAttributeSsrc, &ssrc_id_s, error)) {
+    return false;
+  }
+  uint32 ssrc_id = talk_base::FromString<uint32>(ssrc_id_s);
+
+  std::string attribute;
+  std::string value;
+  if (!SplitByDelimiter(field2, kSdpDelimiterColon,
+                        &attribute, &value)) {
+    std::ostringstream description;
+    description << "Failed to get the ssrc attribute value from " << field2
+                << ". Expected format <attribute>:<value>.";
+    return ParseFailed(line, description.str(), error);
+  }
+
+  // Check if there's already an item for this |ssrc_id|. Create a new one if
+  // there isn't.
+  SsrcInfoVec::iterator ssrc_info = ssrc_infos->begin();
+  for (; ssrc_info != ssrc_infos->end(); ++ssrc_info) {
+    if (ssrc_info->ssrc_id == ssrc_id) {
+      break;
+    }
+  }
+  if (ssrc_info == ssrc_infos->end()) {
+    SsrcInfo info;
+    info.ssrc_id = ssrc_id;
+    ssrc_infos->push_back(info);
+    ssrc_info = ssrc_infos->end() - 1;
+  }
+
+  // Store the info to the |ssrc_info|.
+  if (attribute == kSsrcAttributeCname) {
+    // RFC 5576
+    // cname:<value>
+    ssrc_info->cname = value;
+  } else if (attribute == kSsrcAttributeMsid) {
+    // draft-alvestrand-mmusic-msid-00
+    // "msid:" identifier [ " " appdata ]
+    std::vector<std::string> fields;
+    talk_base::split(value, kSdpDelimiterSpace, &fields);
+    if (fields.size() < 1 || fields.size() > 2) {
+      return ParseFailed(line,
+                         "Expected format \"msid:<identifier>[ <appdata>]\".",
+                         error);
+    }
+    ssrc_info->msid_identifier = fields[0];
+    if (fields.size() == 2) {
+      ssrc_info->msid_appdata = fields[1];
+    }
+  } else if (attribute == kSsrcAttributeMslabel) {
+    // draft-alvestrand-rtcweb-mid-01
+    // mslabel:<value>
+    ssrc_info->mslabel = value;
+  } else if (attribute == kSSrcAttributeLabel) {
+    // The label isn't defined.
+    // label:<value>
+    ssrc_info->label = value;
+  }
+  return true;
+}
+
+bool ParseSsrcGroupAttribute(const std::string& line,
+                             SsrcGroupVec* ssrc_groups,
+                             SdpParseError* error) {
+  ASSERT(ssrc_groups != NULL);
+  // RFC 5576
+  // a=ssrc-group:<semantics> <ssrc-id> ...
+  std::vector<std::string> fields;
+  talk_base::split(line.substr(kLinePrefixLength),
+                   kSdpDelimiterSpace, &fields);
+  const size_t expected_min_fields = 2;
+  if (fields.size() < expected_min_fields) {
+    return ParseFailedExpectMinFieldNum(line, expected_min_fields, error);
+  }
+  std::string semantics;
+  if (!GetValue(fields[0], kAttributeSsrcGroup, &semantics, error)) {
+    return false;
+  }
+  std::vector<uint32> ssrcs;
+  for (size_t i = 1; i < fields.size(); ++i) {
+    uint32 ssrc = talk_base::FromString<uint32>(fields[i]);
+    ssrcs.push_back(ssrc);
+  }
+  ssrc_groups->push_back(SsrcGroup(semantics, ssrcs));
+  return true;
+}
+
+bool ParseCryptoAttribute(const std::string& line,
+                          MediaContentDescription* media_desc,
+                          SdpParseError* error) {
+  std::vector<std::string> fields;
+  talk_base::split(line.substr(kLinePrefixLength),
+                   kSdpDelimiterSpace, &fields);
+  // RFC 4568
+  // a=crypto:<tag> <crypto-suite> <key-params> [<session-params>]
+  const size_t expected_min_fields = 3;
+  if (fields.size() < expected_min_fields) {
+    return ParseFailedExpectMinFieldNum(line, expected_min_fields, error);
+  }
+  std::string tag_value;
+  if (!GetValue(fields[0], kAttributeCrypto, &tag_value, error)) {
+    return false;
+  }
+  int tag = talk_base::FromString<int>(tag_value);
+  const std::string crypto_suite = fields[1];
+  const std::string key_params = fields[2];
+  std::string session_params;
+  if (fields.size() > 3) {
+    session_params = fields[3];
+  }
+  media_desc->AddCrypto(CryptoParams(tag, crypto_suite, key_params,
+                                     session_params));
+  return true;
+}
+
+// Updates or creates a new codec entry in the audio description with according
+// to |name|, |clockrate|, |bitrate|, |channels| and |preference|.
+void UpdateCodec(int payload_type, const std::string& name, int clockrate,
+                 int bitrate, int channels, int preference,
+                 AudioContentDescription* audio_desc) {
+  // Codec may already be populated with (only) optional parameters
+  // (from an fmtp).
+  cricket::AudioCodec codec = GetCodec(audio_desc->codecs(), payload_type);
+  codec.name = name;
+  codec.clockrate = clockrate;
+  codec.bitrate = bitrate;
+  codec.channels = channels;
+  codec.preference = preference;
+  AddOrReplaceCodec<AudioContentDescription, cricket::AudioCodec>(audio_desc,
+                                                                  codec);
+}
+
+// Updates or creates a new codec entry in the video description according to
+// |name|, |width|, |height|, |framerate| and |preference|.
+void UpdateCodec(int payload_type, const std::string& name, int width,
+                 int height, int framerate, int preference,
+                 VideoContentDescription* video_desc) {
+  // Codec may already be populated with (only) optional parameters
+  // (from an fmtp).
+  cricket::VideoCodec codec = GetCodec(video_desc->codecs(), payload_type);
+  codec.name = name;
+  codec.width = width;
+  codec.height = height;
+  codec.framerate = framerate;
+  codec.preference = preference;
+  AddOrReplaceCodec<VideoContentDescription, cricket::VideoCodec>(video_desc,
+                                                                  codec);
+}
+
+bool ParseRtpmapAttribute(const std::string& line,
+                          const MediaType media_type,
+                          const std::vector<int>& codec_preference,
+                          MediaContentDescription* media_desc,
+                          SdpParseError* error) {
+  std::vector<std::string> fields;
+  talk_base::split(line.substr(kLinePrefixLength),
+                   kSdpDelimiterSpace, &fields);
+  // RFC 4566
+  // a=rtpmap:<payload type> <encoding name>/<clock rate>[/<encodingparameters>]
+  const size_t expected_min_fields = 2;
+  if (fields.size() < expected_min_fields) {
+    return ParseFailedExpectMinFieldNum(line, expected_min_fields, error);
+  }
+  std::string payload_type_value;
+  if (!GetValue(fields[0], kAttributeRtpmap, &payload_type_value, error)) {
+    return false;
+  }
+  const int payload_type = talk_base::FromString<int>(payload_type_value);
+
+  // Set the preference order depending on the order of the pl type in the
+  // <fmt> of the m-line.
+  const int preference = codec_preference.end() -
+      std::find(codec_preference.begin(), codec_preference.end(),
+                payload_type);
+  if (preference == 0) {
+    LOG(LS_WARNING) << "Ignore rtpmap line that did not appear in the "
+                    << "<fmt> of the m-line: " << line;
+    return true;
+  }
+  const std::string encoder = fields[1];
+  std::vector<std::string> codec_params;
+  talk_base::split(encoder, '/', &codec_params);
+  // <encoding name>/<clock rate>[/<encodingparameters>]
+  // 2 mandatory fields
+  if (codec_params.size() < 2 || codec_params.size() > 3) {
+    return ParseFailed(line,
+                       "Expected format \"<encoding name>/<clock rate>"
+                       "[/<encodingparameters>]\".",
+                       error);
+  }
+  const std::string encoding_name = codec_params[0];
+  const int clock_rate = talk_base::FromString<int>(codec_params[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
+    // JsepSessionDescription::kMaxVideoCodecWidth and kMaxVideoCodecHeight.
+    UpdateCodec(payload_type, encoding_name,
+                JsepSessionDescription::kMaxVideoCodecWidth,
+                JsepSessionDescription::kMaxVideoCodecHeight,
+                JsepSessionDescription::kDefaultVideoCodecFramerate,
+                preference, video_desc);
+  } else if (media_type == cricket::MEDIA_TYPE_AUDIO) {
+    // RFC 4566
+    // For audio streams, <encoding parameters> indicates the number
+    // of audio channels.  This parameter is OPTIONAL and may be
+    // omitted if the number of channels is one, provided that no
+    // additional parameters are needed.
+    int channels = 1;
+    if (codec_params.size() == 3) {
+      channels = talk_base::FromString<int>(codec_params[2]);
+    }
+    int bitrate = 0;
+    // The default behavior for ISAC (bitrate == 0) in webrtcvoiceengine.cc
+    // (specifically FindWebRtcCodec) is bandwidth-adaptive variable bitrate.
+    // The bandwidth adaptation doesn't always work well, so this code
+    // sets a fixed target bitrate instead.
+    if (_stricmp(encoding_name.c_str(), kIsacCodecName) == 0) {
+      if (clock_rate <= 16000) {
+        bitrate = kIsacWbDefaultRate;
+      } else {
+        bitrate = kIsacSwbDefaultRate;
+      }
+    }
+    AudioContentDescription* audio_desc =
+        static_cast<AudioContentDescription*>(media_desc);
+    UpdateCodec(payload_type, encoding_name, clock_rate, bitrate, channels,
+                preference, audio_desc);
+  } else if (media_type == cricket::MEDIA_TYPE_DATA) {
+    DataContentDescription* data_desc =
+        static_cast<DataContentDescription*>(media_desc);
+    data_desc->AddCodec(cricket::DataCodec(payload_type, encoding_name,
+                                           preference));
+  }
+  return true;
+}
+
+void PruneRight(const char delimiter, std::string* message) {
+  size_t trailing = message->find(delimiter);
+  if (trailing != std::string::npos) {
+    *message = message->substr(0, trailing);
+  }
+}
+
+bool ParseFmtpParam(const std::string& line, std::string* parameter,
+                    std::string* value, SdpParseError* error) {
+  if (!SplitByDelimiter(line, kSdpDelimiterEqual, parameter, value)) {
+    ParseFailed(line, "Unable to parse fmtp parameter. \'=\' missing.", error);
+    return false;
+  }
+  // a=fmtp:<payload_type> <param1>=<value1>; <param2>=<value2>; ...
+  // When parsing the values the trailing ";" gets picked up. Remove them.
+  PruneRight(kSdpDelimiterSemicolon, value);
+  return true;
+}
+
+bool ParseFmtpAttributes(const std::string& line, const MediaType media_type,
+                         MediaContentDescription* media_desc,
+                         SdpParseError* error) {
+  if (media_type != cricket::MEDIA_TYPE_AUDIO &&
+      media_type != cricket::MEDIA_TYPE_VIDEO) {
+    return true;
+  }
+  std::vector<std::string> fields;
+  talk_base::split(line.substr(kLinePrefixLength),
+                   kSdpDelimiterSpace, &fields);
+
+  // RFC 5576
+  // a=fmtp:<format> <format specific parameters>
+  // At least two fields, whereas the second one is any of the optional
+  // parameters.
+  if (fields.size() < 2) {
+    ParseFailedExpectMinFieldNum(line, 2, error);
+    return false;
+  }
+
+  std::string payload_type;
+  if (!GetValue(fields[0], kAttributeFmtp, &payload_type, error)) {
+    return false;
+  }
+
+  cricket::CodecParameterMap codec_params;
+  for (std::vector<std::string>::const_iterator iter = fields.begin() + 1;
+       iter != fields.end(); ++iter) {
+    std::string name;
+    std::string value;
+    if (iter->find(kSdpDelimiterEqual) == std::string::npos) {
+      // Only fmtps with equals are currently supported. Other fmtp types
+      // should be ignored. Unknown fmtps do not constitute an error.
+      continue;
+    }
+    if (!ParseFmtpParam(*iter, &name, &value, error)) {
+      return false;
+    }
+    codec_params[name] = value;
+  }
+
+  int int_payload_type = talk_base::FromString<int>(payload_type);
+  if (media_type == cricket::MEDIA_TYPE_AUDIO) {
+    UpdateCodec<AudioContentDescription, cricket::AudioCodec>(
+        media_desc, int_payload_type, codec_params);
+  } else if (media_type == cricket::MEDIA_TYPE_VIDEO) {
+    UpdateCodec<VideoContentDescription, cricket::VideoCodec>(
+        media_desc, int_payload_type, codec_params);
+  }
+  return true;
+}
+
+bool ParseRtcpFbAttribute(const std::string& line, const MediaType media_type,
+                          MediaContentDescription* media_desc,
+                          SdpParseError* error) {
+  if (media_type != cricket::MEDIA_TYPE_AUDIO &&
+      media_type != cricket::MEDIA_TYPE_VIDEO) {
+    return true;
+  }
+  std::vector<std::string> rtcp_fb_fields;
+  talk_base::split(line.c_str(), kSdpDelimiterSpace, &rtcp_fb_fields);
+  if (rtcp_fb_fields.size() < 2) {
+    return ParseFailedGetValue(line, kAttributeRtcpFb, error);
+  }
+  std::string payload_type_string;
+  if (!GetValue(rtcp_fb_fields[0], kAttributeRtcpFb, &payload_type_string,
+                error)) {
+    return false;
+  }
+  int payload_type = (payload_type_string == "*") ?
+      kWildcardPayloadType : talk_base::FromString<int>(payload_type_string);
+  std::string id = rtcp_fb_fields[1];
+  std::string param = "";
+  for (std::vector<std::string>::iterator iter = rtcp_fb_fields.begin() + 2;
+       iter != rtcp_fb_fields.end(); ++iter) {
+    param.append(*iter);
+  }
+  const cricket::FeedbackParam feedback_param(id, param);
+
+  if (media_type == cricket::MEDIA_TYPE_AUDIO) {
+    UpdateCodec<AudioContentDescription, cricket::AudioCodec>(media_desc,
+                                                              payload_type,
+                                                              feedback_param);
+  } else if (media_type == cricket::MEDIA_TYPE_VIDEO) {
+    UpdateCodec<VideoContentDescription, cricket::VideoCodec>(media_desc,
+                                                              payload_type,
+                                                              feedback_param);
+  }
+  return true;
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/webrtcsdp.h b/talk/app/webrtc/webrtcsdp.h
new file mode 100644
index 0000000..c2f93a0
--- /dev/null
+++ b/talk/app/webrtc/webrtcsdp.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.
+ */
+
+// 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>
+
+namespace webrtc {
+
+class IceCandidateInterface;
+class JsepIceCandidate;
+class JsepSessionDescription;
+struct SdpParseError;
+
+// Serializes the passed in JsepSessionDescription.
+// Serialize SessionDescription including candidates if
+// JsepSessionDescription has candidates.
+// jdesc - The JsepSessionDescription object to be serialized.
+// return - SDP string serialized from the arguments.
+std::string SdpSerialize(const JsepSessionDescription& jdesc);
+
+// Serializes the passed in IceCandidateInterface to a SDP string.
+// candidate - The candidate to be serialized.
+std::string SdpSerializeCandidate(const IceCandidateInterface& candidate);
+
+// Deserializes the passed in SDP string to a JsepSessionDescription.
+// message - SDP string to be Deserialized.
+// jdesc - The JsepSessionDescription deserialized from the SDP string.
+// error - The detail error information when parsing fails.
+// return - true on success, false on failure.
+bool SdpDeserialize(const std::string& message,
+                    JsepSessionDescription* jdesc,
+                    SdpParseError* error);
+
+// Deserializes the passed in SDP string to one JsepIceCandidate.
+// The first line must be a=candidate line and only the first line will be
+// parsed.
+// message - The SDP string to be Deserialized.
+// candidates - The JsepIceCandidate from the SDP string.
+// error - The detail error information when parsing fails.
+// return - true on success, false on failure.
+bool SdpDeserializeCandidate(const std::string& message,
+                             JsepIceCandidate* candidate,
+                             SdpParseError* error);
+}  // 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..9c3debd
--- /dev/null
+++ b/talk/app/webrtc/webrtcsdp_unittest.cc
@@ -0,0 +1,1961 @@
+/*
+ * 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 <set>
+#include <string>
+#include <vector>
+
+#include "talk/app/webrtc/jsepsessiondescription.h"
+#include "talk/app/webrtc/webrtcsdp.h"
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/messagedigest.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sslfingerprint.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+#include "talk/media/base/constants.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/session/media/mediasession.h"
+
+using cricket::AudioCodec;
+using cricket::AudioContentDescription;
+using cricket::Candidate;
+using cricket::ContentInfo;
+using cricket::CryptoParams;
+using cricket::ContentGroup;
+using cricket::DataCodec;
+using cricket::DataContentDescription;
+using cricket::ICE_CANDIDATE_COMPONENT_RTCP;
+using cricket::ICE_CANDIDATE_COMPONENT_RTP;
+using cricket::kFecSsrcGroupSemantics;
+using cricket::LOCAL_PORT_TYPE;
+using cricket::NS_JINGLE_DRAFT_SCTP;
+using cricket::NS_JINGLE_ICE_UDP;
+using cricket::NS_JINGLE_RTP;
+using cricket::RtpHeaderExtension;
+using cricket::RELAY_PORT_TYPE;
+using cricket::SessionDescription;
+using cricket::StreamParams;
+using cricket::STUN_PORT_TYPE;
+using cricket::TransportDescription;
+using cricket::TransportInfo;
+using cricket::VideoCodec;
+using cricket::VideoContentDescription;
+using webrtc::IceCandidateCollection;
+using webrtc::IceCandidateInterface;
+using webrtc::JsepIceCandidate;
+using webrtc::JsepSessionDescription;
+using webrtc::SdpParseError;
+using webrtc::SessionDescriptionInterface;
+
+typedef std::vector<AudioCodec> AudioCodecs;
+typedef std::vector<Candidate> Candidates;
+
+static const char kSessionTime[] = "t=0 0\r\n";
+static const uint32 kCandidatePriority = 2130706432U;  // pref = 1.0
+static const char kCandidateUfragVoice[] = "ufrag_voice";
+static const char kCandidatePwdVoice[] = "pwd_voice";
+static const char kAttributeIcePwdVoice[] = "a=ice-pwd:pwd_voice\r\n";
+static const char kCandidateUfragVideo[] = "ufrag_video";
+static const char kCandidatePwdVideo[] = "pwd_video";
+static const char kCandidateUfragData[] = "ufrag_data";
+static const char kCandidatePwdData[] = "pwd_data";
+static const char kAttributeIcePwdVideo[] = "a=ice-pwd:pwd_video\r\n";
+static const uint32 kCandidateGeneration = 2;
+static const char kCandidateFoundation1[] = "a0+B/1";
+static const char kCandidateFoundation2[] = "a0+B/2";
+static const char kCandidateFoundation3[] = "a0+B/3";
+static const char kCandidateFoundation4[] = "a0+B/4";
+static const char kAttributeCryptoVoice[] =
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_32 "
+    "inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 "
+    "dummy_session_params\r\n";
+static const char kAttributeCryptoVideo[] =
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
+    "inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32\r\n";
+static const char kFingerprint[] = "a=fingerprint:sha-1 "
+    "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB\r\n";
+static const int kExtmapId = 1;
+static const char kExtmapUri[] = "http://example.com/082005/ext.htm#ttime";
+static const char kExtmap[] =
+    "a=extmap:1 http://example.com/082005/ext.htm#ttime\r\n";
+static const char kExtmapWithDirectionAndAttribute[] =
+    "a=extmap:1/sendrecv http://example.com/082005/ext.htm#ttime a1 a2\r\n";
+
+static const uint8 kIdentityDigest[] = {0x4A, 0xAD, 0xB9, 0xB1,
+                                        0x3F, 0x82, 0x18, 0x3B,
+                                        0x54, 0x02, 0x12, 0xDF,
+                                        0x3E, 0x5D, 0x49, 0x6B,
+                                        0x19, 0xE5, 0x7C, 0xAB};
+
+struct CodecParams {
+  int max_ptime;
+  int ptime;
+  int min_ptime;
+  int sprop_stereo;
+  int stereo;
+  int useinband;
+};
+
+// Reference sdp string
+static const char kSdpFullString[] =
+    "v=0\r\n"
+    "o=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1\r\n"
+    "s=-\r\n"
+    "t=0 0\r\n"
+    "a=msid-semantic: WMS local_stream_1 local_stream_2\r\n"
+    "m=audio 2345 RTP/SAVPF 111 103 104\r\n"
+    "c=IN IP4 74.125.127.126\r\n"
+    "a=rtcp:2347 IN IP4 74.125.127.126\r\n"
+    "a=candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1234 typ host "
+    "generation 2\r\n"
+    "a=candidate:a0+B/1 2 udp 2130706432 192.168.1.5 1235 typ host "
+    "generation 2\r\n"
+    "a=candidate:a0+B/2 1 udp 2130706432 ::1 1238 typ host "
+    "generation 2\r\n"
+    "a=candidate:a0+B/2 2 udp 2130706432 ::1 1239 typ host "
+    "generation 2\r\n"
+    "a=candidate:a0+B/3 1 udp 2130706432 74.125.127.126 2345 typ srflx "
+    "raddr 192.168.1.5 rport 2346 "
+    "generation 2\r\n"
+    "a=candidate:a0+B/3 2 udp 2130706432 74.125.127.126 2347 typ srflx "
+    "raddr 192.168.1.5 rport 2348 "
+    "generation 2\r\n"
+    "a=ice-ufrag:ufrag_voice\r\na=ice-pwd:pwd_voice\r\n"
+    "a=mid:audio_content_name\r\n"
+    "a=sendrecv\r\n"
+    "a=rtcp-mux\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_32 "
+    "inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 "
+    "dummy_session_params\r\n"
+    "a=rtpmap:111 opus/48000/2\r\n"
+    "a=rtpmap:103 ISAC/16000\r\n"
+    "a=rtpmap:104 CELT/32000/2\r\n"
+    "a=ssrc:1 cname:stream_1_cname\r\n"
+    "a=ssrc:1 msid:local_stream_1 audio_track_id_1\r\n"
+    "a=ssrc:1 mslabel:local_stream_1\r\n"
+    "a=ssrc:1 label:audio_track_id_1\r\n"
+    "a=ssrc:4 cname:stream_2_cname\r\n"
+    "a=ssrc:4 msid:local_stream_2 audio_track_id_2\r\n"
+    "a=ssrc:4 mslabel:local_stream_2\r\n"
+    "a=ssrc:4 label:audio_track_id_2\r\n"
+    "m=video 3457 RTP/SAVPF 120\r\n"
+    "c=IN IP4 74.125.224.39\r\n"
+    "a=rtcp:3456 IN IP4 74.125.224.39\r\n"
+    "a=candidate:a0+B/1 2 udp 2130706432 192.168.1.5 1236 typ host "
+    "generation 2\r\n"
+    "a=candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1237 typ host "
+    "generation 2\r\n"
+    "a=candidate:a0+B/2 2 udp 2130706432 ::1 1240 typ host "
+    "generation 2\r\n"
+    "a=candidate:a0+B/2 1 udp 2130706432 ::1 1241 typ host "
+    "generation 2\r\n"
+    "a=candidate:a0+B/4 2 udp 2130706432 74.125.224.39 3456 typ relay "
+    "generation 2\r\n"
+    "a=candidate:a0+B/4 1 udp 2130706432 74.125.224.39 3457 typ relay "
+    "generation 2\r\n"
+    "a=ice-ufrag:ufrag_video\r\na=ice-pwd:pwd_video\r\n"
+    "a=mid:video_content_name\r\n"
+    "a=sendrecv\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
+    "inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32\r\n"
+    "a=rtpmap:120 VP8/90000\r\n"
+    "a=ssrc:2 cname:stream_1_cname\r\n"
+    "a=ssrc:2 msid:local_stream_1 video_track_id_1\r\n"
+    "a=ssrc:2 mslabel:local_stream_1\r\n"
+    "a=ssrc:2 label:video_track_id_1\r\n"
+    "a=ssrc:3 cname:stream_1_cname\r\n"
+    "a=ssrc:3 msid:local_stream_1 video_track_id_2\r\n"
+    "a=ssrc:3 mslabel:local_stream_1\r\n"
+    "a=ssrc:3 label:video_track_id_2\r\n"
+    "a=ssrc-group:FEC 5 6\r\n"
+    "a=ssrc:5 cname:stream_2_cname\r\n"
+    "a=ssrc:5 msid:local_stream_2 video_track_id_3\r\n"
+    "a=ssrc:5 mslabel:local_stream_2\r\n"
+    "a=ssrc:5 label:video_track_id_3\r\n"
+    "a=ssrc:6 cname:stream_2_cname\r\n"
+    "a=ssrc:6 msid:local_stream_2 video_track_id_3\r\n"
+    "a=ssrc:6 mslabel:local_stream_2\r\n"
+    "a=ssrc:6 label:video_track_id_3\r\n";
+
+// SDP reference string without the candidates.
+static const char kSdpString[] =
+    "v=0\r\n"
+    "o=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1\r\n"
+    "s=-\r\n"
+    "t=0 0\r\n"
+    "a=msid-semantic: WMS local_stream_1 local_stream_2\r\n"
+    "m=audio 1 RTP/SAVPF 111 103 104\r\n"
+    "c=IN IP4 0.0.0.0\r\n"
+    "a=rtcp:1 IN IP4 0.0.0.0\r\n"
+    "a=ice-ufrag:ufrag_voice\r\na=ice-pwd:pwd_voice\r\n"
+    "a=mid:audio_content_name\r\n"
+    "a=sendrecv\r\n"
+    "a=rtcp-mux\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_32 "
+    "inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 "
+    "dummy_session_params\r\n"
+    "a=rtpmap:111 opus/48000/2\r\n"
+    "a=rtpmap:103 ISAC/16000\r\n"
+    "a=rtpmap:104 CELT/32000/2\r\n"
+    "a=ssrc:1 cname:stream_1_cname\r\n"
+    "a=ssrc:1 msid:local_stream_1 audio_track_id_1\r\n"
+    "a=ssrc:1 mslabel:local_stream_1\r\n"
+    "a=ssrc:1 label:audio_track_id_1\r\n"
+    "a=ssrc:4 cname:stream_2_cname\r\n"
+    "a=ssrc:4 msid:local_stream_2 audio_track_id_2\r\n"
+    "a=ssrc:4 mslabel:local_stream_2\r\n"
+    "a=ssrc:4 label:audio_track_id_2\r\n"
+    "m=video 1 RTP/SAVPF 120\r\n"
+    "c=IN IP4 0.0.0.0\r\n"
+    "a=rtcp:1 IN IP4 0.0.0.0\r\n"
+    "a=ice-ufrag:ufrag_video\r\na=ice-pwd:pwd_video\r\n"
+    "a=mid:video_content_name\r\n"
+    "a=sendrecv\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
+    "inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32\r\n"
+    "a=rtpmap:120 VP8/90000\r\n"
+    "a=ssrc:2 cname:stream_1_cname\r\n"
+    "a=ssrc:2 msid:local_stream_1 video_track_id_1\r\n"
+    "a=ssrc:2 mslabel:local_stream_1\r\n"
+    "a=ssrc:2 label:video_track_id_1\r\n"
+    "a=ssrc:3 cname:stream_1_cname\r\n"
+    "a=ssrc:3 msid:local_stream_1 video_track_id_2\r\n"
+    "a=ssrc:3 mslabel:local_stream_1\r\n"
+    "a=ssrc:3 label:video_track_id_2\r\n"
+    "a=ssrc-group:FEC 5 6\r\n"
+    "a=ssrc:5 cname:stream_2_cname\r\n"
+    "a=ssrc:5 msid:local_stream_2 video_track_id_3\r\n"
+    "a=ssrc:5 mslabel:local_stream_2\r\n"
+    "a=ssrc:5 label:video_track_id_3\r\n"
+    "a=ssrc:6 cname:stream_2_cname\r\n"
+    "a=ssrc:6 msid:local_stream_2 video_track_id_3\r\n"
+    "a=ssrc:6 mslabel:local_stream_2\r\n"
+    "a=ssrc:6 label:video_track_id_3\r\n";
+
+static const char kSdpRtpDataChannelString[] =
+    "m=application 1 RTP/SAVPF 101\r\n"
+    "c=IN IP4 0.0.0.0\r\n"
+    "a=rtcp:1 IN IP4 0.0.0.0\r\n"
+    "a=ice-ufrag:ufrag_data\r\n"
+    "a=ice-pwd:pwd_data\r\n"
+    "a=mid:data_content_name\r\n"
+    "a=sendrecv\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
+    "inline:FvLcvU2P3ZWmQxgPAgcDu7Zl9vftYElFOjEzhWs5\r\n"
+    "a=rtpmap:101 google-data/90000\r\n"
+    "a=ssrc:10 cname:data_channel_cname\r\n"
+    "a=ssrc:10 msid:data_channel data_channeld0\r\n"
+    "a=ssrc:10 mslabel:data_channel\r\n"
+    "a=ssrc:10 label:data_channeld0\r\n";
+
+static const char kSdpSctpDataChannelString[] =
+    "m=application 1 DTLS/SCTP 5000\r\n"
+    "c=IN IP4 0.0.0.0\r\n"
+    "a=ice-ufrag:ufrag_data\r\n"
+    "a=ice-pwd:pwd_data\r\n"
+    "a=mid:data_content_name\r\n"
+    "a=fmtp:5000 protocol=webrtc-datachannel; streams=10\r\n";
+
+static const char kSdpSctpDataChannelWithCandidatesString[] =
+    "m=application 2345 DTLS/SCTP 5000\r\n"
+    "c=IN IP4 74.125.127.126\r\n"
+    "a=candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1234 typ host "
+    "generation 2\r\n"
+    "a=candidate:a0+B/2 1 udp 2130706432 ::1 1238 typ host "
+    "generation 2\r\n"
+    "a=candidate:a0+B/3 1 udp 2130706432 74.125.127.126 2345 typ srflx "
+    "raddr 192.168.1.5 rport 2346 "
+    "generation 2\r\n"
+    "a=ice-ufrag:ufrag_data\r\n"
+    "a=ice-pwd:pwd_data\r\n"
+    "a=mid:data_content_name\r\n"
+    "a=fmtp:5000 protocol=webrtc-datachannel; streams=10\r\n";
+
+
+// One candidate reference string as per W3c spec.
+// candidate:<blah> not a=candidate:<blah>CRLF
+static const char kRawCandidate[] =
+    "candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1234 typ host generation 2";
+// One candidate reference string.
+static const char kSdpOneCandidate[] =
+    "a=candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1234 typ host "
+    "generation 2\r\n";
+
+// One candidate reference string.
+static const char kSdpOneCandidateOldFormat[] =
+    "a=candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1234 typ host network_name"
+    " eth0 username user_rtp password password_rtp generation 2\r\n";
+
+// Session id and version
+static const char kSessionId[] = "18446744069414584320";
+static const char kSessionVersion[] = "18446462598732840960";
+
+// Ice options
+static const char kIceOption1[] = "iceoption1";
+static const char kIceOption2[] = "iceoption2";
+static const char kIceOption3[] = "iceoption3";
+
+// Content name
+static const char kAudioContentName[] = "audio_content_name";
+static const char kVideoContentName[] = "video_content_name";
+static const char kDataContentName[] = "data_content_name";
+
+// MediaStream 1
+static const char kStreamLabel1[] = "local_stream_1";
+static const char kStream1Cname[] = "stream_1_cname";
+static const char kAudioTrackId1[] = "audio_track_id_1";
+static const uint32 kAudioTrack1Ssrc = 1;
+static const char kVideoTrackId1[] = "video_track_id_1";
+static const uint32 kVideoTrack1Ssrc = 2;
+static const char kVideoTrackId2[] = "video_track_id_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 kAudioTrackId2[] = "audio_track_id_2";
+static const uint32 kAudioTrack2Ssrc = 4;
+static const char kVideoTrackId3[] = "video_track_id_3";
+static const uint32 kVideoTrack3Ssrc = 5;
+static const uint32 kVideoTrack4Ssrc = 6;
+
+// DataChannel
+static const char kDataChannelLabel[] = "data_channel";
+static const char kDataChannelMsid[] = "data_channeld0";
+static const char kDataChannelCname[] = "data_channel_cname";
+static const uint32 kDataChannelSsrc = 10;
+
+// Candidate
+static const char kDummyMid[] = "dummy_mid";
+static const int kDummyIndex = 123;
+
+// Misc
+static const char kDummyString[] = "dummy";
+
+// Helper functions
+
+static bool SdpDeserialize(const std::string& message,
+                           JsepSessionDescription* jdesc) {
+  return webrtc::SdpDeserialize(message, jdesc, NULL);
+}
+
+static bool SdpDeserializeCandidate(const std::string& message,
+                                    JsepIceCandidate* candidate) {
+  return webrtc::SdpDeserializeCandidate(message, candidate, NULL);
+}
+
+// Add some extra |newlines| to the |message| after |line|.
+static void InjectAfter(const std::string& line,
+                        const std::string& newlines,
+                        std::string* message) {
+  const std::string tmp = line + newlines;
+  talk_base::replace_substrs(line.c_str(), line.length(),
+                             tmp.c_str(), tmp.length(), message);
+}
+
+static void Replace(const std::string& line,
+                    const std::string& newlines,
+                    std::string* message) {
+  talk_base::replace_substrs(line.c_str(), line.length(),
+                             newlines.c_str(), newlines.length(), message);
+}
+
+static void ReplaceAndTryToParse(const char* search, const char* replace) {
+  JsepSessionDescription desc(kDummyString);
+  std::string sdp = kSdpFullString;
+  Replace(search, replace, &sdp);
+  SdpParseError error;
+  bool ret = webrtc::SdpDeserialize(sdp, &desc, &error);
+  EXPECT_FALSE(ret);
+  EXPECT_NE(std::string::npos, error.line.find(replace));
+}
+
+static void ReplaceDirection(cricket::MediaContentDirection direction,
+                             std::string* message) {
+  std::string new_direction;
+  switch (direction) {
+    case cricket::MD_INACTIVE:
+      new_direction = "a=inactive";
+      break;
+    case cricket::MD_SENDONLY:
+      new_direction = "a=sendonly";
+      break;
+    case cricket::MD_RECVONLY:
+      new_direction = "a=recvonly";
+      break;
+    case cricket::MD_SENDRECV:
+    default:
+      new_direction = "a=sendrecv";
+      break;
+  }
+  Replace("a=sendrecv", new_direction, message);
+}
+
+static void ReplaceRejected(bool audio_rejected, bool video_rejected,
+                            std::string* message) {
+  if (audio_rejected) {
+    Replace("m=audio 2345", "m=audio 0", message);
+  }
+  if (video_rejected) {
+    Replace("m=video 3457", "m=video 0", message);
+  }
+}
+
+// WebRtcSdpTest
+
+class WebRtcSdpTest : public testing::Test {
+ public:
+  WebRtcSdpTest()
+     : jdesc_(kDummyString) {
+    // AudioContentDescription
+    audio_desc_ = CreateAudioContentDescription();
+    AudioCodec opus(111, "opus", 48000, 0, 2, 3);
+    audio_desc_->AddCodec(opus);
+    audio_desc_->AddCodec(AudioCodec(103, "ISAC", 16000, 32000, 1, 2));
+    audio_desc_->AddCodec(AudioCodec(104, "CELT", 32000, 0, 2, 1));
+    desc_.AddContent(kAudioContentName, NS_JINGLE_RTP, audio_desc_);
+
+    // VideoContentDescription
+    talk_base::scoped_ptr<VideoContentDescription> video(
+        new VideoContentDescription());
+    video_desc_ = video.get();
+    StreamParams video_stream1;
+    video_stream1.id = kVideoTrackId1;
+    video_stream1.cname = kStream1Cname;
+    video_stream1.sync_label = kStreamLabel1;
+    video_stream1.ssrcs.push_back(kVideoTrack1Ssrc);
+    video->AddStream(video_stream1);
+    StreamParams video_stream2;
+    video_stream2.id = kVideoTrackId2;
+    video_stream2.cname = kStream1Cname;
+    video_stream2.sync_label = kStreamLabel1;
+    video_stream2.ssrcs.push_back(kVideoTrack2Ssrc);
+    video->AddStream(video_stream2);
+    StreamParams video_stream3;
+    video_stream3.id = kVideoTrackId3;
+    video_stream3.cname = kStream2Cname;
+    video_stream3.sync_label = kStreamLabel2;
+    video_stream3.ssrcs.push_back(kVideoTrack3Ssrc);
+    video_stream3.ssrcs.push_back(kVideoTrack4Ssrc);
+    cricket::SsrcGroup ssrc_group(kFecSsrcGroupSemantics, video_stream3.ssrcs);
+    video_stream3.ssrc_groups.push_back(ssrc_group);
+    video->AddStream(video_stream3);
+    video->AddCrypto(CryptoParams(1, "AES_CM_128_HMAC_SHA1_80",
+        "inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32", ""));
+    video->set_protocol(cricket::kMediaProtocolSavpf);
+    video->AddCodec(VideoCodec(
+        120,
+        JsepSessionDescription::kDefaultVideoCodecName,
+        JsepSessionDescription::kMaxVideoCodecWidth,
+        JsepSessionDescription::kMaxVideoCodecHeight,
+        JsepSessionDescription::kDefaultVideoCodecFramerate,
+        JsepSessionDescription::kDefaultVideoCodecPreference));
+
+    desc_.AddContent(kVideoContentName, NS_JINGLE_RTP,
+                     video.release());
+
+    // TransportInfo
+    EXPECT_TRUE(desc_.AddTransportInfo(
+        TransportInfo(kAudioContentName,
+                      TransportDescription(NS_JINGLE_ICE_UDP,
+                                           std::vector<std::string>(),
+                                           kCandidateUfragVoice,
+                                           kCandidatePwdVoice,
+                                           cricket::ICEMODE_FULL,
+                                           NULL, Candidates()))));
+    EXPECT_TRUE(desc_.AddTransportInfo(
+        TransportInfo(kVideoContentName,
+                      TransportDescription(NS_JINGLE_ICE_UDP,
+                                           std::vector<std::string>(),
+                                           kCandidateUfragVideo,
+                                           kCandidatePwdVideo,
+                                           cricket::ICEMODE_FULL,
+                                           NULL, Candidates()))));
+
+    // v4 host
+    int port = 1234;
+    talk_base::SocketAddress address("192.168.1.5", port++);
+    Candidate candidate1(
+        "", ICE_CANDIDATE_COMPONENT_RTP, "udp", address, kCandidatePriority,
+        "", "", LOCAL_PORT_TYPE,
+        "", kCandidateGeneration, kCandidateFoundation1);
+    address.SetPort(port++);
+    Candidate candidate2(
+        "", ICE_CANDIDATE_COMPONENT_RTCP, "udp", address, kCandidatePriority,
+        "", "", LOCAL_PORT_TYPE,
+        "", kCandidateGeneration, kCandidateFoundation1);
+    address.SetPort(port++);
+    Candidate candidate3(
+        "", ICE_CANDIDATE_COMPONENT_RTCP, "udp", address, kCandidatePriority,
+        "", "", LOCAL_PORT_TYPE,
+        "", kCandidateGeneration, kCandidateFoundation1);
+    address.SetPort(port++);
+    Candidate candidate4(
+        "", ICE_CANDIDATE_COMPONENT_RTP, "udp", address, kCandidatePriority,
+        "", "", LOCAL_PORT_TYPE,
+        "", kCandidateGeneration, kCandidateFoundation1);
+
+    // v6 host
+    talk_base::SocketAddress v6_address("::1", port++);
+    cricket::Candidate candidate5(
+        "", cricket::ICE_CANDIDATE_COMPONENT_RTP,
+        "udp", v6_address, kCandidatePriority,
+        "", "", cricket::LOCAL_PORT_TYPE,
+        "", kCandidateGeneration, kCandidateFoundation2);
+    v6_address.SetPort(port++);
+    cricket::Candidate candidate6(
+        "", cricket::ICE_CANDIDATE_COMPONENT_RTCP,
+        "udp", v6_address, kCandidatePriority,
+        "", "", cricket::LOCAL_PORT_TYPE,
+        "", kCandidateGeneration, kCandidateFoundation2);
+    v6_address.SetPort(port++);
+    cricket::Candidate candidate7(
+        "", cricket::ICE_CANDIDATE_COMPONENT_RTCP,
+        "udp", v6_address, kCandidatePriority,
+        "", "", cricket::LOCAL_PORT_TYPE,
+        "", kCandidateGeneration, kCandidateFoundation2);
+    v6_address.SetPort(port++);
+    cricket::Candidate candidate8(
+        "", cricket::ICE_CANDIDATE_COMPONENT_RTP,
+        "udp", v6_address, kCandidatePriority,
+        "", "", cricket::LOCAL_PORT_TYPE,
+        "", kCandidateGeneration, kCandidateFoundation2);
+
+    // stun
+    int port_stun = 2345;
+    talk_base::SocketAddress address_stun("74.125.127.126", port_stun++);
+    talk_base::SocketAddress rel_address_stun("192.168.1.5", port_stun++);
+    cricket::Candidate candidate9
+        ("", cricket::ICE_CANDIDATE_COMPONENT_RTP,
+         "udp", address_stun, kCandidatePriority,
+         "", "", STUN_PORT_TYPE,
+         "", kCandidateGeneration, kCandidateFoundation3);
+    candidate9.set_related_address(rel_address_stun);
+
+    address_stun.SetPort(port_stun++);
+    rel_address_stun.SetPort(port_stun++);
+    cricket::Candidate candidate10(
+        "", cricket::ICE_CANDIDATE_COMPONENT_RTCP,
+        "udp", address_stun, kCandidatePriority,
+        "", "", STUN_PORT_TYPE,
+        "", kCandidateGeneration, kCandidateFoundation3);
+    candidate10.set_related_address(rel_address_stun);
+
+    // relay
+    int port_relay = 3456;
+    talk_base::SocketAddress address_relay("74.125.224.39", port_relay++);
+    cricket::Candidate candidate11(
+        "", cricket::ICE_CANDIDATE_COMPONENT_RTCP,
+        "udp", address_relay, kCandidatePriority,
+        "", "",
+        cricket::RELAY_PORT_TYPE, "",
+        kCandidateGeneration, kCandidateFoundation4);
+    address_relay.SetPort(port_relay++);
+    cricket::Candidate candidate12(
+        "", cricket::ICE_CANDIDATE_COMPONENT_RTP,
+        "udp", address_relay, kCandidatePriority,
+        "", "",
+        RELAY_PORT_TYPE, "",
+        kCandidateGeneration, kCandidateFoundation4);
+
+    // voice
+    candidates_.push_back(candidate1);
+    candidates_.push_back(candidate2);
+    candidates_.push_back(candidate5);
+    candidates_.push_back(candidate6);
+    candidates_.push_back(candidate9);
+    candidates_.push_back(candidate10);
+
+    // video
+    candidates_.push_back(candidate3);
+    candidates_.push_back(candidate4);
+    candidates_.push_back(candidate7);
+    candidates_.push_back(candidate8);
+    candidates_.push_back(candidate11);
+    candidates_.push_back(candidate12);
+
+    jcandidate_.reset(new JsepIceCandidate(std::string("audio_content_name"),
+                                           0, candidate1));
+
+    // Set up JsepSessionDescription.
+    jdesc_.Initialize(desc_.Copy(), kSessionId, kSessionVersion);
+    std::string mline_id;
+    int mline_index = 0;
+    for (size_t i = 0; i< candidates_.size(); ++i) {
+      // In this test, the audio m line index will be 0, and the video m line
+      // will be 1.
+      bool is_video = (i > 5);
+      mline_id = is_video ? "video_content_name" : "audio_content_name";
+      mline_index = is_video ? 1 : 0;
+      JsepIceCandidate jice(mline_id,
+                            mline_index,
+                            candidates_.at(i));
+      jdesc_.AddCandidate(&jice);
+    }
+  }
+
+  AudioContentDescription* CreateAudioContentDescription() {
+    AudioContentDescription* audio = new AudioContentDescription();
+    audio->set_rtcp_mux(true);
+    StreamParams audio_stream1;
+    audio_stream1.id = kAudioTrackId1;
+    audio_stream1.cname = kStream1Cname;
+    audio_stream1.sync_label = kStreamLabel1;
+    audio_stream1.ssrcs.push_back(kAudioTrack1Ssrc);
+    audio->AddStream(audio_stream1);
+    StreamParams audio_stream2;
+    audio_stream2.id = kAudioTrackId2;
+    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",
+        "dummy_session_params"));
+    audio->set_protocol(cricket::kMediaProtocolSavpf);
+    return audio;
+  }
+
+  template <class MCD>
+  void CompareMediaContentDescription(const MCD* cd1,
+                                      const MCD* cd2) {
+    // type
+    EXPECT_EQ(cd1->type(), cd1->type());
+
+    // content direction
+    EXPECT_EQ(cd1->direction(), cd2->direction());
+
+    // rtcp_mux
+    EXPECT_EQ(cd1->rtcp_mux(), cd2->rtcp_mux());
+
+    // cryptos
+    EXPECT_EQ(cd1->cryptos().size(), cd2->cryptos().size());
+    if (cd1->cryptos().size() != cd2->cryptos().size()) {
+      ADD_FAILURE();
+      return;
+    }
+    for (size_t i = 0; i< cd1->cryptos().size(); ++i) {
+      const CryptoParams c1 = cd1->cryptos().at(i);
+      const CryptoParams c2 = cd2->cryptos().at(i);
+      EXPECT_TRUE(c1.Matches(c2));
+      EXPECT_EQ(c1.key_params, c2.key_params);
+      EXPECT_EQ(c1.session_params, c2.session_params);
+    }
+    // protocol
+    EXPECT_EQ(cd1->protocol(), cd2->protocol());
+
+    // codecs
+    EXPECT_EQ(cd1->codecs(), cd2->codecs());
+
+    // bandwidth
+    EXPECT_EQ(cd1->bandwidth(), cd2->bandwidth());
+
+    // streams
+    EXPECT_EQ(cd1->streams(), cd2->streams());
+
+    // extmap
+    ASSERT_EQ(cd1->rtp_header_extensions().size(),
+              cd2->rtp_header_extensions().size());
+    for (size_t i = 0; i< cd1->rtp_header_extensions().size(); ++i) {
+      const RtpHeaderExtension ext1 = cd1->rtp_header_extensions().at(i);
+      const RtpHeaderExtension ext2 = cd2->rtp_header_extensions().at(i);
+      EXPECT_EQ(ext1.uri, ext2.uri);
+      EXPECT_EQ(ext1.id, ext2.id);
+    }
+
+    // buffered mode latency
+    EXPECT_EQ(cd1->buffered_mode_latency(), cd2->buffered_mode_latency());
+  }
+
+
+  void CompareSessionDescription(const SessionDescription& desc1,
+                                 const SessionDescription& desc2) {
+    // Compare content descriptions.
+    if (desc1.contents().size() != desc2.contents().size()) {
+      ADD_FAILURE();
+      return;
+    }
+    for (size_t i = 0 ; i < desc1.contents().size(); ++i) {
+      const cricket::ContentInfo& c1 = desc1.contents().at(i);
+      const cricket::ContentInfo& c2 = desc2.contents().at(i);
+      // content name
+      EXPECT_EQ(c1.name, c2.name);
+      // content type
+      // Note, ASSERT will return from the function, but will not stop the test.
+      ASSERT_EQ(c1.type, c2.type);
+
+      ASSERT_EQ(IsAudioContent(&c1), IsAudioContent(&c2));
+      if (IsAudioContent(&c1)) {
+        const AudioContentDescription* acd1 =
+            static_cast<const AudioContentDescription*>(c1.description);
+        const AudioContentDescription* acd2 =
+            static_cast<const AudioContentDescription*>(c2.description);
+        CompareMediaContentDescription<AudioContentDescription>(acd1, acd2);
+      }
+
+      ASSERT_EQ(IsVideoContent(&c1), IsVideoContent(&c2));
+      if (IsVideoContent(&c1)) {
+        const VideoContentDescription* vcd1 =
+            static_cast<const VideoContentDescription*>(c1.description);
+        const VideoContentDescription* vcd2 =
+            static_cast<const VideoContentDescription*>(c2.description);
+        CompareMediaContentDescription<VideoContentDescription>(vcd1, vcd2);
+      }
+
+      ASSERT_EQ(IsDataContent(&c1), IsDataContent(&c2));
+      if (IsDataContent(&c1)) {
+        const DataContentDescription* dcd1 =
+            static_cast<const DataContentDescription*>(c1.description);
+        const DataContentDescription* dcd2 =
+            static_cast<const DataContentDescription*>(c2.description);
+        CompareMediaContentDescription<DataContentDescription>(dcd1, dcd2);
+      }
+    }
+
+    // group
+    const cricket::ContentGroups groups1 = desc1.groups();
+    const cricket::ContentGroups groups2 = desc2.groups();
+    EXPECT_EQ(groups1.size(), groups1.size());
+    if (groups1.size() != groups2.size()) {
+      ADD_FAILURE();
+      return;
+    }
+    for (size_t i = 0; i < groups1.size(); ++i) {
+      const cricket::ContentGroup group1 = groups1.at(i);
+      const cricket::ContentGroup group2 = groups2.at(i);
+      EXPECT_EQ(group1.semantics(), group2.semantics());
+      const cricket::ContentNames names1 = group1.content_names();
+      const cricket::ContentNames names2 = group2.content_names();
+      EXPECT_EQ(names1.size(), names2.size());
+      if (names1.size() != names2.size()) {
+        ADD_FAILURE();
+        return;
+      }
+      cricket::ContentNames::const_iterator iter1 = names1.begin();
+      cricket::ContentNames::const_iterator iter2 = names2.begin();
+      while (iter1 != names1.end()) {
+        EXPECT_EQ(*iter1++, *iter2++);
+      }
+    }
+
+    // transport info
+    const cricket::TransportInfos transports1 = desc1.transport_infos();
+    const cricket::TransportInfos transports2 = desc2.transport_infos();
+    EXPECT_EQ(transports1.size(), transports2.size());
+    if (transports1.size() != transports2.size()) {
+      ADD_FAILURE();
+      return;
+    }
+    for (size_t i = 0; i < transports1.size(); ++i) {
+      const cricket::TransportInfo transport1 = transports1.at(i);
+      const cricket::TransportInfo transport2 = transports2.at(i);
+      EXPECT_EQ(transport1.content_name, transport2.content_name);
+      EXPECT_EQ(transport1.description.transport_type,
+                transport2.description.transport_type);
+      EXPECT_EQ(transport1.description.ice_ufrag,
+                transport2.description.ice_ufrag);
+      EXPECT_EQ(transport1.description.ice_pwd,
+                transport2.description.ice_pwd);
+      if (transport1.description.identity_fingerprint) {
+        EXPECT_EQ(*transport1.description.identity_fingerprint,
+                  *transport2.description.identity_fingerprint);
+      } else {
+        EXPECT_EQ(transport1.description.identity_fingerprint.get(),
+                  transport2.description.identity_fingerprint.get());
+      }
+      EXPECT_EQ(transport1.description.transport_options,
+                transport2.description.transport_options);
+      EXPECT_TRUE(CompareCandidates(transport1.description.candidates,
+                                    transport2.description.candidates));
+    }
+  }
+
+  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 Candidate c1 = cs1.at(i);
+      const Candidate c2 = cs2.at(i);
+      EXPECT_TRUE(c1.IsEquivalent(c2));
+    }
+    return true;
+  }
+
+  bool CompareSessionDescription(
+      const JsepSessionDescription& desc1,
+      const JsepSessionDescription& desc2) {
+    EXPECT_EQ(desc1.session_id(), desc2.session_id());
+    EXPECT_EQ(desc1.session_version(), desc2.session_version());
+    CompareSessionDescription(*desc1.description(), *desc2.description());
+    if (desc1.number_of_mediasections() != desc2.number_of_mediasections())
+      return false;
+    for (size_t i = 0; i < desc1.number_of_mediasections(); ++i) {
+      const IceCandidateCollection* cc1 = desc1.candidates(i);
+      const IceCandidateCollection* cc2 = desc2.candidates(i);
+      if (cc1->count() != cc2->count())
+        return false;
+      for (size_t j = 0; j < cc1->count(); ++j) {
+        const IceCandidateInterface* c1 = cc1->at(j);
+        const IceCandidateInterface* c2 = cc2->at(j);
+        EXPECT_EQ(c1->sdp_mid(), c2->sdp_mid());
+        EXPECT_EQ(c1->sdp_mline_index(), c2->sdp_mline_index());
+        EXPECT_TRUE(c1->candidate().IsEquivalent(c2->candidate()));
+      }
+    }
+    return true;
+  }
+
+  // Disable the ice-ufrag and ice-pwd in given |sdp| message by replacing
+  // them with invalid keywords so that the parser will just ignore them.
+  bool RemoveCandidateUfragPwd(std::string* sdp) {
+    const char ice_ufrag[] = "a=ice-ufrag";
+    const char ice_ufragx[] = "a=xice-ufrag";
+    const char ice_pwd[] = "a=ice-pwd";
+    const char ice_pwdx[] = "a=xice-pwd";
+    talk_base::replace_substrs(ice_ufrag, strlen(ice_ufrag),
+        ice_ufragx, strlen(ice_ufragx), sdp);
+    talk_base::replace_substrs(ice_pwd, strlen(ice_pwd),
+        ice_pwdx, strlen(ice_pwdx), sdp);
+    return true;
+  }
+
+  // Update the candidates in |jdesc| to use the given |ufrag| and |pwd|.
+  bool UpdateCandidateUfragPwd(JsepSessionDescription* jdesc, int mline_index,
+      const std::string& ufrag, const std::string& pwd) {
+    std::string content_name;
+    if (mline_index == 0) {
+      content_name = kAudioContentName;
+    } else if (mline_index == 1) {
+      content_name = kVideoContentName;
+    } else {
+      ASSERT(false);
+    }
+    TransportInfo transport_info(
+        content_name, TransportDescription(NS_JINGLE_ICE_UDP,
+                                           std::vector<std::string>(),
+                                           ufrag, pwd, cricket::ICEMODE_FULL,
+                                           NULL, Candidates()));
+    SessionDescription* desc =
+        const_cast<SessionDescription*>(jdesc->description());
+    desc->RemoveTransportInfoByName(content_name);
+    EXPECT_TRUE(desc->AddTransportInfo(transport_info));
+    for (size_t i = 0; i < jdesc_.number_of_mediasections(); ++i) {
+      const IceCandidateCollection* cc = jdesc_.candidates(i);
+      for (size_t j = 0; j < cc->count(); ++j) {
+        if (cc->at(j)->sdp_mline_index() == mline_index) {
+          const_cast<Candidate&>(cc->at(j)->candidate()).set_username(
+              ufrag);
+          const_cast<Candidate&>(cc->at(j)->candidate()).set_password(
+              pwd);
+        }
+      }
+    }
+    return true;
+  }
+
+  void AddIceOptions(const std::string& content_name,
+                     const std::vector<std::string>& transport_options) {
+    ASSERT_TRUE(desc_.GetTransportInfoByName(content_name) != NULL);
+    cricket::TransportInfo transport_info =
+        *(desc_.GetTransportInfoByName(content_name));
+    desc_.RemoveTransportInfoByName(content_name);
+    transport_info.description.transport_options = transport_options;
+    desc_.AddTransportInfo(transport_info);
+  }
+
+  void AddFingerprint() {
+    desc_.RemoveTransportInfoByName(kAudioContentName);
+    desc_.RemoveTransportInfoByName(kVideoContentName);
+    talk_base::SSLFingerprint fingerprint(talk_base::DIGEST_SHA_1,
+                                          kIdentityDigest,
+                                          sizeof(kIdentityDigest));
+    EXPECT_TRUE(desc_.AddTransportInfo(
+        TransportInfo(kAudioContentName,
+                      TransportDescription(NS_JINGLE_ICE_UDP,
+                                           std::vector<std::string>(),
+                                           kCandidateUfragVoice,
+                                           kCandidatePwdVoice,
+                                           cricket::ICEMODE_FULL, &fingerprint,
+                                           Candidates()))));
+    EXPECT_TRUE(desc_.AddTransportInfo(
+        TransportInfo(kVideoContentName,
+                      TransportDescription(NS_JINGLE_ICE_UDP,
+                                           std::vector<std::string>(),
+                                           kCandidateUfragVideo,
+                                           kCandidatePwdVideo,
+                                           cricket::ICEMODE_FULL, &fingerprint,
+                                           Candidates()))));
+  }
+
+  void AddExtmap() {
+    audio_desc_ = static_cast<AudioContentDescription*>(
+        audio_desc_->Copy());
+    video_desc_ = static_cast<VideoContentDescription*>(
+        video_desc_->Copy());
+    audio_desc_->AddRtpHeaderExtension(
+        RtpHeaderExtension(kExtmapUri, kExtmapId));
+    video_desc_->AddRtpHeaderExtension(
+        RtpHeaderExtension(kExtmapUri, kExtmapId));
+    desc_.RemoveContentByName(kAudioContentName);
+    desc_.RemoveContentByName(kVideoContentName);
+    desc_.AddContent(kAudioContentName, NS_JINGLE_RTP, audio_desc_);
+    desc_.AddContent(kVideoContentName, NS_JINGLE_RTP, video_desc_);
+  }
+
+  void RemoveCryptos() {
+    audio_desc_->set_cryptos(std::vector<CryptoParams>());
+    video_desc_->set_cryptos(std::vector<CryptoParams>());
+  }
+
+  bool TestSerializeDirection(cricket::MediaContentDirection direction) {
+    audio_desc_->set_direction(direction);
+    video_desc_->set_direction(direction);
+    std::string new_sdp = kSdpFullString;
+    ReplaceDirection(direction, &new_sdp);
+
+    if (!jdesc_.Initialize(desc_.Copy(),
+                           jdesc_.session_id(),
+                           jdesc_.session_version())) {
+      return false;
+    }
+    std::string message = webrtc::SdpSerialize(jdesc_);
+    EXPECT_EQ(new_sdp, message);
+    return true;
+  }
+
+  bool TestSerializeRejected(bool audio_rejected, bool video_rejected) {
+    audio_desc_ = static_cast<AudioContentDescription*>(
+        audio_desc_->Copy());
+    video_desc_ = static_cast<VideoContentDescription*>(
+        video_desc_->Copy());
+    desc_.RemoveContentByName(kAudioContentName);
+    desc_.RemoveContentByName(kVideoContentName);
+    desc_.AddContent(kAudioContentName, NS_JINGLE_RTP, audio_rejected,
+                     audio_desc_);
+    desc_.AddContent(kVideoContentName, NS_JINGLE_RTP, video_rejected,
+                     video_desc_);
+    std::string new_sdp = kSdpFullString;
+    ReplaceRejected(audio_rejected, video_rejected, &new_sdp);
+
+    if (!jdesc_.Initialize(desc_.Copy(),
+                           jdesc_.session_id(),
+                           jdesc_.session_version())) {
+      return false;
+    }
+    std::string message = webrtc::SdpSerialize(jdesc_);
+    EXPECT_EQ(new_sdp, message);
+    return true;
+  }
+
+  void AddSctpDataChannel() {
+    talk_base::scoped_ptr<DataContentDescription> data(
+        new DataContentDescription());
+    data_desc_ = data.get();
+    data_desc_->set_protocol(cricket::kMediaProtocolDtlsSctp);
+    desc_.AddContent(kDataContentName, NS_JINGLE_DRAFT_SCTP, data.release());
+    EXPECT_TRUE(desc_.AddTransportInfo(
+           TransportInfo(kDataContentName,
+                         TransportDescription(NS_JINGLE_ICE_UDP,
+                                              std::vector<std::string>(),
+                                              kCandidateUfragData,
+                                              kCandidatePwdData,
+                                              cricket::ICEMODE_FULL,
+                                              NULL, Candidates()))));
+  }
+
+  void AddRtpDataChannel() {
+    talk_base::scoped_ptr<DataContentDescription> data(
+        new DataContentDescription());
+    data_desc_ = data.get();
+
+    data_desc_->AddCodec(DataCodec(101, "google-data", 1));
+    StreamParams data_stream;
+    data_stream.id = kDataChannelMsid;
+    data_stream.cname = kDataChannelCname;
+    data_stream.sync_label = kDataChannelLabel;
+    data_stream.ssrcs.push_back(kDataChannelSsrc);
+    data_desc_->AddStream(data_stream);
+    data_desc_->AddCrypto(CryptoParams(
+        1, "AES_CM_128_HMAC_SHA1_80",
+        "inline:FvLcvU2P3ZWmQxgPAgcDu7Zl9vftYElFOjEzhWs5", ""));
+    data_desc_->set_protocol(cricket::kMediaProtocolSavpf);
+    desc_.AddContent(kDataContentName, NS_JINGLE_RTP, data.release());
+    EXPECT_TRUE(desc_.AddTransportInfo(
+           TransportInfo(kDataContentName,
+                         TransportDescription(NS_JINGLE_ICE_UDP,
+                                              std::vector<std::string>(),
+                                              kCandidateUfragData,
+                                              kCandidatePwdData,
+                                              cricket::ICEMODE_FULL,
+                                              NULL, Candidates()))));
+  }
+
+  bool TestDeserializeDirection(cricket::MediaContentDirection direction) {
+    std::string new_sdp = kSdpFullString;
+    ReplaceDirection(direction, &new_sdp);
+    JsepSessionDescription new_jdesc(kDummyString);
+
+    EXPECT_TRUE(SdpDeserialize(new_sdp, &new_jdesc));
+
+    audio_desc_->set_direction(direction);
+    video_desc_->set_direction(direction);
+    if (!jdesc_.Initialize(desc_.Copy(),
+                           jdesc_.session_id(),
+                           jdesc_.session_version())) {
+      return false;
+    }
+    EXPECT_TRUE(CompareSessionDescription(jdesc_, new_jdesc));
+    return true;
+  }
+
+  bool TestDeserializeRejected(bool audio_rejected, bool video_rejected) {
+    std::string new_sdp = kSdpFullString;
+    ReplaceRejected(audio_rejected, video_rejected, &new_sdp);
+    JsepSessionDescription new_jdesc(JsepSessionDescription::kOffer);
+
+    EXPECT_TRUE(SdpDeserialize(new_sdp, &new_jdesc));
+    audio_desc_ = static_cast<AudioContentDescription*>(
+        audio_desc_->Copy());
+    video_desc_ = static_cast<VideoContentDescription*>(
+        video_desc_->Copy());
+    desc_.RemoveContentByName(kAudioContentName);
+    desc_.RemoveContentByName(kVideoContentName);
+    desc_.AddContent(kAudioContentName, NS_JINGLE_RTP, audio_rejected,
+                     audio_desc_);
+    desc_.AddContent(kVideoContentName, NS_JINGLE_RTP, video_rejected,
+                     video_desc_);
+    if (!jdesc_.Initialize(desc_.Copy(),
+                           jdesc_.session_id(),
+                           jdesc_.session_version())) {
+      return false;
+    }
+    EXPECT_TRUE(CompareSessionDescription(jdesc_, new_jdesc));
+    return true;
+  }
+
+  void TestDeserializeExtmap(bool session_level, bool media_level) {
+    AddExtmap();
+    JsepSessionDescription new_jdesc("dummy");
+    ASSERT_TRUE(new_jdesc.Initialize(desc_.Copy(),
+                                     jdesc_.session_id(),
+                                     jdesc_.session_version()));
+    JsepSessionDescription jdesc_with_extmap("dummy");
+    std::string sdp_with_extmap = kSdpString;
+    if (session_level) {
+      InjectAfter(kSessionTime, kExtmapWithDirectionAndAttribute,
+                  &sdp_with_extmap);
+    }
+    if (media_level) {
+      InjectAfter(kAttributeIcePwdVoice, kExtmapWithDirectionAndAttribute,
+                  &sdp_with_extmap);
+      InjectAfter(kAttributeIcePwdVideo, kExtmapWithDirectionAndAttribute,
+                  &sdp_with_extmap);
+    }
+    // The extmap can't be present at the same time in both session level and
+    // media level.
+    if (session_level && media_level) {
+      SdpParseError error;
+      EXPECT_FALSE(webrtc::SdpDeserialize(sdp_with_extmap,
+                   &jdesc_with_extmap, &error));
+      EXPECT_NE(std::string::npos, error.description.find("a=extmap"));
+    } else {
+      EXPECT_TRUE(SdpDeserialize(sdp_with_extmap, &jdesc_with_extmap));
+      EXPECT_TRUE(CompareSessionDescription(jdesc_with_extmap, new_jdesc));
+    }
+  }
+
+  void VerifyCodecParameter(const cricket::CodecParameterMap& params,
+      const std::string& name, int expected_value) {
+    cricket::CodecParameterMap::const_iterator found = params.find(name);
+    ASSERT_TRUE(found != params.end());
+    EXPECT_EQ(found->second, talk_base::ToString<int>(expected_value));
+  }
+
+  void TestDeserializeCodecParams(const CodecParams& params,
+                                  JsepSessionDescription* jdesc_output) {
+    std::string sdp =
+        "v=0\r\n"
+        "o=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1\r\n"
+        "s=-\r\n"
+        "t=0 0\r\n"
+        // Include semantics for WebRTC Media Streams since it is supported by
+        // this parser, and will be added to the SDP when serializing a session
+        // description.
+        "a=msid-semantic: WMS\r\n"
+        // Pl type 111 preferred.
+        "m=audio 1 RTP/SAVPF 111 104 103 102\r\n"
+        // Pltype 111 listed before 103 and 104 in the map.
+        "a=rtpmap:111 opus/48000/2\r\n"
+        // Pltype 103 listed before 104.
+        "a=rtpmap:103 ISAC/16000\r\n"
+        "a=rtpmap:104 CELT/32000/2\r\n"
+        "a=rtpmap:102 ISAC/32000/1\r\n"
+        "a=fmtp:111 0-15,66,70 ";
+    std::ostringstream os;
+    os << "minptime=" << params.min_ptime << " stereo=" << params.stereo
+       << " sprop-stereo=" << params.sprop_stereo
+       << " useinbandfec=" << params.useinband << "\r\n"
+       << "a=ptime:" << params.ptime << "\r\n"
+       << "a=maxptime:" << params.max_ptime << "\r\n";
+    sdp += os.str();
+
+    // Deserialize
+    SdpParseError error;
+    EXPECT_TRUE(webrtc::SdpDeserialize(sdp, jdesc_output, &error));
+
+    const ContentInfo* ac = GetFirstAudioContent(jdesc_output->description());
+    ASSERT_TRUE(ac != NULL);
+    const AudioContentDescription* acd =
+        static_cast<const AudioContentDescription*>(ac->description);
+    ASSERT_FALSE(acd->codecs().empty());
+    cricket::AudioCodec opus = acd->codecs()[0];
+    EXPECT_EQ("opus", opus.name);
+    EXPECT_EQ(111, opus.id);
+    VerifyCodecParameter(opus.params, "minptime", params.min_ptime);
+    VerifyCodecParameter(opus.params, "stereo", params.stereo);
+    VerifyCodecParameter(opus.params, "sprop-stereo", params.sprop_stereo);
+    VerifyCodecParameter(opus.params, "useinbandfec", params.useinband);
+    for (size_t i = 0; i < acd->codecs().size(); ++i) {
+      cricket::AudioCodec codec = acd->codecs()[i];
+      VerifyCodecParameter(codec.params, "ptime", params.ptime);
+      VerifyCodecParameter(codec.params, "maxptime", params.max_ptime);
+      if (codec.name == "ISAC") {
+        if (codec.clockrate == 16000) {
+          EXPECT_EQ(32000, codec.bitrate);
+        } else {
+          EXPECT_EQ(56000, codec.bitrate);
+        }
+      }
+    }
+  }
+
+  void TestDeserializeRtcpFb(JsepSessionDescription* jdesc_output,
+                             bool use_wildcard) {
+    std::string sdp =
+        "v=0\r\n"
+        "o=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1\r\n"
+        "s=-\r\n"
+        "t=0 0\r\n"
+        // Include semantics for WebRTC Media Streams since it is supported by
+        // this parser, and will be added to the SDP when serializing a session
+        // description.
+        "a=msid-semantic: WMS\r\n"
+        "m=audio 1 RTP/SAVPF 111\r\n"
+        "a=rtpmap:111 opus/48000/2\r\n"
+        "a=rtcp-fb:111 nack\r\n"
+        "m=video 3457 RTP/SAVPF 101\r\n"
+        "a=rtpmap:101 VP8/90000\r\n"
+        "a=rtcp-fb:101 nack\r\n"
+        "a=rtcp-fb:101 goog-remb\r\n"
+        "a=rtcp-fb:101 ccm fir\r\n";
+    std::ostringstream os;
+    os << "a=rtcp-fb:" << (use_wildcard ? "*" : "101") <<  " ccm fir\r\n";
+    sdp += os.str();
+    // Deserialize
+    SdpParseError error;
+    EXPECT_TRUE(webrtc::SdpDeserialize(sdp, jdesc_output, &error));
+    const ContentInfo* ac = GetFirstAudioContent(jdesc_output->description());
+    ASSERT_TRUE(ac != NULL);
+    const AudioContentDescription* acd =
+        static_cast<const AudioContentDescription*>(ac->description);
+    ASSERT_FALSE(acd->codecs().empty());
+    cricket::AudioCodec opus = acd->codecs()[0];
+    EXPECT_EQ(111, opus.id);
+    EXPECT_TRUE(opus.HasFeedbackParam(
+        cricket::FeedbackParam(cricket::kRtcpFbParamNack,
+                               cricket::kParamValueEmpty)));
+
+    const ContentInfo* vc = GetFirstVideoContent(jdesc_output->description());
+    ASSERT_TRUE(vc != NULL);
+    const VideoContentDescription* vcd =
+        static_cast<const VideoContentDescription*>(vc->description);
+    ASSERT_FALSE(vcd->codecs().empty());
+    cricket::VideoCodec vp8 = vcd->codecs()[0];
+    EXPECT_STREQ(webrtc::JsepSessionDescription::kDefaultVideoCodecName,
+                 vp8.name.c_str());
+    EXPECT_EQ(101, vp8.id);
+    EXPECT_TRUE(vp8.HasFeedbackParam(
+        cricket::FeedbackParam(cricket::kRtcpFbParamNack,
+                               cricket::kParamValueEmpty)));
+    EXPECT_TRUE(vp8.HasFeedbackParam(
+        cricket::FeedbackParam(cricket::kRtcpFbParamRemb,
+                               cricket::kParamValueEmpty)));
+    EXPECT_TRUE(vp8.HasFeedbackParam(
+        cricket::FeedbackParam(cricket::kRtcpFbParamCcm,
+                               cricket::kRtcpFbCcmParamFir)));
+  }
+
+  // Two SDP messages can mean the same thing but be different strings, e.g.
+  // some of the lines can be serialized in different order.
+  // However, a deserialized description can be compared field by field and has
+  // no order. If deserializer has already been tested, serializing then
+  // deserializing and comparing JsepSessionDescription will test
+  // the serializer sufficiently.
+  void TestSerialize(const JsepSessionDescription& jdesc) {
+    std::string message = webrtc::SdpSerialize(jdesc);
+    JsepSessionDescription jdesc_output_des(kDummyString);
+    SdpParseError error;
+    EXPECT_TRUE(webrtc::SdpDeserialize(message, &jdesc_output_des, &error));
+    EXPECT_TRUE(CompareSessionDescription(jdesc, jdesc_output_des));
+  }
+
+ protected:
+  SessionDescription desc_;
+  AudioContentDescription* audio_desc_;
+  VideoContentDescription* video_desc_;
+  DataContentDescription* data_desc_;
+  Candidates candidates_;
+  talk_base::scoped_ptr<IceCandidateInterface> jcandidate_;
+  JsepSessionDescription jdesc_;
+};
+
+void TestMismatch(const std::string& string1, const std::string& string2) {
+  int position = 0;
+  for (size_t i = 0; i < string1.length() && i < string2.length(); ++i) {
+    if (string1.c_str()[i] != string2.c_str()[i]) {
+      position = i;
+      break;
+    }
+  }
+  EXPECT_EQ(0, position) << "Strings mismatch at the " << position
+                         << " character\n"
+                         << " 1: " << string1.substr(position, 20) << "\n"
+                         << " 2: " << string2.substr(position, 20) << "\n";
+}
+
+std::string GetLine(const std::string& message,
+                    const std::string& session_description_name) {
+  size_t start = message.find(session_description_name);
+  if (std::string::npos == start) {
+    return "";
+  }
+  size_t stop = message.find("\r\n", start);
+  if (std::string::npos == stop) {
+    return "";
+  }
+  if (stop <= start) {
+    return "";
+  }
+  return message.substr(start, stop - start);
+}
+
+TEST_F(WebRtcSdpTest, SerializeSessionDescription) {
+  // SessionDescription with desc and candidates.
+  std::string message = webrtc::SdpSerialize(jdesc_);
+  TestMismatch(std::string(kSdpFullString), message);
+}
+
+TEST_F(WebRtcSdpTest, SerializeSessionDescriptionEmpty) {
+  JsepSessionDescription jdesc_empty(kDummyString);
+  EXPECT_EQ("", webrtc::SdpSerialize(jdesc_empty));
+}
+
+// This tests serialization of SDP with a=crypto and a=fingerprint, as would be
+// the case in a DTLS offer.
+TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithFingerprint) {
+  AddFingerprint();
+  JsepSessionDescription jdesc_with_fingerprint(kDummyString);
+  ASSERT_TRUE(jdesc_with_fingerprint.Initialize(desc_.Copy(),
+                                                kSessionId, kSessionVersion));
+  std::string message = webrtc::SdpSerialize(jdesc_with_fingerprint);
+
+  std::string sdp_with_fingerprint = kSdpString;
+  InjectAfter(kAttributeIcePwdVoice,
+              kFingerprint, &sdp_with_fingerprint);
+  InjectAfter(kAttributeIcePwdVideo,
+              kFingerprint, &sdp_with_fingerprint);
+
+  EXPECT_EQ(sdp_with_fingerprint, message);
+}
+
+// This tests serialization of SDP with a=fingerprint with no a=crypto, as would
+// be the case in a DTLS answer.
+TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithFingerprintNoCryptos) {
+  AddFingerprint();
+  RemoveCryptos();
+  JsepSessionDescription jdesc_with_fingerprint(kDummyString);
+  ASSERT_TRUE(jdesc_with_fingerprint.Initialize(desc_.Copy(),
+                                                kSessionId, kSessionVersion));
+  std::string message = webrtc::SdpSerialize(jdesc_with_fingerprint);
+
+  std::string sdp_with_fingerprint = kSdpString;
+  Replace(kAttributeCryptoVoice, "", &sdp_with_fingerprint);
+  Replace(kAttributeCryptoVideo, "", &sdp_with_fingerprint);
+  InjectAfter(kAttributeIcePwdVoice,
+              kFingerprint, &sdp_with_fingerprint);
+  InjectAfter(kAttributeIcePwdVideo,
+              kFingerprint, &sdp_with_fingerprint);
+
+  EXPECT_EQ(sdp_with_fingerprint, message);
+}
+
+TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithoutCandidates) {
+  // JsepSessionDescription with desc but without candidates.
+  JsepSessionDescription jdesc_no_candidates(kDummyString);
+  ASSERT_TRUE(jdesc_no_candidates.Initialize(desc_.Copy(),
+                                             kSessionId, kSessionVersion));
+  std::string message = webrtc::SdpSerialize(jdesc_no_candidates);
+  EXPECT_EQ(std::string(kSdpString), message);
+}
+
+TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithBundle) {
+  ContentGroup group(cricket::GROUP_TYPE_BUNDLE);
+  group.AddContentName(kAudioContentName);
+  group.AddContentName(kVideoContentName);
+  desc_.AddGroup(group);
+  ASSERT_TRUE(jdesc_.Initialize(desc_.Copy(),
+                                jdesc_.session_id(),
+                                jdesc_.session_version()));
+  std::string message = webrtc::SdpSerialize(jdesc_);
+  std::string sdp_with_bundle = kSdpFullString;
+  InjectAfter(kSessionTime,
+              "a=group:BUNDLE audio_content_name video_content_name\r\n",
+              &sdp_with_bundle);
+  EXPECT_EQ(sdp_with_bundle, message);
+}
+
+TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithBandwidth) {
+  VideoContentDescription* vcd = static_cast<VideoContentDescription*>(
+      GetFirstVideoContent(&desc_)->description);
+  vcd->set_bandwidth(100 * 1000);
+  AudioContentDescription* acd = static_cast<AudioContentDescription*>(
+      GetFirstAudioContent(&desc_)->description);
+  acd->set_bandwidth(50 * 1000);
+  ASSERT_TRUE(jdesc_.Initialize(desc_.Copy(),
+                                jdesc_.session_id(),
+                                jdesc_.session_version()));
+  std::string message = webrtc::SdpSerialize(jdesc_);
+  std::string sdp_with_bandwidth = kSdpFullString;
+  InjectAfter("a=mid:video_content_name\r\na=sendrecv\r\n",
+              "b=AS:100\r\n",
+              &sdp_with_bandwidth);
+  InjectAfter("a=mid:audio_content_name\r\na=sendrecv\r\n",
+              "b=AS:50\r\n",
+              &sdp_with_bandwidth);
+  EXPECT_EQ(sdp_with_bandwidth, message);
+}
+
+TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithIceOptions) {
+  std::vector<std::string> transport_options;
+  transport_options.push_back(kIceOption1);
+  transport_options.push_back(kIceOption3);
+  AddIceOptions(kAudioContentName, transport_options);
+  transport_options.clear();
+  transport_options.push_back(kIceOption2);
+  transport_options.push_back(kIceOption3);
+  AddIceOptions(kVideoContentName, transport_options);
+  ASSERT_TRUE(jdesc_.Initialize(desc_.Copy(),
+                                jdesc_.session_id(),
+                                jdesc_.session_version()));
+  std::string message = webrtc::SdpSerialize(jdesc_);
+  std::string sdp_with_ice_options = kSdpFullString;
+  InjectAfter(kAttributeIcePwdVoice,
+              "a=ice-options:iceoption1 iceoption3\r\n",
+              &sdp_with_ice_options);
+  InjectAfter(kAttributeIcePwdVideo,
+              "a=ice-options:iceoption2 iceoption3\r\n",
+              &sdp_with_ice_options);
+  EXPECT_EQ(sdp_with_ice_options, message);
+}
+
+TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithRecvOnlyContent) {
+  EXPECT_TRUE(TestSerializeDirection(cricket::MD_RECVONLY));
+}
+
+TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithSendOnlyContent) {
+  EXPECT_TRUE(TestSerializeDirection(cricket::MD_SENDONLY));
+}
+
+TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithInactiveContent) {
+  EXPECT_TRUE(TestSerializeDirection(cricket::MD_INACTIVE));
+}
+
+TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithAudioRejected) {
+  EXPECT_TRUE(TestSerializeRejected(true, false));
+}
+
+TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithVideoRejected) {
+  EXPECT_TRUE(TestSerializeRejected(false, true));
+}
+
+TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithAudioVideoRejected) {
+  EXPECT_TRUE(TestSerializeRejected(true, true));
+}
+
+TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithRtpDataChannel) {
+  AddRtpDataChannel();
+  JsepSessionDescription jsep_desc(kDummyString);
+
+  ASSERT_TRUE(jsep_desc.Initialize(desc_.Copy(), kSessionId, kSessionVersion));
+  std::string message = webrtc::SdpSerialize(jsep_desc);
+
+  std::string expected_sdp = kSdpString;
+  expected_sdp.append(kSdpRtpDataChannelString);
+  EXPECT_EQ(expected_sdp, message);
+}
+
+TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithSctpDataChannel) {
+  AddSctpDataChannel();
+  JsepSessionDescription jsep_desc(kDummyString);
+
+  ASSERT_TRUE(jsep_desc.Initialize(desc_.Copy(), kSessionId, kSessionVersion));
+  std::string message = webrtc::SdpSerialize(jsep_desc);
+
+  std::string expected_sdp = kSdpString;
+  expected_sdp.append(kSdpSctpDataChannelString);
+  EXPECT_EQ(message, expected_sdp);
+}
+
+TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithExtmap) {
+  AddExtmap();
+  JsepSessionDescription desc_with_extmap("dummy");
+  ASSERT_TRUE(desc_with_extmap.Initialize(desc_.Copy(),
+                                          kSessionId, kSessionVersion));
+  std::string message = webrtc::SdpSerialize(desc_with_extmap);
+
+  std::string sdp_with_extmap = kSdpString;
+  InjectAfter("a=mid:audio_content_name\r\n",
+              kExtmap, &sdp_with_extmap);
+  InjectAfter("a=mid:video_content_name\r\n",
+              kExtmap, &sdp_with_extmap);
+
+  EXPECT_EQ(sdp_with_extmap, message);
+}
+
+
+TEST_F(WebRtcSdpTest, SerializeCandidates) {
+  std::string message = webrtc::SdpSerializeCandidate(*jcandidate_);
+  EXPECT_EQ(std::string(kSdpOneCandidate), message);
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSessionDescription) {
+  JsepSessionDescription jdesc(kDummyString);
+  // Deserialize
+  EXPECT_TRUE(SdpDeserialize(kSdpFullString, &jdesc));
+  // Verify
+  EXPECT_TRUE(CompareSessionDescription(jdesc_, jdesc));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithoutCarriageReturn) {
+  JsepSessionDescription jdesc(kDummyString);
+  std::string sdp_without_carriage_return = kSdpFullString;
+  Replace("\r\n", "\n", &sdp_without_carriage_return);
+  // Deserialize
+  EXPECT_TRUE(SdpDeserialize(sdp_without_carriage_return, &jdesc));
+  // Verify
+  EXPECT_TRUE(CompareSessionDescription(jdesc_, jdesc));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithoutCandidates) {
+  // SessionDescription with desc but without candidates.
+  JsepSessionDescription jdesc_no_candidates(kDummyString);
+  ASSERT_TRUE(jdesc_no_candidates.Initialize(desc_.Copy(),
+                                             kSessionId, kSessionVersion));
+  JsepSessionDescription new_jdesc(kDummyString);
+  EXPECT_TRUE(SdpDeserialize(kSdpString, &new_jdesc));
+  EXPECT_TRUE(CompareSessionDescription(jdesc_no_candidates, new_jdesc));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithoutRtpmap) {
+  static const char kSdpNoRtpmapString[] =
+      "v=0\r\n"
+      "o=- 11 22 IN IP4 127.0.0.1\r\n"
+      "s=-\r\n"
+      "t=0 0\r\n"
+      "m=audio 49232 RTP/AVP 0 18 103\r\n"
+      // Codec that doesn't appear in the m= line will be ignored.
+      "a=rtpmap:104 CELT/32000/2\r\n"
+      // The rtpmap line for static payload codec is optional.
+      "a=rtpmap:18 G729/16000\r\n"
+      "a=rtpmap:103 ISAC/16000\r\n";
+
+  JsepSessionDescription jdesc(kDummyString);
+  EXPECT_TRUE(SdpDeserialize(kSdpNoRtpmapString, &jdesc));
+  cricket::AudioContentDescription* audio =
+    static_cast<AudioContentDescription*>(
+        jdesc.description()->GetContentDescriptionByName(cricket::CN_AUDIO));
+  AudioCodecs ref_codecs;
+  // The codecs in the AudioContentDescription will be sorted by preference.
+  ref_codecs.push_back(AudioCodec(0, "PCMU", 8000, 0, 1, 3));
+  ref_codecs.push_back(AudioCodec(18, "G729", 16000, 0, 1, 2));
+  ref_codecs.push_back(AudioCodec(103, "ISAC", 16000, 32000, 1, 1));
+  EXPECT_EQ(ref_codecs, audio->codecs());
+}
+
+// Ensure that we can deserialize SDP with a=fingerprint properly.
+TEST_F(WebRtcSdpTest, DeserializeJsepSessionDescriptionWithFingerprint) {
+  // Add a DTLS a=fingerprint attribute to our session description.
+  AddFingerprint();
+  JsepSessionDescription new_jdesc(kDummyString);
+  ASSERT_TRUE(new_jdesc.Initialize(desc_.Copy(),
+                                   jdesc_.session_id(),
+                                   jdesc_.session_version()));
+
+  JsepSessionDescription jdesc_with_fingerprint(kDummyString);
+  std::string sdp_with_fingerprint = kSdpString;
+  InjectAfter(kAttributeIcePwdVoice, kFingerprint, &sdp_with_fingerprint);
+  InjectAfter(kAttributeIcePwdVideo, kFingerprint, &sdp_with_fingerprint);
+  EXPECT_TRUE(SdpDeserialize(sdp_with_fingerprint, &jdesc_with_fingerprint));
+  EXPECT_TRUE(CompareSessionDescription(jdesc_with_fingerprint, new_jdesc));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithBundle) {
+  JsepSessionDescription jdesc_with_bundle(kDummyString);
+  std::string sdp_with_bundle = kSdpFullString;
+  InjectAfter(kSessionTime,
+              "a=group:BUNDLE audio_content_name video_content_name\r\n",
+              &sdp_with_bundle);
+  EXPECT_TRUE(SdpDeserialize(sdp_with_bundle, &jdesc_with_bundle));
+  ContentGroup group(cricket::GROUP_TYPE_BUNDLE);
+  group.AddContentName(kAudioContentName);
+  group.AddContentName(kVideoContentName);
+  desc_.AddGroup(group);
+  ASSERT_TRUE(jdesc_.Initialize(desc_.Copy(),
+                                jdesc_.session_id(),
+                                jdesc_.session_version()));
+  EXPECT_TRUE(CompareSessionDescription(jdesc_, jdesc_with_bundle));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithBandwidth) {
+  JsepSessionDescription jdesc_with_bandwidth(kDummyString);
+  std::string sdp_with_bandwidth = kSdpFullString;
+  InjectAfter("a=mid:video_content_name\r\na=sendrecv\r\n",
+              "b=AS:100\r\n",
+              &sdp_with_bandwidth);
+  InjectAfter("a=mid:audio_content_name\r\na=sendrecv\r\n",
+              "b=AS:50\r\n",
+              &sdp_with_bandwidth);
+  EXPECT_TRUE(
+      SdpDeserialize(sdp_with_bandwidth, &jdesc_with_bandwidth));
+  VideoContentDescription* vcd = static_cast<VideoContentDescription*>(
+      GetFirstVideoContent(&desc_)->description);
+  vcd->set_bandwidth(100 * 1000);
+  AudioContentDescription* acd = static_cast<AudioContentDescription*>(
+      GetFirstAudioContent(&desc_)->description);
+  acd->set_bandwidth(50 * 1000);
+  ASSERT_TRUE(jdesc_.Initialize(desc_.Copy(),
+                                jdesc_.session_id(),
+                                jdesc_.session_version()));
+  EXPECT_TRUE(CompareSessionDescription(jdesc_, jdesc_with_bandwidth));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithIceOptions) {
+  JsepSessionDescription jdesc_with_ice_options(kDummyString);
+  std::string sdp_with_ice_options = kSdpFullString;
+  InjectAfter(kSessionTime,
+              "a=ice-options:iceoption3\r\n",
+              &sdp_with_ice_options);
+  InjectAfter(kAttributeIcePwdVoice,
+              "a=ice-options:iceoption1\r\n",
+              &sdp_with_ice_options);
+  InjectAfter(kAttributeIcePwdVideo,
+              "a=ice-options:iceoption2\r\n",
+              &sdp_with_ice_options);
+  EXPECT_TRUE(SdpDeserialize(sdp_with_ice_options, &jdesc_with_ice_options));
+  std::vector<std::string> transport_options;
+  transport_options.push_back(kIceOption3);
+  transport_options.push_back(kIceOption1);
+  AddIceOptions(kAudioContentName, transport_options);
+  transport_options.clear();
+  transport_options.push_back(kIceOption3);
+  transport_options.push_back(kIceOption2);
+  AddIceOptions(kVideoContentName, transport_options);
+  ASSERT_TRUE(jdesc_.Initialize(desc_.Copy(),
+                                jdesc_.session_id(),
+                                jdesc_.session_version()));
+  EXPECT_TRUE(CompareSessionDescription(jdesc_, jdesc_with_ice_options));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithUfragPwd) {
+  // Remove the original ice-ufrag and ice-pwd
+  JsepSessionDescription jdesc_with_ufrag_pwd(kDummyString);
+  std::string sdp_with_ufrag_pwd = kSdpFullString;
+  EXPECT_TRUE(RemoveCandidateUfragPwd(&sdp_with_ufrag_pwd));
+  // Add session level ufrag and pwd
+  InjectAfter(kSessionTime,
+      "a=ice-pwd:session+level+icepwd\r\n"
+      "a=ice-ufrag:session+level+iceufrag\r\n",
+      &sdp_with_ufrag_pwd);
+  // Add media level ufrag and pwd for audio
+  InjectAfter("a=mid:audio_content_name\r\n",
+      "a=ice-pwd:media+level+icepwd\r\na=ice-ufrag:media+level+iceufrag\r\n",
+      &sdp_with_ufrag_pwd);
+  // Update the candidate ufrag and pwd to the expected ones.
+  EXPECT_TRUE(UpdateCandidateUfragPwd(&jdesc_, 0,
+      "media+level+iceufrag", "media+level+icepwd"));
+  EXPECT_TRUE(UpdateCandidateUfragPwd(&jdesc_, 1,
+      "session+level+iceufrag", "session+level+icepwd"));
+  EXPECT_TRUE(SdpDeserialize(sdp_with_ufrag_pwd, &jdesc_with_ufrag_pwd));
+  EXPECT_TRUE(CompareSessionDescription(jdesc_, jdesc_with_ufrag_pwd));
+}
+
+
+TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithRecvOnlyContent) {
+  EXPECT_TRUE(TestDeserializeDirection(cricket::MD_RECVONLY));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithSendOnlyContent) {
+  EXPECT_TRUE(TestDeserializeDirection(cricket::MD_SENDONLY));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithInactiveContent) {
+  EXPECT_TRUE(TestDeserializeDirection(cricket::MD_INACTIVE));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithRejectedAudio) {
+  EXPECT_TRUE(TestDeserializeRejected(true, false));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithRejectedVideo) {
+  EXPECT_TRUE(TestDeserializeRejected(false, true));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithRejectedAudioVideo) {
+  EXPECT_TRUE(TestDeserializeRejected(true, true));
+}
+
+// Tests that we can still handle the sdp uses mslabel and label instead of
+// msid for backward compatibility.
+TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithoutMsid) {
+  JsepSessionDescription jdesc(kDummyString);
+  std::string sdp_without_msid = kSdpFullString;
+  Replace("msid", "xmsid", &sdp_without_msid);
+  // Deserialize
+  EXPECT_TRUE(SdpDeserialize(sdp_without_msid, &jdesc));
+  // Verify
+  EXPECT_TRUE(CompareSessionDescription(jdesc_, jdesc));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeCandidate) {
+  JsepIceCandidate jcandidate(kDummyMid, kDummyIndex);
+
+  std::string sdp = kSdpOneCandidate;
+  EXPECT_TRUE(SdpDeserializeCandidate(sdp, &jcandidate));
+  EXPECT_EQ(kDummyMid, jcandidate.sdp_mid());
+  EXPECT_EQ(kDummyIndex, jcandidate.sdp_mline_index());
+  EXPECT_TRUE(jcandidate.candidate().IsEquivalent(jcandidate_->candidate()));
+
+  // Candidate line without generation extension.
+  sdp = kSdpOneCandidate;
+  Replace(" generation 2", "", &sdp);
+  EXPECT_TRUE(SdpDeserializeCandidate(sdp, &jcandidate));
+  EXPECT_EQ(kDummyMid, jcandidate.sdp_mid());
+  EXPECT_EQ(kDummyIndex, jcandidate.sdp_mline_index());
+  Candidate expected = jcandidate_->candidate();
+  expected.set_generation(0);
+  EXPECT_TRUE(jcandidate.candidate().IsEquivalent(expected));
+
+  // Multiple candidate lines.
+  // Only the first line will be deserialized. The rest will be ignored.
+  sdp = kSdpOneCandidate;
+  sdp.append("a=candidate:1 2 tcp 1234 192.168.1.100 5678 typ host\r\n");
+  EXPECT_TRUE(SdpDeserializeCandidate(sdp, &jcandidate));
+  EXPECT_EQ(kDummyMid, jcandidate.sdp_mid());
+  EXPECT_EQ(kDummyIndex, jcandidate.sdp_mline_index());
+  EXPECT_TRUE(jcandidate.candidate().IsEquivalent(jcandidate_->candidate()));
+}
+
+// This test verifies the deserialization of candidate-attribute
+// as per RFC 5245. Candiate-attribute will be of the format
+// candidate:<blah>. This format will be used when candidates
+// are trickled.
+TEST_F(WebRtcSdpTest, DeserializeRawCandidateAttribute) {
+  JsepIceCandidate jcandidate(kDummyMid, kDummyIndex);
+
+  std::string candidate_attribute = kRawCandidate;
+  EXPECT_TRUE(SdpDeserializeCandidate(candidate_attribute, &jcandidate));
+  EXPECT_EQ(kDummyMid, jcandidate.sdp_mid());
+  EXPECT_EQ(kDummyIndex, jcandidate.sdp_mline_index());
+  EXPECT_TRUE(jcandidate.candidate().IsEquivalent(jcandidate_->candidate()));
+  EXPECT_EQ(2u, jcandidate.candidate().generation());
+
+  // Candidate line without generation extension.
+  candidate_attribute = kRawCandidate;
+  Replace(" generation 2", "", &candidate_attribute);
+  EXPECT_TRUE(SdpDeserializeCandidate(candidate_attribute, &jcandidate));
+  EXPECT_EQ(kDummyMid, jcandidate.sdp_mid());
+  EXPECT_EQ(kDummyIndex, jcandidate.sdp_mline_index());
+  Candidate expected = jcandidate_->candidate();
+  expected.set_generation(0);
+  EXPECT_TRUE(jcandidate.candidate().IsEquivalent(expected));
+
+  // Candidate line without candidate:
+  candidate_attribute = kRawCandidate;
+  Replace("candidate:", "", &candidate_attribute);
+  EXPECT_FALSE(SdpDeserializeCandidate(candidate_attribute, &jcandidate));
+
+  // Concatenating additional candidate. Expecting deserialization to fail.
+  candidate_attribute = kRawCandidate;
+  candidate_attribute.append("candidate:1 2 udp 1234 192.168.1.1 typ host");
+  EXPECT_FALSE(SdpDeserializeCandidate(candidate_attribute, &jcandidate));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSdpWithRtpDataChannels) {
+  AddRtpDataChannel();
+  JsepSessionDescription jdesc(kDummyString);
+  ASSERT_TRUE(jdesc.Initialize(desc_.Copy(), kSessionId, kSessionVersion));
+
+  std::string sdp_with_data = kSdpString;
+  sdp_with_data.append(kSdpRtpDataChannelString);
+  JsepSessionDescription jdesc_output(kDummyString);
+
+  // Deserialize
+  EXPECT_TRUE(SdpDeserialize(sdp_with_data, &jdesc_output));
+  // Verify
+  EXPECT_TRUE(CompareSessionDescription(jdesc, jdesc_output));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSdpWithSctpDataChannels) {
+  AddSctpDataChannel();
+  JsepSessionDescription jdesc(kDummyString);
+  ASSERT_TRUE(jdesc.Initialize(desc_.Copy(), kSessionId, kSessionVersion));
+
+  std::string sdp_with_data = kSdpString;
+  sdp_with_data.append(kSdpSctpDataChannelString);
+  JsepSessionDescription jdesc_output(kDummyString);
+
+  EXPECT_TRUE(SdpDeserialize(sdp_with_data, &jdesc_output));
+  EXPECT_TRUE(CompareSessionDescription(jdesc, jdesc_output));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithSessionLevelExtmap) {
+  TestDeserializeExtmap(true, false);
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithMediaLevelExtmap) {
+  TestDeserializeExtmap(false, true);
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithInvalidExtmap) {
+  TestDeserializeExtmap(true, true);
+}
+
+TEST_F(WebRtcSdpTest, DeserializeCandidateWithDifferentTransport) {
+  JsepIceCandidate jcandidate(kDummyMid, kDummyIndex);
+  std::string new_sdp = kSdpOneCandidate;
+  Replace("udp", "unsupported_transport", &new_sdp);
+  EXPECT_FALSE(SdpDeserializeCandidate(new_sdp, &jcandidate));
+  new_sdp = kSdpOneCandidate;
+  Replace("udp", "uDP", &new_sdp);
+  EXPECT_TRUE(SdpDeserializeCandidate(new_sdp, &jcandidate));
+  EXPECT_EQ(kDummyMid, jcandidate.sdp_mid());
+  EXPECT_EQ(kDummyIndex, jcandidate.sdp_mline_index());
+  EXPECT_TRUE(jcandidate.candidate().IsEquivalent(jcandidate_->candidate()));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeCandidateOldFormat) {
+  JsepIceCandidate jcandidate(kDummyMid, kDummyIndex);
+  EXPECT_TRUE(SdpDeserializeCandidate(kSdpOneCandidateOldFormat,&jcandidate));
+  EXPECT_EQ(kDummyMid, jcandidate.sdp_mid());
+  EXPECT_EQ(kDummyIndex, jcandidate.sdp_mline_index());
+  Candidate ref_candidate = jcandidate_->candidate();
+  ref_candidate.set_username("user_rtp");
+  ref_candidate.set_password("password_rtp");
+  EXPECT_TRUE(jcandidate.candidate().IsEquivalent(ref_candidate));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeBrokenSdp) {
+  const char kSdpDestroyer[] = "!@#$%^&";
+  const char kSdpInvalidLine1[] = " =candidate";
+  const char kSdpInvalidLine2[] = "a+candidate";
+  const char kSdpInvalidLine3[] = "a= candidate";
+  // Broken fingerprint.
+  const char kSdpInvalidLine4[] = "a=fingerprint:sha-1 "
+      "4AAD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB";
+  // Extra field.
+  const char kSdpInvalidLine5[] = "a=fingerprint:sha-1 "
+      "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB XXX";
+  // Missing space.
+  const char kSdpInvalidLine6[] = "a=fingerprint:sha-1"
+      "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB";
+
+  // Broken session description
+  ReplaceAndTryToParse("v=", kSdpDestroyer);
+  ReplaceAndTryToParse("o=", kSdpDestroyer);
+  ReplaceAndTryToParse("s=-", kSdpDestroyer);
+  // Broken time description
+  ReplaceAndTryToParse("t=", kSdpDestroyer);
+
+  // Broken media description
+  ReplaceAndTryToParse("m=video", kSdpDestroyer);
+
+  // Invalid lines
+  ReplaceAndTryToParse("a=candidate", kSdpInvalidLine1);
+  ReplaceAndTryToParse("a=candidate", kSdpInvalidLine2);
+  ReplaceAndTryToParse("a=candidate", kSdpInvalidLine3);
+
+  // Bogus fingerprint replacing a=sendrev. We selected this attribute
+  // because it's orthogonal to what we are replacing and hence
+  // safe.
+  ReplaceAndTryToParse("a=sendrecv", kSdpInvalidLine4);
+  ReplaceAndTryToParse("a=sendrecv", kSdpInvalidLine5);
+  ReplaceAndTryToParse("a=sendrecv", kSdpInvalidLine6);
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSdpWithReorderedPltypes) {
+  JsepSessionDescription jdesc_output(kDummyString);
+
+  const char kSdpWithReorderedPlTypesString[] =
+      "v=0\r\n"
+      "o=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1\r\n"
+      "s=-\r\n"
+      "t=0 0\r\n"
+      "m=audio 1 RTP/SAVPF 104 103\r\n"  // Pl type 104 preferred.
+      "a=rtpmap:111 opus/48000/2\r\n"  // Pltype 111 listed before 103 and 104
+                                       // in the map.
+      "a=rtpmap:103 ISAC/16000\r\n"  // Pltype 103 listed before 104 in the map.
+      "a=rtpmap:104 CELT/32000/2\r\n";
+
+  // Deserialize
+  EXPECT_TRUE(SdpDeserialize(kSdpWithReorderedPlTypesString, &jdesc_output));
+
+  const ContentInfo* ac = GetFirstAudioContent(jdesc_output.description());
+  ASSERT_TRUE(ac != NULL);
+  const AudioContentDescription* acd =
+      static_cast<const AudioContentDescription*>(ac->description);
+  ASSERT_FALSE(acd->codecs().empty());
+  EXPECT_EQ("CELT", acd->codecs()[0].name);
+  EXPECT_EQ(104, acd->codecs()[0].id);
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSerializeCodecParams) {
+  JsepSessionDescription jdesc_output(kDummyString);
+  CodecParams params;
+  params.max_ptime = 40;
+  params.ptime = 30;
+  params.min_ptime = 10;
+  params.sprop_stereo = 1;
+  params.stereo = 1;
+  params.useinband = 1;
+  TestDeserializeCodecParams(params, &jdesc_output);
+  TestSerialize(jdesc_output);
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSerializeRtcpFb) {
+  const bool kUseWildcard = false;
+  JsepSessionDescription jdesc_output(kDummyString);
+  TestDeserializeRtcpFb(&jdesc_output, kUseWildcard);
+  TestSerialize(jdesc_output);
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSerializeRtcpFbWildcard) {
+  const bool kUseWildcard = true;
+  JsepSessionDescription jdesc_output(kDummyString);
+  TestDeserializeRtcpFb(&jdesc_output, kUseWildcard);
+  TestSerialize(jdesc_output);
+}
+
+TEST_F(WebRtcSdpTest, DeserializeVideoFmtp) {
+  JsepSessionDescription jdesc_output(kDummyString);
+
+  const char kSdpWithFmtpString[] =
+      "v=0\r\n"
+      "o=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1\r\n"
+      "s=-\r\n"
+      "t=0 0\r\n"
+      "m=video 3457 RTP/SAVPF 120\r\n"
+      "a=rtpmap:120 VP8/90000\r\n"
+      "a=fmtp:120 x-google-min-bitrate=10; x-google-max-quantization=40\r\n";
+
+  // Deserialize
+  SdpParseError error;
+  EXPECT_TRUE(webrtc::SdpDeserialize(kSdpWithFmtpString, &jdesc_output,
+                                     &error));
+
+  const ContentInfo* vc = GetFirstVideoContent(jdesc_output.description());
+  ASSERT_TRUE(vc != NULL);
+  const VideoContentDescription* vcd =
+      static_cast<const VideoContentDescription*>(vc->description);
+  ASSERT_FALSE(vcd->codecs().empty());
+  cricket::VideoCodec vp8 = vcd->codecs()[0];
+  EXPECT_EQ("VP8", vp8.name);
+  EXPECT_EQ(120, vp8.id);
+  cricket::CodecParameterMap::iterator found =
+      vp8.params.find("x-google-min-bitrate");
+  ASSERT_TRUE(found != vp8.params.end());
+  EXPECT_EQ(found->second, "10");
+  found = vp8.params.find("x-google-max-quantization");
+  ASSERT_TRUE(found != vp8.params.end());
+  EXPECT_EQ(found->second, "40");
+}
+
+TEST_F(WebRtcSdpTest, SerializeVideoFmtp) {
+  VideoContentDescription* vcd = static_cast<VideoContentDescription*>(
+      GetFirstVideoContent(&desc_)->description);
+
+  cricket::VideoCodecs codecs = vcd->codecs();
+  codecs[0].params["x-google-min-bitrate"] = "10";
+  vcd->set_codecs(codecs);
+
+  ASSERT_TRUE(jdesc_.Initialize(desc_.Copy(),
+                                jdesc_.session_id(),
+                                jdesc_.session_version()));
+  std::string message = webrtc::SdpSerialize(jdesc_);
+  std::string sdp_with_fmtp = kSdpFullString;
+  InjectAfter("a=rtpmap:120 VP8/90000\r\n",
+              "a=fmtp:120 x-google-min-bitrate=10\r\n",
+              &sdp_with_fmtp);
+  EXPECT_EQ(sdp_with_fmtp, message);
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSdpWithIceLite) {
+  JsepSessionDescription jdesc_with_icelite(kDummyString);
+  std::string sdp_with_icelite = kSdpFullString;
+  EXPECT_TRUE(SdpDeserialize(sdp_with_icelite, &jdesc_with_icelite));
+  cricket::SessionDescription* desc = jdesc_with_icelite.description();
+  const cricket::TransportInfo* tinfo1 =
+      desc->GetTransportInfoByName("audio_content_name");
+  EXPECT_EQ(cricket::ICEMODE_FULL, tinfo1->description.ice_mode);
+  const cricket::TransportInfo* tinfo2 =
+      desc->GetTransportInfoByName("video_content_name");
+  EXPECT_EQ(cricket::ICEMODE_FULL, tinfo2->description.ice_mode);
+  InjectAfter(kSessionTime,
+              "a=ice-lite\r\n",
+              &sdp_with_icelite);
+  EXPECT_TRUE(SdpDeserialize(sdp_with_icelite, &jdesc_with_icelite));
+  desc = jdesc_with_icelite.description();
+  const cricket::TransportInfo* atinfo =
+      desc->GetTransportInfoByName("audio_content_name");
+  EXPECT_EQ(cricket::ICEMODE_LITE, atinfo->description.ice_mode);
+  const cricket::TransportInfo* vtinfo =
+        desc->GetTransportInfoByName("video_content_name");
+  EXPECT_EQ(cricket::ICEMODE_LITE, vtinfo->description.ice_mode);
+}
+
+// Verifies that the candidates in the input SDP are parsed and serialized
+// correctly in the output SDP.
+TEST_F(WebRtcSdpTest, RoundTripSdpWithSctpDataChannelsWithCandidates) {
+  std::string sdp_with_data = kSdpString;
+  sdp_with_data.append(kSdpSctpDataChannelWithCandidatesString);
+  JsepSessionDescription jdesc_output(kDummyString);
+
+  EXPECT_TRUE(SdpDeserialize(sdp_with_data, &jdesc_output));
+  EXPECT_EQ(sdp_with_data, webrtc::SdpSerialize(jdesc_output));
+}
diff --git a/talk/app/webrtc/webrtcsession.cc b/talk/app/webrtc/webrtcsession.cc
new file mode 100644
index 0000000..fee8d42
--- /dev/null
+++ b/talk/app/webrtc/webrtcsession.cc
@@ -0,0 +1,1440 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/webrtcsession.h"
+
+#include <algorithm>
+#include <climits>
+#include <vector>
+
+#include "talk/app/webrtc/jsepicecandidate.h"
+#include "talk/app/webrtc/jsepsessiondescription.h"
+#include "talk/app/webrtc/mediaconstraintsinterface.h"
+#include "talk/app/webrtc/mediastreamsignaling.h"
+#include "talk/app/webrtc/peerconnectioninterface.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringencode.h"
+#include "talk/media/base/constants.h"
+#include "talk/media/base/videocapturer.h"
+#include "talk/session/media/channel.h"
+#include "talk/session/media/channelmanager.h"
+#include "talk/session/media/mediasession.h"
+
+using cricket::ContentInfo;
+using cricket::ContentInfos;
+using cricket::MediaContentDescription;
+using cricket::SessionDescription;
+using cricket::TransportInfo;
+
+typedef cricket::MediaSessionOptions::Stream Stream;
+typedef cricket::MediaSessionOptions::Streams Streams;
+
+namespace webrtc {
+
+static const uint64 kInitSessionVersion = 2;
+
+const char kInternalConstraintPrefix[] = "internal";
+
+// Supported MediaConstraints.
+// DTLS-SRTP pseudo-constraints.
+const char MediaConstraintsInterface::kEnableDtlsSrtp[] =
+    "DtlsSrtpKeyAgreement";
+// DataChannel pseudo constraints.
+const char MediaConstraintsInterface::kEnableRtpDataChannels[] =
+    "RtpDataChannels";
+// This constraint is for internal use only, representing the Chrome command
+// line flag. So it is prefixed with kInternalConstraintPrefix so JS values
+// will be removed.
+const char MediaConstraintsInterface::kEnableSctpDataChannels[] =
+    "internalSctpDataChannels";
+
+// Arbitrary constant used as prefix for the identity.
+// Chosen to make the certificates more readable.
+const char kWebRTCIdentityPrefix[] = "WebRTC";
+
+// Error messages
+const char kSetLocalSdpFailed[] = "SetLocalDescription failed: ";
+const char kSetRemoteSdpFailed[] = "SetRemoteDescription failed: ";
+const char kCreateChannelFailed[] = "Failed to create channels.";
+const char kInvalidCandidates[] = "Description contains invalid candidates.";
+const char kInvalidSdp[] = "Invalid session description.";
+const char kMlineMismatch[] =
+    "Offer and answer descriptions m-lines are not matching. "
+    "Rejecting answer.";
+const char kSdpWithoutCrypto[] = "Called with a SDP without crypto enabled.";
+const char kSessionError[] = "Session error code: ";
+const char kUpdateStateFailed[] = "Failed to update session state: ";
+const char kPushDownOfferTDFailed[] =
+    "Failed to push down offer transport description.";
+const char kPushDownPranswerTDFailed[] =
+    "Failed to push down pranswer transport description.";
+const char kPushDownAnswerTDFailed[] =
+    "Failed to push down answer transport description.";
+
+// Compares |answer| against |offer|. Comparision is done
+// for number of m-lines in answer against offer. If matches true will be
+// returned otherwise false.
+static bool VerifyMediaDescriptions(
+    const SessionDescription* answer, const SessionDescription* offer) {
+  if (offer->contents().size() != answer->contents().size())
+    return false;
+
+  for (size_t i = 0; i < offer->contents().size(); ++i) {
+    if ((offer->contents()[i].name) != answer->contents()[i].name) {
+      return false;
+    }
+  }
+  return true;
+}
+
+static void CopyCandidatesFromSessionDescription(
+    const SessionDescriptionInterface* source_desc,
+    SessionDescriptionInterface* dest_desc) {
+  if (!source_desc)
+    return;
+  for (size_t m = 0; m < source_desc->number_of_mediasections() &&
+                     m < dest_desc->number_of_mediasections(); ++m) {
+    const IceCandidateCollection* source_candidates =
+        source_desc->candidates(m);
+    const IceCandidateCollection* dest_candidates = dest_desc->candidates(m);
+    for  (size_t n = 0; n < source_candidates->count(); ++n) {
+      const IceCandidateInterface* new_candidate = source_candidates->at(n);
+      if (!dest_candidates->HasCandidate(new_candidate))
+        dest_desc->AddCandidate(source_candidates->at(n));
+    }
+  }
+}
+
+// Checks that each non-rejected content has SDES crypto keys or a DTLS
+// fingerprint. Mismatches, such as replying with a DTLS fingerprint to SDES
+// keys, will be caught in Transport negotiation, and backstopped by Channel's
+// |secure_required| check.
+static bool VerifyCrypto(const SessionDescription* desc) {
+  if (!desc) {
+    return false;
+  }
+  const ContentInfos& contents = desc->contents();
+  for (size_t index = 0; index < contents.size(); ++index) {
+    const ContentInfo* cinfo = &contents[index];
+    if (cinfo->rejected) {
+      continue;
+    }
+
+    // If the content isn't rejected, crypto must be present.
+    const MediaContentDescription* media =
+        static_cast<const MediaContentDescription*>(cinfo->description);
+    const TransportInfo* tinfo = desc->GetTransportInfoByName(cinfo->name);
+    if (!media || !tinfo) {
+      // Something is not right.
+      LOG(LS_ERROR) << kInvalidSdp;
+      return false;
+    }
+    if (media->cryptos().empty() &&
+        !tinfo->description.identity_fingerprint) {
+      // Crypto must be supplied.
+      LOG(LS_WARNING) << "Session description must have SDES or DTLS-SRTP.";
+      return false;
+    }
+  }
+
+  return true;
+}
+
+static bool CompareStream(const Stream& stream1, const Stream& stream2) {
+  return (stream1.id < stream2.id);
+}
+
+static bool SameId(const Stream& stream1, const Stream& stream2) {
+  return (stream1.id == stream2.id);
+}
+
+// Checks if each Stream within the |streams| has unique id.
+static bool ValidStreams(const Streams& streams) {
+  Streams sorted_streams = streams;
+  std::sort(sorted_streams.begin(), sorted_streams.end(), CompareStream);
+  Streams::iterator it =
+      std::adjacent_find(sorted_streams.begin(), sorted_streams.end(),
+                         SameId);
+  return (it == sorted_streams.end());
+}
+
+static bool GetAudioSsrcByTrackId(
+    const SessionDescription* session_description,
+    const std::string& track_id, uint32 *ssrc) {
+  const cricket::ContentInfo* audio_info =
+      cricket::GetFirstAudioContent(session_description);
+  if (!audio_info) {
+    LOG(LS_ERROR) << "Audio not used in this call";
+    return false;
+  }
+
+  const cricket::MediaContentDescription* audio_content =
+      static_cast<const cricket::MediaContentDescription*>(
+          audio_info->description);
+  cricket::StreamParams stream;
+  if (!cricket::GetStreamByIds(audio_content->streams(), "", track_id,
+                               &stream)) {
+    return false;
+  }
+  *ssrc = stream.first_ssrc();
+  return true;
+}
+
+static bool GetTrackIdBySsrc(const SessionDescription* session_description,
+                             uint32 ssrc, std::string* track_id) {
+  ASSERT(track_id != NULL);
+
+  cricket::StreamParams stream_out;
+  const cricket::ContentInfo* audio_info =
+      cricket::GetFirstAudioContent(session_description);
+  if (!audio_info) {
+    return false;
+  }
+  const cricket::MediaContentDescription* audio_content =
+      static_cast<const cricket::MediaContentDescription*>(
+          audio_info->description);
+
+  if (cricket::GetStreamBySsrc(audio_content->streams(), ssrc, &stream_out)) {
+    *track_id = stream_out.id;
+    return true;
+  }
+
+  const cricket::ContentInfo* video_info =
+      cricket::GetFirstVideoContent(session_description);
+  if (!video_info) {
+    return false;
+  }
+  const cricket::MediaContentDescription* video_content =
+      static_cast<const cricket::MediaContentDescription*>(
+          video_info->description);
+
+  if (cricket::GetStreamBySsrc(video_content->streams(), ssrc, &stream_out)) {
+    *track_id = stream_out.id;
+    return true;
+  }
+  return false;
+}
+
+static bool BadSdp(const std::string& desc, std::string* err_desc) {
+  if (err_desc) {
+    *err_desc = desc;
+  }
+  LOG(LS_ERROR) << desc;
+  return false;
+}
+
+static bool BadLocalSdp(const std::string& desc, std::string* err_desc) {
+  std::string set_local_sdp_failed = kSetLocalSdpFailed;
+  set_local_sdp_failed.append(desc);
+  return BadSdp(set_local_sdp_failed, err_desc);
+}
+
+static bool BadRemoteSdp(const std::string& desc, std::string* err_desc) {
+  std::string set_remote_sdp_failed = kSetRemoteSdpFailed;
+  set_remote_sdp_failed.append(desc);
+  return BadSdp(set_remote_sdp_failed, err_desc);
+}
+
+static bool BadSdp(cricket::ContentSource source,
+                   const std::string& desc, std::string* err_desc) {
+  if (source == cricket::CS_LOCAL) {
+    return BadLocalSdp(desc, err_desc);
+  } else {
+    return BadRemoteSdp(desc, err_desc);
+  }
+}
+
+static std::string SessionErrorMsg(cricket::BaseSession::Error error) {
+  std::ostringstream desc;
+  desc << kSessionError << error;
+  return desc.str();
+}
+
+#define GET_STRING_OF_STATE(state)  \
+  case cricket::BaseSession::state:  \
+    result = #state;  \
+    break;
+
+static std::string GetStateString(cricket::BaseSession::State state) {
+  std::string result;
+  switch (state) {
+    GET_STRING_OF_STATE(STATE_INIT)
+    GET_STRING_OF_STATE(STATE_SENTINITIATE)
+    GET_STRING_OF_STATE(STATE_RECEIVEDINITIATE)
+    GET_STRING_OF_STATE(STATE_SENTPRACCEPT)
+    GET_STRING_OF_STATE(STATE_SENTACCEPT)
+    GET_STRING_OF_STATE(STATE_RECEIVEDPRACCEPT)
+    GET_STRING_OF_STATE(STATE_RECEIVEDACCEPT)
+    GET_STRING_OF_STATE(STATE_SENTMODIFY)
+    GET_STRING_OF_STATE(STATE_RECEIVEDMODIFY)
+    GET_STRING_OF_STATE(STATE_SENTREJECT)
+    GET_STRING_OF_STATE(STATE_RECEIVEDREJECT)
+    GET_STRING_OF_STATE(STATE_SENTREDIRECT)
+    GET_STRING_OF_STATE(STATE_SENTTERMINATE)
+    GET_STRING_OF_STATE(STATE_RECEIVEDTERMINATE)
+    GET_STRING_OF_STATE(STATE_INPROGRESS)
+    GET_STRING_OF_STATE(STATE_DEINIT)
+    default:
+      ASSERT(false);
+      break;
+  }
+  return result;
+}
+
+#define GET_STRING_OF_ERROR(err)  \
+  case cricket::BaseSession::err:  \
+    result = #err;  \
+    break;
+
+static std::string GetErrorString(cricket::BaseSession::Error err) {
+  std::string result;
+  switch (err) {
+    GET_STRING_OF_ERROR(ERROR_NONE)
+    GET_STRING_OF_ERROR(ERROR_TIME)
+    GET_STRING_OF_ERROR(ERROR_RESPONSE)
+    GET_STRING_OF_ERROR(ERROR_NETWORK)
+    GET_STRING_OF_ERROR(ERROR_CONTENT)
+    GET_STRING_OF_ERROR(ERROR_TRANSPORT)
+    default:
+      ASSERT(false);
+      break;
+  }
+  return result;
+}
+
+static bool SetSessionStateFailed(cricket::ContentSource source,
+                                  cricket::BaseSession::Error err,
+                                  std::string* err_desc) {
+  std::string set_state_err = kUpdateStateFailed;
+  set_state_err.append(GetErrorString(err));
+  return BadSdp(source, set_state_err, err_desc);
+}
+
+// Help class used to remember if a a remote peer has requested ice restart by
+// by sending a description with new ice ufrag and password.
+class IceRestartAnswerLatch {
+ public:
+  IceRestartAnswerLatch() : ice_restart_(false) { }
+
+  // Returns true if CheckForRemoteIceRestart has been called since last
+  // time this method was called with a new session description where
+  // ice password and ufrag has changed.
+  bool AnswerWithIceRestartLatch() {
+    if (ice_restart_) {
+      ice_restart_ = false;
+      return true;
+    }
+    return false;
+  }
+
+  void CheckForRemoteIceRestart(
+      const SessionDescriptionInterface* old_desc,
+      const SessionDescriptionInterface* new_desc) {
+    if (!old_desc || new_desc->type() != SessionDescriptionInterface::kOffer) {
+      return;
+    }
+    const SessionDescription* new_sd = new_desc->description();
+    const SessionDescription* old_sd = old_desc->description();
+    const ContentInfos& contents = new_sd->contents();
+    for (size_t index = 0; index < contents.size(); ++index) {
+      const ContentInfo* cinfo = &contents[index];
+      if (cinfo->rejected) {
+        continue;
+      }
+      // If the content isn't rejected, check if ufrag and password has
+      // changed.
+      const cricket::TransportDescription* new_transport_desc =
+          new_sd->GetTransportDescriptionByName(cinfo->name);
+      const cricket::TransportDescription* old_transport_desc =
+          old_sd->GetTransportDescriptionByName(cinfo->name);
+      if (!new_transport_desc || !old_transport_desc) {
+        // No transport description exist. This is not an ice restart.
+        continue;
+      }
+      if (new_transport_desc->ice_pwd != old_transport_desc->ice_pwd &&
+          new_transport_desc->ice_ufrag != old_transport_desc->ice_ufrag) {
+        LOG(LS_INFO) << "Remote peer request ice restart.";
+        ice_restart_ = true;
+        break;
+      }
+    }
+  }
+
+ private:
+  bool ice_restart_;
+};
+
+WebRtcSession::WebRtcSession(cricket::ChannelManager* channel_manager,
+                             talk_base::Thread* signaling_thread,
+                             talk_base::Thread* worker_thread,
+                             cricket::PortAllocator* port_allocator,
+                             MediaStreamSignaling* mediastream_signaling)
+    : cricket::BaseSession(signaling_thread, worker_thread, port_allocator,
+                           talk_base::ToString(talk_base::CreateRandomId64() &
+                                               LLONG_MAX),
+                           cricket::NS_JINGLE_RTP, false),
+      // RFC 3264: The numeric value of the session id and version in the
+      // o line MUST be representable with a "64 bit signed integer".
+      // Due to this constraint session id |sid_| is max limited to LLONG_MAX.
+      channel_manager_(channel_manager),
+      session_desc_factory_(channel_manager, &transport_desc_factory_),
+      mediastream_signaling_(mediastream_signaling),
+      ice_observer_(NULL),
+      ice_connection_state_(PeerConnectionInterface::kIceConnectionNew),
+      // RFC 4566 suggested a Network Time Protocol (NTP) format timestamp
+      // as the session id and session version. To simplify, it should be fine
+      // to just use a random number as session id and start version from
+      // |kInitSessionVersion|.
+      session_version_(kInitSessionVersion),
+      older_version_remote_peer_(false),
+      data_channel_type_(cricket::DCT_NONE),
+      ice_restart_latch_(new IceRestartAnswerLatch) {
+  transport_desc_factory_.set_protocol(cricket::ICEPROTO_HYBRID);
+}
+
+WebRtcSession::~WebRtcSession() {
+  if (voice_channel_.get()) {
+    SignalVoiceChannelDestroyed();
+    channel_manager_->DestroyVoiceChannel(voice_channel_.release());
+  }
+  if (video_channel_.get()) {
+    SignalVideoChannelDestroyed();
+    channel_manager_->DestroyVideoChannel(video_channel_.release());
+  }
+  if (data_channel_.get()) {
+    SignalDataChannelDestroyed();
+    channel_manager_->DestroyDataChannel(data_channel_.release());
+  }
+  for (size_t i = 0; i < saved_candidates_.size(); ++i) {
+    delete saved_candidates_[i];
+  }
+  delete identity();
+  set_identity(NULL);
+  transport_desc_factory_.set_identity(NULL);
+}
+
+bool WebRtcSession::Initialize(const MediaConstraintsInterface* constraints) {
+  // TODO(perkj): Take |constraints| into consideration. Return false if not all
+  // mandatory constraints can be fulfilled. Note that |constraints|
+  // can be null.
+
+  // By default SRTP-SDES is enabled in WebRtc.
+  set_secure_policy(cricket::SEC_REQUIRED);
+
+  // Enable DTLS-SRTP if the constraint is set.
+  bool value;
+  if (FindConstraint(constraints, MediaConstraintsInterface::kEnableDtlsSrtp,
+      &value, NULL) && value) {
+    LOG(LS_INFO) << "DTLS-SRTP enabled; generating identity";
+    std::string identity_name = kWebRTCIdentityPrefix +
+        talk_base::ToString(talk_base::CreateRandomId());
+    transport_desc_factory_.set_identity(talk_base::SSLIdentity::Generate(
+        identity_name));
+    LOG(LS_INFO) << "Finished generating identity";
+    set_identity(transport_desc_factory_.identity());
+    transport_desc_factory_.set_digest_algorithm(talk_base::DIGEST_SHA_256);
+
+    transport_desc_factory_.set_secure(cricket::SEC_ENABLED);
+  }
+
+  // Enable creation of RTP data channels if the kEnableRtpDataChannels is set.
+  // It takes precendence over the kEnableSctpDataChannels constraint.
+  if (FindConstraint(
+      constraints, MediaConstraintsInterface::kEnableRtpDataChannels,
+      &value, NULL) && value) {
+    LOG(LS_INFO) << "Allowing RTP data engine.";
+    data_channel_type_ = cricket::DCT_RTP;
+  } else if (
+      FindConstraint(
+          constraints,
+          MediaConstraintsInterface::kEnableSctpDataChannels,
+          &value, NULL) && value &&
+      // DTLS has to be enabled to use SCTP.
+      (transport_desc_factory_.secure() == cricket::SEC_ENABLED)) {
+    LOG(LS_INFO) << "Allowing SCTP data engine.";
+    data_channel_type_ = cricket::DCT_SCTP;
+  }
+  if (data_channel_type_ != cricket::DCT_NONE) {
+    mediastream_signaling_->SetDataChannelFactory(this);
+  }
+
+  // Make sure SessionDescriptions only contains the StreamParams we negotiate.
+  session_desc_factory_.set_add_legacy_streams(false);
+
+  const cricket::VideoCodec default_codec(
+      JsepSessionDescription::kDefaultVideoCodecId,
+      JsepSessionDescription::kDefaultVideoCodecName,
+      JsepSessionDescription::kMaxVideoCodecWidth,
+      JsepSessionDescription::kMaxVideoCodecHeight,
+      JsepSessionDescription::kDefaultVideoCodecFramerate,
+      JsepSessionDescription::kDefaultVideoCodecPreference);
+  channel_manager_->SetDefaultVideoEncoderConfig(
+      cricket::VideoEncoderConfig(default_codec));
+  return true;
+}
+
+void WebRtcSession::Terminate() {
+  SetState(STATE_RECEIVEDTERMINATE);
+  RemoveUnusedChannelsAndTransports(NULL);
+  ASSERT(voice_channel_.get() == NULL);
+  ASSERT(video_channel_.get() == NULL);
+  ASSERT(data_channel_.get() == NULL);
+}
+
+bool WebRtcSession::StartCandidatesAllocation() {
+  // SpeculativelyConnectTransportChannels, will call ConnectChannels method
+  // from TransportProxy to start gathering ice candidates.
+  SpeculativelyConnectAllTransportChannels();
+  if (!saved_candidates_.empty()) {
+    // If there are saved candidates which arrived before local description is
+    // set, copy those to remote description.
+    CopySavedCandidates(remote_desc_.get());
+  }
+  // Push remote candidates present in remote description to transport channels.
+  UseCandidatesInSessionDescription(remote_desc_.get());
+  return true;
+}
+
+void WebRtcSession::set_secure_policy(
+    cricket::SecureMediaPolicy secure_policy) {
+  session_desc_factory_.set_secure(secure_policy);
+}
+
+SessionDescriptionInterface* WebRtcSession::CreateOffer(
+    const MediaConstraintsInterface* constraints) {
+  cricket::MediaSessionOptions options;
+
+  if (!mediastream_signaling_->GetOptionsForOffer(constraints, &options)) {
+    LOG(LS_ERROR) << "CreateOffer called with invalid constraints.";
+    return NULL;
+  }
+
+  if (!ValidStreams(options.streams)) {
+    LOG(LS_ERROR) << "CreateOffer called with invalid media streams.";
+    return NULL;
+  }
+
+  if (data_channel_type_ == cricket::DCT_SCTP) {
+    options.data_channel_type = cricket::DCT_SCTP;
+  }
+  SessionDescription* desc(
+      session_desc_factory_.CreateOffer(options,
+                                        BaseSession::local_description()));
+  // RFC 3264
+  // When issuing an offer that modifies the session,
+  // the "o=" line of the new SDP MUST be identical to that in the
+  // previous SDP, except that the version in the origin field MUST
+  // increment by one from the previous SDP.
+
+  // Just increase the version number by one each time when a new offer
+  // is created regardless if it's identical to the previous one or not.
+  // The |session_version_| is a uint64, the wrap around should not happen.
+  ASSERT(session_version_ + 1 > session_version_);
+  JsepSessionDescription* offer(new JsepSessionDescription(
+      JsepSessionDescription::kOffer));
+  if (!offer->Initialize(desc, id(),
+                         talk_base::ToString(session_version_++))) {
+    delete offer;
+    return NULL;
+  }
+  if (local_description() && !options.transport_options.ice_restart) {
+    // Include all local ice candidates in the SessionDescription unless
+    // the an ice restart has been requested.
+    CopyCandidatesFromSessionDescription(local_description(), offer);
+  }
+  return offer;
+}
+
+SessionDescriptionInterface* WebRtcSession::CreateAnswer(
+    const MediaConstraintsInterface* constraints) {
+  if (!remote_description()) {
+    LOG(LS_ERROR) << "CreateAnswer can't be called before"
+                  << " SetRemoteDescription.";
+    return NULL;
+  }
+  if (remote_description()->type() != JsepSessionDescription::kOffer) {
+    LOG(LS_ERROR) << "CreateAnswer failed because remote_description is not an"
+                  << " offer.";
+    return NULL;
+  }
+
+  cricket::MediaSessionOptions options;
+  if (!mediastream_signaling_->GetOptionsForAnswer(constraints, &options)) {
+    LOG(LS_ERROR) << "CreateAnswer called with invalid constraints.";
+    return NULL;
+  }
+  if (!ValidStreams(options.streams)) {
+    LOG(LS_ERROR) << "CreateAnswer called with invalid media streams.";
+    return NULL;
+  }
+  if (data_channel_type_ == cricket::DCT_SCTP) {
+    options.data_channel_type = cricket::DCT_SCTP;
+  }
+  // According to http://tools.ietf.org/html/rfc5245#section-9.2.1.1
+  // an answer should also contain new ice ufrag and password if an offer has
+  // been received with new ufrag and password.
+  options.transport_options.ice_restart =
+      ice_restart_latch_->AnswerWithIceRestartLatch();
+  SessionDescription* desc(
+      session_desc_factory_.CreateAnswer(BaseSession::remote_description(),
+                                         options,
+                                         BaseSession::local_description()));
+  // RFC 3264
+  // If the answer is different from the offer in any way (different IP
+  // addresses, ports, etc.), the origin line MUST be different in the answer.
+  // In that case, the version number in the "o=" line of the answer is
+  // unrelated to the version number in the o line of the offer.
+  // Get a new version number by increasing the |session_version_answer_|.
+  // The |session_version_| is a uint64, the wrap around should not happen.
+  ASSERT(session_version_ + 1 > session_version_);
+  JsepSessionDescription* answer(new JsepSessionDescription(
+      JsepSessionDescription::kAnswer));
+  if (!answer->Initialize(desc, id(),
+                          talk_base::ToString(session_version_++))) {
+    delete answer;
+    return NULL;
+  }
+  if (local_description() && !options.transport_options.ice_restart) {
+    // Include all local ice candidates in the SessionDescription unless
+    // the remote peer has requested an ice restart.
+    CopyCandidatesFromSessionDescription(local_description(), answer);
+  }
+  return answer;
+}
+
+bool WebRtcSession::SetLocalDescription(SessionDescriptionInterface* desc,
+                                        std::string* err_desc) {
+  if (error() != cricket::BaseSession::ERROR_NONE) {
+    delete desc;
+    return BadLocalSdp(SessionErrorMsg(error()), err_desc);
+  }
+
+  if (!desc || !desc->description()) {
+    delete desc;
+    return BadLocalSdp(kInvalidSdp, err_desc);
+  }
+  Action action = GetAction(desc->type());
+  if (!ExpectSetLocalDescription(action)) {
+    std::string type = desc->type();
+    delete desc;
+    return BadLocalSdp(BadStateErrMsg(type, state()), err_desc);
+  }
+
+  if (session_desc_factory_.secure() == cricket::SEC_REQUIRED &&
+      !VerifyCrypto(desc->description())) {
+    delete desc;
+    return BadLocalSdp(kSdpWithoutCrypto, err_desc);
+  }
+
+  if (action == kAnswer && !VerifyMediaDescriptions(
+          desc->description(), remote_description()->description())) {
+    return BadLocalSdp(kMlineMismatch, err_desc);
+  }
+
+  // Update the initiator flag if this session is the initiator.
+  if (state() == STATE_INIT && action == kOffer) {
+    set_initiator(true);
+  }
+
+  // Update the MediaContentDescription crypto settings as per the policy set.
+  UpdateSessionDescriptionSecurePolicy(desc->description());
+
+  set_local_description(desc->description()->Copy());
+  local_desc_.reset(desc);
+
+  // Transport and Media channels will be created only when offer is set.
+  if (action == kOffer && !CreateChannels(desc->description())) {
+    // TODO(mallinath) - Handle CreateChannel failure, as new local description
+    // is applied. Restore back to old description.
+    return BadLocalSdp(kCreateChannelFailed, err_desc);
+  }
+
+  // Remove channel and transport proxies, if MediaContentDescription is
+  // rejected.
+  RemoveUnusedChannelsAndTransports(desc->description());
+
+  if (!UpdateSessionState(action, cricket::CS_LOCAL,
+                          desc->description(), err_desc)) {
+    return false;
+  }
+  // Kick starting the ice candidates allocation.
+  StartCandidatesAllocation();
+
+  // Update state and SSRC of local MediaStreams and DataChannels based on the
+  // local session description.
+  mediastream_signaling_->OnLocalDescriptionChanged(local_desc_.get());
+
+  if (error() != cricket::BaseSession::ERROR_NONE) {
+    return BadLocalSdp(SessionErrorMsg(error()), err_desc);
+  }
+  return true;
+}
+
+bool WebRtcSession::SetRemoteDescription(SessionDescriptionInterface* desc,
+                                         std::string* err_desc) {
+  if (error() != cricket::BaseSession::ERROR_NONE) {
+    delete desc;
+    return BadRemoteSdp(SessionErrorMsg(error()), err_desc);
+  }
+
+  if (!desc || !desc->description()) {
+    delete desc;
+    return BadRemoteSdp(kInvalidSdp, err_desc);
+  }
+  Action action = GetAction(desc->type());
+  if (!ExpectSetRemoteDescription(action)) {
+    std::string type = desc->type();
+    delete desc;
+    return BadRemoteSdp(BadStateErrMsg(type, state()), err_desc);
+  }
+
+  if (action == kAnswer && !VerifyMediaDescriptions(
+          desc->description(), local_description()->description())) {
+    return BadRemoteSdp(kMlineMismatch, err_desc);
+  }
+
+  if (session_desc_factory_.secure() == cricket::SEC_REQUIRED &&
+      !VerifyCrypto(desc->description())) {
+    delete desc;
+    return BadRemoteSdp(kSdpWithoutCrypto, err_desc);
+  }
+
+  // Transport and Media channels will be created only when offer is set.
+  if (action == kOffer && !CreateChannels(desc->description())) {
+    // TODO(mallinath) - Handle CreateChannel failure, as new local description
+    // is applied. Restore back to old description.
+    return BadRemoteSdp(kCreateChannelFailed, err_desc);
+  }
+
+  // Remove channel and transport proxies, if MediaContentDescription is
+  // rejected.
+  RemoveUnusedChannelsAndTransports(desc->description());
+
+  // NOTE: Candidates allocation will be initiated only when SetLocalDescription
+  // is called.
+  set_remote_description(desc->description()->Copy());
+  if (!UpdateSessionState(action, cricket::CS_REMOTE,
+                          desc->description(), err_desc)) {
+    return false;
+  }
+
+  // Update remote MediaStreams.
+  mediastream_signaling_->OnRemoteDescriptionChanged(desc);
+  if (local_description() && !UseCandidatesInSessionDescription(desc)) {
+    delete desc;
+    return BadRemoteSdp(kInvalidCandidates, err_desc);
+  }
+
+  // Copy all saved candidates.
+  CopySavedCandidates(desc);
+  // We retain all received candidates.
+  CopyCandidatesFromSessionDescription(remote_desc_.get(), desc);
+  // Check if this new SessionDescription contains new ice ufrag and password
+  // that indicates the remote peer requests ice restart.
+  ice_restart_latch_->CheckForRemoteIceRestart(remote_desc_.get(),
+                                               desc);
+  remote_desc_.reset(desc);
+  if (error() != cricket::BaseSession::ERROR_NONE) {
+    return BadRemoteSdp(SessionErrorMsg(error()), err_desc);
+  }
+  return true;
+}
+
+bool WebRtcSession::UpdateSessionState(
+    Action action, cricket::ContentSource source,
+    const cricket::SessionDescription* desc,
+    std::string* err_desc) {
+  // If there's already a pending error then no state transition should happen.
+  // But all call-sites should be verifying this before calling us!
+  ASSERT(error() == cricket::BaseSession::ERROR_NONE);
+  if (action == kOffer) {
+    if (!PushdownTransportDescription(source, cricket::CA_OFFER)) {
+      return BadSdp(source, kPushDownOfferTDFailed, err_desc);
+    }
+    SetState(source == cricket::CS_LOCAL ?
+        STATE_SENTINITIATE : STATE_RECEIVEDINITIATE);
+    if (error() != cricket::BaseSession::ERROR_NONE) {
+      return SetSessionStateFailed(source, error(), err_desc);
+    }
+  } else if (action == kPrAnswer) {
+    if (!PushdownTransportDescription(source, cricket::CA_PRANSWER)) {
+      return BadSdp(source, kPushDownPranswerTDFailed, err_desc);
+    }
+    EnableChannels();
+    SetState(source == cricket::CS_LOCAL ?
+        STATE_SENTPRACCEPT : STATE_RECEIVEDPRACCEPT);
+    if (error() != cricket::BaseSession::ERROR_NONE) {
+      return SetSessionStateFailed(source, error(), err_desc);
+    }
+  } else if (action == kAnswer) {
+    if (!PushdownTransportDescription(source, cricket::CA_ANSWER)) {
+      return BadSdp(source, kPushDownAnswerTDFailed, err_desc);
+    }
+    MaybeEnableMuxingSupport();
+    EnableChannels();
+    SetState(source == cricket::CS_LOCAL ?
+        STATE_SENTACCEPT : STATE_RECEIVEDACCEPT);
+    if (error() != cricket::BaseSession::ERROR_NONE) {
+      return SetSessionStateFailed(source, error(), err_desc);
+    }
+  }
+  return true;
+}
+
+WebRtcSession::Action WebRtcSession::GetAction(const std::string& type) {
+  if (type == SessionDescriptionInterface::kOffer) {
+    return WebRtcSession::kOffer;
+  } else if (type == SessionDescriptionInterface::kPrAnswer) {
+    return WebRtcSession::kPrAnswer;
+  } else if (type == SessionDescriptionInterface::kAnswer) {
+    return WebRtcSession::kAnswer;
+  }
+  ASSERT(false && "unknown action type");
+  return WebRtcSession::kOffer;
+}
+
+bool WebRtcSession::ProcessIceMessage(const IceCandidateInterface* candidate) {
+  if (state() == STATE_INIT) {
+     LOG(LS_ERROR) << "ProcessIceMessage: ICE candidates can't be added "
+                   << "without any offer (local or remote) "
+                   << "session description.";
+     return false;
+  }
+
+  if (!candidate) {
+    LOG(LS_ERROR) << "ProcessIceMessage: Candidate is NULL";
+    return false;
+  }
+
+  if (!local_description() || !remote_description()) {
+    LOG(LS_INFO) << "ProcessIceMessage: Remote description not set, "
+                 << "save the candidate for later use.";
+    saved_candidates_.push_back(
+        new JsepIceCandidate(candidate->sdp_mid(), candidate->sdp_mline_index(),
+                             candidate->candidate()));
+    return true;
+  }
+
+  // Add this candidate to the remote session description.
+  if (!remote_desc_->AddCandidate(candidate)) {
+    LOG(LS_ERROR) << "ProcessIceMessage: Candidate cannot be used";
+    return false;
+  }
+
+  return UseCandidatesInSessionDescription(remote_desc_.get());
+}
+
+bool WebRtcSession::GetTrackIdBySsrc(uint32 ssrc, std::string* id) {
+  if (GetLocalTrackId(ssrc, id)) {
+    if (GetRemoteTrackId(ssrc, id)) {
+      LOG(LS_WARNING) << "SSRC " << ssrc
+                      << " exists in both local and remote descriptions";
+      return true;  // We return the remote track id.
+    }
+    return true;
+  } else {
+    return GetRemoteTrackId(ssrc, id);
+  }
+}
+
+bool WebRtcSession::GetLocalTrackId(uint32 ssrc, std::string* track_id) {
+  if (!BaseSession::local_description())
+    return false;
+  return webrtc::GetTrackIdBySsrc(
+    BaseSession::local_description(), ssrc, track_id);
+}
+
+bool WebRtcSession::GetRemoteTrackId(uint32 ssrc, std::string* track_id) {
+  if (!BaseSession::remote_description())
+      return false;
+  return webrtc::GetTrackIdBySsrc(
+    BaseSession::remote_description(), ssrc, track_id);
+}
+
+std::string WebRtcSession::BadStateErrMsg(
+    const std::string& type, State state) {
+  std::ostringstream desc;
+  desc << "Called with type in wrong state, "
+       << "type: " << type << " state: " << GetStateString(state);
+  return desc.str();
+}
+
+void WebRtcSession::SetAudioPlayout(uint32 ssrc, bool enable) {
+  ASSERT(signaling_thread()->IsCurrent());
+  if (!voice_channel_) {
+    LOG(LS_ERROR) << "SetAudioPlayout: No audio channel exists.";
+    return;
+  }
+  if (!voice_channel_->SetOutputScaling(ssrc, enable ? 1 : 0, enable ? 1 : 0)) {
+    // Allow that SetOutputScaling fail if |enable| is false but assert
+    // otherwise. This in the normal case when the underlying media channel has
+    // already been deleted.
+    ASSERT(enable == false);
+  }
+}
+
+void WebRtcSession::SetAudioSend(uint32 ssrc, bool enable,
+                                 const cricket::AudioOptions& options) {
+  ASSERT(signaling_thread()->IsCurrent());
+  if (!voice_channel_) {
+    LOG(LS_ERROR) << "SetAudioSend: No audio channel exists.";
+    return;
+  }
+  if (!voice_channel_->MuteStream(ssrc, !enable)) {
+    // Allow that MuteStream fail if |enable| is false but assert otherwise.
+    // This in the normal case when the underlying media channel has already
+    // been deleted.
+    ASSERT(enable == false);
+    return;
+  }
+  if (enable)
+    voice_channel_->SetChannelOptions(options);
+}
+
+bool WebRtcSession::SetAudioRenderer(uint32 ssrc,
+                                     cricket::AudioRenderer* renderer) {
+  if (!voice_channel_) {
+    LOG(LS_ERROR) << "SetAudioRenderer: No audio channel exists.";
+    return false;
+  }
+
+  if (!voice_channel_->SetRenderer(ssrc, renderer)) {
+    // SetRenderer() can fail if the ssrc is not mapping to the playout channel.
+    LOG(LS_ERROR) << "SetAudioRenderer: ssrc is incorrect: " << ssrc;
+    return false;
+  }
+
+  return true;
+}
+
+bool WebRtcSession::SetCaptureDevice(uint32 ssrc,
+                                     cricket::VideoCapturer* camera) {
+  ASSERT(signaling_thread()->IsCurrent());
+
+  if (!video_channel_.get()) {
+    // |video_channel_| doesnt't exist. Probably because the remote end doesnt't
+    // support video.
+    LOG(LS_WARNING) << "Video not used in this call.";
+    return false;
+  }
+  if (!video_channel_->SetCapturer(ssrc, camera)) {
+    // Allow that SetCapturer fail if |camera| is NULL but assert otherwise.
+    // This in the normal case when the underlying media channel has already
+    // been deleted.
+    ASSERT(camera == NULL);
+    return false;
+  }
+  return true;
+}
+
+void WebRtcSession::SetVideoPlayout(uint32 ssrc,
+                                    bool enable,
+                                    cricket::VideoRenderer* renderer) {
+  ASSERT(signaling_thread()->IsCurrent());
+  if (!video_channel_) {
+    LOG(LS_WARNING) << "SetVideoPlayout: No video channel exists.";
+    return;
+  }
+  if (!video_channel_->SetRenderer(ssrc, enable ? renderer : NULL)) {
+    // Allow that SetRenderer fail if |renderer| is NULL but assert otherwise.
+    // This in the normal case when the underlying media channel has already
+    // been deleted.
+    ASSERT(renderer == NULL);
+  }
+}
+
+void WebRtcSession::SetVideoSend(uint32 ssrc, bool enable,
+                                 const cricket::VideoOptions* options) {
+  ASSERT(signaling_thread()->IsCurrent());
+  if (!video_channel_) {
+    LOG(LS_WARNING) << "SetVideoSend: No video channel exists.";
+    return;
+  }
+  if (!video_channel_->MuteStream(ssrc, !enable)) {
+    // Allow that MuteStream fail if |enable| is false but assert otherwise.
+    // This in the normal case when the underlying media channel has already
+    // been deleted.
+    ASSERT(enable == false);
+    return;
+  }
+  if (enable && options)
+    video_channel_->SetChannelOptions(*options);
+}
+
+bool WebRtcSession::CanInsertDtmf(const std::string& track_id) {
+  ASSERT(signaling_thread()->IsCurrent());
+  if (!voice_channel_) {
+    LOG(LS_ERROR) << "CanInsertDtmf: No audio channel exists.";
+    return false;
+  }
+  uint32 send_ssrc = 0;
+  // The Dtmf is negotiated per channel not ssrc, so we only check if the ssrc
+  // exists.
+  if (!GetAudioSsrcByTrackId(BaseSession::local_description(), track_id,
+                             &send_ssrc)) {
+    LOG(LS_ERROR) << "CanInsertDtmf: Track does not exist: " << track_id;
+    return false;
+  }
+  return voice_channel_->CanInsertDtmf();
+}
+
+bool WebRtcSession::InsertDtmf(const std::string& track_id,
+                               int code, int duration) {
+  ASSERT(signaling_thread()->IsCurrent());
+  if (!voice_channel_) {
+    LOG(LS_ERROR) << "InsertDtmf: No audio channel exists.";
+    return false;
+  }
+  uint32 send_ssrc = 0;
+  if (!VERIFY(GetAudioSsrcByTrackId(BaseSession::local_description(),
+                                    track_id, &send_ssrc))) {
+    LOG(LS_ERROR) << "InsertDtmf: Track does not exist: " << track_id;
+    return false;
+  }
+  if (!voice_channel_->InsertDtmf(send_ssrc, code, duration,
+                                  cricket::DF_SEND)) {
+    LOG(LS_ERROR) << "Failed to insert DTMF to channel.";
+    return false;
+  }
+  return true;
+}
+
+sigslot::signal0<>* WebRtcSession::GetOnDestroyedSignal() {
+  return &SignalVoiceChannelDestroyed;
+}
+
+talk_base::scoped_refptr<DataChannel> WebRtcSession::CreateDataChannel(
+      const std::string& label,
+      const DataChannelInit* config) {
+  if (state() == STATE_RECEIVEDTERMINATE) {
+    return NULL;
+  }
+  if (data_channel_type_ == cricket::DCT_NONE) {
+    LOG(LS_ERROR) << "CreateDataChannel: Data is not supported in this call.";
+    return NULL;
+  }
+  DataChannelInit new_config = config ? (*config) : DataChannelInit();
+
+  if (data_channel_type_ == cricket::DCT_SCTP) {
+    if (new_config.id < 0) {
+      if (!mediastream_signaling_->AllocateSctpId(&new_config.id)) {
+        LOG(LS_ERROR) << "No id can be allocated for the SCTP data channel.";
+        return NULL;
+      }
+    } else if (!mediastream_signaling_->IsSctpIdAvailable(new_config.id)) {
+      LOG(LS_ERROR) << "Failed to create a SCTP data channel "
+                    << "because the id is already in use or out of range.";
+      return NULL;
+    }
+  }
+  talk_base::scoped_refptr<DataChannel> channel(
+      DataChannel::Create(this, label, &new_config));
+  if (channel == NULL)
+    return NULL;
+  if (!mediastream_signaling_->AddDataChannel(channel))
+    return NULL;
+  return channel;
+}
+
+cricket::DataChannelType WebRtcSession::data_channel_type() const {
+  return data_channel_type_;
+}
+
+void WebRtcSession::SetIceConnectionState(
+      PeerConnectionInterface::IceConnectionState state) {
+  if (ice_connection_state_ == state) {
+    return;
+  }
+
+  // ASSERT that the requested transition is allowed.  Note that
+  // WebRtcSession does not implement "kIceConnectionClosed" (that is handled
+  // within PeerConnection).  This switch statement should compile away when
+  // ASSERTs are disabled.
+  switch (ice_connection_state_) {
+    case PeerConnectionInterface::kIceConnectionNew:
+      ASSERT(state == PeerConnectionInterface::kIceConnectionChecking);
+      break;
+    case PeerConnectionInterface::kIceConnectionChecking:
+      ASSERT(state == PeerConnectionInterface::kIceConnectionFailed ||
+             state == PeerConnectionInterface::kIceConnectionConnected);
+      break;
+    case PeerConnectionInterface::kIceConnectionConnected:
+      ASSERT(state == PeerConnectionInterface::kIceConnectionDisconnected ||
+             state == PeerConnectionInterface::kIceConnectionChecking ||
+             state == PeerConnectionInterface::kIceConnectionCompleted);
+      break;
+    case PeerConnectionInterface::kIceConnectionCompleted:
+      ASSERT(state == PeerConnectionInterface::kIceConnectionConnected ||
+             state == PeerConnectionInterface::kIceConnectionDisconnected);
+      break;
+    case PeerConnectionInterface::kIceConnectionFailed:
+      ASSERT(state == PeerConnectionInterface::kIceConnectionNew);
+      break;
+    case PeerConnectionInterface::kIceConnectionDisconnected:
+      ASSERT(state == PeerConnectionInterface::kIceConnectionChecking ||
+             state == PeerConnectionInterface::kIceConnectionConnected ||
+             state == PeerConnectionInterface::kIceConnectionCompleted ||
+             state == PeerConnectionInterface::kIceConnectionFailed);
+      break;
+    case PeerConnectionInterface::kIceConnectionClosed:
+      ASSERT(false);
+      break;
+    default:
+      ASSERT(false);
+      break;
+  }
+
+  ice_connection_state_ = state;
+  if (ice_observer_) {
+    ice_observer_->OnIceConnectionChange(ice_connection_state_);
+  }
+}
+
+void WebRtcSession::OnTransportRequestSignaling(
+    cricket::Transport* transport) {
+  ASSERT(signaling_thread()->IsCurrent());
+  transport->OnSignalingReady();
+  if (ice_observer_) {
+    ice_observer_->OnIceGatheringChange(
+      PeerConnectionInterface::kIceGatheringGathering);
+  }
+}
+
+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());
+  // TODO(bemasc): Expose more API from Transport to detect when
+  // candidate selection starts or stops, due to success or failure.
+  if (transport->all_channels_writable()) {
+    if (ice_connection_state_ ==
+            PeerConnectionInterface::kIceConnectionChecking ||
+        ice_connection_state_ ==
+            PeerConnectionInterface::kIceConnectionDisconnected) {
+      SetIceConnectionState(PeerConnectionInterface::kIceConnectionConnected);
+    }
+  } else if (transport->HasChannels()) {
+    // If the current state is Connected or Completed, then there were writable
+    // channels but now there are not, so the next state must be Disconnected.
+    if (ice_connection_state_ ==
+            PeerConnectionInterface::kIceConnectionConnected ||
+        ice_connection_state_ ==
+            PeerConnectionInterface::kIceConnectionCompleted) {
+      SetIceConnectionState(
+          PeerConnectionInterface::kIceConnectionDisconnected);
+    }
+  }
+}
+
+void WebRtcSession::OnTransportProxyCandidatesReady(
+    cricket::TransportProxy* proxy, const cricket::Candidates& candidates) {
+  ASSERT(signaling_thread()->IsCurrent());
+  ProcessNewLocalCandidate(proxy->content_name(), candidates);
+}
+
+bool WebRtcSession::ExpectSetLocalDescription(Action action) {
+  return ((action == kOffer && state() == STATE_INIT) ||
+          // update local offer
+          (action == kOffer && state() == STATE_SENTINITIATE) ||
+          // update the current ongoing session.
+          (action == kOffer && state() == STATE_RECEIVEDACCEPT) ||
+          (action == kOffer && state() == STATE_SENTACCEPT) ||
+          (action == kOffer && state() == STATE_INPROGRESS) ||
+          // accept remote offer
+          (action == kAnswer && state() == STATE_RECEIVEDINITIATE) ||
+          (action == kAnswer && state() == STATE_SENTPRACCEPT) ||
+          (action == kPrAnswer && state() == STATE_RECEIVEDINITIATE) ||
+          (action == kPrAnswer && state() == STATE_SENTPRACCEPT));
+}
+
+bool WebRtcSession::ExpectSetRemoteDescription(Action action) {
+  return ((action == kOffer && state() == STATE_INIT) ||
+          // update remote offer
+          (action == kOffer && state() == STATE_RECEIVEDINITIATE) ||
+          // update the current ongoing session
+          (action == kOffer && state() == STATE_RECEIVEDACCEPT) ||
+          (action == kOffer && state() == STATE_SENTACCEPT) ||
+          (action == kOffer && state() == STATE_INPROGRESS) ||
+          // accept local offer
+          (action == kAnswer && state() == STATE_SENTINITIATE) ||
+          (action == kAnswer && state() == STATE_RECEIVEDPRACCEPT) ||
+          (action == kPrAnswer && state() == STATE_SENTINITIATE) ||
+          (action == kPrAnswer && state() == STATE_RECEIVEDPRACCEPT));
+}
+
+void WebRtcSession::OnCandidatesAllocationDone() {
+  ASSERT(signaling_thread()->IsCurrent());
+  if (ice_observer_) {
+    ice_observer_->OnIceGatheringChange(
+      PeerConnectionInterface::kIceGatheringComplete);
+    ice_observer_->OnIceComplete();
+  }
+}
+
+// Enabling voice and video channel.
+void WebRtcSession::EnableChannels() {
+  if (voice_channel_ && !voice_channel_->enabled())
+    voice_channel_->Enable(true);
+
+  if (video_channel_ && !video_channel_->enabled())
+    video_channel_->Enable(true);
+
+  if (data_channel_.get() && !data_channel_->enabled())
+    data_channel_->Enable(true);
+}
+
+void WebRtcSession::ProcessNewLocalCandidate(
+    const std::string& content_name,
+    const cricket::Candidates& candidates) {
+  int sdp_mline_index;
+  if (!GetLocalCandidateMediaIndex(content_name, &sdp_mline_index)) {
+    LOG(LS_ERROR) << "ProcessNewLocalCandidate: content name "
+                  << content_name << " not found";
+    return;
+  }
+
+  for (cricket::Candidates::const_iterator citer = candidates.begin();
+      citer != candidates.end(); ++citer) {
+    // Use content_name as the candidate media id.
+    JsepIceCandidate candidate(content_name, sdp_mline_index, *citer);
+    if (ice_observer_) {
+      ice_observer_->OnIceCandidate(&candidate);
+    }
+    if (local_desc_) {
+      local_desc_->AddCandidate(&candidate);
+    }
+  }
+}
+
+// Returns the media index for a local ice candidate given the content name.
+bool WebRtcSession::GetLocalCandidateMediaIndex(const std::string& content_name,
+                                                int* sdp_mline_index) {
+  if (!BaseSession::local_description() || !sdp_mline_index)
+    return false;
+
+  bool content_found = false;
+  const ContentInfos& contents = BaseSession::local_description()->contents();
+  for (size_t index = 0; index < contents.size(); ++index) {
+    if (contents[index].name == content_name) {
+      *sdp_mline_index = index;
+      content_found = true;
+      break;
+    }
+  }
+  return content_found;
+}
+
+bool WebRtcSession::UseCandidatesInSessionDescription(
+    const SessionDescriptionInterface* remote_desc) {
+  if (!remote_desc)
+    return true;
+  bool ret = true;
+  for (size_t m = 0; m < remote_desc->number_of_mediasections(); ++m) {
+    const IceCandidateCollection* candidates = remote_desc->candidates(m);
+    for  (size_t n = 0; n < candidates->count(); ++n) {
+      ret = UseCandidate(candidates->at(n));
+      if (!ret)
+        break;
+    }
+  }
+  return ret;
+}
+
+bool WebRtcSession::UseCandidate(
+    const IceCandidateInterface* candidate) {
+
+  size_t mediacontent_index = static_cast<size_t>(candidate->sdp_mline_index());
+  size_t remote_content_size =
+      BaseSession::remote_description()->contents().size();
+  if (mediacontent_index >= remote_content_size) {
+    LOG(LS_ERROR)
+        << "UseRemoteCandidateInSession: Invalid candidate media index.";
+    return false;
+  }
+
+  cricket::ContentInfo content =
+      BaseSession::remote_description()->contents()[mediacontent_index];
+  std::vector<cricket::Candidate> candidates;
+  candidates.push_back(candidate->candidate());
+  // Invoking BaseSession method to handle remote candidates.
+  std::string error;
+  if (OnRemoteCandidates(content.name, candidates, &error)) {
+    // Candidates successfully submitted for checking.
+    if (ice_connection_state_ == PeerConnectionInterface::kIceConnectionNew ||
+        ice_connection_state_ ==
+            PeerConnectionInterface::kIceConnectionDisconnected) {
+      // If state is New, then the session has just gotten its first remote ICE
+      // candidates, so go to Checking.
+      // If state is Disconnected, the session is re-using old candidates or
+      // receiving additional ones, so go to Checking.
+      // If state is Connected, stay Connected.
+      // TODO(bemasc): If state is Connected, and the new candidates are for a
+      // newly added transport, then the state actually _should_ move to
+      // checking.  Add a way to distinguish that case.
+      SetIceConnectionState(PeerConnectionInterface::kIceConnectionChecking);
+    }
+    // TODO(bemasc): If state is Completed, go back to Connected.
+  } else {
+    LOG(LS_WARNING) << error;
+  }
+  return true;
+}
+
+void WebRtcSession::RemoveUnusedChannelsAndTransports(
+    const SessionDescription* desc) {
+  const cricket::ContentInfo* voice_info =
+      cricket::GetFirstAudioContent(desc);
+  if ((!voice_info || voice_info->rejected) && voice_channel_) {
+    mediastream_signaling_->OnAudioChannelClose();
+    SignalVoiceChannelDestroyed();
+    const std::string content_name = voice_channel_->content_name();
+    channel_manager_->DestroyVoiceChannel(voice_channel_.release());
+    DestroyTransportProxy(content_name);
+  }
+
+  const cricket::ContentInfo* video_info =
+      cricket::GetFirstVideoContent(desc);
+  if ((!video_info || video_info->rejected) && video_channel_) {
+    mediastream_signaling_->OnVideoChannelClose();
+    SignalVideoChannelDestroyed();
+    const std::string content_name = video_channel_->content_name();
+    channel_manager_->DestroyVideoChannel(video_channel_.release());
+    DestroyTransportProxy(content_name);
+  }
+
+  const cricket::ContentInfo* data_info =
+      cricket::GetFirstDataContent(desc);
+  if ((!data_info || data_info->rejected) && data_channel_) {
+    mediastream_signaling_->OnDataChannelClose();
+    SignalDataChannelDestroyed();
+    const std::string content_name = data_channel_->content_name();
+    channel_manager_->DestroyDataChannel(data_channel_.release());
+    DestroyTransportProxy(content_name);
+  }
+}
+
+bool WebRtcSession::CreateChannels(const SessionDescription* desc) {
+  // Disabling the BUNDLE flag in PortAllocator if offer disabled it.
+  if (state() == STATE_INIT && !desc->HasGroup(cricket::GROUP_TYPE_BUNDLE)) {
+    port_allocator()->set_flags(port_allocator()->flags() &
+                                ~cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+  }
+
+  // Creating the media channels and transport proxies.
+  const cricket::ContentInfo* voice = cricket::GetFirstAudioContent(desc);
+  if (voice && !voice->rejected && !voice_channel_) {
+    if (!CreateVoiceChannel(desc)) {
+      LOG(LS_ERROR) << "Failed to create voice channel.";
+      return false;
+    }
+  }
+
+  const cricket::ContentInfo* video = cricket::GetFirstVideoContent(desc);
+  if (video && !video->rejected && !video_channel_) {
+    if (!CreateVideoChannel(desc)) {
+      LOG(LS_ERROR) << "Failed to create video channel.";
+      return false;
+    }
+  }
+
+  const cricket::ContentInfo* data = cricket::GetFirstDataContent(desc);
+  if (data_channel_type_ != cricket::DCT_NONE &&
+      data && !data->rejected && !data_channel_.get()) {
+    if (!CreateDataChannel(desc)) {
+      LOG(LS_ERROR) << "Failed to create data channel.";
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool WebRtcSession::CreateVoiceChannel(const SessionDescription* desc) {
+  const cricket::ContentInfo* voice = cricket::GetFirstAudioContent(desc);
+  voice_channel_.reset(channel_manager_->CreateVoiceChannel(
+      this, voice->name, true));
+  return voice_channel_ ? true : false;
+}
+
+bool WebRtcSession::CreateVideoChannel(const SessionDescription* desc) {
+  const cricket::ContentInfo* video = cricket::GetFirstVideoContent(desc);
+  video_channel_.reset(channel_manager_->CreateVideoChannel(
+      this, video->name, true, voice_channel_.get()));
+  return video_channel_ ? true : false;
+}
+
+bool WebRtcSession::CreateDataChannel(const SessionDescription* desc) {
+  const cricket::ContentInfo* data = cricket::GetFirstDataContent(desc);
+  bool rtcp = (data_channel_type_ == cricket::DCT_RTP);
+  data_channel_.reset(channel_manager_->CreateDataChannel(
+      this, data->name, rtcp, data_channel_type_));
+  if (!data_channel_.get()) {
+    return false;
+  }
+  return true;
+}
+
+void WebRtcSession::CopySavedCandidates(
+    SessionDescriptionInterface* dest_desc) {
+  if (!dest_desc) {
+    ASSERT(false);
+    return;
+  }
+  for (size_t i = 0; i < saved_candidates_.size(); ++i) {
+    dest_desc->AddCandidate(saved_candidates_[i]);
+    delete saved_candidates_[i];
+  }
+  saved_candidates_.clear();
+}
+
+void WebRtcSession::UpdateSessionDescriptionSecurePolicy(
+    SessionDescription* sdesc) {
+  if (!sdesc) {
+    return;
+  }
+
+  // Updating the |crypto_required_| in MediaContentDescription to the
+  // appropriate state based on the current security policy.
+  for (cricket::ContentInfos::iterator iter = sdesc->contents().begin();
+       iter != sdesc->contents().end(); ++iter) {
+    if (cricket::IsMediaContent(&*iter)) {
+      MediaContentDescription* mdesc =
+          static_cast<MediaContentDescription*> (iter->description);
+      if (mdesc) {
+        mdesc->set_crypto_required(
+            session_desc_factory_.secure() == cricket::SEC_REQUIRED);
+      }
+    }
+  }
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/webrtcsession.h b/talk/app/webrtc/webrtcsession.h
new file mode 100644
index 0000000..045d347
--- /dev/null
+++ b/talk/app/webrtc/webrtcsession.h
@@ -0,0 +1,295 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 <string>
+
+#include "talk/app/webrtc/peerconnectioninterface.h"
+#include "talk/app/webrtc/dtmfsender.h"
+#include "talk/app/webrtc/mediastreamprovider.h"
+#include "talk/app/webrtc/datachannel.h"
+#include "talk/app/webrtc/statstypes.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/thread.h"
+#include "talk/media/base/mediachannel.h"
+#include "talk/p2p/base/session.h"
+#include "talk/p2p/base/transportdescriptionfactory.h"
+#include "talk/session/media/mediasession.h"
+
+namespace cricket {
+
+class ChannelManager;
+class DataChannel;
+class StatsReport;
+class Transport;
+class VideoCapturer;
+class BaseChannel;
+class VideoChannel;
+class VoiceChannel;
+
+}  // namespace cricket
+
+namespace webrtc {
+
+class IceRestartAnswerLatch;
+class MediaStreamSignaling;
+
+extern const char kSetLocalSdpFailed[];
+extern const char kSetRemoteSdpFailed[];
+extern const char kCreateChannelFailed[];
+extern const char kInvalidCandidates[];
+extern const char kInvalidSdp[];
+extern const char kMlineMismatch[];
+extern const char kSdpWithoutCrypto[];
+extern const char kSessionError[];
+extern const char kUpdateStateFailed[];
+extern const char kPushDownOfferTDFailed[];
+extern const char kPushDownPranswerTDFailed[];
+extern const char kPushDownAnswerTDFailed[];
+
+// ICE state callback interface.
+class IceObserver {
+ public:
+  // Called any time the IceConnectionState changes
+  virtual void OnIceConnectionChange(
+      PeerConnectionInterface::IceConnectionState new_state) {}
+  // Called any time the IceGatheringState changes
+  virtual void OnIceGatheringChange(
+      PeerConnectionInterface::IceGatheringState new_state) {}
+  // New Ice candidate have been found.
+  virtual void OnIceCandidate(const IceCandidateInterface* candidate) = 0;
+  // All Ice candidates have been found.
+  // TODO(bemasc): Remove this once callers transition to OnIceGatheringChange.
+  // (via PeerConnectionObserver)
+  virtual void OnIceComplete() {}
+
+ protected:
+  ~IceObserver() {}
+};
+
+class WebRtcSession : public cricket::BaseSession,
+                      public AudioProviderInterface,
+                      public DataChannelFactory,
+                      public VideoProviderInterface,
+                      public DtmfProviderInterface {
+ public:
+  WebRtcSession(cricket::ChannelManager* channel_manager,
+                talk_base::Thread* signaling_thread,
+                talk_base::Thread* worker_thread,
+                cricket::PortAllocator* port_allocator,
+                MediaStreamSignaling* mediastream_signaling);
+  virtual ~WebRtcSession();
+
+  bool Initialize(const MediaConstraintsInterface* constraints);
+  // Deletes the voice, video and data channel and changes the session state
+  // to STATE_RECEIVEDTERMINATE.
+  void Terminate();
+
+  void RegisterIceObserver(IceObserver* observer) {
+    ice_observer_ = observer;
+  }
+
+  virtual cricket::VoiceChannel* voice_channel() {
+    return voice_channel_.get();
+  }
+  virtual cricket::VideoChannel* video_channel() {
+    return video_channel_.get();
+  }
+  virtual cricket::DataChannel* data_channel() {
+    return data_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;
+
+  SessionDescriptionInterface* CreateOffer(
+      const MediaConstraintsInterface* constraints);
+
+  SessionDescriptionInterface* CreateAnswer(
+      const MediaConstraintsInterface* constraints);
+
+  bool SetLocalDescription(SessionDescriptionInterface* desc,
+                           std::string* err_desc);
+  bool SetRemoteDescription(SessionDescriptionInterface* desc,
+                            std::string* err_desc);
+  bool ProcessIceMessage(const IceCandidateInterface* ice_candidate);
+  const SessionDescriptionInterface* local_description() const {
+    return local_desc_.get();
+  }
+  const SessionDescriptionInterface* remote_description() const {
+    return remote_desc_.get();
+  }
+
+  // Get the id used as a media stream track's "id" field from ssrc.
+  virtual bool GetTrackIdBySsrc(uint32 ssrc, std::string* id);
+
+  // AudioMediaProviderInterface implementation.
+  virtual void SetAudioPlayout(uint32 ssrc, bool enable) OVERRIDE;
+  virtual void SetAudioSend(uint32 ssrc, bool enable,
+                            const cricket::AudioOptions& options) OVERRIDE;
+  virtual bool SetAudioRenderer(uint32 ssrc,
+                                cricket::AudioRenderer* renderer) OVERRIDE;
+
+  // Implements VideoMediaProviderInterface.
+  virtual bool SetCaptureDevice(uint32 ssrc,
+                                cricket::VideoCapturer* camera) OVERRIDE;
+  virtual void SetVideoPlayout(uint32 ssrc,
+                               bool enable,
+                               cricket::VideoRenderer* renderer) OVERRIDE;
+  virtual void SetVideoSend(uint32 ssrc, bool enable,
+                            const cricket::VideoOptions* options) OVERRIDE;
+
+  // Implements DtmfProviderInterface.
+  virtual bool CanInsertDtmf(const std::string& track_id);
+  virtual bool InsertDtmf(const std::string& track_id,
+                          int code, int duration);
+  virtual sigslot::signal0<>* GetOnDestroyedSignal();
+
+  talk_base::scoped_refptr<DataChannel> CreateDataChannel(
+      const std::string& label,
+      const DataChannelInit* config);
+
+  cricket::DataChannelType data_channel_type() const;
+
+ private:
+  // Indicates the type of SessionDescription in a call to SetLocalDescription
+  // and SetRemoteDescription.
+  enum Action {
+    kOffer,
+    kPrAnswer,
+    kAnswer,
+  };
+  // Invokes ConnectChannels() on transport proxies, which initiates ice
+  // candidates allocation.
+  bool StartCandidatesAllocation();
+  bool UpdateSessionState(Action action, cricket::ContentSource source,
+                          const cricket::SessionDescription* desc,
+                          std::string* err_desc);
+  static Action GetAction(const std::string& type);
+
+  // 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 OnTransportProxyCandidatesReady(
+      cricket::TransportProxy* proxy,
+      const cricket::Candidates& candidates);
+  virtual void OnCandidatesAllocationDone();
+
+  // Check if a call to SetLocalDescription is acceptable with |action|.
+  bool ExpectSetLocalDescription(Action action);
+  // Check if a call to SetRemoteDescription is acceptable with |action|.
+  bool ExpectSetRemoteDescription(Action action);
+  // Creates local session description with audio and video contents.
+  bool CreateDefaultLocalDescription();
+  // Enables media channels to allow sending of media.
+  void EnableChannels();
+  // Creates a JsepIceCandidate and adds it to the local session description
+  // and notify observers. Called when a new local candidate have been found.
+  void ProcessNewLocalCandidate(const std::string& content_name,
+                                const cricket::Candidates& candidates);
+  // Returns the media index for a local ice candidate given the content name.
+  // Returns false if the local session description does not have a media
+  // content called  |content_name|.
+  bool GetLocalCandidateMediaIndex(const std::string& content_name,
+                                   int* sdp_mline_index);
+  // Uses all remote candidates in |remote_desc| in this session.
+  bool UseCandidatesInSessionDescription(
+      const SessionDescriptionInterface* remote_desc);
+  // Uses |candidate| in this session.
+  bool UseCandidate(const IceCandidateInterface* candidate);
+  // Deletes the corresponding channel of contents that don't exist in |desc|.
+  // |desc| can be null. This means that all channels are deleted.
+  void RemoveUnusedChannelsAndTransports(
+      const cricket::SessionDescription* desc);
+
+  // Allocates media channels based on the |desc|. If |desc| doesn't have
+  // the BUNDLE option, this method will disable BUNDLE in PortAllocator.
+  // This method will also delete any existing media channels before creating.
+  bool CreateChannels(const cricket::SessionDescription* desc);
+
+  // Helper methods to create media channels.
+  bool CreateVoiceChannel(const cricket::SessionDescription* desc);
+  bool CreateVideoChannel(const cricket::SessionDescription* desc);
+  bool CreateDataChannel(const cricket::SessionDescription* desc);
+  // Copy the candidates from |saved_candidates_| to |dest_desc|.
+  // The |saved_candidates_| will be cleared after this function call.
+  void CopySavedCandidates(SessionDescriptionInterface* dest_desc);
+
+  // Forces |desc->crypto_required| to the appropriate state based on the
+  // current security policy, to ensure a failure occurs if there is an error
+  // in crypto negotiation.
+  // Called when processing the local session description.
+  void UpdateSessionDescriptionSecurePolicy(cricket::SessionDescription* desc);
+
+  bool GetLocalTrackId(uint32 ssrc, std::string* track_id);
+  bool GetRemoteTrackId(uint32 ssrc, std::string* track_id);
+
+  std::string BadStateErrMsg(const std::string& type, State state);
+  void SetIceConnectionState(PeerConnectionInterface::IceConnectionState state);
+
+  talk_base::scoped_ptr<cricket::VoiceChannel> voice_channel_;
+  talk_base::scoped_ptr<cricket::VideoChannel> video_channel_;
+  talk_base::scoped_ptr<cricket::DataChannel> data_channel_;
+  cricket::ChannelManager* channel_manager_;
+  cricket::TransportDescriptionFactory transport_desc_factory_;
+  cricket::MediaSessionDescriptionFactory session_desc_factory_;
+  MediaStreamSignaling* mediastream_signaling_;
+  IceObserver* ice_observer_;
+  PeerConnectionInterface::IceConnectionState ice_connection_state_;
+  talk_base::scoped_ptr<SessionDescriptionInterface> local_desc_;
+  talk_base::scoped_ptr<SessionDescriptionInterface> remote_desc_;
+  // Candidates that arrived before the remote description was set.
+  std::vector<IceCandidateInterface*> saved_candidates_;
+  uint64 session_version_;
+  // If the remote peer is using a older version of implementation.
+  bool older_version_remote_peer_;
+  // Specifies which kind of data channel is allowed. This is controlled
+  // by the chrome command-line flag and constraints:
+  // 1. If chrome command-line switch 'enable-sctp-data-channels' is enabled,
+  // constraint kEnableDtlsSrtp is true, and constaint kEnableRtpDataChannels is
+  // not set or false, SCTP is allowed (DCT_SCTP);
+  // 2. If constraint kEnableRtpDataChannels is true, RTP is allowed (DCT_RTP);
+  // 3. If both 1&2 are false, data channel is not allowed (DCT_NONE).
+  cricket::DataChannelType data_channel_type_;
+  talk_base::scoped_ptr<IceRestartAnswerLatch> ice_restart_latch_;
+  sigslot::signal0<> SignalVoiceChannelDestroyed;
+  sigslot::signal0<> SignalVideoChannelDestroyed;
+  sigslot::signal0<> SignalDataChannelDestroyed;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_WEBRTCSESSION_H_
diff --git a/talk/app/webrtc/webrtcsession_unittest.cc b/talk/app/webrtc/webrtcsession_unittest.cc
new file mode 100644
index 0000000..55b2950
--- /dev/null
+++ b/talk/app/webrtc/webrtcsession_unittest.cc
@@ -0,0 +1,2473 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/audiotrack.h"
+#include "talk/app/webrtc/jsepicecandidate.h"
+#include "talk/app/webrtc/jsepsessiondescription.h"
+#include "talk/app/webrtc/mediastreamsignaling.h"
+#include "talk/app/webrtc/streamcollection.h"
+#include "talk/app/webrtc/videotrack.h"
+#include "talk/app/webrtc/test/fakeconstraints.h"
+#include "talk/app/webrtc/webrtcsession.h"
+#include "talk/base/fakenetwork.h"
+#include "talk/base/firewallsocketserver.h"
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/network.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/sslstreamadapter.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+#include "talk/base/virtualsocketserver.h"
+#include "talk/media/base/fakemediaengine.h"
+#include "talk/media/base/fakevideorenderer.h"
+#include "talk/media/base/mediachannel.h"
+#include "talk/media/devices/fakedevicemanager.h"
+#include "talk/p2p/base/stunserver.h"
+#include "talk/p2p/base/teststunserver.h"
+#include "talk/p2p/client/basicportallocator.h"
+#include "talk/session/media/channelmanager.h"
+#include "talk/session/media/mediasession.h"
+
+#define MAYBE_SKIP_TEST(feature)                    \
+  if (!(feature())) {                               \
+    LOG(LS_INFO) << "Feature disabled... skipping"; \
+    return;                                         \
+  }
+
+using cricket::BaseSession;
+using cricket::DF_PLAY;
+using cricket::DF_SEND;
+using cricket::FakeVoiceMediaChannel;
+using cricket::NS_GINGLE_P2P;
+using cricket::NS_JINGLE_ICE_UDP;
+using cricket::TransportInfo;
+using cricket::kDtmfDelay;
+using cricket::kDtmfReset;
+using talk_base::SocketAddress;
+using talk_base::scoped_ptr;
+using webrtc::CreateSessionDescription;
+using webrtc::FakeConstraints;
+using webrtc::IceCandidateCollection;
+using webrtc::JsepIceCandidate;
+using webrtc::JsepSessionDescription;
+using webrtc::PeerConnectionInterface;
+using webrtc::SessionDescriptionInterface;
+using webrtc::StreamCollection;
+using webrtc::kMlineMismatch;
+using webrtc::kSdpWithoutCrypto;
+using webrtc::kSessionError;
+using webrtc::kSetLocalSdpFailed;
+using webrtc::kSetRemoteSdpFailed;
+using webrtc::kPushDownAnswerTDFailed;
+using webrtc::kPushDownPranswerTDFailed;
+
+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 kSessionVersion[] = "1";
+
+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";
+
+// Media index of candidates belonging to the first media content.
+static const int kMediaContentIndex0 = 0;
+static const char kMediaContentName0[] = "audio";
+
+// Media index of candidates belonging to the second media content.
+static const int kMediaContentIndex1 = 1;
+static const char kMediaContentName1[] = "video";
+
+static const int kIceCandidatesTimeout = 10000;
+
+static const cricket::AudioCodec
+    kTelephoneEventCodec(106, "telephone-event", 8000, 0, 1, 0);
+static const cricket::AudioCodec kCNCodec1(102, "CN", 8000, 0, 1, 0);
+static const cricket::AudioCodec kCNCodec2(103, "CN", 16000, 0, 1, 0);
+
+// Add some extra |newlines| to the |message| after |line|.
+static void InjectAfter(const std::string& line,
+                        const std::string& newlines,
+                        std::string* message) {
+  const std::string tmp = line + newlines;
+  talk_base::replace_substrs(line.c_str(), line.length(),
+                             tmp.c_str(), tmp.length(), message);
+}
+
+class MockIceObserver : public webrtc::IceObserver {
+ public:
+  MockIceObserver()
+      : oncandidatesready_(false),
+        ice_connection_state_(PeerConnectionInterface::kIceConnectionNew),
+        ice_gathering_state_(PeerConnectionInterface::kIceGatheringNew) {
+  }
+
+  virtual void OnIceConnectionChange(
+      PeerConnectionInterface::IceConnectionState new_state) {
+    ice_connection_state_ = new_state;
+  }
+  virtual void OnIceGatheringChange(
+      PeerConnectionInterface::IceGatheringState new_state) {
+    // We can never transition back to "new".
+    EXPECT_NE(PeerConnectionInterface::kIceGatheringNew, new_state);
+    ice_gathering_state_ = new_state;
+
+    // oncandidatesready_ really means "ICE gathering is complete".
+    // This if statement ensures that this value remains correct when we
+    // transition from kIceGatheringComplete to kIceGatheringGathering.
+    if (new_state == PeerConnectionInterface::kIceGatheringGathering) {
+      oncandidatesready_ = false;
+    }
+  }
+
+  // Found a new candidate.
+  virtual void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) {
+    if (candidate->sdp_mline_index() == kMediaContentIndex0) {
+      mline_0_candidates_.push_back(candidate->candidate());
+    } else if (candidate->sdp_mline_index() == kMediaContentIndex1) {
+      mline_1_candidates_.push_back(candidate->candidate());
+    }
+    // The ICE gathering state should always be Gathering when a candidate is
+    // received (or possibly Completed in the case of the final candidate).
+    EXPECT_NE(PeerConnectionInterface::kIceGatheringNew, ice_gathering_state_);
+  }
+
+  // TODO(bemasc): Remove this once callers transition to OnIceGatheringChange.
+  virtual void OnIceComplete() {
+    EXPECT_FALSE(oncandidatesready_);
+    oncandidatesready_ = true;
+
+    // OnIceGatheringChange(IceGatheringCompleted) and OnIceComplete() should
+    // be called approximately simultaneously.  For ease of testing, this
+    // check additionally requires that they be called in the above order.
+    EXPECT_EQ(PeerConnectionInterface::kIceGatheringComplete,
+              ice_gathering_state_);
+  }
+
+  bool oncandidatesready_;
+  std::vector<cricket::Candidate> mline_0_candidates_;
+  std::vector<cricket::Candidate> mline_1_candidates_;
+  PeerConnectionInterface::IceConnectionState ice_connection_state_;
+  PeerConnectionInterface::IceGatheringState ice_gathering_state_;
+};
+
+class WebRtcSessionForTest : public webrtc::WebRtcSession {
+ public:
+  WebRtcSessionForTest(cricket::ChannelManager* cmgr,
+                       talk_base::Thread* signaling_thread,
+                       talk_base::Thread* worker_thread,
+                       cricket::PortAllocator* port_allocator,
+                       webrtc::IceObserver* ice_observer,
+                       webrtc::MediaStreamSignaling* mediastream_signaling)
+    : WebRtcSession(cmgr, signaling_thread, worker_thread, port_allocator,
+                    mediastream_signaling) {
+    RegisterIceObserver(ice_observer);
+  }
+  virtual ~WebRtcSessionForTest() {}
+
+  using cricket::BaseSession::GetTransportProxy;
+  using webrtc::WebRtcSession::SetAudioPlayout;
+  using webrtc::WebRtcSession::SetAudioSend;
+  using webrtc::WebRtcSession::SetCaptureDevice;
+  using webrtc::WebRtcSession::SetVideoPlayout;
+  using webrtc::WebRtcSession::SetVideoSend;
+};
+
+class FakeMediaStreamSignaling : public webrtc::MediaStreamSignaling,
+                                 public webrtc::MediaStreamSignalingObserver {
+ public:
+  FakeMediaStreamSignaling() :
+    webrtc::MediaStreamSignaling(talk_base::Thread::Current(), this) {
+  }
+
+  void SendAudioVideoStream1() {
+    ClearLocalStreams();
+    AddLocalStream(CreateStream(kStream1, kAudioTrack1, kVideoTrack1));
+  }
+
+  void SendAudioVideoStream2() {
+    ClearLocalStreams();
+    AddLocalStream(CreateStream(kStream2, kAudioTrack2, kVideoTrack2));
+  }
+
+  void SendAudioVideoStream1And2() {
+    ClearLocalStreams();
+    AddLocalStream(CreateStream(kStream1, kAudioTrack1, kVideoTrack1));
+    AddLocalStream(CreateStream(kStream2, kAudioTrack2, kVideoTrack2));
+  }
+
+  void SendNothing() {
+    ClearLocalStreams();
+  }
+
+  void UseOptionsAudioOnly() {
+    ClearLocalStreams();
+    AddLocalStream(CreateStream(kStream2, kAudioTrack2, ""));
+  }
+
+  void UseOptionsVideoOnly() {
+    ClearLocalStreams();
+    AddLocalStream(CreateStream(kStream2, "", kVideoTrack2));
+  }
+
+  void ClearLocalStreams() {
+    while (local_streams()->count() != 0) {
+     RemoveLocalStream(local_streams()->at(0));
+    }
+  }
+
+  // Implements MediaStreamSignalingObserver.
+  virtual void OnAddRemoteStream(webrtc::MediaStreamInterface* stream) {
+  }
+  virtual void OnRemoveRemoteStream(webrtc::MediaStreamInterface* stream) {
+  }
+  virtual void OnAddDataChannel(webrtc::DataChannelInterface* data_channel) {
+  }
+  virtual void OnAddLocalAudioTrack(webrtc::MediaStreamInterface* stream,
+                                    webrtc::AudioTrackInterface* audio_track,
+                                    uint32 ssrc) {
+  }
+  virtual void OnAddLocalVideoTrack(webrtc::MediaStreamInterface* stream,
+                                    webrtc::VideoTrackInterface* video_track,
+                                    uint32 ssrc) {
+  }
+  virtual void OnAddRemoteAudioTrack(webrtc::MediaStreamInterface* stream,
+                                     webrtc::AudioTrackInterface* audio_track,
+                                     uint32 ssrc) {
+  }
+
+  virtual void OnAddRemoteVideoTrack(webrtc::MediaStreamInterface* stream,
+                                     webrtc::VideoTrackInterface* video_track,
+                                     uint32 ssrc) {
+  }
+
+  virtual void OnRemoveRemoteAudioTrack(
+      webrtc::MediaStreamInterface* stream,
+      webrtc::AudioTrackInterface* audio_track) {
+  }
+
+  virtual void OnRemoveRemoteVideoTrack(
+      webrtc::MediaStreamInterface* stream,
+      webrtc::VideoTrackInterface* video_track) {
+  }
+
+  virtual void OnRemoveLocalAudioTrack(
+      webrtc::MediaStreamInterface* stream,
+      webrtc::AudioTrackInterface* audio_track) {
+  }
+  virtual void OnRemoveLocalVideoTrack(
+      webrtc::MediaStreamInterface* stream,
+      webrtc::VideoTrackInterface* video_track) {
+  }
+  virtual void OnRemoveLocalStream(webrtc::MediaStreamInterface* stream) {
+  }
+
+ private:
+  talk_base::scoped_refptr<webrtc::MediaStreamInterface> CreateStream(
+      const std::string& stream_label,
+      const std::string& audio_track_id,
+      const std::string& video_track_id) {
+    talk_base::scoped_refptr<webrtc::MediaStreamInterface> stream(
+        webrtc::MediaStream::Create(stream_label));
+
+    if (!audio_track_id.empty()) {
+      talk_base::scoped_refptr<webrtc::AudioTrackInterface> audio_track(
+          webrtc::AudioTrack::Create(audio_track_id, NULL));
+      stream->AddTrack(audio_track);
+    }
+
+    if (!video_track_id.empty()) {
+      talk_base::scoped_refptr<webrtc::VideoTrackInterface> video_track(
+          webrtc::VideoTrack::Create(video_track_id, NULL));
+      stream->AddTrack(video_track);
+    }
+    return stream;
+  }
+
+  cricket::MediaSessionOptions options_;
+};
+
+class WebRtcSessionTest : public testing::Test {
+ protected:
+  // TODO Investigate why ChannelManager crashes, if it's created
+  // after stun_server.
+  WebRtcSessionTest()
+    : media_engine_(new cricket::FakeMediaEngine()),
+      data_engine_(new cricket::FakeDataEngine()),
+      device_manager_(new cricket::FakeDeviceManager()),
+      channel_manager_(new cricket::ChannelManager(
+         media_engine_, data_engine_, device_manager_,
+         new cricket::CaptureManager(), talk_base::Thread::Current())),
+      tdesc_factory_(new cricket::TransportDescriptionFactory()),
+      desc_factory_(new cricket::MediaSessionDescriptionFactory(
+          channel_manager_.get(), tdesc_factory_.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()) {
+    tdesc_factory_->set_protocol(cricket::ICEPROTO_HYBRID);
+    allocator_.set_flags(cricket::PORTALLOCATOR_DISABLE_TCP |
+                         cricket::PORTALLOCATOR_DISABLE_RELAY |
+                         cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+    EXPECT_TRUE(channel_manager_->Init());
+    desc_factory_->set_add_legacy_streams(false);
+  }
+
+  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_,
+        &observer_,
+        &mediastream_signaling_));
+
+    EXPECT_EQ(PeerConnectionInterface::kIceConnectionNew,
+        observer_.ice_connection_state_);
+    EXPECT_EQ(PeerConnectionInterface::kIceGatheringNew,
+        observer_.ice_gathering_state_);
+
+    EXPECT_TRUE(session_->Initialize(constraints_.get()));
+  }
+
+  void InitWithDtmfCodec() {
+    // Add kTelephoneEventCodec for dtmf test.
+    std::vector<cricket::AudioCodec> codecs;
+    codecs.push_back(kTelephoneEventCodec);
+    media_engine_->SetAudioCodecs(codecs);
+    desc_factory_->set_audio_codecs(codecs);
+    Init();
+  }
+
+  void InitWithDtls() {
+    constraints_.reset(new FakeConstraints());
+    constraints_->AddOptional(
+        webrtc::MediaConstraintsInterface::kEnableDtlsSrtp, true);
+
+    Init();
+  }
+
+  // Creates a local offer and applies it. Starts ice.
+  // Call mediastream_signaling_.UseOptionsWithStreamX() before this function
+  // to decide which streams to create.
+  void InitiateCall() {
+    SessionDescriptionInterface* offer = session_->CreateOffer(NULL);
+    SetLocalDescriptionWithoutError(offer);
+    EXPECT_TRUE_WAIT(PeerConnectionInterface::kIceGatheringNew !=
+        observer_.ice_gathering_state_,
+        kIceCandidatesTimeout);
+  }
+
+  bool ChannelsExist() {
+    return (session_->voice_channel() != NULL &&
+            session_->video_channel() != NULL);
+  }
+
+  void CheckTransportChannels() {
+    EXPECT_TRUE(session_->GetChannel(cricket::CN_AUDIO, 1) != NULL);
+    EXPECT_TRUE(session_->GetChannel(cricket::CN_AUDIO, 2) != NULL);
+    EXPECT_TRUE(session_->GetChannel(cricket::CN_VIDEO, 1) != NULL);
+    EXPECT_TRUE(session_->GetChannel(cricket::CN_VIDEO, 2) != NULL);
+  }
+
+  void VerifyCryptoParams(const cricket::SessionDescription* sdp) {
+    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);
+    ASSERT_EQ(1U, audio_content->cryptos().size());
+    ASSERT_EQ(47U, audio_content->cryptos()[0].key_params.size());
+    ASSERT_EQ("AES_CM_128_HMAC_SHA1_80",
+              audio_content->cryptos()[0].cipher_suite);
+    EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf),
+              audio_content->protocol());
+
+    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());
+    EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf),
+              video_content->protocol());
+  }
+
+  void VerifyNoCryptoParams(const cricket::SessionDescription* sdp, bool dtls) {
+    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());
+
+    if (dtls) {
+      EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf),
+                audio_content->protocol());
+      EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf),
+                video_content->protocol());
+    } else {
+      EXPECT_EQ(std::string(cricket::kMediaProtocolAvpf),
+                audio_content->protocol());
+      EXPECT_EQ(std::string(cricket::kMediaProtocolAvpf),
+                video_content->protocol());
+    }
+  }
+
+  // Set the internal fake description factories to do DTLS-SRTP.
+  void SetFactoryDtlsSrtp() {
+    desc_factory_->set_secure(cricket::SEC_ENABLED);
+    std::string identity_name = "WebRTC" +
+        talk_base::ToString(talk_base::CreateRandomId());
+    tdesc_factory_->set_identity(talk_base::SSLIdentity::Generate(
+        identity_name));
+    tdesc_factory_->set_digest_algorithm(talk_base::DIGEST_SHA_256);
+    tdesc_factory_->set_secure(cricket::SEC_REQUIRED);
+  }
+
+  void VerifyFingerprintStatus(const cricket::SessionDescription* sdp,
+                               bool expected) {
+    const TransportInfo* audio = sdp->GetTransportInfoByName("audio");
+    ASSERT_TRUE(audio != NULL);
+    ASSERT_EQ(expected, audio->description.identity_fingerprint.get() != NULL);
+    if (expected) {
+      ASSERT_EQ(std::string(talk_base::DIGEST_SHA_256), audio->description.
+                identity_fingerprint->algorithm);
+    }
+    const TransportInfo* video = sdp->GetTransportInfoByName("video");
+    ASSERT_TRUE(video != NULL);
+    ASSERT_EQ(expected, video->description.identity_fingerprint.get() != NULL);
+    if (expected) {
+      ASSERT_EQ(std::string(talk_base::DIGEST_SHA_256), video->description.
+                identity_fingerprint->algorithm);
+    }
+  }
+
+  void VerifyAnswerFromNonCryptoOffer() {
+    // Create a SDP without Crypto.
+    cricket::MediaSessionOptions options;
+    options.has_video = true;
+    scoped_ptr<JsepSessionDescription> offer(
+        CreateRemoteOffer(options, cricket::SEC_DISABLED));
+    ASSERT_TRUE(offer.get() != NULL);
+    VerifyNoCryptoParams(offer->description(), false);
+    SetRemoteDescriptionExpectError("Called with a SDP without crypto enabled",
+                                    offer.release());
+    const webrtc::SessionDescriptionInterface* answer =
+        session_->CreateAnswer(NULL);
+    // Answer should be NULL as no crypto params in offer.
+    ASSERT_TRUE(answer == NULL);
+  }
+
+  void VerifyAnswerFromCryptoOffer() {
+    cricket::MediaSessionOptions options;
+    options.has_video = true;
+    options.bundle_enabled = true;
+    scoped_ptr<JsepSessionDescription> offer(
+        CreateRemoteOffer(options, cricket::SEC_REQUIRED));
+    ASSERT_TRUE(offer.get() != NULL);
+    VerifyCryptoParams(offer->description());
+    SetRemoteDescriptionWithoutError(offer.release());
+    scoped_ptr<SessionDescriptionInterface> answer(
+        session_->CreateAnswer(NULL));
+    ASSERT_TRUE(answer.get() != NULL);
+    VerifyCryptoParams(answer->description());
+  }
+
+  void CompareIceUfragAndPassword(const cricket::SessionDescription* desc1,
+                                  const cricket::SessionDescription* desc2,
+                                  bool expect_equal) {
+    if (desc1->contents().size() != desc2->contents().size()) {
+      EXPECT_FALSE(expect_equal);
+      return;
+    }
+
+    const cricket::ContentInfos& contents = desc1->contents();
+    cricket::ContentInfos::const_iterator it = contents.begin();
+
+    for (; it != contents.end(); ++it) {
+      const cricket::TransportDescription* transport_desc1 =
+          desc1->GetTransportDescriptionByName(it->name);
+      const cricket::TransportDescription* transport_desc2 =
+          desc2->GetTransportDescriptionByName(it->name);
+      if (!transport_desc1 || !transport_desc2) {
+        EXPECT_FALSE(expect_equal);
+        return;
+      }
+      if (transport_desc1->ice_pwd != transport_desc2->ice_pwd ||
+          transport_desc1->ice_ufrag != transport_desc2->ice_ufrag) {
+        EXPECT_FALSE(expect_equal);
+        return;
+      }
+    }
+    EXPECT_TRUE(expect_equal);
+  }
+  // Creates a remote offer and and applies it as a remote description,
+  // creates a local answer and applies is as a local description.
+  // Call mediastream_signaling_.UseOptionsWithStreamX() before this function
+  // to decide which local and remote streams to create.
+  void CreateAndSetRemoteOfferAndLocalAnswer() {
+    SessionDescriptionInterface* offer = CreateRemoteOffer();
+    SetRemoteDescriptionWithoutError(offer);
+    SessionDescriptionInterface* answer = session_->CreateAnswer(NULL);
+    SetLocalDescriptionWithoutError(answer);
+  }
+  void SetLocalDescriptionWithoutError(SessionDescriptionInterface* desc) {
+    EXPECT_TRUE(session_->SetLocalDescription(desc, NULL));
+  }
+  void SetLocalDescriptionExpectState(SessionDescriptionInterface* desc,
+                                      BaseSession::State expected_state) {
+    SetLocalDescriptionWithoutError(desc);
+    EXPECT_EQ(expected_state, session_->state());
+  }
+  void SetLocalDescriptionExpectError(const std::string& expected_error,
+                                      SessionDescriptionInterface* desc) {
+    std::string error;
+    EXPECT_FALSE(session_->SetLocalDescription(desc, &error));
+    EXPECT_NE(std::string::npos, error.find(kSetLocalSdpFailed));
+    EXPECT_NE(std::string::npos, error.find(expected_error));
+  }
+  void SetRemoteDescriptionWithoutError(SessionDescriptionInterface* desc) {
+    EXPECT_TRUE(session_->SetRemoteDescription(desc, NULL));
+  }
+  void SetRemoteDescriptionExpectState(SessionDescriptionInterface* desc,
+                                       BaseSession::State expected_state) {
+    SetRemoteDescriptionWithoutError(desc);
+    EXPECT_EQ(expected_state, session_->state());
+  }
+  void SetRemoteDescriptionExpectError(const std::string& expected_error,
+                                       SessionDescriptionInterface* desc) {
+    std::string error;
+    EXPECT_FALSE(session_->SetRemoteDescription(desc, &error));
+    EXPECT_NE(std::string::npos, error.find(kSetRemoteSdpFailed));
+    EXPECT_NE(std::string::npos, error.find(expected_error));
+  }
+
+  void CreateCryptoOfferAndNonCryptoAnswer(SessionDescriptionInterface** offer,
+      SessionDescriptionInterface** nocrypto_answer) {
+    // Create a SDP without Crypto.
+    cricket::MediaSessionOptions options;
+    options.has_video = true;
+    options.bundle_enabled = true;
+    *offer = CreateRemoteOffer(options, cricket::SEC_ENABLED);
+    ASSERT_TRUE(*offer != NULL);
+    VerifyCryptoParams((*offer)->description());
+
+    *nocrypto_answer = CreateRemoteAnswer(*offer, options,
+                                          cricket::SEC_DISABLED);
+    EXPECT_TRUE(*nocrypto_answer != NULL);
+  }
+
+  JsepSessionDescription* CreateRemoteOfferWithVersion(
+        cricket::MediaSessionOptions options,
+        cricket::SecurePolicy secure_policy,
+        const std::string& session_version,
+        const SessionDescriptionInterface* current_desc) {
+    std::string session_id = talk_base::ToString(talk_base::CreateRandomId64());
+    const cricket::SessionDescription* cricket_desc = NULL;
+    if (current_desc) {
+      cricket_desc = current_desc->description();
+      session_id = current_desc->session_id();
+    }
+
+    desc_factory_->set_secure(secure_policy);
+    JsepSessionDescription* offer(
+        new JsepSessionDescription(JsepSessionDescription::kOffer));
+    if (!offer->Initialize(desc_factory_->CreateOffer(options, cricket_desc),
+                           session_id, session_version)) {
+      delete offer;
+      offer = NULL;
+    }
+    return offer;
+  }
+  JsepSessionDescription* CreateRemoteOffer(
+      cricket::MediaSessionOptions options) {
+    return CreateRemoteOfferWithVersion(options, cricket::SEC_ENABLED,
+                                        kSessionVersion, NULL);
+  }
+  JsepSessionDescription* CreateRemoteOffer(
+      cricket::MediaSessionOptions options, cricket::SecurePolicy policy) {
+    return CreateRemoteOfferWithVersion(options, policy, kSessionVersion, NULL);
+  }
+  JsepSessionDescription* CreateRemoteOffer(
+      cricket::MediaSessionOptions options,
+      const SessionDescriptionInterface* current_desc) {
+    return CreateRemoteOfferWithVersion(options, cricket::SEC_ENABLED,
+                                        kSessionVersion, current_desc);
+  }
+
+  // Create a remote offer. Call mediastream_signaling_.UseOptionsWithStreamX()
+  // before this function to decide which streams to create.
+  JsepSessionDescription* CreateRemoteOffer() {
+    cricket::MediaSessionOptions options;
+    mediastream_signaling_.GetOptionsForAnswer(NULL, &options);
+    return CreateRemoteOffer(options, session_->remote_description());
+  }
+
+  JsepSessionDescription* CreateRemoteAnswer(
+      const SessionDescriptionInterface* offer,
+      cricket::MediaSessionOptions options,
+      cricket::SecurePolicy policy) {
+    desc_factory_->set_secure(policy);
+    const std::string session_id =
+        talk_base::ToString(talk_base::CreateRandomId64());
+    JsepSessionDescription* answer(
+        new JsepSessionDescription(JsepSessionDescription::kAnswer));
+    if (!answer->Initialize(desc_factory_->CreateAnswer(offer->description(),
+                                                        options, NULL),
+                            session_id, kSessionVersion)) {
+      delete answer;
+      answer = NULL;
+    }
+    return answer;
+  }
+
+  JsepSessionDescription* CreateRemoteAnswer(
+      const SessionDescriptionInterface* offer,
+      cricket::MediaSessionOptions options) {
+      return CreateRemoteAnswer(offer, options, cricket::SEC_REQUIRED);
+  }
+
+  // Creates an answer session description with streams based on
+  // |mediastream_signaling_|. Call
+  // mediastream_signaling_.UseOptionsWithStreamX() before this function
+  // to decide which streams to create.
+  JsepSessionDescription* CreateRemoteAnswer(
+      const SessionDescriptionInterface* offer) {
+    cricket::MediaSessionOptions options;
+    mediastream_signaling_.GetOptionsForAnswer(NULL, &options);
+    return CreateRemoteAnswer(offer, options, cricket::SEC_REQUIRED);
+  }
+
+  void TestSessionCandidatesWithBundleRtcpMux(bool bundle, bool rtcp_mux) {
+    AddInterface(kClientAddr1);
+    Init();
+    mediastream_signaling_.SendAudioVideoStream1();
+    FakeConstraints constraints;
+    constraints.SetMandatoryUseRtpMux(bundle);
+    SessionDescriptionInterface* offer = session_->CreateOffer(&constraints);
+    // SetLocalDescription and SetRemoteDescriptions takes ownership of offer
+    // and answer.
+    SetLocalDescriptionWithoutError(offer);
+
+    SessionDescriptionInterface* answer = CreateRemoteAnswer(
+        session_->local_description());
+    std::string sdp;
+    EXPECT_TRUE(answer->ToString(&sdp));
+
+    size_t expected_candidate_num = 2;
+    if (!rtcp_mux) {
+      // If rtcp_mux is enabled we should expect 4 candidates - host and srflex
+      // for rtp and rtcp.
+      expected_candidate_num = 4;
+      // Disable rtcp-mux from the answer
+
+      const std::string kRtcpMux = "a=rtcp-mux";
+      const std::string kXRtcpMux = "a=xrtcp-mux";
+      talk_base::replace_substrs(kRtcpMux.c_str(), kRtcpMux.length(),
+                                 kXRtcpMux.c_str(), kXRtcpMux.length(),
+                                 &sdp);
+    }
+
+    SessionDescriptionInterface* new_answer = CreateSessionDescription(
+        JsepSessionDescription::kAnswer, sdp, NULL);
+    delete answer;
+    answer = new_answer;
+
+    // SetRemoteDescription to enable rtcp mux.
+    SetRemoteDescriptionWithoutError(answer);
+    EXPECT_TRUE_WAIT(observer_.oncandidatesready_, kIceCandidatesTimeout);
+    EXPECT_EQ(expected_candidate_num, observer_.mline_0_candidates_.size());
+    EXPECT_EQ(expected_candidate_num, observer_.mline_1_candidates_.size());
+    for (size_t i = 0; i < observer_.mline_0_candidates_.size(); ++i) {
+      cricket::Candidate c0 = observer_.mline_0_candidates_[i];
+      cricket::Candidate c1 = observer_.mline_1_candidates_[i];
+      if (bundle) {
+        EXPECT_TRUE(c0.IsEquivalent(c1));
+      } else {
+        EXPECT_FALSE(c0.IsEquivalent(c1));
+      }
+    }
+  }
+  // Tests that we can only send DTMF when the dtmf codec is supported.
+  void TestCanInsertDtmf(bool can) {
+    if (can) {
+      InitWithDtmfCodec();
+    } else {
+      Init();
+    }
+    mediastream_signaling_.SendAudioVideoStream1();
+    CreateAndSetRemoteOfferAndLocalAnswer();
+    EXPECT_FALSE(session_->CanInsertDtmf(""));
+    EXPECT_EQ(can, session_->CanInsertDtmf(kAudioTrack1));
+  }
+
+  // The method sets up a call from the session to itself, in a loopback
+  // arrangement.  It also uses a firewall rule to create a temporary
+  // disconnection.  This code is placed as a method so that it can be invoked
+  // by multiple tests with different allocators (e.g. with and without BUNDLE).
+  // While running the call, this method also checks if the session goes through
+  // the correct sequence of ICE states when a connection is established,
+  // broken, and re-established.
+  // The Connection state should go:
+  // New -> Checking -> Connected -> Disconnected -> Connected.
+  // The Gathering state should go: New -> Gathering -> Completed.
+  void TestLoopbackCall() {
+    AddInterface(kClientAddr1);
+    Init();
+    mediastream_signaling_.SendAudioVideoStream1();
+    SessionDescriptionInterface* offer = session_->CreateOffer(NULL);
+
+    EXPECT_EQ(PeerConnectionInterface::kIceGatheringNew,
+              observer_.ice_gathering_state_);
+    SetLocalDescriptionWithoutError(offer);
+    EXPECT_EQ(PeerConnectionInterface::kIceConnectionNew,
+              observer_.ice_connection_state_);
+    EXPECT_EQ_WAIT(PeerConnectionInterface::kIceGatheringGathering,
+                   observer_.ice_gathering_state_,
+                   kIceCandidatesTimeout);
+    EXPECT_TRUE_WAIT(observer_.oncandidatesready_, kIceCandidatesTimeout);
+    EXPECT_EQ_WAIT(PeerConnectionInterface::kIceGatheringComplete,
+                   observer_.ice_gathering_state_,
+                   kIceCandidatesTimeout);
+
+    std::string sdp;
+    offer->ToString(&sdp);
+    SessionDescriptionInterface* desc =
+        webrtc::CreateSessionDescription(JsepSessionDescription::kAnswer, sdp);
+    ASSERT_TRUE(desc != NULL);
+    SetRemoteDescriptionWithoutError(desc);
+
+    EXPECT_EQ_WAIT(PeerConnectionInterface::kIceConnectionChecking,
+                   observer_.ice_connection_state_,
+                   kIceCandidatesTimeout);
+    EXPECT_EQ_WAIT(PeerConnectionInterface::kIceConnectionConnected,
+                   observer_.ice_connection_state_,
+                   kIceCandidatesTimeout);
+    // TODO(bemasc): EXPECT(Completed) once the details are standardized.
+
+    // Adding firewall rule to block ping requests, which should cause
+    // transport channel failure.
+    fss_->AddRule(false, talk_base::FP_ANY, talk_base::FD_ANY, kClientAddr1);
+    EXPECT_EQ_WAIT(PeerConnectionInterface::kIceConnectionDisconnected,
+                   observer_.ice_connection_state_,
+                   kIceCandidatesTimeout);
+
+    // Clearing the rules, session should move back to completed state.
+    fss_->ClearRules();
+    // Session is automatically calling OnSignalingReady after creation of
+    // new portallocator session which will allocate new set of candidates.
+
+    // TODO(bemasc): Change this to Completed once the details are standardized.
+    EXPECT_EQ_WAIT(PeerConnectionInterface::kIceConnectionConnected,
+                   observer_.ice_connection_state_,
+                   kIceCandidatesTimeout);
+  }
+
+  void VerifyTransportType(const std::string& content_name,
+                           cricket::TransportProtocol protocol) {
+    const cricket::Transport* transport = session_->GetTransport(content_name);
+    ASSERT_TRUE(transport != NULL);
+    EXPECT_EQ(protocol, transport->protocol());
+  }
+
+  // Adds CN codecs to FakeMediaEngine and MediaDescriptionFactory.
+  void AddCNCodecs() {
+    // Add kTelephoneEventCodec for dtmf test.
+    std::vector<cricket::AudioCodec> codecs = media_engine_->audio_codecs();;
+    codecs.push_back(kCNCodec1);
+    codecs.push_back(kCNCodec2);
+    media_engine_->SetAudioCodecs(codecs);
+    desc_factory_->set_audio_codecs(codecs);
+  }
+
+  bool VerifyNoCNCodecs(const cricket::ContentInfo* content) {
+    const cricket::ContentDescription* description = content->description;
+    ASSERT(description != NULL);
+    const cricket::AudioContentDescription* audio_content_desc =
+        static_cast<const cricket::AudioContentDescription*> (description);
+    ASSERT(audio_content_desc != NULL);
+    for (size_t i = 0; i < audio_content_desc->codecs().size(); ++i) {
+      if (audio_content_desc->codecs()[i].name == "CN")
+        return false;
+    }
+    return true;
+  }
+
+  void SetLocalDescriptionWithDataChannel() {
+    webrtc::DataChannelInit dci;
+    dci.reliable = false;
+    session_->CreateDataChannel("datachannel", &dci);
+    SessionDescriptionInterface* offer = session_->CreateOffer(NULL);
+    SetLocalDescriptionWithoutError(offer);
+  }
+
+  cricket::FakeMediaEngine* media_engine_;
+  cricket::FakeDataEngine* data_engine_;
+  cricket::FakeDeviceManager* device_manager_;
+  talk_base::scoped_ptr<cricket::ChannelManager> channel_manager_;
+  talk_base::scoped_ptr<cricket::TransportDescriptionFactory> tdesc_factory_;
+  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<FakeConstraints> constraints_;
+  FakeMediaStreamSignaling mediastream_signaling_;
+  talk_base::scoped_ptr<WebRtcSessionForTest> session_;
+  MockIceObserver observer_;
+  cricket::FakeVideoMediaChannel* video_channel_;
+  cricket::FakeVoiceMediaChannel* voice_channel_;
+};
+
+TEST_F(WebRtcSessionTest, TestInitialize) {
+  Init();
+}
+
+TEST_F(WebRtcSessionTest, TestInitializeWithDtls) {
+  InitWithDtls();
+}
+
+TEST_F(WebRtcSessionTest, TestSessionCandidates) {
+  TestSessionCandidatesWithBundleRtcpMux(false, false);
+}
+
+// Below test cases (TestSessionCandidatesWith*) verify the candidates gathered
+// with rtcp-mux and/or bundle.
+TEST_F(WebRtcSessionTest, TestSessionCandidatesWithRtcpMux) {
+  TestSessionCandidatesWithBundleRtcpMux(false, true);
+}
+
+TEST_F(WebRtcSessionTest, TestSessionCandidatesWithBundle) {
+  TestSessionCandidatesWithBundleRtcpMux(true, false);
+}
+
+TEST_F(WebRtcSessionTest, TestSessionCandidatesWithBundleRtcpMux) {
+  TestSessionCandidatesWithBundleRtcpMux(true, true);
+}
+
+TEST_F(WebRtcSessionTest, TestMultihomeCandidates) {
+  AddInterface(kClientAddr1);
+  AddInterface(kClientAddr2);
+  Init();
+  mediastream_signaling_.SendAudioVideoStream1();
+  InitiateCall();
+  EXPECT_TRUE_WAIT(observer_.oncandidatesready_, kIceCandidatesTimeout);
+  EXPECT_EQ(8u, observer_.mline_0_candidates_.size());
+  EXPECT_EQ(8u, observer_.mline_1_candidates_.size());
+}
+
+TEST_F(WebRtcSessionTest, TestStunError) {
+  AddInterface(kClientAddr1);
+  AddInterface(kClientAddr2);
+  fss_->AddRule(false, talk_base::FP_UDP, talk_base::FD_ANY, kClientAddr1);
+  Init();
+  mediastream_signaling_.SendAudioVideoStream1();
+  InitiateCall();
+  // Since kClientAddr1 is blocked, not expecting stun candidates for it.
+  EXPECT_TRUE_WAIT(observer_.oncandidatesready_, kIceCandidatesTimeout);
+  EXPECT_EQ(6u, observer_.mline_0_candidates_.size());
+  EXPECT_EQ(6u, observer_.mline_1_candidates_.size());
+}
+
+// Test creating offers and receive answers and make sure the
+// media engine creates the expected send and receive streams.
+TEST_F(WebRtcSessionTest, TestCreateOfferReceiveAnswer) {
+  Init();
+  mediastream_signaling_.SendAudioVideoStream1();
+  SessionDescriptionInterface* offer = session_->CreateOffer(NULL);
+  const std::string session_id_orig = offer->session_id();
+  const std::string session_version_orig = offer->session_version();
+  SetLocalDescriptionWithoutError(offer);
+
+  mediastream_signaling_.SendAudioVideoStream2();
+  SessionDescriptionInterface* answer =
+      CreateRemoteAnswer(session_->local_description());
+  SetRemoteDescriptionWithoutError(answer);
+
+  video_channel_ = media_engine_->GetVideoChannel(0);
+  voice_channel_ = media_engine_->GetVoiceChannel(0);
+
+  ASSERT_EQ(1u, video_channel_->recv_streams().size());
+  EXPECT_TRUE(kVideoTrack2 == video_channel_->recv_streams()[0].id);
+
+  ASSERT_EQ(1u, voice_channel_->recv_streams().size());
+  EXPECT_TRUE(kAudioTrack2 == voice_channel_->recv_streams()[0].id);
+
+  ASSERT_EQ(1u, video_channel_->send_streams().size());
+  EXPECT_TRUE(kVideoTrack1 == video_channel_->send_streams()[0].id);
+  ASSERT_EQ(1u, voice_channel_->send_streams().size());
+  EXPECT_TRUE(kAudioTrack1 == voice_channel_->send_streams()[0].id);
+
+  // Create new offer without send streams.
+  mediastream_signaling_.SendNothing();
+  offer = session_->CreateOffer(NULL);
+
+  // Verify the session id is the same and the session version is
+  // increased.
+  EXPECT_EQ(session_id_orig, offer->session_id());
+  EXPECT_LT(talk_base::FromString<uint64>(session_version_orig),
+            talk_base::FromString<uint64>(offer->session_version()));
+
+  SetLocalDescriptionWithoutError(offer);
+
+  mediastream_signaling_.SendAudioVideoStream2();
+  answer = CreateRemoteAnswer(session_->local_description());
+  SetRemoteDescriptionWithoutError(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_TRUE(kVideoTrack2 == video_channel_->recv_streams()[0].id);
+  ASSERT_EQ(1u, voice_channel_->recv_streams().size());
+  EXPECT_TRUE(kAudioTrack2 == voice_channel_->recv_streams()[0].id);
+}
+
+// Test receiving offers and creating answers and make sure the
+// media engine creates the expected send and receive streams.
+TEST_F(WebRtcSessionTest, TestReceiveOfferCreateAnswer) {
+  Init();
+  mediastream_signaling_.SendAudioVideoStream2();
+  SessionDescriptionInterface* offer = session_->CreateOffer(NULL);
+  SetRemoteDescriptionWithoutError(offer);
+
+  mediastream_signaling_.SendAudioVideoStream1();
+  SessionDescriptionInterface* answer = session_->CreateAnswer(NULL);
+  SetLocalDescriptionWithoutError(answer);
+
+  const std::string session_id_orig = answer->session_id();
+  const std::string session_version_orig = answer->session_version();
+
+  video_channel_ = media_engine_->GetVideoChannel(0);
+  voice_channel_ = media_engine_->GetVoiceChannel(0);
+
+  ASSERT_EQ(1u, video_channel_->recv_streams().size());
+  EXPECT_TRUE(kVideoTrack2 == video_channel_->recv_streams()[0].id);
+
+  ASSERT_EQ(1u, voice_channel_->recv_streams().size());
+  EXPECT_TRUE(kAudioTrack2 == voice_channel_->recv_streams()[0].id);
+
+  ASSERT_EQ(1u, video_channel_->send_streams().size());
+  EXPECT_TRUE(kVideoTrack1 == video_channel_->send_streams()[0].id);
+  ASSERT_EQ(1u, voice_channel_->send_streams().size());
+  EXPECT_TRUE(kAudioTrack1 == voice_channel_->send_streams()[0].id);
+
+  mediastream_signaling_.SendAudioVideoStream1And2();
+  offer = session_->CreateOffer(NULL);
+  SetRemoteDescriptionWithoutError(offer);
+
+  // Answer by turning off all send streams.
+  mediastream_signaling_.SendNothing();
+  answer = session_->CreateAnswer(NULL);
+
+  // Verify the session id is the same and the session version is
+  // increased.
+  EXPECT_EQ(session_id_orig, answer->session_id());
+  EXPECT_LT(talk_base::FromString<uint64>(session_version_orig),
+            talk_base::FromString<uint64>(answer->session_version()));
+  SetLocalDescriptionWithoutError(answer);
+
+  ASSERT_EQ(2u, video_channel_->recv_streams().size());
+  EXPECT_TRUE(kVideoTrack1 == video_channel_->recv_streams()[0].id);
+  EXPECT_TRUE(kVideoTrack2 == video_channel_->recv_streams()[1].id);
+  ASSERT_EQ(2u, voice_channel_->recv_streams().size());
+  EXPECT_TRUE(kAudioTrack1 == voice_channel_->recv_streams()[0].id);
+  EXPECT_TRUE(kAudioTrack2 == voice_channel_->recv_streams()[1].id);
+
+  // Make sure we have no send streams.
+  EXPECT_EQ(0u, video_channel_->send_streams().size());
+  EXPECT_EQ(0u, voice_channel_->send_streams().size());
+}
+
+// Test we will return fail when apply an offer that doesn't have
+// crypto enabled.
+TEST_F(WebRtcSessionTest, SetNonCryptoOffer) {
+  Init();
+  cricket::MediaSessionOptions options;
+  options.has_video = true;
+  JsepSessionDescription* offer = CreateRemoteOffer(
+      options, cricket::SEC_DISABLED);
+  ASSERT_TRUE(offer != NULL);
+  VerifyNoCryptoParams(offer->description(), false);
+  // SetRemoteDescription and SetLocalDescription will take the ownership of
+  // the offer.
+  SetRemoteDescriptionExpectError(kSdpWithoutCrypto, offer);
+  offer = CreateRemoteOffer(options, cricket::SEC_DISABLED);
+  ASSERT_TRUE(offer != NULL);
+  SetLocalDescriptionExpectError(kSdpWithoutCrypto, offer);
+}
+
+// Test we will return fail when apply an answer that doesn't have
+// crypto enabled.
+TEST_F(WebRtcSessionTest, SetLocalNonCryptoAnswer) {
+  Init();
+  SessionDescriptionInterface* offer = NULL;
+  SessionDescriptionInterface* answer = NULL;
+  CreateCryptoOfferAndNonCryptoAnswer(&offer, &answer);
+  // SetRemoteDescription and SetLocalDescription will take the ownership of
+  // the offer.
+  SetRemoteDescriptionWithoutError(offer);
+  SetLocalDescriptionExpectError(kSdpWithoutCrypto, answer);
+}
+
+// Test we will return fail when apply an answer that doesn't have
+// crypto enabled.
+TEST_F(WebRtcSessionTest, SetRemoteNonCryptoAnswer) {
+  Init();
+  SessionDescriptionInterface* offer = NULL;
+  SessionDescriptionInterface* answer = NULL;
+  CreateCryptoOfferAndNonCryptoAnswer(&offer, &answer);
+  // SetRemoteDescription and SetLocalDescription will take the ownership of
+  // the offer.
+  SetLocalDescriptionWithoutError(offer);
+  SetRemoteDescriptionExpectError(kSdpWithoutCrypto, answer);
+}
+
+// Test that we can create and set an offer with a DTLS fingerprint.
+TEST_F(WebRtcSessionTest, CreateSetDtlsOffer) {
+  MAYBE_SKIP_TEST(talk_base::SSLStreamAdapter::HaveDtlsSrtp);
+  InitWithDtls();
+  mediastream_signaling_.SendAudioVideoStream1();
+  SessionDescriptionInterface* offer = session_->CreateOffer(NULL);
+  ASSERT_TRUE(offer != NULL);
+  VerifyFingerprintStatus(offer->description(), true);
+  // SetLocalDescription will take the ownership of the offer.
+  SetLocalDescriptionWithoutError(offer);
+}
+
+// Test that we can process an offer with a DTLS fingerprint
+// and that we return an answer with a fingerprint.
+TEST_F(WebRtcSessionTest, ReceiveDtlsOfferCreateAnswer) {
+  MAYBE_SKIP_TEST(talk_base::SSLStreamAdapter::HaveDtlsSrtp);
+  InitWithDtls();
+  SetFactoryDtlsSrtp();
+  cricket::MediaSessionOptions options;
+  options.has_video = true;
+  JsepSessionDescription* offer = CreateRemoteOffer(options);
+  ASSERT_TRUE(offer != NULL);
+  VerifyFingerprintStatus(offer->description(), true);
+
+  // SetRemoteDescription will take the ownership of the offer.
+  SetRemoteDescriptionWithoutError(offer);
+
+  // Verify that we get a crypto fingerprint in the answer.
+  SessionDescriptionInterface* answer = session_->CreateAnswer(NULL);
+  ASSERT_TRUE(answer != NULL);
+  VerifyFingerprintStatus(answer->description(), true);
+  // Check that we don't have an a=crypto line in the answer.
+  VerifyNoCryptoParams(answer->description(), true);
+
+  // Now set the local description, which should work, even without a=crypto.
+  SetLocalDescriptionWithoutError(answer);
+}
+
+// Test that even if we support DTLS, if the other side didn't offer a
+// fingerprint, we don't either.
+TEST_F(WebRtcSessionTest, ReceiveNoDtlsOfferCreateAnswer) {
+  MAYBE_SKIP_TEST(talk_base::SSLStreamAdapter::HaveDtlsSrtp);
+  InitWithDtls();
+  cricket::MediaSessionOptions options;
+  options.has_video = true;
+  JsepSessionDescription* offer = CreateRemoteOffer(
+      options, cricket::SEC_REQUIRED);
+  ASSERT_TRUE(offer != NULL);
+  VerifyFingerprintStatus(offer->description(), false);
+
+  // SetRemoteDescription will take the ownership of
+  // the offer.
+  SetRemoteDescriptionWithoutError(offer);
+
+  // Verify that we don't get a crypto fingerprint in the answer.
+  SessionDescriptionInterface* answer = session_->CreateAnswer(NULL);
+  ASSERT_TRUE(answer != NULL);
+  VerifyFingerprintStatus(answer->description(), false);
+
+  // Now set the local description.
+  SetLocalDescriptionWithoutError(answer);
+}
+
+TEST_F(WebRtcSessionTest, TestSetLocalOfferTwice) {
+  Init();
+  mediastream_signaling_.SendNothing();
+  // SetLocalDescription take ownership of offer.
+  SessionDescriptionInterface* offer = session_->CreateOffer(NULL);
+  SetLocalDescriptionWithoutError(offer);
+
+  // SetLocalDescription take ownership of offer.
+  SessionDescriptionInterface* offer2 = session_->CreateOffer(NULL);
+  SetLocalDescriptionWithoutError(offer2);
+}
+
+TEST_F(WebRtcSessionTest, TestSetRemoteOfferTwice) {
+  Init();
+  mediastream_signaling_.SendNothing();
+  // SetLocalDescription take ownership of offer.
+  SessionDescriptionInterface* offer = session_->CreateOffer(NULL);
+  SetRemoteDescriptionWithoutError(offer);
+
+  SessionDescriptionInterface* offer2 = session_->CreateOffer(NULL);
+  SetRemoteDescriptionWithoutError(offer2);
+}
+
+TEST_F(WebRtcSessionTest, TestSetLocalAndRemoteOffer) {
+  Init();
+  mediastream_signaling_.SendNothing();
+  SessionDescriptionInterface* offer = session_->CreateOffer(NULL);
+  SetLocalDescriptionWithoutError(offer);
+  offer = session_->CreateOffer(NULL);
+  SetRemoteDescriptionExpectError(
+      "Called with type in wrong state, type: offer state: STATE_SENTINITIATE",
+      offer);
+}
+
+TEST_F(WebRtcSessionTest, TestSetRemoteAndLocalOffer) {
+  Init();
+  mediastream_signaling_.SendNothing();
+  SessionDescriptionInterface* offer = session_->CreateOffer(NULL);
+  SetRemoteDescriptionWithoutError(offer);
+  offer = session_->CreateOffer(NULL);
+  SetLocalDescriptionExpectError(
+      "Called with type in wrong state, type: "
+      "offer state: STATE_RECEIVEDINITIATE",
+      offer);
+}
+
+TEST_F(WebRtcSessionTest, TestSetLocalPrAnswer) {
+  Init();
+  mediastream_signaling_.SendNothing();
+  SessionDescriptionInterface* offer = CreateRemoteOffer();
+  SetRemoteDescriptionExpectState(offer, BaseSession::STATE_RECEIVEDINITIATE);
+
+  JsepSessionDescription* pranswer = static_cast<JsepSessionDescription*>(
+      session_->CreateAnswer(NULL));
+  pranswer->set_type(SessionDescriptionInterface::kPrAnswer);
+  SetLocalDescriptionExpectState(pranswer, BaseSession::STATE_SENTPRACCEPT);
+
+  mediastream_signaling_.SendAudioVideoStream1();
+  JsepSessionDescription* pranswer2 = static_cast<JsepSessionDescription*>(
+      session_->CreateAnswer(NULL));
+  pranswer2->set_type(SessionDescriptionInterface::kPrAnswer);
+
+  SetLocalDescriptionExpectState(pranswer2, BaseSession::STATE_SENTPRACCEPT);
+
+  mediastream_signaling_.SendAudioVideoStream2();
+  SessionDescriptionInterface* answer = session_->CreateAnswer(NULL);
+  SetLocalDescriptionExpectState(answer, BaseSession::STATE_SENTACCEPT);
+}
+
+TEST_F(WebRtcSessionTest, TestSetRemotePrAnswer) {
+  Init();
+  mediastream_signaling_.SendNothing();
+  SessionDescriptionInterface* offer = session_->CreateOffer(NULL);
+  SetLocalDescriptionExpectState(offer, BaseSession::STATE_SENTINITIATE);
+
+  JsepSessionDescription* pranswer =
+      CreateRemoteAnswer(session_->local_description());
+  pranswer->set_type(SessionDescriptionInterface::kPrAnswer);
+
+  SetRemoteDescriptionExpectState(pranswer,
+                                  BaseSession::STATE_RECEIVEDPRACCEPT);
+
+  mediastream_signaling_.SendAudioVideoStream1();
+  JsepSessionDescription* pranswer2 =
+      CreateRemoteAnswer(session_->local_description());
+  pranswer2->set_type(SessionDescriptionInterface::kPrAnswer);
+
+  SetRemoteDescriptionExpectState(pranswer2,
+                                  BaseSession::STATE_RECEIVEDPRACCEPT);
+
+  mediastream_signaling_.SendAudioVideoStream2();
+  SessionDescriptionInterface* answer =
+      CreateRemoteAnswer(session_->local_description());
+  SetRemoteDescriptionExpectState(answer, BaseSession::STATE_RECEIVEDACCEPT);
+}
+
+TEST_F(WebRtcSessionTest, TestSetLocalAnswerWithoutOffer) {
+  Init();
+  mediastream_signaling_.SendNothing();
+  talk_base::scoped_ptr<SessionDescriptionInterface> offer(
+      session_->CreateOffer(NULL));
+  SessionDescriptionInterface* answer =
+      CreateRemoteAnswer(offer.get());
+  SetLocalDescriptionExpectError(
+      "Called with type in wrong state, type: answer state: STATE_INIT",
+      answer);
+}
+
+TEST_F(WebRtcSessionTest, TestSetRemoteAnswerWithoutOffer) {
+  Init();
+  mediastream_signaling_.SendNothing();
+  talk_base::scoped_ptr<SessionDescriptionInterface> offer(
+        session_->CreateOffer(NULL));
+  SessionDescriptionInterface* answer =
+      CreateRemoteAnswer(offer.get());
+  SetRemoteDescriptionExpectError(
+      "Called with type in wrong state, type: answer state: STATE_INIT",
+      answer);
+}
+
+TEST_F(WebRtcSessionTest, TestAddRemoteCandidate) {
+  Init();
+  mediastream_signaling_.SendAudioVideoStream1();
+
+  cricket::Candidate candidate;
+  candidate.set_component(1);
+  JsepIceCandidate ice_candidate1(kMediaContentName0, 0, candidate);
+
+  // Fail since we have not set a offer description.
+  EXPECT_FALSE(session_->ProcessIceMessage(&ice_candidate1));
+
+  SessionDescriptionInterface* offer = session_->CreateOffer(NULL);
+  SetLocalDescriptionWithoutError(offer);
+  // Candidate should be allowed to add before remote description.
+  EXPECT_TRUE(session_->ProcessIceMessage(&ice_candidate1));
+  candidate.set_component(2);
+  JsepIceCandidate ice_candidate2(kMediaContentName0, 0, candidate);
+  EXPECT_TRUE(session_->ProcessIceMessage(&ice_candidate2));
+
+  SessionDescriptionInterface* answer = CreateRemoteAnswer(
+      session_->local_description());
+  SetRemoteDescriptionWithoutError(answer);
+
+  // Verifying the candidates are copied properly from internal vector.
+  const SessionDescriptionInterface* remote_desc =
+      session_->remote_description();
+  ASSERT_TRUE(remote_desc != NULL);
+  ASSERT_EQ(2u, remote_desc->number_of_mediasections());
+  const IceCandidateCollection* candidates =
+      remote_desc->candidates(kMediaContentIndex0);
+  ASSERT_EQ(2u, candidates->count());
+  EXPECT_EQ(kMediaContentIndex0, candidates->at(0)->sdp_mline_index());
+  EXPECT_EQ(kMediaContentName0, candidates->at(0)->sdp_mid());
+  EXPECT_EQ(1, candidates->at(0)->candidate().component());
+  EXPECT_EQ(2, candidates->at(1)->candidate().component());
+
+  candidate.set_component(2);
+  JsepIceCandidate ice_candidate3(kMediaContentName0, 0, candidate);
+  EXPECT_TRUE(session_->ProcessIceMessage(&ice_candidate3));
+  ASSERT_EQ(3u, candidates->count());
+
+  JsepIceCandidate bad_ice_candidate("bad content name", 99, candidate);
+  EXPECT_FALSE(session_->ProcessIceMessage(&bad_ice_candidate));
+}
+
+// Test that a remote candidate is added to the remote session description and
+// that it is retained if the remote session description is changed.
+TEST_F(WebRtcSessionTest, TestRemoteCandidatesAddedToSessionDescription) {
+  Init();
+  cricket::Candidate candidate1;
+  candidate1.set_component(1);
+  JsepIceCandidate ice_candidate1(kMediaContentName0, kMediaContentIndex0,
+                                  candidate1);
+  mediastream_signaling_.SendAudioVideoStream1();
+  CreateAndSetRemoteOfferAndLocalAnswer();
+
+  EXPECT_TRUE(session_->ProcessIceMessage(&ice_candidate1));
+  const SessionDescriptionInterface* remote_desc =
+      session_->remote_description();
+  ASSERT_TRUE(remote_desc != NULL);
+  ASSERT_EQ(2u, remote_desc->number_of_mediasections());
+  const IceCandidateCollection* candidates =
+      remote_desc->candidates(kMediaContentIndex0);
+  ASSERT_EQ(1u, candidates->count());
+  EXPECT_EQ(kMediaContentIndex0, candidates->at(0)->sdp_mline_index());
+
+  // Update the RemoteSessionDescription with a new session description and
+  // a candidate and check that the new remote session description contains both
+  // candidates.
+  SessionDescriptionInterface* offer = CreateRemoteOffer();
+  cricket::Candidate candidate2;
+  JsepIceCandidate ice_candidate2(kMediaContentName0, kMediaContentIndex0,
+                                  candidate2);
+  EXPECT_TRUE(offer->AddCandidate(&ice_candidate2));
+  SetRemoteDescriptionWithoutError(offer);
+
+  remote_desc = session_->remote_description();
+  ASSERT_TRUE(remote_desc != NULL);
+  ASSERT_EQ(2u, remote_desc->number_of_mediasections());
+  candidates = remote_desc->candidates(kMediaContentIndex0);
+  ASSERT_EQ(2u, candidates->count());
+  EXPECT_EQ(kMediaContentIndex0, candidates->at(0)->sdp_mline_index());
+  // Username and password have be updated with the TransportInfo of the
+  // SessionDescription, won't be equal to the original one.
+  candidate2.set_username(candidates->at(0)->candidate().username());
+  candidate2.set_password(candidates->at(0)->candidate().password());
+  EXPECT_TRUE(candidate2.IsEquivalent(candidates->at(0)->candidate()));
+  EXPECT_EQ(kMediaContentIndex0, candidates->at(1)->sdp_mline_index());
+  // No need to verify the username and password.
+  candidate1.set_username(candidates->at(1)->candidate().username());
+  candidate1.set_password(candidates->at(1)->candidate().password());
+  EXPECT_TRUE(candidate1.IsEquivalent(candidates->at(1)->candidate()));
+
+  // Test that the candidate is ignored if we can add the same candidate again.
+  EXPECT_TRUE(session_->ProcessIceMessage(&ice_candidate2));
+}
+
+// Test that local candidates are added to the local session description and
+// that they are retained if the local session description is changed.
+TEST_F(WebRtcSessionTest, TestLocalCandidatesAddedToSessionDescription) {
+  AddInterface(kClientAddr1);
+  Init();
+  mediastream_signaling_.SendAudioVideoStream1();
+  CreateAndSetRemoteOfferAndLocalAnswer();
+
+  const SessionDescriptionInterface* local_desc = session_->local_description();
+  const IceCandidateCollection* candidates =
+      local_desc->candidates(kMediaContentIndex0);
+  ASSERT_TRUE(candidates != NULL);
+  EXPECT_EQ(0u, candidates->count());
+
+  EXPECT_TRUE_WAIT(observer_.oncandidatesready_, kIceCandidatesTimeout);
+
+  local_desc = session_->local_description();
+  candidates = local_desc->candidates(kMediaContentIndex0);
+  ASSERT_TRUE(candidates != NULL);
+  EXPECT_LT(0u, candidates->count());
+  candidates = local_desc->candidates(1);
+  ASSERT_TRUE(candidates != NULL);
+  EXPECT_LT(0u, candidates->count());
+
+  // Update the session descriptions.
+  mediastream_signaling_.SendAudioVideoStream1();
+  CreateAndSetRemoteOfferAndLocalAnswer();
+
+  local_desc = session_->local_description();
+  candidates = local_desc->candidates(kMediaContentIndex0);
+  ASSERT_TRUE(candidates != NULL);
+  EXPECT_LT(0u, candidates->count());
+  candidates = local_desc->candidates(1);
+  ASSERT_TRUE(candidates != NULL);
+  EXPECT_LT(0u, candidates->count());
+}
+
+// Test that we can set a remote session description with remote candidates.
+TEST_F(WebRtcSessionTest, TestSetRemoteSessionDescriptionWithCandidates) {
+  Init();
+
+  cricket::Candidate candidate1;
+  candidate1.set_component(1);
+  JsepIceCandidate ice_candidate(kMediaContentName0, kMediaContentIndex0,
+                                 candidate1);
+  mediastream_signaling_.SendAudioVideoStream1();
+  SessionDescriptionInterface* offer = session_->CreateOffer(NULL);
+
+  EXPECT_TRUE(offer->AddCandidate(&ice_candidate));
+  SetRemoteDescriptionWithoutError(offer);
+
+  const SessionDescriptionInterface* remote_desc =
+      session_->remote_description();
+  ASSERT_TRUE(remote_desc != NULL);
+  ASSERT_EQ(2u, remote_desc->number_of_mediasections());
+  const IceCandidateCollection* candidates =
+      remote_desc->candidates(kMediaContentIndex0);
+  ASSERT_EQ(1u, candidates->count());
+  EXPECT_EQ(kMediaContentIndex0, candidates->at(0)->sdp_mline_index());
+
+  SessionDescriptionInterface* answer = session_->CreateAnswer(NULL);
+  SetLocalDescriptionWithoutError(answer);
+}
+
+// Test that offers and answers contains ice candidates when Ice candidates have
+// been gathered.
+TEST_F(WebRtcSessionTest, TestSetLocalAndRemoteDescriptionWithCandidates) {
+  AddInterface(kClientAddr1);
+  Init();
+  mediastream_signaling_.SendAudioVideoStream1();
+  // Ice is started but candidates are not provided until SetLocalDescription
+  // is called.
+  EXPECT_EQ(0u, observer_.mline_0_candidates_.size());
+  EXPECT_EQ(0u, observer_.mline_1_candidates_.size());
+  CreateAndSetRemoteOfferAndLocalAnswer();
+  // Wait until at least one local candidate has been collected.
+  EXPECT_TRUE_WAIT(0u < observer_.mline_0_candidates_.size(),
+                   kIceCandidatesTimeout);
+  EXPECT_TRUE_WAIT(0u < observer_.mline_1_candidates_.size(),
+                   kIceCandidatesTimeout);
+
+  talk_base::scoped_ptr<SessionDescriptionInterface> local_offer(
+      session_->CreateOffer(NULL));
+  ASSERT_TRUE(local_offer->candidates(kMediaContentIndex0) != NULL);
+  EXPECT_LT(0u, local_offer->candidates(kMediaContentIndex0)->count());
+  ASSERT_TRUE(local_offer->candidates(kMediaContentIndex1) != NULL);
+  EXPECT_LT(0u, local_offer->candidates(kMediaContentIndex1)->count());
+
+  SessionDescriptionInterface* remote_offer(CreateRemoteOffer());
+  SetRemoteDescriptionWithoutError(remote_offer);
+  SessionDescriptionInterface* answer = session_->CreateAnswer(NULL);
+  ASSERT_TRUE(answer->candidates(kMediaContentIndex0) != NULL);
+  EXPECT_LT(0u, answer->candidates(kMediaContentIndex0)->count());
+  ASSERT_TRUE(answer->candidates(kMediaContentIndex1) != NULL);
+  EXPECT_LT(0u, answer->candidates(kMediaContentIndex1)->count());
+  SetLocalDescriptionWithoutError(answer);
+}
+
+// Verifies TransportProxy and media channels are created with content names
+// present in the SessionDescription.
+TEST_F(WebRtcSessionTest, TestChannelCreationsWithContentNames) {
+  Init();
+  mediastream_signaling_.SendAudioVideoStream1();
+  talk_base::scoped_ptr<SessionDescriptionInterface> offer(
+      session_->CreateOffer(NULL));
+
+  // CreateOffer creates session description with the content names "audio" and
+  // "video". Goal is to modify these content names and verify transport channel
+  // proxy in the BaseSession, as proxies are created with the content names
+  // present in SDP.
+  std::string sdp;
+  EXPECT_TRUE(offer->ToString(&sdp));
+  const std::string kAudioMid = "a=mid:audio";
+  const std::string kAudioMidReplaceStr = "a=mid:audio_content_name";
+  const std::string kVideoMid = "a=mid:video";
+  const std::string kVideoMidReplaceStr = "a=mid:video_content_name";
+
+  // Replacing |audio| with |audio_content_name|.
+  talk_base::replace_substrs(kAudioMid.c_str(), kAudioMid.length(),
+                             kAudioMidReplaceStr.c_str(),
+                             kAudioMidReplaceStr.length(),
+                             &sdp);
+  // Replacing |video| with |video_content_name|.
+  talk_base::replace_substrs(kVideoMid.c_str(), kVideoMid.length(),
+                             kVideoMidReplaceStr.c_str(),
+                             kVideoMidReplaceStr.length(),
+                             &sdp);
+
+  SessionDescriptionInterface* modified_offer =
+      CreateSessionDescription(JsepSessionDescription::kOffer, sdp, NULL);
+
+  SetRemoteDescriptionWithoutError(modified_offer);
+
+  SessionDescriptionInterface* answer =
+      session_->CreateAnswer(NULL);
+  SetLocalDescriptionWithoutError(answer);
+
+  EXPECT_TRUE(session_->GetTransportProxy("audio_content_name") != NULL);
+  EXPECT_TRUE(session_->GetTransportProxy("video_content_name") != NULL);
+  EXPECT_TRUE((video_channel_ = media_engine_->GetVideoChannel(0)) != NULL);
+  EXPECT_TRUE((voice_channel_ = media_engine_->GetVoiceChannel(0)) != NULL);
+}
+
+// Test that an offer contains the correct media content descriptions based on
+// the send streams when no constraints have been set.
+TEST_F(WebRtcSessionTest, CreateOfferWithoutConstraintsOrStreams) {
+  Init();
+  talk_base::scoped_ptr<SessionDescriptionInterface> offer(
+      session_->CreateOffer(NULL));
+  ASSERT_TRUE(offer != NULL);
+  const cricket::ContentInfo* content =
+      cricket::GetFirstAudioContent(offer->description());
+  EXPECT_TRUE(content != NULL);
+  content = cricket::GetFirstVideoContent(offer->description());
+  EXPECT_TRUE(content == NULL);
+}
+
+// Test that an offer contains the correct media content descriptions based on
+// the send streams when no constraints have been set.
+TEST_F(WebRtcSessionTest, CreateOfferWithoutConstraints) {
+  Init();
+  // Test Audio only offer.
+  mediastream_signaling_.UseOptionsAudioOnly();
+  talk_base::scoped_ptr<SessionDescriptionInterface> offer(
+        session_->CreateOffer(NULL));
+  const cricket::ContentInfo* content =
+      cricket::GetFirstAudioContent(offer->description());
+  EXPECT_TRUE(content != NULL);
+  content = cricket::GetFirstVideoContent(offer->description());
+  EXPECT_TRUE(content == NULL);
+
+  // Test Audio / Video offer.
+  mediastream_signaling_.SendAudioVideoStream1();
+  offer.reset(session_->CreateOffer(NULL));
+  content = cricket::GetFirstAudioContent(offer->description());
+  EXPECT_TRUE(content != NULL);
+  content = cricket::GetFirstVideoContent(offer->description());
+  EXPECT_TRUE(content != NULL);
+}
+
+// Test that an offer contains no media content descriptions if
+// kOfferToReceiveVideo and kOfferToReceiveAudio constraints are set to false.
+TEST_F(WebRtcSessionTest, CreateOfferWithConstraintsWithoutStreams) {
+  Init();
+  webrtc::FakeConstraints constraints_no_receive;
+  constraints_no_receive.SetMandatoryReceiveAudio(false);
+  constraints_no_receive.SetMandatoryReceiveVideo(false);
+
+  talk_base::scoped_ptr<SessionDescriptionInterface> offer(
+      session_->CreateOffer(&constraints_no_receive));
+  ASSERT_TRUE(offer != NULL);
+  const cricket::ContentInfo* content =
+      cricket::GetFirstAudioContent(offer->description());
+  EXPECT_TRUE(content == NULL);
+  content = cricket::GetFirstVideoContent(offer->description());
+  EXPECT_TRUE(content == NULL);
+}
+
+// Test that an offer contains only audio media content descriptions if
+// kOfferToReceiveAudio constraints are set to true.
+TEST_F(WebRtcSessionTest, CreateAudioOnlyOfferWithConstraints) {
+  Init();
+  webrtc::FakeConstraints constraints_audio_only;
+  constraints_audio_only.SetMandatoryReceiveAudio(true);
+  talk_base::scoped_ptr<SessionDescriptionInterface> offer(
+        session_->CreateOffer(&constraints_audio_only));
+
+  const cricket::ContentInfo* content =
+      cricket::GetFirstAudioContent(offer->description());
+  EXPECT_TRUE(content != NULL);
+  content = cricket::GetFirstVideoContent(offer->description());
+  EXPECT_TRUE(content == NULL);
+}
+
+// Test that an offer contains audio and video media content descriptions if
+// kOfferToReceiveAudio and kOfferToReceiveVideo constraints are set to true.
+TEST_F(WebRtcSessionTest, CreateOfferWithConstraints) {
+  Init();
+  // Test Audio / Video offer.
+  webrtc::FakeConstraints constraints_audio_video;
+  constraints_audio_video.SetMandatoryReceiveAudio(true);
+  constraints_audio_video.SetMandatoryReceiveVideo(true);
+  talk_base::scoped_ptr<SessionDescriptionInterface> offer(
+      session_->CreateOffer(&constraints_audio_video));
+  const cricket::ContentInfo* content =
+      cricket::GetFirstAudioContent(offer->description());
+
+  EXPECT_TRUE(content != NULL);
+  content = cricket::GetFirstVideoContent(offer->description());
+  EXPECT_TRUE(content != NULL);
+
+  // TODO(perkj): Should the direction be set to SEND_ONLY if
+  // The constraints is set to not receive audio or video but a track is added?
+}
+
+// Test that an answer can not be created if the last remote description is not
+// an offer.
+TEST_F(WebRtcSessionTest, CreateAnswerWithoutAnOffer) {
+  Init();
+  SessionDescriptionInterface* offer = session_->CreateOffer(NULL);
+  SetLocalDescriptionWithoutError(offer);
+  SessionDescriptionInterface* answer = CreateRemoteAnswer(offer);
+  SetRemoteDescriptionWithoutError(answer);
+  EXPECT_TRUE(session_->CreateAnswer(NULL) == NULL);
+}
+
+// Test that an answer contains the correct media content descriptions when no
+// constraints have been set.
+TEST_F(WebRtcSessionTest, CreateAnswerWithoutConstraintsOrStreams) {
+  Init();
+  // Create a remote offer with audio and video content.
+  talk_base::scoped_ptr<JsepSessionDescription> offer(CreateRemoteOffer());
+  SetRemoteDescriptionWithoutError(offer.release());
+  talk_base::scoped_ptr<SessionDescriptionInterface> answer(
+      session_->CreateAnswer(NULL));
+  const cricket::ContentInfo* content =
+      cricket::GetFirstAudioContent(answer->description());
+  ASSERT_TRUE(content != NULL);
+  EXPECT_FALSE(content->rejected);
+
+  content = cricket::GetFirstVideoContent(answer->description());
+  ASSERT_TRUE(content != NULL);
+  EXPECT_FALSE(content->rejected);
+}
+
+// Test that an answer contains the correct media content descriptions when no
+// constraints have been set and the offer only contain audio.
+TEST_F(WebRtcSessionTest, CreateAudioAnswerWithoutConstraintsOrStreams) {
+  Init();
+  // Create a remote offer with audio only.
+  cricket::MediaSessionOptions options;
+  options.has_audio = true;
+  options.has_video = false;
+  talk_base::scoped_ptr<JsepSessionDescription> offer(
+      CreateRemoteOffer(options));
+  ASSERT_TRUE(cricket::GetFirstVideoContent(offer->description()) == NULL);
+  ASSERT_TRUE(cricket::GetFirstAudioContent(offer->description()) != NULL);
+
+  SetRemoteDescriptionWithoutError(offer.release());
+  talk_base::scoped_ptr<SessionDescriptionInterface> answer(
+      session_->CreateAnswer(NULL));
+  const cricket::ContentInfo* content =
+      cricket::GetFirstAudioContent(answer->description());
+  ASSERT_TRUE(content != NULL);
+  EXPECT_FALSE(content->rejected);
+
+  EXPECT_TRUE(cricket::GetFirstVideoContent(answer->description()) == NULL);
+}
+
+// Test that an answer contains the correct media content descriptions when no
+// constraints have been set.
+TEST_F(WebRtcSessionTest, CreateAnswerWithoutConstraints) {
+  Init();
+  // Create a remote offer with audio and video content.
+  talk_base::scoped_ptr<JsepSessionDescription> offer(CreateRemoteOffer());
+  SetRemoteDescriptionWithoutError(offer.release());
+  // Test with a stream with tracks.
+  mediastream_signaling_.SendAudioVideoStream1();
+  talk_base::scoped_ptr<SessionDescriptionInterface> answer(
+      session_->CreateAnswer(NULL));
+  const cricket::ContentInfo* content =
+      cricket::GetFirstAudioContent(answer->description());
+  ASSERT_TRUE(content != NULL);
+  EXPECT_FALSE(content->rejected);
+
+  content = cricket::GetFirstVideoContent(answer->description());
+  ASSERT_TRUE(content != NULL);
+  EXPECT_FALSE(content->rejected);
+}
+
+// Test that an answer contains the correct media content descriptions when
+// constraints have been set but no stream is sent.
+TEST_F(WebRtcSessionTest, CreateAnswerWithConstraintsWithoutStreams) {
+  Init();
+  // Create a remote offer with audio and video content.
+  talk_base::scoped_ptr<JsepSessionDescription> offer(CreateRemoteOffer());
+  SetRemoteDescriptionWithoutError(offer.release());
+
+  webrtc::FakeConstraints constraints_no_receive;
+  constraints_no_receive.SetMandatoryReceiveAudio(false);
+  constraints_no_receive.SetMandatoryReceiveVideo(false);
+
+  talk_base::scoped_ptr<SessionDescriptionInterface> answer(
+      session_->CreateAnswer(&constraints_no_receive));
+  const cricket::ContentInfo* content =
+      cricket::GetFirstAudioContent(answer->description());
+  ASSERT_TRUE(content != NULL);
+  EXPECT_TRUE(content->rejected);
+
+  content = cricket::GetFirstVideoContent(answer->description());
+  ASSERT_TRUE(content != NULL);
+  EXPECT_TRUE(content->rejected);
+}
+
+// Test that an answer contains the correct media content descriptions when
+// constraints have been set and streams are sent.
+TEST_F(WebRtcSessionTest, CreateAnswerWithConstraints) {
+  Init();
+  // Create a remote offer with audio and video content.
+  talk_base::scoped_ptr<JsepSessionDescription> offer(CreateRemoteOffer());
+  SetRemoteDescriptionWithoutError(offer.release());
+
+  webrtc::FakeConstraints constraints_no_receive;
+  constraints_no_receive.SetMandatoryReceiveAudio(false);
+  constraints_no_receive.SetMandatoryReceiveVideo(false);
+
+  // Test with a stream with tracks.
+  mediastream_signaling_.SendAudioVideoStream1();
+  talk_base::scoped_ptr<SessionDescriptionInterface> answer(
+      session_->CreateAnswer(&constraints_no_receive));
+
+  // TODO(perkj): Should the direction be set to SEND_ONLY?
+  const cricket::ContentInfo* content =
+      cricket::GetFirstAudioContent(answer->description());
+  ASSERT_TRUE(content != NULL);
+  EXPECT_FALSE(content->rejected);
+
+  // TODO(perkj): Should the direction be set to SEND_ONLY?
+  content = cricket::GetFirstVideoContent(answer->description());
+  ASSERT_TRUE(content != NULL);
+  EXPECT_FALSE(content->rejected);
+}
+
+TEST_F(WebRtcSessionTest, CreateOfferWithoutCNCodecs) {
+  AddCNCodecs();
+  Init();
+  webrtc::FakeConstraints constraints;
+  constraints.SetOptionalVAD(false);
+  talk_base::scoped_ptr<SessionDescriptionInterface> offer(
+      session_->CreateOffer(&constraints));
+  const cricket::ContentInfo* content =
+      cricket::GetFirstAudioContent(offer->description());
+  EXPECT_TRUE(content != NULL);
+  EXPECT_TRUE(VerifyNoCNCodecs(content));
+}
+
+TEST_F(WebRtcSessionTest, CreateAnswerWithoutCNCodecs) {
+  AddCNCodecs();
+  Init();
+  // Create a remote offer with audio and video content.
+  talk_base::scoped_ptr<JsepSessionDescription> offer(CreateRemoteOffer());
+  SetRemoteDescriptionWithoutError(offer.release());
+
+  webrtc::FakeConstraints constraints;
+  constraints.SetOptionalVAD(false);
+  talk_base::scoped_ptr<SessionDescriptionInterface> answer(
+      session_->CreateAnswer(&constraints));
+  const cricket::ContentInfo* content =
+      cricket::GetFirstAudioContent(answer->description());
+  ASSERT_TRUE(content != NULL);
+  EXPECT_TRUE(VerifyNoCNCodecs(content));
+}
+
+// This test verifies the call setup when remote answer with audio only and
+// later updates with video.
+TEST_F(WebRtcSessionTest, TestAVOfferWithAudioOnlyAnswer) {
+  Init();
+  EXPECT_TRUE(media_engine_->GetVideoChannel(0) == NULL);
+  EXPECT_TRUE(media_engine_->GetVoiceChannel(0) == NULL);
+
+  mediastream_signaling_.SendAudioVideoStream1();
+  SessionDescriptionInterface* offer = session_->CreateOffer(NULL);
+
+  cricket::MediaSessionOptions options;
+  options.has_video = false;
+  SessionDescriptionInterface* answer = CreateRemoteAnswer(offer, options);
+
+  // SetLocalDescription and SetRemoteDescriptions takes ownership of offer
+  // and answer;
+  SetLocalDescriptionWithoutError(offer);
+  SetRemoteDescriptionWithoutError(answer);
+
+  video_channel_ = media_engine_->GetVideoChannel(0);
+  voice_channel_ = media_engine_->GetVoiceChannel(0);
+
+  ASSERT_TRUE(video_channel_ == NULL);
+
+  ASSERT_EQ(0u, voice_channel_->recv_streams().size());
+  ASSERT_EQ(1u, voice_channel_->send_streams().size());
+  EXPECT_EQ(kAudioTrack1, voice_channel_->send_streams()[0].id);
+
+  // Let the remote end update the session descriptions, with Audio and Video.
+  mediastream_signaling_.SendAudioVideoStream2();
+  CreateAndSetRemoteOfferAndLocalAnswer();
+
+  video_channel_ = media_engine_->GetVideoChannel(0);
+  voice_channel_ = media_engine_->GetVoiceChannel(0);
+
+  ASSERT_TRUE(video_channel_ != NULL);
+  ASSERT_TRUE(voice_channel_ != NULL);
+
+  ASSERT_EQ(1u, video_channel_->recv_streams().size());
+  ASSERT_EQ(1u, video_channel_->send_streams().size());
+  EXPECT_EQ(kVideoTrack2, video_channel_->recv_streams()[0].id);
+  EXPECT_EQ(kVideoTrack2, video_channel_->send_streams()[0].id);
+  ASSERT_EQ(1u, voice_channel_->recv_streams().size());
+  ASSERT_EQ(1u, voice_channel_->send_streams().size());
+  EXPECT_EQ(kAudioTrack2, voice_channel_->recv_streams()[0].id);
+  EXPECT_EQ(kAudioTrack2, voice_channel_->send_streams()[0].id);
+
+  // Change session back to audio only.
+  mediastream_signaling_.UseOptionsAudioOnly();
+  CreateAndSetRemoteOfferAndLocalAnswer();
+
+  EXPECT_EQ(0u, video_channel_->recv_streams().size());
+  ASSERT_EQ(1u, voice_channel_->recv_streams().size());
+  EXPECT_EQ(kAudioTrack2, voice_channel_->recv_streams()[0].id);
+  ASSERT_EQ(1u, voice_channel_->send_streams().size());
+  EXPECT_EQ(kAudioTrack2, voice_channel_->send_streams()[0].id);
+}
+
+// This test verifies the call setup when remote answer with video only and
+// later updates with audio.
+TEST_F(WebRtcSessionTest, TestAVOfferWithVideoOnlyAnswer) {
+  Init();
+  EXPECT_TRUE(media_engine_->GetVideoChannel(0) == NULL);
+  EXPECT_TRUE(media_engine_->GetVoiceChannel(0) == NULL);
+  mediastream_signaling_.SendAudioVideoStream1();
+  SessionDescriptionInterface* offer = session_->CreateOffer(NULL);
+
+  cricket::MediaSessionOptions options;
+  options.has_audio = false;
+  options.has_video = true;
+  SessionDescriptionInterface* answer = CreateRemoteAnswer(
+      offer, options, cricket::SEC_ENABLED);
+
+  // SetLocalDescription and SetRemoteDescriptions takes ownership of offer
+  // and answer.
+  SetLocalDescriptionWithoutError(offer);
+  SetRemoteDescriptionWithoutError(answer);
+
+  video_channel_ = media_engine_->GetVideoChannel(0);
+  voice_channel_ = media_engine_->GetVoiceChannel(0);
+
+  ASSERT_TRUE(voice_channel_ == NULL);
+  ASSERT_TRUE(video_channel_ != NULL);
+
+  EXPECT_EQ(0u, video_channel_->recv_streams().size());
+  ASSERT_EQ(1u, video_channel_->send_streams().size());
+  EXPECT_EQ(kVideoTrack1, video_channel_->send_streams()[0].id);
+
+  // Update the session descriptions, with Audio and Video.
+  mediastream_signaling_.SendAudioVideoStream2();
+  CreateAndSetRemoteOfferAndLocalAnswer();
+
+  voice_channel_ = media_engine_->GetVoiceChannel(0);
+  ASSERT_TRUE(voice_channel_ != NULL);
+
+  ASSERT_EQ(1u, voice_channel_->recv_streams().size());
+  ASSERT_EQ(1u, voice_channel_->send_streams().size());
+  EXPECT_EQ(kAudioTrack2, voice_channel_->recv_streams()[0].id);
+  EXPECT_EQ(kAudioTrack2, voice_channel_->send_streams()[0].id);
+
+  // Change session back to video only.
+  mediastream_signaling_.UseOptionsVideoOnly();
+  CreateAndSetRemoteOfferAndLocalAnswer();
+
+  video_channel_ = media_engine_->GetVideoChannel(0);
+  voice_channel_ = media_engine_->GetVoiceChannel(0);
+
+  ASSERT_EQ(1u, video_channel_->recv_streams().size());
+  EXPECT_EQ(kVideoTrack2, video_channel_->recv_streams()[0].id);
+  ASSERT_EQ(1u, video_channel_->send_streams().size());
+  EXPECT_EQ(kVideoTrack2, video_channel_->send_streams()[0].id);
+}
+
+TEST_F(WebRtcSessionTest, TestDefaultSetSecurePolicy) {
+  Init();
+  EXPECT_EQ(cricket::SEC_REQUIRED, session_->secure_policy());
+}
+
+TEST_F(WebRtcSessionTest, VerifyCryptoParamsInSDP) {
+  Init();
+  mediastream_signaling_.SendAudioVideoStream1();
+  scoped_ptr<SessionDescriptionInterface> offer(
+      session_->CreateOffer(NULL));
+  VerifyCryptoParams(offer->description());
+  SetRemoteDescriptionWithoutError(offer.release());
+  const webrtc::SessionDescriptionInterface* answer =
+      session_->CreateAnswer(NULL);
+  VerifyCryptoParams(answer->description());
+}
+
+TEST_F(WebRtcSessionTest, VerifyNoCryptoParamsInSDP) {
+  Init();
+  session_->set_secure_policy(cricket::SEC_DISABLED);
+  mediastream_signaling_.SendAudioVideoStream1();
+  scoped_ptr<SessionDescriptionInterface> offer(
+        session_->CreateOffer(NULL));
+  VerifyNoCryptoParams(offer->description(), false);
+}
+
+TEST_F(WebRtcSessionTest, VerifyAnswerFromNonCryptoOffer) {
+  Init();
+  VerifyAnswerFromNonCryptoOffer();
+}
+
+TEST_F(WebRtcSessionTest, VerifyAnswerFromCryptoOffer) {
+  Init();
+  VerifyAnswerFromCryptoOffer();
+}
+
+TEST_F(WebRtcSessionTest, VerifyBundleFlagInPA) {
+  // This test verifies BUNDLE flag in PortAllocator, if BUNDLE information in
+  // local description is removed by the application, BUNDLE flag should be
+  // disabled in PortAllocator. By default BUNDLE is enabled in the WebRtc.
+  Init();
+  EXPECT_TRUE((cricket::PORTALLOCATOR_ENABLE_BUNDLE & allocator_.flags()) ==
+      cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+  talk_base::scoped_ptr<SessionDescriptionInterface> offer(
+      session_->CreateOffer(NULL));
+  cricket::SessionDescription* offer_copy =
+      offer->description()->Copy();
+  offer_copy->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
+  JsepSessionDescription* modified_offer =
+      new JsepSessionDescription(JsepSessionDescription::kOffer);
+  modified_offer->Initialize(offer_copy, "1", "1");
+
+  SetLocalDescriptionWithoutError(modified_offer);
+  EXPECT_FALSE(allocator_.flags() & cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+}
+
+TEST_F(WebRtcSessionTest, TestDisabledBundleInAnswer) {
+  Init();
+  mediastream_signaling_.SendAudioVideoStream1();
+  EXPECT_TRUE((cricket::PORTALLOCATOR_ENABLE_BUNDLE & allocator_.flags()) ==
+      cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+  FakeConstraints constraints;
+  constraints.SetMandatoryUseRtpMux(true);
+  SessionDescriptionInterface* offer = session_->CreateOffer(&constraints);
+  SetLocalDescriptionWithoutError(offer);
+  mediastream_signaling_.SendAudioVideoStream2();
+  talk_base::scoped_ptr<SessionDescriptionInterface> answer(
+      CreateRemoteAnswer(session_->local_description()));
+  cricket::SessionDescription* answer_copy = answer->description()->Copy();
+  answer_copy->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
+  JsepSessionDescription* modified_answer =
+      new JsepSessionDescription(JsepSessionDescription::kAnswer);
+  modified_answer->Initialize(answer_copy, "1", "1");
+  SetRemoteDescriptionWithoutError(modified_answer);
+  EXPECT_TRUE((cricket::PORTALLOCATOR_ENABLE_BUNDLE & allocator_.flags()) ==
+      cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+
+  video_channel_ = media_engine_->GetVideoChannel(0);
+  voice_channel_ = media_engine_->GetVoiceChannel(0);
+
+  ASSERT_EQ(1u, video_channel_->recv_streams().size());
+  EXPECT_TRUE(kVideoTrack2 == video_channel_->recv_streams()[0].id);
+
+  ASSERT_EQ(1u, voice_channel_->recv_streams().size());
+  EXPECT_TRUE(kAudioTrack2 == voice_channel_->recv_streams()[0].id);
+
+  ASSERT_EQ(1u, video_channel_->send_streams().size());
+  EXPECT_TRUE(kVideoTrack1 == video_channel_->send_streams()[0].id);
+  ASSERT_EQ(1u, voice_channel_->send_streams().size());
+  EXPECT_TRUE(kAudioTrack1 == voice_channel_->send_streams()[0].id);
+}
+
+TEST_F(WebRtcSessionTest, SetAudioPlayout) {
+  Init();
+  mediastream_signaling_.SendAudioVideoStream1();
+  CreateAndSetRemoteOfferAndLocalAnswer();
+  cricket::FakeVoiceMediaChannel* channel = media_engine_->GetVoiceChannel(0);
+  ASSERT_TRUE(channel != NULL);
+  ASSERT_EQ(1u, channel->recv_streams().size());
+  uint32 receive_ssrc  = channel->recv_streams()[0].first_ssrc();
+  double left_vol, right_vol;
+  EXPECT_TRUE(channel->GetOutputScaling(receive_ssrc, &left_vol, &right_vol));
+  EXPECT_EQ(1, left_vol);
+  EXPECT_EQ(1, right_vol);
+  session_->SetAudioPlayout(receive_ssrc, false);
+  EXPECT_TRUE(channel->GetOutputScaling(receive_ssrc, &left_vol, &right_vol));
+  EXPECT_EQ(0, left_vol);
+  EXPECT_EQ(0, right_vol);
+  session_->SetAudioPlayout(receive_ssrc, true);
+  EXPECT_TRUE(channel->GetOutputScaling(receive_ssrc, &left_vol, &right_vol));
+  EXPECT_EQ(1, left_vol);
+  EXPECT_EQ(1, right_vol);
+}
+
+TEST_F(WebRtcSessionTest, SetAudioSend) {
+  Init();
+  mediastream_signaling_.SendAudioVideoStream1();
+  CreateAndSetRemoteOfferAndLocalAnswer();
+  cricket::FakeVoiceMediaChannel* channel = media_engine_->GetVoiceChannel(0);
+  ASSERT_TRUE(channel != NULL);
+  ASSERT_EQ(1u, channel->send_streams().size());
+  uint32 send_ssrc  = channel->send_streams()[0].first_ssrc();
+  EXPECT_FALSE(channel->IsStreamMuted(send_ssrc));
+
+  cricket::AudioOptions options;
+  options.echo_cancellation.Set(true);
+
+  session_->SetAudioSend(send_ssrc, false, options);
+  EXPECT_TRUE(channel->IsStreamMuted(send_ssrc));
+  EXPECT_FALSE(channel->options().echo_cancellation.IsSet());
+
+  session_->SetAudioSend(send_ssrc, true, options);
+  EXPECT_FALSE(channel->IsStreamMuted(send_ssrc));
+  bool value;
+  EXPECT_TRUE(channel->options().echo_cancellation.Get(&value));
+  EXPECT_TRUE(value);
+}
+
+TEST_F(WebRtcSessionTest, SetVideoPlayout) {
+  Init();
+  mediastream_signaling_.SendAudioVideoStream1();
+  CreateAndSetRemoteOfferAndLocalAnswer();
+  cricket::FakeVideoMediaChannel* channel = media_engine_->GetVideoChannel(0);
+  ASSERT_TRUE(channel != NULL);
+  ASSERT_LT(0u, channel->renderers().size());
+  EXPECT_TRUE(channel->renderers().begin()->second == NULL);
+  ASSERT_EQ(1u, channel->recv_streams().size());
+  uint32 receive_ssrc  = channel->recv_streams()[0].first_ssrc();
+  cricket::FakeVideoRenderer renderer;
+  session_->SetVideoPlayout(receive_ssrc, true, &renderer);
+  EXPECT_TRUE(channel->renderers().begin()->second == &renderer);
+  session_->SetVideoPlayout(receive_ssrc, false, &renderer);
+  EXPECT_TRUE(channel->renderers().begin()->second == NULL);
+}
+
+TEST_F(WebRtcSessionTest, SetVideoSend) {
+  Init();
+  mediastream_signaling_.SendAudioVideoStream1();
+  CreateAndSetRemoteOfferAndLocalAnswer();
+  cricket::FakeVideoMediaChannel* channel = media_engine_->GetVideoChannel(0);
+  ASSERT_TRUE(channel != NULL);
+  ASSERT_EQ(1u, channel->send_streams().size());
+  uint32 send_ssrc  = channel->send_streams()[0].first_ssrc();
+  EXPECT_FALSE(channel->IsStreamMuted(send_ssrc));
+  cricket::VideoOptions* options = NULL;
+  session_->SetVideoSend(send_ssrc, false, options);
+  EXPECT_TRUE(channel->IsStreamMuted(send_ssrc));
+  session_->SetVideoSend(send_ssrc, true, options);
+  EXPECT_FALSE(channel->IsStreamMuted(send_ssrc));
+}
+
+TEST_F(WebRtcSessionTest, CanNotInsertDtmf) {
+  TestCanInsertDtmf(false);
+}
+
+TEST_F(WebRtcSessionTest, CanInsertDtmf) {
+  TestCanInsertDtmf(true);
+}
+
+TEST_F(WebRtcSessionTest, InsertDtmf) {
+  // Setup
+  Init();
+  mediastream_signaling_.SendAudioVideoStream1();
+  CreateAndSetRemoteOfferAndLocalAnswer();
+  FakeVoiceMediaChannel* channel = media_engine_->GetVoiceChannel(0);
+  EXPECT_EQ(0U, channel->dtmf_info_queue().size());
+
+  // Insert DTMF
+  const int expected_flags = DF_SEND;
+  const int expected_duration = 90;
+  session_->InsertDtmf(kAudioTrack1, 0, expected_duration);
+  session_->InsertDtmf(kAudioTrack1, 1, expected_duration);
+  session_->InsertDtmf(kAudioTrack1, 2, expected_duration);
+
+  // Verify
+  ASSERT_EQ(3U, channel->dtmf_info_queue().size());
+  const uint32 send_ssrc  = channel->send_streams()[0].first_ssrc();
+  EXPECT_TRUE(CompareDtmfInfo(channel->dtmf_info_queue()[0], send_ssrc, 0,
+                              expected_duration, expected_flags));
+  EXPECT_TRUE(CompareDtmfInfo(channel->dtmf_info_queue()[1], send_ssrc, 1,
+                              expected_duration, expected_flags));
+  EXPECT_TRUE(CompareDtmfInfo(channel->dtmf_info_queue()[2], send_ssrc, 2,
+                              expected_duration, expected_flags));
+}
+
+// This test verifies the |initiator| flag when session initiates the call.
+TEST_F(WebRtcSessionTest, TestInitiatorFlagAsOriginator) {
+  Init();
+  EXPECT_FALSE(session_->initiator());
+  SessionDescriptionInterface* offer = session_->CreateOffer(NULL);
+  SessionDescriptionInterface* answer = CreateRemoteAnswer(offer);
+  SetLocalDescriptionWithoutError(offer);
+  EXPECT_TRUE(session_->initiator());
+  SetRemoteDescriptionWithoutError(answer);
+  EXPECT_TRUE(session_->initiator());
+}
+
+// This test verifies the |initiator| flag when session receives the call.
+TEST_F(WebRtcSessionTest, TestInitiatorFlagAsReceiver) {
+  Init();
+  EXPECT_FALSE(session_->initiator());
+  SessionDescriptionInterface* offer = CreateRemoteOffer();
+  SetRemoteDescriptionWithoutError(offer);
+  SessionDescriptionInterface* answer = session_->CreateAnswer(NULL);
+
+  EXPECT_FALSE(session_->initiator());
+  SetLocalDescriptionWithoutError(answer);
+  EXPECT_FALSE(session_->initiator());
+}
+
+// This test verifies the ice protocol type at initiator of the call
+// if |a=ice-options:google-ice| is present in answer.
+TEST_F(WebRtcSessionTest, TestInitiatorGIceInAnswer) {
+  Init();
+  mediastream_signaling_.SendAudioVideoStream1();
+  SessionDescriptionInterface* offer = session_->CreateOffer(NULL);
+  SessionDescriptionInterface* answer = CreateRemoteAnswer(offer);
+  SetLocalDescriptionWithoutError(offer);
+  std::string sdp;
+  EXPECT_TRUE(answer->ToString(&sdp));
+  // Adding ice-options to the session level.
+  InjectAfter("t=0 0\r\n",
+              "a=ice-options:google-ice\r\n",
+              &sdp);
+  SessionDescriptionInterface* answer_with_gice =
+      CreateSessionDescription(JsepSessionDescription::kAnswer, sdp, NULL);
+  SetRemoteDescriptionWithoutError(answer_with_gice);
+  VerifyTransportType("audio", cricket::ICEPROTO_GOOGLE);
+  VerifyTransportType("video", cricket::ICEPROTO_GOOGLE);
+}
+
+// This test verifies the ice protocol type at initiator of the call
+// if ICE RFC5245 is supported in answer.
+TEST_F(WebRtcSessionTest, TestInitiatorIceInAnswer) {
+  Init();
+  mediastream_signaling_.SendAudioVideoStream1();
+  SessionDescriptionInterface* offer = session_->CreateOffer(NULL);
+  SessionDescriptionInterface* answer = CreateRemoteAnswer(offer);
+  SetLocalDescriptionWithoutError(offer);
+
+  SetRemoteDescriptionWithoutError(answer);
+  VerifyTransportType("audio", cricket::ICEPROTO_RFC5245);
+  VerifyTransportType("video", cricket::ICEPROTO_RFC5245);
+}
+
+// This test verifies the ice protocol type at receiver side of the call if
+// receiver decides to use google-ice.
+TEST_F(WebRtcSessionTest, TestReceiverGIceInOffer) {
+  Init();
+  mediastream_signaling_.SendAudioVideoStream1();
+  SessionDescriptionInterface* offer = session_->CreateOffer(NULL);
+  SetRemoteDescriptionWithoutError(offer);
+  SessionDescriptionInterface* answer = session_->CreateAnswer(NULL);
+  std::string sdp;
+  EXPECT_TRUE(answer->ToString(&sdp));
+  // Adding ice-options to the session level.
+  InjectAfter("t=0 0\r\n",
+              "a=ice-options:google-ice\r\n",
+              &sdp);
+  SessionDescriptionInterface* answer_with_gice =
+      CreateSessionDescription(JsepSessionDescription::kAnswer, sdp, NULL);
+  SetLocalDescriptionWithoutError(answer_with_gice);
+  VerifyTransportType("audio", cricket::ICEPROTO_GOOGLE);
+  VerifyTransportType("video", cricket::ICEPROTO_GOOGLE);
+}
+
+// This test verifies the ice protocol type at receiver side of the call if
+// receiver decides to use ice RFC 5245.
+TEST_F(WebRtcSessionTest, TestReceiverIceInOffer) {
+  Init();
+  mediastream_signaling_.SendAudioVideoStream1();
+  SessionDescriptionInterface* offer = session_->CreateOffer(NULL);
+  SetRemoteDescriptionWithoutError(offer);
+  SessionDescriptionInterface* answer = session_->CreateAnswer(NULL);
+  SetLocalDescriptionWithoutError(answer);
+  VerifyTransportType("audio", cricket::ICEPROTO_RFC5245);
+  VerifyTransportType("video", cricket::ICEPROTO_RFC5245);
+}
+
+// This test verifies the session state when ICE RFC5245 in offer and
+// ICE google-ice in answer.
+TEST_F(WebRtcSessionTest, TestIceOfferGIceOnlyAnswer) {
+  Init();
+  mediastream_signaling_.SendAudioVideoStream1();
+  talk_base::scoped_ptr<SessionDescriptionInterface> offer(
+      session_->CreateOffer(NULL));
+  std::string offer_str;
+  offer->ToString(&offer_str);
+  // Disable google-ice
+  const std::string gice_option = "google-ice";
+  const std::string xgoogle_xice = "xgoogle-xice";
+  talk_base::replace_substrs(gice_option.c_str(), gice_option.length(),
+                             xgoogle_xice.c_str(), xgoogle_xice.length(),
+                             &offer_str);
+  JsepSessionDescription *ice_only_offer =
+      new JsepSessionDescription(JsepSessionDescription::kOffer);
+  EXPECT_TRUE((ice_only_offer)->Initialize(offer_str, NULL));
+  SetLocalDescriptionWithoutError(ice_only_offer);
+  std::string original_offer_sdp;
+  EXPECT_TRUE(offer->ToString(&original_offer_sdp));
+  SessionDescriptionInterface* pranswer_with_gice =
+      CreateSessionDescription(JsepSessionDescription::kPrAnswer,
+                               original_offer_sdp, NULL);
+  SetRemoteDescriptionExpectError(kPushDownPranswerTDFailed,
+                                  pranswer_with_gice);
+  SessionDescriptionInterface* answer_with_gice =
+      CreateSessionDescription(JsepSessionDescription::kAnswer,
+                               original_offer_sdp, NULL);
+  SetRemoteDescriptionExpectError(kPushDownAnswerTDFailed, answer_with_gice);
+}
+
+// Verifing local offer and remote answer have matching m-lines as per RFC 3264.
+TEST_F(WebRtcSessionTest, TestIncorrectMLinesInRemoteAnswer) {
+  Init();
+  mediastream_signaling_.SendAudioVideoStream1();
+  SessionDescriptionInterface* offer = session_->CreateOffer(NULL);
+  SetLocalDescriptionWithoutError(offer);
+  talk_base::scoped_ptr<SessionDescriptionInterface> answer(
+      CreateRemoteAnswer(session_->local_description()));
+
+  cricket::SessionDescription* answer_copy = answer->description()->Copy();
+  answer_copy->RemoveContentByName("video");
+  JsepSessionDescription* modified_answer =
+      new JsepSessionDescription(JsepSessionDescription::kAnswer);
+
+  EXPECT_TRUE(modified_answer->Initialize(answer_copy,
+                                          answer->session_id(),
+                                          answer->session_version()));
+  SetRemoteDescriptionExpectError(kMlineMismatch, modified_answer);
+
+  // Modifying content names.
+  std::string sdp;
+  EXPECT_TRUE(answer->ToString(&sdp));
+  const std::string kAudioMid = "a=mid:audio";
+  const std::string kAudioMidReplaceStr = "a=mid:audio_content_name";
+
+  // Replacing |audio| with |audio_content_name|.
+  talk_base::replace_substrs(kAudioMid.c_str(), kAudioMid.length(),
+                             kAudioMidReplaceStr.c_str(),
+                             kAudioMidReplaceStr.length(),
+                             &sdp);
+
+  SessionDescriptionInterface* modified_answer1 =
+      CreateSessionDescription(JsepSessionDescription::kAnswer, sdp, NULL);
+  SetRemoteDescriptionExpectError(kMlineMismatch, modified_answer1);
+
+  SetRemoteDescriptionWithoutError(answer.release());
+}
+
+// Verifying remote offer and local answer have matching m-lines as per
+// RFC 3264.
+TEST_F(WebRtcSessionTest, TestIncorrectMLinesInLocalAnswer) {
+  Init();
+  mediastream_signaling_.SendAudioVideoStream1();
+  SessionDescriptionInterface* offer = CreateRemoteOffer();
+  SetRemoteDescriptionWithoutError(offer);
+  SessionDescriptionInterface* answer = session_->CreateAnswer(NULL);
+
+  cricket::SessionDescription* answer_copy = answer->description()->Copy();
+  answer_copy->RemoveContentByName("video");
+  JsepSessionDescription* modified_answer =
+      new JsepSessionDescription(JsepSessionDescription::kAnswer);
+
+  EXPECT_TRUE(modified_answer->Initialize(answer_copy,
+                                          answer->session_id(),
+                                          answer->session_version()));
+  SetLocalDescriptionExpectError(kMlineMismatch, modified_answer);
+  SetLocalDescriptionWithoutError(answer);
+}
+
+// This test verifies that WebRtcSession does not start candidate allocation
+// before SetLocalDescription is called.
+TEST_F(WebRtcSessionTest, TestIceStartAfterSetLocalDescriptionOnly) {
+  Init();
+  mediastream_signaling_.SendAudioVideoStream1();
+  SessionDescriptionInterface* offer = CreateRemoteOffer();
+  cricket::Candidate candidate;
+  candidate.set_component(1);
+  JsepIceCandidate ice_candidate(kMediaContentName0, kMediaContentIndex0,
+                                 candidate);
+  EXPECT_TRUE(offer->AddCandidate(&ice_candidate));
+  cricket::Candidate candidate1;
+  candidate1.set_component(1);
+  JsepIceCandidate ice_candidate1(kMediaContentName1, kMediaContentIndex1,
+                                  candidate1);
+  EXPECT_TRUE(offer->AddCandidate(&ice_candidate1));
+  SetRemoteDescriptionWithoutError(offer);
+  ASSERT_TRUE(session_->GetTransportProxy("audio") != NULL);
+  ASSERT_TRUE(session_->GetTransportProxy("video") != NULL);
+
+  // Pump for 1 second and verify that no candidates are generated.
+  talk_base::Thread::Current()->ProcessMessages(1000);
+  EXPECT_TRUE(observer_.mline_0_candidates_.empty());
+  EXPECT_TRUE(observer_.mline_1_candidates_.empty());
+
+  SessionDescriptionInterface* answer = session_->CreateAnswer(NULL);
+  SetLocalDescriptionWithoutError(answer);
+  EXPECT_TRUE(session_->GetTransportProxy("audio")->negotiated());
+  EXPECT_TRUE(session_->GetTransportProxy("video")->negotiated());
+  EXPECT_TRUE_WAIT(observer_.oncandidatesready_, kIceCandidatesTimeout);
+}
+
+// This test verifies that crypto parameter is updated in local session
+// description as per security policy set in MediaSessionDescriptionFactory.
+TEST_F(WebRtcSessionTest, TestCryptoAfterSetLocalDescription) {
+  Init();
+  mediastream_signaling_.SendAudioVideoStream1();
+  talk_base::scoped_ptr<SessionDescriptionInterface> offer(
+      session_->CreateOffer(NULL));
+
+  // Making sure SetLocalDescription correctly sets crypto value in
+  // SessionDescription object after de-serialization of sdp string. The value
+  // will be set as per MediaSessionDescriptionFactory.
+  std::string offer_str;
+  offer->ToString(&offer_str);
+  SessionDescriptionInterface* jsep_offer_str =
+      CreateSessionDescription(JsepSessionDescription::kOffer, offer_str, NULL);
+  SetLocalDescriptionWithoutError(jsep_offer_str);
+  EXPECT_TRUE(session_->voice_channel()->secure_required());
+  EXPECT_TRUE(session_->video_channel()->secure_required());
+}
+
+// This test verifies the crypto parameter when security is disabled.
+TEST_F(WebRtcSessionTest, TestCryptoAfterSetLocalDescriptionWithDisabled) {
+  Init();
+  mediastream_signaling_.SendAudioVideoStream1();
+  session_->set_secure_policy(cricket::SEC_DISABLED);
+  talk_base::scoped_ptr<SessionDescriptionInterface> offer(
+      session_->CreateOffer(NULL));
+
+  // Making sure SetLocalDescription correctly sets crypto value in
+  // SessionDescription object after de-serialization of sdp string. The value
+  // will be set as per MediaSessionDescriptionFactory.
+  std::string offer_str;
+  offer->ToString(&offer_str);
+  SessionDescriptionInterface *jsep_offer_str =
+      CreateSessionDescription(JsepSessionDescription::kOffer, offer_str, NULL);
+  SetLocalDescriptionWithoutError(jsep_offer_str);
+  EXPECT_FALSE(session_->voice_channel()->secure_required());
+  EXPECT_FALSE(session_->video_channel()->secure_required());
+}
+
+// This test verifies that an answer contains new ufrag and password if an offer
+// with new ufrag and password is received.
+TEST_F(WebRtcSessionTest, TestCreateAnswerWithNewUfragAndPassword) {
+  Init();
+  cricket::MediaSessionOptions options;
+  options.has_audio = true;
+  options.has_video = true;
+  talk_base::scoped_ptr<JsepSessionDescription> offer(
+      CreateRemoteOffer(options));
+  SetRemoteDescriptionWithoutError(offer.release());
+
+  mediastream_signaling_.SendAudioVideoStream1();
+  talk_base::scoped_ptr<SessionDescriptionInterface> answer(
+      session_->CreateAnswer(NULL));
+  SetLocalDescriptionWithoutError(answer.release());
+
+  // Receive an offer with new ufrag and password.
+  options.transport_options.ice_restart = true;
+  talk_base::scoped_ptr<JsepSessionDescription> updated_offer1(
+      CreateRemoteOffer(options,
+                                           session_->remote_description()));
+  SetRemoteDescriptionWithoutError(updated_offer1.release());
+
+  talk_base::scoped_ptr<SessionDescriptionInterface> updated_answer1(
+      session_->CreateAnswer(NULL));
+
+  CompareIceUfragAndPassword(updated_answer1->description(),
+                             session_->local_description()->description(),
+                             false);
+
+  SetLocalDescriptionWithoutError(updated_answer1.release());
+
+  // Receive yet an offer without changed ufrag or password.
+  options.transport_options.ice_restart = false;
+  talk_base::scoped_ptr<JsepSessionDescription> updated_offer2(
+      CreateRemoteOffer(options,
+                                           session_->remote_description()));
+  SetRemoteDescriptionWithoutError(updated_offer2.release());
+
+  talk_base::scoped_ptr<SessionDescriptionInterface> updated_answer2(
+      session_->CreateAnswer(NULL));
+
+  CompareIceUfragAndPassword(updated_answer2->description(),
+                             session_->local_description()->description(),
+                             true);
+
+  SetLocalDescriptionWithoutError(updated_answer2.release());
+}
+
+TEST_F(WebRtcSessionTest, TestSessionContentError) {
+  Init();
+  mediastream_signaling_.SendAudioVideoStream1();
+  SessionDescriptionInterface* offer = session_->CreateOffer(NULL);
+  const std::string session_id_orig = offer->session_id();
+  const std::string session_version_orig = offer->session_version();
+  SetLocalDescriptionWithoutError(offer);
+
+  video_channel_ = media_engine_->GetVideoChannel(0);
+  video_channel_->set_fail_set_send_codecs(true);
+
+  mediastream_signaling_.SendAudioVideoStream2();
+  SessionDescriptionInterface* answer =
+      CreateRemoteAnswer(session_->local_description());
+  SetRemoteDescriptionExpectError("ERROR_CONTENT", answer);
+}
+
+// Runs the loopback call test with BUNDLE and STUN disabled.
+TEST_F(WebRtcSessionTest, TestIceStatesBasic) {
+  // Lets try with only UDP ports.
+  allocator_.set_flags(cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG |
+                       cricket::PORTALLOCATOR_DISABLE_TCP |
+                       cricket::PORTALLOCATOR_DISABLE_STUN |
+                       cricket::PORTALLOCATOR_DISABLE_RELAY);
+  TestLoopbackCall();
+}
+
+// Regression-test for a crash which should have been an error.
+TEST_F(WebRtcSessionTest, TestNoStateTransitionPendingError) {
+  Init();
+  cricket::MediaSessionOptions options;
+  options.has_audio = true;
+  options.has_video = true;
+
+  session_->SetError(cricket::BaseSession::ERROR_CONTENT);
+  SessionDescriptionInterface* offer = CreateRemoteOffer(options);
+  SessionDescriptionInterface* answer =
+      CreateRemoteAnswer(offer, options);
+  SetRemoteDescriptionExpectError(kSessionError, offer);
+  SetLocalDescriptionExpectError(kSessionError, answer);
+  // Not crashing is our success.
+}
+
+TEST_F(WebRtcSessionTest, TestRtpDataChannel) {
+  constraints_.reset(new FakeConstraints());
+  constraints_->AddOptional(
+      webrtc::MediaConstraintsInterface::kEnableRtpDataChannels, true);
+  Init();
+
+  SetLocalDescriptionWithDataChannel();
+  EXPECT_EQ(cricket::DCT_RTP, data_engine_->last_channel_type());
+}
+
+TEST_F(WebRtcSessionTest, TestRtpDataChannelConstraintTakesPrecedence) {
+  MAYBE_SKIP_TEST(talk_base::SSLStreamAdapter::HaveDtlsSrtp);
+
+  constraints_.reset(new FakeConstraints());
+  constraints_->AddOptional(
+      webrtc::MediaConstraintsInterface::kEnableRtpDataChannels, true);
+  constraints_->AddOptional(
+    webrtc::MediaConstraintsInterface::kEnableSctpDataChannels, true);
+  constraints_->AddOptional(
+      webrtc::MediaConstraintsInterface::kEnableDtlsSrtp, true);
+  Init();
+
+  SetLocalDescriptionWithDataChannel();
+  EXPECT_EQ(cricket::DCT_RTP, data_engine_->last_channel_type());
+}
+
+TEST_F(WebRtcSessionTest, TestSctpDataChannelWithoutDtls) {
+  constraints_.reset(new FakeConstraints());
+  constraints_->AddOptional(
+      webrtc::MediaConstraintsInterface::kEnableSctpDataChannels, true);
+  Init();
+
+  SetLocalDescriptionWithDataChannel();
+  EXPECT_EQ(cricket::DCT_NONE, data_engine_->last_channel_type());
+}
+
+TEST_F(WebRtcSessionTest, TestSctpDataChannelWithDtls) {
+  MAYBE_SKIP_TEST(talk_base::SSLStreamAdapter::HaveDtlsSrtp);
+
+  constraints_.reset(new FakeConstraints());
+  constraints_->AddOptional(
+      webrtc::MediaConstraintsInterface::kEnableSctpDataChannels, true);
+  constraints_->AddOptional(
+      webrtc::MediaConstraintsInterface::kEnableDtlsSrtp, true);
+  Init();
+
+  SetLocalDescriptionWithDataChannel();
+  EXPECT_EQ(cricket::DCT_SCTP, data_engine_->last_channel_type());
+}
+// TODO(bemasc): Add a TestIceStatesBundle with BUNDLE enabled.  That test
+// currently fails because upon disconnection and reconnection OnIceComplete is
+// called more than once without returning to IceGatheringGathering.
diff --git a/talk/base/asyncfile.cc b/talk/base/asyncfile.cc
new file mode 100644
index 0000000..5c6e11d
--- /dev/null
+++ b/talk/base/asyncfile.cc
@@ -0,0 +1,38 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/asyncfile.h"
+
+namespace talk_base {
+
+AsyncFile::AsyncFile() {
+}
+
+AsyncFile::~AsyncFile() {
+}
+
+}  // namespace talk_base
diff --git a/talk/base/asyncfile.h b/talk/base/asyncfile.h
new file mode 100644
index 0000000..8af52be
--- /dev/null
+++ b/talk/base/asyncfile.h
@@ -0,0 +1,57 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_ASYNCFILE_H__
+#define TALK_BASE_ASYNCFILE_H__
+
+#include "talk/base/sigslot.h"
+
+namespace talk_base {
+
+// Provides the ability to perform file I/O asynchronously.
+// TODO: Create a common base class with AsyncSocket.
+class AsyncFile {
+ public:
+  AsyncFile();
+  virtual ~AsyncFile();
+
+  // Determines whether the file will receive read events.
+  virtual bool readable() = 0;
+  virtual void set_readable(bool value) = 0;
+
+  // Determines whether the file will receive write events.
+  virtual bool writable() = 0;
+  virtual void set_writable(bool value) = 0;
+
+  sigslot::signal1<AsyncFile*> SignalReadEvent;
+  sigslot::signal1<AsyncFile*> SignalWriteEvent;
+  sigslot::signal2<AsyncFile*, int> SignalCloseEvent;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_ASYNCFILE_H__
diff --git a/talk/base/asynchttprequest.cc b/talk/base/asynchttprequest.cc
new file mode 100644
index 0000000..68f6100
--- /dev/null
+++ b/talk/base/asynchttprequest.cc
@@ -0,0 +1,133 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/asynchttprequest.h"
+
+namespace talk_base {
+
+enum {
+  MSG_TIMEOUT = SignalThread::ST_MSG_FIRST_AVAILABLE,
+  MSG_LAUNCH_REQUEST
+};
+static const int kDefaultHTTPTimeout = 30 * 1000;  // 30 sec
+
+///////////////////////////////////////////////////////////////////////////////
+// AsyncHttpRequest
+///////////////////////////////////////////////////////////////////////////////
+
+AsyncHttpRequest::AsyncHttpRequest(const std::string &user_agent)
+    : start_delay_(0),
+      firewall_(NULL),
+      port_(80),
+      secure_(false),
+      timeout_(kDefaultHTTPTimeout),
+      fail_redirect_(false),
+      factory_(Thread::Current()->socketserver(), user_agent),
+      pool_(&factory_),
+      client_(user_agent.c_str(), &pool_),
+      error_(HE_NONE) {
+  client_.SignalHttpClientComplete.connect(this,
+      &AsyncHttpRequest::OnComplete);
+}
+
+AsyncHttpRequest::~AsyncHttpRequest() {
+}
+
+void AsyncHttpRequest::OnWorkStart() {
+  if (start_delay_ <= 0) {
+    LaunchRequest();
+  } else {
+    Thread::Current()->PostDelayed(start_delay_, this, MSG_LAUNCH_REQUEST);
+  }
+}
+
+void AsyncHttpRequest::OnWorkStop() {
+  // worker is already quitting, no need to explicitly quit
+  LOG(LS_INFO) << "HttpRequest cancelled";
+}
+
+void AsyncHttpRequest::OnComplete(HttpClient* client, HttpErrorType error) {
+  Thread::Current()->Clear(this, MSG_TIMEOUT);
+
+  set_error(error);
+  if (!error) {
+    LOG(LS_INFO) << "HttpRequest completed successfully";
+
+    std::string value;
+    if (client_.response().hasHeader(HH_LOCATION, &value)) {
+      response_redirect_ = value.c_str();
+    }
+  } else {
+    LOG(LS_INFO) << "HttpRequest completed with error: " << error;
+  }
+
+  worker()->Quit();
+}
+
+void AsyncHttpRequest::OnMessage(Message* message) {
+  switch (message->message_id) {
+   case MSG_TIMEOUT:
+    LOG(LS_INFO) << "HttpRequest timed out";
+    client_.reset();
+    worker()->Quit();
+    break;
+   case MSG_LAUNCH_REQUEST:
+    LaunchRequest();
+    break;
+   default:
+    SignalThread::OnMessage(message);
+    break;
+  }
+}
+
+void AsyncHttpRequest::DoWork() {
+  // Do nothing while we wait for the request to finish. We only do this so
+  // that we can be a SignalThread; in the future this class should not be
+  // a SignalThread, since it does not need to spawn a new thread.
+  Thread::Current()->ProcessMessages(kForever);
+}
+
+void AsyncHttpRequest::LaunchRequest() {
+  factory_.SetProxy(proxy_);
+  if (secure_)
+    factory_.UseSSL(host_.c_str());
+
+  bool transparent_proxy = (port_ == 80) &&
+           ((proxy_.type == PROXY_HTTPS) || (proxy_.type == PROXY_UNKNOWN));
+  if (transparent_proxy) {
+    client_.set_proxy(proxy_);
+  }
+  client_.set_fail_redirect(fail_redirect_);
+  client_.set_server(SocketAddress(host_, port_));
+
+  LOG(LS_INFO) << "HttpRequest start: " << host_ + client_.request().path;
+
+  Thread::Current()->PostDelayed(timeout_, this, MSG_TIMEOUT);
+  client_.start();
+}
+
+}  // namespace talk_base
diff --git a/talk/base/asynchttprequest.h b/talk/base/asynchttprequest.h
new file mode 100644
index 0000000..13edf61
--- /dev/null
+++ b/talk/base/asynchttprequest.h
@@ -0,0 +1,121 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_ASYNCHTTPREQUEST_H_
+#define TALK_BASE_ASYNCHTTPREQUEST_H_
+
+#include <string>
+#include "talk/base/event.h"
+#include "talk/base/httpclient.h"
+#include "talk/base/signalthread.h"
+#include "talk/base/socketpool.h"
+#include "talk/base/sslsocketfactory.h"
+
+namespace talk_base {
+
+class FirewallManager;
+
+///////////////////////////////////////////////////////////////////////////////
+// AsyncHttpRequest
+// Performs an HTTP request on a background thread.  Notifies on the foreground
+// thread once the request is done (successfully or unsuccessfully).
+///////////////////////////////////////////////////////////////////////////////
+
+class AsyncHttpRequest : public SignalThread {
+ public:
+  explicit AsyncHttpRequest(const std::string &user_agent);
+  ~AsyncHttpRequest();
+
+  // If start_delay is less than or equal to zero, this starts immediately.
+  // Start_delay defaults to zero.
+  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;
+  }
+  void set_firewall(FirewallManager * firewall) {
+    firewall_ = firewall;
+  }
+
+  // The DNS name of the host to connect to.
+  const std::string& host() { return host_; }
+  void set_host(const std::string& host) { host_ = host; }
+
+  // The port to connect to on the target host.
+  int port() { return port_; }
+  void set_port(int port) { port_ = port; }
+
+  // Whether the request should use SSL.
+  bool secure() { return secure_; }
+  void set_secure(bool secure) { secure_ = secure; }
+
+  // Time to wait on the download, in ms.
+  int timeout() { return timeout_; }
+  void set_timeout(int timeout) { timeout_ = timeout; }
+
+  // Fail redirects to allow analysis of redirect urls, etc.
+  bool fail_redirect() const { return fail_redirect_; }
+  void set_fail_redirect(bool redirect) { fail_redirect_ = redirect; }
+
+  // Returns the redirect when redirection occurs
+  const std::string& response_redirect() { return response_redirect_; }
+
+  HttpRequestData& request() { return client_.request(); }
+  HttpResponseData& response() { return client_.response(); }
+  HttpErrorType error() { return error_; }
+
+ protected:
+  void set_error(HttpErrorType error) { error_ = error; }
+  virtual void OnWorkStart();
+  virtual void OnWorkStop();
+  void OnComplete(HttpClient* client, HttpErrorType error);
+  virtual void OnMessage(Message* message);
+  virtual void DoWork();
+
+ private:
+  void LaunchRequest();
+
+  int start_delay_;
+  ProxyInfo proxy_;
+  FirewallManager* firewall_;
+  std::string host_;
+  int port_;
+  bool secure_;
+  int timeout_;
+  bool fail_redirect_;
+  SslSocketFactory factory_;
+  ReuseSocketPool pool_;
+  HttpClient client_;
+  HttpErrorType error_;
+  std::string response_redirect_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_ASYNCHTTPREQUEST_H_
diff --git a/talk/base/asynchttprequest_unittest.cc b/talk/base/asynchttprequest_unittest.cc
new file mode 100644
index 0000000..13842da
--- /dev/null
+++ b/talk/base/asynchttprequest_unittest.cc
@@ -0,0 +1,250 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+#include "talk/base/asynchttprequest.h"
+#include "talk/base/gunit.h"
+#include "talk/base/httpserver.h"
+#include "talk/base/socketstream.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+static const SocketAddress kServerAddr("127.0.0.1", 0);
+static const SocketAddress kServerHostnameAddr("localhost", 0);
+static const char kServerGetPath[] = "/get";
+static const char kServerPostPath[] = "/post";
+static const char kServerResponse[] = "This is a test";
+
+class TestHttpServer : public HttpServer, public sigslot::has_slots<> {
+ public:
+  TestHttpServer(Thread* thread, const SocketAddress& addr) :
+      socket_(thread->socketserver()->CreateAsyncSocket(addr.family(),
+                                                        SOCK_STREAM)) {
+    socket_->Bind(addr);
+    socket_->Listen(5);
+    socket_->SignalReadEvent.connect(this, &TestHttpServer::OnAccept);
+  }
+
+  SocketAddress address() const { return socket_->GetLocalAddress(); }
+  void Close() const { socket_->Close(); }
+
+ private:
+  void OnAccept(AsyncSocket* socket) {
+    AsyncSocket* new_socket = socket_->Accept(NULL);
+    if (new_socket) {
+      HandleConnection(new SocketStream(new_socket));
+    }
+  }
+  talk_base::scoped_ptr<AsyncSocket> socket_;
+};
+
+class AsyncHttpRequestTest : public testing::Test,
+                             public sigslot::has_slots<> {
+ public:
+  AsyncHttpRequestTest()
+      : started_(false),
+        done_(false),
+        server_(Thread::Current(), kServerAddr) {
+    server_.SignalHttpRequest.connect(this, &AsyncHttpRequestTest::OnRequest);
+  }
+
+  bool started() const { return started_; }
+  bool done() const { return done_; }
+
+  AsyncHttpRequest* CreateGetRequest(const std::string& host, int port,
+                                     const std::string& path) {
+    talk_base::AsyncHttpRequest* request =
+        new talk_base::AsyncHttpRequest("unittest");
+    request->SignalWorkDone.connect(this,
+        &AsyncHttpRequestTest::OnRequestDone);
+    request->request().verb = talk_base::HV_GET;
+    request->set_host(host);
+    request->set_port(port);
+    request->request().path = path;
+    request->response().document.reset(new MemoryStream());
+    return request;
+  }
+  AsyncHttpRequest* CreatePostRequest(const std::string& host, int port,
+                                      const std::string& path,
+                                      const std::string content_type,
+                                      StreamInterface* content) {
+    talk_base::AsyncHttpRequest* request =
+        new talk_base::AsyncHttpRequest("unittest");
+    request->SignalWorkDone.connect(this,
+        &AsyncHttpRequestTest::OnRequestDone);
+    request->request().verb = talk_base::HV_POST;
+    request->set_host(host);
+    request->set_port(port);
+    request->request().path = path;
+    request->request().setContent(content_type, content);
+    request->response().document.reset(new MemoryStream());
+    return request;
+  }
+
+  const TestHttpServer& server() const { return server_; }
+
+ protected:
+  void OnRequest(HttpServer* server, HttpServerTransaction* t) {
+    started_ = true;
+
+    if (t->request.path == kServerGetPath) {
+      t->response.set_success("text/plain", new MemoryStream(kServerResponse));
+    } else if (t->request.path == kServerPostPath) {
+      // reverse the data and reply
+      size_t size;
+      StreamInterface* in = t->request.document.get();
+      StreamInterface* out = new MemoryStream();
+      in->GetSize(&size);
+      for (size_t i = 0; i < size; ++i) {
+        char ch;
+        in->SetPosition(size - i - 1);
+        in->Read(&ch, 1, NULL, NULL);
+        out->Write(&ch, 1, NULL, NULL);
+      }
+      out->Rewind();
+      t->response.set_success("text/plain", out);
+    } else {
+      t->response.set_error(404);
+    }
+    server_.Respond(t);
+  }
+  void OnRequestDone(SignalThread* thread) {
+    done_ = true;
+  }
+
+ private:
+  bool started_;
+  bool done_;
+  TestHttpServer server_;
+};
+
+TEST_F(AsyncHttpRequestTest, TestGetSuccess) {
+  AsyncHttpRequest* req = CreateGetRequest(
+      kServerHostnameAddr.hostname(), server().address().port(),
+      kServerGetPath);
+  EXPECT_FALSE(started());
+  req->Start();
+  EXPECT_TRUE_WAIT(started(), 5000);  // Should have started by now.
+  EXPECT_TRUE_WAIT(done(), 5000);
+  std::string response;
+  EXPECT_EQ(200U, req->response().scode);
+  ASSERT_TRUE(req->response().document);
+  req->response().document->Rewind();
+  req->response().document->ReadLine(&response);
+  EXPECT_EQ(kServerResponse, response);
+  req->Release();
+}
+
+TEST_F(AsyncHttpRequestTest, TestGetNotFound) {
+  AsyncHttpRequest* req = CreateGetRequest(
+      kServerHostnameAddr.hostname(), server().address().port(),
+      "/bad");
+  req->Start();
+  EXPECT_TRUE_WAIT(done(), 5000);
+  size_t size;
+  EXPECT_EQ(404U, req->response().scode);
+  ASSERT_TRUE(req->response().document);
+  req->response().document->GetSize(&size);
+  EXPECT_EQ(0U, size);
+  req->Release();
+}
+
+TEST_F(AsyncHttpRequestTest, TestGetToNonServer) {
+  AsyncHttpRequest* req = CreateGetRequest(
+      "127.0.0.1", server().address().port(),
+      kServerGetPath);
+  // Stop the server before we send the request.
+  server().Close();
+  req->Start();
+  EXPECT_TRUE_WAIT(done(), 10000);
+  size_t size;
+  EXPECT_EQ(500U, req->response().scode);
+  ASSERT_TRUE(req->response().document);
+  req->response().document->GetSize(&size);
+  EXPECT_EQ(0U, size);
+  req->Release();
+}
+
+TEST_F(AsyncHttpRequestTest, DISABLED_TestGetToInvalidHostname) {
+  AsyncHttpRequest* req = CreateGetRequest(
+      "invalid", server().address().port(),
+      kServerGetPath);
+  req->Start();
+  EXPECT_TRUE_WAIT(done(), 5000);
+  size_t size;
+  EXPECT_EQ(500U, req->response().scode);
+  ASSERT_TRUE(req->response().document);
+  req->response().document->GetSize(&size);
+  EXPECT_EQ(0U, size);
+  req->Release();
+}
+
+TEST_F(AsyncHttpRequestTest, TestPostSuccess) {
+  AsyncHttpRequest* req = CreatePostRequest(
+      kServerHostnameAddr.hostname(), server().address().port(),
+      kServerPostPath, "text/plain", new MemoryStream("abcd1234"));
+  req->Start();
+  EXPECT_TRUE_WAIT(done(), 5000);
+  std::string response;
+  EXPECT_EQ(200U, req->response().scode);
+  ASSERT_TRUE(req->response().document);
+  req->response().document->Rewind();
+  req->response().document->ReadLine(&response);
+  EXPECT_EQ("4321dcba", response);
+  req->Release();
+}
+
+// Ensure that we shut down properly even if work is outstanding.
+TEST_F(AsyncHttpRequestTest, TestCancel) {
+  AsyncHttpRequest* req = CreateGetRequest(
+      kServerHostnameAddr.hostname(), server().address().port(),
+      kServerGetPath);
+  req->Start();
+  req->Destroy(true);
+}
+
+TEST_F(AsyncHttpRequestTest, TestGetSuccessDelay) {
+  AsyncHttpRequest* req = CreateGetRequest(
+      kServerHostnameAddr.hostname(), server().address().port(),
+      kServerGetPath);
+  req->set_start_delay(10);  // Delay 10ms.
+  req->Start();
+  Thread::SleepMs(5);
+  EXPECT_FALSE(started());  // Should not have started immediately.
+  EXPECT_TRUE_WAIT(started(), 5000);  // Should have started by now.
+  EXPECT_TRUE_WAIT(done(), 5000);
+  std::string response;
+  EXPECT_EQ(200U, req->response().scode);
+  ASSERT_TRUE(req->response().document);
+  req->response().document->Rewind();
+  req->response().document->ReadLine(&response);
+  EXPECT_EQ(kServerResponse, response);
+  req->Release();
+}
+
+}  // namespace talk_base
diff --git a/talk/base/asyncpacketsocket.h b/talk/base/asyncpacketsocket.h
new file mode 100644
index 0000000..a88f770
--- /dev/null
+++ b/talk/base/asyncpacketsocket.h
@@ -0,0 +1,108 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_ASYNCPACKETSOCKET_H_
+#define TALK_BASE_ASYNCPACKETSOCKET_H_
+
+#include "talk/base/sigslot.h"
+#include "talk/base/socket.h"
+
+namespace talk_base {
+
+// Provides the ability to receive packets asynchronously. Sends are not
+// buffered since it is acceptable to drop packets under high load.
+class AsyncPacketSocket : public sigslot::has_slots<> {
+ public:
+  enum State {
+    STATE_CLOSED,
+    STATE_BINDING,
+    STATE_BOUND,
+    STATE_CONNECTING,
+    STATE_CONNECTED
+  };
+
+  AsyncPacketSocket() { }
+  virtual ~AsyncPacketSocket() { }
+
+  // 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 = 0;
+
+  // Returns remote address. Returns zeroes if this is not a client TCP socket.
+  virtual SocketAddress GetRemoteAddress() const = 0;
+
+  // Send a packet.
+  virtual int Send(const void *pv, size_t cb) = 0;
+  virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr) = 0;
+
+  // Close the socket.
+  virtual int Close() = 0;
+
+  // Returns current state of the socket.
+  virtual State GetState() const = 0;
+
+  // Get/set options.
+  virtual int GetOption(Socket::Option opt, int* value) = 0;
+  virtual int SetOption(Socket::Option opt, int value) = 0;
+
+  // Get/Set current error.
+  // TODO: Remove SetError().
+  virtual int GetError() const = 0;
+  virtual void SetError(int error) = 0;
+
+  // Emitted each time a packet is read. Used only for UDP and
+  // connected TCP sockets.
+  sigslot::signal4<AsyncPacketSocket*, const char*, size_t,
+                   const SocketAddress&> SignalReadPacket;
+
+  // Emitted when the socket is currently able to send.
+  sigslot::signal1<AsyncPacketSocket*> SignalReadyToSend;
+
+  // Emitted after address for the socket is allocated, i.e. binding
+  // is finished. State of the socket is changed from BINDING to BOUND
+  // (for UDP and server TCP sockets) or CONNECTING (for client TCP
+  // sockets).
+  sigslot::signal2<AsyncPacketSocket*, const SocketAddress&> SignalAddressReady;
+
+  // Emitted for client TCP sockets when state is changed from
+  // CONNECTING to CONNECTED.
+  sigslot::signal1<AsyncPacketSocket*> SignalConnect;
+
+  // Emitted for client TCP sockets when state is changed from
+  // CONNECTED to CLOSED.
+  sigslot::signal2<AsyncPacketSocket*, int> SignalClose;
+
+  // Used only for listening TCP sockets.
+  sigslot::signal2<AsyncPacketSocket*, AsyncPacketSocket*> SignalNewConnection;
+
+ private:
+  DISALLOW_EVIL_CONSTRUCTORS(AsyncPacketSocket);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_ASYNCPACKETSOCKET_H_
diff --git a/talk/base/asyncsocket.cc b/talk/base/asyncsocket.cc
new file mode 100644
index 0000000..d9ed94c
--- /dev/null
+++ b/talk/base/asyncsocket.cc
@@ -0,0 +1,61 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/asyncsocket.h"
+
+namespace talk_base {
+
+AsyncSocket::AsyncSocket() {
+}
+
+AsyncSocket::~AsyncSocket() {
+}
+
+AsyncSocketAdapter::AsyncSocketAdapter(AsyncSocket* socket) : socket_(NULL) {
+  Attach(socket);
+}
+
+AsyncSocketAdapter::~AsyncSocketAdapter() {
+  delete socket_;
+}
+
+void AsyncSocketAdapter::Attach(AsyncSocket* socket) {
+  ASSERT(!socket_);
+  socket_ = socket;
+  if (socket_) {
+    socket_->SignalConnectEvent.connect(this,
+        &AsyncSocketAdapter::OnConnectEvent);
+    socket_->SignalReadEvent.connect(this,
+        &AsyncSocketAdapter::OnReadEvent);
+    socket_->SignalWriteEvent.connect(this,
+        &AsyncSocketAdapter::OnWriteEvent);
+    socket_->SignalCloseEvent.connect(this,
+        &AsyncSocketAdapter::OnCloseEvent);
+  }
+}
+
+}  // namespace talk_base
diff --git a/talk/base/asyncsocket.h b/talk/base/asyncsocket.h
new file mode 100644
index 0000000..3d12984
--- /dev/null
+++ b/talk/base/asyncsocket.h
@@ -0,0 +1,133 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_ASYNCSOCKET_H_
+#define TALK_BASE_ASYNCSOCKET_H_
+
+#include "talk/base/common.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socket.h"
+
+namespace talk_base {
+
+// TODO: Remove Socket and rename AsyncSocket to Socket.
+
+// Provides the ability to perform socket I/O asynchronously.
+class AsyncSocket : public Socket {
+ public:
+  AsyncSocket();
+  virtual ~AsyncSocket();
+
+  virtual AsyncSocket* Accept(SocketAddress* paddr) = 0;
+
+  sigslot::signal1<AsyncSocket*> SignalReadEvent;        // ready to read
+  sigslot::signal1<AsyncSocket*> SignalWriteEvent;       // ready to write
+  sigslot::signal1<AsyncSocket*> SignalConnectEvent;     // connected
+  sigslot::signal2<AsyncSocket*, int> SignalCloseEvent;  // closed
+};
+
+class AsyncSocketAdapter : public AsyncSocket, public sigslot::has_slots<> {
+ public:
+  // The adapted socket may explicitly be NULL, and later assigned using Attach.
+  // However, subclasses which support detached mode must override any methods
+  // that will be called during the detached period (usually GetState()), to
+  // avoid dereferencing a null pointer.
+  explicit AsyncSocketAdapter(AsyncSocket* socket);
+  virtual ~AsyncSocketAdapter();
+  void Attach(AsyncSocket* socket);
+  virtual SocketAddress GetLocalAddress() const {
+    return socket_->GetLocalAddress();
+  }
+  virtual SocketAddress GetRemoteAddress() const {
+    return socket_->GetRemoteAddress();
+  }
+  virtual int Bind(const SocketAddress& addr) {
+    return socket_->Bind(addr);
+  }
+  virtual int Connect(const SocketAddress& addr) {
+    return socket_->Connect(addr);
+  }
+  virtual int Send(const void* pv, size_t cb) {
+    return socket_->Send(pv, cb);
+  }
+  virtual int SendTo(const void* pv, size_t cb, const SocketAddress& addr) {
+    return socket_->SendTo(pv, cb, addr);
+  }
+  virtual int Recv(void* pv, size_t cb) {
+    return socket_->Recv(pv, cb);
+  }
+  virtual int RecvFrom(void* pv, size_t cb, SocketAddress* paddr) {
+    return socket_->RecvFrom(pv, cb, paddr);
+  }
+  virtual int Listen(int backlog) {
+    return socket_->Listen(backlog);
+  }
+  virtual AsyncSocket* Accept(SocketAddress* paddr) {
+    return socket_->Accept(paddr);
+  }
+  virtual int Close() {
+    return socket_->Close();
+  }
+  virtual int GetError() const {
+    return socket_->GetError();
+  }
+  virtual void SetError(int error) {
+    return socket_->SetError(error);
+  }
+  virtual ConnState GetState() const {
+    return socket_->GetState();
+  }
+  virtual int EstimateMTU(uint16* mtu) {
+    return socket_->EstimateMTU(mtu);
+  }
+  virtual int GetOption(Option opt, int* value) {
+    return socket_->GetOption(opt, value);
+  }
+  virtual int SetOption(Option opt, int value) {
+    return socket_->SetOption(opt, value);
+  }
+
+ protected:
+  virtual void OnConnectEvent(AsyncSocket* socket) {
+    SignalConnectEvent(this);
+  }
+  virtual void OnReadEvent(AsyncSocket* socket) {
+    SignalReadEvent(this);
+  }
+  virtual void OnWriteEvent(AsyncSocket* socket) {
+    SignalWriteEvent(this);
+  }
+  virtual void OnCloseEvent(AsyncSocket* socket, int err) {
+    SignalCloseEvent(this, err);
+  }
+
+  AsyncSocket* socket_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_ASYNCSOCKET_H_
diff --git a/talk/base/asynctcpsocket.cc b/talk/base/asynctcpsocket.cc
new file mode 100644
index 0000000..095413d
--- /dev/null
+++ b/talk/base/asynctcpsocket.cc
@@ -0,0 +1,313 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/asynctcpsocket.h"
+
+#include <cstring>
+
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+
+#ifdef POSIX
+#include <errno.h>
+#endif  // POSIX
+
+namespace talk_base {
+
+static const size_t kMaxPacketSize = 64 * 1024;
+
+typedef uint16 PacketLength;
+static const size_t kPacketLenSize = sizeof(PacketLength);
+
+static const size_t kBufSize = kMaxPacketSize + kPacketLenSize;
+
+static const int kListenBacklog = 5;
+
+// Binds and connects |socket|
+AsyncSocket* AsyncTCPSocketBase::ConnectSocket(
+    talk_base::AsyncSocket* socket,
+    const talk_base::SocketAddress& bind_address,
+    const talk_base::SocketAddress& remote_address) {
+  talk_base::scoped_ptr<talk_base::AsyncSocket> owned_socket(socket);
+  if (socket->Bind(bind_address) < 0) {
+    LOG(LS_ERROR) << "Bind() failed with error " << socket->GetError();
+    return NULL;
+  }
+  if (socket->Connect(remote_address) < 0) {
+    LOG(LS_ERROR) << "Connect() failed with error " << socket->GetError();
+    return NULL;
+  }
+  return owned_socket.release();
+}
+
+AsyncTCPSocketBase::AsyncTCPSocketBase(AsyncSocket* socket, bool listen,
+                                       size_t max_packet_size)
+    : socket_(socket),
+      listen_(listen),
+      insize_(max_packet_size),
+      inpos_(0),
+      outsize_(max_packet_size),
+      outpos_(0) {
+  inbuf_ = new char[insize_];
+  outbuf_ = new char[outsize_];
+
+  ASSERT(socket_.get() != NULL);
+  socket_->SignalConnectEvent.connect(
+      this, &AsyncTCPSocketBase::OnConnectEvent);
+  socket_->SignalReadEvent.connect(this, &AsyncTCPSocketBase::OnReadEvent);
+  socket_->SignalWriteEvent.connect(this, &AsyncTCPSocketBase::OnWriteEvent);
+  socket_->SignalCloseEvent.connect(this, &AsyncTCPSocketBase::OnCloseEvent);
+
+  if (listen_) {
+    if (socket_->Listen(kListenBacklog) < 0) {
+      LOG(LS_ERROR) << "Listen() failed with error " << socket_->GetError();
+    }
+  }
+}
+
+AsyncTCPSocketBase::~AsyncTCPSocketBase() {
+  delete [] inbuf_;
+  delete [] outbuf_;
+}
+
+SocketAddress AsyncTCPSocketBase::GetLocalAddress() const {
+  return socket_->GetLocalAddress();
+}
+
+SocketAddress AsyncTCPSocketBase::GetRemoteAddress() const {
+  return socket_->GetRemoteAddress();
+}
+
+int AsyncTCPSocketBase::Close() {
+  return socket_->Close();
+}
+
+AsyncTCPSocket::State AsyncTCPSocketBase::GetState() const {
+  switch (socket_->GetState()) {
+    case Socket::CS_CLOSED:
+      return STATE_CLOSED;
+    case Socket::CS_CONNECTING:
+      if (listen_) {
+        return STATE_BOUND;
+      } else {
+        return STATE_CONNECTING;
+      }
+    case Socket::CS_CONNECTED:
+      return STATE_CONNECTED;
+    default:
+      ASSERT(false);
+      return STATE_CLOSED;
+  }
+}
+
+int AsyncTCPSocketBase::GetOption(Socket::Option opt, int* value) {
+  return socket_->GetOption(opt, value);
+}
+
+int AsyncTCPSocketBase::SetOption(Socket::Option opt, int value) {
+  return socket_->SetOption(opt, value);
+}
+
+int AsyncTCPSocketBase::GetError() const {
+  return socket_->GetError();
+}
+
+void AsyncTCPSocketBase::SetError(int error) {
+  return socket_->SetError(error);
+}
+
+int AsyncTCPSocketBase::SendTo(const void *pv, size_t cb,
+                               const SocketAddress& addr) {
+  if (addr == GetRemoteAddress())
+    return Send(pv, cb);
+
+  ASSERT(false);
+  socket_->SetError(ENOTCONN);
+  return -1;
+}
+
+int AsyncTCPSocketBase::SendRaw(const void * pv, size_t cb) {
+  if (outpos_ + cb > outsize_) {
+    socket_->SetError(EMSGSIZE);
+    return -1;
+  }
+
+  memcpy(outbuf_ + outpos_, pv, cb);
+  outpos_ += cb;
+
+  return FlushOutBuffer();
+}
+
+int AsyncTCPSocketBase::FlushOutBuffer() {
+  int res = socket_->Send(outbuf_, outpos_);
+  if (res <= 0) {
+    return res;
+  }
+  if (static_cast<size_t>(res) <= outpos_) {
+    outpos_ -= res;
+  } else {
+    ASSERT(false);
+    return -1;
+  }
+  if (outpos_ > 0) {
+    memmove(outbuf_, outbuf_ + res, outpos_);
+  }
+  return res;
+}
+
+void AsyncTCPSocketBase::AppendToOutBuffer(const void* pv, size_t cb) {
+  ASSERT(outpos_ + cb < outsize_);
+  memcpy(outbuf_ + outpos_, pv, cb);
+  outpos_ += cb;
+}
+
+void AsyncTCPSocketBase::OnConnectEvent(AsyncSocket* socket) {
+  SignalConnect(this);
+}
+
+void AsyncTCPSocketBase::OnReadEvent(AsyncSocket* socket) {
+  ASSERT(socket_.get() == socket);
+
+  if (listen_) {
+    talk_base::SocketAddress address;
+    talk_base::AsyncSocket* new_socket = socket->Accept(&address);
+    if (!new_socket) {
+      // TODO: Do something better like forwarding the error
+      // to the user.
+      LOG(LS_ERROR) << "TCP accept failed with error " << socket_->GetError();
+      return;
+    }
+
+    HandleIncomingConnection(new_socket);
+
+    // Prime a read event in case data is waiting.
+    new_socket->SignalReadEvent(new_socket);
+  } else {
+    int len = socket_->Recv(inbuf_ + inpos_, insize_ - inpos_);
+    if (len < 0) {
+      // TODO: Do something better like forwarding the error to the user.
+      if (!socket_->IsBlocking()) {
+        LOG(LS_ERROR) << "Recv() returned error: " << socket_->GetError();
+      }
+      return;
+    }
+
+    inpos_ += len;
+
+    ProcessInput(inbuf_, &inpos_);
+
+    if (inpos_ >= insize_) {
+      LOG(LS_ERROR) << "input buffer overflow";
+      ASSERT(false);
+      inpos_ = 0;
+    }
+  }
+}
+
+void AsyncTCPSocketBase::OnWriteEvent(AsyncSocket* socket) {
+  ASSERT(socket_.get() == socket);
+
+  if (outpos_ > 0) {
+    FlushOutBuffer();
+  }
+
+  if (outpos_ == 0) {
+    SignalReadyToSend(this);
+  }
+}
+
+void AsyncTCPSocketBase::OnCloseEvent(AsyncSocket* socket, int error) {
+  SignalClose(this, error);
+}
+
+// AsyncTCPSocket
+// Binds and connects |socket| and creates AsyncTCPSocket for
+// it. Takes ownership of |socket|. Returns NULL if bind() or
+// connect() fail (|socket| is destroyed in that case).
+AsyncTCPSocket* AsyncTCPSocket::Create(
+    AsyncSocket* socket,
+    const SocketAddress& bind_address,
+    const SocketAddress& remote_address) {
+  return new AsyncTCPSocket(AsyncTCPSocketBase::ConnectSocket(
+      socket, bind_address, remote_address), false);
+}
+
+AsyncTCPSocket::AsyncTCPSocket(AsyncSocket* socket, bool listen)
+    : AsyncTCPSocketBase(socket, listen, kBufSize) {
+}
+
+int AsyncTCPSocket::Send(const void *pv, size_t cb) {
+  if (cb > kBufSize) {
+    SetError(EMSGSIZE);
+    return -1;
+  }
+
+  // If we are blocking on send, then silently drop this packet
+  if (!IsOutBufferEmpty())
+    return static_cast<int>(cb);
+
+  PacketLength pkt_len = HostToNetwork16(static_cast<PacketLength>(cb));
+  AppendToOutBuffer(&pkt_len, kPacketLenSize);
+  AppendToOutBuffer(pv, cb);
+
+  int res = FlushOutBuffer();
+  if (res <= 0) {
+    // drop packet if we made no progress
+    ClearOutBuffer();
+    return res;
+  }
+
+  // We claim to have sent the whole thing, even if we only sent partial
+  return static_cast<int>(cb);
+}
+
+void AsyncTCPSocket::ProcessInput(char * data, size_t* len) {
+  SocketAddress remote_addr(GetRemoteAddress());
+
+  while (true) {
+    if (*len < kPacketLenSize)
+      return;
+
+    PacketLength pkt_len = talk_base::GetBE16(data);
+    if (*len < kPacketLenSize + pkt_len)
+      return;
+
+    SignalReadPacket(this, data + kPacketLenSize, pkt_len, remote_addr);
+
+    *len -= kPacketLenSize + pkt_len;
+    if (*len > 0) {
+      memmove(data, data + kPacketLenSize + pkt_len, *len);
+    }
+  }
+}
+
+void AsyncTCPSocket::HandleIncomingConnection(AsyncSocket* socket) {
+  SignalNewConnection(this, new AsyncTCPSocket(socket, false));
+}
+
+}  // namespace talk_base
diff --git a/talk/base/asynctcpsocket.h b/talk/base/asynctcpsocket.h
new file mode 100644
index 0000000..b34ce18
--- /dev/null
+++ b/talk/base/asynctcpsocket.h
@@ -0,0 +1,114 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_ASYNCTCPSOCKET_H_
+#define TALK_BASE_ASYNCTCPSOCKET_H_
+
+#include "talk/base/asyncpacketsocket.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socketfactory.h"
+
+namespace talk_base {
+
+// Simulates UDP semantics over TCP.  Send and Recv packet sizes
+// are preserved, and drops packets silently on Send, rather than
+// buffer them in user space.
+class AsyncTCPSocketBase : public AsyncPacketSocket {
+ public:
+  AsyncTCPSocketBase(AsyncSocket* socket, bool listen, size_t max_packet_size);
+  virtual ~AsyncTCPSocketBase();
+
+  // Pure virtual methods to send and recv data.
+  virtual int Send(const void *pv, size_t cb) = 0;
+  virtual void ProcessInput(char* data, size_t* len) = 0;
+  // Signals incoming connection.
+  virtual void HandleIncomingConnection(AsyncSocket* socket) = 0;
+
+  virtual SocketAddress GetLocalAddress() const;
+  virtual SocketAddress GetRemoteAddress() const;
+  virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr);
+  virtual int Close();
+
+  virtual State GetState() const;
+  virtual int GetOption(Socket::Option opt, int* value);
+  virtual int SetOption(Socket::Option opt, int value);
+  virtual int GetError() const;
+  virtual void SetError(int error);
+
+ protected:
+  // Binds and connects |socket| and creates AsyncTCPSocket for
+  // it. Takes ownership of |socket|. Returns NULL if bind() or
+  // connect() fail (|socket| is destroyed in that case).
+  static AsyncSocket* ConnectSocket(AsyncSocket* socket,
+                                    const SocketAddress& bind_address,
+                                    const SocketAddress& remote_address);
+  virtual int SendRaw(const void* pv, size_t cb);
+  int FlushOutBuffer();
+  // Add data to |outbuf_|.
+  void AppendToOutBuffer(const void* pv, size_t cb);
+
+  // Helper methods for |outpos_|.
+  bool IsOutBufferEmpty() const { return outpos_ == 0; }
+  void ClearOutBuffer() { outpos_ = 0; }
+
+ private:
+  // Called by the underlying socket
+  void OnConnectEvent(AsyncSocket* socket);
+  void OnReadEvent(AsyncSocket* socket);
+  void OnWriteEvent(AsyncSocket* socket);
+  void OnCloseEvent(AsyncSocket* socket, int error);
+
+  scoped_ptr<AsyncSocket> socket_;
+  bool listen_;
+  char* inbuf_, * outbuf_;
+  size_t insize_, inpos_, outsize_, outpos_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(AsyncTCPSocketBase);
+};
+
+class AsyncTCPSocket : public AsyncTCPSocketBase {
+ public:
+  // Binds and connects |socket| and creates AsyncTCPSocket for
+  // it. Takes ownership of |socket|. Returns NULL if bind() or
+  // connect() fail (|socket| is destroyed in that case).
+  static AsyncTCPSocket* Create(AsyncSocket* socket,
+                                const SocketAddress& bind_address,
+                                const SocketAddress& remote_address);
+  AsyncTCPSocket(AsyncSocket* socket, bool listen);
+  virtual ~AsyncTCPSocket() {}
+
+  virtual int Send(const void* pv, size_t cb);
+  virtual void ProcessInput(char* data, size_t* len);
+  virtual void HandleIncomingConnection(AsyncSocket* socket);
+
+ private:
+  DISALLOW_EVIL_CONSTRUCTORS(AsyncTCPSocket);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_ASYNCTCPSOCKET_H_
diff --git a/talk/base/asynctcpsocket_unittest.cc b/talk/base/asynctcpsocket_unittest.cc
new file mode 100644
index 0000000..4f87dbd
--- /dev/null
+++ b/talk/base/asynctcpsocket_unittest.cc
@@ -0,0 +1,70 @@
+/*
+ * libjingle
+ * Copyright 2004--2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/asynctcpsocket.h"
+#include "talk/base/gunit.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/virtualsocketserver.h"
+
+namespace talk_base {
+
+class AsyncTCPSocketTest
+    : public testing::Test,
+      public sigslot::has_slots<> {
+ public:
+  AsyncTCPSocketTest()
+      : pss_(new talk_base::PhysicalSocketServer),
+        vss_(new talk_base::VirtualSocketServer(pss_.get())),
+        socket_(vss_->CreateAsyncSocket(SOCK_STREAM)),
+        tcp_socket_(new AsyncTCPSocket(socket_, true)),
+        ready_to_send_(false) {
+    tcp_socket_->SignalReadyToSend.connect(this,
+                                           &AsyncTCPSocketTest::OnReadyToSend);
+  }
+
+  void OnReadyToSend(talk_base::AsyncPacketSocket* socket) {
+    ready_to_send_ = true;
+  }
+
+ protected:
+  scoped_ptr<PhysicalSocketServer> pss_;
+  scoped_ptr<VirtualSocketServer> vss_;
+  AsyncSocket* socket_;
+  scoped_ptr<AsyncTCPSocket> tcp_socket_;
+  bool ready_to_send_;
+};
+
+TEST_F(AsyncTCPSocketTest, OnWriteEvent) {
+  EXPECT_FALSE(ready_to_send_);
+  socket_->SignalWriteEvent(socket_);
+  EXPECT_TRUE(ready_to_send_);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/asyncudpsocket.cc b/talk/base/asyncudpsocket.cc
new file mode 100644
index 0000000..6388ce7
--- /dev/null
+++ b/talk/base/asyncudpsocket.cc
@@ -0,0 +1,136 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/asyncudpsocket.h"
+#include "talk/base/logging.h"
+
+namespace talk_base {
+
+static const int BUF_SIZE = 64 * 1024;
+
+AsyncUDPSocket* AsyncUDPSocket::Create(
+    AsyncSocket* socket,
+    const SocketAddress& bind_address) {
+  scoped_ptr<AsyncSocket> owned_socket(socket);
+  if (socket->Bind(bind_address) < 0) {
+    LOG(LS_ERROR) << "Bind() failed with error " << socket->GetError();
+    return NULL;
+  }
+  return new AsyncUDPSocket(owned_socket.release());
+}
+
+AsyncUDPSocket* AsyncUDPSocket::Create(SocketFactory* factory,
+                                       const SocketAddress& bind_address) {
+  AsyncSocket* socket =
+      factory->CreateAsyncSocket(bind_address.family(), SOCK_DGRAM);
+  if (!socket)
+    return NULL;
+  return Create(socket, bind_address);
+}
+
+AsyncUDPSocket::AsyncUDPSocket(AsyncSocket* socket)
+    : socket_(socket) {
+  ASSERT(socket_);
+  size_ = BUF_SIZE;
+  buf_ = new char[size_];
+
+  // The socket should start out readable but not writable.
+  socket_->SignalReadEvent.connect(this, &AsyncUDPSocket::OnReadEvent);
+  socket_->SignalWriteEvent.connect(this, &AsyncUDPSocket::OnWriteEvent);
+}
+
+AsyncUDPSocket::~AsyncUDPSocket() {
+  delete [] buf_;
+}
+
+SocketAddress AsyncUDPSocket::GetLocalAddress() const {
+  return socket_->GetLocalAddress();
+}
+
+SocketAddress AsyncUDPSocket::GetRemoteAddress() const {
+  return socket_->GetRemoteAddress();
+}
+
+int AsyncUDPSocket::Send(const void *pv, size_t cb) {
+  return socket_->Send(pv, cb);
+}
+
+int AsyncUDPSocket::SendTo(
+    const void *pv, size_t cb, const SocketAddress& addr) {
+  return socket_->SendTo(pv, cb, addr);
+}
+
+int AsyncUDPSocket::Close() {
+  return socket_->Close();
+}
+
+AsyncUDPSocket::State AsyncUDPSocket::GetState() const {
+  return STATE_BOUND;
+}
+
+int AsyncUDPSocket::GetOption(Socket::Option opt, int* value) {
+  return socket_->GetOption(opt, value);
+}
+
+int AsyncUDPSocket::SetOption(Socket::Option opt, int value) {
+  return socket_->SetOption(opt, value);
+}
+
+int AsyncUDPSocket::GetError() const {
+  return socket_->GetError();
+}
+
+void AsyncUDPSocket::SetError(int error) {
+  return socket_->SetError(error);
+}
+
+void AsyncUDPSocket::OnReadEvent(AsyncSocket* socket) {
+  ASSERT(socket_.get() == socket);
+
+  SocketAddress remote_addr;
+  int len = socket_->RecvFrom(buf_, size_, &remote_addr);
+  if (len < 0) {
+    // An error here typically means we got an ICMP error in response to our
+    // send datagram, indicating the remote address was unreachable.
+    // When doing ICE, this kind of thing will often happen.
+    // TODO: Do something better like forwarding the error to the user.
+    SocketAddress local_addr = socket_->GetLocalAddress();
+    LOG(LS_INFO) << "AsyncUDPSocket[" << local_addr.ToSensitiveString() << "] "
+                 << "receive failed with error " << socket_->GetError();
+    return;
+  }
+
+  // TODO: Make sure that we got all of the packet.
+  // If we did not, then we should resize our buffer to be large enough.
+  SignalReadPacket(this, buf_, (size_t)len, remote_addr);
+}
+
+void AsyncUDPSocket::OnWriteEvent(AsyncSocket* socket) {
+  SignalReadyToSend(this);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/asyncudpsocket.h b/talk/base/asyncudpsocket.h
new file mode 100644
index 0000000..1bf2ad2
--- /dev/null
+++ b/talk/base/asyncudpsocket.h
@@ -0,0 +1,78 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_ASYNCUDPSOCKET_H_
+#define TALK_BASE_ASYNCUDPSOCKET_H_
+
+#include "talk/base/asyncpacketsocket.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socketfactory.h"
+
+namespace talk_base {
+
+// Provides the ability to receive packets asynchronously.  Sends are not
+// buffered since it is acceptable to drop packets under high load.
+class AsyncUDPSocket : public AsyncPacketSocket {
+ public:
+  // Binds |socket| and creates AsyncUDPSocket for it. Takes ownership
+  // of |socket|. Returns NULL if bind() fails (|socket| is destroyed
+  // in that case).
+  static AsyncUDPSocket* Create(AsyncSocket* socket,
+                                const SocketAddress& bind_address);
+  // Creates a new socket for sending asynchronous UDP packets using an
+  // asynchronous socket from the given factory.
+  static AsyncUDPSocket* Create(SocketFactory* factory,
+                                const SocketAddress& bind_address);
+  explicit AsyncUDPSocket(AsyncSocket* socket);
+  virtual ~AsyncUDPSocket();
+
+  virtual SocketAddress GetLocalAddress() const;
+  virtual SocketAddress GetRemoteAddress() const;
+  virtual int Send(const void *pv, size_t cb);
+  virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr);
+  virtual int Close();
+
+  virtual State GetState() const;
+  virtual int GetOption(Socket::Option opt, int* value);
+  virtual int SetOption(Socket::Option opt, int value);
+  virtual int GetError() const;
+  virtual void SetError(int error);
+
+ private:
+  // Called when the underlying socket is ready to be read from.
+  void OnReadEvent(AsyncSocket* socket);
+  // Called when the underlying socket is ready to send.
+  void OnWriteEvent(AsyncSocket* socket);
+
+  scoped_ptr<AsyncSocket> socket_;
+  char* buf_;
+  size_t size_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_ASYNCUDPSOCKET_H_
diff --git a/talk/base/asyncudpsocket_unittest.cc b/talk/base/asyncudpsocket_unittest.cc
new file mode 100644
index 0000000..562577a
--- /dev/null
+++ b/talk/base/asyncudpsocket_unittest.cc
@@ -0,0 +1,70 @@
+/*
+ * libjingle
+ * Copyright 2004--2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/asyncudpsocket.h"
+#include "talk/base/gunit.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/virtualsocketserver.h"
+
+namespace talk_base {
+
+class AsyncUdpSocketTest
+    : public testing::Test,
+      public sigslot::has_slots<> {
+ public:
+  AsyncUdpSocketTest()
+      : pss_(new talk_base::PhysicalSocketServer),
+        vss_(new talk_base::VirtualSocketServer(pss_.get())),
+        socket_(vss_->CreateAsyncSocket(SOCK_DGRAM)),
+        udp_socket_(new AsyncUDPSocket(socket_)),
+        ready_to_send_(false) {
+    udp_socket_->SignalReadyToSend.connect(this,
+                                           &AsyncUdpSocketTest::OnReadyToSend);
+  }
+
+  void OnReadyToSend(talk_base::AsyncPacketSocket* socket) {
+    ready_to_send_ = true;
+  }
+
+ protected:
+  scoped_ptr<PhysicalSocketServer> pss_;
+  scoped_ptr<VirtualSocketServer> vss_;
+  AsyncSocket* socket_;
+  scoped_ptr<AsyncUDPSocket> udp_socket_;
+  bool ready_to_send_;
+};
+
+TEST_F(AsyncUdpSocketTest, OnWriteEvent) {
+  EXPECT_FALSE(ready_to_send_);
+  socket_->SignalWriteEvent(socket_);
+  EXPECT_TRUE(ready_to_send_);
+}
+
+}  // namespace talk_base
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
new file mode 100644
index 0000000..02cbaad
--- /dev/null
+++ b/talk/base/autodetectproxy.cc
@@ -0,0 +1,290 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/autodetectproxy.h"
+#include "talk/base/httpcommon.h"
+#include "talk/base/httpcommon-inl.h"
+#include "talk/base/nethelpers.h"
+
+namespace talk_base {
+
+static const ProxyType TEST_ORDER[] = {
+  PROXY_HTTPS, PROXY_SOCKS5, PROXY_UNKNOWN
+};
+
+static const int kSavedStringLimit = 128;
+
+static void SaveStringToStack(char *dst,
+                              const std::string &src,
+                              size_t dst_size) {
+  strncpy(dst, src.c_str(), dst_size - 1);
+  dst[dst_size - 1] = '\0';
+}
+
+AutoDetectProxy::AutoDetectProxy(const std::string& user_agent)
+    : agent_(user_agent), resolver_(NULL), socket_(NULL), next_(0) {
+}
+
+AutoDetectProxy::~AutoDetectProxy() {
+  if (resolver_) {
+    resolver_->Destroy(false);
+  }
+}
+
+void AutoDetectProxy::DoWork() {
+  // TODO: Try connecting to server_url without proxy first here?
+  if (!server_url_.empty()) {
+    LOG(LS_INFO) << "GetProxySettingsForUrl(" << server_url_ << ") - start";
+    GetProxyForUrl(agent_.c_str(), server_url_.c_str(), &proxy_);
+    LOG(LS_INFO) << "GetProxySettingsForUrl - stop";
+  }
+  Url<char> url(proxy_.address.HostAsURIString());
+  if (url.valid()) {
+    LOG(LS_WARNING) << "AutoDetectProxy removing http prefix on proxy host";
+    proxy_.address.SetIP(url.host());
+  }
+  LOG(LS_INFO) << "AutoDetectProxy found proxy at " << proxy_.address;
+  if (proxy_.type == PROXY_UNKNOWN) {
+    LOG(LS_INFO) << "AutoDetectProxy initiating proxy classification";
+    Next();
+    // Process I/O until Stop()
+    Thread::Current()->ProcessMessages(kForever);
+    // Clean up the autodetect socket, from the thread that created it
+    delete socket_;
+  }
+  // TODO: If we found a proxy, try to use it to verify that it
+  // works by sending a request to server_url. This could either be
+  // done here or by the HttpPortAllocator.
+}
+
+void AutoDetectProxy::OnMessage(Message *msg) {
+  if (MSG_TIMEOUT == msg->message_id) {
+    OnCloseEvent(socket_, ETIMEDOUT);
+  } else {
+    // This must be the ST_MSG_WORKER_DONE message that deletes the
+    // AutoDetectProxy object. We have observed crashes within this stack that
+    // seem to be highly reproducible for a small subset of users and thus are
+    // probably correlated with a specific proxy setting, so copy potentially
+    // relevant information onto the stack to make it available in Windows
+    // minidumps.
+
+    // Save the user agent and the number of auto-detection passes that we
+    // needed.
+    char agent[kSavedStringLimit];
+    SaveStringToStack(agent, agent_, sizeof agent);
+
+    int next = next_;
+
+    // Now the detected proxy config (minus the password field, which could be
+    // sensitive).
+    ProxyType type = proxy().type;
+
+    char address_hostname[kSavedStringLimit];
+    SaveStringToStack(address_hostname,
+                      proxy().address.hostname(),
+                      sizeof address_hostname);
+
+    IPAddress address_ip = proxy().address.ipaddr();
+
+    uint16 address_port = proxy().address.port();
+
+    char autoconfig_url[kSavedStringLimit];
+    SaveStringToStack(autoconfig_url,
+                      proxy().autoconfig_url,
+                      sizeof autoconfig_url);
+
+    bool autodetect = proxy().autodetect;
+
+    char bypass_list[kSavedStringLimit];
+    SaveStringToStack(bypass_list, proxy().bypass_list, sizeof bypass_list);
+
+    char username[kSavedStringLimit];
+    SaveStringToStack(username, proxy().username, sizeof username);
+
+    SignalThread::OnMessage(msg);
+
+    // Log the gathered data at a log level that will never actually be enabled
+    // so that the compiler is forced to retain the data on the stack.
+    LOG(LS_SENSITIVE) << agent << " " << next << " " << type << " "
+                      << address_hostname << " " << address_ip << " "
+                      << address_port << " " << autoconfig_url << " "
+                      << autodetect << " " << bypass_list << " " << username;
+  }
+}
+
+void AutoDetectProxy::OnResolveResult(SignalThread* thread) {
+  if (thread != resolver_) {
+    return;
+  }
+  int error = resolver_->error();
+  if (error == 0) {
+    LOG(LS_VERBOSE) << "Resolved " << proxy_.address << " to "
+                    << resolver_->address();
+    proxy_.address = resolver_->address();
+    DoConnect();
+  } else {
+    LOG(LS_INFO) << "Failed to resolve " << resolver_->address();
+    resolver_->Destroy(false);
+    resolver_ = NULL;
+    proxy_.address = SocketAddress();
+    Thread::Current()->Post(this, MSG_TIMEOUT);
+  }
+}
+
+void AutoDetectProxy::Next() {
+  if (TEST_ORDER[next_] >= PROXY_UNKNOWN) {
+    Complete(PROXY_UNKNOWN);
+    return;
+  }
+
+  LOG(LS_VERBOSE) << "AutoDetectProxy connecting to "
+                  << proxy_.address.ToSensitiveString();
+
+  if (socket_) {
+    Thread::Current()->Clear(this, MSG_TIMEOUT);
+    socket_->Close();
+    Thread::Current()->Dispose(socket_);
+    socket_ = NULL;
+  }
+  int timeout = 2000;
+  if (proxy_.address.IsUnresolvedIP()) {
+    // Launch an asyncresolver. This thread will spin waiting for it.
+    timeout += 2000;
+    if (!resolver_) {
+      resolver_ = new AsyncResolver();
+    }
+    resolver_->set_address(proxy_.address);
+    resolver_->SignalWorkDone.connect(this,
+                                      &AutoDetectProxy::OnResolveResult);
+    resolver_->Start();
+  } else {
+    DoConnect();
+  }
+  Thread::Current()->PostDelayed(timeout, this, MSG_TIMEOUT);
+}
+
+void AutoDetectProxy::DoConnect() {
+  if (resolver_) {
+    resolver_->Destroy(false);
+    resolver_ = NULL;
+  }
+  socket_ =
+      Thread::Current()->socketserver()->CreateAsyncSocket(
+          proxy_.address.family(), SOCK_STREAM);
+  if (!socket_) {
+    LOG(LS_VERBOSE) << "Unable to create socket for " << proxy_.address;
+    return;
+  }
+  socket_->SignalConnectEvent.connect(this, &AutoDetectProxy::OnConnectEvent);
+  socket_->SignalReadEvent.connect(this, &AutoDetectProxy::OnReadEvent);
+  socket_->SignalCloseEvent.connect(this, &AutoDetectProxy::OnCloseEvent);
+  socket_->Connect(proxy_.address);
+}
+
+void AutoDetectProxy::Complete(ProxyType type) {
+  Thread::Current()->Clear(this, MSG_TIMEOUT);
+  if (socket_) {
+    socket_->Close();
+  }
+
+  proxy_.type = type;
+  LoggingSeverity sev = (proxy_.type == PROXY_UNKNOWN) ? LS_ERROR : LS_INFO;
+  LOG_V(sev) << "AutoDetectProxy detected "
+             << proxy_.address.ToSensitiveString()
+             << " as type " << proxy_.type;
+
+  Thread::Current()->Quit();
+}
+
+void AutoDetectProxy::OnConnectEvent(AsyncSocket * socket) {
+  std::string probe;
+
+  switch (TEST_ORDER[next_]) {
+    case PROXY_HTTPS:
+      probe.assign("CONNECT www.google.com:443 HTTP/1.0\r\n"
+                   "User-Agent: ");
+      probe.append(agent_);
+      probe.append("\r\n"
+                   "Host: www.google.com\r\n"
+                   "Content-Length: 0\r\n"
+                   "Proxy-Connection: Keep-Alive\r\n"
+                   "\r\n");
+      break;
+    case PROXY_SOCKS5:
+      probe.assign("\005\001\000", 3);
+      break;
+    default:
+      ASSERT(false);
+      return;
+  }
+
+  LOG(LS_VERBOSE) << "AutoDetectProxy probing type " << TEST_ORDER[next_]
+                  << " sending " << probe.size() << " bytes";
+  socket_->Send(probe.data(), probe.size());
+}
+
+void AutoDetectProxy::OnReadEvent(AsyncSocket * socket) {
+  char data[257];
+  int len = socket_->Recv(data, 256);
+  if (len > 0) {
+    data[len] = 0;
+    LOG(LS_VERBOSE) << "AutoDetectProxy read " << len << " bytes";
+  }
+
+  switch (TEST_ORDER[next_]) {
+    case PROXY_HTTPS:
+      if ((len >= 2) && (data[0] == '\x05')) {
+        Complete(PROXY_SOCKS5);
+        return;
+      }
+      if ((len >= 5) && (strncmp(data, "HTTP/", 5) == 0)) {
+        Complete(PROXY_HTTPS);
+        return;
+      }
+      break;
+    case PROXY_SOCKS5:
+      if ((len >= 2) && (data[0] == '\x05')) {
+        Complete(PROXY_SOCKS5);
+        return;
+      }
+      break;
+    default:
+      ASSERT(false);
+      return;
+  }
+
+  ++next_;
+  Next();
+}
+
+void AutoDetectProxy::OnCloseEvent(AsyncSocket * socket, int error) {
+  LOG(LS_VERBOSE) << "AutoDetectProxy closed with error: " << error;
+  ++next_;
+  Next();
+}
+
+}  // namespace talk_base
diff --git a/talk/base/autodetectproxy.h b/talk/base/autodetectproxy.h
new file mode 100644
index 0000000..a6ad3d1
--- /dev/null
+++ b/talk/base/autodetectproxy.h
@@ -0,0 +1,106 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_AUTODETECTPROXY_H_
+#define TALK_BASE_AUTODETECTPROXY_H_
+
+#include <string>
+
+#include "talk/base/constructormagic.h"
+#include "talk/base/cryptstring.h"
+#include "talk/base/proxydetect.h"
+#include "talk/base/proxyinfo.h"
+#include "talk/base/signalthread.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// AutoDetectProxy
+///////////////////////////////////////////////////////////////////////////////
+
+class AsyncResolver;
+class AsyncSocket;
+
+class AutoDetectProxy : public SignalThread {
+ public:
+  explicit AutoDetectProxy(const std::string& user_agent);
+
+  const ProxyInfo& proxy() const { return proxy_; }
+
+  void set_server_url(const std::string& url) {
+    server_url_ = url;
+  }
+  void set_proxy(const SocketAddress& proxy) {
+    proxy_.type = PROXY_UNKNOWN;
+    proxy_.address = proxy;
+  }
+  void set_auth_info(bool use_auth, const std::string& username,
+                     const CryptString& password) {
+    if (use_auth) {
+      proxy_.username = username;
+      proxy_.password = password;
+    }
+  }
+  // Default implementation of GetProxySettingsForUrl. Override for special
+  // implementation.
+  virtual bool GetProxyForUrl(const char* agent, const char* url,
+                              talk_base::ProxyInfo* proxy) {
+    return GetProxySettingsForUrl(agent, url, proxy, true);
+  }
+  enum { MSG_TIMEOUT = SignalThread::ST_MSG_FIRST_AVAILABLE,
+         ADP_MSG_FIRST_AVAILABLE};
+
+ protected:
+  virtual ~AutoDetectProxy();
+
+  // SignalThread Interface
+  virtual void DoWork();
+  virtual void OnMessage(Message *msg);
+
+  void Next();
+  void Complete(ProxyType type);
+
+  void OnConnectEvent(AsyncSocket * socket);
+  void OnReadEvent(AsyncSocket * socket);
+  void OnCloseEvent(AsyncSocket * socket, int error);
+  void OnResolveResult(SignalThread* thread);
+  void DoConnect();
+
+ private:
+  std::string agent_;
+  std::string server_url_;
+  ProxyInfo proxy_;
+  AsyncResolver* resolver_;
+  AsyncSocket* socket_;
+  int next_;
+
+  DISALLOW_IMPLICIT_CONSTRUCTORS(AutoDetectProxy);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_AUTODETECTPROXY_H_
diff --git a/talk/base/autodetectproxy_unittest.cc b/talk/base/autodetectproxy_unittest.cc
new file mode 100644
index 0000000..3fca4c6
--- /dev/null
+++ b/talk/base/autodetectproxy_unittest.cc
@@ -0,0 +1,141 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/autodetectproxy.h"
+#include "talk/base/gunit.h"
+#include "talk/base/httpcommon.h"
+#include "talk/base/httpcommon-inl.h"
+
+namespace talk_base {
+
+static const char kUserAgent[] = "";
+static const char kPath[] = "/";
+static const char kHost[] = "relay.google.com";
+static const uint16 kPort = 443;
+static const bool kSecure = true;
+// Each of the two stages in AutoDetectProxy has a 2-second time-out, so 5
+// seconds total should be enough.
+static const int kTimeoutMs = 5000;
+
+class AutoDetectProxyTest : public testing::Test, public sigslot::has_slots<> {
+ public:
+  AutoDetectProxyTest() : auto_detect_proxy_(NULL), done_(false) {}
+
+ protected:
+  bool Create(const std::string &user_agent,
+              const std::string &path,
+              const std::string &host,
+              uint16 port,
+              bool secure,
+              bool startnow) {
+    auto_detect_proxy_ = new AutoDetectProxy(user_agent);
+    EXPECT_TRUE(auto_detect_proxy_ != NULL);
+    if (!auto_detect_proxy_) {
+      return false;
+    }
+    Url<char> host_url(path, host, port);
+    host_url.set_secure(secure);
+    auto_detect_proxy_->set_server_url(host_url.url());
+    auto_detect_proxy_->SignalWorkDone.connect(
+        this,
+        &AutoDetectProxyTest::OnWorkDone);
+    if (startnow) {
+      auto_detect_proxy_->Start();
+    }
+    return true;
+  }
+
+  bool Run(int timeout_ms) {
+    EXPECT_TRUE_WAIT(done_, timeout_ms);
+    return done_;
+  }
+
+  void SetProxy(const SocketAddress& proxy) {
+    auto_detect_proxy_->set_proxy(proxy);
+  }
+
+  void Start() {
+    auto_detect_proxy_->Start();
+  }
+
+  void TestCopesWithProxy(const SocketAddress& proxy) {
+    // Tests that at least autodetect doesn't crash for a given proxy address.
+    ASSERT_TRUE(Create(kUserAgent,
+                       kPath,
+                       kHost,
+                       kPort,
+                       kSecure,
+                       false));
+    SetProxy(proxy);
+    Start();
+    ASSERT_TRUE(Run(kTimeoutMs));
+  }
+
+ private:
+  void OnWorkDone(talk_base::SignalThread *thread) {
+    AutoDetectProxy *auto_detect_proxy =
+        static_cast<talk_base::AutoDetectProxy *>(thread);
+    EXPECT_TRUE(auto_detect_proxy == auto_detect_proxy_);
+    auto_detect_proxy_ = NULL;
+    auto_detect_proxy->Release();
+    done_ = true;
+  }
+
+  AutoDetectProxy *auto_detect_proxy_;
+  bool done_;
+};
+
+TEST_F(AutoDetectProxyTest, TestDetectUnresolvedProxy) {
+  TestCopesWithProxy(talk_base::SocketAddress("localhost", 9999));
+}
+
+TEST_F(AutoDetectProxyTest, TestDetectUnresolvableProxy) {
+  TestCopesWithProxy(talk_base::SocketAddress("invalid", 9999));
+}
+
+TEST_F(AutoDetectProxyTest, TestDetectIPv6Proxy) {
+  TestCopesWithProxy(talk_base::SocketAddress("::1", 9999));
+}
+
+TEST_F(AutoDetectProxyTest, TestDetectIPv4Proxy) {
+  TestCopesWithProxy(talk_base::SocketAddress("127.0.0.1", 9999));
+}
+
+// Test that proxy detection completes successfully. (Does not actually verify
+// the correct detection result since we don't know what proxy to expect on an
+// arbitrary machine.)
+TEST_F(AutoDetectProxyTest, TestProxyDetection) {
+  ASSERT_TRUE(Create(kUserAgent,
+                     kPath,
+                     kHost,
+                     kPort,
+                     kSecure,
+                     true));
+  ASSERT_TRUE(Run(kTimeoutMs));
+}
+
+}  // namespace talk_base
diff --git a/talk/base/bandwidthsmoother.cc b/talk/base/bandwidthsmoother.cc
new file mode 100644
index 0000000..3916488
--- /dev/null
+++ b/talk/base/bandwidthsmoother.cc
@@ -0,0 +1,101 @@
+/*
+ * 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,
+                                     double min_sample_count_percent)
+    : 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),
+      min_sample_count_percent_(
+          talk_base::_min(1.0,
+                          talk_base::_max(0.0, min_sample_count_percent))) {
+}
+
+// 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);
+
+  if (accumulator_.count() < static_cast<size_t>(
+          accumulator_.max_count() * min_sample_count_percent_)) {
+    // We have not collected enough samples yet.
+    return false;
+  }
+
+  // Replace bandwidth with the mean of sampled bandwidths.
+  const int mean_bandwidth = accumulator_.ComputeMean();
+
+  if (mean_bandwidth < bandwidth_estimation_) {
+    time_at_last_change_ = sample_time;
+    bandwidth_estimation_ = mean_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 (mean_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_ must be zero. Assume a jump from zero to a
+      // positive bandwidth means we have regained connectivity.
+      bandwidth_estimation_ = mean_bandwidth;
+    } 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..f63a0f5
--- /dev/null
+++ b/talk/base/bandwidthsmoother.h
@@ -0,0 +1,76 @@
+/*
+ * 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.
+// We retain the initial bandwidth guess as our current bandwidth estimation
+// until we have received (min_sample_count_percent * samples_count_to_average)
+// number of samples. Min_sample_count_percent must be in range [0, 1].
+class BandwidthSmoother {
+ public:
+  BandwidthSmoother(int initial_bandwidth_guess,
+                    uint32 time_between_increase,
+                    double percent_increase,
+                    size_t samples_count_to_average,
+                    double min_sample_count_percent);
+
+  // 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_;
+  double min_sample_count_percent_;
+};
+
+}  // 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..3ba846f
--- /dev/null
+++ b/talk/base/bandwidthsmoother_unittest.cc
@@ -0,0 +1,133 @@
+/*
+ * 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 {
+
+static const int kTimeBetweenIncrease = 10;
+static const double kPercentIncrease = 1.1;
+static const size_t kSamplesCountToAverage = 2;
+static const double kMinSampleCountPercent = 1.0;
+
+TEST(BandwidthSmootherTest, TestSampleIncrease) {
+  BandwidthSmoother mon(1000,  // initial_bandwidth_guess
+                        kTimeBetweenIncrease,
+                        kPercentIncrease,
+                        kSamplesCountToAverage,
+                        kMinSampleCountPercent);
+
+  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(BandwidthSmootherTest, TestSampleIncreaseFromZero) {
+  BandwidthSmoother mon(0,  // initial_bandwidth_guess
+                        kTimeBetweenIncrease,
+                        kPercentIncrease,
+                        kSamplesCountToAverage,
+                        kMinSampleCountPercent);
+
+  const int kBandwidthSample = 1000;
+  EXPECT_EQ(0, mon.get_bandwidth_estimation());
+  EXPECT_FALSE(mon.Sample(9, kBandwidthSample));
+  EXPECT_TRUE(mon.Sample(10, kBandwidthSample));
+  EXPECT_EQ(kBandwidthSample, mon.get_bandwidth_estimation());
+}
+
+TEST(BandwidthSmootherTest, TestSampleDecrease) {
+  BandwidthSmoother mon(1000,  // initial_bandwidth_guess
+                        kTimeBetweenIncrease,
+                        kPercentIncrease,
+                        kSamplesCountToAverage,
+                        kMinSampleCountPercent);
+
+  const int kBandwidthSample = 999;
+  EXPECT_EQ(1000, mon.get_bandwidth_estimation());
+  EXPECT_FALSE(mon.Sample(1, kBandwidthSample));
+  EXPECT_EQ(1000, mon.get_bandwidth_estimation());
+  EXPECT_TRUE(mon.Sample(2, kBandwidthSample));
+  EXPECT_EQ(kBandwidthSample, mon.get_bandwidth_estimation());
+}
+
+TEST(BandwidthSmootherTest, TestSampleTooFewSamples) {
+  BandwidthSmoother mon(1000,  // initial_bandwidth_guess
+                        kTimeBetweenIncrease,
+                        kPercentIncrease,
+                        10,  // 10 samples.
+                        0.5);  // 5 min samples.
+
+  const int kBandwidthSample = 500;
+  EXPECT_EQ(1000, mon.get_bandwidth_estimation());
+  EXPECT_FALSE(mon.Sample(1, kBandwidthSample));
+  EXPECT_FALSE(mon.Sample(2, kBandwidthSample));
+  EXPECT_FALSE(mon.Sample(3, kBandwidthSample));
+  EXPECT_FALSE(mon.Sample(4, kBandwidthSample));
+  EXPECT_EQ(1000, mon.get_bandwidth_estimation());
+  EXPECT_TRUE(mon.Sample(5, kBandwidthSample));
+  EXPECT_EQ(kBandwidthSample, mon.get_bandwidth_estimation());
+}
+
+TEST(BandwidthSmootherTest, TestSampleRollover) {
+  const int kHugeBandwidth = 2000000000;  // > INT_MAX/1.1
+  BandwidthSmoother mon(kHugeBandwidth,
+                        kTimeBetweenIncrease,
+                        kPercentIncrease,
+                        kSamplesCountToAverage,
+                        kMinSampleCountPercent);
+
+  EXPECT_FALSE(mon.Sample(10, INT_MAX));
+  EXPECT_FALSE(mon.Sample(11, INT_MAX));
+  EXPECT_EQ(kHugeBandwidth, mon.get_bandwidth_estimation());
+}
+
+TEST(BandwidthSmootherTest, TestSampleNegative) {
+  BandwidthSmoother mon(1000,  // initial_bandwidth_guess
+                        kTimeBetweenIncrease,
+                        kPercentIncrease,
+                        kSamplesCountToAverage,
+                        kMinSampleCountPercent);
+
+  EXPECT_FALSE(mon.Sample(10, -1));
+  EXPECT_FALSE(mon.Sample(11, -1));
+  EXPECT_EQ(1000, mon.get_bandwidth_estimation());
+}
+
+}  // namespace talk_base
diff --git a/talk/base/base64.cc b/talk/base/base64.cc
new file mode 100644
index 0000000..7765f10
--- /dev/null
+++ b/talk/base/base64.cc
@@ -0,0 +1,259 @@
+
+//*********************************************************************
+//* Base64 - a simple base64 encoder and decoder.
+//*
+//*     Copyright (c) 1999, Bob Withers - bwit@pobox.com
+//*
+//* This code may be freely used for any purpose, either personal
+//* or commercial, provided the authors copyright notice remains
+//* intact.
+//*
+//* Enhancements by Stanley Yamane:
+//*     o reverse lookup table for the decode function
+//*     o reserve string buffer space in advance
+//*
+//*********************************************************************
+
+#include "talk/base/base64.h"
+
+#include <string.h>
+
+#include "talk/base/common.h"
+
+using std::string;
+using std::vector;
+
+namespace talk_base {
+
+static const char kPad = '=';
+static const unsigned char pd = 0xFD;  // Padding
+static const unsigned char sp = 0xFE;  // Whitespace
+static const unsigned char il = 0xFF;  // Illegal base64 character
+
+const char Base64::Base64Table[] =
+// 0000000000111111111122222222223333333333444444444455555555556666
+// 0123456789012345678901234567890123456789012345678901234567890123
+  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+// Decode Table gives the index of any valid base64 character in the
+// Base64 table
+// 65 == A, 97 == a, 48 == 0, 43 == +, 47 == /
+
+const unsigned char Base64::DecodeTable[] = {
+// 0  1  2  3  4  5  6  7  8  9
+  il,il,il,il,il,il,il,il,il,sp,  //   0 -   9
+  sp,sp,sp,sp,il,il,il,il,il,il,  //  10 -  19
+  il,il,il,il,il,il,il,il,il,il,  //  20 -  29
+  il,il,sp,il,il,il,il,il,il,il,  //  30 -  39
+  il,il,il,62,il,il,il,63,52,53,  //  40 -  49
+  54,55,56,57,58,59,60,61,il,il,  //  50 -  59
+  il,pd,il,il,il, 0, 1, 2, 3, 4,  //  60 -  69
+   5, 6, 7, 8, 9,10,11,12,13,14,  //  70 -  79
+  15,16,17,18,19,20,21,22,23,24,  //  80 -  89
+  25,il,il,il,il,il,il,26,27,28,  //  90 -  99
+  29,30,31,32,33,34,35,36,37,38,  // 100 - 109
+  39,40,41,42,43,44,45,46,47,48,  // 110 - 119
+  49,50,51,il,il,il,il,il,il,il,  // 120 - 129
+  il,il,il,il,il,il,il,il,il,il,  // 130 - 139
+  il,il,il,il,il,il,il,il,il,il,  // 140 - 149
+  il,il,il,il,il,il,il,il,il,il,  // 150 - 159
+  il,il,il,il,il,il,il,il,il,il,  // 160 - 169
+  il,il,il,il,il,il,il,il,il,il,  // 170 - 179
+  il,il,il,il,il,il,il,il,il,il,  // 180 - 189
+  il,il,il,il,il,il,il,il,il,il,  // 190 - 199
+  il,il,il,il,il,il,il,il,il,il,  // 200 - 209
+  il,il,il,il,il,il,il,il,il,il,  // 210 - 219
+  il,il,il,il,il,il,il,il,il,il,  // 220 - 229
+  il,il,il,il,il,il,il,il,il,il,  // 230 - 239
+  il,il,il,il,il,il,il,il,il,il,  // 240 - 249
+  il,il,il,il,il,il               // 250 - 255
+};
+
+bool Base64::IsBase64Char(char ch) {
+  return (('A' <= ch) && (ch <= 'Z')) ||
+         (('a' <= ch) && (ch <= 'z')) ||
+         (('0' <= ch) && (ch <= '9')) ||
+         (ch == '+') || (ch == '/');
+}
+
+bool Base64::GetNextBase64Char(char ch, char* next_ch) {
+  if (next_ch == NULL) {
+    return false;
+  }
+  const char* p = strchr(Base64Table, ch);
+  if (!p)
+    return false;
+  ++p;
+  *next_ch = (*p) ? *p : Base64Table[0];
+  return true;
+}
+
+bool Base64::IsBase64Encoded(const std::string& str) {
+  for (size_t i = 0; i < str.size(); ++i) {
+    if (!IsBase64Char(str.at(i)))
+      return false;
+  }
+  return true;
+}
+
+void Base64::EncodeFromArray(const void* data, size_t len, string* result) {
+  ASSERT(NULL != result);
+  result->clear();
+  result->resize(((len + 2) / 3) * 4);
+  const unsigned char* byte_data = static_cast<const unsigned char*>(data);
+
+  unsigned char c;
+  size_t i = 0;
+  size_t dest_ix = 0;
+  while (i < len) {
+    c = (byte_data[i] >> 2) & 0x3f;
+    (*result)[dest_ix++] = Base64Table[c];
+
+    c = (byte_data[i] << 4) & 0x3f;
+    if (++i < len) {
+      c |= (byte_data[i] >> 4) & 0x0f;
+    }
+    (*result)[dest_ix++] = Base64Table[c];
+
+    if (i < len) {
+      c = (byte_data[i] << 2) & 0x3f;
+      if (++i < len) {
+        c |= (byte_data[i] >> 6) & 0x03;
+      }
+      (*result)[dest_ix++] = Base64Table[c];
+    } else {
+      (*result)[dest_ix++] = kPad;
+    }
+
+    if (i < len) {
+      c = byte_data[i] & 0x3f;
+      (*result)[dest_ix++] = Base64Table[c];
+      ++i;
+    } else {
+      (*result)[dest_ix++] = kPad;
+    }
+  }
+}
+
+size_t Base64::GetNextQuantum(DecodeFlags parse_flags, bool illegal_pads,
+                              const char* data, size_t len, size_t* dpos,
+                              unsigned char qbuf[4], bool* padded)
+{
+  size_t byte_len = 0, pad_len = 0, pad_start = 0;
+  for (; (byte_len < 4) && (*dpos < len); ++*dpos) {
+    qbuf[byte_len] = DecodeTable[static_cast<unsigned char>(data[*dpos])];
+    if ((il == qbuf[byte_len]) || (illegal_pads && (pd == qbuf[byte_len]))) {
+      if (parse_flags != DO_PARSE_ANY)
+        break;
+      // Ignore illegal characters
+    } else if (sp == qbuf[byte_len]) {
+      if (parse_flags == DO_PARSE_STRICT)
+        break;
+      // Ignore spaces
+    } else if (pd == qbuf[byte_len]) {
+      if (byte_len < 2) {
+        if (parse_flags != DO_PARSE_ANY)
+          break;
+        // Ignore unexpected padding
+      } else if (byte_len + pad_len >= 4) {
+        if (parse_flags != DO_PARSE_ANY)
+          break;
+        // Ignore extra pads
+      } else {
+        if (1 == ++pad_len) {
+          pad_start = *dpos;
+        }
+      }
+    } else {
+      if (pad_len > 0) {
+        if (parse_flags != DO_PARSE_ANY)
+          break;
+        // Ignore pads which are followed by data
+        pad_len = 0;
+      }
+      ++byte_len;
+    }
+  }
+  for (size_t i = byte_len; i < 4; ++i) {
+    qbuf[i] = 0;
+  }
+  if (4 == byte_len + pad_len) {
+    *padded = true;
+  } else {
+    *padded = false;
+    if (pad_len) {
+      // Roll back illegal padding
+      *dpos = pad_start;
+    }
+  }
+  return byte_len;
+}
+
+bool Base64::DecodeFromArray(const char* data, size_t len, DecodeFlags flags,
+                             string* result, size_t* data_used) {
+  return DecodeFromArrayTemplate<string>(data, len, flags, result, data_used);
+}
+
+bool Base64::DecodeFromArray(const char* data, size_t len, DecodeFlags flags,
+                             vector<char>* result, size_t* data_used) {
+  return DecodeFromArrayTemplate<vector<char> >(data, len, flags, result,
+                                                data_used);
+}
+
+template<typename T>
+bool Base64::DecodeFromArrayTemplate(const char* data, size_t len,
+                                     DecodeFlags flags, T* result,
+                                     size_t* data_used)
+{
+  ASSERT(NULL != result);
+  ASSERT(flags <= (DO_PARSE_MASK | DO_PAD_MASK | DO_TERM_MASK));
+
+  const DecodeFlags parse_flags = flags & DO_PARSE_MASK;
+  const DecodeFlags pad_flags   = flags & DO_PAD_MASK;
+  const DecodeFlags term_flags  = flags & DO_TERM_MASK;
+  ASSERT(0 != parse_flags);
+  ASSERT(0 != pad_flags);
+  ASSERT(0 != term_flags);
+
+  result->clear();
+  result->reserve(len);
+
+  size_t dpos = 0;
+  bool success = true, padded;
+  unsigned char c, qbuf[4];
+  while (dpos < len) {
+    size_t qlen = GetNextQuantum(parse_flags, (DO_PAD_NO == pad_flags),
+                                 data, len, &dpos, qbuf, &padded);
+    c = (qbuf[0] << 2) | ((qbuf[1] >> 4) & 0x3);
+    if (qlen >= 2) {
+      result->push_back(c);
+      c = ((qbuf[1] << 4) & 0xf0) | ((qbuf[2] >> 2) & 0xf);
+      if (qlen >= 3) {
+        result->push_back(c);
+        c = ((qbuf[2] << 6) & 0xc0) | qbuf[3];
+        if (qlen >= 4) {
+          result->push_back(c);
+          c = 0;
+        }
+      }
+    }
+    if (qlen < 4) {
+      if ((DO_TERM_ANY != term_flags) && (0 != c)) {
+        success = false;  // unused bits
+      }
+      if ((DO_PAD_YES == pad_flags) && !padded) {
+        success = false;  // expected padding
+      }
+      break;
+    }
+  }
+  if ((DO_TERM_BUFFER == term_flags) && (dpos != len)) {
+    success = false;  // unused chars
+  }
+  if (data_used) {
+    *data_used = dpos;
+  }
+  return success;
+}
+
+} // namespace talk_base
diff --git a/talk/base/base64.h b/talk/base/base64.h
new file mode 100644
index 0000000..a963515
--- /dev/null
+++ b/talk/base/base64.h
@@ -0,0 +1,104 @@
+
+//*********************************************************************
+//* C_Base64 - a simple base64 encoder and decoder.
+//*
+//*     Copyright (c) 1999, Bob Withers - bwit@pobox.com
+//*
+//* This code may be freely used for any purpose, either personal
+//* or commercial, provided the authors copyright notice remains
+//* intact.
+//*********************************************************************
+
+#ifndef TALK_BASE_BASE64_H__
+#define TALK_BASE_BASE64_H__
+
+#include <string>
+#include <vector>
+
+namespace talk_base {
+
+class Base64
+{
+public:
+  enum DecodeOption {
+    DO_PARSE_STRICT =  1,  // Parse only base64 characters
+    DO_PARSE_WHITE  =  2,  // Parse only base64 and whitespace characters
+    DO_PARSE_ANY    =  3,  // Parse all characters
+    DO_PARSE_MASK   =  3,
+
+    DO_PAD_YES      =  4,  // Padding is required
+    DO_PAD_ANY      =  8,  // Padding is optional
+    DO_PAD_NO       = 12,  // Padding is disallowed
+    DO_PAD_MASK     = 12,
+
+    DO_TERM_BUFFER  = 16,  // Must termiante at end of buffer
+    DO_TERM_CHAR    = 32,  // May terminate at any character boundary
+    DO_TERM_ANY     = 48,  // May terminate at a sub-character bit offset
+    DO_TERM_MASK    = 48,
+
+    // Strictest interpretation
+    DO_STRICT = DO_PARSE_STRICT | DO_PAD_YES | DO_TERM_BUFFER,
+
+    DO_LAX    = DO_PARSE_ANY | DO_PAD_ANY | DO_TERM_CHAR,
+  };
+  typedef int DecodeFlags;
+
+  static bool IsBase64Char(char ch);
+
+  // Get the char next to the |ch| from the Base64Table.
+  // If the |ch| is the last one in the Base64Table then returns
+  // the first one from the table.
+  // Expects the |ch| be a base64 char.
+  // The result will be saved in |next_ch|.
+  // Returns true on success.
+  static bool GetNextBase64Char(char ch, char* next_ch);
+
+  // Determines whether the given string consists entirely of valid base64
+  // encoded characters.
+  static bool IsBase64Encoded(const std::string& str);
+
+  static void EncodeFromArray(const void* data, size_t len,
+                              std::string* result);
+  static bool DecodeFromArray(const char* data, size_t len, DecodeFlags flags,
+                              std::string* result, size_t* data_used);
+  static bool DecodeFromArray(const char* data, size_t len, DecodeFlags flags,
+                              std::vector<char>* result, size_t* data_used);
+
+  // Convenience Methods
+  static inline std::string Encode(const std::string& data) {
+    std::string result;
+    EncodeFromArray(data.data(), data.size(), &result);
+    return result;
+  }
+  static inline std::string Decode(const std::string& data, DecodeFlags flags) {
+    std::string result;
+    DecodeFromArray(data.data(), data.size(), flags, &result, NULL);
+    return result;
+  }
+  static inline bool Decode(const std::string& data, DecodeFlags flags,
+                            std::string* result, size_t* data_used)
+  {
+    return DecodeFromArray(data.data(), data.size(), flags, result, data_used);
+  }
+  static inline bool Decode(const std::string& data, DecodeFlags flags,
+                            std::vector<char>* result, size_t* data_used)
+  {
+    return DecodeFromArray(data.data(), data.size(), flags, result, data_used);
+  }
+
+private:
+  static const char Base64Table[];
+  static const unsigned char DecodeTable[];
+
+  static size_t GetNextQuantum(DecodeFlags parse_flags, bool illegal_pads,
+                               const char* data, size_t len, size_t* dpos,
+                               unsigned char qbuf[4], bool* padded);
+  template<typename T>
+  static bool DecodeFromArrayTemplate(const char* data, size_t len,
+                                      DecodeFlags flags, T* result,
+                                      size_t* data_used);
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_BASE64_H__
diff --git a/talk/base/base64_unittest.cc b/talk/base/base64_unittest.cc
new file mode 100644
index 0000000..20869e4
--- /dev/null
+++ b/talk/base/base64_unittest.cc
@@ -0,0 +1,1018 @@
+/*
+ * 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);
+
+      size_t 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)));
+}
+
+TEST(Base64, GetNextBase64Char) {
+  // The table looks like this:
+  // "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
+  char next_char;
+  EXPECT_TRUE(Base64::GetNextBase64Char('A', &next_char));
+  EXPECT_EQ('B', next_char);
+  EXPECT_TRUE(Base64::GetNextBase64Char('Z', &next_char));
+  EXPECT_EQ('a', next_char);
+  EXPECT_TRUE(Base64::GetNextBase64Char('/', &next_char));
+  EXPECT_EQ('A', next_char);
+  EXPECT_FALSE(Base64::GetNextBase64Char('&', &next_char));
+  EXPECT_FALSE(Base64::GetNextBase64Char('Z', NULL));
+}
diff --git a/talk/base/basicdefs.h b/talk/base/basicdefs.h
new file mode 100644
index 0000000..7829d45
--- /dev/null
+++ b/talk/base/basicdefs.h
@@ -0,0 +1,37 @@
+/*
+ * 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_BASE_BASICDEFS_H_
+#define TALK_BASE_BASICDEFS_H_
+
+#if HAVE_CONFIG_H
+#include "config.h"  // NOLINT
+#endif
+
+#define ARRAY_SIZE(x) (static_cast<int>(sizeof(x) / sizeof(x[0])))
+
+#endif  // TALK_BASE_BASICDEFS_H_
diff --git a/talk/base/basictypes.h b/talk/base/basictypes.h
new file mode 100644
index 0000000..f7f5b66
--- /dev/null
+++ b/talk/base/basictypes.h
@@ -0,0 +1,171 @@
+/*
+ * 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_BASE_BASICTYPES_H_
+#define TALK_BASE_BASICTYPES_H_
+
+#include <stddef.h>  // for NULL, size_t
+
+#if !(defined(_MSC_VER) && (_MSC_VER < 1600))
+#include <stdint.h>  // for uintptr_t
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"  // NOLINT
+#endif
+
+#include "talk/base/constructormagic.h"
+
+#if !defined(INT_TYPES_DEFINED)
+#define INT_TYPES_DEFINED
+#ifdef COMPILER_MSVC
+typedef unsigned __int64 uint64;
+typedef __int64 int64;
+#ifndef INT64_C
+#define INT64_C(x) x ## I64
+#endif
+#ifndef UINT64_C
+#define UINT64_C(x) x ## UI64
+#endif
+#define INT64_F "I64"
+#else  // COMPILER_MSVC
+// On Mac OS X, cssmconfig.h defines uint64 as uint64_t
+// TODO(fbarchard): Use long long for compatibility with chromium on BSD/OSX.
+#if defined(OSX)
+typedef uint64_t uint64;
+typedef int64_t int64;
+#ifndef INT64_C
+#define INT64_C(x) x ## LL
+#endif
+#ifndef UINT64_C
+#define UINT64_C(x) x ## ULL
+#endif
+#define INT64_F "l"
+#elif defined(__LP64__)
+typedef unsigned long uint64;  // NOLINT
+typedef long int64;  // NOLINT
+#ifndef INT64_C
+#define INT64_C(x) x ## L
+#endif
+#ifndef UINT64_C
+#define UINT64_C(x) x ## UL
+#endif
+#define INT64_F "l"
+#else  // __LP64__
+typedef unsigned long long uint64;  // NOLINT
+typedef long long int64;  // NOLINT
+#ifndef INT64_C
+#define INT64_C(x) x ## LL
+#endif
+#ifndef UINT64_C
+#define UINT64_C(x) x ## ULL
+#endif
+#define INT64_F "ll"
+#endif  // __LP64__
+#endif  // COMPILER_MSVC
+typedef unsigned int uint32;
+typedef int int32;
+typedef unsigned short uint16;  // NOLINT
+typedef short int16;  // NOLINT
+typedef unsigned char uint8;
+typedef signed char int8;
+#endif  // INT_TYPES_DEFINED
+
+// Detect compiler is for x86 or x64.
+#if defined(__x86_64__) || defined(_M_X64) || \
+    defined(__i386__) || defined(_M_IX86)
+#define CPU_X86 1
+#endif
+// Detect compiler is for arm.
+#if defined(__arm__) || defined(_M_ARM)
+#define CPU_ARM 1
+#endif
+#if defined(CPU_X86) && defined(CPU_ARM)
+#error CPU_X86 and CPU_ARM both defined.
+#endif
+#if !defined(ARCH_CPU_BIG_ENDIAN) && !defined(ARCH_CPU_LITTLE_ENDIAN)
+// x86, arm or GCC provided __BYTE_ORDER__ macros
+#if CPU_X86 || CPU_ARM ||  \
+  (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+#define ARCH_CPU_LITTLE_ENDIAN
+#elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+#define ARCH_CPU_BIG_ENDIAN
+#else
+#error ARCH_CPU_BIG_ENDIAN or ARCH_CPU_LITTLE_ENDIAN should be defined.
+#endif
+#endif
+#if defined(ARCH_CPU_BIG_ENDIAN) && defined(ARCH_CPU_LITTLE_ENDIAN)
+#error ARCH_CPU_BIG_ENDIAN and ARCH_CPU_LITTLE_ENDIAN both defined.
+#endif
+
+#ifdef WIN32
+typedef int socklen_t;
+#endif
+
+// The following only works for C++
+#ifdef __cplusplus
+namespace talk_base {
+  template<class T> inline T _min(T a, T b) { return (a > b) ? b : a; }
+  template<class T> inline T _max(T a, T b) { return (a < b) ? b : a; }
+
+  // For wait functions that take a number of milliseconds, kForever indicates
+  // unlimited time.
+  const int kForever = -1;
+}
+
+#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)))
+
+// Note: UNUSED is also defined in common.h
+#ifndef UNUSED
+#define UNUSED(x) Unused(static_cast<const void*>(&x))
+#define UNUSED2(x, y) Unused(static_cast<const void*>(&x)); \
+    Unused(static_cast<const void*>(&y))
+#define UNUSED3(x, y, z) Unused(static_cast<const void*>(&x)); \
+    Unused(static_cast<const void*>(&y)); \
+    Unused(static_cast<const void*>(&z))
+#define UNUSED4(x, y, z, a) Unused(static_cast<const void*>(&x)); \
+    Unused(static_cast<const void*>(&y)); \
+    Unused(static_cast<const void*>(&z)); \
+    Unused(static_cast<const void*>(&a))
+#define UNUSED5(x, y, z, a, b) Unused(static_cast<const void*>(&x)); \
+    Unused(static_cast<const void*>(&y)); \
+    Unused(static_cast<const void*>(&z)); \
+    Unused(static_cast<const void*>(&a)); \
+    Unused(static_cast<const void*>(&b))
+inline void Unused(const void*) {}
+#endif  // UNUSED
+
+// 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  // __cplusplus
+#endif  // TALK_BASE_BASICTYPES_H_
diff --git a/talk/base/basictypes_unittest.cc b/talk/base/basictypes_unittest.cc
new file mode 100644
index 0000000..caf1115
--- /dev/null
+++ b/talk/base/basictypes_unittest.cc
@@ -0,0 +1,92 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/basictypes.h"
+
+#include "talk/base/gunit.h"
+
+namespace talk_base {
+
+TEST(BasicTypesTest, Endian) {
+  uint16 v16 = 0x1234u;
+  uint8 first_byte = *reinterpret_cast<uint8*>(&v16);
+#if defined(ARCH_CPU_LITTLE_ENDIAN)
+  EXPECT_EQ(0x34u, first_byte);
+#elif defined(ARCH_CPU_BIG_ENDIAN)
+  EXPECT_EQ(0x12u, first_byte);
+#endif
+}
+
+TEST(BasicTypesTest, SizeOfTypes) {
+  int8 i8 = -1;
+  uint8 u8 = 1u;
+  int16 i16 = -1;
+  uint16 u16 = 1u;
+  int32 i32 = -1;
+  uint32 u32 = 1u;
+  int64 i64 = -1;
+  uint64 u64 = 1u;
+  EXPECT_EQ(1u, sizeof(i8));
+  EXPECT_EQ(1u, sizeof(u8));
+  EXPECT_EQ(2u, sizeof(i16));
+  EXPECT_EQ(2u, sizeof(u16));
+  EXPECT_EQ(4u, sizeof(i32));
+  EXPECT_EQ(4u, sizeof(u32));
+  EXPECT_EQ(8u, sizeof(i64));
+  EXPECT_EQ(8u, sizeof(u64));
+  EXPECT_GT(0, i8);
+  EXPECT_LT(0u, u8);
+  EXPECT_GT(0, i16);
+  EXPECT_LT(0u, u16);
+  EXPECT_GT(0, i32);
+  EXPECT_LT(0u, u32);
+  EXPECT_GT(0, i64);
+  EXPECT_LT(0u, u64);
+}
+
+TEST(BasicTypesTest, SizeOfConstants) {
+  EXPECT_EQ(8u, sizeof(INT64_C(0)));
+  EXPECT_EQ(8u, sizeof(UINT64_C(0)));
+  EXPECT_EQ(8u, sizeof(INT64_C(0x1234567887654321)));
+  EXPECT_EQ(8u, sizeof(UINT64_C(0x8765432112345678)));
+}
+
+// Test CPU_ macros
+#if !defined(CPU_ARM) && defined(__arm__)
+#error expected CPU_ARM to be defined.
+#endif
+#if !defined(CPU_X86) && (defined(WIN32) || defined(OSX))
+#error expected CPU_X86 to be defined.
+#endif
+#if !defined(ARCH_CPU_LITTLE_ENDIAN) && \
+  (defined(WIN32) || defined(OSX) || defined(CPU_X86))
+#error expected ARCH_CPU_LITTLE_ENDIAN to be defined.
+#endif
+
+// TODO(fbarchard): Test all macros in basictypes.h
+
+}  // namespace talk_base
diff --git a/talk/base/bind.h b/talk/base/bind.h
new file mode 100644
index 0000000..622cc67
--- /dev/null
+++ b/talk/base/bind.h
@@ -0,0 +1,397 @@
+// This file was GENERATED by command:
+//     pump.py bind.h.pump
+// DO NOT EDIT BY HAND!!!
+
+/*
+ * libjingle
+ * Copyright 2012 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// To generate bind.h from bind.h.pump, execute:
+// /home/build/google3/third_party/gtest/scripts/pump.py bind.h.pump
+
+// Bind() is an overloaded function that converts method calls into function
+// objects (aka functors). It captures any arguments to the method by value
+// when Bind is called, producing a stateful, nullary function object. Care
+// should be taken about the lifetime of objects captured by Bind(); the
+// returned functor knows nothing about the lifetime of the method's object or
+// any arguments passed by pointer, and calling the functor with a destroyed
+// object will surely do bad things.
+//
+// Example usage:
+//   struct Foo {
+//     int Test1() { return 42; }
+//     int Test2() const { return 52; }
+//     int Test3(int x) { return x*x; }
+//     float Test4(int x, float y) { return x + y; }
+//   };
+//
+//   int main() {
+//     Foo foo;
+//     cout << talk_base::Bind(&Foo::Test1, &foo)() << endl;
+//     cout << talk_base::Bind(&Foo::Test2, &foo)() << endl;
+//     cout << talk_base::Bind(&Foo::Test3, &foo, 3)() << endl;
+//     cout << talk_base::Bind(&Foo::Test4, &foo, 7, 8.5f)() << endl;
+//   }
+
+#ifndef TALK_BASE_BIND_H_
+#define TALK_BASE_BIND_H_
+
+#define NONAME
+
+namespace talk_base {
+namespace detail {
+// This is needed because the template parameters in Bind can't be resolved
+// if they're used both as parameters of the function pointer type and as
+// parameters to Bind itself: the function pointer parameters are exact
+// matches to the function prototype, but the parameters to bind have
+// references stripped. This trick allows the compiler to dictate the Bind
+// parameter types rather than deduce them.
+template <class T> struct identity { typedef T type; };
+}  // namespace detail
+
+template <class ObjectT, class MethodT, class R>
+class MethodFunctor0 {
+ public:
+  MethodFunctor0(MethodT method, ObjectT* object)
+      : method_(method), object_(object) {}
+  R operator()() const {
+    return (object_->*method_)(); }
+ private:
+  MethodT method_;
+  ObjectT* object_;
+};
+
+#define FP_T(x) R (ObjectT::*x)()
+
+template <class ObjectT, class R>
+MethodFunctor0<ObjectT, FP_T(NONAME), R>
+Bind(FP_T(method), ObjectT* object) {
+  return MethodFunctor0<ObjectT, FP_T(NONAME), R>(
+      method, object);
+}
+
+#undef FP_T
+#define FP_T(x) R (ObjectT::*x)() const
+
+template <class ObjectT, class R>
+MethodFunctor0<const ObjectT, FP_T(NONAME), R>
+Bind(FP_T(method), const ObjectT* object) {
+  return MethodFunctor0<const ObjectT, FP_T(NONAME), R>(
+      method, object);
+}
+
+#undef FP_T
+
+template <class ObjectT, class MethodT, class R,
+          class P1>
+class MethodFunctor1 {
+ public:
+  MethodFunctor1(MethodT method, ObjectT* object,
+                 P1 p1)
+      : method_(method), object_(object),
+      p1_(p1) {}
+  R operator()() const {
+    return (object_->*method_)(p1_); }
+ private:
+  MethodT method_;
+  ObjectT* object_;
+  P1 p1_;
+};
+
+#define FP_T(x) R (ObjectT::*x)(P1)
+
+template <class ObjectT, class R,
+          class P1>
+MethodFunctor1<ObjectT, FP_T(NONAME), R, P1>
+Bind(FP_T(method), ObjectT* object,
+     typename detail::identity<P1>::type p1) {
+  return MethodFunctor1<ObjectT, FP_T(NONAME), R, P1>(
+      method, object, p1);
+}
+
+#undef FP_T
+#define FP_T(x) R (ObjectT::*x)(P1) const
+
+template <class ObjectT, class R,
+          class P1>
+MethodFunctor1<const ObjectT, FP_T(NONAME), R, P1>
+Bind(FP_T(method), const ObjectT* object,
+     typename detail::identity<P1>::type p1) {
+  return MethodFunctor1<const ObjectT, FP_T(NONAME), R, P1>(
+      method, object, p1);
+}
+
+#undef FP_T
+
+template <class ObjectT, class MethodT, class R,
+          class P1,
+          class P2>
+class MethodFunctor2 {
+ public:
+  MethodFunctor2(MethodT method, ObjectT* object,
+                 P1 p1,
+                 P2 p2)
+      : method_(method), object_(object),
+      p1_(p1),
+      p2_(p2) {}
+  R operator()() const {
+    return (object_->*method_)(p1_, p2_); }
+ private:
+  MethodT method_;
+  ObjectT* object_;
+  P1 p1_;
+  P2 p2_;
+};
+
+#define FP_T(x) R (ObjectT::*x)(P1, P2)
+
+template <class ObjectT, class R,
+          class P1,
+          class P2>
+MethodFunctor2<ObjectT, FP_T(NONAME), R, P1, P2>
+Bind(FP_T(method), ObjectT* object,
+     typename detail::identity<P1>::type p1,
+     typename detail::identity<P2>::type p2) {
+  return MethodFunctor2<ObjectT, FP_T(NONAME), R, P1, P2>(
+      method, object, p1, p2);
+}
+
+#undef FP_T
+#define FP_T(x) R (ObjectT::*x)(P1, P2) const
+
+template <class ObjectT, class R,
+          class P1,
+          class P2>
+MethodFunctor2<const ObjectT, FP_T(NONAME), R, P1, P2>
+Bind(FP_T(method), const ObjectT* object,
+     typename detail::identity<P1>::type p1,
+     typename detail::identity<P2>::type p2) {
+  return MethodFunctor2<const ObjectT, FP_T(NONAME), R, P1, P2>(
+      method, object, p1, p2);
+}
+
+#undef FP_T
+
+template <class ObjectT, class MethodT, class R,
+          class P1,
+          class P2,
+          class P3>
+class MethodFunctor3 {
+ public:
+  MethodFunctor3(MethodT method, ObjectT* object,
+                 P1 p1,
+                 P2 p2,
+                 P3 p3)
+      : method_(method), object_(object),
+      p1_(p1),
+      p2_(p2),
+      p3_(p3) {}
+  R operator()() const {
+    return (object_->*method_)(p1_, p2_, p3_); }
+ private:
+  MethodT method_;
+  ObjectT* object_;
+  P1 p1_;
+  P2 p2_;
+  P3 p3_;
+};
+
+#define FP_T(x) R (ObjectT::*x)(P1, P2, P3)
+
+template <class ObjectT, class R,
+          class P1,
+          class P2,
+          class P3>
+MethodFunctor3<ObjectT, FP_T(NONAME), R, P1, P2, P3>
+Bind(FP_T(method), ObjectT* object,
+     typename detail::identity<P1>::type p1,
+     typename detail::identity<P2>::type p2,
+     typename detail::identity<P3>::type p3) {
+  return MethodFunctor3<ObjectT, FP_T(NONAME), R, P1, P2, P3>(
+      method, object, p1, p2, p3);
+}
+
+#undef FP_T
+#define FP_T(x) R (ObjectT::*x)(P1, P2, P3) const
+
+template <class ObjectT, class R,
+          class P1,
+          class P2,
+          class P3>
+MethodFunctor3<const ObjectT, FP_T(NONAME), R, P1, P2, P3>
+Bind(FP_T(method), const ObjectT* object,
+     typename detail::identity<P1>::type p1,
+     typename detail::identity<P2>::type p2,
+     typename detail::identity<P3>::type p3) {
+  return MethodFunctor3<const ObjectT, FP_T(NONAME), R, P1, P2, P3>(
+      method, object, p1, p2, p3);
+}
+
+#undef FP_T
+
+template <class ObjectT, class MethodT, class R,
+          class P1,
+          class P2,
+          class P3,
+          class P4>
+class MethodFunctor4 {
+ public:
+  MethodFunctor4(MethodT method, ObjectT* object,
+                 P1 p1,
+                 P2 p2,
+                 P3 p3,
+                 P4 p4)
+      : method_(method), object_(object),
+      p1_(p1),
+      p2_(p2),
+      p3_(p3),
+      p4_(p4) {}
+  R operator()() const {
+    return (object_->*method_)(p1_, p2_, p3_, p4_); }
+ private:
+  MethodT method_;
+  ObjectT* object_;
+  P1 p1_;
+  P2 p2_;
+  P3 p3_;
+  P4 p4_;
+};
+
+#define FP_T(x) R (ObjectT::*x)(P1, P2, P3, P4)
+
+template <class ObjectT, class R,
+          class P1,
+          class P2,
+          class P3,
+          class P4>
+MethodFunctor4<ObjectT, FP_T(NONAME), R, P1, P2, P3, P4>
+Bind(FP_T(method), ObjectT* object,
+     typename detail::identity<P1>::type p1,
+     typename detail::identity<P2>::type p2,
+     typename detail::identity<P3>::type p3,
+     typename detail::identity<P4>::type p4) {
+  return MethodFunctor4<ObjectT, FP_T(NONAME), R, P1, P2, P3, P4>(
+      method, object, p1, p2, p3, p4);
+}
+
+#undef FP_T
+#define FP_T(x) R (ObjectT::*x)(P1, P2, P3, P4) const
+
+template <class ObjectT, class R,
+          class P1,
+          class P2,
+          class P3,
+          class P4>
+MethodFunctor4<const ObjectT, FP_T(NONAME), R, P1, P2, P3, P4>
+Bind(FP_T(method), const ObjectT* object,
+     typename detail::identity<P1>::type p1,
+     typename detail::identity<P2>::type p2,
+     typename detail::identity<P3>::type p3,
+     typename detail::identity<P4>::type p4) {
+  return MethodFunctor4<const ObjectT, FP_T(NONAME), R, P1, P2, P3, P4>(
+      method, object, p1, p2, p3, p4);
+}
+
+#undef FP_T
+
+template <class ObjectT, class MethodT, class R,
+          class P1,
+          class P2,
+          class P3,
+          class P4,
+          class P5>
+class MethodFunctor5 {
+ public:
+  MethodFunctor5(MethodT method, ObjectT* object,
+                 P1 p1,
+                 P2 p2,
+                 P3 p3,
+                 P4 p4,
+                 P5 p5)
+      : method_(method), object_(object),
+      p1_(p1),
+      p2_(p2),
+      p3_(p3),
+      p4_(p4),
+      p5_(p5) {}
+  R operator()() const {
+    return (object_->*method_)(p1_, p2_, p3_, p4_, p5_); }
+ private:
+  MethodT method_;
+  ObjectT* object_;
+  P1 p1_;
+  P2 p2_;
+  P3 p3_;
+  P4 p4_;
+  P5 p5_;
+};
+
+#define FP_T(x) R (ObjectT::*x)(P1, P2, P3, P4, P5)
+
+template <class ObjectT, class R,
+          class P1,
+          class P2,
+          class P3,
+          class P4,
+          class P5>
+MethodFunctor5<ObjectT, FP_T(NONAME), R, P1, P2, P3, P4, P5>
+Bind(FP_T(method), ObjectT* object,
+     typename detail::identity<P1>::type p1,
+     typename detail::identity<P2>::type p2,
+     typename detail::identity<P3>::type p3,
+     typename detail::identity<P4>::type p4,
+     typename detail::identity<P5>::type p5) {
+  return MethodFunctor5<ObjectT, FP_T(NONAME), R, P1, P2, P3, P4, P5>(
+      method, object, p1, p2, p3, p4, p5);
+}
+
+#undef FP_T
+#define FP_T(x) R (ObjectT::*x)(P1, P2, P3, P4, P5) const
+
+template <class ObjectT, class R,
+          class P1,
+          class P2,
+          class P3,
+          class P4,
+          class P5>
+MethodFunctor5<const ObjectT, FP_T(NONAME), R, P1, P2, P3, P4, P5>
+Bind(FP_T(method), const ObjectT* object,
+     typename detail::identity<P1>::type p1,
+     typename detail::identity<P2>::type p2,
+     typename detail::identity<P3>::type p3,
+     typename detail::identity<P4>::type p4,
+     typename detail::identity<P5>::type p5) {
+  return MethodFunctor5<const ObjectT, FP_T(NONAME), R, P1, P2, P3, P4, P5>(
+      method, object, p1, p2, p3, p4, p5);
+}
+
+#undef FP_T
+
+}  // namespace talk_base
+
+#undef NONAME
+
+#endif  // TALK_BASE_BIND_H_
diff --git a/talk/base/bind.h.pump b/talk/base/bind.h.pump
new file mode 100644
index 0000000..7f4c39e
--- /dev/null
+++ b/talk/base/bind.h.pump
@@ -0,0 +1,125 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// To generate bind.h from bind.h.pump, execute:
+// /home/build/google3/third_party/gtest/scripts/pump.py bind.h.pump
+
+// Bind() is an overloaded function that converts method calls into function
+// objects (aka functors). It captures any arguments to the method by value
+// when Bind is called, producing a stateful, nullary function object. Care
+// should be taken about the lifetime of objects captured by Bind(); the
+// returned functor knows nothing about the lifetime of the method's object or
+// any arguments passed by pointer, and calling the functor with a destroyed
+// object will surely do bad things.
+//
+// Example usage:
+//   struct Foo {
+//     int Test1() { return 42; }
+//     int Test2() const { return 52; }
+//     int Test3(int x) { return x*x; }
+//     float Test4(int x, float y) { return x + y; }
+//   };
+//
+//   int main() {
+//     Foo foo;
+//     cout << talk_base::Bind(&Foo::Test1, &foo)() << endl;
+//     cout << talk_base::Bind(&Foo::Test2, &foo)() << endl;
+//     cout << talk_base::Bind(&Foo::Test3, &foo, 3)() << endl;
+//     cout << talk_base::Bind(&Foo::Test4, &foo, 7, 8.5f)() << endl;
+//   }
+
+#ifndef TALK_BASE_BIND_H_
+#define TALK_BASE_BIND_H_
+
+#define NONAME
+
+namespace talk_base {
+namespace detail {
+// This is needed because the template parameters in Bind can't be resolved
+// if they're used both as parameters of the function pointer type and as
+// parameters to Bind itself: the function pointer parameters are exact
+// matches to the function prototype, but the parameters to bind have
+// references stripped. This trick allows the compiler to dictate the Bind
+// parameter types rather than deduce them.
+template <class T> struct identity { typedef T type; };
+}  // namespace detail
+
+$var n = 5
+$range i 0..n
+$for i [[
+$range j 1..i
+
+template <class ObjectT, class MethodT, class R$for j [[,
+          class P$j]]>
+class MethodFunctor$i {
+ public:
+  MethodFunctor$i(MethodT method, ObjectT* object$for j [[,
+                 P$j p$j]])
+      : method_(method), object_(object)$for j [[,
+      p$(j)_(p$j)]] {}
+  R operator()() const {
+    return (object_->*method_)($for j , [[p$(j)_]]); }
+ private:
+  MethodT method_;
+  ObjectT* object_;$for j [[
+
+  P$j p$(j)_;]]
+
+};
+
+#define FP_T(x) R (ObjectT::*x)($for j , [[P$j]])
+
+template <class ObjectT, class R$for j [[,
+          class P$j]]>
+MethodFunctor$i<ObjectT, FP_T(NONAME), R$for j [[, P$j]]>
+Bind(FP_T(method), ObjectT* object$for j [[,
+     typename detail::identity<P$j>::type p$j]]) {
+  return MethodFunctor$i<ObjectT, FP_T(NONAME), R$for j [[, P$j]]>(
+      method, object$for j [[, p$j]]);
+}
+
+#undef FP_T
+#define FP_T(x) R (ObjectT::*x)($for j , [[P$j]]) const
+
+template <class ObjectT, class R$for j [[,
+          class P$j]]>
+MethodFunctor$i<const ObjectT, FP_T(NONAME), R$for j [[, P$j]]>
+Bind(FP_T(method), const ObjectT* object$for j [[,
+     typename detail::identity<P$j>::type p$j]]) {
+  return MethodFunctor$i<const ObjectT, FP_T(NONAME), R$for j [[, P$j]]>(
+      method, object$for j [[, p$j]]);
+}
+
+#undef FP_T
+
+]]
+
+}  // namespace talk_base
+
+#undef NONAME
+
+#endif  // TALK_BASE_BIND_H_
diff --git a/talk/base/bind_unittest.cc b/talk/base/bind_unittest.cc
new file mode 100644
index 0000000..81bbddd
--- /dev/null
+++ b/talk/base/bind_unittest.cc
@@ -0,0 +1,74 @@
+/*
+ * 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/bind.h"
+#include "talk/base/gunit.h"
+
+namespace talk_base {
+
+namespace {
+
+struct MethodBindTester {
+  void NullaryVoid() { ++call_count; }
+  int NullaryInt() { ++call_count; return 1; }
+  int NullaryConst() const { ++call_count; return 2; }
+  void UnaryVoid(int dummy) { ++call_count; }
+  template <class T> T Identity(T value) { ++call_count; return value; }
+  int UnaryByRef(int& value) const { ++call_count; return ++value; }  // NOLINT
+  int Multiply(int a, int b) const { ++call_count; return a * b; }
+  mutable int call_count;
+};
+
+}  // namespace
+
+TEST(BindTest, BindToMethod) {
+  MethodBindTester object = {0};
+  EXPECT_EQ(0, object.call_count);
+  Bind(&MethodBindTester::NullaryVoid, &object)();
+  EXPECT_EQ(1, object.call_count);
+  EXPECT_EQ(1, Bind(&MethodBindTester::NullaryInt, &object)());
+  EXPECT_EQ(2, object.call_count);
+  EXPECT_EQ(2, Bind(&MethodBindTester::NullaryConst,
+                    static_cast<const MethodBindTester*>(&object))());
+  EXPECT_EQ(3, object.call_count);
+  Bind(&MethodBindTester::UnaryVoid, &object, 5)();
+  EXPECT_EQ(4, object.call_count);
+  EXPECT_EQ(100, Bind(&MethodBindTester::Identity<int>, &object, 100)());
+  EXPECT_EQ(5, object.call_count);
+  const std::string string_value("test string");
+  EXPECT_EQ(string_value, Bind(&MethodBindTester::Identity<std::string>,
+                               &object, string_value)());
+  EXPECT_EQ(6, object.call_count);
+  int value = 11;
+  EXPECT_EQ(12, Bind(&MethodBindTester::UnaryByRef, &object, value)());
+  EXPECT_EQ(12, value);
+  EXPECT_EQ(7, object.call_count);
+  EXPECT_EQ(56, Bind(&MethodBindTester::Multiply, &object, 7, 8)());
+  EXPECT_EQ(8, object.call_count);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/buffer.h b/talk/base/buffer.h
new file mode 100644
index 0000000..311cfad
--- /dev/null
+++ b/talk/base/buffer.h
@@ -0,0 +1,119 @@
+/*
+ * libjingle
+ * Copyright 2004-2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_BUFFER_H_
+#define TALK_BASE_BUFFER_H_
+
+#include <cstring>
+
+#include "talk/base/scoped_ptr.h"
+
+namespace talk_base {
+
+// Basic buffer class, can be grown and shrunk dynamically.
+// Unlike std::string/vector, does not initialize data when expanding capacity.
+class Buffer {
+ public:
+  Buffer() {
+    Construct(NULL, 0, 0);
+  }
+  Buffer(const void* data, size_t length) {
+    Construct(data, length, length);
+  }
+  Buffer(const void* data, size_t length, size_t capacity) {
+    Construct(data, length, capacity);
+  }
+  Buffer(const Buffer& buf) {
+    Construct(buf.data(), buf.length(), buf.length());
+  }
+
+  const char* data() const { return data_.get(); }
+  char* data() { return data_.get(); }
+  // TODO: should this be size(), like STL?
+  size_t length() const { return length_; }
+  size_t capacity() const { return capacity_; }
+
+  Buffer& operator=(const Buffer& buf) {
+    if (&buf != this) {
+      Construct(buf.data(), buf.length(), buf.length());
+    }
+    return *this;
+  }
+  bool operator==(const Buffer& buf) const {
+    return (length_ == buf.length() &&
+            memcmp(data_.get(), buf.data(), length_) == 0);
+  }
+  bool operator!=(const Buffer& buf) const {
+    return !operator==(buf);
+  }
+
+  void SetData(const void* data, size_t length) {
+    ASSERT(data != NULL || length == 0);
+    SetLength(length);
+    memcpy(data_.get(), data, length);
+  }
+  void AppendData(const void* data, size_t length) {
+    ASSERT(data != NULL || length == 0);
+    size_t old_length = length_;
+    SetLength(length_ + length);
+    memcpy(data_.get() + old_length, data, length);
+  }
+  void SetLength(size_t length) {
+    SetCapacity(length);
+    length_ = length;
+  }
+  void SetCapacity(size_t capacity) {
+    if (capacity > capacity_) {
+      talk_base::scoped_array<char> data(new char[capacity]);
+      memcpy(data.get(), data_.get(), length_);
+      data_.swap(data);
+      capacity_ = capacity;
+    }
+  }
+
+  void TransferTo(Buffer* buf) {
+    ASSERT(buf != NULL);
+    buf->data_.reset(data_.release());
+    buf->length_ = length_;
+    buf->capacity_ = capacity_;
+    Construct(NULL, 0, 0);
+  }
+
+ protected:
+  void Construct(const void* data, size_t length, size_t capacity) {
+    data_.reset(new char[capacity_ = capacity]);
+    SetData(data, length);
+  }
+
+  scoped_array<char> data_;
+  size_t length_;
+  size_t capacity_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_BUFFER_H_
diff --git a/talk/base/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/bytebuffer.cc b/talk/base/bytebuffer.cc
new file mode 100644
index 0000000..523475d
--- /dev/null
+++ b/talk/base/bytebuffer.cc
@@ -0,0 +1,250 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/bytebuffer.h"
+
+#include <algorithm>
+#include <cassert>
+#include <cstring>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/byteorder.h"
+
+namespace talk_base {
+
+static const int DEFAULT_SIZE = 4096;
+
+ByteBuffer::ByteBuffer() {
+  Construct(NULL, DEFAULT_SIZE, ORDER_NETWORK);
+}
+
+ByteBuffer::ByteBuffer(ByteOrder byte_order) {
+  Construct(NULL, DEFAULT_SIZE, byte_order);
+}
+
+ByteBuffer::ByteBuffer(const char* bytes, size_t len) {
+  Construct(bytes, len, ORDER_NETWORK);
+}
+
+ByteBuffer::ByteBuffer(const char* bytes, size_t len, ByteOrder byte_order) {
+  Construct(bytes, len, byte_order);
+}
+
+ByteBuffer::ByteBuffer(const char* bytes) {
+  Construct(bytes, strlen(bytes), ORDER_NETWORK);
+}
+
+void ByteBuffer::Construct(const char* bytes, size_t len,
+                           ByteOrder byte_order) {
+  version_ = 0;
+  start_ = 0;
+  size_ = len;
+  byte_order_ = byte_order;
+  bytes_ = new char[size_];
+
+  if (bytes) {
+    end_ = len;
+    memcpy(bytes_, bytes, end_);
+  } else {
+    end_ = 0;
+  }
+}
+
+ByteBuffer::~ByteBuffer() {
+  delete[] bytes_;
+}
+
+bool ByteBuffer::ReadUInt8(uint8* val) {
+  if (!val) return false;
+
+  return ReadBytes(reinterpret_cast<char*>(val), 1);
+}
+
+bool ByteBuffer::ReadUInt16(uint16* val) {
+  if (!val) return false;
+
+  uint16 v;
+  if (!ReadBytes(reinterpret_cast<char*>(&v), 2)) {
+    return false;
+  } else {
+    *val = (byte_order_ == ORDER_NETWORK) ? NetworkToHost16(v) : v;
+    return true;
+  }
+}
+
+bool ByteBuffer::ReadUInt24(uint32* val) {
+  if (!val) return false;
+
+  uint32 v = 0;
+  char* read_into = reinterpret_cast<char*>(&v);
+  if (byte_order_ == ORDER_NETWORK || IsHostBigEndian()) {
+    ++read_into;
+  }
+
+  if (!ReadBytes(read_into, 3)) {
+    return false;
+  } else {
+    *val = (byte_order_ == ORDER_NETWORK) ? NetworkToHost32(v) : v;
+    return true;
+  }
+}
+
+bool ByteBuffer::ReadUInt32(uint32* val) {
+  if (!val) return false;
+
+  uint32 v;
+  if (!ReadBytes(reinterpret_cast<char*>(&v), 4)) {
+    return false;
+  } else {
+    *val = (byte_order_ == ORDER_NETWORK) ? NetworkToHost32(v) : v;
+    return true;
+  }
+}
+
+bool ByteBuffer::ReadUInt64(uint64* val) {
+  if (!val) return false;
+
+  uint64 v;
+  if (!ReadBytes(reinterpret_cast<char*>(&v), 8)) {
+    return false;
+  } else {
+    *val = (byte_order_ == ORDER_NETWORK) ? NetworkToHost64(v) : v;
+    return true;
+  }
+}
+
+bool ByteBuffer::ReadString(std::string* val, size_t len) {
+  if (!val) return false;
+
+  if (len > Length()) {
+    return false;
+  } else {
+    val->append(bytes_ + start_, len);
+    start_ += len;
+    return true;
+  }
+}
+
+bool ByteBuffer::ReadBytes(char* val, size_t len) {
+  if (len > Length()) {
+    return false;
+  } else {
+    memcpy(val, bytes_ + start_, len);
+    start_ += len;
+    return true;
+  }
+}
+
+void ByteBuffer::WriteUInt8(uint8 val) {
+  WriteBytes(reinterpret_cast<const char*>(&val), 1);
+}
+
+void ByteBuffer::WriteUInt16(uint16 val) {
+  uint16 v = (byte_order_ == ORDER_NETWORK) ? HostToNetwork16(val) : val;
+  WriteBytes(reinterpret_cast<const char*>(&v), 2);
+}
+
+void ByteBuffer::WriteUInt24(uint32 val) {
+  uint32 v = (byte_order_ == ORDER_NETWORK) ? HostToNetwork32(val) : val;
+  char* start = reinterpret_cast<char*>(&v);
+  if (byte_order_ == ORDER_NETWORK || IsHostBigEndian()) {
+    ++start;
+  }
+  WriteBytes(start, 3);
+}
+
+void ByteBuffer::WriteUInt32(uint32 val) {
+  uint32 v = (byte_order_ == ORDER_NETWORK) ? HostToNetwork32(val) : val;
+  WriteBytes(reinterpret_cast<const char*>(&v), 4);
+}
+
+void ByteBuffer::WriteUInt64(uint64 val) {
+  uint64 v = (byte_order_ == ORDER_NETWORK) ? HostToNetwork64(val) : val;
+  WriteBytes(reinterpret_cast<const char*>(&v), 8);
+}
+
+void ByteBuffer::WriteString(const std::string& val) {
+  WriteBytes(val.c_str(), val.size());
+}
+
+void ByteBuffer::WriteBytes(const char* val, size_t len) {
+  memcpy(ReserveWriteBuffer(len), val, len);
+}
+
+char* ByteBuffer::ReserveWriteBuffer(size_t len) {
+  if (Length() + len > Capacity())
+    Resize(Length() + len);
+
+  char* start = bytes_ + end_;
+  end_ += len;
+  return start;
+}
+
+void ByteBuffer::Resize(size_t size) {
+  size_t len = _min(end_ - start_, size);
+  if (size <= size_) {
+    // Don't reallocate, just move data backwards
+    memmove(bytes_, bytes_ + start_, len);
+  } else {
+    // Reallocate a larger buffer.
+    size_ = _max(size, 3 * size_ / 2);
+    char* new_bytes = new char[size_];
+    memcpy(new_bytes, bytes_ + start_, len);
+    delete [] bytes_;
+    bytes_ = new_bytes;
+  }
+  start_ = 0;
+  end_ = len;
+  ++version_;
+}
+
+bool ByteBuffer::Consume(size_t size) {
+  if (size > Length())
+    return false;
+  start_ += size;
+  return true;
+}
+
+ByteBuffer::ReadPosition ByteBuffer::GetReadPosition() const {
+  return ReadPosition(start_, version_);
+}
+
+bool ByteBuffer::SetReadPosition(const ReadPosition &position) {
+  if (position.version_ != version_) {
+    return false;
+  }
+  start_ = position.start_;
+  return true;
+}
+
+void ByteBuffer::Clear() {
+  memset(bytes_, 0, size_);
+  start_ = end_ = 0;
+  ++version_;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/bytebuffer.h b/talk/base/bytebuffer.h
new file mode 100644
index 0000000..a12c59c
--- /dev/null
+++ b/talk/base/bytebuffer.h
@@ -0,0 +1,136 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_BYTEBUFFER_H_
+#define TALK_BASE_BYTEBUFFER_H_
+
+#include <string>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/constructormagic.h"
+
+namespace talk_base {
+
+class ByteBuffer {
+ public:
+
+  enum ByteOrder {
+    ORDER_NETWORK = 0,  // Default, use network byte order (big endian).
+    ORDER_HOST,         // Use the native order of the host.
+  };
+
+  // |byte_order| defines order of bytes in the buffer.
+  ByteBuffer();
+  explicit ByteBuffer(ByteOrder byte_order);
+  ByteBuffer(const char* bytes, size_t len);
+  ByteBuffer(const char* bytes, size_t len, ByteOrder byte_order);
+
+  // Initializes buffer from a zero-terminated string.
+  explicit ByteBuffer(const char* bytes);
+
+  ~ByteBuffer();
+
+  const char* Data() const { return bytes_ + start_; }
+  size_t Length() const { return end_ - start_; }
+  size_t Capacity() const { return size_ - start_; }
+  ByteOrder Order() const { return byte_order_; }
+
+  // Read a next value from the buffer. Return false if there isn't
+  // enough data left for the specified type.
+  bool ReadUInt8(uint8* val);
+  bool ReadUInt16(uint16* val);
+  bool ReadUInt24(uint32* val);
+  bool ReadUInt32(uint32* val);
+  bool ReadUInt64(uint64* val);
+  bool ReadBytes(char* val, size_t len);
+
+  // Appends next |len| bytes from the buffer to |val|. Returns false
+  // if there is less than |len| bytes left.
+  bool ReadString(std::string* val, size_t len);
+
+  // Write value to the buffer. Resizes the buffer when it is
+  // neccessary.
+  void WriteUInt8(uint8 val);
+  void WriteUInt16(uint16 val);
+  void WriteUInt24(uint32 val);
+  void WriteUInt32(uint32 val);
+  void WriteUInt64(uint64 val);
+  void WriteString(const std::string& val);
+  void WriteBytes(const char* val, size_t len);
+
+  // Reserves the given number of bytes and returns a char* that can be written
+  // into. Useful for functions that require a char* buffer and not a
+  // ByteBuffer.
+  char* ReserveWriteBuffer(size_t len);
+
+  // Resize the buffer to the specified |size|. This invalidates any remembered
+  // seek positions.
+  void Resize(size_t size);
+
+  // Moves current position |size| bytes forward. Returns false if
+  // there is less than |size| bytes left in the buffer. Consume doesn't
+  // permanently remove data, so remembered read positions are still valid
+  // after this call.
+  bool Consume(size_t size);
+
+  // Clears the contents of the buffer. After this, Length() will be 0.
+  void Clear();
+
+  // Used with GetReadPosition/SetReadPosition.
+  class ReadPosition {
+    friend class ByteBuffer;
+    ReadPosition(size_t start, int version)
+        : start_(start), version_(version) { }
+    size_t start_;
+    int version_;
+  };
+
+  // Remembers the current read position for a future SetReadPosition. Any
+  // calls to Shift or Resize in the interim will invalidate the position.
+  ReadPosition GetReadPosition() const;
+
+  // If the given position is still valid, restores that read position.
+  bool SetReadPosition(const ReadPosition &position);
+
+ private:
+  void Construct(const char* bytes, size_t size, ByteOrder byte_order);
+
+  char* bytes_;
+  size_t size_;
+  size_t start_;
+  size_t end_;
+  int version_;
+  ByteOrder byte_order_;
+
+  // There are sensible ways to define these, but they aren't needed in our code
+  // base.
+  DISALLOW_COPY_AND_ASSIGN(ByteBuffer);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_BYTEBUFFER_H_
diff --git a/talk/base/bytebuffer_unittest.cc b/talk/base/bytebuffer_unittest.cc
new file mode 100644
index 0000000..2c73453
--- /dev/null
+++ b/talk/base/bytebuffer_unittest.cc
@@ -0,0 +1,228 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/bytebuffer.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+
+namespace talk_base {
+
+TEST(ByteBufferTest, TestByteOrder) {
+  uint16 n16 = 1;
+  uint32 n32 = 1;
+  uint64 n64 = 1;
+
+  EXPECT_EQ(n16, NetworkToHost16(HostToNetwork16(n16)));
+  EXPECT_EQ(n32, NetworkToHost32(HostToNetwork32(n32)));
+  EXPECT_EQ(n64, NetworkToHost64(HostToNetwork64(n64)));
+
+  if (IsHostBigEndian()) {
+    // The host is the network (big) endian.
+    EXPECT_EQ(n16, HostToNetwork16(n16));
+    EXPECT_EQ(n32, HostToNetwork32(n32));
+    EXPECT_EQ(n64, HostToNetwork64(n64));
+
+    // GetBE converts big endian to little endian here.
+    EXPECT_EQ(n16 >> 8, GetBE16(&n16));
+    EXPECT_EQ(n32 >> 24, GetBE32(&n32));
+    EXPECT_EQ(n64 >> 56, GetBE64(&n64));
+  } else {
+    // The host is little endian.
+    EXPECT_NE(n16, HostToNetwork16(n16));
+    EXPECT_NE(n32, HostToNetwork32(n32));
+    EXPECT_NE(n64, HostToNetwork64(n64));
+
+    // GetBE converts little endian to big endian here.
+    EXPECT_EQ(GetBE16(&n16), HostToNetwork16(n16));
+    EXPECT_EQ(GetBE32(&n32), HostToNetwork32(n32));
+    EXPECT_EQ(GetBE64(&n64), HostToNetwork64(n64));
+
+    // GetBE converts little endian to big endian here.
+    EXPECT_EQ(n16 << 8, GetBE16(&n16));
+    EXPECT_EQ(n32 << 24, GetBE32(&n32));
+    EXPECT_EQ(n64 << 56, GetBE64(&n64));
+  }
+}
+
+TEST(ByteBufferTest, TestBufferLength) {
+  ByteBuffer buffer;
+  size_t size = 0;
+  EXPECT_EQ(size, buffer.Length());
+
+  buffer.WriteUInt8(1);
+  ++size;
+  EXPECT_EQ(size, buffer.Length());
+
+  buffer.WriteUInt16(1);
+  size += 2;
+  EXPECT_EQ(size, buffer.Length());
+
+  buffer.WriteUInt24(1);
+  size += 3;
+  EXPECT_EQ(size, buffer.Length());
+
+  buffer.WriteUInt32(1);
+  size += 4;
+  EXPECT_EQ(size, buffer.Length());
+
+  buffer.WriteUInt64(1);
+  size += 8;
+  EXPECT_EQ(size, buffer.Length());
+
+  EXPECT_TRUE(buffer.Consume(0));
+  EXPECT_EQ(size, buffer.Length());
+
+  EXPECT_TRUE(buffer.Consume(4));
+  size -= 4;
+  EXPECT_EQ(size, buffer.Length());
+}
+
+TEST(ByteBufferTest, TestGetSetReadPosition) {
+  ByteBuffer buffer("ABCDEF", 6);
+  EXPECT_EQ(6U, buffer.Length());
+  ByteBuffer::ReadPosition pos(buffer.GetReadPosition());
+  EXPECT_TRUE(buffer.SetReadPosition(pos));
+  EXPECT_EQ(6U, buffer.Length());
+  std::string read;
+  EXPECT_TRUE(buffer.ReadString(&read, 3));
+  EXPECT_EQ("ABC", read);
+  EXPECT_EQ(3U, buffer.Length());
+  EXPECT_TRUE(buffer.SetReadPosition(pos));
+  EXPECT_EQ(6U, buffer.Length());
+  read.clear();
+  EXPECT_TRUE(buffer.ReadString(&read, 3));
+  EXPECT_EQ("ABC", read);
+  EXPECT_EQ(3U, buffer.Length());
+  // For a resize by writing Capacity() number of bytes.
+  size_t capacity = buffer.Capacity();
+  buffer.ReserveWriteBuffer(buffer.Capacity());
+  EXPECT_EQ(capacity + 3U, buffer.Length());
+  EXPECT_FALSE(buffer.SetReadPosition(pos));
+  read.clear();
+  EXPECT_TRUE(buffer.ReadString(&read, 3));
+  EXPECT_EQ("DEF", read);
+}
+
+TEST(ByteBufferTest, TestReadWriteBuffer) {
+  ByteBuffer::ByteOrder orders[2] = { ByteBuffer::ORDER_HOST,
+                                      ByteBuffer::ORDER_NETWORK };
+  for (size_t i = 0; i < ARRAY_SIZE(orders); i++) {
+    ByteBuffer buffer(orders[i]);
+    EXPECT_EQ(orders[i], buffer.Order());
+    uint8 ru8;
+    EXPECT_FALSE(buffer.ReadUInt8(&ru8));
+
+    // Write and read uint8.
+    uint8 wu8 = 1;
+    buffer.WriteUInt8(wu8);
+    EXPECT_TRUE(buffer.ReadUInt8(&ru8));
+    EXPECT_EQ(wu8, ru8);
+    EXPECT_EQ(0U, buffer.Length());
+
+    // Write and read uint16.
+    uint16 wu16 = (1 << 8) + 1;
+    buffer.WriteUInt16(wu16);
+    uint16 ru16;
+    EXPECT_TRUE(buffer.ReadUInt16(&ru16));
+    EXPECT_EQ(wu16, ru16);
+    EXPECT_EQ(0U, buffer.Length());
+
+    // Write and read uint24.
+    uint32 wu24 = (3 << 16) + (2 << 8) + 1;
+    buffer.WriteUInt24(wu24);
+    uint32 ru24;
+    EXPECT_TRUE(buffer.ReadUInt24(&ru24));
+    EXPECT_EQ(wu24, ru24);
+    EXPECT_EQ(0U, buffer.Length());
+
+    // Write and read uint32.
+    uint32 wu32 = (4 << 24) + (3 << 16) + (2 << 8) + 1;
+    buffer.WriteUInt32(wu32);
+    uint32 ru32;
+    EXPECT_TRUE(buffer.ReadUInt32(&ru32));
+    EXPECT_EQ(wu32, ru32);
+    EXPECT_EQ(0U, buffer.Length());
+
+    // Write and read uint64.
+    uint32 another32 = (8 << 24) + (7 << 16) + (6 << 8) + 5;
+    uint64 wu64 = (static_cast<uint64>(another32) << 32) + wu32;
+    buffer.WriteUInt64(wu64);
+    uint64 ru64;
+    EXPECT_TRUE(buffer.ReadUInt64(&ru64));
+    EXPECT_EQ(wu64, ru64);
+    EXPECT_EQ(0U, buffer.Length());
+
+    // Write and read string.
+    std::string write_string("hello");
+    buffer.WriteString(write_string);
+    std::string read_string;
+    EXPECT_TRUE(buffer.ReadString(&read_string, write_string.size()));
+    EXPECT_EQ(write_string, read_string);
+    EXPECT_EQ(0U, buffer.Length());
+
+    // Write and read bytes
+    char write_bytes[] = "foo";
+    buffer.WriteBytes(write_bytes, 3);
+    char read_bytes[3];
+    EXPECT_TRUE(buffer.ReadBytes(read_bytes, 3));
+    for (int i = 0; i < 3; ++i) {
+      EXPECT_EQ(write_bytes[i], read_bytes[i]);
+    }
+    EXPECT_EQ(0U, buffer.Length());
+
+    // Write and read reserved buffer space
+    char* write_dst = buffer.ReserveWriteBuffer(3);
+    memcpy(write_dst, write_bytes, 3);
+    memset(read_bytes, 0, 3);
+    EXPECT_TRUE(buffer.ReadBytes(read_bytes, 3));
+    for (int i = 0; i < 3; ++i) {
+      EXPECT_EQ(write_bytes[i], read_bytes[i]);
+    }
+    EXPECT_EQ(0U, buffer.Length());
+
+    // Write and read in order.
+    buffer.WriteUInt8(wu8);
+    buffer.WriteUInt16(wu16);
+    buffer.WriteUInt24(wu24);
+    buffer.WriteUInt32(wu32);
+    buffer.WriteUInt64(wu64);
+    EXPECT_TRUE(buffer.ReadUInt8(&ru8));
+    EXPECT_EQ(wu8, ru8);
+    EXPECT_TRUE(buffer.ReadUInt16(&ru16));
+    EXPECT_EQ(wu16, ru16);
+    EXPECT_TRUE(buffer.ReadUInt24(&ru24));
+    EXPECT_EQ(wu24, ru24);
+    EXPECT_TRUE(buffer.ReadUInt32(&ru32));
+    EXPECT_EQ(wu32, ru32);
+    EXPECT_TRUE(buffer.ReadUInt64(&ru64));
+    EXPECT_EQ(wu64, ru64);
+    EXPECT_EQ(0U, buffer.Length());
+  }
+}
+
+}  // namespace talk_base
diff --git a/talk/base/byteorder.h b/talk/base/byteorder.h
new file mode 100644
index 0000000..c6d0dbb
--- /dev/null
+++ b/talk/base/byteorder.h
@@ -0,0 +1,185 @@
+/*
+ * 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_BASE_BYTEORDER_H_
+#define TALK_BASE_BYTEORDER_H_
+
+#ifdef POSIX
+#include <arpa/inet.h>
+#endif
+
+#ifdef WIN32
+#include <stdlib.h>
+#endif
+
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+// Reading and writing of little and big-endian numbers from memory
+// TODO: Optimized versions, with direct read/writes of
+// integers in host-endian format, when the platform supports it.
+
+inline void Set8(void* memory, size_t offset, uint8 v) {
+  static_cast<uint8*>(memory)[offset] = v;
+}
+
+inline uint8 Get8(const void* memory, size_t offset) {
+  return static_cast<const uint8*>(memory)[offset];
+}
+
+inline void SetBE16(void* memory, uint16 v) {
+  Set8(memory, 0, static_cast<uint8>(v >> 8));
+  Set8(memory, 1, static_cast<uint8>(v >> 0));
+}
+
+inline void SetBE32(void* memory, uint32 v) {
+  Set8(memory, 0, static_cast<uint8>(v >> 24));
+  Set8(memory, 1, static_cast<uint8>(v >> 16));
+  Set8(memory, 2, static_cast<uint8>(v >> 8));
+  Set8(memory, 3, static_cast<uint8>(v >> 0));
+}
+
+inline void SetBE64(void* memory, uint64 v) {
+  Set8(memory, 0, static_cast<uint8>(v >> 56));
+  Set8(memory, 1, static_cast<uint8>(v >> 48));
+  Set8(memory, 2, static_cast<uint8>(v >> 40));
+  Set8(memory, 3, static_cast<uint8>(v >> 32));
+  Set8(memory, 4, static_cast<uint8>(v >> 24));
+  Set8(memory, 5, static_cast<uint8>(v >> 16));
+  Set8(memory, 6, static_cast<uint8>(v >> 8));
+  Set8(memory, 7, static_cast<uint8>(v >> 0));
+}
+
+inline uint16 GetBE16(const void* memory) {
+  return static_cast<uint16>((Get8(memory, 0) << 8) |
+                             (Get8(memory, 1) << 0));
+}
+
+inline uint32 GetBE32(const void* memory) {
+  return (static_cast<uint32>(Get8(memory, 0)) << 24) |
+      (static_cast<uint32>(Get8(memory, 1)) << 16) |
+      (static_cast<uint32>(Get8(memory, 2)) << 8) |
+      (static_cast<uint32>(Get8(memory, 3)) << 0);
+}
+
+inline uint64 GetBE64(const void* memory) {
+  return (static_cast<uint64>(Get8(memory, 0)) << 56) |
+      (static_cast<uint64>(Get8(memory, 1)) << 48) |
+      (static_cast<uint64>(Get8(memory, 2)) << 40) |
+      (static_cast<uint64>(Get8(memory, 3)) << 32) |
+      (static_cast<uint64>(Get8(memory, 4)) << 24) |
+      (static_cast<uint64>(Get8(memory, 5)) << 16) |
+      (static_cast<uint64>(Get8(memory, 6)) << 8) |
+      (static_cast<uint64>(Get8(memory, 7)) << 0);
+}
+
+inline void SetLE16(void* memory, uint16 v) {
+  Set8(memory, 0, static_cast<uint8>(v >> 0));
+  Set8(memory, 1, static_cast<uint8>(v >> 8));
+}
+
+inline void SetLE32(void* memory, uint32 v) {
+  Set8(memory, 0, static_cast<uint8>(v >> 0));
+  Set8(memory, 1, static_cast<uint8>(v >> 8));
+  Set8(memory, 2, static_cast<uint8>(v >> 16));
+  Set8(memory, 3, static_cast<uint8>(v >> 24));
+}
+
+inline void SetLE64(void* memory, uint64 v) {
+  Set8(memory, 0, static_cast<uint8>(v >> 0));
+  Set8(memory, 1, static_cast<uint8>(v >> 8));
+  Set8(memory, 2, static_cast<uint8>(v >> 16));
+  Set8(memory, 3, static_cast<uint8>(v >> 24));
+  Set8(memory, 4, static_cast<uint8>(v >> 32));
+  Set8(memory, 5, static_cast<uint8>(v >> 40));
+  Set8(memory, 6, static_cast<uint8>(v >> 48));
+  Set8(memory, 7, static_cast<uint8>(v >> 56));
+}
+
+inline uint16 GetLE16(const void* memory) {
+  return static_cast<uint16>((Get8(memory, 0) << 0) |
+                             (Get8(memory, 1) << 8));
+}
+
+inline uint32 GetLE32(const void* memory) {
+  return (static_cast<uint32>(Get8(memory, 0)) << 0) |
+      (static_cast<uint32>(Get8(memory, 1)) << 8) |
+      (static_cast<uint32>(Get8(memory, 2)) << 16) |
+      (static_cast<uint32>(Get8(memory, 3)) << 24);
+}
+
+inline uint64 GetLE64(const void* memory) {
+  return (static_cast<uint64>(Get8(memory, 0)) << 0) |
+      (static_cast<uint64>(Get8(memory, 1)) << 8) |
+      (static_cast<uint64>(Get8(memory, 2)) << 16) |
+      (static_cast<uint64>(Get8(memory, 3)) << 24) |
+      (static_cast<uint64>(Get8(memory, 4)) << 32) |
+      (static_cast<uint64>(Get8(memory, 5)) << 40) |
+      (static_cast<uint64>(Get8(memory, 6)) << 48) |
+      (static_cast<uint64>(Get8(memory, 7)) << 56);
+}
+
+// Check if the current host is big endian.
+inline bool IsHostBigEndian() {
+  static const int number = 1;
+  return 0 == *reinterpret_cast<const char*>(&number);
+}
+
+inline uint16 HostToNetwork16(uint16 n) {
+  uint16 result;
+  SetBE16(&result, n);
+  return result;
+}
+
+inline uint32 HostToNetwork32(uint32 n) {
+  uint32 result;
+  SetBE32(&result, n);
+  return result;
+}
+
+inline uint64 HostToNetwork64(uint64 n) {
+  uint64 result;
+  SetBE64(&result, n);
+  return result;
+}
+
+inline uint16 NetworkToHost16(uint16 n) {
+  return GetBE16(&n);
+}
+
+inline uint32 NetworkToHost32(uint32 n) {
+  return GetBE32(&n);
+}
+
+inline uint64 NetworkToHost64(uint64 n) {
+  return GetBE64(&n);
+}
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_BYTEORDER_H_
diff --git a/talk/base/byteorder_unittest.cc b/talk/base/byteorder_unittest.cc
new file mode 100644
index 0000000..01dd26a
--- /dev/null
+++ b/talk/base/byteorder_unittest.cc
@@ -0,0 +1,100 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/basictypes.h"
+#include "talk/base/gunit.h"
+
+namespace talk_base {
+
+// Test memory set functions put values into memory in expected order.
+TEST(ByteOrderTest, TestSet) {
+  uint8 buf[8] = { 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u };
+  Set8(buf, 0, 0xfb);
+  Set8(buf, 1, 0x12);
+  EXPECT_EQ(0xfb, buf[0]);
+  EXPECT_EQ(0x12, buf[1]);
+  SetBE16(buf, 0x1234);
+  EXPECT_EQ(0x12, buf[0]);
+  EXPECT_EQ(0x34, buf[1]);
+  SetLE16(buf, 0x1234);
+  EXPECT_EQ(0x34, buf[0]);
+  EXPECT_EQ(0x12, buf[1]);
+  SetBE32(buf, 0x12345678);
+  EXPECT_EQ(0x12, buf[0]);
+  EXPECT_EQ(0x34, buf[1]);
+  EXPECT_EQ(0x56, buf[2]);
+  EXPECT_EQ(0x78, buf[3]);
+  SetLE32(buf, 0x12345678);
+  EXPECT_EQ(0x78, buf[0]);
+  EXPECT_EQ(0x56, buf[1]);
+  EXPECT_EQ(0x34, buf[2]);
+  EXPECT_EQ(0x12, buf[3]);
+  SetBE64(buf, UINT64_C(0x0123456789abcdef));
+  EXPECT_EQ(0x01, buf[0]);
+  EXPECT_EQ(0x23, buf[1]);
+  EXPECT_EQ(0x45, buf[2]);
+  EXPECT_EQ(0x67, buf[3]);
+  EXPECT_EQ(0x89, buf[4]);
+  EXPECT_EQ(0xab, buf[5]);
+  EXPECT_EQ(0xcd, buf[6]);
+  EXPECT_EQ(0xef, buf[7]);
+  SetLE64(buf, UINT64_C(0x0123456789abcdef));
+  EXPECT_EQ(0xef, buf[0]);
+  EXPECT_EQ(0xcd, buf[1]);
+  EXPECT_EQ(0xab, buf[2]);
+  EXPECT_EQ(0x89, buf[3]);
+  EXPECT_EQ(0x67, buf[4]);
+  EXPECT_EQ(0x45, buf[5]);
+  EXPECT_EQ(0x23, buf[6]);
+  EXPECT_EQ(0x01, buf[7]);
+}
+
+// Test memory get functions get values from memory in expected order.
+TEST(ByteOrderTest, TestGet) {
+  uint8 buf[8];
+  buf[0] = 0x01u;
+  buf[1] = 0x23u;
+  buf[2] = 0x45u;
+  buf[3] = 0x67u;
+  buf[4] = 0x89u;
+  buf[5] = 0xabu;
+  buf[6] = 0xcdu;
+  buf[7] = 0xefu;
+  EXPECT_EQ(0x01u, Get8(buf, 0));
+  EXPECT_EQ(0x23u, Get8(buf, 1));
+  EXPECT_EQ(0x0123u, GetBE16(buf));
+  EXPECT_EQ(0x2301u, GetLE16(buf));
+  EXPECT_EQ(0x01234567u, GetBE32(buf));
+  EXPECT_EQ(0x67452301u, GetLE32(buf));
+  EXPECT_EQ(UINT64_C(0x0123456789abcdef), GetBE64(buf));
+  EXPECT_EQ(UINT64_C(0xefcdab8967452301), GetLE64(buf));
+}
+
+}  // namespace talk_base
+
diff --git a/talk/base/checks.cc b/talk/base/checks.cc
new file mode 100644
index 0000000..5466783
--- /dev/null
+++ b/talk/base/checks.cc
@@ -0,0 +1,47 @@
+/*
+ * libjingle
+ * Copyright 2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include "talk/base/checks.h"
+#include "talk/base/logging.h"
+
+void Fatal(const char* file, int line, const char* format, ...) {
+  char msg[256];
+
+  va_list arguments;
+  va_start(arguments, format);
+  vsnprintf(msg, sizeof(msg), format, arguments);
+  va_end(arguments);
+
+  LOG(LS_ERROR) << "\n\n#\n# Fatal error in " << file
+                << ", line " << line << "\n#" << msg
+                << "\n#\n";
+  abort();
+}
diff --git a/talk/base/checks.h b/talk/base/checks.h
new file mode 100644
index 0000000..83ad372
--- /dev/null
+++ b/talk/base/checks.h
@@ -0,0 +1,44 @@
+/*
+ * libjingle
+ * Copyright 2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// This module contains some basic debugging facilities.
+// Originally comes from shared/commandlineflags/checks.h
+
+#ifndef TALK_BASE_CHECKS_H_
+#define TALK_BASE_CHECKS_H_
+
+#include <string.h>
+
+// Prints an error message to stderr and aborts execution.
+void Fatal(const char* file, int line, const char* format, ...);
+
+
+// The UNREACHABLE macro is very useful during development.
+#define UNREACHABLE()                                   \
+  Fatal(__FILE__, __LINE__, "unreachable code")
+
+#endif  // TALK_BASE_CHECKS_H_
diff --git a/talk/base/common.cc b/talk/base/common.cc
new file mode 100644
index 0000000..842d925
--- /dev/null
+++ b/talk/base/common.cc
@@ -0,0 +1,80 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <memory.h>
+
+#if WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#endif  // WIN32
+
+#if OSX
+#include <CoreServices/CoreServices.h>
+#endif  // OSX
+
+#include <algorithm>
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+
+//////////////////////////////////////////////////////////////////////
+// Assertions
+//////////////////////////////////////////////////////////////////////
+
+namespace talk_base {
+
+void Break() {
+#if WIN32
+  ::DebugBreak();
+#elif OSX  // !WIN32
+  __asm__("int $3");
+#else // !OSX && !WIN32
+#if _DEBUG_HAVE_BACKTRACE
+  OutputTrace();
+#endif
+  abort();
+#endif // !OSX && !WIN32
+}
+
+static AssertLogger custom_assert_logger_ = NULL;
+
+void SetCustomAssertLogger(AssertLogger logger) {
+  custom_assert_logger_ = logger;
+}
+
+void LogAssert(const char* function, const char* file, int line,
+               const char* expression) {
+  if (custom_assert_logger_) {
+    custom_assert_logger_(function, file, line, expression);
+  } else {
+    LOG(LS_ERROR) << file << "(" << line << ")" << ": ASSERT FAILED: "
+                  << expression << " @ " << function;
+  }
+}
+
+} // namespace talk_base
diff --git a/talk/base/common.h b/talk/base/common.h
new file mode 100644
index 0000000..d624ddc
--- /dev/null
+++ b/talk/base/common.h
@@ -0,0 +1,188 @@
+/*
+ * 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_BASE_COMMON_H_
+#define TALK_BASE_COMMON_H_
+
+#include "talk/base/basictypes.h"
+#include "talk/base/constructormagic.h"
+
+#if defined(_MSC_VER)
+// warning C4355: 'this' : used in base member initializer list
+#pragma warning(disable:4355)
+#endif
+
+//////////////////////////////////////////////////////////////////////
+// General Utilities
+//////////////////////////////////////////////////////////////////////
+
+// Note: UNUSED is also defined in basictypes.h
+#ifndef UNUSED
+#define UNUSED(x) Unused(static_cast<const void*>(&x))
+#define UNUSED2(x, y) Unused(static_cast<const void*>(&x)); \
+    Unused(static_cast<const void*>(&y))
+#define UNUSED3(x, y, z) Unused(static_cast<const void*>(&x)); \
+    Unused(static_cast<const void*>(&y)); \
+    Unused(static_cast<const void*>(&z))
+#define UNUSED4(x, y, z, a) Unused(static_cast<const void*>(&x)); \
+    Unused(static_cast<const void*>(&y)); \
+    Unused(static_cast<const void*>(&z)); \
+    Unused(static_cast<const void*>(&a))
+#define UNUSED5(x, y, z, a, b) Unused(static_cast<const void*>(&x)); \
+    Unused(static_cast<const void*>(&y)); \
+    Unused(static_cast<const void*>(&z)); \
+    Unused(static_cast<const void*>(&a)); \
+    Unused(static_cast<const void*>(&b))
+inline void Unused(const void*) {}
+#endif  // UNUSED
+
+#ifndef WIN32
+#define strnicmp(x, y, n) strncasecmp(x, y, n)
+#define stricmp(x, y) strcasecmp(x, y)
+
+// TODO: Remove this. std::max should be used everywhere in the code.
+// NOMINMAX must be defined where we include <windows.h>.
+#define stdmax(x, y) std::max(x, y)
+#else
+#define stdmax(x, y) talk_base::_max(x, y)
+#endif
+
+#define ARRAY_SIZE(x) (static_cast<int>(sizeof(x) / sizeof(x[0])))
+
+/////////////////////////////////////////////////////////////////////////////
+// Assertions
+/////////////////////////////////////////////////////////////////////////////
+
+#ifndef ENABLE_DEBUG
+#define ENABLE_DEBUG _DEBUG
+#endif  // !defined(ENABLE_DEBUG)
+
+// Even for release builds, allow for the override of LogAssert. Though no
+// macro is provided, this can still be used for explicit runtime asserts
+// and allow applications to override the assert behavior.
+
+namespace talk_base {
+
+// LogAssert writes information about an assertion to the log. It's called by
+// Assert (and from the ASSERT macro in debug mode) before any other action
+// is taken (e.g. breaking the debugger, abort()ing, etc.).
+void LogAssert(const char* function, const char* file, int line,
+               const char* expression);
+
+typedef void (*AssertLogger)(const char* function,
+                             const char* file,
+                             int line,
+                             const char* expression);
+
+// Sets a custom assert logger to be used instead of the default LogAssert
+// behavior. To clear the custom assert logger, pass NULL for |logger| and the
+// default behavior will be restored. Only one custom assert logger can be set
+// at a time, so this should generally be set during application startup and
+// only by one component.
+void SetCustomAssertLogger(AssertLogger logger);
+
+}  // namespace talk_base
+
+
+#if ENABLE_DEBUG
+
+namespace talk_base {
+
+// Break causes the debugger to stop executing, or the program to abort.
+void Break();
+
+inline bool Assert(bool result, const char* function, const char* file,
+                   int line, const char* expression) {
+  if (!result) {
+    LogAssert(function, file, line, expression);
+    Break();
+    return false;
+  }
+  return true;
+}
+
+}  // namespace talk_base
+
+#if defined(_MSC_VER) && _MSC_VER < 1300
+#define __FUNCTION__ ""
+#endif
+
+#ifndef ASSERT
+#define ASSERT(x) \
+    (void)talk_base::Assert((x), __FUNCTION__, __FILE__, __LINE__, #x)
+#endif
+
+#ifndef VERIFY
+#define VERIFY(x) talk_base::Assert((x), __FUNCTION__, __FILE__, __LINE__, #x)
+#endif
+
+#else  // !ENABLE_DEBUG
+
+namespace talk_base {
+
+inline bool ImplicitCastToBool(bool result) { return result; }
+
+}  // namespace talk_base
+
+#ifndef ASSERT
+#define ASSERT(x) (void)0
+#endif
+
+#ifndef VERIFY
+#define VERIFY(x) talk_base::ImplicitCastToBool(x)
+#endif
+
+#endif  // !ENABLE_DEBUG
+
+#define COMPILE_TIME_ASSERT(expr)       char CTA_UNIQUE_NAME[expr]
+#define CTA_UNIQUE_NAME                 CTA_MAKE_NAME(__LINE__)
+#define CTA_MAKE_NAME(line)             CTA_MAKE_NAME2(line)
+#define CTA_MAKE_NAME2(line)            constraint_ ## line
+
+// Forces compiler to inline, even against its better judgement. Use wisely.
+#if defined(__GNUC__)
+#define FORCE_INLINE __attribute__((always_inline))
+#elif defined(WIN32)
+#define FORCE_INLINE __forceinline
+#else
+#define FORCE_INLINE
+#endif
+
+// Borrowed from Chromium's base/compiler_specific.h.
+// Annotate a virtual method indicating it must be overriding a virtual
+// method in the parent class.
+// Use like:
+//   virtual void foo() OVERRIDE;
+#if defined(WIN32)
+#define OVERRIDE override
+#elif defined(__clang__)
+#define OVERRIDE override
+#else
+#define OVERRIDE
+#endif
+
+#endif  // TALK_BASE_COMMON_H_
diff --git a/talk/base/constructormagic.h b/talk/base/constructormagic.h
new file mode 100644
index 0000000..8b1f7ff
--- /dev/null
+++ b/talk/base/constructormagic.h
@@ -0,0 +1,55 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_CONSTRUCTORMAGIC_H_
+#define TALK_BASE_CONSTRUCTORMAGIC_H_
+
+#define DISALLOW_ASSIGN(TypeName) \
+  void operator=(const TypeName&)
+
+// A macro to disallow the evil copy constructor and operator= functions
+// This should be used in the private: declarations for a class
+#define DISALLOW_COPY_AND_ASSIGN(TypeName)    \
+  TypeName(const TypeName&);                    \
+  DISALLOW_ASSIGN(TypeName)
+
+// Alternative, less-accurate legacy name.
+#define DISALLOW_EVIL_CONSTRUCTORS(TypeName) \
+  DISALLOW_COPY_AND_ASSIGN(TypeName)
+
+// A macro to disallow all the implicit constructors, namely the
+// default constructor, copy constructor and operator= functions.
+//
+// This should be used in the private: declarations for a class
+// that wants to prevent anyone from instantiating it. This is
+// especially useful for classes containing only static methods.
+#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \
+  TypeName();                                    \
+  DISALLOW_EVIL_CONSTRUCTORS(TypeName)
+
+
+#endif  // TALK_BASE_CONSTRUCTORMAGIC_H_
diff --git a/talk/base/cpumonitor.cc b/talk/base/cpumonitor.cc
new file mode 100644
index 0000000..0a70887
--- /dev/null
+++ b/talk/base/cpumonitor.cc
@@ -0,0 +1,423 @@
+/*
+ * 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_) {
+    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_) {
+    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) {
+}
+
+CpuMonitor::~CpuMonitor() {
+  Stop();
+}
+
+void CpuMonitor::set_thread(Thread* thread) {
+  ASSERT(monitor_thread_ == NULL || monitor_thread_ == thread);
+  monitor_thread_ = thread;
+}
+
+bool CpuMonitor::Start(int period_ms) {
+  if (!monitor_thread_  || !sampler_.Init()) return false;
+
+  monitor_thread_->SignalQueueDestroyed.connect(
+       this, &CpuMonitor::OnMessageQueueDestroyed);
+
+  period_ms_ = period_ms;
+  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..e0c3655
--- /dev/null
+++ b/talk/base/cpumonitor.h
@@ -0,0 +1,140 @@
+/*
+ * 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();
+  void set_thread(Thread* thread);
+
+  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..952b89e
--- /dev/null
+++ b/talk/base/cpumonitor_unittest.cc
@@ -0,0 +1,402 @@
+/*
+ * 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.
+static const int kLongInterval = 2000;  // Interval longer than busy times
+
+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:      "
+                 << std::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:       "
+                 << std::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:  "
+                 << std::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:   "
+                 << std::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:      "
+                 << std::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:       "
+                 << std::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:"
+                   << std::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: "
+                   << std::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:      "
+                 << std::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:       "
+                 << std::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: Set interval to large value so sampler will not update.
+  sampler.set_load_interval(kLongInterval);
+
+  sampler.GetProcessLoad();
+  sampler.GetSystemLoad();
+
+  float proc_orig = sampler.GetProcessLoad();
+  float sys_orig = sampler.GetSystemLoad();
+
+  Thread::SleepMs(kIdleTime);
+
+  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/crc32.cc b/talk/base/crc32.cc
new file mode 100644
index 0000000..82998ee
--- /dev/null
+++ b/talk/base/crc32.cc
@@ -0,0 +1,69 @@
+/*
+ * libjingle
+ * Copyright 2012, Google, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/crc32.h"
+
+#include "talk/base/basicdefs.h"
+
+namespace talk_base {
+
+// This implementation is based on the sample implementation in RFC 1952.
+
+// CRC32 polynomial, in reversed form.
+// See RFC 1952, or http://en.wikipedia.org/wiki/Cyclic_redundancy_check
+static const uint32 kCrc32Polynomial = 0xEDB88320;
+static uint32 kCrc32Table[256] = { 0 };
+
+static void EnsureCrc32TableInited() {
+  if (kCrc32Table[ARRAY_SIZE(kCrc32Table) - 1])
+    return;  // already inited
+  for (uint32 i = 0; i < ARRAY_SIZE(kCrc32Table); ++i) {
+    uint32 c = i;
+    for (size_t j = 0; j < 8; ++j) {
+      if (c & 1) {
+        c = kCrc32Polynomial ^ (c >> 1);
+      } else {
+        c >>= 1;
+      }
+    }
+    kCrc32Table[i] = c;
+  }
+}
+
+uint32 UpdateCrc32(uint32 start, const void* buf, size_t len) {
+  EnsureCrc32TableInited();
+
+  uint32 c = start ^ 0xFFFFFFFF;
+  const uint8* u = static_cast<const uint8*>(buf);
+  for (size_t i = 0; i < len; ++i) {
+    c = kCrc32Table[(c ^ u[i]) & 0xFF] ^ (c >> 8);
+  }
+  return c ^ 0xFFFFFFFF;
+}
+
+}  // namespace talk_base
+
diff --git a/talk/base/crc32.h b/talk/base/crc32.h
new file mode 100644
index 0000000..144158b
--- /dev/null
+++ b/talk/base/crc32.h
@@ -0,0 +1,51 @@
+/*
+ * libjingle
+ * Copyright 2012, Google, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_CRC32_H_
+#define TALK_BASE_CRC32_H_
+
+#include <string>
+
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+// Updates a CRC32 checksum with |len| bytes from |buf|. |initial| holds the
+// checksum result from the previous update; for the first call, it should be 0.
+uint32 UpdateCrc32(uint32 initial, const void* buf, size_t len);
+
+// Computes a CRC32 checksum using |len| bytes from |buf|.
+inline uint32 ComputeCrc32(const void* buf, size_t len) {
+  return UpdateCrc32(0, buf, len);
+}
+inline uint32 ComputeCrc32(const std::string& str) {
+  return ComputeCrc32(str.c_str(), str.size());
+}
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_CRC32_H_
diff --git a/talk/base/crc32_unittest.cc b/talk/base/crc32_unittest.cc
new file mode 100644
index 0000000..24333d3
--- /dev/null
+++ b/talk/base/crc32_unittest.cc
@@ -0,0 +1,52 @@
+/*
+ * libjingle
+ * Copyright 2012, Google, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/crc32.h"
+#include "talk/base/gunit.h"
+
+#include <string>
+
+namespace talk_base {
+
+TEST(Crc32Test, TestBasic) {
+  EXPECT_EQ(0U, ComputeCrc32(""));
+  EXPECT_EQ(0x352441C2U, ComputeCrc32("abc"));
+  EXPECT_EQ(0x171A3F5FU,
+      ComputeCrc32("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"));
+}
+
+TEST(Crc32Test, TestMultipleUpdates) {
+  std::string input =
+      "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq";
+  uint32 c = 0;
+  for (size_t i = 0; i < input.size(); ++i) {
+    c = UpdateCrc32(c, &input[i], 1);
+  }
+  EXPECT_EQ(0x171A3F5FU, c);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/criticalsection.h b/talk/base/criticalsection.h
new file mode 100644
index 0000000..c6ffbc0
--- /dev/null
+++ b/talk/base/criticalsection.h
@@ -0,0 +1,196 @@
+/*
+ * 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_BASE_CRITICALSECTION_H__
+#define TALK_BASE_CRITICALSECTION_H__
+
+#include "talk/base/constructormagic.h"
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#endif
+
+#ifdef POSIX
+#include <pthread.h>
+#endif
+
+#ifdef _DEBUG
+#define CS_TRACK_OWNER 1
+#endif  // _DEBUG
+
+#if CS_TRACK_OWNER
+#define TRACK_OWNER(x) x
+#else  // !CS_TRACK_OWNER
+#define TRACK_OWNER(x)
+#endif  // !CS_TRACK_OWNER
+
+namespace talk_base {
+
+#ifdef WIN32
+class CriticalSection {
+ public:
+  CriticalSection() {
+    InitializeCriticalSection(&crit_);
+    // Windows docs say 0 is not a valid thread id
+    TRACK_OWNER(thread_ = 0);
+  }
+  ~CriticalSection() {
+    DeleteCriticalSection(&crit_);
+  }
+  void Enter() {
+    EnterCriticalSection(&crit_);
+    TRACK_OWNER(thread_ = GetCurrentThreadId());
+  }
+  bool TryEnter() {
+    if (TryEnterCriticalSection(&crit_) != FALSE) {
+      TRACK_OWNER(thread_ = GetCurrentThreadId());
+      return true;
+    }
+    return false;
+  }
+  void Leave() {
+    TRACK_OWNER(thread_ = 0);
+    LeaveCriticalSection(&crit_);
+  }
+
+#if CS_TRACK_OWNER
+  bool CurrentThreadIsOwner() const { return thread_ == GetCurrentThreadId(); }
+#endif  // CS_TRACK_OWNER
+
+ private:
+  CRITICAL_SECTION crit_;
+  TRACK_OWNER(DWORD thread_);  // The section's owning thread id
+};
+#endif // WIN32
+
+#ifdef POSIX
+class CriticalSection {
+ public:
+  CriticalSection() {
+    pthread_mutexattr_t mutex_attribute;
+    pthread_mutexattr_init(&mutex_attribute);
+    pthread_mutexattr_settype(&mutex_attribute, PTHREAD_MUTEX_RECURSIVE);
+    pthread_mutex_init(&mutex_, &mutex_attribute);
+    pthread_mutexattr_destroy(&mutex_attribute);
+    TRACK_OWNER(thread_ = 0);
+  }
+  ~CriticalSection() {
+    pthread_mutex_destroy(&mutex_);
+  }
+  void Enter() {
+    pthread_mutex_lock(&mutex_);
+    TRACK_OWNER(thread_ = pthread_self());
+  }
+  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_);
+  }
+
+#if CS_TRACK_OWNER
+  bool CurrentThreadIsOwner() const { return pthread_equal(thread_, pthread_self()); }
+#endif  // CS_TRACK_OWNER
+
+ private:
+  pthread_mutex_t mutex_;
+  TRACK_OWNER(pthread_t thread_);
+};
+#endif // POSIX
+
+// CritScope, for serializing execution through a scope.
+class CritScope {
+ public:
+  explicit CritScope(CriticalSection *pcrit) {
+    pcrit_ = pcrit;
+    pcrit_->Enter();
+  }
+  ~CritScope() {
+    pcrit_->Leave();
+  }
+ private:
+  CriticalSection *pcrit_;
+  DISALLOW_COPY_AND_ASSIGN(CritScope);
+};
+
+// Tries to lock a critical section on construction via
+// CriticalSection::TryEnter, and unlocks on destruction if the
+// lock was taken. Never blocks.
+//
+// IMPORTANT: Unlike CritScope, the lock may not be owned by this thread in
+// subsequent code. Users *must* check locked() to determine if the
+// lock was taken. If you're not calling locked(), you're doing it wrong!
+class TryCritScope {
+ public:
+  explicit TryCritScope(CriticalSection *pcrit) {
+    pcrit_ = pcrit;
+    locked_ = pcrit_->TryEnter();
+  }
+  ~TryCritScope() {
+    if (locked_) {
+      pcrit_->Leave();
+    }
+  }
+  bool locked() const {
+    return locked_;
+  }
+ private:
+  CriticalSection *pcrit_;
+  bool locked_;
+  DISALLOW_COPY_AND_ASSIGN(TryCritScope);
+};
+
+// TODO: Move this to atomicops.h, which can't be done easily because of
+// complex compile rules.
+class AtomicOps {
+ public:
+#ifdef WIN32
+  // Assumes sizeof(int) == sizeof(LONG), which it is on Win32 and Win64.
+  static int Increment(int* i) {
+    return ::InterlockedIncrement(reinterpret_cast<LONG*>(i));
+  }
+  static int Decrement(int* i) {
+    return ::InterlockedDecrement(reinterpret_cast<LONG*>(i));
+  }
+#else
+  static int Increment(int* i) {
+    return __sync_add_and_fetch(i, 1);
+  }
+  static int Decrement(int* i) {
+    return __sync_sub_and_fetch(i, 1);
+  }
+#endif
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_CRITICALSECTION_H__
diff --git a/talk/base/cryptstring.h b/talk/base/cryptstring.h
new file mode 100644
index 0000000..eb39be2
--- /dev/null
+++ b/talk/base/cryptstring.h
@@ -0,0 +1,198 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _TALK_BASE_CRYPTSTRING_H_
+#define _TALK_BASE_CRYPTSTRING_H_
+
+#include <cstring>
+#include <string>
+#include <vector>
+#include "talk/base/linked_ptr.h"
+#include "talk/base/scoped_ptr.h"
+
+namespace talk_base {
+
+class CryptStringImpl {
+public:
+  virtual ~CryptStringImpl() {}
+  virtual size_t GetLength() const = 0;
+  virtual void CopyTo(char * dest, bool nullterminate) const = 0;
+  virtual std::string UrlEncode() const = 0;
+  virtual CryptStringImpl * Copy() const = 0;
+  virtual void CopyRawTo(std::vector<unsigned char> * dest) const = 0;
+};
+
+class EmptyCryptStringImpl : public CryptStringImpl {
+public:
+  virtual ~EmptyCryptStringImpl() {}
+  virtual size_t GetLength() const { return 0; }
+  virtual void CopyTo(char * dest, bool nullterminate) const {
+    if (nullterminate) {
+      *dest = '\0';
+    }
+  }
+  virtual std::string UrlEncode() const { return ""; }
+  virtual CryptStringImpl * Copy() const { return new EmptyCryptStringImpl(); }
+  virtual void CopyRawTo(std::vector<unsigned char> * dest) const {
+    dest->clear();
+  }
+};
+
+class CryptString {
+public:
+  CryptString() : impl_(new EmptyCryptStringImpl()) {}
+  size_t GetLength() const { return impl_->GetLength(); }
+  void CopyTo(char * dest, bool nullterminate) const { impl_->CopyTo(dest, nullterminate); }
+  CryptString(const CryptString & other) : impl_(other.impl_->Copy()) {}
+  explicit CryptString(const CryptStringImpl & impl) : impl_(impl.Copy()) {}
+  CryptString & operator=(const CryptString & other) {
+    if (this != &other) {
+      impl_.reset(other.impl_->Copy());
+    }
+    return *this;
+  }
+  void Clear() { impl_.reset(new EmptyCryptStringImpl()); }
+  std::string UrlEncode() const { return impl_->UrlEncode(); }
+  void CopyRawTo(std::vector<unsigned char> * dest) const {
+    return impl_->CopyRawTo(dest);
+  }
+  
+private:
+  scoped_ptr<const CryptStringImpl> impl_;
+};
+
+
+// Used for constructing strings where a password is involved and we
+// need to ensure that we zero memory afterwards
+class FormatCryptString {
+public:
+  FormatCryptString() {
+    storage_ = new char[32];
+    capacity_ = 32;
+    length_ = 0;
+    storage_[0] = 0;
+  }
+  
+  void Append(const std::string & text) {
+    Append(text.data(), text.length());
+  }
+
+  void Append(const char * data, size_t length) {
+    EnsureStorage(length_ + length + 1);
+    memcpy(storage_ + length_, data, length);
+    length_ += length;
+    storage_[length_] = '\0';
+  }
+  
+  void Append(const CryptString * password) {
+    size_t len = password->GetLength();
+    EnsureStorage(length_ + len + 1);
+    password->CopyTo(storage_ + length_, true);
+    length_ += len;
+  }
+
+  size_t GetLength() {
+    return length_;
+  }
+
+  const char * GetData() {
+    return storage_;
+  }
+
+
+  // Ensures storage of at least n bytes
+  void EnsureStorage(size_t n) {
+    if (capacity_ >= n) {
+      return;
+    }
+
+    size_t old_capacity = capacity_;
+    char * old_storage = storage_;
+
+    for (;;) {
+      capacity_ *= 2;
+      if (capacity_ >= n)
+        break;
+    }
+
+    storage_ = new char[capacity_];
+
+    if (old_capacity) {
+      memcpy(storage_, old_storage, length_);
+    
+      // zero memory in a way that an optimizer won't optimize it out
+      old_storage[0] = 0;
+      for (size_t i = 1; i < old_capacity; i++) {
+        old_storage[i] = old_storage[i - 1];
+      }
+      delete[] old_storage;
+    }
+  }  
+
+  ~FormatCryptString() {
+    if (capacity_) {
+      storage_[0] = 0;
+      for (size_t i = 1; i < capacity_; i++) {
+        storage_[i] = storage_[i - 1];
+      }
+    }
+    delete[] storage_;
+  }
+private:
+  char * storage_;
+  size_t capacity_;
+  size_t length_;
+};
+
+class InsecureCryptStringImpl : public CryptStringImpl {
+ public:
+  std::string& password() { return password_; }
+  const std::string& password() const { return password_; }
+
+  virtual ~InsecureCryptStringImpl() {}
+  virtual size_t GetLength() const { return password_.size(); }
+  virtual void CopyTo(char * dest, bool nullterminate) const {
+    memcpy(dest, password_.data(), password_.size());
+    if (nullterminate) dest[password_.size()] = 0;
+  }
+  virtual std::string UrlEncode() const { return password_; }
+  virtual CryptStringImpl * Copy() const {
+    InsecureCryptStringImpl * copy = new InsecureCryptStringImpl;
+    copy->password() = password_;
+    return copy;
+  }
+  virtual void CopyRawTo(std::vector<unsigned char> * dest) const {
+    dest->resize(password_.size());
+    memcpy(&dest->front(), password_.data(), password_.size());
+  }
+ private:
+  std::string password_;
+};
+
+}
+
+#endif  // _TALK_BASE_CRYPTSTRING_H_
diff --git a/talk/base/dbus.cc b/talk/base/dbus.cc
new file mode 100644
index 0000000..8e071c7
--- /dev/null
+++ b/talk/base/dbus.cc
@@ -0,0 +1,409 @@
+/*
+ * 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 <glib.h>
+
+#include "talk/base/logging.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);
+    }
+  }
+}
+
+inline static LibDBusGlibSymbolTable *GetSymbols() {
+  return DBusMonitor::GetDBusGlibSymbolTable();
+}
+
+// Implementation of class DBusSigMessageData
+DBusSigMessageData::DBusSigMessageData(DBusMessage *message)
+    : TypedMessageData<DBusMessage *>(message) {
+  GetSymbols()->dbus_message_ref()(data());
+}
+
+DBusSigMessageData::~DBusSigMessageData() {
+  GetSymbols()->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);
+    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_ = g_idle_source_new();
+    if (idle_source_) {
+      g_source_set_callback(idle_source_, &Idle, this, NULL);
+      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 != GetSymbols()->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;
+      }
+
+      GetSymbols()->dbus_bus_add_match()(
+          GetSymbols()->dbus_g_connection_get_connection()(connection_),
+          filter->filter().c_str(), NULL);
+
+      if (!GetSymbols()->dbus_connection_add_filter()(
+              GetSymbols()->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 != GetSymbols()->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;
+      }
+      GetSymbols()->dbus_connection_remove_filter()(
+          GetSymbols()->dbus_g_connection_get_connection()(connection_),
+          &DBusSigFilter::DBusCallback, filter);
+    }
+  }
+
+  // Sets up the monitoring thread.
+  bool Setup() {
+    g_main_context_push_thread_default(context_);
+
+    // Start connection to dbus.
+    // If dbus daemon is not running, returns false immediately.
+    connection_ = GetSymbols()->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 == GetSymbols()->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.
+    GetSymbols()->dbus_connection_set_exit_on_disconnect()(
+        GetSymbols()->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.
+      g_source_destroy(idle_source_);
+      // We need to unref() the GSource to end the last reference we got.
+      g_source_unref(idle_source_);
+      idle_source_ = NULL;
+    }
+    if (connection_) {
+      if (GetSymbols()->dbus_g_connection_get_connection()(connection_)) {
+        UnRegisterAllFilters();
+        GetSymbols()->dbus_connection_close()(
+            GetSymbols()->dbus_g_connection_get_connection()(connection_));
+      }
+      GetSymbols()->dbus_g_connection_unref()(connection_);
+      connection_ = NULL;
+    }
+    g_main_loop_unref(mainloop_);
+    mainloop_ = NULL;
+    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() {
+    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_) {
+    g_type_init();
+    g_thread_init(NULL);
+    GetSymbols()->dbus_g_thread_init()();
+
+    GMainContext *context = g_main_context_new();
+    if (NULL == context) {
+      LOG(LS_ERROR) << "g_main_context_new() failed.";
+      return false;
+    }
+
+    GMainLoop *mainloop = g_main_loop_new(context, FALSE);
+    if (NULL == mainloop) {
+      LOG(LS_ERROR) << "g_main_loop_new() failed.";
+      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.";
+      g_main_context_unref(context);
+      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..94f01c0
--- /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) {
+    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) {
+    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) {
+    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) {
+    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) {
+    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) {
+    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) {
+    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.cc b/talk/base/diskcache.cc
new file mode 100644
index 0000000..afaf9d2
--- /dev/null
+++ b/talk/base/diskcache.cc
@@ -0,0 +1,364 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <time.h>
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#endif
+
+#include "talk/base/common.h"
+#include "talk/base/diskcache.h"
+#include "talk/base/fileutils.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+
+#ifdef _DEBUG
+#define TRANSPARENT_CACHE_NAMES 1
+#else  // !_DEBUG
+#define TRANSPARENT_CACHE_NAMES 0
+#endif  // !_DEBUG
+
+namespace talk_base {
+
+class DiskCache;
+
+///////////////////////////////////////////////////////////////////////////////
+// DiskCacheAdapter
+///////////////////////////////////////////////////////////////////////////////
+
+class DiskCacheAdapter : public StreamAdapterInterface {
+public:
+  DiskCacheAdapter(const DiskCache* cache, const std::string& id, size_t index,
+                   StreamInterface* stream)
+  : StreamAdapterInterface(stream), cache_(cache), id_(id), index_(index)
+  { }
+  virtual ~DiskCacheAdapter() {
+    Close();
+    cache_->ReleaseResource(id_, index_);
+  }
+
+private:
+  const DiskCache* cache_;
+  std::string id_;
+  size_t index_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// DiskCache
+///////////////////////////////////////////////////////////////////////////////
+
+DiskCache::DiskCache() : max_cache_(0), total_size_(0), total_accessors_(0) {
+}
+
+DiskCache::~DiskCache() {
+  ASSERT(0 == total_accessors_);
+}
+
+bool DiskCache::Initialize(const std::string& folder, size_t size) {
+  if (!folder_.empty() || !Filesystem::CreateFolder(folder))
+    return false;
+
+  folder_ = folder;
+  max_cache_ = size;
+  ASSERT(0 == total_size_);
+
+  if (!InitializeEntries())
+    return false;
+
+  return CheckLimit();
+}
+
+bool DiskCache::Purge() {
+  if (folder_.empty())
+    return false;
+
+  if (total_accessors_ > 0) {
+    LOG_F(LS_WARNING) << "Cache files open";
+    return false;
+  }
+
+  if (!PurgeFiles())
+    return false;
+
+  map_.clear();
+  return true;
+}
+
+bool DiskCache::LockResource(const std::string& id) {
+  Entry* entry = GetOrCreateEntry(id, true);
+  if (LS_LOCKED == entry->lock_state)
+    return false;
+  if ((LS_UNLOCKED == entry->lock_state) && (entry->accessors > 0))
+    return false;
+  if ((total_size_ > max_cache_) && !CheckLimit()) {
+    LOG_F(LS_WARNING) << "Cache overfull";
+    return false;
+  }
+  entry->lock_state = LS_LOCKED;
+  return true;
+}
+
+StreamInterface* DiskCache::WriteResource(const std::string& id, size_t index) {
+  Entry* entry = GetOrCreateEntry(id, false);
+  if (LS_LOCKED != entry->lock_state)
+    return NULL;
+
+  size_t previous_size = 0;
+  std::string filename(IdToFilename(id, index));
+  FileStream::GetSize(filename, &previous_size);
+  ASSERT(previous_size <= entry->size);
+  if (previous_size > entry->size) {
+    previous_size = entry->size;
+  }
+
+  scoped_ptr<FileStream> file(new FileStream);
+  if (!file->Open(filename, "wb", NULL)) {
+    LOG_F(LS_ERROR) << "Couldn't create cache file";
+    return NULL;
+  }
+
+  entry->streams = stdmax(entry->streams, index + 1);
+  entry->size -= previous_size;
+  total_size_ -= previous_size;
+
+  entry->accessors += 1;
+  total_accessors_ += 1;
+  return new DiskCacheAdapter(this, id, index, file.release());
+}
+
+bool DiskCache::UnlockResource(const std::string& id) {
+  Entry* entry = GetOrCreateEntry(id, false);
+  if (LS_LOCKED != entry->lock_state)
+    return false;
+
+  if (entry->accessors > 0) {
+    entry->lock_state = LS_UNLOCKING;
+  } else {
+    entry->lock_state = LS_UNLOCKED;
+    entry->last_modified = time(0);
+    CheckLimit();
+  }
+  return true;
+}
+
+StreamInterface* DiskCache::ReadResource(const std::string& id,
+                                         size_t index) const {
+  const Entry* entry = GetEntry(id);
+  if (LS_UNLOCKED != entry->lock_state)
+    return NULL;
+  if (index >= entry->streams)
+    return NULL;
+
+  scoped_ptr<FileStream> file(new FileStream);
+  if (!file->Open(IdToFilename(id, index), "rb", NULL))
+    return NULL;
+
+  entry->accessors += 1;
+  total_accessors_ += 1;
+  return new DiskCacheAdapter(this, id, index, file.release());
+}
+
+bool DiskCache::HasResource(const std::string& id) const {
+  const Entry* entry = GetEntry(id);
+  return (NULL != entry) && (entry->streams > 0);
+}
+
+bool DiskCache::HasResourceStream(const std::string& id, size_t index) const {
+  const Entry* entry = GetEntry(id);
+  if ((NULL == entry) || (index >= entry->streams))
+    return false;
+
+  std::string filename = IdToFilename(id, index);
+
+  return FileExists(filename);
+}
+
+bool DiskCache::DeleteResource(const std::string& id) {
+  Entry* entry = GetOrCreateEntry(id, false);
+  if (!entry)
+    return true;
+
+  if ((LS_UNLOCKED != entry->lock_state) || (entry->accessors > 0))
+    return false;
+
+  bool success = true;
+  for (size_t index = 0; index < entry->streams; ++index) {
+    std::string filename = IdToFilename(id, index);
+
+    if (!FileExists(filename))
+      continue;
+
+    if (!DeleteFile(filename)) {
+      LOG_F(LS_ERROR) << "Couldn't remove cache file: " << filename;
+      success = false;
+    }
+  }
+
+  total_size_ -= entry->size;
+  map_.erase(id);
+  return success;
+}
+
+bool DiskCache::CheckLimit() {
+#ifdef _DEBUG
+  // Temporary check to make sure everything is working correctly.
+  size_t cache_size = 0;
+  for (EntryMap::iterator it = map_.begin(); it != map_.end(); ++it) {
+    cache_size += it->second.size;
+  }
+  ASSERT(cache_size == total_size_);
+#endif  // _DEBUG
+
+  // TODO: Replace this with a non-brain-dead algorithm for clearing out the
+  // oldest resources... something that isn't O(n^2)
+  while (total_size_ > max_cache_) {
+    EntryMap::iterator oldest = map_.end();
+    for (EntryMap::iterator it = map_.begin(); it != map_.end(); ++it) {
+      if ((LS_UNLOCKED != it->second.lock_state) || (it->second.accessors > 0))
+        continue;
+      oldest = it;
+      break;
+    }
+    if (oldest == map_.end()) {
+      LOG_F(LS_WARNING) << "All resources are locked!";
+      return false;
+    }
+    for (EntryMap::iterator it = oldest++; it != map_.end(); ++it) {
+      if (it->second.last_modified < oldest->second.last_modified) {
+        oldest = it;
+      }
+    }
+    if (!DeleteResource(oldest->first)) {
+      LOG_F(LS_ERROR) << "Couldn't delete from cache!";
+      return false;
+    }
+  }
+  return true;
+}
+
+std::string DiskCache::IdToFilename(const std::string& id, size_t index) const {
+#ifdef TRANSPARENT_CACHE_NAMES
+  // This escapes colons and other filesystem characters, so the user can't open
+  // special devices (like "COM1:"), or access other directories.
+  size_t buffer_size = id.length()*3 + 1;
+  char* buffer = new char[buffer_size];
+  encode(buffer, buffer_size, id.data(), id.length(),
+         unsafe_filename_characters(), '%');
+  // TODO: ASSERT(strlen(buffer) < FileSystem::MaxBasenameLength());
+#else  // !TRANSPARENT_CACHE_NAMES
+  // We might want to just use a hash of the filename at some point, both for
+  // obfuscation, and to avoid both filename length and escaping issues.
+  ASSERT(false);
+#endif  // !TRANSPARENT_CACHE_NAMES
+
+  char extension[32];
+  sprintfn(extension, ARRAY_SIZE(extension), ".%u", index);
+
+  Pathname pathname;
+  pathname.SetFolder(folder_);
+  pathname.SetBasename(buffer);
+  pathname.SetExtension(extension);
+
+#ifdef TRANSPARENT_CACHE_NAMES
+  delete [] buffer;
+#endif  // TRANSPARENT_CACHE_NAMES
+
+  return pathname.pathname();
+}
+
+bool DiskCache::FilenameToId(const std::string& filename, std::string* id,
+                             size_t* index) const {
+  Pathname pathname(filename);
+  unsigned tempdex;
+  if (1 != sscanf(pathname.extension().c_str(), ".%u", &tempdex))
+    return false;
+
+  *index = static_cast<size_t>(tempdex);
+
+  size_t buffer_size = pathname.basename().length() + 1;
+  char* buffer = new char[buffer_size];
+  decode(buffer, buffer_size, pathname.basename().data(),
+         pathname.basename().length(), '%');
+  id->assign(buffer);
+  delete [] buffer;
+  return true;
+}
+
+DiskCache::Entry* DiskCache::GetOrCreateEntry(const std::string& id,
+                                              bool create) {
+  EntryMap::iterator it = map_.find(id);
+  if (it != map_.end())
+    return &it->second;
+  if (!create)
+    return NULL;
+  Entry e;
+  e.lock_state = LS_UNLOCKED;
+  e.accessors = 0;
+  e.size = 0;
+  e.streams = 0;
+  e.last_modified = time(0);
+  it = map_.insert(EntryMap::value_type(id, e)).first;
+  return &it->second;
+}
+
+void DiskCache::ReleaseResource(const std::string& id, size_t index) const {
+  const Entry* entry = GetEntry(id);
+  if (!entry) {
+    LOG_F(LS_WARNING) << "Missing cache entry";
+    ASSERT(false);
+    return;
+  }
+
+  entry->accessors -= 1;
+  total_accessors_ -= 1;
+
+  if (LS_UNLOCKED != entry->lock_state) {
+    // This is safe, because locked resources only issue WriteResource, which
+    // is non-const.  Think about a better way to handle it.
+    DiskCache* this2 = const_cast<DiskCache*>(this);
+    Entry* entry2 = this2->GetOrCreateEntry(id, false);
+
+    size_t new_size = 0;
+    std::string filename(IdToFilename(id, index));
+    FileStream::GetSize(filename, &new_size);
+    entry2->size += new_size;
+    this2->total_size_ += new_size;
+
+    if ((LS_UNLOCKING == entry->lock_state) && (0 == entry->accessors)) {
+      entry2->last_modified = time(0);
+      entry2->lock_state = LS_UNLOCKED;
+      this2->CheckLimit();
+    }
+  }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/diskcache.h b/talk/base/diskcache.h
new file mode 100644
index 0000000..c5a1dfc
--- /dev/null
+++ b/talk/base/diskcache.h
@@ -0,0 +1,142 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_DISKCACHE_H__
+#define TALK_BASE_DISKCACHE_H__
+
+#include <map>
+#include <string>
+
+#ifdef WIN32
+#undef UnlockResource
+#endif  // WIN32
+
+namespace talk_base {
+
+class StreamInterface;
+
+///////////////////////////////////////////////////////////////////////////////
+// DiskCache - An LRU cache of streams, stored on disk.
+//
+// Streams are identified by a unique resource id.  Multiple streams can be
+// associated with each resource id, distinguished by an index.  When old
+// resources are flushed from the cache, all streams associated with those
+// resources are removed together.
+// DiskCache is designed to persist across executions of the program.  It is
+// safe for use from an arbitrary number of users on a single thread, but not
+// from multiple threads or other processes.
+///////////////////////////////////////////////////////////////////////////////
+
+class DiskCache {
+public:
+  DiskCache();
+  virtual ~DiskCache();
+
+  bool Initialize(const std::string& folder, size_t size);
+  bool Purge();
+
+  bool LockResource(const std::string& id);
+  StreamInterface* WriteResource(const std::string& id, size_t index);
+  bool UnlockResource(const std::string& id);
+
+  StreamInterface* ReadResource(const std::string& id, size_t index) const;
+
+  bool HasResource(const std::string& id) const;
+  bool HasResourceStream(const std::string& id, size_t index) const;
+  bool DeleteResource(const std::string& id);
+
+ protected:
+  virtual bool InitializeEntries() = 0;
+  virtual bool PurgeFiles() = 0;
+
+  virtual bool FileExists(const std::string& filename) const = 0;
+  virtual bool DeleteFile(const std::string& filename) const = 0;
+
+  enum LockState { LS_UNLOCKED, LS_LOCKED, LS_UNLOCKING };
+  struct Entry {
+    LockState lock_state;
+    mutable size_t accessors;
+    size_t size;
+    size_t streams;
+    time_t last_modified;
+  };
+  typedef std::map<std::string, Entry> EntryMap;
+  friend class DiskCacheAdapter;
+
+  bool CheckLimit();
+
+  std::string IdToFilename(const std::string& id, size_t index) const;
+  bool FilenameToId(const std::string& filename, std::string* id,
+                    size_t* index) const;
+
+  const Entry* GetEntry(const std::string& id) const {
+    return const_cast<DiskCache*>(this)->GetOrCreateEntry(id, false);
+  }
+  Entry* GetOrCreateEntry(const std::string& id, bool create);
+
+  void ReleaseResource(const std::string& id, size_t index) const;
+
+  std::string folder_;
+  size_t max_cache_, total_size_;
+  EntryMap map_;
+  mutable size_t total_accessors_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// CacheLock - Automatically manage locking and unlocking, with optional
+// rollback semantics
+///////////////////////////////////////////////////////////////////////////////
+
+class CacheLock {
+public:
+  CacheLock(DiskCache* cache, const std::string& id, bool rollback = false)
+  : cache_(cache), id_(id), rollback_(rollback)
+  {
+    locked_ = cache_->LockResource(id_);
+  }
+  ~CacheLock() {
+    if (locked_) {
+      cache_->UnlockResource(id_);
+      if (rollback_) {
+        cache_->DeleteResource(id_);
+      }
+    }
+  }
+  bool IsLocked() const { return locked_; }
+  void Commit() { rollback_ = false; }
+
+private:
+  DiskCache* cache_;
+  std::string id_;
+  bool rollback_, locked_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif // TALK_BASE_DISKCACHE_H__
diff --git a/talk/base/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/event.cc b/talk/base/event.cc
new file mode 100644
index 0000000..6089c8c
--- /dev/null
+++ b/talk/base/event.cc
@@ -0,0 +1,139 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/event.h"
+
+#if defined(WIN32)
+#include <windows.h>
+#elif defined(POSIX)
+#include <pthread.h>
+#include <sys/time.h>
+#include <time.h>
+#else
+#error "Must define either WIN32 or POSIX."
+#endif
+
+namespace talk_base {
+
+#if defined(WIN32)
+
+Event::Event(bool manual_reset, bool initially_signaled)
+    : is_manual_reset_(manual_reset),
+      is_initially_signaled_(initially_signaled) {
+  event_handle_ = ::CreateEvent(NULL,                 // Security attributes.
+                                is_manual_reset_,
+                                is_initially_signaled_,
+                                NULL);                // Name.
+  ASSERT(event_handle_ != NULL);
+}
+
+Event::~Event() {
+  CloseHandle(event_handle_);
+}
+
+void Event::Set() {
+  SetEvent(event_handle_);
+}
+
+void Event::Reset() {
+  ResetEvent(event_handle_);
+}
+
+bool Event::Wait(int cms) {
+  DWORD ms = (cms == kForever)? INFINITE : cms;
+  return (WaitForSingleObject(event_handle_, ms) == WAIT_OBJECT_0);
+}
+
+#elif defined(POSIX)
+
+Event::Event(bool manual_reset, bool initially_signaled)
+    : is_manual_reset_(manual_reset),
+      event_status_(initially_signaled) {
+  VERIFY(pthread_mutex_init(&event_mutex_, NULL) == 0);
+  VERIFY(pthread_cond_init(&event_cond_, NULL) == 0);
+}
+
+Event::~Event() {
+  pthread_mutex_destroy(&event_mutex_);
+  pthread_cond_destroy(&event_cond_);
+}
+
+void Event::Set() {
+  pthread_mutex_lock(&event_mutex_);
+  event_status_ = true;
+  pthread_cond_broadcast(&event_cond_);
+  pthread_mutex_unlock(&event_mutex_);
+}
+
+void Event::Reset() {
+  pthread_mutex_lock(&event_mutex_);
+  event_status_ = false;
+  pthread_mutex_unlock(&event_mutex_);
+}
+
+bool Event::Wait(int cms) {
+  pthread_mutex_lock(&event_mutex_);
+  int error = 0;
+
+  if (cms != kForever) {
+    // Converting from seconds and microseconds (1e-6) plus
+    // milliseconds (1e-3) to seconds and nanoseconds (1e-9).
+
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+
+    struct timespec ts;
+    ts.tv_sec = tv.tv_sec + (cms / 1000);
+    ts.tv_nsec = tv.tv_usec * 1000 + (cms % 1000) * 1000000;
+
+    // Handle overflow.
+    if (ts.tv_nsec >= 1000000000) {
+      ts.tv_sec++;
+      ts.tv_nsec -= 1000000000;
+    }
+
+    while (!event_status_ && error == 0)
+      error = pthread_cond_timedwait(&event_cond_, &event_mutex_, &ts);
+  } else {
+    while (!event_status_ && error == 0)
+      error = pthread_cond_wait(&event_cond_, &event_mutex_);
+  }
+
+  // NOTE(liulk): Exactly one thread will auto-reset this event. All
+  // the other threads will think it's unsignaled.  This seems to be
+  // consistent with auto-reset events in WIN32.
+  if (error == 0 && !is_manual_reset_)
+    event_status_ = false;
+
+  pthread_mutex_unlock(&event_mutex_);
+
+  return (error == 0);
+}
+
+#endif
+
+}  // namespace talk_base
diff --git a/talk/base/event.h b/talk/base/event.h
new file mode 100644
index 0000000..72edae8
--- /dev/null
+++ b/talk/base/event.h
@@ -0,0 +1,68 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_EVENT_H__
+#define TALK_BASE_EVENT_H__
+
+#if defined(WIN32)
+#include "talk/base/win32.h"  // NOLINT: consider this a system header.
+#elif defined(POSIX)
+#include <pthread.h>
+#else
+#error "Must define either WIN32 or POSIX."
+#endif
+
+#include "talk/base/basictypes.h"
+#include "talk/base/common.h"
+
+namespace talk_base {
+
+class Event {
+ public:
+  Event(bool manual_reset, bool initially_signaled);
+  ~Event();
+
+  void Set();
+  void Reset();
+  bool Wait(int cms);
+
+ private:
+  bool is_manual_reset_;
+
+#if defined(WIN32)
+  bool is_initially_signaled_;
+  HANDLE event_handle_;
+#elif defined(POSIX)
+  bool event_status_;
+  pthread_mutex_t event_mutex_;
+  pthread_cond_t event_cond_;
+#endif
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_EVENT_H__
diff --git a/talk/base/event_unittest.cc b/talk/base/event_unittest.cc
new file mode 100644
index 0000000..5a3c1c6
--- /dev/null
+++ b/talk/base/event_unittest.cc
@@ -0,0 +1,59 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/event.h"
+#include "talk/base/gunit.h"
+
+namespace talk_base {
+
+TEST(EventTest, InitiallySignaled) {
+  Event event(false, true);
+  ASSERT_TRUE(event.Wait(0));
+}
+
+TEST(EventTest, ManualReset) {
+  Event event(true, false);
+  ASSERT_FALSE(event.Wait(0));
+
+  event.Set();
+  ASSERT_TRUE(event.Wait(0));
+  ASSERT_TRUE(event.Wait(0));
+
+  event.Reset();
+  ASSERT_FALSE(event.Wait(0));
+}
+
+TEST(EventTest, AutoReset) {
+  Event event(false, false);
+  ASSERT_FALSE(event.Wait(0));
+
+  event.Set();
+  ASSERT_TRUE(event.Wait(0));
+  ASSERT_FALSE(event.Wait(0));
+}
+
+}  // namespace talk_base
diff --git a/talk/base/fakecpumonitor.h b/talk/base/fakecpumonitor.h
new file mode 100644
index 0000000..8b8e36d
--- /dev/null
+++ b/talk/base/fakecpumonitor.h
@@ -0,0 +1,49 @@
+/*
+ * libjingle
+ * Copyright 2013 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_FAKECPUMONITOR_H_
+#define TALK_BASE_FAKECPUMONITOR_H_
+
+#include "talk/base/cpumonitor.h"
+
+namespace talk_base {
+
+class FakeCpuMonitor : public talk_base::CpuMonitor {
+ public:
+  explicit FakeCpuMonitor(Thread* thread)
+      : CpuMonitor(thread) {
+  }
+  ~FakeCpuMonitor() {
+  }
+
+  virtual void OnMessage(talk_base::Message* msg) {
+  }
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_FAKECPUMONITOR_H_
diff --git a/talk/base/fakenetwork.h b/talk/base/fakenetwork.h
new file mode 100644
index 0000000..3bdc97f
--- /dev/null
+++ b/talk/base/fakenetwork.h
@@ -0,0 +1,136 @@
+/*
+ * 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 {
+
+const int kFakeIPv4NetworkPrefixLength = 24;
+const int kFakeIPv6NetworkPrefixLength = 64;
+
+// 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),
+        sent_first_update_(false) {
+  }
+
+  typedef std::vector<SocketAddress> IfaceList;
+
+  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 (IfaceList::iterator it = ifaces_.begin();
+         it != ifaces_.end(); ++it) {
+      if (it->EqualIPs(iface)) {
+        ifaces_.erase(it);
+        break;
+      }
+    }
+    DoUpdateNetworks();
+  }
+
+  virtual void StartUpdating() {
+    if (started_) {
+      if (sent_first_update_)
+        SignalNetworksChanged();
+      return;
+    }
+
+    started_ = true;
+    sent_first_update_ = false;
+    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 (IfaceList::iterator it = ifaces_.begin();
+         it != ifaces_.end(); ++it) {
+      int prefix_length = 0;
+      if (it->ipaddr().family() == AF_INET) {
+        prefix_length = kFakeIPv4NetworkPrefixLength;
+      } else if (it->ipaddr().family() == AF_INET6) {
+        prefix_length = kFakeIPv6NetworkPrefixLength;
+      }
+      IPAddress prefix = TruncateIP(it->ipaddr(), prefix_length);
+      scoped_ptr<Network> net(new Network(it->hostname(),
+                                          it->hostname(),
+                                          prefix,
+                                          prefix_length));
+      net->AddIP(it->ipaddr());
+      networks.push_back(net.release());
+    }
+    bool changed;
+    MergeNetworkList(networks, &changed);
+    if (changed || !sent_first_update_) {
+      SignalNetworksChanged();
+      sent_first_update_ = true;
+    }
+  }
+
+  Thread* thread_;
+  IfaceList ifaces_;
+  int next_index_;
+  bool started_;
+  bool sent_first_update_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_FAKENETWORK_H_
diff --git a/talk/base/fakesslidentity.h b/talk/base/fakesslidentity.h
new file mode 100644
index 0000000..f3c44e4
--- /dev/null
+++ b/talk/base/fakesslidentity.h
@@ -0,0 +1,69 @@
+/*
+ * libjingle
+ * Copyright 2012, The Libjingle Authors.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_FAKESSLIDENTITY_H_
+#define TALK_BASE_FAKESSLIDENTITY_H_
+
+#include "talk/base/messagedigest.h"
+#include "talk/base/sslidentity.h"
+
+namespace talk_base {
+
+class FakeSSLCertificate : public talk_base::SSLCertificate {
+ public:
+  explicit FakeSSLCertificate(const std::string& data) : data_(data) {}
+  virtual FakeSSLCertificate* GetReference() const {
+    return new FakeSSLCertificate(*this);
+  }
+  virtual std::string ToPEMString() const {
+    return data_;
+  }
+  virtual bool ComputeDigest(const std::string &algorithm,
+                             unsigned char *digest, std::size_t size,
+                             std::size_t *length) const {
+    *length = talk_base::ComputeDigest(algorithm, data_.c_str(), data_.size(),
+                                       digest, size);
+    return (*length != 0);
+  }
+ private:
+  std::string data_;
+};
+
+class FakeSSLIdentity : public talk_base::SSLIdentity {
+ public:
+  explicit FakeSSLIdentity(const std::string& data) : cert_(data) {}
+  virtual FakeSSLIdentity* GetReference() const {
+    return new FakeSSLIdentity(*this);
+  }
+  virtual const FakeSSLCertificate& certificate() const { return cert_; }
+ private:
+  FakeSSLCertificate cert_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_FAKESSLIDENTITY_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..77a474a
--- /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_) {
+    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
new file mode 100644
index 0000000..ff34147
--- /dev/null
+++ b/talk/base/fileutils.cc
@@ -0,0 +1,297 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <cassert>
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#endif
+
+#include "talk/base/pathutils.h"
+#include "talk/base/fileutils.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/stream.h"
+
+#ifdef WIN32
+#include "talk/base/win32filesystem.h"
+#else
+#include "talk/base/unixfilesystem.h"
+#endif
+
+#ifndef WIN32
+#define MAX_PATH 260
+#endif
+
+namespace talk_base {
+
+//////////////////////////
+// Directory Iterator   //
+//////////////////////////
+
+// A DirectoryIterator is created with a given directory. It originally points
+// to the first file in the directory, and can be advanecd with Next(). This
+// allows you to get information about each file.
+
+  // Constructor
+DirectoryIterator::DirectoryIterator()
+#ifdef _WIN32
+    : handle_(INVALID_HANDLE_VALUE) {
+#else
+    : dir_(NULL), dirent_(NULL) {
+#endif
+}
+
+  // Destructor
+DirectoryIterator::~DirectoryIterator() {
+#ifdef WIN32
+  if (handle_ != INVALID_HANDLE_VALUE)
+    ::FindClose(handle_);
+#else
+  if (dir_)
+    closedir(dir_);
+#endif
+}
+
+  // Starts traversing a directory.
+  // dir is the directory to traverse
+  // returns true if the directory exists and is valid
+bool DirectoryIterator::Iterate(const Pathname &dir) {
+  directory_ = dir.pathname();
+#ifdef WIN32
+  if (handle_ != INVALID_HANDLE_VALUE)
+    ::FindClose(handle_);
+  std::string d = dir.pathname() + '*';
+  handle_ = ::FindFirstFile(ToUtf16(d).c_str(), &data_);
+  if (handle_ == INVALID_HANDLE_VALUE)
+    return false;
+#else
+  if (dir_ != NULL)
+    closedir(dir_);
+  dir_ = ::opendir(directory_.c_str());
+  if (dir_ == NULL)
+    return false;
+  dirent_ = readdir(dir_);
+  if (dirent_ == NULL)
+    return false;
+
+  if (::stat(std::string(directory_ + Name()).c_str(), &stat_) != 0)
+    return false;
+#endif
+  return true;
+}
+
+  // Advances to the next file
+  // returns true if there were more files in the directory.
+bool DirectoryIterator::Next() {
+#ifdef WIN32
+  return ::FindNextFile(handle_, &data_) == TRUE;
+#else
+  dirent_ = ::readdir(dir_);
+  if (dirent_ == NULL)
+    return false;
+
+  return ::stat(std::string(directory_ + Name()).c_str(), &stat_) == 0;
+#endif
+}
+
+  // returns true if the file currently pointed to is a directory
+bool DirectoryIterator::IsDirectory() const {
+#ifdef WIN32
+  return (data_.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != FALSE;
+#else
+  return S_ISDIR(stat_.st_mode);
+#endif
+}
+
+  // returns the name of the file currently pointed to
+std::string DirectoryIterator::Name() const {
+#ifdef WIN32
+  return ToUtf8(data_.cFileName);
+#else
+  assert(dirent_ != NULL);
+  return dirent_->d_name;
+#endif
+}
+
+  // returns the size of the file currently pointed to
+size_t DirectoryIterator::FileSize() const {
+#ifndef WIN32
+  return stat_.st_size;
+#else
+  return data_.nFileSizeLow;
+#endif
+}
+
+  // returns the last modified time of this file
+time_t DirectoryIterator::FileModifyTime() const {
+#ifdef WIN32
+  time_t val;
+  FileTimeToUnixTime(data_.ftLastWriteTime, &val);
+  return val;
+#else
+  return stat_.st_mtime;
+#endif
+}
+
+FilesystemInterface* Filesystem::default_filesystem_ = NULL;
+
+FilesystemInterface *Filesystem::EnsureDefaultFilesystem() {
+  if (!default_filesystem_) {
+#ifdef WIN32
+    default_filesystem_ = new Win32Filesystem();
+#else
+    default_filesystem_ = new UnixFilesystem();
+#endif
+  }
+  return default_filesystem_;
+}
+
+bool FilesystemInterface::CopyFolder(const Pathname &old_path,
+                                     const Pathname &new_path) {
+  bool success = true;
+  VERIFY(IsFolder(old_path));
+  Pathname new_dir;
+  new_dir.SetFolder(new_path.pathname());
+  Pathname old_dir;
+  old_dir.SetFolder(old_path.pathname());
+  if (!CreateFolder(new_dir))
+    return false;
+  DirectoryIterator *di = IterateDirectory();
+  if (!di)
+    return false;
+  if (di->Iterate(old_dir.pathname())) {
+    do {
+      if (di->Name() == "." || di->Name() == "..")
+        continue;
+      Pathname source;
+      Pathname dest;
+      source.SetFolder(old_dir.pathname());
+      dest.SetFolder(new_path.pathname());
+      source.SetFilename(di->Name());
+      dest.SetFilename(di->Name());
+      if (!CopyFileOrFolder(source, dest))
+        success = false;
+    } while (di->Next());
+  }
+  delete di;
+  return success;
+}
+
+bool FilesystemInterface::DeleteFolderContents(const Pathname &folder) {
+  bool success = true;
+  VERIFY(IsFolder(folder));
+  DirectoryIterator *di = IterateDirectory();
+  if (!di)
+    return false;
+  if (di->Iterate(folder)) {
+    do {
+      if (di->Name() == "." || di->Name() == "..")
+        continue;
+      Pathname subdir;
+      subdir.SetFolder(folder.pathname());
+      if (di->IsDirectory()) {
+        subdir.AppendFolder(di->Name());
+        if (!DeleteFolderAndContents(subdir)) {
+          success = false;
+        }
+      } else {
+        subdir.SetFilename(di->Name());
+        if (!DeleteFile(subdir)) {
+          success = false;
+        }
+      }
+    } while (di->Next());
+  }
+  delete di;
+  return success;
+}
+
+bool FilesystemInterface::CleanAppTempFolder() {
+  Pathname path;
+  if (!GetAppTempFolder(&path))
+    return false;
+  if (IsAbsent(path))
+    return true;
+  if (!IsTemporaryPath(path)) {
+    ASSERT(false);
+    return false;
+  }
+  return DeleteFolderContents(path);
+}
+
+Pathname Filesystem::GetCurrentDirectory() {
+  return EnsureDefaultFilesystem()->GetCurrentDirectory();
+}
+
+bool CreateUniqueFile(Pathname& path, bool create_empty) {
+  LOG(LS_INFO) << "Path " << path.pathname() << std::endl;
+  // If no folder is supplied, use the temporary folder
+  if (path.folder().empty()) {
+    Pathname temporary_path;
+    if (!Filesystem::GetTemporaryFolder(temporary_path, true, NULL)) {
+      printf("Get temp failed\n");
+      return false;
+    }
+    path.SetFolder(temporary_path.pathname());
+  }
+
+  // If no filename is supplied, use a temporary name
+  if (path.filename().empty()) {
+    std::string folder(path.folder());
+    std::string filename = Filesystem::TempFilename(folder, "gt");
+    path.SetPathname(filename);
+    if (!create_empty) {
+      Filesystem::DeleteFile(path.pathname());
+    }
+    return true;
+  }
+
+  // Otherwise, create a unique name based on the given filename
+  // foo.txt -> foo-N.txt
+  const std::string basename = path.basename();
+  const size_t MAX_VERSION = 100;
+  size_t version = 0;
+  while (version < MAX_VERSION) {
+    std::string pathname = path.pathname();
+
+    if (!Filesystem::IsFile(pathname)) {
+      if (create_empty) {
+        FileStream* fs = Filesystem::OpenFile(pathname, "w");
+        delete fs;
+      }
+      return true;
+    }
+    version += 1;
+    char version_base[MAX_PATH];
+    sprintfn(version_base, ARRAY_SIZE(version_base), "%s-%u",
+             basename.c_str(), version);
+    path.SetBasename(version_base);
+  }
+  return true;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/fileutils.h b/talk/base/fileutils.h
new file mode 100644
index 0000000..186c963
--- /dev/null
+++ b/talk/base/fileutils.h
@@ -0,0 +1,457 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_FILEUTILS_H_
+#define TALK_BASE_FILEUTILS_H_
+
+#include <string>
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#else
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#endif
+
+#include "talk/base/basictypes.h"
+#include "talk/base/common.h"
+#include "talk/base/scoped_ptr.h"
+
+namespace talk_base {
+
+class FileStream;
+class Pathname;
+
+//////////////////////////
+// Directory Iterator   //
+//////////////////////////
+
+// A DirectoryIterator is created with a given directory. It originally points
+// to the first file in the directory, and can be advanecd with Next(). This
+// allows you to get information about each file.
+
+class DirectoryIterator {
+  friend class Filesystem;
+ public:
+  // Constructor
+  DirectoryIterator();
+  // Destructor
+  virtual ~DirectoryIterator();
+
+  // Starts traversing a directory
+  // dir is the directory to traverse
+  // returns true if the directory exists and is valid
+  // The iterator will point to the first entry in the directory
+  virtual bool Iterate(const Pathname &path);
+
+  // Advances to the next file
+  // returns true if there were more files in the directory.
+  virtual bool Next();
+
+  // returns true if the file currently pointed to is a directory
+  virtual bool IsDirectory() const;
+
+  // returns the name of the file currently pointed to
+  virtual std::string Name() const;
+
+  // returns the size of the file currently pointed to
+  virtual size_t FileSize() const;
+
+  // returns the last modified time of the file currently pointed to
+  virtual time_t FileModifyTime() const;
+
+  // checks whether current file is a special directory file "." or ".."
+  bool IsDots() const {
+    std::string filename(Name());
+    return (filename.compare(".") == 0) || (filename.compare("..") == 0);
+  }
+
+ private:
+  std::string directory_;
+#ifdef WIN32
+  WIN32_FIND_DATA data_;
+  HANDLE handle_;
+#else
+  DIR *dir_;
+  struct dirent *dirent_;
+  struct stat stat_;
+#endif
+};
+
+enum FileTimeType { FTT_CREATED, FTT_MODIFIED, FTT_ACCESSED };
+
+class FilesystemInterface {
+ public:
+  virtual ~FilesystemInterface() {}
+
+  // Returns a DirectoryIterator for a given pathname.
+  // TODO: Do fancy abstracted stuff
+  virtual DirectoryIterator *IterateDirectory() {
+    return new DirectoryIterator();
+  }
+
+  // Opens a file. Returns an open StreamInterface if function succeeds.
+  // Otherwise, returns NULL.
+  // TODO: Add an error param to indicate failure reason, similar to
+  // FileStream::Open
+  virtual FileStream *OpenFile(const Pathname &filename,
+                               const std::string &mode) = 0;
+
+  // Atomically creates an empty file accessible only to the current user if one
+  // does not already exist at the given path, otherwise fails. This is the only
+  // secure way to create a file in a shared temp directory (e.g., C:\Temp on
+  // Windows or /tmp on Linux).
+  // Note that if it is essential that a file be successfully created then the
+  // app must generate random names and retry on failure, or else it will be
+  // vulnerable to a trivial DoS.
+  virtual bool CreatePrivateFile(const Pathname &filename) = 0;
+
+  // This will attempt to delete the path located at filename.
+  // It ASSERTS and returns false if the path points to a folder or a
+  // non-existent file.
+  virtual bool DeleteFile(const Pathname &filename) = 0;
+
+  // This will attempt to delete the empty folder located at 'folder'
+  // It ASSERTS and returns false if the path points to a file or a non-existent
+  // folder. It fails normally if the folder is not empty or can otherwise
+  // not be deleted.
+  virtual bool DeleteEmptyFolder(const Pathname &folder) = 0;
+
+  // This will call IterateDirectory, to get a directory iterator, and then
+  // call DeleteFolderAndContents and DeleteFile on every path contained in this
+  // folder. If the folder is empty, this returns true.
+  virtual bool DeleteFolderContents(const Pathname &folder);
+
+  // This deletes the contents of a folder, recursively, and then deletes
+  // the folder itself.
+  virtual bool DeleteFolderAndContents(const Pathname &folder) {
+    return DeleteFolderContents(folder) && DeleteEmptyFolder(folder);
+  }
+
+  // This will delete whatever is located at path, be it a file or a folder.
+  // If it is a folder, it will delete it recursively by calling
+  // DeleteFolderAndContents
+  bool DeleteFileOrFolder(const Pathname &path) {
+    if (IsFolder(path))
+      return DeleteFolderAndContents(path);
+    else
+      return DeleteFile(path);
+  }
+
+  // Creates a directory. This will call itself recursively to create /foo/bar
+  // even if /foo does not exist. Returns true if the function succeeds.
+  virtual bool CreateFolder(const Pathname &pathname) = 0;
+
+  // This moves a file from old_path to new_path, where "old_path" is a
+  // plain file. This ASSERTs and returns false if old_path points to a
+  // directory, and returns true if the function succeeds.
+  // If the new path is on a different volume than the old path, this function
+  // will attempt to copy and, if that succeeds, delete the old path.
+  virtual bool MoveFolder(const Pathname &old_path,
+                          const Pathname &new_path) = 0;
+
+  // This moves a directory from old_path to new_path, where "old_path" is a
+  // directory. This ASSERTs and returns false if old_path points to a plain
+  // file, and returns true if the function succeeds.
+  // If the new path is on a different volume, this function will attempt to
+  // copy and if that succeeds, delete the old path.
+  virtual bool MoveFile(const Pathname &old_path, const Pathname &new_path) = 0;
+
+  // This attempts to move whatever is located at old_path to new_path,
+  // be it a file or folder.
+  bool MoveFileOrFolder(const Pathname &old_path, const Pathname &new_path) {
+    if (IsFile(old_path)) {
+      return MoveFile(old_path, new_path);
+    } else {
+      return MoveFolder(old_path, new_path);
+    }
+  }
+
+  // This copies a file from old_path to new_path. This method ASSERTs and
+  // returns false if old_path is a folder, and returns true if the copy
+  // succeeds.
+  virtual bool CopyFile(const Pathname &old_path, const Pathname &new_path) = 0;
+
+  // This copies a folder from old_path to new_path.
+  bool CopyFolder(const Pathname &old_path, const Pathname &new_path);
+
+  bool CopyFileOrFolder(const Pathname &old_path, const Pathname &new_path) {
+    if (IsFile(old_path))
+      return CopyFile(old_path, new_path);
+    else
+      return CopyFolder(old_path, new_path);
+  }
+
+  // Returns true if pathname refers to a directory
+  virtual bool IsFolder(const Pathname& pathname) = 0;
+
+  // Returns true if pathname refers to a file
+  virtual bool IsFile(const Pathname& pathname) = 0;
+
+  // Returns true if pathname refers to no filesystem object, every parent
+  // directory either exists, or is also absent.
+  virtual bool IsAbsent(const Pathname& pathname) = 0;
+
+  // Returns true if pathname represents a temporary location on the system.
+  virtual bool IsTemporaryPath(const Pathname& pathname) = 0;
+
+  // A folder appropriate for storing temporary files (Contents are
+  // automatically deleted when the program exits)
+  virtual bool GetTemporaryFolder(Pathname &path, bool create,
+                                  const std::string *append) = 0;
+
+  virtual std::string TempFilename(const Pathname &dir,
+                                   const std::string &prefix) = 0;
+
+  // Determines the size of the file indicated by path.
+  virtual bool GetFileSize(const Pathname& path, size_t* size) = 0;
+
+  // Determines a timestamp associated with the file indicated by path.
+  virtual bool GetFileTime(const Pathname& path, FileTimeType which,
+                           time_t* time) = 0;
+
+  // Returns the path to the running application.
+  // Note: This is not guaranteed to work on all platforms.  Be aware of the
+  // limitations before using it, and robustly handle failure.
+  virtual bool GetAppPathname(Pathname* path) = 0;
+
+  // Get a folder that is unique to the current application, which is suitable
+  // for sharing data between executions of the app.  If the per_user arg is
+  // true, the folder is also specific to the current user.
+  virtual bool GetAppDataFolder(Pathname* path, bool per_user) = 0;
+
+  // Get a temporary folder that is unique to the current user and application.
+  // TODO: Re-evaluate the goals of this function.  We probably just need any
+  // directory that won't collide with another existing directory, and which
+  // will be cleaned up when the program exits.
+  virtual bool GetAppTempFolder(Pathname* path) = 0;
+
+  // Delete the contents of the folder returned by GetAppTempFolder
+  bool CleanAppTempFolder();
+
+  virtual bool GetDiskFreeSpace(const Pathname& path, int64 *freebytes) = 0;
+
+  // Returns the absolute path of the current directory.
+  virtual Pathname GetCurrentDirectory() = 0;
+
+  // Note: These might go into some shared config section later, but they're
+  // used by some methods in this interface, so we're leaving them here for now.
+  void SetOrganizationName(const std::string& organization) {
+    organization_name_ = organization;
+  }
+  void GetOrganizationName(std::string* organization) {
+    ASSERT(NULL != organization);
+    *organization = organization_name_;
+  }
+  void SetApplicationName(const std::string& application) {
+    application_name_ = application;
+  }
+  void GetApplicationName(std::string* application) {
+    ASSERT(NULL != application);
+    *application = application_name_;
+  }
+
+ protected:
+  std::string organization_name_;
+  std::string application_name_;
+};
+
+class Filesystem {
+ public:
+  static FilesystemInterface *default_filesystem() {
+    ASSERT(default_filesystem_ != NULL);
+    return default_filesystem_;
+  }
+
+  static void set_default_filesystem(FilesystemInterface *filesystem) {
+    default_filesystem_ = filesystem;
+  }
+
+  static FilesystemInterface *swap_default_filesystem(
+      FilesystemInterface *filesystem) {
+    FilesystemInterface *cur = default_filesystem_;
+    default_filesystem_ = filesystem;
+    return cur;
+  }
+
+  static DirectoryIterator *IterateDirectory() {
+    return EnsureDefaultFilesystem()->IterateDirectory();
+  }
+
+  static bool CreateFolder(const Pathname &pathname) {
+    return EnsureDefaultFilesystem()->CreateFolder(pathname);
+  }
+
+  static FileStream *OpenFile(const Pathname &filename,
+                              const std::string &mode) {
+    return EnsureDefaultFilesystem()->OpenFile(filename, mode);
+  }
+
+  static bool CreatePrivateFile(const Pathname &filename) {
+    return EnsureDefaultFilesystem()->CreatePrivateFile(filename);
+  }
+
+  static bool DeleteFile(const Pathname &filename) {
+    return EnsureDefaultFilesystem()->DeleteFile(filename);
+  }
+
+  static bool DeleteEmptyFolder(const Pathname &folder) {
+    return EnsureDefaultFilesystem()->DeleteEmptyFolder(folder);
+  }
+
+  static bool DeleteFolderContents(const Pathname &folder) {
+    return EnsureDefaultFilesystem()->DeleteFolderContents(folder);
+  }
+
+  static bool DeleteFolderAndContents(const Pathname &folder) {
+    return EnsureDefaultFilesystem()->DeleteFolderAndContents(folder);
+  }
+
+  static bool MoveFolder(const Pathname &old_path, const Pathname &new_path) {
+    return EnsureDefaultFilesystem()->MoveFolder(old_path, new_path);
+  }
+
+  static bool MoveFile(const Pathname &old_path, const Pathname &new_path) {
+    return EnsureDefaultFilesystem()->MoveFile(old_path, new_path);
+  }
+
+  static bool CopyFolder(const Pathname &old_path, const Pathname &new_path) {
+    return EnsureDefaultFilesystem()->CopyFolder(old_path, new_path);
+  }
+
+  static bool CopyFile(const Pathname &old_path, const Pathname &new_path) {
+    return EnsureDefaultFilesystem()->CopyFile(old_path, new_path);
+  }
+
+  static bool IsFolder(const Pathname& pathname) {
+    return EnsureDefaultFilesystem()->IsFolder(pathname);
+  }
+
+  static bool IsFile(const Pathname &pathname) {
+    return EnsureDefaultFilesystem()->IsFile(pathname);
+  }
+
+  static bool IsAbsent(const Pathname &pathname) {
+    return EnsureDefaultFilesystem()->IsAbsent(pathname);
+  }
+
+  static bool IsTemporaryPath(const Pathname& pathname) {
+    return EnsureDefaultFilesystem()->IsTemporaryPath(pathname);
+  }
+
+  static bool GetTemporaryFolder(Pathname &path, bool create,
+                                 const std::string *append) {
+    return EnsureDefaultFilesystem()->GetTemporaryFolder(path, create, append);
+  }
+
+  static std::string TempFilename(const Pathname &dir,
+                                  const std::string &prefix) {
+    return EnsureDefaultFilesystem()->TempFilename(dir, prefix);
+  }
+
+  static bool GetFileSize(const Pathname& path, size_t* size) {
+    return EnsureDefaultFilesystem()->GetFileSize(path, size);
+  }
+
+  static bool GetFileTime(const Pathname& path, FileTimeType which,
+                          time_t* time) {
+    return EnsureDefaultFilesystem()->GetFileTime(path, which, time);
+  }
+
+  static bool GetAppPathname(Pathname* path) {
+    return EnsureDefaultFilesystem()->GetAppPathname(path);
+  }
+
+  static bool GetAppDataFolder(Pathname* path, bool per_user) {
+    return EnsureDefaultFilesystem()->GetAppDataFolder(path, per_user);
+  }
+
+  static bool GetAppTempFolder(Pathname* path) {
+    return EnsureDefaultFilesystem()->GetAppTempFolder(path);
+  }
+
+  static bool CleanAppTempFolder() {
+    return EnsureDefaultFilesystem()->CleanAppTempFolder();
+  }
+
+  static bool GetDiskFreeSpace(const Pathname& path, int64 *freebytes) {
+    return EnsureDefaultFilesystem()->GetDiskFreeSpace(path, freebytes);
+  }
+
+  // Definition has to be in the .cc file due to returning forward-declared
+  // Pathname by value.
+  static Pathname GetCurrentDirectory();
+
+  static void SetOrganizationName(const std::string& organization) {
+    EnsureDefaultFilesystem()->SetOrganizationName(organization);
+  }
+
+  static void GetOrganizationName(std::string* organization) {
+    EnsureDefaultFilesystem()->GetOrganizationName(organization);
+  }
+
+  static void SetApplicationName(const std::string& application) {
+    EnsureDefaultFilesystem()->SetApplicationName(application);
+  }
+
+  static void GetApplicationName(std::string* application) {
+    EnsureDefaultFilesystem()->GetApplicationName(application);
+  }
+
+ private:
+  static FilesystemInterface* default_filesystem_;
+
+  static FilesystemInterface *EnsureDefaultFilesystem();
+  DISALLOW_IMPLICIT_CONSTRUCTORS(Filesystem);
+};
+
+class FilesystemScope{
+ public:
+  explicit FilesystemScope(FilesystemInterface *new_fs) {
+    old_fs_ = Filesystem::swap_default_filesystem(new_fs);
+  }
+  ~FilesystemScope() {
+    Filesystem::set_default_filesystem(old_fs_);
+  }
+ private:
+  FilesystemInterface* old_fs_;
+  DISALLOW_IMPLICIT_CONSTRUCTORS(FilesystemScope);
+};
+
+// Generates a unique filename based on the input path.  If no path component
+// is specified, it uses the temporary directory.  If a filename is provided,
+// up to 100 variations of form basename-N.extension are tried.  When
+// create_empty is true, an empty file of this name is created (which
+// decreases the chance of a temporary filename collision with another
+// process).
+bool CreateUniqueFile(Pathname& path, bool create_empty);
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_FILEUTILS_H_
diff --git a/talk/base/fileutils_mock.h b/talk/base/fileutils_mock.h
new file mode 100644
index 0000000..b91e802
--- /dev/null
+++ b/talk/base/fileutils_mock.h
@@ -0,0 +1,270 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_FILEUTILS_MOCK_H_
+#define TALK_BASE_FILEUTILS_MOCK_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "talk/base/fileutils.h"
+#include "talk/base/gunit.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+
+namespace talk_base {
+
+class FakeFileStream : public FileStream {
+  public:
+    explicit FakeFileStream(const std::string & contents) :
+      string_stream_(contents)
+    {}
+
+    virtual StreamResult Read(void* buffer, size_t buffer_len,
+                              size_t* read, int* error) {
+      return string_stream_.Read(buffer, buffer_len, read, error);
+    }
+
+    virtual void Close() {
+      return string_stream_.Close();
+    }
+    virtual bool GetSize(size_t* size) const {
+      return string_stream_.GetSize(size);
+    }
+
+  private:
+    StringStream string_stream_;
+};
+
+class FakeDirectoryIterator : public DirectoryIterator {
+  public:
+    typedef std::pair<std::string, std::string> File;
+
+    /*
+     * files should be sorted by directory
+     * put '/' at the end of file if you want it to be a directory
+     *
+     * Sample list:
+     *  /var/dir/file1
+     *  /var/dir/file2
+     *  /var/dir/subdir1/
+     *  /var/dir/subdir2/
+     *  /var/dir2/file2
+     *  /var/dir3/
+     *
+     *  you can call Iterate for any path: /var, /var/dir, /var/dir2
+     *  unrelated files will be ignored
+     */
+    explicit FakeDirectoryIterator(const std::vector<File>& all_files) :
+      all_files_(all_files) {}
+
+    virtual bool Iterate(const Pathname& path) {
+      path_iterator_ = all_files_.begin();
+      path_ = path.pathname();
+
+      // make sure path ends end with '/'
+      if (path_.rfind(Pathname::DefaultFolderDelimiter()) != path_.size() - 1)
+        path_ += Pathname::DefaultFolderDelimiter();
+
+      return  FakeDirectoryIterator::Search(std::string(""));
+    }
+
+    virtual bool Next() {
+      std::string current_name = Name();
+      path_iterator_++;
+      return FakeDirectoryIterator::Search(current_name);
+    }
+
+    bool Search(const std::string& current_name) {
+      for (; path_iterator_ != all_files_.end(); path_iterator_++) {
+        if (path_iterator_->first.find(path_) == 0
+            && Name().compare(current_name) != 0) {
+          return true;
+        }
+      }
+
+      return false;
+    }
+
+    virtual bool IsDirectory() const {
+      std::string sub_path = path_iterator_->first;
+
+      return std::string::npos !=
+        sub_path.find(Pathname::DefaultFolderDelimiter(), path_.size());
+    }
+
+    virtual std::string Name() const {
+      std::string sub_path = path_iterator_->first;
+
+      // path     - top level path  (ex. /var/lib)
+      // sub_path - subpath under top level path (ex. /var/lib/dir/dir/file )
+      // find shortest non-trivial common path. (ex. /var/lib/dir)
+      size_t start  = path_.size();
+      size_t end    = sub_path.find(Pathname::DefaultFolderDelimiter(), start);
+
+      if (end != std::string::npos) {
+        return sub_path.substr(start, end - start);
+      } else {
+        return sub_path.substr(start);
+      }
+    }
+
+  private:
+    const std::vector<File> all_files_;
+
+    std::string path_;
+    std::vector<File>::const_iterator path_iterator_;
+};
+
+class FakeFileSystem : public FilesystemInterface {
+  public:
+    typedef std::pair<std::string, std::string> File;
+
+    explicit FakeFileSystem(const std::vector<File>& all_files) :
+     all_files_(all_files) {}
+
+    virtual DirectoryIterator *IterateDirectory() {
+     return new FakeDirectoryIterator(all_files_);
+    }
+
+    virtual FileStream * OpenFile(
+       const Pathname &filename,
+       const std::string &mode) {
+     std::vector<File>::const_iterator i_files = all_files_.begin();
+     std::string path = filename.pathname();
+
+     for (; i_files != all_files_.end(); i_files++) {
+       if (i_files->first.compare(path) == 0) {
+         return new FakeFileStream(i_files->second);
+       }
+     }
+
+     return NULL;
+    }
+
+    bool CreatePrivateFile(const Pathname &filename) {
+      EXPECT_TRUE(false) << "Unsupported operation";
+      return false;
+    }
+    bool DeleteFile(const Pathname &filename) {
+      EXPECT_TRUE(false) << "Unsupported operation";
+      return false;
+    }
+    bool DeleteEmptyFolder(const Pathname &folder) {
+      EXPECT_TRUE(false) << "Unsupported operation";
+      return false;
+    }
+    bool DeleteFolderContents(const Pathname &folder) {
+      EXPECT_TRUE(false) << "Unsupported operation";
+      return false;
+    }
+    bool DeleteFolderAndContents(const Pathname &folder) {
+      EXPECT_TRUE(false) << "Unsupported operation";
+      return false;
+    }
+    bool CreateFolder(const Pathname &pathname) {
+      EXPECT_TRUE(false) << "Unsupported operation";
+      return false;
+    }
+    bool MoveFolder(const Pathname &old_path, const Pathname &new_path) {
+      EXPECT_TRUE(false) << "Unsupported operation";
+      return false;
+    }
+    bool MoveFile(const Pathname &old_path, const Pathname &new_path) {
+      EXPECT_TRUE(false) << "Unsupported operation";
+      return false;
+    }
+    bool CopyFile(const Pathname &old_path, const Pathname &new_path) {
+      EXPECT_TRUE(false) << "Unsupported operation";
+      return false;
+    }
+    bool IsFolder(const Pathname &pathname) {
+      EXPECT_TRUE(false) << "Unsupported operation";
+      return false;
+    }
+    bool IsFile(const Pathname &pathname) {
+      EXPECT_TRUE(false) << "Unsupported operation";
+      return false;
+    }
+    bool IsAbsent(const Pathname &pathname) {
+      EXPECT_TRUE(false) << "Unsupported operation";
+      return false;
+    }
+    bool IsTemporaryPath(const Pathname &pathname) {
+      EXPECT_TRUE(false) << "Unsupported operation";
+      return false;
+    }
+    bool GetTemporaryFolder(Pathname &path, bool create,
+                            const std::string *append) {
+      EXPECT_TRUE(false) << "Unsupported operation";
+      return false;
+    }
+    std::string TempFilename(const Pathname &dir, const std::string &prefix) {
+      EXPECT_TRUE(false) << "Unsupported operation";
+      return std::string();
+    }
+    bool GetFileSize(const Pathname &path, size_t *size) {
+      EXPECT_TRUE(false) << "Unsupported operation";
+      return false;
+    }
+    bool GetFileTime(const Pathname &path, FileTimeType which,
+                     time_t* time) {
+      EXPECT_TRUE(false) << "Unsupported operation";
+      return false;
+    }
+    bool GetAppPathname(Pathname *path) {
+      EXPECT_TRUE(false) << "Unsupported operation";
+      return false;
+    }
+    bool GetAppDataFolder(Pathname *path, bool per_user) {
+      EXPECT_TRUE(per_user) << "Unsupported operation";
+#ifdef WIN32
+      path->SetPathname("c:\\Users\\test_user", "");
+#else
+      path->SetPathname("/home/user/test_user", "");
+#endif
+      return true;
+    }
+    bool GetAppTempFolder(Pathname *path) {
+      EXPECT_TRUE(false) << "Unsupported operation";
+      return false;
+    }
+    bool GetDiskFreeSpace(const Pathname &path, int64 *freebytes) {
+      EXPECT_TRUE(false) << "Unsupported operation";
+      return false;
+    }
+    Pathname GetCurrentDirectory() {
+      return Pathname();
+    }
+
+  private:
+    const std::vector<File> all_files_;
+};
+}  // namespace talk_base
+
+#endif  // TALK_BASE_FILEUTILS_MOCK_H_
diff --git a/talk/base/fileutils_unittest.cc b/talk/base/fileutils_unittest.cc
new file mode 100644
index 0000000..992ea82
--- /dev/null
+++ b/talk/base/fileutils_unittest.cc
@@ -0,0 +1,148 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/fileutils.h"
+#include "talk/base/gunit.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+
+namespace talk_base {
+
+// Make sure we can get a temp folder for the later tests.
+TEST(FilesystemTest, GetTemporaryFolder) {
+  Pathname path;
+  EXPECT_TRUE(Filesystem::GetTemporaryFolder(path, true, NULL));
+}
+
+// Test creating a temp file, reading it back in, and deleting it.
+TEST(FilesystemTest, TestOpenFile) {
+  Pathname path;
+  EXPECT_TRUE(Filesystem::GetTemporaryFolder(path, true, NULL));
+  path.SetPathname(Filesystem::TempFilename(path, "ut"));
+
+  FileStream* fs;
+  char buf[256];
+  size_t bytes;
+
+  fs = Filesystem::OpenFile(path, "wb");
+  ASSERT_TRUE(fs != NULL);
+  EXPECT_EQ(SR_SUCCESS, fs->Write("test", 4, &bytes, NULL));
+  EXPECT_EQ(4U, bytes);
+  delete fs;
+
+  EXPECT_TRUE(Filesystem::IsFile(path));
+
+  fs = Filesystem::OpenFile(path, "rb");
+  ASSERT_TRUE(fs != NULL);
+  EXPECT_EQ(SR_SUCCESS, fs->Read(buf, sizeof(buf), &bytes, NULL));
+  EXPECT_EQ(4U, bytes);
+  delete fs;
+
+  EXPECT_TRUE(Filesystem::DeleteFile(path));
+  EXPECT_FALSE(Filesystem::IsFile(path));
+}
+
+// Test opening a non-existent file.
+TEST(FilesystemTest, TestOpenBadFile) {
+  Pathname path;
+  EXPECT_TRUE(Filesystem::GetTemporaryFolder(path, true, NULL));
+  path.SetFilename("not an actual file");
+
+  EXPECT_FALSE(Filesystem::IsFile(path));
+
+  FileStream* fs = Filesystem::OpenFile(path, "rb");
+  EXPECT_FALSE(fs != NULL);
+}
+
+// Test that CreatePrivateFile fails for existing files and succeeds for
+// non-existent ones.
+TEST(FilesystemTest, TestCreatePrivateFile) {
+  Pathname path;
+  EXPECT_TRUE(Filesystem::GetTemporaryFolder(path, true, NULL));
+  path.SetFilename("private_file_test");
+
+  // First call should succeed because the file doesn't exist yet.
+  EXPECT_TRUE(Filesystem::CreatePrivateFile(path));
+  // Next call should fail, because now it exists.
+  EXPECT_FALSE(Filesystem::CreatePrivateFile(path));
+
+  // Verify that we have permission to open the file for reading and writing.
+  scoped_ptr<FileStream> fs(Filesystem::OpenFile(path, "wb"));
+  EXPECT_TRUE(fs.get() != NULL);
+  // Have to close the file on Windows before it will let us delete it.
+  fs.reset();
+
+  // Verify that we have permission to delete the file.
+  EXPECT_TRUE(Filesystem::DeleteFile(path));
+}
+
+// Test checking for free disk space.
+TEST(FilesystemTest, TestGetDiskFreeSpace) {
+  // Note that we should avoid picking any file/folder which could be located
+  // at the remotely mounted drive/device.
+  Pathname path;
+  ASSERT_TRUE(Filesystem::GetAppDataFolder(&path, true));
+
+  int64 free1 = 0;
+  EXPECT_TRUE(Filesystem::IsFolder(path));
+  EXPECT_FALSE(Filesystem::IsFile(path));
+  EXPECT_TRUE(Filesystem::GetDiskFreeSpace(path, &free1));
+  EXPECT_GT(free1, 0);
+
+  int64 free2 = 0;
+  path.AppendFolder("this_folder_doesnt_exist");
+  EXPECT_FALSE(Filesystem::IsFolder(path));
+  EXPECT_TRUE(Filesystem::IsAbsent(path));
+  EXPECT_TRUE(Filesystem::GetDiskFreeSpace(path, &free2));
+  // These should be the same disk, and disk free space should not have changed
+  // by more than 1% between the two calls.
+  EXPECT_LT(static_cast<int64>(free1 * .9), free2);
+  EXPECT_LT(free2, static_cast<int64>(free1 * 1.1));
+
+  int64 free3 = 0;
+  path.clear();
+  EXPECT_TRUE(path.empty());
+  EXPECT_TRUE(Filesystem::GetDiskFreeSpace(path, &free3));
+  // Current working directory may not be where exe is.
+  // EXPECT_LT(static_cast<int64>(free1 * .9), free3);
+  // EXPECT_LT(free3, static_cast<int64>(free1 * 1.1));
+  EXPECT_GT(free3, 0);
+}
+
+// Tests that GetCurrentDirectory() returns something.
+TEST(FilesystemTest, TestGetCurrentDirectory) {
+  EXPECT_FALSE(Filesystem::GetCurrentDirectory().empty());
+}
+
+// Tests that GetAppPathname returns something.
+TEST(FilesystemTest, TestGetAppPathname) {
+  Pathname path;
+  EXPECT_TRUE(Filesystem::GetAppPathname(&path));
+  EXPECT_FALSE(path.empty());
+}
+
+}  // namespace talk_base
diff --git a/talk/base/firewallsocketserver.cc b/talk/base/firewallsocketserver.cc
new file mode 100644
index 0000000..ee2c22d
--- /dev/null
+++ b/talk/base/firewallsocketserver.cc
@@ -0,0 +1,254 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/firewallsocketserver.h"
+
+#include <cassert>
+#include <algorithm>
+
+#include "talk/base/asyncsocket.h"
+#include "talk/base/logging.h"
+
+namespace talk_base {
+
+class FirewallSocket : public AsyncSocketAdapter {
+ public:
+  FirewallSocket(FirewallSocketServer* server, AsyncSocket* socket, int type)
+    : AsyncSocketAdapter(socket), server_(server), type_(type) {
+  }
+
+  virtual int Connect(const SocketAddress& addr) {
+    if (type_ == SOCK_STREAM) {
+      if (!server_->Check(FP_TCP, GetLocalAddress(), addr)) {
+        LOG(LS_VERBOSE) << "FirewallSocket outbound TCP connection from "
+                        << GetLocalAddress().ToSensitiveString() << " to "
+                        << addr.ToSensitiveString() << " denied";
+        // TODO: Handle this asynchronously.
+        SetError(EHOSTUNREACH);
+        return SOCKET_ERROR;
+      }
+    }
+    return AsyncSocketAdapter::Connect(addr);
+  }
+  virtual int Send(const void* pv, size_t cb) {
+    return SendTo(pv, cb, GetRemoteAddress());
+  }
+  virtual int SendTo(const void* pv, size_t cb, const SocketAddress& addr) {
+    if (type_ == SOCK_DGRAM) {
+      if (!server_->Check(FP_UDP, GetLocalAddress(), addr)) {
+        LOG(LS_VERBOSE) << "FirewallSocket outbound UDP packet from "
+                        << GetLocalAddress().ToSensitiveString() << " to "
+                        << addr.ToSensitiveString() << " dropped";
+        return static_cast<int>(cb);
+      }
+    }
+    return AsyncSocketAdapter::SendTo(pv, cb, addr);
+  }
+  virtual int Recv(void* pv, size_t cb) {
+    SocketAddress addr;
+    return RecvFrom(pv, cb, &addr);
+  }
+  virtual int RecvFrom(void* pv, size_t cb, SocketAddress* paddr) {
+    if (type_ == SOCK_DGRAM) {
+      while (true) {
+        int res = AsyncSocketAdapter::RecvFrom(pv, cb, paddr);
+        if (res <= 0)
+          return res;
+        if (server_->Check(FP_UDP, *paddr, GetLocalAddress()))
+          return res;
+        LOG(LS_VERBOSE) << "FirewallSocket inbound UDP packet from "
+                        << paddr->ToSensitiveString() << " to "
+                        << GetLocalAddress().ToSensitiveString() << " dropped";
+      }
+    }
+    return AsyncSocketAdapter::RecvFrom(pv, cb, paddr);
+  }
+
+  virtual int Listen(int backlog) {
+    if (!server_->tcp_listen_enabled()) {
+      LOG(LS_VERBOSE) << "FirewallSocket listen attempt denied";
+      return -1;
+    }
+
+    return AsyncSocketAdapter::Listen(backlog);
+  }
+  virtual AsyncSocket* Accept(SocketAddress* paddr) {
+    SocketAddress addr;
+    while (AsyncSocket* sock = AsyncSocketAdapter::Accept(&addr)) {
+      if (server_->Check(FP_TCP, addr, GetLocalAddress())) {
+        if (paddr)
+          *paddr = addr;
+        return sock;
+      }
+      sock->Close();
+      delete sock;
+      LOG(LS_VERBOSE) << "FirewallSocket inbound TCP connection from "
+                      << addr.ToSensitiveString() << " to "
+                      << GetLocalAddress().ToSensitiveString() << " denied";
+    }
+    return 0;
+  }
+
+ private:
+  FirewallSocketServer* server_;
+  int type_;
+};
+
+FirewallSocketServer::FirewallSocketServer(SocketServer* server,
+                                           FirewallManager* manager,
+                                           bool should_delete_server)
+    : server_(server), manager_(manager),
+      should_delete_server_(should_delete_server),
+      udp_sockets_enabled_(true), tcp_sockets_enabled_(true),
+      tcp_listen_enabled_(true) {
+  if (manager_)
+    manager_->AddServer(this);
+}
+
+FirewallSocketServer::~FirewallSocketServer() {
+  if (manager_)
+    manager_->RemoveServer(this);
+
+  if (server_ && should_delete_server_) {
+    delete server_;
+    server_ = NULL;
+  }
+}
+
+void FirewallSocketServer::AddRule(bool allow, FirewallProtocol p,
+                                   FirewallDirection d,
+                                   const SocketAddress& addr) {
+  SocketAddress src, dst;
+  if (d == FD_IN) {
+    dst = addr;
+  } else {
+    src = addr;
+  }
+  AddRule(allow, p, src, dst);
+}
+
+
+void FirewallSocketServer::AddRule(bool allow, FirewallProtocol p,
+                                   const SocketAddress& src,
+                                   const SocketAddress& dst) {
+  Rule r;
+  r.allow = allow;
+  r.p = p;
+  r.src = src;
+  r.dst = dst;
+  CritScope scope(&crit_);
+  rules_.push_back(r);
+}
+
+void FirewallSocketServer::ClearRules() {
+  CritScope scope(&crit_);
+  rules_.clear();
+}
+
+bool FirewallSocketServer::Check(FirewallProtocol p,
+                                 const SocketAddress& src,
+                                 const SocketAddress& dst) {
+  CritScope scope(&crit_);
+  for (size_t i = 0; i < rules_.size(); ++i) {
+    const Rule& r = rules_[i];
+    if ((r.p != p) && (r.p != FP_ANY))
+      continue;
+    if ((r.src.ipaddr() != src.ipaddr()) && !r.src.IsNil())
+      continue;
+    if ((r.src.port() != src.port()) && (r.src.port() != 0))
+      continue;
+    if ((r.dst.ipaddr() != dst.ipaddr()) && !r.dst.IsNil())
+      continue;
+    if ((r.dst.port() != dst.port()) && (r.dst.port() != 0))
+      continue;
+    return r.allow;
+  }
+  return true;
+}
+
+Socket* FirewallSocketServer::CreateSocket(int type) {
+  return CreateSocket(AF_INET, type);
+}
+
+Socket* FirewallSocketServer::CreateSocket(int family, int type) {
+  return WrapSocket(server_->CreateAsyncSocket(family, type), type);
+}
+
+AsyncSocket* FirewallSocketServer::CreateAsyncSocket(int type) {
+  return CreateAsyncSocket(AF_INET, type);
+}
+
+AsyncSocket* FirewallSocketServer::CreateAsyncSocket(int family, int type) {
+  return WrapSocket(server_->CreateAsyncSocket(family, type), type);
+}
+
+AsyncSocket* FirewallSocketServer::WrapSocket(AsyncSocket* sock, int type) {
+  if (!sock ||
+      (type == SOCK_STREAM && !tcp_sockets_enabled_) ||
+      (type == SOCK_DGRAM && !udp_sockets_enabled_)) {
+    LOG(LS_VERBOSE) << "FirewallSocketServer socket creation denied";
+    return NULL;
+  }
+  return new FirewallSocket(this, sock, type);
+}
+
+FirewallManager::FirewallManager() {
+}
+
+FirewallManager::~FirewallManager() {
+  assert(servers_.empty());
+}
+
+void FirewallManager::AddServer(FirewallSocketServer* server) {
+  CritScope scope(&crit_);
+  servers_.push_back(server);
+}
+
+void FirewallManager::RemoveServer(FirewallSocketServer* server) {
+  CritScope scope(&crit_);
+  servers_.erase(std::remove(servers_.begin(), servers_.end(), server),
+                 servers_.end());
+}
+
+void FirewallManager::AddRule(bool allow, FirewallProtocol p,
+                              FirewallDirection d, const SocketAddress& addr) {
+  CritScope scope(&crit_);
+  for (std::vector<FirewallSocketServer*>::const_iterator it =
+      servers_.begin(); it != servers_.end(); ++it) {
+    (*it)->AddRule(allow, p, d, addr);
+  }
+}
+
+void FirewallManager::ClearRules() {
+  CritScope scope(&crit_);
+  for (std::vector<FirewallSocketServer*>::const_iterator it =
+      servers_.begin(); it != servers_.end(); ++it) {
+    (*it)->ClearRules();
+  }
+}
+
+}  // namespace talk_base
diff --git a/talk/base/firewallsocketserver.h b/talk/base/firewallsocketserver.h
new file mode 100644
index 0000000..aa63c11
--- /dev/null
+++ b/talk/base/firewallsocketserver.h
@@ -0,0 +1,137 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_FIREWALLSOCKETSERVER_H_
+#define TALK_BASE_FIREWALLSOCKETSERVER_H_
+
+#include <vector>
+#include "talk/base/socketserver.h"
+#include "talk/base/criticalsection.h"
+
+namespace talk_base {
+
+class FirewallManager;
+
+// This SocketServer shim simulates a rule-based firewall server.
+
+enum FirewallProtocol { FP_UDP, FP_TCP, FP_ANY };
+enum FirewallDirection { FD_IN, FD_OUT, FD_ANY };
+
+class FirewallSocketServer : public SocketServer {
+ public:
+  FirewallSocketServer(SocketServer * server,
+                       FirewallManager * manager = NULL,
+                       bool should_delete_server = false);
+  virtual ~FirewallSocketServer();
+
+  SocketServer* socketserver() const { return server_; }
+  void set_socketserver(SocketServer* server) {
+    if (server_ && should_delete_server_) {
+      delete server_;
+      server_ = NULL;
+      should_delete_server_ = false;
+    }
+    server_ = server;
+  }
+
+  // Settings to control whether CreateSocket or Socket::Listen succeed.
+  void set_udp_sockets_enabled(bool enabled) { udp_sockets_enabled_ = enabled; }
+  void set_tcp_sockets_enabled(bool enabled) { tcp_sockets_enabled_ = enabled; }
+  bool tcp_listen_enabled() const { return tcp_listen_enabled_; }
+  void set_tcp_listen_enabled(bool enabled) { tcp_listen_enabled_ = enabled; }
+
+  // Rules govern the behavior of Connect/Accept/Send/Recv attempts.
+  void AddRule(bool allow, FirewallProtocol p = FP_ANY,
+               FirewallDirection d = FD_ANY,
+               const SocketAddress& addr = SocketAddress());
+  void AddRule(bool allow, FirewallProtocol p,
+               const SocketAddress& src, const SocketAddress& dst);
+  void ClearRules();
+
+  bool Check(FirewallProtocol p,
+             const SocketAddress& src, const SocketAddress& dst);
+
+  virtual Socket* CreateSocket(int type);
+  virtual Socket* CreateSocket(int family, int type);
+
+  virtual AsyncSocket* CreateAsyncSocket(int type);
+  virtual AsyncSocket* CreateAsyncSocket(int family, int type);
+
+  virtual void SetMessageQueue(MessageQueue* queue) {
+    server_->SetMessageQueue(queue);
+  }
+  virtual bool Wait(int cms, bool process_io) {
+    return server_->Wait(cms, process_io);
+  }
+  virtual void WakeUp() {
+    return server_->WakeUp();
+  }
+
+  Socket * WrapSocket(Socket * sock, int type);
+  AsyncSocket * WrapSocket(AsyncSocket * sock, int type);
+
+ private:
+  SocketServer * server_;
+  FirewallManager * manager_;
+  CriticalSection crit_;
+  struct Rule {
+    bool allow;
+    FirewallProtocol p;
+    FirewallDirection d;
+    SocketAddress src;
+    SocketAddress dst;
+  };
+  std::vector<Rule> rules_;
+  bool should_delete_server_;
+  bool udp_sockets_enabled_;
+  bool tcp_sockets_enabled_;
+  bool tcp_listen_enabled_;
+};
+
+// FirewallManager allows you to manage firewalls in multiple threads together
+
+class FirewallManager {
+ public:
+  FirewallManager();
+  ~FirewallManager();
+
+  void AddServer(FirewallSocketServer * server);
+  void RemoveServer(FirewallSocketServer * server);
+
+  void AddRule(bool allow, FirewallProtocol p = FP_ANY,
+               FirewallDirection d = FD_ANY,
+               const SocketAddress& addr = SocketAddress());
+  void ClearRules();
+
+ private:
+  CriticalSection crit_;
+  std::vector<FirewallSocketServer *> servers_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_FIREWALLSOCKETSERVER_H_
diff --git a/talk/base/flags.cc b/talk/base/flags.cc
new file mode 100644
index 0000000..10aee04
--- /dev/null
+++ b/talk/base/flags.cc
@@ -0,0 +1,315 @@
+/*
+ * libjingle
+ * Copyright 2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#include <shellapi.h>
+#endif
+
+#include "talk/base/flags.h"
+
+
+// -----------------------------------------------------------------------------
+// Implementation of Flag
+
+Flag::Flag(const char* file, const char* name, const char* comment,
+           Type type, void* variable, FlagValue default__)
+    : file_(file),
+      name_(name),
+      comment_(comment),
+      type_(type),
+      variable_(reinterpret_cast<FlagValue*>(variable)),
+      default_(default__) {
+  FlagList::Register(this);
+}
+
+
+void Flag::SetToDefault() {
+  // Note that we cannot simply do '*variable_ = default_;' since
+  // flag variables are not really of type FlagValue and thus may
+  // be smaller! The FlagValue union is simply 'overlayed' on top
+  // of a flag variable for convenient access. Since union members
+  // are guarantee to be aligned at the beginning, this works.
+  switch (type_) {
+    case Flag::BOOL:
+      variable_->b = default_.b;
+      return;
+    case Flag::INT:
+      variable_->i = default_.i;
+      return;
+    case Flag::FLOAT:
+      variable_->f = default_.f;
+      return;
+    case Flag::STRING:
+      variable_->s = default_.s;
+      return;
+  }
+  UNREACHABLE();
+}
+
+
+static const char* Type2String(Flag::Type type) {
+  switch (type) {
+    case Flag::BOOL: return "bool";
+    case Flag::INT: return "int";
+    case Flag::FLOAT: return "float";
+    case Flag::STRING: return "string";
+  }
+  UNREACHABLE();
+  return NULL;
+}
+
+
+static void PrintFlagValue(Flag::Type type, FlagValue* p) {
+  switch (type) {
+    case Flag::BOOL:
+      printf("%s", (p->b ? "true" : "false"));
+      return;
+    case Flag::INT:
+      printf("%d", p->i);
+      return;
+    case Flag::FLOAT:
+      printf("%f", p->f);
+      return;
+    case Flag::STRING:
+      printf("%s", p->s);
+      return;
+  }
+  UNREACHABLE();
+}
+
+
+void Flag::Print(bool print_current_value) {
+  printf("  --%s (%s)  type: %s  default: ", name_, comment_,
+          Type2String(type_));
+  PrintFlagValue(type_, &default_);
+  if (print_current_value) {
+    printf("  current value: ");
+    PrintFlagValue(type_, variable_);
+  }
+  printf("\n");
+}
+
+
+// -----------------------------------------------------------------------------
+// Implementation of FlagList
+
+Flag* FlagList::list_ = NULL;
+
+
+FlagList::FlagList() {
+  list_ = NULL;
+}
+
+void FlagList::Print(const char* file, bool print_current_value) {
+  // Since flag registration is likely by file (= C++ file),
+  // we don't need to sort by file and still get grouped output.
+  const char* current = NULL;
+  for (Flag* f = list_; f != NULL; f = f->next()) {
+    if (file == NULL || file == f->file()) {
+      if (current != f->file()) {
+        printf("Flags from %s:\n", f->file());
+        current = f->file();
+      }
+      f->Print(print_current_value);
+    }
+  }
+}
+
+
+Flag* FlagList::Lookup(const char* name) {
+  Flag* f = list_;
+  while (f != NULL && strcmp(name, f->name()) != 0)
+    f = f->next();
+  return f;
+}
+
+
+void FlagList::SplitArgument(const char* arg,
+                             char* buffer, int buffer_size,
+                             const char** name, const char** value,
+                             bool* is_bool) {
+  *name = NULL;
+  *value = NULL;
+  *is_bool = false;
+
+  if (*arg == '-') {
+    // find the begin of the flag name
+    arg++;  // remove 1st '-'
+    if (*arg == '-')
+      arg++;  // remove 2nd '-'
+    if (arg[0] == 'n' && arg[1] == 'o') {
+      arg += 2;  // remove "no"
+      *is_bool = true;
+    }
+    *name = arg;
+
+    // find the end of the flag name
+    while (*arg != '\0' && *arg != '=')
+      arg++;
+
+    // get the value if any
+    if (*arg == '=') {
+      // make a copy so we can NUL-terminate flag name
+      int n = static_cast<int>(arg - *name);
+      if (n >= buffer_size)
+        Fatal(__FILE__, __LINE__, "CHECK(%s) failed", "n < buffer_size");
+      memcpy(buffer, *name, n * sizeof(char));
+      buffer[n] = '\0';
+      *name = buffer;
+      // get the value
+      *value = arg + 1;
+    }
+  }
+}
+
+
+int FlagList::SetFlagsFromCommandLine(int* argc, const char** argv,
+                                      bool remove_flags) {
+  // parse arguments
+  for (int i = 1; i < *argc; /* see below */) {
+    int j = i;  // j > 0
+    const char* arg = argv[i++];
+
+    // split arg into flag components
+    char buffer[1024];
+    const char* name;
+    const char* value;
+    bool is_bool;
+    SplitArgument(arg, buffer, sizeof buffer, &name, &value, &is_bool);
+
+    if (name != NULL) {
+      // lookup the flag
+      Flag* flag = Lookup(name);
+      if (flag == NULL) {
+        fprintf(stderr, "Error: unrecognized flag %s\n", arg);
+        return j;
+      }
+
+      // if we still need a flag value, use the next argument if available
+      if (flag->type() != Flag::BOOL && value == NULL) {
+        if (i < *argc) {
+          value = argv[i++];
+        } else {
+          fprintf(stderr, "Error: missing value for flag %s of type %s\n",
+            arg, Type2String(flag->type()));
+          return j;
+        }
+      }
+
+      // set the flag
+      char empty[] = { '\0' };
+      char* endp = empty;
+      switch (flag->type()) {
+        case Flag::BOOL:
+          *flag->bool_variable() = !is_bool;
+          break;
+        case Flag::INT:
+          *flag->int_variable() = strtol(value, &endp, 10);
+          break;
+        case Flag::FLOAT:
+          *flag->float_variable() = strtod(value, &endp);
+          break;
+        case Flag::STRING:
+          *flag->string_variable() = value;
+          break;
+      }
+
+      // handle errors
+      if ((flag->type() == Flag::BOOL && value != NULL) ||
+          (flag->type() != Flag::BOOL && is_bool) ||
+          *endp != '\0') {
+        fprintf(stderr, "Error: illegal value for flag %s of type %s\n",
+          arg, Type2String(flag->type()));
+        return j;
+      }
+
+      // remove the flag & value from the command
+      if (remove_flags)
+        while (j < i)
+          argv[j++] = NULL;
+    }
+  }
+
+  // shrink the argument list
+  if (remove_flags) {
+    int j = 1;
+    for (int i = 1; i < *argc; i++) {
+      if (argv[i] != NULL)
+        argv[j++] = argv[i];
+    }
+    *argc = j;
+  }
+
+  // parsed all flags successfully
+  return 0;
+}
+
+void FlagList::Register(Flag* flag) {
+  assert(flag != NULL && strlen(flag->name()) > 0);
+  if (Lookup(flag->name()) != NULL)
+    Fatal(flag->file(), 0, "flag %s declared twice", flag->name());
+  flag->next_ = list_;
+  list_ = flag;
+}
+
+#ifdef WIN32
+WindowsCommandLineArguments::WindowsCommandLineArguments() {
+  // start by getting the command line.
+  LPTSTR command_line = ::GetCommandLine();
+   // now, convert it to a list of wide char strings.
+  LPWSTR *wide_argv = ::CommandLineToArgvW(command_line, &argc_);
+  // now allocate an array big enough to hold that many string pointers.
+  argv_ = new char*[argc_];
+
+  // iterate over the returned wide strings;
+  for(int i = 0; i < argc_; ++i) {
+    std::string s = talk_base::ToUtf8(wide_argv[i], wcslen(wide_argv[i]));
+    char *buffer = new char[s.length() + 1];
+    talk_base::strcpyn(buffer, s.length() + 1, s.c_str());
+
+    // make sure the argv array has the right string at this point.
+    argv_[i] = buffer;
+  }
+  LocalFree(wide_argv);
+}
+
+WindowsCommandLineArguments::~WindowsCommandLineArguments() {
+  // need to free each string in the array, and then the array.
+  for(int i = 0; i < argc_; i++) {
+    delete[] argv_[i];
+  }
+
+  delete[] argv_;
+}
+#endif  // WIN32
+
diff --git a/talk/base/flags.h b/talk/base/flags.h
new file mode 100644
index 0000000..f22e125
--- /dev/null
+++ b/talk/base/flags.h
@@ -0,0 +1,284 @@
+/*
+ * 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.
+ */
+
+
+// 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,
+// see the FlagList class. For full programmatic access to any
+// flag, see the Flag class.
+//
+// The implementation only relies and basic C++ functionality
+// and needs no special library or STL support.
+
+#ifndef TALK_BASE_FLAGS_H__
+#define TALK_BASE_FLAGS_H__
+
+#include <assert.h>
+
+#include "talk/base/checks.h"
+#include "talk/base/common.h"
+
+// Internal use only.
+union FlagValue {
+  // Note: Because in C++ non-bool values are silently converted into
+  // bool values ('bool b = "false";' results in b == true!), we pass
+  // and int argument to New_BOOL as this appears to be safer - sigh.
+  // In particular, it prevents the (not uncommon!) bug where a bool
+  // flag is defined via: DEFINE_bool(flag, "false", "some comment");.
+  static FlagValue New_BOOL(int b) {
+    FlagValue v;
+    v.b = (b != 0);
+    return v;
+  }
+
+  static FlagValue New_INT(int i) {
+    FlagValue v;
+    v.i = i;
+    return v;
+  }
+
+  static FlagValue New_FLOAT(float f) {
+    FlagValue v;
+    v.f = f;
+    return v;
+  }
+
+  static FlagValue New_STRING(const char* s) {
+    FlagValue v;
+    v.s = s;
+    return v;
+  }
+
+  bool b;
+  int i;
+  double f;
+  const char* s;
+};
+
+
+// Each flag can be accessed programmatically via a Flag object.
+class Flag {
+ public:
+  enum Type { BOOL, INT, FLOAT, STRING };
+
+  // Internal use only.
+  Flag(const char* file, const char* name, const char* comment,
+       Type type, void* variable, FlagValue default_);
+
+  // General flag information
+  const char* file() const  { return file_; }
+  const char* name() const  { return name_; }
+  const char* comment() const  { return comment_; }
+
+  // Flag type
+  Type type() const  { return type_; }
+
+  // Flag variables
+  bool* bool_variable() const {
+    assert(type_ == BOOL);
+    return &variable_->b;
+  }
+  
+  int* int_variable() const {
+    assert(type_ == INT);
+    return &variable_->i;
+  }
+  
+  double* float_variable() const {
+    assert(type_ == FLOAT);
+    return &variable_->f;
+  }
+  
+  const char** string_variable() const {
+    assert(type_ == STRING);
+    return &variable_->s;
+  }
+
+  // Default values
+  bool bool_default() const {
+    assert(type_ == BOOL);
+    return default_.b;
+  }
+  
+  int int_default() const {
+    assert(type_ == INT);
+    return default_.i;
+  }
+  
+  double float_default() const {
+    assert(type_ == FLOAT);
+    return default_.f;
+  }
+  
+  const char* string_default() const {
+    assert(type_ == STRING);
+    return default_.s;
+  }
+
+  // Resets a flag to its default value
+  void SetToDefault();
+
+  // Iteration support
+  Flag* next() const  { return next_; }
+
+  // Prints flag information. The current flag value is only printed
+  // if print_current_value is set.
+  void Print(bool print_current_value);
+
+ private:
+  const char* file_;
+  const char* name_;
+  const char* comment_;
+
+  Type type_;
+  FlagValue* variable_;
+  FlagValue default_;
+
+  Flag* next_;
+
+  friend class FlagList;  // accesses next_
+};
+
+
+// Internal use only.
+#define DEFINE_FLAG(type, c_type, name, default, comment) \
+  /* define and initialize the flag */                    \
+  c_type FLAG_##name = (default);                         \
+  /* register the flag */                                 \
+  static Flag Flag_##name(__FILE__, #name, (comment),   \
+                          Flag::type, &FLAG_##name,       \
+                          FlagValue::New_##type(default))
+
+
+// Internal use only.
+#define DECLARE_FLAG(c_type, name)              \
+  /* declare the external flag */               \
+  extern c_type FLAG_##name
+
+
+// Use the following macros to define a new flag:
+#define DEFINE_bool(name, default, comment) \
+  DEFINE_FLAG(BOOL, bool, name, default, comment)
+#define DEFINE_int(name, default, comment) \
+  DEFINE_FLAG(INT, int, name, default, comment)
+#define DEFINE_float(name, default, comment) \
+  DEFINE_FLAG(FLOAT, double, name, default, comment)
+#define DEFINE_string(name, default, comment) \
+  DEFINE_FLAG(STRING, const char*, name, default, comment)
+
+
+// Use the following macros to declare a flag defined elsewhere:
+#define DECLARE_bool(name)  DECLARE_FLAG(bool, name)
+#define DECLARE_int(name)  DECLARE_FLAG(int, name)
+#define DECLARE_float(name)  DECLARE_FLAG(double, name)
+#define DECLARE_string(name)  DECLARE_FLAG(const char*, name)
+
+
+// The global list of all flags.
+class FlagList {
+ public:
+  FlagList();
+
+  // The NULL-terminated list of all flags. Traverse with Flag::next().
+  static Flag* list()  { return list_; }
+
+  // If file != NULL, prints information for all flags defined in file;
+  // otherwise prints information for all flags in all files. The current
+  // flag value is only printed if print_current_value is set.
+  static void Print(const char* file, bool print_current_value);
+
+  // Lookup a flag by name. Returns the matching flag or NULL.
+  static Flag* Lookup(const char* name);
+
+  // Helper function to parse flags: Takes an argument arg and splits it into
+  // a flag name and flag value (or NULL if they are missing). is_bool is set
+  // if the arg started with "-no" or "--no". The buffer may be used to NUL-
+  // terminate the name, it must be large enough to hold any possible name.
+  static void SplitArgument(const char* arg,
+                            char* buffer, int buffer_size,
+                            const char** name, const char** value,
+                            bool* is_bool);
+
+  // Set the flag values by parsing the command line. If remove_flags
+  // is set, the flags and associated values are removed from (argc,
+  // argv). Returns 0 if no error occurred. Otherwise, returns the
+  // argv index > 0 for the argument where an error occurred. In that
+  // case, (argc, argv) will remain unchanged indepdendent of the
+  // remove_flags value, and no assumptions about flag settings should
+  // be made.
+  //
+  // The following syntax for flags is accepted (both '-' and '--' are ok):
+  //
+  //   --flag        (bool flags only)
+  //   --noflag      (bool flags only)
+  //   --flag=value  (non-bool flags only, no spaces around '=')
+  //   --flag value  (non-bool flags only)
+  static int SetFlagsFromCommandLine(int* argc,
+                                     const char** argv,
+                                     bool remove_flags);
+  static inline int SetFlagsFromCommandLine(int* argc,
+                                            char** argv,
+                                            bool remove_flags) {
+    return SetFlagsFromCommandLine(argc, const_cast<const char**>(argv),
+                                   remove_flags);
+  }
+
+  // Registers a new flag. Called during program initialization. Not
+  // thread-safe.
+  static void Register(Flag* flag);
+
+ private:
+  static Flag* list_;
+};
+
+#ifdef WIN32
+// A helper class to translate Windows command line arguments into UTF8,
+// which then allows us to just pass them to the flags system.
+// This encapsulates all the work of getting the command line and translating
+// it to an array of 8-bit strings; all you have to do is create one of these,
+// and then call argc() and argv().
+class WindowsCommandLineArguments {
+ public:
+  WindowsCommandLineArguments();
+  ~WindowsCommandLineArguments();
+
+  int argc() { return argc_; }
+  char **argv() { return argv_; }
+ private:
+  int argc_;
+  char **argv_;
+
+ private:
+  DISALLOW_EVIL_CONSTRUCTORS(WindowsCommandLineArguments);
+};
+#endif  // WIN32
+
+
+#endif  // SHARED_COMMANDLINEFLAGS_FLAGS_H__
diff --git a/talk/base/gunit.h b/talk/base/gunit.h
new file mode 100644
index 0000000..3a03214
--- /dev/null
+++ b/talk/base/gunit.h
@@ -0,0 +1,112 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_GUNIT_H_
+#define TALK_BASE_GUNIT_H_
+
+#include "talk/base/logging.h"
+#include "talk/base/thread.h"
+#if defined(ANDROID) || defined(GTEST_RELATIVE_PATH)
+#include "gtest/gtest.h"
+#else
+#include "testing/base/public/gunit.h"
+#endif
+
+// forward declarations
+namespace talk_base {
+class Pathname;
+}
+
+// Wait until "ex" is true, or "timeout" expires.
+#define WAIT(ex, timeout) \
+  for (uint32 start = talk_base::Time(); \
+      !(ex) && talk_base::Time() < start + timeout;) \
+    talk_base::Thread::Current()->ProcessMessages(1);
+
+// This returns the result of the test in res, so that we don't re-evaluate
+// the expression in the XXXX_WAIT macros below, since that causes problems
+// when the expression is only true the first time you check it.
+#define WAIT_(ex, timeout, res) \
+  do { \
+    uint32 start = talk_base::Time(); \
+    res = (ex); \
+    while (!res && talk_base::Time() < start + timeout) { \
+      talk_base::Thread::Current()->ProcessMessages(1); \
+      res = (ex); \
+    } \
+  } while (0);
+
+// The typical EXPECT_XXXX and ASSERT_XXXXs, but done until true or a timeout.
+#define EXPECT_TRUE_WAIT(ex, timeout) \
+  do { \
+    bool res; \
+    WAIT_(ex, timeout, res); \
+    if (!res) EXPECT_TRUE(ex); \
+  } while (0);
+
+#define EXPECT_EQ_WAIT(v1, v2, timeout) \
+  do { \
+    bool res; \
+    WAIT_(v1 == v2, timeout, res); \
+    if (!res) EXPECT_EQ(v1, v2); \
+  } while (0);
+
+#define ASSERT_TRUE_WAIT(ex, timeout) \
+  do { \
+    bool res; \
+    WAIT_(ex, timeout, res); \
+    if (!res) ASSERT_TRUE(ex); \
+  } while (0);
+
+#define ASSERT_EQ_WAIT(v1, v2, timeout) \
+  do { \
+    bool res; \
+    WAIT_(v1 == v2, timeout, res); \
+    if (!res) ASSERT_EQ(v1, v2); \
+  } while (0);
+
+// Version with a "soft" timeout and a margin. This logs if the timeout is
+// exceeded, but it only fails if the expression still isn't true after the
+// margin time passes.
+#define EXPECT_TRUE_WAIT_MARGIN(ex, timeout, margin) \
+  do { \
+    bool res; \
+    WAIT_(ex, timeout, res); \
+    if (res) { \
+      break; \
+    } \
+    LOG(LS_WARNING) << "Expression " << #ex << " still not true after " << \
+        timeout << "ms; waiting an additional " << margin << "ms"; \
+    WAIT_(ex, margin, res); \
+    if (!res) { \
+      EXPECT_TRUE(ex); \
+    } \
+  } while (0);
+
+talk_base::Pathname GetTalkDirectory();
+
+#endif  // TALK_BASE_GUNIT_H_
diff --git a/talk/base/gunit_prod.h b/talk/base/gunit_prod.h
new file mode 100644
index 0000000..6be5c53
--- /dev/null
+++ b/talk/base/gunit_prod.h
@@ -0,0 +1,37 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_GUNIT_PROD_H_
+#define TALK_BASE_GUNIT_PROD_H_
+
+#if defined(ANDROID) || defined (GTEST_RELATIVE_PATH)
+#include "gtest/gtest_prod.h"
+#else
+#include "testing/base/gunit_prod.h"
+#endif
+
+#endif  // TALK_BASE_GUNIT_PROD_H_
diff --git a/talk/base/helpers.cc b/talk/base/helpers.cc
new file mode 100644
index 0000000..c2d989b
--- /dev/null
+++ b/talk/base/helpers.cc
@@ -0,0 +1,289 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/helpers.h"
+
+#include <limits>
+
+#include "talk/base/sslconfig.h"
+#if defined(SSL_USE_OPENSSL)
+#include <openssl/rand.h>
+#elif defined(SSL_USE_NSS_RNG)
+#include "pk11func.h"
+#else
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <ntsecapi.h>
+#endif  // WIN32
+#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/timeutils.h"
+
+// Protect against max macro inclusion.
+#undef max
+
+namespace talk_base {
+
+// Base class for RNG implementations.
+class RandomGenerator {
+ public:
+  virtual ~RandomGenerator() {}
+  virtual bool Init(const void* seed, size_t len) = 0;
+  virtual bool Generate(void* buf, size_t len) = 0;
+};
+
+#if defined(SSL_USE_OPENSSL)
+// The OpenSSL RNG. Need to make sure it doesn't run out of entropy.
+class SecureRandomGenerator : public RandomGenerator {
+ public:
+  SecureRandomGenerator() : inited_(false) {
+  }
+  ~SecureRandomGenerator() {
+  }
+  virtual bool Init(const void* seed, size_t len) {
+    // By default, seed from the system state.
+    if (!inited_) {
+      if (RAND_poll() <= 0) {
+        return false;
+      }
+      inited_ = true;
+    }
+    // Allow app data to be mixed in, if provided.
+    if (seed) {
+      RAND_seed(seed, len);
+    }
+    return true;
+  }
+  virtual bool Generate(void* buf, size_t len) {
+    if (!inited_ && !Init(NULL, 0)) {
+      return false;
+    }
+    return (RAND_bytes(reinterpret_cast<unsigned char*>(buf), len) > 0);
+  }
+
+ private:
+  bool inited_;
+};
+
+#elif defined(SSL_USE_NSS_RNG)
+// The NSS RNG.
+class SecureRandomGenerator : public RandomGenerator {
+ public:
+  SecureRandomGenerator() {}
+  ~SecureRandomGenerator() {}
+  virtual bool Init(const void* seed, size_t len) {
+    return true;
+  }
+  virtual bool Generate(void* buf, size_t len) {
+    return (PK11_GenerateRandom(reinterpret_cast<unsigned char*>(buf),
+                                static_cast<int>(len)) == SECSuccess);
+  }
+};
+
+#else
+#ifdef WIN32
+class SecureRandomGenerator : public RandomGenerator {
+ public:
+  SecureRandomGenerator() : advapi32_(NULL), rtl_gen_random_(NULL) {}
+  ~SecureRandomGenerator() {
+    FreeLibrary(advapi32_);
+  }
+
+  virtual bool Init(const void* seed, size_t seed_len) {
+    // We don't do any additional seeding on Win32, we just use the CryptoAPI
+    // RNG (which is exposed as a hidden function off of ADVAPI32 so that we
+    // don't need to drag in all of CryptoAPI)
+    if (rtl_gen_random_) {
+      return true;
+    }
+
+    advapi32_ = LoadLibrary(L"advapi32.dll");
+    if (!advapi32_) {
+      return false;
+    }
+
+    rtl_gen_random_ = reinterpret_cast<RtlGenRandomProc>(
+        GetProcAddress(advapi32_, "SystemFunction036"));
+    if (!rtl_gen_random_) {
+      FreeLibrary(advapi32_);
+      return false;
+    }
+
+    return true;
+  }
+  virtual bool Generate(void* buf, size_t len) {
+    if (!rtl_gen_random_ && !Init(NULL, 0)) {
+      return false;
+    }
+    return (rtl_gen_random_(buf, static_cast<int>(len)) != FALSE);
+  }
+
+ private:
+  typedef BOOL (WINAPI *RtlGenRandomProc)(PVOID, ULONG);
+  HINSTANCE advapi32_;
+  RtlGenRandomProc rtl_gen_random_;
+};
+
+#else
+
+#error No SSL implementation has been selected!
+
+#endif  // WIN32
+#endif
+
+// A test random generator, for predictable output.
+class TestRandomGenerator : public RandomGenerator {
+ public:
+  TestRandomGenerator() : seed_(7) {
+  }
+  ~TestRandomGenerator() {
+  }
+  virtual bool Init(const void* seed, size_t len) {
+    return true;
+  }
+  virtual bool Generate(void* buf, size_t len) {
+    for (size_t i = 0; i < len; ++i) {
+      static_cast<uint8*>(buf)[i] = static_cast<uint8>(GetRandom());
+    }
+    return true;
+  }
+
+ private:
+  int GetRandom() {
+    return ((seed_ = seed_ * 214013L + 2531011L) >> 16) & 0x7fff;
+  }
+  int seed_;
+};
+
+// TODO: Use Base64::Base64Table instead.
+static const char BASE64[64] = {
+  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+  'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+  'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+  'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+  '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
+};
+
+namespace {
+
+// This round about way of creating a global RNG is to safe-guard against
+// indeterminant static initialization order.
+scoped_ptr<RandomGenerator>& GetGlobalRng() {
+  LIBJINGLE_DEFINE_STATIC_LOCAL(scoped_ptr<RandomGenerator>, global_rng,
+                                (new SecureRandomGenerator()));
+  return global_rng;
+}
+
+RandomGenerator& Rng() {
+  return *GetGlobalRng();
+}
+
+}  // namespace
+
+void SetRandomTestMode(bool test) {
+  if (!test) {
+    GetGlobalRng().reset(new SecureRandomGenerator());
+  } else {
+    GetGlobalRng().reset(new TestRandomGenerator());
+  }
+}
+
+bool InitRandom(int seed) {
+  return InitRandom(reinterpret_cast<const char*>(&seed), sizeof(seed));
+}
+
+bool InitRandom(const char* seed, size_t len) {
+  if (!Rng().Init(seed, len)) {
+    LOG(LS_ERROR) << "Failed to init random generator!";
+    return false;
+  }
+  return true;
+}
+
+std::string CreateRandomString(size_t len) {
+  std::string str;
+  CreateRandomString(len, &str);
+  return str;
+}
+
+bool CreateRandomString(size_t len,
+                        const char* table, int table_size,
+                        std::string* str) {
+  str->clear();
+  scoped_array<uint8> bytes(new uint8[len]);
+  if (!Rng().Generate(bytes.get(), len)) {
+    LOG(LS_ERROR) << "Failed to generate random string!";
+    return false;
+  }
+  str->reserve(len);
+  for (size_t i = 0; i < len; ++i) {
+    str->push_back(table[bytes[i] % table_size]);
+  }
+  return true;
+}
+
+bool CreateRandomString(size_t len, std::string* str) {
+  return CreateRandomString(len, BASE64, 64, str);
+}
+
+bool CreateRandomString(size_t len, const std::string& table,
+                        std::string* str) {
+  return CreateRandomString(len, table.c_str(),
+                            static_cast<int>(table.size()), str);
+}
+
+uint32 CreateRandomId() {
+  uint32 id;
+  if (!Rng().Generate(&id, sizeof(id))) {
+    LOG(LS_ERROR) << "Failed to generate random id!";
+  }
+  return id;
+}
+
+uint64 CreateRandomId64() {
+  return static_cast<uint64> (CreateRandomId()) << 32 | CreateRandomId();
+}
+
+uint32 CreateRandomNonZeroId() {
+  uint32 id;
+  do {
+    id = CreateRandomId();
+  } while (id == 0);
+  return id;
+}
+
+double CreateRandomDouble() {
+  return CreateRandomId() / (std::numeric_limits<uint32>::max() +
+      std::numeric_limits<double>::epsilon());
+}
+
+}  // namespace talk_base
diff --git a/talk/base/helpers.h b/talk/base/helpers.h
new file mode 100644
index 0000000..a297554
--- /dev/null
+++ b/talk/base/helpers.h
@@ -0,0 +1,73 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_HELPERS_H_
+#define TALK_BASE_HELPERS_H_
+
+#include <string>
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+// For testing, we can return predictable data.
+void SetRandomTestMode(bool test);
+
+// Initializes the RNG, and seeds it with the specified entropy.
+bool InitRandom(int seed);
+bool InitRandom(const char* seed, size_t len);
+
+// Generates a (cryptographically) random string of the given length.
+// We generate base64 values so that they will be printable.
+// WARNING: could silently fail. Use the version below instead.
+std::string CreateRandomString(size_t length);
+
+// Generates a (cryptographically) random string of the given length.
+// We generate base64 values so that they will be printable.
+// Return false if the random number generator failed.
+bool CreateRandomString(size_t length, std::string* str);
+
+// Generates a (cryptographically) random string of the given length,
+// with characters from the given table. Return false if the random
+// number generator failed.
+bool CreateRandomString(size_t length, const std::string& table,
+                        std::string* str);
+
+// Generates a random id.
+uint32 CreateRandomId();
+
+// Generates a 64 bit random id.
+uint64 CreateRandomId64();
+
+// Generates a random id > 0.
+uint32 CreateRandomNonZeroId();
+
+// Generates a random double between 0.0 (inclusive) and 1.0 (exclusive).
+double CreateRandomDouble();
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_HELPERS_H_
diff --git a/talk/base/helpers_unittest.cc b/talk/base/helpers_unittest.cc
new file mode 100644
index 0000000..0fe1d5b
--- /dev/null
+++ b/talk/base/helpers_unittest.cc
@@ -0,0 +1,83 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+
+namespace talk_base {
+
+TEST(RandomTest, TestCreateRandomId) {
+  CreateRandomId();
+}
+
+TEST(RandomTest, TestCreateRandomDouble) {
+  for (int i = 0; i < 100; ++i) {
+    double r = CreateRandomDouble();
+    EXPECT_GE(r, 0.0);
+    EXPECT_LT(r, 1.0);
+  }
+}
+
+TEST(RandomTest, TestCreateNonZeroRandomId) {
+  EXPECT_NE(0U, CreateRandomNonZeroId());
+}
+
+TEST(RandomTest, TestCreateRandomString) {
+  std::string random = CreateRandomString(256);
+  EXPECT_EQ(256U, random.size());
+  std::string random2;
+  EXPECT_TRUE(CreateRandomString(256, &random2));
+  EXPECT_NE(random, random2);
+  EXPECT_EQ(256U, random2.size());
+}
+
+TEST(RandomTest, TestCreateRandomForTest) {
+  // Make sure we get the output we expect.
+  SetRandomTestMode(true);
+  EXPECT_EQ(2154761789U, CreateRandomId());
+  EXPECT_EQ("h0ISP4S5SJKH/9EY", CreateRandomString(16));
+
+  // Reset and make sure we get the same output.
+  SetRandomTestMode(true);
+  EXPECT_EQ(2154761789U, CreateRandomId());
+  EXPECT_EQ("h0ISP4S5SJKH/9EY", CreateRandomString(16));
+
+  // Test different character sets.
+  SetRandomTestMode(true);
+  std::string str;
+  EXPECT_TRUE(CreateRandomString(16, "a", &str));
+  EXPECT_EQ("aaaaaaaaaaaaaaaa", str);
+  EXPECT_TRUE(CreateRandomString(16, "abc", &str));
+  EXPECT_EQ("acbccaaaabbaacbb", str);
+
+  // Turn off test mode for other tests.
+  SetRandomTestMode(false);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/host.cc b/talk/base/host.cc
new file mode 100644
index 0000000..7decc49
--- /dev/null
+++ b/talk/base/host.cc
@@ -0,0 +1,49 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/host.h"
+
+#ifdef POSIX
+#include <sys/utsname.h>
+#endif  // POSIX
+
+#include <string>
+
+namespace talk_base {
+
+std::string GetHostName() {
+  // TODO: fix or get rid of this
+#if 0
+  struct utsname nm;
+  if (uname(&nm) < 0)
+    FatalError("uname", LAST_SYSTEM_ERROR);
+  return std::string(nm.nodename);
+#endif
+  return "cricket";
+}
+
+}  // namespace talk_base
diff --git a/talk/base/host.h b/talk/base/host.h
new file mode 100644
index 0000000..8528240
--- /dev/null
+++ b/talk/base/host.h
@@ -0,0 +1,40 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_HOST_H_
+#define TALK_BASE_HOST_H_
+
+#include <string>
+
+namespace talk_base {
+
+// Returns the name of the local host.
+std::string GetHostName();
+
+} // namespace talk_base
+
+#endif // TALK_BASE_HOST_H_
diff --git a/talk/base/host_unittest.cc b/talk/base/host_unittest.cc
new file mode 100644
index 0000000..aba87af
--- /dev/null
+++ b/talk/base/host_unittest.cc
@@ -0,0 +1,33 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/host.h"
+
+TEST(Host, GetHostName) {
+  EXPECT_NE("", talk_base::GetHostName());
+}
diff --git a/talk/base/httpbase.cc b/talk/base/httpbase.cc
new file mode 100644
index 0000000..90c1a78
--- /dev/null
+++ b/talk/base/httpbase.cc
@@ -0,0 +1,893 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Copyright 2005 Google Inc.  All Rights Reserved.
+//
+
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#else  // !WIN32
+#define SEC_E_CERT_EXPIRED (-2146893016)
+#endif  // !WIN32
+
+#include "talk/base/common.h"
+#include "talk/base/httpbase.h"
+#include "talk/base/logging.h"
+#include "talk/base/socket.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// Helpers
+//////////////////////////////////////////////////////////////////////
+
+bool MatchHeader(const char* str, size_t len, HttpHeader header) {
+  const char* const header_str = ToString(header);
+  const size_t header_len = strlen(header_str);
+  return (len == header_len) && (_strnicmp(str, header_str, header_len) == 0);
+}
+
+enum {
+  MSG_READ
+};
+
+//////////////////////////////////////////////////////////////////////
+// HttpParser
+//////////////////////////////////////////////////////////////////////
+
+HttpParser::HttpParser() {
+  reset();
+}
+
+HttpParser::~HttpParser() {
+}
+
+void
+HttpParser::reset() {
+  state_ = ST_LEADER;
+  chunked_ = false;
+  data_size_ = SIZE_UNKNOWN;
+}
+
+HttpParser::ProcessResult
+HttpParser::Process(const char* buffer, size_t len, size_t* processed,
+                    HttpError* error) {
+  *processed = 0;
+  *error = HE_NONE;
+
+  if (state_ >= ST_COMPLETE) {
+    ASSERT(false);
+    return PR_COMPLETE;
+  }
+
+  while (true) {
+    if (state_ < ST_DATA) {
+      size_t pos = *processed;
+      while ((pos < len) && (buffer[pos] != '\n')) {
+        pos += 1;
+      }
+      if (pos >= len) {
+        break;  // don't have a full header
+      }
+      const char* line = buffer + *processed;
+      size_t len = (pos - *processed);
+      *processed = pos + 1;
+      while ((len > 0) && isspace(static_cast<unsigned char>(line[len-1]))) {
+        len -= 1;
+      }
+      ProcessResult result = ProcessLine(line, len, error);
+      LOG(LS_VERBOSE) << "Processed line, result=" << result;
+
+      if (PR_CONTINUE != result) {
+        return result;
+      }
+    } else if (data_size_ == 0) {
+      if (chunked_) {
+        state_ = ST_CHUNKTERM;
+      } else {
+        return PR_COMPLETE;
+      }
+    } else {
+      size_t available = len - *processed;
+      if (available <= 0) {
+        break; // no more data
+      }
+      if ((data_size_ != SIZE_UNKNOWN) && (available > data_size_)) {
+        available = data_size_;
+      }
+      size_t read = 0;
+      ProcessResult result = ProcessData(buffer + *processed, available, read,
+                                         error);
+      LOG(LS_VERBOSE) << "Processed data, result: " << result << " read: "
+                      << read << " err: " << error;
+
+      if (PR_CONTINUE != result) {
+        return result;
+      }
+      *processed += read;
+      if (data_size_ != SIZE_UNKNOWN) {
+        data_size_ -= read;
+      }
+    }
+  }
+
+  return PR_CONTINUE;
+}
+
+HttpParser::ProcessResult
+HttpParser::ProcessLine(const char* line, size_t len, HttpError* error) {
+  LOG_F(LS_VERBOSE) << " state: " << state_ << " line: "
+                    << std::string(line, len) << " len: " << len << " err: "
+                    << error;
+
+  switch (state_) {
+  case ST_LEADER:
+    state_ = ST_HEADERS;
+    return ProcessLeader(line, len, error);
+
+  case ST_HEADERS:
+    if (len > 0) {
+      const char* value = strchrn(line, len, ':');
+      if (!value) {
+        *error = HE_PROTOCOL;
+        return PR_COMPLETE;
+      }
+      size_t nlen = (value - line);
+      const char* eol = line + len;
+      do {
+        value += 1;
+      } while ((value < eol) && isspace(static_cast<unsigned char>(*value)));
+      size_t vlen = eol - value;
+      if (MatchHeader(line, nlen, HH_CONTENT_LENGTH)) {
+	unsigned int temp_size;
+        if (sscanf(value, "%u", &temp_size) != 1) {
+          *error = HE_PROTOCOL;
+          return PR_COMPLETE;
+        }
+	data_size_ = static_cast<size_t>(temp_size);
+      } else if (MatchHeader(line, nlen, HH_TRANSFER_ENCODING)) {
+        if ((vlen == 7) && (_strnicmp(value, "chunked", 7) == 0)) {
+          chunked_ = true;
+        } else if ((vlen == 8) && (_strnicmp(value, "identity", 8) == 0)) {
+          chunked_ = false;
+        } else {
+          *error = HE_PROTOCOL;
+          return PR_COMPLETE;
+        }
+      }
+      return ProcessHeader(line, nlen, value, vlen, error);
+    } else {
+      state_ = chunked_ ? ST_CHUNKSIZE : ST_DATA;
+      return ProcessHeaderComplete(chunked_, data_size_, error);
+    }
+    break;
+
+  case ST_CHUNKSIZE:
+    if (len > 0) {
+      char* ptr = NULL;
+      data_size_ = strtoul(line, &ptr, 16);
+      if (ptr != line + len) {
+        *error = HE_PROTOCOL;
+        return PR_COMPLETE;
+      }
+      state_ = (data_size_ == 0) ? ST_TRAILERS : ST_DATA;
+    } else {
+      *error = HE_PROTOCOL;
+      return PR_COMPLETE;
+    }
+    break;
+
+  case ST_CHUNKTERM:
+    if (len > 0) {
+      *error = HE_PROTOCOL;
+      return PR_COMPLETE;
+    } else {
+      state_ = chunked_ ? ST_CHUNKSIZE : ST_DATA;
+    }
+    break;
+
+  case ST_TRAILERS:
+    if (len == 0) {
+      return PR_COMPLETE;
+    }
+    // *error = onHttpRecvTrailer();
+    break;
+
+  default:
+    ASSERT(false);
+    break;
+  }
+
+  return PR_CONTINUE;
+}
+
+bool
+HttpParser::is_valid_end_of_input() const {
+  return (state_ == ST_DATA) && (data_size_ == SIZE_UNKNOWN);
+}
+
+void
+HttpParser::complete(HttpError error) {
+  if (state_ < ST_COMPLETE) {
+    state_ = ST_COMPLETE;
+    OnComplete(error);
+  }
+}
+
+//////////////////////////////////////////////////////////////////////
+// HttpBase::DocumentStream
+//////////////////////////////////////////////////////////////////////
+
+class BlockingMemoryStream : public ExternalMemoryStream {
+public:
+  BlockingMemoryStream(char* buffer, size_t size)
+  : ExternalMemoryStream(buffer, size) { }
+
+  virtual StreamResult DoReserve(size_t size, int* error) {
+    return (buffer_length_ >= size) ? SR_SUCCESS : SR_BLOCK;
+  }
+};
+
+class HttpBase::DocumentStream : public StreamInterface {
+public:
+  DocumentStream(HttpBase* base) : base_(base), error_(HE_DEFAULT) { }
+
+  virtual StreamState GetState() const {
+    if (NULL == base_)
+      return SS_CLOSED;
+    if (HM_RECV == base_->mode_)
+      return SS_OPEN;
+    return SS_OPENING;
+  }
+
+  virtual StreamResult Read(void* buffer, size_t buffer_len,
+                            size_t* read, int* error) {
+    if (!base_) {
+      if (error) *error = error_;
+      return (HE_NONE == error_) ? SR_EOS : SR_ERROR;
+    }
+
+    if (HM_RECV != base_->mode_) {
+      return SR_BLOCK;
+    }
+
+    // DoReceiveLoop writes http document data to the StreamInterface* document
+    // member of HttpData.  In this case, we want this data to be written
+    // directly to our buffer.  To accomplish this, we wrap our buffer with a
+    // StreamInterface, and replace the existing document with our wrapper.
+    // When the method returns, we restore the old document.  Ideally, we would
+    // pass our StreamInterface* to DoReceiveLoop, but due to the callbacks
+    // of HttpParser, we would still need to store the pointer temporarily.
+    scoped_ptr<StreamInterface>
+        stream(new BlockingMemoryStream(reinterpret_cast<char*>(buffer),
+                                        buffer_len));
+
+    // Replace the existing document with our wrapped buffer.
+    base_->data_->document.swap(stream);
+
+    // Pump the I/O loop.  DoReceiveLoop is guaranteed not to attempt to
+    // complete the I/O process, which means that our wrapper is not in danger
+    // of being deleted.  To ensure this, DoReceiveLoop returns true when it
+    // wants complete to be called.  We make sure to uninstall our wrapper
+    // before calling complete().
+    HttpError http_error;
+    bool complete = base_->DoReceiveLoop(&http_error);
+
+    // Reinstall the original output document.
+    base_->data_->document.swap(stream);
+
+    // If we reach the end of the receive stream, we disconnect our stream
+    // adapter from the HttpBase, and further calls to read will either return
+    // EOS or ERROR, appropriately.  Finally, we call complete().
+    StreamResult result = SR_BLOCK;
+    if (complete) {
+      HttpBase* base = Disconnect(http_error);
+      if (error) *error = error_;
+      result = (HE_NONE == error_) ? SR_EOS : SR_ERROR;
+      base->complete(http_error);
+    }
+
+    // Even if we are complete, if some data was read we must return SUCCESS.
+    // Future Reads will return EOS or ERROR based on the error_ variable.
+    size_t position;
+    stream->GetPosition(&position);
+    if (position > 0) {
+      if (read) *read = position;
+      result = SR_SUCCESS;
+    }
+    return result;
+  }
+
+  virtual StreamResult Write(const void* data, size_t data_len,
+                             size_t* written, int* error) {
+    if (error) *error = -1;
+    return SR_ERROR;
+  }
+
+  virtual void Close() {
+    if (base_) {
+      HttpBase* base = Disconnect(HE_NONE);
+      if (HM_RECV == base->mode_ && base->http_stream_) {
+        // Read I/O could have been stalled on the user of this DocumentStream,
+        // so restart the I/O process now that we've removed ourselves.
+        base->http_stream_->PostEvent(SE_READ, 0);
+      }
+    }
+  }
+
+  virtual bool GetAvailable(size_t* size) const {
+    if (!base_ || HM_RECV != base_->mode_)
+      return false;
+    size_t data_size = base_->GetDataRemaining();
+    if (SIZE_UNKNOWN == data_size)
+      return false;
+    if (size)
+      *size = data_size;
+    return true;
+  }
+
+  HttpBase* Disconnect(HttpError error) {
+    ASSERT(NULL != base_);
+    ASSERT(NULL != base_->doc_stream_);
+    HttpBase* base = base_;
+    base_->doc_stream_ = NULL;
+    base_ = NULL;
+    error_ = error;
+    return base;
+  }
+
+private:
+  HttpBase* base_;
+  HttpError error_;
+};
+
+//////////////////////////////////////////////////////////////////////
+// HttpBase
+//////////////////////////////////////////////////////////////////////
+
+HttpBase::HttpBase() : mode_(HM_NONE), data_(NULL), notify_(NULL),
+                       http_stream_(NULL), doc_stream_(NULL) {
+}
+
+HttpBase::~HttpBase() {
+  ASSERT(HM_NONE == mode_);
+}
+
+bool
+HttpBase::isConnected() const {
+  return (http_stream_ != NULL) && (http_stream_->GetState() == SS_OPEN);
+}
+
+bool
+HttpBase::attach(StreamInterface* stream) {
+  if ((mode_ != HM_NONE) || (http_stream_ != NULL) || (stream == NULL)) {
+    ASSERT(false);
+    return false;
+  }
+  http_stream_ = stream;
+  http_stream_->SignalEvent.connect(this, &HttpBase::OnHttpStreamEvent);
+  mode_ = (http_stream_->GetState() == SS_OPENING) ? HM_CONNECT : HM_NONE;
+  return true;
+}
+
+StreamInterface*
+HttpBase::detach() {
+  ASSERT(HM_NONE == mode_);
+  if (mode_ != HM_NONE) {
+    return NULL;
+  }
+  StreamInterface* stream = http_stream_;
+  http_stream_ = NULL;
+  if (stream) {
+    stream->SignalEvent.disconnect(this);
+  }
+  return stream;
+}
+
+void
+HttpBase::send(HttpData* data) {
+  ASSERT(HM_NONE == mode_);
+  if (mode_ != HM_NONE) {
+    return;
+  } else if (!isConnected()) {
+    OnHttpStreamEvent(http_stream_, SE_CLOSE, HE_DISCONNECTED);
+    return;
+  }
+
+  mode_ = HM_SEND;
+  data_ = data;
+  len_ = 0;
+  ignore_data_ = chunk_data_ = false;
+
+  if (data_->document) {
+    data_->document->SignalEvent.connect(this, &HttpBase::OnDocumentEvent);
+  }
+
+  std::string encoding;
+  if (data_->hasHeader(HH_TRANSFER_ENCODING, &encoding)
+      && (encoding == "chunked")) {
+    chunk_data_ = true;
+  }
+
+  len_ = data_->formatLeader(buffer_, sizeof(buffer_));
+  len_ += strcpyn(buffer_ + len_, sizeof(buffer_) - len_, "\r\n");
+
+  header_ = data_->begin();
+  if (header_ == data_->end()) {
+    // We must call this at least once, in the case where there are no headers.
+    queue_headers();
+  }
+
+  flush_data();
+}
+
+void
+HttpBase::recv(HttpData* data) {
+  ASSERT(HM_NONE == mode_);
+  if (mode_ != HM_NONE) {
+    return;
+  } else if (!isConnected()) {
+    OnHttpStreamEvent(http_stream_, SE_CLOSE, HE_DISCONNECTED);
+    return;
+  }
+
+  mode_ = HM_RECV;
+  data_ = data;
+  len_ = 0;
+  ignore_data_ = chunk_data_ = false;
+
+  reset();
+  if (doc_stream_) {
+    doc_stream_->SignalEvent(doc_stream_, SE_OPEN | SE_READ, 0);
+  } else {
+    read_and_process_data();
+  }
+}
+
+void
+HttpBase::abort(HttpError err) {
+  if (mode_ != HM_NONE) {
+    if (http_stream_ != NULL) {
+      http_stream_->Close();
+    }
+    do_complete(err);
+  }
+}
+
+StreamInterface* HttpBase::GetDocumentStream() {
+  if (doc_stream_)
+    return NULL;
+  doc_stream_ = new DocumentStream(this);
+  return doc_stream_;
+}
+
+HttpError HttpBase::HandleStreamClose(int error) {
+  if (http_stream_ != NULL) {
+    http_stream_->Close();
+  }
+  if (error == 0) {
+    if ((mode_ == HM_RECV) && is_valid_end_of_input()) {
+      return HE_NONE;
+    } else {
+      return HE_DISCONNECTED;
+    }
+  } else if (error == SOCKET_EACCES) {
+    return HE_AUTH;
+  } else if (error == SEC_E_CERT_EXPIRED) {
+    return HE_CERTIFICATE_EXPIRED;
+  }
+  LOG_F(LS_ERROR) << "(" << error << ")";
+  return (HM_CONNECT == mode_) ? HE_CONNECT_FAILED : HE_SOCKET_ERROR;
+}
+
+bool HttpBase::DoReceiveLoop(HttpError* error) {
+  ASSERT(HM_RECV == mode_);
+  ASSERT(NULL != error);
+
+  // Do to the latency between receiving read notifications from
+  // pseudotcpchannel, we rely on repeated calls to read in order to acheive
+  // ideal throughput.  The number of reads is limited to prevent starving
+  // the caller.
+
+  size_t loop_count = 0;
+  const size_t kMaxReadCount = 20;
+  bool process_requires_more_data = false;
+  do {
+    // The most frequent use of this function is response to new data available
+    // on http_stream_.  Therefore, we optimize by attempting to read from the
+    // network first (as opposed to processing existing data first).
+
+    if (len_ < sizeof(buffer_)) {
+      // Attempt to buffer more data.
+      size_t read;
+      int read_error;
+      StreamResult read_result = http_stream_->Read(buffer_ + len_,
+                                                    sizeof(buffer_) - len_,
+                                                    &read, &read_error);
+      switch (read_result) {
+      case SR_SUCCESS:
+        ASSERT(len_ + read <= sizeof(buffer_));
+        len_ += read;
+        break;
+      case SR_BLOCK:
+        if (process_requires_more_data) {
+          // We're can't make progress until more data is available.
+          return false;
+        }
+        // Attempt to process the data already in our buffer.
+        break;
+      case SR_EOS:
+        // Clean close, with no error.  Fall through to HandleStreamClose.
+        read_error = 0;
+      case SR_ERROR:
+        *error = HandleStreamClose(read_error);
+        return true;
+      }
+    } else if (process_requires_more_data) {
+      // We have too much unprocessed data in our buffer.  This should only
+      // occur when a single HTTP header is longer than the buffer size (32K).
+      // Anything longer than that is almost certainly an error.
+      *error = HE_OVERFLOW;
+      return true;
+    }
+
+    // Process data in our buffer.  Process is not guaranteed to process all
+    // the buffered data.  In particular, it will wait until a complete
+    // protocol element (such as http header, or chunk size) is available,
+    // before processing it in its entirety.  Also, it is valid and sometimes
+    // necessary to call Process with an empty buffer, since the state machine
+    // may have interrupted state transitions to complete.
+    size_t processed;
+    ProcessResult process_result = Process(buffer_, len_, &processed,
+                                            error);
+    ASSERT(processed <= len_);
+    len_ -= processed;
+    memmove(buffer_, buffer_ + processed, len_);
+    switch (process_result) {
+    case PR_CONTINUE:
+      // We need more data to make progress.
+      process_requires_more_data = true;
+      break;
+    case PR_BLOCK:
+      // We're stalled on writing the processed data.
+      return false;
+    case PR_COMPLETE:
+      // *error already contains the correct code.
+      return true;
+    }
+  } while (++loop_count <= kMaxReadCount);
+
+  LOG_F(LS_WARNING) << "danger of starvation";
+  return false;
+}
+
+void
+HttpBase::read_and_process_data() {
+  HttpError error;
+  if (DoReceiveLoop(&error)) {
+    complete(error);
+  }
+}
+
+void
+HttpBase::flush_data() {
+  ASSERT(HM_SEND == mode_);
+
+  // When send_required is true, no more buffering can occur without a network
+  // write.
+  bool send_required = (len_ >= sizeof(buffer_));
+
+  while (true) {
+    ASSERT(len_ <= sizeof(buffer_));
+
+    // HTTP is inherently sensitive to round trip latency, since a frequent use
+    // case is for small requests and responses to be sent back and forth, and
+    // the lack of pipelining forces a single request to take a minimum of the
+    // round trip time.  As a result, it is to our benefit to pack as much data
+    // into each packet as possible.  Thus, we defer network writes until we've
+    // buffered as much data as possible.
+
+    if (!send_required && (header_ != data_->end())) {
+      // First, attempt to queue more header data.
+      send_required = queue_headers();
+    }
+
+    if (!send_required && data_->document) {
+      // Next, attempt to queue document data.
+
+      const size_t kChunkDigits = 8;
+      size_t offset, reserve;
+      if (chunk_data_) {
+        // Reserve characters at the start for X-byte hex value and \r\n
+        offset = len_ + kChunkDigits + 2;
+        // ... and 2 characters at the end for \r\n
+        reserve = offset + 2;
+      } else {
+        offset = len_;
+        reserve = offset;
+      }
+
+      if (reserve >= sizeof(buffer_)) {
+        send_required = true;
+      } else {
+        size_t read;
+        int error;
+        StreamResult result = data_->document->Read(buffer_ + offset,
+                                                    sizeof(buffer_) - reserve,
+                                                    &read, &error);
+        if (result == SR_SUCCESS) {
+          ASSERT(reserve + read <= sizeof(buffer_));
+          if (chunk_data_) {
+            // Prepend the chunk length in hex.
+            // Note: sprintfn appends a null terminator, which is why we can't
+            // combine it with the line terminator.
+            sprintfn(buffer_ + len_, kChunkDigits + 1, "%.*x",
+                     kChunkDigits, read);
+            // Add line terminator to the chunk length.
+            memcpy(buffer_ + len_ + kChunkDigits, "\r\n", 2);
+            // Add line terminator to the end of the chunk.
+            memcpy(buffer_ + offset + read, "\r\n", 2);
+          }
+          len_ = reserve + read;
+        } else if (result == SR_BLOCK) {
+          // Nothing to do but flush data to the network.
+          send_required = true;
+        } else if (result == SR_EOS) {
+          if (chunk_data_) {
+            // Append the empty chunk and empty trailers, then turn off
+            // chunking.
+            ASSERT(len_ + 5 <= sizeof(buffer_));
+            memcpy(buffer_ + len_, "0\r\n\r\n", 5);
+            len_ += 5;
+            chunk_data_ = false;
+          } else if (0 == len_) {
+            // No more data to read, and no more data to write.
+            do_complete();
+            return;
+          }
+          // Although we are done reading data, there is still data which needs
+          // to be flushed to the network.
+          send_required = true;
+        } else {
+          LOG_F(LS_ERROR) << "Read error: " << error;
+          do_complete(HE_STREAM);
+          return;
+        }
+      }
+    }
+
+    if (0 == len_) {
+      // No data currently available to send.
+      if (!data_->document) {
+        // If there is no source document, that means we're done.
+        do_complete();
+      }
+      return;
+    }
+
+    size_t written;
+    int error;
+    StreamResult result = http_stream_->Write(buffer_, len_, &written, &error);
+    if (result == SR_SUCCESS) {
+      ASSERT(written <= len_);
+      len_ -= written;
+      memmove(buffer_, buffer_ + written, len_);
+      send_required = false;
+    } else if (result == SR_BLOCK) {
+      if (send_required) {
+        // Nothing more we can do until network is writeable.
+        return;
+      }
+    } else {
+      ASSERT(result == SR_ERROR);
+      LOG_F(LS_ERROR) << "error";
+      OnHttpStreamEvent(http_stream_, SE_CLOSE, error);
+      return;
+    }
+  }
+
+  ASSERT(false);
+}
+
+bool
+HttpBase::queue_headers() {
+  ASSERT(HM_SEND == mode_);
+  while (header_ != data_->end()) {
+    size_t len = sprintfn(buffer_ + len_, sizeof(buffer_) - len_,
+                          "%.*s: %.*s\r\n",
+                          header_->first.size(), header_->first.data(),
+                          header_->second.size(), header_->second.data());
+    if (len_ + len < sizeof(buffer_) - 3) {
+      len_ += len;
+      ++header_;
+    } else if (len_ == 0) {
+      LOG(WARNING) << "discarding header that is too long: " << header_->first;
+      ++header_;
+    } else {
+      // Not enough room for the next header, write to network first.
+      return true;
+    }
+  }
+  // End of headers
+  len_ += strcpyn(buffer_ + len_, sizeof(buffer_) - len_, "\r\n");
+  return false;
+}
+
+void
+HttpBase::do_complete(HttpError err) {
+  ASSERT(mode_ != HM_NONE);
+  HttpMode mode = mode_;
+  mode_ = HM_NONE;
+  if (data_ && data_->document) {
+    data_->document->SignalEvent.disconnect(this);
+  }
+  data_ = NULL;
+  if ((HM_RECV == mode) && doc_stream_) {
+    ASSERT(HE_NONE != err);  // We should have Disconnected doc_stream_ already.
+    DocumentStream* ds = doc_stream_;
+    ds->Disconnect(err);
+    ds->SignalEvent(ds, SE_CLOSE, err);
+  }
+  if (notify_) {
+    notify_->onHttpComplete(mode, err);
+  }
+}
+
+//
+// Stream Signals
+//
+
+void
+HttpBase::OnHttpStreamEvent(StreamInterface* stream, int events, int error) {
+  ASSERT(stream == http_stream_);
+  if ((events & SE_OPEN) && (mode_ == HM_CONNECT)) {
+    do_complete();
+    return;
+  }
+
+  if ((events & SE_WRITE) && (mode_ == HM_SEND)) {
+    flush_data();
+    return;
+  }
+
+  if ((events & SE_READ) && (mode_ == HM_RECV)) {
+    if (doc_stream_) {
+      doc_stream_->SignalEvent(doc_stream_, SE_READ, 0);
+    } else {
+      read_and_process_data();
+    }
+    return;
+  }
+
+  if ((events & SE_CLOSE) == 0)
+    return;
+
+  HttpError http_error = HandleStreamClose(error);
+  if (mode_ == HM_RECV) {
+    complete(http_error);
+  } else if (mode_ != HM_NONE) {
+    do_complete(http_error);
+  } else if (notify_) {
+    notify_->onHttpClosed(http_error);
+  }
+}
+
+void
+HttpBase::OnDocumentEvent(StreamInterface* stream, int events, int error) {
+  ASSERT(stream == data_->document.get());
+  if ((events & SE_WRITE) && (mode_ == HM_RECV)) {
+    read_and_process_data();
+    return;
+  }
+
+  if ((events & SE_READ) && (mode_ == HM_SEND)) {
+    flush_data();
+    return;
+  }
+
+  if (events & SE_CLOSE) {
+    LOG_F(LS_ERROR) << "Read error: " << error;
+    do_complete(HE_STREAM);
+    return;
+  }
+}
+
+//
+// HttpParser Implementation
+//
+
+HttpParser::ProcessResult
+HttpBase::ProcessLeader(const char* line, size_t len, HttpError* error) {
+  *error = data_->parseLeader(line, len);
+  return (HE_NONE == *error) ? PR_CONTINUE : PR_COMPLETE;
+}
+
+HttpParser::ProcessResult
+HttpBase::ProcessHeader(const char* name, size_t nlen, const char* value,
+                        size_t vlen, HttpError* error) {
+  std::string sname(name, nlen), svalue(value, vlen);
+  data_->addHeader(sname, svalue);
+  return PR_CONTINUE;
+}
+
+HttpParser::ProcessResult
+HttpBase::ProcessHeaderComplete(bool chunked, size_t& data_size,
+                                HttpError* error) {
+  StreamInterface* old_docstream = doc_stream_;
+  if (notify_) {
+    *error = notify_->onHttpHeaderComplete(chunked, data_size);
+    // The request must not be aborted as a result of this callback.
+    ASSERT(NULL != data_);
+  }
+  if ((HE_NONE == *error) && data_->document) {
+    data_->document->SignalEvent.connect(this, &HttpBase::OnDocumentEvent);
+  }
+  if (HE_NONE != *error) {
+    return PR_COMPLETE;
+  }
+  if (old_docstream != doc_stream_) {
+    // Break out of Process loop, since our I/O model just changed.
+    return PR_BLOCK;
+  }
+  return PR_CONTINUE;
+}
+
+HttpParser::ProcessResult
+HttpBase::ProcessData(const char* data, size_t len, size_t& read,
+                      HttpError* error) {
+  if (ignore_data_ || !data_->document) {
+    read = len;
+    return PR_CONTINUE;
+  }
+  int write_error = 0;
+  switch (data_->document->Write(data, len, &read, &write_error)) {
+  case SR_SUCCESS:
+    return PR_CONTINUE;
+  case SR_BLOCK:
+    return PR_BLOCK;
+  case SR_EOS:
+    LOG_F(LS_ERROR) << "Unexpected EOS";
+    *error = HE_STREAM;
+    return PR_COMPLETE;
+  case SR_ERROR:
+  default:
+    LOG_F(LS_ERROR) << "Write error: " << write_error;
+    *error = HE_STREAM;
+    return PR_COMPLETE;
+  }
+}
+
+void
+HttpBase::OnComplete(HttpError err) {
+  LOG_F(LS_VERBOSE);
+  do_complete(err);
+}
+
+} // namespace talk_base
diff --git a/talk/base/httpbase.h b/talk/base/httpbase.h
new file mode 100644
index 0000000..97527eb
--- /dev/null
+++ b/talk/base/httpbase.h
@@ -0,0 +1,201 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Copyright 2005 Google Inc.  All Rights Reserved.
+//
+
+
+#ifndef TALK_BASE_HTTPBASE_H__
+#define TALK_BASE_HTTPBASE_H__
+
+#include "talk/base/httpcommon.h"
+
+namespace talk_base {
+
+class StreamInterface;
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpParser - Parses an HTTP stream provided via Process and end_of_input, and
+// generates events for:
+//  Structural Elements: Leader, Headers, Document Data
+//  Events: End of Headers, End of Document, Errors
+///////////////////////////////////////////////////////////////////////////////
+
+class HttpParser {
+public:
+  enum ProcessResult { PR_CONTINUE, PR_BLOCK, PR_COMPLETE };
+  HttpParser();
+  virtual ~HttpParser();
+  
+  void reset();
+  ProcessResult Process(const char* buffer, size_t len, size_t* processed,
+                        HttpError* error);
+  bool is_valid_end_of_input() const;
+  void complete(HttpError err);
+  
+  size_t GetDataRemaining() const { return data_size_; }
+
+protected:
+  ProcessResult ProcessLine(const char* line, size_t len, HttpError* error);
+
+  // HttpParser Interface
+  virtual ProcessResult ProcessLeader(const char* line, size_t len,
+                                      HttpError* error) = 0;
+  virtual ProcessResult ProcessHeader(const char* name, size_t nlen,
+                                      const char* value, size_t vlen,
+                                      HttpError* error) = 0;
+  virtual ProcessResult ProcessHeaderComplete(bool chunked, size_t& data_size,
+                                              HttpError* error) = 0;
+  virtual ProcessResult ProcessData(const char* data, size_t len, size_t& read,
+                                    HttpError* error) = 0;
+  virtual void OnComplete(HttpError err) = 0;
+  
+private:
+  enum State {
+    ST_LEADER, ST_HEADERS,
+    ST_CHUNKSIZE, ST_CHUNKTERM, ST_TRAILERS,
+    ST_DATA, ST_COMPLETE
+  } state_;
+  bool chunked_;
+  size_t data_size_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// IHttpNotify
+///////////////////////////////////////////////////////////////////////////////
+
+enum HttpMode { HM_NONE, HM_CONNECT, HM_RECV, HM_SEND };
+
+class IHttpNotify {
+public:
+  virtual ~IHttpNotify() {}
+  virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size) = 0;
+  virtual void onHttpComplete(HttpMode mode, HttpError err) = 0;
+  virtual void onHttpClosed(HttpError err) = 0;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpBase - Provides a state machine for implementing HTTP-based components.
+// Attach HttpBase to a StreamInterface which represents a bidirectional HTTP
+// stream, and then call send() or recv() to initiate sending or receiving one
+// side of an HTTP transaction.  By default, HttpBase operates as an I/O pump,
+// moving data from the HTTP stream to the HttpData object and vice versa.
+// However, it can also operate in stream mode, in which case the user of the
+// stream interface drives I/O via calls to Read().
+///////////////////////////////////////////////////////////////////////////////
+
+class HttpBase
+: private HttpParser,
+  public sigslot::has_slots<>
+{
+public:
+  HttpBase();
+  virtual ~HttpBase();
+
+  void notify(IHttpNotify* notify) { notify_ = notify; }
+  bool attach(StreamInterface* stream);
+  StreamInterface* stream() { return http_stream_; }
+  StreamInterface* detach();
+  bool isConnected() const;
+
+  void send(HttpData* data);
+  void recv(HttpData* data);
+  void abort(HttpError err);
+
+  HttpMode mode() const { return mode_; }
+
+  void set_ignore_data(bool ignore) { ignore_data_ = ignore; }
+  bool ignore_data() const { return ignore_data_; }
+
+  // Obtaining this stream puts HttpBase into stream mode until the stream
+  // is closed.  HttpBase can only expose one open stream interface at a time.
+  // Further calls will return NULL.
+  StreamInterface* GetDocumentStream();
+
+protected:
+  // Do cleanup when the http stream closes (error may be 0 for a clean
+  // shutdown), and return the error code to signal.
+  HttpError HandleStreamClose(int error);
+
+  // DoReceiveLoop acts as a data pump, pulling data from the http stream,
+  // pushing it through the HttpParser, and then populating the HttpData object
+  // based on the callbacks from the parser.  One of the most interesting
+  // callbacks is ProcessData, which provides the actual http document body.
+  // This data is then written to the HttpData::document.  As a result, data
+  // flows from the network to the document, with some incidental protocol
+  // parsing in between.
+  // Ideally, we would pass in the document* to DoReceiveLoop, to more easily
+  // support GetDocumentStream().  However, since the HttpParser is callback
+  // driven, we are forced to store the pointer somewhere until the callback
+  // is triggered.
+  // Returns true if the received document has finished, and
+  // HttpParser::complete should be called.
+  bool DoReceiveLoop(HttpError* err);
+
+  void read_and_process_data();
+  void flush_data();
+  bool queue_headers();
+  void do_complete(HttpError err = HE_NONE);
+
+  void OnHttpStreamEvent(StreamInterface* stream, int events, int error);
+  void OnDocumentEvent(StreamInterface* stream, int events, int error);
+
+  // HttpParser Interface
+  virtual ProcessResult ProcessLeader(const char* line, size_t len,
+                                      HttpError* error);
+  virtual ProcessResult ProcessHeader(const char* name, size_t nlen,
+                                      const char* value, size_t vlen,
+                                      HttpError* error);
+  virtual ProcessResult ProcessHeaderComplete(bool chunked, size_t& data_size,
+                                              HttpError* error);
+  virtual ProcessResult ProcessData(const char* data, size_t len, size_t& read,
+                                    HttpError* error);
+  virtual void OnComplete(HttpError err);
+
+private:
+  class DocumentStream;
+  friend class DocumentStream;
+
+  enum { kBufferSize = 32 * 1024 };
+
+  HttpMode mode_;
+  HttpData* data_;
+  IHttpNotify* notify_;
+  StreamInterface* http_stream_;
+  DocumentStream* doc_stream_;
+  char buffer_[kBufferSize];
+  size_t len_;
+
+  bool ignore_data_, chunk_data_;
+  HttpData::const_iterator header_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_HTTPBASE_H__
diff --git a/talk/base/httpbase_unittest.cc b/talk/base/httpbase_unittest.cc
new file mode 100644
index 0000000..73ef949
--- /dev/null
+++ b/talk/base/httpbase_unittest.cc
@@ -0,0 +1,536 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/httpbase.h"
+#include "talk/base/testutils.h"
+
+namespace talk_base {
+
+const char* const kHttpResponse =
+  "HTTP/1.1 200\r\n"
+  "Connection: Keep-Alive\r\n"
+  "Content-Type: text/plain\r\n"
+  "Proxy-Authorization: 42\r\n"
+  "Transfer-Encoding: chunked\r\n"
+  "\r\n"
+  "00000008\r\n"
+  "Goodbye!\r\n"
+  "0\r\n\r\n";
+
+const char* const kHttpEmptyResponse =
+  "HTTP/1.1 200\r\n"
+  "Connection: Keep-Alive\r\n"
+  "Content-Length: 0\r\n"
+  "Proxy-Authorization: 42\r\n"
+  "\r\n";
+
+const char* const kHttpResponsePrefix =
+  "HTTP/1.1 200\r\n"
+  "Connection: Keep-Alive\r\n"
+  "Content-Type: text/plain\r\n"
+  "Proxy-Authorization: 42\r\n"
+  "Transfer-Encoding: chunked\r\n"
+  "\r\n"
+  "8\r\n"
+  "Goodbye!\r\n";
+
+class HttpBaseTest : public testing::Test, public IHttpNotify {
+public:
+  enum EventType { E_HEADER_COMPLETE, E_COMPLETE, E_CLOSED };
+  struct Event {
+    EventType event;
+    bool chunked;
+    size_t data_size;
+    HttpMode mode;
+    HttpError err;
+  };
+  HttpBaseTest() : mem(NULL), obtain_stream(false), http_stream(NULL) { }
+
+  virtual void SetUp() { }
+  virtual void TearDown() {
+    // Avoid an ASSERT, in case a test doesn't clean up properly
+    base.abort(HE_NONE);
+  }
+
+  virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size) {
+    LOG_F(LS_VERBOSE) << "chunked: " << chunked << " size: " << data_size;
+    Event e = { E_HEADER_COMPLETE, chunked, data_size, HM_NONE, HE_NONE};
+    events.push_back(e);
+    if (obtain_stream) {
+      ObtainDocumentStream();
+    }
+    return HE_NONE;
+  }
+  virtual void onHttpComplete(HttpMode mode, HttpError err) {
+    LOG_F(LS_VERBOSE) << "mode: " << mode << " err: " << err;
+    Event e = { E_COMPLETE, false, 0, mode, err };
+    events.push_back(e);
+  }
+  virtual void onHttpClosed(HttpError err) {
+    LOG_F(LS_VERBOSE) << "err: " << err;
+    Event e = { E_CLOSED, false, 0, HM_NONE, err };
+    events.push_back(e);
+  }
+
+  void SetupSource(const char* response);
+
+  void VerifyHeaderComplete(size_t event_count, bool empty_doc);
+  void VerifyDocumentContents(const char* expected_data,
+                              size_t expected_length = SIZE_UNKNOWN);
+
+  void ObtainDocumentStream();
+  void VerifyDocumentStreamIsOpening();
+  void VerifyDocumentStreamOpenEvent();
+  void ReadDocumentStreamData(const char* expected_data);
+  void VerifyDocumentStreamIsEOS();
+
+  void SetupDocument(const char* response);
+  void VerifySourceContents(const char* expected_data,
+                            size_t expected_length = SIZE_UNKNOWN);
+
+  void VerifyTransferComplete(HttpMode mode, HttpError error);
+
+  HttpBase base;
+  MemoryStream* mem;
+  HttpResponseData data;
+
+  // The source of http data, and source events
+  testing::StreamSource src;
+  std::vector<Event> events;
+
+  // Document stream, and stream events
+  bool obtain_stream;
+  StreamInterface* http_stream;
+  testing::StreamSink sink;
+};
+
+void HttpBaseTest::SetupSource(const char* http_data) {
+  LOG_F(LS_VERBOSE) << "Enter";
+
+  src.SetState(SS_OPENING);
+  src.QueueString(http_data);
+
+  base.notify(this);
+  base.attach(&src);
+  EXPECT_TRUE(events.empty());
+
+  src.SetState(SS_OPEN);
+  ASSERT_EQ(1U, events.size());
+  EXPECT_EQ(E_COMPLETE, events[0].event);
+  EXPECT_EQ(HM_CONNECT, events[0].mode);
+  EXPECT_EQ(HE_NONE, events[0].err);
+  events.clear();
+
+  mem = new MemoryStream;
+  data.document.reset(mem);
+  LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::VerifyHeaderComplete(size_t event_count, bool empty_doc) {
+  LOG_F(LS_VERBOSE) << "Enter";
+
+  ASSERT_EQ(event_count, events.size());
+  EXPECT_EQ(E_HEADER_COMPLETE, events[0].event);
+
+  std::string header;
+  EXPECT_EQ(HVER_1_1, data.version);
+  EXPECT_EQ(static_cast<uint32>(HC_OK), data.scode);
+  EXPECT_TRUE(data.hasHeader(HH_PROXY_AUTHORIZATION, &header));
+  EXPECT_EQ("42", header);
+  EXPECT_TRUE(data.hasHeader(HH_CONNECTION, &header));
+  EXPECT_EQ("Keep-Alive", header);
+
+  if (empty_doc) {
+    EXPECT_FALSE(events[0].chunked);
+    EXPECT_EQ(0U, events[0].data_size);
+
+    EXPECT_TRUE(data.hasHeader(HH_CONTENT_LENGTH, &header));
+    EXPECT_EQ("0", header);
+  } else {
+    EXPECT_TRUE(events[0].chunked);
+    EXPECT_EQ(SIZE_UNKNOWN, events[0].data_size);
+
+    EXPECT_TRUE(data.hasHeader(HH_CONTENT_TYPE, &header));
+    EXPECT_EQ("text/plain", header);
+    EXPECT_TRUE(data.hasHeader(HH_TRANSFER_ENCODING, &header));
+    EXPECT_EQ("chunked", header);
+  }
+  LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::VerifyDocumentContents(const char* expected_data,
+                                          size_t expected_length) {
+  LOG_F(LS_VERBOSE) << "Enter";
+
+  if (SIZE_UNKNOWN == expected_length) {
+    expected_length = strlen(expected_data);
+  }
+  EXPECT_EQ(mem, data.document.get());
+
+  size_t length;
+  mem->GetSize(&length);
+  EXPECT_EQ(expected_length, length);
+  EXPECT_TRUE(0 == memcmp(expected_data, mem->GetBuffer(), length));
+  LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::ObtainDocumentStream() {
+  LOG_F(LS_VERBOSE) << "Enter";
+  EXPECT_FALSE(http_stream);
+  http_stream = base.GetDocumentStream();
+  ASSERT_TRUE(NULL != http_stream);
+  sink.Monitor(http_stream);
+  LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::VerifyDocumentStreamIsOpening() {
+  LOG_F(LS_VERBOSE) << "Enter";
+  ASSERT_TRUE(NULL != http_stream);
+  EXPECT_EQ(0, sink.Events(http_stream));
+  EXPECT_EQ(SS_OPENING, http_stream->GetState());
+
+  size_t read = 0;
+  char buffer[5] = { 0 };
+  EXPECT_EQ(SR_BLOCK, http_stream->Read(buffer, sizeof(buffer), &read, NULL));
+  LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::VerifyDocumentStreamOpenEvent() {
+  LOG_F(LS_VERBOSE) << "Enter";
+
+  ASSERT_TRUE(NULL != http_stream);
+  EXPECT_EQ(SE_OPEN | SE_READ, sink.Events(http_stream));
+  EXPECT_EQ(SS_OPEN, http_stream->GetState());
+
+  // HTTP headers haven't arrived yet
+  EXPECT_EQ(0U, events.size());
+  EXPECT_EQ(static_cast<uint32>(HC_INTERNAL_SERVER_ERROR), data.scode);
+  LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::ReadDocumentStreamData(const char* expected_data) {
+  LOG_F(LS_VERBOSE) << "Enter";
+
+  ASSERT_TRUE(NULL != http_stream);
+  EXPECT_EQ(SS_OPEN, http_stream->GetState());
+
+  // Pump the HTTP I/O using Read, and verify the results.
+  size_t verified_length = 0;
+  const size_t expected_length = strlen(expected_data);
+  while (verified_length < expected_length) {
+    size_t read = 0;
+    char buffer[5] = { 0 };
+    size_t amt_to_read = _min(expected_length - verified_length, sizeof(buffer));
+    EXPECT_EQ(SR_SUCCESS, http_stream->Read(buffer, amt_to_read, &read, NULL));
+    EXPECT_EQ(amt_to_read, read);
+    EXPECT_TRUE(0 == memcmp(expected_data + verified_length, buffer, read));
+    verified_length += read;
+  }
+  LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::VerifyDocumentStreamIsEOS() {
+  LOG_F(LS_VERBOSE) << "Enter";
+
+  ASSERT_TRUE(NULL != http_stream);
+  size_t read = 0;
+  char buffer[5] = { 0 };
+  EXPECT_EQ(SR_EOS, http_stream->Read(buffer, sizeof(buffer), &read, NULL));
+  EXPECT_EQ(SS_CLOSED, http_stream->GetState());
+
+  // When EOS is caused by Read, we don't expect SE_CLOSE
+  EXPECT_EQ(0, sink.Events(http_stream));
+  LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::SetupDocument(const char* document_data) {
+  LOG_F(LS_VERBOSE) << "Enter";
+  src.SetState(SS_OPEN);
+
+  base.notify(this);
+  base.attach(&src);
+  EXPECT_TRUE(events.empty());
+
+  if (document_data) {
+    // Note: we could just call data.set_success("text/plain", mem), but that
+    // won't allow us to use the chunked transfer encoding.
+    mem = new MemoryStream(document_data);
+    data.document.reset(mem);
+    data.setHeader(HH_CONTENT_TYPE, "text/plain");
+    data.setHeader(HH_TRANSFER_ENCODING, "chunked");
+  } else {
+    data.setHeader(HH_CONTENT_LENGTH, "0");
+  }
+  data.scode = HC_OK;
+  data.setHeader(HH_PROXY_AUTHORIZATION, "42");
+  data.setHeader(HH_CONNECTION, "Keep-Alive");
+  LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::VerifySourceContents(const char* expected_data,
+                                        size_t expected_length) {
+  LOG_F(LS_VERBOSE) << "Enter";
+  if (SIZE_UNKNOWN == expected_length) {
+    expected_length = strlen(expected_data);
+  }
+  std::string contents = src.ReadData();
+  EXPECT_EQ(expected_length, contents.length());
+  EXPECT_TRUE(0 == memcmp(expected_data, contents.data(), expected_length));
+  LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::VerifyTransferComplete(HttpMode mode, HttpError error) {
+  LOG_F(LS_VERBOSE) << "Enter";
+  // Verify that http operation has completed
+  ASSERT_TRUE(events.size() > 0);
+  size_t last_event = events.size() - 1;
+  EXPECT_EQ(E_COMPLETE, events[last_event].event);
+  EXPECT_EQ(mode, events[last_event].mode);
+  EXPECT_EQ(error, events[last_event].err);
+  LOG_F(LS_VERBOSE) << "Exit";
+}
+
+//
+// Tests
+//
+
+TEST_F(HttpBaseTest, SupportsSend) {
+  // Queue response document
+  SetupDocument("Goodbye!");
+
+  // Begin send
+  base.send(&data);
+
+  // Send completed successfully
+  VerifyTransferComplete(HM_SEND, HE_NONE);
+  VerifySourceContents(kHttpResponse);
+}
+
+TEST_F(HttpBaseTest, SupportsSendNoDocument) {
+  // Queue response document
+  SetupDocument(NULL);
+
+  // Begin send
+  base.send(&data);
+
+  // Send completed successfully
+  VerifyTransferComplete(HM_SEND, HE_NONE);
+  VerifySourceContents(kHttpEmptyResponse);
+}
+
+TEST_F(HttpBaseTest, SignalsCompleteOnInterruptedSend) {
+  // This test is attempting to expose a bug that occurs when a particular
+  // base objects is used for receiving, and then used for sending.  In
+  // particular, the HttpParser state is different after receiving.  Simulate
+  // that here.
+  SetupSource(kHttpResponse);
+  base.recv(&data);
+  VerifyTransferComplete(HM_RECV, HE_NONE);
+
+  src.Clear();
+  data.clear(true);
+  events.clear();
+  base.detach();
+
+  // Queue response document
+  SetupDocument("Goodbye!");
+
+  // Prevent entire response from being sent
+  const size_t kInterruptedLength = strlen(kHttpResponse) - 1;
+  src.SetWriteBlock(kInterruptedLength);
+
+  // Begin send
+  base.send(&data);
+
+  // Document is mostly complete, but no completion signal yet.
+  EXPECT_TRUE(events.empty());
+  VerifySourceContents(kHttpResponse, kInterruptedLength);
+
+  src.SetState(SS_CLOSED);
+
+  // Send completed with disconnect error, and no additional data.
+  VerifyTransferComplete(HM_SEND, HE_DISCONNECTED);
+  EXPECT_TRUE(src.ReadData().empty());
+}
+
+TEST_F(HttpBaseTest, SupportsReceiveViaDocumentPush) {
+  // Queue response document
+  SetupSource(kHttpResponse);
+
+  // Begin receive
+  base.recv(&data);
+
+  // Document completed successfully
+  VerifyHeaderComplete(2, false);
+  VerifyTransferComplete(HM_RECV, HE_NONE);
+  VerifyDocumentContents("Goodbye!");
+}
+
+TEST_F(HttpBaseTest, SupportsReceiveViaStreamPull) {
+  // Switch to pull mode
+  ObtainDocumentStream();
+  VerifyDocumentStreamIsOpening();
+
+  // Queue response document
+  SetupSource(kHttpResponse);
+  VerifyDocumentStreamIsOpening();
+
+  // Begin receive
+  base.recv(&data);
+
+  // Pull document data
+  VerifyDocumentStreamOpenEvent();
+  ReadDocumentStreamData("Goodbye!");
+  VerifyDocumentStreamIsEOS();
+
+  // Document completed successfully
+  VerifyHeaderComplete(2, false);
+  VerifyTransferComplete(HM_RECV, HE_NONE);
+  VerifyDocumentContents("");
+}
+
+TEST_F(HttpBaseTest, DISABLED_AllowsCloseStreamBeforeDocumentIsComplete) {
+
+  // TODO: Remove extra logging once test failure is understood
+  int old_sev = talk_base::LogMessage::GetLogToDebug();
+  talk_base::LogMessage::LogToDebug(LS_VERBOSE);
+
+
+  // Switch to pull mode
+  ObtainDocumentStream();
+  VerifyDocumentStreamIsOpening();
+
+  // Queue response document
+  SetupSource(kHttpResponse);
+  VerifyDocumentStreamIsOpening();
+
+  // Begin receive
+  base.recv(&data);
+
+  // Pull some of the data
+  VerifyDocumentStreamOpenEvent();
+  ReadDocumentStreamData("Goodb");
+
+  // We've seen the header by now
+  VerifyHeaderComplete(1, false);
+
+  // Close the pull stream, this will transition back to push I/O.
+  http_stream->Close();
+  Thread::Current()->ProcessMessages(0);
+
+  // Remainder of document completed successfully
+  VerifyTransferComplete(HM_RECV, HE_NONE);
+  VerifyDocumentContents("ye!");
+
+  talk_base::LogMessage::LogToDebug(old_sev);
+}
+
+TEST_F(HttpBaseTest, AllowsGetDocumentStreamInResponseToHttpHeader) {
+  // Queue response document
+  SetupSource(kHttpResponse);
+
+  // Switch to pull mode in response to header arrival
+  obtain_stream = true;
+
+  // Begin receive
+  base.recv(&data);
+
+  // We've already seen the header, but not data has arrived
+  VerifyHeaderComplete(1, false);
+  VerifyDocumentContents("");
+
+  // Pull the document data
+  ReadDocumentStreamData("Goodbye!");
+  VerifyDocumentStreamIsEOS();
+
+  // Document completed successfully
+  VerifyTransferComplete(HM_RECV, HE_NONE);
+  VerifyDocumentContents("");
+}
+
+TEST_F(HttpBaseTest, AllowsGetDocumentStreamWithEmptyDocumentBody) {
+  // Queue empty response document
+  SetupSource(kHttpEmptyResponse);
+
+  // Switch to pull mode in response to header arrival
+  obtain_stream = true;
+
+  // Begin receive
+  base.recv(&data);
+
+  // We've already seen the header, but not data has arrived
+  VerifyHeaderComplete(1, true);
+  VerifyDocumentContents("");
+
+  // The document is still open, until we attempt to read
+  ASSERT_TRUE(NULL != http_stream);
+  EXPECT_EQ(SS_OPEN, http_stream->GetState());
+
+  // Attempt to read data, and discover EOS
+  VerifyDocumentStreamIsEOS();
+
+  // Document completed successfully
+  VerifyTransferComplete(HM_RECV, HE_NONE);
+  VerifyDocumentContents("");
+}
+
+TEST_F(HttpBaseTest, SignalsDocumentStreamCloseOnUnexpectedClose) {
+  // Switch to pull mode
+  ObtainDocumentStream();
+  VerifyDocumentStreamIsOpening();
+
+  // Queue response document
+  SetupSource(kHttpResponsePrefix);
+  VerifyDocumentStreamIsOpening();
+
+  // Begin receive
+  base.recv(&data);
+
+  // Pull document data
+  VerifyDocumentStreamOpenEvent();
+  ReadDocumentStreamData("Goodbye!");
+
+  // Simulate unexpected close
+  src.SetState(SS_CLOSED);
+
+  // Observe error event on document stream
+  EXPECT_EQ(testing::SSE_ERROR, sink.Events(http_stream));
+
+  // Future reads give an error
+  int error = 0;
+  char buffer[5] = { 0 };
+  EXPECT_EQ(SR_ERROR, http_stream->Read(buffer, sizeof(buffer), NULL, &error));
+  EXPECT_EQ(HE_DISCONNECTED, error);
+
+  // Document completed with error
+  VerifyHeaderComplete(2, false);
+  VerifyTransferComplete(HM_RECV, HE_DISCONNECTED);
+  VerifyDocumentContents("");
+}
+
+} // namespace talk_base
diff --git a/talk/base/httpclient.cc b/talk/base/httpclient.cc
new file mode 100644
index 0000000..5a16676
--- /dev/null
+++ b/talk/base/httpclient.cc
@@ -0,0 +1,847 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <time.h>
+
+#include "talk/base/httpcommon-inl.h"
+
+#include "talk/base/asyncsocket.h"
+#include "talk/base/common.h"
+#include "talk/base/diskcache.h"
+#include "talk/base/httpclient.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/socketstream.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// Helpers
+//////////////////////////////////////////////////////////////////////
+
+namespace {
+
+const size_t kCacheHeader = 0;
+const size_t kCacheBody = 1;
+
+// Convert decimal string to integer
+bool HttpStringToUInt(const std::string& str, size_t* val) {
+  ASSERT(NULL != val);
+  char* eos = NULL;
+  *val = strtoul(str.c_str(), &eos, 10);
+  return (*eos == '\0');
+}
+
+bool HttpShouldCache(const HttpTransaction& t) {
+  bool verb_allows_cache = (t.request.verb == HV_GET)
+                           || (t.request.verb == HV_HEAD);
+  bool is_range_response = t.response.hasHeader(HH_CONTENT_RANGE, NULL);
+  bool has_expires = t.response.hasHeader(HH_EXPIRES, NULL);
+  bool request_allows_cache =
+    has_expires || (std::string::npos != t.request.path.find('?'));
+  bool response_allows_cache =
+    has_expires || HttpCodeIsCacheable(t.response.scode);
+
+  bool may_cache = verb_allows_cache
+                   && request_allows_cache
+                   && response_allows_cache
+                   && !is_range_response;
+
+  std::string value;
+  if (t.response.hasHeader(HH_CACHE_CONTROL, &value)) {
+    HttpAttributeList directives;
+    HttpParseAttributes(value.data(), value.size(), directives);
+    // Response Directives Summary:
+    // public - always cacheable
+    // private - do not cache in a shared cache
+    // no-cache - may cache, but must revalidate whether fresh or stale
+    // no-store - sensitive information, do not cache or store in any way
+    // max-age - supplants Expires for staleness
+    // s-maxage - use as max-age for shared caches, ignore otherwise
+    // must-revalidate - may cache, but must revalidate after stale
+    // proxy-revalidate - shared cache must revalidate
+    if (HttpHasAttribute(directives, "no-store", NULL)) {
+      may_cache = false;
+    } else if (HttpHasAttribute(directives, "public", NULL)) {
+      may_cache = true;
+    }
+  }
+  return may_cache;
+}
+
+enum HttpCacheState {
+  HCS_FRESH,  // In cache, may use
+  HCS_STALE,  // In cache, must revalidate
+  HCS_NONE    // Not in cache
+};
+
+HttpCacheState HttpGetCacheState(const HttpTransaction& t) {
+  // Temporaries
+  std::string s_temp;
+  time_t u_temp;
+
+  // Current time
+  size_t now = time(0);
+
+  HttpAttributeList cache_control;
+  if (t.response.hasHeader(HH_CACHE_CONTROL, &s_temp)) {
+    HttpParseAttributes(s_temp.data(), s_temp.size(), cache_control);
+  }
+
+  // Compute age of cache document
+  time_t date;
+  if (!t.response.hasHeader(HH_DATE, &s_temp)
+      || !HttpDateToSeconds(s_temp, &date))
+    return HCS_NONE;
+
+  // TODO: Timestamp when cache request sent and response received?
+  time_t request_time = date;
+  time_t response_time = date;
+
+  time_t apparent_age = 0;
+  if (response_time > date) {
+    apparent_age = response_time - date;
+  }
+
+  size_t corrected_received_age = apparent_age;
+  size_t i_temp;
+  if (t.response.hasHeader(HH_AGE, &s_temp)
+      && HttpStringToUInt(s_temp, (&i_temp))) {
+    u_temp = static_cast<time_t>(i_temp);
+    corrected_received_age = stdmax(apparent_age, u_temp);
+  }
+
+  size_t response_delay = response_time - request_time;
+  size_t corrected_initial_age = corrected_received_age + response_delay;
+  size_t resident_time = now - response_time;
+  size_t current_age = corrected_initial_age + resident_time;
+
+  // Compute lifetime of document
+  size_t lifetime;
+  if (HttpHasAttribute(cache_control, "max-age", &s_temp)) {
+    lifetime = atoi(s_temp.c_str());
+  } else if (t.response.hasHeader(HH_EXPIRES, &s_temp)
+             && HttpDateToSeconds(s_temp, &u_temp)) {
+    lifetime = u_temp - date;
+  } else if (t.response.hasHeader(HH_LAST_MODIFIED, &s_temp)
+             && HttpDateToSeconds(s_temp, &u_temp)) {
+    // TODO: Issue warning 113 if age > 24 hours
+    lifetime = static_cast<size_t>(now - u_temp) / 10;
+  } else {
+    return HCS_STALE;
+  }
+
+  return (lifetime > current_age) ? HCS_FRESH : HCS_STALE;
+}
+
+enum HttpValidatorStrength {
+  HVS_NONE,
+  HVS_WEAK,
+  HVS_STRONG
+};
+
+HttpValidatorStrength
+HttpRequestValidatorLevel(const HttpRequestData& request) {
+  if (HV_GET != request.verb)
+    return HVS_STRONG;
+  return request.hasHeader(HH_RANGE, NULL) ? HVS_STRONG : HVS_WEAK;
+}
+
+HttpValidatorStrength
+HttpResponseValidatorLevel(const HttpResponseData& response) {
+  std::string value;
+  if (response.hasHeader(HH_ETAG, &value)) {
+    bool is_weak = (strnicmp(value.c_str(), "W/", 2) == 0);
+    return is_weak ? HVS_WEAK : HVS_STRONG;
+  }
+  if (response.hasHeader(HH_LAST_MODIFIED, &value)) {
+    time_t last_modified, date;
+    if (HttpDateToSeconds(value, &last_modified)
+        && response.hasHeader(HH_DATE, &value)
+        && HttpDateToSeconds(value, &date)
+        && (last_modified + 60 < date)) {
+      return HVS_STRONG;
+    }
+    return HVS_WEAK;
+  }
+  return HVS_NONE;
+}
+
+std::string GetCacheID(const HttpRequestData& request) {
+  std::string id, url;
+  id.append(ToString(request.verb));
+  id.append("_");
+  request.getAbsoluteUri(&url);
+  id.append(url);
+  return id;
+}
+
+}  // anonymous namespace
+
+//////////////////////////////////////////////////////////////////////
+// Public Helpers
+//////////////////////////////////////////////////////////////////////
+
+bool HttpWriteCacheHeaders(const HttpResponseData* response,
+                           StreamInterface* output, size_t* size) {
+  size_t length = 0;
+  // Write all unknown and end-to-end headers to a cache file
+  for (HttpData::const_iterator it = response->begin();
+       it != response->end(); ++it) {
+    HttpHeader header;
+    if (FromString(header, it->first) && !HttpHeaderIsEndToEnd(header))
+      continue;
+    length += it->first.length() + 2 + it->second.length() + 2;
+    if (!output)
+      continue;
+    std::string formatted_header(it->first);
+    formatted_header.append(": ");
+    formatted_header.append(it->second);
+    formatted_header.append("\r\n");
+    StreamResult result = output->WriteAll(formatted_header.data(),
+                                           formatted_header.length(),
+                                           NULL, NULL);
+    if (SR_SUCCESS != result) {
+      return false;
+    }
+  }
+  if (output && (SR_SUCCESS != output->WriteAll("\r\n", 2, NULL, NULL))) {
+    return false;
+  }
+  length += 2;
+  if (size)
+    *size = length;
+  return true;
+}
+
+bool HttpReadCacheHeaders(StreamInterface* input, HttpResponseData* response,
+                          HttpData::HeaderCombine combine) {
+  while (true) {
+    std::string formatted_header;
+    StreamResult result = input->ReadLine(&formatted_header);
+    if ((SR_EOS == result) || (1 == formatted_header.size())) {
+      break;
+    }
+    if (SR_SUCCESS != result) {
+      return false;
+    }
+    size_t end_of_name = formatted_header.find(':');
+    if (std::string::npos == end_of_name) {
+      LOG_F(LS_WARNING) << "Malformed cache header";
+      continue;
+    }
+    size_t start_of_value = end_of_name + 1;
+    size_t end_of_value = formatted_header.length();
+    while ((start_of_value < end_of_value)
+           && isspace(formatted_header[start_of_value]))
+      ++start_of_value;
+    while ((start_of_value < end_of_value)
+           && isspace(formatted_header[end_of_value-1]))
+     --end_of_value;
+    size_t value_length = end_of_value - start_of_value;
+
+    std::string name(formatted_header.substr(0, end_of_name));
+    std::string value(formatted_header.substr(start_of_value, value_length));
+    response->changeHeader(name, value, combine);
+  }
+  return true;
+}
+
+//////////////////////////////////////////////////////////////////////
+// HttpClient
+//////////////////////////////////////////////////////////////////////
+
+const size_t kDefaultRetries = 1;
+const size_t kMaxRedirects = 5;
+
+HttpClient::HttpClient(const std::string& agent, StreamPool* pool,
+                       HttpTransaction* transaction)
+    : agent_(agent), pool_(pool),
+      transaction_(transaction), free_transaction_(false),
+      retries_(kDefaultRetries), attempt_(0), redirects_(0),
+      redirect_action_(REDIRECT_DEFAULT),
+      uri_form_(URI_DEFAULT), cache_(NULL), cache_state_(CS_READY),
+      resolver_(NULL) {
+  base_.notify(this);
+  if (NULL == transaction_) {
+    free_transaction_ = true;
+    transaction_ = new HttpTransaction;
+  }
+}
+
+HttpClient::~HttpClient() {
+  base_.notify(NULL);
+  base_.abort(HE_SHUTDOWN);
+  if (resolver_) {
+    resolver_->Destroy(false);
+  }
+  release();
+  if (free_transaction_)
+    delete transaction_;
+}
+
+void HttpClient::reset() {
+  server_.Clear();
+  request().clear(true);
+  response().clear(true);
+  context_.reset();
+  redirects_ = 0;
+  base_.abort(HE_OPERATION_CANCELLED);
+}
+
+void HttpClient::OnResolveResult(SignalThread* thread) {
+  if (thread != resolver_) {
+    return;
+  }
+  int error = resolver_->error();
+  server_ = resolver_->address();
+  resolver_->Destroy(false);
+  resolver_ = NULL;
+  if (error != 0) {
+    LOG(LS_ERROR) << "Error " << error << " resolving name: "
+                  << server_;
+    onHttpComplete(HM_CONNECT, HE_CONNECT_FAILED);
+  } else {
+    connect();
+  }
+}
+
+void HttpClient::StartDNSLookup() {
+  resolver_ = new AsyncResolver();
+  resolver_->set_address(server_);
+  resolver_->SignalWorkDone.connect(this, &HttpClient::OnResolveResult);
+  resolver_->Start();
+}
+
+void HttpClient::set_server(const SocketAddress& address) {
+  server_ = address;
+  // Setting 'Host' here allows it to be overridden before starting the request,
+  // if necessary.
+  request().setHeader(HH_HOST, HttpAddress(server_, false), true);
+}
+
+StreamInterface* HttpClient::GetDocumentStream() {
+  return base_.GetDocumentStream();
+}
+
+void HttpClient::start() {
+  if (base_.mode() != HM_NONE) {
+    // call reset() to abort an in-progress request
+    ASSERT(false);
+    return;
+  }
+
+  ASSERT(!IsCacheActive());
+
+  if (request().hasHeader(HH_TRANSFER_ENCODING, NULL)) {
+    // Exact size must be known on the client.  Instead of using chunked
+    // encoding, wrap data with auto-caching file or memory stream.
+    ASSERT(false);
+    return;
+  }
+
+  attempt_ = 0;
+
+  // If no content has been specified, using length of 0.
+  request().setHeader(HH_CONTENT_LENGTH, "0", false);
+
+  if (!agent_.empty()) {
+    request().setHeader(HH_USER_AGENT, agent_, false);
+  }
+
+  UriForm uri_form = uri_form_;
+  if (PROXY_HTTPS == proxy_.type) {
+    // Proxies require absolute form
+    uri_form = URI_ABSOLUTE;
+    request().version = HVER_1_0;
+    request().setHeader(HH_PROXY_CONNECTION, "Keep-Alive", false);
+  } else {
+    request().setHeader(HH_CONNECTION, "Keep-Alive", false);
+  }
+
+  if (URI_ABSOLUTE == uri_form) {
+    // Convert to absolute uri form
+    std::string url;
+    if (request().getAbsoluteUri(&url)) {
+      request().path = url;
+    } else {
+      LOG(LS_WARNING) << "Couldn't obtain absolute uri";
+    }
+  } else if (URI_RELATIVE == uri_form) {
+    // Convert to relative uri form
+    std::string host, path;
+    if (request().getRelativeUri(&host, &path)) {
+      request().setHeader(HH_HOST, host);
+      request().path = path;
+    } else {
+      LOG(LS_WARNING) << "Couldn't obtain relative uri";
+    }
+  }
+
+  if ((NULL != cache_) && CheckCache()) {
+    return;
+  }
+
+  connect();
+}
+
+void HttpClient::connect() {
+  int stream_err;
+  if (server_.IsUnresolvedIP()) {
+    StartDNSLookup();
+    return;
+  }
+  StreamInterface* stream = pool_->RequestConnectedStream(server_, &stream_err);
+  if (stream == NULL) {
+    ASSERT(0 != stream_err);
+    LOG(LS_ERROR) << "RequestConnectedStream error: " << stream_err;
+    onHttpComplete(HM_CONNECT, HE_CONNECT_FAILED);
+  } else {
+    base_.attach(stream);
+    if (stream->GetState() == SS_OPEN) {
+      base_.send(&transaction_->request);
+    }
+  }
+}
+
+void HttpClient::prepare_get(const std::string& url) {
+  reset();
+  Url<char> purl(url);
+  set_server(SocketAddress(purl.host(), purl.port()));
+  request().verb = HV_GET;
+  request().path = purl.full_path();
+}
+
+void HttpClient::prepare_post(const std::string& url,
+                              const std::string& content_type,
+                              StreamInterface* request_doc) {
+  reset();
+  Url<char> purl(url);
+  set_server(SocketAddress(purl.host(), purl.port()));
+  request().verb = HV_POST;
+  request().path = purl.full_path();
+  request().setContent(content_type, request_doc);
+}
+
+void HttpClient::release() {
+  if (StreamInterface* stream = base_.detach()) {
+    pool_->ReturnConnectedStream(stream);
+  }
+}
+
+bool HttpClient::ShouldRedirect(std::string* location) const {
+  // TODO: Unittest redirection.
+  if ((REDIRECT_NEVER == redirect_action_)
+      || !HttpCodeIsRedirection(response().scode)
+      || !response().hasHeader(HH_LOCATION, location)
+      || (redirects_ >= kMaxRedirects))
+    return false;
+  return (REDIRECT_ALWAYS == redirect_action_)
+         || (HC_SEE_OTHER == response().scode)
+         || (HV_HEAD == request().verb)
+         || (HV_GET == request().verb);
+}
+
+bool HttpClient::BeginCacheFile() {
+  ASSERT(NULL != cache_);
+  ASSERT(CS_READY == cache_state_);
+
+  std::string id = GetCacheID(request());
+  CacheLock lock(cache_, id, true);
+  if (!lock.IsLocked()) {
+    LOG_F(LS_WARNING) << "Couldn't lock cache";
+    return false;
+  }
+
+  if (HE_NONE != WriteCacheHeaders(id)) {
+    return false;
+  }
+
+  scoped_ptr<StreamInterface> stream(cache_->WriteResource(id, kCacheBody));
+  if (!stream) {
+    LOG_F(LS_ERROR) << "Couldn't open body cache";
+    return false;
+  }
+  lock.Commit();
+
+  // Let's secretly replace the response document with Folgers Crystals,
+  // er, StreamTap, so that we can mirror the data to our cache.
+  StreamInterface* output = response().document.release();
+  if (!output) {
+    output = new NullStream;
+  }
+  StreamTap* tap = new StreamTap(output, stream.release());
+  response().document.reset(tap);
+  return true;
+}
+
+HttpError HttpClient::WriteCacheHeaders(const std::string& id) {
+  scoped_ptr<StreamInterface> stream(cache_->WriteResource(id, kCacheHeader));
+  if (!stream) {
+    LOG_F(LS_ERROR) << "Couldn't open header cache";
+    return HE_CACHE;
+  }
+
+  if (!HttpWriteCacheHeaders(&transaction_->response, stream.get(), NULL)) {
+    LOG_F(LS_ERROR) << "Couldn't write header cache";
+    return HE_CACHE;
+  }
+
+  return HE_NONE;
+}
+
+void HttpClient::CompleteCacheFile() {
+  // Restore previous response document
+  StreamTap* tap = static_cast<StreamTap*>(response().document.release());
+  response().document.reset(tap->Detach());
+
+  int error;
+  StreamResult result = tap->GetTapResult(&error);
+
+  // Delete the tap and cache stream (which completes cache unlock)
+  delete tap;
+
+  if (SR_SUCCESS != result) {
+    LOG(LS_ERROR) << "Cache file error: " << error;
+    cache_->DeleteResource(GetCacheID(request()));
+  }
+}
+
+bool HttpClient::CheckCache() {
+  ASSERT(NULL != cache_);
+  ASSERT(CS_READY == cache_state_);
+
+  std::string id = GetCacheID(request());
+  if (!cache_->HasResource(id)) {
+    // No cache file available
+    return false;
+  }
+
+  HttpError error = ReadCacheHeaders(id, true);
+
+  if (HE_NONE == error) {
+    switch (HttpGetCacheState(*transaction_)) {
+    case HCS_FRESH:
+      // Cache content is good, read from cache
+      break;
+    case HCS_STALE:
+      // Cache content may be acceptable.  Issue a validation request.
+      if (PrepareValidate()) {
+        return false;
+      }
+      // Couldn't validate, fall through.
+    case HCS_NONE:
+      // Cache content is not useable.  Issue a regular request.
+      response().clear(false);
+      return false;
+    }
+  }
+
+  if (HE_NONE == error) {
+    error = ReadCacheBody(id);
+    cache_state_ = CS_READY;
+  }
+
+  if (HE_CACHE == error) {
+    LOG_F(LS_WARNING) << "Cache failure, continuing with normal request";
+    response().clear(false);
+    return false;
+  }
+
+  SignalHttpClientComplete(this, error);
+  return true;
+}
+
+HttpError HttpClient::ReadCacheHeaders(const std::string& id, bool override) {
+  scoped_ptr<StreamInterface> stream(cache_->ReadResource(id, kCacheHeader));
+  if (!stream) {
+    return HE_CACHE;
+  }
+
+  HttpData::HeaderCombine combine =
+    override ? HttpData::HC_REPLACE : HttpData::HC_AUTO;
+
+  if (!HttpReadCacheHeaders(stream.get(), &transaction_->response, combine)) {
+    LOG_F(LS_ERROR) << "Error reading cache headers";
+    return HE_CACHE;
+  }
+
+  response().scode = HC_OK;
+  return HE_NONE;
+}
+
+HttpError HttpClient::ReadCacheBody(const std::string& id) {
+  cache_state_ = CS_READING;
+
+  HttpError error = HE_NONE;
+
+  size_t data_size;
+  scoped_ptr<StreamInterface> stream(cache_->ReadResource(id, kCacheBody));
+  if (!stream || !stream->GetAvailable(&data_size)) {
+    LOG_F(LS_ERROR) << "Unavailable cache body";
+    error = HE_CACHE;
+  } else {
+    error = OnHeaderAvailable(false, false, data_size);
+  }
+
+  if ((HE_NONE == error)
+      && (HV_HEAD != request().verb)
+      && response().document) {
+    char buffer[1024 * 64];
+    StreamResult result = Flow(stream.get(), buffer, ARRAY_SIZE(buffer),
+                               response().document.get());
+    if (SR_SUCCESS != result) {
+      error = HE_STREAM;
+    }
+  }
+
+  return error;
+}
+
+bool HttpClient::PrepareValidate() {
+  ASSERT(CS_READY == cache_state_);
+  // At this point, request() contains the pending request, and response()
+  // contains the cached response headers.  Reformat the request to validate
+  // the cached content.
+  HttpValidatorStrength vs_required = HttpRequestValidatorLevel(request());
+  HttpValidatorStrength vs_available = HttpResponseValidatorLevel(response());
+  if (vs_available < vs_required) {
+    return false;
+  }
+  std::string value;
+  if (response().hasHeader(HH_ETAG, &value)) {
+    request().addHeader(HH_IF_NONE_MATCH, value);
+  }
+  if (response().hasHeader(HH_LAST_MODIFIED, &value)) {
+    request().addHeader(HH_IF_MODIFIED_SINCE, value);
+  }
+  response().clear(false);
+  cache_state_ = CS_VALIDATING;
+  return true;
+}
+
+HttpError HttpClient::CompleteValidate() {
+  ASSERT(CS_VALIDATING == cache_state_);
+
+  std::string id = GetCacheID(request());
+
+  // Merge cached headers with new headers
+  HttpError error = ReadCacheHeaders(id, false);
+  if (HE_NONE != error) {
+    // Rewrite merged headers to cache
+    CacheLock lock(cache_, id);
+    error = WriteCacheHeaders(id);
+  }
+  if (HE_NONE != error) {
+    error = ReadCacheBody(id);
+  }
+  return error;
+}
+
+HttpError HttpClient::OnHeaderAvailable(bool ignore_data, bool chunked,
+                                        size_t data_size) {
+  // If we are ignoring the data, this is an intermediate header.
+  // TODO: don't signal intermediate headers.  Instead, do all header-dependent
+  // processing now, and either set up the next request, or fail outright.
+  // TODO: by default, only write response documents with a success code.
+  SignalHeaderAvailable(this, !ignore_data, ignore_data ? 0 : data_size);
+  if (!ignore_data && !chunked && (data_size != SIZE_UNKNOWN)
+      && response().document) {
+    // Attempt to pre-allocate space for the downloaded data.
+    if (!response().document->ReserveSize(data_size)) {
+      return HE_OVERFLOW;
+    }
+  }
+  return HE_NONE;
+}
+
+//
+// HttpBase Implementation
+//
+
+HttpError HttpClient::onHttpHeaderComplete(bool chunked, size_t& data_size) {
+  if (CS_VALIDATING == cache_state_) {
+    if (HC_NOT_MODIFIED == response().scode) {
+      return CompleteValidate();
+    }
+    // Should we remove conditional headers from request?
+    cache_state_ = CS_READY;
+    cache_->DeleteResource(GetCacheID(request()));
+    // Continue processing response as normal
+  }
+
+  ASSERT(!IsCacheActive());
+  if ((request().verb == HV_HEAD) || !HttpCodeHasBody(response().scode)) {
+    // HEAD requests and certain response codes contain no body
+    data_size = 0;
+  }
+  if (ShouldRedirect(NULL)
+      || ((HC_PROXY_AUTHENTICATION_REQUIRED == response().scode)
+          && (PROXY_HTTPS == proxy_.type))) {
+    // We're going to issue another request, so ignore the incoming data.
+    base_.set_ignore_data(true);
+  }
+
+  HttpError error = OnHeaderAvailable(base_.ignore_data(), chunked, data_size);
+  if (HE_NONE != error) {
+    return error;
+  }
+
+  if ((NULL != cache_)
+      && !base_.ignore_data()
+      && HttpShouldCache(*transaction_)) {
+    if (BeginCacheFile()) {
+      cache_state_ = CS_WRITING;
+    }
+  }
+  return HE_NONE;
+}
+
+void HttpClient::onHttpComplete(HttpMode mode, HttpError err) {
+  if (((HE_DISCONNECTED == err) || (HE_CONNECT_FAILED == err)
+       || (HE_SOCKET_ERROR == err))
+      && (HC_INTERNAL_SERVER_ERROR == response().scode)
+      && (attempt_ < retries_)) {
+    // If the response code has not changed from the default, then we haven't
+    // received anything meaningful from the server, so we are eligible for a
+    // retry.
+    ++attempt_;
+    if (request().document && !request().document->Rewind()) {
+      // Unable to replay the request document.
+      err = HE_STREAM;
+    } else {
+      release();
+      connect();
+      return;
+    }
+  } else if (err != HE_NONE) {
+    // fall through
+  } else if (mode == HM_CONNECT) {
+    base_.send(&transaction_->request);
+    return;
+  } else if ((mode == HM_SEND) || HttpCodeIsInformational(response().scode)) {
+    // If you're interested in informational headers, catch
+    // SignalHeaderAvailable.
+    base_.recv(&transaction_->response);
+    return;
+  } else {
+    if (!HttpShouldKeepAlive(response())) {
+      LOG(LS_VERBOSE) << "HttpClient: closing socket";
+      base_.stream()->Close();
+    }
+    std::string location;
+    if (ShouldRedirect(&location)) {
+      Url<char> purl(location);
+      set_server(SocketAddress(purl.host(), purl.port()));
+      request().path = purl.full_path();
+      if (response().scode == HC_SEE_OTHER) {
+        request().verb = HV_GET;
+        request().clearHeader(HH_CONTENT_TYPE);
+        request().clearHeader(HH_CONTENT_LENGTH);
+        request().document.reset();
+      } else if (request().document && !request().document->Rewind()) {
+        // Unable to replay the request document.
+        ASSERT(REDIRECT_ALWAYS == redirect_action_);
+        err = HE_STREAM;
+      }
+      if (err == HE_NONE) {
+        ++redirects_;
+        context_.reset();
+        response().clear(false);
+        release();
+        start();
+        return;
+      }
+    } else if ((HC_PROXY_AUTHENTICATION_REQUIRED == response().scode)
+               && (PROXY_HTTPS == proxy_.type)) {
+      std::string authorization, auth_method;
+      HttpData::const_iterator begin = response().begin(HH_PROXY_AUTHENTICATE);
+      HttpData::const_iterator end = response().end(HH_PROXY_AUTHENTICATE);
+      for (HttpData::const_iterator it = begin; it != end; ++it) {
+        HttpAuthContext *context = context_.get();
+        HttpAuthResult res = HttpAuthenticate(
+          it->second.data(), it->second.size(),
+          proxy_.address,
+          ToString(request().verb), request().path,
+          proxy_.username, proxy_.password,
+          context, authorization, auth_method);
+        context_.reset(context);
+        if (res == HAR_RESPONSE) {
+          request().setHeader(HH_PROXY_AUTHORIZATION, authorization);
+          if (request().document && !request().document->Rewind()) {
+            err = HE_STREAM;
+          } else {
+            // Explicitly do not reset the HttpAuthContext
+            response().clear(false);
+            // TODO: Reuse socket when authenticating?
+            release();
+            start();
+            return;
+          }
+        } else if (res == HAR_IGNORE) {
+          LOG(INFO) << "Ignoring Proxy-Authenticate: " << auth_method;
+          continue;
+        } else {
+          break;
+        }
+      }
+    }
+  }
+  if (CS_WRITING == cache_state_) {
+    CompleteCacheFile();
+    cache_state_ = CS_READY;
+  } else if (CS_READING == cache_state_) {
+    cache_state_ = CS_READY;
+  }
+  release();
+  SignalHttpClientComplete(this, err);
+}
+
+void HttpClient::onHttpClosed(HttpError err) {
+  // This shouldn't occur, since we return the stream to the pool upon command
+  // completion.
+  ASSERT(false);
+}
+
+//////////////////////////////////////////////////////////////////////
+// HttpClientDefault
+//////////////////////////////////////////////////////////////////////
+
+HttpClientDefault::HttpClientDefault(SocketFactory* factory,
+                                     const std::string& agent,
+                                     HttpTransaction* transaction)
+    : ReuseSocketPool(factory ? factory : Thread::Current()->socketserver()),
+      HttpClient(agent, NULL, transaction) {
+  set_pool(this);
+}
+
+//////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
diff --git a/talk/base/httpclient.h b/talk/base/httpclient.h
new file mode 100644
index 0000000..2e77b0d
--- /dev/null
+++ b/talk/base/httpclient.h
@@ -0,0 +1,219 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_HTTPCLIENT_H__
+#define TALK_BASE_HTTPCLIENT_H__
+
+#include "talk/base/common.h"
+#include "talk/base/httpbase.h"
+#include "talk/base/nethelpers.h"
+#include "talk/base/proxyinfo.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/socketpool.h"
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// Client-specific http utilities
+//////////////////////////////////////////////////////////////////////
+
+// Write cache-relevant response headers to output stream.  If size is non-null,
+// it contains the length of the output in bytes.  output may be null if only
+// the length is desired.
+bool HttpWriteCacheHeaders(const HttpResponseData* response,
+                           StreamInterface* output, size_t* size);
+// Read cached headers from a stream, and them merge them into the response
+// object using the specified combine operation.
+bool HttpReadCacheHeaders(StreamInterface* input,
+                          HttpResponseData* response,
+                          HttpData::HeaderCombine combine);
+
+//////////////////////////////////////////////////////////////////////
+// HttpClient
+// Implements an HTTP 1.1 client.
+//////////////////////////////////////////////////////////////////////
+
+class DiskCache;
+class HttpClient;
+class IPNetPool;
+
+class SignalThread;
+// What to do:  Define STRICT_HTTP_ERROR=1 in your makefile.  Use HttpError in
+// your code (HttpErrorType should only be used for code that is shared
+// with groups which have not yet migrated).
+#if STRICT_HTTP_ERROR
+typedef HttpError HttpErrorType;
+#else  // !STRICT_HTTP_ERROR
+typedef int HttpErrorType;
+#endif  // !STRICT_HTTP_ERROR
+
+class HttpClient : private IHttpNotify, public sigslot::has_slots<> {
+public:
+  // If HttpRequestData and HttpResponseData objects are provided, they must
+  // be freed by the caller.  Otherwise, an internal object is allocated.
+  HttpClient(const std::string& agent, StreamPool* pool,
+             HttpTransaction* transaction = NULL);
+  virtual ~HttpClient();
+
+  void set_pool(StreamPool* pool) { pool_ = pool; }
+
+  void set_agent(const std::string& agent) { agent_ = agent; }
+  const std::string& agent() const { return agent_; }
+  
+  void set_proxy(const ProxyInfo& proxy) { proxy_ = proxy; }
+  const ProxyInfo& proxy() const { return proxy_; }
+
+  // Request retries occur when the connection closes before the beginning of
+  // an http response is received.  In these cases, the http server may have
+  // timed out the keepalive connection before it received our request.  Note
+  // that if a request document cannot be rewound, no retry is made.  The
+  // default is 1.
+  void set_request_retries(size_t retries) { retries_ = retries; }
+  size_t request_retries() const { return retries_; }
+
+  enum RedirectAction { REDIRECT_DEFAULT, REDIRECT_ALWAYS, REDIRECT_NEVER };
+  void set_redirect_action(RedirectAction action) { redirect_action_ = action; }
+  RedirectAction redirect_action() const { return redirect_action_; }
+  // Deprecated
+  void set_fail_redirect(bool fail_redirect) {
+    redirect_action_ = REDIRECT_NEVER;
+  }
+  bool fail_redirect() const { return (REDIRECT_NEVER == redirect_action_); }
+
+  enum UriForm { URI_DEFAULT, URI_ABSOLUTE, URI_RELATIVE };
+  void set_uri_form(UriForm form) { uri_form_ = form; }
+  UriForm uri_form() const { return uri_form_; }
+
+  void set_cache(DiskCache* cache) { ASSERT(!IsCacheActive()); cache_ = cache; }
+  bool cache_enabled() const { return (NULL != cache_); }
+
+  // reset clears the server, request, and response structures.  It will also
+  // abort an active request.
+  void reset();
+  
+  void set_server(const SocketAddress& address);
+  const SocketAddress& server() const { return server_; }
+
+  // Note: in order for HttpClient to retry a POST in response to
+  // an authentication challenge, a redirect response, or socket disconnection,
+  // the request document must support 'replaying' by calling Rewind() on it.
+  // In the case where just a subset of a stream should be used as the request
+  // document, the stream may be wrapped with the StreamSegment adapter.
+  HttpTransaction* transaction() { return transaction_; }
+  const HttpTransaction* transaction() const { return transaction_; }
+  HttpRequestData& request() { return transaction_->request; }
+  const HttpRequestData& request() const { return transaction_->request; }
+  HttpResponseData& response() { return transaction_->response; }
+  const HttpResponseData& response() const { return transaction_->response; }
+  
+  // convenience methods
+  void prepare_get(const std::string& url);
+  void prepare_post(const std::string& url, const std::string& content_type,
+                    StreamInterface* request_doc);
+
+  // Convert HttpClient to a pull-based I/O model.
+  StreamInterface* GetDocumentStream();
+
+  // After you finish setting up your request, call start.
+  void start();
+  
+  // Signalled when the header has finished downloading, before the document
+  // content is processed.  You may change the response document in response
+  // to this signal.  The second parameter indicates whether this is an
+  // intermediate (false) or final (true) header.  An intermediate header is
+  // one that generates another request, such as a redirect or authentication
+  // challenge.  The third parameter indicates the length of the response
+  // document, or else SIZE_UNKNOWN.  Note: Do NOT abort the request in response
+  // to this signal.
+  sigslot::signal3<HttpClient*,bool,size_t> SignalHeaderAvailable;
+  // Signalled when the current request finishes.  On success, err is 0.
+  sigslot::signal2<HttpClient*,HttpErrorType> SignalHttpClientComplete;
+
+protected:
+  void connect();
+  void release();
+
+  bool ShouldRedirect(std::string* location) const;
+
+  bool BeginCacheFile();
+  HttpError WriteCacheHeaders(const std::string& id);
+  void CompleteCacheFile();
+
+  bool CheckCache();
+  HttpError ReadCacheHeaders(const std::string& id, bool override);
+  HttpError ReadCacheBody(const std::string& id);
+
+  bool PrepareValidate();
+  HttpError CompleteValidate();
+
+  HttpError OnHeaderAvailable(bool ignore_data, bool chunked, size_t data_size);
+
+  void StartDNSLookup();
+  void OnResolveResult(SignalThread* thread);
+
+  // IHttpNotify Interface
+  virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size);
+  virtual void onHttpComplete(HttpMode mode, HttpError err);
+  virtual void onHttpClosed(HttpError err);
+  
+private:
+  enum CacheState { CS_READY, CS_WRITING, CS_READING, CS_VALIDATING };
+  bool IsCacheActive() const { return (cache_state_ > CS_READY); }
+
+  std::string agent_;
+  StreamPool* pool_;
+  HttpBase base_;
+  SocketAddress server_;
+  ProxyInfo proxy_;
+  HttpTransaction* transaction_;
+  bool free_transaction_;
+  size_t retries_, attempt_, redirects_;
+  RedirectAction redirect_action_;
+  UriForm uri_form_;
+  scoped_ptr<HttpAuthContext> context_;
+  DiskCache* cache_;
+  CacheState cache_state_;
+  AsyncResolver* resolver_;
+};
+
+//////////////////////////////////////////////////////////////////////
+// HttpClientDefault - Default implementation of HttpClient
+//////////////////////////////////////////////////////////////////////
+
+class HttpClientDefault : public ReuseSocketPool, public HttpClient {
+public:
+  HttpClientDefault(SocketFactory* factory, const std::string& agent,
+                    HttpTransaction* transaction = NULL);
+};
+
+//////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif // TALK_BASE_HTTPCLIENT_H__
diff --git a/talk/base/httpcommon-inl.h b/talk/base/httpcommon-inl.h
new file mode 100644
index 0000000..c9eaffc
--- /dev/null
+++ b/talk/base/httpcommon-inl.h
@@ -0,0 +1,148 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_HTTPCOMMON_INL_H__
+#define TALK_BASE_HTTPCOMMON_INL_H__
+
+#include "talk/base/common.h"
+#include "talk/base/httpcommon.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// Url
+///////////////////////////////////////////////////////////////////////////////
+
+template<class CTYPE>
+void Url<CTYPE>::do_set_url(const CTYPE* val, size_t len) {
+  if (ascnicmp(val, "http://", 7) == 0) {
+    val += 7; len -= 7;
+    secure_ = false;
+  } else if (ascnicmp(val, "https://", 8) == 0) {
+    val += 8; len -= 8;
+    secure_ = true;
+  } else {
+    clear();
+    return;
+  }
+  const CTYPE* path = strchrn(val, len, static_cast<CTYPE>('/'));
+  if (!path) {
+    path = val + len;
+  }
+  size_t address_length = (path - val);
+  do_set_address(val, address_length);
+  do_set_full_path(path, len - address_length);
+}
+
+template<class CTYPE>
+void Url<CTYPE>::do_set_address(const CTYPE* val, size_t len) {
+  if (const CTYPE* at = strchrn(val, len, static_cast<CTYPE>('@'))) {
+    // Everything before the @ is a user:password combo, so skip it.
+    len -= at - val + 1;
+    val = at + 1;
+  }
+  if (const CTYPE* colon = strchrn(val, len, static_cast<CTYPE>(':'))) {
+    host_.assign(val, colon - val);
+    // Note: In every case, we're guaranteed that colon is followed by a null,
+    // or non-numeric character.
+    port_ = static_cast<uint16>(::strtoul(colon + 1, NULL, 10));
+    // TODO: Consider checking for invalid data following port number.
+  } else {
+    host_.assign(val, len);
+    port_ = HttpDefaultPort(secure_);
+  }
+}
+
+template<class CTYPE>
+void Url<CTYPE>::do_set_full_path(const CTYPE* val, size_t len) {
+  const CTYPE* query = strchrn(val, len, static_cast<CTYPE>('?'));
+  if (!query) {
+    query = val + len;
+  }
+  size_t path_length = (query - val);
+  if (0 == path_length) {
+    // TODO: consider failing in this case.
+    path_.assign(1, static_cast<CTYPE>('/'));
+  } else {
+    ASSERT(val[0] == static_cast<CTYPE>('/'));
+    path_.assign(val, path_length);
+  }
+  query_.assign(query, len - path_length);
+}
+
+template<class CTYPE>
+void Url<CTYPE>::do_get_url(string* val) const {
+  CTYPE protocol[9];
+  asccpyn(protocol, ARRAY_SIZE(protocol), secure_ ? "https://" : "http://");
+  val->append(protocol);
+  do_get_address(val);
+  do_get_full_path(val);
+}
+
+template<class CTYPE>
+void Url<CTYPE>::do_get_address(string* val) const {
+  val->append(host_);
+  if (port_ != HttpDefaultPort(secure_)) {
+    CTYPE format[5], port[32];
+    asccpyn(format, ARRAY_SIZE(format), ":%hu");
+    sprintfn(port, ARRAY_SIZE(port), format, port_);
+    val->append(port);
+  }
+}
+
+template<class CTYPE>
+void Url<CTYPE>::do_get_full_path(string* val) const {
+  val->append(path_);
+  val->append(query_);
+}
+
+template<class CTYPE>
+bool Url<CTYPE>::get_attribute(const string& name, string* value) const {
+  if (query_.empty())
+    return false;
+
+  std::string::size_type pos = query_.find(name, 1);
+  if (std::string::npos == pos)
+    return false;
+
+  pos += name.length() + 1;
+  if ((pos > query_.length()) || (static_cast<CTYPE>('=') != query_[pos-1]))
+    return false;
+
+  std::string::size_type end = query_.find(static_cast<CTYPE>('&'), pos);
+  if (std::string::npos == end) {
+    end = query_.length();
+  }
+  value->assign(query_.substr(pos, end - pos));
+  return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_HTTPCOMMON_INL_H__
diff --git a/talk/base/httpcommon.cc b/talk/base/httpcommon.cc
new file mode 100644
index 0000000..458f2f9
--- /dev/null
+++ b/talk/base/httpcommon.cc
@@ -0,0 +1,1055 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <time.h>
+
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#define SECURITY_WIN32
+#include <security.h>
+#endif
+
+#include "talk/base/httpcommon-inl.h"
+
+#include "talk/base/base64.h"
+#include "talk/base/common.h"
+#include "talk/base/cryptstring.h"
+#include "talk/base/httpcommon.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/stringdigest.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+
+namespace talk_base {
+
+#ifdef WIN32
+extern const ConstantLabel SECURITY_ERRORS[];
+#endif
+
+//////////////////////////////////////////////////////////////////////
+// Enum - TODO: expose globally later?
+//////////////////////////////////////////////////////////////////////
+
+bool find_string(size_t& index, const std::string& needle,
+                 const char* const haystack[], size_t max_index) {
+  for (index=0; index<max_index; ++index) {
+	if (_stricmp(needle.c_str(), haystack[index]) == 0) {
+	  return true;
+	}
+  }
+  return false;
+}
+
+template<class E>
+struct Enum {
+  static const char** Names;
+  static size_t Size;
+
+  static inline const char* Name(E val) { return Names[val]; }
+  static inline bool Parse(E& val, const std::string& name) {
+	size_t index;
+	if (!find_string(index, name, Names, Size))
+	  return false;
+	val = static_cast<E>(index);
+	return true;
+  }
+
+  E val;
+
+  inline operator E&() { return val; }
+  inline Enum& operator=(E rhs) { val = rhs; return *this; }
+
+  inline const char* name() const { return Name(val); }
+  inline bool assign(const std::string& name) { return Parse(val, name); }
+  inline Enum& operator=(const std::string& rhs) { assign(rhs); return *this; }
+};
+
+#define ENUM(e,n) \
+  template<> const char** Enum<e>::Names = n; \
+  template<> size_t Enum<e>::Size = sizeof(n)/sizeof(n[0])
+
+//////////////////////////////////////////////////////////////////////
+// HttpCommon
+//////////////////////////////////////////////////////////////////////
+
+static const char* kHttpVersions[HVER_LAST+1] = {
+  "1.0", "1.1", "Unknown"
+};
+ENUM(HttpVersion, kHttpVersions);
+
+static const char* kHttpVerbs[HV_LAST+1] = {
+  "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD"
+};
+ENUM(HttpVerb, kHttpVerbs);
+
+static const char* kHttpHeaders[HH_LAST+1] = {
+  "Age",
+  "Cache-Control",
+  "Connection",
+  "Content-Disposition",
+  "Content-Length",
+  "Content-Range",
+  "Content-Type",
+  "Cookie",
+  "Date",
+  "ETag",
+  "Expires",
+  "Host",
+  "If-Modified-Since",
+  "If-None-Match",
+  "Keep-Alive",
+  "Last-Modified",
+  "Location",
+  "Proxy-Authenticate",
+  "Proxy-Authorization",
+  "Proxy-Connection",
+  "Range",
+  "Set-Cookie",
+  "TE",
+  "Trailers",
+  "Transfer-Encoding",
+  "Upgrade",
+  "User-Agent",
+  "WWW-Authenticate",
+};
+ENUM(HttpHeader, kHttpHeaders);
+
+const char* ToString(HttpVersion version) {
+  return Enum<HttpVersion>::Name(version);
+}
+
+bool FromString(HttpVersion& version, const std::string& str) {
+  return Enum<HttpVersion>::Parse(version, str);
+}
+
+const char* ToString(HttpVerb verb) {
+  return Enum<HttpVerb>::Name(verb);
+}
+
+bool FromString(HttpVerb& verb, const std::string& str) {
+  return Enum<HttpVerb>::Parse(verb, str);
+}
+
+const char* ToString(HttpHeader header) {
+  return Enum<HttpHeader>::Name(header);
+}
+
+bool FromString(HttpHeader& header, const std::string& str) {
+  return Enum<HttpHeader>::Parse(header, str);
+}
+
+bool HttpCodeHasBody(uint32 code) {
+  return !HttpCodeIsInformational(code)
+         && (code != HC_NO_CONTENT) && (code != HC_NOT_MODIFIED);
+}
+
+bool HttpCodeIsCacheable(uint32 code) {
+  switch (code) {
+  case HC_OK:
+  case HC_NON_AUTHORITATIVE:
+  case HC_PARTIAL_CONTENT:
+  case HC_MULTIPLE_CHOICES:
+  case HC_MOVED_PERMANENTLY:
+  case HC_GONE:
+    return true;
+  default:
+    return false;
+  }
+}
+
+bool HttpHeaderIsEndToEnd(HttpHeader header) {
+  switch (header) {
+  case HH_CONNECTION:
+  case HH_KEEP_ALIVE:
+  case HH_PROXY_AUTHENTICATE:
+  case HH_PROXY_AUTHORIZATION:
+  case HH_PROXY_CONNECTION:  // Note part of RFC... this is non-standard header
+  case HH_TE:
+  case HH_TRAILERS:
+  case HH_TRANSFER_ENCODING:
+  case HH_UPGRADE:
+    return false;
+  default:
+    return true;
+  }
+}
+
+bool HttpHeaderIsCollapsible(HttpHeader header) {
+  switch (header) {
+  case HH_SET_COOKIE:
+  case HH_PROXY_AUTHENTICATE:
+  case HH_WWW_AUTHENTICATE:
+    return false;
+  default:
+    return true;
+  }
+}
+
+bool HttpShouldKeepAlive(const HttpData& data) {
+  std::string connection;
+  if ((data.hasHeader(HH_PROXY_CONNECTION, &connection)
+      || data.hasHeader(HH_CONNECTION, &connection))) {
+    return (_stricmp(connection.c_str(), "Keep-Alive") == 0);
+  }
+  return (data.version >= HVER_1_1);
+}
+
+namespace {
+
+inline bool IsEndOfAttributeName(size_t pos, size_t len, const char * data) {
+  if (pos >= len)
+    return true;
+  if (isspace(static_cast<unsigned char>(data[pos])))
+    return true;
+  // The reason for this complexity is that some attributes may contain trailing
+  // equal signs (like base64 tokens in Negotiate auth headers)
+  if ((pos+1 < len) && (data[pos] == '=') &&
+      !isspace(static_cast<unsigned char>(data[pos+1])) &&
+      (data[pos+1] != '=')) {
+    return true;
+  }
+  return false;
+}
+
+// TODO: unittest for EscapeAttribute and HttpComposeAttributes.
+
+std::string EscapeAttribute(const std::string& attribute) {
+  const size_t kMaxLength = attribute.length() * 2 + 1;
+  char* buffer = STACK_ARRAY(char, kMaxLength);
+  size_t len = escape(buffer, kMaxLength, attribute.data(), attribute.length(),
+                      "\"", '\\');
+  return std::string(buffer, len);
+}
+
+}  // anonymous namespace
+
+void HttpComposeAttributes(const HttpAttributeList& attributes, char separator,
+                           std::string* composed) {
+  std::stringstream ss;
+  for (size_t i=0; i<attributes.size(); ++i) {
+    if (i > 0) {
+      ss << separator << " ";
+    }
+    ss << attributes[i].first;
+    if (!attributes[i].second.empty()) {
+      ss << "=\"" << EscapeAttribute(attributes[i].second) << "\"";
+    }
+  }
+  *composed = ss.str();
+}
+
+void HttpParseAttributes(const char * data, size_t len,
+                         HttpAttributeList& attributes) {
+  size_t pos = 0;
+  while (true) {
+    // Skip leading whitespace
+    while ((pos < len) && isspace(static_cast<unsigned char>(data[pos]))) {
+      ++pos;
+    }
+
+    // End of attributes?
+    if (pos >= len)
+      return;
+
+    // Find end of attribute name
+    size_t start = pos;
+    while (!IsEndOfAttributeName(pos, len, data)) {
+      ++pos;
+    }
+
+    HttpAttribute attribute;
+    attribute.first.assign(data + start, data + pos);
+
+    // Attribute has value?
+    if ((pos < len) && (data[pos] == '=')) {
+      ++pos; // Skip '='
+      // Check if quoted value
+      if ((pos < len) && (data[pos] == '"')) {
+        while (++pos < len) {
+          if (data[pos] == '"') {
+            ++pos;
+            break;
+          }
+          if ((data[pos] == '\\') && (pos + 1 < len))
+            ++pos;
+          attribute.second.append(1, data[pos]);
+        }
+      } else {
+        while ((pos < len) &&
+            !isspace(static_cast<unsigned char>(data[pos])) &&
+            (data[pos] != ',')) {
+          attribute.second.append(1, data[pos++]);
+        }
+      }
+    }
+
+    attributes.push_back(attribute);
+    if ((pos < len) && (data[pos] == ',')) ++pos; // Skip ','
+  }
+}
+
+bool HttpHasAttribute(const HttpAttributeList& attributes,
+                      const std::string& name,
+                      std::string* value) {
+  for (HttpAttributeList::const_iterator it = attributes.begin();
+       it != attributes.end(); ++it) {
+    if (it->first == name) {
+      if (value) {
+        *value = it->second;
+      }
+      return true;
+    }
+  }
+  return false;
+}
+
+bool HttpHasNthAttribute(HttpAttributeList& attributes,
+                         size_t index,
+                         std::string* name,
+                         std::string* value) {
+  if (index >= attributes.size())
+    return false;
+
+  if (name)
+    *name = attributes[index].first;
+  if (value)
+    *value = attributes[index].second;
+  return true;
+}
+
+bool HttpDateToSeconds(const std::string& date, time_t* seconds) {
+  const char* const kTimeZones[] = {
+    "UT", "GMT", "EST", "EDT", "CST", "CDT", "MST", "MDT", "PST", "PDT",
+    "A", "B", "C", "D", "E", "F", "G", "H", "I", "K", "L", "M",
+    "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y"
+  };
+  const int kTimeZoneOffsets[] = {
+     0,  0, -5, -4, -6, -5, -7, -6, -8, -7,
+    -1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12,
+     1,  2,  3,  4,  5,  6,  7,  8,  9,  10,  11,  12
+  };
+
+  ASSERT(NULL != seconds);
+  struct tm tval;
+  memset(&tval, 0, sizeof(tval));
+  char month[4], zone[6];
+  memset(month, 0, sizeof(month));
+  memset(zone, 0, sizeof(zone));
+
+  if (7 != sscanf(date.c_str(), "%*3s, %d %3s %d %d:%d:%d %5c",
+                  &tval.tm_mday, month, &tval.tm_year,
+                  &tval.tm_hour, &tval.tm_min, &tval.tm_sec, zone)) {
+    return false;
+  }
+  switch (toupper(month[2])) {
+  case 'N': tval.tm_mon = (month[1] == 'A') ? 0 : 5; break;
+  case 'B': tval.tm_mon = 1; break;
+  case 'R': tval.tm_mon = (month[0] == 'M') ? 2 : 3; break;
+  case 'Y': tval.tm_mon = 4; break;
+  case 'L': tval.tm_mon = 6; break;
+  case 'G': tval.tm_mon = 7; break;
+  case 'P': tval.tm_mon = 8; break;
+  case 'T': tval.tm_mon = 9; break;
+  case 'V': tval.tm_mon = 10; break;
+  case 'C': tval.tm_mon = 11; break;
+  }
+  tval.tm_year -= 1900;
+  size_t gmt, non_gmt = mktime(&tval);
+  if ((zone[0] == '+') || (zone[0] == '-')) {
+    if (!isdigit(zone[1]) || !isdigit(zone[2])
+        || !isdigit(zone[3]) || !isdigit(zone[4])) {
+      return false;
+    }
+    int hours = (zone[1] - '0') * 10 + (zone[2] - '0');
+    int minutes = (zone[3] - '0') * 10 + (zone[4] - '0');
+    int offset = (hours * 60 + minutes) * 60;
+    gmt = non_gmt + ((zone[0] == '+') ? offset : -offset);
+  } else {
+    size_t zindex;
+    if (!find_string(zindex, zone, kTimeZones, ARRAY_SIZE(kTimeZones))) {
+      return false;
+    }
+    gmt = non_gmt + kTimeZoneOffsets[zindex] * 60 * 60;
+  }
+  // TODO: Android should support timezone, see b/2441195
+#if defined(OSX) || defined(ANDROID) || defined(BSD)
+  tm *tm_for_timezone = localtime((time_t *)&gmt);
+  *seconds = gmt + tm_for_timezone->tm_gmtoff;
+#else
+  *seconds = gmt - timezone;
+#endif
+  return true;
+}
+
+std::string HttpAddress(const SocketAddress& address, bool secure) {
+  return (address.port() == HttpDefaultPort(secure))
+          ? address.hostname() : address.ToString();
+}
+
+//////////////////////////////////////////////////////////////////////
+// HttpData
+//////////////////////////////////////////////////////////////////////
+
+void
+HttpData::clear(bool release_document) {
+  // Clear headers first, since releasing a document may have far-reaching
+  // effects.
+  headers_.clear();
+  if (release_document) {
+    document.reset();
+  }
+}
+
+void
+HttpData::copy(const HttpData& src) {
+  headers_ = src.headers_;
+}
+
+void
+HttpData::changeHeader(const std::string& name, const std::string& value,
+                       HeaderCombine combine) {
+  if (combine == HC_AUTO) {
+    HttpHeader header;
+    // Unrecognized headers are collapsible
+    combine = !FromString(header, name) || HttpHeaderIsCollapsible(header)
+              ? HC_YES : HC_NO;
+  } else if (combine == HC_REPLACE) {
+    headers_.erase(name);
+    combine = HC_NO;
+  }
+  // At this point, combine is one of (YES, NO, NEW)
+  if (combine != HC_NO) {
+    HeaderMap::iterator it = headers_.find(name);
+    if (it != headers_.end()) {
+      if (combine == HC_YES) {
+        it->second.append(",");
+        it->second.append(value);
+	  }
+      return;
+	}
+  }
+  headers_.insert(HeaderMap::value_type(name, value));
+}
+
+size_t HttpData::clearHeader(const std::string& name) {
+  return headers_.erase(name);
+}
+
+HttpData::iterator HttpData::clearHeader(iterator header) {
+  iterator deprecated = header++;
+  headers_.erase(deprecated);
+  return header;
+}
+
+bool
+HttpData::hasHeader(const std::string& name, std::string* value) const {
+  HeaderMap::const_iterator it = headers_.find(name);
+  if (it == headers_.end()) {
+    return false;
+  } else if (value) {
+    *value = it->second;
+  }
+  return true;
+}
+
+void HttpData::setContent(const std::string& content_type,
+                          StreamInterface* document) {
+  setHeader(HH_CONTENT_TYPE, content_type);
+  setDocumentAndLength(document);
+}
+
+void HttpData::setDocumentAndLength(StreamInterface* document) {
+  // TODO: Consider calling Rewind() here?
+  ASSERT(!hasHeader(HH_CONTENT_LENGTH, NULL));
+  ASSERT(!hasHeader(HH_TRANSFER_ENCODING, NULL));
+  ASSERT(document != NULL);
+  this->document.reset(document);
+  size_t content_length = 0;
+  if (this->document->GetAvailable(&content_length)) {
+    char buffer[32];
+    sprintfn(buffer, sizeof(buffer), "%d", content_length);
+    setHeader(HH_CONTENT_LENGTH, buffer);
+  } else {
+    setHeader(HH_TRANSFER_ENCODING, "chunked");
+  }
+}
+
+//
+// HttpRequestData
+//
+
+void
+HttpRequestData::clear(bool release_document) {
+  verb = HV_GET;
+  path.clear();
+  HttpData::clear(release_document);
+}
+
+void
+HttpRequestData::copy(const HttpRequestData& src) {
+  verb = src.verb;
+  path = src.path;
+  HttpData::copy(src);
+}
+
+size_t
+HttpRequestData::formatLeader(char* buffer, size_t size) const {
+  ASSERT(path.find(' ') == std::string::npos);
+  return sprintfn(buffer, size, "%s %.*s HTTP/%s", ToString(verb), path.size(),
+                  path.data(), ToString(version));
+}
+
+HttpError
+HttpRequestData::parseLeader(const char* line, size_t len) {
+  UNUSED(len);
+  unsigned int vmajor, vminor;
+  int vend, dstart, dend;
+  if ((sscanf(line, "%*s%n %n%*s%n HTTP/%u.%u", &vend, &dstart, &dend,
+              &vmajor, &vminor) != 2)
+      || (vmajor != 1)) {
+    return HE_PROTOCOL;
+  }
+  if (vminor == 0) {
+    version = HVER_1_0;
+  } else if (vminor == 1) {
+    version = HVER_1_1;
+  } else {
+    return HE_PROTOCOL;
+  }
+  std::string sverb(line, vend);
+  if (!FromString(verb, sverb.c_str())) {
+    return HE_PROTOCOL; // !?! HC_METHOD_NOT_SUPPORTED?
+  }
+  path.assign(line + dstart, line + dend);
+  return HE_NONE;
+}
+
+bool HttpRequestData::getAbsoluteUri(std::string* uri) const {
+  if (HV_CONNECT == verb)
+    return false;
+  Url<char> url(path);
+  if (url.valid()) {
+    uri->assign(path);
+    return true;
+  }
+  std::string host;
+  if (!hasHeader(HH_HOST, &host))
+    return false;
+  url.set_address(host);
+  url.set_full_path(path);
+  uri->assign(url.url());
+  return url.valid();
+}
+
+bool HttpRequestData::getRelativeUri(std::string* host,
+                                     std::string* path) const
+{
+  if (HV_CONNECT == verb)
+    return false;
+  Url<char> url(this->path);
+  if (url.valid()) {
+    host->assign(url.address());
+    path->assign(url.full_path());
+    return true;
+  }
+  if (!hasHeader(HH_HOST, host))
+    return false;
+  path->assign(this->path);
+  return true;
+}
+
+//
+// HttpResponseData
+//
+
+void
+HttpResponseData::clear(bool release_document) {
+  scode = HC_INTERNAL_SERVER_ERROR;
+  message.clear();
+  HttpData::clear(release_document);
+}
+
+void
+HttpResponseData::copy(const HttpResponseData& src) {
+  scode = src.scode;
+  message = src.message;
+  HttpData::copy(src);
+}
+
+void
+HttpResponseData::set_success(uint32 scode) {
+  this->scode = scode;
+  message.clear();
+  setHeader(HH_CONTENT_LENGTH, "0", false);
+}
+
+void
+HttpResponseData::set_success(const std::string& content_type,
+                              StreamInterface* document,
+                              uint32 scode) {
+  this->scode = scode;
+  message.erase(message.begin(), message.end());
+  setContent(content_type, document);
+}
+
+void
+HttpResponseData::set_redirect(const std::string& location, uint32 scode) {
+  this->scode = scode;
+  message.clear();
+  setHeader(HH_LOCATION, location);
+  setHeader(HH_CONTENT_LENGTH, "0", false);
+}
+
+void
+HttpResponseData::set_error(uint32 scode) {
+  this->scode = scode;
+  message.clear();
+  setHeader(HH_CONTENT_LENGTH, "0", false);
+}
+
+size_t
+HttpResponseData::formatLeader(char* buffer, size_t size) const {
+  size_t len = sprintfn(buffer, size, "HTTP/%s %lu", ToString(version), scode);
+  if (!message.empty()) {
+    len += sprintfn(buffer + len, size - len, " %.*s",
+                    message.size(), message.data());
+  }
+  return len;
+}
+
+HttpError
+HttpResponseData::parseLeader(const char* line, size_t len) {
+  size_t pos = 0;
+  unsigned int vmajor, vminor, temp_scode;
+  int temp_pos;
+  if (sscanf(line, "HTTP %u%n",
+             &temp_scode, &temp_pos) == 1) {
+    // This server's response has no version. :( NOTE: This happens for every
+    // response to requests made from Chrome plugins, regardless of the server's
+    // behaviour.
+    LOG(LS_VERBOSE) << "HTTP version missing from response";
+    version = HVER_UNKNOWN;
+  } else if ((sscanf(line, "HTTP/%u.%u %u%n",
+                     &vmajor, &vminor, &temp_scode, &temp_pos) == 3)
+             && (vmajor == 1)) {
+    // This server's response does have a version.
+    if (vminor == 0) {
+      version = HVER_1_0;
+    } else if (vminor == 1) {
+      version = HVER_1_1;
+    } else {
+      return HE_PROTOCOL;
+    }
+  } else {
+    return HE_PROTOCOL;
+  }
+  scode = temp_scode;
+  pos = static_cast<size_t>(temp_pos);
+  while ((pos < len) && isspace(static_cast<unsigned char>(line[pos]))) ++pos;
+  message.assign(line + pos, len - pos);
+  return HE_NONE;
+}
+
+//////////////////////////////////////////////////////////////////////
+// Http Authentication
+//////////////////////////////////////////////////////////////////////
+
+#define TEST_DIGEST 0
+#if TEST_DIGEST
+/*
+const char * const DIGEST_CHALLENGE =
+  "Digest realm=\"testrealm@host.com\","
+  " qop=\"auth,auth-int\","
+  " nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\","
+  " opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"";
+const char * const DIGEST_METHOD = "GET";
+const char * const DIGEST_URI =
+  "/dir/index.html";;
+const char * const DIGEST_CNONCE =
+  "0a4f113b";
+const char * const DIGEST_RESPONSE =
+  "6629fae49393a05397450978507c4ef1";
+//user_ = "Mufasa";
+//pass_ = "Circle Of Life";
+*/
+const char * const DIGEST_CHALLENGE =
+  "Digest realm=\"Squid proxy-caching web server\","
+  " nonce=\"Nny4QuC5PwiSDixJ\","
+  " qop=\"auth\","
+  " stale=false";
+const char * const DIGEST_URI =
+  "/";
+const char * const DIGEST_CNONCE =
+  "6501d58e9a21cee1e7b5fec894ded024";
+const char * const DIGEST_RESPONSE =
+  "edffcb0829e755838b073a4a42de06bc";
+#endif
+
+std::string quote(const std::string& str) {
+  std::string result;
+  result.push_back('"');
+  for (size_t i=0; i<str.size(); ++i) {
+    if ((str[i] == '"') || (str[i] == '\\'))
+      result.push_back('\\');
+    result.push_back(str[i]);
+  }
+  result.push_back('"');
+  return result;
+}
+
+#ifdef WIN32
+struct NegotiateAuthContext : public HttpAuthContext {
+  CredHandle cred;
+  CtxtHandle ctx;
+  size_t steps;
+  bool specified_credentials;
+
+  NegotiateAuthContext(const std::string& auth, CredHandle c1, CtxtHandle c2)
+  : HttpAuthContext(auth), cred(c1), ctx(c2), steps(0),
+    specified_credentials(false)
+  { }
+
+  virtual ~NegotiateAuthContext() {
+    DeleteSecurityContext(&ctx);
+    FreeCredentialsHandle(&cred);
+  }
+};
+#endif // WIN32
+
+HttpAuthResult HttpAuthenticate(
+  const char * challenge, size_t len,
+  const SocketAddress& server,
+  const std::string& method, const std::string& uri,
+  const std::string& username, const CryptString& password,
+  HttpAuthContext *& context, std::string& response, std::string& auth_method)
+{
+#if TEST_DIGEST
+  challenge = DIGEST_CHALLENGE;
+  len = strlen(challenge);
+#endif
+
+  HttpAttributeList args;
+  HttpParseAttributes(challenge, len, args);
+  HttpHasNthAttribute(args, 0, &auth_method, NULL);
+
+  if (context && (context->auth_method != auth_method))
+    return HAR_IGNORE;
+
+  // BASIC
+  if (_stricmp(auth_method.c_str(), "basic") == 0) {
+    if (context)
+      return HAR_CREDENTIALS; // Bad credentials
+    if (username.empty())
+      return HAR_CREDENTIALS; // Missing credentials
+
+    context = new HttpAuthContext(auth_method);
+
+    // TODO: convert sensitive to a secure buffer that gets securely deleted
+    //std::string decoded = username + ":" + password;
+    size_t len = username.size() + password.GetLength() + 2;
+    char * sensitive = new char[len];
+    size_t pos = strcpyn(sensitive, len, username.data(), username.size());
+    pos += strcpyn(sensitive + pos, len - pos, ":");
+    password.CopyTo(sensitive + pos, true);
+
+    response = auth_method;
+    response.append(" ");
+    // TODO: create a sensitive-source version of Base64::encode
+    response.append(Base64::Encode(sensitive));
+    memset(sensitive, 0, len);
+    delete [] sensitive;
+    return HAR_RESPONSE;
+  }
+
+  // DIGEST
+  if (_stricmp(auth_method.c_str(), "digest") == 0) {
+    if (context)
+      return HAR_CREDENTIALS; // Bad credentials
+    if (username.empty())
+      return HAR_CREDENTIALS; // Missing credentials
+
+    context = new HttpAuthContext(auth_method);
+
+    std::string cnonce, ncount;
+#if TEST_DIGEST
+    method = DIGEST_METHOD;
+    uri    = DIGEST_URI;
+    cnonce = DIGEST_CNONCE;
+#else
+    char buffer[256];
+    sprintf(buffer, "%d", static_cast<int>(time(0)));
+    cnonce = MD5(buffer);
+#endif
+    ncount = "00000001";
+
+    std::string realm, nonce, qop, opaque;
+    HttpHasAttribute(args, "realm", &realm);
+    HttpHasAttribute(args, "nonce", &nonce);
+    bool has_qop = HttpHasAttribute(args, "qop", &qop);
+    bool has_opaque = HttpHasAttribute(args, "opaque", &opaque);
+
+    // TODO: convert sensitive to be secure buffer
+    //std::string A1 = username + ":" + realm + ":" + password;
+    size_t len = username.size() + realm.size() + password.GetLength() + 3;
+    char * sensitive = new char[len];  // A1
+    size_t pos = strcpyn(sensitive, len, username.data(), username.size());
+    pos += strcpyn(sensitive + pos, len - pos, ":");
+    pos += strcpyn(sensitive + pos, len - pos, realm.c_str());
+    pos += strcpyn(sensitive + pos, len - pos, ":");
+    password.CopyTo(sensitive + pos, true);
+
+    std::string A2 = method + ":" + uri;
+    std::string middle;
+    if (has_qop) {
+      qop = "auth";
+      middle = nonce + ":" + ncount + ":" + cnonce + ":" + qop;
+    } else {
+      middle = nonce;
+    }
+    std::string HA1 = MD5(sensitive);
+    memset(sensitive, 0, len);
+    delete [] sensitive;
+    std::string HA2 = MD5(A2);
+    std::string dig_response = MD5(HA1 + ":" + middle + ":" + HA2);
+
+#if TEST_DIGEST
+    ASSERT(strcmp(dig_response.c_str(), DIGEST_RESPONSE) == 0);
+#endif
+
+    std::stringstream ss;
+    ss << auth_method;
+    ss << " username=" << quote(username);
+    ss << ", realm=" << quote(realm);
+    ss << ", nonce=" << quote(nonce);
+    ss << ", uri=" << quote(uri);
+    if (has_qop) {
+      ss << ", qop=" << qop;
+      ss << ", nc="  << ncount;
+      ss << ", cnonce=" << quote(cnonce);
+    }
+    ss << ", response=\"" << dig_response << "\"";
+    if (has_opaque) {
+      ss << ", opaque=" << quote(opaque);
+    }
+    response = ss.str();
+    return HAR_RESPONSE;
+  }
+
+#ifdef WIN32
+#if 1
+  bool want_negotiate = (_stricmp(auth_method.c_str(), "negotiate") == 0);
+  bool want_ntlm = (_stricmp(auth_method.c_str(), "ntlm") == 0);
+  // SPNEGO & NTLM
+  if (want_negotiate || want_ntlm) {
+    const size_t MAX_MESSAGE = 12000, MAX_SPN = 256;
+    char out_buf[MAX_MESSAGE], spn[MAX_SPN];
+
+#if 0 // Requires funky windows versions
+    DWORD len = MAX_SPN;
+    if (DsMakeSpn("HTTP", server.HostAsURIString().c_str(), NULL,
+                  server.port(),
+                  0, &len, spn) != ERROR_SUCCESS) {
+      LOG_F(WARNING) << "(Negotiate) - DsMakeSpn failed";
+      return HAR_IGNORE;
+    }
+#else
+    sprintfn(spn, MAX_SPN, "HTTP/%s", server.ToString().c_str());
+#endif
+
+    SecBuffer out_sec;
+    out_sec.pvBuffer   = out_buf;
+    out_sec.cbBuffer   = sizeof(out_buf);
+    out_sec.BufferType = SECBUFFER_TOKEN;
+
+    SecBufferDesc out_buf_desc;
+    out_buf_desc.ulVersion = 0;
+    out_buf_desc.cBuffers  = 1;
+    out_buf_desc.pBuffers  = &out_sec;
+
+    const ULONG NEG_FLAGS_DEFAULT =
+      //ISC_REQ_ALLOCATE_MEMORY
+      ISC_REQ_CONFIDENTIALITY
+      //| ISC_REQ_EXTENDED_ERROR
+      //| ISC_REQ_INTEGRITY
+      | ISC_REQ_REPLAY_DETECT
+      | ISC_REQ_SEQUENCE_DETECT
+      //| ISC_REQ_STREAM
+      //| ISC_REQ_USE_SUPPLIED_CREDS
+      ;
+
+    ::TimeStamp lifetime;
+    SECURITY_STATUS ret = S_OK;
+    ULONG ret_flags = 0, flags = NEG_FLAGS_DEFAULT;
+
+    bool specify_credentials = !username.empty();
+    size_t steps = 0;
+
+    //uint32 now = Time();
+
+    NegotiateAuthContext * neg = static_cast<NegotiateAuthContext *>(context);
+    if (neg) {
+      const size_t max_steps = 10;
+      if (++neg->steps >= max_steps) {
+        LOG(WARNING) << "AsyncHttpsProxySocket::Authenticate(Negotiate) too many retries";
+        return HAR_ERROR;
+      }
+      steps = neg->steps;
+
+      std::string challenge, decoded_challenge;
+      if (HttpHasNthAttribute(args, 1, &challenge, NULL)
+          && Base64::Decode(challenge, Base64::DO_STRICT,
+                            &decoded_challenge, NULL)) {
+        SecBuffer in_sec;
+        in_sec.pvBuffer   = const_cast<char *>(decoded_challenge.data());
+        in_sec.cbBuffer   = static_cast<unsigned long>(decoded_challenge.size());
+        in_sec.BufferType = SECBUFFER_TOKEN;
+
+        SecBufferDesc in_buf_desc;
+        in_buf_desc.ulVersion = 0;
+        in_buf_desc.cBuffers  = 1;
+        in_buf_desc.pBuffers  = &in_sec;
+
+        ret = InitializeSecurityContextA(&neg->cred, &neg->ctx, spn, flags, 0, SECURITY_NATIVE_DREP, &in_buf_desc, 0, &neg->ctx, &out_buf_desc, &ret_flags, &lifetime);
+        //LOG(INFO) << "$$$ InitializeSecurityContext @ " << TimeSince(now);
+        if (FAILED(ret)) {
+          LOG(LS_ERROR) << "InitializeSecurityContext returned: "
+                      << ErrorName(ret, SECURITY_ERRORS);
+          return HAR_ERROR;
+        }
+      } else if (neg->specified_credentials) {
+        // Try again with default credentials
+        specify_credentials = false;
+        delete context;
+        context = neg = 0;
+      } else {
+        return HAR_CREDENTIALS;
+      }
+    }
+
+    if (!neg) {
+      unsigned char userbuf[256], passbuf[256], domainbuf[16];
+      SEC_WINNT_AUTH_IDENTITY_A auth_id, * pauth_id = 0;
+      if (specify_credentials) {
+        memset(&auth_id, 0, sizeof(auth_id));
+        size_t len = password.GetLength()+1;
+        char * sensitive = new char[len];
+        password.CopyTo(sensitive, true);
+        std::string::size_type pos = username.find('\\');
+        if (pos == std::string::npos) {
+          auth_id.UserLength = static_cast<unsigned long>(
+            _min(sizeof(userbuf) - 1, username.size()));
+          memcpy(userbuf, username.c_str(), auth_id.UserLength);
+          userbuf[auth_id.UserLength] = 0;
+          auth_id.DomainLength = 0;
+          domainbuf[auth_id.DomainLength] = 0;
+          auth_id.PasswordLength = static_cast<unsigned long>(
+            _min(sizeof(passbuf) - 1, password.GetLength()));
+          memcpy(passbuf, sensitive, auth_id.PasswordLength);
+          passbuf[auth_id.PasswordLength] = 0;
+        } else {
+          auth_id.UserLength = static_cast<unsigned long>(
+            _min(sizeof(userbuf) - 1, username.size() - pos - 1));
+          memcpy(userbuf, username.c_str() + pos + 1, auth_id.UserLength);
+          userbuf[auth_id.UserLength] = 0;
+          auth_id.DomainLength = static_cast<unsigned long>(
+            _min(sizeof(domainbuf) - 1, pos));
+          memcpy(domainbuf, username.c_str(), auth_id.DomainLength);
+          domainbuf[auth_id.DomainLength] = 0;
+          auth_id.PasswordLength = static_cast<unsigned long>(
+            _min(sizeof(passbuf) - 1, password.GetLength()));
+          memcpy(passbuf, sensitive, auth_id.PasswordLength);
+          passbuf[auth_id.PasswordLength] = 0;
+        }
+        memset(sensitive, 0, len);
+        delete [] sensitive;
+        auth_id.User = userbuf;
+        auth_id.Domain = domainbuf;
+        auth_id.Password = passbuf;
+        auth_id.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
+        pauth_id = &auth_id;
+        LOG(LS_VERBOSE) << "Negotiate protocol: Using specified credentials";
+      } else {
+        LOG(LS_VERBOSE) << "Negotiate protocol: Using default credentials";
+      }
+
+      CredHandle cred;
+      ret = AcquireCredentialsHandleA(0, want_negotiate ? NEGOSSP_NAME_A : NTLMSP_NAME_A, SECPKG_CRED_OUTBOUND, 0, pauth_id, 0, 0, &cred, &lifetime);
+      //LOG(INFO) << "$$$ AcquireCredentialsHandle @ " << TimeSince(now);
+      if (ret != SEC_E_OK) {
+        LOG(LS_ERROR) << "AcquireCredentialsHandle error: "
+                    << ErrorName(ret, SECURITY_ERRORS);
+        return HAR_IGNORE;
+      }
+
+      //CSecBufferBundle<5, CSecBufferBase::FreeSSPI> sb_out;
+
+      CtxtHandle ctx;
+      ret = InitializeSecurityContextA(&cred, 0, spn, flags, 0, SECURITY_NATIVE_DREP, 0, 0, &ctx, &out_buf_desc, &ret_flags, &lifetime);
+      //LOG(INFO) << "$$$ InitializeSecurityContext @ " << TimeSince(now);
+      if (FAILED(ret)) {
+        LOG(LS_ERROR) << "InitializeSecurityContext returned: "
+                    << ErrorName(ret, SECURITY_ERRORS);
+        FreeCredentialsHandle(&cred);
+        return HAR_IGNORE;
+      }
+
+      ASSERT(!context);
+      context = neg = new NegotiateAuthContext(auth_method, cred, ctx);
+      neg->specified_credentials = specify_credentials;
+      neg->steps = steps;
+    }
+
+    if ((ret == SEC_I_COMPLETE_NEEDED) || (ret == SEC_I_COMPLETE_AND_CONTINUE)) {
+      ret = CompleteAuthToken(&neg->ctx, &out_buf_desc);
+      //LOG(INFO) << "$$$ CompleteAuthToken @ " << TimeSince(now);
+      LOG(LS_VERBOSE) << "CompleteAuthToken returned: "
+                      << ErrorName(ret, SECURITY_ERRORS);
+      if (FAILED(ret)) {
+        return HAR_ERROR;
+      }
+    }
+
+    //LOG(INFO) << "$$$ NEGOTIATE took " << TimeSince(now) << "ms";
+
+    std::string decoded(out_buf, out_buf + out_sec.cbBuffer);
+    response = auth_method;
+    response.append(" ");
+    response.append(Base64::Encode(decoded));
+    return HAR_RESPONSE;
+  }
+#endif
+#endif // WIN32
+
+  return HAR_IGNORE;
+}
+
+//////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/httpcommon.h b/talk/base/httpcommon.h
new file mode 100644
index 0000000..1112f8d
--- /dev/null
+++ b/talk/base/httpcommon.h
@@ -0,0 +1,463 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_HTTPCOMMON_H__
+#define TALK_BASE_HTTPCOMMON_H__
+
+#include <map>
+#include <string>
+#include <vector>
+#include "talk/base/basictypes.h"
+#include "talk/base/common.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/stream.h"
+
+namespace talk_base {
+
+class CryptString;
+class SocketAddress;
+
+//////////////////////////////////////////////////////////////////////
+// Constants
+//////////////////////////////////////////////////////////////////////
+
+enum HttpCode {
+  HC_OK = 200,
+  HC_NON_AUTHORITATIVE = 203,
+  HC_NO_CONTENT = 204,
+  HC_PARTIAL_CONTENT = 206,
+
+  HC_MULTIPLE_CHOICES = 300,
+  HC_MOVED_PERMANENTLY = 301,
+  HC_FOUND = 302,
+  HC_SEE_OTHER = 303,
+  HC_NOT_MODIFIED = 304,
+  HC_MOVED_TEMPORARILY = 307,
+
+  HC_BAD_REQUEST = 400,
+  HC_UNAUTHORIZED = 401,
+  HC_FORBIDDEN = 403,
+  HC_NOT_FOUND = 404,
+  HC_PROXY_AUTHENTICATION_REQUIRED = 407,
+  HC_GONE = 410,
+
+  HC_INTERNAL_SERVER_ERROR = 500,
+  HC_NOT_IMPLEMENTED = 501,
+  HC_SERVICE_UNAVAILABLE = 503,
+};
+
+enum HttpVersion {
+  HVER_1_0, HVER_1_1, HVER_UNKNOWN,
+  HVER_LAST = HVER_UNKNOWN
+};
+
+enum HttpVerb {
+  HV_GET, HV_POST, HV_PUT, HV_DELETE, HV_CONNECT, HV_HEAD,
+  HV_LAST = HV_HEAD
+};
+
+enum HttpError {
+  HE_NONE,
+  HE_PROTOCOL,            // Received non-valid HTTP data
+  HE_DISCONNECTED,        // Connection closed unexpectedly
+  HE_OVERFLOW,            // Received too much data for internal buffers
+  HE_CONNECT_FAILED,      // The socket failed to connect.
+  HE_SOCKET_ERROR,        // An error occurred on a connected socket
+  HE_SHUTDOWN,            // Http object is being destroyed
+  HE_OPERATION_CANCELLED, // Connection aborted locally
+  HE_AUTH,                // Proxy Authentication Required
+  HE_CERTIFICATE_EXPIRED, // During SSL negotiation
+  HE_STREAM,              // Problem reading or writing to the document
+  HE_CACHE,               // Problem reading from cache
+  HE_DEFAULT
+};
+
+enum HttpHeader {
+  HH_AGE,
+  HH_CACHE_CONTROL,
+  HH_CONNECTION,
+  HH_CONTENT_DISPOSITION,
+  HH_CONTENT_LENGTH,
+  HH_CONTENT_RANGE,
+  HH_CONTENT_TYPE,
+  HH_COOKIE,
+  HH_DATE,
+  HH_ETAG,
+  HH_EXPIRES,
+  HH_HOST,
+  HH_IF_MODIFIED_SINCE,
+  HH_IF_NONE_MATCH,
+  HH_KEEP_ALIVE,
+  HH_LAST_MODIFIED,
+  HH_LOCATION,
+  HH_PROXY_AUTHENTICATE,
+  HH_PROXY_AUTHORIZATION,
+  HH_PROXY_CONNECTION,
+  HH_RANGE,
+  HH_SET_COOKIE,
+  HH_TE,
+  HH_TRAILERS,
+  HH_TRANSFER_ENCODING,
+  HH_UPGRADE,
+  HH_USER_AGENT,
+  HH_WWW_AUTHENTICATE,
+  HH_LAST = HH_WWW_AUTHENTICATE
+};
+
+const uint16 HTTP_DEFAULT_PORT = 80;
+const uint16 HTTP_SECURE_PORT = 443;
+
+//////////////////////////////////////////////////////////////////////
+// Utility Functions
+//////////////////////////////////////////////////////////////////////
+
+inline HttpError mkerr(HttpError err, HttpError def_err = HE_DEFAULT) {
+  return (err != HE_NONE) ? err : def_err;
+}
+
+const char* ToString(HttpVersion version);
+bool FromString(HttpVersion& version, const std::string& str);
+
+const char* ToString(HttpVerb verb);
+bool FromString(HttpVerb& verb, const std::string& str);
+
+const char* ToString(HttpHeader header);
+bool FromString(HttpHeader& header, const std::string& str);
+
+inline bool HttpCodeIsInformational(uint32 code) { return ((code / 100) == 1); }
+inline bool HttpCodeIsSuccessful(uint32 code)    { return ((code / 100) == 2); }
+inline bool HttpCodeIsRedirection(uint32 code)   { return ((code / 100) == 3); }
+inline bool HttpCodeIsClientError(uint32 code)   { return ((code / 100) == 4); }
+inline bool HttpCodeIsServerError(uint32 code)   { return ((code / 100) == 5); }
+
+bool HttpCodeHasBody(uint32 code);
+bool HttpCodeIsCacheable(uint32 code);
+bool HttpHeaderIsEndToEnd(HttpHeader header);
+bool HttpHeaderIsCollapsible(HttpHeader header);
+
+struct HttpData;
+bool HttpShouldKeepAlive(const HttpData& data);
+
+typedef std::pair<std::string, std::string> HttpAttribute;
+typedef std::vector<HttpAttribute> HttpAttributeList;
+void HttpComposeAttributes(const HttpAttributeList& attributes, char separator,
+                           std::string* composed);
+void HttpParseAttributes(const char * data, size_t len,
+                         HttpAttributeList& attributes);
+bool HttpHasAttribute(const HttpAttributeList& attributes,
+                      const std::string& name,
+                      std::string* value);
+bool HttpHasNthAttribute(HttpAttributeList& attributes,
+                         size_t index,
+                         std::string* name,
+                         std::string* value);
+
+// Convert RFC1123 date (DoW, DD Mon YYYY HH:MM:SS TZ) to unix timestamp
+bool HttpDateToSeconds(const std::string& date, time_t* seconds);
+
+inline uint16 HttpDefaultPort(bool secure) {
+  return secure ? HTTP_SECURE_PORT : HTTP_DEFAULT_PORT;
+}
+
+// Returns the http server notation for a given address
+std::string HttpAddress(const SocketAddress& address, bool secure);
+
+// functional for insensitive std::string compare
+struct iless {
+  bool operator()(const std::string& lhs, const std::string& rhs) const {
+    return (::_stricmp(lhs.c_str(), rhs.c_str()) < 0);
+  }
+};
+
+// put quotes around a string and escape any quotes inside it
+std::string quote(const std::string& str);
+
+//////////////////////////////////////////////////////////////////////
+// Url
+//////////////////////////////////////////////////////////////////////
+
+template<class CTYPE>
+class Url {
+public:
+  typedef typename Traits<CTYPE>::string string;
+
+  // TODO: Implement Encode/Decode
+  static int Encode(const CTYPE* source, CTYPE* destination, size_t len);
+  static int Encode(const string& source, string& destination);
+  static int Decode(const CTYPE* source, CTYPE* destination, size_t len);
+  static int Decode(const string& source, string& destination);
+
+  Url(const string& url) { do_set_url(url.c_str(), url.size()); }
+  Url(const string& path, const string& host, uint16 port = HTTP_DEFAULT_PORT)
+  : host_(host), port_(port), secure_(HTTP_SECURE_PORT == port)
+  { set_full_path(path); }
+
+  bool valid() const { return !host_.empty(); }
+  void clear() {
+    host_.clear();
+    port_ = HTTP_DEFAULT_PORT;
+    secure_ = false;
+    path_.assign(1, static_cast<CTYPE>('/'));
+    query_.clear();
+  }
+
+  void set_url(const string& val) {
+    do_set_url(val.c_str(), val.size());
+  }
+  string url() const {
+    string val; do_get_url(&val); return val;
+  }
+
+  void set_address(const string& val) {
+    do_set_address(val.c_str(), val.size());
+  }
+  string address() const {
+    string val; do_get_address(&val); return val;
+  }
+
+  void set_full_path(const string& val) {
+    do_set_full_path(val.c_str(), val.size());
+  }
+  string full_path() const {
+    string val; do_get_full_path(&val); return val;
+  }
+
+  void set_host(const string& val) { host_ = val; }
+  const string& host() const { return host_; }
+
+  void set_port(uint16 val) { port_ = val; }
+  uint16 port() const { return port_; }
+
+  void set_secure(bool val) { secure_ = val; }
+  bool secure() const { return secure_; }
+
+  void set_path(const string& val) {
+    if (val.empty()) {
+      path_.assign(1, static_cast<CTYPE>('/'));
+    } else {
+      ASSERT(val[0] == static_cast<CTYPE>('/'));
+      path_ = val;
+    }
+  }
+  const string& path() const { return path_; }
+
+  void set_query(const string& val) {
+    ASSERT(val.empty() || (val[0] == static_cast<CTYPE>('?')));
+    query_ = val;
+  }
+  const string& query() const { return query_; }
+
+  bool get_attribute(const string& name, string* value) const;
+
+private:
+  void do_set_url(const CTYPE* val, size_t len);
+  void do_set_address(const CTYPE* val, size_t len);
+  void do_set_full_path(const CTYPE* val, size_t len);
+
+  void do_get_url(string* val) const;
+  void do_get_address(string* val) const;
+  void do_get_full_path(string* val) const;
+
+  string host_, path_, query_;
+  uint16 port_;
+  bool secure_;
+};
+
+//////////////////////////////////////////////////////////////////////
+// HttpData
+//////////////////////////////////////////////////////////////////////
+
+struct HttpData {
+  typedef std::multimap<std::string, std::string, iless> HeaderMap;
+  typedef HeaderMap::const_iterator const_iterator;
+  typedef HeaderMap::iterator iterator;
+
+  HttpVersion version;
+  scoped_ptr<StreamInterface> document;
+
+  HttpData() : version(HVER_1_1) { }
+
+  enum HeaderCombine { HC_YES, HC_NO, HC_AUTO, HC_REPLACE, HC_NEW };
+  void changeHeader(const std::string& name, const std::string& value,
+                    HeaderCombine combine);
+  inline void addHeader(const std::string& name, const std::string& value,
+                        bool append = true) {
+    changeHeader(name, value, append ? HC_AUTO : HC_NO);
+  }
+  inline void setHeader(const std::string& name, const std::string& value,
+                        bool overwrite = true) {
+    changeHeader(name, value, overwrite ? HC_REPLACE : HC_NEW);
+  }
+  // Returns count of erased headers
+  size_t clearHeader(const std::string& name);
+  // Returns iterator to next header
+  iterator clearHeader(iterator header);
+
+  // keep in mind, this may not do what you want in the face of multiple headers
+  bool hasHeader(const std::string& name, std::string* value) const;
+
+  inline const_iterator begin() const {
+    return headers_.begin();
+  }
+  inline const_iterator end() const {
+    return headers_.end();
+  }
+  inline iterator begin() {
+    return headers_.begin();
+  }
+  inline iterator end() {
+    return headers_.end();
+  }
+  inline const_iterator begin(const std::string& name) const {
+    return headers_.lower_bound(name);
+  }
+  inline const_iterator end(const std::string& name) const {
+    return headers_.upper_bound(name);
+  }
+  inline iterator begin(const std::string& name) {
+    return headers_.lower_bound(name);
+  }
+  inline iterator end(const std::string& name) {
+    return headers_.upper_bound(name);
+  }
+
+  // Convenience methods using HttpHeader
+  inline void changeHeader(HttpHeader header, const std::string& value,
+                           HeaderCombine combine) {
+    changeHeader(ToString(header), value, combine);
+  }
+  inline void addHeader(HttpHeader header, const std::string& value,
+                        bool append = true) {
+    addHeader(ToString(header), value, append);
+  }
+  inline void setHeader(HttpHeader header, const std::string& value,
+                        bool overwrite = true) {
+    setHeader(ToString(header), value, overwrite);
+  }
+  inline void clearHeader(HttpHeader header) {
+    clearHeader(ToString(header));
+  }
+  inline bool hasHeader(HttpHeader header, std::string* value) const {
+    return hasHeader(ToString(header), value);
+  }
+  inline const_iterator begin(HttpHeader header) const {
+    return headers_.lower_bound(ToString(header));
+  }
+  inline const_iterator end(HttpHeader header) const {
+    return headers_.upper_bound(ToString(header));
+  }
+  inline iterator begin(HttpHeader header) {
+    return headers_.lower_bound(ToString(header));
+  }
+  inline iterator end(HttpHeader header) {
+    return headers_.upper_bound(ToString(header));
+  }
+
+  void setContent(const std::string& content_type, StreamInterface* document);
+  void setDocumentAndLength(StreamInterface* document);
+
+  virtual size_t formatLeader(char* buffer, size_t size) const = 0;
+  virtual HttpError parseLeader(const char* line, size_t len) = 0;
+
+protected:
+  virtual ~HttpData() { }
+  void clear(bool release_document);
+  void copy(const HttpData& src);
+
+private:
+  HeaderMap headers_;
+};
+
+struct HttpRequestData : public HttpData {
+  HttpVerb verb;
+  std::string path;
+
+  HttpRequestData() : verb(HV_GET) { }
+
+  void clear(bool release_document);
+  void copy(const HttpRequestData& src);
+
+  virtual size_t formatLeader(char* buffer, size_t size) const;
+  virtual HttpError parseLeader(const char* line, size_t len);
+
+  bool getAbsoluteUri(std::string* uri) const;
+  bool getRelativeUri(std::string* host, std::string* path) const;
+};
+
+struct HttpResponseData : public HttpData {
+  uint32 scode;
+  std::string message;
+
+  HttpResponseData() : scode(HC_INTERNAL_SERVER_ERROR) { }
+  void clear(bool release_document);
+  void copy(const HttpResponseData& src);
+
+  // Convenience methods
+  void set_success(uint32 scode = HC_OK);
+  void set_success(const std::string& content_type, StreamInterface* document,
+                   uint32 scode = HC_OK);
+  void set_redirect(const std::string& location,
+                    uint32 scode = HC_MOVED_TEMPORARILY);
+  void set_error(uint32 scode);
+
+  virtual size_t formatLeader(char* buffer, size_t size) const;
+  virtual HttpError parseLeader(const char* line, size_t len);
+};
+
+struct HttpTransaction {
+  HttpRequestData request;
+  HttpResponseData response;
+};
+
+//////////////////////////////////////////////////////////////////////
+// Http Authentication
+//////////////////////////////////////////////////////////////////////
+
+struct HttpAuthContext {
+  std::string auth_method;
+  HttpAuthContext(const std::string& auth) : auth_method(auth) { }
+  virtual ~HttpAuthContext() { }
+};
+
+enum HttpAuthResult { HAR_RESPONSE, HAR_IGNORE, HAR_CREDENTIALS, HAR_ERROR };
+
+// 'context' is used by this function to record information between calls.
+// Start by passing a null pointer, then pass the same pointer each additional
+// call.  When the authentication attempt is finished, delete the context.
+HttpAuthResult HttpAuthenticate(
+  const char * challenge, size_t len,
+  const SocketAddress& server,
+  const std::string& method, const std::string& uri,
+  const std::string& username, const CryptString& password,
+  HttpAuthContext *& context, std::string& response, std::string& auth_method);
+
+//////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_HTTPCOMMON_H__
diff --git a/talk/base/httpcommon_unittest.cc b/talk/base/httpcommon_unittest.cc
new file mode 100644
index 0000000..b60f597
--- /dev/null
+++ b/talk/base/httpcommon_unittest.cc
@@ -0,0 +1,182 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/httpcommon-inl.h"
+#include "talk/base/httpcommon.h"
+
+namespace talk_base {
+
+#define TEST_PROTOCOL "http://"
+#define TEST_HOST "www.google.com"
+#define TEST_PATH "/folder/file.html"
+#define TEST_QUERY "?query=x&attr=y"
+#define TEST_URL TEST_PROTOCOL TEST_HOST TEST_PATH TEST_QUERY
+
+TEST(Url, DecomposesUrls) {
+  Url<char> url(TEST_URL);
+  EXPECT_TRUE(url.valid());
+  EXPECT_FALSE(url.secure());
+  EXPECT_STREQ(TEST_HOST, url.host().c_str());
+  EXPECT_EQ(80, url.port());
+  EXPECT_STREQ(TEST_PATH, url.path().c_str());
+  EXPECT_STREQ(TEST_QUERY, url.query().c_str());
+  EXPECT_STREQ(TEST_HOST, url.address().c_str());
+  EXPECT_STREQ(TEST_PATH TEST_QUERY, url.full_path().c_str());
+  EXPECT_STREQ(TEST_URL, url.url().c_str());
+}
+
+TEST(Url, ComposesUrls) {
+  // Set in constructor
+  Url<char> url(TEST_PATH TEST_QUERY, TEST_HOST, 80);
+  EXPECT_TRUE(url.valid());
+  EXPECT_FALSE(url.secure());
+  EXPECT_STREQ(TEST_HOST, url.host().c_str());
+  EXPECT_EQ(80, url.port());
+  EXPECT_STREQ(TEST_PATH, url.path().c_str());
+  EXPECT_STREQ(TEST_QUERY, url.query().c_str());
+  EXPECT_STREQ(TEST_HOST, url.address().c_str());
+  EXPECT_STREQ(TEST_PATH TEST_QUERY, url.full_path().c_str());
+  EXPECT_STREQ(TEST_URL, url.url().c_str());
+
+  url.clear();
+  EXPECT_FALSE(url.valid());
+  EXPECT_FALSE(url.secure());
+  EXPECT_STREQ("", url.host().c_str());
+  EXPECT_EQ(80, url.port());
+  EXPECT_STREQ("/", url.path().c_str());
+  EXPECT_STREQ("", url.query().c_str());
+
+  // Set component-wise
+  url.set_host(TEST_HOST);
+  url.set_port(80);
+  url.set_path(TEST_PATH);
+  url.set_query(TEST_QUERY);
+  EXPECT_TRUE(url.valid());
+  EXPECT_FALSE(url.secure());
+  EXPECT_STREQ(TEST_HOST, url.host().c_str());
+  EXPECT_EQ(80, url.port());
+  EXPECT_STREQ(TEST_PATH, url.path().c_str());
+  EXPECT_STREQ(TEST_QUERY, url.query().c_str());
+  EXPECT_STREQ(TEST_HOST, url.address().c_str());
+  EXPECT_STREQ(TEST_PATH TEST_QUERY, url.full_path().c_str());
+  EXPECT_STREQ(TEST_URL, url.url().c_str());
+}
+
+TEST(Url, EnsuresNonEmptyPath) {
+  Url<char> url(TEST_PROTOCOL TEST_HOST);
+  EXPECT_TRUE(url.valid());
+  EXPECT_STREQ("/", url.path().c_str());
+
+  url.clear();
+  EXPECT_STREQ("/", url.path().c_str());
+  url.set_path("");
+  EXPECT_STREQ("/", url.path().c_str());
+
+  url.clear();
+  EXPECT_STREQ("/", url.path().c_str());
+  url.set_full_path("");
+  EXPECT_STREQ("/", url.path().c_str());
+}
+
+TEST(Url, GetQueryAttributes) {
+  Url<char> url(TEST_URL);
+  std::string value;
+  EXPECT_TRUE(url.get_attribute("query", &value));
+  EXPECT_STREQ("x", value.c_str());
+  value.clear();
+  EXPECT_TRUE(url.get_attribute("attr", &value));
+  EXPECT_STREQ("y", value.c_str());
+  value.clear();
+  EXPECT_FALSE(url.get_attribute("Query", &value));
+  EXPECT_TRUE(value.empty());
+}
+
+TEST(Url, SkipsUserAndPassword) {
+  Url<char> url("https://mail.google.com:pwd@badsite.com:12345/asdf");
+  EXPECT_TRUE(url.valid());
+  EXPECT_TRUE(url.secure());
+  EXPECT_STREQ("badsite.com", url.host().c_str());
+  EXPECT_EQ(12345, url.port());
+  EXPECT_STREQ("/asdf", url.path().c_str());
+  EXPECT_STREQ("badsite.com:12345", url.address().c_str());
+}
+
+TEST(Url, SkipsUser) {
+  Url<char> url("https://mail.google.com@badsite.com:12345/asdf");
+  EXPECT_TRUE(url.valid());
+  EXPECT_TRUE(url.secure());
+  EXPECT_STREQ("badsite.com", url.host().c_str());
+  EXPECT_EQ(12345, url.port());
+  EXPECT_STREQ("/asdf", url.path().c_str());
+  EXPECT_STREQ("badsite.com:12345", url.address().c_str());
+}
+
+TEST(HttpResponseData, parseLeaderHttp1_0) {
+  static const char kResponseString[] = "HTTP/1.0 200 OK";
+  HttpResponseData response;
+  EXPECT_EQ(HE_NONE, response.parseLeader(kResponseString,
+                                          sizeof(kResponseString) - 1));
+  EXPECT_EQ(HVER_1_0, response.version);
+  EXPECT_EQ(200U, response.scode);
+}
+
+TEST(HttpResponseData, parseLeaderHttp1_1) {
+  static const char kResponseString[] = "HTTP/1.1 200 OK";
+  HttpResponseData response;
+  EXPECT_EQ(HE_NONE, response.parseLeader(kResponseString,
+                                          sizeof(kResponseString) - 1));
+  EXPECT_EQ(HVER_1_1, response.version);
+  EXPECT_EQ(200U, response.scode);
+}
+
+TEST(HttpResponseData, parseLeaderHttpUnknown) {
+  static const char kResponseString[] = "HTTP 200 OK";
+  HttpResponseData response;
+  EXPECT_EQ(HE_NONE, response.parseLeader(kResponseString,
+                                          sizeof(kResponseString) - 1));
+  EXPECT_EQ(HVER_UNKNOWN, response.version);
+  EXPECT_EQ(200U, response.scode);
+}
+
+TEST(HttpResponseData, parseLeaderHttpFailure) {
+  static const char kResponseString[] = "HTTP/1.1 503 Service Unavailable";
+  HttpResponseData response;
+  EXPECT_EQ(HE_NONE, response.parseLeader(kResponseString,
+                                          sizeof(kResponseString) - 1));
+  EXPECT_EQ(HVER_1_1, response.version);
+  EXPECT_EQ(503U, response.scode);
+}
+
+TEST(HttpResponseData, parseLeaderHttpInvalid) {
+  static const char kResponseString[] = "Durrrrr, what's HTTP?";
+  HttpResponseData response;
+  EXPECT_EQ(HE_PROTOCOL, response.parseLeader(kResponseString,
+                                              sizeof(kResponseString) - 1));
+}
+
+} // namespace talk_base
diff --git a/talk/base/httprequest.cc b/talk/base/httprequest.cc
new file mode 100644
index 0000000..48c924e
--- /dev/null
+++ b/talk/base/httprequest.cc
@@ -0,0 +1,127 @@
+/*
+ * libjingle
+ * Copyright 2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/httprequest.h"
+
+#include "talk/base/common.h"
+#include "talk/base/firewallsocketserver.h"
+#include "talk/base/httpclient.h"
+#include "talk/base/logging.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/socketadapters.h"
+#include "talk/base/socketpool.h"
+#include "talk/base/ssladapter.h"
+
+using namespace talk_base;
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpMonitor
+///////////////////////////////////////////////////////////////////////////////
+
+HttpMonitor::HttpMonitor(SocketServer *ss) {
+  ASSERT(Thread::Current() != NULL);
+  ss_ = ss;
+  reset();
+}
+
+void HttpMonitor::Connect(HttpClient *http) {
+  http->SignalHttpClientComplete.connect(this,
+    &HttpMonitor::OnHttpClientComplete);
+}
+
+void HttpMonitor::OnHttpClientComplete(HttpClient * http, HttpErrorType error) {
+  complete_ = true;
+  error_ = error;
+  ss_->WakeUp();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpRequest
+///////////////////////////////////////////////////////////////////////////////
+
+const int kDefaultHTTPTimeout = 30 * 1000; // 30 sec
+
+HttpRequest::HttpRequest(const std::string &user_agent)
+    : firewall_(0), port_(80), secure_(false),
+      timeout_(kDefaultHTTPTimeout), fail_redirect_(false),
+      client_(user_agent.c_str(), NULL), error_(HE_NONE) {
+}
+
+void HttpRequest::Send() {
+  // TODO: Rewrite this to use the thread's native socket server, and a more
+  // natural flow?
+
+  PhysicalSocketServer physical;
+  SocketServer * ss = &physical;
+  if (firewall_) {
+    ss = new FirewallSocketServer(ss, firewall_);
+  }
+
+  SslSocketFactory factory(ss, client_.agent());
+  factory.SetProxy(proxy_);
+  if (secure_)
+    factory.UseSSL(host_.c_str());
+
+  //factory.SetLogging("HttpRequest");
+
+  ReuseSocketPool pool(&factory);
+  client_.set_pool(&pool);
+
+  bool transparent_proxy = (port_ == 80) && ((proxy_.type == PROXY_HTTPS) ||
+                           (proxy_.type == PROXY_UNKNOWN));
+
+  if (transparent_proxy) {
+    client_.set_proxy(proxy_);
+  }
+  client_.set_fail_redirect(fail_redirect_);
+
+  SocketAddress server(host_, port_);
+  client_.set_server(server);
+
+  LOG(LS_INFO) << "HttpRequest start: " << host_ + client_.request().path;
+
+  HttpMonitor monitor(ss);
+  monitor.Connect(&client_);
+  client_.start();
+  ss->Wait(timeout_, true);
+  if (!monitor.done()) {
+    LOG(LS_INFO) << "HttpRequest request timed out";
+    client_.reset();
+    return;
+  }
+
+  set_error(monitor.error());
+  if (error_) {
+    LOG(LS_INFO) << "HttpRequest request error: " << error_;
+    return;
+  }
+
+  std::string value;
+  if (client_.response().hasHeader(HH_LOCATION, &value)) {
+    response_redirect_ = value.c_str();
+  }
+}
diff --git a/talk/base/httprequest.h b/talk/base/httprequest.h
new file mode 100644
index 0000000..2e13c32
--- /dev/null
+++ b/talk/base/httprequest.h
@@ -0,0 +1,132 @@
+/*
+ * libjingle
+ * Copyright 2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _HTTPREQUEST_H_
+#define _HTTPREQUEST_H_
+
+#include "talk/base/httpclient.h"
+#include "talk/base/logging.h"
+#include "talk/base/proxyinfo.h"
+#include "talk/base/socketserver.h"
+#include "talk/base/thread.h"
+#include "talk/base/sslsocketfactory.h"  // Deprecated include
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpRequest
+///////////////////////////////////////////////////////////////////////////////
+
+class FirewallManager;
+class MemoryStream;
+
+class HttpRequest {
+public:
+  HttpRequest(const std::string &user_agent);
+
+  void Send();
+
+  void set_proxy(const ProxyInfo& proxy) {
+    proxy_ = proxy;
+  }
+  void set_firewall(FirewallManager * firewall) {
+    firewall_ = firewall;
+  }
+
+  // The DNS name of the host to connect to.
+  const std::string& host() { return host_; }
+  void set_host(const std::string& host) { host_ = host; }
+
+  // The port to connect to on the target host.
+  int port() { return port_; }
+  void set_port(int port) { port_ = port; }
+
+   // Whether the request should use SSL.
+  bool secure() { return secure_; }
+  void set_secure(bool secure) { secure_ = secure; }
+
+  // Returns the redirect when redirection occurs
+  const std::string& response_redirect() { return response_redirect_; }
+
+  // Time to wait on the download, in ms.  Default is 5000 (5s)
+  int timeout() { return timeout_; }
+  void set_timeout(int timeout) { timeout_ = timeout; }
+
+  // Fail redirects to allow analysis of redirect urls, etc.
+  bool fail_redirect() const { return fail_redirect_; }
+  void set_fail_redirect(bool fail_redirect) { fail_redirect_ = fail_redirect; }
+
+  HttpRequestData& request() { return client_.request(); }
+  HttpResponseData& response() { return client_.response(); }
+  HttpErrorType error() { return error_; }
+
+protected:
+  void set_error(HttpErrorType error) { error_ = error; }
+
+private:
+  ProxyInfo proxy_;
+  FirewallManager * firewall_;
+  std::string host_;
+  int port_;
+  bool secure_;
+  int timeout_;
+  bool fail_redirect_;
+  HttpClient client_;
+  HttpErrorType error_;
+  std::string response_redirect_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpMonitor
+///////////////////////////////////////////////////////////////////////////////
+
+class HttpMonitor : public sigslot::has_slots<> {
+public:
+  HttpMonitor(SocketServer *ss);
+
+  void reset() {
+    complete_ = false;
+    error_ = HE_DEFAULT;
+  }
+
+  bool done() const { return complete_; }
+  HttpErrorType error() const { return error_; }
+
+  void Connect(HttpClient* http);
+  void OnHttpClientComplete(HttpClient * http, HttpErrorType error);
+
+private:
+  bool complete_;
+  HttpErrorType error_;
+  SocketServer *ss_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base_
+
+#endif  // _HTTPREQUEST_H_
diff --git a/talk/base/httpserver.cc b/talk/base/httpserver.cc
new file mode 100644
index 0000000..7d467c3
--- /dev/null
+++ b/talk/base/httpserver.cc
@@ -0,0 +1,305 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <algorithm>
+
+#include "talk/base/httpcommon-inl.h"
+
+#include "talk/base/asyncsocket.h"
+#include "talk/base/common.h"
+#include "talk/base/httpserver.h"
+#include "talk/base/logging.h"
+#include "talk/base/socketstream.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpServer
+///////////////////////////////////////////////////////////////////////////////
+
+HttpServer::HttpServer() : next_connection_id_(1), closing_(false) {
+}
+
+HttpServer::~HttpServer() {
+  if (closing_) {
+    LOG(LS_WARNING) << "HttpServer::CloseAll has not completed";
+  }
+  for (ConnectionMap::iterator it = connections_.begin();
+       it != connections_.end();
+       ++it) {
+    StreamInterface* stream = it->second->EndProcess();
+    delete stream;
+    delete it->second;
+  }
+}
+
+int
+HttpServer::HandleConnection(StreamInterface* stream) {
+  int connection_id = next_connection_id_++;
+  ASSERT(connection_id != HTTP_INVALID_CONNECTION_ID);
+  Connection* connection = new Connection(connection_id, this);
+  connections_.insert(ConnectionMap::value_type(connection_id, connection));
+  connection->BeginProcess(stream);
+  return connection_id;
+}
+
+void
+HttpServer::Respond(HttpServerTransaction* transaction) {
+  int connection_id = transaction->connection_id();
+  if (Connection* connection = Find(connection_id)) {
+    connection->Respond(transaction);
+  } else {
+    delete transaction;
+    // We may be tempted to SignalHttpComplete, but that implies that a
+    // connection still exists.
+  }
+}
+
+void
+HttpServer::Close(int connection_id, bool force) {
+  if (Connection* connection = Find(connection_id)) {
+    connection->InitiateClose(force);
+  }
+}
+
+void
+HttpServer::CloseAll(bool force) {
+  if (connections_.empty()) {
+    SignalCloseAllComplete(this);
+    return;
+  }
+  closing_ = true;
+  std::list<Connection*> connections;
+  for (ConnectionMap::const_iterator it = connections_.begin();
+       it != connections_.end(); ++it) {
+    connections.push_back(it->second);
+  }
+  for (std::list<Connection*>::const_iterator it = connections.begin();
+      it != connections.end(); ++it) {
+    (*it)->InitiateClose(force);
+  }
+}
+
+HttpServer::Connection*
+HttpServer::Find(int connection_id) {
+  ConnectionMap::iterator it = connections_.find(connection_id);
+  if (it == connections_.end())
+    return NULL;
+  return it->second;
+}
+
+void
+HttpServer::Remove(int connection_id) {
+  ConnectionMap::iterator it = connections_.find(connection_id);
+  if (it == connections_.end()) {
+    ASSERT(false);
+    return;
+  }
+  Connection* connection = it->second;
+  connections_.erase(it);
+  SignalConnectionClosed(this, connection_id, connection->EndProcess());
+  delete connection;
+  if (closing_ && connections_.empty()) {
+    closing_ = false;
+    SignalCloseAllComplete(this);
+  }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpServer::Connection
+///////////////////////////////////////////////////////////////////////////////
+
+HttpServer::Connection::Connection(int connection_id, HttpServer* server)
+  : connection_id_(connection_id), server_(server),
+    current_(NULL), signalling_(false), close_(false) {
+}
+
+HttpServer::Connection::~Connection() {
+  // It's possible that an object hosted inside this transaction signalled
+  // an event which caused the connection to close.
+  Thread::Current()->Dispose(current_);
+}
+
+void
+HttpServer::Connection::BeginProcess(StreamInterface* stream) {
+  base_.notify(this);
+  base_.attach(stream);
+  current_ = new HttpServerTransaction(connection_id_);
+  if (base_.mode() != HM_CONNECT)
+    base_.recv(&current_->request);
+}
+
+StreamInterface*
+HttpServer::Connection::EndProcess() {
+  base_.notify(NULL);
+  base_.abort(HE_DISCONNECTED);
+  return base_.detach();
+}
+
+void
+HttpServer::Connection::Respond(HttpServerTransaction* transaction) {
+  ASSERT(current_ == NULL);
+  current_ = transaction;
+  if (current_->response.begin() == current_->response.end()) {
+    current_->response.set_error(HC_INTERNAL_SERVER_ERROR);
+  }
+  bool keep_alive = HttpShouldKeepAlive(current_->request);
+  current_->response.setHeader(HH_CONNECTION,
+                               keep_alive ? "Keep-Alive" : "Close",
+                               false);
+  close_ = !HttpShouldKeepAlive(current_->response);
+  base_.send(&current_->response);
+}
+
+void
+HttpServer::Connection::InitiateClose(bool force) {
+  bool request_in_progress = (HM_SEND == base_.mode()) || (NULL == current_);
+  if (!signalling_ && (force || !request_in_progress)) {
+    server_->Remove(connection_id_);
+  } else {
+    close_ = true;
+  }
+}
+
+//
+// IHttpNotify Implementation
+//
+
+HttpError
+HttpServer::Connection::onHttpHeaderComplete(bool chunked, size_t& data_size) {
+  if (data_size == SIZE_UNKNOWN) {
+    data_size = 0;
+  }
+  ASSERT(current_ != NULL);
+  bool custom_document = false;
+  server_->SignalHttpRequestHeader(server_, current_, &custom_document);
+  if (!custom_document) {
+    current_->request.document.reset(new MemoryStream);
+  }
+  return HE_NONE;
+}
+
+void
+HttpServer::Connection::onHttpComplete(HttpMode mode, HttpError err) {
+  if (mode == HM_SEND) {
+    ASSERT(current_ != NULL);
+    signalling_ = true;
+    server_->SignalHttpRequestComplete(server_, current_, err);
+    signalling_ = false;
+    if (close_) {
+      // Force a close
+      err = HE_DISCONNECTED;
+    }
+  }
+  if (err != HE_NONE) {
+    server_->Remove(connection_id_);
+  } else if (mode == HM_CONNECT) {
+    base_.recv(&current_->request);
+  } else if (mode == HM_RECV) {
+    ASSERT(current_ != NULL);
+    // TODO: do we need this?
+    //request_.document_->rewind();
+    HttpServerTransaction* transaction = current_;
+    current_ = NULL;
+    server_->SignalHttpRequest(server_, transaction);
+  } else if (mode == HM_SEND) {
+    Thread::Current()->Dispose(current_->response.document.release());
+    current_->request.clear(true);
+    current_->response.clear(true);
+    base_.recv(&current_->request);
+  } else {
+    ASSERT(false);
+  }
+}
+
+void
+HttpServer::Connection::onHttpClosed(HttpError err) {
+  UNUSED(err);
+  server_->Remove(connection_id_);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpListenServer
+///////////////////////////////////////////////////////////////////////////////
+
+HttpListenServer::HttpListenServer() {
+  SignalConnectionClosed.connect(this, &HttpListenServer::OnConnectionClosed);
+}
+
+HttpListenServer::~HttpListenServer() {
+}
+
+int HttpListenServer::Listen(const SocketAddress& address) {
+  AsyncSocket* sock =
+      Thread::Current()->socketserver()->CreateAsyncSocket(address.family(),
+                                                           SOCK_STREAM);
+  if (!sock) {
+    return SOCKET_ERROR;
+  }
+  listener_.reset(sock);
+  listener_->SignalReadEvent.connect(this, &HttpListenServer::OnReadEvent);
+  if ((listener_->Bind(address) != SOCKET_ERROR) &&
+      (listener_->Listen(5) != SOCKET_ERROR))
+    return 0;
+  return listener_->GetError();
+}
+
+bool HttpListenServer::GetAddress(SocketAddress* address) const {
+  if (!listener_) {
+    return false;
+  }
+  *address = listener_->GetLocalAddress();
+  return !address->IsNil();
+}
+
+void HttpListenServer::StopListening() {
+  if (listener_) {
+    listener_->Close();
+  }
+}
+
+void HttpListenServer::OnReadEvent(AsyncSocket* socket) {
+  ASSERT(socket == listener_.get());
+  ASSERT(listener_);
+  AsyncSocket* incoming = listener_->Accept(NULL);
+  if (incoming) {
+    StreamInterface* stream = new SocketStream(incoming);
+    //stream = new LoggingAdapter(stream, LS_VERBOSE, "HttpServer", false);
+    HandleConnection(stream);
+  }
+}
+
+void HttpListenServer::OnConnectionClosed(HttpServer* server,
+                                          int connection_id,
+                                          StreamInterface* stream) {
+  Thread::Current()->Dispose(stream);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
diff --git a/talk/base/httpserver.h b/talk/base/httpserver.h
new file mode 100644
index 0000000..67061ee
--- /dev/null
+++ b/talk/base/httpserver.h
@@ -0,0 +1,154 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_HTTPSERVER_H__
+#define TALK_BASE_HTTPSERVER_H__
+
+#include <map>
+#include "talk/base/httpbase.h"
+
+namespace talk_base {
+
+class AsyncSocket;
+class HttpServer;
+class SocketAddress;
+
+//////////////////////////////////////////////////////////////////////
+// HttpServer
+//////////////////////////////////////////////////////////////////////
+
+const int HTTP_INVALID_CONNECTION_ID = 0;
+
+struct HttpServerTransaction : public HttpTransaction {
+public:
+  HttpServerTransaction(int id) : connection_id_(id) { }
+  int connection_id() const { return connection_id_; }
+
+private:
+  int connection_id_;
+};
+
+class HttpServer {
+public:
+  HttpServer();
+  virtual ~HttpServer();
+
+  int HandleConnection(StreamInterface* stream);
+  // Due to sigslot issues, we can't destroy some streams at an arbitrary time.
+  sigslot::signal3<HttpServer*, int, StreamInterface*> SignalConnectionClosed;
+
+  // This signal occurs when the HTTP request headers have been received, but
+  // before the request body is written to the request document.  By default,
+  // the request document is a MemoryStream.  By handling this signal, the
+  // document can be overridden, in which case the third signal argument should
+  // be set to true.  In the case where the request body should be ignored,
+  // the document can be set to NULL.  Note that the transaction object is still
+  // owened by the HttpServer at this point.  
+  sigslot::signal3<HttpServer*, HttpServerTransaction*, bool*>
+    SignalHttpRequestHeader;
+
+  // An HTTP request has been made, and is available in the transaction object.
+  // Populate the transaction's response, and then return the object via the
+  // Respond method.  Note that during this time, ownership of the transaction
+  // object is transferred, so it may be passed between threads, although
+  // respond must be called on the server's active thread.
+  sigslot::signal2<HttpServer*, HttpServerTransaction*> SignalHttpRequest;
+  void Respond(HttpServerTransaction* transaction);
+
+  // If you want to know when a request completes, listen to this event.
+  sigslot::signal3<HttpServer*, HttpServerTransaction*, int>
+    SignalHttpRequestComplete;
+
+  // Stop processing the connection indicated by connection_id.
+  // Unless force is true, the server will complete sending a response that is
+  // in progress.
+  void Close(int connection_id, bool force);
+  void CloseAll(bool force);
+
+  // After calling CloseAll, this event is signalled to indicate that all
+  // outstanding connections have closed.
+  sigslot::signal1<HttpServer*> SignalCloseAllComplete;
+
+private:
+  class Connection : private IHttpNotify {
+  public:
+    Connection(int connection_id, HttpServer* server);
+    virtual ~Connection();
+
+    void BeginProcess(StreamInterface* stream);
+    StreamInterface* EndProcess();
+    
+    void Respond(HttpServerTransaction* transaction);
+    void InitiateClose(bool force);
+
+    // IHttpNotify Interface
+    virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size);
+    virtual void onHttpComplete(HttpMode mode, HttpError err);
+    virtual void onHttpClosed(HttpError err);
+  
+    int connection_id_;
+    HttpServer* server_;
+    HttpBase base_;
+    HttpServerTransaction* current_;
+    bool signalling_, close_;
+  };
+
+  Connection* Find(int connection_id);
+  void Remove(int connection_id);
+
+  friend class Connection;
+  typedef std::map<int,Connection*> ConnectionMap;
+
+  ConnectionMap connections_;
+  int next_connection_id_;
+  bool closing_;
+};
+
+//////////////////////////////////////////////////////////////////////
+
+class HttpListenServer : public HttpServer, public sigslot::has_slots<> {
+public:
+  HttpListenServer();
+  virtual ~HttpListenServer();
+
+  int Listen(const SocketAddress& address);
+  bool GetAddress(SocketAddress* address) const;
+  void StopListening();
+
+private:
+  void OnReadEvent(AsyncSocket* socket);
+  void OnConnectionClosed(HttpServer* server, int connection_id,
+                          StreamInterface* stream);
+
+  scoped_ptr<AsyncSocket> listener_;
+};
+
+//////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif // TALK_BASE_HTTPSERVER_H__
diff --git a/talk/base/httpserver_unittest.cc b/talk/base/httpserver_unittest.cc
new file mode 100644
index 0000000..d0e0760
--- /dev/null
+++ b/talk/base/httpserver_unittest.cc
@@ -0,0 +1,130 @@
+// Copyright 2007 Google Inc.
+// All Rights Reserved.
+
+
+#include "talk/base/gunit.h"
+#include "talk/base/httpserver.h"
+#include "talk/base/testutils.h"
+
+using namespace testing;
+
+namespace talk_base {
+
+namespace {
+  const char* const kRequest =
+    "GET /index.html HTTP/1.1\r\n"
+    "Host: localhost\r\n"
+    "\r\n";
+
+  const char* const kResponse =
+    "HTTP/1.1 200\r\n"
+    "Connection: Close\r\n"
+    "Content-Length: 0\r\n"
+    "\r\n";
+
+  struct HttpServerMonitor : public sigslot::has_slots<> {
+    HttpServerTransaction* transaction;
+    bool server_closed, connection_closed;
+
+    HttpServerMonitor(HttpServer* server)
+    : transaction(NULL), server_closed(false), connection_closed(false) {
+      server->SignalCloseAllComplete.connect(this,
+        &HttpServerMonitor::OnClosed);
+      server->SignalHttpRequest.connect(this, &HttpServerMonitor::OnRequest);
+      server->SignalHttpRequestComplete.connect(this,
+        &HttpServerMonitor::OnRequestComplete);
+      server->SignalConnectionClosed.connect(this,
+        &HttpServerMonitor::OnConnectionClosed);
+    }
+    void OnRequest(HttpServer*, HttpServerTransaction* t) {
+      ASSERT_FALSE(transaction);
+      transaction = t;
+      transaction->response.set_success();
+      transaction->response.setHeader(HH_CONNECTION, "Close");
+    }
+    void OnRequestComplete(HttpServer*, HttpServerTransaction* t, int) {
+      ASSERT_EQ(transaction, t);
+      transaction = NULL;
+    }
+    void OnClosed(HttpServer*) {
+      server_closed = true;
+    }
+    void OnConnectionClosed(HttpServer*, int, StreamInterface* stream) {
+      connection_closed = true;
+      delete stream;
+    }
+  };
+
+  void CreateClientConnection(HttpServer& server,
+                              HttpServerMonitor& monitor,
+                              bool send_request) {
+    StreamSource* client = new StreamSource;
+    client->SetState(SS_OPEN);
+    server.HandleConnection(client);
+    EXPECT_FALSE(monitor.server_closed);
+    EXPECT_FALSE(monitor.transaction);
+
+    if (send_request) {
+      // Simulate a request
+      client->QueueString(kRequest);
+      EXPECT_FALSE(monitor.server_closed);
+    }
+  }
+}  // anonymous namespace
+
+TEST(HttpServer, DoesNotSignalCloseUnlessCloseAllIsCalled) {
+  HttpServer server;
+  HttpServerMonitor monitor(&server);
+  // Add an active client connection
+  CreateClientConnection(server, monitor, true);
+  // Simulate a response
+  ASSERT_TRUE(NULL != monitor.transaction);
+  server.Respond(monitor.transaction);
+  EXPECT_FALSE(monitor.transaction);
+  // Connection has closed, but no server close signal
+  EXPECT_FALSE(monitor.server_closed);
+  EXPECT_TRUE(monitor.connection_closed);
+}
+
+TEST(HttpServer, SignalsCloseWhenNoConnectionsAreActive) {
+  HttpServer server;
+  HttpServerMonitor monitor(&server);
+  // Add an idle client connection
+  CreateClientConnection(server, monitor, false);
+  // Perform graceful close
+  server.CloseAll(false);
+  // Connections have all closed
+  EXPECT_TRUE(monitor.server_closed);
+  EXPECT_TRUE(monitor.connection_closed);
+}
+
+TEST(HttpServer, SignalsCloseAfterGracefulCloseAll) {
+  HttpServer server;
+  HttpServerMonitor monitor(&server);
+  // Add an active client connection
+  CreateClientConnection(server, monitor, true);
+  // Initiate a graceful close
+  server.CloseAll(false);
+  EXPECT_FALSE(monitor.server_closed);
+  // Simulate a response
+  ASSERT_TRUE(NULL != monitor.transaction);
+  server.Respond(monitor.transaction);
+  EXPECT_FALSE(monitor.transaction);
+  // Connections have all closed
+  EXPECT_TRUE(monitor.server_closed);
+  EXPECT_TRUE(monitor.connection_closed);
+}
+
+TEST(HttpServer, SignalsCloseAfterForcedCloseAll) {
+  HttpServer server;
+  HttpServerMonitor monitor(&server);
+  // Add an active client connection
+  CreateClientConnection(server, monitor, true);
+  // Initiate a forceful close
+  server.CloseAll(true);
+  // Connections have all closed
+  EXPECT_TRUE(monitor.server_closed);
+  EXPECT_TRUE(monitor.connection_closed);
+}
+
+} // namespace talk_base
diff --git a/talk/base/ifaddrs-android.cc b/talk/base/ifaddrs-android.cc
new file mode 100644
index 0000000..7826840
--- /dev/null
+++ b/talk/base/ifaddrs-android.cc
@@ -0,0 +1,234 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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(ANDROID)
+#include "talk/base/ifaddrs-android.h"
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/utsname.h>
+#include <sys/ioctl.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <unistd.h>
+#include <errno.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+struct netlinkrequest {
+  nlmsghdr header;
+  ifaddrmsg msg;
+};
+
+namespace {
+const int kMaxReadSize = 4096;
+};
+
+int set_ifname(struct ifaddrs* ifaddr, int interface) {
+  char buf[IFNAMSIZ] = {0};
+  char* name = if_indextoname(interface, buf);
+  if (name == NULL) {
+    return -1;
+  }
+  ifaddr->ifa_name = new char[strlen(name) + 1];
+  strncpy(ifaddr->ifa_name, name, strlen(name) + 1);
+  return 0;
+}
+
+int set_flags(struct ifaddrs* ifaddr) {
+  int fd = socket(AF_INET, SOCK_DGRAM, 0);
+  if (fd == -1) {
+    return -1;
+  }
+  ifreq ifr;
+  memset(&ifr, 0, sizeof(ifr));
+  strncpy(ifr.ifr_name, ifaddr->ifa_name, IFNAMSIZ - 1);
+  int rc = ioctl(fd, SIOCGIFFLAGS, &ifr);
+  close(fd);
+  if (rc == -1) {
+    return -1;
+  }
+  ifaddr->ifa_flags = ifr.ifr_flags;
+  return 0;
+}
+
+int set_addresses(struct ifaddrs* ifaddr, ifaddrmsg* msg, void* data,
+                  size_t len) {
+  if (msg->ifa_family == AF_INET) {
+    sockaddr_in* sa = new sockaddr_in;
+    sa->sin_family = AF_INET;
+    memcpy(&sa->sin_addr, data, len);
+    ifaddr->ifa_addr = reinterpret_cast<sockaddr*>(sa);
+  } else if (msg->ifa_family == AF_INET6) {
+    sockaddr_in6* sa = new sockaddr_in6;
+    sa->sin6_family = AF_INET6;
+    sa->sin6_scope_id = msg->ifa_index;
+    memcpy(&sa->sin6_addr, data, len);
+    ifaddr->ifa_addr = reinterpret_cast<sockaddr*>(sa);
+  } else {
+    return -1;
+  }
+  return 0;
+}
+
+int make_prefixes(struct ifaddrs* ifaddr, int family, int prefixlen) {
+  char* prefix = NULL;
+  if (family == AF_INET) {
+    sockaddr_in* mask = new sockaddr_in;
+    mask->sin_family = AF_INET;
+    memset(&mask->sin_addr, 0, sizeof(in_addr));
+    ifaddr->ifa_netmask = reinterpret_cast<sockaddr*>(mask);
+    if (prefixlen > 32) {
+      prefixlen = 32;
+    }
+    prefix = reinterpret_cast<char*>(&mask->sin_addr);
+  } else if (family == AF_INET6) {
+    sockaddr_in6* mask = new sockaddr_in6;
+    mask->sin6_family = AF_INET6;
+    memset(&mask->sin6_addr, 0, sizeof(in6_addr));
+    ifaddr->ifa_netmask = reinterpret_cast<sockaddr*>(mask);
+    if (prefixlen > 128) {
+      prefixlen = 128;
+    }
+    prefix = reinterpret_cast<char*>(&mask->sin6_addr);
+  } else {
+    return -1;
+  }
+  for (int i = 0; i < (prefixlen / 8); i++) {
+    *prefix++ = 0xFF;
+  }
+  char remainder = 0xff;
+  remainder <<= (8 - prefixlen % 8);
+  *prefix = remainder;
+  return 0;
+}
+
+int populate_ifaddrs(struct ifaddrs* ifaddr, ifaddrmsg* msg, void* bytes,
+                     size_t len) {
+  if (set_ifname(ifaddr, msg->ifa_index) != 0) {
+    return -1;
+  }
+  if (set_flags(ifaddr) != 0) {
+    return -1;
+  }
+  if (set_addresses(ifaddr, msg, bytes, len) != 0) {
+    return -1;
+  }
+  if (make_prefixes(ifaddr, msg->ifa_family, msg->ifa_prefixlen) != 0) {
+    return -1;
+  }
+  return 0;
+}
+
+int getifaddrs(struct ifaddrs** result) {
+  int fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (fd < 0) {
+    return -1;
+  }
+
+  netlinkrequest ifaddr_request;
+  memset(&ifaddr_request, 0, sizeof(ifaddr_request));
+  ifaddr_request.header.nlmsg_flags = NLM_F_ROOT | NLM_F_REQUEST;
+  ifaddr_request.header.nlmsg_type = RTM_GETADDR;
+  ifaddr_request.header.nlmsg_len = NLMSG_LENGTH(sizeof(ifaddrmsg));
+
+  ssize_t count = send(fd, &ifaddr_request, ifaddr_request.header.nlmsg_len, 0);
+  if (static_cast<size_t>(count) != ifaddr_request.header.nlmsg_len) {
+    close(fd);
+    return -1;
+  }
+  struct ifaddrs* start = NULL;
+  struct ifaddrs* current = NULL;
+  char buf[kMaxReadSize];
+  ssize_t amount_read = recv(fd, &buf, kMaxReadSize, 0);
+  while (amount_read > 0) {
+    nlmsghdr* header = reinterpret_cast<nlmsghdr*>(&buf[0]);
+    size_t header_size = static_cast<size_t>(amount_read);
+    for ( ; NLMSG_OK(header, header_size);
+          header = NLMSG_NEXT(header, header_size)) {
+      switch (header->nlmsg_type) {
+        case NLMSG_DONE:
+          // Success. Return.
+          *result = start;
+          close(fd);
+          return 0;
+        case NLMSG_ERROR:
+          close(fd);
+          freeifaddrs(start);
+          return -1;
+        case RTM_NEWADDR: {
+          ifaddrmsg* address_msg =
+              reinterpret_cast<ifaddrmsg*>(NLMSG_DATA(header));
+          rtattr* rta = IFA_RTA(address_msg);
+          ssize_t payload_len = IFA_PAYLOAD(header);
+          while (RTA_OK(rta, payload_len)) {
+            if (rta->rta_type == IFA_ADDRESS) {
+              int family = address_msg->ifa_family;
+              if (family == AF_INET || family == AF_INET6) {
+                ifaddrs* newest = new ifaddrs;
+                memset(newest, 0, sizeof(ifaddrs));
+                if (current) {
+                  current->ifa_next = newest;
+                } else {
+                  start = newest;
+                }
+                if (populate_ifaddrs(newest, address_msg, RTA_DATA(rta),
+                                     RTA_PAYLOAD(rta)) != 0) {
+                  freeifaddrs(start);
+                  *result = NULL;
+                  return -1;
+                }
+                current = newest;
+              }
+            }
+            rta = RTA_NEXT(rta, payload_len);
+          }
+          break;
+        }
+      }
+    }
+    amount_read = recv(fd, &buf, kMaxReadSize, 0);
+  }
+  close(fd);
+  freeifaddrs(start);
+  return -1;
+}
+
+void freeifaddrs(struct ifaddrs* addrs) {
+  struct ifaddrs* last = NULL;
+  struct ifaddrs* cursor = addrs;
+  while (cursor) {
+    delete[] cursor->ifa_name;
+    delete cursor->ifa_addr;
+    delete cursor->ifa_netmask;
+    last = cursor;
+    cursor = cursor->ifa_next;
+    delete last;
+  }
+}
+#endif  // defined(ANDROID)
diff --git a/talk/base/ifaddrs-android.h b/talk/base/ifaddrs-android.h
new file mode 100644
index 0000000..e7d81e8
--- /dev/null
+++ b/talk/base/ifaddrs-android.h
@@ -0,0 +1,50 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_IFADDRS_ANDROID_H_
+#define TALK_BASE_IFADDRS_ANDROID_H_
+
+#include <stdio.h>
+#include <sys/socket.h>
+// Implementation of getifaddrs for Android.
+// Fills out a list of ifaddr structs (see below) which contain information
+// about every network interface available on the host.
+// See 'man getifaddrs' on Linux or OS X (nb: it is not a POSIX function).
+struct ifaddrs {
+  struct ifaddrs* ifa_next;
+  char* ifa_name;
+  unsigned int ifa_flags;
+  struct sockaddr* ifa_addr;
+  struct sockaddr* ifa_netmask;
+  // Real ifaddrs has broadcast, point to point and data members.
+  // We don't need them (yet?).
+};
+
+int getifaddrs(struct ifaddrs** result);
+void freeifaddrs(struct ifaddrs* addrs);
+
+#endif  // TALK_BASE_IFADDRS_ANDROID_H_
diff --git a/talk/base/ipaddress.cc b/talk/base/ipaddress.cc
new file mode 100644
index 0000000..4672590
--- /dev/null
+++ b/talk/base/ipaddress.cc
@@ -0,0 +1,466 @@
+/*
+ * 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 {
+
+// Prefixes used for categorizing IPv6 addresses.
+static const in6_addr kULAPrefix = {{{0xfc, 0}}};
+static const in6_addr kV4MappedPrefix = {{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                                           0xFF, 0xFF, 0}}};
+static const in6_addr k6To4Prefix = {{{0x20, 0x02, 0}}};
+static const in6_addr kTeredoPrefix = {{{0x20, 0x01, 0x00, 0x00}}};
+static const in6_addr kV4CompatibilityPrefix = {{{0}}};
+static const in6_addr kSiteLocalPrefix = {{{0xfe, 0xc0, 0}}};
+static const in6_addr k6BonePrefix = {{{0x3f, 0xfe, 0}}};
+
+bool IPAddress::strip_sensitive_ = false;
+
+static bool IsPrivateV4(uint32 ip);
+static in_addr ExtractMappedAddress(const in6_addr& addr);
+
+uint32 IPAddress::v4AddressAsHostOrderInteger() const {
+  if (family_ == AF_INET) {
+    return NetworkToHost32(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 NetworkToHost32(u_.ip4.s_addr) <
+          NetworkToHost32(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);
+}
+
+std::string IPAddress::ToSensitiveString() const {
+  if (!strip_sensitive_)
+    return ToString();
+
+  switch (family_) {
+    case AF_INET: {
+      std::string address = ToString();
+      size_t find_pos = address.rfind('.');
+      if (find_pos == std::string::npos)
+        return std::string();
+      address.resize(find_pos);
+      address += ".x";
+      return address;
+    }
+    case AF_INET6: {
+      // TODO(grunell): Return a string of format 1:2:3:x:x:x:x:x or such
+      // instead of zeroing out.
+      return TruncateIP(*this, 128 - 80).ToString();
+    }
+  }
+  return std::string();
+}
+
+IPAddress IPAddress::Normalized() const {
+  if (family_ != AF_INET6) {
+    return *this;
+  }
+  if (!IPIsV4Mapped(*this)) {
+    return *this;
+  }
+  in_addr addr = ExtractMappedAddress(u_.ip6);
+  return IPAddress(addr);
+}
+
+IPAddress IPAddress::AsIPv6Address() const {
+  if (family_ != AF_INET) {
+    return *this;
+  }
+  in6_addr v6addr = kV4MappedPrefix;
+  ::memcpy(&v6addr.s6_addr[12], &u_.ip4.s_addr, sizeof(u_.ip4.s_addr));
+  return IPAddress(v6addr);
+}
+
+void IPAddress::set_strip_sensitive(bool enable) {
+  strip_sensitive_ = enable;
+}
+
+
+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));
+}
+
+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 IPFromAddrInfo(struct addrinfo* info, IPAddress* out) {
+  if (!info || !info->ai_addr) {
+    return false;
+  }
+  if (info->ai_addr->sa_family == AF_INET) {
+    sockaddr_in* addr = reinterpret_cast<sockaddr_in*>(info->ai_addr);
+    *out = IPAddress(addr->sin_addr);
+    return true;
+  } else if (info->ai_addr->sa_family == AF_INET6) {
+    sockaddr_in6* addr = reinterpret_cast<sockaddr_in6*>(info->ai_addr);
+    *out = IPAddress(addr->sin6_addr);
+    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) {
+  switch (ip.family()) {
+    case AF_INET:
+      return ip == IPAddress(INADDR_ANY);
+    case AF_INET6:
+      return ip == IPAddress(in6addr_any);
+    case AF_UNSPEC:
+      return false;
+  }
+  return false;
+}
+
+bool IPIsLoopback(const IPAddress& ip) {
+  switch (ip.family()) {
+    case AF_INET: {
+      return ip == IPAddress(INADDR_LOOPBACK);
+    }
+    case AF_INET6: {
+      return ip == IPAddress(in6addr_loopback);
+    }
+  }
+  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;
+}
+
+bool IPIsUnspec(const IPAddress& ip) {
+  return ip.family() == AF_UNSPEC;
+}
+
+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 = 32 - (length - (position * 32));
+    // Note: 64bit mask constant needed to allow possible 32-bit left shift.
+    uint32 inner_mask = 0xFFFFFFFFLL  << inner_length;
+    uint32* v6_as_ints =
+        reinterpret_cast<uint32*>(&v6addr.s6_addr);
+    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) {
+  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;
+    }
+  }
+  if (word_to_count == 0) {
+    return bits;
+  }
+
+  // Public domain bit-twiddling hack from:
+  // http://graphics.stanford.edu/~seander/bithacks.html
+  // Counts the trailing 0s in the word.
+  unsigned int zeroes = 32;
+  word_to_count &= -static_cast<int32>(word_to_count);
+  if (word_to_count) zeroes--;
+  if (word_to_count & 0x0000FFFF) zeroes -= 16;
+  if (word_to_count & 0x00FF00FF) zeroes -= 8;
+  if (word_to_count & 0x0F0F0F0F) zeroes -= 4;
+  if (word_to_count & 0x33333333) zeroes -= 2;
+  if (word_to_count & 0x55555555) zeroes -= 1;
+
+  return bits + (32 - zeroes);
+}
+
+bool IPIsHelper(const IPAddress& ip, const in6_addr& tomatch, int length) {
+  // Helper method for checking IP prefix matches (but only on whole byte
+  // lengths). Length is in bits.
+  in6_addr addr = ip.ipv6_address();
+  return ::memcmp(&addr, &tomatch, (length >> 3)) == 0;
+}
+
+bool IPIs6Bone(const IPAddress& ip) {
+  return IPIsHelper(ip, k6BonePrefix, 16);
+}
+
+bool IPIs6To4(const IPAddress& ip) {
+  return IPIsHelper(ip, k6To4Prefix, 16);
+}
+
+bool IPIsSiteLocal(const IPAddress& ip) {
+  // Can't use the helper because the prefix is 10 bits.
+  in6_addr addr = ip.ipv6_address();
+  return addr.s6_addr[0] == 0xFE && (addr.s6_addr[1] & 0xC0) == 0xC0;
+}
+
+bool IPIsULA(const IPAddress& ip) {
+  // Can't use the helper because the prefix is 7 bits.
+  in6_addr addr = ip.ipv6_address();
+  return (addr.s6_addr[0] & 0xFE) == 0xFC;
+}
+
+bool IPIsTeredo(const IPAddress& ip) {
+  return IPIsHelper(ip, kTeredoPrefix, 32);
+}
+
+bool IPIsV4Compatibility(const IPAddress& ip) {
+  return IPIsHelper(ip, kV4CompatibilityPrefix, 96);
+}
+
+bool IPIsV4Mapped(const IPAddress& ip) {
+  return IPIsHelper(ip, kV4MappedPrefix, 96);
+}
+
+int IPAddressPrecedence(const IPAddress& ip) {
+  // Precedence values from RFC 3484-bis. Prefers native v4 over 6to4/Teredo.
+  if (ip.family() == AF_INET) {
+    return 30;
+  } else if (ip.family() == AF_INET6) {
+    if (IPIsLoopback(ip)) {
+      return 60;
+    } else if (IPIsULA(ip)) {
+      return 50;
+    } else if (IPIsV4Mapped(ip)) {
+      return 30;
+    } else if (IPIs6To4(ip)) {
+      return 20;
+    } else if (IPIsTeredo(ip)) {
+      return 10;
+    } else if (IPIsV4Compatibility(ip) || IPIsSiteLocal(ip) || IPIs6Bone(ip)) {
+      return 1;
+    } else {
+      // A 'normal' IPv6 address.
+      return 40;
+    }
+  }
+  return 0;
+}
+
+}  // Namespace talk base
diff --git a/talk/base/ipaddress.h b/talk/base/ipaddress.h
new file mode 100644
index 0000000..b60de8ab
--- /dev/null
+++ b/talk/base/ipaddress.h
@@ -0,0 +1,158 @@
+/*
+ * 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"
+#include "talk/base/byteorder.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 = HostToNetwork32(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;
+
+  // Same as ToString but anonymizes it by hiding the last part.
+  std::string ToSensitiveString() 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;
+
+  static void set_strip_sensitive(bool enable);
+
+ private:
+  int family_;
+  union {
+    in_addr ip4;
+    in6_addr ip6;
+  } u_;
+
+  static bool strip_sensitive_;
+};
+
+bool IPFromAddrInfo(struct addrinfo* info, 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);
+bool IPIsUnspec(const IPAddress& ip);
+size_t HashIP(const IPAddress& ip);
+
+// These are only really applicable for IPv6 addresses.
+bool IPIs6Bone(const IPAddress& ip);
+bool IPIs6To4(const IPAddress& ip);
+bool IPIsSiteLocal(const IPAddress& ip);
+bool IPIsTeredo(const IPAddress& ip);
+bool IPIsULA(const IPAddress& ip);
+bool IPIsV4Compatibility(const IPAddress& ip);
+bool IPIsV4Mapped(const IPAddress& ip);
+
+// Returns the precedence value for this IP as given in RFC3484.
+int IPAddressPrecedence(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..424b557
--- /dev/null
+++ b/talk/base/ipaddress_unittest.cc
@@ -0,0 +1,888 @@
+/*
+ * 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 kIPv4PublicAddrAnonymizedString = "1.2.3.x";
+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 kIPv6PublicAddrAnonymizedString = "2401:fa00:4::";
+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 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 TryInvalidMaskCount(const std::string& mask) {
+  // We don't care about the result at all, but we do want to know if
+  // CountIPMaskBits is going to crash or infinite loop or something.
+  IPAddress addr;
+  if (!IPFromString(mask, &addr)) {
+    return false;
+  }
+  CountIPMaskBits(addr);
+  return true;
+}
+
+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, 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, TestIPFromAddrInfo) {
+  struct sockaddr_in expected4;
+  struct sockaddr_in6 expected6;
+  struct addrinfo test_info;
+  struct addrinfo next_info;
+  memset(&next_info, 'A', sizeof(next_info));
+  test_info.ai_next = &next_info;
+  // Check that we can get an IPv4 address out.
+  test_info.ai_addr = reinterpret_cast<struct sockaddr*>(&expected4);
+  expected4.sin_addr.s_addr = HostToNetwork32(kIPv4PublicAddr);
+  expected4.sin_family = AF_INET;
+  IPAddress expected(kIPv4PublicAddr);
+  IPAddress addr;
+  EXPECT_TRUE(IPFromAddrInfo(&test_info, &addr));
+  EXPECT_EQ(expected, addr);
+  // Check that we can get an IPv6 address out.
+  expected6.sin6_addr = kIPv6PublicAddr;
+  expected6.sin6_family = AF_INET6;
+  expected = IPAddress(kIPv6PublicAddr);
+  test_info.ai_addr = reinterpret_cast<struct sockaddr*>(&expected6);
+  EXPECT_TRUE(IPFromAddrInfo(&test_info, &addr));
+  EXPECT_EQ(expected, addr);
+  // Check that unspec fails.
+  expected6.sin6_family = AF_UNSPEC;
+  EXPECT_FALSE(IPFromAddrInfo(&test_info, &addr));
+  // Check a zeroed out addrinfo doesn't crash us.
+  memset(&next_info, 0, sizeof(next_info));
+  EXPECT_FALSE(IPFromAddrInfo(&next_info, &addr));
+}
+
+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 invalid but lets test them
+  // to make sure they don't crash anything or infinite loop or something.
+  EXPECT_PRED1(TryInvalidMaskCount, "217.0.0.0");
+  EXPECT_PRED1(TryInvalidMaskCount, "255.185.0.0");
+  EXPECT_PRED1(TryInvalidMaskCount, "255.255.251.0");
+  EXPECT_PRED1(TryInvalidMaskCount, "255.255.251.255");
+  EXPECT_PRED1(TryInvalidMaskCount, "255.255.254.201");
+  EXPECT_PRED1(TryInvalidMaskCount, "::1");
+  EXPECT_PRED1(TryInvalidMaskCount, "fe80::1");
+  EXPECT_PRED1(TryInvalidMaskCount, "ff80::1");
+  EXPECT_PRED1(TryInvalidMaskCount, "ffff::1");
+  EXPECT_PRED1(TryInvalidMaskCount, "ffff:ff00:1::1");
+  EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff::ffff:1");
+  EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ff00:1::");
+  EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff::ff00");
+  EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ff00:1234::");
+  EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ffff:0012::ffff");
+  EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ffff:ff01::");
+  EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ffff:ffff:7f00::");
+  EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ffff:ffff:ff7a::");
+  EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:7f00:0000");
+  EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ff70:0000");
+  EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:0211");
+  EXPECT_PRED1(TryInvalidMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff7f");
+}
+
+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, "ffff:ff80:1111:2222:3333:4444:5555:6666", 32,
+               "ffff:ff80::");
+  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, "9999:1111:2233:4567:5555:6666:7777:8888", 64,
+               "9999:1111:2233:4567::");
+  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", 96,
+               "1111:2222:3333:4444:5555:6666::");
+  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,
+               "::");
+}
+
+TEST(IPAddressTest, TestCategorizeIPv6) {
+  // Test determining if an IPAddress is 6Bone/6To4/Teredo/etc.
+  // IPv4 address, should be none of these (not even v4compat/v4mapped).
+  IPAddress v4_addr(kIPv4PublicAddr);
+  EXPECT_FALSE(IPIs6Bone(v4_addr));
+  EXPECT_FALSE(IPIs6To4(v4_addr));
+  EXPECT_FALSE(IPIsSiteLocal(v4_addr));
+  EXPECT_FALSE(IPIsTeredo(v4_addr));
+  EXPECT_FALSE(IPIsULA(v4_addr));
+  EXPECT_FALSE(IPIsV4Compatibility(v4_addr));
+  EXPECT_FALSE(IPIsV4Mapped(v4_addr));
+  // Linklocal (fe80::/16) adddress; should be none of these.
+  IPAddress linklocal_addr(kIPv6LinkLocalAddr);
+  EXPECT_FALSE(IPIs6Bone(linklocal_addr));
+  EXPECT_FALSE(IPIs6To4(linklocal_addr));
+  EXPECT_FALSE(IPIsSiteLocal(linklocal_addr));
+  EXPECT_FALSE(IPIsTeredo(linklocal_addr));
+  EXPECT_FALSE(IPIsULA(linklocal_addr));
+  EXPECT_FALSE(IPIsV4Compatibility(linklocal_addr));
+  EXPECT_FALSE(IPIsV4Mapped(linklocal_addr));
+  // 'Normal' IPv6 address, should also be none of these.
+  IPAddress normal_addr(kIPv6PublicAddr);
+  EXPECT_FALSE(IPIs6Bone(normal_addr));
+  EXPECT_FALSE(IPIs6To4(normal_addr));
+  EXPECT_FALSE(IPIsSiteLocal(normal_addr));
+  EXPECT_FALSE(IPIsTeredo(normal_addr));
+  EXPECT_FALSE(IPIsULA(normal_addr));
+  EXPECT_FALSE(IPIsV4Compatibility(normal_addr));
+  EXPECT_FALSE(IPIsV4Mapped(normal_addr));
+  // IPv4 mapped address (::ffff:123.123.123.123)
+  IPAddress v4mapped_addr(kIPv4MappedPublicAddr);
+  EXPECT_TRUE(IPIsV4Mapped(v4mapped_addr));
+  EXPECT_FALSE(IPIsV4Compatibility(v4mapped_addr));
+  EXPECT_FALSE(IPIs6Bone(v4mapped_addr));
+  EXPECT_FALSE(IPIs6To4(v4mapped_addr));
+  EXPECT_FALSE(IPIsSiteLocal(v4mapped_addr));
+  EXPECT_FALSE(IPIsTeredo(v4mapped_addr));
+  EXPECT_FALSE(IPIsULA(v4mapped_addr));
+  // IPv4 compatibility address (::123.123.123.123)
+  IPAddress v4compat_addr;
+  IPFromString("::192.168.7.1", &v4compat_addr);
+  EXPECT_TRUE(IPIsV4Compatibility(v4compat_addr));
+  EXPECT_FALSE(IPIs6Bone(v4compat_addr));
+  EXPECT_FALSE(IPIs6To4(v4compat_addr));
+  EXPECT_FALSE(IPIsSiteLocal(v4compat_addr));
+  EXPECT_FALSE(IPIsTeredo(v4compat_addr));
+  EXPECT_FALSE(IPIsULA(v4compat_addr));
+  EXPECT_FALSE(IPIsV4Mapped(v4compat_addr));
+  // 6Bone address (3FFE::/16)
+  IPAddress sixbone_addr;
+  IPFromString("3FFE:123:456::789:123", &sixbone_addr);
+  EXPECT_TRUE(IPIs6Bone(sixbone_addr));
+  EXPECT_FALSE(IPIs6To4(sixbone_addr));
+  EXPECT_FALSE(IPIsSiteLocal(sixbone_addr));
+  EXPECT_FALSE(IPIsTeredo(sixbone_addr));
+  EXPECT_FALSE(IPIsULA(sixbone_addr));
+  EXPECT_FALSE(IPIsV4Mapped(sixbone_addr));
+  EXPECT_FALSE(IPIsV4Compatibility(sixbone_addr));
+  // Unique Local Address (FC::/7)
+  IPAddress ula_addr;
+  IPFromString("FC00:123:456::789:123", &ula_addr);
+  EXPECT_TRUE(IPIsULA(ula_addr));
+  EXPECT_FALSE(IPIs6Bone(ula_addr));
+  EXPECT_FALSE(IPIs6To4(ula_addr));
+  EXPECT_FALSE(IPIsSiteLocal(ula_addr));
+  EXPECT_FALSE(IPIsTeredo(ula_addr));
+  EXPECT_FALSE(IPIsV4Mapped(ula_addr));
+  EXPECT_FALSE(IPIsV4Compatibility(ula_addr));
+  // 6To4 Address (2002::/16)
+  IPAddress sixtofour_addr;
+  IPFromString("2002:123:456::789:123", &sixtofour_addr);
+  EXPECT_TRUE(IPIs6To4(sixtofour_addr));
+  EXPECT_FALSE(IPIs6Bone(sixtofour_addr));
+  EXPECT_FALSE(IPIsSiteLocal(sixtofour_addr));
+  EXPECT_FALSE(IPIsTeredo(sixtofour_addr));
+  EXPECT_FALSE(IPIsULA(sixtofour_addr));
+  EXPECT_FALSE(IPIsV4Compatibility(sixtofour_addr));
+  EXPECT_FALSE(IPIsV4Mapped(sixtofour_addr));
+  // Site Local address (FEC0::/10)
+  IPAddress sitelocal_addr;
+  IPFromString("FEC0:123:456::789:123", &sitelocal_addr);
+  EXPECT_TRUE(IPIsSiteLocal(sitelocal_addr));
+  EXPECT_FALSE(IPIs6Bone(sitelocal_addr));
+  EXPECT_FALSE(IPIs6To4(sitelocal_addr));
+  EXPECT_FALSE(IPIsTeredo(sitelocal_addr));
+  EXPECT_FALSE(IPIsULA(sitelocal_addr));
+  EXPECT_FALSE(IPIsV4Compatibility(sitelocal_addr));
+  EXPECT_FALSE(IPIsV4Mapped(sitelocal_addr));
+  // Teredo Address (2001:0000::/32)
+  IPAddress teredo_addr;
+  IPFromString("2001:0000:123:456::789:123", &teredo_addr);
+  EXPECT_TRUE(IPIsTeredo(teredo_addr));
+  EXPECT_FALSE(IPIsSiteLocal(teredo_addr));
+  EXPECT_FALSE(IPIs6Bone(teredo_addr));
+  EXPECT_FALSE(IPIs6To4(teredo_addr));
+  EXPECT_FALSE(IPIsULA(teredo_addr));
+  EXPECT_FALSE(IPIsV4Compatibility(teredo_addr));
+  EXPECT_FALSE(IPIsV4Mapped(teredo_addr));
+}
+
+TEST(IPAddressTest, TestToSensitiveString) {
+  IPAddress addr_v4 = IPAddress(kIPv4PublicAddr);
+  EXPECT_EQ(kIPv4PublicAddrString, addr_v4.ToString());
+  EXPECT_EQ(kIPv4PublicAddrString, addr_v4.ToSensitiveString());
+  IPAddress::set_strip_sensitive(true);
+  EXPECT_EQ(kIPv4PublicAddrString, addr_v4.ToString());
+  EXPECT_EQ(kIPv4PublicAddrAnonymizedString, addr_v4.ToSensitiveString());
+  IPAddress::set_strip_sensitive(false);
+
+  IPAddress addr_v6 = IPAddress(kIPv6PublicAddr);
+  EXPECT_EQ(kIPv6PublicAddrString, addr_v6.ToString());
+  EXPECT_EQ(kIPv6PublicAddrString, addr_v6.ToSensitiveString());
+  IPAddress::set_strip_sensitive(true);
+  EXPECT_EQ(kIPv6PublicAddrString, addr_v6.ToString());
+  EXPECT_EQ(kIPv6PublicAddrAnonymizedString, addr_v6.ToSensitiveString());
+  IPAddress::set_strip_sensitive(false);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/json.cc b/talk/base/json.cc
new file mode 100644
index 0000000..af81e06
--- /dev/null
+++ b/talk/base/json.cc
@@ -0,0 +1,313 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/json.h"
+
+#include <errno.h>
+
+#include <climits>
+#include <cstdlib>
+#include <sstream>
+
+bool GetStringFromJson(const Json::Value& in, std::string* out) {
+  if (!in.isString()) {
+    std::ostringstream s;
+    if (in.isBool()) {
+      s << std::boolalpha << in.asBool();
+    } else if (in.isInt()) {
+      s << in.asInt();
+    } else if (in.isUInt()) {
+      s << in.asUInt();
+    } else if (in.isDouble()) {
+      s << in.asDouble();
+    } else {
+      return false;
+    }
+    *out = s.str();
+  } else {
+    *out = in.asString();
+  }
+  return true;
+}
+
+bool GetIntFromJson(const Json::Value& in, int* out) {
+  bool ret;
+  if (!in.isString()) {
+    ret = in.isConvertibleTo(Json::intValue);
+    if (ret) {
+      *out = in.asInt();
+    }
+  } else {
+    long val;  // NOLINT
+    const char* c_str = in.asCString();
+    char* end_ptr;
+    errno = 0;
+    val = strtol(c_str, &end_ptr, 10);  // NOLINT
+    ret = (end_ptr != c_str && *end_ptr == '\0' && !errno &&
+           val >= INT_MIN && val <= INT_MAX);
+    *out = val;
+  }
+  return ret;
+}
+
+bool GetUIntFromJson(const Json::Value& in, unsigned int* out) {
+  bool ret;
+  if (!in.isString()) {
+    ret = in.isConvertibleTo(Json::uintValue);
+    if (ret) {
+      *out = in.asUInt();
+    }
+  } else {
+    unsigned long val;  // NOLINT
+    const char* c_str = in.asCString();
+    char* end_ptr;
+    errno = 0;
+    val = strtoul(c_str, &end_ptr, 10);  // NOLINT
+    ret = (end_ptr != c_str && *end_ptr == '\0' && !errno &&
+           val <= UINT_MAX);
+    *out = val;
+  }
+  return ret;
+}
+
+bool GetBoolFromJson(const Json::Value& in, bool* out) {
+  bool ret;
+  if (!in.isString()) {
+    ret = in.isConvertibleTo(Json::booleanValue);
+    if (ret) {
+      *out = in.asBool();
+    }
+  } else {
+    if (in.asString() == "true") {
+      *out = true;
+      ret = true;
+    } else if (in.asString() == "false") {
+      *out = false;
+      ret = true;
+    } else {
+      ret = false;
+    }
+  }
+  return ret;
+}
+
+bool GetDoubleFromJson(const Json::Value& in, double* out) {
+  bool ret;
+  if (!in.isString()) {
+    ret = in.isConvertibleTo(Json::realValue);
+    if (ret) {
+      *out = in.asDouble();
+    }
+  } else {
+    double val;
+    const char* c_str = in.asCString();
+    char* end_ptr;
+    errno = 0;
+    val = strtod(c_str, &end_ptr);
+    ret = (end_ptr != c_str && *end_ptr == '\0' && !errno);
+    *out = val;
+  }
+  return ret;
+}
+
+namespace {
+template<typename T>
+bool JsonArrayToVector(const Json::Value& value,
+                       bool (*getter)(const Json::Value& in, T* out),
+                       std::vector<T> *vec) {
+  vec->clear();
+  if (!value.isArray()) {
+    return false;
+  }
+
+  for (Json::Value::ArrayIndex i = 0; i < value.size(); ++i) {
+    T val;
+    if (!getter(value[i], &val)) {
+      return false;
+    }
+    vec->push_back(val);
+  }
+
+  return true;
+}
+// Trivial getter helper
+bool GetValueFromJson(const Json::Value& in, Json::Value* out) {
+  *out = in;
+  return true;
+}
+}  // unnamed namespace
+
+bool JsonArrayToValueVector(const Json::Value& in,
+                            std::vector<Json::Value>* out) {
+  return JsonArrayToVector(in, GetValueFromJson, out);
+}
+
+bool JsonArrayToIntVector(const Json::Value& in,
+                          std::vector<int>* out) {
+  return JsonArrayToVector(in, GetIntFromJson, out);
+}
+
+bool JsonArrayToUIntVector(const Json::Value& in,
+                           std::vector<unsigned int>* out) {
+  return JsonArrayToVector(in, GetUIntFromJson, out);
+}
+
+bool JsonArrayToStringVector(const Json::Value& in,
+                             std::vector<std::string>* out) {
+  return JsonArrayToVector(in, GetStringFromJson, out);
+}
+
+bool JsonArrayToBoolVector(const Json::Value& in,
+                           std::vector<bool>* out) {
+  return JsonArrayToVector(in, GetBoolFromJson, out);
+}
+
+bool JsonArrayToDoubleVector(const Json::Value& in,
+                             std::vector<double>* out) {
+  return JsonArrayToVector(in, GetDoubleFromJson, out);
+}
+
+namespace {
+template<typename T>
+Json::Value VectorToJsonArray(const std::vector<T>& vec) {
+  Json::Value result(Json::arrayValue);
+  for (size_t i = 0; i < vec.size(); ++i) {
+    result.append(Json::Value(vec[i]));
+  }
+  return result;
+}
+}  // unnamed namespace
+
+Json::Value ValueVectorToJsonArray(const std::vector<Json::Value>& in) {
+  return VectorToJsonArray(in);
+}
+
+Json::Value IntVectorToJsonArray(const std::vector<int>& in) {
+  return VectorToJsonArray(in);
+}
+
+Json::Value UIntVectorToJsonArray(const std::vector<unsigned int>& in) {
+  return VectorToJsonArray(in);
+}
+
+Json::Value StringVectorToJsonArray(const std::vector<std::string>& in) {
+  return VectorToJsonArray(in);
+}
+
+Json::Value BoolVectorToJsonArray(const std::vector<bool>& in) {
+  return VectorToJsonArray(in);
+}
+
+Json::Value DoubleVectorToJsonArray(const std::vector<double>& in) {
+  return VectorToJsonArray(in);
+}
+
+bool GetValueFromJsonArray(const Json::Value& in, size_t n,
+                           Json::Value* out) {
+  if (!in.isArray() || !in.isValidIndex(static_cast<int>(n))) {
+    return false;
+  }
+
+  *out = in[static_cast<Json::Value::ArrayIndex>(n)];
+  return true;
+}
+
+bool GetIntFromJsonArray(const Json::Value& in, size_t n,
+                         int* out) {
+  Json::Value x;
+  return GetValueFromJsonArray(in, n, &x) && GetIntFromJson(x, out);
+}
+
+bool GetUIntFromJsonArray(const Json::Value& in, size_t n,
+                          unsigned int* out)  {
+  Json::Value x;
+  return GetValueFromJsonArray(in, n, &x) && GetUIntFromJson(x, out);
+}
+
+bool GetStringFromJsonArray(const Json::Value& in, size_t n,
+                            std::string* out) {
+  Json::Value x;
+  return GetValueFromJsonArray(in, n, &x) && GetStringFromJson(x, out);
+}
+
+bool GetBoolFromJsonArray(const Json::Value& in, size_t n,
+                          bool* out) {
+  Json::Value x;
+  return GetValueFromJsonArray(in, n, &x) && GetBoolFromJson(x, out);
+}
+
+bool GetDoubleFromJsonArray(const Json::Value& in, size_t n,
+                            double* out) {
+  Json::Value x;
+  return GetValueFromJsonArray(in, n, &x) && GetDoubleFromJson(x, out);
+}
+
+bool GetValueFromJsonObject(const Json::Value& in, const std::string& k,
+                            Json::Value* out) {
+  if (!in.isObject() || !in.isMember(k)) {
+    return false;
+  }
+
+  *out = in[k];
+  return true;
+}
+
+bool GetIntFromJsonObject(const Json::Value& in, const std::string& k,
+                          int* out) {
+  Json::Value x;
+  return GetValueFromJsonObject(in, k, &x) && GetIntFromJson(x, out);
+}
+
+bool GetUIntFromJsonObject(const Json::Value& in, const std::string& k,
+                           unsigned int* out)  {
+  Json::Value x;
+  return GetValueFromJsonObject(in, k, &x) && GetUIntFromJson(x, out);
+}
+
+bool GetStringFromJsonObject(const Json::Value& in, const std::string& k,
+                             std::string* out)  {
+  Json::Value x;
+  return GetValueFromJsonObject(in, k, &x) && GetStringFromJson(x, out);
+}
+
+bool GetBoolFromJsonObject(const Json::Value& in, const std::string& k,
+                           bool* out) {
+  Json::Value x;
+  return GetValueFromJsonObject(in, k, &x) && GetBoolFromJson(x, out);
+}
+
+bool GetDoubleFromJsonObject(const Json::Value& in, const std::string& k,
+                             double* out) {
+  Json::Value x;
+  return GetValueFromJsonObject(in, k, &x) && GetDoubleFromJson(x, out);
+}
+
+std::string JsonValueToString(const Json::Value& json) {
+  Json::FastWriter w;
+  std::string value = w.write(json);
+  return value.substr(0, value.size() - 1);  // trim trailing newline
+}
diff --git a/talk/base/json.h b/talk/base/json.h
new file mode 100644
index 0000000..50a4122
--- /dev/null
+++ b/talk/base/json.h
@@ -0,0 +1,106 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_JSON_H_
+#define TALK_BASE_JSON_H_
+
+#include <string>
+#include <vector>
+
+#ifdef JSONCPP_RELATIVE_PATH
+#include "json/json.h"
+#else
+#include "third_party/jsoncpp/json.h"
+#endif
+
+// TODO: Move to talk_base namespace
+
+///////////////////////////////////////////////////////////////////////////////
+// JSON Helpers
+///////////////////////////////////////////////////////////////////////////////
+
+// Robust conversion operators, better than the ones in JsonCpp.
+bool GetIntFromJson(const Json::Value& in, int* out);
+bool GetUIntFromJson(const Json::Value& in, unsigned int* out);
+bool GetStringFromJson(const Json::Value& in, std::string* out);
+bool GetBoolFromJson(const Json::Value& in, bool* out);
+bool GetDoubleFromJson(const Json::Value& in, double* out);
+
+// Pull values out of a JSON array.
+bool GetValueFromJsonArray(const Json::Value& in, size_t n,
+                           Json::Value* out);
+bool GetIntFromJsonArray(const Json::Value& in, size_t n,
+                         int* out);
+bool GetUIntFromJsonArray(const Json::Value& in, size_t n,
+                          unsigned int* out);
+bool GetStringFromJsonArray(const Json::Value& in, size_t n,
+                            std::string* out);
+bool GetBoolFromJsonArray(const Json::Value& in, size_t n,
+                          bool* out);
+bool GetDoubleFromJsonArray(const Json::Value& in, size_t n,
+                            double* out);
+
+// Convert json arrays to std::vector
+bool JsonArrayToValueVector(const Json::Value& in,
+                            std::vector<Json::Value>* out);
+bool JsonArrayToIntVector(const Json::Value& in,
+                          std::vector<int>* out);
+bool JsonArrayToUIntVector(const Json::Value& in,
+                           std::vector<unsigned int>* out);
+bool JsonArrayToStringVector(const Json::Value& in,
+                             std::vector<std::string>* out);
+bool JsonArrayToBoolVector(const Json::Value& in,
+                           std::vector<bool>* out);
+bool JsonArrayToDoubleVector(const Json::Value& in,
+                             std::vector<double>* out);
+
+// Convert std::vector to json array
+Json::Value ValueVectorToJsonArray(const std::vector<Json::Value>& in);
+Json::Value IntVectorToJsonArray(const std::vector<int>& in);
+Json::Value UIntVectorToJsonArray(const std::vector<unsigned int>& in);
+Json::Value StringVectorToJsonArray(const std::vector<std::string>& in);
+Json::Value BoolVectorToJsonArray(const std::vector<bool>& in);
+Json::Value DoubleVectorToJsonArray(const std::vector<double>& in);
+
+// Pull values out of a JSON object.
+bool GetValueFromJsonObject(const Json::Value& in, const std::string& k,
+                            Json::Value* out);
+bool GetIntFromJsonObject(const Json::Value& in, const std::string& k,
+                          int* out);
+bool GetUIntFromJsonObject(const Json::Value& in, const std::string& k,
+                           unsigned int* out);
+bool GetStringFromJsonObject(const Json::Value& in, const std::string& k,
+                             std::string* out);
+bool GetBoolFromJsonObject(const Json::Value& in, const std::string& k,
+                           bool* out);
+bool GetDoubleFromJsonObject(const Json::Value& in, const std::string& k,
+                             double* out);
+
+// Writes out a Json value as a string.
+std::string JsonValueToString(const Json::Value& json);
+
+#endif  // TALK_BASE_JSON_H_
diff --git a/talk/base/json_unittest.cc b/talk/base/json_unittest.cc
new file mode 100644
index 0000000..96a7975
--- /dev/null
+++ b/talk/base/json_unittest.cc
@@ -0,0 +1,294 @@
+/*
+ * 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));
+}
+
+namespace {
+template <typename T>
+std::vector<T> VecOf3(const T& a, const T& b, const T& c) {
+  std::vector<T> in;
+  in.push_back(a);
+  in.push_back(b);
+  in.push_back(c);
+  return in;
+}
+template <typename T>
+Json::Value JsonVecOf3(const T& a, const T& b, const T& c) {
+  Json::Value in(Json::arrayValue);
+  in.append(a);
+  in.append(b);
+  in.append(c);
+  return in;
+}
+}  // unnamed namespace
+
+TEST(JsonTest, ValueVectorToFromArray) {
+  std::vector<Json::Value> in = VecOf3<Json::Value>("a", "b", "c");
+  Json::Value out = ValueVectorToJsonArray(in);
+  EXPECT_EQ(in.size(), out.size());
+  for (Json::Value::ArrayIndex i = 0; i < in.size(); ++i) {
+    EXPECT_EQ(in[i].asString(), out[i].asString());
+  }
+  Json::Value inj = JsonVecOf3<Json::Value>("a", "b", "c");
+  EXPECT_EQ(inj, out);
+  std::vector<Json::Value> outj;
+  EXPECT_TRUE(JsonArrayToValueVector(inj, &outj));
+  for (Json::Value::ArrayIndex i = 0; i < in.size(); i++) {
+    EXPECT_EQ(in[i], outj[i]);
+  }
+}
+
+TEST(JsonTest, IntVectorToFromArray) {
+  std::vector<int> in = VecOf3<int>(1, 2, 3);
+  Json::Value out = IntVectorToJsonArray(in);
+  EXPECT_EQ(in.size(), out.size());
+  for (Json::Value::ArrayIndex i = 0; i < in.size(); ++i) {
+    EXPECT_EQ(in[i], out[i].asInt());
+  }
+  Json::Value inj = JsonVecOf3<int>(1, 2, 3);
+  EXPECT_EQ(inj, out);
+  std::vector<int> outj;
+  EXPECT_TRUE(JsonArrayToIntVector(inj, &outj));
+  for (Json::Value::ArrayIndex i = 0; i < in.size(); i++) {
+    EXPECT_EQ(in[i], outj[i]);
+  }
+}
+
+TEST(JsonTest, UIntVectorToFromArray) {
+  std::vector<unsigned int> in = VecOf3<unsigned int>(1, 2, 3);
+  Json::Value out = UIntVectorToJsonArray(in);
+  EXPECT_EQ(in.size(), out.size());
+  for (Json::Value::ArrayIndex i = 0; i < in.size(); ++i) {
+    EXPECT_EQ(in[i], out[i].asUInt());
+  }
+  Json::Value inj = JsonVecOf3<unsigned int>(1, 2, 3);
+  EXPECT_EQ(inj, out);
+  std::vector<unsigned int> outj;
+  EXPECT_TRUE(JsonArrayToUIntVector(inj, &outj));
+  for (Json::Value::ArrayIndex i = 0; i < in.size(); i++) {
+    EXPECT_EQ(in[i], outj[i]);
+  }
+}
+
+TEST(JsonTest, StringVectorToFromArray) {
+  std::vector<std::string> in = VecOf3<std::string>("a", "b", "c");
+  Json::Value out = StringVectorToJsonArray(in);
+  EXPECT_EQ(in.size(), out.size());
+  for (Json::Value::ArrayIndex i = 0; i < in.size(); ++i) {
+    EXPECT_EQ(in[i], out[i].asString());
+  }
+  Json::Value inj = JsonVecOf3<std::string>("a", "b", "c");
+  EXPECT_EQ(inj, out);
+  std::vector<std::string> outj;
+  EXPECT_TRUE(JsonArrayToStringVector(inj, &outj));
+  for (Json::Value::ArrayIndex i = 0; i < in.size(); i++) {
+    EXPECT_EQ(in[i], outj[i]);
+  }
+}
+
+TEST(JsonTest, BoolVectorToFromArray) {
+  std::vector<bool> in = VecOf3<bool>(false, true, false);
+  Json::Value out = BoolVectorToJsonArray(in);
+  EXPECT_EQ(in.size(), out.size());
+  for (Json::Value::ArrayIndex i = 0; i < in.size(); ++i) {
+    EXPECT_EQ(in[i], out[i].asBool());
+  }
+  Json::Value inj = JsonVecOf3<bool>(false, true, false);
+  EXPECT_EQ(inj, out);
+  std::vector<bool> outj;
+  EXPECT_TRUE(JsonArrayToBoolVector(inj, &outj));
+  for (Json::Value::ArrayIndex i = 0; i < in.size(); i++) {
+    EXPECT_EQ(in[i], outj[i]);
+  }
+}
+
+TEST(JsonTest, DoubleVectorToFromArray) {
+  std::vector<double> in = VecOf3<double>(1.0, 2.0, 3.0);
+  Json::Value out = DoubleVectorToJsonArray(in);
+  EXPECT_EQ(in.size(), out.size());
+  for (Json::Value::ArrayIndex i = 0; i < in.size(); ++i) {
+    EXPECT_EQ(in[i], out[i].asDouble());
+  }
+  Json::Value inj = JsonVecOf3<double>(1.0, 2.0, 3.0);
+  EXPECT_EQ(inj, out);
+  std::vector<double> outj;
+  EXPECT_TRUE(JsonArrayToDoubleVector(inj, &outj));
+  for (Json::Value::ArrayIndex i = 0; i < in.size(); i++) {
+    EXPECT_EQ(in[i], outj[i]);
+  }
+}
diff --git a/talk/base/latebindingsymboltable.cc b/talk/base/latebindingsymboltable.cc
new file mode 100644
index 0000000..2226219
--- /dev/null
+++ b/talk/base/latebindingsymboltable.cc
@@ -0,0 +1,157 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/latebindingsymboltable.h"
+
+#ifdef POSIX
+#include <dlfcn.h>
+#endif
+
+#include "talk/base/logging.h"
+
+namespace talk_base {
+
+#ifdef POSIX
+static const DllHandle kInvalidDllHandle = NULL;
+#else
+#error Not implemented
+#endif
+
+static const char *GetDllError() {
+#ifdef POSIX
+  const char *err = dlerror();
+  if (err) {
+    return err;
+  } else {
+    return "No error";
+  }
+#else
+#error Not implemented
+#endif
+}
+
+static bool LoadSymbol(DllHandle handle,
+                       const char *symbol_name,
+                       void **symbol) {
+#ifdef POSIX
+  *symbol = dlsym(handle, symbol_name);
+  const char *err = dlerror();
+  if (err) {
+    LOG(LS_ERROR) << "Error loading symbol " << symbol_name << ": " << err;
+    return false;
+  } else if (!*symbol) {
+    // ELF allows for symbols to be NULL, but that should never happen for our
+    // usage.
+    LOG(LS_ERROR) << "Symbol " << symbol_name << " is NULL";
+    return false;
+  }
+  return true;
+#else
+#error Not implemented
+#endif
+}
+
+LateBindingSymbolTable::LateBindingSymbolTable(const TableInfo *info,
+    void **table)
+    : info_(info),
+      table_(table),
+      handle_(kInvalidDllHandle),
+      undefined_symbols_(false) {
+  ClearSymbols();
+}
+
+LateBindingSymbolTable::~LateBindingSymbolTable() {
+  Unload();
+}
+
+bool LateBindingSymbolTable::IsLoaded() const {
+  return handle_ != kInvalidDllHandle;
+}
+
+bool LateBindingSymbolTable::Load() {
+  ASSERT(info_->dll_name != NULL);
+  return LoadFromPath(info_->dll_name);
+}
+
+bool LateBindingSymbolTable::LoadFromPath(const char *dll_path) {
+  if (IsLoaded()) {
+    return true;
+  }
+  if (undefined_symbols_) {
+    // We do not attempt to load again because repeated attempts are not
+    // likely to succeed and DLL loading is costly.
+    LOG(LS_ERROR) << "We know there are undefined symbols";
+    return false;
+  }
+
+#ifdef POSIX
+  handle_ = dlopen(dll_path, RTLD_NOW);
+#else
+#error Not implemented
+#endif
+
+  if (handle_ == kInvalidDllHandle) {
+    LOG(LS_WARNING) << "Can't load " << dll_path << ": "
+                    << GetDllError();
+    return false;
+  }
+#ifdef POSIX
+  // Clear any old errors.
+  dlerror();
+#endif
+  for (int i = 0; i < info_->num_symbols; ++i) {
+    if (!LoadSymbol(handle_, info_->symbol_names[i], &table_[i])) {
+      undefined_symbols_ = true;
+      Unload();
+      return false;
+    }
+  }
+  return true;
+}
+
+void LateBindingSymbolTable::Unload() {
+  if (!IsLoaded()) {
+    return;
+  }
+
+#ifdef POSIX
+  if (dlclose(handle_) != 0) {
+    LOG(LS_ERROR) << GetDllError();
+  }
+#else
+#error Not implemented
+#endif
+
+  handle_ = kInvalidDllHandle;
+  ClearSymbols();
+}
+
+void LateBindingSymbolTable::ClearSymbols() {
+  memset(table_, 0, sizeof(void *) * info_->num_symbols);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/latebindingsymboltable.cc.def b/talk/base/latebindingsymboltable.cc.def
new file mode 100644
index 0000000..1f84f30
--- /dev/null
+++ b/talk/base/latebindingsymboltable.cc.def
@@ -0,0 +1,85 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 is a supermacro
+// (see http://wanderinghorse.net/computing/papers/supermacros_cpp.html) to
+// expand a definition of a late-binding symbol table class.
+//
+// Arguments:
+// LATE_BINDING_SYMBOL_TABLE_CLASS_NAME: Name of the class to generate.
+// LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST: List of symbols to load from the DLL,
+//     as an X-Macro list (see http://www.drdobbs.com/blogs/cpp/228700289).
+// LATE_BINDING_SYMBOL_TABLE_DLL_NAME: String literal for the DLL file name to
+//     load.
+//
+// From a .cc file, include the header file containing your call to the .h.def
+// supermacro, and then call this supermacro (optionally from inside the
+// namespace for the class to generate, if any). Example:
+//
+// #include "myclassname.h"
+//
+// namespace foo {
+//
+// #define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME MY_CLASS_NAME
+// #define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST MY_SYMBOLS_LIST
+// #define LATE_BINDING_SYMBOL_TABLE_DLL_NAME "libdll.so.n"
+// #include "talk/base/latebindingsymboltable.cc.def"
+//
+// }
+
+#ifndef LATE_BINDING_SYMBOL_TABLE_CLASS_NAME
+#error You must define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME
+#endif
+
+#ifndef LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST
+#error You must define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST
+#endif
+
+#ifndef LATE_BINDING_SYMBOL_TABLE_DLL_NAME
+#error You must define LATE_BINDING_SYMBOL_TABLE_DLL_NAME
+#endif
+
+const ::talk_base::LateBindingSymbolTable::TableInfo
+    LATE_BINDING_SYMBOL_TABLE_CLASS_NAME::kTableInfo = {
+  LATE_BINDING_SYMBOL_TABLE_DLL_NAME,
+  SYMBOL_TABLE_SIZE,
+  (const char *const []){
+#define X(sym) \
+    #sym,
+LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST
+#undef X
+  },
+};
+
+LATE_BINDING_SYMBOL_TABLE_CLASS_NAME::LATE_BINDING_SYMBOL_TABLE_CLASS_NAME()
+    : ::talk_base::LateBindingSymbolTable(&kTableInfo, table_) {}
+
+LATE_BINDING_SYMBOL_TABLE_CLASS_NAME::~LATE_BINDING_SYMBOL_TABLE_CLASS_NAME() {}
+
+#undef LATE_BINDING_SYMBOL_TABLE_CLASS_NAME
+#undef LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST
+#undef LATE_BINDING_SYMBOL_TABLE_DLL_NAME
diff --git a/talk/base/latebindingsymboltable.h b/talk/base/latebindingsymboltable.h
new file mode 100644
index 0000000..a53648b
--- /dev/null
+++ b/talk/base/latebindingsymboltable.h
@@ -0,0 +1,83 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_LATEBINDINGSYMBOLTABLE_H_
+#define TALK_BASE_LATEBINDINGSYMBOLTABLE_H_
+
+#include <string.h>
+
+#include "talk/base/common.h"
+
+namespace talk_base {
+
+#ifdef POSIX
+typedef void *DllHandle;
+#else
+#error Not implemented for this platform
+#endif
+
+// This is the base class for "symbol table" classes to simplify the dynamic
+// loading of symbols from DLLs. Currently the implementation only supports
+// Linux and OS X, and pure C symbols (or extern "C" symbols that wrap C++
+// functions).  Sub-classes for specific DLLs are generated via the "supermacro"
+// files latebindingsymboltable.h.def and latebindingsymboltable.cc.def. See
+// talk/sound/pulseaudiosymboltable.(h|cc) for an example.
+class LateBindingSymbolTable {
+ public:
+  struct TableInfo {
+    const char *dll_name;
+    int num_symbols;
+    // Array of size num_symbols.
+    const char *const *symbol_names;
+  };
+
+  LateBindingSymbolTable(const TableInfo *info, void **table);
+  ~LateBindingSymbolTable();
+
+  bool IsLoaded() const;
+  // Loads the DLL and the symbol table. Returns true iff the DLL and symbol
+  // table loaded successfully.
+  bool Load();
+  // Like load, but allows overriding the dll path for when the dll path is
+  // dynamic.
+  bool LoadFromPath(const char *dll_path);
+  void Unload();
+
+ private:
+  void ClearSymbols();
+
+  const TableInfo *info_;
+  void **table_;
+  DllHandle handle_;
+  bool undefined_symbols_;
+
+  DISALLOW_COPY_AND_ASSIGN(LateBindingSymbolTable);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_LATEBINDINGSYMBOLTABLE_H_
diff --git a/talk/base/latebindingsymboltable.h.def b/talk/base/latebindingsymboltable.h.def
new file mode 100644
index 0000000..cd8c176
--- /dev/null
+++ b/talk/base/latebindingsymboltable.h.def
@@ -0,0 +1,99 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 is a supermacro
+// (see http://wanderinghorse.net/computing/papers/supermacros_cpp.html) to
+// expand a declaration of a late-binding symbol table class.
+//
+// Arguments:
+// LATE_BINDING_SYMBOL_TABLE_CLASS_NAME: Name of the class to generate.
+// LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST: List of symbols to load from the DLL,
+//     as an X-Macro list (see http://www.drdobbs.com/blogs/cpp/228700289).
+//
+// From a .h file, include the header(s) for the DLL to late-bind and the
+// latebindingsymboltable.h header, and then call this supermacro (optionally
+// from inside the namespace for the class to generate, if any). Example:
+//
+// #include <headerfordll.h>
+//
+// #include "talk/base/latebindingsymboltable.h"
+//
+// namespace foo {
+//
+// #define MY_CLASS_NAME DesiredClassName
+// #define MY_SYMBOLS_LIST X(acos) X(sin) X(tan)
+//
+// #define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME MY_CLASS_NAME
+// #define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST MY_SYMBOLS_LIST
+// #include "talk/base/latebindingsymboltable.h.def"
+//
+// }
+
+#ifndef TALK_BASE_LATEBINDINGSYMBOLTABLE_H_
+#error You must first include latebindingsymboltable.h
+#endif
+
+#ifndef LATE_BINDING_SYMBOL_TABLE_CLASS_NAME
+#error You must define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME
+#endif
+
+#ifndef LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST
+#error You must define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST
+#endif
+
+class LATE_BINDING_SYMBOL_TABLE_CLASS_NAME :
+    public ::talk_base::LateBindingSymbolTable {
+ public:
+  LATE_BINDING_SYMBOL_TABLE_CLASS_NAME();
+  ~LATE_BINDING_SYMBOL_TABLE_CLASS_NAME();
+
+#define X(sym) \
+  typeof(&::sym) sym() const { \
+    ASSERT(::talk_base::LateBindingSymbolTable::IsLoaded()); \
+    return reinterpret_cast<typeof(&::sym)>(table_[SYMBOL_TABLE_INDEX_##sym]); \
+  }
+LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST
+#undef X
+
+ private:
+  enum {
+#define X(sym) \
+    SYMBOL_TABLE_INDEX_##sym,
+LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST
+#undef X
+    SYMBOL_TABLE_SIZE
+  };
+
+  static const ::talk_base::LateBindingSymbolTable::TableInfo kTableInfo;
+
+  void *table_[SYMBOL_TABLE_SIZE];
+
+  DISALLOW_COPY_AND_ASSIGN(LATE_BINDING_SYMBOL_TABLE_CLASS_NAME);
+};
+
+#undef LATE_BINDING_SYMBOL_TABLE_CLASS_NAME
+#undef LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST
diff --git a/talk/base/latebindingsymboltable_unittest.cc b/talk/base/latebindingsymboltable_unittest.cc
new file mode 100644
index 0000000..58afdcd
--- /dev/null
+++ b/talk/base/latebindingsymboltable_unittest.cc
@@ -0,0 +1,72 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef LINUX
+#include <math.h>
+#endif
+
+#include "talk/base/gunit.h"
+#include "talk/base/latebindingsymboltable.h"
+
+namespace talk_base {
+
+#ifdef LINUX
+
+#define LIBM_SYMBOLS_CLASS_NAME LibmTestSymbolTable
+#define LIBM_SYMBOLS_LIST \
+  X(acos) \
+  X(sin) \
+  X(tan)
+
+#define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME LIBM_SYMBOLS_CLASS_NAME
+#define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST LIBM_SYMBOLS_LIST
+#include "talk/base/latebindingsymboltable.h.def"
+
+#define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME LIBM_SYMBOLS_CLASS_NAME
+#define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST LIBM_SYMBOLS_LIST
+#define LATE_BINDING_SYMBOL_TABLE_DLL_NAME "libm.so.6"
+#include "talk/base/latebindingsymboltable.cc.def"
+
+TEST(LateBindingSymbolTable, libm) {
+  LibmTestSymbolTable table;
+  EXPECT_FALSE(table.IsLoaded());
+  ASSERT_TRUE(table.Load());
+  EXPECT_TRUE(table.IsLoaded());
+  EXPECT_EQ(table.acos()(0.5), acos(0.5));
+  EXPECT_EQ(table.sin()(0.5), sin(0.5));
+  EXPECT_EQ(table.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..9c4be7f
--- /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.
+ */
+
+#ifdef HAVE_DBUS_GLIB
+
+#include "talk/base/libdbusglibsymboltable.h"
+
+namespace talk_base {
+
+#define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME LIBDBUS_GLIB_CLASS_NAME
+#define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST LIBDBUS_GLIB_SYMBOLS_LIST
+#define LATE_BINDING_SYMBOL_TABLE_DLL_NAME "libdbus-glib-1.so"
+#include "talk/base/latebindingsymboltable.cc.def"
+
+}  // namespace talk_base
+
+#endif  // HAVE_DBUS_GLIB
diff --git a/talk/base/libdbusglibsymboltable.h b/talk/base/libdbusglibsymboltable.h
new file mode 100644
index 0000000..8dc140f
--- /dev/null
+++ b/talk/base/libdbusglibsymboltable.h
@@ -0,0 +1,73 @@
+/*
+ * 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_
+
+#ifdef HAVE_DBUS_GLIB
+
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include "talk/base/latebindingsymboltable.h"
+
+namespace talk_base {
+
+#define LIBDBUS_GLIB_CLASS_NAME LibDBusGlibSymbolTable
+// 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)
+
+#define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME LIBDBUS_GLIB_CLASS_NAME
+#define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST LIBDBUS_GLIB_SYMBOLS_LIST
+#include "talk/base/latebindingsymboltable.h.def"
+
+}  // namespace talk_base
+
+#endif  // HAVE_DBUS_GLIB
+
+#endif  // TALK_BASE_LIBDBUSGLIBSYMBOLTABLE_H_
diff --git a/talk/base/linked_ptr.h b/talk/base/linked_ptr.h
new file mode 100644
index 0000000..a98a367
--- /dev/null
+++ b/talk/base/linked_ptr.h
@@ -0,0 +1,142 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * linked_ptr - simple reference linked pointer
+ * (like reference counting, just using a linked list of the references
+ * instead of their count.)
+ *
+ * The implementation stores three pointers for every linked_ptr, but
+ * does not allocate anything on the free store.
+ */
+
+#ifndef TALK_BASE_LINKED_PTR_H__
+#define TALK_BASE_LINKED_PTR_H__
+
+namespace talk_base {
+
+/* For ANSI-challenged compilers, you may want to #define
+ * NO_MEMBER_TEMPLATES, explicit or mutable */
+#define NO_MEMBER_TEMPLATES
+
+template <class X> class linked_ptr
+{
+public:
+
+#ifndef NO_MEMBER_TEMPLATES
+#   define TEMPLATE_FUNCTION template <class Y>
+    TEMPLATE_FUNCTION friend class linked_ptr<Y>;
+#else
+#   define TEMPLATE_FUNCTION
+    typedef X Y;
+#endif
+
+    typedef X element_type;
+
+    explicit linked_ptr(X* p = 0) throw()
+        : itsPtr(p) {itsPrev = itsNext = this;}
+    ~linked_ptr()
+        {release();}
+    linked_ptr(const linked_ptr& r) throw()
+        {acquire(r);}
+    linked_ptr& operator=(const linked_ptr& r)
+    {
+        if (this != &r) {
+            release();
+            acquire(r);
+        }
+        return *this;
+    }
+
+#ifndef NO_MEMBER_TEMPLATES
+    template <class Y> friend class linked_ptr<Y>;
+    template <class Y> linked_ptr(const linked_ptr<Y>& r) throw()
+        {acquire(r);}
+    template <class Y> linked_ptr& operator=(const linked_ptr<Y>& r)
+    {
+        if (this != &r) {
+            release();
+            acquire(r);
+        }
+        return *this;
+    }
+#endif // NO_MEMBER_TEMPLATES
+
+    X& operator*()  const throw()   {return *itsPtr;}
+    X* operator->() const throw()   {return itsPtr;}
+    X* get()        const throw()   {return itsPtr;}
+    bool unique()   const throw()   {return itsPrev ? itsPrev==this : true;}
+
+private:
+    X*                          itsPtr;
+    mutable const linked_ptr*   itsPrev;
+    mutable const linked_ptr*   itsNext;
+
+    void acquire(const linked_ptr& r) throw()
+    { // insert this to the list
+        itsPtr = r.itsPtr;
+        itsNext = r.itsNext;
+        itsNext->itsPrev = this;
+        itsPrev = &r;
+#ifndef mutable
+        r.itsNext = this;
+#else // for ANSI-challenged compilers
+        (const_cast<linked_ptr<X>*>(&r))->itsNext = this;
+#endif
+    }
+
+#ifndef NO_MEMBER_TEMPLATES
+    template <class Y> void acquire(const linked_ptr<Y>& r) throw()
+    { // insert this to the list
+        itsPtr = r.itsPtr;
+        itsNext = r.itsNext;
+        itsNext->itsPrev = this;
+        itsPrev = &r;
+#ifndef mutable
+        r.itsNext = this;
+#else // for ANSI-challenged compilers
+        (const_cast<linked_ptr<X>*>(&r))->itsNext = this;
+#endif
+    }
+#endif // NO_MEMBER_TEMPLATES
+
+    void release()
+    { // erase this from the list, delete if unique
+        if (unique()) delete itsPtr;
+        else {
+            itsPrev->itsNext = itsNext;
+            itsNext->itsPrev = itsPrev;
+            itsPrev = itsNext = 0;
+        }
+        itsPtr = 0;
+    }
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_LINKED_PTR_H__
+
diff --git a/talk/base/linux.cc b/talk/base/linux.cc
new file mode 100644
index 0000000..644ec45
--- /dev/null
+++ b/talk/base/linux.cc
@@ -0,0 +1,282 @@
+/*
+ * 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.
+ */
+
+#if defined(LINUX) || defined(ANDROID)
+#include "talk/base/linux.h"
+
+#include <ctype.h>
+
+#include <errno.h>
+#include <sys/utsname.h>
+#include <sys/wait.h>
+
+#include <cstdio>
+#include <set>
+
+#include "talk/base/stringencode.h"
+
+namespace talk_base {
+
+static const char kCpuInfoFile[] = "/proc/cpuinfo";
+static const char kCpuMaxFreqFile[] =
+    "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq";
+
+ProcCpuInfo::ProcCpuInfo() {
+}
+
+ProcCpuInfo::~ProcCpuInfo() {
+}
+
+bool ProcCpuInfo::LoadFromSystem() {
+  ConfigParser procfs;
+  if (!procfs.Open(kCpuInfoFile)) {
+    return false;
+  }
+  return procfs.Parse(&sections_);
+};
+
+bool ProcCpuInfo::GetSectionCount(size_t* count) {
+  if (sections_.empty()) {
+    return false;
+  }
+  if (count) {
+    *count = sections_.size();
+  }
+  return true;
+}
+
+bool ProcCpuInfo::GetNumCpus(int* num) {
+  if (sections_.empty()) {
+    return false;
+  }
+  int total_cpus = 0;
+#if defined(__arm__)
+  // Count the number of blocks that have a "processor" key defined. On ARM,
+  // there may be extra blocks of information that aren't per-processor.
+  size_t section_count = sections_.size();
+  for (size_t i = 0; i < section_count; ++i) {
+    int processor_id;
+    if (GetSectionIntValue(i, "processor", &processor_id)) {
+      ++total_cpus;
+    }
+  }
+  // Single core ARM systems don't include "processor" keys at all, so return
+  // that we have a single core if we didn't find any explicitly above.
+  if (total_cpus == 0) {
+    total_cpus = 1;
+  }
+#else
+  // On X86, there is exactly one info section per processor.
+  total_cpus = static_cast<int>(sections_.size());
+#endif
+  if (num) {
+    *num = total_cpus;
+  }
+  return true;
+}
+
+bool ProcCpuInfo::GetNumPhysicalCpus(int* num) {
+  if (sections_.empty()) {
+    return false;
+  }
+  // TODO: /proc/cpuinfo only reports cores that are currently
+  // _online_, so this may underreport the number of physical cores.
+#if defined(__arm__)
+  // ARM (currently) has no hyperthreading, so just return the same value
+  // as GetNumCpus.
+  return GetNumCpus(num);
+#else
+  int total_cores = 0;
+  std::set<int> physical_ids;
+  size_t section_count = sections_.size();
+  for (size_t i = 0; i < section_count; ++i) {
+    int physical_id;
+    int cores;
+    // Count the cores for the physical id only if we have not counted the id.
+    if (GetSectionIntValue(i, "physical id", &physical_id) &&
+        GetSectionIntValue(i, "cpu cores", &cores) &&
+        physical_ids.find(physical_id) == physical_ids.end()) {
+      physical_ids.insert(physical_id);
+      total_cores += cores;
+    }
+  }
+
+  if (num) {
+    *num = total_cores;
+  }
+  return true;
+#endif
+}
+
+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;
+  }
+  return true;
+}
+
+bool ProcCpuInfo::GetSectionStringValue(size_t section_num,
+                                        const std::string& key,
+                                        std::string* result) {
+  if (section_num >= sections_.size()) {
+    return false;
+  }
+  ConfigParser::SimpleMap::iterator iter = sections_[section_num].find(key);
+  if (iter == sections_[section_num].end()) {
+    return false;
+  }
+  *result = iter->second;
+  return true;
+}
+
+bool ProcCpuInfo::GetSectionIntValue(size_t section_num,
+                                     const std::string& key,
+                                     int* result) {
+  if (section_num >= sections_.size()) {
+    return false;
+  }
+  ConfigParser::SimpleMap::iterator iter = sections_[section_num].find(key);
+  if (iter == sections_[section_num].end()) {
+    return false;
+  }
+  return FromString(iter->second, result);
+}
+
+ConfigParser::ConfigParser() {}
+
+ConfigParser::~ConfigParser() {}
+
+bool ConfigParser::Open(const std::string& filename) {
+  FileStream* fs = new FileStream();
+  if (!fs->Open(filename, "r", NULL)) {
+    return false;
+  }
+  instream_.reset(fs);
+  return true;
+}
+
+void ConfigParser::Attach(StreamInterface* stream) {
+  instream_.reset(stream);
+}
+
+bool ConfigParser::Parse(MapVector* key_val_pairs) {
+  // Parses the file and places the found key-value pairs into key_val_pairs.
+  SimpleMap section;
+  while (ParseSection(&section)) {
+    key_val_pairs->push_back(section);
+    section.clear();
+  }
+  return (!key_val_pairs->empty());
+}
+
+bool ConfigParser::ParseSection(SimpleMap* key_val_pair) {
+  // Parses the next section in the filestream and places the found key-value
+  // pairs into key_val_pair.
+  std::string key, value;
+  while (ParseLine(&key, &value)) {
+    (*key_val_pair)[key] = value;
+  }
+  return (!key_val_pair->empty());
+}
+
+bool ConfigParser::ParseLine(std::string* key, std::string* value) {
+  // Parses the next line in the filestream and places the found key-value
+  // pair into key and val.
+  std::string line;
+  if ((instream_->ReadLine(&line)) == SR_EOS) {
+    return false;
+  }
+  std::vector<std::string> tokens;
+  if (2 != split(line, ':', &tokens)) {
+    return false;
+  }
+  // Removes whitespace at the end of Key name
+  size_t pos = tokens[0].length() - 1;
+  while ((pos > 0) && isspace(tokens[0][pos])) {
+    pos--;
+  }
+  tokens[0].erase(pos + 1);
+  // Removes whitespace at the start of value
+  pos = 0;
+  while (pos < tokens[1].length() && isspace(tokens[1][pos])) {
+    pos++;
+  }
+  tokens[1].erase(0, pos);
+  *key = tokens[0];
+  *value = tokens[1];
+  return true;
+}
+
+
+std::string ReadLinuxUname() {
+  struct utsname buf;
+  if (uname(&buf) < 0) {
+    LOG_ERR(LS_ERROR) << "Can't call uname()";
+    return std::string();
+  }
+  std::ostringstream sstr;
+  sstr << buf.sysname << " "
+       << buf.release << " "
+       << buf.version << " "
+       << buf.machine;
+  return sstr.str();
+}
+
+int ReadCpuMaxFreq() {
+  FileStream fs;
+  std::string str;
+  int freq = -1;
+  if (!fs.Open(kCpuMaxFreqFile, "r", NULL) ||
+      SR_SUCCESS != fs.ReadLine(&str) ||
+      !FromString(str, &freq)) {
+    return -1;
+  }
+  return freq;
+}
+
+}  // namespace talk_base
+
+#endif  // defined(LINUX) || defined(ANDROID)
diff --git a/talk/base/linux.h b/talk/base/linux.h
new file mode 100644
index 0000000..63e3021
--- /dev/null
+++ b/talk/base/linux.h
@@ -0,0 +1,135 @@
+/*
+ * 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_LINUX_H_
+#define TALK_BASE_LINUX_H_
+
+#if defined(LINUX) || defined(ANDROID)
+#include <string>
+#include <map>
+#include <vector>
+
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stream.h"
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////////////
+// ConfigParser parses a FileStream of an ".ini."-type format into a map.
+//////////////////////////////////////////////////////////////////////////////
+
+// Sample Usage:
+//   ConfigParser parser;
+//   ConfigParser::MapVector key_val_pairs;
+//   if (parser.Open(inifile) && parser.Parse(&key_val_pairs)) {
+//     for (int section_num=0; i < key_val_pairs.size(); ++section_num) {
+//       std::string val1 = key_val_pairs[section_num][key1];
+//       std::string val2 = key_val_pairs[section_num][key2];
+//       // Do something with valn;
+//     }
+//   }
+
+class ConfigParser {
+ public:
+  typedef std::map<std::string, std::string> SimpleMap;
+  typedef std::vector<SimpleMap> MapVector;
+
+  ConfigParser();
+  virtual ~ConfigParser();
+
+  virtual bool Open(const std::string& filename);
+  virtual void Attach(StreamInterface* stream);
+  virtual bool Parse(MapVector* key_val_pairs);
+  virtual bool ParseSection(SimpleMap* key_val_pair);
+  virtual bool ParseLine(std::string* key, std::string* value);
+
+ private:
+  scoped_ptr<StreamInterface> instream_;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+// ProcCpuInfo reads CPU info from the /proc subsystem on any *NIX platform.
+//////////////////////////////////////////////////////////////////////////////
+
+// Sample Usage:
+//   ProcCpuInfo proc_info;
+//   int no_of_cpu;
+//   if (proc_info.LoadFromSystem()) {
+//      std::string out_str;
+//      proc_info.GetNumCpus(&no_of_cpu);
+//      proc_info.GetCpuStringValue(0, "vendor_id", &out_str);
+//      }
+//   }
+
+class ProcCpuInfo {
+ public:
+  ProcCpuInfo();
+  virtual ~ProcCpuInfo();
+
+  // Reads the proc subsystem's cpu info into memory. If this fails, this
+  // returns false; if it succeeds, it returns true.
+  virtual bool LoadFromSystem();
+
+  // Obtains the number of logical CPU threads and places the value num.
+  virtual bool GetNumCpus(int* num);
+
+  // Obtains the number of physical CPU cores and places the value num.
+  virtual bool GetNumPhysicalCpus(int* num);
+
+  // Obtains the CPU family id.
+  virtual bool GetCpuFamily(int* id);
+
+  // Obtains the number of sections in /proc/cpuinfo, which may be greater
+  // than the number of CPUs (e.g. on ARM)
+  virtual bool GetSectionCount(size_t* count);
+
+  // Looks for the CPU proc item with the given name for the given section
+  // number and places the string value in result.
+  virtual bool GetSectionStringValue(size_t section_num, const std::string& key,
+                                     std::string* result);
+
+  // Looks for the CPU proc item with the given name for the given section
+  // number and places the int value in result.
+  virtual bool GetSectionIntValue(size_t section_num, const std::string& key,
+                                  int* result);
+
+ private:
+  ConfigParser::MapVector sections_;
+};
+
+// Returns the output of "uname".
+std::string ReadLinuxUname();
+
+// Returns the content (int) of
+// /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq
+// Returns -1 on error.
+int ReadCpuMaxFreq();
+
+}  // namespace talk_base
+
+#endif  // defined(LINUX) || defined(ANDROID)
+#endif  // TALK_BASE_LINUX_H_
diff --git a/talk/base/linux_unittest.cc b/talk/base/linux_unittest.cc
new file mode 100644
index 0000000..efc7f87
--- /dev/null
+++ b/talk/base/linux_unittest.cc
@@ -0,0 +1,113 @@
+/*
+ * 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 "talk/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(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..75e47d5
--- /dev/null
+++ b/talk/base/linuxwindowpicker.cc
@@ -0,0 +1,835 @@
+/*
+ * 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 <algorithm>
+#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 {
+
+// Convenience wrapper for XGetWindowProperty results.
+template <class PropertyType>
+class XWindowProperty {
+ public:
+  XWindowProperty(Display* display, Window window, Atom property)
+      : data_(NULL) {
+    const int kBitsPerByte = 8;
+    Atom actual_type;
+    int actual_format;
+    unsigned long bytes_after;  // NOLINT: type required by XGetWindowProperty
+    int status = XGetWindowProperty(display, window, property, 0L, ~0L, False,
+                                    AnyPropertyType, &actual_type,
+                                    &actual_format, &size_,
+                                    &bytes_after, &data_);
+    succeeded_ = (status == Success);
+    if (!succeeded_) {
+      data_ = NULL;  // Ensure nothing is freed.
+    } else if (sizeof(PropertyType) * kBitsPerByte != actual_format) {
+      LOG(LS_WARNING) << "Returned type size differs from "
+          "requested type size.";
+      succeeded_ = false;
+      // We still need to call XFree in this case, so leave data_ alone.
+    }
+    if (!succeeded_) {
+      size_ = 0;
+    }
+  }
+
+  ~XWindowProperty() {
+    if (data_) {
+      XFree(data_);
+    }
+  }
+
+  bool succeeded() const { return succeeded_; }
+  size_t size() const { return size_; }
+  const PropertyType* data() const {
+    return reinterpret_cast<PropertyType*>(data_);
+  }
+  PropertyType* data() {
+    return reinterpret_cast<PropertyType*>(data_);
+  }
+
+ private:
+  bool succeeded_;
+  unsigned long size_;  // NOLINT: type required by XGetWindowProperty
+  unsigned char* data_;
+
+  DISALLOW_COPY_AND_ASSIGN(XWindowProperty);
+};
+
+// 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_);
+    Window default_root_window = XDefaultRootWindow(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, "");
+      desc.set_primary(root_window == default_root_window);
+      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. Here we
+    // automatically turn it on for shareable windows so that we can get
+    // thumbnails. We used to avoid it because the transition is visually ugly,
+    // but recent window managers don't always redirect windows which led to
+    // no thumbnails at all, which is a worse experience.
+
+    // Redirect drawing to an offscreen buffer (ie, turn on compositing).
+    // X11 remembers what has requested this and will turn it off for us when
+    // we exit.
+    XCompositeRedirectWindow(display_, id.id(), CompositeRedirectAutomatic);
+    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);
+  }
+
+  bool GetDesktopDimensions(const DesktopId& id, int* width, int* height) {
+    if (!Init()) {
+      return false;
+    }
+    XErrorSuppressor error_suppressor(display_);
+    XWindowAttributes attr;
+    if (!XGetWindowAttributes(display_, id.id(), &attr)) {
+      LOG(LS_ERROR) << "XGetWindowAttributes() failed";
+      return false;
+    }
+    *width = attr.width;
+    *height = attr.height;
+    return true;
+  }
+
+ 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.
+#ifdef CHROMEOS
+      // TODO(jhorwich): Short-term fix for crbug.com/120229: Don't need to
+      // filter, just return all windows and let the picker scan through them.
+      Window app_window = children[num_children - 1 - i];
+#else
+      Window app_window = GetApplicationWindow(children[num_children - 1 - i]);
+#endif
+      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;
+  }
+
+  // First look for _NET_WM_WINDOW_TYPE. The standard
+  // (http://standards.freedesktop.org/wm-spec/latest/ar01s05.html#id2760306)
+  // says this hint *should* be present on all windows, and we use the existence
+  // of _NET_WM_WINDOW_TYPE_NORMAL in the property to indicate a window is not
+  // a desktop element (that is, only "normal" windows should be shareable).
+  Atom window_type_atom = XInternAtom(display, "_NET_WM_WINDOW_TYPE", True);
+  XWindowProperty<uint32_t> window_type(display, window, window_type_atom);
+  if (window_type.succeeded() && window_type.size() > 0) {
+    Atom normal_window_type_atom = XInternAtom(
+        display, "_NET_WM_WINDOW_TYPE_NORMAL", True);
+    uint32_t* end = window_type.data() + window_type.size();
+    bool is_normal = (end != std::find(
+        window_type.data(), end, normal_window_type_atom));
+    return !is_normal;
+  }
+
+  // Fall back on using the hint.
+  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);
+}
+
+bool LinuxWindowPicker::GetDesktopDimensions(const DesktopId& id, int* width,
+                                             int* height) {
+  return enumerator_->GetDesktopDimensions(id, width, height);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/linuxwindowpicker.h b/talk/base/linuxwindowpicker.h
new file mode 100644
index 0000000..8e45d8f
--- /dev/null
+++ b/talk/base/linuxwindowpicker.h
@@ -0,0 +1,68 @@
+/*
+ * 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);
+  virtual bool GetDesktopDimensions(const DesktopId& id, int* width,
+                                    int* height);
+  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..5ea9c93
--- /dev/null
+++ b/talk/base/linuxwindowpicker_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 "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);
+}
+
+TEST(LinuxWindowPickerTest, TestGetDesktopList) {
+  LinuxWindowPicker window_picker;
+  DesktopDescriptionList descriptions;
+  EXPECT_TRUE(window_picker.Init());
+  EXPECT_TRUE(window_picker.GetDesktopList(&descriptions));
+  EXPECT_TRUE(descriptions.size() > 0);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/logging.cc b/talk/base/logging.cc
new file mode 100644
index 0000000..6653d34
--- /dev/null
+++ b/talk/base/logging.cc
@@ -0,0 +1,635 @@
+/*
+ * 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 WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#define snprintf _snprintf
+#undef ERROR  // wingdi.h
+#endif
+
+#ifdef OSX
+#include <CoreServices/CoreServices.h>
+#elif defined(ANDROID)
+#include <android/log.h>
+static const char kLibjingle[] = "libjingle";
+// Android has a 1024 limit on log inputs. We use 60 chars as an
+// approx for the header/tag portion.
+// See android/system/core/liblog/logd_write.c
+static const int kMaxLogLineSize = 1024 - 60;
+#endif  // OSX || ANDROID
+
+#include <time.h>
+
+#include <ostream>
+#include <iomanip>
+#include <limits.h>
+#include <vector>
+
+#include "talk/base/logging.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/timeutils.h"
+
+namespace talk_base {
+
+/////////////////////////////////////////////////////////////////////////////
+// Constant Labels
+/////////////////////////////////////////////////////////////////////////////
+
+const char * FindLabel(int value, const ConstantLabel entries[]) {
+  for (int i = 0; entries[i].label; ++i) {
+    if (value == entries[i].value) {
+      return entries[i].label;
+    }
+  }
+  return 0;
+}
+
+std::string ErrorName(int err, const ConstantLabel * err_table) {
+  if (err == 0)
+    return "No error";
+
+  if (err_table != 0) {
+    if (const char * value = FindLabel(err, err_table))
+      return value;
+  }
+
+  char buffer[16];
+  snprintf(buffer, sizeof(buffer), "0x%08x", err);
+  return buffer;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// LogMessage
+/////////////////////////////////////////////////////////////////////////////
+
+const int LogMessage::NO_LOGGING = LS_ERROR + 1;
+
+#if _DEBUG
+static const int LOG_DEFAULT = LS_INFO;
+#else  // !_DEBUG
+static const int LOG_DEFAULT = LogMessage::NO_LOGGING;
+#endif  // !_DEBUG
+
+// Global lock for log subsystem, only needed to serialize access to streams_.
+CriticalSection LogMessage::crit_;
+
+// By default, release builds don't log, debug builds at info level
+int LogMessage::min_sev_ = LOG_DEFAULT;
+int LogMessage::dbg_sev_ = LOG_DEFAULT;
+
+// Don't bother printing context for the ubiquitous INFO log messages
+int LogMessage::ctx_sev_ = LS_WARNING;
+
+// The list of logging streams currently configured.
+// Note: we explicitly do not clean this up, because of the uncertain ordering
+// of destructors at program exit.  Let the person who sets the stream trigger
+// cleanup by setting to NULL, or let it leak (safe at program exit).
+LogMessage::StreamList LogMessage::streams_;
+
+// Boolean options default to false (0)
+bool LogMessage::thread_, LogMessage::timestamp_;
+
+// If we're in diagnostic mode, we'll be explicitly set that way; default=false.
+bool LogMessage::is_diagnostic_mode_ = false;
+
+LogMessage::LogMessage(const char* file, int line, LoggingSeverity sev,
+                       LogErrorContext err_ctx, int err, const char* module)
+    : severity_(sev),
+      warn_slow_logs_delay_(WARN_SLOW_LOGS_DELAY) {
+  // Android's logging facility keeps track of timestamp and thread.
+#ifndef ANDROID
+  if (timestamp_) {
+    uint32 time = TimeSince(LogStartTime());
+    // Also ensure WallClockStartTime is initialized, so that it matches
+    // LogStartTime.
+    WallClockStartTime();
+    print_stream_ << "[" << std::setfill('0') << std::setw(3) << (time / 1000)
+                  << ":" << std::setw(3) << (time % 1000) << std::setfill(' ')
+                  << "] ";
+  }
+
+  if (thread_) {
+#ifdef WIN32
+    DWORD id = GetCurrentThreadId();
+    print_stream_ << "[" << std::hex << id << std::dec << "] ";
+#endif  // WIN32
+  }
+#endif  // !ANDROID
+
+  if (severity_ >= ctx_sev_) {
+    print_stream_ << Describe(sev) << "(" << DescribeFile(file)
+                  << ":" << line << "): ";
+  }
+
+  if (err_ctx != ERRCTX_NONE) {
+    std::ostringstream tmp;
+    tmp << "[0x" << std::setfill('0') << std::hex << std::setw(8) << err << "]";
+    switch (err_ctx) {
+      case ERRCTX_ERRNO:
+        tmp << " " << strerror(err);
+        break;
+#if WIN32
+      case ERRCTX_HRESULT: {
+        char msgbuf[256];
+        DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM;
+        HMODULE hmod = GetModuleHandleA(module);
+        if (hmod)
+          flags |= FORMAT_MESSAGE_FROM_HMODULE;
+        if (DWORD len = FormatMessageA(
+            flags, hmod, err,
+            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+            msgbuf, sizeof(msgbuf) / sizeof(msgbuf[0]), NULL)) {
+          while ((len > 0) &&
+              isspace(static_cast<unsigned char>(msgbuf[len-1]))) {
+            msgbuf[--len] = 0;
+          }
+          tmp << " " << msgbuf;
+        }
+        break;
+      }
+#endif  // WIN32
+#if OSX
+      case ERRCTX_OSSTATUS: {
+        tmp << " " << nonnull(GetMacOSStatusErrorString(err), "Unknown error");
+        if (const char* desc = GetMacOSStatusCommentString(err)) {
+          tmp << ": " << desc;
+        }
+        break;
+      }
+#endif  // OSX
+      default:
+        break;
+    }
+    extra_ = tmp.str();
+  }
+}
+
+LogMessage::~LogMessage() {
+  if (!extra_.empty())
+    print_stream_ << " : " << extra_;
+  print_stream_ << std::endl;
+
+  const std::string& str = print_stream_.str();
+  if (severity_ >= dbg_sev_) {
+    OutputToDebug(str, severity_);
+  }
+
+  uint32 before = Time();
+  // Must lock streams_ before accessing
+  CritScope cs(&crit_);
+  for (StreamList::iterator it = streams_.begin(); it != streams_.end(); ++it) {
+    if (severity_ >= it->second) {
+      OutputToStream(it->first, str);
+    }
+  }
+  uint32 delay = TimeSince(before);
+  if (delay >= warn_slow_logs_delay_) {
+    LogMessage slow_log_warning =
+        talk_base::LogMessage(__FILE__, __LINE__, LS_WARNING);
+    // If our warning is slow, we don't want to warn about it, because
+    // that would lead to inifinite recursion.  So, give a really big
+    // number for the delay threshold.
+    slow_log_warning.warn_slow_logs_delay_ = UINT_MAX;
+    slow_log_warning.stream() << "Slow log: took " << delay << "ms to write "
+                              << str.size() << " bytes.";
+  }
+}
+
+uint32 LogMessage::LogStartTime() {
+  static const uint32 g_start = Time();
+  return g_start;
+}
+
+uint32 LogMessage::WallClockStartTime() {
+  static const uint32 g_start_wallclock = time(NULL);
+  return g_start_wallclock;
+}
+
+void LogMessage::LogContext(int min_sev) {
+  ctx_sev_ = min_sev;
+}
+
+void LogMessage::LogThreads(bool on) {
+  thread_ = on;
+}
+
+void LogMessage::LogTimestamps(bool on) {
+  timestamp_ = on;
+}
+
+void LogMessage::LogToDebug(int min_sev) {
+  dbg_sev_ = min_sev;
+  UpdateMinLogSeverity();
+}
+
+void LogMessage::LogToStream(StreamInterface* stream, int min_sev) {
+  CritScope cs(&crit_);
+  // Discard and delete all previously installed streams
+  for (StreamList::iterator it = streams_.begin(); it != streams_.end(); ++it) {
+    delete it->first;
+  }
+  streams_.clear();
+  // Install the new stream, if specified
+  if (stream) {
+    AddLogToStream(stream, min_sev);
+  }
+}
+
+int LogMessage::GetLogToStream(StreamInterface* stream) {
+  CritScope cs(&crit_);
+  int sev = NO_LOGGING;
+  for (StreamList::iterator it = streams_.begin(); it != streams_.end(); ++it) {
+    if (!stream || stream == it->first) {
+      sev = _min(sev, it->second);
+    }
+  }
+  return sev;
+}
+
+void LogMessage::AddLogToStream(StreamInterface* stream, int min_sev) {
+  CritScope cs(&crit_);
+  streams_.push_back(std::make_pair(stream, min_sev));
+  UpdateMinLogSeverity();
+}
+
+void LogMessage::RemoveLogToStream(StreamInterface* stream) {
+  CritScope cs(&crit_);
+  for (StreamList::iterator it = streams_.begin(); it != streams_.end(); ++it) {
+    if (stream == it->first) {
+      streams_.erase(it);
+      break;
+    }
+  }
+  UpdateMinLogSeverity();
+}
+
+void LogMessage::ConfigureLogging(const char* params, const char* filename) {
+  int current_level = LS_VERBOSE;
+  int debug_level = GetLogToDebug();
+  int file_level = GetLogToStream();
+
+  std::vector<std::string> tokens;
+  tokenize(params, ' ', &tokens);
+
+  for (size_t i = 0; i < tokens.size(); ++i) {
+    if (tokens[i].empty())
+      continue;
+
+    // Logging features
+    if (tokens[i] == "tstamp") {
+      LogTimestamps();
+    } else if (tokens[i] == "thread") {
+      LogThreads();
+
+    // Logging levels
+    } else if (tokens[i] == "sensitive") {
+      current_level = LS_SENSITIVE;
+    } else if (tokens[i] == "verbose") {
+      current_level = LS_VERBOSE;
+    } else if (tokens[i] == "info") {
+      current_level = LS_INFO;
+    } else if (tokens[i] == "warning") {
+      current_level = LS_WARNING;
+    } else if (tokens[i] == "error") {
+      current_level = LS_ERROR;
+    } else if (tokens[i] == "none") {
+      current_level = NO_LOGGING;
+
+    // Logging targets
+    } else if (tokens[i] == "file") {
+      file_level = current_level;
+    } else if (tokens[i] == "debug") {
+      debug_level = current_level;
+    }
+  }
+
+#ifdef WIN32
+  if ((NO_LOGGING != debug_level) && !::IsDebuggerPresent()) {
+    // First, attempt to attach to our parent's console... so if you invoke
+    // from the command line, we'll see the output there.  Otherwise, create
+    // our own console window.
+    // Note: These methods fail if a console already exists, which is fine.
+    bool success = false;
+    typedef BOOL (WINAPI* PFN_AttachConsole)(DWORD);
+    if (HINSTANCE kernel32 = ::LoadLibrary(L"kernel32.dll")) {
+      // AttachConsole is defined on WinXP+.
+      if (PFN_AttachConsole attach_console = reinterpret_cast<PFN_AttachConsole>
+            (::GetProcAddress(kernel32, "AttachConsole"))) {
+        success = (FALSE != attach_console(ATTACH_PARENT_PROCESS));
+      }
+      ::FreeLibrary(kernel32);
+    }
+    if (!success) {
+      ::AllocConsole();
+    }
+  }
+#endif  // WIN32
+
+  scoped_ptr<FileStream> stream;
+  if (NO_LOGGING != file_level) {
+    stream.reset(new FileStream);
+    if (!stream->Open(filename, "wb", NULL) || !stream->DisableBuffering()) {
+      stream.reset();
+    }
+  }
+
+  LogToDebug(debug_level);
+  LogToStream(stream.release(), file_level);
+}
+
+int LogMessage::ParseLogSeverity(const std::string& value) {
+  int level = NO_LOGGING;
+  if (value == "LS_SENSITIVE") {
+    level = LS_SENSITIVE;
+  } else if (value == "LS_VERBOSE") {
+    level = LS_VERBOSE;
+  } else if (value == "LS_INFO") {
+    level = LS_INFO;
+  } else if (value == "LS_WARNING") {
+    level = LS_WARNING;
+  } else if (value == "LS_ERROR") {
+    level = LS_ERROR;
+  } else if (isdigit(value[0])) {
+    level = atoi(value.c_str());  // NOLINT
+  }
+  return level;
+}
+
+void LogMessage::UpdateMinLogSeverity() {
+  int min_sev = dbg_sev_;
+  for (StreamList::iterator it = streams_.begin(); it != streams_.end(); ++it) {
+    min_sev = _min(dbg_sev_, it->second);
+  }
+  min_sev_ = min_sev;
+}
+
+const char* LogMessage::Describe(LoggingSeverity sev) {
+  switch (sev) {
+  case LS_SENSITIVE: return "Sensitive";
+  case LS_VERBOSE:   return "Verbose";
+  case LS_INFO:      return "Info";
+  case LS_WARNING:   return "Warning";
+  case LS_ERROR:     return "Error";
+  default:           return "<unknown>";
+  }
+}
+
+const char* LogMessage::DescribeFile(const char* file) {
+  const char* end1 = ::strrchr(file, '/');
+  const char* end2 = ::strrchr(file, '\\');
+  if (!end1 && !end2)
+    return file;
+  else
+    return (end1 > end2) ? end1 + 1 : end2 + 1;
+}
+
+void LogMessage::OutputToDebug(const std::string& str,
+                               LoggingSeverity severity) {
+  bool log_to_stderr = true;
+#if defined(OSX) && (!defined(DEBUG) || defined(NDEBUG))
+  // On the Mac, all stderr output goes to the Console log and causes clutter.
+  // So in opt builds, don't log to stderr unless the user specifically sets
+  // a preference to do so.
+  CFStringRef key = CFStringCreateWithCString(kCFAllocatorDefault,
+                                              "logToStdErr",
+                                              kCFStringEncodingUTF8);
+  CFStringRef domain = CFBundleGetIdentifier(CFBundleGetMainBundle());
+  if (key != NULL && domain != NULL) {
+    Boolean exists_and_is_valid;
+    Boolean should_log =
+        CFPreferencesGetAppBooleanValue(key, domain, &exists_and_is_valid);
+    // If the key doesn't exist or is invalid or is false, we will not log to
+    // stderr.
+    log_to_stderr = exists_and_is_valid && should_log;
+  }
+  if (key != NULL) {
+    CFRelease(key);
+  }
+#endif
+#ifdef WIN32
+  // Always log to the debugger.
+  // Perhaps stderr should be controlled by a preference, as on Mac?
+  OutputDebugStringA(str.c_str());
+  if (log_to_stderr) {
+    // This handles dynamically allocated consoles, too.
+    if (HANDLE error_handle = ::GetStdHandle(STD_ERROR_HANDLE)) {
+      log_to_stderr = false;
+      DWORD written = 0;
+      ::WriteFile(error_handle, str.data(), static_cast<DWORD>(str.size()),
+                  &written, 0);
+    }
+  }
+#endif  // WIN32
+#ifdef ANDROID
+  // Android's logging facility uses severity to log messages but we
+  // need to map libjingle's severity levels to Android ones first.
+  // Also write to stderr which maybe available to executable started
+  // from the shell.
+  int prio;
+  switch (severity) {
+    case LS_SENSITIVE:
+      __android_log_write(ANDROID_LOG_INFO, kLibjingle, "SENSITIVE");
+      if (log_to_stderr) {
+        fprintf(stderr, "SENSITIVE");
+        fflush(stderr);
+      }
+      return;
+    case LS_VERBOSE:
+      prio = ANDROID_LOG_VERBOSE;
+      break;
+    case LS_INFO:
+      prio = ANDROID_LOG_INFO;
+      break;
+    case LS_WARNING:
+      prio = ANDROID_LOG_WARN;
+      break;
+    case LS_ERROR:
+      prio = ANDROID_LOG_ERROR;
+      break;
+    default:
+      prio = ANDROID_LOG_UNKNOWN;
+  }
+
+  int size = str.size();
+  int line = 0;
+  int idx = 0;
+  const int max_lines = size / kMaxLogLineSize + 1;
+  if (max_lines == 1) {
+    __android_log_print(prio, kLibjingle, "%.*s", size, str.c_str());
+  } else {
+    while (size > 0) {
+      const int len = std::min(size, kMaxLogLineSize);
+      // Use the size of the string in the format (str may have \0 in the
+      // middle).
+      __android_log_print(prio, kLibjingle, "[%d/%d] %.*s",
+                          line + 1, max_lines,
+                          len, str.c_str() + idx);
+      idx += len;
+      size -= len;
+      ++line;
+    }
+  }
+#endif  // ANDROID
+  if (log_to_stderr) {
+    fprintf(stderr, "%s", str.c_str());
+    fflush(stderr);
+  }
+}
+
+void LogMessage::OutputToStream(StreamInterface* stream,
+                                const std::string& str) {
+  // If write isn't fully successful, what are we going to do, log it? :)
+  stream->WriteAll(str.data(), str.size(), NULL, NULL);
+}
+
+//////////////////////////////////////////////////////////////////////
+// Logging Helpers
+//////////////////////////////////////////////////////////////////////
+
+void LogMultiline(LoggingSeverity level, const char* label, bool input,
+                  const void* data, size_t len, bool hex_mode,
+                  LogMultilineState* state) {
+  if (!LOG_CHECK_LEVEL_V(level))
+    return;
+
+  const char * direction = (input ? " << " : " >> ");
+
+  // NULL data means to flush our count of unprintable characters.
+  if (!data) {
+    if (state && state->unprintable_count_[input]) {
+      LOG_V(level) << label << direction << "## "
+                   << state->unprintable_count_[input]
+                   << " consecutive unprintable ##";
+      state->unprintable_count_[input] = 0;
+    }
+    return;
+  }
+
+  // The ctype classification functions want unsigned chars.
+  const unsigned char* udata = static_cast<const unsigned char*>(data);
+
+  if (hex_mode) {
+    const size_t LINE_SIZE = 24;
+    char hex_line[LINE_SIZE * 9 / 4 + 2], asc_line[LINE_SIZE + 1];
+    while (len > 0) {
+      memset(asc_line, ' ', sizeof(asc_line));
+      memset(hex_line, ' ', sizeof(hex_line));
+      size_t line_len = _min(len, LINE_SIZE);
+      for (size_t i = 0; i < line_len; ++i) {
+        unsigned char ch = udata[i];
+        asc_line[i] = isprint(ch) ? ch : '.';
+        hex_line[i*2 + i/4] = hex_encode(ch >> 4);
+        hex_line[i*2 + i/4 + 1] = hex_encode(ch & 0xf);
+      }
+      asc_line[sizeof(asc_line)-1] = 0;
+      hex_line[sizeof(hex_line)-1] = 0;
+      LOG_V(level) << label << direction
+                   << asc_line << " " << hex_line << " ";
+      udata += line_len;
+      len -= line_len;
+    }
+    return;
+  }
+
+  size_t consecutive_unprintable = state ? state->unprintable_count_[input] : 0;
+
+  const unsigned char* end = udata + len;
+  while (udata < end) {
+    const unsigned char* line = udata;
+    const unsigned char* end_of_line = strchrn<unsigned char>(udata,
+                                                              end - udata,
+                                                              '\n');
+    if (!end_of_line) {
+      udata = end_of_line = end;
+    } else {
+      udata = end_of_line + 1;
+    }
+
+    bool is_printable = true;
+
+    // If we are in unprintable mode, we need to see a line of at least
+    // kMinPrintableLine characters before we'll switch back.
+    const ptrdiff_t kMinPrintableLine = 4;
+    if (consecutive_unprintable && ((end_of_line - line) < kMinPrintableLine)) {
+      is_printable = false;
+    } else {
+      // Determine if the line contains only whitespace and printable
+      // characters.
+      bool is_entirely_whitespace = true;
+      for (const unsigned char* pos = line; pos < end_of_line; ++pos) {
+        if (isspace(*pos))
+          continue;
+        is_entirely_whitespace = false;
+        if (!isprint(*pos)) {
+          is_printable = false;
+          break;
+        }
+      }
+      // Treat an empty line following unprintable data as unprintable.
+      if (consecutive_unprintable && is_entirely_whitespace) {
+        is_printable = false;
+      }
+    }
+    if (!is_printable) {
+      consecutive_unprintable += (udata - line);
+      continue;
+    }
+    // Print out the current line, but prefix with a count of prior unprintable
+    // characters.
+    if (consecutive_unprintable) {
+      LOG_V(level) << label << direction << "## " << consecutive_unprintable
+                  << " consecutive unprintable ##";
+      consecutive_unprintable = 0;
+    }
+    // Strip off trailing whitespace.
+    while ((end_of_line > line) && isspace(*(end_of_line-1))) {
+      --end_of_line;
+    }
+    // Filter out any private data
+    std::string substr(reinterpret_cast<const char*>(line), end_of_line - line);
+    std::string::size_type pos_private = substr.find("Email");
+    if (pos_private == std::string::npos) {
+      pos_private = substr.find("Passwd");
+    }
+    if (pos_private == std::string::npos) {
+      LOG_V(level) << label << direction << substr;
+    } else {
+      LOG_V(level) << label << direction << "## omitted for privacy ##";
+    }
+  }
+
+  if (state) {
+    state->unprintable_count_[input] = consecutive_unprintable;
+  }
+}
+
+//////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
diff --git a/talk/base/logging.h b/talk/base/logging.h
new file mode 100644
index 0000000..2f341fa
--- /dev/null
+++ b/talk/base/logging.h
@@ -0,0 +1,389 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+//   LOG(...) an ostream target that can be used to send formatted
+// output to a variety of logging targets, such as debugger console, stderr,
+// file, or any StreamInterface.
+//   The severity level passed as the first argument to the LOGging
+// functions is used as a filter, to limit the verbosity of the logging.
+//   Static members of LogMessage documented below are used to control the
+// verbosity and target of the output.
+//   There are several variations on the LOG macro which facilitate logging
+// of common error conditions, detailed below.
+
+// LOG(sev) logs the given stream at severity "sev", which must be a
+//     compile-time constant of the LoggingSeverity type, without the namespace
+//     prefix.
+// LOG_V(sev) Like LOG(), but sev is a run-time variable of the LoggingSeverity
+//     type (basically, it just doesn't prepend the namespace).
+// LOG_F(sev) Like LOG(), but includes the name of the current function.
+// LOG_GLE(M)(sev [, mod]) attempt to add a string description of the
+//     HRESULT returned by GetLastError.  The "M" variant allows searching of a
+//     DLL's string table for the error description.
+// LOG_ERRNO(sev) attempts to add a string description of an errno-derived
+//     error. errno and associated facilities exist on both Windows and POSIX,
+//     but on Windows they only apply to the C/C++ runtime.
+// LOG_ERR(sev) is an alias for the platform's normal error system, i.e. _GLE on
+//     Windows and _ERRNO on POSIX.
+// (The above three also all have _EX versions that let you specify the error
+// code, rather than using the last one.)
+// LOG_E(sev, ctx, err, ...) logs a detailed error interpreted using the
+//     specified context.
+// LOG_CHECK_LEVEL(sev) (and LOG_CHECK_LEVEL_V(sev)) can be used as a test
+//     before performing expensive or sensitive operations whose sole purpose is
+//     to output logging data at the desired level.
+// Lastly, PLOG(sev, err) is an alias for LOG_ERR_EX.
+
+#ifndef TALK_BASE_LOGGING_H_
+#define TALK_BASE_LOGGING_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"  // NOLINT
+#endif
+
+#include <list>
+#include <sstream>
+#include <string>
+#include <utility>
+#include "talk/base/basictypes.h"
+#include "talk/base/criticalsection.h"
+
+namespace talk_base {
+
+class StreamInterface;
+
+///////////////////////////////////////////////////////////////////////////////
+// ConstantLabel can be used to easily generate string names from constant
+// values.  This can be useful for logging descriptive names of error messages.
+// Usage:
+//   const ConstantLabel LIBRARY_ERRORS[] = {
+//     KLABEL(SOME_ERROR),
+//     KLABEL(SOME_OTHER_ERROR),
+//     ...
+//     LASTLABEL
+//   }
+//
+//   int err = LibraryFunc();
+//   LOG(LS_ERROR) << "LibraryFunc returned: "
+//                 << ErrorName(err, LIBRARY_ERRORS);
+
+struct ConstantLabel { int value; const char * label; };
+#define KLABEL(x) { x, #x }
+#define TLABEL(x, y) { x, y }
+#define LASTLABEL { 0, 0 }
+
+const char * FindLabel(int value, const ConstantLabel entries[]);
+std::string ErrorName(int err, const ConstantLabel* err_table);
+
+//////////////////////////////////////////////////////////////////////
+
+// Note that the non-standard LoggingSeverity aliases exist because they are
+// still in broad use.  The meanings of the levels are:
+//  LS_SENSITIVE: Information which should only be logged with the consent
+//   of the user, due to privacy concerns.
+//  LS_VERBOSE: This level is for data which we do not want to appear in the
+//   normal debug log, but should appear in diagnostic logs.
+//  LS_INFO: Chatty level used in debugging for all sorts of things, the default
+//   in debug builds.
+//  LS_WARNING: Something that may warrant investigation.
+//  LS_ERROR: Something that should not have occurred.
+enum LoggingSeverity { LS_SENSITIVE, LS_VERBOSE, LS_INFO, LS_WARNING, LS_ERROR,
+                       INFO = LS_INFO,
+                       WARNING = LS_WARNING,
+                       LERROR = LS_ERROR };
+
+// LogErrorContext assists in interpreting the meaning of an error value.
+enum LogErrorContext {
+  ERRCTX_NONE,
+  ERRCTX_ERRNO,     // System-local errno
+  ERRCTX_HRESULT,   // Windows HRESULT
+  ERRCTX_OSSTATUS,  // MacOS OSStatus
+
+  // Abbreviations for LOG_E macro
+  ERRCTX_EN = ERRCTX_ERRNO,     // LOG_E(sev, EN, x)
+  ERRCTX_HR = ERRCTX_HRESULT,   // LOG_E(sev, HR, x)
+  ERRCTX_OS = ERRCTX_OSSTATUS,  // LOG_E(sev, OS, x)
+};
+
+class LogMessage {
+ public:
+  static const int NO_LOGGING;
+  static const uint32 WARN_SLOW_LOGS_DELAY = 50;  // ms
+
+  LogMessage(const char* file, int line, LoggingSeverity sev,
+             LogErrorContext err_ctx = ERRCTX_NONE, int err = 0,
+             const char* module = NULL);
+  ~LogMessage();
+
+  static inline bool Loggable(LoggingSeverity sev) { return (sev >= min_sev_); }
+  std::ostream& stream() { return print_stream_; }
+
+  // Returns the time at which this function was called for the first time.
+  // The time will be used as the logging start time.
+  // If this is not called externally, the LogMessage ctor also calls it, in
+  // which case the logging start time will be the time of the first LogMessage
+  // instance is created.
+  static uint32 LogStartTime();
+
+  // Returns the wall clock equivalent of |LogStartTime|, in seconds from the
+  // epoch.
+  static uint32 WallClockStartTime();
+
+  // These are attributes which apply to all logging channels
+  //  LogContext: Display the file and line number of the message
+  static void LogContext(int min_sev);
+  //  LogThreads: Display the thread identifier of the current thread
+  static void LogThreads(bool on = true);
+  //  LogTimestamps: Display the elapsed time of the program
+  static void LogTimestamps(bool on = true);
+
+  // These are the available logging channels
+  //  Debug: Debug console on Windows, otherwise stderr
+  static void LogToDebug(int min_sev);
+  static int GetLogToDebug() { return dbg_sev_; }
+
+  //  Stream: Any non-blocking stream interface.  LogMessage takes ownership of
+  //   the stream. Multiple streams may be specified by using AddLogToStream.
+  //   LogToStream is retained for backwards compatibility; when invoked, it
+  //   will discard any previously set streams and install the specified stream.
+  //   GetLogToStream gets the severity for the specified stream, of if none
+  //   is specified, the minimum stream severity.
+  //   RemoveLogToStream removes the specified stream, without destroying it.
+  static void LogToStream(StreamInterface* stream, int min_sev);
+  static int GetLogToStream(StreamInterface* stream = NULL);
+  static void AddLogToStream(StreamInterface* stream, int min_sev);
+  static void RemoveLogToStream(StreamInterface* stream);
+
+  // Testing against MinLogSeverity allows code to avoid potentially expensive
+  // logging operations by pre-checking the logging level.
+  static int GetMinLogSeverity() { return min_sev_; }
+
+  static void SetDiagnosticMode(bool f) { is_diagnostic_mode_ = f; }
+  static bool IsDiagnosticMode() { return is_diagnostic_mode_; }
+
+  // Parses the provided parameter stream to configure the options above.
+  // Useful for configuring logging from the command line.  If file logging
+  // is enabled, it is output to the specified filename.
+  static void ConfigureLogging(const char* params, const char* filename);
+
+  // Convert the string to a LS_ value; also accept numeric values.
+  static int ParseLogSeverity(const std::string& value);
+
+ private:
+  typedef std::list<std::pair<StreamInterface*, int> > StreamList;
+
+  // Updates min_sev_ appropriately when debug sinks change.
+  static void UpdateMinLogSeverity();
+
+  // These assist in formatting some parts of the debug output.
+  static const char* Describe(LoggingSeverity sev);
+  static const char* DescribeFile(const char* file);
+
+  // These write out the actual log messages.
+  static void OutputToDebug(const std::string& msg, LoggingSeverity severity_);
+  static void OutputToStream(StreamInterface* stream, const std::string& msg);
+
+  // The ostream that buffers the formatted message before output
+  std::ostringstream print_stream_;
+
+  // The severity level of this message
+  LoggingSeverity severity_;
+
+  // String data generated in the constructor, that should be appended to
+  // the message before output.
+  std::string extra_;
+
+  // If time it takes to write to stream is more than this, log one
+  // additional warning about it.
+  uint32 warn_slow_logs_delay_;
+
+  // Global lock for the logging subsystem
+  static CriticalSection crit_;
+
+  // dbg_sev_ is the thresholds for those output targets
+  // min_sev_ is the minimum (most verbose) of those levels, and is used
+  //  as a short-circuit in the logging macros to identify messages that won't
+  //  be logged.
+  // ctx_sev_ is the minimum level at which file context is displayed
+  static int min_sev_, dbg_sev_, ctx_sev_;
+
+  // The output streams and their associated severities
+  static StreamList streams_;
+
+  // Flags for formatting options
+  static bool thread_, timestamp_;
+
+  // are we in diagnostic mode (as defined by the app)?
+  static bool is_diagnostic_mode_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(LogMessage);
+};
+
+//////////////////////////////////////////////////////////////////////
+// Logging Helpers
+//////////////////////////////////////////////////////////////////////
+
+class LogMultilineState {
+ public:
+  size_t unprintable_count_[2];
+  LogMultilineState() {
+    unprintable_count_[0] = unprintable_count_[1] = 0;
+  }
+};
+
+// When possible, pass optional state variable to track various data across
+// multiple calls to LogMultiline.  Otherwise, pass NULL.
+void LogMultiline(LoggingSeverity level, const char* label, bool input,
+                  const void* data, size_t len, bool hex_mode,
+                  LogMultilineState* state);
+
+//////////////////////////////////////////////////////////////////////
+// Macros which automatically disable logging when LOGGING == 0
+//////////////////////////////////////////////////////////////////////
+
+// If LOGGING is not explicitly defined, default to enabled in debug mode
+#if !defined(LOGGING)
+#if defined(_DEBUG) && !defined(NDEBUG)
+#define LOGGING 1
+#else
+#define LOGGING 0
+#endif
+#endif  // !defined(LOGGING)
+
+#ifndef LOG
+#if LOGGING
+
+// The following non-obvious technique for implementation of a
+// conditional log stream was stolen from google3/base/logging.h.
+
+// This class is used to explicitly ignore values in the conditional
+// logging macros.  This avoids compiler warnings like "value computed
+// is not used" and "statement has no effect".
+
+class LogMessageVoidify {
+ public:
+  LogMessageVoidify() { }
+  // This has to be an operator with a precedence lower than << but
+  // higher than ?:
+  void operator&(std::ostream&) { }
+};
+
+#define LOG_SEVERITY_PRECONDITION(sev) \
+  !(talk_base::LogMessage::Loggable(sev)) \
+    ? (void) 0 \
+    : talk_base::LogMessageVoidify() &
+
+#define LOG(sev) \
+  LOG_SEVERITY_PRECONDITION(talk_base::sev) \
+    talk_base::LogMessage(__FILE__, __LINE__, talk_base::sev).stream()
+
+// The _V version is for when a variable is passed in.  It doesn't do the
+// namespace concatination.
+#define LOG_V(sev) \
+  LOG_SEVERITY_PRECONDITION(sev) \
+    talk_base::LogMessage(__FILE__, __LINE__, sev).stream()
+
+// The _F version prefixes the message with the current function name.
+#if (defined(__GNUC__) && defined(_DEBUG)) || defined(WANT_PRETTY_LOG_F)
+#define LOG_F(sev) LOG(sev) << __PRETTY_FUNCTION__ << ": "
+#else
+#define LOG_F(sev) LOG(sev) << __FUNCTION__ << ": "
+#endif
+
+#define LOG_CHECK_LEVEL(sev) \
+  talk_base::LogCheckLevel(talk_base::sev)
+#define LOG_CHECK_LEVEL_V(sev) \
+  talk_base::LogCheckLevel(sev)
+inline bool LogCheckLevel(LoggingSeverity sev) {
+  return (LogMessage::GetMinLogSeverity() <= sev);
+}
+
+#define LOG_E(sev, ctx, err, ...) \
+  LOG_SEVERITY_PRECONDITION(talk_base::sev) \
+    talk_base::LogMessage(__FILE__, __LINE__, talk_base::sev, \
+                          talk_base::ERRCTX_ ## ctx, err , ##__VA_ARGS__) \
+        .stream()
+
+#else  // !LOGGING
+
+// Hopefully, the compiler will optimize away some of this code.
+// Note: syntax of "1 ? (void)0 : LogMessage" was causing errors in g++,
+//   converted to "while (false)"
+#define LOG(sev) \
+  while (false)talk_base:: LogMessage(NULL, 0, talk_base::sev).stream()
+#define LOG_V(sev) \
+  while (false) talk_base::LogMessage(NULL, 0, sev).stream()
+#define LOG_F(sev) LOG(sev) << __FUNCTION__ << ": "
+#define LOG_CHECK_LEVEL(sev) \
+  false
+#define LOG_CHECK_LEVEL_V(sev) \
+  false
+
+#define LOG_E(sev, ctx, err, ...) \
+  while (false) talk_base::LogMessage(__FILE__, __LINE__, talk_base::sev, \
+                          talk_base::ERRCTX_ ## ctx, err , ##__VA_ARGS__) \
+      .stream()
+
+#endif  // !LOGGING
+
+#define LOG_ERRNO_EX(sev, err) \
+  LOG_E(sev, ERRNO, err)
+#define LOG_ERRNO(sev) \
+  LOG_ERRNO_EX(sev, errno)
+
+#ifdef WIN32
+#define LOG_GLE_EX(sev, err) \
+  LOG_E(sev, HRESULT, err)
+#define LOG_GLE(sev) \
+  LOG_GLE_EX(sev, GetLastError())
+#define LOG_GLEM(sev, mod) \
+  LOG_E(sev, HRESULT, GetLastError(), mod)
+#define LOG_ERR_EX(sev, err) \
+  LOG_GLE_EX(sev, err)
+#define LOG_ERR(sev) \
+  LOG_GLE(sev)
+#define LAST_SYSTEM_ERROR \
+  (::GetLastError())
+#elif POSIX
+#define LOG_ERR_EX(sev, err) \
+  LOG_ERRNO_EX(sev, err)
+#define LOG_ERR(sev) \
+  LOG_ERRNO(sev)
+#define LAST_SYSTEM_ERROR \
+  (errno)
+#endif  // WIN32
+
+#define PLOG(sev, err) \
+  LOG_ERR_EX(sev, err)
+
+// TODO(?): Add an "assert" wrapper that logs in the same manner.
+
+#endif  // LOG
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_LOGGING_H_
diff --git a/talk/base/logging_unittest.cc b/talk/base/logging_unittest.cc
new file mode 100644
index 0000000..b0c219f
--- /dev/null
+++ b/talk/base/logging_unittest.cc
@@ -0,0 +1,149 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/fileutils.h"
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+// Test basic logging operation. We should get the INFO log but not the VERBOSE.
+// We should restore the correct global state at the end.
+TEST(LogTest, SingleStream) {
+  int sev = LogMessage::GetLogToStream(NULL);
+
+  std::string str;
+  StringStream stream(str);
+  LogMessage::AddLogToStream(&stream, LS_INFO);
+  EXPECT_EQ(LS_INFO, LogMessage::GetLogToStream(&stream));
+
+  LOG(LS_INFO) << "INFO";
+  LOG(LS_VERBOSE) << "VERBOSE";
+  EXPECT_NE(std::string::npos, str.find("INFO"));
+  EXPECT_EQ(std::string::npos, str.find("VERBOSE"));
+
+  LogMessage::RemoveLogToStream(&stream);
+  EXPECT_EQ(LogMessage::NO_LOGGING, LogMessage::GetLogToStream(&stream));
+
+  EXPECT_EQ(sev, LogMessage::GetLogToStream(NULL));
+}
+
+// Test using multiple log streams. The INFO stream should get the INFO message,
+// the VERBOSE stream should get the INFO and the VERBOSE.
+// We should restore the correct global state at the end.
+TEST(LogTest, MultipleStreams) {
+  int sev = LogMessage::GetLogToStream(NULL);
+
+  std::string str1, str2;
+  StringStream stream1(str1), stream2(str2);
+  LogMessage::AddLogToStream(&stream1, LS_INFO);
+  LogMessage::AddLogToStream(&stream2, LS_VERBOSE);
+  EXPECT_EQ(LS_INFO, LogMessage::GetLogToStream(&stream1));
+  EXPECT_EQ(LS_VERBOSE, LogMessage::GetLogToStream(&stream2));
+
+  LOG(LS_INFO) << "INFO";
+  LOG(LS_VERBOSE) << "VERBOSE";
+
+  EXPECT_NE(std::string::npos, str1.find("INFO"));
+  EXPECT_EQ(std::string::npos, str1.find("VERBOSE"));
+  EXPECT_NE(std::string::npos, str2.find("INFO"));
+  EXPECT_NE(std::string::npos, str2.find("VERBOSE"));
+
+  LogMessage::RemoveLogToStream(&stream2);
+  LogMessage::RemoveLogToStream(&stream1);
+  EXPECT_EQ(LogMessage::NO_LOGGING, LogMessage::GetLogToStream(&stream2));
+  EXPECT_EQ(LogMessage::NO_LOGGING, LogMessage::GetLogToStream(&stream1));
+
+  EXPECT_EQ(sev, LogMessage::GetLogToStream(NULL));
+}
+
+// Ensure we don't crash when adding/removing streams while threads are going.
+// We should restore the correct global state at the end.
+class LogThread : public Thread {
+  void Run() {
+    // LS_SENSITIVE to avoid cluttering up any real logging going on
+    LOG(LS_SENSITIVE) << "LOG";
+  }
+};
+
+TEST(LogTest, MultipleThreads) {
+  int sev = LogMessage::GetLogToStream(NULL);
+
+  LogThread thread1, thread2, thread3;
+  thread1.Start();
+  thread2.Start();
+  thread3.Start();
+
+  NullStream stream1, stream2, stream3;
+  for (int i = 0; i < 1000; ++i) {
+    LogMessage::AddLogToStream(&stream1, LS_INFO);
+    LogMessage::AddLogToStream(&stream2, LS_VERBOSE);
+    LogMessage::AddLogToStream(&stream3, LS_SENSITIVE);
+    LogMessage::RemoveLogToStream(&stream1);
+    LogMessage::RemoveLogToStream(&stream2);
+    LogMessage::RemoveLogToStream(&stream3);
+  }
+
+  EXPECT_EQ(sev, LogMessage::GetLogToStream(NULL));
+}
+
+
+TEST(LogTest, WallClockStartTime) {
+  uint32 time = LogMessage::WallClockStartTime();
+  // Expect the time to be in a sensible range, e.g. > 2012-01-01.
+  EXPECT_GT(time, 1325376000u);
+}
+
+// Test the time required to write 1000 80-character logs to an unbuffered file.
+TEST(LogTest, Perf) {
+  Pathname path;
+  EXPECT_TRUE(Filesystem::GetTemporaryFolder(path, true, NULL));
+  path.SetPathname(Filesystem::TempFilename(path, "ut"));
+
+  FileStream stream;
+  EXPECT_TRUE(stream.Open(path.pathname(), "wb", NULL));
+  stream.DisableBuffering();
+  LogMessage::AddLogToStream(&stream, LS_SENSITIVE);
+
+  uint32 start = Time(), finish;
+  std::string message('X', 80);
+  for (int i = 0; i < 1000; ++i) {
+    LOG(LS_SENSITIVE) << message;
+  }
+  finish = Time();
+
+  LogMessage::RemoveLogToStream(&stream);
+  stream.Close();
+  Filesystem::DeleteFile(path);
+
+  LOG(LS_INFO) << "Average log time: " << TimeDiff(finish, start) << " us";
+}
+
+}  // namespace talk_base
diff --git a/talk/base/macasyncsocket.cc b/talk/base/macasyncsocket.cc
new file mode 100644
index 0000000..54ad604
--- /dev/null
+++ b/talk/base/macasyncsocket.cc
@@ -0,0 +1,472 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+
+//         thaloun@google.com (Tim Haloun)
+//
+// MacAsyncSocket is a kind of AsyncSocket. It does not support the SOCK_DGRAM
+// type (yet). It works asynchronously, which means that users of this socket
+// should connect to the various events declared in asyncsocket.h to receive
+// notifications about this socket.  It uses CFSockets for signals, but prefers
+// the basic bsd socket operations rather than their CFSocket wrappers when
+// possible.
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <fcntl.h>
+
+#include "talk/base/macasyncsocket.h"
+
+#include "talk/base/logging.h"
+#include "talk/base/macsocketserver.h"
+
+namespace talk_base {
+
+static const int kCallbackFlags = kCFSocketReadCallBack |
+                                  kCFSocketConnectCallBack |
+                                  kCFSocketWriteCallBack;
+
+MacAsyncSocket::MacAsyncSocket(MacBaseSocketServer* ss, int family)
+    : ss_(ss),
+      socket_(NULL),
+      native_socket_(INVALID_SOCKET),
+      source_(NULL),
+      current_callbacks_(0),
+      disabled_(false),
+      error_(0),
+      state_(CS_CLOSED),
+      resolver_(NULL) {
+  Initialize(family);
+}
+
+MacAsyncSocket::~MacAsyncSocket() {
+  Close();
+}
+
+// Returns the address to which the socket is bound.  If the socket is not
+// bound, then the any-address is returned.
+SocketAddress MacAsyncSocket::GetLocalAddress() const {
+  SocketAddress address;
+
+  // The CFSocket doesn't pick up on implicit binds from the connect call.
+  // Calling bind in before connect explicitly causes errors, so just query
+  // the underlying bsd socket.
+  sockaddr_storage addr;
+  socklen_t addrlen = sizeof(addr);
+  int result = ::getsockname(native_socket_,
+                             reinterpret_cast<sockaddr*>(&addr), &addrlen);
+  if (result >= 0) {
+    SocketAddressFromSockAddrStorage(addr, &address);
+  }
+  return address;
+}
+
+// Returns the address to which the socket is connected.  If the socket is not
+// connected, then the any-address is returned.
+SocketAddress MacAsyncSocket::GetRemoteAddress() const {
+  SocketAddress address;
+
+  // Use native_socket for consistency with GetLocalAddress.
+  sockaddr_storage addr;
+  socklen_t addrlen = sizeof(addr);
+  int result = ::getpeername(native_socket_,
+                             reinterpret_cast<sockaddr*>(&addr), &addrlen);
+  if (result >= 0) {
+    SocketAddressFromSockAddrStorage(addr, &address);
+  }
+  return address;
+}
+
+// Bind the socket to a local address.
+int MacAsyncSocket::Bind(const SocketAddress& address) {
+  sockaddr_storage saddr = {0};
+  size_t len = address.ToSockAddrStorage(&saddr);
+  int err = ::bind(native_socket_, reinterpret_cast<sockaddr*>(&saddr), len);
+  if (err == SOCKET_ERROR) error_ = errno;
+  return err;
+}
+
+void MacAsyncSocket::OnResolveResult(SignalThread* thread) {
+  if (thread != resolver_) {
+    return;
+  }
+  int error = resolver_->error();
+  if (error == 0) {
+    error = DoConnect(resolver_->address());
+  } else {
+    Close();
+  }
+  if (error) {
+    error_ = error;
+    SignalCloseEvent(this, error_);
+  }
+}
+
+// Connect to a remote address.
+int MacAsyncSocket::Connect(const SocketAddress& addr) {
+  // TODO(djw): Consolidate all the connect->resolve->doconnect implementations.
+  if (state_ != CS_CLOSED) {
+    SetError(EALREADY);
+    return SOCKET_ERROR;
+  }
+  if (addr.IsUnresolved()) {
+    LOG(LS_VERBOSE) << "Resolving addr in MacAsyncSocket::Connect";
+    resolver_ = new AsyncResolver();
+    resolver_->set_address(addr);
+    resolver_->SignalWorkDone.connect(this,
+                                      &MacAsyncSocket::OnResolveResult);
+    resolver_->Start();
+    state_ = CS_CONNECTING;
+    return 0;
+  }
+  return DoConnect(addr);
+}
+
+int MacAsyncSocket::DoConnect(const SocketAddress& addr) {
+  if (!valid()) {
+    Initialize(addr.family());
+    if (!valid())
+      return SOCKET_ERROR;
+  }
+
+  sockaddr_storage saddr;
+  size_t len = addr.ToSockAddrStorage(&saddr);
+  int result = ::connect(native_socket_, reinterpret_cast<sockaddr*>(&saddr),
+                         len);
+
+  if (result != SOCKET_ERROR) {
+    state_ = CS_CONNECTED;
+  } else {
+    error_ = errno;
+    if (error_ == EINPROGRESS) {
+      state_ = CS_CONNECTING;
+      result = 0;
+    }
+  }
+  return result;
+}
+
+// Send to the remote end we're connected to.
+int MacAsyncSocket::Send(const void* buffer, size_t length) {
+  if (!valid()) {
+    return SOCKET_ERROR;
+  }
+
+  int sent = ::send(native_socket_, buffer, length, 0);
+
+  if (sent == SOCKET_ERROR) {
+    error_ = errno;
+
+    if (IsBlocking()) {
+      // Reenable the writable callback (once), since we are flow controlled.
+      CFSocketEnableCallBacks(socket_, kCallbackFlags);
+      current_callbacks_ = kCallbackFlags;
+    }
+  }
+  return sent;
+}
+
+// Send to the given address. We may or may not be connected to anyone.
+int MacAsyncSocket::SendTo(const void* buffer, size_t length,
+                           const SocketAddress& address) {
+  if (!valid()) {
+    return SOCKET_ERROR;
+  }
+
+  sockaddr_storage saddr;
+  size_t len = address.ToSockAddrStorage(&saddr);
+  int sent = ::sendto(native_socket_, buffer, length, 0,
+                      reinterpret_cast<sockaddr*>(&saddr), len);
+
+  if (sent == SOCKET_ERROR) {
+    error_ = errno;
+  }
+
+  return sent;
+}
+
+// Read data received from the remote end we're connected to.
+int MacAsyncSocket::Recv(void* buffer, size_t length) {
+  int received = ::recv(native_socket_, reinterpret_cast<char*>(buffer),
+                        length, 0);
+  if (received == SOCKET_ERROR) error_ = errno;
+
+  // Recv should only be called when there is data to read
+  ASSERT((received != 0) || (length == 0));
+  return received;
+}
+
+// Read data received from any remote party
+int MacAsyncSocket::RecvFrom(void* buffer, size_t length,
+                             SocketAddress* out_addr) {
+  sockaddr_storage saddr;
+  socklen_t addr_len = sizeof(saddr);
+  int received = ::recvfrom(native_socket_, reinterpret_cast<char*>(buffer),
+                            length, 0, reinterpret_cast<sockaddr*>(&saddr),
+                            &addr_len);
+  if (received >= 0 && out_addr != NULL) {
+    SocketAddressFromSockAddrStorage(saddr, out_addr);
+  } else if (received == SOCKET_ERROR) {
+    error_ = errno;
+  }
+  return received;
+}
+
+int MacAsyncSocket::Listen(int backlog) {
+  if (!valid()) {
+    return SOCKET_ERROR;
+  }
+
+  int res = ::listen(native_socket_, backlog);
+  if (res != SOCKET_ERROR)
+    state_ = CS_CONNECTING;
+  else
+    error_ = errno;
+
+  return res;
+}
+
+MacAsyncSocket* MacAsyncSocket::Accept(SocketAddress* out_addr) {
+  sockaddr_storage saddr;
+  socklen_t addr_len = sizeof(saddr);
+
+  int socket_fd = ::accept(native_socket_, reinterpret_cast<sockaddr*>(&saddr),
+                           &addr_len);
+  if (socket_fd == INVALID_SOCKET) {
+    error_ = errno;
+    return NULL;
+  }
+
+  MacAsyncSocket* s = new MacAsyncSocket(ss_, saddr.ss_family, socket_fd);
+  if (s && s->valid()) {
+    s->state_ = CS_CONNECTED;
+    if (out_addr)
+      SocketAddressFromSockAddrStorage(saddr, out_addr);
+  } else {
+    delete s;
+    s = NULL;
+  }
+  return s;
+}
+
+int MacAsyncSocket::Close() {
+  if (source_ != NULL) {
+    CFRunLoopSourceInvalidate(source_);
+    CFRelease(source_);
+    if (ss_) ss_->UnregisterSocket(this);
+    source_ = NULL;
+  }
+
+  if (socket_ != NULL) {
+    CFSocketInvalidate(socket_);
+    CFRelease(socket_);
+    socket_ = NULL;
+  }
+
+  if (resolver_) {
+    resolver_->Destroy(false);
+    resolver_ = NULL;
+  }
+
+  native_socket_ = INVALID_SOCKET;  // invalidates the socket
+  error_ = 0;
+  state_ = CS_CLOSED;
+  return 0;
+}
+
+int MacAsyncSocket::EstimateMTU(uint16* mtu) {
+  ASSERT(false && "NYI");
+  return -1;
+}
+
+int MacAsyncSocket::GetError() const {
+  return error_;
+}
+
+void MacAsyncSocket::SetError(int error) {
+  error_ = error;
+}
+
+Socket::ConnState MacAsyncSocket::GetState() const {
+  return state_;
+}
+
+int MacAsyncSocket::GetOption(Option opt, int* value) {
+  ASSERT(false && "NYI");
+  return -1;
+}
+
+int MacAsyncSocket::SetOption(Option opt, int value) {
+  ASSERT(false && "NYI");
+  return -1;
+}
+
+void MacAsyncSocket::EnableCallbacks() {
+  if (valid()) {
+    disabled_ = false;
+    CFSocketEnableCallBacks(socket_, current_callbacks_);
+  }
+}
+
+void MacAsyncSocket::DisableCallbacks() {
+  if (valid()) {
+    disabled_ = true;
+    CFSocketDisableCallBacks(socket_, kCallbackFlags);
+  }
+}
+
+MacAsyncSocket::MacAsyncSocket(MacBaseSocketServer* ss, int family,
+                               int native_socket)
+    : ss_(ss),
+      socket_(NULL),
+      native_socket_(native_socket),
+      source_(NULL),
+      current_callbacks_(0),
+      disabled_(false),
+      error_(0),
+      state_(CS_CLOSED),
+      resolver_(NULL) {
+  Initialize(family);
+}
+
+// Create a new socket, wrapping the native socket if provided or creating one
+// otherwise. In case of any failure, consume the native socket.  We assume the
+// wrapped socket is in the closed state.  If this is not the case you must
+// update the state_ field for this socket yourself.
+void MacAsyncSocket::Initialize(int family) {
+  CFSocketContext ctx = { 0 };
+  ctx.info = this;
+
+  // First create the CFSocket
+  CFSocketRef cf_socket = NULL;
+  bool res = false;
+  if (native_socket_ == INVALID_SOCKET) {
+    cf_socket = CFSocketCreate(kCFAllocatorDefault,
+                               family, SOCK_STREAM, IPPROTO_TCP,
+                               kCallbackFlags, MacAsyncSocketCallBack, &ctx);
+  } else {
+    cf_socket = CFSocketCreateWithNative(kCFAllocatorDefault,
+                                         native_socket_, kCallbackFlags,
+                                         MacAsyncSocketCallBack, &ctx);
+  }
+
+  if (cf_socket) {
+    res = true;
+    socket_ = cf_socket;
+    native_socket_ = CFSocketGetNative(cf_socket);
+    current_callbacks_ = kCallbackFlags;
+  }
+
+  if (res) {
+    // Make the underlying socket asynchronous
+    res = (-1 != ::fcntl(native_socket_, F_SETFL,
+                         ::fcntl(native_socket_, F_GETFL, 0) | O_NONBLOCK));
+  }
+
+  if (res) {
+    // 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) {
+    if (ss_) ss_->RegisterSocket(this);
+    CFRunLoopAddSource(CFRunLoopGetCurrent(), source_, kCFRunLoopCommonModes);
+  }
+
+  if (!res) {
+    int error = errno;
+    Close();  //  Clears error_.
+    error_ = error;
+  }
+}
+
+// Call CFRelease on the result when done using it
+CFDataRef MacAsyncSocket::CopyCFAddress(const SocketAddress& address) {
+  sockaddr_storage saddr;
+  size_t len = address.ToSockAddrStorage(&saddr);
+
+  const UInt8* bytes = reinterpret_cast<UInt8*>(&saddr);
+
+  CFDataRef cf_address = CFDataCreate(kCFAllocatorDefault,
+                                      bytes, len);
+
+  ASSERT(cf_address != NULL);
+  return cf_address;
+}
+
+void MacAsyncSocket::MacAsyncSocketCallBack(CFSocketRef s,
+                                            CFSocketCallBackType callbackType,
+                                            CFDataRef address,
+                                            const void* data,
+                                            void* info) {
+  MacAsyncSocket* this_socket =
+      reinterpret_cast<MacAsyncSocket*>(info);
+  ASSERT(this_socket != NULL && this_socket->socket_ == s);
+
+  // Don't signal any socket messages if the socketserver is not listening on
+  // them.  When we are reenabled they will be requeued and will fire again.
+  if (this_socket->disabled_)
+    return;
+
+  switch (callbackType) {
+    case kCFSocketReadCallBack:
+      // This callback is invoked in one of 3 situations:
+      // 1. A new connection is waiting to be accepted.
+      // 2. The remote end closed the connection (a recv will return 0).
+      // 3. Data is available to read.
+      // 4. The connection closed unhappily (recv will return -1).
+      if (this_socket->state_ == CS_CONNECTING) {
+        // Case 1.
+        this_socket->SignalReadEvent(this_socket);
+      } else {
+        char ch, amt;
+        amt = ::recv(this_socket->native_socket_, &ch, 1, MSG_PEEK);
+        if (amt == 0) {
+          // Case 2.
+          this_socket->state_ = CS_CLOSED;
+
+          // Disable additional callbacks or we will signal close twice.
+          CFSocketDisableCallBacks(this_socket->socket_, kCFSocketReadCallBack);
+          this_socket->current_callbacks_ &= ~kCFSocketReadCallBack;
+          this_socket->SignalCloseEvent(this_socket, 0);
+        } else if (amt > 0) {
+          // Case 3.
+          this_socket->SignalReadEvent(this_socket);
+        } else {
+          // Case 4.
+          int error = errno;
+          if (error == EAGAIN) {
+            // Observed in practice.  Let's hope it's a spurious or out of date
+            // signal, since we just eat it.
+          } else {
+            this_socket->error_ = error;
+            this_socket->SignalCloseEvent(this_socket, error);
+          }
+        }
+      }
+      break;
+
+    case kCFSocketConnectCallBack:
+      if (data != NULL) {
+        // An error occured in the background while connecting
+        this_socket->error_ = errno;
+        this_socket->state_ = CS_CLOSED;
+        this_socket->SignalCloseEvent(this_socket, this_socket->error_);
+      } else {
+        this_socket->state_ = CS_CONNECTED;
+        this_socket->SignalConnectEvent(this_socket);
+      }
+      break;
+
+    case kCFSocketWriteCallBack:
+      // Update our callback tracking.  Write doesn't reenable, so it's off now.
+      this_socket->current_callbacks_ &= ~kCFSocketWriteCallBack;
+      this_socket->SignalWriteEvent(this_socket);
+      break;
+
+    default:
+      ASSERT(false && "Invalid callback type for socket");
+  }
+}
+
+}  // namespace talk_base
diff --git a/talk/base/macasyncsocket.h b/talk/base/macasyncsocket.h
new file mode 100644
index 0000000..12d2add
--- /dev/null
+++ b/talk/base/macasyncsocket.h
@@ -0,0 +1,91 @@
+// Copyright 2008 Google Inc. All Rights Reserved.
+
+//
+// MacAsyncSocket is a kind of AsyncSocket. It only creates sockets
+// of the TCP type, and does not (yet) support listen and accept. It works
+// asynchronously, which means that users of this socket should connect to
+// the various events declared in asyncsocket.h to receive notifications about
+// this socket.
+
+#ifndef TALK_BASE_MACASYNCSOCKET_H__
+#define TALK_BASE_MACASYNCSOCKET_H__
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "talk/base/asyncsocket.h"
+#include "talk/base/nethelpers.h"
+
+namespace talk_base {
+
+class MacBaseSocketServer;
+
+class MacAsyncSocket : public AsyncSocket, public sigslot::has_slots<> {
+ public:
+  MacAsyncSocket(MacBaseSocketServer* ss, int family);
+  virtual ~MacAsyncSocket();
+
+  bool valid() const { return source_ != NULL; }
+
+  // Socket interface
+  virtual SocketAddress GetLocalAddress() const;
+  virtual SocketAddress GetRemoteAddress() const;
+  virtual int Bind(const SocketAddress& addr);
+  virtual int Connect(const SocketAddress& addr);
+  virtual int Send(const void* buffer, size_t length);
+  virtual int SendTo(const void* buffer, size_t length,
+                     const SocketAddress& addr);
+  virtual int Recv(void* buffer, size_t length);
+  virtual int RecvFrom(void* buffer, size_t length, SocketAddress* out_addr);
+  virtual int Listen(int backlog);
+  virtual MacAsyncSocket* Accept(SocketAddress* out_addr);
+  virtual int Close();
+  virtual int GetError() const;
+  virtual void SetError(int error);
+  virtual ConnState GetState() const;
+  virtual int EstimateMTU(uint16* mtu);
+  virtual int GetOption(Option opt, int* value);
+  virtual int SetOption(Option opt, int value);
+
+  // For the MacBaseSocketServer to disable callbacks when process_io is false.
+  void EnableCallbacks();
+  void DisableCallbacks();
+
+ protected:
+  void OnResolveResult(SignalThread* thread);
+  int DoConnect(const SocketAddress& addr);
+
+ private:
+  // Creates an async socket from an existing bsd socket
+  MacAsyncSocket(MacBaseSocketServer* ss, int family, int native_socket);
+
+   // Attaches the socket to the CFRunloop and sets the wrapped bsd socket
+  // to async mode
+  void Initialize(int family);
+
+  // Translate the SocketAddress into a CFDataRef to pass to CF socket
+  // functions. Caller must call CFRelease on the result when done.
+  static CFDataRef CopyCFAddress(const SocketAddress& address);
+
+  // Callback for the underlying CFSocketRef.
+  static void MacAsyncSocketCallBack(CFSocketRef s,
+                                     CFSocketCallBackType callbackType,
+                                     CFDataRef address,
+                                     const void* data,
+                                     void* info);
+
+  MacBaseSocketServer* ss_;
+  CFSocketRef socket_;
+  int native_socket_;
+  CFRunLoopSourceRef source_;
+  int current_callbacks_;
+  bool disabled_;
+  int error_;
+  ConnState state_;
+  AsyncResolver* resolver_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(MacAsyncSocket);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_MACASYNCSOCKET_H__
diff --git a/talk/base/maccocoasocketserver.h b/talk/base/maccocoasocketserver.h
new file mode 100644
index 0000000..f4aeb33
--- /dev/null
+++ b/talk/base/maccocoasocketserver.h
@@ -0,0 +1,63 @@
+/*
+ * 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.
+
+#ifndef TALK_BASE_MACCOCOASOCKETSERVER_H_
+#define TALK_BASE_MACCOCOASOCKETSERVER_H_
+
+#include "talk/base/macsocketserver.h"
+
+#ifdef __OBJC__
+@class NSTimer, MacCocoaSocketServerHelper;
+#else
+class NSTimer;
+class MacCocoaSocketServerHelper;
+#endif
+
+namespace talk_base {
+
+// A socketserver implementation that wraps the main cocoa
+// application loop accessed through [NSApp run].
+class MacCocoaSocketServer : public MacBaseSocketServer {
+ public:
+  explicit MacCocoaSocketServer();
+  virtual ~MacCocoaSocketServer();
+
+  virtual bool Wait(int cms, bool process_io);
+  virtual void WakeUp();
+
+ private:
+  MacCocoaSocketServerHelper* helper_;
+  NSTimer* timer_;  // Weak.
+
+  DISALLOW_EVIL_CONSTRUCTORS(MacCocoaSocketServer);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_MACCOCOASOCKETSERVER_H_
diff --git a/talk/base/maccocoasocketserver.mm b/talk/base/maccocoasocketserver.mm
new file mode 100644
index 0000000..bf308e6
--- /dev/null
+++ b/talk/base/maccocoasocketserver.mm
@@ -0,0 +1,134 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "talk/base/maccocoasocketserver.h"
+
+#import <Foundation/Foundation.h>
+#import <AppKit/AppKit.h>
+#include <assert.h>
+
+#include "talk/base/scoped_autorelease_pool.h"
+
+// 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
+
+@implementation MacCocoaSocketServerHelper
+- (id)initWithSocketServer:(talk_base::MacCocoaSocketServer*)ss {
+  self = [super init];
+  if (self) {
+    socketServer_ = ss;
+  }
+  return self;
+}
+
+- (void)timerFired:(NSTimer*)timer {
+  socketServer_->WakeUp();
+}
+@end
+
+namespace talk_base {
+
+MacCocoaSocketServer::MacCocoaSocketServer() {
+  helper_ = [[MacCocoaSocketServerHelper alloc] initWithSocketServer:this];
+  timer_ = nil;
+
+  // Initialize the shared NSApplication
+  [NSApplication sharedApplication];
+}
+
+MacCocoaSocketServer::~MacCocoaSocketServer() {
+  [timer_ invalidate];
+  [timer_ release];
+  [helper_ release];
+}
+
+bool MacCocoaSocketServer::Wait(int cms, bool process_io) {
+  talk_base::ScopedAutoreleasePool pool;
+  if (!process_io && cms == 0) {
+    // No op.
+    return true;
+  }
+
+  if (!process_io) {
+    // No way to listen to common modes and not get socket events, unless
+    // we disable each one's callbacks.
+    EnableSocketCallbacks(false);
+  }
+
+  if (kForever != cms) {
+    // Install a timer that fires wakeup after cms has elapsed.
+    timer_ =
+        [NSTimer scheduledTimerWithTimeInterval:cms / 1000.0
+                                         target:helper_
+                                       selector:@selector(timerFired:)
+                                       userInfo:nil
+                                        repeats:NO];
+    [timer_ retain];
+  }
+
+  // Run until WakeUp is called, which will call stop and exit this loop.
+  [NSApp run];
+
+  if (!process_io) {
+    // Reenable them.  Hopefully this won't cause spurious callbacks or
+    // missing ones while they were disabled.
+    EnableSocketCallbacks(true);
+  }
+
+  return true;
+}
+
+void MacCocoaSocketServer::WakeUp() {
+  // Timer has either fired or shortcutted.
+  [timer_ invalidate];
+  [timer_ release];
+  timer_ = nil;
+  [NSApp stop:nil];
+
+  // NSApp stop only exits after finishing processing of the
+  // current event.  Since we're potentially in a timer callback
+  //  and not an NSEvent handler, we need to trigger a dummy one
+  // and turn the loop over.  We may be able to skip this if we're
+  // on the ss' thread and not inside the app loop already.
+  NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined
+                                      location:NSMakePoint(0,0)
+                                 modifierFlags:0
+                                     timestamp:0
+                                  windowNumber:0
+                                       context:nil
+                                       subtype:1
+                                         data1:1
+                                         data2:1];
+  [NSApp postEvent:event atStart:YES];
+}
+
+}  // namespace talk_base
diff --git a/talk/base/maccocoasocketserver_unittest.mm b/talk/base/maccocoasocketserver_unittest.mm
new file mode 100644
index 0000000..d6f4b2c
--- /dev/null
+++ b/talk/base/maccocoasocketserver_unittest.mm
@@ -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.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/thread.h"
+#include "talk/base/maccocoasocketserver.h"
+
+namespace talk_base {
+
+class WakeThread : public Thread {
+ public:
+  WakeThread(SocketServer* ss) : ss_(ss) {
+  }
+  void Run() {
+    ss_->WakeUp();
+  }
+ private:
+  SocketServer* ss_;
+};
+
+// Test that MacCocoaSocketServer::Wait works as expected.
+TEST(MacCocoaSocketServer, TestWait) {
+  MacCocoaSocketServer server;
+  uint32 start = Time();
+  server.Wait(1000, true);
+  EXPECT_GE(TimeSince(start), 1000);
+}
+
+// Test that MacCocoaSocketServer::Wakeup works as expected.
+TEST(MacCocoaSocketServer, TestWakeup) {
+  MacCFSocketServer server;
+  WakeThread thread(&server);
+  uint32 start = Time();
+  thread.Start();
+  server.Wait(10000, true);
+  EXPECT_LT(TimeSince(start), 10000);
+}
+
+} // namespace talk_base
diff --git a/talk/base/maccocoathreadhelper.h b/talk/base/maccocoathreadhelper.h
new file mode 100644
index 0000000..336e638
--- /dev/null
+++ b/talk/base/maccocoathreadhelper.h
@@ -0,0 +1,44 @@
+/*
+ * 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__
+
+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..fee3972
--- /dev/null
+++ b/talk/base/maccocoathreadhelper.mm
@@ -0,0 +1,61 @@
+/*
+ * 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"
+
+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(class)
+                             toTarget:[NSObject class]
+                           withObject:nil];
+  }
+
+  assert([NSThread isMultiThreaded]);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/macconversion.cc b/talk/base/macconversion.cc
new file mode 100644
index 0000000..4654e53
--- /dev/null
+++ b/talk/base/macconversion.cc
@@ -0,0 +1,176 @@
+/*
+ * libjingle
+ * Copyright 2004--2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef OSX
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "talk/base/logging.h"
+#include "talk/base/macconversion.h"
+
+bool p_convertHostCFStringRefToCPPString(
+  const CFStringRef cfstr, std::string& cppstr) {
+  bool result = false;
+
+  // First this must be non-null,
+  if (NULL != cfstr) {
+    // it must actually *be* a CFString, and not something just masquerading
+    // as one,
+    if (CFGetTypeID(cfstr) == CFStringGetTypeID()) {
+      // and we must be able to get the characters out of it.
+      // (The cfstr owns this buffer; it came from somewhere else,
+      // so someone else gets to take care of getting rid of the cfstr,
+      // and then this buffer will go away automatically.)
+      unsigned length = CFStringGetLength(cfstr);
+      char* buf = new char[1 + length];
+      if (CFStringGetCString(cfstr, buf, 1 + length, kCFStringEncodingASCII)) {
+        if (strlen(buf) == length) {
+          cppstr.assign(buf);
+          result = true;
+        }
+      }
+      delete [] buf;
+    }
+  }
+
+  return result;
+}
+
+bool p_convertCFNumberToInt(CFNumberRef cfn, int* i) {
+  bool converted = false;
+
+  // It must not be null.
+  if (NULL != cfn) {
+    // It must actually *be* a CFNumber and not something just masquerading
+    // as one.
+    if (CFGetTypeID(cfn) == CFNumberGetTypeID()) {
+      CFNumberType ntype = CFNumberGetType(cfn);
+      switch (ntype) {
+        case kCFNumberSInt8Type:
+          SInt8 sint8;
+          converted = CFNumberGetValue(cfn, ntype, static_cast<void*>(&sint8));
+          if (converted) *i = static_cast<int>(sint8);
+          break;
+        case kCFNumberSInt16Type:
+          SInt16 sint16;
+          converted = CFNumberGetValue(cfn, ntype, static_cast<void*>(&sint16));
+          if (converted) *i = static_cast<int>(sint16);
+          break;
+        case kCFNumberSInt32Type:
+          SInt32 sint32;
+          converted = CFNumberGetValue(cfn, ntype, static_cast<void*>(&sint32));
+          if (converted) *i = static_cast<int>(sint32);
+          break;
+        case kCFNumberSInt64Type:
+          SInt64 sint64;
+          converted = CFNumberGetValue(cfn, ntype, static_cast<void*>(&sint64));
+          if (converted) *i = static_cast<int>(sint64);
+          break;
+        case kCFNumberFloat32Type:
+          Float32 float32;
+          converted = CFNumberGetValue(cfn, ntype,
+                                       static_cast<void*>(&float32));
+          if (converted) *i = static_cast<int>(float32);
+          break;
+        case kCFNumberFloat64Type:
+          Float64 float64;
+          converted = CFNumberGetValue(cfn, ntype,
+                                       static_cast<void*>(&float64));
+          if (converted) *i = static_cast<int>(float64);
+          break;
+        case kCFNumberCharType:
+          char charvalue;
+          converted = CFNumberGetValue(cfn, ntype,
+                                       static_cast<void*>(&charvalue));
+          if (converted) *i = static_cast<int>(charvalue);
+          break;
+        case kCFNumberShortType:
+          short shortvalue;
+          converted = CFNumberGetValue(cfn, ntype,
+                                       static_cast<void*>(&shortvalue));
+          if (converted) *i = static_cast<int>(shortvalue);
+          break;
+        case kCFNumberIntType:
+          int intvalue;
+          converted = CFNumberGetValue(cfn, ntype,
+                                       static_cast<void*>(&intvalue));
+          if (converted) *i = static_cast<int>(intvalue);
+          break;
+        case kCFNumberLongType:
+          long longvalue;
+          converted = CFNumberGetValue(cfn, ntype,
+                     static_cast<void*>(&longvalue));
+          if (converted) *i = static_cast<int>(longvalue);
+          break;
+        case kCFNumberLongLongType:
+          long long llvalue;
+          converted = CFNumberGetValue(cfn, ntype,
+                                       static_cast<void*>(&llvalue));
+          if (converted) *i = static_cast<int>(llvalue);
+          break;
+        case kCFNumberFloatType:
+          float floatvalue;
+          converted = CFNumberGetValue(cfn, ntype,
+                                       static_cast<void*>(&floatvalue));
+          if (converted) *i = static_cast<int>(floatvalue);
+          break;
+        case kCFNumberDoubleType:
+          double doublevalue;
+          converted = CFNumberGetValue(cfn, ntype,
+                                       static_cast<void*>(&doublevalue));
+          if (converted) *i = static_cast<int>(doublevalue);
+          break;
+        case kCFNumberCFIndexType:
+          CFIndex cfindex;
+          converted = CFNumberGetValue(cfn, ntype,
+                                       static_cast<void*>(&cfindex));
+          if (converted) *i = static_cast<int>(cfindex);
+          break;
+        default:
+          LOG(LS_ERROR) << "got unknown type.";
+          break;
+      }
+    }
+  }
+
+  return converted;
+}
+
+bool p_isCFNumberTrue(CFNumberRef cfn) {
+  // We assume it's false until proven otherwise.
+  bool result = false;
+  int asInt;
+  bool converted = p_convertCFNumberToInt(cfn, &asInt);
+
+  if (converted && (0 != asInt)) {
+    result = true;
+  }
+
+  return result;
+}
+
+#endif  // OSX
diff --git a/talk/base/macconversion.h b/talk/base/macconversion.h
new file mode 100644
index 0000000..a401cab
--- /dev/null
+++ b/talk/base/macconversion.h
@@ -0,0 +1,56 @@
+/*
+ * libjingle
+ * Copyright 2004--2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_MACCONVERSION_H_
+#define TALK_BASE_MACCONVERSION_H_
+
+#ifdef OSX
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include <string>
+
+// given a CFStringRef, attempt to convert it to a C++ string.
+// returns true if it succeeds, false otherwise.
+// We can safely assume, given our context, that the string is
+// going to be in ASCII, because it will either be an IP address,
+// or a domain name, which is guaranteed to be ASCII-representable.
+bool p_convertHostCFStringRefToCPPString(const CFStringRef cfstr,
+                                         std::string& cppstr);
+
+// Convert the CFNumber to an integer, putting the integer in the location
+// given, and returhing true, if the conversion succeeds.
+// If given a NULL or a non-CFNumber, returns false.
+// This is pretty aggresive about trying to convert to int.
+bool p_convertCFNumberToInt(CFNumberRef cfn, int* i);
+
+// given a CFNumberRef, determine if it represents a true value.
+bool p_isCFNumberTrue(CFNumberRef cfn);
+
+#endif  // OSX
+
+#endif  // TALK_BASE_MACCONVERSION_H_
diff --git a/talk/base/macsocketserver.cc b/talk/base/macsocketserver.cc
new file mode 100644
index 0000000..895b0bf
--- /dev/null
+++ b/talk/base/macsocketserver.cc
@@ -0,0 +1,369 @@
+
+
+#include "talk/base/macsocketserver.h"
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/macasyncsocket.h"
+#include "talk/base/macutils.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// MacBaseSocketServer
+///////////////////////////////////////////////////////////////////////////////
+
+MacBaseSocketServer::MacBaseSocketServer() {
+}
+
+MacBaseSocketServer::~MacBaseSocketServer() {
+}
+
+AsyncSocket* MacBaseSocketServer::CreateAsyncSocket(int type) {
+  return CreateAsyncSocket(AF_INET, type);
+}
+
+AsyncSocket* MacBaseSocketServer::CreateAsyncSocket(int family, int type) {
+  if (SOCK_STREAM != type)
+    return NULL;
+
+  MacAsyncSocket* socket = new MacAsyncSocket(this, family);
+  if (!socket->valid()) {
+    delete socket;
+    return NULL;
+  }
+  return socket;
+}
+
+void MacBaseSocketServer::RegisterSocket(MacAsyncSocket* s) {
+  sockets_.insert(s);
+}
+
+void MacBaseSocketServer::UnregisterSocket(MacAsyncSocket* s) {
+  VERIFY(1 == sockets_.erase(s));   // 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) {
+    if (enable) {
+      (*it)->EnableCallbacks();
+    } else {
+      (*it)->DisableCallbacks();
+    }
+  }
+}
+
+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
+///////////////////////////////////////////////////////////////////////////////
+
+void WakeUpCallback(void* info) {
+  MacCFSocketServer* server = static_cast<MacCFSocketServer*>(info);
+  ASSERT(NULL != server);
+  server->OnWakeUpCallback();
+}
+
+MacCFSocketServer::MacCFSocketServer()
+    : run_loop_(CFRunLoopGetCurrent()),
+      wake_up_(NULL) {
+  CFRunLoopSourceContext ctx;
+  memset(&ctx, 0, sizeof(ctx));
+  ctx.info = this;
+  ctx.perform = &WakeUpCallback;
+  wake_up_ = CFRunLoopSourceCreate(NULL, 0, &ctx);
+  ASSERT(NULL != wake_up_);
+  if (wake_up_) {
+    CFRunLoopAddSource(run_loop_, wake_up_, kCFRunLoopCommonModes);
+  }
+}
+
+MacCFSocketServer::~MacCFSocketServer() {
+  if (wake_up_) {
+    CFRunLoopSourceInvalidate(wake_up_);
+    CFRelease(wake_up_);
+  }
+}
+
+bool MacCFSocketServer::Wait(int cms, bool process_io) {
+  ASSERT(CFRunLoopGetCurrent() == run_loop_);
+
+  if (!process_io && cms == 0) {
+    // No op.
+    return true;
+  }
+
+  if (!process_io) {
+    // No way to listen to common modes and not get socket events, unless
+    // we disable each one's callbacks.
+    EnableSocketCallbacks(false);
+  }
+
+  SInt32 result;
+  if (kForever == cms) {
+    do {
+      // Would prefer to run in a custom mode that only listens to wake_up,
+      // but we have qtkit sending work to the main thread which is effectively
+      // blocked here, causing deadlock.  Thus listen to the common modes.
+      // TODO: If QTKit becomes thread safe, do the above.
+      result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10000000, false);
+    } while (result != kCFRunLoopRunFinished && result != kCFRunLoopRunStopped);
+  } else {
+    // TODO: In the case of 0ms wait, this will only process one event, so we
+    // may want to loop until it returns TimedOut.
+    CFTimeInterval seconds = cms / 1000.0;
+    result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, seconds, false);
+  }
+
+  if (!process_io) {
+    // Reenable them.  Hopefully this won't cause spurious callbacks or
+    // missing ones while they were disabled.
+    EnableSocketCallbacks(true);
+  }
+
+  if (kCFRunLoopRunFinished == result) {
+    return false;
+  }
+  return true;
+}
+
+void MacCFSocketServer::WakeUp() {
+  if (wake_up_) {
+    CFRunLoopSourceSignal(wake_up_);
+    CFRunLoopWakeUp(run_loop_);
+  }
+}
+
+void MacCFSocketServer::OnWakeUpCallback() {
+  ASSERT(run_loop_ == CFRunLoopGetCurrent());
+  CFRunLoopStop(run_loop_);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// MacCarbonSocketServer
+///////////////////////////////////////////////////////////////////////////////
+#ifndef CARBON_DEPRECATED
+
+const UInt32 kEventClassSocketServer = 'MCSS';
+const UInt32 kEventWakeUp = 'WAKE';
+const EventTypeSpec kEventWakeUpSpec[] = {
+  { kEventClassSocketServer, kEventWakeUp }
+};
+
+std::string DecodeEvent(EventRef event) {
+  std::string str;
+  DecodeFourChar(::GetEventClass(event), &str);
+  str.push_back(':');
+  DecodeFourChar(::GetEventKind(event), &str);
+  return str;
+}
+
+MacCarbonSocketServer::MacCarbonSocketServer()
+    : event_queue_(GetCurrentEventQueue()), wake_up_(NULL) {
+  VERIFY(noErr == CreateEvent(NULL, kEventClassSocketServer, kEventWakeUp, 0,
+                              kEventAttributeUserEvent, &wake_up_));
+}
+
+MacCarbonSocketServer::~MacCarbonSocketServer() {
+  if (wake_up_) {
+    ReleaseEvent(wake_up_);
+  }
+}
+
+bool MacCarbonSocketServer::Wait(int cms, bool process_io) {
+  ASSERT(GetCurrentEventQueue() == event_queue_);
+
+  // Listen to all events if we're processing I/O.
+  // Only listen for our wakeup event if we're not.
+  UInt32 num_types = 0;
+  const EventTypeSpec* events = NULL;
+  if (!process_io) {
+    num_types = GetEventTypeCount(kEventWakeUpSpec);
+    events = kEventWakeUpSpec;
+  }
+
+  EventTargetRef target = GetEventDispatcherTarget();
+  EventTimeout timeout =
+      (kForever == cms) ? kEventDurationForever : cms / 1000.0;
+  EventTimeout end_time = GetCurrentEventTime() + timeout;
+
+  bool done = false;
+  while (!done) {
+    EventRef event;
+    OSStatus result = ReceiveNextEvent(num_types, events, timeout, true,
+                                       &event);
+    if (noErr == result) {
+      if (wake_up_ != event) {
+        LOG_F(LS_VERBOSE) << "Dispatching event: " << DecodeEvent(event);
+        result = SendEventToEventTarget(event, target);
+        if ((noErr != result) && (eventNotHandledErr != result)) {
+          LOG_E(LS_ERROR, OS, result) << "SendEventToEventTarget";
+        }
+      } else {
+        done = true;
+      }
+      ReleaseEvent(event);
+    } else if (eventLoopTimedOutErr == result) {
+      ASSERT(cms != kForever);
+      done = true;
+    } else if (eventLoopQuitErr == result) {
+      // Ignore this... we get spurious quits for a variety of reasons.
+      LOG_E(LS_VERBOSE, OS, result) << "ReceiveNextEvent";
+    } else {
+      // Some strange error occurred. Log it.
+      LOG_E(LS_WARNING, OS, result) << "ReceiveNextEvent";
+      return false;
+    }
+    if (kForever != cms) {
+      timeout = end_time - GetCurrentEventTime();
+    }
+  }
+  return true;
+}
+
+void MacCarbonSocketServer::WakeUp() {
+  if (!IsEventInQueue(event_queue_, wake_up_)) {
+    RetainEvent(wake_up_);
+    OSStatus result = PostEventToQueue(event_queue_, wake_up_,
+                                       kEventPriorityStandard);
+    if (noErr != result) {
+      LOG_E(LS_ERROR, OS, result) << "PostEventToQueue";
+    }
+  }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// MacCarbonAppSocketServer
+///////////////////////////////////////////////////////////////////////////////
+
+MacCarbonAppSocketServer::MacCarbonAppSocketServer()
+    : event_queue_(GetCurrentEventQueue()) {
+  // Install event handler
+  VERIFY(noErr == InstallApplicationEventHandler(
+      NewEventHandlerUPP(WakeUpEventHandler), 1, kEventWakeUpSpec, this,
+      &event_handler_));
+
+  // Install a timer and set it idle to begin with.
+  VERIFY(noErr == InstallEventLoopTimer(GetMainEventLoop(),
+                                        kEventDurationForever,
+                                        kEventDurationForever,
+                                        NewEventLoopTimerUPP(TimerHandler),
+                                        this,
+                                        &timer_));
+}
+
+MacCarbonAppSocketServer::~MacCarbonAppSocketServer() {
+  RemoveEventLoopTimer(timer_);
+  RemoveEventHandler(event_handler_);
+}
+
+OSStatus MacCarbonAppSocketServer::WakeUpEventHandler(
+    EventHandlerCallRef next, EventRef event, void *data) {
+  QuitApplicationEventLoop();
+  return noErr;
+}
+
+void MacCarbonAppSocketServer::TimerHandler(
+    EventLoopTimerRef timer, void *data) {
+  QuitApplicationEventLoop();
+}
+
+bool MacCarbonAppSocketServer::Wait(int cms, bool process_io) {
+  if (!process_io && cms == 0) {
+    // No op.
+    return true;
+  }
+  if (kForever != cms) {
+    // Start a timer.
+    OSStatus error =
+        SetEventLoopTimerNextFireTime(timer_, cms / 1000.0);
+    if (error != noErr) {
+      LOG(LS_ERROR) << "Failed setting next fire time.";
+    }
+  }
+  if (!process_io) {
+    // No way to listen to common modes and not get socket events, unless
+    // we disable each one's callbacks.
+    EnableSocketCallbacks(false);
+  }
+  RunApplicationEventLoop();
+  if (!process_io) {
+    // Reenable them.  Hopefully this won't cause spurious callbacks or
+    // missing ones while they were disabled.
+    EnableSocketCallbacks(true);
+  }
+  return true;
+}
+
+void MacCarbonAppSocketServer::WakeUp() {
+  // TODO: No-op if there's already a WakeUp in flight.
+  EventRef wake_up;
+  VERIFY(noErr == CreateEvent(NULL, kEventClassSocketServer, kEventWakeUp, 0,
+                              kEventAttributeUserEvent, &wake_up));
+  OSStatus result = PostEventToQueue(event_queue_, wake_up,
+                                       kEventPriorityStandard);
+  if (noErr != result) {
+    LOG_E(LS_ERROR, OS, result) << "PostEventToQueue";
+  }
+  ReleaseEvent(wake_up);
+}
+
+#endif
+} // namespace talk_base
diff --git a/talk/base/macsocketserver.h b/talk/base/macsocketserver.h
new file mode 100644
index 0000000..2febb7f
--- /dev/null
+++ b/talk/base/macsocketserver.h
@@ -0,0 +1,130 @@
+// Copyright 2007, Google Inc.
+
+
+#ifndef TALK_BASE_MACSOCKETSERVER_H__
+#define TALK_BASE_MACSOCKETSERVER_H__
+
+#include <set>
+#ifdef OSX // Invalid on IOS
+#include <Carbon/Carbon.h>
+#endif
+#include "talk/base/physicalsocketserver.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// MacBaseSocketServer
+///////////////////////////////////////////////////////////////////////////////
+class MacAsyncSocket;
+
+class MacBaseSocketServer : public PhysicalSocketServer {
+ public:
+  MacBaseSocketServer();
+  virtual ~MacBaseSocketServer();
+
+  // SocketServer Interface
+  virtual Socket* CreateSocket(int type) { return NULL; }
+  virtual Socket* CreateSocket(int family, int type) { return NULL; }
+
+  virtual AsyncSocket* CreateAsyncSocket(int type);
+  virtual AsyncSocket* CreateAsyncSocket(int family, int type);
+
+  virtual bool Wait(int cms, bool process_io) = 0;
+  virtual void WakeUp() = 0;
+
+  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() {
+    return sockets_;
+  }
+
+ private:
+  static void FileDescriptorCallback(CFFileDescriptorRef ref,
+                                     CFOptionFlags flags,
+                                     void* context);
+
+  std::set<MacAsyncSocket*> sockets_;
+};
+
+// Core Foundation implementation of the socket server. While idle it
+// will run the current CF run loop. When the socket server has work
+// to do the run loop will be paused. Does not support Carbon or Cocoa
+// UI interaction.
+class MacCFSocketServer : public MacBaseSocketServer {
+ public:
+  MacCFSocketServer();
+  virtual ~MacCFSocketServer();
+
+  // SocketServer Interface
+  virtual bool Wait(int cms, bool process_io);
+  virtual void WakeUp();
+  void OnWakeUpCallback();
+
+ private:
+  CFRunLoopRef run_loop_;
+  CFRunLoopSourceRef wake_up_;
+};
+
+#ifndef CARBON_DEPRECATED
+
+///////////////////////////////////////////////////////////////////////////////
+// MacCarbonSocketServer
+///////////////////////////////////////////////////////////////////////////////
+
+// Interacts with the Carbon event queue. While idle it will block,
+// waiting for events. When the socket server has work to do, it will
+// post a 'wake up' event to the queue, causing the thread to exit the
+// event loop until the next call to Wait. Other events are dispatched
+// to their target. Supports Carbon and Cocoa UI interaction.
+class MacCarbonSocketServer : public MacBaseSocketServer {
+ public:
+  MacCarbonSocketServer();
+  virtual ~MacCarbonSocketServer();
+
+  // SocketServer Interface
+  virtual bool Wait(int cms, bool process_io);
+  virtual void WakeUp();
+
+ private:
+  EventQueueRef event_queue_;
+  EventRef wake_up_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// MacCarbonAppSocketServer
+///////////////////////////////////////////////////////////////////////////////
+
+// Runs the Carbon application event loop on the current thread while
+// idle. When the socket server has work to do, it will post an event
+// to the queue, causing the thread to exit the event loop until the
+// next call to Wait. Other events are automatically dispatched to
+// their target.
+class MacCarbonAppSocketServer : public MacBaseSocketServer {
+ public:
+  MacCarbonAppSocketServer();
+  virtual ~MacCarbonAppSocketServer();
+
+  // SocketServer Interface
+  virtual bool Wait(int cms, bool process_io);
+  virtual void WakeUp();
+
+ private:
+  static OSStatus WakeUpEventHandler(EventHandlerCallRef next, EventRef event,
+                                     void *data);
+  static void TimerHandler(EventLoopTimerRef timer, void *data);
+
+  EventQueueRef event_queue_;
+  EventHandlerRef event_handler_;
+  EventLoopTimerRef timer_;
+};
+
+#endif
+} // namespace talk_base
+
+#endif  // TALK_BASE_MACSOCKETSERVER_H__
diff --git a/talk/base/macsocketserver_unittest.cc b/talk/base/macsocketserver_unittest.cc
new file mode 100644
index 0000000..07cce26
--- /dev/null
+++ b/talk/base/macsocketserver_unittest.cc
@@ -0,0 +1,250 @@
+/*
+ * 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_;
+};
+
+#ifndef CARBON_DEPRECATED
+
+// 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);
+}
+
+#endif
+
+// 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, TestConnectIPv4) {
+  SocketTest::TestConnectIPv4();
+}
+
+TEST_F(MacAsyncSocketTest, TestConnectIPv6) {
+  SocketTest::TestConnectIPv6();
+}
+
+TEST_F(MacAsyncSocketTest, TestConnectWithDnsLookupIPv4) {
+  SocketTest::TestConnectWithDnsLookupIPv4();
+}
+
+TEST_F(MacAsyncSocketTest, TestConnectWithDnsLookupIPv6) {
+  SocketTest::TestConnectWithDnsLookupIPv6();
+}
+
+TEST_F(MacAsyncSocketTest, TestConnectFailIPv4) {
+  SocketTest::TestConnectFailIPv4();
+}
+
+TEST_F(MacAsyncSocketTest, TestConnectFailIPv6) {
+  SocketTest::TestConnectFailIPv6();
+}
+
+// Reenable once we have mac async dns
+TEST_F(MacAsyncSocketTest, DISABLED_TestConnectWithDnsLookupFailIPv4) {
+  SocketTest::TestConnectWithDnsLookupFailIPv4();
+}
+
+TEST_F(MacAsyncSocketTest, DISABLED_TestConnectWithDnsLookupFailIPv6) {
+  SocketTest::TestConnectWithDnsLookupFailIPv6();
+}
+
+TEST_F(MacAsyncSocketTest, TestConnectWithClosedSocketIPv4) {
+  SocketTest::TestConnectWithClosedSocketIPv4();
+}
+
+TEST_F(MacAsyncSocketTest, TestConnectWithClosedSocketIPv6) {
+  SocketTest::TestConnectWithClosedSocketIPv6();
+}
+
+// Flaky at the moment (10% failure rate).  Seems the client doesn't get
+// signalled in a timely manner...
+TEST_F(MacAsyncSocketTest, DISABLED_TestServerCloseDuringConnectIPv4) {
+  SocketTest::TestServerCloseDuringConnectIPv4();
+}
+
+TEST_F(MacAsyncSocketTest, DISABLED_TestServerCloseDuringConnectIPv6) {
+  SocketTest::TestServerCloseDuringConnectIPv6();
+}
+// Flaky at the moment (0.5% failure rate).  Seems the client doesn't get
+// signalled in a timely manner...
+TEST_F(MacAsyncSocketTest, TestClientCloseDuringConnectIPv4) {
+  SocketTest::TestClientCloseDuringConnectIPv4();
+}
+
+TEST_F(MacAsyncSocketTest, TestClientCloseDuringConnectIPv6) {
+  SocketTest::TestClientCloseDuringConnectIPv6();
+}
+
+TEST_F(MacAsyncSocketTest, TestServerCloseIPv4) {
+  SocketTest::TestServerCloseIPv4();
+}
+
+TEST_F(MacAsyncSocketTest, TestServerCloseIPv6) {
+  SocketTest::TestServerCloseIPv6();
+}
+
+TEST_F(MacAsyncSocketTest, TestCloseInClosedCallbackIPv4) {
+  SocketTest::TestCloseInClosedCallbackIPv4();
+}
+
+TEST_F(MacAsyncSocketTest, TestCloseInClosedCallbackIPv6) {
+  SocketTest::TestCloseInClosedCallbackIPv6();
+}
+
+TEST_F(MacAsyncSocketTest, TestSocketServerWaitIPv4) {
+  SocketTest::TestSocketServerWaitIPv4();
+}
+
+TEST_F(MacAsyncSocketTest, TestSocketServerWaitIPv6) {
+  SocketTest::TestSocketServerWaitIPv6();
+}
+
+TEST_F(MacAsyncSocketTest, TestTcpIPv4) {
+  SocketTest::TestTcpIPv4();
+}
+
+TEST_F(MacAsyncSocketTest, TestTcpIPv6) {
+  SocketTest::TestTcpIPv6();
+}
+
+TEST_F(MacAsyncSocketTest, TestSingleFlowControlCallbackIPv4) {
+  SocketTest::TestSingleFlowControlCallbackIPv4();
+}
+
+TEST_F(MacAsyncSocketTest, TestSingleFlowControlCallbackIPv6) {
+  SocketTest::TestSingleFlowControlCallbackIPv6();
+}
+
+TEST_F(MacAsyncSocketTest, DISABLED_TestUdpIPv4) {
+  SocketTest::TestUdpIPv4();
+}
+
+TEST_F(MacAsyncSocketTest, DISABLED_TestUdpIPv6) {
+  SocketTest::TestUdpIPv6();
+}
+
+TEST_F(MacAsyncSocketTest, DISABLED_TestGetSetOptionsIPv4) {
+  SocketTest::TestGetSetOptionsIPv4();
+}
+
+TEST_F(MacAsyncSocketTest, DISABLED_TestGetSetOptionsIPv6) {
+  SocketTest::TestGetSetOptionsIPv6();
+}
+
+#ifndef CARBON_DEPRECATED
+class MacCarbonAppAsyncSocketTest : public MacAsyncSocketTest {
+  virtual MacBaseSocketServer* CreateSocketServer() {
+    return new MacCarbonAppSocketServer();
+  };
+};
+
+TEST_F(MacCarbonAppAsyncSocketTest, TestSocketServerWaitIPv4) {
+  SocketTest::TestSocketServerWaitIPv4();
+}
+
+TEST_F(MacCarbonAppAsyncSocketTest, TestSocketServerWaitIPv6) {
+  SocketTest::TestSocketServerWaitIPv6();
+}
+#endif
+}  // namespace talk_base
diff --git a/talk/base/macutils.cc b/talk/base/macutils.cc
new file mode 100644
index 0000000..c73b0fa
--- /dev/null
+++ b/talk/base/macutils.cc
@@ -0,0 +1,231 @@
+/*
+ * 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 <sstream>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/macutils.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stringutils.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool ToUtf8(const CFStringRef str16, std::string* str8) {
+  if ((NULL == str16) || (NULL == str8))
+    return false;
+  size_t maxlen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str16),
+                                                    kCFStringEncodingUTF8)
+                  + 1;
+  scoped_array<char> buffer(new char[maxlen]);
+  if (!buffer || !CFStringGetCString(str16, buffer.get(), maxlen,
+                                     kCFStringEncodingUTF8))
+    return false;
+  str8->assign(buffer.get());
+  return true;
+}
+
+bool ToUtf16(const std::string& str8, CFStringRef* str16) {
+  if (NULL == str16)
+    return false;
+  *str16 = CFStringCreateWithBytes(kCFAllocatorDefault,
+                                   reinterpret_cast<const UInt8*>(str8.data()),
+                                   str8.length(), kCFStringEncodingUTF8,
+                                   false);
+  return (NULL != *str16);
+}
+
+#ifdef OSX
+void DecodeFourChar(UInt32 fc, std::string* out) {
+  std::stringstream ss;
+  ss << '\'';
+  bool printable = true;
+  for (int i = 3; i >= 0; --i) {
+    char ch = (fc >> (8 * i)) & 0xFF;
+    if (isprint(static_cast<unsigned char>(ch))) {
+      ss << ch;
+    } else {
+      printable = false;
+      break;
+    }
+  }
+  if (printable) {
+    ss << '\'';
+  } else {
+    ss.str("");
+    ss << "0x" << std::hex << fc;
+  }
+  out->append(ss.str());
+}
+
+static bool GetGestalt(OSType ostype, int* value) {
+  ASSERT(NULL != value);
+  SInt32 native_value;
+  OSStatus result = Gestalt(ostype, &native_value);
+  if (noErr == result) {
+    *value = native_value;
+    return true;
+  }
+  std::string str;
+  DecodeFourChar(ostype, &str);
+  LOG_E(LS_ERROR, OS, result) << "Gestalt(" << str << ")";
+  return false;
+}
+
+bool GetOSVersion(int* major, int* minor, int* bugfix) {
+  ASSERT(major && minor && bugfix);
+  if (!GetGestalt(gestaltSystemVersion, major))
+    return false;
+  if (*major < 0x1040) {
+    *bugfix = *major & 0xF;
+    *minor = (*major >> 4) & 0xF;
+    *major = (*major >> 8);
+    return true;
+  }
+  return GetGestalt(gestaltSystemVersionMajor, major)
+      && GetGestalt(gestaltSystemVersionMinor, minor)
+      && GetGestalt(gestaltSystemVersionBugFix, bugfix);
+}
+
+MacOSVersionName GetOSVersionName() {
+  int major = 0, minor = 0, bugfix = 0;
+  if (!GetOSVersion(&major, &minor, &bugfix))
+    return kMacOSUnknown;
+  if (major > 10) {
+    return kMacOSNewer;
+  }
+  if ((major < 10) || (minor < 3)) {
+    return kMacOSOlder;
+  }
+  switch (minor) {
+    case 3:
+      return kMacOSPanther;
+    case 4:
+      return kMacOSTiger;
+    case 5:
+      return kMacOSLeopard;
+    case 6:
+      return kMacOSSnowLeopard;
+    case 7:
+      return kMacOSLion;
+    case 8:
+      return kMacOSMountainLion;
+  }
+  return kMacOSNewer;
+}
+
+bool GetQuickTimeVersion(std::string* out) {
+  int ver;
+  if (!GetGestalt(gestaltQuickTimeVersion, &ver))
+    return false;
+
+  std::stringstream ss;
+  ss << std::hex << ver;
+  *out = ss.str();
+  return true;
+}
+
+bool RunAppleScript(const std::string& script) {
+  // TODO(thaloun): Add a .mm file that contains something like this:
+  // NSString source from script
+  // NSAppleScript* appleScript = [[NSAppleScript alloc] initWithSource:&source]
+  // if (appleScript != nil) {
+  //   [appleScript executeAndReturnError:nil]
+  //   [appleScript release]
+#ifndef CARBON_DEPRECATED
+  ComponentInstance component = NULL;
+  AEDesc script_desc;
+  AEDesc result_data;
+  OSStatus err;
+  OSAID script_id, result_id;
+
+  AECreateDesc(typeNull, NULL, 0, &script_desc);
+  AECreateDesc(typeNull, NULL, 0, &result_data);
+  script_id = kOSANullScript;
+  result_id = kOSANullScript;
+
+  component = OpenDefaultComponent(kOSAComponentType, typeAppleScript);
+  if (component == NULL) {
+    LOG(LS_ERROR) << "Failed opening Apple Script component";
+    return false;
+  }
+  err = AECreateDesc(typeUTF8Text, script.data(), script.size(), &script_desc);
+  if (err != noErr) {
+    CloseComponent(component);
+    LOG(LS_ERROR) << "Failed creating Apple Script description";
+    return false;
+  }
+
+  err = OSACompile(component, &script_desc, kOSAModeCanInteract, &script_id);
+  if (err != noErr) {
+    AEDisposeDesc(&script_desc);
+    if (script_id != kOSANullScript) {
+      OSADispose(component, script_id);
+    }
+    CloseComponent(component);
+    LOG(LS_ERROR) << "Error compiling Apple Script";
+    return false;
+  }
+
+  err = OSAExecute(component, script_id, kOSANullScript, kOSAModeCanInteract,
+                   &result_id);
+
+  if (err == errOSAScriptError) {
+    LOG(LS_ERROR) << "Error when executing Apple Script: " << script;
+    AECreateDesc(typeNull, NULL, 0, &result_data);
+    OSAScriptError(component, kOSAErrorMessage, typeChar, &result_data);
+    int len = AEGetDescDataSize(&result_data);
+    char* data = (char*) malloc(len);
+    if (data != NULL) {
+      err = AEGetDescData(&result_data, data, len);
+      LOG(LS_ERROR) << "Script error: " << data;
+    }
+    AEDisposeDesc(&script_desc);
+    AEDisposeDesc(&result_data);
+    return false;
+  }
+  AEDisposeDesc(&script_desc);
+  if (script_id != kOSANullScript) {
+    OSADispose(component, script_id);
+  }
+  if (result_id != kOSANullScript) {
+    OSADispose(component, result_id);
+  }
+  CloseComponent(component);
+  return true;
+#else
+  // TODO(thaloun): Support applescripts with the NSAppleScript API.
+  return false;
+#endif  // CARBON_DEPRECATED
+}
+#endif  // OSX
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
diff --git a/talk/base/macutils.h b/talk/base/macutils.h
new file mode 100644
index 0000000..ad5e7ad
--- /dev/null
+++ b/talk/base/macutils.h
@@ -0,0 +1,75 @@
+/*
+ * libjingle
+ * Copyright 2007 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_MACUTILS_H__
+#define TALK_BASE_MACUTILS_H__
+
+#include <CoreFoundation/CoreFoundation.h>
+#ifdef OSX
+#include <Carbon/Carbon.h>
+#endif
+#include <string>
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Note that some of these functions work for both iOS and Mac OS X.  The ones
+// that are specific to Mac are #ifdef'ed as such.
+
+bool ToUtf8(const CFStringRef str16, std::string* str8);
+bool ToUtf16(const std::string& str8, CFStringRef* str16);
+
+#ifdef OSX
+void DecodeFourChar(UInt32 fc, std::string* out);
+
+enum MacOSVersionName {
+  kMacOSUnknown,       // ???
+  kMacOSOlder,         // 10.2-
+  kMacOSPanther,       // 10.3
+  kMacOSTiger,         // 10.4
+  kMacOSLeopard,       // 10.5
+  kMacOSSnowLeopard,   // 10.6
+  kMacOSLion,          // 10.7
+  kMacOSMountainLion,  // 10.8
+  kMacOSNewer,         // 10.9+
+};
+
+bool GetOSVersion(int* major, int* minor, int* bugfix);
+MacOSVersionName GetOSVersionName();
+bool GetQuickTimeVersion(std::string* version);
+
+// Runs the given apple script. Only supports scripts that does not
+// require user interaction.
+bool RunAppleScript(const std::string& script);
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_MACUTILS_H__
diff --git a/talk/base/macutils_unittest.cc b/talk/base/macutils_unittest.cc
new file mode 100644
index 0000000..25858a27
--- /dev/null
+++ b/talk/base/macutils_unittest.cc
@@ -0,0 +1,58 @@
+/*
+ * 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));
+}
+
+#ifdef CARBON_DEPRECATED
+TEST(MacUtilsTest, DISABLED_RunAppleScriptSuccess) {
+#else
+TEST(MacUtilsTest, RunAppleScriptSuccess) {
+#endif
+  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..209b4ab
--- /dev/null
+++ b/talk/base/macwindowpicker.cc
@@ -0,0 +1,250 @@
+// 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, "");
+    desc.set_primary(CGDisplayIsMain(id.id()));
+    descriptions->push_back(desc);
+  }
+  return display_count > 0;
+}
+
+bool MacWindowPicker::GetDesktopDimensions(const DesktopId& id,
+                                           int* width,
+                                           int* height) {
+  *width = CGDisplayPixelsWide(id.id());
+  *height = CGDisplayPixelsHigh(id.id());
+  return true;
+}
+
+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..85fcc36
--- /dev/null
+++ b/talk/base/macwindowpicker.h
@@ -0,0 +1,31 @@
+// 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);
+  virtual bool GetDesktopDimensions(const DesktopId& id, int* width,
+                                    int* height);
+
+ 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/md5.cc b/talk/base/md5.cc
new file mode 100644
index 0000000..84e06f9
--- /dev/null
+++ b/talk/base/md5.cc
@@ -0,0 +1,218 @@
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest.  This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+
+// Changes from original C code:
+// Ported to C++, type casting, Google code style.
+
+#include "talk/base/md5.h"
+
+// TODO: Avoid memcmpy - hash directly from memory.
+#include <string.h>  // for memcpy().
+
+#include "talk/base/byteorder.h"  // for ARCH_CPU_LITTLE_ENDIAN.
+
+#ifdef ARCH_CPU_LITTLE_ENDIAN
+#define ByteReverse(buf, len)  // Nothing.
+#else  // ARCH_CPU_BIG_ENDIAN
+static void ByteReverse(uint32* buf, int len) {
+  for (int i = 0; i < len; ++i) {
+    buf[i] = talk_base::GetLE32(&buf[i]);
+  }
+}
+#endif
+
+// Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
+// initialization constants.
+void MD5Init(MD5Context* ctx) {
+  ctx->buf[0] = 0x67452301;
+  ctx->buf[1] = 0xefcdab89;
+  ctx->buf[2] = 0x98badcfe;
+  ctx->buf[3] = 0x10325476;
+  ctx->bits[0] = 0;
+  ctx->bits[1] = 0;
+}
+
+// Update context to reflect the concatenation of another buffer full of bytes.
+void MD5Update(MD5Context* ctx, const uint8* buf, size_t len) {
+  // Update bitcount.
+  uint32 t = ctx->bits[0];
+  if ((ctx->bits[0] = t + (static_cast<uint32>(len) << 3)) < t) {
+    ctx->bits[1]++;  // Carry from low to high.
+  }
+  ctx->bits[1] += len >> 29;
+  t = (t >> 3) & 0x3f;  // Bytes already in shsInfo->data.
+
+  // Handle any leading odd-sized chunks.
+  if (t) {
+    uint8* p = reinterpret_cast<uint8*>(ctx->in) + t;
+
+    t = 64-t;
+    if (len < t) {
+      memcpy(p, buf, len);
+      return;
+    }
+    memcpy(p, buf, t);
+    ByteReverse(ctx->in, 16);
+    MD5Transform(ctx->buf, ctx->in);
+    buf += t;
+    len -= t;
+  }
+
+  // Process data in 64-byte chunks.
+  while (len >= 64) {
+    memcpy(ctx->in, buf, 64);
+    ByteReverse(ctx->in, 16);
+    MD5Transform(ctx->buf, ctx->in);
+    buf += 64;
+    len -= 64;
+  }
+
+  // Handle any remaining bytes of data.
+  memcpy(ctx->in, buf, len);
+}
+
+// Final wrapup - pad to 64-byte boundary with the bit pattern.
+// 1 0* (64-bit count of bits processed, MSB-first)
+void MD5Final(MD5Context* ctx, uint8 digest[16]) {
+  // Compute number of bytes mod 64.
+  uint32 count = (ctx->bits[0] >> 3) & 0x3F;
+
+  // Set the first char of padding to 0x80.  This is safe since there is
+  // always at least one byte free.
+  uint8* p = reinterpret_cast<uint8*>(ctx->in) + count;
+  *p++ = 0x80;
+
+  // Bytes of padding needed to make 64 bytes.
+  count = 64 - 1 - count;
+
+  // Pad out to 56 mod 64.
+  if (count < 8) {
+    // Two lots of padding:  Pad the first block to 64 bytes.
+    memset(p, 0, count);
+    ByteReverse(ctx->in, 16);
+    MD5Transform(ctx->buf, ctx->in);
+
+    // Now fill the next block with 56 bytes.
+    memset(ctx->in, 0, 56);
+  } else {
+    // Pad block to 56 bytes.
+    memset(p, 0, count - 8);
+  }
+  ByteReverse(ctx->in, 14);
+
+  // Append length in bits and transform.
+  ctx->in[14] = ctx->bits[0];
+  ctx->in[15] = ctx->bits[1];
+
+  MD5Transform(ctx->buf, ctx->in);
+  ByteReverse(ctx->buf, 4);
+  memcpy(digest, ctx->buf, 16);
+  memset(ctx, 0, sizeof(*ctx));  // In case it's sensitive.
+}
+
+// The four core functions - F1 is optimized somewhat.
+// #define F1(x, y, z) (x & y | ~x & z)
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+// This is the central step in the MD5 algorithm.
+#define MD5STEP(f, w, x, y, z, data, s) \
+    (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x)
+
+// The core of the MD5 algorithm, this alters an existing MD5 hash to
+// reflect the addition of 16 longwords of new data.  MD5Update blocks
+// the data and converts bytes into longwords for this routine.
+void MD5Transform(uint32 buf[4], const uint32 in[16]) {
+  uint32 a = buf[0];
+  uint32 b = buf[1];
+  uint32 c = buf[2];
+  uint32 d = buf[3];
+
+  MD5STEP(F1, a, b, c, d, in[ 0] + 0xd76aa478, 7);
+  MD5STEP(F1, d, a, b, c, in[ 1] + 0xe8c7b756, 12);
+  MD5STEP(F1, c, d, a, b, in[ 2] + 0x242070db, 17);
+  MD5STEP(F1, b, c, d, a, in[ 3] + 0xc1bdceee, 22);
+  MD5STEP(F1, a, b, c, d, in[ 4] + 0xf57c0faf, 7);
+  MD5STEP(F1, d, a, b, c, in[ 5] + 0x4787c62a, 12);
+  MD5STEP(F1, c, d, a, b, in[ 6] + 0xa8304613, 17);
+  MD5STEP(F1, b, c, d, a, in[ 7] + 0xfd469501, 22);
+  MD5STEP(F1, a, b, c, d, in[ 8] + 0x698098d8, 7);
+  MD5STEP(F1, d, a, b, c, in[ 9] + 0x8b44f7af, 12);
+  MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+  MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+  MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+  MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+  MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+  MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+  MD5STEP(F2, a, b, c, d, in[ 1] + 0xf61e2562, 5);
+  MD5STEP(F2, d, a, b, c, in[ 6] + 0xc040b340, 9);
+  MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+  MD5STEP(F2, b, c, d, a, in[ 0] + 0xe9b6c7aa, 20);
+  MD5STEP(F2, a, b, c, d, in[ 5] + 0xd62f105d, 5);
+  MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+  MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+  MD5STEP(F2, b, c, d, a, in[ 4] + 0xe7d3fbc8, 20);
+  MD5STEP(F2, a, b, c, d, in[ 9] + 0x21e1cde6, 5);
+  MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+  MD5STEP(F2, c, d, a, b, in[ 3] + 0xf4d50d87, 14);
+  MD5STEP(F2, b, c, d, a, in[ 8] + 0x455a14ed, 20);
+  MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+  MD5STEP(F2, d, a, b, c, in[ 2] + 0xfcefa3f8, 9);
+  MD5STEP(F2, c, d, a, b, in[ 7] + 0x676f02d9, 14);
+  MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+  MD5STEP(F3, a, b, c, d, in[ 5] + 0xfffa3942, 4);
+  MD5STEP(F3, d, a, b, c, in[ 8] + 0x8771f681, 11);
+  MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+  MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+  MD5STEP(F3, a, b, c, d, in[ 1] + 0xa4beea44, 4);
+  MD5STEP(F3, d, a, b, c, in[ 4] + 0x4bdecfa9, 11);
+  MD5STEP(F3, c, d, a, b, in[ 7] + 0xf6bb4b60, 16);
+  MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+  MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+  MD5STEP(F3, d, a, b, c, in[ 0] + 0xeaa127fa, 11);
+  MD5STEP(F3, c, d, a, b, in[ 3] + 0xd4ef3085, 16);
+  MD5STEP(F3, b, c, d, a, in[ 6] + 0x04881d05, 23);
+  MD5STEP(F3, a, b, c, d, in[ 9] + 0xd9d4d039, 4);
+  MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+  MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+  MD5STEP(F3, b, c, d, a, in[ 2] + 0xc4ac5665, 23);
+
+  MD5STEP(F4, a, b, c, d, in[ 0] + 0xf4292244, 6);
+  MD5STEP(F4, d, a, b, c, in[ 7] + 0x432aff97, 10);
+  MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+  MD5STEP(F4, b, c, d, a, in[ 5] + 0xfc93a039, 21);
+  MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+  MD5STEP(F4, d, a, b, c, in[ 3] + 0x8f0ccc92, 10);
+  MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+  MD5STEP(F4, b, c, d, a, in[ 1] + 0x85845dd1, 21);
+  MD5STEP(F4, a, b, c, d, in[ 8] + 0x6fa87e4f, 6);
+  MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+  MD5STEP(F4, c, d, a, b, in[ 6] + 0xa3014314, 15);
+  MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+  MD5STEP(F4, a, b, c, d, in[ 4] + 0xf7537e82, 6);
+  MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+  MD5STEP(F4, c, d, a, b, in[ 2] + 0x2ad7d2bb, 15);
+  MD5STEP(F4, b, c, d, a, in[ 9] + 0xeb86d391, 21);
+  buf[0] += a;
+  buf[1] += b;
+  buf[2] += c;
+  buf[3] += d;
+}
diff --git a/talk/base/md5.h b/talk/base/md5.h
new file mode 100644
index 0000000..3aba3d2
--- /dev/null
+++ b/talk/base/md5.h
@@ -0,0 +1,40 @@
+/*
+ * This is the header file for the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest.  This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ *
+ */
+
+// Changes(fbarchard): Ported to C++ and Google style guide.
+// Made context first parameter in MD5Final for consistency with Sha1.
+
+#ifndef TALK_BASE_MD5_H_
+#define TALK_BASE_MD5_H_
+
+#include "talk/base/basictypes.h"
+
+// Canonical name for a MD5 context structure, used in many crypto libs.
+typedef struct MD5Context MD5_CTX;
+
+struct MD5Context {
+  uint32 buf[4];
+  uint32 bits[2];
+  uint32 in[16];
+};
+
+void MD5Init(MD5Context* context);
+void MD5Update(MD5Context* context, const uint8* data, size_t len);
+void MD5Final(MD5Context* context, uint8 digest[16]);
+void MD5Transform(uint32 buf[4], const uint32 in[16]);
+
+#endif  // TALK_BASE_MD5_H_
diff --git a/talk/base/md5digest.h b/talk/base/md5digest.h
new file mode 100644
index 0000000..a6c2ea9
--- /dev/null
+++ b/talk/base/md5digest.h
@@ -0,0 +1,63 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_MD5DIGEST_H_
+#define TALK_BASE_MD5DIGEST_H_
+
+#include "talk/base/md5.h"
+#include "talk/base/messagedigest.h"
+
+namespace talk_base {
+
+// A simple wrapper for our MD5 implementation.
+class Md5Digest : public MessageDigest {
+ public:
+  enum { kSize = 16 };
+  Md5Digest() {
+    MD5Init(&ctx_);
+  }
+  virtual size_t Size() const {
+    return kSize;
+  }
+  virtual void Update(const void* buf, size_t len) {
+    MD5Update(&ctx_, static_cast<const uint8*>(buf), len);
+  }
+  virtual size_t Finish(void* buf, size_t len) {
+    if (len < kSize) {
+      return 0;
+    }
+    MD5Final(&ctx_, static_cast<uint8*>(buf));
+    MD5Init(&ctx_);  // Reset for next use.
+    return kSize;
+  }
+ private:
+  MD5_CTX ctx_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_MD5DIGEST_H_
diff --git a/talk/base/md5digest_unittest.cc b/talk/base/md5digest_unittest.cc
new file mode 100644
index 0000000..40b19e5
--- /dev/null
+++ b/talk/base/md5digest_unittest.cc
@@ -0,0 +1,96 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/md5digest.h"
+#include "talk/base/gunit.h"
+#include "talk/base/stringencode.h"
+
+namespace talk_base {
+
+std::string Md5(const std::string& input) {
+  Md5Digest md5;
+  return ComputeDigest(&md5, input);
+}
+
+TEST(Md5DigestTest, TestSize) {
+  Md5Digest md5;
+  EXPECT_EQ(16U, Md5Digest::kSize);
+  EXPECT_EQ(16U, md5.Size());
+}
+
+TEST(Md5DigestTest, TestBasic) {
+  // These are the standard MD5 test vectors from RFC 1321.
+  EXPECT_EQ("d41d8cd98f00b204e9800998ecf8427e", Md5(""));
+  EXPECT_EQ("0cc175b9c0f1b6a831c399e269772661", Md5("a"));
+  EXPECT_EQ("900150983cd24fb0d6963f7d28e17f72", Md5("abc"));
+  EXPECT_EQ("f96b697d7cb7938d525a2f31aaf161d0", Md5("message digest"));
+  EXPECT_EQ("c3fcd3d76192e4007dfb496cca67e13b",
+            Md5("abcdefghijklmnopqrstuvwxyz"));
+}
+
+TEST(Md5DigestTest, TestMultipleUpdates) {
+  Md5Digest md5;
+  std::string input = "abcdefghijklmnopqrstuvwxyz";
+  char output[Md5Digest::kSize];
+  for (size_t i = 0; i < input.size(); ++i) {
+    md5.Update(&input[i], 1);
+  }
+  EXPECT_EQ(md5.Size(), md5.Finish(output, sizeof(output)));
+  EXPECT_EQ("c3fcd3d76192e4007dfb496cca67e13b",
+            hex_encode(output, sizeof(output)));
+}
+
+TEST(Md5DigestTest, TestReuse) {
+  Md5Digest md5;
+  std::string input = "message digest";
+  EXPECT_EQ("f96b697d7cb7938d525a2f31aaf161d0", ComputeDigest(&md5, input));
+  input = "abcdefghijklmnopqrstuvwxyz";
+  EXPECT_EQ("c3fcd3d76192e4007dfb496cca67e13b", ComputeDigest(&md5, input));
+}
+
+TEST(Md5DigestTest, TestBufferTooSmall) {
+  Md5Digest md5;
+  std::string input = "abcdefghijklmnopqrstuvwxyz";
+  char output[Md5Digest::kSize - 1];
+  md5.Update(input.c_str(), input.size());
+  EXPECT_EQ(0U, md5.Finish(output, sizeof(output)));
+}
+
+TEST(Md5DigestTest, TestBufferConst) {
+  Md5Digest md5;
+  const int kLongSize = 1000000;
+  std::string input(kLongSize, '\0');
+  for (int i = 0; i < kLongSize; ++i) {
+    input[i] = static_cast<char>(i);
+  }
+  md5.Update(input.c_str(), input.size());
+  for (int i = 0; i < kLongSize; ++i) {
+    EXPECT_EQ(static_cast<char>(i), input[i]);
+  }
+}
+
+}  // namespace talk_base
diff --git a/talk/base/messagedigest.cc b/talk/base/messagedigest.cc
new file mode 100644
index 0000000..6136ae2
--- /dev/null
+++ b/talk/base/messagedigest.cc
@@ -0,0 +1,184 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/messagedigest.h"
+
+#include <string.h>
+
+#include "talk/base/sslconfig.h"
+#if SSL_USE_OPENSSL
+#include "talk/base/openssldigest.h"
+#else
+#include "talk/base/md5digest.h"
+#include "talk/base/sha1digest.h"
+#endif
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stringencode.h"
+
+namespace talk_base {
+
+// From RFC 4572.
+const char DIGEST_MD5[]     = "md5";
+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";
+
+static const size_t kBlockSize = 64;  // valid for SHA-256 and down
+
+MessageDigest* MessageDigestFactory::Create(const std::string& alg) {
+#if SSL_USE_OPENSSL
+  MessageDigest* digest = new OpenSSLDigest(alg);
+  if (digest->Size() == 0) {  // invalid algorithm
+    delete digest;
+    digest = NULL;
+  }
+  return digest;
+#else
+  MessageDigest* digest = NULL;
+  if (alg == DIGEST_MD5) {
+    digest = new Md5Digest();
+  } else if (alg == DIGEST_SHA_1) {
+    digest = new Sha1Digest();
+  }
+  return digest;
+#endif
+}
+
+size_t ComputeDigest(MessageDigest* digest, const void* input, size_t in_len,
+                     void* output, size_t out_len) {
+  digest->Update(input, in_len);
+  return digest->Finish(output, out_len);
+}
+
+size_t ComputeDigest(const std::string& alg, const void* input, size_t in_len,
+                     void* output, size_t out_len) {
+  scoped_ptr<MessageDigest> digest(MessageDigestFactory::Create(alg));
+  return (digest) ?
+      ComputeDigest(digest.get(), input, in_len, output, out_len) :
+      0;
+}
+
+std::string ComputeDigest(MessageDigest* digest, const std::string& input) {
+  scoped_array<char> output(new char[digest->Size()]);
+  ComputeDigest(digest, input.data(), input.size(),
+                output.get(), digest->Size());
+  return hex_encode(output.get(), digest->Size());
+}
+
+bool ComputeDigest(const std::string& alg, const std::string& input,
+                   std::string* output) {
+  scoped_ptr<MessageDigest> digest(MessageDigestFactory::Create(alg));
+  if (!digest) {
+    return false;
+  }
+  *output = ComputeDigest(digest.get(), input);
+  return true;
+}
+
+std::string ComputeDigest(const std::string& alg, const std::string& input) {
+  std::string output;
+  ComputeDigest(alg, input, &output);
+  return output;
+}
+
+// Compute a RFC 2104 HMAC: H(K XOR opad, H(K XOR ipad, text))
+size_t ComputeHmac(MessageDigest* digest,
+                   const void* key, size_t key_len,
+                   const void* input, size_t in_len,
+                   void* output, size_t out_len) {
+  // We only handle algorithms with a 64-byte blocksize.
+  // TODO: Add BlockSize() method to MessageDigest.
+  size_t block_len = kBlockSize;
+  if (digest->Size() > 32) {
+    return 0;
+  }
+  // Copy the key to a block-sized buffer to simplify padding.
+  // If the key is longer than a block, hash it and use the result instead.
+  scoped_array<uint8> new_key(new uint8[block_len]);
+  if (key_len > block_len) {
+    ComputeDigest(digest, key, key_len, new_key.get(), block_len);
+    memset(new_key.get() + digest->Size(), 0, block_len - digest->Size());
+  } else {
+    memcpy(new_key.get(), key, key_len);
+    memset(new_key.get() + key_len, 0, block_len - key_len);
+  }
+  // Set up the padding from the key, salting appropriately for each padding.
+  scoped_array<uint8> o_pad(new uint8[block_len]), i_pad(new uint8[block_len]);
+  for (size_t i = 0; i < block_len; ++i) {
+    o_pad[i] = 0x5c ^ new_key[i];
+    i_pad[i] = 0x36 ^ new_key[i];
+  }
+  // Inner hash; hash the inner padding, and then the input buffer.
+  scoped_array<uint8> inner(new uint8[digest->Size()]);
+  digest->Update(i_pad.get(), block_len);
+  digest->Update(input, in_len);
+  digest->Finish(inner.get(), digest->Size());
+  // Outer hash; hash the outer padding, and then the result of the inner hash.
+  digest->Update(o_pad.get(), block_len);
+  digest->Update(inner.get(), digest->Size());
+  return digest->Finish(output, out_len);
+}
+
+size_t ComputeHmac(const std::string& alg, const void* key, size_t key_len,
+                   const void* input, size_t in_len,
+                   void* output, size_t out_len) {
+  scoped_ptr<MessageDigest> digest(MessageDigestFactory::Create(alg));
+  if (!digest) {
+    return 0;
+  }
+  return ComputeHmac(digest.get(), key, key_len,
+                     input, in_len, output, out_len);
+}
+
+std::string ComputeHmac(MessageDigest* digest, const std::string& key,
+                        const std::string& input) {
+  scoped_array<char> output(new char[digest->Size()]);
+  ComputeHmac(digest, key.data(), key.size(),
+              input.data(), input.size(), output.get(), digest->Size());
+  return hex_encode(output.get(), digest->Size());
+}
+
+bool ComputeHmac(const std::string& alg, const std::string& key,
+                 const std::string& input, std::string* output) {
+  scoped_ptr<MessageDigest> digest(MessageDigestFactory::Create(alg));
+  if (!digest) {
+    return false;
+  }
+  *output = ComputeHmac(digest.get(), key, input);
+  return true;
+}
+
+std::string ComputeHmac(const std::string& alg, const std::string& key,
+                        const std::string& input) {
+  std::string output;
+  ComputeHmac(alg, key, input, &output);
+  return output;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/messagedigest.h b/talk/base/messagedigest.h
new file mode 100644
index 0000000..734082b
--- /dev/null
+++ b/talk/base/messagedigest.h
@@ -0,0 +1,123 @@
+/*
+ * 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_BASE_MESSAGEDIGEST_H_
+#define TALK_BASE_MESSAGEDIGEST_H_
+
+#include <string>
+
+namespace talk_base {
+
+// Definitions for the digest algorithms.
+extern const char DIGEST_MD5[];
+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[];
+
+// A general class for computing hashes.
+class MessageDigest {
+ public:
+  enum { kMaxSize = 64 };  // Maximum known size (SHA-512)
+  virtual ~MessageDigest() {}
+  // Returns the digest output size (e.g. 16 bytes for MD5).
+  virtual size_t Size() const = 0;
+  // Updates the digest with |len| bytes from |buf|.
+  virtual void Update(const void* buf, size_t len) = 0;
+  // Outputs the digest value to |buf| with length |len|.
+  // Returns the number of bytes written, i.e., Size().
+  virtual size_t Finish(void* buf, size_t len) = 0;
+};
+
+// A factory class for creating digest objects.
+class MessageDigestFactory {
+ public:
+  static MessageDigest* Create(const std::string& alg);
+};
+
+// Functions to create hashes.
+
+// Computes the hash of |in_len| bytes of |input|, using the |digest| hash
+// implementation, and outputs the hash to the buffer |output|, which is
+// |out_len| bytes long. Returns the number of bytes written to |output| if
+// successful, or 0 if |out_len| was too small.
+size_t ComputeDigest(MessageDigest* digest, const void* input, size_t in_len,
+                     void* output, size_t out_len);
+// Like the previous function, but creates a digest implementation based on
+// the desired digest name |alg|, e.g. DIGEST_SHA_1. Returns 0 if there is no
+// digest with the given name.
+size_t ComputeDigest(const std::string& alg, const void* input, size_t in_len,
+                     void* output, size_t out_len);
+// Computes the hash of |input| using the |digest| hash implementation, and
+// returns it as a hex-encoded string.
+std::string ComputeDigest(MessageDigest* digest, const std::string& input);
+// Like the previous function, but creates a digest implementation based on
+// the desired digest name |alg|, e.g. DIGEST_SHA_1. Returns empty string if
+// there is no digest with the given name.
+std::string ComputeDigest(const std::string& alg, const std::string& input);
+// Like the previous function, but returns an explicit result code.
+bool ComputeDigest(const std::string& alg, const std::string& input,
+                   std::string* output);
+
+// Shorthand way to compute a hex-encoded hash using MD5.
+inline std::string MD5(const std::string& input) {
+  return ComputeDigest(DIGEST_MD5, input);
+}
+
+// Functions to compute RFC 2104 HMACs.
+
+// Computes the HMAC of |in_len| bytes of |input|, using the |digest| hash
+// implementation and |key_len| bytes of |key| to key the HMAC, and outputs
+// the HMAC to the buffer |output|, which is |out_len| bytes long. Returns the
+// number of bytes written to |output| if successful, or 0 if |out_len| was too
+// small.
+size_t ComputeHmac(MessageDigest* digest, const void* key, size_t key_len,
+                   const void* input, size_t in_len,
+                   void* output, size_t out_len);
+// Like the previous function, but creates a digest implementation based on
+// the desired digest name |alg|, e.g. DIGEST_SHA_1. Returns 0 if there is no
+// digest with the given name.
+size_t ComputeHmac(const std::string& alg, const void* key, size_t key_len,
+                   const void* input, size_t in_len,
+                   void* output, size_t out_len);
+// Computes the HMAC of |input| using the |digest| hash implementation and |key|
+// to key the HMAC, and returns it as a hex-encoded string.
+std::string ComputeHmac(MessageDigest* digest, const std::string& key,
+                        const std::string& input);
+// Like the previous function, but creates a digest implementation based on
+// the desired digest name |alg|, e.g. DIGEST_SHA_1. Returns empty string if
+// there is no digest with the given name.
+std::string ComputeHmac(const std::string& alg, const std::string& key,
+                        const std::string& input);
+// Like the previous function, but returns an explicit result code.
+bool ComputeHmac(const std::string& alg, const std::string& key,
+                 const std::string& input, std::string* output);
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_MESSAGEDIGEST_H_
diff --git a/talk/base/messagedigest_unittest.cc b/talk/base/messagedigest_unittest.cc
new file mode 100644
index 0000000..cd68e86
--- /dev/null
+++ b/talk/base/messagedigest_unittest.cc
@@ -0,0 +1,168 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/messagedigest.h"
+#include "talk/base/stringencode.h"
+
+namespace talk_base {
+
+// Test vectors from RFC 1321.
+TEST(MessageDigestTest, TestMd5Digest) {
+  // Test the string versions of the APIs.
+  EXPECT_EQ("d41d8cd98f00b204e9800998ecf8427e",
+      ComputeDigest(DIGEST_MD5, ""));
+  EXPECT_EQ("900150983cd24fb0d6963f7d28e17f72",
+      ComputeDigest(DIGEST_MD5, "abc"));
+  EXPECT_EQ("c3fcd3d76192e4007dfb496cca67e13b",
+      ComputeDigest(DIGEST_MD5, "abcdefghijklmnopqrstuvwxyz"));
+
+  // Test the raw buffer versions of the APIs; also check output buffer size.
+  char output[16];
+  EXPECT_EQ(sizeof(output),
+      ComputeDigest(DIGEST_MD5, "abc", 3, output, sizeof(output)));
+  EXPECT_EQ("900150983cd24fb0d6963f7d28e17f72",
+      hex_encode(output, sizeof(output)));
+  EXPECT_EQ(0U,
+      ComputeDigest(DIGEST_MD5, "abc", 3, output, sizeof(output) - 1));
+}
+
+// Test vectors from RFC 3174.
+TEST(MessageDigestTest, TestSha1Digest) {
+  // Test the string versions of the APIs.
+  EXPECT_EQ("da39a3ee5e6b4b0d3255bfef95601890afd80709",
+      ComputeDigest(DIGEST_SHA_1, ""));
+  EXPECT_EQ("a9993e364706816aba3e25717850c26c9cd0d89d",
+      ComputeDigest(DIGEST_SHA_1, "abc"));
+  EXPECT_EQ("84983e441c3bd26ebaae4aa1f95129e5e54670f1",
+      ComputeDigest(DIGEST_SHA_1,
+          "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"));
+
+  // Test the raw buffer versions of the APIs; also check output buffer size.
+  char output[20];
+  EXPECT_EQ(sizeof(output),
+      ComputeDigest(DIGEST_SHA_1, "abc", 3, output, sizeof(output)));
+  EXPECT_EQ("a9993e364706816aba3e25717850c26c9cd0d89d",
+      hex_encode(output, sizeof(output)));
+  EXPECT_EQ(0U,
+      ComputeDigest(DIGEST_SHA_1, "abc", 3, output, sizeof(output) - 1));
+}
+
+// Test that we fail properly if a bad digest algorithm is specified.
+TEST(MessageDigestTest, TestBadDigest) {
+  std::string output;
+  EXPECT_FALSE(ComputeDigest("sha-9000", "abc", &output));
+  EXPECT_EQ("", ComputeDigest("sha-9000", "abc"));
+}
+
+// Test vectors from RFC 2202.
+TEST(MessageDigestTest, TestMd5Hmac) {
+  // Test the string versions of the APIs.
+  EXPECT_EQ("9294727a3638bb1c13f48ef8158bfc9d",
+      ComputeHmac(DIGEST_MD5, std::string(16, '\x0b'), "Hi There"));
+  EXPECT_EQ("750c783e6ab0b503eaa86e310a5db738",
+      ComputeHmac(DIGEST_MD5, "Jefe", "what do ya want for nothing?"));
+  EXPECT_EQ("56be34521d144c88dbb8c733f0e8b3f6",
+      ComputeHmac(DIGEST_MD5, std::string(16, '\xaa'),
+          std::string(50, '\xdd')));
+  EXPECT_EQ("697eaf0aca3a3aea3a75164746ffaa79",
+      ComputeHmac(DIGEST_MD5,
+          "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
+          "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19",
+          std::string(50, '\xcd')));
+  EXPECT_EQ("56461ef2342edc00f9bab995690efd4c",
+      ComputeHmac(DIGEST_MD5, std::string(16, '\x0c'),
+          "Test With Truncation"));
+  EXPECT_EQ("6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd",
+      ComputeHmac(DIGEST_MD5, std::string(80, '\xaa'),
+          "Test Using Larger Than Block-Size Key - Hash Key First"));
+  EXPECT_EQ("6f630fad67cda0ee1fb1f562db3aa53e",
+      ComputeHmac(DIGEST_MD5, std::string(80, '\xaa'),
+          "Test Using Larger Than Block-Size Key and Larger "
+          "Than One Block-Size Data"));
+
+  // Test the raw buffer versions of the APIs; also check output buffer size.
+  std::string key(16, '\x0b');
+  std::string input("Hi There");
+  char output[16];
+  EXPECT_EQ(sizeof(output),
+      ComputeHmac(DIGEST_MD5, key.c_str(), key.size(),
+          input.c_str(), input.size(), output, sizeof(output)));
+  EXPECT_EQ("9294727a3638bb1c13f48ef8158bfc9d",
+      hex_encode(output, sizeof(output)));
+  EXPECT_EQ(0U,
+      ComputeHmac(DIGEST_MD5, key.c_str(), key.size(),
+          input.c_str(), input.size(), output, sizeof(output) - 1));
+}
+
+// Test vectors from RFC 2202.
+TEST(MessageDigestTest, TestSha1Hmac) {
+  // Test the string versions of the APIs.
+  EXPECT_EQ("b617318655057264e28bc0b6fb378c8ef146be00",
+      ComputeHmac(DIGEST_SHA_1, std::string(20, '\x0b'), "Hi There"));
+  EXPECT_EQ("effcdf6ae5eb2fa2d27416d5f184df9c259a7c79",
+      ComputeHmac(DIGEST_SHA_1, "Jefe", "what do ya want for nothing?"));
+  EXPECT_EQ("125d7342b9ac11cd91a39af48aa17b4f63f175d3",
+      ComputeHmac(DIGEST_SHA_1, std::string(20, '\xaa'),
+          std::string(50, '\xdd')));
+  EXPECT_EQ("4c9007f4026250c6bc8414f9bf50c86c2d7235da",
+      ComputeHmac(DIGEST_SHA_1,
+          "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
+          "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19",
+          std::string(50, '\xcd')));
+  EXPECT_EQ("4c1a03424b55e07fe7f27be1d58bb9324a9a5a04",
+      ComputeHmac(DIGEST_SHA_1, std::string(20, '\x0c'),
+          "Test With Truncation"));
+  EXPECT_EQ("aa4ae5e15272d00e95705637ce8a3b55ed402112",
+      ComputeHmac(DIGEST_SHA_1, std::string(80, '\xaa'),
+          "Test Using Larger Than Block-Size Key - Hash Key First"));
+  EXPECT_EQ("e8e99d0f45237d786d6bbaa7965c7808bbff1a91",
+      ComputeHmac(DIGEST_SHA_1, std::string(80, '\xaa'),
+          "Test Using Larger Than Block-Size Key and Larger "
+          "Than One Block-Size Data"));
+
+  // Test the raw buffer versions of the APIs; also check output buffer size.
+  std::string key(20, '\x0b');
+  std::string input("Hi There");
+  char output[20];
+  EXPECT_EQ(sizeof(output),
+      ComputeHmac(DIGEST_SHA_1, key.c_str(), key.size(),
+          input.c_str(), input.size(), output, sizeof(output)));
+  EXPECT_EQ("b617318655057264e28bc0b6fb378c8ef146be00",
+      hex_encode(output, sizeof(output)));
+  EXPECT_EQ(0U,
+      ComputeHmac(DIGEST_SHA_1, key.c_str(), key.size(),
+          input.c_str(), input.size(), output, sizeof(output) - 1));
+}
+
+TEST(MessageDigestTest, TestBadHmac) {
+  std::string output;
+  EXPECT_FALSE(ComputeHmac("sha-9000", "key", "abc", &output));
+  EXPECT_EQ("", ComputeHmac("sha-9000", "key", "abc"));
+}
+
+}  // namespace talk_base
diff --git a/talk/base/messagehandler.cc b/talk/base/messagehandler.cc
new file mode 100644
index 0000000..5b3585b
--- /dev/null
+++ b/talk/base/messagehandler.cc
@@ -0,0 +1,37 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/messagehandler.h"
+#include "talk/base/messagequeue.h"
+
+namespace talk_base {
+
+MessageHandler::~MessageHandler() {
+  MessageQueueManager::Instance()->Clear(this);
+}
+
+} // namespace talk_base
diff --git a/talk/base/messagehandler.h b/talk/base/messagehandler.h
new file mode 100644
index 0000000..913edf8
--- /dev/null
+++ b/talk/base/messagehandler.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 TALK_BASE_MESSAGEHANDLER_H_
+#define TALK_BASE_MESSAGEHANDLER_H_
+
+#include "talk/base/constructormagic.h"
+
+namespace talk_base {
+
+struct Message;
+
+// Messages get dispatched to a MessageHandler
+
+class MessageHandler {
+ public:
+  virtual void OnMessage(Message* msg) = 0;
+
+ protected:
+  MessageHandler() {}
+  virtual ~MessageHandler();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MessageHandler);
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_MESSAGEHANDLER_H_
diff --git a/talk/base/messagequeue.cc b/talk/base/messagequeue.cc
new file mode 100644
index 0000000..5c40622
--- /dev/null
+++ b/talk/base/messagequeue.cc
@@ -0,0 +1,388 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/physicalsocketserver.h"
+
+
+namespace talk_base {
+
+const uint32 kMaxMsgLatency = 150;  // 150 ms
+
+//------------------------------------------------------------------
+// MessageQueueManager
+
+MessageQueueManager* MessageQueueManager::instance_;
+
+MessageQueueManager* MessageQueueManager::Instance() {
+  // Note: This is not thread safe, but it is first called before threads are
+  // spawned.
+  if (!instance_)
+    instance_ = new MessageQueueManager;
+  return instance_;
+}
+
+MessageQueueManager::MessageQueueManager() {
+}
+
+MessageQueueManager::~MessageQueueManager() {
+}
+
+void MessageQueueManager::Add(MessageQueue *message_queue) {
+  // MessageQueueManager methods should be non-reentrant, so we
+  // ASSERT that is the case.  If any of these ASSERT, please
+  // contact bpm or jbeda.
+  ASSERT(!crit_.CurrentThreadIsOwner());
+  CritScope cs(&crit_);
+  message_queues_.push_back(message_queue);
+}
+
+void MessageQueueManager::Remove(MessageQueue *message_queue) {
+  ASSERT(!crit_.CurrentThreadIsOwner());  // See note above.
+  // If this is the last MessageQueue, destroy the manager as well so that
+  // we don't leak this object at program shutdown. As mentioned above, this is
+  // not thread-safe, but this should only happen at program termination (when
+  // the ThreadManager is destroyed, and threads are no longer active).
+  bool destroy = false;
+  {
+    CritScope cs(&crit_);
+    std::vector<MessageQueue *>::iterator iter;
+    iter = std::find(message_queues_.begin(), message_queues_.end(),
+                     message_queue);
+    if (iter != message_queues_.end()) {
+      message_queues_.erase(iter);
+    }
+    destroy = message_queues_.empty();
+  }
+  if (destroy) {
+    instance_ = NULL;
+    delete this;
+  }
+}
+
+void MessageQueueManager::Clear(MessageHandler *handler) {
+  ASSERT(!crit_.CurrentThreadIsOwner());  // See note above.
+  CritScope cs(&crit_);
+  std::vector<MessageQueue *>::iterator iter;
+  for (iter = message_queues_.begin(); iter != message_queues_.end(); iter++)
+    (*iter)->Clear(handler);
+}
+
+//------------------------------------------------------------------
+// MessageQueue
+
+MessageQueue::MessageQueue(SocketServer* ss)
+    : ss_(ss), fStop_(false), fPeekKeep_(false), active_(false),
+      dmsgq_next_num_(0) {
+  if (!ss_) {
+    // Currently, MessageQueue holds a socket server, and is the base class for
+    // Thread.  It seems like it makes more sense for Thread to hold the socket
+    // server, and provide it to the MessageQueue, since the Thread controls
+    // the I/O model, and MQ is agnostic to those details.  Anyway, this causes
+    // messagequeue_unittest to depend on network libraries... yuck.
+    default_ss_.reset(new PhysicalSocketServer());
+    ss_ = default_ss_.get();
+  }
+  ss_->SetMessageQueue(this);
+}
+
+MessageQueue::~MessageQueue() {
+  // The signal is done from here to ensure
+  // that it always gets called when the queue
+  // is going away.
+  SignalQueueDestroyed();
+  if (active_) {
+    MessageQueueManager::Instance()->Remove(this);
+    Clear(NULL);
+  }
+  if (ss_) {
+    ss_->SetMessageQueue(NULL);
+  }
+}
+
+void MessageQueue::set_socketserver(SocketServer* ss) {
+  ss_ = ss ? ss : default_ss_.get();
+  ss_->SetMessageQueue(this);
+}
+
+void MessageQueue::Quit() {
+  fStop_ = true;
+  ss_->WakeUp();
+}
+
+bool MessageQueue::IsQuitting() {
+  return fStop_;
+}
+
+void MessageQueue::Restart() {
+  fStop_ = false;
+}
+
+bool MessageQueue::Peek(Message *pmsg, int cmsWait) {
+  if (fPeekKeep_) {
+    *pmsg = msgPeek_;
+    return true;
+  }
+  if (!Get(pmsg, cmsWait))
+    return false;
+  msgPeek_ = *pmsg;
+  fPeekKeep_ = true;
+  return true;
+}
+
+bool MessageQueue::Get(Message *pmsg, int cmsWait, bool process_io) {
+  // Return and clear peek if present
+  // Always return the peek if it exists so there is Peek/Get symmetry
+
+  if (fPeekKeep_) {
+    *pmsg = msgPeek_;
+    fPeekKeep_ = false;
+    return true;
+  }
+
+  // Get w/wait + timer scan / dispatch + socket / event multiplexer dispatch
+
+  int cmsTotal = cmsWait;
+  int cmsElapsed = 0;
+  uint32 msStart = Time();
+  uint32 msCurrent = msStart;
+  while (true) {
+    // Check for sent messages
+    ReceiveSends();
+
+    // Check for posted events
+    int cmsDelayNext = kForever;
+    bool first_pass = true;
+    while (true) {
+      // All queue operations need to be locked, but nothing else in this loop
+      // (specifically handling disposed message) can happen inside the crit.
+      // Otherwise, disposed MessageHandlers will cause deadlocks.
+      {
+        CritScope cs(&crit_);
+        // On the first pass, check for delayed messages that have been
+        // triggered and calculate the next trigger time.
+        if (first_pass) {
+          first_pass = false;
+          while (!dmsgq_.empty()) {
+            if (TimeIsLater(msCurrent, dmsgq_.top().msTrigger_)) {
+              cmsDelayNext = TimeDiff(dmsgq_.top().msTrigger_, msCurrent);
+              break;
+            }
+            msgq_.push_back(dmsgq_.top().msg_);
+            dmsgq_.pop();
+          }
+        }
+        // Pull a message off the message queue, if available.
+        if (msgq_.empty()) {
+          break;
+        } else {
+          *pmsg = msgq_.front();
+          msgq_.pop_front();
+        }
+      }  // crit_ is released here.
+
+      // Log a warning for time-sensitive messages that we're late to deliver.
+      if (pmsg->ts_sensitive) {
+        int32 delay = TimeDiff(msCurrent, pmsg->ts_sensitive);
+        if (delay > 0) {
+          LOG_F(LS_WARNING) << "id: " << pmsg->message_id << "  delay: "
+                            << (delay + kMaxMsgLatency) << "ms";
+        }
+      }
+      // If this was a dispose message, delete it and skip it.
+      if (MQID_DISPOSE == pmsg->message_id) {
+        ASSERT(NULL == pmsg->phandler);
+        delete pmsg->pdata;
+        *pmsg = Message();
+        continue;
+      }
+      return true;
+    }
+
+    if (fStop_)
+      break;
+
+    // Which is shorter, the delay wait or the asked wait?
+
+    int cmsNext;
+    if (cmsWait == kForever) {
+      cmsNext = cmsDelayNext;
+    } else {
+      cmsNext = _max(0, cmsTotal - cmsElapsed);
+      if ((cmsDelayNext != kForever) && (cmsDelayNext < cmsNext))
+        cmsNext = cmsDelayNext;
+    }
+
+    // Wait and multiplex in the meantime
+    if (!ss_->Wait(cmsNext, process_io))
+      return false;
+
+    // If the specified timeout expired, return
+
+    msCurrent = Time();
+    cmsElapsed = TimeDiff(msCurrent, msStart);
+    if (cmsWait != kForever) {
+      if (cmsElapsed >= cmsWait)
+        return false;
+    }
+  }
+  return false;
+}
+
+void MessageQueue::ReceiveSends() {
+}
+
+void MessageQueue::Post(MessageHandler *phandler, uint32 id,
+    MessageData *pdata, bool time_sensitive) {
+  if (fStop_)
+    return;
+
+  // Keep thread safe
+  // Add the message to the end of the queue
+  // Signal for the multiplexer to return
+
+  CritScope cs(&crit_);
+  EnsureActive();
+  Message msg;
+  msg.phandler = phandler;
+  msg.message_id = id;
+  msg.pdata = pdata;
+  if (time_sensitive) {
+    msg.ts_sensitive = Time() + kMaxMsgLatency;
+  }
+  msgq_.push_back(msg);
+  ss_->WakeUp();
+}
+
+void MessageQueue::DoDelayPost(int cmsDelay, uint32 tstamp,
+    MessageHandler *phandler, uint32 id, MessageData* pdata) {
+  if (fStop_)
+    return;
+
+  // Keep thread safe
+  // Add to the priority queue. Gets sorted soonest first.
+  // Signal for the multiplexer to return.
+
+  CritScope cs(&crit_);
+  EnsureActive();
+  Message msg;
+  msg.phandler = phandler;
+  msg.message_id = id;
+  msg.pdata = pdata;
+  DelayedMessage dmsg(cmsDelay, tstamp, dmsgq_next_num_, msg);
+  dmsgq_.push(dmsg);
+  // If this message queue processes 1 message every millisecond for 50 days,
+  // we will wrap this number.  Even then, only messages with identical times
+  // will be misordered, and then only briefly.  This is probably ok.
+  VERIFY(0 != ++dmsgq_next_num_);
+  ss_->WakeUp();
+}
+
+int MessageQueue::GetDelay() {
+  CritScope cs(&crit_);
+
+  if (!msgq_.empty())
+    return 0;
+
+  if (!dmsgq_.empty()) {
+    int delay = TimeUntil(dmsgq_.top().msTrigger_);
+    if (delay < 0)
+      delay = 0;
+    return delay;
+  }
+
+  return kForever;
+}
+
+void MessageQueue::Clear(MessageHandler *phandler, uint32 id,
+                         MessageList* removed) {
+  CritScope cs(&crit_);
+
+  // Remove messages with phandler
+
+  if (fPeekKeep_ && msgPeek_.Match(phandler, id)) {
+    if (removed) {
+      removed->push_back(msgPeek_);
+    } else {
+      delete msgPeek_.pdata;
+    }
+    fPeekKeep_ = false;
+  }
+
+  // Remove from ordered message queue
+
+  for (MessageList::iterator it = msgq_.begin(); it != msgq_.end();) {
+    if (it->Match(phandler, id)) {
+      if (removed) {
+        removed->push_back(*it);
+      } else {
+        delete it->pdata;
+      }
+      it = msgq_.erase(it);
+    } else {
+      ++it;
+    }
+  }
+
+  // Remove from priority queue. Not directly iterable, so use this approach
+
+  PriorityQueue::container_type::iterator new_end = dmsgq_.container().begin();
+  for (PriorityQueue::container_type::iterator it = new_end;
+       it != dmsgq_.container().end(); ++it) {
+    if (it->msg_.Match(phandler, id)) {
+      if (removed) {
+        removed->push_back(it->msg_);
+      } else {
+        delete it->msg_.pdata;
+      }
+    } else {
+      *new_end++ = *it;
+    }
+  }
+  dmsgq_.container().erase(new_end, dmsgq_.container().end());
+  dmsgq_.reheap();
+}
+
+void MessageQueue::Dispatch(Message *pmsg) {
+  pmsg->phandler->OnMessage(pmsg);
+}
+
+void MessageQueue::EnsureActive() {
+  ASSERT(crit_.CurrentThreadIsOwner());
+  if (!active_) {
+    active_ = true;
+    MessageQueueManager::Instance()->Add(this);
+  }
+}
+
+}  // namespace talk_base
diff --git a/talk/base/messagequeue.h b/talk/base/messagequeue.h
new file mode 100644
index 0000000..331f207
--- /dev/null
+++ b/talk/base/messagequeue.h
@@ -0,0 +1,264 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_MESSAGEQUEUE_H_
+#define TALK_BASE_MESSAGEQUEUE_H_
+
+#include <algorithm>
+#include <cstring>
+#include <list>
+#include <queue>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/constructormagic.h"
+#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/timeutils.h"
+
+namespace talk_base {
+
+struct Message;
+class MessageQueue;
+
+// MessageQueueManager does cleanup of of message queues
+
+class MessageQueueManager {
+ public:
+  static MessageQueueManager* Instance();
+
+  void Add(MessageQueue *message_queue);
+  void Remove(MessageQueue *message_queue);
+  void Clear(MessageHandler *handler);
+
+ private:
+  MessageQueueManager();
+  ~MessageQueueManager();
+
+  static MessageQueueManager* instance_;
+  // This list contains 'active' MessageQueues.
+  std::vector<MessageQueue *> message_queues_;
+  CriticalSection crit_;
+};
+
+// Derive from this for specialized data
+// App manages lifetime, except when messages are purged
+
+class MessageData {
+ public:
+  MessageData() {}
+  virtual ~MessageData() {}
+};
+
+template <class T>
+class TypedMessageData : public MessageData {
+ public:
+  explicit TypedMessageData(const T& data) : data_(data) { }
+  const T& data() const { return data_; }
+  T& data() { return data_; }
+ private:
+  T data_;
+};
+
+// Like TypedMessageData, but for pointers that require a delete.
+template <class T>
+class ScopedMessageData : public MessageData {
+ public:
+  explicit ScopedMessageData(T* data) : data_(data) { }
+  const scoped_ptr<T>& data() const { return data_; }
+  scoped_ptr<T>& data() { return data_; }
+ private:
+  scoped_ptr<T> data_;
+};
+
+// 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);
+}
+
+template<class T>
+inline const T& UseMessageData(MessageData* data) {
+  return static_cast< TypedMessageData<T>* >(data)->data();
+}
+
+template<class T>
+class DisposeData : public MessageData {
+ public:
+  explicit DisposeData(T* data) : data_(data) { }
+  virtual ~DisposeData() { delete data_; }
+ private:
+  T* data_;
+};
+
+const uint32 MQID_ANY = static_cast<uint32>(-1);
+const uint32 MQID_DISPOSE = static_cast<uint32>(-2);
+
+// No destructor
+
+struct Message {
+  Message() {
+    memset(this, 0, sizeof(*this));
+  }
+  inline bool Match(MessageHandler* handler, uint32 id) const {
+    return (handler == NULL || handler == phandler)
+           && (id == MQID_ANY || id == message_id);
+  }
+  MessageHandler *phandler;
+  uint32 message_id;
+  MessageData *pdata;
+  uint32 ts_sensitive;
+};
+
+typedef std::list<Message> MessageList;
+
+// DelayedMessage goes into a priority queue, sorted by trigger time.  Messages
+// with the same trigger time are processed in num_ (FIFO) order.
+
+class DelayedMessage {
+ public:
+  DelayedMessage(int delay, uint32 trigger, uint32 num, const Message& msg)
+  : cmsDelay_(delay), msTrigger_(trigger), num_(num), msg_(msg) { }
+
+  bool operator< (const DelayedMessage& dmsg) const {
+    return (dmsg.msTrigger_ < msTrigger_)
+           || ((dmsg.msTrigger_ == msTrigger_) && (dmsg.num_ < num_));
+  }
+
+  int cmsDelay_;  // for debugging
+  uint32 msTrigger_;
+  uint32 num_;
+  Message msg_;
+};
+
+class MessageQueue {
+ public:
+  explicit MessageQueue(SocketServer* ss = NULL);
+  virtual ~MessageQueue();
+
+  SocketServer* socketserver() { return ss_; }
+  void set_socketserver(SocketServer* ss);
+
+  // Note: The behavior of MessageQueue has changed.  When a MQ is stopped,
+  // futher Posts and Sends will fail.  However, any pending Sends and *ready*
+  // Posts (as opposed to unexpired delayed Posts) will be delivered before
+  // Get (or Peek) returns false.  By guaranteeing delivery of those messages,
+  // we eliminate the race condition when an MessageHandler and MessageQueue
+  // may be destroyed independently of each other.
+  virtual void Quit();
+  virtual bool IsQuitting();
+  virtual void Restart();
+
+  // Get() will process I/O until:
+  //  1) A message is available (returns true)
+  //  2) cmsWait seconds have elapsed (returns false)
+  //  3) Stop() is called (returns false)
+  virtual bool Get(Message *pmsg, int cmsWait = kForever,
+                   bool process_io = true);
+  virtual bool Peek(Message *pmsg, int cmsWait = 0);
+  virtual void Post(MessageHandler *phandler, uint32 id = 0,
+                    MessageData *pdata = NULL, bool time_sensitive = false);
+  virtual void PostDelayed(int cmsDelay, MessageHandler *phandler,
+                           uint32 id = 0, MessageData *pdata = NULL) {
+    return DoDelayPost(cmsDelay, TimeAfter(cmsDelay), phandler, id, pdata);
+  }
+  virtual void PostAt(uint32 tstamp, MessageHandler *phandler,
+                      uint32 id = 0, MessageData *pdata = NULL) {
+    return DoDelayPost(TimeUntil(tstamp), tstamp, phandler, id, pdata);
+  }
+  virtual void Clear(MessageHandler *phandler, uint32 id = MQID_ANY,
+                     MessageList* removed = NULL);
+  virtual void Dispatch(Message *pmsg);
+  virtual void ReceiveSends();
+
+  // Amount of time until the next message can be retrieved
+  virtual int GetDelay();
+
+  bool empty() const { return size() == 0u; }
+  size_t size() const {
+    CritScope cs(&crit_);  // msgq_.size() is not thread safe.
+    return msgq_.size() + dmsgq_.size() + (fPeekKeep_ ? 1u : 0u);
+  }
+
+  // Internally posts a message which causes the doomed object to be deleted
+  template<class T> void Dispose(T* doomed) {
+    if (doomed) {
+      Post(NULL, MQID_DISPOSE, new DisposeData<T>(doomed));
+    }
+  }
+
+  // When this signal is sent out, any references to this queue should
+  // no longer be used.
+  sigslot::signal0<> SignalQueueDestroyed;
+
+ protected:
+  class PriorityQueue : public std::priority_queue<DelayedMessage> {
+   public:
+    container_type& container() { return c; }
+    void reheap() { make_heap(c.begin(), c.end(), comp); }
+  };
+
+  void EnsureActive();
+  void DoDelayPost(int cmsDelay, uint32 tstamp, MessageHandler *phandler,
+                   uint32 id, MessageData* pdata);
+
+  // The SocketServer is not owned by MessageQueue.
+  SocketServer* ss_;
+  // If a server isn't supplied in the constructor, use this one.
+  scoped_ptr<SocketServer> default_ss_;
+  bool fStop_;
+  bool fPeekKeep_;
+  Message msgPeek_;
+  // A message queue is active if it has ever had a message posted to it.
+  // This also corresponds to being in MessageQueueManager's global list.
+  bool active_;
+  MessageList msgq_;
+  PriorityQueue dmsgq_;
+  uint32 dmsgq_next_num_;
+  mutable CriticalSection crit_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MessageQueue);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_MESSAGEQUEUE_H_
diff --git a/talk/base/messagequeue_unittest.cc b/talk/base/messagequeue_unittest.cc
new file mode 100644
index 0000000..8e55548
--- /dev/null
+++ b/talk/base/messagequeue_unittest.cc
@@ -0,0 +1,132 @@
+/*
+ * 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/messagequeue.h"
+
+#include "talk/base/bind.h"
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+#include "talk/base/nullsocketserver.h"
+
+using namespace talk_base;
+
+class MessageQueueTest: public testing::Test, public MessageQueue {
+ public:
+  bool IsLocked_Worker() {
+    if (!crit_.TryEnter()) {
+      return true;
+    }
+    crit_.Leave();
+    return false;
+  }
+  bool IsLocked() {
+    // We have to do this on a worker thread, or else the TryEnter will
+    // succeed, since our critical sections are reentrant.
+    Thread worker;
+    worker.Start();
+    return worker.Invoke<bool>(
+        talk_base::Bind(&MessageQueueTest::IsLocked_Worker, this));
+  }
+};
+
+struct DeletedLockChecker {
+  DeletedLockChecker(MessageQueueTest* test, bool* was_locked, bool* deleted)
+      : test(test), was_locked(was_locked), deleted(deleted) { }
+  ~DeletedLockChecker() {
+    *deleted = true;
+    *was_locked = test->IsLocked();
+  }
+  MessageQueueTest* test;
+  bool* was_locked;
+  bool* deleted;
+};
+
+static void DelayedPostsWithIdenticalTimesAreProcessedInFifoOrder(
+    MessageQueue* q) {
+  EXPECT_TRUE(q != NULL);
+  TimeStamp now = Time();
+  q->PostAt(now, NULL, 3);
+  q->PostAt(now - 2, NULL, 0);
+  q->PostAt(now - 1, NULL, 1);
+  q->PostAt(now, NULL, 4);
+  q->PostAt(now - 1, NULL, 2);
+
+  Message msg;
+  for (size_t i=0; i<5; ++i) {
+    memset(&msg, 0, sizeof(msg));
+    EXPECT_TRUE(q->Get(&msg, 0));
+    EXPECT_EQ(i, msg.message_id);
+  }
+
+  EXPECT_FALSE(q->Get(&msg, 0));  // No more messages
+}
+
+TEST_F(MessageQueueTest,
+       DelayedPostsWithIdenticalTimesAreProcessedInFifoOrder) {
+  MessageQueue q;
+  DelayedPostsWithIdenticalTimesAreProcessedInFifoOrder(&q);
+  NullSocketServer nullss;
+  MessageQueue q_nullss(&nullss);
+  DelayedPostsWithIdenticalTimesAreProcessedInFifoOrder(&q_nullss);
+}
+
+TEST_F(MessageQueueTest, DisposeNotLocked) {
+  bool was_locked = true;
+  bool deleted = false;
+  DeletedLockChecker* d = new DeletedLockChecker(this, &was_locked, &deleted);
+  Dispose(d);
+  Message msg;
+  EXPECT_FALSE(Get(&msg, 0));
+  EXPECT_TRUE(deleted);
+  EXPECT_FALSE(was_locked);
+}
+
+class DeletedMessageHandler : public MessageHandler {
+ public:
+  explicit DeletedMessageHandler(bool* deleted) : deleted_(deleted) { }
+  ~DeletedMessageHandler() {
+    *deleted_ = true;
+  }
+  void OnMessage(Message* msg) { }
+ private:
+  bool* deleted_;
+};
+
+TEST_F(MessageQueueTest, DiposeHandlerWithPostedMessagePending) {
+  bool deleted = false;
+  DeletedMessageHandler *handler = new DeletedMessageHandler(&deleted);
+  // First, post a dispose.
+  Dispose(handler);
+  // Now, post a message, which should *not* be returned by Get().
+  Post(handler, 1);
+  Message msg;
+  EXPECT_FALSE(Get(&msg, 0));
+  EXPECT_TRUE(deleted);
+}
+
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..03b1cd1
--- /dev/null
+++ b/talk/base/nat_unittest.cc
@@ -0,0 +1,359 @@
+/*
+ * 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/nethelpers.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";
+    ASSERT_NE(AF_UNSPEC, trans_addr.family());
+    ASSERT_NE(AF_UNSPEC, trans_addr2.family());
+  }
+
+  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);
+}
+
+bool TestConnectivity(const SocketAddress& src, const IPAddress& dst) {
+  // The physical NAT tests require connectivity to the selected ip from the
+  // internal address used for the NAT. Things like firewalls can break that, so
+  // check to see if it's worth even trying with this ip.
+  scoped_ptr<PhysicalSocketServer> pss(new PhysicalSocketServer());
+  scoped_ptr<AsyncSocket> client(pss->CreateAsyncSocket(src.family(),
+                                                        SOCK_DGRAM));
+  scoped_ptr<AsyncSocket> server(pss->CreateAsyncSocket(src.family(),
+                                                        SOCK_DGRAM));
+  if (client->Bind(SocketAddress(src.ipaddr(), 0)) != 0 ||
+      server->Bind(SocketAddress(dst, 0)) != 0) {
+    return false;
+  }
+  const char* buf = "hello other socket";
+  size_t len = strlen(buf);
+  int sent = client->SendTo(buf, len, server->GetLocalAddress());
+  SocketAddress addr;
+  const size_t kRecvBufSize = 64;
+  char recvbuf[kRecvBufSize];
+  Thread::Current()->SleepMs(100);
+  int received = server->RecvFrom(recvbuf, kRecvBufSize, &addr);
+  return received == sent && ::memcmp(buf, recvbuf, len) == 0;
+}
+
+void TestPhysicalInternal(const SocketAddress& int_addr) {
+  BasicNetworkManager network_manager;
+  network_manager.set_ipv6_enabled(true);
+  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 ext_addr1(int_addr);
+  SocketAddress ext_addr2;
+  // Find an available IP with matching family. The test breaks if int_addr
+  // can't talk to ip, so check for connectivity as well.
+  for (std::vector<Network*>::iterator it = networks.begin();
+      it != networks.end(); ++it) {
+    const IPAddress& ip = (*it)->ip();
+    if (ip.family() == int_addr.family() && TestConnectivity(int_addr, ip)) {
+      ext_addr2.SetIP(ip);
+      break;
+    }
+  }
+  if (ext_addr2.IsNil()) {
+    LOG(LS_WARNING) << "No available IP of same family as " << int_addr;
+    return;
+  }
+
+  LOG(LS_INFO) << "selected ip " << ext_addr2.ipaddr();
+
+  SocketAddress ext_addrs[4] = {
+      SocketAddress(ext_addr1),
+      SocketAddress(ext_addr2),
+      SocketAddress(ext_addr1),
+      SocketAddress(ext_addr2)
+  };
+
+  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);
+}
+
+TEST(NatTest, TestPhysicalIPv4) {
+  TestPhysicalInternal(SocketAddress("127.0.0.1", 0));
+}
+
+TEST(NatTest, TestPhysicalIPv6) {
+  if (HasIPv6Enabled()) {
+    TestPhysicalInternal(SocketAddress("::1", 0));
+  } else {
+    LOG(LS_WARNING) << "No IPv6, skipping";
+  }
+}
+
+class TestVirtualSocketServer : public VirtualSocketServer {
+ public:
+  explicit TestVirtualSocketServer(SocketServer* ss)
+      : VirtualSocketServer(ss) {}
+  // Expose this publicly
+  IPAddress GetNextIP(int af) { return VirtualSocketServer::GetNextIP(af); }
+};
+
+void TestVirtualInternal(int family) {
+  TestVirtualSocketServer* int_vss = new TestVirtualSocketServer(
+      new PhysicalSocketServer());
+  TestVirtualSocketServer* ext_vss = new TestVirtualSocketServer(
+      new PhysicalSocketServer());
+
+  SocketAddress int_addr;
+  SocketAddress ext_addrs[4];
+  int_addr.SetIP(int_vss->GetNextIP(family));
+  ext_addrs[0].SetIP(ext_vss->GetNextIP(int_addr.family()));
+  ext_addrs[1].SetIP(ext_vss->GetNextIP(int_addr.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);
+}
+
+TEST(NatTest, TestVirtualIPv4) {
+  TestVirtualInternal(AF_INET);
+}
+
+TEST(NatTest, TestVirtualIPv6) {
+  if (HasIPv6Enabled()) {
+    TestVirtualInternal(AF_INET6);
+  } else {
+    LOG(LS_WARNING) << "No IPv6, skipping";
+  }
+}
+
+// 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..7a3a045
--- /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;
+  size_t 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.ToSensitiveString()
+                 << " 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..a7c4240
--- /dev/null
+++ b/talk/base/natsocketfactory.cc
@@ -0,0 +1,505 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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])) = HostToNetwork16(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 = NetworkToHost16(*(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 family, int type)
+      : sf_(sf), family_(family), 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 NIL if not connected
+  }
+
+  virtual int Bind(const SocketAddress& addr) {
+    if (socket_) {  // already bound, bubble up error
+      return -1;
+    }
+
+    int result;
+    socket_ = sf_->CreateInternalSocket(family_, 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_.IsNil() ? 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_.IsNil() || 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_.IsNil() || 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 - static_cast<int>(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_.IsNil()) {
+      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_.IsNil() && !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 family_;
+  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 CreateSocket(AF_INET, type);
+}
+
+Socket* NATSocketFactory::CreateSocket(int family, int type) {
+  return new NATSocket(this, family, type);
+}
+
+AsyncSocket* NATSocketFactory::CreateAsyncSocket(int type) {
+  return CreateAsyncSocket(AF_INET, type);
+}
+
+AsyncSocket* NATSocketFactory::CreateAsyncSocket(int family, int type) {
+  return new NATSocket(this, family, type);
+}
+
+AsyncSocket* NATSocketFactory::CreateInternalSocket(int family, int type,
+    const SocketAddress& local_addr, SocketAddress* nat_addr) {
+  *nat_addr = nat_addr_;
+  return factory_->CreateAsyncSocket(family, 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 CreateSocket(AF_INET, type);
+}
+
+Socket* NATSocketServer::CreateSocket(int family, int type) {
+  return new NATSocket(this, family, type);
+}
+
+AsyncSocket* NATSocketServer::CreateAsyncSocket(int type) {
+  return CreateAsyncSocket(AF_INET, type);
+}
+
+AsyncSocket* NATSocketServer::CreateAsyncSocket(int family, int type) {
+  return new NATSocket(this, family, type);
+}
+
+AsyncSocket* NATSocketServer::CreateInternalSocket(int family, 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(family, type);
+    *nat_addr = (type == SOCK_STREAM) ?
+        nat->internal_tcp_address() : nat->internal_address();
+  } else {
+    socket = server_->CreateAsyncSocket(family, 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..d02503f
--- /dev/null
+++ b/talk/base/natsocketfactory.h
@@ -0,0 +1,183 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#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 family, 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 Socket* CreateSocket(int family, int type);
+  virtual AsyncSocket* CreateAsyncSocket(int type);
+  virtual AsyncSocket* CreateAsyncSocket(int family, int type);
+
+  // NATInternalSocketFactory implementation
+  virtual AsyncSocket* CreateInternalSocket(int family, 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 Socket* CreateSocket(int family, int type);
+
+  virtual AsyncSocket* CreateAsyncSocket(int type);
+  virtual AsyncSocket* CreateAsyncSocket(int family, 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 family, 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
new file mode 100644
index 0000000..eebc6cf
--- /dev/null
+++ b/talk/base/nethelpers.cc
@@ -0,0 +1,142 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/nethelpers.h"
+
+#if defined(WIN32)
+#include <ws2spi.h>
+#include <ws2tcpip.h>
+#include "talk/base/win32.h"
+#endif
+
+#include "talk/base/byteorder.h"
+#include "talk/base/signalthread.h"
+
+namespace talk_base {
+
+int ResolveHostname(const std::string& hostname, int family,
+                    std::vector<IPAddress>* addresses) {
+  if (!addresses) {
+    return -1;
+  }
+  addresses->clear();
+  struct addrinfo* result = NULL;
+  struct addrinfo hints = {0};
+  // TODO(djw): For now this is IPv4 only so existing users remain unaffected.
+  hints.ai_family = AF_INET;
+  hints.ai_flags = AI_ADDRCONFIG;
+  int ret = getaddrinfo(hostname.c_str(), NULL, &hints, &result);
+  if (ret != 0) {
+    return ret;
+  }
+  struct addrinfo* cursor = result;
+  for (; cursor; cursor = cursor->ai_next) {
+    if (family == AF_UNSPEC || cursor->ai_family == family) {
+      IPAddress ip;
+      if (IPFromAddrInfo(cursor, &ip)) {
+        addresses->push_back(ip);
+      }
+    }
+  }
+  freeaddrinfo(result);
+  return 0;
+}
+
+// AsyncResolver
+AsyncResolver::AsyncResolver() : error_(0) {
+}
+
+void AsyncResolver::DoWork() {
+  error_ = ResolveHostname(addr_.hostname().c_str(), addr_.family(),
+                           &addresses_);
+}
+
+void AsyncResolver::OnWorkDone() {
+  if (addresses_.size() > 0) {
+    addr_.SetIP(addresses_[0]);
+  }
+}
+
+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
+}
+
+bool HasIPv6Enabled() {
+#ifndef WIN32
+  // We only need to check this for Windows XP (so far).
+  return true;
+#else
+  if (IsWindowsVistaOrLater()) {
+    return true;
+  }
+  if (!IsWindowsXpOrLater()) {
+    return false;
+  }
+  DWORD protbuff_size = 4096;
+  scoped_array<char> protocols;
+  LPWSAPROTOCOL_INFOW protocol_infos = NULL;
+  int requested_protocols[2] = {AF_INET6, 0};
+
+  int err = 0;
+  int ret = 0;
+  // Check for protocols in a do-while loop until we provide a buffer large
+  // enough. (WSCEnumProtocols sets protbuff_size to its desired value).
+  // It is extremely unlikely that this will loop more than once.
+  do {
+    protocols.reset(new char[protbuff_size]);
+    protocol_infos = reinterpret_cast<LPWSAPROTOCOL_INFOW>(protocols.get());
+    ret = WSCEnumProtocols(requested_protocols, protocol_infos,
+                           &protbuff_size, &err);
+  } while (ret == SOCKET_ERROR && err == WSAENOBUFS);
+
+  if (ret == SOCKET_ERROR) {
+    return false;
+  }
+
+  // Even if ret is positive, check specifically for IPv6.
+  // Non-IPv6 enabled WinXP will still return a RAW protocol.
+  for (int i = 0; i < ret; ++i) {
+    if (protocol_infos[i].iAddressFamily == AF_INET6) {
+      return true;
+    }
+  }
+  return false;
+#endif
+}
+}  // namespace talk_base
diff --git a/talk/base/nethelpers.h b/talk/base/nethelpers.h
new file mode 100644
index 0000000..66f7910
--- /dev/null
+++ b/talk/base/nethelpers.h
@@ -0,0 +1,77 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_NETHELPERS_H_
+#define TALK_BASE_NETHELPERS_H_
+
+#ifdef POSIX
+#include <netdb.h>
+#include <cstddef>
+#elif WIN32
+#include <winsock2.h>  // NOLINT
+#endif
+
+#include <list>
+
+#include "talk/base/signalthread.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socketaddress.h"
+
+namespace talk_base {
+
+// AsyncResolver will perform async DNS resolution, signaling the result on
+// the inherited SignalWorkDone when the operation completes.
+class AsyncResolver : public SignalThread {
+ public:
+  AsyncResolver();
+
+  const SocketAddress& address() const { return addr_; }
+  const std::vector<IPAddress>& addresses() const { return addresses_; }
+  void set_address(const SocketAddress& addr) { addr_ = addr; }
+  int error() const { return error_; }
+  void set_error(int error) { error_ = error; }
+
+
+ protected:
+  virtual void DoWork();
+  virtual void OnWorkDone();
+
+ private:
+  SocketAddress addr_;
+  std::vector<IPAddress> addresses_;
+  int error_;
+};
+
+// 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);
+
+bool HasIPv6Enabled();
+}  // namespace talk_base
+
+#endif  // TALK_BASE_NETHELPERS_H_
diff --git a/talk/base/network.cc b/talk/base/network.cc
new file mode 100644
index 0000000..b32bb09
--- /dev/null
+++ b/talk/base/network.cc
@@ -0,0 +1,542 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "talk/base/network.h"
+
+#ifdef POSIX
+#include <sys/socket.h>
+#include <sys/utsname.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <unistd.h>
+#include <errno.h>
+#ifdef ANDROID
+#include "talk/base/ifaddrs-android.h"
+#else
+#include <ifaddrs.h>
+#endif
+#endif  // POSIX
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#include <Iphlpapi.h>
+#endif
+
+#include <algorithm>
+#include <cstdio>
+
+#include "talk/base/host.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socket.h"  // includes something that makes windows happy
+#include "talk/base/stream.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+namespace {
+
+const uint32 kUpdateNetworksMessage = 1;
+const uint32 kSignalNetworksMessage = 2;
+
+// Fetch list of networks every two seconds.
+const int kNetworksUpdateIntervalMs = 2000;
+
+
+// Makes a string key for this network. Used in the network manager's maps.
+// Network objects are keyed on interface name, network prefix and the
+// length of that prefix.
+std::string MakeNetworkKey(const std::string& name, const IPAddress& prefix,
+                           int prefix_length) {
+  std::ostringstream ost;
+  ost << name << "%" << prefix.ToString() << "/" << prefix_length;
+  return ost.str();
+}
+
+bool CompareNetworks(const Network* a, const Network* b) {
+  if (a->prefix_length() == b->prefix_length()) {
+    if (a->name() == b->name()) {
+      return a->prefix() < b->prefix();
+    }
+  }
+  return a->name() < b->name();
+}
+
+
+}  // namespace
+
+NetworkManager::NetworkManager() {
+}
+
+NetworkManager::~NetworkManager() {
+}
+
+NetworkManagerBase::NetworkManagerBase() : ipv6_enabled_(true) {
+}
+
+NetworkManagerBase::~NetworkManagerBase() {
+  for (NetworkMap::iterator i = networks_map_.begin();
+       i != networks_map_.end(); ++i) {
+    delete i->second;
+  }
+}
+
+void NetworkManagerBase::GetNetworks(NetworkList* result) const {
+  *result = networks_;
+}
+
+void NetworkManagerBase::MergeNetworkList(const NetworkList& new_networks,
+                                          bool* changed) {
+  // Sort the list so that we can detect when it changes.
+  typedef std::pair<Network*, std::vector<IPAddress> > address_list;
+  std::map<std::string, address_list> address_map;
+  NetworkList list(new_networks);
+  NetworkList merged_list;
+  std::sort(list.begin(), list.end(), CompareNetworks);
+
+  *changed = false;
+
+  if (networks_.size() != list.size())
+    *changed = true;
+
+  // First, build a set of network-keys to the ipaddresses.
+  for (uint32 i = 0; i < list.size(); ++i) {
+    bool might_add_to_merged_list = false;
+    std::string key = MakeNetworkKey(list[i]->name(),
+                                     list[i]->prefix(),
+                                     list[i]->prefix_length());
+    if (address_map.find(key) == address_map.end()) {
+      address_map[key] = address_list(list[i], std::vector<IPAddress>());
+      might_add_to_merged_list = true;
+    }
+    const std::vector<IPAddress>& addresses = list[i]->GetIPs();
+    address_list& current_list = address_map[key];
+    for (std::vector<IPAddress>::const_iterator it = addresses.begin();
+         it != addresses.end();
+         ++it) {
+      current_list.second.push_back(*it);
+    }
+    if (!might_add_to_merged_list) {
+      delete list[i];
+    }
+  }
+
+  // Next, look for existing network objects to re-use.
+  for (std::map<std::string, address_list >::iterator it = address_map.begin();
+       it != address_map.end();
+       ++it) {
+    const std::string& key = it->first;
+    Network* net = it->second.first;
+    NetworkMap::iterator existing = networks_map_.find(key);
+    if (existing == networks_map_.end()) {
+      // This network is new. Place it in the network map.
+      merged_list.push_back(net);
+      networks_map_[key] = net;
+      *changed = true;
+    } else {
+      // This network exists in the map already. Reset its IP addresses.
+      *changed = existing->second->SetIPs(it->second.second, *changed);
+      merged_list.push_back(existing->second);
+      if (existing->second != net) {
+        delete net;
+      }
+    }
+  }
+  networks_ = merged_list;
+}
+
+BasicNetworkManager::BasicNetworkManager()
+    : thread_(NULL),
+      start_count_(0) {
+}
+
+BasicNetworkManager::~BasicNetworkManager() {
+}
+
+#if defined(POSIX)
+void BasicNetworkManager::ConvertIfAddrs(struct ifaddrs* interfaces,
+                                         bool include_ignored,
+                                         NetworkList* networks) const {
+  NetworkMap current_networks;
+  for (struct ifaddrs* cursor = interfaces;
+       cursor != NULL; cursor = cursor->ifa_next) {
+    IPAddress prefix;
+    IPAddress mask;
+    IPAddress ip;
+    int scope_id = 0;
+
+    // Some interfaces may not have address assigned.
+    if (!cursor->ifa_addr || !cursor->ifa_netmask)
+      continue;
+
+    switch (cursor->ifa_addr->sa_family) {
+      case AF_INET: {
+        ip = IPAddress(
+            reinterpret_cast<sockaddr_in*>(cursor->ifa_addr)->sin_addr);
+        mask = IPAddress(
+            reinterpret_cast<sockaddr_in*>(cursor->ifa_netmask)->sin_addr);
+        break;
+      }
+      case AF_INET6: {
+        if (ipv6_enabled()) {
+          ip = IPAddress(
+              reinterpret_cast<sockaddr_in6*>(cursor->ifa_addr)->sin6_addr);
+          mask = IPAddress(
+              reinterpret_cast<sockaddr_in6*>(cursor->ifa_netmask)->sin6_addr);
+          scope_id =
+              reinterpret_cast<sockaddr_in6*>(cursor->ifa_addr)->sin6_scope_id;
+          break;
+        } else {
+          continue;
+        }
+      }
+      default: {
+        continue;
+      }
+    }
+    int prefix_length = CountIPMaskBits(mask);
+    prefix = TruncateIP(ip, prefix_length);
+    std::string key = MakeNetworkKey(std::string(cursor->ifa_name),
+                                     prefix, prefix_length);
+    NetworkMap::iterator existing_network = current_networks.find(key);
+    if (existing_network == current_networks.end()) {
+      scoped_ptr<Network> network(new Network(cursor->ifa_name,
+                                              cursor->ifa_name,
+                                              prefix,
+                                              prefix_length));
+      network->set_scope_id(scope_id);
+      network->AddIP(ip);
+      bool ignored = ((cursor->ifa_flags & IFF_LOOPBACK) ||
+                      IsIgnoredNetwork(*network));
+      network->set_ignored(ignored);
+      if (include_ignored || !network->ignored()) {
+        networks->push_back(network.release());
+      }
+    } else {
+      (*existing_network).second->AddIP(ip);
+    }
+  }
+}
+
+bool BasicNetworkManager::CreateNetworks(bool include_ignored,
+                                         NetworkList* networks) const {
+  struct ifaddrs* interfaces;
+  int error = getifaddrs(&interfaces);
+  if (error != 0) {
+    LOG_ERR(LERROR) << "getifaddrs failed to gather interface data: " << error;
+    return false;
+  }
+
+  ConvertIfAddrs(interfaces, include_ignored, networks);
+
+  freeifaddrs(interfaces);
+  return true;
+}
+
+#elif defined(WIN32)
+
+unsigned int GetPrefix(PIP_ADAPTER_PREFIX prefixlist,
+              const IPAddress& ip, IPAddress* prefix) {
+  IPAddress current_prefix;
+  IPAddress best_prefix;
+  unsigned int best_length = 0;
+  while (prefixlist) {
+    // Look for the longest matching prefix in the prefixlist.
+    if (prefixlist->Address.lpSockaddr == NULL ||
+        prefixlist->Address.lpSockaddr->sa_family != ip.family()) {
+      prefixlist = prefixlist->Next;
+      continue;
+    }
+    switch (prefixlist->Address.lpSockaddr->sa_family) {
+      case AF_INET: {
+        sockaddr_in* v4_addr =
+            reinterpret_cast<sockaddr_in*>(prefixlist->Address.lpSockaddr);
+        current_prefix = IPAddress(v4_addr->sin_addr);
+        break;
+      }
+      case AF_INET6: {
+          sockaddr_in6* v6_addr =
+              reinterpret_cast<sockaddr_in6*>(prefixlist->Address.lpSockaddr);
+          current_prefix = IPAddress(v6_addr->sin6_addr);
+          break;
+      }
+      default: {
+        prefixlist = prefixlist->Next;
+        continue;
+      }
+    }
+    if (TruncateIP(ip, prefixlist->PrefixLength) == current_prefix &&
+        prefixlist->PrefixLength > best_length) {
+      best_prefix = current_prefix;
+      best_length = prefixlist->PrefixLength;
+    }
+    prefixlist = prefixlist->Next;
+  }
+  *prefix = best_prefix;
+  return best_length;
+}
+
+bool BasicNetworkManager::CreateNetworks(bool include_ignored,
+                                         NetworkList* networks) const {
+  NetworkMap current_networks;
+  // MSDN recommends a 15KB buffer for the first try at GetAdaptersAddresses.
+  size_t buffer_size = 16384;
+  scoped_array<char> adapter_info(new char[buffer_size]);
+  PIP_ADAPTER_ADDRESSES adapter_addrs =
+      reinterpret_cast<PIP_ADAPTER_ADDRESSES>(adapter_info.get());
+  int adapter_flags = (GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_ANYCAST |
+                       GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_INCLUDE_PREFIX);
+  int ret = 0;
+  do {
+    adapter_info.reset(new char[buffer_size]);
+    adapter_addrs = reinterpret_cast<PIP_ADAPTER_ADDRESSES>(adapter_info.get());
+    ret = GetAdaptersAddresses(AF_UNSPEC, adapter_flags,
+                               0, adapter_addrs,
+                               reinterpret_cast<PULONG>(&buffer_size));
+  } while (ret == ERROR_BUFFER_OVERFLOW);
+  if (ret != ERROR_SUCCESS) {
+    return false;
+  }
+  int count = 0;
+  while (adapter_addrs) {
+    if (adapter_addrs->OperStatus == IfOperStatusUp) {
+      PIP_ADAPTER_UNICAST_ADDRESS address = adapter_addrs->FirstUnicastAddress;
+      PIP_ADAPTER_PREFIX prefixlist = adapter_addrs->FirstPrefix;
+      std::string name;
+      std::string description;
+#ifdef _DEBUG
+      name = ToUtf8(adapter_addrs->FriendlyName,
+                    wcslen(adapter_addrs->FriendlyName));
+#endif
+      description = ToUtf8(adapter_addrs->Description,
+                           wcslen(adapter_addrs->Description));
+      for (; address; address = address->Next) {
+#ifndef _DEBUG
+        name = talk_base::ToString(count);
+#endif
+
+        IPAddress ip;
+        int scope_id = 0;
+        scoped_ptr<Network> network;
+        switch (address->Address.lpSockaddr->sa_family) {
+          case AF_INET: {
+            sockaddr_in* v4_addr =
+                reinterpret_cast<sockaddr_in*>(address->Address.lpSockaddr);
+            ip = IPAddress(v4_addr->sin_addr);
+            break;
+          }
+          case AF_INET6: {
+            if (ipv6_enabled()) {
+              sockaddr_in6* v6_addr =
+                  reinterpret_cast<sockaddr_in6*>(address->Address.lpSockaddr);
+              scope_id = v6_addr->sin6_scope_id;
+              ip = IPAddress(v6_addr->sin6_addr);
+              break;
+            } else {
+              continue;
+            }
+          }
+          default: {
+            continue;
+          }
+        }
+        IPAddress prefix;
+        int prefix_length = GetPrefix(prefixlist, ip, &prefix);
+        std::string key = MakeNetworkKey(name, prefix, prefix_length);
+        NetworkMap::iterator existing_network = current_networks.find(key);
+        if (existing_network == current_networks.end()) {
+          scoped_ptr<Network> network(new Network(name,
+                                                  description,
+                                                  prefix,
+                                                  prefix_length));
+          network->set_scope_id(scope_id);
+          network->AddIP(ip);
+          bool ignore = ((adapter_addrs->IfType == IF_TYPE_SOFTWARE_LOOPBACK) ||
+                         IsIgnoredNetwork(*network));
+          network->set_ignored(ignore);
+          if (include_ignored || !network->ignored()) {
+            networks->push_back(network.release());
+          }
+        } else {
+          (*existing_network).second->AddIP(ip);
+        }
+      }
+      // Count is per-adapter - all 'Networks' created from the same
+      // adapter need to have the same name.
+      ++count;
+    }
+    adapter_addrs = adapter_addrs->Next;
+  }
+  return true;
+}
+#endif  // WIN32
+
+bool BasicNetworkManager::IsIgnoredNetwork(const Network& network) {
+#ifdef POSIX
+  // Ignore local networks (lo, lo0, etc)
+  // Also filter out VMware interfaces, typically named vmnet1 and vmnet8
+  if (strncmp(network.name().c_str(), "vmnet", 5) == 0 ||
+      strncmp(network.name().c_str(), "vnic", 4) == 0) {
+    return true;
+  }
+#elif defined(WIN32)
+  // Ignore any HOST side vmware adapters with a description like:
+  // VMware Virtual Ethernet Adapter for VMnet1
+  // but don't ignore any GUEST side adapters with a description like:
+  // VMware Accelerated AMD PCNet Adapter #2
+  if (strstr(network.description().c_str(), "VMnet") != NULL) {
+    return true;
+  }
+#endif
+
+  // Ignore any networks with a 0.x.y.z IP
+  if (network.prefix().family() == AF_INET) {
+    return (network.prefix().v4AddressAsHostOrderInteger() < 0x01000000);
+  }
+  return false;
+}
+
+void BasicNetworkManager::StartUpdating() {
+  thread_ = Thread::Current();
+  if (start_count_) {
+    // If network interfaces are already discovered and signal is sent,
+    // we should trigger network signal immediately for the new clients
+    // to start allocating ports.
+    if (sent_first_update_)
+      thread_->Post(this, kSignalNetworksMessage);
+  } else {
+    thread_->Post(this, kUpdateNetworksMessage);
+  }
+  ++start_count_;
+}
+
+void BasicNetworkManager::StopUpdating() {
+  ASSERT(Thread::Current() == thread_);
+  if (!start_count_)
+    return;
+
+  --start_count_;
+  if (!start_count_) {
+    thread_->Clear(this);
+    sent_first_update_ = false;
+  }
+}
+
+void BasicNetworkManager::OnMessage(Message* msg) {
+  switch (msg->message_id) {
+    case kUpdateNetworksMessage:  {
+      DoUpdateNetworks();
+      break;
+    }
+    case kSignalNetworksMessage:  {
+      SignalNetworksChanged();
+      break;
+    }
+    default:
+      ASSERT(false);
+  }
+}
+
+void BasicNetworkManager::DoUpdateNetworks() {
+  if (!start_count_)
+    return;
+
+  ASSERT(Thread::Current() == thread_);
+
+  NetworkList list;
+  if (!CreateNetworks(false, &list)) {
+    SignalError();
+  } else {
+    bool changed;
+    MergeNetworkList(list, &changed);
+    if (changed || !sent_first_update_) {
+      SignalNetworksChanged();
+      sent_first_update_ = true;
+    }
+  }
+
+  thread_->PostDelayed(kNetworksUpdateIntervalMs, this, kUpdateNetworksMessage);
+}
+
+void BasicNetworkManager::DumpNetworks(bool include_ignored) {
+  NetworkList list;
+  CreateNetworks(include_ignored, &list);
+  LOG(LS_INFO) << "NetworkManager detected " << list.size() << " networks:";
+  for (size_t i = 0; i < list.size(); ++i) {
+    const Network* network = list[i];
+    if (!network->ignored() || include_ignored) {
+      LOG(LS_INFO) << network->ToString() << ": "
+                   << network->description()
+                   << ((network->ignored()) ? ", Ignored" : "");
+    }
+  }
+}
+
+Network::Network(const std::string& name, const std::string& desc,
+                 const IPAddress& prefix, int prefix_length)
+    : name_(name), description_(desc), prefix_(prefix),
+      prefix_length_(prefix_length), scope_id_(0), ignored_(false),
+      uniform_numerator_(0), uniform_denominator_(0), exponential_numerator_(0),
+      exponential_denominator_(0) {
+}
+
+std::string Network::ToString() const {
+  std::stringstream ss;
+  // Print out the first space-terminated token of the network desc, plus
+  // the IP address.
+  ss << "Net[" << description_.substr(0, description_.find(' '))
+     << ":" << prefix_.ToSensitiveString() << "/" << prefix_length_ << "]";
+  return ss.str();
+}
+
+// Sets the addresses of this network. Returns true if the address set changed.
+// Change detection is short circuited if the changed argument is true.
+bool Network::SetIPs(const std::vector<IPAddress>& ips, bool changed) {
+  changed = changed || ips.size() != ips_.size();
+  // Detect changes with a nested loop; n-squared but we expect on the order
+  // of 2-3 addresses per network.
+  for (std::vector<IPAddress>::const_iterator it = ips.begin();
+      !changed && it != ips.end();
+      ++it) {
+    bool found = false;
+    for (std::vector<IPAddress>::iterator inner_it = ips_.begin();
+         !found && inner_it != ips_.end();
+         ++inner_it) {
+      if (*it == *inner_it) {
+        found = true;
+      }
+    }
+    changed = !found;
+  }
+  ips_ = ips;
+  return changed;
+}
+}  // namespace talk_base
diff --git a/talk/base/network.h b/talk/base/network.h
new file mode 100644
index 0000000..f87063d
--- /dev/null
+++ b/talk/base/network.h
@@ -0,0 +1,227 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_NETWORK_H_
+#define TALK_BASE_NETWORK_H_
+
+#include <deque>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/ipaddress.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/sigslot.h"
+
+#if defined(POSIX)
+struct ifaddrs;
+#endif  // defined(POSIX)
+
+namespace talk_base {
+
+class Network;
+class NetworkSession;
+class Thread;
+
+// Generic network manager interface. It provides list of local
+// networks.
+class NetworkManager {
+ public:
+  typedef std::vector<Network*> NetworkList;
+
+  NetworkManager();
+  virtual ~NetworkManager();
+
+  // Called when network list is updated.
+  sigslot::signal0<> SignalNetworksChanged;
+
+  // Indicates a failure when getting list of network interfaces.
+  sigslot::signal0<> SignalError;
+
+  // Start/Stop monitoring of network interfaces
+  // list. SignalNetworksChanged or SignalError is emitted immidiately
+  // after StartUpdating() is called. After that SignalNetworksChanged
+  // is emitted wheneven list of networks changes.
+  virtual void StartUpdating() = 0;
+  virtual void StopUpdating() = 0;
+
+  // Returns the current list of networks available on this machine.
+  // UpdateNetworks() must be called before this method is called.
+  // It makes sure that repeated calls return the same object for a
+  // given network, so that quality is tracked appropriately. Does not
+  // include ignored networks.
+  virtual void GetNetworks(NetworkList* networks) const = 0;
+
+  // Dumps a list of networks available to LS_INFO.
+  virtual void DumpNetworks(bool include_ignored) {}
+};
+
+// Base class for NetworkManager implementations.
+class NetworkManagerBase : public NetworkManager {
+ public:
+  NetworkManagerBase();
+  virtual ~NetworkManagerBase();
+
+  virtual void GetNetworks(std::vector<Network*>* networks) const;
+  bool ipv6_enabled() const { return ipv6_enabled_; }
+  void set_ipv6_enabled(bool enabled) { ipv6_enabled_ = enabled; }
+
+ protected:
+  typedef std::map<std::string, Network*> NetworkMap;
+  // Updates |networks_| with the networks listed in |list|. If
+  // |network_map_| already has a Network object for a network listed
+  // in the |list| then it is reused. Accept ownership of the Network
+  // objects in the |list|. |changed| will be set to true if there is
+  // any change in the network list.
+  void MergeNetworkList(const NetworkList& list, bool* changed);
+
+ private:
+  friend class NetworkTest;
+  void DoUpdateNetworks();
+
+  NetworkList networks_;
+  NetworkMap networks_map_;
+  bool ipv6_enabled_;
+};
+
+// Basic implementation of the NetworkManager interface that gets list
+// of networks using OS APIs.
+class BasicNetworkManager : public NetworkManagerBase,
+                            public MessageHandler {
+ public:
+  BasicNetworkManager();
+  virtual ~BasicNetworkManager();
+
+  virtual void StartUpdating();
+  virtual void StopUpdating();
+
+  // Logs the available networks.
+  virtual void DumpNetworks(bool include_ignored);
+
+  // MessageHandler interface.
+  virtual void OnMessage(Message* msg);
+  bool started() { return start_count_ > 0; }
+
+ protected:
+#if defined(POSIX)
+  // Separated from CreateNetworks for tests.
+  void ConvertIfAddrs(ifaddrs* interfaces,
+                      bool include_ignored,
+                      NetworkList* networks) const;
+#endif  // defined(POSIX)
+
+  // Creates a network object for each network available on the machine.
+  bool CreateNetworks(bool include_ignored, NetworkList* networks) const;
+
+  // Determines if a network should be ignored.
+  static bool IsIgnoredNetwork(const Network& network);
+
+ private:
+  friend class NetworkTest;
+
+  void DoUpdateNetworks();
+
+  Thread* thread_;
+  bool sent_first_update_;
+  int start_count_;
+};
+
+// Represents a Unix-type network interface, with a name and single address.
+class Network {
+ public:
+  Network() : prefix_(INADDR_ANY), scope_id_(0) {}
+  Network(const std::string& name, const std::string& description,
+          const IPAddress& prefix, int prefix_length);
+
+  // Returns the name of the interface this network is associated wtih.
+  const std::string& name() const { return name_; }
+
+  // Returns the OS-assigned name for this network. This is useful for
+  // debugging but should not be sent over the wire (for privacy reasons).
+  const std::string& description() const { return description_; }
+
+  // Returns the prefix for this network.
+  const IPAddress& prefix() const { return prefix_; }
+  // Returns the length, in bits, of this network's prefix.
+  int prefix_length() const { return prefix_length_; }
+
+  // Returns the Network's current idea of the 'best' IP it has.
+  // 'Best' currently means the first one added.
+  // TODO: We should be preferring temporary addresses.
+  // Returns an unset IP if this network has no active addresses.
+  IPAddress ip() const {
+    if (ips_.size() == 0) {
+      return IPAddress();
+    }
+    return ips_.at(0);
+  }
+  // Adds an active IP address to this network. Does not check for duplicates.
+  void AddIP(const IPAddress& ip) { ips_.push_back(ip); }
+
+  // Sets the network's IP address list. Returns true if new IP addresses were
+  // detected. Passing true to already_changed skips this check.
+  bool SetIPs(const std::vector<IPAddress>& ips, bool already_changed);
+  // Get the list of IP Addresses associated with this network.
+  const std::vector<IPAddress>& GetIPs() { return ips_;}
+  // Clear the network's list of addresses.
+  void ClearIPs() { ips_.clear(); }
+
+  // Returns the scope-id of the network's address.
+  // Should only be relevant for link-local IPv6 addresses.
+  int scope_id() const { return scope_id_; }
+  void set_scope_id(int id) { scope_id_ = id; }
+
+  // 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; }
+
+  // Debugging description of this network
+  std::string ToString() const;
+
+ private:
+  typedef std::vector<NetworkSession*> SessionList;
+
+  std::string name_;
+  std::string description_;
+  IPAddress prefix_;
+  int prefix_length_;
+  std::vector<IPAddress> ips_;
+  int scope_id_;
+  bool ignored_;
+  SessionList sessions_;
+  double uniform_numerator_;
+  double uniform_denominator_;
+  double exponential_numerator_;
+  double exponential_denominator_;
+
+  friend class NetworkManager;
+};
+}  // namespace talk_base
+
+#endif  // TALK_BASE_NETWORK_H_
diff --git a/talk/base/network_unittest.cc b/talk/base/network_unittest.cc
new file mode 100644
index 0000000..146b785
--- /dev/null
+++ b/talk/base/network_unittest.cc
@@ -0,0 +1,512 @@
+/*
+ * 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/network.h"
+
+#include <vector>
+#if defined(POSIX)
+#include <sys/types.h>
+#ifndef ANDROID
+#include <ifaddrs.h>
+#else
+#include "talk/base/ifaddrs-android.h"
+#endif
+#endif
+#include "talk/base/gunit.h"
+
+namespace talk_base {
+
+class NetworkTest : public testing::Test, public sigslot::has_slots<>  {
+ public:
+  NetworkTest() : callback_called_(false) {}
+
+  void OnNetworksChanged() {
+    callback_called_ = true;
+  }
+
+  void MergeNetworkList(BasicNetworkManager& network_manager,
+                        const NetworkManager::NetworkList& list,
+                        bool* changed ) {
+    network_manager.MergeNetworkList(list, changed);
+  }
+
+  bool IsIgnoredNetwork(const Network& network) {
+    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;
+  }
+
+#if defined(POSIX)
+  // Separated from CreateNetworks for tests.
+  static void CallConvertIfAddrs(const BasicNetworkManager& network_manager,
+                                 struct ifaddrs* interfaces,
+                                 bool include_ignored,
+                                 NetworkManager::NetworkList* networks) {
+    network_manager.ConvertIfAddrs(interfaces, include_ignored, networks);
+  }
+#endif  // defined(POSIX)
+
+ protected:
+  bool callback_called_;
+};
+
+// Test that the Network ctor works properly.
+TEST_F(NetworkTest, TestNetworkConstruct) {
+  Network ipv4_network1("test_eth0", "Test Network Adapter 1",
+                        IPAddress(0x12345600U), 24);
+  EXPECT_EQ("test_eth0", ipv4_network1.name());
+  EXPECT_EQ("Test Network Adapter 1", ipv4_network1.description());
+  EXPECT_EQ(IPAddress(0x12345600U), ipv4_network1.prefix());
+  EXPECT_EQ(24, ipv4_network1.prefix_length());
+  EXPECT_FALSE(ipv4_network1.ignored());
+}
+
+// Tests that our ignore function works properly.
+TEST_F(NetworkTest, TestNetworkIgnore) {
+  Network ipv4_network1("test_eth0", "Test Network Adapter 1",
+                        IPAddress(0x12345600U), 24);
+  Network ipv4_network2("test_eth1", "Test Network Adapter 2",
+                        IPAddress(0x00010000U), 16);
+  EXPECT_FALSE(IsIgnoredNetwork(ipv4_network1));
+  EXPECT_TRUE(IsIgnoredNetwork(ipv4_network2));
+}
+
+TEST_F(NetworkTest, TestCreateNetworks) {
+  BasicNetworkManager manager;
+  NetworkManager::NetworkList result = GetNetworks(manager, true);
+  // We should be able to bind to any addresses we find.
+  NetworkManager::NetworkList::iterator it;
+  for (it = result.begin();
+       it != result.end();
+       ++it) {
+    sockaddr_storage storage;
+    memset(&storage, 0, sizeof(storage));
+    IPAddress ip = (*it)->ip();
+    SocketAddress bindaddress(ip, 0);
+    bindaddress.SetScopeID((*it)->scope_id());
+    // TODO: Make this use talk_base::AsyncSocket once it supports IPv6.
+    int fd = static_cast<int>(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),
+                           static_cast<int>(ipsize));
+      EXPECT_EQ(0, success);
+#ifdef WIN32
+      closesocket(fd);
+#else
+      close(fd);
+#endif
+    }
+  }
+}
+
+// Test that UpdateNetworks succeeds.
+TEST_F(NetworkTest, TestUpdateNetworks) {
+  BasicNetworkManager manager;
+  manager.SignalNetworksChanged.connect(
+      static_cast<NetworkTest*>(this), &NetworkTest::OnNetworksChanged);
+  manager.StartUpdating();
+  Thread::Current()->ProcessMessages(0);
+  EXPECT_TRUE(callback_called_);
+  callback_called_ = false;
+  // Callback should be triggered immediately when StartUpdating
+  // is called, after network update signal is already sent.
+  manager.StartUpdating();
+  EXPECT_TRUE(manager.started());
+  Thread::Current()->ProcessMessages(0);
+  EXPECT_TRUE(callback_called_);
+  manager.StopUpdating();
+  EXPECT_TRUE(manager.started());
+  manager.StopUpdating();
+  EXPECT_FALSE(manager.started());
+  manager.StopUpdating();
+  EXPECT_FALSE(manager.started());
+  callback_called_ = false;
+  // Callback should be triggered immediately after StartUpdating is called
+  // when start_count_ is reset to 0.
+  manager.StartUpdating();
+  Thread::Current()->ProcessMessages(0);
+  EXPECT_TRUE(callback_called_);
+}
+
+// Verify that MergeNetworkList() merges network lists properly.
+TEST_F(NetworkTest, TestBasicMergeNetworkList) {
+  Network ipv4_network1("test_eth0", "Test Network Adapter 1",
+                        IPAddress(0x12345600U), 24);
+  Network ipv4_network2("test_eth1", "Test Network Adapter 2",
+                        IPAddress(0x00010000U), 16);
+  ipv4_network1.AddIP(IPAddress(0x12345678));
+  ipv4_network2.AddIP(IPAddress(0x00010004));
+  BasicNetworkManager manager;
+
+  // Add ipv4_network1 to the list of networks.
+  NetworkManager::NetworkList list;
+  list.push_back(new Network(ipv4_network1));
+  bool changed;
+  MergeNetworkList(manager, list, &changed);
+  EXPECT_TRUE(changed);
+  list.clear();
+
+  manager.GetNetworks(&list);
+  EXPECT_EQ(1U, list.size());
+  EXPECT_EQ(ipv4_network1.ToString(), list[0]->ToString());
+  Network* net1 = list[0];
+  list.clear();
+
+  // Replace ipv4_network1 with ipv4_network2.
+  list.push_back(new Network(ipv4_network2));
+  MergeNetworkList(manager, list, &changed);
+  EXPECT_TRUE(changed);
+  list.clear();
+
+  manager.GetNetworks(&list);
+  EXPECT_EQ(1U, list.size());
+  EXPECT_EQ(ipv4_network2.ToString(), list[0]->ToString());
+  Network* net2 = list[0];
+  list.clear();
+
+  // Add Network2 back.
+  list.push_back(new Network(ipv4_network1));
+  list.push_back(new Network(ipv4_network2));
+  MergeNetworkList(manager, list, &changed);
+  EXPECT_TRUE(changed);
+  list.clear();
+
+  // Verify that we get previous instances of Network objects.
+  manager.GetNetworks(&list);
+  EXPECT_EQ(2U, list.size());
+  EXPECT_TRUE((net1 == list[0] && net2 == list[1]) ||
+              (net1 == list[1] && net2 == list[0]));
+  list.clear();
+
+  // Call MergeNetworkList() again and verify that we don't get update
+  // notification.
+  list.push_back(new Network(ipv4_network2));
+  list.push_back(new Network(ipv4_network1));
+  MergeNetworkList(manager, list, &changed);
+  EXPECT_FALSE(changed);
+  list.clear();
+
+  // Verify that we get previous instances of Network objects.
+  manager.GetNetworks(&list);
+  EXPECT_EQ(2U, list.size());
+  EXPECT_TRUE((net1 == list[0] && net2 == list[1]) ||
+              (net1 == list[1] && net2 == list[0]));
+  list.clear();
+}
+
+// Sets up some test IPv6 networks and appends them to list.
+// Four networks are added - public and link local, for two interfaces.
+void SetupNetworks(NetworkManager::NetworkList* list) {
+  IPAddress ip;
+  IPAddress prefix;
+  EXPECT_TRUE(IPFromString("fe80::1234:5678:abcd:ef12", &ip));
+  EXPECT_TRUE(IPFromString("fe80::", &prefix));
+  // First, fake link-locals.
+  Network ipv6_eth0_linklocalnetwork("test_eth0", "Test NetworkAdapter 1",
+                                     prefix, 64);
+  ipv6_eth0_linklocalnetwork.AddIP(ip);
+  EXPECT_TRUE(IPFromString("fe80::5678:abcd:ef12:3456", &ip));
+  Network ipv6_eth1_linklocalnetwork("test_eth1", "Test NetworkAdapter 2",
+                                     prefix, 64);
+  ipv6_eth1_linklocalnetwork.AddIP(ip);
+  // Public networks:
+  EXPECT_TRUE(IPFromString("2401:fa00:4:1000:be30:5bff:fee5:c3", &ip));
+  prefix = TruncateIP(ip, 64);
+  Network ipv6_eth0_publicnetwork1_ip1("test_eth0", "Test NetworkAdapter 1",
+                                       prefix, 64);
+  ipv6_eth0_publicnetwork1_ip1.AddIP(ip);
+  EXPECT_TRUE(IPFromString("2400:4030:1:2c00:be30:abcd:efab:cdef", &ip));
+  prefix = TruncateIP(ip, 64);
+  Network ipv6_eth1_publicnetwork1_ip1("test_eth1", "Test NetworkAdapter 1",
+                                       prefix, 64);
+  ipv6_eth1_publicnetwork1_ip1.AddIP(ip);
+  list->push_back(new Network(ipv6_eth0_linklocalnetwork));
+  list->push_back(new Network(ipv6_eth1_linklocalnetwork));
+  list->push_back(new Network(ipv6_eth0_publicnetwork1_ip1));
+  list->push_back(new Network(ipv6_eth1_publicnetwork1_ip1));
+}
+
+// Test that the basic network merging case works.
+TEST_F(NetworkTest, TestIPv6MergeNetworkList) {
+  BasicNetworkManager manager;
+  manager.SignalNetworksChanged.connect(
+      static_cast<NetworkTest*>(this), &NetworkTest::OnNetworksChanged);
+  NetworkManager::NetworkList original_list;
+  SetupNetworks(&original_list);
+  bool changed = false;
+  MergeNetworkList(manager, original_list, &changed);
+  EXPECT_TRUE(changed);
+  NetworkManager::NetworkList list;
+  manager.GetNetworks(&list);
+  EXPECT_EQ(original_list.size(), list.size());
+  // Verify that the original members are in the merged list.
+  for (NetworkManager::NetworkList::iterator it = original_list.begin();
+       it != original_list.end(); ++it) {
+    EXPECT_NE(list.end(), std::find(list.begin(), list.end(), *it));
+  }
+}
+
+// Tests that when two network lists that describe the same set of networks are
+// merged, that the changed callback is not called, and that the original
+// objects remain in the result list.
+TEST_F(NetworkTest, TestNoChangeMerge) {
+  BasicNetworkManager manager;
+  manager.SignalNetworksChanged.connect(
+      static_cast<NetworkTest*>(this), &NetworkTest::OnNetworksChanged);
+  NetworkManager::NetworkList original_list;
+  SetupNetworks(&original_list);
+  bool changed = false;
+  MergeNetworkList(manager, original_list, &changed);
+  EXPECT_TRUE(changed);
+  // Second list that describes the same networks but with new objects.
+  NetworkManager::NetworkList second_list;
+  SetupNetworks(&second_list);
+  changed = false;
+  MergeNetworkList(manager, second_list, &changed);
+  EXPECT_FALSE(changed);
+  NetworkManager::NetworkList resulting_list;
+  manager.GetNetworks(&resulting_list);
+  EXPECT_EQ(original_list.size(), resulting_list.size());
+  // Verify that the original members are in the merged list.
+  for (NetworkManager::NetworkList::iterator it = original_list.begin();
+       it != original_list.end(); ++it) {
+    EXPECT_NE(resulting_list.end(),
+              std::find(resulting_list.begin(), resulting_list.end(), *it));
+  }
+  // Doublecheck that the new networks aren't in the list.
+  for (NetworkManager::NetworkList::iterator it = second_list.begin();
+       it != second_list.end(); ++it) {
+    EXPECT_EQ(resulting_list.end(),
+              std::find(resulting_list.begin(), resulting_list.end(), *it));
+  }
+}
+
+// Test that we can merge a network that is the same as another network but with
+// a different IP. The original network should remain in the list, but have its
+// IP changed.
+TEST_F(NetworkTest, MergeWithChangedIP) {
+  BasicNetworkManager manager;
+  manager.SignalNetworksChanged.connect(
+      static_cast<NetworkTest*>(this), &NetworkTest::OnNetworksChanged);
+  NetworkManager::NetworkList original_list;
+  SetupNetworks(&original_list);
+  // Make a network that we're going to change.
+  IPAddress ip;
+  EXPECT_TRUE(IPFromString("2401:fa01:4:1000:be30:faa:fee:faa", &ip));
+  IPAddress prefix = TruncateIP(ip, 64);
+  Network* network_to_change = new Network("test_eth0",
+                                          "Test Network Adapter 1",
+                                          prefix, 64);
+  Network* changed_network = new Network(*network_to_change);
+  network_to_change->AddIP(ip);
+  IPAddress changed_ip;
+  EXPECT_TRUE(IPFromString("2401:fa01:4:1000:be30:f00:f00:f00", &changed_ip));
+  changed_network->AddIP(changed_ip);
+  original_list.push_back(network_to_change);
+  bool changed = false;
+  MergeNetworkList(manager, original_list, &changed);
+  NetworkManager::NetworkList second_list;
+  SetupNetworks(&second_list);
+  second_list.push_back(changed_network);
+  changed = false;
+  MergeNetworkList(manager, second_list, &changed);
+  EXPECT_TRUE(changed);
+  NetworkManager::NetworkList list;
+  manager.GetNetworks(&list);
+  EXPECT_EQ(original_list.size(), list.size());
+  // Make sure the original network is still in the merged list.
+  EXPECT_NE(list.end(),
+            std::find(list.begin(), list.end(), network_to_change));
+  EXPECT_EQ(changed_ip, network_to_change->GetIPs().at(0));
+}
+
+// Testing a similar case to above, but checking that a network can be updated
+// with additional IPs (not just a replacement).
+TEST_F(NetworkTest, TestMultipleIPMergeNetworkList) {
+  BasicNetworkManager manager;
+  manager.SignalNetworksChanged.connect(
+      static_cast<NetworkTest*>(this), &NetworkTest::OnNetworksChanged);
+  NetworkManager::NetworkList original_list;
+  SetupNetworks(&original_list);
+  bool changed = false;
+  MergeNetworkList(manager, original_list, &changed);
+  EXPECT_TRUE(changed);
+  IPAddress ip;
+  IPAddress check_ip;
+  IPAddress prefix;
+  // Add a second IP to the public network on eth0 (2401:fa00:4:1000/64).
+  EXPECT_TRUE(IPFromString("2401:fa00:4:1000:be30:5bff:fee5:c6", &ip));
+  prefix = TruncateIP(ip, 64);
+  Network ipv6_eth0_publicnetwork1_ip2("test_eth0", "Test NetworkAdapter 1",
+                                       prefix, 64);
+  // This is the IP that already existed in the public network on eth0.
+  EXPECT_TRUE(IPFromString("2401:fa00:4:1000:be30:5bff:fee5:c3", &check_ip));
+  ipv6_eth0_publicnetwork1_ip2.AddIP(ip);
+  original_list.push_back(new Network(ipv6_eth0_publicnetwork1_ip2));
+  changed = false;
+  MergeNetworkList(manager, original_list, &changed);
+  EXPECT_TRUE(changed);
+  // There should still be four networks.
+  NetworkManager::NetworkList list;
+  manager.GetNetworks(&list);
+  EXPECT_EQ(4U, list.size());
+  // Check the gathered IPs.
+  int matchcount = 0;
+  for (NetworkManager::NetworkList::iterator it = list.begin();
+       it != list.end(); ++it) {
+    if ((*it)->ToString() == original_list[2]->ToString()) {
+      ++matchcount;
+      EXPECT_EQ(1, matchcount);
+      // This should be the same network object as before.
+      EXPECT_EQ((*it), original_list[2]);
+      // But with two addresses now.
+      EXPECT_EQ(2U, (*it)->GetIPs().size());
+      EXPECT_NE((*it)->GetIPs().end(),
+                std::find((*it)->GetIPs().begin(),
+                          (*it)->GetIPs().end(),
+                          check_ip));
+      EXPECT_NE((*it)->GetIPs().end(),
+                std::find((*it)->GetIPs().begin(),
+                          (*it)->GetIPs().end(),
+                          ip));
+    } else {
+      // Check the IP didn't get added anywhere it wasn't supposed to.
+      EXPECT_EQ((*it)->GetIPs().end(),
+                std::find((*it)->GetIPs().begin(),
+                          (*it)->GetIPs().end(),
+                          ip));
+    }
+  }
+}
+
+// Test that merge correctly distinguishes multiple networks on an interface.
+TEST_F(NetworkTest, TestMultiplePublicNetworksOnOneInterfaceMerge) {
+  BasicNetworkManager manager;
+  manager.SignalNetworksChanged.connect(
+      static_cast<NetworkTest*>(this), &NetworkTest::OnNetworksChanged);
+  NetworkManager::NetworkList original_list;
+  SetupNetworks(&original_list);
+  bool changed = false;
+  MergeNetworkList(manager, original_list, &changed);
+  EXPECT_TRUE(changed);
+  IPAddress ip;
+  IPAddress prefix;
+  // A second network for eth0.
+  EXPECT_TRUE(IPFromString("2400:4030:1:2c00:be30:5bff:fee5:c3", &ip));
+  prefix = TruncateIP(ip, 64);
+  Network ipv6_eth0_publicnetwork2_ip1("test_eth0", "Test NetworkAdapter 1",
+                                       prefix, 64);
+  ipv6_eth0_publicnetwork2_ip1.AddIP(ip);
+  original_list.push_back(new Network(ipv6_eth0_publicnetwork2_ip1));
+  changed = false;
+  MergeNetworkList(manager, original_list, &changed);
+  EXPECT_TRUE(changed);
+  // There should be five networks now.
+  NetworkManager::NetworkList list;
+  manager.GetNetworks(&list);
+  EXPECT_EQ(5U, list.size());
+  // Check the resulting addresses.
+  for (NetworkManager::NetworkList::iterator it = list.begin();
+       it != list.end(); ++it) {
+    if ((*it)->prefix() == ipv6_eth0_publicnetwork2_ip1.prefix() &&
+        (*it)->name() == ipv6_eth0_publicnetwork2_ip1.name()) {
+      // Check the new network has 1 IP and that it's the correct one.
+      EXPECT_EQ(1U, (*it)->GetIPs().size());
+      EXPECT_EQ(ip, (*it)->GetIPs().at(0));
+    } else {
+      // Check the IP didn't get added anywhere it wasn't supposed to.
+      EXPECT_EQ((*it)->GetIPs().end(),
+                std::find((*it)->GetIPs().begin(),
+                          (*it)->GetIPs().end(),
+                          ip));
+    }
+  }
+}
+
+// Test that DumpNetworks works.
+TEST_F(NetworkTest, TestDumpNetworks) {
+  BasicNetworkManager manager;
+  manager.DumpNetworks(true);
+}
+
+// Test that we can toggle IPv6 on and off.
+TEST_F(NetworkTest, TestIPv6Toggle) {
+  BasicNetworkManager manager;
+  bool ipv6_found = false;
+  NetworkManager::NetworkList list;
+#ifndef WIN32
+  // There should be at least one IPv6 network (fe80::/64 should be in there).
+  // TODO: Disabling this test on windows for the moment as the test
+  // machines don't seem to have IPv6 installed on them at all.
+  manager.set_ipv6_enabled(true);
+  list = GetNetworks(manager, true);
+  for (NetworkManager::NetworkList::iterator it = list.begin();
+       it != list.end(); ++it) {
+    if ((*it)->prefix().family() == AF_INET6) {
+      ipv6_found = true;
+      break;
+    }
+  }
+  EXPECT_TRUE(ipv6_found);
+#endif
+  ipv6_found = false;
+  manager.set_ipv6_enabled(false);
+  list = GetNetworks(manager, true);
+  for (NetworkManager::NetworkList::iterator it = list.begin();
+       it != list.end(); ++it) {
+    if ((*it)->prefix().family() == AF_INET6) {
+      ipv6_found = true;
+      break;
+    }
+  }
+  EXPECT_FALSE(ipv6_found);
+}
+
+#if defined(POSIX)
+// Verify that we correctly handle interfaces with no address.
+TEST_F(NetworkTest, TestConvertIfAddrsNoAddress) {
+  ifaddrs list;
+  memset(&list, 0, sizeof(list));
+  list.ifa_name = const_cast<char*>("test_iface");
+
+  NetworkManager::NetworkList result;
+  BasicNetworkManager manager;
+  CallConvertIfAddrs(manager, &list, true, &result);
+  EXPECT_TRUE(result.empty());
+}
+#endif  // defined(POSIX)
+
+
+}  // namespace talk_base
diff --git a/talk/base/nssidentity.cc b/talk/base/nssidentity.cc
new file mode 100644
index 0000000..b587888
--- /dev/null
+++ b/talk/base/nssidentity.cc
@@ -0,0 +1,428 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ * Copyright 2012, 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.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif  // HAVE_CONFIG_H
+
+#if HAVE_NSS_SSL_H
+
+#include "talk/base/nssidentity.h"
+
+#include <string>
+
+#include "cert.h"
+#include "cryptohi.h"
+#include "keyhi.h"
+#include "nss.h"
+#include "pk11pub.h"
+#include "sechash.h"
+
+#include "talk/base/base64.h"
+#include "talk/base/logging.h"
+#include "talk/base/helpers.h"
+#include "talk/base/nssstreamadapter.h"
+
+namespace talk_base {
+
+// Helper function to parse PEM-encoded DER.
+static bool PemToDer(const std::string& pem_type,
+                     const std::string& pem_string,
+                     std::string* der) {
+  // Find the inner body. We need this to fulfill the contract of
+  // returning pem_length.
+  size_t header = pem_string.find("-----BEGIN " + pem_type + "-----");
+  if (header == std::string::npos)
+    return false;
+
+  size_t body = pem_string.find("\n", header);
+  if (body == std::string::npos)
+    return false;
+
+  size_t trailer = pem_string.find("-----END " + pem_type + "-----");
+  if (trailer == std::string::npos)
+    return false;
+
+  std::string inner = pem_string.substr(body + 1, trailer - (body + 1));
+
+  *der = Base64::Decode(inner, Base64::DO_PARSE_WHITE |
+                        Base64::DO_PAD_ANY |
+                        Base64::DO_TERM_BUFFER);
+  return true;
+}
+
+static std::string DerToPem(const std::string& pem_type,
+                            const unsigned char *data,
+                            size_t length) {
+  std::stringstream result;
+
+  result << "-----BEGIN " << pem_type << "-----\n";
+
+  std::string tmp;
+  Base64::EncodeFromArray(data, length, &tmp);
+  result << tmp;
+
+  result << "-----END " << pem_type << "-----\n";
+
+  return result.str();
+}
+
+NSSKeyPair::~NSSKeyPair() {
+  if (privkey_)
+    SECKEY_DestroyPrivateKey(privkey_);
+  if (pubkey_)
+    SECKEY_DestroyPublicKey(pubkey_);
+}
+
+NSSKeyPair *NSSKeyPair::Generate() {
+  SECKEYPrivateKey *privkey = NULL;
+  SECKEYPublicKey *pubkey = NULL;
+  PK11RSAGenParams rsaparams;
+  rsaparams.keySizeInBits = 1024;
+  rsaparams.pe = 0x010001;  // 65537 -- a common RSA public exponent.
+
+  privkey = PK11_GenerateKeyPair(NSSContext::GetSlot(),
+                                 CKM_RSA_PKCS_KEY_PAIR_GEN,
+                                 &rsaparams, &pubkey, PR_FALSE /*permanent*/,
+                                 PR_FALSE /*sensitive*/, NULL);
+  if (!privkey) {
+    LOG(LS_ERROR) << "Couldn't generate key pair";
+    return NULL;
+  }
+
+  return new NSSKeyPair(privkey, pubkey);
+}
+
+// Just make a copy.
+NSSKeyPair *NSSKeyPair::GetReference() {
+  SECKEYPrivateKey *privkey = SECKEY_CopyPrivateKey(privkey_);
+  if (!privkey)
+    return NULL;
+
+  SECKEYPublicKey *pubkey = SECKEY_CopyPublicKey(pubkey_);
+  if (!pubkey) {
+    SECKEY_DestroyPrivateKey(privkey);
+    return NULL;
+  }
+
+  return new NSSKeyPair(privkey, pubkey);
+}
+
+NSSCertificate *NSSCertificate::FromPEMString(const std::string &pem_string) {
+  std::string der;
+  if (!PemToDer("CERTIFICATE", pem_string, &der))
+    return NULL;
+
+  SECItem der_cert;
+  der_cert.data = reinterpret_cast<unsigned char *>(const_cast<char *>(
+      der.data()));
+  der_cert.len = der.size();
+  CERTCertificate *cert = CERT_NewTempCertificate(CERT_GetDefaultCertDB(),
+      &der_cert, NULL, PR_FALSE, PR_TRUE);
+
+  if (!cert)
+    return NULL;
+
+  return new NSSCertificate(cert);
+}
+
+NSSCertificate *NSSCertificate::GetReference() const {
+  CERTCertificate *certificate = CERT_DupCertificate(certificate_);
+  if (!certificate)
+    return NULL;
+
+  return new NSSCertificate(certificate);
+}
+
+std::string NSSCertificate::ToPEMString() const {
+  return DerToPem("CERTIFICATE",
+                  certificate_->derCert.data,
+                  certificate_->derCert.len);
+}
+
+bool NSSCertificate::GetDigestLength(const std::string &algorithm,
+                                     std::size_t *length) {
+  const SECHashObject *ho;
+
+  if (!GetDigestObject(algorithm, &ho))
+    return false;
+
+  *length = ho->length;
+
+  return true;
+}
+
+bool NSSCertificate::ComputeDigest(const std::string &algorithm,
+                                   unsigned char *digest, std::size_t size,
+                                   std::size_t *length) const {
+  const SECHashObject *ho;
+
+  if (!GetDigestObject(algorithm, &ho))
+    return false;
+
+  if (size < ho->length)  // Sanity check for fit
+    return false;
+
+  SECStatus rv = HASH_HashBuf(ho->type, digest,
+                              certificate_->derCert.data,
+                              certificate_->derCert.len);
+  if (rv != SECSuccess)
+    return false;
+
+  *length = ho->length;
+
+  return true;
+}
+
+bool NSSCertificate::Equals(const NSSCertificate *tocompare) const {
+  if (!certificate_->derCert.len)
+    return false;
+  if (!tocompare->certificate_->derCert.len)
+    return false;
+
+  if (certificate_->derCert.len != tocompare->certificate_->derCert.len)
+    return false;
+
+  return memcmp(certificate_->derCert.data,
+                tocompare->certificate_->derCert.data,
+                certificate_->derCert.len) == 0;
+}
+
+
+bool NSSCertificate::GetDigestObject(const std::string &algorithm,
+                                     const SECHashObject **hop) {
+  const SECHashObject *ho;
+  HASH_HashType hash_type;
+
+  if (algorithm == DIGEST_SHA_1) {
+    hash_type = HASH_AlgSHA1;
+  // HASH_AlgSHA224 is not supported in the chromium linux build system.
+#if 0
+  } else if (algorithm == DIGEST_SHA_224) {
+    hash_type = HASH_AlgSHA224;
+#endif
+  } else if (algorithm == DIGEST_SHA_256) {
+    hash_type = HASH_AlgSHA256;
+  } else if (algorithm == DIGEST_SHA_384) {
+    hash_type = HASH_AlgSHA384;
+  } else if (algorithm == DIGEST_SHA_512) {
+    hash_type = HASH_AlgSHA512;
+  } else {
+    return false;
+  }
+
+  ho = HASH_GetHashObject(hash_type);
+
+  ASSERT(ho->length >= 20);  // Can't happen
+  *hop = ho;
+
+  return true;
+}
+
+
+NSSIdentity *NSSIdentity::Generate(const std::string &common_name) {
+  std::string subject_name_string = "CN=" + common_name;
+  CERTName *subject_name = CERT_AsciiToName(
+      const_cast<char *>(subject_name_string.c_str()));
+  NSSIdentity *identity = NULL;
+  CERTSubjectPublicKeyInfo *spki = NULL;
+  CERTCertificateRequest *certreq = NULL;
+  CERTValidity *validity;
+  CERTCertificate *certificate = NULL;
+  NSSKeyPair *keypair = NSSKeyPair::Generate();
+  SECItem inner_der;
+  SECStatus rv;
+  PLArenaPool* arena;
+  SECItem signed_cert;
+  PRTime not_before, not_after;
+  PRTime now = PR_Now();
+  PRTime one_day;
+
+  inner_der.len = 0;
+  inner_der.data = NULL;
+
+  if (!keypair) {
+    LOG(LS_ERROR) << "Couldn't generate key pair";
+    goto fail;
+  }
+
+  if (!subject_name) {
+    LOG(LS_ERROR) << "Couldn't convert subject name " << subject_name;
+    goto fail;
+  }
+
+  spki = SECKEY_CreateSubjectPublicKeyInfo(keypair->pubkey());
+  if (!spki) {
+    LOG(LS_ERROR) << "Couldn't create SPKI";
+    goto fail;
+  }
+
+  certreq = CERT_CreateCertificateRequest(subject_name, spki, NULL);
+  if (!certreq) {
+    LOG(LS_ERROR) << "Couldn't create certificate signing request";
+    goto fail;
+  }
+
+  one_day = 86400;
+  one_day *= PR_USEC_PER_SEC;
+  not_before = now - one_day;
+  not_after = now + 30 * one_day;
+
+  validity = CERT_CreateValidity(not_before, not_after);
+  if (!validity) {
+    LOG(LS_ERROR) << "Couldn't create validity";
+    goto fail;
+  }
+
+  unsigned long serial;
+  // Note: This serial in principle could collide, but it's unlikely
+  rv = PK11_GenerateRandom(reinterpret_cast<unsigned char *>(&serial),
+                           sizeof(serial));
+  if (rv != SECSuccess) {
+    LOG(LS_ERROR) << "Couldn't generate random serial";
+    goto fail;
+  }
+
+  certificate = CERT_CreateCertificate(serial, subject_name, validity, certreq);
+  if (!certificate) {
+    LOG(LS_ERROR) << "Couldn't create certificate";
+    goto fail;
+  }
+
+  arena = certificate->arena;
+
+  rv = SECOID_SetAlgorithmID(arena, &certificate->signature,
+                             SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, NULL);
+  if (rv != SECSuccess)
+    goto fail;
+
+  // Set version to X509v3.
+  *(certificate->version.data) = 2;
+  certificate->version.len = 1;
+
+  if (!SEC_ASN1EncodeItem(arena, &inner_der, certificate,
+                          SEC_ASN1_GET(CERT_CertificateTemplate)))
+    goto fail;
+
+  rv = SEC_DerSignData(arena, &signed_cert, inner_der.data, inner_der.len,
+                       keypair->privkey(),
+                       SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION);
+  if (rv != SECSuccess) {
+    LOG(LS_ERROR) << "Couldn't sign certificate";
+    goto fail;
+  }
+  certificate->derCert = signed_cert;
+
+  identity = new NSSIdentity(keypair, new NSSCertificate(certificate));
+
+  goto done;
+
+ fail:
+  delete keypair;
+  CERT_DestroyCertificate(certificate);
+
+ done:
+  if (subject_name) CERT_DestroyName(subject_name);
+  if (spki) SECKEY_DestroySubjectPublicKeyInfo(spki);
+  if (certreq) CERT_DestroyCertificateRequest(certreq);
+  if (validity) CERT_DestroyValidity(validity);
+  return identity;
+}
+
+SSLIdentity* NSSIdentity::FromPEMStrings(const std::string& private_key,
+                                         const std::string& certificate) {
+  std::string private_key_der;
+  if (!PemToDer(
+      "RSA PRIVATE KEY", private_key, &private_key_der))
+    return NULL;
+
+  SECItem private_key_item;
+  private_key_item.data =
+      reinterpret_cast<unsigned char *>(
+          const_cast<char *>(private_key_der.c_str()));
+  private_key_item.len = private_key_der.size();
+
+  const unsigned int key_usage = KU_KEY_ENCIPHERMENT | KU_DATA_ENCIPHERMENT |
+      KU_DIGITAL_SIGNATURE;
+
+  SECKEYPrivateKey* privkey = NULL;
+  SECStatus rv =
+      PK11_ImportDERPrivateKeyInfoAndReturnKey(NSSContext::GetSlot(),
+                                               &private_key_item,
+                                               NULL, NULL, PR_FALSE, PR_FALSE,
+                                               key_usage, &privkey, NULL);
+  if (rv != SECSuccess) {
+    LOG(LS_ERROR) << "Couldn't import private key";
+    return NULL;
+  }
+
+  SECKEYPublicKey *pubkey = SECKEY_ConvertToPublicKey(privkey);
+  if (rv != SECSuccess) {
+    SECKEY_DestroyPrivateKey(privkey);
+    LOG(LS_ERROR) << "Couldn't convert private key to public key";
+    return NULL;
+  }
+
+  // Assign to a scoped_ptr so we don't leak on error.
+  scoped_ptr<NSSKeyPair> keypair(new NSSKeyPair(privkey, pubkey));
+
+  scoped_ptr<NSSCertificate> cert(NSSCertificate::FromPEMString(certificate));
+  if (!cert) {
+    LOG(LS_ERROR) << "Couldn't parse certificate";
+    return NULL;
+  }
+
+  // TODO(ekr@rtfm.com): Check the public key against the certificate.
+
+  return new NSSIdentity(keypair.release(), cert.release());
+}
+
+NSSIdentity *NSSIdentity::GetReference() const {
+  NSSKeyPair *keypair = keypair_->GetReference();
+  if (!keypair)
+    return NULL;
+
+  NSSCertificate *certificate = certificate_->GetReference();
+  if (!certificate) {
+    delete keypair;
+    return NULL;
+  }
+
+  return new NSSIdentity(keypair, certificate);
+}
+
+
+NSSCertificate &NSSIdentity::certificate() const {
+  return *certificate_;
+}
+
+
+}  // talk_base namespace
+
+#endif  // HAVE_NSS_SSL_H
+
diff --git a/talk/base/nssidentity.h b/talk/base/nssidentity.h
new file mode 100644
index 0000000..725c546
--- /dev/null
+++ b/talk/base/nssidentity.h
@@ -0,0 +1,128 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_NSSIDENTITY_H_
+#define TALK_BASE_NSSIDENTITY_H_
+
+#include <string>
+
+#include "cert.h"
+#include "nspr.h"
+#include "hasht.h"
+#include "keythi.h"
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sslidentity.h"
+
+namespace talk_base {
+
+class NSSKeyPair {
+ public:
+  NSSKeyPair(SECKEYPrivateKey* privkey, SECKEYPublicKey* pubkey) :
+      privkey_(privkey), pubkey_(pubkey) {}
+  ~NSSKeyPair();
+
+  // Generate a 1024-bit RSA key pair.
+  static NSSKeyPair* Generate();
+  NSSKeyPair* GetReference();
+
+  SECKEYPrivateKey* privkey() const { return privkey_; }
+  SECKEYPublicKey * pubkey() const { return pubkey_; }
+
+ private:
+  SECKEYPrivateKey* privkey_;
+  SECKEYPublicKey* pubkey_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(NSSKeyPair);
+};
+
+
+class NSSCertificate : public SSLCertificate {
+ public:
+  static NSSCertificate* FromPEMString(const std::string& pem_string);
+  explicit NSSCertificate(CERTCertificate* cert) : certificate_(cert) {}
+  virtual ~NSSCertificate() {
+    if (certificate_)
+      CERT_DestroyCertificate(certificate_);
+  }
+
+  virtual NSSCertificate* GetReference() const;
+
+  virtual std::string ToPEMString() const;
+
+  virtual bool ComputeDigest(const std::string& algorithm,
+                             unsigned char* digest, std::size_t size,
+                             std::size_t* length) const;
+
+  CERTCertificate* certificate() { return certificate_; }
+
+  // Helper function to get the length of a digest
+  static bool GetDigestLength(const std::string& algorithm,
+                              std::size_t* length);
+
+  // Comparison
+  bool Equals(const NSSCertificate* tocompare) const;
+
+ private:
+  static bool GetDigestObject(const std::string& algorithm,
+                              const SECHashObject** hash_object);
+
+  CERTCertificate* certificate_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(NSSCertificate);
+};
+
+// Represents a SSL key pair and certificate for NSS.
+class NSSIdentity : public SSLIdentity {
+ public:
+  static NSSIdentity* Generate(const std::string& common_name);
+  static SSLIdentity* FromPEMStrings(const std::string& private_key,
+                                     const std::string& certificate);
+  virtual ~NSSIdentity() {
+    LOG(LS_INFO) << "Destroying NSS identity";
+  }
+
+  virtual NSSIdentity* GetReference() const;
+  virtual NSSCertificate& certificate() const;
+
+  NSSKeyPair* keypair() const { return keypair_.get(); }
+
+ private:
+  NSSIdentity(NSSKeyPair* keypair, NSSCertificate* cert) :
+      keypair_(keypair), certificate_(cert) {}
+
+  talk_base::scoped_ptr<NSSKeyPair> keypair_;
+  talk_base::scoped_ptr<NSSCertificate> certificate_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(NSSIdentity);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_NSSIDENTITY_H_
diff --git a/talk/base/nssstreamadapter.cc b/talk/base/nssstreamadapter.cc
new file mode 100644
index 0000000..c9a540d
--- /dev/null
+++ b/talk/base/nssstreamadapter.cc
@@ -0,0 +1,1007 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ * Copyright 2004--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 <vector>
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif  // HAVE_CONFIG_H
+
+#if HAVE_NSS_SSL_H
+
+#include "talk/base/nssstreamadapter.h"
+
+#include "keyhi.h"
+#include "nspr.h"
+#include "nss.h"
+#include "pk11pub.h"
+#include "secerr.h"
+
+#ifdef NSS_SSL_RELATIVE_PATH
+#include "ssl.h"
+#include "sslerr.h"
+#include "sslproto.h"
+#else
+#include "net/third_party/nss/ssl/ssl.h"
+#include "net/third_party/nss/ssl/sslerr.h"
+#include "net/third_party/nss/ssl/sslproto.h"
+#endif
+
+#include "talk/base/nssidentity.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+PRDescIdentity NSSStreamAdapter::nspr_layer_identity = PR_INVALID_IO_LAYER;
+
+#define UNIMPLEMENTED \
+  PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); \
+  LOG(LS_ERROR) \
+  << "Call to unimplemented function "<< __FUNCTION__; ASSERT(false)
+
+#ifdef SRTP_AES128_CM_HMAC_SHA1_80
+#define HAVE_DTLS_SRTP
+#endif
+
+#ifdef HAVE_DTLS_SRTP
+// SRTP cipher suite table
+struct SrtpCipherMapEntry {
+  const char* external_name;
+  PRUint16 cipher_id;
+};
+
+// This isn't elegant, but it's better than an external reference
+static const SrtpCipherMapEntry kSrtpCipherMap[] = {
+  {"AES_CM_128_HMAC_SHA1_80", SRTP_AES128_CM_HMAC_SHA1_80 },
+  {"AES_CM_128_HMAC_SHA1_32", SRTP_AES128_CM_HMAC_SHA1_32 },
+  {NULL, 0}
+};
+#endif
+
+
+// Implementation of NSPR methods
+static PRStatus StreamClose(PRFileDesc *socket) {
+  // Noop
+  return PR_SUCCESS;
+}
+
+static PRInt32 StreamRead(PRFileDesc *socket, void *buf, PRInt32 length) {
+  StreamInterface *stream = reinterpret_cast<StreamInterface *>(socket->secret);
+  size_t read;
+  int error;
+  StreamResult result = stream->Read(buf, length, &read, &error);
+  if (result == SR_SUCCESS) {
+    return read;
+  }
+
+  if (result == SR_EOS) {
+    return 0;
+  }
+
+  if (result == SR_BLOCK) {
+    PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+    return -1;
+  }
+
+  PR_SetError(PR_UNKNOWN_ERROR, error);
+  return -1;
+}
+
+static PRInt32 StreamWrite(PRFileDesc *socket, const void *buf,
+                           PRInt32 length) {
+  StreamInterface *stream = reinterpret_cast<StreamInterface *>(socket->secret);
+  size_t written;
+  int error;
+  StreamResult result = stream->Write(buf, length, &written, &error);
+  if (result == SR_SUCCESS) {
+    return written;
+  }
+
+  if (result == SR_BLOCK) {
+    LOG(LS_INFO) <<
+        "NSSStreamAdapter: write to underlying transport would block";
+    PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+    return -1;
+  }
+
+  LOG(LS_ERROR) << "Write error";
+  PR_SetError(PR_UNKNOWN_ERROR, error);
+  return -1;
+}
+
+static PRInt32 StreamAvailable(PRFileDesc *socket) {
+  UNIMPLEMENTED;
+  return -1;
+}
+
+PRInt64 StreamAvailable64(PRFileDesc *socket) {
+  UNIMPLEMENTED;
+  return -1;
+}
+
+static PRStatus StreamSync(PRFileDesc *socket) {
+  UNIMPLEMENTED;
+  return PR_FAILURE;
+}
+
+static PROffset32 StreamSeek(PRFileDesc *socket, PROffset32 offset,
+                             PRSeekWhence how) {
+  UNIMPLEMENTED;
+  return -1;
+}
+
+static PROffset64 StreamSeek64(PRFileDesc *socket, PROffset64 offset,
+                               PRSeekWhence how) {
+  UNIMPLEMENTED;
+  return -1;
+}
+
+static PRStatus StreamFileInfo(PRFileDesc *socket, PRFileInfo *info) {
+  UNIMPLEMENTED;
+  return PR_FAILURE;
+}
+
+static PRStatus StreamFileInfo64(PRFileDesc *socket, PRFileInfo64 *info) {
+  UNIMPLEMENTED;
+  return PR_FAILURE;
+}
+
+static PRInt32 StreamWritev(PRFileDesc *socket, const PRIOVec *iov,
+                     PRInt32 iov_size, PRIntervalTime timeout) {
+  UNIMPLEMENTED;
+  return -1;
+}
+
+static PRStatus StreamConnect(PRFileDesc *socket, const PRNetAddr *addr,
+                              PRIntervalTime timeout) {
+  UNIMPLEMENTED;
+  return PR_FAILURE;
+}
+
+static PRFileDesc *StreamAccept(PRFileDesc *sd, PRNetAddr *addr,
+                                PRIntervalTime timeout) {
+  UNIMPLEMENTED;
+  return NULL;
+}
+
+static PRStatus StreamBind(PRFileDesc *socket, const PRNetAddr *addr) {
+  UNIMPLEMENTED;
+  return PR_FAILURE;
+}
+
+static PRStatus StreamListen(PRFileDesc *socket, PRIntn depth) {
+  UNIMPLEMENTED;
+  return PR_FAILURE;
+}
+
+static PRStatus StreamShutdown(PRFileDesc *socket, PRIntn how) {
+  UNIMPLEMENTED;
+  return PR_FAILURE;
+}
+
+// Note: this is always nonblocking and ignores the timeout.
+// TODO(ekr@rtfm.com): In future verify that the socket is
+// actually in non-blocking mode.
+// This function does not support peek.
+static PRInt32 StreamRecv(PRFileDesc *socket, void *buf, PRInt32 amount,
+                   PRIntn flags, PRIntervalTime to) {
+  ASSERT(flags == 0);
+
+  if (flags != 0) {
+    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
+    return -1;
+  }
+
+  return StreamRead(socket, buf, amount);
+}
+
+// Note: this is always nonblocking and assumes a zero timeout.
+// This function does not support peek.
+static PRInt32 StreamSend(PRFileDesc *socket, const void *buf,
+                          PRInt32 amount, PRIntn flags,
+                          PRIntervalTime to) {
+  ASSERT(flags == 0);
+
+  return StreamWrite(socket, buf, amount);
+}
+
+static PRInt32 StreamRecvfrom(PRFileDesc *socket, void *buf,
+                              PRInt32 amount, PRIntn flags,
+                              PRNetAddr *addr, PRIntervalTime to) {
+  UNIMPLEMENTED;
+  return -1;
+}
+
+static PRInt32 StreamSendto(PRFileDesc *socket, const void *buf,
+                            PRInt32 amount, PRIntn flags,
+                            const PRNetAddr *addr, PRIntervalTime to) {
+  UNIMPLEMENTED;
+  return -1;
+}
+
+static PRInt16 StreamPoll(PRFileDesc *socket, PRInt16 in_flags,
+                          PRInt16 *out_flags) {
+  UNIMPLEMENTED;
+  return -1;
+}
+
+static PRInt32 StreamAcceptRead(PRFileDesc *sd, PRFileDesc **nd,
+                                PRNetAddr **raddr,
+                                void *buf, PRInt32 amount, PRIntervalTime t) {
+  UNIMPLEMENTED;
+  return -1;
+}
+
+static PRInt32 StreamTransmitFile(PRFileDesc *sd, PRFileDesc *socket,
+                                  const void *headers, PRInt32 hlen,
+                                  PRTransmitFileFlags flags, PRIntervalTime t) {
+  UNIMPLEMENTED;
+  return -1;
+}
+
+static PRStatus StreamGetPeerName(PRFileDesc *socket, PRNetAddr *addr) {
+  // TODO(ekr@rtfm.com): Modify to return unique names for each channel
+  // somehow, as opposed to always the same static address. The current
+  // implementation messes up the session cache, which is why it's off
+  // elsewhere
+  addr->inet.family = PR_AF_INET;
+  addr->inet.port = 0;
+  addr->inet.ip = 0;
+
+  return PR_SUCCESS;
+}
+
+static PRStatus StreamGetSockName(PRFileDesc *socket, PRNetAddr *addr) {
+  UNIMPLEMENTED;
+  return PR_FAILURE;
+}
+
+static PRStatus StreamGetSockOption(PRFileDesc *socket, PRSocketOptionData *opt) {
+  switch (opt->option) {
+    case PR_SockOpt_Nonblocking:
+      opt->value.non_blocking = PR_TRUE;
+      return PR_SUCCESS;
+    default:
+      UNIMPLEMENTED;
+      break;
+  }
+
+  return PR_FAILURE;
+}
+
+// Imitate setting socket options. These are mostly noops.
+static PRStatus StreamSetSockOption(PRFileDesc *socket,
+                                    const PRSocketOptionData *opt) {
+  switch (opt->option) {
+    case PR_SockOpt_Nonblocking:
+      return PR_SUCCESS;
+    case PR_SockOpt_NoDelay:
+      return PR_SUCCESS;
+    default:
+      UNIMPLEMENTED;
+      break;
+  }
+
+  return PR_FAILURE;
+}
+
+static PRInt32 StreamSendfile(PRFileDesc *out, PRSendFileData *in,
+                              PRTransmitFileFlags flags, PRIntervalTime to) {
+  UNIMPLEMENTED;
+  return -1;
+}
+
+static PRStatus StreamConnectContinue(PRFileDesc *socket, PRInt16 flags) {
+  UNIMPLEMENTED;
+  return PR_FAILURE;
+}
+
+static PRIntn StreamReserved(PRFileDesc *socket) {
+  UNIMPLEMENTED;
+  return -1;
+}
+
+static const struct PRIOMethods nss_methods = {
+  PR_DESC_LAYERED,
+  StreamClose,
+  StreamRead,
+  StreamWrite,
+  StreamAvailable,
+  StreamAvailable64,
+  StreamSync,
+  StreamSeek,
+  StreamSeek64,
+  StreamFileInfo,
+  StreamFileInfo64,
+  StreamWritev,
+  StreamConnect,
+  StreamAccept,
+  StreamBind,
+  StreamListen,
+  StreamShutdown,
+  StreamRecv,
+  StreamSend,
+  StreamRecvfrom,
+  StreamSendto,
+  StreamPoll,
+  StreamAcceptRead,
+  StreamTransmitFile,
+  StreamGetSockName,
+  StreamGetPeerName,
+  StreamReserved,
+  StreamReserved,
+  StreamGetSockOption,
+  StreamSetSockOption,
+  StreamSendfile,
+  StreamConnectContinue,
+  StreamReserved,
+  StreamReserved,
+  StreamReserved,
+  StreamReserved
+};
+
+NSSStreamAdapter::NSSStreamAdapter(StreamInterface *stream)
+    : SSLStreamAdapterHelper(stream),
+      ssl_fd_(NULL),
+      cert_ok_(false) {
+}
+
+bool NSSStreamAdapter::Init() {
+  if (nspr_layer_identity == PR_INVALID_IO_LAYER) {
+    nspr_layer_identity = PR_GetUniqueIdentity("nssstreamadapter");
+  }
+  PRFileDesc *pr_fd = PR_CreateIOLayerStub(nspr_layer_identity, &nss_methods);
+  if (!pr_fd)
+    return false;
+  pr_fd->secret = reinterpret_cast<PRFilePrivate *>(stream());
+
+  PRFileDesc *ssl_fd;
+  if (ssl_mode_ == SSL_MODE_DTLS) {
+    ssl_fd = DTLS_ImportFD(NULL, pr_fd);
+  } else {
+    ssl_fd = SSL_ImportFD(NULL, pr_fd);
+  }
+  ASSERT(ssl_fd != NULL);  // This should never happen
+  if (!ssl_fd) {
+    PR_Close(pr_fd);
+    return false;
+  }
+
+  SECStatus rv;
+  // Turn on security.
+  rv = SSL_OptionSet(ssl_fd, SSL_SECURITY, PR_TRUE);
+  if (rv != SECSuccess) {
+    LOG(LS_ERROR) << "Error enabling security on SSL Socket";
+    return false;
+  }
+
+  // Disable SSLv2.
+  rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_SSL2, PR_FALSE);
+  if (rv != SECSuccess) {
+    LOG(LS_ERROR) << "Error disabling SSL2";
+    return false;
+  }
+
+  // Disable caching.
+  // TODO(ekr@rtfm.com): restore this when I have the caching
+  // identity set.
+  rv = SSL_OptionSet(ssl_fd, SSL_NO_CACHE, PR_TRUE);
+  if (rv != SECSuccess) {
+    LOG(LS_ERROR) << "Error disabling cache";
+    return false;
+  }
+
+  // Disable session tickets.
+  rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_SESSION_TICKETS, PR_FALSE);
+  if (rv != SECSuccess) {
+    LOG(LS_ERROR) << "Error enabling tickets";
+    return false;
+  }
+
+  // Disable renegotiation.
+  rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_RENEGOTIATION,
+                     SSL_RENEGOTIATE_NEVER);
+  if (rv != SECSuccess) {
+    LOG(LS_ERROR) << "Error disabling renegotiation";
+    return false;
+  }
+
+  // Disable false start.
+  rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_FALSE_START, PR_FALSE);
+  if (rv != SECSuccess) {
+    LOG(LS_ERROR) << "Error disabling false start";
+    return false;
+  }
+          
+  ssl_fd_ = ssl_fd;
+
+  return true;
+}
+
+NSSStreamAdapter::~NSSStreamAdapter() {
+  if (ssl_fd_)
+    PR_Close(ssl_fd_);
+};
+
+
+int NSSStreamAdapter::BeginSSL() {
+  SECStatus rv;
+
+  if (!Init()) {
+    Error("Init", -1, false);
+    return -1;
+  }
+
+  ASSERT(state_ == SSL_CONNECTING);
+  // The underlying stream has been opened. 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 ||
+         !peer_certificate_digest_algorithm_.empty());
+  LOG(LS_INFO) << "BeginSSL: "
+               << (!ssl_server_name_.empty() ? ssl_server_name_ :
+                                               "with peer");
+
+  if (role_ == SSL_CLIENT) {
+    LOG(LS_INFO) << "BeginSSL: as client";
+
+    rv = SSL_GetClientAuthDataHook(ssl_fd_, GetClientAuthDataHook,
+                                   this);
+    if (rv != SECSuccess) {
+      Error("BeginSSL", -1, false);
+      return -1;
+    }
+  } else {
+    LOG(LS_INFO) << "BeginSSL: as server";
+    NSSIdentity *identity;
+
+    if (identity_.get()) {
+      identity = static_cast<NSSIdentity *>(identity_.get());
+    } else {
+      LOG(LS_ERROR) << "Can't be an SSL server without an identity";
+      Error("BeginSSL", -1, false);
+      return -1;
+    }
+    rv = SSL_ConfigSecureServer(ssl_fd_, identity->certificate().certificate(),
+                                identity->keypair()->privkey(),
+                                kt_rsa);
+    if (rv != SECSuccess) {
+      Error("BeginSSL", -1, false);
+      return -1;
+    }
+
+    // Insist on a certificate from the client
+    rv = SSL_OptionSet(ssl_fd_, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+    if (rv != SECSuccess) {
+      Error("BeginSSL", -1, false);
+      return -1;
+    }
+
+    rv = SSL_OptionSet(ssl_fd_, SSL_REQUIRE_CERTIFICATE, PR_TRUE);
+    if (rv != SECSuccess) {
+      Error("BeginSSL", -1, false);
+      return -1;
+    }
+  }
+
+  // Set the version range.
+  SSLVersionRange vrange;
+  vrange.min =  (ssl_mode_ == SSL_MODE_DTLS) ?
+      SSL_LIBRARY_VERSION_TLS_1_1 :
+      SSL_LIBRARY_VERSION_TLS_1_0;
+  vrange.max = SSL_LIBRARY_VERSION_TLS_1_1;
+  
+  rv = SSL_VersionRangeSet(ssl_fd_, &vrange);
+  if (rv != SECSuccess) {
+    Error("BeginSSL", -1, false);
+    return -1;
+  }
+
+  // SRTP
+#ifdef HAVE_DTLS_SRTP
+  if (!srtp_ciphers_.empty()) {
+    rv = SSL_SetSRTPCiphers(ssl_fd_, &srtp_ciphers_[0], srtp_ciphers_.size());
+    if (rv != SECSuccess) {
+      Error("BeginSSL", -1, false);
+      return -1;
+    }
+  }
+#endif
+
+  // Certificate validation
+  rv = SSL_AuthCertificateHook(ssl_fd_, AuthCertificateHook, this);
+  if (rv != SECSuccess) {
+    Error("BeginSSL", -1, false);
+    return -1;
+  }
+
+  // Now start the handshake
+  rv = SSL_ResetHandshake(ssl_fd_, role_ == SSL_SERVER ? PR_TRUE : PR_FALSE);
+  if (rv != SECSuccess) {
+    Error("BeginSSL", -1, false);
+    return -1;
+  }
+
+  return ContinueSSL();
+}
+
+int NSSStreamAdapter::ContinueSSL() {
+  LOG(LS_INFO) << "ContinueSSL";
+  ASSERT(state_ == SSL_CONNECTING);
+
+  // Clear the DTLS timer
+  Thread::Current()->Clear(this, MSG_DTLS_TIMEOUT);
+
+  SECStatus rv = SSL_ForceHandshake(ssl_fd_);
+
+  if (rv == SECSuccess) {
+    LOG(LS_INFO) << "Handshake complete";
+
+    ASSERT(cert_ok_);
+    if (!cert_ok_) {
+      Error("ContinueSSL", -1, true);
+      return -1;
+    }
+
+    state_ = SSL_CONNECTED;
+    StreamAdapterInterface::OnEvent(stream(), SE_OPEN|SE_READ|SE_WRITE, 0);
+    return 0;
+  }
+
+  PRInt32 err = PR_GetError();
+  switch (err) {
+    case SSL_ERROR_RX_MALFORMED_HANDSHAKE:
+      if (ssl_mode_ != SSL_MODE_DTLS) {
+        Error("ContinueSSL", -1, true);
+        return -1;
+      } else {
+        LOG(LS_INFO) << "Malformed DTLS message. Ignoring.";
+        // Fall through
+      }
+    case PR_WOULD_BLOCK_ERROR:
+      LOG(LS_INFO) << "Would have blocked";
+      if (ssl_mode_ == SSL_MODE_DTLS) {
+        PRIntervalTime timeout;
+
+        SECStatus rv = DTLS_GetHandshakeTimeout(ssl_fd_, &timeout);
+        if (rv == SECSuccess) {
+          LOG(LS_INFO) << "Timeout is " << timeout << " ms";
+          Thread::Current()->PostDelayed(PR_IntervalToMilliseconds(timeout),
+                                         this, MSG_DTLS_TIMEOUT, 0);
+        }
+      }
+
+      return 0;
+    default:
+      LOG(LS_INFO) << "Error " << err;
+      break;
+  }
+
+  Error("ContinueSSL", -1, true);
+  return -1;
+}
+
+void NSSStreamAdapter::Cleanup() {
+  if (state_ != SSL_ERROR) {
+    state_ = SSL_CLOSED;
+  }
+
+  if (ssl_fd_) {
+    PR_Close(ssl_fd_);
+    ssl_fd_ = NULL;
+  }
+
+  identity_.reset();
+  peer_certificate_.reset();
+
+  Thread::Current()->Clear(this, MSG_DTLS_TIMEOUT);
+}
+
+StreamResult NSSStreamAdapter::Read(void* data, size_t data_len,
+                                    size_t* read, int* error) {
+  // SSL_CONNECTED sanity check.
+  switch (state_) {
+    case SSL_NONE:
+    case SSL_WAIT:
+    case SSL_CONNECTING:
+      return SR_BLOCK;
+
+    case SSL_CONNECTED:
+      break;
+
+    case SSL_CLOSED:
+      return SR_EOS;
+
+    case SSL_ERROR:
+    default:
+      if (error)
+        *error = ssl_error_code_;
+      return SR_ERROR;
+  }
+
+  PRInt32 rv = PR_Read(ssl_fd_, data, data_len);
+
+  if (rv == 0) {
+    return SR_EOS;
+  }
+
+  // Error
+  if (rv < 0) {
+    PRInt32 err = PR_GetError();
+
+    switch (err) {
+      case PR_WOULD_BLOCK_ERROR:
+        return SR_BLOCK;
+      default:
+        Error("Read", -1, false);
+        *error = err;  // libjingle semantics are that this is impl-specific
+        return SR_ERROR;
+    }
+  }
+
+  // Success
+  *read = rv;
+
+  return SR_SUCCESS;
+}
+
+StreamResult NSSStreamAdapter::Write(const void* data, size_t data_len,
+                                     size_t* written, int* error) {
+  // SSL_CONNECTED sanity check.
+  switch (state_) {
+    case SSL_NONE:
+    case SSL_WAIT:
+    case SSL_CONNECTING:
+      return SR_BLOCK;
+
+    case SSL_CONNECTED:
+      break;
+      
+    case SSL_ERROR:
+    case SSL_CLOSED:
+    default:
+      if (error)
+        *error = ssl_error_code_;
+      return SR_ERROR;
+  }
+
+  PRInt32 rv = PR_Write(ssl_fd_, data, data_len);
+
+  // Error
+  if (rv < 0) {
+    PRInt32 err = PR_GetError();
+
+    switch (err) {
+      case PR_WOULD_BLOCK_ERROR:
+        return SR_BLOCK;
+      default:
+        Error("Write", -1, false);
+        *error = err;  // libjingle semantics are that this is impl-specific
+        return SR_ERROR;
+    }
+  }
+
+  // Success
+  *written = rv;
+
+  return SR_SUCCESS;
+}
+
+void NSSStreamAdapter::OnEvent(StreamInterface* stream, int events,
+                               int err) {
+  int events_to_signal = 0;
+  int signal_error = 0;
+  ASSERT(stream == this->stream());
+  if ((events & SE_OPEN)) {
+    LOG(LS_INFO) << "NSSStreamAdapter::OnEvent SE_OPEN";
+    if (state_ != SSL_WAIT) {
+      ASSERT(state_ == SSL_NONE);
+      events_to_signal |= SE_OPEN;
+    } else {
+      state_ = SSL_CONNECTING;
+      if (int err = BeginSSL()) {
+        Error("BeginSSL", err, true);
+        return;
+      }
+    }
+  }
+  if ((events & (SE_READ|SE_WRITE))) {
+    LOG(LS_INFO) << "NSSStreamAdapter::OnEvent"
+                 << ((events & SE_READ) ? " SE_READ" : "")
+                 << ((events & SE_WRITE) ? " SE_WRITE" : "");
+    if (state_ == SSL_NONE) {
+      events_to_signal |= events & (SE_READ|SE_WRITE);
+    } else if (state_ == SSL_CONNECTING) {
+      if (int err = ContinueSSL()) {
+        Error("ContinueSSL", err, true);
+        return;
+      }
+    } else if (state_ == SSL_CONNECTED) {
+      if (events & SE_WRITE) {
+        LOG(LS_INFO) << " -- onStreamWriteable";
+        events_to_signal |= SE_WRITE;
+      }
+      if (events & SE_READ) {
+        LOG(LS_INFO) << " -- onStreamReadable";
+        events_to_signal |= SE_READ;
+      }
+    }
+  }
+  if ((events & SE_CLOSE)) {
+    LOG(LS_INFO) << "NSSStreamAdapter::OnEvent(SE_CLOSE, " << err << ")";
+    Cleanup();
+    events_to_signal |= SE_CLOSE;
+    // SE_CLOSE is the only event that uses the final parameter to OnEvent().
+    ASSERT(signal_error == 0);
+    signal_error = err;
+  }
+  if (events_to_signal)
+    StreamAdapterInterface::OnEvent(stream, events_to_signal, signal_error);
+}
+
+void NSSStreamAdapter::OnMessage(Message* msg) {
+  // Process our own messages and then pass others to the superclass
+  if (MSG_DTLS_TIMEOUT == msg->message_id) {
+    LOG(LS_INFO) << "DTLS timeout expired";
+    ContinueSSL();
+  } else {
+    StreamInterface::OnMessage(msg);
+  }
+}
+
+// Certificate verification callback. Called to check any certificate
+SECStatus NSSStreamAdapter::AuthCertificateHook(void *arg,
+                                                PRFileDesc *fd,
+                                                PRBool checksig,
+                                                PRBool isServer) {
+  LOG(LS_INFO) << "NSSStreamAdapter::AuthCertificateHook";
+  NSSCertificate peer_cert(SSL_PeerCertificate(fd));
+  bool ok = false;
+
+  // TODO(ekr@rtfm.com): Should we be enforcing self-signed like
+  // the OpenSSL version?
+  NSSStreamAdapter *stream = reinterpret_cast<NSSStreamAdapter *>(arg);
+
+  if (stream->peer_certificate_.get()) {
+    LOG(LS_INFO) << "Checking against specified certificate";
+
+    // The peer certificate was specified
+    if (reinterpret_cast<NSSCertificate *>(stream->peer_certificate_.get())->
+        Equals(&peer_cert)) {
+      LOG(LS_INFO) << "Accepted peer certificate";
+      ok = true;
+    }
+  } else if (!stream->peer_certificate_digest_algorithm_.empty()) {
+    LOG(LS_INFO) << "Checking against specified digest";
+    // The peer certificate digest was specified
+    unsigned char digest[64];  // Maximum size
+    std::size_t digest_length;
+
+    if (!peer_cert.ComputeDigest(
+            stream->peer_certificate_digest_algorithm_,
+            digest, sizeof(digest), &digest_length)) {
+      LOG(LS_ERROR) << "Digest computation failed";
+    } else {
+      Buffer computed_digest(digest, digest_length);
+      if (computed_digest == stream->peer_certificate_digest_value_) {
+        LOG(LS_INFO) << "Accepted peer certificate";
+        ok = true;
+      }
+    }
+  } else {
+    // Other modes, but we haven't implemented yet
+    // TODO(ekr@rtfm.com): Implement real certificate validation
+    UNIMPLEMENTED;
+  }
+
+  if (ok) {
+    stream->cert_ok_ = true;
+    return SECSuccess;
+  }
+
+  if (!ok && stream->ignore_bad_cert()) {
+    LOG(LS_WARNING) << "Ignoring cert error while verifying cert chain";
+    stream->cert_ok_ = true;
+    return SECSuccess;
+  }
+
+  PORT_SetError(SEC_ERROR_UNTRUSTED_CERT);
+  return SECFailure;
+}
+
+
+SECStatus NSSStreamAdapter::GetClientAuthDataHook(void *arg, PRFileDesc *fd,
+                                                  CERTDistNames *caNames,
+                                                  CERTCertificate **pRetCert,
+                                                  SECKEYPrivateKey **pRetKey) {
+  LOG(LS_INFO) << "Client cert requested";
+  NSSStreamAdapter *stream = reinterpret_cast<NSSStreamAdapter *>(arg);
+
+  if (!stream->identity_.get()) {
+    LOG(LS_ERROR) << "No identity available";
+    return SECFailure;
+  }
+
+  NSSIdentity *identity = static_cast<NSSIdentity *>(stream->identity_.get());
+  // Destroyed internally by NSS
+  *pRetCert = CERT_DupCertificate(identity->certificate().certificate());
+  *pRetKey = SECKEY_CopyPrivateKey(identity->keypair()->privkey());
+
+  return SECSuccess;
+}
+
+// RFC 5705 Key Exporter
+bool NSSStreamAdapter::ExportKeyingMaterial(const std::string& label,
+                                            const uint8* context,
+                                            size_t context_len,
+                                            bool use_context,
+                                            uint8* result,
+                                            size_t result_len) {
+  SECStatus rv = SSL_ExportKeyingMaterial(ssl_fd_,
+                                          label.c_str(), label.size(),
+                                          use_context,
+                                          context, context_len,
+                                          result, result_len);
+
+  return rv == SECSuccess;
+}
+
+bool NSSStreamAdapter::SetDtlsSrtpCiphers(
+    const std::vector<std::string>& ciphers) {
+#ifdef HAVE_DTLS_SRTP
+  std::vector<PRUint16> internal_ciphers;
+  if (state_ != SSL_NONE)
+    return false;
+
+  for (std::vector<std::string>::const_iterator cipher = ciphers.begin();
+       cipher != ciphers.end(); ++cipher) {
+    bool found = false;
+    for (const SrtpCipherMapEntry *entry = kSrtpCipherMap; entry->cipher_id;
+         ++entry) {
+      if (*cipher == entry->external_name) {
+        found = true;
+        internal_ciphers.push_back(entry->cipher_id);
+        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 NSSStreamAdapter::GetDtlsSrtpCipher(std::string* cipher) {
+#ifdef HAVE_DTLS_SRTP
+  ASSERT(state_ == SSL_CONNECTED);
+  if (state_ != SSL_CONNECTED)
+    return false;
+
+  PRUint16 selected_cipher;
+
+  SECStatus rv = SSL_GetSRTPCipher(ssl_fd_, &selected_cipher);
+  if (rv == SECFailure)
+    return false;
+
+  for (const SrtpCipherMapEntry *entry = kSrtpCipherMap;
+       entry->cipher_id; ++entry) {
+    if (selected_cipher == entry->cipher_id) {
+      *cipher = entry->external_name;
+      return true;
+    }
+  }
+
+  ASSERT(false);  // This should never happen
+#endif
+  return false;
+}
+
+
+bool NSSContext::initialized;
+NSSContext *NSSContext::global_nss_context;
+
+// Static initialization and shutdown
+NSSContext *NSSContext::Instance() {
+  if (!global_nss_context) {
+    NSSContext *new_ctx = new NSSContext();
+
+    if (!(new_ctx->slot_ = PK11_GetInternalSlot())) {
+      delete new_ctx;
+      goto fail;
+    }
+
+    global_nss_context = new_ctx;
+  }
+
+ fail:
+  return global_nss_context;
+}
+
+
+
+bool NSSContext::InitializeSSL(VerificationCallback callback) {
+  ASSERT(!callback);
+
+  if (!initialized) {
+    SECStatus rv;
+
+    rv = NSS_NoDB_Init(NULL);
+    if (rv != SECSuccess) {
+      LOG(LS_ERROR) << "Couldn't initialize NSS error=" <<
+          PORT_GetError();
+      return false;
+    }
+
+    NSS_SetDomesticPolicy();
+
+    initialized = true;
+  }
+
+  return true;
+}
+
+bool NSSContext::InitializeSSLThread() {
+  // Not needed
+  return true;
+}
+
+bool NSSContext::CleanupSSL() {
+  // Not needed
+  return true;
+}
+
+bool NSSStreamAdapter::HaveDtls() {
+  return true;
+}
+
+bool NSSStreamAdapter::HaveDtlsSrtp() {
+#ifdef HAVE_DTLS_SRTP
+  return true;
+#else
+  return false;
+#endif
+}
+
+bool NSSStreamAdapter::HaveExporter() {
+  return true;
+}
+
+}  // namespace talk_base
+
+#endif  // HAVE_NSS_SSL_H
diff --git a/talk/base/nssstreamadapter.h b/talk/base/nssstreamadapter.h
new file mode 100644
index 0000000..219f619
--- /dev/null
+++ b/talk/base/nssstreamadapter.h
@@ -0,0 +1,130 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc. 
+ * 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.
+ */
+
+#ifndef TALK_BASE_NSSSTREAMADAPTER_H_
+#define TALK_BASE_NSSSTREAMADAPTER_H_
+
+#include <string>
+#include <vector>
+
+#include "nspr.h"
+#include "nss.h"
+#include "secmodt.h"
+
+#include "talk/base/buffer.h"
+#include "talk/base/nssidentity.h"
+#include "talk/base/ssladapter.h"
+#include "talk/base/sslstreamadapter.h"
+#include "talk/base/sslstreamadapterhelper.h"
+
+namespace talk_base {
+
+// Singleton
+class NSSContext {
+ public:
+  NSSContext() {}
+  ~NSSContext() {
+  }
+
+  static PK11SlotInfo *GetSlot() {
+    return Instance() ? Instance()->slot_: NULL;
+  }
+
+  static NSSContext *Instance();
+  static bool InitializeSSL(VerificationCallback callback);
+  static bool InitializeSSLThread();
+  static bool CleanupSSL();
+
+ private:
+  PK11SlotInfo *slot_;                    // The PKCS-11 slot
+  static bool initialized;                // Was this initialized?
+  static NSSContext *global_nss_context;  // The global context
+};
+
+
+class NSSStreamAdapter : public SSLStreamAdapterHelper {
+ public:
+  explicit NSSStreamAdapter(StreamInterface* stream);
+  virtual ~NSSStreamAdapter();
+  bool Init();
+
+  virtual StreamResult Read(void* data, size_t data_len,
+                            size_t* read, int* error);
+  virtual StreamResult Write(const void* data, size_t data_len,
+                             size_t* written, int* error);
+  void OnMessage(Message *msg);
+
+  // 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:
+  // Override SSLStreamAdapter
+  virtual void OnEvent(StreamInterface* stream, int events, int err);
+
+  // Override SSLStreamAdapterHelper
+  virtual int BeginSSL();
+  virtual void Cleanup();
+  virtual bool GetDigestLength(const std::string &algorithm,
+                               std::size_t *length) {
+    return NSSCertificate::GetDigestLength(algorithm, length);
+  }
+
+ private:
+  int ContinueSSL();
+  static SECStatus AuthCertificateHook(void *arg, PRFileDesc *fd,
+                                       PRBool checksig, PRBool isServer);
+  static SECStatus GetClientAuthDataHook(void *arg, PRFileDesc *fd,
+                                         CERTDistNames *caNames,
+                                         CERTCertificate **pRetCert,
+                                         SECKEYPrivateKey **pRetKey);
+
+  PRFileDesc *ssl_fd_;              // NSS's SSL file descriptor
+  static bool initialized;          // Was InitializeSSL() called?
+  bool cert_ok_;                    // Did we get and check a cert
+  std::vector<PRUint16> srtp_ciphers_;  // SRTP cipher list
+
+  static PRDescIdentity nspr_layer_identity;  // The NSPR layer identity
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_NSSSTREAMADAPTER_H_
diff --git a/talk/base/nullsocketserver.h b/talk/base/nullsocketserver.h
new file mode 100644
index 0000000..6b3b288
--- /dev/null
+++ b/talk/base/nullsocketserver.h
@@ -0,0 +1,78 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_NULLSOCKETSERVER_H_
+#define TALK_BASE_NULLSOCKETSERVER_H_
+
+#include "talk/base/event.h"
+#include "talk/base/physicalsocketserver.h"
+
+namespace talk_base {
+
+// NullSocketServer
+
+class NullSocketServer : public talk_base::SocketServer {
+ public:
+  NullSocketServer() : event_(false, false) {}
+
+  virtual bool Wait(int cms, bool process_io) {
+    event_.Wait(cms);
+    return true;
+  }
+
+  virtual void WakeUp() {
+    event_.Set();
+  }
+
+  virtual talk_base::Socket* CreateSocket(int type) {
+    ASSERT(false);
+    return NULL;
+  }
+
+  virtual talk_base::Socket* CreateSocket(int family, int type) {
+    ASSERT(false);
+    return NULL;
+  }
+
+  virtual talk_base::AsyncSocket* CreateAsyncSocket(int type) {
+    ASSERT(false);
+    return NULL;
+  }
+
+  virtual talk_base::AsyncSocket* CreateAsyncSocket(int family, int type) {
+    ASSERT(false);
+    return NULL;
+  }
+
+
+ private:
+  talk_base::Event event_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_NULLSOCKETSERVER_H_
diff --git a/talk/base/nullsocketserver_unittest.cc b/talk/base/nullsocketserver_unittest.cc
new file mode 100644
index 0000000..18cde2d
--- /dev/null
+++ b/talk/base/nullsocketserver_unittest.cc
@@ -0,0 +1,64 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/nullsocketserver.h"
+
+namespace talk_base {
+
+static const uint32 kTimeout = 5000U;
+
+class NullSocketServerTest
+    : public testing::Test,
+      public MessageHandler {
+ public:
+  NullSocketServerTest() {}
+ protected:
+  virtual void OnMessage(Message* message) {
+    ss_.WakeUp();
+  }
+  NullSocketServer ss_;
+};
+
+TEST_F(NullSocketServerTest, WaitAndSet) {
+  Thread thread;
+  EXPECT_TRUE(thread.Start());
+  thread.Post(this, 0);
+  // The process_io will be ignored.
+  const bool process_io = true;
+  EXPECT_TRUE_WAIT(ss_.Wait(talk_base::kForever, process_io), kTimeout);
+}
+
+TEST_F(NullSocketServerTest, TestWait) {
+  uint32 start = Time();
+  ss_.Wait(200, true);
+  // The actual wait time is dependent on the resolution of the timer used by
+  // the Event class. Allow for the event to signal ~20ms early.
+  EXPECT_GE(TimeSince(start), 180);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/openssladapter.cc b/talk/base/openssladapter.cc
new file mode 100644
index 0000000..50391e5
--- /dev/null
+++ b/talk/base/openssladapter.cc
@@ -0,0 +1,908 @@
+/*
+ * 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.
+ */
+
+#if HAVE_OPENSSL_SSL_H
+
+#include "talk/base/openssladapter.h"
+
+#if defined(POSIX)
+#include <unistd.h>
+#endif
+
+// Must be included first before openssl headers.
+#include "talk/base/win32.h"  // NOLINT
+
+#include <openssl/bio.h>
+#include <openssl/crypto.h>
+#include <openssl/err.h>
+#include <openssl/opensslv.h>
+#include <openssl/rand.h>
+#include <openssl/ssl.h>
+#include <openssl/x509v3.h>
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif  // HAVE_CONFIG_H
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/sslroots.h"
+#include "talk/base/stringutils.h"
+
+// TODO: Use a nicer abstraction for mutex.
+
+#if defined(WIN32)
+  #define MUTEX_TYPE HANDLE
+  #define MUTEX_SETUP(x) (x) = CreateMutex(NULL, FALSE, NULL)
+  #define MUTEX_CLEANUP(x) CloseHandle(x)
+  #define MUTEX_LOCK(x) WaitForSingleObject((x), INFINITE)
+  #define MUTEX_UNLOCK(x) ReleaseMutex(x)
+  #define THREAD_ID GetCurrentThreadId()
+#elif defined(_POSIX_THREADS)
+  // _POSIX_THREADS is normally defined in unistd.h if pthreads are available
+  // on your platform.
+  #define MUTEX_TYPE pthread_mutex_t
+  #define MUTEX_SETUP(x) pthread_mutex_init(&(x), NULL)
+  #define MUTEX_CLEANUP(x) pthread_mutex_destroy(&(x))
+  #define MUTEX_LOCK(x) pthread_mutex_lock(&(x))
+  #define MUTEX_UNLOCK(x) pthread_mutex_unlock(&(x))
+  #define THREAD_ID pthread_self()
+#else
+  #error You must define mutex operations appropriate for your platform!
+#endif
+
+struct CRYPTO_dynlock_value {
+  MUTEX_TYPE mutex;
+};
+
+//////////////////////////////////////////////////////////////////////
+// SocketBIO
+//////////////////////////////////////////////////////////////////////
+
+static int socket_write(BIO* h, const char* buf, int num);
+static int socket_read(BIO* h, char* buf, int size);
+static int socket_puts(BIO* h, const char* str);
+static long socket_ctrl(BIO* h, int cmd, long arg1, void* arg2);
+static int socket_new(BIO* h);
+static int socket_free(BIO* data);
+
+static BIO_METHOD methods_socket = {
+  BIO_TYPE_BIO,
+  "socket",
+  socket_write,
+  socket_read,
+  socket_puts,
+  0,
+  socket_ctrl,
+  socket_new,
+  socket_free,
+  NULL,
+};
+
+BIO_METHOD* BIO_s_socket2() { return(&methods_socket); }
+
+BIO* BIO_new_socket(talk_base::AsyncSocket* socket) {
+  BIO* ret = BIO_new(BIO_s_socket2());
+  if (ret == NULL) {
+          return NULL;
+  }
+  ret->ptr = socket;
+  return ret;
+}
+
+static int socket_new(BIO* b) {
+  b->shutdown = 0;
+  b->init = 1;
+  b->num = 0; // 1 means socket closed
+  b->ptr = 0;
+  return 1;
+}
+
+static int socket_free(BIO* b) {
+  if (b == NULL)
+    return 0;
+  return 1;
+}
+
+static int socket_read(BIO* b, char* out, int outl) {
+  if (!out)
+    return -1;
+  talk_base::AsyncSocket* socket = static_cast<talk_base::AsyncSocket*>(b->ptr);
+  BIO_clear_retry_flags(b);
+  int result = socket->Recv(out, outl);
+  if (result > 0) {
+    return result;
+  } else if (result == 0) {
+    b->num = 1;
+  } else if (socket->IsBlocking()) {
+    BIO_set_retry_read(b);
+  }
+  return -1;
+}
+
+static int socket_write(BIO* b, const char* in, int inl) {
+  if (!in)
+    return -1;
+  talk_base::AsyncSocket* socket = static_cast<talk_base::AsyncSocket*>(b->ptr);
+  BIO_clear_retry_flags(b);
+  int result = socket->Send(in, inl);
+  if (result > 0) {
+    return result;
+  } else if (socket->IsBlocking()) {
+    BIO_set_retry_write(b);
+  }
+  return -1;
+}
+
+static int socket_puts(BIO* b, const char* str) {
+  return socket_write(b, str, strlen(str));
+}
+
+static long socket_ctrl(BIO* b, int cmd, long num, void* ptr) {
+  UNUSED(num);
+  UNUSED(ptr);
+
+  switch (cmd) {
+  case BIO_CTRL_RESET:
+    return 0;
+  case BIO_CTRL_EOF:
+    return b->num;
+  case BIO_CTRL_WPENDING:
+  case BIO_CTRL_PENDING:
+    return 0;
+  case BIO_CTRL_FLUSH:
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// OpenSSLAdapter
+/////////////////////////////////////////////////////////////////////////////
+
+namespace talk_base {
+
+// This array will store all of the mutexes available to OpenSSL.
+static MUTEX_TYPE* mutex_buf = NULL;
+
+static void locking_function(int mode, int n, const char * file, int line) {
+  if (mode & CRYPTO_LOCK) {
+    MUTEX_LOCK(mutex_buf[n]);
+  } else {
+    MUTEX_UNLOCK(mutex_buf[n]);
+  }
+}
+
+static unsigned long id_function() {  // NOLINT
+  // Use old-style C cast because THREAD_ID's type varies with the platform,
+  // in some cases requiring static_cast, and in others requiring
+  // reinterpret_cast.
+  return (unsigned long)THREAD_ID; // NOLINT
+}
+
+static CRYPTO_dynlock_value* dyn_create_function(const char* file, int line) {
+  CRYPTO_dynlock_value* value = new CRYPTO_dynlock_value;
+  if (!value)
+    return NULL;
+  MUTEX_SETUP(value->mutex);
+  return value;
+}
+
+static void dyn_lock_function(int mode, CRYPTO_dynlock_value* l,
+                              const char* file, int line) {
+  if (mode & CRYPTO_LOCK) {
+    MUTEX_LOCK(l->mutex);
+  } else {
+    MUTEX_UNLOCK(l->mutex);
+  }
+}
+
+static void dyn_destroy_function(CRYPTO_dynlock_value* l,
+                                 const char* file, int line) {
+  MUTEX_CLEANUP(l->mutex);
+  delete l;
+}
+
+VerificationCallback OpenSSLAdapter::custom_verify_callback_ = NULL;
+
+bool OpenSSLAdapter::InitializeSSL(VerificationCallback callback) {
+  if (!InitializeSSLThread() || !SSL_library_init())
+      return false;
+  SSL_load_error_strings();
+  ERR_load_BIO_strings();
+  OpenSSL_add_all_algorithms();
+  RAND_poll();
+  custom_verify_callback_ = callback;
+  return true;
+}
+
+bool OpenSSLAdapter::InitializeSSLThread() {
+  mutex_buf = new MUTEX_TYPE[CRYPTO_num_locks()];
+  if (!mutex_buf)
+    return false;
+  for (int i = 0; i < CRYPTO_num_locks(); ++i)
+    MUTEX_SETUP(mutex_buf[i]);
+
+  // we need to cast our id_function to return an unsigned long -- pthread_t is
+  // a pointer
+  CRYPTO_set_id_callback(id_function);
+  CRYPTO_set_locking_callback(locking_function);
+  CRYPTO_set_dynlock_create_callback(dyn_create_function);
+  CRYPTO_set_dynlock_lock_callback(dyn_lock_function);
+  CRYPTO_set_dynlock_destroy_callback(dyn_destroy_function);
+  return true;
+}
+
+bool OpenSSLAdapter::CleanupSSL() {
+  if (!mutex_buf)
+    return false;
+  CRYPTO_set_id_callback(NULL);
+  CRYPTO_set_locking_callback(NULL);
+  CRYPTO_set_dynlock_create_callback(NULL);
+  CRYPTO_set_dynlock_lock_callback(NULL);
+  CRYPTO_set_dynlock_destroy_callback(NULL);
+  for (int i = 0; i < CRYPTO_num_locks(); ++i)
+    MUTEX_CLEANUP(mutex_buf[i]);
+  delete [] mutex_buf;
+  mutex_buf = NULL;
+  return true;
+}
+
+OpenSSLAdapter::OpenSSLAdapter(AsyncSocket* socket)
+  : SSLAdapter(socket),
+    state_(SSL_NONE),
+    ssl_read_needs_write_(false),
+    ssl_write_needs_read_(false),
+    restartable_(false),
+    ssl_(NULL), ssl_ctx_(NULL),
+    custom_verification_succeeded_(false) {
+}
+
+OpenSSLAdapter::~OpenSSLAdapter() {
+  Cleanup();
+}
+
+int
+OpenSSLAdapter::StartSSL(const char* hostname, bool restartable) {
+  if (state_ != SSL_NONE)
+    return -1;
+
+  ssl_host_name_ = hostname;
+  restartable_ = restartable;
+
+  if (socket_->GetState() != Socket::CS_CONNECTED) {
+    state_ = SSL_WAIT;
+    return 0;
+  }
+
+  state_ = SSL_CONNECTING;
+  if (int err = BeginSSL()) {
+    Error("BeginSSL", err, false);
+    return err;
+  }
+
+  return 0;
+}
+
+int
+OpenSSLAdapter::BeginSSL() {
+  LOG(LS_INFO) << "BeginSSL: " << ssl_host_name_;
+  ASSERT(state_ == SSL_CONNECTING);
+
+  int err = 0;
+  BIO* bio = NULL;
+
+  // First set up the context
+  if (!ssl_ctx_)
+    ssl_ctx_ = SetupSSLContext();
+
+  if (!ssl_ctx_) {
+    err = -1;
+    goto ssl_error;
+  }
+
+  bio = BIO_new_socket(static_cast<AsyncSocketAdapter*>(socket_));
+  if (!bio) {
+    err = -1;
+    goto ssl_error;
+  }
+
+  ssl_ = SSL_new(ssl_ctx_);
+  if (!ssl_) {
+    err = -1;
+    goto ssl_error;
+  }
+
+  SSL_set_app_data(ssl_, this);
+
+  SSL_set_bio(ssl_, bio, bio);
+  SSL_set_mode(ssl_, SSL_MODE_ENABLE_PARTIAL_WRITE |
+                     SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+
+  // the SSL object owns the bio now
+  bio = NULL;
+
+  // Do the connect
+  err = ContinueSSL();
+  if (err != 0)
+    goto ssl_error;
+
+  return err;
+
+ssl_error:
+  Cleanup();
+  if (bio)
+    BIO_free(bio);
+
+  return err;
+}
+
+int
+OpenSSLAdapter::ContinueSSL() {
+  ASSERT(state_ == SSL_CONNECTING);
+
+  int code = SSL_connect(ssl_);
+  switch (SSL_get_error(ssl_, code)) {
+  case SSL_ERROR_NONE:
+    if (!SSLPostConnectionCheck(ssl_, ssl_host_name_.c_str())) {
+      LOG(LS_ERROR) << "TLS post connection check failed";
+      // make sure we close the socket
+      Cleanup();
+      // The connect failed so return -1 to shut down the socket
+      return -1;
+    }
+
+    state_ = SSL_CONNECTED;
+    AsyncSocketAdapter::OnConnectEvent(this);
+#if 0  // TODO: worry about this
+    // Don't let ourselves go away during the callbacks
+    PRefPtr<OpenSSLAdapter> lock(this);
+    LOG(LS_INFO) << " -- onStreamReadable";
+    AsyncSocketAdapter::OnReadEvent(this);
+    LOG(LS_INFO) << " -- onStreamWriteable";
+    AsyncSocketAdapter::OnWriteEvent(this);
+#endif
+    break;
+
+  case SSL_ERROR_WANT_READ:
+  case SSL_ERROR_WANT_WRITE:
+    break;
+
+  case SSL_ERROR_ZERO_RETURN:
+  default:
+    LOG(LS_WARNING) << "ContinueSSL -- error " << code;
+    return (code != 0) ? code : -1;
+  }
+
+  return 0;
+}
+
+void
+OpenSSLAdapter::Error(const char* context, int err, bool signal) {
+  LOG(LS_WARNING) << "OpenSSLAdapter::Error("
+                  << context << ", " << err << ")";
+  state_ = SSL_ERROR;
+  SetError(err);
+  if (signal)
+    AsyncSocketAdapter::OnCloseEvent(this, err);
+}
+
+void
+OpenSSLAdapter::Cleanup() {
+  LOG(LS_INFO) << "Cleanup";
+
+  state_ = SSL_NONE;
+  ssl_read_needs_write_ = false;
+  ssl_write_needs_read_ = false;
+  custom_verification_succeeded_ = false;
+
+  if (ssl_) {
+    SSL_free(ssl_);
+    ssl_ = NULL;
+  }
+
+  if (ssl_ctx_) {
+    SSL_CTX_free(ssl_ctx_);
+    ssl_ctx_ = NULL;
+  }
+}
+
+//
+// AsyncSocket Implementation
+//
+
+int
+OpenSSLAdapter::Send(const void* pv, size_t cb) {
+  //LOG(LS_INFO) << "OpenSSLAdapter::Send(" << cb << ")";
+
+  switch (state_) {
+  case SSL_NONE:
+    return AsyncSocketAdapter::Send(pv, cb);
+
+  case SSL_WAIT:
+  case SSL_CONNECTING:
+    SetError(EWOULDBLOCK);
+    return SOCKET_ERROR;
+
+  case SSL_CONNECTED:
+    break;
+
+  case SSL_ERROR:
+  default:
+    return SOCKET_ERROR;
+  }
+
+  // OpenSSL will return an error if we try to write zero bytes
+  if (cb == 0)
+    return 0;
+
+  ssl_write_needs_read_ = false;
+
+  int code = SSL_write(ssl_, pv, cb);
+  switch (SSL_get_error(ssl_, code)) {
+  case SSL_ERROR_NONE:
+    //LOG(LS_INFO) << " -- success";
+    return code;
+  case SSL_ERROR_WANT_READ:
+    //LOG(LS_INFO) << " -- error want read";
+    ssl_write_needs_read_ = true;
+    SetError(EWOULDBLOCK);
+    break;
+  case SSL_ERROR_WANT_WRITE:
+    //LOG(LS_INFO) << " -- error want write";
+    SetError(EWOULDBLOCK);
+    break;
+  case SSL_ERROR_ZERO_RETURN:
+    //LOG(LS_INFO) << " -- remote side closed";
+    SetError(EWOULDBLOCK);
+    // do we need to signal closure?
+    break;
+  default:
+    //LOG(LS_INFO) << " -- error " << code;
+    Error("SSL_write", (code ? code : -1), false);
+    break;
+  }
+
+  return SOCKET_ERROR;
+}
+
+int
+OpenSSLAdapter::Recv(void* pv, size_t cb) {
+  //LOG(LS_INFO) << "OpenSSLAdapter::Recv(" << cb << ")";
+  switch (state_) {
+
+  case SSL_NONE:
+    return AsyncSocketAdapter::Recv(pv, cb);
+
+  case SSL_WAIT:
+  case SSL_CONNECTING:
+    SetError(EWOULDBLOCK);
+    return SOCKET_ERROR;
+
+  case SSL_CONNECTED:
+    break;
+
+  case SSL_ERROR:
+  default:
+    return SOCKET_ERROR;
+  }
+
+  // Don't trust OpenSSL with zero byte reads
+  if (cb == 0)
+    return 0;
+
+  ssl_read_needs_write_ = false;
+
+  int code = SSL_read(ssl_, pv, cb);
+  switch (SSL_get_error(ssl_, code)) {
+  case SSL_ERROR_NONE:
+    //LOG(LS_INFO) << " -- success";
+    return code;
+  case SSL_ERROR_WANT_READ:
+    //LOG(LS_INFO) << " -- error want read";
+    SetError(EWOULDBLOCK);
+    break;
+  case SSL_ERROR_WANT_WRITE:
+    //LOG(LS_INFO) << " -- error want write";
+    ssl_read_needs_write_ = true;
+    SetError(EWOULDBLOCK);
+    break;
+  case SSL_ERROR_ZERO_RETURN:
+    //LOG(LS_INFO) << " -- remote side closed";
+    SetError(EWOULDBLOCK);
+    // do we need to signal closure?
+    break;
+  default:
+    //LOG(LS_INFO) << " -- error " << code;
+    Error("SSL_read", (code ? code : -1), false);
+    break;
+  }
+
+  return SOCKET_ERROR;
+}
+
+int
+OpenSSLAdapter::Close() {
+  Cleanup();
+  state_ = restartable_ ? SSL_WAIT : SSL_NONE;
+  return AsyncSocketAdapter::Close();
+}
+
+Socket::ConnState
+OpenSSLAdapter::GetState() const {
+  //if (signal_close_)
+  //  return CS_CONNECTED;
+  ConnState state = socket_->GetState();
+  if ((state == CS_CONNECTED)
+      && ((state_ == SSL_WAIT) || (state_ == SSL_CONNECTING)))
+    state = CS_CONNECTING;
+  return state;
+}
+
+void
+OpenSSLAdapter::OnConnectEvent(AsyncSocket* socket) {
+  LOG(LS_INFO) << "OpenSSLAdapter::OnConnectEvent";
+  if (state_ != SSL_WAIT) {
+    ASSERT(state_ == SSL_NONE);
+    AsyncSocketAdapter::OnConnectEvent(socket);
+    return;
+  }
+
+  state_ = SSL_CONNECTING;
+  if (int err = BeginSSL()) {
+    AsyncSocketAdapter::OnCloseEvent(socket, err);
+  }
+}
+
+void
+OpenSSLAdapter::OnReadEvent(AsyncSocket* socket) {
+  //LOG(LS_INFO) << "OpenSSLAdapter::OnReadEvent";
+
+  if (state_ == SSL_NONE) {
+    AsyncSocketAdapter::OnReadEvent(socket);
+    return;
+  }
+
+  if (state_ == SSL_CONNECTING) {
+    if (int err = ContinueSSL()) {
+      Error("ContinueSSL", err);
+    }
+    return;
+  }
+
+  if (state_ != SSL_CONNECTED)
+    return;
+
+  // Don't let ourselves go away during the callbacks
+  //PRefPtr<OpenSSLAdapter> lock(this); // TODO: fix this
+  if (ssl_write_needs_read_)  {
+    //LOG(LS_INFO) << " -- onStreamWriteable";
+    AsyncSocketAdapter::OnWriteEvent(socket);
+  }
+
+  //LOG(LS_INFO) << " -- onStreamReadable";
+  AsyncSocketAdapter::OnReadEvent(socket);
+}
+
+void
+OpenSSLAdapter::OnWriteEvent(AsyncSocket* socket) {
+  //LOG(LS_INFO) << "OpenSSLAdapter::OnWriteEvent";
+
+  if (state_ == SSL_NONE) {
+    AsyncSocketAdapter::OnWriteEvent(socket);
+    return;
+  }
+
+  if (state_ == SSL_CONNECTING) {
+    if (int err = ContinueSSL()) {
+      Error("ContinueSSL", err);
+    }
+    return;
+  }
+
+  if (state_ != SSL_CONNECTED)
+    return;
+
+  // Don't let ourselves go away during the callbacks
+  //PRefPtr<OpenSSLAdapter> lock(this); // TODO: fix this
+
+  if (ssl_read_needs_write_)  {
+    //LOG(LS_INFO) << " -- onStreamReadable";
+    AsyncSocketAdapter::OnReadEvent(socket);
+  }
+
+  //LOG(LS_INFO) << " -- onStreamWriteable";
+  AsyncSocketAdapter::OnWriteEvent(socket);
+}
+
+void
+OpenSSLAdapter::OnCloseEvent(AsyncSocket* socket, int err) {
+  LOG(LS_INFO) << "OpenSSLAdapter::OnCloseEvent(" << err << ")";
+  AsyncSocketAdapter::OnCloseEvent(socket, err);
+}
+
+// This code is taken from the "Network Security with OpenSSL"
+// sample in chapter 5
+
+bool OpenSSLAdapter::VerifyServerName(SSL* ssl, const char* host,
+                                      bool ignore_bad_cert) {
+  if (!host)
+    return false;
+
+  // Checking the return from SSL_get_peer_certificate here is not strictly
+  // necessary.  With our setup, it is not possible for it to return
+  // NULL.  However, it is good form to check the return.
+  X509* certificate = SSL_get_peer_certificate(ssl);
+  if (!certificate)
+    return false;
+
+  // Logging certificates is extremely verbose. So it is disabled by default.
+#ifdef LOG_CERTIFICATES
+  {
+    LOG(LS_INFO) << "Certificate from server:";
+    BIO* mem = BIO_new(BIO_s_mem());
+    X509_print_ex(mem, certificate, XN_FLAG_SEP_CPLUS_SPC, X509_FLAG_NO_HEADER);
+    BIO_write(mem, "\0", 1);
+    char* buffer;
+    BIO_get_mem_data(mem, &buffer);
+    LOG(LS_INFO) << buffer;
+    BIO_free(mem);
+
+    char* cipher_description =
+      SSL_CIPHER_description(SSL_get_current_cipher(ssl), NULL, 128);
+    LOG(LS_INFO) << "Cipher: " << cipher_description;
+    OPENSSL_free(cipher_description);
+  }
+#endif
+
+  bool ok = false;
+  int extension_count = X509_get_ext_count(certificate);
+  for (int i = 0; i < extension_count; ++i) {
+    X509_EXTENSION* extension = X509_get_ext(certificate, i);
+    int extension_nid = OBJ_obj2nid(X509_EXTENSION_get_object(extension));
+
+    if (extension_nid == NID_subject_alt_name) {
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+      const X509V3_EXT_METHOD* meth = X509V3_EXT_get(extension);
+#else
+      X509V3_EXT_METHOD* meth = X509V3_EXT_get(extension);
+#endif
+      if (!meth)
+        break;
+
+      void* ext_str = NULL;
+
+      // We assign this to a local variable, instead of passing the address
+      // directly to ASN1_item_d2i.
+      // See http://readlist.com/lists/openssl.org/openssl-users/0/4761.html.
+      unsigned char* ext_value_data = extension->value->data;
+
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+      const unsigned char **ext_value_data_ptr =
+          (const_cast<const unsigned char **>(&ext_value_data));
+#else
+      unsigned char **ext_value_data_ptr = &ext_value_data;
+#endif
+
+      if (meth->it) {
+        ext_str = ASN1_item_d2i(NULL, ext_value_data_ptr,
+                                extension->value->length,
+                                ASN1_ITEM_ptr(meth->it));
+      } else {
+        ext_str = meth->d2i(NULL, ext_value_data_ptr, extension->value->length);
+      }
+
+      STACK_OF(CONF_VALUE)* value = meth->i2v(meth, ext_str, NULL);
+      for (int j = 0; j < sk_CONF_VALUE_num(value); ++j) {
+        CONF_VALUE* nval = sk_CONF_VALUE_value(value, j);
+        // The value for nval can contain wildcards
+        if (!strcmp(nval->name, "DNS") && string_match(host, nval->value)) {
+          ok = true;
+          break;
+        }
+      }
+      sk_CONF_VALUE_pop_free(value, X509V3_conf_free);
+      value = NULL;
+
+      if (meth->it) {
+        ASN1_item_free(reinterpret_cast<ASN1_VALUE*>(ext_str),
+                       ASN1_ITEM_ptr(meth->it));
+      } else {
+        meth->ext_free(ext_str);
+      }
+      ext_str = NULL;
+    }
+    if (ok)
+      break;
+  }
+
+  char data[256];
+  X509_name_st* subject;
+  if (!ok
+      && ((subject = X509_get_subject_name(certificate)) != NULL)
+      && (X509_NAME_get_text_by_NID(subject, NID_commonName,
+                                    data, sizeof(data)) > 0)) {
+    data[sizeof(data)-1] = 0;
+    if (_stricmp(data, host) == 0)
+      ok = true;
+  }
+
+  X509_free(certificate);
+
+  // 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.";
+    ok = true;
+  }
+
+  return ok;
+}
+
+bool OpenSSLAdapter::SSLPostConnectionCheck(SSL* ssl, const char* host) {
+  bool ok = VerifyServerName(ssl, host, ignore_bad_cert());
+
+  if (ok) {
+    ok = (SSL_get_verify_result(ssl) == X509_V_OK ||
+          custom_verification_succeeded_);
+  }
+
+  if (!ok && ignore_bad_cert()) {
+    LOG(LS_INFO) << "Other TLS post connection checks failed.";
+    ok = true;
+  }
+
+  return ok;
+}
+
+#if _DEBUG
+
+// We only use this for tracing and so it is only needed in debug mode
+
+void
+OpenSSLAdapter::SSLInfoCallback(const SSL* s, int where, int ret) {
+  const char* str = "undefined";
+  int w = where & ~SSL_ST_MASK;
+  if (w & SSL_ST_CONNECT) {
+    str = "SSL_connect";
+  } else if (w & SSL_ST_ACCEPT) {
+    str = "SSL_accept";
+  }
+  if (where & SSL_CB_LOOP) {
+    LOG(LS_INFO) <<  str << ":" << SSL_state_string_long(s);
+  } else if (where & SSL_CB_ALERT) {
+    str = (where & SSL_CB_READ) ? "read" : "write";
+    LOG(LS_INFO) <<  "SSL3 alert " << str
+      << ":" << SSL_alert_type_string_long(ret)
+      << ":" << SSL_alert_desc_string_long(ret);
+  } else if (where & SSL_CB_EXIT) {
+    if (ret == 0) {
+      LOG(LS_INFO) << str << ":failed in " << SSL_state_string_long(s);
+    } else if (ret < 0) {
+      LOG(LS_INFO) << str << ":error in " << SSL_state_string_long(s);
+    }
+  }
+}
+
+#endif  // _DEBUG
+
+int
+OpenSSLAdapter::SSLVerifyCallback(int ok, X509_STORE_CTX* store) {
+#if _DEBUG
+  if (!ok) {
+    char data[256];
+    X509* cert = X509_STORE_CTX_get_current_cert(store);
+    int depth = X509_STORE_CTX_get_error_depth(store);
+    int err = X509_STORE_CTX_get_error(store);
+
+    LOG(LS_INFO) << "Error with certificate at depth: " << depth;
+    X509_NAME_oneline(X509_get_issuer_name(cert), data, sizeof(data));
+    LOG(LS_INFO) << "  issuer  = " << data;
+    X509_NAME_oneline(X509_get_subject_name(cert), data, sizeof(data));
+    LOG(LS_INFO) << "  subject = " << data;
+    LOG(LS_INFO) << "  err     = " << err
+      << ":" << X509_verify_cert_error_string(err);
+  }
+#endif
+
+  // Get our stream pointer from the store
+  SSL* ssl = reinterpret_cast<SSL*>(
+                X509_STORE_CTX_get_ex_data(store,
+                  SSL_get_ex_data_X509_STORE_CTX_idx()));
+
+  OpenSSLAdapter* stream =
+    reinterpret_cast<OpenSSLAdapter*>(SSL_get_app_data(ssl));
+
+  if (!ok && custom_verify_callback_) {
+    void* cert =
+        reinterpret_cast<void*>(X509_STORE_CTX_get_current_cert(store));
+    if (custom_verify_callback_(cert)) {
+      stream->custom_verification_succeeded_ = true;
+      LOG(LS_INFO) << "validated certificate using custom callback";
+      ok = true;
+    }
+  }
+
+  // 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;
+  }
+
+  return ok;
+}
+
+bool OpenSSLAdapter::ConfigureTrustedRootCertificates(SSL_CTX* ctx) {
+  // Add the root cert that we care about to the SSL context
+  int count_of_added_certs = 0;
+  for (int i = 0; i < ARRAY_SIZE(kSSLCertCertificateList); i++) {
+    const unsigned char* cert_buffer = kSSLCertCertificateList[i];
+    size_t cert_buffer_len = kSSLCertCertificateSizeList[i];
+    X509* cert = d2i_X509(NULL, &cert_buffer, cert_buffer_len);
+    if (cert) {
+      int return_value = X509_STORE_add_cert(SSL_CTX_get_cert_store(ctx), cert);
+      if (return_value == 0) {
+        LOG(LS_WARNING) << "Unable to add certificate.";
+      } else {
+        count_of_added_certs++;
+      }
+      X509_free(cert);
+    }
+  }
+  return count_of_added_certs > 0;
+}
+
+SSL_CTX*
+OpenSSLAdapter::SetupSSLContext() {
+  SSL_CTX* ctx = SSL_CTX_new(TLSv1_client_method());
+  if (ctx == NULL) {
+    unsigned long error = ERR_get_error();  // NOLINT: type used by OpenSSL.
+    LOG(LS_WARNING) << "SSL_CTX creation failed: "
+                    << '"' << ERR_reason_error_string(error) << "\" "
+                    << "(error=" << error << ')';
+    return NULL;
+  }
+  if (!ConfigureTrustedRootCertificates(ctx)) {
+    SSL_CTX_free(ctx);
+    return NULL;
+  }
+
+#ifdef _DEBUG
+  SSL_CTX_set_info_callback(ctx, SSLInfoCallback);
+#endif
+
+  SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, SSLVerifyCallback);
+  SSL_CTX_set_verify_depth(ctx, 4);
+  SSL_CTX_set_cipher_list(ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
+
+  return ctx;
+}
+
+} // namespace talk_base
+
+#endif  // HAVE_OPENSSL_SSL_H
diff --git a/talk/base/openssladapter.h b/talk/base/openssladapter.h
new file mode 100644
index 0000000..c89c292
--- /dev/null
+++ b/talk/base/openssladapter.h
@@ -0,0 +1,105 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_OPENSSLADAPTER_H__
+#define TALK_BASE_OPENSSLADAPTER_H__
+
+#include <string>
+#include "talk/base/ssladapter.h"
+
+typedef struct ssl_st SSL;
+typedef struct ssl_ctx_st SSL_CTX;
+typedef struct x509_store_ctx_st X509_STORE_CTX;
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+
+class OpenSSLAdapter : public SSLAdapter {
+public:
+  static bool InitializeSSL(VerificationCallback callback);
+  static bool InitializeSSLThread();
+  static bool CleanupSSL();
+
+  OpenSSLAdapter(AsyncSocket* socket);
+  virtual ~OpenSSLAdapter();
+
+  virtual int StartSSL(const char* hostname, bool restartable);
+  virtual int Send(const void* pv, size_t cb);
+  virtual int Recv(void* pv, size_t cb);
+  virtual int Close();
+
+  // Note that the socket returns ST_CONNECTING while SSL is being negotiated.
+  virtual ConnState GetState() const;
+
+protected:
+  virtual void OnConnectEvent(AsyncSocket* socket);
+  virtual void OnReadEvent(AsyncSocket* socket);
+  virtual void OnWriteEvent(AsyncSocket* socket);
+  virtual void OnCloseEvent(AsyncSocket* socket, int err);
+
+private:
+  enum SSLState {
+    SSL_NONE, SSL_WAIT, SSL_CONNECTING, SSL_CONNECTED, SSL_ERROR
+  };
+
+  int BeginSSL();
+  int ContinueSSL();
+  void Error(const char* context, int err, bool signal = true);
+  void Cleanup();
+
+  static bool VerifyServerName(SSL* ssl, const char* host,
+                               bool ignore_bad_cert);
+  bool SSLPostConnectionCheck(SSL* ssl, const char* host);
+#if _DEBUG
+  static void SSLInfoCallback(const SSL* s, int where, int ret);
+#endif  // !_DEBUG
+  static int SSLVerifyCallback(int ok, X509_STORE_CTX* store);
+  static VerificationCallback custom_verify_callback_;
+  friend class OpenSSLStreamAdapter;  // for custom_verify_callback_;
+
+  static bool ConfigureTrustedRootCertificates(SSL_CTX* ctx);
+  static SSL_CTX* SetupSSLContext();
+
+  SSLState state_;
+  bool ssl_read_needs_write_;
+  bool ssl_write_needs_read_;
+  // If true, socket will retain SSL configuration after Close.
+  bool restartable_;
+
+  SSL* ssl_;
+  SSL_CTX* ssl_ctx_;
+  std::string ssl_host_name_;
+
+  bool custom_verification_succeeded_;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_OPENSSLADAPTER_H__
diff --git a/talk/base/openssldigest.cc b/talk/base/openssldigest.cc
new file mode 100644
index 0000000..bb0e027
--- /dev/null
+++ b/talk/base/openssldigest.cc
@@ -0,0 +1,114 @@
+/*
+ * libjingle
+ * Copyright 2004--2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_OPENSSL_SSL_H
+
+#include "talk/base/openssldigest.h"
+
+#include "talk/base/common.h"
+
+namespace talk_base {
+
+OpenSSLDigest::OpenSSLDigest(const std::string& algorithm) {
+  EVP_MD_CTX_init(&ctx_);
+  if (GetDigestEVP(algorithm, &md_)) {
+    EVP_DigestInit_ex(&ctx_, md_, NULL);
+  } else {
+    md_ = NULL;
+  }
+}
+
+OpenSSLDigest::~OpenSSLDigest() {
+  EVP_MD_CTX_cleanup(&ctx_);
+}
+
+size_t OpenSSLDigest::Size() const {
+  if (!md_) {
+    return 0;
+  }
+  return EVP_MD_size(md_);
+}
+
+void OpenSSLDigest::Update(const void* buf, size_t len) {
+  if (!md_) {
+    return;
+  }
+  EVP_DigestUpdate(&ctx_, buf, len);
+}
+
+size_t OpenSSLDigest::Finish(void* buf, size_t len) {
+  if (!md_ || len < Size()) {
+    return 0;
+  }
+  unsigned int md_len;
+  EVP_DigestFinal_ex(&ctx_, static_cast<unsigned char*>(buf), &md_len);
+  EVP_DigestInit_ex(&ctx_, md_, NULL);  // prepare for future Update()s
+  ASSERT(md_len == Size());
+  return md_len;
+}
+
+bool OpenSSLDigest::GetDigestEVP(const std::string& algorithm,
+                                 const EVP_MD** mdp) {
+  const EVP_MD* md;
+  if (algorithm == DIGEST_MD5) {
+    md = EVP_md5();
+  } else if (algorithm == DIGEST_SHA_1) {
+    md = EVP_sha1();
+#if OPENSSL_VERSION_NUMBER >= 0x00908000L
+  } 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) >= 16);
+  *mdp = md;
+  return true;
+}
+
+bool OpenSSLDigest::GetDigestSize(const std::string& algorithm,
+                                  size_t* length) {
+  const EVP_MD *md;
+  if (!GetDigestEVP(algorithm, &md))
+    return false;
+
+  *length = EVP_MD_size(md);
+  return true;
+}
+
+}  // namespace talk_base
+
+#endif  // HAVE_OPENSSL_SSL_H
+
diff --git a/talk/base/openssldigest.h b/talk/base/openssldigest.h
new file mode 100644
index 0000000..0a12189
--- /dev/null
+++ b/talk/base/openssldigest.h
@@ -0,0 +1,64 @@
+/*
+ * libjingle
+ * Copyright 2004--2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_OPENSSLDIGEST_H_
+#define TALK_BASE_OPENSSLDIGEST_H_
+
+#include <openssl/evp.h>
+
+#include "talk/base/messagedigest.h"
+
+namespace talk_base {
+
+// An implementation of the digest class that uses OpenSSL.
+class OpenSSLDigest : public MessageDigest {
+ public:
+  // Creates an OpenSSLDigest with |algorithm| as the hash algorithm.
+  explicit OpenSSLDigest(const std::string& algorithm);
+  ~OpenSSLDigest();
+  // Returns the digest output size (e.g. 16 bytes for MD5).
+  virtual size_t Size() const;
+  // Updates the digest with |len| bytes from |buf|.
+  virtual void Update(const void* buf, size_t len);
+  // Outputs the digest value to |buf| with length |len|.
+  virtual size_t Finish(void* buf, size_t len);
+
+  // Helper function to look up a digest.
+  static bool GetDigestEVP(const std::string &algorithm,
+                           const EVP_MD** md);
+  // Helper function to get the length of a digest.
+  static bool GetDigestSize(const std::string &algorithm,
+                            size_t* len);
+
+ private:
+  EVP_MD_CTX ctx_;
+  const EVP_MD* md_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_OPENSSLDIGEST_H_
diff --git a/talk/base/opensslidentity.cc b/talk/base/opensslidentity.cc
new file mode 100644
index 0000000..a48c94f
--- /dev/null
+++ b/talk/base/opensslidentity.cc
@@ -0,0 +1,342 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_OPENSSL_SSL_H
+
+#include "talk/base/opensslidentity.h"
+
+// Must be included first before openssl headers.
+#include "talk/base/win32.h"  // NOLINT
+
+#include <openssl/ssl.h>
+#include <openssl/bio.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/bn.h>
+#include <openssl/rsa.h>
+#include <openssl/crypto.h>
+
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/openssldigest.h"
+
+namespace talk_base {
+
+// We could have exposed a myriad of parameters for the crypto stuff,
+// but keeping it simple seems best.
+
+// Strength of generated keys. Those are RSA.
+static const int KEY_LENGTH = 1024;
+
+// Random bits for certificate serial number
+static const int SERIAL_RAND_BITS = 64;
+
+// Certificate validity lifetime
+static const int CERTIFICATE_LIFETIME = 60*60*24*365;  // one year, arbitrarily
+// Certificate validity window.
+// This is to compensate for slightly incorrect system clocks.
+static const int CERTIFICATE_WINDOW = -60*60*24;
+
+// Generate a key pair. Caller is responsible for freeing the returned object.
+static EVP_PKEY* MakeKey() {
+  LOG(LS_INFO) << "Making key pair";
+  EVP_PKEY* pkey = EVP_PKEY_new();
+#if OPENSSL_VERSION_NUMBER < 0x00908000l
+  // Only RSA_generate_key is available. Use that.
+  RSA* rsa = RSA_generate_key(KEY_LENGTH, 0x10001, NULL, NULL);
+  if (!EVP_PKEY_assign_RSA(pkey, rsa)) {
+    EVP_PKEY_free(pkey);
+    RSA_free(rsa);
+    return NULL;
+  }
+#else
+  // RSA_generate_key is deprecated. Use _ex version.
+  BIGNUM* exponent = BN_new();
+  RSA* rsa = RSA_new();
+  if (!pkey || !exponent || !rsa ||
+      !BN_set_word(exponent, 0x10001) ||  // 65537 RSA exponent
+      !RSA_generate_key_ex(rsa, KEY_LENGTH, exponent, NULL) ||
+      !EVP_PKEY_assign_RSA(pkey, rsa)) {
+    EVP_PKEY_free(pkey);
+    BN_free(exponent);
+    RSA_free(rsa);
+    return NULL;
+  }
+  // ownership of rsa struct was assigned, don't free it.
+  BN_free(exponent);
+#endif
+  LOG(LS_INFO) << "Returning key pair";
+  return pkey;
+}
+
+// Generate a self-signed certificate, with the public key from the
+// given key pair. Caller is responsible for freeing the returned object.
+static X509* MakeCertificate(EVP_PKEY* pkey, const char* common_name) {
+  LOG(LS_INFO) << "Making certificate for " << common_name;
+  X509* x509 = NULL;
+  BIGNUM* serial_number = NULL;
+  X509_NAME* name = NULL;
+
+  if ((x509=X509_new()) == NULL)
+    goto error;
+
+  if (!X509_set_pubkey(x509, pkey))
+    goto error;
+
+  // serial number
+  // temporary reference to serial number inside x509 struct
+  ASN1_INTEGER* asn1_serial_number;
+  if ((serial_number = BN_new()) == NULL ||
+      !BN_pseudo_rand(serial_number, SERIAL_RAND_BITS, 0, 0) ||
+      (asn1_serial_number = X509_get_serialNumber(x509)) == NULL ||
+      !BN_to_ASN1_INTEGER(serial_number, asn1_serial_number))
+    goto error;
+
+  if (!X509_set_version(x509, 0L))  // version 1
+    goto error;
+
+  // There are a lot of possible components for the name entries. In
+  // our P2P SSL mode however, the certificates are pre-exchanged
+  // (through the secure XMPP channel), and so the certificate
+  // identification is arbitrary. It can't be empty, so we set some
+  // arbitrary common_name. Note that this certificate goes out in
+  // clear during SSL negotiation, so there may be a privacy issue in
+  // putting anything recognizable here.
+  if ((name = X509_NAME_new()) == NULL ||
+      !X509_NAME_add_entry_by_NID(name, NID_commonName, MBSTRING_UTF8,
+                                     (unsigned char*)common_name, -1, -1, 0) ||
+      !X509_set_subject_name(x509, name) ||
+      !X509_set_issuer_name(x509, name))
+    goto error;
+
+  if (!X509_gmtime_adj(X509_get_notBefore(x509), CERTIFICATE_WINDOW) ||
+      !X509_gmtime_adj(X509_get_notAfter(x509), CERTIFICATE_LIFETIME))
+    goto error;
+
+  if (!X509_sign(x509, pkey, EVP_sha1()))
+    goto error;
+
+  BN_free(serial_number);
+  X509_NAME_free(name);
+  LOG(LS_INFO) << "Returning certificate";
+  return x509;
+
+ error:
+  BN_free(serial_number);
+  X509_NAME_free(name);
+  X509_free(x509);
+  return NULL;
+}
+
+// This dumps the SSL error stack to the log.
+static void LogSSLErrors(const std::string& prefix) {
+  char error_buf[200];
+  unsigned long err;
+
+  while ((err = ERR_get_error()) != 0) {
+    ERR_error_string_n(err, error_buf, sizeof(error_buf));
+    LOG(LS_ERROR) << prefix << ": " << error_buf << "\n";
+  }
+}
+
+OpenSSLKeyPair* OpenSSLKeyPair::Generate() {
+  EVP_PKEY* pkey = MakeKey();
+  if (!pkey) {
+    LogSSLErrors("Generating key pair");
+    return NULL;
+  }
+  return new OpenSSLKeyPair(pkey);
+}
+
+OpenSSLKeyPair::~OpenSSLKeyPair() {
+  EVP_PKEY_free(pkey_);
+}
+
+void OpenSSLKeyPair::AddReference() {
+  CRYPTO_add(&pkey_->references, 1, CRYPTO_LOCK_EVP_PKEY);
+}
+
+#ifdef _DEBUG
+// Print a certificate to the log, for debugging.
+static void PrintCert(X509* x509) {
+  BIO* temp_memory_bio = BIO_new(BIO_s_mem());
+  if (!temp_memory_bio) {
+    LOG_F(LS_ERROR) << "Failed to allocate temporary memory bio";
+    return;
+  }
+  X509_print_ex(temp_memory_bio, x509, XN_FLAG_SEP_CPLUS_SPC, 0);
+  BIO_write(temp_memory_bio, "\0", 1);
+  char* buffer;
+  BIO_get_mem_data(temp_memory_bio, &buffer);
+  LOG(LS_VERBOSE) << buffer;
+  BIO_free(temp_memory_bio);
+}
+#endif
+
+OpenSSLCertificate* OpenSSLCertificate::Generate(
+    OpenSSLKeyPair* key_pair, const std::string& common_name) {
+  std::string actual_common_name = common_name;
+  if (actual_common_name.empty())
+    // Use a random string, arbitrarily 8chars long.
+    actual_common_name = CreateRandomString(8);
+  X509* x509 = MakeCertificate(key_pair->pkey(), actual_common_name.c_str());
+  if (!x509) {
+    LogSSLErrors("Generating certificate");
+    return NULL;
+  }
+#ifdef _DEBUG
+  PrintCert(x509);
+#endif
+  return new OpenSSLCertificate(x509);
+}
+
+OpenSSLCertificate* OpenSSLCertificate::FromPEMString(
+    const std::string& pem_string) {
+  BIO* bio = BIO_new_mem_buf(const_cast<char*>(pem_string.c_str()), -1);
+  if (!bio)
+    return NULL;
+  (void)BIO_set_close(bio, BIO_NOCLOSE);
+  BIO_set_mem_eof_return(bio, 0);
+  X509 *x509 = PEM_read_bio_X509(bio, NULL, NULL,
+                                 const_cast<char*>("\0"));
+  BIO_free(bio);
+  if (x509)
+    return new OpenSSLCertificate(x509);
+  else
+    return NULL;
+}
+
+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 (!OpenSSLDigest::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;
+}
+
+OpenSSLCertificate::~OpenSSLCertificate() {
+  X509_free(x509_);
+}
+
+std::string OpenSSLCertificate::ToPEMString() const {
+  BIO* bio = BIO_new(BIO_s_mem());
+  if (!bio)
+    return NULL;
+  if (!PEM_write_bio_X509(bio, x509_)) {
+    BIO_free(bio);
+    return NULL;
+  }
+  BIO_write(bio, "\0", 1);
+  char* buffer;
+  BIO_get_mem_data(bio, &buffer);
+  std::string ret(buffer);
+  BIO_free(bio);
+  return ret;
+}
+
+void OpenSSLCertificate::AddReference() const {
+  CRYPTO_add(&x509_->references, 1, CRYPTO_LOCK_X509);
+}
+
+OpenSSLIdentity* OpenSSLIdentity::Generate(const std::string& common_name) {
+  OpenSSLKeyPair *key_pair = OpenSSLKeyPair::Generate();
+  if (key_pair) {
+    OpenSSLCertificate *certificate =
+        OpenSSLCertificate::Generate(key_pair, common_name);
+    if (certificate)
+      return new OpenSSLIdentity(key_pair, certificate);
+    delete key_pair;
+  }
+  LOG(LS_INFO) << "Identity generation failed";
+  return NULL;
+}
+
+SSLIdentity* OpenSSLIdentity::FromPEMStrings(
+    const std::string& private_key,
+    const std::string& certificate) {
+  scoped_ptr<OpenSSLCertificate> cert(
+      OpenSSLCertificate::FromPEMString(certificate));
+  if (!cert) {
+    LOG(LS_ERROR) << "Failed to create OpenSSLCertificate from PEM string.";
+    return NULL;
+  }
+
+  BIO* bio = BIO_new_mem_buf(const_cast<char*>(private_key.c_str()), -1);
+  if (!bio) {
+    LOG(LS_ERROR) << "Failed to create a new BIO buffer.";
+    return NULL;
+  }
+  (void)BIO_set_close(bio, BIO_NOCLOSE);
+  BIO_set_mem_eof_return(bio, 0);
+  EVP_PKEY *pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL,
+                                           const_cast<char*>("\0"));
+  BIO_free(bio);
+
+  if (!pkey) {
+    LOG(LS_ERROR) << "Failed to create the private key from PEM string.";
+    return NULL;
+  }
+
+  return new OpenSSLIdentity(new OpenSSLKeyPair(pkey),
+                             cert.release());
+}
+
+bool OpenSSLIdentity::ConfigureIdentity(SSL_CTX* ctx) {
+  // 1 is the documented success return code.
+  if (SSL_CTX_use_certificate(ctx, certificate_->x509()) != 1 ||
+     SSL_CTX_use_PrivateKey(ctx, key_pair_->pkey()) != 1) {
+    LogSSLErrors("Configuring key and certificate");
+    return false;
+  }
+  return true;
+}
+
+}  // namespace talk_base
+
+#endif  // HAVE_OPENSSL_SSL_H
+
+
diff --git a/talk/base/opensslidentity.h b/talk/base/opensslidentity.h
new file mode 100644
index 0000000..ca001b5
--- /dev/null
+++ b/talk/base/opensslidentity.h
@@ -0,0 +1,151 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_OPENSSLIDENTITY_H__
+#define TALK_BASE_OPENSSLIDENTITY_H__
+
+#include <openssl/evp.h>
+#include <openssl/x509.h>
+
+#include <string>
+
+#include "talk/base/common.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sslidentity.h"
+
+typedef struct ssl_ctx_st SSL_CTX;
+
+namespace talk_base {
+
+// OpenSSLKeyPair encapsulates an OpenSSL EVP_PKEY* keypair object,
+// which is reference counted inside the OpenSSL library.
+class OpenSSLKeyPair {
+ public:
+  explicit OpenSSLKeyPair(EVP_PKEY* pkey) : pkey_(pkey) {
+    ASSERT(pkey_ != NULL);
+  }
+
+  static OpenSSLKeyPair* Generate();
+
+  virtual ~OpenSSLKeyPair();
+
+  virtual OpenSSLKeyPair* GetReference() {
+    AddReference();
+    return new OpenSSLKeyPair(pkey_);
+  }
+
+  EVP_PKEY* pkey() const { return pkey_; }
+
+ private:
+  void AddReference();
+
+  EVP_PKEY* pkey_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(OpenSSLKeyPair);
+};
+
+// OpenSSLCertificate encapsulates an OpenSSL X509* certificate object,
+// which is also reference counted inside the OpenSSL library.
+class OpenSSLCertificate : public SSLCertificate {
+ public:
+  static OpenSSLCertificate* Generate(OpenSSLKeyPair* key_pair,
+                                      const std::string& common_name);
+  static OpenSSLCertificate* FromPEMString(const std::string& pem_string);
+
+  virtual ~OpenSSLCertificate();
+
+  virtual OpenSSLCertificate* GetReference() const {
+    AddReference();
+    return new OpenSSLCertificate(x509_);
+  }
+
+  X509* x509() const { return x509_; }
+
+  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);
+
+ private:
+  explicit OpenSSLCertificate(X509* x509) : x509_(x509) {
+    ASSERT(x509_ != NULL);
+  }
+  void AddReference() const;
+
+  X509* x509_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(OpenSSLCertificate);
+};
+
+// Holds a keypair and certificate together, and a method to generate
+// them consistently.
+class OpenSSLIdentity : public SSLIdentity {
+ public:
+  static OpenSSLIdentity* Generate(const std::string& common_name);
+  static SSLIdentity* FromPEMStrings(const std::string& private_key,
+                                     const std::string& certificate);
+  virtual ~OpenSSLIdentity() { }
+
+  virtual const OpenSSLCertificate& certificate() const {
+    return *certificate_;
+  }
+
+  virtual OpenSSLIdentity* GetReference() const {
+    return new OpenSSLIdentity(key_pair_->GetReference(),
+                               certificate_->GetReference());
+  }
+
+  // Configure an SSL context object to use our key and certificate.
+  bool ConfigureIdentity(SSL_CTX* ctx);
+
+ private:
+  OpenSSLIdentity(OpenSSLKeyPair* key_pair,
+                  OpenSSLCertificate* certificate)
+      : key_pair_(key_pair), certificate_(certificate) {
+    ASSERT(key_pair != NULL);
+    ASSERT(certificate != NULL);
+  }
+
+  scoped_ptr<OpenSSLKeyPair> key_pair_;
+  scoped_ptr<OpenSSLCertificate> certificate_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(OpenSSLIdentity);
+};
+
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_OPENSSLIDENTITY_H__
diff --git a/talk/base/opensslstreamadapter.cc b/talk/base/opensslstreamadapter.cc
new file mode 100644
index 0000000..16021a9
--- /dev/null
+++ b/talk/base/opensslstreamadapter.cc
@@ -0,0 +1,940 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif  // HAVE_CONFIG_H
+
+#if HAVE_OPENSSL_SSL_H
+
+#include "talk/base/opensslstreamadapter.h"
+
+#include <openssl/bio.h>
+#include <openssl/crypto.h>
+#include <openssl/err.h>
+#include <openssl/rand.h>
+#include <openssl/ssl.h>
+#include <openssl/x509v3.h>
+
+#include <vector>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/stream.h"
+#include "talk/base/openssladapter.h"
+#include "talk/base/openssldigest.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
+//////////////////////////////////////////////////////////////////////
+
+static int stream_write(BIO* h, const char* buf, int num);
+static int stream_read(BIO* h, char* buf, int size);
+static int stream_puts(BIO* h, const char* str);
+static long stream_ctrl(BIO* h, int cmd, long arg1, void* arg2);
+static int stream_new(BIO* h);
+static int stream_free(BIO* data);
+
+static BIO_METHOD methods_stream = {
+  BIO_TYPE_BIO,
+  "stream",
+  stream_write,
+  stream_read,
+  stream_puts,
+  0,
+  stream_ctrl,
+  stream_new,
+  stream_free,
+  NULL,
+};
+
+static BIO_METHOD* BIO_s_stream() { return(&methods_stream); }
+
+static BIO* BIO_new_stream(StreamInterface* stream) {
+  BIO* ret = BIO_new(BIO_s_stream());
+  if (ret == NULL)
+    return NULL;
+  ret->ptr = stream;
+  return ret;
+}
+
+// bio methods return 1 (or at least non-zero) on success and 0 on failure.
+
+static int stream_new(BIO* b) {
+  b->shutdown = 0;
+  b->init = 1;
+  b->num = 0;  // 1 means end-of-stream
+  b->ptr = 0;
+  return 1;
+}
+
+static int stream_free(BIO* b) {
+  if (b == NULL)
+    return 0;
+  return 1;
+}
+
+static int stream_read(BIO* b, char* out, int outl) {
+  if (!out)
+    return -1;
+  StreamInterface* stream = static_cast<StreamInterface*>(b->ptr);
+  BIO_clear_retry_flags(b);
+  size_t read;
+  int error;
+  StreamResult result = stream->Read(out, outl, &read, &error);
+  if (result == SR_SUCCESS) {
+    return read;
+  } else if (result == SR_EOS) {
+    b->num = 1;
+  } else if (result == SR_BLOCK) {
+    BIO_set_retry_read(b);
+  }
+  return -1;
+}
+
+static int stream_write(BIO* b, const char* in, int inl) {
+  if (!in)
+    return -1;
+  StreamInterface* stream = static_cast<StreamInterface*>(b->ptr);
+  BIO_clear_retry_flags(b);
+  size_t written;
+  int error;
+  StreamResult result = stream->Write(in, inl, &written, &error);
+  if (result == SR_SUCCESS) {
+    return written;
+  } else if (result == SR_BLOCK) {
+    BIO_set_retry_write(b);
+  }
+  return -1;
+}
+
+static int stream_puts(BIO* b, const char* str) {
+  return stream_write(b, str, strlen(str));
+}
+
+static long stream_ctrl(BIO* b, int cmd, long num, void* ptr) {
+  UNUSED(num);
+  UNUSED(ptr);
+
+  switch (cmd) {
+    case BIO_CTRL_RESET:
+      return 0;
+    case BIO_CTRL_EOF:
+      return b->num;
+    case BIO_CTRL_WPENDING:
+    case BIO_CTRL_PENDING:
+      return 0;
+    case BIO_CTRL_FLUSH:
+      return 1;
+    default:
+      return 0;
+  }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// OpenSSLStreamAdapter
+/////////////////////////////////////////////////////////////////////////////
+
+OpenSSLStreamAdapter::OpenSSLStreamAdapter(StreamInterface* stream)
+    : SSLStreamAdapter(stream),
+      state_(SSL_NONE),
+      role_(SSL_CLIENT),
+      ssl_read_needs_write_(false), ssl_write_needs_read_(false),
+      ssl_(NULL), ssl_ctx_(NULL),
+      custom_verification_succeeded_(false),
+      ssl_mode_(SSL_MODE_TLS) {
+}
+
+OpenSSLStreamAdapter::~OpenSSLStreamAdapter() {
+  Cleanup();
+}
+
+void OpenSSLStreamAdapter::SetIdentity(SSLIdentity* identity) {
+  ASSERT(!identity_);
+  identity_.reset(static_cast<OpenSSLIdentity*>(identity));
+}
+
+void OpenSSLStreamAdapter::SetServerRole(SSLRole role) {
+  role_ = role;
+}
+
+void OpenSSLStreamAdapter::SetPeerCertificate(SSLCertificate* cert) {
+  ASSERT(!peer_certificate_);
+  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_);
+  ASSERT(peer_certificate_digest_algorithm_.size() == 0);
+  ASSERT(ssl_server_name_.empty());
+  size_t expected_len;
+
+  if (!OpenSSLDigest::GetDigestSize(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 false;
+
+  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;
+  return StartSSL();
+}
+
+int OpenSSLStreamAdapter::StartSSLWithPeer() {
+  ASSERT(ssl_server_name_.empty());
+  // It is permitted to specify peer_certificate_ only later.
+  return StartSSL();
+}
+
+void OpenSSLStreamAdapter::SetMode(SSLMode mode) {
+  ASSERT(state_ == SSL_NONE);
+  ssl_mode_ = mode;
+}
+
+//
+// StreamInterface Implementation
+//
+
+StreamResult OpenSSLStreamAdapter::Write(const void* data, size_t data_len,
+                                         size_t* written, int* error) {
+  LOG(LS_VERBOSE) << "OpenSSLStreamAdapter::Write(" << data_len << ")";
+
+  switch (state_) {
+  case SSL_NONE:
+    // pass-through in clear text
+    return StreamAdapterInterface::Write(data, data_len, written, error);
+
+  case SSL_WAIT:
+  case SSL_CONNECTING:
+    return SR_BLOCK;
+
+  case SSL_CONNECTED:
+    break;
+
+  case SSL_ERROR:
+  case SSL_CLOSED:
+  default:
+    if (error)
+      *error = ssl_error_code_;
+    return SR_ERROR;
+  }
+
+  // OpenSSL will return an error if we try to write zero bytes
+  if (data_len == 0) {
+    if (written)
+      *written = 0;
+    return SR_SUCCESS;
+  }
+
+  ssl_write_needs_read_ = false;
+
+  int code = SSL_write(ssl_, data, data_len);
+  int ssl_error = SSL_get_error(ssl_, code);
+  switch (ssl_error) {
+  case SSL_ERROR_NONE:
+    LOG(LS_VERBOSE) << " -- success";
+    ASSERT(0 < code && static_cast<unsigned>(code) <= data_len);
+    if (written)
+      *written = code;
+    return SR_SUCCESS;
+  case SSL_ERROR_WANT_READ:
+    LOG(LS_VERBOSE) << " -- error want read";
+    ssl_write_needs_read_ = true;
+    return SR_BLOCK;
+  case SSL_ERROR_WANT_WRITE:
+    LOG(LS_VERBOSE) << " -- error want write";
+    return SR_BLOCK;
+
+  case SSL_ERROR_ZERO_RETURN:
+  default:
+    Error("SSL_write", (ssl_error ? ssl_error : -1), false);
+    if (error)
+      *error = ssl_error_code_;
+    return SR_ERROR;
+  }
+  // not reached
+}
+
+StreamResult OpenSSLStreamAdapter::Read(void* data, size_t data_len,
+                                        size_t* read, int* error) {
+  LOG(LS_VERBOSE) << "OpenSSLStreamAdapter::Read(" << data_len << ")";
+  switch (state_) {
+    case SSL_NONE:
+      // pass-through in clear text
+      return StreamAdapterInterface::Read(data, data_len, read, error);
+
+    case SSL_WAIT:
+    case SSL_CONNECTING:
+      return SR_BLOCK;
+
+    case SSL_CONNECTED:
+      break;
+
+    case SSL_CLOSED:
+      return SR_EOS;
+
+    case SSL_ERROR:
+    default:
+      if (error)
+        *error = ssl_error_code_;
+      return SR_ERROR;
+  }
+
+  // Don't trust OpenSSL with zero byte reads
+  if (data_len == 0) {
+    if (read)
+      *read = 0;
+    return SR_SUCCESS;
+  }
+
+  ssl_read_needs_write_ = false;
+
+  int code = SSL_read(ssl_, data, data_len);
+  int ssl_error = SSL_get_error(ssl_, code);
+  switch (ssl_error) {
+    case SSL_ERROR_NONE:
+      LOG(LS_VERBOSE) << " -- 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_VERBOSE) << " -- error want read";
+      return SR_BLOCK;
+    case SSL_ERROR_WANT_WRITE:
+      LOG(LS_VERBOSE) << " -- error want write";
+      ssl_read_needs_write_ = true;
+      return SR_BLOCK;
+    case SSL_ERROR_ZERO_RETURN:
+      LOG(LS_VERBOSE) << " -- remote side closed";
+      return SR_EOS;
+      break;
+    default:
+      LOG(LS_VERBOSE) << " -- error " << code;
+      Error("SSL_read", (ssl_error ? ssl_error : -1), false);
+      if (error)
+        *error = ssl_error_code_;
+      return SR_ERROR;
+  }
+  // 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_VERBOSE) << " -- error " << code;
+      Error("SSL_read", (ssl_error ? ssl_error : -1), false);
+      return;
+    }
+
+    LOG(LS_VERBOSE) << " -- flushed " << code << " bytes";
+    left -= code;
+  }
+}
+
+void OpenSSLStreamAdapter::Close() {
+  Cleanup();
+  ASSERT(state_ == SSL_CLOSED || state_ == SSL_ERROR);
+  StreamAdapterInterface::Close();
+}
+
+StreamState OpenSSLStreamAdapter::GetState() const {
+  switch (state_) {
+    case SSL_WAIT:
+    case SSL_CONNECTING:
+      return SS_OPENING;
+    case SSL_CONNECTED:
+      return SS_OPEN;
+    default:
+      return SS_CLOSED;
+  };
+  // not reached
+}
+
+void OpenSSLStreamAdapter::OnEvent(StreamInterface* stream, int events,
+                                   int err) {
+  int events_to_signal = 0;
+  int signal_error = 0;
+  ASSERT(stream == this->stream());
+  if ((events & SE_OPEN)) {
+    LOG(LS_VERBOSE) << "OpenSSLStreamAdapter::OnEvent SE_OPEN";
+    if (state_ != SSL_WAIT) {
+      ASSERT(state_ == SSL_NONE);
+      events_to_signal |= SE_OPEN;
+    } else {
+      state_ = SSL_CONNECTING;
+      if (int err = BeginSSL()) {
+        Error("BeginSSL", err, true);
+        return;
+      }
+    }
+  }
+  if ((events & (SE_READ|SE_WRITE))) {
+    LOG(LS_VERBOSE) << "OpenSSLStreamAdapter::OnEvent"
+                 << ((events & SE_READ) ? " SE_READ" : "")
+                 << ((events & SE_WRITE) ? " SE_WRITE" : "");
+    if (state_ == SSL_NONE) {
+      events_to_signal |= events & (SE_READ|SE_WRITE);
+    } else if (state_ == SSL_CONNECTING) {
+      if (int err = ContinueSSL()) {
+        Error("ContinueSSL", err, true);
+        return;
+      }
+    } else if (state_ == SSL_CONNECTED) {
+      if (((events & SE_READ) && ssl_write_needs_read_) ||
+          (events & SE_WRITE)) {
+        LOG(LS_VERBOSE) << " -- onStreamWriteable";
+        events_to_signal |= SE_WRITE;
+      }
+      if (((events & SE_WRITE) && ssl_read_needs_write_) ||
+          (events & SE_READ)) {
+        LOG(LS_VERBOSE) << " -- onStreamReadable";
+        events_to_signal |= SE_READ;
+      }
+    }
+  }
+  if ((events & SE_CLOSE)) {
+    LOG(LS_VERBOSE) << "OpenSSLStreamAdapter::OnEvent(SE_CLOSE, " << err << ")";
+    Cleanup();
+    events_to_signal |= SE_CLOSE;
+    // SE_CLOSE is the only event that uses the final parameter to OnEvent().
+    ASSERT(signal_error == 0);
+    signal_error = err;
+  }
+  if (events_to_signal)
+    StreamAdapterInterface::OnEvent(stream, events_to_signal, signal_error);
+}
+
+int OpenSSLStreamAdapter::StartSSL() {
+  ASSERT(state_ == SSL_NONE);
+
+  if (StreamAdapterInterface::GetState() != SS_OPEN) {
+    state_ = SSL_WAIT;
+    return 0;
+  }
+
+  state_ = SSL_CONNECTING;
+  if (int err = BeginSSL()) {
+    Error("BeginSSL", err, false);
+    return err;
+  }
+
+  return 0;
+}
+
+int OpenSSLStreamAdapter::BeginSSL() {
+  ASSERT(state_ == SSL_CONNECTING);
+  // The underlying stream has open. If we are in peer-to-peer mode
+  // then a peer certificate must have been specified by now.
+  ASSERT(!ssl_server_name_.empty() ||
+         peer_certificate_ ||
+         !peer_certificate_digest_algorithm_.empty());
+  LOG(LS_INFO) << "BeginSSL: "
+               << (!ssl_server_name_.empty() ? ssl_server_name_ :
+                                               "with peer");
+
+  BIO* bio = NULL;
+
+  // First set up the context
+  ASSERT(ssl_ctx_ == NULL);
+  ssl_ctx_ = SetupSSLContext();
+  if (!ssl_ctx_)
+    return -1;
+
+  bio = BIO_new_stream(static_cast<StreamInterface*>(stream()));
+  if (!bio)
+    return -1;
+
+  ssl_ = SSL_new(ssl_ctx_);
+  if (!ssl_) {
+    BIO_free(bio);
+    return -1;
+  }
+
+  SSL_set_app_data(ssl_, this);
+
+  SSL_set_bio(ssl_, bio, bio);  // the SSL object owns the bio now.
+
+  SSL_set_mode(ssl_, SSL_MODE_ENABLE_PARTIAL_WRITE |
+               SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+
+  // Do the connect
+  return ContinueSSL();
+}
+
+int OpenSSLStreamAdapter::ContinueSSL() {
+  LOG(LS_VERBOSE) << "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_);
+  int ssl_error;
+  switch (ssl_error = SSL_get_error(ssl_, code)) {
+    case SSL_ERROR_NONE:
+      LOG(LS_VERBOSE) << " -- success";
+
+      if (!SSLPostConnectionCheck(ssl_, ssl_server_name_.c_str(),
+                                  peer_certificate_ ?
+                                      peer_certificate_->x509() : NULL,
+                                  peer_certificate_digest_algorithm_)) {
+        LOG(LS_ERROR) << "TLS post connection check failed";
+        return -1;
+      }
+
+      state_ = SSL_CONNECTED;
+      StreamAdapterInterface::OnEvent(stream(), SE_OPEN|SE_READ|SE_WRITE, 0);
+      break;
+
+    case SSL_ERROR_WANT_READ: {
+        LOG(LS_VERBOSE) << " -- 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:
+      LOG(LS_VERBOSE) << " -- error want write";
+      break;
+
+    case SSL_ERROR_ZERO_RETURN:
+    default:
+      LOG(LS_VERBOSE) << " -- error " << code;
+      return (ssl_error != 0) ? ssl_error : -1;
+  }
+
+  return 0;
+}
+
+void OpenSSLStreamAdapter::Error(const char* context, int err, bool signal) {
+  LOG(LS_WARNING) << "OpenSSLStreamAdapter::Error("
+                  << context << ", " << err << ")";
+  state_ = SSL_ERROR;
+  ssl_error_code_ = err;
+  Cleanup();
+  if (signal)
+    StreamAdapterInterface::OnEvent(stream(), SE_CLOSE, err);
+}
+
+void OpenSSLStreamAdapter::Cleanup() {
+  LOG(LS_INFO) << "Cleanup";
+
+  if (state_ != SSL_ERROR) {
+    state_ = SSL_CLOSED;
+    ssl_error_code_ = 0;
+  }
+
+  if (ssl_) {
+    SSL_free(ssl_);
+    ssl_ = NULL;
+  }
+  if (ssl_ctx_) {
+    SSL_CTX_free(ssl_ctx_);
+    ssl_ctx_ = NULL;
+  }
+  identity_.reset();
+  peer_certificate_.reset();
+
+  // 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 = 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;
+
+  if (identity_ && !identity_->ConfigureIdentity(ctx)) {
+    SSL_CTX_free(ctx);
+    return NULL;
+  }
+
+  if (!peer_certificate_) {  // traditional mode
+    // Add the root cert to the SSL context
+    if (!OpenSSLAdapter::ConfigureTrustedRootCertificates(ctx)) {
+      SSL_CTX_free(ctx);
+      return NULL;
+    }
+  }
+
+  if (peer_certificate_ && role_ == SSL_SERVER)
+    // we must specify which client cert to ask for
+    SSL_CTX_add_client_CA(ctx, peer_certificate_->x509());
+
+#ifdef _DEBUG
+  SSL_CTX_set_info_callback(ctx, OpenSSLAdapter::SSLInfoCallback);
+#endif
+
+  SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER |SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
+                     SSLVerifyCallback);
+  SSL_CTX_set_verify_depth(ctx, 4);
+  SSL_CTX_set_cipher_list(ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
+
+#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;
+}
+
+int OpenSSLStreamAdapter::SSLVerifyCallback(int ok, X509_STORE_CTX* store) {
+#if _DEBUG
+  if (!ok) {
+    char data[256];
+    X509* cert = X509_STORE_CTX_get_current_cert(store);
+    int depth = X509_STORE_CTX_get_error_depth(store);
+    int err = X509_STORE_CTX_get_error(store);
+
+    LOG(LS_INFO) << "Error with certificate at depth: " << depth;
+    X509_NAME_oneline(X509_get_issuer_name(cert), data, sizeof(data));
+    LOG(LS_INFO) << "  issuer  = " << data;
+    X509_NAME_oneline(X509_get_subject_name(cert), data, sizeof(data));
+    LOG(LS_INFO) << "  subject = " << data;
+    LOG(LS_INFO) << "  err     = " << err
+      << ":" << X509_verify_cert_error_string(err);
+  }
+#endif
+
+  // Get our SSL structure from the store
+  SSL* ssl = reinterpret_cast<SSL*>(X509_STORE_CTX_get_ex_data(
+                                        store,
+                                        SSL_get_ex_data_X509_STORE_CTX_idx()));
+
+  OpenSSLStreamAdapter* stream =
+    reinterpret_cast<OpenSSLStreamAdapter*>(SSL_get_app_data(ssl));
+
+  // In peer-to-peer mode, no root cert / certificate authority was
+  // specified, so the libraries knows of no certificate to accept,
+  // and therefore it will necessarily call here on the first cert it
+  // tries to verify.
+  if (!ok && stream->peer_certificate_) {
+    X509* cert = X509_STORE_CTX_get_current_cert(store);
+    int err = X509_STORE_CTX_get_error(store);
+    // peer-to-peer mode: allow the certificate to be self-signed,
+    // assuming it matches the cert that was specified.
+    if (err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT &&
+        X509_cmp(cert, stream->peer_certificate_->x509()) == 0) {
+      LOG(LS_INFO) << "Accepted self-signed peer certificate authority";
+      ok = 1;
+    }
+  } else if (!ok && !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 =
+        reinterpret_cast<void*>(X509_STORE_CTX_get_current_cert(store));
+    if (OpenSSLAdapter::custom_verify_callback_(cert)) {
+      stream->custom_verification_succeeded_ = true;
+      LOG(LS_INFO) << "validated certificate using custom callback";
+      ok = 1;
+    }
+  }
+
+  if (!ok && stream->ignore_bad_cert()) {
+    LOG(LS_WARNING) << "Ignoring cert error while verifying cert chain";
+    ok = 1;
+  }
+
+  return ok;
+}
+
+// This code is taken from the "Network Security with OpenSSL"
+// sample in chapter 5
+bool OpenSSLStreamAdapter::SSLPostConnectionCheck(SSL* ssl,
+                                                  const char* server_name,
+                                                  const X509* peer_cert,
+                                                  const std::string
+                                                  &peer_digest) {
+  ASSERT(server_name != NULL);
+  bool ok;
+  if (server_name[0] != '\0') {  // traditional mode
+    ok = OpenSSLAdapter::VerifyServerName(ssl, server_name, ignore_bad_cert());
+
+    if (ok) {
+      ok = (SSL_get_verify_result(ssl) == X509_V_OK ||
+            custom_verification_succeeded_);
+    }
+  } else {  // peer-to-peer mode
+    ASSERT((peer_cert != NULL) || (!peer_digest.empty()));
+    // no server name validation
+    ok = true;
+  }
+
+  if (!ok && ignore_bad_cert()) {
+    LOG(LS_ERROR) << "SSL_get_verify_result(ssl) = "
+                  << SSL_get_verify_result(ssl);
+    LOG(LS_INFO) << "Other TLS post connection checks failed.";
+    ok = true;
+  }
+
+  return ok;
+}
+
+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
new file mode 100644
index 0000000..8e92a10
--- /dev/null
+++ b/talk/base/opensslstreamadapter.h
@@ -0,0 +1,215 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_OPENSSLSTREAMADAPTER_H__
+#define TALK_BASE_OPENSSLSTREAMADAPTER_H__
+
+#include <string>
+#include <vector>
+
+#include "talk/base/buffer.h"
+#include "talk/base/sslstreamadapter.h"
+#include "talk/base/opensslidentity.h"
+
+typedef struct ssl_st SSL;
+typedef struct ssl_ctx_st SSL_CTX;
+typedef struct x509_store_ctx_st X509_STORE_CTX;
+
+namespace talk_base {
+
+// This class was written with OpenSSLAdapter (a socket adapter) as a
+// starting point. It has similar structure and functionality, with
+// the peer-to-peer mode added.
+//
+// Static methods to initialize and deinit the SSL library are in
+// OpenSSLAdapter. This class also uses
+// OpenSSLAdapter::custom_verify_callback_ (a static field). These
+// should probably be moved out to a neutral class.
+//
+// In a few cases I have factored out some OpenSSLAdapter code into
+// static methods so it can be reused from this class. Eventually that
+// code should probably be moved to a common support
+// class. Unfortunately there remain a few duplicated sections of
+// code. I have not done more restructuring because I did not want to
+// affect existing code that uses OpenSSLAdapter.
+//
+// This class does not support the SSL connection restart feature
+// present in OpenSSLAdapter. I am not entirely sure how the feature
+// is useful and I am not convinced that it works properly.
+//
+// This implementation is careful to disallow data exchange after an
+// SSL error, and it has an explicit SSL_CLOSED state. It should not
+// be possible to send any data in clear after one of the StartSSL
+// methods has been called.
+
+// Look in sslstreamadapter.h for documentation of the methods.
+
+class OpenSSLIdentity;
+
+///////////////////////////////////////////////////////////////////////////////
+
+class OpenSSLStreamAdapter : public SSLStreamAdapter {
+ public:
+  explicit OpenSSLStreamAdapter(StreamInterface* stream);
+  virtual ~OpenSSLStreamAdapter();
+
+  virtual void SetIdentity(SSLIdentity* identity);
+
+  // 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);
+  virtual StreamResult Write(const void* data, size_t data_len,
+                             size_t* written, int* error);
+  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);
+
+ private:
+  enum SSLState {
+    // Before calling one of the StartSSL methods, data flows
+    // in clear text.
+    SSL_NONE,
+    SSL_WAIT,  // waiting for the stream to open to start SSL negotiation
+    SSL_CONNECTING,  // SSL negotiation in progress
+    SSL_CONNECTED,  // SSL stream successfully established
+    SSL_ERROR,  // some SSL error occurred, stream is closed
+    SSL_CLOSED  // Clean close
+  };
+
+  enum { 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
+  // on some other error cases, so it can't really be interpreted
+  // unfortunately.
+
+  // Go from state SSL_NONE to either SSL_CONNECTING or SSL_WAIT,
+  // depending on whether the underlying stream is already open or
+  // not.
+  int StartSSL();
+  // Prepare SSL library, state is SSL_CONNECTING.
+  int BeginSSL();
+  // Perform SSL negotiation steps.
+  int ContinueSSL();
+
+  // Error handler helper. signal is given as true for errors in
+  // asynchronous contexts (when an error method was not returned
+  // through some other method), and in that case an SE_CLOSE event is
+  // raised on the stream with the specified error.
+  // A 0 error means a graceful close, otherwise there is not really enough
+  // context to interpret the error code.
+  void Error(const char* context, int err, bool signal);
+  void Cleanup();
+
+  // 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 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
+  // passed.
+  static int SSLVerifyCallback(int ok, X509_STORE_CTX* store);
+
+
+  SSLState state_;
+  SSLRole role_;
+  int ssl_error_code_;  // valid when state_ == SSL_ERROR or SSL_CLOSED
+  // Whether the SSL negotiation is blocked on needing to read or
+  // write to the wrapped stream.
+  bool ssl_read_needs_write_;
+  bool ssl_write_needs_read_;
+
+  SSL* ssl_;
+  SSL_CTX* ssl_ctx_;
+
+  // 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_;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_OPENSSLSTREAMADAPTER_H__
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.cc b/talk/base/pathutils.cc
new file mode 100644
index 0000000..02aba7f
--- /dev/null
+++ b/talk/base/pathutils.cc
@@ -0,0 +1,268 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#include <shellapi.h>
+#include <shlobj.h>
+#include <tchar.h>
+#endif  // WIN32
+
+#include "talk/base/common.h"
+#include "talk/base/fileutils.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/urlencode.h"
+
+namespace talk_base {
+
+static const char EMPTY_STR[] = "";
+
+// EXT_DELIM separates a file basename from extension
+const char EXT_DELIM = '.';
+
+// FOLDER_DELIMS separate folder segments and the filename
+const char* const FOLDER_DELIMS = "/\\";
+
+// DEFAULT_FOLDER_DELIM is the preferred delimiter for this platform
+#if WIN32
+const char DEFAULT_FOLDER_DELIM = '\\';
+#else  // !WIN32
+const char DEFAULT_FOLDER_DELIM = '/';
+#endif  // !WIN32
+
+///////////////////////////////////////////////////////////////////////////////
+// Pathname - parsing of pathnames into components, and vice versa
+///////////////////////////////////////////////////////////////////////////////
+
+bool Pathname::IsFolderDelimiter(char ch) {
+  return (NULL != ::strchr(FOLDER_DELIMS, ch));
+}
+
+char Pathname::DefaultFolderDelimiter() {
+  return DEFAULT_FOLDER_DELIM;
+}
+
+Pathname::Pathname()
+    : folder_delimiter_(DEFAULT_FOLDER_DELIM) {
+}
+
+Pathname::Pathname(const std::string& pathname)
+    : folder_delimiter_(DEFAULT_FOLDER_DELIM) {
+  SetPathname(pathname);
+}
+
+Pathname::Pathname(const std::string& folder, const std::string& filename)
+    : folder_delimiter_(DEFAULT_FOLDER_DELIM) {
+  SetPathname(folder, filename);
+}
+
+void Pathname::SetFolderDelimiter(char delimiter) {
+  ASSERT(IsFolderDelimiter(delimiter));
+  folder_delimiter_ = delimiter;
+}
+
+void Pathname::Normalize() {
+  for (size_t i=0; i<folder_.length(); ++i) {
+    if (IsFolderDelimiter(folder_[i])) {
+      folder_[i] = folder_delimiter_;
+    }
+  }
+}
+
+void Pathname::clear() {
+  folder_.clear();
+  basename_.clear();
+  extension_.clear();
+}
+
+bool Pathname::empty() const {
+  return folder_.empty() && basename_.empty() && extension_.empty();
+}
+
+std::string Pathname::pathname() const {
+  std::string pathname(folder_);
+  pathname.append(basename_);
+  pathname.append(extension_);
+  if (pathname.empty()) {
+    // Instead of the empty pathname, return the current working directory.
+    pathname.push_back('.');
+    pathname.push_back(folder_delimiter_);
+  }
+  return pathname;
+}
+
+std::string Pathname::url() const {
+  std::string s = "file:///";
+  for (size_t i=0; i<folder_.length(); ++i) {
+    if (IsFolderDelimiter(folder_[i]))
+      s += '/';
+    else
+      s += folder_[i];
+  }
+  s += basename_;
+  s += extension_;
+  return UrlEncodeStringForOnlyUnsafeChars(s);
+}
+
+void Pathname::SetPathname(const std::string& pathname) {
+  std::string::size_type pos = pathname.find_last_of(FOLDER_DELIMS);
+  if (pos != std::string::npos) {
+    SetFolder(pathname.substr(0, pos + 1));
+    SetFilename(pathname.substr(pos + 1));
+  } else {
+    SetFolder(EMPTY_STR);
+    SetFilename(pathname);
+  }
+}
+
+void Pathname::SetPathname(const std::string& folder,
+                           const std::string& filename) {
+  SetFolder(folder);
+  SetFilename(filename);
+}
+
+void Pathname::AppendPathname(const std::string& pathname) {
+  std::string full_pathname(folder_);
+  full_pathname.append(pathname);
+  SetPathname(full_pathname);
+}
+
+std::string Pathname::folder() const {
+  return folder_;
+}
+
+std::string Pathname::folder_name() const {
+  std::string::size_type pos = std::string::npos;
+  if (folder_.size() >= 2) {
+    pos = folder_.find_last_of(FOLDER_DELIMS, folder_.length() - 2);
+  }
+  if (pos != std::string::npos) {
+    return folder_.substr(pos + 1);
+  } else {
+    return folder_;
+  }
+}
+
+std::string Pathname::parent_folder() const {
+  std::string::size_type pos = std::string::npos;
+  if (folder_.size() >= 2) {
+    pos = folder_.find_last_of(FOLDER_DELIMS, folder_.length() - 2);
+  }
+  if (pos != std::string::npos) {
+    return folder_.substr(0, pos + 1);
+  } else {
+    return EMPTY_STR;
+  }
+}
+
+void Pathname::SetFolder(const std::string& folder) {
+  folder_.assign(folder);
+  // Ensure folder ends in a path delimiter
+  if (!folder_.empty() && !IsFolderDelimiter(folder_[folder_.length()-1])) {
+    folder_.push_back(folder_delimiter_);
+  }
+}
+
+void Pathname::AppendFolder(const std::string& folder) {
+  folder_.append(folder);
+  // Ensure folder ends in a path delimiter
+  if (!folder_.empty() && !IsFolderDelimiter(folder_[folder_.length()-1])) {
+    folder_.push_back(folder_delimiter_);
+  }
+}
+
+std::string Pathname::basename() const {
+  return basename_;
+}
+
+bool Pathname::SetBasename(const std::string& basename) {
+  if(basename.find_first_of(FOLDER_DELIMS) != std::string::npos) {
+    return false;
+  }
+  basename_.assign(basename);
+  return true;
+}
+
+std::string Pathname::extension() const {
+  return extension_;
+}
+
+bool Pathname::SetExtension(const std::string& extension) {
+  if (extension.find_first_of(FOLDER_DELIMS) != std::string::npos ||
+    extension.find_first_of(EXT_DELIM, 1) != std::string::npos) {
+      return false;
+  }
+  extension_.assign(extension);
+  // Ensure extension begins with the extension delimiter
+  if (!extension_.empty() && (extension_[0] != EXT_DELIM)) {
+    extension_.insert(extension_.begin(), EXT_DELIM);
+  }
+  return true;
+}
+
+std::string Pathname::filename() const {
+  std::string filename(basename_);
+  filename.append(extension_);
+  return filename;
+}
+
+bool Pathname::SetFilename(const std::string& filename) {
+  std::string::size_type pos = filename.rfind(EXT_DELIM);
+  if ((pos == std::string::npos) || (pos == 0)) {
+    return SetExtension(EMPTY_STR) && SetBasename(filename);
+  } else {
+    return SetExtension(filename.substr(pos)) && SetBasename(filename.substr(0, pos));
+  }
+}
+
+#ifdef WIN32
+bool Pathname::GetDrive(char *drive, uint32 bytes) const {
+  return GetDrive(drive, bytes, folder_);
+}
+
+// static
+bool Pathname::GetDrive(char *drive, uint32 bytes,
+                        const std::string& pathname) {
+  // need at lease 4 bytes to save c:
+  if (bytes < 4 || pathname.size() < 3) {
+    return false;
+  }
+
+  memcpy(drive, pathname.c_str(), 3);
+  drive[3] = 0;
+  // sanity checking
+  return (isalpha(drive[0]) &&
+          drive[1] == ':' &&
+          drive[2] == '\\');
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/pathutils.h b/talk/base/pathutils.h
new file mode 100644
index 0000000..ab2aacd
--- /dev/null
+++ b/talk/base/pathutils.h
@@ -0,0 +1,180 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_PATHUTILS_H__
+#define TALK_BASE_PATHUTILS_H__
+
+#include <string>
+// Temporary, until deprecated helpers are removed.
+#include "talk/base/fileutils.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// Pathname - parsing of pathnames into components, and vice versa.
+//
+// To establish consistent terminology, a filename never contains a folder
+// component.  A folder never contains a filename.  A pathname may include
+// a folder and/or filename component.  Here are some examples:
+//
+//   pathname()      /home/john/example.txt
+//   folder()        /home/john/
+//   filename()                 example.txt
+//   parent_folder() /home/
+//   folder_name()         john/
+//   basename()                 example
+//   extension()                       .txt
+//
+// Basename may begin, end, and/or include periods, but no folder delimiters.
+// If extension exists, it consists of a period followed by zero or more
+// non-period/non-delimiter characters, and basename is non-empty.
+///////////////////////////////////////////////////////////////////////////////
+
+class Pathname {
+public:
+  // Folder delimiters are slash and backslash
+  static bool IsFolderDelimiter(char ch);
+  static char DefaultFolderDelimiter();
+
+  Pathname();
+  Pathname(const std::string& pathname);
+  Pathname(const std::string& folder, const std::string& filename);
+
+  // Set's the default folder delimiter for this Pathname
+  char folder_delimiter() const { return folder_delimiter_; }
+  void SetFolderDelimiter(char delimiter);
+
+  // Normalize changes all folder delimiters to folder_delimiter()
+  void Normalize();
+
+  // Reset to the empty pathname
+  void clear();
+
+  // Returns true if the pathname is empty.  Note: this->pathname().empty()
+  // is always false.
+  bool empty() const;
+
+  std::string url() const;
+
+  // Returns the folder and filename components.  If the pathname is empty,
+  // returns a string representing the current directory (as a relative path,
+  // i.e., ".").
+  std::string pathname() const;
+  void SetPathname(const std::string& pathname);
+  void SetPathname(const std::string& folder, const std::string& filename);
+
+  // Append pathname to the current folder (if any).  Any existing filename
+  // will be discarded.
+  void AppendPathname(const std::string& pathname);
+
+  std::string folder() const;
+  std::string folder_name() const;
+  std::string parent_folder() const;
+  // SetFolder and AppendFolder will append a folder delimiter, if needed.
+  void SetFolder(const std::string& folder);
+  void AppendFolder(const std::string& folder);
+
+  std::string basename() const;
+  bool SetBasename(const std::string& basename);
+
+  std::string extension() const;
+  // SetExtension will prefix a period, if needed.
+  bool SetExtension(const std::string& extension);
+
+  std::string filename() const;
+  bool SetFilename(const std::string& filename);
+
+#ifdef WIN32
+  bool GetDrive(char *drive, uint32 bytes) const;
+  static bool GetDrive(char *drive, uint32 bytes,const std::string& pathname);
+#endif
+
+private:
+  std::string folder_, basename_, extension_;
+  char folder_delimiter_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Global Helpers (deprecated)
+///////////////////////////////////////////////////////////////////////////////
+
+inline void SetOrganizationName(const std::string& organization) {
+  Filesystem::SetOrganizationName(organization);
+}
+inline void SetApplicationName(const std::string& application) {
+  Filesystem::SetApplicationName(application);
+}
+inline void GetOrganizationName(std::string* organization) {
+  Filesystem::GetOrganizationName(organization);
+}
+inline void GetApplicationName(std::string* application) {
+  Filesystem::GetApplicationName(application);
+}
+inline bool CreateFolder(const Pathname& path) {
+  return Filesystem::CreateFolder(path);
+}
+inline bool FinishPath(Pathname& path, bool create, const std::string& append) {
+  if (!append.empty())
+    path.AppendFolder(append);
+  return !create || CreateFolder(path);
+}
+// Note: this method uses the convention of <temp>/<appname> for the temporary
+// folder.  Filesystem uses <temp>/<exename>.  We will be migrating exclusively
+// to <temp>/<orgname>/<appname> eventually.  Since these are temp folders,
+// it's probably ok to orphan them during the transition.
+inline bool GetTemporaryFolder(Pathname& path, bool create,
+                               const std::string& append) {
+  std::string application_name;
+  Filesystem::GetApplicationName(&application_name);
+  ASSERT(!application_name.empty());
+  return Filesystem::GetTemporaryFolder(path, create, &application_name)
+         && FinishPath(path, create, append);
+}
+inline bool GetAppDataFolder(Pathname& path, bool create,
+                             const std::string& append) {
+  ASSERT(!create); // TODO: Support create flag on Filesystem::GetAppDataFolder.
+  return Filesystem::GetAppDataFolder(&path, true)
+         && FinishPath(path, create, append);
+}
+inline bool CleanupTemporaryFolder() {
+  Pathname path;
+  if (!GetTemporaryFolder(path, false, ""))
+    return false;
+  if (Filesystem::IsAbsent(path))
+    return true;
+  if (!Filesystem::IsTemporaryPath(path)) {
+    ASSERT(false);
+    return false;
+  }
+  return Filesystem::DeleteFolderContents(path);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif // TALK_BASE_PATHUTILS_H__
diff --git a/talk/base/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
new file mode 100644
index 0000000..8a1bb5c
--- /dev/null
+++ b/talk/base/physicalsocketserver.cc
@@ -0,0 +1,1673 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if defined(_MSC_VER) && _MSC_VER < 1300
+#pragma warning(disable:4786)
+#endif
+
+#include <cassert>
+
+#ifdef POSIX
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <signal.h>
+#endif
+
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#undef SetPort
+#endif
+
+#include <algorithm>
+#include <map>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/nethelpers.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/timeutils.h"
+#include "talk/base/winping.h"
+#include "talk/base/win32socketinit.h"
+
+// stm: this will tell us if we are on OSX
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef POSIX
+#include <netinet/tcp.h>  // for TCP_NODELAY
+#define IP_MTU 14 // Until this is integrated from linux/in.h to netinet/in.h
+typedef void* SockOptArg;
+#endif  // POSIX
+
+#ifdef WIN32
+typedef char* SockOptArg;
+#endif
+
+namespace talk_base {
+
+// Standard MTUs, from RFC 1191
+const uint16 PACKET_MAXIMUMS[] = {
+  65535,    // Theoretical maximum, Hyperchannel
+  32000,    // Nothing
+  17914,    // 16Mb IBM Token Ring
+  8166,     // IEEE 802.4
+  //4464,   // IEEE 802.5 (4Mb max)
+  4352,     // FDDI
+  //2048,   // Wideband Network
+  2002,     // IEEE 802.5 (4Mb recommended)
+  //1536,   // Expermental Ethernet Networks
+  //1500,   // Ethernet, Point-to-Point (default)
+  1492,     // IEEE 802.3
+  1006,     // SLIP, ARPANET
+  //576,    // X.25 Networks
+  //544,    // DEC IP Portal
+  //512,    // NETBIOS
+  508,      // IEEE 802/Source-Rt Bridge, ARCNET
+  296,      // Point-to-Point (low delay)
+  68,       // Official minimum
+  0,        // End of list marker
+};
+
+static const int IP_HEADER_SIZE = 20u;
+static const int IPV6_HEADER_SIZE = 40u;
+static const int ICMP_HEADER_SIZE = 8u;
+static const int ICMP_PING_TIMEOUT_MILLIS = 10000u;
+
+class PhysicalSocket : public AsyncSocket, public sigslot::has_slots<> {
+ public:
+  PhysicalSocket(PhysicalSocketServer* ss, SOCKET s = INVALID_SOCKET)
+    : ss_(ss), s_(s), enabled_events_(0), error_(0),
+      state_((s == INVALID_SOCKET) ? CS_CLOSED : CS_CONNECTED),
+      resolver_(NULL) {
+#ifdef WIN32
+    // EnsureWinsockInit() ensures that winsock is initialized. The default
+    // version of this function doesn't do anything because winsock is
+    // initialized by constructor of a static object. If neccessary libjingle
+    // users can link it with a different version of this function by replacing
+    // win32socketinit.cc. See win32socketinit.cc for more details.
+    EnsureWinsockInit();
+#endif
+    if (s_ != INVALID_SOCKET) {
+      enabled_events_ = DE_READ | DE_WRITE;
+
+      int type = SOCK_STREAM;
+      socklen_t len = sizeof(type);
+      VERIFY(0 == getsockopt(s_, SOL_SOCKET, SO_TYPE, (SockOptArg)&type, &len));
+      udp_ = (SOCK_DGRAM == type);
+    }
+  }
+
+  virtual ~PhysicalSocket() {
+    Close();
+  }
+
+  // Creates the underlying OS socket (same as the "socket" function).
+  virtual bool Create(int family, int type) {
+    Close();
+    s_ = ::socket(family, type, 0);
+    udp_ = (SOCK_DGRAM == type);
+    UpdateLastError();
+    if (udp_)
+      enabled_events_ = DE_READ | DE_WRITE;
+    return s_ != INVALID_SOCKET;
+  }
+
+  SocketAddress GetLocalAddress() const {
+    sockaddr_storage addr_storage = {0};
+    socklen_t addrlen = sizeof(addr_storage);
+    sockaddr* addr = reinterpret_cast<sockaddr*>(&addr_storage);
+    int result = ::getsockname(s_, addr, &addrlen);
+    SocketAddress address;
+    if (result >= 0) {
+      SocketAddressFromSockAddrStorage(addr_storage, &address);
+    } else {
+      LOG(LS_WARNING) << "GetLocalAddress: unable to get local addr, socket="
+                      << s_;
+    }
+    return address;
+  }
+
+  SocketAddress GetRemoteAddress() const {
+    sockaddr_storage addr_storage = {0};
+    socklen_t addrlen = sizeof(addr_storage);
+    sockaddr* addr = reinterpret_cast<sockaddr*>(&addr_storage);
+    int result = ::getpeername(s_, addr, &addrlen);
+    SocketAddress address;
+    if (result >= 0) {
+      SocketAddressFromSockAddrStorage(addr_storage, &address);
+    } else {
+      LOG(LS_WARNING) << "GetRemoteAddress: unable to get remote addr, socket="
+                      << s_;
+    }
+    return address;
+  }
+
+  int Bind(const SocketAddress& bind_addr) {
+    sockaddr_storage addr_storage;
+    size_t len = bind_addr.ToSockAddrStorage(&addr_storage);
+    sockaddr* addr = reinterpret_cast<sockaddr*>(&addr_storage);
+    int err = ::bind(s_, addr, static_cast<int>(len));
+    UpdateLastError();
+#ifdef _DEBUG
+    if (0 == err) {
+      dbg_addr_ = "Bound @ ";
+      dbg_addr_.append(GetLocalAddress().ToString());
+    }
+#endif  // _DEBUG
+    return err;
+  }
+
+  int Connect(const SocketAddress& addr) {
+    // TODO: Implicit creation is required to reconnect...
+    // ...but should we make it more explicit?
+    if (state_ != CS_CLOSED) {
+      SetError(EALREADY);
+      return SOCKET_ERROR;
+    }
+    if (addr.IsUnresolved()) {
+      LOG(LS_VERBOSE) << "Resolving addr in PhysicalSocket::Connect";
+      resolver_ = new AsyncResolver();
+      resolver_->set_address(addr);
+      resolver_->SignalWorkDone.connect(this, &PhysicalSocket::OnResolveResult);
+      resolver_->Start();
+      state_ = CS_CONNECTING;
+      return 0;
+    }
+
+    return DoConnect(addr);
+  }
+
+  int DoConnect(const SocketAddress& connect_addr) {
+    if ((s_ == INVALID_SOCKET) &&
+        !Create(connect_addr.family(), SOCK_STREAM)) {
+      return SOCKET_ERROR;
+    }
+    sockaddr_storage addr_storage;
+    size_t len = connect_addr.ToSockAddrStorage(&addr_storage);
+    sockaddr* addr = reinterpret_cast<sockaddr*>(&addr_storage);
+    int err = ::connect(s_, addr, static_cast<int>(len));
+    UpdateLastError();
+    if (err == 0) {
+      state_ = CS_CONNECTED;
+    } else if (IsBlockingError(error_)) {
+      state_ = CS_CONNECTING;
+      enabled_events_ |= DE_CONNECT;
+    } else {
+      return SOCKET_ERROR;
+    }
+
+    enabled_events_ |= DE_READ | DE_WRITE;
+    return 0;
+  }
+
+  int GetError() const {
+    return error_;
+  }
+
+  void SetError(int error) {
+    error_ = error;
+  }
+
+  ConnState GetState() const {
+    return state_;
+  }
+
+  int GetOption(Option opt, int* value) {
+    int slevel;
+    int sopt;
+    if (TranslateOption(opt, &slevel, &sopt) == -1)
+      return -1;
+    socklen_t optlen = sizeof(*value);
+    int ret = ::getsockopt(s_, slevel, sopt, (SockOptArg)value, &optlen);
+    if (ret != -1 && opt == OPT_DONTFRAGMENT) {
+#ifdef LINUX
+      *value = (*value != IP_PMTUDISC_DONT) ? 1 : 0;
+#endif
+    }
+    return ret;
+  }
+
+  int SetOption(Option opt, int value) {
+    int slevel;
+    int sopt;
+    if (TranslateOption(opt, &slevel, &sopt) == -1)
+      return -1;
+    if (opt == OPT_DONTFRAGMENT) {
+#ifdef LINUX
+      value = (value) ? IP_PMTUDISC_DO : IP_PMTUDISC_DONT;
+#endif
+    }
+    return ::setsockopt(s_, slevel, sopt, (SockOptArg)&value, sizeof(value));
+  }
+
+  int Send(const void *pv, size_t cb) {
+    int sent = ::send(s_, reinterpret_cast<const char *>(pv), (int)cb,
+#ifdef LINUX
+        // Suppress SIGPIPE. Without this, attempting to send on a socket whose
+        // other end is closed will result in a SIGPIPE signal being raised to
+        // our process, which by default will terminate the process, which we
+        // don't want. By specifying this flag, we'll just get the error EPIPE
+        // instead and can handle the error gracefully.
+        MSG_NOSIGNAL
+#else
+        0
+#endif
+        );
+    UpdateLastError();
+    MaybeRemapSendError();
+    // We have seen minidumps where this may be false.
+    ASSERT(sent <= static_cast<int>(cb));
+    if ((sent < 0) && IsBlockingError(error_)) {
+      enabled_events_ |= DE_WRITE;
+    }
+    return sent;
+  }
+
+  int SendTo(const void* buffer, size_t length, const SocketAddress& addr) {
+    sockaddr_storage saddr;
+    size_t len = addr.ToSockAddrStorage(&saddr);
+    int sent = ::sendto(
+        s_, static_cast<const char *>(buffer), static_cast<int>(length),
+#ifdef LINUX
+        // Suppress SIGPIPE. See above for explanation.
+        MSG_NOSIGNAL,
+#else
+        0,
+#endif
+        reinterpret_cast<sockaddr*>(&saddr), static_cast<int>(len));
+    UpdateLastError();
+    MaybeRemapSendError();
+    // We have seen minidumps where this may be false.
+    ASSERT(sent <= static_cast<int>(length));
+    if ((sent < 0) && IsBlockingError(error_)) {
+      enabled_events_ |= DE_WRITE;
+    }
+    return sent;
+  }
+
+  int Recv(void* buffer, size_t length) {
+    int received = ::recv(s_, static_cast<char*>(buffer),
+                          static_cast<int>(length), 0);
+    if ((received == 0) && (length != 0)) {
+      // Note: on graceful shutdown, recv can return 0.  In this case, we
+      // pretend it is blocking, and then signal close, so that simplifying
+      // assumptions can be made about Recv.
+      LOG(LS_WARNING) << "EOF from socket; deferring close event";
+      // Must turn this back on so that the select() loop will notice the close
+      // event.
+      enabled_events_ |= DE_READ;
+      error_ = EWOULDBLOCK;
+      return SOCKET_ERROR;
+    }
+    UpdateLastError();
+    bool success = (received >= 0) || IsBlockingError(error_);
+    if (udp_ || success) {
+      enabled_events_ |= DE_READ;
+    }
+    if (!success) {
+      LOG_F(LS_VERBOSE) << "Error = " << error_;
+    }
+    return received;
+  }
+
+  int RecvFrom(void* buffer, size_t length, SocketAddress *out_addr) {
+    sockaddr_storage addr_storage;
+    socklen_t addr_len = sizeof(addr_storage);
+    sockaddr* addr = reinterpret_cast<sockaddr*>(&addr_storage);
+    int received = ::recvfrom(s_, static_cast<char*>(buffer),
+                              static_cast<int>(length), 0, addr, &addr_len);
+    UpdateLastError();
+    if ((received >= 0) && (out_addr != NULL))
+      SocketAddressFromSockAddrStorage(addr_storage, out_addr);
+    bool success = (received >= 0) || IsBlockingError(error_);
+    if (udp_ || success) {
+      enabled_events_ |= DE_READ;
+    }
+    if (!success) {
+      LOG_F(LS_VERBOSE) << "Error = " << error_;
+    }
+    return received;
+  }
+
+  int Listen(int backlog) {
+    int err = ::listen(s_, backlog);
+    UpdateLastError();
+    if (err == 0) {
+      state_ = CS_CONNECTING;
+      enabled_events_ |= DE_ACCEPT;
+#ifdef _DEBUG
+      dbg_addr_ = "Listening @ ";
+      dbg_addr_.append(GetLocalAddress().ToString());
+#endif  // _DEBUG
+    }
+    return err;
+  }
+
+  AsyncSocket* Accept(SocketAddress *out_addr) {
+    sockaddr_storage addr_storage;
+    socklen_t addr_len = sizeof(addr_storage);
+    sockaddr* addr = reinterpret_cast<sockaddr*>(&addr_storage);
+    SOCKET s = ::accept(s_, addr, &addr_len);
+    UpdateLastError();
+    if (s == INVALID_SOCKET)
+      return NULL;
+    enabled_events_ |= DE_ACCEPT;
+    if (out_addr != NULL)
+      SocketAddressFromSockAddrStorage(addr_storage, out_addr);
+    return ss_->WrapSocket(s);
+  }
+
+  int Close() {
+    if (s_ == INVALID_SOCKET)
+      return 0;
+    int err = ::closesocket(s_);
+    UpdateLastError();
+    s_ = INVALID_SOCKET;
+    state_ = CS_CLOSED;
+    enabled_events_ = 0;
+    if (resolver_) {
+      resolver_->Destroy(false);
+      resolver_ = NULL;
+    }
+    return err;
+  }
+
+  int EstimateMTU(uint16* mtu) {
+    SocketAddress addr = GetRemoteAddress();
+    if (addr.IsAny()) {
+      error_ = ENOTCONN;
+      return -1;
+    }
+
+#if defined(WIN32)
+    // Gets the interface MTU (TTL=1) for the interface used to reach |addr|.
+    WinPing ping;
+    if (!ping.IsValid()) {
+      error_ = EINVAL; // can't think of a better error ID
+      return -1;
+    }
+    int header_size = ICMP_HEADER_SIZE;
+    if (addr.family() == AF_INET6) {
+      header_size += IPV6_HEADER_SIZE;
+    } else if (addr.family() == AF_INET) {
+      header_size += IP_HEADER_SIZE;
+    }
+
+    for (int level = 0; PACKET_MAXIMUMS[level + 1] > 0; ++level) {
+      int32 size = PACKET_MAXIMUMS[level] - header_size;
+      WinPing::PingResult result = ping.Ping(addr.ipaddr(), size,
+                                             ICMP_PING_TIMEOUT_MILLIS,
+                                             1, false);
+      if (result == WinPing::PING_FAIL) {
+        error_ = EINVAL; // can't think of a better error ID
+        return -1;
+      } else if (result != WinPing::PING_TOO_LARGE) {
+        *mtu = PACKET_MAXIMUMS[level];
+        return 0;
+      }
+    }
+
+    ASSERT(false);
+    return -1;
+#elif defined(IOS) || defined(OSX)
+    // No simple way to do this on Mac OS X.
+    // SIOCGIFMTU would work if we knew which interface would be used, but
+    // figuring that out is pretty complicated. For now we'll return an error
+    // and let the caller pick a default MTU.
+    error_ = EINVAL;
+    return -1;
+#elif defined(LINUX) || defined(ANDROID)
+    // Gets the path MTU.
+    int value;
+    socklen_t vlen = sizeof(value);
+    int err = getsockopt(s_, IPPROTO_IP, IP_MTU, &value, &vlen);
+    if (err < 0) {
+      UpdateLastError();
+      return err;
+    }
+
+    ASSERT((0 <= value) && (value <= 65536));
+    *mtu = value;
+    return 0;
+#endif
+  }
+
+  SocketServer* socketserver() { return ss_; }
+
+ protected:
+  void OnResolveResult(SignalThread* thread) {
+    if (thread != resolver_) {
+      return;
+    }
+
+    int error = resolver_->error();
+    if (error == 0) {
+      error = DoConnect(resolver_->address());
+    } else {
+      Close();
+    }
+
+    if (error) {
+      error_ = error;
+      SignalCloseEvent(this, error_);
+    }
+  }
+
+  void UpdateLastError() {
+    error_ = LAST_SYSTEM_ERROR;
+  }
+
+  void MaybeRemapSendError() {
+#if defined(OSX)
+    // https://developer.apple.com/library/mac/documentation/Darwin/
+    // Reference/ManPages/man2/sendto.2.html
+    // ENOBUFS - The output queue for a network interface is full.
+    // This generally indicates that the interface has stopped sending,
+    // but may be caused by transient congestion.
+    if (error_ == ENOBUFS) {
+      error_ = EWOULDBLOCK;
+    }
+#endif
+  }
+
+  static int TranslateOption(Option opt, int* slevel, int* sopt) {
+    switch (opt) {
+      case OPT_DONTFRAGMENT:
+#ifdef WIN32
+        *slevel = IPPROTO_IP;
+        *sopt = IP_DONTFRAGMENT;
+        break;
+#elif defined(IOS) || defined(OSX) || defined(BSD)
+        LOG(LS_WARNING) << "Socket::OPT_DONTFRAGMENT not supported.";
+        return -1;
+#elif defined(POSIX)
+        *slevel = IPPROTO_IP;
+        *sopt = IP_MTU_DISCOVER;
+        break;
+#endif
+      case OPT_RCVBUF:
+        *slevel = SOL_SOCKET;
+        *sopt = SO_RCVBUF;
+        break;
+      case OPT_SNDBUF:
+        *slevel = SOL_SOCKET;
+        *sopt = SO_SNDBUF;
+        break;
+      case OPT_NODELAY:
+        *slevel = IPPROTO_TCP;
+        *sopt = TCP_NODELAY;
+        break;
+      default:
+        ASSERT(false);
+        return -1;
+    }
+    return 0;
+  }
+
+  PhysicalSocketServer* ss_;
+  SOCKET s_;
+  uint8 enabled_events_;
+  bool udp_;
+  int error_;
+  ConnState state_;
+  AsyncResolver* resolver_;
+
+#ifdef _DEBUG
+  std::string dbg_addr_;
+#endif  // _DEBUG;
+};
+
+#ifdef POSIX
+class EventDispatcher : public Dispatcher {
+ public:
+  EventDispatcher(PhysicalSocketServer* ss) : ss_(ss), fSignaled_(false) {
+    if (pipe(afd_) < 0)
+      LOG(LERROR) << "pipe failed";
+    ss_->Add(this);
+  }
+
+  virtual ~EventDispatcher() {
+    ss_->Remove(this);
+    close(afd_[0]);
+    close(afd_[1]);
+  }
+
+  virtual void Signal() {
+    CritScope cs(&crit_);
+    if (!fSignaled_) {
+      const uint8 b[1] = { 0 };
+      if (VERIFY(1 == write(afd_[1], b, sizeof(b)))) {
+        fSignaled_ = true;
+      }
+    }
+  }
+
+  virtual uint32 GetRequestedEvents() {
+    return DE_READ;
+  }
+
+  virtual void OnPreEvent(uint32 ff) {
+    // It is not possible to perfectly emulate an auto-resetting event with
+    // pipes.  This simulates it by resetting before the event is handled.
+
+    CritScope cs(&crit_);
+    if (fSignaled_) {
+      uint8 b[4];  // Allow for reading more than 1 byte, but expect 1.
+      VERIFY(1 == read(afd_[0], b, sizeof(b)));
+      fSignaled_ = false;
+    }
+  }
+
+  virtual void OnEvent(uint32 ff, int err) {
+    ASSERT(false);
+  }
+
+  virtual int GetDescriptor() {
+    return afd_[0];
+  }
+
+  virtual bool IsDescriptorClosed() {
+    return false;
+  }
+
+ private:
+  PhysicalSocketServer *ss_;
+  int afd_[2];
+  bool fSignaled_;
+  CriticalSection crit_;
+};
+
+// These two classes use the self-pipe trick to deliver POSIX signals to our
+// select loop. This is the only safe, reliable, cross-platform way to do
+// non-trivial things with a POSIX signal in an event-driven program (until
+// proper pselect() implementations become ubiquitous).
+
+class PosixSignalHandler {
+ public:
+  // POSIX only specifies 32 signals, but in principle the system might have
+  // more and the programmer might choose to use them, so we size our array
+  // for 128.
+  static const int kNumPosixSignals = 128;
+
+  // 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 {
+    ASSERT(signum < ARRAY_SIZE(received_signal_));
+    if (signum < ARRAY_SIZE(received_signal_)) {
+      return received_signal_[signum];
+    } else {
+      return false;
+    }
+  }
+
+  // Clears the given signal number.
+  void ClearSignal(int signum) {
+    ASSERT(signum < ARRAY_SIZE(received_signal_));
+    if (signum < ARRAY_SIZE(received_signal_)) {
+      received_signal_[signum] = false;
+    }
+  }
+
+  // Returns the file descriptor to monitor for signal events.
+  int GetDescriptor() const {
+    return afd_[0];
+  }
+
+  // This is called directly from our real signal handler, so it must be
+  // signal-handler-safe. That means it cannot assume anything about the
+  // user-level state of the process, since the handler could be executed at any
+  // time on any thread.
+  void OnPosixSignalReceived(int signum) {
+    if (signum >= ARRAY_SIZE(received_signal_)) {
+      // We don't have space in our array for this.
+      return;
+    }
+    // Set a flag saying we've seen this signal.
+    received_signal_[signum] = true;
+    // Notify application code that we got a signal.
+    const uint8 b[1] = { 0 };
+    if (-1 == write(afd_[1], b, sizeof(b))) {
+      // Nothing we can do here. If there's an error somehow then there's
+      // nothing we can safely do from a signal handler.
+      // No, we can't even safely log it.
+      // But, we still have to check the return value here. Otherwise,
+      // GCC 4.4.1 complains ignoring return value. Even (void) doesn't help.
+      return;
+    }
+  }
+
+ private:
+  PosixSignalHandler() {
+    if (pipe(afd_) < 0) {
+      LOG_ERR(LS_ERROR) << "pipe failed";
+      return;
+    }
+    if (fcntl(afd_[0], F_SETFL, O_NONBLOCK) < 0) {
+      LOG_ERR(LS_WARNING) << "fcntl #1 failed";
+    }
+    if (fcntl(afd_[1], F_SETFL, O_NONBLOCK) < 0) {
+      LOG_ERR(LS_WARNING) << "fcntl #2 failed";
+    }
+    memset(const_cast<void *>(static_cast<volatile void *>(received_signal_)),
+           0,
+           sizeof(received_signal_));
+  }
+
+  ~PosixSignalHandler() {
+    int fd1 = afd_[0];
+    int fd2 = afd_[1];
+    // We clobber the stored file descriptor numbers here or else in principle
+    // a signal that happens to be delivered during application termination
+    // could erroneously write a zero byte to an unrelated file handle in
+    // OnPosixSignalReceived() if some other file happens to be opened later
+    // during shutdown and happens to be given the same file descriptor number
+    // as our pipe had. Unfortunately even with this precaution there is still a
+    // race where that could occur if said signal happens to be handled
+    // concurrently with this code and happens to have already read the value of
+    // afd_[1] from memory before we clobber it, but that's unlikely.
+    afd_[0] = -1;
+    afd_[1] = -1;
+    close(fd1);
+    close(fd2);
+  }
+
+  int afd_[2];
+  // These are boolean flags that will be set in our signal handler and read
+  // and cleared from Wait(). There is a race involved in this, but it is
+  // benign. The signal handler sets the flag before signaling the pipe, so
+  // we'll never end up blocking in select() while a flag is still true.
+  // However, if two of the same signal arrive close to each other then it's
+  // possible that the second time the handler may set the flag while it's still
+  // true, meaning that signal will be missed. But the first occurrence of it
+  // will still be handled, so this isn't a problem.
+  // Volatile is not necessary here for correctness, but this data _is_ volatile
+  // so I've marked it as such.
+  volatile uint8 received_signal_[kNumPosixSignals];
+};
+
+class PosixSignalDispatcher : public Dispatcher {
+ public:
+  PosixSignalDispatcher(PhysicalSocketServer *owner) : owner_(owner) {
+    owner_->Add(this);
+  }
+
+  virtual ~PosixSignalDispatcher() {
+    owner_->Remove(this);
+  }
+
+  virtual uint32 GetRequestedEvents() {
+    return DE_READ;
+  }
+
+  virtual void OnPreEvent(uint32 ff) {
+    // Events might get grouped if signals come very fast, so we read out up to
+    // 16 bytes to make sure we keep the pipe empty.
+    uint8 b[16];
+    ssize_t ret = read(GetDescriptor(), b, sizeof(b));
+    if (ret < 0) {
+      LOG_ERR(LS_WARNING) << "Error in read()";
+    } else if (ret == 0) {
+      LOG(LS_WARNING) << "Should have read at least one byte";
+    }
+  }
+
+  virtual void OnEvent(uint32 ff, int err) {
+    for (int signum = 0; signum < PosixSignalHandler::kNumPosixSignals;
+         ++signum) {
+      if (PosixSignalHandler::Instance()->IsSignalSet(signum)) {
+        PosixSignalHandler::Instance()->ClearSignal(signum);
+        HandlerMap::iterator i = handlers_.find(signum);
+        if (i == handlers_.end()) {
+          // This can happen if a signal is delivered to our process at around
+          // the same time as we unset our handler for it. It is not an error
+          // condition, but it's unusual enough to be worth logging.
+          LOG(LS_INFO) << "Received signal with no handler: " << signum;
+        } else {
+          // Otherwise, execute our handler.
+          (*i->second)(signum);
+        }
+      }
+    }
+  }
+
+  virtual int GetDescriptor() {
+    return PosixSignalHandler::Instance()->GetDescriptor();
+  }
+
+  virtual bool IsDescriptorClosed() {
+    return false;
+  }
+
+  void SetHandler(int signum, void (*handler)(int)) {
+    handlers_[signum] = handler;
+  }
+
+  void ClearHandler(int signum) {
+    handlers_.erase(signum);
+  }
+
+  bool HasHandlers() {
+    return !handlers_.empty();
+  }
+
+ private:
+  typedef std::map<int, void (*)(int)> HandlerMap;
+
+  HandlerMap handlers_;
+  // Our owner.
+  PhysicalSocketServer *owner_;
+};
+
+class SocketDispatcher : public Dispatcher, public PhysicalSocket {
+ public:
+  explicit SocketDispatcher(PhysicalSocketServer *ss) : PhysicalSocket(ss) {
+  }
+  SocketDispatcher(SOCKET s, PhysicalSocketServer *ss) : PhysicalSocket(ss, s) {
+  }
+
+  virtual ~SocketDispatcher() {
+    Close();
+  }
+
+  bool Initialize() {
+    ss_->Add(this);
+    fcntl(s_, F_SETFL, fcntl(s_, F_GETFL, 0) | O_NONBLOCK);
+    return true;
+  }
+
+  virtual bool Create(int type) {
+    return Create(AF_INET, type);
+  }
+
+  virtual bool Create(int family, int type) {
+    // Change the socket to be non-blocking.
+    if (!PhysicalSocket::Create(family, type))
+      return false;
+
+    return Initialize();
+  }
+
+  virtual int GetDescriptor() {
+    return s_;
+  }
+
+  virtual bool IsDescriptorClosed() {
+    // We don't have a reliable way of distinguishing end-of-stream
+    // from readability.  So test on each readable call.  Is this
+    // inefficient?  Probably.
+    char ch;
+    ssize_t res = ::recv(s_, &ch, 1, MSG_PEEK);
+    if (res > 0) {
+      // Data available, so not closed.
+      return false;
+    } else if (res == 0) {
+      // EOF, so closed.
+      return true;
+    } else {  // error
+      switch (errno) {
+        // Returned if we've already closed s_.
+        case EBADF:
+        // Returned during ungraceful peer shutdown.
+        case ECONNRESET:
+          return true;
+        default:
+          // Assume that all other errors are just blocking errors, meaning the
+          // connection is still good but we just can't read from it right now.
+          // This should only happen when connecting (and at most once), because
+          // in all other cases this function is only called if the file
+          // descriptor is already known to be in the readable state. However,
+          // it's not necessary a problem if we spuriously interpret a
+          // "connection lost"-type error as a blocking error, because typically
+          // the next recv() will get EOF, so we'll still eventually notice that
+          // the socket is closed.
+          LOG_ERR(LS_WARNING) << "Assuming benign blocking error";
+          return false;
+      }
+    }
+  }
+
+  virtual uint32 GetRequestedEvents() {
+    return enabled_events_;
+  }
+
+  virtual void OnPreEvent(uint32 ff) {
+    if ((ff & DE_CONNECT) != 0)
+      state_ = CS_CONNECTED;
+    if ((ff & DE_CLOSE) != 0)
+      state_ = CS_CLOSED;
+  }
+
+  virtual void OnEvent(uint32 ff, int err) {
+    // Make sure we deliver connect/accept first. Otherwise, consumers may see
+    // something like a READ followed by a CONNECT, which would be odd.
+    if ((ff & DE_CONNECT) != 0) {
+      enabled_events_ &= ~DE_CONNECT;
+      SignalConnectEvent(this);
+    }
+    if ((ff & DE_ACCEPT) != 0) {
+      enabled_events_ &= ~DE_ACCEPT;
+      SignalReadEvent(this);
+    }
+    if ((ff & DE_READ) != 0) {
+      enabled_events_ &= ~DE_READ;
+      SignalReadEvent(this);
+    }
+    if ((ff & DE_WRITE) != 0) {
+      enabled_events_ &= ~DE_WRITE;
+      SignalWriteEvent(this);
+    }
+    if ((ff & DE_CLOSE) != 0) {
+      // The socket is now dead to us, so stop checking it.
+      enabled_events_ = 0;
+      SignalCloseEvent(this, err);
+    }
+  }
+
+  virtual int Close() {
+    if (s_ == INVALID_SOCKET)
+      return 0;
+
+    ss_->Remove(this);
+    return PhysicalSocket::Close();
+  }
+};
+
+class FileDispatcher: public Dispatcher, public AsyncFile {
+ public:
+  FileDispatcher(int fd, PhysicalSocketServer *ss) : ss_(ss), fd_(fd) {
+    set_readable(true);
+
+    ss_->Add(this);
+
+    fcntl(fd_, F_SETFL, fcntl(fd_, F_GETFL, 0) | O_NONBLOCK);
+  }
+
+  virtual ~FileDispatcher() {
+    ss_->Remove(this);
+  }
+
+  SocketServer* socketserver() { return ss_; }
+
+  virtual int GetDescriptor() {
+    return fd_;
+  }
+
+  virtual bool IsDescriptorClosed() {
+    return false;
+  }
+
+  virtual uint32 GetRequestedEvents() {
+    return flags_;
+  }
+
+  virtual void OnPreEvent(uint32 ff) {
+  }
+
+  virtual void OnEvent(uint32 ff, int err) {
+    if ((ff & DE_READ) != 0)
+      SignalReadEvent(this);
+    if ((ff & DE_WRITE) != 0)
+      SignalWriteEvent(this);
+    if ((ff & DE_CLOSE) != 0)
+      SignalCloseEvent(this, err);
+  }
+
+  virtual bool readable() {
+    return (flags_ & DE_READ) != 0;
+  }
+
+  virtual void set_readable(bool value) {
+    flags_ = value ? (flags_ | DE_READ) : (flags_ & ~DE_READ);
+  }
+
+  virtual bool writable() {
+    return (flags_ & DE_WRITE) != 0;
+  }
+
+  virtual void set_writable(bool value) {
+    flags_ = value ? (flags_ | DE_WRITE) : (flags_ & ~DE_WRITE);
+  }
+
+ private:
+  PhysicalSocketServer* ss_;
+  int fd_;
+  int flags_;
+};
+
+AsyncFile* PhysicalSocketServer::CreateFile(int fd) {
+  return new FileDispatcher(fd, this);
+}
+
+#endif // POSIX
+
+#ifdef WIN32
+static uint32 FlagsToEvents(uint32 events) {
+  uint32 ffFD = FD_CLOSE;
+  if (events & DE_READ)
+    ffFD |= FD_READ;
+  if (events & DE_WRITE)
+    ffFD |= FD_WRITE;
+  if (events & DE_CONNECT)
+    ffFD |= FD_CONNECT;
+  if (events & DE_ACCEPT)
+    ffFD |= FD_ACCEPT;
+  return ffFD;
+}
+
+class EventDispatcher : public Dispatcher {
+ public:
+  EventDispatcher(PhysicalSocketServer *ss) : ss_(ss) {
+    hev_ = WSACreateEvent();
+    if (hev_) {
+      ss_->Add(this);
+    }
+  }
+
+  ~EventDispatcher() {
+    if (hev_ != NULL) {
+      ss_->Remove(this);
+      WSACloseEvent(hev_);
+      hev_ = NULL;
+    }
+  }
+
+  virtual void Signal() {
+    if (hev_ != NULL)
+      WSASetEvent(hev_);
+  }
+
+  virtual uint32 GetRequestedEvents() {
+    return 0;
+  }
+
+  virtual void OnPreEvent(uint32 ff) {
+    WSAResetEvent(hev_);
+  }
+
+  virtual void OnEvent(uint32 ff, int err) {
+  }
+
+  virtual WSAEVENT GetWSAEvent() {
+    return hev_;
+  }
+
+  virtual SOCKET GetSocket() {
+    return INVALID_SOCKET;
+  }
+
+  virtual bool CheckSignalClose() { return false; }
+
+private:
+  PhysicalSocketServer* ss_;
+  WSAEVENT hev_;
+};
+
+class SocketDispatcher : public Dispatcher, public PhysicalSocket {
+ public:
+  static int next_id_;
+  int id_;
+  bool signal_close_;
+  int signal_err_;
+
+  SocketDispatcher(PhysicalSocketServer* ss)
+      : PhysicalSocket(ss),
+        id_(0),
+        signal_close_(false) {
+  }
+
+  SocketDispatcher(SOCKET s, PhysicalSocketServer* ss)
+      : PhysicalSocket(ss, s),
+        id_(0),
+        signal_close_(false) {
+  }
+
+  virtual ~SocketDispatcher() {
+    Close();
+  }
+
+  bool Initialize() {
+    ASSERT(s_ != INVALID_SOCKET);
+    // Must be a non-blocking
+    u_long argp = 1;
+    ioctlsocket(s_, FIONBIO, &argp);
+    ss_->Add(this);
+    return true;
+  }
+
+  virtual bool Create(int type) {
+    return Create(AF_INET, type);
+  }
+
+  virtual bool Create(int family, int type) {
+    // Create socket
+    if (!PhysicalSocket::Create(family, type))
+      return false;
+
+    if (!Initialize())
+      return false;
+
+    do { id_ = ++next_id_; } while (id_ == 0);
+    return true;
+  }
+
+  virtual int Close() {
+    if (s_ == INVALID_SOCKET)
+      return 0;
+
+    id_ = 0;
+    signal_close_ = false;
+    ss_->Remove(this);
+    return PhysicalSocket::Close();
+  }
+
+  virtual uint32 GetRequestedEvents() {
+    return enabled_events_;
+  }
+
+  virtual void OnPreEvent(uint32 ff) {
+    if ((ff & DE_CONNECT) != 0)
+      state_ = CS_CONNECTED;
+    // We set CS_CLOSED from CheckSignalClose.
+  }
+
+  virtual void OnEvent(uint32 ff, int err) {
+    int cache_id = id_;
+    // Make sure we deliver connect/accept first. Otherwise, consumers may see
+    // something like a READ followed by a CONNECT, which would be odd.
+    if (((ff & DE_CONNECT) != 0) && (id_ == cache_id)) {
+      if (ff != DE_CONNECT)
+        LOG(LS_VERBOSE) << "Signalled with DE_CONNECT: " << ff;
+      enabled_events_ &= ~DE_CONNECT;
+#ifdef _DEBUG
+      dbg_addr_ = "Connected @ ";
+      dbg_addr_.append(GetRemoteAddress().ToString());
+#endif  // _DEBUG
+      SignalConnectEvent(this);
+    }
+    if (((ff & DE_ACCEPT) != 0) && (id_ == cache_id)) {
+      enabled_events_ &= ~DE_ACCEPT;
+      SignalReadEvent(this);
+    }
+    if ((ff & DE_READ) != 0) {
+      enabled_events_ &= ~DE_READ;
+      SignalReadEvent(this);
+    }
+    if (((ff & DE_WRITE) != 0) && (id_ == cache_id)) {
+      enabled_events_ &= ~DE_WRITE;
+      SignalWriteEvent(this);
+    }
+    if (((ff & DE_CLOSE) != 0) && (id_ == cache_id)) {
+      signal_close_ = true;
+      signal_err_ = err;
+    }
+  }
+
+  virtual WSAEVENT GetWSAEvent() {
+    return WSA_INVALID_EVENT;
+  }
+
+  virtual SOCKET GetSocket() {
+    return s_;
+  }
+
+  virtual bool CheckSignalClose() {
+    if (!signal_close_)
+      return false;
+
+    char ch;
+    if (recv(s_, &ch, 1, MSG_PEEK) > 0)
+      return false;
+
+    state_ = CS_CLOSED;
+    signal_close_ = false;
+    SignalCloseEvent(this, signal_err_);
+    return true;
+  }
+};
+
+int SocketDispatcher::next_id_ = 0;
+
+#endif  // WIN32
+
+// Sets the value of a boolean value to false when signaled.
+class Signaler : public EventDispatcher {
+ public:
+  Signaler(PhysicalSocketServer* ss, bool* pf)
+      : EventDispatcher(ss), pf_(pf) {
+  }
+  virtual ~Signaler() { }
+
+  void OnEvent(uint32 ff, int err) {
+    if (pf_)
+      *pf_ = false;
+  }
+
+ private:
+  bool *pf_;
+};
+
+PhysicalSocketServer::PhysicalSocketServer()
+    : fWait_(false),
+      last_tick_tracked_(0),
+      last_tick_dispatch_count_(0) {
+  signal_wakeup_ = new Signaler(this, &fWait_);
+#ifdef WIN32
+  socket_ev_ = WSACreateEvent();
+#endif
+}
+
+PhysicalSocketServer::~PhysicalSocketServer() {
+#ifdef WIN32
+  WSACloseEvent(socket_ev_);
+#endif
+#ifdef POSIX
+  signal_dispatcher_.reset();
+#endif
+  delete signal_wakeup_;
+  ASSERT(dispatchers_.empty());
+}
+
+void PhysicalSocketServer::WakeUp() {
+  signal_wakeup_->Signal();
+}
+
+Socket* PhysicalSocketServer::CreateSocket(int type) {
+  return CreateSocket(AF_INET, type);
+}
+
+Socket* PhysicalSocketServer::CreateSocket(int family, int type) {
+  PhysicalSocket* socket = new PhysicalSocket(this);
+  if (socket->Create(family, type)) {
+    return socket;
+  } else {
+    delete socket;
+    return 0;
+  }
+}
+
+AsyncSocket* PhysicalSocketServer::CreateAsyncSocket(int type) {
+  return CreateAsyncSocket(AF_INET, type);
+}
+
+AsyncSocket* PhysicalSocketServer::CreateAsyncSocket(int family, int type) {
+  SocketDispatcher* dispatcher = new SocketDispatcher(this);
+  if (dispatcher->Create(family, type)) {
+    return dispatcher;
+  } else {
+    delete dispatcher;
+    return 0;
+  }
+}
+
+AsyncSocket* PhysicalSocketServer::WrapSocket(SOCKET s) {
+  SocketDispatcher* dispatcher = new SocketDispatcher(s, this);
+  if (dispatcher->Initialize()) {
+    return dispatcher;
+  } else {
+    delete dispatcher;
+    return 0;
+  }
+}
+
+void PhysicalSocketServer::Add(Dispatcher *pdispatcher) {
+  CritScope cs(&crit_);
+  // Prevent duplicates. This can cause dead dispatchers to stick around.
+  DispatcherList::iterator pos = std::find(dispatchers_.begin(),
+                                           dispatchers_.end(),
+                                           pdispatcher);
+  if (pos != dispatchers_.end())
+    return;
+  dispatchers_.push_back(pdispatcher);
+}
+
+void PhysicalSocketServer::Remove(Dispatcher *pdispatcher) {
+  CritScope cs(&crit_);
+  DispatcherList::iterator pos = std::find(dispatchers_.begin(),
+                                           dispatchers_.end(),
+                                           pdispatcher);
+  ASSERT(pos != dispatchers_.end());
+  size_t index = pos - dispatchers_.begin();
+  dispatchers_.erase(pos);
+  for (IteratorList::iterator it = iterators_.begin(); it != iterators_.end();
+       ++it) {
+    if (index < **it) {
+      --**it;
+    }
+  }
+}
+
+#ifdef POSIX
+bool PhysicalSocketServer::Wait(int cmsWait, bool process_io) {
+  // Calculate timing information
+
+  struct timeval *ptvWait = NULL;
+  struct timeval tvWait;
+  struct timeval tvStop;
+  if (cmsWait != kForever) {
+    // Calculate wait timeval
+    tvWait.tv_sec = cmsWait / 1000;
+    tvWait.tv_usec = (cmsWait % 1000) * 1000;
+    ptvWait = &tvWait;
+
+    // Calculate when to return in a timeval
+    gettimeofday(&tvStop, NULL);
+    tvStop.tv_sec += tvWait.tv_sec;
+    tvStop.tv_usec += tvWait.tv_usec;
+    if (tvStop.tv_usec >= 1000000) {
+      tvStop.tv_usec -= 1000000;
+      tvStop.tv_sec += 1;
+    }
+  }
+
+  // Zero all fd_sets. Don't need to do this inside the loop since
+  // select() zeros the descriptors not signaled
+
+  fd_set fdsRead;
+  FD_ZERO(&fdsRead);
+  fd_set fdsWrite;
+  FD_ZERO(&fdsWrite);
+
+  fWait_ = true;
+
+  while (fWait_) {
+    int fdmax = -1;
+    {
+      CritScope cr(&crit_);
+      for (size_t i = 0; i < dispatchers_.size(); ++i) {
+        // Query dispatchers for read and write wait state
+        Dispatcher *pdispatcher = dispatchers_[i];
+        ASSERT(pdispatcher);
+        if (!process_io && (pdispatcher != signal_wakeup_))
+          continue;
+        int fd = pdispatcher->GetDescriptor();
+        if (fd > fdmax)
+          fdmax = fd;
+
+        uint32 ff = pdispatcher->GetRequestedEvents();
+        if (ff & (DE_READ | DE_ACCEPT))
+          FD_SET(fd, &fdsRead);
+        if (ff & (DE_WRITE | DE_CONNECT))
+          FD_SET(fd, &fdsWrite);
+      }
+    }
+
+    // Wait then call handlers as appropriate
+    // < 0 means error
+    // 0 means timeout
+    // > 0 means count of descriptors ready
+    int n = select(fdmax + 1, &fdsRead, &fdsWrite, NULL, ptvWait);
+
+    // If error, return error.
+    if (n < 0) {
+      if (errno != EINTR) {
+        LOG_E(LS_ERROR, EN, errno) << "select";
+        return false;
+      }
+      // Else ignore the error and keep going. If this EINTR was for one of the
+      // signals managed by this PhysicalSocketServer, the
+      // PosixSignalDeliveryDispatcher will be in the signaled state in the next
+      // iteration.
+    } else if (n == 0) {
+      // If timeout, return success
+      return true;
+    } else {
+      // We have signaled descriptors
+      CritScope cr(&crit_);
+      for (size_t i = 0; i < dispatchers_.size(); ++i) {
+        Dispatcher *pdispatcher = dispatchers_[i];
+        int fd = pdispatcher->GetDescriptor();
+        uint32 ff = 0;
+        int errcode = 0;
+
+        // Reap any error code, which can be signaled through reads or writes.
+        // TODO: Should we set errcode if getsockopt fails?
+        if (FD_ISSET(fd, &fdsRead) || FD_ISSET(fd, &fdsWrite)) {
+          socklen_t len = sizeof(errcode);
+          ::getsockopt(fd, SOL_SOCKET, SO_ERROR, &errcode, &len);
+        }
+
+        // Check readable descriptors. If we're waiting on an accept, signal
+        // that. Otherwise we're waiting for data, check to see if we're
+        // readable or really closed.
+        // TODO: Only peek at TCP descriptors.
+        if (FD_ISSET(fd, &fdsRead)) {
+          FD_CLR(fd, &fdsRead);
+          if (pdispatcher->GetRequestedEvents() & DE_ACCEPT) {
+            ff |= DE_ACCEPT;
+          } else if (errcode || pdispatcher->IsDescriptorClosed()) {
+            ff |= DE_CLOSE;
+          } else {
+            ff |= DE_READ;
+          }
+        }
+
+        // Check writable descriptors. If we're waiting on a connect, detect
+        // success versus failure by the reaped error code.
+        if (FD_ISSET(fd, &fdsWrite)) {
+          FD_CLR(fd, &fdsWrite);
+          if (pdispatcher->GetRequestedEvents() & DE_CONNECT) {
+            if (!errcode) {
+              ff |= DE_CONNECT;
+            } else {
+              ff |= DE_CLOSE;
+            }
+          } else {
+            ff |= DE_WRITE;
+          }
+        }
+
+        // Tell the descriptor about the event.
+        if (ff != 0) {
+          pdispatcher->OnPreEvent(ff);
+          pdispatcher->OnEvent(ff, errcode);
+        }
+      }
+    }
+
+    // Recalc the time remaining to wait. Doing it here means it doesn't get
+    // calced twice the first time through the loop
+    if (ptvWait) {
+      ptvWait->tv_sec = 0;
+      ptvWait->tv_usec = 0;
+      struct timeval tvT;
+      gettimeofday(&tvT, NULL);
+      if ((tvStop.tv_sec > tvT.tv_sec)
+          || ((tvStop.tv_sec == tvT.tv_sec)
+              && (tvStop.tv_usec > tvT.tv_usec))) {
+        ptvWait->tv_sec = tvStop.tv_sec - tvT.tv_sec;
+        ptvWait->tv_usec = tvStop.tv_usec - tvT.tv_usec;
+        if (ptvWait->tv_usec < 0) {
+          ASSERT(ptvWait->tv_sec > 0);
+          ptvWait->tv_usec += 1000000;
+          ptvWait->tv_sec -= 1;
+        }
+      }
+    }
+  }
+
+  return true;
+}
+
+static void GlobalSignalHandler(int signum) {
+  PosixSignalHandler::Instance()->OnPosixSignalReceived(signum);
+}
+
+bool PhysicalSocketServer::SetPosixSignalHandler(int signum,
+                                                 void (*handler)(int)) {
+  // If handler is SIG_IGN or SIG_DFL then clear our user-level handler,
+  // otherwise set one.
+  if (handler == SIG_IGN || handler == SIG_DFL) {
+    if (!InstallSignal(signum, handler)) {
+      return false;
+    }
+    if (signal_dispatcher_) {
+      signal_dispatcher_->ClearHandler(signum);
+      if (!signal_dispatcher_->HasHandlers()) {
+        signal_dispatcher_.reset();
+      }
+    }
+  } else {
+    if (!signal_dispatcher_) {
+      signal_dispatcher_.reset(new PosixSignalDispatcher(this));
+    }
+    signal_dispatcher_->SetHandler(signum, handler);
+    if (!InstallSignal(signum, &GlobalSignalHandler)) {
+      return false;
+    }
+  }
+  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.
+  if (sigemptyset(&act.sa_mask) != 0) {
+    LOG_ERR(LS_ERROR) << "Couldn't set mask";
+    return false;
+  }
+  act.sa_handler = handler;
+  // Use SA_RESTART so that our syscalls don't get EINTR, since we don't need it
+  // and it's a nuisance. Though some syscalls still return EINTR and there's no
+  // real standard for which ones. :(
+  act.sa_flags = SA_RESTART;
+  if (sigaction(signum, &act, NULL) != 0) {
+    LOG_ERR(LS_ERROR) << "Couldn't set sigaction";
+    return false;
+  }
+  return true;
+}
+#endif  // POSIX
+
+#ifdef WIN32
+bool PhysicalSocketServer::Wait(int cmsWait, bool process_io) {
+  int cmsTotal = cmsWait;
+  int cmsElapsed = 0;
+  uint32 msStart = Time();
+
+#if LOGGING
+  if (last_tick_dispatch_count_ == 0) {
+    last_tick_tracked_ = msStart;
+  }
+#endif
+
+  fWait_ = true;
+  while (fWait_) {
+    std::vector<WSAEVENT> events;
+    std::vector<Dispatcher *> event_owners;
+
+    events.push_back(socket_ev_);
+
+    {
+      CritScope cr(&crit_);
+      size_t i = 0;
+      iterators_.push_back(&i);
+      // Don't track dispatchers_.size(), because we want to pick up any new
+      // dispatchers that were added while processing the loop.
+      while (i < dispatchers_.size()) {
+        Dispatcher* disp = dispatchers_[i++];
+        if (!process_io && (disp != signal_wakeup_))
+          continue;
+        SOCKET s = disp->GetSocket();
+        if (disp->CheckSignalClose()) {
+          // We just signalled close, don't poll this socket
+        } else if (s != INVALID_SOCKET) {
+          WSAEventSelect(s,
+                         events[0],
+                         FlagsToEvents(disp->GetRequestedEvents()));
+        } else {
+          events.push_back(disp->GetWSAEvent());
+          event_owners.push_back(disp);
+        }
+      }
+      ASSERT(iterators_.back() == &i);
+      iterators_.pop_back();
+    }
+
+    // Which is shorter, the delay wait or the asked wait?
+
+    int cmsNext;
+    if (cmsWait == kForever) {
+      cmsNext = cmsWait;
+    } else {
+      cmsNext = _max(0, cmsTotal - cmsElapsed);
+    }
+
+    // Wait for one of the events to signal
+    DWORD dw = WSAWaitForMultipleEvents(static_cast<DWORD>(events.size()),
+                                        &events[0],
+                                        false,
+                                        cmsNext,
+                                        false);
+
+#if 0  // LOGGING
+    // we track this information purely for logging purposes.
+    last_tick_dispatch_count_++;
+    if (last_tick_dispatch_count_ >= 1000) {
+      int32 elapsed = TimeSince(last_tick_tracked_);
+      LOG(INFO) << "PhysicalSocketServer took " << elapsed
+                << "ms for 1000 events";
+
+      // If we get more than 1000 events in a second, we are spinning badly
+      // (normally it should take about 8-20 seconds).
+      ASSERT(elapsed > 1000);
+
+      last_tick_tracked_ = Time();
+      last_tick_dispatch_count_ = 0;
+    }
+#endif
+
+    if (dw == WSA_WAIT_FAILED) {
+      // Failed?
+      // TODO: need a better strategy than this!
+      int error = WSAGetLastError();
+      ASSERT(false);
+      return false;
+    } else if (dw == WSA_WAIT_TIMEOUT) {
+      // Timeout?
+      return true;
+    } else {
+      // Figure out which one it is and call it
+      CritScope cr(&crit_);
+      int index = dw - WSA_WAIT_EVENT_0;
+      if (index > 0) {
+        --index; // The first event is the socket event
+        event_owners[index]->OnPreEvent(0);
+        event_owners[index]->OnEvent(0, 0);
+      } else if (process_io) {
+        size_t i = 0, end = dispatchers_.size();
+        iterators_.push_back(&i);
+        iterators_.push_back(&end);  // Don't iterate over new dispatchers.
+        while (i < end) {
+          Dispatcher* disp = dispatchers_[i++];
+          SOCKET s = disp->GetSocket();
+          if (s == INVALID_SOCKET)
+            continue;
+
+          WSANETWORKEVENTS wsaEvents;
+          int err = WSAEnumNetworkEvents(s, events[0], &wsaEvents);
+          if (err == 0) {
+
+#if LOGGING
+            {
+              if ((wsaEvents.lNetworkEvents & FD_READ) &&
+                  wsaEvents.iErrorCode[FD_READ_BIT] != 0) {
+                LOG(WARNING) << "PhysicalSocketServer got FD_READ_BIT error "
+                             << wsaEvents.iErrorCode[FD_READ_BIT];
+              }
+              if ((wsaEvents.lNetworkEvents & FD_WRITE) &&
+                  wsaEvents.iErrorCode[FD_WRITE_BIT] != 0) {
+                LOG(WARNING) << "PhysicalSocketServer got FD_WRITE_BIT error "
+                             << wsaEvents.iErrorCode[FD_WRITE_BIT];
+              }
+              if ((wsaEvents.lNetworkEvents & FD_CONNECT) &&
+                  wsaEvents.iErrorCode[FD_CONNECT_BIT] != 0) {
+                LOG(WARNING) << "PhysicalSocketServer got FD_CONNECT_BIT error "
+                             << wsaEvents.iErrorCode[FD_CONNECT_BIT];
+              }
+              if ((wsaEvents.lNetworkEvents & FD_ACCEPT) &&
+                  wsaEvents.iErrorCode[FD_ACCEPT_BIT] != 0) {
+                LOG(WARNING) << "PhysicalSocketServer got FD_ACCEPT_BIT error "
+                             << wsaEvents.iErrorCode[FD_ACCEPT_BIT];
+              }
+              if ((wsaEvents.lNetworkEvents & FD_CLOSE) &&
+                  wsaEvents.iErrorCode[FD_CLOSE_BIT] != 0) {
+                LOG(WARNING) << "PhysicalSocketServer got FD_CLOSE_BIT error "
+                             << wsaEvents.iErrorCode[FD_CLOSE_BIT];
+              }
+            }
+#endif
+            uint32 ff = 0;
+            int errcode = 0;
+            if (wsaEvents.lNetworkEvents & FD_READ)
+              ff |= DE_READ;
+            if (wsaEvents.lNetworkEvents & FD_WRITE)
+              ff |= DE_WRITE;
+            if (wsaEvents.lNetworkEvents & FD_CONNECT) {
+              if (wsaEvents.iErrorCode[FD_CONNECT_BIT] == 0) {
+                ff |= DE_CONNECT;
+              } else {
+                ff |= DE_CLOSE;
+                errcode = wsaEvents.iErrorCode[FD_CONNECT_BIT];
+              }
+            }
+            if (wsaEvents.lNetworkEvents & FD_ACCEPT)
+              ff |= DE_ACCEPT;
+            if (wsaEvents.lNetworkEvents & FD_CLOSE) {
+              ff |= DE_CLOSE;
+              errcode = wsaEvents.iErrorCode[FD_CLOSE_BIT];
+            }
+            if (ff != 0) {
+              disp->OnPreEvent(ff);
+              disp->OnEvent(ff, errcode);
+            }
+          }
+        }
+        ASSERT(iterators_.back() == &end);
+        iterators_.pop_back();
+        ASSERT(iterators_.back() == &i);
+        iterators_.pop_back();
+      }
+
+      // Reset the network event until new activity occurs
+      WSAResetEvent(socket_ev_);
+    }
+
+    // Break?
+    if (!fWait_)
+      break;
+    cmsElapsed = TimeSince(msStart);
+    if ((cmsWait != kForever) && (cmsElapsed >= cmsWait)) {
+       break;
+    }
+  }
+
+  // Done
+  return true;
+}
+#endif  // WIN32
+
+}  // namespace talk_base
diff --git a/talk/base/physicalsocketserver.h b/talk/base/physicalsocketserver.h
new file mode 100644
index 0000000..709f85a
--- /dev/null
+++ b/talk/base/physicalsocketserver.h
@@ -0,0 +1,139 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_PHYSICALSOCKETSERVER_H__
+#define TALK_BASE_PHYSICALSOCKETSERVER_H__
+
+#include <vector>
+
+#include "talk/base/asyncfile.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socketserver.h"
+#include "talk/base/criticalsection.h"
+
+#ifdef POSIX
+typedef int SOCKET;
+#endif // POSIX
+
+namespace talk_base {
+
+// Event constants for the Dispatcher class.
+enum DispatcherEvent {
+  DE_READ    = 0x0001,
+  DE_WRITE   = 0x0002,
+  DE_CONNECT = 0x0004,
+  DE_CLOSE   = 0x0008,
+  DE_ACCEPT  = 0x0010,
+};
+
+class Signaler;
+#ifdef POSIX
+class PosixSignalDispatcher;
+#endif
+
+class Dispatcher {
+ public:
+  virtual ~Dispatcher() {}
+  virtual uint32 GetRequestedEvents() = 0;
+  virtual void OnPreEvent(uint32 ff) = 0;
+  virtual void OnEvent(uint32 ff, int err) = 0;
+#ifdef WIN32
+  virtual WSAEVENT GetWSAEvent() = 0;
+  virtual SOCKET GetSocket() = 0;
+  virtual bool CheckSignalClose() = 0;
+#elif POSIX
+  virtual int GetDescriptor() = 0;
+  virtual bool IsDescriptorClosed() = 0;
+#endif
+};
+
+// A socket server that provides the real sockets of the underlying OS.
+class PhysicalSocketServer : public SocketServer {
+ public:
+  PhysicalSocketServer();
+  virtual ~PhysicalSocketServer();
+
+  // SocketFactory:
+  virtual Socket* CreateSocket(int type);
+  virtual Socket* CreateSocket(int family, int type);
+
+  virtual AsyncSocket* CreateAsyncSocket(int type);
+  virtual AsyncSocket* CreateAsyncSocket(int family, int type);
+
+  // Internal Factory for Accept
+  AsyncSocket* WrapSocket(SOCKET s);
+
+  // SocketServer:
+  virtual bool Wait(int cms, bool process_io);
+  virtual void WakeUp();
+
+  void Add(Dispatcher* dispatcher);
+  void Remove(Dispatcher* dispatcher);
+
+#ifdef POSIX
+  AsyncFile* CreateFile(int fd);
+
+  // Sets the function to be executed in response to the specified POSIX signal.
+  // The function is executed from inside Wait() using the "self-pipe trick"--
+  // regardless of which thread receives the signal--and hence can safely
+  // manipulate user-level data structures.
+  // "handler" may be SIG_IGN, SIG_DFL, or a user-specified function, just like
+  // with signal(2).
+  // Only one PhysicalSocketServer should have user-level signal handlers.
+  // Dispatching signals on multiple PhysicalSocketServers is not reliable.
+  // The signal mask is not modified. It is the caller's responsibily to
+  // maintain it as desired.
+  virtual bool SetPosixSignalHandler(int signum, void (*handler)(int));
+
+ protected:
+  Dispatcher* signal_dispatcher();
+#endif
+
+ private:
+  typedef std::vector<Dispatcher*> DispatcherList;
+  typedef std::vector<size_t*> IteratorList;
+
+#ifdef POSIX
+  static bool InstallSignal(int signum, void (*handler)(int));
+
+  scoped_ptr<PosixSignalDispatcher> signal_dispatcher_;
+#endif
+  DispatcherList dispatchers_;
+  IteratorList iterators_;
+  Signaler* signal_wakeup_;
+  CriticalSection crit_;
+  bool fWait_;
+  uint32 last_tick_tracked_;
+  int last_tick_dispatch_count_;
+#ifdef WIN32
+  WSAEVENT socket_ev_;
+#endif
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_PHYSICALSOCKETSERVER_H__
diff --git a/talk/base/physicalsocketserver_unittest.cc b/talk/base/physicalsocketserver_unittest.cc
new file mode 100644
index 0000000..b7f6848
--- /dev/null
+++ b/talk/base/physicalsocketserver_unittest.cc
@@ -0,0 +1,299 @@
+/*
+ * 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, TestConnectIPv4) {
+  SocketTest::TestConnectIPv4();
+}
+
+TEST_F(PhysicalSocketTest, TestConnectIPv6) {
+  SocketTest::TestConnectIPv6();
+}
+
+TEST_F(PhysicalSocketTest, TestConnectWithDnsLookupIPv4) {
+  SocketTest::TestConnectWithDnsLookupIPv4();
+}
+
+TEST_F(PhysicalSocketTest, TestConnectWithDnsLookupIPv6) {
+  SocketTest::TestConnectWithDnsLookupIPv6();
+}
+
+TEST_F(PhysicalSocketTest, TestConnectFailIPv4) {
+  SocketTest::TestConnectFailIPv4();
+}
+
+TEST_F(PhysicalSocketTest, TestConnectFailIPv6) {
+  SocketTest::TestConnectFailIPv6();
+}
+
+TEST_F(PhysicalSocketTest, TestConnectWithDnsLookupFailIPv4) {
+  SocketTest::TestConnectWithDnsLookupFailIPv4();
+}
+
+
+TEST_F(PhysicalSocketTest, TestConnectWithDnsLookupFailIPv6) {
+  SocketTest::TestConnectWithDnsLookupFailIPv6();
+}
+
+
+#ifdef OSX
+// This test crashes the OS X kernel on 10.6 (at bsd/netinet/tcp_subr.c:2118).
+TEST_F(PhysicalSocketTest, DISABLED_TestConnectWithClosedSocketIPv4) {
+#else
+TEST_F(PhysicalSocketTest, TestConnectWithClosedSocketIPv4) {
+#endif
+  SocketTest::TestConnectWithClosedSocketIPv4();
+}
+
+#ifdef OSX
+// This test crashes the OS X kernel on 10.6 (at bsd/netinet/tcp_subr.c:2118).
+TEST_F(PhysicalSocketTest, DISABLED_TestConnectWithClosedSocketIPv6) {
+#else
+TEST_F(PhysicalSocketTest, TestConnectWithClosedSocketIPv6) {
+#endif
+  SocketTest::TestConnectWithClosedSocketIPv6();
+}
+
+TEST_F(PhysicalSocketTest, TestConnectWhileNotClosedIPv4) {
+  SocketTest::TestConnectWhileNotClosedIPv4();
+}
+
+TEST_F(PhysicalSocketTest, TestConnectWhileNotClosedIPv6) {
+  SocketTest::TestConnectWhileNotClosedIPv6();
+}
+
+TEST_F(PhysicalSocketTest, TestServerCloseDuringConnectIPv4) {
+  SocketTest::TestServerCloseDuringConnectIPv4();
+}
+
+TEST_F(PhysicalSocketTest, TestServerCloseDuringConnectIPv6) {
+  SocketTest::TestServerCloseDuringConnectIPv6();
+}
+
+TEST_F(PhysicalSocketTest, TestClientCloseDuringConnectIPv4) {
+  SocketTest::TestClientCloseDuringConnectIPv4();
+}
+
+TEST_F(PhysicalSocketTest, TestClientCloseDuringConnectIPv6) {
+  SocketTest::TestClientCloseDuringConnectIPv6();
+}
+
+TEST_F(PhysicalSocketTest, TestServerCloseIPv4) {
+  SocketTest::TestServerCloseIPv4();
+}
+
+TEST_F(PhysicalSocketTest, TestServerCloseIPv6) {
+  SocketTest::TestServerCloseIPv6();
+}
+
+TEST_F(PhysicalSocketTest, TestCloseInClosedCallbackIPv4) {
+  SocketTest::TestCloseInClosedCallbackIPv4();
+}
+
+TEST_F(PhysicalSocketTest, TestCloseInClosedCallbackIPv6) {
+  SocketTest::TestCloseInClosedCallbackIPv6();
+}
+
+TEST_F(PhysicalSocketTest, TestSocketServerWaitIPv4) {
+  SocketTest::TestSocketServerWaitIPv4();
+}
+
+TEST_F(PhysicalSocketTest, TestSocketServerWaitIPv6) {
+  SocketTest::TestSocketServerWaitIPv6();
+}
+
+TEST_F(PhysicalSocketTest, TestTcpIPv4) {
+  SocketTest::TestTcpIPv4();
+}
+
+TEST_F(PhysicalSocketTest, TestTcpIPv6) {
+  SocketTest::TestTcpIPv6();
+}
+
+TEST_F(PhysicalSocketTest, TestUdpIPv4) {
+  SocketTest::TestUdpIPv4();
+}
+
+TEST_F(PhysicalSocketTest, TestUdpIPv6) {
+  SocketTest::TestUdpIPv6();
+}
+
+TEST_F(PhysicalSocketTest, TestUdpReadyToSendIPv4) {
+  SocketTest::TestUdpReadyToSendIPv4();
+}
+
+TEST_F(PhysicalSocketTest, TestUdpReadyToSendIPv6) {
+  SocketTest::TestUdpReadyToSendIPv6();
+}
+
+TEST_F(PhysicalSocketTest, TestGetSetOptionsIPv4) {
+  SocketTest::TestGetSetOptionsIPv4();
+}
+
+TEST_F(PhysicalSocketTest, TestGetSetOptionsIPv6) {
+  SocketTest::TestGetSetOptionsIPv6();
+}
+
+#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/profiler.cc b/talk/base/profiler.cc
new file mode 100644
index 0000000..9d0f32b
--- /dev/null
+++ b/talk/base/profiler.cc
@@ -0,0 +1,171 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/profiler.h"
+
+#include <math.h>
+
+#include "talk/base/timeutils.h"
+
+namespace {
+
+// When written to an ostream, FormattedTime chooses an appropriate scale and
+// suffix for a time value given in seconds.
+class FormattedTime {
+ public:
+  explicit FormattedTime(double t) : time_(t) {}
+  double time() const { return time_; }
+ private:
+  double time_;
+};
+
+std::ostream& operator<<(std::ostream& stream, const FormattedTime& time) {
+  if (time.time() < 1.0) {
+    stream << (time.time() * 1000.0) << "ms";
+  } else {
+    stream << time.time() << 's';
+  }
+  return stream;
+}
+
+}  // namespace
+
+namespace talk_base {
+
+ProfilerEvent::ProfilerEvent()
+    : total_time_(0.0),
+      mean_(0.0),
+      sum_of_squared_differences_(0.0),
+      start_count_(0),
+      event_count_(0) {
+}
+
+void ProfilerEvent::Start() {
+  if (start_count_ == 0) {
+    current_start_time_ = TimeNanos();
+  }
+  ++start_count_;
+}
+
+void ProfilerEvent::Stop() {
+  uint64 stop_time = TimeNanos();
+  --start_count_;
+  ASSERT(start_count_ >= 0);
+  if (start_count_ == 0) {
+    double elapsed = static_cast<double>(stop_time - current_start_time_) /
+        kNumNanosecsPerSec;
+    total_time_ += elapsed;
+    if (event_count_ == 0) {
+      minimum_ = maximum_ = elapsed;
+    } else {
+      minimum_ = _min(minimum_, elapsed);
+      maximum_ = _max(maximum_, elapsed);
+    }
+    // Online variance and mean algorithm: http://en.wikipedia.org/wiki/
+    // Algorithms_for_calculating_variance#Online_algorithm
+    ++event_count_;
+    double delta = elapsed - mean_;
+    mean_ = mean_ + delta / event_count_;
+    sum_of_squared_differences_ += delta * (elapsed - mean_);
+  }
+}
+
+double ProfilerEvent::standard_deviation() const {
+    if (event_count_ <= 1) return 0.0;
+    return sqrt(sum_of_squared_differences_ / (event_count_ - 1.0));
+}
+
+Profiler* Profiler::Instance() {
+  LIBJINGLE_DEFINE_STATIC_LOCAL(Profiler, instance, ());
+  return &instance;
+}
+
+void Profiler::StartEvent(const std::string& event_name) {
+  events_[event_name].Start();
+}
+
+void Profiler::StopEvent(const std::string& event_name) {
+  events_[event_name].Stop();
+}
+
+void Profiler::ReportToLog(const char* file, int line,
+                           LoggingSeverity severity_to_use,
+                           const std::string& event_prefix) {
+  if (!LogMessage::Loggable(severity_to_use)) {
+    return;
+  }
+  { // Output first line.
+    LogMessage msg(file, line, severity_to_use);
+    msg.stream() << "=== Profile report ";
+    if (event_prefix.empty()) {
+      msg.stream() << "(prefix: '" << event_prefix << "') ";
+    }
+    msg.stream() << "===";
+  }
+  typedef std::map<std::string, ProfilerEvent>::const_iterator iterator;
+  for (iterator it = events_.begin(); it != events_.end(); ++it) {
+    if (event_prefix.empty() || it->first.find(event_prefix) == 0) {
+      LogMessage(file, line, severity_to_use).stream()
+          << it->first << " count=" << it->second.event_count()
+          << " total=" << FormattedTime(it->second.total_time())
+          << " mean=" << FormattedTime(it->second.mean())
+          << " min=" << FormattedTime(it->second.minimum())
+          << " max=" << FormattedTime(it->second.maximum())
+          << " sd=" << it->second.standard_deviation();
+    }
+  }
+  LogMessage(file, line, severity_to_use).stream()
+      << "=== End profile report ===";
+}
+
+void Profiler::ReportAllToLog(const char* file, int line,
+                           LoggingSeverity severity_to_use) {
+  ReportToLog(file, line, severity_to_use, "");
+}
+
+const ProfilerEvent* Profiler::GetEvent(const std::string& event_name) const {
+  std::map<std::string, ProfilerEvent>::const_iterator it =
+      events_.find(event_name);
+  return (it == events_.end()) ? NULL : &it->second;
+}
+
+bool Profiler::Clear() {
+  bool result = true;
+  // Clear all events that aren't started.
+  std::map<std::string, ProfilerEvent>::iterator it = events_.begin();
+  while (it != events_.end()) {
+    if (it->second.is_started()) {
+      ++it;  // Can't clear started events.
+      result = false;
+    } else {
+      events_.erase(it++);
+    }
+  }
+  return result;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/profiler.h b/talk/base/profiler.h
new file mode 100644
index 0000000..1198b8e
--- /dev/null
+++ b/talk/base/profiler.h
@@ -0,0 +1,169 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 simple wall-clock profiler for instrumented code.
+// Example:
+//   void MyLongFunction() {
+//     PROFILE_F();  // Time the execution of this function.
+//     // Do something
+//     {  // Time just what is in this scope.
+//       PROFILE("My event");
+//       // Do something else
+//     }
+//   }
+// Another example:
+//   void StartAsyncProcess() {
+//     PROFILE_START("My event");
+//     DoSomethingAsyncAndThenCall(&Callback);
+//   }
+//   void Callback() {
+//     PROFILE_STOP("My async event");
+//     // Handle callback.
+//   }
+
+#ifndef TALK_BASE_PROFILER_H_
+#define TALK_BASE_PROFILER_H_
+
+#include <map>
+#include <string>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+
+// Profiling could be switched via a build flag, but for now, it's always on.
+#define ENABLE_PROFILING
+
+#ifdef ENABLE_PROFILING
+
+#define UV_HELPER2(x) _uv_ ## x
+#define UV_HELPER(x) UV_HELPER2(x)
+#define UNIQUE_VAR UV_HELPER(__LINE__)
+
+// Profiles the current scope.
+#define PROFILE(msg) talk_base::ProfilerScope UNIQUE_VAR(msg)
+// When placed at the start of a function, profiles the current function.
+#define PROFILE_F() PROFILE(__FUNCTION__)
+// Reports current timings to the log at severity |sev|.
+#define PROFILE_DUMP_ALL(sev) \
+  talk_base::Profiler::Instance()->ReportAllToLog(__FILE__, __LINE__, sev)
+// Reports current timings for all events whose names are prefixed by |prefix|
+// to the log at severity |sev|. Using a unique event name as |prefix| will
+// report only that event.
+#define PROFILE_DUMP(sev, prefix) \
+  talk_base::Profiler::Instance()->ReportToLog(__FILE__, __LINE__, sev, prefix)
+// Starts and stops a profile event. Useful when an event is not easily
+// captured within a scope (eg, an async call with a callback when done).
+#define PROFILE_START(msg) talk_base::Profiler::Instance()->StartEvent(msg)
+#define PROFILE_STOP(msg) talk_base::Profiler::Instance()->StopEvent(msg)
+// TODO(ryanpetrie): Consider adding PROFILE_DUMP_EVERY(sev, iterations)
+
+#undef UV_HELPER2
+#undef UV_HELPER
+#undef UNIQUE_VAR
+
+#else  // ENABLE_PROFILING
+
+#define PROFILE(msg) (void)0
+#define PROFILE_F() (void)0
+#define PROFILE_DUMP_ALL(sev) (void)0
+#define PROFILE_DUMP(sev, prefix) (void)0
+#define PROFILE_START(msg) (void)0
+#define PROFILE_STOP(msg) (void)0
+
+#endif  // ENABLE_PROFILING
+
+namespace talk_base {
+
+// Tracks information for one profiler event.
+class ProfilerEvent {
+ public:
+  ProfilerEvent();
+  void Start();
+  void Stop();
+  double standard_deviation() const;
+  double total_time() const { return total_time_; }
+  double mean() const { return mean_; }
+  double minimum() const { return minimum_; }
+  double maximum() const { return maximum_; }
+  int event_count() const { return event_count_; }
+  bool is_started() const { return start_count_ > 0; }
+
+ private:
+  uint64 current_start_time_;
+  double total_time_;
+  double mean_;
+  double sum_of_squared_differences_;
+  double minimum_;
+  double maximum_;
+  int start_count_;
+  int event_count_;
+};
+
+// Singleton that owns ProfilerEvents and reports results. Prefer to use
+// macros, defined above, rather than directly calling Profiler methods.
+class Profiler {
+ public:
+  void StartEvent(const std::string& event_name);
+  void StopEvent(const std::string& event_name);
+  void ReportToLog(const char* file, int line, LoggingSeverity severity_to_use,
+                   const std::string& event_prefix);
+  void ReportAllToLog(const char* file, int line,
+                      LoggingSeverity severity_to_use);
+  const ProfilerEvent* GetEvent(const std::string& event_name) const;
+  // Clears all _stopped_ events. Returns true if _all_ events were cleared.
+  bool Clear();
+
+  static Profiler* Instance();
+ private:
+  Profiler() {}
+
+  std::map<std::string, ProfilerEvent> events_;
+
+  DISALLOW_COPY_AND_ASSIGN(Profiler);
+};
+
+// Starts an event on construction and stops it on destruction.
+// Used by PROFILE macro.
+class ProfilerScope {
+ public:
+  explicit ProfilerScope(const std::string& event_name)
+      : event_name_(event_name) {
+    Profiler::Instance()->StartEvent(event_name_);
+  }
+  ~ProfilerScope() {
+    Profiler::Instance()->StopEvent(event_name_);
+  }
+ private:
+  std::string event_name_;
+
+  DISALLOW_COPY_AND_ASSIGN(ProfilerScope);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_PROFILER_H_
diff --git a/talk/base/profiler_unittest.cc b/talk/base/profiler_unittest.cc
new file mode 100644
index 0000000..f451e5f
--- /dev/null
+++ b/talk/base/profiler_unittest.cc
@@ -0,0 +1,126 @@
+/*
+ * libjingle
+ * Copyright 2004--2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/profiler.h"
+#include "talk/base/thread.h"
+
+namespace {
+
+const int kWaitMs = 250;
+const double kWaitSec = 0.250;
+const double kTolerance = 0.1;
+
+const char* TestFunc() {
+  PROFILE_F();
+  talk_base::Thread::SleepMs(kWaitMs);
+  return __FUNCTION__;
+}
+
+}  // namespace
+
+namespace talk_base {
+
+TEST(ProfilerTest, TestFunction) {
+  ASSERT_TRUE(Profiler::Instance()->Clear());
+  // Profile a long-running function.
+  const char* function_name = TestFunc();
+  const ProfilerEvent* event = Profiler::Instance()->GetEvent(function_name);
+  ASSERT_TRUE(event != NULL);
+  EXPECT_FALSE(event->is_started());
+  EXPECT_EQ(1, event->event_count());
+  EXPECT_NEAR(kWaitSec, event->mean(), kTolerance);
+  // Run it a second time.
+  TestFunc();
+  EXPECT_FALSE(event->is_started());
+  EXPECT_EQ(2, event->event_count());
+  EXPECT_NEAR(kWaitSec, event->mean(), kTolerance);
+  EXPECT_NEAR(kWaitSec * 2, event->total_time(), kTolerance * 2);
+  EXPECT_DOUBLE_EQ(event->mean(), event->total_time() / event->event_count());
+}
+
+TEST(ProfilerTest, TestScopedEvents) {
+  const std::string kEvent1Name = "Event 1";
+  const std::string kEvent2Name = "Event 2";
+  const int kEvent2WaitMs = 150;
+  const double kEvent2WaitSec = 0.150;
+  const ProfilerEvent* event1;
+  const ProfilerEvent* event2;
+  ASSERT_TRUE(Profiler::Instance()->Clear());
+  {  // Profile a scope.
+    PROFILE(kEvent1Name);
+    event1 = Profiler::Instance()->GetEvent(kEvent1Name);
+    ASSERT_TRUE(event1 != NULL);
+    EXPECT_TRUE(event1->is_started());
+    EXPECT_EQ(0, event1->event_count());
+    talk_base::Thread::SleepMs(kWaitMs);
+    EXPECT_TRUE(event1->is_started());
+  }
+  // Check the result.
+  EXPECT_FALSE(event1->is_started());
+  EXPECT_EQ(1, event1->event_count());
+  EXPECT_NEAR(kWaitSec, event1->mean(), kTolerance);
+  {  // Profile a second event.
+    PROFILE(kEvent2Name);
+    event2 = Profiler::Instance()->GetEvent(kEvent2Name);
+    ASSERT_TRUE(event2 != NULL);
+    EXPECT_FALSE(event1->is_started());
+    EXPECT_TRUE(event2->is_started());
+    talk_base::Thread::SleepMs(kEvent2WaitMs);
+  }
+  // Check the result.
+  EXPECT_FALSE(event2->is_started());
+  EXPECT_EQ(1, event2->event_count());
+  EXPECT_NEAR(kEvent2WaitSec, event2->mean(), kTolerance);
+  // Make sure event1 is unchanged.
+  EXPECT_FALSE(event1->is_started());
+  EXPECT_EQ(1, event1->event_count());
+  {  // Run another event 1.
+    PROFILE(kEvent1Name);
+    EXPECT_TRUE(event1->is_started());
+    talk_base::Thread::SleepMs(kWaitMs);
+  }
+  // Check the result.
+  EXPECT_FALSE(event1->is_started());
+  EXPECT_EQ(2, event1->event_count());
+  EXPECT_NEAR(kWaitSec, event1->mean(), kTolerance);
+  EXPECT_NEAR(kWaitSec * 2, event1->total_time(), kTolerance * 2);
+  EXPECT_DOUBLE_EQ(event1->mean(),
+                   event1->total_time() / event1->event_count());
+}
+
+TEST(ProfilerTest, Clear) {
+  ASSERT_TRUE(Profiler::Instance()->Clear());
+  PROFILE_START("event");
+  EXPECT_FALSE(Profiler::Instance()->Clear());
+  EXPECT_TRUE(Profiler::Instance()->GetEvent("event") != NULL);
+  PROFILE_STOP("event");
+  EXPECT_TRUE(Profiler::Instance()->Clear());
+  EXPECT_EQ(NULL, Profiler::Instance()->GetEvent("event"));
+}
+
+}  // namespace talk_base
diff --git a/talk/base/proxy_unittest.cc b/talk/base/proxy_unittest.cc
new file mode 100644
index 0000000..4ace292
--- /dev/null
+++ b/talk/base/proxy_unittest.cc
@@ -0,0 +1,152 @@
+/*
+ * 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(kSocksProxyIntAddr.family(), SOCK_STREAM);
+  talk_base::AsyncSocksProxySocket* proxy_socket =
+      new talk_base::AsyncSocksProxySocket(socket, kSocksProxyIntAddr,
+                                           "", talk_base::CryptString());
+  // TODO: IPv6-ize these tests when proxy supports IPv6.
+
+  talk_base::TestEchoServer server(Thread::Current(),
+                                   SocketAddress(INADDR_ANY, 0));
+
+  talk_base::AsyncTCPSocket* packet_socket = talk_base::AsyncTCPSocket::Create(
+      proxy_socket, SocketAddress(INADDR_ANY, 0), 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
new file mode 100644
index 0000000..d3c9983
--- /dev/null
+++ b/talk/base/proxydetect.cc
@@ -0,0 +1,1263 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/proxydetect.h"
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#include <shlobj.h>
+#endif  // WIN32
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef OSX
+#include <SystemConfiguration/SystemConfiguration.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <CoreServices/CoreServices.h>
+#include <Security/Security.h>
+#include "macconversion.h"
+#endif
+
+#include <map>
+
+#include "talk/base/fileutils.h"
+#include "talk/base/httpcommon.h"
+#include "talk/base/httpcommon-inl.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stringutils.h"
+
+#ifdef WIN32
+#define _TRY_WINHTTP 1
+#define _TRY_JSPROXY 0
+#define _TRY_WM_FINDPROXY 0
+#define _TRY_IE_LAN_SETTINGS 1
+#endif  // WIN32
+
+// For all platforms try Firefox.
+#define _TRY_FIREFOX 1
+
+// Use profiles.ini to find the correct profile for this user.
+// If not set, we'll just look for the default one.
+#define USE_FIREFOX_PROFILES_INI 1
+
+static const size_t kMaxLineLength = 1024;
+static const char kFirefoxPattern[] = "Firefox";
+static const char kInternetExplorerPattern[] = "MSIE";
+
+struct StringMap {
+ public:
+  void Add(const char * name, const char * value) { map_[name] = value; }
+  const std::string& Get(const char * name, const char * def = "") const {
+    std::map<std::string, std::string>::const_iterator it =
+        map_.find(name);
+    if (it != map_.end())
+      return it->second;
+    def_ = def;
+    return def_;
+  }
+  bool IsSet(const char * name) const {
+    return (map_.find(name) != map_.end());
+  }
+ private:
+  std::map<std::string, std::string> map_;
+  mutable std::string def_;
+};
+
+enum UserAgent {
+  UA_FIREFOX,
+  UA_INTERNETEXPLORER,
+  UA_OTHER,
+  UA_UNKNOWN
+};
+
+#if _TRY_WINHTTP
+//#include <winhttp.h>
+// Note: From winhttp.h
+
+const char WINHTTP[] = "winhttp";
+
+typedef LPVOID HINTERNET;
+
+typedef struct {
+  DWORD  dwAccessType;      // see WINHTTP_ACCESS_* types below
+  LPWSTR lpszProxy;         // proxy server list
+  LPWSTR lpszProxyBypass;   // proxy bypass list
+} WINHTTP_PROXY_INFO, * LPWINHTTP_PROXY_INFO;
+
+typedef struct {
+  DWORD   dwFlags;
+  DWORD   dwAutoDetectFlags;
+  LPCWSTR lpszAutoConfigUrl;
+  LPVOID  lpvReserved;
+  DWORD   dwReserved;
+  BOOL    fAutoLogonIfChallenged;
+} WINHTTP_AUTOPROXY_OPTIONS;
+
+typedef struct {
+  BOOL    fAutoDetect;
+  LPWSTR  lpszAutoConfigUrl;
+  LPWSTR  lpszProxy;
+  LPWSTR  lpszProxyBypass;
+} WINHTTP_CURRENT_USER_IE_PROXY_CONFIG;
+
+extern "C" {
+  typedef HINTERNET (WINAPI * pfnWinHttpOpen)
+      (
+          IN LPCWSTR pwszUserAgent,
+          IN DWORD   dwAccessType,
+          IN LPCWSTR pwszProxyName   OPTIONAL,
+          IN LPCWSTR pwszProxyBypass OPTIONAL,
+          IN DWORD   dwFlags
+          );
+  typedef BOOL (STDAPICALLTYPE * pfnWinHttpCloseHandle)
+      (
+          IN HINTERNET hInternet
+          );
+  typedef BOOL (STDAPICALLTYPE * pfnWinHttpGetProxyForUrl)
+      (
+          IN  HINTERNET                   hSession,
+          IN  LPCWSTR                     lpcwszUrl,
+          IN  WINHTTP_AUTOPROXY_OPTIONS * pAutoProxyOptions,
+          OUT WINHTTP_PROXY_INFO *        pProxyInfo
+          );
+  typedef BOOL (STDAPICALLTYPE * pfnWinHttpGetIEProxyConfig)
+      (
+          IN OUT WINHTTP_CURRENT_USER_IE_PROXY_CONFIG * pProxyConfig
+          );
+
+} // extern "C"
+
+#define WINHTTP_AUTOPROXY_AUTO_DETECT           0x00000001
+#define WINHTTP_AUTOPROXY_CONFIG_URL            0x00000002
+#define WINHTTP_AUTOPROXY_RUN_INPROCESS         0x00010000
+#define WINHTTP_AUTOPROXY_RUN_OUTPROCESS_ONLY   0x00020000
+#define WINHTTP_AUTO_DETECT_TYPE_DHCP           0x00000001
+#define WINHTTP_AUTO_DETECT_TYPE_DNS_A          0x00000002
+#define WINHTTP_ACCESS_TYPE_DEFAULT_PROXY               0
+#define WINHTTP_ACCESS_TYPE_NO_PROXY                    1
+#define WINHTTP_ACCESS_TYPE_NAMED_PROXY                 3
+#define WINHTTP_NO_PROXY_NAME     NULL
+#define WINHTTP_NO_PROXY_BYPASS   NULL
+
+#endif // _TRY_WINHTTP
+
+#if _TRY_JSPROXY
+extern "C" {
+  typedef BOOL (STDAPICALLTYPE * pfnInternetGetProxyInfo)
+      (
+          LPCSTR lpszUrl,
+          DWORD dwUrlLength,
+          LPSTR lpszUrlHostName,
+          DWORD dwUrlHostNameLength,
+          LPSTR * lplpszProxyHostName,
+          LPDWORD lpdwProxyHostNameLength
+          );
+} // extern "C"
+#endif // _TRY_JSPROXY
+
+#if _TRY_WM_FINDPROXY
+#include <comutil.h>
+#include <wmnetsourcecreator.h>
+#include <wmsinternaladminnetsource.h>
+#endif // _TRY_WM_FINDPROXY
+
+#if _TRY_IE_LAN_SETTINGS
+#include <wininet.h>
+#include <string>
+#endif // _TRY_IE_LAN_SETTINGS
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// Utility Functions
+//////////////////////////////////////////////////////////////////////
+
+#ifdef WIN32
+#ifdef _UNICODE
+
+typedef std::wstring tstring;
+std::string Utf8String(const tstring& str) { return ToUtf8(str); }
+
+#else  // !_UNICODE
+
+typedef std::string tstring;
+std::string Utf8String(const tstring& str) { return str; }
+
+#endif  // !_UNICODE
+#endif  // WIN32
+
+bool ProxyItemMatch(const Url<char>& url, char * item, size_t len) {
+  // hostname:443
+  if (char * port = ::strchr(item, ':')) {
+    *port++ = '\0';
+    if (url.port() != atol(port)) {
+      return false;
+    }
+  }
+
+  // A.B.C.D or A.B.C.D/24
+  int a, b, c, d, m;
+  int match = sscanf(item, "%d.%d.%d.%d/%d", &a, &b, &c, &d, &m);
+  if (match >= 4) {
+    uint32 ip = ((a & 0xFF) << 24) | ((b & 0xFF) << 16) | ((c & 0xFF) << 8) |
+        (d & 0xFF);
+    if ((match < 5) || (m > 32))
+      m = 32;
+    else if (m < 0)
+      m = 0;
+    uint32 mask = (m == 0) ? 0 : (~0UL) << (32 - m);
+    SocketAddress addr(url.host(), 0);
+    // TODO: Support IPv6 proxyitems. This code block is IPv4 only anyway.
+    return !addr.IsUnresolved() &&
+        ((addr.ipaddr().v4AddressAsHostOrderInteger() & mask) == (ip & mask));
+  }
+
+  // .foo.com
+  if (*item == '.') {
+    size_t hostlen = url.host().length();
+    return (hostlen > len)
+        && (stricmp(url.host().c_str() + (hostlen - len), item) == 0);
+  }
+
+  // localhost or www.*.com
+  if (!string_match(url.host().c_str(), item))
+    return false;
+
+  return true;
+}
+
+bool ProxyListMatch(const Url<char>& url, const std::string& proxy_list,
+                    char sep) {
+  const size_t BUFSIZE = 256;
+  char buffer[BUFSIZE];
+  const char* list = proxy_list.c_str();
+  while (*list) {
+    // Remove leading space
+    if (isspace(*list)) {
+      ++list;
+      continue;
+    }
+    // Break on separator
+    size_t len;
+    const char * start = list;
+    if (const char * end = ::strchr(list, sep)) {
+      len = (end - list);
+      list += len + 1;
+    } else {
+      len = strlen(list);
+      list += len;
+    }
+    // Remove trailing space
+    while ((len > 0) && isspace(start[len-1]))
+      --len;
+    // Check for oversized entry
+    if (len >= BUFSIZE)
+      continue;
+    memcpy(buffer, start, len);
+    buffer[len] = 0;
+    if (!ProxyItemMatch(url, buffer, len))
+      continue;
+    return true;
+  }
+  return false;
+}
+
+bool Better(ProxyType lhs, const ProxyType rhs) {
+  // PROXY_NONE, PROXY_HTTPS, PROXY_SOCKS5, PROXY_UNKNOWN
+  const int PROXY_VALUE[5] = { 0, 2, 3, 1 };
+  return (PROXY_VALUE[lhs] > PROXY_VALUE[rhs]);
+}
+
+bool ParseProxy(const std::string& saddress, ProxyInfo* proxy) {
+  const size_t kMaxAddressLength = 1024;
+  // Allow semicolon, space, or tab as an address separator
+  const char* const kAddressSeparator = " ;\t";
+
+  ProxyType ptype;
+  std::string host;
+  uint16 port;
+
+  const char* address = saddress.c_str();
+  while (*address) {
+    size_t len;
+    const char * start = address;
+    if (const char * sep = strchr(address, kAddressSeparator)) {
+      len = (sep - address);
+      address += len + 1;
+      while (*address != '\0' && ::strchr(kAddressSeparator, *address)) {
+        address += 1;
+      }
+    } else {
+      len = strlen(address);
+      address += len;
+    }
+
+    if (len > kMaxAddressLength - 1) {
+      LOG(LS_WARNING) << "Proxy address too long [" << start << "]";
+      continue;
+    }
+
+    char buffer[kMaxAddressLength];
+    memcpy(buffer, start, len);
+    buffer[len] = 0;
+
+    char * colon = ::strchr(buffer, ':');
+    if (!colon) {
+      LOG(LS_WARNING) << "Proxy address without port [" << buffer << "]";
+      continue;
+    }
+
+    *colon = 0;
+    char * endptr;
+    port = static_cast<uint16>(strtol(colon + 1, &endptr, 0));
+    if (*endptr != 0) {
+      LOG(LS_WARNING) << "Proxy address with invalid port [" << buffer << "]";
+      continue;
+    }
+
+    if (char * equals = ::strchr(buffer, '=')) {
+      *equals = 0;
+      host = equals + 1;
+      if (_stricmp(buffer, "socks") == 0) {
+        ptype = PROXY_SOCKS5;
+      } else if (_stricmp(buffer, "https") == 0) {
+        ptype = PROXY_HTTPS;
+      } else {
+        LOG(LS_WARNING) << "Proxy address with unknown protocol ["
+                        << buffer << "]";
+        ptype = PROXY_UNKNOWN;
+      }
+    } else {
+      host = buffer;
+      ptype = PROXY_UNKNOWN;
+    }
+
+    if (Better(ptype, proxy->type)) {
+      proxy->type = ptype;
+      proxy->address.SetIP(host);
+      proxy->address.SetPort(port);
+    }
+  }
+
+  return proxy->type != PROXY_NONE;
+}
+
+UserAgent GetAgent(const char* agent) {
+  if (agent) {
+    std::string agent_str(agent);
+    if (agent_str.find(kFirefoxPattern) != std::string::npos) {
+      return UA_FIREFOX;
+    } else if (agent_str.find(kInternetExplorerPattern) != std::string::npos) {
+      return UA_INTERNETEXPLORER;
+    } else if (agent_str.empty()) {
+      return UA_UNKNOWN;
+    }
+  }
+  return UA_OTHER;
+}
+
+bool EndsWith(const std::string& a, const std::string& b) {
+  if (b.size() > a.size()) {
+    return false;
+  }
+  int result = a.compare(a.size() - b.size(), b.size(), b);
+  return result == 0;
+}
+
+bool GetFirefoxProfilePath(Pathname* path) {
+#ifdef WIN32
+  wchar_t w_path[MAX_PATH];
+  if (SHGetFolderPath(0, CSIDL_APPDATA, 0, SHGFP_TYPE_CURRENT, w_path) !=
+      S_OK) {
+    LOG(LS_ERROR) << "SHGetFolderPath failed";
+    return false;
+  }
+  path->SetFolder(ToUtf8(w_path, wcslen(w_path)));
+  path->AppendFolder("Mozilla");
+  path->AppendFolder("Firefox");
+#elif OSX
+  FSRef fr;
+  if (0 != FSFindFolder(kUserDomain, kApplicationSupportFolderType,
+                        kCreateFolder, &fr)) {
+    LOG(LS_ERROR) << "FSFindFolder failed";
+    return false;
+  }
+  char buffer[NAME_MAX + 1];
+  if (0 != FSRefMakePath(&fr, reinterpret_cast<uint8*>(buffer),
+                         ARRAY_SIZE(buffer))) {
+    LOG(LS_ERROR) << "FSRefMakePath failed";
+    return false;
+  }
+  path->SetFolder(std::string(buffer));
+  path->AppendFolder("Firefox");
+#else
+  char* user_home = getenv("HOME");
+  if (user_home == NULL) {
+    return false;
+  }
+  path->SetFolder(std::string(user_home));
+  path->AppendFolder(".mozilla");
+  path->AppendFolder("firefox");
+#endif  // WIN32
+  return true;
+}
+
+bool GetDefaultFirefoxProfile(Pathname* profile_path) {
+  ASSERT(NULL != profile_path);
+  Pathname path;
+  if (!GetFirefoxProfilePath(&path)) {
+    return false;
+  }
+
+#if USE_FIREFOX_PROFILES_INI
+  // [Profile0]
+  // Name=default
+  // IsRelative=1
+  // Path=Profiles/2de53ejb.default
+  // Default=1
+
+  // Note: we are looking for the first entry with "Default=1", or the last
+  // entry in the file
+  path.SetFilename("profiles.ini");
+  FileStream* fs = Filesystem::OpenFile(path, "r");
+  if (!fs) {
+    return false;
+  }
+  Pathname candidate;
+  bool relative = true;
+  std::string line;
+  while (fs->ReadLine(&line) == SR_SUCCESS) {
+    if (line.length() == 0) {
+      continue;
+    }
+    if (line.at(0) == '[') {
+      relative = true;
+      candidate.clear();
+    } else if (line.find("IsRelative=") == 0 &&
+               line.length() >= 12) {
+      // TODO: The initial Linux public launch revealed a fairly
+      // high number of machines where IsRelative= did not have anything after
+      // it. Perhaps that is legal profiles.ini syntax?
+      relative = (line.at(11) != '0');
+    } else if (line.find("Path=") == 0 &&
+               line.length() >= 6) {
+      if (relative) {
+        candidate = path;
+      } else {
+        candidate.clear();
+      }
+      candidate.AppendFolder(line.substr(5));
+    } else if (line.find("Default=") == 0 &&
+               line.length() >= 9) {
+      if ((line.at(8) != '0') && !candidate.empty()) {
+        break;
+      }
+    }
+  }
+  fs->Close();
+  if (candidate.empty()) {
+    return false;
+  }
+  profile_path->SetPathname(candidate.pathname());
+
+#else // !USE_FIREFOX_PROFILES_INI
+  path.AppendFolder("Profiles");
+  DirectoryIterator* it = Filesystem::IterateDirectory();
+  it->Iterate(path);
+  std::string extension(".default");
+  while (!EndsWith(it->Name(), extension)) {
+    if (!it->Next()) {
+      return false;
+    }
+  }
+
+  profile_path->SetPathname(path);
+  profile->AppendFolder("Profiles");
+  profile->AppendFolder(it->Name());
+  delete it;
+
+#endif // !USE_FIREFOX_PROFILES_INI
+
+  return true;
+}
+
+bool ReadFirefoxPrefs(const Pathname& filename,
+                      const char * prefix,
+                      StringMap* settings) {
+  FileStream* fs = Filesystem::OpenFile(filename, "r");
+  if (!fs) {
+    LOG(LS_ERROR) << "Failed to open file: " << filename.pathname();
+    return false;
+  }
+
+  std::string line;
+  while (fs->ReadLine(&line) == SR_SUCCESS) {
+    size_t prefix_len = strlen(prefix);
+
+    // Skip blank lines and too long lines.
+    if ((line.length() == 0) || (line.length() > kMaxLineLength)
+        || (line.at(0) == '#') || line.compare(0, 2, "/*") == 0
+        || line.compare(0, 2, " *") == 0) {
+      continue;
+    }
+
+    char buffer[kMaxLineLength];
+    strcpyn(buffer, sizeof(buffer), line.c_str());
+    int nstart = 0, nend = 0, vstart = 0, vend = 0;
+    sscanf(buffer, "user_pref(\"%n%*[^\"]%n\", %n%*[^)]%n);",
+           &nstart, &nend, &vstart, &vend);
+    if (vend > 0) {
+      char* name = buffer + nstart;
+      name[nend - nstart] = 0;
+      if ((vend - vstart >= 2) && (buffer[vstart] == '"')) {
+        vstart += 1;
+        vend -= 1;
+      }
+      char* value = buffer + vstart;
+      value[vend - vstart] = 0;
+      if ((strncmp(name, prefix, prefix_len) == 0) && *value) {
+        settings->Add(name + prefix_len, value);
+      }
+    } else {
+      LOG_F(LS_WARNING) << "Unparsed pref [" << buffer << "]";
+    }
+  }
+  fs->Close();
+  return true;
+}
+
+bool GetFirefoxProxySettings(const char* url, ProxyInfo* proxy) {
+  Url<char> purl(url);
+  Pathname path;
+  bool success = false;
+  if (GetDefaultFirefoxProfile(&path)) {
+    StringMap settings;
+    path.SetFilename("prefs.js");
+    if (ReadFirefoxPrefs(path, "network.proxy.", &settings)) {
+      success = true;
+      proxy->bypass_list =
+          settings.Get("no_proxies_on", "localhost, 127.0.0.1");
+      if (settings.Get("type") == "1") {
+        // User has manually specified a proxy, try to figure out what
+        // type it is.
+        if (ProxyListMatch(purl, proxy->bypass_list.c_str(), ',')) {
+          // Our url is in the list of url's to bypass proxy.
+        } else if (settings.Get("share_proxy_settings") == "true") {
+          proxy->type = PROXY_UNKNOWN;
+          proxy->address.SetIP(settings.Get("http"));
+          proxy->address.SetPort(atoi(settings.Get("http_port").c_str()));
+        } else if (settings.IsSet("socks")) {
+          proxy->type = PROXY_SOCKS5;
+          proxy->address.SetIP(settings.Get("socks"));
+          proxy->address.SetPort(atoi(settings.Get("socks_port").c_str()));
+        } else if (settings.IsSet("ssl")) {
+          proxy->type = PROXY_HTTPS;
+          proxy->address.SetIP(settings.Get("ssl"));
+          proxy->address.SetPort(atoi(settings.Get("ssl_port").c_str()));
+        } else if (settings.IsSet("http")) {
+          proxy->type = PROXY_HTTPS;
+          proxy->address.SetIP(settings.Get("http"));
+          proxy->address.SetPort(atoi(settings.Get("http_port").c_str()));
+        }
+      } else if (settings.Get("type") == "2") {
+        // Browser is configured to get proxy settings from a given url.
+        proxy->autoconfig_url = settings.Get("autoconfig_url").c_str();
+      } else if (settings.Get("type") == "4") {
+        // Browser is configured to auto detect proxy config.
+        proxy->autodetect = true;
+      } else {
+        // No proxy set.
+      }
+    }
+  }
+  return success;
+}
+
+#ifdef WIN32  // Windows specific implementation for reading Internet
+              // Explorer proxy settings.
+
+void LogGetProxyFault() {
+  LOG_GLEM(LERROR, WINHTTP) << "WinHttpGetProxyForUrl faulted!!";
+}
+
+BOOL MyWinHttpGetProxyForUrl(pfnWinHttpGetProxyForUrl pWHGPFU,
+                             HINTERNET hWinHttp, LPCWSTR url,
+                             WINHTTP_AUTOPROXY_OPTIONS *options,
+                             WINHTTP_PROXY_INFO *info) {
+  // WinHttpGetProxyForUrl() can call plugins which can crash.
+  // In the case of McAfee scriptproxy.dll, it does crash in
+  // older versions. Try to catch crashes here and treat as an
+  // error.
+  BOOL success = FALSE;
+
+#if (_HAS_EXCEPTIONS == 0)
+  __try {
+    success = pWHGPFU(hWinHttp, url, options, info);
+  } __except(EXCEPTION_EXECUTE_HANDLER) {
+    // This is a separate function to avoid
+    // Visual C++ error 2712 when compiling with C++ EH
+    LogGetProxyFault();
+  }
+#else
+  success = pWHGPFU(hWinHttp, url, options, info);
+#endif  // (_HAS_EXCEPTIONS == 0)
+
+  return success;
+}
+
+bool IsDefaultBrowserFirefox() {
+  HKEY key;
+  LONG result = RegOpenKeyEx(HKEY_CLASSES_ROOT, L"http\\shell\\open\\command",
+                             0, KEY_READ, &key);
+  if (ERROR_SUCCESS != result)
+    return false;
+
+  wchar_t* value = NULL;
+  DWORD size, type;
+  result = RegQueryValueEx(key, L"", 0, &type, NULL, &size);
+  if (REG_SZ != type) {
+    result = ERROR_ACCESS_DENIED;  // Any error is fine
+  } else if (ERROR_SUCCESS == result) {
+    value = new wchar_t[size+1];
+    BYTE* buffer = reinterpret_cast<BYTE*>(value);
+    result = RegQueryValueEx(key, L"", 0, &type, buffer, &size);
+  }
+  RegCloseKey(key);
+
+  bool success = false;
+  if (ERROR_SUCCESS == result) {
+    value[size] = L'\0';
+    for (size_t i = 0; i < size; ++i) {
+      value[i] = tolowercase(value[i]);
+    }
+    success = (NULL != strstr(value, L"firefox.exe"));
+  }
+  delete [] value;
+  return success;
+}
+
+bool GetWinHttpProxySettings(const char* url, ProxyInfo* proxy) {
+  HMODULE winhttp_handle = LoadLibrary(L"winhttp.dll");
+  if (winhttp_handle == NULL) {
+    LOG(LS_ERROR) << "Failed to load winhttp.dll.";
+    return false;
+  }
+  WINHTTP_CURRENT_USER_IE_PROXY_CONFIG iecfg;
+  memset(&iecfg, 0, sizeof(iecfg));
+  Url<char> purl(url);
+  pfnWinHttpGetIEProxyConfig pWHGIEPC =
+      reinterpret_cast<pfnWinHttpGetIEProxyConfig>(
+          GetProcAddress(winhttp_handle,
+                         "WinHttpGetIEProxyConfigForCurrentUser"));
+  bool success = false;
+  if (pWHGIEPC && pWHGIEPC(&iecfg)) {
+    // We were read proxy config successfully.
+    success = true;
+    if (iecfg.fAutoDetect) {
+      proxy->autodetect = true;
+    }
+    if (iecfg.lpszAutoConfigUrl) {
+      proxy->autoconfig_url = ToUtf8(iecfg.lpszAutoConfigUrl);
+      GlobalFree(iecfg.lpszAutoConfigUrl);
+    }
+    if (iecfg.lpszProxyBypass) {
+      proxy->bypass_list = ToUtf8(iecfg.lpszProxyBypass);
+      GlobalFree(iecfg.lpszProxyBypass);
+    }
+    if (iecfg.lpszProxy) {
+      if (!ProxyListMatch(purl, proxy->bypass_list, ';')) {
+        ParseProxy(ToUtf8(iecfg.lpszProxy), proxy);
+      }
+      GlobalFree(iecfg.lpszProxy);
+    }
+  }
+  FreeLibrary(winhttp_handle);
+  return success;
+}
+
+// Uses the WinHTTP API to auto detect proxy for the given url. Firefox and IE
+// have slightly different option dialogs for proxy settings. In Firefox,
+// either a location of a proxy configuration file can be specified or auto
+// detection can be selected. In IE theese two options can be independently
+// selected. For the case where both options are selected (only IE) we try to
+// fetch the config file first, and if that fails we'll perform an auto
+// detection.
+//
+// Returns true if we successfully performed an auto detection not depending on
+// whether we found a proxy or not. Returns false on error.
+bool WinHttpAutoDetectProxyForUrl(const char* agent, const char* url,
+                                  ProxyInfo* proxy) {
+  Url<char> purl(url);
+  bool success = true;
+  HMODULE winhttp_handle = LoadLibrary(L"winhttp.dll");
+  if (winhttp_handle == NULL) {
+    LOG(LS_ERROR) << "Failed to load winhttp.dll.";
+    return false;
+  }
+  pfnWinHttpOpen pWHO =
+      reinterpret_cast<pfnWinHttpOpen>(GetProcAddress(winhttp_handle,
+                                                      "WinHttpOpen"));
+  pfnWinHttpCloseHandle pWHCH =
+      reinterpret_cast<pfnWinHttpCloseHandle>(
+          GetProcAddress(winhttp_handle, "WinHttpCloseHandle"));
+  pfnWinHttpGetProxyForUrl pWHGPFU =
+      reinterpret_cast<pfnWinHttpGetProxyForUrl>(
+          GetProcAddress(winhttp_handle, "WinHttpGetProxyForUrl"));
+  if (pWHO && pWHCH && pWHGPFU) {
+    if (HINTERNET hWinHttp = pWHO(ToUtf16(agent).c_str(),
+                                  WINHTTP_ACCESS_TYPE_NO_PROXY,
+                                  WINHTTP_NO_PROXY_NAME,
+                                  WINHTTP_NO_PROXY_BYPASS,
+                                  0)) {
+      BOOL result = FALSE;
+      WINHTTP_PROXY_INFO info;
+      memset(&info, 0, sizeof(info));
+      if (proxy->autodetect) {
+        // Use DHCP and DNS to try to find any proxy to use.
+        WINHTTP_AUTOPROXY_OPTIONS options;
+        memset(&options, 0, sizeof(options));
+        options.fAutoLogonIfChallenged = TRUE;
+
+        options.dwFlags |= WINHTTP_AUTOPROXY_AUTO_DETECT;
+        options.dwAutoDetectFlags |= WINHTTP_AUTO_DETECT_TYPE_DHCP
+            | WINHTTP_AUTO_DETECT_TYPE_DNS_A;
+        result = MyWinHttpGetProxyForUrl(
+            pWHGPFU, hWinHttp, ToUtf16(url).c_str(), &options, &info);
+      }
+      if (!result && !proxy->autoconfig_url.empty()) {
+        // We have the location of a proxy config file. Download it and
+        // execute it to find proxy settings for our url.
+        WINHTTP_AUTOPROXY_OPTIONS options;
+        memset(&options, 0, sizeof(options));
+        memset(&info, 0, sizeof(info));
+        options.fAutoLogonIfChallenged = TRUE;
+
+        std::wstring autoconfig_url16((ToUtf16)(proxy->autoconfig_url));
+        options.dwFlags |= WINHTTP_AUTOPROXY_CONFIG_URL;
+        options.lpszAutoConfigUrl = autoconfig_url16.c_str();
+
+        result = MyWinHttpGetProxyForUrl(
+            pWHGPFU, hWinHttp, ToUtf16(url).c_str(), &options, &info);
+      }
+      if (result) {
+        // Either the given auto config url was valid or auto
+        // detection found a proxy on this network.
+        if (info.lpszProxy) {
+          // TODO: Does this bypass list differ from the list
+          // retreived from GetWinHttpProxySettings earlier?
+          if (info.lpszProxyBypass) {
+            proxy->bypass_list = ToUtf8(info.lpszProxyBypass);
+            GlobalFree(info.lpszProxyBypass);
+          } else {
+            proxy->bypass_list.clear();
+          }
+          if (!ProxyListMatch(purl, proxy->bypass_list, ';')) {
+            // Found proxy for this URL. If parsing the address turns
+            // out ok then we are successful.
+            success = ParseProxy(ToUtf8(info.lpszProxy), proxy);
+          }
+          GlobalFree(info.lpszProxy);
+        }
+      } else {
+        // We could not find any proxy for this url.
+        LOG(LS_INFO) << "No proxy detected for " << url;
+      }
+      pWHCH(hWinHttp);
+    }
+  } else {
+    LOG(LS_ERROR) << "Failed loading WinHTTP functions.";
+    success = false;
+  }
+  FreeLibrary(winhttp_handle);
+  return success;
+}
+
+#if 0  // Below functions currently not used.
+
+bool GetJsProxySettings(const char* url, ProxyInfo* proxy) {
+  Url<char> purl(url);
+  bool success = false;
+
+  if (HMODULE hModJS = LoadLibrary(_T("jsproxy.dll"))) {
+    pfnInternetGetProxyInfo pIGPI =
+        reinterpret_cast<pfnInternetGetProxyInfo>(
+            GetProcAddress(hModJS, "InternetGetProxyInfo"));
+    if (pIGPI) {
+      char proxy[256], host[256];
+      memset(proxy, 0, sizeof(proxy));
+      char * ptr = proxy;
+      DWORD proxylen = sizeof(proxy);
+      std::string surl = Utf8String(url);
+      DWORD hostlen = _snprintf(host, sizeof(host), "http%s://%S",
+                                purl.secure() ? "s" : "", purl.server());
+      if (pIGPI(surl.data(), surl.size(), host, hostlen, &ptr, &proxylen)) {
+        LOG(INFO) << "Proxy: " << proxy;
+      } else {
+        LOG_GLE(INFO) << "InternetGetProxyInfo";
+      }
+    }
+    FreeLibrary(hModJS);
+  }
+  return success;
+}
+
+bool GetWmProxySettings(const char* url, ProxyInfo* proxy) {
+  Url<char> purl(url);
+  bool success = false;
+
+  INSNetSourceCreator * nsc = 0;
+  HRESULT hr = CoCreateInstance(CLSID_ClientNetManager, 0, CLSCTX_ALL,
+                                IID_INSNetSourceCreator, (LPVOID *) &nsc);
+  if (SUCCEEDED(hr)) {
+    if (SUCCEEDED(hr = nsc->Initialize())) {
+      VARIANT dispatch;
+      VariantInit(&dispatch);
+      if (SUCCEEDED(hr = nsc->GetNetSourceAdminInterface(L"http", &dispatch))) {
+        IWMSInternalAdminNetSource * ians = 0;
+        if (SUCCEEDED(hr = dispatch.pdispVal->QueryInterface(
+                IID_IWMSInternalAdminNetSource, (LPVOID *) &ians))) {
+          _bstr_t host(purl.server());
+          BSTR proxy = 0;
+          BOOL bProxyEnabled = FALSE;
+          DWORD port, context = 0;
+          if (SUCCEEDED(hr = ians->FindProxyForURL(
+                  L"http", host, &bProxyEnabled, &proxy, &port, &context))) {
+            success = true;
+            if (bProxyEnabled) {
+              _bstr_t sproxy = proxy;
+              proxy->ptype = PT_HTTPS;
+              proxy->host = sproxy;
+              proxy->port = port;
+            }
+          }
+          SysFreeString(proxy);
+          if (FAILED(hr = ians->ShutdownProxyContext(context))) {
+            LOG(LS_INFO) << "IWMSInternalAdminNetSource::ShutdownProxyContext"
+                         << "failed: " << hr;
+          }
+          ians->Release();
+        }
+      }
+      VariantClear(&dispatch);
+      if (FAILED(hr = nsc->Shutdown())) {
+        LOG(LS_INFO) << "INSNetSourceCreator::Shutdown failed: " << hr;
+      }
+    }
+    nsc->Release();
+  }
+  return success;
+}
+
+bool GetIePerConnectionProxySettings(const char* url, ProxyInfo* proxy) {
+  Url<char> purl(url);
+  bool success = false;
+
+  INTERNET_PER_CONN_OPTION_LIST list;
+  INTERNET_PER_CONN_OPTION options[3];
+  memset(&list, 0, sizeof(list));
+  memset(&options, 0, sizeof(options));
+
+  list.dwSize = sizeof(list);
+  list.dwOptionCount = 3;
+  list.pOptions = options;
+  options[0].dwOption = INTERNET_PER_CONN_FLAGS;
+  options[1].dwOption = INTERNET_PER_CONN_PROXY_SERVER;
+  options[2].dwOption = INTERNET_PER_CONN_PROXY_BYPASS;
+  DWORD dwSize = sizeof(list);
+
+  if (!InternetQueryOption(0, INTERNET_OPTION_PER_CONNECTION_OPTION, &list,
+                           &dwSize)) {
+    LOG(LS_INFO) << "InternetQueryOption failed: " << GetLastError();
+  } else if ((options[0].Value.dwValue & PROXY_TYPE_PROXY) != 0) {
+    success = true;
+    if (!ProxyListMatch(purl, nonnull(options[2].Value.pszValue), _T(';'))) {
+      ParseProxy(nonnull(options[1].Value.pszValue), proxy);
+    }
+  } else if ((options[0].Value.dwValue & PROXY_TYPE_DIRECT) != 0) {
+    success = true;
+  } else {
+    LOG(LS_INFO) << "unknown internet access type: "
+                 << options[0].Value.dwValue;
+  }
+  if (options[1].Value.pszValue) {
+    GlobalFree(options[1].Value.pszValue);
+  }
+  if (options[2].Value.pszValue) {
+    GlobalFree(options[2].Value.pszValue);
+  }
+  return success;
+}
+
+#endif  // 0
+
+// Uses the InternetQueryOption function to retrieve proxy settings
+// from the registry. This will only give us the 'static' settings,
+// ie, not any information about auto config etc.
+bool GetIeLanProxySettings(const char* url, ProxyInfo* proxy) {
+  Url<char> purl(url);
+  bool success = false;
+
+  wchar_t buffer[1024];
+  memset(buffer, 0, sizeof(buffer));
+  INTERNET_PROXY_INFO * info = reinterpret_cast<INTERNET_PROXY_INFO *>(buffer);
+  DWORD dwSize = sizeof(buffer);
+
+  if (!InternetQueryOption(0, INTERNET_OPTION_PROXY, info, &dwSize)) {
+    LOG(LS_INFO) << "InternetQueryOption failed: " << GetLastError();
+  } else if (info->dwAccessType == INTERNET_OPEN_TYPE_DIRECT) {
+    success = true;
+  } else if (info->dwAccessType == INTERNET_OPEN_TYPE_PROXY) {
+    success = true;
+    if (!ProxyListMatch(purl, nonnull(reinterpret_cast<const char*>(
+            info->lpszProxyBypass)), ' ')) {
+      ParseProxy(nonnull(reinterpret_cast<const char*>(info->lpszProxy)),
+                 proxy);
+    }
+  } else {
+    LOG(LS_INFO) << "unknown internet access type: " << info->dwAccessType;
+  }
+  return success;
+}
+
+bool GetIeProxySettings(const char* agent, const char* url, ProxyInfo* proxy) {
+  bool success = GetWinHttpProxySettings(url, proxy);
+  if (!success) {
+    // TODO: Should always call this if no proxy were detected by
+    // GetWinHttpProxySettings?
+    // WinHttp failed. Try using the InternetOptionQuery method instead.
+    return GetIeLanProxySettings(url, proxy);
+  }
+  return true;
+}
+
+#endif  // WIN32
+
+#ifdef OSX  // OSX specific implementation for reading system wide
+            // proxy settings.
+
+bool p_getProxyInfoForTypeFromDictWithKeys(ProxyInfo* proxy,
+                                           ProxyType type,
+                                           const CFDictionaryRef proxyDict,
+                                           const CFStringRef enabledKey,
+                                           const CFStringRef hostKey,
+                                           const CFStringRef portKey) {
+  // whether or not we set up the proxy info.
+  bool result = false;
+
+  // we use this as a scratch variable for determining if operations
+  // succeeded.
+  bool converted = false;
+
+  // the data we need to construct the SocketAddress for the proxy.
+  std::string hostname;
+  int port;
+
+  if ((proxyDict != NULL) &&
+      (CFGetTypeID(proxyDict) == CFDictionaryGetTypeID())) {
+    // CoreFoundation stuff that we'll have to get from
+    // the dictionaries and interpret or convert into more usable formats.
+    CFNumberRef enabledCFNum;
+    CFNumberRef portCFNum;
+    CFStringRef hostCFStr;
+
+    enabledCFNum = (CFNumberRef)CFDictionaryGetValue(proxyDict, enabledKey);
+
+    if (p_isCFNumberTrue(enabledCFNum)) {
+      // let's see if we can get the address and port.
+      hostCFStr = (CFStringRef)CFDictionaryGetValue(proxyDict, hostKey);
+      converted = p_convertHostCFStringRefToCPPString(hostCFStr, hostname);
+      if (converted) {
+        portCFNum = (CFNumberRef)CFDictionaryGetValue(proxyDict, portKey);
+        converted = p_convertCFNumberToInt(portCFNum, &port);
+        if (converted) {
+          // we have something enabled, with a hostname and a port.
+          // That's sufficient to set up the proxy info.
+          proxy->type = type;
+          proxy->address.SetIP(hostname);
+          proxy->address.SetPort(port);
+          result = true;
+        }
+      }
+    }
+  }
+
+  return result;
+}
+
+// Looks for proxy information in the given dictionary,
+// return true if it found sufficient information to define one,
+// false otherwise.  This is guaranteed to not change the values in proxy
+// unless a full-fledged proxy description was discovered in the dictionary.
+// However, at the present time this does not support username or password.
+// Checks first for a SOCKS proxy, then for HTTPS, then HTTP.
+bool GetMacProxySettingsFromDictionary(ProxyInfo* proxy,
+                                       const CFDictionaryRef proxyDict) {
+  // the function result.
+  bool gotProxy = false;
+
+
+  // first we see if there's a SOCKS proxy in place.
+  gotProxy = p_getProxyInfoForTypeFromDictWithKeys(proxy,
+                                                   PROXY_SOCKS5,
+                                                   proxyDict,
+                                                   kSCPropNetProxiesSOCKSEnable,
+                                                   kSCPropNetProxiesSOCKSProxy,
+                                                   kSCPropNetProxiesSOCKSPort);
+
+  if (!gotProxy) {
+    // okay, no SOCKS proxy, let's look for https.
+    gotProxy = p_getProxyInfoForTypeFromDictWithKeys(proxy,
+                                               PROXY_HTTPS,
+                                               proxyDict,
+                                               kSCPropNetProxiesHTTPSEnable,
+                                               kSCPropNetProxiesHTTPSProxy,
+                                               kSCPropNetProxiesHTTPSPort);
+    if (!gotProxy) {
+      // Finally, try HTTP proxy. Note that flute doesn't
+      // differentiate between HTTPS and HTTP, hence we are using the
+      // same flute type here, ie. PROXY_HTTPS.
+      gotProxy = p_getProxyInfoForTypeFromDictWithKeys(
+          proxy, PROXY_HTTPS, proxyDict, kSCPropNetProxiesHTTPEnable,
+          kSCPropNetProxiesHTTPProxy, kSCPropNetProxiesHTTPPort);
+    }
+  }
+  return gotProxy;
+}
+
+// TODO(hughv) Update keychain functions. They work on 10.8, but are depricated.
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+bool p_putPasswordInProxyInfo(ProxyInfo* proxy) {
+  bool result = true;  // by default we assume we're good.
+  // for all we know there isn't any password.  We'll set to false
+  // if we find a problem.
+
+  // Ask the keychain for an internet password search for the given protocol.
+  OSStatus oss = 0;
+  SecKeychainAttributeList attrList;
+  attrList.count = 3;
+  SecKeychainAttribute attributes[3];
+  attrList.attr = attributes;
+
+  attributes[0].tag = kSecProtocolItemAttr;
+  attributes[0].length = sizeof(SecProtocolType);
+  SecProtocolType protocol;
+  switch (proxy->type) {
+    case PROXY_HTTPS :
+      protocol = kSecProtocolTypeHTTPS;
+      break;
+    case PROXY_SOCKS5 :
+      protocol = kSecProtocolTypeSOCKS;
+      break;
+    default :
+      LOG(LS_ERROR) << "asked for proxy password for unknown proxy type.";
+      result = false;
+      break;
+  }
+  attributes[0].data = &protocol;
+
+  UInt32 port = proxy->address.port();
+  attributes[1].tag = kSecPortItemAttr;
+  attributes[1].length = sizeof(UInt32);
+  attributes[1].data = &port;
+
+  std::string ip = proxy->address.ipaddr().ToString();
+  attributes[2].tag = kSecServerItemAttr;
+  attributes[2].length = ip.length();
+  attributes[2].data = const_cast<char*>(ip.c_str());
+
+  if (result) {
+    LOG(LS_INFO) << "trying to get proxy username/password";
+    SecKeychainSearchRef sref;
+    oss = SecKeychainSearchCreateFromAttributes(NULL,
+                                                kSecInternetPasswordItemClass,
+                                                &attrList, &sref);
+    if (0 == oss) {
+      LOG(LS_INFO) << "SecKeychainSearchCreateFromAttributes was good";
+      // Get the first item, if there is one.
+      SecKeychainItemRef iref;
+      oss = SecKeychainSearchCopyNext(sref, &iref);
+      if (0 == oss) {
+        LOG(LS_INFO) << "...looks like we have the username/password data";
+        // If there is, get the username and the password.
+
+        SecKeychainAttributeInfo attribsToGet;
+        attribsToGet.count = 1;
+        UInt32 tag = kSecAccountItemAttr;
+        UInt32 format = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
+        void *data;
+        UInt32 length;
+        SecKeychainAttributeList *localList;
+
+        attribsToGet.tag = &tag;
+        attribsToGet.format = &format;
+        OSStatus copyres = SecKeychainItemCopyAttributesAndData(iref,
+                                                                &attribsToGet,
+                                                                NULL,
+                                                                &localList,
+                                                                &length,
+                                                                &data);
+        if (0 == copyres) {
+          LOG(LS_INFO) << "...and we can pull it out.";
+          // now, we know from experimentation (sadly not from docs)
+          // that the username is in the local attribute list,
+          // and the password in the data,
+          // both without null termination but with info on their length.
+          // grab the password from the data.
+          std::string password;
+          password.append(static_cast<const char*>(data), length);
+
+          // make the password into a CryptString
+          // huh, at the time of writing, you can't.
+          // so we'll skip that for now and come back to it later.
+
+          // now put the username in the proxy.
+          if (1 <= localList->attr->length) {
+            proxy->username.append(
+                static_cast<const char*>(localList->attr->data),
+                localList->attr->length);
+            LOG(LS_INFO) << "username is " << proxy->username;
+          } else {
+            LOG(LS_ERROR) << "got keychain entry with no username";
+            result = false;
+          }
+        } else {
+          LOG(LS_ERROR) << "couldn't copy info from keychain.";
+          result = false;
+        }
+        SecKeychainItemFreeAttributesAndData(localList, data);
+      } else if (errSecItemNotFound == oss) {
+        LOG(LS_INFO) << "...username/password info not found";
+      } else {
+        // oooh, neither 0 nor itemNotFound.
+        LOG(LS_ERROR) << "Couldn't get keychain information, error code" << oss;
+        result = false;
+      }
+    } else if (errSecItemNotFound == oss) {  // noop
+    } else {
+      // oooh, neither 0 nor itemNotFound.
+      LOG(LS_ERROR) << "Couldn't get keychain information, error code" << oss;
+      result = false;
+    }
+  }
+
+  return result;
+}
+
+bool GetMacProxySettings(ProxyInfo* proxy) {
+  // based on the Apple Technical Q&A QA1234
+  // http://developer.apple.com/qa/qa2001/qa1234.html
+  CFDictionaryRef proxyDict = SCDynamicStoreCopyProxies(NULL);
+  bool result = false;
+
+  if (proxyDict != NULL) {
+    // sending it off to another function makes it easier to unit test
+    // since we can make our own dictionary to hand to that function.
+    result = GetMacProxySettingsFromDictionary(proxy, proxyDict);
+
+    if (result) {
+      result = p_putPasswordInProxyInfo(proxy);
+    }
+
+    // We created the dictionary with something that had the
+    // word 'copy' in it, so we have to release it, according
+    // to the Carbon memory management standards.
+    CFRelease(proxyDict);
+  } else {
+    LOG(LS_ERROR) << "SCDynamicStoreCopyProxies failed";
+  }
+
+  return result;
+}
+#endif  // OSX
+
+bool AutoDetectProxySettings(const char* agent, const char* url,
+                             ProxyInfo* proxy) {
+#ifdef WIN32
+  return WinHttpAutoDetectProxyForUrl(agent, url, proxy);
+#else
+  LOG(LS_WARNING) << "Proxy auto-detection not implemented for this platform";
+  return false;
+#endif
+}
+
+bool GetSystemDefaultProxySettings(const char* agent, const char* url,
+                                   ProxyInfo* proxy) {
+#ifdef WIN32
+  return GetIeProxySettings(agent, url, proxy);
+#elif OSX
+  return GetMacProxySettings(proxy);
+#else
+  // TODO: Get System settings if browser is not firefox.
+  return GetFirefoxProxySettings(url, proxy);
+#endif
+}
+
+bool GetProxySettingsForUrl(const char* agent, const char* url,
+                            ProxyInfo* proxy, bool long_operation) {
+  UserAgent a = GetAgent(agent);
+  bool result;
+  switch (a) {
+    case UA_FIREFOX: {
+      result = GetFirefoxProxySettings(url, proxy);
+      break;
+    }
+#ifdef WIN32
+    case UA_INTERNETEXPLORER:
+      result = GetIeProxySettings(agent, url, proxy);
+      break;
+    case UA_UNKNOWN:
+      // Agent not defined, check default browser.
+      if (IsDefaultBrowserFirefox()) {
+        result = GetFirefoxProxySettings(url, proxy);
+      } else {
+        result = GetIeProxySettings(agent, url, proxy);
+      }
+      break;
+#endif  // WIN32
+    default:
+      result = GetSystemDefaultProxySettings(agent, url, proxy);
+      break;
+  }
+
+  // TODO: Consider using the 'long_operation' parameter to
+  // decide whether to do the auto detection.
+  if (result && (proxy->autodetect ||
+                 !proxy->autoconfig_url.empty())) {
+    // Use WinHTTP to auto detect proxy for us.
+    result = AutoDetectProxySettings(agent, url, proxy);
+    if (!result) {
+      // Either auto detection is not supported or we simply didn't
+      // find any proxy, reset type.
+      proxy->type = talk_base::PROXY_NONE;
+    }
+  }
+  return result;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/proxydetect.h b/talk/base/proxydetect.h
new file mode 100644
index 0000000..4218fbb
--- /dev/null
+++ b/talk/base/proxydetect.h
@@ -0,0 +1,48 @@
+/*
+ * libjingle
+ * Copyright 2007, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _PROXYDETECT_H_
+#define _PROXYDETECT_H_
+
+#include "talk/base/proxyinfo.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+namespace talk_base {
+// Auto-detect the proxy server.  Returns true if a proxy is configured,
+// although hostname may be empty if the proxy is not required for
+// the given URL.
+
+bool GetProxySettingsForUrl(const char* agent, const char* url,
+                            talk_base::ProxyInfo* proxy,
+                            bool long_operation = false);
+
+}  // namespace talk_base
+
+#endif  // _PROXYDETECT_H_
diff --git a/talk/base/proxydetect_unittest.cc b/talk/base/proxydetect_unittest.cc
new file mode 100644
index 0000000..685066d
--- /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/proxyinfo.cc b/talk/base/proxyinfo.cc
new file mode 100644
index 0000000..1d9c588
--- /dev/null
+++ b/talk/base/proxyinfo.cc
@@ -0,0 +1,37 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/proxyinfo.h"
+
+namespace talk_base {
+
+const char * ProxyToString(ProxyType proxy) {
+  const char * const PROXY_NAMES[] = { "none", "https", "socks5", "unknown" };
+  return PROXY_NAMES[proxy];
+}
+
+} // namespace talk_base
diff --git a/talk/base/proxyinfo.h b/talk/base/proxyinfo.h
new file mode 100644
index 0000000..9e28f1a
--- /dev/null
+++ b/talk/base/proxyinfo.h
@@ -0,0 +1,59 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_PROXYINFO_H__
+#define TALK_BASE_PROXYINFO_H__
+
+#include <string>
+#include "talk/base/socketaddress.h"
+#include "talk/base/cryptstring.h"
+
+namespace talk_base {
+
+enum ProxyType {
+  PROXY_NONE,
+  PROXY_HTTPS,
+  PROXY_SOCKS5,
+  PROXY_UNKNOWN
+};
+const char * ProxyToString(ProxyType proxy);
+
+struct ProxyInfo {
+  ProxyType type;
+  SocketAddress address;
+  std::string autoconfig_url;
+  bool autodetect;
+  std::string bypass_list;
+  std::string username;
+  CryptString password;
+
+  ProxyInfo() : type(PROXY_NONE), autodetect(false) { }
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_PROXYINFO_H__
diff --git a/talk/base/proxyserver.cc b/talk/base/proxyserver.cc
new file mode 100644
index 0000000..416a5c7
--- /dev/null
+++ b/talk/base/proxyserver.cc
@@ -0,0 +1,161 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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(int_addr.family(),
+                                                    SOCK_STREAM)) {
+  ASSERT(server_socket_.get() != NULL);
+  ASSERT(int_addr.family() == AF_INET || int_addr.family() == AF_INET6);
+  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 != NULL && socket == server_socket_.get());
+  AsyncSocket* int_socket = socket->Accept(NULL);
+  AsyncProxyServerSocket* wrapped_socket = WrapSocket(int_socket);
+  AsyncSocket* ext_socket = ext_factory_->CreateAsyncSocket(ext_ip_.family(),
+                                                            SOCK_STREAM);
+  if (ext_socket) {
+    ext_socket->Bind(ext_ip_);
+    bindings_.push_back(new ProxyBinding(wrapped_socket, ext_socket));
+  } else {
+    LOG(LS_ERROR) << "Unable to create external socket on proxy accept event";
+  }
+}
+
+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_.get() != NULL);
+  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) {
+  ASSERT(socket != NULL);
+  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.
+  ASSERT(socket != NULL);
+  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) {
+  ASSERT(socket != NULL);
+  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/ratelimiter.cc b/talk/base/ratelimiter.cc
new file mode 100644
index 0000000..6df7a18
--- /dev/null
+++ b/talk/base/ratelimiter.cc
@@ -0,0 +1,46 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/ratelimiter.h"
+
+namespace talk_base {
+
+bool RateLimiter::CanUse(size_t desired, double time) {
+  return ((time > period_end_ && desired <= max_per_period_) ||
+          (used_in_period_ + desired) <= max_per_period_);
+}
+
+void RateLimiter::Use(size_t used, double time) {
+  if (time > period_end_) {
+    period_start_ = time;
+    period_end_ = time + period_length_;
+    used_in_period_ = 0;
+  }
+  used_in_period_ += used;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/ratelimiter.h b/talk/base/ratelimiter.h
new file mode 100644
index 0000000..255afb4
--- /dev/null
+++ b/talk/base/ratelimiter.h
@@ -0,0 +1,80 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_RATELIMITER_H_
+#define TALK_BASE_RATELIMITER_H_
+
+#include <stdlib.h>
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+// Limits the rate of use to a certain maximum quantity per period of
+// time.  Use, for example, for simple bandwidth throttling.
+//
+// It's implemented like a diet plan: You have so many calories per
+// day.  If you hit the limit, you can't eat any more until the next
+// day.
+class RateLimiter {
+ public:
+  // For example, 100kb per second.
+  RateLimiter(size_t max, double period)
+      : max_per_period_(max),
+        period_length_(period),
+        used_in_period_(0),
+        period_start_(0.0),
+        period_end_(period) {
+  }
+  virtual ~RateLimiter() {}
+
+  // Returns true if if the desired quantity is available in the
+  // current period (< (max - used)).  Once the given time passes the
+  // end of the period, used is set to zero and more use is available.
+  bool CanUse(size_t desired, double time);
+  // Increment the quantity used this period.  If past the end of a
+  // period, a new period is started.
+  void Use(size_t used, double time);
+
+  size_t used_in_period() const {
+    return used_in_period_;
+  }
+
+  size_t max_per_period() const {
+    return max_per_period_;
+  }
+
+ private:
+  size_t max_per_period_;
+  double period_length_;
+  size_t used_in_period_;
+  double period_start_;
+  double period_end_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_RATELIMITER_H_
diff --git a/talk/base/ratelimiter_unittest.cc b/talk/base/ratelimiter_unittest.cc
new file mode 100644
index 0000000..3c1a1df
--- /dev/null
+++ b/talk/base/ratelimiter_unittest.cc
@@ -0,0 +1,76 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/ratelimiter.h"
+
+namespace talk_base {
+
+TEST(RateLimiterTest, TestCanUse) {
+  // Diet: Can eat 2,000 calories per day.
+  RateLimiter limiter = RateLimiter(2000, 1.0);
+
+  double monday = 1.0;
+  double tuesday = 2.0;
+  double thursday = 4.0;
+
+  EXPECT_TRUE(limiter.CanUse(0, monday));
+  EXPECT_TRUE(limiter.CanUse(1000, monday));
+  EXPECT_TRUE(limiter.CanUse(1999, monday));
+  EXPECT_TRUE(limiter.CanUse(2000, monday));
+  EXPECT_FALSE(limiter.CanUse(2001, monday));
+
+  limiter.Use(1000, monday);
+
+  EXPECT_TRUE(limiter.CanUse(0, monday));
+  EXPECT_TRUE(limiter.CanUse(999, monday));
+  EXPECT_TRUE(limiter.CanUse(1000, monday));
+  EXPECT_FALSE(limiter.CanUse(1001, monday));
+
+  limiter.Use(1000, monday);
+
+  EXPECT_TRUE(limiter.CanUse(0, monday));
+  EXPECT_FALSE(limiter.CanUse(1, monday));
+
+  EXPECT_TRUE(limiter.CanUse(0, tuesday));
+  EXPECT_TRUE(limiter.CanUse(1, tuesday));
+  EXPECT_TRUE(limiter.CanUse(1999, tuesday));
+  EXPECT_TRUE(limiter.CanUse(2000, tuesday));
+  EXPECT_FALSE(limiter.CanUse(2001, tuesday));
+
+  limiter.Use(1000, tuesday);
+
+  EXPECT_TRUE(limiter.CanUse(1000, tuesday));
+  EXPECT_FALSE(limiter.CanUse(1001, tuesday));
+
+  limiter.Use(1000, thursday);
+
+  EXPECT_TRUE(limiter.CanUse(1000, tuesday));
+  EXPECT_FALSE(limiter.CanUse(1001, tuesday));
+}
+
+}  // namespace talk_base
diff --git a/talk/base/ratetracker.cc b/talk/base/ratetracker.cc
new file mode 100644
index 0000000..383df93
--- /dev/null
+++ b/talk/base/ratetracker.cc
@@ -0,0 +1,80 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/ratetracker.h"
+#include "talk/base/timeutils.h"
+
+namespace talk_base {
+
+RateTracker::RateTracker()
+    : total_units_(0), units_second_(0),
+      last_units_second_time_(static_cast<uint32>(-1)),
+      last_units_second_calc_(0) {
+}
+
+size_t RateTracker::total_units() const {
+  return total_units_;
+}
+
+size_t RateTracker::units_second() {
+  // Snapshot units / second calculator. Determine how many seconds have
+  // elapsed since our last reference point. If over 1 second, establish
+  // a new reference point that is an integer number of seconds since the
+  // last one, and compute the units over that interval.
+  uint32 current_time = Time();
+  if (last_units_second_time_ != static_cast<uint32>(-1)) {
+    int delta = talk_base::TimeDiff(current_time, last_units_second_time_);
+    if (delta >= 1000) {
+      int fraction_time = delta % 1000;
+      int seconds = delta / 1000;
+      int fraction_units =
+          static_cast<int>(total_units_ - last_units_second_calc_) *
+              fraction_time / delta;
+      // Compute "units received during the interval" / "seconds in interval"
+      units_second_ =
+          (total_units_ - last_units_second_calc_ - fraction_units) / seconds;
+      last_units_second_time_ = current_time - fraction_time;
+      last_units_second_calc_ = total_units_ - fraction_units;
+    }
+  }
+  if (last_units_second_time_ == static_cast<uint32>(-1)) {
+    last_units_second_time_ = current_time;
+    last_units_second_calc_ = total_units_;
+  }
+
+  return units_second_;
+}
+
+void RateTracker::Update(size_t units) {
+  total_units_ += units;
+}
+
+uint32 RateTracker::Time() const {
+  return talk_base::Time();
+}
+
+}  // namespace talk_base
diff --git a/talk/base/ratetracker.h b/talk/base/ratetracker.h
new file mode 100644
index 0000000..28c7bb3
--- /dev/null
+++ b/talk/base/ratetracker.h
@@ -0,0 +1,59 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_RATETRACKER_H_
+#define TALK_BASE_RATETRACKER_H_
+
+#include <stdlib.h>
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+// Computes instantaneous units per second.
+class RateTracker {
+ public:
+  RateTracker();
+  virtual ~RateTracker() {}
+
+  size_t total_units() const;
+  size_t units_second();
+  void Update(size_t units);
+
+ protected:
+  // overrideable for tests
+  virtual uint32 Time() const;
+
+ private:
+  size_t total_units_;
+  size_t units_second_;
+  uint32 last_units_second_time_;
+  size_t last_units_second_calc_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_RATETRACKER_H_
diff --git a/talk/base/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..38cf147
--- /dev/null
+++ b/talk/base/refcount.h
@@ -0,0 +1,95 @@
+/*
+ * 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;
+ protected:
+  virtual ~RefCountInterface() {}
+};
+
+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:
+  virtual ~RefCountedObject() {
+  }
+
+  int ref_count_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_APP_BASE_REFCOUNT_H_
diff --git a/talk/base/referencecountedsingletonfactory.h b/talk/base/referencecountedsingletonfactory.h
new file mode 100644
index 0000000..7f90b04
--- /dev/null
+++ b/talk/base/referencecountedsingletonfactory.h
@@ -0,0 +1,174 @@
+/*
+ * 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_REFERENCECOUNTEDSINGLETONFACTORY_H_
+#define TALK_BASE_REFERENCECOUNTEDSINGLETONFACTORY_H_
+
+#include "talk/base/common.h"
+#include "talk/base/criticalsection.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+
+namespace talk_base {
+
+template <typename Interface> class rcsf_ptr;
+
+// A ReferenceCountedSingletonFactory is an object which owns another object,
+// and doles out the owned object to consumers in a reference-counted manner.
+// Thus, the factory owns at most one object of the desired kind, and
+// hands consumers a special pointer to it, through which they can access it.
+// When the consumers delete the pointer, the reference count goes down,
+// and if the reference count hits zero, the factory can throw the object
+// away.  If a consumer requests the pointer and the factory has none,
+// it can create one on the fly and pass it back.
+template <typename Interface>
+class ReferenceCountedSingletonFactory {
+  friend class rcsf_ptr<Interface>;
+ public:
+  ReferenceCountedSingletonFactory() : ref_count_(0) {}
+
+  virtual ~ReferenceCountedSingletonFactory() {
+    ASSERT(ref_count_ == 0);
+  }
+
+ protected:
+  // Must be implemented in a sub-class. The sub-class may choose whether or not
+  // to cache the instance across lifetimes by either reset()'ing or not
+  // reset()'ing the scoped_ptr in CleanupInstance().
+  virtual bool SetupInstance() = 0;
+  virtual void CleanupInstance() = 0;
+
+  scoped_ptr<Interface> instance_;
+
+ private:
+  Interface* GetInstance() {
+    talk_base::CritScope cs(&crit_);
+    if (ref_count_ == 0) {
+      if (!SetupInstance()) {
+        LOG(LS_VERBOSE) << "Failed to setup instance";
+        return NULL;
+      }
+      ASSERT(instance_.get() != NULL);
+    }
+    ++ref_count_;
+
+    LOG(LS_VERBOSE) << "Number of references: " << ref_count_;
+    return instance_.get();
+  }
+
+  void ReleaseInstance() {
+    talk_base::CritScope cs(&crit_);
+    ASSERT(ref_count_ > 0);
+    ASSERT(instance_.get() != NULL);
+    --ref_count_;
+    LOG(LS_VERBOSE) << "Number of references: " << ref_count_;
+    if (ref_count_ == 0) {
+      CleanupInstance();
+    }
+  }
+
+  CriticalSection crit_;
+  int ref_count_;
+
+  DISALLOW_COPY_AND_ASSIGN(ReferenceCountedSingletonFactory);
+};
+
+template <typename Interface>
+class rcsf_ptr {
+ public:
+  // Create a pointer that uses the factory to get the instance.
+  // This is lazy - it won't generate the instance until it is requested.
+  explicit rcsf_ptr(ReferenceCountedSingletonFactory<Interface>* factory)
+      : instance_(NULL),
+        factory_(factory) {
+  }
+
+  ~rcsf_ptr() {
+    release();
+  }
+
+  Interface& operator*() {
+    EnsureAcquired();
+    return *instance_;
+  }
+
+  Interface* operator->() {
+    EnsureAcquired();
+    return instance_;
+  }
+
+  // Gets the pointer, creating the singleton if necessary. May return NULL if
+  // creation failed.
+  Interface* get() {
+    Acquire();
+    return instance_;
+  }
+
+  // Set instance to NULL and tell the factory we aren't using the instance
+  // anymore.
+  void release() {
+    if (instance_) {
+      instance_ = NULL;
+      factory_->ReleaseInstance();
+    }
+  }
+
+  // Lets us know whether instance is valid or not right now.
+  // Even though attempts to use the instance will automatically create it, it
+  // is advisable to check this because creation can fail.
+  bool valid() const {
+    return instance_ != NULL;
+  }
+
+  // Returns the factory that this pointer is using.
+  ReferenceCountedSingletonFactory<Interface>* factory() const {
+    return factory_;
+  }
+
+ private:
+  void EnsureAcquired() {
+    Acquire();
+    ASSERT(instance_ != NULL);
+  }
+
+  void Acquire() {
+    // Since we're getting a singleton back, acquire is a noop if instance is
+    // already populated.
+    if (!instance_) {
+      instance_ = factory_->GetInstance();
+    }
+  }
+
+  Interface* instance_;
+  ReferenceCountedSingletonFactory<Interface>* factory_;
+
+  DISALLOW_IMPLICIT_CONSTRUCTORS(rcsf_ptr);
+};
+
+};  // namespace talk_base
+
+#endif  // TALK_BASE_REFERENCECOUNTEDSINGLETONFACTORY_H_
diff --git a/talk/base/referencecountedsingletonfactory_unittest.cc b/talk/base/referencecountedsingletonfactory_unittest.cc
new file mode 100644
index 0000000..3fc7fd2
--- /dev/null
+++ b/talk/base/referencecountedsingletonfactory_unittest.cc
@@ -0,0 +1,149 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/referencecountedsingletonfactory.h"
+
+namespace talk_base {
+
+class MyExistenceWatcher {
+ public:
+  MyExistenceWatcher() { create_called_ = true; }
+  ~MyExistenceWatcher() { delete_called_ = true; }
+
+  static bool create_called_;
+  static bool delete_called_;
+};
+
+bool MyExistenceWatcher::create_called_ = false;
+bool MyExistenceWatcher::delete_called_ = false;
+
+class TestReferenceCountedSingletonFactory :
+    public ReferenceCountedSingletonFactory<MyExistenceWatcher> {
+ protected:
+  virtual bool SetupInstance() {
+    instance_.reset(new MyExistenceWatcher());
+    return true;
+  }
+
+  virtual void CleanupInstance() {
+    instance_.reset();
+  }
+};
+
+static void DoCreateAndGoOutOfScope(
+    ReferenceCountedSingletonFactory<MyExistenceWatcher> *factory) {
+  rcsf_ptr<MyExistenceWatcher> ptr(factory);
+  ptr.get();
+  // and now ptr should go out of scope.
+}
+
+TEST(ReferenceCountedSingletonFactory, ZeroReferenceCountCausesDeletion) {
+  TestReferenceCountedSingletonFactory factory;
+  MyExistenceWatcher::delete_called_ = false;
+  DoCreateAndGoOutOfScope(&factory);
+  EXPECT_TRUE(MyExistenceWatcher::delete_called_);
+}
+
+TEST(ReferenceCountedSingletonFactory, NonZeroReferenceCountDoesNotDelete) {
+  TestReferenceCountedSingletonFactory factory;
+  rcsf_ptr<MyExistenceWatcher> ptr(&factory);
+  ptr.get();
+  MyExistenceWatcher::delete_called_ = false;
+  DoCreateAndGoOutOfScope(&factory);
+  EXPECT_FALSE(MyExistenceWatcher::delete_called_);
+}
+
+TEST(ReferenceCountedSingletonFactory, ReturnedPointersReferToSameThing) {
+  TestReferenceCountedSingletonFactory factory;
+  rcsf_ptr<MyExistenceWatcher> one(&factory), two(&factory);
+
+  EXPECT_EQ(one.get(), two.get());
+}
+
+TEST(ReferenceCountedSingletonFactory, Release) {
+  TestReferenceCountedSingletonFactory factory;
+
+  rcsf_ptr<MyExistenceWatcher> one(&factory);
+  one.get();
+
+  MyExistenceWatcher::delete_called_ = false;
+  one.release();
+  EXPECT_TRUE(MyExistenceWatcher::delete_called_);
+}
+
+TEST(ReferenceCountedSingletonFactory, GetWithoutRelease) {
+  TestReferenceCountedSingletonFactory factory;
+  rcsf_ptr<MyExistenceWatcher> one(&factory);
+  one.get();
+
+  MyExistenceWatcher::create_called_ = false;
+  one.get();
+  EXPECT_FALSE(MyExistenceWatcher::create_called_);
+}
+
+TEST(ReferenceCountedSingletonFactory, GetAfterRelease) {
+  TestReferenceCountedSingletonFactory factory;
+  rcsf_ptr<MyExistenceWatcher> one(&factory);
+
+  MyExistenceWatcher::create_called_ = false;
+  one.release();
+  one.get();
+  EXPECT_TRUE(MyExistenceWatcher::create_called_);
+}
+
+TEST(ReferenceCountedSingletonFactory, MultipleReleases) {
+  TestReferenceCountedSingletonFactory factory;
+  rcsf_ptr<MyExistenceWatcher> one(&factory), two(&factory);
+
+  MyExistenceWatcher::create_called_ = false;
+  MyExistenceWatcher::delete_called_ = false;
+  one.release();
+  EXPECT_FALSE(MyExistenceWatcher::delete_called_);
+  one.release();
+  EXPECT_FALSE(MyExistenceWatcher::delete_called_);
+  one.release();
+  EXPECT_FALSE(MyExistenceWatcher::delete_called_);
+  one.get();
+  EXPECT_TRUE(MyExistenceWatcher::create_called_);
+}
+
+TEST(ReferenceCountedSingletonFactory, Existentialism) {
+  TestReferenceCountedSingletonFactory factory;
+
+  rcsf_ptr<MyExistenceWatcher> one(&factory);
+
+  MyExistenceWatcher::create_called_ = false;
+  MyExistenceWatcher::delete_called_ = false;
+
+  one.get();
+  EXPECT_TRUE(MyExistenceWatcher::create_called_);
+  one.release();
+  EXPECT_TRUE(MyExistenceWatcher::delete_called_);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/rollingaccumulator.h b/talk/base/rollingaccumulator.h
new file mode 100644
index 0000000..cdad025
--- /dev/null
+++ b/talk/base/rollingaccumulator.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_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() {
+  }
+
+  size_t max_count() const {
+    return samples_.size();
+  }
+
+  size_t 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_);
+  }
+
+  // O(n) time complexity.
+  // Weights nth sample with weight (learning_rate)^n. Learning_rate should be
+  // between (0.0, 1.0], otherwise the non-weighted mean is returned.
+  T ComputeWeightedMean(double learning_rate) const {
+    if (count_ < 1 || learning_rate <= 0.0 || learning_rate >= 1.0) {
+      return ComputeMean();
+    }
+    double weighted_mean = 0.0;
+    double current_weight = 1.0;
+    double weight_sum = 0.0;
+    const size_t max_size = max_count();
+    for (size_t i = 0; i < count_; ++i) {
+      current_weight *= learning_rate;
+      weight_sum += current_weight;
+      // Add max_size to prevent underflow.
+      size_t index = (next_index_ + max_size - i - 1) % max_size;
+      weighted_mean += current_weight * samples_[index];
+    }
+    return static_cast<T>(weighted_mean / weight_sum);
+  }
+
+  // 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:
+  size_t count_;
+  size_t 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..c283103
--- /dev/null
+++ b/talk/base/rollingaccumulator_unittest.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 "talk/base/gunit.h"
+#include "talk/base/rollingaccumulator.h"
+
+namespace talk_base {
+
+namespace {
+
+const double kLearningRate = 0.5;
+
+}  // namespace
+
+TEST(RollingAccumulatorTest, ZeroSamples) {
+  RollingAccumulator<int> accum(10);
+
+  EXPECT_EQ(0U, accum.count());
+  EXPECT_EQ(0, accum.ComputeMean());
+  EXPECT_EQ(0, accum.ComputeVariance());
+}
+
+TEST(RollingAccumulatorTest, SomeSamples) {
+  RollingAccumulator<int> accum(10);
+  for (int i = 0; i < 4; ++i) {
+    accum.AddSample(i);
+  }
+
+  EXPECT_EQ(4U, accum.count());
+  EXPECT_EQ(6, accum.ComputeSum());
+  EXPECT_EQ(1, accum.ComputeMean());
+  EXPECT_EQ(2, accum.ComputeWeightedMean(kLearningRate));
+  EXPECT_EQ(1, accum.ComputeVariance());
+}
+
+TEST(RollingAccumulatorTest, RollingSamples) {
+  RollingAccumulator<int> accum(10);
+  for (int i = 0; i < 12; ++i) {
+    accum.AddSample(i);
+  }
+
+  EXPECT_EQ(10U, accum.count());
+  EXPECT_EQ(65, accum.ComputeSum());
+  EXPECT_EQ(6, accum.ComputeMean());
+  EXPECT_EQ(10, accum.ComputeWeightedMean(kLearningRate));
+  EXPECT_NEAR(9, accum.ComputeVariance(), 1);
+}
+
+TEST(RollingAccumulatorTest, RollingSamplesDouble) {
+  RollingAccumulator<double> accum(10);
+  for (int i = 0; i < 23; ++i) {
+    accum.AddSample(5 * i);
+  }
+
+  EXPECT_EQ(10u, accum.count());
+  EXPECT_DOUBLE_EQ(875.0, accum.ComputeSum());
+  EXPECT_DOUBLE_EQ(87.5, accum.ComputeMean());
+  EXPECT_NEAR(105.049, accum.ComputeWeightedMean(kLearningRate), 0.1);
+  EXPECT_NEAR(229.166667, accum.ComputeVariance(), 25);
+}
+
+TEST(RollingAccumulatorTest, ComputeWeightedMeanCornerCases) {
+  RollingAccumulator<int> accum(10);
+  EXPECT_EQ(0, accum.ComputeWeightedMean(kLearningRate));
+  EXPECT_EQ(0, accum.ComputeWeightedMean(0.0));
+  EXPECT_EQ(0, accum.ComputeWeightedMean(1.1));
+
+  for (int i = 0; i < 8; ++i) {
+    accum.AddSample(i);
+  }
+
+  EXPECT_EQ(3, accum.ComputeMean());
+  EXPECT_EQ(3, accum.ComputeWeightedMean(0));
+  EXPECT_EQ(3, accum.ComputeWeightedMean(1.1));
+  EXPECT_EQ(6, accum.ComputeWeightedMean(kLearningRate));
+}
+
+}  // namespace talk_base
diff --git a/talk/base/schanneladapter.cc b/talk/base/schanneladapter.cc
new file mode 100644
index 0000000..a376328
--- /dev/null
+++ b/talk/base/schanneladapter.cc
@@ -0,0 +1,719 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/win32.h"
+#define SECURITY_WIN32
+#include <security.h>
+#include <schannel.h>
+
+#include <iomanip>
+#include <vector>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/schanneladapter.h"
+#include "talk/base/sec_buffer.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+/////////////////////////////////////////////////////////////////////////////
+// SChannelAdapter
+/////////////////////////////////////////////////////////////////////////////
+
+extern const ConstantLabel SECURITY_ERRORS[];
+
+const ConstantLabel SCHANNEL_BUFFER_TYPES[] = {
+  KLABEL(SECBUFFER_EMPTY),              //  0
+  KLABEL(SECBUFFER_DATA),               //  1
+  KLABEL(SECBUFFER_TOKEN),              //  2
+  KLABEL(SECBUFFER_PKG_PARAMS),         //  3
+  KLABEL(SECBUFFER_MISSING),            //  4
+  KLABEL(SECBUFFER_EXTRA),              //  5
+  KLABEL(SECBUFFER_STREAM_TRAILER),     //  6
+  KLABEL(SECBUFFER_STREAM_HEADER),      //  7
+  KLABEL(SECBUFFER_MECHLIST),           // 11
+  KLABEL(SECBUFFER_MECHLIST_SIGNATURE), // 12
+  KLABEL(SECBUFFER_TARGET),             // 13
+  KLABEL(SECBUFFER_CHANNEL_BINDINGS),   // 14
+  LASTLABEL
+};
+
+void DescribeBuffer(LoggingSeverity severity, const char* prefix,
+                    const SecBuffer& sb) {
+  LOG_V(severity)
+    << prefix
+    << "(" << sb.cbBuffer
+    << ", " << FindLabel(sb.BufferType & ~SECBUFFER_ATTRMASK,
+                          SCHANNEL_BUFFER_TYPES)
+    << ", " << sb.pvBuffer << ")";
+}
+
+void DescribeBuffers(LoggingSeverity severity, const char* prefix,
+                     const SecBufferDesc* sbd) {
+  if (!LOG_CHECK_LEVEL_V(severity))
+    return;
+  LOG_V(severity) << prefix << "(";
+  for (size_t i=0; i<sbd->cBuffers; ++i) {
+    DescribeBuffer(severity, "  ", sbd->pBuffers[i]);
+  }
+  LOG_V(severity) << ")";
+}
+
+const ULONG SSL_FLAGS_DEFAULT = ISC_REQ_ALLOCATE_MEMORY
+                              | ISC_REQ_CONFIDENTIALITY
+                              | ISC_REQ_EXTENDED_ERROR
+                              | ISC_REQ_INTEGRITY
+                              | ISC_REQ_REPLAY_DETECT
+                              | ISC_REQ_SEQUENCE_DETECT
+                              | ISC_REQ_STREAM;
+                              //| ISC_REQ_USE_SUPPLIED_CREDS;
+
+typedef std::vector<char> SChannelBuffer;
+
+struct SChannelAdapter::SSLImpl {
+  CredHandle cred;
+  CtxtHandle ctx;
+  bool cred_init, ctx_init;
+  SChannelBuffer inbuf, outbuf, readable;
+  SecPkgContext_StreamSizes sizes;
+
+  SSLImpl() : cred_init(false), ctx_init(false) { }
+};
+
+SChannelAdapter::SChannelAdapter(AsyncSocket* socket)
+  : SSLAdapter(socket), state_(SSL_NONE),
+    restartable_(false), signal_close_(false), message_pending_(false),
+    impl_(new SSLImpl) {
+}
+
+SChannelAdapter::~SChannelAdapter() {
+  Cleanup();
+}
+
+int
+SChannelAdapter::StartSSL(const char* hostname, bool restartable) {
+  if (state_ != SSL_NONE)
+    return ERROR_ALREADY_INITIALIZED;
+
+  ssl_host_name_ = hostname;
+  restartable_ = restartable;
+
+  if (socket_->GetState() != Socket::CS_CONNECTED) {
+    state_ = SSL_WAIT;
+    return 0;
+  }
+
+  state_ = SSL_CONNECTING;
+  if (int err = BeginSSL()) {
+    Error("BeginSSL", err, false);
+    return err;
+  }
+
+  return 0;
+}
+
+int
+SChannelAdapter::BeginSSL() {
+  LOG(LS_VERBOSE) << "BeginSSL: " << ssl_host_name_;
+  ASSERT(state_ == SSL_CONNECTING);
+
+  SECURITY_STATUS ret;
+
+  SCHANNEL_CRED sc_cred = { 0 };
+  sc_cred.dwVersion = SCHANNEL_CRED_VERSION;
+  //sc_cred.dwMinimumCipherStrength = 128; // Note: use system default
+  sc_cred.dwFlags = SCH_CRED_NO_DEFAULT_CREDS | SCH_CRED_AUTO_CRED_VALIDATION;
+
+  ret = AcquireCredentialsHandle(NULL, UNISP_NAME, SECPKG_CRED_OUTBOUND, NULL,
+                                 &sc_cred, NULL, NULL, &impl_->cred, NULL);
+  if (ret != SEC_E_OK) {
+    LOG(LS_ERROR) << "AcquireCredentialsHandle error: "
+                  << ErrorName(ret, SECURITY_ERRORS);
+    return ret;
+  }
+  impl_->cred_init = true;
+
+  if (LOG_CHECK_LEVEL(LS_VERBOSE)) {
+    SecPkgCred_CipherStrengths cipher_strengths = { 0 };
+    ret = QueryCredentialsAttributes(&impl_->cred,
+                                     SECPKG_ATTR_CIPHER_STRENGTHS,
+                                     &cipher_strengths);
+    if (SUCCEEDED(ret)) {
+      LOG(LS_VERBOSE) << "SChannel cipher strength: "
+                  << cipher_strengths.dwMinimumCipherStrength << " - "
+                  << cipher_strengths.dwMaximumCipherStrength;
+    }
+
+    SecPkgCred_SupportedAlgs supported_algs = { 0 };
+    ret = QueryCredentialsAttributes(&impl_->cred,
+                                     SECPKG_ATTR_SUPPORTED_ALGS,
+                                     &supported_algs);
+    if (SUCCEEDED(ret)) {
+      LOG(LS_VERBOSE) << "SChannel supported algorithms:";
+      for (DWORD i=0; i<supported_algs.cSupportedAlgs; ++i) {
+        ALG_ID alg_id = supported_algs.palgSupportedAlgs[i];
+        PCCRYPT_OID_INFO oinfo = CryptFindOIDInfo(CRYPT_OID_INFO_ALGID_KEY,
+                                                  &alg_id, 0);
+        LPCWSTR alg_name = (NULL != oinfo) ? oinfo->pwszName : L"Unknown";
+        LOG(LS_VERBOSE) << "  " << ToUtf8(alg_name) << " (" << alg_id << ")";
+      }
+      CSecBufferBase::FreeSSPI(supported_algs.palgSupportedAlgs);
+    }
+  }
+
+  ULONG flags = SSL_FLAGS_DEFAULT, ret_flags = 0;
+  if (ignore_bad_cert())
+    flags |= ISC_REQ_MANUAL_CRED_VALIDATION;
+
+  CSecBufferBundle<2, CSecBufferBase::FreeSSPI> sb_out;
+  ret = InitializeSecurityContextA(&impl_->cred, NULL,
+                                   const_cast<char*>(ssl_host_name_.c_str()),
+                                   flags, 0, 0, NULL, 0,
+                                   &impl_->ctx, sb_out.desc(),
+                                   &ret_flags, NULL);
+  if (SUCCEEDED(ret))
+    impl_->ctx_init = true;
+  return ProcessContext(ret, NULL, sb_out.desc());
+}
+
+int
+SChannelAdapter::ContinueSSL() {
+  LOG(LS_VERBOSE) << "ContinueSSL";
+  ASSERT(state_ == SSL_CONNECTING);
+
+  SECURITY_STATUS ret;
+
+  CSecBufferBundle<2> sb_in;
+  sb_in[0].BufferType = SECBUFFER_TOKEN;
+  sb_in[0].cbBuffer = static_cast<unsigned long>(impl_->inbuf.size());
+  sb_in[0].pvBuffer = &impl_->inbuf[0];
+  //DescribeBuffers(LS_VERBOSE, "Input Buffer ", sb_in.desc());
+
+  ULONG flags = SSL_FLAGS_DEFAULT, ret_flags = 0;
+  if (ignore_bad_cert())
+    flags |= ISC_REQ_MANUAL_CRED_VALIDATION;
+
+  CSecBufferBundle<2, CSecBufferBase::FreeSSPI> sb_out;
+  ret = InitializeSecurityContextA(&impl_->cred, &impl_->ctx,
+                                   const_cast<char*>(ssl_host_name_.c_str()),
+                                   flags, 0, 0, sb_in.desc(), 0,
+                                   NULL, sb_out.desc(),
+                                   &ret_flags, NULL);
+  return ProcessContext(ret, sb_in.desc(), sb_out.desc());
+}
+
+int
+SChannelAdapter::ProcessContext(long int status, _SecBufferDesc* sbd_in,
+                                _SecBufferDesc* sbd_out) {
+  if (status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED &&
+      status != SEC_E_INCOMPLETE_MESSAGE) {
+    LOG(LS_ERROR)
+      << "InitializeSecurityContext error: "
+      << ErrorName(status, SECURITY_ERRORS);
+  }
+  //if (sbd_in)
+  //  DescribeBuffers(LS_VERBOSE, "Input Buffer ", sbd_in);
+  //if (sbd_out)
+  //  DescribeBuffers(LS_VERBOSE, "Output Buffer ", sbd_out);
+
+  if (status == SEC_E_INCOMPLETE_MESSAGE) {
+    // Wait for more input from server.
+    return Flush();
+  }
+
+  if (FAILED(status)) {
+    // We can't continue.  Common errors:
+    // SEC_E_CERT_EXPIRED - Typically, this means the computer clock is wrong.
+    return status;
+  }
+
+  // Note: we check both input and output buffers for SECBUFFER_EXTRA.
+  // Experience shows it appearing in the input, but the documentation claims
+  // it should appear in the output.
+  size_t extra = 0;
+  if (sbd_in) {
+    for (size_t i=0; i<sbd_in->cBuffers; ++i) {
+      SecBuffer& buffer = sbd_in->pBuffers[i];
+      if (buffer.BufferType == SECBUFFER_EXTRA) {
+        extra += buffer.cbBuffer;
+      }
+    }
+  }
+  if (sbd_out) {
+    for (size_t i=0; i<sbd_out->cBuffers; ++i) {
+      SecBuffer& buffer = sbd_out->pBuffers[i];
+      if (buffer.BufferType == SECBUFFER_EXTRA) {
+        extra += buffer.cbBuffer;
+      } else if (buffer.BufferType == SECBUFFER_TOKEN) {
+        impl_->outbuf.insert(impl_->outbuf.end(),
+          reinterpret_cast<char*>(buffer.pvBuffer),
+          reinterpret_cast<char*>(buffer.pvBuffer) + buffer.cbBuffer);
+      }
+    }
+  }
+
+  if (extra) {
+    ASSERT(extra <= impl_->inbuf.size());
+    size_t consumed = impl_->inbuf.size() - extra;
+    memmove(&impl_->inbuf[0], &impl_->inbuf[consumed], extra);
+    impl_->inbuf.resize(extra);
+  } else {
+    impl_->inbuf.clear();
+  }
+
+  if (SEC_I_CONTINUE_NEEDED == status) {
+    // Send data to server and wait for response.
+    // Note: ContinueSSL will result in a Flush, anyway.
+    return impl_->inbuf.empty() ? Flush() : ContinueSSL();
+  }
+
+  if (SEC_E_OK == status) {
+    LOG(LS_VERBOSE) << "QueryContextAttributes";
+    status = QueryContextAttributes(&impl_->ctx, SECPKG_ATTR_STREAM_SIZES,
+                                    &impl_->sizes);
+    if (FAILED(status)) {
+      LOG(LS_ERROR) << "QueryContextAttributes error: "
+                    << ErrorName(status, SECURITY_ERRORS);
+      return status;
+    }
+
+    state_ = SSL_CONNECTED;
+
+    if (int err = DecryptData()) {
+      return err;
+    } else if (int err = Flush()) {
+      return err;
+    } else {
+      // If we decrypted any data, queue up a notification here
+      PostEvent();
+      // Signal our connectedness
+      AsyncSocketAdapter::OnConnectEvent(this);
+    }
+    return 0;
+  }
+
+  if (SEC_I_INCOMPLETE_CREDENTIALS == status) {
+    // We don't support client authentication in schannel.
+    return status;
+  }
+
+  // We don't expect any other codes
+  ASSERT(false);
+  return status;
+}
+
+int
+SChannelAdapter::DecryptData() {
+  SChannelBuffer& inbuf = impl_->inbuf;
+  SChannelBuffer& readable = impl_->readable;
+
+  while (!inbuf.empty()) {
+    CSecBufferBundle<4> in_buf;
+    in_buf[0].BufferType = SECBUFFER_DATA;
+    in_buf[0].cbBuffer = static_cast<unsigned long>(inbuf.size());
+    in_buf[0].pvBuffer = &inbuf[0];
+
+    //DescribeBuffers(LS_VERBOSE, "Decrypt In ", in_buf.desc());
+    SECURITY_STATUS status = DecryptMessage(&impl_->ctx, in_buf.desc(), 0, 0);
+    //DescribeBuffers(LS_VERBOSE, "Decrypt Out ", in_buf.desc());
+
+    // Note: We are explicitly treating SEC_E_OK, SEC_I_CONTEXT_EXPIRED, and
+    // any other successful results as continue.
+    if (SUCCEEDED(status)) {
+      size_t data_len = 0, extra_len = 0;
+      for (size_t i=0; i<in_buf.desc()->cBuffers; ++i) {
+        if (in_buf[i].BufferType == SECBUFFER_DATA) {
+          data_len += in_buf[i].cbBuffer;
+          readable.insert(readable.end(),
+            reinterpret_cast<char*>(in_buf[i].pvBuffer),
+            reinterpret_cast<char*>(in_buf[i].pvBuffer) + in_buf[i].cbBuffer);
+        } else if (in_buf[i].BufferType == SECBUFFER_EXTRA) {
+          extra_len += in_buf[i].cbBuffer;
+        }
+      }
+      // There is a bug on Win2K where SEC_I_CONTEXT_EXPIRED is misclassified.
+      if ((data_len == 0) && (inbuf[0] == 0x15)) {
+        status = SEC_I_CONTEXT_EXPIRED;
+      }
+      if (extra_len) {
+        size_t consumed = inbuf.size() - extra_len;
+        memmove(&inbuf[0], &inbuf[consumed], extra_len);
+        inbuf.resize(extra_len);
+      } else {
+        inbuf.clear();
+      }
+      // TODO: Handle SEC_I_CONTEXT_EXPIRED to do clean shutdown
+      if (status != SEC_E_OK) {
+        LOG(LS_INFO) << "DecryptMessage returned continuation code: "
+                      << ErrorName(status, SECURITY_ERRORS);
+      }
+      continue;
+    }
+
+    if (status == SEC_E_INCOMPLETE_MESSAGE) {
+      break;
+    } else {
+      return status;
+    }
+  }
+
+  return 0;
+}
+
+void
+SChannelAdapter::Cleanup() {
+  if (impl_->ctx_init)
+    DeleteSecurityContext(&impl_->ctx);
+  if (impl_->cred_init)
+    FreeCredentialsHandle(&impl_->cred);
+  delete impl_;
+}
+
+void
+SChannelAdapter::PostEvent() {
+  // Check if there's anything notable to signal
+  if (impl_->readable.empty() && !signal_close_)
+    return;
+
+  // Only one post in the queue at a time
+  if (message_pending_)
+    return;
+
+  if (Thread* thread = Thread::Current()) {
+    message_pending_ = true;
+    thread->Post(this);
+  } else {
+    LOG(LS_ERROR) << "No thread context available for SChannelAdapter";
+    ASSERT(false);
+  }
+}
+
+void
+SChannelAdapter::Error(const char* context, int err, bool signal) {
+  LOG(LS_WARNING) << "SChannelAdapter::Error("
+                  << context << ", "
+                  << ErrorName(err, SECURITY_ERRORS) << ")";
+  state_ = SSL_ERROR;
+  SetError(err);
+  if (signal)
+    AsyncSocketAdapter::OnCloseEvent(this, err);
+}
+
+int
+SChannelAdapter::Read() {
+  char buffer[4096];
+  SChannelBuffer& inbuf = impl_->inbuf;
+  while (true) {
+    int ret = AsyncSocketAdapter::Recv(buffer, sizeof(buffer));
+    if (ret > 0) {
+      inbuf.insert(inbuf.end(), buffer, buffer + ret);
+    } else if (GetError() == EWOULDBLOCK) {
+      return 0;  // Blocking
+    } else {
+      return GetError();
+    }
+  }
+}
+
+int
+SChannelAdapter::Flush() {
+  int result = 0;
+  size_t pos = 0;
+  SChannelBuffer& outbuf = impl_->outbuf;
+  while (pos < outbuf.size()) {
+    int sent = AsyncSocketAdapter::Send(&outbuf[pos], outbuf.size() - pos);
+    if (sent > 0) {
+      pos += sent;
+    } else if (GetError() == EWOULDBLOCK) {
+      break;  // Blocking
+    } else {
+      result = GetError();
+      break;
+    }
+  }
+  if (int remainder = static_cast<int>(outbuf.size() - pos)) {
+    memmove(&outbuf[0], &outbuf[pos], remainder);
+    outbuf.resize(remainder);
+  } else {
+    outbuf.clear();
+  }
+  return result;
+}
+
+//
+// AsyncSocket Implementation
+//
+
+int
+SChannelAdapter::Send(const void* pv, size_t cb) {
+  switch (state_) {
+  case SSL_NONE:
+    return AsyncSocketAdapter::Send(pv, cb);
+
+  case SSL_WAIT:
+  case SSL_CONNECTING:
+    SetError(EWOULDBLOCK);
+    return SOCKET_ERROR;
+
+  case SSL_CONNECTED:
+    break;
+
+  case SSL_ERROR:
+  default:
+    return SOCKET_ERROR;
+  }
+
+  size_t written = 0;
+  SChannelBuffer& outbuf = impl_->outbuf;
+  while (written < cb) {
+    const size_t encrypt_len = std::min<size_t>(cb - written,
+                                                impl_->sizes.cbMaximumMessage);
+
+    CSecBufferBundle<4> out_buf;
+    out_buf[0].BufferType = SECBUFFER_STREAM_HEADER;
+    out_buf[0].cbBuffer = impl_->sizes.cbHeader;
+    out_buf[1].BufferType = SECBUFFER_DATA;
+    out_buf[1].cbBuffer = static_cast<unsigned long>(encrypt_len);
+    out_buf[2].BufferType = SECBUFFER_STREAM_TRAILER;
+    out_buf[2].cbBuffer = impl_->sizes.cbTrailer;
+
+    size_t packet_len = out_buf[0].cbBuffer
+                      + out_buf[1].cbBuffer
+                      + out_buf[2].cbBuffer;
+
+    SChannelBuffer message;
+    message.resize(packet_len);
+    out_buf[0].pvBuffer = &message[0];
+    out_buf[1].pvBuffer = &message[out_buf[0].cbBuffer];
+    out_buf[2].pvBuffer = &message[out_buf[0].cbBuffer + out_buf[1].cbBuffer];
+
+    memcpy(out_buf[1].pvBuffer,
+           static_cast<const char*>(pv) + written,
+           encrypt_len);
+
+    //DescribeBuffers(LS_VERBOSE, "Encrypt In ", out_buf.desc());
+    SECURITY_STATUS res = EncryptMessage(&impl_->ctx, 0, out_buf.desc(), 0);
+    //DescribeBuffers(LS_VERBOSE, "Encrypt Out ", out_buf.desc());
+
+    if (FAILED(res)) {
+      Error("EncryptMessage", res, false);
+      return SOCKET_ERROR;
+    }
+
+    // We assume that the header and data segments do not change length,
+    // or else encrypting the concatenated packet in-place is wrong.
+    ASSERT(out_buf[0].cbBuffer == impl_->sizes.cbHeader);
+    ASSERT(out_buf[1].cbBuffer == static_cast<unsigned long>(encrypt_len));
+
+    // However, the length of the trailer may change due to padding.
+    ASSERT(out_buf[2].cbBuffer <= impl_->sizes.cbTrailer);
+
+    packet_len = out_buf[0].cbBuffer
+               + out_buf[1].cbBuffer
+               + out_buf[2].cbBuffer;
+
+    written += encrypt_len;
+    outbuf.insert(outbuf.end(), &message[0], &message[packet_len-1]+1);
+  }
+
+  if (int err = Flush()) {
+    state_ = SSL_ERROR;
+    SetError(err);
+    return SOCKET_ERROR;
+  }
+
+  return static_cast<int>(written);
+}
+
+int
+SChannelAdapter::Recv(void* pv, size_t cb) {
+  switch (state_) {
+  case SSL_NONE:
+    return AsyncSocketAdapter::Recv(pv, cb);
+
+  case SSL_WAIT:
+  case SSL_CONNECTING:
+    SetError(EWOULDBLOCK);
+    return SOCKET_ERROR;
+
+  case SSL_CONNECTED:
+    break;
+
+  case SSL_ERROR:
+  default:
+    return SOCKET_ERROR;
+  }
+
+  SChannelBuffer& readable = impl_->readable;
+  if (readable.empty()) {
+    SetError(EWOULDBLOCK);
+    return SOCKET_ERROR;
+  }
+  size_t read = _min(cb, readable.size());
+  memcpy(pv, &readable[0], read);
+  if (size_t remaining = readable.size() - read) {
+    memmove(&readable[0], &readable[read], remaining);
+    readable.resize(remaining);
+  } else {
+    readable.clear();
+  }
+
+  PostEvent();
+  return static_cast<int>(read);
+}
+
+int
+SChannelAdapter::Close() {
+  if (!impl_->readable.empty()) {
+    LOG(WARNING) << "SChannelAdapter::Close with readable data";
+    // Note: this isn't strictly an error, but we're using it temporarily to
+    // track bugs.
+    //ASSERT(false);
+  }
+  if (state_ == SSL_CONNECTED) {
+    DWORD token = SCHANNEL_SHUTDOWN;
+    CSecBufferBundle<1> sb_in;
+    sb_in[0].BufferType = SECBUFFER_TOKEN;
+    sb_in[0].cbBuffer = sizeof(token);
+    sb_in[0].pvBuffer = &token;
+    ApplyControlToken(&impl_->ctx, sb_in.desc());
+    // TODO: In theory, to do a nice shutdown, we need to begin shutdown
+    // negotiation with more calls to InitializeSecurityContext.  Since the
+    // socket api doesn't support nice shutdown at this point, we don't bother.
+  }
+  Cleanup();
+  impl_ = new SSLImpl;
+  state_ = restartable_ ? SSL_WAIT : SSL_NONE;
+  signal_close_ = false;
+  message_pending_ = false;
+  return AsyncSocketAdapter::Close();
+}
+
+Socket::ConnState
+SChannelAdapter::GetState() const {
+  if (signal_close_)
+    return CS_CONNECTED;
+  ConnState state = socket_->GetState();
+  if ((state == CS_CONNECTED)
+      && ((state_ == SSL_WAIT) || (state_ == SSL_CONNECTING)))
+    state = CS_CONNECTING;
+  return state;
+}
+
+void
+SChannelAdapter::OnConnectEvent(AsyncSocket* socket) {
+  LOG(LS_VERBOSE) << "SChannelAdapter::OnConnectEvent";
+  if (state_ != SSL_WAIT) {
+    ASSERT(state_ == SSL_NONE);
+    AsyncSocketAdapter::OnConnectEvent(socket);
+    return;
+  }
+
+  state_ = SSL_CONNECTING;
+  if (int err = BeginSSL()) {
+    Error("BeginSSL", err);
+  }
+}
+
+void
+SChannelAdapter::OnReadEvent(AsyncSocket* socket) {
+  if (state_ == SSL_NONE) {
+    AsyncSocketAdapter::OnReadEvent(socket);
+    return;
+  }
+
+  if (int err = Read()) {
+    Error("Read", err);
+    return;
+  }
+
+  if (impl_->inbuf.empty())
+    return;
+
+  if (state_ == SSL_CONNECTED) {
+    if (int err = DecryptData()) {
+      Error("DecryptData", err);
+    } else if (!impl_->readable.empty()) {
+      AsyncSocketAdapter::OnReadEvent(this);
+    }
+  } else if (state_ == SSL_CONNECTING) {
+    if (int err = ContinueSSL()) {
+      Error("ContinueSSL", err);
+    }
+  }
+}
+
+void
+SChannelAdapter::OnWriteEvent(AsyncSocket* socket) {
+  if (state_ == SSL_NONE) {
+    AsyncSocketAdapter::OnWriteEvent(socket);
+    return;
+  }
+
+  if (int err = Flush()) {
+    Error("Flush", err);
+    return;
+  }
+
+  // See if we have more data to write
+  if (!impl_->outbuf.empty())
+    return;
+
+  // Buffer is empty, submit notification
+  if (state_ == SSL_CONNECTED) {
+    AsyncSocketAdapter::OnWriteEvent(socket);
+  }
+}
+
+void
+SChannelAdapter::OnCloseEvent(AsyncSocket* socket, int err) {
+  if ((state_ == SSL_NONE) || impl_->readable.empty()) {
+    AsyncSocketAdapter::OnCloseEvent(socket, err);
+    return;
+  }
+
+  // If readable is non-empty, then we have a pending Message
+  // that will allow us to signal close (eventually).
+  signal_close_ = true;
+}
+
+void
+SChannelAdapter::OnMessage(Message* pmsg) {
+  if (!message_pending_)
+    return;  // This occurs when socket is closed
+
+  message_pending_ = false;
+  if (!impl_->readable.empty()) {
+    AsyncSocketAdapter::OnReadEvent(this);
+  } else if (signal_close_) {
+    signal_close_ = false;
+    AsyncSocketAdapter::OnCloseEvent(this, 0); // TODO: cache this error?
+  }
+}
+
+} // namespace talk_base
diff --git a/talk/base/schanneladapter.h b/talk/base/schanneladapter.h
new file mode 100644
index 0000000..a5ab7b3
--- /dev/null
+++ b/talk/base/schanneladapter.h
@@ -0,0 +1,94 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_SCHANNELADAPTER_H__
+#define TALK_BASE_SCHANNELADAPTER_H__
+
+#include <string>
+#include "talk/base/ssladapter.h"
+#include "talk/base/messagequeue.h"
+struct _SecBufferDesc;
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SChannelAdapter : public SSLAdapter, public MessageHandler {
+public:
+  SChannelAdapter(AsyncSocket* socket);
+  virtual ~SChannelAdapter();
+
+  virtual int StartSSL(const char* hostname, bool restartable);
+  virtual int Send(const void* pv, size_t cb);
+  virtual int Recv(void* pv, size_t cb);
+  virtual int Close();
+
+  // Note that the socket returns ST_CONNECTING while SSL is being negotiated.
+  virtual ConnState GetState() const;
+
+protected:
+  enum SSLState {
+    SSL_NONE, SSL_WAIT, SSL_CONNECTING, SSL_CONNECTED, SSL_ERROR
+  };
+  struct SSLImpl;
+
+  virtual void OnConnectEvent(AsyncSocket* socket);
+  virtual void OnReadEvent(AsyncSocket* socket);
+  virtual void OnWriteEvent(AsyncSocket* socket);
+  virtual void OnCloseEvent(AsyncSocket* socket, int err);
+  virtual void OnMessage(Message* pmsg);
+
+  int BeginSSL();
+  int ContinueSSL();
+  int ProcessContext(long int status, _SecBufferDesc* sbd_in,
+                     _SecBufferDesc* sbd_out);
+  int DecryptData();
+
+  int Read();
+  int Flush();
+  void Error(const char* context, int err, bool signal = true);
+  void Cleanup();
+
+  void PostEvent();
+
+private:
+  SSLState state_;
+  std::string ssl_host_name_;
+  // If true, socket will retain SSL configuration after Close.
+  bool restartable_; 
+  // If true, we are delaying signalling close until all data is read.
+  bool signal_close_;
+  // If true, we are waiting to be woken up to signal readability or closure.
+  bool message_pending_;
+  SSLImpl* impl_;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_SCHANNELADAPTER_H__
diff --git a/talk/base/scoped_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_ptr.h b/talk/base/scoped_ptr.h
new file mode 100644
index 0000000..5a8364e
--- /dev/null
+++ b/talk/base/scoped_ptr.h
@@ -0,0 +1,277 @@
+//  (C) Copyright Greg Colvin and Beman Dawes 1998, 1999.
+//  Copyright (c) 2001, 2002 Peter Dimov
+//
+//  Permission to copy, use, modify, sell and distribute this software
+//  is granted provided this copyright notice appears in all copies.
+//  This software is provided "as is" without express or implied
+//  warranty, and with no claim as to its suitability for any purpose.
+//
+//  See http://www.boost.org/libs/smart_ptr/scoped_ptr.htm for documentation.
+//
+
+//  scoped_ptr mimics a built-in pointer except that it guarantees deletion
+//  of the object pointed to, either on destruction of the scoped_ptr or via
+//  an explicit reset(). scoped_ptr is a simple solution for simple needs;
+//  use shared_ptr or std::auto_ptr if your needs are more complex.
+
+//  scoped_ptr_malloc added in by Google.  When one of
+//  these goes out of scope, instead of doing a delete or delete[], it
+//  calls free().  scoped_ptr_malloc<char> is likely to see much more
+//  use than any other specializations.
+
+//  release() added in by Google. Use this to conditionally
+//  transfer ownership of a heap-allocated object to the caller, usually on
+//  method success.
+#ifndef TALK_BASE_SCOPED_PTR_H__
+#define TALK_BASE_SCOPED_PTR_H__
+
+#include <cstddef>             // for std::ptrdiff_t
+#include <stdlib.h>            // for free() decl
+
+#include "talk/base/common.h"  // for ASSERT
+
+#ifdef _WIN32
+namespace std { using ::ptrdiff_t; };
+#endif // _WIN32
+
+namespace talk_base {
+
+template <typename T>
+class scoped_ptr {
+ private:
+
+  T* ptr;
+
+  scoped_ptr(scoped_ptr const &);
+  scoped_ptr & operator=(scoped_ptr const &);
+
+ public:
+
+  typedef T element_type;
+
+  explicit scoped_ptr(T* p = NULL): ptr(p) {}
+
+  ~scoped_ptr() {
+    typedef char type_must_be_complete[sizeof(T)];
+    delete ptr;
+  }
+
+  void reset(T* p = NULL) {
+    typedef char type_must_be_complete[sizeof(T)];
+
+    if (ptr != p) {
+      T* obj = ptr;
+      ptr = p;
+      // Delete last, in case obj destructor indirectly results in ~scoped_ptr
+      delete obj;
+    }
+  }
+
+  T& operator*() const {
+    ASSERT(ptr != NULL);
+    return *ptr;
+  }
+
+  T* operator->() const  {
+    ASSERT(ptr != NULL);
+    return ptr;
+  }
+
+  T* get() const  {
+    return ptr;
+  }
+
+  void swap(scoped_ptr & b) {
+    T* tmp = b.ptr;
+    b.ptr = ptr;
+    ptr = tmp;
+  }
+
+  T* release() {
+    T* tmp = ptr;
+    ptr = NULL;
+    return tmp;
+  }
+
+  T** accept() {
+    if (ptr) {
+      delete ptr;
+      ptr = NULL;
+    }
+    return &ptr;
+  }
+
+  T** use() {
+    return &ptr;
+  }
+
+  // Allow scoped_ptr<T> to be used in boolean expressions, but not
+  // implicitly convertible to a real bool (which is dangerous).
+  // Borrowed from chromium's scoped_ptr implementation.
+  typedef T* scoped_ptr::*Testable;
+  operator Testable() const { return ptr ? &scoped_ptr::ptr : NULL; }
+
+};
+
+template<typename T> inline
+void swap(scoped_ptr<T>& a, scoped_ptr<T>& b) {
+  a.swap(b);
+}
+
+
+
+
+//  scoped_array extends scoped_ptr to arrays. Deletion of the array pointed to
+//  is guaranteed, either on destruction of the scoped_array or via an explicit
+//  reset(). Use shared_array or std::vector if your needs are more complex.
+
+template<typename T>
+class scoped_array {
+ private:
+
+  T* ptr;
+
+  scoped_array(scoped_array const &);
+  scoped_array & operator=(scoped_array const &);
+
+ public:
+
+  typedef T element_type;
+
+  explicit scoped_array(T* p = NULL) : ptr(p) {}
+
+  ~scoped_array() {
+    typedef char type_must_be_complete[sizeof(T)];
+    delete[] ptr;
+  }
+
+  void reset(T* p = NULL) {
+    typedef char type_must_be_complete[sizeof(T)];
+
+    if (ptr != p) {
+      T* arr = ptr;
+      ptr = p;
+      // Delete last, in case arr destructor indirectly results in ~scoped_array
+      delete [] arr;
+    }
+  }
+
+  T& operator[](std::ptrdiff_t i) const {
+    ASSERT(ptr != NULL);
+    ASSERT(i >= 0);
+    return ptr[i];
+  }
+
+  T* get() const {
+    return ptr;
+  }
+
+  void swap(scoped_array & b) {
+    T* tmp = b.ptr;
+    b.ptr = ptr;
+    ptr = tmp;
+  }
+
+  T* release() {
+    T* tmp = ptr;
+    ptr = NULL;
+    return tmp;
+  }
+
+  T** accept() {
+    if (ptr) {
+      delete [] ptr;
+      ptr = NULL;
+    }
+    return &ptr;
+  }
+
+  // Allow scoped_array<T> to be used in boolean expressions, but not
+  // implicitly convertible to a real bool (which is dangerous).
+  // Borrowed from chromium's scoped_array implementation.
+  typedef T* scoped_array::*Testable;
+  operator Testable() const { return ptr ? &scoped_array::ptr : NULL; }
+};
+
+template<class T> inline
+void swap(scoped_array<T>& a, scoped_array<T>& b) {
+  a.swap(b);
+}
+
+// scoped_ptr_malloc<> is similar to scoped_ptr<>, but it accepts a
+// second template argument, the function used to free the object.
+
+template<typename T, void (*FF)(T*) = free> class scoped_ptr_malloc {
+ private:
+
+  T* ptr;
+
+  scoped_ptr_malloc(scoped_ptr_malloc const &);
+  scoped_ptr_malloc & operator=(scoped_ptr_malloc const &);
+
+ public:
+
+  typedef T element_type;
+
+  explicit scoped_ptr_malloc(T* p = 0): ptr(p) {}
+
+  ~scoped_ptr_malloc() {
+    FF(ptr);
+  }
+
+  void reset(T* p = 0) {
+    if (ptr != p) {
+      FF(ptr);
+      ptr = p;
+    }
+  }
+
+  T& operator*() const {
+    ASSERT(ptr != 0);
+    return *ptr;
+  }
+
+  T* operator->() const {
+    ASSERT(ptr != 0);
+    return ptr;
+  }
+
+  T* get() const {
+    return ptr;
+  }
+
+  void swap(scoped_ptr_malloc & b) {
+    T* tmp = b.ptr;
+    b.ptr = ptr;
+    ptr = tmp;
+  }
+
+  T* release() {
+    T* tmp = ptr;
+    ptr = 0;
+    return tmp;
+  }
+
+  T** accept() {
+    if (ptr) {
+      FF(ptr);
+      ptr = 0;
+    }
+    return &ptr;
+  }
+
+  // Allow scoped_ptr_malloc<T> to be used in boolean expressions, but not
+  // implicitly convertible to a real bool (which is dangerous).
+  // Borrowed from chromium's scoped_ptr_malloc implementation.
+  typedef T* scoped_ptr_malloc::*Testable;
+  operator Testable() const { return ptr ? &scoped_ptr_malloc::ptr : NULL; }
+};
+
+template<typename T, void (*FF)(T*)> inline
+void swap(scoped_ptr_malloc<T,FF>& a, scoped_ptr_malloc<T,FF>& b) {
+  a.swap(b);
+}
+
+} // namespace talk_base
+
+#endif  // #ifndef TALK_BASE_SCOPED_PTR_H__
diff --git a/talk/base/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/sec_buffer.h b/talk/base/sec_buffer.h
new file mode 100644
index 0000000..585e27f
--- /dev/null
+++ b/talk/base/sec_buffer.h
@@ -0,0 +1,173 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// @file Contains utility classes that make it easier to use SecBuffers
+
+#ifndef TALK_BASE_SEC_BUFFER_H__
+#define TALK_BASE_SEC_BUFFER_H__
+
+namespace talk_base {
+
+// A base class for CSecBuffer<T>. Contains
+// all implementation that does not require
+// template arguments.
+class CSecBufferBase : public SecBuffer {
+ public:
+  CSecBufferBase() {
+    Clear();
+  }
+
+  // Uses the SSPI to free a pointer, must be
+  // used for buffers returned from SSPI APIs.
+  static void FreeSSPI(void *ptr) {
+    if ( ptr ) {
+      SECURITY_STATUS status;
+      status = ::FreeContextBuffer(ptr);
+      ASSERT(SEC_E_OK == status); // "Freeing context buffer"
+    }
+  }
+
+  // Deletes a buffer with operator delete
+  static void FreeDelete(void *ptr) {
+    delete [] reinterpret_cast<char*>(ptr);
+  }
+
+  // A noop delete, for buffers over other
+  // people's memory
+  static void FreeNone(void *ptr) {
+  }
+
+ protected:
+  // Clears the buffer to EMPTY & NULL
+  void Clear() {
+    this->BufferType = SECBUFFER_EMPTY;
+    this->cbBuffer = 0;
+    this->pvBuffer = NULL;
+  }
+};
+
+// Wrapper class for SecBuffer to take care
+// of initialization and destruction.
+template <void (*pfnFreeBuffer)(void *ptr)>
+class CSecBuffer: public CSecBufferBase {
+ public:
+  // Initializes buffer to empty & NULL
+  CSecBuffer() {
+  }
+
+  // Frees any allocated memory
+  ~CSecBuffer() {
+    Release();
+  }
+
+  // Frees the buffer appropriately, and re-nulls
+  void Release() {
+    pfnFreeBuffer(this->pvBuffer);
+    Clear();
+  }
+
+ private:
+  // A placeholder function for compile-time asserts on the class
+  void CompileAsserts() {
+    // never invoked...
+    assert(false); // _T("Notreached")
+
+    // This class must not extend the size of SecBuffer, since
+    // we use arrays of CSecBuffer in CSecBufferBundle below
+    cassert(sizeof(CSecBuffer<SSPIFree> == sizeof(SecBuffer)));
+  }
+};
+
+// Contains all generic implementation for the
+// SecBufferBundle class
+class SecBufferBundleBase {
+ public:
+};
+
+// A template class that bundles a SecBufferDesc with
+// one or more SecBuffers for convenience. Can take
+// care of deallocating buffers appropriately, as indicated
+// by pfnFreeBuffer function.
+// By default does no deallocation.
+template <int num_buffers,
+          void (*pfnFreeBuffer)(void *ptr) = CSecBufferBase::FreeNone>
+class CSecBufferBundle : public SecBufferBundleBase {
+ public:
+  // Constructs a security buffer bundle with num_buffers
+  // buffers, all of which are empty and nulled.
+  CSecBufferBundle() {
+    desc_.ulVersion = SECBUFFER_VERSION;
+    desc_.cBuffers = num_buffers;
+    desc_.pBuffers = buffers_;
+  }
+
+  // Frees all currently used buffers.
+  ~CSecBufferBundle() {
+    Release();
+  }
+
+  // Accessor for the descriptor
+  PSecBufferDesc desc() {
+    return &desc_;
+  }
+
+  // Accessor for the descriptor
+  const PSecBufferDesc desc() const {
+    return &desc_;
+  }
+
+  // returns the i-th security buffer
+  SecBuffer &operator[] (size_t num) {
+    ASSERT(num < num_buffers); // "Buffer index out of bounds"
+    return buffers_[num];
+  }
+
+  // returns the i-th security buffer
+  const SecBuffer &operator[] (size_t num) const {
+    ASSERT(num < num_buffers); // "Buffer index out of bounds"
+    return buffers_[num];
+  }
+
+  // Frees all non-NULL security buffers,
+  // using the deallocation function
+  void Release() {
+    for ( size_t i = 0; i < num_buffers; ++i ) {
+      buffers_[i].Release();
+    }
+  }
+
+ private:
+  // Our descriptor
+  SecBufferDesc               desc_;
+  // Our bundled buffers, each takes care of its own
+  // initialization and destruction
+  CSecBuffer<pfnFreeBuffer>   buffers_[num_buffers];
+};
+
+} // namespace talk_base
+
+#endif  // TALK_BASE_SEC_BUFFER_H__
diff --git a/talk/base/sha1.cc b/talk/base/sha1.cc
new file mode 100644
index 0000000..d52d56d
--- /dev/null
+++ b/talk/base/sha1.cc
@@ -0,0 +1,282 @@
+/*
+ * SHA-1 in C
+ * By Steve Reid <sreid@sea-to-sky.net>
+ * 100% Public Domain
+ *
+ * -----------------
+ * Modified 7/98
+ * By James H. Brown <jbrown@burgoyne.com>
+ * Still 100% Public Domain
+ *
+ * Corrected a problem which generated improper hash values on 16 bit machines
+ * Routine SHA1Update changed from
+ *   void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned int
+ * len)
+ * to
+ *   void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned
+ * long len)
+ *
+ * The 'len' parameter was declared an int which works fine on 32 bit machines.
+ * However, on 16 bit machines an int is too small for the shifts being done
+ * against
+ * it.  This caused the hash function to generate incorrect values if len was
+ * greater than 8191 (8K - 1) due to the 'len << 3' on line 3 of SHA1Update().
+ *
+ * Since the file IO in main() reads 16K at a time, any file 8K or larger would
+ * be guaranteed to generate the wrong hash (e.g. Test Vector #3, a million
+ * "a"s).
+ *
+ * I also changed the declaration of variables i & j in SHA1Update to
+ * unsigned long from unsigned int for the same reason.
+ *
+ * These changes should make no difference to any 32 bit implementations since
+ * an
+ * int and a long are the same size in those environments.
+ *
+ * --
+ * I also corrected a few compiler warnings generated by Borland C.
+ * 1. Added #include <process.h> for exit() prototype
+ * 2. Removed unused variable 'j' in SHA1Final
+ * 3. Changed exit(0) to return(0) at end of main.
+ *
+ * ALL changes I made can be located by searching for comments containing 'JHB'
+ * -----------------
+ * Modified 8/98
+ * By Steve Reid <sreid@sea-to-sky.net>
+ * Still 100% public domain
+ *
+ * 1- Removed #include <process.h> and used return() instead of exit()
+ * 2- Fixed overwriting of finalcount in SHA1Final() (discovered by Chris Hall)
+ * 3- Changed email address from steve@edmweb.com to sreid@sea-to-sky.net
+ *
+ * -----------------
+ * Modified 4/01
+ * By Saul Kravitz <Saul.Kravitz@celera.com>
+ * Still 100% PD
+ * Modified to run on Compaq Alpha hardware.
+ *
+ * -----------------
+ * Modified 07/2002
+ * By Ralph Giles <giles@ghostscript.com>
+ * Still 100% public domain
+ * modified for use with stdint types, autoconf
+ * code cleanup, removed attribution comments
+ * switched SHA1Final() argument order for consistency
+ * use SHA1_ prefix for public api
+ * move public api to sha1.h
+ *
+ * -----------------
+ * Modified 02/2012
+ * By Justin Uberti <juberti@google.com>
+ * Remove underscore from SHA1 prefix to avoid conflict with OpenSSL
+ * Remove test code
+ * Untabify
+ *
+ * -----------------
+ * Modified 03/2012
+ * By Ronghua Wu <ronghuawu@google.com>
+ * Change the typedef of uint32(8)_t to uint32(8). We need this because in the
+ * chromium android build, the stdio.h will include stdint.h which already
+ * defined uint32(8)_t.
+ *
+ * -----------------
+ * Modified 04/2012
+ * By Frank Barchard <fbarchard@google.com>
+ * Ported to C++, Google style, change len to size_t, enable SHA1HANDSOFF
+ *
+ * Test Vectors (from FIPS PUB 180-1)
+ * "abc"
+ *   A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
+ * "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
+ *   84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
+ * A million repetitions of "a"
+ *   34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
+ */
+
+// Enabling SHA1HANDSOFF preserves the caller's data buffer.
+// Disabling SHA1HANDSOFF the buffer will be modified (end swapped).
+#define SHA1HANDSOFF
+
+#include "talk/base/sha1.h"
+
+#include <stdio.h>
+#include <string.h>
+
+void SHA1Transform(uint32 state[5], const uint8 buffer[64]);
+
+#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
+
+// blk0() and blk() perform the initial expand.
+// I got the idea of expanding during the round function from SSLeay
+// FIXME: can we do this in an endian-proof way?
+#ifdef ARCH_CPU_BIG_ENDIAN
+#define blk0(i) block->l[i]
+#else
+#define blk0(i) (block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) | \
+    (rol(block->l[i], 8) & 0x00FF00FF))
+#endif
+#define blk(i) (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ \
+    block->l[(i + 8) & 15] ^ block->l[(i + 2) & 15] ^ block->l[i & 15], 1))
+
+// (R0+R1), R2, R3, R4 are the different operations used in SHA1.
+#define R0(v, w, x, y, z, i) \
+    z += ((w & (x ^ y)) ^ y) + blk0(i) + 0x5A827999 + rol(v, 5); \
+    w = rol(w, 30);
+#define R1(v, w, x, y, z, i) \
+    z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
+    w = rol(w, 30);
+#define R2(v, w, x, y, z, i) \
+    z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5);\
+    w = rol(w, 30);
+#define R3(v, w, x, y, z, i) \
+    z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \
+    w = rol(w, 30);
+#define R4(v, w, x, y, z, i) \
+    z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
+    w = rol(w, 30);
+
+#ifdef VERBOSE  // SAK
+void SHAPrintContext(SHA1_CTX *context, char *msg) {
+  printf("%s (%d,%d) %x %x %x %x %x\n",
+         msg,
+         context->count[0], context->count[1],
+         context->state[0],
+         context->state[1],
+         context->state[2],
+         context->state[3],
+         context->state[4]);
+}
+#endif /* VERBOSE */
+
+// Hash a single 512-bit block. This is the core of the algorithm.
+void SHA1Transform(uint32 state[5], const uint8 buffer[64]) {
+  union CHAR64LONG16 {
+    uint8 c[64];
+    uint32 l[16];
+  };
+#ifdef SHA1HANDSOFF
+  static uint8 workspace[64];
+  memcpy(workspace, buffer, 64);
+  CHAR64LONG16* block = reinterpret_cast<CHAR64LONG16*>(workspace);
+#else
+  // Note(fbarchard): This option does modify the user's data buffer.
+  CHAR64LONG16* block = const_cast<CHAR64LONG16*>(
+      reinterpret_cast<const CHAR64LONG16*>(buffer));
+#endif
+
+  // Copy context->state[] to working vars.
+  uint32 a = state[0];
+  uint32 b = state[1];
+  uint32 c = state[2];
+  uint32 d = state[3];
+  uint32 e = state[4];
+
+  // 4 rounds of 20 operations each. Loop unrolled.
+  // Note(fbarchard): The following has lint warnings for multiple ; on
+  // a line and no space after , but is left as-is to be similar to the
+  // original code.
+  R0(a,b,c,d,e,0); R0(e,a,b,c,d,1); R0(d,e,a,b,c,2); R0(c,d,e,a,b,3);
+  R0(b,c,d,e,a,4); R0(a,b,c,d,e,5); R0(e,a,b,c,d,6); R0(d,e,a,b,c,7);
+  R0(c,d,e,a,b,8); R0(b,c,d,e,a,9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11);
+  R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15);
+  R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
+  R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);
+  R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);
+  R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);
+  R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);
+  R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);
+  R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);
+  R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);
+  R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);
+  R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);
+  R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);
+  R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);
+  R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);
+  R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);
+  R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);
+  R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);
+
+  // Add the working vars back into context.state[].
+  state[0] += a;
+  state[1] += b;
+  state[2] += c;
+  state[3] += d;
+  state[4] += e;
+}
+
+// SHA1Init - Initialize new context.
+void SHA1Init(SHA1_CTX* context) {
+  // SHA1 initialization constants.
+  context->state[0] = 0x67452301;
+  context->state[1] = 0xEFCDAB89;
+  context->state[2] = 0x98BADCFE;
+  context->state[3] = 0x10325476;
+  context->state[4] = 0xC3D2E1F0;
+  context->count[0] = context->count[1] = 0;
+}
+
+// Run your data through this.
+void SHA1Update(SHA1_CTX* context, const uint8* data, size_t input_len) {
+  size_t i = 0;
+
+#ifdef VERBOSE
+  SHAPrintContext(context, "before");
+#endif
+
+  // Compute number of bytes mod 64.
+  size_t index = (context->count[0] >> 3) & 63;
+
+  // Update number of bits.
+  // TODO: Use uint64 instead of 2 uint32 for count.
+  // count[0] has low 29 bits for byte count + 3 pad 0's making 32 bits for
+  // bit count.
+  // Add bit count to low uint32
+  context->count[0] += static_cast<uint32>(input_len << 3);
+  if (context->count[0] < static_cast<uint32>(input_len << 3)) {
+    ++context->count[1];  // if overlow (carry), add one to high word
+  }
+  context->count[1] += static_cast<uint32>(input_len >> 29);
+  if ((index + input_len) > 63) {
+    i = 64 - index;
+    memcpy(&context->buffer[index], data, i);
+    SHA1Transform(context->state, context->buffer);
+    for (; i + 63 < input_len; i += 64) {
+      SHA1Transform(context->state, data + i);
+    }
+    index = 0;
+  }
+  memcpy(&context->buffer[index], &data[i], input_len - i);
+
+#ifdef VERBOSE
+  SHAPrintContext(context, "after ");
+#endif
+}
+
+// Add padding and return the message digest.
+void SHA1Final(SHA1_CTX* context, uint8 digest[SHA1_DIGEST_SIZE]) {
+  uint8 finalcount[8];
+  for (int i = 0; i < 8; ++i) {
+    // Endian independent
+    finalcount[i] = static_cast<uint8>(
+        (context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8) ) & 255);
+  }
+  SHA1Update(context, reinterpret_cast<const uint8*>("\200"), 1);
+  while ((context->count[0] & 504) != 448) {
+    SHA1Update(context, reinterpret_cast<const uint8*>("\0"), 1);
+  }
+  SHA1Update(context, finalcount, 8);  // Should cause a SHA1Transform().
+  for (int i = 0; i < SHA1_DIGEST_SIZE; ++i) {
+    digest[i] = static_cast<uint8>(
+        (context->state[i >> 2] >> ((3 - (i & 3)) * 8) ) & 255);
+  }
+
+  // Wipe variables.
+  memset(context->buffer, 0, 64);
+  memset(context->state, 0, 20);
+  memset(context->count, 0, 8);
+  memset(finalcount, 0, 8);   // SWR
+
+#ifdef SHA1HANDSOFF  // Make SHA1Transform overwrite its own static vars.
+  SHA1Transform(context->state, context->buffer);
+#endif
+}
diff --git a/talk/base/sha1.h b/talk/base/sha1.h
new file mode 100644
index 0000000..262b744
--- /dev/null
+++ b/talk/base/sha1.h
@@ -0,0 +1,28 @@
+/*
+ * SHA-1 in C
+ * By Steve Reid <sreid@sea-to-sky.net>
+ * 100% Public Domain
+ *
+*/
+
+// Ported to C++, Google style and uses basictypes.h
+
+#ifndef TALK_BASE_SHA1_H_
+#define TALK_BASE_SHA1_H_
+
+#include "talk/base/basictypes.h"
+
+struct SHA1_CTX {
+  uint32 state[5];
+  // TODO: Change bit count to uint64.
+  uint32 count[2];  // Bit count of input.
+  uint8 buffer[64];
+};
+
+#define SHA1_DIGEST_SIZE 20
+
+void SHA1Init(SHA1_CTX* context);
+void SHA1Update(SHA1_CTX* context, const uint8* data, size_t len);
+void SHA1Final(SHA1_CTX* context, uint8 digest[SHA1_DIGEST_SIZE]);
+
+#endif  // TALK_BASE_SHA1_H_
diff --git a/talk/base/sha1digest.h b/talk/base/sha1digest.h
new file mode 100644
index 0000000..c8b1e46
--- /dev/null
+++ b/talk/base/sha1digest.h
@@ -0,0 +1,64 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_SHA1DIGEST_H_
+#define TALK_BASE_SHA1DIGEST_H_
+
+#include "talk/base/messagedigest.h"
+#include "talk/base/sha1.h"
+
+namespace talk_base {
+
+// A simple wrapper for our SHA-1 implementation.
+class Sha1Digest : public MessageDigest {
+ public:
+  enum { kSize = SHA1_DIGEST_SIZE };
+  Sha1Digest() {
+    SHA1Init(&ctx_);
+  }
+  virtual size_t Size() const {
+    return kSize;
+  }
+  virtual void Update(const void* buf, size_t len) {
+    SHA1Update(&ctx_, static_cast<const uint8*>(buf), len);
+  }
+  virtual size_t Finish(void* buf, size_t len) {
+    if (len < kSize) {
+      return 0;
+    }
+    SHA1Final(&ctx_, static_cast<uint8*>(buf));
+    SHA1Init(&ctx_);  // Reset for next use.
+    return kSize;
+  }
+
+ private:
+  SHA1_CTX ctx_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SHA1DIGEST_H_
diff --git a/talk/base/sha1digest_unittest.cc b/talk/base/sha1digest_unittest.cc
new file mode 100644
index 0000000..5ab6819
--- /dev/null
+++ b/talk/base/sha1digest_unittest.cc
@@ -0,0 +1,99 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/sha1digest.h"
+#include "talk/base/gunit.h"
+#include "talk/base/stringencode.h"
+
+namespace talk_base {
+
+std::string Sha1(const std::string& input) {
+  Sha1Digest sha1;
+  return ComputeDigest(&sha1, input);
+}
+
+TEST(Sha1DigestTest, TestSize) {
+  Sha1Digest sha1;
+  EXPECT_EQ(20U, Sha1Digest::kSize);
+  EXPECT_EQ(20U, sha1.Size());
+}
+
+TEST(Sha1DigestTest, TestBasic) {
+  // Test vectors from sha1.c.
+  EXPECT_EQ("da39a3ee5e6b4b0d3255bfef95601890afd80709", Sha1(""));
+  EXPECT_EQ("a9993e364706816aba3e25717850c26c9cd0d89d", Sha1("abc"));
+  EXPECT_EQ("84983e441c3bd26ebaae4aa1f95129e5e54670f1",
+            Sha1("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"));
+  std::string a_million_as(1000000, 'a');
+  EXPECT_EQ("34aa973cd4c4daa4f61eeb2bdbad27316534016f", Sha1(a_million_as));
+}
+
+TEST(Sha1DigestTest, TestMultipleUpdates) {
+  Sha1Digest sha1;
+  std::string input =
+      "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq";
+  char output[Sha1Digest::kSize];
+  for (size_t i = 0; i < input.size(); ++i) {
+    sha1.Update(&input[i], 1);
+  }
+  EXPECT_EQ(sha1.Size(), sha1.Finish(output, sizeof(output)));
+  EXPECT_EQ("84983e441c3bd26ebaae4aa1f95129e5e54670f1",
+            hex_encode(output, sizeof(output)));
+}
+
+TEST(Sha1DigestTest, TestReuse) {
+  Sha1Digest sha1;
+  std::string input = "abc";
+  EXPECT_EQ("a9993e364706816aba3e25717850c26c9cd0d89d",
+            ComputeDigest(&sha1, input));
+  input = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq";
+  EXPECT_EQ("84983e441c3bd26ebaae4aa1f95129e5e54670f1",
+            ComputeDigest(&sha1, input));
+}
+
+TEST(Sha1DigestTest, TestBufferTooSmall) {
+  Sha1Digest sha1;
+  std::string input = "abcdefghijklmnopqrstuvwxyz";
+  char output[Sha1Digest::kSize - 1];
+  sha1.Update(input.c_str(), input.size());
+  EXPECT_EQ(0U, sha1.Finish(output, sizeof(output)));
+}
+
+TEST(Sha1DigestTest, TestBufferConst) {
+  Sha1Digest sha1;
+  const int kLongSize = 1000000;
+  std::string input(kLongSize, '\0');
+  for (int i = 0; i < kLongSize; ++i) {
+    input[i] = static_cast<char>(i);
+  }
+  sha1.Update(input.c_str(), input.size());
+  for (int i = 0; i < kLongSize; ++i) {
+    EXPECT_EQ(static_cast<char>(i), input[i]);
+  }
+}
+
+}  // namespace talk_base
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/signalthread.cc b/talk/base/signalthread.cc
new file mode 100644
index 0000000..88f3ff7
--- /dev/null
+++ b/talk/base/signalthread.cc
@@ -0,0 +1,166 @@
+/*
+ * libjingle
+ * Copyright 2004--2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/signalthread.h"
+
+#include "talk/base/common.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// SignalThread
+///////////////////////////////////////////////////////////////////////////////
+
+SignalThread::SignalThread()
+    : main_(Thread::Current()),
+      worker_(this),
+      state_(kInit),
+      refcount_(1) {
+  main_->SignalQueueDestroyed.connect(this,
+                                      &SignalThread::OnMainThreadDestroyed);
+  worker_.SetName("SignalThread", this);
+}
+
+SignalThread::~SignalThread() {
+  ASSERT(refcount_ == 0);
+}
+
+bool SignalThread::SetName(const std::string& name, const void* obj) {
+  EnterExit ee(this);
+  ASSERT(main_->IsCurrent());
+  ASSERT(kInit == state_);
+  return worker_.SetName(name, obj);
+}
+
+bool SignalThread::SetPriority(ThreadPriority priority) {
+  EnterExit ee(this);
+  ASSERT(main_->IsCurrent());
+  ASSERT(kInit == state_);
+  return worker_.SetPriority(priority);
+}
+
+void SignalThread::Start() {
+  EnterExit ee(this);
+  ASSERT(main_->IsCurrent());
+  if (kInit == state_ || kComplete == state_) {
+    state_ = kRunning;
+    OnWorkStart();
+    worker_.Start();
+  } else {
+    ASSERT(false);
+  }
+}
+
+void SignalThread::Destroy(bool wait) {
+  EnterExit ee(this);
+  ASSERT(main_->IsCurrent());
+  if ((kInit == state_) || (kComplete == state_)) {
+    refcount_--;
+  } else if (kRunning == state_ || kReleasing == state_) {
+    state_ = kStopping;
+    // OnWorkStop() must follow Quit(), so that when the thread wakes up due to
+    // OWS(), ContinueWork() will return false.
+    worker_.Quit();
+    OnWorkStop();
+    if (wait) {
+      // Release the thread's lock so that it can return from ::Run.
+      cs_.Leave();
+      worker_.Stop();
+      cs_.Enter();
+      refcount_--;
+    }
+  } else {
+    ASSERT(false);
+  }
+}
+
+void SignalThread::Release() {
+  EnterExit ee(this);
+  ASSERT(main_->IsCurrent());
+  if (kComplete == state_) {
+    refcount_--;
+  } else if (kRunning == state_) {
+    state_ = kReleasing;
+  } else {
+    // if (kInit == state_) use Destroy()
+    ASSERT(false);
+  }
+}
+
+bool SignalThread::ContinueWork() {
+  EnterExit ee(this);
+  ASSERT(worker_.IsCurrent());
+  return worker_.ProcessMessages(0);
+}
+
+void SignalThread::OnMessage(Message *msg) {
+  EnterExit ee(this);
+  if (ST_MSG_WORKER_DONE == msg->message_id) {
+    ASSERT(main_->IsCurrent());
+    OnWorkDone();
+    bool do_delete = false;
+    if (kRunning == state_) {
+      state_ = kComplete;
+    } else {
+      do_delete = true;
+    }
+    if (kStopping != state_) {
+      // Before signaling that the work is done, make sure that the worker
+      // thread actually is done. We got here because DoWork() finished and
+      // Run() posted the ST_MSG_WORKER_DONE message. This means the worker
+      // thread is about to go away anyway, but sometimes it doesn't actually
+      // finish before SignalWorkDone is processed, and for a reusable
+      // SignalThread this makes an assert in thread.cc fire.
+      //
+      // Calling Stop() on the worker ensures that the OS thread that underlies
+      // the worker will finish, and will be set to NULL, enabling us to call
+      // Start() again.
+      worker_.Stop();
+      SignalWorkDone(this);
+    }
+    if (do_delete) {
+      refcount_--;
+    }
+  }
+}
+
+void SignalThread::Run() {
+  DoWork();
+  {
+    EnterExit ee(this);
+    if (main_) {
+      main_->Post(this, ST_MSG_WORKER_DONE);
+    }
+  }
+}
+
+void SignalThread::OnMainThreadDestroyed() {
+  EnterExit ee(this);
+  main_ = NULL;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/signalthread.h b/talk/base/signalthread.h
new file mode 100644
index 0000000..79c00be
--- /dev/null
+++ b/talk/base/signalthread.h
@@ -0,0 +1,172 @@
+/*
+ * libjingle
+ * Copyright 2004--2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_SIGNALTHREAD_H_
+#define TALK_BASE_SIGNALTHREAD_H_
+
+#include <string>
+
+#include "talk/base/constructormagic.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// SignalThread - Base class for worker threads.  The main thread should call
+//  Start() to begin work, and then follow one of these models:
+//   Normal: Wait for SignalWorkDone, and then call Release to destroy.
+//   Cancellation: Call Release(true), to abort the worker thread.
+//   Fire-and-forget: Call Release(false), which allows the thread to run to
+//    completion, and then self-destruct without further notification.
+//   Periodic tasks: Wait for SignalWorkDone, then eventually call Start()
+//    again to repeat the task. When the instance isn't needed anymore,
+//    call Release. DoWork, OnWorkStart and OnWorkStop are called again,
+//    on a new thread.
+//  The subclass should override DoWork() to perform the background task.  By
+//   periodically calling ContinueWork(), it can check for cancellation.
+//   OnWorkStart and OnWorkDone can be overridden to do pre- or post-work
+//   tasks in the context of the main thread.
+///////////////////////////////////////////////////////////////////////////////
+
+class SignalThread
+    : public sigslot::has_slots<>,
+      protected MessageHandler {
+ public:
+  SignalThread();
+
+  // Context: Main Thread.  Call before Start to change the worker's name.
+  bool SetName(const std::string& name, const void* obj);
+
+  // Context: Main Thread.  Call before Start to change the worker's priority.
+  bool SetPriority(ThreadPriority priority);
+
+  // Context: Main Thread.  Call to begin the worker thread.
+  void Start();
+
+  // Context: Main Thread.  If the worker thread is not running, deletes the
+  // object immediately.  Otherwise, asks the worker thread to abort processing,
+  // and schedules the object to be deleted once the worker exits.
+  // SignalWorkDone will not be signalled.  If wait is true, does not return
+  // until the thread is deleted.
+  void Destroy(bool wait);
+
+  // Context: Main Thread.  If the worker thread is complete, deletes the
+  // object immediately.  Otherwise, schedules the object to be deleted once
+  // the worker thread completes.  SignalWorkDone will be signalled.
+  void Release();
+
+  // Context: Main Thread.  Signalled when work is complete.
+  sigslot::signal1<SignalThread *> SignalWorkDone;
+
+  enum { ST_MSG_WORKER_DONE, ST_MSG_FIRST_AVAILABLE };
+
+ protected:
+  virtual ~SignalThread();
+
+  Thread* worker() { return &worker_; }
+
+  // Context: Main Thread.  Subclass should override to do pre-work setup.
+  virtual void OnWorkStart() { }
+
+  // Context: Worker Thread.  Subclass should override to do work.
+  virtual void DoWork() = 0;
+
+  // Context: Worker Thread.  Subclass should call periodically to
+  // dispatch messages and determine if the thread should terminate.
+  bool ContinueWork();
+
+  // Context: Worker Thread.  Subclass should override when extra work is
+  // needed to abort the worker thread.
+  virtual void OnWorkStop() { }
+
+  // Context: Main Thread.  Subclass should override to do post-work cleanup.
+  virtual void OnWorkDone() { }
+
+  // Context: Any Thread.  If subclass overrides, be sure to call the base
+  // implementation.  Do not use (message_id < ST_MSG_FIRST_AVAILABLE)
+  virtual void OnMessage(Message *msg);
+
+ private:
+  enum State {
+    kInit,            // Initialized, but not started
+    kRunning,         // Started and doing work
+    kReleasing,       // Same as running, but to be deleted when work is done
+    kComplete,        // Work is done
+    kStopping,        // Work is being interrupted
+  };
+
+  class Worker : public Thread {
+   public:
+    explicit Worker(SignalThread* parent) : parent_(parent) {}
+    virtual void Run() { parent_->Run(); }
+
+   private:
+    SignalThread* parent_;
+
+    DISALLOW_IMPLICIT_CONSTRUCTORS(Worker);
+  };
+
+  class EnterExit {
+   public:
+    explicit EnterExit(SignalThread* t) : t_(t) {
+      t_->cs_.Enter();
+      // If refcount_ is zero then the object has already been deleted and we
+      // will be double-deleting it in ~EnterExit()! (shouldn't happen)
+      ASSERT(t_->refcount_ != 0);
+      ++t_->refcount_;
+    }
+    ~EnterExit() {
+      bool d = (0 == --t_->refcount_);
+      t_->cs_.Leave();
+      if (d)
+        delete t_;
+    }
+
+   private:
+    SignalThread* t_;
+
+    DISALLOW_IMPLICIT_CONSTRUCTORS(EnterExit);
+  };
+
+  void Run();
+  void OnMainThreadDestroyed();
+
+  Thread* main_;
+  Worker worker_;
+  CriticalSection cs_;
+  State state_;
+  int refcount_;
+
+  DISALLOW_COPY_AND_ASSIGN(SignalThread);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SIGNALTHREAD_H_
diff --git a/talk/base/signalthread_unittest.cc b/talk/base/signalthread_unittest.cc
new file mode 100644
index 0000000..4ad5961
--- /dev/null
+++ b/talk/base/signalthread_unittest.cc
@@ -0,0 +1,209 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/signalthread.h"
+#include "talk/base/thread.h"
+
+using namespace talk_base;
+
+class SignalThreadTest : public testing::Test, public sigslot::has_slots<> {
+ public:
+  class SlowSignalThread : public SignalThread {
+   public:
+    SlowSignalThread(SignalThreadTest* harness) : harness_(harness) {
+    }
+
+    virtual ~SlowSignalThread() {
+      EXPECT_EQ(harness_->main_thread_, Thread::Current());
+      ++harness_->thread_deleted_;
+    }
+
+    const SignalThreadTest* harness() { return harness_; }
+
+   protected:
+    virtual void OnWorkStart() {
+      ASSERT_TRUE(harness_ != NULL);
+      ++harness_->thread_started_;
+      EXPECT_EQ(harness_->main_thread_, Thread::Current());
+      EXPECT_FALSE(worker()->started());  // not started yet
+    }
+
+    virtual void OnWorkStop() {
+      ++harness_->thread_stopped_;
+      EXPECT_EQ(harness_->main_thread_, Thread::Current());
+      EXPECT_TRUE(worker()->started());  // not stopped yet
+    }
+
+    virtual void OnWorkDone() {
+      ++harness_->thread_done_;
+      EXPECT_EQ(harness_->main_thread_, Thread::Current());
+      EXPECT_TRUE(worker()->started());  // not stopped yet
+    }
+
+    virtual void DoWork() {
+      EXPECT_NE(harness_->main_thread_, Thread::Current());
+      EXPECT_EQ(worker(), Thread::Current());
+      Thread::Current()->socketserver()->Wait(250, false);
+    }
+
+   private:
+    SignalThreadTest* harness_;
+    DISALLOW_EVIL_CONSTRUCTORS(SlowSignalThread);
+  };
+
+  void OnWorkComplete(talk_base::SignalThread* thread) {
+    SlowSignalThread* t = static_cast<SlowSignalThread*>(thread);
+    EXPECT_EQ(t->harness(), this);
+    EXPECT_EQ(main_thread_, Thread::Current());
+
+    ++thread_completed_;
+    if (!called_release_) {
+      thread->Release();
+    }
+  }
+
+  virtual void SetUp() {
+    main_thread_ = Thread::Current();
+    thread_ = new SlowSignalThread(this);
+    thread_->SignalWorkDone.connect(this, &SignalThreadTest::OnWorkComplete);
+    called_release_ = false;
+    thread_started_ = 0;
+    thread_done_ = 0;
+    thread_completed_ = 0;
+    thread_stopped_ = 0;
+    thread_deleted_ = 0;
+  }
+
+  virtual void TearDown() {
+  }
+
+  Thread* main_thread_;
+  SlowSignalThread* thread_;
+  bool called_release_;
+
+  int thread_started_;
+  int thread_done_;
+  int thread_completed_;
+  int thread_stopped_;
+  int thread_deleted_;
+};
+
+class OwnerThread : public Thread, public sigslot::has_slots<> {
+ public:
+  explicit OwnerThread(SignalThreadTest* harness)
+      : harness_(harness),
+        has_run_(false) {
+  }
+
+  virtual void Run() {
+    SignalThreadTest::SlowSignalThread* signal_thread =
+        new SignalThreadTest::SlowSignalThread(harness_);
+    signal_thread->SignalWorkDone.connect(this, &OwnerThread::OnWorkDone);
+    signal_thread->Start();
+    Thread::Current()->socketserver()->Wait(100, false);
+    signal_thread->Release();
+    has_run_ = true;
+  }
+
+  bool has_run() { return has_run_; }
+  void OnWorkDone(SignalThread* signal_thread) {
+    FAIL() << " This shouldn't get called.";
+  }
+
+ private:
+  SignalThreadTest* harness_;
+  bool has_run_;
+  DISALLOW_EVIL_CONSTRUCTORS(OwnerThread);
+};
+
+// Test for when the main thread goes away while the
+// signal thread is still working.  This may happen
+// when shutting down the process.
+TEST_F(SignalThreadTest, OwnerThreadGoesAway) {
+  {
+    scoped_ptr<OwnerThread> owner(new OwnerThread(this));
+    main_thread_ = owner.get();
+    owner->Start();
+    while (!owner->has_run()) {
+      Thread::Current()->socketserver()->Wait(10, false);
+    }
+  }
+  // At this point the main thread has gone away.
+  // Give the SignalThread a little time to do its callback,
+  // which will crash if the signal thread doesn't handle
+  // this situation well.
+  Thread::Current()->socketserver()->Wait(500, false);
+}
+
+#define EXPECT_STATE(started, done, completed, stopped, deleted) \
+  EXPECT_EQ(started, thread_started_); \
+  EXPECT_EQ(done, thread_done_); \
+  EXPECT_EQ(completed, thread_completed_); \
+  EXPECT_EQ(stopped, thread_stopped_); \
+  EXPECT_EQ(deleted, thread_deleted_);
+
+TEST_F(SignalThreadTest, ThreadFinishes) {
+  thread_->Start();
+  EXPECT_STATE(1, 0, 0, 0, 0);
+  Thread::SleepMs(500);
+  EXPECT_STATE(1, 0, 0, 0, 0);
+  Thread::Current()->ProcessMessages(0);
+  EXPECT_STATE(1, 1, 1, 0, 1);
+}
+
+TEST_F(SignalThreadTest, ReleasedThreadFinishes) {
+  thread_->Start();
+  EXPECT_STATE(1, 0, 0, 0, 0);
+  thread_->Release();
+  called_release_ = true;
+  EXPECT_STATE(1, 0, 0, 0, 0);
+  Thread::SleepMs(500);
+  EXPECT_STATE(1, 0, 0, 0, 0);
+  Thread::Current()->ProcessMessages(0);
+  EXPECT_STATE(1, 1, 1, 0, 1);
+}
+
+TEST_F(SignalThreadTest, DestroyedThreadCleansUp) {
+  thread_->Start();
+  EXPECT_STATE(1, 0, 0, 0, 0);
+  thread_->Destroy(true);
+  EXPECT_STATE(1, 0, 0, 1, 1);
+  Thread::Current()->ProcessMessages(0);
+  EXPECT_STATE(1, 0, 0, 1, 1);
+}
+
+TEST_F(SignalThreadTest, DeferredDestroyedThreadCleansUp) {
+  thread_->Start();
+  EXPECT_STATE(1, 0, 0, 0, 0);
+  thread_->Destroy(false);
+  EXPECT_STATE(1, 0, 0, 1, 0);
+  Thread::SleepMs(500);
+  EXPECT_STATE(1, 0, 0, 1, 0);
+  Thread::Current()->ProcessMessages(0);
+  EXPECT_STATE(1, 1, 0, 1, 1);
+}
diff --git a/talk/base/sigslot.h b/talk/base/sigslot.h
new file mode 100644
index 0000000..192aaa7
--- /dev/null
+++ b/talk/base/sigslot.h
@@ -0,0 +1,2850 @@
+// sigslot.h: Signal/Slot classes
+//
+// Written by Sarah Thompson (sarah@telergy.com) 2002.
+//
+// License: Public domain. You are free to use this code however you like, with the proviso that
+//          the author takes on no responsibility or liability for any use.
+//
+// QUICK DOCUMENTATION
+//
+//				(see also the full documentation at http://sigslot.sourceforge.net/)
+//
+//		#define switches
+//			SIGSLOT_PURE_ISO			- Define this to force ISO C++ compliance. This also disables
+//										  all of the thread safety support on platforms where it is
+//										  available.
+//
+//			SIGSLOT_USE_POSIX_THREADS	- Force use of Posix threads when using a C++ compiler other than
+//										  gcc on a platform that supports Posix threads. (When using gcc,
+//										  this is the default - use SIGSLOT_PURE_ISO to disable this if
+//										  necessary)
+//
+//			SIGSLOT_DEFAULT_MT_POLICY	- Where thread support is enabled, this defaults to multi_threaded_global.
+//										  Otherwise, the default is single_threaded. #define this yourself to
+//										  override the default. In pure ISO mode, anything other than
+//										  single_threaded will cause a compiler error.
+//
+//		PLATFORM NOTES
+//
+//			Win32						- On Win32, the WIN32 symbol must be #defined. Most mainstream
+//										  compilers do this by default, but you may need to define it
+//										  yourself if your build environment is less standard. This causes
+//										  the Win32 thread support to be compiled in and used automatically.
+//
+//			Unix/Linux/BSD, etc.		- If you're using gcc, it is assumed that you have Posix threads
+//										  available, so they are used automatically. You can override this
+//										  (as under Windows) with the SIGSLOT_PURE_ISO switch. If you're using
+//										  something other than gcc but still want to use Posix threads, you
+//										  need to #define SIGSLOT_USE_POSIX_THREADS.
+//
+//			ISO C++						- If none of the supported platforms are detected, or if
+//										  SIGSLOT_PURE_ISO is defined, all multithreading support is turned off,
+//										  along with any code that might cause a pure ISO C++ environment to
+//										  complain. Before you ask, gcc -ansi -pedantic won't compile this
+//										  library, but gcc -ansi is fine. Pedantic mode seems to throw a lot of
+//										  errors that aren't really there. If you feel like investigating this,
+//										  please contact the author.
+//
+//
+//		THREADING MODES
+//
+//			single_threaded				- Your program is assumed to be single threaded from the point of view
+//										  of signal/slot usage (i.e. all objects using signals and slots are
+//										  created and destroyed from a single thread). Behaviour if objects are
+//										  destroyed concurrently is undefined (i.e. you'll get the occasional
+//										  segmentation fault/memory exception).
+//
+//			multi_threaded_global		- Your program is assumed to be multi threaded. Objects using signals and
+//										  slots can be safely created and destroyed from any thread, even when
+//										  connections exist. In multi_threaded_global mode, this is achieved by a
+//										  single global mutex (actually a critical section on Windows because they
+//										  are faster). This option uses less OS resources, but results in more
+//										  opportunities for contention, possibly resulting in more context switches
+//										  than are strictly necessary.
+//
+//			multi_threaded_local		- Behaviour in this mode is essentially the same as multi_threaded_global,
+//										  except that each signal, and each object that inherits has_slots, all
+//										  have their own mutex/critical section. In practice, this means that
+//										  mutex collisions (and hence context switches) only happen if they are
+//										  absolutely essential. However, on some platforms, creating a lot of
+//										  mutexes can slow down the whole OS, so use this option with care.
+//
+//		USING THE LIBRARY
+//
+//			See the full documentation at http://sigslot.sourceforge.net/
+//
+//
+// Libjingle specific:
+// This file has been modified such that has_slots and signalx do not have to be
+// using the same threading requirements. E.g. it is possible to connect a
+// has_slots<single_threaded> and signal0<multi_threaded_local> or
+// has_slots<multi_threaded_local> and signal0<single_threaded>.
+// If has_slots is single threaded the user must ensure that it is not trying
+// to connect or disconnect to signalx concurrently or data race may occur.
+// If signalx is single threaded the user must ensure that disconnect, connect
+// or signal is not happening concurrently or data race may occur.
+
+#ifndef TALK_BASE_SIGSLOT_H__
+#define TALK_BASE_SIGSLOT_H__
+
+#include <list>
+#include <set>
+#include <stdlib.h>
+
+// On our copy of sigslot.h, we set single threading as default.
+#define SIGSLOT_DEFAULT_MT_POLICY single_threaded
+
+#if defined(SIGSLOT_PURE_ISO) || (!defined(WIN32) && !defined(__GNUG__) && !defined(SIGSLOT_USE_POSIX_THREADS))
+#	define _SIGSLOT_SINGLE_THREADED
+#elif defined(WIN32)
+#	define _SIGSLOT_HAS_WIN32_THREADS
+#	if !defined(WIN32_LEAN_AND_MEAN)
+#		define WIN32_LEAN_AND_MEAN
+#	endif
+#	include "talk/base/win32.h"
+#elif defined(__GNUG__) || defined(SIGSLOT_USE_POSIX_THREADS)
+#	define _SIGSLOT_HAS_POSIX_THREADS
+#	include <pthread.h>
+#else
+#	define _SIGSLOT_SINGLE_THREADED
+#endif
+
+#ifndef SIGSLOT_DEFAULT_MT_POLICY
+#	ifdef _SIGSLOT_SINGLE_THREADED
+#		define SIGSLOT_DEFAULT_MT_POLICY single_threaded
+#	else
+#		define SIGSLOT_DEFAULT_MT_POLICY multi_threaded_local
+#	endif
+#endif
+
+// TODO: change this namespace to talk_base?
+namespace sigslot {
+
+	class single_threaded
+	{
+	public:
+		single_threaded()
+		{
+			;
+		}
+
+		virtual ~single_threaded()
+		{
+			;
+		}
+
+		virtual void lock()
+		{
+			;
+		}
+
+		virtual void unlock()
+		{
+			;
+		}
+	};
+
+#ifdef _SIGSLOT_HAS_WIN32_THREADS
+	// The multi threading policies only get compiled in if they are enabled.
+	class multi_threaded_global
+	{
+	public:
+		multi_threaded_global()
+		{
+			static bool isinitialised = false;
+
+			if(!isinitialised)
+			{
+				InitializeCriticalSection(get_critsec());
+				isinitialised = true;
+			}
+		}
+
+		multi_threaded_global(const multi_threaded_global&)
+		{
+			;
+		}
+
+		virtual ~multi_threaded_global()
+		{
+			;
+		}
+
+		virtual void lock()
+		{
+			EnterCriticalSection(get_critsec());
+		}
+
+		virtual void unlock()
+		{
+			LeaveCriticalSection(get_critsec());
+		}
+
+	private:
+		CRITICAL_SECTION* get_critsec()
+		{
+			static CRITICAL_SECTION g_critsec;
+			return &g_critsec;
+		}
+	};
+
+	class multi_threaded_local
+	{
+	public:
+		multi_threaded_local()
+		{
+			InitializeCriticalSection(&m_critsec);
+		}
+
+		multi_threaded_local(const multi_threaded_local&)
+		{
+			InitializeCriticalSection(&m_critsec);
+		}
+
+		virtual ~multi_threaded_local()
+		{
+			DeleteCriticalSection(&m_critsec);
+		}
+
+		virtual void lock()
+		{
+			EnterCriticalSection(&m_critsec);
+		}
+
+		virtual void unlock()
+		{
+			LeaveCriticalSection(&m_critsec);
+		}
+
+	private:
+		CRITICAL_SECTION m_critsec;
+	};
+#endif // _SIGSLOT_HAS_WIN32_THREADS
+
+#ifdef _SIGSLOT_HAS_POSIX_THREADS
+	// The multi threading policies only get compiled in if they are enabled.
+	class multi_threaded_global
+	{
+	public:
+		multi_threaded_global()
+		{
+			pthread_mutex_init(get_mutex(), NULL);
+		}
+
+		multi_threaded_global(const multi_threaded_global&)
+		{
+			;
+		}
+
+		virtual ~multi_threaded_global()
+		{
+			;
+		}
+
+		virtual void lock()
+		{
+			pthread_mutex_lock(get_mutex());
+		}
+
+		virtual void unlock()
+		{
+			pthread_mutex_unlock(get_mutex());
+		}
+
+	private:
+		pthread_mutex_t* get_mutex()
+		{
+			static pthread_mutex_t g_mutex;
+			return &g_mutex;
+		}
+	};
+
+	class multi_threaded_local
+	{
+	public:
+		multi_threaded_local()
+		{
+			pthread_mutex_init(&m_mutex, NULL);
+		}
+
+		multi_threaded_local(const multi_threaded_local&)
+		{
+			pthread_mutex_init(&m_mutex, NULL);
+		}
+
+		virtual ~multi_threaded_local()
+		{
+			pthread_mutex_destroy(&m_mutex);
+		}
+
+		virtual void lock()
+		{
+			pthread_mutex_lock(&m_mutex);
+		}
+
+		virtual void unlock()
+		{
+			pthread_mutex_unlock(&m_mutex);
+		}
+
+	private:
+		pthread_mutex_t m_mutex;
+	};
+#endif // _SIGSLOT_HAS_POSIX_THREADS
+
+	template<class mt_policy>
+	class lock_block
+	{
+	public:
+		mt_policy *m_mutex;
+
+		lock_block(mt_policy *mtx)
+			: m_mutex(mtx)
+		{
+			m_mutex->lock();
+		}
+
+		~lock_block()
+		{
+			m_mutex->unlock();
+		}
+	};
+
+	class has_slots_interface;
+
+	template<class mt_policy>
+	class _connection_base0
+	{
+	public:
+		virtual ~_connection_base0() {}
+		virtual has_slots_interface* getdest() const = 0;
+		virtual void emit() = 0;
+		virtual _connection_base0* clone() = 0;
+		virtual _connection_base0* duplicate(has_slots_interface* pnewdest) = 0;
+	};
+
+	template<class arg1_type, class mt_policy>
+	class _connection_base1
+	{
+	public:
+		virtual ~_connection_base1() {}
+		virtual has_slots_interface* getdest() const = 0;
+		virtual void emit(arg1_type) = 0;
+		virtual _connection_base1<arg1_type, mt_policy>* clone() = 0;
+		virtual _connection_base1<arg1_type, mt_policy>* duplicate(has_slots_interface* pnewdest) = 0;
+	};
+
+	template<class arg1_type, class arg2_type, class mt_policy>
+	class _connection_base2
+	{
+	public:
+		virtual ~_connection_base2() {}
+		virtual has_slots_interface* getdest() const = 0;
+		virtual void emit(arg1_type, arg2_type) = 0;
+		virtual _connection_base2<arg1_type, arg2_type, mt_policy>* clone() = 0;
+		virtual _connection_base2<arg1_type, arg2_type, mt_policy>* duplicate(has_slots_interface* pnewdest) = 0;
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class mt_policy>
+	class _connection_base3
+	{
+	public:
+		virtual ~_connection_base3() {}
+		virtual has_slots_interface* getdest() const = 0;
+		virtual void emit(arg1_type, arg2_type, arg3_type) = 0;
+		virtual _connection_base3<arg1_type, arg2_type, arg3_type, mt_policy>* clone() = 0;
+		virtual _connection_base3<arg1_type, arg2_type, arg3_type, mt_policy>* duplicate(has_slots_interface* pnewdest) = 0;
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type, class mt_policy>
+	class _connection_base4
+	{
+	public:
+		virtual ~_connection_base4() {}
+		virtual has_slots_interface* getdest() const = 0;
+		virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type) = 0;
+		virtual _connection_base4<arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>* clone() = 0;
+		virtual _connection_base4<arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>* duplicate(has_slots_interface* pnewdest) = 0;
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+	class arg5_type, class mt_policy>
+	class _connection_base5
+	{
+	public:
+		virtual ~_connection_base5() {}
+		virtual has_slots_interface* getdest() const = 0;
+		virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type) = 0;
+		virtual _connection_base5<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, mt_policy>* clone() = 0;
+		virtual _connection_base5<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, mt_policy>* duplicate(has_slots_interface* pnewdest) = 0;
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+	class arg5_type, class arg6_type, class mt_policy>
+	class _connection_base6
+	{
+	public:
+		virtual ~_connection_base6() {}
+		virtual has_slots_interface* getdest() const = 0;
+		virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type,
+			arg6_type) = 0;
+		virtual _connection_base6<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, mt_policy>* clone() = 0;
+		virtual _connection_base6<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, mt_policy>* duplicate(has_slots_interface* pnewdest) = 0;
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+	class arg5_type, class arg6_type, class arg7_type, class mt_policy>
+	class _connection_base7
+	{
+	public:
+		virtual ~_connection_base7() {}
+		virtual has_slots_interface* getdest() const = 0;
+		virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type,
+			arg6_type, arg7_type) = 0;
+		virtual _connection_base7<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, mt_policy>* clone() = 0;
+		virtual _connection_base7<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, mt_policy>* duplicate(has_slots_interface* pnewdest) = 0;
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+	class arg5_type, class arg6_type, class arg7_type, class arg8_type, class mt_policy>
+	class _connection_base8
+	{
+	public:
+		virtual ~_connection_base8() {}
+		virtual has_slots_interface* getdest() const = 0;
+		virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type,
+			arg6_type, arg7_type, arg8_type) = 0;
+		virtual _connection_base8<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>* clone() = 0;
+		virtual _connection_base8<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>* duplicate(has_slots_interface* pnewdest) = 0;
+	};
+
+	class _signal_base_interface
+	{
+	public:
+		virtual void slot_disconnect(has_slots_interface* pslot) = 0;
+		virtual void slot_duplicate(const has_slots_interface* poldslot, has_slots_interface* pnewslot) = 0;
+	};
+
+	template<class mt_policy>
+	class _signal_base : public _signal_base_interface, public mt_policy
+	{
+	};
+
+	class has_slots_interface
+	{
+	public:
+		has_slots_interface()
+		{
+			;
+		}
+
+		virtual void signal_connect(_signal_base_interface* sender) = 0;
+
+		virtual void signal_disconnect(_signal_base_interface* sender) = 0;
+
+		virtual ~has_slots_interface()
+		{
+		}
+
+		virtual void disconnect_all() = 0;
+	};
+
+	template<class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+	class has_slots : public has_slots_interface, public mt_policy
+	{
+	private:
+		typedef std::set<_signal_base_interface*> sender_set;
+		typedef sender_set::const_iterator const_iterator;
+
+	public:
+		has_slots()
+		{
+			;
+		}
+
+		has_slots(const has_slots& hs)
+		{
+			lock_block<mt_policy> lock(this);
+			const_iterator it = hs.m_senders.begin();
+			const_iterator itEnd = hs.m_senders.end();
+
+			while(it != itEnd)
+			{
+				(*it)->slot_duplicate(&hs, this);
+				m_senders.insert(*it);
+				++it;
+			}
+		}
+
+		void signal_connect(_signal_base_interface* sender)
+		{
+			lock_block<mt_policy> lock(this);
+			m_senders.insert(sender);
+		}
+
+		void signal_disconnect(_signal_base_interface* sender)
+		{
+			lock_block<mt_policy> lock(this);
+			m_senders.erase(sender);
+		}
+
+		virtual ~has_slots()
+		{
+			disconnect_all();
+		}
+
+		void disconnect_all()
+		{
+			lock_block<mt_policy> lock(this);
+			const_iterator it = m_senders.begin();
+			const_iterator itEnd = m_senders.end();
+
+			while(it != itEnd)
+			{
+				(*it)->slot_disconnect(this);
+				++it;
+			}
+
+			m_senders.erase(m_senders.begin(), m_senders.end());
+		}
+
+	private:
+		sender_set m_senders;
+	};
+
+	template<class mt_policy>
+	class _signal_base0 : public _signal_base<mt_policy>
+	{
+	public:
+		typedef std::list<_connection_base0<mt_policy> *>  connections_list;
+
+		_signal_base0()
+		{
+			;
+		}
+
+		_signal_base0(const _signal_base0& s)
+			: _signal_base<mt_policy>(s)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = s.m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_connect(this);
+				m_connected_slots.push_back((*it)->clone());
+
+				++it;
+			}
+		}
+
+		~_signal_base0()
+		{
+			disconnect_all();
+		}
+
+		bool is_empty()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			return it == itEnd;
+		}
+
+		void disconnect_all()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_disconnect(this);
+				delete *it;
+
+				++it;
+			}
+
+			m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+		}
+
+#ifdef _DEBUG
+			bool connected(has_slots_interface* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+				if ((*it)->getdest() == pclass)
+					return true;
+				it = itNext;
+			}
+			return false;
+		}
+#endif
+
+		void disconnect(has_slots_interface* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == pclass)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+					pclass->signal_disconnect(this);
+					return;
+				}
+
+				++it;
+			}
+		}
+
+		void slot_disconnect(has_slots_interface* pslot)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				typename connections_list::iterator itNext = it;
+				++itNext;
+
+				if((*it)->getdest() == pslot)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+				}
+
+				it = itNext;
+			}
+		}
+
+		void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == oldtarget)
+				{
+					m_connected_slots.push_back((*it)->duplicate(newtarget));
+				}
+
+				++it;
+			}
+		}
+
+	protected:
+		connections_list m_connected_slots;
+	};
+
+	template<class arg1_type, class mt_policy>
+	class _signal_base1 : public _signal_base<mt_policy>
+	{
+	public:
+		typedef std::list<_connection_base1<arg1_type, mt_policy> *>  connections_list;
+
+		_signal_base1()
+		{
+			;
+		}
+
+		_signal_base1(const _signal_base1<arg1_type, mt_policy>& s)
+			: _signal_base<mt_policy>(s)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = s.m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_connect(this);
+				m_connected_slots.push_back((*it)->clone());
+
+				++it;
+			}
+		}
+
+		void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == oldtarget)
+				{
+					m_connected_slots.push_back((*it)->duplicate(newtarget));
+				}
+
+				++it;
+			}
+		}
+
+		~_signal_base1()
+		{
+			disconnect_all();
+		}
+
+		bool is_empty()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			return it == itEnd;
+		}
+
+		void disconnect_all()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_disconnect(this);
+				delete *it;
+
+				++it;
+			}
+
+			m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+		}
+
+#ifdef _DEBUG
+			bool connected(has_slots_interface* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+				if ((*it)->getdest() == pclass)
+					return true;
+				it = itNext;
+			}
+			return false;
+		}
+#endif
+
+		void disconnect(has_slots_interface* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == pclass)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+					pclass->signal_disconnect(this);
+					return;
+				}
+
+				++it;
+			}
+		}
+
+		void slot_disconnect(has_slots_interface* pslot)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				typename connections_list::iterator itNext = it;
+				++itNext;
+
+				if((*it)->getdest() == pslot)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+				}
+
+				it = itNext;
+			}
+		}
+
+
+	protected:
+		connections_list m_connected_slots;
+	};
+
+	template<class arg1_type, class arg2_type, class mt_policy>
+	class _signal_base2 : public _signal_base<mt_policy>
+	{
+	public:
+		typedef std::list<_connection_base2<arg1_type, arg2_type, mt_policy> *>
+			connections_list;
+
+		_signal_base2()
+		{
+			;
+		}
+
+		_signal_base2(const _signal_base2<arg1_type, arg2_type, mt_policy>& s)
+			: _signal_base<mt_policy>(s)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = s.m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_connect(this);
+				m_connected_slots.push_back((*it)->clone());
+
+				++it;
+			}
+		}
+
+		void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == oldtarget)
+				{
+					m_connected_slots.push_back((*it)->duplicate(newtarget));
+				}
+
+				++it;
+			}
+		}
+
+		~_signal_base2()
+		{
+			disconnect_all();
+		}
+
+		bool is_empty()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			return it == itEnd;
+		}
+
+		void disconnect_all()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_disconnect(this);
+				delete *it;
+
+				++it;
+			}
+
+			m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+		}
+
+#ifdef _DEBUG
+			bool connected(has_slots_interface* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+				if ((*it)->getdest() == pclass)
+					return true;
+				it = itNext;
+			}
+			return false;
+		}
+#endif
+
+		void disconnect(has_slots_interface* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == pclass)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+					pclass->signal_disconnect(this);
+					return;
+				}
+
+				++it;
+			}
+		}
+
+		void slot_disconnect(has_slots_interface* pslot)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				typename connections_list::iterator itNext = it;
+				++itNext;
+
+				if((*it)->getdest() == pslot)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+				}
+
+				it = itNext;
+			}
+		}
+
+	protected:
+		connections_list m_connected_slots;
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class mt_policy>
+	class _signal_base3 : public _signal_base<mt_policy>
+	{
+	public:
+		typedef std::list<_connection_base3<arg1_type, arg2_type, arg3_type, mt_policy> *>
+			connections_list;
+
+		_signal_base3()
+		{
+			;
+		}
+
+		_signal_base3(const _signal_base3<arg1_type, arg2_type, arg3_type, mt_policy>& s)
+			: _signal_base<mt_policy>(s)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = s.m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_connect(this);
+				m_connected_slots.push_back((*it)->clone());
+
+				++it;
+			}
+		}
+
+		void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == oldtarget)
+				{
+					m_connected_slots.push_back((*it)->duplicate(newtarget));
+				}
+
+				++it;
+			}
+		}
+
+		~_signal_base3()
+		{
+			disconnect_all();
+		}
+
+		bool is_empty()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			return it == itEnd;
+		}
+
+		void disconnect_all()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_disconnect(this);
+				delete *it;
+
+				++it;
+			}
+
+			m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+		}
+
+#ifdef _DEBUG
+			bool connected(has_slots_interface* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+				if ((*it)->getdest() == pclass)
+					return true;
+				it = itNext;
+			}
+			return false;
+		}
+#endif
+
+		void disconnect(has_slots_interface* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == pclass)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+					pclass->signal_disconnect(this);
+					return;
+				}
+
+				++it;
+			}
+		}
+
+		void slot_disconnect(has_slots_interface* pslot)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				typename connections_list::iterator itNext = it;
+				++itNext;
+
+				if((*it)->getdest() == pslot)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+				}
+
+				it = itNext;
+			}
+		}
+
+	protected:
+		connections_list m_connected_slots;
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type, class mt_policy>
+	class _signal_base4 : public _signal_base<mt_policy>
+	{
+	public:
+		typedef std::list<_connection_base4<arg1_type, arg2_type, arg3_type,
+			arg4_type, mt_policy> *>  connections_list;
+
+		_signal_base4()
+		{
+			;
+		}
+
+		_signal_base4(const _signal_base4<arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>& s)
+			: _signal_base<mt_policy>(s)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = s.m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_connect(this);
+				m_connected_slots.push_back((*it)->clone());
+
+				++it;
+			}
+		}
+
+		void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == oldtarget)
+				{
+					m_connected_slots.push_back((*it)->duplicate(newtarget));
+				}
+
+				++it;
+			}
+		}
+
+		~_signal_base4()
+		{
+			disconnect_all();
+		}
+
+		bool is_empty()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			return it == itEnd;
+		}
+
+		void disconnect_all()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_disconnect(this);
+				delete *it;
+
+				++it;
+			}
+
+			m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+		}
+
+#ifdef _DEBUG
+			bool connected(has_slots_interface* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+				if ((*it)->getdest() == pclass)
+					return true;
+				it = itNext;
+			}
+			return false;
+		}
+#endif
+
+		void disconnect(has_slots_interface* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == pclass)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+					pclass->signal_disconnect(this);
+					return;
+				}
+
+				++it;
+			}
+		}
+
+		void slot_disconnect(has_slots_interface* pslot)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				typename connections_list::iterator itNext = it;
+				++itNext;
+
+				if((*it)->getdest() == pslot)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+				}
+
+				it = itNext;
+			}
+		}
+
+	protected:
+		connections_list m_connected_slots;
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+	class arg5_type, class mt_policy>
+	class _signal_base5 : public _signal_base<mt_policy>
+	{
+	public:
+		typedef std::list<_connection_base5<arg1_type, arg2_type, arg3_type,
+			arg4_type, arg5_type, mt_policy> *>  connections_list;
+
+		_signal_base5()
+		{
+			;
+		}
+
+		_signal_base5(const _signal_base5<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, mt_policy>& s)
+			: _signal_base<mt_policy>(s)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = s.m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_connect(this);
+				m_connected_slots.push_back((*it)->clone());
+
+				++it;
+			}
+		}
+
+		void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == oldtarget)
+				{
+					m_connected_slots.push_back((*it)->duplicate(newtarget));
+				}
+
+				++it;
+			}
+		}
+
+		~_signal_base5()
+		{
+			disconnect_all();
+		}
+
+		bool is_empty()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			return it == itEnd;
+		}
+
+		void disconnect_all()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_disconnect(this);
+				delete *it;
+
+				++it;
+			}
+
+			m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+		}
+
+#ifdef _DEBUG
+			bool connected(has_slots_interface* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+				if ((*it)->getdest() == pclass)
+					return true;
+				it = itNext;
+			}
+			return false;
+		}
+#endif
+
+		void disconnect(has_slots_interface* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == pclass)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+					pclass->signal_disconnect(this);
+					return;
+				}
+
+				++it;
+			}
+		}
+
+		void slot_disconnect(has_slots_interface* pslot)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				typename connections_list::iterator itNext = it;
+				++itNext;
+
+				if((*it)->getdest() == pslot)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+				}
+
+				it = itNext;
+			}
+		}
+
+	protected:
+		connections_list m_connected_slots;
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+	class arg5_type, class arg6_type, class mt_policy>
+	class _signal_base6 : public _signal_base<mt_policy>
+	{
+	public:
+		typedef std::list<_connection_base6<arg1_type, arg2_type, arg3_type,
+			arg4_type, arg5_type, arg6_type, mt_policy> *>  connections_list;
+
+		_signal_base6()
+		{
+			;
+		}
+
+		_signal_base6(const _signal_base6<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, mt_policy>& s)
+			: _signal_base<mt_policy>(s)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = s.m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_connect(this);
+				m_connected_slots.push_back((*it)->clone());
+
+				++it;
+			}
+		}
+
+		void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == oldtarget)
+				{
+					m_connected_slots.push_back((*it)->duplicate(newtarget));
+				}
+
+				++it;
+			}
+		}
+
+		~_signal_base6()
+		{
+			disconnect_all();
+		}
+
+		bool is_empty()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			return it == itEnd;
+		}
+
+		void disconnect_all()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_disconnect(this);
+				delete *it;
+
+				++it;
+			}
+
+			m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+		}
+
+#ifdef _DEBUG
+			bool connected(has_slots_interface* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+				if ((*it)->getdest() == pclass)
+					return true;
+				it = itNext;
+			}
+			return false;
+		}
+#endif
+
+		void disconnect(has_slots_interface* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == pclass)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+					pclass->signal_disconnect(this);
+					return;
+				}
+
+				++it;
+			}
+		}
+
+		void slot_disconnect(has_slots_interface* pslot)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				typename connections_list::iterator itNext = it;
+				++itNext;
+
+				if((*it)->getdest() == pslot)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+				}
+
+				it = itNext;
+			}
+		}
+
+	protected:
+		connections_list m_connected_slots;
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+	class arg5_type, class arg6_type, class arg7_type, class mt_policy>
+	class _signal_base7 : public _signal_base<mt_policy>
+	{
+	public:
+		typedef std::list<_connection_base7<arg1_type, arg2_type, arg3_type,
+			arg4_type, arg5_type, arg6_type, arg7_type, mt_policy> *>  connections_list;
+
+		_signal_base7()
+		{
+			;
+		}
+
+		_signal_base7(const _signal_base7<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, mt_policy>& s)
+			: _signal_base<mt_policy>(s)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = s.m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_connect(this);
+				m_connected_slots.push_back((*it)->clone());
+
+				++it;
+			}
+		}
+
+		void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == oldtarget)
+				{
+					m_connected_slots.push_back((*it)->duplicate(newtarget));
+				}
+
+				++it;
+			}
+		}
+
+		~_signal_base7()
+		{
+			disconnect_all();
+		}
+
+		bool is_empty()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			return it == itEnd;
+		}
+
+		void disconnect_all()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_disconnect(this);
+				delete *it;
+
+				++it;
+			}
+
+			m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+		}
+
+#ifdef _DEBUG
+			bool connected(has_slots_interface* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+				if ((*it)->getdest() == pclass)
+					return true;
+				it = itNext;
+			}
+			return false;
+		}
+#endif
+
+		void disconnect(has_slots_interface* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == pclass)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+					pclass->signal_disconnect(this);
+					return;
+				}
+
+				++it;
+			}
+		}
+
+		void slot_disconnect(has_slots_interface* pslot)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				typename connections_list::iterator itNext = it;
+				++itNext;
+
+				if((*it)->getdest() == pslot)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+				}
+
+				it = itNext;
+			}
+		}
+
+	protected:
+		connections_list m_connected_slots;
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+	class arg5_type, class arg6_type, class arg7_type, class arg8_type, class mt_policy>
+	class _signal_base8 : public _signal_base<mt_policy>
+	{
+	public:
+		typedef std::list<_connection_base8<arg1_type, arg2_type, arg3_type,
+			arg4_type, arg5_type, arg6_type, arg7_type, arg8_type, mt_policy> *>
+			connections_list;
+
+		_signal_base8()
+		{
+			;
+		}
+
+		_signal_base8(const _signal_base8<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>& s)
+			: _signal_base<mt_policy>(s)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = s.m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_connect(this);
+				m_connected_slots.push_back((*it)->clone());
+
+				++it;
+			}
+		}
+
+		void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == oldtarget)
+				{
+					m_connected_slots.push_back((*it)->duplicate(newtarget));
+				}
+
+				++it;
+			}
+		}
+
+		~_signal_base8()
+		{
+			disconnect_all();
+		}
+
+		bool is_empty()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			return it == itEnd;
+		}
+
+		void disconnect_all()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				(*it)->getdest()->signal_disconnect(this);
+				delete *it;
+
+				++it;
+			}
+
+			m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+		}
+
+#ifdef _DEBUG
+			bool connected(has_slots_interface* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+				if ((*it)->getdest() == pclass)
+					return true;
+				it = itNext;
+			}
+			return false;
+		}
+#endif
+
+		void disconnect(has_slots_interface* pclass)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				if((*it)->getdest() == pclass)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+					pclass->signal_disconnect(this);
+					return;
+				}
+
+				++it;
+			}
+		}
+
+		void slot_disconnect(has_slots_interface* pslot)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::iterator it = m_connected_slots.begin();
+			typename connections_list::iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				typename connections_list::iterator itNext = it;
+				++itNext;
+
+				if((*it)->getdest() == pslot)
+				{
+					delete *it;
+					m_connected_slots.erase(it);
+				}
+
+				it = itNext;
+			}
+		}
+
+	protected:
+		connections_list m_connected_slots;
+	};
+
+
+	template<class dest_type, class mt_policy>
+	class _connection0 : public _connection_base0<mt_policy>
+	{
+	public:
+		_connection0()
+		{
+			m_pobject = NULL;
+			m_pmemfun = NULL;
+		}
+
+		_connection0(dest_type* pobject, void (dest_type::*pmemfun)())
+		{
+			m_pobject = pobject;
+			m_pmemfun = pmemfun;
+		}
+
+		virtual ~_connection0()
+		{
+                }
+
+		virtual _connection_base0<mt_policy>* clone()
+		{
+			return new _connection0<dest_type, mt_policy>(*this);
+		}
+
+		virtual _connection_base0<mt_policy>* duplicate(has_slots_interface* pnewdest)
+		{
+			return new _connection0<dest_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+		}
+
+		virtual void emit()
+		{
+			(m_pobject->*m_pmemfun)();
+		}
+
+		virtual has_slots_interface* getdest() const
+		{
+			return m_pobject;
+		}
+
+	private:
+		dest_type* m_pobject;
+		void (dest_type::* m_pmemfun)();
+	};
+
+	template<class dest_type, class arg1_type, class mt_policy>
+	class _connection1 : public _connection_base1<arg1_type, mt_policy>
+	{
+	public:
+		_connection1()
+		{
+			m_pobject = NULL;
+			m_pmemfun = NULL;
+		}
+
+		_connection1(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type))
+		{
+			m_pobject = pobject;
+			m_pmemfun = pmemfun;
+		}
+
+		virtual ~_connection1()
+		{
+                }
+
+		virtual _connection_base1<arg1_type, mt_policy>* clone()
+		{
+			return new _connection1<dest_type, arg1_type, mt_policy>(*this);
+		}
+
+		virtual _connection_base1<arg1_type, mt_policy>* duplicate(has_slots_interface* pnewdest)
+		{
+			return new _connection1<dest_type, arg1_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+		}
+
+		virtual void emit(arg1_type a1)
+		{
+			(m_pobject->*m_pmemfun)(a1);
+		}
+
+		virtual has_slots_interface* getdest() const
+		{
+			return m_pobject;
+		}
+
+	private:
+		dest_type* m_pobject;
+		void (dest_type::* m_pmemfun)(arg1_type);
+	};
+
+	template<class dest_type, class arg1_type, class arg2_type, class mt_policy>
+	class _connection2 : public _connection_base2<arg1_type, arg2_type, mt_policy>
+	{
+	public:
+		_connection2()
+		{
+			m_pobject = NULL;
+			m_pmemfun = NULL;
+		}
+
+		_connection2(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type,
+			arg2_type))
+		{
+			m_pobject = pobject;
+			m_pmemfun = pmemfun;
+		}
+
+		virtual ~_connection2()
+		{
+                }
+
+		virtual _connection_base2<arg1_type, arg2_type, mt_policy>* clone()
+		{
+			return new _connection2<dest_type, arg1_type, arg2_type, mt_policy>(*this);
+		}
+
+		virtual _connection_base2<arg1_type, arg2_type, mt_policy>* duplicate(has_slots_interface* pnewdest)
+		{
+			return new _connection2<dest_type, arg1_type, arg2_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+		}
+
+		virtual void emit(arg1_type a1, arg2_type a2)
+		{
+			(m_pobject->*m_pmemfun)(a1, a2);
+		}
+
+		virtual has_slots_interface* getdest() const
+		{
+			return m_pobject;
+		}
+
+	private:
+		dest_type* m_pobject;
+		void (dest_type::* m_pmemfun)(arg1_type, arg2_type);
+	};
+
+	template<class dest_type, class arg1_type, class arg2_type, class arg3_type, class mt_policy>
+	class _connection3 : public _connection_base3<arg1_type, arg2_type, arg3_type, mt_policy>
+	{
+	public:
+		_connection3()
+		{
+			m_pobject = NULL;
+			m_pmemfun = NULL;
+		}
+
+		_connection3(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type,
+			arg2_type, arg3_type))
+		{
+			m_pobject = pobject;
+			m_pmemfun = pmemfun;
+		}
+
+		virtual ~_connection3()
+		{
+                }
+
+		virtual _connection_base3<arg1_type, arg2_type, arg3_type, mt_policy>* clone()
+		{
+			return new _connection3<dest_type, arg1_type, arg2_type, arg3_type, mt_policy>(*this);
+		}
+
+		virtual _connection_base3<arg1_type, arg2_type, arg3_type, mt_policy>* duplicate(has_slots_interface* pnewdest)
+		{
+			return new _connection3<dest_type, arg1_type, arg2_type, arg3_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+		}
+
+		virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3)
+		{
+			(m_pobject->*m_pmemfun)(a1, a2, a3);
+		}
+
+		virtual has_slots_interface* getdest() const
+		{
+			return m_pobject;
+		}
+
+	private:
+		dest_type* m_pobject;
+		void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type);
+	};
+
+	template<class dest_type, class arg1_type, class arg2_type, class arg3_type,
+	class arg4_type, class mt_policy>
+	class _connection4 : public _connection_base4<arg1_type, arg2_type,
+		arg3_type, arg4_type, mt_policy>
+	{
+	public:
+		_connection4()
+		{
+			m_pobject = NULL;
+			m_pmemfun = NULL;
+		}
+
+		_connection4(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type,
+			arg2_type, arg3_type, arg4_type))
+		{
+			m_pobject = pobject;
+			m_pmemfun = pmemfun;
+		}
+
+		virtual ~_connection4()
+		{
+                }
+
+		virtual _connection_base4<arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>* clone()
+		{
+			return new _connection4<dest_type, arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>(*this);
+		}
+
+		virtual _connection_base4<arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>* duplicate(has_slots_interface* pnewdest)
+		{
+			return new _connection4<dest_type, arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+		}
+
+		virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3,
+			arg4_type a4)
+		{
+			(m_pobject->*m_pmemfun)(a1, a2, a3, a4);
+		}
+
+		virtual has_slots_interface* getdest() const
+		{
+			return m_pobject;
+		}
+
+	private:
+		dest_type* m_pobject;
+		void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type,
+			arg4_type);
+	};
+
+	template<class dest_type, class arg1_type, class arg2_type, class arg3_type,
+	class arg4_type, class arg5_type, class mt_policy>
+	class _connection5 : public _connection_base5<arg1_type, arg2_type,
+		arg3_type, arg4_type, arg5_type, mt_policy>
+	{
+	public:
+		_connection5()
+		{
+			m_pobject = NULL;
+			m_pmemfun = NULL;
+		}
+
+		_connection5(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type,
+			arg2_type, arg3_type, arg4_type, arg5_type))
+		{
+			m_pobject = pobject;
+			m_pmemfun = pmemfun;
+		}
+
+		virtual ~_connection5()
+		{
+                }
+
+		virtual _connection_base5<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, mt_policy>* clone()
+		{
+			return new _connection5<dest_type, arg1_type, arg2_type, arg3_type, arg4_type,
+				arg5_type, mt_policy>(*this);
+		}
+
+		virtual _connection_base5<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, mt_policy>* duplicate(has_slots_interface* pnewdest)
+		{
+			return new _connection5<dest_type, arg1_type, arg2_type, arg3_type, arg4_type,
+				arg5_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+		}
+
+		virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+			arg5_type a5)
+		{
+			(m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5);
+		}
+
+		virtual has_slots_interface* getdest() const
+		{
+			return m_pobject;
+		}
+
+	private:
+		dest_type* m_pobject;
+		void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type);
+	};
+
+	template<class dest_type, class arg1_type, class arg2_type, class arg3_type,
+	class arg4_type, class arg5_type, class arg6_type, class mt_policy>
+	class _connection6 : public _connection_base6<arg1_type, arg2_type,
+		arg3_type, arg4_type, arg5_type, arg6_type, mt_policy>
+	{
+	public:
+		_connection6()
+		{
+			m_pobject = NULL;
+			m_pmemfun = NULL;
+		}
+
+		_connection6(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type,
+			arg2_type, arg3_type, arg4_type, arg5_type, arg6_type))
+		{
+			m_pobject = pobject;
+			m_pmemfun = pmemfun;
+		}
+
+		virtual ~_connection6()
+		{
+                }
+
+		virtual _connection_base6<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, mt_policy>* clone()
+		{
+			return new _connection6<dest_type, arg1_type, arg2_type, arg3_type, arg4_type,
+				arg5_type, arg6_type, mt_policy>(*this);
+		}
+
+		virtual _connection_base6<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, mt_policy>* duplicate(has_slots_interface* pnewdest)
+		{
+			return new _connection6<dest_type, arg1_type, arg2_type, arg3_type, arg4_type,
+				arg5_type, arg6_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+		}
+
+		virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+			arg5_type a5, arg6_type a6)
+		{
+			(m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6);
+		}
+
+		virtual has_slots_interface* getdest() const
+		{
+			return m_pobject;
+		}
+
+	private:
+		dest_type* m_pobject;
+		void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type);
+	};
+
+	template<class dest_type, class arg1_type, class arg2_type, class arg3_type,
+	class arg4_type, class arg5_type, class arg6_type, class arg7_type, class mt_policy>
+	class _connection7 : public _connection_base7<arg1_type, arg2_type,
+		arg3_type, arg4_type, arg5_type, arg6_type, arg7_type, mt_policy>
+	{
+	public:
+		_connection7()
+		{
+			m_pobject = NULL;
+			m_pmemfun = NULL;
+		}
+
+		_connection7(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type,
+			arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, arg7_type))
+		{
+			m_pobject = pobject;
+			m_pmemfun = pmemfun;
+		}
+
+		virtual ~_connection7()
+		{
+                }
+
+		virtual _connection_base7<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, mt_policy>* clone()
+		{
+			return new _connection7<dest_type, arg1_type, arg2_type, arg3_type, arg4_type,
+				arg5_type, arg6_type, arg7_type, mt_policy>(*this);
+		}
+
+		virtual _connection_base7<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, mt_policy>* duplicate(has_slots_interface* pnewdest)
+		{
+			return new _connection7<dest_type, arg1_type, arg2_type, arg3_type, arg4_type,
+				arg5_type, arg6_type, arg7_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+		}
+
+		virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+			arg5_type a5, arg6_type a6, arg7_type a7)
+		{
+			(m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6, a7);
+		}
+
+		virtual has_slots_interface* getdest() const
+		{
+			return m_pobject;
+		}
+
+	private:
+		dest_type* m_pobject;
+		void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type);
+	};
+
+	template<class dest_type, class arg1_type, class arg2_type, class arg3_type,
+	class arg4_type, class arg5_type, class arg6_type, class arg7_type,
+	class arg8_type, class mt_policy>
+	class _connection8 : public _connection_base8<arg1_type, arg2_type,
+		arg3_type, arg4_type, arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>
+	{
+	public:
+		_connection8()
+		{
+			m_pobject = NULL;
+			m_pmemfun = NULL;
+		}
+
+		_connection8(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type,
+			arg2_type, arg3_type, arg4_type, arg5_type, arg6_type,
+			arg7_type, arg8_type))
+		{
+			m_pobject = pobject;
+			m_pmemfun = pmemfun;
+		}
+
+		virtual ~_connection8()
+		{
+                }
+
+		virtual _connection_base8<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>* clone()
+		{
+			return new _connection8<dest_type, arg1_type, arg2_type, arg3_type, arg4_type,
+				arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>(*this);
+		}
+
+		virtual _connection_base8<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>* duplicate(has_slots_interface* pnewdest)
+		{
+			return new _connection8<dest_type, arg1_type, arg2_type, arg3_type, arg4_type,
+				arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+		}
+
+		virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+			arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8)
+		{
+			(m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6, a7, a8);
+		}
+
+		virtual has_slots_interface* getdest() const
+		{
+			return m_pobject;
+		}
+
+	private:
+		dest_type* m_pobject;
+		void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, arg8_type);
+	};
+
+	template<class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+	class signal0 : public _signal_base0<mt_policy>
+	{
+	public:
+		typedef _signal_base0<mt_policy> base;
+		typedef typename base::connections_list connections_list;
+		using base::m_connected_slots;
+
+		signal0()
+		{
+			;
+		}
+
+		signal0(const signal0<mt_policy>& s)
+			: _signal_base0<mt_policy>(s)
+		{
+			;
+		}
+
+		template<class desttype>
+			void connect(desttype* pclass, void (desttype::*pmemfun)())
+		{
+			lock_block<mt_policy> lock(this);
+			_connection0<desttype, mt_policy>* conn =
+				new _connection0<desttype, mt_policy>(pclass, pmemfun);
+			m_connected_slots.push_back(conn);
+			pclass->signal_connect(this);
+		}
+
+		void emit()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit();
+
+				it = itNext;
+			}
+		}
+
+		void operator()()
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit();
+
+				it = itNext;
+			}
+		}
+	};
+
+	template<class arg1_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+	class signal1 : public _signal_base1<arg1_type, mt_policy>
+	{
+	public:
+		typedef _signal_base1<arg1_type, mt_policy> base;
+		typedef typename base::connections_list connections_list;
+		using base::m_connected_slots;
+
+		signal1()
+		{
+			;
+		}
+
+		signal1(const signal1<arg1_type, mt_policy>& s)
+			: _signal_base1<arg1_type, mt_policy>(s)
+		{
+			;
+		}
+
+		template<class desttype>
+			void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type))
+		{
+			lock_block<mt_policy> lock(this);
+			_connection1<desttype, arg1_type, mt_policy>* conn =
+				new _connection1<desttype, arg1_type, mt_policy>(pclass, pmemfun);
+			m_connected_slots.push_back(conn);
+			pclass->signal_connect(this);
+		}
+
+		void emit(arg1_type a1)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1);
+
+				it = itNext;
+			}
+		}
+
+		void operator()(arg1_type a1)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1);
+
+				it = itNext;
+			}
+		}
+	};
+
+	template<class arg1_type, class arg2_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+	class signal2 : public _signal_base2<arg1_type, arg2_type, mt_policy>
+	{
+	public:
+		typedef _signal_base2<arg1_type, arg2_type, mt_policy> base;
+		typedef typename base::connections_list connections_list;
+		using base::m_connected_slots;
+
+		signal2()
+		{
+			;
+		}
+
+		signal2(const signal2<arg1_type, arg2_type, mt_policy>& s)
+			: _signal_base2<arg1_type, arg2_type, mt_policy>(s)
+		{
+			;
+		}
+
+		template<class desttype>
+			void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type,
+			arg2_type))
+		{
+			lock_block<mt_policy> lock(this);
+			_connection2<desttype, arg1_type, arg2_type, mt_policy>* conn = new
+				_connection2<desttype, arg1_type, arg2_type, mt_policy>(pclass, pmemfun);
+			m_connected_slots.push_back(conn);
+			pclass->signal_connect(this);
+		}
+
+		void emit(arg1_type a1, arg2_type a2)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2);
+
+				it = itNext;
+			}
+		}
+
+		void operator()(arg1_type a1, arg2_type a2)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2);
+
+				it = itNext;
+			}
+		}
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+	class signal3 : public _signal_base3<arg1_type, arg2_type, arg3_type, mt_policy>
+	{
+	public:
+		typedef _signal_base3<arg1_type, arg2_type, arg3_type, mt_policy> base;
+		typedef typename base::connections_list connections_list;
+		using base::m_connected_slots;
+
+		signal3()
+		{
+			;
+		}
+
+		signal3(const signal3<arg1_type, arg2_type, arg3_type, mt_policy>& s)
+			: _signal_base3<arg1_type, arg2_type, arg3_type, mt_policy>(s)
+		{
+			;
+		}
+
+		template<class desttype>
+			void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type,
+			arg2_type, arg3_type))
+		{
+			lock_block<mt_policy> lock(this);
+			_connection3<desttype, arg1_type, arg2_type, arg3_type, mt_policy>* conn =
+				new _connection3<desttype, arg1_type, arg2_type, arg3_type, mt_policy>(pclass,
+				pmemfun);
+			m_connected_slots.push_back(conn);
+			pclass->signal_connect(this);
+		}
+
+		void emit(arg1_type a1, arg2_type a2, arg3_type a3)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2, a3);
+
+				it = itNext;
+			}
+		}
+
+		void operator()(arg1_type a1, arg2_type a2, arg3_type a3)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2, a3);
+
+				it = itNext;
+			}
+		}
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+	class signal4 : public _signal_base4<arg1_type, arg2_type, arg3_type,
+		arg4_type, mt_policy>
+	{
+	public:
+		typedef _signal_base4<arg1_type, arg2_type, arg3_type, arg4_type, mt_policy> base;
+		typedef typename base::connections_list connections_list;
+		using base::m_connected_slots;
+
+		signal4()
+		{
+			;
+		}
+
+		signal4(const signal4<arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>& s)
+			: _signal_base4<arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>(s)
+		{
+			;
+		}
+
+		template<class desttype>
+			void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type,
+			arg2_type, arg3_type, arg4_type))
+		{
+			lock_block<mt_policy> lock(this);
+			_connection4<desttype, arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>*
+				conn = new _connection4<desttype, arg1_type, arg2_type, arg3_type,
+				arg4_type, mt_policy>(pclass, pmemfun);
+			m_connected_slots.push_back(conn);
+			pclass->signal_connect(this);
+		}
+
+		void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2, a3, a4);
+
+				it = itNext;
+			}
+		}
+
+		void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2, a3, a4);
+
+				it = itNext;
+			}
+		}
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+	class arg5_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+	class signal5 : public _signal_base5<arg1_type, arg2_type, arg3_type,
+		arg4_type, arg5_type, mt_policy>
+	{
+	public:
+		typedef _signal_base5<arg1_type, arg2_type, arg3_type, arg4_type, arg5_type, mt_policy> base;
+		typedef typename base::connections_list connections_list;
+		using base::m_connected_slots;
+
+		signal5()
+		{
+			;
+		}
+
+		signal5(const signal5<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, mt_policy>& s)
+			: _signal_base5<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, mt_policy>(s)
+		{
+			;
+		}
+
+		template<class desttype>
+			void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type,
+			arg2_type, arg3_type, arg4_type, arg5_type))
+		{
+			lock_block<mt_policy> lock(this);
+			_connection5<desttype, arg1_type, arg2_type, arg3_type, arg4_type,
+				arg5_type, mt_policy>* conn = new _connection5<desttype, arg1_type, arg2_type,
+				arg3_type, arg4_type, arg5_type, mt_policy>(pclass, pmemfun);
+			m_connected_slots.push_back(conn);
+			pclass->signal_connect(this);
+		}
+
+		void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+			arg5_type a5)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2, a3, a4, a5);
+
+				it = itNext;
+			}
+		}
+
+		void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+			arg5_type a5)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2, a3, a4, a5);
+
+				it = itNext;
+			}
+		}
+	};
+
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+	class arg5_type, class arg6_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+	class signal6 : public _signal_base6<arg1_type, arg2_type, arg3_type,
+		arg4_type, arg5_type, arg6_type, mt_policy>
+	{
+	public:
+		typedef _signal_base6<arg1_type, arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, mt_policy> base;
+		typedef typename base::connections_list connections_list;
+		using base::m_connected_slots;
+
+		signal6()
+		{
+			;
+		}
+
+		signal6(const signal6<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, mt_policy>& s)
+			: _signal_base6<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, mt_policy>(s)
+		{
+			;
+		}
+
+		template<class desttype>
+			void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type,
+			arg2_type, arg3_type, arg4_type, arg5_type, arg6_type))
+		{
+			lock_block<mt_policy> lock(this);
+			_connection6<desttype, arg1_type, arg2_type, arg3_type, arg4_type,
+				arg5_type, arg6_type, mt_policy>* conn =
+				new _connection6<desttype, arg1_type, arg2_type, arg3_type,
+				arg4_type, arg5_type, arg6_type, mt_policy>(pclass, pmemfun);
+			m_connected_slots.push_back(conn);
+			pclass->signal_connect(this);
+		}
+
+		void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+			arg5_type a5, arg6_type a6)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2, a3, a4, a5, a6);
+
+				it = itNext;
+			}
+		}
+
+		void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+			arg5_type a5, arg6_type a6)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2, a3, a4, a5, a6);
+
+				it = itNext;
+			}
+		}
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+	class arg5_type, class arg6_type, class arg7_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+	class signal7 : public _signal_base7<arg1_type, arg2_type, arg3_type,
+		arg4_type, arg5_type, arg6_type, arg7_type, mt_policy>
+	{
+	public:
+		typedef _signal_base7<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, mt_policy> base;
+		typedef typename base::connections_list connections_list;
+		using base::m_connected_slots;
+
+		signal7()
+		{
+			;
+		}
+
+		signal7(const signal7<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, mt_policy>& s)
+			: _signal_base7<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, mt_policy>(s)
+		{
+			;
+		}
+
+		template<class desttype>
+			void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type,
+			arg2_type, arg3_type, arg4_type, arg5_type, arg6_type,
+			arg7_type))
+		{
+			lock_block<mt_policy> lock(this);
+			_connection7<desttype, arg1_type, arg2_type, arg3_type, arg4_type,
+				arg5_type, arg6_type, arg7_type, mt_policy>* conn =
+				new _connection7<desttype, arg1_type, arg2_type, arg3_type,
+				arg4_type, arg5_type, arg6_type, arg7_type, mt_policy>(pclass, pmemfun);
+			m_connected_slots.push_back(conn);
+			pclass->signal_connect(this);
+		}
+
+		void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+			arg5_type a5, arg6_type a6, arg7_type a7)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2, a3, a4, a5, a6, a7);
+
+				it = itNext;
+			}
+		}
+
+		void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+			arg5_type a5, arg6_type a6, arg7_type a7)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2, a3, a4, a5, a6, a7);
+
+				it = itNext;
+			}
+		}
+	};
+
+	template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+	class arg5_type, class arg6_type, class arg7_type, class arg8_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+	class signal8 : public _signal_base8<arg1_type, arg2_type, arg3_type,
+		arg4_type, arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>
+	{
+	public:
+		typedef _signal_base8<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, arg8_type, mt_policy> base;
+		typedef typename base::connections_list connections_list;
+		using base::m_connected_slots;
+
+		signal8()
+		{
+			;
+		}
+
+		signal8(const signal8<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>& s)
+			: _signal_base8<arg1_type, arg2_type, arg3_type, arg4_type,
+			arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>(s)
+		{
+			;
+		}
+
+		template<class desttype>
+			void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type,
+			arg2_type, arg3_type, arg4_type, arg5_type, arg6_type,
+			arg7_type, arg8_type))
+		{
+			lock_block<mt_policy> lock(this);
+			_connection8<desttype, arg1_type, arg2_type, arg3_type, arg4_type,
+				arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>* conn =
+				new _connection8<desttype, arg1_type, arg2_type, arg3_type,
+				arg4_type, arg5_type, arg6_type, arg7_type,
+				arg8_type, mt_policy>(pclass, pmemfun);
+			m_connected_slots.push_back(conn);
+			pclass->signal_connect(this);
+		}
+
+		void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+			arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2, a3, a4, a5, a6, a7, a8);
+
+				it = itNext;
+			}
+		}
+
+		void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+			arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8)
+		{
+			lock_block<mt_policy> lock(this);
+			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+			typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+			while(it != itEnd)
+			{
+				itNext = it;
+				++itNext;
+
+				(*it)->emit(a1, a2, a3, a4, a5, a6, a7, a8);
+
+				it = itNext;
+			}
+		}
+	};
+
+}; // namespace sigslot
+
+#endif // TALK_BASE_SIGSLOT_H__
diff --git a/talk/base/sigslot_unittest.cc b/talk/base/sigslot_unittest.cc
new file mode 100644
index 0000000..62b03c2
--- /dev/null
+++ b/talk/base/sigslot_unittest.cc
@@ -0,0 +1,267 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/sigslot.h"
+
+#include "talk/base/gunit.h"
+
+// This function, when passed a has_slots or signalx, will break the build if
+// its threading requirement is not single threaded
+static bool TemplateIsST(const sigslot::single_threaded* p) {
+  return true;
+}
+// This function, when passed a has_slots or signalx, will break the build if
+// its threading requirement is not multi threaded
+static bool TemplateIsMT(const sigslot::multi_threaded_local* p) {
+  return true;
+}
+
+class SigslotDefault : public testing::Test, public sigslot::has_slots<> {
+ protected:
+  sigslot::signal0<> signal_;
+};
+
+template<class slot_policy = sigslot::single_threaded,
+         class signal_policy = sigslot::single_threaded>
+class SigslotReceiver : public sigslot::has_slots<slot_policy> {
+ public:
+  SigslotReceiver() : signal_(NULL), signal_count_(0) {
+  }
+  ~SigslotReceiver() {
+  }
+
+  void Connect(sigslot::signal0<signal_policy>* signal) {
+    if (!signal) return;
+    Disconnect();
+    signal_ = signal;
+    signal->connect(this,
+                    &SigslotReceiver<slot_policy, signal_policy>::OnSignal);
+  }
+  void Disconnect() {
+    if (!signal_) return;
+    signal_->disconnect(this);
+    signal_ = NULL;
+  }
+  void OnSignal() {
+    ++signal_count_;
+  }
+  int signal_count() { return signal_count_; }
+
+ private:
+  sigslot::signal0<signal_policy>* signal_;
+  int signal_count_;
+};
+
+template<class slot_policy = sigslot::single_threaded,
+         class mt_signal_policy = sigslot::multi_threaded_local>
+class SigslotSlotTest : public testing::Test {
+ protected:
+  SigslotSlotTest() {
+    mt_signal_policy mt_policy;
+    TemplateIsMT(&mt_policy);
+  }
+
+  virtual void SetUp() {
+    Connect();
+  }
+  virtual void TearDown() {
+    Disconnect();
+  }
+
+  void Disconnect() {
+    st_receiver_.Disconnect();
+    mt_receiver_.Disconnect();
+  }
+
+  void Connect() {
+    st_receiver_.Connect(&SignalSTLoopback);
+    mt_receiver_.Connect(&SignalMTLoopback);
+  }
+
+  int st_loop_back_count() { return st_receiver_.signal_count(); }
+  int mt_loop_back_count() { return mt_receiver_.signal_count(); }
+
+  sigslot::signal0<> SignalSTLoopback;
+  SigslotReceiver<slot_policy, sigslot::single_threaded> st_receiver_;
+  sigslot::signal0<mt_signal_policy> SignalMTLoopback;
+  SigslotReceiver<slot_policy, mt_signal_policy> mt_receiver_;
+};
+
+typedef SigslotSlotTest<> SigslotSTSlotTest;
+typedef SigslotSlotTest<sigslot::multi_threaded_local,
+                        sigslot::multi_threaded_local> SigslotMTSlotTest;
+
+class multi_threaded_local_fake : public sigslot::multi_threaded_local {
+ public:
+  multi_threaded_local_fake() : lock_count_(0), unlock_count_(0) {
+  }
+
+  virtual void lock() {
+    ++lock_count_;
+  }
+  virtual void unlock() {
+    ++unlock_count_;
+  }
+
+  int lock_count() { return lock_count_; }
+
+  bool InCriticalSection() { return lock_count_ != unlock_count_; }
+
+ protected:
+  int lock_count_;
+  int unlock_count_;
+};
+
+typedef SigslotSlotTest<multi_threaded_local_fake,
+                        multi_threaded_local_fake> SigslotMTLockBase;
+
+class SigslotMTLockTest : public SigslotMTLockBase {
+ protected:
+  SigslotMTLockTest() {}
+
+  virtual void SetUp() {
+    EXPECT_EQ(0, SlotLockCount());
+    SigslotMTLockBase::SetUp();
+    // Connects to two signals (ST and MT). However,
+    // SlotLockCount() only gets the count for the
+    // MT signal (there are two separate SigslotReceiver which
+    // keep track of their own count).
+    EXPECT_EQ(1, SlotLockCount());
+  }
+  virtual void TearDown() {
+    const int previous_lock_count = SlotLockCount();
+    SigslotMTLockBase::TearDown();
+    // Disconnects from two signals. Note analogous to SetUp().
+    EXPECT_EQ(previous_lock_count + 1, SlotLockCount());
+  }
+
+  int SlotLockCount() { return mt_receiver_.lock_count(); }
+  void Signal() { SignalMTLoopback(); }
+  int SignalLockCount() { return SignalMTLoopback.lock_count(); }
+  int signal_count() { return mt_loop_back_count(); }
+  bool InCriticalSection() { return SignalMTLoopback.InCriticalSection(); }
+};
+
+// This test will always succeed. However, if the default template instantiation
+// changes from single threaded to multi threaded it will break the build here.
+TEST_F(SigslotDefault, DefaultIsST) {
+  EXPECT_TRUE(TemplateIsST(this));
+  EXPECT_TRUE(TemplateIsST(&signal_));
+}
+
+// ST slot, ST signal
+TEST_F(SigslotSTSlotTest, STLoopbackTest) {
+  SignalSTLoopback();
+  EXPECT_EQ(1, st_loop_back_count());
+  EXPECT_EQ(0, mt_loop_back_count());
+}
+
+// ST slot, MT signal
+TEST_F(SigslotSTSlotTest, MTLoopbackTest) {
+  SignalMTLoopback();
+  EXPECT_EQ(1, mt_loop_back_count());
+  EXPECT_EQ(0, st_loop_back_count());
+}
+
+// ST slot, both ST and MT (separate) signal
+TEST_F(SigslotSTSlotTest, AllLoopbackTest) {
+  SignalSTLoopback();
+  SignalMTLoopback();
+  EXPECT_EQ(1, mt_loop_back_count());
+  EXPECT_EQ(1, st_loop_back_count());
+}
+
+TEST_F(SigslotSTSlotTest, Reconnect) {
+  SignalSTLoopback();
+  SignalMTLoopback();
+  EXPECT_EQ(1, mt_loop_back_count());
+  EXPECT_EQ(1, st_loop_back_count());
+  Disconnect();
+  SignalSTLoopback();
+  SignalMTLoopback();
+  EXPECT_EQ(1, mt_loop_back_count());
+  EXPECT_EQ(1, st_loop_back_count());
+  Connect();
+  SignalSTLoopback();
+  SignalMTLoopback();
+  EXPECT_EQ(2, mt_loop_back_count());
+  EXPECT_EQ(2, st_loop_back_count());
+}
+
+// MT slot, ST signal
+TEST_F(SigslotMTSlotTest, STLoopbackTest) {
+  SignalSTLoopback();
+  EXPECT_EQ(1, st_loop_back_count());
+  EXPECT_EQ(0, mt_loop_back_count());
+}
+
+// MT slot, MT signal
+TEST_F(SigslotMTSlotTest, MTLoopbackTest) {
+  SignalMTLoopback();
+  EXPECT_EQ(1, mt_loop_back_count());
+  EXPECT_EQ(0, st_loop_back_count());
+}
+
+// MT slot, both ST and MT (separate) signal
+TEST_F(SigslotMTSlotTest, AllLoopbackTest) {
+  SignalMTLoopback();
+  SignalSTLoopback();
+  EXPECT_EQ(1, st_loop_back_count());
+  EXPECT_EQ(1, mt_loop_back_count());
+}
+
+// Test that locks are acquired and released correctly.
+TEST_F(SigslotMTLockTest, LockSanity) {
+  const int lock_count = SignalLockCount();
+  Signal();
+  EXPECT_FALSE(InCriticalSection());
+  EXPECT_EQ(lock_count + 1, SignalLockCount());
+  EXPECT_EQ(1, signal_count());
+}
+
+// Destroy signal and slot in different orders.
+TEST(DestructionOrder, SignalFirst) {
+  sigslot::signal0<>* signal = new sigslot::signal0<>;
+  SigslotReceiver<>* receiver = new SigslotReceiver<>();
+  receiver->Connect(signal);
+  (*signal)();
+  EXPECT_EQ(1, receiver->signal_count());
+  delete signal;
+  delete receiver;
+}
+
+TEST(DestructionOrder, SlotFirst) {
+  sigslot::signal0<>* signal = new sigslot::signal0<>;
+  SigslotReceiver<>* receiver = new SigslotReceiver<>();
+  receiver->Connect(signal);
+  (*signal)();
+  EXPECT_EQ(1, receiver->signal_count());
+
+  delete receiver;
+  (*signal)();
+  delete signal;
+}
diff --git a/talk/base/sigslotrepeater.h b/talk/base/sigslotrepeater.h
new file mode 100644
index 0000000..628089b
--- /dev/null
+++ b/talk/base/sigslotrepeater.h
@@ -0,0 +1,111 @@
+/*
+ * libjingle
+ * Copyright 2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_SIGSLOTREPEATER_H__
+#define TALK_BASE_SIGSLOTREPEATER_H__
+
+// repeaters are both signals and slots, which are designed as intermediate
+// pass-throughs for signals and slots which don't know about each other (for
+// modularity or encapsulation).  This eliminates the need to declare a signal
+// handler whose sole purpose is to fire another signal.  The repeater connects
+// to the originating signal using the 'repeat' method.  When the repeated
+// signal fires, the repeater will also fire.
+
+#include "talk/base/sigslot.h"
+
+namespace sigslot {
+
+  template<class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+  class repeater0 : public signal0<mt_policy>,
+                    public has_slots<mt_policy>
+  {
+  public:
+    typedef signal0<mt_policy> base_type;
+    typedef repeater0<mt_policy> this_type;
+
+    repeater0() { }
+    repeater0(const this_type& s) : base_type(s) { }
+
+    void reemit() { signal0<mt_policy>::emit(); }
+    void repeat(base_type &s) { s.connect(this, &this_type::reemit); }
+    void stop(base_type &s) { s.disconnect(this); }
+  };
+
+  template<class arg1_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+  class repeater1 : public signal1<arg1_type, mt_policy>,
+                    public has_slots<mt_policy>
+  {
+  public:
+    typedef signal1<arg1_type, mt_policy> base_type;
+    typedef repeater1<arg1_type, mt_policy> this_type;
+
+    repeater1() { }
+    repeater1(const this_type& s) : base_type(s) { }
+
+    void reemit(arg1_type a1) { signal1<arg1_type, mt_policy>::emit(a1); }
+    void repeat(base_type& s) { s.connect(this, &this_type::reemit); }
+    void stop(base_type &s) { s.disconnect(this); }
+  };
+
+  template<class arg1_type, class arg2_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+  class repeater2 : public signal2<arg1_type, arg2_type, mt_policy>,
+                    public has_slots<mt_policy>
+  {
+  public:
+    typedef signal2<arg1_type, arg2_type, mt_policy> base_type;
+    typedef repeater2<arg1_type, arg2_type, mt_policy> this_type;
+
+    repeater2() { }
+    repeater2(const this_type& s) : base_type(s) { }
+
+    void reemit(arg1_type a1, arg2_type a2) { signal2<arg1_type, arg2_type, mt_policy>::emit(a1,a2); }
+    void repeat(base_type& s) { s.connect(this, &this_type::reemit); }
+    void stop(base_type &s) { s.disconnect(this); }
+  };
+
+  template<class arg1_type, class arg2_type, class arg3_type,
+           class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+  class repeater3 : public signal3<arg1_type, arg2_type, arg3_type, mt_policy>,
+                    public has_slots<mt_policy>
+  {
+  public:
+    typedef signal3<arg1_type, arg2_type, arg3_type, mt_policy> base_type;
+    typedef repeater3<arg1_type, arg2_type, arg3_type, mt_policy> this_type;
+
+    repeater3() { }
+    repeater3(const this_type& s) : base_type(s) { }
+
+    void reemit(arg1_type a1, arg2_type a2, arg3_type a3) {
+            signal3<arg1_type, arg2_type, arg3_type, mt_policy>::emit(a1,a2,a3);
+    }
+    void repeat(base_type& s) { s.connect(this, &this_type::reemit); }
+    void stop(base_type &s) { s.disconnect(this); }
+  };
+
+}  // namespace sigslot
+
+#endif  // TALK_BASE_SIGSLOTREPEATER_H__
diff --git a/talk/base/socket.h b/talk/base/socket.h
new file mode 100644
index 0000000..9932cda
--- /dev/null
+++ b/talk/base/socket.h
@@ -0,0 +1,201 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_SOCKET_H__
+#define TALK_BASE_SOCKET_H__
+
+#include <errno.h>
+
+#ifdef POSIX
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#define SOCKET_EACCES EACCES
+#endif
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#endif
+
+#include "talk/base/basictypes.h"
+#include "talk/base/socketaddress.h"
+
+// Rather than converting errors into a private namespace,
+// Reuse the POSIX socket api errors. Note this depends on
+// Win32 compatibility.
+
+#ifdef WIN32
+#undef EWOULDBLOCK  // Remove errno.h's definition for each macro below.
+#define EWOULDBLOCK WSAEWOULDBLOCK
+#undef EINPROGRESS
+#define EINPROGRESS WSAEINPROGRESS
+#undef EALREADY
+#define EALREADY WSAEALREADY
+#undef ENOTSOCK
+#define ENOTSOCK WSAENOTSOCK
+#undef EDESTADDRREQ
+#define EDESTADDRREQ WSAEDESTADDRREQ
+#undef EMSGSIZE
+#define EMSGSIZE WSAEMSGSIZE
+#undef EPROTOTYPE
+#define EPROTOTYPE WSAEPROTOTYPE
+#undef ENOPROTOOPT
+#define ENOPROTOOPT WSAENOPROTOOPT
+#undef EPROTONOSUPPORT
+#define EPROTONOSUPPORT WSAEPROTONOSUPPORT
+#undef ESOCKTNOSUPPORT
+#define ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT
+#undef EOPNOTSUPP
+#define EOPNOTSUPP WSAEOPNOTSUPP
+#undef EPFNOSUPPORT
+#define EPFNOSUPPORT WSAEPFNOSUPPORT
+#undef EAFNOSUPPORT
+#define EAFNOSUPPORT WSAEAFNOSUPPORT
+#undef EADDRINUSE
+#define EADDRINUSE WSAEADDRINUSE
+#undef EADDRNOTAVAIL
+#define EADDRNOTAVAIL WSAEADDRNOTAVAIL
+#undef ENETDOWN
+#define ENETDOWN WSAENETDOWN
+#undef ENETUNREACH
+#define ENETUNREACH WSAENETUNREACH
+#undef ENETRESET
+#define ENETRESET WSAENETRESET
+#undef ECONNABORTED
+#define ECONNABORTED WSAECONNABORTED
+#undef ECONNRESET
+#define ECONNRESET WSAECONNRESET
+#undef ENOBUFS
+#define ENOBUFS WSAENOBUFS
+#undef EISCONN
+#define EISCONN WSAEISCONN
+#undef ENOTCONN
+#define ENOTCONN WSAENOTCONN
+#undef ESHUTDOWN
+#define ESHUTDOWN WSAESHUTDOWN
+#undef ETOOMANYREFS
+#define ETOOMANYREFS WSAETOOMANYREFS
+#undef ETIMEDOUT
+#define ETIMEDOUT WSAETIMEDOUT
+#undef ECONNREFUSED
+#define ECONNREFUSED WSAECONNREFUSED
+#undef ELOOP
+#define ELOOP WSAELOOP
+#undef ENAMETOOLONG
+#define ENAMETOOLONG WSAENAMETOOLONG
+#undef EHOSTDOWN
+#define EHOSTDOWN WSAEHOSTDOWN
+#undef EHOSTUNREACH
+#define EHOSTUNREACH WSAEHOSTUNREACH
+#undef ENOTEMPTY
+#define ENOTEMPTY WSAENOTEMPTY
+#undef EPROCLIM
+#define EPROCLIM WSAEPROCLIM
+#undef EUSERS
+#define EUSERS WSAEUSERS
+#undef EDQUOT
+#define EDQUOT WSAEDQUOT
+#undef ESTALE
+#define ESTALE WSAESTALE
+#undef EREMOTE
+#define EREMOTE WSAEREMOTE
+#undef EACCES
+#define SOCKET_EACCES WSAEACCES
+#endif  // WIN32
+
+#ifdef POSIX
+#define INVALID_SOCKET (-1)
+#define SOCKET_ERROR (-1)
+#define closesocket(s) close(s)
+#endif  // POSIX
+
+namespace talk_base {
+
+inline bool IsBlockingError(int e) {
+  return (e == EWOULDBLOCK) || (e == EAGAIN) || (e == EINPROGRESS);
+}
+
+// General interface for the socket implementations of various networks.  The
+// methods match those of normal UNIX sockets very closely.
+class Socket {
+ public:
+  virtual ~Socket() {}
+
+  // Returns the address to which the socket is bound.  If the socket is not
+  // bound, then the any-address is returned.
+  virtual SocketAddress GetLocalAddress() const = 0;
+
+  // Returns the address to which the socket is connected.  If the socket is
+  // not connected, then the any-address is returned.
+  virtual SocketAddress GetRemoteAddress() const = 0;
+
+  virtual int Bind(const SocketAddress& addr) = 0;
+  virtual int Connect(const SocketAddress& addr) = 0;
+  virtual int Send(const void *pv, size_t cb) = 0;
+  virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr) = 0;
+  virtual int Recv(void *pv, size_t cb) = 0;
+  virtual int RecvFrom(void *pv, size_t cb, SocketAddress *paddr) = 0;
+  virtual int Listen(int backlog) = 0;
+  virtual Socket *Accept(SocketAddress *paddr) = 0;
+  virtual int Close() = 0;
+  virtual int GetError() const = 0;
+  virtual void SetError(int error) = 0;
+  inline bool IsBlocking() const { return IsBlockingError(GetError()); }
+
+  enum ConnState {
+    CS_CLOSED,
+    CS_CONNECTING,
+    CS_CONNECTED
+  };
+  virtual ConnState GetState() const = 0;
+
+  // Fills in the given uint16 with the current estimate of the MTU along the
+  // path to the address to which this socket is connected. NOTE: This method
+  // can block for up to 10 seconds on Windows.
+  virtual int EstimateMTU(uint16* mtu) = 0;
+
+  enum Option {
+    OPT_DONTFRAGMENT,
+    OPT_RCVBUF,      // receive buffer size
+    OPT_SNDBUF,      // send buffer size
+    OPT_NODELAY,     // whether Nagle algorithm is enabled
+    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;
+
+ protected:
+  Socket() {}
+
+ private:
+  DISALLOW_EVIL_CONSTRUCTORS(Socket);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SOCKET_H__
diff --git a/talk/base/socket_unittest.cc b/talk/base/socket_unittest.cc
new file mode 100644
index 0000000..dd4b1e5
--- /dev/null
+++ b/talk/base/socket_unittest.cc
@@ -0,0 +1,1018 @@
+/*
+ * 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/nethelpers.h"
+#include "talk/base/socketserver.h"
+#include "talk/base/testclient.h"
+#include "talk/base/testutils.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+#define MAYBE_SKIP_IPV6                             \
+  if (!HasIPv6Enabled()) {                          \
+    LOG(LS_INFO) << "No IPv6... skipping";          \
+    return;                                         \
+  }
+
+
+void SocketTest::TestConnectIPv4() {
+  ConnectInternal(kIPv4Loopback);
+}
+
+void SocketTest::TestConnectIPv6() {
+  MAYBE_SKIP_IPV6;
+  ConnectInternal(kIPv6Loopback);
+}
+
+void SocketTest::TestConnectWithDnsLookupIPv4() {
+  ConnectWithDnsLookupInternal(kIPv4Loopback, "localhost");
+}
+
+void SocketTest::TestConnectWithDnsLookupIPv6() {
+  // TODO: Enable this when DNS resolution supports IPv6.
+  LOG(LS_INFO) << "Skipping IPv6 DNS test";
+  // ConnectWithDnsLookupInternal(kIPv6Loopback, "localhost6");
+}
+
+void SocketTest::TestConnectFailIPv4() {
+  ConnectFailInternal(kIPv4Loopback);
+}
+
+void SocketTest::TestConnectFailIPv6() {
+  MAYBE_SKIP_IPV6;
+  ConnectFailInternal(kIPv6Loopback);
+}
+
+void SocketTest::TestConnectWithDnsLookupFailIPv4() {
+  ConnectWithDnsLookupFailInternal(kIPv4Loopback);
+}
+
+void SocketTest::TestConnectWithDnsLookupFailIPv6() {
+  MAYBE_SKIP_IPV6;
+  ConnectWithDnsLookupFailInternal(kIPv6Loopback);
+}
+
+void SocketTest::TestConnectWithClosedSocketIPv4() {
+  ConnectWithClosedSocketInternal(kIPv4Loopback);
+}
+
+void SocketTest::TestConnectWithClosedSocketIPv6() {
+  MAYBE_SKIP_IPV6;
+  ConnectWithClosedSocketInternal(kIPv6Loopback);
+}
+
+void SocketTest::TestConnectWhileNotClosedIPv4() {
+  ConnectWhileNotClosedInternal(kIPv4Loopback);
+}
+
+void SocketTest::TestConnectWhileNotClosedIPv6() {
+  MAYBE_SKIP_IPV6;
+  ConnectWhileNotClosedInternal(kIPv6Loopback);
+}
+
+void SocketTest::TestServerCloseDuringConnectIPv4() {
+  ServerCloseDuringConnectInternal(kIPv4Loopback);
+}
+
+void SocketTest::TestServerCloseDuringConnectIPv6() {
+  MAYBE_SKIP_IPV6;
+  ServerCloseDuringConnectInternal(kIPv6Loopback);
+}
+
+void SocketTest::TestClientCloseDuringConnectIPv4() {
+  ClientCloseDuringConnectInternal(kIPv4Loopback);
+}
+
+void SocketTest::TestClientCloseDuringConnectIPv6() {
+  MAYBE_SKIP_IPV6;
+  ClientCloseDuringConnectInternal(kIPv6Loopback);
+}
+
+void SocketTest::TestServerCloseIPv4() {
+  ServerCloseInternal(kIPv4Loopback);
+}
+
+void SocketTest::TestServerCloseIPv6() {
+  MAYBE_SKIP_IPV6;
+  ServerCloseInternal(kIPv6Loopback);
+}
+
+void SocketTest::TestCloseInClosedCallbackIPv4() {
+  CloseInClosedCallbackInternal(kIPv4Loopback);
+}
+
+void SocketTest::TestCloseInClosedCallbackIPv6() {
+  MAYBE_SKIP_IPV6;
+  CloseInClosedCallbackInternal(kIPv6Loopback);
+}
+
+void SocketTest::TestSocketServerWaitIPv4() {
+  SocketServerWaitInternal(kIPv4Loopback);
+}
+
+void SocketTest::TestSocketServerWaitIPv6() {
+  MAYBE_SKIP_IPV6;
+  SocketServerWaitInternal(kIPv6Loopback);
+}
+
+void SocketTest::TestTcpIPv4() {
+  TcpInternal(kIPv4Loopback);
+}
+
+void SocketTest::TestTcpIPv6() {
+  MAYBE_SKIP_IPV6;
+  TcpInternal(kIPv6Loopback);
+}
+
+void SocketTest::TestSingleFlowControlCallbackIPv4() {
+  SingleFlowControlCallbackInternal(kIPv4Loopback);
+}
+
+void SocketTest::TestSingleFlowControlCallbackIPv6() {
+  MAYBE_SKIP_IPV6;
+  SingleFlowControlCallbackInternal(kIPv6Loopback);
+}
+
+void SocketTest::TestUdpIPv4() {
+  UdpInternal(kIPv4Loopback);
+}
+
+void SocketTest::TestUdpIPv6() {
+  MAYBE_SKIP_IPV6;
+  UdpInternal(kIPv6Loopback);
+}
+
+void SocketTest::TestUdpReadyToSendIPv4() {
+#if !defined(OSX)
+  // TODO(ronghuawu): Enable this test (currently failed on build bots) on mac.
+  UdpReadyToSend(kIPv4Loopback);
+#endif
+}
+
+void SocketTest::TestUdpReadyToSendIPv6() {
+#if defined(WIN32)
+  // TODO(ronghuawu): Enable this test (currently flakey) on mac and linux.
+  MAYBE_SKIP_IPV6;
+  UdpReadyToSend(kIPv6Loopback);
+#endif
+}
+
+void SocketTest::TestGetSetOptionsIPv4() {
+  GetSetOptionsInternal(kIPv4Loopback);
+}
+
+void SocketTest::TestGetSetOptionsIPv6() {
+  MAYBE_SKIP_IPV6;
+  GetSetOptionsInternal(kIPv6Loopback);
+}
+
+// For unbound sockets, GetLocalAddress / GetRemoteAddress return AF_UNSPEC
+// values on Windows, but an empty address of the same family on Linux/MacOS X.
+bool IsUnspecOrEmptyIP(const IPAddress& address) {
+#ifndef WIN32
+  return IPIsAny(address);
+#else
+  return address.family() == AF_UNSPEC;
+#endif
+}
+
+void SocketTest::ConnectInternal(const IPAddress& loopback) {
+  testing::StreamSink sink;
+  SocketAddress accept_addr;
+
+  // Create client.
+  scoped_ptr<AsyncSocket> client(ss_->CreateAsyncSocket(loopback.family(),
+                                                        SOCK_STREAM));
+  sink.Monitor(client.get());
+  EXPECT_EQ(AsyncSocket::CS_CLOSED, client->GetState());
+  EXPECT_PRED1(IsUnspecOrEmptyIP, client->GetLocalAddress().ipaddr());
+
+  // Create server and listen.
+  scoped_ptr<AsyncSocket> server(
+      ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM));
+  sink.Monitor(server.get());
+  EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0)));
+  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_TRUE(accept_addr.IsNil());
+
+  // Attempt connect to listening socket.
+  EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+  EXPECT_FALSE(client->GetLocalAddress().IsNil());
+  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(accepted);
+  EXPECT_FALSE(accept_addr.IsNil());
+  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::ConnectWithDnsLookupInternal(const IPAddress& loopback,
+                                              const std::string& host) {
+  testing::StreamSink sink;
+  SocketAddress accept_addr;
+
+  // Create client.
+  scoped_ptr<AsyncSocket> client(
+      ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM));
+  sink.Monitor(client.get());
+
+  // Create server and listen.
+  scoped_ptr<AsyncSocket> server(
+      ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM));
+  sink.Monitor(server.get());
+  EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0)));
+  EXPECT_EQ(0, server->Listen(5));
+
+  // Attempt connect to listening socket.
+  SocketAddress dns_addr(server->GetLocalAddress());
+  dns_addr.SetIP(host);
+  EXPECT_EQ(0, client->Connect(dns_addr));
+  // 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(accepted);
+  EXPECT_FALSE(accept_addr.IsNil());
+  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::ConnectFailInternal(const IPAddress& loopback) {
+  testing::StreamSink sink;
+  SocketAddress accept_addr;
+
+  // Create client.
+  scoped_ptr<AsyncSocket> client(
+      ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM));
+  sink.Monitor(client.get());
+
+  // Create server, but don't listen yet.
+  scoped_ptr<AsyncSocket> server(
+      ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM));
+  sink.Monitor(server.get());
+  EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0)));
+
+  // 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(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_TRUE(client->GetRemoteAddress().IsNil());
+
+  // Should be no pending server connections.
+  EXPECT_FALSE(sink.Check(server.get(), testing::SSE_READ));
+  EXPECT_TRUE(NULL == server->Accept(&accept_addr));
+  EXPECT_EQ(IPAddress(), accept_addr.ipaddr());
+}
+
+void SocketTest::ConnectWithDnsLookupFailInternal(const IPAddress& loopback) {
+  testing::StreamSink sink;
+  SocketAddress accept_addr;
+
+  // Create client.
+  scoped_ptr<AsyncSocket> client(
+      ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM));
+  sink.Monitor(client.get());
+
+  // Create server, but don't listen yet.
+  scoped_ptr<AsyncSocket> server(
+      ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM));
+  sink.Monitor(server.get());
+  EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0)));
+
+  // 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_TRUE(client->GetRemoteAddress().IsNil());
+  // Should be no pending server connections.
+  EXPECT_FALSE(sink.Check(server.get(), testing::SSE_READ));
+  EXPECT_TRUE(NULL == server->Accept(&accept_addr));
+  EXPECT_TRUE(accept_addr.IsNil());
+}
+
+void SocketTest::ConnectWithClosedSocketInternal(const IPAddress& loopback) {
+  // Create server and listen.
+  scoped_ptr<AsyncSocket> server(
+      ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM));
+  EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0)));
+  EXPECT_EQ(0, server->Listen(5));
+
+  // Create a client and put in to CS_CLOSED state.
+  scoped_ptr<AsyncSocket> client(
+      ss_->CreateAsyncSocket(loopback.family(), 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::ConnectWhileNotClosedInternal(const IPAddress& loopback) {
+  // Create server and listen.
+  testing::StreamSink sink;
+  scoped_ptr<AsyncSocket> server(
+      ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM));
+  sink.Monitor(server.get());
+  EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0)));
+  EXPECT_EQ(0, server->Listen(5));
+  // Create client, connect.
+  scoped_ptr<AsyncSocket> client(
+      ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM));
+  EXPECT_EQ(0, client->Connect(SocketAddress(server->GetLocalAddress())));
+  EXPECT_EQ(AsyncSocket::CS_CONNECTING, client->GetState());
+  // Try to connect again. Should fail, but not interfere with original attempt.
+  EXPECT_EQ(SOCKET_ERROR,
+            client->Connect(SocketAddress(server->GetLocalAddress())));
+
+  // Accept the original connection.
+  SocketAddress accept_addr;
+  EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout);
+  scoped_ptr<AsyncSocket> accepted(server->Accept(&accept_addr));
+  ASSERT_TRUE(accepted);
+  EXPECT_FALSE(accept_addr.IsNil());
+
+  // Check the states and addresses.
+  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_EQ(client->GetRemoteAddress(), server->GetLocalAddress());
+  EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress());
+
+  // Try to connect again, to an unresolved hostname.
+  // Shouldn't break anything.
+  EXPECT_EQ(SOCKET_ERROR,
+            client->Connect(SocketAddress("localhost",
+                                          server->GetLocalAddress().port())));
+  EXPECT_EQ(AsyncSocket::CS_CONNECTED, accepted->GetState());
+  EXPECT_EQ(AsyncSocket::CS_CONNECTED, client->GetState());
+  EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress());
+  EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress());
+}
+
+void SocketTest::ServerCloseDuringConnectInternal(const IPAddress& loopback) {
+  testing::StreamSink sink;
+
+  // Create client.
+  scoped_ptr<AsyncSocket> client(
+      ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM));
+  sink.Monitor(client.get());
+
+  // Create server and listen.
+  scoped_ptr<AsyncSocket> server(
+      ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM));
+  sink.Monitor(server.get());
+  EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0)));
+  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::ClientCloseDuringConnectInternal(const IPAddress& loopback) {
+  testing::StreamSink sink;
+  SocketAddress accept_addr;
+
+  // Create client.
+  scoped_ptr<AsyncSocket> client(
+      ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM));
+  sink.Monitor(client.get());
+
+  // Create server and listen.
+  scoped_ptr<AsyncSocket> server(
+      ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM));
+  sink.Monitor(server.get());
+  EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0)));
+  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(accepted);
+  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::ServerCloseInternal(const IPAddress& loopback) {
+  testing::StreamSink sink;
+  SocketAddress accept_addr;
+
+  // Create client.
+  scoped_ptr<AsyncSocket> client(
+      ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM));
+  sink.Monitor(client.get());
+
+  // Create server and listen.
+  scoped_ptr<AsyncSocket> server(
+      ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM));
+  sink.Monitor(server.get());
+  EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0)));
+  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(accepted);
+  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_FALSE(client->GetRemoteAddress().IsAnyIP());
+
+  // The closer should not get a close signal.
+  EXPECT_FALSE(sink.Check(accepted.get(), testing::SSE_CLOSE));
+  EXPECT_TRUE(accepted->GetRemoteAddress().IsNil());
+
+  // 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_TRUE(client->GetRemoteAddress().IsNil());
+}
+
+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::CloseInClosedCallbackInternal(const IPAddress& loopback) {
+  testing::StreamSink sink;
+  SocketCloser closer;
+  SocketAddress accept_addr;
+
+  // Create client.
+  scoped_ptr<AsyncSocket> client(
+      ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM));
+  sink.Monitor(client.get());
+  client->SignalCloseEvent.connect(&closer, &SocketCloser::OnClose);
+
+  // Create server and listen.
+  scoped_ptr<AsyncSocket> server(
+      ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM));
+  sink.Monitor(server.get());
+  EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0)));
+  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(accepted);
+  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::SocketServerWaitInternal(const IPAddress& loopback) {
+  testing::StreamSink sink;
+  SocketAddress accept_addr;
+
+  // Create & connect server and client sockets.
+  scoped_ptr<AsyncSocket> client(
+      ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM));
+  scoped_ptr<AsyncSocket> server(
+      ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM));
+  sink.Monitor(client.get());
+  sink.Monitor(server.get());
+  EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0)));
+  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(accepted);
+  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::TcpInternal(const IPAddress& loopback) {
+  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] = static_cast<char>(i % 256);
+    recv_buffer[i] = 0;
+  }
+
+  // Create client.
+  scoped_ptr<AsyncSocket> client(
+      ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM));
+  sink.Monitor(client.get());
+
+  // Create server and listen.
+  scoped_ptr<AsyncSocket> server(
+      ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM));
+  sink.Monitor(server.get());
+  EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0)));
+  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(accepted);
+  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 = static_cast<int>(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.
+        // TODO: The following line is disabled due to flakey pulse
+        //     builds.  Re-enable if/when possible.
+        // 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::SingleFlowControlCallbackInternal(const IPAddress& loopback) {
+  testing::StreamSink sink;
+  SocketAddress accept_addr;
+
+  // Create client.
+  scoped_ptr<AsyncSocket> client(
+      ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM));
+  sink.Monitor(client.get());
+
+  // Create server and listen.
+  scoped_ptr<AsyncSocket> server(
+      ss_->CreateAsyncSocket(loopback.family(), SOCK_STREAM));
+  sink.Monitor(server.get());
+  EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0)));
+  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(accepted);
+  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());
+
+  // Expect a writable callback from the connect.
+  EXPECT_TRUE_WAIT(sink.Check(accepted.get(), testing::SSE_WRITE), kTimeout);
+
+  // Fill the socket buffer.
+  char buf[1024 * 16] = {0};
+  int sends = 0;
+  while (++sends && accepted->Send(&buf, ARRAY_SIZE(buf)) != -1) {}
+  EXPECT_TRUE(accepted->IsBlocking());
+
+  // Wait until data is available.
+  EXPECT_TRUE_WAIT(sink.Check(client.get(), testing::SSE_READ), kTimeout);
+
+  // Pull data.
+  for (int i = 0; i < sends; ++i) {
+    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::UdpInternal(const IPAddress& loopback) {
+  SocketAddress empty = EmptySocketAddressWithFamily(loopback.family());
+  // Test basic bind and connect behavior.
+  AsyncSocket* socket =
+      ss_->CreateAsyncSocket(loopback.family(), SOCK_DGRAM);
+  EXPECT_EQ(AsyncSocket::CS_CLOSED, socket->GetState());
+  EXPECT_EQ(0, socket->Bind(SocketAddress(loopback, 0)));
+  SocketAddress 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_, empty)));
+
+  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_, empty)));
+
+    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::UdpReadyToSend(const IPAddress& loopback) {
+  SocketAddress empty = EmptySocketAddressWithFamily(loopback.family());
+  // RFC 5737 - The blocks 192.0.2.0/24 (TEST-NET-1) ... are provided for use in
+  // documentation.
+  // RFC 3849 - 2001:DB8::/32 as a documentation-only prefix.
+  std::string dest = (loopback.family() == AF_INET6) ?
+      "2001:db8::1" : "192.0.2.0";
+  SocketAddress test_addr(dest, 2345);
+
+  // Test send
+  scoped_ptr<TestClient> client(
+      new TestClient(AsyncUDPSocket::Create(ss_, empty)));
+  int test_packet_size = 1200;
+  talk_base::scoped_array<char> test_packet(new char[test_packet_size]);
+  // Set the send buffer size to the same size as the test packet to have a
+  // better chance to get EWOULDBLOCK.
+  int send_buffer_size = test_packet_size;
+#if defined(LINUX)
+  send_buffer_size /= 2;
+#endif
+  client->SetOption(talk_base::Socket::OPT_SNDBUF, send_buffer_size);
+
+  int error = 0;
+  uint32 start_ms = Time();
+  int sent_packet_num = 0;
+  int expected_error = EWOULDBLOCK;
+  while (start_ms + kTimeout > Time()) {
+    int ret = client->SendTo(test_packet.get(), test_packet_size, test_addr);
+    ++sent_packet_num;
+    if (ret != test_packet_size) {
+      error = client->GetError();
+      if (error == expected_error) {
+        LOG(LS_INFO) << "Got expected error code after sending "
+                     << sent_packet_num << " packets.";
+        break;
+      }
+    }
+  }
+  EXPECT_EQ(expected_error, error);
+  EXPECT_FALSE(client->ready_to_send());
+  EXPECT_TRUE_WAIT(client->ready_to_send(), kTimeout);
+  LOG(LS_INFO) << "Got SignalReadyToSend";
+}
+
+void SocketTest::GetSetOptionsInternal(const IPAddress& loopback) {
+  talk_base::scoped_ptr<AsyncSocket> socket(
+      ss_->CreateAsyncSocket(loopback.family(), SOCK_DGRAM));
+  socket->Bind(SocketAddress(loopback, 0));
+
+  // 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));
+
+  // Skip the esimate MTU test for IPv6 for now.
+  if (loopback.family() != AF_INET6) {
+    // Try estimating MTU.
+    talk_base::scoped_ptr<AsyncSocket>
+        mtu_socket(
+            ss_->CreateAsyncSocket(loopback.family(), SOCK_DGRAM));
+    mtu_socket->Bind(SocketAddress(loopback, 0));
+    uint16 mtu;
+    // should fail until we connect
+    ASSERT_EQ(-1, mtu_socket->EstimateMTU(&mtu));
+    mtu_socket->Connect(SocketAddress(loopback, 0));
+#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..86c4c93
--- /dev/null
+++ b/talk/base/socket_unittest.h
@@ -0,0 +1,105 @@
+/*
+ * 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), kIPv4Loopback(INADDR_LOOPBACK),
+                 kIPv6Loopback(in6addr_loopback) {}
+  virtual void SetUp() { ss_ = Thread::Current()->socketserver(); }
+  void TestConnectIPv4();
+  void TestConnectIPv6();
+  void TestConnectWithDnsLookupIPv4();
+  void TestConnectWithDnsLookupIPv6();
+  void TestConnectFailIPv4();
+  void TestConnectFailIPv6();
+  void TestConnectWithDnsLookupFailIPv4();
+  void TestConnectWithDnsLookupFailIPv6();
+  void TestConnectWithClosedSocketIPv4();
+  void TestConnectWithClosedSocketIPv6();
+  void TestConnectWhileNotClosedIPv4();
+  void TestConnectWhileNotClosedIPv6();
+  void TestServerCloseDuringConnectIPv4();
+  void TestServerCloseDuringConnectIPv6();
+  void TestClientCloseDuringConnectIPv4();
+  void TestClientCloseDuringConnectIPv6();
+  void TestServerCloseIPv4();
+  void TestServerCloseIPv6();
+  void TestCloseInClosedCallbackIPv4();
+  void TestCloseInClosedCallbackIPv6();
+  void TestSocketServerWaitIPv4();
+  void TestSocketServerWaitIPv6();
+  void TestTcpIPv4();
+  void TestTcpIPv6();
+  void TestSingleFlowControlCallbackIPv4();
+  void TestSingleFlowControlCallbackIPv6();
+  void TestUdpIPv4();
+  void TestUdpIPv6();
+  void TestUdpReadyToSendIPv4();
+  void TestUdpReadyToSendIPv6();
+  void TestGetSetOptionsIPv4();
+  void TestGetSetOptionsIPv6();
+
+ private:
+  void ConnectInternal(const IPAddress& loopback);
+  void ConnectWithDnsLookupInternal(const IPAddress& loopback,
+                                    const std::string& host);
+  void ConnectFailInternal(const IPAddress& loopback);
+
+  void ConnectWithDnsLookupFailInternal(const IPAddress& loopback);
+  void ConnectWithClosedSocketInternal(const IPAddress& loopback);
+  void ConnectWhileNotClosedInternal(const IPAddress& loopback);
+  void ServerCloseDuringConnectInternal(const IPAddress& loopback);
+  void ClientCloseDuringConnectInternal(const IPAddress& loopback);
+  void ServerCloseInternal(const IPAddress& loopback);
+  void CloseInClosedCallbackInternal(const IPAddress& loopback);
+  void SocketServerWaitInternal(const IPAddress& loopback);
+  void TcpInternal(const IPAddress& loopback);
+  void SingleFlowControlCallbackInternal(const IPAddress& loopback);
+  void UdpInternal(const IPAddress& loopback);
+  void UdpReadyToSend(const IPAddress& loopback);
+  void GetSetOptionsInternal(const IPAddress& loopback);
+
+  static const int kTimeout = 5000;  // ms
+  SocketServer* ss_;
+  const IPAddress kIPv4Loopback;
+  const IPAddress kIPv6Loopback;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SOCKET_UNITTEST_H_
diff --git a/talk/base/socketadapters.cc b/talk/base/socketadapters.cc
new file mode 100644
index 0000000..4361eec
--- /dev/null
+++ b/talk/base/socketadapters.cc
@@ -0,0 +1,910 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if defined(_MSC_VER) && _MSC_VER < 1300
+#pragma warning(disable:4786)
+#endif
+
+#include <time.h>
+#include <errno.h>
+
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#define SECURITY_WIN32
+#include <security.h>
+#endif
+
+#include "talk/base/bytebuffer.h"
+#include "talk/base/common.h"
+#include "talk/base/httpcommon.h"
+#include "talk/base/logging.h"
+#include "talk/base/socketadapters.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+
+#ifdef WIN32
+#include "talk/base/sec_buffer.h"
+#endif  // WIN32
+
+namespace talk_base {
+
+BufferedReadAdapter::BufferedReadAdapter(AsyncSocket* socket, size_t size)
+    : AsyncSocketAdapter(socket), buffer_size_(size),
+      data_len_(0), buffering_(false) {
+  buffer_ = new char[buffer_size_];
+}
+
+BufferedReadAdapter::~BufferedReadAdapter() {
+  delete [] buffer_;
+}
+
+int BufferedReadAdapter::Send(const void *pv, size_t cb) {
+  if (buffering_) {
+    // TODO: Spoof error better; Signal Writeable
+    socket_->SetError(EWOULDBLOCK);
+    return -1;
+  }
+  return AsyncSocketAdapter::Send(pv, cb);
+}
+
+int BufferedReadAdapter::Recv(void *pv, size_t cb) {
+  if (buffering_) {
+    socket_->SetError(EWOULDBLOCK);
+    return -1;
+  }
+
+  size_t read = 0;
+
+  if (data_len_) {
+    read = _min(cb, data_len_);
+    memcpy(pv, buffer_, read);
+    data_len_ -= read;
+    if (data_len_ > 0) {
+      memmove(buffer_, buffer_ + read, data_len_);
+    }
+    pv = static_cast<char *>(pv) + read;
+    cb -= read;
+  }
+
+  // FIX: If cb == 0, we won't generate another read event
+
+  int res = AsyncSocketAdapter::Recv(pv, cb);
+  if (res < 0)
+    return res;
+
+  return res + static_cast<int>(read);
+}
+
+void BufferedReadAdapter::BufferInput(bool on) {
+  buffering_ = on;
+}
+
+void BufferedReadAdapter::OnReadEvent(AsyncSocket * socket) {
+  ASSERT(socket == socket_);
+
+  if (!buffering_) {
+    AsyncSocketAdapter::OnReadEvent(socket);
+    return;
+  }
+
+  if (data_len_ >= buffer_size_) {
+    LOG(INFO) << "Input buffer overflow";
+    ASSERT(false);
+    data_len_ = 0;
+  }
+
+  int len = socket_->Recv(buffer_ + data_len_, buffer_size_ - data_len_);
+  if (len < 0) {
+    // TODO: Do something better like forwarding the error to the user.
+    LOG_ERR(INFO) << "Recv";
+    return;
+  }
+
+  data_len_ += len;
+
+  ProcessInput(buffer_, &data_len_);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// This is a SSL v2 CLIENT_HELLO message.
+// TODO: Should this have a session id? The response doesn't have a
+// certificate, so the hello should have a session id.
+static const uint8 kSslClientHello[] = {
+  0x80, 0x46,                                            // msg len
+  0x01,                                                  // CLIENT_HELLO
+  0x03, 0x01,                                            // SSL 3.1
+  0x00, 0x2d,                                            // ciphersuite len
+  0x00, 0x00,                                            // session id len
+  0x00, 0x10,                                            // challenge len
+  0x01, 0x00, 0x80, 0x03, 0x00, 0x80, 0x07, 0x00, 0xc0,  // ciphersuites
+  0x06, 0x00, 0x40, 0x02, 0x00, 0x80, 0x04, 0x00, 0x80,  //
+  0x00, 0x00, 0x04, 0x00, 0xfe, 0xff, 0x00, 0x00, 0x0a,  //
+  0x00, 0xfe, 0xfe, 0x00, 0x00, 0x09, 0x00, 0x00, 0x64,  //
+  0x00, 0x00, 0x62, 0x00, 0x00, 0x03, 0x00, 0x00, 0x06,  //
+  0x1f, 0x17, 0x0c, 0xa6, 0x2f, 0x00, 0x78, 0xfc,        // challenge
+  0x46, 0x55, 0x2e, 0xb1, 0x83, 0x39, 0xf1, 0xea         //
+};
+
+// This is a TLSv1 SERVER_HELLO message.
+static const uint8 kSslServerHello[] = {
+  0x16,                                            // handshake message
+  0x03, 0x01,                                      // SSL 3.1
+  0x00, 0x4a,                                      // message len
+  0x02,                                            // SERVER_HELLO
+  0x00, 0x00, 0x46,                                // handshake len
+  0x03, 0x01,                                      // SSL 3.1
+  0x42, 0x85, 0x45, 0xa7, 0x27, 0xa9, 0x5d, 0xa0,  // server random
+  0xb3, 0xc5, 0xe7, 0x53, 0xda, 0x48, 0x2b, 0x3f,  //
+  0xc6, 0x5a, 0xca, 0x89, 0xc1, 0x58, 0x52, 0xa1,  //
+  0x78, 0x3c, 0x5b, 0x17, 0x46, 0x00, 0x85, 0x3f,  //
+  0x20,                                            // session id len
+  0x0e, 0xd3, 0x06, 0x72, 0x5b, 0x5b, 0x1b, 0x5f,  // session id
+  0x15, 0xac, 0x13, 0xf9, 0x88, 0x53, 0x9d, 0x9b,  //
+  0xe8, 0x3d, 0x7b, 0x0c, 0x30, 0x32, 0x6e, 0x38,  //
+  0x4d, 0xa2, 0x75, 0x57, 0x41, 0x6c, 0x34, 0x5c,  //
+  0x00, 0x04,                                      // RSA/RC4-128/MD5
+  0x00                                             // null compression
+};
+
+AsyncSSLSocket::AsyncSSLSocket(AsyncSocket* socket)
+    : BufferedReadAdapter(socket, 1024) {
+}
+
+int AsyncSSLSocket::Connect(const SocketAddress& addr) {
+  // Begin buffering before we connect, so that there isn't a race condition
+  // between potential senders and receiving the OnConnectEvent signal
+  BufferInput(true);
+  return BufferedReadAdapter::Connect(addr);
+}
+
+void AsyncSSLSocket::OnConnectEvent(AsyncSocket * socket) {
+  ASSERT(socket == socket_);
+  // TODO: we could buffer output too...
+  VERIFY(sizeof(kSslClientHello) ==
+      DirectSend(kSslClientHello, sizeof(kSslClientHello)));
+}
+
+void AsyncSSLSocket::ProcessInput(char* data, size_t* len) {
+  if (*len < sizeof(kSslServerHello))
+    return;
+
+  if (memcmp(kSslServerHello, data, sizeof(kSslServerHello)) != 0) {
+    Close();
+    SignalCloseEvent(this, 0);  // TODO: error code?
+    return;
+  }
+
+  *len -= sizeof(kSslServerHello);
+  if (*len > 0) {
+    memmove(data, data + sizeof(kSslServerHello), *len);
+  }
+
+  bool remainder = (*len > 0);
+  BufferInput(false);
+  SignalConnectEvent(this);
+
+  // FIX: if SignalConnect causes the socket to be destroyed, we are in trouble
+  if (remainder)
+    SignalReadEvent(this);
+}
+
+AsyncSSLServerSocket::AsyncSSLServerSocket(AsyncSocket* socket)
+     : BufferedReadAdapter(socket, 1024) {
+  BufferInput(true);
+}
+
+void AsyncSSLServerSocket::ProcessInput(char* data, size_t* len) {
+  // We only accept client hello messages.
+  if (*len < sizeof(kSslClientHello)) {
+    return;
+  }
+
+  if (memcmp(kSslClientHello, data, sizeof(kSslClientHello)) != 0) {
+    Close();
+    SignalCloseEvent(this, 0);
+    return;
+  }
+
+  *len -= sizeof(kSslClientHello);
+
+  // Clients should not send more data until the handshake is completed.
+  ASSERT(*len == 0);
+
+  // Send a server hello back to the client.
+  DirectSend(kSslServerHello, sizeof(kSslServerHello));
+
+  // Handshake completed for us, redirect input to our parent.
+  BufferInput(false);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+AsyncHttpsProxySocket::AsyncHttpsProxySocket(AsyncSocket* socket,
+                                             const std::string& user_agent,
+                                             const SocketAddress& proxy,
+                                             const std::string& username,
+                                             const CryptString& password)
+  : BufferedReadAdapter(socket, 1024), proxy_(proxy), agent_(user_agent),
+    user_(username), pass_(password), force_connect_(false), state_(PS_ERROR),
+    context_(0) {
+}
+
+AsyncHttpsProxySocket::~AsyncHttpsProxySocket() {
+  delete context_;
+}
+
+int AsyncHttpsProxySocket::Connect(const SocketAddress& addr) {
+  int ret;
+  LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::Connect("
+                  << proxy_.ToSensitiveString() << ")";
+  dest_ = addr;
+  state_ = PS_INIT;
+  if (ShouldIssueConnect()) {
+    BufferInput(true);
+  }
+  ret = BufferedReadAdapter::Connect(proxy_);
+  // TODO: Set state_ appropriately if Connect fails.
+  return ret;
+}
+
+SocketAddress AsyncHttpsProxySocket::GetRemoteAddress() const {
+  return dest_;
+}
+
+int AsyncHttpsProxySocket::Close() {
+  headers_.clear();
+  state_ = PS_ERROR;
+  dest_.Clear();
+  delete context_;
+  context_ = NULL;
+  return BufferedReadAdapter::Close();
+}
+
+Socket::ConnState AsyncHttpsProxySocket::GetState() const {
+  if (state_ < PS_TUNNEL) {
+    return CS_CONNECTING;
+  } else if (state_ == PS_TUNNEL) {
+    return CS_CONNECTED;
+  } else {
+    return CS_CLOSED;
+  }
+}
+
+void AsyncHttpsProxySocket::OnConnectEvent(AsyncSocket * socket) {
+  LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::OnConnectEvent";
+  if (!ShouldIssueConnect()) {
+    state_ = PS_TUNNEL;
+    BufferedReadAdapter::OnConnectEvent(socket);
+    return;
+  }
+  SendRequest();
+}
+
+void AsyncHttpsProxySocket::OnCloseEvent(AsyncSocket * socket, int err) {
+  LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::OnCloseEvent(" << err << ")";
+  if ((state_ == PS_WAIT_CLOSE) && (err == 0)) {
+    state_ = PS_ERROR;
+    Connect(dest_);
+  } else {
+    BufferedReadAdapter::OnCloseEvent(socket, err);
+  }
+}
+
+void AsyncHttpsProxySocket::ProcessInput(char* data, size_t* len) {
+  size_t start = 0;
+  for (size_t pos = start; state_ < PS_TUNNEL && pos < *len;) {
+    if (state_ == PS_SKIP_BODY) {
+      size_t consume = _min(*len - pos, content_length_);
+      pos += consume;
+      start = pos;
+      content_length_ -= consume;
+      if (content_length_ == 0) {
+        EndResponse();
+      }
+      continue;
+    }
+
+    if (data[pos++] != '\n')
+      continue;
+
+    size_t len = pos - start - 1;
+    if ((len > 0) && (data[start + len - 1] == '\r'))
+      --len;
+
+    data[start + len] = 0;
+    ProcessLine(data + start, len);
+    start = pos;
+  }
+
+  *len -= start;
+  if (*len > 0) {
+    memmove(data, data + start, *len);
+  }
+
+  if (state_ != PS_TUNNEL)
+    return;
+
+  bool remainder = (*len > 0);
+  BufferInput(false);
+  SignalConnectEvent(this);
+
+  // FIX: if SignalConnect causes the socket to be destroyed, we are in trouble
+  if (remainder)
+    SignalReadEvent(this);  // TODO: signal this??
+}
+
+bool AsyncHttpsProxySocket::ShouldIssueConnect() const {
+  // TODO: Think about whether a more sophisticated test
+  // than dest port == 80 is needed.
+  return force_connect_ || (dest_.port() != 80);
+}
+
+void AsyncHttpsProxySocket::SendRequest() {
+  std::stringstream ss;
+  ss << "CONNECT " << dest_.ToString() << " HTTP/1.0\r\n";
+  ss << "User-Agent: " << agent_ << "\r\n";
+  ss << "Host: " << dest_.HostAsURIString() << "\r\n";
+  ss << "Content-Length: 0\r\n";
+  ss << "Proxy-Connection: Keep-Alive\r\n";
+  ss << headers_;
+  ss << "\r\n";
+  std::string str = ss.str();
+  DirectSend(str.c_str(), str.size());
+  state_ = PS_LEADER;
+  expect_close_ = true;
+  content_length_ = 0;
+  headers_.clear();
+
+  LOG(LS_VERBOSE) << "AsyncHttpsProxySocket >> " << str;
+}
+
+void AsyncHttpsProxySocket::ProcessLine(char * data, size_t len) {
+  LOG(LS_VERBOSE) << "AsyncHttpsProxySocket << " << data;
+
+  if (len == 0) {
+    if (state_ == PS_TUNNEL_HEADERS) {
+      state_ = PS_TUNNEL;
+    } else if (state_ == PS_ERROR_HEADERS) {
+      Error(defer_error_);
+      return;
+    } else if (state_ == PS_SKIP_HEADERS) {
+      if (content_length_) {
+        state_ = PS_SKIP_BODY;
+      } else {
+        EndResponse();
+        return;
+      }
+    } else {
+      static bool report = false;
+      if (!unknown_mechanisms_.empty() && !report) {
+        report = true;
+        std::string msg(
+          "Unable to connect to the Google Talk service due to an incompatibility "
+          "with your proxy.\r\nPlease help us resolve this issue by submitting the "
+          "following information to us using our technical issue submission form "
+          "at:\r\n\r\n"
+          "http://www.google.com/support/talk/bin/request.py\r\n\r\n"
+          "We apologize for the inconvenience.\r\n\r\n"
+          "Information to submit to Google: "
+          );
+        //std::string msg("Please report the following information to foo@bar.com:\r\nUnknown methods: ");
+        msg.append(unknown_mechanisms_);
+#ifdef WIN32
+        MessageBoxA(0, msg.c_str(), "Oops!", MB_OK);
+#endif
+#ifdef POSIX
+        // TODO: Raise a signal so the UI can be separated.
+        LOG(LS_ERROR) << "Oops!\n\n" << msg;
+#endif
+      }
+      // Unexpected end of headers
+      Error(0);
+      return;
+    }
+  } else if (state_ == PS_LEADER) {
+    unsigned int code;
+    if (sscanf(data, "HTTP/%*u.%*u %u", &code) != 1) {
+      Error(0);
+      return;
+    }
+    switch (code) {
+    case 200:
+      // connection good!
+      state_ = PS_TUNNEL_HEADERS;
+      return;
+#if defined(HTTP_STATUS_PROXY_AUTH_REQ) && (HTTP_STATUS_PROXY_AUTH_REQ != 407)
+#error Wrong code for HTTP_STATUS_PROXY_AUTH_REQ
+#endif
+    case 407:  // HTTP_STATUS_PROXY_AUTH_REQ
+      state_ = PS_AUTHENTICATE;
+      return;
+    default:
+      defer_error_ = 0;
+      state_ = PS_ERROR_HEADERS;
+      return;
+    }
+  } else if ((state_ == PS_AUTHENTICATE)
+             && (_strnicmp(data, "Proxy-Authenticate:", 19) == 0)) {
+    std::string response, auth_method;
+    switch (HttpAuthenticate(data + 19, len - 19,
+                             proxy_, "CONNECT", "/",
+                             user_, pass_, context_, response, auth_method)) {
+    case HAR_IGNORE:
+      LOG(LS_VERBOSE) << "Ignoring Proxy-Authenticate: " << auth_method;
+      if (!unknown_mechanisms_.empty())
+        unknown_mechanisms_.append(", ");
+      unknown_mechanisms_.append(auth_method);
+      break;
+    case HAR_RESPONSE:
+      headers_ = "Proxy-Authorization: ";
+      headers_.append(response);
+      headers_.append("\r\n");
+      state_ = PS_SKIP_HEADERS;
+      unknown_mechanisms_.clear();
+      break;
+    case HAR_CREDENTIALS:
+      defer_error_ = SOCKET_EACCES;
+      state_ = PS_ERROR_HEADERS;
+      unknown_mechanisms_.clear();
+      break;
+    case HAR_ERROR:
+      defer_error_ = 0;
+      state_ = PS_ERROR_HEADERS;
+      unknown_mechanisms_.clear();
+      break;
+    }
+  } else if (_strnicmp(data, "Content-Length:", 15) == 0) {
+    content_length_ = strtoul(data + 15, 0, 0);
+  } else if (_strnicmp(data, "Proxy-Connection: Keep-Alive", 28) == 0) {
+    expect_close_ = false;
+    /*
+  } else if (_strnicmp(data, "Connection: close", 17) == 0) {
+    expect_close_ = true;
+    */
+  }
+}
+
+void AsyncHttpsProxySocket::EndResponse() {
+  if (!expect_close_) {
+    SendRequest();
+    return;
+  }
+
+  // No point in waiting for the server to close... let's close now
+  // TODO: Refactor out PS_WAIT_CLOSE
+  state_ = PS_WAIT_CLOSE;
+  BufferedReadAdapter::Close();
+  OnCloseEvent(this, 0);
+}
+
+void AsyncHttpsProxySocket::Error(int error) {
+  BufferInput(false);
+  Close();
+  SetError(error);
+  SignalCloseEvent(this, error);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+AsyncSocksProxySocket::AsyncSocksProxySocket(AsyncSocket* socket,
+                                             const SocketAddress& proxy,
+                                             const std::string& username,
+                                             const CryptString& password)
+    : BufferedReadAdapter(socket, 1024), state_(SS_ERROR), proxy_(proxy),
+      user_(username), pass_(password) {
+}
+
+int AsyncSocksProxySocket::Connect(const SocketAddress& addr) {
+  int ret;
+  dest_ = addr;
+  state_ = SS_INIT;
+  BufferInput(true);
+  ret = BufferedReadAdapter::Connect(proxy_);
+  // TODO: Set state_ appropriately if Connect fails.
+  return ret;
+}
+
+SocketAddress AsyncSocksProxySocket::GetRemoteAddress() const {
+  return dest_;
+}
+
+int AsyncSocksProxySocket::Close() {
+  state_ = SS_ERROR;
+  dest_.Clear();
+  return BufferedReadAdapter::Close();
+}
+
+Socket::ConnState AsyncSocksProxySocket::GetState() const {
+  if (state_ < SS_TUNNEL) {
+    return CS_CONNECTING;
+  } else if (state_ == SS_TUNNEL) {
+    return CS_CONNECTED;
+  } else {
+    return CS_CLOSED;
+  }
+}
+
+void AsyncSocksProxySocket::OnConnectEvent(AsyncSocket* socket) {
+  SendHello();
+}
+
+void AsyncSocksProxySocket::ProcessInput(char* data, size_t* len) {
+  ASSERT(state_ < SS_TUNNEL);
+
+  ByteBuffer response(data, *len);
+
+  if (state_ == SS_HELLO) {
+    uint8 ver, method;
+    if (!response.ReadUInt8(&ver) ||
+        !response.ReadUInt8(&method))
+      return;
+
+    if (ver != 5) {
+      Error(0);
+      return;
+    }
+
+    if (method == 0) {
+      SendConnect();
+    } else if (method == 2) {
+      SendAuth();
+    } else {
+      Error(0);
+      return;
+    }
+  } else if (state_ == SS_AUTH) {
+    uint8 ver, status;
+    if (!response.ReadUInt8(&ver) ||
+        !response.ReadUInt8(&status))
+      return;
+
+    if ((ver != 1) || (status != 0)) {
+      Error(SOCKET_EACCES);
+      return;
+    }
+
+    SendConnect();
+  } else if (state_ == SS_CONNECT) {
+    uint8 ver, rep, rsv, atyp;
+    if (!response.ReadUInt8(&ver) ||
+        !response.ReadUInt8(&rep) ||
+        !response.ReadUInt8(&rsv) ||
+        !response.ReadUInt8(&atyp))
+      return;
+
+    if ((ver != 5) || (rep != 0)) {
+      Error(0);
+      return;
+    }
+
+    uint16 port;
+    if (atyp == 1) {
+      uint32 addr;
+      if (!response.ReadUInt32(&addr) ||
+          !response.ReadUInt16(&port))
+        return;
+      LOG(LS_VERBOSE) << "Bound on " << addr << ":" << port;
+    } else if (atyp == 3) {
+      uint8 len;
+      std::string addr;
+      if (!response.ReadUInt8(&len) ||
+          !response.ReadString(&addr, len) ||
+          !response.ReadUInt16(&port))
+        return;
+      LOG(LS_VERBOSE) << "Bound on " << addr << ":" << port;
+    } else if (atyp == 4) {
+      std::string addr;
+      if (!response.ReadString(&addr, 16) ||
+          !response.ReadUInt16(&port))
+        return;
+      LOG(LS_VERBOSE) << "Bound on <IPV6>:" << port;
+    } else {
+      Error(0);
+      return;
+    }
+
+    state_ = SS_TUNNEL;
+  }
+
+  // Consume parsed data
+  *len = response.Length();
+  memcpy(data, response.Data(), *len);
+
+  if (state_ != SS_TUNNEL)
+    return;
+
+  bool remainder = (*len > 0);
+  BufferInput(false);
+  SignalConnectEvent(this);
+
+  // FIX: if SignalConnect causes the socket to be destroyed, we are in trouble
+  if (remainder)
+    SignalReadEvent(this);  // TODO: signal this??
+}
+
+void AsyncSocksProxySocket::SendHello() {
+  ByteBuffer request;
+  request.WriteUInt8(5);    // Socks Version
+  if (user_.empty()) {
+    request.WriteUInt8(1);  // Authentication Mechanisms
+    request.WriteUInt8(0);  // No authentication
+  } else {
+    request.WriteUInt8(2);  // Authentication Mechanisms
+    request.WriteUInt8(0);  // No authentication
+    request.WriteUInt8(2);  // Username/Password
+  }
+  DirectSend(request.Data(), request.Length());
+  state_ = SS_HELLO;
+}
+
+void AsyncSocksProxySocket::SendAuth() {
+  ByteBuffer request;
+  request.WriteUInt8(1);           // Negotiation Version
+  request.WriteUInt8(static_cast<uint8>(user_.size()));
+  request.WriteString(user_);      // Username
+  request.WriteUInt8(static_cast<uint8>(pass_.GetLength()));
+  size_t len = pass_.GetLength() + 1;
+  char * sensitive = new char[len];
+  pass_.CopyTo(sensitive, true);
+  request.WriteString(sensitive);  // Password
+  memset(sensitive, 0, len);
+  delete [] sensitive;
+  DirectSend(request.Data(), request.Length());
+  state_ = SS_AUTH;
+}
+
+void AsyncSocksProxySocket::SendConnect() {
+  ByteBuffer request;
+  request.WriteUInt8(5);              // Socks Version
+  request.WriteUInt8(1);              // CONNECT
+  request.WriteUInt8(0);              // Reserved
+  if (dest_.IsUnresolved()) {
+    std::string hostname = dest_.hostname();
+    request.WriteUInt8(3);            // DOMAINNAME
+    request.WriteUInt8(static_cast<uint8>(hostname.size()));
+    request.WriteString(hostname);    // Destination Hostname
+  } else {
+    request.WriteUInt8(1);            // IPV4
+    request.WriteUInt32(dest_.ip());  // Destination IP
+  }
+  request.WriteUInt16(dest_.port());  // Destination Port
+  DirectSend(request.Data(), request.Length());
+  state_ = SS_CONNECT;
+}
+
+void AsyncSocksProxySocket::Error(int error) {
+  state_ = SS_ERROR;
+  BufferInput(false);
+  Close();
+  SetError(SOCKET_EACCES);
+  SignalCloseEvent(this, error);
+}
+
+AsyncSocksProxyServerSocket::AsyncSocksProxyServerSocket(AsyncSocket* socket)
+    : AsyncProxyServerSocket(socket, kBufferSize), state_(SS_HELLO) {
+  BufferInput(true);
+}
+
+void AsyncSocksProxyServerSocket::ProcessInput(char* data, size_t* len) {
+  // TODO: See if the whole message has arrived
+  ASSERT(state_ < SS_CONNECT_PENDING);
+
+  ByteBuffer response(data, *len);
+  if (state_ == SS_HELLO) {
+    HandleHello(&response);
+  } else if (state_ == SS_AUTH) {
+    HandleAuth(&response);
+  } else if (state_ == SS_CONNECT) {
+    HandleConnect(&response);
+  }
+
+  // Consume parsed data
+  *len = response.Length();
+  memcpy(data, response.Data(), *len);
+}
+
+void AsyncSocksProxyServerSocket::DirectSend(const ByteBuffer& buf) {
+  BufferedReadAdapter::DirectSend(buf.Data(), buf.Length());
+}
+
+void AsyncSocksProxyServerSocket::HandleHello(ByteBuffer* request) {
+  uint8 ver, num_methods;
+  if (!request->ReadUInt8(&ver) ||
+      !request->ReadUInt8(&num_methods)) {
+    Error(0);
+    return;
+  }
+
+  if (ver != 5) {
+    Error(0);
+    return;
+  }
+
+  // Handle either no-auth (0) or user/pass auth (2)
+  uint8 method = 0xFF;
+  if (num_methods > 0 && !request->ReadUInt8(&method)) {
+    Error(0);
+    return;
+  }
+
+  // TODO: Ask the server which method to use.
+  SendHelloReply(method);
+  if (method == 0) {
+    state_ = SS_CONNECT;
+  } else if (method == 2) {
+    state_ = SS_AUTH;
+  } else {
+    state_ = SS_ERROR;
+  }
+}
+
+void AsyncSocksProxyServerSocket::SendHelloReply(int method) {
+  ByteBuffer response;
+  response.WriteUInt8(5);  // Socks Version
+  response.WriteUInt8(method);  // Auth method
+  DirectSend(response);
+}
+
+void AsyncSocksProxyServerSocket::HandleAuth(ByteBuffer* request) {
+  uint8 ver, user_len, pass_len;
+  std::string user, pass;
+  if (!request->ReadUInt8(&ver) ||
+      !request->ReadUInt8(&user_len) ||
+      !request->ReadString(&user, user_len) ||
+      !request->ReadUInt8(&pass_len) ||
+      !request->ReadString(&pass, pass_len)) {
+    Error(0);
+    return;
+  }
+
+  // TODO: Allow for checking of credentials.
+  SendAuthReply(0);
+  state_ = SS_CONNECT;
+}
+
+void AsyncSocksProxyServerSocket::SendAuthReply(int result) {
+  ByteBuffer response;
+  response.WriteUInt8(1);  // Negotiation Version
+  response.WriteUInt8(result);
+  DirectSend(response);
+}
+
+void AsyncSocksProxyServerSocket::HandleConnect(ByteBuffer* request) {
+  uint8 ver, command, reserved, addr_type;
+  uint32 ip;
+  uint16 port;
+  if (!request->ReadUInt8(&ver) ||
+      !request->ReadUInt8(&command) ||
+      !request->ReadUInt8(&reserved) ||
+      !request->ReadUInt8(&addr_type) ||
+      !request->ReadUInt32(&ip) ||
+      !request->ReadUInt16(&port)) {
+      Error(0);
+      return;
+  }
+
+  if (ver != 5 || command != 1 ||
+      reserved != 0 || addr_type != 1) {
+      Error(0);
+      return;
+  }
+
+  SignalConnectRequest(this, SocketAddress(ip, port));
+  state_ = SS_CONNECT_PENDING;
+}
+
+void AsyncSocksProxyServerSocket::SendConnectResult(int result,
+                                                    const SocketAddress& addr) {
+  if (state_ != SS_CONNECT_PENDING)
+    return;
+
+  ByteBuffer response;
+  response.WriteUInt8(5);  // Socks version
+  response.WriteUInt8((result != 0));  // 0x01 is generic error
+  response.WriteUInt8(0);  // reserved
+  response.WriteUInt8(1);  // IPv4 address
+  response.WriteUInt32(addr.ip());
+  response.WriteUInt16(addr.port());
+  DirectSend(response);
+  BufferInput(false);
+  state_ = SS_TUNNEL;
+}
+
+void AsyncSocksProxyServerSocket::Error(int error) {
+  state_ = SS_ERROR;
+  BufferInput(false);
+  Close();
+  SetError(SOCKET_EACCES);
+  SignalCloseEvent(this, error);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+LoggingSocketAdapter::LoggingSocketAdapter(AsyncSocket* socket,
+                                           LoggingSeverity level,
+                                           const char * label, bool hex_mode)
+    : AsyncSocketAdapter(socket), level_(level), hex_mode_(hex_mode) {
+  label_.append("[");
+  label_.append(label);
+  label_.append("]");
+}
+
+int LoggingSocketAdapter::Send(const void *pv, size_t cb) {
+  int res = AsyncSocketAdapter::Send(pv, cb);
+  if (res > 0)
+    LogMultiline(level_, label_.c_str(), false, pv, res, hex_mode_, &lms_);
+  return res;
+}
+
+int LoggingSocketAdapter::SendTo(const void *pv, size_t cb,
+                             const SocketAddress& addr) {
+  int res = AsyncSocketAdapter::SendTo(pv, cb, addr);
+  if (res > 0)
+    LogMultiline(level_, label_.c_str(), false, pv, res, hex_mode_, &lms_);
+  return res;
+}
+
+int LoggingSocketAdapter::Recv(void *pv, size_t cb) {
+  int res = AsyncSocketAdapter::Recv(pv, cb);
+  if (res > 0)
+    LogMultiline(level_, label_.c_str(), true, pv, res, hex_mode_, &lms_);
+  return res;
+}
+
+int LoggingSocketAdapter::RecvFrom(void *pv, size_t cb, SocketAddress *paddr) {
+  int res = AsyncSocketAdapter::RecvFrom(pv, cb, paddr);
+  if (res > 0)
+    LogMultiline(level_, label_.c_str(), true, pv, res, hex_mode_, &lms_);
+  return res;
+}
+
+int LoggingSocketAdapter::Close() {
+  LogMultiline(level_, label_.c_str(), false, NULL, 0, hex_mode_, &lms_);
+  LogMultiline(level_, label_.c_str(), true, NULL, 0, hex_mode_, &lms_);
+  LOG_V(level_) << label_ << " Closed locally";
+  return socket_->Close();
+}
+
+void LoggingSocketAdapter::OnConnectEvent(AsyncSocket * socket) {
+  LOG_V(level_) << label_ << " Connected";
+  AsyncSocketAdapter::OnConnectEvent(socket);
+}
+
+void LoggingSocketAdapter::OnCloseEvent(AsyncSocket * socket, int err) {
+  LogMultiline(level_, label_.c_str(), false, NULL, 0, hex_mode_, &lms_);
+  LogMultiline(level_, label_.c_str(), true, NULL, 0, hex_mode_, &lms_);
+  LOG_V(level_) << label_ << " Closed with error: " << err;
+  AsyncSocketAdapter::OnCloseEvent(socket, err);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
diff --git a/talk/base/socketadapters.h b/talk/base/socketadapters.h
new file mode 100644
index 0000000..320da6f
--- /dev/null
+++ b/talk/base/socketadapters.h
@@ -0,0 +1,261 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_SOCKETADAPTERS_H_
+#define TALK_BASE_SOCKETADAPTERS_H_
+
+#include <map>
+#include <string>
+
+#include "talk/base/asyncsocket.h"
+#include "talk/base/cryptstring.h"
+#include "talk/base/logging.h"
+
+namespace talk_base {
+
+struct HttpAuthContext;
+class ByteBuffer;
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Implements a socket adapter that can buffer and process data internally,
+// as in the case of connecting to a proxy, where you must speak the proxy
+// protocol before commencing normal socket behavior.
+class BufferedReadAdapter : public AsyncSocketAdapter {
+ public:
+  BufferedReadAdapter(AsyncSocket* socket, size_t buffer_size);
+  virtual ~BufferedReadAdapter();
+
+  virtual int Send(const void* pv, size_t cb);
+  virtual int Recv(void* pv, size_t cb);
+
+ protected:
+  int DirectSend(const void* pv, size_t cb) {
+    return AsyncSocketAdapter::Send(pv, cb);
+  }
+
+  void BufferInput(bool on = true);
+  virtual void ProcessInput(char* data, size_t* len) = 0;
+
+  virtual void OnReadEvent(AsyncSocket * socket);
+
+ private:
+  char * buffer_;
+  size_t buffer_size_, data_len_;
+  bool buffering_;
+  DISALLOW_EVIL_CONSTRUCTORS(BufferedReadAdapter);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Interface for implementing proxy server sockets.
+class AsyncProxyServerSocket : public BufferedReadAdapter {
+ public:
+  AsyncProxyServerSocket(AsyncSocket* socket, size_t buffer_size)
+      : BufferedReadAdapter(socket, buffer_size) {}
+  sigslot::signal2<AsyncProxyServerSocket*,
+                   const SocketAddress&>  SignalConnectRequest;
+  virtual void SendConnectResult(int err, const SocketAddress& addr) = 0;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Implements a socket adapter that performs the client side of a
+// fake SSL handshake. Used for "ssltcp" P2P functionality.
+class AsyncSSLSocket : public BufferedReadAdapter {
+ public:
+  explicit AsyncSSLSocket(AsyncSocket* socket);
+
+  virtual int Connect(const SocketAddress& addr);
+
+ protected:
+  virtual void OnConnectEvent(AsyncSocket* socket);
+  virtual void ProcessInput(char* data, size_t* len);
+  DISALLOW_EVIL_CONSTRUCTORS(AsyncSSLSocket);
+};
+
+// Implements a socket adapter that performs the server side of a
+// fake SSL handshake. Used when implementing a relay server that does "ssltcp".
+class AsyncSSLServerSocket : public BufferedReadAdapter {
+ public:
+  explicit AsyncSSLServerSocket(AsyncSocket* socket);
+
+ protected:
+  virtual void ProcessInput(char* data, size_t* len);
+  DISALLOW_EVIL_CONSTRUCTORS(AsyncSSLServerSocket);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Implements a socket adapter that speaks the HTTP/S proxy protocol.
+class AsyncHttpsProxySocket : public BufferedReadAdapter {
+ public:
+  AsyncHttpsProxySocket(AsyncSocket* socket, const std::string& user_agent,
+    const SocketAddress& proxy,
+    const std::string& username, const CryptString& password);
+  virtual ~AsyncHttpsProxySocket();
+
+  // If connect is forced, the adapter will always issue an HTTP CONNECT to the
+  // target address.  Otherwise, it will connect only if the destination port
+  // is not port 80.
+  void SetForceConnect(bool force) { force_connect_ = force; }
+
+  virtual int Connect(const SocketAddress& addr);
+  virtual SocketAddress GetRemoteAddress() const;
+  virtual int Close();
+  virtual ConnState GetState() const;
+
+ protected:
+  virtual void OnConnectEvent(AsyncSocket* socket);
+  virtual void OnCloseEvent(AsyncSocket* socket, int err);
+  virtual void ProcessInput(char* data, size_t* len);
+
+  bool ShouldIssueConnect() const;
+  void SendRequest();
+  void ProcessLine(char* data, size_t len);
+  void EndResponse();
+  void Error(int error);
+
+ private:
+  SocketAddress proxy_, dest_;
+  std::string agent_, user_, headers_;
+  CryptString pass_;
+  bool force_connect_;
+  size_t content_length_;
+  int defer_error_;
+  bool expect_close_;
+  enum ProxyState {
+    PS_INIT, PS_LEADER, PS_AUTHENTICATE, PS_SKIP_HEADERS, PS_ERROR_HEADERS,
+    PS_TUNNEL_HEADERS, PS_SKIP_BODY, PS_TUNNEL, PS_WAIT_CLOSE, PS_ERROR
+  } state_;
+  HttpAuthContext * context_;
+  std::string unknown_mechanisms_;
+  DISALLOW_EVIL_CONSTRUCTORS(AsyncHttpsProxySocket);
+};
+
+/* TODO: Implement this.
+class AsyncHttpsProxyServerSocket : public AsyncProxyServerSocket {
+ public:
+  explicit AsyncHttpsProxyServerSocket(AsyncSocket* socket);
+
+ private:
+  virtual void ProcessInput(char * data, size_t& len);
+  void Error(int error);
+  DISALLOW_EVIL_CONSTRUCTORS(AsyncHttpsProxyServerSocket);
+};
+*/
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Implements a socket adapter that speaks the SOCKS proxy protocol.
+class AsyncSocksProxySocket : public BufferedReadAdapter {
+ public:
+  AsyncSocksProxySocket(AsyncSocket* socket, const SocketAddress& proxy,
+    const std::string& username, const CryptString& password);
+
+  virtual int Connect(const SocketAddress& addr);
+  virtual SocketAddress GetRemoteAddress() const;
+  virtual int Close();
+  virtual ConnState GetState() const;
+
+ protected:
+  virtual void OnConnectEvent(AsyncSocket* socket);
+  virtual void ProcessInput(char* data, size_t* len);
+
+  void SendHello();
+  void SendConnect();
+  void SendAuth();
+  void Error(int error);
+
+ private:
+  enum State {
+    SS_INIT, SS_HELLO, SS_AUTH, SS_CONNECT, SS_TUNNEL, SS_ERROR
+  };
+  State state_;
+  SocketAddress proxy_, dest_;
+  std::string user_;
+  CryptString pass_;
+  DISALLOW_EVIL_CONSTRUCTORS(AsyncSocksProxySocket);
+};
+
+// Implements a proxy server socket for the SOCKS protocol.
+class AsyncSocksProxyServerSocket : public AsyncProxyServerSocket {
+ public:
+  explicit AsyncSocksProxyServerSocket(AsyncSocket* socket);
+
+ private:
+  virtual void ProcessInput(char* data, size_t* len);
+  void DirectSend(const ByteBuffer& buf);
+
+  void HandleHello(ByteBuffer* request);
+  void SendHelloReply(int method);
+  void HandleAuth(ByteBuffer* request);
+  void SendAuthReply(int result);
+  void HandleConnect(ByteBuffer* request);
+  virtual void SendConnectResult(int result, const SocketAddress& addr);
+
+  void Error(int error);
+
+  static const int kBufferSize = 1024;
+  enum State {
+    SS_HELLO, SS_AUTH, SS_CONNECT, SS_CONNECT_PENDING, SS_TUNNEL, SS_ERROR
+  };
+  State state_;
+  DISALLOW_EVIL_CONSTRUCTORS(AsyncSocksProxyServerSocket);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Implements a socket adapter that logs everything that it sends and receives.
+class LoggingSocketAdapter : public AsyncSocketAdapter {
+ public:
+  LoggingSocketAdapter(AsyncSocket* socket, LoggingSeverity level,
+                 const char * label, bool hex_mode = false);
+
+  virtual int Send(const void *pv, size_t cb);
+  virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr);
+  virtual int Recv(void *pv, size_t cb);
+  virtual int RecvFrom(void *pv, size_t cb, SocketAddress *paddr);
+  virtual int Close();
+
+ protected:
+  virtual void OnConnectEvent(AsyncSocket * socket);
+  virtual void OnCloseEvent(AsyncSocket * socket, int err);
+
+ private:
+  LoggingSeverity level_;
+  std::string label_;
+  bool hex_mode_;
+  LogMultilineState lms_;
+  DISALLOW_EVIL_CONSTRUCTORS(LoggingSocketAdapter);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SOCKETADAPTERS_H_
diff --git a/talk/base/socketaddress.cc b/talk/base/socketaddress.cc
new file mode 100644
index 0000000..193a232
--- /dev/null
+++ b/talk/base/socketaddress.cc
@@ -0,0 +1,398 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/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>
+#include <unistd.h>
+#endif
+
+#include <sstream>
+
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/nethelpers.h"
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#endif
+
+namespace talk_base {
+
+SocketAddress::SocketAddress() {
+  Clear();
+}
+
+SocketAddress::SocketAddress(const std::string& hostname, int port) {
+  SetIP(hostname);
+  SetPort(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);
+}
+
+SocketAddress::SocketAddress(const SocketAddress& addr) {
+  this->operator=(addr);
+}
+
+void SocketAddress::Clear() {
+  hostname_.clear();
+  literal_ = false;
+  ip_ = IPAddress();
+  port_ = 0;
+  scope_id_ = 0;
+}
+
+bool SocketAddress::IsNil() const {
+  return hostname_.empty() && IPIsUnspec(ip_) && 0 == port_;
+}
+
+bool SocketAddress::IsComplete() const {
+  return (!IPIsAny(ip_)) && (0 != port_);
+}
+
+SocketAddress& SocketAddress::operator=(const SocketAddress& addr) {
+  hostname_ = addr.hostname_;
+  ip_ = addr.ip_;
+  port_ = addr.port_;
+  literal_ = addr.literal_;
+  scope_id_ = addr.scope_id_;
+  return *this;
+}
+
+void SocketAddress::SetIP(uint32 ip_as_host_order_integer) {
+  hostname_.clear();
+  literal_ = false;
+  ip_ = IPAddress(ip_as_host_order_integer);
+  scope_id_ = 0;
+}
+
+void SocketAddress::SetIP(const IPAddress& ip) {
+  hostname_.clear();
+  literal_ = false;
+  ip_ = ip;
+  scope_id_ = 0;
+}
+
+void SocketAddress::SetIP(const std::string& hostname) {
+  hostname_ = hostname;
+  literal_ = IPFromString(hostname, &ip_);
+  if (!literal_) {
+    ip_ = IPAddress();
+  }
+  scope_id_ = 0;
+}
+
+void SocketAddress::SetResolvedIP(uint32 ip_as_host_order_integer) {
+  ip_ = IPAddress(ip_as_host_order_integer);
+  scope_id_ = 0;
+}
+
+void SocketAddress::SetResolvedIP(const IPAddress& ip) {
+  ip_ = ip;
+  scope_id_ = 0;
+}
+
+void SocketAddress::SetPort(int port) {
+  ASSERT((0 <= port) && (port < 65536));
+  port_ = port;
+}
+
+uint32 SocketAddress::ip() const {
+  return ip_.v4AddressAsHostOrderInteger();
+}
+
+const IPAddress& SocketAddress::ipaddr() const {
+  return ip_;
+}
+
+uint16 SocketAddress::port() const {
+  return port_;
+}
+
+std::string SocketAddress::HostAsURIString() const {
+  // 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_;
+  if (ip_.family() == AF_INET6) {
+    return "[" + ip_.ToString() + "]";
+  } else {
+    return ip_.ToString();
+  }
+}
+
+std::string SocketAddress::HostAsSensitiveURIString() const {
+  // 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_;
+  if (ip_.family() == AF_INET6) {
+    return "[" + ip_.ToSensitiveString() + "]";
+  } else {
+    return ip_.ToSensitiveString();
+  }
+}
+
+std::string SocketAddress::PortAsString() const {
+  std::ostringstream ost;
+  ost << port_;
+  return ost.str();
+}
+
+std::string SocketAddress::ToString() const {
+  std::ostringstream ost;
+  ost << *this;
+  return ost.str();
+}
+
+std::string SocketAddress::ToSensitiveString() const {
+  std::ostringstream ost;
+  ost << HostAsSensitiveURIString() << ":" << port();
+  return ost.str();
+}
+
+bool SocketAddress::FromString(const std::string& str) {
+  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;
+}
+
+std::ostream& operator<<(std::ostream& os, const SocketAddress& addr) {
+  os << addr.HostAsURIString() << ":" << addr.port();
+  return os;
+}
+
+bool SocketAddress::IsAnyIP() const {
+  return IPIsAny(ip_);
+}
+
+bool SocketAddress::IsLoopbackIP() const {
+  return IPIsLoopback(ip_) || (IPIsAny(ip_) &&
+                               0 == strcmp(hostname_.c_str(), "localhost"));
+}
+
+bool SocketAddress::IsPrivateIP() const {
+  return IPIsPrivate(ip_);
+}
+
+bool SocketAddress::IsUnresolvedIP() const {
+  return IPIsUnspec(ip_) && !literal_ && !hostname_.empty();
+}
+
+bool SocketAddress::operator==(const SocketAddress& addr) const {
+  return EqualIPs(addr) && EqualPorts(addr);
+}
+
+bool SocketAddress::operator<(const SocketAddress& addr) const {
+  if (ip_ < addr.ip_)
+    return true;
+  else if (addr.ip_ < ip_)
+    return false;
+
+  // We only check hostnames if both IPs are zero.  This matches EqualIPs()
+  if (addr.IsAnyIP()) {
+    if (hostname_ < addr.hostname_)
+      return true;
+    else if (addr.hostname_ < hostname_)
+      return false;
+  }
+
+  return port_ < addr.port_;
+}
+
+bool SocketAddress::EqualIPs(const SocketAddress& addr) const {
+  return (ip_ == addr.ip_) &&
+      ((!IPIsAny(ip_)) || (hostname_ == addr.hostname_));
+}
+
+bool SocketAddress::EqualPorts(const SocketAddress& addr) const {
+  return (port_ == addr.port_);
+}
+
+size_t SocketAddress::Hash() const {
+  size_t h = 0;
+  h ^= HashIP(ip_);
+  h ^= port_ | (port_ << 16);
+  return h;
+}
+
+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 (IPIsAny(ip_)) {
+    saddr->sin_addr.s_addr = INADDR_ANY;
+  } else {
+    saddr->sin_addr = ip_.ipv4_address();
+  }
+}
+
+bool SocketAddress::FromSockAddr(const sockaddr_in& saddr) {
+  if (saddr.sin_family != AF_INET)
+    return false;
+  SetIP(NetworkToHost32(saddr.sin_addr.s_addr));
+  SetPort(NetworkToHost16(saddr.sin_port));
+  literal_ = false;
+  return true;
+}
+
+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) {
+  return IPAddress(ip_as_host_order_integer).ToString();
+}
+
+std::string IPToSensitiveString(uint32 ip_as_host_order_integer) {
+  return IPAddress(ip_as_host_order_integer).ToSensitiveString();
+}
+
+bool SocketAddress::StringToIP(const std::string& hostname, uint32* ip) {
+  in_addr addr;
+  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);
+  return ip;
+}
+
+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;
+}
+
+SocketAddress EmptySocketAddressWithFamily(int family) {
+  if (family == AF_INET) {
+    return SocketAddress(IPAddress(INADDR_ANY), 0);
+  } else if (family == AF_INET6) {
+    return SocketAddress(IPAddress(in6addr_any), 0);
+  }
+  return SocketAddress();
+}
+
+}  // namespace talk_base
diff --git a/talk/base/socketaddress.h b/talk/base/socketaddress.h
new file mode 100644
index 0000000..08f2659
--- /dev/null
+++ b/talk/base/socketaddress.h
@@ -0,0 +1,231 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_SOCKETADDRESS_H_
+#define TALK_BASE_SOCKETADDRESS_H_
+
+#include <string>
+#include <vector>
+#include <iosfwd>
+#include "talk/base/basictypes.h"
+#include "talk/base/ipaddress.h"
+
+#undef SetPort
+
+struct sockaddr_in;
+struct sockaddr_storage;
+
+namespace talk_base {
+
+// Records an IP address and port.
+class SocketAddress {
+ public:
+  // Creates a nil address.
+  SocketAddress();
+
+  // Creates the address with the given host and port. Host may be a
+  // literal IP string or a hostname to be resolved later.
+  SocketAddress(const std::string& hostname, int port);
+
+  // Creates the address with the given IP and 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);
+
+  // Resets to the nil address.
+  void Clear();
+
+  // Determines if this is a nil address (empty hostname, any IP, null port)
+  bool IsNil() const;
+
+  // Returns true if ip and port are set.
+  bool IsComplete() const;
+
+  // Replaces our address with the given one.
+  SocketAddress& operator=(const SocketAddress& addr);
+
+  // Changes the IP of this address to the given one, and clears the hostname
+  // 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(const IPAddress& ip);
+
+  // Changes the hostname of this address to the given one.
+  // Does not resolve the address; use Resolve to do so.
+  void SetIP(const std::string& hostname);
+
+  // Sets the IP address while retaining the hostname.  Useful for bypassing
+  // DNS for a pre-resolved IP.
+  // 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.
+  const std::string& hostname() const { return hostname_; }
+
+  // Returns the IP address as a host byte order integer.
+  // Returns 0 for non-v4 addresses.
+  uint32 ip() const;
+
+  const IPAddress& ipaddr() const;
+
+  int family() const {return ip_.family(); }
+
+  // Returns the port part of this address.
+  uint16 port() const;
+
+  // 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 'host' portion of the address (hostname or IP) in a form
+  // suitable for use in a URI. If both IP and hostname are present, hostname
+  // is preferred. IPv6 addresses are enclosed in square brackets ('[' and ']').
+  std::string HostAsURIString() const;
+
+  // Same as HostAsURIString but anonymizes IP addresses by hiding the last
+  // part.
+  std::string HostAsSensitiveURIString() const;
+
+  // Returns the port as a string.
+  std::string PortAsString() const;
+
+  // Returns hostname:port or [hostname]:port.
+  std::string ToString() const;
+
+  // Same as ToString but anonymizes it by hiding the last part.
+  std::string ToSensitiveString() const;
+
+  // 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.
+  // 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.
+  // 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 whether the IP address is in one of the private ranges:
+  // 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.
+  bool IsUnresolvedIP() const;
+  inline bool IsUnresolved() const { return IsUnresolvedIP(); }  // deprecated
+
+  // Determines whether this address is identical to the given one.
+  bool operator ==(const SocketAddress& addr) const;
+  inline bool operator !=(const SocketAddress& addr) const {
+    return !this->operator ==(addr);
+  }
+
+  // Compares based on IP and then port.
+  bool operator <(const SocketAddress& addr) const;
+
+  // Determines whether this address has the same IP as the one given.
+  bool EqualIPs(const SocketAddress& addr) const;
+
+  // Determines whether this address has the same port as the one given.
+  bool EqualPorts(const SocketAddress& addr) const;
+
+  // Hashes this address into a small number.
+  size_t Hash() const;
+
+  // 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);
+
+  // 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);
+
+  // Same as IPToString but anonymizes it by hiding the last part.
+  // TODO: Deprecate this.
+  static std::string IPToSensitiveString(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  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);
+
+  // Converts the IP address given in printable form into an IPAddress.
+  static bool StringToIP(const std::string& str, IPAddress* ip);
+
+ private:
+  std::string hostname_;
+  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);
+SocketAddress EmptySocketAddressWithFamily(int family);
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SOCKETADDRESS_H_
diff --git a/talk/base/socketaddress_unittest.cc b/talk/base/socketaddress_unittest.cc
new file mode 100644
index 0000000..c57db8d
--- /dev/null
+++ b/talk/base/socketaddress_unittest.cc
@@ -0,0 +1,352 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef POSIX
+#include <netinet/in.h>  // for sockaddr_in
+#endif
+
+#include "talk/base/gunit.h"
+#include "talk/base/socketaddress.h"
+#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 kTestV6AddrAnonymizedString = "2001:db8:1020::";
+const std::string kTestV6AddrFullString =
+    "[2001:db8:1020:3040:5060:7080:90a0:b0c0]:5678";
+const std::string kTestV6AddrFullAnonymizedString = "[2001:db8:1020::]:5678";
+
+TEST(SocketAddressTest, TestDefaultCtor) {
+  SocketAddress addr;
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  EXPECT_EQ(IPAddress(), addr.ipaddr());
+  EXPECT_EQ(0, addr.port());
+  EXPECT_EQ("", addr.hostname());
+}
+
+TEST(SocketAddressTest, TestIPPortCtor) {
+  SocketAddress addr(IPAddress(0x01020304), 5678);
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  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, TestIPv4StringPortCtor) {
+  SocketAddress addr("1.2.3.4", 5678);
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  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(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());
+}
+
+TEST(SocketAddressTest, TestHostnamePortCtor) {
+  SocketAddress addr("a.b.com", 5678);
+  EXPECT_TRUE(addr.IsUnresolvedIP());
+  EXPECT_EQ(IPAddress(), addr.ipaddr());
+  EXPECT_EQ(5678, addr.port());
+  EXPECT_EQ("a.b.com", addr.hostname());
+  EXPECT_EQ("a.b.com:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestCopyCtor) {
+  SocketAddress from("1.2.3.4", 5678);
+  SocketAddress addr(from);
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  EXPECT_EQ(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, TestAssign) {
+  SocketAddress from("1.2.3.4", 5678);
+  SocketAddress addr(IPAddress(0x88888888), 9999);
+  addr = from;
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  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(IPAddress(0x88888888), 9999);
+  addr.SetIP(IPAddress(0x01020304));
+  addr.SetPort(5678);
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  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(IPAddress(0x88888888), 9999);
+  addr.SetIP("1.2.3.4");
+  addr.SetPort(5678);
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  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(IPAddress(0x88888888), 9999);
+  addr.SetIP("a.b.com");
+  addr.SetPort(5678);
+  EXPECT_TRUE(addr.IsUnresolvedIP());
+  EXPECT_EQ(IPAddress(), addr.ipaddr());
+  EXPECT_EQ(5678, addr.port());
+  EXPECT_EQ("a.b.com", addr.hostname());
+  EXPECT_EQ("a.b.com:5678", addr.ToString());
+  addr.SetResolvedIP(IPAddress(0x01020304));
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr());
+  EXPECT_EQ("a.b.com", addr.hostname());
+  EXPECT_EQ("a.b.com:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestFromIPv4String) {
+  SocketAddress addr;
+  EXPECT_TRUE(addr.FromString("1.2.3.4:5678"));
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  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(IPAddress(), addr.ipaddr());
+  EXPECT_EQ(5678, addr.port());
+  EXPECT_EQ("a.b.com", addr.hostname());
+  EXPECT_EQ("a.b.com:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestToFromSockAddr) {
+  SocketAddress from("1.2.3.4", 5678), addr;
+  sockaddr_in addr_in;
+  from.ToSockAddr(&addr_in);
+  EXPECT_TRUE(addr.FromSockAddr(addr_in));
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr());
+  EXPECT_EQ(5678, addr.port());
+  EXPECT_EQ("", addr.hostname());
+  EXPECT_EQ("1.2.3.4:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestToFromSockAddrStorage) {
+  SocketAddress from("1.2.3.4", 5678), addr;
+  sockaddr_storage addr_storage;
+  from.ToSockAddrStorage(&addr_storage);
+  EXPECT_TRUE(SocketAddressFromSockAddrStorage(addr_storage, &addr));
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  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));
+}
+
+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);
+}
+
+TEST(SocketAddressTest, TestToSensitiveString) {
+  SocketAddress addr_v4("1.2.3.4", 5678);
+  EXPECT_EQ("1.2.3.4", addr_v4.HostAsURIString());
+  EXPECT_EQ("1.2.3.4:5678", addr_v4.ToString());
+  EXPECT_EQ("1.2.3.4", addr_v4.HostAsSensitiveURIString());
+  EXPECT_EQ("1.2.3.4:5678", addr_v4.ToSensitiveString());
+  IPAddress::set_strip_sensitive(true);
+  EXPECT_EQ("1.2.3.x", addr_v4.HostAsSensitiveURIString());
+  EXPECT_EQ("1.2.3.x:5678", addr_v4.ToSensitiveString());
+  IPAddress::set_strip_sensitive(false);
+
+  SocketAddress addr_v6(kTestV6AddrString, 5678);
+  EXPECT_EQ("[" + kTestV6AddrString + "]", addr_v6.HostAsURIString());
+  EXPECT_EQ(kTestV6AddrFullString, addr_v6.ToString());
+  EXPECT_EQ("[" + kTestV6AddrString + "]", addr_v6.HostAsSensitiveURIString());
+  EXPECT_EQ(kTestV6AddrFullString, addr_v6.ToSensitiveString());
+  IPAddress::set_strip_sensitive(true);
+  EXPECT_EQ("[" + kTestV6AddrAnonymizedString + "]",
+            addr_v6.HostAsSensitiveURIString());
+  EXPECT_EQ(kTestV6AddrFullAnonymizedString, addr_v6.ToSensitiveString());
+  IPAddress::set_strip_sensitive(false);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/socketaddresspair.cc b/talk/base/socketaddresspair.cc
new file mode 100644
index 0000000..7f190a9
--- /dev/null
+++ b/talk/base/socketaddresspair.cc
@@ -0,0 +1,58 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/socketaddresspair.h"
+
+namespace talk_base {
+
+SocketAddressPair::SocketAddressPair(
+    const SocketAddress& src, const SocketAddress& dest)
+    : src_(src), dest_(dest) {
+}
+
+
+bool SocketAddressPair::operator ==(const SocketAddressPair& p) const {
+  return (src_ == p.src_) && (dest_ == p.dest_);
+}
+
+bool SocketAddressPair::operator <(const SocketAddressPair& p) const {
+  if (src_ < p.src_)
+    return true;
+  if (p.src_ < src_)
+    return false;
+  if (dest_ < p.dest_)
+    return true;
+  if (p.dest_ < dest_)
+    return false;
+  return false;
+}
+
+size_t SocketAddressPair::Hash() const {
+  return src_.Hash() ^ dest_.Hash();
+}
+
+} // namespace talk_base
diff --git a/talk/base/socketaddresspair.h b/talk/base/socketaddresspair.h
new file mode 100644
index 0000000..10f5d30
--- /dev/null
+++ b/talk/base/socketaddresspair.h
@@ -0,0 +1,58 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_SOCKETADDRESSPAIR_H__
+#define TALK_BASE_SOCKETADDRESSPAIR_H__
+
+#include "talk/base/socketaddress.h"
+
+namespace talk_base {
+
+// Records a pair (source,destination) of socket addresses.  The two addresses
+// identify a connection between two machines.  (For UDP, this "connection" is
+// not maintained explicitly in a socket.)
+class SocketAddressPair {
+public:
+  SocketAddressPair() {}
+  SocketAddressPair(const SocketAddress& srs, const SocketAddress& dest);
+
+  const SocketAddress& source() const { return src_; }
+  const SocketAddress& destination() const { return dest_; }
+
+  bool operator ==(const SocketAddressPair& r) const;
+  bool operator <(const SocketAddressPair& r) const;
+
+  size_t Hash() const;
+
+private:
+  SocketAddress src_;
+  SocketAddress dest_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_SOCKETADDRESSPAIR_H__
diff --git a/talk/base/socketfactory.h b/talk/base/socketfactory.h
new file mode 100644
index 0000000..291df60
--- /dev/null
+++ b/talk/base/socketfactory.h
@@ -0,0 +1,55 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_SOCKETFACTORY_H__
+#define TALK_BASE_SOCKETFACTORY_H__
+
+#include "talk/base/socket.h"
+#include "talk/base/asyncsocket.h"
+
+namespace talk_base {
+
+class SocketFactory {
+public:
+  virtual ~SocketFactory() {}
+
+  // Returns a new socket for blocking communication.  The type can be
+  // SOCK_DGRAM and SOCK_STREAM.
+  // TODO: C++ inheritance rules mean that all users must have both
+  // CreateSocket(int) and CreateSocket(int,int). Will remove CreateSocket(int)
+  // (and CreateAsyncSocket(int) when all callers are changed.
+  virtual Socket* CreateSocket(int type) = 0;
+  virtual Socket* CreateSocket(int family, int type) = 0;
+  // Returns a new socket for nonblocking communication.  The type can be
+  // SOCK_DGRAM and SOCK_STREAM.
+  virtual AsyncSocket* CreateAsyncSocket(int type) = 0;
+  virtual AsyncSocket* CreateAsyncSocket(int family, int type) = 0;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_SOCKETFACTORY_H__
diff --git a/talk/base/socketpool.cc b/talk/base/socketpool.cc
new file mode 100644
index 0000000..10d4303
--- /dev/null
+++ b/talk/base/socketpool.cc
@@ -0,0 +1,297 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <iomanip>
+
+#include "talk/base/asyncsocket.h"
+#include "talk/base/logging.h"
+#include "talk/base/socketfactory.h"
+#include "talk/base/socketpool.h"
+#include "talk/base/socketstream.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamCache - Caches a set of open streams, defers creation to a separate
+//  StreamPool.
+///////////////////////////////////////////////////////////////////////////////
+
+StreamCache::StreamCache(StreamPool* pool) : pool_(pool) {
+}
+
+StreamCache::~StreamCache() {
+  for (ConnectedList::iterator it = active_.begin(); it != active_.end();
+       ++it) {
+    delete it->second;
+  }
+  for (ConnectedList::iterator it = cached_.begin(); it != cached_.end();
+       ++it) {
+    delete it->second;
+  }
+}
+
+StreamInterface* StreamCache::RequestConnectedStream(
+    const SocketAddress& remote, int* err) {
+  LOG_F(LS_VERBOSE) << "(" << remote << ")";
+  for (ConnectedList::iterator it = cached_.begin(); it != cached_.end();
+       ++it) {
+    if (remote == it->first) {
+      it->second->SignalEvent.disconnect(this);
+      // Move from cached_ to active_
+      active_.push_front(*it);
+      cached_.erase(it);
+      if (err)
+        *err = 0;
+      LOG_F(LS_VERBOSE) << "Providing cached stream";
+      return active_.front().second;
+    }
+  }
+  if (StreamInterface* stream = pool_->RequestConnectedStream(remote, err)) {
+    // We track active streams so that we can remember their address
+    active_.push_front(ConnectedStream(remote, stream));
+    LOG_F(LS_VERBOSE) << "Providing new stream";
+    return active_.front().second;
+  }
+  return NULL;
+}
+
+void StreamCache::ReturnConnectedStream(StreamInterface* stream) {
+  for (ConnectedList::iterator it = active_.begin(); it != active_.end();
+       ++it) {
+    if (stream == it->second) {
+      LOG_F(LS_VERBOSE) << "(" << it->first << ")";
+      if (stream->GetState() == SS_CLOSED) {
+        // Return closed streams
+        LOG_F(LS_VERBOSE) << "Returning closed stream";
+        pool_->ReturnConnectedStream(it->second);
+      } else {
+        // Monitor open streams
+        stream->SignalEvent.connect(this, &StreamCache::OnStreamEvent);
+        LOG_F(LS_VERBOSE) << "Caching stream";
+        cached_.push_front(*it);
+      }
+      active_.erase(it);
+      return;
+    }
+  }
+  ASSERT(false);
+}
+
+void StreamCache::OnStreamEvent(StreamInterface* stream, int events, int err) {
+  if ((events & SE_CLOSE) == 0) {
+    LOG_F(LS_WARNING) << "(" << events << ", " << err
+                      << ") received non-close event";
+    return;
+  }
+  for (ConnectedList::iterator it = cached_.begin(); it != cached_.end();
+       ++it) {
+    if (stream == it->second) {
+      LOG_F(LS_VERBOSE) << "(" << it->first << ")";
+      // We don't cache closed streams, so return it.
+      it->second->SignalEvent.disconnect(this);
+      LOG_F(LS_VERBOSE) << "Returning closed stream";
+      pool_->ReturnConnectedStream(it->second);
+      cached_.erase(it);
+      return;
+    }
+  }
+  ASSERT(false);
+}
+
+//////////////////////////////////////////////////////////////////////
+// NewSocketPool
+//////////////////////////////////////////////////////////////////////
+
+NewSocketPool::NewSocketPool(SocketFactory* factory) : factory_(factory) {
+}
+
+NewSocketPool::~NewSocketPool() {
+}
+
+StreamInterface*
+NewSocketPool::RequestConnectedStream(const SocketAddress& remote, int* err) {
+  AsyncSocket* socket =
+      factory_->CreateAsyncSocket(remote.family(), SOCK_STREAM);
+  if (!socket) {
+    if (err)
+      *err = -1;
+    return NULL;
+  }
+  if ((socket->Connect(remote) != 0) && !socket->IsBlocking()) {
+    if (err)
+      *err = socket->GetError();
+    delete socket;
+    return NULL;
+  }
+  if (err)
+    *err = 0;
+  return new SocketStream(socket);
+}
+
+void
+NewSocketPool::ReturnConnectedStream(StreamInterface* stream) {
+  Thread::Current()->Dispose(stream);
+}
+
+//////////////////////////////////////////////////////////////////////
+// ReuseSocketPool
+//////////////////////////////////////////////////////////////////////
+
+ReuseSocketPool::ReuseSocketPool(SocketFactory* factory)
+: factory_(factory), stream_(NULL), checked_out_(false) {
+}
+
+ReuseSocketPool::~ReuseSocketPool() {
+  ASSERT(!checked_out_);
+  delete stream_;
+}
+
+StreamInterface*
+ReuseSocketPool::RequestConnectedStream(const SocketAddress& remote, int* err) {
+  // Only one socket can be used from this "pool" at a time
+  ASSERT(!checked_out_);
+  if (!stream_) {
+    LOG_F(LS_VERBOSE) << "Creating new socket";
+    int family = remote.family();
+    // TODO: Deal with this when we/I clean up DNS resolution.
+    if (remote.IsUnresolvedIP()) {
+      family = AF_INET;
+    }
+    AsyncSocket* socket =
+        factory_->CreateAsyncSocket(family, SOCK_STREAM);
+    if (!socket) {
+      if (err)
+        *err = -1;
+      return NULL;
+    }
+    stream_ = new SocketStream(socket);
+  }
+  if ((stream_->GetState() == SS_OPEN) && (remote == remote_)) {
+    LOG_F(LS_VERBOSE) << "Reusing connection to: " << remote_;
+  } else {
+    remote_ = remote;
+    stream_->Close();
+    if ((stream_->GetSocket()->Connect(remote_) != 0)
+        && !stream_->GetSocket()->IsBlocking()) {
+      if (err)
+        *err = stream_->GetSocket()->GetError();
+      return NULL;
+    } else {
+      LOG_F(LS_VERBOSE) << "Opening connection to: " << remote_;
+    }
+  }
+  stream_->SignalEvent.disconnect(this);
+  checked_out_ = true;
+  if (err)
+    *err = 0;
+  return stream_;
+}
+
+void
+ReuseSocketPool::ReturnConnectedStream(StreamInterface* stream) {
+  ASSERT(stream == stream_);
+  ASSERT(checked_out_);
+  checked_out_ = false;
+  // Until the socket is reused, monitor it to determine if it closes.
+  stream_->SignalEvent.connect(this, &ReuseSocketPool::OnStreamEvent);
+}
+
+void
+ReuseSocketPool::OnStreamEvent(StreamInterface* stream, int events, int err) {
+  ASSERT(stream == stream_);
+  ASSERT(!checked_out_);
+
+  // If the stream was written to and then immediately returned to us then
+  // we may get a writable notification for it, which we should ignore.
+  if (events == SE_WRITE) {
+    LOG_F(LS_VERBOSE) << "Pooled Socket unexpectedly writable: ignoring";
+    return;
+  }
+
+  // If the peer sent data, we can't process it, so drop the connection.
+  // If the socket has closed, clean it up.
+  // In either case, we'll reconnect it the next time it is used.
+  ASSERT(0 != (events & (SE_READ|SE_CLOSE)));
+  if (0 != (events & SE_CLOSE)) {
+    LOG_F(LS_VERBOSE) << "Connection closed with error: " << err;
+  } else {
+    LOG_F(LS_VERBOSE) << "Pooled Socket unexpectedly readable: closing";
+  }
+  stream_->Close();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// LoggingPoolAdapter - Adapts a StreamPool to supply streams with attached
+// LoggingAdapters.
+///////////////////////////////////////////////////////////////////////////////
+
+LoggingPoolAdapter::LoggingPoolAdapter(
+    StreamPool* pool, LoggingSeverity level, const std::string& label,
+    bool binary_mode)
+  : pool_(pool), level_(level), label_(label), binary_mode_(binary_mode) {
+}
+
+LoggingPoolAdapter::~LoggingPoolAdapter() {
+  for (StreamList::iterator it = recycle_bin_.begin();
+       it != recycle_bin_.end(); ++it) {
+    delete *it;
+  }
+}
+
+StreamInterface* LoggingPoolAdapter::RequestConnectedStream(
+    const SocketAddress& remote, int* err) {
+  if (StreamInterface* stream = pool_->RequestConnectedStream(remote, err)) {
+    ASSERT(SS_CLOSED != stream->GetState());
+    std::stringstream ss;
+    ss << label_ << "(0x" << std::setfill('0') << std::hex << std::setw(8)
+       << stream << ")";
+    LOG_V(level_) << ss.str()
+                  << ((SS_OPEN == stream->GetState()) ? " Connected"
+                                                      : " Connecting")
+                  << " to " << remote;
+    if (recycle_bin_.empty()) {
+      return new LoggingAdapter(stream, level_, ss.str(), binary_mode_);
+    }
+    LoggingAdapter* logging = recycle_bin_.front();
+    recycle_bin_.pop_front();
+    logging->set_label(ss.str());
+    logging->Attach(stream);
+    return logging;
+  }
+  return NULL;
+}
+
+void LoggingPoolAdapter::ReturnConnectedStream(StreamInterface* stream) {
+  LoggingAdapter* logging = static_cast<LoggingAdapter*>(stream);
+  pool_->ReturnConnectedStream(logging->Detach());
+  recycle_bin_.push_back(logging);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/socketpool.h b/talk/base/socketpool.h
new file mode 100644
index 0000000..847d8ff
--- /dev/null
+++ b/talk/base/socketpool.h
@@ -0,0 +1,160 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_SOCKETPOOL_H_
+#define TALK_BASE_SOCKETPOOL_H_
+
+#include <deque>
+#include <list>
+#include "talk/base/logging.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socketaddress.h"
+
+namespace talk_base {
+
+class AsyncSocket;
+class LoggingAdapter;
+class SocketFactory;
+class SocketStream;
+class StreamInterface;
+
+//////////////////////////////////////////////////////////////////////
+// StreamPool
+//////////////////////////////////////////////////////////////////////
+
+class StreamPool {
+public:
+  virtual ~StreamPool() { }
+
+  virtual StreamInterface* RequestConnectedStream(const SocketAddress& remote,
+                                                  int* err) = 0;
+  virtual void ReturnConnectedStream(StreamInterface* stream) = 0;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamCache - Caches a set of open streams, defers creation/destruction to
+//  the supplied StreamPool.
+///////////////////////////////////////////////////////////////////////////////
+
+class StreamCache : public StreamPool, public sigslot::has_slots<> {
+public:
+  StreamCache(StreamPool* pool);
+  virtual ~StreamCache();
+
+  // StreamPool Interface
+  virtual StreamInterface* RequestConnectedStream(const SocketAddress& remote,
+                                                  int* err);
+  virtual void ReturnConnectedStream(StreamInterface* stream);
+
+private:
+  typedef std::pair<SocketAddress, StreamInterface*> ConnectedStream;
+  typedef std::list<ConnectedStream> ConnectedList;
+
+  void OnStreamEvent(StreamInterface* stream, int events, int err);
+
+  // We delegate stream creation and deletion to this pool.
+  StreamPool* pool_;
+  // Streams that are in use (returned from RequestConnectedStream).
+  ConnectedList active_;
+  // Streams which were returned to us, but are still open.
+  ConnectedList cached_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// NewSocketPool
+// Creates a new stream on every request
+///////////////////////////////////////////////////////////////////////////////
+
+class NewSocketPool : public StreamPool {
+public:
+  NewSocketPool(SocketFactory* factory);
+  virtual ~NewSocketPool();
+  
+  // StreamPool Interface
+  virtual StreamInterface* RequestConnectedStream(const SocketAddress& remote,
+                                                  int* err);
+  virtual void ReturnConnectedStream(StreamInterface* stream);
+  
+private:
+  SocketFactory* factory_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// ReuseSocketPool
+// Maintains a single socket at a time, and will reuse it without closing if
+// the destination address is the same.
+///////////////////////////////////////////////////////////////////////////////
+
+class ReuseSocketPool : public StreamPool, public sigslot::has_slots<> {
+public:
+  ReuseSocketPool(SocketFactory* factory);
+  virtual ~ReuseSocketPool();
+
+  // StreamPool Interface
+  virtual StreamInterface* RequestConnectedStream(const SocketAddress& remote,
+                                                  int* err);
+  virtual void ReturnConnectedStream(StreamInterface* stream);
+  
+private:
+  void OnStreamEvent(StreamInterface* stream, int events, int err);
+
+  SocketFactory* factory_;
+  SocketStream* stream_;
+  SocketAddress remote_;
+  bool checked_out_;  // Whether the stream is currently checked out
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// LoggingPoolAdapter - Adapts a StreamPool to supply streams with attached
+// LoggingAdapters.
+///////////////////////////////////////////////////////////////////////////////
+
+class LoggingPoolAdapter : public StreamPool {
+public:
+  LoggingPoolAdapter(StreamPool* pool, LoggingSeverity level,
+                     const std::string& label, bool binary_mode);
+  virtual ~LoggingPoolAdapter();
+
+  // StreamPool Interface
+  virtual StreamInterface* RequestConnectedStream(const SocketAddress& remote,
+                                                  int* err);
+  virtual void ReturnConnectedStream(StreamInterface* stream);
+
+private:
+  StreamPool* pool_;
+  LoggingSeverity level_;
+  std::string label_;
+  bool binary_mode_;
+  typedef std::deque<LoggingAdapter*> StreamList;
+  StreamList recycle_bin_;
+};
+
+//////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SOCKETPOOL_H_
diff --git a/talk/base/socketserver.h b/talk/base/socketserver.h
new file mode 100644
index 0000000..151ce61
--- /dev/null
+++ b/talk/base/socketserver.h
@@ -0,0 +1,61 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_SOCKETSERVER_H_
+#define TALK_BASE_SOCKETSERVER_H_
+
+#include "talk/base/socketfactory.h"
+
+namespace talk_base {
+
+class MessageQueue;
+
+// Provides the ability to wait for activity on a set of sockets.  The Thread
+// class provides a nice wrapper on a socket server.
+//
+// The server is also a socket factory.  The sockets it creates will be
+// notified of asynchronous I/O from this server's Wait method.
+class SocketServer : public SocketFactory {
+ public:
+  // When the socket server is installed into a Thread, this function is
+  // called to allow the socket server to use the thread's message queue for
+  // any messaging that it might need to perform.
+  virtual void SetMessageQueue(MessageQueue* queue) {}
+
+  // Sleeps until:
+  //  1) cms milliseconds have elapsed (unless cms == kForever)
+  //  2) WakeUp() is called
+  // While sleeping, I/O is performed if process_io is true.
+  virtual bool Wait(int cms, bool process_io) = 0;
+
+  // Causes the current wait (if one is in progress) to wake up.
+  virtual void WakeUp() = 0;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SOCKETSERVER_H_
diff --git a/talk/base/socketstream.cc b/talk/base/socketstream.cc
new file mode 100644
index 0000000..3dc5a95
--- /dev/null
+++ b/talk/base/socketstream.cc
@@ -0,0 +1,138 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/socketstream.h"
+
+namespace talk_base {
+
+SocketStream::SocketStream(AsyncSocket* socket) : socket_(NULL) {
+  Attach(socket);
+}
+
+SocketStream::~SocketStream() {
+  delete socket_;
+}
+
+void SocketStream::Attach(AsyncSocket* socket) {
+  if (socket_)
+    delete socket_;
+  socket_ = socket;
+  if (socket_) {
+    socket_->SignalConnectEvent.connect(this, &SocketStream::OnConnectEvent);
+    socket_->SignalReadEvent.connect(this,    &SocketStream::OnReadEvent);
+    socket_->SignalWriteEvent.connect(this,   &SocketStream::OnWriteEvent);
+    socket_->SignalCloseEvent.connect(this,   &SocketStream::OnCloseEvent);
+  }
+}
+
+AsyncSocket* SocketStream::Detach() {
+  AsyncSocket* socket = socket_;
+  if (socket_) {
+    socket_->SignalConnectEvent.disconnect(this);
+    socket_->SignalReadEvent.disconnect(this);
+    socket_->SignalWriteEvent.disconnect(this);
+    socket_->SignalCloseEvent.disconnect(this);
+    socket_ = NULL;
+  }
+  return socket;
+}
+
+StreamState SocketStream::GetState() const {
+  ASSERT(socket_ != NULL);
+  switch (socket_->GetState()) {
+    case Socket::CS_CONNECTED:
+      return SS_OPEN;
+    case Socket::CS_CONNECTING:
+      return SS_OPENING;
+    case Socket::CS_CLOSED:
+    default:
+      return SS_CLOSED;
+  }
+}
+
+StreamResult SocketStream::Read(void* buffer, size_t buffer_len,
+                                size_t* read, int* error) {
+  ASSERT(socket_ != NULL);
+  int result = socket_->Recv(buffer, buffer_len);
+  if (result < 0) {
+    if (socket_->IsBlocking())
+      return SR_BLOCK;
+    if (error)
+      *error = socket_->GetError();
+    return SR_ERROR;
+  }
+  if ((result > 0) || (buffer_len == 0)) {
+    if (read)
+      *read = result;
+    return SR_SUCCESS;
+  }
+  return SR_EOS;
+}
+
+StreamResult SocketStream::Write(const void* data, size_t data_len,
+                                 size_t* written, int* error) {
+  ASSERT(socket_ != NULL);
+  int result = socket_->Send(data, data_len);
+  if (result < 0) {
+    if (socket_->IsBlocking())
+      return SR_BLOCK;
+    if (error)
+      *error = socket_->GetError();
+    return SR_ERROR;
+  }
+  if (written)
+    *written = result;
+  return SR_SUCCESS;
+}
+
+void SocketStream::Close() {
+  ASSERT(socket_ != NULL);
+  socket_->Close();
+}
+
+void SocketStream::OnConnectEvent(AsyncSocket* socket) {
+  ASSERT(socket == socket_);
+  SignalEvent(this, SE_OPEN | SE_READ | SE_WRITE, 0);
+}
+
+void SocketStream::OnReadEvent(AsyncSocket* socket) {
+  ASSERT(socket == socket_);
+  SignalEvent(this, SE_READ, 0);
+}
+
+void SocketStream::OnWriteEvent(AsyncSocket* socket) {
+  ASSERT(socket == socket_);
+  SignalEvent(this, SE_WRITE, 0);
+}
+
+void SocketStream::OnCloseEvent(AsyncSocket* socket, int err) {
+  ASSERT(socket == socket_);
+  SignalEvent(this, SE_CLOSE, err);
+}
+
+
+}  // namespace talk_base
diff --git a/talk/base/socketstream.h b/talk/base/socketstream.h
new file mode 100644
index 0000000..591dc4c
--- /dev/null
+++ b/talk/base/socketstream.h
@@ -0,0 +1,74 @@
+/*
+ * libjingle
+ * Copyright 2005--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_SOCKETSTREAM_H_
+#define TALK_BASE_SOCKETSTREAM_H_
+
+#include "talk/base/asyncsocket.h"
+#include "talk/base/common.h"
+#include "talk/base/stream.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SocketStream : public StreamInterface, public sigslot::has_slots<> {
+ public:
+  explicit SocketStream(AsyncSocket* socket);
+  virtual ~SocketStream();
+
+  void Attach(AsyncSocket* socket);
+  AsyncSocket* Detach();
+
+  AsyncSocket* GetSocket() { return socket_; }
+
+  virtual StreamState GetState() const;
+
+  virtual StreamResult Read(void* buffer, size_t buffer_len,
+                            size_t* read, int* error);
+
+  virtual StreamResult Write(const void* data, size_t data_len,
+                             size_t* written, int* error);
+
+  virtual void Close();
+
+ private:
+  void OnConnectEvent(AsyncSocket* socket);
+  void OnReadEvent(AsyncSocket* socket);
+  void OnWriteEvent(AsyncSocket* socket);
+  void OnCloseEvent(AsyncSocket* socket, int err);
+
+  AsyncSocket* socket_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(SocketStream);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SOCKETSTREAM_H_
diff --git a/talk/base/ssladapter.cc b/talk/base/ssladapter.cc
new file mode 100644
index 0000000..b7d8294
--- /dev/null
+++ b/talk/base/ssladapter.cc
@@ -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.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif  // HAVE_CONFIG_H
+
+#include "talk/base/ssladapter.h"
+
+#include "talk/base/sslconfig.h"
+
+#if SSL_USE_SCHANNEL
+
+#include "schanneladapter.h"
+
+#elif SSL_USE_OPENSSL  // && !SSL_USE_SCHANNEL
+
+#include "openssladapter.h"
+
+#elif SSL_USE_NSS     // && !SSL_USE_CHANNEL && !SSL_USE_OPENSSL
+
+#include "nssstreamadapter.h"
+
+#endif  // SSL_USE_OPENSSL && !SSL_USE_SCHANNEL && !SSL_USE_NSS
+
+///////////////////////////////////////////////////////////////////////////////
+
+namespace talk_base {
+
+SSLAdapter*
+SSLAdapter::Create(AsyncSocket* socket) {
+#if SSL_USE_SCHANNEL
+  return new SChannelAdapter(socket);
+#elif SSL_USE_OPENSSL  // && !SSL_USE_SCHANNEL
+  return new OpenSSLAdapter(socket);
+#else  // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL
+  return NULL;
+#endif  // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if SSL_USE_OPENSSL
+
+bool InitializeSSL(VerificationCallback callback) {
+  return OpenSSLAdapter::InitializeSSL(callback);
+}
+
+bool InitializeSSLThread() {
+  return OpenSSLAdapter::InitializeSSLThread();
+}
+
+bool CleanupSSL() {
+  return OpenSSLAdapter::CleanupSSL();
+}
+
+#elif SSL_USE_NSS  // !SSL_USE_OPENSSL
+
+bool InitializeSSL(VerificationCallback callback) {
+  return NSSContext::InitializeSSL(callback);
+}
+
+bool InitializeSSLThread() {
+  return NSSContext::InitializeSSLThread();
+}
+
+bool CleanupSSL() {
+  return NSSContext::CleanupSSL();
+}
+
+#else  // !SSL_USE_OPENSSL && !SSL_USE_NSS
+
+bool InitializeSSL(VerificationCallback callback) {
+  return true;
+}
+
+bool InitializeSSLThread() {
+  return true;
+}
+
+bool CleanupSSL() {
+  return true;
+}
+
+#endif  // !SSL_USE_OPENSSL && !SSL_USE_NSS
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
diff --git a/talk/base/ssladapter.h b/talk/base/ssladapter.h
new file mode 100644
index 0000000..1583dc2
--- /dev/null
+++ b/talk/base/ssladapter.h
@@ -0,0 +1,76 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_SSLADAPTER_H_
+#define TALK_BASE_SSLADAPTER_H_
+
+#include "talk/base/asyncsocket.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SSLAdapter : public AsyncSocketAdapter {
+ public:
+  explicit SSLAdapter(AsyncSocket* socket)
+    : AsyncSocketAdapter(socket), ignore_bad_cert_(false) { }
+
+  bool ignore_bad_cert() const { return ignore_bad_cert_; }
+  void set_ignore_bad_cert(bool ignore) { ignore_bad_cert_ = ignore; }
+
+  // StartSSL returns 0 if successful.
+  // If StartSSL is called while the socket is closed or connecting, the SSL
+  // negotiation will begin as soon as the socket connects.
+  virtual int StartSSL(const char* hostname, bool restartable) = 0;
+
+  // Create the default SSL adapter for this platform
+  static SSLAdapter* Create(AsyncSocket* socket);
+
+ private:
+  // If true, the server certificate need not match the configured hostname.
+  bool ignore_bad_cert_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+typedef bool (*VerificationCallback)(void* cert);
+
+// Call this on the main thread, before using SSL.
+// Call CleanupSSLThread when finished with SSL.
+bool InitializeSSL(VerificationCallback callback = NULL);
+
+// Call to initialize additional threads.
+bool InitializeSSLThread();
+
+// Call to cleanup additional threads, and also the main thread.
+bool CleanupSSL();
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SSLADAPTER_H_
diff --git a/talk/base/sslconfig.h b/talk/base/sslconfig.h
new file mode 100644
index 0000000..cc3a733
--- /dev/null
+++ b/talk/base/sslconfig.h
@@ -0,0 +1,50 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_SSLCONFIG_H_
+#define TALK_BASE_SSLCONFIG_H_
+
+// If no preference has been indicated, default to SChannel on Windows and
+// OpenSSL everywhere else, if it is available.
+#if !defined(SSL_USE_SCHANNEL) && !defined(SSL_USE_OPENSSL) && \
+    !defined(SSL_USE_NSS)
+#if defined(WIN32)
+
+#define SSL_USE_SCHANNEL 1
+
+#else  // defined(WIN32)
+
+#if defined(HAVE_OPENSSL_SSL_H)
+#define SSL_USE_OPENSSL 1
+#elif defined(HAVE_NSS_SSL_H)
+#define SSL_USE_NSS 1
+#endif
+
+#endif  // !defined(WIN32)
+#endif
+
+#endif  // TALK_BASE_SSLCONFIG_H_
diff --git a/talk/base/sslfingerprint.h b/talk/base/sslfingerprint.h
new file mode 100644
index 0000000..4d41156
--- /dev/null
+++ b/talk/base/sslfingerprint.h
@@ -0,0 +1,109 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ * Copyright 2012, 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.
+ */
+
+#ifndef TALK_BASE_SSLFINGERPRINT_H_
+#define TALK_BASE_SSLFINGERPRINT_H_
+
+#include <ctype.h>
+#include <string>
+
+#include "talk/base/buffer.h"
+#include "talk/base/helpers.h"
+#include "talk/base/messagedigest.h"
+#include "talk/base/sslidentity.h"
+#include "talk/base/stringencode.h"
+
+namespace talk_base {
+
+struct SSLFingerprint {
+  static SSLFingerprint* Create(const std::string& algorithm,
+                                const talk_base::SSLIdentity* identity) {
+    if (!identity) {
+      return NULL;
+    }
+
+    uint8 digest_val[64];
+    size_t digest_len;
+    bool ret = identity->certificate().ComputeDigest(
+        algorithm, digest_val, sizeof(digest_val), &digest_len);
+    if (!ret) {
+      return NULL;
+    }
+
+    return new SSLFingerprint(algorithm, digest_val, digest_len);
+  }
+
+  static SSLFingerprint* CreateFromRfc4572(const std::string& algorithm,
+                                           const std::string& fingerprint) {
+    if (algorithm.empty())
+      return NULL;
+
+    if (fingerprint.empty())
+      return NULL;
+
+    size_t value_len;
+    char value[talk_base::MessageDigest::kMaxSize];
+    value_len = talk_base::hex_decode_with_delimiter(value, sizeof(value),
+                                                     fingerprint.c_str(),
+                                                     fingerprint.length(),
+                                                     ':');
+    if (!value_len)
+      return NULL;
+
+    return new SSLFingerprint(algorithm,
+                              reinterpret_cast<uint8*>(value),
+                              value_len);
+  }
+
+  SSLFingerprint(const std::string& algorithm, const uint8* digest_in,
+                 size_t digest_len) : algorithm(algorithm) {
+    digest.SetData(digest_in, digest_len);
+  }
+  SSLFingerprint(const SSLFingerprint& from)
+      : algorithm(from.algorithm), digest(from.digest) {}
+  bool operator==(const SSLFingerprint& other) const {
+    return algorithm == other.algorithm &&
+           digest == other.digest;
+  }
+
+  std::string GetRfc4572Fingerprint() const {
+    std::string fingerprint =
+        talk_base::hex_encode_with_delimiter(
+            digest.data(), digest.length(), ':');
+    std::transform(fingerprint.begin(), fingerprint.end(),
+                   fingerprint.begin(), ::toupper);
+    return fingerprint;
+  }
+
+  std::string algorithm;
+  talk_base::Buffer digest;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SSLFINGERPRINT_H_
diff --git a/talk/base/sslidentity.cc b/talk/base/sslidentity.cc
new file mode 100644
index 0000000..4978052
--- /dev/null
+++ b/talk/base/sslidentity.cc
@@ -0,0 +1,104 @@
+/*
+ * 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.
+ */
+
+// Handling of certificates and keypairs for SSLStreamAdapter's peer mode.
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif  // HAVE_CONFIG_H
+
+#include "talk/base/sslidentity.h"
+
+#include <string>
+
+#include "talk/base/sslconfig.h"
+
+#if SSL_USE_SCHANNEL
+
+#elif SSL_USE_OPENSSL  // !SSL_USE_SCHANNEL
+
+#include "talk/base/opensslidentity.h"
+
+#elif SSL_USE_NSS  // !SSL_USE_SCHANNEL && !SSL_USE_OPENSSL
+
+#include "talk/base/nssidentity.h"
+
+#endif  // SSL_USE_SCHANNEL
+
+namespace talk_base {
+
+#if SSL_USE_SCHANNEL
+
+SSLCertificate* SSLCertificate::FromPEMString(const std::string& pem_string) {
+  return NULL;
+}
+
+SSLIdentity* SSLIdentity::Generate(const std::string& common_name) {
+  return NULL;
+}
+
+SSLIdentity* SSLIdentity::FromPEMStrings(const std::string& private_key,
+                                         const std::string& certificate) {
+  return NULL;
+}
+
+#elif SSL_USE_OPENSSL  // !SSL_USE_SCHANNEL
+
+SSLCertificate* SSLCertificate::FromPEMString(const std::string& pem_string) {
+  return OpenSSLCertificate::FromPEMString(pem_string);
+}
+
+SSLIdentity* SSLIdentity::Generate(const std::string& common_name) {
+  return OpenSSLIdentity::Generate(common_name);
+}
+
+SSLIdentity* SSLIdentity::FromPEMStrings(const std::string& private_key,
+                                         const std::string& certificate) {
+  return OpenSSLIdentity::FromPEMStrings(private_key, certificate);
+}
+
+#elif SSL_USE_NSS  // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL
+
+SSLCertificate* SSLCertificate::FromPEMString(const std::string& pem_string) {
+  return NSSCertificate::FromPEMString(pem_string);
+}
+
+SSLIdentity* SSLIdentity::Generate(const std::string& common_name) {
+  return NSSIdentity::Generate(common_name);
+}
+
+SSLIdentity* SSLIdentity::FromPEMStrings(const std::string& private_key,
+                                         const std::string& certificate) {
+  return NSSIdentity::FromPEMStrings(private_key, certificate);
+}
+
+#else  // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL && !SSL_USE_NSS
+
+#error "No SSL implementation"
+
+#endif  // SSL_USE_SCHANNEL
+
+}  // namespace talk_base
diff --git a/talk/base/sslidentity.h b/talk/base/sslidentity.h
new file mode 100644
index 0000000..b63c066
--- /dev/null
+++ b/talk/base/sslidentity.h
@@ -0,0 +1,100 @@
+/*
+ * 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.
+ */
+
+// Handling of certificates and keypairs for SSLStreamAdapter's peer mode.
+
+#ifndef TALK_BASE_SSLIDENTITY_H_
+#define TALK_BASE_SSLIDENTITY_H_
+
+#include <string>
+#include "talk/base/messagedigest.h"
+
+namespace talk_base {
+
+// Abstract interface overridden by SSL library specific
+// implementations.
+
+// A somewhat opaque type used to encapsulate a certificate.
+// Wraps the SSL library's notion of a certificate, with reference counting.
+// The SSLCertificate object is pretty much immutable once created.
+// (The OpenSSL implementation only does reference counting and
+// possibly caching of intermediate results.)
+class SSLCertificate {
+ public:
+  // Parses and build a certificate from a PEM encoded string.
+  // Returns NULL on failure.
+  // The length of the string representation of the certificate is
+  // stored in *pem_length if it is non-NULL, and only if
+  // parsing was successful.
+  // Caller is responsible for freeing the returned object.
+  static SSLCertificate* FromPEMString(const std::string& pem_string);
+  virtual ~SSLCertificate() {}
+
+  // Returns a new SSLCertificate object instance wrapping the same
+  // underlying certificate.
+  // Caller is responsible for freeing the returned object.
+  virtual SSLCertificate* GetReference() const = 0;
+
+  // 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
+// with the same public key).
+// This too is pretty much immutable once created.
+class SSLIdentity {
+ public:
+  // Generates an identity (keypair and self-signed certificate). If
+  // common_name is non-empty, it will be used for the certificate's
+  // subject and issuer name, otherwise a random string will be used.
+  // Returns NULL on failure.
+  // Caller is responsible for freeing the returned object.
+  static SSLIdentity* Generate(const std::string& common_name);
+
+  // Construct an identity from a private key and a certificate.
+  static SSLIdentity* FromPEMStrings(const std::string& private_key,
+                                     const std::string& certificate);
+
+  virtual ~SSLIdentity() {}
+
+  // Returns a new SSLIdentity object instance wrapping the same
+  // identity information.
+  // Caller is responsible for freeing the returned object.
+  virtual SSLIdentity* GetReference() const = 0;
+
+  // Returns a temporary reference to the certificate.
+  virtual const SSLCertificate& certificate() const = 0;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SSLIDENTITY_H__
diff --git a/talk/base/sslidentity_unittest.cc b/talk/base/sslidentity_unittest.cc
new file mode 100644
index 0000000..3605c00
--- /dev/null
+++ b/talk/base/sslidentity_unittest.cc
@@ -0,0 +1,190 @@
+/*
+ * 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/ssladapter.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() {
+  }
+
+  static void SetUpTestCase() {
+    talk_base::InitializeSSL();
+  }
+
+  virtual void SetUp() {
+    identity1_.reset(talk_base::SSLIdentity::Generate("test1"));
+    identity2_.reset(talk_base::SSLIdentity::Generate("test2"));
+
+    ASSERT_TRUE(identity1_);
+    ASSERT_TRUE(identity2_);
+
+    test_cert_.reset(
+        talk_base::SSLCertificate::FromPEMString(kTestCertificate));
+    ASSERT_TRUE(test_cert_);
+  }
+
+  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) {
+      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);
+}
+
+// HASH_AlgSHA224 is not supported in the chromium linux build.
+#if SSL_USE_NSS
+TEST_F(SSLIdentityTest, DISABLED_DigestSHA224) {
+#else
+TEST_F(SSLIdentityTest, DigestSHA224) {
+#endif
+  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);
+}
+
+TEST_F(SSLIdentityTest, FromPEMStrings) {
+  static const char kRSA_PRIVATE_KEY_PEM[] =
+      "-----BEGIN RSA PRIVATE KEY-----\n"
+      "MIICXQIBAAKBgQDCueE4a9hDMZ3sbVZdlXOz9ZA+cvzie3zJ9gXnT/BCt9P4b9HE\n"
+      "vD/tr73YBqD3Wr5ZWScmyGYF9EMn0r3rzBxv6oooLU5TdUvOm4rzUjkCLQaQML8o\n"
+      "NxXq+qW/j3zUKGikLhaaAl/amaX2zSWUsRQ1CpngQ3+tmDNH4/25TncNmQIDAQAB\n"
+      "AoGAUcuU0Id0k10fMjYHZk4mCPzot2LD2Tr4Aznl5vFMQipHzv7hhZtx2xzMSRcX\n"
+      "vG+Qr6VkbcUWHgApyWubvZXCh3+N7Vo2aYdMAQ8XqmFpBdIrL5CVdVfqFfEMlgEy\n"
+      "LSZNG5klnrIfl3c7zQVovLr4eMqyl2oGfAqPQz75+fecv1UCQQD6wNHch9NbAG1q\n"
+      "yuFEhMARB6gDXb+5SdzFjjtTWW5uJfm4DcZLoYyaIZm0uxOwsUKd0Rsma+oGitS1\n"
+      "CXmuqfpPAkEAxszyN3vIdpD44SREEtyKZBMNOk5pEIIGdbeMJC5/XHvpxww9xkoC\n"
+      "+39NbvUZYd54uT+rafbx4QZKc0h9xA/HlwJBAL37lYVWy4XpPv1olWCKi9LbUCqs\n"
+      "vvQtyD1N1BkEayy9TQRsO09WKOcmigRqsTJwOx7DLaTgokEuspYvhagWVPUCQE/y\n"
+      "0+YkTbYBD1Xbs9SyBKXCU6uDJRWSdO6aZi2W1XloC9gUwDMiSJjD1Wwt/YsyYPJ+\n"
+      "/Hyc5yFL2l0KZimW/vkCQQCjuZ/lPcH46EuzhdbRfumDOG5N3ld7UhGI1TIRy17W\n"
+      "dGF90cG33/L6BfS8Ll+fkkW/2AMRk8FDvF4CZi2nfW4L\n"
+      "-----END RSA PRIVATE KEY-----\n";
+
+  static const char kCERT_PEM[] =
+      "-----BEGIN CERTIFICATE-----\n"
+      "MIIBmTCCAQICCQCPNJORW/M13DANBgkqhkiG9w0BAQUFADARMQ8wDQYDVQQDDAZ3\n"
+      "ZWJydGMwHhcNMTMwNjE0MjIzMDAxWhcNMTQwNjE0MjIzMDAxWjARMQ8wDQYDVQQD\n"
+      "DAZ3ZWJydGMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMK54Thr2EMxnext\n"
+      "Vl2Vc7P1kD5y/OJ7fMn2BedP8EK30/hv0cS8P+2vvdgGoPdavllZJybIZgX0QyfS\n"
+      "vevMHG/qiigtTlN1S86bivNSOQItBpAwvyg3Fer6pb+PfNQoaKQuFpoCX9qZpfbN\n"
+      "JZSxFDUKmeBDf62YM0fj/blOdw2ZAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAECMt\n"
+      "UZb35H8TnjGx4XPzco/kbnurMLFFWcuve/DwTsuf10Ia9N4md8LY0UtgIgtyNqWc\n"
+      "ZwyRMwxONF6ty3wcaIiPbGqiAa55T3YRuPibkRmck9CjrmM9JAtyvqHnpHd2TsBD\n"
+      "qCV42aXS3onOXDQ1ibuWq0fr0//aj0wo4KV474c=\n"
+      "-----END CERTIFICATE-----\n";
+
+  talk_base::scoped_ptr<talk_base::SSLIdentity> identity(
+      talk_base::SSLIdentity::FromPEMStrings(kRSA_PRIVATE_KEY_PEM, kCERT_PEM));
+  EXPECT_TRUE(identity);
+  EXPECT_EQ(kCERT_PEM, identity->certificate().ToPEMString());
+}
diff --git a/talk/base/sslroots.h b/talk/base/sslroots.h
new file mode 100644
index 0000000..0f983cd
--- /dev/null
+++ b/talk/base/sslroots.h
@@ -0,0 +1,4930 @@
+// This file is the root certificates in C form that are needed to connect to
+// Google.
+
+// It was generated with the following command line:
+// > python //depot/googleclient/talk/tools/generate_sslroots.py
+//    //depot/google3/security/cacerts/for_connecting_to_google/roots.pem
+
+/* subject:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root */
+/* issuer :/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root */
+
+
+const unsigned char AddTrust_External_Root_certificate[1082]={
+0x30,0x82,0x04,0x36,0x30,0x82,0x03,0x1E,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,
+0x6F,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53,0x45,0x31,0x14,
+0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54,0x72,0x75,0x73,
+0x74,0x20,0x41,0x42,0x31,0x26,0x30,0x24,0x06,0x03,0x55,0x04,0x0B,0x13,0x1D,0x41,
+0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x45,0x78,0x74,0x65,0x72,0x6E,0x61,0x6C,
+0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x22,0x30,0x20,
+0x06,0x03,0x55,0x04,0x03,0x13,0x19,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,
+0x45,0x78,0x74,0x65,0x72,0x6E,0x61,0x6C,0x20,0x43,0x41,0x20,0x52,0x6F,0x6F,0x74,
+0x30,0x1E,0x17,0x0D,0x30,0x30,0x30,0x35,0x33,0x30,0x31,0x30,0x34,0x38,0x33,0x38,
+0x5A,0x17,0x0D,0x32,0x30,0x30,0x35,0x33,0x30,0x31,0x30,0x34,0x38,0x33,0x38,0x5A,
+0x30,0x6F,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53,0x45,0x31,
+0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54,0x72,0x75,
+0x73,0x74,0x20,0x41,0x42,0x31,0x26,0x30,0x24,0x06,0x03,0x55,0x04,0x0B,0x13,0x1D,
+0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x45,0x78,0x74,0x65,0x72,0x6E,0x61,
+0x6C,0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x22,0x30,
+0x20,0x06,0x03,0x55,0x04,0x03,0x13,0x19,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,
+0x20,0x45,0x78,0x74,0x65,0x72,0x6E,0x61,0x6C,0x20,0x43,0x41,0x20,0x52,0x6F,0x6F,
+0x74,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,
+0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,
+0x01,0x00,0xB7,0xF7,0x1A,0x33,0xE6,0xF2,0x00,0x04,0x2D,0x39,0xE0,0x4E,0x5B,0xED,
+0x1F,0xBC,0x6C,0x0F,0xCD,0xB5,0xFA,0x23,0xB6,0xCE,0xDE,0x9B,0x11,0x33,0x97,0xA4,
+0x29,0x4C,0x7D,0x93,0x9F,0xBD,0x4A,0xBC,0x93,0xED,0x03,0x1A,0xE3,0x8F,0xCF,0xE5,
+0x6D,0x50,0x5A,0xD6,0x97,0x29,0x94,0x5A,0x80,0xB0,0x49,0x7A,0xDB,0x2E,0x95,0xFD,
+0xB8,0xCA,0xBF,0x37,0x38,0x2D,0x1E,0x3E,0x91,0x41,0xAD,0x70,0x56,0xC7,0xF0,0x4F,
+0x3F,0xE8,0x32,0x9E,0x74,0xCA,0xC8,0x90,0x54,0xE9,0xC6,0x5F,0x0F,0x78,0x9D,0x9A,
+0x40,0x3C,0x0E,0xAC,0x61,0xAA,0x5E,0x14,0x8F,0x9E,0x87,0xA1,0x6A,0x50,0xDC,0xD7,
+0x9A,0x4E,0xAF,0x05,0xB3,0xA6,0x71,0x94,0x9C,0x71,0xB3,0x50,0x60,0x0A,0xC7,0x13,
+0x9D,0x38,0x07,0x86,0x02,0xA8,0xE9,0xA8,0x69,0x26,0x18,0x90,0xAB,0x4C,0xB0,0x4F,
+0x23,0xAB,0x3A,0x4F,0x84,0xD8,0xDF,0xCE,0x9F,0xE1,0x69,0x6F,0xBB,0xD7,0x42,0xD7,
+0x6B,0x44,0xE4,0xC7,0xAD,0xEE,0x6D,0x41,0x5F,0x72,0x5A,0x71,0x08,0x37,0xB3,0x79,
+0x65,0xA4,0x59,0xA0,0x94,0x37,0xF7,0x00,0x2F,0x0D,0xC2,0x92,0x72,0xDA,0xD0,0x38,
+0x72,0xDB,0x14,0xA8,0x45,0xC4,0x5D,0x2A,0x7D,0xB7,0xB4,0xD6,0xC4,0xEE,0xAC,0xCD,
+0x13,0x44,0xB7,0xC9,0x2B,0xDD,0x43,0x00,0x25,0xFA,0x61,0xB9,0x69,0x6A,0x58,0x23,
+0x11,0xB7,0xA7,0x33,0x8F,0x56,0x75,0x59,0xF5,0xCD,0x29,0xD7,0x46,0xB7,0x0A,0x2B,
+0x65,0xB6,0xD3,0x42,0x6F,0x15,0xB2,0xB8,0x7B,0xFB,0xEF,0xE9,0x5D,0x53,0xD5,0x34,
+0x5A,0x27,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0xDC,0x30,0x81,0xD9,0x30,0x1D,0x06,
+0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xAD,0xBD,0x98,0x7A,0x34,0xB4,0x26,0xF7,
+0xFA,0xC4,0x26,0x54,0xEF,0x03,0xBD,0xE0,0x24,0xCB,0x54,0x1A,0x30,0x0B,0x06,0x03,
+0x55,0x1D,0x0F,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,
+0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x81,0x99,0x06,0x03,0x55,
+0x1D,0x23,0x04,0x81,0x91,0x30,0x81,0x8E,0x80,0x14,0xAD,0xBD,0x98,0x7A,0x34,0xB4,
+0x26,0xF7,0xFA,0xC4,0x26,0x54,0xEF,0x03,0xBD,0xE0,0x24,0xCB,0x54,0x1A,0xA1,0x73,
+0xA4,0x71,0x30,0x6F,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53,
+0x45,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54,
+0x72,0x75,0x73,0x74,0x20,0x41,0x42,0x31,0x26,0x30,0x24,0x06,0x03,0x55,0x04,0x0B,
+0x13,0x1D,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x45,0x78,0x74,0x65,0x72,
+0x6E,0x61,0x6C,0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,
+0x22,0x30,0x20,0x06,0x03,0x55,0x04,0x03,0x13,0x19,0x41,0x64,0x64,0x54,0x72,0x75,
+0x73,0x74,0x20,0x45,0x78,0x74,0x65,0x72,0x6E,0x61,0x6C,0x20,0x43,0x41,0x20,0x52,
+0x6F,0x6F,0x74,0x82,0x01,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,
+0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0xB0,0x9B,0xE0,0x85,0x25,0xC2,
+0xD6,0x23,0xE2,0x0F,0x96,0x06,0x92,0x9D,0x41,0x98,0x9C,0xD9,0x84,0x79,0x81,0xD9,
+0x1E,0x5B,0x14,0x07,0x23,0x36,0x65,0x8F,0xB0,0xD8,0x77,0xBB,0xAC,0x41,0x6C,0x47,
+0x60,0x83,0x51,0xB0,0xF9,0x32,0x3D,0xE7,0xFC,0xF6,0x26,0x13,0xC7,0x80,0x16,0xA5,
+0xBF,0x5A,0xFC,0x87,0xCF,0x78,0x79,0x89,0x21,0x9A,0xE2,0x4C,0x07,0x0A,0x86,0x35,
+0xBC,0xF2,0xDE,0x51,0xC4,0xD2,0x96,0xB7,0xDC,0x7E,0x4E,0xEE,0x70,0xFD,0x1C,0x39,
+0xEB,0x0C,0x02,0x51,0x14,0x2D,0x8E,0xBD,0x16,0xE0,0xC1,0xDF,0x46,0x75,0xE7,0x24,
+0xAD,0xEC,0xF4,0x42,0xB4,0x85,0x93,0x70,0x10,0x67,0xBA,0x9D,0x06,0x35,0x4A,0x18,
+0xD3,0x2B,0x7A,0xCC,0x51,0x42,0xA1,0x7A,0x63,0xD1,0xE6,0xBB,0xA1,0xC5,0x2B,0xC2,
+0x36,0xBE,0x13,0x0D,0xE6,0xBD,0x63,0x7E,0x79,0x7B,0xA7,0x09,0x0D,0x40,0xAB,0x6A,
+0xDD,0x8F,0x8A,0xC3,0xF6,0xF6,0x8C,0x1A,0x42,0x05,0x51,0xD4,0x45,0xF5,0x9F,0xA7,
+0x62,0x21,0x68,0x15,0x20,0x43,0x3C,0x99,0xE7,0x7C,0xBD,0x24,0xD8,0xA9,0x91,0x17,
+0x73,0x88,0x3F,0x56,0x1B,0x31,0x38,0x18,0xB4,0x71,0x0F,0x9A,0xCD,0xC8,0x0E,0x9E,
+0x8E,0x2E,0x1B,0xE1,0x8C,0x98,0x83,0xCB,0x1F,0x31,0xF1,0x44,0x4C,0xC6,0x04,0x73,
+0x49,0x76,0x60,0x0F,0xC7,0xF8,0xBD,0x17,0x80,0x6B,0x2E,0xE9,0xCC,0x4C,0x0E,0x5A,
+0x9A,0x79,0x0F,0x20,0x0A,0x2E,0xD5,0x9E,0x63,0x26,0x1E,0x55,0x92,0x94,0xD8,0x82,
+0x17,0x5A,0x7B,0xD0,0xBC,0xC7,0x8F,0x4E,0x86,0x04,
+};
+
+
+/* subject:/C=SE/O=AddTrust AB/OU=AddTrust TTP Network/CN=AddTrust Class 1 CA Root */
+/* issuer :/C=SE/O=AddTrust AB/OU=AddTrust TTP Network/CN=AddTrust Class 1 CA Root */
+
+
+const unsigned char AddTrust_Low_Value_Services_Root_certificate[1052]={
+0x30,0x82,0x04,0x18,0x30,0x82,0x03,0x00,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,
+0x65,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53,0x45,0x31,0x14,
+0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54,0x72,0x75,0x73,
+0x74,0x20,0x41,0x42,0x31,0x1D,0x30,0x1B,0x06,0x03,0x55,0x04,0x0B,0x13,0x14,0x41,
+0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77,
+0x6F,0x72,0x6B,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x41,0x64,
+0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x31,0x20,0x43,
+0x41,0x20,0x52,0x6F,0x6F,0x74,0x30,0x1E,0x17,0x0D,0x30,0x30,0x30,0x35,0x33,0x30,
+0x31,0x30,0x33,0x38,0x33,0x31,0x5A,0x17,0x0D,0x32,0x30,0x30,0x35,0x33,0x30,0x31,
+0x30,0x33,0x38,0x33,0x31,0x5A,0x30,0x65,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,
+0x06,0x13,0x02,0x53,0x45,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,
+0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x41,0x42,0x31,0x1D,0x30,0x1B,0x06,
+0x03,0x55,0x04,0x0B,0x13,0x14,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x54,
+0x54,0x50,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x21,0x30,0x1F,0x06,0x03,
+0x55,0x04,0x03,0x13,0x18,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x43,0x6C,
+0x61,0x73,0x73,0x20,0x31,0x20,0x43,0x41,0x20,0x52,0x6F,0x6F,0x74,0x30,0x82,0x01,
+0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,
+0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0x96,0x96,
+0xD4,0x21,0x49,0x60,0xE2,0x6B,0xE8,0x41,0x07,0x0C,0xDE,0xC4,0xE0,0xDC,0x13,0x23,
+0xCD,0xC1,0x35,0xC7,0xFB,0xD6,0x4E,0x11,0x0A,0x67,0x5E,0xF5,0x06,0x5B,0x6B,0xA5,
+0x08,0x3B,0x5B,0x29,0x16,0x3A,0xE7,0x87,0xB2,0x34,0x06,0xC5,0xBC,0x05,0xA5,0x03,
+0x7C,0x82,0xCB,0x29,0x10,0xAE,0xE1,0x88,0x81,0xBD,0xD6,0x9E,0xD3,0xFE,0x2D,0x56,
+0xC1,0x15,0xCE,0xE3,0x26,0x9D,0x15,0x2E,0x10,0xFB,0x06,0x8F,0x30,0x04,0xDE,0xA7,
+0xB4,0x63,0xB4,0xFF,0xB1,0x9C,0xAE,0x3C,0xAF,0x77,0xB6,0x56,0xC5,0xB5,0xAB,0xA2,
+0xE9,0x69,0x3A,0x3D,0x0E,0x33,0x79,0x32,0x3F,0x70,0x82,0x92,0x99,0x61,0x6D,0x8D,
+0x30,0x08,0x8F,0x71,0x3F,0xA6,0x48,0x57,0x19,0xF8,0x25,0xDC,0x4B,0x66,0x5C,0xA5,
+0x74,0x8F,0x98,0xAE,0xC8,0xF9,0xC0,0x06,0x22,0xE7,0xAC,0x73,0xDF,0xA5,0x2E,0xFB,
+0x52,0xDC,0xB1,0x15,0x65,0x20,0xFA,0x35,0x66,0x69,0xDE,0xDF,0x2C,0xF1,0x6E,0xBC,
+0x30,0xDB,0x2C,0x24,0x12,0xDB,0xEB,0x35,0x35,0x68,0x90,0xCB,0x00,0xB0,0x97,0x21,
+0x3D,0x74,0x21,0x23,0x65,0x34,0x2B,0xBB,0x78,0x59,0xA3,0xD6,0xE1,0x76,0x39,0x9A,
+0xA4,0x49,0x8E,0x8C,0x74,0xAF,0x6E,0xA4,0x9A,0xA3,0xD9,0x9B,0xD2,0x38,0x5C,0x9B,
+0xA2,0x18,0xCC,0x75,0x23,0x84,0xBE,0xEB,0xE2,0x4D,0x33,0x71,0x8E,0x1A,0xF0,0xC2,
+0xF8,0xC7,0x1D,0xA2,0xAD,0x03,0x97,0x2C,0xF8,0xCF,0x25,0xC6,0xF6,0xB8,0x24,0x31,
+0xB1,0x63,0x5D,0x92,0x7F,0x63,0xF0,0x25,0xC9,0x53,0x2E,0x1F,0xBF,0x4D,0x02,0x03,
+0x01,0x00,0x01,0xA3,0x81,0xD2,0x30,0x81,0xCF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,
+0x04,0x16,0x04,0x14,0x95,0xB1,0xB4,0xF0,0x94,0xB6,0xBD,0xC7,0xDA,0xD1,0x11,0x09,
+0x21,0xBE,0xC1,0xAF,0x49,0xFD,0x10,0x7B,0x30,0x0B,0x06,0x03,0x55,0x1D,0x0F,0x04,
+0x04,0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,
+0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x81,0x8F,0x06,0x03,0x55,0x1D,0x23,0x04,0x81,
+0x87,0x30,0x81,0x84,0x80,0x14,0x95,0xB1,0xB4,0xF0,0x94,0xB6,0xBD,0xC7,0xDA,0xD1,
+0x11,0x09,0x21,0xBE,0xC1,0xAF,0x49,0xFD,0x10,0x7B,0xA1,0x69,0xA4,0x67,0x30,0x65,
+0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53,0x45,0x31,0x14,0x30,
+0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,
+0x20,0x41,0x42,0x31,0x1D,0x30,0x1B,0x06,0x03,0x55,0x04,0x0B,0x13,0x14,0x41,0x64,
+0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77,0x6F,
+0x72,0x6B,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x41,0x64,0x64,
+0x54,0x72,0x75,0x73,0x74,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x31,0x20,0x43,0x41,
+0x20,0x52,0x6F,0x6F,0x74,0x82,0x01,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,
+0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x2C,0x6D,0x64,0x1B,
+0x1F,0xCD,0x0D,0xDD,0xB9,0x01,0xFA,0x96,0x63,0x34,0x32,0x48,0x47,0x99,0xAE,0x97,
+0xED,0xFD,0x72,0x16,0xA6,0x73,0x47,0x5A,0xF4,0xEB,0xDD,0xE9,0xF5,0xD6,0xFB,0x45,
+0xCC,0x29,0x89,0x44,0x5D,0xBF,0x46,0x39,0x3D,0xE8,0xEE,0xBC,0x4D,0x54,0x86,0x1E,
+0x1D,0x6C,0xE3,0x17,0x27,0x43,0xE1,0x89,0x56,0x2B,0xA9,0x6F,0x72,0x4E,0x49,0x33,
+0xE3,0x72,0x7C,0x2A,0x23,0x9A,0xBC,0x3E,0xFF,0x28,0x2A,0xED,0xA3,0xFF,0x1C,0x23,
+0xBA,0x43,0x57,0x09,0x67,0x4D,0x4B,0x62,0x06,0x2D,0xF8,0xFF,0x6C,0x9D,0x60,0x1E,
+0xD8,0x1C,0x4B,0x7D,0xB5,0x31,0x2F,0xD9,0xD0,0x7C,0x5D,0xF8,0xDE,0x6B,0x83,0x18,
+0x78,0x37,0x57,0x2F,0xE8,0x33,0x07,0x67,0xDF,0x1E,0xC7,0x6B,0x2A,0x95,0x76,0xAE,
+0x8F,0x57,0xA3,0xF0,0xF4,0x52,0xB4,0xA9,0x53,0x08,0xCF,0xE0,0x4F,0xD3,0x7A,0x53,
+0x8B,0xFD,0xBB,0x1C,0x56,0x36,0xF2,0xFE,0xB2,0xB6,0xE5,0x76,0xBB,0xD5,0x22,0x65,
+0xA7,0x3F,0xFE,0xD1,0x66,0xAD,0x0B,0xBC,0x6B,0x99,0x86,0xEF,0x3F,0x7D,0xF3,0x18,
+0x32,0xCA,0x7B,0xC6,0xE3,0xAB,0x64,0x46,0x95,0xF8,0x26,0x69,0xD9,0x55,0x83,0x7B,
+0x2C,0x96,0x07,0xFF,0x59,0x2C,0x44,0xA3,0xC6,0xE5,0xE9,0xA9,0xDC,0xA1,0x63,0x80,
+0x5A,0x21,0x5E,0x21,0xCF,0x53,0x54,0xF0,0xBA,0x6F,0x89,0xDB,0xA8,0xAA,0x95,0xCF,
+0x8B,0xE3,0x71,0xCC,0x1E,0x1B,0x20,0x44,0x08,0xC0,0x7A,0xB6,0x40,0xFD,0xC4,0xE4,
+0x35,0xE1,0x1D,0x16,0x1C,0xD0,0xBC,0x2B,0x8E,0xD6,0x71,0xD9,
+};
+
+
+/* subject:/C=SE/O=AddTrust AB/OU=AddTrust TTP Network/CN=AddTrust Public CA Root */
+/* issuer :/C=SE/O=AddTrust AB/OU=AddTrust TTP Network/CN=AddTrust Public CA Root */
+
+
+const unsigned char AddTrust_Public_Services_Root_certificate[1049]={
+0x30,0x82,0x04,0x15,0x30,0x82,0x02,0xFD,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,
+0x64,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53,0x45,0x31,0x14,
+0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54,0x72,0x75,0x73,
+0x74,0x20,0x41,0x42,0x31,0x1D,0x30,0x1B,0x06,0x03,0x55,0x04,0x0B,0x13,0x14,0x41,
+0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77,
+0x6F,0x72,0x6B,0x31,0x20,0x30,0x1E,0x06,0x03,0x55,0x04,0x03,0x13,0x17,0x41,0x64,
+0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x50,0x75,0x62,0x6C,0x69,0x63,0x20,0x43,0x41,
+0x20,0x52,0x6F,0x6F,0x74,0x30,0x1E,0x17,0x0D,0x30,0x30,0x30,0x35,0x33,0x30,0x31,
+0x30,0x34,0x31,0x35,0x30,0x5A,0x17,0x0D,0x32,0x30,0x30,0x35,0x33,0x30,0x31,0x30,
+0x34,0x31,0x35,0x30,0x5A,0x30,0x64,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,
+0x13,0x02,0x53,0x45,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,
+0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x41,0x42,0x31,0x1D,0x30,0x1B,0x06,0x03,
+0x55,0x04,0x0B,0x13,0x14,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x54,0x54,
+0x50,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x20,0x30,0x1E,0x06,0x03,0x55,
+0x04,0x03,0x13,0x17,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x50,0x75,0x62,
+0x6C,0x69,0x63,0x20,0x43,0x41,0x20,0x52,0x6F,0x6F,0x74,0x30,0x82,0x01,0x22,0x30,
+0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,
+0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xE9,0x1A,0x30,0x8F,
+0x83,0x88,0x14,0xC1,0x20,0xD8,0x3C,0x9B,0x8F,0x1B,0x7E,0x03,0x74,0xBB,0xDA,0x69,
+0xD3,0x46,0xA5,0xF8,0x8E,0xC2,0x0C,0x11,0x90,0x51,0xA5,0x2F,0x66,0x54,0x40,0x55,
+0xEA,0xDB,0x1F,0x4A,0x56,0xEE,0x9F,0x23,0x6E,0xF4,0x39,0xCB,0xA1,0xB9,0x6F,0xF2,
+0x7E,0xF9,0x5D,0x87,0x26,0x61,0x9E,0x1C,0xF8,0xE2,0xEC,0xA6,0x81,0xF8,0x21,0xC5,
+0x24,0xCC,0x11,0x0C,0x3F,0xDB,0x26,0x72,0x7A,0xC7,0x01,0x97,0x07,0x17,0xF9,0xD7,
+0x18,0x2C,0x30,0x7D,0x0E,0x7A,0x1E,0x62,0x1E,0xC6,0x4B,0xC0,0xFD,0x7D,0x62,0x77,
+0xD3,0x44,0x1E,0x27,0xF6,0x3F,0x4B,0x44,0xB3,0xB7,0x38,0xD9,0x39,0x1F,0x60,0xD5,
+0x51,0x92,0x73,0x03,0xB4,0x00,0x69,0xE3,0xF3,0x14,0x4E,0xEE,0xD1,0xDC,0x09,0xCF,
+0x77,0x34,0x46,0x50,0xB0,0xF8,0x11,0xF2,0xFE,0x38,0x79,0xF7,0x07,0x39,0xFE,0x51,
+0x92,0x97,0x0B,0x5B,0x08,0x5F,0x34,0x86,0x01,0xAD,0x88,0x97,0xEB,0x66,0xCD,0x5E,
+0xD1,0xFF,0xDC,0x7D,0xF2,0x84,0xDA,0xBA,0x77,0xAD,0xDC,0x80,0x08,0xC7,0xA7,0x87,
+0xD6,0x55,0x9F,0x97,0x6A,0xE8,0xC8,0x11,0x64,0xBA,0xE7,0x19,0x29,0x3F,0x11,0xB3,
+0x78,0x90,0x84,0x20,0x52,0x5B,0x11,0xEF,0x78,0xD0,0x83,0xF6,0xD5,0x48,0x90,0xD0,
+0x30,0x1C,0xCF,0x80,0xF9,0x60,0xFE,0x79,0xE4,0x88,0xF2,0xDD,0x00,0xEB,0x94,0x45,
+0xEB,0x65,0x94,0x69,0x40,0xBA,0xC0,0xD5,0xB4,0xB8,0xBA,0x7D,0x04,0x11,0xA8,0xEB,
+0x31,0x05,0x96,0x94,0x4E,0x58,0x21,0x8E,0x9F,0xD0,0x60,0xFD,0x02,0x03,0x01,0x00,
+0x01,0xA3,0x81,0xD1,0x30,0x81,0xCE,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,
+0x04,0x14,0x81,0x3E,0x37,0xD8,0x92,0xB0,0x1F,0x77,0x9F,0x5C,0xB4,0xAB,0x73,0xAA,
+0xE7,0xF6,0x34,0x60,0x2F,0xFA,0x30,0x0B,0x06,0x03,0x55,0x1D,0x0F,0x04,0x04,0x03,
+0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,
+0x03,0x01,0x01,0xFF,0x30,0x81,0x8E,0x06,0x03,0x55,0x1D,0x23,0x04,0x81,0x86,0x30,
+0x81,0x83,0x80,0x14,0x81,0x3E,0x37,0xD8,0x92,0xB0,0x1F,0x77,0x9F,0x5C,0xB4,0xAB,
+0x73,0xAA,0xE7,0xF6,0x34,0x60,0x2F,0xFA,0xA1,0x68,0xA4,0x66,0x30,0x64,0x31,0x0B,
+0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53,0x45,0x31,0x14,0x30,0x12,0x06,
+0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x41,
+0x42,0x31,0x1D,0x30,0x1B,0x06,0x03,0x55,0x04,0x0B,0x13,0x14,0x41,0x64,0x64,0x54,
+0x72,0x75,0x73,0x74,0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,
+0x31,0x20,0x30,0x1E,0x06,0x03,0x55,0x04,0x03,0x13,0x17,0x41,0x64,0x64,0x54,0x72,
+0x75,0x73,0x74,0x20,0x50,0x75,0x62,0x6C,0x69,0x63,0x20,0x43,0x41,0x20,0x52,0x6F,
+0x6F,0x74,0x82,0x01,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,
+0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x03,0xF7,0x15,0x4A,0xF8,0x24,0xDA,
+0x23,0x56,0x16,0x93,0x76,0xDD,0x36,0x28,0xB9,0xAE,0x1B,0xB8,0xC3,0xF1,0x64,0xBA,
+0x20,0x18,0x78,0x95,0x29,0x27,0x57,0x05,0xBC,0x7C,0x2A,0xF4,0xB9,0x51,0x55,0xDA,
+0x87,0x02,0xDE,0x0F,0x16,0x17,0x31,0xF8,0xAA,0x79,0x2E,0x09,0x13,0xBB,0xAF,0xB2,
+0x20,0x19,0x12,0xE5,0x93,0xF9,0x4B,0xF9,0x83,0xE8,0x44,0xD5,0xB2,0x41,0x25,0xBF,
+0x88,0x75,0x6F,0xFF,0x10,0xFC,0x4A,0x54,0xD0,0x5F,0xF0,0xFA,0xEF,0x36,0x73,0x7D,
+0x1B,0x36,0x45,0xC6,0x21,0x6D,0xB4,0x15,0xB8,0x4E,0xCF,0x9C,0x5C,0xA5,0x3D,0x5A,
+0x00,0x8E,0x06,0xE3,0x3C,0x6B,0x32,0x7B,0xF2,0x9F,0xF0,0xB6,0xFD,0xDF,0xF0,0x28,
+0x18,0x48,0xF0,0xC6,0xBC,0xD0,0xBF,0x34,0x80,0x96,0xC2,0x4A,0xB1,0x6D,0x8E,0xC7,
+0x90,0x45,0xDE,0x2F,0x67,0xAC,0x45,0x04,0xA3,0x7A,0xDC,0x55,0x92,0xC9,0x47,0x66,
+0xD8,0x1A,0x8C,0xC7,0xED,0x9C,0x4E,0x9A,0xE0,0x12,0xBB,0xB5,0x6A,0x4C,0x84,0xE1,
+0xE1,0x22,0x0D,0x87,0x00,0x64,0xFE,0x8C,0x7D,0x62,0x39,0x65,0xA6,0xEF,0x42,0xB6,
+0x80,0x25,0x12,0x61,0x01,0xA8,0x24,0x13,0x70,0x00,0x11,0x26,0x5F,0xFA,0x35,0x50,
+0xC5,0x48,0xCC,0x06,0x47,0xE8,0x27,0xD8,0x70,0x8D,0x5F,0x64,0xE6,0xA1,0x44,0x26,
+0x5E,0x22,0xEC,0x92,0xCD,0xFF,0x42,0x9A,0x44,0x21,0x6D,0x5C,0xC5,0xE3,0x22,0x1D,
+0x5F,0x47,0x12,0xE7,0xCE,0x5F,0x5D,0xFA,0xD8,0xAA,0xB1,0x33,0x2D,0xD9,0x76,0xF2,
+0x4E,0x3A,0x33,0x0C,0x2B,0xB3,0x2D,0x90,0x06,
+};
+
+
+/* subject:/C=SE/O=AddTrust AB/OU=AddTrust TTP Network/CN=AddTrust Qualified CA Root */
+/* issuer :/C=SE/O=AddTrust AB/OU=AddTrust TTP Network/CN=AddTrust Qualified CA Root */
+
+
+const unsigned char AddTrust_Qualified_Certificates_Root_certificate[1058]={
+0x30,0x82,0x04,0x1E,0x30,0x82,0x03,0x06,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,
+0x67,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53,0x45,0x31,0x14,
+0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54,0x72,0x75,0x73,
+0x74,0x20,0x41,0x42,0x31,0x1D,0x30,0x1B,0x06,0x03,0x55,0x04,0x0B,0x13,0x14,0x41,
+0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77,
+0x6F,0x72,0x6B,0x31,0x23,0x30,0x21,0x06,0x03,0x55,0x04,0x03,0x13,0x1A,0x41,0x64,
+0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x51,0x75,0x61,0x6C,0x69,0x66,0x69,0x65,0x64,
+0x20,0x43,0x41,0x20,0x52,0x6F,0x6F,0x74,0x30,0x1E,0x17,0x0D,0x30,0x30,0x30,0x35,
+0x33,0x30,0x31,0x30,0x34,0x34,0x35,0x30,0x5A,0x17,0x0D,0x32,0x30,0x30,0x35,0x33,
+0x30,0x31,0x30,0x34,0x34,0x35,0x30,0x5A,0x30,0x67,0x31,0x0B,0x30,0x09,0x06,0x03,
+0x55,0x04,0x06,0x13,0x02,0x53,0x45,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,
+0x13,0x0B,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x41,0x42,0x31,0x1D,0x30,
+0x1B,0x06,0x03,0x55,0x04,0x0B,0x13,0x14,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,
+0x20,0x54,0x54,0x50,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x23,0x30,0x21,
+0x06,0x03,0x55,0x04,0x03,0x13,0x1A,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,
+0x51,0x75,0x61,0x6C,0x69,0x66,0x69,0x65,0x64,0x20,0x43,0x41,0x20,0x52,0x6F,0x6F,
+0x74,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,
+0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,
+0x01,0x00,0xE4,0x1E,0x9A,0xFE,0xDC,0x09,0x5A,0x87,0xA4,0x9F,0x47,0xBE,0x11,0x5F,
+0xAF,0x84,0x34,0xDB,0x62,0x3C,0x79,0x78,0xB7,0xE9,0x30,0xB5,0xEC,0x0C,0x1C,0x2A,
+0xC4,0x16,0xFF,0xE0,0xEC,0x71,0xEB,0x8A,0xF5,0x11,0x6E,0xED,0x4F,0x0D,0x91,0xD2,
+0x12,0x18,0x2D,0x49,0x15,0x01,0xC2,0xA4,0x22,0x13,0xC7,0x11,0x64,0xFF,0x22,0x12,
+0x9A,0xB9,0x8E,0x5C,0x2F,0x08,0xCF,0x71,0x6A,0xB3,0x67,0x01,0x59,0xF1,0x5D,0x46,
+0xF3,0xB0,0x78,0xA5,0xF6,0x0E,0x42,0x7A,0xE3,0x7F,0x1B,0xCC,0xD0,0xF0,0xB7,0x28,
+0xFD,0x2A,0xEA,0x9E,0xB3,0xB0,0xB9,0x04,0xAA,0xFD,0xF6,0xC7,0xB4,0xB1,0xB8,0x2A,
+0xA0,0xFB,0x58,0xF1,0x19,0xA0,0x6F,0x70,0x25,0x7E,0x3E,0x69,0x4A,0x7F,0x0F,0x22,
+0xD8,0xEF,0xAD,0x08,0x11,0x9A,0x29,0x99,0xE1,0xAA,0x44,0x45,0x9A,0x12,0x5E,0x3E,
+0x9D,0x6D,0x52,0xFC,0xE7,0xA0,0x3D,0x68,0x2F,0xF0,0x4B,0x70,0x7C,0x13,0x38,0xAD,
+0xBC,0x15,0x25,0xF1,0xD6,0xCE,0xAB,0xA2,0xC0,0x31,0xD6,0x2F,0x9F,0xE0,0xFF,0x14,
+0x59,0xFC,0x84,0x93,0xD9,0x87,0x7C,0x4C,0x54,0x13,0xEB,0x9F,0xD1,0x2D,0x11,0xF8,
+0x18,0x3A,0x3A,0xDE,0x25,0xD9,0xF7,0xD3,0x40,0xED,0xA4,0x06,0x12,0xC4,0x3B,0xE1,
+0x91,0xC1,0x56,0x35,0xF0,0x14,0xDC,0x65,0x36,0x09,0x6E,0xAB,0xA4,0x07,0xC7,0x35,
+0xD1,0xC2,0x03,0x33,0x36,0x5B,0x75,0x26,0x6D,0x42,0xF1,0x12,0x6B,0x43,0x6F,0x4B,
+0x71,0x94,0xFA,0x34,0x1D,0xED,0x13,0x6E,0xCA,0x80,0x7F,0x98,0x2F,0x6C,0xB9,0x65,
+0xD8,0xE9,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0xD4,0x30,0x81,0xD1,0x30,0x1D,0x06,
+0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x39,0x95,0x8B,0x62,0x8B,0x5C,0xC9,0xD4,
+0x80,0xBA,0x58,0x0F,0x97,0x3F,0x15,0x08,0x43,0xCC,0x98,0xA7,0x30,0x0B,0x06,0x03,
+0x55,0x1D,0x0F,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,
+0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x81,0x91,0x06,0x03,0x55,
+0x1D,0x23,0x04,0x81,0x89,0x30,0x81,0x86,0x80,0x14,0x39,0x95,0x8B,0x62,0x8B,0x5C,
+0xC9,0xD4,0x80,0xBA,0x58,0x0F,0x97,0x3F,0x15,0x08,0x43,0xCC,0x98,0xA7,0xA1,0x6B,
+0xA4,0x69,0x30,0x67,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x53,
+0x45,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x41,0x64,0x64,0x54,
+0x72,0x75,0x73,0x74,0x20,0x41,0x42,0x31,0x1D,0x30,0x1B,0x06,0x03,0x55,0x04,0x0B,
+0x13,0x14,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x54,0x54,0x50,0x20,0x4E,
+0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x23,0x30,0x21,0x06,0x03,0x55,0x04,0x03,0x13,
+0x1A,0x41,0x64,0x64,0x54,0x72,0x75,0x73,0x74,0x20,0x51,0x75,0x61,0x6C,0x69,0x66,
+0x69,0x65,0x64,0x20,0x43,0x41,0x20,0x52,0x6F,0x6F,0x74,0x82,0x01,0x01,0x30,0x0D,
+0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,
+0x01,0x00,0x19,0xAB,0x75,0xEA,0xF8,0x8B,0x65,0x61,0x95,0x13,0xBA,0x69,0x04,0xEF,
+0x86,0xCA,0x13,0xA0,0xC7,0xAA,0x4F,0x64,0x1B,0x3F,0x18,0xF6,0xA8,0x2D,0x2C,0x55,
+0x8F,0x05,0xB7,0x30,0xEA,0x42,0x6A,0x1D,0xC0,0x25,0x51,0x2D,0xA7,0xBF,0x0C,0xB3,
+0xED,0xEF,0x08,0x7F,0x6C,0x3C,0x46,0x1A,0xEA,0x18,0x43,0xDF,0x76,0xCC,0xF9,0x66,
+0x86,0x9C,0x2C,0x68,0xF5,0xE9,0x17,0xF8,0x31,0xB3,0x18,0xC4,0xD6,0x48,0x7D,0x23,
+0x4C,0x68,0xC1,0x7E,0xBB,0x01,0x14,0x6F,0xC5,0xD9,0x6E,0xDE,0xBB,0x04,0x42,0x6A,
+0xF8,0xF6,0x5C,0x7D,0xE5,0xDA,0xFA,0x87,0xEB,0x0D,0x35,0x52,0x67,0xD0,0x9E,0x97,
+0x76,0x05,0x93,0x3F,0x95,0xC7,0x01,0xE6,0x69,0x55,0x38,0x7F,0x10,0x61,0x99,0xC9,
+0xE3,0x5F,0xA6,0xCA,0x3E,0x82,0x63,0x48,0xAA,0xE2,0x08,0x48,0x3E,0xAA,0xF2,0xB2,
+0x85,0x62,0xA6,0xB4,0xA7,0xD9,0xBD,0x37,0x9C,0x68,0xB5,0x2D,0x56,0x7D,0xB0,0xB7,
+0x3F,0xA0,0xB1,0x07,0xD6,0xE9,0x4F,0xDC,0xDE,0x45,0x71,0x30,0x32,0x7F,0x1B,0x2E,
+0x09,0xF9,0xBF,0x52,0xA1,0xEE,0xC2,0x80,0x3E,0x06,0x5C,0x2E,0x55,0x40,0xC1,0x1B,
+0xF5,0x70,0x45,0xB0,0xDC,0x5D,0xFA,0xF6,0x72,0x5A,0x77,0xD2,0x63,0xCD,0xCF,0x58,
+0x89,0x00,0x42,0x63,0x3F,0x79,0x39,0xD0,0x44,0xB0,0x82,0x6E,0x41,0x19,0xE8,0xDD,
+0xE0,0xC1,0x88,0x5A,0xD1,0x1E,0x71,0x93,0x1F,0x24,0x30,0x74,0xE5,0x1E,0xA8,0xDE,
+0x3C,0x27,0x37,0x7F,0x83,0xAE,0x9E,0x77,0xCF,0xF0,0x30,0xB1,0xFF,0x4B,0x99,0xE8,
+0xC6,0xA1,
+};
+
+
+/* subject:/C=US/O=AffirmTrust/CN=AffirmTrust Commercial */
+/* issuer :/C=US/O=AffirmTrust/CN=AffirmTrust Commercial */
+
+
+const unsigned char AffirmTrust_Commercial_certificate[848]={
+0x30,0x82,0x03,0x4C,0x30,0x82,0x02,0x34,0xA0,0x03,0x02,0x01,0x02,0x02,0x08,0x77,
+0x77,0x06,0x27,0x26,0xA9,0xB1,0x7C,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,
+0x0D,0x01,0x01,0x0B,0x05,0x00,0x30,0x44,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,
+0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x0C,0x0B,
+0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x31,0x1F,0x30,0x1D,0x06,
+0x03,0x55,0x04,0x03,0x0C,0x16,0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73,
+0x74,0x20,0x43,0x6F,0x6D,0x6D,0x65,0x72,0x63,0x69,0x61,0x6C,0x30,0x1E,0x17,0x0D,
+0x31,0x30,0x30,0x31,0x32,0x39,0x31,0x34,0x30,0x36,0x30,0x36,0x5A,0x17,0x0D,0x33,
+0x30,0x31,0x32,0x33,0x31,0x31,0x34,0x30,0x36,0x30,0x36,0x5A,0x30,0x44,0x31,0x0B,
+0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06,
+0x03,0x55,0x04,0x0A,0x0C,0x0B,0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73,
+0x74,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x03,0x0C,0x16,0x41,0x66,0x66,0x69,
+0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x20,0x43,0x6F,0x6D,0x6D,0x65,0x72,0x63,0x69,
+0x61,0x6C,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,
+0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,
+0x01,0x01,0x00,0xF6,0x1B,0x4F,0x67,0x07,0x2B,0xA1,0x15,0xF5,0x06,0x22,0xCB,0x1F,
+0x01,0xB2,0xE3,0x73,0x45,0x06,0x44,0x49,0x2C,0xBB,0x49,0x25,0x14,0xD6,0xCE,0xC3,
+0xB7,0xAB,0x2C,0x4F,0xC6,0x41,0x32,0x94,0x57,0xFA,0x12,0xA7,0x5B,0x0E,0xE2,0x8F,
+0x1F,0x1E,0x86,0x19,0xA7,0xAA,0xB5,0x2D,0xB9,0x5F,0x0D,0x8A,0xC2,0xAF,0x85,0x35,
+0x79,0x32,0x2D,0xBB,0x1C,0x62,0x37,0xF2,0xB1,0x5B,0x4A,0x3D,0xCA,0xCD,0x71,0x5F,
+0xE9,0x42,0xBE,0x94,0xE8,0xC8,0xDE,0xF9,0x22,0x48,0x64,0xC6,0xE5,0xAB,0xC6,0x2B,
+0x6D,0xAD,0x05,0xF0,0xFA,0xD5,0x0B,0xCF,0x9A,0xE5,0xF0,0x50,0xA4,0x8B,0x3B,0x47,
+0xA5,0x23,0x5B,0x7A,0x7A,0xF8,0x33,0x3F,0xB8,0xEF,0x99,0x97,0xE3,0x20,0xC1,0xD6,
+0x28,0x89,0xCF,0x94,0xFB,0xB9,0x45,0xED,0xE3,0x40,0x17,0x11,0xD4,0x74,0xF0,0x0B,
+0x31,0xE2,0x2B,0x26,0x6A,0x9B,0x4C,0x57,0xAE,0xAC,0x20,0x3E,0xBA,0x45,0x7A,0x05,
+0xF3,0xBD,0x9B,0x69,0x15,0xAE,0x7D,0x4E,0x20,0x63,0xC4,0x35,0x76,0x3A,0x07,0x02,
+0xC9,0x37,0xFD,0xC7,0x47,0xEE,0xE8,0xF1,0x76,0x1D,0x73,0x15,0xF2,0x97,0xA4,0xB5,
+0xC8,0x7A,0x79,0xD9,0x42,0xAA,0x2B,0x7F,0x5C,0xFE,0xCE,0x26,0x4F,0xA3,0x66,0x81,
+0x35,0xAF,0x44,0xBA,0x54,0x1E,0x1C,0x30,0x32,0x65,0x9D,0xE6,0x3C,0x93,0x5E,0x50,
+0x4E,0x7A,0xE3,0x3A,0xD4,0x6E,0xCC,0x1A,0xFB,0xF9,0xD2,0x37,0xAE,0x24,0x2A,0xAB,
+0x57,0x03,0x22,0x28,0x0D,0x49,0x75,0x7F,0xB7,0x28,0xDA,0x75,0xBF,0x8E,0xE3,0xDC,
+0x0E,0x79,0x31,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40,0x30,0x1D,0x06,0x03,
+0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x9D,0x93,0xC6,0x53,0x8B,0x5E,0xCA,0xAF,0x3F,
+0x9F,0x1E,0x0F,0xE5,0x99,0x95,0xBC,0x24,0xF6,0x94,0x8F,0x30,0x0F,0x06,0x03,0x55,
+0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,
+0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0D,0x06,0x09,
+0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82,0x01,0x01,0x00,
+0x58,0xAC,0xF4,0x04,0x0E,0xCD,0xC0,0x0D,0xFF,0x0A,0xFD,0xD4,0xBA,0x16,0x5F,0x29,
+0xBD,0x7B,0x68,0x99,0x58,0x49,0xD2,0xB4,0x1D,0x37,0x4D,0x7F,0x27,0x7D,0x46,0x06,
+0x5D,0x43,0xC6,0x86,0x2E,0x3E,0x73,0xB2,0x26,0x7D,0x4F,0x93,0xA9,0xB6,0xC4,0x2A,
+0x9A,0xAB,0x21,0x97,0x14,0xB1,0xDE,0x8C,0xD3,0xAB,0x89,0x15,0xD8,0x6B,0x24,0xD4,
+0xF1,0x16,0xAE,0xD8,0xA4,0x5C,0xD4,0x7F,0x51,0x8E,0xED,0x18,0x01,0xB1,0x93,0x63,
+0xBD,0xBC,0xF8,0x61,0x80,0x9A,0x9E,0xB1,0xCE,0x42,0x70,0xE2,0xA9,0x7D,0x06,0x25,
+0x7D,0x27,0xA1,0xFE,0x6F,0xEC,0xB3,0x1E,0x24,0xDA,0xE3,0x4B,0x55,0x1A,0x00,0x3B,
+0x35,0xB4,0x3B,0xD9,0xD7,0x5D,0x30,0xFD,0x81,0x13,0x89,0xF2,0xC2,0x06,0x2B,0xED,
+0x67,0xC4,0x8E,0xC9,0x43,0xB2,0x5C,0x6B,0x15,0x89,0x02,0xBC,0x62,0xFC,0x4E,0xF2,
+0xB5,0x33,0xAA,0xB2,0x6F,0xD3,0x0A,0xA2,0x50,0xE3,0xF6,0x3B,0xE8,0x2E,0x44,0xC2,
+0xDB,0x66,0x38,0xA9,0x33,0x56,0x48,0xF1,0x6D,0x1B,0x33,0x8D,0x0D,0x8C,0x3F,0x60,
+0x37,0x9D,0xD3,0xCA,0x6D,0x7E,0x34,0x7E,0x0D,0x9F,0x72,0x76,0x8B,0x1B,0x9F,0x72,
+0xFD,0x52,0x35,0x41,0x45,0x02,0x96,0x2F,0x1C,0xB2,0x9A,0x73,0x49,0x21,0xB1,0x49,
+0x47,0x45,0x47,0xB4,0xEF,0x6A,0x34,0x11,0xC9,0x4D,0x9A,0xCC,0x59,0xB7,0xD6,0x02,
+0x9E,0x5A,0x4E,0x65,0xB5,0x94,0xAE,0x1B,0xDF,0x29,0xB0,0x16,0xF1,0xBF,0x00,0x9E,
+0x07,0x3A,0x17,0x64,0xB5,0x04,0xB5,0x23,0x21,0x99,0x0A,0x95,0x3B,0x97,0x7C,0xEF,
+};
+
+
+/* subject:/C=US/O=AffirmTrust/CN=AffirmTrust Networking */
+/* issuer :/C=US/O=AffirmTrust/CN=AffirmTrust Networking */
+
+
+const unsigned char AffirmTrust_Networking_certificate[848]={
+0x30,0x82,0x03,0x4C,0x30,0x82,0x02,0x34,0xA0,0x03,0x02,0x01,0x02,0x02,0x08,0x7C,
+0x4F,0x04,0x39,0x1C,0xD4,0x99,0x2D,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,
+0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x44,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,
+0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x0C,0x0B,
+0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x31,0x1F,0x30,0x1D,0x06,
+0x03,0x55,0x04,0x03,0x0C,0x16,0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73,
+0x74,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x69,0x6E,0x67,0x30,0x1E,0x17,0x0D,
+0x31,0x30,0x30,0x31,0x32,0x39,0x31,0x34,0x30,0x38,0x32,0x34,0x5A,0x17,0x0D,0x33,
+0x30,0x31,0x32,0x33,0x31,0x31,0x34,0x30,0x38,0x32,0x34,0x5A,0x30,0x44,0x31,0x0B,
+0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06,
+0x03,0x55,0x04,0x0A,0x0C,0x0B,0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73,
+0x74,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x03,0x0C,0x16,0x41,0x66,0x66,0x69,
+0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x69,
+0x6E,0x67,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,
+0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,
+0x01,0x01,0x00,0xB4,0x84,0xCC,0x33,0x17,0x2E,0x6B,0x94,0x6C,0x6B,0x61,0x52,0xA0,
+0xEB,0xA3,0xCF,0x79,0x94,0x4C,0xE5,0x94,0x80,0x99,0xCB,0x55,0x64,0x44,0x65,0x8F,
+0x67,0x64,0xE2,0x06,0xE3,0x5C,0x37,0x49,0xF6,0x2F,0x9B,0x84,0x84,0x1E,0x2D,0xF2,
+0x60,0x9D,0x30,0x4E,0xCC,0x84,0x85,0xE2,0x2C,0xCF,0x1E,0x9E,0xFE,0x36,0xAB,0x33,
+0x77,0x35,0x44,0xD8,0x35,0x96,0x1A,0x3D,0x36,0xE8,0x7A,0x0E,0xD8,0xD5,0x47,0xA1,
+0x6A,0x69,0x8B,0xD9,0xFC,0xBB,0x3A,0xAE,0x79,0x5A,0xD5,0xF4,0xD6,0x71,0xBB,0x9A,
+0x90,0x23,0x6B,0x9A,0xB7,0x88,0x74,0x87,0x0C,0x1E,0x5F,0xB9,0x9E,0x2D,0xFA,0xAB,
+0x53,0x2B,0xDC,0xBB,0x76,0x3E,0x93,0x4C,0x08,0x08,0x8C,0x1E,0xA2,0x23,0x1C,0xD4,
+0x6A,0xAD,0x22,0xBA,0x99,0x01,0x2E,0x6D,0x65,0xCB,0xBE,0x24,0x66,0x55,0x24,0x4B,
+0x40,0x44,0xB1,0x1B,0xD7,0xE1,0xC2,0x85,0xC0,0xDE,0x10,0x3F,0x3D,0xED,0xB8,0xFC,
+0xF1,0xF1,0x23,0x53,0xDC,0xBF,0x65,0x97,0x6F,0xD9,0xF9,0x40,0x71,0x8D,0x7D,0xBD,
+0x95,0xD4,0xCE,0xBE,0xA0,0x5E,0x27,0x23,0xDE,0xFD,0xA6,0xD0,0x26,0x0E,0x00,0x29,
+0xEB,0x3C,0x46,0xF0,0x3D,0x60,0xBF,0x3F,0x50,0xD2,0xDC,0x26,0x41,0x51,0x9E,0x14,
+0x37,0x42,0x04,0xA3,0x70,0x57,0xA8,0x1B,0x87,0xED,0x2D,0xFA,0x7B,0xEE,0x8C,0x0A,
+0xE3,0xA9,0x66,0x89,0x19,0xCB,0x41,0xF9,0xDD,0x44,0x36,0x61,0xCF,0xE2,0x77,0x46,
+0xC8,0x7D,0xF6,0xF4,0x92,0x81,0x36,0xFD,0xDB,0x34,0xF1,0x72,0x7E,0xF3,0x0C,0x16,
+0xBD,0xB4,0x15,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40,0x30,0x1D,0x06,0x03,
+0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x07,0x1F,0xD2,0xE7,0x9C,0xDA,0xC2,0x6E,0xA2,
+0x40,0xB4,0xB0,0x7A,0x50,0x10,0x50,0x74,0xC4,0xC8,0xBD,0x30,0x0F,0x06,0x03,0x55,
+0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,
+0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0D,0x06,0x09,
+0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,
+0x89,0x57,0xB2,0x16,0x7A,0xA8,0xC2,0xFD,0xD6,0xD9,0x9B,0x9B,0x34,0xC2,0x9C,0xB4,
+0x32,0x14,0x4D,0xA7,0xA4,0xDF,0xEC,0xBE,0xA7,0xBE,0xF8,0x43,0xDB,0x91,0x37,0xCE,
+0xB4,0x32,0x2E,0x50,0x55,0x1A,0x35,0x4E,0x76,0x43,0x71,0x20,0xEF,0x93,0x77,0x4E,
+0x15,0x70,0x2E,0x87,0xC3,0xC1,0x1D,0x6D,0xDC,0xCB,0xB5,0x27,0xD4,0x2C,0x56,0xD1,
+0x52,0x53,0x3A,0x44,0xD2,0x73,0xC8,0xC4,0x1B,0x05,0x65,0x5A,0x62,0x92,0x9C,0xEE,
+0x41,0x8D,0x31,0xDB,0xE7,0x34,0xEA,0x59,0x21,0xD5,0x01,0x7A,0xD7,0x64,0xB8,0x64,
+0x39,0xCD,0xC9,0xED,0xAF,0xED,0x4B,0x03,0x48,0xA7,0xA0,0x99,0x01,0x80,0xDC,0x65,
+0xA3,0x36,0xAE,0x65,0x59,0x48,0x4F,0x82,0x4B,0xC8,0x65,0xF1,0x57,0x1D,0xE5,0x59,
+0x2E,0x0A,0x3F,0x6C,0xD8,0xD1,0xF5,0xE5,0x09,0xB4,0x6C,0x54,0x00,0x0A,0xE0,0x15,
+0x4D,0x87,0x75,0x6D,0xB7,0x58,0x96,0x5A,0xDD,0x6D,0xD2,0x00,0xA0,0xF4,0x9B,0x48,
+0xBE,0xC3,0x37,0xA4,0xBA,0x36,0xE0,0x7C,0x87,0x85,0x97,0x1A,0x15,0xA2,0xDE,0x2E,
+0xA2,0x5B,0xBD,0xAF,0x18,0xF9,0x90,0x50,0xCD,0x70,0x59,0xF8,0x27,0x67,0x47,0xCB,
+0xC7,0xA0,0x07,0x3A,0x7D,0xD1,0x2C,0x5D,0x6C,0x19,0x3A,0x66,0xB5,0x7D,0xFD,0x91,
+0x6F,0x82,0xB1,0xBE,0x08,0x93,0xDB,0x14,0x47,0xF1,0xA2,0x37,0xC7,0x45,0x9E,0x3C,
+0xC7,0x77,0xAF,0x64,0xA8,0x93,0xDF,0xF6,0x69,0x83,0x82,0x60,0xF2,0x49,0x42,0x34,
+0xED,0x5A,0x00,0x54,0x85,0x1C,0x16,0x36,0x92,0x0C,0x5C,0xFA,0xA6,0xAD,0xBF,0xDB,
+};
+
+
+/* subject:/C=US/O=AffirmTrust/CN=AffirmTrust Premium */
+/* issuer :/C=US/O=AffirmTrust/CN=AffirmTrust Premium */
+
+
+const unsigned char AffirmTrust_Premium_certificate[1354]={
+0x30,0x82,0x05,0x46,0x30,0x82,0x03,0x2E,0xA0,0x03,0x02,0x01,0x02,0x02,0x08,0x6D,
+0x8C,0x14,0x46,0xB1,0xA6,0x0A,0xEE,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,
+0x0D,0x01,0x01,0x0C,0x05,0x00,0x30,0x41,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,
+0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x0C,0x0B,
+0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x31,0x1C,0x30,0x1A,0x06,
+0x03,0x55,0x04,0x03,0x0C,0x13,0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73,
+0x74,0x20,0x50,0x72,0x65,0x6D,0x69,0x75,0x6D,0x30,0x1E,0x17,0x0D,0x31,0x30,0x30,
+0x31,0x32,0x39,0x31,0x34,0x31,0x30,0x33,0x36,0x5A,0x17,0x0D,0x34,0x30,0x31,0x32,
+0x33,0x31,0x31,0x34,0x31,0x30,0x33,0x36,0x5A,0x30,0x41,0x31,0x0B,0x30,0x09,0x06,
+0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,
+0x0A,0x0C,0x0B,0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x31,0x1C,
+0x30,0x1A,0x06,0x03,0x55,0x04,0x03,0x0C,0x13,0x41,0x66,0x66,0x69,0x72,0x6D,0x54,
+0x72,0x75,0x73,0x74,0x20,0x50,0x72,0x65,0x6D,0x69,0x75,0x6D,0x30,0x82,0x02,0x22,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,
+0x82,0x02,0x0F,0x00,0x30,0x82,0x02,0x0A,0x02,0x82,0x02,0x01,0x00,0xC4,0x12,0xDF,
+0xA9,0x5F,0xFE,0x41,0xDD,0xDD,0xF5,0x9F,0x8A,0xE3,0xF6,0xAC,0xE1,0x3C,0x78,0x9A,
+0xBC,0xD8,0xF0,0x7F,0x7A,0xA0,0x33,0x2A,0xDC,0x8D,0x20,0x5B,0xAE,0x2D,0x6F,0xE7,
+0x93,0xD9,0x36,0x70,0x6A,0x68,0xCF,0x8E,0x51,0xA3,0x85,0x5B,0x67,0x04,0xA0,0x10,
+0x24,0x6F,0x5D,0x28,0x82,0xC1,0x97,0x57,0xD8,0x48,0x29,0x13,0xB6,0xE1,0xBE,0x91,
+0x4D,0xDF,0x85,0x0C,0x53,0x18,0x9A,0x1E,0x24,0xA2,0x4F,0x8F,0xF0,0xA2,0x85,0x0B,
+0xCB,0xF4,0x29,0x7F,0xD2,0xA4,0x58,0xEE,0x26,0x4D,0xC9,0xAA,0xA8,0x7B,0x9A,0xD9,
+0xFA,0x38,0xDE,0x44,0x57,0x15,0xE5,0xF8,0x8C,0xC8,0xD9,0x48,0xE2,0x0D,0x16,0x27,
+0x1D,0x1E,0xC8,0x83,0x85,0x25,0xB7,0xBA,0xAA,0x55,0x41,0xCC,0x03,0x22,0x4B,0x2D,
+0x91,0x8D,0x8B,0xE6,0x89,0xAF,0x66,0xC7,0xE9,0xFF,0x2B,0xE9,0x3C,0xAC,0xDA,0xD2,
+0xB3,0xC3,0xE1,0x68,0x9C,0x89,0xF8,0x7A,0x00,0x56,0xDE,0xF4,0x55,0x95,0x6C,0xFB,
+0xBA,0x64,0xDD,0x62,0x8B,0xDF,0x0B,0x77,0x32,0xEB,0x62,0xCC,0x26,0x9A,0x9B,0xBB,
+0xAA,0x62,0x83,0x4C,0xB4,0x06,0x7A,0x30,0xC8,0x29,0xBF,0xED,0x06,0x4D,0x97,0xB9,
+0x1C,0xC4,0x31,0x2B,0xD5,0x5F,0xBC,0x53,0x12,0x17,0x9C,0x99,0x57,0x29,0x66,0x77,
+0x61,0x21,0x31,0x07,0x2E,0x25,0x49,0x9D,0x18,0xF2,0xEE,0xF3,0x2B,0x71,0x8C,0xB5,
+0xBA,0x39,0x07,0x49,0x77,0xFC,0xEF,0x2E,0x92,0x90,0x05,0x8D,0x2D,0x2F,0x77,0x7B,
+0xEF,0x43,0xBF,0x35,0xBB,0x9A,0xD8,0xF9,0x73,0xA7,0x2C,0xF2,0xD0,0x57,0xEE,0x28,
+0x4E,0x26,0x5F,0x8F,0x90,0x68,0x09,0x2F,0xB8,0xF8,0xDC,0x06,0xE9,0x2E,0x9A,0x3E,
+0x51,0xA7,0xD1,0x22,0xC4,0x0A,0xA7,0x38,0x48,0x6C,0xB3,0xF9,0xFF,0x7D,0xAB,0x86,
+0x57,0xE3,0xBA,0xD6,0x85,0x78,0x77,0xBA,0x43,0xEA,0x48,0x7F,0xF6,0xD8,0xBE,0x23,
+0x6D,0x1E,0xBF,0xD1,0x36,0x6C,0x58,0x5C,0xF1,0xEE,0xA4,0x19,0x54,0x1A,0xF5,0x03,
+0xD2,0x76,0xE6,0xE1,0x8C,0xBD,0x3C,0xB3,0xD3,0x48,0x4B,0xE2,0xC8,0xF8,0x7F,0x92,
+0xA8,0x76,0x46,0x9C,0x42,0x65,0x3E,0xA4,0x1E,0xC1,0x07,0x03,0x5A,0x46,0x2D,0xB8,
+0x97,0xF3,0xB7,0xD5,0xB2,0x55,0x21,0xEF,0xBA,0xDC,0x4C,0x00,0x97,0xFB,0x14,0x95,
+0x27,0x33,0xBF,0xE8,0x43,0x47,0x46,0xD2,0x08,0x99,0x16,0x60,0x3B,0x9A,0x7E,0xD2,
+0xE6,0xED,0x38,0xEA,0xEC,0x01,0x1E,0x3C,0x48,0x56,0x49,0x09,0xC7,0x4C,0x37,0x00,
+0x9E,0x88,0x0E,0xC0,0x73,0xE1,0x6F,0x66,0xE9,0x72,0x47,0x30,0x3E,0x10,0xE5,0x0B,
+0x03,0xC9,0x9A,0x42,0x00,0x6C,0xC5,0x94,0x7E,0x61,0xC4,0x8A,0xDF,0x7F,0x82,0x1A,
+0x0B,0x59,0xC4,0x59,0x32,0x77,0xB3,0xBC,0x60,0x69,0x56,0x39,0xFD,0xB4,0x06,0x7B,
+0x2C,0xD6,0x64,0x36,0xD9,0xBD,0x48,0xED,0x84,0x1F,0x7E,0xA5,0x22,0x8F,0x2A,0xB8,
+0x42,0xF4,0x82,0xB7,0xD4,0x53,0x90,0x78,0x4E,0x2D,0x1A,0xFD,0x81,0x6F,0x44,0xD7,
+0x3B,0x01,0x74,0x96,0x42,0xE0,0x00,0xE2,0x2E,0x6B,0xEA,0xC5,0xEE,0x72,0xAC,0xBB,
+0xBF,0xFE,0xEA,0xAA,0xA8,0xF8,0xDC,0xF6,0xB2,0x79,0x8A,0xB6,0x67,0x02,0x03,0x01,
+0x00,0x01,0xA3,0x42,0x30,0x40,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,
+0x14,0x9D,0xC0,0x67,0xA6,0x0C,0x22,0xD9,0x26,0xF5,0x45,0xAB,0xA6,0x65,0x52,0x11,
+0x27,0xD8,0x45,0xAC,0x63,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,
+0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,
+0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,
+0x01,0x01,0x0C,0x05,0x00,0x03,0x82,0x02,0x01,0x00,0xB3,0x57,0x4D,0x10,0x62,0x4E,
+0x3A,0xE4,0xAC,0xEA,0xB8,0x1C,0xAF,0x32,0x23,0xC8,0xB3,0x49,0x5A,0x51,0x9C,0x76,
+0x28,0x8D,0x79,0xAA,0x57,0x46,0x17,0xD5,0xF5,0x52,0xF6,0xB7,0x44,0xE8,0x08,0x44,
+0xBF,0x18,0x84,0xD2,0x0B,0x80,0xCD,0xC5,0x12,0xFD,0x00,0x55,0x05,0x61,0x87,0x41,
+0xDC,0xB5,0x24,0x9E,0x3C,0xC4,0xD8,0xC8,0xFB,0x70,0x9E,0x2F,0x78,0x96,0x83,0x20,
+0x36,0xDE,0x7C,0x0F,0x69,0x13,0x88,0xA5,0x75,0x36,0x98,0x08,0xA6,0xC6,0xDF,0xAC,
+0xCE,0xE3,0x58,0xD6,0xB7,0x3E,0xDE,0xBA,0xF3,0xEB,0x34,0x40,0xD8,0xA2,0x81,0xF5,
+0x78,0x3F,0x2F,0xD5,0xA5,0xFC,0xD9,0xA2,0xD4,0x5E,0x04,0x0E,0x17,0xAD,0xFE,0x41,
+0xF0,0xE5,0xB2,0x72,0xFA,0x44,0x82,0x33,0x42,0xE8,0x2D,0x58,0xF7,0x56,0x8C,0x62,
+0x3F,0xBA,0x42,0xB0,0x9C,0x0C,0x5C,0x7E,0x2E,0x65,0x26,0x5C,0x53,0x4F,0x00,0xB2,
+0x78,0x7E,0xA1,0x0D,0x99,0x2D,0x8D,0xB8,0x1D,0x8E,0xA2,0xC4,0xB0,0xFD,0x60,0xD0,
+0x30,0xA4,0x8E,0xC8,0x04,0x62,0xA9,0xC4,0xED,0x35,0xDE,0x7A,0x97,0xED,0x0E,0x38,
+0x5E,0x92,0x2F,0x93,0x70,0xA5,0xA9,0x9C,0x6F,0xA7,0x7D,0x13,0x1D,0x7E,0xC6,0x08,
+0x48,0xB1,0x5E,0x67,0xEB,0x51,0x08,0x25,0xE9,0xE6,0x25,0x6B,0x52,0x29,0x91,0x9C,
+0xD2,0x39,0x73,0x08,0x57,0xDE,0x99,0x06,0xB4,0x5B,0x9D,0x10,0x06,0xE1,0xC2,0x00,
+0xA8,0xB8,0x1C,0x4A,0x02,0x0A,0x14,0xD0,0xC1,0x41,0xCA,0xFB,0x8C,0x35,0x21,0x7D,
+0x82,0x38,0xF2,0xA9,0x54,0x91,0x19,0x35,0x93,0x94,0x6D,0x6A,0x3A,0xC5,0xB2,0xD0,
+0xBB,0x89,0x86,0x93,0xE8,0x9B,0xC9,0x0F,0x3A,0xA7,0x7A,0xB8,0xA1,0xF0,0x78,0x46,
+0xFA,0xFC,0x37,0x2F,0xE5,0x8A,0x84,0xF3,0xDF,0xFE,0x04,0xD9,0xA1,0x68,0xA0,0x2F,
+0x24,0xE2,0x09,0x95,0x06,0xD5,0x95,0xCA,0xE1,0x24,0x96,0xEB,0x7C,0xF6,0x93,0x05,
+0xBB,0xED,0x73,0xE9,0x2D,0xD1,0x75,0x39,0xD7,0xE7,0x24,0xDB,0xD8,0x4E,0x5F,0x43,
+0x8F,0x9E,0xD0,0x14,0x39,0xBF,0x55,0x70,0x48,0x99,0x57,0x31,0xB4,0x9C,0xEE,0x4A,
+0x98,0x03,0x96,0x30,0x1F,0x60,0x06,0xEE,0x1B,0x23,0xFE,0x81,0x60,0x23,0x1A,0x47,
+0x62,0x85,0xA5,0xCC,0x19,0x34,0x80,0x6F,0xB3,0xAC,0x1A,0xE3,0x9F,0xF0,0x7B,0x48,
+0xAD,0xD5,0x01,0xD9,0x67,0xB6,0xA9,0x72,0x93,0xEA,0x2D,0x66,0xB5,0xB2,0xB8,0xE4,
+0x3D,0x3C,0xB2,0xEF,0x4C,0x8C,0xEA,0xEB,0x07,0xBF,0xAB,0x35,0x9A,0x55,0x86,0xBC,
+0x18,0xA6,0xB5,0xA8,0x5E,0xB4,0x83,0x6C,0x6B,0x69,0x40,0xD3,0x9F,0xDC,0xF1,0xC3,
+0x69,0x6B,0xB9,0xE1,0x6D,0x09,0xF4,0xF1,0xAA,0x50,0x76,0x0A,0x7A,0x7D,0x7A,0x17,
+0xA1,0x55,0x96,0x42,0x99,0x31,0x09,0xDD,0x60,0x11,0x8D,0x05,0x30,0x7E,0xE6,0x8E,
+0x46,0xD1,0x9D,0x14,0xDA,0xC7,0x17,0xE4,0x05,0x96,0x8C,0xC4,0x24,0xB5,0x1B,0xCF,
+0x14,0x07,0xB2,0x40,0xF8,0xA3,0x9E,0x41,0x86,0xBC,0x04,0xD0,0x6B,0x96,0xC8,0x2A,
+0x80,0x34,0xFD,0xBF,0xEF,0x06,0xA3,0xDD,0x58,0xC5,0x85,0x3D,0x3E,0x8F,0xFE,0x9E,
+0x29,0xE0,0xB6,0xB8,0x09,0x68,0x19,0x1C,0x18,0x43,
+};
+
+
+/* subject:/C=US/O=AffirmTrust/CN=AffirmTrust Premium ECC */
+/* issuer :/C=US/O=AffirmTrust/CN=AffirmTrust Premium ECC */
+
+
+const unsigned char AffirmTrust_Premium_ECC_certificate[514]={
+0x30,0x82,0x01,0xFE,0x30,0x82,0x01,0x85,0xA0,0x03,0x02,0x01,0x02,0x02,0x08,0x74,
+0x97,0x25,0x8A,0xC7,0x3F,0x7A,0x54,0x30,0x0A,0x06,0x08,0x2A,0x86,0x48,0xCE,0x3D,
+0x04,0x03,0x03,0x30,0x45,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,
+0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x0C,0x0B,0x41,0x66,0x66,
+0x69,0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x31,0x20,0x30,0x1E,0x06,0x03,0x55,0x04,
+0x03,0x0C,0x17,0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x20,0x50,
+0x72,0x65,0x6D,0x69,0x75,0x6D,0x20,0x45,0x43,0x43,0x30,0x1E,0x17,0x0D,0x31,0x30,
+0x30,0x31,0x32,0x39,0x31,0x34,0x32,0x30,0x32,0x34,0x5A,0x17,0x0D,0x34,0x30,0x31,
+0x32,0x33,0x31,0x31,0x34,0x32,0x30,0x32,0x34,0x5A,0x30,0x45,0x31,0x0B,0x30,0x09,
+0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55,
+0x04,0x0A,0x0C,0x0B,0x41,0x66,0x66,0x69,0x72,0x6D,0x54,0x72,0x75,0x73,0x74,0x31,
+0x20,0x30,0x1E,0x06,0x03,0x55,0x04,0x03,0x0C,0x17,0x41,0x66,0x66,0x69,0x72,0x6D,
+0x54,0x72,0x75,0x73,0x74,0x20,0x50,0x72,0x65,0x6D,0x69,0x75,0x6D,0x20,0x45,0x43,
+0x43,0x30,0x76,0x30,0x10,0x06,0x07,0x2A,0x86,0x48,0xCE,0x3D,0x02,0x01,0x06,0x05,
+0x2B,0x81,0x04,0x00,0x22,0x03,0x62,0x00,0x04,0x0D,0x30,0x5E,0x1B,0x15,0x9D,0x03,
+0xD0,0xA1,0x79,0x35,0xB7,0x3A,0x3C,0x92,0x7A,0xCA,0x15,0x1C,0xCD,0x62,0xF3,0x9C,
+0x26,0x5C,0x07,0x3D,0xE5,0x54,0xFA,0xA3,0xD6,0xCC,0x12,0xEA,0xF4,0x14,0x5F,0xE8,
+0x8E,0x19,0xAB,0x2F,0x2E,0x48,0xE6,0xAC,0x18,0x43,0x78,0xAC,0xD0,0x37,0xC3,0xBD,
+0xB2,0xCD,0x2C,0xE6,0x47,0xE2,0x1A,0xE6,0x63,0xB8,0x3D,0x2E,0x2F,0x78,0xC4,0x4F,
+0xDB,0xF4,0x0F,0xA4,0x68,0x4C,0x55,0x72,0x6B,0x95,0x1D,0x4E,0x18,0x42,0x95,0x78,
+0xCC,0x37,0x3C,0x91,0xE2,0x9B,0x65,0x2B,0x29,0xA3,0x42,0x30,0x40,0x30,0x1D,0x06,
+0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x9A,0xAF,0x29,0x7A,0xC0,0x11,0x35,0x35,
+0x26,0x51,0x30,0x00,0xC3,0x6A,0xFE,0x40,0xD5,0xAE,0xD6,0x3C,0x30,0x0F,0x06,0x03,
+0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,
+0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0A,0x06,
+0x08,0x2A,0x86,0x48,0xCE,0x3D,0x04,0x03,0x03,0x03,0x67,0x00,0x30,0x64,0x02,0x30,
+0x17,0x09,0xF3,0x87,0x88,0x50,0x5A,0xAF,0xC8,0xC0,0x42,0xBF,0x47,0x5F,0xF5,0x6C,
+0x6A,0x86,0xE0,0xC4,0x27,0x74,0xE4,0x38,0x53,0xD7,0x05,0x7F,0x1B,0x34,0xE3,0xC6,
+0x2F,0xB3,0xCA,0x09,0x3C,0x37,0x9D,0xD7,0xE7,0xB8,0x46,0xF1,0xFD,0xA1,0xE2,0x71,
+0x02,0x30,0x42,0x59,0x87,0x43,0xD4,0x51,0xDF,0xBA,0xD3,0x09,0x32,0x5A,0xCE,0x88,
+0x7E,0x57,0x3D,0x9C,0x5F,0x42,0x6B,0xF5,0x07,0x2D,0xB5,0xF0,0x82,0x93,0xF9,0x59,
+0x6F,0xAE,0x64,0xFA,0x58,0xE5,0x8B,0x1E,0xE3,0x63,0xBE,0xB5,0x81,0xCD,0x6F,0x02,
+0x8C,0x79,
+};
+
+
+/* subject:/C=US/O=America Online Inc./CN=America Online Root Certification Authority 1 */
+/* issuer :/C=US/O=America Online Inc./CN=America Online Root Certification Authority 1 */
+
+
+const unsigned char America_Online_Root_Certification_Authority_1_certificate[936]={
+0x30,0x82,0x03,0xA4,0x30,0x82,0x02,0x8C,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,
+0x63,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x1C,
+0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x41,0x6D,0x65,0x72,0x69,0x63,0x61,
+0x20,0x4F,0x6E,0x6C,0x69,0x6E,0x65,0x20,0x49,0x6E,0x63,0x2E,0x31,0x36,0x30,0x34,
+0x06,0x03,0x55,0x04,0x03,0x13,0x2D,0x41,0x6D,0x65,0x72,0x69,0x63,0x61,0x20,0x4F,
+0x6E,0x6C,0x69,0x6E,0x65,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65,0x72,0x74,0x69,
+0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,
+0x74,0x79,0x20,0x31,0x30,0x1E,0x17,0x0D,0x30,0x32,0x30,0x35,0x32,0x38,0x30,0x36,
+0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x37,0x31,0x31,0x31,0x39,0x32,0x30,0x34,
+0x33,0x30,0x30,0x5A,0x30,0x63,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,
+0x02,0x55,0x53,0x31,0x1C,0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x41,0x6D,
+0x65,0x72,0x69,0x63,0x61,0x20,0x4F,0x6E,0x6C,0x69,0x6E,0x65,0x20,0x49,0x6E,0x63,
+0x2E,0x31,0x36,0x30,0x34,0x06,0x03,0x55,0x04,0x03,0x13,0x2D,0x41,0x6D,0x65,0x72,
+0x69,0x63,0x61,0x20,0x4F,0x6E,0x6C,0x69,0x6E,0x65,0x20,0x52,0x6F,0x6F,0x74,0x20,
+0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,
+0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x31,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,
+0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,
+0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xA8,0x2F,0xE8,0xA4,0x69,0x06,
+0x03,0x47,0xC3,0xE9,0x2A,0x98,0xFF,0x19,0xA2,0x70,0x9A,0xC6,0x50,0xB2,0x7E,0xA5,
+0xDF,0x68,0x4D,0x1B,0x7C,0x0F,0xB6,0x97,0x68,0x7D,0x2D,0xA6,0x8B,0x97,0xE9,0x64,
+0x86,0xC9,0xA3,0xEF,0xA0,0x86,0xBF,0x60,0x65,0x9C,0x4B,0x54,0x88,0xC2,0x48,0xC5,
+0x4A,0x39,0xBF,0x14,0xE3,0x59,0x55,0xE5,0x19,0xB4,0x74,0xC8,0xB4,0x05,0x39,0x5C,
+0x16,0xA5,0xE2,0x95,0x05,0xE0,0x12,0xAE,0x59,0x8B,0xA2,0x33,0x68,0x58,0x1C,0xA6,
+0xD4,0x15,0xB7,0xD8,0x9F,0xD7,0xDC,0x71,0xAB,0x7E,0x9A,0xBF,0x9B,0x8E,0x33,0x0F,
+0x22,0xFD,0x1F,0x2E,0xE7,0x07,0x36,0xEF,0x62,0x39,0xC5,0xDD,0xCB,0xBA,0x25,0x14,
+0x23,0xDE,0x0C,0xC6,0x3D,0x3C,0xCE,0x82,0x08,0xE6,0x66,0x3E,0xDA,0x51,0x3B,0x16,
+0x3A,0xA3,0x05,0x7F,0xA0,0xDC,0x87,0xD5,0x9C,0xFC,0x72,0xA9,0xA0,0x7D,0x78,0xE4,
+0xB7,0x31,0x55,0x1E,0x65,0xBB,0xD4,0x61,0xB0,0x21,0x60,0xED,0x10,0x32,0x72,0xC5,
+0x92,0x25,0x1E,0xF8,0x90,0x4A,0x18,0x78,0x47,0xDF,0x7E,0x30,0x37,0x3E,0x50,0x1B,
+0xDB,0x1C,0xD3,0x6B,0x9A,0x86,0x53,0x07,0xB0,0xEF,0xAC,0x06,0x78,0xF8,0x84,0x99,
+0xFE,0x21,0x8D,0x4C,0x80,0xB6,0x0C,0x82,0xF6,0x66,0x70,0x79,0x1A,0xD3,0x4F,0xA3,
+0xCF,0xF1,0xCF,0x46,0xB0,0x4B,0x0F,0x3E,0xDD,0x88,0x62,0xB8,0x8C,0xA9,0x09,0x28,
+0x3B,0x7A,0xC7,0x97,0xE1,0x1E,0xE5,0xF4,0x9F,0xC0,0xC0,0xAE,0x24,0xA0,0xC8,0xA1,
+0xD9,0x0F,0xD6,0x7B,0x26,0x82,0x69,0x32,0x3D,0xA7,0x02,0x03,0x01,0x00,0x01,0xA3,
+0x63,0x30,0x61,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,
+0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x00,
+0xAD,0xD9,0xA3,0xF6,0x79,0xF6,0x6E,0x74,0xA9,0x7F,0x33,0x3D,0x81,0x17,0xD7,0x4C,
+0xCF,0x33,0xDE,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,
+0x00,0xAD,0xD9,0xA3,0xF6,0x79,0xF6,0x6E,0x74,0xA9,0x7F,0x33,0x3D,0x81,0x17,0xD7,
+0x4C,0xCF,0x33,0xDE,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,
+0x03,0x02,0x01,0x86,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,
+0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x7C,0x8A,0xD1,0x1F,0x18,0x37,0x82,0xE0,
+0xB8,0xB0,0xA3,0xED,0x56,0x95,0xC8,0x62,0x61,0x9C,0x05,0xA2,0xCD,0xC2,0x62,0x26,
+0x61,0xCD,0x10,0x16,0xD7,0xCC,0xB4,0x65,0x34,0xD0,0x11,0x8A,0xAD,0xA8,0xA9,0x05,
+0x66,0xEF,0x74,0xF3,0x6D,0x5F,0x9D,0x99,0xAF,0xF6,0x8B,0xFB,0xEB,0x52,0xB2,0x05,
+0x98,0xA2,0x6F,0x2A,0xC5,0x54,0xBD,0x25,0xBD,0x5F,0xAE,0xC8,0x86,0xEA,0x46,0x2C,
+0xC1,0xB3,0xBD,0xC1,0xE9,0x49,0x70,0x18,0x16,0x97,0x08,0x13,0x8C,0x20,0xE0,0x1B,
+0x2E,0x3A,0x47,0xCB,0x1E,0xE4,0x00,0x30,0x95,0x5B,0xF4,0x45,0xA3,0xC0,0x1A,0xB0,
+0x01,0x4E,0xAB,0xBD,0xC0,0x23,0x6E,0x63,0x3F,0x80,0x4A,0xC5,0x07,0xED,0xDC,0xE2,
+0x6F,0xC7,0xC1,0x62,0xF1,0xE3,0x72,0xD6,0x04,0xC8,0x74,0x67,0x0B,0xFA,0x88,0xAB,
+0xA1,0x01,0xC8,0x6F,0xF0,0x14,0xAF,0xD2,0x99,0xCD,0x51,0x93,0x7E,0xED,0x2E,0x38,
+0xC7,0xBD,0xCE,0x46,0x50,0x3D,0x72,0xE3,0x79,0x25,0x9D,0x9B,0x88,0x2B,0x10,0x20,
+0xDD,0xA5,0xB8,0x32,0x9F,0x8D,0xE0,0x29,0xDF,0x21,0x74,0x86,0x82,0xDB,0x2F,0x82,
+0x30,0xC6,0xC7,0x35,0x86,0xB3,0xF9,0x96,0x5F,0x46,0xDB,0x0C,0x45,0xFD,0xF3,0x50,
+0xC3,0x6F,0xC6,0xC3,0x48,0xAD,0x46,0xA6,0xE1,0x27,0x47,0x0A,0x1D,0x0E,0x9B,0xB6,
+0xC2,0x77,0x7F,0x63,0xF2,0xE0,0x7D,0x1A,0xBE,0xFC,0xE0,0xDF,0xD7,0xC7,0xA7,0x6C,
+0xB0,0xF9,0xAE,0xBA,0x3C,0xFD,0x74,0xB4,0x11,0xE8,0x58,0x0D,0x80,0xBC,0xD3,0xA8,
+0x80,0x3A,0x99,0xED,0x75,0xCC,0x46,0x7B,
+};
+
+
+/* subject:/C=US/O=America Online Inc./CN=America Online Root Certification Authority 2 */
+/* issuer :/C=US/O=America Online Inc./CN=America Online Root Certification Authority 2 */
+
+
+const unsigned char America_Online_Root_Certification_Authority_2_certificate[1448]={
+0x30,0x82,0x05,0xA4,0x30,0x82,0x03,0x8C,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,
+0x63,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x1C,
+0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x41,0x6D,0x65,0x72,0x69,0x63,0x61,
+0x20,0x4F,0x6E,0x6C,0x69,0x6E,0x65,0x20,0x49,0x6E,0x63,0x2E,0x31,0x36,0x30,0x34,
+0x06,0x03,0x55,0x04,0x03,0x13,0x2D,0x41,0x6D,0x65,0x72,0x69,0x63,0x61,0x20,0x4F,
+0x6E,0x6C,0x69,0x6E,0x65,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65,0x72,0x74,0x69,
+0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,
+0x74,0x79,0x20,0x32,0x30,0x1E,0x17,0x0D,0x30,0x32,0x30,0x35,0x32,0x38,0x30,0x36,
+0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x37,0x30,0x39,0x32,0x39,0x31,0x34,0x30,
+0x38,0x30,0x30,0x5A,0x30,0x63,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,
+0x02,0x55,0x53,0x31,0x1C,0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x41,0x6D,
+0x65,0x72,0x69,0x63,0x61,0x20,0x4F,0x6E,0x6C,0x69,0x6E,0x65,0x20,0x49,0x6E,0x63,
+0x2E,0x31,0x36,0x30,0x34,0x06,0x03,0x55,0x04,0x03,0x13,0x2D,0x41,0x6D,0x65,0x72,
+0x69,0x63,0x61,0x20,0x4F,0x6E,0x6C,0x69,0x6E,0x65,0x20,0x52,0x6F,0x6F,0x74,0x20,
+0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,
+0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x32,0x30,0x82,0x02,0x22,0x30,0x0D,0x06,
+0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x02,0x0F,
+0x00,0x30,0x82,0x02,0x0A,0x02,0x82,0x02,0x01,0x00,0xCC,0x41,0x45,0x1D,0xE9,0x3D,
+0x4D,0x10,0xF6,0x8C,0xB1,0x41,0xC9,0xE0,0x5E,0xCB,0x0D,0xB7,0xBF,0x47,0x73,0xD3,
+0xF0,0x55,0x4D,0xDD,0xC6,0x0C,0xFA,0xB1,0x66,0x05,0x6A,0xCD,0x78,0xB4,0xDC,0x02,
+0xDB,0x4E,0x81,0xF3,0xD7,0xA7,0x7C,0x71,0xBC,0x75,0x63,0xA0,0x5D,0xE3,0x07,0x0C,
+0x48,0xEC,0x25,0xC4,0x03,0x20,0xF4,0xFF,0x0E,0x3B,0x12,0xFF,0x9B,0x8D,0xE1,0xC6,
+0xD5,0x1B,0xB4,0x6D,0x22,0xE3,0xB1,0xDB,0x7F,0x21,0x64,0xAF,0x86,0xBC,0x57,0x22,
+0x2A,0xD6,0x47,0x81,0x57,0x44,0x82,0x56,0x53,0xBD,0x86,0x14,0x01,0x0B,0xFC,0x7F,
+0x74,0xA4,0x5A,0xAE,0xF1,0xBA,0x11,0xB5,0x9B,0x58,0x5A,0x80,0xB4,0x37,0x78,0x09,
+0x33,0x7C,0x32,0x47,0x03,0x5C,0xC4,0xA5,0x83,0x48,0xF4,0x57,0x56,0x6E,0x81,0x36,
+0x27,0x18,0x4F,0xEC,0x9B,0x28,0xC2,0xD4,0xB4,0xD7,0x7C,0x0C,0x3E,0x0C,0x2B,0xDF,
+0xCA,0x04,0xD7,0xC6,0x8E,0xEA,0x58,0x4E,0xA8,0xA4,0xA5,0x18,0x1C,0x6C,0x45,0x98,
+0xA3,0x41,0xD1,0x2D,0xD2,0xC7,0x6D,0x8D,0x19,0xF1,0xAD,0x79,0xB7,0x81,0x3F,0xBD,
+0x06,0x82,0x27,0x2D,0x10,0x58,0x05,0xB5,0x78,0x05,0xB9,0x2F,0xDB,0x0C,0x6B,0x90,
+0x90,0x7E,0x14,0x59,0x38,0xBB,0x94,0x24,0x13,0xE5,0xD1,0x9D,0x14,0xDF,0xD3,0x82,
+0x4D,0x46,0xF0,0x80,0x39,0x52,0x32,0x0F,0xE3,0x84,0xB2,0x7A,0x43,0xF2,0x5E,0xDE,
+0x5F,0x3F,0x1D,0xDD,0xE3,0xB2,0x1B,0xA0,0xA1,0x2A,0x23,0x03,0x6E,0x2E,0x01,0x15,
+0x87,0x5C,0xA6,0x75,0x75,0xC7,0x97,0x61,0xBE,0xDE,0x86,0xDC,0xD4,0x48,0xDB,0xBD,
+0x2A,0xBF,0x4A,0x55,0xDA,0xE8,0x7D,0x50,0xFB,0xB4,0x80,0x17,0xB8,0x94,0xBF,0x01,
+0x3D,0xEA,0xDA,0xBA,0x7C,0xE0,0x58,0x67,0x17,0xB9,0x58,0xE0,0x88,0x86,0x46,0x67,
+0x6C,0x9D,0x10,0x47,0x58,0x32,0xD0,0x35,0x7C,0x79,0x2A,0x90,0xA2,0x5A,0x10,0x11,
+0x23,0x35,0xAD,0x2F,0xCC,0xE4,0x4A,0x5B,0xA7,0xC8,0x27,0xF2,0x83,0xDE,0x5E,0xBB,
+0x5E,0x77,0xE7,0xE8,0xA5,0x6E,0x63,0xC2,0x0D,0x5D,0x61,0xD0,0x8C,0xD2,0x6C,0x5A,
+0x21,0x0E,0xCA,0x28,0xA3,0xCE,0x2A,0xE9,0x95,0xC7,0x48,0xCF,0x96,0x6F,0x1D,0x92,
+0x25,0xC8,0xC6,0xC6,0xC1,0xC1,0x0C,0x05,0xAC,0x26,0xC4,0xD2,0x75,0xD2,0xE1,0x2A,
+0x67,0xC0,0x3D,0x5B,0xA5,0x9A,0xEB,0xCF,0x7B,0x1A,0xA8,0x9D,0x14,0x45,0xE5,0x0F,
+0xA0,0x9A,0x65,0xDE,0x2F,0x28,0xBD,0xCE,0x6F,0x94,0x66,0x83,0x48,0x29,0xD8,0xEA,
+0x65,0x8C,0xAF,0x93,0xD9,0x64,0x9F,0x55,0x57,0x26,0xBF,0x6F,0xCB,0x37,0x31,0x99,
+0xA3,0x60,0xBB,0x1C,0xAD,0x89,0x34,0x32,0x62,0xB8,0x43,0x21,0x06,0x72,0x0C,0xA1,
+0x5C,0x6D,0x46,0xC5,0xFA,0x29,0xCF,0x30,0xDE,0x89,0xDC,0x71,0x5B,0xDD,0xB6,0x37,
+0x3E,0xDF,0x50,0xF5,0xB8,0x07,0x25,0x26,0xE5,0xBC,0xB5,0xFE,0x3C,0x02,0xB3,0xB7,
+0xF8,0xBE,0x43,0xC1,0x87,0x11,0x94,0x9E,0x23,0x6C,0x17,0x8A,0xB8,0x8A,0x27,0x0C,
+0x54,0x47,0xF0,0xA9,0xB3,0xC0,0x80,0x8C,0xA0,0x27,0xEB,0x1D,0x19,0xE3,0x07,0x8E,
+0x77,0x70,0xCA,0x2B,0xF4,0x7D,0x76,0xE0,0x78,0x67,0x02,0x03,0x01,0x00,0x01,0xA3,
+0x63,0x30,0x61,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,
+0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x4D,
+0x45,0xC1,0x68,0x38,0xBB,0x73,0xA9,0x69,0xA1,0x20,0xE7,0xED,0xF5,0x22,0xA1,0x23,
+0x14,0xD7,0x9E,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,
+0x4D,0x45,0xC1,0x68,0x38,0xBB,0x73,0xA9,0x69,0xA1,0x20,0xE7,0xED,0xF5,0x22,0xA1,
+0x23,0x14,0xD7,0x9E,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,
+0x03,0x02,0x01,0x86,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,
+0x05,0x05,0x00,0x03,0x82,0x02,0x01,0x00,0x67,0x6B,0x06,0xB9,0x5F,0x45,0x3B,0x2A,
+0x4B,0x33,0xB3,0xE6,0x1B,0x6B,0x59,0x4E,0x22,0xCC,0xB9,0xB7,0xA4,0x25,0xC9,0xA7,
+0xC4,0xF0,0x54,0x96,0x0B,0x64,0xF3,0xB1,0x58,0x4F,0x5E,0x51,0xFC,0xB2,0x97,0x7B,
+0x27,0x65,0xC2,0xE5,0xCA,0xE7,0x0D,0x0C,0x25,0x7B,0x62,0xE3,0xFA,0x9F,0xB4,0x87,
+0xB7,0x45,0x46,0xAF,0x83,0xA5,0x97,0x48,0x8C,0xA5,0xBD,0xF1,0x16,0x2B,0x9B,0x76,
+0x2C,0x7A,0x35,0x60,0x6C,0x11,0x80,0x97,0xCC,0xA9,0x92,0x52,0xE6,0x2B,0xE6,0x69,
+0xED,0xA9,0xF8,0x36,0x2D,0x2C,0x77,0xBF,0x61,0x48,0xD1,0x63,0x0B,0xB9,0x5B,0x52,
+0xED,0x18,0xB0,0x43,0x42,0x22,0xA6,0xB1,0x77,0xAE,0xDE,0x69,0xC5,0xCD,0xC7,0x1C,
+0xA1,0xB1,0xA5,0x1C,0x10,0xFB,0x18,0xBE,0x1A,0x70,0xDD,0xC1,0x92,0x4B,0xBE,0x29,
+0x5A,0x9D,0x3F,0x35,0xBE,0xE5,0x7D,0x51,0xF8,0x55,0xE0,0x25,0x75,0x23,0x87,0x1E,
+0x5C,0xDC,0xBA,0x9D,0xB0,0xAC,0xB3,0x69,0xDB,0x17,0x83,0xC9,0xF7,0xDE,0x0C,0xBC,
+0x08,0xDC,0x91,0x9E,0xA8,0xD0,0xD7,0x15,0x37,0x73,0xA5,0x35,0xB8,0xFC,0x7E,0xC5,
+0x44,0x40,0x06,0xC3,0xEB,0xF8,0x22,0x80,0x5C,0x47,0xCE,0x02,0xE3,0x11,0x9F,0x44,
+0xFF,0xFD,0x9A,0x32,0xCC,0x7D,0x64,0x51,0x0E,0xEB,0x57,0x26,0x76,0x3A,0xE3,0x1E,
+0x22,0x3C,0xC2,0xA6,0x36,0xDD,0x19,0xEF,0xA7,0xFC,0x12,0xF3,0x26,0xC0,0x59,0x31,
+0x85,0x4C,0x9C,0xD8,0xCF,0xDF,0xA4,0xCC,0xCC,0x29,0x93,0xFF,0x94,0x6D,0x76,0x5C,
+0x13,0x08,0x97,0xF2,0xED,0xA5,0x0B,0x4D,0xDD,0xE8,0xC9,0x68,0x0E,0x66,0xD3,0x00,
+0x0E,0x33,0x12,0x5B,0xBC,0x95,0xE5,0x32,0x90,0xA8,0xB3,0xC6,0x6C,0x83,0xAD,0x77,
+0xEE,0x8B,0x7E,0x7E,0xB1,0xA9,0xAB,0xD3,0xE1,0xF1,0xB6,0xC0,0xB1,0xEA,0x88,0xC0,
+0xE7,0xD3,0x90,0xE9,0x28,0x92,0x94,0x7B,0x68,0x7B,0x97,0x2A,0x0A,0x67,0x2D,0x85,
+0x02,0x38,0x10,0xE4,0x03,0x61,0xD4,0xDA,0x25,0x36,0xC7,0x08,0x58,0x2D,0xA1,0xA7,
+0x51,0xAF,0x30,0x0A,0x49,0xF5,0xA6,0x69,0x87,0x07,0x2D,0x44,0x46,0x76,0x8E,0x2A,
+0xE5,0x9A,0x3B,0xD7,0x18,0xA2,0xFC,0x9C,0x38,0x10,0xCC,0xC6,0x3B,0xD2,0xB5,0x17,
+0x3A,0x6F,0xFD,0xAE,0x25,0xBD,0xF5,0x72,0x59,0x64,0xB1,0x74,0x2A,0x38,0x5F,0x18,
+0x4C,0xDF,0xCF,0x71,0x04,0x5A,0x36,0xD4,0xBF,0x2F,0x99,0x9C,0xE8,0xD9,0xBA,0xB1,
+0x95,0xE6,0x02,0x4B,0x21,0xA1,0x5B,0xD5,0xC1,0x4F,0x8F,0xAE,0x69,0x6D,0x53,0xDB,
+0x01,0x93,0xB5,0x5C,0x1E,0x18,0xDD,0x64,0x5A,0xCA,0x18,0x28,0x3E,0x63,0x04,0x11,
+0xFD,0x1C,0x8D,0x00,0x0F,0xB8,0x37,0xDF,0x67,0x8A,0x9D,0x66,0xA9,0x02,0x6A,0x91,
+0xFF,0x13,0xCA,0x2F,0x5D,0x83,0xBC,0x87,0x93,0x6C,0xDC,0x24,0x51,0x16,0x04,0x25,
+0x66,0xFA,0xB3,0xD9,0xC2,0xBA,0x29,0xBE,0x9A,0x48,0x38,0x82,0x99,0xF4,0xBF,0x3B,
+0x4A,0x31,0x19,0xF9,0xBF,0x8E,0x21,0x33,0x14,0xCA,0x4F,0x54,0x5F,0xFB,0xCE,0xFB,
+0x8F,0x71,0x7F,0xFD,0x5E,0x19,0xA0,0x0F,0x4B,0x91,0xB8,0xC4,0x54,0xBC,0x06,0xB0,
+0x45,0x8F,0x26,0x91,0xA2,0x8E,0xFE,0xA9,
+};
+
+
+/* subject:/C=IE/O=Baltimore/OU=CyberTrust/CN=Baltimore CyberTrust Root */
+/* issuer :/C=IE/O=Baltimore/OU=CyberTrust/CN=Baltimore CyberTrust Root */
+
+
+const unsigned char Baltimore_CyberTrust_Root_certificate[891]={
+0x30,0x82,0x03,0x77,0x30,0x82,0x02,0x5F,0xA0,0x03,0x02,0x01,0x02,0x02,0x04,0x02,
+0x00,0x00,0xB9,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,
+0x05,0x00,0x30,0x5A,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x49,
+0x45,0x31,0x12,0x30,0x10,0x06,0x03,0x55,0x04,0x0A,0x13,0x09,0x42,0x61,0x6C,0x74,
+0x69,0x6D,0x6F,0x72,0x65,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x0B,0x13,0x0A,
+0x43,0x79,0x62,0x65,0x72,0x54,0x72,0x75,0x73,0x74,0x31,0x22,0x30,0x20,0x06,0x03,
+0x55,0x04,0x03,0x13,0x19,0x42,0x61,0x6C,0x74,0x69,0x6D,0x6F,0x72,0x65,0x20,0x43,
+0x79,0x62,0x65,0x72,0x54,0x72,0x75,0x73,0x74,0x20,0x52,0x6F,0x6F,0x74,0x30,0x1E,
+0x17,0x0D,0x30,0x30,0x30,0x35,0x31,0x32,0x31,0x38,0x34,0x36,0x30,0x30,0x5A,0x17,
+0x0D,0x32,0x35,0x30,0x35,0x31,0x32,0x32,0x33,0x35,0x39,0x30,0x30,0x5A,0x30,0x5A,
+0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x49,0x45,0x31,0x12,0x30,
+0x10,0x06,0x03,0x55,0x04,0x0A,0x13,0x09,0x42,0x61,0x6C,0x74,0x69,0x6D,0x6F,0x72,
+0x65,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x0B,0x13,0x0A,0x43,0x79,0x62,0x65,
+0x72,0x54,0x72,0x75,0x73,0x74,0x31,0x22,0x30,0x20,0x06,0x03,0x55,0x04,0x03,0x13,
+0x19,0x42,0x61,0x6C,0x74,0x69,0x6D,0x6F,0x72,0x65,0x20,0x43,0x79,0x62,0x65,0x72,
+0x54,0x72,0x75,0x73,0x74,0x20,0x52,0x6F,0x6F,0x74,0x30,0x82,0x01,0x22,0x30,0x0D,
+0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,
+0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xA3,0x04,0xBB,0x22,0xAB,
+0x98,0x3D,0x57,0xE8,0x26,0x72,0x9A,0xB5,0x79,0xD4,0x29,0xE2,0xE1,0xE8,0x95,0x80,
+0xB1,0xB0,0xE3,0x5B,0x8E,0x2B,0x29,0x9A,0x64,0xDF,0xA1,0x5D,0xED,0xB0,0x09,0x05,
+0x6D,0xDB,0x28,0x2E,0xCE,0x62,0xA2,0x62,0xFE,0xB4,0x88,0xDA,0x12,0xEB,0x38,0xEB,
+0x21,0x9D,0xC0,0x41,0x2B,0x01,0x52,0x7B,0x88,0x77,0xD3,0x1C,0x8F,0xC7,0xBA,0xB9,
+0x88,0xB5,0x6A,0x09,0xE7,0x73,0xE8,0x11,0x40,0xA7,0xD1,0xCC,0xCA,0x62,0x8D,0x2D,
+0xE5,0x8F,0x0B,0xA6,0x50,0xD2,0xA8,0x50,0xC3,0x28,0xEA,0xF5,0xAB,0x25,0x87,0x8A,
+0x9A,0x96,0x1C,0xA9,0x67,0xB8,0x3F,0x0C,0xD5,0xF7,0xF9,0x52,0x13,0x2F,0xC2,0x1B,
+0xD5,0x70,0x70,0xF0,0x8F,0xC0,0x12,0xCA,0x06,0xCB,0x9A,0xE1,0xD9,0xCA,0x33,0x7A,
+0x77,0xD6,0xF8,0xEC,0xB9,0xF1,0x68,0x44,0x42,0x48,0x13,0xD2,0xC0,0xC2,0xA4,0xAE,
+0x5E,0x60,0xFE,0xB6,0xA6,0x05,0xFC,0xB4,0xDD,0x07,0x59,0x02,0xD4,0x59,0x18,0x98,
+0x63,0xF5,0xA5,0x63,0xE0,0x90,0x0C,0x7D,0x5D,0xB2,0x06,0x7A,0xF3,0x85,0xEA,0xEB,
+0xD4,0x03,0xAE,0x5E,0x84,0x3E,0x5F,0xFF,0x15,0xED,0x69,0xBC,0xF9,0x39,0x36,0x72,
+0x75,0xCF,0x77,0x52,0x4D,0xF3,0xC9,0x90,0x2C,0xB9,0x3D,0xE5,0xC9,0x23,0x53,0x3F,
+0x1F,0x24,0x98,0x21,0x5C,0x07,0x99,0x29,0xBD,0xC6,0x3A,0xEC,0xE7,0x6E,0x86,0x3A,
+0x6B,0x97,0x74,0x63,0x33,0xBD,0x68,0x18,0x31,0xF0,0x78,0x8D,0x76,0xBF,0xFC,0x9E,
+0x8E,0x5D,0x2A,0x86,0xA7,0x4D,0x90,0xDC,0x27,0x1A,0x39,0x02,0x03,0x01,0x00,0x01,
+0xA3,0x45,0x30,0x43,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xE5,
+0x9D,0x59,0x30,0x82,0x47,0x58,0xCC,0xAC,0xFA,0x08,0x54,0x36,0x86,0x7B,0x3A,0xB5,
+0x04,0x4D,0xF0,0x30,0x12,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x08,0x30,
+0x06,0x01,0x01,0xFF,0x02,0x01,0x03,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,
+0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,
+0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x85,0x0C,0x5D,0x8E,0xE4,
+0x6F,0x51,0x68,0x42,0x05,0xA0,0xDD,0xBB,0x4F,0x27,0x25,0x84,0x03,0xBD,0xF7,0x64,
+0xFD,0x2D,0xD7,0x30,0xE3,0xA4,0x10,0x17,0xEB,0xDA,0x29,0x29,0xB6,0x79,0x3F,0x76,
+0xF6,0x19,0x13,0x23,0xB8,0x10,0x0A,0xF9,0x58,0xA4,0xD4,0x61,0x70,0xBD,0x04,0x61,
+0x6A,0x12,0x8A,0x17,0xD5,0x0A,0xBD,0xC5,0xBC,0x30,0x7C,0xD6,0xE9,0x0C,0x25,0x8D,
+0x86,0x40,0x4F,0xEC,0xCC,0xA3,0x7E,0x38,0xC6,0x37,0x11,0x4F,0xED,0xDD,0x68,0x31,
+0x8E,0x4C,0xD2,0xB3,0x01,0x74,0xEE,0xBE,0x75,0x5E,0x07,0x48,0x1A,0x7F,0x70,0xFF,
+0x16,0x5C,0x84,0xC0,0x79,0x85,0xB8,0x05,0xFD,0x7F,0xBE,0x65,0x11,0xA3,0x0F,0xC0,
+0x02,0xB4,0xF8,0x52,0x37,0x39,0x04,0xD5,0xA9,0x31,0x7A,0x18,0xBF,0xA0,0x2A,0xF4,
+0x12,0x99,0xF7,0xA3,0x45,0x82,0xE3,0x3C,0x5E,0xF5,0x9D,0x9E,0xB5,0xC8,0x9E,0x7C,
+0x2E,0xC8,0xA4,0x9E,0x4E,0x08,0x14,0x4B,0x6D,0xFD,0x70,0x6D,0x6B,0x1A,0x63,0xBD,
+0x64,0xE6,0x1F,0xB7,0xCE,0xF0,0xF2,0x9F,0x2E,0xBB,0x1B,0xB7,0xF2,0x50,0x88,0x73,
+0x92,0xC2,0xE2,0xE3,0x16,0x8D,0x9A,0x32,0x02,0xAB,0x8E,0x18,0xDD,0xE9,0x10,0x11,
+0xEE,0x7E,0x35,0xAB,0x90,0xAF,0x3E,0x30,0x94,0x7A,0xD0,0x33,0x3D,0xA7,0x65,0x0F,
+0xF5,0xFC,0x8E,0x9E,0x62,0xCF,0x47,0x44,0x2C,0x01,0x5D,0xBB,0x1D,0xB5,0x32,0xD2,
+0x47,0xD2,0x38,0x2E,0xD0,0xFE,0x81,0xDC,0x32,0x6A,0x1E,0xB5,0xEE,0x3C,0xD5,0xFC,
+0xE7,0x81,0x1D,0x19,0xC3,0x24,0x42,0xEA,0x63,0x39,0xA9,
+};
+
+
+/* subject:/C=GB/ST=Greater Manchester/L=Salford/O=Comodo CA Limited/CN=AAA Certificate Services */
+/* issuer :/C=GB/ST=Greater Manchester/L=Salford/O=Comodo CA Limited/CN=AAA Certificate Services */
+
+
+const unsigned char Comodo_AAA_Services_root_certificate[1078]={
+0x30,0x82,0x04,0x32,0x30,0x82,0x03,0x1A,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,
+0x7B,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x47,0x42,0x31,0x1B,
+0x30,0x19,0x06,0x03,0x55,0x04,0x08,0x0C,0x12,0x47,0x72,0x65,0x61,0x74,0x65,0x72,
+0x20,0x4D,0x61,0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31,0x10,0x30,0x0E,0x06,
+0x03,0x55,0x04,0x07,0x0C,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72,0x64,0x31,0x1A,0x30,
+0x18,0x06,0x03,0x55,0x04,0x0A,0x0C,0x11,0x43,0x6F,0x6D,0x6F,0x64,0x6F,0x20,0x43,
+0x41,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,
+0x04,0x03,0x0C,0x18,0x41,0x41,0x41,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,
+0x61,0x74,0x65,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x30,0x1E,0x17,0x0D,
+0x30,0x34,0x30,0x31,0x30,0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,
+0x38,0x31,0x32,0x33,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x7B,0x31,0x0B,
+0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x47,0x42,0x31,0x1B,0x30,0x19,0x06,
+0x03,0x55,0x04,0x08,0x0C,0x12,0x47,0x72,0x65,0x61,0x74,0x65,0x72,0x20,0x4D,0x61,
+0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31,0x10,0x30,0x0E,0x06,0x03,0x55,0x04,
+0x07,0x0C,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72,0x64,0x31,0x1A,0x30,0x18,0x06,0x03,
+0x55,0x04,0x0A,0x0C,0x11,0x43,0x6F,0x6D,0x6F,0x64,0x6F,0x20,0x43,0x41,0x20,0x4C,
+0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x0C,
+0x18,0x41,0x41,0x41,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,
+0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,
+0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,
+0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xBE,0x40,0x9D,0xF4,0x6E,0xE1,
+0xEA,0x76,0x87,0x1C,0x4D,0x45,0x44,0x8E,0xBE,0x46,0xC8,0x83,0x06,0x9D,0xC1,0x2A,
+0xFE,0x18,0x1F,0x8E,0xE4,0x02,0xFA,0xF3,0xAB,0x5D,0x50,0x8A,0x16,0x31,0x0B,0x9A,
+0x06,0xD0,0xC5,0x70,0x22,0xCD,0x49,0x2D,0x54,0x63,0xCC,0xB6,0x6E,0x68,0x46,0x0B,
+0x53,0xEA,0xCB,0x4C,0x24,0xC0,0xBC,0x72,0x4E,0xEA,0xF1,0x15,0xAE,0xF4,0x54,0x9A,
+0x12,0x0A,0xC3,0x7A,0xB2,0x33,0x60,0xE2,0xDA,0x89,0x55,0xF3,0x22,0x58,0xF3,0xDE,
+0xDC,0xCF,0xEF,0x83,0x86,0xA2,0x8C,0x94,0x4F,0x9F,0x68,0xF2,0x98,0x90,0x46,0x84,
+0x27,0xC7,0x76,0xBF,0xE3,0xCC,0x35,0x2C,0x8B,0x5E,0x07,0x64,0x65,0x82,0xC0,0x48,
+0xB0,0xA8,0x91,0xF9,0x61,0x9F,0x76,0x20,0x50,0xA8,0x91,0xC7,0x66,0xB5,0xEB,0x78,
+0x62,0x03,0x56,0xF0,0x8A,0x1A,0x13,0xEA,0x31,0xA3,0x1E,0xA0,0x99,0xFD,0x38,0xF6,
+0xF6,0x27,0x32,0x58,0x6F,0x07,0xF5,0x6B,0xB8,0xFB,0x14,0x2B,0xAF,0xB7,0xAA,0xCC,
+0xD6,0x63,0x5F,0x73,0x8C,0xDA,0x05,0x99,0xA8,0x38,0xA8,0xCB,0x17,0x78,0x36,0x51,
+0xAC,0xE9,0x9E,0xF4,0x78,0x3A,0x8D,0xCF,0x0F,0xD9,0x42,0xE2,0x98,0x0C,0xAB,0x2F,
+0x9F,0x0E,0x01,0xDE,0xEF,0x9F,0x99,0x49,0xF1,0x2D,0xDF,0xAC,0x74,0x4D,0x1B,0x98,
+0xB5,0x47,0xC5,0xE5,0x29,0xD1,0xF9,0x90,0x18,0xC7,0x62,0x9C,0xBE,0x83,0xC7,0x26,
+0x7B,0x3E,0x8A,0x25,0xC7,0xC0,0xDD,0x9D,0xE6,0x35,0x68,0x10,0x20,0x9D,0x8F,0xD8,
+0xDE,0xD2,0xC3,0x84,0x9C,0x0D,0x5E,0xE8,0x2F,0xC9,0x02,0x03,0x01,0x00,0x01,0xA3,
+0x81,0xC0,0x30,0x81,0xBD,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,
+0xA0,0x11,0x0A,0x23,0x3E,0x96,0xF1,0x07,0xEC,0xE2,0xAF,0x29,0xEF,0x82,0xA5,0x7F,
+0xD0,0x30,0xA4,0xB4,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,
+0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,
+0x30,0x03,0x01,0x01,0xFF,0x30,0x7B,0x06,0x03,0x55,0x1D,0x1F,0x04,0x74,0x30,0x72,
+0x30,0x38,0xA0,0x36,0xA0,0x34,0x86,0x32,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,
+0x72,0x6C,0x2E,0x63,0x6F,0x6D,0x6F,0x64,0x6F,0x63,0x61,0x2E,0x63,0x6F,0x6D,0x2F,
+0x41,0x41,0x41,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x53,0x65,
+0x72,0x76,0x69,0x63,0x65,0x73,0x2E,0x63,0x72,0x6C,0x30,0x36,0xA0,0x34,0xA0,0x32,
+0x86,0x30,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E,0x63,0x6F,0x6D,
+0x6F,0x64,0x6F,0x2E,0x6E,0x65,0x74,0x2F,0x41,0x41,0x41,0x43,0x65,0x72,0x74,0x69,
+0x66,0x69,0x63,0x61,0x74,0x65,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x2E,0x63,
+0x72,0x6C,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,
+0x00,0x03,0x82,0x01,0x01,0x00,0x08,0x56,0xFC,0x02,0xF0,0x9B,0xE8,0xFF,0xA4,0xFA,
+0xD6,0x7B,0xC6,0x44,0x80,0xCE,0x4F,0xC4,0xC5,0xF6,0x00,0x58,0xCC,0xA6,0xB6,0xBC,
+0x14,0x49,0x68,0x04,0x76,0xE8,0xE6,0xEE,0x5D,0xEC,0x02,0x0F,0x60,0xD6,0x8D,0x50,
+0x18,0x4F,0x26,0x4E,0x01,0xE3,0xE6,0xB0,0xA5,0xEE,0xBF,0xBC,0x74,0x54,0x41,0xBF,
+0xFD,0xFC,0x12,0xB8,0xC7,0x4F,0x5A,0xF4,0x89,0x60,0x05,0x7F,0x60,0xB7,0x05,0x4A,
+0xF3,0xF6,0xF1,0xC2,0xBF,0xC4,0xB9,0x74,0x86,0xB6,0x2D,0x7D,0x6B,0xCC,0xD2,0xF3,
+0x46,0xDD,0x2F,0xC6,0xE0,0x6A,0xC3,0xC3,0x34,0x03,0x2C,0x7D,0x96,0xDD,0x5A,0xC2,
+0x0E,0xA7,0x0A,0x99,0xC1,0x05,0x8B,0xAB,0x0C,0x2F,0xF3,0x5C,0x3A,0xCF,0x6C,0x37,
+0x55,0x09,0x87,0xDE,0x53,0x40,0x6C,0x58,0xEF,0xFC,0xB6,0xAB,0x65,0x6E,0x04,0xF6,
+0x1B,0xDC,0x3C,0xE0,0x5A,0x15,0xC6,0x9E,0xD9,0xF1,0x59,0x48,0x30,0x21,0x65,0x03,
+0x6C,0xEC,0xE9,0x21,0x73,0xEC,0x9B,0x03,0xA1,0xE0,0x37,0xAD,0xA0,0x15,0x18,0x8F,
+0xFA,0xBA,0x02,0xCE,0xA7,0x2C,0xA9,0x10,0x13,0x2C,0xD4,0xE5,0x08,0x26,0xAB,0x22,
+0x97,0x60,0xF8,0x90,0x5E,0x74,0xD4,0xA2,0x9A,0x53,0xBD,0xF2,0xA9,0x68,0xE0,0xA2,
+0x6E,0xC2,0xD7,0x6C,0xB1,0xA3,0x0F,0x9E,0xBF,0xEB,0x68,0xE7,0x56,0xF2,0xAE,0xF2,
+0xE3,0x2B,0x38,0x3A,0x09,0x81,0xB5,0x6B,0x85,0xD7,0xBE,0x2D,0xED,0x3F,0x1A,0xB7,
+0xB2,0x63,0xE2,0xF5,0x62,0x2C,0x82,0xD4,0x6A,0x00,0x41,0x50,0xF1,0x39,0x83,0x9F,
+0x95,0xE9,0x36,0x96,0x98,0x6E,
+};
+
+
+/* subject:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO Certification Authority */
+/* issuer :/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO Certification Authority */
+
+
+const unsigned char COMODO_Certification_Authority_certificate[1057]={
+0x30,0x82,0x04,0x1D,0x30,0x82,0x03,0x05,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x4E,
+0x81,0x2D,0x8A,0x82,0x65,0xE0,0x0B,0x02,0xEE,0x3E,0x35,0x02,0x46,0xE5,0x3D,0x30,
+0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81,
+0x81,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x47,0x42,0x31,0x1B,
+0x30,0x19,0x06,0x03,0x55,0x04,0x08,0x13,0x12,0x47,0x72,0x65,0x61,0x74,0x65,0x72,
+0x20,0x4D,0x61,0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31,0x10,0x30,0x0E,0x06,
+0x03,0x55,0x04,0x07,0x13,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72,0x64,0x31,0x1A,0x30,
+0x18,0x06,0x03,0x55,0x04,0x0A,0x13,0x11,0x43,0x4F,0x4D,0x4F,0x44,0x4F,0x20,0x43,
+0x41,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x27,0x30,0x25,0x06,0x03,0x55,
+0x04,0x03,0x13,0x1E,0x43,0x4F,0x4D,0x4F,0x44,0x4F,0x20,0x43,0x65,0x72,0x74,0x69,
+0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,
+0x74,0x79,0x30,0x1E,0x17,0x0D,0x30,0x36,0x31,0x32,0x30,0x31,0x30,0x30,0x30,0x30,
+0x30,0x30,0x5A,0x17,0x0D,0x32,0x39,0x31,0x32,0x33,0x31,0x32,0x33,0x35,0x39,0x35,
+0x39,0x5A,0x30,0x81,0x81,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,
+0x47,0x42,0x31,0x1B,0x30,0x19,0x06,0x03,0x55,0x04,0x08,0x13,0x12,0x47,0x72,0x65,
+0x61,0x74,0x65,0x72,0x20,0x4D,0x61,0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31,
+0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x07,0x13,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72,
+0x64,0x31,0x1A,0x30,0x18,0x06,0x03,0x55,0x04,0x0A,0x13,0x11,0x43,0x4F,0x4D,0x4F,
+0x44,0x4F,0x20,0x43,0x41,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x27,0x30,
+0x25,0x06,0x03,0x55,0x04,0x03,0x13,0x1E,0x43,0x4F,0x4D,0x4F,0x44,0x4F,0x20,0x43,
+0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,
+0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,
+0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,
+0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xD0,0x40,0x8B,0x8B,0x72,0xE3,0x91,0x1B,0xF7,
+0x51,0xC1,0x1B,0x54,0x04,0x98,0xD3,0xA9,0xBF,0xC1,0xE6,0x8A,0x5D,0x3B,0x87,0xFB,
+0xBB,0x88,0xCE,0x0D,0xE3,0x2F,0x3F,0x06,0x96,0xF0,0xA2,0x29,0x50,0x99,0xAE,0xDB,
+0x3B,0xA1,0x57,0xB0,0x74,0x51,0x71,0xCD,0xED,0x42,0x91,0x4D,0x41,0xFE,0xA9,0xC8,
+0xD8,0x6A,0x86,0x77,0x44,0xBB,0x59,0x66,0x97,0x50,0x5E,0xB4,0xD4,0x2C,0x70,0x44,
+0xCF,0xDA,0x37,0x95,0x42,0x69,0x3C,0x30,0xC4,0x71,0xB3,0x52,0xF0,0x21,0x4D,0xA1,
+0xD8,0xBA,0x39,0x7C,0x1C,0x9E,0xA3,0x24,0x9D,0xF2,0x83,0x16,0x98,0xAA,0x16,0x7C,
+0x43,0x9B,0x15,0x5B,0xB7,0xAE,0x34,0x91,0xFE,0xD4,0x62,0x26,0x18,0x46,0x9A,0x3F,
+0xEB,0xC1,0xF9,0xF1,0x90,0x57,0xEB,0xAC,0x7A,0x0D,0x8B,0xDB,0x72,0x30,0x6A,0x66,
+0xD5,0xE0,0x46,0xA3,0x70,0xDC,0x68,0xD9,0xFF,0x04,0x48,0x89,0x77,0xDE,0xB5,0xE9,
+0xFB,0x67,0x6D,0x41,0xE9,0xBC,0x39,0xBD,0x32,0xD9,0x62,0x02,0xF1,0xB1,0xA8,0x3D,
+0x6E,0x37,0x9C,0xE2,0x2F,0xE2,0xD3,0xA2,0x26,0x8B,0xC6,0xB8,0x55,0x43,0x88,0xE1,
+0x23,0x3E,0xA5,0xD2,0x24,0x39,0x6A,0x47,0xAB,0x00,0xD4,0xA1,0xB3,0xA9,0x25,0xFE,
+0x0D,0x3F,0xA7,0x1D,0xBA,0xD3,0x51,0xC1,0x0B,0xA4,0xDA,0xAC,0x38,0xEF,0x55,0x50,
+0x24,0x05,0x65,0x46,0x93,0x34,0x4F,0x2D,0x8D,0xAD,0xC6,0xD4,0x21,0x19,0xD2,0x8E,
+0xCA,0x05,0x61,0x71,0x07,0x73,0x47,0xE5,0x8A,0x19,0x12,0xBD,0x04,0x4D,0xCE,0x4E,
+0x9C,0xA5,0x48,0xAC,0xBB,0x26,0xF7,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0x8E,0x30,
+0x81,0x8B,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x0B,0x58,0xE5,
+0x8B,0xC6,0x4C,0x15,0x37,0xA4,0x40,0xA9,0x30,0xA9,0x21,0xBE,0x47,0x36,0x5A,0x56,
+0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,
+0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,
+0x01,0xFF,0x30,0x49,0x06,0x03,0x55,0x1D,0x1F,0x04,0x42,0x30,0x40,0x30,0x3E,0xA0,
+0x3C,0xA0,0x3A,0x86,0x38,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E,
+0x63,0x6F,0x6D,0x6F,0x64,0x6F,0x63,0x61,0x2E,0x63,0x6F,0x6D,0x2F,0x43,0x4F,0x4D,
+0x4F,0x44,0x4F,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,
+0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x2E,0x63,0x72,0x6C,0x30,0x0D,0x06,
+0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,
+0x00,0x3E,0x98,0x9E,0x9B,0xF6,0x1B,0xE9,0xD7,0x39,0xB7,0x78,0xAE,0x1D,0x72,0x18,
+0x49,0xD3,0x87,0xE4,0x43,0x82,0xEB,0x3F,0xC9,0xAA,0xF5,0xA8,0xB5,0xEF,0x55,0x7C,
+0x21,0x52,0x65,0xF9,0xD5,0x0D,0xE1,0x6C,0xF4,0x3E,0x8C,0x93,0x73,0x91,0x2E,0x02,
+0xC4,0x4E,0x07,0x71,0x6F,0xC0,0x8F,0x38,0x61,0x08,0xA8,0x1E,0x81,0x0A,0xC0,0x2F,
+0x20,0x2F,0x41,0x8B,0x91,0xDC,0x48,0x45,0xBC,0xF1,0xC6,0xDE,0xBA,0x76,0x6B,0x33,
+0xC8,0x00,0x2D,0x31,0x46,0x4C,0xED,0xE7,0x9D,0xCF,0x88,0x94,0xFF,0x33,0xC0,0x56,
+0xE8,0x24,0x86,0x26,0xB8,0xD8,0x38,0x38,0xDF,0x2A,0x6B,0xDD,0x12,0xCC,0xC7,0x3F,
+0x47,0x17,0x4C,0xA2,0xC2,0x06,0x96,0x09,0xD6,0xDB,0xFE,0x3F,0x3C,0x46,0x41,0xDF,
+0x58,0xE2,0x56,0x0F,0x3C,0x3B,0xC1,0x1C,0x93,0x35,0xD9,0x38,0x52,0xAC,0xEE,0xC8,
+0xEC,0x2E,0x30,0x4E,0x94,0x35,0xB4,0x24,0x1F,0x4B,0x78,0x69,0xDA,0xF2,0x02,0x38,
+0xCC,0x95,0x52,0x93,0xF0,0x70,0x25,0x59,0x9C,0x20,0x67,0xC4,0xEE,0xF9,0x8B,0x57,
+0x61,0xF4,0x92,0x76,0x7D,0x3F,0x84,0x8D,0x55,0xB7,0xE8,0xE5,0xAC,0xD5,0xF1,0xF5,
+0x19,0x56,0xA6,0x5A,0xFB,0x90,0x1C,0xAF,0x93,0xEB,0xE5,0x1C,0xD4,0x67,0x97,0x5D,
+0x04,0x0E,0xBE,0x0B,0x83,0xA6,0x17,0x83,0xB9,0x30,0x12,0xA0,0xC5,0x33,0x15,0x05,
+0xB9,0x0D,0xFB,0xC7,0x05,0x76,0xE3,0xD8,0x4A,0x8D,0xFC,0x34,0x17,0xA3,0xC6,0x21,
+0x28,0xBE,0x30,0x45,0x31,0x1E,0xC7,0x78,0xBE,0x58,0x61,0x38,0xAC,0x3B,0xE2,0x01,
+0x65,
+};
+
+
+/* subject:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO ECC Certification Authority */
+/* issuer :/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO ECC Certification Authority */
+
+
+const unsigned char COMODO_ECC_Certification_Authority_certificate[653]={
+0x30,0x82,0x02,0x89,0x30,0x82,0x02,0x0F,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x1F,
+0x47,0xAF,0xAA,0x62,0x00,0x70,0x50,0x54,0x4C,0x01,0x9E,0x9B,0x63,0x99,0x2A,0x30,
+0x0A,0x06,0x08,0x2A,0x86,0x48,0xCE,0x3D,0x04,0x03,0x03,0x30,0x81,0x85,0x31,0x0B,
+0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x47,0x42,0x31,0x1B,0x30,0x19,0x06,
+0x03,0x55,0x04,0x08,0x13,0x12,0x47,0x72,0x65,0x61,0x74,0x65,0x72,0x20,0x4D,0x61,
+0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31,0x10,0x30,0x0E,0x06,0x03,0x55,0x04,
+0x07,0x13,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72,0x64,0x31,0x1A,0x30,0x18,0x06,0x03,
+0x55,0x04,0x0A,0x13,0x11,0x43,0x4F,0x4D,0x4F,0x44,0x4F,0x20,0x43,0x41,0x20,0x4C,
+0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x2B,0x30,0x29,0x06,0x03,0x55,0x04,0x03,0x13,
+0x22,0x43,0x4F,0x4D,0x4F,0x44,0x4F,0x20,0x45,0x43,0x43,0x20,0x43,0x65,0x72,0x74,
+0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,
+0x69,0x74,0x79,0x30,0x1E,0x17,0x0D,0x30,0x38,0x30,0x33,0x30,0x36,0x30,0x30,0x30,
+0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x38,0x30,0x31,0x31,0x38,0x32,0x33,0x35,0x39,
+0x35,0x39,0x5A,0x30,0x81,0x85,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,
+0x02,0x47,0x42,0x31,0x1B,0x30,0x19,0x06,0x03,0x55,0x04,0x08,0x13,0x12,0x47,0x72,
+0x65,0x61,0x74,0x65,0x72,0x20,0x4D,0x61,0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,
+0x31,0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x07,0x13,0x07,0x53,0x61,0x6C,0x66,0x6F,
+0x72,0x64,0x31,0x1A,0x30,0x18,0x06,0x03,0x55,0x04,0x0A,0x13,0x11,0x43,0x4F,0x4D,
+0x4F,0x44,0x4F,0x20,0x43,0x41,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x2B,
+0x30,0x29,0x06,0x03,0x55,0x04,0x03,0x13,0x22,0x43,0x4F,0x4D,0x4F,0x44,0x4F,0x20,
+0x45,0x43,0x43,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,
+0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x76,0x30,0x10,0x06,
+0x07,0x2A,0x86,0x48,0xCE,0x3D,0x02,0x01,0x06,0x05,0x2B,0x81,0x04,0x00,0x22,0x03,
+0x62,0x00,0x04,0x03,0x47,0x7B,0x2F,0x75,0xC9,0x82,0x15,0x85,0xFB,0x75,0xE4,0x91,
+0x16,0xD4,0xAB,0x62,0x99,0xF5,0x3E,0x52,0x0B,0x06,0xCE,0x41,0x00,0x7F,0x97,0xE1,
+0x0A,0x24,0x3C,0x1D,0x01,0x04,0xEE,0x3D,0xD2,0x8D,0x09,0x97,0x0C,0xE0,0x75,0xE4,
+0xFA,0xFB,0x77,0x8A,0x2A,0xF5,0x03,0x60,0x4B,0x36,0x8B,0x16,0x23,0x16,0xAD,0x09,
+0x71,0xF4,0x4A,0xF4,0x28,0x50,0xB4,0xFE,0x88,0x1C,0x6E,0x3F,0x6C,0x2F,0x2F,0x09,
+0x59,0x5B,0xA5,0x5B,0x0B,0x33,0x99,0xE2,0xC3,0x3D,0x89,0xF9,0x6A,0x2C,0xEF,0xB2,
+0xD3,0x06,0xE9,0xA3,0x42,0x30,0x40,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,
+0x04,0x14,0x75,0x71,0xA7,0x19,0x48,0x19,0xBC,0x9D,0x9D,0xEA,0x41,0x47,0xDF,0x94,
+0xC4,0x48,0x77,0x99,0xD3,0x79,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,
+0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,
+0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0A,0x06,0x08,0x2A,0x86,0x48,0xCE,0x3D,
+0x04,0x03,0x03,0x03,0x68,0x00,0x30,0x65,0x02,0x31,0x00,0xEF,0x03,0x5B,0x7A,0xAC,
+0xB7,0x78,0x0A,0x72,0xB7,0x88,0xDF,0xFF,0xB5,0x46,0x14,0x09,0x0A,0xFA,0xA0,0xE6,
+0x7D,0x08,0xC6,0x1A,0x87,0xBD,0x18,0xA8,0x73,0xBD,0x26,0xCA,0x60,0x0C,0x9D,0xCE,
+0x99,0x9F,0xCF,0x5C,0x0F,0x30,0xE1,0xBE,0x14,0x31,0xEA,0x02,0x30,0x14,0xF4,0x93,
+0x3C,0x49,0xA7,0x33,0x7A,0x90,0x46,0x47,0xB3,0x63,0x7D,0x13,0x9B,0x4E,0xB7,0x6F,
+0x18,0x37,0x80,0x53,0xFE,0xDD,0x20,0xE0,0x35,0x9A,0x36,0xD1,0xC7,0x01,0xB9,0xE6,
+0xDC,0xDD,0xF3,0xFF,0x1D,0x2C,0x3A,0x16,0x57,0xD9,0x92,0x39,0xD6,
+};
+
+
+/* subject:/C=GB/ST=Greater Manchester/L=Salford/O=Comodo CA Limited/CN=Secure Certificate Services */
+/* issuer :/C=GB/ST=Greater Manchester/L=Salford/O=Comodo CA Limited/CN=Secure Certificate Services */
+
+
+const unsigned char Comodo_Secure_Services_root_certificate[1091]={
+0x30,0x82,0x04,0x3F,0x30,0x82,0x03,0x27,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,
+0x7E,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x47,0x42,0x31,0x1B,
+0x30,0x19,0x06,0x03,0x55,0x04,0x08,0x0C,0x12,0x47,0x72,0x65,0x61,0x74,0x65,0x72,
+0x20,0x4D,0x61,0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31,0x10,0x30,0x0E,0x06,
+0x03,0x55,0x04,0x07,0x0C,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72,0x64,0x31,0x1A,0x30,
+0x18,0x06,0x03,0x55,0x04,0x0A,0x0C,0x11,0x43,0x6F,0x6D,0x6F,0x64,0x6F,0x20,0x43,
+0x41,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x24,0x30,0x22,0x06,0x03,0x55,
+0x04,0x03,0x0C,0x1B,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x43,0x65,0x72,0x74,0x69,
+0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x30,
+0x1E,0x17,0x0D,0x30,0x34,0x30,0x31,0x30,0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,
+0x17,0x0D,0x32,0x38,0x31,0x32,0x33,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,
+0x7E,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x47,0x42,0x31,0x1B,
+0x30,0x19,0x06,0x03,0x55,0x04,0x08,0x0C,0x12,0x47,0x72,0x65,0x61,0x74,0x65,0x72,
+0x20,0x4D,0x61,0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31,0x10,0x30,0x0E,0x06,
+0x03,0x55,0x04,0x07,0x0C,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72,0x64,0x31,0x1A,0x30,
+0x18,0x06,0x03,0x55,0x04,0x0A,0x0C,0x11,0x43,0x6F,0x6D,0x6F,0x64,0x6F,0x20,0x43,
+0x41,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x24,0x30,0x22,0x06,0x03,0x55,
+0x04,0x03,0x0C,0x1B,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x43,0x65,0x72,0x74,0x69,
+0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x30,
+0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,
+0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,
+0xC0,0x71,0x33,0x82,0x8A,0xD0,0x70,0xEB,0x73,0x87,0x82,0x40,0xD5,0x1D,0xE4,0xCB,
+0xC9,0x0E,0x42,0x90,0xF9,0xDE,0x34,0xB9,0xA1,0xBA,0x11,0xF4,0x25,0x85,0xF3,0xCC,
+0x72,0x6D,0xF2,0x7B,0x97,0x6B,0xB3,0x07,0xF1,0x77,0x24,0x91,0x5F,0x25,0x8F,0xF6,
+0x74,0x3D,0xE4,0x80,0xC2,0xF8,0x3C,0x0D,0xF3,0xBF,0x40,0xEA,0xF7,0xC8,0x52,0xD1,
+0x72,0x6F,0xEF,0xC8,0xAB,0x41,0xB8,0x6E,0x2E,0x17,0x2A,0x95,0x69,0x0C,0xCD,0xD2,
+0x1E,0x94,0x7B,0x2D,0x94,0x1D,0xAA,0x75,0xD7,0xB3,0x98,0xCB,0xAC,0xBC,0x64,0x53,
+0x40,0xBC,0x8F,0xAC,0xAC,0x36,0xCB,0x5C,0xAD,0xBB,0xDD,0xE0,0x94,0x17,0xEC,0xD1,
+0x5C,0xD0,0xBF,0xEF,0xA5,0x95,0xC9,0x90,0xC5,0xB0,0xAC,0xFB,0x1B,0x43,0xDF,0x7A,
+0x08,0x5D,0xB7,0xB8,0xF2,0x40,0x1B,0x2B,0x27,0x9E,0x50,0xCE,0x5E,0x65,0x82,0x88,
+0x8C,0x5E,0xD3,0x4E,0x0C,0x7A,0xEA,0x08,0x91,0xB6,0x36,0xAA,0x2B,0x42,0xFB,0xEA,
+0xC2,0xA3,0x39,0xE5,0xDB,0x26,0x38,0xAD,0x8B,0x0A,0xEE,0x19,0x63,0xC7,0x1C,0x24,
+0xDF,0x03,0x78,0xDA,0xE6,0xEA,0xC1,0x47,0x1A,0x0B,0x0B,0x46,0x09,0xDD,0x02,0xFC,
+0xDE,0xCB,0x87,0x5F,0xD7,0x30,0x63,0x68,0xA1,0xAE,0xDC,0x32,0xA1,0xBA,0xBE,0xFE,
+0x44,0xAB,0x68,0xB6,0xA5,0x17,0x15,0xFD,0xBD,0xD5,0xA7,0xA7,0x9A,0xE4,0x44,0x33,
+0xE9,0x88,0x8E,0xFC,0xED,0x51,0xEB,0x93,0x71,0x4E,0xAD,0x01,0xE7,0x44,0x8E,0xAB,
+0x2D,0xCB,0xA8,0xFE,0x01,0x49,0x48,0xF0,0xC0,0xDD,0xC7,0x68,0xD8,0x92,0xFE,0x3D,
+0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0xC7,0x30,0x81,0xC4,0x30,0x1D,0x06,0x03,0x55,
+0x1D,0x0E,0x04,0x16,0x04,0x14,0x3C,0xD8,0x93,0x88,0xC2,0xC0,0x82,0x09,0xCC,0x01,
+0x99,0x06,0x93,0x20,0xE9,0x9E,0x70,0x09,0x63,0x4F,0x30,0x0E,0x06,0x03,0x55,0x1D,
+0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,
+0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x81,0x81,0x06,0x03,
+0x55,0x1D,0x1F,0x04,0x7A,0x30,0x78,0x30,0x3B,0xA0,0x39,0xA0,0x37,0x86,0x35,0x68,
+0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E,0x63,0x6F,0x6D,0x6F,0x64,0x6F,
+0x63,0x61,0x2E,0x63,0x6F,0x6D,0x2F,0x53,0x65,0x63,0x75,0x72,0x65,0x43,0x65,0x72,
+0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,
+0x2E,0x63,0x72,0x6C,0x30,0x39,0xA0,0x37,0xA0,0x35,0x86,0x33,0x68,0x74,0x74,0x70,
+0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E,0x63,0x6F,0x6D,0x6F,0x64,0x6F,0x2E,0x6E,0x65,
+0x74,0x2F,0x53,0x65,0x63,0x75,0x72,0x65,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,
+0x61,0x74,0x65,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x2E,0x63,0x72,0x6C,0x30,
+0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,
+0x01,0x01,0x00,0x87,0x01,0x6D,0x23,0x1D,0x7E,0x5B,0x17,0x7D,0xC1,0x61,0x32,0xCF,
+0x8F,0xE7,0xF3,0x8A,0x94,0x59,0x66,0xE0,0x9E,0x28,0xA8,0x5E,0xD3,0xB7,0xF4,0x34,
+0xE6,0xAA,0x39,0xB2,0x97,0x16,0xC5,0x82,0x6F,0x32,0xA4,0xE9,0x8C,0xE7,0xAF,0xFD,
+0xEF,0xC2,0xE8,0xB9,0x4B,0xAA,0xA3,0xF4,0xE6,0xDA,0x8D,0x65,0x21,0xFB,0xBA,0x80,
+0xEB,0x26,0x28,0x85,0x1A,0xFE,0x39,0x8C,0xDE,0x5B,0x04,0x04,0xB4,0x54,0xF9,0xA3,
+0x67,0x9E,0x41,0xFA,0x09,0x52,0xCC,0x05,0x48,0xA8,0xC9,0x3F,0x21,0x04,0x1E,0xCE,
+0x48,0x6B,0xFC,0x85,0xE8,0xC2,0x7B,0xAF,0x7F,0xB7,0xCC,0xF8,0x5F,0x3A,0xFD,0x35,
+0xC6,0x0D,0xEF,0x97,0xDC,0x4C,0xAB,0x11,0xE1,0x6B,0xCB,0x31,0xD1,0x6C,0xFB,0x48,
+0x80,0xAB,0xDC,0x9C,0x37,0xB8,0x21,0x14,0x4B,0x0D,0x71,0x3D,0xEC,0x83,0x33,0x6E,
+0xD1,0x6E,0x32,0x16,0xEC,0x98,0xC7,0x16,0x8B,0x59,0xA6,0x34,0xAB,0x05,0x57,0x2D,
+0x93,0xF7,0xAA,0x13,0xCB,0xD2,0x13,0xE2,0xB7,0x2E,0x3B,0xCD,0x6B,0x50,0x17,0x09,
+0x68,0x3E,0xB5,0x26,0x57,0xEE,0xB6,0xE0,0xB6,0xDD,0xB9,0x29,0x80,0x79,0x7D,0x8F,
+0xA3,0xF0,0xA4,0x28,0xA4,0x15,0xC4,0x85,0xF4,0x27,0xD4,0x6B,0xBF,0xE5,0x5C,0xE4,
+0x65,0x02,0x76,0x54,0xB4,0xE3,0x37,0x66,0x24,0xD3,0x19,0x61,0xC8,0x52,0x10,0xE5,
+0x8B,0x37,0x9A,0xB9,0xA9,0xF9,0x1D,0xBF,0xEA,0x99,0x92,0x61,0x96,0xFF,0x01,0xCD,
+0xA1,0x5F,0x0D,0xBC,0x71,0xBC,0x0E,0xAC,0x0B,0x1D,0x47,0x45,0x1D,0xC1,0xEC,0x7C,
+0xEC,0xFD,0x29,
+};
+
+
+/* subject:/C=GB/ST=Greater Manchester/L=Salford/O=Comodo CA Limited/CN=Trusted Certificate Services */
+/* issuer :/C=GB/ST=Greater Manchester/L=Salford/O=Comodo CA Limited/CN=Trusted Certificate Services */
+
+
+const unsigned char Comodo_Trusted_Services_root_certificate[1095]={
+0x30,0x82,0x04,0x43,0x30,0x82,0x03,0x2B,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,
+0x7F,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x47,0x42,0x31,0x1B,
+0x30,0x19,0x06,0x03,0x55,0x04,0x08,0x0C,0x12,0x47,0x72,0x65,0x61,0x74,0x65,0x72,
+0x20,0x4D,0x61,0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31,0x10,0x30,0x0E,0x06,
+0x03,0x55,0x04,0x07,0x0C,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72,0x64,0x31,0x1A,0x30,
+0x18,0x06,0x03,0x55,0x04,0x0A,0x0C,0x11,0x43,0x6F,0x6D,0x6F,0x64,0x6F,0x20,0x43,
+0x41,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x25,0x30,0x23,0x06,0x03,0x55,
+0x04,0x03,0x0C,0x1C,0x54,0x72,0x75,0x73,0x74,0x65,0x64,0x20,0x43,0x65,0x72,0x74,
+0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,
+0x30,0x1E,0x17,0x0D,0x30,0x34,0x30,0x31,0x30,0x31,0x30,0x30,0x30,0x30,0x30,0x30,
+0x5A,0x17,0x0D,0x32,0x38,0x31,0x32,0x33,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,
+0x30,0x7F,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x47,0x42,0x31,
+0x1B,0x30,0x19,0x06,0x03,0x55,0x04,0x08,0x0C,0x12,0x47,0x72,0x65,0x61,0x74,0x65,
+0x72,0x20,0x4D,0x61,0x6E,0x63,0x68,0x65,0x73,0x74,0x65,0x72,0x31,0x10,0x30,0x0E,
+0x06,0x03,0x55,0x04,0x07,0x0C,0x07,0x53,0x61,0x6C,0x66,0x6F,0x72,0x64,0x31,0x1A,
+0x30,0x18,0x06,0x03,0x55,0x04,0x0A,0x0C,0x11,0x43,0x6F,0x6D,0x6F,0x64,0x6F,0x20,
+0x43,0x41,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x25,0x30,0x23,0x06,0x03,
+0x55,0x04,0x03,0x0C,0x1C,0x54,0x72,0x75,0x73,0x74,0x65,0x64,0x20,0x43,0x65,0x72,
+0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,
+0x73,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,
+0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,
+0x01,0x00,0xDF,0x71,0x6F,0x36,0x58,0x53,0x5A,0xF2,0x36,0x54,0x57,0x80,0xC4,0x74,
+0x08,0x20,0xED,0x18,0x7F,0x2A,0x1D,0xE6,0x35,0x9A,0x1E,0x25,0xAC,0x9C,0xE5,0x96,
+0x7E,0x72,0x52,0xA0,0x15,0x42,0xDB,0x59,0xDD,0x64,0x7A,0x1A,0xD0,0xB8,0x7B,0xDD,
+0x39,0x15,0xBC,0x55,0x48,0xC4,0xED,0x3A,0x00,0xEA,0x31,0x11,0xBA,0xF2,0x71,0x74,
+0x1A,0x67,0xB8,0xCF,0x33,0xCC,0xA8,0x31,0xAF,0xA3,0xE3,0xD7,0x7F,0xBF,0x33,0x2D,
+0x4C,0x6A,0x3C,0xEC,0x8B,0xC3,0x92,0xD2,0x53,0x77,0x24,0x74,0x9C,0x07,0x6E,0x70,
+0xFC,0xBD,0x0B,0x5B,0x76,0xBA,0x5F,0xF2,0xFF,0xD7,0x37,0x4B,0x4A,0x60,0x78,0xF7,
+0xF0,0xFA,0xCA,0x70,0xB4,0xEA,0x59,0xAA,0xA3,0xCE,0x48,0x2F,0xA9,0xC3,0xB2,0x0B,
+0x7E,0x17,0x72,0x16,0x0C,0xA6,0x07,0x0C,0x1B,0x38,0xCF,0xC9,0x62,0xB7,0x3F,0xA0,
+0x93,0xA5,0x87,0x41,0xF2,0xB7,0x70,0x40,0x77,0xD8,0xBE,0x14,0x7C,0xE3,0xA8,0xC0,
+0x7A,0x8E,0xE9,0x63,0x6A,0xD1,0x0F,0x9A,0xC6,0xD2,0xF4,0x8B,0x3A,0x14,0x04,0x56,
+0xD4,0xED,0xB8,0xCC,0x6E,0xF5,0xFB,0xE2,0x2C,0x58,0xBD,0x7F,0x4F,0x6B,0x2B,0xF7,
+0x60,0x24,0x58,0x24,0xCE,0x26,0xEF,0x34,0x91,0x3A,0xD5,0xE3,0x81,0xD0,0xB2,0xF0,
+0x04,0x02,0xD7,0x5B,0xB7,0x3E,0x92,0xAC,0x6B,0x12,0x8A,0xF9,0xE4,0x05,0xB0,0x3B,
+0x91,0x49,0x5C,0xB2,0xEB,0x53,0xEA,0xF8,0x9F,0x47,0x86,0xEE,0xBF,0x95,0xC0,0xC0,
+0x06,0x9F,0xD2,0x5B,0x5E,0x11,0x1B,0xF4,0xC7,0x04,0x35,0x29,0xD2,0x55,0x5C,0xE4,
+0xED,0xEB,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0xC9,0x30,0x81,0xC6,0x30,0x1D,0x06,
+0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xC5,0x7B,0x58,0xBD,0xED,0xDA,0x25,0x69,
+0xD2,0xF7,0x59,0x16,0xA8,0xB3,0x32,0xC0,0x7B,0x27,0x5B,0xF4,0x30,0x0E,0x06,0x03,
+0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03,
+0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x81,0x83,
+0x06,0x03,0x55,0x1D,0x1F,0x04,0x7C,0x30,0x7A,0x30,0x3C,0xA0,0x3A,0xA0,0x38,0x86,
+0x36,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E,0x63,0x6F,0x6D,0x6F,
+0x64,0x6F,0x63,0x61,0x2E,0x63,0x6F,0x6D,0x2F,0x54,0x72,0x75,0x73,0x74,0x65,0x64,
+0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x53,0x65,0x72,0x76,0x69,
+0x63,0x65,0x73,0x2E,0x63,0x72,0x6C,0x30,0x3A,0xA0,0x38,0xA0,0x36,0x86,0x34,0x68,
+0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E,0x63,0x6F,0x6D,0x6F,0x64,0x6F,
+0x2E,0x6E,0x65,0x74,0x2F,0x54,0x72,0x75,0x73,0x74,0x65,0x64,0x43,0x65,0x72,0x74,
+0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x2E,
+0x63,0x72,0x6C,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,
+0x05,0x00,0x03,0x82,0x01,0x01,0x00,0xC8,0x93,0x81,0x3B,0x89,0xB4,0xAF,0xB8,0x84,
+0x12,0x4C,0x8D,0xD2,0xF0,0xDB,0x70,0xBA,0x57,0x86,0x15,0x34,0x10,0xB9,0x2F,0x7F,
+0x1E,0xB0,0xA8,0x89,0x60,0xA1,0x8A,0xC2,0x77,0x0C,0x50,0x4A,0x9B,0x00,0x8B,0xD8,
+0x8B,0xF4,0x41,0xE2,0xD0,0x83,0x8A,0x4A,0x1C,0x14,0x06,0xB0,0xA3,0x68,0x05,0x70,
+0x31,0x30,0xA7,0x53,0x9B,0x0E,0xE9,0x4A,0xA0,0x58,0x69,0x67,0x0E,0xAE,0x9D,0xF6,
+0xA5,0x2C,0x41,0xBF,0x3C,0x06,0x6B,0xE4,0x59,0xCC,0x6D,0x10,0xF1,0x96,0x6F,0x1F,
+0xDF,0xF4,0x04,0x02,0xA4,0x9F,0x45,0x3E,0xC8,0xD8,0xFA,0x36,0x46,0x44,0x50,0x3F,
+0x82,0x97,0x91,0x1F,0x28,0xDB,0x18,0x11,0x8C,0x2A,0xE4,0x65,0x83,0x57,0x12,0x12,
+0x8C,0x17,0x3F,0x94,0x36,0xFE,0x5D,0xB0,0xC0,0x04,0x77,0x13,0xB8,0xF4,0x15,0xD5,
+0x3F,0x38,0xCC,0x94,0x3A,0x55,0xD0,0xAC,0x98,0xF5,0xBA,0x00,0x5F,0xE0,0x86,0x19,
+0x81,0x78,0x2F,0x28,0xC0,0x7E,0xD3,0xCC,0x42,0x0A,0xF5,0xAE,0x50,0xA0,0xD1,0x3E,
+0xC6,0xA1,0x71,0xEC,0x3F,0xA0,0x20,0x8C,0x66,0x3A,0x89,0xB4,0x8E,0xD4,0xD8,0xB1,
+0x4D,0x25,0x47,0xEE,0x2F,0x88,0xC8,0xB5,0xE1,0x05,0x45,0xC0,0xBE,0x14,0x71,0xDE,
+0x7A,0xFD,0x8E,0x7B,0x7D,0x4D,0x08,0x96,0xA5,0x12,0x73,0xF0,0x2D,0xCA,0x37,0x27,
+0x74,0x12,0x27,0x4C,0xCB,0xB6,0x97,0xE9,0xD9,0xAE,0x08,0x6D,0x5A,0x39,0x40,0xDD,
+0x05,0x47,0x75,0x6A,0x5A,0x21,0xB3,0xA3,0x18,0xCF,0x4E,0xF7,0x2E,0x57,0xB7,0x98,
+0x70,0x5E,0xC8,0xC4,0x78,0xB0,0x62,
+};
+
+
+/* subject:/O=Cybertrust, Inc/CN=Cybertrust Global Root */
+/* issuer :/O=Cybertrust, Inc/CN=Cybertrust Global Root */
+
+
+const unsigned char Cybertrust_Global_Root_certificate[933]={
+0x30,0x82,0x03,0xA1,0x30,0x82,0x02,0x89,0xA0,0x03,0x02,0x01,0x02,0x02,0x0B,0x04,
+0x00,0x00,0x00,0x00,0x01,0x0F,0x85,0xAA,0x2D,0x48,0x30,0x0D,0x06,0x09,0x2A,0x86,
+0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x3B,0x31,0x18,0x30,0x16,0x06,
+0x03,0x55,0x04,0x0A,0x13,0x0F,0x43,0x79,0x62,0x65,0x72,0x74,0x72,0x75,0x73,0x74,
+0x2C,0x20,0x49,0x6E,0x63,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x03,0x13,0x16,
+0x43,0x79,0x62,0x65,0x72,0x74,0x72,0x75,0x73,0x74,0x20,0x47,0x6C,0x6F,0x62,0x61,
+0x6C,0x20,0x52,0x6F,0x6F,0x74,0x30,0x1E,0x17,0x0D,0x30,0x36,0x31,0x32,0x31,0x35,
+0x30,0x38,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x31,0x31,0x32,0x31,0x35,0x30,
+0x38,0x30,0x30,0x30,0x30,0x5A,0x30,0x3B,0x31,0x18,0x30,0x16,0x06,0x03,0x55,0x04,
+0x0A,0x13,0x0F,0x43,0x79,0x62,0x65,0x72,0x74,0x72,0x75,0x73,0x74,0x2C,0x20,0x49,
+0x6E,0x63,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x03,0x13,0x16,0x43,0x79,0x62,
+0x65,0x72,0x74,0x72,0x75,0x73,0x74,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x52,
+0x6F,0x6F,0x74,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,
+0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,
+0x82,0x01,0x01,0x00,0xF8,0xC8,0xBC,0xBD,0x14,0x50,0x66,0x13,0xFF,0xF0,0xD3,0x79,
+0xEC,0x23,0xF2,0xB7,0x1A,0xC7,0x8E,0x85,0xF1,0x12,0x73,0xA6,0x19,0xAA,0x10,0xDB,
+0x9C,0xA2,0x65,0x74,0x5A,0x77,0x3E,0x51,0x7D,0x56,0xF6,0xDC,0x23,0xB6,0xD4,0xED,
+0x5F,0x58,0xB1,0x37,0x4D,0xD5,0x49,0x0E,0x6E,0xF5,0x6A,0x87,0xD6,0xD2,0x8C,0xD2,
+0x27,0xC6,0xE2,0xFF,0x36,0x9F,0x98,0x65,0xA0,0x13,0x4E,0xC6,0x2A,0x64,0x9B,0xD5,
+0x90,0x12,0xCF,0x14,0x06,0xF4,0x3B,0xE3,0xD4,0x28,0xBE,0xE8,0x0E,0xF8,0xAB,0x4E,
+0x48,0x94,0x6D,0x8E,0x95,0x31,0x10,0x5C,0xED,0xA2,0x2D,0xBD,0xD5,0x3A,0x6D,0xB2,
+0x1C,0xBB,0x60,0xC0,0x46,0x4B,0x01,0xF5,0x49,0xAE,0x7E,0x46,0x8A,0xD0,0x74,0x8D,
+0xA1,0x0C,0x02,0xCE,0xEE,0xFC,0xE7,0x8F,0xB8,0x6B,0x66,0xF3,0x7F,0x44,0x00,0xBF,
+0x66,0x25,0x14,0x2B,0xDD,0x10,0x30,0x1D,0x07,0x96,0x3F,0x4D,0xF6,0x6B,0xB8,0x8F,
+0xB7,0x7B,0x0C,0xA5,0x38,0xEB,0xDE,0x47,0xDB,0xD5,0x5D,0x39,0xFC,0x88,0xA7,0xF3,
+0xD7,0x2A,0x74,0xF1,0xE8,0x5A,0xA2,0x3B,0x9F,0x50,0xBA,0xA6,0x8C,0x45,0x35,0xC2,
+0x50,0x65,0x95,0xDC,0x63,0x82,0xEF,0xDD,0xBF,0x77,0x4D,0x9C,0x62,0xC9,0x63,0x73,
+0x16,0xD0,0x29,0x0F,0x49,0xA9,0x48,0xF0,0xB3,0xAA,0xB7,0x6C,0xC5,0xA7,0x30,0x39,
+0x40,0x5D,0xAE,0xC4,0xE2,0x5D,0x26,0x53,0xF0,0xCE,0x1C,0x23,0x08,0x61,0xA8,0x94,
+0x19,0xBA,0x04,0x62,0x40,0xEC,0x1F,0x38,0x70,0x77,0x12,0x06,0x71,0xA7,0x30,0x18,
+0x5D,0x25,0x27,0xA5,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0xA5,0x30,0x81,0xA2,0x30,
+0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,
+0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,
+0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xB6,0x08,0x7B,0x0D,0x7A,
+0xCC,0xAC,0x20,0x4C,0x86,0x56,0x32,0x5E,0xCF,0xAB,0x6E,0x85,0x2D,0x70,0x57,0x30,
+0x3F,0x06,0x03,0x55,0x1D,0x1F,0x04,0x38,0x30,0x36,0x30,0x34,0xA0,0x32,0xA0,0x30,
+0x86,0x2E,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x32,0x2E,0x70,0x75,
+0x62,0x6C,0x69,0x63,0x2D,0x74,0x72,0x75,0x73,0x74,0x2E,0x63,0x6F,0x6D,0x2F,0x63,
+0x72,0x6C,0x2F,0x63,0x74,0x2F,0x63,0x74,0x72,0x6F,0x6F,0x74,0x2E,0x63,0x72,0x6C,
+0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0xB6,0x08,0x7B,
+0x0D,0x7A,0xCC,0xAC,0x20,0x4C,0x86,0x56,0x32,0x5E,0xCF,0xAB,0x6E,0x85,0x2D,0x70,
+0x57,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,
+0x03,0x82,0x01,0x01,0x00,0x56,0xEF,0x0A,0x23,0xA0,0x54,0x4E,0x95,0x97,0xC9,0xF8,
+0x89,0xDA,0x45,0xC1,0xD4,0xA3,0x00,0x25,0xF4,0x1F,0x13,0xAB,0xB7,0xA3,0x85,0x58,
+0x69,0xC2,0x30,0xAD,0xD8,0x15,0x8A,0x2D,0xE3,0xC9,0xCD,0x81,0x5A,0xF8,0x73,0x23,
+0x5A,0xA7,0x7C,0x05,0xF3,0xFD,0x22,0x3B,0x0E,0xD1,0x06,0xC4,0xDB,0x36,0x4C,0x73,
+0x04,0x8E,0xE5,0xB0,0x22,0xE4,0xC5,0xF3,0x2E,0xA5,0xD9,0x23,0xE3,0xB8,0x4E,0x4A,
+0x20,0xA7,0x6E,0x02,0x24,0x9F,0x22,0x60,0x67,0x7B,0x8B,0x1D,0x72,0x09,0xC5,0x31,
+0x5C,0xE9,0x79,0x9F,0x80,0x47,0x3D,0xAD,0xA1,0x0B,0x07,0x14,0x3D,0x47,0xFF,0x03,
+0x69,0x1A,0x0C,0x0B,0x44,0xE7,0x63,0x25,0xA7,0x7F,0xB2,0xC9,0xB8,0x76,0x84,0xED,
+0x23,0xF6,0x7D,0x07,0xAB,0x45,0x7E,0xD3,0xDF,0xB3,0xBF,0xE9,0x8A,0xB6,0xCD,0xA8,
+0xA2,0x67,0x2B,0x52,0xD5,0xB7,0x65,0xF0,0x39,0x4C,0x63,0xA0,0x91,0x79,0x93,0x52,
+0x0F,0x54,0xDD,0x83,0xBB,0x9F,0xD1,0x8F,0xA7,0x53,0x73,0xC3,0xCB,0xFF,0x30,0xEC,
+0x7C,0x04,0xB8,0xD8,0x44,0x1F,0x93,0x5F,0x71,0x09,0x22,0xB7,0x6E,0x3E,0xEA,0x1C,
+0x03,0x4E,0x9D,0x1A,0x20,0x61,0xFB,0x81,0x37,0xEC,0x5E,0xFC,0x0A,0x45,0xAB,0xD7,
+0xE7,0x17,0x55,0xD0,0xA0,0xEA,0x60,0x9B,0xA6,0xF6,0xE3,0x8C,0x5B,0x29,0xC2,0x06,
+0x60,0x14,0x9D,0x2D,0x97,0x4C,0xA9,0x93,0x15,0x9D,0x61,0xC4,0x01,0x5F,0x48,0xD6,
+0x58,0xBD,0x56,0x31,0x12,0x4E,0x11,0xC8,0x21,0xE0,0xB3,0x11,0x91,0x65,0xDB,0xB4,
+0xA6,0x88,0x38,0xCE,0x55,
+};
+
+
+/* subject:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Assured ID Root CA */
+/* issuer :/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Assured ID Root CA */
+
+
+const unsigned char DigiCert_Assured_ID_Root_CA_certificate[955]={
+0x30,0x82,0x03,0xB7,0x30,0x82,0x02,0x9F,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x0C,
+0xE7,0xE0,0xE5,0x17,0xD8,0x46,0xFE,0x8F,0xE5,0x60,0xFC,0x1B,0xF0,0x30,0x39,0x30,
+0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x65,
+0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15,0x30,
+0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74,
+0x20,0x49,0x6E,0x63,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x0B,0x13,0x10,0x77,
+0x77,0x77,0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x31,
+0x24,0x30,0x22,0x06,0x03,0x55,0x04,0x03,0x13,0x1B,0x44,0x69,0x67,0x69,0x43,0x65,
+0x72,0x74,0x20,0x41,0x73,0x73,0x75,0x72,0x65,0x64,0x20,0x49,0x44,0x20,0x52,0x6F,
+0x6F,0x74,0x20,0x43,0x41,0x30,0x1E,0x17,0x0D,0x30,0x36,0x31,0x31,0x31,0x30,0x30,
+0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x31,0x31,0x31,0x31,0x30,0x30,0x30,
+0x30,0x30,0x30,0x30,0x5A,0x30,0x65,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,
+0x13,0x02,0x55,0x53,0x31,0x15,0x30,0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x44,
+0x69,0x67,0x69,0x43,0x65,0x72,0x74,0x20,0x49,0x6E,0x63,0x31,0x19,0x30,0x17,0x06,
+0x03,0x55,0x04,0x0B,0x13,0x10,0x77,0x77,0x77,0x2E,0x64,0x69,0x67,0x69,0x63,0x65,
+0x72,0x74,0x2E,0x63,0x6F,0x6D,0x31,0x24,0x30,0x22,0x06,0x03,0x55,0x04,0x03,0x13,
+0x1B,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74,0x20,0x41,0x73,0x73,0x75,0x72,0x65,
+0x64,0x20,0x49,0x44,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x30,0x82,0x01,0x22,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,
+0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xAD,0x0E,0x15,
+0xCE,0xE4,0x43,0x80,0x5C,0xB1,0x87,0xF3,0xB7,0x60,0xF9,0x71,0x12,0xA5,0xAE,0xDC,
+0x26,0x94,0x88,0xAA,0xF4,0xCE,0xF5,0x20,0x39,0x28,0x58,0x60,0x0C,0xF8,0x80,0xDA,
+0xA9,0x15,0x95,0x32,0x61,0x3C,0xB5,0xB1,0x28,0x84,0x8A,0x8A,0xDC,0x9F,0x0A,0x0C,
+0x83,0x17,0x7A,0x8F,0x90,0xAC,0x8A,0xE7,0x79,0x53,0x5C,0x31,0x84,0x2A,0xF6,0x0F,
+0x98,0x32,0x36,0x76,0xCC,0xDE,0xDD,0x3C,0xA8,0xA2,0xEF,0x6A,0xFB,0x21,0xF2,0x52,
+0x61,0xDF,0x9F,0x20,0xD7,0x1F,0xE2,0xB1,0xD9,0xFE,0x18,0x64,0xD2,0x12,0x5B,0x5F,
+0xF9,0x58,0x18,0x35,0xBC,0x47,0xCD,0xA1,0x36,0xF9,0x6B,0x7F,0xD4,0xB0,0x38,0x3E,
+0xC1,0x1B,0xC3,0x8C,0x33,0xD9,0xD8,0x2F,0x18,0xFE,0x28,0x0F,0xB3,0xA7,0x83,0xD6,
+0xC3,0x6E,0x44,0xC0,0x61,0x35,0x96,0x16,0xFE,0x59,0x9C,0x8B,0x76,0x6D,0xD7,0xF1,
+0xA2,0x4B,0x0D,0x2B,0xFF,0x0B,0x72,0xDA,0x9E,0x60,0xD0,0x8E,0x90,0x35,0xC6,0x78,
+0x55,0x87,0x20,0xA1,0xCF,0xE5,0x6D,0x0A,0xC8,0x49,0x7C,0x31,0x98,0x33,0x6C,0x22,
+0xE9,0x87,0xD0,0x32,0x5A,0xA2,0xBA,0x13,0x82,0x11,0xED,0x39,0x17,0x9D,0x99,0x3A,
+0x72,0xA1,0xE6,0xFA,0xA4,0xD9,0xD5,0x17,0x31,0x75,0xAE,0x85,0x7D,0x22,0xAE,0x3F,
+0x01,0x46,0x86,0xF6,0x28,0x79,0xC8,0xB1,0xDA,0xE4,0x57,0x17,0xC4,0x7E,0x1C,0x0E,
+0xB0,0xB4,0x92,0xA6,0x56,0xB3,0xBD,0xB2,0x97,0xED,0xAA,0xA7,0xF0,0xB7,0xC5,0xA8,
+0x3F,0x95,0x16,0xD0,0xFF,0xA1,0x96,0xEB,0x08,0x5F,0x18,0x77,0x4F,0x02,0x03,0x01,
+0x00,0x01,0xA3,0x63,0x30,0x61,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,
+0x04,0x04,0x03,0x02,0x01,0x86,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,
+0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,
+0x04,0x14,0x45,0xEB,0xA2,0xAF,0xF4,0x92,0xCB,0x82,0x31,0x2D,0x51,0x8B,0xA7,0xA7,
+0x21,0x9D,0xF3,0x6D,0xC8,0x0F,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,
+0x16,0x80,0x14,0x45,0xEB,0xA2,0xAF,0xF4,0x92,0xCB,0x82,0x31,0x2D,0x51,0x8B,0xA7,
+0xA7,0x21,0x9D,0xF3,0x6D,0xC8,0x0F,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,
+0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0xA2,0x0E,0xBC,0xDF,0xE2,
+0xED,0xF0,0xE3,0x72,0x73,0x7A,0x64,0x94,0xBF,0xF7,0x72,0x66,0xD8,0x32,0xE4,0x42,
+0x75,0x62,0xAE,0x87,0xEB,0xF2,0xD5,0xD9,0xDE,0x56,0xB3,0x9F,0xCC,0xCE,0x14,0x28,
+0xB9,0x0D,0x97,0x60,0x5C,0x12,0x4C,0x58,0xE4,0xD3,0x3D,0x83,0x49,0x45,0x58,0x97,
+0x35,0x69,0x1A,0xA8,0x47,0xEA,0x56,0xC6,0x79,0xAB,0x12,0xD8,0x67,0x81,0x84,0xDF,
+0x7F,0x09,0x3C,0x94,0xE6,0xB8,0x26,0x2C,0x20,0xBD,0x3D,0xB3,0x28,0x89,0xF7,0x5F,
+0xFF,0x22,0xE2,0x97,0x84,0x1F,0xE9,0x65,0xEF,0x87,0xE0,0xDF,0xC1,0x67,0x49,0xB3,
+0x5D,0xEB,0xB2,0x09,0x2A,0xEB,0x26,0xED,0x78,0xBE,0x7D,0x3F,0x2B,0xF3,0xB7,0x26,
+0x35,0x6D,0x5F,0x89,0x01,0xB6,0x49,0x5B,0x9F,0x01,0x05,0x9B,0xAB,0x3D,0x25,0xC1,
+0xCC,0xB6,0x7F,0xC2,0xF1,0x6F,0x86,0xC6,0xFA,0x64,0x68,0xEB,0x81,0x2D,0x94,0xEB,
+0x42,0xB7,0xFA,0x8C,0x1E,0xDD,0x62,0xF1,0xBE,0x50,0x67,0xB7,0x6C,0xBD,0xF3,0xF1,
+0x1F,0x6B,0x0C,0x36,0x07,0x16,0x7F,0x37,0x7C,0xA9,0x5B,0x6D,0x7A,0xF1,0x12,0x46,
+0x60,0x83,0xD7,0x27,0x04,0xBE,0x4B,0xCE,0x97,0xBE,0xC3,0x67,0x2A,0x68,0x11,0xDF,
+0x80,0xE7,0x0C,0x33,0x66,0xBF,0x13,0x0D,0x14,0x6E,0xF3,0x7F,0x1F,0x63,0x10,0x1E,
+0xFA,0x8D,0x1B,0x25,0x6D,0x6C,0x8F,0xA5,0xB7,0x61,0x01,0xB1,0xD2,0xA3,0x26,0xA1,
+0x10,0x71,0x9D,0xAD,0xE2,0xC3,0xF9,0xC3,0x99,0x51,0xB7,0x2B,0x07,0x08,0xCE,0x2E,
+0xE6,0x50,0xB2,0xA7,0xFA,0x0A,0x45,0x2F,0xA2,0xF0,0xF2,
+};
+
+
+/* subject:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Global Root CA */
+/* issuer :/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Global Root CA */
+
+
+const unsigned char DigiCert_Global_Root_CA_certificate[947]={
+0x30,0x82,0x03,0xAF,0x30,0x82,0x02,0x97,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x08,
+0x3B,0xE0,0x56,0x90,0x42,0x46,0xB1,0xA1,0x75,0x6A,0xC9,0x59,0x91,0xC7,0x4A,0x30,
+0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x61,
+0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15,0x30,
+0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74,
+0x20,0x49,0x6E,0x63,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x0B,0x13,0x10,0x77,
+0x77,0x77,0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x31,
+0x20,0x30,0x1E,0x06,0x03,0x55,0x04,0x03,0x13,0x17,0x44,0x69,0x67,0x69,0x43,0x65,
+0x72,0x74,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,
+0x41,0x30,0x1E,0x17,0x0D,0x30,0x36,0x31,0x31,0x31,0x30,0x30,0x30,0x30,0x30,0x30,
+0x30,0x5A,0x17,0x0D,0x33,0x31,0x31,0x31,0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
+0x5A,0x30,0x61,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,
+0x31,0x15,0x30,0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x44,0x69,0x67,0x69,0x43,
+0x65,0x72,0x74,0x20,0x49,0x6E,0x63,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x0B,
+0x13,0x10,0x77,0x77,0x77,0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,
+0x6F,0x6D,0x31,0x20,0x30,0x1E,0x06,0x03,0x55,0x04,0x03,0x13,0x17,0x44,0x69,0x67,
+0x69,0x43,0x65,0x72,0x74,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x52,0x6F,0x6F,
+0x74,0x20,0x43,0x41,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,
+0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,
+0x02,0x82,0x01,0x01,0x00,0xE2,0x3B,0xE1,0x11,0x72,0xDE,0xA8,0xA4,0xD3,0xA3,0x57,
+0xAA,0x50,0xA2,0x8F,0x0B,0x77,0x90,0xC9,0xA2,0xA5,0xEE,0x12,0xCE,0x96,0x5B,0x01,
+0x09,0x20,0xCC,0x01,0x93,0xA7,0x4E,0x30,0xB7,0x53,0xF7,0x43,0xC4,0x69,0x00,0x57,
+0x9D,0xE2,0x8D,0x22,0xDD,0x87,0x06,0x40,0x00,0x81,0x09,0xCE,0xCE,0x1B,0x83,0xBF,
+0xDF,0xCD,0x3B,0x71,0x46,0xE2,0xD6,0x66,0xC7,0x05,0xB3,0x76,0x27,0x16,0x8F,0x7B,
+0x9E,0x1E,0x95,0x7D,0xEE,0xB7,0x48,0xA3,0x08,0xDA,0xD6,0xAF,0x7A,0x0C,0x39,0x06,
+0x65,0x7F,0x4A,0x5D,0x1F,0xBC,0x17,0xF8,0xAB,0xBE,0xEE,0x28,0xD7,0x74,0x7F,0x7A,
+0x78,0x99,0x59,0x85,0x68,0x6E,0x5C,0x23,0x32,0x4B,0xBF,0x4E,0xC0,0xE8,0x5A,0x6D,
+0xE3,0x70,0xBF,0x77,0x10,0xBF,0xFC,0x01,0xF6,0x85,0xD9,0xA8,0x44,0x10,0x58,0x32,
+0xA9,0x75,0x18,0xD5,0xD1,0xA2,0xBE,0x47,0xE2,0x27,0x6A,0xF4,0x9A,0x33,0xF8,0x49,
+0x08,0x60,0x8B,0xD4,0x5F,0xB4,0x3A,0x84,0xBF,0xA1,0xAA,0x4A,0x4C,0x7D,0x3E,0xCF,
+0x4F,0x5F,0x6C,0x76,0x5E,0xA0,0x4B,0x37,0x91,0x9E,0xDC,0x22,0xE6,0x6D,0xCE,0x14,
+0x1A,0x8E,0x6A,0xCB,0xFE,0xCD,0xB3,0x14,0x64,0x17,0xC7,0x5B,0x29,0x9E,0x32,0xBF,
+0xF2,0xEE,0xFA,0xD3,0x0B,0x42,0xD4,0xAB,0xB7,0x41,0x32,0xDA,0x0C,0xD4,0xEF,0xF8,
+0x81,0xD5,0xBB,0x8D,0x58,0x3F,0xB5,0x1B,0xE8,0x49,0x28,0xA2,0x70,0xDA,0x31,0x04,
+0xDD,0xF7,0xB2,0x16,0xF2,0x4C,0x0A,0x4E,0x07,0xA8,0xED,0x4A,0x3D,0x5E,0xB5,0x7F,
+0xA3,0x90,0xC3,0xAF,0x27,0x02,0x03,0x01,0x00,0x01,0xA3,0x63,0x30,0x61,0x30,0x0E,
+0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x86,0x30,0x0F,
+0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,
+0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x03,0xDE,0x50,0x35,0x56,0xD1,
+0x4C,0xBB,0x66,0xF0,0xA3,0xE2,0x1B,0x1B,0xC3,0x97,0xB2,0x3D,0xD1,0x55,0x30,0x1F,
+0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0x03,0xDE,0x50,0x35,0x56,
+0xD1,0x4C,0xBB,0x66,0xF0,0xA3,0xE2,0x1B,0x1B,0xC3,0x97,0xB2,0x3D,0xD1,0x55,0x30,
+0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,
+0x01,0x01,0x00,0xCB,0x9C,0x37,0xAA,0x48,0x13,0x12,0x0A,0xFA,0xDD,0x44,0x9C,0x4F,
+0x52,0xB0,0xF4,0xDF,0xAE,0x04,0xF5,0x79,0x79,0x08,0xA3,0x24,0x18,0xFC,0x4B,0x2B,
+0x84,0xC0,0x2D,0xB9,0xD5,0xC7,0xFE,0xF4,0xC1,0x1F,0x58,0xCB,0xB8,0x6D,0x9C,0x7A,
+0x74,0xE7,0x98,0x29,0xAB,0x11,0xB5,0xE3,0x70,0xA0,0xA1,0xCD,0x4C,0x88,0x99,0x93,
+0x8C,0x91,0x70,0xE2,0xAB,0x0F,0x1C,0xBE,0x93,0xA9,0xFF,0x63,0xD5,0xE4,0x07,0x60,
+0xD3,0xA3,0xBF,0x9D,0x5B,0x09,0xF1,0xD5,0x8E,0xE3,0x53,0xF4,0x8E,0x63,0xFA,0x3F,
+0xA7,0xDB,0xB4,0x66,0xDF,0x62,0x66,0xD6,0xD1,0x6E,0x41,0x8D,0xF2,0x2D,0xB5,0xEA,
+0x77,0x4A,0x9F,0x9D,0x58,0xE2,0x2B,0x59,0xC0,0x40,0x23,0xED,0x2D,0x28,0x82,0x45,
+0x3E,0x79,0x54,0x92,0x26,0x98,0xE0,0x80,0x48,0xA8,0x37,0xEF,0xF0,0xD6,0x79,0x60,
+0x16,0xDE,0xAC,0xE8,0x0E,0xCD,0x6E,0xAC,0x44,0x17,0x38,0x2F,0x49,0xDA,0xE1,0x45,
+0x3E,0x2A,0xB9,0x36,0x53,0xCF,0x3A,0x50,0x06,0xF7,0x2E,0xE8,0xC4,0x57,0x49,0x6C,
+0x61,0x21,0x18,0xD5,0x04,0xAD,0x78,0x3C,0x2C,0x3A,0x80,0x6B,0xA7,0xEB,0xAF,0x15,
+0x14,0xE9,0xD8,0x89,0xC1,0xB9,0x38,0x6C,0xE2,0x91,0x6C,0x8A,0xFF,0x64,0xB9,0x77,
+0x25,0x57,0x30,0xC0,0x1B,0x24,0xA3,0xE1,0xDC,0xE9,0xDF,0x47,0x7C,0xB5,0xB4,0x24,
+0x08,0x05,0x30,0xEC,0x2D,0xBD,0x0B,0xBF,0x45,0xBF,0x50,0xB9,0xA9,0xF3,0xEB,0x98,
+0x01,0x12,0xAD,0xC8,0x88,0xC6,0x98,0x34,0x5F,0x8D,0x0A,0x3C,0xC6,0xE9,0xD5,0x95,
+0x95,0x6D,0xDE,
+};
+
+
+/* subject:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV Root CA */
+/* issuer :/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV Root CA */
+
+
+const unsigned char DigiCert_High_Assurance_EV_Root_CA_certificate[969]={
+0x30,0x82,0x03,0xC5,0x30,0x82,0x02,0xAD,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x02,
+0xAC,0x5C,0x26,0x6A,0x0B,0x40,0x9B,0x8F,0x0B,0x79,0xF2,0xAE,0x46,0x25,0x77,0x30,
+0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x6C,
+0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15,0x30,
+0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74,
+0x20,0x49,0x6E,0x63,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x0B,0x13,0x10,0x77,
+0x77,0x77,0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x31,
+0x2B,0x30,0x29,0x06,0x03,0x55,0x04,0x03,0x13,0x22,0x44,0x69,0x67,0x69,0x43,0x65,
+0x72,0x74,0x20,0x48,0x69,0x67,0x68,0x20,0x41,0x73,0x73,0x75,0x72,0x61,0x6E,0x63,
+0x65,0x20,0x45,0x56,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x30,0x1E,0x17,0x0D,
+0x30,0x36,0x31,0x31,0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,
+0x31,0x31,0x31,0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x30,0x6C,0x31,0x0B,
+0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15,0x30,0x13,0x06,
+0x03,0x55,0x04,0x0A,0x13,0x0C,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74,0x20,0x49,
+0x6E,0x63,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x0B,0x13,0x10,0x77,0x77,0x77,
+0x2E,0x64,0x69,0x67,0x69,0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x31,0x2B,0x30,
+0x29,0x06,0x03,0x55,0x04,0x03,0x13,0x22,0x44,0x69,0x67,0x69,0x43,0x65,0x72,0x74,
+0x20,0x48,0x69,0x67,0x68,0x20,0x41,0x73,0x73,0x75,0x72,0x61,0x6E,0x63,0x65,0x20,
+0x45,0x56,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x30,0x82,0x01,0x22,0x30,0x0D,
+0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,
+0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xC6,0xCC,0xE5,0x73,0xE6,
+0xFB,0xD4,0xBB,0xE5,0x2D,0x2D,0x32,0xA6,0xDF,0xE5,0x81,0x3F,0xC9,0xCD,0x25,0x49,
+0xB6,0x71,0x2A,0xC3,0xD5,0x94,0x34,0x67,0xA2,0x0A,0x1C,0xB0,0x5F,0x69,0xA6,0x40,
+0xB1,0xC4,0xB7,0xB2,0x8F,0xD0,0x98,0xA4,0xA9,0x41,0x59,0x3A,0xD3,0xDC,0x94,0xD6,
+0x3C,0xDB,0x74,0x38,0xA4,0x4A,0xCC,0x4D,0x25,0x82,0xF7,0x4A,0xA5,0x53,0x12,0x38,
+0xEE,0xF3,0x49,0x6D,0x71,0x91,0x7E,0x63,0xB6,0xAB,0xA6,0x5F,0xC3,0xA4,0x84,0xF8,
+0x4F,0x62,0x51,0xBE,0xF8,0xC5,0xEC,0xDB,0x38,0x92,0xE3,0x06,0xE5,0x08,0x91,0x0C,
+0xC4,0x28,0x41,0x55,0xFB,0xCB,0x5A,0x89,0x15,0x7E,0x71,0xE8,0x35,0xBF,0x4D,0x72,
+0x09,0x3D,0xBE,0x3A,0x38,0x50,0x5B,0x77,0x31,0x1B,0x8D,0xB3,0xC7,0x24,0x45,0x9A,
+0xA7,0xAC,0x6D,0x00,0x14,0x5A,0x04,0xB7,0xBA,0x13,0xEB,0x51,0x0A,0x98,0x41,0x41,
+0x22,0x4E,0x65,0x61,0x87,0x81,0x41,0x50,0xA6,0x79,0x5C,0x89,0xDE,0x19,0x4A,0x57,
+0xD5,0x2E,0xE6,0x5D,0x1C,0x53,0x2C,0x7E,0x98,0xCD,0x1A,0x06,0x16,0xA4,0x68,0x73,
+0xD0,0x34,0x04,0x13,0x5C,0xA1,0x71,0xD3,0x5A,0x7C,0x55,0xDB,0x5E,0x64,0xE1,0x37,
+0x87,0x30,0x56,0x04,0xE5,0x11,0xB4,0x29,0x80,0x12,0xF1,0x79,0x39,0x88,0xA2,0x02,
+0x11,0x7C,0x27,0x66,0xB7,0x88,0xB7,0x78,0xF2,0xCA,0x0A,0xA8,0x38,0xAB,0x0A,0x64,
+0xC2,0xBF,0x66,0x5D,0x95,0x84,0xC1,0xA1,0x25,0x1E,0x87,0x5D,0x1A,0x50,0x0B,0x20,
+0x12,0xCC,0x41,0xBB,0x6E,0x0B,0x51,0x38,0xB8,0x4B,0xCB,0x02,0x03,0x01,0x00,0x01,
+0xA3,0x63,0x30,0x61,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,
+0x03,0x02,0x01,0x86,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,
+0x30,0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,
+0xB1,0x3E,0xC3,0x69,0x03,0xF8,0xBF,0x47,0x01,0xD4,0x98,0x26,0x1A,0x08,0x02,0xEF,
+0x63,0x64,0x2B,0xC3,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,
+0x14,0xB1,0x3E,0xC3,0x69,0x03,0xF8,0xBF,0x47,0x01,0xD4,0x98,0x26,0x1A,0x08,0x02,
+0xEF,0x63,0x64,0x2B,0xC3,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,
+0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x1C,0x1A,0x06,0x97,0xDC,0xD7,0x9C,
+0x9F,0x3C,0x88,0x66,0x06,0x08,0x57,0x21,0xDB,0x21,0x47,0xF8,0x2A,0x67,0xAA,0xBF,
+0x18,0x32,0x76,0x40,0x10,0x57,0xC1,0x8A,0xF3,0x7A,0xD9,0x11,0x65,0x8E,0x35,0xFA,
+0x9E,0xFC,0x45,0xB5,0x9E,0xD9,0x4C,0x31,0x4B,0xB8,0x91,0xE8,0x43,0x2C,0x8E,0xB3,
+0x78,0xCE,0xDB,0xE3,0x53,0x79,0x71,0xD6,0xE5,0x21,0x94,0x01,0xDA,0x55,0x87,0x9A,
+0x24,0x64,0xF6,0x8A,0x66,0xCC,0xDE,0x9C,0x37,0xCD,0xA8,0x34,0xB1,0x69,0x9B,0x23,
+0xC8,0x9E,0x78,0x22,0x2B,0x70,0x43,0xE3,0x55,0x47,0x31,0x61,0x19,0xEF,0x58,0xC5,
+0x85,0x2F,0x4E,0x30,0xF6,0xA0,0x31,0x16,0x23,0xC8,0xE7,0xE2,0x65,0x16,0x33,0xCB,
+0xBF,0x1A,0x1B,0xA0,0x3D,0xF8,0xCA,0x5E,0x8B,0x31,0x8B,0x60,0x08,0x89,0x2D,0x0C,
+0x06,0x5C,0x52,0xB7,0xC4,0xF9,0x0A,0x98,0xD1,0x15,0x5F,0x9F,0x12,0xBE,0x7C,0x36,
+0x63,0x38,0xBD,0x44,0xA4,0x7F,0xE4,0x26,0x2B,0x0A,0xC4,0x97,0x69,0x0D,0xE9,0x8C,
+0xE2,0xC0,0x10,0x57,0xB8,0xC8,0x76,0x12,0x91,0x55,0xF2,0x48,0x69,0xD8,0xBC,0x2A,
+0x02,0x5B,0x0F,0x44,0xD4,0x20,0x31,0xDB,0xF4,0xBA,0x70,0x26,0x5D,0x90,0x60,0x9E,
+0xBC,0x4B,0x17,0x09,0x2F,0xB4,0xCB,0x1E,0x43,0x68,0xC9,0x07,0x27,0xC1,0xD2,0x5C,
+0xF7,0xEA,0x21,0xB9,0x68,0x12,0x9C,0x3C,0x9C,0xBF,0x9E,0xFC,0x80,0x5C,0x9B,0x63,
+0xCD,0xEC,0x47,0xAA,0x25,0x27,0x67,0xA0,0x37,0xF3,0x00,0x82,0x7D,0x54,0xD7,0xA9,
+0xF8,0xE9,0x2E,0x13,0xA3,0x77,0xE8,0x1F,0x4A,
+};
+
+
+/* subject:/O=Entrust.net/OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/OU=(c) 1999 Entrust.net Limited/CN=Entrust.net Certification Authority (2048) */
+/* issuer :/O=Entrust.net/OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/OU=(c) 1999 Entrust.net Limited/CN=Entrust.net Certification Authority (2048) */
+
+
+const unsigned char Entrust_net_Premium_2048_Secure_Server_CA_certificate[1120]={
+0x30,0x82,0x04,0x5C,0x30,0x82,0x03,0x44,0xA0,0x03,0x02,0x01,0x02,0x02,0x04,0x38,
+0x63,0xB9,0x66,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,
+0x05,0x00,0x30,0x81,0xB4,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,
+0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x31,0x40,0x30,0x3E,0x06,
+0x03,0x55,0x04,0x0B,0x14,0x37,0x77,0x77,0x77,0x2E,0x65,0x6E,0x74,0x72,0x75,0x73,
+0x74,0x2E,0x6E,0x65,0x74,0x2F,0x43,0x50,0x53,0x5F,0x32,0x30,0x34,0x38,0x20,0x69,
+0x6E,0x63,0x6F,0x72,0x70,0x2E,0x20,0x62,0x79,0x20,0x72,0x65,0x66,0x2E,0x20,0x28,
+0x6C,0x69,0x6D,0x69,0x74,0x73,0x20,0x6C,0x69,0x61,0x62,0x2E,0x29,0x31,0x25,0x30,
+0x23,0x06,0x03,0x55,0x04,0x0B,0x13,0x1C,0x28,0x63,0x29,0x20,0x31,0x39,0x39,0x39,
+0x20,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x20,0x4C,0x69,0x6D,
+0x69,0x74,0x65,0x64,0x31,0x33,0x30,0x31,0x06,0x03,0x55,0x04,0x03,0x13,0x2A,0x45,
+0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x20,0x43,0x65,0x72,0x74,0x69,
+0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,
+0x74,0x79,0x20,0x28,0x32,0x30,0x34,0x38,0x29,0x30,0x1E,0x17,0x0D,0x39,0x39,0x31,
+0x32,0x32,0x34,0x31,0x37,0x35,0x30,0x35,0x31,0x5A,0x17,0x0D,0x31,0x39,0x31,0x32,
+0x32,0x34,0x31,0x38,0x32,0x30,0x35,0x31,0x5A,0x30,0x81,0xB4,0x31,0x14,0x30,0x12,
+0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,
+0x65,0x74,0x31,0x40,0x30,0x3E,0x06,0x03,0x55,0x04,0x0B,0x14,0x37,0x77,0x77,0x77,
+0x2E,0x65,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x2F,0x43,0x50,0x53,
+0x5F,0x32,0x30,0x34,0x38,0x20,0x69,0x6E,0x63,0x6F,0x72,0x70,0x2E,0x20,0x62,0x79,
+0x20,0x72,0x65,0x66,0x2E,0x20,0x28,0x6C,0x69,0x6D,0x69,0x74,0x73,0x20,0x6C,0x69,
+0x61,0x62,0x2E,0x29,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x0B,0x13,0x1C,0x28,
+0x63,0x29,0x20,0x31,0x39,0x39,0x39,0x20,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,
+0x6E,0x65,0x74,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x33,0x30,0x31,0x06,
+0x03,0x55,0x04,0x03,0x13,0x2A,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,
+0x74,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,
+0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x28,0x32,0x30,0x34,0x38,0x29,
+0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,
+0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,
+0x00,0xAD,0x4D,0x4B,0xA9,0x12,0x86,0xB2,0xEA,0xA3,0x20,0x07,0x15,0x16,0x64,0x2A,
+0x2B,0x4B,0xD1,0xBF,0x0B,0x4A,0x4D,0x8E,0xED,0x80,0x76,0xA5,0x67,0xB7,0x78,0x40,
+0xC0,0x73,0x42,0xC8,0x68,0xC0,0xDB,0x53,0x2B,0xDD,0x5E,0xB8,0x76,0x98,0x35,0x93,
+0x8B,0x1A,0x9D,0x7C,0x13,0x3A,0x0E,0x1F,0x5B,0xB7,0x1E,0xCF,0xE5,0x24,0x14,0x1E,
+0xB1,0x81,0xA9,0x8D,0x7D,0xB8,0xCC,0x6B,0x4B,0x03,0xF1,0x02,0x0C,0xDC,0xAB,0xA5,
+0x40,0x24,0x00,0x7F,0x74,0x94,0xA1,0x9D,0x08,0x29,0xB3,0x88,0x0B,0xF5,0x87,0x77,
+0x9D,0x55,0xCD,0xE4,0xC3,0x7E,0xD7,0x6A,0x64,0xAB,0x85,0x14,0x86,0x95,0x5B,0x97,
+0x32,0x50,0x6F,0x3D,0xC8,0xBA,0x66,0x0C,0xE3,0xFC,0xBD,0xB8,0x49,0xC1,0x76,0x89,
+0x49,0x19,0xFD,0xC0,0xA8,0xBD,0x89,0xA3,0x67,0x2F,0xC6,0x9F,0xBC,0x71,0x19,0x60,
+0xB8,0x2D,0xE9,0x2C,0xC9,0x90,0x76,0x66,0x7B,0x94,0xE2,0xAF,0x78,0xD6,0x65,0x53,
+0x5D,0x3C,0xD6,0x9C,0xB2,0xCF,0x29,0x03,0xF9,0x2F,0xA4,0x50,0xB2,0xD4,0x48,0xCE,
+0x05,0x32,0x55,0x8A,0xFD,0xB2,0x64,0x4C,0x0E,0xE4,0x98,0x07,0x75,0xDB,0x7F,0xDF,
+0xB9,0x08,0x55,0x60,0x85,0x30,0x29,0xF9,0x7B,0x48,0xA4,0x69,0x86,0xE3,0x35,0x3F,
+0x1E,0x86,0x5D,0x7A,0x7A,0x15,0xBD,0xEF,0x00,0x8E,0x15,0x22,0x54,0x17,0x00,0x90,
+0x26,0x93,0xBC,0x0E,0x49,0x68,0x91,0xBF,0xF8,0x47,0xD3,0x9D,0x95,0x42,0xC1,0x0E,
+0x4D,0xDF,0x6F,0x26,0xCF,0xC3,0x18,0x21,0x62,0x66,0x43,0x70,0xD6,0xD5,0xC0,0x07,
+0xE1,0x02,0x03,0x01,0x00,0x01,0xA3,0x74,0x30,0x72,0x30,0x11,0x06,0x09,0x60,0x86,
+0x48,0x01,0x86,0xF8,0x42,0x01,0x01,0x04,0x04,0x03,0x02,0x00,0x07,0x30,0x1F,0x06,
+0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0x55,0xE4,0x81,0xD1,0x11,0x80,
+0xBE,0xD8,0x89,0xB9,0x08,0xA3,0x31,0xF9,0xA1,0x24,0x09,0x16,0xB9,0x70,0x30,0x1D,
+0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x55,0xE4,0x81,0xD1,0x11,0x80,0xBE,
+0xD8,0x89,0xB9,0x08,0xA3,0x31,0xF9,0xA1,0x24,0x09,0x16,0xB9,0x70,0x30,0x1D,0x06,
+0x09,0x2A,0x86,0x48,0x86,0xF6,0x7D,0x07,0x41,0x00,0x04,0x10,0x30,0x0E,0x1B,0x08,
+0x56,0x35,0x2E,0x30,0x3A,0x34,0x2E,0x30,0x03,0x02,0x04,0x90,0x30,0x0D,0x06,0x09,
+0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,
+0x59,0x47,0xAC,0x21,0x84,0x8A,0x17,0xC9,0x9C,0x89,0x53,0x1E,0xBA,0x80,0x85,0x1A,
+0xC6,0x3C,0x4E,0x3E,0xB1,0x9C,0xB6,0x7C,0xC6,0x92,0x5D,0x18,0x64,0x02,0xE3,0xD3,
+0x06,0x08,0x11,0x61,0x7C,0x63,0xE3,0x2B,0x9D,0x31,0x03,0x70,0x76,0xD2,0xA3,0x28,
+0xA0,0xF4,0xBB,0x9A,0x63,0x73,0xED,0x6D,0xE5,0x2A,0xDB,0xED,0x14,0xA9,0x2B,0xC6,
+0x36,0x11,0xD0,0x2B,0xEB,0x07,0x8B,0xA5,0xDA,0x9E,0x5C,0x19,0x9D,0x56,0x12,0xF5,
+0x54,0x29,0xC8,0x05,0xED,0xB2,0x12,0x2A,0x8D,0xF4,0x03,0x1B,0xFF,0xE7,0x92,0x10,
+0x87,0xB0,0x3A,0xB5,0xC3,0x9D,0x05,0x37,0x12,0xA3,0xC7,0xF4,0x15,0xB9,0xD5,0xA4,
+0x39,0x16,0x9B,0x53,0x3A,0x23,0x91,0xF1,0xA8,0x82,0xA2,0x6A,0x88,0x68,0xC1,0x79,
+0x02,0x22,0xBC,0xAA,0xA6,0xD6,0xAE,0xDF,0xB0,0x14,0x5F,0xB8,0x87,0xD0,0xDD,0x7C,
+0x7F,0x7B,0xFF,0xAF,0x1C,0xCF,0xE6,0xDB,0x07,0xAD,0x5E,0xDB,0x85,0x9D,0xD0,0x2B,
+0x0D,0x33,0xDB,0x04,0xD1,0xE6,0x49,0x40,0x13,0x2B,0x76,0xFB,0x3E,0xE9,0x9C,0x89,
+0x0F,0x15,0xCE,0x18,0xB0,0x85,0x78,0x21,0x4F,0x6B,0x4F,0x0E,0xFA,0x36,0x67,0xCD,
+0x07,0xF2,0xFF,0x08,0xD0,0xE2,0xDE,0xD9,0xBF,0x2A,0xAF,0xB8,0x87,0x86,0x21,0x3C,
+0x04,0xCA,0xB7,0x94,0x68,0x7F,0xCF,0x3C,0xE9,0x98,0xD7,0x38,0xFF,0xEC,0xC0,0xD9,
+0x50,0xF0,0x2E,0x4B,0x58,0xAE,0x46,0x6F,0xD0,0x2E,0xC3,0x60,0xDA,0x72,0x55,0x72,
+0xBD,0x4C,0x45,0x9E,0x61,0xBA,0xBF,0x84,0x81,0x92,0x03,0xD1,0xD2,0x69,0x7C,0xC5,
+};
+
+
+/* subject:/C=US/O=Entrust.net/OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/OU=(c) 1999 Entrust.net Limited/CN=Entrust.net Secure Server Certification Authority */
+/* issuer :/C=US/O=Entrust.net/OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/OU=(c) 1999 Entrust.net Limited/CN=Entrust.net Secure Server Certification Authority */
+
+
+const unsigned char Entrust_net_Secure_Server_CA_certificate[1244]={
+0x30,0x82,0x04,0xD8,0x30,0x82,0x04,0x41,0xA0,0x03,0x02,0x01,0x02,0x02,0x04,0x37,
+0x4A,0xD2,0x43,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,
+0x05,0x00,0x30,0x81,0xC3,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,
+0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,0x0B,0x45,0x6E,0x74,
+0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x31,0x3B,0x30,0x39,0x06,0x03,0x55,0x04,
+0x0B,0x13,0x32,0x77,0x77,0x77,0x2E,0x65,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,
+0x65,0x74,0x2F,0x43,0x50,0x53,0x20,0x69,0x6E,0x63,0x6F,0x72,0x70,0x2E,0x20,0x62,
+0x79,0x20,0x72,0x65,0x66,0x2E,0x20,0x28,0x6C,0x69,0x6D,0x69,0x74,0x73,0x20,0x6C,
+0x69,0x61,0x62,0x2E,0x29,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x0B,0x13,0x1C,
+0x28,0x63,0x29,0x20,0x31,0x39,0x39,0x39,0x20,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,
+0x2E,0x6E,0x65,0x74,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x31,0x3A,0x30,0x38,
+0x06,0x03,0x55,0x04,0x03,0x13,0x31,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,
+0x65,0x74,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x53,0x65,0x72,0x76,0x65,0x72,
+0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,
+0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x1E,0x17,0x0D,0x39,0x39,0x30,0x35,
+0x32,0x35,0x31,0x36,0x30,0x39,0x34,0x30,0x5A,0x17,0x0D,0x31,0x39,0x30,0x35,0x32,
+0x35,0x31,0x36,0x33,0x39,0x34,0x30,0x5A,0x30,0x81,0xC3,0x31,0x0B,0x30,0x09,0x06,
+0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,
+0x0A,0x13,0x0B,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x31,0x3B,
+0x30,0x39,0x06,0x03,0x55,0x04,0x0B,0x13,0x32,0x77,0x77,0x77,0x2E,0x65,0x6E,0x74,
+0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x2F,0x43,0x50,0x53,0x20,0x69,0x6E,0x63,
+0x6F,0x72,0x70,0x2E,0x20,0x62,0x79,0x20,0x72,0x65,0x66,0x2E,0x20,0x28,0x6C,0x69,
+0x6D,0x69,0x74,0x73,0x20,0x6C,0x69,0x61,0x62,0x2E,0x29,0x31,0x25,0x30,0x23,0x06,
+0x03,0x55,0x04,0x0B,0x13,0x1C,0x28,0x63,0x29,0x20,0x31,0x39,0x39,0x39,0x20,0x45,
+0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x20,0x4C,0x69,0x6D,0x69,0x74,
+0x65,0x64,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x03,0x13,0x31,0x45,0x6E,0x74,
+0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,
+0x53,0x65,0x72,0x76,0x65,0x72,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,
+0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x81,
+0x9D,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,
+0x03,0x81,0x8B,0x00,0x30,0x81,0x87,0x02,0x81,0x81,0x00,0xCD,0x28,0x83,0x34,0x54,
+0x1B,0x89,0xF3,0x0F,0xAF,0x37,0x91,0x31,0xFF,0xAF,0x31,0x60,0xC9,0xA8,0xE8,0xB2,
+0x10,0x68,0xED,0x9F,0xE7,0x93,0x36,0xF1,0x0A,0x64,0xBB,0x47,0xF5,0x04,0x17,0x3F,
+0x23,0x47,0x4D,0xC5,0x27,0x19,0x81,0x26,0x0C,0x54,0x72,0x0D,0x88,0x2D,0xD9,0x1F,
+0x9A,0x12,0x9F,0xBC,0xB3,0x71,0xD3,0x80,0x19,0x3F,0x47,0x66,0x7B,0x8C,0x35,0x28,
+0xD2,0xB9,0x0A,0xDF,0x24,0xDA,0x9C,0xD6,0x50,0x79,0x81,0x7A,0x5A,0xD3,0x37,0xF7,
+0xC2,0x4A,0xD8,0x29,0x92,0x26,0x64,0xD1,0xE4,0x98,0x6C,0x3A,0x00,0x8A,0xF5,0x34,
+0x9B,0x65,0xF8,0xED,0xE3,0x10,0xFF,0xFD,0xB8,0x49,0x58,0xDC,0xA0,0xDE,0x82,0x39,
+0x6B,0x81,0xB1,0x16,0x19,0x61,0xB9,0x54,0xB6,0xE6,0x43,0x02,0x01,0x03,0xA3,0x82,
+0x01,0xD7,0x30,0x82,0x01,0xD3,0x30,0x11,0x06,0x09,0x60,0x86,0x48,0x01,0x86,0xF8,
+0x42,0x01,0x01,0x04,0x04,0x03,0x02,0x00,0x07,0x30,0x82,0x01,0x19,0x06,0x03,0x55,
+0x1D,0x1F,0x04,0x82,0x01,0x10,0x30,0x82,0x01,0x0C,0x30,0x81,0xDE,0xA0,0x81,0xDB,
+0xA0,0x81,0xD8,0xA4,0x81,0xD5,0x30,0x81,0xD2,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,
+0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x0A,0x13,
+0x0B,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x31,0x3B,0x30,0x39,
+0x06,0x03,0x55,0x04,0x0B,0x13,0x32,0x77,0x77,0x77,0x2E,0x65,0x6E,0x74,0x72,0x75,
+0x73,0x74,0x2E,0x6E,0x65,0x74,0x2F,0x43,0x50,0x53,0x20,0x69,0x6E,0x63,0x6F,0x72,
+0x70,0x2E,0x20,0x62,0x79,0x20,0x72,0x65,0x66,0x2E,0x20,0x28,0x6C,0x69,0x6D,0x69,
+0x74,0x73,0x20,0x6C,0x69,0x61,0x62,0x2E,0x29,0x31,0x25,0x30,0x23,0x06,0x03,0x55,
+0x04,0x0B,0x13,0x1C,0x28,0x63,0x29,0x20,0x31,0x39,0x39,0x39,0x20,0x45,0x6E,0x74,
+0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x20,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,
+0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x03,0x13,0x31,0x45,0x6E,0x74,0x72,0x75,
+0x73,0x74,0x2E,0x6E,0x65,0x74,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x53,0x65,
+0x72,0x76,0x65,0x72,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,
+0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x31,0x0D,0x30,0x0B,
+0x06,0x03,0x55,0x04,0x03,0x13,0x04,0x43,0x52,0x4C,0x31,0x30,0x29,0xA0,0x27,0xA0,
+0x25,0x86,0x23,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x65,0x6E,
+0x74,0x72,0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x2F,0x43,0x52,0x4C,0x2F,0x6E,0x65,
+0x74,0x31,0x2E,0x63,0x72,0x6C,0x30,0x2B,0x06,0x03,0x55,0x1D,0x10,0x04,0x24,0x30,
+0x22,0x80,0x0F,0x31,0x39,0x39,0x39,0x30,0x35,0x32,0x35,0x31,0x36,0x30,0x39,0x34,
+0x30,0x5A,0x81,0x0F,0x32,0x30,0x31,0x39,0x30,0x35,0x32,0x35,0x31,0x36,0x30,0x39,
+0x34,0x30,0x5A,0x30,0x0B,0x06,0x03,0x55,0x1D,0x0F,0x04,0x04,0x03,0x02,0x01,0x06,
+0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0xF0,0x17,0x62,
+0x13,0x55,0x3D,0xB3,0xFF,0x0A,0x00,0x6B,0xFB,0x50,0x84,0x97,0xF3,0xED,0x62,0xD0,
+0x1A,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xF0,0x17,0x62,0x13,
+0x55,0x3D,0xB3,0xFF,0x0A,0x00,0x6B,0xFB,0x50,0x84,0x97,0xF3,0xED,0x62,0xD0,0x1A,
+0x30,0x0C,0x06,0x03,0x55,0x1D,0x13,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x19,
+0x06,0x09,0x2A,0x86,0x48,0x86,0xF6,0x7D,0x07,0x41,0x00,0x04,0x0C,0x30,0x0A,0x1B,
+0x04,0x56,0x34,0x2E,0x30,0x03,0x02,0x04,0x90,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,
+0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x81,0x81,0x00,0x90,0xDC,0x30,0x02,
+0xFA,0x64,0x74,0xC2,0xA7,0x0A,0xA5,0x7C,0x21,0x8D,0x34,0x17,0xA8,0xFB,0x47,0x0E,
+0xFF,0x25,0x7C,0x8D,0x13,0x0A,0xFB,0xE4,0x98,0xB5,0xEF,0x8C,0xF8,0xC5,0x10,0x0D,
+0xF7,0x92,0xBE,0xF1,0xC3,0xD5,0xD5,0x95,0x6A,0x04,0xBB,0x2C,0xCE,0x26,0x36,0x65,
+0xC8,0x31,0xC6,0xE7,0xEE,0x3F,0xE3,0x57,0x75,0x84,0x7A,0x11,0xEF,0x46,0x4F,0x18,
+0xF4,0xD3,0x98,0xBB,0xA8,0x87,0x32,0xBA,0x72,0xF6,0x3C,0xE2,0x3D,0x9F,0xD7,0x1D,
+0xD9,0xC3,0x60,0x43,0x8C,0x58,0x0E,0x22,0x96,0x2F,0x62,0xA3,0x2C,0x1F,0xBA,0xAD,
+0x05,0xEF,0xAB,0x32,0x78,0x87,0xA0,0x54,0x73,0x19,0xB5,0x5C,0x05,0xF9,0x52,0x3E,
+0x6D,0x2D,0x45,0x0B,0xF7,0x0A,0x93,0xEA,0xED,0x06,0xF9,0xB2,
+};
+
+
+/* subject:/C=US/O=Entrust, Inc./OU=www.entrust.net/CPS is incorporated by reference/OU=(c) 2006 Entrust, Inc./CN=Entrust Root Certification Authority */
+/* issuer :/C=US/O=Entrust, Inc./OU=www.entrust.net/CPS is incorporated by reference/OU=(c) 2006 Entrust, Inc./CN=Entrust Root Certification Authority */
+
+
+const unsigned char Entrust_Root_Certification_Authority_certificate[1173]={
+0x30,0x82,0x04,0x91,0x30,0x82,0x03,0x79,0xA0,0x03,0x02,0x01,0x02,0x02,0x04,0x45,
+0x6B,0x50,0x54,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,
+0x05,0x00,0x30,0x81,0xB0,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,
+0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x45,0x6E,0x74,
+0x72,0x75,0x73,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x39,0x30,0x37,0x06,0x03,
+0x55,0x04,0x0B,0x13,0x30,0x77,0x77,0x77,0x2E,0x65,0x6E,0x74,0x72,0x75,0x73,0x74,
+0x2E,0x6E,0x65,0x74,0x2F,0x43,0x50,0x53,0x20,0x69,0x73,0x20,0x69,0x6E,0x63,0x6F,
+0x72,0x70,0x6F,0x72,0x61,0x74,0x65,0x64,0x20,0x62,0x79,0x20,0x72,0x65,0x66,0x65,
+0x72,0x65,0x6E,0x63,0x65,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16,
+0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x36,0x20,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,
+0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x2D,0x30,0x2B,0x06,0x03,0x55,0x04,0x03,0x13,
+0x24,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65,
+0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,
+0x6F,0x72,0x69,0x74,0x79,0x30,0x1E,0x17,0x0D,0x30,0x36,0x31,0x31,0x32,0x37,0x32,
+0x30,0x32,0x33,0x34,0x32,0x5A,0x17,0x0D,0x32,0x36,0x31,0x31,0x32,0x37,0x32,0x30,
+0x35,0x33,0x34,0x32,0x5A,0x30,0x81,0xB0,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,
+0x06,0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,
+0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x39,0x30,
+0x37,0x06,0x03,0x55,0x04,0x0B,0x13,0x30,0x77,0x77,0x77,0x2E,0x65,0x6E,0x74,0x72,
+0x75,0x73,0x74,0x2E,0x6E,0x65,0x74,0x2F,0x43,0x50,0x53,0x20,0x69,0x73,0x20,0x69,
+0x6E,0x63,0x6F,0x72,0x70,0x6F,0x72,0x61,0x74,0x65,0x64,0x20,0x62,0x79,0x20,0x72,
+0x65,0x66,0x65,0x72,0x65,0x6E,0x63,0x65,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,
+0x0B,0x13,0x16,0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x36,0x20,0x45,0x6E,0x74,0x72,
+0x75,0x73,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x2D,0x30,0x2B,0x06,0x03,0x55,
+0x04,0x03,0x13,0x24,0x45,0x6E,0x74,0x72,0x75,0x73,0x74,0x20,0x52,0x6F,0x6F,0x74,
+0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,
+0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,
+0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,
+0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xB6,0x95,0xB6,0x43,0x42,0xFA,0xC6,
+0x6D,0x2A,0x6F,0x48,0xDF,0x94,0x4C,0x39,0x57,0x05,0xEE,0xC3,0x79,0x11,0x41,0x68,
+0x36,0xED,0xEC,0xFE,0x9A,0x01,0x8F,0xA1,0x38,0x28,0xFC,0xF7,0x10,0x46,0x66,0x2E,
+0x4D,0x1E,0x1A,0xB1,0x1A,0x4E,0xC6,0xD1,0xC0,0x95,0x88,0xB0,0xC9,0xFF,0x31,0x8B,
+0x33,0x03,0xDB,0xB7,0x83,0x7B,0x3E,0x20,0x84,0x5E,0xED,0xB2,0x56,0x28,0xA7,0xF8,
+0xE0,0xB9,0x40,0x71,0x37,0xC5,0xCB,0x47,0x0E,0x97,0x2A,0x68,0xC0,0x22,0x95,0x62,
+0x15,0xDB,0x47,0xD9,0xF5,0xD0,0x2B,0xFF,0x82,0x4B,0xC9,0xAD,0x3E,0xDE,0x4C,0xDB,
+0x90,0x80,0x50,0x3F,0x09,0x8A,0x84,0x00,0xEC,0x30,0x0A,0x3D,0x18,0xCD,0xFB,0xFD,
+0x2A,0x59,0x9A,0x23,0x95,0x17,0x2C,0x45,0x9E,0x1F,0x6E,0x43,0x79,0x6D,0x0C,0x5C,
+0x98,0xFE,0x48,0xA7,0xC5,0x23,0x47,0x5C,0x5E,0xFD,0x6E,0xE7,0x1E,0xB4,0xF6,0x68,
+0x45,0xD1,0x86,0x83,0x5B,0xA2,0x8A,0x8D,0xB1,0xE3,0x29,0x80,0xFE,0x25,0x71,0x88,
+0xAD,0xBE,0xBC,0x8F,0xAC,0x52,0x96,0x4B,0xAA,0x51,0x8D,0xE4,0x13,0x31,0x19,0xE8,
+0x4E,0x4D,0x9F,0xDB,0xAC,0xB3,0x6A,0xD5,0xBC,0x39,0x54,0x71,0xCA,0x7A,0x7A,0x7F,
+0x90,0xDD,0x7D,0x1D,0x80,0xD9,0x81,0xBB,0x59,0x26,0xC2,0x11,0xFE,0xE6,0x93,0xE2,
+0xF7,0x80,0xE4,0x65,0xFB,0x34,0x37,0x0E,0x29,0x80,0x70,0x4D,0xAF,0x38,0x86,0x2E,
+0x9E,0x7F,0x57,0xAF,0x9E,0x17,0xAE,0xEB,0x1C,0xCB,0x28,0x21,0x5F,0xB6,0x1C,0xD8,
+0xE7,0xA2,0x04,0x22,0xF9,0xD3,0xDA,0xD8,0xCB,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,
+0xB0,0x30,0x81,0xAD,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,
+0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,
+0x30,0x03,0x01,0x01,0xFF,0x30,0x2B,0x06,0x03,0x55,0x1D,0x10,0x04,0x24,0x30,0x22,
+0x80,0x0F,0x32,0x30,0x30,0x36,0x31,0x31,0x32,0x37,0x32,0x30,0x32,0x33,0x34,0x32,
+0x5A,0x81,0x0F,0x32,0x30,0x32,0x36,0x31,0x31,0x32,0x37,0x32,0x30,0x35,0x33,0x34,
+0x32,0x5A,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0x68,
+0x90,0xE4,0x67,0xA4,0xA6,0x53,0x80,0xC7,0x86,0x66,0xA4,0xF1,0xF7,0x4B,0x43,0xFB,
+0x84,0xBD,0x6D,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x68,0x90,
+0xE4,0x67,0xA4,0xA6,0x53,0x80,0xC7,0x86,0x66,0xA4,0xF1,0xF7,0x4B,0x43,0xFB,0x84,
+0xBD,0x6D,0x30,0x1D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF6,0x7D,0x07,0x41,0x00,0x04,
+0x10,0x30,0x0E,0x1B,0x08,0x56,0x37,0x2E,0x31,0x3A,0x34,0x2E,0x30,0x03,0x02,0x04,
+0x90,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,
+0x03,0x82,0x01,0x01,0x00,0x93,0xD4,0x30,0xB0,0xD7,0x03,0x20,0x2A,0xD0,0xF9,0x63,
+0xE8,0x91,0x0C,0x05,0x20,0xA9,0x5F,0x19,0xCA,0x7B,0x72,0x4E,0xD4,0xB1,0xDB,0xD0,
+0x96,0xFB,0x54,0x5A,0x19,0x2C,0x0C,0x08,0xF7,0xB2,0xBC,0x85,0xA8,0x9D,0x7F,0x6D,
+0x3B,0x52,0xB3,0x2A,0xDB,0xE7,0xD4,0x84,0x8C,0x63,0xF6,0x0F,0xCB,0x26,0x01,0x91,
+0x50,0x6C,0xF4,0x5F,0x14,0xE2,0x93,0x74,0xC0,0x13,0x9E,0x30,0x3A,0x50,0xE3,0xB4,
+0x60,0xC5,0x1C,0xF0,0x22,0x44,0x8D,0x71,0x47,0xAC,0xC8,0x1A,0xC9,0xE9,0x9B,0x9A,
+0x00,0x60,0x13,0xFF,0x70,0x7E,0x5F,0x11,0x4D,0x49,0x1B,0xB3,0x15,0x52,0x7B,0xC9,
+0x54,0xDA,0xBF,0x9D,0x95,0xAF,0x6B,0x9A,0xD8,0x9E,0xE9,0xF1,0xE4,0x43,0x8D,0xE2,
+0x11,0x44,0x3A,0xBF,0xAF,0xBD,0x83,0x42,0x73,0x52,0x8B,0xAA,0xBB,0xA7,0x29,0xCF,
+0xF5,0x64,0x1C,0x0A,0x4D,0xD1,0xBC,0xAA,0xAC,0x9F,0x2A,0xD0,0xFF,0x7F,0x7F,0xDA,
+0x7D,0xEA,0xB1,0xED,0x30,0x25,0xC1,0x84,0xDA,0x34,0xD2,0x5B,0x78,0x83,0x56,0xEC,
+0x9C,0x36,0xC3,0x26,0xE2,0x11,0xF6,0x67,0x49,0x1D,0x92,0xAB,0x8C,0xFB,0xEB,0xFF,
+0x7A,0xEE,0x85,0x4A,0xA7,0x50,0x80,0xF0,0xA7,0x5C,0x4A,0x94,0x2E,0x5F,0x05,0x99,
+0x3C,0x52,0x41,0xE0,0xCD,0xB4,0x63,0xCF,0x01,0x43,0xBA,0x9C,0x83,0xDC,0x8F,0x60,
+0x3B,0xF3,0x5A,0xB4,0xB4,0x7B,0xAE,0xDA,0x0B,0x90,0x38,0x75,0xEF,0x81,0x1D,0x66,
+0xD2,0xF7,0x57,0x70,0x36,0xB3,0xBF,0xFC,0x28,0xAF,0x71,0x25,0x85,0x5B,0x13,0xFE,
+0x1E,0x7F,0x5A,0xB4,0x3C,
+};
+
+
+/* subject:/C=US/O=Equifax/OU=Equifax Secure Certificate Authority */
+/* issuer :/C=US/O=Equifax/OU=Equifax Secure Certificate Authority */
+
+
+const unsigned char Equifax_Secure_CA_certificate[804]={
+0x30,0x82,0x03,0x20,0x30,0x82,0x02,0x89,0xA0,0x03,0x02,0x01,0x02,0x02,0x04,0x35,
+0xDE,0xF4,0xCF,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,
+0x05,0x00,0x30,0x4E,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,
+0x53,0x31,0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x0A,0x13,0x07,0x45,0x71,0x75,0x69,
+0x66,0x61,0x78,0x31,0x2D,0x30,0x2B,0x06,0x03,0x55,0x04,0x0B,0x13,0x24,0x45,0x71,
+0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x43,0x65,0x72,
+0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,
+0x74,0x79,0x30,0x1E,0x17,0x0D,0x39,0x38,0x30,0x38,0x32,0x32,0x31,0x36,0x34,0x31,
+0x35,0x31,0x5A,0x17,0x0D,0x31,0x38,0x30,0x38,0x32,0x32,0x31,0x36,0x34,0x31,0x35,
+0x31,0x5A,0x30,0x4E,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,
+0x53,0x31,0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x0A,0x13,0x07,0x45,0x71,0x75,0x69,
+0x66,0x61,0x78,0x31,0x2D,0x30,0x2B,0x06,0x03,0x55,0x04,0x0B,0x13,0x24,0x45,0x71,
+0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x43,0x65,0x72,
+0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,
+0x74,0x79,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,
+0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89,0x02,0x81,0x81,0x00,0xC1,
+0x5D,0xB1,0x58,0x67,0x08,0x62,0xEE,0xA0,0x9A,0x2D,0x1F,0x08,0x6D,0x91,0x14,0x68,
+0x98,0x0A,0x1E,0xFE,0xDA,0x04,0x6F,0x13,0x84,0x62,0x21,0xC3,0xD1,0x7C,0xCE,0x9F,
+0x05,0xE0,0xB8,0x01,0xF0,0x4E,0x34,0xEC,0xE2,0x8A,0x95,0x04,0x64,0xAC,0xF1,0x6B,
+0x53,0x5F,0x05,0xB3,0xCB,0x67,0x80,0xBF,0x42,0x02,0x8E,0xFE,0xDD,0x01,0x09,0xEC,
+0xE1,0x00,0x14,0x4F,0xFC,0xFB,0xF0,0x0C,0xDD,0x43,0xBA,0x5B,0x2B,0xE1,0x1F,0x80,
+0x70,0x99,0x15,0x57,0x93,0x16,0xF1,0x0F,0x97,0x6A,0xB7,0xC2,0x68,0x23,0x1C,0xCC,
+0x4D,0x59,0x30,0xAC,0x51,0x1E,0x3B,0xAF,0x2B,0xD6,0xEE,0x63,0x45,0x7B,0xC5,0xD9,
+0x5F,0x50,0xD2,0xE3,0x50,0x0F,0x3A,0x88,0xE7,0xBF,0x14,0xFD,0xE0,0xC7,0xB9,0x02,
+0x03,0x01,0x00,0x01,0xA3,0x82,0x01,0x09,0x30,0x82,0x01,0x05,0x30,0x70,0x06,0x03,
+0x55,0x1D,0x1F,0x04,0x69,0x30,0x67,0x30,0x65,0xA0,0x63,0xA0,0x61,0xA4,0x5F,0x30,
+0x5D,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x10,
+0x30,0x0E,0x06,0x03,0x55,0x04,0x0A,0x13,0x07,0x45,0x71,0x75,0x69,0x66,0x61,0x78,
+0x31,0x2D,0x30,0x2B,0x06,0x03,0x55,0x04,0x0B,0x13,0x24,0x45,0x71,0x75,0x69,0x66,
+0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x43,0x65,0x72,0x74,0x69,0x66,
+0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x31,
+0x0D,0x30,0x0B,0x06,0x03,0x55,0x04,0x03,0x13,0x04,0x43,0x52,0x4C,0x31,0x30,0x1A,
+0x06,0x03,0x55,0x1D,0x10,0x04,0x13,0x30,0x11,0x81,0x0F,0x32,0x30,0x31,0x38,0x30,
+0x38,0x32,0x32,0x31,0x36,0x34,0x31,0x35,0x31,0x5A,0x30,0x0B,0x06,0x03,0x55,0x1D,
+0x0F,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,
+0x30,0x16,0x80,0x14,0x48,0xE6,0x68,0xF9,0x2B,0xD2,0xB2,0x95,0xD7,0x47,0xD8,0x23,
+0x20,0x10,0x4F,0x33,0x98,0x90,0x9F,0xD4,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,
+0x16,0x04,0x14,0x48,0xE6,0x68,0xF9,0x2B,0xD2,0xB2,0x95,0xD7,0x47,0xD8,0x23,0x20,
+0x10,0x4F,0x33,0x98,0x90,0x9F,0xD4,0x30,0x0C,0x06,0x03,0x55,0x1D,0x13,0x04,0x05,
+0x30,0x03,0x01,0x01,0xFF,0x30,0x1A,0x06,0x09,0x2A,0x86,0x48,0x86,0xF6,0x7D,0x07,
+0x41,0x00,0x04,0x0D,0x30,0x0B,0x1B,0x05,0x56,0x33,0x2E,0x30,0x63,0x03,0x02,0x06,
+0xC0,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,
+0x03,0x81,0x81,0x00,0x58,0xCE,0x29,0xEA,0xFC,0xF7,0xDE,0xB5,0xCE,0x02,0xB9,0x17,
+0xB5,0x85,0xD1,0xB9,0xE3,0xE0,0x95,0xCC,0x25,0x31,0x0D,0x00,0xA6,0x92,0x6E,0x7F,
+0xB6,0x92,0x63,0x9E,0x50,0x95,0xD1,0x9A,0x6F,0xE4,0x11,0xDE,0x63,0x85,0x6E,0x98,
+0xEE,0xA8,0xFF,0x5A,0xC8,0xD3,0x55,0xB2,0x66,0x71,0x57,0xDE,0xC0,0x21,0xEB,0x3D,
+0x2A,0xA7,0x23,0x49,0x01,0x04,0x86,0x42,0x7B,0xFC,0xEE,0x7F,0xA2,0x16,0x52,0xB5,
+0x67,0x67,0xD3,0x40,0xDB,0x3B,0x26,0x58,0xB2,0x28,0x77,0x3D,0xAE,0x14,0x77,0x61,
+0xD6,0xFA,0x2A,0x66,0x27,0xA0,0x0D,0xFA,0xA7,0x73,0x5C,0xEA,0x70,0xF1,0x94,0x21,
+0x65,0x44,0x5F,0xFA,0xFC,0xEF,0x29,0x68,0xA9,0xA2,0x87,0x79,0xEF,0x79,0xEF,0x4F,
+0xAC,0x07,0x77,0x38,
+};
+
+
+/* subject:/C=US/O=Equifax Secure Inc./CN=Equifax Secure eBusiness CA-1 */
+/* issuer :/C=US/O=Equifax Secure Inc./CN=Equifax Secure eBusiness CA-1 */
+
+
+const unsigned char Equifax_Secure_eBusiness_CA_1_certificate[646]={
+0x30,0x82,0x02,0x82,0x30,0x82,0x01,0xEB,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x04,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,0x30,
+0x53,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x1C,
+0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x45,0x71,0x75,0x69,0x66,0x61,0x78,
+0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x49,0x6E,0x63,0x2E,0x31,0x26,0x30,0x24,
+0x06,0x03,0x55,0x04,0x03,0x13,0x1D,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53,
+0x65,0x63,0x75,0x72,0x65,0x20,0x65,0x42,0x75,0x73,0x69,0x6E,0x65,0x73,0x73,0x20,
+0x43,0x41,0x2D,0x31,0x30,0x1E,0x17,0x0D,0x39,0x39,0x30,0x36,0x32,0x31,0x30,0x34,
+0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x30,0x30,0x36,0x32,0x31,0x30,0x34,0x30,
+0x30,0x30,0x30,0x5A,0x30,0x53,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,
+0x02,0x55,0x53,0x31,0x1C,0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x45,0x71,
+0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x49,0x6E,0x63,
+0x2E,0x31,0x26,0x30,0x24,0x06,0x03,0x55,0x04,0x03,0x13,0x1D,0x45,0x71,0x75,0x69,
+0x66,0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x65,0x42,0x75,0x73,0x69,
+0x6E,0x65,0x73,0x73,0x20,0x43,0x41,0x2D,0x31,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,
+0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,
+0x81,0x89,0x02,0x81,0x81,0x00,0xCE,0x2F,0x19,0xBC,0x17,0xB7,0x77,0xDE,0x93,0xA9,
+0x5F,0x5A,0x0D,0x17,0x4F,0x34,0x1A,0x0C,0x98,0xF4,0x22,0xD9,0x59,0xD4,0xC4,0x68,
+0x46,0xF0,0xB4,0x35,0xC5,0x85,0x03,0x20,0xC6,0xAF,0x45,0xA5,0x21,0x51,0x45,0x41,
+0xEB,0x16,0x58,0x36,0x32,0x6F,0xE2,0x50,0x62,0x64,0xF9,0xFD,0x51,0x9C,0xAA,0x24,
+0xD9,0xF4,0x9D,0x83,0x2A,0x87,0x0A,0x21,0xD3,0x12,0x38,0x34,0x6C,0x8D,0x00,0x6E,
+0x5A,0xA0,0xD9,0x42,0xEE,0x1A,0x21,0x95,0xF9,0x52,0x4C,0x55,0x5A,0xC5,0x0F,0x38,
+0x4F,0x46,0xFA,0x6D,0xF8,0x2E,0x35,0xD6,0x1D,0x7C,0xEB,0xE2,0xF0,0xB0,0x75,0x80,
+0xC8,0xA9,0x13,0xAC,0xBE,0x88,0xEF,0x3A,0x6E,0xAB,0x5F,0x2A,0x38,0x62,0x02,0xB0,
+0x12,0x7B,0xFE,0x8F,0xA6,0x03,0x02,0x03,0x01,0x00,0x01,0xA3,0x66,0x30,0x64,0x30,
+0x11,0x06,0x09,0x60,0x86,0x48,0x01,0x86,0xF8,0x42,0x01,0x01,0x04,0x04,0x03,0x02,
+0x00,0x07,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,
+0x01,0x01,0xFF,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,
+0x4A,0x78,0x32,0x52,0x11,0xDB,0x59,0x16,0x36,0x5E,0xDF,0xC1,0x14,0x36,0x40,0x6A,
+0x47,0x7C,0x4C,0xA1,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x4A,
+0x78,0x32,0x52,0x11,0xDB,0x59,0x16,0x36,0x5E,0xDF,0xC1,0x14,0x36,0x40,0x6A,0x47,
+0x7C,0x4C,0xA1,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,
+0x05,0x00,0x03,0x81,0x81,0x00,0x75,0x5B,0xA8,0x9B,0x03,0x11,0xE6,0xE9,0x56,0x4C,
+0xCD,0xF9,0xA9,0x4C,0xC0,0x0D,0x9A,0xF3,0xCC,0x65,0x69,0xE6,0x25,0x76,0xCC,0x59,
+0xB7,0xD6,0x54,0xC3,0x1D,0xCD,0x99,0xAC,0x19,0xDD,0xB4,0x85,0xD5,0xE0,0x3D,0xFC,
+0x62,0x20,0xA7,0x84,0x4B,0x58,0x65,0xF1,0xE2,0xF9,0x95,0x21,0x3F,0xF5,0xD4,0x7E,
+0x58,0x1E,0x47,0x87,0x54,0x3E,0x58,0xA1,0xB5,0xB5,0xF8,0x2A,0xEF,0x71,0xE7,0xBC,
+0xC3,0xF6,0xB1,0x49,0x46,0xE2,0xD7,0xA0,0x6B,0xE5,0x56,0x7A,0x9A,0x27,0x98,0x7C,
+0x46,0x62,0x14,0xE7,0xC9,0xFC,0x6E,0x03,0x12,0x79,0x80,0x38,0x1D,0x48,0x82,0x8D,
+0xFC,0x17,0xFE,0x2A,0x96,0x2B,0xB5,0x62,0xA6,0xA6,0x3D,0xBD,0x7F,0x92,0x59,0xCD,
+0x5A,0x2A,0x82,0xB2,0x37,0x79,
+};
+
+
+/* subject:/C=US/O=Equifax Secure/OU=Equifax Secure eBusiness CA-2 */
+/* issuer :/C=US/O=Equifax Secure/OU=Equifax Secure eBusiness CA-2 */
+
+
+const unsigned char Equifax_Secure_eBusiness_CA_2_certificate[804]={
+0x30,0x82,0x03,0x20,0x30,0x82,0x02,0x89,0xA0,0x03,0x02,0x01,0x02,0x02,0x04,0x37,
+0x70,0xCF,0xB5,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,
+0x05,0x00,0x30,0x4E,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,
+0x53,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x45,0x71,0x75,0x69,
+0x66,0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x31,0x26,0x30,0x24,0x06,0x03,
+0x55,0x04,0x0B,0x13,0x1D,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63,
+0x75,0x72,0x65,0x20,0x65,0x42,0x75,0x73,0x69,0x6E,0x65,0x73,0x73,0x20,0x43,0x41,
+0x2D,0x32,0x30,0x1E,0x17,0x0D,0x39,0x39,0x30,0x36,0x32,0x33,0x31,0x32,0x31,0x34,
+0x34,0x35,0x5A,0x17,0x0D,0x31,0x39,0x30,0x36,0x32,0x33,0x31,0x32,0x31,0x34,0x34,
+0x35,0x5A,0x30,0x4E,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,
+0x53,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x45,0x71,0x75,0x69,
+0x66,0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x31,0x26,0x30,0x24,0x06,0x03,
+0x55,0x04,0x0B,0x13,0x1D,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63,
+0x75,0x72,0x65,0x20,0x65,0x42,0x75,0x73,0x69,0x6E,0x65,0x73,0x73,0x20,0x43,0x41,
+0x2D,0x32,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,
+0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89,0x02,0x81,0x81,0x00,0xE4,
+0x39,0x39,0x93,0x1E,0x52,0x06,0x1B,0x28,0x36,0xF8,0xB2,0xA3,0x29,0xC5,0xED,0x8E,
+0xB2,0x11,0xBD,0xFE,0xEB,0xE7,0xB4,0x74,0xC2,0x8F,0xFF,0x05,0xE7,0xD9,0x9D,0x06,
+0xBF,0x12,0xC8,0x3F,0x0E,0xF2,0xD6,0xD1,0x24,0xB2,0x11,0xDE,0xD1,0x73,0x09,0x8A,
+0xD4,0xB1,0x2C,0x98,0x09,0x0D,0x1E,0x50,0x46,0xB2,0x83,0xA6,0x45,0x8D,0x62,0x68,
+0xBB,0x85,0x1B,0x20,0x70,0x32,0xAA,0x40,0xCD,0xA6,0x96,0x5F,0xC4,0x71,0x37,0x3F,
+0x04,0xF3,0xB7,0x41,0x24,0x39,0x07,0x1A,0x1E,0x2E,0x61,0x58,0xA0,0x12,0x0B,0xE5,
+0xA5,0xDF,0xC5,0xAB,0xEA,0x37,0x71,0xCC,0x1C,0xC8,0x37,0x3A,0xB9,0x97,0x52,0xA7,
+0xAC,0xC5,0x6A,0x24,0x94,0x4E,0x9C,0x7B,0xCF,0xC0,0x6A,0xD6,0xDF,0x21,0xBD,0x02,
+0x03,0x01,0x00,0x01,0xA3,0x82,0x01,0x09,0x30,0x82,0x01,0x05,0x30,0x70,0x06,0x03,
+0x55,0x1D,0x1F,0x04,0x69,0x30,0x67,0x30,0x65,0xA0,0x63,0xA0,0x61,0xA4,0x5F,0x30,
+0x5D,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,
+0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x45,0x71,0x75,0x69,0x66,0x61,0x78,
+0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x31,0x26,0x30,0x24,0x06,0x03,0x55,0x04,0x0B,
+0x13,0x1D,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,0x65,
+0x20,0x65,0x42,0x75,0x73,0x69,0x6E,0x65,0x73,0x73,0x20,0x43,0x41,0x2D,0x32,0x31,
+0x0D,0x30,0x0B,0x06,0x03,0x55,0x04,0x03,0x13,0x04,0x43,0x52,0x4C,0x31,0x30,0x1A,
+0x06,0x03,0x55,0x1D,0x10,0x04,0x13,0x30,0x11,0x81,0x0F,0x32,0x30,0x31,0x39,0x30,
+0x36,0x32,0x33,0x31,0x32,0x31,0x34,0x34,0x35,0x5A,0x30,0x0B,0x06,0x03,0x55,0x1D,
+0x0F,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,
+0x30,0x16,0x80,0x14,0x50,0x9E,0x0B,0xEA,0xAF,0x5E,0xB9,0x20,0x48,0xA6,0x50,0x6A,
+0xCB,0xFD,0xD8,0x20,0x7A,0xA7,0x82,0x76,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,
+0x16,0x04,0x14,0x50,0x9E,0x0B,0xEA,0xAF,0x5E,0xB9,0x20,0x48,0xA6,0x50,0x6A,0xCB,
+0xFD,0xD8,0x20,0x7A,0xA7,0x82,0x76,0x30,0x0C,0x06,0x03,0x55,0x1D,0x13,0x04,0x05,
+0x30,0x03,0x01,0x01,0xFF,0x30,0x1A,0x06,0x09,0x2A,0x86,0x48,0x86,0xF6,0x7D,0x07,
+0x41,0x00,0x04,0x0D,0x30,0x0B,0x1B,0x05,0x56,0x33,0x2E,0x30,0x63,0x03,0x02,0x06,
+0xC0,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,
+0x03,0x81,0x81,0x00,0x0C,0x86,0x82,0xAD,0xE8,0x4E,0x1A,0xF5,0x8E,0x89,0x27,0xE2,
+0x35,0x58,0x3D,0x29,0xB4,0x07,0x8F,0x36,0x50,0x95,0xBF,0x6E,0xC1,0x9E,0xEB,0xC4,
+0x90,0xB2,0x85,0xA8,0xBB,0xB7,0x42,0xE0,0x0F,0x07,0x39,0xDF,0xFB,0x9E,0x90,0xB2,
+0xD1,0xC1,0x3E,0x53,0x9F,0x03,0x44,0xB0,0x7E,0x4B,0xF4,0x6F,0xE4,0x7C,0x1F,0xE7,
+0xE2,0xB1,0xE4,0xB8,0x9A,0xEF,0xC3,0xBD,0xCE,0xDE,0x0B,0x32,0x34,0xD9,0xDE,0x28,
+0xED,0x33,0x6B,0xC4,0xD4,0xD7,0x3D,0x12,0x58,0xAB,0x7D,0x09,0x2D,0xCB,0x70,0xF5,
+0x13,0x8A,0x94,0xA1,0x27,0xA4,0xD6,0x70,0xC5,0x6D,0x94,0xB5,0xC9,0x7D,0x9D,0xA0,
+0xD2,0xC6,0x08,0x49,0xD9,0x66,0x9B,0xA6,0xD3,0xF4,0x0B,0xDC,0xC5,0x26,0x57,0xE1,
+0x91,0x30,0xEA,0xCD,
+};
+
+
+/* subject:/C=US/O=Equifax Secure Inc./CN=Equifax Secure Global eBusiness CA-1 */
+/* issuer :/C=US/O=Equifax Secure Inc./CN=Equifax Secure Global eBusiness CA-1 */
+
+
+const unsigned char Equifax_Secure_Global_eBusiness_CA_certificate[660]={
+0x30,0x82,0x02,0x90,0x30,0x82,0x01,0xF9,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,0x30,
+0x5A,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x1C,
+0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x45,0x71,0x75,0x69,0x66,0x61,0x78,
+0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x49,0x6E,0x63,0x2E,0x31,0x2D,0x30,0x2B,
+0x06,0x03,0x55,0x04,0x03,0x13,0x24,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53,
+0x65,0x63,0x75,0x72,0x65,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x65,0x42,0x75,
+0x73,0x69,0x6E,0x65,0x73,0x73,0x20,0x43,0x41,0x2D,0x31,0x30,0x1E,0x17,0x0D,0x39,
+0x39,0x30,0x36,0x32,0x31,0x30,0x34,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x30,
+0x30,0x36,0x32,0x31,0x30,0x34,0x30,0x30,0x30,0x30,0x5A,0x30,0x5A,0x31,0x0B,0x30,
+0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x1C,0x30,0x1A,0x06,0x03,
+0x55,0x04,0x0A,0x13,0x13,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63,
+0x75,0x72,0x65,0x20,0x49,0x6E,0x63,0x2E,0x31,0x2D,0x30,0x2B,0x06,0x03,0x55,0x04,
+0x03,0x13,0x24,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,
+0x65,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x65,0x42,0x75,0x73,0x69,0x6E,0x65,
+0x73,0x73,0x20,0x43,0x41,0x2D,0x31,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86,
+0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89,
+0x02,0x81,0x81,0x00,0xBA,0xE7,0x17,0x90,0x02,0x65,0xB1,0x34,0x55,0x3C,0x49,0xC2,
+0x51,0xD5,0xDF,0xA7,0xD1,0x37,0x8F,0xD1,0xE7,0x81,0x73,0x41,0x52,0x60,0x9B,0x9D,
+0xA1,0x17,0x26,0x78,0xAD,0xC7,0xB1,0xE8,0x26,0x94,0x32,0xB5,0xDE,0x33,0x8D,0x3A,
+0x2F,0xDB,0xF2,0x9A,0x7A,0x5A,0x73,0x98,0xA3,0x5C,0xE9,0xFB,0x8A,0x73,0x1B,0x5C,
+0xE7,0xC3,0xBF,0x80,0x6C,0xCD,0xA9,0xF4,0xD6,0x2B,0xC0,0xF7,0xF9,0x99,0xAA,0x63,
+0xA2,0xB1,0x47,0x02,0x0F,0xD4,0xE4,0x51,0x3A,0x12,0x3C,0x6C,0x8A,0x5A,0x54,0x84,
+0x70,0xDB,0xC1,0xC5,0x90,0xCF,0x72,0x45,0xCB,0xA8,0x59,0xC0,0xCD,0x33,0x9D,0x3F,
+0xA3,0x96,0xEB,0x85,0x33,0x21,0x1C,0x3E,0x1E,0x3E,0x60,0x6E,0x76,0x9C,0x67,0x85,
+0xC5,0xC8,0xC3,0x61,0x02,0x03,0x01,0x00,0x01,0xA3,0x66,0x30,0x64,0x30,0x11,0x06,
+0x09,0x60,0x86,0x48,0x01,0x86,0xF8,0x42,0x01,0x01,0x04,0x04,0x03,0x02,0x00,0x07,
+0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,
+0xFF,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0xBE,0xA8,
+0xA0,0x74,0x72,0x50,0x6B,0x44,0xB7,0xC9,0x23,0xD8,0xFB,0xA8,0xFF,0xB3,0x57,0x6B,
+0x68,0x6C,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xBE,0xA8,0xA0,
+0x74,0x72,0x50,0x6B,0x44,0xB7,0xC9,0x23,0xD8,0xFB,0xA8,0xFF,0xB3,0x57,0x6B,0x68,
+0x6C,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,
+0x03,0x81,0x81,0x00,0x30,0xE2,0x01,0x51,0xAA,0xC7,0xEA,0x5F,0xDA,0xB9,0xD0,0x65,
+0x0F,0x30,0xD6,0x3E,0xDA,0x0D,0x14,0x49,0x6E,0x91,0x93,0x27,0x14,0x31,0xEF,0xC4,
+0xF7,0x2D,0x45,0xF8,0xEC,0xC7,0xBF,0xA2,0x41,0x0D,0x23,0xB4,0x92,0xF9,0x19,0x00,
+0x67,0xBD,0x01,0xAF,0xCD,0xE0,0x71,0xFC,0x5A,0xCF,0x64,0xC4,0xE0,0x96,0x98,0xD0,
+0xA3,0x40,0xE2,0x01,0x8A,0xEF,0x27,0x07,0xF1,0x65,0x01,0x8A,0x44,0x2D,0x06,0x65,
+0x75,0x52,0xC0,0x86,0x10,0x20,0x21,0x5F,0x6C,0x6B,0x0F,0x6C,0xAE,0x09,0x1C,0xAF,
+0xF2,0xA2,0x18,0x34,0xC4,0x75,0xA4,0x73,0x1C,0xF1,0x8D,0xDC,0xEF,0xAD,0xF9,0xB3,
+0x76,0xB4,0x92,0xBF,0xDC,0x95,0x10,0x1E,0xBE,0xCB,0xC8,0x3B,0x5A,0x84,0x60,0x19,
+0x56,0x94,0xA9,0x55,
+};
+
+
+/* subject:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA */
+/* issuer :/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA */
+
+
+const unsigned char GeoTrust_Global_CA_certificate[856]={
+0x30,0x82,0x03,0x54,0x30,0x82,0x02,0x3C,0xA0,0x03,0x02,0x01,0x02,0x02,0x03,0x02,
+0x34,0x56,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,
+0x00,0x30,0x42,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,
+0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,
+0x75,0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1B,0x30,0x19,0x06,0x03,0x55,0x04,
+0x03,0x13,0x12,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x47,0x6C,0x6F,0x62,
+0x61,0x6C,0x20,0x43,0x41,0x30,0x1E,0x17,0x0D,0x30,0x32,0x30,0x35,0x32,0x31,0x30,
+0x34,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x32,0x30,0x35,0x32,0x31,0x30,0x34,
+0x30,0x30,0x30,0x30,0x5A,0x30,0x42,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,
+0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x47,
+0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1B,0x30,0x19,
+0x06,0x03,0x55,0x04,0x03,0x13,0x12,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,
+0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x43,0x41,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,
+0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,
+0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xDA,0xCC,0x18,0x63,0x30,0xFD,
+0xF4,0x17,0x23,0x1A,0x56,0x7E,0x5B,0xDF,0x3C,0x6C,0x38,0xE4,0x71,0xB7,0x78,0x91,
+0xD4,0xBC,0xA1,0xD8,0x4C,0xF8,0xA8,0x43,0xB6,0x03,0xE9,0x4D,0x21,0x07,0x08,0x88,
+0xDA,0x58,0x2F,0x66,0x39,0x29,0xBD,0x05,0x78,0x8B,0x9D,0x38,0xE8,0x05,0xB7,0x6A,
+0x7E,0x71,0xA4,0xE6,0xC4,0x60,0xA6,0xB0,0xEF,0x80,0xE4,0x89,0x28,0x0F,0x9E,0x25,
+0xD6,0xED,0x83,0xF3,0xAD,0xA6,0x91,0xC7,0x98,0xC9,0x42,0x18,0x35,0x14,0x9D,0xAD,
+0x98,0x46,0x92,0x2E,0x4F,0xCA,0xF1,0x87,0x43,0xC1,0x16,0x95,0x57,0x2D,0x50,0xEF,
+0x89,0x2D,0x80,0x7A,0x57,0xAD,0xF2,0xEE,0x5F,0x6B,0xD2,0x00,0x8D,0xB9,0x14,0xF8,
+0x14,0x15,0x35,0xD9,0xC0,0x46,0xA3,0x7B,0x72,0xC8,0x91,0xBF,0xC9,0x55,0x2B,0xCD,
+0xD0,0x97,0x3E,0x9C,0x26,0x64,0xCC,0xDF,0xCE,0x83,0x19,0x71,0xCA,0x4E,0xE6,0xD4,
+0xD5,0x7B,0xA9,0x19,0xCD,0x55,0xDE,0xC8,0xEC,0xD2,0x5E,0x38,0x53,0xE5,0x5C,0x4F,
+0x8C,0x2D,0xFE,0x50,0x23,0x36,0xFC,0x66,0xE6,0xCB,0x8E,0xA4,0x39,0x19,0x00,0xB7,
+0x95,0x02,0x39,0x91,0x0B,0x0E,0xFE,0x38,0x2E,0xD1,0x1D,0x05,0x9A,0xF6,0x4D,0x3E,
+0x6F,0x0F,0x07,0x1D,0xAF,0x2C,0x1E,0x8F,0x60,0x39,0xE2,0xFA,0x36,0x53,0x13,0x39,
+0xD4,0x5E,0x26,0x2B,0xDB,0x3D,0xA8,0x14,0xBD,0x32,0xEB,0x18,0x03,0x28,0x52,0x04,
+0x71,0xE5,0xAB,0x33,0x3D,0xE1,0x38,0xBB,0x07,0x36,0x84,0x62,0x9C,0x79,0xEA,0x16,
+0x30,0xF4,0x5F,0xC0,0x2B,0xE8,0x71,0x6B,0xE4,0xF9,0x02,0x03,0x01,0x00,0x01,0xA3,
+0x53,0x30,0x51,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,
+0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xC0,
+0x7A,0x98,0x68,0x8D,0x89,0xFB,0xAB,0x05,0x64,0x0C,0x11,0x7D,0xAA,0x7D,0x65,0xB8,
+0xCA,0xCC,0x4E,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,
+0xC0,0x7A,0x98,0x68,0x8D,0x89,0xFB,0xAB,0x05,0x64,0x0C,0x11,0x7D,0xAA,0x7D,0x65,
+0xB8,0xCA,0xCC,0x4E,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,
+0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x35,0xE3,0x29,0x6A,0xE5,0x2F,0x5D,0x54,
+0x8E,0x29,0x50,0x94,0x9F,0x99,0x1A,0x14,0xE4,0x8F,0x78,0x2A,0x62,0x94,0xA2,0x27,
+0x67,0x9E,0xD0,0xCF,0x1A,0x5E,0x47,0xE9,0xC1,0xB2,0xA4,0xCF,0xDD,0x41,0x1A,0x05,
+0x4E,0x9B,0x4B,0xEE,0x4A,0x6F,0x55,0x52,0xB3,0x24,0xA1,0x37,0x0A,0xEB,0x64,0x76,
+0x2A,0x2E,0x2C,0xF3,0xFD,0x3B,0x75,0x90,0xBF,0xFA,0x71,0xD8,0xC7,0x3D,0x37,0xD2,
+0xB5,0x05,0x95,0x62,0xB9,0xA6,0xDE,0x89,0x3D,0x36,0x7B,0x38,0x77,0x48,0x97,0xAC,
+0xA6,0x20,0x8F,0x2E,0xA6,0xC9,0x0C,0xC2,0xB2,0x99,0x45,0x00,0xC7,0xCE,0x11,0x51,
+0x22,0x22,0xE0,0xA5,0xEA,0xB6,0x15,0x48,0x09,0x64,0xEA,0x5E,0x4F,0x74,0xF7,0x05,
+0x3E,0xC7,0x8A,0x52,0x0C,0xDB,0x15,0xB4,0xBD,0x6D,0x9B,0xE5,0xC6,0xB1,0x54,0x68,
+0xA9,0xE3,0x69,0x90,0xB6,0x9A,0xA5,0x0F,0xB8,0xB9,0x3F,0x20,0x7D,0xAE,0x4A,0xB5,
+0xB8,0x9C,0xE4,0x1D,0xB6,0xAB,0xE6,0x94,0xA5,0xC1,0xC7,0x83,0xAD,0xDB,0xF5,0x27,
+0x87,0x0E,0x04,0x6C,0xD5,0xFF,0xDD,0xA0,0x5D,0xED,0x87,0x52,0xB7,0x2B,0x15,0x02,
+0xAE,0x39,0xA6,0x6A,0x74,0xE9,0xDA,0xC4,0xE7,0xBC,0x4D,0x34,0x1E,0xA9,0x5C,0x4D,
+0x33,0x5F,0x92,0x09,0x2F,0x88,0x66,0x5D,0x77,0x97,0xC7,0x1D,0x76,0x13,0xA9,0xD5,
+0xE5,0xF1,0x16,0x09,0x11,0x35,0xD5,0xAC,0xDB,0x24,0x71,0x70,0x2C,0x98,0x56,0x0B,
+0xD9,0x17,0xB4,0xD1,0xE3,0x51,0x2B,0x5E,0x75,0xE8,0xD5,0xD0,0xDC,0x4F,0x34,0xED,
+0xC2,0x05,0x66,0x80,0xA1,0xCB,0xE6,0x33,
+};
+
+
+/* subject:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA 2 */
+/* issuer :/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA 2 */
+
+
+const unsigned char GeoTrust_Global_CA_2_certificate[874]={
+0x30,0x82,0x03,0x66,0x30,0x82,0x02,0x4E,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,
+0x44,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16,
+0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,
+0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1D,0x30,0x1B,0x06,0x03,0x55,0x04,0x03,0x13,
+0x14,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,
+0x20,0x43,0x41,0x20,0x32,0x30,0x1E,0x17,0x0D,0x30,0x34,0x30,0x33,0x30,0x34,0x30,
+0x35,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x31,0x39,0x30,0x33,0x30,0x34,0x30,0x35,
+0x30,0x30,0x30,0x30,0x5A,0x30,0x44,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,
+0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x47,
+0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1D,0x30,0x1B,
+0x06,0x03,0x55,0x04,0x03,0x13,0x14,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,
+0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x43,0x41,0x20,0x32,0x30,0x82,0x01,0x22,0x30,
+0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,
+0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xEF,0x3C,0x4D,0x40,
+0x3D,0x10,0xDF,0x3B,0x53,0x00,0xE1,0x67,0xFE,0x94,0x60,0x15,0x3E,0x85,0x88,0xF1,
+0x89,0x0D,0x90,0xC8,0x28,0x23,0x99,0x05,0xE8,0x2B,0x20,0x9D,0xC6,0xF3,0x60,0x46,
+0xD8,0xC1,0xB2,0xD5,0x8C,0x31,0xD9,0xDC,0x20,0x79,0x24,0x81,0xBF,0x35,0x32,0xFC,
+0x63,0x69,0xDB,0xB1,0x2A,0x6B,0xEE,0x21,0x58,0xF2,0x08,0xE9,0x78,0xCB,0x6F,0xCB,
+0xFC,0x16,0x52,0xC8,0x91,0xC4,0xFF,0x3D,0x73,0xDE,0xB1,0x3E,0xA7,0xC2,0x7D,0x66,
+0xC1,0xF5,0x7E,0x52,0x24,0x1A,0xE2,0xD5,0x67,0x91,0xD0,0x82,0x10,0xD7,0x78,0x4B,
+0x4F,0x2B,0x42,0x39,0xBD,0x64,0x2D,0x40,0xA0,0xB0,0x10,0xD3,0x38,0x48,0x46,0x88,
+0xA1,0x0C,0xBB,0x3A,0x33,0x2A,0x62,0x98,0xFB,0x00,0x9D,0x13,0x59,0x7F,0x6F,0x3B,
+0x72,0xAA,0xEE,0xA6,0x0F,0x86,0xF9,0x05,0x61,0xEA,0x67,0x7F,0x0C,0x37,0x96,0x8B,
+0xE6,0x69,0x16,0x47,0x11,0xC2,0x27,0x59,0x03,0xB3,0xA6,0x60,0xC2,0x21,0x40,0x56,
+0xFA,0xA0,0xC7,0x7D,0x3A,0x13,0xE3,0xEC,0x57,0xC7,0xB3,0xD6,0xAE,0x9D,0x89,0x80,
+0xF7,0x01,0xE7,0x2C,0xF6,0x96,0x2B,0x13,0x0D,0x79,0x2C,0xD9,0xC0,0xE4,0x86,0x7B,
+0x4B,0x8C,0x0C,0x72,0x82,0x8A,0xFB,0x17,0xCD,0x00,0x6C,0x3A,0x13,0x3C,0xB0,0x84,
+0x87,0x4B,0x16,0x7A,0x29,0xB2,0x4F,0xDB,0x1D,0xD4,0x0B,0xF3,0x66,0x37,0xBD,0xD8,
+0xF6,0x57,0xBB,0x5E,0x24,0x7A,0xB8,0x3C,0x8B,0xB9,0xFA,0x92,0x1A,0x1A,0x84,0x9E,
+0xD8,0x74,0x8F,0xAA,0x1B,0x7F,0x5E,0xF4,0xFE,0x45,0x22,0x21,0x02,0x03,0x01,0x00,
+0x01,0xA3,0x63,0x30,0x61,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,
+0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,
+0x14,0x71,0x38,0x36,0xF2,0x02,0x31,0x53,0x47,0x2B,0x6E,0xBA,0x65,0x46,0xA9,0x10,
+0x15,0x58,0x20,0x05,0x09,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,
+0x80,0x14,0x71,0x38,0x36,0xF2,0x02,0x31,0x53,0x47,0x2B,0x6E,0xBA,0x65,0x46,0xA9,
+0x10,0x15,0x58,0x20,0x05,0x09,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,
+0x04,0x04,0x03,0x02,0x01,0x86,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,
+0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x03,0xF7,0xB5,0x2B,0xAB,0x5D,
+0x10,0xFC,0x7B,0xB2,0xB2,0x5E,0xAC,0x9B,0x0E,0x7E,0x53,0x78,0x59,0x3E,0x42,0x04,
+0xFE,0x75,0xA3,0xAD,0xAC,0x81,0x4E,0xD7,0x02,0x8B,0x5E,0xC4,0x2D,0xC8,0x52,0x76,
+0xC7,0x2C,0x1F,0xFC,0x81,0x32,0x98,0xD1,0x4B,0xC6,0x92,0x93,0x33,0x35,0x31,0x2F,
+0xFC,0xD8,0x1D,0x44,0xDD,0xE0,0x81,0x7F,0x9D,0xE9,0x8B,0xE1,0x64,0x91,0x62,0x0B,
+0x39,0x08,0x8C,0xAC,0x74,0x9D,0x59,0xD9,0x7A,0x59,0x52,0x97,0x11,0xB9,0x16,0x7B,
+0x6F,0x45,0xD3,0x96,0xD9,0x31,0x7D,0x02,0x36,0x0F,0x9C,0x3B,0x6E,0xCF,0x2C,0x0D,
+0x03,0x46,0x45,0xEB,0xA0,0xF4,0x7F,0x48,0x44,0xC6,0x08,0x40,0xCC,0xDE,0x1B,0x70,
+0xB5,0x29,0xAD,0xBA,0x8B,0x3B,0x34,0x65,0x75,0x1B,0x71,0x21,0x1D,0x2C,0x14,0x0A,
+0xB0,0x96,0x95,0xB8,0xD6,0xEA,0xF2,0x65,0xFB,0x29,0xBA,0x4F,0xEA,0x91,0x93,0x74,
+0x69,0xB6,0xF2,0xFF,0xE1,0x1A,0xD0,0x0C,0xD1,0x76,0x85,0xCB,0x8A,0x25,0xBD,0x97,
+0x5E,0x2C,0x6F,0x15,0x99,0x26,0xE7,0xB6,0x29,0xFF,0x22,0xEC,0xC9,0x02,0xC7,0x56,
+0x00,0xCD,0x49,0xB9,0xB3,0x6C,0x7B,0x53,0x04,0x1A,0xE2,0xA8,0xC9,0xAA,0x12,0x05,
+0x23,0xC2,0xCE,0xE7,0xBB,0x04,0x02,0xCC,0xC0,0x47,0xA2,0xE4,0xC4,0x29,0x2F,0x5B,
+0x45,0x57,0x89,0x51,0xEE,0x3C,0xEB,0x52,0x08,0xFF,0x07,0x35,0x1E,0x9F,0x35,0x6A,
+0x47,0x4A,0x56,0x98,0xD1,0x5A,0x85,0x1F,0x8C,0xF5,0x22,0xBF,0xAB,0xCE,0x83,0xF3,
+0xE2,0x22,0x29,0xAE,0x7D,0x83,0x40,0xA8,0xBA,0x6C,
+};
+
+
+/* subject:/C=US/O=GeoTrust Inc./CN=GeoTrust Primary Certification Authority */
+/* issuer :/C=US/O=GeoTrust Inc./CN=GeoTrust Primary Certification Authority */
+
+
+const unsigned char GeoTrust_Primary_Certification_Authority_certificate[896]={
+0x30,0x82,0x03,0x7C,0x30,0x82,0x02,0x64,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x18,
+0xAC,0xB5,0x6A,0xFD,0x69,0xB6,0x15,0x3A,0x63,0x6C,0xAF,0xDA,0xFA,0xC4,0xA1,0x30,
+0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x58,
+0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16,0x30,
+0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,
+0x20,0x49,0x6E,0x63,0x2E,0x31,0x31,0x30,0x2F,0x06,0x03,0x55,0x04,0x03,0x13,0x28,
+0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,
+0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,
+0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x1E,0x17,0x0D,0x30,0x36,0x31,0x31,
+0x32,0x37,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x36,0x30,0x37,0x31,
+0x36,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x58,0x31,0x0B,0x30,0x09,0x06,0x03,
+0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A,
+0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,
+0x31,0x30,0x2F,0x06,0x03,0x55,0x04,0x03,0x13,0x28,0x47,0x65,0x6F,0x54,0x72,0x75,
+0x73,0x74,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,
+0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,
+0x74,0x79,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,
+0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,
+0x01,0x01,0x00,0xBE,0xB8,0x15,0x7B,0xFF,0xD4,0x7C,0x7D,0x67,0xAD,0x83,0x64,0x7B,
+0xC8,0x42,0x53,0x2D,0xDF,0xF6,0x84,0x08,0x20,0x61,0xD6,0x01,0x59,0x6A,0x9C,0x44,
+0x11,0xAF,0xEF,0x76,0xFD,0x95,0x7E,0xCE,0x61,0x30,0xBB,0x7A,0x83,0x5F,0x02,0xBD,
+0x01,0x66,0xCA,0xEE,0x15,0x8D,0x6F,0xA1,0x30,0x9C,0xBD,0xA1,0x85,0x9E,0x94,0x3A,
+0xF3,0x56,0x88,0x00,0x31,0xCF,0xD8,0xEE,0x6A,0x96,0x02,0xD9,0xED,0x03,0x8C,0xFB,
+0x75,0x6D,0xE7,0xEA,0xB8,0x55,0x16,0x05,0x16,0x9A,0xF4,0xE0,0x5E,0xB1,0x88,0xC0,
+0x64,0x85,0x5C,0x15,0x4D,0x88,0xC7,0xB7,0xBA,0xE0,0x75,0xE9,0xAD,0x05,0x3D,0x9D,
+0xC7,0x89,0x48,0xE0,0xBB,0x28,0xC8,0x03,0xE1,0x30,0x93,0x64,0x5E,0x52,0xC0,0x59,
+0x70,0x22,0x35,0x57,0x88,0x8A,0xF1,0x95,0x0A,0x83,0xD7,0xBC,0x31,0x73,0x01,0x34,
+0xED,0xEF,0x46,0x71,0xE0,0x6B,0x02,0xA8,0x35,0x72,0x6B,0x97,0x9B,0x66,0xE0,0xCB,
+0x1C,0x79,0x5F,0xD8,0x1A,0x04,0x68,0x1E,0x47,0x02,0xE6,0x9D,0x60,0xE2,0x36,0x97,
+0x01,0xDF,0xCE,0x35,0x92,0xDF,0xBE,0x67,0xC7,0x6D,0x77,0x59,0x3B,0x8F,0x9D,0xD6,
+0x90,0x15,0x94,0xBC,0x42,0x34,0x10,0xC1,0x39,0xF9,0xB1,0x27,0x3E,0x7E,0xD6,0x8A,
+0x75,0xC5,0xB2,0xAF,0x96,0xD3,0xA2,0xDE,0x9B,0xE4,0x98,0xBE,0x7D,0xE1,0xE9,0x81,
+0xAD,0xB6,0x6F,0xFC,0xD7,0x0E,0xDA,0xE0,0x34,0xB0,0x0D,0x1A,0x77,0xE7,0xE3,0x08,
+0x98,0xEF,0x58,0xFA,0x9C,0x84,0xB7,0x36,0xAF,0xC2,0xDF,0xAC,0xD2,0xF4,0x10,0x06,
+0x70,0x71,0x35,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40,0x30,0x0F,0x06,0x03,
+0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,
+0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D,0x06,
+0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x2C,0xD5,0x50,0x41,0x97,0x15,0x8B,0xF0,
+0x8F,0x36,0x61,0x5B,0x4A,0xFB,0x6B,0xD9,0x99,0xC9,0x33,0x92,0x30,0x0D,0x06,0x09,
+0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,
+0x5A,0x70,0x7F,0x2C,0xDD,0xB7,0x34,0x4F,0xF5,0x86,0x51,0xA9,0x26,0xBE,0x4B,0xB8,
+0xAA,0xF1,0x71,0x0D,0xDC,0x61,0xC7,0xA0,0xEA,0x34,0x1E,0x7A,0x77,0x0F,0x04,0x35,
+0xE8,0x27,0x8F,0x6C,0x90,0xBF,0x91,0x16,0x24,0x46,0x3E,0x4A,0x4E,0xCE,0x2B,0x16,
+0xD5,0x0B,0x52,0x1D,0xFC,0x1F,0x67,0xA2,0x02,0x45,0x31,0x4F,0xCE,0xF3,0xFA,0x03,
+0xA7,0x79,0x9D,0x53,0x6A,0xD9,0xDA,0x63,0x3A,0xF8,0x80,0xD7,0xD3,0x99,0xE1,0xA5,
+0xE1,0xBE,0xD4,0x55,0x71,0x98,0x35,0x3A,0xBE,0x93,0xEA,0xAE,0xAD,0x42,0xB2,0x90,
+0x6F,0xE0,0xFC,0x21,0x4D,0x35,0x63,0x33,0x89,0x49,0xD6,0x9B,0x4E,0xCA,0xC7,0xE7,
+0x4E,0x09,0x00,0xF7,0xDA,0xC7,0xEF,0x99,0x62,0x99,0x77,0xB6,0x95,0x22,0x5E,0x8A,
+0xA0,0xAB,0xF4,0xB8,0x78,0x98,0xCA,0x38,0x19,0x99,0xC9,0x72,0x9E,0x78,0xCD,0x4B,
+0xAC,0xAF,0x19,0xA0,0x73,0x12,0x2D,0xFC,0xC2,0x41,0xBA,0x81,0x91,0xDA,0x16,0x5A,
+0x31,0xB7,0xF9,0xB4,0x71,0x80,0x12,0x48,0x99,0x72,0x73,0x5A,0x59,0x53,0xC1,0x63,
+0x52,0x33,0xED,0xA7,0xC9,0xD2,0x39,0x02,0x70,0xFA,0xE0,0xB1,0x42,0x66,0x29,0xAA,
+0x9B,0x51,0xED,0x30,0x54,0x22,0x14,0x5F,0xD9,0xAB,0x1D,0xC1,0xE4,0x94,0xF0,0xF8,
+0xF5,0x2B,0xF7,0xEA,0xCA,0x78,0x46,0xD6,0xB8,0x91,0xFD,0xA6,0x0D,0x2B,0x1A,0x14,
+0x01,0x3E,0x80,0xF0,0x42,0xA0,0x95,0x07,0x5E,0x6D,0xCD,0xCC,0x4B,0xA4,0x45,0x8D,
+0xAB,0x12,0xE8,0xB3,0xDE,0x5A,0xE5,0xA0,0x7C,0xE8,0x0F,0x22,0x1D,0x5A,0xE9,0x59,
+};
+
+
+/* subject:/C=US/O=GeoTrust Inc./OU=(c) 2007 GeoTrust Inc. - For authorized use only/CN=GeoTrust Primary Certification Authority - G2 */
+/* issuer :/C=US/O=GeoTrust Inc./OU=(c) 2007 GeoTrust Inc. - For authorized use only/CN=GeoTrust Primary Certification Authority - G2 */
+
+
+const unsigned char GeoTrust_Primary_Certification_Authority___G2_certificate[690]={
+0x30,0x82,0x02,0xAE,0x30,0x82,0x02,0x35,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x3C,
+0xB2,0xF4,0x48,0x0A,0x00,0xE2,0xFE,0xEB,0x24,0x3B,0x5E,0x60,0x3E,0xC3,0x6B,0x30,
+0x0A,0x06,0x08,0x2A,0x86,0x48,0xCE,0x3D,0x04,0x03,0x03,0x30,0x81,0x98,0x31,0x0B,
+0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06,
+0x03,0x55,0x04,0x0A,0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,
+0x6E,0x63,0x2E,0x31,0x39,0x30,0x37,0x06,0x03,0x55,0x04,0x0B,0x13,0x30,0x28,0x63,
+0x29,0x20,0x32,0x30,0x30,0x37,0x20,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,
+0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,
+0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x36,
+0x30,0x34,0x06,0x03,0x55,0x04,0x03,0x13,0x2D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,
+0x74,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,
+0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,
+0x79,0x20,0x2D,0x20,0x47,0x32,0x30,0x1E,0x17,0x0D,0x30,0x37,0x31,0x31,0x30,0x35,
+0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x38,0x30,0x31,0x31,0x38,0x32,
+0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0x98,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,
+0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,
+0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x39,
+0x30,0x37,0x06,0x03,0x55,0x04,0x0B,0x13,0x30,0x28,0x63,0x29,0x20,0x32,0x30,0x30,
+0x37,0x20,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x20,
+0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,
+0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x36,0x30,0x34,0x06,0x03,0x55,
+0x04,0x03,0x13,0x2D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x50,0x72,0x69,
+0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,
+0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x2D,0x20,0x47,
+0x32,0x30,0x76,0x30,0x10,0x06,0x07,0x2A,0x86,0x48,0xCE,0x3D,0x02,0x01,0x06,0x05,
+0x2B,0x81,0x04,0x00,0x22,0x03,0x62,0x00,0x04,0x15,0xB1,0xE8,0xFD,0x03,0x15,0x43,
+0xE5,0xAC,0xEB,0x87,0x37,0x11,0x62,0xEF,0xD2,0x83,0x36,0x52,0x7D,0x45,0x57,0x0B,
+0x4A,0x8D,0x7B,0x54,0x3B,0x3A,0x6E,0x5F,0x15,0x02,0xC0,0x50,0xA6,0xCF,0x25,0x2F,
+0x7D,0xCA,0x48,0xB8,0xC7,0x50,0x63,0x1C,0x2A,0x21,0x08,0x7C,0x9A,0x36,0xD8,0x0B,
+0xFE,0xD1,0x26,0xC5,0x58,0x31,0x30,0x28,0x25,0xF3,0x5D,0x5D,0xA3,0xB8,0xB6,0xA5,
+0xB4,0x92,0xED,0x6C,0x2C,0x9F,0xEB,0xDD,0x43,0x89,0xA2,0x3C,0x4B,0x48,0x91,0x1D,
+0x50,0xEC,0x26,0xDF,0xD6,0x60,0x2E,0xBD,0x21,0xA3,0x42,0x30,0x40,0x30,0x0F,0x06,
+0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,
+0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D,
+0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x15,0x5F,0x35,0x57,0x51,0x55,0xFB,
+0x25,0xB2,0xAD,0x03,0x69,0xFC,0x01,0xA3,0xFA,0xBE,0x11,0x55,0xD5,0x30,0x0A,0x06,
+0x08,0x2A,0x86,0x48,0xCE,0x3D,0x04,0x03,0x03,0x03,0x67,0x00,0x30,0x64,0x02,0x30,
+0x64,0x96,0x59,0xA6,0xE8,0x09,0xDE,0x8B,0xBA,0xFA,0x5A,0x88,0x88,0xF0,0x1F,0x91,
+0xD3,0x46,0xA8,0xF2,0x4A,0x4C,0x02,0x63,0xFB,0x6C,0x5F,0x38,0xDB,0x2E,0x41,0x93,
+0xA9,0x0E,0xE6,0x9D,0xDC,0x31,0x1C,0xB2,0xA0,0xA7,0x18,0x1C,0x79,0xE1,0xC7,0x36,
+0x02,0x30,0x3A,0x56,0xAF,0x9A,0x74,0x6C,0xF6,0xFB,0x83,0xE0,0x33,0xD3,0x08,0x5F,
+0xA1,0x9C,0xC2,0x5B,0x9F,0x46,0xD6,0xB6,0xCB,0x91,0x06,0x63,0xA2,0x06,0xE7,0x33,
+0xAC,0x3E,0xA8,0x81,0x12,0xD0,0xCB,0xBA,0xD0,0x92,0x0B,0xB6,0x9E,0x96,0xAA,0x04,
+0x0F,0x8A,
+};
+
+
+/* subject:/C=US/O=GeoTrust Inc./OU=(c) 2008 GeoTrust Inc. - For authorized use only/CN=GeoTrust Primary Certification Authority - G3 */
+/* issuer :/C=US/O=GeoTrust Inc./OU=(c) 2008 GeoTrust Inc. - For authorized use only/CN=GeoTrust Primary Certification Authority - G3 */
+
+
+const unsigned char GeoTrust_Primary_Certification_Authority___G3_certificate[1026]={
+0x30,0x82,0x03,0xFE,0x30,0x82,0x02,0xE6,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x15,
+0xAC,0x6E,0x94,0x19,0xB2,0x79,0x4B,0x41,0xF6,0x27,0xA9,0xC3,0x18,0x0F,0x1F,0x30,
+0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30,0x81,
+0x98,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16,
+0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,
+0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x39,0x30,0x37,0x06,0x03,0x55,0x04,0x0B,0x13,
+0x30,0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x38,0x20,0x47,0x65,0x6F,0x54,0x72,0x75,
+0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,
+0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,
+0x79,0x31,0x36,0x30,0x34,0x06,0x03,0x55,0x04,0x03,0x13,0x2D,0x47,0x65,0x6F,0x54,
+0x72,0x75,0x73,0x74,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,
+0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,
+0x72,0x69,0x74,0x79,0x20,0x2D,0x20,0x47,0x33,0x30,0x1E,0x17,0x0D,0x30,0x38,0x30,
+0x34,0x30,0x32,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x37,0x31,0x32,
+0x30,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0x98,0x31,0x0B,0x30,0x09,
+0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55,
+0x04,0x0A,0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E,0x63,
+0x2E,0x31,0x39,0x30,0x37,0x06,0x03,0x55,0x04,0x0B,0x13,0x30,0x28,0x63,0x29,0x20,
+0x32,0x30,0x30,0x38,0x20,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E,
+0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,
+0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x36,0x30,0x34,
+0x06,0x03,0x55,0x04,0x03,0x13,0x2D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,
+0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,
+0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20,
+0x2D,0x20,0x47,0x33,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,
+0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,
+0x02,0x82,0x01,0x01,0x00,0xDC,0xE2,0x5E,0x62,0x58,0x1D,0x33,0x57,0x39,0x32,0x33,
+0xFA,0xEB,0xCB,0x87,0x8C,0xA7,0xD4,0x4A,0xDD,0x06,0x88,0xEA,0x64,0x8E,0x31,0x98,
+0xA5,0x38,0x90,0x1E,0x98,0xCF,0x2E,0x63,0x2B,0xF0,0x46,0xBC,0x44,0xB2,0x89,0xA1,
+0xC0,0x28,0x0C,0x49,0x70,0x21,0x95,0x9F,0x64,0xC0,0xA6,0x93,0x12,0x02,0x65,0x26,
+0x86,0xC6,0xA5,0x89,0xF0,0xFA,0xD7,0x84,0xA0,0x70,0xAF,0x4F,0x1A,0x97,0x3F,0x06,
+0x44,0xD5,0xC9,0xEB,0x72,0x10,0x7D,0xE4,0x31,0x28,0xFB,0x1C,0x61,0xE6,0x28,0x07,
+0x44,0x73,0x92,0x22,0x69,0xA7,0x03,0x88,0x6C,0x9D,0x63,0xC8,0x52,0xDA,0x98,0x27,
+0xE7,0x08,0x4C,0x70,0x3E,0xB4,0xC9,0x12,0xC1,0xC5,0x67,0x83,0x5D,0x33,0xF3,0x03,
+0x11,0xEC,0x6A,0xD0,0x53,0xE2,0xD1,0xBA,0x36,0x60,0x94,0x80,0xBB,0x61,0x63,0x6C,
+0x5B,0x17,0x7E,0xDF,0x40,0x94,0x1E,0xAB,0x0D,0xC2,0x21,0x28,0x70,0x88,0xFF,0xD6,
+0x26,0x6C,0x6C,0x60,0x04,0x25,0x4E,0x55,0x7E,0x7D,0xEF,0xBF,0x94,0x48,0xDE,0xB7,
+0x1D,0xDD,0x70,0x8D,0x05,0x5F,0x88,0xA5,0x9B,0xF2,0xC2,0xEE,0xEA,0xD1,0x40,0x41,
+0x6D,0x62,0x38,0x1D,0x56,0x06,0xC5,0x03,0x47,0x51,0x20,0x19,0xFC,0x7B,0x10,0x0B,
+0x0E,0x62,0xAE,0x76,0x55,0xBF,0x5F,0x77,0xBE,0x3E,0x49,0x01,0x53,0x3D,0x98,0x25,
+0x03,0x76,0x24,0x5A,0x1D,0xB4,0xDB,0x89,0xEA,0x79,0xE5,0xB6,0xB3,0x3B,0x3F,0xBA,
+0x4C,0x28,0x41,0x7F,0x06,0xAC,0x6A,0x8E,0xC1,0xD0,0xF6,0x05,0x1D,0x7D,0xE6,0x42,
+0x86,0xE3,0xA5,0xD5,0x47,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40,0x30,0x0F,
+0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,
+0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,
+0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xC4,0x79,0xCA,0x8E,0xA1,0x4E,
+0x03,0x1D,0x1C,0xDC,0x6B,0xDB,0x31,0x5B,0x94,0x3E,0x3F,0x30,0x7F,0x2D,0x30,0x0D,
+0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82,0x01,
+0x01,0x00,0x2D,0xC5,0x13,0xCF,0x56,0x80,0x7B,0x7A,0x78,0xBD,0x9F,0xAE,0x2C,0x99,
+0xE7,0xEF,0xDA,0xDF,0x94,0x5E,0x09,0x69,0xA7,0xE7,0x6E,0x68,0x8C,0xBD,0x72,0xBE,
+0x47,0xA9,0x0E,0x97,0x12,0xB8,0x4A,0xF1,0x64,0xD3,0x39,0xDF,0x25,0x34,0xD4,0xC1,
+0xCD,0x4E,0x81,0xF0,0x0F,0x04,0xC4,0x24,0xB3,0x34,0x96,0xC6,0xA6,0xAA,0x30,0xDF,
+0x68,0x61,0x73,0xD7,0xF9,0x8E,0x85,0x89,0xEF,0x0E,0x5E,0x95,0x28,0x4A,0x2A,0x27,
+0x8F,0x10,0x8E,0x2E,0x7C,0x86,0xC4,0x02,0x9E,0xDA,0x0C,0x77,0x65,0x0E,0x44,0x0D,
+0x92,0xFD,0xFD,0xB3,0x16,0x36,0xFA,0x11,0x0D,0x1D,0x8C,0x0E,0x07,0x89,0x6A,0x29,
+0x56,0xF7,0x72,0xF4,0xDD,0x15,0x9C,0x77,0x35,0x66,0x57,0xAB,0x13,0x53,0xD8,0x8E,
+0xC1,0x40,0xC5,0xD7,0x13,0x16,0x5A,0x72,0xC7,0xB7,0x69,0x01,0xC4,0x7A,0xB1,0x83,
+0x01,0x68,0x7D,0x8D,0x41,0xA1,0x94,0x18,0xC1,0x25,0x5C,0xFC,0xF0,0xFE,0x83,0x02,
+0x87,0x7C,0x0D,0x0D,0xCF,0x2E,0x08,0x5C,0x4A,0x40,0x0D,0x3E,0xEC,0x81,0x61,0xE6,
+0x24,0xDB,0xCA,0xE0,0x0E,0x2D,0x07,0xB2,0x3E,0x56,0xDC,0x8D,0xF5,0x41,0x85,0x07,
+0x48,0x9B,0x0C,0x0B,0xCB,0x49,0x3F,0x7D,0xEC,0xB7,0xFD,0xCB,0x8D,0x67,0x89,0x1A,
+0xAB,0xED,0xBB,0x1E,0xA3,0x00,0x08,0x08,0x17,0x2A,0x82,0x5C,0x31,0x5D,0x46,0x8A,
+0x2D,0x0F,0x86,0x9B,0x74,0xD9,0x45,0xFB,0xD4,0x40,0xB1,0x7A,0xAA,0x68,0x2D,0x86,
+0xB2,0x99,0x22,0xE1,0xC1,0x2B,0xC7,0x9C,0xF8,0xF3,0x5F,0xA8,0x82,0x12,0xEB,0x19,
+0x11,0x2D,
+};
+
+
+/* subject:/C=US/O=GeoTrust Inc./CN=GeoTrust Universal CA */
+/* issuer :/C=US/O=GeoTrust Inc./CN=GeoTrust Universal CA */
+
+
+const unsigned char GeoTrust_Universal_CA_certificate[1388]={
+0x30,0x82,0x05,0x68,0x30,0x82,0x03,0x50,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,
+0x45,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16,
+0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,
+0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1E,0x30,0x1C,0x06,0x03,0x55,0x04,0x03,0x13,
+0x15,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,
+0x73,0x61,0x6C,0x20,0x43,0x41,0x30,0x1E,0x17,0x0D,0x30,0x34,0x30,0x33,0x30,0x34,
+0x30,0x35,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x39,0x30,0x33,0x30,0x34,0x30,
+0x35,0x30,0x30,0x30,0x30,0x5A,0x30,0x45,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,
+0x06,0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,
+0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1E,0x30,
+0x1C,0x06,0x03,0x55,0x04,0x03,0x13,0x15,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,
+0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61,0x6C,0x20,0x43,0x41,0x30,0x82,0x02,
+0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,
+0x03,0x82,0x02,0x0F,0x00,0x30,0x82,0x02,0x0A,0x02,0x82,0x02,0x01,0x00,0xA6,0x15,
+0x55,0xA0,0xA3,0xC6,0xE0,0x1F,0x8C,0x9D,0x21,0x50,0xD7,0xC1,0xBE,0x2B,0x5B,0xB5,
+0xA4,0x9E,0xA1,0xD9,0x72,0x58,0xBD,0x00,0x1B,0x4C,0xBF,0x61,0xC9,0x14,0x1D,0x45,
+0x82,0xAB,0xC6,0x1D,0x80,0xD6,0x3D,0xEB,0x10,0x9C,0x3A,0xAF,0x6D,0x24,0xF8,0xBC,
+0x71,0x01,0x9E,0x06,0xF5,0x7C,0x5F,0x1E,0xC1,0x0E,0x55,0xCA,0x83,0x9A,0x59,0x30,
+0xAE,0x19,0xCB,0x30,0x48,0x95,0xED,0x22,0x37,0x8D,0xF4,0x4A,0x9A,0x72,0x66,0x3E,
+0xAD,0x95,0xC0,0xE0,0x16,0x00,0xE0,0x10,0x1F,0x2B,0x31,0x0E,0xD7,0x94,0x54,0xD3,
+0x42,0x33,0xA0,0x34,0x1D,0x1E,0x45,0x76,0xDD,0x4F,0xCA,0x18,0x37,0xEC,0x85,0x15,
+0x7A,0x19,0x08,0xFC,0xD5,0xC7,0x9C,0xF0,0xF2,0xA9,0x2E,0x10,0xA9,0x92,0xE6,0x3D,
+0x58,0x3D,0xA9,0x16,0x68,0x3C,0x2F,0x75,0x21,0x18,0x7F,0x28,0x77,0xA5,0xE1,0x61,
+0x17,0xB7,0xA6,0xE9,0xF8,0x1E,0x99,0xDB,0x73,0x6E,0xF4,0x0A,0xA2,0x21,0x6C,0xEE,
+0xDA,0xAA,0x85,0x92,0x66,0xAF,0xF6,0x7A,0x6B,0x82,0xDA,0xBA,0x22,0x08,0x35,0x0F,
+0xCF,0x42,0xF1,0x35,0xFA,0x6A,0xEE,0x7E,0x2B,0x25,0xCC,0x3A,0x11,0xE4,0x6D,0xAF,
+0x73,0xB2,0x76,0x1D,0xAD,0xD0,0xB2,0x78,0x67,0x1A,0xA4,0x39,0x1C,0x51,0x0B,0x67,
+0x56,0x83,0xFD,0x38,0x5D,0x0D,0xCE,0xDD,0xF0,0xBB,0x2B,0x96,0x1F,0xDE,0x7B,0x32,
+0x52,0xFD,0x1D,0xBB,0xB5,0x06,0xA1,0xB2,0x21,0x5E,0xA5,0xD6,0x95,0x68,0x7F,0xF0,
+0x99,0x9E,0xDC,0x45,0x08,0x3E,0xE7,0xD2,0x09,0x0D,0x35,0x94,0xDD,0x80,0x4E,0x53,
+0x97,0xD7,0xB5,0x09,0x44,0x20,0x64,0x16,0x17,0x03,0x02,0x4C,0x53,0x0D,0x68,0xDE,
+0xD5,0xAA,0x72,0x4D,0x93,0x6D,0x82,0x0E,0xDB,0x9C,0xBD,0xCF,0xB4,0xF3,0x5C,0x5D,
+0x54,0x7A,0x69,0x09,0x96,0xD6,0xDB,0x11,0xC1,0x8D,0x75,0xA8,0xB4,0xCF,0x39,0xC8,
+0xCE,0x3C,0xBC,0x24,0x7C,0xE6,0x62,0xCA,0xE1,0xBD,0x7D,0xA7,0xBD,0x57,0x65,0x0B,
+0xE4,0xFE,0x25,0xED,0xB6,0x69,0x10,0xDC,0x28,0x1A,0x46,0xBD,0x01,0x1D,0xD0,0x97,
+0xB5,0xE1,0x98,0x3B,0xC0,0x37,0x64,0xD6,0x3D,0x94,0xEE,0x0B,0xE1,0xF5,0x28,0xAE,
+0x0B,0x56,0xBF,0x71,0x8B,0x23,0x29,0x41,0x8E,0x86,0xC5,0x4B,0x52,0x7B,0xD8,0x71,
+0xAB,0x1F,0x8A,0x15,0xA6,0x3B,0x83,0x5A,0xD7,0x58,0x01,0x51,0xC6,0x4C,0x41,0xD9,
+0x7F,0xD8,0x41,0x67,0x72,0xA2,0x28,0xDF,0x60,0x83,0xA9,0x9E,0xC8,0x7B,0xFC,0x53,
+0x73,0x72,0x59,0xF5,0x93,0x7A,0x17,0x76,0x0E,0xCE,0xF7,0xE5,0x5C,0xD9,0x0B,0x55,
+0x34,0xA2,0xAA,0x5B,0xB5,0x6A,0x54,0xE7,0x13,0xCA,0x57,0xEC,0x97,0x6D,0xF4,0x5E,
+0x06,0x2F,0x45,0x8B,0x58,0xD4,0x23,0x16,0x92,0xE4,0x16,0x6E,0x28,0x63,0x59,0x30,
+0xDF,0x50,0x01,0x9C,0x63,0x89,0x1A,0x9F,0xDB,0x17,0x94,0x82,0x70,0x37,0xC3,0x24,
+0x9E,0x9A,0x47,0xD6,0x5A,0xCA,0x4E,0xA8,0x69,0x89,0x72,0x1F,0x91,0x6C,0xDB,0x7E,
+0x9E,0x1B,0xAD,0xC7,0x1F,0x73,0xDD,0x2C,0x4F,0x19,0x65,0xFD,0x7F,0x93,0x40,0x10,
+0x2E,0xD2,0xF0,0xED,0x3C,0x9E,0x2E,0x28,0x3E,0x69,0x26,0x33,0xC5,0x7B,0x02,0x03,
+0x01,0x00,0x01,0xA3,0x63,0x30,0x61,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,
+0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,
+0x16,0x04,0x14,0xDA,0xBB,0x2E,0xAA,0xB0,0x0C,0xB8,0x88,0x26,0x51,0x74,0x5C,0x6D,
+0x03,0xD3,0xC0,0xD8,0x8F,0x7A,0xD6,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,
+0x30,0x16,0x80,0x14,0xDA,0xBB,0x2E,0xAA,0xB0,0x0C,0xB8,0x88,0x26,0x51,0x74,0x5C,
+0x6D,0x03,0xD3,0xC0,0xD8,0x8F,0x7A,0xD6,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,
+0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x86,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,
+0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x02,0x01,0x00,0x31,0x78,0xE6,0xC7,
+0xB5,0xDF,0xB8,0x94,0x40,0xC9,0x71,0xC4,0xA8,0x35,0xEC,0x46,0x1D,0xC2,0x85,0xF3,
+0x28,0x58,0x86,0xB0,0x0B,0xFC,0x8E,0xB2,0x39,0x8F,0x44,0x55,0xAB,0x64,0x84,0x5C,
+0x69,0xA9,0xD0,0x9A,0x38,0x3C,0xFA,0xE5,0x1F,0x35,0xE5,0x44,0xE3,0x80,0x79,0x94,
+0x68,0xA4,0xBB,0xC4,0x9F,0x3D,0xE1,0x34,0xCD,0x30,0x46,0x8B,0x54,0x2B,0x95,0xA5,
+0xEF,0xF7,0x3F,0x99,0x84,0xFD,0x35,0xE6,0xCF,0x31,0xC6,0xDC,0x6A,0xBF,0xA7,0xD7,
+0x23,0x08,0xE1,0x98,0x5E,0xC3,0x5A,0x08,0x76,0xA9,0xA6,0xAF,0x77,0x2F,0xB7,0x60,
+0xBD,0x44,0x46,0x6A,0xEF,0x97,0xFF,0x73,0x95,0xC1,0x8E,0xE8,0x93,0xFB,0xFD,0x31,
+0xB7,0xEC,0x57,0x11,0x11,0x45,0x9B,0x30,0xF1,0x1A,0x88,0x39,0xC1,0x4F,0x3C,0xA7,
+0x00,0xD5,0xC7,0xFC,0xAB,0x6D,0x80,0x22,0x70,0xA5,0x0C,0xE0,0x5D,0x04,0x29,0x02,
+0xFB,0xCB,0xA0,0x91,0xD1,0x7C,0xD6,0xC3,0x7E,0x50,0xD5,0x9D,0x58,0xBE,0x41,0x38,
+0xEB,0xB9,0x75,0x3C,0x15,0xD9,0x9B,0xC9,0x4A,0x83,0x59,0xC0,0xDA,0x53,0xFD,0x33,
+0xBB,0x36,0x18,0x9B,0x85,0x0F,0x15,0xDD,0xEE,0x2D,0xAC,0x76,0x93,0xB9,0xD9,0x01,
+0x8D,0x48,0x10,0xA8,0xFB,0xF5,0x38,0x86,0xF1,0xDB,0x0A,0xC6,0xBD,0x84,0xA3,0x23,
+0x41,0xDE,0xD6,0x77,0x6F,0x85,0xD4,0x85,0x1C,0x50,0xE0,0xAE,0x51,0x8A,0xBA,0x8D,
+0x3E,0x76,0xE2,0xB9,0xCA,0x27,0xF2,0x5F,0x9F,0xEF,0x6E,0x59,0x0D,0x06,0xD8,0x2B,
+0x17,0xA4,0xD2,0x7C,0x6B,0xBB,0x5F,0x14,0x1A,0x48,0x8F,0x1A,0x4C,0xE7,0xB3,0x47,
+0x1C,0x8E,0x4C,0x45,0x2B,0x20,0xEE,0x48,0xDF,0xE7,0xDD,0x09,0x8E,0x18,0xA8,0xDA,
+0x40,0x8D,0x92,0x26,0x11,0x53,0x61,0x73,0x5D,0xEB,0xBD,0xE7,0xC4,0x4D,0x29,0x37,
+0x61,0xEB,0xAC,0x39,0x2D,0x67,0x2E,0x16,0xD6,0xF5,0x00,0x83,0x85,0xA1,0xCC,0x7F,
+0x76,0xC4,0x7D,0xE4,0xB7,0x4B,0x66,0xEF,0x03,0x45,0x60,0x69,0xB6,0x0C,0x52,0x96,
+0x92,0x84,0x5E,0xA6,0xA3,0xB5,0xA4,0x3E,0x2B,0xD9,0xCC,0xD8,0x1B,0x47,0xAA,0xF2,
+0x44,0xDA,0x4F,0xF9,0x03,0xE8,0xF0,0x14,0xCB,0x3F,0xF3,0x83,0xDE,0xD0,0xC1,0x54,
+0xE3,0xB7,0xE8,0x0A,0x37,0x4D,0x8B,0x20,0x59,0x03,0x30,0x19,0xA1,0x2C,0xC8,0xBD,
+0x11,0x1F,0xDF,0xAE,0xC9,0x4A,0xC5,0xF3,0x27,0x66,0x66,0x86,0xAC,0x68,0x91,0xFF,
+0xD9,0xE6,0x53,0x1C,0x0F,0x8B,0x5C,0x69,0x65,0x0A,0x26,0xC8,0x1E,0x34,0xC3,0x5D,
+0x51,0x7B,0xD7,0xA9,0x9C,0x06,0xA1,0x36,0xDD,0xD5,0x89,0x94,0xBC,0xD9,0xE4,0x2D,
+0x0C,0x5E,0x09,0x6C,0x08,0x97,0x7C,0xA3,0x3D,0x7C,0x93,0xFF,0x3F,0xA1,0x14,0xA7,
+0xCF,0xB5,0x5D,0xEB,0xDB,0xDB,0x1C,0xC4,0x76,0xDF,0x88,0xB9,0xBD,0x45,0x05,0x95,
+0x1B,0xAE,0xFC,0x46,0x6A,0x4C,0xAF,0x48,0xE3,0xCE,0xAE,0x0F,0xD2,0x7E,0xEB,0xE6,
+0x6C,0x9C,0x4F,0x81,0x6A,0x7A,0x64,0xAC,0xBB,0x3E,0xD5,0xE7,0xCB,0x76,0x2E,0xC5,
+0xA7,0x48,0xC1,0x5C,0x90,0x0F,0xCB,0xC8,0x3F,0xFA,0xE6,0x32,0xE1,0x8D,0x1B,0x6F,
+0xA4,0xE6,0x8E,0xD8,0xF9,0x29,0x48,0x8A,0xCE,0x73,0xFE,0x2C,
+};
+
+
+/* subject:/C=US/O=GeoTrust Inc./CN=GeoTrust Universal CA 2 */
+/* issuer :/C=US/O=GeoTrust Inc./CN=GeoTrust Universal CA 2 */
+
+
+const unsigned char GeoTrust_Universal_CA_2_certificate[1392]={
+0x30,0x82,0x05,0x6C,0x30,0x82,0x03,0x54,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,
+0x47,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16,
+0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,
+0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,0x20,0x30,0x1E,0x06,0x03,0x55,0x04,0x03,0x13,
+0x17,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,
+0x73,0x61,0x6C,0x20,0x43,0x41,0x20,0x32,0x30,0x1E,0x17,0x0D,0x30,0x34,0x30,0x33,
+0x30,0x34,0x30,0x35,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x39,0x30,0x33,0x30,
+0x34,0x30,0x35,0x30,0x30,0x30,0x30,0x5A,0x30,0x47,0x31,0x0B,0x30,0x09,0x06,0x03,
+0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A,
+0x13,0x0D,0x47,0x65,0x6F,0x54,0x72,0x75,0x73,0x74,0x20,0x49,0x6E,0x63,0x2E,0x31,
+0x20,0x30,0x1E,0x06,0x03,0x55,0x04,0x03,0x13,0x17,0x47,0x65,0x6F,0x54,0x72,0x75,
+0x73,0x74,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61,0x6C,0x20,0x43,0x41,0x20,
+0x32,0x30,0x82,0x02,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,
+0x01,0x01,0x05,0x00,0x03,0x82,0x02,0x0F,0x00,0x30,0x82,0x02,0x0A,0x02,0x82,0x02,
+0x01,0x00,0xB3,0x54,0x52,0xC1,0xC9,0x3E,0xF2,0xD9,0xDC,0xB1,0x53,0x1A,0x59,0x29,
+0xE7,0xB1,0xC3,0x45,0x28,0xE5,0xD7,0xD1,0xED,0xC5,0xC5,0x4B,0xA1,0xAA,0x74,0x7B,
+0x57,0xAF,0x4A,0x26,0xFC,0xD8,0xF5,0x5E,0xA7,0x6E,0x19,0xDB,0x74,0x0C,0x4F,0x35,
+0x5B,0x32,0x0B,0x01,0xE3,0xDB,0xEB,0x7A,0x77,0x35,0xEA,0xAA,0x5A,0xE0,0xD6,0xE8,
+0xA1,0x57,0x94,0xF0,0x90,0xA3,0x74,0x56,0x94,0x44,0x30,0x03,0x1E,0x5C,0x4E,0x2B,
+0x85,0x26,0x74,0x82,0x7A,0x0C,0x76,0xA0,0x6F,0x4D,0xCE,0x41,0x2D,0xA0,0x15,0x06,
+0x14,0x5F,0xB7,0x42,0xCD,0x7B,0x8F,0x58,0x61,0x34,0xDC,0x2A,0x08,0xF9,0x2E,0xC3,
+0x01,0xA6,0x22,0x44,0x1C,0x4C,0x07,0x82,0xE6,0x5B,0xCE,0xD0,0x4A,0x7C,0x04,0xD3,
+0x19,0x73,0x27,0xF0,0xAA,0x98,0x7F,0x2E,0xAF,0x4E,0xEB,0x87,0x1E,0x24,0x77,0x6A,
+0x5D,0xB6,0xE8,0x5B,0x45,0xBA,0xDC,0xC3,0xA1,0x05,0x6F,0x56,0x8E,0x8F,0x10,0x26,
+0xA5,0x49,0xC3,0x2E,0xD7,0x41,0x87,0x22,0xE0,0x4F,0x86,0xCA,0x60,0xB5,0xEA,0xA1,
+0x63,0xC0,0x01,0x97,0x10,0x79,0xBD,0x00,0x3C,0x12,0x6D,0x2B,0x15,0xB1,0xAC,0x4B,
+0xB1,0xEE,0x18,0xB9,0x4E,0x96,0xDC,0xDC,0x76,0xFF,0x3B,0xBE,0xCF,0x5F,0x03,0xC0,
+0xFC,0x3B,0xE8,0xBE,0x46,0x1B,0xFF,0xDA,0x40,0xC2,0x52,0xF7,0xFE,0xE3,0x3A,0xF7,
+0x6A,0x77,0x35,0xD0,0xDA,0x8D,0xEB,0x5E,0x18,0x6A,0x31,0xC7,0x1E,0xBA,0x3C,0x1B,
+0x28,0xD6,0x6B,0x54,0xC6,0xAA,0x5B,0xD7,0xA2,0x2C,0x1B,0x19,0xCC,0xA2,0x02,0xF6,
+0x9B,0x59,0xBD,0x37,0x6B,0x86,0xB5,0x6D,0x82,0xBA,0xD8,0xEA,0xC9,0x56,0xBC,0xA9,
+0x36,0x58,0xFD,0x3E,0x19,0xF3,0xED,0x0C,0x26,0xA9,0x93,0x38,0xF8,0x4F,0xC1,0x5D,
+0x22,0x06,0xD0,0x97,0xEA,0xE1,0xAD,0xC6,0x55,0xE0,0x81,0x2B,0x28,0x83,0x3A,0xFA,
+0xF4,0x7B,0x21,0x51,0x00,0xBE,0x52,0x38,0xCE,0xCD,0x66,0x79,0xA8,0xF4,0x81,0x56,
+0xE2,0xD0,0x83,0x09,0x47,0x51,0x5B,0x50,0x6A,0xCF,0xDB,0x48,0x1A,0x5D,0x3E,0xF7,
+0xCB,0xF6,0x65,0xF7,0x6C,0xF1,0x95,0xF8,0x02,0x3B,0x32,0x56,0x82,0x39,0x7A,0x5B,
+0xBD,0x2F,0x89,0x1B,0xBF,0xA1,0xB4,0xE8,0xFF,0x7F,0x8D,0x8C,0xDF,0x03,0xF1,0x60,
+0x4E,0x58,0x11,0x4C,0xEB,0xA3,0x3F,0x10,0x2B,0x83,0x9A,0x01,0x73,0xD9,0x94,0x6D,
+0x84,0x00,0x27,0x66,0xAC,0xF0,0x70,0x40,0x09,0x42,0x92,0xAD,0x4F,0x93,0x0D,0x61,
+0x09,0x51,0x24,0xD8,0x92,0xD5,0x0B,0x94,0x61,0xB2,0x87,0xB2,0xED,0xFF,0x9A,0x35,
+0xFF,0x85,0x54,0xCA,0xED,0x44,0x43,0xAC,0x1B,0x3C,0x16,0x6B,0x48,0x4A,0x0A,0x1C,
+0x40,0x88,0x1F,0x92,0xC2,0x0B,0x00,0x05,0xFF,0xF2,0xC8,0x02,0x4A,0xA4,0xAA,0xA9,
+0xCC,0x99,0x96,0x9C,0x2F,0x58,0xE0,0x7D,0xE1,0xBE,0xBB,0x07,0xDC,0x5F,0x04,0x72,
+0x5C,0x31,0x34,0xC3,0xEC,0x5F,0x2D,0xE0,0x3D,0x64,0x90,0x22,0xE6,0xD1,0xEC,0xB8,
+0x2E,0xDD,0x59,0xAE,0xD9,0xA1,0x37,0xBF,0x54,0x35,0xDC,0x73,0x32,0x4F,0x8C,0x04,
+0x1E,0x33,0xB2,0xC9,0x46,0xF1,0xD8,0x5C,0xC8,0x55,0x50,0xC9,0x68,0xBD,0xA8,0xBA,
+0x36,0x09,0x02,0x03,0x01,0x00,0x01,0xA3,0x63,0x30,0x61,0x30,0x0F,0x06,0x03,0x55,
+0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,
+0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x76,0xF3,0x55,0xE1,0xFA,0xA4,0x36,0xFB,0xF0,
+0x9F,0x5C,0x62,0x71,0xED,0x3C,0xF4,0x47,0x38,0x10,0x2B,0x30,0x1F,0x06,0x03,0x55,
+0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0x76,0xF3,0x55,0xE1,0xFA,0xA4,0x36,0xFB,
+0xF0,0x9F,0x5C,0x62,0x71,0xED,0x3C,0xF4,0x47,0x38,0x10,0x2B,0x30,0x0E,0x06,0x03,
+0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x86,0x30,0x0D,0x06,0x09,
+0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x02,0x01,0x00,
+0x66,0xC1,0xC6,0x23,0xF3,0xD9,0xE0,0x2E,0x6E,0x5F,0xE8,0xCF,0xAE,0xB0,0xB0,0x25,
+0x4D,0x2B,0xF8,0x3B,0x58,0x9B,0x40,0x24,0x37,0x5A,0xCB,0xAB,0x16,0x49,0xFF,0xB3,
+0x75,0x79,0x33,0xA1,0x2F,0x6D,0x70,0x17,0x34,0x91,0xFE,0x67,0x7E,0x8F,0xEC,0x9B,
+0xE5,0x5E,0x82,0xA9,0x55,0x1F,0x2F,0xDC,0xD4,0x51,0x07,0x12,0xFE,0xAC,0x16,0x3E,
+0x2C,0x35,0xC6,0x63,0xFC,0xDC,0x10,0xEB,0x0D,0xA3,0xAA,0xD0,0x7C,0xCC,0xD1,0xD0,
+0x2F,0x51,0x2E,0xC4,0x14,0x5A,0xDE,0xE8,0x19,0xE1,0x3E,0xC6,0xCC,0xA4,0x29,0xE7,
+0x2E,0x84,0xAA,0x06,0x30,0x78,0x76,0x54,0x73,0x28,0x98,0x59,0x38,0xE0,0x00,0x0D,
+0x62,0xD3,0x42,0x7D,0x21,0x9F,0xAE,0x3D,0x3A,0x8C,0xD5,0xFA,0x77,0x0D,0x18,0x2B,
+0x16,0x0E,0x5F,0x36,0xE1,0xFC,0x2A,0xB5,0x30,0x24,0xCF,0xE0,0x63,0x0C,0x7B,0x58,
+0x1A,0xFE,0x99,0xBA,0x42,0x12,0xB1,0x91,0xF4,0x7C,0x68,0xE2,0xC8,0xE8,0xAF,0x2C,
+0xEA,0xC9,0x7E,0xAE,0xBB,0x2A,0x3D,0x0D,0x15,0xDC,0x34,0x95,0xB6,0x18,0x74,0xA8,
+0x6A,0x0F,0xC7,0xB4,0xF4,0x13,0xC4,0xE4,0x5B,0xED,0x0A,0xD2,0xA4,0x97,0x4C,0x2A,
+0xED,0x2F,0x6C,0x12,0x89,0x3D,0xF1,0x27,0x70,0xAA,0x6A,0x03,0x52,0x21,0x9F,0x40,
+0xA8,0x67,0x50,0xF2,0xF3,0x5A,0x1F,0xDF,0xDF,0x23,0xF6,0xDC,0x78,0x4E,0xE6,0x98,
+0x4F,0x55,0x3A,0x53,0xE3,0xEF,0xF2,0xF4,0x9F,0xC7,0x7C,0xD8,0x58,0xAF,0x29,0x22,
+0x97,0xB8,0xE0,0xBD,0x91,0x2E,0xB0,0x76,0xEC,0x57,0x11,0xCF,0xEF,0x29,0x44,0xF3,
+0xE9,0x85,0x7A,0x60,0x63,0xE4,0x5D,0x33,0x89,0x17,0xD9,0x31,0xAA,0xDA,0xD6,0xF3,
+0x18,0x35,0x72,0xCF,0x87,0x2B,0x2F,0x63,0x23,0x84,0x5D,0x84,0x8C,0x3F,0x57,0xA0,
+0x88,0xFC,0x99,0x91,0x28,0x26,0x69,0x99,0xD4,0x8F,0x97,0x44,0xBE,0x8E,0xD5,0x48,
+0xB1,0xA4,0x28,0x29,0xF1,0x15,0xB4,0xE1,0xE5,0x9E,0xDD,0xF8,0x8F,0xA6,0x6F,0x26,
+0xD7,0x09,0x3C,0x3A,0x1C,0x11,0x0E,0xA6,0x6C,0x37,0xF7,0xAD,0x44,0x87,0x2C,0x28,
+0xC7,0xD8,0x74,0x82,0xB3,0xD0,0x6F,0x4A,0x57,0xBB,0x35,0x29,0x27,0xA0,0x8B,0xE8,
+0x21,0xA7,0x87,0x64,0x36,0x5D,0xCC,0xD8,0x16,0xAC,0xC7,0xB2,0x27,0x40,0x92,0x55,
+0x38,0x28,0x8D,0x51,0x6E,0xDD,0x14,0x67,0x53,0x6C,0x71,0x5C,0x26,0x84,0x4D,0x75,
+0x5A,0xB6,0x7E,0x60,0x56,0xA9,0x4D,0xAD,0xFB,0x9B,0x1E,0x97,0xF3,0x0D,0xD9,0xD2,
+0x97,0x54,0x77,0xDA,0x3D,0x12,0xB7,0xE0,0x1E,0xEF,0x08,0x06,0xAC,0xF9,0x85,0x87,
+0xE9,0xA2,0xDC,0xAF,0x7E,0x18,0x12,0x83,0xFD,0x56,0x17,0x41,0x2E,0xD5,0x29,0x82,
+0x7D,0x99,0xF4,0x31,0xF6,0x71,0xA9,0xCF,0x2C,0x01,0x27,0xA5,0x05,0xB9,0xAA,0xB2,
+0x48,0x4E,0x2A,0xEF,0x9F,0x93,0x52,0x51,0x95,0x3C,0x52,0x73,0x8E,0x56,0x4C,0x17,
+0x40,0xC0,0x09,0x28,0xE4,0x8B,0x6A,0x48,0x53,0xDB,0xEC,0xCD,0x55,0x55,0xF1,0xC6,
+0xF8,0xE9,0xA2,0x2C,0x4C,0xA6,0xD1,0x26,0x5F,0x7E,0xAF,0x5A,0x4C,0xDA,0x1F,0xA6,
+0xF2,0x1C,0x2C,0x7E,0xAE,0x02,0x16,0xD2,0x56,0xD0,0x2F,0x57,0x53,0x47,0xE8,0x92,
+};
+
+
+/* subject:/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA */
+/* issuer :/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA */
+
+
+const unsigned char GlobalSign_Root_CA_certificate[889]={
+0x30,0x82,0x03,0x75,0x30,0x82,0x02,0x5D,0xA0,0x03,0x02,0x01,0x02,0x02,0x0B,0x04,
+0x00,0x00,0x00,0x00,0x01,0x15,0x4B,0x5A,0xC3,0x94,0x30,0x0D,0x06,0x09,0x2A,0x86,
+0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x57,0x31,0x0B,0x30,0x09,0x06,
+0x03,0x55,0x04,0x06,0x13,0x02,0x42,0x45,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,
+0x0A,0x13,0x10,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69,0x67,0x6E,0x20,0x6E,0x76,
+0x2D,0x73,0x61,0x31,0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x0B,0x13,0x07,0x52,0x6F,
+0x6F,0x74,0x20,0x43,0x41,0x31,0x1B,0x30,0x19,0x06,0x03,0x55,0x04,0x03,0x13,0x12,
+0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69,0x67,0x6E,0x20,0x52,0x6F,0x6F,0x74,0x20,
+0x43,0x41,0x30,0x1E,0x17,0x0D,0x39,0x38,0x30,0x39,0x30,0x31,0x31,0x32,0x30,0x30,
+0x30,0x30,0x5A,0x17,0x0D,0x32,0x38,0x30,0x31,0x32,0x38,0x31,0x32,0x30,0x30,0x30,
+0x30,0x5A,0x30,0x57,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x42,
+0x45,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x0A,0x13,0x10,0x47,0x6C,0x6F,0x62,
+0x61,0x6C,0x53,0x69,0x67,0x6E,0x20,0x6E,0x76,0x2D,0x73,0x61,0x31,0x10,0x30,0x0E,
+0x06,0x03,0x55,0x04,0x0B,0x13,0x07,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x31,0x1B,
+0x30,0x19,0x06,0x03,0x55,0x04,0x03,0x13,0x12,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,
+0x69,0x67,0x6E,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x30,0x82,0x01,0x22,0x30,
+0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,
+0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xDA,0x0E,0xE6,0x99,
+0x8D,0xCE,0xA3,0xE3,0x4F,0x8A,0x7E,0xFB,0xF1,0x8B,0x83,0x25,0x6B,0xEA,0x48,0x1F,
+0xF1,0x2A,0xB0,0xB9,0x95,0x11,0x04,0xBD,0xF0,0x63,0xD1,0xE2,0x67,0x66,0xCF,0x1C,
+0xDD,0xCF,0x1B,0x48,0x2B,0xEE,0x8D,0x89,0x8E,0x9A,0xAF,0x29,0x80,0x65,0xAB,0xE9,
+0xC7,0x2D,0x12,0xCB,0xAB,0x1C,0x4C,0x70,0x07,0xA1,0x3D,0x0A,0x30,0xCD,0x15,0x8D,
+0x4F,0xF8,0xDD,0xD4,0x8C,0x50,0x15,0x1C,0xEF,0x50,0xEE,0xC4,0x2E,0xF7,0xFC,0xE9,
+0x52,0xF2,0x91,0x7D,0xE0,0x6D,0xD5,0x35,0x30,0x8E,0x5E,0x43,0x73,0xF2,0x41,0xE9,
+0xD5,0x6A,0xE3,0xB2,0x89,0x3A,0x56,0x39,0x38,0x6F,0x06,0x3C,0x88,0x69,0x5B,0x2A,
+0x4D,0xC5,0xA7,0x54,0xB8,0x6C,0x89,0xCC,0x9B,0xF9,0x3C,0xCA,0xE5,0xFD,0x89,0xF5,
+0x12,0x3C,0x92,0x78,0x96,0xD6,0xDC,0x74,0x6E,0x93,0x44,0x61,0xD1,0x8D,0xC7,0x46,
+0xB2,0x75,0x0E,0x86,0xE8,0x19,0x8A,0xD5,0x6D,0x6C,0xD5,0x78,0x16,0x95,0xA2,0xE9,
+0xC8,0x0A,0x38,0xEB,0xF2,0x24,0x13,0x4F,0x73,0x54,0x93,0x13,0x85,0x3A,0x1B,0xBC,
+0x1E,0x34,0xB5,0x8B,0x05,0x8C,0xB9,0x77,0x8B,0xB1,0xDB,0x1F,0x20,0x91,0xAB,0x09,
+0x53,0x6E,0x90,0xCE,0x7B,0x37,0x74,0xB9,0x70,0x47,0x91,0x22,0x51,0x63,0x16,0x79,
+0xAE,0xB1,0xAE,0x41,0x26,0x08,0xC8,0x19,0x2B,0xD1,0x46,0xAA,0x48,0xD6,0x64,0x2A,
+0xD7,0x83,0x34,0xFF,0x2C,0x2A,0xC1,0x6C,0x19,0x43,0x4A,0x07,0x85,0xE7,0xD3,0x7C,
+0xF6,0x21,0x68,0xEF,0xEA,0xF2,0x52,0x9F,0x7F,0x93,0x90,0xCF,0x02,0x03,0x01,0x00,
+0x01,0xA3,0x42,0x30,0x40,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,
+0x04,0x03,0x02,0x01,0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,
+0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,
+0x14,0x60,0x7B,0x66,0x1A,0x45,0x0D,0x97,0xCA,0x89,0x50,0x2F,0x7D,0x04,0xCD,0x34,
+0xA8,0xFF,0xFC,0xFD,0x4B,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,
+0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0xD6,0x73,0xE7,0x7C,0x4F,0x76,0xD0,
+0x8D,0xBF,0xEC,0xBA,0xA2,0xBE,0x34,0xC5,0x28,0x32,0xB5,0x7C,0xFC,0x6C,0x9C,0x2C,
+0x2B,0xBD,0x09,0x9E,0x53,0xBF,0x6B,0x5E,0xAA,0x11,0x48,0xB6,0xE5,0x08,0xA3,0xB3,
+0xCA,0x3D,0x61,0x4D,0xD3,0x46,0x09,0xB3,0x3E,0xC3,0xA0,0xE3,0x63,0x55,0x1B,0xF2,
+0xBA,0xEF,0xAD,0x39,0xE1,0x43,0xB9,0x38,0xA3,0xE6,0x2F,0x8A,0x26,0x3B,0xEF,0xA0,
+0x50,0x56,0xF9,0xC6,0x0A,0xFD,0x38,0xCD,0xC4,0x0B,0x70,0x51,0x94,0x97,0x98,0x04,
+0xDF,0xC3,0x5F,0x94,0xD5,0x15,0xC9,0x14,0x41,0x9C,0xC4,0x5D,0x75,0x64,0x15,0x0D,
+0xFF,0x55,0x30,0xEC,0x86,0x8F,0xFF,0x0D,0xEF,0x2C,0xB9,0x63,0x46,0xF6,0xAA,0xFC,
+0xDF,0xBC,0x69,0xFD,0x2E,0x12,0x48,0x64,0x9A,0xE0,0x95,0xF0,0xA6,0xEF,0x29,0x8F,
+0x01,0xB1,0x15,0xB5,0x0C,0x1D,0xA5,0xFE,0x69,0x2C,0x69,0x24,0x78,0x1E,0xB3,0xA7,
+0x1C,0x71,0x62,0xEE,0xCA,0xC8,0x97,0xAC,0x17,0x5D,0x8A,0xC2,0xF8,0x47,0x86,0x6E,
+0x2A,0xC4,0x56,0x31,0x95,0xD0,0x67,0x89,0x85,0x2B,0xF9,0x6C,0xA6,0x5D,0x46,0x9D,
+0x0C,0xAA,0x82,0xE4,0x99,0x51,0xDD,0x70,0xB7,0xDB,0x56,0x3D,0x61,0xE4,0x6A,0xE1,
+0x5C,0xD6,0xF6,0xFE,0x3D,0xDE,0x41,0xCC,0x07,0xAE,0x63,0x52,0xBF,0x53,0x53,0xF4,
+0x2B,0xE9,0xC7,0xFD,0xB6,0xF7,0x82,0x5F,0x85,0xD2,0x41,0x18,0xDB,0x81,0xB3,0x04,
+0x1C,0xC5,0x1F,0xA4,0x80,0x6F,0x15,0x20,0xC9,0xDE,0x0C,0x88,0x0A,0x1D,0xD6,0x66,
+0x55,0xE2,0xFC,0x48,0xC9,0x29,0x26,0x69,0xE0,
+};
+
+
+/* subject:/OU=GlobalSign Root CA - R2/O=GlobalSign/CN=GlobalSign */
+/* issuer :/OU=GlobalSign Root CA - R2/O=GlobalSign/CN=GlobalSign */
+
+
+const unsigned char GlobalSign_Root_CA___R2_certificate[958]={
+0x30,0x82,0x03,0xBA,0x30,0x82,0x02,0xA2,0xA0,0x03,0x02,0x01,0x02,0x02,0x0B,0x04,
+0x00,0x00,0x00,0x00,0x01,0x0F,0x86,0x26,0xE6,0x0D,0x30,0x0D,0x06,0x09,0x2A,0x86,
+0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x4C,0x31,0x20,0x30,0x1E,0x06,
+0x03,0x55,0x04,0x0B,0x13,0x17,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69,0x67,0x6E,
+0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x20,0x2D,0x20,0x52,0x32,0x31,0x13,0x30,
+0x11,0x06,0x03,0x55,0x04,0x0A,0x13,0x0A,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69,
+0x67,0x6E,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x03,0x13,0x0A,0x47,0x6C,0x6F,
+0x62,0x61,0x6C,0x53,0x69,0x67,0x6E,0x30,0x1E,0x17,0x0D,0x30,0x36,0x31,0x32,0x31,
+0x35,0x30,0x38,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x31,0x31,0x32,0x31,0x35,
+0x30,0x38,0x30,0x30,0x30,0x30,0x5A,0x30,0x4C,0x31,0x20,0x30,0x1E,0x06,0x03,0x55,
+0x04,0x0B,0x13,0x17,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69,0x67,0x6E,0x20,0x52,
+0x6F,0x6F,0x74,0x20,0x43,0x41,0x20,0x2D,0x20,0x52,0x32,0x31,0x13,0x30,0x11,0x06,
+0x03,0x55,0x04,0x0A,0x13,0x0A,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69,0x67,0x6E,
+0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x03,0x13,0x0A,0x47,0x6C,0x6F,0x62,0x61,
+0x6C,0x53,0x69,0x67,0x6E,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,
+0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,
+0x0A,0x02,0x82,0x01,0x01,0x00,0xA6,0xCF,0x24,0x0E,0xBE,0x2E,0x6F,0x28,0x99,0x45,
+0x42,0xC4,0xAB,0x3E,0x21,0x54,0x9B,0x0B,0xD3,0x7F,0x84,0x70,0xFA,0x12,0xB3,0xCB,
+0xBF,0x87,0x5F,0xC6,0x7F,0x86,0xD3,0xB2,0x30,0x5C,0xD6,0xFD,0xAD,0xF1,0x7B,0xDC,
+0xE5,0xF8,0x60,0x96,0x09,0x92,0x10,0xF5,0xD0,0x53,0xDE,0xFB,0x7B,0x7E,0x73,0x88,
+0xAC,0x52,0x88,0x7B,0x4A,0xA6,0xCA,0x49,0xA6,0x5E,0xA8,0xA7,0x8C,0x5A,0x11,0xBC,
+0x7A,0x82,0xEB,0xBE,0x8C,0xE9,0xB3,0xAC,0x96,0x25,0x07,0x97,0x4A,0x99,0x2A,0x07,
+0x2F,0xB4,0x1E,0x77,0xBF,0x8A,0x0F,0xB5,0x02,0x7C,0x1B,0x96,0xB8,0xC5,0xB9,0x3A,
+0x2C,0xBC,0xD6,0x12,0xB9,0xEB,0x59,0x7D,0xE2,0xD0,0x06,0x86,0x5F,0x5E,0x49,0x6A,
+0xB5,0x39,0x5E,0x88,0x34,0xEC,0xBC,0x78,0x0C,0x08,0x98,0x84,0x6C,0xA8,0xCD,0x4B,
+0xB4,0xA0,0x7D,0x0C,0x79,0x4D,0xF0,0xB8,0x2D,0xCB,0x21,0xCA,0xD5,0x6C,0x5B,0x7D,
+0xE1,0xA0,0x29,0x84,0xA1,0xF9,0xD3,0x94,0x49,0xCB,0x24,0x62,0x91,0x20,0xBC,0xDD,
+0x0B,0xD5,0xD9,0xCC,0xF9,0xEA,0x27,0x0A,0x2B,0x73,0x91,0xC6,0x9D,0x1B,0xAC,0xC8,
+0xCB,0xE8,0xE0,0xA0,0xF4,0x2F,0x90,0x8B,0x4D,0xFB,0xB0,0x36,0x1B,0xF6,0x19,0x7A,
+0x85,0xE0,0x6D,0xF2,0x61,0x13,0x88,0x5C,0x9F,0xE0,0x93,0x0A,0x51,0x97,0x8A,0x5A,
+0xCE,0xAF,0xAB,0xD5,0xF7,0xAA,0x09,0xAA,0x60,0xBD,0xDC,0xD9,0x5F,0xDF,0x72,0xA9,
+0x60,0x13,0x5E,0x00,0x01,0xC9,0x4A,0xFA,0x3F,0xA4,0xEA,0x07,0x03,0x21,0x02,0x8E,
+0x82,0xCA,0x03,0xC2,0x9B,0x8F,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0x9C,0x30,0x81,
+0x99,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,
+0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,
+0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x9B,0xE2,0x07,
+0x57,0x67,0x1C,0x1E,0xC0,0x6A,0x06,0xDE,0x59,0xB4,0x9A,0x2D,0xDF,0xDC,0x19,0x86,
+0x2E,0x30,0x36,0x06,0x03,0x55,0x1D,0x1F,0x04,0x2F,0x30,0x2D,0x30,0x2B,0xA0,0x29,
+0xA0,0x27,0x86,0x25,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E,0x67,
+0x6C,0x6F,0x62,0x61,0x6C,0x73,0x69,0x67,0x6E,0x2E,0x6E,0x65,0x74,0x2F,0x72,0x6F,
+0x6F,0x74,0x2D,0x72,0x32,0x2E,0x63,0x72,0x6C,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,
+0x04,0x18,0x30,0x16,0x80,0x14,0x9B,0xE2,0x07,0x57,0x67,0x1C,0x1E,0xC0,0x6A,0x06,
+0xDE,0x59,0xB4,0x9A,0x2D,0xDF,0xDC,0x19,0x86,0x2E,0x30,0x0D,0x06,0x09,0x2A,0x86,
+0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x99,0x81,
+0x53,0x87,0x1C,0x68,0x97,0x86,0x91,0xEC,0xE0,0x4A,0xB8,0x44,0x0B,0xAB,0x81,0xAC,
+0x27,0x4F,0xD6,0xC1,0xB8,0x1C,0x43,0x78,0xB3,0x0C,0x9A,0xFC,0xEA,0x2C,0x3C,0x6E,
+0x61,0x1B,0x4D,0x4B,0x29,0xF5,0x9F,0x05,0x1D,0x26,0xC1,0xB8,0xE9,0x83,0x00,0x62,
+0x45,0xB6,0xA9,0x08,0x93,0xB9,0xA9,0x33,0x4B,0x18,0x9A,0xC2,0xF8,0x87,0x88,0x4E,
+0xDB,0xDD,0x71,0x34,0x1A,0xC1,0x54,0xDA,0x46,0x3F,0xE0,0xD3,0x2A,0xAB,0x6D,0x54,
+0x22,0xF5,0x3A,0x62,0xCD,0x20,0x6F,0xBA,0x29,0x89,0xD7,0xDD,0x91,0xEE,0xD3,0x5C,
+0xA2,0x3E,0xA1,0x5B,0x41,0xF5,0xDF,0xE5,0x64,0x43,0x2D,0xE9,0xD5,0x39,0xAB,0xD2,
+0xA2,0xDF,0xB7,0x8B,0xD0,0xC0,0x80,0x19,0x1C,0x45,0xC0,0x2D,0x8C,0xE8,0xF8,0x2D,
+0xA4,0x74,0x56,0x49,0xC5,0x05,0xB5,0x4F,0x15,0xDE,0x6E,0x44,0x78,0x39,0x87,0xA8,
+0x7E,0xBB,0xF3,0x79,0x18,0x91,0xBB,0xF4,0x6F,0x9D,0xC1,0xF0,0x8C,0x35,0x8C,0x5D,
+0x01,0xFB,0xC3,0x6D,0xB9,0xEF,0x44,0x6D,0x79,0x46,0x31,0x7E,0x0A,0xFE,0xA9,0x82,
+0xC1,0xFF,0xEF,0xAB,0x6E,0x20,0xC4,0x50,0xC9,0x5F,0x9D,0x4D,0x9B,0x17,0x8C,0x0C,
+0xE5,0x01,0xC9,0xA0,0x41,0x6A,0x73,0x53,0xFA,0xA5,0x50,0xB4,0x6E,0x25,0x0F,0xFB,
+0x4C,0x18,0xF4,0xFD,0x52,0xD9,0x8E,0x69,0xB1,0xE8,0x11,0x0F,0xDE,0x88,0xD8,0xFB,
+0x1D,0x49,0xF7,0xAA,0xDE,0x95,0xCF,0x20,0x78,0xC2,0x60,0x12,0xDB,0x25,0x40,0x8C,
+0x6A,0xFC,0x7E,0x42,0x38,0x40,0x64,0x12,0xF7,0x9E,0x81,0xE1,0x93,0x2E,
+};
+
+
+/* subject:/OU=GlobalSign Root CA - R3/O=GlobalSign/CN=GlobalSign */
+/* issuer :/OU=GlobalSign Root CA - R3/O=GlobalSign/CN=GlobalSign */
+
+
+const unsigned char GlobalSign_Root_CA___R3_certificate[867]={
+0x30,0x82,0x03,0x5F,0x30,0x82,0x02,0x47,0xA0,0x03,0x02,0x01,0x02,0x02,0x0B,0x04,
+0x00,0x00,0x00,0x00,0x01,0x21,0x58,0x53,0x08,0xA2,0x30,0x0D,0x06,0x09,0x2A,0x86,
+0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30,0x4C,0x31,0x20,0x30,0x1E,0x06,
+0x03,0x55,0x04,0x0B,0x13,0x17,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69,0x67,0x6E,
+0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x20,0x2D,0x20,0x52,0x33,0x31,0x13,0x30,
+0x11,0x06,0x03,0x55,0x04,0x0A,0x13,0x0A,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69,
+0x67,0x6E,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x03,0x13,0x0A,0x47,0x6C,0x6F,
+0x62,0x61,0x6C,0x53,0x69,0x67,0x6E,0x30,0x1E,0x17,0x0D,0x30,0x39,0x30,0x33,0x31,
+0x38,0x31,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x39,0x30,0x33,0x31,0x38,
+0x31,0x30,0x30,0x30,0x30,0x30,0x5A,0x30,0x4C,0x31,0x20,0x30,0x1E,0x06,0x03,0x55,
+0x04,0x0B,0x13,0x17,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69,0x67,0x6E,0x20,0x52,
+0x6F,0x6F,0x74,0x20,0x43,0x41,0x20,0x2D,0x20,0x52,0x33,0x31,0x13,0x30,0x11,0x06,
+0x03,0x55,0x04,0x0A,0x13,0x0A,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x53,0x69,0x67,0x6E,
+0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x03,0x13,0x0A,0x47,0x6C,0x6F,0x62,0x61,
+0x6C,0x53,0x69,0x67,0x6E,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,
+0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,
+0x0A,0x02,0x82,0x01,0x01,0x00,0xCC,0x25,0x76,0x90,0x79,0x06,0x78,0x22,0x16,0xF5,
+0xC0,0x83,0xB6,0x84,0xCA,0x28,0x9E,0xFD,0x05,0x76,0x11,0xC5,0xAD,0x88,0x72,0xFC,
+0x46,0x02,0x43,0xC7,0xB2,0x8A,0x9D,0x04,0x5F,0x24,0xCB,0x2E,0x4B,0xE1,0x60,0x82,
+0x46,0xE1,0x52,0xAB,0x0C,0x81,0x47,0x70,0x6C,0xDD,0x64,0xD1,0xEB,0xF5,0x2C,0xA3,
+0x0F,0x82,0x3D,0x0C,0x2B,0xAE,0x97,0xD7,0xB6,0x14,0x86,0x10,0x79,0xBB,0x3B,0x13,
+0x80,0x77,0x8C,0x08,0xE1,0x49,0xD2,0x6A,0x62,0x2F,0x1F,0x5E,0xFA,0x96,0x68,0xDF,
+0x89,0x27,0x95,0x38,0x9F,0x06,0xD7,0x3E,0xC9,0xCB,0x26,0x59,0x0D,0x73,0xDE,0xB0,
+0xC8,0xE9,0x26,0x0E,0x83,0x15,0xC6,0xEF,0x5B,0x8B,0xD2,0x04,0x60,0xCA,0x49,0xA6,
+0x28,0xF6,0x69,0x3B,0xF6,0xCB,0xC8,0x28,0x91,0xE5,0x9D,0x8A,0x61,0x57,0x37,0xAC,
+0x74,0x14,0xDC,0x74,0xE0,0x3A,0xEE,0x72,0x2F,0x2E,0x9C,0xFB,0xD0,0xBB,0xBF,0xF5,
+0x3D,0x00,0xE1,0x06,0x33,0xE8,0x82,0x2B,0xAE,0x53,0xA6,0x3A,0x16,0x73,0x8C,0xDD,
+0x41,0x0E,0x20,0x3A,0xC0,0xB4,0xA7,0xA1,0xE9,0xB2,0x4F,0x90,0x2E,0x32,0x60,0xE9,
+0x57,0xCB,0xB9,0x04,0x92,0x68,0x68,0xE5,0x38,0x26,0x60,0x75,0xB2,0x9F,0x77,0xFF,
+0x91,0x14,0xEF,0xAE,0x20,0x49,0xFC,0xAD,0x40,0x15,0x48,0xD1,0x02,0x31,0x61,0x19,
+0x5E,0xB8,0x97,0xEF,0xAD,0x77,0xB7,0x64,0x9A,0x7A,0xBF,0x5F,0xC1,0x13,0xEF,0x9B,
+0x62,0xFB,0x0D,0x6C,0xE0,0x54,0x69,0x16,0xA9,0x03,0xDA,0x6E,0xE9,0x83,0x93,0x71,
+0x76,0xC6,0x69,0x85,0x82,0x17,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40,0x30,
+0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,
+0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,
+0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x8F,0xF0,0x4B,0x7F,0xA8,
+0x2E,0x45,0x24,0xAE,0x4D,0x50,0xFA,0x63,0x9A,0x8B,0xDE,0xE2,0xDD,0x1B,0xBC,0x30,
+0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82,
+0x01,0x01,0x00,0x4B,0x40,0xDB,0xC0,0x50,0xAA,0xFE,0xC8,0x0C,0xEF,0xF7,0x96,0x54,
+0x45,0x49,0xBB,0x96,0x00,0x09,0x41,0xAC,0xB3,0x13,0x86,0x86,0x28,0x07,0x33,0xCA,
+0x6B,0xE6,0x74,0xB9,0xBA,0x00,0x2D,0xAE,0xA4,0x0A,0xD3,0xF5,0xF1,0xF1,0x0F,0x8A,
+0xBF,0x73,0x67,0x4A,0x83,0xC7,0x44,0x7B,0x78,0xE0,0xAF,0x6E,0x6C,0x6F,0x03,0x29,
+0x8E,0x33,0x39,0x45,0xC3,0x8E,0xE4,0xB9,0x57,0x6C,0xAA,0xFC,0x12,0x96,0xEC,0x53,
+0xC6,0x2D,0xE4,0x24,0x6C,0xB9,0x94,0x63,0xFB,0xDC,0x53,0x68,0x67,0x56,0x3E,0x83,
+0xB8,0xCF,0x35,0x21,0xC3,0xC9,0x68,0xFE,0xCE,0xDA,0xC2,0x53,0xAA,0xCC,0x90,0x8A,
+0xE9,0xF0,0x5D,0x46,0x8C,0x95,0xDD,0x7A,0x58,0x28,0x1A,0x2F,0x1D,0xDE,0xCD,0x00,
+0x37,0x41,0x8F,0xED,0x44,0x6D,0xD7,0x53,0x28,0x97,0x7E,0xF3,0x67,0x04,0x1E,0x15,
+0xD7,0x8A,0x96,0xB4,0xD3,0xDE,0x4C,0x27,0xA4,0x4C,0x1B,0x73,0x73,0x76,0xF4,0x17,
+0x99,0xC2,0x1F,0x7A,0x0E,0xE3,0x2D,0x08,0xAD,0x0A,0x1C,0x2C,0xFF,0x3C,0xAB,0x55,
+0x0E,0x0F,0x91,0x7E,0x36,0xEB,0xC3,0x57,0x49,0xBE,0xE1,0x2E,0x2D,0x7C,0x60,0x8B,
+0xC3,0x41,0x51,0x13,0x23,0x9D,0xCE,0xF7,0x32,0x6B,0x94,0x01,0xA8,0x99,0xE7,0x2C,
+0x33,0x1F,0x3A,0x3B,0x25,0xD2,0x86,0x40,0xCE,0x3B,0x2C,0x86,0x78,0xC9,0x61,0x2F,
+0x14,0xBA,0xEE,0xDB,0x55,0x6F,0xDF,0x84,0xEE,0x05,0x09,0x4D,0xBD,0x28,0xD8,0x72,
+0xCE,0xD3,0x62,0x50,0x65,0x1E,0xEB,0x92,0x97,0x83,0x31,0xD9,0xB3,0xB5,0xCA,0x47,
+0x58,0x3F,0x5F,
+};
+
+
+/* subject:/C=US/O=The Go Daddy Group, Inc./OU=Go Daddy Class 2 Certification Authority */
+/* issuer :/C=US/O=The Go Daddy Group, Inc./OU=Go Daddy Class 2 Certification Authority */
+
+
+const unsigned char Go_Daddy_Class_2_CA_certificate[1028]={
+0x30,0x82,0x04,0x00,0x30,0x82,0x02,0xE8,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x00,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,
+0x63,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x21,
+0x30,0x1F,0x06,0x03,0x55,0x04,0x0A,0x13,0x18,0x54,0x68,0x65,0x20,0x47,0x6F,0x20,
+0x44,0x61,0x64,0x64,0x79,0x20,0x47,0x72,0x6F,0x75,0x70,0x2C,0x20,0x49,0x6E,0x63,
+0x2E,0x31,0x31,0x30,0x2F,0x06,0x03,0x55,0x04,0x0B,0x13,0x28,0x47,0x6F,0x20,0x44,
+0x61,0x64,0x64,0x79,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x32,0x20,0x43,0x65,0x72,
+0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,
+0x72,0x69,0x74,0x79,0x30,0x1E,0x17,0x0D,0x30,0x34,0x30,0x36,0x32,0x39,0x31,0x37,
+0x30,0x36,0x32,0x30,0x5A,0x17,0x0D,0x33,0x34,0x30,0x36,0x32,0x39,0x31,0x37,0x30,
+0x36,0x32,0x30,0x5A,0x30,0x63,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,
+0x02,0x55,0x53,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x0A,0x13,0x18,0x54,0x68,
+0x65,0x20,0x47,0x6F,0x20,0x44,0x61,0x64,0x64,0x79,0x20,0x47,0x72,0x6F,0x75,0x70,
+0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x31,0x30,0x2F,0x06,0x03,0x55,0x04,0x0B,0x13,
+0x28,0x47,0x6F,0x20,0x44,0x61,0x64,0x64,0x79,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,
+0x32,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,
+0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x82,0x01,0x20,0x30,0x0D,0x06,
+0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0D,
+0x00,0x30,0x82,0x01,0x08,0x02,0x82,0x01,0x01,0x00,0xDE,0x9D,0xD7,0xEA,0x57,0x18,
+0x49,0xA1,0x5B,0xEB,0xD7,0x5F,0x48,0x86,0xEA,0xBE,0xDD,0xFF,0xE4,0xEF,0x67,0x1C,
+0xF4,0x65,0x68,0xB3,0x57,0x71,0xA0,0x5E,0x77,0xBB,0xED,0x9B,0x49,0xE9,0x70,0x80,
+0x3D,0x56,0x18,0x63,0x08,0x6F,0xDA,0xF2,0xCC,0xD0,0x3F,0x7F,0x02,0x54,0x22,0x54,
+0x10,0xD8,0xB2,0x81,0xD4,0xC0,0x75,0x3D,0x4B,0x7F,0xC7,0x77,0xC3,0x3E,0x78,0xAB,
+0x1A,0x03,0xB5,0x20,0x6B,0x2F,0x6A,0x2B,0xB1,0xC5,0x88,0x7E,0xC4,0xBB,0x1E,0xB0,
+0xC1,0xD8,0x45,0x27,0x6F,0xAA,0x37,0x58,0xF7,0x87,0x26,0xD7,0xD8,0x2D,0xF6,0xA9,
+0x17,0xB7,0x1F,0x72,0x36,0x4E,0xA6,0x17,0x3F,0x65,0x98,0x92,0xDB,0x2A,0x6E,0x5D,
+0xA2,0xFE,0x88,0xE0,0x0B,0xDE,0x7F,0xE5,0x8D,0x15,0xE1,0xEB,0xCB,0x3A,0xD5,0xE2,
+0x12,0xA2,0x13,0x2D,0xD8,0x8E,0xAF,0x5F,0x12,0x3D,0xA0,0x08,0x05,0x08,0xB6,0x5C,
+0xA5,0x65,0x38,0x04,0x45,0x99,0x1E,0xA3,0x60,0x60,0x74,0xC5,0x41,0xA5,0x72,0x62,
+0x1B,0x62,0xC5,0x1F,0x6F,0x5F,0x1A,0x42,0xBE,0x02,0x51,0x65,0xA8,0xAE,0x23,0x18,
+0x6A,0xFC,0x78,0x03,0xA9,0x4D,0x7F,0x80,0xC3,0xFA,0xAB,0x5A,0xFC,0xA1,0x40,0xA4,
+0xCA,0x19,0x16,0xFE,0xB2,0xC8,0xEF,0x5E,0x73,0x0D,0xEE,0x77,0xBD,0x9A,0xF6,0x79,
+0x98,0xBC,0xB1,0x07,0x67,0xA2,0x15,0x0D,0xDD,0xA0,0x58,0xC6,0x44,0x7B,0x0A,0x3E,
+0x62,0x28,0x5F,0xBA,0x41,0x07,0x53,0x58,0xCF,0x11,0x7E,0x38,0x74,0xC5,0xF8,0xFF,
+0xB5,0x69,0x90,0x8F,0x84,0x74,0xEA,0x97,0x1B,0xAF,0x02,0x01,0x03,0xA3,0x81,0xC0,
+0x30,0x81,0xBD,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xD2,0xC4,
+0xB0,0xD2,0x91,0xD4,0x4C,0x11,0x71,0xB3,0x61,0xCB,0x3D,0xA1,0xFE,0xDD,0xA8,0x6A,
+0xD4,0xE3,0x30,0x81,0x8D,0x06,0x03,0x55,0x1D,0x23,0x04,0x81,0x85,0x30,0x81,0x82,
+0x80,0x14,0xD2,0xC4,0xB0,0xD2,0x91,0xD4,0x4C,0x11,0x71,0xB3,0x61,0xCB,0x3D,0xA1,
+0xFE,0xDD,0xA8,0x6A,0xD4,0xE3,0xA1,0x67,0xA4,0x65,0x30,0x63,0x31,0x0B,0x30,0x09,
+0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,
+0x04,0x0A,0x13,0x18,0x54,0x68,0x65,0x20,0x47,0x6F,0x20,0x44,0x61,0x64,0x64,0x79,
+0x20,0x47,0x72,0x6F,0x75,0x70,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x31,0x30,0x2F,
+0x06,0x03,0x55,0x04,0x0B,0x13,0x28,0x47,0x6F,0x20,0x44,0x61,0x64,0x64,0x79,0x20,
+0x43,0x6C,0x61,0x73,0x73,0x20,0x32,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,
+0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x82,
+0x01,0x00,0x30,0x0C,0x06,0x03,0x55,0x1D,0x13,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,
+0x82,0x01,0x01,0x00,0x32,0x4B,0xF3,0xB2,0xCA,0x3E,0x91,0xFC,0x12,0xC6,0xA1,0x07,
+0x8C,0x8E,0x77,0xA0,0x33,0x06,0x14,0x5C,0x90,0x1E,0x18,0xF7,0x08,0xA6,0x3D,0x0A,
+0x19,0xF9,0x87,0x80,0x11,0x6E,0x69,0xE4,0x96,0x17,0x30,0xFF,0x34,0x91,0x63,0x72,
+0x38,0xEE,0xCC,0x1C,0x01,0xA3,0x1D,0x94,0x28,0xA4,0x31,0xF6,0x7A,0xC4,0x54,0xD7,
+0xF6,0xE5,0x31,0x58,0x03,0xA2,0xCC,0xCE,0x62,0xDB,0x94,0x45,0x73,0xB5,0xBF,0x45,
+0xC9,0x24,0xB5,0xD5,0x82,0x02,0xAD,0x23,0x79,0x69,0x8D,0xB8,0xB6,0x4D,0xCE,0xCF,
+0x4C,0xCA,0x33,0x23,0xE8,0x1C,0x88,0xAA,0x9D,0x8B,0x41,0x6E,0x16,0xC9,0x20,0xE5,
+0x89,0x9E,0xCD,0x3B,0xDA,0x70,0xF7,0x7E,0x99,0x26,0x20,0x14,0x54,0x25,0xAB,0x6E,
+0x73,0x85,0xE6,0x9B,0x21,0x9D,0x0A,0x6C,0x82,0x0E,0xA8,0xF8,0xC2,0x0C,0xFA,0x10,
+0x1E,0x6C,0x96,0xEF,0x87,0x0D,0xC4,0x0F,0x61,0x8B,0xAD,0xEE,0x83,0x2B,0x95,0xF8,
+0x8E,0x92,0x84,0x72,0x39,0xEB,0x20,0xEA,0x83,0xED,0x83,0xCD,0x97,0x6E,0x08,0xBC,
+0xEB,0x4E,0x26,0xB6,0x73,0x2B,0xE4,0xD3,0xF6,0x4C,0xFE,0x26,0x71,0xE2,0x61,0x11,
+0x74,0x4A,0xFF,0x57,0x1A,0x87,0x0F,0x75,0x48,0x2E,0xCF,0x51,0x69,0x17,0xA0,0x02,
+0x12,0x61,0x95,0xD5,0xD1,0x40,0xB2,0x10,0x4C,0xEE,0xC4,0xAC,0x10,0x43,0xA6,0xA5,
+0x9E,0x0A,0xD5,0x95,0x62,0x9A,0x0D,0xCF,0x88,0x82,0xC5,0x32,0x0C,0xE4,0x2B,0x9F,
+0x45,0xE6,0x0D,0x9F,0x28,0x9C,0xB1,0xB9,0x2A,0x5A,0x57,0xAD,0x37,0x0F,0xAF,0x1D,
+0x7F,0xDB,0xBD,0x9F,
+};
+
+
+/* subject:/C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc./CN=Go Daddy Root Certificate Authority - G2 */
+/* issuer :/C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc./CN=Go Daddy Root Certificate Authority - G2 */
+
+
+const unsigned char Go_Daddy_Root_Certificate_Authority___G2_certificate[969]={
+0x30,0x82,0x03,0xC5,0x30,0x82,0x02,0xAD,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x00,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30,
+0x81,0x83,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,
+0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x08,0x13,0x07,0x41,0x72,0x69,0x7A,0x6F,0x6E,
+0x61,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x07,0x13,0x0A,0x53,0x63,0x6F,0x74,
+0x74,0x73,0x64,0x61,0x6C,0x65,0x31,0x1A,0x30,0x18,0x06,0x03,0x55,0x04,0x0A,0x13,
+0x11,0x47,0x6F,0x44,0x61,0x64,0x64,0x79,0x2E,0x63,0x6F,0x6D,0x2C,0x20,0x49,0x6E,
+0x63,0x2E,0x31,0x31,0x30,0x2F,0x06,0x03,0x55,0x04,0x03,0x13,0x28,0x47,0x6F,0x20,
+0x44,0x61,0x64,0x64,0x79,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65,0x72,0x74,0x69,
+0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,
+0x20,0x2D,0x20,0x47,0x32,0x30,0x1E,0x17,0x0D,0x30,0x39,0x30,0x39,0x30,0x31,0x30,
+0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x37,0x31,0x32,0x33,0x31,0x32,0x33,
+0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0x83,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,
+0x06,0x13,0x02,0x55,0x53,0x31,0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x08,0x13,0x07,
+0x41,0x72,0x69,0x7A,0x6F,0x6E,0x61,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x07,
+0x13,0x0A,0x53,0x63,0x6F,0x74,0x74,0x73,0x64,0x61,0x6C,0x65,0x31,0x1A,0x30,0x18,
+0x06,0x03,0x55,0x04,0x0A,0x13,0x11,0x47,0x6F,0x44,0x61,0x64,0x64,0x79,0x2E,0x63,
+0x6F,0x6D,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x31,0x30,0x2F,0x06,0x03,0x55,0x04,
+0x03,0x13,0x28,0x47,0x6F,0x20,0x44,0x61,0x64,0x64,0x79,0x20,0x52,0x6F,0x6F,0x74,
+0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74,
+0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x2D,0x20,0x47,0x32,0x30,0x82,0x01,0x22,0x30,
+0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,
+0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xBF,0x71,0x62,0x08,
+0xF1,0xFA,0x59,0x34,0xF7,0x1B,0xC9,0x18,0xA3,0xF7,0x80,0x49,0x58,0xE9,0x22,0x83,
+0x13,0xA6,0xC5,0x20,0x43,0x01,0x3B,0x84,0xF1,0xE6,0x85,0x49,0x9F,0x27,0xEA,0xF6,
+0x84,0x1B,0x4E,0xA0,0xB4,0xDB,0x70,0x98,0xC7,0x32,0x01,0xB1,0x05,0x3E,0x07,0x4E,
+0xEE,0xF4,0xFA,0x4F,0x2F,0x59,0x30,0x22,0xE7,0xAB,0x19,0x56,0x6B,0xE2,0x80,0x07,
+0xFC,0xF3,0x16,0x75,0x80,0x39,0x51,0x7B,0xE5,0xF9,0x35,0xB6,0x74,0x4E,0xA9,0x8D,
+0x82,0x13,0xE4,0xB6,0x3F,0xA9,0x03,0x83,0xFA,0xA2,0xBE,0x8A,0x15,0x6A,0x7F,0xDE,
+0x0B,0xC3,0xB6,0x19,0x14,0x05,0xCA,0xEA,0xC3,0xA8,0x04,0x94,0x3B,0x46,0x7C,0x32,
+0x0D,0xF3,0x00,0x66,0x22,0xC8,0x8D,0x69,0x6D,0x36,0x8C,0x11,0x18,0xB7,0xD3,0xB2,
+0x1C,0x60,0xB4,0x38,0xFA,0x02,0x8C,0xCE,0xD3,0xDD,0x46,0x07,0xDE,0x0A,0x3E,0xEB,
+0x5D,0x7C,0xC8,0x7C,0xFB,0xB0,0x2B,0x53,0xA4,0x92,0x62,0x69,0x51,0x25,0x05,0x61,
+0x1A,0x44,0x81,0x8C,0x2C,0xA9,0x43,0x96,0x23,0xDF,0xAC,0x3A,0x81,0x9A,0x0E,0x29,
+0xC5,0x1C,0xA9,0xE9,0x5D,0x1E,0xB6,0x9E,0x9E,0x30,0x0A,0x39,0xCE,0xF1,0x88,0x80,
+0xFB,0x4B,0x5D,0xCC,0x32,0xEC,0x85,0x62,0x43,0x25,0x34,0x02,0x56,0x27,0x01,0x91,
+0xB4,0x3B,0x70,0x2A,0x3F,0x6E,0xB1,0xE8,0x9C,0x88,0x01,0x7D,0x9F,0xD4,0xF9,0xDB,
+0x53,0x6D,0x60,0x9D,0xBF,0x2C,0xE7,0x58,0xAB,0xB8,0x5F,0x46,0xFC,0xCE,0xC4,0x1B,
+0x03,0x3C,0x09,0xEB,0x49,0x31,0x5C,0x69,0x46,0xB3,0xE0,0x47,0x02,0x03,0x01,0x00,
+0x01,0xA3,0x42,0x30,0x40,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,
+0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,
+0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,
+0x14,0x3A,0x9A,0x85,0x07,0x10,0x67,0x28,0xB6,0xEF,0xF6,0xBD,0x05,0x41,0x6E,0x20,
+0xC1,0x94,0xDA,0x0F,0xDE,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,
+0x01,0x0B,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x99,0xDB,0x5D,0x79,0xD5,0xF9,0x97,
+0x59,0x67,0x03,0x61,0xF1,0x7E,0x3B,0x06,0x31,0x75,0x2D,0xA1,0x20,0x8E,0x4F,0x65,
+0x87,0xB4,0xF7,0xA6,0x9C,0xBC,0xD8,0xE9,0x2F,0xD0,0xDB,0x5A,0xEE,0xCF,0x74,0x8C,
+0x73,0xB4,0x38,0x42,0xDA,0x05,0x7B,0xF8,0x02,0x75,0xB8,0xFD,0xA5,0xB1,0xD7,0xAE,
+0xF6,0xD7,0xDE,0x13,0xCB,0x53,0x10,0x7E,0x8A,0x46,0xD1,0x97,0xFA,0xB7,0x2E,0x2B,
+0x11,0xAB,0x90,0xB0,0x27,0x80,0xF9,0xE8,0x9F,0x5A,0xE9,0x37,0x9F,0xAB,0xE4,0xDF,
+0x6C,0xB3,0x85,0x17,0x9D,0x3D,0xD9,0x24,0x4F,0x79,0x91,0x35,0xD6,0x5F,0x04,0xEB,
+0x80,0x83,0xAB,0x9A,0x02,0x2D,0xB5,0x10,0xF4,0xD8,0x90,0xC7,0x04,0x73,0x40,0xED,
+0x72,0x25,0xA0,0xA9,0x9F,0xEC,0x9E,0xAB,0x68,0x12,0x99,0x57,0xC6,0x8F,0x12,0x3A,
+0x09,0xA4,0xBD,0x44,0xFD,0x06,0x15,0x37,0xC1,0x9B,0xE4,0x32,0xA3,0xED,0x38,0xE8,
+0xD8,0x64,0xF3,0x2C,0x7E,0x14,0xFC,0x02,0xEA,0x9F,0xCD,0xFF,0x07,0x68,0x17,0xDB,
+0x22,0x90,0x38,0x2D,0x7A,0x8D,0xD1,0x54,0xF1,0x69,0xE3,0x5F,0x33,0xCA,0x7A,0x3D,
+0x7B,0x0A,0xE3,0xCA,0x7F,0x5F,0x39,0xE5,0xE2,0x75,0xBA,0xC5,0x76,0x18,0x33,0xCE,
+0x2C,0xF0,0x2F,0x4C,0xAD,0xF7,0xB1,0xE7,0xCE,0x4F,0xA8,0xC4,0x9B,0x4A,0x54,0x06,
+0xC5,0x7F,0x7D,0xD5,0x08,0x0F,0xE2,0x1C,0xFE,0x7E,0x17,0xB8,0xAC,0x5E,0xF6,0xD4,
+0x16,0xB2,0x43,0x09,0x0C,0x4D,0xF6,0xA7,0x6B,0xB4,0x99,0x84,0x65,0xCA,0x7A,0x88,
+0xE2,0xE2,0x44,0xBE,0x5C,0xF7,0xEA,0x1C,0xF5,
+};
+
+
+/* subject:/C=US/O=GTE Corporation/OU=GTE CyberTrust Solutions, Inc./CN=GTE CyberTrust Global Root */
+/* issuer :/C=US/O=GTE Corporation/OU=GTE CyberTrust Solutions, Inc./CN=GTE CyberTrust Global Root */
+
+
+const unsigned char GTE_CyberTrust_Global_Root_certificate[606]={
+0x30,0x82,0x02,0x5A,0x30,0x82,0x01,0xC3,0x02,0x02,0x01,0xA5,0x30,0x0D,0x06,0x09,
+0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,0x30,0x75,0x31,0x0B,0x30,
+0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x18,0x30,0x16,0x06,0x03,
+0x55,0x04,0x0A,0x13,0x0F,0x47,0x54,0x45,0x20,0x43,0x6F,0x72,0x70,0x6F,0x72,0x61,
+0x74,0x69,0x6F,0x6E,0x31,0x27,0x30,0x25,0x06,0x03,0x55,0x04,0x0B,0x13,0x1E,0x47,
+0x54,0x45,0x20,0x43,0x79,0x62,0x65,0x72,0x54,0x72,0x75,0x73,0x74,0x20,0x53,0x6F,
+0x6C,0x75,0x74,0x69,0x6F,0x6E,0x73,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x23,0x30,
+0x21,0x06,0x03,0x55,0x04,0x03,0x13,0x1A,0x47,0x54,0x45,0x20,0x43,0x79,0x62,0x65,
+0x72,0x54,0x72,0x75,0x73,0x74,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x52,0x6F,
+0x6F,0x74,0x30,0x1E,0x17,0x0D,0x39,0x38,0x30,0x38,0x31,0x33,0x30,0x30,0x32,0x39,
+0x30,0x30,0x5A,0x17,0x0D,0x31,0x38,0x30,0x38,0x31,0x33,0x32,0x33,0x35,0x39,0x30,
+0x30,0x5A,0x30,0x75,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,
+0x53,0x31,0x18,0x30,0x16,0x06,0x03,0x55,0x04,0x0A,0x13,0x0F,0x47,0x54,0x45,0x20,
+0x43,0x6F,0x72,0x70,0x6F,0x72,0x61,0x74,0x69,0x6F,0x6E,0x31,0x27,0x30,0x25,0x06,
+0x03,0x55,0x04,0x0B,0x13,0x1E,0x47,0x54,0x45,0x20,0x43,0x79,0x62,0x65,0x72,0x54,
+0x72,0x75,0x73,0x74,0x20,0x53,0x6F,0x6C,0x75,0x74,0x69,0x6F,0x6E,0x73,0x2C,0x20,
+0x49,0x6E,0x63,0x2E,0x31,0x23,0x30,0x21,0x06,0x03,0x55,0x04,0x03,0x13,0x1A,0x47,
+0x54,0x45,0x20,0x43,0x79,0x62,0x65,0x72,0x54,0x72,0x75,0x73,0x74,0x20,0x47,0x6C,
+0x6F,0x62,0x61,0x6C,0x20,0x52,0x6F,0x6F,0x74,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,
+0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,
+0x81,0x89,0x02,0x81,0x81,0x00,0x95,0x0F,0xA0,0xB6,0xF0,0x50,0x9C,0xE8,0x7A,0xC7,
+0x88,0xCD,0xDD,0x17,0x0E,0x2E,0xB0,0x94,0xD0,0x1B,0x3D,0x0E,0xF6,0x94,0xC0,0x8A,
+0x94,0xC7,0x06,0xC8,0x90,0x97,0xC8,0xB8,0x64,0x1A,0x7A,0x7E,0x6C,0x3C,0x53,0xE1,
+0x37,0x28,0x73,0x60,0x7F,0xB2,0x97,0x53,0x07,0x9F,0x53,0xF9,0x6D,0x58,0x94,0xD2,
+0xAF,0x8D,0x6D,0x88,0x67,0x80,0xE6,0xED,0xB2,0x95,0xCF,0x72,0x31,0xCA,0xA5,0x1C,
+0x72,0xBA,0x5C,0x02,0xE7,0x64,0x42,0xE7,0xF9,0xA9,0x2C,0xD6,0x3A,0x0D,0xAC,0x8D,
+0x42,0xAA,0x24,0x01,0x39,0xE6,0x9C,0x3F,0x01,0x85,0x57,0x0D,0x58,0x87,0x45,0xF8,
+0xD3,0x85,0xAA,0x93,0x69,0x26,0x85,0x70,0x48,0x80,0x3F,0x12,0x15,0xC7,0x79,0xB4,
+0x1F,0x05,0x2F,0x3B,0x62,0x99,0x02,0x03,0x01,0x00,0x01,0x30,0x0D,0x06,0x09,0x2A,
+0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,0x03,0x81,0x81,0x00,0x6D,0xEB,
+0x1B,0x09,0xE9,0x5E,0xD9,0x51,0xDB,0x67,0x22,0x61,0xA4,0x2A,0x3C,0x48,0x77,0xE3,
+0xA0,0x7C,0xA6,0xDE,0x73,0xA2,0x14,0x03,0x85,0x3D,0xFB,0xAB,0x0E,0x30,0xC5,0x83,
+0x16,0x33,0x81,0x13,0x08,0x9E,0x7B,0x34,0x4E,0xDF,0x40,0xC8,0x74,0xD7,0xB9,0x7D,
+0xDC,0xF4,0x76,0x55,0x7D,0x9B,0x63,0x54,0x18,0xE9,0xF0,0xEA,0xF3,0x5C,0xB1,0xD9,
+0x8B,0x42,0x1E,0xB9,0xC0,0x95,0x4E,0xBA,0xFA,0xD5,0xE2,0x7C,0xF5,0x68,0x61,0xBF,
+0x8E,0xEC,0x05,0x97,0x5F,0x5B,0xB0,0xD7,0xA3,0x85,0x34,0xC4,0x24,0xA7,0x0D,0x0F,
+0x95,0x93,0xEF,0xCB,0x94,0xD8,0x9E,0x1F,0x9D,0x5C,0x85,0x6D,0xC7,0xAA,0xAE,0x4F,
+0x1F,0x22,0xB5,0xCD,0x95,0xAD,0xBA,0xA7,0xCC,0xF9,0xAB,0x0B,0x7A,0x7F,
+};
+
+
+/* subject:/C=US/O=Network Solutions L.L.C./CN=Network Solutions Certificate Authority */
+/* issuer :/C=US/O=Network Solutions L.L.C./CN=Network Solutions Certificate Authority */
+
+
+const unsigned char Network_Solutions_Certificate_Authority_certificate[1002]={
+0x30,0x82,0x03,0xE6,0x30,0x82,0x02,0xCE,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x57,
+0xCB,0x33,0x6F,0xC2,0x5C,0x16,0xE6,0x47,0x16,0x17,0xE3,0x90,0x31,0x68,0xE0,0x30,
+0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x62,
+0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x21,0x30,
+0x1F,0x06,0x03,0x55,0x04,0x0A,0x13,0x18,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x20,
+0x53,0x6F,0x6C,0x75,0x74,0x69,0x6F,0x6E,0x73,0x20,0x4C,0x2E,0x4C,0x2E,0x43,0x2E,
+0x31,0x30,0x30,0x2E,0x06,0x03,0x55,0x04,0x03,0x13,0x27,0x4E,0x65,0x74,0x77,0x6F,
+0x72,0x6B,0x20,0x53,0x6F,0x6C,0x75,0x74,0x69,0x6F,0x6E,0x73,0x20,0x43,0x65,0x72,
+0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,
+0x74,0x79,0x30,0x1E,0x17,0x0D,0x30,0x36,0x31,0x32,0x30,0x31,0x30,0x30,0x30,0x30,
+0x30,0x30,0x5A,0x17,0x0D,0x32,0x39,0x31,0x32,0x33,0x31,0x32,0x33,0x35,0x39,0x35,
+0x39,0x5A,0x30,0x62,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,
+0x53,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x0A,0x13,0x18,0x4E,0x65,0x74,0x77,
+0x6F,0x72,0x6B,0x20,0x53,0x6F,0x6C,0x75,0x74,0x69,0x6F,0x6E,0x73,0x20,0x4C,0x2E,
+0x4C,0x2E,0x43,0x2E,0x31,0x30,0x30,0x2E,0x06,0x03,0x55,0x04,0x03,0x13,0x27,0x4E,
+0x65,0x74,0x77,0x6F,0x72,0x6B,0x20,0x53,0x6F,0x6C,0x75,0x74,0x69,0x6F,0x6E,0x73,
+0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74,
+0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,
+0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,
+0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xE4,0xBC,0x7E,0x92,0x30,0x6D,0xC6,0xD8,0x8E,
+0x2B,0x0B,0xBC,0x46,0xCE,0xE0,0x27,0x96,0xDE,0xDE,0xF9,0xFA,0x12,0xD3,0x3C,0x33,
+0x73,0xB3,0x04,0x2F,0xBC,0x71,0x8C,0xE5,0x9F,0xB6,0x22,0x60,0x3E,0x5F,0x5D,0xCE,
+0x09,0xFF,0x82,0x0C,0x1B,0x9A,0x51,0x50,0x1A,0x26,0x89,0xDD,0xD5,0x61,0x5D,0x19,
+0xDC,0x12,0x0F,0x2D,0x0A,0xA2,0x43,0x5D,0x17,0xD0,0x34,0x92,0x20,0xEA,0x73,0xCF,
+0x38,0x2C,0x06,0x26,0x09,0x7A,0x72,0xF7,0xFA,0x50,0x32,0xF8,0xC2,0x93,0xD3,0x69,
+0xA2,0x23,0xCE,0x41,0xB1,0xCC,0xE4,0xD5,0x1F,0x36,0xD1,0x8A,0x3A,0xF8,0x8C,0x63,
+0xE2,0x14,0x59,0x69,0xED,0x0D,0xD3,0x7F,0x6B,0xE8,0xB8,0x03,0xE5,0x4F,0x6A,0xE5,
+0x98,0x63,0x69,0x48,0x05,0xBE,0x2E,0xFF,0x33,0xB6,0xE9,0x97,0x59,0x69,0xF8,0x67,
+0x19,0xAE,0x93,0x61,0x96,0x44,0x15,0xD3,0x72,0xB0,0x3F,0xBC,0x6A,0x7D,0xEC,0x48,
+0x7F,0x8D,0xC3,0xAB,0xAA,0x71,0x2B,0x53,0x69,0x41,0x53,0x34,0xB5,0xB0,0xB9,0xC5,
+0x06,0x0A,0xC4,0xB0,0x45,0xF5,0x41,0x5D,0x6E,0x89,0x45,0x7B,0x3D,0x3B,0x26,0x8C,
+0x74,0xC2,0xE5,0xD2,0xD1,0x7D,0xB2,0x11,0xD4,0xFB,0x58,0x32,0x22,0x9A,0x80,0xC9,
+0xDC,0xFD,0x0C,0xE9,0x7F,0x5E,0x03,0x97,0xCE,0x3B,0x00,0x14,0x87,0x27,0x70,0x38,
+0xA9,0x8E,0x6E,0xB3,0x27,0x76,0x98,0x51,0xE0,0x05,0xE3,0x21,0xAB,0x1A,0xD5,0x85,
+0x22,0x3C,0x29,0xB5,0x9A,0x16,0xC5,0x80,0xA8,0xF4,0xBB,0x6B,0x30,0x8F,0x2F,0x46,
+0x02,0xA2,0xB1,0x0C,0x22,0xE0,0xD3,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0x97,0x30,
+0x81,0x94,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x21,0x30,0xC9,
+0xFB,0x00,0xD7,0x4E,0x98,0xDA,0x87,0xAA,0x2A,0xD0,0xA7,0x2E,0xB1,0x40,0x31,0xA7,
+0x4C,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,
+0x06,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,
+0x01,0xFF,0x30,0x52,0x06,0x03,0x55,0x1D,0x1F,0x04,0x4B,0x30,0x49,0x30,0x47,0xA0,
+0x45,0xA0,0x43,0x86,0x41,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E,
+0x6E,0x65,0x74,0x73,0x6F,0x6C,0x73,0x73,0x6C,0x2E,0x63,0x6F,0x6D,0x2F,0x4E,0x65,
+0x74,0x77,0x6F,0x72,0x6B,0x53,0x6F,0x6C,0x75,0x74,0x69,0x6F,0x6E,0x73,0x43,0x65,
+0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,
+0x74,0x79,0x2E,0x63,0x72,0x6C,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,
+0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0xBB,0xAE,0x4B,0xE7,0xB7,0x57,
+0xEB,0x7F,0xAA,0x2D,0xB7,0x73,0x47,0x85,0x6A,0xC1,0xE4,0xA5,0x1D,0xE4,0xE7,0x3C,
+0xE9,0xF4,0x59,0x65,0x77,0xB5,0x7A,0x5B,0x5A,0x8D,0x25,0x36,0xE0,0x7A,0x97,0x2E,
+0x38,0xC0,0x57,0x60,0x83,0x98,0x06,0x83,0x9F,0xB9,0x76,0x7A,0x6E,0x50,0xE0,0xBA,
+0x88,0x2C,0xFC,0x45,0xCC,0x18,0xB0,0x99,0x95,0x51,0x0E,0xEC,0x1D,0xB8,0x88,0xFF,
+0x87,0x50,0x1C,0x82,0xC2,0xE3,0xE0,0x32,0x80,0xBF,0xA0,0x0B,0x47,0xC8,0xC3,0x31,
+0xEF,0x99,0x67,0x32,0x80,0x4F,0x17,0x21,0x79,0x0C,0x69,0x5C,0xDE,0x5E,0x34,0xAE,
+0x02,0xB5,0x26,0xEA,0x50,0xDF,0x7F,0x18,0x65,0x2C,0xC9,0xF2,0x63,0xE1,0xA9,0x07,
+0xFE,0x7C,0x71,0x1F,0x6B,0x33,0x24,0x6A,0x1E,0x05,0xF7,0x05,0x68,0xC0,0x6A,0x12,
+0xCB,0x2E,0x5E,0x61,0xCB,0xAE,0x28,0xD3,0x7E,0xC2,0xB4,0x66,0x91,0x26,0x5F,0x3C,
+0x2E,0x24,0x5F,0xCB,0x58,0x0F,0xEB,0x28,0xEC,0xAF,0x11,0x96,0xF3,0xDC,0x7B,0x6F,
+0xC0,0xA7,0x88,0xF2,0x53,0x77,0xB3,0x60,0x5E,0xAE,0xAE,0x28,0xDA,0x35,0x2C,0x6F,
+0x34,0x45,0xD3,0x26,0xE1,0xDE,0xEC,0x5B,0x4F,0x27,0x6B,0x16,0x7C,0xBD,0x44,0x04,
+0x18,0x82,0xB3,0x89,0x79,0x17,0x10,0x71,0x3D,0x7A,0xA2,0x16,0x4E,0xF5,0x01,0xCD,
+0xA4,0x6C,0x65,0x68,0xA1,0x49,0x76,0x5C,0x43,0xC9,0xD8,0xBC,0x36,0x67,0x6C,0xA5,
+0x94,0xB5,0xD4,0xCC,0xB9,0xBD,0x6A,0x35,0x56,0x21,0xDE,0xD8,0xC3,0xEB,0xFB,0xCB,
+0xA4,0x60,0x4C,0xB0,0x55,0xA0,0xA0,0x7B,0x57,0xB2,
+};
+
+
+/* subject:/L=ValiCert Validation Network/O=ValiCert, Inc./OU=ValiCert Class 3 Policy Validation Authority/CN=http://www.valicert.com//emailAddress=info@valicert.com */
+/* issuer :/L=ValiCert Validation Network/O=ValiCert, Inc./OU=ValiCert Class 3 Policy Validation Authority/CN=http://www.valicert.com//emailAddress=info@valicert.com */
+
+
+const unsigned char RSA_Root_Certificate_1_certificate[747]={
+0x30,0x82,0x02,0xE7,0x30,0x82,0x02,0x50,0x02,0x01,0x01,0x30,0x0D,0x06,0x09,0x2A,
+0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81,0xBB,0x31,0x24,0x30,
+0x22,0x06,0x03,0x55,0x04,0x07,0x13,0x1B,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,
+0x20,0x56,0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x4E,0x65,0x74,0x77,
+0x6F,0x72,0x6B,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x61,
+0x6C,0x69,0x43,0x65,0x72,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x35,0x30,0x33,
+0x06,0x03,0x55,0x04,0x0B,0x13,0x2C,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20,
+0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x50,0x6F,0x6C,0x69,0x63,0x79,0x20,0x56,
+0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,
+0x69,0x74,0x79,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x68,0x74,
+0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x76,0x61,0x6C,0x69,0x63,0x65,0x72,
+0x74,0x2E,0x63,0x6F,0x6D,0x2F,0x31,0x20,0x30,0x1E,0x06,0x09,0x2A,0x86,0x48,0x86,
+0xF7,0x0D,0x01,0x09,0x01,0x16,0x11,0x69,0x6E,0x66,0x6F,0x40,0x76,0x61,0x6C,0x69,
+0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x30,0x1E,0x17,0x0D,0x39,0x39,0x30,0x36,
+0x32,0x36,0x30,0x30,0x32,0x32,0x33,0x33,0x5A,0x17,0x0D,0x31,0x39,0x30,0x36,0x32,
+0x36,0x30,0x30,0x32,0x32,0x33,0x33,0x5A,0x30,0x81,0xBB,0x31,0x24,0x30,0x22,0x06,
+0x03,0x55,0x04,0x07,0x13,0x1B,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20,0x56,
+0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,
+0x6B,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x61,0x6C,0x69,
+0x43,0x65,0x72,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x35,0x30,0x33,0x06,0x03,
+0x55,0x04,0x0B,0x13,0x2C,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20,0x43,0x6C,
+0x61,0x73,0x73,0x20,0x33,0x20,0x50,0x6F,0x6C,0x69,0x63,0x79,0x20,0x56,0x61,0x6C,
+0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,
+0x79,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x68,0x74,0x74,0x70,
+0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x76,0x61,0x6C,0x69,0x63,0x65,0x72,0x74,0x2E,
+0x63,0x6F,0x6D,0x2F,0x31,0x20,0x30,0x1E,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,
+0x01,0x09,0x01,0x16,0x11,0x69,0x6E,0x66,0x6F,0x40,0x76,0x61,0x6C,0x69,0x63,0x65,
+0x72,0x74,0x2E,0x63,0x6F,0x6D,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,
+0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89,0x02,
+0x81,0x81,0x00,0xE3,0x98,0x51,0x96,0x1C,0xE8,0xD5,0xB1,0x06,0x81,0x6A,0x57,0xC3,
+0x72,0x75,0x93,0xAB,0xCF,0x9E,0xA6,0xFC,0xF3,0x16,0x52,0xD6,0x2D,0x4D,0x9F,0x35,
+0x44,0xA8,0x2E,0x04,0x4D,0x07,0x49,0x8A,0x38,0x29,0xF5,0x77,0x37,0xE7,0xB7,0xAB,
+0x5D,0xDF,0x36,0x71,0x14,0x99,0x8F,0xDC,0xC2,0x92,0xF1,0xE7,0x60,0x92,0x97,0xEC,
+0xD8,0x48,0xDC,0xBF,0xC1,0x02,0x20,0xC6,0x24,0xA4,0x28,0x4C,0x30,0x5A,0x76,0x6D,
+0xB1,0x5C,0xF3,0xDD,0xDE,0x9E,0x10,0x71,0xA1,0x88,0xC7,0x5B,0x9B,0x41,0x6D,0xCA,
+0xB0,0xB8,0x8E,0x15,0xEE,0xAD,0x33,0x2B,0xCF,0x47,0x04,0x5C,0x75,0x71,0x0A,0x98,
+0x24,0x98,0x29,0xA7,0x49,0x59,0xA5,0xDD,0xF8,0xB7,0x43,0x62,0x61,0xF3,0xD3,0xE2,
+0xD0,0x55,0x3F,0x02,0x03,0x01,0x00,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,
+0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x81,0x81,0x00,0x56,0xBB,0x02,0x58,0x84,
+0x67,0x08,0x2C,0xDF,0x1F,0xDB,0x7B,0x49,0x33,0xF5,0xD3,0x67,0x9D,0xF4,0xB4,0x0A,
+0x10,0xB3,0xC9,0xC5,0x2C,0xE2,0x92,0x6A,0x71,0x78,0x27,0xF2,0x70,0x83,0x42,0xD3,
+0x3E,0xCF,0xA9,0x54,0xF4,0xF1,0xD8,0x92,0x16,0x8C,0xD1,0x04,0xCB,0x4B,0xAB,0xC9,
+0x9F,0x45,0xAE,0x3C,0x8A,0xA9,0xB0,0x71,0x33,0x5D,0xC8,0xC5,0x57,0xDF,0xAF,0xA8,
+0x35,0xB3,0x7F,0x89,0x87,0xE9,0xE8,0x25,0x92,0xB8,0x7F,0x85,0x7A,0xAE,0xD6,0xBC,
+0x1E,0x37,0x58,0x2A,0x67,0xC9,0x91,0xCF,0x2A,0x81,0x3E,0xED,0xC6,0x39,0xDF,0xC0,
+0x3E,0x19,0x9C,0x19,0xCC,0x13,0x4D,0x82,0x41,0xB5,0x8C,0xDE,0xE0,0x3D,0x60,0x08,
+0x20,0x0F,0x45,0x7E,0x6B,0xA2,0x7F,0xA3,0x8C,0x15,0xEE,
+};
+
+
+/* subject:/C=US/O=Starfield Technologies, Inc./OU=Starfield Class 2 Certification Authority */
+/* issuer :/C=US/O=Starfield Technologies, Inc./OU=Starfield Class 2 Certification Authority */
+
+
+const unsigned char Starfield_Class_2_CA_certificate[1043]={
+0x30,0x82,0x04,0x0F,0x30,0x82,0x02,0xF7,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x00,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,
+0x68,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x25,
+0x30,0x23,0x06,0x03,0x55,0x04,0x0A,0x13,0x1C,0x53,0x74,0x61,0x72,0x66,0x69,0x65,
+0x6C,0x64,0x20,0x54,0x65,0x63,0x68,0x6E,0x6F,0x6C,0x6F,0x67,0x69,0x65,0x73,0x2C,
+0x20,0x49,0x6E,0x63,0x2E,0x31,0x32,0x30,0x30,0x06,0x03,0x55,0x04,0x0B,0x13,0x29,
+0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C,0x64,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,
+0x32,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,
+0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x1E,0x17,0x0D,0x30,0x34,0x30,
+0x36,0x32,0x39,0x31,0x37,0x33,0x39,0x31,0x36,0x5A,0x17,0x0D,0x33,0x34,0x30,0x36,
+0x32,0x39,0x31,0x37,0x33,0x39,0x31,0x36,0x5A,0x30,0x68,0x31,0x0B,0x30,0x09,0x06,
+0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,
+0x0A,0x13,0x1C,0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C,0x64,0x20,0x54,0x65,0x63,
+0x68,0x6E,0x6F,0x6C,0x6F,0x67,0x69,0x65,0x73,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,
+0x32,0x30,0x30,0x06,0x03,0x55,0x04,0x0B,0x13,0x29,0x53,0x74,0x61,0x72,0x66,0x69,
+0x65,0x6C,0x64,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x32,0x20,0x43,0x65,0x72,0x74,
+0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,
+0x69,0x74,0x79,0x30,0x82,0x01,0x20,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,
+0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0D,0x00,0x30,0x82,0x01,0x08,0x02,
+0x82,0x01,0x01,0x00,0xB7,0x32,0xC8,0xFE,0xE9,0x71,0xA6,0x04,0x85,0xAD,0x0C,0x11,
+0x64,0xDF,0xCE,0x4D,0xEF,0xC8,0x03,0x18,0x87,0x3F,0xA1,0xAB,0xFB,0x3C,0xA6,0x9F,
+0xF0,0xC3,0xA1,0xDA,0xD4,0xD8,0x6E,0x2B,0x53,0x90,0xFB,0x24,0xA4,0x3E,0x84,0xF0,
+0x9E,0xE8,0x5F,0xEC,0xE5,0x27,0x44,0xF5,0x28,0xA6,0x3F,0x7B,0xDE,0xE0,0x2A,0xF0,
+0xC8,0xAF,0x53,0x2F,0x9E,0xCA,0x05,0x01,0x93,0x1E,0x8F,0x66,0x1C,0x39,0xA7,0x4D,
+0xFA,0x5A,0xB6,0x73,0x04,0x25,0x66,0xEB,0x77,0x7F,0xE7,0x59,0xC6,0x4A,0x99,0x25,
+0x14,0x54,0xEB,0x26,0xC7,0xF3,0x7F,0x19,0xD5,0x30,0x70,0x8F,0xAF,0xB0,0x46,0x2A,
+0xFF,0xAD,0xEB,0x29,0xED,0xD7,0x9F,0xAA,0x04,0x87,0xA3,0xD4,0xF9,0x89,0xA5,0x34,
+0x5F,0xDB,0x43,0x91,0x82,0x36,0xD9,0x66,0x3C,0xB1,0xB8,0xB9,0x82,0xFD,0x9C,0x3A,
+0x3E,0x10,0xC8,0x3B,0xEF,0x06,0x65,0x66,0x7A,0x9B,0x19,0x18,0x3D,0xFF,0x71,0x51,
+0x3C,0x30,0x2E,0x5F,0xBE,0x3D,0x77,0x73,0xB2,0x5D,0x06,0x6C,0xC3,0x23,0x56,0x9A,
+0x2B,0x85,0x26,0x92,0x1C,0xA7,0x02,0xB3,0xE4,0x3F,0x0D,0xAF,0x08,0x79,0x82,0xB8,
+0x36,0x3D,0xEA,0x9C,0xD3,0x35,0xB3,0xBC,0x69,0xCA,0xF5,0xCC,0x9D,0xE8,0xFD,0x64,
+0x8D,0x17,0x80,0x33,0x6E,0x5E,0x4A,0x5D,0x99,0xC9,0x1E,0x87,0xB4,0x9D,0x1A,0xC0,
+0xD5,0x6E,0x13,0x35,0x23,0x5E,0xDF,0x9B,0x5F,0x3D,0xEF,0xD6,0xF7,0x76,0xC2,0xEA,
+0x3E,0xBB,0x78,0x0D,0x1C,0x42,0x67,0x6B,0x04,0xD8,0xF8,0xD6,0xDA,0x6F,0x8B,0xF2,
+0x44,0xA0,0x01,0xAB,0x02,0x01,0x03,0xA3,0x81,0xC5,0x30,0x81,0xC2,0x30,0x1D,0x06,
+0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xBF,0x5F,0xB7,0xD1,0xCE,0xDD,0x1F,0x86,
+0xF4,0x5B,0x55,0xAC,0xDC,0xD7,0x10,0xC2,0x0E,0xA9,0x88,0xE7,0x30,0x81,0x92,0x06,
+0x03,0x55,0x1D,0x23,0x04,0x81,0x8A,0x30,0x81,0x87,0x80,0x14,0xBF,0x5F,0xB7,0xD1,
+0xCE,0xDD,0x1F,0x86,0xF4,0x5B,0x55,0xAC,0xDC,0xD7,0x10,0xC2,0x0E,0xA9,0x88,0xE7,
+0xA1,0x6C,0xA4,0x6A,0x30,0x68,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,
+0x02,0x55,0x53,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x0A,0x13,0x1C,0x53,0x74,
+0x61,0x72,0x66,0x69,0x65,0x6C,0x64,0x20,0x54,0x65,0x63,0x68,0x6E,0x6F,0x6C,0x6F,
+0x67,0x69,0x65,0x73,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x32,0x30,0x30,0x06,0x03,
+0x55,0x04,0x0B,0x13,0x29,0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C,0x64,0x20,0x43,
+0x6C,0x61,0x73,0x73,0x20,0x32,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,
+0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x82,0x01,
+0x00,0x30,0x0C,0x06,0x03,0x55,0x1D,0x13,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,
+0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,
+0x01,0x01,0x00,0x05,0x9D,0x3F,0x88,0x9D,0xD1,0xC9,0x1A,0x55,0xA1,0xAC,0x69,0xF3,
+0xF3,0x59,0xDA,0x9B,0x01,0x87,0x1A,0x4F,0x57,0xA9,0xA1,0x79,0x09,0x2A,0xDB,0xF7,
+0x2F,0xB2,0x1E,0xCC,0xC7,0x5E,0x6A,0xD8,0x83,0x87,0xA1,0x97,0xEF,0x49,0x35,0x3E,
+0x77,0x06,0x41,0x58,0x62,0xBF,0x8E,0x58,0xB8,0x0A,0x67,0x3F,0xEC,0xB3,0xDD,0x21,
+0x66,0x1F,0xC9,0x54,0xFA,0x72,0xCC,0x3D,0x4C,0x40,0xD8,0x81,0xAF,0x77,0x9E,0x83,
+0x7A,0xBB,0xA2,0xC7,0xF5,0x34,0x17,0x8E,0xD9,0x11,0x40,0xF4,0xFC,0x2C,0x2A,0x4D,
+0x15,0x7F,0xA7,0x62,0x5D,0x2E,0x25,0xD3,0x00,0x0B,0x20,0x1A,0x1D,0x68,0xF9,0x17,
+0xB8,0xF4,0xBD,0x8B,0xED,0x28,0x59,0xDD,0x4D,0x16,0x8B,0x17,0x83,0xC8,0xB2,0x65,
+0xC7,0x2D,0x7A,0xA5,0xAA,0xBC,0x53,0x86,0x6D,0xDD,0x57,0xA4,0xCA,0xF8,0x20,0x41,
+0x0B,0x68,0xF0,0xF4,0xFB,0x74,0xBE,0x56,0x5D,0x7A,0x79,0xF5,0xF9,0x1D,0x85,0xE3,
+0x2D,0x95,0xBE,0xF5,0x71,0x90,0x43,0xCC,0x8D,0x1F,0x9A,0x00,0x0A,0x87,0x29,0xE9,
+0x55,0x22,0x58,0x00,0x23,0xEA,0xE3,0x12,0x43,0x29,0x5B,0x47,0x08,0xDD,0x8C,0x41,
+0x6A,0x65,0x06,0xA8,0xE5,0x21,0xAA,0x41,0xB4,0x95,0x21,0x95,0xB9,0x7D,0xD1,0x34,
+0xAB,0x13,0xD6,0xAD,0xBC,0xDC,0xE2,0x3D,0x39,0xCD,0xBD,0x3E,0x75,0x70,0xA1,0x18,
+0x59,0x03,0xC9,0x22,0xB4,0x8F,0x9C,0xD5,0x5E,0x2A,0xD7,0xA5,0xB6,0xD4,0x0A,0x6D,
+0xF8,0xB7,0x40,0x11,0x46,0x9A,0x1F,0x79,0x0E,0x62,0xBF,0x0F,0x97,0xEC,0xE0,0x2F,
+0x1F,0x17,0x94,
+};
+
+
+/* subject:/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Root Certificate Authority - G2 */
+/* issuer :/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Root Certificate Authority - G2 */
+
+
+const unsigned char Starfield_Root_Certificate_Authority___G2_certificate[993]={
+0x30,0x82,0x03,0xDD,0x30,0x82,0x02,0xC5,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x00,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30,
+0x81,0x8F,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,
+0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x08,0x13,0x07,0x41,0x72,0x69,0x7A,0x6F,0x6E,
+0x61,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x07,0x13,0x0A,0x53,0x63,0x6F,0x74,
+0x74,0x73,0x64,0x61,0x6C,0x65,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x0A,0x13,
+0x1C,0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C,0x64,0x20,0x54,0x65,0x63,0x68,0x6E,
+0x6F,0x6C,0x6F,0x67,0x69,0x65,0x73,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x32,0x30,
+0x30,0x06,0x03,0x55,0x04,0x03,0x13,0x29,0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C,
+0x64,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,
+0x74,0x65,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x2D,0x20,0x47,
+0x32,0x30,0x1E,0x17,0x0D,0x30,0x39,0x30,0x39,0x30,0x31,0x30,0x30,0x30,0x30,0x30,
+0x30,0x5A,0x17,0x0D,0x33,0x37,0x31,0x32,0x33,0x31,0x32,0x33,0x35,0x39,0x35,0x39,
+0x5A,0x30,0x81,0x8F,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,
+0x53,0x31,0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x08,0x13,0x07,0x41,0x72,0x69,0x7A,
+0x6F,0x6E,0x61,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x07,0x13,0x0A,0x53,0x63,
+0x6F,0x74,0x74,0x73,0x64,0x61,0x6C,0x65,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,
+0x0A,0x13,0x1C,0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C,0x64,0x20,0x54,0x65,0x63,
+0x68,0x6E,0x6F,0x6C,0x6F,0x67,0x69,0x65,0x73,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,
+0x32,0x30,0x30,0x06,0x03,0x55,0x04,0x03,0x13,0x29,0x53,0x74,0x61,0x72,0x66,0x69,
+0x65,0x6C,0x64,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,
+0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x2D,
+0x20,0x47,0x32,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,
+0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,
+0x82,0x01,0x01,0x00,0xBD,0xED,0xC1,0x03,0xFC,0xF6,0x8F,0xFC,0x02,0xB1,0x6F,0x5B,
+0x9F,0x48,0xD9,0x9D,0x79,0xE2,0xA2,0xB7,0x03,0x61,0x56,0x18,0xC3,0x47,0xB6,0xD7,
+0xCA,0x3D,0x35,0x2E,0x89,0x43,0xF7,0xA1,0x69,0x9B,0xDE,0x8A,0x1A,0xFD,0x13,0x20,
+0x9C,0xB4,0x49,0x77,0x32,0x29,0x56,0xFD,0xB9,0xEC,0x8C,0xDD,0x22,0xFA,0x72,0xDC,
+0x27,0x61,0x97,0xEE,0xF6,0x5A,0x84,0xEC,0x6E,0x19,0xB9,0x89,0x2C,0xDC,0x84,0x5B,
+0xD5,0x74,0xFB,0x6B,0x5F,0xC5,0x89,0xA5,0x10,0x52,0x89,0x46,0x55,0xF4,0xB8,0x75,
+0x1C,0xE6,0x7F,0xE4,0x54,0xAE,0x4B,0xF8,0x55,0x72,0x57,0x02,0x19,0xF8,0x17,0x71,
+0x59,0xEB,0x1E,0x28,0x07,0x74,0xC5,0x9D,0x48,0xBE,0x6C,0xB4,0xF4,0xA4,0xB0,0xF3,
+0x64,0x37,0x79,0x92,0xC0,0xEC,0x46,0x5E,0x7F,0xE1,0x6D,0x53,0x4C,0x62,0xAF,0xCD,
+0x1F,0x0B,0x63,0xBB,0x3A,0x9D,0xFB,0xFC,0x79,0x00,0x98,0x61,0x74,0xCF,0x26,0x82,
+0x40,0x63,0xF3,0xB2,0x72,0x6A,0x19,0x0D,0x99,0xCA,0xD4,0x0E,0x75,0xCC,0x37,0xFB,
+0x8B,0x89,0xC1,0x59,0xF1,0x62,0x7F,0x5F,0xB3,0x5F,0x65,0x30,0xF8,0xA7,0xB7,0x4D,
+0x76,0x5A,0x1E,0x76,0x5E,0x34,0xC0,0xE8,0x96,0x56,0x99,0x8A,0xB3,0xF0,0x7F,0xA4,
+0xCD,0xBD,0xDC,0x32,0x31,0x7C,0x91,0xCF,0xE0,0x5F,0x11,0xF8,0x6B,0xAA,0x49,0x5C,
+0xD1,0x99,0x94,0xD1,0xA2,0xE3,0x63,0x5B,0x09,0x76,0xB5,0x56,0x62,0xE1,0x4B,0x74,
+0x1D,0x96,0xD4,0x26,0xD4,0x08,0x04,0x59,0xD0,0x98,0x0E,0x0E,0xE6,0xDE,0xFC,0xC3,
+0xEC,0x1F,0x90,0xF1,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40,0x30,0x0F,0x06,
+0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,
+0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D,
+0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x7C,0x0C,0x32,0x1F,0xA7,0xD9,0x30,
+0x7F,0xC4,0x7D,0x68,0xA3,0x62,0xA8,0xA1,0xCE,0xAB,0x07,0x5B,0x27,0x30,0x0D,0x06,
+0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82,0x01,0x01,
+0x00,0x11,0x59,0xFA,0x25,0x4F,0x03,0x6F,0x94,0x99,0x3B,0x9A,0x1F,0x82,0x85,0x39,
+0xD4,0x76,0x05,0x94,0x5E,0xE1,0x28,0x93,0x6D,0x62,0x5D,0x09,0xC2,0xA0,0xA8,0xD4,
+0xB0,0x75,0x38,0xF1,0x34,0x6A,0x9D,0xE4,0x9F,0x8A,0x86,0x26,0x51,0xE6,0x2C,0xD1,
+0xC6,0x2D,0x6E,0x95,0x20,0x4A,0x92,0x01,0xEC,0xB8,0x8A,0x67,0x7B,0x31,0xE2,0x67,
+0x2E,0x8C,0x95,0x03,0x26,0x2E,0x43,0x9D,0x4A,0x31,0xF6,0x0E,0xB5,0x0C,0xBB,0xB7,
+0xE2,0x37,0x7F,0x22,0xBA,0x00,0xA3,0x0E,0x7B,0x52,0xFB,0x6B,0xBB,0x3B,0xC4,0xD3,
+0x79,0x51,0x4E,0xCD,0x90,0xF4,0x67,0x07,0x19,0xC8,0x3C,0x46,0x7A,0x0D,0x01,0x7D,
+0xC5,0x58,0xE7,0x6D,0xE6,0x85,0x30,0x17,0x9A,0x24,0xC4,0x10,0xE0,0x04,0xF7,0xE0,
+0xF2,0x7F,0xD4,0xAA,0x0A,0xFF,0x42,0x1D,0x37,0xED,0x94,0xE5,0x64,0x59,0x12,0x20,
+0x77,0x38,0xD3,0x32,0x3E,0x38,0x81,0x75,0x96,0x73,0xFA,0x68,0x8F,0xB1,0xCB,0xCE,
+0x1F,0xC5,0xEC,0xFA,0x9C,0x7E,0xCF,0x7E,0xB1,0xF1,0x07,0x2D,0xB6,0xFC,0xBF,0xCA,
+0xA4,0xBF,0xD0,0x97,0x05,0x4A,0xBC,0xEA,0x18,0x28,0x02,0x90,0xBD,0x54,0x78,0x09,
+0x21,0x71,0xD3,0xD1,0x7D,0x1D,0xD9,0x16,0xB0,0xA9,0x61,0x3D,0xD0,0x0A,0x00,0x22,
+0xFC,0xC7,0x7B,0xCB,0x09,0x64,0x45,0x0B,0x3B,0x40,0x81,0xF7,0x7D,0x7C,0x32,0xF5,
+0x98,0xCA,0x58,0x8E,0x7D,0x2A,0xEE,0x90,0x59,0x73,0x64,0xF9,0x36,0x74,0x5E,0x25,
+0xA1,0xF5,0x66,0x05,0x2E,0x7F,0x39,0x15,0xA9,0x2A,0xFB,0x50,0x8B,0x8E,0x85,0x69,
+0xF4,
+};
+
+
+/* subject:/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Services Root Certificate Authority - G2 */
+/* issuer :/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Services Root Certificate Authority - G2 */
+
+
+const unsigned char Starfield_Services_Root_Certificate_Authority___G2_certificate[1011]={
+0x30,0x82,0x03,0xEF,0x30,0x82,0x02,0xD7,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x00,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30,
+0x81,0x98,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,
+0x10,0x30,0x0E,0x06,0x03,0x55,0x04,0x08,0x13,0x07,0x41,0x72,0x69,0x7A,0x6F,0x6E,
+0x61,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x07,0x13,0x0A,0x53,0x63,0x6F,0x74,
+0x74,0x73,0x64,0x61,0x6C,0x65,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x0A,0x13,
+0x1C,0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C,0x64,0x20,0x54,0x65,0x63,0x68,0x6E,
+0x6F,0x6C,0x6F,0x67,0x69,0x65,0x73,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x3B,0x30,
+0x39,0x06,0x03,0x55,0x04,0x03,0x13,0x32,0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C,
+0x64,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x20,0x52,0x6F,0x6F,0x74,0x20,
+0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74,0x68,
+0x6F,0x72,0x69,0x74,0x79,0x20,0x2D,0x20,0x47,0x32,0x30,0x1E,0x17,0x0D,0x30,0x39,
+0x30,0x39,0x30,0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x37,0x31,
+0x32,0x33,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0x98,0x31,0x0B,0x30,
+0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x10,0x30,0x0E,0x06,0x03,
+0x55,0x04,0x08,0x13,0x07,0x41,0x72,0x69,0x7A,0x6F,0x6E,0x61,0x31,0x13,0x30,0x11,
+0x06,0x03,0x55,0x04,0x07,0x13,0x0A,0x53,0x63,0x6F,0x74,0x74,0x73,0x64,0x61,0x6C,
+0x65,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x0A,0x13,0x1C,0x53,0x74,0x61,0x72,
+0x66,0x69,0x65,0x6C,0x64,0x20,0x54,0x65,0x63,0x68,0x6E,0x6F,0x6C,0x6F,0x67,0x69,
+0x65,0x73,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x3B,0x30,0x39,0x06,0x03,0x55,0x04,
+0x03,0x13,0x32,0x53,0x74,0x61,0x72,0x66,0x69,0x65,0x6C,0x64,0x20,0x53,0x65,0x72,
+0x76,0x69,0x63,0x65,0x73,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65,0x72,0x74,0x69,
+0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,
+0x20,0x2D,0x20,0x47,0x32,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,
+0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,
+0x0A,0x02,0x82,0x01,0x01,0x00,0xD5,0x0C,0x3A,0xC4,0x2A,0xF9,0x4E,0xE2,0xF5,0xBE,
+0x19,0x97,0x5F,0x8E,0x88,0x53,0xB1,0x1F,0x3F,0xCB,0xCF,0x9F,0x20,0x13,0x6D,0x29,
+0x3A,0xC8,0x0F,0x7D,0x3C,0xF7,0x6B,0x76,0x38,0x63,0xD9,0x36,0x60,0xA8,0x9B,0x5E,
+0x5C,0x00,0x80,0xB2,0x2F,0x59,0x7F,0xF6,0x87,0xF9,0x25,0x43,0x86,0xE7,0x69,0x1B,
+0x52,0x9A,0x90,0xE1,0x71,0xE3,0xD8,0x2D,0x0D,0x4E,0x6F,0xF6,0xC8,0x49,0xD9,0xB6,
+0xF3,0x1A,0x56,0xAE,0x2B,0xB6,0x74,0x14,0xEB,0xCF,0xFB,0x26,0xE3,0x1A,0xBA,0x1D,
+0x96,0x2E,0x6A,0x3B,0x58,0x94,0x89,0x47,0x56,0xFF,0x25,0xA0,0x93,0x70,0x53,0x83,
+0xDA,0x84,0x74,0x14,0xC3,0x67,0x9E,0x04,0x68,0x3A,0xDF,0x8E,0x40,0x5A,0x1D,0x4A,
+0x4E,0xCF,0x43,0x91,0x3B,0xE7,0x56,0xD6,0x00,0x70,0xCB,0x52,0xEE,0x7B,0x7D,0xAE,
+0x3A,0xE7,0xBC,0x31,0xF9,0x45,0xF6,0xC2,0x60,0xCF,0x13,0x59,0x02,0x2B,0x80,0xCC,
+0x34,0x47,0xDF,0xB9,0xDE,0x90,0x65,0x6D,0x02,0xCF,0x2C,0x91,0xA6,0xA6,0xE7,0xDE,
+0x85,0x18,0x49,0x7C,0x66,0x4E,0xA3,0x3A,0x6D,0xA9,0xB5,0xEE,0x34,0x2E,0xBA,0x0D,
+0x03,0xB8,0x33,0xDF,0x47,0xEB,0xB1,0x6B,0x8D,0x25,0xD9,0x9B,0xCE,0x81,0xD1,0x45,
+0x46,0x32,0x96,0x70,0x87,0xDE,0x02,0x0E,0x49,0x43,0x85,0xB6,0x6C,0x73,0xBB,0x64,
+0xEA,0x61,0x41,0xAC,0xC9,0xD4,0x54,0xDF,0x87,0x2F,0xC7,0x22,0xB2,0x26,0xCC,0x9F,
+0x59,0x54,0x68,0x9F,0xFC,0xBE,0x2A,0x2F,0xC4,0x55,0x1C,0x75,0x40,0x60,0x17,0x85,
+0x02,0x55,0x39,0x8B,0x7F,0x05,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40,0x30,
+0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,
+0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,
+0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x9C,0x5F,0x00,0xDF,0xAA,
+0x01,0xD7,0x30,0x2B,0x38,0x88,0xA2,0xB8,0x6D,0x4A,0x9C,0xF2,0x11,0x91,0x83,0x30,
+0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82,
+0x01,0x01,0x00,0x4B,0x36,0xA6,0x84,0x77,0x69,0xDD,0x3B,0x19,0x9F,0x67,0x23,0x08,
+0x6F,0x0E,0x61,0xC9,0xFD,0x84,0xDC,0x5F,0xD8,0x36,0x81,0xCD,0xD8,0x1B,0x41,0x2D,
+0x9F,0x60,0xDD,0xC7,0x1A,0x68,0xD9,0xD1,0x6E,0x86,0xE1,0x88,0x23,0xCF,0x13,0xDE,
+0x43,0xCF,0xE2,0x34,0xB3,0x04,0x9D,0x1F,0x29,0xD5,0xBF,0xF8,0x5E,0xC8,0xD5,0xC1,
+0xBD,0xEE,0x92,0x6F,0x32,0x74,0xF2,0x91,0x82,0x2F,0xBD,0x82,0x42,0x7A,0xAD,0x2A,
+0xB7,0x20,0x7D,0x4D,0xBC,0x7A,0x55,0x12,0xC2,0x15,0xEA,0xBD,0xF7,0x6A,0x95,0x2E,
+0x6C,0x74,0x9F,0xCF,0x1C,0xB4,0xF2,0xC5,0x01,0xA3,0x85,0xD0,0x72,0x3E,0xAD,0x73,
+0xAB,0x0B,0x9B,0x75,0x0C,0x6D,0x45,0xB7,0x8E,0x94,0xAC,0x96,0x37,0xB5,0xA0,0xD0,
+0x8F,0x15,0x47,0x0E,0xE3,0xE8,0x83,0xDD,0x8F,0xFD,0xEF,0x41,0x01,0x77,0xCC,0x27,
+0xA9,0x62,0x85,0x33,0xF2,0x37,0x08,0xEF,0x71,0xCF,0x77,0x06,0xDE,0xC8,0x19,0x1D,
+0x88,0x40,0xCF,0x7D,0x46,0x1D,0xFF,0x1E,0xC7,0xE1,0xCE,0xFF,0x23,0xDB,0xC6,0xFA,
+0x8D,0x55,0x4E,0xA9,0x02,0xE7,0x47,0x11,0x46,0x3E,0xF4,0xFD,0xBD,0x7B,0x29,0x26,
+0xBB,0xA9,0x61,0x62,0x37,0x28,0xB6,0x2D,0x2A,0xF6,0x10,0x86,0x64,0xC9,0x70,0xA7,
+0xD2,0xAD,0xB7,0x29,0x70,0x79,0xEA,0x3C,0xDA,0x63,0x25,0x9F,0xFD,0x68,0xB7,0x30,
+0xEC,0x70,0xFB,0x75,0x8A,0xB7,0x6D,0x60,0x67,0xB2,0x1E,0xC8,0xB9,0xE9,0xD8,0xA8,
+0x6F,0x02,0x8B,0x67,0x0D,0x4D,0x26,0x57,0x71,0xDA,0x20,0xFC,0xC1,0x4A,0x50,0x8D,
+0xB1,0x28,0xBA,
+};
+
+
+/* subject:/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority */
+/* issuer :/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority */
+
+
+const unsigned char StartCom_Certification_Authority_certificate[1931]={
+0x30,0x82,0x07,0x87,0x30,0x82,0x05,0x6F,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x2D,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30,
+0x7D,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x49,0x4C,0x31,0x16,
+0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x53,0x74,0x61,0x72,0x74,0x43,0x6F,
+0x6D,0x20,0x4C,0x74,0x64,0x2E,0x31,0x2B,0x30,0x29,0x06,0x03,0x55,0x04,0x0B,0x13,
+0x22,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x44,0x69,0x67,0x69,0x74,0x61,0x6C,0x20,
+0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x53,0x69,0x67,0x6E,
+0x69,0x6E,0x67,0x31,0x29,0x30,0x27,0x06,0x03,0x55,0x04,0x03,0x13,0x20,0x53,0x74,
+0x61,0x72,0x74,0x43,0x6F,0x6D,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,
+0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x1E,
+0x17,0x0D,0x30,0x36,0x30,0x39,0x31,0x37,0x31,0x39,0x34,0x36,0x33,0x37,0x5A,0x17,
+0x0D,0x33,0x36,0x30,0x39,0x31,0x37,0x31,0x39,0x34,0x36,0x33,0x36,0x5A,0x30,0x7D,
+0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x49,0x4C,0x31,0x16,0x30,
+0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x53,0x74,0x61,0x72,0x74,0x43,0x6F,0x6D,
+0x20,0x4C,0x74,0x64,0x2E,0x31,0x2B,0x30,0x29,0x06,0x03,0x55,0x04,0x0B,0x13,0x22,
+0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x44,0x69,0x67,0x69,0x74,0x61,0x6C,0x20,0x43,
+0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x65,0x20,0x53,0x69,0x67,0x6E,0x69,
+0x6E,0x67,0x31,0x29,0x30,0x27,0x06,0x03,0x55,0x04,0x03,0x13,0x20,0x53,0x74,0x61,
+0x72,0x74,0x43,0x6F,0x6D,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,
+0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x82,0x02,
+0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,
+0x03,0x82,0x02,0x0F,0x00,0x30,0x82,0x02,0x0A,0x02,0x82,0x02,0x01,0x00,0xC1,0x88,
+0xDB,0x09,0xBC,0x6C,0x46,0x7C,0x78,0x9F,0x95,0x7B,0xB5,0x33,0x90,0xF2,0x72,0x62,
+0xD6,0xC1,0x36,0x20,0x22,0x24,0x5E,0xCE,0xE9,0x77,0xF2,0x43,0x0A,0xA2,0x06,0x64,
+0xA4,0xCC,0x8E,0x36,0xF8,0x38,0xE6,0x23,0xF0,0x6E,0x6D,0xB1,0x3C,0xDD,0x72,0xA3,
+0x85,0x1C,0xA1,0xD3,0x3D,0xB4,0x33,0x2B,0xD3,0x2F,0xAF,0xFE,0xEA,0xB0,0x41,0x59,
+0x67,0xB6,0xC4,0x06,0x7D,0x0A,0x9E,0x74,0x85,0xD6,0x79,0x4C,0x80,0x37,0x7A,0xDF,
+0x39,0x05,0x52,0x59,0xF7,0xF4,0x1B,0x46,0x43,0xA4,0xD2,0x85,0x85,0xD2,0xC3,0x71,
+0xF3,0x75,0x62,0x34,0xBA,0x2C,0x8A,0x7F,0x1E,0x8F,0xEE,0xED,0x34,0xD0,0x11,0xC7,
+0x96,0xCD,0x52,0x3D,0xBA,0x33,0xD6,0xDD,0x4D,0xDE,0x0B,0x3B,0x4A,0x4B,0x9F,0xC2,
+0x26,0x2F,0xFA,0xB5,0x16,0x1C,0x72,0x35,0x77,0xCA,0x3C,0x5D,0xE6,0xCA,0xE1,0x26,
+0x8B,0x1A,0x36,0x76,0x5C,0x01,0xDB,0x74,0x14,0x25,0xFE,0xED,0xB5,0xA0,0x88,0x0F,
+0xDD,0x78,0xCA,0x2D,0x1F,0x07,0x97,0x30,0x01,0x2D,0x72,0x79,0xFA,0x46,0xD6,0x13,
+0x2A,0xA8,0xB9,0xA6,0xAB,0x83,0x49,0x1D,0xE5,0xF2,0xEF,0xDD,0xE4,0x01,0x8E,0x18,
+0x0A,0x8F,0x63,0x53,0x16,0x85,0x62,0xA9,0x0E,0x19,0x3A,0xCC,0xB5,0x66,0xA6,0xC2,
+0x6B,0x74,0x07,0xE4,0x2B,0xE1,0x76,0x3E,0xB4,0x6D,0xD8,0xF6,0x44,0xE1,0x73,0x62,
+0x1F,0x3B,0xC4,0xBE,0xA0,0x53,0x56,0x25,0x6C,0x51,0x09,0xF7,0xAA,0xAB,0xCA,0xBF,
+0x76,0xFD,0x6D,0x9B,0xF3,0x9D,0xDB,0xBF,0x3D,0x66,0xBC,0x0C,0x56,0xAA,0xAF,0x98,
+0x48,0x95,0x3A,0x4B,0xDF,0xA7,0x58,0x50,0xD9,0x38,0x75,0xA9,0x5B,0xEA,0x43,0x0C,
+0x02,0xFF,0x99,0xEB,0xE8,0x6C,0x4D,0x70,0x5B,0x29,0x65,0x9C,0xDD,0xAA,0x5D,0xCC,
+0xAF,0x01,0x31,0xEC,0x0C,0xEB,0xD2,0x8D,0xE8,0xEA,0x9C,0x7B,0xE6,0x6E,0xF7,0x27,
+0x66,0x0C,0x1A,0x48,0xD7,0x6E,0x42,0xE3,0x3F,0xDE,0x21,0x3E,0x7B,0xE1,0x0D,0x70,
+0xFB,0x63,0xAA,0xA8,0x6C,0x1A,0x54,0xB4,0x5C,0x25,0x7A,0xC9,0xA2,0xC9,0x8B,0x16,
+0xA6,0xBB,0x2C,0x7E,0x17,0x5E,0x05,0x4D,0x58,0x6E,0x12,0x1D,0x01,0xEE,0x12,0x10,
+0x0D,0xC6,0x32,0x7F,0x18,0xFF,0xFC,0xF4,0xFA,0xCD,0x6E,0x91,0xE8,0x36,0x49,0xBE,
+0x1A,0x48,0x69,0x8B,0xC2,0x96,0x4D,0x1A,0x12,0xB2,0x69,0x17,0xC1,0x0A,0x90,0xD6,
+0xFA,0x79,0x22,0x48,0xBF,0xBA,0x7B,0x69,0xF8,0x70,0xC7,0xFA,0x7A,0x37,0xD8,0xD8,
+0x0D,0xD2,0x76,0x4F,0x57,0xFF,0x90,0xB7,0xE3,0x91,0xD2,0xDD,0xEF,0xC2,0x60,0xB7,
+0x67,0x3A,0xDD,0xFE,0xAA,0x9C,0xF0,0xD4,0x8B,0x7F,0x72,0x22,0xCE,0xC6,0x9F,0x97,
+0xB6,0xF8,0xAF,0x8A,0xA0,0x10,0xA8,0xD9,0xFB,0x18,0xC6,0xB6,0xB5,0x5C,0x52,0x3C,
+0x89,0xB6,0x19,0x2A,0x73,0x01,0x0A,0x0F,0x03,0xB3,0x12,0x60,0xF2,0x7A,0x2F,0x81,
+0xDB,0xA3,0x6E,0xFF,0x26,0x30,0x97,0xF5,0x8B,0xDD,0x89,0x57,0xB6,0xAD,0x3D,0xB3,
+0xAF,0x2B,0xC5,0xB7,0x76,0x02,0xF0,0xA5,0xD6,0x2B,0x9A,0x86,0x14,0x2A,0x72,0xF6,
+0xE3,0x33,0x8C,0x5D,0x09,0x4B,0x13,0xDF,0xBB,0x8C,0x74,0x13,0x52,0x4B,0x02,0x03,
+0x01,0x00,0x01,0xA3,0x82,0x02,0x10,0x30,0x82,0x02,0x0C,0x30,0x0F,0x06,0x03,0x55,
+0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,
+0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D,0x06,0x03,
+0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x4E,0x0B,0xEF,0x1A,0xA4,0x40,0x5B,0xA5,0x17,
+0x69,0x87,0x30,0xCA,0x34,0x68,0x43,0xD0,0x41,0xAE,0xF2,0x30,0x1F,0x06,0x03,0x55,
+0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0x4E,0x0B,0xEF,0x1A,0xA4,0x40,0x5B,0xA5,
+0x17,0x69,0x87,0x30,0xCA,0x34,0x68,0x43,0xD0,0x41,0xAE,0xF2,0x30,0x82,0x01,0x5A,
+0x06,0x03,0x55,0x1D,0x20,0x04,0x82,0x01,0x51,0x30,0x82,0x01,0x4D,0x30,0x82,0x01,
+0x49,0x06,0x0B,0x2B,0x06,0x01,0x04,0x01,0x81,0xB5,0x37,0x01,0x01,0x01,0x30,0x82,
+0x01,0x38,0x30,0x2E,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x02,0x01,0x16,0x22,
+0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x73,0x74,0x61,0x72,0x74,
+0x73,0x73,0x6C,0x2E,0x63,0x6F,0x6D,0x2F,0x70,0x6F,0x6C,0x69,0x63,0x79,0x2E,0x70,
+0x64,0x66,0x30,0x34,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x02,0x01,0x16,0x28,
+0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x73,0x74,0x61,0x72,0x74,
+0x73,0x73,0x6C,0x2E,0x63,0x6F,0x6D,0x2F,0x69,0x6E,0x74,0x65,0x72,0x6D,0x65,0x64,
+0x69,0x61,0x74,0x65,0x2E,0x70,0x64,0x66,0x30,0x81,0xCF,0x06,0x08,0x2B,0x06,0x01,
+0x05,0x05,0x07,0x02,0x02,0x30,0x81,0xC2,0x30,0x27,0x16,0x20,0x53,0x74,0x61,0x72,
+0x74,0x20,0x43,0x6F,0x6D,0x6D,0x65,0x72,0x63,0x69,0x61,0x6C,0x20,0x28,0x53,0x74,
+0x61,0x72,0x74,0x43,0x6F,0x6D,0x29,0x20,0x4C,0x74,0x64,0x2E,0x30,0x03,0x02,0x01,
+0x01,0x1A,0x81,0x96,0x4C,0x69,0x6D,0x69,0x74,0x65,0x64,0x20,0x4C,0x69,0x61,0x62,
+0x69,0x6C,0x69,0x74,0x79,0x2C,0x20,0x72,0x65,0x61,0x64,0x20,0x74,0x68,0x65,0x20,
+0x73,0x65,0x63,0x74,0x69,0x6F,0x6E,0x20,0x2A,0x4C,0x65,0x67,0x61,0x6C,0x20,0x4C,
+0x69,0x6D,0x69,0x74,0x61,0x74,0x69,0x6F,0x6E,0x73,0x2A,0x20,0x6F,0x66,0x20,0x74,
+0x68,0x65,0x20,0x53,0x74,0x61,0x72,0x74,0x43,0x6F,0x6D,0x20,0x43,0x65,0x72,0x74,
+0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,
+0x69,0x74,0x79,0x20,0x50,0x6F,0x6C,0x69,0x63,0x79,0x20,0x61,0x76,0x61,0x69,0x6C,
+0x61,0x62,0x6C,0x65,0x20,0x61,0x74,0x20,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,
+0x77,0x77,0x2E,0x73,0x74,0x61,0x72,0x74,0x73,0x73,0x6C,0x2E,0x63,0x6F,0x6D,0x2F,
+0x70,0x6F,0x6C,0x69,0x63,0x79,0x2E,0x70,0x64,0x66,0x30,0x11,0x06,0x09,0x60,0x86,
+0x48,0x01,0x86,0xF8,0x42,0x01,0x01,0x04,0x04,0x03,0x02,0x00,0x07,0x30,0x38,0x06,
+0x09,0x60,0x86,0x48,0x01,0x86,0xF8,0x42,0x01,0x0D,0x04,0x2B,0x16,0x29,0x53,0x74,
+0x61,0x72,0x74,0x43,0x6F,0x6D,0x20,0x46,0x72,0x65,0x65,0x20,0x53,0x53,0x4C,0x20,
+0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,
+0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,
+0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82,0x02,0x01,0x00,0x8E,0x8F,0xE7,0xDC,0x94,
+0x79,0x7C,0xF1,0x85,0x7F,0x9F,0x49,0x6F,0x6B,0xCA,0x5D,0xFB,0x8C,0xFE,0x04,0xC5,
+0xC1,0x62,0xD1,0x7D,0x42,0x8A,0xBC,0x53,0xB7,0x94,0x03,0x66,0x30,0x3F,0xB1,0xE7,
+0x0A,0xA7,0x50,0x20,0x55,0x25,0x7F,0x76,0x7A,0x14,0x0D,0xEB,0x04,0x0E,0x40,0xE6,
+0x3E,0xD8,0x88,0xAB,0x07,0x27,0x83,0xA9,0x75,0xA6,0x37,0x73,0xC7,0xFD,0x4B,0xD2,
+0x4D,0xAD,0x17,0x40,0xC8,0x46,0xBE,0x3B,0x7F,0x51,0xFC,0xC3,0xB6,0x05,0x31,0xDC,
+0xCD,0x85,0x22,0x4E,0x71,0xB7,0xF2,0x71,0x5E,0xB0,0x1A,0xC6,0xBA,0x93,0x8B,0x78,
+0x92,0x4A,0x85,0xF8,0x78,0x0F,0x83,0xFE,0x2F,0xAD,0x2C,0xF7,0xE4,0xA4,0xBB,0x2D,
+0xD0,0xE7,0x0D,0x3A,0xB8,0x3E,0xCE,0xF6,0x78,0xF6,0xAE,0x47,0x24,0xCA,0xA3,0x35,
+0x36,0xCE,0xC7,0xC6,0x87,0x98,0xDA,0xEC,0xFB,0xE9,0xB2,0xCE,0x27,0x9B,0x88,0xC3,
+0x04,0xA1,0xF6,0x0B,0x59,0x68,0xAF,0xC9,0xDB,0x10,0x0F,0x4D,0xF6,0x64,0x63,0x5C,
+0xA5,0x12,0x6F,0x92,0xB2,0x93,0x94,0xC7,0x88,0x17,0x0E,0x93,0xB6,0x7E,0x62,0x8B,
+0x90,0x7F,0xAB,0x4E,0x9F,0xFC,0xE3,0x75,0x14,0x4F,0x2A,0x32,0xDF,0x5B,0x0D,0xE0,
+0xF5,0x7B,0x93,0x0D,0xAB,0xA1,0xCF,0x87,0xE1,0xA5,0x04,0x45,0xE8,0x3C,0x12,0xA5,
+0x09,0xC5,0xB0,0xD1,0xB7,0x53,0xF3,0x60,0x14,0xBA,0x85,0x69,0x6A,0x21,0x7C,0x1F,
+0x75,0x61,0x17,0x20,0x17,0x7B,0x6C,0x3B,0x41,0x29,0x5C,0xE1,0xAC,0x5A,0xD1,0xCD,
+0x8C,0x9B,0xEB,0x60,0x1D,0x19,0xEC,0xF7,0xE5,0xB0,0xDA,0xF9,0x79,0x18,0xA5,0x45,
+0x3F,0x49,0x43,0x57,0xD2,0xDD,0x24,0xD5,0x2C,0xA3,0xFD,0x91,0x8D,0x27,0xB5,0xE5,
+0xEB,0x14,0x06,0x9A,0x4C,0x7B,0x21,0xBB,0x3A,0xAD,0x30,0x06,0x18,0xC0,0xD8,0xC1,
+0x6B,0x2C,0x7F,0x59,0x5C,0x5D,0x91,0xB1,0x70,0x22,0x57,0xEB,0x8A,0x6B,0x48,0x4A,
+0xD5,0x0F,0x29,0xEC,0xC6,0x40,0xC0,0x2F,0x88,0x4C,0x68,0x01,0x17,0x77,0xF4,0x24,
+0x19,0x4F,0xBD,0xFA,0xE1,0xB2,0x20,0x21,0x4B,0xDD,0x1A,0xD8,0x29,0x7D,0xAA,0xB8,
+0xDE,0x54,0xEC,0x21,0x55,0x80,0x6C,0x1E,0xF5,0x30,0xC8,0xA3,0x10,0xE5,0xB2,0xE6,
+0x2A,0x14,0x31,0xC3,0x85,0x2D,0x8C,0x98,0xB1,0x86,0x5A,0x4F,0x89,0x59,0x2D,0xB9,
+0xC7,0xF7,0x1C,0xC8,0x8A,0x7F,0xC0,0x9D,0x05,0x4A,0xE6,0x42,0x4F,0x62,0xA3,0x6D,
+0x29,0xA4,0x1F,0x85,0xAB,0xDB,0xE5,0x81,0xC8,0xAD,0x2A,0x3D,0x4C,0x5D,0x5B,0x84,
+0x26,0x71,0xC4,0x85,0x5E,0x71,0x24,0xCA,0xA5,0x1B,0x6C,0xD8,0x61,0xD3,0x1A,0xE0,
+0x54,0xDB,0xCE,0xBA,0xA9,0x32,0xB5,0x22,0xF6,0x73,0x41,0x09,0x5D,0xB8,0x17,0x5D,
+0x0E,0x0F,0x99,0x90,0xD6,0x47,0xDA,0x6F,0x0A,0x3A,0x62,0x28,0x14,0x67,0x82,0xD9,
+0xF1,0xD0,0x80,0x59,0x9B,0xCB,0x31,0xD8,0x9B,0x0F,0x8C,0x77,0x4E,0xB5,0x68,0x8A,
+0xF2,0x6C,0xF6,0x24,0x0E,0x2D,0x6C,0x70,0xC5,0x73,0xD1,0xDE,0x14,0xD0,0x71,0x8F,
+0xB6,0xD3,0x7B,0x02,0xF6,0xE3,0xB8,0xD4,0x09,0x6E,0x6B,0x9E,0x75,0x84,0x39,0xE6,
+0x7F,0x25,0xA5,0xF2,0x48,0x00,0xC0,0xA4,0x01,0xDA,0x3F,
+};
+
+
+/* subject:/C=IL/O=StartCom Ltd./CN=StartCom Certification Authority G2 */
+/* issuer :/C=IL/O=StartCom Ltd./CN=StartCom Certification Authority G2 */
+
+
+const unsigned char StartCom_Certification_Authority_G2_certificate[1383]={
+0x30,0x82,0x05,0x63,0x30,0x82,0x03,0x4B,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x3B,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30,
+0x53,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x49,0x4C,0x31,0x16,
+0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x53,0x74,0x61,0x72,0x74,0x43,0x6F,
+0x6D,0x20,0x4C,0x74,0x64,0x2E,0x31,0x2C,0x30,0x2A,0x06,0x03,0x55,0x04,0x03,0x13,
+0x23,0x53,0x74,0x61,0x72,0x74,0x43,0x6F,0x6D,0x20,0x43,0x65,0x72,0x74,0x69,0x66,
+0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,
+0x79,0x20,0x47,0x32,0x30,0x1E,0x17,0x0D,0x31,0x30,0x30,0x31,0x30,0x31,0x30,0x31,
+0x30,0x30,0x30,0x31,0x5A,0x17,0x0D,0x33,0x39,0x31,0x32,0x33,0x31,0x32,0x33,0x35,
+0x39,0x30,0x31,0x5A,0x30,0x53,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,
+0x02,0x49,0x4C,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x0A,0x13,0x0D,0x53,0x74,
+0x61,0x72,0x74,0x43,0x6F,0x6D,0x20,0x4C,0x74,0x64,0x2E,0x31,0x2C,0x30,0x2A,0x06,
+0x03,0x55,0x04,0x03,0x13,0x23,0x53,0x74,0x61,0x72,0x74,0x43,0x6F,0x6D,0x20,0x43,
+0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,
+0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x47,0x32,0x30,0x82,0x02,0x22,0x30,0x0D,0x06,
+0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x02,0x0F,
+0x00,0x30,0x82,0x02,0x0A,0x02,0x82,0x02,0x01,0x00,0xB6,0x89,0x36,0x5B,0x07,0xB7,
+0x20,0x36,0xBD,0x82,0xBB,0xE1,0x16,0x20,0x03,0x95,0x7A,0xAF,0x0E,0xA3,0x55,0xC9,
+0x25,0x99,0x4A,0xC5,0xD0,0x56,0x41,0x87,0x90,0x4D,0x21,0x60,0xA4,0x14,0x87,0x3B,
+0xCD,0xFD,0xB2,0x3E,0xB4,0x67,0x03,0x6A,0xED,0xE1,0x0F,0x4B,0xC0,0x91,0x85,0x70,
+0x45,0xE0,0x42,0x9E,0xDE,0x29,0x23,0xD4,0x01,0x0D,0xA0,0x10,0x79,0xB8,0xDB,0x03,
+0xBD,0xF3,0xA9,0x2F,0xD1,0xC6,0xE0,0x0F,0xCB,0x9E,0x8A,0x14,0x0A,0xB8,0xBD,0xF6,
+0x56,0x62,0xF1,0xC5,0x72,0xB6,0x32,0x25,0xD9,0xB2,0xF3,0xBD,0x65,0xC5,0x0D,0x2C,
+0x6E,0xD5,0x92,0x6F,0x18,0x8B,0x00,0x41,0x14,0x82,0x6F,0x40,0x20,0x26,0x7A,0x28,
+0x0F,0xF5,0x1E,0x7F,0x27,0xF7,0x94,0xB1,0x37,0x3D,0xB7,0xC7,0x91,0xF7,0xE2,0x01,
+0xEC,0xFD,0x94,0x89,0xE1,0xCC,0x6E,0xD3,0x36,0xD6,0x0A,0x19,0x79,0xAE,0xD7,0x34,
+0x82,0x65,0xFF,0x7C,0x42,0xBB,0xB6,0xDD,0x0B,0xA6,0x34,0xAF,0x4B,0x60,0xFE,0x7F,
+0x43,0x49,0x06,0x8B,0x8C,0x43,0xB8,0x56,0xF2,0xD9,0x7F,0x21,0x43,0x17,0xEA,0xA7,
+0x48,0x95,0x01,0x75,0x75,0xEA,0x2B,0xA5,0x43,0x95,0xEA,0x15,0x84,0x9D,0x08,0x8D,
+0x26,0x6E,0x55,0x9B,0xAB,0xDC,0xD2,0x39,0xD2,0x31,0x1D,0x60,0xE2,0xAC,0xCC,0x56,
+0x45,0x24,0xF5,0x1C,0x54,0xAB,0xEE,0x86,0xDD,0x96,0x32,0x85,0xF8,0x4C,0x4F,0xE8,
+0x95,0x76,0xB6,0x05,0xDD,0x36,0x23,0x67,0xBC,0xFF,0x15,0xE2,0xCA,0x3B,0xE6,0xA6,
+0xEC,0x3B,0xEC,0x26,0x11,0x34,0x48,0x8D,0xF6,0x80,0x2B,0x1A,0x23,0x02,0xEB,0x8A,
+0x1C,0x3A,0x76,0x2A,0x7B,0x56,0x16,0x1C,0x72,0x2A,0xB3,0xAA,0xE3,0x60,0xA5,0x00,
+0x9F,0x04,0x9B,0xE2,0x6F,0x1E,0x14,0x58,0x5B,0xA5,0x6C,0x8B,0x58,0x3C,0xC3,0xBA,
+0x4E,0x3A,0x5C,0xF7,0xE1,0x96,0x2B,0x3E,0xEF,0x07,0xBC,0xA4,0xE5,0x5D,0xCC,0x4D,
+0x9F,0x0D,0xE1,0xDC,0xAA,0xBB,0xE1,0x6E,0x1A,0xEC,0x8F,0xE1,0xB6,0x4C,0x4D,0x79,
+0x72,0x5D,0x17,0x35,0x0B,0x1D,0xD7,0xC1,0x47,0xDA,0x96,0x24,0xE0,0xD0,0x72,0xA8,
+0x5A,0x5F,0x66,0x2D,0x10,0xDC,0x2F,0x2A,0x13,0xAE,0x26,0xFE,0x0A,0x1C,0x19,0xCC,
+0xD0,0x3E,0x0B,0x9C,0xC8,0x09,0x2E,0xF9,0x5B,0x96,0x7A,0x47,0x9C,0xE9,0x7A,0xF3,
+0x05,0x50,0x74,0x95,0x73,0x9E,0x30,0x09,0xF3,0x97,0x82,0x5E,0xE6,0x8F,0x39,0x08,
+0x1E,0x59,0xE5,0x35,0x14,0x42,0x13,0xFF,0x00,0x9C,0xF7,0xBE,0xAA,0x50,0xCF,0xE2,
+0x51,0x48,0xD7,0xB8,0x6F,0xAF,0xF8,0x4E,0x7E,0x33,0x98,0x92,0x14,0x62,0x3A,0x75,
+0x63,0xCF,0x7B,0xFA,0xDE,0x82,0x3B,0xA9,0xBB,0x39,0xE2,0xC4,0xBD,0x2C,0x00,0x0E,
+0xC8,0x17,0xAC,0x13,0xEF,0x4D,0x25,0x8E,0xD8,0xB3,0x90,0x2F,0xA9,0xDA,0x29,0x7D,
+0x1D,0xAF,0x74,0x3A,0xB2,0x27,0xC0,0xC1,0x1E,0x3E,0x75,0xA3,0x16,0xA9,0xAF,0x7A,
+0x22,0x5D,0x9F,0x13,0x1A,0xCF,0xA7,0xA0,0xEB,0xE3,0x86,0x0A,0xD3,0xFD,0xE6,0x96,
+0x95,0xD7,0x23,0xC8,0x37,0xDD,0xC4,0x7C,0xAA,0x36,0xAC,0x98,0x1A,0x12,0xB1,0xE0,
+0x4E,0xE8,0xB1,0x3B,0xF5,0xD6,0x6F,0xF1,0x30,0xD7,0x02,0x03,0x01,0x00,0x01,0xA3,
+0x42,0x30,0x40,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,
+0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,
+0x03,0x02,0x01,0x06,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x4B,
+0xC5,0xB4,0x40,0x6B,0xAD,0x1C,0xB3,0xA5,0x1C,0x65,0x6E,0x46,0x36,0x89,0x87,0x05,
+0x0C,0x0E,0xB6,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,
+0x05,0x00,0x03,0x82,0x02,0x01,0x00,0x73,0x57,0x3F,0x2C,0xD5,0x95,0x32,0x7E,0x37,
+0xDB,0x96,0x92,0xEB,0x19,0x5E,0x7E,0x53,0xE7,0x41,0xEC,0x11,0xB6,0x47,0xEF,0xB5,
+0xDE,0xED,0x74,0x5C,0xC5,0xF1,0x8E,0x49,0xE0,0xFC,0x6E,0x99,0x13,0xCD,0x9F,0x8A,
+0xDA,0xCD,0x3A,0x0A,0xD8,0x3A,0x5A,0x09,0x3F,0x5F,0x34,0xD0,0x2F,0x03,0xD2,0x66,
+0x1D,0x1A,0xBD,0x9C,0x90,0x37,0xC8,0x0C,0x8E,0x07,0x5A,0x94,0x45,0x46,0x2A,0xE6,
+0xBE,0x7A,0xDA,0xA1,0xA9,0xA4,0x69,0x12,0x92,0xB0,0x7D,0x36,0xD4,0x44,0x87,0xD7,
+0x51,0xF1,0x29,0x63,0xD6,0x75,0xCD,0x16,0xE4,0x27,0x89,0x1D,0xF8,0xC2,0x32,0x48,
+0xFD,0xDB,0x99,0xD0,0x8F,0x5F,0x54,0x74,0xCC,0xAC,0x67,0x34,0x11,0x62,0xD9,0x0C,
+0x0A,0x37,0x87,0xD1,0xA3,0x17,0x48,0x8E,0xD2,0x17,0x1D,0xF6,0xD7,0xFD,0xDB,0x65,
+0xEB,0xFD,0xA8,0xD4,0xF5,0xD6,0x4F,0xA4,0x5B,0x75,0xE8,0xC5,0xD2,0x60,0xB2,0xDB,
+0x09,0x7E,0x25,0x8B,0x7B,0xBA,0x52,0x92,0x9E,0x3E,0xE8,0xC5,0x77,0xA1,0x3C,0xE0,
+0x4A,0x73,0x6B,0x61,0xCF,0x86,0xDC,0x43,0xFF,0xFF,0x21,0xFE,0x23,0x5D,0x24,0x4A,
+0xF5,0xD3,0x6D,0x0F,0x62,0x04,0x05,0x57,0x82,0xDA,0x6E,0xA4,0x33,0x25,0x79,0x4B,
+0x2E,0x54,0x19,0x8B,0xCC,0x2C,0x3D,0x30,0xE9,0xD1,0x06,0xFF,0xE8,0x32,0x46,0xBE,
+0xB5,0x33,0x76,0x77,0xA8,0x01,0x5D,0x96,0xC1,0xC1,0xD5,0xBE,0xAE,0x25,0xC0,0xC9,
+0x1E,0x0A,0x09,0x20,0x88,0xA1,0x0E,0xC9,0xF3,0x6F,0x4D,0x82,0x54,0x00,0x20,0xA7,
+0xD2,0x8F,0xE4,0x39,0x54,0x17,0x2E,0x8D,0x1E,0xB8,0x1B,0xBB,0x1B,0xBD,0x9A,0x4E,
+0x3B,0x10,0x34,0xDC,0x9C,0x88,0x53,0xEF,0xA2,0x31,0x5B,0x58,0x4F,0x91,0x62,0xC8,
+0xC2,0x9A,0x9A,0xCD,0x15,0x5D,0x38,0xA9,0xD6,0xBE,0xF8,0x13,0xB5,0x9F,0x12,0x69,
+0xF2,0x50,0x62,0xAC,0xFB,0x17,0x37,0xF4,0xEE,0xB8,0x75,0x67,0x60,0x10,0xFB,0x83,
+0x50,0xF9,0x44,0xB5,0x75,0x9C,0x40,0x17,0xB2,0xFE,0xFD,0x79,0x5D,0x6E,0x58,0x58,
+0x5F,0x30,0xFC,0x00,0xAE,0xAF,0x33,0xC1,0x0E,0x4E,0x6C,0xBA,0xA7,0xA6,0xA1,0x7F,
+0x32,0xDB,0x38,0xE0,0xB1,0x72,0x17,0x0A,0x2B,0x91,0xEC,0x6A,0x63,0x26,0xED,0x89,
+0xD4,0x78,0xCC,0x74,0x1E,0x05,0xF8,0x6B,0xFE,0x8C,0x6A,0x76,0x39,0x29,0xAE,0x65,
+0x23,0x12,0x95,0x08,0x22,0x1C,0x97,0xCE,0x5B,0x06,0xEE,0x0C,0xE2,0xBB,0xBC,0x1F,
+0x44,0x93,0xF6,0xD8,0x38,0x45,0x05,0x21,0xED,0xE4,0xAD,0xAB,0x12,0xB6,0x03,0xA4,
+0x42,0x2E,0x2D,0xC4,0x09,0x3A,0x03,0x67,0x69,0x84,0x9A,0xE1,0x59,0x90,0x8A,0x28,
+0x85,0xD5,0x5D,0x74,0xB1,0xD1,0x0E,0x20,0x58,0x9B,0x13,0xA5,0xB0,0x63,0xA6,0xED,
+0x7B,0x47,0xFD,0x45,0x55,0x30,0xA4,0xEE,0x9A,0xD4,0xE6,0xE2,0x87,0xEF,0x98,0xC9,
+0x32,0x82,0x11,0x29,0x22,0xBC,0x00,0x0A,0x31,0x5E,0x2D,0x0F,0xC0,0x8E,0xE9,0x6B,
+0xB2,0x8F,0x2E,0x06,0xD8,0xD1,0x91,0xC7,0xC6,0x12,0xF4,0x4C,0xFD,0x30,0x17,0xC3,
+0xC1,0xDA,0x38,0x5B,0xE3,0xA9,0xEA,0xE6,0xA1,0xBA,0x79,0xEF,0x73,0xD8,0xB6,0x53,
+0x57,0x2D,0xF6,0xD0,0xE1,0xD7,0x48,
+};
+
+
+/* subject:/C=DE/O=TC TrustCenter GmbH/OU=TC TrustCenter Class 2 CA/CN=TC TrustCenter Class 2 CA II */
+/* issuer :/C=DE/O=TC TrustCenter GmbH/OU=TC TrustCenter Class 2 CA/CN=TC TrustCenter Class 2 CA II */
+
+
+const unsigned char TC_TrustCenter_Class_2_CA_II_certificate[1198]={
+0x30,0x82,0x04,0xAA,0x30,0x82,0x03,0x92,0xA0,0x03,0x02,0x01,0x02,0x02,0x0E,0x2E,
+0x6A,0x00,0x01,0x00,0x02,0x1F,0xD7,0x52,0x21,0x2C,0x11,0x5C,0x3B,0x30,0x0D,0x06,
+0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x76,0x31,0x0B,
+0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x44,0x45,0x31,0x1C,0x30,0x1A,0x06,
+0x03,0x55,0x04,0x0A,0x13,0x13,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,
+0x6E,0x74,0x65,0x72,0x20,0x47,0x6D,0x62,0x48,0x31,0x22,0x30,0x20,0x06,0x03,0x55,
+0x04,0x0B,0x13,0x19,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,
+0x65,0x72,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x32,0x20,0x43,0x41,0x31,0x25,0x30,
+0x23,0x06,0x03,0x55,0x04,0x03,0x13,0x1C,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,
+0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x32,0x20,0x43,
+0x41,0x20,0x49,0x49,0x30,0x1E,0x17,0x0D,0x30,0x36,0x30,0x31,0x31,0x32,0x31,0x34,
+0x33,0x38,0x34,0x33,0x5A,0x17,0x0D,0x32,0x35,0x31,0x32,0x33,0x31,0x32,0x32,0x35,
+0x39,0x35,0x39,0x5A,0x30,0x76,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,
+0x02,0x44,0x45,0x31,0x1C,0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x54,0x43,
+0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x47,0x6D,0x62,
+0x48,0x31,0x22,0x30,0x20,0x06,0x03,0x55,0x04,0x0B,0x13,0x19,0x54,0x43,0x20,0x54,
+0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x43,0x6C,0x61,0x73,0x73,
+0x20,0x32,0x20,0x43,0x41,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x03,0x13,0x1C,
+0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x43,
+0x6C,0x61,0x73,0x73,0x20,0x32,0x20,0x43,0x41,0x20,0x49,0x49,0x30,0x82,0x01,0x22,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,
+0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xAB,0x80,0x87,
+0x9B,0x8E,0xF0,0xC3,0x7C,0x87,0xD7,0xE8,0x24,0x82,0x11,0xB3,0x3C,0xDD,0x43,0x62,
+0xEE,0xF8,0xC3,0x45,0xDA,0xE8,0xE1,0xA0,0x5F,0xD1,0x2A,0xB2,0xEA,0x93,0x68,0xDF,
+0xB4,0xC8,0xD6,0x43,0xE9,0xC4,0x75,0x59,0x7F,0xFC,0xE1,0x1D,0xF8,0x31,0x70,0x23,
+0x1B,0x88,0x9E,0x27,0xB9,0x7B,0xFD,0x3A,0xD2,0xC9,0xA9,0xE9,0x14,0x2F,0x90,0xBE,
+0x03,0x52,0xC1,0x49,0xCD,0xF6,0xFD,0xE4,0x08,0x66,0x0B,0x57,0x8A,0xA2,0x42,0xA0,
+0xB8,0xD5,0x7F,0x69,0x5C,0x90,0x32,0xB2,0x97,0x0D,0xCA,0x4A,0xDC,0x46,0x3E,0x02,
+0x55,0x89,0x53,0xE3,0x1A,0x5A,0xCB,0x36,0xC6,0x07,0x56,0xF7,0x8C,0xCF,0x11,0xF4,
+0x4C,0xBB,0x30,0x70,0x04,0x95,0xA5,0xF6,0x39,0x8C,0xFD,0x73,0x81,0x08,0x7D,0x89,
+0x5E,0x32,0x1E,0x22,0xA9,0x22,0x45,0x4B,0xB0,0x66,0x2E,0x30,0xCC,0x9F,0x65,0xFD,
+0xFC,0xCB,0x81,0xA9,0xF1,0xE0,0x3B,0xAF,0xA3,0x86,0xD1,0x89,0xEA,0xC4,0x45,0x79,
+0x50,0x5D,0xAE,0xE9,0x21,0x74,0x92,0x4D,0x8B,0x59,0x82,0x8F,0x94,0xE3,0xE9,0x4A,
+0xF1,0xE7,0x49,0xB0,0x14,0xE3,0xF5,0x62,0xCB,0xD5,0x72,0xBD,0x1F,0xB9,0xD2,0x9F,
+0xA0,0xCD,0xA8,0xFA,0x01,0xC8,0xD9,0x0D,0xDF,0xDA,0xFC,0x47,0x9D,0xB3,0xC8,0x54,
+0xDF,0x49,0x4A,0xF1,0x21,0xA9,0xFE,0x18,0x4E,0xEE,0x48,0xD4,0x19,0xBB,0xEF,0x7D,
+0xE4,0xE2,0x9D,0xCB,0x5B,0xB6,0x6E,0xFF,0xE3,0xCD,0x5A,0xE7,0x74,0x82,0x05,0xBA,
+0x80,0x25,0x38,0xCB,0xE4,0x69,0x9E,0xAF,0x41,0xAA,0x1A,0x84,0xF5,0x02,0x03,0x01,
+0x00,0x01,0xA3,0x82,0x01,0x34,0x30,0x82,0x01,0x30,0x30,0x0F,0x06,0x03,0x55,0x1D,
+0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55,
+0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D,0x06,0x03,0x55,
+0x1D,0x0E,0x04,0x16,0x04,0x14,0xE3,0xAB,0x54,0x4C,0x80,0xA1,0xDB,0x56,0x43,0xB7,
+0x91,0x4A,0xCB,0xF3,0x82,0x7A,0x13,0x5C,0x08,0xAB,0x30,0x81,0xED,0x06,0x03,0x55,
+0x1D,0x1F,0x04,0x81,0xE5,0x30,0x81,0xE2,0x30,0x81,0xDF,0xA0,0x81,0xDC,0xA0,0x81,
+0xD9,0x86,0x35,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x74,0x72,
+0x75,0x73,0x74,0x63,0x65,0x6E,0x74,0x65,0x72,0x2E,0x64,0x65,0x2F,0x63,0x72,0x6C,
+0x2F,0x76,0x32,0x2F,0x74,0x63,0x5F,0x63,0x6C,0x61,0x73,0x73,0x5F,0x32,0x5F,0x63,
+0x61,0x5F,0x49,0x49,0x2E,0x63,0x72,0x6C,0x86,0x81,0x9F,0x6C,0x64,0x61,0x70,0x3A,
+0x2F,0x2F,0x77,0x77,0x77,0x2E,0x74,0x72,0x75,0x73,0x74,0x63,0x65,0x6E,0x74,0x65,
+0x72,0x2E,0x64,0x65,0x2F,0x43,0x4E,0x3D,0x54,0x43,0x25,0x32,0x30,0x54,0x72,0x75,
+0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x25,0x32,0x30,0x43,0x6C,0x61,0x73,0x73,
+0x25,0x32,0x30,0x32,0x25,0x32,0x30,0x43,0x41,0x25,0x32,0x30,0x49,0x49,0x2C,0x4F,
+0x3D,0x54,0x43,0x25,0x32,0x30,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,
+0x72,0x25,0x32,0x30,0x47,0x6D,0x62,0x48,0x2C,0x4F,0x55,0x3D,0x72,0x6F,0x6F,0x74,
+0x63,0x65,0x72,0x74,0x73,0x2C,0x44,0x43,0x3D,0x74,0x72,0x75,0x73,0x74,0x63,0x65,
+0x6E,0x74,0x65,0x72,0x2C,0x44,0x43,0x3D,0x64,0x65,0x3F,0x63,0x65,0x72,0x74,0x69,
+0x66,0x69,0x63,0x61,0x74,0x65,0x52,0x65,0x76,0x6F,0x63,0x61,0x74,0x69,0x6F,0x6E,
+0x4C,0x69,0x73,0x74,0x3F,0x62,0x61,0x73,0x65,0x3F,0x30,0x0D,0x06,0x09,0x2A,0x86,
+0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x8C,0xD7,
+0xDF,0x7E,0xEE,0x1B,0x80,0x10,0xB3,0x83,0xF5,0xDB,0x11,0xEA,0x6B,0x4B,0xA8,0x92,
+0x18,0xD9,0xF7,0x07,0x39,0xF5,0x2C,0xBE,0x06,0x75,0x7A,0x68,0x53,0x15,0x1C,0xEA,
+0x4A,0xED,0x5E,0xFC,0x23,0xB2,0x13,0xA0,0xD3,0x09,0xFF,0xF6,0xF6,0x2E,0x6B,0x41,
+0x71,0x79,0xCD,0xE2,0x6D,0xFD,0xAE,0x59,0x6B,0x85,0x1D,0xB8,0x4E,0x22,0x9A,0xED,
+0x66,0x39,0x6E,0x4B,0x94,0xE6,0x55,0xFC,0x0B,0x1B,0x8B,0x77,0xC1,0x53,0x13,0x66,
+0x89,0xD9,0x28,0xD6,0x8B,0xF3,0x45,0x4A,0x63,0xB7,0xFD,0x7B,0x0B,0x61,0x5D,0xB8,
+0x6D,0xBE,0xC3,0xDC,0x5B,0x79,0xD2,0xED,0x86,0xE5,0xA2,0x4D,0xBE,0x5E,0x74,0x7C,
+0x6A,0xED,0x16,0x38,0x1F,0x7F,0x58,0x81,0x5A,0x1A,0xEB,0x32,0x88,0x2D,0xB2,0xF3,
+0x39,0x77,0x80,0xAF,0x5E,0xB6,0x61,0x75,0x29,0xDB,0x23,0x4D,0x88,0xCA,0x50,0x28,
+0xCB,0x85,0xD2,0xD3,0x10,0xA2,0x59,0x6E,0xD3,0x93,0x54,0x00,0x7A,0xA2,0x46,0x95,
+0x86,0x05,0x9C,0xA9,0x19,0x98,0xE5,0x31,0x72,0x0C,0x00,0xE2,0x67,0xD9,0x40,0xE0,
+0x24,0x33,0x7B,0x6F,0x2C,0xB9,0x5C,0xAB,0x65,0x9D,0x2C,0xAC,0x76,0xEA,0x35,0x99,
+0xF5,0x97,0xB9,0x0F,0x24,0xEC,0xC7,0x76,0x21,0x28,0x65,0xAE,0x57,0xE8,0x07,0x88,
+0x75,0x4A,0x56,0xA0,0xD2,0x05,0x3A,0xA4,0xE6,0x8D,0x92,0x88,0x2C,0xF3,0xF2,0xE1,
+0xC1,0xC6,0x61,0xDB,0x41,0xC5,0xC7,0x9B,0xF7,0x0E,0x1A,0x51,0x45,0xC2,0x61,0x6B,
+0xDC,0x64,0x27,0x17,0x8C,0x5A,0xB7,0xDA,0x74,0x28,0xCD,0x97,0xE4,0xBD,
+};
+
+
+/* subject:/C=DE/O=TC TrustCenter GmbH/OU=TC TrustCenter Class 3 CA/CN=TC TrustCenter Class 3 CA II */
+/* issuer :/C=DE/O=TC TrustCenter GmbH/OU=TC TrustCenter Class 3 CA/CN=TC TrustCenter Class 3 CA II */
+
+
+const unsigned char TC_TrustCenter_Class_3_CA_II_certificate[1198]={
+0x30,0x82,0x04,0xAA,0x30,0x82,0x03,0x92,0xA0,0x03,0x02,0x01,0x02,0x02,0x0E,0x4A,
+0x47,0x00,0x01,0x00,0x02,0xE5,0xA0,0x5D,0xD6,0x3F,0x00,0x51,0xBF,0x30,0x0D,0x06,
+0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x76,0x31,0x0B,
+0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x44,0x45,0x31,0x1C,0x30,0x1A,0x06,
+0x03,0x55,0x04,0x0A,0x13,0x13,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,
+0x6E,0x74,0x65,0x72,0x20,0x47,0x6D,0x62,0x48,0x31,0x22,0x30,0x20,0x06,0x03,0x55,
+0x04,0x0B,0x13,0x19,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,
+0x65,0x72,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x43,0x41,0x31,0x25,0x30,
+0x23,0x06,0x03,0x55,0x04,0x03,0x13,0x1C,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,
+0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x43,
+0x41,0x20,0x49,0x49,0x30,0x1E,0x17,0x0D,0x30,0x36,0x30,0x31,0x31,0x32,0x31,0x34,
+0x34,0x31,0x35,0x37,0x5A,0x17,0x0D,0x32,0x35,0x31,0x32,0x33,0x31,0x32,0x32,0x35,
+0x39,0x35,0x39,0x5A,0x30,0x76,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,
+0x02,0x44,0x45,0x31,0x1C,0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x54,0x43,
+0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x47,0x6D,0x62,
+0x48,0x31,0x22,0x30,0x20,0x06,0x03,0x55,0x04,0x0B,0x13,0x19,0x54,0x43,0x20,0x54,
+0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x43,0x6C,0x61,0x73,0x73,
+0x20,0x33,0x20,0x43,0x41,0x31,0x25,0x30,0x23,0x06,0x03,0x55,0x04,0x03,0x13,0x1C,
+0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x43,
+0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x43,0x41,0x20,0x49,0x49,0x30,0x82,0x01,0x22,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,
+0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xB4,0xE0,0xBB,
+0x51,0xBB,0x39,0x5C,0x8B,0x04,0xC5,0x4C,0x79,0x1C,0x23,0x86,0x31,0x10,0x63,0x43,
+0x55,0x27,0x3F,0xC6,0x45,0xC7,0xA4,0x3D,0xEC,0x09,0x0D,0x1A,0x1E,0x20,0xC2,0x56,
+0x1E,0xDE,0x1B,0x37,0x07,0x30,0x22,0x2F,0x6F,0xF1,0x06,0xF1,0xAB,0xAD,0xD6,0xC8,
+0xAB,0x61,0xA3,0x2F,0x43,0xC4,0xB0,0xB2,0x2D,0xFC,0xC3,0x96,0x69,0x7B,0x7E,0x8A,
+0xE4,0xCC,0xC0,0x39,0x12,0x90,0x42,0x60,0xC9,0xCC,0x35,0x68,0xEE,0xDA,0x5F,0x90,
+0x56,0x5F,0xCD,0x1C,0x4D,0x5B,0x58,0x49,0xEB,0x0E,0x01,0x4F,0x64,0xFA,0x2C,0x3C,
+0x89,0x58,0xD8,0x2F,0x2E,0xE2,0xB0,0x68,0xE9,0x22,0x3B,0x75,0x89,0xD6,0x44,0x1A,
+0x65,0xF2,0x1B,0x97,0x26,0x1D,0x28,0x6D,0xAC,0xE8,0xBD,0x59,0x1D,0x2B,0x24,0xF6,
+0xD6,0x84,0x03,0x66,0x88,0x24,0x00,0x78,0x60,0xF1,0xF8,0xAB,0xFE,0x02,0xB2,0x6B,
+0xFB,0x22,0xFB,0x35,0xE6,0x16,0xD1,0xAD,0xF6,0x2E,0x12,0xE4,0xFA,0x35,0x6A,0xE5,
+0x19,0xB9,0x5D,0xDB,0x3B,0x1E,0x1A,0xFB,0xD3,0xFF,0x15,0x14,0x08,0xD8,0x09,0x6A,
+0xBA,0x45,0x9D,0x14,0x79,0x60,0x7D,0xAF,0x40,0x8A,0x07,0x73,0xB3,0x93,0x96,0xD3,
+0x74,0x34,0x8D,0x3A,0x37,0x29,0xDE,0x5C,0xEC,0xF5,0xEE,0x2E,0x31,0xC2,0x20,0xDC,
+0xBE,0xF1,0x4F,0x7F,0x23,0x52,0xD9,0x5B,0xE2,0x64,0xD9,0x9C,0xAA,0x07,0x08,0xB5,
+0x45,0xBD,0xD1,0xD0,0x31,0xC1,0xAB,0x54,0x9F,0xA9,0xD2,0xC3,0x62,0x60,0x03,0xF1,
+0xBB,0x39,0x4A,0x92,0x4A,0x3D,0x0A,0xB9,0x9D,0xC5,0xA0,0xFE,0x37,0x02,0x03,0x01,
+0x00,0x01,0xA3,0x82,0x01,0x34,0x30,0x82,0x01,0x30,0x30,0x0F,0x06,0x03,0x55,0x1D,
+0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55,
+0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D,0x06,0x03,0x55,
+0x1D,0x0E,0x04,0x16,0x04,0x14,0xD4,0xA2,0xFC,0x9F,0xB3,0xC3,0xD8,0x03,0xD3,0x57,
+0x5C,0x07,0xA4,0xD0,0x24,0xA7,0xC0,0xF2,0x00,0xD4,0x30,0x81,0xED,0x06,0x03,0x55,
+0x1D,0x1F,0x04,0x81,0xE5,0x30,0x81,0xE2,0x30,0x81,0xDF,0xA0,0x81,0xDC,0xA0,0x81,
+0xD9,0x86,0x35,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x74,0x72,
+0x75,0x73,0x74,0x63,0x65,0x6E,0x74,0x65,0x72,0x2E,0x64,0x65,0x2F,0x63,0x72,0x6C,
+0x2F,0x76,0x32,0x2F,0x74,0x63,0x5F,0x63,0x6C,0x61,0x73,0x73,0x5F,0x33,0x5F,0x63,
+0x61,0x5F,0x49,0x49,0x2E,0x63,0x72,0x6C,0x86,0x81,0x9F,0x6C,0x64,0x61,0x70,0x3A,
+0x2F,0x2F,0x77,0x77,0x77,0x2E,0x74,0x72,0x75,0x73,0x74,0x63,0x65,0x6E,0x74,0x65,
+0x72,0x2E,0x64,0x65,0x2F,0x43,0x4E,0x3D,0x54,0x43,0x25,0x32,0x30,0x54,0x72,0x75,
+0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x25,0x32,0x30,0x43,0x6C,0x61,0x73,0x73,
+0x25,0x32,0x30,0x33,0x25,0x32,0x30,0x43,0x41,0x25,0x32,0x30,0x49,0x49,0x2C,0x4F,
+0x3D,0x54,0x43,0x25,0x32,0x30,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,
+0x72,0x25,0x32,0x30,0x47,0x6D,0x62,0x48,0x2C,0x4F,0x55,0x3D,0x72,0x6F,0x6F,0x74,
+0x63,0x65,0x72,0x74,0x73,0x2C,0x44,0x43,0x3D,0x74,0x72,0x75,0x73,0x74,0x63,0x65,
+0x6E,0x74,0x65,0x72,0x2C,0x44,0x43,0x3D,0x64,0x65,0x3F,0x63,0x65,0x72,0x74,0x69,
+0x66,0x69,0x63,0x61,0x74,0x65,0x52,0x65,0x76,0x6F,0x63,0x61,0x74,0x69,0x6F,0x6E,
+0x4C,0x69,0x73,0x74,0x3F,0x62,0x61,0x73,0x65,0x3F,0x30,0x0D,0x06,0x09,0x2A,0x86,
+0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x36,0x60,
+0xE4,0x70,0xF7,0x06,0x20,0x43,0xD9,0x23,0x1A,0x42,0xF2,0xF8,0xA3,0xB2,0xB9,0x4D,
+0x8A,0xB4,0xF3,0xC2,0x9A,0x55,0x31,0x7C,0xC4,0x3B,0x67,0x9A,0xB4,0xDF,0x4D,0x0E,
+0x8A,0x93,0x4A,0x17,0x8B,0x1B,0x8D,0xCA,0x89,0xE1,0xCF,0x3A,0x1E,0xAC,0x1D,0xF1,
+0x9C,0x32,0xB4,0x8E,0x59,0x76,0xA2,0x41,0x85,0x25,0x37,0xA0,0x13,0xD0,0xF5,0x7C,
+0x4E,0xD5,0xEA,0x96,0xE2,0x6E,0x72,0xC1,0xBB,0x2A,0xFE,0x6C,0x6E,0xF8,0x91,0x98,
+0x46,0xFC,0xC9,0x1B,0x57,0x5B,0xEA,0xC8,0x1A,0x3B,0x3F,0xB0,0x51,0x98,0x3C,0x07,
+0xDA,0x2C,0x59,0x01,0xDA,0x8B,0x44,0xE8,0xE1,0x74,0xFD,0xA7,0x68,0xDD,0x54,0xBA,
+0x83,0x46,0xEC,0xC8,0x46,0xB5,0xF8,0xAF,0x97,0xC0,0x3B,0x09,0x1C,0x8F,0xCE,0x72,
+0x96,0x3D,0x33,0x56,0x70,0xBC,0x96,0xCB,0xD8,0xD5,0x7D,0x20,0x9A,0x83,0x9F,0x1A,
+0xDC,0x39,0xF1,0xC5,0x72,0xA3,0x11,0x03,0xFD,0x3B,0x42,0x52,0x29,0xDB,0xE8,0x01,
+0xF7,0x9B,0x5E,0x8C,0xD6,0x8D,0x86,0x4E,0x19,0xFA,0xBC,0x1C,0xBE,0xC5,0x21,0xA5,
+0x87,0x9E,0x78,0x2E,0x36,0xDB,0x09,0x71,0xA3,0x72,0x34,0xF8,0x6C,0xE3,0x06,0x09,
+0xF2,0x5E,0x56,0xA5,0xD3,0xDD,0x98,0xFA,0xD4,0xE6,0x06,0xF4,0xF0,0xB6,0x20,0x63,
+0x4B,0xEA,0x29,0xBD,0xAA,0x82,0x66,0x1E,0xFB,0x81,0xAA,0xA7,0x37,0xAD,0x13,0x18,
+0xE6,0x92,0xC3,0x81,0xC1,0x33,0xBB,0x88,0x1E,0xA1,0xE7,0xE2,0xB4,0xBD,0x31,0x6C,
+0x0E,0x51,0x3D,0x6F,0xFB,0x96,0x56,0x80,0xE2,0x36,0x17,0xD1,0xDC,0xE4,
+};
+
+
+/* subject:/C=DE/O=TC TrustCenter GmbH/OU=TC TrustCenter Universal CA/CN=TC TrustCenter Universal CA I */
+/* issuer :/C=DE/O=TC TrustCenter GmbH/OU=TC TrustCenter Universal CA/CN=TC TrustCenter Universal CA I */
+
+
+const unsigned char TC_TrustCenter_Universal_CA_I_certificate[993]={
+0x30,0x82,0x03,0xDD,0x30,0x82,0x02,0xC5,0xA0,0x03,0x02,0x01,0x02,0x02,0x0E,0x1D,
+0xA2,0x00,0x01,0x00,0x02,0xEC,0xB7,0x60,0x80,0x78,0x8D,0xB6,0x06,0x30,0x0D,0x06,
+0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x79,0x31,0x0B,
+0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x44,0x45,0x31,0x1C,0x30,0x1A,0x06,
+0x03,0x55,0x04,0x0A,0x13,0x13,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,
+0x6E,0x74,0x65,0x72,0x20,0x47,0x6D,0x62,0x48,0x31,0x24,0x30,0x22,0x06,0x03,0x55,
+0x04,0x0B,0x13,0x1B,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,
+0x65,0x72,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61,0x6C,0x20,0x43,0x41,0x31,
+0x26,0x30,0x24,0x06,0x03,0x55,0x04,0x03,0x13,0x1D,0x54,0x43,0x20,0x54,0x72,0x75,
+0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73,
+0x61,0x6C,0x20,0x43,0x41,0x20,0x49,0x30,0x1E,0x17,0x0D,0x30,0x36,0x30,0x33,0x32,
+0x32,0x31,0x35,0x35,0x34,0x32,0x38,0x5A,0x17,0x0D,0x32,0x35,0x31,0x32,0x33,0x31,
+0x32,0x32,0x35,0x39,0x35,0x39,0x5A,0x30,0x79,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,
+0x04,0x06,0x13,0x02,0x44,0x45,0x31,0x1C,0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,
+0x13,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,
+0x47,0x6D,0x62,0x48,0x31,0x24,0x30,0x22,0x06,0x03,0x55,0x04,0x0B,0x13,0x1B,0x54,
+0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x55,0x6E,
+0x69,0x76,0x65,0x72,0x73,0x61,0x6C,0x20,0x43,0x41,0x31,0x26,0x30,0x24,0x06,0x03,
+0x55,0x04,0x03,0x13,0x1D,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,
+0x74,0x65,0x72,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61,0x6C,0x20,0x43,0x41,
+0x20,0x49,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,
+0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,
+0x01,0x01,0x00,0xA4,0x77,0x23,0x96,0x44,0xAF,0x90,0xF4,0x31,0xA7,0x10,0xF4,0x26,
+0x87,0x9C,0xF3,0x38,0xD9,0x0F,0x5E,0xDE,0xCF,0x41,0xE8,0x31,0xAD,0xC6,0x74,0x91,
+0x24,0x96,0x78,0x1E,0x09,0xA0,0x9B,0x9A,0x95,0x4A,0x4A,0xF5,0x62,0x7C,0x02,0xA8,
+0xCA,0xAC,0xFB,0x5A,0x04,0x76,0x39,0xDE,0x5F,0xF1,0xF9,0xB3,0xBF,0xF3,0x03,0x58,
+0x55,0xD2,0xAA,0xB7,0xE3,0x04,0x22,0xD1,0xF8,0x94,0xDA,0x22,0x08,0x00,0x8D,0xD3,
+0x7C,0x26,0x5D,0xCC,0x77,0x79,0xE7,0x2C,0x78,0x39,0xA8,0x26,0x73,0x0E,0xA2,0x5D,
+0x25,0x69,0x85,0x4F,0x55,0x0E,0x9A,0xEF,0xC6,0xB9,0x44,0xE1,0x57,0x3D,0xDF,0x1F,
+0x54,0x22,0xE5,0x6F,0x65,0xAA,0x33,0x84,0x3A,0xF3,0xCE,0x7A,0xBE,0x55,0x97,0xAE,
+0x8D,0x12,0x0F,0x14,0x33,0xE2,0x50,0x70,0xC3,0x49,0x87,0x13,0xBC,0x51,0xDE,0xD7,
+0x98,0x12,0x5A,0xEF,0x3A,0x83,0x33,0x92,0x06,0x75,0x8B,0x92,0x7C,0x12,0x68,0x7B,
+0x70,0x6A,0x0F,0xB5,0x9B,0xB6,0x77,0x5B,0x48,0x59,0x9D,0xE4,0xEF,0x5A,0xAD,0xF3,
+0xC1,0x9E,0xD4,0xD7,0x45,0x4E,0xCA,0x56,0x34,0x21,0xBC,0x3E,0x17,0x5B,0x6F,0x77,
+0x0C,0x48,0x01,0x43,0x29,0xB0,0xDD,0x3F,0x96,0x6E,0xE6,0x95,0xAA,0x0C,0xC0,0x20,
+0xB6,0xFD,0x3E,0x36,0x27,0x9C,0xE3,0x5C,0xCF,0x4E,0x81,0xDC,0x19,0xBB,0x91,0x90,
+0x7D,0xEC,0xE6,0x97,0x04,0x1E,0x93,0xCC,0x22,0x49,0xD7,0x97,0x86,0xB6,0x13,0x0A,
+0x3C,0x43,0x23,0x77,0x7E,0xF0,0xDC,0xE6,0xCD,0x24,0x1F,0x3B,0x83,0x9B,0x34,0x3A,
+0x83,0x34,0xE3,0x02,0x03,0x01,0x00,0x01,0xA3,0x63,0x30,0x61,0x30,0x1F,0x06,0x03,
+0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0x92,0xA4,0x75,0x2C,0xA4,0x9E,0xBE,
+0x81,0x44,0xEB,0x79,0xFC,0x8A,0xC5,0x95,0xA5,0xEB,0x10,0x75,0x73,0x30,0x0F,0x06,
+0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,
+0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x86,0x30,0x1D,
+0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x92,0xA4,0x75,0x2C,0xA4,0x9E,0xBE,
+0x81,0x44,0xEB,0x79,0xFC,0x8A,0xC5,0x95,0xA5,0xEB,0x10,0x75,0x73,0x30,0x0D,0x06,
+0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,
+0x00,0x28,0xD2,0xE0,0x86,0xD5,0xE6,0xF8,0x7B,0xF0,0x97,0xDC,0x22,0x6B,0x3B,0x95,
+0x14,0x56,0x0F,0x11,0x30,0xA5,0x9A,0x4F,0x3A,0xB0,0x3A,0xE0,0x06,0xCB,0x65,0xF5,
+0xED,0xC6,0x97,0x27,0xFE,0x25,0xF2,0x57,0xE6,0x5E,0x95,0x8C,0x3E,0x64,0x60,0x15,
+0x5A,0x7F,0x2F,0x0D,0x01,0xC5,0xB1,0x60,0xFD,0x45,0x35,0xCF,0xF0,0xB2,0xBF,0x06,
+0xD9,0xEF,0x5A,0xBE,0xB3,0x62,0x21,0xB4,0xD7,0xAB,0x35,0x7C,0x53,0x3E,0xA6,0x27,
+0xF1,0xA1,0x2D,0xDA,0x1A,0x23,0x9D,0xCC,0xDD,0xEC,0x3C,0x2D,0x9E,0x27,0x34,0x5D,
+0x0F,0xC2,0x36,0x79,0xBC,0xC9,0x4A,0x62,0x2D,0xED,0x6B,0xD9,0x7D,0x41,0x43,0x7C,
+0xB6,0xAA,0xCA,0xED,0x61,0xB1,0x37,0x82,0x15,0x09,0x1A,0x8A,0x16,0x30,0xD8,0xEC,
+0xC9,0xD6,0x47,0x72,0x78,0x4B,0x10,0x46,0x14,0x8E,0x5F,0x0E,0xAF,0xEC,0xC7,0x2F,
+0xAB,0x10,0xD7,0xB6,0xF1,0x6E,0xEC,0x86,0xB2,0xC2,0xE8,0x0D,0x92,0x73,0xDC,0xA2,
+0xF4,0x0F,0x3A,0xBF,0x61,0x23,0x10,0x89,0x9C,0x48,0x40,0x6E,0x70,0x00,0xB3,0xD3,
+0xBA,0x37,0x44,0x58,0x11,0x7A,0x02,0x6A,0x88,0xF0,0x37,0x34,0xF0,0x19,0xE9,0xAC,
+0xD4,0x65,0x73,0xF6,0x69,0x8C,0x64,0x94,0x3A,0x79,0x85,0x29,0xB0,0x16,0x2B,0x0C,
+0x82,0x3F,0x06,0x9C,0xC7,0xFD,0x10,0x2B,0x9E,0x0F,0x2C,0xB6,0x9E,0xE3,0x15,0xBF,
+0xD9,0x36,0x1C,0xBA,0x25,0x1A,0x52,0x3D,0x1A,0xEC,0x22,0x0C,0x1C,0xE0,0xA4,0xA2,
+0x3D,0xF0,0xE8,0x39,0xCF,0x81,0xC0,0x7B,0xED,0x5D,0x1F,0x6F,0xC5,0xD0,0x0B,0xD7,
+0x98,
+};
+
+
+/* subject:/C=DE/O=TC TrustCenter GmbH/OU=TC TrustCenter Universal CA/CN=TC TrustCenter Universal CA III */
+/* issuer :/C=DE/O=TC TrustCenter GmbH/OU=TC TrustCenter Universal CA/CN=TC TrustCenter Universal CA III */
+
+
+const unsigned char TC_TrustCenter_Universal_CA_III_certificate[997]={
+0x30,0x82,0x03,0xE1,0x30,0x82,0x02,0xC9,0xA0,0x03,0x02,0x01,0x02,0x02,0x0E,0x63,
+0x25,0x00,0x01,0x00,0x02,0x14,0x8D,0x33,0x15,0x02,0xE4,0x6C,0xF4,0x30,0x0D,0x06,
+0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x7B,0x31,0x0B,
+0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x44,0x45,0x31,0x1C,0x30,0x1A,0x06,
+0x03,0x55,0x04,0x0A,0x13,0x13,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,
+0x6E,0x74,0x65,0x72,0x20,0x47,0x6D,0x62,0x48,0x31,0x24,0x30,0x22,0x06,0x03,0x55,
+0x04,0x0B,0x13,0x1B,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,
+0x65,0x72,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61,0x6C,0x20,0x43,0x41,0x31,
+0x28,0x30,0x26,0x06,0x03,0x55,0x04,0x03,0x13,0x1F,0x54,0x43,0x20,0x54,0x72,0x75,
+0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73,
+0x61,0x6C,0x20,0x43,0x41,0x20,0x49,0x49,0x49,0x30,0x1E,0x17,0x0D,0x30,0x39,0x30,
+0x39,0x30,0x39,0x30,0x38,0x31,0x35,0x32,0x37,0x5A,0x17,0x0D,0x32,0x39,0x31,0x32,
+0x33,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x7B,0x31,0x0B,0x30,0x09,0x06,
+0x03,0x55,0x04,0x06,0x13,0x02,0x44,0x45,0x31,0x1C,0x30,0x1A,0x06,0x03,0x55,0x04,
+0x0A,0x13,0x13,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,
+0x72,0x20,0x47,0x6D,0x62,0x48,0x31,0x24,0x30,0x22,0x06,0x03,0x55,0x04,0x0B,0x13,
+0x1B,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,0x65,0x6E,0x74,0x65,0x72,0x20,
+0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61,0x6C,0x20,0x43,0x41,0x31,0x28,0x30,0x26,
+0x06,0x03,0x55,0x04,0x03,0x13,0x1F,0x54,0x43,0x20,0x54,0x72,0x75,0x73,0x74,0x43,
+0x65,0x6E,0x74,0x65,0x72,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61,0x6C,0x20,
+0x43,0x41,0x20,0x49,0x49,0x49,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,
+0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,
+0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xC2,0xDA,0x9C,0x62,0xB0,0xB9,0x71,0x12,0xB0,
+0x0B,0xC8,0x1A,0x57,0xB2,0xAE,0x83,0x14,0x99,0xB3,0x34,0x4B,0x9B,0x90,0xA2,0xC5,
+0xE7,0xE7,0x2F,0x02,0xA0,0x4D,0x2D,0xA4,0xFA,0x85,0xDA,0x9B,0x25,0x85,0x2D,0x40,
+0x28,0x20,0x6D,0xEA,0xE0,0xBD,0xB1,0x48,0x83,0x22,0x29,0x44,0x9F,0x4E,0x83,0xEE,
+0x35,0x51,0x13,0x73,0x74,0xD5,0xBC,0xF2,0x30,0x66,0x94,0x53,0xC0,0x40,0x36,0x2F,
+0x0C,0x84,0x65,0xCE,0x0F,0x6E,0xC2,0x58,0x93,0xE8,0x2C,0x0B,0x3A,0xE9,0xC1,0x8E,
+0xFB,0xF2,0x6B,0xCA,0x3C,0xE2,0x9C,0x4E,0x8E,0xE4,0xF9,0x7D,0xD3,0x27,0x9F,0x1B,
+0xD5,0x67,0x78,0x87,0x2D,0x7F,0x0B,0x47,0xB3,0xC7,0xE8,0xC9,0x48,0x7C,0xAF,0x2F,
+0xCC,0x0A,0xD9,0x41,0xEF,0x9F,0xFE,0x9A,0xE1,0xB2,0xAE,0xF9,0x53,0xB5,0xE5,0xE9,
+0x46,0x9F,0x60,0xE3,0xDF,0x8D,0xD3,0x7F,0xFB,0x96,0x7E,0xB3,0xB5,0x72,0xF8,0x4B,
+0xAD,0x08,0x79,0xCD,0x69,0x89,0x40,0x27,0xF5,0x2A,0xC1,0xAD,0x43,0xEC,0xA4,0x53,
+0xC8,0x61,0xB6,0xF7,0xD2,0x79,0x2A,0x67,0x18,0x76,0x48,0x6D,0x5B,0x25,0x01,0xD1,
+0x26,0xC5,0xB7,0x57,0x69,0x23,0x15,0x5B,0x61,0x8A,0xAD,0xF0,0x1B,0x2D,0xD9,0xAF,
+0x5C,0xF1,0x26,0x90,0x69,0xA9,0xD5,0x0C,0x40,0xF5,0x33,0x80,0x43,0x8F,0x9C,0xA3,
+0x76,0x2A,0x45,0xB4,0xAF,0xBF,0x7F,0x3E,0x87,0x3F,0x76,0xC5,0xCD,0x2A,0xDE,0x20,
+0xC5,0x16,0x58,0xCB,0xF9,0x1B,0xF5,0x0F,0xCB,0x0D,0x11,0x52,0x64,0xB8,0xD2,0x76,
+0x62,0x77,0x83,0xF1,0x58,0x9F,0xFF,0x02,0x03,0x01,0x00,0x01,0xA3,0x63,0x30,0x61,
+0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0x56,0xE7,0xE1,
+0x5B,0x25,0x43,0x80,0xE0,0xF6,0x8C,0xE1,0x71,0xBC,0x8E,0xE5,0x80,0x2F,0xC4,0x48,
+0xE2,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,
+0x01,0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,
+0x01,0x06,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x56,0xE7,0xE1,
+0x5B,0x25,0x43,0x80,0xE0,0xF6,0x8C,0xE1,0x71,0xBC,0x8E,0xE5,0x80,0x2F,0xC4,0x48,
+0xE2,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,
+0x03,0x82,0x01,0x01,0x00,0x83,0xC7,0xAF,0xEA,0x7F,0x4D,0x0A,0x3C,0x39,0xB1,0x68,
+0xBE,0x7B,0x6D,0x89,0x2E,0xE9,0xB3,0x09,0xE7,0x18,0x57,0x8D,0x85,0x9A,0x17,0xF3,
+0x76,0x42,0x50,0x13,0x0F,0xC7,0x90,0x6F,0x33,0xAD,0xC5,0x49,0x60,0x2B,0x6C,0x49,
+0x58,0x19,0xD4,0xE2,0xBE,0xB7,0xBF,0xAB,0x49,0xBC,0x94,0xC8,0xAB,0xBE,0x28,0x6C,
+0x16,0x68,0xE0,0xC8,0x97,0x46,0x20,0xA0,0x68,0x67,0x60,0x88,0x39,0x20,0x51,0xD8,
+0x68,0x01,0x11,0xCE,0xA7,0xF6,0x11,0x07,0xF6,0xEC,0xEC,0xAC,0x1A,0x1F,0xB2,0x66,
+0x6E,0x56,0x67,0x60,0x7A,0x74,0x5E,0xC0,0x6D,0x97,0x36,0xAE,0xB5,0x0D,0x5D,0x66,
+0x73,0xC0,0x25,0x32,0x45,0xD8,0x4A,0x06,0x07,0x8F,0xC4,0xB7,0x07,0xB1,0x4D,0x06,
+0x0D,0xE1,0xA5,0xEB,0xF4,0x75,0xCA,0xBA,0x9C,0xD0,0xBD,0xB3,0xD3,0x32,0x24,0x4C,
+0xEE,0x7E,0xE2,0x76,0x04,0x4B,0x49,0x53,0xD8,0xF2,0xE9,0x54,0x33,0xFC,0xE5,0x71,
+0x1F,0x3D,0x14,0x5C,0x96,0x4B,0xF1,0x3A,0xF2,0x00,0xBB,0x6C,0xB4,0xFA,0x96,0x55,
+0x08,0x88,0x09,0xC1,0xCC,0x91,0x19,0x29,0xB0,0x20,0x2D,0xFF,0xCB,0x38,0xA4,0x40,
+0xE1,0x17,0xBE,0x79,0x61,0x80,0xFF,0x07,0x03,0x86,0x4C,0x4E,0x7B,0x06,0x9F,0x11,
+0x86,0x8D,0x89,0xEE,0x27,0xC4,0xDB,0xE2,0xBC,0x19,0x8E,0x0B,0xC3,0xC3,0x13,0xC7,
+0x2D,0x03,0x63,0x3B,0xD3,0xE8,0xE4,0xA2,0x2A,0xC2,0x82,0x08,0x94,0x16,0x54,0xF0,
+0xEF,0x1F,0x27,0x90,0x25,0xB8,0x0D,0x0E,0x28,0x1B,0x47,0x77,0x47,0xBD,0x1C,0xA8,
+0x25,0xF1,0x94,0xB4,0x66,
+};
+
+
+/* subject:/C=ZA/ST=Western Cape/L=Cape Town/O=Thawte Consulting cc/OU=Certification Services Division/CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com */
+/* issuer :/C=ZA/ST=Western Cape/L=Cape Town/O=Thawte Consulting cc/OU=Certification Services Division/CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com */
+
+
+const unsigned char Thawte_Premium_Server_CA_certificate[811]={
+0x30,0x82,0x03,0x27,0x30,0x82,0x02,0x90,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,0x30,
+0x81,0xCE,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x5A,0x41,0x31,
+0x15,0x30,0x13,0x06,0x03,0x55,0x04,0x08,0x13,0x0C,0x57,0x65,0x73,0x74,0x65,0x72,
+0x6E,0x20,0x43,0x61,0x70,0x65,0x31,0x12,0x30,0x10,0x06,0x03,0x55,0x04,0x07,0x13,
+0x09,0x43,0x61,0x70,0x65,0x20,0x54,0x6F,0x77,0x6E,0x31,0x1D,0x30,0x1B,0x06,0x03,
+0x55,0x04,0x0A,0x13,0x14,0x54,0x68,0x61,0x77,0x74,0x65,0x20,0x43,0x6F,0x6E,0x73,
+0x75,0x6C,0x74,0x69,0x6E,0x67,0x20,0x63,0x63,0x31,0x28,0x30,0x26,0x06,0x03,0x55,
+0x04,0x0B,0x13,0x1F,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,
+0x6E,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x20,0x44,0x69,0x76,0x69,0x73,
+0x69,0x6F,0x6E,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x54,0x68,
+0x61,0x77,0x74,0x65,0x20,0x50,0x72,0x65,0x6D,0x69,0x75,0x6D,0x20,0x53,0x65,0x72,
+0x76,0x65,0x72,0x20,0x43,0x41,0x31,0x28,0x30,0x26,0x06,0x09,0x2A,0x86,0x48,0x86,
+0xF7,0x0D,0x01,0x09,0x01,0x16,0x19,0x70,0x72,0x65,0x6D,0x69,0x75,0x6D,0x2D,0x73,
+0x65,0x72,0x76,0x65,0x72,0x40,0x74,0x68,0x61,0x77,0x74,0x65,0x2E,0x63,0x6F,0x6D,
+0x30,0x1E,0x17,0x0D,0x39,0x36,0x30,0x38,0x30,0x31,0x30,0x30,0x30,0x30,0x30,0x30,
+0x5A,0x17,0x0D,0x32,0x30,0x31,0x32,0x33,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,
+0x30,0x81,0xCE,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x5A,0x41,
+0x31,0x15,0x30,0x13,0x06,0x03,0x55,0x04,0x08,0x13,0x0C,0x57,0x65,0x73,0x74,0x65,
+0x72,0x6E,0x20,0x43,0x61,0x70,0x65,0x31,0x12,0x30,0x10,0x06,0x03,0x55,0x04,0x07,
+0x13,0x09,0x43,0x61,0x70,0x65,0x20,0x54,0x6F,0x77,0x6E,0x31,0x1D,0x30,0x1B,0x06,
+0x03,0x55,0x04,0x0A,0x13,0x14,0x54,0x68,0x61,0x77,0x74,0x65,0x20,0x43,0x6F,0x6E,
+0x73,0x75,0x6C,0x74,0x69,0x6E,0x67,0x20,0x63,0x63,0x31,0x28,0x30,0x26,0x06,0x03,
+0x55,0x04,0x0B,0x13,0x1F,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,
+0x6F,0x6E,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x20,0x44,0x69,0x76,0x69,
+0x73,0x69,0x6F,0x6E,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x54,
+0x68,0x61,0x77,0x74,0x65,0x20,0x50,0x72,0x65,0x6D,0x69,0x75,0x6D,0x20,0x53,0x65,
+0x72,0x76,0x65,0x72,0x20,0x43,0x41,0x31,0x28,0x30,0x26,0x06,0x09,0x2A,0x86,0x48,
+0x86,0xF7,0x0D,0x01,0x09,0x01,0x16,0x19,0x70,0x72,0x65,0x6D,0x69,0x75,0x6D,0x2D,
+0x73,0x65,0x72,0x76,0x65,0x72,0x40,0x74,0x68,0x61,0x77,0x74,0x65,0x2E,0x63,0x6F,
+0x6D,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,
+0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89,0x02,0x81,0x81,0x00,0xD2,0x36,
+0x36,0x6A,0x8B,0xD7,0xC2,0x5B,0x9E,0xDA,0x81,0x41,0x62,0x8F,0x38,0xEE,0x49,0x04,
+0x55,0xD6,0xD0,0xEF,0x1C,0x1B,0x95,0x16,0x47,0xEF,0x18,0x48,0x35,0x3A,0x52,0xF4,
+0x2B,0x6A,0x06,0x8F,0x3B,0x2F,0xEA,0x56,0xE3,0xAF,0x86,0x8D,0x9E,0x17,0xF7,0x9E,
+0xB4,0x65,0x75,0x02,0x4D,0xEF,0xCB,0x09,0xA2,0x21,0x51,0xD8,0x9B,0xD0,0x67,0xD0,
+0xBA,0x0D,0x92,0x06,0x14,0x73,0xD4,0x93,0xCB,0x97,0x2A,0x00,0x9C,0x5C,0x4E,0x0C,
+0xBC,0xFA,0x15,0x52,0xFC,0xF2,0x44,0x6E,0xDA,0x11,0x4A,0x6E,0x08,0x9F,0x2F,0x2D,
+0xE3,0xF9,0xAA,0x3A,0x86,0x73,0xB6,0x46,0x53,0x58,0xC8,0x89,0x05,0xBD,0x83,0x11,
+0xB8,0x73,0x3F,0xAA,0x07,0x8D,0xF4,0x42,0x4D,0xE7,0x40,0x9D,0x1C,0x37,0x02,0x03,
+0x01,0x00,0x01,0xA3,0x13,0x30,0x11,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,
+0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,
+0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,0x03,0x81,0x81,0x00,0x26,0x48,0x2C,0x16,0xC2,
+0x58,0xFA,0xE8,0x16,0x74,0x0C,0xAA,0xAA,0x5F,0x54,0x3F,0xF2,0xD7,0xC9,0x78,0x60,
+0x5E,0x5E,0x6E,0x37,0x63,0x22,0x77,0x36,0x7E,0xB2,0x17,0xC4,0x34,0xB9,0xF5,0x08,
+0x85,0xFC,0xC9,0x01,0x38,0xFF,0x4D,0xBE,0xF2,0x16,0x42,0x43,0xE7,0xBB,0x5A,0x46,
+0xFB,0xC1,0xC6,0x11,0x1F,0xF1,0x4A,0xB0,0x28,0x46,0xC9,0xC3,0xC4,0x42,0x7D,0xBC,
+0xFA,0xAB,0x59,0x6E,0xD5,0xB7,0x51,0x88,0x11,0xE3,0xA4,0x85,0x19,0x6B,0x82,0x4C,
+0xA4,0x0C,0x12,0xAD,0xE9,0xA4,0xAE,0x3F,0xF1,0xC3,0x49,0x65,0x9A,0x8C,0xC5,0xC8,
+0x3E,0x25,0xB7,0x94,0x99,0xBB,0x92,0x32,0x71,0x07,0xF0,0x86,0x5E,0xED,0x50,0x27,
+0xA6,0x0D,0xA6,0x23,0xF9,0xBB,0xCB,0xA6,0x07,0x14,0x42,
+};
+
+
+/* subject:/C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c) 2006 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA */
+/* issuer :/C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c) 2006 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA */
+
+
+const unsigned char thawte_Primary_Root_CA_certificate[1060]={
+0x30,0x82,0x04,0x20,0x30,0x82,0x03,0x08,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x34,
+0x4E,0xD5,0x57,0x20,0xD5,0xED,0xEC,0x49,0xF4,0x2F,0xCE,0x37,0xDB,0x2B,0x6D,0x30,
+0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81,
+0xA9,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15,
+0x30,0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x74,0x68,0x61,0x77,0x74,0x65,0x2C,
+0x20,0x49,0x6E,0x63,0x2E,0x31,0x28,0x30,0x26,0x06,0x03,0x55,0x04,0x0B,0x13,0x1F,
+0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x53,0x65,
+0x72,0x76,0x69,0x63,0x65,0x73,0x20,0x44,0x69,0x76,0x69,0x73,0x69,0x6F,0x6E,0x31,
+0x38,0x30,0x36,0x06,0x03,0x55,0x04,0x0B,0x13,0x2F,0x28,0x63,0x29,0x20,0x32,0x30,
+0x30,0x36,0x20,0x74,0x68,0x61,0x77,0x74,0x65,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,
+0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,
+0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,
+0x04,0x03,0x13,0x16,0x74,0x68,0x61,0x77,0x74,0x65,0x20,0x50,0x72,0x69,0x6D,0x61,
+0x72,0x79,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x30,0x1E,0x17,0x0D,0x30,0x36,
+0x31,0x31,0x31,0x37,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x36,0x30,
+0x37,0x31,0x36,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0xA9,0x31,0x0B,0x30,
+0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15,0x30,0x13,0x06,0x03,
+0x55,0x04,0x0A,0x13,0x0C,0x74,0x68,0x61,0x77,0x74,0x65,0x2C,0x20,0x49,0x6E,0x63,
+0x2E,0x31,0x28,0x30,0x26,0x06,0x03,0x55,0x04,0x0B,0x13,0x1F,0x43,0x65,0x72,0x74,
+0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x53,0x65,0x72,0x76,0x69,0x63,
+0x65,0x73,0x20,0x44,0x69,0x76,0x69,0x73,0x69,0x6F,0x6E,0x31,0x38,0x30,0x36,0x06,
+0x03,0x55,0x04,0x0B,0x13,0x2F,0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x36,0x20,0x74,
+0x68,0x61,0x77,0x74,0x65,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,
+0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,
+0x20,0x6F,0x6E,0x6C,0x79,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x03,0x13,0x16,
+0x74,0x68,0x61,0x77,0x74,0x65,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x52,
+0x6F,0x6F,0x74,0x20,0x43,0x41,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,
+0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,
+0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xAC,0xA0,0xF0,0xFB,0x80,0x59,0xD4,0x9C,0xC7,
+0xA4,0xCF,0x9D,0xA1,0x59,0x73,0x09,0x10,0x45,0x0C,0x0D,0x2C,0x6E,0x68,0xF1,0x6C,
+0x5B,0x48,0x68,0x49,0x59,0x37,0xFC,0x0B,0x33,0x19,0xC2,0x77,0x7F,0xCC,0x10,0x2D,
+0x95,0x34,0x1C,0xE6,0xEB,0x4D,0x09,0xA7,0x1C,0xD2,0xB8,0xC9,0x97,0x36,0x02,0xB7,
+0x89,0xD4,0x24,0x5F,0x06,0xC0,0xCC,0x44,0x94,0x94,0x8D,0x02,0x62,0x6F,0xEB,0x5A,
+0xDD,0x11,0x8D,0x28,0x9A,0x5C,0x84,0x90,0x10,0x7A,0x0D,0xBD,0x74,0x66,0x2F,0x6A,
+0x38,0xA0,0xE2,0xD5,0x54,0x44,0xEB,0x1D,0x07,0x9F,0x07,0xBA,0x6F,0xEE,0xE9,0xFD,
+0x4E,0x0B,0x29,0xF5,0x3E,0x84,0xA0,0x01,0xF1,0x9C,0xAB,0xF8,0x1C,0x7E,0x89,0xA4,
+0xE8,0xA1,0xD8,0x71,0x65,0x0D,0xA3,0x51,0x7B,0xEE,0xBC,0xD2,0x22,0x60,0x0D,0xB9,
+0x5B,0x9D,0xDF,0xBA,0xFC,0x51,0x5B,0x0B,0xAF,0x98,0xB2,0xE9,0x2E,0xE9,0x04,0xE8,
+0x62,0x87,0xDE,0x2B,0xC8,0xD7,0x4E,0xC1,0x4C,0x64,0x1E,0xDD,0xCF,0x87,0x58,0xBA,
+0x4A,0x4F,0xCA,0x68,0x07,0x1D,0x1C,0x9D,0x4A,0xC6,0xD5,0x2F,0x91,0xCC,0x7C,0x71,
+0x72,0x1C,0xC5,0xC0,0x67,0xEB,0x32,0xFD,0xC9,0x92,0x5C,0x94,0xDA,0x85,0xC0,0x9B,
+0xBF,0x53,0x7D,0x2B,0x09,0xF4,0x8C,0x9D,0x91,0x1F,0x97,0x6A,0x52,0xCB,0xDE,0x09,
+0x36,0xA4,0x77,0xD8,0x7B,0x87,0x50,0x44,0xD5,0x3E,0x6E,0x29,0x69,0xFB,0x39,0x49,
+0x26,0x1E,0x09,0xA5,0x80,0x7B,0x40,0x2D,0xEB,0xE8,0x27,0x85,0xC9,0xFE,0x61,0xFD,
+0x7E,0xE6,0x7C,0x97,0x1D,0xD5,0x9D,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40,
+0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,
+0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,
+0x06,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x7B,0x5B,0x45,0xCF,
+0xAF,0xCE,0xCB,0x7A,0xFD,0x31,0x92,0x1A,0x6A,0xB6,0xF3,0x46,0xEB,0x57,0x48,0x50,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,
+0x82,0x01,0x01,0x00,0x79,0x11,0xC0,0x4B,0xB3,0x91,0xB6,0xFC,0xF0,0xE9,0x67,0xD4,
+0x0D,0x6E,0x45,0xBE,0x55,0xE8,0x93,0xD2,0xCE,0x03,0x3F,0xED,0xDA,0x25,0xB0,0x1D,
+0x57,0xCB,0x1E,0x3A,0x76,0xA0,0x4C,0xEC,0x50,0x76,0xE8,0x64,0x72,0x0C,0xA4,0xA9,
+0xF1,0xB8,0x8B,0xD6,0xD6,0x87,0x84,0xBB,0x32,0xE5,0x41,0x11,0xC0,0x77,0xD9,0xB3,
+0x60,0x9D,0xEB,0x1B,0xD5,0xD1,0x6E,0x44,0x44,0xA9,0xA6,0x01,0xEC,0x55,0x62,0x1D,
+0x77,0xB8,0x5C,0x8E,0x48,0x49,0x7C,0x9C,0x3B,0x57,0x11,0xAC,0xAD,0x73,0x37,0x8E,
+0x2F,0x78,0x5C,0x90,0x68,0x47,0xD9,0x60,0x60,0xE6,0xFC,0x07,0x3D,0x22,0x20,0x17,
+0xC4,0xF7,0x16,0xE9,0xC4,0xD8,0x72,0xF9,0xC8,0x73,0x7C,0xDF,0x16,0x2F,0x15,0xA9,
+0x3E,0xFD,0x6A,0x27,0xB6,0xA1,0xEB,0x5A,0xBA,0x98,0x1F,0xD5,0xE3,0x4D,0x64,0x0A,
+0x9D,0x13,0xC8,0x61,0xBA,0xF5,0x39,0x1C,0x87,0xBA,0xB8,0xBD,0x7B,0x22,0x7F,0xF6,
+0xFE,0xAC,0x40,0x79,0xE5,0xAC,0x10,0x6F,0x3D,0x8F,0x1B,0x79,0x76,0x8B,0xC4,0x37,
+0xB3,0x21,0x18,0x84,0xE5,0x36,0x00,0xEB,0x63,0x20,0x99,0xB9,0xE9,0xFE,0x33,0x04,
+0xBB,0x41,0xC8,0xC1,0x02,0xF9,0x44,0x63,0x20,0x9E,0x81,0xCE,0x42,0xD3,0xD6,0x3F,
+0x2C,0x76,0xD3,0x63,0x9C,0x59,0xDD,0x8F,0xA6,0xE1,0x0E,0xA0,0x2E,0x41,0xF7,0x2E,
+0x95,0x47,0xCF,0xBC,0xFD,0x33,0xF3,0xF6,0x0B,0x61,0x7E,0x7E,0x91,0x2B,0x81,0x47,
+0xC2,0x27,0x30,0xEE,0xA7,0x10,0x5D,0x37,0x8F,0x5C,0x39,0x2B,0xE4,0x04,0xF0,0x7B,
+0x8D,0x56,0x8C,0x68,
+};
+
+
+/* subject:/C=US/O=thawte, Inc./OU=(c) 2007 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA - G2 */
+/* issuer :/C=US/O=thawte, Inc./OU=(c) 2007 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA - G2 */
+
+
+const unsigned char thawte_Primary_Root_CA___G2_certificate[652]={
+0x30,0x82,0x02,0x88,0x30,0x82,0x02,0x0D,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x35,
+0xFC,0x26,0x5C,0xD9,0x84,0x4F,0xC9,0x3D,0x26,0x3D,0x57,0x9B,0xAE,0xD7,0x56,0x30,
+0x0A,0x06,0x08,0x2A,0x86,0x48,0xCE,0x3D,0x04,0x03,0x03,0x30,0x81,0x84,0x31,0x0B,
+0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15,0x30,0x13,0x06,
+0x03,0x55,0x04,0x0A,0x13,0x0C,0x74,0x68,0x61,0x77,0x74,0x65,0x2C,0x20,0x49,0x6E,
+0x63,0x2E,0x31,0x38,0x30,0x36,0x06,0x03,0x55,0x04,0x0B,0x13,0x2F,0x28,0x63,0x29,
+0x20,0x32,0x30,0x30,0x37,0x20,0x74,0x68,0x61,0x77,0x74,0x65,0x2C,0x20,0x49,0x6E,
+0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,
+0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x24,0x30,0x22,
+0x06,0x03,0x55,0x04,0x03,0x13,0x1B,0x74,0x68,0x61,0x77,0x74,0x65,0x20,0x50,0x72,
+0x69,0x6D,0x61,0x72,0x79,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x20,0x2D,0x20,
+0x47,0x32,0x30,0x1E,0x17,0x0D,0x30,0x37,0x31,0x31,0x30,0x35,0x30,0x30,0x30,0x30,
+0x30,0x30,0x5A,0x17,0x0D,0x33,0x38,0x30,0x31,0x31,0x38,0x32,0x33,0x35,0x39,0x35,
+0x39,0x5A,0x30,0x81,0x84,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,
+0x55,0x53,0x31,0x15,0x30,0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x74,0x68,0x61,
+0x77,0x74,0x65,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x38,0x30,0x36,0x06,0x03,0x55,
+0x04,0x0B,0x13,0x2F,0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x37,0x20,0x74,0x68,0x61,
+0x77,0x74,0x65,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,
+0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,
+0x6E,0x6C,0x79,0x31,0x24,0x30,0x22,0x06,0x03,0x55,0x04,0x03,0x13,0x1B,0x74,0x68,
+0x61,0x77,0x74,0x65,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x52,0x6F,0x6F,
+0x74,0x20,0x43,0x41,0x20,0x2D,0x20,0x47,0x32,0x30,0x76,0x30,0x10,0x06,0x07,0x2A,
+0x86,0x48,0xCE,0x3D,0x02,0x01,0x06,0x05,0x2B,0x81,0x04,0x00,0x22,0x03,0x62,0x00,
+0x04,0xA2,0xD5,0x9C,0x82,0x7B,0x95,0x9D,0xF1,0x52,0x78,0x87,0xFE,0x8A,0x16,0xBF,
+0x05,0xE6,0xDF,0xA3,0x02,0x4F,0x0D,0x07,0xC6,0x00,0x51,0xBA,0x0C,0x02,0x52,0x2D,
+0x22,0xA4,0x42,0x39,0xC4,0xFE,0x8F,0xEA,0xC9,0xC1,0xBE,0xD4,0x4D,0xFF,0x9F,0x7A,
+0x9E,0xE2,0xB1,0x7C,0x9A,0xAD,0xA7,0x86,0x09,0x73,0x87,0xD1,0xE7,0x9A,0xE3,0x7A,
+0xA5,0xAA,0x6E,0xFB,0xBA,0xB3,0x70,0xC0,0x67,0x88,0xA2,0x35,0xD4,0xA3,0x9A,0xB1,
+0xFD,0xAD,0xC2,0xEF,0x31,0xFA,0xA8,0xB9,0xF3,0xFB,0x08,0xC6,0x91,0xD1,0xFB,0x29,
+0x95,0xA3,0x42,0x30,0x40,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,
+0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,
+0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,
+0x14,0x9A,0xD8,0x00,0x30,0x00,0xE7,0x6B,0x7F,0x85,0x18,0xEE,0x8B,0xB6,0xCE,0x8A,
+0x0C,0xF8,0x11,0xE1,0xBB,0x30,0x0A,0x06,0x08,0x2A,0x86,0x48,0xCE,0x3D,0x04,0x03,
+0x03,0x03,0x69,0x00,0x30,0x66,0x02,0x31,0x00,0xDD,0xF8,0xE0,0x57,0x47,0x5B,0xA7,
+0xE6,0x0A,0xC3,0xBD,0xF5,0x80,0x8A,0x97,0x35,0x0D,0x1B,0x89,0x3C,0x54,0x86,0x77,
+0x28,0xCA,0xA1,0xF4,0x79,0xDE,0xB5,0xE6,0x38,0xB0,0xF0,0x65,0x70,0x8C,0x7F,0x02,
+0x54,0xC2,0xBF,0xFF,0xD8,0xA1,0x3E,0xD9,0xCF,0x02,0x31,0x00,0xC4,0x8D,0x94,0xFC,
+0xDC,0x53,0xD2,0xDC,0x9D,0x78,0x16,0x1F,0x15,0x33,0x23,0x53,0x52,0xE3,0x5A,0x31,
+0x5D,0x9D,0xCA,0xAE,0xBD,0x13,0x29,0x44,0x0D,0x27,0x5B,0xA8,0xE7,0x68,0x9C,0x12,
+0xF7,0x58,0x3F,0x2E,0x72,0x02,0x57,0xA3,0x8F,0xA1,0x14,0x2E,
+};
+
+
+/* subject:/C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c) 2008 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA - G3 */
+/* issuer :/C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c) 2008 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA - G3 */
+
+
+const unsigned char thawte_Primary_Root_CA___G3_certificate[1070]={
+0x30,0x82,0x04,0x2A,0x30,0x82,0x03,0x12,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x60,
+0x01,0x97,0xB7,0x46,0xA7,0xEA,0xB4,0xB4,0x9A,0xD6,0x4B,0x2F,0xF7,0x90,0xFB,0x30,
+0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30,0x81,
+0xAE,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x15,
+0x30,0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x74,0x68,0x61,0x77,0x74,0x65,0x2C,
+0x20,0x49,0x6E,0x63,0x2E,0x31,0x28,0x30,0x26,0x06,0x03,0x55,0x04,0x0B,0x13,0x1F,
+0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x53,0x65,
+0x72,0x76,0x69,0x63,0x65,0x73,0x20,0x44,0x69,0x76,0x69,0x73,0x69,0x6F,0x6E,0x31,
+0x38,0x30,0x36,0x06,0x03,0x55,0x04,0x0B,0x13,0x2F,0x28,0x63,0x29,0x20,0x32,0x30,
+0x30,0x38,0x20,0x74,0x68,0x61,0x77,0x74,0x65,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,
+0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,
+0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x24,0x30,0x22,0x06,0x03,0x55,
+0x04,0x03,0x13,0x1B,0x74,0x68,0x61,0x77,0x74,0x65,0x20,0x50,0x72,0x69,0x6D,0x61,
+0x72,0x79,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x20,0x2D,0x20,0x47,0x33,0x30,
+0x1E,0x17,0x0D,0x30,0x38,0x30,0x34,0x30,0x32,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,
+0x17,0x0D,0x33,0x37,0x31,0x32,0x30,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,
+0x81,0xAE,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,
+0x15,0x30,0x13,0x06,0x03,0x55,0x04,0x0A,0x13,0x0C,0x74,0x68,0x61,0x77,0x74,0x65,
+0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x28,0x30,0x26,0x06,0x03,0x55,0x04,0x0B,0x13,
+0x1F,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x53,
+0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x20,0x44,0x69,0x76,0x69,0x73,0x69,0x6F,0x6E,
+0x31,0x38,0x30,0x36,0x06,0x03,0x55,0x04,0x0B,0x13,0x2F,0x28,0x63,0x29,0x20,0x32,
+0x30,0x30,0x38,0x20,0x74,0x68,0x61,0x77,0x74,0x65,0x2C,0x20,0x49,0x6E,0x63,0x2E,
+0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,
+0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x24,0x30,0x22,0x06,0x03,
+0x55,0x04,0x03,0x13,0x1B,0x74,0x68,0x61,0x77,0x74,0x65,0x20,0x50,0x72,0x69,0x6D,
+0x61,0x72,0x79,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x41,0x20,0x2D,0x20,0x47,0x33,
+0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,
+0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,
+0x00,0xB2,0xBF,0x27,0x2C,0xFB,0xDB,0xD8,0x5B,0xDD,0x78,0x7B,0x1B,0x9E,0x77,0x66,
+0x81,0xCB,0x3E,0xBC,0x7C,0xAE,0xF3,0xA6,0x27,0x9A,0x34,0xA3,0x68,0x31,0x71,0x38,
+0x33,0x62,0xE4,0xF3,0x71,0x66,0x79,0xB1,0xA9,0x65,0xA3,0xA5,0x8B,0xD5,0x8F,0x60,
+0x2D,0x3F,0x42,0xCC,0xAA,0x6B,0x32,0xC0,0x23,0xCB,0x2C,0x41,0xDD,0xE4,0xDF,0xFC,
+0x61,0x9C,0xE2,0x73,0xB2,0x22,0x95,0x11,0x43,0x18,0x5F,0xC4,0xB6,0x1F,0x57,0x6C,
+0x0A,0x05,0x58,0x22,0xC8,0x36,0x4C,0x3A,0x7C,0xA5,0xD1,0xCF,0x86,0xAF,0x88,0xA7,
+0x44,0x02,0x13,0x74,0x71,0x73,0x0A,0x42,0x59,0x02,0xF8,0x1B,0x14,0x6B,0x42,0xDF,
+0x6F,0x5F,0xBA,0x6B,0x82,0xA2,0x9D,0x5B,0xE7,0x4A,0xBD,0x1E,0x01,0x72,0xDB,0x4B,
+0x74,0xE8,0x3B,0x7F,0x7F,0x7D,0x1F,0x04,0xB4,0x26,0x9B,0xE0,0xB4,0x5A,0xAC,0x47,
+0x3D,0x55,0xB8,0xD7,0xB0,0x26,0x52,0x28,0x01,0x31,0x40,0x66,0xD8,0xD9,0x24,0xBD,
+0xF6,0x2A,0xD8,0xEC,0x21,0x49,0x5C,0x9B,0xF6,0x7A,0xE9,0x7F,0x55,0x35,0x7E,0x96,
+0x6B,0x8D,0x93,0x93,0x27,0xCB,0x92,0xBB,0xEA,0xAC,0x40,0xC0,0x9F,0xC2,0xF8,0x80,
+0xCF,0x5D,0xF4,0x5A,0xDC,0xCE,0x74,0x86,0xA6,0x3E,0x6C,0x0B,0x53,0xCA,0xBD,0x92,
+0xCE,0x19,0x06,0x72,0xE6,0x0C,0x5C,0x38,0x69,0xC7,0x04,0xD6,0xBC,0x6C,0xCE,0x5B,
+0xF6,0xF7,0x68,0x9C,0xDC,0x25,0x15,0x48,0x88,0xA1,0xE9,0xA9,0xF8,0x98,0x9C,0xE0,
+0xF3,0xD5,0x31,0x28,0x61,0x11,0x6C,0x67,0x96,0x8D,0x39,0x99,0xCB,0xC2,0x45,0x24,
+0x39,0x02,0x03,0x01,0x00,0x01,0xA3,0x42,0x30,0x40,0x30,0x0F,0x06,0x03,0x55,0x1D,
+0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55,
+0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x1D,0x06,0x03,0x55,
+0x1D,0x0E,0x04,0x16,0x04,0x14,0xAD,0x6C,0xAA,0x94,0x60,0x9C,0xED,0xE4,0xFF,0xFA,
+0x3E,0x0A,0x74,0x2B,0x63,0x03,0xF7,0xB6,0x59,0xBF,0x30,0x0D,0x06,0x09,0x2A,0x86,
+0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x1A,0x40,
+0xD8,0x95,0x65,0xAC,0x09,0x92,0x89,0xC6,0x39,0xF4,0x10,0xE5,0xA9,0x0E,0x66,0x53,
+0x5D,0x78,0xDE,0xFA,0x24,0x91,0xBB,0xE7,0x44,0x51,0xDF,0xC6,0x16,0x34,0x0A,0xEF,
+0x6A,0x44,0x51,0xEA,0x2B,0x07,0x8A,0x03,0x7A,0xC3,0xEB,0x3F,0x0A,0x2C,0x52,0x16,
+0xA0,0x2B,0x43,0xB9,0x25,0x90,0x3F,0x70,0xA9,0x33,0x25,0x6D,0x45,0x1A,0x28,0x3B,
+0x27,0xCF,0xAA,0xC3,0x29,0x42,0x1B,0xDF,0x3B,0x4C,0xC0,0x33,0x34,0x5B,0x41,0x88,
+0xBF,0x6B,0x2B,0x65,0xAF,0x28,0xEF,0xB2,0xF5,0xC3,0xAA,0x66,0xCE,0x7B,0x56,0xEE,
+0xB7,0xC8,0xCB,0x67,0xC1,0xC9,0x9C,0x1A,0x18,0xB8,0xC4,0xC3,0x49,0x03,0xF1,0x60,
+0x0E,0x50,0xCD,0x46,0xC5,0xF3,0x77,0x79,0xF7,0xB6,0x15,0xE0,0x38,0xDB,0xC7,0x2F,
+0x28,0xA0,0x0C,0x3F,0x77,0x26,0x74,0xD9,0x25,0x12,0xDA,0x31,0xDA,0x1A,0x1E,0xDC,
+0x29,0x41,0x91,0x22,0x3C,0x69,0xA7,0xBB,0x02,0xF2,0xB6,0x5C,0x27,0x03,0x89,0xF4,
+0x06,0xEA,0x9B,0xE4,0x72,0x82,0xE3,0xA1,0x09,0xC1,0xE9,0x00,0x19,0xD3,0x3E,0xD4,
+0x70,0x6B,0xBA,0x71,0xA6,0xAA,0x58,0xAE,0xF4,0xBB,0xE9,0x6C,0xB6,0xEF,0x87,0xCC,
+0x9B,0xBB,0xFF,0x39,0xE6,0x56,0x61,0xD3,0x0A,0xA7,0xC4,0x5C,0x4C,0x60,0x7B,0x05,
+0x77,0x26,0x7A,0xBF,0xD8,0x07,0x52,0x2C,0x62,0xF7,0x70,0x63,0xD9,0x39,0xBC,0x6F,
+0x1C,0xC2,0x79,0xDC,0x76,0x29,0xAF,0xCE,0xC5,0x2C,0x64,0x04,0x5E,0x88,0x36,0x6E,
+0x31,0xD4,0x40,0x1A,0x62,0x34,0x36,0x3F,0x35,0x01,0xAE,0xAC,0x63,0xA0,
+};
+
+
+/* subject:/C=ZA/ST=Western Cape/L=Cape Town/O=Thawte Consulting cc/OU=Certification Services Division/CN=Thawte Server CA/emailAddress=server-certs@thawte.com */
+/* issuer :/C=ZA/ST=Western Cape/L=Cape Town/O=Thawte Consulting cc/OU=Certification Services Division/CN=Thawte Server CA/emailAddress=server-certs@thawte.com */
+
+
+const unsigned char Thawte_Server_CA_certificate[791]={
+0x30,0x82,0x03,0x13,0x30,0x82,0x02,0x7C,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,0x30,
+0x81,0xC4,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x5A,0x41,0x31,
+0x15,0x30,0x13,0x06,0x03,0x55,0x04,0x08,0x13,0x0C,0x57,0x65,0x73,0x74,0x65,0x72,
+0x6E,0x20,0x43,0x61,0x70,0x65,0x31,0x12,0x30,0x10,0x06,0x03,0x55,0x04,0x07,0x13,
+0x09,0x43,0x61,0x70,0x65,0x20,0x54,0x6F,0x77,0x6E,0x31,0x1D,0x30,0x1B,0x06,0x03,
+0x55,0x04,0x0A,0x13,0x14,0x54,0x68,0x61,0x77,0x74,0x65,0x20,0x43,0x6F,0x6E,0x73,
+0x75,0x6C,0x74,0x69,0x6E,0x67,0x20,0x63,0x63,0x31,0x28,0x30,0x26,0x06,0x03,0x55,
+0x04,0x0B,0x13,0x1F,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,
+0x6E,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,0x20,0x44,0x69,0x76,0x69,0x73,
+0x69,0x6F,0x6E,0x31,0x19,0x30,0x17,0x06,0x03,0x55,0x04,0x03,0x13,0x10,0x54,0x68,
+0x61,0x77,0x74,0x65,0x20,0x53,0x65,0x72,0x76,0x65,0x72,0x20,0x43,0x41,0x31,0x26,
+0x30,0x24,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x09,0x01,0x16,0x17,0x73,
+0x65,0x72,0x76,0x65,0x72,0x2D,0x63,0x65,0x72,0x74,0x73,0x40,0x74,0x68,0x61,0x77,
+0x74,0x65,0x2E,0x63,0x6F,0x6D,0x30,0x1E,0x17,0x0D,0x39,0x36,0x30,0x38,0x30,0x31,
+0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x30,0x31,0x32,0x33,0x31,0x32,
+0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0xC4,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,
+0x04,0x06,0x13,0x02,0x5A,0x41,0x31,0x15,0x30,0x13,0x06,0x03,0x55,0x04,0x08,0x13,
+0x0C,0x57,0x65,0x73,0x74,0x65,0x72,0x6E,0x20,0x43,0x61,0x70,0x65,0x31,0x12,0x30,
+0x10,0x06,0x03,0x55,0x04,0x07,0x13,0x09,0x43,0x61,0x70,0x65,0x20,0x54,0x6F,0x77,
+0x6E,0x31,0x1D,0x30,0x1B,0x06,0x03,0x55,0x04,0x0A,0x13,0x14,0x54,0x68,0x61,0x77,
+0x74,0x65,0x20,0x43,0x6F,0x6E,0x73,0x75,0x6C,0x74,0x69,0x6E,0x67,0x20,0x63,0x63,
+0x31,0x28,0x30,0x26,0x06,0x03,0x55,0x04,0x0B,0x13,0x1F,0x43,0x65,0x72,0x74,0x69,
+0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,
+0x73,0x20,0x44,0x69,0x76,0x69,0x73,0x69,0x6F,0x6E,0x31,0x19,0x30,0x17,0x06,0x03,
+0x55,0x04,0x03,0x13,0x10,0x54,0x68,0x61,0x77,0x74,0x65,0x20,0x53,0x65,0x72,0x76,
+0x65,0x72,0x20,0x43,0x41,0x31,0x26,0x30,0x24,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,
+0x0D,0x01,0x09,0x01,0x16,0x17,0x73,0x65,0x72,0x76,0x65,0x72,0x2D,0x63,0x65,0x72,
+0x74,0x73,0x40,0x74,0x68,0x61,0x77,0x74,0x65,0x2E,0x63,0x6F,0x6D,0x30,0x81,0x9F,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,
+0x81,0x8D,0x00,0x30,0x81,0x89,0x02,0x81,0x81,0x00,0xD3,0xA4,0x50,0x6E,0xC8,0xFF,
+0x56,0x6B,0xE6,0xCF,0x5D,0xB6,0xEA,0x0C,0x68,0x75,0x47,0xA2,0xAA,0xC2,0xDA,0x84,
+0x25,0xFC,0xA8,0xF4,0x47,0x51,0xDA,0x85,0xB5,0x20,0x74,0x94,0x86,0x1E,0x0F,0x75,
+0xC9,0xE9,0x08,0x61,0xF5,0x06,0x6D,0x30,0x6E,0x15,0x19,0x02,0xE9,0x52,0xC0,0x62,
+0xDB,0x4D,0x99,0x9E,0xE2,0x6A,0x0C,0x44,0x38,0xCD,0xFE,0xBE,0xE3,0x64,0x09,0x70,
+0xC5,0xFE,0xB1,0x6B,0x29,0xB6,0x2F,0x49,0xC8,0x3B,0xD4,0x27,0x04,0x25,0x10,0x97,
+0x2F,0xE7,0x90,0x6D,0xC0,0x28,0x42,0x99,0xD7,0x4C,0x43,0xDE,0xC3,0xF5,0x21,0x6D,
+0x54,0x9F,0x5D,0xC3,0x58,0xE1,0xC0,0xE4,0xD9,0x5B,0xB0,0xB8,0xDC,0xB4,0x7B,0xDF,
+0x36,0x3A,0xC2,0xB5,0x66,0x22,0x12,0xD6,0x87,0x0D,0x02,0x03,0x01,0x00,0x01,0xA3,
+0x13,0x30,0x11,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,
+0x03,0x01,0x01,0xFF,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,
+0x04,0x05,0x00,0x03,0x81,0x81,0x00,0x07,0xFA,0x4C,0x69,0x5C,0xFB,0x95,0xCC,0x46,
+0xEE,0x85,0x83,0x4D,0x21,0x30,0x8E,0xCA,0xD9,0xA8,0x6F,0x49,0x1A,0xE6,0xDA,0x51,
+0xE3,0x60,0x70,0x6C,0x84,0x61,0x11,0xA1,0x1A,0xC8,0x48,0x3E,0x59,0x43,0x7D,0x4F,
+0x95,0x3D,0xA1,0x8B,0xB7,0x0B,0x62,0x98,0x7A,0x75,0x8A,0xDD,0x88,0x4E,0x4E,0x9E,
+0x40,0xDB,0xA8,0xCC,0x32,0x74,0xB9,0x6F,0x0D,0xC6,0xE3,0xB3,0x44,0x0B,0xD9,0x8A,
+0x6F,0x9A,0x29,0x9B,0x99,0x18,0x28,0x3B,0xD1,0xE3,0x40,0x28,0x9A,0x5A,0x3C,0xD5,
+0xB5,0xE7,0x20,0x1B,0x8B,0xCA,0xA4,0xAB,0x8D,0xE9,0x51,0xD9,0xE2,0x4C,0x2C,0x59,
+0xA9,0xDA,0xB9,0xB2,0x75,0x1B,0xF6,0x42,0xF2,0xEF,0xC7,0xF2,0x18,0xF9,0x89,0xBC,
+0xA3,0xFF,0x8A,0x23,0x2E,0x70,0x47,
+};
+
+
+/* subject:/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN - DATACorp SGC */
+/* issuer :/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN - DATACorp SGC */
+
+
+const unsigned char UTN_DATACorp_SGC_Root_CA_certificate[1122]={
+0x30,0x82,0x04,0x5E,0x30,0x82,0x03,0x46,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x44,
+0xBE,0x0C,0x8B,0x50,0x00,0x21,0xB4,0x11,0xD3,0x2A,0x68,0x06,0xA9,0xAD,0x69,0x30,
+0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81,
+0x93,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x0B,
+0x30,0x09,0x06,0x03,0x55,0x04,0x08,0x13,0x02,0x55,0x54,0x31,0x17,0x30,0x15,0x06,
+0x03,0x55,0x04,0x07,0x13,0x0E,0x53,0x61,0x6C,0x74,0x20,0x4C,0x61,0x6B,0x65,0x20,
+0x43,0x69,0x74,0x79,0x31,0x1E,0x30,0x1C,0x06,0x03,0x55,0x04,0x0A,0x13,0x15,0x54,
+0x68,0x65,0x20,0x55,0x53,0x45,0x52,0x54,0x52,0x55,0x53,0x54,0x20,0x4E,0x65,0x74,
+0x77,0x6F,0x72,0x6B,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x0B,0x13,0x18,0x68,
+0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x75,0x73,0x65,0x72,0x74,0x72,
+0x75,0x73,0x74,0x2E,0x63,0x6F,0x6D,0x31,0x1B,0x30,0x19,0x06,0x03,0x55,0x04,0x03,
+0x13,0x12,0x55,0x54,0x4E,0x20,0x2D,0x20,0x44,0x41,0x54,0x41,0x43,0x6F,0x72,0x70,
+0x20,0x53,0x47,0x43,0x30,0x1E,0x17,0x0D,0x39,0x39,0x30,0x36,0x32,0x34,0x31,0x38,
+0x35,0x37,0x32,0x31,0x5A,0x17,0x0D,0x31,0x39,0x30,0x36,0x32,0x34,0x31,0x39,0x30,
+0x36,0x33,0x30,0x5A,0x30,0x81,0x93,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,
+0x13,0x02,0x55,0x53,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x08,0x13,0x02,0x55,
+0x54,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x07,0x13,0x0E,0x53,0x61,0x6C,0x74,
+0x20,0x4C,0x61,0x6B,0x65,0x20,0x43,0x69,0x74,0x79,0x31,0x1E,0x30,0x1C,0x06,0x03,
+0x55,0x04,0x0A,0x13,0x15,0x54,0x68,0x65,0x20,0x55,0x53,0x45,0x52,0x54,0x52,0x55,
+0x53,0x54,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x21,0x30,0x1F,0x06,0x03,
+0x55,0x04,0x0B,0x13,0x18,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,
+0x75,0x73,0x65,0x72,0x74,0x72,0x75,0x73,0x74,0x2E,0x63,0x6F,0x6D,0x31,0x1B,0x30,
+0x19,0x06,0x03,0x55,0x04,0x03,0x13,0x12,0x55,0x54,0x4E,0x20,0x2D,0x20,0x44,0x41,
+0x54,0x41,0x43,0x6F,0x72,0x70,0x20,0x53,0x47,0x43,0x30,0x82,0x01,0x22,0x30,0x0D,
+0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,
+0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xDF,0xEE,0x58,0x10,0xA2,
+0x2B,0x6E,0x55,0xC4,0x8E,0xBF,0x2E,0x46,0x09,0xE7,0xE0,0x08,0x0F,0x2E,0x2B,0x7A,
+0x13,0x94,0x1B,0xBD,0xF6,0xB6,0x80,0x8E,0x65,0x05,0x93,0x00,0x1E,0xBC,0xAF,0xE2,
+0x0F,0x8E,0x19,0x0D,0x12,0x47,0xEC,0xAC,0xAD,0xA3,0xFA,0x2E,0x70,0xF8,0xDE,0x6E,
+0xFB,0x56,0x42,0x15,0x9E,0x2E,0x5C,0xEF,0x23,0xDE,0x21,0xB9,0x05,0x76,0x27,0x19,
+0x0F,0x4F,0xD6,0xC3,0x9C,0xB4,0xBE,0x94,0x19,0x63,0xF2,0xA6,0x11,0x0A,0xEB,0x53,
+0x48,0x9C,0xBE,0xF2,0x29,0x3B,0x16,0xE8,0x1A,0xA0,0x4C,0xA6,0xC9,0xF4,0x18,0x59,
+0x68,0xC0,0x70,0xF2,0x53,0x00,0xC0,0x5E,0x50,0x82,0xA5,0x56,0x6F,0x36,0xF9,0x4A,
+0xE0,0x44,0x86,0xA0,0x4D,0x4E,0xD6,0x47,0x6E,0x49,0x4A,0xCB,0x67,0xD7,0xA6,0xC4,
+0x05,0xB9,0x8E,0x1E,0xF4,0xFC,0xFF,0xCD,0xE7,0x36,0xE0,0x9C,0x05,0x6C,0xB2,0x33,
+0x22,0x15,0xD0,0xB4,0xE0,0xCC,0x17,0xC0,0xB2,0xC0,0xF4,0xFE,0x32,0x3F,0x29,0x2A,
+0x95,0x7B,0xD8,0xF2,0xA7,0x4E,0x0F,0x54,0x7C,0xA1,0x0D,0x80,0xB3,0x09,0x03,0xC1,
+0xFF,0x5C,0xDD,0x5E,0x9A,0x3E,0xBC,0xAE,0xBC,0x47,0x8A,0x6A,0xAE,0x71,0xCA,0x1F,
+0xB1,0x2A,0xB8,0x5F,0x42,0x05,0x0B,0xEC,0x46,0x30,0xD1,0x72,0x0B,0xCA,0xE9,0x56,
+0x6D,0xF5,0xEF,0xDF,0x78,0xBE,0x61,0xBA,0xB2,0xA5,0xAE,0x04,0x4C,0xBC,0xA8,0xAC,
+0x69,0x15,0x97,0xBD,0xEF,0xEB,0xB4,0x8C,0xBF,0x35,0xF8,0xD4,0xC3,0xD1,0x28,0x0E,
+0x5C,0x3A,0x9F,0x70,0x18,0x33,0x20,0x77,0xC4,0xA2,0xAF,0x02,0x03,0x01,0x00,0x01,
+0xA3,0x81,0xAB,0x30,0x81,0xA8,0x30,0x0B,0x06,0x03,0x55,0x1D,0x0F,0x04,0x04,0x03,
+0x02,0x01,0xC6,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,
+0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x53,
+0x32,0xD1,0xB3,0xCF,0x7F,0xFA,0xE0,0xF1,0xA0,0x5D,0x85,0x4E,0x92,0xD2,0x9E,0x45,
+0x1D,0xB4,0x4F,0x30,0x3D,0x06,0x03,0x55,0x1D,0x1F,0x04,0x36,0x30,0x34,0x30,0x32,
+0xA0,0x30,0xA0,0x2E,0x86,0x2C,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,
+0x2E,0x75,0x73,0x65,0x72,0x74,0x72,0x75,0x73,0x74,0x2E,0x63,0x6F,0x6D,0x2F,0x55,
+0x54,0x4E,0x2D,0x44,0x41,0x54,0x41,0x43,0x6F,0x72,0x70,0x53,0x47,0x43,0x2E,0x63,
+0x72,0x6C,0x30,0x2A,0x06,0x03,0x55,0x1D,0x25,0x04,0x23,0x30,0x21,0x06,0x08,0x2B,
+0x06,0x01,0x05,0x05,0x07,0x03,0x01,0x06,0x0A,0x2B,0x06,0x01,0x04,0x01,0x82,0x37,
+0x0A,0x03,0x03,0x06,0x09,0x60,0x86,0x48,0x01,0x86,0xF8,0x42,0x04,0x01,0x30,0x0D,
+0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,
+0x01,0x00,0x27,0x35,0x97,0x00,0x8A,0x8B,0x28,0xBD,0xC6,0x33,0x30,0x1E,0x29,0xFC,
+0xE2,0xF7,0xD5,0x98,0xD4,0x40,0xBB,0x60,0xCA,0xBF,0xAB,0x17,0x2C,0x09,0x36,0x7F,
+0x50,0xFA,0x41,0xDC,0xAE,0x96,0x3A,0x0A,0x23,0x3E,0x89,0x59,0xC9,0xA3,0x07,0xED,
+0x1B,0x37,0xAD,0xFC,0x7C,0xBE,0x51,0x49,0x5A,0xDE,0x3A,0x0A,0x54,0x08,0x16,0x45,
+0xC2,0x99,0xB1,0x87,0xCD,0x8C,0x68,0xE0,0x69,0x03,0xE9,0xC4,0x4E,0x98,0xB2,0x3B,
+0x8C,0x16,0xB3,0x0E,0xA0,0x0C,0x98,0x50,0x9B,0x93,0xA9,0x70,0x09,0xC8,0x2C,0xA3,
+0x8F,0xDF,0x02,0xE4,0xE0,0x71,0x3A,0xF1,0xB4,0x23,0x72,0xA0,0xAA,0x01,0xDF,0xDF,
+0x98,0x3E,0x14,0x50,0xA0,0x31,0x26,0xBD,0x28,0xE9,0x5A,0x30,0x26,0x75,0xF9,0x7B,
+0x60,0x1C,0x8D,0xF3,0xCD,0x50,0x26,0x6D,0x04,0x27,0x9A,0xDF,0xD5,0x0D,0x45,0x47,
+0x29,0x6B,0x2C,0xE6,0x76,0xD9,0xA9,0x29,0x7D,0x32,0xDD,0xC9,0x36,0x3C,0xBD,0xAE,
+0x35,0xF1,0x11,0x9E,0x1D,0xBB,0x90,0x3F,0x12,0x47,0x4E,0x8E,0xD7,0x7E,0x0F,0x62,
+0x73,0x1D,0x52,0x26,0x38,0x1C,0x18,0x49,0xFD,0x30,0x74,0x9A,0xC4,0xE5,0x22,0x2F,
+0xD8,0xC0,0x8D,0xED,0x91,0x7A,0x4C,0x00,0x8F,0x72,0x7F,0x5D,0xDA,0xDD,0x1B,0x8B,
+0x45,0x6B,0xE7,0xDD,0x69,0x97,0xA8,0xC5,0x56,0x4C,0x0F,0x0C,0xF6,0x9F,0x7A,0x91,
+0x37,0xF6,0x97,0x82,0xE0,0xDD,0x71,0x69,0xFF,0x76,0x3F,0x60,0x4D,0x3C,0xCF,0xF7,
+0x99,0xF9,0xC6,0x57,0xF4,0xC9,0x55,0x39,0x78,0xBA,0x2C,0x79,0xC9,0xA6,0x88,0x2B,
+0xF4,0x08,
+};
+
+
+/* subject:/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN-USERFirst-Hardware */
+/* issuer :/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN-USERFirst-Hardware */
+
+
+const unsigned char UTN_USERFirst_Hardware_Root_CA_certificate[1144]={
+0x30,0x82,0x04,0x74,0x30,0x82,0x03,0x5C,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x44,
+0xBE,0x0C,0x8B,0x50,0x00,0x24,0xB4,0x11,0xD3,0x36,0x2A,0xFE,0x65,0x0A,0xFD,0x30,
+0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81,
+0x97,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x0B,
+0x30,0x09,0x06,0x03,0x55,0x04,0x08,0x13,0x02,0x55,0x54,0x31,0x17,0x30,0x15,0x06,
+0x03,0x55,0x04,0x07,0x13,0x0E,0x53,0x61,0x6C,0x74,0x20,0x4C,0x61,0x6B,0x65,0x20,
+0x43,0x69,0x74,0x79,0x31,0x1E,0x30,0x1C,0x06,0x03,0x55,0x04,0x0A,0x13,0x15,0x54,
+0x68,0x65,0x20,0x55,0x53,0x45,0x52,0x54,0x52,0x55,0x53,0x54,0x20,0x4E,0x65,0x74,
+0x77,0x6F,0x72,0x6B,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x0B,0x13,0x18,0x68,
+0x74,0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x75,0x73,0x65,0x72,0x74,0x72,
+0x75,0x73,0x74,0x2E,0x63,0x6F,0x6D,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x03,
+0x13,0x16,0x55,0x54,0x4E,0x2D,0x55,0x53,0x45,0x52,0x46,0x69,0x72,0x73,0x74,0x2D,
+0x48,0x61,0x72,0x64,0x77,0x61,0x72,0x65,0x30,0x1E,0x17,0x0D,0x39,0x39,0x30,0x37,
+0x30,0x39,0x31,0x38,0x31,0x30,0x34,0x32,0x5A,0x17,0x0D,0x31,0x39,0x30,0x37,0x30,
+0x39,0x31,0x38,0x31,0x39,0x32,0x32,0x5A,0x30,0x81,0x97,0x31,0x0B,0x30,0x09,0x06,
+0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,
+0x08,0x13,0x02,0x55,0x54,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x07,0x13,0x0E,
+0x53,0x61,0x6C,0x74,0x20,0x4C,0x61,0x6B,0x65,0x20,0x43,0x69,0x74,0x79,0x31,0x1E,
+0x30,0x1C,0x06,0x03,0x55,0x04,0x0A,0x13,0x15,0x54,0x68,0x65,0x20,0x55,0x53,0x45,
+0x52,0x54,0x52,0x55,0x53,0x54,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x21,
+0x30,0x1F,0x06,0x03,0x55,0x04,0x0B,0x13,0x18,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,
+0x77,0x77,0x77,0x2E,0x75,0x73,0x65,0x72,0x74,0x72,0x75,0x73,0x74,0x2E,0x63,0x6F,
+0x6D,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x03,0x13,0x16,0x55,0x54,0x4E,0x2D,
+0x55,0x53,0x45,0x52,0x46,0x69,0x72,0x73,0x74,0x2D,0x48,0x61,0x72,0x64,0x77,0x61,
+0x72,0x65,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,
+0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,
+0x01,0x01,0x00,0xB1,0xF7,0xC3,0x38,0x3F,0xB4,0xA8,0x7F,0xCF,0x39,0x82,0x51,0x67,
+0xD0,0x6D,0x9F,0xD2,0xFF,0x58,0xF3,0xE7,0x9F,0x2B,0xEC,0x0D,0x89,0x54,0x99,0xB9,
+0x38,0x99,0x16,0xF7,0xE0,0x21,0x79,0x48,0xC2,0xBB,0x61,0x74,0x12,0x96,0x1D,0x3C,
+0x6A,0x72,0xD5,0x3C,0x10,0x67,0x3A,0x39,0xED,0x2B,0x13,0xCD,0x66,0xEB,0x95,0x09,
+0x33,0xA4,0x6C,0x97,0xB1,0xE8,0xC6,0xEC,0xC1,0x75,0x79,0x9C,0x46,0x5E,0x8D,0xAB,
+0xD0,0x6A,0xFD,0xB9,0x2A,0x55,0x17,0x10,0x54,0xB3,0x19,0xF0,0x9A,0xF6,0xF1,0xB1,
+0x5D,0xB6,0xA7,0x6D,0xFB,0xE0,0x71,0x17,0x6B,0xA2,0x88,0xFB,0x00,0xDF,0xFE,0x1A,
+0x31,0x77,0x0C,0x9A,0x01,0x7A,0xB1,0x32,0xE3,0x2B,0x01,0x07,0x38,0x6E,0xC3,0xA5,
+0x5E,0x23,0xBC,0x45,0x9B,0x7B,0x50,0xC1,0xC9,0x30,0x8F,0xDB,0xE5,0x2B,0x7A,0xD3,
+0x5B,0xFB,0x33,0x40,0x1E,0xA0,0xD5,0x98,0x17,0xBC,0x8B,0x87,0xC3,0x89,0xD3,0x5D,
+0xA0,0x8E,0xB2,0xAA,0xAA,0xF6,0x8E,0x69,0x88,0x06,0xC5,0xFA,0x89,0x21,0xF3,0x08,
+0x9D,0x69,0x2E,0x09,0x33,0x9B,0x29,0x0D,0x46,0x0F,0x8C,0xCC,0x49,0x34,0xB0,0x69,
+0x51,0xBD,0xF9,0x06,0xCD,0x68,0xAD,0x66,0x4C,0xBC,0x3E,0xAC,0x61,0xBD,0x0A,0x88,
+0x0E,0xC8,0xDF,0x3D,0xEE,0x7C,0x04,0x4C,0x9D,0x0A,0x5E,0x6B,0x91,0xD6,0xEE,0xC7,
+0xED,0x28,0x8D,0xAB,0x4D,0x87,0x89,0x73,0xD0,0x6E,0xA4,0xD0,0x1E,0x16,0x8B,0x14,
+0xE1,0x76,0x44,0x03,0x7F,0x63,0xAC,0xE4,0xCD,0x49,0x9C,0xC5,0x92,0xF4,0xAB,0x32,
+0xA1,0x48,0x5B,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,0xB9,0x30,0x81,0xB6,0x30,0x0B,
+0x06,0x03,0x55,0x1D,0x0F,0x04,0x04,0x03,0x02,0x01,0xC6,0x30,0x0F,0x06,0x03,0x55,
+0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,
+0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xA1,0x72,0x5F,0x26,0x1B,0x28,0x98,0x43,0x95,
+0x5D,0x07,0x37,0xD5,0x85,0x96,0x9D,0x4B,0xD2,0xC3,0x45,0x30,0x44,0x06,0x03,0x55,
+0x1D,0x1F,0x04,0x3D,0x30,0x3B,0x30,0x39,0xA0,0x37,0xA0,0x35,0x86,0x33,0x68,0x74,
+0x74,0x70,0x3A,0x2F,0x2F,0x63,0x72,0x6C,0x2E,0x75,0x73,0x65,0x72,0x74,0x72,0x75,
+0x73,0x74,0x2E,0x63,0x6F,0x6D,0x2F,0x55,0x54,0x4E,0x2D,0x55,0x53,0x45,0x52,0x46,
+0x69,0x72,0x73,0x74,0x2D,0x48,0x61,0x72,0x64,0x77,0x61,0x72,0x65,0x2E,0x63,0x72,
+0x6C,0x30,0x31,0x06,0x03,0x55,0x1D,0x25,0x04,0x2A,0x30,0x28,0x06,0x08,0x2B,0x06,
+0x01,0x05,0x05,0x07,0x03,0x01,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x03,0x05,
+0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x03,0x06,0x06,0x08,0x2B,0x06,0x01,0x05,
+0x05,0x07,0x03,0x07,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,
+0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x47,0x19,0x0F,0xDE,0x74,0xC6,0x99,0x97,
+0xAF,0xFC,0xAD,0x28,0x5E,0x75,0x8E,0xEB,0x2D,0x67,0xEE,0x4E,0x7B,0x2B,0xD7,0x0C,
+0xFF,0xF6,0xDE,0xCB,0x55,0xA2,0x0A,0xE1,0x4C,0x54,0x65,0x93,0x60,0x6B,0x9F,0x12,
+0x9C,0xAD,0x5E,0x83,0x2C,0xEB,0x5A,0xAE,0xC0,0xE4,0x2D,0xF4,0x00,0x63,0x1D,0xB8,
+0xC0,0x6C,0xF2,0xCF,0x49,0xBB,0x4D,0x93,0x6F,0x06,0xA6,0x0A,0x22,0xB2,0x49,0x62,
+0x08,0x4E,0xFF,0xC8,0xC8,0x14,0xB2,0x88,0x16,0x5D,0xE7,0x01,0xE4,0x12,0x95,0xE5,
+0x45,0x34,0xB3,0x8B,0x69,0xBD,0xCF,0xB4,0x85,0x8F,0x75,0x51,0x9E,0x7D,0x3A,0x38,
+0x3A,0x14,0x48,0x12,0xC6,0xFB,0xA7,0x3B,0x1A,0x8D,0x0D,0x82,0x40,0x07,0xE8,0x04,
+0x08,0x90,0xA1,0x89,0xCB,0x19,0x50,0xDF,0xCA,0x1C,0x01,0xBC,0x1D,0x04,0x19,0x7B,
+0x10,0x76,0x97,0x3B,0xEE,0x90,0x90,0xCA,0xC4,0x0E,0x1F,0x16,0x6E,0x75,0xEF,0x33,
+0xF8,0xD3,0x6F,0x5B,0x1E,0x96,0xE3,0xE0,0x74,0x77,0x74,0x7B,0x8A,0xA2,0x6E,0x2D,
+0xDD,0x76,0xD6,0x39,0x30,0x82,0xF0,0xAB,0x9C,0x52,0xF2,0x2A,0xC7,0xAF,0x49,0x5E,
+0x7E,0xC7,0x68,0xE5,0x82,0x81,0xC8,0x6A,0x27,0xF9,0x27,0x88,0x2A,0xD5,0x58,0x50,
+0x95,0x1F,0xF0,0x3B,0x1C,0x57,0xBB,0x7D,0x14,0x39,0x62,0x2B,0x9A,0xC9,0x94,0x92,
+0x2A,0xA3,0x22,0x0C,0xFF,0x89,0x26,0x7D,0x5F,0x23,0x2B,0x47,0xD7,0x15,0x1D,0xA9,
+0x6A,0x9E,0x51,0x0D,0x2A,0x51,0x9E,0x81,0xF9,0xD4,0x3B,0x5E,0x70,0x12,0x7F,0x10,
+0x32,0x9C,0x1E,0xBB,0x9D,0xF8,0x66,0xA8,
+};
+
+
+/* subject:/L=ValiCert Validation Network/O=ValiCert, Inc./OU=ValiCert Class 1 Policy Validation Authority/CN=http://www.valicert.com//emailAddress=info@valicert.com */
+/* issuer :/L=ValiCert Validation Network/O=ValiCert, Inc./OU=ValiCert Class 1 Policy Validation Authority/CN=http://www.valicert.com//emailAddress=info@valicert.com */
+
+
+const unsigned char ValiCert_Class_1_VA_certificate[747]={
+0x30,0x82,0x02,0xE7,0x30,0x82,0x02,0x50,0x02,0x01,0x01,0x30,0x0D,0x06,0x09,0x2A,
+0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81,0xBB,0x31,0x24,0x30,
+0x22,0x06,0x03,0x55,0x04,0x07,0x13,0x1B,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,
+0x20,0x56,0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x4E,0x65,0x74,0x77,
+0x6F,0x72,0x6B,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x61,
+0x6C,0x69,0x43,0x65,0x72,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x35,0x30,0x33,
+0x06,0x03,0x55,0x04,0x0B,0x13,0x2C,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20,
+0x43,0x6C,0x61,0x73,0x73,0x20,0x31,0x20,0x50,0x6F,0x6C,0x69,0x63,0x79,0x20,0x56,
+0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,
+0x69,0x74,0x79,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x68,0x74,
+0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x76,0x61,0x6C,0x69,0x63,0x65,0x72,
+0x74,0x2E,0x63,0x6F,0x6D,0x2F,0x31,0x20,0x30,0x1E,0x06,0x09,0x2A,0x86,0x48,0x86,
+0xF7,0x0D,0x01,0x09,0x01,0x16,0x11,0x69,0x6E,0x66,0x6F,0x40,0x76,0x61,0x6C,0x69,
+0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x30,0x1E,0x17,0x0D,0x39,0x39,0x30,0x36,
+0x32,0x35,0x32,0x32,0x32,0x33,0x34,0x38,0x5A,0x17,0x0D,0x31,0x39,0x30,0x36,0x32,
+0x35,0x32,0x32,0x32,0x33,0x34,0x38,0x5A,0x30,0x81,0xBB,0x31,0x24,0x30,0x22,0x06,
+0x03,0x55,0x04,0x07,0x13,0x1B,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20,0x56,
+0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,
+0x6B,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x61,0x6C,0x69,
+0x43,0x65,0x72,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x35,0x30,0x33,0x06,0x03,
+0x55,0x04,0x0B,0x13,0x2C,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20,0x43,0x6C,
+0x61,0x73,0x73,0x20,0x31,0x20,0x50,0x6F,0x6C,0x69,0x63,0x79,0x20,0x56,0x61,0x6C,
+0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,
+0x79,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x68,0x74,0x74,0x70,
+0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x76,0x61,0x6C,0x69,0x63,0x65,0x72,0x74,0x2E,
+0x63,0x6F,0x6D,0x2F,0x31,0x20,0x30,0x1E,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,
+0x01,0x09,0x01,0x16,0x11,0x69,0x6E,0x66,0x6F,0x40,0x76,0x61,0x6C,0x69,0x63,0x65,
+0x72,0x74,0x2E,0x63,0x6F,0x6D,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,
+0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89,0x02,
+0x81,0x81,0x00,0xD8,0x59,0x82,0x7A,0x89,0xB8,0x96,0xBA,0xA6,0x2F,0x68,0x6F,0x58,
+0x2E,0xA7,0x54,0x1C,0x06,0x6E,0xF4,0xEA,0x8D,0x48,0xBC,0x31,0x94,0x17,0xF0,0xF3,
+0x4E,0xBC,0xB2,0xB8,0x35,0x92,0x76,0xB0,0xD0,0xA5,0xA5,0x01,0xD7,0x00,0x03,0x12,
+0x22,0x19,0x08,0xF8,0xFF,0x11,0x23,0x9B,0xCE,0x07,0xF5,0xBF,0x69,0x1A,0x26,0xFE,
+0x4E,0xE9,0xD1,0x7F,0x9D,0x2C,0x40,0x1D,0x59,0x68,0x6E,0xA6,0xF8,0x58,0xB0,0x9D,
+0x1A,0x8F,0xD3,0x3F,0xF1,0xDC,0x19,0x06,0x81,0xA8,0x0E,0xE0,0x3A,0xDD,0xC8,0x53,
+0x45,0x09,0x06,0xE6,0x0F,0x70,0xC3,0xFA,0x40,0xA6,0x0E,0xE2,0x56,0x05,0x0F,0x18,
+0x4D,0xFC,0x20,0x82,0xD1,0x73,0x55,0x74,0x8D,0x76,0x72,0xA0,0x1D,0x9D,0x1D,0xC0,
+0xDD,0x3F,0x71,0x02,0x03,0x01,0x00,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,
+0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x81,0x81,0x00,0x50,0x68,0x3D,0x49,0xF4,
+0x2C,0x1C,0x06,0x94,0xDF,0x95,0x60,0x7F,0x96,0x7B,0x17,0xFE,0x4F,0x71,0xAD,0x64,
+0xC8,0xDD,0x77,0xD2,0xEF,0x59,0x55,0xE8,0x3F,0xE8,0x8E,0x05,0x2A,0x21,0xF2,0x07,
+0xD2,0xB5,0xA7,0x52,0xFE,0x9C,0xB1,0xB6,0xE2,0x5B,0x77,0x17,0x40,0xEA,0x72,0xD6,
+0x23,0xCB,0x28,0x81,0x32,0xC3,0x00,0x79,0x18,0xEC,0x59,0x17,0x89,0xC9,0xC6,0x6A,
+0x1E,0x71,0xC9,0xFD,0xB7,0x74,0xA5,0x25,0x45,0x69,0xC5,0x48,0xAB,0x19,0xE1,0x45,
+0x8A,0x25,0x6B,0x19,0xEE,0xE5,0xBB,0x12,0xF5,0x7F,0xF7,0xA6,0x8D,0x51,0xC3,0xF0,
+0x9D,0x74,0xB7,0xA9,0x3E,0xA0,0xA5,0xFF,0xB6,0x49,0x03,0x13,0xDA,0x22,0xCC,0xED,
+0x71,0x82,0x2B,0x99,0xCF,0x3A,0xB7,0xF5,0x2D,0x72,0xC8,
+};
+
+
+/* subject:/L=ValiCert Validation Network/O=ValiCert, Inc./OU=ValiCert Class 2 Policy Validation Authority/CN=http://www.valicert.com//emailAddress=info@valicert.com */
+/* issuer :/L=ValiCert Validation Network/O=ValiCert, Inc./OU=ValiCert Class 2 Policy Validation Authority/CN=http://www.valicert.com//emailAddress=info@valicert.com */
+
+
+const unsigned char ValiCert_Class_2_VA_certificate[747]={
+0x30,0x82,0x02,0xE7,0x30,0x82,0x02,0x50,0x02,0x01,0x01,0x30,0x0D,0x06,0x09,0x2A,
+0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81,0xBB,0x31,0x24,0x30,
+0x22,0x06,0x03,0x55,0x04,0x07,0x13,0x1B,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,
+0x20,0x56,0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x4E,0x65,0x74,0x77,
+0x6F,0x72,0x6B,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x61,
+0x6C,0x69,0x43,0x65,0x72,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x35,0x30,0x33,
+0x06,0x03,0x55,0x04,0x0B,0x13,0x2C,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20,
+0x43,0x6C,0x61,0x73,0x73,0x20,0x32,0x20,0x50,0x6F,0x6C,0x69,0x63,0x79,0x20,0x56,
+0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,
+0x69,0x74,0x79,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x68,0x74,
+0x74,0x70,0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x76,0x61,0x6C,0x69,0x63,0x65,0x72,
+0x74,0x2E,0x63,0x6F,0x6D,0x2F,0x31,0x20,0x30,0x1E,0x06,0x09,0x2A,0x86,0x48,0x86,
+0xF7,0x0D,0x01,0x09,0x01,0x16,0x11,0x69,0x6E,0x66,0x6F,0x40,0x76,0x61,0x6C,0x69,
+0x63,0x65,0x72,0x74,0x2E,0x63,0x6F,0x6D,0x30,0x1E,0x17,0x0D,0x39,0x39,0x30,0x36,
+0x32,0x36,0x30,0x30,0x31,0x39,0x35,0x34,0x5A,0x17,0x0D,0x31,0x39,0x30,0x36,0x32,
+0x36,0x30,0x30,0x31,0x39,0x35,0x34,0x5A,0x30,0x81,0xBB,0x31,0x24,0x30,0x22,0x06,
+0x03,0x55,0x04,0x07,0x13,0x1B,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20,0x56,
+0x61,0x6C,0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,
+0x6B,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x61,0x6C,0x69,
+0x43,0x65,0x72,0x74,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x35,0x30,0x33,0x06,0x03,
+0x55,0x04,0x0B,0x13,0x2C,0x56,0x61,0x6C,0x69,0x43,0x65,0x72,0x74,0x20,0x43,0x6C,
+0x61,0x73,0x73,0x20,0x32,0x20,0x50,0x6F,0x6C,0x69,0x63,0x79,0x20,0x56,0x61,0x6C,
+0x69,0x64,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,
+0x79,0x31,0x21,0x30,0x1F,0x06,0x03,0x55,0x04,0x03,0x13,0x18,0x68,0x74,0x74,0x70,
+0x3A,0x2F,0x2F,0x77,0x77,0x77,0x2E,0x76,0x61,0x6C,0x69,0x63,0x65,0x72,0x74,0x2E,
+0x63,0x6F,0x6D,0x2F,0x31,0x20,0x30,0x1E,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,
+0x01,0x09,0x01,0x16,0x11,0x69,0x6E,0x66,0x6F,0x40,0x76,0x61,0x6C,0x69,0x63,0x65,
+0x72,0x74,0x2E,0x63,0x6F,0x6D,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,
+0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89,0x02,
+0x81,0x81,0x00,0xCE,0x3A,0x71,0xCA,0xE5,0xAB,0xC8,0x59,0x92,0x55,0xD7,0xAB,0xD8,
+0x74,0x0E,0xF9,0xEE,0xD9,0xF6,0x55,0x47,0x59,0x65,0x47,0x0E,0x05,0x55,0xDC,0xEB,
+0x98,0x36,0x3C,0x5C,0x53,0x5D,0xD3,0x30,0xCF,0x38,0xEC,0xBD,0x41,0x89,0xED,0x25,
+0x42,0x09,0x24,0x6B,0x0A,0x5E,0xB3,0x7C,0xDD,0x52,0x2D,0x4C,0xE6,0xD4,0xD6,0x7D,
+0x5A,0x59,0xA9,0x65,0xD4,0x49,0x13,0x2D,0x24,0x4D,0x1C,0x50,0x6F,0xB5,0xC1,0x85,
+0x54,0x3B,0xFE,0x71,0xE4,0xD3,0x5C,0x42,0xF9,0x80,0xE0,0x91,0x1A,0x0A,0x5B,0x39,
+0x36,0x67,0xF3,0x3F,0x55,0x7C,0x1B,0x3F,0xB4,0x5F,0x64,0x73,0x34,0xE3,0xB4,0x12,
+0xBF,0x87,0x64,0xF8,0xDA,0x12,0xFF,0x37,0x27,0xC1,0xB3,0x43,0xBB,0xEF,0x7B,0x6E,
+0x2E,0x69,0xF7,0x02,0x03,0x01,0x00,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,
+0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x81,0x81,0x00,0x3B,0x7F,0x50,0x6F,0x6F,
+0x50,0x94,0x99,0x49,0x62,0x38,0x38,0x1F,0x4B,0xF8,0xA5,0xC8,0x3E,0xA7,0x82,0x81,
+0xF6,0x2B,0xC7,0xE8,0xC5,0xCE,0xE8,0x3A,0x10,0x82,0xCB,0x18,0x00,0x8E,0x4D,0xBD,
+0xA8,0x58,0x7F,0xA1,0x79,0x00,0xB5,0xBB,0xE9,0x8D,0xAF,0x41,0xD9,0x0F,0x34,0xEE,
+0x21,0x81,0x19,0xA0,0x32,0x49,0x28,0xF4,0xC4,0x8E,0x56,0xD5,0x52,0x33,0xFD,0x50,
+0xD5,0x7E,0x99,0x6C,0x03,0xE4,0xC9,0x4C,0xFC,0xCB,0x6C,0xAB,0x66,0xB3,0x4A,0x21,
+0x8C,0xE5,0xB5,0x0C,0x32,0x3E,0x10,0xB2,0xCC,0x6C,0xA1,0xDC,0x9A,0x98,0x4C,0x02,
+0x5B,0xF3,0xCE,0xB9,0x9E,0xA5,0x72,0x0E,0x4A,0xB7,0x3F,0x3C,0xE6,0x16,0x68,0xF8,
+0xBE,0xED,0x74,0x4C,0xBC,0x5B,0xD5,0x62,0x1F,0x43,0xDD,
+};
+
+
+/* subject:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority */
+/* issuer :/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority */
+
+
+const unsigned char Verisign_Class_3_Public_Primary_Certification_Authority_certificate[576]={
+0x30,0x82,0x02,0x3C,0x30,0x82,0x01,0xA5,0x02,0x10,0x3C,0x91,0x31,0xCB,0x1F,0xF6,
+0xD0,0x1B,0x0E,0x9A,0xB8,0xD0,0x44,0xBF,0x12,0xBE,0x30,0x0D,0x06,0x09,0x2A,0x86,
+0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x5F,0x31,0x0B,0x30,0x09,0x06,
+0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,
+0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E,0x63,
+0x2E,0x31,0x37,0x30,0x35,0x06,0x03,0x55,0x04,0x0B,0x13,0x2E,0x43,0x6C,0x61,0x73,
+0x73,0x20,0x33,0x20,0x50,0x75,0x62,0x6C,0x69,0x63,0x20,0x50,0x72,0x69,0x6D,0x61,
+0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,
+0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x1E,0x17,0x0D,0x39,0x36,
+0x30,0x31,0x32,0x39,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x38,0x30,
+0x38,0x30,0x32,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x5F,0x31,0x0B,0x30,0x09,
+0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06,0x03,0x55,
+0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E,
+0x63,0x2E,0x31,0x37,0x30,0x35,0x06,0x03,0x55,0x04,0x0B,0x13,0x2E,0x43,0x6C,0x61,
+0x73,0x73,0x20,0x33,0x20,0x50,0x75,0x62,0x6C,0x69,0x63,0x20,0x50,0x72,0x69,0x6D,
+0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,
+0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x81,0x9F,0x30,0x0D,
+0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x81,0x8D,
+0x00,0x30,0x81,0x89,0x02,0x81,0x81,0x00,0xC9,0x5C,0x59,0x9E,0xF2,0x1B,0x8A,0x01,
+0x14,0xB4,0x10,0xDF,0x04,0x40,0xDB,0xE3,0x57,0xAF,0x6A,0x45,0x40,0x8F,0x84,0x0C,
+0x0B,0xD1,0x33,0xD9,0xD9,0x11,0xCF,0xEE,0x02,0x58,0x1F,0x25,0xF7,0x2A,0xA8,0x44,
+0x05,0xAA,0xEC,0x03,0x1F,0x78,0x7F,0x9E,0x93,0xB9,0x9A,0x00,0xAA,0x23,0x7D,0xD6,
+0xAC,0x85,0xA2,0x63,0x45,0xC7,0x72,0x27,0xCC,0xF4,0x4C,0xC6,0x75,0x71,0xD2,0x39,
+0xEF,0x4F,0x42,0xF0,0x75,0xDF,0x0A,0x90,0xC6,0x8E,0x20,0x6F,0x98,0x0F,0xF8,0xAC,
+0x23,0x5F,0x70,0x29,0x36,0xA4,0xC9,0x86,0xE7,0xB1,0x9A,0x20,0xCB,0x53,0xA5,0x85,
+0xE7,0x3D,0xBE,0x7D,0x9A,0xFE,0x24,0x45,0x33,0xDC,0x76,0x15,0xED,0x0F,0xA2,0x71,
+0x64,0x4C,0x65,0x2E,0x81,0x68,0x45,0xA7,0x02,0x03,0x01,0x00,0x01,0x30,0x0D,0x06,
+0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x81,0x81,0x00,
+0x10,0x72,0x52,0xA9,0x05,0x14,0x19,0x32,0x08,0x41,0xF0,0xC5,0x6B,0x0A,0xCC,0x7E,
+0x0F,0x21,0x19,0xCD,0xE4,0x67,0xDC,0x5F,0xA9,0x1B,0xE6,0xCA,0xE8,0x73,0x9D,0x22,
+0xD8,0x98,0x6E,0x73,0x03,0x61,0x91,0xC5,0x7C,0xB0,0x45,0x40,0x6E,0x44,0x9D,0x8D,
+0xB0,0xB1,0x96,0x74,0x61,0x2D,0x0D,0xA9,0x45,0xD2,0xA4,0x92,0x2A,0xD6,0x9A,0x75,
+0x97,0x6E,0x3F,0x53,0xFD,0x45,0x99,0x60,0x1D,0xA8,0x2B,0x4C,0xF9,0x5E,0xA7,0x09,
+0xD8,0x75,0x30,0xD7,0xD2,0x65,0x60,0x3D,0x67,0xD6,0x48,0x55,0x75,0x69,0x3F,0x91,
+0xF5,0x48,0x0B,0x47,0x69,0x22,0x69,0x82,0x96,0xBE,0xC9,0xC8,0x38,0x86,0x4A,0x7A,
+0x2C,0x73,0x19,0x48,0x69,0x4E,0x6B,0x7C,0x65,0xBF,0x0F,0xFC,0x70,0xCE,0x88,0x90,
+};
+
+
+/* subject:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority - G2/OU=(c) 1998 VeriSign, Inc. - For authorized use only/OU=VeriSign Trust Network */
+/* issuer :/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority - G2/OU=(c) 1998 VeriSign, Inc. - For authorized use only/OU=VeriSign Trust Network */
+
+
+const unsigned char Verisign_Class_3_Public_Primary_Certification_Authority___G2_certificate[774]={
+0x30,0x82,0x03,0x02,0x30,0x82,0x02,0x6B,0x02,0x10,0x7D,0xD9,0xFE,0x07,0xCF,0xA8,
+0x1E,0xB7,0x10,0x79,0x67,0xFB,0xA7,0x89,0x34,0xC6,0x30,0x0D,0x06,0x09,0x2A,0x86,
+0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81,0xC1,0x31,0x0B,0x30,0x09,
+0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06,0x03,0x55,
+0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E,
+0x63,0x2E,0x31,0x3C,0x30,0x3A,0x06,0x03,0x55,0x04,0x0B,0x13,0x33,0x43,0x6C,0x61,
+0x73,0x73,0x20,0x33,0x20,0x50,0x75,0x62,0x6C,0x69,0x63,0x20,0x50,0x72,0x69,0x6D,
+0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,
+0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x2D,0x20,0x47,0x32,
+0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31,0x28,0x63,0x29,0x20,0x31,
+0x39,0x39,0x38,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E,
+0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,
+0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x1F,0x30,0x1D,
+0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20,
+0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x30,0x1E,0x17,
+0x0D,0x39,0x38,0x30,0x35,0x31,0x38,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,
+0x32,0x38,0x30,0x38,0x30,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0xC1,
+0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,
+0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,
+0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x3C,0x30,0x3A,0x06,0x03,0x55,0x04,0x0B,0x13,
+0x33,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x50,0x75,0x62,0x6C,0x69,0x63,0x20,
+0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,
+0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20,
+0x2D,0x20,0x47,0x32,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31,0x28,
+0x63,0x29,0x20,0x31,0x39,0x39,0x38,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,
+0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,
+0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,
+0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56,0x65,0x72,0x69,0x53,
+0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,
+0x6B,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,
+0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89,0x02,0x81,0x81,0x00,0xCC,0x5E,
+0xD1,0x11,0x5D,0x5C,0x69,0xD0,0xAB,0xD3,0xB9,0x6A,0x4C,0x99,0x1F,0x59,0x98,0x30,
+0x8E,0x16,0x85,0x20,0x46,0x6D,0x47,0x3F,0xD4,0x85,0x20,0x84,0xE1,0x6D,0xB3,0xF8,
+0xA4,0xED,0x0C,0xF1,0x17,0x0F,0x3B,0xF9,0xA7,0xF9,0x25,0xD7,0xC1,0xCF,0x84,0x63,
+0xF2,0x7C,0x63,0xCF,0xA2,0x47,0xF2,0xC6,0x5B,0x33,0x8E,0x64,0x40,0x04,0x68,0xC1,
+0x80,0xB9,0x64,0x1C,0x45,0x77,0xC7,0xD8,0x6E,0xF5,0x95,0x29,0x3C,0x50,0xE8,0x34,
+0xD7,0x78,0x1F,0xA8,0xBA,0x6D,0x43,0x91,0x95,0x8F,0x45,0x57,0x5E,0x7E,0xC5,0xFB,
+0xCA,0xA4,0x04,0xEB,0xEA,0x97,0x37,0x54,0x30,0x6F,0xBB,0x01,0x47,0x32,0x33,0xCD,
+0xDC,0x57,0x9B,0x64,0x69,0x61,0xF8,0x9B,0x1D,0x1C,0x89,0x4F,0x5C,0x67,0x02,0x03,
+0x01,0x00,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,
+0x05,0x00,0x03,0x81,0x81,0x00,0x51,0x4D,0xCD,0xBE,0x5C,0xCB,0x98,0x19,0x9C,0x15,
+0xB2,0x01,0x39,0x78,0x2E,0x4D,0x0F,0x67,0x70,0x70,0x99,0xC6,0x10,0x5A,0x94,0xA4,
+0x53,0x4D,0x54,0x6D,0x2B,0xAF,0x0D,0x5D,0x40,0x8B,0x64,0xD3,0xD7,0xEE,0xDE,0x56,
+0x61,0x92,0x5F,0xA6,0xC4,0x1D,0x10,0x61,0x36,0xD3,0x2C,0x27,0x3C,0xE8,0x29,0x09,
+0xB9,0x11,0x64,0x74,0xCC,0xB5,0x73,0x9F,0x1C,0x48,0xA9,0xBC,0x61,0x01,0xEE,0xE2,
+0x17,0xA6,0x0C,0xE3,0x40,0x08,0x3B,0x0E,0xE7,0xEB,0x44,0x73,0x2A,0x9A,0xF1,0x69,
+0x92,0xEF,0x71,0x14,0xC3,0x39,0xAC,0x71,0xA7,0x91,0x09,0x6F,0xE4,0x71,0x06,0xB3,
+0xBA,0x59,0x57,0x26,0x79,0x00,0xF6,0xF8,0x0D,0xA2,0x33,0x30,0x28,0xD4,0xAA,0x58,
+0xA0,0x9D,0x9D,0x69,0x91,0xFD,
+};
+
+
+/* subject:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 1999 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G3 */
+/* issuer :/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 1999 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G3 */
+
+
+const unsigned char Verisign_Class_3_Public_Primary_Certification_Authority___G3_certificate[1054]={
+0x30,0x82,0x04,0x1A,0x30,0x82,0x03,0x02,0x02,0x11,0x00,0x9B,0x7E,0x06,0x49,0xA3,
+0x3E,0x62,0xB9,0xD5,0xEE,0x90,0x48,0x71,0x29,0xEF,0x57,0x30,0x0D,0x06,0x09,0x2A,
+0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81,0xCA,0x31,0x0B,0x30,
+0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06,0x03,
+0x55,0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,
+0x6E,0x63,0x2E,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56,0x65,
+0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65,0x74,
+0x77,0x6F,0x72,0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31,0x28,
+0x63,0x29,0x20,0x31,0x39,0x39,0x39,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,
+0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,
+0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,
+0x31,0x45,0x30,0x43,0x06,0x03,0x55,0x04,0x03,0x13,0x3C,0x56,0x65,0x72,0x69,0x53,
+0x69,0x67,0x6E,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x50,0x75,0x62,0x6C,
+0x69,0x63,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,
+0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,
+0x74,0x79,0x20,0x2D,0x20,0x47,0x33,0x30,0x1E,0x17,0x0D,0x39,0x39,0x31,0x30,0x30,
+0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x36,0x30,0x37,0x31,0x36,
+0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0xCA,0x31,0x0B,0x30,0x09,0x06,0x03,
+0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,
+0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E,
+0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56,0x65,0x72,0x69,0x53,
+0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,
+0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31,0x28,0x63,0x29,0x20,
+0x31,0x39,0x39,0x39,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,
+0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72,
+0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x45,0x30,
+0x43,0x06,0x03,0x55,0x04,0x03,0x13,0x3C,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,
+0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x50,0x75,0x62,0x6C,0x69,0x63,0x20,
+0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,
+0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20,
+0x2D,0x20,0x47,0x33,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,
+0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,
+0x02,0x82,0x01,0x01,0x00,0xCB,0xBA,0x9C,0x52,0xFC,0x78,0x1F,0x1A,0x1E,0x6F,0x1B,
+0x37,0x73,0xBD,0xF8,0xC9,0x6B,0x94,0x12,0x30,0x4F,0xF0,0x36,0x47,0xF5,0xD0,0x91,
+0x0A,0xF5,0x17,0xC8,0xA5,0x61,0xC1,0x16,0x40,0x4D,0xFB,0x8A,0x61,0x90,0xE5,0x76,
+0x20,0xC1,0x11,0x06,0x7D,0xAB,0x2C,0x6E,0xA6,0xF5,0x11,0x41,0x8E,0xFA,0x2D,0xAD,
+0x2A,0x61,0x59,0xA4,0x67,0x26,0x4C,0xD0,0xE8,0xBC,0x52,0x5B,0x70,0x20,0x04,0x58,
+0xD1,0x7A,0xC9,0xA4,0x69,0xBC,0x83,0x17,0x64,0xAD,0x05,0x8B,0xBC,0xD0,0x58,0xCE,
+0x8D,0x8C,0xF5,0xEB,0xF0,0x42,0x49,0x0B,0x9D,0x97,0x27,0x67,0x32,0x6E,0xE1,0xAE,
+0x93,0x15,0x1C,0x70,0xBC,0x20,0x4D,0x2F,0x18,0xDE,0x92,0x88,0xE8,0x6C,0x85,0x57,
+0x11,0x1A,0xE9,0x7E,0xE3,0x26,0x11,0x54,0xA2,0x45,0x96,0x55,0x83,0xCA,0x30,0x89,
+0xE8,0xDC,0xD8,0xA3,0xED,0x2A,0x80,0x3F,0x7F,0x79,0x65,0x57,0x3E,0x15,0x20,0x66,
+0x08,0x2F,0x95,0x93,0xBF,0xAA,0x47,0x2F,0xA8,0x46,0x97,0xF0,0x12,0xE2,0xFE,0xC2,
+0x0A,0x2B,0x51,0xE6,0x76,0xE6,0xB7,0x46,0xB7,0xE2,0x0D,0xA6,0xCC,0xA8,0xC3,0x4C,
+0x59,0x55,0x89,0xE6,0xE8,0x53,0x5C,0x1C,0xEA,0x9D,0xF0,0x62,0x16,0x0B,0xA7,0xC9,
+0x5F,0x0C,0xF0,0xDE,0xC2,0x76,0xCE,0xAF,0xF7,0x6A,0xF2,0xFA,0x41,0xA6,0xA2,0x33,
+0x14,0xC9,0xE5,0x7A,0x63,0xD3,0x9E,0x62,0x37,0xD5,0x85,0x65,0x9E,0x0E,0xE6,0x53,
+0x24,0x74,0x1B,0x5E,0x1D,0x12,0x53,0x5B,0xC7,0x2C,0xE7,0x83,0x49,0x3B,0x15,0xAE,
+0x8A,0x68,0xB9,0x57,0x97,0x02,0x03,0x01,0x00,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86,
+0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x11,0x14,
+0x96,0xC1,0xAB,0x92,0x08,0xF7,0x3F,0x2F,0xC9,0xB2,0xFE,0xE4,0x5A,0x9F,0x64,0xDE,
+0xDB,0x21,0x4F,0x86,0x99,0x34,0x76,0x36,0x57,0xDD,0xD0,0x15,0x2F,0xC5,0xAD,0x7F,
+0x15,0x1F,0x37,0x62,0x73,0x3E,0xD4,0xE7,0x5F,0xCE,0x17,0x03,0xDB,0x35,0xFA,0x2B,
+0xDB,0xAE,0x60,0x09,0x5F,0x1E,0x5F,0x8F,0x6E,0xBB,0x0B,0x3D,0xEA,0x5A,0x13,0x1E,
+0x0C,0x60,0x6F,0xB5,0xC0,0xB5,0x23,0x22,0x2E,0x07,0x0B,0xCB,0xA9,0x74,0xCB,0x47,
+0xBB,0x1D,0xC1,0xD7,0xA5,0x6B,0xCC,0x2F,0xD2,0x42,0xFD,0x49,0xDD,0xA7,0x89,0xCF,
+0x53,0xBA,0xDA,0x00,0x5A,0x28,0xBF,0x82,0xDF,0xF8,0xBA,0x13,0x1D,0x50,0x86,0x82,
+0xFD,0x8E,0x30,0x8F,0x29,0x46,0xB0,0x1E,0x3D,0x35,0xDA,0x38,0x62,0x16,0x18,0x4A,
+0xAD,0xE6,0xB6,0x51,0x6C,0xDE,0xAF,0x62,0xEB,0x01,0xD0,0x1E,0x24,0xFE,0x7A,0x8F,
+0x12,0x1A,0x12,0x68,0xB8,0xFB,0x66,0x99,0x14,0x14,0x45,0x5C,0xAE,0xE7,0xAE,0x69,
+0x17,0x81,0x2B,0x5A,0x37,0xC9,0x5E,0x2A,0xF4,0xC6,0xE2,0xA1,0x5C,0x54,0x9B,0xA6,
+0x54,0x00,0xCF,0xF0,0xF1,0xC1,0xC7,0x98,0x30,0x1A,0x3B,0x36,0x16,0xDB,0xA3,0x6E,
+0xEA,0xFD,0xAD,0xB2,0xC2,0xDA,0xEF,0x02,0x47,0x13,0x8A,0xC0,0xF1,0xB3,0x31,0xAD,
+0x4F,0x1C,0xE1,0x4F,0x9C,0xAF,0x0F,0x0C,0x9D,0xF7,0x78,0x0D,0xD8,0xF4,0x35,0x56,
+0x80,0xDA,0xB7,0x6D,0x17,0x8F,0x9D,0x1E,0x81,0x64,0xE1,0xFE,0xC5,0x45,0xBA,0xAD,
+0x6B,0xB9,0x0A,0x7A,0x4E,0x4F,0x4B,0x84,0xEE,0x4B,0xF1,0x7D,0xDD,0x11,
+};
+
+
+/* subject:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2007 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G4 */
+/* issuer :/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2007 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G4 */
+
+
+const unsigned char VeriSign_Class_3_Public_Primary_Certification_Authority___G4_certificate[904]={
+0x30,0x82,0x03,0x84,0x30,0x82,0x03,0x0A,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x2F,
+0x80,0xFE,0x23,0x8C,0x0E,0x22,0x0F,0x48,0x67,0x12,0x28,0x91,0x87,0xAC,0xB3,0x30,
+0x0A,0x06,0x08,0x2A,0x86,0x48,0xCE,0x3D,0x04,0x03,0x03,0x30,0x81,0xCA,0x31,0x0B,
+0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06,
+0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,
+0x49,0x6E,0x63,0x2E,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56,
+0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65,
+0x74,0x77,0x6F,0x72,0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31,
+0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x37,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67,
+0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,
+0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,
+0x79,0x31,0x45,0x30,0x43,0x06,0x03,0x55,0x04,0x03,0x13,0x3C,0x56,0x65,0x72,0x69,
+0x53,0x69,0x67,0x6E,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x50,0x75,0x62,
+0x6C,0x69,0x63,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,
+0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,
+0x69,0x74,0x79,0x20,0x2D,0x20,0x47,0x34,0x30,0x1E,0x17,0x0D,0x30,0x37,0x31,0x31,
+0x30,0x35,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x38,0x30,0x31,0x31,
+0x38,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0xCA,0x31,0x0B,0x30,0x09,0x06,
+0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,
+0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E,0x63,
+0x2E,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56,0x65,0x72,0x69,
+0x53,0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65,0x74,0x77,0x6F,
+0x72,0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31,0x28,0x63,0x29,
+0x20,0x32,0x30,0x30,0x37,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,
+0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,
+0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x45,
+0x30,0x43,0x06,0x03,0x55,0x04,0x03,0x13,0x3C,0x56,0x65,0x72,0x69,0x53,0x69,0x67,
+0x6E,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x50,0x75,0x62,0x6C,0x69,0x63,
+0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,
+0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,
+0x20,0x2D,0x20,0x47,0x34,0x30,0x76,0x30,0x10,0x06,0x07,0x2A,0x86,0x48,0xCE,0x3D,
+0x02,0x01,0x06,0x05,0x2B,0x81,0x04,0x00,0x22,0x03,0x62,0x00,0x04,0xA7,0x56,0x7A,
+0x7C,0x52,0xDA,0x64,0x9B,0x0E,0x2D,0x5C,0xD8,0x5E,0xAC,0x92,0x3D,0xFE,0x01,0xE6,
+0x19,0x4A,0x3D,0x14,0x03,0x4B,0xFA,0x60,0x27,0x20,0xD9,0x83,0x89,0x69,0xFA,0x54,
+0xC6,0x9A,0x18,0x5E,0x55,0x2A,0x64,0xDE,0x06,0xF6,0x8D,0x4A,0x3B,0xAD,0x10,0x3C,
+0x65,0x3D,0x90,0x88,0x04,0x89,0xE0,0x30,0x61,0xB3,0xAE,0x5D,0x01,0xA7,0x7B,0xDE,
+0x7C,0xB2,0xBE,0xCA,0x65,0x61,0x00,0x86,0xAE,0xDA,0x8F,0x7B,0xD0,0x89,0xAD,0x4D,
+0x1D,0x59,0x9A,0x41,0xB1,0xBC,0x47,0x80,0xDC,0x9E,0x62,0xC3,0xF9,0xA3,0x81,0xB2,
+0x30,0x81,0xAF,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,
+0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,
+0x03,0x02,0x01,0x06,0x30,0x6D,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x01,0x0C,
+0x04,0x61,0x30,0x5F,0xA1,0x5D,0xA0,0x5B,0x30,0x59,0x30,0x57,0x30,0x55,0x16,0x09,
+0x69,0x6D,0x61,0x67,0x65,0x2F,0x67,0x69,0x66,0x30,0x21,0x30,0x1F,0x30,0x07,0x06,
+0x05,0x2B,0x0E,0x03,0x02,0x1A,0x04,0x14,0x8F,0xE5,0xD3,0x1A,0x86,0xAC,0x8D,0x8E,
+0x6B,0xC3,0xCF,0x80,0x6A,0xD4,0x48,0x18,0x2C,0x7B,0x19,0x2E,0x30,0x25,0x16,0x23,
+0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x6C,0x6F,0x67,0x6F,0x2E,0x76,0x65,0x72,0x69,
+0x73,0x69,0x67,0x6E,0x2E,0x63,0x6F,0x6D,0x2F,0x76,0x73,0x6C,0x6F,0x67,0x6F,0x2E,
+0x67,0x69,0x66,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xB3,0x16,
+0x91,0xFD,0xEE,0xA6,0x6E,0xE4,0xB5,0x2E,0x49,0x8F,0x87,0x78,0x81,0x80,0xEC,0xE5,
+0xB1,0xB5,0x30,0x0A,0x06,0x08,0x2A,0x86,0x48,0xCE,0x3D,0x04,0x03,0x03,0x03,0x68,
+0x00,0x30,0x65,0x02,0x30,0x66,0x21,0x0C,0x18,0x26,0x60,0x5A,0x38,0x7B,0x56,0x42,
+0xE0,0xA7,0xFC,0x36,0x84,0x51,0x91,0x20,0x2C,0x76,0x4D,0x43,0x3D,0xC4,0x1D,0x84,
+0x23,0xD0,0xAC,0xD6,0x7C,0x35,0x06,0xCE,0xCD,0x69,0xBD,0x90,0x0D,0xDB,0x6C,0x48,
+0x42,0x1D,0x0E,0xAA,0x42,0x02,0x31,0x00,0x9C,0x3D,0x48,0x39,0x23,0x39,0x58,0x1A,
+0x15,0x12,0x59,0x6A,0x9E,0xEF,0xD5,0x59,0xB2,0x1D,0x52,0x2C,0x99,0x71,0xCD,0xC7,
+0x29,0xDF,0x1B,0x2A,0x61,0x7B,0x71,0xD1,0xDE,0xF3,0xC0,0xE5,0x0D,0x3A,0x4A,0xAA,
+0x2D,0xA7,0xD8,0x86,0x2A,0xDD,0x2E,0x10,
+};
+
+
+/* subject:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2006 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G5 */
+/* issuer :/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2006 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G5 */
+
+
+const unsigned char VeriSign_Class_3_Public_Primary_Certification_Authority___G5_certificate[1239]={
+0x30,0x82,0x04,0xD3,0x30,0x82,0x03,0xBB,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x18,
+0xDA,0xD1,0x9E,0x26,0x7D,0xE8,0xBB,0x4A,0x21,0x58,0xCD,0xCC,0x6B,0x3B,0x4A,0x30,
+0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81,
+0xCA,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,
+0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,
+0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,
+0x13,0x16,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,
+0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,
+0x0B,0x13,0x31,0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x36,0x20,0x56,0x65,0x72,0x69,
+0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,
+0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,
+0x6F,0x6E,0x6C,0x79,0x31,0x45,0x30,0x43,0x06,0x03,0x55,0x04,0x03,0x13,0x3C,0x56,
+0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,
+0x50,0x75,0x62,0x6C,0x69,0x63,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,
+0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,
+0x68,0x6F,0x72,0x69,0x74,0x79,0x20,0x2D,0x20,0x47,0x35,0x30,0x1E,0x17,0x0D,0x30,
+0x36,0x31,0x31,0x30,0x38,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x36,
+0x30,0x37,0x31,0x36,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0xCA,0x31,0x0B,
+0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06,
+0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,
+0x49,0x6E,0x63,0x2E,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56,
+0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65,
+0x74,0x77,0x6F,0x72,0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31,
+0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x36,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67,
+0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,
+0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,
+0x79,0x31,0x45,0x30,0x43,0x06,0x03,0x55,0x04,0x03,0x13,0x3C,0x56,0x65,0x72,0x69,
+0x53,0x69,0x67,0x6E,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x33,0x20,0x50,0x75,0x62,
+0x6C,0x69,0x63,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,
+0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,
+0x69,0x74,0x79,0x20,0x2D,0x20,0x47,0x35,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,
+0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,
+0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xAF,0x24,0x08,0x08,0x29,0x7A,0x35,
+0x9E,0x60,0x0C,0xAA,0xE7,0x4B,0x3B,0x4E,0xDC,0x7C,0xBC,0x3C,0x45,0x1C,0xBB,0x2B,
+0xE0,0xFE,0x29,0x02,0xF9,0x57,0x08,0xA3,0x64,0x85,0x15,0x27,0xF5,0xF1,0xAD,0xC8,
+0x31,0x89,0x5D,0x22,0xE8,0x2A,0xAA,0xA6,0x42,0xB3,0x8F,0xF8,0xB9,0x55,0xB7,0xB1,
+0xB7,0x4B,0xB3,0xFE,0x8F,0x7E,0x07,0x57,0xEC,0xEF,0x43,0xDB,0x66,0x62,0x15,0x61,
+0xCF,0x60,0x0D,0xA4,0xD8,0xDE,0xF8,0xE0,0xC3,0x62,0x08,0x3D,0x54,0x13,0xEB,0x49,
+0xCA,0x59,0x54,0x85,0x26,0xE5,0x2B,0x8F,0x1B,0x9F,0xEB,0xF5,0xA1,0x91,0xC2,0x33,
+0x49,0xD8,0x43,0x63,0x6A,0x52,0x4B,0xD2,0x8F,0xE8,0x70,0x51,0x4D,0xD1,0x89,0x69,
+0x7B,0xC7,0x70,0xF6,0xB3,0xDC,0x12,0x74,0xDB,0x7B,0x5D,0x4B,0x56,0xD3,0x96,0xBF,
+0x15,0x77,0xA1,0xB0,0xF4,0xA2,0x25,0xF2,0xAF,0x1C,0x92,0x67,0x18,0xE5,0xF4,0x06,
+0x04,0xEF,0x90,0xB9,0xE4,0x00,0xE4,0xDD,0x3A,0xB5,0x19,0xFF,0x02,0xBA,0xF4,0x3C,
+0xEE,0xE0,0x8B,0xEB,0x37,0x8B,0xEC,0xF4,0xD7,0xAC,0xF2,0xF6,0xF0,0x3D,0xAF,0xDD,
+0x75,0x91,0x33,0x19,0x1D,0x1C,0x40,0xCB,0x74,0x24,0x19,0x21,0x93,0xD9,0x14,0xFE,
+0xAC,0x2A,0x52,0xC7,0x8F,0xD5,0x04,0x49,0xE4,0x8D,0x63,0x47,0x88,0x3C,0x69,0x83,
+0xCB,0xFE,0x47,0xBD,0x2B,0x7E,0x4F,0xC5,0x95,0xAE,0x0E,0x9D,0xD4,0xD1,0x43,0xC0,
+0x67,0x73,0xE3,0x14,0x08,0x7E,0xE5,0x3F,0x9F,0x73,0xB8,0x33,0x0A,0xCF,0x5D,0x3F,
+0x34,0x87,0x96,0x8A,0xEE,0x53,0xE8,0x25,0x15,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,
+0xB2,0x30,0x81,0xAF,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,
+0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55,0x1D,0x0F,0x01,0x01,0xFF,0x04,
+0x04,0x03,0x02,0x01,0x06,0x30,0x6D,0x06,0x08,0x2B,0x06,0x01,0x05,0x05,0x07,0x01,
+0x0C,0x04,0x61,0x30,0x5F,0xA1,0x5D,0xA0,0x5B,0x30,0x59,0x30,0x57,0x30,0x55,0x16,
+0x09,0x69,0x6D,0x61,0x67,0x65,0x2F,0x67,0x69,0x66,0x30,0x21,0x30,0x1F,0x30,0x07,
+0x06,0x05,0x2B,0x0E,0x03,0x02,0x1A,0x04,0x14,0x8F,0xE5,0xD3,0x1A,0x86,0xAC,0x8D,
+0x8E,0x6B,0xC3,0xCF,0x80,0x6A,0xD4,0x48,0x18,0x2C,0x7B,0x19,0x2E,0x30,0x25,0x16,
+0x23,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x6C,0x6F,0x67,0x6F,0x2E,0x76,0x65,0x72,
+0x69,0x73,0x69,0x67,0x6E,0x2E,0x63,0x6F,0x6D,0x2F,0x76,0x73,0x6C,0x6F,0x67,0x6F,
+0x2E,0x67,0x69,0x66,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0x7F,
+0xD3,0x65,0xA7,0xC2,0xDD,0xEC,0xBB,0xF0,0x30,0x09,0xF3,0x43,0x39,0xFA,0x02,0xAF,
+0x33,0x31,0x33,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,
+0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x93,0x24,0x4A,0x30,0x5F,0x62,0xCF,0xD8,0x1A,
+0x98,0x2F,0x3D,0xEA,0xDC,0x99,0x2D,0xBD,0x77,0xF6,0xA5,0x79,0x22,0x38,0xEC,0xC4,
+0xA7,0xA0,0x78,0x12,0xAD,0x62,0x0E,0x45,0x70,0x64,0xC5,0xE7,0x97,0x66,0x2D,0x98,
+0x09,0x7E,0x5F,0xAF,0xD6,0xCC,0x28,0x65,0xF2,0x01,0xAA,0x08,0x1A,0x47,0xDE,0xF9,
+0xF9,0x7C,0x92,0x5A,0x08,0x69,0x20,0x0D,0xD9,0x3E,0x6D,0x6E,0x3C,0x0D,0x6E,0xD8,
+0xE6,0x06,0x91,0x40,0x18,0xB9,0xF8,0xC1,0xED,0xDF,0xDB,0x41,0xAA,0xE0,0x96,0x20,
+0xC9,0xCD,0x64,0x15,0x38,0x81,0xC9,0x94,0xEE,0xA2,0x84,0x29,0x0B,0x13,0x6F,0x8E,
+0xDB,0x0C,0xDD,0x25,0x02,0xDB,0xA4,0x8B,0x19,0x44,0xD2,0x41,0x7A,0x05,0x69,0x4A,
+0x58,0x4F,0x60,0xCA,0x7E,0x82,0x6A,0x0B,0x02,0xAA,0x25,0x17,0x39,0xB5,0xDB,0x7F,
+0xE7,0x84,0x65,0x2A,0x95,0x8A,0xBD,0x86,0xDE,0x5E,0x81,0x16,0x83,0x2D,0x10,0xCC,
+0xDE,0xFD,0xA8,0x82,0x2A,0x6D,0x28,0x1F,0x0D,0x0B,0xC4,0xE5,0xE7,0x1A,0x26,0x19,
+0xE1,0xF4,0x11,0x6F,0x10,0xB5,0x95,0xFC,0xE7,0x42,0x05,0x32,0xDB,0xCE,0x9D,0x51,
+0x5E,0x28,0xB6,0x9E,0x85,0xD3,0x5B,0xEF,0xA5,0x7D,0x45,0x40,0x72,0x8E,0xB7,0x0E,
+0x6B,0x0E,0x06,0xFB,0x33,0x35,0x48,0x71,0xB8,0x9D,0x27,0x8B,0xC4,0x65,0x5F,0x0D,
+0x86,0x76,0x9C,0x44,0x7A,0xF6,0x95,0x5C,0xF6,0x5D,0x32,0x08,0x33,0xA4,0x54,0xB6,
+0x18,0x3F,0x68,0x5C,0xF2,0x42,0x4A,0x85,0x38,0x54,0x83,0x5F,0xD1,0xE8,0x2C,0xF2,
+0xAC,0x11,0xD6,0xA8,0xED,0x63,0x6A,
+};
+
+
+/* subject:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 1999 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 4 Public Primary Certification Authority - G3 */
+/* issuer :/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 1999 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 4 Public Primary Certification Authority - G3 */
+
+
+const unsigned char Verisign_Class_4_Public_Primary_Certification_Authority___G3_certificate[1054]={
+0x30,0x82,0x04,0x1A,0x30,0x82,0x03,0x02,0x02,0x11,0x00,0xEC,0xA0,0xA7,0x8B,0x6E,
+0x75,0x6A,0x01,0xCF,0xC4,0x7C,0xCC,0x2F,0x94,0x5E,0xD7,0x30,0x0D,0x06,0x09,0x2A,
+0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81,0xCA,0x31,0x0B,0x30,
+0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06,0x03,
+0x55,0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,
+0x6E,0x63,0x2E,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56,0x65,
+0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65,0x74,
+0x77,0x6F,0x72,0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31,0x28,
+0x63,0x29,0x20,0x31,0x39,0x39,0x39,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,
+0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,
+0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,
+0x31,0x45,0x30,0x43,0x06,0x03,0x55,0x04,0x03,0x13,0x3C,0x56,0x65,0x72,0x69,0x53,
+0x69,0x67,0x6E,0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x34,0x20,0x50,0x75,0x62,0x6C,
+0x69,0x63,0x20,0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,
+0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,
+0x74,0x79,0x20,0x2D,0x20,0x47,0x33,0x30,0x1E,0x17,0x0D,0x39,0x39,0x31,0x30,0x30,
+0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x33,0x36,0x30,0x37,0x31,0x36,
+0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,0xCA,0x31,0x0B,0x30,0x09,0x06,0x03,
+0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,
+0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E,
+0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,0x13,0x16,0x56,0x65,0x72,0x69,0x53,
+0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,
+0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,0x0B,0x13,0x31,0x28,0x63,0x29,0x20,
+0x31,0x39,0x39,0x39,0x20,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,
+0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,0x20,0x61,0x75,0x74,0x68,0x6F,0x72,
+0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,0x6F,0x6E,0x6C,0x79,0x31,0x45,0x30,
+0x43,0x06,0x03,0x55,0x04,0x03,0x13,0x3C,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,
+0x20,0x43,0x6C,0x61,0x73,0x73,0x20,0x34,0x20,0x50,0x75,0x62,0x6C,0x69,0x63,0x20,
+0x50,0x72,0x69,0x6D,0x61,0x72,0x79,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,
+0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x20,
+0x2D,0x20,0x47,0x33,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,
+0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,
+0x02,0x82,0x01,0x01,0x00,0xAD,0xCB,0xA5,0x11,0x69,0xC6,0x59,0xAB,0xF1,0x8F,0xB5,
+0x19,0x0F,0x56,0xCE,0xCC,0xB5,0x1F,0x20,0xE4,0x9E,0x26,0x25,0x4B,0xE0,0x73,0x65,
+0x89,0x59,0xDE,0xD0,0x83,0xE4,0xF5,0x0F,0xB5,0xBB,0xAD,0xF1,0x7C,0xE8,0x21,0xFC,
+0xE4,0xE8,0x0C,0xEE,0x7C,0x45,0x22,0x19,0x76,0x92,0xB4,0x13,0xB7,0x20,0x5B,0x09,
+0xFA,0x61,0xAE,0xA8,0xF2,0xA5,0x8D,0x85,0xC2,0x2A,0xD6,0xDE,0x66,0x36,0xD2,0x9B,
+0x02,0xF4,0xA8,0x92,0x60,0x7C,0x9C,0x69,0xB4,0x8F,0x24,0x1E,0xD0,0x86,0x52,0xF6,
+0x32,0x9C,0x41,0x58,0x1E,0x22,0xBD,0xCD,0x45,0x62,0x95,0x08,0x6E,0xD0,0x66,0xDD,
+0x53,0xA2,0xCC,0xF0,0x10,0xDC,0x54,0x73,0x8B,0x04,0xA1,0x46,0x33,0x33,0x5C,0x17,
+0x40,0xB9,0x9E,0x4D,0xD3,0xF3,0xBE,0x55,0x83,0xE8,0xB1,0x89,0x8E,0x5A,0x7C,0x9A,
+0x96,0x22,0x90,0x3B,0x88,0x25,0xF2,0xD2,0x53,0x88,0x02,0x0C,0x0B,0x78,0xF2,0xE6,
+0x37,0x17,0x4B,0x30,0x46,0x07,0xE4,0x80,0x6D,0xA6,0xD8,0x96,0x2E,0xE8,0x2C,0xF8,
+0x11,0xB3,0x38,0x0D,0x66,0xA6,0x9B,0xEA,0xC9,0x23,0x5B,0xDB,0x8E,0xE2,0xF3,0x13,
+0x8E,0x1A,0x59,0x2D,0xAA,0x02,0xF0,0xEC,0xA4,0x87,0x66,0xDC,0xC1,0x3F,0xF5,0xD8,
+0xB9,0xF4,0xEC,0x82,0xC6,0xD2,0x3D,0x95,0x1D,0xE5,0xC0,0x4F,0x84,0xC9,0xD9,0xA3,
+0x44,0x28,0x06,0x6A,0xD7,0x45,0xAC,0xF0,0x6B,0x6A,0xEF,0x4E,0x5F,0xF8,0x11,0x82,
+0x1E,0x38,0x63,0x34,0x66,0x50,0xD4,0x3E,0x93,0x73,0xFA,0x30,0xC3,0x66,0xAD,0xFF,
+0x93,0x2D,0x97,0xEF,0x03,0x02,0x03,0x01,0x00,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86,
+0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x8F,0xFA,
+0x25,0x6B,0x4F,0x5B,0xE4,0xA4,0x4E,0x27,0x55,0xAB,0x22,0x15,0x59,0x3C,0xCA,0xB5,
+0x0A,0xD4,0x4A,0xDB,0xAB,0xDD,0xA1,0x5F,0x53,0xC5,0xA0,0x57,0x39,0xC2,0xCE,0x47,
+0x2B,0xBE,0x3A,0xC8,0x56,0xBF,0xC2,0xD9,0x27,0x10,0x3A,0xB1,0x05,0x3C,0xC0,0x77,
+0x31,0xBB,0x3A,0xD3,0x05,0x7B,0x6D,0x9A,0x1C,0x30,0x8C,0x80,0xCB,0x93,0x93,0x2A,
+0x83,0xAB,0x05,0x51,0x82,0x02,0x00,0x11,0x67,0x6B,0xF3,0x88,0x61,0x47,0x5F,0x03,
+0x93,0xD5,0x5B,0x0D,0xE0,0xF1,0xD4,0xA1,0x32,0x35,0x85,0xB2,0x3A,0xDB,0xB0,0x82,
+0xAB,0xD1,0xCB,0x0A,0xBC,0x4F,0x8C,0x5B,0xC5,0x4B,0x00,0x3B,0x1F,0x2A,0x82,0xA6,
+0x7E,0x36,0x85,0xDC,0x7E,0x3C,0x67,0x00,0xB5,0xE4,0x3B,0x52,0xE0,0xA8,0xEB,0x5D,
+0x15,0xF9,0xC6,0x6D,0xF0,0xAD,0x1D,0x0E,0x85,0xB7,0xA9,0x9A,0x73,0x14,0x5A,0x5B,
+0x8F,0x41,0x28,0xC0,0xD5,0xE8,0x2D,0x4D,0xA4,0x5E,0xCD,0xAA,0xD9,0xED,0xCE,0xDC,
+0xD8,0xD5,0x3C,0x42,0x1D,0x17,0xC1,0x12,0x5D,0x45,0x38,0xC3,0x38,0xF3,0xFC,0x85,
+0x2E,0x83,0x46,0x48,0xB2,0xD7,0x20,0x5F,0x92,0x36,0x8F,0xE7,0x79,0x0F,0x98,0x5E,
+0x99,0xE8,0xF0,0xD0,0xA4,0xBB,0xF5,0x53,0xBD,0x2A,0xCE,0x59,0xB0,0xAF,0x6E,0x7F,
+0x6C,0xBB,0xD2,0x1E,0x00,0xB0,0x21,0xED,0xF8,0x41,0x62,0x82,0xB9,0xD8,0xB2,0xC4,
+0xBB,0x46,0x50,0xF3,0x31,0xC5,0x8F,0x01,0xA8,0x74,0xEB,0xF5,0x78,0x27,0xDA,0xE7,
+0xF7,0x66,0x43,0xF3,0x9E,0x83,0x3E,0x20,0xAA,0xC3,0x35,0x60,0x91,0xCE,
+};
+
+
+/* subject:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2008 VeriSign, Inc. - For authorized use only/CN=VeriSign Universal Root Certification Authority */
+/* issuer :/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2008 VeriSign, Inc. - For authorized use only/CN=VeriSign Universal Root Certification Authority */
+
+
+const unsigned char VeriSign_Universal_Root_Certification_Authority_certificate[1213]={
+0x30,0x82,0x04,0xB9,0x30,0x82,0x03,0xA1,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x40,
+0x1A,0xC4,0x64,0x21,0xB3,0x13,0x21,0x03,0x0E,0xBB,0xE4,0x12,0x1A,0xC5,0x1D,0x30,
+0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x30,0x81,
+0xBD,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,
+0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,
+0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,
+0x13,0x16,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,
+0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,
+0x0B,0x13,0x31,0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x38,0x20,0x56,0x65,0x72,0x69,
+0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,
+0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,
+0x6F,0x6E,0x6C,0x79,0x31,0x38,0x30,0x36,0x06,0x03,0x55,0x04,0x03,0x13,0x2F,0x56,
+0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61,
+0x6C,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,
+0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x1E,
+0x17,0x0D,0x30,0x38,0x30,0x34,0x30,0x32,0x30,0x30,0x30,0x30,0x30,0x30,0x5A,0x17,
+0x0D,0x33,0x37,0x31,0x32,0x30,0x31,0x32,0x33,0x35,0x39,0x35,0x39,0x5A,0x30,0x81,
+0xBD,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x17,
+0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x56,0x65,0x72,0x69,0x53,0x69,0x67,
+0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x31,0x1F,0x30,0x1D,0x06,0x03,0x55,0x04,0x0B,
+0x13,0x16,0x56,0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x54,0x72,0x75,0x73,0x74,
+0x20,0x4E,0x65,0x74,0x77,0x6F,0x72,0x6B,0x31,0x3A,0x30,0x38,0x06,0x03,0x55,0x04,
+0x0B,0x13,0x31,0x28,0x63,0x29,0x20,0x32,0x30,0x30,0x38,0x20,0x56,0x65,0x72,0x69,
+0x53,0x69,0x67,0x6E,0x2C,0x20,0x49,0x6E,0x63,0x2E,0x20,0x2D,0x20,0x46,0x6F,0x72,
+0x20,0x61,0x75,0x74,0x68,0x6F,0x72,0x69,0x7A,0x65,0x64,0x20,0x75,0x73,0x65,0x20,
+0x6F,0x6E,0x6C,0x79,0x31,0x38,0x30,0x36,0x06,0x03,0x55,0x04,0x03,0x13,0x2F,0x56,
+0x65,0x72,0x69,0x53,0x69,0x67,0x6E,0x20,0x55,0x6E,0x69,0x76,0x65,0x72,0x73,0x61,
+0x6C,0x20,0x52,0x6F,0x6F,0x74,0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,
+0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x82,
+0x01,0x22,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,
+0x00,0x03,0x82,0x01,0x0F,0x00,0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0xC7,
+0x61,0x37,0x5E,0xB1,0x01,0x34,0xDB,0x62,0xD7,0x15,0x9B,0xFF,0x58,0x5A,0x8C,0x23,
+0x23,0xD6,0x60,0x8E,0x91,0xD7,0x90,0x98,0x83,0x7A,0xE6,0x58,0x19,0x38,0x8C,0xC5,
+0xF6,0xE5,0x64,0x85,0xB4,0xA2,0x71,0xFB,0xED,0xBD,0xB9,0xDA,0xCD,0x4D,0x00,0xB4,
+0xC8,0x2D,0x73,0xA5,0xC7,0x69,0x71,0x95,0x1F,0x39,0x3C,0xB2,0x44,0x07,0x9C,0xE8,
+0x0E,0xFA,0x4D,0x4A,0xC4,0x21,0xDF,0x29,0x61,0x8F,0x32,0x22,0x61,0x82,0xC5,0x87,
+0x1F,0x6E,0x8C,0x7C,0x5F,0x16,0x20,0x51,0x44,0xD1,0x70,0x4F,0x57,0xEA,0xE3,0x1C,
+0xE3,0xCC,0x79,0xEE,0x58,0xD8,0x0E,0xC2,0xB3,0x45,0x93,0xC0,0x2C,0xE7,0x9A,0x17,
+0x2B,0x7B,0x00,0x37,0x7A,0x41,0x33,0x78,0xE1,0x33,0xE2,0xF3,0x10,0x1A,0x7F,0x87,
+0x2C,0xBE,0xF6,0xF5,0xF7,0x42,0xE2,0xE5,0xBF,0x87,0x62,0x89,0x5F,0x00,0x4B,0xDF,
+0xC5,0xDD,0xE4,0x75,0x44,0x32,0x41,0x3A,0x1E,0x71,0x6E,0x69,0xCB,0x0B,0x75,0x46,
+0x08,0xD1,0xCA,0xD2,0x2B,0x95,0xD0,0xCF,0xFB,0xB9,0x40,0x6B,0x64,0x8C,0x57,0x4D,
+0xFC,0x13,0x11,0x79,0x84,0xED,0x5E,0x54,0xF6,0x34,0x9F,0x08,0x01,0xF3,0x10,0x25,
+0x06,0x17,0x4A,0xDA,0xF1,0x1D,0x7A,0x66,0x6B,0x98,0x60,0x66,0xA4,0xD9,0xEF,0xD2,
+0x2E,0x82,0xF1,0xF0,0xEF,0x09,0xEA,0x44,0xC9,0x15,0x6A,0xE2,0x03,0x6E,0x33,0xD3,
+0xAC,0x9F,0x55,0x00,0xC7,0xF6,0x08,0x6A,0x94,0xB9,0x5F,0xDC,0xE0,0x33,0xF1,0x84,
+0x60,0xF9,0x5B,0x27,0x11,0xB4,0xFC,0x16,0xF2,0xBB,0x56,0x6A,0x80,0x25,0x8D,0x02,
+0x03,0x01,0x00,0x01,0xA3,0x81,0xB2,0x30,0x81,0xAF,0x30,0x0F,0x06,0x03,0x55,0x1D,
+0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x0E,0x06,0x03,0x55,
+0x1D,0x0F,0x01,0x01,0xFF,0x04,0x04,0x03,0x02,0x01,0x06,0x30,0x6D,0x06,0x08,0x2B,
+0x06,0x01,0x05,0x05,0x07,0x01,0x0C,0x04,0x61,0x30,0x5F,0xA1,0x5D,0xA0,0x5B,0x30,
+0x59,0x30,0x57,0x30,0x55,0x16,0x09,0x69,0x6D,0x61,0x67,0x65,0x2F,0x67,0x69,0x66,
+0x30,0x21,0x30,0x1F,0x30,0x07,0x06,0x05,0x2B,0x0E,0x03,0x02,0x1A,0x04,0x14,0x8F,
+0xE5,0xD3,0x1A,0x86,0xAC,0x8D,0x8E,0x6B,0xC3,0xCF,0x80,0x6A,0xD4,0x48,0x18,0x2C,
+0x7B,0x19,0x2E,0x30,0x25,0x16,0x23,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x6C,0x6F,
+0x67,0x6F,0x2E,0x76,0x65,0x72,0x69,0x73,0x69,0x67,0x6E,0x2E,0x63,0x6F,0x6D,0x2F,
+0x76,0x73,0x6C,0x6F,0x67,0x6F,0x2E,0x67,0x69,0x66,0x30,0x1D,0x06,0x03,0x55,0x1D,
+0x0E,0x04,0x16,0x04,0x14,0xB6,0x77,0xFA,0x69,0x48,0x47,0x9F,0x53,0x12,0xD5,0xC2,
+0xEA,0x07,0x32,0x76,0x07,0xD1,0x97,0x07,0x19,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,
+0x86,0xF7,0x0D,0x01,0x01,0x0B,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x4A,0xF8,0xF8,
+0xB0,0x03,0xE6,0x2C,0x67,0x7B,0xE4,0x94,0x77,0x63,0xCC,0x6E,0x4C,0xF9,0x7D,0x0E,
+0x0D,0xDC,0xC8,0xB9,0x35,0xB9,0x70,0x4F,0x63,0xFA,0x24,0xFA,0x6C,0x83,0x8C,0x47,
+0x9D,0x3B,0x63,0xF3,0x9A,0xF9,0x76,0x32,0x95,0x91,0xB1,0x77,0xBC,0xAC,0x9A,0xBE,
+0xB1,0xE4,0x31,0x21,0xC6,0x81,0x95,0x56,0x5A,0x0E,0xB1,0xC2,0xD4,0xB1,0xA6,0x59,
+0xAC,0xF1,0x63,0xCB,0xB8,0x4C,0x1D,0x59,0x90,0x4A,0xEF,0x90,0x16,0x28,0x1F,0x5A,
+0xAE,0x10,0xFB,0x81,0x50,0x38,0x0C,0x6C,0xCC,0xF1,0x3D,0xC3,0xF5,0x63,0xE3,0xB3,
+0xE3,0x21,0xC9,0x24,0x39,0xE9,0xFD,0x15,0x66,0x46,0xF4,0x1B,0x11,0xD0,0x4D,0x73,
+0xA3,0x7D,0x46,0xF9,0x3D,0xED,0xA8,0x5F,0x62,0xD4,0xF1,0x3F,0xF8,0xE0,0x74,0x57,
+0x2B,0x18,0x9D,0x81,0xB4,0xC4,0x28,0xDA,0x94,0x97,0xA5,0x70,0xEB,0xAC,0x1D,0xBE,
+0x07,0x11,0xF0,0xD5,0xDB,0xDD,0xE5,0x8C,0xF0,0xD5,0x32,0xB0,0x83,0xE6,0x57,0xE2,
+0x8F,0xBF,0xBE,0xA1,0xAA,0xBF,0x3D,0x1D,0xB5,0xD4,0x38,0xEA,0xD7,0xB0,0x5C,0x3A,
+0x4F,0x6A,0x3F,0x8F,0xC0,0x66,0x6C,0x63,0xAA,0xE9,0xD9,0xA4,0x16,0xF4,0x81,0xD1,
+0x95,0x14,0x0E,0x7D,0xCD,0x95,0x34,0xD9,0xD2,0x8F,0x70,0x73,0x81,0x7B,0x9C,0x7E,
+0xBD,0x98,0x61,0xD8,0x45,0x87,0x98,0x90,0xC5,0xEB,0x86,0x30,0xC6,0x35,0xBF,0xF0,
+0xFF,0xC3,0x55,0x88,0x83,0x4B,0xEF,0x05,0x92,0x06,0x71,0xF2,0xB8,0x98,0x93,0xB7,
+0xEC,0xCD,0x82,0x61,0xF1,0x38,0xE6,0x4F,0x97,0x98,0x2A,0x5A,0x8D,
+};
+
+
+/* subject:/C=US/OU=www.xrampsecurity.com/O=XRamp Security Services Inc/CN=XRamp Global Certification Authority */
+/* issuer :/C=US/OU=www.xrampsecurity.com/O=XRamp Security Services Inc/CN=XRamp Global Certification Authority */
+
+
+const unsigned char XRamp_Global_CA_Root_certificate[1076]={
+0x30,0x82,0x04,0x30,0x30,0x82,0x03,0x18,0xA0,0x03,0x02,0x01,0x02,0x02,0x10,0x50,
+0x94,0x6C,0xEC,0x18,0xEA,0xD5,0x9C,0x4D,0xD5,0x97,0xEF,0x75,0x8F,0xA0,0xAD,0x30,
+0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x30,0x81,
+0x82,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x1E,
+0x30,0x1C,0x06,0x03,0x55,0x04,0x0B,0x13,0x15,0x77,0x77,0x77,0x2E,0x78,0x72,0x61,
+0x6D,0x70,0x73,0x65,0x63,0x75,0x72,0x69,0x74,0x79,0x2E,0x63,0x6F,0x6D,0x31,0x24,
+0x30,0x22,0x06,0x03,0x55,0x04,0x0A,0x13,0x1B,0x58,0x52,0x61,0x6D,0x70,0x20,0x53,
+0x65,0x63,0x75,0x72,0x69,0x74,0x79,0x20,0x53,0x65,0x72,0x76,0x69,0x63,0x65,0x73,
+0x20,0x49,0x6E,0x63,0x31,0x2D,0x30,0x2B,0x06,0x03,0x55,0x04,0x03,0x13,0x24,0x58,
+0x52,0x61,0x6D,0x70,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x43,0x65,0x72,0x74,
+0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,0x75,0x74,0x68,0x6F,0x72,
+0x69,0x74,0x79,0x30,0x1E,0x17,0x0D,0x30,0x34,0x31,0x31,0x30,0x31,0x31,0x37,0x31,
+0x34,0x30,0x34,0x5A,0x17,0x0D,0x33,0x35,0x30,0x31,0x30,0x31,0x30,0x35,0x33,0x37,
+0x31,0x39,0x5A,0x30,0x81,0x82,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,
+0x02,0x55,0x53,0x31,0x1E,0x30,0x1C,0x06,0x03,0x55,0x04,0x0B,0x13,0x15,0x77,0x77,
+0x77,0x2E,0x78,0x72,0x61,0x6D,0x70,0x73,0x65,0x63,0x75,0x72,0x69,0x74,0x79,0x2E,
+0x63,0x6F,0x6D,0x31,0x24,0x30,0x22,0x06,0x03,0x55,0x04,0x0A,0x13,0x1B,0x58,0x52,
+0x61,0x6D,0x70,0x20,0x53,0x65,0x63,0x75,0x72,0x69,0x74,0x79,0x20,0x53,0x65,0x72,
+0x76,0x69,0x63,0x65,0x73,0x20,0x49,0x6E,0x63,0x31,0x2D,0x30,0x2B,0x06,0x03,0x55,
+0x04,0x03,0x13,0x24,0x58,0x52,0x61,0x6D,0x70,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,
+0x20,0x43,0x65,0x72,0x74,0x69,0x66,0x69,0x63,0x61,0x74,0x69,0x6F,0x6E,0x20,0x41,
+0x75,0x74,0x68,0x6F,0x72,0x69,0x74,0x79,0x30,0x82,0x01,0x22,0x30,0x0D,0x06,0x09,
+0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x82,0x01,0x0F,0x00,
+0x30,0x82,0x01,0x0A,0x02,0x82,0x01,0x01,0x00,0x98,0x24,0x1E,0xBD,0x15,0xB4,0xBA,
+0xDF,0xC7,0x8C,0xA5,0x27,0xB6,0x38,0x0B,0x69,0xF3,0xB6,0x4E,0xA8,0x2C,0x2E,0x21,
+0x1D,0x5C,0x44,0xDF,0x21,0x5D,0x7E,0x23,0x74,0xFE,0x5E,0x7E,0xB4,0x4A,0xB7,0xA6,
+0xAD,0x1F,0xAE,0xE0,0x06,0x16,0xE2,0x9B,0x5B,0xD9,0x67,0x74,0x6B,0x5D,0x80,0x8F,
+0x29,0x9D,0x86,0x1B,0xD9,0x9C,0x0D,0x98,0x6D,0x76,0x10,0x28,0x58,0xE4,0x65,0xB0,
+0x7F,0x4A,0x98,0x79,0x9F,0xE0,0xC3,0x31,0x7E,0x80,0x2B,0xB5,0x8C,0xC0,0x40,0x3B,
+0x11,0x86,0xD0,0xCB,0xA2,0x86,0x36,0x60,0xA4,0xD5,0x30,0x82,0x6D,0xD9,0x6E,0xD0,
+0x0F,0x12,0x04,0x33,0x97,0x5F,0x4F,0x61,0x5A,0xF0,0xE4,0xF9,0x91,0xAB,0xE7,0x1D,
+0x3B,0xBC,0xE8,0xCF,0xF4,0x6B,0x2D,0x34,0x7C,0xE2,0x48,0x61,0x1C,0x8E,0xF3,0x61,
+0x44,0xCC,0x6F,0xA0,0x4A,0xA9,0x94,0xB0,0x4D,0xDA,0xE7,0xA9,0x34,0x7A,0x72,0x38,
+0xA8,0x41,0xCC,0x3C,0x94,0x11,0x7D,0xEB,0xC8,0xA6,0x8C,0xB7,0x86,0xCB,0xCA,0x33,
+0x3B,0xD9,0x3D,0x37,0x8B,0xFB,0x7A,0x3E,0x86,0x2C,0xE7,0x73,0xD7,0x0A,0x57,0xAC,
+0x64,0x9B,0x19,0xEB,0xF4,0x0F,0x04,0x08,0x8A,0xAC,0x03,0x17,0x19,0x64,0xF4,0x5A,
+0x25,0x22,0x8D,0x34,0x2C,0xB2,0xF6,0x68,0x1D,0x12,0x6D,0xD3,0x8A,0x1E,0x14,0xDA,
+0xC4,0x8F,0xA6,0xE2,0x23,0x85,0xD5,0x7A,0x0D,0xBD,0x6A,0xE0,0xE9,0xEC,0xEC,0x17,
+0xBB,0x42,0x1B,0x67,0xAA,0x25,0xED,0x45,0x83,0x21,0xFC,0xC1,0xC9,0x7C,0xD5,0x62,
+0x3E,0xFA,0xF2,0xC5,0x2D,0xD3,0xFD,0xD4,0x65,0x02,0x03,0x01,0x00,0x01,0xA3,0x81,
+0x9F,0x30,0x81,0x9C,0x30,0x13,0x06,0x09,0x2B,0x06,0x01,0x04,0x01,0x82,0x37,0x14,
+0x02,0x04,0x06,0x1E,0x04,0x00,0x43,0x00,0x41,0x30,0x0B,0x06,0x03,0x55,0x1D,0x0F,
+0x04,0x04,0x03,0x02,0x01,0x86,0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,
+0x04,0x05,0x30,0x03,0x01,0x01,0xFF,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,
+0x04,0x14,0xC6,0x4F,0xA2,0x3D,0x06,0x63,0x84,0x09,0x9C,0xCE,0x62,0xE4,0x04,0xAC,
+0x8D,0x5C,0xB5,0xE9,0xB6,0x1B,0x30,0x36,0x06,0x03,0x55,0x1D,0x1F,0x04,0x2F,0x30,
+0x2D,0x30,0x2B,0xA0,0x29,0xA0,0x27,0x86,0x25,0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,
+0x63,0x72,0x6C,0x2E,0x78,0x72,0x61,0x6D,0x70,0x73,0x65,0x63,0x75,0x72,0x69,0x74,
+0x79,0x2E,0x63,0x6F,0x6D,0x2F,0x58,0x47,0x43,0x41,0x2E,0x63,0x72,0x6C,0x30,0x10,
+0x06,0x09,0x2B,0x06,0x01,0x04,0x01,0x82,0x37,0x15,0x01,0x04,0x03,0x02,0x01,0x01,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x05,0x05,0x00,0x03,
+0x82,0x01,0x01,0x00,0x91,0x15,0x39,0x03,0x01,0x1B,0x67,0xFB,0x4A,0x1C,0xF9,0x0A,
+0x60,0x5B,0xA1,0xDA,0x4D,0x97,0x62,0xF9,0x24,0x53,0x27,0xD7,0x82,0x64,0x4E,0x90,
+0x2E,0xC3,0x49,0x1B,0x2B,0x9A,0xDC,0xFC,0xA8,0x78,0x67,0x35,0xF1,0x1D,0xF0,0x11,
+0xBD,0xB7,0x48,0xE3,0x10,0xF6,0x0D,0xDF,0x3F,0xD2,0xC9,0xB6,0xAA,0x55,0xA4,0x48,
+0xBA,0x02,0xDB,0xDE,0x59,0x2E,0x15,0x5B,0x3B,0x9D,0x16,0x7D,0x47,0xD7,0x37,0xEA,
+0x5F,0x4D,0x76,0x12,0x36,0xBB,0x1F,0xD7,0xA1,0x81,0x04,0x46,0x20,0xA3,0x2C,0x6D,
+0xA9,0x9E,0x01,0x7E,0x3F,0x29,0xCE,0x00,0x93,0xDF,0xFD,0xC9,0x92,0x73,0x89,0x89,
+0x64,0x9E,0xE7,0x2B,0xE4,0x1C,0x91,0x2C,0xD2,0xB9,0xCE,0x7D,0xCE,0x6F,0x31,0x99,
+0xD3,0xE6,0xBE,0xD2,0x1E,0x90,0xF0,0x09,0x14,0x79,0x5C,0x23,0xAB,0x4D,0xD2,0xDA,
+0x21,0x1F,0x4D,0x99,0x79,0x9D,0xE1,0xCF,0x27,0x9F,0x10,0x9B,0x1C,0x88,0x0D,0xB0,
+0x8A,0x64,0x41,0x31,0xB8,0x0E,0x6C,0x90,0x24,0xA4,0x9B,0x5C,0x71,0x8F,0xBA,0xBB,
+0x7E,0x1C,0x1B,0xDB,0x6A,0x80,0x0F,0x21,0xBC,0xE9,0xDB,0xA6,0xB7,0x40,0xF4,0xB2,
+0x8B,0xA9,0xB1,0xE4,0xEF,0x9A,0x1A,0xD0,0x3D,0x69,0x99,0xEE,0xA8,0x28,0xA3,0xE1,
+0x3C,0xB3,0xF0,0xB2,0x11,0x9C,0xCF,0x7C,0x40,0xE6,0xDD,0xE7,0x43,0x7D,0xA2,0xD8,
+0x3A,0xB5,0xA9,0x8D,0xF2,0x34,0x99,0xC4,0xD4,0x10,0xE1,0x06,0xFD,0x09,0x84,0x10,
+0x3B,0xEE,0xC4,0x4C,0xF4,0xEC,0x27,0x7C,0x42,0xC2,0x74,0x7C,0x82,0x8A,0x09,0xC9,
+0xB4,0x03,0x25,0xBC,
+};
+
+
+const unsigned char* kSSLCertCertificateList[] = {
+ AddTrust_External_Root_certificate,
+ AddTrust_Low_Value_Services_Root_certificate,
+ AddTrust_Public_Services_Root_certificate,
+ AddTrust_Qualified_Certificates_Root_certificate,
+ AffirmTrust_Commercial_certificate,
+ AffirmTrust_Networking_certificate,
+ AffirmTrust_Premium_certificate,
+ AffirmTrust_Premium_ECC_certificate,
+ America_Online_Root_Certification_Authority_1_certificate,
+ America_Online_Root_Certification_Authority_2_certificate,
+ Baltimore_CyberTrust_Root_certificate,
+ Comodo_AAA_Services_root_certificate,
+ COMODO_Certification_Authority_certificate,
+ COMODO_ECC_Certification_Authority_certificate,
+ Comodo_Secure_Services_root_certificate,
+ Comodo_Trusted_Services_root_certificate,
+ Cybertrust_Global_Root_certificate,
+ DigiCert_Assured_ID_Root_CA_certificate,
+ DigiCert_Global_Root_CA_certificate,
+ DigiCert_High_Assurance_EV_Root_CA_certificate,
+ Entrust_net_Premium_2048_Secure_Server_CA_certificate,
+ Entrust_net_Secure_Server_CA_certificate,
+ Entrust_Root_Certification_Authority_certificate,
+ Equifax_Secure_CA_certificate,
+ Equifax_Secure_eBusiness_CA_1_certificate,
+ Equifax_Secure_eBusiness_CA_2_certificate,
+ Equifax_Secure_Global_eBusiness_CA_certificate,
+ GeoTrust_Global_CA_certificate,
+ GeoTrust_Global_CA_2_certificate,
+ GeoTrust_Primary_Certification_Authority_certificate,
+ GeoTrust_Primary_Certification_Authority___G2_certificate,
+ GeoTrust_Primary_Certification_Authority___G3_certificate,
+ GeoTrust_Universal_CA_certificate,
+ GeoTrust_Universal_CA_2_certificate,
+ GlobalSign_Root_CA_certificate,
+ GlobalSign_Root_CA___R2_certificate,
+ GlobalSign_Root_CA___R3_certificate,
+ Go_Daddy_Class_2_CA_certificate,
+ Go_Daddy_Root_Certificate_Authority___G2_certificate,
+ GTE_CyberTrust_Global_Root_certificate,
+ Network_Solutions_Certificate_Authority_certificate,
+ RSA_Root_Certificate_1_certificate,
+ Starfield_Class_2_CA_certificate,
+ Starfield_Root_Certificate_Authority___G2_certificate,
+ Starfield_Services_Root_Certificate_Authority___G2_certificate,
+ StartCom_Certification_Authority_certificate,
+ StartCom_Certification_Authority_G2_certificate,
+ TC_TrustCenter_Class_2_CA_II_certificate,
+ TC_TrustCenter_Class_3_CA_II_certificate,
+ TC_TrustCenter_Universal_CA_I_certificate,
+ TC_TrustCenter_Universal_CA_III_certificate,
+ Thawte_Premium_Server_CA_certificate,
+ thawte_Primary_Root_CA_certificate,
+ thawte_Primary_Root_CA___G2_certificate,
+ thawte_Primary_Root_CA___G3_certificate,
+ Thawte_Server_CA_certificate,
+ UTN_DATACorp_SGC_Root_CA_certificate,
+ UTN_USERFirst_Hardware_Root_CA_certificate,
+ ValiCert_Class_1_VA_certificate,
+ ValiCert_Class_2_VA_certificate,
+ Verisign_Class_3_Public_Primary_Certification_Authority_certificate,
+ Verisign_Class_3_Public_Primary_Certification_Authority___G2_certificate,
+ Verisign_Class_3_Public_Primary_Certification_Authority___G3_certificate,
+ VeriSign_Class_3_Public_Primary_Certification_Authority___G4_certificate,
+ VeriSign_Class_3_Public_Primary_Certification_Authority___G5_certificate,
+ Verisign_Class_4_Public_Primary_Certification_Authority___G3_certificate,
+ VeriSign_Universal_Root_Certification_Authority_certificate,
+ XRamp_Global_CA_Root_certificate,
+};
+
+const size_t kSSLCertCertificateSizeList[] = {
+  1082,
+  1052,
+  1049,
+  1058,
+  848,
+  848,
+  1354,
+  514,
+  936,
+  1448,
+  891,
+  1078,
+  1057,
+  653,
+  1091,
+  1095,
+  933,
+  955,
+  947,
+  969,
+  1120,
+  1244,
+  1173,
+  804,
+  646,
+  804,
+  660,
+  856,
+  874,
+  896,
+  690,
+  1026,
+  1388,
+  1392,
+  889,
+  958,
+  867,
+  1028,
+  969,
+  606,
+  1002,
+  747,
+  1043,
+  993,
+  1011,
+  1931,
+  1383,
+  1198,
+  1198,
+  993,
+  997,
+  811,
+  1060,
+  652,
+  1070,
+  791,
+  1122,
+  1144,
+  747,
+  747,
+  576,
+  774,
+  1054,
+  904,
+  1239,
+  1054,
+  1213,
+  1076,
+};
+
diff --git a/talk/base/sslsocketfactory.cc b/talk/base/sslsocketfactory.cc
new file mode 100644
index 0000000..f44724e
--- /dev/null
+++ b/talk/base/sslsocketfactory.cc
@@ -0,0 +1,192 @@
+/*
+ * libjingle
+ * Copyright 2007, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/autodetectproxy.h"
+#include "talk/base/httpcommon.h"
+#include "talk/base/httpcommon-inl.h"
+#include "talk/base/socketadapters.h"
+#include "talk/base/ssladapter.h"
+#include "talk/base/sslsocketfactory.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// ProxySocketAdapter
+// TODO: Consider combining AutoDetectProxy and ProxySocketAdapter.  I think
+// the socket adapter is the more appropriate idiom for automatic proxy
+// detection.  We may or may not want to combine proxydetect.* as well.
+///////////////////////////////////////////////////////////////////////////////
+
+class ProxySocketAdapter : public AsyncSocketAdapter {
+ public:
+  ProxySocketAdapter(SslSocketFactory* factory, int family, int type)
+      : AsyncSocketAdapter(NULL), factory_(factory), family_(family),
+        type_(type), detect_(NULL) {
+  }
+  virtual ~ProxySocketAdapter() {
+    Close();
+  }
+
+  virtual int Connect(const SocketAddress& addr) {
+    ASSERT(NULL == detect_);
+    ASSERT(NULL == socket_);
+    remote_ = addr;
+    if (remote_.IsAnyIP() && remote_.hostname().empty()) {
+      LOG_F(LS_ERROR) << "Empty address";
+      return SOCKET_ERROR;
+    }
+    Url<char> url("/", remote_.HostAsURIString(), remote_.port());
+    detect_ = new AutoDetectProxy(factory_->agent_);
+    detect_->set_server_url(url.url());
+    detect_->SignalWorkDone.connect(this,
+        &ProxySocketAdapter::OnProxyDetectionComplete);
+    detect_->Start();
+    return SOCKET_ERROR;
+  }
+  virtual int GetError() const {
+    if (socket_) {
+      return socket_->GetError();
+    }
+    return detect_ ? EWOULDBLOCK : EADDRNOTAVAIL;
+  }
+  virtual int Close() {
+    if (socket_) {
+      return socket_->Close();
+    }
+    if (detect_) {
+      detect_->Destroy(false);
+      detect_ = NULL;
+    }
+    return 0;
+  }
+  virtual ConnState GetState() const {
+    if (socket_) {
+      return socket_->GetState();
+    }
+    return detect_ ? CS_CONNECTING : CS_CLOSED;
+  }
+
+private:
+  // AutoDetectProxy Slots
+  void OnProxyDetectionComplete(SignalThread* thread) {
+    ASSERT(detect_ == thread);
+    Attach(factory_->CreateProxySocket(detect_->proxy(), family_, type_));
+    detect_->Release();
+    detect_ = NULL;
+    if (0 == AsyncSocketAdapter::Connect(remote_)) {
+      SignalConnectEvent(this);
+    } else if (!IsBlockingError(socket_->GetError())) {
+      SignalCloseEvent(this, socket_->GetError());
+    }
+  }
+
+  SslSocketFactory* factory_;
+  int family_;
+  int type_;
+  SocketAddress remote_;
+  AutoDetectProxy* detect_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// SslSocketFactory
+///////////////////////////////////////////////////////////////////////////////
+
+Socket* SslSocketFactory::CreateSocket(int type) {
+  return CreateSocket(AF_INET, type);
+}
+
+Socket* SslSocketFactory::CreateSocket(int family, int type) {
+  return factory_->CreateSocket(family, type);
+}
+
+AsyncSocket* SslSocketFactory::CreateAsyncSocket(int type) {
+  return CreateAsyncSocket(AF_INET, type);
+}
+
+AsyncSocket* SslSocketFactory::CreateAsyncSocket(int family, int type) {
+  if (autodetect_proxy_) {
+    return new ProxySocketAdapter(this, family, type);
+  } else {
+    return CreateProxySocket(proxy_, family, type);
+  }
+}
+
+
+AsyncSocket* SslSocketFactory::CreateProxySocket(const ProxyInfo& proxy,
+                                                 int family,
+                                                 int type) {
+  AsyncSocket* socket = factory_->CreateAsyncSocket(family, type);
+  if (!socket)
+    return NULL;
+
+  // Binary logging happens at the lowest level
+  if (!logging_label_.empty() && binary_mode_) {
+    socket = new LoggingSocketAdapter(socket, logging_level_,
+                                      logging_label_.c_str(), binary_mode_);
+  }
+
+  if (proxy.type) {
+    AsyncSocket* proxy_socket = 0;
+    if (proxy_.type == PROXY_SOCKS5) {
+      proxy_socket = new AsyncSocksProxySocket(socket, proxy.address,
+                                               proxy.username, proxy.password);
+    } else {
+      // Note: we are trying unknown proxies as HTTPS currently
+      AsyncHttpsProxySocket* http_proxy =
+          new AsyncHttpsProxySocket(socket, agent_, proxy.address,
+                                    proxy.username, proxy.password);
+      http_proxy->SetForceConnect(force_connect_ || !hostname_.empty());
+      proxy_socket = http_proxy;
+    }
+    if (!proxy_socket) {
+      delete socket;
+      return NULL;
+    }
+    socket = proxy_socket;  // for our purposes the proxy is now the socket
+  }
+
+  if (!hostname_.empty()) {
+    if (SSLAdapter* ssl_adapter = SSLAdapter::Create(socket)) {
+      ssl_adapter->set_ignore_bad_cert(ignore_bad_cert_);
+      ssl_adapter->StartSSL(hostname_.c_str(), true);
+      socket = ssl_adapter;
+    } else {
+      LOG_F(LS_ERROR) << "SSL unavailable";
+    }
+  }
+
+  // Regular logging occurs at the highest level
+  if (!logging_label_.empty() && !binary_mode_) {
+    socket = new LoggingSocketAdapter(socket, logging_level_,
+                                      logging_label_.c_str(), binary_mode_);
+  }
+  return socket;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
diff --git a/talk/base/sslsocketfactory.h b/talk/base/sslsocketfactory.h
new file mode 100644
index 0000000..32acd15
--- /dev/null
+++ b/talk/base/sslsocketfactory.h
@@ -0,0 +1,98 @@
+/*
+ * libjingle
+ * Copyright 2007, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_SSLSOCKETFACTORY_H__
+#define TALK_BASE_SSLSOCKETFACTORY_H__
+
+#include "talk/base/proxyinfo.h"
+#include "talk/base/socketserver.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// SslSocketFactory
+///////////////////////////////////////////////////////////////////////////////
+
+class SslSocketFactory : public SocketFactory {
+ public:
+  SslSocketFactory(SocketFactory* factory, const std::string& user_agent)
+     : factory_(factory), agent_(user_agent), autodetect_proxy_(true),
+       force_connect_(false), logging_level_(LS_VERBOSE), binary_mode_(false),
+       ignore_bad_cert_(false) {
+  }
+
+  void SetAutoDetectProxy() {
+    autodetect_proxy_ = true;
+  }
+  void SetForceConnect(bool force) {
+    force_connect_ = force;
+  }
+  void SetProxy(const ProxyInfo& proxy) {
+    autodetect_proxy_ = false;
+    proxy_ = proxy;
+  }
+  bool autodetect_proxy() const { return autodetect_proxy_; }
+  const ProxyInfo& proxy() const { return proxy_; }
+
+  void UseSSL(const char* hostname) { hostname_ = hostname; }
+  void DisableSSL() { hostname_.clear(); }
+  void SetIgnoreBadCert(bool ignore) { ignore_bad_cert_ = ignore; }
+  bool ignore_bad_cert() const { return ignore_bad_cert_; }
+
+  void SetLogging(LoggingSeverity level, const std::string& label, 
+                  bool binary_mode = false) {
+    logging_level_ = level;
+    logging_label_ = label;
+    binary_mode_ = binary_mode;
+  }
+
+  // SocketFactory Interface
+  virtual Socket* CreateSocket(int type);
+  virtual Socket* CreateSocket(int family, int type);
+
+  virtual AsyncSocket* CreateAsyncSocket(int type);
+  virtual AsyncSocket* CreateAsyncSocket(int family, int type);
+
+ private:
+  friend class ProxySocketAdapter;
+  AsyncSocket* CreateProxySocket(const ProxyInfo& proxy, int family, int type);
+
+  SocketFactory* factory_;
+  std::string agent_;
+  bool autodetect_proxy_, force_connect_;
+  ProxyInfo proxy_;
+  std::string hostname_, logging_label_;
+  LoggingSeverity logging_level_;
+  bool binary_mode_;
+  bool ignore_bad_cert_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SSLSOCKETFACTORY_H__
diff --git a/talk/base/sslstreamadapter.cc b/talk/base/sslstreamadapter.cc
new file mode 100644
index 0000000..dc59ee0
--- /dev/null
+++ b/talk/base/sslstreamadapter.cc
@@ -0,0 +1,94 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif  // HAVE_CONFIG_H
+
+#include "talk/base/sslstreamadapter.h"
+#include "talk/base/sslconfig.h"
+
+#if SSL_USE_SCHANNEL
+
+// SChannel support for DTLS and peer-to-peer mode are not
+// done.
+#elif SSL_USE_OPENSSL  // && !SSL_USE_SCHANNEL
+
+#include "talk/base/opensslstreamadapter.h"
+
+#elif SSL_USE_NSS      // && !SSL_USE_SCHANNEL && !SSL_USE_OPENSSL
+
+#include "talk/base/nssstreamadapter.h"
+
+#endif  // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL && !SSL_USE_NSS
+
+///////////////////////////////////////////////////////////////////////////////
+
+namespace talk_base {
+
+SSLStreamAdapter* SSLStreamAdapter::Create(StreamInterface* stream) {
+#if SSL_USE_SCHANNEL
+  return NULL;
+#elif SSL_USE_OPENSSL  // !SSL_USE_SCHANNEL
+  return new OpenSSLStreamAdapter(stream);
+#elif SSL_USE_NSS     //  !SSL_USE_SCHANNEL && !SSL_USE_OPENSSL
+  return new NSSStreamAdapter(stream);
+#else  // !SSL_USE_SCHANNEL && !SSL_USE_OPENSSL && !SSL_USE_NSS
+  return NULL;
+#endif
+}
+
+// Note: this matches the logic above with SCHANNEL dominating
+#if SSL_USE_SCHANNEL
+bool SSLStreamAdapter::HaveDtls() { return false; }
+bool SSLStreamAdapter::HaveDtlsSrtp() { return false; }
+bool SSLStreamAdapter::HaveExporter() { return false; }
+#elif SSL_USE_OPENSSL
+bool SSLStreamAdapter::HaveDtls() {
+  return OpenSSLStreamAdapter::HaveDtls();
+}
+bool SSLStreamAdapter::HaveDtlsSrtp() {
+  return OpenSSLStreamAdapter::HaveDtlsSrtp();
+}
+bool SSLStreamAdapter::HaveExporter() {
+  return OpenSSLStreamAdapter::HaveExporter();
+}
+#elif SSL_USE_NSS
+bool SSLStreamAdapter::HaveDtls() {
+  return NSSStreamAdapter::HaveDtls();
+}
+bool SSLStreamAdapter::HaveDtlsSrtp() {
+  return NSSStreamAdapter::HaveDtlsSrtp();
+}
+bool SSLStreamAdapter::HaveExporter() {
+  return NSSStreamAdapter::HaveExporter();
+}
+#endif  // !SSL_USE_SCHANNEL && !SSL_USE_OPENSSL && !SSL_USE_NSS
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
diff --git a/talk/base/sslstreamadapter.h b/talk/base/sslstreamadapter.h
new file mode 100644
index 0000000..2afe1da
--- /dev/null
+++ b/talk/base/sslstreamadapter.h
@@ -0,0 +1,185 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_SSLSTREAMADAPTER_H__
+#define TALK_BASE_SSLSTREAMADAPTER_H__
+
+#include <string>
+#include <vector>
+
+#include "talk/base/stream.h"
+#include "talk/base/sslidentity.h"
+
+namespace talk_base {
+
+// SSLStreamAdapter : A StreamInterfaceAdapter that does SSL/TLS.
+// After SSL has been started, the stream will only open on successful
+// SSL verification of certificates, and the communication is
+// encrypted of course.
+//
+// This class was written with SSLAdapter as a starting point. It
+// offers a similar interface, with two differences: there is no
+// support for a restartable SSL connection, and this class has a
+// peer-to-peer mode.
+//
+// The SSL library requires initialization and cleanup. Static method
+// for doing this are in SSLAdapter. They should possibly be moved out
+// to a neutral class.
+
+
+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,
+  // (using the selected implementation for the platform).
+  // Caller is responsible for freeing the returned object.
+  static SSLStreamAdapter* Create(StreamInterface* stream);
+
+  explicit SSLStreamAdapter(StreamInterface* stream)
+      : StreamAdapterInterface(stream), ignore_bad_cert_(false) { }
+
+  void set_ignore_bad_cert(bool ignore) { ignore_bad_cert_ = ignore; }
+  bool ignore_bad_cert() const { return ignore_bad_cert_; }
+
+  // Specify our SSL identity: key and certificate. Mostly this is
+  // only used in the peer-to-peer mode (unless we actually want to
+  // provide a client certificate to a server).
+  // SSLStream takes ownership of the SSLIdentity object and will
+  // free it when appropriate. Should be called no more than once on a
+  // given SSLStream instance.
+  virtual void SetIdentity(SSLIdentity* identity) = 0;
+
+  // Call this to indicate that we are to play the server's role in
+  // the peer-to-peer mode.
+  // 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.
+  // Use of the stream prior to calling either of these functions will
+  // pass data in clear text.
+  // Calling one of these functions causes SSL negotiation to begin as
+  // soon as possible: right away if the underlying wrapped stream is
+  // already opened, or else as soon as it opens.
+  //
+  // These functions return a negative error code on failure.
+  // Returning 0 means success so far, but negotiation is probably not
+  // complete and will continue asynchronously.  In that case, the
+  // exposed stream will open after successful negotiation and
+  // verification, or an SE_CLOSE event will be raised if negotiation
+  // fails.
+
+  // StartSSLWithServer starts SSL negotiation with a server in
+  // traditional mode. server_name specifies the expected server name
+  // which the server's certificate needs to specify.
+  virtual int StartSSLWithServer(const char* server_name) = 0;
+
+  // StartSSLWithPeer starts negotiation in the special peer-to-peer
+  // mode.
+  // Generally, SetIdentity() and possibly SetServerRole() should have
+  // been called before this.
+  // SetPeerCertificate() must also be called. It may be called after
+  // StartSSLWithPeer() but must be called before the underlying
+  // stream opens.
+  virtual int StartSSLWithPeer() = 0;
+
+  // Specify the certificate that our peer is expected to use in
+  // peer-to-peer mode. Only this certificate will be accepted during
+  // SSL verification. The certificate is assumed to have been
+  // obtained through some other secure channel (such as the XMPP
+  // channel). (This could also specify the certificate authority that
+  // will sign the peer's certificate.)
+  // SSLStream takes ownership of the SSLCertificate object and will
+  // free it when appropriate. Should be called no more than once on a
+  // given SSLStream instance.
+  virtual void SetPeerCertificate(SSLCertificate* cert) = 0;
+
+  // 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.
+  bool ignore_bad_cert_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SSLSTREAMADAPTER_H__
diff --git a/talk/base/sslstreamadapter_unittest.cc b/talk/base/sslstreamadapter_unittest.cc
new file mode 100644
index 0000000..3b08baf
--- /dev/null
+++ b/talk/base/sslstreamadapter_unittest.cc
@@ -0,0 +1,886 @@
+/*
+ * 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 <algorithm>
+#include <set>
+#include <string>
+
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/ssladapter.h"
+#include "talk/base/sslconfig.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);
+
+static const char kRSA_PRIVATE_KEY_PEM[] =
+    "-----BEGIN RSA PRIVATE KEY-----\n"
+    "MIICXQIBAAKBgQDCueE4a9hDMZ3sbVZdlXOz9ZA+cvzie3zJ9gXnT/BCt9P4b9HE\n"
+    "vD/tr73YBqD3Wr5ZWScmyGYF9EMn0r3rzBxv6oooLU5TdUvOm4rzUjkCLQaQML8o\n"
+    "NxXq+qW/j3zUKGikLhaaAl/amaX2zSWUsRQ1CpngQ3+tmDNH4/25TncNmQIDAQAB\n"
+    "AoGAUcuU0Id0k10fMjYHZk4mCPzot2LD2Tr4Aznl5vFMQipHzv7hhZtx2xzMSRcX\n"
+    "vG+Qr6VkbcUWHgApyWubvZXCh3+N7Vo2aYdMAQ8XqmFpBdIrL5CVdVfqFfEMlgEy\n"
+    "LSZNG5klnrIfl3c7zQVovLr4eMqyl2oGfAqPQz75+fecv1UCQQD6wNHch9NbAG1q\n"
+    "yuFEhMARB6gDXb+5SdzFjjtTWW5uJfm4DcZLoYyaIZm0uxOwsUKd0Rsma+oGitS1\n"
+    "CXmuqfpPAkEAxszyN3vIdpD44SREEtyKZBMNOk5pEIIGdbeMJC5/XHvpxww9xkoC\n"
+    "+39NbvUZYd54uT+rafbx4QZKc0h9xA/HlwJBAL37lYVWy4XpPv1olWCKi9LbUCqs\n"
+    "vvQtyD1N1BkEayy9TQRsO09WKOcmigRqsTJwOx7DLaTgokEuspYvhagWVPUCQE/y\n"
+    "0+YkTbYBD1Xbs9SyBKXCU6uDJRWSdO6aZi2W1XloC9gUwDMiSJjD1Wwt/YsyYPJ+\n"
+    "/Hyc5yFL2l0KZimW/vkCQQCjuZ/lPcH46EuzhdbRfumDOG5N3ld7UhGI1TIRy17W\n"
+    "dGF90cG33/L6BfS8Ll+fkkW/2AMRk8FDvF4CZi2nfW4L\n"
+    "-----END RSA PRIVATE KEY-----\n";
+
+static const char kCERT_PEM[] =
+    "-----BEGIN CERTIFICATE-----\n"
+    "MIIBmTCCAQICCQCPNJORW/M13DANBgkqhkiG9w0BAQUFADARMQ8wDQYDVQQDDAZ3\n"
+    "ZWJydGMwHhcNMTMwNjE0MjIzMDAxWhcNMTQwNjE0MjIzMDAxWjARMQ8wDQYDVQQD\n"
+    "DAZ3ZWJydGMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMK54Thr2EMxnext\n"
+    "Vl2Vc7P1kD5y/OJ7fMn2BedP8EK30/hv0cS8P+2vvdgGoPdavllZJybIZgX0QyfS\n"
+    "vevMHG/qiigtTlN1S86bivNSOQItBpAwvyg3Fer6pb+PfNQoaKQuFpoCX9qZpfbN\n"
+    "JZSxFDUKmeBDf62YM0fj/blOdw2ZAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAECMt\n"
+    "UZb35H8TnjGx4XPzco/kbnurMLFFWcuve/DwTsuf10Ia9N4md8LY0UtgIgtyNqWc\n"
+    "ZwyRMwxONF6ty3wcaIiPbGqiAa55T3YRuPibkRmck9CjrmM9JAtyvqHnpHd2TsBD\n"
+    "qCV42aXS3onOXDQ1ibuWq0fr0//aj0wo4KV474c=\n"
+    "-----END CERTIFICATE-----\n";
+
+#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_;
+};
+
+static const int kFifoBufferSize = 4096;
+
+class SSLStreamAdapterTestBase : public testing::Test,
+                                 public sigslot::has_slots<> {
+ public:
+  SSLStreamAdapterTestBase(const std::string& client_cert_pem,
+                           const std::string& client_private_key_pem,
+                           bool dtls) :
+      client_buffer_(kFifoBufferSize), server_buffer_(kFifoBufferSize),
+      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),
+      damage_(false), dtls_(dtls),
+      handshake_wait_(5000), identities_set_(false) {
+    // Set use of the test RNG to get predictable loss patterns.
+    talk_base::SetRandomTestMode(true);
+
+    // Set up the slots
+    client_ssl_->SignalEvent.connect(this, &SSLStreamAdapterTestBase::OnEvent);
+    server_ssl_->SignalEvent.connect(this, &SSLStreamAdapterTestBase::OnEvent);
+
+    if (!client_cert_pem.empty() && !client_private_key_pem.empty()) {
+      client_identity_ = talk_base::SSLIdentity::FromPEMStrings(
+          client_private_key_pem, client_cert_pem);
+    } else {
+      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);
+  }
+
+  static void SetUpTestCase() {
+    talk_base::InitializeSSL();
+  }
+
+  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;
+    }
+
+    // Optionally damage application data (type 23). Note that we don't damage
+    // handshake packets and we damage the last byte to keep the header
+    // intact but break the MAC.
+    if (damage_ && (*static_cast<const unsigned char *>(data) == 23)) {
+      std::vector<char> buf(data_len);
+
+      LOG(LS_INFO) << "Damaging packet";
+
+      memcpy(&buf[0], data, data_len);
+      buf[data_len - 1]++;
+
+      return from->WriteData(&buf[0], data_len, written, error);
+    }
+
+    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 SetDamage() {
+    damage_ = true;
+  }
+
+  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 damage_;
+  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 || r == talk_base::SR_EOS) {
+        // Unfortunately, errors are the way that the stream adapter
+        // signals close in OpenSSL
+        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) {
+  }
+
+  SSLStreamAdapterTestDTLS(const std::string& cert_pem,
+                           const std::string& private_key_pem) :
+      SSLStreamAdapterTestBase(cert_pem, private_key_pem, 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 (damage_) {
+      WAIT(false, 2000);
+      EXPECT_EQ(0U, received_.size());
+    } else if (loss_ == 0) {
+        EXPECT_EQ_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;
+};
+
+class SSLStreamAdapterTestDTLSFromPEMStrings : public SSLStreamAdapterTestDTLS {
+ public:
+  SSLStreamAdapterTestDTLSFromPEMStrings() :
+      SSLStreamAdapterTestDTLS(kCERT_PEM, kRSA_PRIVATE_KEY_PEM) {
+  }
+};
+
+// Basic tests: TLS
+
+// Test that we cannot read/write if we have not yet handshaked.
+// This test only applies to NSS because OpenSSL has passthrough
+// semantics for I/O before the handshake is started.
+#if SSL_USE_NSS
+TEST_F(SSLStreamAdapterTestTLS, TestNoReadWriteBeforeConnect) {
+  talk_base::StreamResult rv;
+  char block[kBlockSize];
+  size_t dummy;
+
+  rv = client_ssl_->Write(block, sizeof(block), &dummy, NULL);
+  ASSERT_EQ(talk_base::SR_BLOCK, rv);
+
+  rv = client_ssl_->Read(block, sizeof(block), &dummy, NULL);
+  ASSERT_EQ(talk_base::SR_BLOCK, rv);
+}
+#endif
+
+
+// Test that we can make a handshake work
+TEST_F(SSLStreamAdapterTestTLS, TestTLSConnect) {
+  TestHandshake();
+};
+
+// Test transfer -- trivial
+TEST_F(SSLStreamAdapterTestTLS, TestTLSTransfer) {
+  TestHandshake();
+  TestTransfer(100000);
+};
+
+// Test read-write after close.
+TEST_F(SSLStreamAdapterTestTLS, ReadWriteAfterClose) {
+  TestHandshake();
+  TestTransfer(100000);
+  client_ssl_->Close();
+
+  talk_base::StreamResult rv;
+  char block[kBlockSize];
+  size_t dummy;
+
+  // It's an error to write after closed.
+  rv = client_ssl_->Write(block, sizeof(block), &dummy, NULL);
+  ASSERT_EQ(talk_base::SR_ERROR, rv);
+
+  // But after closed read gives you EOS.
+  rv = client_ssl_->Read(block, sizeof(block), &dummy, NULL);
+  ASSERT_EQ(talk_base::SR_EOS, rv);
+};
+
+// 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);
+  SetHandshakeWait(20000);
+  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_F(SSLStreamAdapterTestDTLS, TestDTLSTransferWithDamage) {
+  MAYBE_SKIP_TEST(HaveDtls);
+  SetDamage();  // Must be called first because first packet
+                // write happens at end of handshake.
+  TestHandshake();
+  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)));
+}
+
+// Test data transfer using certs created from strings.
+TEST_F(SSLStreamAdapterTestDTLSFromPEMStrings, TestTransfer) {
+  MAYBE_SKIP_TEST(HaveDtls);
+  TestHandshake();
+  TestTransfer(100);
+}
diff --git a/talk/base/sslstreamadapterhelper.cc b/talk/base/sslstreamadapterhelper.cc
new file mode 100644
index 0000000..5a1a255
--- /dev/null
+++ b/talk/base/sslstreamadapterhelper.cc
@@ -0,0 +1,147 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ * Copyright 2012, 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 <vector>
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif  // HAVE_CONFIG_H
+
+#include "talk/base/sslstreamadapterhelper.h"
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/stream.h"
+
+namespace talk_base {
+
+void SSLStreamAdapterHelper::SetIdentity(SSLIdentity* identity) {
+  ASSERT(identity_.get() == NULL);
+  identity_.reset(identity);
+}
+
+void SSLStreamAdapterHelper::SetServerRole(SSLRole role) {
+  role_ = role;
+}
+
+int SSLStreamAdapterHelper::StartSSLWithServer(const char* server_name) {
+  ASSERT(server_name != NULL && server_name[0] != '\0');
+  ssl_server_name_ = server_name;
+  return StartSSL();
+}
+
+int SSLStreamAdapterHelper::StartSSLWithPeer() {
+  ASSERT(ssl_server_name_.empty());
+  // It is permitted to specify peer_certificate_ only later.
+  return StartSSL();
+}
+
+void SSLStreamAdapterHelper::SetMode(SSLMode mode) {
+  ASSERT(state_ == SSL_NONE);
+  ssl_mode_ = mode;
+}
+
+StreamState SSLStreamAdapterHelper::GetState() const {
+  switch (state_) {
+    case SSL_WAIT:
+    case SSL_CONNECTING:
+      return SS_OPENING;
+    case SSL_CONNECTED:
+      return SS_OPEN;
+    default:
+      return SS_CLOSED;
+  };
+  // not reached
+}
+
+void SSLStreamAdapterHelper::SetPeerCertificate(SSLCertificate* cert) {
+  ASSERT(peer_certificate_.get() == NULL);
+  ASSERT(peer_certificate_digest_algorithm_.empty());
+  ASSERT(ssl_server_name_.empty());
+  peer_certificate_.reset(cert);
+}
+
+bool SSLStreamAdapterHelper::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_.empty());
+  ASSERT(ssl_server_name_.empty());
+  size_t expected_len;
+
+  if (!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;
+}
+
+void SSLStreamAdapterHelper::Error(const char* context, int err, bool signal) {
+  LOG(LS_WARNING) << "SSLStreamAdapterHelper::Error("
+                  << context << ", " << err << "," << signal << ")";
+  state_ = SSL_ERROR;
+  ssl_error_code_ = err;
+  Cleanup();
+  if (signal)
+    StreamAdapterInterface::OnEvent(stream(), SE_CLOSE, err);
+}
+
+void SSLStreamAdapterHelper::Close() {
+  Cleanup();
+  ASSERT(state_ == SSL_CLOSED || state_ == SSL_ERROR);
+  StreamAdapterInterface::Close();
+}
+
+int SSLStreamAdapterHelper::StartSSL() {
+  ASSERT(state_ == SSL_NONE);
+
+  if (StreamAdapterInterface::GetState() != SS_OPEN) {
+    state_ = SSL_WAIT;
+    return 0;
+  }
+
+  state_ = SSL_CONNECTING;
+  int err = BeginSSL();
+  if (err) {
+    Error("BeginSSL", err, false);
+    return err;
+  }
+
+  return 0;
+}
+
+}  // namespace talk_base
+
diff --git a/talk/base/sslstreamadapterhelper.h b/talk/base/sslstreamadapterhelper.h
new file mode 100644
index 0000000..e8cb3b0
--- /dev/null
+++ b/talk/base/sslstreamadapterhelper.h
@@ -0,0 +1,137 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_SSLSTREAMADAPTERHELPER_H_
+#define TALK_BASE_SSLSTREAMADAPTERHELPER_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/buffer.h"
+#include "talk/base/stream.h"
+#include "talk/base/sslidentity.h"
+#include "talk/base/sslstreamadapter.h"
+
+namespace talk_base {
+
+// SSLStreamAdapterHelper : A stream adapter which implements much
+// of the logic that is common between the known implementations
+// (NSS and OpenSSL)
+class SSLStreamAdapterHelper : public SSLStreamAdapter {
+ public:
+  explicit SSLStreamAdapterHelper(StreamInterface* stream)
+      : SSLStreamAdapter(stream),
+        state_(SSL_NONE),
+        role_(SSL_CLIENT),
+        ssl_error_code_(0),  // Not meaningful yet
+        ssl_mode_(SSL_MODE_TLS) {}
+
+
+  // Overrides of SSLStreamAdapter
+  virtual void SetIdentity(SSLIdentity* identity);
+  virtual void SetServerRole(SSLRole role = SSL_SERVER);
+  virtual void SetMode(SSLMode mode);
+
+  virtual int StartSSLWithServer(const char* server_name);
+  virtual int StartSSLWithPeer();
+
+  virtual void SetPeerCertificate(SSLCertificate* cert);
+  virtual bool SetPeerCertificateDigest(const std::string& digest_alg,
+                                        const unsigned char* digest_val,
+                                        size_t digest_len);
+  virtual StreamState GetState() const;
+  virtual void Close();
+
+ protected:
+  // Internal helper methods
+  // The following method returns 0 on success and a negative
+  // error code on failure. The error code may be either -1 or
+  // from the impl on some other error cases, so it can't really be
+  // interpreted unfortunately.
+
+  // Perform SSL negotiation steps.
+  int ContinueSSL();
+
+  // Error handler helper. signal is given as true for errors in
+  // asynchronous contexts (when an error code was not returned
+  // through some other method), and in that case an SE_CLOSE event is
+  // raised on the stream with the specified error.
+  // A 0 error means a graceful close, otherwise there is not really enough
+  // context to interpret the error code.
+  virtual void Error(const char* context, int err, bool signal);
+
+  // Must be implemented by descendents
+  virtual int BeginSSL() = 0;
+  virtual void Cleanup() = 0;
+  virtual bool GetDigestLength(const std::string &algorithm,
+                               std::size_t *length) = 0;
+
+  enum SSLState {
+    // Before calling one of the StartSSL methods, data flows
+    // in clear text.
+    SSL_NONE,
+    SSL_WAIT,  // waiting for the stream to open to start SSL negotiation
+    SSL_CONNECTING,  // SSL negotiation in progress
+    SSL_CONNECTED,  // SSL stream successfully established
+    SSL_ERROR,  // some SSL error occurred, stream is closed
+    SSL_CLOSED  // Clean close
+  };
+
+  // MSG_MAX is the maximum generic stream message number.
+  enum { MSG_DTLS_TIMEOUT = MSG_MAX + 1 };
+
+  SSLState state_;
+  SSLRole role_;
+  int ssl_error_code_;  // valid when state_ == SSL_ERROR
+
+  // Our key and certificate, mostly useful in peer-to-peer mode.
+  scoped_ptr<SSLIdentity> 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<SSLCertificate> 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_;
+
+  // Do DTLS or not
+  SSLMode ssl_mode_;
+
+ private:
+  // Go from state SSL_NONE to either SSL_CONNECTING or SSL_WAIT,
+  // depending on whether the underlying stream is already open or
+  // not. Returns 0 on success and a negative value on error.
+  int StartSSL();
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_SSLSTREAMADAPTERHELPER_H_
diff --git a/talk/base/stream.cc b/talk/base/stream.cc
new file mode 100644
index 0000000..20adfcf
--- /dev/null
+++ b/talk/base/stream.cc
@@ -0,0 +1,1252 @@
+/*
+ * 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.
+ */
+
+#if defined(POSIX)
+#include <sys/file.h>
+#endif  // POSIX
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <string>
+#include "talk/base/basictypes.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#define fileno _fileno
+#endif
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamInterface
+///////////////////////////////////////////////////////////////////////////////
+StreamInterface::~StreamInterface() {
+}
+
+StreamResult StreamInterface::WriteAll(const void* data, size_t data_len,
+                                       size_t* written, int* error) {
+  StreamResult result = SR_SUCCESS;
+  size_t total_written = 0, current_written;
+  while (total_written < data_len) {
+    result = Write(static_cast<const char*>(data) + total_written,
+                   data_len - total_written, &current_written, error);
+    if (result != SR_SUCCESS)
+      break;
+    total_written += current_written;
+  }
+  if (written)
+    *written = total_written;
+  return result;
+}
+
+StreamResult StreamInterface::ReadAll(void* buffer, size_t buffer_len,
+                                      size_t* read, int* error) {
+  StreamResult result = SR_SUCCESS;
+  size_t total_read = 0, current_read;
+  while (total_read < buffer_len) {
+    result = Read(static_cast<char*>(buffer) + total_read,
+                  buffer_len - total_read, &current_read, error);
+    if (result != SR_SUCCESS)
+      break;
+    total_read += current_read;
+  }
+  if (read)
+    *read = total_read;
+  return result;
+}
+
+StreamResult StreamInterface::ReadLine(std::string* line) {
+  line->clear();
+  StreamResult result = SR_SUCCESS;
+  while (true) {
+    char ch;
+    result = Read(&ch, sizeof(ch), NULL, NULL);
+    if (result != SR_SUCCESS) {
+      break;
+    }
+    if (ch == '\n') {
+      break;
+    }
+    line->push_back(ch);
+  }
+  if (!line->empty()) {   // give back the line we've collected so far with
+    result = SR_SUCCESS;  // a success code.  Otherwise return the last code
+  }
+  return result;
+}
+
+void StreamInterface::PostEvent(Thread* t, int events, int err) {
+  t->Post(this, MSG_POST_EVENT, new StreamEventData(events, err));
+}
+
+void StreamInterface::PostEvent(int events, int err) {
+  PostEvent(Thread::Current(), events, err);
+}
+
+StreamInterface::StreamInterface() {
+}
+
+void StreamInterface::OnMessage(Message* msg) {
+  if (MSG_POST_EVENT == msg->message_id) {
+    StreamEventData* pe = static_cast<StreamEventData*>(msg->pdata);
+    SignalEvent(this, pe->events, pe->error);
+    delete msg->pdata;
+  }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamAdapterInterface
+///////////////////////////////////////////////////////////////////////////////
+
+StreamAdapterInterface::StreamAdapterInterface(StreamInterface* stream,
+                                               bool owned)
+    : stream_(stream), owned_(owned) {
+  if (NULL != stream_)
+    stream_->SignalEvent.connect(this, &StreamAdapterInterface::OnEvent);
+}
+
+void StreamAdapterInterface::Attach(StreamInterface* stream, bool owned) {
+  if (NULL != stream_)
+    stream_->SignalEvent.disconnect(this);
+  if (owned_)
+    delete stream_;
+  stream_ = stream;
+  owned_ = owned;
+  if (NULL != stream_)
+    stream_->SignalEvent.connect(this, &StreamAdapterInterface::OnEvent);
+}
+
+StreamInterface* StreamAdapterInterface::Detach() {
+  if (NULL != stream_)
+    stream_->SignalEvent.disconnect(this);
+  StreamInterface* stream = stream_;
+  stream_ = NULL;
+  return stream;
+}
+
+StreamAdapterInterface::~StreamAdapterInterface() {
+  if (owned_)
+    delete stream_;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamTap
+///////////////////////////////////////////////////////////////////////////////
+
+StreamTap::StreamTap(StreamInterface* stream, StreamInterface* tap)
+    : StreamAdapterInterface(stream), tap_(NULL), tap_result_(SR_SUCCESS),
+        tap_error_(0) {
+  AttachTap(tap);
+}
+
+void StreamTap::AttachTap(StreamInterface* tap) {
+  tap_.reset(tap);
+}
+
+StreamInterface* StreamTap::DetachTap() {
+  return tap_.release();
+}
+
+StreamResult StreamTap::GetTapResult(int* error) {
+  if (error) {
+    *error = tap_error_;
+  }
+  return tap_result_;
+}
+
+StreamResult StreamTap::Read(void* buffer, size_t buffer_len,
+                             size_t* read, int* error) {
+  size_t backup_read;
+  if (!read) {
+    read = &backup_read;
+  }
+  StreamResult res = StreamAdapterInterface::Read(buffer, buffer_len,
+                                                  read, error);
+  if ((res == SR_SUCCESS) && (tap_result_ == SR_SUCCESS)) {
+    tap_result_ = tap_->WriteAll(buffer, *read, NULL, &tap_error_);
+  }
+  return res;
+}
+
+StreamResult StreamTap::Write(const void* data, size_t data_len,
+                              size_t* written, int* error) {
+  size_t backup_written;
+  if (!written) {
+    written = &backup_written;
+  }
+  StreamResult res = StreamAdapterInterface::Write(data, data_len,
+                                                   written, error);
+  if ((res == SR_SUCCESS) && (tap_result_ == SR_SUCCESS)) {
+    tap_result_ = tap_->WriteAll(data, *written, NULL, &tap_error_);
+  }
+  return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamSegment
+///////////////////////////////////////////////////////////////////////////////
+
+StreamSegment::StreamSegment(StreamInterface* stream)
+    : StreamAdapterInterface(stream), start_(SIZE_UNKNOWN), pos_(0),
+    length_(SIZE_UNKNOWN) {
+  // It's ok for this to fail, in which case start_ is left as SIZE_UNKNOWN.
+  stream->GetPosition(&start_);
+}
+
+StreamSegment::StreamSegment(StreamInterface* stream, size_t length)
+    : StreamAdapterInterface(stream), start_(SIZE_UNKNOWN), pos_(0),
+    length_(length) {
+  // It's ok for this to fail, in which case start_ is left as SIZE_UNKNOWN.
+  stream->GetPosition(&start_);
+}
+
+StreamResult StreamSegment::Read(void* buffer, size_t buffer_len,
+                                 size_t* read, int* error) {
+  if (SIZE_UNKNOWN != length_) {
+    if (pos_ >= length_)
+      return SR_EOS;
+    buffer_len = _min(buffer_len, length_ - pos_);
+  }
+  size_t backup_read;
+  if (!read) {
+    read = &backup_read;
+  }
+  StreamResult result = StreamAdapterInterface::Read(buffer, buffer_len,
+                                                     read, error);
+  if (SR_SUCCESS == result) {
+    pos_ += *read;
+  }
+  return result;
+}
+
+bool StreamSegment::SetPosition(size_t position) {
+  if (SIZE_UNKNOWN == start_)
+    return false;  // Not seekable
+  if ((SIZE_UNKNOWN != length_) && (position > length_))
+    return false;  // Seek past end of segment
+  if (!StreamAdapterInterface::SetPosition(start_ + position))
+    return false;
+  pos_ = position;
+  return true;
+}
+
+bool StreamSegment::GetPosition(size_t* position) const {
+  if (SIZE_UNKNOWN == start_)
+    return false;  // Not seekable
+  if (!StreamAdapterInterface::GetPosition(position))
+    return false;
+  if (position) {
+    ASSERT(*position >= start_);
+    *position -= start_;
+  }
+  return true;
+}
+
+bool StreamSegment::GetSize(size_t* size) const {
+  if (!StreamAdapterInterface::GetSize(size))
+    return false;
+  if (size) {
+    if (SIZE_UNKNOWN != start_) {
+      ASSERT(*size >= start_);
+      *size -= start_;
+    }
+    if (SIZE_UNKNOWN != length_) {
+      *size = _min(*size, length_);
+    }
+  }
+  return true;
+}
+
+bool StreamSegment::GetAvailable(size_t* size) const {
+  if (!StreamAdapterInterface::GetAvailable(size))
+    return false;
+  if (size && (SIZE_UNKNOWN != length_))
+    *size = _min(*size, length_ - pos_);
+  return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// NullStream
+///////////////////////////////////////////////////////////////////////////////
+
+NullStream::NullStream() {
+}
+
+NullStream::~NullStream() {
+}
+
+StreamState NullStream::GetState() const {
+  return SS_OPEN;
+}
+
+StreamResult NullStream::Read(void* buffer, size_t buffer_len,
+                              size_t* read, int* error) {
+  if (error) *error = -1;
+  return SR_ERROR;
+}
+
+StreamResult NullStream::Write(const void* data, size_t data_len,
+                               size_t* written, int* error) {
+  if (written) *written = data_len;
+  return SR_SUCCESS;
+}
+
+void NullStream::Close() {
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// FileStream
+///////////////////////////////////////////////////////////////////////////////
+
+FileStream::FileStream() : file_(NULL) {
+}
+
+FileStream::~FileStream() {
+  FileStream::Close();
+}
+
+bool FileStream::Open(const std::string& filename, const char* mode,
+                      int* error) {
+  Close();
+#ifdef WIN32
+  std::wstring wfilename;
+  if (Utf8ToWindowsFilename(filename, &wfilename)) {
+    file_ = _wfopen(wfilename.c_str(), ToUtf16(mode).c_str());
+  } else {
+    if (error) {
+      *error = -1;
+      return false;
+    }
+  }
+#else
+  file_ = fopen(filename.c_str(), mode);
+#endif
+  if (!file_ && error) {
+    *error = errno;
+  }
+  return (file_ != NULL);
+}
+
+bool FileStream::OpenShare(const std::string& filename, const char* mode,
+                           int shflag, int* error) {
+  Close();
+#ifdef WIN32
+  std::wstring wfilename;
+  if (Utf8ToWindowsFilename(filename, &wfilename)) {
+    file_ = _wfsopen(wfilename.c_str(), ToUtf16(mode).c_str(), shflag);
+    if (!file_ && error) {
+      *error = errno;
+      return false;
+    }
+    return file_ != NULL;
+  } else {
+    if (error) {
+      *error = -1;
+    }
+    return false;
+  }
+#else
+  return Open(filename, mode, error);
+#endif
+}
+
+bool FileStream::DisableBuffering() {
+  if (!file_)
+    return false;
+  return (setvbuf(file_, NULL, _IONBF, 0) == 0);
+}
+
+StreamState FileStream::GetState() const {
+  return (file_ == NULL) ? SS_CLOSED : SS_OPEN;
+}
+
+StreamResult FileStream::Read(void* buffer, size_t buffer_len,
+                              size_t* read, int* error) {
+  if (!file_)
+    return SR_EOS;
+  size_t result = fread(buffer, 1, buffer_len, file_);
+  if ((result == 0) && (buffer_len > 0)) {
+    if (feof(file_))
+      return SR_EOS;
+    if (error)
+      *error = errno;
+    return SR_ERROR;
+  }
+  if (read)
+    *read = result;
+  return SR_SUCCESS;
+}
+
+StreamResult FileStream::Write(const void* data, size_t data_len,
+                               size_t* written, int* error) {
+  if (!file_)
+    return SR_EOS;
+  size_t result = fwrite(data, 1, data_len, file_);
+  if ((result == 0) && (data_len > 0)) {
+    if (error)
+      *error = errno;
+    return SR_ERROR;
+  }
+  if (written)
+    *written = result;
+  return SR_SUCCESS;
+}
+
+void FileStream::Close() {
+  if (file_) {
+    DoClose();
+    file_ = NULL;
+  }
+}
+
+bool FileStream::SetPosition(size_t position) {
+  if (!file_)
+    return false;
+  return (fseek(file_, static_cast<int>(position), SEEK_SET) == 0);
+}
+
+bool FileStream::GetPosition(size_t* position) const {
+  ASSERT(NULL != position);
+  if (!file_)
+    return false;
+  long result = ftell(file_);
+  if (result < 0)
+    return false;
+  if (position)
+    *position = result;
+  return true;
+}
+
+bool FileStream::GetSize(size_t* size) const {
+  ASSERT(NULL != size);
+  if (!file_)
+    return false;
+  struct stat file_stats;
+  if (fstat(fileno(file_), &file_stats) != 0)
+    return false;
+  if (size)
+    *size = file_stats.st_size;
+  return true;
+}
+
+bool FileStream::GetAvailable(size_t* size) const {
+  ASSERT(NULL != size);
+  if (!GetSize(size))
+    return false;
+  long result = ftell(file_);
+  if (result < 0)
+    return false;
+  if (size)
+    *size -= result;
+  return true;
+}
+
+bool FileStream::ReserveSize(size_t size) {
+  // TODO: extend the file to the proper length
+  return true;
+}
+
+bool FileStream::GetSize(const std::string& filename, size_t* size) {
+  struct stat file_stats;
+  if (stat(filename.c_str(), &file_stats) != 0)
+    return false;
+  *size = file_stats.st_size;
+  return true;
+}
+
+bool FileStream::Flush() {
+  if (file_) {
+    return (0 == fflush(file_));
+  }
+  // try to flush empty file?
+  ASSERT(false);
+  return false;
+}
+
+#if defined(POSIX)
+
+bool FileStream::TryLock() {
+  if (file_ == NULL) {
+    // Stream not open.
+    ASSERT(false);
+    return false;
+  }
+
+  return flock(fileno(file_), LOCK_EX|LOCK_NB) == 0;
+}
+
+bool FileStream::Unlock() {
+  if (file_ == NULL) {
+    // Stream not open.
+    ASSERT(false);
+    return false;
+  }
+
+  return flock(fileno(file_), LOCK_UN) == 0;
+}
+
+#endif
+
+void FileStream::DoClose() {
+  fclose(file_);
+}
+
+AsyncWriteStream::~AsyncWriteStream() {
+  write_thread_->Clear(this, 0, NULL);
+  ClearBufferAndWrite();
+
+  CritScope cs(&crit_stream_);
+  stream_.reset();
+}
+
+// This is needed by some stream writers, such as RtpDumpWriter.
+bool AsyncWriteStream::GetPosition(size_t* position) const {
+  CritScope cs(&crit_stream_);
+  return stream_->GetPosition(position);
+}
+
+// This is needed by some stream writers, such as the plugin log writers.
+StreamResult AsyncWriteStream::Read(void* buffer, size_t buffer_len,
+                                    size_t* read, int* error) {
+  CritScope cs(&crit_stream_);
+  return stream_->Read(buffer, buffer_len, read, error);
+}
+
+void AsyncWriteStream::Close() {
+  if (state_ == SS_CLOSED) {
+    return;
+  }
+
+  write_thread_->Clear(this, 0, NULL);
+  ClearBufferAndWrite();
+
+  CritScope cs(&crit_stream_);
+  stream_->Close();
+  state_ = SS_CLOSED;
+}
+
+StreamResult AsyncWriteStream::Write(const void* data, size_t data_len,
+                                     size_t* written, int* error) {
+  if (state_ == SS_CLOSED) {
+    return SR_ERROR;
+  }
+
+  size_t previous_buffer_length = 0;
+  {
+    CritScope cs(&crit_buffer_);
+    previous_buffer_length = buffer_.length();
+    buffer_.AppendData(data, data_len);
+  }
+
+  if (previous_buffer_length == 0) {
+    // If there's stuff already in the buffer, then we already called
+    // Post and the write_thread_ hasn't pulled it out yet, so we
+    // don't need to re-Post.
+    write_thread_->Post(this, 0, NULL);
+  }
+  // Return immediately, assuming that it works.
+  if (written) {
+    *written = data_len;
+  }
+  return SR_SUCCESS;
+}
+
+void AsyncWriteStream::OnMessage(talk_base::Message* pmsg) {
+  ClearBufferAndWrite();
+}
+
+bool AsyncWriteStream::Flush() {
+  if (state_ == SS_CLOSED) {
+    return false;
+  }
+
+  ClearBufferAndWrite();
+
+  CritScope cs(&crit_stream_);
+  return stream_->Flush();
+}
+
+void AsyncWriteStream::ClearBufferAndWrite() {
+  Buffer to_write;
+  {
+    CritScope cs_buffer(&crit_buffer_);
+    buffer_.TransferTo(&to_write);
+  }
+
+  if (to_write.length() > 0) {
+    CritScope cs(&crit_stream_);
+    stream_->WriteAll(to_write.data(), to_write.length(), NULL, NULL);
+  }
+}
+
+#ifdef POSIX
+
+// Have to identically rewrite the FileStream destructor or else it would call
+// the base class's Close() instead of the sub-class's.
+POpenStream::~POpenStream() {
+  POpenStream::Close();
+}
+
+bool POpenStream::Open(const std::string& subcommand,
+                       const char* mode,
+                       int* error) {
+  Close();
+  file_ = popen(subcommand.c_str(), mode);
+  if (file_ == NULL) {
+    if (error)
+      *error = errno;
+    return false;
+  }
+  return true;
+}
+
+bool POpenStream::OpenShare(const std::string& subcommand, const char* mode,
+                            int shflag, int* error) {
+  return Open(subcommand, mode, error);
+}
+
+void POpenStream::DoClose() {
+  wait_status_ = pclose(file_);
+}
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+// MemoryStream
+///////////////////////////////////////////////////////////////////////////////
+
+MemoryStreamBase::MemoryStreamBase()
+  : buffer_(NULL), buffer_length_(0), data_length_(0),
+    seek_position_(0) {
+}
+
+StreamState MemoryStreamBase::GetState() const {
+  return SS_OPEN;
+}
+
+StreamResult MemoryStreamBase::Read(void* buffer, size_t bytes,
+                                    size_t* bytes_read, int* error) {
+  if (seek_position_ >= data_length_) {
+    return SR_EOS;
+  }
+  size_t available = data_length_ - seek_position_;
+  if (bytes > available) {
+    // Read partial buffer
+    bytes = available;
+  }
+  memcpy(buffer, &buffer_[seek_position_], bytes);
+  seek_position_ += bytes;
+  if (bytes_read) {
+    *bytes_read = bytes;
+  }
+  return SR_SUCCESS;
+}
+
+StreamResult MemoryStreamBase::Write(const void* buffer, size_t bytes,
+                                     size_t* bytes_written, int* error) {
+  size_t available = buffer_length_ - seek_position_;
+  if (0 == available) {
+    // Increase buffer size to the larger of:
+    // a) new position rounded up to next 256 bytes
+    // b) double the previous length
+    size_t new_buffer_length = _max(((seek_position_ + bytes) | 0xFF) + 1,
+                                    buffer_length_ * 2);
+    StreamResult result = DoReserve(new_buffer_length, error);
+    if (SR_SUCCESS != result) {
+      return result;
+    }
+    ASSERT(buffer_length_ >= new_buffer_length);
+    available = buffer_length_ - seek_position_;
+  }
+
+  if (bytes > available) {
+    bytes = available;
+  }
+  memcpy(&buffer_[seek_position_], buffer, bytes);
+  seek_position_ += bytes;
+  if (data_length_ < seek_position_) {
+    data_length_ = seek_position_;
+  }
+  if (bytes_written) {
+    *bytes_written = bytes;
+  }
+  return SR_SUCCESS;
+}
+
+void MemoryStreamBase::Close() {
+  // nothing to do
+}
+
+bool MemoryStreamBase::SetPosition(size_t position) {
+  if (position > data_length_)
+    return false;
+  seek_position_ = position;
+  return true;
+}
+
+bool MemoryStreamBase::GetPosition(size_t* position) const {
+  if (position)
+    *position = seek_position_;
+  return true;
+}
+
+bool MemoryStreamBase::GetSize(size_t* size) const {
+  if (size)
+    *size = data_length_;
+  return true;
+}
+
+bool MemoryStreamBase::GetAvailable(size_t* size) const {
+  if (size)
+    *size = data_length_ - seek_position_;
+  return true;
+}
+
+bool MemoryStreamBase::ReserveSize(size_t size) {
+  return (SR_SUCCESS == DoReserve(size, NULL));
+}
+
+StreamResult MemoryStreamBase::DoReserve(size_t size, int* error) {
+  return (buffer_length_ >= size) ? SR_SUCCESS : SR_EOS;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+MemoryStream::MemoryStream()
+  : buffer_alloc_(NULL) {
+}
+
+MemoryStream::MemoryStream(const char* data)
+  : buffer_alloc_(NULL) {
+  SetData(data, strlen(data));
+}
+
+MemoryStream::MemoryStream(const void* data, size_t length)
+  : buffer_alloc_(NULL) {
+  SetData(data, length);
+}
+
+MemoryStream::~MemoryStream() {
+  delete [] buffer_alloc_;
+}
+
+void MemoryStream::SetData(const void* data, size_t length) {
+  data_length_ = buffer_length_ = length;
+  delete [] buffer_alloc_;
+  buffer_alloc_ = new char[buffer_length_ + kAlignment];
+  buffer_ = reinterpret_cast<char*>(ALIGNP(buffer_alloc_, kAlignment));
+  memcpy(buffer_, data, data_length_);
+  seek_position_ = 0;
+}
+
+StreamResult MemoryStream::DoReserve(size_t size, int* error) {
+  if (buffer_length_ >= size)
+    return SR_SUCCESS;
+
+  if (char* new_buffer_alloc = new char[size + kAlignment]) {
+    char* new_buffer = reinterpret_cast<char*>(
+        ALIGNP(new_buffer_alloc, kAlignment));
+    memcpy(new_buffer, buffer_, data_length_);
+    delete [] buffer_alloc_;
+    buffer_alloc_ = new_buffer_alloc;
+    buffer_ = new_buffer;
+    buffer_length_ = size;
+    return SR_SUCCESS;
+  }
+
+  if (error) {
+    *error = ENOMEM;
+  }
+  return SR_ERROR;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ExternalMemoryStream::ExternalMemoryStream() {
+}
+
+ExternalMemoryStream::ExternalMemoryStream(void* data, size_t length) {
+  SetData(data, length);
+}
+
+ExternalMemoryStream::~ExternalMemoryStream() {
+}
+
+void ExternalMemoryStream::SetData(void* data, size_t length) {
+  data_length_ = buffer_length_ = length;
+  buffer_ = static_cast<char*>(data);
+  seek_position_ = 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// FifoBuffer
+///////////////////////////////////////////////////////////////////////////////
+
+FifoBuffer::FifoBuffer(size_t size)
+    : state_(SS_OPEN), buffer_(new char[size]), buffer_length_(size),
+      data_length_(0), read_position_(0), owner_(Thread::Current()) {
+  // all events are done on the owner_ thread
+}
+
+FifoBuffer::FifoBuffer(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() {
+}
+
+bool FifoBuffer::GetBuffered(size_t* size) const {
+  CritScope cs(&crit_);
+  *size = data_length_;
+  return true;
+}
+
+bool FifoBuffer::SetCapacity(size_t size) {
+  CritScope cs(&crit_);
+  if (data_length_ > size) {
+    return false;
+  }
+
+  if (size != buffer_length_) {
+    char* buffer = new char[size];
+    const size_t copy = data_length_;
+    const size_t tail_copy = _min(copy, buffer_length_ - read_position_);
+    memcpy(buffer, &buffer_[read_position_], tail_copy);
+    memcpy(buffer + tail_copy, &buffer_[0], copy - tail_copy);
+    buffer_.reset(buffer);
+    read_position_ = 0;
+    buffer_length_ = size;
+  }
+  return true;
+}
+
+StreamResult FifoBuffer::ReadOffset(void* buffer, size_t bytes,
+                                    size_t offset, size_t* bytes_read) {
+  CritScope cs(&crit_);
+  return ReadOffsetLocked(buffer, bytes, offset, bytes_read);
+}
+
+StreamResult FifoBuffer::WriteOffset(const void* buffer, size_t bytes,
+                                     size_t offset, size_t* bytes_written) {
+  CritScope cs(&crit_);
+  return WriteOffsetLocked(buffer, bytes, offset, bytes_written);
+}
+
+StreamState FifoBuffer::GetState() const {
+  return state_;
+}
+
+StreamResult FifoBuffer::Read(void* buffer, size_t bytes,
+                              size_t* bytes_read, int* error) {
+  CritScope cs(&crit_);
+  const bool was_writable = data_length_ < buffer_length_;
+  size_t copy = 0;
+  StreamResult result = ReadOffsetLocked(buffer, bytes, 0, &copy);
+
+  if (result == SR_SUCCESS) {
+    // If read was successful then adjust the read position and number of
+    // bytes buffered.
+    read_position_ = (read_position_ + copy) % buffer_length_;
+    data_length_ -= copy;
+    if (bytes_read) {
+      *bytes_read = copy;
+    }
+
+    // if we were full before, and now we're not, post an event
+    if (!was_writable && copy > 0) {
+      PostEvent(owner_, SE_WRITE, 0);
+    }
+  }
+  return result;
+}
+
+StreamResult FifoBuffer::Write(const void* buffer, size_t bytes,
+                               size_t* bytes_written, int* error) {
+  CritScope cs(&crit_);
+
+  const bool was_readable = (data_length_ > 0);
+  size_t copy = 0;
+  StreamResult result = WriteOffsetLocked(buffer, bytes, 0, &copy);
+
+  if (result == SR_SUCCESS) {
+    // If write was successful then adjust the number of readable bytes.
+    data_length_ += copy;
+    if (bytes_written) {
+      *bytes_written = copy;
+    }
+
+    // if we didn't have any data to read before, and now we do, post an event
+    if (!was_readable && copy > 0) {
+      PostEvent(owner_, SE_READ, 0);
+    }
+  }
+  return result;
+}
+
+void FifoBuffer::Close() {
+  CritScope cs(&crit_);
+  state_ = SS_CLOSED;
+}
+
+const void* FifoBuffer::GetReadData(size_t* size) {
+  CritScope cs(&crit_);
+  *size = (read_position_ + data_length_ <= buffer_length_) ?
+      data_length_ : buffer_length_ - read_position_;
+  return &buffer_[read_position_];
+}
+
+void FifoBuffer::ConsumeReadData(size_t size) {
+  CritScope cs(&crit_);
+  ASSERT(size <= data_length_);
+  const bool was_writable = data_length_ < buffer_length_;
+  read_position_ = (read_position_ + size) % buffer_length_;
+  data_length_ -= size;
+  if (!was_writable && size > 0) {
+    PostEvent(owner_, SE_WRITE, 0);
+  }
+}
+
+void* FifoBuffer::GetWriteBuffer(size_t* size) {
+  CritScope cs(&crit_);
+  if (state_ == SS_CLOSED) {
+    return NULL;
+  }
+
+  // if empty, reset the write position to the beginning, so we can get
+  // the biggest possible block
+  if (data_length_ == 0) {
+    read_position_ = 0;
+  }
+
+  const size_t write_position = (read_position_ + data_length_)
+      % buffer_length_;
+  *size = (write_position > read_position_ || data_length_ == 0) ?
+      buffer_length_ - write_position : read_position_ - write_position;
+  return &buffer_[write_position];
+}
+
+void FifoBuffer::ConsumeWriteBuffer(size_t size) {
+  CritScope cs(&crit_);
+  ASSERT(size <= buffer_length_ - data_length_);
+  const bool was_readable = (data_length_ > 0);
+  data_length_ += size;
+  if (!was_readable && size > 0) {
+    PostEvent(owner_, SE_READ, 0);
+  }
+}
+
+bool FifoBuffer::GetWriteRemaining(size_t* size) const {
+  CritScope cs(&crit_);
+  *size = buffer_length_ - data_length_;
+  return true;
+}
+
+StreamResult FifoBuffer::ReadOffsetLocked(void* buffer,
+                                          size_t bytes,
+                                          size_t offset,
+                                          size_t* bytes_read) {
+  if (offset >= data_length_) {
+    return (state_ != SS_CLOSED) ? SR_BLOCK : SR_EOS;
+  }
+
+  const size_t available = data_length_ - offset;
+  const size_t read_position = (read_position_ + offset) % buffer_length_;
+  const size_t copy = _min(bytes, available);
+  const size_t tail_copy = _min(copy, buffer_length_ - read_position);
+  char* const p = static_cast<char*>(buffer);
+  memcpy(p, &buffer_[read_position], tail_copy);
+  memcpy(p + tail_copy, &buffer_[0], copy - tail_copy);
+
+  if (bytes_read) {
+    *bytes_read = copy;
+  }
+  return SR_SUCCESS;
+}
+
+StreamResult FifoBuffer::WriteOffsetLocked(const void* buffer,
+                                           size_t bytes,
+                                           size_t offset,
+                                           size_t* bytes_written) {
+  if (state_ == SS_CLOSED) {
+    return SR_EOS;
+  }
+
+  if (data_length_ + offset >= buffer_length_) {
+    return SR_BLOCK;
+  }
+
+  const size_t available = buffer_length_ - data_length_ - offset;
+  const size_t write_position = (read_position_ + data_length_ + offset)
+      % buffer_length_;
+  const size_t copy = _min(bytes, available);
+  const size_t tail_copy = _min(copy, buffer_length_ - write_position);
+  const char* const p = static_cast<const char*>(buffer);
+  memcpy(&buffer_[write_position], p, tail_copy);
+  memcpy(&buffer_[0], p + tail_copy, copy - tail_copy);
+
+  if (bytes_written) {
+    *bytes_written = copy;
+  }
+  return SR_SUCCESS;
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// LoggingAdapter
+///////////////////////////////////////////////////////////////////////////////
+
+LoggingAdapter::LoggingAdapter(StreamInterface* stream, LoggingSeverity level,
+                               const std::string& label, bool hex_mode)
+    : StreamAdapterInterface(stream), level_(level), hex_mode_(hex_mode) {
+  set_label(label);
+}
+
+void LoggingAdapter::set_label(const std::string& label) {
+  label_.assign("[");
+  label_.append(label);
+  label_.append("]");
+}
+
+StreamResult LoggingAdapter::Read(void* buffer, size_t buffer_len,
+                                  size_t* read, int* error) {
+  size_t local_read; if (!read) read = &local_read;
+  StreamResult result = StreamAdapterInterface::Read(buffer, buffer_len, read,
+                                                     error);
+  if (result == SR_SUCCESS) {
+    LogMultiline(level_, label_.c_str(), true, buffer, *read, hex_mode_, &lms_);
+  }
+  return result;
+}
+
+StreamResult LoggingAdapter::Write(const void* data, size_t data_len,
+                                   size_t* written, int* error) {
+  size_t local_written;
+  if (!written) written = &local_written;
+  StreamResult result = StreamAdapterInterface::Write(data, data_len, written,
+                                                      error);
+  if (result == SR_SUCCESS) {
+    LogMultiline(level_, label_.c_str(), false, data, *written, hex_mode_,
+                 &lms_);
+  }
+  return result;
+}
+
+void LoggingAdapter::Close() {
+  LogMultiline(level_, label_.c_str(), false, NULL, 0, hex_mode_, &lms_);
+  LogMultiline(level_, label_.c_str(), true, NULL, 0, hex_mode_, &lms_);
+  LOG_V(level_) << label_ << " Closed locally";
+  StreamAdapterInterface::Close();
+}
+
+void LoggingAdapter::OnEvent(StreamInterface* stream, int events, int err) {
+  if (events & SE_OPEN) {
+    LOG_V(level_) << label_ << " Open";
+  } else if (events & SE_CLOSE) {
+    LogMultiline(level_, label_.c_str(), false, NULL, 0, hex_mode_, &lms_);
+    LogMultiline(level_, label_.c_str(), true, NULL, 0, hex_mode_, &lms_);
+    LOG_V(level_) << label_ << " Closed with error: " << err;
+  }
+  StreamAdapterInterface::OnEvent(stream, events, err);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// StringStream - Reads/Writes to an external std::string
+///////////////////////////////////////////////////////////////////////////////
+
+StringStream::StringStream(std::string& str)
+    : str_(str), read_pos_(0), read_only_(false) {
+}
+
+StringStream::StringStream(const std::string& str)
+    : str_(const_cast<std::string&>(str)), read_pos_(0), read_only_(true) {
+}
+
+StreamState StringStream::GetState() const {
+  return SS_OPEN;
+}
+
+StreamResult StringStream::Read(void* buffer, size_t buffer_len,
+                                      size_t* read, int* error) {
+  size_t available = _min(buffer_len, str_.size() - read_pos_);
+  if (!available)
+    return SR_EOS;
+  memcpy(buffer, str_.data() + read_pos_, available);
+  read_pos_ += available;
+  if (read)
+    *read = available;
+  return SR_SUCCESS;
+}
+
+StreamResult StringStream::Write(const void* data, size_t data_len,
+                                      size_t* written, int* error) {
+  if (read_only_) {
+    if (error) {
+      *error = -1;
+    }
+    return SR_ERROR;
+  }
+  str_.append(static_cast<const char*>(data),
+              static_cast<const char*>(data) + data_len);
+  if (written)
+    *written = data_len;
+  return SR_SUCCESS;
+}
+
+void StringStream::Close() {
+}
+
+bool StringStream::SetPosition(size_t position) {
+  if (position > str_.size())
+    return false;
+  read_pos_ = position;
+  return true;
+}
+
+bool StringStream::GetPosition(size_t* position) const {
+  if (position)
+    *position = read_pos_;
+  return true;
+}
+
+bool StringStream::GetSize(size_t* size) const {
+  if (size)
+    *size = str_.size();
+  return true;
+}
+
+bool StringStream::GetAvailable(size_t* size) const {
+  if (size)
+    *size = str_.size() - read_pos_;
+  return true;
+}
+
+bool StringStream::ReserveSize(size_t size) {
+  if (read_only_)
+    return false;
+  str_.reserve(size);
+  return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamReference
+///////////////////////////////////////////////////////////////////////////////
+
+StreamReference::StreamReference(StreamInterface* stream)
+    : StreamAdapterInterface(stream, false) {
+  // owner set to false so the destructor does not free the stream.
+  stream_ref_count_ = new StreamRefCount(stream);
+}
+
+StreamInterface* StreamReference::NewReference() {
+  stream_ref_count_->AddReference();
+  return new StreamReference(stream_ref_count_, stream());
+}
+
+StreamReference::~StreamReference() {
+  stream_ref_count_->Release();
+}
+
+StreamReference::StreamReference(StreamRefCount* stream_ref_count,
+                                 StreamInterface* stream)
+    : StreamAdapterInterface(stream, false),
+      stream_ref_count_(stream_ref_count) {
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+StreamResult Flow(StreamInterface* source,
+                  char* buffer, size_t buffer_len,
+                  StreamInterface* sink,
+                  size_t* data_len /* = NULL */) {
+  ASSERT(buffer_len > 0);
+
+  StreamResult result;
+  size_t count, read_pos, write_pos;
+  if (data_len) {
+    read_pos = *data_len;
+  } else {
+    read_pos = 0;
+  }
+
+  bool end_of_stream = false;
+  do {
+    // Read until buffer is full, end of stream, or error
+    while (!end_of_stream && (read_pos < buffer_len)) {
+      result = source->Read(buffer + read_pos, buffer_len - read_pos,
+                            &count, NULL);
+      if (result == SR_EOS) {
+        end_of_stream = true;
+      } else if (result != SR_SUCCESS) {
+        if (data_len) {
+          *data_len = read_pos;
+        }
+        return result;
+      } else {
+        read_pos += count;
+      }
+    }
+
+    // Write until buffer is empty, or error (including end of stream)
+    write_pos = 0;
+    while (write_pos < read_pos) {
+      result = sink->Write(buffer + write_pos, read_pos - write_pos,
+                           &count, NULL);
+      if (result != SR_SUCCESS) {
+        if (data_len) {
+          *data_len = read_pos - write_pos;
+          if (write_pos > 0) {
+            memmove(buffer, buffer + write_pos, *data_len);
+          }
+        }
+        return result;
+      }
+      write_pos += count;
+    }
+
+    read_pos = 0;
+  } while (!end_of_stream);
+
+  if (data_len) {
+    *data_len = 0;
+  }
+  return SR_SUCCESS;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
diff --git a/talk/base/stream.h b/talk/base/stream.h
new file mode 100644
index 0000000..6700c40
--- /dev/null
+++ b/talk/base/stream.h
@@ -0,0 +1,807 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_STREAM_H_
+#define TALK_BASE_STREAM_H_
+
+#include "talk/base/basictypes.h"
+#include "talk/base/buffer.h"
+#include "talk/base/criticalsection.h"
+#include "talk/base/logging.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamInterface is a generic asynchronous stream interface, supporting read,
+// write, and close operations, and asynchronous signalling of state changes.
+// The interface is designed with file, memory, and socket implementations in
+// mind.  Some implementations offer extended operations, such as seeking.
+///////////////////////////////////////////////////////////////////////////////
+
+// The following enumerations are declared outside of the StreamInterface
+// class for brevity in use.
+
+// The SS_OPENING state indicates that the stream will signal open or closed
+// in the future.
+enum StreamState { SS_CLOSED, SS_OPENING, SS_OPEN };
+
+// Stream read/write methods return this value to indicate various success
+// and failure conditions described below.
+enum StreamResult { SR_ERROR, SR_SUCCESS, SR_BLOCK, SR_EOS };
+
+// StreamEvents are used to asynchronously signal state transitionss.  The flags
+// may be combined.
+//  SE_OPEN: The stream has transitioned to the SS_OPEN state
+//  SE_CLOSE: The stream has transitioned to the SS_CLOSED state
+//  SE_READ: Data is available, so Read is likely to not return SR_BLOCK
+//  SE_WRITE: Data can be written, so Write is likely to not return SR_BLOCK
+enum StreamEvent { SE_OPEN = 1, SE_READ = 2, SE_WRITE = 4, SE_CLOSE = 8 };
+
+class Thread;
+
+struct StreamEventData : public MessageData {
+  int events, error;
+  StreamEventData(int ev, int er) : events(ev), error(er) { }
+};
+
+class StreamInterface : public MessageHandler {
+ public:
+  enum {
+    MSG_POST_EVENT = 0xF1F1, MSG_MAX = MSG_POST_EVENT
+  };
+
+  virtual ~StreamInterface();
+
+  virtual StreamState GetState() const = 0;
+
+  // Read attempts to fill buffer of size buffer_len.  Write attempts to send
+  // data_len bytes stored in data.  The variables read and write are set only
+  // on SR_SUCCESS (see below).  Likewise, error is only set on SR_ERROR.
+  // Read and Write return a value indicating:
+  //  SR_ERROR: an error occurred, which is returned in a non-null error
+  //    argument.  Interpretation of the error requires knowledge of the
+  //    stream's concrete type, which limits its usefulness.
+  //  SR_SUCCESS: some number of bytes were successfully written, which is
+  //    returned in a non-null read/write argument.
+  //  SR_BLOCK: the stream is in non-blocking mode, and the operation would
+  //    block, or the stream is in SS_OPENING state.
+  //  SR_EOS: the end-of-stream has been reached, or the stream is in the
+  //    SS_CLOSED state.
+  virtual StreamResult Read(void* buffer, size_t buffer_len,
+                            size_t* read, int* error) = 0;
+  virtual StreamResult Write(const void* data, size_t data_len,
+                             size_t* written, int* error) = 0;
+  // Attempt to transition to the SS_CLOSED state.  SE_CLOSE will not be
+  // signalled as a result of this call.
+  virtual void Close() = 0;
+
+  // Streams may signal one or more StreamEvents to indicate state changes.
+  // The first argument identifies the stream on which the state change occured.
+  // The second argument is a bit-wise combination of StreamEvents.
+  // If SE_CLOSE is signalled, then the third argument is the associated error
+  // code.  Otherwise, the value is undefined.
+  // Note: Not all streams will support asynchronous event signalling.  However,
+  // SS_OPENING and SR_BLOCK returned from stream member functions imply that
+  // certain events will be raised in the future.
+  sigslot::signal3<StreamInterface*, int, int> SignalEvent;
+
+  // Like calling SignalEvent, but posts a message to the specified thread,
+  // which will call SignalEvent.  This helps unroll the stack and prevent
+  // re-entrancy.
+  void PostEvent(Thread* t, int events, int err);
+  // Like the aforementioned method, but posts to the current thread.
+  void PostEvent(int events, int err);
+
+  //
+  // OPTIONAL OPERATIONS
+  //
+  // Not all implementations will support the following operations.  In general,
+  // a stream will only support an operation if it reasonably efficient to do
+  // so.  For example, while a socket could buffer incoming data to support
+  // seeking, it will not do so.  Instead, a buffering stream adapter should
+  // be used.
+  //
+  // Even though several of these operations are related, you should
+  // always use whichever operation is most relevant.  For example, you may
+  // be tempted to use GetSize() and GetPosition() to deduce the result of
+  // GetAvailable().  However, a stream which is read-once may support the
+  // latter operation but not the former.
+  //
+
+  // The following four methods are used to avoid 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
+  // available, or if the method fails.  If the caller processes the data, it
+  // must call ConsumeReadData with the number of processed bytes.  GetReadData
+  // does not require a matching call to ConsumeReadData if the data is not
+  // processed.  Read and ConsumeReadData invalidate the buffer returned by
+  // GetReadData.
+  virtual const void* GetReadData(size_t* data_len) { return NULL; }
+  virtual void ConsumeReadData(size_t used) {}
+
+  // GetWriteBuffer returns a pointer to a buffer which is owned by the stream.
+  // The buffer has a capacity of buf_len bytes.  NULL is returned if there is
+  // no buffer available, or if the method fails.  The call may write data to
+  // the buffer, and then call ConsumeWriteBuffer with the number of bytes
+  // written.  GetWriteBuffer does not require a matching call to
+  // ConsumeWriteData if no data is written.  Write, ForceWrite, and
+  // ConsumeWriteData invalidate the buffer returned by GetWriteBuffer.
+  // TODO: Allow the caller to specify a minimum buffer size.  If the specified
+  // amount of buffer is not yet available, return NULL and Signal SE_WRITE
+  // when it is available.  If the requested amount is too large, return an
+  // error.
+  virtual void* GetWriteBuffer(size_t* buf_len) { return NULL; }
+  virtual void ConsumeWriteBuffer(size_t used) {}
+
+  // Write data_len bytes found in data, circumventing any throttling which
+  // would could cause SR_BLOCK to be returned.  Returns true if all the data
+  // was written.  Otherwise, the method is unsupported, or an unrecoverable
+  // error occurred, and the error value is set.  This method should be used
+  // sparingly to write critical data which should not be throttled.  A stream
+  // which cannot circumvent its blocking constraints should not implement this
+  // method.
+  // NOTE: This interface is being considered experimentally at the moment.  It
+  // would be used by JUDP and BandwidthStream as a way to circumvent certain
+  // soft limits in writing.
+  //virtual bool ForceWrite(const void* data, size_t data_len, int* error) {
+  //  if (error) *error = -1;
+  //  return false;
+  //}
+
+  // Seek to a byte offset from the beginning of the stream.  Returns false if
+  // the stream does not support seeking, or cannot seek to the specified
+  // position.
+  virtual bool SetPosition(size_t position) { return false; }
+
+  // Get the byte offset of the current position from the start of the stream.
+  // Returns false if the position is not known.
+  virtual bool GetPosition(size_t* position) const { return false; }
+
+  // Get the byte length of the entire stream.  Returns false if the length
+  // is not known.
+  virtual bool GetSize(size_t* size) const { return false; }
+
+  // Return the number of Read()-able bytes remaining before end-of-stream.
+  // Returns false if not known.
+  virtual bool GetAvailable(size_t* size) const { return false; }
+
+  // Return the number of Write()-able bytes remaining before end-of-stream.
+  // Returns false if not known.
+  virtual bool GetWriteRemaining(size_t* size) const { return false; }
+
+  // Return true if flush is successful.
+  virtual bool Flush() { return false; }
+
+  // Communicates the amount of data which will be written to the stream.  The
+  // stream may choose to preallocate memory to accomodate this data.  The
+  // stream may return false to indicate that there is not enough room (ie,
+  // Write will return SR_EOS/SR_ERROR at some point).  Note that calling this
+  // function should not affect the existing state of data in the stream.
+  virtual bool ReserveSize(size_t size) { return true; }
+
+  //
+  // CONVENIENCE METHODS
+  //
+  // These methods are implemented in terms of other methods, for convenience.
+  //
+
+  // Seek to the start of the stream.
+  inline bool Rewind() { return SetPosition(0); }
+
+  // WriteAll is a helper function which repeatedly calls Write until all the
+  // data is written, or something other than SR_SUCCESS is returned.  Note that
+  // unlike Write, the argument 'written' is always set, and may be non-zero
+  // on results other than SR_SUCCESS.  The remaining arguments have the
+  // same semantics as Write.
+  StreamResult WriteAll(const void* data, size_t data_len,
+                        size_t* written, int* error);
+
+  // Similar to ReadAll.  Calls Read until buffer_len bytes have been read, or
+  // until a non-SR_SUCCESS result is returned.  'read' is always set.
+  StreamResult ReadAll(void* buffer, size_t buffer_len,
+                       size_t* read, int* error);
+
+  // ReadLine is a helper function which repeatedly calls Read until it hits
+  // the end-of-line character, or something other than SR_SUCCESS.
+  // TODO: this is too inefficient to keep here.  Break this out into a buffered
+  // readline object or adapter
+  StreamResult ReadLine(std::string* line);
+
+ protected:
+  StreamInterface();
+
+  // MessageHandler Interface
+  virtual void OnMessage(Message* msg);
+
+ private:
+  DISALLOW_EVIL_CONSTRUCTORS(StreamInterface);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamAdapterInterface is a convenient base-class for adapting a stream.
+// By default, all operations are pass-through.  Override the methods that you
+// require adaptation.  Streams should really be upgraded to reference-counted.
+// In the meantime, use the owned flag to indicate whether the adapter should
+// own the adapted stream.
+///////////////////////////////////////////////////////////////////////////////
+
+class StreamAdapterInterface : public StreamInterface,
+                               public sigslot::has_slots<> {
+ public:
+  explicit StreamAdapterInterface(StreamInterface* stream, bool owned = true);
+
+  // Core Stream Interface
+  virtual StreamState GetState() const {
+    return stream_->GetState();
+  }
+  virtual StreamResult Read(void* buffer, size_t buffer_len,
+                            size_t* read, int* error) {
+    return stream_->Read(buffer, buffer_len, read, error);
+  }
+  virtual StreamResult Write(const void* data, size_t data_len,
+                             size_t* written, int* error) {
+    return stream_->Write(data, data_len, written, error);
+  }
+  virtual void Close() {
+    stream_->Close();
+  }
+
+  // Optional Stream Interface
+  /*  Note: Many stream adapters were implemented prior to this Read/Write
+      interface.  Therefore, a simple pass through of data in those cases may
+      be broken.  At a later time, we should do a once-over pass of all
+      adapters, and make them compliant with these interfaces, after which this
+      code can be uncommented.
+  virtual const void* GetReadData(size_t* data_len) {
+    return stream_->GetReadData(data_len);
+  }
+  virtual void ConsumeReadData(size_t used) {
+    stream_->ConsumeReadData(used);
+  }
+
+  virtual void* GetWriteBuffer(size_t* buf_len) {
+    return stream_->GetWriteBuffer(buf_len);
+  }
+  virtual void ConsumeWriteBuffer(size_t used) {
+    stream_->ConsumeWriteBuffer(used);
+  }
+  */
+
+  /*  Note: This interface is currently undergoing evaluation.
+  virtual bool ForceWrite(const void* data, size_t data_len, int* error) {
+    return stream_->ForceWrite(data, data_len, error);
+  }
+  */
+
+  virtual bool SetPosition(size_t position) {
+    return stream_->SetPosition(position);
+  }
+  virtual bool GetPosition(size_t* position) const {
+    return stream_->GetPosition(position);
+  }
+  virtual bool GetSize(size_t* size) const {
+    return stream_->GetSize(size);
+  }
+  virtual bool GetAvailable(size_t* size) const {
+    return stream_->GetAvailable(size);
+  }
+  virtual bool GetWriteRemaining(size_t* size) const {
+    return stream_->GetWriteRemaining(size);
+  }
+  virtual bool ReserveSize(size_t size) {
+    return stream_->ReserveSize(size);
+  }
+  virtual bool Flush() {
+    return stream_->Flush();
+  }
+
+  void Attach(StreamInterface* stream, bool owned = true);
+  StreamInterface* Detach();
+
+ protected:
+  virtual ~StreamAdapterInterface();
+
+  // Note that the adapter presents itself as the origin of the stream events,
+  // since users of the adapter may not recognize the adapted object.
+  virtual void OnEvent(StreamInterface* stream, int events, int err) {
+    SignalEvent(this, events, err);
+  }
+  StreamInterface* stream() { return stream_; }
+
+ private:
+  StreamInterface* stream_;
+  bool owned_;
+  DISALLOW_EVIL_CONSTRUCTORS(StreamAdapterInterface);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamTap is a non-modifying, pass-through adapter, which copies all data
+// in either direction to the tap.  Note that errors or blocking on writing to
+// the tap will prevent further tap writes from occurring.
+///////////////////////////////////////////////////////////////////////////////
+
+class StreamTap : public StreamAdapterInterface {
+ public:
+  explicit StreamTap(StreamInterface* stream, StreamInterface* tap);
+
+  void AttachTap(StreamInterface* tap);
+  StreamInterface* DetachTap();
+  StreamResult GetTapResult(int* error);
+
+  // StreamAdapterInterface Interface
+  virtual StreamResult Read(void* buffer, size_t buffer_len,
+                            size_t* read, int* error);
+  virtual StreamResult Write(const void* data, size_t data_len,
+                             size_t* written, int* error);
+
+ private:
+  scoped_ptr<StreamInterface> tap_;
+  StreamResult tap_result_;
+  int tap_error_;
+  DISALLOW_EVIL_CONSTRUCTORS(StreamTap);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamSegment adapts a read stream, to expose a subset of the adapted
+// stream's data.  This is useful for cases where a stream contains multiple
+// documents concatenated together.  StreamSegment can expose a subset of
+// the data as an independent stream, including support for rewinding and
+// seeking.
+///////////////////////////////////////////////////////////////////////////////
+
+class StreamSegment : public StreamAdapterInterface {
+ public:
+  // The current position of the adapted stream becomes the beginning of the
+  // segment.  If a length is specified, it bounds the length of the segment.
+  explicit StreamSegment(StreamInterface* stream);
+  explicit StreamSegment(StreamInterface* stream, size_t length);
+
+  // StreamAdapterInterface Interface
+  virtual StreamResult Read(void* buffer, size_t buffer_len,
+                            size_t* read, int* error);
+  virtual bool SetPosition(size_t position);
+  virtual bool GetPosition(size_t* position) const;
+  virtual bool GetSize(size_t* size) const;
+  virtual bool GetAvailable(size_t* size) const;
+
+ private:
+  size_t start_, pos_, length_;
+  DISALLOW_EVIL_CONSTRUCTORS(StreamSegment);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// NullStream gives errors on read, and silently discards all written data.
+///////////////////////////////////////////////////////////////////////////////
+
+class NullStream : public StreamInterface {
+ public:
+  NullStream();
+  virtual ~NullStream();
+
+  // StreamInterface Interface
+  virtual StreamState GetState() const;
+  virtual StreamResult Read(void* buffer, size_t buffer_len,
+                            size_t* read, int* error);
+  virtual StreamResult Write(const void* data, size_t data_len,
+                             size_t* written, int* error);
+  virtual void Close();
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// FileStream is a simple implementation of a StreamInterface, which does not
+// support asynchronous notification.
+///////////////////////////////////////////////////////////////////////////////
+
+class FileStream : public StreamInterface {
+ public:
+  FileStream();
+  virtual ~FileStream();
+
+  // The semantics of filename and mode are the same as stdio's fopen
+  virtual bool Open(const std::string& filename, const char* mode, int* error);
+  virtual bool OpenShare(const std::string& filename, const char* mode,
+                         int shflag, int* error);
+
+  // By default, reads and writes are buffered for efficiency.  Disabling
+  // buffering causes writes to block until the bytes on disk are updated.
+  virtual bool DisableBuffering();
+
+  virtual StreamState GetState() const;
+  virtual StreamResult Read(void* buffer, size_t buffer_len,
+                            size_t* read, int* error);
+  virtual StreamResult Write(const void* data, size_t data_len,
+                             size_t* written, int* error);
+  virtual void Close();
+  virtual bool SetPosition(size_t position);
+  virtual bool GetPosition(size_t* position) const;
+  virtual bool GetSize(size_t* size) const;
+  virtual bool GetAvailable(size_t* size) const;
+  virtual bool ReserveSize(size_t size);
+
+  virtual bool Flush();
+
+#if defined(POSIX)
+  // Tries to aquire an exclusive lock on the file.
+  // Use OpenShare(...) on win32 to get similar functionality.
+  bool TryLock();
+  bool Unlock();
+#endif
+
+  // Note: Deprecated in favor of Filesystem::GetFileSize().
+  static bool GetSize(const std::string& filename, size_t* size);
+
+ protected:
+  virtual void DoClose();
+
+  FILE* file_;
+
+ private:
+  DISALLOW_EVIL_CONSTRUCTORS(FileStream);
+};
+
+
+// A stream which pushes writes onto a separate thread and
+// returns from the write call immediately.
+class AsyncWriteStream : public StreamInterface {
+ public:
+  // Takes ownership of the stream, but not the thread.
+  AsyncWriteStream(StreamInterface* stream, talk_base::Thread* write_thread)
+      : stream_(stream),
+        write_thread_(write_thread),
+        state_(stream ? stream->GetState() : SS_CLOSED) {
+  }
+
+  virtual ~AsyncWriteStream();
+
+  // StreamInterface Interface
+  virtual StreamState GetState() const { return state_; }
+  // This is needed by some stream writers, such as RtpDumpWriter.
+  virtual bool GetPosition(size_t* position) 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 Flush();
+
+ protected:
+  // From MessageHandler
+  virtual void OnMessage(talk_base::Message* pmsg);
+  virtual void ClearBufferAndWrite();
+
+ private:
+  talk_base::scoped_ptr<StreamInterface> stream_;
+  Thread* write_thread_;
+  StreamState state_;
+  Buffer buffer_;
+  mutable CriticalSection crit_stream_;
+  CriticalSection crit_buffer_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(AsyncWriteStream);
+};
+
+
+#ifdef POSIX
+// A FileStream that is actually not a file, but the output or input of a
+// sub-command. See "man 3 popen" for documentation of the underlying OS popen()
+// function.
+class POpenStream : public FileStream {
+ public:
+  POpenStream() : wait_status_(-1) {}
+  virtual ~POpenStream();
+
+  virtual bool Open(const std::string& subcommand, const char* mode,
+                    int* error);
+  // Same as Open(). shflag is ignored.
+  virtual bool OpenShare(const std::string& subcommand, const char* mode,
+                         int shflag, int* error);
+
+  // Returns the wait status from the last Close() of an Open()'ed stream, or
+  // -1 if no Open()+Close() has been done on this object. Meaning of the number
+  // is documented in "man 2 wait".
+  int GetWaitStatus() const { return wait_status_; }
+
+ protected:
+  virtual void DoClose();
+
+ private:
+  int wait_status_;
+};
+#endif  // POSIX
+
+///////////////////////////////////////////////////////////////////////////////
+// MemoryStream is a simple implementation of a StreamInterface over in-memory
+// data.  Data is read and written at the current seek position.  Reads return
+// end-of-stream when they reach the end of data.  Writes actually extend the
+// end of data mark.
+///////////////////////////////////////////////////////////////////////////////
+
+class MemoryStreamBase : public StreamInterface {
+ public:
+  virtual StreamState GetState() const;
+  virtual StreamResult Read(void* buffer, size_t bytes, size_t* bytes_read,
+                            int* error);
+  virtual StreamResult Write(const void* buffer, size_t bytes,
+                             size_t* bytes_written, int* error);
+  virtual void Close();
+  virtual bool SetPosition(size_t position);
+  virtual bool GetPosition(size_t* position) const;
+  virtual bool GetSize(size_t* size) const;
+  virtual bool GetAvailable(size_t* size) const;
+  virtual bool ReserveSize(size_t size);
+
+  char* GetBuffer() { return buffer_; }
+  const char* GetBuffer() const { return buffer_; }
+
+ protected:
+  MemoryStreamBase();
+
+  virtual StreamResult DoReserve(size_t size, int* error);
+
+  // Invariant: 0 <= seek_position <= data_length_ <= buffer_length_
+  char* buffer_;
+  size_t buffer_length_;
+  size_t data_length_;
+  size_t seek_position_;
+
+ private:
+  DISALLOW_EVIL_CONSTRUCTORS(MemoryStreamBase);
+};
+
+// MemoryStream dynamically resizes to accomodate written data.
+
+class MemoryStream : public MemoryStreamBase {
+ public:
+  MemoryStream();
+  explicit MemoryStream(const char* data);  // Calls SetData(data, strlen(data))
+  MemoryStream(const void* data, size_t length);  // Calls SetData(data, length)
+  virtual ~MemoryStream();
+
+  void SetData(const void* data, size_t length);
+
+ protected:
+  virtual StreamResult DoReserve(size_t size, int* error);
+  // Memory Streams are aligned for efficiency.
+  static const int kAlignment = 16;
+  char* buffer_alloc_;
+};
+
+// ExternalMemoryStream adapts an external memory buffer, so writes which would
+// extend past the end of the buffer will return end-of-stream.
+
+class ExternalMemoryStream : public MemoryStreamBase {
+ public:
+  ExternalMemoryStream();
+  ExternalMemoryStream(void* data, size_t length);
+  virtual ~ExternalMemoryStream();
+
+  void SetData(void* data, size_t length);
+};
+
+// FifoBuffer allows for efficient, thread-safe buffering of data between
+// writer and reader. As the data can wrap around the end of the buffer,
+// MemoryStreamBase can't help us here.
+
+class FifoBuffer : public StreamInterface {
+ public:
+  // Creates a FIFO buffer with the specified capacity.
+  explicit FifoBuffer(size_t length);
+  // 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;
+  // Resizes the buffer to the specified capacity. Fails if data_length_ > size
+  bool SetCapacity(size_t length);
+
+  // Read into |buffer| with an offset from the current read position, offset
+  // is specified in number of bytes.
+  // This method doesn't adjust read position nor the number of available
+  // bytes, user has to call ConsumeReadData() to do this.
+  StreamResult ReadOffset(void* buffer, size_t bytes, size_t offset,
+                          size_t* bytes_read);
+
+  // Write |buffer| with an offset from the current write position, offset is
+  // specified in number of bytes.
+  // This method doesn't adjust the number of buffered bytes, user has to call
+  // ConsumeWriteBuffer() to do this.
+  StreamResult WriteOffset(const void* buffer, size_t bytes, size_t offset,
+                           size_t* bytes_written);
+
+  // StreamInterface methods
+  virtual StreamState GetState() const;
+  virtual StreamResult Read(void* buffer, size_t bytes,
+                            size_t* bytes_read, int* error);
+  virtual StreamResult Write(const void* buffer, size_t bytes,
+                             size_t* bytes_written, int* error);
+  virtual void Close();
+  virtual const void* GetReadData(size_t* data_len);
+  virtual void ConsumeReadData(size_t used);
+  virtual void* GetWriteBuffer(size_t* buf_len);
+  virtual void ConsumeWriteBuffer(size_t used);
+  virtual bool GetWriteRemaining(size_t* size) const;
+
+ private:
+  // Helper method that implements ReadOffset. Caller must acquire a lock
+  // when calling this method.
+  StreamResult ReadOffsetLocked(void* buffer, size_t bytes, size_t offset,
+                                size_t* bytes_read);
+
+  // Helper method that implements WriteOffset. Caller must acquire a lock
+  // when calling this method.
+  StreamResult WriteOffsetLocked(const void* buffer, size_t bytes,
+                                 size_t offset, size_t* bytes_written);
+
+  StreamState state_;  // keeps the opened/closed state of the stream
+  scoped_array<char> buffer_;  // the allocated buffer
+  size_t buffer_length_;  // size of the allocated buffer
+  size_t data_length_;  // amount of readable data in the buffer
+  size_t read_position_;  // offset to the readable data
+  Thread* owner_;  // stream callbacks are dispatched on this thread
+  mutable CriticalSection crit_;  // object lock
+  DISALLOW_EVIL_CONSTRUCTORS(FifoBuffer);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class LoggingAdapter : public StreamAdapterInterface {
+ public:
+  LoggingAdapter(StreamInterface* stream, LoggingSeverity level,
+                 const std::string& label, bool hex_mode = false);
+
+  void set_label(const std::string& label);
+
+  virtual StreamResult Read(void* buffer, size_t buffer_len,
+                            size_t* read, int* error);
+  virtual StreamResult Write(const void* data, size_t data_len,
+                             size_t* written, int* error);
+  virtual void Close();
+
+ protected:
+  virtual void OnEvent(StreamInterface* stream, int events, int err);
+
+ private:
+  LoggingSeverity level_;
+  std::string label_;
+  bool hex_mode_;
+  LogMultilineState lms_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(LoggingAdapter);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// StringStream - Reads/Writes to an external std::string
+///////////////////////////////////////////////////////////////////////////////
+
+class StringStream : public StreamInterface {
+ public:
+  explicit StringStream(std::string& str);
+  explicit StringStream(const std::string& str);
+
+  virtual StreamState GetState() const;
+  virtual StreamResult Read(void* buffer, size_t buffer_len,
+                            size_t* read, int* error);
+  virtual StreamResult Write(const void* data, size_t data_len,
+                             size_t* written, int* error);
+  virtual void Close();
+  virtual bool SetPosition(size_t position);
+  virtual bool GetPosition(size_t* position) const;
+  virtual bool GetSize(size_t* size) const;
+  virtual bool GetAvailable(size_t* size) const;
+  virtual bool ReserveSize(size_t size);
+
+ private:
+  std::string& str_;
+  size_t read_pos_;
+  bool read_only_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamReference - A reference counting stream adapter
+///////////////////////////////////////////////////////////////////////////////
+
+// Keep in mind that the streams and adapters defined in this file are
+// not thread-safe, so this has limited uses.
+
+// A StreamRefCount holds the reference count and a pointer to the
+// wrapped stream. It deletes the wrapped stream when there are no
+// more references. We can then have multiple StreamReference
+// instances pointing to one StreamRefCount, all wrapping the same
+// stream.
+
+class StreamReference : public StreamAdapterInterface {
+  class StreamRefCount;
+ public:
+  // Constructor for the first reference to a stream
+  // Note: get more references through NewReference(). Use this
+  // constructor only once on a given stream.
+  explicit StreamReference(StreamInterface* stream);
+  StreamInterface* GetStream() { return stream(); }
+  StreamInterface* NewReference();
+  virtual ~StreamReference();
+
+ private:
+  class StreamRefCount {
+   public:
+    explicit StreamRefCount(StreamInterface* stream)
+        : stream_(stream), ref_count_(1) {
+    }
+    void AddReference() {
+      CritScope lock(&cs_);
+      ++ref_count_;
+    }
+    void Release() {
+      int ref_count;
+      {  // Atomic ops would have been a better fit here.
+        CritScope lock(&cs_);
+        ref_count = --ref_count_;
+      }
+      if (ref_count == 0) {
+        delete stream_;
+        delete this;
+      }
+    }
+   private:
+    StreamInterface* stream_;
+    int ref_count_;
+    CriticalSection cs_;
+    DISALLOW_EVIL_CONSTRUCTORS(StreamRefCount);
+  };
+
+  // Constructor for adding references
+  explicit StreamReference(StreamRefCount* stream_ref_count,
+                           StreamInterface* stream);
+
+  StreamRefCount* stream_ref_count_;
+  DISALLOW_EVIL_CONSTRUCTORS(StreamReference);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Flow attempts to move bytes from source to sink via buffer of size
+// buffer_len.  The function returns SR_SUCCESS when source reaches
+// end-of-stream (returns SR_EOS), and all the data has been written successful
+// to sink.  Alternately, if source returns SR_BLOCK or SR_ERROR, or if sink
+// returns SR_BLOCK, SR_ERROR, or SR_EOS, then the function immediately returns
+// with the unexpected StreamResult value.
+// data_len is the length of the valid data in buffer. in case of error
+// this is the data that read from source but can't move to destination.
+// as a pass in parameter, it indicates data in buffer that should move to sink
+StreamResult Flow(StreamInterface* source,
+                  char* buffer, size_t buffer_len,
+                  StreamInterface* sink, size_t* data_len = NULL);
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_STREAM_H_
diff --git a/talk/base/stream_unittest.cc b/talk/base/stream_unittest.cc
new file mode 100644
index 0000000..856c943
--- /dev/null
+++ b/talk/base/stream_unittest.cc
@@ -0,0 +1,509 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/stream.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// TestStream
+///////////////////////////////////////////////////////////////////////////////
+
+class TestStream : public StreamInterface {
+ public:
+  TestStream() : pos_(0) { }
+
+  virtual StreamState GetState() const { return SS_OPEN; }
+  virtual StreamResult Read(void* buffer, size_t buffer_len,
+                            size_t* read, int* error) {
+    unsigned char* uc_buffer = static_cast<unsigned char*>(buffer);
+    for (size_t i = 0; i < buffer_len; ++i) {
+      uc_buffer[i] = static_cast<unsigned char>(pos_++);
+    }
+    if (read)
+      *read = buffer_len;
+    return SR_SUCCESS;
+  }
+  virtual StreamResult Write(const void* data, size_t data_len,
+                             size_t* written, int* error) {
+    if (error)
+      *error = -1;
+    return SR_ERROR;
+  }
+  virtual void Close() { }
+  virtual bool SetPosition(size_t position) {
+    pos_ = position;
+    return true;
+  }
+  virtual bool GetPosition(size_t* position) const {
+    if (position) *position = pos_;
+    return true;
+  }
+  virtual bool GetSize(size_t* size) const {
+    return false;
+  }
+  virtual bool GetAvailable(size_t* size) const {
+    return false;
+  }
+
+ private:
+  size_t pos_;
+};
+
+bool VerifyTestBuffer(unsigned char* buffer, size_t len,
+                      unsigned char value) {
+  bool passed = true;
+  for (size_t i = 0; i < len; ++i) {
+    if (buffer[i] != value++) {
+      passed = false;
+      break;
+    }
+  }
+  // Ensure that we don't pass again without re-writing
+  memset(buffer, 0, len);
+  return passed;
+}
+
+void SeekTest(StreamInterface* stream, const unsigned char value) {
+  size_t bytes;
+  unsigned char buffer[13] = { 0 };
+  const size_t kBufSize = sizeof(buffer);
+
+  EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS);
+  EXPECT_EQ(bytes, kBufSize);
+  EXPECT_TRUE(VerifyTestBuffer(buffer, kBufSize, value));
+  EXPECT_TRUE(stream->GetPosition(&bytes));
+  EXPECT_EQ(13U, bytes);
+
+  EXPECT_TRUE(stream->SetPosition(7));
+
+  EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS);
+  EXPECT_EQ(bytes, kBufSize);
+  EXPECT_TRUE(VerifyTestBuffer(buffer, kBufSize, value + 7));
+  EXPECT_TRUE(stream->GetPosition(&bytes));
+  EXPECT_EQ(20U, bytes);
+}
+
+TEST(StreamSegment, TranslatesPosition) {
+  TestStream* test = new TestStream;
+  // Verify behavior of original stream
+  SeekTest(test, 0);
+  StreamSegment* segment = new StreamSegment(test);
+  // Verify behavior of adapted stream (all values offset by 20)
+  SeekTest(segment, 20);
+  delete segment;
+}
+
+TEST(StreamSegment, SupportsArtificialTermination) {
+  TestStream* test = new TestStream;
+
+  size_t bytes;
+  unsigned char buffer[5000] = { 0 };
+  const size_t kBufSize = sizeof(buffer);
+
+  {
+    StreamInterface* stream = test;
+
+    // Read a lot of bytes
+    EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS);
+    EXPECT_EQ(bytes, kBufSize);
+    EXPECT_TRUE(VerifyTestBuffer(buffer, kBufSize, 0));
+
+    // Test seeking far ahead
+    EXPECT_TRUE(stream->SetPosition(12345));
+
+    // Read a bunch more bytes
+    EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS);
+    EXPECT_EQ(bytes, kBufSize);
+    EXPECT_TRUE(VerifyTestBuffer(buffer, kBufSize, 12345 % 256));
+  }
+
+  // Create a segment of test stream in range [100,600)
+  EXPECT_TRUE(test->SetPosition(100));
+  StreamSegment* segment = new StreamSegment(test, 500);
+
+  {
+    StreamInterface* stream = segment;
+
+    EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS);
+    EXPECT_EQ(500U, bytes);
+    EXPECT_TRUE(VerifyTestBuffer(buffer, 500, 100));
+    EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_EOS);
+
+    // Test seeking past "end" of stream
+    EXPECT_FALSE(stream->SetPosition(12345));
+    EXPECT_FALSE(stream->SetPosition(501));
+
+    // Test seeking to end (edge case)
+    EXPECT_TRUE(stream->SetPosition(500));
+    EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_EOS);
+
+    // Test seeking to start
+    EXPECT_TRUE(stream->SetPosition(0));
+    EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS);
+    EXPECT_EQ(500U, bytes);
+    EXPECT_TRUE(VerifyTestBuffer(buffer, 500, 100));
+    EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_EOS);
+  }
+
+  delete segment;
+}
+
+TEST(FifoBufferTest, TestAll) {
+  const size_t kSize = 16;
+  const char in[kSize * 2 + 1] = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
+  char out[kSize * 2];
+  void* p;
+  const void* q;
+  size_t bytes;
+  FifoBuffer buf(kSize);
+  StreamInterface* stream = &buf;
+
+  // Test assumptions about base state
+  EXPECT_EQ(SS_OPEN, stream->GetState());
+  EXPECT_EQ(SR_BLOCK, stream->Read(out, kSize, &bytes, NULL));
+  EXPECT_TRUE(NULL != stream->GetReadData(&bytes));
+  EXPECT_EQ((size_t)0, bytes);
+  stream->ConsumeReadData(0);
+  EXPECT_TRUE(NULL != stream->GetWriteBuffer(&bytes));
+  EXPECT_EQ(kSize, bytes);
+  stream->ConsumeWriteBuffer(0);
+
+  // Try a full write
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL));
+  EXPECT_EQ(kSize, bytes);
+
+  // Try a write that should block
+  EXPECT_EQ(SR_BLOCK, stream->Write(in, kSize, &bytes, NULL));
+
+  // Try a full read
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize, &bytes, NULL));
+  EXPECT_EQ(kSize, bytes);
+  EXPECT_EQ(0, memcmp(in, out, kSize));
+
+  // Try a read that should block
+  EXPECT_EQ(SR_BLOCK, stream->Read(out, kSize, &bytes, NULL));
+
+  // Try a too-big write
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize * 2, &bytes, NULL));
+  EXPECT_EQ(bytes, kSize);
+
+  // Try a too-big read
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize * 2, &bytes, NULL));
+  EXPECT_EQ(kSize, bytes);
+  EXPECT_EQ(0, memcmp(in, out, kSize));
+
+  // Try some small writes and reads
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL));
+  EXPECT_EQ(kSize / 2, bytes);
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL));
+  EXPECT_EQ(kSize / 2, bytes);
+  EXPECT_EQ(0, memcmp(in, out, kSize / 2));
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL));
+  EXPECT_EQ(kSize / 2, bytes);
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL));
+  EXPECT_EQ(kSize / 2, bytes);
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL));
+  EXPECT_EQ(kSize / 2, bytes);
+  EXPECT_EQ(0, memcmp(in, out, kSize / 2));
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL));
+  EXPECT_EQ(kSize / 2, bytes);
+  EXPECT_EQ(0, memcmp(in, out, kSize / 2));
+
+  // Try wraparound reads and writes in the following pattern
+  // WWWWWWWWWWWW.... 0123456789AB....
+  // RRRRRRRRXXXX.... ........89AB....
+  // WWWW....XXXXWWWW 4567....89AB0123
+  // XXXX....RRRRXXXX 4567........0123
+  // XXXXWWWWWWWWXXXX 4567012345670123
+  // RRRRXXXXXXXXRRRR ....01234567....
+  // ....RRRRRRRR.... ................
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize * 3 / 4, &bytes, NULL));
+  EXPECT_EQ(kSize * 3 / 4, bytes);
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL));
+  EXPECT_EQ(kSize / 2, bytes);
+  EXPECT_EQ(0, memcmp(in, out, kSize / 2));
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL));
+  EXPECT_EQ(kSize / 2, bytes);
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 4, &bytes, NULL));
+  EXPECT_EQ(kSize / 4 , bytes);
+  EXPECT_EQ(0, memcmp(in + kSize / 2, out, kSize / 4));
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL));
+  EXPECT_EQ(kSize / 2, bytes);
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL));
+  EXPECT_EQ(kSize / 2 , bytes);
+  EXPECT_EQ(0, memcmp(in, out, kSize / 2));
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL));
+  EXPECT_EQ(kSize / 2 , bytes);
+  EXPECT_EQ(0, memcmp(in, out, kSize / 2));
+
+  // Use GetWriteBuffer to reset the read_position for the next tests
+  stream->GetWriteBuffer(&bytes);
+  stream->ConsumeWriteBuffer(0);
+
+  // Try using GetReadData to do a full read
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL));
+  q = stream->GetReadData(&bytes);
+  EXPECT_TRUE(NULL != q);
+  EXPECT_EQ(kSize, bytes);
+  EXPECT_EQ(0, memcmp(q, in, kSize));
+  stream->ConsumeReadData(kSize);
+  EXPECT_EQ(SR_BLOCK, stream->Read(out, kSize, &bytes, NULL));
+
+  // Try using GetReadData to do some small reads
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL));
+  q = stream->GetReadData(&bytes);
+  EXPECT_TRUE(NULL != q);
+  EXPECT_EQ(kSize, bytes);
+  EXPECT_EQ(0, memcmp(q, in, kSize / 2));
+  stream->ConsumeReadData(kSize / 2);
+  q = stream->GetReadData(&bytes);
+  EXPECT_TRUE(NULL != q);
+  EXPECT_EQ(kSize / 2, bytes);
+  EXPECT_EQ(0, memcmp(q, in + kSize / 2, kSize / 2));
+  stream->ConsumeReadData(kSize / 2);
+  EXPECT_EQ(SR_BLOCK, stream->Read(out, kSize, &bytes, NULL));
+
+  // Try using GetReadData in a wraparound case
+  // WWWWWWWWWWWWWWWW 0123456789ABCDEF
+  // RRRRRRRRRRRRXXXX ............CDEF
+  // WWWWWWWW....XXXX 01234567....CDEF
+  // ............RRRR 01234567........
+  // RRRRRRRR........ ................
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL));
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize * 3 / 4, &bytes, NULL));
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL));
+  q = stream->GetReadData(&bytes);
+  EXPECT_TRUE(NULL != q);
+  EXPECT_EQ(kSize / 4, bytes);
+  EXPECT_EQ(0, memcmp(q, in + kSize * 3 / 4, kSize / 4));
+  stream->ConsumeReadData(kSize / 4);
+  q = stream->GetReadData(&bytes);
+  EXPECT_TRUE(NULL != q);
+  EXPECT_EQ(kSize / 2, bytes);
+  EXPECT_EQ(0, memcmp(q, in, kSize / 2));
+  stream->ConsumeReadData(kSize / 2);
+
+  // Use GetWriteBuffer to reset the read_position for the next tests
+  stream->GetWriteBuffer(&bytes);
+  stream->ConsumeWriteBuffer(0);
+
+  // Try using GetWriteBuffer to do a full write
+  p = stream->GetWriteBuffer(&bytes);
+  EXPECT_TRUE(NULL != p);
+  EXPECT_EQ(kSize, bytes);
+  memcpy(p, in, kSize);
+  stream->ConsumeWriteBuffer(kSize);
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize, &bytes, NULL));
+  EXPECT_EQ(kSize, bytes);
+  EXPECT_EQ(0, memcmp(in, out, kSize));
+
+  // Try using GetWriteBuffer to do some small writes
+  p = stream->GetWriteBuffer(&bytes);
+  EXPECT_TRUE(NULL != p);
+  EXPECT_EQ(kSize, bytes);
+  memcpy(p, in, kSize / 2);
+  stream->ConsumeWriteBuffer(kSize / 2);
+  p = stream->GetWriteBuffer(&bytes);
+  EXPECT_TRUE(NULL != p);
+  EXPECT_EQ(kSize / 2, bytes);
+  memcpy(p, in + kSize / 2, kSize / 2);
+  stream->ConsumeWriteBuffer(kSize / 2);
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize, &bytes, NULL));
+  EXPECT_EQ(kSize, bytes);
+  EXPECT_EQ(0, memcmp(in, out, kSize));
+
+  // Try using GetWriteBuffer in a wraparound case
+  // WWWWWWWWWWWW.... 0123456789AB....
+  // RRRRRRRRXXXX.... ........89AB....
+  // ........XXXXWWWW ........89AB0123
+  // WWWW....XXXXXXXX 4567....89AB0123
+  // RRRR....RRRRRRRR ................
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize * 3 / 4, &bytes, NULL));
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL));
+  p = stream->GetWriteBuffer(&bytes);
+  EXPECT_TRUE(NULL != p);
+  EXPECT_EQ(kSize / 4, bytes);
+  memcpy(p, in, kSize / 4);
+  stream->ConsumeWriteBuffer(kSize / 4);
+  p = stream->GetWriteBuffer(&bytes);
+  EXPECT_TRUE(NULL != p);
+  EXPECT_EQ(kSize / 2, bytes);
+  memcpy(p, in + kSize / 4, kSize / 4);
+  stream->ConsumeWriteBuffer(kSize / 4);
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize * 3 / 4, &bytes, NULL));
+  EXPECT_EQ(kSize * 3 / 4, bytes);
+  EXPECT_EQ(0, memcmp(in + kSize / 2, out, kSize / 4));
+  EXPECT_EQ(0, memcmp(in, out + kSize / 4, kSize / 4));
+
+  // Check that the stream is now empty
+  EXPECT_EQ(SR_BLOCK, stream->Read(out, kSize, &bytes, NULL));
+
+  // Try growing the buffer
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL));
+  EXPECT_EQ(kSize, bytes);
+  EXPECT_TRUE(buf.SetCapacity(kSize * 2));
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in + kSize, kSize, &bytes, NULL));
+  EXPECT_EQ(kSize, bytes);
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize * 2, &bytes, NULL));
+  EXPECT_EQ(kSize * 2, bytes);
+  EXPECT_EQ(0, memcmp(in, out, kSize * 2));
+
+  // Try shrinking the buffer
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL));
+  EXPECT_EQ(kSize, bytes);
+  EXPECT_TRUE(buf.SetCapacity(kSize));
+  EXPECT_EQ(SR_BLOCK, stream->Write(in, kSize, &bytes, NULL));
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize, &bytes, NULL));
+  EXPECT_EQ(kSize, bytes);
+  EXPECT_EQ(0, memcmp(in, out, kSize));
+
+  // Write to the stream, close it, read the remaining bytes
+  EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL));
+  stream->Close();
+  EXPECT_EQ(SS_CLOSED, stream->GetState());
+  EXPECT_EQ(SR_EOS, stream->Write(in, kSize / 2, &bytes, NULL));
+  EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL));
+  EXPECT_EQ(0, memcmp(in, out, kSize / 2));
+  EXPECT_EQ(SR_EOS, stream->Read(out, kSize / 2, &bytes, NULL));
+}
+
+TEST(FifoBufferTest, 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";
+  char out[kSize * 2];
+  FifoBuffer buf(kSize);
+
+  // Write 14 bytes.
+  EXPECT_EQ(SR_SUCCESS, buf.Write(in, 14, NULL, NULL));
+
+  // Make sure data is in |buf|.
+  size_t buffered;
+  EXPECT_TRUE(buf.GetBuffered(&buffered));
+  EXPECT_EQ(14u, buffered);
+
+  // Read 10 bytes.
+  buf.ConsumeReadData(10);
+
+  // There should be now 12 bytes of available space.
+  size_t remaining;
+  EXPECT_TRUE(buf.GetWriteRemaining(&remaining));
+  EXPECT_EQ(12u, remaining);
+
+  // Write at offset 12, this should fail.
+  EXPECT_EQ(SR_BLOCK, buf.WriteOffset(in, 10, 12, NULL));
+
+  // Write 8 bytes at offset 4, this wraps around the buffer.
+  EXPECT_EQ(SR_SUCCESS, buf.WriteOffset(in, 8, 4, NULL));
+
+  // Number of available space remains the same until we call
+  // ConsumeWriteBuffer().
+  EXPECT_TRUE(buf.GetWriteRemaining(&remaining));
+  EXPECT_EQ(12u, remaining);
+  buf.ConsumeWriteBuffer(12);
+
+  // There's 4 bytes bypassed and 4 bytes no read so skip them and verify the
+  // 8 bytes written.
+  size_t read;
+  EXPECT_EQ(SR_SUCCESS, buf.ReadOffset(out, 8, 8, &read));
+  EXPECT_EQ(8u, read);
+  EXPECT_EQ(0, memcmp(out, in, 8));
+
+  // There should still be 16 bytes available for reading.
+  EXPECT_TRUE(buf.GetBuffered(&buffered));
+  EXPECT_EQ(16u, buffered);
+
+  // Read at offset 16, this should fail since we don't have that much data.
+  EXPECT_EQ(SR_BLOCK, buf.ReadOffset(out, 10, 16, NULL));
+}
+
+TEST(AsyncWriteTest, TestWrite) {
+  FifoBuffer* buf = new FifoBuffer(100);
+  AsyncWriteStream stream(buf, Thread::Current());
+  EXPECT_EQ(SS_OPEN, stream.GetState());
+
+  // Write "abc".  Will go to the logging thread, which is the current
+  // thread.
+  stream.Write("abc", 3, NULL, NULL);
+  char bytes[100];
+  size_t count;
+  // Messages on the thread's queue haven't been processed, so "abc"
+  // hasn't been written yet.
+  EXPECT_NE(SR_SUCCESS, buf->ReadOffset(&bytes, 3, 0, &count));
+  // Now we process the messages on the thread's queue, so "abc" has
+  // been written.
+  EXPECT_TRUE_WAIT(SR_SUCCESS == buf->ReadOffset(&bytes, 3, 0, &count), 10);
+  EXPECT_EQ(3u, count);
+  EXPECT_EQ(0, memcmp(bytes, "abc", 3));
+
+  // Write "def".  Will go to the logging thread, which is the current
+  // thread.
+  stream.Write("d", 1, &count, NULL);
+  stream.Write("e", 1, &count, NULL);
+  stream.Write("f", 1, &count, NULL);
+  EXPECT_EQ(1u, count);
+  // Messages on the thread's queue haven't been processed, so "def"
+  // hasn't been written yet.
+  EXPECT_NE(SR_SUCCESS, buf->ReadOffset(&bytes, 3, 3, &count));
+  // Flush() causes the message to be processed, so "def" has now been
+  // written.
+  stream.Flush();
+  EXPECT_EQ(SR_SUCCESS, buf->ReadOffset(&bytes, 3, 3, &count));
+  EXPECT_EQ(3u, count);
+  EXPECT_EQ(0, memcmp(bytes, "def", 3));
+
+  // Write "xyz".  Will go to the logging thread, which is the current
+  // thread.
+  stream.Write("xyz", 3, &count, NULL);
+  EXPECT_EQ(3u, count);
+  // Messages on the thread's queue haven't been processed, so "xyz"
+  // hasn't been written yet.
+  EXPECT_NE(SR_SUCCESS, buf->ReadOffset(&bytes, 3, 6, &count));
+  // Close() causes the message to be processed, so "xyz" has now been
+  // written.
+  stream.Close();
+  EXPECT_EQ(SR_SUCCESS, buf->ReadOffset(&bytes, 3, 6, &count));
+  EXPECT_EQ(3u, count);
+  EXPECT_EQ(0, memcmp(bytes, "xyz", 3));
+  EXPECT_EQ(SS_CLOSED, stream.GetState());
+
+  // Is't closed, so the writes should fail.
+  EXPECT_EQ(SR_ERROR, stream.Write("000", 3, NULL, NULL));
+
+}
+
+}  // namespace talk_base
diff --git a/talk/base/stringdigest.h b/talk/base/stringdigest.h
new file mode 100644
index 0000000..f03e92e
--- /dev/null
+++ b/talk/base/stringdigest.h
@@ -0,0 +1,34 @@
+/*
+ * 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_BASE_STRINGDIGEST_H_
+#define TALK_BASE_STRINGDIGEST_H_
+
+// TODO: Update remaining callers to use messagedigest.h instead
+#include "talk/base/messagedigest.h"
+
+#endif  // TALK_BASE_STRINGDIGEST_H_
diff --git a/talk/base/stringencode.cc b/talk/base/stringencode.cc
new file mode 100644
index 0000000..194848e
--- /dev/null
+++ b/talk/base/stringencode.cc
@@ -0,0 +1,674 @@
+/*
+ * 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/stringencode.h"
+
+#include <cstdio>
+#include <cstdlib>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/common.h"
+#include "talk/base/stringutils.h"
+
+namespace talk_base {
+
+/////////////////////////////////////////////////////////////////////////////
+// String Encoding Utilities
+/////////////////////////////////////////////////////////////////////////////
+
+size_t escape(char * buffer, size_t buflen,
+              const char * source, size_t srclen,
+              const char * illegal, char escape) {
+  ASSERT(NULL != buffer);  // TODO: estimate output size
+  if (buflen <= 0)
+    return 0;
+
+  size_t srcpos = 0, bufpos = 0;
+  while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+    char ch = source[srcpos++];
+    if ((ch == escape) || ::strchr(illegal, ch)) {
+      if (bufpos + 2 >= buflen)
+        break;
+      buffer[bufpos++] = escape;
+    }
+    buffer[bufpos++] = ch;
+  }
+
+  buffer[bufpos] = '\0';
+  return bufpos;
+}
+
+size_t unescape(char * buffer, size_t buflen,
+                const char * source, size_t srclen,
+                char escape) {
+  ASSERT(NULL != buffer);  // TODO: estimate output size
+  if (buflen <= 0)
+    return 0;
+
+  size_t srcpos = 0, bufpos = 0;
+  while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+    char ch = source[srcpos++];
+    if ((ch == escape) && (srcpos < srclen)) {
+      ch = source[srcpos++];
+    }
+    buffer[bufpos++] = ch;
+  }
+  buffer[bufpos] = '\0';
+  return bufpos;
+}
+
+size_t encode(char * buffer, size_t buflen,
+              const char * source, size_t srclen,
+              const char * illegal, char escape) {
+  ASSERT(NULL != buffer);  // TODO: estimate output size
+  if (buflen <= 0)
+    return 0;
+
+  size_t srcpos = 0, bufpos = 0;
+  while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+    char ch = source[srcpos++];
+    if ((ch != escape) && !::strchr(illegal, ch)) {
+      buffer[bufpos++] = ch;
+    } else if (bufpos + 3 >= buflen) {
+      break;
+    } else {
+      buffer[bufpos+0] = escape;
+      buffer[bufpos+1] = hex_encode((static_cast<unsigned char>(ch) >> 4) & 0xF);
+      buffer[bufpos+2] = hex_encode((static_cast<unsigned char>(ch)     ) & 0xF);
+      bufpos += 3;
+    }
+  }
+  buffer[bufpos] = '\0';
+  return bufpos;
+}
+
+size_t decode(char * buffer, size_t buflen,
+              const char * source, size_t srclen,
+              char escape) {
+  if (buflen <= 0)
+    return 0;
+
+  unsigned char h1, h2;
+  size_t srcpos = 0, bufpos = 0;
+  while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+    char ch = source[srcpos++];
+    if ((ch == escape)
+        && (srcpos + 1 < srclen)
+        && hex_decode(source[srcpos], &h1)
+        && hex_decode(source[srcpos+1], &h2)) {
+      buffer[bufpos++] = (h1 << 4) | h2;
+      srcpos += 2;
+    } else {
+      buffer[bufpos++] = ch;
+    }
+  }
+  buffer[bufpos] = '\0';
+  return bufpos;
+}
+
+const char* unsafe_filename_characters() {
+  // It might be better to have a single specification which is the union of
+  // all operating systems, unless one system is overly restrictive.
+#ifdef WIN32
+  return "\\/:*?\"<>|";
+#else  // !WIN32
+  // TODO
+  ASSERT(false);
+  return "";
+#endif  // !WIN23
+}
+
+const unsigned char URL_UNSAFE  = 0x1; // 0-33 "#$%&+,/:;<=>?@[\]^`{|} 127
+const unsigned char XML_UNSAFE  = 0x2; // "&'<>
+const unsigned char HTML_UNSAFE = 0x2; // "&'<>
+
+//  ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 6 5 7 8 9 : ; < = > ?
+//@ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _
+//` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~
+
+const unsigned char ASCII_CLASS[128] = {
+  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  1,0,3,1,1,1,3,2,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,1,1,3,1,3,1,
+  1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,
+  1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1,
+};
+
+size_t url_encode(char * buffer, size_t buflen,
+                  const char * source, size_t srclen) {
+  if (NULL == buffer)
+    return srclen * 3 + 1;
+  if (buflen <= 0)
+    return 0;
+
+  size_t srcpos = 0, bufpos = 0;
+  while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+    unsigned char ch = source[srcpos++];
+    if ((ch < 128) && (ASCII_CLASS[ch] & URL_UNSAFE)) {
+      if (bufpos + 3 >= buflen) {
+        break;
+      }
+      buffer[bufpos+0] = '%';
+      buffer[bufpos+1] = hex_encode((ch >> 4) & 0xF);
+      buffer[bufpos+2] = hex_encode((ch     ) & 0xF);
+      bufpos += 3;
+    } else {
+      buffer[bufpos++] = ch;
+    }
+  }
+  buffer[bufpos] = '\0';
+  return bufpos;
+}
+
+size_t url_decode(char * buffer, size_t buflen,
+                  const char * source, size_t srclen) {
+  if (NULL == buffer)
+    return srclen + 1;
+  if (buflen <= 0)
+    return 0;
+
+  unsigned char h1, h2;
+  size_t srcpos = 0, bufpos = 0;
+  while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+    unsigned char ch = source[srcpos++];
+    if (ch == '+') {
+      buffer[bufpos++] = ' ';
+    } else if ((ch == '%')
+               && (srcpos + 1 < srclen)
+               && hex_decode(source[srcpos], &h1)
+               && hex_decode(source[srcpos+1], &h2))
+    {
+      buffer[bufpos++] = (h1 << 4) | h2;
+      srcpos += 2;
+    } else {
+      buffer[bufpos++] = ch;
+    }
+  }
+  buffer[bufpos] = '\0';
+  return bufpos;
+}
+
+size_t utf8_decode(const char* source, size_t srclen, unsigned long* value) {
+  const unsigned char* s = reinterpret_cast<const unsigned char*>(source);
+  if ((s[0] & 0x80) == 0x00) {                    // Check s[0] == 0xxxxxxx
+    *value = s[0];
+    return 1;
+  }
+  if ((srclen < 2) || ((s[1] & 0xC0) != 0x80)) {  // Check s[1] != 10xxxxxx
+    return 0;
+  }
+  // Accumulate the trailer byte values in value16, and combine it with the
+  // relevant bits from s[0], once we've determined the sequence length.
+  unsigned long value16 = (s[1] & 0x3F);
+  if ((s[0] & 0xE0) == 0xC0) {                    // Check s[0] == 110xxxxx
+    *value = ((s[0] & 0x1F) << 6) | value16;
+    return 2;
+  }
+  if ((srclen < 3) || ((s[2] & 0xC0) != 0x80)) {  // Check s[2] != 10xxxxxx
+    return 0;
+  }
+  value16 = (value16 << 6) | (s[2] & 0x3F);
+  if ((s[0] & 0xF0) == 0xE0) {                    // Check s[0] == 1110xxxx
+    *value = ((s[0] & 0x0F) << 12) | value16;
+    return 3;
+  }
+  if ((srclen < 4) || ((s[3] & 0xC0) != 0x80)) {  // Check s[3] != 10xxxxxx
+    return 0;
+  }
+  value16 = (value16 << 6) | (s[3] & 0x3F);
+  if ((s[0] & 0xF8) == 0xF0) {                    // Check s[0] == 11110xxx
+    *value = ((s[0] & 0x07) << 18) | value16;
+    return 4;
+  }
+  return 0;
+}
+
+size_t utf8_encode(char* buffer, size_t buflen, unsigned long value) {
+  if ((value <= 0x7F) && (buflen >= 1)) {
+    buffer[0] = static_cast<unsigned char>(value);
+    return 1;
+  }
+  if ((value <= 0x7FF) && (buflen >= 2)) {
+    buffer[0] = 0xC0 | static_cast<unsigned char>(value >> 6);
+    buffer[1] = 0x80 | static_cast<unsigned char>(value & 0x3F);
+    return 2;
+  }
+  if ((value <= 0xFFFF) && (buflen >= 3)) {
+    buffer[0] = 0xE0 | static_cast<unsigned char>(value >> 12);
+    buffer[1] = 0x80 | static_cast<unsigned char>((value >> 6) & 0x3F);
+    buffer[2] = 0x80 | static_cast<unsigned char>(value & 0x3F);
+    return 3;
+  }
+  if ((value <= 0x1FFFFF) && (buflen >= 4)) {
+    buffer[0] = 0xF0 | static_cast<unsigned char>(value >> 18);
+    buffer[1] = 0x80 | static_cast<unsigned char>((value >> 12) & 0x3F);
+    buffer[2] = 0x80 | static_cast<unsigned char>((value >> 6) & 0x3F);
+    buffer[3] = 0x80 | static_cast<unsigned char>(value & 0x3F);
+    return 4;
+  }
+  return 0;
+}
+
+size_t html_encode(char * buffer, size_t buflen,
+                   const char * source, size_t srclen) {
+  ASSERT(NULL != buffer);  // TODO: estimate output size
+  if (buflen <= 0)
+    return 0;
+
+  size_t srcpos = 0, bufpos = 0;
+  while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+    unsigned char ch = source[srcpos];
+    if (ch < 128) {
+      srcpos += 1;
+      if (ASCII_CLASS[ch] & HTML_UNSAFE) {
+        const char * escseq = 0;
+        size_t esclen = 0;
+        switch (ch) {
+          case '<':  escseq = "&lt;";   esclen = 4; break;
+          case '>':  escseq = "&gt;";   esclen = 4; break;
+          case '\'': escseq = "&#39;";  esclen = 5; break;
+          case '\"': escseq = "&quot;"; esclen = 6; break;
+          case '&':  escseq = "&amp;";  esclen = 5; break;
+          default: ASSERT(false);
+        }
+        if (bufpos + esclen >= buflen) {
+          break;
+        }
+        memcpy(buffer + bufpos, escseq, esclen);
+        bufpos += esclen;
+      } else {
+        buffer[bufpos++] = ch;
+      }
+    } else {
+      // Largest value is 0x1FFFFF => &#2097151;  (10 characters)
+      char escseq[11];
+      unsigned long val;
+      if (size_t vallen = utf8_decode(&source[srcpos], srclen - srcpos, &val)) {
+        srcpos += vallen;
+      } else {
+        // Not a valid utf8 sequence, just use the raw character.
+        val = static_cast<unsigned char>(source[srcpos++]);
+      }
+      size_t esclen = sprintfn(escseq, ARRAY_SIZE(escseq), "&#%lu;", val);
+      if (bufpos + esclen >= buflen) {
+        break;
+      }
+      memcpy(buffer + bufpos, escseq, esclen);
+      bufpos += esclen;
+    }
+  }
+  buffer[bufpos] = '\0';
+  return bufpos;
+}
+
+size_t html_decode(char * buffer, size_t buflen,
+                   const char * source, size_t srclen) {
+  ASSERT(NULL != buffer);  // TODO: estimate output size
+  return xml_decode(buffer, buflen, source, srclen);
+}
+
+size_t xml_encode(char * buffer, size_t buflen,
+                  const char * source, size_t srclen) {
+  ASSERT(NULL != buffer);  // TODO: estimate output size
+  if (buflen <= 0)
+    return 0;
+
+  size_t srcpos = 0, bufpos = 0;
+  while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+    unsigned char ch = source[srcpos++];
+    if ((ch < 128) && (ASCII_CLASS[ch] & XML_UNSAFE)) {
+      const char * escseq = 0;
+      size_t esclen = 0;
+      switch (ch) {
+        case '<':  escseq = "&lt;";   esclen = 4; break;
+        case '>':  escseq = "&gt;";   esclen = 4; break;
+        case '\'': escseq = "&apos;"; esclen = 6; break;
+        case '\"': escseq = "&quot;"; esclen = 6; break;
+        case '&':  escseq = "&amp;";  esclen = 5; break;
+        default: ASSERT(false);
+      }
+      if (bufpos + esclen >= buflen) {
+        break;
+      }
+      memcpy(buffer + bufpos, escseq, esclen);
+      bufpos += esclen;
+    } else {
+      buffer[bufpos++] = ch;
+    }
+  }
+  buffer[bufpos] = '\0';
+  return bufpos;
+}
+
+size_t xml_decode(char * buffer, size_t buflen,
+                  const char * source, size_t srclen) {
+  ASSERT(NULL != buffer);  // TODO: estimate output size
+  if (buflen <= 0)
+    return 0;
+
+  size_t srcpos = 0, bufpos = 0;
+  while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+    unsigned char ch = source[srcpos++];
+    if (ch != '&') {
+      buffer[bufpos++] = ch;
+    } else if ((srcpos + 2 < srclen)
+               && (memcmp(source + srcpos, "lt;", 3) == 0)) {
+      buffer[bufpos++] = '<';
+      srcpos += 3;
+    } else if ((srcpos + 2 < srclen)
+               && (memcmp(source + srcpos, "gt;", 3) == 0)) {
+      buffer[bufpos++] = '>';
+      srcpos += 3;
+    } else if ((srcpos + 4 < srclen)
+               && (memcmp(source + srcpos, "apos;", 5) == 0)) {
+      buffer[bufpos++] = '\'';
+      srcpos += 5;
+    } else if ((srcpos + 4 < srclen)
+               && (memcmp(source + srcpos, "quot;", 5) == 0)) {
+      buffer[bufpos++] = '\"';
+      srcpos += 5;
+    } else if ((srcpos + 3 < srclen)
+               && (memcmp(source + srcpos, "amp;", 4) == 0)) {
+      buffer[bufpos++] = '&';
+      srcpos += 4;
+    } else if ((srcpos < srclen) && (source[srcpos] == '#')) {
+      int int_base = 10;
+      if ((srcpos + 1 < srclen) && (source[srcpos+1] == 'x')) {
+        int_base = 16;
+        srcpos += 1;
+      }
+      char * ptr;
+      // TODO: Fix hack (ptr may go past end of data)
+      unsigned long val = strtoul(source + srcpos + 1, &ptr, int_base);
+      if ((static_cast<size_t>(ptr - source) < srclen) && (*ptr == ';')) {
+        srcpos = ptr - source + 1;
+      } else {
+        // Not a valid escape sequence.
+        break;
+      }
+      if (size_t esclen = utf8_encode(buffer + bufpos, buflen - bufpos, val)) {
+        bufpos += esclen;
+      } else {
+        // Not enough room to encode the character, or illegal character
+        break;
+      }
+    } else {
+      // Unrecognized escape sequence.
+      break;
+    }
+  }
+  buffer[bufpos] = '\0';
+  return bufpos;
+}
+
+static const char HEX[] = "0123456789abcdef";
+
+char hex_encode(unsigned char val) {
+  ASSERT(val < 16);
+  return (val < 16) ? HEX[val] : '!';
+}
+
+bool hex_decode(char ch, unsigned char* val) {
+  if ((ch >= '0') && (ch <= '9')) {
+    *val = ch - '0';
+  } else if ((ch >= 'A') && (ch <= 'Z')) {
+    *val = (ch - 'A') + 10;
+  } else if ((ch >= 'a') && (ch <= 'z')) {
+    *val = (ch - 'a') + 10;
+  } else {
+    return false;
+  }
+  return true;
+}
+
+size_t hex_encode(char* buffer, size_t buflen,
+                  const char* csource, size_t srclen) {
+  return hex_encode_with_delimiter(buffer, buflen, csource, srclen, 0);
+}
+
+size_t hex_encode_with_delimiter(char* buffer, size_t buflen,
+                                 const char* csource, size_t srclen,
+                                 char delimiter) {
+  ASSERT(NULL != buffer);  // TODO: estimate output size
+  if (buflen == 0)
+    return 0;
+
+  // Init and check bounds.
+  const unsigned char* bsource =
+      reinterpret_cast<const unsigned char*>(csource);
+  size_t srcpos = 0, bufpos = 0;
+  size_t needed = delimiter ? (srclen * 3) : (srclen * 2 + 1);
+  if (buflen < needed)
+    return 0;
+
+  while (srcpos < srclen) {
+    unsigned char ch = bsource[srcpos++];
+    buffer[bufpos  ] = hex_encode((ch >> 4) & 0xF);
+    buffer[bufpos+1] = hex_encode((ch     ) & 0xF);
+    bufpos += 2;
+
+    // Don't write a delimiter after the last byte.
+    if (delimiter && (srcpos < srclen)) {
+      buffer[bufpos] = delimiter;
+      ++bufpos;
+    }
+  }
+
+  // Null terminate.
+  buffer[bufpos] = '\0';
+  return bufpos;
+}
+
+std::string hex_encode(const char* source, size_t srclen) {
+  return hex_encode_with_delimiter(source, srclen, 0);
+}
+
+std::string hex_encode_with_delimiter(const char* source, size_t srclen,
+                                      char delimiter) {
+  const size_t kBufferSize = srclen * 3;
+  char* buffer = STACK_ARRAY(char, kBufferSize);
+  size_t length = hex_encode_with_delimiter(buffer, kBufferSize,
+                                            source, srclen, delimiter);
+  ASSERT(srclen == 0 || length > 0);
+  return std::string(buffer, length);
+}
+
+size_t hex_decode(char * cbuffer, size_t buflen,
+                  const char * source, size_t srclen) {
+  return hex_decode_with_delimiter(cbuffer, buflen, source, srclen, 0);
+}
+
+size_t hex_decode_with_delimiter(char* cbuffer, size_t buflen,
+                                 const char* source, size_t srclen,
+                                 char delimiter) {
+  ASSERT(NULL != cbuffer);  // TODO: estimate output size
+  if (buflen == 0)
+    return 0;
+
+  // Init and bounds check.
+  unsigned char* bbuffer = reinterpret_cast<unsigned char*>(cbuffer);
+  size_t srcpos = 0, bufpos = 0;
+  size_t needed = (delimiter) ? (srclen + 1) / 3 : srclen / 2;
+  if (buflen < needed)
+    return 0;
+
+  while (srcpos < srclen) {
+    if ((srclen - srcpos) < 2) {
+      // This means we have an odd number of bytes.
+      return 0;
+    }
+
+    unsigned char h1, h2;
+    if (!hex_decode(source[srcpos], &h1) ||
+        !hex_decode(source[srcpos + 1], &h2))
+      return 0;
+
+    bbuffer[bufpos++] = (h1 << 4) | h2;
+    srcpos += 2;
+
+    // Remove the delimiter if needed.
+    if (delimiter && (srclen - srcpos) > 1) {
+      if (source[srcpos] != delimiter)
+        return 0;
+      ++srcpos;
+    }
+  }
+
+  return bufpos;
+}
+
+size_t hex_decode(char* buffer, size_t buflen, const std::string& source) {
+  return hex_decode_with_delimiter(buffer, buflen, source, 0);
+}
+size_t hex_decode_with_delimiter(char* buffer, size_t buflen,
+                                 const std::string& source, char delimiter) {
+  return hex_decode_with_delimiter(buffer, buflen,
+                                   source.c_str(), source.length(), delimiter);
+}
+
+size_t transform(std::string& value, size_t maxlen, const std::string& source,
+                 Transform t) {
+  char* buffer = STACK_ARRAY(char, maxlen + 1);
+  size_t length = t(buffer, maxlen + 1, source.data(), source.length());
+  value.assign(buffer, length);
+  return length;
+}
+
+std::string s_transform(const std::string& source, Transform t) {
+  // Ask transformation function to approximate the destination size (returns upper bound)
+  size_t maxlen = t(NULL, 0, source.data(), source.length());
+  char * buffer = STACK_ARRAY(char, maxlen);
+  size_t len = t(buffer, maxlen, source.data(), source.length());
+  std::string result(buffer, len);
+  return result;
+}
+
+size_t tokenize(const std::string& source, char delimiter,
+                std::vector<std::string>* fields) {
+  ASSERT(NULL != fields);
+  fields->clear();
+  size_t last = 0;
+  for (size_t i = 0; i < source.length(); ++i) {
+    if (source[i] == delimiter) {
+      if (i != last) {
+        fields->push_back(source.substr(last, i - last));
+      }
+      last = i + 1;
+    }
+  }
+  if (last != source.length()) {
+    fields->push_back(source.substr(last, source.length() - last));
+  }
+  return fields->size();
+}
+
+size_t tokenize_append(const std::string& source, char delimiter,
+                       std::vector<std::string>* fields) {
+  if (!fields) return 0;
+
+  std::vector<std::string> new_fields;
+  tokenize(source, delimiter, &new_fields);
+  fields->insert(fields->end(), new_fields.begin(), new_fields.end());
+  return fields->size();
+}
+
+size_t tokenize(const std::string& source, char delimiter, char start_mark,
+                char end_mark, std::vector<std::string>* fields) {
+  if (!fields) return 0;
+  fields->clear();
+
+  std::string remain_source = source;
+  while (!remain_source.empty()) {
+    size_t start_pos = remain_source.find(start_mark);
+    if (std::string::npos == start_pos) break;
+    std::string pre_mark;
+    if (start_pos > 0) {
+      pre_mark = remain_source.substr(0, start_pos - 1);
+    }
+
+    ++start_pos;
+    size_t end_pos = remain_source.find(end_mark, start_pos);
+    if (std::string::npos == end_pos) break;
+
+    // We have found the matching marks. First tokenize the pre-mask. Then add
+    // the marked part as a single field. Finally, loop back for the post-mark.
+    tokenize_append(pre_mark, delimiter, fields);
+    fields->push_back(remain_source.substr(start_pos, end_pos - start_pos));
+    remain_source = remain_source.substr(end_pos + 1);
+  }
+
+  return tokenize_append(remain_source, delimiter, fields);
+}
+
+size_t split(const std::string& source, char delimiter,
+             std::vector<std::string>* fields) {
+  ASSERT(NULL != fields);
+  fields->clear();
+  size_t last = 0;
+  for (size_t i = 0; i < source.length(); ++i) {
+    if (source[i] == delimiter) {
+      fields->push_back(source.substr(last, i - last));
+      last = i + 1;
+    }
+  }
+  fields->push_back(source.substr(last, source.length() - last));
+  return fields->size();
+}
+
+char make_char_safe_for_filename(char c) {
+  if (c < 32)
+    return '_';
+
+  switch (c) {
+    case '<':
+    case '>':
+    case ':':
+    case '"':
+    case '/':
+    case '\\':
+    case '|':
+    case '*':
+    case '?':
+      return '_';
+
+    default:
+      return c;
+  }
+}
+
+/*
+void sprintf(std::string& value, size_t maxlen, const char * format, ...) {
+  char * buffer = STACK_ARRAY(char, maxlen + 1);
+  va_list args;
+  va_start(args, format);
+  value.assign(buffer, vsprintfn(buffer, maxlen + 1, format, args));
+  va_end(args);
+}
+*/
+
+/////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
diff --git a/talk/base/stringencode.h b/talk/base/stringencode.h
new file mode 100644
index 0000000..872dfd4
--- /dev/null
+++ b/talk/base/stringencode.h
@@ -0,0 +1,227 @@
+/*
+ * 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_BASE_STRINGENCODE_H_
+#define TALK_BASE_STRINGENCODE_H_
+
+#include <string>
+#include <sstream>
+#include <vector>
+
+#include "talk/base/common.h"
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// String Encoding Utilities
+//////////////////////////////////////////////////////////////////////
+
+// Convert an unsigned value to it's utf8 representation.  Returns the length
+// of the encoded string, or 0 if the encoding is longer than buflen - 1.
+size_t utf8_encode(char* buffer, size_t buflen, unsigned long value);
+// Decode the utf8 encoded value pointed to by source.  Returns the number of
+// bytes used by the encoding, or 0 if the encoding is invalid.
+size_t utf8_decode(const char* source, size_t srclen, unsigned long* value);
+
+// Escaping prefixes illegal characters with the escape character.  Compact, but
+// illegal characters still appear in the string.
+size_t escape(char * buffer, size_t buflen,
+              const char * source, size_t srclen,
+              const char * illegal, char escape);
+// Note: in-place unescaping (buffer == source) is allowed.
+size_t unescape(char * buffer, size_t buflen,
+                const char * source, size_t srclen,
+                char escape);
+
+// Encoding replaces illegal characters with the escape character and 2 hex
+// chars, so it's a little less compact than escape, but completely removes
+// illegal characters.  note that hex digits should not be used as illegal
+// characters.
+size_t encode(char * buffer, size_t buflen,
+              const char * source, size_t srclen,
+              const char * illegal, char escape);
+// Note: in-place decoding (buffer == source) is allowed.
+size_t decode(char * buffer, size_t buflen,
+              const char * source, size_t srclen,
+              char escape);
+
+// Returns a list of characters that may be unsafe for use in the name of a
+// file, suitable for passing to the 'illegal' member of escape or encode.
+const char* unsafe_filename_characters();
+
+// url_encode is an encode operation with a predefined set of illegal characters
+// and escape character (for use in URLs, obviously).
+size_t url_encode(char * buffer, size_t buflen,
+                  const char * source, size_t srclen);
+// Note: in-place decoding (buffer == source) is allowed.
+size_t url_decode(char * buffer, size_t buflen,
+                  const char * source, size_t srclen);
+
+// html_encode prevents data embedded in html from containing markup.
+size_t html_encode(char * buffer, size_t buflen,
+                   const char * source, size_t srclen);
+// Note: in-place decoding (buffer == source) is allowed.
+size_t html_decode(char * buffer, size_t buflen,
+                   const char * source, size_t srclen);
+
+// xml_encode makes data suitable for inside xml attributes and values.
+size_t xml_encode(char * buffer, size_t buflen,
+                  const char * source, size_t srclen);
+// Note: in-place decoding (buffer == source) is allowed.
+size_t xml_decode(char * buffer, size_t buflen,
+                  const char * source, size_t srclen);
+
+// Convert an unsigned value from 0 to 15 to the hex character equivalent...
+char hex_encode(unsigned char val);
+// ...and vice-versa.
+bool hex_decode(char ch, unsigned char* val);
+
+// hex_encode shows the hex representation of binary data in ascii.
+size_t hex_encode(char* buffer, size_t buflen,
+                  const char* source, size_t srclen);
+
+// hex_encode, but separate each byte representation with a delimiter.
+// |delimiter| == 0 means no delimiter
+// If the buffer is too short, we return 0
+size_t hex_encode_with_delimiter(char* buffer, size_t buflen,
+                                 const char* source, size_t srclen,
+                                 char delimiter);
+
+// Helper functions for hex_encode.
+std::string hex_encode(const char* source, size_t srclen);
+std::string hex_encode_with_delimiter(const char* source, size_t srclen,
+                                      char delimiter);
+
+// hex_decode converts ascii hex to binary.
+size_t hex_decode(char* buffer, size_t buflen,
+                  const char* source, size_t srclen);
+
+// hex_decode, assuming that there is a delimiter between every byte
+// pair.
+// |delimiter| == 0 means no delimiter
+// If the buffer is too short or the data is invalid, we return 0.
+size_t hex_decode_with_delimiter(char* buffer, size_t buflen,
+                                 const char* source, size_t srclen,
+                                 char delimiter);
+
+// Helper functions for hex_decode.
+size_t hex_decode(char* buffer, size_t buflen, const std::string& source);
+size_t hex_decode_with_delimiter(char* buffer, size_t buflen,
+                                 const std::string& source, char delimiter);
+
+// Apply any suitable string transform (including the ones above) to an STL
+// string.  Stack-allocated temporary space is used for the transformation,
+// so value and source may refer to the same string.
+typedef size_t (*Transform)(char * buffer, size_t buflen,
+                            const char * source, size_t srclen);
+size_t transform(std::string& value, size_t maxlen, const std::string& source,
+                 Transform t);
+
+// Return the result of applying transform t to source.
+std::string s_transform(const std::string& source, Transform t);
+
+// Convenience wrappers.
+inline std::string s_url_encode(const std::string& source) {
+  return s_transform(source, url_encode);
+}
+inline std::string s_url_decode(const std::string& source) {
+  return s_transform(source, url_decode);
+}
+
+// Splits the source string into multiple fields separated by delimiter,
+// with duplicates of delimiter creating empty fields.
+size_t split(const std::string& source, char delimiter,
+             std::vector<std::string>* fields);
+
+// Splits the source string into multiple fields separated by delimiter,
+// with duplicates of delimiter ignored.  Trailing delimiter ignored.
+size_t tokenize(const std::string& source, char delimiter,
+                std::vector<std::string>* fields);
+
+// Tokenize and append the tokens to fields. Return the new size of fields.
+size_t tokenize_append(const std::string& source, char delimiter,
+                       std::vector<std::string>* fields);
+
+// Splits the source string into multiple fields separated by delimiter, with
+// duplicates of delimiter ignored. Trailing delimiter ignored. A substring in
+// between the start_mark and the end_mark is treated as a single field. Return
+// the size of fields. For example, if source is "filename
+// \"/Library/Application Support/media content.txt\"", delimiter is ' ', and
+// the start_mark and end_mark are '"', this method returns two fields:
+// "filename" and "/Library/Application Support/media content.txt".
+size_t tokenize(const std::string& source, char delimiter, char start_mark,
+                char end_mark, std::vector<std::string>* fields);
+
+// Safe sprintf to std::string
+//void sprintf(std::string& value, size_t maxlen, const char * format, ...)
+//     PRINTF_FORMAT(3);
+
+// Convert arbitrary values to/from a string.
+
+template <class T>
+static bool ToString(const T &t, std::string* s) {
+  ASSERT(NULL != s);
+  std::ostringstream oss;
+  oss << std::boolalpha << t;
+  *s = oss.str();
+  return !oss.fail();
+}
+
+template <class T>
+static bool FromString(const std::string& s, T* t) {
+  ASSERT(NULL != t);
+  std::istringstream iss(s);
+  iss >> std::boolalpha >> *t;
+  return !iss.fail();
+}
+
+// Inline versions of the string conversion routines.
+
+template<typename T>
+static inline std::string ToString(const T& val) {
+  std::string str; ToString(val, &str); return str;
+}
+
+template<typename T>
+static inline T FromString(const std::string& str) {
+  T val; FromString(str, &val); return val;
+}
+
+template<typename T>
+static inline T FromString(const T& defaultValue, const std::string& str) {
+  T val(defaultValue); FromString(str, &val); return val;
+}
+
+// simple function to strip out characters which shouldn't be
+// used in filenames
+char make_char_safe_for_filename(char c);
+
+//////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_STRINGENCODE_H__
diff --git a/talk/base/stringencode_unittest.cc b/talk/base/stringencode_unittest.cc
new file mode 100644
index 0000000..c1ec53f
--- /dev/null
+++ b/talk/base/stringencode_unittest.cc
@@ -0,0 +1,402 @@
+/*
+ * 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/common.h"
+#include "talk/base/gunit.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+
+namespace talk_base {
+
+TEST(Utf8EncodeTest, EncodeDecode) {
+  const struct Utf8Test {
+    const char* encoded;
+    size_t encsize, enclen;
+    unsigned long decoded;
+  } kTests[] = {
+    { "a    ",             5, 1, 'a' },
+    { "\x7F    ",          5, 1, 0x7F },
+    { "\xC2\x80   ",       5, 2, 0x80 },
+    { "\xDF\xBF   ",       5, 2, 0x7FF },
+    { "\xE0\xA0\x80  ",    5, 3, 0x800 },
+    { "\xEF\xBF\xBF  ",    5, 3, 0xFFFF },
+    { "\xF0\x90\x80\x80 ", 5, 4, 0x10000 },
+    { "\xF0\x90\x80\x80 ", 3, 0, 0x10000 },
+    { "\xF0\xF0\x80\x80 ", 5, 0, 0 },
+    { "\xF0\x90\x80  ",    5, 0, 0 },
+    { "\x90\x80\x80  ",    5, 0, 0 },
+    { NULL, 0, 0 },
+  };
+  for (size_t i = 0; kTests[i].encoded; ++i) {
+    unsigned long val = 0;
+    ASSERT_EQ(kTests[i].enclen, utf8_decode(kTests[i].encoded,
+                                            kTests[i].encsize,
+                                            &val));
+    unsigned long result = (kTests[i].enclen == 0) ? 0 : kTests[i].decoded;
+    ASSERT_EQ(result, val);
+
+    if (kTests[i].decoded == 0) {
+      // Not an interesting encoding test case
+      continue;
+    }
+
+    char buffer[5];
+    memset(buffer, 0x01, ARRAY_SIZE(buffer));
+    ASSERT_EQ(kTests[i].enclen, utf8_encode(buffer,
+                                            kTests[i].encsize,
+                                            kTests[i].decoded));
+    ASSERT_TRUE(memcmp(buffer, kTests[i].encoded, kTests[i].enclen) == 0);
+    // Make sure remainder of buffer is unchanged
+    ASSERT_TRUE(memory_check(buffer + kTests[i].enclen,
+                             0x1,
+                             ARRAY_SIZE(buffer) - kTests[i].enclen));
+  }
+}
+
+class HexEncodeTest : public testing::Test {
+ public:
+  HexEncodeTest() : enc_res_(0), dec_res_(0) {
+    for (size_t i = 0; i < sizeof(data_); ++i) {
+      data_[i] = (i + 128) & 0xff;
+    }
+    memset(decoded_, 0x7f, sizeof(decoded_));
+  }
+
+  char data_[10];
+  char encoded_[31];
+  char decoded_[11];
+  size_t enc_res_;
+  size_t dec_res_;
+};
+
+// Test that we can convert to/from hex with no delimiter.
+TEST_F(HexEncodeTest, TestWithNoDelimiter) {
+  enc_res_ = hex_encode(encoded_, sizeof(encoded_), data_, sizeof(data_));
+  ASSERT_EQ(sizeof(data_) * 2, enc_res_);
+  ASSERT_STREQ("80818283848586878889", encoded_);
+  dec_res_ = hex_decode(decoded_, sizeof(decoded_), encoded_, enc_res_);
+  ASSERT_EQ(sizeof(data_), dec_res_);
+  ASSERT_EQ(0, memcmp(data_, decoded_, dec_res_));
+}
+
+// Test that we can convert to/from hex with a colon delimiter.
+TEST_F(HexEncodeTest, TestWithDelimiter) {
+  enc_res_ = hex_encode_with_delimiter(encoded_, sizeof(encoded_),
+                                       data_, sizeof(data_), ':');
+  ASSERT_EQ(sizeof(data_) * 3 - 1, enc_res_);
+  ASSERT_STREQ("80:81:82:83:84:85:86:87:88:89", encoded_);
+  dec_res_ = hex_decode_with_delimiter(decoded_, sizeof(decoded_),
+                                       encoded_, enc_res_, ':');
+  ASSERT_EQ(sizeof(data_), dec_res_);
+  ASSERT_EQ(0, memcmp(data_, decoded_, dec_res_));
+}
+
+// Test that encoding with one delimiter and decoding with another fails.
+TEST_F(HexEncodeTest, TestWithWrongDelimiter) {
+  enc_res_ = hex_encode_with_delimiter(encoded_, sizeof(encoded_),
+                                       data_, sizeof(data_), ':');
+  ASSERT_EQ(sizeof(data_) * 3 - 1, enc_res_);
+  dec_res_ = hex_decode_with_delimiter(decoded_, sizeof(decoded_),
+                                       encoded_, enc_res_, '/');
+  ASSERT_EQ(0U, dec_res_);
+}
+
+// Test that encoding without a delimiter and decoding with one fails.
+TEST_F(HexEncodeTest, TestExpectedDelimiter) {
+  enc_res_ = hex_encode(encoded_, sizeof(encoded_), data_, sizeof(data_));
+  ASSERT_EQ(sizeof(data_) * 2, enc_res_);
+  dec_res_ = hex_decode_with_delimiter(decoded_, sizeof(decoded_),
+                                       encoded_, enc_res_, ':');
+  ASSERT_EQ(0U, dec_res_);
+}
+
+// Test that encoding with a delimiter and decoding without one fails.
+TEST_F(HexEncodeTest, TestExpectedNoDelimiter) {
+  enc_res_ = hex_encode_with_delimiter(encoded_, sizeof(encoded_),
+                                       data_, sizeof(data_), ':');
+  ASSERT_EQ(sizeof(data_) * 3 - 1, enc_res_);
+  dec_res_ = hex_decode(decoded_, sizeof(decoded_), encoded_, enc_res_);
+  ASSERT_EQ(0U, dec_res_);
+}
+
+// Test that we handle a zero-length buffer with no delimiter.
+TEST_F(HexEncodeTest, TestZeroLengthNoDelimiter) {
+  enc_res_ = hex_encode(encoded_, sizeof(encoded_), "", 0);
+  ASSERT_EQ(0U, enc_res_);
+  dec_res_ = hex_decode(decoded_, sizeof(decoded_), encoded_, enc_res_);
+  ASSERT_EQ(0U, dec_res_);
+}
+
+// Test that we handle a zero-length buffer with a delimiter.
+TEST_F(HexEncodeTest, TestZeroLengthWithDelimiter) {
+  enc_res_ = hex_encode_with_delimiter(encoded_, sizeof(encoded_), "", 0, ':');
+  ASSERT_EQ(0U, enc_res_);
+  dec_res_ = hex_decode_with_delimiter(decoded_, sizeof(decoded_),
+                                       encoded_, enc_res_, ':');
+  ASSERT_EQ(0U, dec_res_);
+}
+
+// Test the std::string variants that take no delimiter.
+TEST_F(HexEncodeTest, TestHelpersNoDelimiter) {
+  std::string result = hex_encode(data_, sizeof(data_));
+  ASSERT_EQ("80818283848586878889", result);
+  dec_res_ = hex_decode(decoded_, sizeof(decoded_), result);
+  ASSERT_EQ(sizeof(data_), dec_res_);
+  ASSERT_EQ(0, memcmp(data_, decoded_, dec_res_));
+}
+
+// Test the std::string variants that use a delimiter.
+TEST_F(HexEncodeTest, TestHelpersWithDelimiter) {
+  std::string result = hex_encode_with_delimiter(data_, sizeof(data_), ':');
+  ASSERT_EQ("80:81:82:83:84:85:86:87:88:89", result);
+  dec_res_ = hex_decode_with_delimiter(decoded_, sizeof(decoded_), result, ':');
+  ASSERT_EQ(sizeof(data_), dec_res_);
+  ASSERT_EQ(0, memcmp(data_, decoded_, dec_res_));
+}
+
+// Test that encoding into a too-small output buffer (without delimiter) fails.
+TEST_F(HexEncodeTest, TestEncodeTooShort) {
+  enc_res_ = hex_encode_with_delimiter(encoded_, sizeof(data_) * 2,
+                                       data_, sizeof(data_), 0);
+  ASSERT_EQ(0U, enc_res_);
+}
+
+// Test that encoding into a too-small output buffer (with delimiter) fails.
+TEST_F(HexEncodeTest, TestEncodeWithDelimiterTooShort) {
+  enc_res_ = hex_encode_with_delimiter(encoded_, sizeof(data_) * 3 - 1,
+                                       data_, sizeof(data_), ':');
+  ASSERT_EQ(0U, enc_res_);
+}
+
+// Test that decoding into a too-small output buffer fails.
+TEST_F(HexEncodeTest, TestDecodeTooShort) {
+  dec_res_ = hex_decode_with_delimiter(decoded_, 4, "0123456789", 10, 0);
+  ASSERT_EQ(0U, dec_res_);
+  ASSERT_EQ(0x7f, decoded_[4]);
+}
+
+// Test that decoding non-hex data fails.
+TEST_F(HexEncodeTest, TestDecodeBogusData) {
+  dec_res_ = hex_decode_with_delimiter(decoded_, sizeof(decoded_), "xyz", 3, 0);
+  ASSERT_EQ(0U, dec_res_);
+}
+
+// Test that decoding an odd number of hex characters fails.
+TEST_F(HexEncodeTest, TestDecodeOddHexDigits) {
+  dec_res_ = hex_decode_with_delimiter(decoded_, sizeof(decoded_), "012", 3, 0);
+  ASSERT_EQ(0U, dec_res_);
+}
+
+// Test that decoding a string with too many delimiters fails.
+TEST_F(HexEncodeTest, TestDecodeWithDelimiterTooManyDelimiters) {
+  dec_res_ = hex_decode_with_delimiter(decoded_, 4, "01::23::45::67", 14, ':');
+  ASSERT_EQ(0U, dec_res_);
+}
+
+// Test that decoding a string with a leading delimiter fails.
+TEST_F(HexEncodeTest, TestDecodeWithDelimiterLeadingDelimiter) {
+  dec_res_ = hex_decode_with_delimiter(decoded_, 4, ":01:23:45:67", 12, ':');
+  ASSERT_EQ(0U, dec_res_);
+}
+
+// Test that decoding a string with a trailing delimiter fails.
+TEST_F(HexEncodeTest, TestDecodeWithDelimiterTrailingDelimiter) {
+  dec_res_ = hex_decode_with_delimiter(decoded_, 4, "01:23:45:67:", 12, ':');
+  ASSERT_EQ(0U, dec_res_);
+}
+
+// Tests counting substrings.
+TEST(TokenizeTest, CountSubstrings) {
+  std::vector<std::string> fields;
+
+  EXPECT_EQ(5ul, tokenize("one two three four five", ' ', &fields));
+  fields.clear();
+  EXPECT_EQ(1ul, tokenize("one", ' ', &fields));
+
+  // Extra spaces should be ignored.
+  fields.clear();
+  EXPECT_EQ(5ul, tokenize("  one    two  three    four five  ", ' ', &fields));
+  fields.clear();
+  EXPECT_EQ(1ul, tokenize("  one  ", ' ', &fields));
+  fields.clear();
+  EXPECT_EQ(0ul, tokenize(" ", ' ', &fields));
+}
+
+// Tests comparing substrings.
+TEST(TokenizeTest, CompareSubstrings) {
+  std::vector<std::string> fields;
+
+  tokenize("find middle one", ' ', &fields);
+  ASSERT_EQ(3ul, fields.size());
+  ASSERT_STREQ("middle", fields.at(1).c_str());
+  fields.clear();
+
+  // Extra spaces should be ignored.
+  tokenize("  find   middle  one    ", ' ', &fields);
+  ASSERT_EQ(3ul, fields.size());
+  ASSERT_STREQ("middle", fields.at(1).c_str());
+  fields.clear();
+  tokenize(" ", ' ', &fields);
+  ASSERT_EQ(0ul, fields.size());
+}
+
+TEST(TokenizeTest, TokenizeAppend) {
+  ASSERT_EQ(0ul, tokenize_append("A B C", ' ', NULL));
+
+  std::vector<std::string> fields;
+
+  tokenize_append("A B C", ' ', &fields);
+  ASSERT_EQ(3ul, fields.size());
+  ASSERT_STREQ("B", fields.at(1).c_str());
+
+  tokenize_append("D E", ' ', &fields);
+  ASSERT_EQ(5ul, fields.size());
+  ASSERT_STREQ("B", fields.at(1).c_str());
+  ASSERT_STREQ("E", fields.at(4).c_str());
+}
+
+TEST(TokenizeTest, TokenizeWithMarks) {
+  ASSERT_EQ(0ul, tokenize("D \"A B", ' ', '(', ')', NULL));
+
+  std::vector<std::string> fields;
+  tokenize("A B C", ' ', '"', '"', &fields);
+  ASSERT_EQ(3ul, fields.size());
+  ASSERT_STREQ("C", fields.at(2).c_str());
+
+  tokenize("\"A B\" C", ' ', '"', '"', &fields);
+  ASSERT_EQ(2ul, fields.size());
+  ASSERT_STREQ("A B", fields.at(0).c_str());
+
+  tokenize("D \"A B\" C", ' ', '"', '"', &fields);
+  ASSERT_EQ(3ul, fields.size());
+  ASSERT_STREQ("D", fields.at(0).c_str());
+  ASSERT_STREQ("A B", fields.at(1).c_str());
+
+  tokenize("D \"A B\" C \"E F\"", ' ', '"', '"', &fields);
+  ASSERT_EQ(4ul, fields.size());
+  ASSERT_STREQ("D", fields.at(0).c_str());
+  ASSERT_STREQ("A B", fields.at(1).c_str());
+  ASSERT_STREQ("E F", fields.at(3).c_str());
+
+  // No matching marks.
+  tokenize("D \"A B", ' ', '"', '"', &fields);
+  ASSERT_EQ(3ul, fields.size());
+  ASSERT_STREQ("D", fields.at(0).c_str());
+  ASSERT_STREQ("\"A", fields.at(1).c_str());
+
+  tokenize("D (A B) C (E F) G", ' ', '(', ')', &fields);
+  ASSERT_EQ(5ul, fields.size());
+  ASSERT_STREQ("D", fields.at(0).c_str());
+  ASSERT_STREQ("A B", fields.at(1).c_str());
+  ASSERT_STREQ("E F", fields.at(3).c_str());
+}
+
+// Tests counting substrings.
+TEST(SplitTest, CountSubstrings) {
+  std::vector<std::string> fields;
+
+  EXPECT_EQ(5ul, split("one,two,three,four,five", ',', &fields));
+  fields.clear();
+  EXPECT_EQ(1ul, split("one", ',', &fields));
+
+  // Empty fields between commas count.
+  fields.clear();
+  EXPECT_EQ(5ul, split("one,,three,four,five", ',', &fields));
+  fields.clear();
+  EXPECT_EQ(3ul, split(",three,", ',', &fields));
+  fields.clear();
+  EXPECT_EQ(1ul, split("", ',', &fields));
+}
+
+// Tests comparing substrings.
+TEST(SplitTest, CompareSubstrings) {
+  std::vector<std::string> fields;
+
+  split("find,middle,one", ',', &fields);
+  ASSERT_EQ(3ul, fields.size());
+  ASSERT_STREQ("middle", fields.at(1).c_str());
+  fields.clear();
+
+  // Empty fields between commas count.
+  split("find,,middle,one", ',', &fields);
+  ASSERT_EQ(4ul, fields.size());
+  ASSERT_STREQ("middle", fields.at(2).c_str());
+  fields.clear();
+  split("", ',', &fields);
+  ASSERT_EQ(1ul, fields.size());
+  ASSERT_STREQ("", fields.at(0).c_str());
+}
+
+TEST(BoolTest, DecodeValid) {
+  bool value;
+  EXPECT_TRUE(FromString("true", &value));
+  EXPECT_TRUE(value);
+  EXPECT_TRUE(FromString("true,", &value));
+  EXPECT_TRUE(value);
+  EXPECT_TRUE(FromString("true , true", &value));
+  EXPECT_TRUE(value);
+  EXPECT_TRUE(FromString("true ,\n false", &value));
+  EXPECT_TRUE(value);
+  EXPECT_TRUE(FromString("  true  \n", &value));
+  EXPECT_TRUE(value);
+
+  EXPECT_TRUE(FromString("false", &value));
+  EXPECT_FALSE(value);
+  EXPECT_TRUE(FromString("  false ", &value));
+  EXPECT_FALSE(value);
+  EXPECT_TRUE(FromString("  false, ", &value));
+  EXPECT_FALSE(value);
+
+  EXPECT_TRUE(FromString<bool>("true\n"));
+  EXPECT_FALSE(FromString<bool>("false\n"));
+}
+
+TEST(BoolTest, DecodeInvalid) {
+  bool value;
+  EXPECT_FALSE(FromString("True", &value));
+  EXPECT_FALSE(FromString("TRUE", &value));
+  EXPECT_FALSE(FromString("False", &value));
+  EXPECT_FALSE(FromString("FALSE", &value));
+  EXPECT_FALSE(FromString("0", &value));
+  EXPECT_FALSE(FromString("1", &value));
+  EXPECT_FALSE(FromString("0,", &value));
+  EXPECT_FALSE(FromString("1,", &value));
+  EXPECT_FALSE(FromString("1,0", &value));
+  EXPECT_FALSE(FromString("1.", &value));
+  EXPECT_FALSE(FromString("1.0", &value));
+  EXPECT_FALSE(FromString("", &value));
+  EXPECT_FALSE(FromString<bool>("false\nfalse"));
+}
+
+TEST(BoolTest, RoundTrip) {
+  bool value;
+  EXPECT_TRUE(FromString(ToString(true), &value));
+  EXPECT_TRUE(value);
+  EXPECT_TRUE(FromString(ToString(false), &value));
+  EXPECT_FALSE(value);
+}
+}  // namespace talk_base
diff --git a/talk/base/stringutils.cc b/talk/base/stringutils.cc
new file mode 100644
index 0000000..c4c2b2f
--- /dev/null
+++ b/talk/base/stringutils.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/stringutils.h"
+#include "talk/base/common.h"
+
+namespace talk_base {
+
+bool memory_check(const void* memory, int c, size_t count) {
+  const char* char_memory = static_cast<const char*>(memory);
+  char char_c = static_cast<char>(c);
+  for (size_t i = 0; i < count; ++i) {
+    if (char_memory[i] != char_c) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool string_match(const char* target, const char* pattern) {
+  while (*pattern) {
+    if (*pattern == '*') {
+      if (!*++pattern) {
+        return true;
+      }
+      while (*target) {
+        if ((toupper(*pattern) == toupper(*target))
+            && string_match(target + 1, pattern + 1)) {
+          return true;
+        }
+        ++target;
+      }
+      return false;
+    } else {
+      if (toupper(*pattern) != toupper(*target)) {
+        return false;
+      }
+      ++target;
+      ++pattern;
+    }
+  }
+  return !*target;
+}
+
+#ifdef WIN32
+int ascii_string_compare(const wchar_t* s1, const char* s2, size_t n,
+                         CharacterTransformation transformation) {
+  wchar_t c1, c2;
+  while (true) {
+    if (n-- == 0) return 0;
+    c1 = transformation(*s1);
+    // Double check that characters are not UTF-8
+    ASSERT(static_cast<unsigned char>(*s2) < 128);
+    // Note: *s2 gets implicitly promoted to wchar_t
+    c2 = transformation(*s2);
+    if (c1 != c2) return (c1 < c2) ? -1 : 1;
+    if (!c1) return 0;
+    ++s1;
+    ++s2;
+  }
+}
+
+size_t asccpyn(wchar_t* buffer, size_t buflen,
+               const char* source, size_t srclen) {
+  if (buflen <= 0)
+    return 0;
+
+  if (srclen == SIZE_UNKNOWN) {
+    srclen = strlenn(source, buflen - 1);
+  } else if (srclen >= buflen) {
+    srclen = buflen - 1;
+  }
+#if _DEBUG
+  // Double check that characters are not UTF-8
+  for (size_t pos = 0; pos < srclen; ++pos)
+    ASSERT(static_cast<unsigned char>(source[pos]) < 128);
+#endif  // _DEBUG
+  std::copy(source, source + srclen, buffer);
+  buffer[srclen] = 0;
+  return srclen;
+}
+
+#endif  // WIN32
+
+void replace_substrs(const char *search,
+                     size_t search_len,
+                     const char *replace,
+                     size_t replace_len,
+                     std::string *s) {
+  size_t pos = 0;
+  while ((pos = s->find(search, pos, search_len)) != std::string::npos) {
+    s->replace(pos, search_len, replace, replace_len);
+    pos += replace_len;
+  }
+}
+
+bool starts_with(const char *s1, const char *s2) {
+  return strncmp(s1, s2, strlen(s2)) == 0;
+}
+
+bool ends_with(const char *s1, const char *s2) {
+  size_t s1_length = strlen(s1);
+  size_t s2_length = strlen(s2);
+
+  if (s2_length > s1_length) {
+    return false;
+  }
+
+  const char* start = s1 + (s1_length - s2_length);
+  return strncmp(start, s2, s2_length) == 0;
+}
+
+static const char kWhitespace[] = " \n\r\t";
+
+std::string string_trim(const std::string& s) {
+  std::string::size_type first = s.find_first_not_of(kWhitespace);
+  std::string::size_type last  = s.find_last_not_of(kWhitespace);
+
+  if (first == std::string::npos || last == std::string::npos) {
+    return std::string("");
+  }
+
+  return s.substr(first, last - first + 1);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/stringutils.h b/talk/base/stringutils.h
new file mode 100644
index 0000000..9f9e1a6
--- /dev/null
+++ b/talk/base/stringutils.h
@@ -0,0 +1,335 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_STRINGUTILS_H__
+#define TALK_BASE_STRINGUTILS_H__
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#ifdef WIN32
+#include <malloc.h>
+#include <wchar.h>
+#define alloca _alloca
+#endif  // WIN32
+
+#ifdef POSIX
+#ifdef BSD
+#include <stdlib.h>
+#else  // BSD
+#include <alloca.h>
+#endif  // !BSD
+#endif  // POSIX
+
+#include <cstring>
+#include <string>
+
+#include "talk/base/basictypes.h"
+
+///////////////////////////////////////////////////////////////////////////////
+// Generic string/memory utilities
+///////////////////////////////////////////////////////////////////////////////
+
+#define STACK_ARRAY(TYPE, LEN) static_cast<TYPE*>(::alloca((LEN)*sizeof(TYPE)))
+
+namespace talk_base {
+
+// Complement to memset.  Verifies memory consists of count bytes of value c.
+bool memory_check(const void* memory, int c, size_t count);
+
+// Determines whether the simple wildcard pattern matches target.
+// Alpha characters in pattern match case-insensitively.
+// Asterisks in pattern match 0 or more characters.
+// Ex: string_match("www.TEST.GOOGLE.COM", "www.*.com") -> true
+bool string_match(const char* target, const char* pattern);
+
+}  // namespace talk_base
+
+///////////////////////////////////////////////////////////////////////////////
+// Rename a bunch of common string functions so they are consistent across
+// platforms and between char and wchar_t variants.
+// Here is the full list of functions that are unified:
+//  strlen, strcmp, stricmp, strncmp, strnicmp
+//  strchr, vsnprintf, strtoul, tolowercase
+// tolowercase is like tolower, but not compatible with end-of-file value
+//
+// It's not clear if we will ever use wchar_t strings on unix.  In theory,
+// all strings should be Utf8 all the time, except when interfacing with Win32
+// APIs that require Utf16.
+///////////////////////////////////////////////////////////////////////////////
+
+inline char tolowercase(char c) {
+  return static_cast<char>(tolower(c));
+}
+
+#ifdef WIN32
+
+inline size_t strlen(const wchar_t* s) {
+  return wcslen(s);
+}
+inline int strcmp(const wchar_t* s1, const wchar_t* s2) {
+  return wcscmp(s1, s2);
+}
+inline int stricmp(const wchar_t* s1, const wchar_t* s2) {
+  return _wcsicmp(s1, s2);
+}
+inline int strncmp(const wchar_t* s1, const wchar_t* s2, size_t n) {
+  return wcsncmp(s1, s2, n);
+}
+inline int strnicmp(const wchar_t* s1, const wchar_t* s2, size_t n) {
+  return _wcsnicmp(s1, s2, n);
+}
+inline const wchar_t* strchr(const wchar_t* s, wchar_t c) {
+  return wcschr(s, c);
+}
+inline const wchar_t* strstr(const wchar_t* haystack, const wchar_t* needle) {
+  return wcsstr(haystack, needle);
+}
+#ifndef vsnprintf
+inline int vsnprintf(wchar_t* buf, size_t n, const wchar_t* fmt, va_list args) {
+  return _vsnwprintf(buf, n, fmt, args);
+}
+#endif // !vsnprintf
+inline unsigned long strtoul(const wchar_t* snum, wchar_t** end, int base) {
+  return wcstoul(snum, end, base);
+}
+inline wchar_t tolowercase(wchar_t c) {
+  return static_cast<wchar_t>(towlower(c));
+}
+
+#endif  // WIN32
+
+#ifdef POSIX
+
+inline int _stricmp(const char* s1, const char* s2) {
+  return strcasecmp(s1, s2);
+}
+inline int _strnicmp(const char* s1, const char* s2, size_t n) {
+  return strncasecmp(s1, s2, n);
+}
+
+#endif // POSIX
+
+///////////////////////////////////////////////////////////////////////////////
+// Traits simplifies porting string functions to be CTYPE-agnostic
+///////////////////////////////////////////////////////////////////////////////
+
+namespace talk_base {
+
+const size_t SIZE_UNKNOWN = static_cast<size_t>(-1);
+
+template<class CTYPE>
+struct Traits {
+  // STL string type
+  //typedef XXX string;
+  // Null-terminated string
+  //inline static const CTYPE* empty_str();
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// String utilities which work with char or wchar_t
+///////////////////////////////////////////////////////////////////////////////
+
+template<class CTYPE>
+inline const CTYPE* nonnull(const CTYPE* str, const CTYPE* def_str = NULL) {
+  return str ? str : (def_str ? def_str : Traits<CTYPE>::empty_str());
+}
+
+template<class CTYPE>
+const CTYPE* strchr(const CTYPE* str, const CTYPE* chs) {
+  for (size_t i=0; str[i]; ++i) {
+    for (size_t j=0; chs[j]; ++j) {
+      if (str[i] == chs[j]) {
+        return str + i;
+      }
+    }
+  }
+  return 0;
+}
+
+template<class CTYPE>
+const CTYPE* strchrn(const CTYPE* str, size_t slen, CTYPE ch) {
+  for (size_t i=0; i<slen && str[i]; ++i) {
+    if (str[i] == ch) {
+      return str + i;
+    }
+  }
+  return 0;
+}
+
+template<class CTYPE>
+size_t strlenn(const CTYPE* buffer, size_t buflen) {
+  size_t bufpos = 0;
+  while (buffer[bufpos] && (bufpos < buflen)) {
+    ++bufpos;
+  }
+  return bufpos;
+}
+
+// Safe versions of strncpy, strncat, snprintf and vsnprintf that always
+// null-terminate.
+
+template<class CTYPE>
+size_t strcpyn(CTYPE* buffer, size_t buflen,
+               const CTYPE* source, size_t srclen = SIZE_UNKNOWN) {
+  if (buflen <= 0)
+    return 0;
+
+  if (srclen == SIZE_UNKNOWN) {
+    srclen = strlenn(source, buflen - 1);
+  } else if (srclen >= buflen) {
+    srclen = buflen - 1;
+  }
+  memcpy(buffer, source, srclen * sizeof(CTYPE));
+  buffer[srclen] = 0;
+  return srclen;
+}
+
+template<class CTYPE>
+size_t strcatn(CTYPE* buffer, size_t buflen,
+               const CTYPE* source, size_t srclen = SIZE_UNKNOWN) {
+  if (buflen <= 0)
+    return 0;
+
+  size_t bufpos = strlenn(buffer, buflen - 1);
+  return bufpos + strcpyn(buffer + bufpos, buflen - bufpos, source, srclen);
+}
+
+// Some compilers (clang specifically) require vsprintfn be defined before
+// sprintfn.
+template<class CTYPE>
+size_t vsprintfn(CTYPE* buffer, size_t buflen, const CTYPE* format,
+                 va_list args) {
+  int len = vsnprintf(buffer, buflen, format, args);
+  if ((len < 0) || (static_cast<size_t>(len) >= buflen)) {
+    len = static_cast<int>(buflen - 1);
+    buffer[len] = 0;
+  }
+  return len;
+}
+
+template<class CTYPE>
+size_t sprintfn(CTYPE* buffer, size_t buflen, const CTYPE* format, ...);
+template<class CTYPE>
+size_t sprintfn(CTYPE* buffer, size_t buflen, const CTYPE* format, ...) {
+  va_list args;
+  va_start(args, format);
+  size_t len = vsprintfn(buffer, buflen, format, args);
+  va_end(args);
+  return len;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Allow safe comparing and copying ascii (not UTF-8) with both wide and
+// non-wide character strings.
+///////////////////////////////////////////////////////////////////////////////
+
+inline int asccmp(const char* s1, const char* s2) {
+  return strcmp(s1, s2);
+}
+inline int ascicmp(const char* s1, const char* s2) {
+  return _stricmp(s1, s2);
+}
+inline int ascncmp(const char* s1, const char* s2, size_t n) {
+  return strncmp(s1, s2, n);
+}
+inline int ascnicmp(const char* s1, const char* s2, size_t n) {
+  return _strnicmp(s1, s2, n);
+}
+inline size_t asccpyn(char* buffer, size_t buflen,
+                      const char* source, size_t srclen = SIZE_UNKNOWN) {
+  return strcpyn(buffer, buflen, source, srclen);
+}
+
+#ifdef WIN32
+
+typedef wchar_t(*CharacterTransformation)(wchar_t);
+inline wchar_t identity(wchar_t c) { return c; }
+int ascii_string_compare(const wchar_t* s1, const char* s2, size_t n,
+                         CharacterTransformation transformation);
+
+inline int asccmp(const wchar_t* s1, const char* s2) {
+  return ascii_string_compare(s1, s2, static_cast<size_t>(-1), identity);
+}
+inline int ascicmp(const wchar_t* s1, const char* s2) {
+  return ascii_string_compare(s1, s2, static_cast<size_t>(-1), tolowercase);
+}
+inline int ascncmp(const wchar_t* s1, const char* s2, size_t n) {
+  return ascii_string_compare(s1, s2, n, identity);
+}
+inline int ascnicmp(const wchar_t* s1, const char* s2, size_t n) {
+  return ascii_string_compare(s1, s2, n, tolowercase);
+}
+size_t asccpyn(wchar_t* buffer, size_t buflen,
+               const char* source, size_t srclen = SIZE_UNKNOWN);
+
+#endif  // WIN32
+
+///////////////////////////////////////////////////////////////////////////////
+// Traits<char> specializations
+///////////////////////////////////////////////////////////////////////////////
+
+template<>
+struct Traits<char> {
+  typedef std::string string;
+  inline static const char* empty_str() { return ""; }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Traits<wchar_t> specializations (Windows only, currently)
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef WIN32
+
+template<>
+struct Traits<wchar_t> {
+  typedef std::wstring string;
+  inline static const wchar_t* Traits<wchar_t>::empty_str() { return L""; }
+};
+
+#endif  // WIN32
+
+// Replaces all occurrences of "search" with "replace".
+void replace_substrs(const char *search,
+                     size_t search_len,
+                     const char *replace,
+                     size_t replace_len,
+                     std::string *s);
+
+// True iff s1 starts with s2.
+bool starts_with(const char *s1, const char *s2);
+
+// True iff s1 ends with s2.
+bool ends_with(const char *s1, const char *s2);
+
+// Remove leading and trailing whitespaces.
+std::string string_trim(const std::string& s);
+
+}  // namespace talk_base
+
+#endif // TALK_BASE_STRINGUTILS_H__
diff --git a/talk/base/stringutils_unittest.cc b/talk/base/stringutils_unittest.cc
new file mode 100644
index 0000000..5611869
--- /dev/null
+++ b/talk/base/stringutils_unittest.cc
@@ -0,0 +1,126 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/common.h"
+
+namespace talk_base {
+
+// Tests for string_match().
+
+TEST(string_matchTest, Matches) {
+  EXPECT_TRUE( string_match("A.B.C.D", "a.b.c.d"));
+  EXPECT_TRUE( string_match("www.TEST.GOOGLE.COM", "www.*.com"));
+  EXPECT_TRUE( string_match("127.0.0.1",  "12*.0.*1"));
+  EXPECT_TRUE( string_match("127.1.0.21", "12*.0.*1"));
+  EXPECT_FALSE(string_match("127.0.0.0",  "12*.0.*1"));
+  EXPECT_FALSE(string_match("127.0.0.0",  "12*.0.*1"));
+  EXPECT_FALSE(string_match("127.1.1.21", "12*.0.*1"));
+}
+
+// It's not clear if we will ever use wchar_t strings on unix.  In theory,
+// all strings should be Utf8 all the time, except when interfacing with Win32
+// APIs that require Utf16.
+
+#ifdef WIN32
+
+// Tests for ascii_string_compare().
+
+// Tests NULL input.
+TEST(ascii_string_compareTest, NullInput) {
+  // The following results in an access violation in
+  // ascii_string_compare.  Is this a bug or by design?  stringutils.h
+  // should document the expected behavior in this case.
+
+  // EXPECT_EQ(0, ascii_string_compare(NULL, NULL, 1, identity));
+}
+
+// Tests comparing two strings of different lengths.
+TEST(ascii_string_compareTest, DifferentLengths) {
+  EXPECT_EQ(-1, ascii_string_compare(L"Test", "Test1", 5, identity));
+}
+
+// Tests the case where the buffer size is smaller than the string
+// lengths.
+TEST(ascii_string_compareTest, SmallBuffer) {
+  EXPECT_EQ(0, ascii_string_compare(L"Test", "Test1", 3, identity));
+}
+
+// Tests the case where the buffer is not full.
+TEST(ascii_string_compareTest, LargeBuffer) {
+  EXPECT_EQ(0, ascii_string_compare(L"Test", "Test", 10, identity));
+}
+
+// Tests comparing two eqaul strings.
+TEST(ascii_string_compareTest, Equal) {
+  EXPECT_EQ(0, ascii_string_compare(L"Test", "Test", 5, identity));
+  EXPECT_EQ(0, ascii_string_compare(L"TeSt", "tEsT", 5, tolowercase));
+}
+
+// Tests comparing a smller string to a larger one.
+TEST(ascii_string_compareTest, LessThan) {
+  EXPECT_EQ(-1, ascii_string_compare(L"abc", "abd", 4, identity));
+  EXPECT_EQ(-1, ascii_string_compare(L"ABC", "abD", 5, tolowercase));
+}
+
+// Tests comparing a larger string to a smaller one.
+TEST(ascii_string_compareTest, GreaterThan) {
+  EXPECT_EQ(1, ascii_string_compare(L"xyz", "xy", 5, identity));
+  EXPECT_EQ(1, ascii_string_compare(L"abc", "ABB", 5, tolowercase));
+}
+#endif  // WIN32
+
+TEST(string_trim_Test, Trimming) {
+  EXPECT_EQ("temp", string_trim("\n\r\t temp \n\r\t"));
+  EXPECT_EQ("temp\n\r\t temp", string_trim(" temp\n\r\t temp "));
+  EXPECT_EQ("temp temp", string_trim("temp temp"));
+  EXPECT_EQ("", string_trim(" \r\n\t"));
+  EXPECT_EQ("", string_trim(""));
+}
+
+TEST(string_startsTest, StartsWith) {
+  EXPECT_TRUE(starts_with("foobar", "foo"));
+  EXPECT_TRUE(starts_with("foobar", "foobar"));
+  EXPECT_TRUE(starts_with("foobar", ""));
+  EXPECT_TRUE(starts_with("", ""));
+  EXPECT_FALSE(starts_with("foobar", "bar"));
+  EXPECT_FALSE(starts_with("foobar", "foobarbaz"));
+  EXPECT_FALSE(starts_with("", "f"));
+}
+
+TEST(string_endsTest, EndsWith) {
+  EXPECT_TRUE(ends_with("foobar", "bar"));
+  EXPECT_TRUE(ends_with("foobar", "foobar"));
+  EXPECT_TRUE(ends_with("foobar", ""));
+  EXPECT_TRUE(ends_with("", ""));
+  EXPECT_FALSE(ends_with("foobar", "foo"));
+  EXPECT_FALSE(ends_with("foobar", "foobarbaz"));
+  EXPECT_FALSE(ends_with("", "f"));
+}
+
+} // namespace talk_base
diff --git a/talk/base/systeminfo.cc b/talk/base/systeminfo.cc
new file mode 100644
index 0000000..ec18658
--- /dev/null
+++ b/talk/base/systeminfo.cc
@@ -0,0 +1,533 @@
+/*
+ * 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/systeminfo.h"
+
+#if defined(WIN32)
+#include <winsock2.h>
+#ifndef EXCLUDE_D3D9
+#include <d3d9.h>
+#endif
+#include <intrin.h>  // for __cpuid()
+#elif defined(OSX)
+#include <ApplicationServices/ApplicationServices.h>
+#include <CoreServices/CoreServices.h>
+#elif defined(LINUX) || defined(ANDROID)
+#include <unistd.h>
+#endif
+#if defined(OSX) || defined(IOS)
+#include <sys/sysctl.h>
+#endif
+
+#if defined(WIN32)
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/win32.h"
+#elif defined(OSX)
+#include "talk/base/macconversion.h"
+#elif defined(LINUX) || defined(ANDROID)
+#include "talk/base/linux.h"
+#endif
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+
+namespace talk_base {
+
+// See Also: http://msdn.microsoft.com/en-us/library/ms683194(v=vs.85).aspx
+#if defined(WIN32)
+typedef BOOL (WINAPI *LPFN_GLPI)(
+    PSYSTEM_LOGICAL_PROCESSOR_INFORMATION,
+    PDWORD);
+
+static void GetProcessorInformation(int* physical_cpus, int* cache_size) {
+  // 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;
+  }
+  // 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;
+    }
+  }
+  *physical_cpus = 0;
+  *cache_size = 0;
+  for (size_t i = 0;
+      i < return_length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); ++i) {
+    if (infos[i].Relationship == RelationProcessorCore) {
+      ++*physical_cpus;
+    } else if (infos[i].Relationship == RelationCache) {
+      int next_cache_size = static_cast<int>(infos[i].Cache.Size);
+      if (next_cache_size >= *cache_size) {
+        *cache_size = next_cache_size;
+      }
+    }
+  }
+  return;
+}
+#else
+// TODO(fbarchard): Use gcc 4.4 provided cpuid intrinsic
+// 32 bit fpic requires ebx be preserved
+#if (defined(__pic__) || defined(__APPLE__)) && defined(__i386__)
+static inline void __cpuid(int cpu_info[4], int info_type) {
+  __asm__ volatile (  // NOLINT
+    "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)
+  );  // NOLINT
+}
+#elif defined(__i386__) || defined(__x86_64__)
+static inline void __cpuid(int cpu_info[4], int info_type) {
+  __asm__ volatile (  // NOLINT
+    "cpuid\n"
+    : "=a"(cpu_info[0]), "=b"(cpu_info[1]), "=c"(cpu_info[2]), "=d"(cpu_info[3])
+    : "a"(info_type)
+  );  // NOLINT
+}
+#endif
+#endif  // WIN32
+
+// Note(fbarchard):
+// Family and model are extended family and extended model.  8 bits each.
+SystemInfo::SystemInfo()
+    : physical_cpus_(1), logical_cpus_(1), cache_size_(0),
+      cpu_family_(0), cpu_model_(0), cpu_stepping_(0),
+      cpu_speed_(0), memory_(0) {
+  // Initialize the basic information.
+#if defined(__arm__) || defined(_M_ARM)
+  cpu_arch_ = SI_ARCH_ARM;
+#elif defined(__x86_64__) || defined(_M_X64)
+  cpu_arch_ = SI_ARCH_X64;
+#elif defined(__i386__) || defined(_M_IX86)
+  cpu_arch_ = SI_ARCH_X86;
+#else
+  cpu_arch_ = SI_ARCH_UNKNOWN;
+#endif
+
+#if defined(WIN32)
+  SYSTEM_INFO si;
+  GetSystemInfo(&si);
+  logical_cpus_ = si.dwNumberOfProcessors;
+  GetProcessorInformation(&physical_cpus_, &cache_size_);
+  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);
+  }
+  uint64_t sysctl_value64;
+  length = sizeof(sysctl_value64);
+  if (!sysctlbyname("hw.l3cachesize", &sysctl_value64, &length, NULL, 0)) {
+    cache_size_ = static_cast<int>(sysctl_value64);
+  }
+  if (!cache_size_) {
+    length = sizeof(sysctl_value64);
+    if (!sysctlbyname("hw.l2cachesize", &sysctl_value64, &length, NULL, 0)) {
+      cache_size_ = static_cast<int>(sysctl_value64);
+    }
+  }
+  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(CPU_X86)
+    // These values only apply to x86 systems.
+    proc_info.GetSectionIntValue(0, "model", &cpu_model_);
+    proc_info.GetSectionIntValue(0, "stepping", &cpu_stepping_);
+    proc_info.GetSectionIntValue(0, "cpu MHz", &cpu_speed_);
+    proc_info.GetSectionIntValue(0, "cache size", &cache_size_);
+    cache_size_ *= 1024;
+#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.
+  // cpuinfo_max_freq is measured in KHz and requires conversion to MHz.
+  int max_freq = talk_base::ReadCpuMaxFreq();
+  if (max_freq > 0) {
+    cpu_speed_ = max_freq / 1000;
+  }
+#endif
+// For L2 CacheSize see also
+// http://www.flounder.com/cpuid_explorer2.htm#CPUID(0x800000006)
+#ifdef CPU_X86
+  if (cache_size_ == 0) {
+    int cpu_info[4];
+    __cpuid(cpu_info, 0x80000000);  // query maximum extended cpuid function.
+    if (static_cast<uint32>(cpu_info[0]) >= 0x80000006) {
+      __cpuid(cpu_info, 0x80000006);
+      cache_size_ = (cpu_info[2] >> 16) * 1024;
+    }
+  }
+#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;
+#if defined(WIN32)
+  DWORD_PTR 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) || 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()) {
+#if defined(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;
+    cpu_vendor_ = std::string(reinterpret_cast<char*>(&cpu_info[0]));
+#elif defined(CPU_ARM)
+    cpu_vendor_ = std::string("ARM");
+#else
+    cpu_vendor_ = std::string("Undefined");
+#endif
+  }
+  return cpu_vendor_;
+}
+
+int SystemInfo::GetCpuCacheSize() {
+  return cache_size_;
+}
+
+// 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_;
+  }
+#if defined(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(fbarchard): 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() {
+#if defined(WIN32)
+  // TODO(fbarchard): 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(fbarchard): 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_;
+  }
+
+#if defined(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  // LINUX || ANDROID
+  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(fbarchard): 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..f84b5fe
--- /dev/null
+++ b/talk/base/systeminfo.h
@@ -0,0 +1,98 @@
+/*
+ * 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 {
+    SI_ARCH_UNKNOWN = -1,
+    SI_ARCH_X86 = 0,
+    SI_ARCH_X64 = 1,
+    SI_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();
+  // Return size of CPU cache in bytes.  Uses largest available cache (L3).
+  int GetCpuCacheSize();
+  // Estimated speed of the CPUs, in MHz.  e.g. 2400 for 2.4 GHz
+  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_;
+  int cache_size_;
+  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_;
+};
+
+}  // namespace talk_base
+
+#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..310b1eb
--- /dev/null
+++ b/talk/base/systeminfo_unittest.cc
@@ -0,0 +1,211 @@
+/*
+ * 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"
+
+#if defined(CPU_X86) || defined(CPU_ARM)
+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, CpuVendorIntelAMDARM) {
+  talk_base::SystemInfo info;
+#if defined(CPU_X86)
+  EXPECT_TRUE(talk_base::string_match(info.GetCpuVendor().c_str(),
+                                      "GenuineIntel") ||
+              talk_base::string_match(info.GetCpuVendor().c_str(),
+                                      "AuthenticAMD"));
+#elif defined(CPU_ARM)
+  EXPECT_TRUE(talk_base::string_match(info.GetCpuVendor().c_str(), "ARM"));
+#endif
+}
+#endif  // defined(CPU_X86) || defined(CPU_ARM)
+
+// Tests CpuArchitecture matches expectations.
+TEST(SystemInfoTest, GetCpuArchitecture) {
+  talk_base::SystemInfo info;
+  LOG(LS_INFO) << "CpuArchitecture: " << info.GetCpuArchitecture();
+  talk_base::SystemInfo::Architecture architecture = info.GetCpuArchitecture();
+#if defined(CPU_X86) || defined(CPU_ARM)
+  if (sizeof(intptr_t) == 8) {
+    EXPECT_EQ(talk_base::SystemInfo::SI_ARCH_X64, architecture);
+  } else if (sizeof(intptr_t) == 4) {
+#if defined(CPU_ARM)
+    EXPECT_EQ(talk_base::SystemInfo::SI_ARCH_ARM, architecture);
+#else
+    EXPECT_EQ(talk_base::SystemInfo::SI_ARCH_X86, architecture);
+#endif
+  }
+#endif
+}
+
+// Tests Cpu Cache Size
+TEST(SystemInfoTest, CpuCacheSize) {
+  talk_base::SystemInfo info;
+  LOG(LS_INFO) << "CpuCacheSize: " << info.GetCpuCacheSize();
+  EXPECT_GE(info.GetCpuCacheSize(), 8192);  // 8 KB min cache
+  EXPECT_LE(info.GetCpuCacheSize(), 1024 * 1024 * 1024);  // 1 GB max cache
+}
+
+// Tests MachineModel is set.  On Mac test machine model is known.
+TEST(SystemInfoTest, MachineModelKnown) {
+  talk_base::SystemInfo info;
+  EXPECT_FALSE(info.GetMachineModel().empty());
+  const char *machine_model = info.GetMachineModel().c_str();
+  LOG(LS_INFO) << "MachineModel: " << machine_model;
+  bool known = true;
+#if defined(OSX)
+  // Full list as of May 2012.  Update when new OSX based models are added.
+  known = talk_base::string_match(machine_model, "MacBookPro*") ||
+          talk_base::string_match(machine_model, "MacBookAir*") ||
+          talk_base::string_match(machine_model, "MacBook*") ||
+          talk_base::string_match(machine_model, "MacPro*") ||
+          talk_base::string_match(machine_model, "Macmini*") ||
+          talk_base::string_match(machine_model, "iMac*") ||
+          talk_base::string_match(machine_model, "Xserve*");
+#elif !defined(IOS)
+  // All other machines return Not available.
+  known = talk_base::string_match(info.GetMachineModel().c_str(),
+                                  "Not available");
+#endif
+  if (!known) {
+    LOG(LS_WARNING) << "Machine Model Unknown: " << machine_model;
+  }
+}
+
+// Tests maximum cpu clockrate.
+TEST(SystemInfoTest, CpuMaxCpuSpeed) {
+  talk_base::SystemInfo info;
+  LOG(LS_INFO) << "MaxCpuSpeed: " << info.GetMaxCpuSpeed();
+  EXPECT_GT(info.GetMaxCpuSpeed(), 0);
+  EXPECT_LT(info.GetMaxCpuSpeed(), 100000);  // 100 Ghz
+}
+
+// Tests current cpu clockrate.
+TEST(SystemInfoTest, CpuCurCpuSpeed) {
+  talk_base::SystemInfo info;
+  LOG(LS_INFO) << "MaxCurSpeed: " << info.GetCurCpuSpeed();
+  EXPECT_GT(info.GetCurCpuSpeed(), 0);
+  EXPECT_LT(info.GetMaxCpuSpeed(), 100000);
+}
+
+// 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/stepping is only available on X86. The following tests
+// that they are set when running on x86 CPUs. Valid Family/Model/Stepping
+// values are non-zero on known CPUs.
+
+// Tests Intel CPU Family identification.
+TEST(SystemInfoTest, CpuFamily) {
+  talk_base::SystemInfo info;
+  LOG(LS_INFO) << "CpuFamily: " << info.GetCpuFamily();
+  EXPECT_GT(info.GetCpuFamily(), 0);
+}
+
+// Tests Intel CPU Model identification.
+TEST(SystemInfoTest, CpuModel) {
+  talk_base::SystemInfo info;
+  LOG(LS_INFO) << "CpuModel: " << info.GetCpuModel();
+  EXPECT_GT(info.GetCpuModel(), 0);
+}
+
+// Tests Intel CPU Stepping identification.
+TEST(SystemInfoTest, CpuStepping) {
+  talk_base::SystemInfo info;
+  LOG(LS_INFO) << "CpuStepping: " << info.GetCpuStepping();
+  EXPECT_GT(info.GetCpuStepping(), 0);
+}
+#else  // CPU_X86
+// If not running on x86 CPU the following tests expect the functions to
+// return 0.
+TEST(SystemInfoTest, CpuFamily) {
+  talk_base::SystemInfo info;
+  LOG(LS_INFO) << "CpuFamily: " << info.GetCpuFamily();
+  EXPECT_EQ(0, info.GetCpuFamily());
+}
+
+// Tests Intel CPU Model identification.
+TEST(SystemInfoTest, CpuModel) {
+  talk_base::SystemInfo info;
+  LOG(LS_INFO) << "CpuModel: " << info.GetCpuModel();
+  EXPECT_EQ(0, info.GetCpuModel());
+}
+
+// Tests Intel CPU Stepping identification.
+TEST(SystemInfoTest, CpuStepping) {
+  talk_base::SystemInfo info;
+  LOG(LS_INFO) << "CpuStepping: " << info.GetCpuStepping();
+  EXPECT_EQ(0, info.GetCpuStepping());
+}
+#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
new file mode 100644
index 0000000..c37797c
--- /dev/null
+++ b/talk/base/task.cc
@@ -0,0 +1,289 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/task.h"
+#include "talk/base/common.h"
+#include "talk/base/taskrunner.h"
+
+namespace talk_base {
+
+int32 Task::unique_id_seed_ = 0;
+
+Task::Task(TaskParent *parent)
+    : TaskParent(this, parent),
+      state_(STATE_INIT),
+      blocked_(false),
+      done_(false),
+      aborted_(false),
+      busy_(false),
+      error_(false),
+      start_time_(0),
+      timeout_time_(0),
+      timeout_seconds_(0),
+      timeout_suspended_(false)  {
+  unique_id_ = unique_id_seed_++;
+
+  // sanity check that we didn't roll-over our id seed
+  ASSERT(unique_id_ < unique_id_seed_);
+}
+
+Task::~Task() {
+  // Is this task being deleted in the correct manner?
+  ASSERT(!done_ || GetRunner()->is_ok_to_delete(this));
+  ASSERT(state_ == STATE_INIT || done_);
+  ASSERT(state_ == STATE_INIT || blocked_);
+
+  // If the task is being deleted without being done, it
+  // means that it hasn't been removed from its parent.
+  // This happens if a task is deleted outside of TaskRunner.
+  if (!done_) {
+    Stop();
+  }
+}
+
+int64 Task::CurrentTime() {
+  return GetRunner()->CurrentTime();
+}
+
+int64 Task::ElapsedTime() {
+  return CurrentTime() - start_time_;
+}
+
+void Task::Start() {
+  if (state_ != STATE_INIT)
+    return;
+  // Set the start time before starting the task.  Otherwise if the task
+  // finishes quickly and deletes the Task object, setting start_time_
+  // will crash.
+  start_time_ = CurrentTime();
+  GetRunner()->StartTask(this);
+}
+
+void Task::Step() {
+  if (done_) {
+#ifdef _DEBUG
+    // we do not know how !blocked_ happens when done_ - should be impossible.
+    // But it causes problems, so in retail build, we force blocked_, and
+    // under debug we assert.
+    ASSERT(blocked_);
+#else
+    blocked_ = true;
+#endif
+    return;
+  }
+
+  // Async Error() was called
+  if (error_) {
+    done_ = true;
+    state_ = STATE_ERROR;
+    blocked_ = true;
+//   obsolete - an errored task is not considered done now
+//   SignalDone();
+
+    Stop();
+#ifdef _DEBUG
+    // verify that stop removed this from its parent
+    ASSERT(!parent()->IsChildTask(this));
+#endif
+    return;
+  }
+
+  busy_ = true;
+  int new_state = Process(state_);
+  busy_ = false;
+
+  if (aborted_) {
+    Abort(true);  // no need to wake because we're awake
+    return;
+  }
+
+  if (new_state == STATE_BLOCKED) {
+    blocked_ = true;
+    // Let the timeout continue
+  } else {
+    state_ = new_state;
+    blocked_ = false;
+    ResetTimeout();
+  }
+
+  if (new_state == STATE_DONE) {
+    done_ = true;
+  } else if (new_state == STATE_ERROR) {
+    done_ = true;
+    error_ = true;
+  }
+
+  if (done_) {
+//  obsolete - call this yourself
+//    SignalDone();
+
+    Stop();
+#if _DEBUG
+    // verify that stop removed this from its parent
+    ASSERT(!parent()->IsChildTask(this));
+#endif
+    blocked_ = true;
+  }
+}
+
+void Task::Abort(bool nowake) {
+  // Why only check for done_ (instead of "aborted_ || done_")?
+  //
+  // If aborted_ && !done_, it means the logic for aborting still
+  // needs to be executed (because busy_ must have been true when
+  // Abort() was previously called).
+  if (done_)
+    return;
+  aborted_ = true;
+  if (!busy_) {
+    done_ = true;
+    blocked_ = true;
+    error_ = true;
+
+    // "done_" is set before calling "Stop()" to ensure that this code 
+    // doesn't execute more than once (recursively) for the same task.
+    Stop();
+#ifdef _DEBUG
+    // verify that stop removed this from its parent
+    ASSERT(!parent()->IsChildTask(this));
+#endif
+    if (!nowake) {
+      // WakeTasks to self-delete.
+      // Don't call Wake() because it is a no-op after "done_" is set.
+      // Even if Wake() did run, it clears "blocked_" which isn't desireable.
+      GetRunner()->WakeTasks();
+    }
+  }
+}
+
+void Task::Wake() {
+  if (done_)
+    return;
+  if (blocked_) {
+    blocked_ = false;
+    GetRunner()->WakeTasks();
+  }
+}
+
+void Task::Error() {
+  if (error_ || done_)
+    return;
+  error_ = true;
+  Wake();
+}
+
+std::string Task::GetStateName(int state) const {
+  switch (state) {
+    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 "??";
+}
+
+int Task::Process(int state) {
+  int newstate = STATE_ERROR;
+
+  if (TimedOut()) {
+    ClearTimeout();
+    newstate = OnTimeout();
+    SignalTimeout();
+  } else {
+    switch (state) {
+      case STATE_INIT:
+        newstate = STATE_START;
+        break;
+      case STATE_START:
+        newstate = ProcessStart();
+        break;
+      case STATE_RESPONSE:
+        newstate = ProcessResponse();
+        break;
+      case STATE_DONE:
+      case STATE_ERROR:
+        newstate = STATE_BLOCKED;
+        break;
+    }
+  }
+
+  return newstate;
+}
+
+void Task::Stop() {
+  // No need to wake because we're either awake or in abort
+  TaskParent::OnStopped(this);
+}
+
+void Task::set_timeout_seconds(const int timeout_seconds) {
+  timeout_seconds_ = timeout_seconds;
+  ResetTimeout();
+}
+
+bool Task::TimedOut() {
+  return timeout_seconds_ &&
+    timeout_time_ &&
+    CurrentTime() >= timeout_time_;
+}
+
+void Task::ResetTimeout() {
+  int64 previous_timeout_time = timeout_time_;
+  bool timeout_allowed = (state_ != STATE_INIT)
+                      && (state_ != STATE_DONE)
+                      && (state_ != STATE_ERROR);
+  if (timeout_seconds_ && timeout_allowed && !timeout_suspended_)
+    timeout_time_ = CurrentTime() +
+                    (timeout_seconds_ * kSecToMsec * kMsecTo100ns);
+  else
+    timeout_time_ = 0;
+
+  GetRunner()->UpdateTaskTimeout(this, previous_timeout_time);
+}
+
+void Task::ClearTimeout() {
+  int64 previous_timeout_time = timeout_time_;
+  timeout_time_ = 0;
+  GetRunner()->UpdateTaskTimeout(this, previous_timeout_time);
+}
+
+void Task::SuspendTimeout() {
+  if (!timeout_suspended_) {
+    timeout_suspended_ = true;
+    ResetTimeout();
+  }
+}
+
+void Task::ResumeTimeout() {
+  if (timeout_suspended_) {
+    timeout_suspended_ = false;
+    ResetTimeout();
+  }
+}
+
+} // namespace talk_base
diff --git a/talk/base/task.h b/talk/base/task.h
new file mode 100644
index 0000000..10e6f5c
--- /dev/null
+++ b/talk/base/task.h
@@ -0,0 +1,194 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_TASK_H__
+#define TALK_BASE_TASK_H__
+
+#include <string>
+#include "talk/base/basictypes.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/taskparent.h"
+
+/////////////////////////////////////////////////////////////////////
+//
+// TASK
+//
+/////////////////////////////////////////////////////////////////////
+//
+// Task is a state machine infrastructure.  States are pushed forward by
+// pushing forwards a TaskRunner that holds on to all Tasks.  The purpose
+// of Task is threefold:
+//
+// (1) It manages ongoing work on the UI thread.  Multitasking without
+// threads, keeping it easy, keeping it real. :-)  It does this by
+// organizing a set of states for each task.  When you return from your
+// Process*() function, you return an integer for the next state.  You do
+// not go onto the next state yourself.  Every time you enter a state,
+// you check to see if you can do anything yet.  If not, you return
+// STATE_BLOCKED.  If you _could_ do anything, do not return
+// STATE_BLOCKED - even if you end up in the same state, return
+// STATE_mysamestate.  When you are done, return STATE_DONE and then the
+// task will self-delete sometime afterwards.
+//
+// (2) It helps you avoid all those reentrancy problems when you chain
+// too many triggers on one thread.  Basically if you want to tell a task
+// to process something for you, you feed your task some information and
+// then you Wake() it.  Don't tell it to process it right away.  If it
+// might be working on something as you send it information, you may want
+// to have a queue in the task.
+//
+// (3) Finally it helps manage parent tasks and children.  If a parent
+// task gets aborted, all the children tasks are too.  The nice thing
+// about this, for example, is if you have one parent task that
+// represents, say, and Xmpp connection, then you can spawn a whole bunch
+// of infinite lifetime child tasks and now worry about cleaning them up.
+//  When the parent task goes to STATE_DONE, the task engine will make
+// sure all those children are aborted and get deleted.
+//
+// Notice that Task has a few built-in states, e.g.,
+//
+// STATE_INIT - the task isn't running yet
+// STATE_START - the task is in its first state
+// STATE_RESPONSE - the task is in its second state
+// STATE_DONE - the task is done
+//
+// STATE_ERROR - indicates an error - we should audit the error code in
+// light of any usage of it to see if it should be improved.  When I
+// first put down the task stuff I didn't have a good sense of what was
+// needed for Abort and Error, and now the subclasses of Task will ground
+// the design in a stronger way.
+//
+// STATE_NEXT - the first undefined state number.  (like WM_USER) - you
+// can start defining more task states there.
+//
+// When you define more task states, just override Process(int state) and
+// add your own switch statement.  If you want to delegate to
+// Task::Process, you can effectively delegate to its switch statement.
+// No fancy method pointers or such - this is all just pretty low tech,
+// easy to debug, and fast.
+//
+// Also notice that Task has some primitive built-in timeout functionality.
+//
+// A timeout is defined as "the task stays in STATE_BLOCKED longer than
+// timeout_seconds_."
+//
+// Descendant classes can override this behavior by calling the
+// various protected methods to change the timeout behavior.  For
+// instance, a descendand might call SuspendTimeout() when it knows
+// that it isn't waiting for anything that might timeout, but isn't
+// yet in the STATE_DONE state.
+//
+
+namespace talk_base {
+
+// Executes a sequence of steps
+class Task : public TaskParent {
+ public:
+  Task(TaskParent *parent);
+  virtual ~Task();
+
+  int32 unique_id() { return unique_id_; }
+
+  void Start();
+  void Step();
+  int GetState() const { return state_; }
+  bool HasError() const { return (GetState() == STATE_ERROR); }
+  bool Blocked() const { return blocked_; }
+  bool IsDone() const { return done_; }
+  int64 ElapsedTime();
+
+  // Called from outside to stop task without any more callbacks
+  void Abort(bool nowake = false);
+
+  bool TimedOut();
+
+  int64 timeout_time() const { return timeout_time_; }
+  int timeout_seconds() const { return timeout_seconds_; }
+  void set_timeout_seconds(int timeout_seconds);
+
+  sigslot::signal0<> SignalTimeout;
+
+  // Called inside the task to signal that the task may be unblocked
+  void Wake();
+
+ protected:
+
+  enum {
+    STATE_BLOCKED = -1,
+    STATE_INIT = 0,
+    STATE_START = 1,
+    STATE_DONE = 2,
+    STATE_ERROR = 3,
+    STATE_RESPONSE = 4,
+    STATE_NEXT = 5,  // Subclasses which need more states start here and higher
+  };
+
+  // Called inside to advise that the task should wake and signal an error
+  void Error();
+
+  int64 CurrentTime();
+
+  virtual std::string GetStateName(int state) const;
+  virtual int Process(int state);
+  virtual void Stop();
+  virtual int ProcessStart() = 0;
+  virtual int ProcessResponse() { return STATE_DONE; }
+
+  void ResetTimeout();
+  void ClearTimeout();
+
+  void SuspendTimeout();
+  void ResumeTimeout();
+
+ protected:
+  virtual int OnTimeout() {
+    // by default, we are finished after timing out
+    return STATE_DONE;
+  }
+
+ private:
+  void Done();
+
+  int state_;
+  bool blocked_;
+  bool done_;
+  bool aborted_;
+  bool busy_;
+  bool error_;
+  int64 start_time_;
+  int64 timeout_time_;
+  int timeout_seconds_;
+  bool timeout_suspended_;
+  int32 unique_id_;
+  
+  static int32 unique_id_seed_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_TASK_H__
diff --git a/talk/base/task_unittest.cc b/talk/base/task_unittest.cc
new file mode 100644
index 0000000..0c4a7a20
--- /dev/null
+++ b/talk/base/task_unittest.cc
@@ -0,0 +1,562 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef POSIX
+#include <sys/time.h>
+#endif  // POSIX
+
+// TODO: Remove this once the cause of sporadic failures in these
+// tests is tracked down.
+#include <iostream>
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#endif  // WIN32
+
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/task.h"
+#include "talk/base/taskrunner.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+
+namespace talk_base {
+
+static int64 GetCurrentTime() {
+  return static_cast<int64>(Time()) * 10000;
+}
+
+// feel free to change these numbers.  Note that '0' won't work, though
+#define STUCK_TASK_COUNT 5
+#define HAPPY_TASK_COUNT 20
+
+// this is a generic timeout task which, when it signals timeout, will
+// include the unique ID of the task in the signal (we don't use this
+// in production code because we haven't yet had occasion to generate
+// an array of the same types of task)
+
+class IdTimeoutTask : public Task, public sigslot::has_slots<> {
+ public:
+  explicit IdTimeoutTask(TaskParent *parent) : Task(parent) {
+    SignalTimeout.connect(this, &IdTimeoutTask::OnLocalTimeout);
+  }
+
+  sigslot::signal1<const int> SignalTimeoutId;
+  sigslot::signal1<const int> SignalDoneId;
+
+  virtual int ProcessStart() {
+    return STATE_RESPONSE;
+  }
+
+  void OnLocalTimeout() {
+    SignalTimeoutId(unique_id());
+  }
+
+ protected:
+  virtual void Stop() {
+    SignalDoneId(unique_id());
+    Task::Stop();
+  }
+};
+
+class StuckTask : public IdTimeoutTask {
+ public:
+  explicit StuckTask(TaskParent *parent) : IdTimeoutTask(parent) {}
+  virtual int ProcessStart() {
+    return STATE_BLOCKED;
+  }
+};
+
+class HappyTask : public IdTimeoutTask {
+ public:
+  explicit HappyTask(TaskParent *parent) : IdTimeoutTask(parent) {
+    time_to_perform_ = rand() % (STUCK_TASK_COUNT / 2);
+  }
+  virtual int ProcessStart() {
+    if (ElapsedTime() > (time_to_perform_ * 1000 * 10000))
+      return STATE_RESPONSE;
+    else
+      return STATE_BLOCKED;
+  }
+
+ private:
+  int time_to_perform_;
+};
+
+// simple implementation of a task runner which uses Windows'
+// GetSystemTimeAsFileTime() to get the current clock ticks
+
+class MyTaskRunner : public TaskRunner {
+ public:
+  virtual void WakeTasks() { RunTasks(); }
+  virtual int64 CurrentTime() {
+    return GetCurrentTime();
+  }
+
+  bool timeout_change() const {
+    return timeout_change_;
+  }
+
+  void clear_timeout_change() {
+    timeout_change_ = false;
+  }
+ protected:
+  virtual void OnTimeoutChange() {
+    timeout_change_ = true;
+  }
+  bool timeout_change_;
+};
+
+//
+// this unit test is primarily concerned (for now) with the timeout
+// functionality in tasks.  It works as follows:
+//
+//   * Create a bunch of tasks, some "stuck" (ie., guaranteed to timeout)
+//     and some "happy" (will immediately finish).
+//   * Set the timeout on the "stuck" tasks to some number of seconds between
+//     1 and the number of stuck tasks
+//   * Start all the stuck & happy tasks in random order
+//   * Wait "number of stuck tasks" seconds and make sure everything timed out
+
+class TaskTest : public sigslot::has_slots<> {
+ public:
+  TaskTest() {}
+
+  // no need to delete any tasks; the task runner owns them
+  ~TaskTest() {}
+
+  void Start() {
+    // create and configure tasks
+    for (int i = 0; i < STUCK_TASK_COUNT; ++i) {
+      stuck_[i].task_ = new StuckTask(&task_runner_);
+      stuck_[i].task_->SignalTimeoutId.connect(this,
+                                               &TaskTest::OnTimeoutStuck);
+      stuck_[i].timed_out_ = false;
+      stuck_[i].xlat_ = stuck_[i].task_->unique_id();
+      stuck_[i].task_->set_timeout_seconds(i + 1);
+      LOG(LS_INFO) << "Task " << stuck_[i].xlat_ << " created with timeout "
+                   << stuck_[i].task_->timeout_seconds();
+    }
+
+    for (int i = 0; i < HAPPY_TASK_COUNT; ++i) {
+      happy_[i].task_ = new HappyTask(&task_runner_);
+      happy_[i].task_->SignalTimeoutId.connect(this,
+                                               &TaskTest::OnTimeoutHappy);
+      happy_[i].task_->SignalDoneId.connect(this,
+                                            &TaskTest::OnDoneHappy);
+      happy_[i].timed_out_ = false;
+      happy_[i].xlat_ = happy_[i].task_->unique_id();
+    }
+
+    // start all the tasks in random order
+    int stuck_index = 0;
+    int happy_index = 0;
+    for (int i = 0; i < STUCK_TASK_COUNT + HAPPY_TASK_COUNT; ++i) {
+      if ((stuck_index < STUCK_TASK_COUNT) &&
+          (happy_index < HAPPY_TASK_COUNT)) {
+        if (rand() % 2 == 1) {
+          stuck_[stuck_index++].task_->Start();
+        } else {
+          happy_[happy_index++].task_->Start();
+        }
+      } else if (stuck_index < STUCK_TASK_COUNT) {
+        stuck_[stuck_index++].task_->Start();
+      } else {
+        happy_[happy_index++].task_->Start();
+      }
+    }
+
+    for (int i = 0; i < STUCK_TASK_COUNT; ++i) {
+      std::cout << "Stuck task #" << i << " timeout is " <<
+          stuck_[i].task_->timeout_seconds() << " at " <<
+          stuck_[i].task_->timeout_time() << std::endl;
+    }
+
+    // just a little self-check to make sure we started all the tasks
+    ASSERT_EQ(STUCK_TASK_COUNT, stuck_index);
+    ASSERT_EQ(HAPPY_TASK_COUNT, happy_index);
+
+    // run the unblocked tasks
+    LOG(LS_INFO) << "Running tasks";
+    task_runner_.RunTasks();
+
+    std::cout << "Start time is " << GetCurrentTime() << std::endl;
+
+    // give all the stuck tasks time to timeout
+    for (int i = 0; !task_runner_.AllChildrenDone() && i < STUCK_TASK_COUNT;
+         ++i) {
+      Thread::Current()->ProcessMessages(1000);
+      for (int j = 0; j < HAPPY_TASK_COUNT; ++j) {
+        if (happy_[j].task_) {
+          happy_[j].task_->Wake();
+        }
+      }
+      LOG(LS_INFO) << "Polling tasks";
+      task_runner_.PollTasks();
+    }
+
+    // We see occasional test failures here due to the stuck tasks not having
+    // timed-out yet, which seems like it should be impossible. To help track
+    // this down we have added logging of the timing information, which we send
+    // directly to stdout so that we get it in opt builds too.
+    std::cout << "End time is " << GetCurrentTime() << std::endl;
+  }
+
+  void OnTimeoutStuck(const int id) {
+    LOG(LS_INFO) << "Timed out task " << id;
+
+    int i;
+    for (i = 0; i < STUCK_TASK_COUNT; ++i) {
+      if (stuck_[i].xlat_ == id) {
+        stuck_[i].timed_out_ = true;
+        stuck_[i].task_ = NULL;
+        break;
+      }
+    }
+
+    // getting a bad ID here is a failure, but let's continue
+    // running to see what else might go wrong
+    EXPECT_LT(i, STUCK_TASK_COUNT);
+  }
+
+  void OnTimeoutHappy(const int id) {
+    int i;
+    for (i = 0; i < HAPPY_TASK_COUNT; ++i) {
+      if (happy_[i].xlat_ == id) {
+        happy_[i].timed_out_ = true;
+        happy_[i].task_ = NULL;
+        break;
+      }
+    }
+
+    // getting a bad ID here is a failure, but let's continue
+    // running to see what else might go wrong
+    EXPECT_LT(i, HAPPY_TASK_COUNT);
+  }
+
+  void OnDoneHappy(const int id) {
+    int i;
+    for (i = 0; i < HAPPY_TASK_COUNT; ++i) {
+      if (happy_[i].xlat_ == id) {
+        happy_[i].task_ = NULL;
+        break;
+      }
+    }
+
+    // getting a bad ID here is a failure, but let's continue
+    // running to see what else might go wrong
+    EXPECT_LT(i, HAPPY_TASK_COUNT);
+  }
+
+  void check_passed() {
+    EXPECT_TRUE(task_runner_.AllChildrenDone());
+
+    // make sure none of our happy tasks timed out
+    for (int i = 0; i < HAPPY_TASK_COUNT; ++i) {
+      EXPECT_FALSE(happy_[i].timed_out_);
+    }
+
+    // make sure all of our stuck tasks timed out
+    for (int i = 0; i < STUCK_TASK_COUNT; ++i) {
+      EXPECT_TRUE(stuck_[i].timed_out_);
+      if (!stuck_[i].timed_out_) {
+        std::cout << "Stuck task #" << i << " timeout is at "
+            << stuck_[i].task_->timeout_time() << std::endl;        
+      }
+    }
+
+    std::cout.flush();
+  }
+
+ private:
+  struct TaskInfo {
+    IdTimeoutTask *task_;
+    bool timed_out_;
+    int xlat_;
+  };
+
+  MyTaskRunner task_runner_;
+  TaskInfo stuck_[STUCK_TASK_COUNT];
+  TaskInfo happy_[HAPPY_TASK_COUNT];
+};
+
+TEST(start_task_test, Timeout) {
+  TaskTest task_test;
+  task_test.Start();
+  task_test.check_passed();
+}
+
+// Test for aborting the task while it is running
+
+class AbortTask : public Task {
+ public:
+  explicit AbortTask(TaskParent *parent) : Task(parent) {
+    set_timeout_seconds(1);
+  }
+
+  virtual int ProcessStart() {
+    Abort();
+    return STATE_NEXT;
+  }
+ private:
+  DISALLOW_EVIL_CONSTRUCTORS(AbortTask);
+};
+
+class TaskAbortTest : public sigslot::has_slots<> {
+ public:
+  TaskAbortTest() {}
+
+  // no need to delete any tasks; the task runner owns them
+  ~TaskAbortTest() {}
+
+  void Start() {
+    Task *abort_task = new AbortTask(&task_runner_);
+    abort_task->SignalTimeout.connect(this, &TaskAbortTest::OnTimeout);
+    abort_task->Start();
+
+    // run the task
+    task_runner_.RunTasks();
+  }
+
+ private:
+  void OnTimeout() {
+    FAIL() << "Task timed out instead of aborting.";
+  }
+
+  MyTaskRunner task_runner_;
+  DISALLOW_EVIL_CONSTRUCTORS(TaskAbortTest);
+};
+
+TEST(start_task_test, Abort) {
+  TaskAbortTest abort_test;
+  abort_test.Start();
+}
+
+// Test for aborting a task to verify that it does the Wake operation
+// which gets it deleted.
+
+class SetBoolOnDeleteTask : public Task {
+ public:
+  SetBoolOnDeleteTask(TaskParent *parent, bool *set_when_deleted)
+    : Task(parent),
+      set_when_deleted_(set_when_deleted) {
+    EXPECT_TRUE(NULL != set_when_deleted);
+    EXPECT_FALSE(*set_when_deleted);
+  }
+
+  virtual ~SetBoolOnDeleteTask() {
+    *set_when_deleted_ = true;
+  }
+
+  virtual int ProcessStart() {
+    return STATE_BLOCKED;
+  }
+
+ private:
+  bool* set_when_deleted_;
+  DISALLOW_EVIL_CONSTRUCTORS(SetBoolOnDeleteTask);
+};
+
+class AbortShouldWakeTest : public sigslot::has_slots<> {
+ public:
+  AbortShouldWakeTest() {}
+
+  // no need to delete any tasks; the task runner owns them
+  ~AbortShouldWakeTest() {}
+
+  void Start() {
+    bool task_deleted = false;
+    Task *task_to_abort = new SetBoolOnDeleteTask(&task_runner_, &task_deleted);
+    task_to_abort->Start();
+
+    // Task::Abort() should call TaskRunner::WakeTasks(). WakeTasks calls
+    // TaskRunner::RunTasks() immediately which should delete the task.
+    task_to_abort->Abort();
+    EXPECT_TRUE(task_deleted);
+
+    if (!task_deleted) {
+      // avoid a crash (due to referencing a local variable)
+      // if the test fails.
+      task_runner_.RunTasks();
+    }
+  }
+
+ private:
+  void OnTimeout() {
+    FAIL() << "Task timed out instead of aborting.";
+  }
+
+  MyTaskRunner task_runner_;
+  DISALLOW_EVIL_CONSTRUCTORS(AbortShouldWakeTest);
+};
+
+TEST(start_task_test, AbortShouldWake) {
+  AbortShouldWakeTest abort_should_wake_test;
+  abort_should_wake_test.Start();
+}
+
+// Validate that TaskRunner's OnTimeoutChange gets called appropriately
+//  * When a task calls UpdateTaskTimeout
+//  * When the next timeout task time, times out
+class TimeoutChangeTest : public sigslot::has_slots<> {
+ public:
+  TimeoutChangeTest()
+    : task_count_(ARRAY_SIZE(stuck_tasks_)) {}
+
+  // no need to delete any tasks; the task runner owns them
+  ~TimeoutChangeTest() {}
+
+  void Start() {
+    for (int i = 0; i < task_count_; ++i) {
+      stuck_tasks_[i] = new StuckTask(&task_runner_);
+      stuck_tasks_[i]->set_timeout_seconds(i + 2);
+      stuck_tasks_[i]->SignalTimeoutId.connect(this,
+                                               &TimeoutChangeTest::OnTimeoutId);
+    }
+
+    for (int i = task_count_ - 1; i >= 0; --i) {
+      stuck_tasks_[i]->Start();
+    }
+    task_runner_.clear_timeout_change();
+
+    // At this point, our timeouts are set as follows
+    // task[0] is 2 seconds, task[1] at 3 seconds, etc.
+
+    stuck_tasks_[0]->set_timeout_seconds(2);
+    // Now, task[0] is 2 seconds, task[1] at 3 seconds...
+    // so timeout change shouldn't be called.
+    EXPECT_FALSE(task_runner_.timeout_change());
+    task_runner_.clear_timeout_change();
+
+    stuck_tasks_[0]->set_timeout_seconds(1);
+    // task[0] is 1 seconds, task[1] at 3 seconds...
+    // The smallest timeout got smaller so timeout change be called.
+    EXPECT_TRUE(task_runner_.timeout_change());
+    task_runner_.clear_timeout_change();
+
+    stuck_tasks_[1]->set_timeout_seconds(2);
+    // task[0] is 1 seconds, task[1] at 2 seconds...
+    // The smallest timeout is still 1 second so no timeout change.
+    EXPECT_FALSE(task_runner_.timeout_change());
+    task_runner_.clear_timeout_change();
+
+    while (task_count_ > 0) {
+      int previous_count = task_count_;
+      task_runner_.PollTasks();
+      if (previous_count != task_count_) {
+        // We only get here when a task times out.  When that
+        // happens, the timeout change should get called because
+        // the smallest timeout is now in the past.
+        EXPECT_TRUE(task_runner_.timeout_change());
+        task_runner_.clear_timeout_change();
+      }
+      Thread::Current()->socketserver()->Wait(500, false);
+    }
+  }
+
+ private:
+  void OnTimeoutId(const int id) {
+    for (int i = 0; i < ARRAY_SIZE(stuck_tasks_); ++i) {
+      if (stuck_tasks_[i] && stuck_tasks_[i]->unique_id() == id) {
+        task_count_--;
+        stuck_tasks_[i] = NULL;
+        break;
+      }
+    }
+  }
+
+  MyTaskRunner task_runner_;
+  StuckTask* (stuck_tasks_[3]);
+  int task_count_;
+  DISALLOW_EVIL_CONSTRUCTORS(TimeoutChangeTest);
+};
+
+TEST(start_task_test, TimeoutChange) {
+  TimeoutChangeTest timeout_change_test;
+  timeout_change_test.Start();
+}
+
+class DeleteTestTaskRunner : public TaskRunner {
+ public:
+  DeleteTestTaskRunner() {
+  }
+  virtual void WakeTasks() { }
+  virtual int64 CurrentTime() {
+    return GetCurrentTime();
+  }
+ private:
+  DISALLOW_EVIL_CONSTRUCTORS(DeleteTestTaskRunner);
+};
+
+TEST(unstarted_task_test, DeleteTask) {
+  // This test ensures that we don't
+  // crash if a task is deleted without running it.
+  DeleteTestTaskRunner task_runner;
+  HappyTask* happy_task = new HappyTask(&task_runner);
+  happy_task->Start();
+
+  // try deleting the task directly
+  HappyTask* child_happy_task = new HappyTask(happy_task);
+  delete child_happy_task;
+
+  // run the unblocked tasks
+  task_runner.RunTasks();
+}
+
+TEST(unstarted_task_test, DoNotDeleteTask1) {
+  // This test ensures that we don't
+  // crash if a task runner is deleted without
+  // running a certain task.
+  DeleteTestTaskRunner task_runner;
+  HappyTask* happy_task = new HappyTask(&task_runner);
+  happy_task->Start();
+
+  HappyTask* child_happy_task = new HappyTask(happy_task);
+  child_happy_task->Start();
+
+  // Never run the tasks
+}
+
+TEST(unstarted_task_test, DoNotDeleteTask2) {
+  // This test ensures that we don't
+  // crash if a taskrunner is delete with a
+  // task that has never been started.
+  DeleteTestTaskRunner task_runner;
+  HappyTask* happy_task = new HappyTask(&task_runner);
+  happy_task->Start();
+
+  // Do not start the task.
+  // Note: this leaks memory, so don't do this.
+  // Instead, always run your tasks or delete them.
+  new HappyTask(happy_task);
+
+  // run the unblocked tasks
+  task_runner.RunTasks();
+}
+
+}  // namespace talk_base
diff --git a/talk/base/taskparent.cc b/talk/base/taskparent.cc
new file mode 100644
index 0000000..f05ee82
--- /dev/null
+++ b/talk/base/taskparent.cc
@@ -0,0 +1,112 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <algorithm>
+
+#include "talk/base/taskparent.h"
+
+#include "talk/base/task.h"
+#include "talk/base/taskrunner.h"
+
+namespace talk_base {
+
+TaskParent::TaskParent(Task* derived_instance, TaskParent *parent)
+    : parent_(parent) {
+  ASSERT(derived_instance != NULL);
+  ASSERT(parent != NULL);
+  runner_ = parent->GetRunner();
+  parent_->AddChild(derived_instance);
+  Initialize();
+}
+
+TaskParent::TaskParent(TaskRunner *derived_instance)
+    : parent_(NULL),
+      runner_(derived_instance) {
+  ASSERT(derived_instance != NULL);
+  Initialize();
+}
+
+// Does common initialization of member variables
+void TaskParent::Initialize() {
+  children_.reset(new ChildSet());
+  child_error_ = false;
+}
+
+void TaskParent::AddChild(Task *child) {
+  children_->insert(child);
+}
+
+#ifdef _DEBUG
+bool TaskParent::IsChildTask(Task *task) {
+  ASSERT(task != NULL);
+  return task->parent_ == this && children_->find(task) != children_->end();
+}
+#endif
+
+bool TaskParent::AllChildrenDone() {
+  for (ChildSet::iterator it = children_->begin();
+       it != children_->end();
+       ++it) {
+    if (!(*it)->IsDone())
+      return false;
+  }
+  return true;
+}
+
+bool TaskParent::AnyChildError() {
+  return child_error_;
+}
+
+void TaskParent::AbortAllChildren() {
+  if (children_->size() > 0) {
+#ifdef _DEBUG
+    runner_->IncrementAbortCount();
+#endif
+
+    ChildSet copy = *children_;
+    for (ChildSet::iterator it = copy.begin(); it != copy.end(); ++it) {
+      (*it)->Abort(true);  // Note we do not wake
+    }
+
+#ifdef _DEBUG
+    runner_->DecrementAbortCount();
+#endif
+  }
+}
+
+void TaskParent::OnStopped(Task *task) {
+  AbortAllChildren();
+  parent_->OnChildStopped(task);
+}
+
+void TaskParent::OnChildStopped(Task *child) {
+  if (child->HasError())
+    child_error_ = true;
+  children_->erase(child);
+}
+
+} // namespace talk_base
diff --git a/talk/base/taskparent.h b/talk/base/taskparent.h
new file mode 100644
index 0000000..e2093d6
--- /dev/null
+++ b/talk/base/taskparent.h
@@ -0,0 +1,79 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_TASKPARENT_H__
+#define TALK_BASE_TASKPARENT_H__
+
+#include <set>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/scoped_ptr.h"
+
+namespace talk_base {
+
+class Task;
+class TaskRunner;
+
+class TaskParent {
+ public:
+  TaskParent(Task *derived_instance, TaskParent *parent);
+  explicit TaskParent(TaskRunner *derived_instance);
+  virtual ~TaskParent() { }
+
+  TaskParent *GetParent() { return parent_; }
+  TaskRunner *GetRunner() { return runner_; }
+
+  bool AllChildrenDone();
+  bool AnyChildError();
+#ifdef _DEBUG
+  bool IsChildTask(Task *task);
+#endif
+
+ protected:
+  void OnStopped(Task *task);
+  void AbortAllChildren();
+  TaskParent *parent() {
+    return parent_;
+  }
+
+ private:
+  void Initialize();
+  void OnChildStopped(Task *child);
+  void AddChild(Task *child);
+
+  TaskParent *parent_;
+  TaskRunner *runner_;
+  bool child_error_;
+  typedef std::set<Task *> ChildSet;
+  scoped_ptr<ChildSet> children_;
+  DISALLOW_EVIL_CONSTRUCTORS(TaskParent);
+};
+
+
+} // namespace talk_base
+
+#endif  // TALK_BASE_TASKPARENT_H__
diff --git a/talk/base/taskrunner.cc b/talk/base/taskrunner.cc
new file mode 100644
index 0000000..0c0816c
--- /dev/null
+++ b/talk/base/taskrunner.cc
@@ -0,0 +1,241 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <algorithm>
+
+#include "talk/base/taskrunner.h"
+
+#include "talk/base/common.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/task.h"
+#include "talk/base/logging.h"
+
+namespace talk_base {
+
+TaskRunner::TaskRunner()
+  : TaskParent(this),
+    next_timeout_task_(NULL),
+    tasks_running_(false)
+#ifdef _DEBUG
+    , abort_count_(0),
+    deleting_task_(NULL)
+#endif
+{
+}
+
+TaskRunner::~TaskRunner() {
+  // this kills and deletes children silently!
+  AbortAllChildren();
+  InternalRunTasks(true);
+}
+
+void TaskRunner::StartTask(Task * task) {
+  tasks_.push_back(task);
+
+  // the task we just started could be about to timeout --
+  // make sure our "next timeout task" is correct
+  UpdateTaskTimeout(task, 0);
+
+  WakeTasks();
+}
+
+void TaskRunner::RunTasks() {
+  InternalRunTasks(false);
+}
+
+void TaskRunner::InternalRunTasks(bool in_destructor) {
+  // This shouldn't run while an abort is happening.
+  // If that occurs, then tasks may be deleted in this method,
+  // but pointers to them will still be in the
+  // "ChildSet copy" in TaskParent::AbortAllChildren.
+  // Subsequent use of those task may cause data corruption or crashes.  
+  ASSERT(!abort_count_);
+  // Running continues until all tasks are Blocked (ok for a small # of tasks)
+  if (tasks_running_) {
+    return;  // don't reenter
+  }
+
+  tasks_running_ = true;
+
+  int64 previous_timeout_time = next_task_timeout();
+
+  int did_run = true;
+  while (did_run) {
+    did_run = false;
+    // use indexing instead of iterators because tasks_ may grow
+    for (size_t i = 0; i < tasks_.size(); ++i) {
+      while (!tasks_[i]->Blocked()) {
+        tasks_[i]->Step();
+        did_run = true;
+      }
+    }
+  }
+  // Tasks are deleted when running has paused
+  bool need_timeout_recalc = false;
+  for (size_t i = 0; i < tasks_.size(); ++i) {
+    if (tasks_[i]->IsDone()) {
+      Task* task = tasks_[i];
+      if (next_timeout_task_ &&
+          task->unique_id() == next_timeout_task_->unique_id()) {
+        next_timeout_task_ = NULL;
+        need_timeout_recalc = true;
+      }
+
+#ifdef _DEBUG
+      deleting_task_ = task;
+#endif
+      delete task;
+#ifdef _DEBUG
+      deleting_task_ = NULL;
+#endif
+      tasks_[i] = NULL;
+    }
+  }
+  // Finally, remove nulls
+  std::vector<Task *>::iterator it;
+  it = std::remove(tasks_.begin(),
+                   tasks_.end(),
+                   reinterpret_cast<Task *>(NULL));
+
+  tasks_.erase(it, tasks_.end());
+
+  if (need_timeout_recalc)
+    RecalcNextTimeout(NULL);
+
+  // Make sure that adjustments are done to account
+  // for any timeout changes (but don't call this
+  // while being destroyed since it calls a pure virtual function).
+  if (!in_destructor)
+    CheckForTimeoutChange(previous_timeout_time);
+
+  tasks_running_ = false;
+}
+
+void TaskRunner::PollTasks() {
+  // see if our "next potentially timed-out task" has indeed timed out.
+  // If it has, wake it up, then queue up the next task in line
+  // Repeat while we have new timed-out tasks.
+  // TODO: We need to guard against WakeTasks not updating
+  // next_timeout_task_. Maybe also add documentation in the header file once
+  // we understand this code better.
+  Task* old_timeout_task = NULL;
+  while (next_timeout_task_ &&
+      old_timeout_task != next_timeout_task_ &&
+      next_timeout_task_->TimedOut()) {
+    old_timeout_task = next_timeout_task_;
+    next_timeout_task_->Wake();
+    WakeTasks();
+  }
+}
+
+int64 TaskRunner::next_task_timeout() const {
+  if (next_timeout_task_) {
+    return next_timeout_task_->timeout_time();
+  }
+  return 0;
+}
+
+// this function gets called frequently -- when each task changes
+// state to something other than DONE, ERROR or BLOCKED, it calls
+// ResetTimeout(), which will call this function to make sure that
+// the next timeout-able task hasn't changed.  The logic in this function
+// prevents RecalcNextTimeout() from getting called in most cases,
+// effectively making the task scheduler O-1 instead of O-N
+
+void TaskRunner::UpdateTaskTimeout(Task* task,
+                                   int64 previous_task_timeout_time) {
+  ASSERT(task != NULL);
+  int64 previous_timeout_time = next_task_timeout();
+  bool task_is_timeout_task = next_timeout_task_ != NULL &&
+      task->unique_id() == next_timeout_task_->unique_id();
+  if (task_is_timeout_task) {
+    previous_timeout_time = previous_task_timeout_time;
+  }
+
+  // if the relevant task has a timeout, then
+  // check to see if it's closer than the current
+  // "about to timeout" task
+  if (task->timeout_time()) {
+    if (next_timeout_task_ == NULL ||
+        (task->timeout_time() <= next_timeout_task_->timeout_time())) {
+      next_timeout_task_ = task;
+    }
+  } else if (task_is_timeout_task) {
+    // otherwise, if the task doesn't have a timeout,
+    // and it used to be our "about to timeout" task,
+    // walk through all the tasks looking for the real
+    // "about to timeout" task
+    RecalcNextTimeout(task);
+  }
+
+  // Note when task_running_, then the running routine
+  // (TaskRunner::InternalRunTasks) is responsible for calling
+  // CheckForTimeoutChange.
+  if (!tasks_running_) {
+    CheckForTimeoutChange(previous_timeout_time);
+  }
+}
+
+void TaskRunner::RecalcNextTimeout(Task *exclude_task) {
+  // walk through all the tasks looking for the one
+  // which satisfies the following:
+  //   it's not finished already
+  //   we're not excluding it
+  //   it has the closest timeout time
+
+  int64 next_timeout_time = 0;
+  next_timeout_task_ = NULL;
+
+  for (size_t i = 0; i < tasks_.size(); ++i) {
+    Task *task = tasks_[i];
+    // if the task isn't complete, and it actually has a timeout time
+    if (!task->IsDone() && (task->timeout_time() > 0))
+      // if it doesn't match our "exclude" task
+      if (exclude_task == NULL ||
+          exclude_task->unique_id() != task->unique_id())
+        // if its timeout time is sooner than our current timeout time
+        if (next_timeout_time == 0 ||
+            task->timeout_time() <= next_timeout_time) {
+          // set this task as our next-to-timeout
+          next_timeout_time = task->timeout_time();
+          next_timeout_task_ = task;
+        }
+  }
+}
+
+void TaskRunner::CheckForTimeoutChange(int64 previous_timeout_time) {
+  int64 next_timeout = next_task_timeout();
+  bool timeout_change = (previous_timeout_time == 0 && next_timeout != 0) ||
+      next_timeout < previous_timeout_time ||
+      (previous_timeout_time <= CurrentTime() &&
+       previous_timeout_time != next_timeout);
+  if (timeout_change) {
+    OnTimeoutChange();
+  }
+}
+
+} // namespace talk_base
diff --git a/talk/base/taskrunner.h b/talk/base/taskrunner.h
new file mode 100644
index 0000000..f34a609
--- /dev/null
+++ b/talk/base/taskrunner.h
@@ -0,0 +1,117 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_TASKRUNNER_H__
+#define TALK_BASE_TASKRUNNER_H__
+
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/taskparent.h"
+
+namespace talk_base {
+class Task;
+
+const int64 kSecToMsec = 1000;
+const int64 kMsecTo100ns = 10000;
+const int64 kSecTo100ns = kSecToMsec * kMsecTo100ns;
+
+class TaskRunner : public TaskParent, public sigslot::has_slots<> {
+ public:
+  TaskRunner();
+  virtual ~TaskRunner();
+
+  virtual void WakeTasks() = 0;
+
+  // Returns the current time in 100ns units.  It is used for
+  // determining timeouts.  The origin is not important, only
+  // the units and that rollover while the computer is running.
+  //
+  // On Windows, GetSystemTimeAsFileTime is the typical implementation.
+  virtual int64 CurrentTime() = 0 ;
+
+  void StartTask(Task *task);
+  void RunTasks();
+  void PollTasks();
+
+  void UpdateTaskTimeout(Task *task, int64 previous_task_timeout_time);
+
+#ifdef _DEBUG
+  bool is_ok_to_delete(Task* task) {
+    return task == deleting_task_;
+  }
+
+  void IncrementAbortCount() {
+    ++abort_count_;
+  }
+
+  void DecrementAbortCount() {
+    --abort_count_;
+  }
+#endif
+
+  // Returns the next absolute time when a task times out
+  // OR "0" if there is no next timeout.
+  int64 next_task_timeout() const;
+
+ protected:
+  // The primary usage of this method is to know if
+  // a callback timer needs to be set-up or adjusted.
+  // This method will be called
+  //  * when the next_task_timeout() becomes a smaller value OR
+  //  * when next_task_timeout() has changed values and the previous
+  //    value is in the past.
+  //
+  // If the next_task_timeout moves to the future, this method will *not*
+  // get called (because it subclass should check next_task_timeout()
+  // when its timer goes off up to see if it needs to set-up a new timer).
+  //
+  // Note that this maybe called conservatively.  In that it may be
+  // called when no time change has happened.
+  virtual void OnTimeoutChange() {
+    // by default, do nothing.
+  }
+
+ private:
+  void InternalRunTasks(bool in_destructor);
+  void CheckForTimeoutChange(int64 previous_timeout_time);
+
+  std::vector<Task *> tasks_;
+  Task *next_timeout_task_;
+  bool tasks_running_;
+#ifdef _DEBUG
+  int abort_count_;
+  Task* deleting_task_;
+#endif
+
+  void RecalcNextTimeout(Task *exclude_task);
+};
+
+} // namespace talk_base
+
+#endif  // TASK_BASE_TASKRUNNER_H__
diff --git a/talk/base/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..0e7625f
--- /dev/null
+++ b/talk/base/testclient.cc
@@ -0,0 +1,155 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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), ready_to_send_(false) {
+  packets_ = new std::vector<Packet*>();
+  socket_->SignalReadPacket.connect(this, &TestClient::OnPacket);
+  socket_->SignalReadyToSend.connect(this, &TestClient::OnReadyToSend);
+}
+
+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;
+}
+
+int TestClient::GetError() {
+  return socket_->GetError();
+}
+
+int TestClient::SetOption(Socket::Option opt, int value) {
+  return socket_->SetOption(opt, value);
+}
+
+bool TestClient::ready_to_send() const {
+  return ready_to_send_;
+}
+
+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));
+}
+
+void TestClient::OnReadyToSend(AsyncPacketSocket* socket) {
+  ready_to_send_ = true;
+}
+
+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..1e1780a
--- /dev/null
+++ b/talk/base/testclient.h
@@ -0,0 +1,109 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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();
+
+  int GetError();
+  int SetOption(Socket::Option opt, int value);
+
+  bool ready_to_send() const;
+
+ 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);
+  void OnReadyToSend(AsyncPacketSocket* socket);
+
+  CriticalSection crit_;
+  AsyncPacketSocket* socket_;
+  std::vector<Packet*>* packets_;
+  bool ready_to_send_;
+  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..1269236
--- /dev/null
+++ b/talk/base/testclient_unittest.cc
@@ -0,0 +1,95 @@
+/*
+ * 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/nethelpers.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;
+
+void TestUdpInternal(const SocketAddress& loopback) {
+  Thread *main = Thread::Current();
+  AsyncSocket* socket = main->socketserver()
+      ->CreateAsyncSocket(loopback.family(), SOCK_DGRAM);
+  socket->Bind(loopback);
+
+  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());
+}
+
+void TestTcpInternal(const SocketAddress& loopback) {
+  Thread *main = Thread::Current();
+  TestEchoServer server(main, loopback);
+
+  AsyncSocket* socket = main->socketserver()
+      ->CreateAsyncSocket(loopback.family(), SOCK_STREAM);
+  AsyncTCPSocket* tcp_socket = AsyncTCPSocket::Create(
+      socket, loopback, 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());
+}
+
+// Tests whether the TestClient can send UDP to itself.
+TEST(TestClientTest, TestUdpIPv4) {
+  TestUdpInternal(SocketAddress("127.0.0.1", 0));
+}
+
+TEST(TestClientTest, TestUdpIPv6) {
+  if (HasIPv6Enabled()) {
+    TestUdpInternal(SocketAddress("::1", 0));
+  } else {
+    LOG(LS_INFO) << "Skipping IPv6 test.";
+  }
+}
+
+// Tests whether the TestClient can connect to a server and exchange data.
+TEST(TestClientTest, TestTcpIPv4) {
+  TestTcpInternal(SocketAddress("127.0.0.1", 0));
+}
+
+TEST(TestClientTest, TestTcpIPv6) {
+  if (HasIPv6Enabled()) {
+    TestTcpInternal(SocketAddress("::1", 0));
+  } else {
+    LOG(LS_INFO) << "Skipping IPv6 test.";
+  }
+}
diff --git a/talk/base/testechoserver.h b/talk/base/testechoserver.h
new file mode 100644
index 0000000..9bb5178
--- /dev/null
+++ b/talk/base/testechoserver.h
@@ -0,0 +1,88 @@
+/*
+ * 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_TESTECHOSERVER_H_
+#define TALK_BASE_TESTECHOSERVER_H_
+
+#include <list>
+#include "talk/base/asynctcpsocket.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+// A test echo server, echoes back any packets sent to it.
+// Useful for unit tests.
+class TestEchoServer : public sigslot::has_slots<> {
+ public:
+  TestEchoServer(Thread* thread, const SocketAddress& addr)
+      : server_socket_(thread->socketserver()->CreateAsyncSocket(addr.family(),
+                                                                 SOCK_STREAM)) {
+    server_socket_->Bind(addr);
+    server_socket_->Listen(5);
+    server_socket_->SignalReadEvent.connect(this, &TestEchoServer::OnAccept);
+  }
+  ~TestEchoServer() {
+    for (ClientList::iterator it = client_sockets_.begin();
+         it != client_sockets_.end(); ++it) {
+      delete *it;
+    }
+  }
+
+  SocketAddress address() const { return server_socket_->GetLocalAddress(); }
+
+ private:
+  void OnAccept(AsyncSocket* socket) {
+    AsyncSocket* raw_socket = socket->Accept(NULL);
+    if (raw_socket) {
+      AsyncTCPSocket* packet_socket = new AsyncTCPSocket(raw_socket, false);
+      packet_socket->SignalReadPacket.connect(this, &TestEchoServer::OnPacket);
+      packet_socket->SignalClose.connect(this, &TestEchoServer::OnClose);
+      client_sockets_.push_back(packet_socket);
+    }
+  }
+  void OnPacket(AsyncPacketSocket* socket, const char* buf, size_t size,
+                const SocketAddress& remote_addr) {
+    socket->Send(buf, size);
+  }
+  void OnClose(AsyncPacketSocket* socket, int err) {
+    ClientList::iterator it =
+        std::find(client_sockets_.begin(), client_sockets_.end(), socket);
+    client_sockets_.erase(it);
+    Thread::Current()->Dispose(socket);
+  }
+
+  typedef std::list<AsyncTCPSocket*> ClientList;
+  scoped_ptr<AsyncSocket> server_socket_;
+  ClientList client_sockets_;
+  DISALLOW_EVIL_CONSTRUCTORS(TestEchoServer);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_TESTECHOSERVER_H_
diff --git a/talk/base/testutils.h b/talk/base/testutils.h
new file mode 100644
index 0000000..769d95f
--- /dev/null
+++ b/talk/base/testutils.h
@@ -0,0 +1,570 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_TESTUTILS_H__
+#define TALK_BASE_TESTUTILS_H__
+
+// Utilities for testing talk_base infrastructure in unittests
+
+#include <map>
+#include <vector>
+#include "talk/base/asyncsocket.h"
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/base/nethelpers.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+
+namespace testing {
+
+using namespace talk_base;
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamSink - Monitor asynchronously signalled events from StreamInterface
+// or AsyncSocket (which should probably be a StreamInterface.
+///////////////////////////////////////////////////////////////////////////////
+
+// Note: Any event that is an error is treaded as SSE_ERROR instead of that
+// event.
+
+enum StreamSinkEvent {
+  SSE_OPEN  = SE_OPEN,
+  SSE_READ  = SE_READ,
+  SSE_WRITE = SE_WRITE,
+  SSE_CLOSE = SE_CLOSE,
+  SSE_ERROR = 16
+};
+
+class StreamSink : public sigslot::has_slots<> {
+ public:
+  void Monitor(StreamInterface* stream) {
+   stream->SignalEvent.connect(this, &StreamSink::OnEvent);
+   events_.erase(stream);
+  }
+  void Unmonitor(StreamInterface* stream) {
+   stream->SignalEvent.disconnect(this);
+   // In case you forgot to unmonitor a previous object with this address
+   events_.erase(stream);
+  }
+  bool Check(StreamInterface* stream, StreamSinkEvent event, bool reset = true) {
+    return DoCheck(stream, event, reset);
+  }
+  int Events(StreamInterface* stream, bool reset = true) {
+    return DoEvents(stream, reset);
+  }
+
+  void Monitor(AsyncSocket* socket) {
+   socket->SignalConnectEvent.connect(this, &StreamSink::OnConnectEvent);
+   socket->SignalReadEvent.connect(this, &StreamSink::OnReadEvent);
+   socket->SignalWriteEvent.connect(this, &StreamSink::OnWriteEvent);
+   socket->SignalCloseEvent.connect(this, &StreamSink::OnCloseEvent);
+   // In case you forgot to unmonitor a previous object with this address
+   events_.erase(socket);
+  }
+  void Unmonitor(AsyncSocket* socket) {
+   socket->SignalConnectEvent.disconnect(this);
+   socket->SignalReadEvent.disconnect(this);
+   socket->SignalWriteEvent.disconnect(this);
+   socket->SignalCloseEvent.disconnect(this);
+   events_.erase(socket);
+  }
+  bool Check(AsyncSocket* socket, StreamSinkEvent event, bool reset = true) {
+    return DoCheck(socket, event, reset);
+  }
+  int Events(AsyncSocket* socket, bool reset = true) {
+    return DoEvents(socket, reset);
+  }
+
+ private:
+  typedef std::map<void*,int> EventMap;
+
+  void OnEvent(StreamInterface* stream, int events, int error) {
+    if (error) {
+      events = SSE_ERROR;
+    }
+    AddEvents(stream, events);
+  }
+  void OnConnectEvent(AsyncSocket* socket) {
+    AddEvents(socket, SSE_OPEN);
+  }
+  void OnReadEvent(AsyncSocket* socket) {
+    AddEvents(socket, SSE_READ);
+  }
+  void OnWriteEvent(AsyncSocket* socket) {
+    AddEvents(socket, SSE_WRITE);
+  }
+  void OnCloseEvent(AsyncSocket* socket, int error) {
+    AddEvents(socket, (0 == error) ? SSE_CLOSE : SSE_ERROR);
+  }
+
+  void AddEvents(void* obj, int events) {
+    EventMap::iterator it = events_.find(obj);
+    if (events_.end() == it) {
+      events_.insert(EventMap::value_type(obj, events));
+    } else {
+      it->second |= events;
+    }
+  }
+  bool DoCheck(void* obj, StreamSinkEvent event, bool reset) {
+    EventMap::iterator it = events_.find(obj);
+    if ((events_.end() == it) || (0 == (it->second & event))) {
+      return false;
+    }
+    if (reset) {
+      it->second &= ~event;
+    }
+    return true;
+  }
+  int DoEvents(void* obj, bool reset) {
+    EventMap::iterator it = events_.find(obj);
+    if (events_.end() == it)
+      return 0;
+    int events = it->second;
+    if (reset) {
+      it->second = 0;
+    }
+    return events;
+  }
+
+  EventMap events_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamSource - Implements stream interface and simulates asynchronous
+// events on the stream, without a network.  Also buffers written data.
+///////////////////////////////////////////////////////////////////////////////
+
+class StreamSource : public StreamInterface {
+public:
+  StreamSource() {
+    Clear();
+  }
+
+  void Clear() {
+    readable_data_.clear();
+    written_data_.clear();
+    state_ = SS_CLOSED;
+    read_block_ = 0;
+    write_block_ = SIZE_UNKNOWN;
+  }
+  void QueueString(const char* data) {
+    QueueData(data, strlen(data));
+  }
+  void QueueStringF(const char* format, ...) {
+    va_list args;
+    va_start(args, format);
+    char buffer[1024];
+    size_t len = vsprintfn(buffer, sizeof(buffer), format, args);
+    ASSERT(len < sizeof(buffer) - 1);
+    va_end(args);
+    QueueData(buffer, len);
+  }
+  void QueueData(const char* data, size_t len) {
+    readable_data_.insert(readable_data_.end(), data, data + len);
+    if ((SS_OPEN == state_) && (readable_data_.size() == len)) {
+      SignalEvent(this, SE_READ, 0);
+    }
+  }
+  std::string ReadData() {
+    std::string data;
+    // avoid accessing written_data_[0] if it is undefined
+    if (written_data_.size() > 0) {
+      data.insert(0, &written_data_[0], written_data_.size());
+    }
+    written_data_.clear();
+    return data;
+  }
+  void SetState(StreamState state) {
+    int events = 0;
+    if ((SS_OPENING == state_) && (SS_OPEN == state)) {
+      events |= SE_OPEN;
+      if (!readable_data_.empty()) {
+        events |= SE_READ;
+      }
+    } else if ((SS_CLOSED != state_) && (SS_CLOSED == state)) {
+      events |= SE_CLOSE;
+    }
+    state_ = state;
+    if (events) {
+      SignalEvent(this, events, 0);
+    }
+  }
+  // Will cause Read to block when there are pos bytes in the read queue.
+  void SetReadBlock(size_t pos) { read_block_ = pos; }
+  // Will cause Write to block when there are pos bytes in the write queue.
+  void SetWriteBlock(size_t pos) { write_block_ = pos; }
+
+  virtual StreamState GetState() const { return state_; }
+  virtual StreamResult Read(void* buffer, size_t buffer_len,
+                            size_t* read, int* error) {
+    if (SS_CLOSED == state_) {
+      if (error) *error = -1;
+      return SR_ERROR;
+    }
+    if ((SS_OPENING == state_) || (readable_data_.size() <= read_block_)) {
+      return SR_BLOCK;
+    }
+    size_t count = _min(buffer_len, readable_data_.size() - read_block_);
+    memcpy(buffer, &readable_data_[0], count);
+    size_t new_size = readable_data_.size() - count;
+    // Avoid undefined access beyond the last element of the vector.
+    // This only happens when new_size is 0.
+    if (count < readable_data_.size()) {
+      memmove(&readable_data_[0], &readable_data_[count], new_size);
+    }
+    readable_data_.resize(new_size);
+    if (read) *read = count;
+    return SR_SUCCESS;
+  }
+  virtual StreamResult Write(const void* data, size_t data_len,
+                             size_t* written, int* error) {
+    if (SS_CLOSED == state_) {
+      if (error) *error = -1;
+      return SR_ERROR;
+    }
+    if (SS_OPENING == state_) {
+      return SR_BLOCK;
+    }
+    if (SIZE_UNKNOWN != write_block_) {
+      if (written_data_.size() >= write_block_) {
+        return SR_BLOCK;
+      }
+      if (data_len > (write_block_ - written_data_.size())) {
+        data_len = write_block_ - written_data_.size();
+      }
+    }
+    if (written) *written = data_len;
+    const char* cdata = static_cast<const char*>(data);
+    written_data_.insert(written_data_.end(), cdata, cdata + data_len);
+    return SR_SUCCESS;
+  }
+  virtual void Close() { state_ = SS_CLOSED; }
+
+private:
+  typedef std::vector<char> Buffer;
+  Buffer readable_data_, written_data_;
+  StreamState state_;
+  size_t read_block_, write_block_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// SocketTestClient
+// Creates a simulated client for testing.  Works on real and virtual networks.
+///////////////////////////////////////////////////////////////////////////////
+
+class SocketTestClient : public sigslot::has_slots<> {
+public:
+  SocketTestClient() {
+    Init(NULL, AF_INET);
+  }
+  SocketTestClient(AsyncSocket* socket) {
+    Init(socket, socket->GetLocalAddress().family());
+  }
+  SocketTestClient(const SocketAddress& address) {
+    Init(NULL, address.family());
+    socket_->Connect(address);
+  }
+
+  AsyncSocket* socket() { return socket_.get(); }
+
+  void QueueString(const char* data) {
+    QueueData(data, strlen(data));
+  }
+  void QueueStringF(const char* format, ...) {
+    va_list args;
+    va_start(args, format);
+    char buffer[1024];
+    size_t len = vsprintfn(buffer, sizeof(buffer), format, args);
+    ASSERT(len < sizeof(buffer) - 1);
+    va_end(args);
+    QueueData(buffer, len);
+  }
+  void QueueData(const char* data, size_t len) {
+    send_buffer_.insert(send_buffer_.end(), data, data + len);
+    if (Socket::CS_CONNECTED == socket_->GetState()) {
+      Flush();
+    }
+  }
+  std::string ReadData() {
+    std::string data(&recv_buffer_[0], recv_buffer_.size());
+    recv_buffer_.clear();
+    return data;
+  }
+
+  bool IsConnected() const {
+    return (Socket::CS_CONNECTED == socket_->GetState());
+  }
+  bool IsClosed() const {
+    return (Socket::CS_CLOSED == socket_->GetState());
+  }
+
+private:
+  typedef std::vector<char> Buffer;
+
+  void Init(AsyncSocket* socket, int family) {
+    if (!socket) {
+      socket = Thread::Current()->socketserver()
+          ->CreateAsyncSocket(family, SOCK_STREAM);
+    }
+    socket_.reset(socket);
+    socket_->SignalConnectEvent.connect(this,
+      &SocketTestClient::OnConnectEvent);
+    socket_->SignalReadEvent.connect(this, &SocketTestClient::OnReadEvent);
+    socket_->SignalWriteEvent.connect(this, &SocketTestClient::OnWriteEvent);
+    socket_->SignalCloseEvent.connect(this, &SocketTestClient::OnCloseEvent);
+  }
+
+  void Flush() {
+    size_t sent = 0;
+    while (sent < send_buffer_.size()) {
+      int result = socket_->Send(&send_buffer_[sent],
+                                 send_buffer_.size() - sent);
+      if (result > 0) {
+        sent += result;
+      } else {
+        break;
+      }
+    }
+    size_t new_size = send_buffer_.size() - sent;
+    memmove(&send_buffer_[0], &send_buffer_[sent], new_size);
+    send_buffer_.resize(new_size);
+  }
+
+  void OnConnectEvent(AsyncSocket* socket) {
+    if (!send_buffer_.empty()) {
+      Flush();
+    }
+  }
+  void OnReadEvent(AsyncSocket* socket) {
+    char data[64 * 1024];
+    int result = socket_->Recv(data, ARRAY_SIZE(data));
+    if (result > 0) {
+      recv_buffer_.insert(recv_buffer_.end(), data, data + result);
+    }
+  }
+  void OnWriteEvent(AsyncSocket* socket) {
+    if (!send_buffer_.empty()) {
+      Flush();
+    }
+  }
+  void OnCloseEvent(AsyncSocket* socket, int error) {
+  }
+
+  scoped_ptr<AsyncSocket> socket_;
+  Buffer send_buffer_, recv_buffer_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// SocketTestServer
+// Creates a simulated server for testing.  Works on real and virtual networks.
+///////////////////////////////////////////////////////////////////////////////
+
+class SocketTestServer : public sigslot::has_slots<> {
+ public:
+  SocketTestServer(const SocketAddress& address)
+      : socket_(Thread::Current()->socketserver()
+                ->CreateAsyncSocket(address.family(), SOCK_STREAM))
+  {
+    socket_->SignalReadEvent.connect(this, &SocketTestServer::OnReadEvent);
+    socket_->Bind(address);
+    socket_->Listen(5);
+  }
+  virtual ~SocketTestServer() {
+    clear();
+  }
+
+  size_t size() const { return clients_.size(); }
+  SocketTestClient* client(size_t index) const { return clients_[index]; }
+  SocketTestClient* operator[](size_t index) const { return client(index); }
+
+  void clear() {
+    for (size_t i=0; i<clients_.size(); ++i) {
+      delete clients_[i];
+    }
+    clients_.clear();
+  }
+
+ private:
+  void OnReadEvent(AsyncSocket* socket) {
+    AsyncSocket* accepted =
+      static_cast<AsyncSocket*>(socket_->Accept(NULL));
+    if (!accepted)
+      return;
+    clients_.push_back(new SocketTestClient(accepted));
+  }
+
+  scoped_ptr<AsyncSocket> socket_;
+  std::vector<SocketTestClient*> clients_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Generic Utilities
+///////////////////////////////////////////////////////////////////////////////
+
+inline bool ReadFile(const char* filename, std::string* contents) {
+  FILE* fp = fopen(filename, "rb");
+  if (!fp)
+    return false;
+  char buffer[1024*64];
+  size_t read;
+  contents->clear();
+  while ((read = fread(buffer, 1, sizeof(buffer), fp))) {
+    contents->append(buffer, read);
+  }
+  bool success = (0 != feof(fp));
+  fclose(fp);
+  return success;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Unittest predicates which are similar to STREQ, but for raw memory
+///////////////////////////////////////////////////////////////////////////////
+
+inline AssertionResult CmpHelperMemEq(const char* expected_expression,
+                                      const char* expected_length_expression,
+                                      const char* actual_expression,
+                                      const char* actual_length_expression,
+                                      const void* expected,
+                                      size_t expected_length,
+                                      const void* actual,
+                                      size_t actual_length)
+{
+  if ((expected_length == actual_length)
+      && (0 == memcmp(expected, actual, expected_length))) {
+    return AssertionSuccess();
+  }
+
+  Message msg;
+  msg << "Value of: " << actual_expression
+      << " [" << actual_length_expression << "]";
+  if (true) {  //!actual_value.Equals(actual_expression)) {
+    size_t buffer_size = actual_length * 2 + 1;
+    char* buffer = STACK_ARRAY(char, buffer_size);
+    hex_encode(buffer, buffer_size,
+               reinterpret_cast<const char*>(actual), actual_length);
+    msg << "\n  Actual: " << buffer << " [" << actual_length << "]";
+  }
+
+  msg << "\nExpected: " << expected_expression
+      << " [" << expected_length_expression << "]";
+  if (true) {  //!expected_value.Equals(expected_expression)) {
+    size_t buffer_size = expected_length * 2 + 1;
+    char* buffer = STACK_ARRAY(char, buffer_size);
+    hex_encode(buffer, buffer_size,
+               reinterpret_cast<const char*>(expected), expected_length);
+    msg << "\nWhich is: " << buffer << " [" << expected_length << "]";
+  }
+
+  return AssertionFailure(msg);
+}
+
+inline AssertionResult CmpHelperFileEq(const char* expected_expression,
+                                       const char* expected_length_expression,
+                                       const char* actual_filename,
+                                       const void* expected,
+                                       size_t expected_length,
+                                       const char* filename)
+{
+  std::string contents;
+  if (!ReadFile(filename, &contents)) {
+    Message msg;
+    msg << "File '" << filename << "' could not be read.";
+    return AssertionFailure(msg);
+  }
+  return CmpHelperMemEq(expected_expression, expected_length_expression,
+                        actual_filename, "",
+                        expected, expected_length,
+                        contents.c_str(), contents.size());
+}
+
+#define EXPECT_MEMEQ(expected, expected_length, actual, actual_length) \
+  EXPECT_PRED_FORMAT4(::testing::CmpHelperMemEq, expected, expected_length, \
+                      actual, actual_length)
+
+#define ASSERT_MEMEQ(expected, expected_length, actual, actual_length) \
+  ASSERT_PRED_FORMAT4(::testing::CmpHelperMemEq, expected, expected_length, \
+                      actual, actual_length)
+
+#define EXPECT_FILEEQ(expected, expected_length, filename) \
+  EXPECT_PRED_FORMAT3(::testing::CmpHelperFileEq, expected, expected_length, \
+                      filename)
+
+#define ASSERT_FILEEQ(expected, expected_length, filename) \
+  ASSERT_PRED_FORMAT3(::testing::CmpHelperFileEq, expected, expected_length, \
+                      filename)
+
+///////////////////////////////////////////////////////////////////////////////
+// Helpers for initializing constant memory with integers in a particular byte
+// order
+///////////////////////////////////////////////////////////////////////////////
+
+#define BYTE_CAST(x) static_cast<uint8>((x) & 0xFF)
+
+// Declare a N-bit integer as a little-endian sequence of bytes
+#define LE16(x) BYTE_CAST(((uint16)x) >>  0), BYTE_CAST(((uint16)x) >>  8)
+
+#define LE32(x) BYTE_CAST(((uint32)x) >>  0), BYTE_CAST(((uint32)x) >>  8), \
+                BYTE_CAST(((uint32)x) >> 16), BYTE_CAST(((uint32)x) >> 24)
+
+#define LE64(x) BYTE_CAST(((uint64)x) >>  0), BYTE_CAST(((uint64)x) >>  8), \
+                BYTE_CAST(((uint64)x) >> 16), BYTE_CAST(((uint64)x) >> 24), \
+                BYTE_CAST(((uint64)x) >> 32), BYTE_CAST(((uint64)x) >> 40), \
+                BYTE_CAST(((uint64)x) >> 48), BYTE_CAST(((uint64)x) >> 56)
+
+// Declare a N-bit integer as a big-endian (Internet) sequence of bytes
+#define BE16(x) BYTE_CAST(((uint16)x) >>  8), BYTE_CAST(((uint16)x) >>  0)
+
+#define BE32(x) BYTE_CAST(((uint32)x) >> 24), BYTE_CAST(((uint32)x) >> 16), \
+                BYTE_CAST(((uint32)x) >>  8), BYTE_CAST(((uint32)x) >>  0)
+
+#define BE64(x) BYTE_CAST(((uint64)x) >> 56), BYTE_CAST(((uint64)x) >> 48), \
+                BYTE_CAST(((uint64)x) >> 40), BYTE_CAST(((uint64)x) >> 32), \
+                BYTE_CAST(((uint64)x) >> 24), BYTE_CAST(((uint64)x) >> 16), \
+                BYTE_CAST(((uint64)x) >>  8), BYTE_CAST(((uint64)x) >>  0)
+
+// Declare a N-bit integer as a this-endian (local machine) sequence of bytes
+#ifndef BIG_ENDIAN
+#define BIG_ENDIAN 1
+#endif  // BIG_ENDIAN
+
+#if BIG_ENDIAN
+#define TE16 BE16
+#define TE32 BE32
+#define TE64 BE64
+#else  // !BIG_ENDIAN
+#define TE16 LE16
+#define TE32 LE32
+#define TE64 LE64
+#endif  // !BIG_ENDIAN
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace testing
+
+#endif  // TALK_BASE_TESTUTILS_H__
diff --git a/talk/base/thread.cc b/talk/base/thread.cc
new file mode 100644
index 0000000..d21d5f1
--- /dev/null
+++ b/talk/base/thread.cc
@@ -0,0 +1,582 @@
+/*
+ * 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/thread.h"
+
+#ifndef __has_feature
+#define __has_feature(x) 0  // Compatibility with non-clang or LLVM compilers.
+#endif  // __has_feature
+
+#if defined(WIN32)
+#include <comdef.h>
+#elif defined(POSIX)
+#include <time.h>
+#endif
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/timeutils.h"
+
+#if !__has_feature(objc_arc) && (defined(OSX) || defined(IOS))
+#include "talk/base/maccocoathreadhelper.h"
+#include "talk/base/scoped_autorelease_pool.h"
+#endif
+
+namespace talk_base {
+
+ThreadManager* ThreadManager::Instance() {
+  LIBJINGLE_DEFINE_STATIC_LOCAL(ThreadManager, thread_manager, ());
+  return &thread_manager;
+}
+
+// static
+Thread* Thread::Current() {
+  return ThreadManager::Instance()->CurrentThread();
+}
+
+#ifdef POSIX
+ThreadManager::ThreadManager() {
+  pthread_key_create(&key_, NULL);
+#ifndef NO_MAIN_THREAD_WRAPPING
+  WrapCurrentThread();
+#endif
+#if !__has_feature(objc_arc) && (defined(OSX) || defined(IOS))
+  // Under Automatic Reference Counting (ARC), you cannot use autorelease pools
+  // directly. Instead, you use @autoreleasepool blocks instead.  Also, we are
+  // maintaining thread safety using immutability within context of GCD dispatch
+  // queues in this case.
+  InitCocoaMultiThreading();
+#endif
+}
+
+ThreadManager::~ThreadManager() {
+#if __has_feature(objc_arc)
+  @autoreleasepool
+#elif defined(OSX) || defined(IOS)
+  // This is called during exit, at which point apparently no NSAutoreleasePools
+  // are available; but we might still need them to do cleanup (or we get the
+  // "no autoreleasepool in place, just leaking" warning when exiting).
+  ScopedAutoreleasePool pool;
+#endif
+  {
+    UnwrapCurrentThread();
+    pthread_key_delete(key_);
+  }
+}
+
+Thread *ThreadManager::CurrentThread() {
+  return static_cast<Thread *>(pthread_getspecific(key_));
+}
+
+void ThreadManager::SetCurrentThread(Thread *thread) {
+  pthread_setspecific(key_, thread);
+}
+#endif
+
+#ifdef WIN32
+ThreadManager::ThreadManager() {
+  key_ = TlsAlloc();
+#ifndef NO_MAIN_THREAD_WRAPPING
+  WrapCurrentThread();
+#endif
+}
+
+ThreadManager::~ThreadManager() {
+  UnwrapCurrentThread();
+  TlsFree(key_);
+}
+
+Thread *ThreadManager::CurrentThread() {
+  return static_cast<Thread *>(TlsGetValue(key_));
+}
+
+void ThreadManager::SetCurrentThread(Thread *thread) {
+  TlsSetValue(key_, thread);
+}
+#endif
+
+Thread *ThreadManager::WrapCurrentThread() {
+  Thread* result = CurrentThread();
+  if (NULL == result) {
+    result = new Thread();
+    result->WrapCurrentWithThreadManager(this);
+  }
+  return result;
+}
+
+void ThreadManager::UnwrapCurrentThread() {
+  Thread* t = CurrentThread();
+  if (t && !(t->IsOwned())) {
+    t->UnwrapCurrent();
+    delete t;
+  }
+}
+
+struct ThreadInit {
+  Thread* thread;
+  Runnable* runnable;
+};
+
+Thread::Thread(SocketServer* ss)
+    : MessageQueue(ss),
+      priority_(PRIORITY_NORMAL),
+      started_(false),
+      has_sends_(false),
+#if defined(WIN32)
+      thread_(NULL),
+      thread_id_(0),
+#endif
+      owned_(true),
+      delete_self_when_complete_(false) {
+  SetName("Thread", this);  // default name
+}
+
+Thread::~Thread() {
+  Stop();
+  if (active_)
+    Clear(NULL);
+}
+
+bool Thread::SleepMs(int milliseconds) {
+#ifdef WIN32
+  ::Sleep(milliseconds);
+  return true;
+#else
+  // POSIX has both a usleep() and a nanosleep(), but the former is deprecated,
+  // so we use nanosleep() even though it has greater precision than necessary.
+  struct timespec ts;
+  ts.tv_sec = milliseconds / 1000;
+  ts.tv_nsec = (milliseconds % 1000) * 1000000;
+  int ret = nanosleep(&ts, NULL);
+  if (ret != 0) {
+    LOG_ERR(LS_WARNING) << "nanosleep() returning early";
+    return false;
+  }
+  return true;
+#endif
+}
+
+bool Thread::SetName(const std::string& name, const void* obj) {
+  if (started_) return false;
+  name_ = name;
+  if (obj) {
+    char buf[16];
+    sprintfn(buf, sizeof(buf), " 0x%p", obj);
+    name_ += buf;
+  }
+  return true;
+}
+
+bool Thread::SetPriority(ThreadPriority priority) {
+#if defined(WIN32)
+  if (started_) {
+    BOOL ret = FALSE;
+    if (priority == PRIORITY_NORMAL) {
+      ret = ::SetThreadPriority(thread_, THREAD_PRIORITY_NORMAL);
+    } else if (priority == PRIORITY_HIGH) {
+      ret = ::SetThreadPriority(thread_, THREAD_PRIORITY_HIGHEST);
+    } else if (priority == PRIORITY_ABOVE_NORMAL) {
+      ret = ::SetThreadPriority(thread_, THREAD_PRIORITY_ABOVE_NORMAL);
+    } else if (priority == PRIORITY_IDLE) {
+      ret = ::SetThreadPriority(thread_, THREAD_PRIORITY_IDLE);
+    }
+    if (!ret) {
+      return false;
+    }
+  }
+  priority_ = priority;
+  return true;
+#else
+  // TODO: Implement for Linux/Mac if possible.
+  if (started_) return false;
+  priority_ = priority;
+  return true;
+#endif
+}
+
+bool Thread::Start(Runnable* runnable) {
+  ASSERT(owned_);
+  if (!owned_) return false;
+  ASSERT(!started_);
+  if (started_) return false;
+
+  Restart();  // reset fStop_ if the thread is being restarted
+
+  // 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;
+#if defined(WIN32)
+  DWORD flags = 0;
+  if (priority_ != PRIORITY_NORMAL) {
+    flags = CREATE_SUSPENDED;
+  }
+  thread_ = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PreRun, init, flags,
+                         &thread_id_);
+  if (thread_) {
+    started_ = true;
+    if (priority_ != PRIORITY_NORMAL) {
+      SetPriority(priority_);
+      ::ResumeThread(thread_);
+    }
+  } else {
+    return false;
+  }
+#elif defined(POSIX)
+  pthread_attr_t attr;
+  pthread_attr_init(&attr);
+  if (priority_ != PRIORITY_NORMAL) {
+    if (priority_ == PRIORITY_IDLE) {
+      // There is no POSIX-standard way to set a below-normal priority for an
+      // individual thread (only whole process), so let's not support it.
+      LOG(LS_WARNING) << "PRIORITY_IDLE not supported";
+    } else {
+      // Set real-time round-robin policy.
+      if (pthread_attr_setschedpolicy(&attr, SCHED_RR) != 0) {
+        LOG(LS_ERROR) << "pthread_attr_setschedpolicy";
+      }
+      struct sched_param param;
+      if (pthread_attr_getschedparam(&attr, &param) != 0) {
+        LOG(LS_ERROR) << "pthread_attr_getschedparam";
+      } else {
+        // The numbers here are arbitrary.
+        if (priority_ == PRIORITY_HIGH) {
+          param.sched_priority = 6;           // 6 = HIGH
+        } else {
+          ASSERT(priority_ == PRIORITY_ABOVE_NORMAL);
+          param.sched_priority = 4;           // 4 = ABOVE_NORMAL
+        }
+        if (pthread_attr_setschedparam(&attr, &param) != 0) {
+          LOG(LS_ERROR) << "pthread_attr_setschedparam";
+        }
+      }
+    }
+  }
+  int error_code = pthread_create(&thread_, &attr, PreRun, init);
+  if (0 != error_code) {
+    LOG(LS_ERROR) << "Unable to create pthread, error " << error_code;
+    return false;
+  }
+  started_ = true;
+#endif
+  return true;
+}
+
+void Thread::Join() {
+  if (started_) {
+    ASSERT(!IsCurrent());
+#if defined(WIN32)
+    WaitForSingleObject(thread_, INFINITE);
+    CloseHandle(thread_);
+    thread_ = NULL;
+    thread_id_ = 0;
+#elif defined(POSIX)
+    void *pv;
+    pthread_join(thread_, &pv);
+#endif
+    started_ = false;
+  }
+}
+
+#ifdef WIN32
+// As seen on MSDN.
+// http://msdn.microsoft.com/en-us/library/xcb2z8hs(VS.71).aspx
+#define MSDEV_SET_THREAD_NAME  0x406D1388
+typedef struct tagTHREADNAME_INFO {
+  DWORD dwType;
+  LPCSTR szName;
+  DWORD dwThreadID;
+  DWORD dwFlags;
+} THREADNAME_INFO;
+
+void SetThreadName(DWORD dwThreadID, LPCSTR szThreadName) {
+  THREADNAME_INFO info;
+  info.dwType = 0x1000;
+  info.szName = szThreadName;
+  info.dwThreadID = dwThreadID;
+  info.dwFlags = 0;
+
+  __try {
+    RaiseException(MSDEV_SET_THREAD_NAME, 0, sizeof(info) / sizeof(DWORD),
+                   reinterpret_cast<ULONG_PTR*>(&info));
+  }
+  __except(EXCEPTION_CONTINUE_EXECUTION) {
+  }
+}
+#endif  // WIN32
+
+void* Thread::PreRun(void* pv) {
+  ThreadInit* init = static_cast<ThreadInit*>(pv);
+  ThreadManager::Instance()->SetCurrentThread(init->thread);
+#if defined(WIN32)
+  SetThreadName(GetCurrentThreadId(), init->thread->name_.c_str());
+#elif defined(POSIX)
+  // TODO: See if naming exists for pthreads.
+#endif
+#if __has_feature(objc_arc)
+  @autoreleasepool
+#elif defined(OSX) || defined(IOS)
+  // Make sure the new thread has an autoreleasepool
+  ScopedAutoreleasePool pool;
+#endif
+  {
+    if (init->runnable) {
+      init->runnable->Run(init->thread);
+    } else {
+      init->thread->Run();
+    }
+    if (init->thread->delete_self_when_complete_) {
+      init->thread->started_ = false;
+      delete init->thread;
+    }
+    delete init;
+    return NULL;
+  }
+}
+
+void Thread::Run() {
+  ProcessMessages(kForever);
+}
+
+bool Thread::IsOwned() {
+  return owned_;
+}
+
+void Thread::Stop() {
+  MessageQueue::Quit();
+  Join();
+}
+
+void Thread::Send(MessageHandler *phandler, uint32 id, MessageData *pdata) {
+  if (fStop_)
+    return;
+
+  // Sent messages are sent to the MessageHandler directly, in the context
+  // of "thread", like Win32 SendMessage. If in the right context,
+  // call the handler directly.
+
+  Message msg;
+  msg.phandler = phandler;
+  msg.message_id = id;
+  msg.pdata = pdata;
+  if (IsCurrent()) {
+    phandler->OnMessage(&msg);
+    return;
+  }
+
+  AutoThread thread;
+  Thread *current_thread = Thread::Current();
+  ASSERT(current_thread != NULL);  // AutoThread ensures this
+
+  bool ready = false;
+  {
+    CritScope cs(&crit_);
+    EnsureActive();
+    _SendMessage smsg;
+    smsg.thread = current_thread;
+    smsg.msg = msg;
+    smsg.ready = &ready;
+    sendlist_.push_back(smsg);
+    has_sends_ = true;
+  }
+
+  // Wait for a reply
+
+  ss_->WakeUp();
+
+  bool waited = false;
+  while (!ready) {
+    current_thread->ReceiveSends();
+    current_thread->socketserver()->Wait(kForever, false);
+    waited = true;
+  }
+
+  // Our Wait loop above may have consumed some WakeUp events for this
+  // MessageQueue, that weren't relevant to this Send.  Losing these WakeUps can
+  // cause problems for some SocketServers.
+  //
+  // Concrete example:
+  // Win32SocketServer on thread A calls Send on thread B.  While processing the
+  // message, thread B Posts a message to A.  We consume the wakeup for that
+  // Post while waiting for the Send to complete, which means that when we exit
+  // this loop, we need to issue another WakeUp, or else the Posted message
+  // won't be processed in a timely manner.
+
+  if (waited) {
+    current_thread->socketserver()->WakeUp();
+  }
+}
+
+void Thread::ReceiveSends() {
+  // Before entering critical section, check boolean.
+
+  if (!has_sends_)
+    return;
+
+  // Receive a sent message. Cleanup scenarios:
+  // - thread sending exits: We don't allow this, since thread can exit
+  //   only via Join, so Send must complete.
+  // - thread receiving exits: Wakeup/set ready in Thread::Clear()
+  // - object target cleared: Wakeup/set ready in Thread::Clear()
+  crit_.Enter();
+  while (!sendlist_.empty()) {
+    _SendMessage smsg = sendlist_.front();
+    sendlist_.pop_front();
+    crit_.Leave();
+    smsg.msg.phandler->OnMessage(&smsg.msg);
+    crit_.Enter();
+    *smsg.ready = true;
+    smsg.thread->socketserver()->WakeUp();
+  }
+  has_sends_ = false;
+  crit_.Leave();
+}
+
+void Thread::Clear(MessageHandler *phandler, uint32 id,
+                   MessageList* removed) {
+  CritScope cs(&crit_);
+
+  // Remove messages on sendlist_ with phandler
+  // Object target cleared: remove from send list, wakeup/set ready
+  // if sender not NULL.
+
+  std::list<_SendMessage>::iterator iter = sendlist_.begin();
+  while (iter != sendlist_.end()) {
+    _SendMessage smsg = *iter;
+    if (smsg.msg.Match(phandler, id)) {
+      if (removed) {
+        removed->push_back(smsg.msg);
+      } else {
+        delete smsg.msg.pdata;
+      }
+      iter = sendlist_.erase(iter);
+      *smsg.ready = true;
+      smsg.thread->socketserver()->WakeUp();
+      continue;
+    }
+    ++iter;
+  }
+
+  MessageQueue::Clear(phandler, id, removed);
+}
+
+bool Thread::ProcessMessages(int cmsLoop) {
+  uint32 msEnd = (kForever == cmsLoop) ? 0 : TimeAfter(cmsLoop);
+  int cmsNext = cmsLoop;
+
+  while (true) {
+#if __has_feature(objc_arc)
+    @autoreleasepool
+#elif defined(OSX) || defined(IOS)
+    // see: http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSAutoreleasePool_Class/Reference/Reference.html
+    // Each thread is supposed to have an autorelease pool. Also for event loops
+    // like this, autorelease pool needs to be created and drained/released
+    // for each cycle.
+    ScopedAutoreleasePool pool;
+#endif
+    {
+      Message msg;
+      if (!Get(&msg, cmsNext))
+        return !IsQuitting();
+      Dispatch(&msg);
+
+      if (cmsLoop != kForever) {
+        cmsNext = TimeUntil(msEnd);
+        if (cmsNext < 0)
+          return true;
+      }
+    }
+  }
+}
+
+bool Thread::WrapCurrent() {
+  return WrapCurrentWithThreadManager(ThreadManager::Instance());
+}
+
+bool Thread::WrapCurrentWithThreadManager(ThreadManager* thread_manager) {
+  if (started_)
+    return false;
+#if defined(WIN32)
+  // We explicitly ask for no rights other than synchronization.
+  // This gives us the best chance of succeeding.
+  thread_ = OpenThread(SYNCHRONIZE, FALSE, GetCurrentThreadId());
+  if (!thread_) {
+    LOG_GLE(LS_ERROR) << "Unable to get handle to thread.";
+    return false;
+  }
+  thread_id_ = GetCurrentThreadId();
+#elif defined(POSIX)
+  thread_ = pthread_self();
+#endif
+  owned_ = false;
+  started_ = true;
+  thread_manager->SetCurrentThread(this);
+  return true;
+}
+
+void Thread::UnwrapCurrent() {
+  // Clears the platform-specific thread-specific storage.
+  ThreadManager::Instance()->SetCurrentThread(NULL);
+#ifdef WIN32
+  if (!CloseHandle(thread_)) {
+    LOG_GLE(LS_ERROR) << "When unwrapping thread, failed to close handle.";
+  }
+#endif
+  started_ = false;
+}
+
+
+AutoThread::AutoThread(SocketServer* ss) : Thread(ss) {
+  if (!ThreadManager::Instance()->CurrentThread()) {
+    ThreadManager::Instance()->SetCurrentThread(this);
+  }
+}
+
+AutoThread::~AutoThread() {
+  if (ThreadManager::Instance()->CurrentThread() == this) {
+    ThreadManager::Instance()->SetCurrentThread(NULL);
+  }
+}
+
+#ifdef WIN32
+void ComThread::Run() {
+  HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+  ASSERT(SUCCEEDED(hr));
+  if (SUCCEEDED(hr)) {
+    Thread::Run();
+    CoUninitialize();
+  } else {
+    LOG(LS_ERROR) << "CoInitialize failed, hr=" << hr;
+  }
+}
+#endif
+
+}  // namespace talk_base
diff --git a/talk/base/thread.h b/talk/base/thread.h
new file mode 100644
index 0000000..55ec0da
--- /dev/null
+++ b/talk/base/thread.h
@@ -0,0 +1,323 @@
+/*
+ * 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_BASE_THREAD_H_
+#define TALK_BASE_THREAD_H_
+
+#include <algorithm>
+#include <list>
+#include <string>
+#include <vector>
+
+#ifdef POSIX
+#include <pthread.h>
+#endif
+#include "talk/base/constructormagic.h"
+#include "talk/base/messagequeue.h"
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#endif
+
+namespace talk_base {
+
+class Thread;
+
+class ThreadManager {
+ public:
+  ThreadManager();
+  ~ThreadManager();
+
+  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.
+  // If there already *is* a Thread object corresponding to this thread,
+  // this method will return that.  Otherwise it creates a new Thread
+  // object whose wrapped() method will return true, and whose
+  // handle will, on Win32, be opened with only synchronization privileges -
+  // if you need more privilegs, rather than changing this method, please
+  // write additional code to adjust the privileges, or call a different
+  // factory method of your own devising, because this one gets used in
+  // unexpected contexts (like inside browser plugins) and it would be a
+  // shame to break it.  It is also conceivable on Win32 that we won't even
+  // be able to get synchronization privileges, in which case the result
+  // will have a NULL handle.
+  Thread *WrapCurrentThread();
+  void UnwrapCurrentThread();
+
+ private:
+#ifdef POSIX
+  pthread_key_t key_;
+#endif
+
+#ifdef WIN32
+  DWORD key_;
+#endif
+
+  DISALLOW_COPY_AND_ASSIGN(ThreadManager);
+};
+
+struct _SendMessage {
+  _SendMessage() {}
+  Thread *thread;
+  Message msg;
+  bool *ready;
+};
+
+enum ThreadPriority {
+  PRIORITY_IDLE = -1,
+  PRIORITY_NORMAL = 0,
+  PRIORITY_ABOVE_NORMAL = 1,
+  PRIORITY_HIGH = 2,
+};
+
+class Runnable {
+ public:
+  virtual ~Runnable() {}
+  virtual void Run(Thread* thread) = 0;
+
+ protected:
+  Runnable() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Runnable);
+};
+
+class Thread : public MessageQueue {
+ public:
+  explicit Thread(SocketServer* ss = NULL);
+  virtual ~Thread();
+
+  static Thread* Current();
+
+  bool IsCurrent() const {
+    return Current() == this;
+  }
+
+  // Sleeps the calling thread for the specified number of milliseconds, during
+  // which time no processing is performed. Returns false if sleeping was
+  // interrupted by a signal (POSIX only).
+  static bool SleepMs(int millis);
+
+  // Sets the thread's name, for debugging. Must be called before Start().
+  // If |obj| is non-NULL, its value is appended to |name|.
+  const std::string& name() const { return name_; }
+  bool SetName(const std::string& name, const void* obj);
+
+  // Sets the thread's priority. Must be called before Start().
+  ThreadPriority priority() const { return priority_; }
+  bool SetPriority(ThreadPriority priority);
+
+  // Starts the execution of the thread.
+  bool started() const { return started_; }
+  bool Start(Runnable* runnable = NULL);
+
+  // Used for fire-and-forget threads.  Deletes this thread object when the
+  // Run method returns.
+  void Release() {
+    delete_self_when_complete_ = true;
+  }
+
+  // Tells the thread to stop and waits until it is joined.
+  // Never call Stop on the current thread.  Instead use the inherited Quit
+  // function which will exit the base MessageQueue without terminating the
+  // underlying OS thread.
+  virtual void Stop();
+
+  // By default, Thread::Run() calls ProcessMessages(kForever).  To do other
+  // work, override Run().  To receive and dispatch messages, call
+  // ProcessMessages occasionally.
+  virtual void Run();
+
+  virtual void Send(MessageHandler *phandler, uint32 id = 0,
+      MessageData *pdata = NULL);
+
+  // Convenience method to invoke a functor on another thread.  Caller must
+  // provide the |ReturnT| template argument, which cannot (easily) be deduced.
+  // Uses Send() internally, which blocks the current thread until execution
+  // is complete.
+  // Ex: bool result = thread.Invoke<bool>(&MyFunctionReturningBool);
+  template <class ReturnT, class FunctorT>
+  ReturnT Invoke(const FunctorT& functor) {
+    FunctorMessageHandler<ReturnT, FunctorT> handler(functor);
+    Send(&handler);
+    return handler.result();
+  }
+
+  // From MessageQueue
+  virtual void Clear(MessageHandler *phandler, uint32 id = MQID_ANY,
+                     MessageList* removed = NULL);
+  virtual void ReceiveSends();
+
+  // ProcessMessages will process I/O and dispatch messages until:
+  //  1) cms milliseconds have elapsed (returns true)
+  //  2) Stop() is called (returns false)
+  bool ProcessMessages(int cms);
+
+  // Returns true if this is a thread that we created using the standard
+  // constructor, false if it was created by a call to
+  // ThreadManager::WrapCurrentThread().  The main thread of an application
+  // is generally not owned, since the OS representation of the thread
+  // obviously exists before we can get to it.
+  // You cannot call Start on non-owned threads.
+  bool IsOwned();
+
+#ifdef WIN32
+  HANDLE GetHandle() const {
+    return thread_;
+  }
+  DWORD GetId() const {
+    return thread_id_;
+  }
+#elif POSIX
+  pthread_t GetPThread() {
+    return thread_;
+  }
+#endif
+
+  // This method should be called when thread is created using non standard
+  // method, like derived implementation of talk_base::Thread and it can not be
+  // started by calling Start(). This will set started flag to true and
+  // owned to false. This must be called from the current thread.
+  // NOTE: These methods should be used by the derived classes only, added here
+  // only for testing.
+  bool WrapCurrent();
+  void UnwrapCurrent();
+
+ protected:
+  // Blocks the calling thread until this thread has terminated.
+  void Join();
+
+ private:
+  // Helper class to facilitate executing a functor on a thread.
+  template <class ReturnT, class FunctorT>
+  class FunctorMessageHandler : public MessageHandler {
+   public:
+    explicit FunctorMessageHandler(const FunctorT& functor)
+        : functor_(functor) {}
+    virtual void OnMessage(Message* msg) {
+      result_ = functor_();
+    }
+    const ReturnT& result() const { return result_; }
+   private:
+    FunctorT functor_;
+    ReturnT result_;
+  };
+
+  // Specialization for ReturnT of void.
+  template <class FunctorT>
+  class FunctorMessageHandler<void, FunctorT> : public MessageHandler {
+   public:
+    explicit FunctorMessageHandler(const FunctorT& functor)
+        : functor_(functor) {}
+    virtual void OnMessage(Message* msg) { functor_(); }
+    void result() const {}
+   private:
+    FunctorT functor_;
+  };
+
+  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_;
+  bool started_;
+  bool has_sends_;
+
+#ifdef POSIX
+  pthread_t thread_;
+#endif
+
+#ifdef WIN32
+  HANDLE thread_;
+  DWORD thread_id_;
+#endif
+
+  bool owned_;
+  bool delete_self_when_complete_;
+
+  friend class ThreadManager;
+
+  DISALLOW_COPY_AND_ASSIGN(Thread);
+};
+
+// AutoThread automatically installs itself at construction
+// uninstalls at destruction, if a Thread object is
+// _not already_ associated with the current OS thread.
+
+class AutoThread : public Thread {
+ public:
+  explicit AutoThread(SocketServer* ss = 0);
+  virtual ~AutoThread();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(AutoThread);
+};
+
+// Win32 extension for threads that need to use COM
+#ifdef WIN32
+class ComThread : public Thread {
+ public:
+  ComThread() {}
+
+ protected:
+  virtual void Run();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ComThread);
+};
+#endif
+
+// Provides an easy way to install/uninstall a socketserver on a thread.
+class SocketServerScope {
+ public:
+  explicit SocketServerScope(SocketServer* ss) {
+    old_ss_ = Thread::Current()->socketserver();
+    Thread::Current()->set_socketserver(ss);
+  }
+  ~SocketServerScope() {
+    Thread::Current()->set_socketserver(old_ss_);
+  }
+
+ private:
+  SocketServer* old_ss_;
+
+  DISALLOW_IMPLICIT_CONSTRUCTORS(SocketServerScope);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_THREAD_H_
diff --git a/talk/base/thread_unittest.cc b/talk/base/thread_unittest.cc
new file mode 100644
index 0000000..11b493d
--- /dev/null
+++ b/talk/base/thread_unittest.cc
@@ -0,0 +1,329 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/asyncudpsocket.h"
+#include "talk/base/event.h"
+#include "talk/base/gunit.h"
+#include "talk/base/host.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/thread.h"
+
+#ifdef WIN32
+#include <comdef.h>  // NOLINT
+#endif
+
+using namespace talk_base;
+
+const int MAX = 65536;
+
+// Generates a sequence of numbers (collaboratively).
+class TestGenerator {
+ public:
+  TestGenerator() : last(0), count(0) {}
+
+  int Next(int prev) {
+    int result = prev + last;
+    last = result;
+    count += 1;
+    return result;
+  }
+
+  int last;
+  int count;
+};
+
+struct TestMessage : public MessageData {
+  explicit TestMessage(int v) : value(v) {}
+  virtual ~TestMessage() {}
+
+  int value;
+};
+
+// Receives on a socket and sends by posting messages.
+class SocketClient : public TestGenerator, public sigslot::has_slots<> {
+ public:
+  SocketClient(AsyncSocket* socket, const SocketAddress& addr,
+               Thread* post_thread, MessageHandler* phandler)
+      : socket_(AsyncUDPSocket::Create(socket, addr)),
+        post_thread_(post_thread),
+        post_handler_(phandler) {
+    socket_->SignalReadPacket.connect(this, &SocketClient::OnPacket);
+  }
+
+  ~SocketClient() {
+    delete socket_;
+  }
+
+  SocketAddress address() const { return socket_->GetLocalAddress(); }
+
+  void OnPacket(AsyncPacketSocket* socket, const char* buf, size_t size,
+                const SocketAddress& remote_addr) {
+    EXPECT_EQ(size, sizeof(uint32));
+    uint32 prev = reinterpret_cast<const uint32*>(buf)[0];
+    uint32 result = Next(prev);
+
+    //socket_->set_readable(last < MAX);
+    post_thread_->PostDelayed(200, post_handler_, 0, new TestMessage(result));
+  }
+
+ private:
+  AsyncUDPSocket* socket_;
+  Thread* post_thread_;
+  MessageHandler* post_handler_;
+};
+
+// Receives messages and sends on a socket.
+class MessageClient : public MessageHandler, public TestGenerator {
+ public:
+  MessageClient(Thread* pth, Socket* socket)
+      : thread_(pth), socket_(socket) {
+  }
+
+  virtual ~MessageClient() {
+    delete socket_;
+  }
+
+  virtual void OnMessage(Message *pmsg) {
+    TestMessage* msg = static_cast<TestMessage*>(pmsg->pdata);
+    int result = Next(msg->value);
+    EXPECT_GE(socket_->Send(&result, sizeof(result)), 0);
+    delete msg;
+  }
+
+ private:
+  Thread* thread_;
+  Socket* socket_;
+};
+
+class CustomThread : public talk_base::Thread {
+ public:
+  CustomThread() {}
+  virtual ~CustomThread() {}
+  bool Start() { return false; }
+};
+
+
+// A thread that does nothing when it runs and signals an event
+// when it is destroyed.
+class SignalWhenDestroyedThread : public Thread {
+ public:
+  SignalWhenDestroyedThread(Event* event)
+      : event_(event) {
+  }
+
+  virtual ~SignalWhenDestroyedThread() {
+    event_->Set();
+  }
+
+  virtual void Run() {
+    // Do nothing.
+  }
+
+ private:
+  Event* event_;
+};
+
+// Function objects to test Thread::Invoke.
+struct Functor1 {
+  int operator()() { return 42; }
+};
+class Functor2 {
+ public:
+  explicit Functor2(bool* flag) : flag_(flag) {}
+  void operator()() { if (flag_) *flag_ = true; }
+ private:
+  bool* flag_;
+};
+
+
+TEST(ThreadTest, Main) {
+  const SocketAddress addr("127.0.0.1", 0);
+
+  // Create the messaging client on its own thread.
+  Thread th1;
+  Socket* socket = th1.socketserver()->CreateAsyncSocket(addr.family(),
+                                                         SOCK_DGRAM);
+  MessageClient msg_client(&th1, socket);
+
+  // Create the socket client on its own thread.
+  Thread th2;
+  AsyncSocket* asocket =
+      th2.socketserver()->CreateAsyncSocket(addr.family(), SOCK_DGRAM);
+  SocketClient sock_client(asocket, addr, &th1, &msg_client);
+
+  socket->Connect(sock_client.address());
+
+  th1.Start();
+  th2.Start();
+
+  // Get the messages started.
+  th1.PostDelayed(100, &msg_client, 0, new TestMessage(1));
+
+  // Give the clients a little while to run.
+  // Messages will be processed at 100, 300, 500, 700, 900.
+  Thread* th_main = Thread::Current();
+  th_main->ProcessMessages(1000);
+
+  // Stop the sending client. Give the receiver a bit longer to run, in case
+  // it is running on a machine that is under load (e.g. the build machine).
+  th1.Stop();
+  th_main->ProcessMessages(200);
+  th2.Stop();
+
+  // Make sure the results were correct
+  EXPECT_EQ(5, msg_client.count);
+  EXPECT_EQ(34, msg_client.last);
+  EXPECT_EQ(5, sock_client.count);
+  EXPECT_EQ(55, sock_client.last);
+}
+
+// Test that setting thread names doesn't cause a malfunction.
+// There's no easy way to verify the name was set properly at this time.
+TEST(ThreadTest, Names) {
+  // Default name
+  Thread *thread;
+  thread = new Thread();
+  EXPECT_TRUE(thread->Start());
+  thread->Stop();
+  delete thread;
+  thread = new Thread();
+  // Name with no object parameter
+  EXPECT_TRUE(thread->SetName("No object", NULL));
+  EXPECT_TRUE(thread->Start());
+  thread->Stop();
+  delete thread;
+  // Really long name
+  thread = new Thread();
+  EXPECT_TRUE(thread->SetName("Abcdefghijklmnopqrstuvwxyz1234567890", this));
+  EXPECT_TRUE(thread->Start());
+  thread->Stop();
+  delete thread;
+}
+
+// Test that setting thread priorities doesn't cause a malfunction.
+// There's no easy way to verify the priority was set properly at this time.
+TEST(ThreadTest, Priorities) {
+  Thread *thread;
+  thread = new Thread();
+  EXPECT_TRUE(thread->SetPriority(PRIORITY_HIGH));
+  EXPECT_TRUE(thread->Start());
+  thread->Stop();
+  delete thread;
+  thread = new Thread();
+  EXPECT_TRUE(thread->SetPriority(PRIORITY_ABOVE_NORMAL));
+  EXPECT_TRUE(thread->Start());
+  thread->Stop();
+  delete thread;
+
+  thread = new Thread();
+  EXPECT_TRUE(thread->Start());
+#ifdef WIN32
+  EXPECT_TRUE(thread->SetPriority(PRIORITY_ABOVE_NORMAL));
+#else
+  EXPECT_FALSE(thread->SetPriority(PRIORITY_ABOVE_NORMAL));
+#endif
+  thread->Stop();
+  delete thread;
+
+}
+
+TEST(ThreadTest, Wrap) {
+  Thread* current_thread = Thread::Current();
+  current_thread->UnwrapCurrent();
+  CustomThread* cthread = new CustomThread();
+  EXPECT_TRUE(cthread->WrapCurrent());
+  EXPECT_TRUE(cthread->started());
+  EXPECT_FALSE(cthread->IsOwned());
+  cthread->UnwrapCurrent();
+  EXPECT_FALSE(cthread->started());
+  delete cthread;
+  current_thread->WrapCurrent();
+}
+
+// Test that calling Release on a thread causes it to self-destruct when
+// it's finished running
+TEST(ThreadTest, Release) {
+  scoped_ptr<Event> event(new Event(true, false));
+  // Ensure the event is initialized.
+  event->Reset();
+
+  Thread* thread = new SignalWhenDestroyedThread(event.get());
+  thread->Start();
+  thread->Release();
+
+  // The event should get signaled when the thread completes, which should
+  // be nearly instantaneous, since it doesn't do anything.  For safety,
+  // give it 3 seconds in case the machine is under load.
+  bool signaled = event->Wait(3000);
+  EXPECT_TRUE(signaled);
+}
+
+TEST(ThreadTest, Invoke) {
+  // Create and start the thread.
+  Thread thread;
+  thread.Start();
+  // Try calling functors.
+  EXPECT_EQ(42, thread.Invoke<int>(Functor1()));
+  bool called = false;
+  Functor2 f2(&called);
+  thread.Invoke<void>(f2);
+  EXPECT_TRUE(called);
+  // Try calling bare functions.
+  struct LocalFuncs {
+    static int Func1() { return 999; }
+    static void Func2() {}
+  };
+  EXPECT_EQ(999, thread.Invoke<int>(&LocalFuncs::Func1));
+  thread.Invoke<void>(&LocalFuncs::Func2);
+}
+
+#ifdef WIN32
+class ComThreadTest : public testing::Test, public MessageHandler {
+ public:
+  ComThreadTest() : done_(false) {}
+ protected:
+  virtual void OnMessage(Message* message) {
+    HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+    // S_FALSE means the thread was already inited for a multithread apartment.
+    EXPECT_EQ(S_FALSE, hr);
+    if (SUCCEEDED(hr)) {
+      CoUninitialize();
+    }
+    done_ = true;
+  }
+  bool done_;
+};
+
+TEST_F(ComThreadTest, ComInited) {
+  Thread* thread = new ComThread();
+  EXPECT_TRUE(thread->Start());
+  thread->Post(this, 0);
+  EXPECT_TRUE_WAIT(done_, 1000);
+  delete thread;
+}
+#endif
diff --git a/talk/base/timeutils.cc b/talk/base/timeutils.cc
new file mode 100644
index 0000000..66b9bf2
--- /dev/null
+++ b/talk/base/timeutils.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.
+ */
+
+#ifdef POSIX
+#include <sys/time.h>
+#if defined(OSX) || defined(IOS)
+#include <mach/mach_time.h>
+#endif
+#endif
+
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <mmsystem.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;
+
+uint64 TimeNanos() {
+  int64 ticks = 0;
+#if defined(OSX) || defined(IOS)
+  static mach_timebase_info_data_t timebase;
+  if (timebase.denom == 0) {
+    // Get the timebase if this is the first time we run.
+    // Recommended by Apple's QA1398.
+    VERIFY(KERN_SUCCESS == mach_timebase_info(&timebase));
+  }
+  // Use timebase to convert absolute time tick units into nanoseconds.
+  ticks = mach_absolute_time() * timebase.numer / timebase.denom;
+#elif defined(POSIX)
+  struct timespec ts;
+  // TODO: Do we need to handle the case when CLOCK_MONOTONIC
+  // is not supported?
+  clock_gettime(CLOCK_MONOTONIC, &ts);
+  ticks = kNumNanosecsPerSec * static_cast<int64>(ts.tv_sec) +
+      static_cast<int64>(ts.tv_nsec);
+#elif defined(WIN32)
+  static volatile LONG last_timegettime = 0;
+  static volatile int64 num_wrap_timegettime = 0;
+  volatile LONG* last_timegettime_ptr = &last_timegettime;
+  DWORD now = timeGetTime();
+  // Atomically update the last gotten time
+  DWORD old = InterlockedExchange(last_timegettime_ptr, now);
+  if (now < old) {
+    // If now is earlier than old, there may have been a race between
+    // threads.
+    // 0x0fffffff ~3.1 days, the code will not take that long to execute
+    // so it must have been a wrap around.
+    if (old > 0xf0000000 && now < 0x0fffffff) {
+      num_wrap_timegettime++;
+    }
+  }
+  ticks = now + (num_wrap_timegettime << 32);
+  // TODO: Calculate with nanosecond precision.  Otherwise, we're just
+  // wasting a multiply and divide when doing Time() on Windows.
+  ticks = ticks * kNumNanosecsPerMillisec;
+#endif
+  return ticks;
+}
+
+uint32 Time() {
+  return static_cast<uint32>(TimeNanos() / kNumNanosecsPerMillisec);
+}
+
+#if defined(WIN32)
+static const uint64 kFileTimeToUnixTimeEpochOffset = 116444736000000000ULL;
+
+struct timeval {
+  long tv_sec, tv_usec;  // NOLINT
+};
+
+// Emulate POSIX gettimeofday().
+// Based on breakpad/src/third_party/glog/src/utilities.cc
+static int gettimeofday(struct timeval *tv, void *tz) {
+  // FILETIME is measured in tens of microseconds since 1601-01-01 UTC.
+  FILETIME ft;
+  GetSystemTimeAsFileTime(&ft);
+
+  LARGE_INTEGER li;
+  li.LowPart = ft.dwLowDateTime;
+  li.HighPart = ft.dwHighDateTime;
+
+  // Convert to seconds and microseconds since Unix time Epoch.
+  int64 micros = (li.QuadPart - kFileTimeToUnixTimeEpochOffset) / 10;
+  tv->tv_sec = static_cast<long>(micros / kNumMicrosecsPerSec);  // NOLINT
+  tv->tv_usec = static_cast<long>(micros % kNumMicrosecsPerSec); // NOLINT
+
+  return 0;
+}
+
+// Emulate POSIX gmtime_r().
+static struct tm *gmtime_r(const time_t *timep, struct tm *result) {
+  // On Windows, gmtime is thread safe.
+  struct tm *tm = gmtime(timep);  // NOLINT
+  if (tm == NULL) {
+    return NULL;
+  }
+  *result = *tm;
+  return result;
+}
+#endif  // WIN32
+
+void CurrentTmTime(struct tm *tm, int *microseconds) {
+  struct timeval timeval;
+  if (gettimeofday(&timeval, NULL) < 0) {
+    // Incredibly unlikely code path.
+    timeval.tv_sec = timeval.tv_usec = 0;
+  }
+  time_t secs = timeval.tv_sec;
+  gmtime_r(&secs, tm);
+  *microseconds = timeval.tv_usec;
+}
+
+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..545e86a
--- /dev/null
+++ b/talk/base/timeutils.h
@@ -0,0 +1,98 @@
+/*
+ * 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_
+
+#include <time.h>
+
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+static const int64 kNumMillisecsPerSec = INT64_C(1000);
+static const int64 kNumMicrosecsPerSec = INT64_C(1000000);
+static const int64 kNumNanosecsPerSec = INT64_C(1000000000);
+
+static const int64 kNumMicrosecsPerMillisec = kNumMicrosecsPerSec /
+    kNumMillisecsPerSec;
+static const int64 kNumNanosecsPerMillisec =  kNumNanosecsPerSec /
+    kNumMillisecsPerSec;
+
+// January 1970, in NTP milliseconds.
+static const int64 kJan1970AsNtpMillisecs = INT64_C(2208988800000);
+
+typedef uint32 TimeStamp;
+
+// Returns the current time in milliseconds.
+uint32 Time();
+// Returns the current time in nanoseconds.
+uint64 TimeNanos();
+
+// Stores current time in *tm and microseconds in *microseconds.
+void CurrentTmTime(struct tm *tm, int *microseconds);
+
+// 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());
+}
+
+// Converts a unix timestamp in nanoseconds to an NTP timestamp in ms.
+inline int64 UnixTimestampNanosecsToNtpMillisecs(int64 unix_ts_ns) {
+  return unix_ts_ns / kNumNanosecsPerMillisec + kJan1970AsNtpMillisecs;
+}
+
+}  // 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..c90f6a4
--- /dev/null
+++ b/talk/base/timeutils_unittest.cc
@@ -0,0 +1,163 @@
+/*
+ * 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, TimeInMs) {
+  uint32 ts_earlier = Time();
+  Thread::SleepMs(100);
+  uint32 ts_now = Time();
+  // Allow for the thread to wakeup ~20ms early.
+  EXPECT_GE(ts_now, ts_earlier + 80);
+  // Make sure the Time is not returning in smaller unit like microseconds.
+  EXPECT_LT(ts_now, ts_earlier + 1000);
+}
+
+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));
+}
+
+TEST(TimeTest, CurrentTmTime) {
+  struct tm tm;
+  int microseconds;
+
+  time_t before = ::time(NULL);
+  CurrentTmTime(&tm, &microseconds);
+  time_t after = ::time(NULL);
+
+  // Assert that 'tm' represents a time between 'before' and 'after'.
+  // mktime() uses local time, so we have to compensate for that.
+  time_t local_delta = before - ::mktime(::gmtime(&before));  // NOLINT
+  time_t t = ::mktime(&tm) + local_delta;
+
+  EXPECT_TRUE(before <= t && t <= after);
+  EXPECT_TRUE(0 <= microseconds && microseconds < 1000000);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/timing.cc b/talk/base/timing.cc
new file mode 100644
index 0000000..4df9f1f
--- /dev/null
+++ b/talk/base/timing.cc
@@ -0,0 +1,129 @@
+/*
+ * 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"
+#include "talk/base/timeutils.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
+
+namespace talk_base {
+
+Timing::Timing() {
+#if defined(WIN32)
+  // 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() {
+  return (static_cast<double>(TimeNanos()) / kNumNanosecsPerSec);
+}
+
+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;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/timing.h b/talk/base/timing.h
new file mode 100644
index 0000000..f2bf013
--- /dev/null
+++ b/talk/base/timing.h
@@ -0,0 +1,76 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_TIMING_H_
+#define TALK_BASE_TIMING_H_
+
+#if defined(WIN32)
+#include "talk/base/win32.h"
+#endif
+
+namespace talk_base {
+
+class Timing {
+ public:
+  Timing();
+  virtual ~Timing();
+
+  // WallTimeNow() returns the current wall-clock time in seconds,
+  // within 10 milliseconds resolution.
+  virtual 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.
+  // Made virtual so we can make a fake one.
+  virtual 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)
+  HANDLE timer_handle_;
+#endif
+};
+
+}  // namespace talk_base
+
+#endif // TALK_BASE_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
new file mode 100644
index 0000000..bca3671
--- /dev/null
+++ b/talk/base/unittest_main.cc
@@ -0,0 +1,118 @@
+// Copyright 2007 Google Inc. All Rights Reserved.
+
+//         juberti@google.com (Justin Uberti)
+//
+// A reuseable entry point for gunit tests.
+
+#ifdef WIN32
+#include <crtdbg.h>
+#endif
+
+#include "talk/base/flags.h"
+#include "talk/base/fileutils.h"
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+
+DEFINE_bool(help, false, "prints this message");
+DEFINE_string(log, "", "logging options to use");
+#ifdef WIN32
+DEFINE_int(crt_break_alloc, -1, "memory allocation to break on");
+DEFINE_bool(default_error_handlers, false,
+            "leave the default exception/dbg handler functions in place");
+
+void TestInvalidParameterHandler(const wchar_t* expression,
+                                 const wchar_t* function,
+                                 const wchar_t* file,
+                                 unsigned int line,
+                                 uintptr_t pReserved) {
+  LOG(LS_ERROR) << "InvalidParameter Handler called.  Exiting.";
+  LOG(LS_ERROR) << expression << std::endl << function << std::endl << file
+                << std::endl << line;
+  exit(1);
+}
+void TestPureCallHandler() {
+  LOG(LS_ERROR) << "Purecall Handler called.  Exiting.";
+  exit(1);
+}
+int TestCrtReportHandler(int report_type, char* msg, int* retval) {
+    LOG(LS_ERROR) << "CrtReport Handler called...";
+    LOG(LS_ERROR) << msg;
+  if (report_type == _CRT_ASSERT) {
+    exit(1);
+  } else {
+    *retval = 0;
+    return TRUE;
+  }
+}
+#endif  // WIN32
+
+talk_base::Pathname GetTalkDirectory() {
+  // Locate talk directory.
+  talk_base::Pathname path = talk_base::Filesystem::GetCurrentDirectory();
+  std::string talk_folder_name("talk");
+  talk_folder_name += path.folder_delimiter();
+  while (path.folder_name() != talk_folder_name && !path.empty()) {
+    path.SetFolder(path.parent_folder());
+  }
+
+  // If not running inside "talk" folder, then assume running in its parent
+  // folder.
+  if (path.empty()) {
+    path = talk_base::Filesystem::GetCurrentDirectory();
+    path.AppendFolder("talk");
+    // Make sure the folder exist.
+    if (!talk_base::Filesystem::IsFolder(path)) {
+      path.clear();
+    }
+  }
+  return path;
+}
+
+int main(int argc, char** argv) {
+  testing::InitGoogleTest(&argc, argv);
+  FlagList::SetFlagsFromCommandLine(&argc, argv, false);
+  if (FLAG_help) {
+    FlagList::Print(NULL, false);
+    return 0;
+  }
+
+#ifdef WIN32
+  if (!FLAG_default_error_handlers) {
+    // Make sure any errors don't throw dialogs hanging the test run.
+    _set_invalid_parameter_handler(TestInvalidParameterHandler);
+    _set_purecall_handler(TestPureCallHandler);
+    _CrtSetReportHook2(_CRT_RPTHOOK_INSTALL, TestCrtReportHandler);
+  }
+
+#ifdef _DEBUG  // Turn on memory leak checking on Windows.
+  _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF |_CRTDBG_LEAK_CHECK_DF);
+  if (FLAG_crt_break_alloc >= 0) {
+    _crtBreakAlloc = FLAG_crt_break_alloc;
+  }
+#endif  // _DEBUG
+#endif  // WIN32
+
+  talk_base::Filesystem::SetOrganizationName("google");
+  talk_base::Filesystem::SetApplicationName("unittest");
+
+  // By default, log timestamps. Allow overrides by used of a --log flag.
+  talk_base::LogMessage::LogTimestamps();
+  if (*FLAG_log != '\0') {
+    talk_base::LogMessage::ConfigureLogging(FLAG_log, "unittest.log");
+  }
+
+  int res = RUN_ALL_TESTS();
+
+  // clean up logging so we don't appear to leak memory.
+  talk_base::LogMessage::ConfigureLogging("", "");
+
+#ifdef WIN32
+  // Unhook crt function so that we don't ever log after statics have been
+  // uninitialized.
+  if (!FLAG_default_error_handlers)
+    _CrtSetReportHook2(_CRT_RPTHOOK_REMOVE, TestCrtReportHandler);
+#endif
+
+  return res;
+}
diff --git a/talk/base/unixfilesystem.cc b/talk/base/unixfilesystem.cc
new file mode 100644
index 0000000..74168f2
--- /dev/null
+++ b/talk/base/unixfilesystem.cc
@@ -0,0 +1,546 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/unixfilesystem.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#ifdef OSX
+#include <Carbon/Carbon.h>
+#include <IOKit/IOCFBundle.h>
+#include <sys/statvfs.h>
+#include "talk/base/macutils.h"
+#endif  // OSX
+
+#if defined(POSIX) && !defined(OSX)
+#include <sys/types.h>
+#ifdef ANDROID
+#include <sys/statfs.h>
+#else
+#include <sys/statvfs.h>
+#endif  // ANDROID
+#include <pwd.h>
+#include <stdio.h>
+#include <unistd.h>
+#endif  // POSIX && !OSX
+
+#ifdef LINUX
+#include <ctype.h>
+#include <algorithm>
+#endif
+
+#include "talk/base/fileutils.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringutils.h"
+
+namespace talk_base {
+
+#if !defined(ANDROID) && !defined(IOS)
+char* UnixFilesystem::app_temp_path_ = NULL;
+#else
+char* UnixFilesystem::provided_app_data_folder_ = NULL;
+char* UnixFilesystem::provided_app_temp_folder_ = NULL;
+
+void UnixFilesystem::SetAppDataFolder(const std::string& folder) {
+  delete [] provided_app_data_folder_;
+  provided_app_data_folder_ = CopyString(folder);
+}
+
+void UnixFilesystem::SetAppTempFolder(const std::string& folder) {
+  delete [] provided_app_temp_folder_;
+  provided_app_temp_folder_ = CopyString(folder);
+}
+#endif
+
+bool UnixFilesystem::CreateFolder(const Pathname &path, mode_t mode) {
+  std::string pathname(path.pathname());
+  int len = pathname.length();
+  if ((len == 0) || (pathname[len - 1] != '/'))
+    return false;
+
+  struct stat st;
+  int res = ::stat(pathname.c_str(), &st);
+  if (res == 0) {
+    // Something exists at this location, check if it is a directory
+    return S_ISDIR(st.st_mode) != 0;
+  } else if (errno != ENOENT) {
+    // Unexpected error
+    return false;
+  }
+
+  // Directory doesn't exist, look up one directory level
+  do {
+    --len;
+  } while ((len > 0) && (pathname[len - 1] != '/'));
+
+  if (!CreateFolder(Pathname(pathname.substr(0, len)), mode)) {
+    return false;
+  }
+
+  LOG(LS_INFO) << "Creating folder: " << pathname;
+  return (0 == ::mkdir(pathname.c_str(), mode));
+}
+
+bool UnixFilesystem::CreateFolder(const Pathname &path) {
+  return CreateFolder(path, 0755);
+}
+
+FileStream *UnixFilesystem::OpenFile(const Pathname &filename,
+                                     const std::string &mode) {
+  FileStream *fs = new FileStream();
+  if (fs && !fs->Open(filename.pathname().c_str(), mode.c_str(), NULL)) {
+    delete fs;
+    fs = NULL;
+  }
+  return fs;
+}
+
+bool UnixFilesystem::CreatePrivateFile(const Pathname &filename) {
+  int fd = open(filename.pathname().c_str(),
+                O_RDWR | O_CREAT | O_EXCL,
+                S_IRUSR | S_IWUSR);
+  if (fd < 0) {
+    LOG_ERR(LS_ERROR) << "open() failed.";
+    return false;
+  }
+  // Don't need to keep the file descriptor.
+  if (close(fd) < 0) {
+    LOG_ERR(LS_ERROR) << "close() failed.";
+    // Continue.
+  }
+  return true;
+}
+
+bool UnixFilesystem::DeleteFile(const Pathname &filename) {
+  LOG(LS_INFO) << "Deleting file:" << filename.pathname();
+
+  if (!IsFile(filename)) {
+    ASSERT(IsFile(filename));
+    return false;
+  }
+  return ::unlink(filename.pathname().c_str()) == 0;
+}
+
+bool UnixFilesystem::DeleteEmptyFolder(const Pathname &folder) {
+  LOG(LS_INFO) << "Deleting folder" << folder.pathname();
+
+  if (!IsFolder(folder)) {
+    ASSERT(IsFolder(folder));
+    return false;
+  }
+  std::string no_slash(folder.pathname(), 0, folder.pathname().length()-1);
+  return ::rmdir(no_slash.c_str()) == 0;
+}
+
+bool UnixFilesystem::GetTemporaryFolder(Pathname &pathname, bool create,
+                                        const std::string *append) {
+#ifdef OSX
+  FSRef fr;
+  if (0 != FSFindFolder(kOnAppropriateDisk, kTemporaryFolderType,
+                        kCreateFolder, &fr))
+    return false;
+  unsigned char buffer[NAME_MAX+1];
+  if (0 != FSRefMakePath(&fr, buffer, ARRAY_SIZE(buffer)))
+    return false;
+  pathname.SetPathname(reinterpret_cast<char*>(buffer), "");
+#elif defined(ANDROID) || defined(IOS)
+  ASSERT(provided_app_temp_folder_ != NULL);
+  pathname.SetPathname(provided_app_temp_folder_, "");
+#else  // !OSX && !ANDROID
+  if (const char* tmpdir = getenv("TMPDIR")) {
+    pathname.SetPathname(tmpdir, "");
+  } else if (const char* tmp = getenv("TMP")) {
+    pathname.SetPathname(tmp, "");
+  } else {
+#ifdef P_tmpdir
+    pathname.SetPathname(P_tmpdir, "");
+#else  // !P_tmpdir
+    pathname.SetPathname("/tmp/", "");
+#endif  // !P_tmpdir
+  }
+#endif  // !OSX && !ANDROID
+  if (append) {
+    ASSERT(!append->empty());
+    pathname.AppendFolder(*append);
+  }
+  return !create || CreateFolder(pathname);
+}
+
+std::string UnixFilesystem::TempFilename(const Pathname &dir,
+                                         const std::string &prefix) {
+  int len = dir.pathname().size() + prefix.size() + 2 + 6;
+  char *tempname = new char[len];
+
+  snprintf(tempname, len, "%s/%sXXXXXX", dir.pathname().c_str(),
+           prefix.c_str());
+  int fd = ::mkstemp(tempname);
+  if (fd != -1)
+    ::close(fd);
+  std::string ret(tempname);
+  delete[] tempname;
+
+  return ret;
+}
+
+bool UnixFilesystem::MoveFile(const Pathname &old_path,
+                              const Pathname &new_path) {
+  if (!IsFile(old_path)) {
+    ASSERT(IsFile(old_path));
+    return false;
+  }
+  LOG(LS_VERBOSE) << "Moving " << old_path.pathname()
+                  << " to " << new_path.pathname();
+  if (rename(old_path.pathname().c_str(), new_path.pathname().c_str()) != 0) {
+    if (errno != EXDEV)
+      return false;
+    if (!CopyFile(old_path, new_path))
+      return false;
+    if (!DeleteFile(old_path))
+      return false;
+  }
+  return true;
+}
+
+bool UnixFilesystem::MoveFolder(const Pathname &old_path,
+                                const Pathname &new_path) {
+  if (!IsFolder(old_path)) {
+    ASSERT(IsFolder(old_path));
+    return false;
+  }
+  LOG(LS_VERBOSE) << "Moving " << old_path.pathname()
+                  << " to " << new_path.pathname();
+  if (rename(old_path.pathname().c_str(), new_path.pathname().c_str()) != 0) {
+    if (errno != EXDEV)
+      return false;
+    if (!CopyFolder(old_path, new_path))
+      return false;
+    if (!DeleteFolderAndContents(old_path))
+      return false;
+  }
+  return true;
+}
+
+bool UnixFilesystem::IsFolder(const Pathname &path) {
+  struct stat st;
+  if (stat(path.pathname().c_str(), &st) < 0)
+    return false;
+  return S_ISDIR(st.st_mode);
+}
+
+bool UnixFilesystem::CopyFile(const Pathname &old_path,
+                              const Pathname &new_path) {
+  LOG(LS_VERBOSE) << "Copying " << old_path.pathname()
+                  << " to " << new_path.pathname();
+  char buf[256];
+  size_t len;
+
+  StreamInterface *source = OpenFile(old_path, "rb");
+  if (!source)
+    return false;
+
+  StreamInterface *dest = OpenFile(new_path, "wb");
+  if (!dest) {
+    delete source;
+    return false;
+  }
+
+  while (source->Read(buf, sizeof(buf), &len, NULL) == SR_SUCCESS)
+    dest->Write(buf, len, NULL, NULL);
+
+  delete source;
+  delete dest;
+  return true;
+}
+
+bool UnixFilesystem::IsTemporaryPath(const Pathname& pathname) {
+#if defined(ANDROID) || defined(IOS)
+  ASSERT(provided_app_temp_folder_ != NULL);
+#endif
+
+  const char* const kTempPrefixes[] = {
+#if defined(ANDROID) || defined(IOS)
+    provided_app_temp_folder_,
+#else
+    "/tmp/", "/var/tmp/",
+#ifdef OSX
+    "/private/tmp/", "/private/var/tmp/", "/private/var/folders/",
+#endif  // OSX
+#endif  // ANDROID || IOS
+  };
+  for (size_t i = 0; i < ARRAY_SIZE(kTempPrefixes); ++i) {
+    if (0 == strncmp(pathname.pathname().c_str(), kTempPrefixes[i],
+                     strlen(kTempPrefixes[i])))
+      return true;
+  }
+  return false;
+}
+
+bool UnixFilesystem::IsFile(const Pathname& pathname) {
+  struct stat st;
+  int res = ::stat(pathname.pathname().c_str(), &st);
+  // Treat symlinks, named pipes, etc. all as files.
+  return res == 0 && !S_ISDIR(st.st_mode);
+}
+
+bool UnixFilesystem::IsAbsent(const Pathname& pathname) {
+  struct stat st;
+  int res = ::stat(pathname.pathname().c_str(), &st);
+  // Note: we specifically maintain ENOTDIR as an error, because that implies
+  // that you could not call CreateFolder(pathname).
+  return res != 0 && ENOENT == errno;
+}
+
+bool UnixFilesystem::GetFileSize(const Pathname& pathname, size_t *size) {
+  struct stat st;
+  if (::stat(pathname.pathname().c_str(), &st) != 0)
+    return false;
+  *size = st.st_size;
+  return true;
+}
+
+bool UnixFilesystem::GetFileTime(const Pathname& path, FileTimeType which,
+                                 time_t* time) {
+  struct stat st;
+  if (::stat(path.pathname().c_str(), &st) != 0)
+    return false;
+  switch (which) {
+  case FTT_CREATED:
+    *time = st.st_ctime;
+    break;
+  case FTT_MODIFIED:
+    *time = st.st_mtime;
+    break;
+  case FTT_ACCESSED:
+    *time = st.st_atime;
+    break;
+  default:
+    return false;
+  }
+  return true;
+}
+
+bool UnixFilesystem::GetAppPathname(Pathname* path) {
+#ifdef OSX
+  ProcessSerialNumber psn = { 0, kCurrentProcess };
+  CFDictionaryRef procinfo = ProcessInformationCopyDictionary(&psn,
+      kProcessDictionaryIncludeAllInformationMask);
+  if (NULL == procinfo)
+    return false;
+  CFStringRef cfpath = (CFStringRef) CFDictionaryGetValue(procinfo,
+      kIOBundleExecutableKey);
+  std::string path8;
+  bool success = ToUtf8(cfpath, &path8);
+  CFRelease(procinfo);
+  if (success)
+    path->SetPathname(path8);
+  return success;
+#else  // OSX
+  char buffer[NAME_MAX+1];
+  size_t len = readlink("/proc/self/exe", buffer, ARRAY_SIZE(buffer) - 1);
+  if (len <= 0)
+    return false;
+  buffer[len] = '\0';
+  path->SetPathname(buffer);
+  return true;
+#endif  // OSX
+}
+
+bool UnixFilesystem::GetAppDataFolder(Pathname* path, bool per_user) {
+  ASSERT(!organization_name_.empty());
+  ASSERT(!application_name_.empty());
+
+  // First get the base directory for app data.
+#ifdef OSX
+  if (per_user) {
+    // Use ~/Library/Application Support/<orgname>/<appname>/
+    FSRef fr;
+    if (0 != FSFindFolder(kUserDomain, kApplicationSupportFolderType,
+                          kCreateFolder, &fr))
+      return false;
+    unsigned char buffer[NAME_MAX+1];
+    if (0 != FSRefMakePath(&fr, buffer, ARRAY_SIZE(buffer)))
+      return false;
+    path->SetPathname(reinterpret_cast<char*>(buffer), "");
+  } else {
+    // TODO
+    return false;
+  }
+#elif defined(ANDROID) || defined(IOS)  // && !OSX
+  ASSERT(provided_app_data_folder_ != NULL);
+  path->SetPathname(provided_app_data_folder_, "");
+#elif defined(LINUX)  // && !OSX && !defined(ANDROID) && !defined(IOS)
+  if (per_user) {
+    // We follow the recommendations in
+    // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
+    // It specifies separate directories for data and config files, but
+    // GetAppDataFolder() does not distinguish. We just return the config dir
+    // path.
+    const char* xdg_config_home = getenv("XDG_CONFIG_HOME");
+    if (xdg_config_home) {
+      path->SetPathname(xdg_config_home, "");
+    } else {
+      // XDG says to default to $HOME/.config. We also support falling back to
+      // other synonyms for HOME if for some reason it is not defined.
+      const char* homedir;
+      if (const char* home = getenv("HOME")) {
+        homedir = home;
+      } else if (const char* dotdir = getenv("DOTDIR")) {
+        homedir = dotdir;
+      } else if (passwd* pw = getpwuid(geteuid())) {
+        homedir = pw->pw_dir;
+      } else {
+        return false;
+      }
+      path->SetPathname(homedir, "");
+      path->AppendFolder(".config");
+    }
+  } else {
+    // XDG does not define a standard directory for writable global data. Let's
+    // just use this.
+    path->SetPathname("/var/cache/", "");
+  }
+#endif  // !OSX && !defined(ANDROID) && !defined(LINUX)
+
+  // Now add on a sub-path for our app.
+#if defined(OSX) || defined(ANDROID) || defined(IOS)
+  path->AppendFolder(organization_name_);
+  path->AppendFolder(application_name_);
+#elif defined(LINUX)
+  // XDG says to use a single directory level, so we concatenate the org and app
+  // name with a hyphen. We also do the Linuxy thing and convert to all
+  // lowercase with no spaces.
+  std::string subdir(organization_name_);
+  subdir.append("-");
+  subdir.append(application_name_);
+  replace_substrs(" ", 1, "", 0, &subdir);
+  std::transform(subdir.begin(), subdir.end(), subdir.begin(), ::tolower);
+  path->AppendFolder(subdir);
+#endif
+  if (!CreateFolder(*path, 0700)) {
+    return false;
+  }
+  // If the folder already exists, it may have the wrong mode or be owned by
+  // someone else, both of which are security problems. Setting the mode
+  // avoids both issues since it will fail if the path is not owned by us.
+  if (0 != ::chmod(path->pathname().c_str(), 0700)) {
+    LOG_ERR(LS_ERROR) << "Can't set mode on " << path;
+    return false;
+  }
+  return true;
+}
+
+bool UnixFilesystem::GetAppTempFolder(Pathname* path) {
+#if defined(ANDROID) || defined(IOS)
+  ASSERT(provided_app_temp_folder_ != NULL);
+  path->SetPathname(provided_app_temp_folder_);
+  return true;
+#else
+  ASSERT(!application_name_.empty());
+  // TODO: Consider whether we are worried about thread safety.
+  if (app_temp_path_ != NULL && strlen(app_temp_path_) > 0) {
+    path->SetPathname(app_temp_path_);
+    return true;
+  }
+
+  // Create a random directory as /tmp/<appname>-<pid>-<timestamp>
+  char buffer[128];
+  sprintfn(buffer, ARRAY_SIZE(buffer), "-%d-%d",
+           static_cast<int>(getpid()),
+           static_cast<int>(time(0)));
+  std::string folder(application_name_);
+  folder.append(buffer);
+  if (!GetTemporaryFolder(*path, true, &folder))
+    return false;
+
+  delete [] app_temp_path_;
+  app_temp_path_ = CopyString(path->pathname());
+  // TODO: atexit(DeleteFolderAndContents(app_temp_path_));
+  return true;
+#endif
+}
+
+bool UnixFilesystem::GetDiskFreeSpace(const Pathname& path, int64 *freebytes) {
+  ASSERT(NULL != freebytes);
+  // TODO: Consider making relative paths absolute using cwd.
+  // TODO: When popping off a symlink, push back on the components of the
+  // symlink, so we don't jump out of the target disk inadvertently.
+  Pathname existing_path(path.folder(), "");
+  while (!existing_path.folder().empty() && IsAbsent(existing_path)) {
+    existing_path.SetFolder(existing_path.parent_folder());
+  }
+#ifdef ANDROID
+  struct statfs vfs;
+  memset(&vfs, 0, sizeof(vfs));
+  if (0 != statfs(existing_path.pathname().c_str(), &vfs))
+    return false;
+#else
+  struct statvfs vfs;
+  memset(&vfs, 0, sizeof(vfs));
+  if (0 != statvfs(existing_path.pathname().c_str(), &vfs))
+    return false;
+#endif  // ANDROID
+#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;
+#endif
+
+  return true;
+}
+
+Pathname UnixFilesystem::GetCurrentDirectory() {
+  Pathname cwd;
+  char buffer[PATH_MAX];
+  char *path = getcwd(buffer, PATH_MAX);
+
+  if (!path) {
+    LOG_ERR(LS_ERROR) << "getcwd() failed";
+    return cwd;  // returns empty pathname
+  }
+  cwd.SetFolder(std::string(path));
+
+  return cwd;
+}
+
+char* UnixFilesystem::CopyString(const std::string& str) {
+  size_t size = str.length() + 1;
+
+  char* buf = new char[size];
+  if (!buf) {
+    return NULL;
+  }
+
+  strcpyn(buf, size, str.c_str());
+  return buf;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/unixfilesystem.h b/talk/base/unixfilesystem.h
new file mode 100644
index 0000000..aa9c920
--- /dev/null
+++ b/talk/base/unixfilesystem.h
@@ -0,0 +1,139 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_UNIXFILESYSTEM_H_
+#define TALK_BASE_UNIXFILESYSTEM_H_
+
+#include <sys/types.h>
+
+#include "talk/base/fileutils.h"
+
+namespace talk_base {
+
+class UnixFilesystem : public FilesystemInterface {
+ public:
+
+#if defined(ANDROID) || defined(IOS)
+// Android does not have a native code API to fetch the app data or temp
+// folders. That needs to be passed into this class from Java. Similarly, iOS
+// only supports an Objective-C API for fetching the folder locations, so that
+// needs to be passed in here from Objective-C.
+
+  static void SetAppDataFolder(const std::string& folder);
+  static void SetAppTempFolder(const std::string& folder);
+#endif
+
+  // Opens a file. Returns an open StreamInterface if function succeeds.
+  // Otherwise, returns NULL.
+  virtual FileStream *OpenFile(const Pathname &filename,
+                               const std::string &mode);
+
+  // Atomically creates an empty file accessible only to the current user if one
+  // does not already exist at the given path, otherwise fails.
+  virtual bool CreatePrivateFile(const Pathname &filename);
+
+  // This will attempt to delete the file located at filename.
+  // It will fail with VERIY if you pass it a non-existant file, or a directory.
+  virtual bool DeleteFile(const Pathname &filename);
+
+  // This will attempt to delete the folder located at 'folder'
+  // It ASSERTs and returns false if you pass it a non-existant folder or a
+  // plain file.
+  virtual bool DeleteEmptyFolder(const Pathname &folder);
+
+  // Creates a directory. This will call itself recursively to create /foo/bar
+  // even if /foo does not exist. All created directories are created with the
+  // given mode.
+  // Returns TRUE if function succeeds
+  virtual bool CreateFolder(const Pathname &pathname, mode_t mode);
+
+  // As above, with mode = 0755.
+  virtual bool CreateFolder(const Pathname &pathname);
+
+  // This moves a file from old_path to new_path, where "file" can be a plain
+  // file or directory, which will be moved recursively.
+  // Returns true if function succeeds.
+  virtual bool MoveFile(const Pathname &old_path, const Pathname &new_path);
+  virtual bool MoveFolder(const Pathname &old_path, const Pathname &new_path);
+
+  // This copies a file from old_path to _new_path where "file" can be a plain
+  // file or directory, which will be copied recursively.
+  // Returns true if function succeeds
+  virtual bool CopyFile(const Pathname &old_path, const Pathname &new_path);
+
+  // Returns true if a pathname is a directory
+  virtual bool IsFolder(const Pathname& pathname);
+
+  // Returns true if pathname represents a temporary location on the system.
+  virtual bool IsTemporaryPath(const Pathname& pathname);
+
+  // Returns true of pathname represents an existing file
+  virtual bool IsFile(const Pathname& pathname);
+
+  // Returns true if pathname refers to no filesystem object, every parent
+  // directory either exists, or is also absent.
+  virtual bool IsAbsent(const Pathname& pathname);
+
+  virtual std::string TempFilename(const Pathname &dir,
+                                   const std::string &prefix);
+
+  // A folder appropriate for storing temporary files (Contents are
+  // automatically deleted when the program exists)
+  virtual bool GetTemporaryFolder(Pathname &path, bool create,
+                                 const std::string *append);
+
+  virtual bool GetFileSize(const Pathname& path, size_t* size);
+  virtual bool GetFileTime(const Pathname& path, FileTimeType which,
+                           time_t* time);
+
+  // Returns the path to the running application.
+  virtual bool GetAppPathname(Pathname* path);
+
+  virtual bool GetAppDataFolder(Pathname* path, bool per_user);
+
+  // Get a temporary folder that is unique to the current user and application.
+  virtual bool GetAppTempFolder(Pathname* path);
+
+  virtual bool GetDiskFreeSpace(const Pathname& path, int64 *freebytes);
+
+  // Returns the absolute path of the current directory.
+  virtual Pathname GetCurrentDirectory();
+
+ private:
+#if defined(ANDROID) || defined(IOS)
+  static char* provided_app_data_folder_;
+  static char* provided_app_temp_folder_;
+#else
+  static char* app_temp_path_;
+#endif
+
+  static char* CopyString(const std::string& str);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_UNIXFILESYSTEM_H_
diff --git a/talk/base/urlencode.cc b/talk/base/urlencode.cc
new file mode 100644
index 0000000..6fe7178
--- /dev/null
+++ b/talk/base/urlencode.cc
@@ -0,0 +1,196 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/urlencode.h"
+
+#include "talk/base/common.h"
+#include "talk/base/stringutils.h"
+
+static int HexPairValue(const char * code) {
+  int value = 0;
+  const char * pch = code;
+  for (;;) {
+    int digit = *pch++;
+    if (digit >= '0' && digit <= '9') {
+      value += digit - '0';
+    }
+    else if (digit >= 'A' && digit <= 'F') {
+      value += digit - 'A' + 10;
+    }
+    else if (digit >= 'a' && digit <= 'f') {
+      value += digit - 'a' + 10;
+    }
+    else {
+      return -1;
+    }
+    if (pch == code + 2)
+      return value;
+    value <<= 4;
+  }
+}
+
+int InternalUrlDecode(const char *source, char *dest,
+                      bool encode_space_as_plus) {
+  char * start = dest;
+
+  while (*source) {
+    switch (*source) {
+    case '+':
+      if (encode_space_as_plus) {
+        *(dest++) = ' ';
+      } else {
+        *dest++ = *source;
+      }
+      break;
+    case '%':
+      if (source[1] && source[2]) {
+        int value = HexPairValue(source + 1);
+        if (value >= 0) {
+          *(dest++) = value;
+          source += 2;
+        }
+        else {
+          *dest++ = '?';
+        }
+      }
+      else {
+        *dest++ = '?';
+      }
+      break;
+    default:
+      *dest++ = *source;
+    }
+    source++;
+  }
+
+  *dest = 0;
+  return static_cast<int>(dest - start);
+}
+
+int UrlDecode(const char *source, char *dest) {
+  return InternalUrlDecode(source, dest, true);
+}
+
+int UrlDecodeWithoutEncodingSpaceAsPlus(const char *source, char *dest) {
+  return InternalUrlDecode(source, dest, false);
+}
+
+bool IsValidUrlChar(char ch, bool unsafe_only) {
+  if (unsafe_only) {
+    return !(ch <= ' ' || strchr("\\\"^&`<>[]{}", ch));
+  } else {
+    return isalnum(ch) || strchr("-_.!~*'()", ch);
+  }
+}
+
+int InternalUrlEncode(const char *source, char *dest, unsigned int max,
+                      bool encode_space_as_plus, bool unsafe_only) {
+  static const char *digits = "0123456789ABCDEF";
+  if (max == 0) {
+    return 0;
+  }
+
+  char *start = dest;
+  while (static_cast<unsigned>(dest - start) < max && *source) {
+    unsigned char ch = static_cast<unsigned char>(*source);
+    if (*source == ' ' && encode_space_as_plus && !unsafe_only) {
+      *dest++ = '+';
+    } else if (IsValidUrlChar(ch, unsafe_only)) {
+      *dest++ = *source;
+    } else {
+      if (static_cast<unsigned>(dest - start) + 4 > max) {
+        break;
+      }
+      *dest++ = '%';
+      *dest++ = digits[(ch >> 4) & 0x0F];
+      *dest++ = digits[       ch & 0x0F];
+    }
+    source++;
+  }
+  ASSERT(static_cast<unsigned int>(dest - start) < max);
+  *dest = 0;
+
+  return static_cast<int>(dest - start);
+}
+
+int UrlEncode(const char *source, char *dest, unsigned max) {
+  return InternalUrlEncode(source, dest, max, true, false);
+}
+
+int UrlEncodeWithoutEncodingSpaceAsPlus(const char *source, char *dest,
+                                        unsigned max) {
+  return InternalUrlEncode(source, dest, max, false, false);
+}
+
+int UrlEncodeOnlyUnsafeChars(const char *source, char *dest, unsigned max) {
+  return InternalUrlEncode(source, dest, max, false, true);
+}
+
+std::string
+InternalUrlDecodeString(const std::string & encoded,
+                        bool encode_space_as_plus) {
+  size_t needed_length = encoded.length() + 1;
+  char* buf = STACK_ARRAY(char, needed_length);
+  InternalUrlDecode(encoded.c_str(), buf, encode_space_as_plus);
+  return buf;
+}
+
+std::string
+UrlDecodeString(const std::string & encoded) {
+  return InternalUrlDecodeString(encoded, true);
+}
+
+std::string
+UrlDecodeStringWithoutEncodingSpaceAsPlus(const std::string & encoded) {
+  return InternalUrlDecodeString(encoded, false);
+}
+
+std::string
+InternalUrlEncodeString(const std::string & decoded,
+                        bool encode_space_as_plus,
+                        bool unsafe_only) {
+  int needed_length = static_cast<int>(decoded.length()) * 3 + 1;
+  char* buf = STACK_ARRAY(char, needed_length);
+  InternalUrlEncode(decoded.c_str(), buf, needed_length,
+                    encode_space_as_plus, unsafe_only);
+  return buf;
+}
+
+std::string
+UrlEncodeString(const std::string & decoded) {
+  return InternalUrlEncodeString(decoded, true, false);
+}
+
+std::string
+UrlEncodeStringWithoutEncodingSpaceAsPlus(const std::string & decoded) {
+  return InternalUrlEncodeString(decoded, false, false);
+}
+
+std::string
+UrlEncodeStringForOnlyUnsafeChars(const std::string & decoded) {
+  return InternalUrlEncodeString(decoded, false, true);
+}
diff --git a/talk/base/urlencode.h b/talk/base/urlencode.h
new file mode 100644
index 0000000..05165e8
--- /dev/null
+++ b/talk/base/urlencode.h
@@ -0,0 +1,60 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _URLENCODE_H_
+#define _URLENCODE_H_ 
+
+#include <string>
+
+// Decode all encoded characters. Also decode + as space.
+int UrlDecode(const char *source, char *dest);
+
+// Decode all encoded characters.
+int UrlDecodeWithoutEncodingSpaceAsPlus(const char *source, char *dest);
+
+// Encode all characters except alphas, numbers, and -_.!~*'()
+// Also encode space as +.
+int UrlEncode(const char *source, char *dest, unsigned max);
+
+// Encode all characters except alphas, numbers, and -_.!~*'()
+int UrlEncodeWithoutEncodingSpaceAsPlus(const char *source, char *dest,
+                                        unsigned max);
+
+// Encode only unsafe chars, including \ "^&`<>[]{}
+// Also encode space as %20, instead of +
+int UrlEncodeOnlyUnsafeChars(const char *source, char *dest, unsigned max);
+
+std::string UrlDecodeString(const std::string & encoded);
+std::string UrlDecodeStringWithoutEncodingSpaceAsPlus(
+    const std::string & encoded);
+std::string UrlEncodeString(const std::string & decoded);
+std::string UrlEncodeStringWithoutEncodingSpaceAsPlus(
+    const std::string & decoded);
+std::string UrlEncodeStringForOnlyUnsafeChars(const std::string & decoded);
+
+#endif
+
diff --git a/talk/base/urlencode_unittest.cc b/talk/base/urlencode_unittest.cc
new file mode 100644
index 0000000..f71cd75
--- /dev/null
+++ b/talk/base/urlencode_unittest.cc
@@ -0,0 +1,98 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/base/thread.h"
+#include "talk/base/urlencode.h"
+
+TEST(Urlencode, SourceTooLong) {
+  char source[] = "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"
+      "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^";
+  char dest[1];
+  ASSERT_EQ(0, UrlEncode(source, dest, ARRAY_SIZE(dest)));
+  ASSERT_EQ('\0', dest[0]);
+
+  dest[0] = 'a';
+  ASSERT_EQ(0, UrlEncode(source, dest, 0));
+  ASSERT_EQ('a', dest[0]);
+}
+
+TEST(Urlencode, OneCharacterConversion) {
+  char source[] = "^";
+  char dest[4];
+  ASSERT_EQ(3, UrlEncode(source, dest, ARRAY_SIZE(dest)));
+  ASSERT_STREQ("%5E", dest);
+}
+
+TEST(Urlencode, ShortDestinationNoEncoding) {
+  // In this case we have a destination that would not be
+  // big enough to hold an encoding but is big enough to
+  // hold the text given.
+  char source[] = "aa";
+  char dest[3];
+  ASSERT_EQ(2, UrlEncode(source, dest, ARRAY_SIZE(dest)));
+  ASSERT_STREQ("aa", dest);
+}
+
+TEST(Urlencode, ShortDestinationEncoding) {
+  // In this case we have a destination that is not
+  // big enough to hold the encoding.
+  char source[] = "&";
+  char dest[3];
+  ASSERT_EQ(0, UrlEncode(source, dest, ARRAY_SIZE(dest)));
+  ASSERT_EQ('\0', dest[0]);
+}
+
+TEST(Urlencode, Encoding1) {
+  char source[] = "A^ ";
+  char dest[8];
+  ASSERT_EQ(5, UrlEncode(source, dest, ARRAY_SIZE(dest)));
+  ASSERT_STREQ("A%5E+", dest);
+}
+
+TEST(Urlencode, Encoding2) {
+  char source[] = "A^ ";
+  char dest[8];
+  ASSERT_EQ(7, UrlEncodeWithoutEncodingSpaceAsPlus(source, dest,
+                                                   ARRAY_SIZE(dest)));
+  ASSERT_STREQ("A%5E%20", dest);
+}
+
+TEST(Urldecode, Decoding1) {
+  char source[] = "A%5E+";
+  char dest[8];
+  ASSERT_EQ(3, UrlDecode(source, dest));
+  ASSERT_STREQ("A^ ", dest);
+}
+
+TEST(Urldecode, Decoding2) {
+  char source[] = "A%5E+";
+  char dest[8];
+  ASSERT_EQ(3, UrlDecodeWithoutEncodingSpaceAsPlus(source, dest));
+  ASSERT_STREQ("A^+", dest);
+}
diff --git a/talk/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..244568e
--- /dev/null
+++ b/talk/base/virtualsocket_unittest.cc
@@ -0,0 +1,1016 @@
+/*
+ * 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;
+  size_t count;
+  size_t 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(initial_addr.family(),
+                                                 SOCK_DGRAM);
+    socket->Bind(initial_addr);
+    SocketAddress server_addr = socket->GetLocalAddress();
+    // Make sure VSS didn't switch families on us.
+    EXPECT_EQ(server_addr.family(), initial_addr.family());
+
+    TestClient* client1 = new TestClient(new AsyncUDPSocket(socket));
+    AsyncSocket* socket2 =
+        ss_->CreateAsyncSocket(initial_addr.family(), 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);
+
+    SocketAddress empty = EmptySocketAddressWithFamily(initial_addr.family());
+    for (int i = 0; i < 10; i++) {
+      client2 = new TestClient(AsyncUDPSocket::Create(ss_, empty));
+
+      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 =
+        EmptySocketAddressWithFamily(initial_addr.family());
+
+    // Create client
+    AsyncSocket* client = ss_->CreateAsyncSocket(initial_addr.family(),
+                                                 SOCK_STREAM);
+    sink.Monitor(client);
+    EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED);
+    EXPECT_TRUE(client->GetLocalAddress().IsNil());
+
+    // Create server
+    AsyncSocket* server = ss_->CreateAsyncSocket(initial_addr.family(),
+                                                 SOCK_STREAM);
+    sink.Monitor(server);
+    EXPECT_NE(0, server->Listen(5));  // Bind required
+    EXPECT_EQ(0, server->Bind(initial_addr));
+    EXPECT_EQ(server->GetLocalAddress().family(), initial_addr.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(AF_UNSPEC, accept_addr.family());
+
+    // Attempt connect to listening socket
+    EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+    EXPECT_NE(client->GetLocalAddress(), kEmptyAddr);  // Implicit Bind
+    EXPECT_NE(AF_UNSPEC, client->GetLocalAddress().family());  // 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 nil_addr;
+    const SocketAddress empty_addr =
+        EmptySocketAddressWithFamily(initial_addr.family());
+
+    // Create client
+    AsyncSocket* client = ss_->CreateAsyncSocket(initial_addr.family(),
+                                                 SOCK_STREAM);
+    sink.Monitor(client);
+
+    // Create server
+    AsyncSocket* server = ss_->CreateAsyncSocket(initial_addr.family(),
+                                                 SOCK_STREAM);
+    sink.Monitor(server);
+    EXPECT_EQ(0, server->Bind(initial_addr));
+    EXPECT_EQ(server->GetLocalAddress().family(), initial_addr.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, nil_addr);
+
+    // 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(), nil_addr);
+  }
+
+  void CloseDuringConnectTest(const SocketAddress& initial_addr) {
+    testing::StreamSink sink;
+    SocketAddress accept_addr;
+    const SocketAddress empty_addr =
+        EmptySocketAddressWithFamily(initial_addr.family());
+
+    // Create client and server
+    AsyncSocket* client = ss_->CreateAsyncSocket(initial_addr.family(),
+                                                 SOCK_STREAM);
+    sink.Monitor(client);
+    AsyncSocket* server = ss_->CreateAsyncSocket(initial_addr.family(),
+                                                 SOCK_STREAM);
+    sink.Monitor(server);
+
+    // Initiate connect
+    EXPECT_EQ(0, server->Bind(initial_addr));
+    EXPECT_EQ(server->GetLocalAddress().family(), initial_addr.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(initial_addr.family(), SOCK_STREAM);
+    sink.Monitor(server);
+
+    // Initiate connect
+    EXPECT_EQ(0, server->Bind(initial_addr));
+    EXPECT_EQ(server->GetLocalAddress().family(), initial_addr.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(initial_addr.family(), SOCK_STREAM);
+    sink.Monitor(server);
+
+    // Initiate connect
+    EXPECT_EQ(0, server->Bind(initial_addr));
+    EXPECT_EQ(server->GetLocalAddress().family(), initial_addr.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(initial_addr.family(), SOCK_STREAM);
+    sink.Monitor(a);
+    a->Bind(initial_addr);
+    EXPECT_EQ(a->GetLocalAddress().family(), initial_addr.family());
+
+
+    AsyncSocket* b = ss_->CreateAsyncSocket(initial_addr.family(), SOCK_STREAM);
+    sink.Monitor(b);
+    b->Bind(initial_addr);
+    EXPECT_EQ(b->GetLocalAddress().family(), initial_addr.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(initial_addr.family(), SOCK_STREAM);
+    sink.Monitor(a);
+    a->Bind(initial_addr);
+    EXPECT_EQ(a->GetLocalAddress().family(), initial_addr.family());
+
+    AsyncSocket* b = ss_->CreateAsyncSocket(initial_addr.family(), SOCK_STREAM);
+    sink.Monitor(b);
+    b->Bind(initial_addr);
+    EXPECT_EQ(b->GetLocalAddress().family(), initial_addr.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] = static_cast<char>(i % 256);
+    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(initial_addr.family(),
+                                            SOCK_STREAM);
+    AsyncSocket* b = ss_->CreateAsyncSocket(initial_addr.family(),
+                                            SOCK_STREAM);
+    a->Bind(initial_addr);
+    EXPECT_EQ(a->GetLocalAddress().family(), initial_addr.family());
+
+    b->Bind(initial_addr);
+    EXPECT_EQ(b->GetLocalAddress().family(), initial_addr.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 char cNumPackets = 10;
+    for (char i = 0; i < cNumPackets; ++i) {
+      buffer[0] = '0' + i;
+      EXPECT_EQ(1, a->Send(buffer, 1));
+    }
+
+    ss_->ProcessMessagesUntilIdle();
+
+    for (char 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 (char i = 0; i < cNumPackets; ++i) {
+      buffer[0] = 'A' + i;
+      EXPECT_EQ(1, a->Send(buffer, 1));
+    }
+
+    ss_->ProcessMessagesUntilIdle();
+
+    for (char 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(initial_addr.family(), SOCK_DGRAM);
+    AsyncSocket* recv_socket =
+        ss_->CreateAsyncSocket(initial_addr.family(), SOCK_DGRAM);
+    ASSERT_EQ(0, send_socket->Bind(initial_addr));
+    ASSERT_EQ(0, recv_socket->Bind(initial_addr));
+    EXPECT_EQ(send_socket->GetLocalAddress().family(), initial_addr.family());
+    EXPECT_EQ(recv_socket->GetLocalAddress().family(), initial_addr.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(static_cast<unsigned int>(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(initial_addr.family(), SOCK_DGRAM);
+    AsyncSocket* recv_socket =
+        ss_->CreateAsyncSocket(initial_addr.family(), SOCK_DGRAM);
+    ASSERT_EQ(0, send_socket->Bind(initial_addr));
+    ASSERT_EQ(0, recv_socket->Bind(initial_addr));
+    EXPECT_EQ(send_socket->GetLocalAddress().family(), initial_addr.family());
+    EXPECT_EQ(recv_socket->GetLocalAddress().family(), initial_addr.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(client_addr.family(),
+                                                 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(server_addr.family(),
+                                                 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("0.0.0.0", 0),
+                            SocketAddress("::", 5000),
+                            true);
+}
+
+TEST_F(VirtualSocketServerTest, CanSendDatagramFromUnboundIPv4ToIPv6Any) {
+  CrossFamilyDatagramTest(SocketAddress("0.0.0.0", 0),
+                          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..c8cac0e
--- /dev/null
+++ b/talk/base/virtualsocketserver.cc
@@ -0,0 +1,1117 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 family, int type, bool async)
+      : server_(server), family_(family), 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_.IsNil()) {
+      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_.IsNil() && 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_.IsNil()) {
+      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_, AF_INET, 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 {
+    size_t 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_.IsNil()) {
+      error_ = (CS_CONNECTED == state_) ? EISCONN : EINPROGRESS;
+      return -1;
+    }
+    if (local_addr_.IsNil()) {
+      // 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_.IsNil()) {
+      local_addr_ = EmptySocketAddressWithFamily(addr.ipaddr().family());
+      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 static_cast<int>(consumed);
+  }
+
+  VirtualSocketServer* server_;
+  int family_;
+  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_;
+  size_t 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_
+  size_t 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 =
+        HostToNetwork32(NetworkToHost32(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 CreateSocket(AF_INET, type);
+}
+
+Socket* VirtualSocketServer::CreateSocket(int family, int type) {
+  return CreateSocketInternal(family, type);
+}
+
+AsyncSocket* VirtualSocketServer::CreateAsyncSocket(int type) {
+  return CreateAsyncSocket(AF_INET, type);
+}
+
+AsyncSocket* VirtualSocketServer::CreateAsyncSocket(int family, int type) {
+  return CreateSocketInternal(family, type);
+}
+
+VirtualSocket* VirtualSocketServer::CreateSocketInternal(int family, int type) {
+  return new VirtualSocket(this, family, 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(!IPIsUnspec(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 if (!IPIsUnspec(addr->ipaddr())) {
+    addr->SetIP(addr->ipaddr().Normalized());
+  } else {
+    ASSERT(false);
+  }
+
+  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(AF_INET, 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(static_cast<uint32>(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..280ae65
--- /dev/null
+++ b/talk/base/virtualsocketserver.h
@@ -0,0 +1,250 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 Socket* CreateSocket(int family, int type);
+
+  virtual AsyncSocket* CreateAsyncSocket(int type);
+  virtual AsyncSocket* CreateAsyncSocket(int family, 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 family, 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
new file mode 100644
index 0000000..a9fda8a
--- /dev/null
+++ b/talk/base/win32.cc
@@ -0,0 +1,473 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 <winsock2.h>
+#include <ws2tcpip.h>
+#include <algorithm>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/byteorder.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. Implements inet_pton for v4 and v6.
+// 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,
+                 static_cast<socklen_t>(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", NetworkToHost16(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.
+// |src| points to a character string containing an IPv4 network address in
+// dotted-decimal format, "ddd.ddd.ddd.ddd", where ddd is a decimal number
+// of up to three digits in the range 0 to 255.
+// The address is converted and copied to dst,
+// which must be sizeof(struct in_addr) (4) bytes (32 bits) long.
+int inet_pton_v4(const char* src, void* dst) {
+  const int kIpv4AddressSize = 4;
+  int found = 0;
+  const char* src_pos = src;
+  unsigned char result[kIpv4AddressSize] = {0};
+
+  while (*src_pos != '\0') {
+    // strtol won't treat whitespace characters in the begining as an error,
+    // so check to ensure this is started with digit before passing to strtol.
+    if (!isdigit(*src_pos)) {
+      return 0;
+    }
+    char* end_pos;
+    long value = strtol(src_pos, &end_pos, 10);
+    if (value < 0 || value > 255 || src_pos == end_pos) {
+      return 0;
+    }
+    ++found;
+    if (found > kIpv4AddressSize) {
+      return 0;
+    }
+    result[found - 1] = static_cast<unsigned char>(value);
+    src_pos = end_pos;
+    if (*src_pos == '.') {
+      // There's more.
+      ++src_pos;
+    } else if (*src_pos != '\0') {
+      // If it's neither '.' nor '\0' then return fail.
+      return 0;
+    }
+  }
+  if (found != kIpv4AddressSize) {
+    return 0;
+  }
+  memcpy(dst, result, sizeof(result));
+  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 = HostToNetwork16(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
+// convert to/from unix time.
+// The units of FILETIME are 100ns intervals, so by multiplying by or dividing
+// by 10000000, we can convert to/from seconds.
+//
+// FileTime = UnixTime*10000000 + FileTime(1970)
+// UnixTime = (FileTime-FileTime(1970))/10000000
+//
+
+void FileTimeToUnixTime(const FILETIME& ft, time_t* ut) {
+  ASSERT(NULL != ut);
+
+  // FILETIME has an earlier date base than time_t (1/1/1970), so subtract off
+  // the difference.
+  SYSTEMTIME base_st;
+  memset(&base_st, 0, sizeof(base_st));
+  base_st.wDay = 1;
+  base_st.wMonth = 1;
+  base_st.wYear = 1970;
+
+  FILETIME base_ft;
+  SystemTimeToFileTime(&base_st, &base_ft);
+
+  ULARGE_INTEGER base_ul, current_ul;
+  memcpy(&base_ul, &base_ft, sizeof(FILETIME));
+  memcpy(&current_ul, &ft, sizeof(FILETIME));
+
+  // Divide by big number to convert to seconds, then subtract out the 1970
+  // base date value.
+  const ULONGLONG RATIO = 10000000;
+  *ut = static_cast<time_t>((current_ul.QuadPart - base_ul.QuadPart) / RATIO);
+}
+
+void UnixTimeToFileTime(const time_t& ut, FILETIME* ft) {
+  ASSERT(NULL != ft);
+
+  // FILETIME has an earlier date base than time_t (1/1/1970), so add in
+  // the difference.
+  SYSTEMTIME base_st;
+  memset(&base_st, 0, sizeof(base_st));
+  base_st.wDay = 1;
+  base_st.wMonth = 1;
+  base_st.wYear = 1970;
+
+  FILETIME base_ft;
+  SystemTimeToFileTime(&base_st, &base_ft);
+
+  ULARGE_INTEGER base_ul;
+  memcpy(&base_ul, &base_ft, sizeof(FILETIME));
+
+  // Multiply by big number to convert to 100ns units, then add in the 1970
+  // base date value.
+  const ULONGLONG RATIO = 10000000;
+  ULARGE_INTEGER current_ul;
+  current_ul.QuadPart = base_ul.QuadPart + static_cast<int64>(ut) * RATIO;
+  memcpy(ft, &current_ul, sizeof(FILETIME));
+}
+
+bool Utf8ToWindowsFilename(const std::string& utf8, std::wstring* filename) {
+  // TODO: Integrate into fileutils.h
+  // TODO: Handle wide and non-wide cases via TCHAR?
+  // TODO: Skip \\?\ processing if the length is not > MAX_PATH?
+  // TODO: Write unittests
+
+  // Convert to Utf16
+  int wlen = ::MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(),
+                                   static_cast<int>(utf8.length() + 1), NULL,
+                                   0);
+  if (0 == wlen) {
+    return false;
+  }
+  wchar_t* wfilename = STACK_ARRAY(wchar_t, wlen);
+  if (0 == ::MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(),
+                                 static_cast<int>(utf8.length() + 1),
+                                 wfilename, wlen)) {
+    return false;
+  }
+  // Replace forward slashes with backslashes
+  std::replace(wfilename, wfilename + wlen, L'/', L'\\');
+  // Convert to complete filename
+  DWORD full_len = ::GetFullPathName(wfilename, 0, NULL, NULL);
+  if (0 == full_len) {
+    return false;
+  }
+  wchar_t* filepart = NULL;
+  wchar_t* full_filename = STACK_ARRAY(wchar_t, full_len + 6);
+  wchar_t* start = full_filename + 6;
+  if (0 == ::GetFullPathName(wfilename, full_len, start, &filepart)) {
+    return false;
+  }
+  // Add long-path prefix
+  const wchar_t kLongPathPrefix[] = L"\\\\?\\UNC";
+  if ((start[0] != L'\\') || (start[1] != L'\\')) {
+    // Non-unc path:     <pathname>
+    //      Becomes: \\?\<pathname>
+    start -= 4;
+    ASSERT(start >= full_filename);
+    memcpy(start, kLongPathPrefix, 4 * sizeof(wchar_t));
+  } else if (start[2] != L'?') {
+    // Unc path:       \\<server>\<pathname>
+    //  Becomes: \\?\UNC\<server>\<pathname>
+    start -= 6;
+    ASSERT(start >= full_filename);
+    memcpy(start, kLongPathPrefix, 7 * sizeof(wchar_t));
+  } else {
+    // Already in long-path form.
+  }
+  filename->assign(start);
+  return true;
+}
+
+bool GetOsVersion(int* major, int* minor, int* build) {
+  OSVERSIONINFO info = {0};
+  info.dwOSVersionInfoSize = sizeof(info);
+  if (GetVersionEx(&info)) {
+    if (major) *major = info.dwMajorVersion;
+    if (minor) *minor = info.dwMinorVersion;
+    if (build) *build = info.dwBuildNumber;
+    return true;
+  }
+  return false;
+}
+
+bool GetCurrentProcessIntegrityLevel(int* level) {
+  bool ret = false;
+  HANDLE process = ::GetCurrentProcess(), token;
+  if (OpenProcessToken(process, TOKEN_QUERY | TOKEN_QUERY_SOURCE, &token)) {
+    DWORD size;
+    if (!GetTokenInformation(token, TokenIntegrityLevel, NULL, 0, &size) &&
+        GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+
+      char* buf = STACK_ARRAY(char, size);
+      TOKEN_MANDATORY_LABEL* til =
+          reinterpret_cast<TOKEN_MANDATORY_LABEL*>(buf);
+      if (GetTokenInformation(token, TokenIntegrityLevel, til, size, &size)) {
+
+        DWORD count = *GetSidSubAuthorityCount(til->Label.Sid);
+        *level = *GetSidSubAuthority(til->Label.Sid, count - 1);
+        ret = true;
+      }
+    }
+    CloseHandle(token);
+  }
+  return ret;
+}
+}  // namespace talk_base
diff --git a/talk/base/win32.h b/talk/base/win32.h
new file mode 100644
index 0000000..617d639
--- /dev/null
+++ b/talk/base/win32.h
@@ -0,0 +1,146 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_WIN32_H_
+#define TALK_BASE_WIN32_H_
+
+#ifdef WIN32
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+
+// Make sure we don't get min/max macros
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
+
+#include <winsock2.h>
+#include <windows.h>
+
+#ifndef SECURITY_MANDATORY_LABEL_AUTHORITY
+// Add defines that we use if we are compiling against older sdks
+#define SECURITY_MANDATORY_MEDIUM_RID               (0x00002000L)
+#define TokenIntegrityLevel static_cast<TOKEN_INFORMATION_CLASS>(0x19)
+typedef struct _TOKEN_MANDATORY_LABEL {
+    SID_AND_ATTRIBUTES Label;
+} TOKEN_MANDATORY_LABEL, *PTOKEN_MANDATORY_LABEL;
+#endif  // SECURITY_MANDATORY_LABEL_AUTHORITY
+
+#undef SetPort
+
+#include <string>
+
+#include "talk/base/stringutils.h"
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+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) {
+  int len16 = ::MultiByteToWideChar(CP_UTF8, 0, utf8, static_cast<int>(len),
+                                    NULL, 0);
+  wchar_t* ws = STACK_ARRAY(wchar_t, len16);
+  ::MultiByteToWideChar(CP_UTF8, 0, utf8, static_cast<int>(len), ws, len16);
+  return std::wstring(ws, len16);
+}
+
+inline std::wstring ToUtf16(const std::string& str) {
+  return ToUtf16(str.data(), str.length());
+}
+
+inline std::string ToUtf8(const wchar_t* wide, size_t len) {
+  int len8 = ::WideCharToMultiByte(CP_UTF8, 0, wide, static_cast<int>(len),
+                                   NULL, 0, NULL, NULL);
+  char* ns = STACK_ARRAY(char, len8);
+  ::WideCharToMultiByte(CP_UTF8, 0, wide, static_cast<int>(len), ns, len8,
+                        NULL, NULL);
+  return std::string(ns, len8);
+}
+
+inline std::string ToUtf8(const wchar_t* wide) {
+  return ToUtf8(wide, wcslen(wide));
+}
+
+inline std::string ToUtf8(const std::wstring& wstr) {
+  return ToUtf8(wstr.data(), wstr.length());
+}
+
+// Convert FILETIME to time_t
+void FileTimeToUnixTime(const FILETIME& ft, time_t* ut);
+
+// Convert time_t to FILETIME
+void UnixTimeToFileTime(const time_t& ut, FILETIME * ft);
+
+// Convert a Utf8 path representation to a non-length-limited Unicode pathname.
+bool Utf8ToWindowsFilename(const std::string& utf8, std::wstring* filename);
+
+// Convert a FILETIME to a UInt64
+inline uint64 ToUInt64(const FILETIME& ft) {
+  ULARGE_INTEGER r = {ft.dwLowDateTime, ft.dwHighDateTime};
+  return r.QuadPart;
+}
+
+enum WindowsMajorVersions {
+  kWindows2000 = 5,
+  kWindowsVista = 6,
+};
+bool GetOsVersion(int* major, int* minor, int* build);
+
+inline bool IsWindowsVistaOrLater() {
+  int major;
+  return (GetOsVersion(&major, NULL, NULL) && major >= kWindowsVista);
+}
+
+inline bool IsWindowsXpOrLater() {
+  int major, minor;
+  return (GetOsVersion(&major, &minor, NULL) &&
+          (major >= kWindowsVista ||
+          (major == kWindows2000 && minor >= 1)));
+}
+
+// Determine the current integrity level of the process.
+bool GetCurrentProcessIntegrityLevel(int* level);
+
+inline bool IsCurrentProcessLowIntegrity() {
+  int level;
+  return (GetCurrentProcessIntegrityLevel(&level) &&
+      level < SECURITY_MANDATORY_MEDIUM_RID);
+}
+
+bool AdjustCurrentProcessPrivilege(const TCHAR* privilege, bool to_enable);
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // WIN32
+#endif  // TALK_BASE_WIN32_H_
diff --git a/talk/base/win32_unittest.cc b/talk/base/win32_unittest.cc
new file mode 100644
index 0000000..502de5b
--- /dev/null
+++ b/talk/base/win32_unittest.cc
@@ -0,0 +1,79 @@
+/*
+ * 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/nethelpers.h"
+#include "talk/base/win32.h"
+#include "talk/base/winping.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));
+}
+
+TEST_F(Win32Test, WinPingTest) {
+  WinPing ping;
+  ASSERT_TRUE(ping.IsValid());
+
+  // Test valid ping cases.
+  WinPing::PingResult result = ping.Ping(IPAddress(INADDR_LOOPBACK), 20, 50, 1,
+                                         false);
+  ASSERT_EQ(WinPing::PING_SUCCESS, result);
+  if (HasIPv6Enabled()) {
+    WinPing::PingResult v6result = ping.Ping(IPAddress(in6addr_loopback), 20,
+                                             50, 1, false);
+    ASSERT_EQ(WinPing::PING_SUCCESS, v6result);
+  }
+
+  // Test invalid parameter cases.
+  ASSERT_EQ(WinPing::PING_INVALID_PARAMS, ping.Ping(
+            IPAddress(INADDR_LOOPBACK), 0, 50, 1, false));
+  ASSERT_EQ(WinPing::PING_INVALID_PARAMS, ping.Ping(
+            IPAddress(INADDR_LOOPBACK), 20, 0, 1, false));
+  ASSERT_EQ(WinPing::PING_INVALID_PARAMS, ping.Ping(
+            IPAddress(INADDR_LOOPBACK), 20, 50, 0, false));
+}
+
+}  // namespace talk_base
diff --git a/talk/base/win32filesystem.cc b/talk/base/win32filesystem.cc
new file mode 100644
index 0000000..42c0388
--- /dev/null
+++ b/talk/base/win32filesystem.cc
@@ -0,0 +1,477 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/win32filesystem.h"
+
+#include "talk/base/win32.h"
+#include <shellapi.h>
+#include <shlobj.h>
+#include <tchar.h>
+
+#include "talk/base/fileutils.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringutils.h"
+
+// In several places in this file, we test the integrity level of the process
+// before calling GetLongPathName. We do this because calling GetLongPathName
+// when running under protected mode IE (a low integrity process) can result in
+// a virtualized path being returned, which is wrong if you only plan to read.
+// TODO: Waiting to hear back from IE team on whether this is the
+// best approach; IEIsProtectedModeProcess is another possible solution.
+
+namespace talk_base {
+
+bool Win32Filesystem::CreateFolder(const Pathname &pathname) {
+  if (pathname.pathname().empty() || !pathname.filename().empty())
+    return false;
+
+  std::wstring path16;
+  if (!Utf8ToWindowsFilename(pathname.pathname(), &path16))
+    return false;
+
+  DWORD res = ::GetFileAttributes(path16.c_str());
+  if (res != INVALID_FILE_ATTRIBUTES) {
+    // Something exists at this location, check if it is a directory
+    return ((res & FILE_ATTRIBUTE_DIRECTORY) != 0);
+  } else if ((GetLastError() != ERROR_FILE_NOT_FOUND)
+              && (GetLastError() != ERROR_PATH_NOT_FOUND)) {
+    // Unexpected error
+    return false;
+  }
+
+  // Directory doesn't exist, look up one directory level
+  if (!pathname.parent_folder().empty()) {
+    Pathname parent(pathname);
+    parent.SetFolder(pathname.parent_folder());
+    if (!CreateFolder(parent)) {
+      return false;
+    }
+  }
+
+  return (::CreateDirectory(path16.c_str(), NULL) != 0);
+}
+
+FileStream *Win32Filesystem::OpenFile(const Pathname &filename,
+                                      const std::string &mode) {
+  FileStream *fs = new FileStream();
+  if (fs && !fs->Open(filename.pathname().c_str(), mode.c_str(), NULL)) {
+    delete fs;
+    fs = NULL;
+  }
+  return fs;
+}
+
+bool Win32Filesystem::CreatePrivateFile(const Pathname &filename) {
+  // To make the file private to the current user, we first must construct a
+  // SECURITY_DESCRIPTOR specifying an ACL. This code is mostly based upon
+  // http://msdn.microsoft.com/en-us/library/ms707085%28VS.85%29.aspx
+
+  // Get the current process token.
+  HANDLE process_token = INVALID_HANDLE_VALUE;
+  if (!::OpenProcessToken(::GetCurrentProcess(),
+                          TOKEN_QUERY,
+                          &process_token)) {
+    LOG_ERR(LS_ERROR) << "OpenProcessToken() failed";
+    return false;
+  }
+
+  // Get the size of its TOKEN_USER structure. Return value is not checked
+  // because we expect it to fail.
+  DWORD token_user_size = 0;
+  (void)::GetTokenInformation(process_token,
+                              TokenUser,
+                              NULL,
+                              0,
+                              &token_user_size);
+
+  // Get the TOKEN_USER structure.
+  scoped_array<char> token_user_bytes(new char[token_user_size]);
+  PTOKEN_USER token_user = reinterpret_cast<PTOKEN_USER>(
+      token_user_bytes.get());
+  memset(token_user, 0, token_user_size);
+  BOOL success = ::GetTokenInformation(process_token,
+                                       TokenUser,
+                                       token_user,
+                                       token_user_size,
+                                       &token_user_size);
+  // We're now done with this.
+  ::CloseHandle(process_token);
+  if (!success) {
+    LOG_ERR(LS_ERROR) << "GetTokenInformation() failed";
+    return false;
+  }
+
+  if (!IsValidSid(token_user->User.Sid)) {
+    LOG_ERR(LS_ERROR) << "Current process has invalid user SID";
+    return false;
+  }
+
+  // Compute size needed for an ACL that allows access to just this user.
+  int acl_size = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) +
+      GetLengthSid(token_user->User.Sid);
+
+  // Allocate it.
+  scoped_array<char> acl_bytes(new char[acl_size]);
+  PACL acl = reinterpret_cast<PACL>(acl_bytes.get());
+  memset(acl, 0, acl_size);
+  if (!::InitializeAcl(acl, acl_size, ACL_REVISION)) {
+    LOG_ERR(LS_ERROR) << "InitializeAcl() failed";
+    return false;
+  }
+
+  // Allow access to only the current user.
+  if (!::AddAccessAllowedAce(acl,
+                             ACL_REVISION,
+                             GENERIC_READ | GENERIC_WRITE | STANDARD_RIGHTS_ALL,
+                             token_user->User.Sid)) {
+    LOG_ERR(LS_ERROR) << "AddAccessAllowedAce() failed";
+    return false;
+  }
+
+  // Now make the security descriptor.
+  SECURITY_DESCRIPTOR security_descriptor;
+  if (!::InitializeSecurityDescriptor(&security_descriptor,
+                                      SECURITY_DESCRIPTOR_REVISION)) {
+    LOG_ERR(LS_ERROR) << "InitializeSecurityDescriptor() failed";
+    return false;
+  }
+
+  // Put the ACL in it.
+  if (!::SetSecurityDescriptorDacl(&security_descriptor,
+                                   TRUE,
+                                   acl,
+                                   FALSE)) {
+    LOG_ERR(LS_ERROR) << "SetSecurityDescriptorDacl() failed";
+    return false;
+  }
+
+  // Finally create the file.
+  SECURITY_ATTRIBUTES security_attributes;
+  security_attributes.nLength = sizeof(security_attributes);
+  security_attributes.lpSecurityDescriptor = &security_descriptor;
+  security_attributes.bInheritHandle = FALSE;
+  HANDLE handle = ::CreateFile(
+      ToUtf16(filename.pathname()).c_str(),
+      GENERIC_READ | GENERIC_WRITE,
+      FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
+      &security_attributes,
+      CREATE_NEW,
+      0,
+      NULL);
+  if (INVALID_HANDLE_VALUE == handle) {
+    LOG_ERR(LS_ERROR) << "CreateFile() failed";
+    return false;
+  }
+  if (!::CloseHandle(handle)) {
+    LOG_ERR(LS_ERROR) << "CloseFile() failed";
+    // Continue.
+  }
+  return true;
+}
+
+bool Win32Filesystem::DeleteFile(const Pathname &filename) {
+  LOG(LS_INFO) << "Deleting file " << filename.pathname();
+  if (!IsFile(filename)) {
+    ASSERT(IsFile(filename));
+    return false;
+  }
+  return ::DeleteFile(ToUtf16(filename.pathname()).c_str()) != 0;
+}
+
+bool Win32Filesystem::DeleteEmptyFolder(const Pathname &folder) {
+  LOG(LS_INFO) << "Deleting folder " << folder.pathname();
+
+  std::string no_slash(folder.pathname(), 0, folder.pathname().length()-1);
+  return ::RemoveDirectory(ToUtf16(no_slash).c_str()) != 0;
+}
+
+bool Win32Filesystem::GetTemporaryFolder(Pathname &pathname, bool create,
+                                         const std::string *append) {
+  wchar_t buffer[MAX_PATH + 1];
+  if (!::GetTempPath(ARRAY_SIZE(buffer), buffer))
+    return false;
+  if (!IsCurrentProcessLowIntegrity() &&
+      !::GetLongPathName(buffer, buffer, ARRAY_SIZE(buffer)))
+    return false;
+  size_t len = strlen(buffer);
+  if ((len > 0) && (buffer[len-1] != '\\')) {
+    len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, L"\\");
+  }
+  if (len >= ARRAY_SIZE(buffer) - 1)
+    return false;
+  pathname.clear();
+  pathname.SetFolder(ToUtf8(buffer));
+  if (append != NULL) {
+    ASSERT(!append->empty());
+    pathname.AppendFolder(*append);
+  }
+  return !create || CreateFolder(pathname);
+}
+
+std::string Win32Filesystem::TempFilename(const Pathname &dir,
+                                          const std::string &prefix) {
+  wchar_t filename[MAX_PATH];
+  if (::GetTempFileName(ToUtf16(dir.pathname()).c_str(),
+                        ToUtf16(prefix).c_str(), 0, filename) != 0)
+    return ToUtf8(filename);
+  ASSERT(false);
+  return "";
+}
+
+bool Win32Filesystem::MoveFile(const Pathname &old_path,
+                               const Pathname &new_path) {
+  if (!IsFile(old_path)) {
+    ASSERT(IsFile(old_path));
+    return false;
+  }
+  LOG(LS_INFO) << "Moving " << old_path.pathname()
+               << " to " << new_path.pathname();
+  return ::MoveFile(ToUtf16(old_path.pathname()).c_str(),
+                    ToUtf16(new_path.pathname()).c_str()) != 0;
+}
+
+bool Win32Filesystem::MoveFolder(const Pathname &old_path,
+                                 const Pathname &new_path) {
+  if (!IsFolder(old_path)) {
+    ASSERT(IsFolder(old_path));
+    return false;
+  }
+  LOG(LS_INFO) << "Moving " << old_path.pathname()
+               << " to " << new_path.pathname();
+  if (::MoveFile(ToUtf16(old_path.pathname()).c_str(),
+               ToUtf16(new_path.pathname()).c_str()) == 0) {
+    if (::GetLastError() != ERROR_NOT_SAME_DEVICE) {
+      LOG_GLE(LS_ERROR) << "Failed to move file";
+      return false;
+    }
+    if (!CopyFolder(old_path, new_path))
+      return false;
+    if (!DeleteFolderAndContents(old_path))
+      return false;
+  }
+  return true;
+}
+
+bool Win32Filesystem::IsFolder(const Pathname &path) {
+  WIN32_FILE_ATTRIBUTE_DATA data = {0};
+  if (0 == ::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(),
+                                 GetFileExInfoStandard, &data))
+    return false;
+  return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ==
+      FILE_ATTRIBUTE_DIRECTORY;
+}
+
+bool Win32Filesystem::IsFile(const Pathname &path) {
+  WIN32_FILE_ATTRIBUTE_DATA data = {0};
+  if (0 == ::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(),
+                                 GetFileExInfoStandard, &data))
+    return false;
+  return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
+}
+
+bool Win32Filesystem::IsAbsent(const Pathname& path) {
+  WIN32_FILE_ATTRIBUTE_DATA data = {0};
+  if (0 != ::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(),
+                                 GetFileExInfoStandard, &data))
+    return false;
+  DWORD err = ::GetLastError();
+  return (ERROR_FILE_NOT_FOUND == err || ERROR_PATH_NOT_FOUND == err);
+}
+
+bool Win32Filesystem::CopyFile(const Pathname &old_path,
+                               const Pathname &new_path) {
+  return ::CopyFile(ToUtf16(old_path.pathname()).c_str(),
+                    ToUtf16(new_path.pathname()).c_str(), TRUE) != 0;
+}
+
+bool Win32Filesystem::IsTemporaryPath(const Pathname& pathname) {
+  TCHAR buffer[MAX_PATH + 1];
+  if (!::GetTempPath(ARRAY_SIZE(buffer), buffer))
+    return false;
+  if (!IsCurrentProcessLowIntegrity() &&
+      !::GetLongPathName(buffer, buffer, ARRAY_SIZE(buffer)))
+    return false;
+  return (::strnicmp(ToUtf16(pathname.pathname()).c_str(),
+                     buffer, strlen(buffer)) == 0);
+}
+
+bool Win32Filesystem::GetFileSize(const Pathname &pathname, size_t *size) {
+  WIN32_FILE_ATTRIBUTE_DATA data = {0};
+  if (::GetFileAttributesEx(ToUtf16(pathname.pathname()).c_str(),
+                            GetFileExInfoStandard, &data) == 0)
+  return false;
+  *size = data.nFileSizeLow;
+  return true;
+}
+
+bool Win32Filesystem::GetFileTime(const Pathname& path, FileTimeType which,
+                                  time_t* time) {
+  WIN32_FILE_ATTRIBUTE_DATA data = {0};
+  if (::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(),
+                            GetFileExInfoStandard, &data) == 0)
+    return false;
+  switch (which) {
+  case FTT_CREATED:
+    FileTimeToUnixTime(data.ftCreationTime, time);
+    break;
+  case FTT_MODIFIED:
+    FileTimeToUnixTime(data.ftLastWriteTime, time);
+    break;
+  case FTT_ACCESSED:
+    FileTimeToUnixTime(data.ftLastAccessTime, time);
+    break;
+  default:
+    return false;
+  }
+  return true;
+}
+
+bool Win32Filesystem::GetAppPathname(Pathname* path) {
+  TCHAR buffer[MAX_PATH + 1];
+  if (0 == ::GetModuleFileName(NULL, buffer, ARRAY_SIZE(buffer)))
+    return false;
+  path->SetPathname(ToUtf8(buffer));
+  return true;
+}
+
+bool Win32Filesystem::GetAppDataFolder(Pathname* path, bool per_user) {
+  ASSERT(!organization_name_.empty());
+  ASSERT(!application_name_.empty());
+  TCHAR buffer[MAX_PATH + 1];
+  int csidl = per_user ? CSIDL_LOCAL_APPDATA : CSIDL_COMMON_APPDATA;
+  if (!::SHGetSpecialFolderPath(NULL, buffer, csidl, TRUE))
+    return false;
+  if (!IsCurrentProcessLowIntegrity() &&
+      !::GetLongPathName(buffer, buffer, ARRAY_SIZE(buffer)))
+    return false;
+  size_t len = strcatn(buffer, ARRAY_SIZE(buffer), __T("\\"));
+  len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len,
+                 ToUtf16(organization_name_).c_str());
+  if ((len > 0) && (buffer[len-1] != __T('\\'))) {
+    len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, __T("\\"));
+  }
+  len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len,
+                 ToUtf16(application_name_).c_str());
+  if ((len > 0) && (buffer[len-1] != __T('\\'))) {
+    len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, __T("\\"));
+  }
+  if (len >= ARRAY_SIZE(buffer) - 1)
+    return false;
+  path->clear();
+  path->SetFolder(ToUtf8(buffer));
+  return CreateFolder(*path);
+}
+
+bool Win32Filesystem::GetAppTempFolder(Pathname* path) {
+  if (!GetAppPathname(path))
+    return false;
+  std::string filename(path->filename());
+  return GetTemporaryFolder(*path, true, &filename);
+}
+
+bool Win32Filesystem::GetDiskFreeSpace(const Pathname& path, int64 *freebytes) {
+  if (!freebytes) {
+    return false;
+  }
+  char drive[4];
+  std::wstring drive16;
+  const wchar_t* target_drive = NULL;
+  if (path.GetDrive(drive, sizeof(drive))) {
+    drive16 = ToUtf16(drive);
+    target_drive = drive16.c_str();
+  } else if (path.folder().substr(0, 2) == "\\\\") {
+    // UNC path, fail.
+    // TODO: Handle UNC paths.
+    return false;
+  } else {
+    // The path is probably relative.  GetDriveType and GetDiskFreeSpaceEx
+    // use the current drive if NULL is passed as the drive name.
+    // TODO: Add method to Pathname to determine if the path is relative.
+    // TODO: Add method to Pathname to convert a path to absolute.
+  }
+  UINT driveType = ::GetDriveType(target_drive);
+  if ( (driveType & DRIVE_REMOTE) || (driveType & DRIVE_UNKNOWN) ) {
+    LOG(LS_VERBOSE) << " remove or unknown drive " << drive;
+    return false;
+  }
+
+  int64 totalNumberOfBytes;  // receives the number of bytes on disk
+  int64 totalNumberOfFreeBytes;  // receives the free bytes on disk
+  // make sure things won't change in 64 bit machine
+  // TODO replace with compile time assert
+  ASSERT(sizeof(ULARGE_INTEGER) == sizeof(uint64));  //NOLINT
+  if (::GetDiskFreeSpaceEx(target_drive,
+                           (PULARGE_INTEGER)freebytes,
+                           (PULARGE_INTEGER)&totalNumberOfBytes,
+                           (PULARGE_INTEGER)&totalNumberOfFreeBytes)) {
+    return true;
+  } else {
+    LOG(LS_VERBOSE) << " GetDiskFreeSpaceEx returns error ";
+    return false;
+  }
+}
+
+Pathname Win32Filesystem::GetCurrentDirectory() {
+  Pathname cwd;
+  int path_len = 0;
+  scoped_array<wchar_t> path;
+  do {
+    int needed = ::GetCurrentDirectory(path_len, path.get());
+    if (needed == 0) {
+      // Error.
+      LOG_GLE(LS_ERROR) << "::GetCurrentDirectory() failed";
+      return cwd;  // returns empty pathname
+    }
+    if (needed <= path_len) {
+      // It wrote successfully.
+      break;
+    }
+    // Else need to re-alloc for "needed".
+    path.reset(new wchar_t[needed]);
+    path_len = needed;
+  } while (true);
+  cwd.SetFolder(ToUtf8(path.get()));
+  return cwd;
+}
+
+// TODO: Consider overriding DeleteFolderAndContents for speed and potentially
+// better OS integration (recycle bin?)
+/*
+  std::wstring temp_path16 = ToUtf16(temp_path.pathname());
+  temp_path16.append(1, '*');
+  temp_path16.append(1, '\0');
+
+  SHFILEOPSTRUCT file_op = { 0 };
+  file_op.wFunc = FO_DELETE;
+  file_op.pFrom = temp_path16.c_str();
+  file_op.fFlags = FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT;
+  return (0 == SHFileOperation(&file_op));
+*/
+
+}  // namespace talk_base
diff --git a/talk/base/win32filesystem.h b/talk/base/win32filesystem.h
new file mode 100644
index 0000000..c17bdd9
--- /dev/null
+++ b/talk/base/win32filesystem.h
@@ -0,0 +1,118 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _TALK_BASE_WIN32FILESYSTEM_H__
+#define _TALK_BASE_WIN32FILESYSTEM_H__
+
+#include "fileutils.h"
+
+namespace talk_base {
+
+class Win32Filesystem : public FilesystemInterface {
+ public:
+  // Opens a file. Returns an open StreamInterface if function succeeds. Otherwise,
+  // returns NULL.
+  virtual FileStream *OpenFile(const Pathname &filename, 
+                               const std::string &mode);
+
+  // Atomically creates an empty file accessible only to the current user if one
+  // does not already exist at the given path, otherwise fails.
+  virtual bool CreatePrivateFile(const Pathname &filename);
+
+  // This will attempt to delete the path located at filename.
+  // If the path points to a folder, it will fail with VERIFY
+  virtual bool DeleteFile(const Pathname &filename);
+
+  // This will attempt to delete an empty folder. If the path does not point to
+  // a folder, it fails with VERIFY. If the folder is not empty, it fails normally
+  virtual bool DeleteEmptyFolder(const Pathname &folder);
+
+  // Creates a directory. This will call itself recursively to create /foo/bar even if
+  // /foo does not exist.
+  // Returns TRUE if function succeeds
+  virtual bool CreateFolder(const Pathname &pathname);
+  
+  // This moves a file from old_path to new_path. If the new path is on a 
+  // different volume than the old, it will attempt to copy and then delete
+  // the folder
+  // Returns true if the file is successfully moved
+  virtual bool MoveFile(const Pathname &old_path, const Pathname &new_path);
+  
+  // Moves a folder from old_path to new_path. If the new path is on a different
+  // volume from the old, it will attempt to Copy and then Delete the folder
+  // Returns true if the folder is successfully moved
+  virtual bool MoveFolder(const Pathname &old_path, const Pathname &new_path);
+  
+  // This copies a file from old_path to _new_path
+  // Returns true if function succeeds
+  virtual bool CopyFile(const Pathname &old_path, const Pathname &new_path);
+
+  // Returns true if a pathname is a directory
+  virtual bool IsFolder(const Pathname& pathname);
+  
+  // Returns true if a file exists at path
+  virtual bool IsFile(const Pathname &path);
+
+  // Returns true if pathname refers to no filesystem object, every parent
+  // directory either exists, or is also absent.
+  virtual bool IsAbsent(const Pathname& pathname);
+
+  // Returns true if pathname represents a temporary location on the system.
+  virtual bool IsTemporaryPath(const Pathname& pathname);
+
+  // All of the following functions set pathname and return true if successful.
+  // Returned paths always include a trailing backslash.
+  // If create is true, the path will be recursively created.
+  // If append is non-NULL, it will be appended (and possibly created).
+
+  virtual std::string TempFilename(const Pathname &dir, const std::string &prefix);
+
+  virtual bool GetFileSize(const Pathname& path, size_t* size);
+  virtual bool GetFileTime(const Pathname& path, FileTimeType which,
+                           time_t* time);
+ 
+  // A folder appropriate for storing temporary files (Contents are
+  // automatically deleted when the program exists)
+  virtual bool GetTemporaryFolder(Pathname &path, bool create,
+                                 const std::string *append);
+
+  // Returns the path to the running application.
+  virtual bool GetAppPathname(Pathname* path);
+
+  virtual bool GetAppDataFolder(Pathname* path, bool per_user);
+
+  // Get a temporary folder that is unique to the current user and application.
+  virtual bool GetAppTempFolder(Pathname* path);
+
+  virtual bool GetDiskFreeSpace(const Pathname& path, int64 *freebytes);
+
+  virtual Pathname GetCurrentDirectory();
+};
+
+}  // namespace talk_base
+
+#endif  // _WIN32FILESYSTEM_H__
diff --git a/talk/base/win32regkey.cc b/talk/base/win32regkey.cc
new file mode 100644
index 0000000..cb98caf
--- /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
+  size_t 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
+  size_t 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/win32securityerrors.cc b/talk/base/win32securityerrors.cc
new file mode 100644
index 0000000..50f4f66
--- /dev/null
+++ b/talk/base/win32securityerrors.cc
@@ -0,0 +1,66 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/win32.h"
+#include "talk/base/logging.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+
+extern const ConstantLabel SECURITY_ERRORS[];
+
+const ConstantLabel SECURITY_ERRORS[] = {
+  KLABEL(SEC_I_COMPLETE_AND_CONTINUE),
+  KLABEL(SEC_I_COMPLETE_NEEDED),
+  KLABEL(SEC_I_CONTEXT_EXPIRED),
+  KLABEL(SEC_I_CONTINUE_NEEDED),
+  KLABEL(SEC_I_INCOMPLETE_CREDENTIALS),
+  KLABEL(SEC_I_RENEGOTIATE),
+  KLABEL(SEC_E_CERT_EXPIRED),
+  KLABEL(SEC_E_INCOMPLETE_MESSAGE),
+  KLABEL(SEC_E_INSUFFICIENT_MEMORY),
+  KLABEL(SEC_E_INTERNAL_ERROR),
+  KLABEL(SEC_E_INVALID_HANDLE),
+  KLABEL(SEC_E_INVALID_TOKEN),
+  KLABEL(SEC_E_LOGON_DENIED),
+  KLABEL(SEC_E_NO_AUTHENTICATING_AUTHORITY),
+  KLABEL(SEC_E_NO_CREDENTIALS),
+  KLABEL(SEC_E_NOT_OWNER),
+  KLABEL(SEC_E_OK),
+  KLABEL(SEC_E_SECPKG_NOT_FOUND),
+  KLABEL(SEC_E_TARGET_UNKNOWN),
+  KLABEL(SEC_E_UNKNOWN_CREDENTIALS),
+  KLABEL(SEC_E_UNSUPPORTED_FUNCTION),
+  KLABEL(SEC_E_UNTRUSTED_ROOT),
+  KLABEL(SEC_E_WRONG_PRINCIPAL),
+  LASTLABEL
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
diff --git a/talk/base/win32socketinit.cc b/talk/base/win32socketinit.cc
new file mode 100644
index 0000000..f6ac666
--- /dev/null
+++ b/talk/base/win32socketinit.cc
@@ -0,0 +1,63 @@
+/*
+ * libjingle
+ * Copyright 2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/win32socketinit.h"
+
+#include "talk/base/win32.h"
+
+namespace talk_base {
+
+// Please don't remove this function.
+void EnsureWinsockInit() {
+  // The default implementation uses a global initializer, so WSAStartup
+  // happens at module load time.  Thus we don't need to do anything here.
+  // The hook is provided so that a client that statically links with
+  // libjingle can override it, to provide its own initialization.
+}
+
+#ifdef WIN32
+class WinsockInitializer {
+ public:
+  WinsockInitializer() {
+    WSADATA wsaData;
+    WORD wVersionRequested = MAKEWORD(1, 0);
+    err_ = WSAStartup(wVersionRequested, &wsaData);
+  }
+  ~WinsockInitializer() {
+    if (!err_)
+      WSACleanup();
+  }
+  int error() {
+    return err_;
+  }
+ private:
+  int err_;
+};
+WinsockInitializer g_winsockinit;
+#endif
+
+}  // namespace talk_base
diff --git a/talk/base/win32socketinit.h b/talk/base/win32socketinit.h
new file mode 100644
index 0000000..f56b7ff
--- /dev/null
+++ b/talk/base/win32socketinit.h
@@ -0,0 +1,37 @@
+/*
+ * libjingle
+ * Copyright 2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_WIN32SOCKETINIT_H_
+#define TALK_BASE_WIN32SOCKETINIT_H_
+
+namespace talk_base {
+
+void EnsureWinsockInit();
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_WIN32SOCKETINIT_H_
diff --git a/talk/base/win32socketserver.cc b/talk/base/win32socketserver.cc
new file mode 100644
index 0000000..55128e7
--- /dev/null
+++ b/talk/base/win32socketserver.cc
@@ -0,0 +1,864 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/win32socketserver.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/winping.h"
+#include "talk/base/win32window.h"
+#include <ws2tcpip.h>  // NOLINT
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// Win32Socket
+///////////////////////////////////////////////////////////////////////////////
+
+// TODO: Move this to a common place where PhysicalSocketServer can
+// share it.
+// Standard MTUs
+static const uint16 PACKET_MAXIMUMS[] = {
+  65535,    // Theoretical maximum, Hyperchannel
+  32000,    // Nothing
+  17914,    // 16Mb IBM Token Ring
+  8166,     // IEEE 802.4
+  // 4464   // IEEE 802.5 (4Mb max)
+  4352,     // FDDI
+  // 2048,  // Wideband Network
+  2002,     // IEEE 802.5 (4Mb recommended)
+  // 1536,  // Expermental Ethernet Networks
+  // 1500,  // Ethernet, Point-to-Point (default)
+  1492,     // IEEE 802.3
+  1006,     // SLIP, ARPANET
+  // 576,   // X.25 Networks
+  // 544,   // DEC IP Portal
+  // 512,   // NETBIOS
+  508,      // IEEE 802/Source-Rt Bridge, ARCNET
+  296,      // Point-to-Point (low delay)
+  68,       // Official minimum
+  0,        // End of list marker
+};
+
+static const int IP_HEADER_SIZE = 20u;
+static const int ICMP_HEADER_SIZE = 8u;
+static const int ICMP_PING_TIMEOUT_MILLIS = 10000u;
+
+// TODO: Enable for production builds also? Use FormatMessage?
+#ifdef _DEBUG
+LPCSTR WSAErrorToString(int error, LPCSTR *description_result) {
+  LPCSTR string = "Unspecified";
+  LPCSTR description = "Unspecified description";
+  switch (error) {
+    case ERROR_SUCCESS:
+      string = "SUCCESS";
+      description = "Operation succeeded";
+      break;
+    case WSAEWOULDBLOCK:
+      string = "WSAEWOULDBLOCK";
+      description = "Using a non-blocking socket, will notify later";
+      break;
+    case WSAEACCES:
+      string = "WSAEACCES";
+      description = "Access denied, or sharing violation";
+      break;
+    case WSAEADDRNOTAVAIL:
+      string = "WSAEADDRNOTAVAIL";
+      description = "Address is not valid in this context";
+      break;
+    case WSAENETDOWN:
+      string = "WSAENETDOWN";
+      description = "Network is down";
+      break;
+    case WSAENETUNREACH:
+      string = "WSAENETUNREACH";
+      description = "Network is up, but unreachable";
+      break;
+    case WSAENETRESET:
+      string = "WSANETRESET";
+      description = "Connection has been reset due to keep-alive activity";
+      break;
+    case WSAECONNABORTED:
+      string = "WSAECONNABORTED";
+      description = "Aborted by host";
+      break;
+    case WSAECONNRESET:
+      string = "WSAECONNRESET";
+      description = "Connection reset by host";
+      break;
+    case WSAETIMEDOUT:
+      string = "WSAETIMEDOUT";
+      description = "Timed out, host failed to respond";
+      break;
+    case WSAECONNREFUSED:
+      string = "WSAECONNREFUSED";
+      description = "Host actively refused connection";
+      break;
+    case WSAEHOSTDOWN:
+      string = "WSAEHOSTDOWN";
+      description = "Host is down";
+      break;
+    case WSAEHOSTUNREACH:
+      string = "WSAEHOSTUNREACH";
+      description = "Host is unreachable";
+      break;
+    case WSAHOST_NOT_FOUND:
+      string = "WSAHOST_NOT_FOUND";
+      description = "No such host is known";
+      break;
+  }
+  if (description_result) {
+    *description_result = description;
+  }
+  return string;
+}
+
+void ReportWSAError(LPCSTR context, int error, const SocketAddress& address) {
+  LPCSTR description_string;
+  LPCSTR error_string = WSAErrorToString(error, &description_string);
+  LOG(LS_INFO) << context << " = " << error
+    << " (" << error_string << ":" << description_string << ") ["
+    << address.ToString() << "]";
+}
+#else
+void ReportWSAError(LPCSTR context, int error, const SocketAddress& address) {}
+#endif
+
+/////////////////////////////////////////////////////////////////////////////
+// Win32Socket::EventSink
+/////////////////////////////////////////////////////////////////////////////
+
+#define WM_SOCKETNOTIFY  (WM_USER + 50)
+#define WM_DNSNOTIFY     (WM_USER + 51)
+
+struct Win32Socket::DnsLookup {
+  HANDLE handle;
+  uint16 port;
+  char buffer[MAXGETHOSTSTRUCT];
+};
+
+class Win32Socket::EventSink : public Win32Window {
+ public:
+  explicit EventSink(Win32Socket * parent) : parent_(parent) { }
+
+  void Dispose();
+
+  virtual bool OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam,
+                         LRESULT& result);
+  virtual void OnNcDestroy();
+
+ private:
+  bool OnSocketNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& result);
+  bool OnDnsNotify(WPARAM wParam, LPARAM lParam, LRESULT& result);
+
+  Win32Socket * parent_;
+};
+
+void Win32Socket::EventSink::Dispose() {
+  parent_ = NULL;
+  if (::IsWindow(handle())) {
+    ::DestroyWindow(handle());
+  } else {
+    delete this;
+  }
+}
+
+bool Win32Socket::EventSink::OnMessage(UINT uMsg, WPARAM wParam,
+                                       LPARAM lParam, LRESULT& result) {
+  switch (uMsg) {
+  case WM_SOCKETNOTIFY:
+  case WM_TIMER:
+    return OnSocketNotify(uMsg, wParam, lParam, result);
+  case WM_DNSNOTIFY:
+    return OnDnsNotify(wParam, lParam, result);
+  }
+  return false;
+}
+
+bool Win32Socket::EventSink::OnSocketNotify(UINT uMsg, WPARAM wParam,
+                                            LPARAM lParam, LRESULT& result) {
+  result = 0;
+
+  int wsa_event = WSAGETSELECTEVENT(lParam);
+  int wsa_error = WSAGETSELECTERROR(lParam);
+
+  // Treat connect timeouts as close notifications
+  if (uMsg == WM_TIMER) {
+    wsa_event = FD_CLOSE;
+    wsa_error = WSAETIMEDOUT;
+  }
+
+  if (parent_)
+    parent_->OnSocketNotify(static_cast<SOCKET>(wParam), wsa_event, wsa_error);
+  return true;
+}
+
+bool Win32Socket::EventSink::OnDnsNotify(WPARAM wParam, LPARAM lParam,
+                                         LRESULT& result) {
+  result = 0;
+
+  int error = WSAGETASYNCERROR(lParam);
+  if (parent_)
+    parent_->OnDnsNotify(reinterpret_cast<HANDLE>(wParam), error);
+  return true;
+}
+
+void Win32Socket::EventSink::OnNcDestroy() {
+  if (parent_) {
+    LOG(LS_ERROR) << "EventSink hwnd is being destroyed, but the event sink"
+                     " hasn't yet been disposed.";
+  } else {
+    delete this;
+  }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Win32Socket
+/////////////////////////////////////////////////////////////////////////////
+
+Win32Socket::Win32Socket()
+    : socket_(INVALID_SOCKET), error_(0), state_(CS_CLOSED), connect_time_(0),
+      closing_(false), close_error_(0), sink_(NULL), dns_(NULL) {
+}
+
+Win32Socket::~Win32Socket() {
+  Close();
+}
+
+bool Win32Socket::CreateT(int family, int type) {
+  Close();
+  int proto = (SOCK_DGRAM == type) ? IPPROTO_UDP : IPPROTO_TCP;
+  socket_ = ::WSASocket(family, type, proto, NULL, NULL, 0);
+  if (socket_ == INVALID_SOCKET) {
+    UpdateLastError();
+    return false;
+  }
+  if ((SOCK_DGRAM == type) && !SetAsync(FD_READ | FD_WRITE)) {
+    return false;
+  }
+  return true;
+}
+
+int Win32Socket::Attach(SOCKET s) {
+  ASSERT(socket_ == INVALID_SOCKET);
+  if (socket_ != INVALID_SOCKET)
+    return SOCKET_ERROR;
+
+  ASSERT(s != INVALID_SOCKET);
+  if (s == INVALID_SOCKET)
+    return SOCKET_ERROR;
+
+  socket_ = s;
+  state_ = CS_CONNECTED;
+
+  if (!SetAsync(FD_READ | FD_WRITE | FD_CLOSE))
+    return SOCKET_ERROR;
+
+  return 0;
+}
+
+void Win32Socket::SetTimeout(int ms) {
+  if (sink_)
+    ::SetTimer(sink_->handle(), 1, ms, 0);
+}
+
+SocketAddress Win32Socket::GetLocalAddress() const {
+  sockaddr_storage addr = {0};
+  socklen_t addrlen = sizeof(addr);
+  int result = ::getsockname(socket_, reinterpret_cast<sockaddr*>(&addr),
+                             &addrlen);
+  SocketAddress address;
+  if (result >= 0) {
+    SocketAddressFromSockAddrStorage(addr, &address);
+  } else {
+    LOG(LS_WARNING) << "GetLocalAddress: unable to get local addr, socket="
+                    << socket_;
+  }
+  return address;
+}
+
+SocketAddress Win32Socket::GetRemoteAddress() const {
+  sockaddr_storage addr = {0};
+  socklen_t addrlen = sizeof(addr);
+  int result = ::getpeername(socket_, reinterpret_cast<sockaddr*>(&addr),
+                             &addrlen);
+  SocketAddress address;
+  if (result >= 0) {
+    SocketAddressFromSockAddrStorage(addr, &address);
+  } else {
+    LOG(LS_WARNING) << "GetRemoteAddress: unable to get remote addr, socket="
+                    << socket_;
+  }
+  return address;
+}
+
+int Win32Socket::Bind(const SocketAddress& addr) {
+  ASSERT(socket_ != INVALID_SOCKET);
+  if (socket_ == INVALID_SOCKET)
+    return SOCKET_ERROR;
+
+  sockaddr_storage saddr;
+  size_t len = addr.ToSockAddrStorage(&saddr);
+  int err = ::bind(socket_,
+                   reinterpret_cast<sockaddr*>(&saddr),
+                   static_cast<int>(len));
+  UpdateLastError();
+  return err;
+}
+
+int Win32Socket::Connect(const SocketAddress& addr) {
+  if (state_ != CS_CLOSED) {
+    SetError(EALREADY);
+    return SOCKET_ERROR;
+  }
+
+  if (!addr.IsUnresolvedIP()) {
+    return DoConnect(addr);
+  }
+
+  LOG_F(LS_INFO) << "async dns lookup (" << addr.hostname() << ")";
+  DnsLookup * dns = new DnsLookup;
+  if (!sink_) {
+    // Explicitly create the sink ourselves here; we can't rely on SetAsync
+    // because we don't have a socket_ yet.
+    CreateSink();
+  }
+  // TODO: Replace with IPv6 compatible lookup.
+  dns->handle = WSAAsyncGetHostByName(sink_->handle(), WM_DNSNOTIFY,
+                                      addr.hostname().c_str(), dns->buffer,
+                                      sizeof(dns->buffer));
+
+  if (!dns->handle) {
+    LOG_F(LS_ERROR) << "WSAAsyncGetHostByName error: " << WSAGetLastError();
+    delete dns;
+    UpdateLastError();
+    Close();
+    return SOCKET_ERROR;
+  }
+
+  dns->port = addr.port();
+  dns_ = dns;
+  state_ = CS_CONNECTING;
+  return 0;
+}
+
+int Win32Socket::DoConnect(const SocketAddress& addr) {
+  if ((socket_ == INVALID_SOCKET) && !CreateT(addr.family(), SOCK_STREAM)) {
+    return SOCKET_ERROR;
+  }
+  if (!SetAsync(FD_READ | FD_WRITE | FD_CONNECT | FD_CLOSE)) {
+    return SOCKET_ERROR;
+  }
+
+  sockaddr_storage saddr = {0};
+  size_t len = addr.ToSockAddrStorage(&saddr);
+  connect_time_ = Time();
+  int result = connect(socket_,
+                       reinterpret_cast<SOCKADDR*>(&saddr),
+                       static_cast<int>(len));
+  if (result != SOCKET_ERROR) {
+    state_ = CS_CONNECTED;
+  } else {
+    int code = WSAGetLastError();
+    if (code == WSAEWOULDBLOCK) {
+      state_ = CS_CONNECTING;
+    } else {
+      ReportWSAError("WSAAsync:connect", code, addr);
+      error_ = code;
+      Close();
+      return SOCKET_ERROR;
+    }
+  }
+  addr_ = addr;
+
+  return 0;
+}
+
+int Win32Socket::GetError() const {
+  return error_;
+}
+
+void Win32Socket::SetError(int error) {
+  error_ = error;
+}
+
+Socket::ConnState Win32Socket::GetState() const {
+  return state_;
+}
+
+int Win32Socket::GetOption(Option opt, int* value) {
+  int slevel;
+  int sopt;
+  if (TranslateOption(opt, &slevel, &sopt) == -1)
+    return -1;
+
+  char* p = reinterpret_cast<char*>(value);
+  int optlen = sizeof(value);
+  return ::getsockopt(socket_, slevel, sopt, p, &optlen);
+}
+
+int Win32Socket::SetOption(Option opt, int value) {
+  int slevel;
+  int sopt;
+  if (TranslateOption(opt, &slevel, &sopt) == -1)
+    return -1;
+
+  const char* p = reinterpret_cast<const char*>(&value);
+  return ::setsockopt(socket_, slevel, sopt, p, sizeof(value));
+}
+
+int Win32Socket::Send(const void* buffer, size_t length) {
+  int sent = ::send(socket_,
+                    reinterpret_cast<const char*>(buffer),
+                    static_cast<int>(length),
+                    0);
+  UpdateLastError();
+  return sent;
+}
+
+int Win32Socket::SendTo(const void* buffer, size_t length,
+                        const SocketAddress& addr) {
+  sockaddr_storage saddr;
+  size_t addr_len = addr.ToSockAddrStorage(&saddr);
+  int sent = ::sendto(socket_, reinterpret_cast<const char*>(buffer),
+                      static_cast<int>(length), 0,
+                      reinterpret_cast<sockaddr*>(&saddr),
+                      static_cast<int>(addr_len));
+  UpdateLastError();
+  return sent;
+}
+
+int Win32Socket::Recv(void* buffer, size_t length) {
+  int received = ::recv(socket_, static_cast<char*>(buffer),
+                        static_cast<int>(length), 0);
+  UpdateLastError();
+  if (closing_ && received <= static_cast<int>(length))
+    PostClosed();
+  return received;
+}
+
+int Win32Socket::RecvFrom(void* buffer, size_t length,
+                          SocketAddress* out_addr) {
+  sockaddr_storage saddr;
+  socklen_t addr_len = sizeof(saddr);
+  int received = ::recvfrom(socket_, static_cast<char*>(buffer),
+                            static_cast<int>(length), 0,
+                            reinterpret_cast<sockaddr*>(&saddr), &addr_len);
+  UpdateLastError();
+  if (received != SOCKET_ERROR)
+    SocketAddressFromSockAddrStorage(saddr, out_addr);
+  if (closing_ && received <= static_cast<int>(length))
+    PostClosed();
+  return received;
+}
+
+int Win32Socket::Listen(int backlog) {
+  int err = ::listen(socket_, backlog);
+  if (!SetAsync(FD_ACCEPT))
+    return SOCKET_ERROR;
+
+  UpdateLastError();
+  if (err == 0)
+    state_ = CS_CONNECTING;
+  return err;
+}
+
+Win32Socket* Win32Socket::Accept(SocketAddress* out_addr) {
+  sockaddr_storage saddr;
+  socklen_t addr_len = sizeof(saddr);
+  SOCKET s = ::accept(socket_, reinterpret_cast<sockaddr*>(&saddr), &addr_len);
+  UpdateLastError();
+  if (s == INVALID_SOCKET)
+    return NULL;
+  if (out_addr)
+    SocketAddressFromSockAddrStorage(saddr, out_addr);
+  Win32Socket* socket = new Win32Socket;
+  if (0 == socket->Attach(s))
+    return socket;
+  delete socket;
+  return NULL;
+}
+
+int Win32Socket::Close() {
+  int err = 0;
+  if (socket_ != INVALID_SOCKET) {
+    err = ::closesocket(socket_);
+    socket_ = INVALID_SOCKET;
+    closing_ = false;
+    close_error_ = 0;
+    UpdateLastError();
+  }
+  if (dns_) {
+    WSACancelAsyncRequest(dns_->handle);
+    delete dns_;
+    dns_ = NULL;
+  }
+  if (sink_) {
+    sink_->Dispose();
+    sink_ = NULL;
+  }
+  addr_.Clear();
+  state_ = CS_CLOSED;
+  return err;
+}
+
+int Win32Socket::EstimateMTU(uint16* mtu) {
+  SocketAddress addr = GetRemoteAddress();
+  if (addr.IsAny()) {
+    error_ = ENOTCONN;
+    return -1;
+  }
+
+  WinPing ping;
+  if (!ping.IsValid()) {
+    error_ = EINVAL;  // can't think of a better error ID
+    return -1;
+  }
+
+  for (int level = 0; PACKET_MAXIMUMS[level + 1] > 0; ++level) {
+    int32 size = PACKET_MAXIMUMS[level] - IP_HEADER_SIZE - ICMP_HEADER_SIZE;
+    WinPing::PingResult result = ping.Ping(addr.ipaddr(), size,
+                                           ICMP_PING_TIMEOUT_MILLIS, 1, false);
+    if (result == WinPing::PING_FAIL) {
+      error_ = EINVAL;  // can't think of a better error ID
+      return -1;
+    }
+    if (result != WinPing::PING_TOO_LARGE) {
+      *mtu = PACKET_MAXIMUMS[level];
+      return 0;
+    }
+  }
+
+  ASSERT(false);
+  return 0;
+}
+
+void Win32Socket::CreateSink() {
+  ASSERT(NULL == sink_);
+
+  // Create window
+  sink_ = new EventSink(this);
+  sink_->Create(NULL, L"EventSink", 0, 0, 0, 0, 10, 10);
+}
+
+bool Win32Socket::SetAsync(int events) {
+  if (NULL == sink_) {
+    CreateSink();
+    ASSERT(NULL != sink_);
+  }
+
+  // start the async select
+  if (WSAAsyncSelect(socket_, sink_->handle(), WM_SOCKETNOTIFY, events)
+      == SOCKET_ERROR) {
+    UpdateLastError();
+    Close();
+    return false;
+  }
+
+  return true;
+}
+
+bool Win32Socket::HandleClosed(int close_error) {
+  // WM_CLOSE will be received before all data has been read, so we need to
+  // hold on to it until the read buffer has been drained.
+  char ch;
+  closing_ = true;
+  close_error_ = close_error;
+  return (::recv(socket_, &ch, 1, MSG_PEEK) <= 0);
+}
+
+void Win32Socket::PostClosed() {
+  // If we see that the buffer is indeed drained, then send the close.
+  closing_ = false;
+  ::PostMessage(sink_->handle(), WM_SOCKETNOTIFY,
+                socket_, WSAMAKESELECTREPLY(FD_CLOSE, close_error_));
+}
+
+void Win32Socket::UpdateLastError() {
+  error_ = WSAGetLastError();
+}
+
+int Win32Socket::TranslateOption(Option opt, int* slevel, int* sopt) {
+  switch (opt) {
+    case OPT_DONTFRAGMENT:
+      *slevel = IPPROTO_IP;
+      *sopt = IP_DONTFRAGMENT;
+      break;
+    case OPT_RCVBUF:
+      *slevel = SOL_SOCKET;
+      *sopt = SO_RCVBUF;
+      break;
+    case OPT_SNDBUF:
+      *slevel = SOL_SOCKET;
+      *sopt = SO_SNDBUF;
+      break;
+    case OPT_NODELAY:
+      *slevel = IPPROTO_TCP;
+      *sopt = TCP_NODELAY;
+      break;
+    default:
+      ASSERT(false);
+      return -1;
+  }
+  return 0;
+}
+
+void Win32Socket::OnSocketNotify(SOCKET socket, int event, int error) {
+  // Ignore events if we're already closed.
+  if (socket != socket_)
+    return;
+
+  error_ = error;
+  switch (event) {
+    case FD_CONNECT:
+      if (error != ERROR_SUCCESS) {
+        ReportWSAError("WSAAsync:connect notify", error, addr_);
+#ifdef _DEBUG
+        int32 duration = TimeSince(connect_time_);
+        LOG(LS_INFO) << "WSAAsync:connect error (" << duration
+                     << " ms), faking close";
+#endif
+        state_ = CS_CLOSED;
+        // If you get an error connecting, close doesn't really do anything
+        // and it certainly doesn't send back any close notification, but
+        // we really only maintain a few states, so it is easiest to get
+        // back into a known state by pretending that a close happened, even
+        // though the connect event never did occur.
+        SignalCloseEvent(this, error);
+      } else {
+#ifdef _DEBUG
+        int32 duration = TimeSince(connect_time_);
+        LOG(LS_INFO) << "WSAAsync:connect (" << duration << " ms)";
+#endif
+        state_ = CS_CONNECTED;
+        SignalConnectEvent(this);
+      }
+      break;
+
+    case FD_ACCEPT:
+    case FD_READ:
+      if (error != ERROR_SUCCESS) {
+        ReportWSAError("WSAAsync:read notify", error, addr_);
+      } else {
+        SignalReadEvent(this);
+      }
+      break;
+
+    case FD_WRITE:
+      if (error != ERROR_SUCCESS) {
+        ReportWSAError("WSAAsync:write notify", error, addr_);
+      } else {
+        SignalWriteEvent(this);
+      }
+      break;
+
+    case FD_CLOSE:
+      if (HandleClosed(error)) {
+        ReportWSAError("WSAAsync:close notify", error, addr_);
+        state_ = CS_CLOSED;
+        SignalCloseEvent(this, error);
+      }
+      break;
+  }
+}
+
+void Win32Socket::OnDnsNotify(HANDLE task, int error) {
+  if (!dns_ || dns_->handle != task)
+    return;
+
+  uint32 ip = 0;
+  if (error == 0) {
+    hostent* pHost = reinterpret_cast<hostent*>(dns_->buffer);
+    uint32 net_ip = *reinterpret_cast<uint32*>(pHost->h_addr_list[0]);
+    ip = NetworkToHost32(net_ip);
+  }
+
+  LOG_F(LS_INFO) << "(" << IPAddress(ip).ToSensitiveString()
+                 << ", " << error << ")";
+
+  if (error == 0) {
+    SocketAddress address(ip, dns_->port);
+    error = DoConnect(address);
+  } else {
+    Close();
+  }
+
+  if (error) {
+    error_ = error;
+    SignalCloseEvent(this, error_);
+  } else {
+    delete dns_;
+    dns_ = NULL;
+  }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Win32SocketServer
+// Provides cricket base services on top of a win32 gui thread
+///////////////////////////////////////////////////////////////////////////////
+
+static UINT s_wm_wakeup_id = 0;
+const TCHAR Win32SocketServer::kWindowName[] = L"libjingle Message Window";
+
+Win32SocketServer::Win32SocketServer(MessageQueue* message_queue)
+    : message_queue_(message_queue),
+      wnd_(this),
+      posted_(false),
+      hdlg_(NULL) {
+  if (s_wm_wakeup_id == 0)
+    s_wm_wakeup_id = RegisterWindowMessage(L"WM_WAKEUP");
+  if (!wnd_.Create(NULL, kWindowName, 0, 0, 0, 0, 0, 0)) {
+    LOG_GLE(LS_ERROR) << "Failed to create message window.";
+  }
+}
+
+Win32SocketServer::~Win32SocketServer() {
+  if (wnd_.handle() != NULL) {
+    KillTimer(wnd_.handle(), 1);
+    wnd_.Destroy();
+  }
+}
+
+Socket* Win32SocketServer::CreateSocket(int type) {
+  return CreateSocket(AF_INET, type);
+}
+
+Socket* Win32SocketServer::CreateSocket(int family, int type) {
+  return CreateAsyncSocket(family, type);
+}
+
+AsyncSocket* Win32SocketServer::CreateAsyncSocket(int type) {
+  return CreateAsyncSocket(AF_INET, type);
+}
+
+AsyncSocket* Win32SocketServer::CreateAsyncSocket(int family, int type) {
+  Win32Socket* socket = new Win32Socket;
+  if (socket->CreateT(family, type)) {
+    return socket;
+  }
+  delete socket;
+  return NULL;
+}
+
+void Win32SocketServer::SetMessageQueue(MessageQueue* queue) {
+  message_queue_ = queue;
+}
+
+bool Win32SocketServer::Wait(int cms, bool process_io) {
+  BOOL b;
+  if (process_io) {
+    // Spin the Win32 message pump at least once, and as long as requested.
+    // This is the Thread::ProcessMessages case.
+    uint32 start = Time();
+    do {
+      MSG msg;
+      SetTimer(wnd_.handle(), 0, cms, NULL);
+      // Get the next available message. If we have a modeless dialog, give
+      // give the message to IsDialogMessage, which will return true if it
+      // 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 == -1) {
+        LOG_GLE(LS_ERROR) << "GetMessage failed.";
+        return false;
+      } else if(b) {
+        if (!hdlg_ || !IsDialogMessage(hdlg_, &msg)) {
+          TranslateMessage(&msg);
+          DispatchMessage(&msg);
+        }
+      }
+      KillTimer(wnd_.handle(), 0);
+    } while (b && TimeSince(start) < cms);
+  } else if (cms != 0) {
+    // Sit and wait forever for a WakeUp. This is the Thread::Send case.
+    ASSERT(cms == -1);
+    MSG msg;
+    b = GetMessage(&msg, NULL, s_wm_wakeup_id, s_wm_wakeup_id);
+    {
+      CritScope scope(&cs_);
+      posted_ = false;
+    }
+  } else {
+    // No-op (cms == 0 && !process_io). This is the Pump case.
+    b = TRUE;
+  }
+  return (b != FALSE);
+}
+
+void Win32SocketServer::WakeUp() {
+  if (wnd_.handle()) {
+    // Set the "message pending" flag, if not already set.
+    {
+      CritScope scope(&cs_);
+      if (posted_)
+        return;
+      posted_ = true;
+    }
+
+    PostMessage(wnd_.handle(), s_wm_wakeup_id, 0, 0);
+  }
+}
+
+void Win32SocketServer::Pump() {
+  // Clear the "message pending" flag.
+  {
+    CritScope scope(&cs_);
+    posted_ = false;
+  }
+
+  // Dispatch all the messages that are currently in our queue. If new messages
+  // are posted during the dispatch, they will be handled in the next Pump.
+  // We use max(1, ...) to make sure we try to dispatch at least once, since
+  // this allow us to process "sent" messages, not included in the size() count.
+  Message msg;
+  for (size_t max_messages_to_process = _max<size_t>(1, message_queue_->size());
+       max_messages_to_process > 0 && message_queue_->Get(&msg, 0, false);
+       --max_messages_to_process) {
+    message_queue_->Dispatch(&msg);
+  }
+
+  // Anything remaining?
+  int delay = message_queue_->GetDelay();
+  if (delay == -1) {
+    KillTimer(wnd_.handle(), 1);
+  } else {
+    SetTimer(wnd_.handle(), 1, delay, NULL);
+  }
+}
+
+bool Win32SocketServer::MessageWindow::OnMessage(UINT wm, WPARAM wp,
+                                                 LPARAM lp, LRESULT& lr) {
+  bool handled = false;
+  if (wm == s_wm_wakeup_id || (wm == WM_TIMER && wp == 1)) {
+    ss_->Pump();
+    lr = 0;
+    handled = true;
+  }
+  return handled;
+}
+
+}  // namespace talk_base
diff --git a/talk/base/win32socketserver.h b/talk/base/win32socketserver.h
new file mode 100644
index 0000000..1fa6523
--- /dev/null
+++ b/talk/base/win32socketserver.h
@@ -0,0 +1,180 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_WIN32SOCKETSERVER_H_
+#define TALK_BASE_WIN32SOCKETSERVER_H_
+
+#ifdef WIN32
+#include "talk/base/asyncsocket.h"
+#include "talk/base/criticalsection.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/socketserver.h"
+#include "talk/base/socketfactory.h"
+#include "talk/base/socket.h"
+#include "talk/base/thread.h"
+#include "talk/base/win32window.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// Win32Socket
+///////////////////////////////////////////////////////////////////////////////
+
+class Win32Socket : public AsyncSocket {
+ public:
+  Win32Socket();
+  virtual ~Win32Socket();
+
+  bool CreateT(int family, int type);
+
+  int Attach(SOCKET s);
+  void SetTimeout(int ms);
+
+  // AsyncSocket Interface
+  virtual SocketAddress GetLocalAddress() const;
+  virtual SocketAddress GetRemoteAddress() const;
+  virtual int Bind(const SocketAddress& addr);
+  virtual int Connect(const SocketAddress& addr);
+  virtual int Send(const void *buffer, size_t length);
+  virtual int SendTo(const void *buffer, size_t length, const SocketAddress& addr);
+  virtual int Recv(void *buffer, size_t length);
+  virtual int RecvFrom(void *buffer, size_t length, SocketAddress *out_addr);
+  virtual int Listen(int backlog);
+  virtual Win32Socket *Accept(SocketAddress *out_addr);
+  virtual int Close();
+  virtual int GetError() const;
+  virtual void SetError(int error);
+  virtual ConnState GetState() const;
+  virtual int EstimateMTU(uint16* mtu);
+  virtual int GetOption(Option opt, int* value);
+  virtual int SetOption(Option opt, int value);
+
+ private:
+  void CreateSink();
+  bool SetAsync(int events);
+  int DoConnect(const SocketAddress& addr);
+  bool HandleClosed(int close_error);
+  void PostClosed();
+  void UpdateLastError();
+  static int TranslateOption(Option opt, int* slevel, int* sopt);
+
+  void OnSocketNotify(SOCKET socket, int event, int error);
+  void OnDnsNotify(HANDLE task, int error);
+
+  SOCKET socket_;
+  int error_;
+  ConnState state_;
+  SocketAddress addr_;         // address that we connected to (see DoConnect)
+  uint32 connect_time_;
+  bool closing_;
+  int close_error_;
+
+  class EventSink;
+  friend class EventSink;
+  EventSink * sink_;
+
+  struct DnsLookup;
+  DnsLookup * dns_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Win32SocketServer
+///////////////////////////////////////////////////////////////////////////////
+
+class Win32SocketServer : public SocketServer {
+ public:
+  explicit Win32SocketServer(MessageQueue* message_queue);
+  virtual ~Win32SocketServer();
+
+  void set_modeless_dialog(HWND hdlg) {
+    hdlg_ = hdlg;
+  }
+
+  // SocketServer Interface
+  virtual Socket* CreateSocket(int type);
+  virtual Socket* CreateSocket(int family, int type);
+
+  virtual AsyncSocket* CreateAsyncSocket(int type);
+  virtual AsyncSocket* CreateAsyncSocket(int family, int type);
+
+  virtual void SetMessageQueue(MessageQueue* queue);
+  virtual bool Wait(int cms, bool process_io);
+  virtual void WakeUp();
+
+  void Pump();
+
+  HWND handle() { return wnd_.handle(); }
+
+ private:
+  class MessageWindow : public Win32Window {
+   public:
+    explicit MessageWindow(Win32SocketServer* ss) : ss_(ss) {}
+   private:
+    virtual bool OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT& result);
+    Win32SocketServer* ss_;
+  };
+
+  static const TCHAR kWindowName[];
+  MessageQueue *message_queue_;
+  MessageWindow wnd_;
+  CriticalSection cs_;
+  bool posted_;
+  HWND hdlg_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Win32Thread. Automatically pumps Windows messages.
+///////////////////////////////////////////////////////////////////////////////
+
+class Win32Thread : public Thread {
+ public:
+  Win32Thread() : ss_(this), id_(0) {
+    set_socketserver(&ss_);
+  }
+  virtual ~Win32Thread() {
+    set_socketserver(NULL);
+  }
+  virtual void Run() {
+    id_ = GetCurrentThreadId();
+    Thread::Run();
+    id_ = 0;
+  }
+  virtual void Quit() {
+    PostThreadMessage(id_, WM_QUIT, 0, 0);
+  }
+ private:
+  Win32SocketServer ss_;
+  DWORD id_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // WIN32
+
+#endif  // TALK_BASE_WIN32SOCKETSERVER_H_
diff --git a/talk/base/win32socketserver_unittest.cc b/talk/base/win32socketserver_unittest.cc
new file mode 100644
index 0000000..f78a444
--- /dev/null
+++ b/talk/base/win32socketserver_unittest.cc
@@ -0,0 +1,151 @@
+// 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, WM_USER, 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, TestConnectIPv4) {
+  SocketTest::TestConnectIPv4();
+}
+
+TEST_F(Win32SocketTest, TestConnectIPv6) {
+  SocketTest::TestConnectIPv6();
+}
+
+TEST_F(Win32SocketTest, TestConnectWithDnsLookupIPv4) {
+  SocketTest::TestConnectWithDnsLookupIPv4();
+}
+
+TEST_F(Win32SocketTest, TestConnectWithDnsLookupIPv6) {
+  SocketTest::TestConnectWithDnsLookupIPv6();
+}
+
+TEST_F(Win32SocketTest, TestConnectFailIPv4) {
+  SocketTest::TestConnectFailIPv4();
+}
+
+TEST_F(Win32SocketTest, TestConnectFailIPv6) {
+  SocketTest::TestConnectFailIPv6();
+}
+
+TEST_F(Win32SocketTest, TestConnectWithDnsLookupFailIPv4) {
+  SocketTest::TestConnectWithDnsLookupFailIPv4();
+}
+
+TEST_F(Win32SocketTest, TestConnectWithDnsLookupFailIPv6) {
+  SocketTest::TestConnectWithDnsLookupFailIPv6();
+}
+
+TEST_F(Win32SocketTest, TestConnectWithClosedSocketIPv4) {
+  SocketTest::TestConnectWithClosedSocketIPv4();
+}
+
+TEST_F(Win32SocketTest, TestConnectWithClosedSocketIPv6) {
+  SocketTest::TestConnectWithClosedSocketIPv6();
+}
+
+TEST_F(Win32SocketTest, TestConnectWhileNotClosedIPv4) {
+  SocketTest::TestConnectWhileNotClosedIPv4();
+}
+
+TEST_F(Win32SocketTest, TestConnectWhileNotClosedIPv6) {
+  SocketTest::TestConnectWhileNotClosedIPv6();
+}
+
+TEST_F(Win32SocketTest, TestServerCloseDuringConnectIPv4) {
+  SocketTest::TestServerCloseDuringConnectIPv4();
+}
+
+TEST_F(Win32SocketTest, TestServerCloseDuringConnectIPv6) {
+  SocketTest::TestServerCloseDuringConnectIPv6();
+}
+
+TEST_F(Win32SocketTest, TestClientCloseDuringConnectIPv4) {
+  SocketTest::TestClientCloseDuringConnectIPv4();
+}
+
+TEST_F(Win32SocketTest, TestClientCloseDuringConnectIPv6) {
+  SocketTest::TestClientCloseDuringConnectIPv6();
+}
+
+TEST_F(Win32SocketTest, TestServerCloseIPv4) {
+  SocketTest::TestServerCloseIPv4();
+}
+
+TEST_F(Win32SocketTest, TestServerCloseIPv6) {
+  SocketTest::TestServerCloseIPv6();
+}
+
+TEST_F(Win32SocketTest, TestCloseInClosedCallbackIPv4) {
+  SocketTest::TestCloseInClosedCallbackIPv4();
+}
+
+TEST_F(Win32SocketTest, TestCloseInClosedCallbackIPv6) {
+  SocketTest::TestCloseInClosedCallbackIPv6();
+}
+
+TEST_F(Win32SocketTest, TestSocketServerWaitIPv4) {
+  SocketTest::TestSocketServerWaitIPv4();
+}
+
+TEST_F(Win32SocketTest, TestSocketServerWaitIPv6) {
+  SocketTest::TestSocketServerWaitIPv6();
+}
+
+TEST_F(Win32SocketTest, TestTcpIPv4) {
+  SocketTest::TestTcpIPv4();
+}
+
+TEST_F(Win32SocketTest, TestTcpIPv6) {
+  SocketTest::TestTcpIPv6();
+}
+
+TEST_F(Win32SocketTest, TestUdpIPv4) {
+  SocketTest::TestUdpIPv4();
+}
+
+TEST_F(Win32SocketTest, TestUdpIPv6) {
+  SocketTest::TestUdpIPv6();
+}
+
+TEST_F(Win32SocketTest, TestGetSetOptionsIPv4) {
+  SocketTest::TestGetSetOptionsIPv4();
+}
+
+TEST_F(Win32SocketTest, TestGetSetOptionsIPv6) {
+  SocketTest::TestGetSetOptionsIPv6();
+}
+
+}  // 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
new file mode 100644
index 0000000..b11c349
--- /dev/null
+++ b/talk/base/win32window.cc
@@ -0,0 +1,138 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/win32window.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// Win32Window
+///////////////////////////////////////////////////////////////////////////////
+
+static const wchar_t kWindowBaseClassName[] = L"WindowBaseClass";
+HINSTANCE Win32Window::instance_ = NULL;
+ATOM Win32Window::window_class_ = 0;
+
+Win32Window::Win32Window() : wnd_(NULL) {
+}
+
+Win32Window::~Win32Window() {
+  ASSERT(NULL == wnd_);
+}
+
+bool Win32Window::Create(HWND parent, const wchar_t* title, DWORD style,
+                         DWORD exstyle, int x, int y, int cx, int cy) {
+  if (wnd_) {
+    // Window already exists.
+    return false;
+  }
+
+  if (!window_class_) {
+    if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
+                           GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
+                           reinterpret_cast<LPCWSTR>(&Win32Window::WndProc),
+                           &instance_)) {
+      LOG_GLE(LS_ERROR) << "GetModuleHandleEx failed";
+      return false;
+    }
+
+    // Class not registered, register it.
+    WNDCLASSEX wcex;
+    memset(&wcex, 0, sizeof(wcex));
+    wcex.cbSize = sizeof(wcex);
+    wcex.hInstance = instance_;
+    wcex.lpfnWndProc = &Win32Window::WndProc;
+    wcex.lpszClassName = kWindowBaseClassName;
+    window_class_ = ::RegisterClassEx(&wcex);
+    if (!window_class_) {
+      LOG_GLE(LS_ERROR) << "RegisterClassEx failed";
+      return false;
+    }
+  }
+  wnd_ = ::CreateWindowEx(exstyle, kWindowBaseClassName, title, style,
+                          x, y, cx, cy, parent, NULL, instance_, this);
+  return (NULL != wnd_);
+}
+
+void Win32Window::Destroy() {
+  VERIFY(::DestroyWindow(wnd_) != FALSE);
+}
+
+void Win32Window::Shutdown() {
+  if (window_class_) {
+    ::UnregisterClass(MAKEINTATOM(window_class_), instance_);
+    window_class_ = 0;
+  }
+}
+
+bool Win32Window::OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam,
+                            LRESULT& result) {
+  switch (uMsg) {
+  case WM_CLOSE:
+    if (!OnClose()) {
+      result = 0;
+      return true;
+    }
+    break;
+  }
+  return false;
+}
+
+LRESULT Win32Window::WndProc(HWND hwnd, UINT uMsg,
+                             WPARAM wParam, LPARAM lParam) {
+  Win32Window* that = reinterpret_cast<Win32Window*>(
+      ::GetWindowLongPtr(hwnd, GWLP_USERDATA));
+  if (!that && (WM_CREATE == uMsg)) {
+    CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lParam);
+    that = static_cast<Win32Window*>(cs->lpCreateParams);
+    that->wnd_ = hwnd;
+    ::SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(that));
+  }
+  if (that) {
+    LRESULT result;
+    bool handled = that->OnMessage(uMsg, wParam, lParam, result);
+    if (WM_DESTROY == uMsg) {
+      for (HWND child = ::GetWindow(hwnd, GW_CHILD); child;
+           child = ::GetWindow(child, GW_HWNDNEXT)) {
+        LOG(LS_INFO) << "Child window: " << static_cast<void*>(child);
+      }
+    }
+    if (WM_NCDESTROY == uMsg) {
+      ::SetWindowLongPtr(hwnd, GWLP_USERDATA, NULL);
+      that->wnd_ = NULL;
+      that->OnNcDestroy();
+    }
+    if (handled) {
+      return result;
+    }
+  }
+  return ::DefWindowProc(hwnd, uMsg, wParam, lParam);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/win32window.h b/talk/base/win32window.h
new file mode 100644
index 0000000..37f3696
--- /dev/null
+++ b/talk/base/win32window.h
@@ -0,0 +1,77 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_WIN32WINDOW_H_
+#define TALK_BASE_WIN32WINDOW_H_
+
+#ifdef WIN32
+
+#include "talk/base/win32.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// Win32Window
+///////////////////////////////////////////////////////////////////////////////
+
+class Win32Window {
+ public:
+  Win32Window();
+  virtual ~Win32Window();
+
+  HWND handle() const { return wnd_; }
+
+  bool Create(HWND parent, const wchar_t* title, DWORD style, DWORD exstyle,
+              int x, int y, int cx, int cy);
+  void Destroy();
+
+  // Call this when your DLL unloads.
+  static void Shutdown();
+
+ protected:
+  virtual bool OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam,
+                         LRESULT& result);
+
+  virtual bool OnClose() { return true; }
+  virtual void OnNcDestroy() { }
+
+ private:
+  static LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam,
+                                  LPARAM lParam);
+
+  HWND wnd_;
+  static HINSTANCE instance_;
+  static ATOM window_class_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // WIN32
+
+#endif  // TALK_BASE_WIN32WINDOW_H_
diff --git a/talk/base/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..d996c0e
--- /dev/null
+++ b/talk/base/win32windowpicker.cc
@@ -0,0 +1,137 @@
+// 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);
+  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);
+
+  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, static_cast<int>(desktop_desc->size()));
+  // TODO: Figure out an appropriate desktop title.
+  DesktopDescription desc(id, "");
+
+  // Determine whether it's the primary monitor.
+  MONITORINFO monitor_info = {0};
+  monitor_info.cbSize = sizeof(monitor_info);
+  bool primary = (GetMonitorInfo(h_monitor, &monitor_info) &&
+      (monitor_info.dwFlags & MONITORINFOF_PRIMARY) != 0);
+  desc.set_primary(primary);
+
+  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::GetDesktopDimensions(const DesktopId& id,
+                                             int* width,
+                                             int* height) {
+  MONITORINFOEX monitor_info;
+  monitor_info.cbSize = sizeof(MONITORINFOEX);
+  if (!GetMonitorInfo(id.id(), &monitor_info)) {
+    return false;
+  }
+  *width = monitor_info.rcMonitor.right - monitor_info.rcMonitor.left;
+  *height = monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top;
+  return true;
+}
+
+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..5e8fc6a
--- /dev/null
+++ b/talk/base/win32windowpicker.h
@@ -0,0 +1,33 @@
+// 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);
+  virtual bool GetDesktopDimensions(const DesktopId& id, int* width,
+                                    int* height);
+
+ 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..b418fd7
--- /dev/null
+++ b/talk/base/win32windowpicker_unittest.cc
@@ -0,0 +1,93 @@
+// 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(0, wcscmp(window_title, 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..ad9467a
--- /dev/null
+++ b/talk/base/window.h
@@ -0,0 +1,141 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_WINDOW_H_
+#define TALK_BASE_WINDOW_H_
+
+#include "talk/base/stringencode.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(uint64 id) {
+#if defined(WIN32)
+    return WindowId(reinterpret_cast<WindowId::WindowT>(id));
+#else
+    return WindowId(static_cast<WindowId::WindowT>(id));
+#endif
+  }
+
+  static uint64 Format(const WindowT& id) {
+#if defined(WIN32)
+    return static_cast<uint64>(reinterpret_cast<uintptr_t>(id));
+#else
+    return static_cast<uint64>(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,
+};
+
+inline std::string ToString(const WindowId& window) {
+  return ToString(window.id());
+}
+
+}  // 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..e948d4c
--- /dev/null
+++ b/talk/base/windowpicker.h
@@ -0,0 +1,78 @@
+// 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), primary_(false) {
+  }
+  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; }
+  // Indicates whether it is the primary desktop in the system.
+  bool primary() const { return primary_; }
+  void set_primary(bool primary) { primary_ = primary; }
+
+ private:
+  DesktopId id_;
+  std::string title_;
+  bool primary_;
+};
+
+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;
+  // Gets the width and height of a desktop.
+  // Returns true if successful.
+  virtual bool GetDesktopDimensions(const DesktopId& id, int* width,
+                                    int* height) = 0;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_WINDOWPICKER_H_
diff --git a/talk/base/windowpicker_unittest.cc b/talk/base/windowpicker_unittest.cc
new file mode 100644
index 0000000..e1a815d
--- /dev/null
+++ b/talk/base/windowpicker_unittest.cc
@@ -0,0 +1,55 @@
+#include "talk/base/gunit.h"
+#include "talk/base/window.h"
+#include "talk/base/windowpicker.h"
+#include "talk/base/windowpickerfactory.h"
+
+#ifdef OSX
+#  define DISABLE_ON_MAC(name) DISABLED_ ## name
+#else
+#  define DISABLE_ON_MAC(name) name
+#endif
+
+TEST(WindowPickerTest, GetWindowList) {
+  if (!talk_base::WindowPickerFactory::IsSupported()) {
+    LOG(LS_INFO) << "skipping test: window capturing is not supported with "
+                 << "current configuration.";
+  }
+  talk_base::scoped_ptr<talk_base::WindowPicker> picker(
+      talk_base::WindowPickerFactory::CreateWindowPicker());
+  EXPECT_TRUE(picker->Init());
+  talk_base::WindowDescriptionList descriptions;
+  EXPECT_TRUE(picker->GetWindowList(&descriptions));
+}
+
+// TODO(hughv) Investigate why this fails on pulse but not locally after
+// upgrading to XCode 4.5.  The failure is GetDesktopList returning FALSE.
+TEST(WindowPickerTest, DISABLE_ON_MAC(GetDesktopList)) {
+  if (!talk_base::WindowPickerFactory::IsSupported()) {
+    LOG(LS_INFO) << "skipping test: window capturing is not supported with "
+                 << "current configuration.";
+  }
+  talk_base::scoped_ptr<talk_base::WindowPicker> picker(
+      talk_base::WindowPickerFactory::CreateWindowPicker());
+  EXPECT_TRUE(picker->Init());
+  talk_base::DesktopDescriptionList descriptions;
+  EXPECT_TRUE(picker->GetDesktopList(&descriptions));
+  if (descriptions.size() > 0) {
+    int width = 0;
+    int height = 0;
+    EXPECT_TRUE(picker->GetDesktopDimensions(descriptions[0].id(), &width,
+                                             &height));
+    EXPECT_GT(width, 0);
+    EXPECT_GT(height, 0);
+
+    // Test |IsPrimaryDesktop|. Only one desktop should be a primary.
+    bool found_primary = false;
+    for (talk_base::DesktopDescriptionList::iterator it = descriptions.begin();
+         it != descriptions.end(); ++it) {
+      if (it->primary()) {
+        EXPECT_FALSE(found_primary);
+        found_primary = true;
+      }
+    }
+    EXPECT_TRUE(found_primary);
+  }
+}
diff --git a/talk/base/windowpickerfactory.h b/talk/base/windowpickerfactory.h
new file mode 100644
index 0000000..b55cc7d
--- /dev/null
+++ b/talk/base/windowpickerfactory.h
@@ -0,0 +1,76 @@
+/*
+ * 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:
+  virtual ~WindowPickerFactory() {}
+
+  // Instance method for dependency injection.
+  virtual WindowPicker* Create() {
+    return CreateWindowPicker();
+  }
+
+  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.cc b/talk/base/winfirewall.cc
new file mode 100644
index 0000000..e87ee5a
--- /dev/null
+++ b/talk/base/winfirewall.cc
@@ -0,0 +1,172 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/winfirewall.h"
+
+#include "talk/base/win32.h"
+
+#include <comdef.h>
+#include <netfw.h>
+
+#define RELEASE(lpUnk) do { \
+  if ((lpUnk) != NULL) { \
+    (lpUnk)->Release(); \
+    (lpUnk) = NULL; \
+  } \
+} while (0)
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// WinFirewall
+//////////////////////////////////////////////////////////////////////
+
+WinFirewall::WinFirewall() : mgr_(NULL), policy_(NULL), profile_(NULL) {
+}
+
+WinFirewall::~WinFirewall() {
+  Shutdown();
+}
+
+bool WinFirewall::Initialize(HRESULT* result) {
+  if (mgr_) {
+    if (result) {
+      *result = S_OK;
+    }
+    return true;
+  }
+
+  HRESULT hr = CoCreateInstance(__uuidof(NetFwMgr),
+                                0, CLSCTX_INPROC_SERVER,
+                                __uuidof(INetFwMgr),
+                                reinterpret_cast<void **>(&mgr_));
+  if (SUCCEEDED(hr) && (mgr_ != NULL))
+    hr = mgr_->get_LocalPolicy(&policy_);
+  if (SUCCEEDED(hr) && (policy_ != NULL))
+    hr = policy_->get_CurrentProfile(&profile_);
+
+  if (result)
+    *result = hr;
+  return SUCCEEDED(hr) && (profile_ != NULL);
+}
+
+void WinFirewall::Shutdown() {
+  RELEASE(profile_);
+  RELEASE(policy_);
+  RELEASE(mgr_);
+}
+
+bool WinFirewall::Enabled() const {
+  if (!profile_)
+    return false;
+
+  VARIANT_BOOL fwEnabled = VARIANT_FALSE;
+  profile_->get_FirewallEnabled(&fwEnabled);
+  return (fwEnabled != VARIANT_FALSE);
+}
+
+bool WinFirewall::QueryAuthorized(const char* filename, bool* authorized)
+    const {
+  return QueryAuthorizedW(ToUtf16(filename).c_str(), authorized);
+}
+
+bool WinFirewall::QueryAuthorizedW(const wchar_t* filename, bool* authorized)
+    const {
+  *authorized = false;
+  bool success = false;
+
+  if (!profile_)
+    return false;
+
+  _bstr_t bfilename = filename;
+
+  INetFwAuthorizedApplications* apps = NULL;
+  HRESULT hr = profile_->get_AuthorizedApplications(&apps);
+  if (SUCCEEDED(hr) && (apps != NULL)) {
+    INetFwAuthorizedApplication* app = NULL;
+    hr = apps->Item(bfilename, &app);
+    if (SUCCEEDED(hr) && (app != NULL)) {
+      VARIANT_BOOL fwEnabled = VARIANT_FALSE;
+      hr = app->get_Enabled(&fwEnabled);
+      app->Release();
+
+      if (SUCCEEDED(hr)) {
+        success = true;
+        *authorized = (fwEnabled != VARIANT_FALSE);
+      }
+    } else if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) {
+      // No entry in list of authorized apps
+      success = true;
+    } else {
+      // Unexpected error
+    }
+    apps->Release();
+  }
+
+  return success;
+}
+
+bool WinFirewall::AddApplication(const char* filename,
+                                 const char* friendly_name,
+                                 bool authorized,
+                                 HRESULT* result) {
+  return AddApplicationW(ToUtf16(filename).c_str(),
+      ToUtf16(friendly_name).c_str(), authorized, result);
+}
+
+bool WinFirewall::AddApplicationW(const wchar_t* filename,
+                                  const wchar_t* friendly_name,
+                                  bool authorized,
+                                  HRESULT* result) {
+  INetFwAuthorizedApplications* apps = NULL;
+  HRESULT hr = profile_->get_AuthorizedApplications(&apps);
+  if (SUCCEEDED(hr) && (apps != NULL)) {
+    INetFwAuthorizedApplication* app = NULL;
+    hr = CoCreateInstance(__uuidof(NetFwAuthorizedApplication),
+                          0, CLSCTX_INPROC_SERVER,
+                          __uuidof(INetFwAuthorizedApplication),
+                          reinterpret_cast<void **>(&app));
+    if (SUCCEEDED(hr) && (app != NULL)) {
+      _bstr_t bstr = filename;
+      hr = app->put_ProcessImageFileName(bstr);
+      bstr = friendly_name;
+      if (SUCCEEDED(hr))
+        hr = app->put_Name(bstr);
+      if (SUCCEEDED(hr))
+        hr = app->put_Enabled(authorized ? VARIANT_TRUE : VARIANT_FALSE);
+      if (SUCCEEDED(hr))
+        hr = apps->Add(app);
+      app->Release();
+    }
+    apps->Release();
+  }
+  if (result)
+    *result = hr;
+  return SUCCEEDED(hr);
+}
+
+}  // namespace talk_base
diff --git a/talk/base/winfirewall.h b/talk/base/winfirewall.h
new file mode 100644
index 0000000..11d687e
--- /dev/null
+++ b/talk/base/winfirewall.h
@@ -0,0 +1,73 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_WINFIREWALL_H_
+#define TALK_BASE_WINFIREWALL_H_
+
+#ifndef _HRESULT_DEFINED
+#define _HRESULT_DEFINED
+typedef long HRESULT;  // Can't forward declare typedef, but don't need all win
+#endif // !_HRESULT_DEFINED
+
+struct INetFwMgr;
+struct INetFwPolicy;
+struct INetFwProfile;
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// WinFirewall
+//////////////////////////////////////////////////////////////////////
+
+class WinFirewall {
+ public:
+  WinFirewall();
+  ~WinFirewall();
+
+  bool Initialize(HRESULT* result);
+  void Shutdown();
+
+  bool Enabled() const;
+  bool QueryAuthorized(const char* filename, bool* authorized) const;
+  bool QueryAuthorizedW(const wchar_t* filename, bool* authorized) const;
+
+  bool AddApplication(const char* filename, const char* friendly_name,
+                      bool authorized, HRESULT* result);
+  bool AddApplicationW(const wchar_t* filename, const wchar_t* friendly_name,
+                       bool authorized, HRESULT* result);
+
+ private:
+  INetFwMgr* mgr_;
+  INetFwPolicy* policy_;
+  INetFwProfile* profile_;
+};
+
+//////////////////////////////////////////////////////////////////////
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_WINFIREWALL_H_
diff --git a/talk/base/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
new file mode 100644
index 0000000..001740a
--- /dev/null
+++ b/talk/base/winping.cc
@@ -0,0 +1,376 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/winping.h"
+
+#include <Iphlpapi.h>
+#include <cassert>
+
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/ipaddress.h"
+#include "talk/base/logging.h"
+#include "talk/base/nethelpers.h"
+#include "talk/base/socketaddress.h"
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// Found in IPExport.h
+//////////////////////////////////////////////////////////////////////
+
+typedef struct icmp_echo_reply {
+    ULONG   Address;            // Replying address
+    ULONG   Status;             // Reply IP_STATUS
+    ULONG   RoundTripTime;      // RTT in milliseconds
+    USHORT  DataSize;           // Reply data size in bytes
+    USHORT  Reserved;           // Reserved for system use
+    PVOID   Data;               // Pointer to the reply data
+    struct ip_option_information Options; // Reply options
+} ICMP_ECHO_REPLY, * PICMP_ECHO_REPLY;
+
+typedef struct icmpv6_echo_reply_lh {
+  sockaddr_in6    Address;
+  ULONG           Status;
+  unsigned int    RoundTripTime;
+} ICMPV6_ECHO_REPLY, *PICMPV6_ECHO_REPLY;
+
+//
+// IP_STATUS codes returned from IP APIs
+//
+
+#define IP_STATUS_BASE              11000
+
+#define IP_SUCCESS                  0
+#define IP_BUF_TOO_SMALL            (IP_STATUS_BASE + 1)
+#define IP_DEST_NET_UNREACHABLE     (IP_STATUS_BASE + 2)
+#define IP_DEST_HOST_UNREACHABLE    (IP_STATUS_BASE + 3)
+#define IP_DEST_PROT_UNREACHABLE    (IP_STATUS_BASE + 4)
+#define IP_DEST_PORT_UNREACHABLE    (IP_STATUS_BASE + 5)
+#define IP_NO_RESOURCES             (IP_STATUS_BASE + 6)
+#define IP_BAD_OPTION               (IP_STATUS_BASE + 7)
+#define IP_HW_ERROR                 (IP_STATUS_BASE + 8)
+#define IP_PACKET_TOO_BIG           (IP_STATUS_BASE + 9)
+#define IP_REQ_TIMED_OUT            (IP_STATUS_BASE + 10)
+#define IP_BAD_REQ                  (IP_STATUS_BASE + 11)
+#define IP_BAD_ROUTE                (IP_STATUS_BASE + 12)
+#define IP_TTL_EXPIRED_TRANSIT      (IP_STATUS_BASE + 13)
+#define IP_TTL_EXPIRED_REASSEM      (IP_STATUS_BASE + 14)
+#define IP_PARAM_PROBLEM            (IP_STATUS_BASE + 15)
+#define IP_SOURCE_QUENCH            (IP_STATUS_BASE + 16)
+#define IP_OPTION_TOO_BIG           (IP_STATUS_BASE + 17)
+#define IP_BAD_DESTINATION          (IP_STATUS_BASE + 18)
+
+#define IP_ADDR_DELETED             (IP_STATUS_BASE + 19)
+#define IP_SPEC_MTU_CHANGE          (IP_STATUS_BASE + 20)
+#define IP_MTU_CHANGE               (IP_STATUS_BASE + 21)
+#define IP_UNLOAD                   (IP_STATUS_BASE + 22)
+#define IP_ADDR_ADDED               (IP_STATUS_BASE + 23)
+#define IP_MEDIA_CONNECT            (IP_STATUS_BASE + 24)
+#define IP_MEDIA_DISCONNECT         (IP_STATUS_BASE + 25)
+#define IP_BIND_ADAPTER             (IP_STATUS_BASE + 26)
+#define IP_UNBIND_ADAPTER           (IP_STATUS_BASE + 27)
+#define IP_DEVICE_DOES_NOT_EXIST    (IP_STATUS_BASE + 28)
+#define IP_DUPLICATE_ADDRESS        (IP_STATUS_BASE + 29)
+#define IP_INTERFACE_METRIC_CHANGE  (IP_STATUS_BASE + 30)
+#define IP_RECONFIG_SECFLTR         (IP_STATUS_BASE + 31)
+#define IP_NEGOTIATING_IPSEC        (IP_STATUS_BASE + 32)
+#define IP_INTERFACE_WOL_CAPABILITY_CHANGE  (IP_STATUS_BASE + 33)
+#define IP_DUPLICATE_IPADD          (IP_STATUS_BASE + 34)
+
+#define IP_GENERAL_FAILURE          (IP_STATUS_BASE + 50)
+#define MAX_IP_STATUS               IP_GENERAL_FAILURE
+#define IP_PENDING                  (IP_STATUS_BASE + 255)
+
+//
+// Values used in the IP header Flags field.
+//
+#define IP_FLAG_DF      0x2         // Don't fragment this packet.
+
+//
+// Supported IP Option Types.
+//
+// These types define the options which may be used in the OptionsData field
+// of the ip_option_information structure.  See RFC 791 for a complete
+// description of each.
+//
+#define IP_OPT_EOL      0          // End of list option
+#define IP_OPT_NOP      1          // No operation
+#define IP_OPT_SECURITY 0x82       // Security option
+#define IP_OPT_LSRR     0x83       // Loose source route
+#define IP_OPT_SSRR     0x89       // Strict source route
+#define IP_OPT_RR       0x7        // Record route
+#define IP_OPT_TS       0x44       // Timestamp
+#define IP_OPT_SID      0x88       // Stream ID (obsolete)
+#define IP_OPT_ROUTER_ALERT 0x94  // Router Alert Option
+
+#define MAX_OPT_SIZE    40         // Maximum length of IP options in bytes
+
+//////////////////////////////////////////////////////////////////////
+// Global Constants and Types
+//////////////////////////////////////////////////////////////////////
+
+const char * const ICMP_DLL_NAME = "Iphlpapi.dll";
+const char * const ICMP_CREATE_FUNC = "IcmpCreateFile";
+const char * const ICMP_CLOSE_FUNC = "IcmpCloseHandle";
+const char * const ICMP_SEND_FUNC = "IcmpSendEcho";
+const char * const ICMP6_CREATE_FUNC = "Icmp6CreateFile";
+const char * const ICMP6_CLOSE_FUNC = "Icmp6CloseHandle";
+const char * const ICMP6_SEND_FUNC = "Icmp6SendEcho2";
+
+inline uint32 ReplySize(uint32 data_size, int family) {
+  if (family == AF_INET) {
+    // A ping error message is 8 bytes long, so make sure we allow for at least
+    // 8 bytes of reply data.
+    return sizeof(ICMP_ECHO_REPLY) + talk_base::_max<uint32>(8, data_size);
+  } else if (family == AF_INET6) {
+    // Per MSDN, Send6IcmpEcho2 needs at least one ICMPV6_ECHO_REPLY,
+    // 8 bytes for ICMP header, _and_ an IO_BLOCK_STATUS (2 pointers),
+    // in addition to the data size.
+    return sizeof(ICMPV6_ECHO_REPLY) + data_size + 8 + (2 * sizeof(DWORD*));
+  } else {
+    return 0;
+  }
+}
+
+//////////////////////////////////////////////////////////////////////
+// WinPing
+//////////////////////////////////////////////////////////////////////
+
+WinPing::WinPing()
+    : dll_(0), hping_(INVALID_HANDLE_VALUE), create_(0), close_(0), send_(0),
+      create6_(0), send6_(0), data_(0), dlen_(0), reply_(0),
+      rlen_(0), valid_(false) {
+
+  dll_ = LoadLibraryA(ICMP_DLL_NAME);
+  if (!dll_) {
+    LOG(LERROR) << "LoadLibrary: " << GetLastError();
+    return;
+  }
+
+  create_ = (PIcmpCreateFile) GetProcAddress(dll_, ICMP_CREATE_FUNC);
+  close_ = (PIcmpCloseHandle) GetProcAddress(dll_, ICMP_CLOSE_FUNC);
+  send_ = (PIcmpSendEcho) GetProcAddress(dll_, ICMP_SEND_FUNC);
+  if (!create_ || !close_ || !send_) {
+    LOG(LERROR) << "GetProcAddress(ICMP_*): " << GetLastError();
+    return;
+  }
+  hping_ = create_();
+  if (hping_ == INVALID_HANDLE_VALUE) {
+    LOG(LERROR) << "IcmpCreateFile: " << GetLastError();
+    return;
+  }
+
+  if (HasIPv6Enabled()) {
+    create6_ = (PIcmp6CreateFile) GetProcAddress(dll_, ICMP6_CREATE_FUNC);
+    send6_ = (PIcmp6SendEcho2) GetProcAddress(dll_, ICMP6_SEND_FUNC);
+    if (!create6_ || !send6_) {
+      LOG(LERROR) << "GetProcAddress(ICMP6_*): " << GetLastError();
+      return;
+    }
+    hping6_ = create6_();
+    if (hping6_ == INVALID_HANDLE_VALUE) {
+      LOG(LERROR) << "Icmp6CreateFile: " << GetLastError();
+    }
+  }
+
+  dlen_ = 0;
+  rlen_ = ReplySize(dlen_, AF_INET);
+  data_ = new char[dlen_];
+  reply_ = new char[rlen_];
+
+  valid_ = true;
+}
+
+WinPing::~WinPing() {
+  if ((hping_ != INVALID_HANDLE_VALUE) && close_) {
+    if (!close_(hping_))
+      LOG(WARNING) << "IcmpCloseHandle: " << GetLastError();
+  }
+  if ((hping6_ != INVALID_HANDLE_VALUE) && close_) {
+    if (!close_(hping6_)) {
+      LOG(WARNING) << "Icmp6CloseHandle: " << GetLastError();
+    }
+  }
+
+  if (dll_)
+    FreeLibrary(dll_);
+
+  delete[] data_;
+  delete[] reply_;
+}
+
+WinPing::PingResult WinPing::Ping(
+    IPAddress ip, uint32 data_size, uint32 timeout, uint8 ttl,
+    bool allow_fragments) {
+
+  if (data_size == 0 || timeout == 0 || ttl == 0) {
+    LOG(LERROR) << "IcmpSendEcho: data_size/timeout/ttl is 0.";
+    return PING_INVALID_PARAMS;
+  }
+
+  assert(IsValid());
+
+  IP_OPTION_INFORMATION ipopt;
+  memset(&ipopt, 0, sizeof(ipopt));
+  if (!allow_fragments)
+    ipopt.Flags |= IP_FLAG_DF;
+  ipopt.Ttl = ttl;
+
+  uint32 reply_size = ReplySize(data_size, ip.family());
+
+  if (data_size > dlen_) {
+    delete [] data_;
+    dlen_ = data_size;
+    data_ = new char[dlen_];
+    memset(data_, 'z', dlen_);
+  }
+
+  if (reply_size > rlen_) {
+    delete [] reply_;
+    rlen_ = reply_size;
+    reply_ = new char[rlen_];
+  }
+  DWORD result = 0;
+  if (ip.family() == AF_INET) {
+    result = send_(hping_, ip.ipv4_address().S_un.S_addr,
+                   data_, uint16(data_size), &ipopt,
+                   reply_, reply_size, timeout);
+  } else if (ip.family() == AF_INET6) {
+    sockaddr_in6 src = {0};
+    sockaddr_in6 dst = {0};
+    src.sin6_family = AF_INET6;
+    dst.sin6_family = AF_INET6;
+    dst.sin6_addr = ip.ipv6_address();
+    result = send6_(hping6_, NULL, NULL, NULL,
+                    &src, &dst,
+                    data_, int16(data_size), &ipopt,
+                    reply_, reply_size, timeout);
+  }
+  if (result == 0) {
+    DWORD error = GetLastError();
+    if (error == IP_PACKET_TOO_BIG)
+      return PING_TOO_LARGE;
+    if (error == IP_REQ_TIMED_OUT)
+      return PING_TIMEOUT;
+    LOG(LERROR) << "IcmpSendEcho(" << ip.ToSensitiveString()
+                << ", " << data_size << "): " << error;
+    return PING_FAIL;
+  }
+
+  return PING_SUCCESS;
+}
+
+//////////////////////////////////////////////////////////////////////
+// Microsoft Documenation
+//////////////////////////////////////////////////////////////////////
+//
+// Routine Name:
+//
+//     IcmpCreateFile
+//
+// Routine Description:
+//
+//     Opens a handle on which ICMP Echo Requests can be issued.
+//
+// Arguments:
+//
+//     None.
+//
+// Return Value:
+//
+//     An open file handle or INVALID_HANDLE_VALUE. Extended error information
+//     is available by calling GetLastError().
+//
+//////////////////////////////////////////////////////////////////////
+//
+// Routine Name:
+//
+//     IcmpCloseHandle
+//
+// Routine Description:
+//
+//     Closes a handle opened by ICMPOpenFile.
+//
+// Arguments:
+//
+//     IcmpHandle  - The handle to close.
+//
+// Return Value:
+//
+//     TRUE if the handle was closed successfully, otherwise FALSE. Extended
+//     error information is available by calling GetLastError().
+//
+//////////////////////////////////////////////////////////////////////
+//
+// Routine Name:
+//
+//     IcmpSendEcho
+//
+// Routine Description:
+//
+//     Sends an ICMP Echo request and returns any replies. The
+//     call returns when the timeout has expired or the reply buffer
+//     is filled.
+//
+// Arguments:
+//
+//     IcmpHandle           - An open handle returned by ICMPCreateFile.
+//
+//     DestinationAddress   - The destination of the echo request.
+//
+//     RequestData          - A buffer containing the data to send in the
+//                            request.
+//
+//     RequestSize          - The number of bytes in the request data buffer.
+//
+//     RequestOptions       - Pointer to the IP header options for the request.
+//                            May be NULL.
+//
+//     ReplyBuffer          - A buffer to hold any replies to the request.
+//                            On return, the buffer will contain an array of
+//                            ICMP_ECHO_REPLY structures followed by the
+//                            options and data for the replies. The buffer
+//                            should be large enough to hold at least one
+//                            ICMP_ECHO_REPLY structure plus
+//                            MAX(RequestSize, 8) bytes of data since an ICMP
+//                            error message contains 8 bytes of data.
+//
+//     ReplySize            - The size in bytes of the reply buffer.
+//
+//     Timeout              - The time in milliseconds to wait for replies.
+//
+// Return Value:
+//
+//     Returns the number of ICMP_ECHO_REPLY structures stored in ReplyBuffer.
+//     The status of each reply is contained in the structure. If the return
+//     value is zero, extended error information is available via
+//     GetLastError().
+//
+//////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/winping.h b/talk/base/winping.h
new file mode 100644
index 0000000..34b5bbd
--- /dev/null
+++ b/talk/base/winping.h
@@ -0,0 +1,120 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_WINPING_H__
+#define TALK_BASE_WINPING_H__
+
+#ifdef WIN32
+
+#include "talk/base/win32.h"
+#include "talk/base/basictypes.h"
+#include "talk/base/IPAddress.h"
+
+namespace talk_base {
+
+// This class wraps a Win32 API for doing ICMP pinging.  This API, unlike the
+// the normal socket APIs (as implemented on Win9x), will return an error if
+// an ICMP packet with the dont-fragment bit set is too large.  This means this
+// class can be used to detect the MTU to a given address.
+
+typedef struct ip_option_information {
+    UCHAR   Ttl;                // Time To Live
+    UCHAR   Tos;                // Type Of Service
+    UCHAR   Flags;              // IP header flags
+    UCHAR   OptionsSize;        // Size in bytes of options data
+    PUCHAR  OptionsData;        // Pointer to options data
+} IP_OPTION_INFORMATION, * PIP_OPTION_INFORMATION;
+
+typedef HANDLE (WINAPI *PIcmpCreateFile)();
+
+typedef BOOL (WINAPI *PIcmpCloseHandle)(HANDLE icmp_handle);
+
+typedef HANDLE (WINAPI *PIcmp6CreateFile)();
+
+typedef BOOL (WINAPI *PIcmp6CloseHandle)(HANDLE icmp_handle);
+
+typedef DWORD (WINAPI *PIcmpSendEcho)(
+    HANDLE                   IcmpHandle,
+    ULONG                    DestinationAddress,
+    LPVOID                   RequestData,
+    WORD                     RequestSize,
+    PIP_OPTION_INFORMATION   RequestOptions,
+    LPVOID                   ReplyBuffer,
+    DWORD                    ReplySize,
+    DWORD                    Timeout);
+
+typedef DWORD (WINAPI *PIcmp6SendEcho2)(
+    HANDLE IcmpHandle,
+    HANDLE Event,
+    FARPROC ApcRoutine,
+    PVOID ApcContext,
+    struct sockaddr_in6 *SourceAddress,
+    struct sockaddr_in6 *DestinationAddress,
+    LPVOID RequestData,
+    WORD RequestSize,
+    PIP_OPTION_INFORMATION RequestOptions,
+    LPVOID ReplyBuffer,
+    DWORD ReplySize,
+    DWORD Timeout
+);
+
+class WinPing {
+public:
+    WinPing();
+    ~WinPing();
+
+    // Determines whether the class was initialized correctly.
+    bool IsValid() { return valid_; }
+
+    // Attempts to send a ping with the given parameters.
+    enum PingResult { PING_FAIL, PING_INVALID_PARAMS,
+                      PING_TOO_LARGE, PING_TIMEOUT, PING_SUCCESS };
+    PingResult Ping(
+        IPAddress ip, uint32 data_size, uint32 timeout_millis, uint8 ttl,
+        bool allow_fragments);
+
+private:
+    HMODULE dll_;
+    HANDLE hping_;
+    HANDLE hping6_;
+    PIcmpCreateFile create_;
+    PIcmpCloseHandle close_;
+    PIcmpSendEcho send_;
+    PIcmp6CreateFile create6_;
+    PIcmp6SendEcho2 send6_;
+    char* data_;
+    uint32 dlen_;
+    char* reply_;
+    uint32 rlen_;
+    bool valid_;
+};
+
+} // namespace talk_base
+
+#endif // WIN32
+
+#endif // TALK_BASE_WINPING_H__
diff --git a/talk/base/worker.cc b/talk/base/worker.cc
new file mode 100644
index 0000000..28fcc9f
--- /dev/null
+++ b/talk/base/worker.cc
@@ -0,0 +1,92 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/worker.h"
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+enum {
+  MSG_HAVEWORK = 0,
+};
+
+Worker::Worker() : worker_thread_(NULL) {}
+
+Worker::~Worker() {
+  // We need to already be stopped before being destroyed. We cannot call
+  // StopWork() from here because the subclass's data has already been
+  // destructed, so OnStop() cannot be called.
+  ASSERT(!worker_thread_);
+}
+
+bool Worker::StartWork() {
+  talk_base::Thread *me = talk_base::Thread::Current();
+  if (worker_thread_) {
+    if (worker_thread_ == me) {
+      // Already working on this thread, so nothing to do.
+      return true;
+    } else {
+      LOG(LS_ERROR) << "Automatically switching threads is not supported";
+      ASSERT(false);
+      return false;
+    }
+  }
+  worker_thread_ = me;
+  OnStart();
+  return true;
+}
+
+bool Worker::StopWork() {
+  if (!worker_thread_) {
+    // Already not working, so nothing to do.
+    return true;
+  } else if (worker_thread_ != talk_base::Thread::Current()) {
+    LOG(LS_ERROR) << "Stopping from a different thread is not supported";
+    ASSERT(false);
+    return false;
+  }
+  OnStop();
+  worker_thread_->Clear(this, MSG_HAVEWORK);
+  worker_thread_ = NULL;
+  return true;
+}
+
+void Worker::HaveWork() {
+  ASSERT(worker_thread_ != NULL);
+  worker_thread_->Post(this, MSG_HAVEWORK);
+}
+
+void Worker::OnMessage(talk_base::Message *msg) {
+  ASSERT(msg->message_id == MSG_HAVEWORK);
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  OnHaveWork();
+}
+
+}  // namespace talk_base
diff --git a/talk/base/worker.h b/talk/base/worker.h
new file mode 100644
index 0000000..582fe1b
--- /dev/null
+++ b/talk/base/worker.h
@@ -0,0 +1,89 @@
+/*
+ * 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_WORKER_H_
+#define TALK_BASE_WORKER_H_
+
+#include "talk/base/constructormagic.h"
+#include "talk/base/messagehandler.h"
+
+namespace talk_base {
+
+class Thread;
+
+// A worker is an object that performs some specific long-lived task in an
+// event-driven manner.
+// The only method that should be considered thread-safe is HaveWork(), which
+// allows you to signal the availability of work from any thread. All other
+// methods are thread-hostile. Specifically:
+// StartWork()/StopWork() should not be called concurrently with themselves or
+// each other, and it is an error to call them while the worker is running on
+// a different thread.
+// The destructor may not be called if the worker is currently running
+// (regardless of the thread), but you can call StopWork() in a subclass's
+// destructor.
+class Worker : private MessageHandler {
+ public:
+  Worker();
+
+  // Destroys this Worker, but it must have already been stopped via StopWork().
+  virtual ~Worker();
+
+  // Attaches the worker to the current thread and begins processing work if not
+  // already doing so.
+  bool StartWork();
+  // Stops processing work if currently doing so and detaches from the current
+  // thread.
+  bool StopWork();
+
+ protected:
+  // Signal that work is available to be done. May only be called within the
+  // lifetime of a OnStart()/OnStop() pair.
+  void HaveWork();
+
+  // These must be implemented by a subclass.
+  // Called on the worker thread to start working.
+  virtual void OnStart() = 0;
+  // Called on the worker thread when work has been signalled via HaveWork().
+  virtual void OnHaveWork() = 0;
+  // Called on the worker thread to stop working. Upon return, any pending
+  // OnHaveWork() calls are cancelled.
+  virtual void OnStop() = 0;
+
+ private:
+  // Inherited from MessageHandler.
+  virtual void OnMessage(Message *msg);
+
+  // The thread that is currently doing the work.
+  Thread *worker_thread_;
+
+  DISALLOW_COPY_AND_ASSIGN(Worker);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_WORKER_H_
diff --git a/talk/build/build_jar.sh b/talk/build/build_jar.sh
new file mode 100755
index 0000000..e2953aa
--- /dev/null
+++ b/talk/build/build_jar.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+
+# libjingle
+# Copyright 2013, Google Inc.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#  1. Redistributions of source code must retain the above copyright notice,
+#     this list of conditions and the following disclaimer.
+#  2. Redistributions in binary form must reproduce the above copyright notice,
+#     this list of conditions and the following disclaimer in the documentation
+#     and/or other materials provided with the distribution.
+#  3. The name of the author may not be used to endorse or promote products
+#     derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# javac & jar wrapper helping to simplify gyp action specification.
+
+set -e  # Exit on any error.
+
+# Allow build-error parsers (such as emacs' compilation-mode) to find failing
+# files easily.
+echo "$0: Entering directory \``pwd`'"
+
+JAVA_HOME="$1"; shift
+JAR_NAME="$1"; shift
+TMP_DIR="$1"; shift
+CLASSPATH="$1"; shift
+
+if [ -z "$1" ]; then
+  echo "Usage: $0 jar-name temp-work-dir source-path-dir .so-to-bundle " \
+    "classpath path/to/Source1.java path/to/Source2.java ..." >&2
+  exit 1
+fi
+
+rm -rf "$TMP_DIR"
+mkdir -p "$TMP_DIR"
+
+$JAVA_HOME/bin/javac -Xlint:deprecation -Xlint:unchecked -d "$TMP_DIR" \
+  -classpath "$CLASSPATH" "$@"
+$JAVA_HOME/bin/jar cf "$JAR_NAME" -C "$TMP_DIR" .
diff --git a/talk/build/common.gypi b/talk/build/common.gypi
new file mode 100644
index 0000000..28481eb
--- /dev/null
+++ b/talk/build/common.gypi
@@ -0,0 +1,117 @@
+#
+# libjingle
+# Copyright 2012, Google Inc.
+#
+# Redistribution and use in source and binary forms, with or without 
+# modification, are permitted provided that the following conditions are met:
+#
+#  1. Redistributions of source code must retain the above copyright notice, 
+#     this list of conditions and the following disclaimer.
+#  2. Redistributions in binary form must reproduce the above copyright notice,
+#     this list of conditions and the following disclaimer in the documentation
+#     and/or other materials provided with the distribution.
+#  3. The name of the author may not be used to endorse or promote products 
+#     derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 common settings for building libjingle components.
+
+{
+  'variables': {
+    # TODO(ronghuawu): Chromium build will need a different libjingle_root.
+    'libjingle_root%': '<(DEPTH)',
+    # TODO(ronghuawu): For now, disable the Chrome plugins, which causes a
+    # flood of chromium-style warnings.
+    'clang_use_chrome_plugins%': 0,
+    'libpeer_target_type%': 'static_library',
+    'java_home%': '<!(python -c "import os; print os.getenv(\'JAVA_HOME\');")',
+    # Whether or not to build the ObjectiveC PeerConnection API & tests.
+    'libjingle_objc%' : 0,
+  },
+  'target_defaults': {
+    'include_dirs': [
+      '../..',
+      '../../third_party',
+      '../../third_party/webrtc',
+    ],
+    'defines': [
+      'EXPAT_RELATIVE_PATH',
+      'FEATURE_ENABLE_VOICEMAIL',
+      'GTEST_RELATIVE_PATH',
+      'JSONCPP_RELATIVE_PATH',
+      'LOGGING=1',
+      'SRTP_RELATIVE_PATH',
+
+      # Feature selection
+      'FEATURE_ENABLE_SSL',
+      'FEATURE_ENABLE_VOICEMAIL',
+      'FEATURE_ENABLE_PSTN',
+      # TODO(eric): enable HAVE_NSS_SSL_H and SSL_USE_NSS once they are ready.
+      # 'HAVE_NSS_SSL_H=1',
+      'HAVE_SRTP',
+      'HAVE_WEBRTC_VIDEO',
+      'HAVE_WEBRTC_VOICE',
+      # 'SSL_USE_NSS',
+      # TODO(ronghuawu): Remove this once libjingle is updated to use the new
+      # webrtc.
+      'USE_WEBRTC_DEV_BRANCH',
+    ],
+    'conditions': [
+      # TODO(ronghuawu): Support dynamic library build.
+      ['"<(libpeer_target_type)"=="static_library"', {
+        'defines': [ 'LIBPEERCONNECTION_LIB=1' ],
+      }],
+      ['OS=="linux"', {
+        'defines': [
+          'LINUX',
+        ],
+        'conditions': [
+          ['clang==1', {
+            'cflags': [
+              # TODO(ronghuawu): Fix the warning caused by
+              # LateBindingSymbolTable::TableInfo from
+              # latebindingsymboltable.cc.def and remove below flag.
+              '-Wno-address-of-array-temporary',
+            ],
+          }],
+        ],
+      }],
+      ['OS=="mac"', {
+        'defines': [
+          'OSX',
+        ],
+      }],
+      ['OS=="ios"', {
+        'defines': [
+          'IOS',
+          'SSL_USE_NSS',
+          'SSL_USE_NSS_RNG',
+        ],
+        'variables': {
+          'use_nss%': 1,
+        },
+      }],
+      ['os_posix==1', {
+        'defines': [
+          'HASH_NAMESPACE=__gnu_cxx',
+          'POSIX',
+          'DISABLE_DYNAMIC_CAST',
+          'HAVE_OPENSSL_SSL_H=1',
+          # The POSIX standard says we have to define this.
+          '_REENTRANT',
+        ],
+      }],
+    ],
+  }, # target_defaults
+}
diff --git a/talk/examples/android/AndroidManifest.xml b/talk/examples/android/AndroidManifest.xml
new file mode 100644
index 0000000..52e67a2
--- /dev/null
+++ b/talk/examples/android/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="org.appspot.apprtc"
+          android:versionCode="1"
+          android:versionName="1.0">
+
+  <uses-feature android:name="android.hardware.camera" />
+  <uses-feature android:name="android.hardware.camera.autofocus" />
+  <uses-feature android:glEsVersion="0x00020000" android:required="true"></uses-feature>
+  <uses-sdk android:minSdkVersion="13" android:targetSdkVersion="17" />
+
+  <uses-permission android:name="android.permission.CAMERA"></uses-permission>
+  <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+  <uses-permission android:name="android.permission.RECORD_AUDIO" />
+  <uses-permission android:name="android.permission.INTERNET" />
+  <uses-permission android:name="android.permission.WAKE_LOCK" />
+  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+
+  <application android:label="@string/app_name"
+               android:icon="@drawable/ic_launcher"
+               android:allowBackup="false">
+    <activity android:name="AppRTCDemoActivity"
+              android:label="@string/app_name"
+              android:screenOrientation="landscape"
+              android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
+      <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+      </intent-filter>
+
+      <intent-filter>
+        <action android:name="android.intent.action.VIEW" />
+        <category android:name="android.intent.category.DEFAULT" />
+        <category android:name="android.intent.category.BROWSABLE" />
+        <data android:scheme="https" android:host="apprtc.appspot.com" />
+        <data android:scheme="http" android:host="apprtc.appspot.com" />
+      </intent-filter>
+    </activity>
+  </application>
+</manifest>
diff --git a/talk/examples/android/README b/talk/examples/android/README
new file mode 100644
index 0000000..feabadb
--- /dev/null
+++ b/talk/examples/android/README
@@ -0,0 +1,43 @@
+This directory contains an example Android client for http://apprtc.appspot.com 
+
+Prerequisites:
+- Make sure gclient is checking out tools necessary to target Android: your
+  .gclient file should contain a line like:
+  target_os = ['android', 'unix']
+  Make sure to re-run gclient sync after adding this to download the tools.
+- Env vars need to be set up to target Android; easiest way to do this is to run
+  (from the libjingle trunk directory):
+  . ./build/android/envsetup.sh
+  Note that this clobbers any previously-set $GYP_DEFINES so it must be done
+  before the next item.
+- Set up webrtc-related GYP variables:
+  export GYP_DEFINES="build_with_libjingle=1 build_with_chromium=0 libjingle_java=1 $GYP_DEFINES"
+  export JAVA_HOME=</path/to/JDK>
+  export PATH=$JAVA_HOME/bin:$PATH
+  To cause WEBRTC_LOGGING to emit to Android's logcat, add enable_tracing=1 to
+  the $GYP_DEFINES above.
+- When targeting both desktop & android, make sure to use a different output_dir
+  value in $GYP_GENERATOR_FLAGS or you'll likely end up with mismatched ARM &
+  x86 output artifacts.  If you use an output_dir other than out/ make sure to
+  modify the command-lines below appropriately.
+- Finally, run "gclient runhooks" to generate Android-targeting .ninja files.
+
+Example of building & using the app:
+
+cd <path/to/libjingle>/trunk
+ninja -C out/Debug AppRTCDemo
+adb install -r out/Debug/AppRTCDemo-debug.apk
+
+In desktop chrome, navigate to http://apprtc.appspot.com and note the r=<NNN> room 
+this redirects to.  Launch AppRTC on the device and enter the same <NNN> into
+the dialog box.
+
+Alternatively, replace the <NNN> from the desktop chrome into the following
+command:
+adb shell am start -a android.intent.action.VIEW -d '"https://apprtc.appspot.com/?r=<NNN>"'
+This should result in the app launching on Android and connecting to the apprtc
+page displayed in the desktop browser.
+
+Yet another way to is to send the apprtc room URL to the Android device (e.g. using
+https://chrome.google.com/webstore/detail/google-chrome-to-phone-ex/oadboiipflhobonjjffjbfekfjcgkhco)
+and choose to open the URL with the AppRTCDemo app.
diff --git a/talk/examples/android/ant.properties b/talk/examples/android/ant.properties
new file mode 100644
index 0000000..b0971e8
--- /dev/null
+++ b/talk/examples/android/ant.properties
@@ -0,0 +1,17 @@
+# This file is used to override default values used by the Ant build system.
+#
+# This file must be checked into Version Control Systems, as it is
+# integral to the build system of your project.
+
+# This file is only used by the Ant script.
+
+# You can use this to override default values such as
+#  'source.dir' for the location of your java source folder and
+#  'out.dir' for the location of your output folder.
+
+# You can also use it define how the release builds are signed by declaring
+# the following properties:
+#  'key.store' for the location of your keystore and
+#  'key.alias' for the name of the key to use.
+# The password will be asked during the build when you use the 'release' target.
+
diff --git a/talk/examples/android/assets/channel.html b/talk/examples/android/assets/channel.html
new file mode 100644
index 0000000..86c2b44
--- /dev/null
+++ b/talk/examples/android/assets/channel.html
@@ -0,0 +1,54 @@
+<html>
+  <head>
+    <script src="https://apprtc.appspot.com/_ah/channel/jsapi"></script>
+  </head>
+  <!--
+  Helper HTML that redirects Google AppEngine's Channel API to a JS object named
+  |androidMessageHandler|, which is expected to be injected into the WebView
+  rendering this page by an Android app's class such as AppRTCClient
+  -->
+  <body onbeforeunload="closeSocket()" onload="openSocket()">
+    <script type="text/javascript">
+      // QueryString is copy/pasta from
+      // chromium's chrome/test/data/media/html/utils.js.
+      var QueryString = function () {
+        // Allows access to query parameters on the URL; e.g., given a URL like:
+        //    http://<url>/my.html?test=123&bob=123
+        // parameters can now be accessed via QueryString.test or QueryString.bob.
+        var params = {};
+
+        // RegEx to split out values by &.
+        var r = /([^&=]+)=?([^&]*)/g;
+
+        // Lambda function for decoding extracted match values. Replaces '+' with
+        // space so decodeURIComponent functions properly.
+        function d(s) { return decodeURIComponent(s.replace(/\+/g, ' ')); }
+
+        var match;
+        while (match = r.exec(window.location.search.substring(1)))
+          params[d(match[1])] = d(match[2]);
+
+        return params;
+      } ();
+
+      var channel = null;
+      var socket = null;
+
+      function openSocket() {
+        if (!QueryString.token || !QueryString.token.match(/^[A-z0-9_-]+$/))
+          throw "Missing/malformed token parameter: " + QueryString.token;
+        channel = new goog.appengine.Channel(QueryString.token);
+        socket = channel.open({
+          'onopen': function() { androidMessageHandler.onOpen(); },
+          'onmessage': function(msg) { androidMessageHandler.onMessage(msg.data); },
+          'onclose': function() { androidMessageHandler.onClose(); },
+          'onerror': function(err) { androidMessageHandler.onError(err.code, err.description); }
+        });
+      }
+
+      function closeSocket() {
+        socket.close();
+      }
+    </script>
+  </body>
+</html>
diff --git a/talk/examples/android/build.xml b/talk/examples/android/build.xml
new file mode 100644
index 0000000..ae06794
--- /dev/null
+++ b/talk/examples/android/build.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="AppRTCDemo" default="help">
+
+    <!-- The local.properties file is created and updated by the 'android' tool.
+         It contains the path to the SDK. It should *NOT* be checked into
+         Version Control Systems. -->
+    <property file="local.properties" />
+
+    <!-- The ant.properties file can be created by you. It is only edited by the
+         'android' tool to add properties to it.
+         This is the place to change some Ant specific build properties.
+         Here are some properties you may want to change/update:
+
+         source.dir
+             The name of the source directory. Default is 'src'.
+         out.dir
+             The name of the output directory. Default is 'bin'.
+
+         For other overridable properties, look at the beginning of the rules
+         files in the SDK, at tools/ant/build.xml
+
+         Properties related to the SDK location or the project target should
+         be updated using the 'android' tool with the 'update' action.
+
+         This file is an integral part of the build system for your
+         application and should be checked into Version Control Systems.
+
+         -->
+    <property file="ant.properties" />
+
+    <!-- if sdk.dir was not set from one of the property file, then
+         get it from the ANDROID_HOME env var.
+         This must be done before we load project.properties since
+         the proguard config can use sdk.dir -->
+    <property environment="env" />
+    <condition property="sdk.dir" value="${env.ANDROID_SDK_ROOT}">
+        <isset property="env.ANDROID_SDK_ROOT" />
+    </condition>
+
+    <!-- The project.properties file is created and updated by the 'android'
+         tool, as well as ADT.
+
+         This contains project specific properties such as project target, and library
+         dependencies. Lower level build properties are stored in ant.properties
+         (or in .classpath for Eclipse projects).
+
+         This file is an integral part of the build system for your
+         application and should be checked into Version Control Systems. -->
+    <loadproperties srcFile="project.properties" />
+
+    <!-- quick check on sdk.dir -->
+    <fail
+            message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
+            unless="sdk.dir"
+    />
+
+    <!--
+        Import per project custom build rules if present at the root of the project.
+        This is the place to put custom intermediary targets such as:
+            -pre-build
+            -pre-compile
+            -post-compile (This is typically used for code obfuscation.
+                           Compiled code location: ${out.classes.absolute.dir}
+                           If this is not done in place, override ${out.dex.input.absolute.dir})
+            -post-package
+            -post-build
+            -pre-clean
+    -->
+    <import file="custom_rules.xml" optional="true" />
+
+    <!-- Import the actual build file.
+
+         To customize existing targets, there are two options:
+         - Customize only one target:
+             - copy/paste the target into this file, *before* the
+               <import> task.
+             - customize it to your needs.
+         - Customize the whole content of build.xml
+             - copy/paste the content of the rules files (minus the top node)
+               into this file, replacing the <import> task.
+             - customize to your needs.
+
+         ***********************
+         ****** IMPORTANT ******
+         ***********************
+         In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
+         in order to avoid having your file be overridden by tools such as "android update project"
+    -->
+    <!-- version-tag: 1 -->
+    <import file="${sdk.dir}/tools/ant/build.xml" />
+
+</project>
diff --git a/talk/examples/android/jni/Android.mk b/talk/examples/android/jni/Android.mk
new file mode 100644
index 0000000..8e80160
--- /dev/null
+++ b/talk/examples/android/jni/Android.mk
@@ -0,0 +1,2 @@
+# This space intentionally left blank (required for Android build system).
+
diff --git a/talk/examples/android/project.properties b/talk/examples/android/project.properties
new file mode 100644
index 0000000..a3ee5ab6
--- /dev/null
+++ b/talk/examples/android/project.properties
@@ -0,0 +1,14 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-17
diff --git a/talk/examples/android/res/drawable-hdpi/ic_launcher.png b/talk/examples/android/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..f3e9d12
--- /dev/null
+++ b/talk/examples/android/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/talk/examples/android/res/drawable-ldpi/ic_launcher.png b/talk/examples/android/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..5492ed7
--- /dev/null
+++ b/talk/examples/android/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/talk/examples/android/res/drawable-mdpi/ic_launcher.png b/talk/examples/android/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..9709a1e
--- /dev/null
+++ b/talk/examples/android/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/talk/examples/android/res/drawable-xhdpi/ic_launcher.png b/talk/examples/android/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..db2c4f6
--- /dev/null
+++ b/talk/examples/android/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/talk/examples/android/res/values/strings.xml b/talk/examples/android/res/values/strings.xml
new file mode 100644
index 0000000..bac765a
--- /dev/null
+++ b/talk/examples/android/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="app_name">AppRTC</string>
+</resources>
diff --git a/talk/examples/android/src/org/appspot/apprtc/AppRTCClient.java b/talk/examples/android/src/org/appspot/apprtc/AppRTCClient.java
new file mode 100644
index 0000000..fe41564
--- /dev/null
+++ b/talk/examples/android/src/org/appspot/apprtc/AppRTCClient.java
@@ -0,0 +1,432 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.appspot.apprtc;
+
+import android.app.Activity;
+import android.os.AsyncTask;
+import android.util.Log;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.webrtc.MediaConstraints;
+import org.webrtc.PeerConnection;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Scanner;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Negotiates signaling for chatting with apprtc.appspot.com "rooms".
+ * Uses the client<->server specifics of the apprtc AppEngine webapp.
+ *
+ * To use: create an instance of this object (registering a message handler) and
+ * call connectToRoom().  Once that's done call sendMessage() and wait for the
+ * registered handler to be called with received messages.
+ */
+public class AppRTCClient {
+  private static final String TAG = "AppRTCClient";
+  private GAEChannelClient channelClient;
+  private final Activity activity;
+  private final GAEChannelClient.MessageHandler gaeHandler;
+  private final IceServersObserver iceServersObserver;
+
+  // These members are only read/written under sendQueue's lock.
+  private LinkedList<String> sendQueue = new LinkedList<String>();
+  private AppRTCSignalingParameters appRTCSignalingParameters;
+
+  /**
+   * Callback fired once the room's signaling parameters specify the set of
+   * ICE servers to use.
+   */
+  public static interface IceServersObserver {
+    public void onIceServers(List<PeerConnection.IceServer> iceServers);
+  }
+
+  public AppRTCClient(
+      Activity activity, GAEChannelClient.MessageHandler gaeHandler,
+      IceServersObserver iceServersObserver) {
+    this.activity = activity;
+    this.gaeHandler = gaeHandler;
+    this.iceServersObserver = iceServersObserver;
+  }
+
+  /**
+   * Asynchronously connect to an AppRTC room URL, e.g.
+   * https://apprtc.appspot.com/?r=NNN and register message-handling callbacks
+   * on its GAE Channel.
+   */
+  public void connectToRoom(String url) {
+    while (url.indexOf('?') < 0) {
+      // Keep redirecting until we get a room number.
+      (new RedirectResolver()).execute(url);
+      return;  // RedirectResolver above calls us back with the next URL.
+    }
+    (new RoomParameterGetter()).execute(url);
+  }
+
+  /**
+   * Disconnect from the GAE Channel.
+   */
+  public void disconnect() {
+    if (channelClient != null) {
+      channelClient.close();
+      channelClient = null;
+    }
+  }
+
+  /**
+   * Queue a message for sending to the room's channel and send it if already
+   * connected (other wise queued messages are drained when the channel is
+     eventually established).
+   */
+  public synchronized void sendMessage(String msg) {
+    synchronized (sendQueue) {
+      sendQueue.add(msg);
+    }
+    requestQueueDrainInBackground();
+  }
+
+  public boolean isInitiator() {
+    return appRTCSignalingParameters.initiator;
+  }
+
+  public MediaConstraints pcConstraints() {
+    return appRTCSignalingParameters.pcConstraints;
+  }
+
+  public MediaConstraints videoConstraints() {
+    return appRTCSignalingParameters.videoConstraints;
+  }
+
+  // Struct holding the signaling parameters of an AppRTC room.
+  private class AppRTCSignalingParameters {
+    public final List<PeerConnection.IceServer> iceServers;
+    public final String gaeBaseHref;
+    public final String channelToken;
+    public final String postMessageUrl;
+    public final boolean initiator;
+    public final MediaConstraints pcConstraints;
+    public final MediaConstraints videoConstraints;
+
+    public AppRTCSignalingParameters(
+        List<PeerConnection.IceServer> iceServers,
+        String gaeBaseHref, String channelToken, String postMessageUrl,
+        boolean initiator, MediaConstraints pcConstraints,
+        MediaConstraints videoConstraints) {
+      this.iceServers = iceServers;
+      this.gaeBaseHref = gaeBaseHref;
+      this.channelToken = channelToken;
+      this.postMessageUrl = postMessageUrl;
+      this.initiator = initiator;
+      this.pcConstraints = pcConstraints;
+      this.videoConstraints = videoConstraints;
+    }
+  }
+
+  // Load the given URL and return the value of the Location header of the
+  // resulting 302 response.  If the result is not a 302, throws.
+  private class RedirectResolver extends AsyncTask<String, Void, String> {
+    @Override
+    protected String doInBackground(String... urls) {
+      if (urls.length != 1) {
+        throw new RuntimeException("Must be called with a single URL");
+      }
+      try {
+        return followRedirect(urls[0]);
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    @Override
+    protected void onPostExecute(String url) {
+      connectToRoom(url);
+    }
+
+    private String followRedirect(String url) throws IOException {
+      HttpURLConnection connection = (HttpURLConnection)
+          new URL(url).openConnection();
+      connection.setInstanceFollowRedirects(false);
+      int code = connection.getResponseCode();
+      if (code != HttpURLConnection.HTTP_MOVED_TEMP) {
+        throw new IOException("Unexpected response: " + code + " for " + url +
+            ", with contents: " + drainStream(connection.getInputStream()));
+      }
+      int n = 0;
+      String name, value;
+      while ((name = connection.getHeaderFieldKey(n)) != null) {
+        value = connection.getHeaderField(n);
+        if (name.equals("Location")) {
+          return value;
+        }
+        ++n;
+      }
+      throw new IOException("Didn't find Location header!");
+    }
+  }
+
+  // AsyncTask that converts an AppRTC room URL into the set of signaling
+  // parameters to use with that room.
+  private class RoomParameterGetter
+      extends AsyncTask<String, Void, AppRTCSignalingParameters> {
+    @Override
+    protected AppRTCSignalingParameters doInBackground(String... urls) {
+      if (urls.length != 1) {
+        throw new RuntimeException("Must be called with a single URL");
+      }
+      try {
+        return getParametersForRoomUrl(urls[0]);
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    @Override
+    protected void onPostExecute(AppRTCSignalingParameters params) {
+      channelClient =
+          new GAEChannelClient(activity, params.channelToken, gaeHandler);
+      synchronized (sendQueue) {
+        appRTCSignalingParameters = params;
+      }
+      requestQueueDrainInBackground();
+      iceServersObserver.onIceServers(appRTCSignalingParameters.iceServers);
+    }
+
+    // Fetches |url| and fishes the signaling parameters out of the HTML via
+    // regular expressions.
+    //
+    // TODO(fischman): replace this hackery with a dedicated JSON-serving URL in
+    // apprtc so that this isn't necessary (here and in other future apps that
+    // want to interop with apprtc).
+    private AppRTCSignalingParameters getParametersForRoomUrl(String url)
+        throws IOException {
+      final Pattern fullRoomPattern = Pattern.compile(
+          ".*\n *Sorry, this room is full\\..*");
+
+      String roomHtml =
+          drainStream((new URL(url)).openConnection().getInputStream());
+
+      Matcher fullRoomMatcher = fullRoomPattern.matcher(roomHtml);
+      if (fullRoomMatcher.find()) {
+        throw new IOException("Room is full!");
+      }
+
+      String gaeBaseHref = url.substring(0, url.indexOf('?'));
+      String token = getVarValue(roomHtml, "channelToken", true);
+      String postMessageUrl = "/message?r=" +
+          getVarValue(roomHtml, "roomKey", true) + "&u=" +
+          getVarValue(roomHtml, "me", true);
+      boolean initiator = getVarValue(roomHtml, "initiator", false).equals("1");
+      LinkedList<PeerConnection.IceServer> iceServers =
+          iceServersFromPCConfigJSON(getVarValue(roomHtml, "pcConfig", false));
+
+      boolean isTurnPresent = false;
+      for (PeerConnection.IceServer server : iceServers) {
+        if (server.uri.startsWith("turn:")) {
+          isTurnPresent = true;
+          break;
+        }
+      }
+      if (!isTurnPresent) {
+        iceServers.add(
+            requestTurnServer(getVarValue(roomHtml, "turnUrl", true)));
+      }
+
+      MediaConstraints pcConstraints = constraintsFromJSON(
+          getVarValue(roomHtml, "pcConstraints", false));
+      Log.d(TAG, "pcConstraints: " + pcConstraints);
+
+      MediaConstraints videoConstraints = constraintsFromJSON(
+          getVideoConstraints(
+              getVarValue(roomHtml, "mediaConstraints", false)));
+      Log.d(TAG, "videoConstraints: " + videoConstraints);
+
+      return new AppRTCSignalingParameters(
+          iceServers, gaeBaseHref, token, postMessageUrl, initiator,
+          pcConstraints, videoConstraints);
+    }
+
+    private String getVideoConstraints(String mediaConstraintsString) {
+      try {
+        JSONObject json = new JSONObject(mediaConstraintsString);
+        JSONObject videoJson = json.optJSONObject("video");
+        if (videoJson == null) {
+          return "";
+        }
+        return videoJson.toString();
+      } catch (JSONException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    private MediaConstraints constraintsFromJSON(String jsonString) {
+      try {
+        MediaConstraints constraints = new MediaConstraints();
+        JSONObject json = new JSONObject(jsonString);
+        JSONObject mandatoryJSON = json.optJSONObject("mandatory");
+        if (mandatoryJSON != null) {
+          JSONArray mandatoryKeys = mandatoryJSON.names();
+          if (mandatoryKeys != null) {
+            for (int i = 0; i < mandatoryKeys.length(); ++i) {
+              String key = (String) mandatoryKeys.getString(i);
+              String value = mandatoryJSON.getString(key);
+              constraints.mandatory.add(
+                  new MediaConstraints.KeyValuePair(key, value));
+            }
+          }
+        }
+        JSONArray optionalJSON = json.optJSONArray("optional");
+        if (optionalJSON != null) {
+          for (int i = 0; i < optionalJSON.length(); ++i) {
+            JSONObject keyValueDict = optionalJSON.getJSONObject(i);
+            String key = keyValueDict.names().getString(0);
+            String value = keyValueDict.getString(key);
+            constraints.optional.add(
+                new MediaConstraints.KeyValuePair(key, value));
+          }
+        }
+        return constraints;
+      } catch (JSONException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    // Scan |roomHtml| for declaration & assignment of |varName| and return its
+    // value, optionally stripping outside quotes if |stripQuotes| requests it.
+    private String getVarValue(
+        String roomHtml, String varName, boolean stripQuotes)
+        throws IOException {
+      final Pattern pattern = Pattern.compile(
+          ".*\n *var " + varName + " = ([^\n]*);\n.*");
+      Matcher matcher = pattern.matcher(roomHtml);
+      if (!matcher.find()) {
+        throw new IOException("Missing " + varName + " in HTML: " + roomHtml);
+      }
+      String varValue = matcher.group(1);
+      if (matcher.find()) {
+        throw new IOException("Too many " + varName + " in HTML: " + roomHtml);
+      }
+      if (stripQuotes) {
+        varValue = varValue.substring(1, varValue.length() - 1);
+      }
+      return varValue;
+    }
+
+    // Requests & returns a TURN ICE Server based on a request URL.  Must be run
+    // off the main thread!
+    private PeerConnection.IceServer requestTurnServer(String url) {
+      try {
+        URLConnection connection = (new URL(url)).openConnection();
+        connection.addRequestProperty("user-agent", "Mozilla/5.0");
+        connection.addRequestProperty("origin", "https://apprtc.appspot.com");
+        String response = drainStream(connection.getInputStream());
+        JSONObject responseJSON = new JSONObject(response);
+        String uri = responseJSON.getJSONArray("uris").getString(0);
+        String username = responseJSON.getString("username");
+        String password = responseJSON.getString("password");
+        return new PeerConnection.IceServer(uri, username, password);
+      } catch (JSONException e) {
+        throw new RuntimeException(e);
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+    }
+  }
+
+  // Return the list of ICE servers described by a WebRTCPeerConnection
+  // configuration string.
+  private LinkedList<PeerConnection.IceServer> iceServersFromPCConfigJSON(
+      String pcConfig) {
+    try {
+      JSONObject json = new JSONObject(pcConfig);
+      JSONArray servers = json.getJSONArray("iceServers");
+      LinkedList<PeerConnection.IceServer> ret =
+          new LinkedList<PeerConnection.IceServer>();
+      for (int i = 0; i < servers.length(); ++i) {
+        JSONObject server = servers.getJSONObject(i);
+        String url = server.getString("url");
+        String credential =
+            server.has("credential") ? server.getString("credential") : "";
+        ret.add(new PeerConnection.IceServer(url, "", credential));
+      }
+      return ret;
+    } catch (JSONException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  // Request an attempt to drain the send queue, on a background thread.
+  private void requestQueueDrainInBackground() {
+    (new AsyncTask<Void, Void, Void>() {
+      public Void doInBackground(Void... unused) {
+        maybeDrainQueue();
+        return null;
+      }
+    }).execute();
+  }
+
+  // Send all queued messages if connected to the room.
+  private void maybeDrainQueue() {
+    synchronized (sendQueue) {
+      if (appRTCSignalingParameters == null) {
+        return;
+      }
+      try {
+        for (String msg : sendQueue) {
+          URLConnection connection = new URL(
+              appRTCSignalingParameters.gaeBaseHref +
+              appRTCSignalingParameters.postMessageUrl).openConnection();
+          connection.setDoOutput(true);
+          connection.getOutputStream().write(msg.getBytes("UTF-8"));
+          if (!connection.getHeaderField(null).startsWith("HTTP/1.1 200 ")) {
+            throw new IOException(
+                "Non-200 response to POST: " + connection.getHeaderField(null) +
+                " for msg: " + msg);
+          }
+        }
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+      sendQueue.clear();
+    }
+  }
+
+  // Return the contents of an InputStream as a String.
+  private static String drainStream(InputStream in) {
+    Scanner s = new Scanner(in).useDelimiter("\\A");
+    return s.hasNext() ? s.next() : "";
+  }
+}
diff --git a/talk/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java b/talk/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java
new file mode 100644
index 0000000..bd17323
--- /dev/null
+++ b/talk/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java
@@ -0,0 +1,499 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.appspot.apprtc;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.graphics.Point;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.util.Log;
+import android.webkit.JavascriptInterface;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.webrtc.IceCandidate;
+import org.webrtc.MediaConstraints;
+import org.webrtc.MediaStream;
+import org.webrtc.PeerConnection;
+import org.webrtc.PeerConnectionFactory;
+import org.webrtc.SdpObserver;
+import org.webrtc.SessionDescription;
+import org.webrtc.StatsObserver;
+import org.webrtc.StatsReport;
+import org.webrtc.VideoCapturer;
+import org.webrtc.VideoRenderer;
+import org.webrtc.VideoRenderer.I420Frame;
+import org.webrtc.VideoSource;
+import org.webrtc.VideoTrack;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Main Activity of the AppRTCDemo Android app demonstrating interoperability
+ * between the Android/Java implementation of PeerConnection and the
+ * apprtc.appspot.com demo webapp.
+ */
+public class AppRTCDemoActivity extends Activity
+    implements AppRTCClient.IceServersObserver {
+  private static final String TAG = "AppRTCDemoActivity";
+  private PeerConnection pc;
+  private final PCObserver pcObserver = new PCObserver();
+  private final SDPObserver sdpObserver = new SDPObserver();
+  private final GAEChannelClient.MessageHandler gaeHandler = new GAEHandler();
+  private AppRTCClient appRtcClient = new AppRTCClient(this, gaeHandler, this);
+  private VideoStreamsView vsv;
+  private Toast logToast;
+  private LinkedList<IceCandidate> queuedRemoteCandidates =
+      new LinkedList<IceCandidate>();
+  // Synchronize on quit[0] to avoid teardown-related crashes.
+  private final Boolean[] quit = new Boolean[] { false };
+  private MediaConstraints sdpMediaConstraints;
+  private PowerManager.WakeLock wakeLock;
+
+  @Override
+  public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+
+    // Since the error-handling of this demo consists of throwing
+    // RuntimeExceptions and we assume that'll terminate the app, we install
+    // this default handler so it's applied to background threads as well.
+    Thread.setDefaultUncaughtExceptionHandler(
+        new Thread.UncaughtExceptionHandler() {
+          public void uncaughtException(Thread t, Throwable e) {
+            e.printStackTrace();
+            System.exit(-1);
+          }
+        });
+
+    PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
+    wakeLock = powerManager.newWakeLock(
+        PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "AppRTCDemo");
+    wakeLock.acquire();
+
+    Point displaySize = new Point();
+    getWindowManager().getDefaultDisplay().getSize(displaySize);
+    vsv = new VideoStreamsView(this, displaySize);
+    setContentView(vsv);
+
+    abortUnless(PeerConnectionFactory.initializeAndroidGlobals(this),
+        "Failed to initializeAndroidGlobals");
+
+    AudioManager audioManager =
+        ((AudioManager) getSystemService(AUDIO_SERVICE));
+    audioManager.setMode(audioManager.isWiredHeadsetOn() ?
+        AudioManager.MODE_IN_CALL : AudioManager.MODE_IN_COMMUNICATION);
+    audioManager.setSpeakerphoneOn(!audioManager.isWiredHeadsetOn());
+
+    sdpMediaConstraints = new MediaConstraints();
+    sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
+        "OfferToReceiveAudio", "true"));
+    sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
+        "OfferToReceiveVideo", "true"));
+
+    final Intent intent = getIntent();
+    if ("android.intent.action.VIEW".equals(intent.getAction())) {
+      connectToRoom(intent.getData().toString());
+      return;
+    }
+    showGetRoomUI();
+  }
+
+  private void showGetRoomUI() {
+    final EditText roomInput = new EditText(this);
+    roomInput.setText("https://apprtc.appspot.com/?r=");
+    roomInput.setSelection(roomInput.getText().length());
+    DialogInterface.OnClickListener listener =
+        new DialogInterface.OnClickListener() {
+          @Override public void onClick(DialogInterface dialog, int which) {
+            abortUnless(which == DialogInterface.BUTTON_POSITIVE, "lolwat?");
+            dialog.dismiss();
+            connectToRoom(roomInput.getText().toString());
+          }
+        };
+    AlertDialog.Builder builder = new AlertDialog.Builder(this);
+    builder
+        .setMessage("Enter room URL").setView(roomInput)
+        .setPositiveButton("Go!", listener).show();
+  }
+
+  private void connectToRoom(String roomUrl) {
+    logAndToast("Connecting to room...");
+    appRtcClient.connectToRoom(roomUrl);
+  }
+
+  @Override
+  public void onPause() {
+    super.onPause();
+    vsv.onPause();
+    // TODO(fischman): IWBN to support pause/resume, but the WebRTC codebase
+    // isn't ready for that yet; e.g.
+    // https://code.google.com/p/webrtc/issues/detail?id=1407
+    // Instead, simply exit instead of pausing (the alternative leads to
+    // system-borking with wedged cameras; e.g. b/8224551)
+    disconnectAndExit();
+  }
+
+  @Override
+  public void onResume() {
+    // The onResume() is a lie!  See TODO(fischman) in onPause() above.
+    super.onResume();
+    vsv.onResume();
+  }
+
+  @Override
+  public void onIceServers(List<PeerConnection.IceServer> iceServers) {
+    PeerConnectionFactory factory = new PeerConnectionFactory();
+
+    pc = factory.createPeerConnection(
+        iceServers, appRtcClient.pcConstraints(), pcObserver);
+
+    {
+      final PeerConnection finalPC = pc;
+      final Runnable repeatedStatsLogger = new Runnable() {
+          public void run() {
+            synchronized (quit[0]) {
+              if (quit[0]) {
+                return;
+              }
+              final Runnable runnableThis = this;
+              boolean success = finalPC.getStats(new StatsObserver() {
+                  public void onComplete(StatsReport[] reports) {
+                    for (StatsReport report : reports) {
+                      Log.d(TAG, "Stats: " + report.toString());
+                    }
+                    vsv.postDelayed(runnableThis, 10000);
+                  }
+                }, null);
+              if (!success) {
+                throw new RuntimeException("getStats() return false!");
+              }
+            }
+          }
+        };
+      vsv.postDelayed(repeatedStatsLogger, 10000);
+    }
+
+    {
+      logAndToast("Creating local video source...");
+      VideoCapturer capturer = getVideoCapturer();
+      VideoSource videoSource = factory.createVideoSource(
+          capturer, appRtcClient.videoConstraints());
+      MediaStream lMS = factory.createLocalMediaStream("ARDAMS");
+      VideoTrack videoTrack = factory.createVideoTrack("ARDAMSv0", videoSource);
+      videoTrack.addRenderer(new VideoRenderer(new VideoCallbacks(
+          vsv, VideoStreamsView.Endpoint.LOCAL)));
+      lMS.addTrack(videoTrack);
+      lMS.addTrack(factory.createAudioTrack("ARDAMSa0"));
+      pc.addStream(lMS, new MediaConstraints());
+    }
+    logAndToast("Waiting for ICE candidates...");
+  }
+
+  // Cycle through likely device names for the camera and return the first
+  // capturer that works, or crash if none do.
+  private VideoCapturer getVideoCapturer() {
+    String[] cameraFacing = { "front", "back" };
+    int[] cameraIndex = { 0, 1 };
+    int[] cameraOrientation = { 0, 90, 180, 270 };
+    for (String facing : cameraFacing) {
+      for (int index : cameraIndex) {
+        for (int orientation : cameraOrientation) {
+          String name = "Camera " + index + ", Facing " + facing +
+              ", Orientation " + orientation;
+          VideoCapturer capturer = VideoCapturer.create(name);
+          if (capturer != null) {
+            logAndToast("Using camera: " + name);
+            return capturer;
+          }
+        }
+      }
+    }
+    throw new RuntimeException("Failed to open capturer");
+  }
+
+  @Override
+  public void onDestroy() {
+    super.onDestroy();
+  }
+
+  // Poor-man's assert(): die with |msg| unless |condition| is true.
+  private static void abortUnless(boolean condition, String msg) {
+    if (!condition) {
+      throw new RuntimeException(msg);
+    }
+  }
+
+  // Log |msg| and Toast about it.
+  private void logAndToast(String msg) {
+    Log.d(TAG, msg);
+    if (logToast != null) {
+      logToast.cancel();
+    }
+    logToast = Toast.makeText(this, msg, Toast.LENGTH_SHORT);
+    logToast.show();
+  }
+
+  // Send |json| to the underlying AppEngine Channel.
+  private void sendMessage(JSONObject json) {
+    appRtcClient.sendMessage(json.toString());
+  }
+
+  // Put a |key|->|value| mapping in |json|.
+  private static void jsonPut(JSONObject json, String key, Object value) {
+    try {
+      json.put(key, value);
+    } catch (JSONException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  // Implementation detail: observe ICE & stream changes and react accordingly.
+  private class PCObserver implements PeerConnection.Observer {
+    @Override public void onIceCandidate(final IceCandidate candidate){
+      runOnUiThread(new Runnable() {
+          public void run() {
+            JSONObject json = new JSONObject();
+            jsonPut(json, "type", "candidate");
+            jsonPut(json, "label", candidate.sdpMLineIndex);
+            jsonPut(json, "id", candidate.sdpMid);
+            jsonPut(json, "candidate", candidate.sdp);
+            sendMessage(json);
+          }
+        });
+    }
+
+    @Override public void onError(){
+      runOnUiThread(new Runnable() {
+          public void run() {
+            throw new RuntimeException("PeerConnection error!");
+          }
+        });
+    }
+
+    @Override public void onSignalingChange(
+        PeerConnection.SignalingState newState) {
+    }
+
+    @Override public void onIceConnectionChange(
+        PeerConnection.IceConnectionState newState) {
+    }
+
+    @Override public void onIceGatheringChange(
+        PeerConnection.IceGatheringState newState) {
+    }
+
+    @Override public void onAddStream(final MediaStream stream){
+      runOnUiThread(new Runnable() {
+          public void run() {
+            abortUnless(stream.audioTracks.size() == 1 &&
+                stream.videoTracks.size() == 1,
+                "Weird-looking stream: " + stream);
+            stream.videoTracks.get(0).addRenderer(new VideoRenderer(
+                new VideoCallbacks(vsv, VideoStreamsView.Endpoint.REMOTE)));
+          }
+        });
+    }
+
+    @Override public void onRemoveStream(final MediaStream stream){
+      runOnUiThread(new Runnable() {
+          public void run() {
+            stream.videoTracks.get(0).dispose();
+          }
+        });
+    }
+  }
+
+  // Implementation detail: handle offer creation/signaling and answer setting,
+  // as well as adding remote ICE candidates once the answer SDP is set.
+  private class SDPObserver implements SdpObserver {
+    @Override public void onCreateSuccess(final SessionDescription sdp) {
+      runOnUiThread(new Runnable() {
+          public void run() {
+            logAndToast("Sending " + sdp.type);
+            JSONObject json = new JSONObject();
+            jsonPut(json, "type", sdp.type.canonicalForm());
+            jsonPut(json, "sdp", sdp.description);
+            sendMessage(json);
+            pc.setLocalDescription(sdpObserver, sdp);
+          }
+        });
+    }
+
+    @Override public void onSetSuccess() {
+      runOnUiThread(new Runnable() {
+          public void run() {
+            if (appRtcClient.isInitiator()) {
+              if (pc.getRemoteDescription() != null) {
+                // We've set our local offer and received & set the remote
+                // answer, so drain candidates.
+                drainRemoteCandidates();
+              }
+            } else {
+              if (pc.getLocalDescription() == null) {
+                // We just set the remote offer, time to create our answer.
+                logAndToast("Creating answer");
+                pc.createAnswer(SDPObserver.this, sdpMediaConstraints);
+              } else {
+                // Sent our answer and set it as local description; drain
+                // candidates.
+                drainRemoteCandidates();
+              }
+            }
+          }
+        });
+    }
+
+    @Override public void onCreateFailure(final String error) {
+      runOnUiThread(new Runnable() {
+          public void run() {
+            throw new RuntimeException("createSDP error: " + error);
+          }
+        });
+    }
+
+    @Override public void onSetFailure(final String error) {
+      runOnUiThread(new Runnable() {
+          public void run() {
+            throw new RuntimeException("setSDP error: " + error);
+          }
+        });
+    }
+
+    private void drainRemoteCandidates() {
+      for (IceCandidate candidate : queuedRemoteCandidates) {
+        pc.addIceCandidate(candidate);
+      }
+      queuedRemoteCandidates = null;
+    }
+  }
+
+  // Implementation detail: handler for receiving GAE messages and dispatching
+  // them appropriately.
+  private class GAEHandler implements GAEChannelClient.MessageHandler {
+    @JavascriptInterface public void onOpen() {
+      if (!appRtcClient.isInitiator()) {
+        return;
+      }
+      logAndToast("Creating offer...");
+      pc.createOffer(sdpObserver, sdpMediaConstraints);
+    }
+
+    @JavascriptInterface public void onMessage(String data) {
+      try {
+        JSONObject json = new JSONObject(data);
+        String type = (String) json.get("type");
+        if (type.equals("candidate")) {
+          IceCandidate candidate = new IceCandidate(
+              (String) json.get("id"),
+              json.getInt("label"),
+              (String) json.get("candidate"));
+          if (queuedRemoteCandidates != null) {
+            queuedRemoteCandidates.add(candidate);
+          } else {
+            pc.addIceCandidate(candidate);
+          }
+        } else if (type.equals("answer") || type.equals("offer")) {
+          SessionDescription sdp = new SessionDescription(
+              SessionDescription.Type.fromCanonicalForm(type),
+              (String) json.get("sdp"));
+          pc.setRemoteDescription(sdpObserver, sdp);
+        } else if (type.equals("bye")) {
+          logAndToast("Remote end hung up; dropping PeerConnection");
+          disconnectAndExit();
+        } else {
+          throw new RuntimeException("Unexpected message: " + data);
+        }
+      } catch (JSONException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    @JavascriptInterface public void onClose() {
+      disconnectAndExit();
+    }
+
+    @JavascriptInterface public void onError(int code, String description) {
+      disconnectAndExit();
+    }
+  }
+
+  // Disconnect from remote resources, dispose of local resources, and exit.
+  private void disconnectAndExit() {
+    synchronized (quit[0]) {
+      if (quit[0]) {
+        return;
+      }
+      quit[0] = true;
+      wakeLock.release();
+      if (pc != null) {
+        pc.dispose();
+        pc = null;
+      }
+      if (appRtcClient != null) {
+        appRtcClient.sendMessage("{\"type\": \"bye\"}");
+        appRtcClient.disconnect();
+        appRtcClient = null;
+      }
+      finish();
+    }
+  }
+
+  // Implementation detail: bridge the VideoRenderer.Callbacks interface to the
+  // VideoStreamsView implementation.
+  private class VideoCallbacks implements VideoRenderer.Callbacks {
+    private final VideoStreamsView view;
+    private final VideoStreamsView.Endpoint stream;
+
+    public VideoCallbacks(
+        VideoStreamsView view, VideoStreamsView.Endpoint stream) {
+      this.view = view;
+      this.stream = stream;
+    }
+
+    @Override
+    public void setSize(final int width, final int height) {
+      view.queueEvent(new Runnable() {
+          public void run() {
+            view.setSize(stream, width, height);
+          }
+        });
+    }
+
+    @Override
+    public void renderFrame(I420Frame frame) {
+      view.queueFrame(stream, frame);
+    }
+  }
+}
diff --git a/talk/examples/android/src/org/appspot/apprtc/FramePool.java b/talk/examples/android/src/org/appspot/apprtc/FramePool.java
new file mode 100644
index 0000000..6f11286
--- /dev/null
+++ b/talk/examples/android/src/org/appspot/apprtc/FramePool.java
@@ -0,0 +1,104 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.appspot.apprtc;
+
+import org.webrtc.VideoRenderer.I420Frame;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+
+/**
+ * This class acts as an allocation pool meant to minimize GC churn caused by
+ * frame allocation & disposal.  The public API comprises of just two methods:
+ * copyFrame(), which allocates as necessary and copies, and
+ * returnFrame(), which returns frame ownership to the pool for use by a later
+ * call to copyFrame().
+ *
+ * This class is thread-safe; calls to copyFrame() and returnFrame() are allowed
+ * to happen on any thread.
+ */
+class FramePool {
+  // Maps each summary code (see summarizeFrameDimensions()) to a list of frames
+  // of that description.
+  private final HashMap<Long, LinkedList<I420Frame>> availableFrames =
+      new HashMap<Long, LinkedList<I420Frame>>();
+  // Every dimension (e.g. width, height, stride) of a frame must be less than
+  // this value.
+  private static final long MAX_DIMENSION = 4096;
+
+  public I420Frame takeFrame(I420Frame source) {
+    long desc = summarizeFrameDimensions(source);
+    I420Frame dst = null;
+    synchronized (availableFrames) {
+      LinkedList<I420Frame> frames = availableFrames.get(desc);
+      if (frames == null) {
+        frames = new LinkedList<I420Frame>();
+        availableFrames.put(desc, frames);
+      }
+      if (!frames.isEmpty()) {
+        dst = frames.pop();
+      } else {
+        dst = new I420Frame(
+            source.width, source.height, source.yuvStrides, null);
+      }
+    }
+    return dst;
+  }
+
+  public void returnFrame(I420Frame frame) {
+    long desc = summarizeFrameDimensions(frame);
+    synchronized (availableFrames) {
+      LinkedList<I420Frame> frames = availableFrames.get(desc);
+      if (frames == null) {
+        throw new IllegalArgumentException("Unexpected frame dimensions");
+      }
+      frames.add(frame);
+    }
+  }
+
+  /** Validate that |frame| can be managed by the pool. */
+  public static boolean validateDimensions(I420Frame frame) {
+    return frame.width < MAX_DIMENSION && frame.height < MAX_DIMENSION &&
+        frame.yuvStrides[0] < MAX_DIMENSION &&
+        frame.yuvStrides[1] < MAX_DIMENSION &&
+        frame.yuvStrides[2] < MAX_DIMENSION;
+  }
+
+  // Return a code summarizing the dimensions of |frame|.  Two frames that
+  // return the same summary are guaranteed to be able to store each others'
+  // contents.  Used like Object.hashCode(), but we need all the bits of a long
+  // to do a good job, and hashCode() returns int, so we do this.
+  private static long summarizeFrameDimensions(I420Frame frame) {
+    long ret = frame.width;
+    ret = ret * MAX_DIMENSION + frame.height;
+    ret = ret * MAX_DIMENSION + frame.yuvStrides[0];
+    ret = ret * MAX_DIMENSION + frame.yuvStrides[1];
+    ret = ret * MAX_DIMENSION + frame.yuvStrides[2];
+    return ret;
+  }
+}
diff --git a/talk/examples/android/src/org/appspot/apprtc/GAEChannelClient.java b/talk/examples/android/src/org/appspot/apprtc/GAEChannelClient.java
new file mode 100644
index 0000000..46f638d
--- /dev/null
+++ b/talk/examples/android/src/org/appspot/apprtc/GAEChannelClient.java
@@ -0,0 +1,164 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.appspot.apprtc;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.util.Log;
+import android.webkit.ConsoleMessage;
+import android.webkit.JavascriptInterface;
+import android.webkit.WebChromeClient;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+/**
+ * Java-land version of Google AppEngine's JavaScript Channel API:
+ * https://developers.google.com/appengine/docs/python/channel/javascript
+ *
+ * Requires a hosted HTML page that opens the desired channel and dispatches JS
+ * on{Open,Message,Close,Error}() events to a global object named
+ * "androidMessageHandler".
+ */
+public class GAEChannelClient {
+  private static final String TAG = "GAEChannelClient";
+  private WebView webView;
+  private final ProxyingMessageHandler proxyingMessageHandler;
+
+  /**
+   * Callback interface for messages delivered on the Google AppEngine channel.
+   *
+   * Methods are guaranteed to be invoked on the UI thread of |activity| passed
+   * to GAEChannelClient's constructor.
+   */
+  public interface MessageHandler {
+    public void onOpen();
+    public void onMessage(String data);
+    public void onClose();
+    public void onError(int code, String description);
+  }
+
+  /** Asynchronously open an AppEngine channel. */
+  @SuppressLint("SetJavaScriptEnabled")
+  public GAEChannelClient(
+      Activity activity, String token, MessageHandler handler) {
+    webView = new WebView(activity);
+    webView.getSettings().setJavaScriptEnabled(true);
+    webView.setWebChromeClient(new WebChromeClient() {  // Purely for debugging.
+        public boolean onConsoleMessage (ConsoleMessage msg) {
+          Log.d(TAG, "console: " + msg.message() + " at " +
+              msg.sourceId() + ":" + msg.lineNumber());
+          return false;
+        }
+      });
+    webView.setWebViewClient(new WebViewClient() {  // Purely for debugging.
+        public void onReceivedError(
+            WebView view, int errorCode, String description,
+            String failingUrl) {
+          Log.e(TAG, "JS error: " + errorCode + " in " + failingUrl +
+              ", desc: " + description);
+        }
+      });
+    proxyingMessageHandler = new ProxyingMessageHandler(activity, handler);
+    webView.addJavascriptInterface(
+        proxyingMessageHandler, "androidMessageHandler");
+    webView.loadUrl("file:///android_asset/channel.html?token=" + token);
+  }
+
+  /** Close the connection to the AppEngine channel. */
+  public void close() {
+    if (webView == null) {
+      return;
+    }
+    proxyingMessageHandler.disconnect();
+    webView.removeJavascriptInterface("androidMessageHandler");
+    webView.loadUrl("about:blank");
+    webView = null;
+  }
+
+  // Helper class for proxying callbacks from the Java<->JS interaction
+  // (private, background) thread to the Activity's UI thread.
+  private static class ProxyingMessageHandler {
+    private final Activity activity;
+    private final MessageHandler handler;
+    private final boolean[] disconnected = { false };
+
+    public ProxyingMessageHandler(Activity activity, MessageHandler handler) {
+      this.activity = activity;
+      this.handler = handler;
+    }
+
+    public void disconnect() {
+      disconnected[0] = true;
+    }
+
+    private boolean disconnected() {
+      return disconnected[0];
+    }
+
+    @JavascriptInterface public void onOpen() {
+      activity.runOnUiThread(new Runnable() {
+          public void run() {
+            if (!disconnected()) {
+              handler.onOpen();
+            }
+          }
+        });
+    }
+
+    @JavascriptInterface public void onMessage(final String data) {
+      activity.runOnUiThread(new Runnable() {
+          public void run() {
+            if (!disconnected()) {
+              handler.onMessage(data);
+            }
+          }
+        });
+    }
+
+    @JavascriptInterface public void onClose() {
+      activity.runOnUiThread(new Runnable() {
+          public void run() {
+            if (!disconnected()) {
+              handler.onClose();
+            }
+          }
+        });
+    }
+
+    @JavascriptInterface public void onError(
+        final int code, final String description) {
+      activity.runOnUiThread(new Runnable() {
+          public void run() {
+            if (!disconnected()) {
+              handler.onError(code, description);
+            }
+          }
+        });
+    }
+  }
+}
diff --git a/talk/examples/android/src/org/appspot/apprtc/VideoStreamsView.java b/talk/examples/android/src/org/appspot/apprtc/VideoStreamsView.java
new file mode 100644
index 0000000..12217d6
--- /dev/null
+++ b/talk/examples/android/src/org/appspot/apprtc/VideoStreamsView.java
@@ -0,0 +1,295 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.appspot.apprtc;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.opengl.GLES20;
+import android.opengl.GLSurfaceView;
+import android.util.Log;
+
+import org.webrtc.VideoRenderer.I420Frame;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.util.EnumMap;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * A GLSurfaceView{,.Renderer} that efficiently renders YUV frames from local &
+ * remote VideoTracks using the GPU for CSC.  Clients will want to call the
+ * constructor, setSize() and updateFrame() as appropriate, but none of the
+ * other public methods of this class are of interest to clients (only to system
+ * classes).
+ */
+public class VideoStreamsView
+    extends GLSurfaceView
+    implements GLSurfaceView.Renderer {
+
+  /** Identify which of the two video streams is being addressed. */
+  public static enum Endpoint { LOCAL, REMOTE };
+
+  private final static String TAG = "VideoStreamsView";
+  private EnumMap<Endpoint, Rect> rects =
+      new EnumMap<Endpoint, Rect>(Endpoint.class);
+  private Point screenDimensions;
+  // [0] are local Y,U,V, [1] are remote Y,U,V.
+  private int[][] yuvTextures = { { -1, -1, -1}, {-1, -1, -1 }};
+  private int posLocation = -1;
+  private long lastFPSLogTime = System.nanoTime();
+  private long numFramesSinceLastLog = 0;
+  private FramePool framePool = new FramePool();
+
+  public VideoStreamsView(Context c, Point screenDimensions) {
+    super(c);
+    this.screenDimensions = screenDimensions;
+    setEGLContextClientVersion(2);
+    setRenderer(this);
+    setRenderMode(RENDERMODE_WHEN_DIRTY);
+  }
+
+  /** Queue |frame| to be uploaded. */
+  public void queueFrame(final Endpoint stream, I420Frame frame) {
+    // Paying for the copy of the YUV data here allows CSC and painting time
+    // to get spent on the render thread instead of the UI thread.
+    abortUnless(framePool.validateDimensions(frame), "Frame too large!");
+    final I420Frame frameCopy = framePool.takeFrame(frame).copyFrom(frame);
+    queueEvent(new Runnable() {
+        public void run() {
+          updateFrame(stream, frameCopy);
+        }
+      });
+  }
+
+  // Upload the planes from |frame| to the textures owned by this View.
+  private void updateFrame(Endpoint stream, I420Frame frame) {
+    int[] textures = yuvTextures[stream == Endpoint.LOCAL ? 0 : 1];
+    texImage2D(frame, textures);
+    framePool.returnFrame(frame);
+    requestRender();
+  }
+
+  /** Inform this View of the dimensions of frames coming from |stream|. */
+  public void setSize(Endpoint stream, int width, int height) {
+    // Generate 3 texture ids for Y/U/V and place them into |textures|,
+    // allocating enough storage for |width|x|height| pixels.
+    int[] textures = yuvTextures[stream == Endpoint.LOCAL ? 0 : 1];
+    GLES20.glGenTextures(3, textures, 0);
+    for (int i = 0; i < 3; ++i) {
+      int w = i == 0 ? width : width / 2;
+      int h = i == 0 ? height : height / 2;
+      GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i);
+      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[i]);
+      GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, w, h, 0,
+          GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, null);
+      GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
+          GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
+      GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
+          GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
+      GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
+          GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
+      GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
+          GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
+    }
+    checkNoGLES2Error();
+  }
+
+  @Override
+  protected void onMeasure(int unusedX, int unusedY) {
+    // Go big or go home!
+    setMeasuredDimension(screenDimensions.x, screenDimensions.y);
+  }
+
+  @Override
+  public void onSurfaceChanged(GL10 unused, int width, int height) {
+    GLES20.glViewport(0, 0, width, height);
+    checkNoGLES2Error();
+  }
+
+  @Override
+  public void onDrawFrame(GL10 unused) {
+    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+    drawRectangle(yuvTextures[1], remoteVertices);
+    drawRectangle(yuvTextures[0], localVertices);
+    ++numFramesSinceLastLog;
+    long now = System.nanoTime();
+    if (lastFPSLogTime == -1 || now - lastFPSLogTime > 1e9) {
+      double fps = numFramesSinceLastLog / ((now - lastFPSLogTime) / 1e9);
+      Log.d(TAG, "Rendered FPS: " + fps);
+      lastFPSLogTime = now;
+      numFramesSinceLastLog = 1;
+    }
+    checkNoGLES2Error();
+  }
+
+  @Override
+  public void onSurfaceCreated(GL10 unused, EGLConfig config) {
+    int program = GLES20.glCreateProgram();
+    addShaderTo(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_STRING, program);
+    addShaderTo(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_STRING, program);
+
+    GLES20.glLinkProgram(program);
+    int[] result = new int[] { GLES20.GL_FALSE };
+    result[0] = GLES20.GL_FALSE;
+    GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, result, 0);
+    abortUnless(result[0] == GLES20.GL_TRUE,
+        GLES20.glGetProgramInfoLog(program));
+    GLES20.glUseProgram(program);
+
+    GLES20.glUniform1i(GLES20.glGetUniformLocation(program, "y_tex"), 0);
+    GLES20.glUniform1i(GLES20.glGetUniformLocation(program, "u_tex"), 1);
+    GLES20.glUniform1i(GLES20.glGetUniformLocation(program, "v_tex"), 2);
+
+    // Actually set in drawRectangle(), but queried only once here.
+    posLocation = GLES20.glGetAttribLocation(program, "in_pos");
+
+    int tcLocation = GLES20.glGetAttribLocation(program, "in_tc");
+    GLES20.glEnableVertexAttribArray(tcLocation);
+    GLES20.glVertexAttribPointer(
+        tcLocation, 2, GLES20.GL_FLOAT, false, 0, textureCoords);
+
+    GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+    checkNoGLES2Error();
+  }
+
+  // Wrap a float[] in a direct FloatBuffer using native byte order.
+  private static FloatBuffer directNativeFloatBuffer(float[] array) {
+    FloatBuffer buffer = ByteBuffer.allocateDirect(array.length * 4).order(
+        ByteOrder.nativeOrder()).asFloatBuffer();
+    buffer.put(array);
+    buffer.flip();
+    return buffer;
+  }
+
+  // Upload the YUV planes from |frame| to |textures|.
+  private void texImage2D(I420Frame frame, int[] textures) {
+    for (int i = 0; i < 3; ++i) {
+      ByteBuffer plane = frame.yuvPlanes[i];
+      GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i);
+      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[i]);
+      int w = i == 0 ? frame.width : frame.width / 2;
+      int h = i == 0 ? frame.height : frame.height / 2;
+      abortUnless(w == frame.yuvStrides[i], frame.yuvStrides[i] + "!=" + w);
+      GLES20.glTexImage2D(
+          GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, w, h, 0,
+          GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, plane);
+    }
+    checkNoGLES2Error();
+  }
+
+  // Draw |textures| using |vertices| (X,Y coordinates).
+  private void drawRectangle(int[] textures, FloatBuffer vertices) {
+    for (int i = 0; i < 3; ++i) {
+      GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i);
+      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[i]);
+    }
+
+    GLES20.glVertexAttribPointer(
+        posLocation, 2, GLES20.GL_FLOAT, false, 0, vertices);
+    GLES20.glEnableVertexAttribArray(posLocation);
+
+    GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+    checkNoGLES2Error();
+  }
+
+  // Compile & attach a |type| shader specified by |source| to |program|.
+  private static void addShaderTo(
+      int type, String source, int program) {
+    int[] result = new int[] { GLES20.GL_FALSE };
+    int shader = GLES20.glCreateShader(type);
+    GLES20.glShaderSource(shader, source);
+    GLES20.glCompileShader(shader);
+    GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, result, 0);
+    abortUnless(result[0] == GLES20.GL_TRUE,
+        GLES20.glGetShaderInfoLog(shader) + ", source: " + source);
+    GLES20.glAttachShader(program, shader);
+    GLES20.glDeleteShader(shader);
+    checkNoGLES2Error();
+  }
+
+  // Poor-man's assert(): die with |msg| unless |condition| is true.
+  private static void abortUnless(boolean condition, String msg) {
+    if (!condition) {
+      throw new RuntimeException(msg);
+    }
+  }
+
+  // Assert that no OpenGL ES 2.0 error has been raised.
+  private static void checkNoGLES2Error() {
+    int error = GLES20.glGetError();
+    abortUnless(error == GLES20.GL_NO_ERROR, "GLES20 error: " + error);
+  }
+
+  // Remote image should span the full screen.
+  private static final FloatBuffer remoteVertices = directNativeFloatBuffer(
+      new float[] { -1, 1, -1, -1, 1, 1, 1, -1 });
+
+  // Local image should be thumbnailish.
+  private static final FloatBuffer localVertices = directNativeFloatBuffer(
+      new float[] { 0.6f, 0.9f, 0.6f, 0.6f, 0.9f, 0.9f, 0.9f, 0.6f });
+
+  // Texture Coordinates mapping the entire texture.
+  private static final FloatBuffer textureCoords = directNativeFloatBuffer(
+      new float[] { 0, 0, 0, 1, 1, 0, 1, 1 });
+
+  // Pass-through vertex shader.
+  private static final String VERTEX_SHADER_STRING =
+      "varying vec2 interp_tc;\n" +
+      "\n" +
+      "attribute vec4 in_pos;\n" +
+      "attribute vec2 in_tc;\n" +
+      "\n" +
+      "void main() {\n" +
+      "  gl_Position = in_pos;\n" +
+      "  interp_tc = in_tc;\n" +
+      "}\n";
+
+  // YUV to RGB pixel shader. Loads a pixel from each plane and pass through the
+  // matrix.
+  private static final String FRAGMENT_SHADER_STRING =
+      "precision mediump float;\n" +
+      "varying vec2 interp_tc;\n" +
+      "\n" +
+      "uniform sampler2D y_tex;\n" +
+      "uniform sampler2D u_tex;\n" +
+      "uniform sampler2D v_tex;\n" +
+      "\n" +
+      "void main() {\n" +
+      "  float y = texture2D(y_tex, interp_tc).r;\n" +
+      "  float u = texture2D(u_tex, interp_tc).r - .5;\n" +
+      "  float v = texture2D(v_tex, interp_tc).r - .5;\n" +
+      // CSC according to http://www.fourcc.org/fccyvrgb.php
+      "  gl_FragColor = vec4(y + 1.403 * v, " +
+      "                      y - 0.344 * u - 0.714 * v, " +
+      "                      y + 1.77 * u, 1);\n" +
+      "}\n";
+}
diff --git a/talk/examples/call/Info.plist b/talk/examples/call/Info.plist
new file mode 100644
index 0000000..a59cfa5
--- /dev/null
+++ b/talk/examples/call/Info.plist
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>CFBundleIdentifier</key>
+  <string>com.google.call</string>
+  <key>CFBundleName</key>
+  <string>call</string>
+</dict>
+</plist>
+
diff --git a/talk/examples/call/call_main.cc b/talk/examples/call/call_main.cc
new file mode 100644
index 0000000..2ee796b
--- /dev/null
+++ b/talk/examples/call/call_main.cc
@@ -0,0 +1,499 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <cstdio>
+#include <cstring>
+#include <time.h>
+#include <iomanip>
+#include <iostream>
+#include <vector>
+
+#include "talk/base/flags.h"
+#include "talk/base/logging.h"
+#ifdef OSX
+#include "talk/base/maccocoasocketserver.h"
+#endif
+#include "talk/base/pathutils.h"
+#include "talk/base/ssladapter.h"
+#include "talk/base/stream.h"
+#include "talk/base/win32socketserver.h"
+#include "talk/examples/call/callclient.h"
+#include "talk/examples/call/console.h"
+#include "talk/examples/call/mediaenginefactory.h"
+#include "talk/p2p/base/constants.h"
+#ifdef ANDROID
+#include "talk/media/other/androidmediaengine.h"
+#endif
+#include "talk/session/media/mediasessionclient.h"
+#include "talk/session/media/srtpfilter.h"
+#include "talk/xmpp/xmppauth.h"
+#include "talk/xmpp/xmppclientsettings.h"
+#include "talk/xmpp/xmpppump.h"
+#include "talk/xmpp/xmppsocket.h"
+
+class DebugLog : public sigslot::has_slots<> {
+ public:
+  DebugLog() :
+    debug_input_buf_(NULL), debug_input_len_(0), debug_input_alloc_(0),
+    debug_output_buf_(NULL), debug_output_len_(0), debug_output_alloc_(0),
+    censor_password_(false)
+      {}
+  char * debug_input_buf_;
+  int debug_input_len_;
+  int debug_input_alloc_;
+  char * debug_output_buf_;
+  int debug_output_len_;
+  int debug_output_alloc_;
+  bool censor_password_;
+
+  void Input(const char * data, int len) {
+    if (debug_input_len_ + len > debug_input_alloc_) {
+      char * old_buf = debug_input_buf_;
+      debug_input_alloc_ = 4096;
+      while (debug_input_alloc_ < debug_input_len_ + len) {
+        debug_input_alloc_ *= 2;
+      }
+      debug_input_buf_ = new char[debug_input_alloc_];
+      memcpy(debug_input_buf_, old_buf, debug_input_len_);
+      delete[] old_buf;
+    }
+    memcpy(debug_input_buf_ + debug_input_len_, data, len);
+    debug_input_len_ += len;
+    DebugPrint(debug_input_buf_, &debug_input_len_, false);
+  }
+
+  void Output(const char * data, int len) {
+    if (debug_output_len_ + len > debug_output_alloc_) {
+      char * old_buf = debug_output_buf_;
+      debug_output_alloc_ = 4096;
+      while (debug_output_alloc_ < debug_output_len_ + len) {
+        debug_output_alloc_ *= 2;
+      }
+      debug_output_buf_ = new char[debug_output_alloc_];
+      memcpy(debug_output_buf_, old_buf, debug_output_len_);
+      delete[] old_buf;
+    }
+    memcpy(debug_output_buf_ + debug_output_len_, data, len);
+    debug_output_len_ += len;
+    DebugPrint(debug_output_buf_, &debug_output_len_, true);
+  }
+
+  static bool IsAuthTag(const char * str, size_t len) {
+    if (str[0] == '<' && str[1] == 'a' &&
+                         str[2] == 'u' &&
+                         str[3] == 't' &&
+                         str[4] == 'h' &&
+                         str[5] <= ' ') {
+      std::string tag(str, len);
+
+      if (tag.find("mechanism") != std::string::npos)
+        return true;
+    }
+    return false;
+  }
+
+  void DebugPrint(char * buf, int * plen, bool output) {
+    int len = *plen;
+    if (len > 0) {
+      time_t tim = time(NULL);
+      struct tm * now = localtime(&tim);
+      char *time_string = asctime(now);
+      if (time_string) {
+        size_t time_len = strlen(time_string);
+        if (time_len > 0) {
+          time_string[time_len-1] = 0;    // trim off terminating \n
+        }
+      }
+      LOG(INFO) << (output ? "SEND >>>>>>>>>>>>>>>>" : "RECV <<<<<<<<<<<<<<<<")
+                << " : " << time_string;
+
+      bool indent;
+      int start = 0, nest = 3;
+      for (int i = 0; i < len; i += 1) {
+        if (buf[i] == '>') {
+          if ((i > 0) && (buf[i-1] == '/')) {
+            indent = false;
+          } else if ((start + 1 < len) && (buf[start + 1] == '/')) {
+            indent = false;
+            nest -= 2;
+          } else {
+            indent = true;
+          }
+
+          // Output a tag
+          LOG(INFO) << std::setw(nest) << " "
+                    << std::string(buf + start, i + 1 - start);
+
+          if (indent)
+            nest += 2;
+
+          // Note if it's a PLAIN auth tag
+          if (IsAuthTag(buf + start, i + 1 - start)) {
+            censor_password_ = true;
+          }
+
+          // incr
+          start = i + 1;
+        }
+
+        if (buf[i] == '<' && start < i) {
+          if (censor_password_) {
+            LOG(INFO) << std::setw(nest) << " " << "## TEXT REMOVED ##";
+            censor_password_ = false;
+          } else {
+            LOG(INFO) << std::setw(nest) << " "
+                      << std::string(buf + start, i - start);
+          }
+          start = i;
+        }
+      }
+      len = len - start;
+      memcpy(buf, buf + start, len);
+      *plen = len;
+    }
+  }
+};
+
+static DebugLog debug_log_;
+static const int DEFAULT_PORT = 5222;
+
+#ifdef ANDROID
+static std::vector<cricket::AudioCodec> codecs;
+static const cricket::AudioCodec ISAC(103, "ISAC", 40000, 16000, 1, 0);
+
+cricket::MediaEngine *AndroidMediaEngineFactory() {
+    cricket::FakeMediaEngine *engine = new cricket::FakeMediaEngine();
+
+    codecs.push_back(ISAC);
+    engine->SetAudioCodecs(codecs);
+    return engine;
+}
+#endif
+
+// TODO: Move this into Console.
+void Print(const char* chars) {
+  printf("%s", chars);
+  fflush(stdout);
+}
+
+bool GetSecurePolicy(const std::string& in, cricket::SecurePolicy* out) {
+  if (in == "disable") {
+    *out = cricket::SEC_DISABLED;
+  } else if (in == "enable") {
+    *out = cricket::SEC_ENABLED;
+  } else if (in == "require") {
+    *out = cricket::SEC_REQUIRED;
+  } else {
+    return false;
+  }
+  return true;
+}
+
+int main(int argc, char **argv) {
+  // This app has three threads. The main thread will run the XMPP client,
+  // which will print to the screen in its own thread. A second thread
+  // will get input from the console, parse it, and pass the appropriate
+  // message back to the XMPP client's thread. A third thread is used
+  // by MediaSessionClient as its worker thread.
+
+  // define options
+  DEFINE_string(s, "talk.google.com", "The connection server to use.");
+  DEFINE_string(tls, "require",
+      "Select connection encryption: disable, enable, require.");
+  DEFINE_bool(allowplain, false, "Allow plain authentication.");
+  DEFINE_bool(testserver, false, "Use test server.");
+  DEFINE_string(oauth, "", "OAuth2 access token.");
+  DEFINE_bool(a, false, "Turn on auto accept for incoming calls.");
+  DEFINE_string(signaling, "hybrid",
+      "Initial signaling protocol to use: jingle, gingle, or hybrid.");
+  DEFINE_string(transport, "hybrid",
+      "Initial transport protocol to use: ice, gice, or hybrid.");
+  DEFINE_string(sdes, "enable",
+      "Select SDES media encryption: disable, enable, require.");
+  DEFINE_string(dtls, "disable",
+      "Select DTLS transport encryption: disable, enable, require.");
+  DEFINE_int(portallocator, 0, "Filter out unwanted connection types.");
+  DEFINE_string(pmuc, "groupchat.google.com", "The persistant muc domain.");
+  DEFINE_string(capsnode, "http://code.google.com/p/libjingle/call",
+                "Caps node: A URI identifying the app.");
+  DEFINE_string(capsver, "0.6",
+                "Caps ver: A string identifying the version of the app.");
+  DEFINE_string(voiceinput, NULL, "RTP dump file for voice input.");
+  DEFINE_string(voiceoutput, NULL, "RTP dump file for voice output.");
+  DEFINE_string(videoinput, NULL, "RTP dump file for video input.");
+  DEFINE_string(videooutput, NULL, "RTP dump file for video output.");
+  DEFINE_bool(render, true, "Renders the video.");
+  DEFINE_string(datachannel, "",
+                "Enable a data channel, and choose the type: rtp or sctp.");
+  DEFINE_bool(d, false, "Turn on debugging.");
+  DEFINE_string(log, "", "Turn on debugging to a file.");
+  DEFINE_bool(debugsrtp, false, "Enable debugging for srtp.");
+  DEFINE_bool(help, false, "Prints this message");
+  DEFINE_bool(multisession, false,
+              "Enable support for multiple sessions in calls.");
+  DEFINE_bool(roster, false,
+      "Enable roster messages printed in console.");
+
+  // parse options
+  FlagList::SetFlagsFromCommandLine(&argc, argv, true);
+  if (FLAG_help) {
+    FlagList::Print(NULL, false);
+    return 0;
+  }
+
+  bool auto_accept = FLAG_a;
+  bool debug = FLAG_d;
+  std::string log = FLAG_log;
+  std::string signaling = FLAG_signaling;
+  std::string transport = FLAG_transport;
+  bool test_server = FLAG_testserver;
+  bool allow_plain = FLAG_allowplain;
+  std::string tls = FLAG_tls;
+  std::string oauth_token = FLAG_oauth;
+  int32 portallocator_flags = FLAG_portallocator;
+  std::string pmuc_domain = FLAG_pmuc;
+  std::string server = FLAG_s;
+  std::string sdes = FLAG_sdes;
+  std::string dtls = FLAG_dtls;
+  std::string caps_node = FLAG_capsnode;
+  std::string caps_ver = FLAG_capsver;
+  bool debugsrtp = FLAG_debugsrtp;
+  bool render = FLAG_render;
+  std::string data_channel = FLAG_datachannel;
+  bool multisession_enabled = FLAG_multisession;
+  talk_base::SSLIdentity* ssl_identity = NULL;
+  bool show_roster_messages = FLAG_roster;
+
+  // Set up debugging.
+  if (debug) {
+    talk_base::LogMessage::LogToDebug(talk_base::LS_VERBOSE);
+  }
+
+  if (!log.empty()) {
+    talk_base::StreamInterface* stream =
+        talk_base::Filesystem::OpenFile(log, "a");
+    if (stream) {
+      talk_base::LogMessage::LogToStream(stream, talk_base::LS_VERBOSE);
+    } else {
+      Print(("Cannot open debug log " + log + "\n").c_str());
+      return 1;
+    }
+  }
+
+  if (debugsrtp) {
+    cricket::EnableSrtpDebugging();
+  }
+
+  // Set up the crypto subsystem.
+  talk_base::InitializeSSL();
+
+  // Parse username and password, if present.
+  buzz::Jid jid;
+  std::string username;
+  talk_base::InsecureCryptStringImpl pass;
+  if (argc > 1) {
+    username = argv[1];
+    if (argc > 2) {
+      pass.password() = argv[2];
+    }
+  }
+
+  if (username.empty()) {
+    Print("JID: ");
+    std::cin >> username;
+  }
+  if (username.find('@') == std::string::npos) {
+    username.append("@localhost");
+  }
+  jid = buzz::Jid(username);
+  if (!jid.IsValid() || jid.node() == "") {
+    Print("Invalid JID. JIDs should be in the form user@domain\n");
+    return 1;
+  }
+  if (pass.password().empty() && !test_server && oauth_token.empty()) {
+    Console::SetEcho(false);
+    Print("Password: ");
+    std::cin >> pass.password();
+    Console::SetEcho(true);
+    Print("\n");
+  }
+
+  // Decide on the connection settings.
+  buzz::XmppClientSettings xcs;
+  xcs.set_user(jid.node());
+  xcs.set_resource("call");
+  xcs.set_host(jid.domain());
+  xcs.set_allow_plain(allow_plain);
+
+  if (tls == "disable") {
+    xcs.set_use_tls(buzz::TLS_DISABLED);
+  } else if (tls == "enable") {
+    xcs.set_use_tls(buzz::TLS_ENABLED);
+  } else if (tls == "require") {
+    xcs.set_use_tls(buzz::TLS_REQUIRED);
+  } else {
+    Print("Invalid TLS option, must be enable, disable, or require.\n");
+    return 1;
+  }
+
+  if (test_server) {
+    pass.password() = jid.node();
+    xcs.set_allow_plain(true);
+    xcs.set_use_tls(buzz::TLS_DISABLED);
+    xcs.set_test_server_domain("google.com");
+  }
+  xcs.set_pass(talk_base::CryptString(pass));
+  if (!oauth_token.empty()) {
+    xcs.set_auth_token(buzz::AUTH_MECHANISM_OAUTH2, oauth_token);
+  }
+
+  std::string host;
+  int port;
+
+  int colon = server.find(':');
+  if (colon == -1) {
+    host = server;
+    port = DEFAULT_PORT;
+  } else {
+    host = server.substr(0, colon);
+    port = atoi(server.substr(colon + 1).c_str());
+  }
+
+  xcs.set_server(talk_base::SocketAddress(host, port));
+
+  // Decide on the signaling and crypto settings.
+  cricket::SignalingProtocol signaling_protocol = cricket::PROTOCOL_HYBRID;
+  if (signaling == "jingle") {
+    signaling_protocol = cricket::PROTOCOL_JINGLE;
+  } else if (signaling == "gingle") {
+    signaling_protocol = cricket::PROTOCOL_GINGLE;
+  } else if (signaling == "hybrid") {
+    signaling_protocol = cricket::PROTOCOL_HYBRID;
+  } else {
+    Print("Invalid signaling protocol.  Must be jingle, gingle, or hybrid.\n");
+    return 1;
+  }
+
+  cricket::TransportProtocol transport_protocol = cricket::ICEPROTO_HYBRID;
+  if (transport == "ice") {
+    transport_protocol = cricket::ICEPROTO_RFC5245;
+  } else if (transport == "gice") {
+    transport_protocol = cricket::ICEPROTO_GOOGLE;
+  } else if (transport == "hybrid") {
+    transport_protocol = cricket::ICEPROTO_HYBRID;
+  } else {
+    Print("Invalid transport protocol.  Must be ice, gice, or hybrid.\n");
+    return 1;
+  }
+
+  cricket::DataChannelType data_channel_type = cricket::DCT_NONE;
+  if (data_channel == "rtp") {
+    data_channel_type = cricket::DCT_RTP;
+  } else if (data_channel == "sctp") {
+    data_channel_type = cricket::DCT_SCTP;
+  } else if (!data_channel.empty()) {
+    Print("Invalid data channel type.  Must be rtp or sctp.\n");
+    return 1;
+  }
+
+  cricket::SecurePolicy sdes_policy, dtls_policy;
+  if (!GetSecurePolicy(sdes, &sdes_policy)) {
+    Print("Invalid SDES policy. Must be enable, disable, or require.\n");
+    return 1;
+  }
+  if (!GetSecurePolicy(dtls, &dtls_policy)) {
+    Print("Invalid DTLS policy. Must be enable, disable, or require.\n");
+    return 1;
+  }
+  if (dtls_policy != cricket::SEC_DISABLED) {
+    ssl_identity = talk_base::SSLIdentity::Generate(jid.Str());
+    if (!ssl_identity) {
+      Print("Failed to generate identity for DTLS.\n");
+      return 1;
+    }
+  }
+
+#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::Instance()->SetCurrentThread(&w32_thread);
+#endif
+  talk_base::Thread* main_thread = talk_base::Thread::Current();
+#ifdef OSX
+  talk_base::MacCocoaSocketServer ss;
+  talk_base::SocketServerScope ss_scope(&ss);
+#endif
+
+  buzz::XmppPump pump;
+  CallClient *client = new CallClient(pump.client(), caps_node, caps_ver);
+
+  if (FLAG_voiceinput || FLAG_voiceoutput ||
+      FLAG_videoinput || FLAG_videooutput) {
+    // If any dump file is specified, we use a FileMediaEngine.
+    cricket::MediaEngineInterface* engine =
+        MediaEngineFactory::CreateFileMediaEngine(
+            FLAG_voiceinput, FLAG_voiceoutput,
+            FLAG_videoinput, FLAG_videooutput);
+    client->SetMediaEngine(engine);
+  }
+
+  Console *console = new Console(main_thread, client);
+  client->SetConsole(console);
+  client->SetAutoAccept(auto_accept);
+  client->SetPmucDomain(pmuc_domain);
+  client->SetPortAllocatorFlags(portallocator_flags);
+  client->SetAllowLocalIps(true);
+  client->SetSignalingProtocol(signaling_protocol);
+  client->SetTransportProtocol(transport_protocol);
+  client->SetSecurePolicy(sdes_policy, dtls_policy);
+  client->SetSslIdentity(ssl_identity);
+  client->SetRender(render);
+  client->SetDataChannelType(data_channel_type);
+  client->SetMultiSessionEnabled(multisession_enabled);
+  client->SetShowRosterMessages(show_roster_messages);
+  console->Start();
+
+  if (debug) {
+    pump.client()->SignalLogInput.connect(&debug_log_, &DebugLog::Input);
+    pump.client()->SignalLogOutput.connect(&debug_log_, &DebugLog::Output);
+  }
+
+  Print(("Logging in to " + server + " as " + jid.Str() + "\n").c_str());
+  pump.DoLogin(xcs, new buzz::XmppSocket(buzz::TLS_REQUIRED), new XmppAuth());
+  main_thread->Run();
+  pump.DoDisconnect();
+
+  console->Stop();
+  delete console;
+  delete client;
+
+  return 0;
+}
diff --git a/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
new file mode 100644
index 0000000..66c4b6f
--- /dev/null
+++ b/talk/examples/call/callclient.cc
@@ -0,0 +1,1615 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/examples/call/callclient.h"
+
+#include <string>
+
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/network.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+#include "talk/base/windowpickerfactory.h"
+#include "talk/examples/call/console.h"
+#include "talk/examples/call/friendinvitesendtask.h"
+#include "talk/examples/call/muc.h"
+#include "talk/examples/call/mucinviterecvtask.h"
+#include "talk/examples/call/mucinvitesendtask.h"
+#include "talk/examples/call/presencepushtask.h"
+#include "talk/media/base/mediacommon.h"
+#include "talk/media/base/mediaengine.h"
+#include "talk/media/base/rtpdataengine.h"
+#include "talk/media/base/screencastid.h"
+#ifdef HAVE_SCTP
+#include "talk/media/sctp/sctpdataengine.h"
+#endif
+#include "talk/media/base/videorenderer.h"
+#include "talk/media/devices/devicemanager.h"
+#include "talk/media/devices/videorendererfactory.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/client/basicportallocator.h"
+#include "talk/p2p/client/sessionmanagertask.h"
+#include "talk/session/media/mediamessages.h"
+#include "talk/session/media/mediasessionclient.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/hangoutpubsubclient.h"
+#include "talk/xmpp/mucroomconfigtask.h"
+#include "talk/xmpp/mucroomlookuptask.h"
+#include "talk/xmpp/presenceouttask.h"
+#include "talk/xmpp/pingtask.h"
+
+namespace {
+
+// Must be period >= timeout.
+const uint32 kPingPeriodMillis = 10000;
+const uint32 kPingTimeoutMillis = 10000;
+
+const char* DescribeStatus(buzz::PresenceStatus::Show show,
+                           const std::string& desc) {
+  switch (show) {
+  case buzz::PresenceStatus::SHOW_XA:      return desc.c_str();
+  case buzz::PresenceStatus::SHOW_ONLINE:  return "online";
+  case buzz::PresenceStatus::SHOW_AWAY:    return "away";
+  case buzz::PresenceStatus::SHOW_DND:     return "do not disturb";
+  case buzz::PresenceStatus::SHOW_CHAT:    return "ready to chat";
+  default:                                 return "offline";
+  }
+}
+
+std::string GetWord(const std::vector<std::string>& words,
+                    size_t index, const std::string& def) {
+  if (words.size() > index) {
+    return words[index];
+  } else {
+    return def;
+  }
+}
+
+int GetInt(const std::vector<std::string>& words, size_t index, int def) {
+  int val;
+  if (words.size() > index && talk_base::FromString(words[index], &val)) {
+    return val;
+  } else {
+    return def;
+  }
+}
+
+}  // namespace
+
+const char* CALL_COMMANDS =
+"Available commands:\n"
+"\n"
+"  hangup            Ends the call.\n"
+"  hold              Puts the current call on hold\n"
+"  calls             Lists the current calls and their sessions\n"
+"  switch [call_id]  Switch to the specified call\n"
+"  addsession [jid]  Add a new session to the current call.\n"
+"  rmsession [sid]   Remove specified session.\n"
+"  mute              Stops sending voice.\n"
+"  unmute            Re-starts sending voice.\n"
+"  vmute             Stops sending video.\n"
+"  vunmute           Re-starts sending video.\n"
+"  dtmf              Sends a DTMF tone.\n"
+"  stats             Print voice stats for the current call.\n"
+"  quit              Quits the application.\n"
+"";
+
+// TODO: Make present and record really work.
+const char* HANGOUT_COMMANDS =
+"Available MUC commands:\n"
+"\n"
+"  present    Starts presenting (just signalling; not actually presenting.)\n"
+"  unpresent  Stops presenting (just signalling; not actually presenting.)\n"
+"  record     Starts recording (just signalling; not actually recording.)\n"
+"  unrecord   Stops recording (just signalling; not actually recording.)\n"
+"  rmute [nick] Remote mute another participant.\n"
+"  block [nick] Block another participant.\n"
+"  screencast [fps] Starts screencast. \n"
+"  unscreencast Stops screencast. \n"
+"  quit       Quits the application.\n"
+"";
+
+const char* RECEIVE_COMMANDS =
+"Available commands:\n"
+"\n"
+"  accept [bw] Accepts the incoming call and switches to it.\n"
+"  reject  Rejects the incoming call and stays with the current call.\n"
+"  quit    Quits the application.\n"
+"";
+
+const char* CONSOLE_COMMANDS =
+"Available commands:\n"
+"\n"
+"  roster              Prints the online friends from your roster.\n"
+"  friend user         Request to add a user to your roster.\n"
+"  call [jid] [bw]     Initiates a call to the user[/room] with the\n"
+"                      given JID and with optional bandwidth.\n"
+"  vcall [jid] [bw]    Initiates a video call to the user[/room] with\n"
+"                      the given JID and with optional bandwidth.\n"
+"  calls               Lists the current calls\n"
+"  switch [call_id]    Switch to the specified call\n"
+"  join [room_jid]     Joins a multi-user-chat with room JID.\n"
+"  ljoin [room_name]   Joins a MUC by looking up JID from room name.\n"
+"  invite user [room]  Invites a friend to a multi-user-chat.\n"
+"  leave [room]        Leaves a multi-user-chat.\n"
+"  nick [nick]         Sets the nick.\n"
+"  priority [int]      Sets the priority.\n"
+"  getdevs             Prints the available media devices.\n"
+"  quit                Quits the application.\n"
+"";
+
+void CallClient::ParseLine(const std::string& line) {
+  std::vector<std::string> words;
+  int start = -1;
+  int state = 0;
+  for (int index = 0; index <= static_cast<int>(line.size()); ++index) {
+    if (state == 0) {
+      if (!isspace(line[index])) {
+        start = index;
+        state = 1;
+      }
+    } else {
+      ASSERT(state == 1);
+      ASSERT(start >= 0);
+      if (isspace(line[index])) {
+        std::string word(line, start, index - start);
+        words.push_back(word);
+        start = -1;
+        state = 0;
+      }
+    }
+  }
+
+  // Global commands
+  const std::string& command = GetWord(words, 0, "");
+  if (command == "quit") {
+    Quit();
+  } else if (call_ && incoming_call_) {
+    if (command == "accept") {
+      cricket::CallOptions options;
+      options.video_bandwidth = GetInt(words, 1, cricket::kAutoBandwidth);
+      options.has_video = true;
+      options.data_channel_type = data_channel_type_;
+      Accept(options);
+    } else if (command == "reject") {
+      Reject();
+    } else {
+      console_->PrintLine(RECEIVE_COMMANDS);
+    }
+  } else if (call_) {
+    if (command == "hangup") {
+      call_->Terminate();
+    } else if (command == "hold") {
+      media_client_->SetFocus(NULL);
+      call_ = NULL;
+    } else if (command == "addsession") {
+      std::string to = GetWord(words, 1, "");
+      cricket::CallOptions options;
+      options.has_video = call_->has_video();
+      options.video_bandwidth = cricket::kAutoBandwidth;
+      options.data_channel_type = data_channel_type_;
+      options.AddStream(cricket::MEDIA_TYPE_VIDEO, "", "");
+      if (!InitiateAdditionalSession(to, options)) {
+        console_->PrintLine("Failed to initiate additional session.");
+      }
+    } else if (command == "rmsession") {
+      std::string id = GetWord(words, 1, "");
+      TerminateAndRemoveSession(call_, id);
+    } else if (command == "calls") {
+      PrintCalls();
+    } else if ((words.size() == 2) && (command == "switch")) {
+      SwitchToCall(GetInt(words, 1, -1));
+    } else if (command == "mute") {
+      call_->Mute(true);
+      if (InMuc()) {
+        hangout_pubsub_client_->PublishAudioMuteState(true);
+      }
+    } else if (command == "unmute") {
+      call_->Mute(false);
+      if (InMuc()) {
+        hangout_pubsub_client_->PublishAudioMuteState(false);
+      }
+    } else if (command == "vmute") {
+      call_->MuteVideo(true);
+      if (InMuc()) {
+        hangout_pubsub_client_->PublishVideoMuteState(true);
+      }
+    } else if (command == "vunmute") {
+      call_->MuteVideo(false);
+      if (InMuc()) {
+        hangout_pubsub_client_->PublishVideoMuteState(false);
+      }
+    } else if (command == "screencast") {
+      if (screencast_ssrc_ != 0) {
+        console_->PrintLine("Can't screencast twice.  Unscreencast first.");
+      } else {
+        std::string streamid = "screencast";
+        screencast_ssrc_ = talk_base::CreateRandomId();
+        int fps = GetInt(words, 1, 5);  // Default to 5 fps.
+
+        cricket::ScreencastId screencastid;
+        cricket::Session* session = GetFirstSession();
+        if (session && SelectFirstDesktopScreencastId(&screencastid)) {
+          call_->StartScreencast(
+              session, streamid, screencast_ssrc_, screencastid, fps);
+        }
+      }
+    } else if (command == "unscreencast") {
+      // TODO: Use a random ssrc
+      std::string streamid = "screencast";
+
+      cricket::Session* session = GetFirstSession();
+      if (session) {
+        call_->StopScreencast(session, streamid, screencast_ssrc_);
+        screencast_ssrc_ = 0;
+      }
+    } else if (command == "present") {
+      if (InMuc()) {
+        hangout_pubsub_client_->PublishPresenterState(true);
+      }
+    } else if (command == "unpresent") {
+      if (InMuc()) {
+        hangout_pubsub_client_->PublishPresenterState(false);
+      }
+    } else if (command == "record") {
+      if (InMuc()) {
+        hangout_pubsub_client_->PublishRecordingState(true);
+      }
+    } else if (command == "unrecord") {
+      if (InMuc()) {
+        hangout_pubsub_client_->PublishRecordingState(false);
+      }
+    } else if ((command == "rmute") && (words.size() == 2)) {
+      if (InMuc()) {
+        const std::string& nick = words[1];
+        hangout_pubsub_client_->RemoteMute(nick);
+      }
+    } else if ((command == "block") && (words.size() == 2)) {
+      if (InMuc()) {
+        const std::string& nick = words[1];
+        hangout_pubsub_client_->BlockMedia(nick);
+      }
+    } else if (command == "senddata") {
+      // "" is the default streamid.
+      SendData("", words[1]);
+    } else if ((command == "dtmf") && (words.size() == 2)) {
+      int ev = std::string("0123456789*#").find(words[1][0]);
+      call_->PressDTMF(ev);
+    } else if (command == "stats") {
+      PrintStats();
+    } else {
+      console_->PrintLine(CALL_COMMANDS);
+      if (InMuc()) {
+        console_->PrintLine(HANGOUT_COMMANDS);
+      }
+    }
+  } else {
+    if (command == "roster") {
+      PrintRoster();
+    } else if (command == "send") {
+      buzz::Jid jid(words[1]);
+      if (jid.IsValid()) {
+        last_sent_to_ = words[1];
+        SendChat(words[1], words[2]);
+      } else if (!last_sent_to_.empty()) {
+        SendChat(last_sent_to_, words[1]);
+      } else {
+        console_->PrintLine(
+            "Invalid JID. JIDs should be in the form user@domain");
+      }
+    } else if ((words.size() == 2) && (command == "friend")) {
+      InviteFriend(words[1]);
+    } else if (command == "call") {
+      std::string to = GetWord(words, 1, "");
+      cricket::CallOptions options;
+      options.data_channel_type = data_channel_type_;
+      if (!PlaceCall(to, options)) {
+        console_->PrintLine("Failed to initiate call.");
+      }
+    } else if (command == "vcall") {
+      std::string to = GetWord(words, 1, "");
+      int bandwidth = GetInt(words, 2, cricket::kAutoBandwidth);
+      cricket::CallOptions options;
+      options.has_video = true;
+      options.video_bandwidth = bandwidth;
+      options.data_channel_type = data_channel_type_;
+      if (!PlaceCall(to, options)) {
+        console_->PrintLine("Failed to initiate call.");
+      }
+    } else if (command == "calls") {
+      PrintCalls();
+    } else if ((words.size() == 2) && (command == "switch")) {
+      SwitchToCall(GetInt(words, 1, -1));
+    } else if (command == "join") {
+      JoinMuc(GetWord(words, 1, ""));
+    } else if (command == "ljoin") {
+      LookupAndJoinMuc(GetWord(words, 1, ""));
+    } else if ((words.size() >= 2) && (command == "invite")) {
+      InviteToMuc(words[1], GetWord(words, 2, ""));
+    } else if (command == "leave") {
+      LeaveMuc(GetWord(words, 1, ""));
+    } else if (command == "nick") {
+      SetNick(GetWord(words, 1, ""));
+    } else if (command == "priority") {
+      int priority = GetInt(words, 1, 0);
+      SetPriority(priority);
+      SendStatus();
+    } else if (command == "getdevs") {
+      GetDevices();
+    } else if ((words.size() == 2) && (command == "setvol")) {
+      SetVolume(words[1]);
+    } else {
+      console_->PrintLine(CONSOLE_COMMANDS);
+    }
+  }
+}
+
+CallClient::CallClient(buzz::XmppClient* xmpp_client,
+                       const std::string& caps_node, const std::string& version)
+    : xmpp_client_(xmpp_client),
+      worker_thread_(NULL),
+      media_engine_(NULL),
+      data_engine_(NULL),
+      media_client_(NULL),
+      call_(NULL),
+      hangout_pubsub_client_(NULL),
+      incoming_call_(false),
+      auto_accept_(false),
+      pmuc_domain_("groupchat.google.com"),
+      render_(true),
+      data_channel_type_(cricket::DCT_NONE),
+      multisession_enabled_(false),
+      local_renderer_(NULL),
+      static_views_accumulated_count_(0),
+      screencast_ssrc_(0),
+      roster_(new RosterMap),
+      portallocator_flags_(0),
+      allow_local_ips_(false),
+      signaling_protocol_(cricket::PROTOCOL_HYBRID),
+      transport_protocol_(cricket::ICEPROTO_HYBRID),
+      sdes_policy_(cricket::SEC_DISABLED),
+      dtls_policy_(cricket::SEC_DISABLED),
+      ssl_identity_(NULL),
+      show_roster_messages_(false) {
+  xmpp_client_->SignalStateChange.connect(this, &CallClient::OnStateChange);
+  my_status_.set_caps_node(caps_node);
+  my_status_.set_version(version);
+}
+
+CallClient::~CallClient() {
+  delete media_client_;
+  delete roster_;
+  delete worker_thread_;
+}
+
+const std::string CallClient::strerror(buzz::XmppEngine::Error err) {
+  switch (err) {
+    case buzz::XmppEngine::ERROR_NONE:
+      return "";
+    case buzz::XmppEngine::ERROR_XML:
+      return "Malformed XML or encoding error";
+    case buzz::XmppEngine::ERROR_STREAM:
+      return "XMPP stream error";
+    case buzz::XmppEngine::ERROR_VERSION:
+      return "XMPP version error";
+    case buzz::XmppEngine::ERROR_UNAUTHORIZED:
+      return "User is not authorized (Check your username and password)";
+    case buzz::XmppEngine::ERROR_TLS:
+      return "TLS could not be negotiated";
+    case buzz::XmppEngine::ERROR_AUTH:
+      return "Authentication could not be negotiated";
+    case buzz::XmppEngine::ERROR_BIND:
+      return "Resource or session binding could not be negotiated";
+    case buzz::XmppEngine::ERROR_CONNECTION_CLOSED:
+      return "Connection closed by output handler.";
+    case buzz::XmppEngine::ERROR_DOCUMENT_CLOSED:
+      return "Closed by </stream:stream>";
+    case buzz::XmppEngine::ERROR_SOCKET:
+      return "Socket error";
+    default:
+      return "Unknown error";
+  }
+}
+
+void CallClient::OnCallDestroy(cricket::Call* call) {
+  RemoveCallsStaticRenderedViews(call);
+  if (call == call_) {
+    if (local_renderer_) {
+      delete local_renderer_;
+      local_renderer_ = NULL;
+    }
+    console_->PrintLine("call destroyed");
+    call_ = NULL;
+    delete hangout_pubsub_client_;
+    hangout_pubsub_client_ = NULL;
+  }
+}
+
+void CallClient::OnStateChange(buzz::XmppEngine::State state) {
+  switch (state) {
+    case buzz::XmppEngine::STATE_START:
+      console_->PrintLine("connecting...");
+      break;
+    case buzz::XmppEngine::STATE_OPENING:
+      console_->PrintLine("logging in...");
+      break;
+    case buzz::XmppEngine::STATE_OPEN:
+      console_->PrintLine("logged in...");
+      InitMedia();
+      InitPresence();
+      break;
+    case buzz::XmppEngine::STATE_CLOSED:
+      {
+        buzz::XmppEngine::Error error = xmpp_client_->GetError(NULL);
+        console_->PrintLine("logged out... %s", strerror(error).c_str());
+        Quit();
+      }
+      break;
+    default:
+      break;
+  }
+}
+
+void CallClient::InitMedia() {
+  worker_thread_ = new talk_base::Thread();
+  // The worker thread must be started here since initialization of
+  // the ChannelManager will generate messages that need to be
+  // dispatched by it.
+  worker_thread_->Start();
+
+  // TODO: It looks like we are leaking many objects. E.g.
+  // |network_manager_| is never deleted.
+  network_manager_ = new talk_base::BasicNetworkManager();
+
+  // TODO: Decide if the relay address should be specified here.
+  talk_base::SocketAddress stun_addr("stun.l.google.com", 19302);
+  port_allocator_ =  new cricket::BasicPortAllocator(
+      network_manager_, stun_addr, talk_base::SocketAddress(),
+      talk_base::SocketAddress(), talk_base::SocketAddress());
+
+  if (portallocator_flags_ != 0) {
+    port_allocator_->set_flags(portallocator_flags_);
+  }
+  session_manager_ = new cricket::SessionManager(
+      port_allocator_, worker_thread_);
+  session_manager_->set_secure(dtls_policy_);
+  session_manager_->set_identity(ssl_identity_.get());
+  session_manager_->set_transport_protocol(transport_protocol_);
+  session_manager_->SignalRequestSignaling.connect(
+      this, &CallClient::OnRequestSignaling);
+  session_manager_->SignalSessionCreate.connect(
+      this, &CallClient::OnSessionCreate);
+  session_manager_->OnSignalingReady();
+
+  session_manager_task_ =
+      new cricket::SessionManagerTask(xmpp_client_, session_manager_);
+  session_manager_task_->EnableOutgoingMessages();
+  session_manager_task_->Start();
+
+  if (!media_engine_) {
+    media_engine_ = cricket::MediaEngineFactory::Create();
+  }
+
+  if (!data_engine_) {
+    if (data_channel_type_ == cricket::DCT_SCTP) {
+#ifdef HAVE_SCTP
+      data_engine_ = new cricket::SctpDataEngine();
+#else
+      LOG(LS_WARNING) << "SCTP Data Engine not supported.";
+      data_channel_type_ = cricket::DCT_NONE;
+      data_engine_ = new cricket::RtpDataEngine();
+#endif
+    } else {
+      // Even if we have DCT_NONE, we still have a data engine, just
+      // to make sure it isn't NULL.
+      data_engine_ = new cricket::RtpDataEngine();
+    }
+  }
+
+  media_client_ = new cricket::MediaSessionClient(
+      xmpp_client_->jid(),
+      session_manager_,
+      media_engine_,
+      data_engine_,
+      cricket::DeviceManagerFactory::Create());
+  media_client_->SignalCallCreate.connect(this, &CallClient::OnCallCreate);
+  media_client_->SignalCallDestroy.connect(this, &CallClient::OnCallDestroy);
+  media_client_->SignalDevicesChange.connect(this,
+                                             &CallClient::OnDevicesChange);
+  media_client_->set_secure(sdes_policy_);
+  media_client_->set_multisession_enabled(multisession_enabled_);
+}
+
+void CallClient::OnRequestSignaling() {
+  session_manager_->OnSignalingReady();
+}
+
+void CallClient::OnSessionCreate(cricket::Session* session, bool initiate) {
+  session->set_current_protocol(signaling_protocol_);
+}
+
+void CallClient::OnCallCreate(cricket::Call* call) {
+  call->SignalSessionState.connect(this, &CallClient::OnSessionState);
+  call->SignalMediaStreamsUpdate.connect(
+      this, &CallClient::OnMediaStreamsUpdate);
+}
+
+void CallClient::OnSessionState(cricket::Call* call,
+                                cricket::Session* session,
+                                cricket::Session::State state) {
+  if (state == cricket::Session::STATE_RECEIVEDINITIATE) {
+    buzz::Jid jid(session->remote_name());
+    if (call_ == call && multisession_enabled_) {
+      // We've received an initiate for an existing call. This is actually a
+      // new session for that call.
+      console_->PrintLine("Incoming session from '%s'", jid.Str().c_str());
+      AddSession(session);
+
+      cricket::CallOptions options;
+      options.has_video = call_->has_video();
+      options.data_channel_type = data_channel_type_;
+      call_->AcceptSession(session, options);
+
+      if (call_->has_video() && render_) {
+        RenderAllStreams(call, session, true);
+      }
+    } else {
+      console_->PrintLine("Incoming call from '%s'", jid.Str().c_str());
+      call_ = call;
+      AddSession(session);
+      incoming_call_ = true;
+      if (call->has_video() && render_) {
+        local_renderer_ =
+            cricket::VideoRendererFactory::CreateGuiVideoRenderer(160, 100);
+      }
+      if (auto_accept_) {
+        cricket::CallOptions options;
+        options.has_video = true;
+        options.data_channel_type = data_channel_type_;
+        Accept(options);
+      }
+    }
+  } else if (state == cricket::Session::STATE_SENTINITIATE) {
+    if (call->has_video() && render_) {
+      local_renderer_ =
+          cricket::VideoRendererFactory::CreateGuiVideoRenderer(160, 100);
+    }
+    console_->PrintLine("calling...");
+  } else if (state == cricket::Session::STATE_RECEIVEDACCEPT) {
+    console_->PrintLine("call answered");
+    SetupAcceptedCall();
+  } else if (state == cricket::Session::STATE_RECEIVEDREJECT) {
+    console_->PrintLine("call not answered");
+  } else if (state == cricket::Session::STATE_INPROGRESS) {
+    console_->PrintLine("call in progress");
+    call->SignalSpeakerMonitor.connect(this, &CallClient::OnSpeakerChanged);
+    call->StartSpeakerMonitor(session);
+  } else if (state == cricket::Session::STATE_RECEIVEDTERMINATE) {
+    console_->PrintLine("other side terminated");
+    TerminateAndRemoveSession(call, session->id());
+  }
+}
+
+void CallClient::OnSpeakerChanged(cricket::Call* call,
+                                  cricket::Session* session,
+                                  const cricket::StreamParams& speaker) {
+  if (!speaker.has_ssrcs()) {
+    console_->PrintLine("Session %s has no current speaker.",
+                        session->id().c_str());
+  } else if (speaker.id.empty()) {
+    console_->PrintLine("Session %s speaker change to unknown (%u).",
+                        session->id().c_str(), speaker.first_ssrc());
+  } else {
+    console_->PrintLine("Session %s speaker changed to %s (%u).",
+                        session->id().c_str(), speaker.id.c_str(),
+                        speaker.first_ssrc());
+  }
+}
+
+void SetMediaCaps(int media_caps, buzz::PresenceStatus* status) {
+  status->set_voice_capability((media_caps & cricket::AUDIO_RECV) != 0);
+  status->set_video_capability((media_caps & cricket::VIDEO_RECV) != 0);
+  status->set_camera_capability((media_caps & cricket::VIDEO_SEND) != 0);
+}
+
+void SetCaps(int media_caps, buzz::PresenceStatus* status) {
+  status->set_know_capabilities(true);
+  status->set_pmuc_capability(true);
+  SetMediaCaps(media_caps, status);
+}
+
+void SetAvailable(const buzz::Jid& jid, buzz::PresenceStatus* status) {
+  status->set_jid(jid);
+  status->set_available(true);
+  status->set_show(buzz::PresenceStatus::SHOW_ONLINE);
+}
+
+void CallClient::InitPresence() {
+  presence_push_ = new buzz::PresencePushTask(xmpp_client_, this);
+  presence_push_->SignalStatusUpdate.connect(
+    this, &CallClient::OnStatusUpdate);
+  presence_push_->SignalMucJoined.connect(this, &CallClient::OnMucJoined);
+  presence_push_->SignalMucLeft.connect(this, &CallClient::OnMucLeft);
+  presence_push_->SignalMucStatusUpdate.connect(
+    this, &CallClient::OnMucStatusUpdate);
+  presence_push_->Start();
+
+  presence_out_ = new buzz::PresenceOutTask(xmpp_client_);
+  SetAvailable(xmpp_client_->jid(), &my_status_);
+  SetCaps(media_client_->GetCapabilities(), &my_status_);
+  SendStatus(my_status_);
+  presence_out_->Start();
+
+  muc_invite_recv_ = new buzz::MucInviteRecvTask(xmpp_client_);
+  muc_invite_recv_->SignalInviteReceived.connect(this,
+      &CallClient::OnMucInviteReceived);
+  muc_invite_recv_->Start();
+
+  muc_invite_send_ = new buzz::MucInviteSendTask(xmpp_client_);
+  muc_invite_send_->Start();
+
+  friend_invite_send_ = new buzz::FriendInviteSendTask(xmpp_client_);
+  friend_invite_send_->Start();
+
+  StartXmppPing();
+}
+
+void CallClient::StartXmppPing() {
+  buzz::PingTask* ping = new buzz::PingTask(
+      xmpp_client_, talk_base::Thread::Current(),
+      kPingPeriodMillis, kPingTimeoutMillis);
+  ping->SignalTimeout.connect(this, &CallClient::OnPingTimeout);
+  ping->Start();
+}
+
+void CallClient::OnPingTimeout() {
+  LOG(LS_WARNING) << "XMPP Ping timeout. Will keep trying...";
+  StartXmppPing();
+
+  // Or should we do this instead?
+  // Quit();
+}
+
+void CallClient::SendStatus(const buzz::PresenceStatus& status) {
+  presence_out_->Send(status);
+}
+
+void CallClient::OnStatusUpdate(const buzz::PresenceStatus& status) {
+  RosterItem item;
+  item.jid = status.jid();
+  item.show = status.show();
+  item.status = status.status();
+
+  std::string key = item.jid.Str();
+
+  if (status.available() && status.voice_capability()) {
+    if (show_roster_messages_) {
+      console_->PrintLine("Adding to roster: %s", key.c_str());
+    }
+    (*roster_)[key] = item;
+    // TODO: Make some of these constants.
+  } else {
+    if (show_roster_messages_) {
+      console_->PrintLine("Removing from roster: %s", key.c_str());
+    }
+    RosterMap::iterator iter = roster_->find(key);
+    if (iter != roster_->end())
+      roster_->erase(iter);
+  }
+}
+
+void CallClient::PrintRoster() {
+  console_->PrintLine("Roster contains %d callable", roster_->size());
+  RosterMap::iterator iter = roster_->begin();
+  while (iter != roster_->end()) {
+    console_->PrintLine("%s - %s",
+                        iter->second.jid.BareJid().Str().c_str(),
+                        DescribeStatus(iter->second.show, iter->second.status));
+    iter++;
+  }
+}
+
+void CallClient::SendChat(const std::string& to, const std::string msg) {
+  buzz::XmlElement* stanza = new buzz::XmlElement(buzz::QN_MESSAGE);
+  stanza->AddAttr(buzz::QN_TO, to);
+  stanza->AddAttr(buzz::QN_ID, talk_base::CreateRandomString(16));
+  stanza->AddAttr(buzz::QN_TYPE, "chat");
+  buzz::XmlElement* body = new buzz::XmlElement(buzz::QN_BODY);
+  body->SetBodyText(msg);
+  stanza->AddElement(body);
+
+  xmpp_client_->SendStanza(stanza);
+  delete stanza;
+}
+
+void CallClient::SendData(const std::string& streamid,
+                          const std::string& text) {
+  // TODO(mylesj): Support sending data over sessions other than the first.
+  cricket::Session* session = GetFirstSession();
+  if (!call_ || !session) {
+    console_->PrintLine("Must be in a call to send data.");
+    return;
+  }
+  if (!call_->has_data()) {
+    console_->PrintLine("This call doesn't have a data channel.");
+    return;
+  }
+
+  const cricket::DataContentDescription* data =
+      cricket::GetFirstDataContentDescription(session->local_description());
+  if (!data) {
+    console_->PrintLine("This call doesn't have a data content.");
+    return;
+  }
+
+  cricket::StreamParams stream;
+  if (!cricket::GetStreamByIds(
+          data->streams(), "", streamid, &stream)) {
+    LOG(LS_WARNING) << "Could not send data: no such stream: "
+                    << streamid << ".";
+    return;
+  }
+
+  cricket::SendDataParams params;
+  params.ssrc = stream.first_ssrc();
+  talk_base::Buffer payload(text.data(), text.length());
+  cricket::SendDataResult result;
+  bool sent = call_->SendData(session, params, payload, &result);
+  if (!sent) {
+    if (result == cricket::SDR_BLOCK) {
+      LOG(LS_WARNING) << "Could not send data because it would block.";
+    } else {
+      LOG(LS_WARNING) << "Could not send data for unknown reason.";
+    }
+  }
+}
+
+void CallClient::InviteFriend(const std::string& name) {
+  buzz::Jid jid(name);
+  if (!jid.IsValid() || jid.node() == "") {
+    console_->PrintLine("Invalid JID. JIDs should be in the form user@domain.");
+    return;
+  }
+  // Note: for some reason the Buzz backend does not forward our presence
+  // subscription requests to the end user when that user is another call
+  // client as opposed to a Smurf user. Thus, in that scenario, you must
+  // run the friend command as the other user too to create the linkage
+  // (and you won't be notified to do so).
+  friend_invite_send_->Send(jid);
+  console_->PrintLine("Requesting to befriend %s.", name.c_str());
+}
+
+bool CallClient::FindJid(const std::string& name, buzz::Jid* found_jid,
+                         cricket::CallOptions* options) {
+  bool found = false;
+  options->is_muc = false;
+  buzz::Jid callto_jid(name);
+  if (name.length() == 0 && mucs_.size() > 0) {
+    // if no name, and in a MUC, establish audio with the MUC
+    *found_jid = mucs_.begin()->first;
+    found = true;
+    options->is_muc = true;
+  } else if (name[0] == '+') {
+    // if the first character is a +, assume it's a phone number
+    *found_jid = callto_jid;
+    found = true;
+  } else {
+    // otherwise, it's a friend
+    for (RosterMap::iterator iter = roster_->begin();
+         iter != roster_->end(); ++iter) {
+      if (iter->second.jid.BareEquals(callto_jid)) {
+        found = true;
+        *found_jid = iter->second.jid;
+        break;
+      }
+    }
+
+    if (!found) {
+      if (mucs_.count(callto_jid) == 1 &&
+          mucs_[callto_jid]->state() == buzz::Muc::MUC_JOINED) {
+        found = true;
+        *found_jid = callto_jid;
+        options->is_muc = true;
+      }
+    }
+  }
+
+  if (found) {
+    console_->PrintLine("Found %s '%s'",
+                        options->is_muc ? "room" : "online friend",
+                        found_jid->Str().c_str());
+  } else {
+    console_->PrintLine("Could not find online friend '%s'", name.c_str());
+  }
+
+  return found;
+}
+
+void CallClient::OnDataReceived(cricket::Call*,
+                                const cricket::ReceiveDataParams& params,
+                                const talk_base::Buffer& payload) {
+  // TODO(mylesj): Support receiving data on sessions other than the first.
+  cricket::Session* session = GetFirstSession();
+  if (!session)
+    return;
+
+  cricket::StreamParams stream;
+  const std::vector<cricket::StreamParams>* data_streams =
+      call_->GetDataRecvStreams(session);
+  std::string text(payload.data(), payload.length());
+  if (data_streams && GetStreamBySsrc(*data_streams, params.ssrc, &stream)) {
+    console_->PrintLine(
+        "Received data from '%s' on stream '%s' (ssrc=%u): %s",
+        stream.groupid.c_str(), stream.id.c_str(),
+        params.ssrc, text.c_str());
+  } else {
+    console_->PrintLine(
+        "Received data (ssrc=%u): %s",
+        params.ssrc, text.c_str());
+  }
+}
+
+bool CallClient::PlaceCall(const std::string& name,
+                           cricket::CallOptions options) {
+  buzz::Jid jid;
+  if (!FindJid(name, &jid, &options))
+    return false;
+
+  if (!call_) {
+    call_ = media_client_->CreateCall();
+    AddSession(call_->InitiateSession(jid, media_client_->jid(), options));
+  }
+  media_client_->SetFocus(call_);
+  if (call_->has_video() && render_) {
+    if (!options.is_muc) {
+      call_->SetLocalRenderer(local_renderer_);
+    }
+  }
+  if (options.is_muc) {
+    const std::string& nick = mucs_[jid]->local_jid().resource();
+    hangout_pubsub_client_ =
+        new buzz::HangoutPubSubClient(xmpp_client_, jid, nick);
+    hangout_pubsub_client_->SignalPresenterStateChange.connect(
+        this, &CallClient::OnPresenterStateChange);
+    hangout_pubsub_client_->SignalAudioMuteStateChange.connect(
+        this, &CallClient::OnAudioMuteStateChange);
+    hangout_pubsub_client_->SignalRecordingStateChange.connect(
+        this, &CallClient::OnRecordingStateChange);
+    hangout_pubsub_client_->SignalRemoteMute.connect(
+        this, &CallClient::OnRemoteMuted);
+    hangout_pubsub_client_->SignalMediaBlock.connect(
+        this, &CallClient::OnMediaBlocked);
+    hangout_pubsub_client_->SignalRequestError.connect(
+        this, &CallClient::OnHangoutRequestError);
+    hangout_pubsub_client_->SignalPublishAudioMuteError.connect(
+        this, &CallClient::OnHangoutPublishAudioMuteError);
+    hangout_pubsub_client_->SignalPublishPresenterError.connect(
+        this, &CallClient::OnHangoutPublishPresenterError);
+    hangout_pubsub_client_->SignalPublishRecordingError.connect(
+        this, &CallClient::OnHangoutPublishRecordingError);
+    hangout_pubsub_client_->SignalRemoteMuteError.connect(
+        this, &CallClient::OnHangoutRemoteMuteError);
+    hangout_pubsub_client_->RequestAll();
+  }
+
+  return true;
+}
+
+bool CallClient::InitiateAdditionalSession(const std::string& name,
+                                           cricket::CallOptions options) {
+  // Can't add a session if there is no call yet.
+  if (!call_)
+    return false;
+
+  buzz::Jid jid;
+  if (!FindJid(name, &jid, &options))
+    return false;
+
+  std::vector<cricket::Session*>& call_sessions = sessions_[call_->id()];
+  call_sessions.push_back(
+      call_->InitiateSession(jid,
+                             buzz::Jid(call_sessions[0]->remote_name()),
+                             options));
+
+  return true;
+}
+
+void CallClient::TerminateAndRemoveSession(cricket::Call* call,
+                                           const std::string& id) {
+  std::vector<cricket::Session*>& call_sessions = sessions_[call->id()];
+  for (std::vector<cricket::Session*>::iterator iter = call_sessions.begin();
+       iter != call_sessions.end(); ++iter) {
+    if ((*iter)->id() == id) {
+      RenderAllStreams(call, *iter, false);
+      call_->TerminateSession(*iter);
+      call_sessions.erase(iter);
+      break;
+    }
+  }
+}
+
+void CallClient::PrintCalls() {
+  const std::map<uint32, cricket::Call*>& calls = media_client_->calls();
+  for (std::map<uint32, cricket::Call*>::const_iterator i = calls.begin();
+       i != calls.end(); ++i) {
+    console_->PrintLine("Call (id:%d), is %s",
+                        i->first,
+                        i->second == call_ ? "active" : "on hold");
+    std::vector<cricket::Session *>& sessions = sessions_[call_->id()];
+    for (std::vector<cricket::Session *>::const_iterator j = sessions.begin();
+         j != sessions.end(); ++j) {
+      console_->PrintLine("|--Session (id:%s), to %s", (*j)->id().c_str(),
+                          (*j)->remote_name().c_str());
+
+      std::vector<cricket::StreamParams>::const_iterator k;
+      const std::vector<cricket::StreamParams>* streams =
+          i->second->GetAudioRecvStreams(*j);
+      if (streams)
+        for (k = streams->begin(); k != streams->end(); ++k) {
+          console_->PrintLine("|----Audio Stream: %s", k->ToString().c_str());
+        }
+      streams = i->second->GetVideoRecvStreams(*j);
+      if (streams)
+        for (k = streams->begin(); k != streams->end(); ++k) {
+          console_->PrintLine("|----Video Stream: %s", k->ToString().c_str());
+        }
+      streams = i->second->GetDataRecvStreams(*j);
+      if (streams)
+        for (k = streams->begin(); k != streams->end(); ++k) {
+          console_->PrintLine("|----Data Stream: %s", k->ToString().c_str());
+        }
+    }
+  }
+}
+
+void CallClient::SwitchToCall(uint32 call_id) {
+  const std::map<uint32, cricket::Call*>& calls = media_client_->calls();
+  std::map<uint32, cricket::Call*>::const_iterator call_iter =
+      calls.find(call_id);
+  if (call_iter != calls.end()) {
+    media_client_->SetFocus(call_iter->second);
+    call_ = call_iter->second;
+  } else {
+    console_->PrintLine("Unable to find call: %d", call_id);
+  }
+}
+
+void CallClient::OnPresenterStateChange(
+    const std::string& nick, bool was_presenting, bool is_presenting) {
+  if (!was_presenting && is_presenting) {
+    console_->PrintLine("%s now presenting.", nick.c_str());
+  } else if (was_presenting && !is_presenting) {
+    console_->PrintLine("%s no longer presenting.", nick.c_str());
+  } else if (was_presenting && is_presenting) {
+    console_->PrintLine("%s still presenting.", nick.c_str());
+  } else if (!was_presenting && !is_presenting) {
+    console_->PrintLine("%s still not presenting.", nick.c_str());
+  }
+}
+
+void CallClient::OnAudioMuteStateChange(
+    const std::string& nick, bool was_muted, bool is_muted) {
+  if (!was_muted && is_muted) {
+    console_->PrintLine("%s now muted.", nick.c_str());
+  } else if (was_muted && !is_muted) {
+    console_->PrintLine("%s no longer muted.", nick.c_str());
+  }
+}
+
+void CallClient::OnRecordingStateChange(
+    const std::string& nick, bool was_recording, bool is_recording) {
+  if (!was_recording && is_recording) {
+    console_->PrintLine("%s now recording.", nick.c_str());
+  } else if (was_recording && !is_recording) {
+    console_->PrintLine("%s no longer recording.", nick.c_str());
+  }
+}
+
+void CallClient::OnRemoteMuted(const std::string& mutee_nick,
+                               const std::string& muter_nick,
+                               bool should_mute_locally) {
+  if (should_mute_locally) {
+    call_->Mute(true);
+    console_->PrintLine("Remote muted by %s.", muter_nick.c_str());
+  } else {
+    console_->PrintLine("%s remote muted by %s.",
+                        mutee_nick.c_str(), muter_nick.c_str());
+  }
+}
+
+void CallClient::OnMediaBlocked(const std::string& blockee_nick,
+                                const std::string& blocker_nick) {
+  console_->PrintLine("%s blocked by %s.",
+                      blockee_nick.c_str(), blocker_nick.c_str());
+}
+
+void CallClient::OnHangoutRequestError(const std::string& node,
+                                       const buzz::XmlElement* stanza) {
+  console_->PrintLine("Failed request pub sub items for node %s.",
+                      node.c_str());
+}
+
+void CallClient::OnHangoutPublishAudioMuteError(
+    const std::string& task_id, const buzz::XmlElement* stanza) {
+  console_->PrintLine("Failed to publish audio mute state.");
+}
+
+void CallClient::OnHangoutPublishPresenterError(
+    const std::string& task_id, const buzz::XmlElement* stanza) {
+  console_->PrintLine("Failed to publish presenting state.");
+}
+
+void CallClient::OnHangoutPublishRecordingError(
+    const std::string& task_id, const buzz::XmlElement* stanza) {
+  console_->PrintLine("Failed to publish recording state.");
+}
+
+void CallClient::OnHangoutRemoteMuteError(const std::string& task_id,
+                                          const std::string& mutee_nick,
+                                          const buzz::XmlElement* stanza) {
+  console_->PrintLine("Failed to remote mute.");
+}
+
+void CallClient::Accept(const cricket::CallOptions& options) {
+  ASSERT(call_ && incoming_call_);
+  ASSERT(sessions_[call_->id()].size() == 1);
+  cricket::Session* session = GetFirstSession();
+  call_->AcceptSession(session, options);
+  media_client_->SetFocus(call_);
+  if (call_->has_video() && render_) {
+    call_->SetLocalRenderer(local_renderer_);
+    RenderAllStreams(call_, session, true);
+  }
+  SetupAcceptedCall();
+  incoming_call_ = false;
+}
+
+void CallClient::SetupAcceptedCall() {
+  if (call_->has_data()) {
+    call_->SignalDataReceived.connect(this, &CallClient::OnDataReceived);
+  }
+}
+
+void CallClient::Reject() {
+  ASSERT(call_ && incoming_call_);
+  call_->RejectSession(call_->sessions()[0]);
+  incoming_call_ = false;
+}
+
+void CallClient::Quit() {
+  talk_base::Thread::Current()->Quit();
+}
+
+void CallClient::SetNick(const std::string& muc_nick) {
+  my_status_.set_nick(muc_nick);
+
+  // TODO: We might want to re-send presence, but right
+  // now, it appears to be ignored by the MUC.
+  //
+  // presence_out_->Send(my_status_); for (MucMap::const_iterator itr
+  // = mucs_.begin(); itr != mucs_.end(); ++itr) {
+  // presence_out_->SendDirected(itr->second->local_jid(),
+  // my_status_); }
+
+  console_->PrintLine("Nick set to '%s'.", muc_nick.c_str());
+}
+
+void CallClient::LookupAndJoinMuc(const std::string& room_name) {
+  // The room_name can't be empty for lookup task.
+  if (room_name.empty()) {
+    console_->PrintLine("Please provide a room name or room jid.");
+    return;
+  }
+
+  std::string room = room_name;
+  std::string domain = xmpp_client_->jid().domain();
+  if (room_name.find("@") != std::string::npos) {
+    // Assume the room_name is a fully qualified room name.
+    // We'll find the room name string and domain name string from it.
+    room = room_name.substr(0, room_name.find("@"));
+    domain = room_name.substr(room_name.find("@") + 1);
+  }
+
+  buzz::MucRoomLookupTask* lookup_query_task =
+      buzz::MucRoomLookupTask::CreateLookupTaskForRoomName(
+          xmpp_client_, buzz::Jid(buzz::STR_GOOGLE_MUC_LOOKUP_JID), room,
+          domain);
+  lookup_query_task->SignalResult.connect(this,
+      &CallClient::OnRoomLookupResponse);
+  lookup_query_task->SignalError.connect(this,
+      &CallClient::OnRoomLookupError);
+  lookup_query_task->Start();
+}
+
+void CallClient::JoinMuc(const std::string& room_jid_str) {
+  if (room_jid_str.empty()) {
+    buzz::Jid room_jid = GenerateRandomMucJid();
+    console_->PrintLine("Generated a random room jid: %s",
+                        room_jid.Str().c_str());
+    JoinMuc(room_jid);
+  } else {
+    JoinMuc(buzz::Jid(room_jid_str));
+  }
+}
+
+void CallClient::JoinMuc(const buzz::Jid& room_jid) {
+  if (!room_jid.IsValid()) {
+    console_->PrintLine("Unable to make valid muc endpoint for %s",
+                        room_jid.Str().c_str());
+    return;
+  }
+
+  std::string room_nick = room_jid.resource();
+  if (room_nick.empty()) {
+    room_nick = (xmpp_client_->jid().node()
+                 + "_" + xmpp_client_->jid().resource());
+  }
+
+  MucMap::iterator elem = mucs_.find(room_jid);
+  if (elem != mucs_.end()) {
+    console_->PrintLine("This MUC already exists.");
+    return;
+  }
+
+  buzz::Muc* muc = new buzz::Muc(room_jid.BareJid(), room_nick);
+  mucs_[muc->jid()] = muc;
+  presence_out_->SendDirected(muc->local_jid(), my_status_);
+}
+
+void CallClient::OnRoomLookupResponse(buzz::MucRoomLookupTask* task,
+                                      const buzz::MucRoomInfo& room) {
+  // The server requires the room be "configured" before being used.
+  // We only need to configure it if we create it, but rooms are
+  // auto-created at lookup, so there's currently no way to know if we
+  // created it.  So, we configure it every time, just in case.
+  // Luckily, it appears to be safe to configure a room that's already
+  // configured.  Our current flow is:
+  // 1. Lookup/auto-create
+  // 2. Configure
+  // 3. Join
+  // TODO: In the future, once the server supports it, we
+  // should:
+  // 1. Lookup
+  // 2. Create and Configure if necessary
+  // 3. Join
+  std::vector<std::string> room_features;
+  room_features.push_back(buzz::STR_MUC_ROOM_FEATURE_ENTERPRISE);
+  buzz::MucRoomConfigTask* room_config_task = new buzz::MucRoomConfigTask(
+      xmpp_client_, room.jid, room.full_name(), room_features);
+  room_config_task->SignalResult.connect(this,
+      &CallClient::OnRoomConfigResult);
+  room_config_task->SignalError.connect(this,
+      &CallClient::OnRoomConfigError);
+  room_config_task->Start();
+}
+
+void CallClient::OnRoomLookupError(buzz::IqTask* task,
+                                   const buzz::XmlElement* stanza) {
+  if (stanza == NULL) {
+    console_->PrintLine("Room lookup failed.");
+  } else {
+    console_->PrintLine("Room lookup error: ", stanza->Str().c_str());
+  }
+}
+
+void CallClient::OnRoomConfigResult(buzz::MucRoomConfigTask* task) {
+  JoinMuc(task->room_jid());
+}
+
+void CallClient::OnRoomConfigError(buzz::IqTask* task,
+                                   const buzz::XmlElement* stanza) {
+  console_->PrintLine("Room config failed.");
+  // We join the muc anyway, because if the room is already
+  // configured, the configure will fail, but we still want to join.
+  // Idealy, we'd know why the room config failed and only do this on
+  // "already configured" errors.  But right now all we get back is
+  // "not-allowed".
+  buzz::MucRoomConfigTask* config_task =
+      static_cast<buzz::MucRoomConfigTask*>(task);
+  JoinMuc(config_task->room_jid());
+}
+
+void CallClient::OnMucInviteReceived(const buzz::Jid& inviter,
+    const buzz::Jid& room,
+    const std::vector<buzz::AvailableMediaEntry>& avail) {
+
+  console_->PrintLine("Invited to join %s by %s.", room.Str().c_str(),
+      inviter.Str().c_str());
+  console_->PrintLine("Available media:");
+  if (avail.size() > 0) {
+    for (std::vector<buzz::AvailableMediaEntry>::const_iterator i =
+            avail.begin();
+        i != avail.end();
+        ++i) {
+      console_->PrintLine("  %s, %s",
+                          buzz::AvailableMediaEntry::TypeAsString(i->type),
+                          buzz::AvailableMediaEntry::StatusAsString(i->status));
+    }
+  } else {
+    console_->PrintLine("  None");
+  }
+  // We automatically join the room.
+  JoinMuc(room);
+}
+
+void CallClient::OnMucJoined(const buzz::Jid& endpoint) {
+  MucMap::iterator elem = mucs_.find(endpoint);
+  ASSERT(elem != mucs_.end() &&
+         elem->second->state() == buzz::Muc::MUC_JOINING);
+
+  buzz::Muc* muc = elem->second;
+  muc->set_state(buzz::Muc::MUC_JOINED);
+  console_->PrintLine("Joined \"%s\"", muc->jid().Str().c_str());
+}
+
+void CallClient::OnMucStatusUpdate(const buzz::Jid& jid,
+    const buzz::MucPresenceStatus& status) {
+
+  // Look up this muc.
+  MucMap::iterator elem = mucs_.find(jid);
+  ASSERT(elem != mucs_.end());
+
+  buzz::Muc* muc = elem->second;
+
+  if (status.jid().IsBare() || status.jid() == muc->local_jid()) {
+    // We are only interested in status about other users.
+    return;
+  }
+
+  if (!status.available()) {
+    // Remove them from the room.
+    muc->members().erase(status.jid().resource());
+  }
+}
+
+bool CallClient::InMuc() {
+  const buzz::Jid* muc_jid = FirstMucJid();
+  if (!muc_jid) return false;
+  return muc_jid->IsValid();
+}
+
+const buzz::Jid* CallClient::FirstMucJid() {
+  if (mucs_.empty()) return NULL;
+  return &(mucs_.begin()->first);
+}
+
+void CallClient::LeaveMuc(const std::string& room) {
+  buzz::Jid room_jid;
+  const buzz::Jid* muc_jid = FirstMucJid();
+  if (room.length() > 0) {
+    room_jid = buzz::Jid(room);
+  } else if (mucs_.size() > 0) {
+    // leave the first MUC if no JID specified
+    if (muc_jid) {
+      room_jid = *(muc_jid);
+    }
+  }
+
+  if (!room_jid.IsValid()) {
+    console_->PrintLine("Invalid MUC JID.");
+    return;
+  }
+
+  MucMap::iterator elem = mucs_.find(room_jid);
+  if (elem == mucs_.end()) {
+    console_->PrintLine("No such MUC.");
+    return;
+  }
+
+  buzz::Muc* muc = elem->second;
+  muc->set_state(buzz::Muc::MUC_LEAVING);
+
+  buzz::PresenceStatus status;
+  status.set_jid(my_status_.jid());
+  status.set_available(false);
+  status.set_priority(0);
+  presence_out_->SendDirected(muc->local_jid(), status);
+}
+
+void CallClient::OnMucLeft(const buzz::Jid& endpoint, int error) {
+  // We could be kicked from a room from any state.  We would hope this
+  // happens While in the MUC_LEAVING state
+  MucMap::iterator elem = mucs_.find(endpoint);
+  if (elem == mucs_.end())
+    return;
+
+  buzz::Muc* muc = elem->second;
+  if (muc->state() == buzz::Muc::MUC_JOINING) {
+    console_->PrintLine("Failed to join \"%s\", code=%d",
+                        muc->jid().Str().c_str(), error);
+  } else if (muc->state() == buzz::Muc::MUC_JOINED) {
+    console_->PrintLine("Kicked from \"%s\"",
+                        muc->jid().Str().c_str());
+  }
+
+  delete muc;
+  mucs_.erase(elem);
+}
+
+void CallClient::InviteToMuc(const std::string& given_user,
+                             const std::string& room) {
+  std::string user = given_user;
+
+  // First find the room.
+  const buzz::Muc* found_muc;
+  if (room.length() == 0) {
+    if (mucs_.size() == 0) {
+      console_->PrintLine("Not in a room yet; can't invite.");
+      return;
+    }
+    // Invite to the first muc
+    found_muc = mucs_.begin()->second;
+  } else {
+    MucMap::iterator elem = mucs_.find(buzz::Jid(room));
+    if (elem == mucs_.end()) {
+      console_->PrintLine("Not in room %s.", room.c_str());
+      return;
+    }
+    found_muc = elem->second;
+  }
+
+  buzz::Jid invite_to = found_muc->jid();
+
+  // Now find the user. We invite all of their resources.
+  bool found_user = false;
+  buzz::Jid user_jid(user);
+  for (RosterMap::iterator iter = roster_->begin();
+       iter != roster_->end(); ++iter) {
+    if (iter->second.jid.BareEquals(user_jid)) {
+      buzz::Jid invitee = iter->second.jid;
+      muc_invite_send_->Send(invite_to, invitee);
+      found_user = true;
+    }
+  }
+  if (!found_user) {
+    buzz::Jid invitee = user_jid;
+    muc_invite_send_->Send(invite_to, invitee);
+  }
+}
+
+void CallClient::GetDevices() {
+  std::vector<std::string> names;
+  media_client_->GetAudioInputDevices(&names);
+  console_->PrintLine("Audio input devices:");
+  PrintDevices(names);
+  media_client_->GetAudioOutputDevices(&names);
+  console_->PrintLine("Audio output devices:");
+  PrintDevices(names);
+  media_client_->GetVideoCaptureDevices(&names);
+  console_->PrintLine("Video capture devices:");
+  PrintDevices(names);
+}
+
+void CallClient::PrintDevices(const std::vector<std::string>& names) {
+  for (size_t i = 0; i < names.size(); ++i) {
+    console_->PrintLine("%d: %s", static_cast<int>(i), names[i].c_str());
+  }
+}
+
+void CallClient::OnDevicesChange() {
+  console_->PrintLine("Devices changed.");
+  SetMediaCaps(media_client_->GetCapabilities(), &my_status_);
+  SendStatus(my_status_);
+}
+
+void CallClient::SetVolume(const std::string& level) {
+  media_client_->SetOutputVolume(strtol(level.c_str(), NULL, 10));
+}
+
+void CallClient::OnMediaStreamsUpdate(cricket::Call* call,
+                                      cricket::Session* session,
+                                      const cricket::MediaStreams& added,
+                                      const cricket::MediaStreams& removed) {
+  if (call && call->has_video()) {
+    for (std::vector<cricket::StreamParams>::const_iterator
+         it = removed.video().begin(); it != removed.video().end(); ++it) {
+      RemoveStaticRenderedView(it->first_ssrc());
+    }
+
+    if (render_) {
+      RenderStreams(call, session, added.video(), true);
+    }
+    SendViewRequest(call, session);
+  }
+}
+
+void CallClient::RenderAllStreams(cricket::Call* call,
+                                  cricket::Session* session,
+                                  bool enable) {
+  const std::vector<cricket::StreamParams>* video_streams =
+      call->GetVideoRecvStreams(session);
+  if (video_streams) {
+    RenderStreams(call, session, *video_streams, enable);
+  }
+}
+
+void CallClient::RenderStreams(
+    cricket::Call* call,
+    cricket::Session* session,
+    const std::vector<cricket::StreamParams>& video_streams,
+    bool enable) {
+  std::vector<cricket::StreamParams>::const_iterator stream;
+  for (stream = video_streams.begin(); stream != video_streams.end();
+       ++stream) {
+    RenderStream(call, session, *stream, enable);
+  }
+}
+
+void CallClient::RenderStream(cricket::Call* call,
+                              cricket::Session* session,
+                              const cricket::StreamParams& stream,
+                              bool enable) {
+  if (!stream.has_ssrcs()) {
+    // Nothing to see here; move along.
+    return;
+  }
+
+  uint32 ssrc = stream.first_ssrc();
+  StaticRenderedViews::iterator iter =
+      static_rendered_views_.find(std::make_pair(session, ssrc));
+  if (enable) {
+    if (iter == static_rendered_views_.end()) {
+      // TODO(pthatcher): Make dimensions and positions more configurable.
+      int offset = (50 * static_views_accumulated_count_) % 300;
+      AddStaticRenderedView(session, ssrc, 640, 400, 30,
+                            offset, offset);
+      // Should have it now.
+      iter = static_rendered_views_.find(std::make_pair(session, ssrc));
+    }
+    call->SetVideoRenderer(session, ssrc, iter->second.renderer);
+  } else {
+    if (iter != static_rendered_views_.end()) {
+      call->SetVideoRenderer(session, ssrc, NULL);
+      RemoveStaticRenderedView(ssrc);
+    }
+  }
+}
+
+// TODO: Would these methods to add and remove views make
+// more sense in call.cc?  Would other clients use them?
+void CallClient::AddStaticRenderedView(
+    cricket::Session* session,
+    uint32 ssrc, int width, int height, int framerate,
+    int x_offset, int y_offset) {
+  StaticRenderedView rendered_view(
+      cricket::StaticVideoView(
+          cricket::StreamSelector(ssrc), width, height, framerate),
+      cricket::VideoRendererFactory::CreateGuiVideoRenderer(
+          x_offset, y_offset));
+  rendered_view.renderer->SetSize(width, height, 0);
+  static_rendered_views_.insert(std::make_pair(std::make_pair(session, ssrc),
+                                               rendered_view));
+  ++static_views_accumulated_count_;
+  console_->PrintLine("Added renderer for ssrc %d", ssrc);
+}
+
+bool CallClient::RemoveStaticRenderedView(uint32 ssrc) {
+  for (StaticRenderedViews::iterator it = static_rendered_views_.begin();
+       it != static_rendered_views_.end(); ++it) {
+    if (it->second.view.selector.ssrc == ssrc) {
+      delete it->second.renderer;
+      static_rendered_views_.erase(it);
+      console_->PrintLine("Removed renderer for ssrc %d", ssrc);
+      return true;
+    }
+  }
+  return false;
+}
+
+void CallClient::RemoveCallsStaticRenderedViews(cricket::Call* call) {
+  std::vector<cricket::Session*>& sessions = sessions_[call->id()];
+  std::set<cricket::Session*> call_sessions(sessions.begin(), sessions.end());
+  for (StaticRenderedViews::iterator it = static_rendered_views_.begin();
+       it != static_rendered_views_.end(); ) {
+    if (call_sessions.find(it->first.first) != call_sessions.end()) {
+      delete it->second.renderer;
+      static_rendered_views_.erase(it++);
+    } else {
+      ++it;
+    }
+  }
+}
+
+void CallClient::SendViewRequest(cricket::Call* call,
+                                 cricket::Session* session) {
+  cricket::ViewRequest request;
+  for (StaticRenderedViews::iterator it = static_rendered_views_.begin();
+       it != static_rendered_views_.end(); ++it) {
+    if (it->first.first == session) {
+      request.static_video_views.push_back(it->second.view);
+    }
+  }
+  call->SendViewRequest(session, request);
+}
+
+buzz::Jid CallClient::GenerateRandomMucJid() {
+  // Generate a GUID of the form XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX,
+  // for an eventual JID of private-chat-<GUID>@groupchat.google.com.
+  char guid[37], guid_room[256];
+  for (size_t i = 0; i < ARRAY_SIZE(guid) - 1;) {
+    if (i == 8 || i == 13 || i == 18 || i == 23) {
+      guid[i++] = '-';
+    } else {
+      sprintf(guid + i, "%04x", rand());
+      i += 4;
+    }
+  }
+
+  talk_base::sprintfn(guid_room,
+                      ARRAY_SIZE(guid_room),
+                      "private-chat-%s@%s",
+                      guid,
+                      pmuc_domain_.c_str());
+  return buzz::Jid(guid_room);
+}
+
+bool CallClient::SelectFirstDesktopScreencastId(
+    cricket::ScreencastId* screencastid) {
+  if (!talk_base::WindowPickerFactory::IsSupported()) {
+    LOG(LS_WARNING) << "Window picker not suported on this OS.";
+    return false;
+  }
+
+  talk_base::WindowPicker* picker =
+      talk_base::WindowPickerFactory::CreateWindowPicker();
+  if (!picker) {
+    LOG(LS_WARNING) << "Could not create a window picker.";
+    return false;
+  }
+
+  talk_base::DesktopDescriptionList desktops;
+  if (!picker->GetDesktopList(&desktops) || desktops.empty()) {
+    LOG(LS_WARNING) << "Could not get a list of desktops.";
+    return false;
+  }
+
+  *screencastid = cricket::ScreencastId(desktops[0].id());
+  return true;
+}
+
+void CallClient::PrintStats() const {
+  const cricket::VoiceMediaInfo& vmi = call_->last_voice_media_info();
+
+  for (std::vector<cricket::VoiceSenderInfo>::const_iterator it =
+       vmi.senders.begin(); it != vmi.senders.end(); ++it) {
+    console_->PrintLine("Sender: ssrc=%u codec='%s' bytes=%d packets=%d "
+                        "rtt=%d jitter=%d",
+                        it->ssrc, it->codec_name.c_str(), it->bytes_sent,
+                        it->packets_sent, it->rtt_ms, it->jitter_ms);
+  }
+
+  for (std::vector<cricket::VoiceReceiverInfo>::const_iterator it =
+       vmi.receivers.begin(); it != vmi.receivers.end(); ++it) {
+    console_->PrintLine("Receiver: ssrc=%u bytes=%d packets=%d "
+                        "jitter=%d loss=%.2f",
+                        it->ssrc, it->bytes_rcvd, it->packets_rcvd,
+                        it->jitter_ms, it->fraction_lost);
+  }
+}
diff --git a/talk/examples/call/callclient.h b/talk/examples/call/callclient.h
new file mode 100644
index 0000000..39a5b11
--- /dev/null
+++ b/talk/examples/call/callclient.h
@@ -0,0 +1,352 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_EXAMPLES_CALL_CALLCLIENT_H_
+#define TALK_EXAMPLES_CALL_CALLCLIENT_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sslidentity.h"
+#include "talk/examples/call/console.h"
+#include "talk/media/base/mediachannel.h"
+#include "talk/p2p/base/session.h"
+#include "talk/session/media/mediamessages.h"
+#include "talk/session/media/mediasessionclient.h"
+#include "talk/xmpp/hangoutpubsubclient.h"
+#include "talk/xmpp/presencestatus.h"
+#include "talk/xmpp/xmppclient.h"
+
+namespace buzz {
+class PresencePushTask;
+class PresenceOutTask;
+class MucInviteRecvTask;
+class MucInviteSendTask;
+class FriendInviteSendTask;
+class DiscoInfoQueryTask;
+class Muc;
+class PresenceStatus;
+class IqTask;
+class MucRoomConfigTask;
+class MucRoomLookupTask;
+class MucPresenceStatus;
+class XmlElement;
+class HangoutPubSubClient;
+struct AvailableMediaEntry;
+struct MucRoomInfo;
+}  // namespace buzz
+
+namespace talk_base {
+class Thread;
+class NetworkManager;
+}  // namespace talk_base
+
+namespace cricket {
+class PortAllocator;
+class MediaEngineInterface;
+class MediaSessionClient;
+class Call;
+class SessionManagerTask;
+struct CallOptions;
+struct MediaStreams;
+struct StreamParams;
+}  // namespace cricket
+
+struct RosterItem {
+  buzz::Jid jid;
+  buzz::PresenceStatus::Show show;
+  std::string status;
+};
+
+struct StaticRenderedView {
+  StaticRenderedView(const cricket::StaticVideoView& view,
+                     cricket::VideoRenderer* renderer) :
+      view(view),
+      renderer(renderer) {
+  }
+
+  cricket::StaticVideoView view;
+  cricket::VideoRenderer* renderer;
+};
+
+// Maintain a mapping of (session, ssrc) to rendered view.
+typedef std::map<std::pair<cricket::Session*, uint32>,
+                 StaticRenderedView> StaticRenderedViews;
+
+class CallClient: public sigslot::has_slots<> {
+ public:
+  CallClient(buzz::XmppClient* xmpp_client,
+             const std::string& caps_node,
+             const std::string& version);
+  ~CallClient();
+
+  cricket::MediaSessionClient* media_client() const { return media_client_; }
+  void SetMediaEngine(cricket::MediaEngineInterface* media_engine) {
+    media_engine_ = media_engine;
+  }
+  void SetAutoAccept(bool auto_accept) {
+    auto_accept_ = auto_accept;
+  }
+  void SetPmucDomain(const std::string &pmuc_domain) {
+    pmuc_domain_ = pmuc_domain;
+  }
+  void SetRender(bool render) {
+    render_ = render;
+  }
+  void SetDataChannelType(cricket::DataChannelType data_channel_type) {
+    data_channel_type_ = data_channel_type;
+  }
+  void SetMultiSessionEnabled(bool multisession_enabled) {
+    multisession_enabled_ = multisession_enabled;
+  }
+  void SetConsole(Console *console) {
+    console_ = console;
+  }
+  void SetPriority(int priority) {
+    my_status_.set_priority(priority);
+  }
+  void SendStatus() {
+    SendStatus(my_status_);
+  }
+  void SendStatus(const buzz::PresenceStatus& status);
+
+  void ParseLine(const std::string &str);
+
+  void SendChat(const std::string& to, const std::string msg);
+  void SendData(const std::string& stream_name,
+                const std::string& text);
+  void InviteFriend(const std::string& user);
+  void JoinMuc(const buzz::Jid& room_jid);
+  void JoinMuc(const std::string& room_jid_str);
+  void LookupAndJoinMuc(const std::string& room_name);
+  void InviteToMuc(const std::string& user, const std::string& room);
+  bool InMuc();
+  const buzz::Jid* FirstMucJid();
+  void LeaveMuc(const std::string& room);
+  void SetNick(const std::string& muc_nick);
+  void SetPortAllocatorFlags(uint32 flags) { portallocator_flags_ = flags; }
+  void SetAllowLocalIps(bool allow_local_ips) {
+    allow_local_ips_ = allow_local_ips;
+  }
+
+  void SetSignalingProtocol(cricket::SignalingProtocol protocol) {
+    signaling_protocol_ = protocol;
+  }
+  void SetTransportProtocol(cricket::TransportProtocol protocol) {
+    transport_protocol_ = protocol;
+  }
+  void SetSecurePolicy(cricket::SecurePolicy sdes_policy,
+                       cricket::SecurePolicy dtls_policy) {
+    sdes_policy_ = sdes_policy;
+    dtls_policy_ = dtls_policy;
+  }
+  void SetSslIdentity(talk_base::SSLIdentity* identity) {
+    ssl_identity_.reset(identity);
+  }
+
+  typedef std::map<buzz::Jid, buzz::Muc*> MucMap;
+
+  const MucMap& mucs() const {
+    return mucs_;
+  }
+
+  void SetShowRosterMessages(bool show_roster_messages) {
+    show_roster_messages_ = show_roster_messages;
+  }
+
+ private:
+  void AddStream(uint32 audio_src_id, uint32 video_src_id);
+  void RemoveStream(uint32 audio_src_id, uint32 video_src_id);
+  void OnStateChange(buzz::XmppEngine::State state);
+
+  void InitMedia();
+  void InitPresence();
+  void StartXmppPing();
+  void OnPingTimeout();
+  void OnRequestSignaling();
+  void OnSessionCreate(cricket::Session* session, bool initiate);
+  void OnCallCreate(cricket::Call* call);
+  void OnCallDestroy(cricket::Call* call);
+  void OnSessionState(cricket::Call* call,
+                      cricket::Session* session,
+                      cricket::Session::State state);
+  void OnStatusUpdate(const buzz::PresenceStatus& status);
+  void OnMucInviteReceived(const buzz::Jid& inviter, const buzz::Jid& room,
+      const std::vector<buzz::AvailableMediaEntry>& avail);
+  void OnMucJoined(const buzz::Jid& endpoint);
+  void OnMucStatusUpdate(const buzz::Jid& jid,
+                         const buzz::MucPresenceStatus& status);
+  void OnMucLeft(const buzz::Jid& endpoint, int error);
+  void OnPresenterStateChange(const std::string& nick,
+                              bool was_presenting, bool is_presenting);
+  void OnAudioMuteStateChange(const std::string& nick,
+                              bool was_muted, bool is_muted);
+  void OnRecordingStateChange(const std::string& nick,
+                              bool was_recording, bool is_recording);
+  void OnRemoteMuted(const std::string& mutee_nick,
+                     const std::string& muter_nick,
+                     bool should_mute_locally);
+  void OnMediaBlocked(const std::string& blockee_nick,
+                      const std::string& blocker_nick);
+  void OnHangoutRequestError(const std::string& node,
+                             const buzz::XmlElement* stanza);
+  void OnHangoutPublishAudioMuteError(const std::string& task_id,
+                                      const buzz::XmlElement* stanza);
+  void OnHangoutPublishPresenterError(const std::string& task_id,
+                                      const buzz::XmlElement* stanza);
+  void OnHangoutPublishRecordingError(const std::string& task_id,
+                                      const buzz::XmlElement* stanza);
+  void OnHangoutRemoteMuteError(const std::string& task_id,
+                                const std::string& mutee_nick,
+                                const buzz::XmlElement* stanza);
+  void OnDevicesChange();
+  void OnMediaStreamsUpdate(cricket::Call* call,
+                            cricket::Session* session,
+                            const cricket::MediaStreams& added,
+                            const cricket::MediaStreams& removed);
+  void OnSpeakerChanged(cricket::Call* call,
+                        cricket::Session* session,
+                        const cricket::StreamParams& speaker_stream);
+  void OnRoomLookupResponse(buzz::MucRoomLookupTask* task,
+                            const buzz::MucRoomInfo& room_info);
+  void OnRoomLookupError(buzz::IqTask* task,
+                         const buzz::XmlElement* stanza);
+  void OnRoomConfigResult(buzz::MucRoomConfigTask* task);
+  void OnRoomConfigError(buzz::IqTask* task,
+                         const buzz::XmlElement* stanza);
+  void OnDataReceived(cricket::Call*,
+                      const cricket::ReceiveDataParams& params,
+                      const talk_base::Buffer& payload);
+  buzz::Jid GenerateRandomMucJid();
+
+  // Depending on |enable|, render (or don't) all the streams in |session|.
+  void RenderAllStreams(cricket::Call* call,
+                        cricket::Session* session,
+                        bool enable);
+
+  // Depending on |enable|, render (or don't) the streams in |video_streams|.
+  void RenderStreams(cricket::Call* call,
+                     cricket::Session* session,
+                     const std::vector<cricket::StreamParams>& video_streams,
+                     bool enable);
+
+  // Depending on |enable|, render (or don't) the supplied |stream|.
+  void RenderStream(cricket::Call* call,
+                    cricket::Session* session,
+                    const cricket::StreamParams& stream,
+                    bool enable);
+  void AddStaticRenderedView(
+      cricket::Session* session,
+      uint32 ssrc, int width, int height, int framerate,
+      int x_offset, int y_offset);
+  bool RemoveStaticRenderedView(uint32 ssrc);
+  void RemoveCallsStaticRenderedViews(cricket::Call* call);
+  void SendViewRequest(cricket::Call* call, cricket::Session* session);
+  bool SelectFirstDesktopScreencastId(cricket::ScreencastId* screencastid);
+
+  static const std::string strerror(buzz::XmppEngine::Error err);
+
+  void PrintRoster();
+  bool FindJid(const std::string& name,
+               buzz::Jid* found_jid,
+               cricket::CallOptions* options);
+  bool PlaceCall(const std::string& name, cricket::CallOptions options);
+  bool InitiateAdditionalSession(const std::string& name,
+                                 cricket::CallOptions options);
+  void TerminateAndRemoveSession(cricket::Call* call, const std::string& id);
+  void PrintCalls();
+  void SwitchToCall(uint32 call_id);
+  void Accept(const cricket::CallOptions& options);
+  void Reject();
+  void Quit();
+
+  void GetDevices();
+  void PrintDevices(const std::vector<std::string>& names);
+
+  void SetVolume(const std::string& level);
+
+  cricket::Session* GetFirstSession() { return sessions_[call_->id()][0]; }
+  void AddSession(cricket::Session* session) {
+    sessions_[call_->id()].push_back(session);
+  }
+
+  void PrintStats() const;
+  void SetupAcceptedCall();
+
+  typedef std::map<std::string, RosterItem> RosterMap;
+
+  Console *console_;
+  buzz::XmppClient* xmpp_client_;
+  talk_base::Thread* worker_thread_;
+  talk_base::NetworkManager* network_manager_;
+  cricket::PortAllocator* port_allocator_;
+  cricket::SessionManager* session_manager_;
+  cricket::SessionManagerTask* session_manager_task_;
+  cricket::MediaEngineInterface* media_engine_;
+  cricket::DataEngineInterface* data_engine_;
+  cricket::MediaSessionClient* media_client_;
+  MucMap mucs_;
+
+  cricket::Call* call_;
+  typedef std::map<uint32, std::vector<cricket::Session *> > SessionMap;
+  SessionMap sessions_;
+
+  buzz::HangoutPubSubClient* hangout_pubsub_client_;
+  bool incoming_call_;
+  bool auto_accept_;
+  std::string pmuc_domain_;
+  bool render_;
+  cricket::DataChannelType data_channel_type_;
+  bool multisession_enabled_;
+  cricket::VideoRenderer* local_renderer_;
+  StaticRenderedViews static_rendered_views_;
+  uint32 static_views_accumulated_count_;
+  uint32 screencast_ssrc_;
+
+  buzz::PresenceStatus my_status_;
+  buzz::PresencePushTask* presence_push_;
+  buzz::PresenceOutTask* presence_out_;
+  buzz::MucInviteRecvTask* muc_invite_recv_;
+  buzz::MucInviteSendTask* muc_invite_send_;
+  buzz::FriendInviteSendTask* friend_invite_send_;
+  RosterMap* roster_;
+  uint32 portallocator_flags_;
+
+  bool allow_local_ips_;
+  cricket::SignalingProtocol signaling_protocol_;
+  cricket::TransportProtocol transport_protocol_;
+  cricket::SecurePolicy sdes_policy_;
+  cricket::SecurePolicy dtls_policy_;
+  talk_base::scoped_ptr<talk_base::SSLIdentity> ssl_identity_;
+  std::string last_sent_to_;
+
+  bool show_roster_messages_;
+};
+
+#endif  // TALK_EXAMPLES_CALL_CALLCLIENT_H_
diff --git a/talk/examples/call/callclient_unittest.cc b/talk/examples/call/callclient_unittest.cc
new file mode 100644
index 0000000..b0e9d89
--- /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/media/base/filemediaengine.h"
+#include "talk/media/base/mediaengine.h"
+#include "talk/xmpp/xmppthread.h"
+
+TEST(CallClientTest, CreateCallClientWithDefaultMediaEngine) {
+  buzz::XmppPump pump;
+  CallClient *client = new CallClient(pump.client(), "app", "version");
+  delete client;
+}
+
+TEST(CallClientTest, CreateCallClientWithFileMediaEngine) {
+  buzz::XmppPump pump;
+  CallClient *client = new CallClient(pump.client(), "app", "version");
+  client->SetMediaEngine(new cricket::FileMediaEngine);
+  delete client;
+}
diff --git a/talk/examples/call/console.cc b/talk/examples/call/console.cc
new file mode 100644
index 0000000..dec3b4a
--- /dev/null
+++ b/talk/examples/call/console.cc
@@ -0,0 +1,165 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define _CRT_SECURE_NO_DEPRECATE 1
+
+#ifdef POSIX
+#include <signal.h>
+#include <termios.h>
+#include <unistd.h>
+#endif  // POSIX
+#include <cassert>
+#include "talk/base/logging.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/stringutils.h"
+#include "talk/examples/call/console.h"
+#include "talk/examples/call/callclient.h"
+
+#ifdef POSIX
+static void DoNothing(int unused) {}
+#endif
+
+Console::Console(talk_base::Thread *thread, CallClient *client) :
+  client_(client),
+  client_thread_(thread),
+  console_thread_(new talk_base::Thread()) {}
+
+Console::~Console() {
+  Stop();
+}
+
+void Console::Start() {
+  if (!console_thread_) {
+    // stdin was closed in Stop(), so we can't restart.
+    LOG(LS_ERROR) << "Cannot re-start";
+    return;
+  }
+  if (console_thread_->started()) {
+    LOG(LS_WARNING) << "Already started";
+    return;
+  }
+  console_thread_->Start();
+  console_thread_->Post(this, MSG_START);
+}
+
+void Console::Stop() {
+  if (console_thread_ && console_thread_->started()) {
+#ifdef WIN32
+    CloseHandle(GetStdHandle(STD_INPUT_HANDLE));
+#else
+    close(fileno(stdin));
+    // This forces the read() in fgets() to return with errno = EINTR. fgets()
+    // will retry the read() and fail, thus returning.
+    pthread_kill(console_thread_->GetPThread(), SIGUSR1);
+#endif
+    console_thread_->Stop();
+    console_thread_.reset();
+  }
+}
+
+void Console::SetEcho(bool on) {
+#ifdef WIN32
+  HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
+  if ((hIn == INVALID_HANDLE_VALUE) || (hIn == NULL))
+    return;
+
+  DWORD mode;
+  if (!GetConsoleMode(hIn, &mode))
+    return;
+
+  if (on) {
+    mode = mode | ENABLE_ECHO_INPUT;
+  } else {
+    mode = mode & ~ENABLE_ECHO_INPUT;
+  }
+
+  SetConsoleMode(hIn, mode);
+#else
+  const int fd = fileno(stdin);
+  if (fd == -1)
+   return;
+
+  struct termios tcflags;
+  if (tcgetattr(fd, &tcflags) == -1)
+    return;
+
+  if (on) {
+    tcflags.c_lflag |= ECHO;
+  } else {
+    tcflags.c_lflag &= ~ECHO;
+  }
+
+  tcsetattr(fd, TCSANOW, &tcflags);
+#endif
+}
+
+void Console::PrintLine(const char* format, ...) {
+  va_list ap;
+  va_start(ap, format);
+
+  char buf[4096];
+  int size = vsnprintf(buf, sizeof(buf), format, ap);
+  assert(size >= 0);
+  assert(size < static_cast<int>(sizeof(buf)));
+  buf[size] = '\0';
+  printf("%s\n", buf);
+  fflush(stdout);
+
+  va_end(ap);
+}
+
+void Console::RunConsole() {
+  char input_buffer[128];
+  while (fgets(input_buffer, sizeof(input_buffer), stdin) != NULL) {
+    client_thread_->Post(this, MSG_INPUT,
+        new talk_base::TypedMessageData<std::string>(input_buffer));
+  }
+}
+
+void Console::OnMessage(talk_base::Message *msg) {
+  switch (msg->message_id) {
+    case MSG_START:
+#ifdef POSIX
+      // Install a no-op signal so that we can abort RunConsole() by raising
+      // SIGUSR1.
+      struct sigaction act;
+      act.sa_handler = &DoNothing;
+      sigemptyset(&act.sa_mask);
+      act.sa_flags = 0;
+      if (sigaction(SIGUSR1, &act, NULL) < 0) {
+        LOG(LS_WARNING) << "Can't install signal";
+      }
+#endif
+      RunConsole();
+      break;
+    case MSG_INPUT:
+      talk_base::TypedMessageData<std::string> *data =
+          static_cast<talk_base::TypedMessageData<std::string>*>(msg->pdata);
+      client_->ParseLine(data->data());
+      break;
+  }
+}
diff --git a/talk/examples/call/console.h b/talk/examples/call/console.h
new file mode 100644
index 0000000..4a90a7f
--- /dev/null
+++ b/talk/examples/call/console.h
@@ -0,0 +1,69 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_EXAMPLES_CALL_CONSOLE_H_
+#define TALK_EXAMPLES_CALL_CONSOLE_H_
+
+#include <cstdio>
+
+#include "talk/base/thread.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/scoped_ptr.h"
+
+class CallClient;
+
+class Console : public talk_base::MessageHandler {
+ public:
+  Console(talk_base::Thread *thread, CallClient *client);
+  ~Console();
+
+  // Starts reading lines from the console and giving them to the CallClient.
+  void Start();
+  // Stops reading lines. Cannot be restarted.
+  void Stop();
+
+  virtual void OnMessage(talk_base::Message *msg);
+
+  void PrintLine(const char* format, ...);
+
+  static void SetEcho(bool on);
+
+ private:
+  enum {
+    MSG_START,
+    MSG_INPUT,
+  };
+
+  void RunConsole();
+  void ParseLine(std::string &str);
+
+  CallClient *client_;
+  talk_base::Thread *client_thread_;
+  talk_base::scoped_ptr<talk_base::Thread> console_thread_;
+};
+
+#endif // TALK_EXAMPLES_CALL_CONSOLE_H_
diff --git a/talk/examples/call/friendinvitesendtask.cc b/talk/examples/call/friendinvitesendtask.cc
new file mode 100644
index 0000000..cdb0b2c
--- /dev/null
+++ b/talk/examples/call/friendinvitesendtask.cc
@@ -0,0 +1,76 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/xmpp/constants.h"
+#include "talk/examples/call/friendinvitesendtask.h"
+
+namespace buzz {
+
+XmppReturnStatus
+FriendInviteSendTask::Send(const Jid& user) {
+  if (GetState() != STATE_INIT && GetState() != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+
+  // Need to first add to roster, then subscribe to presence.
+  XmlElement* iq = new XmlElement(QN_IQ);
+  iq->AddAttr(QN_TYPE, STR_SET);
+  XmlElement* query = new XmlElement(QN_ROSTER_QUERY);
+  XmlElement* item = new XmlElement(QN_ROSTER_ITEM);
+  item->AddAttr(QN_JID, user.Str());
+  item->AddAttr(QN_NAME, user.node());
+  query->AddElement(item);
+  iq->AddElement(query);
+  QueueStanza(iq);
+
+  // Subscribe to presence
+  XmlElement* presence = new XmlElement(QN_PRESENCE);
+  presence->AddAttr(QN_TO, user.Str());
+  presence->AddAttr(QN_TYPE, STR_SUBSCRIBE);
+  XmlElement* invitation = new XmlElement(QN_INVITATION);
+  invitation->AddAttr(QN_INVITE_MESSAGE,
+      "I've been using Google Talk and thought you might like to try it out. "
+      "We can use it to call each other for free over the internet. Here's an "
+      "invitation to download Google Talk. Give it a try!");
+  presence->AddElement(invitation);
+  QueueStanza(presence);
+
+  return XMPP_RETURN_OK;
+}
+
+int
+FriendInviteSendTask::ProcessStart() {
+  const XmlElement* stanza = NextStanza();
+  if (stanza == NULL)
+    return STATE_BLOCKED;
+
+  if (SendStanza(stanza) != XMPP_RETURN_OK)
+    return STATE_ERROR;
+
+  return STATE_START;
+}
+
+}
diff --git a/talk/examples/call/friendinvitesendtask.h b/talk/examples/call/friendinvitesendtask.h
new file mode 100644
index 0000000..625f077
--- /dev/null
+++ b/talk/examples/call/friendinvitesendtask.h
@@ -0,0 +1,49 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _FRIENDINVITESENDTASK_H_
+#define _FRIENDINVITESENDTASK_H_
+
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+
+class FriendInviteSendTask : public XmppTask {
+public:
+  explicit FriendInviteSendTask(XmppTaskParentInterface* parent)
+    : XmppTask(parent) {}
+  virtual ~FriendInviteSendTask() {}
+
+  XmppReturnStatus Send(const Jid& user);
+
+  virtual int ProcessStart();
+};
+
+}
+
+#endif
diff --git a/talk/examples/call/mediaenginefactory.cc b/talk/examples/call/mediaenginefactory.cc
new file mode 100644
index 0000000..983345d
--- /dev/null
+++ b/talk/examples/call/mediaenginefactory.cc
@@ -0,0 +1,81 @@
+//
+// libjingle
+// Copyright 2004--2007, Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//  1. Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//  3. The name of the author may not be used to endorse or promote products
+//     derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include "talk/examples/call/mediaenginefactory.h"
+
+#include "talk/base/stringutils.h"
+#include "talk/media/base/fakemediaengine.h"
+#include "talk/media/base/filemediaengine.h"
+#include "talk/media/base/mediaengine.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..90407f9
--- /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/media/base/mediaengine.h"
+
+class MediaEngineFactory {
+ public:
+  static cricket::MediaEngineInterface* CreateFileMediaEngine(
+      const char* voice_in, const char* voice_out,
+      const char* video_in, const char* video_out);
+};
+
+#endif  // TALK_EXAMPLES_CALL_MEDIAENGINEFACTORY_H_
diff --git a/talk/examples/call/muc.h b/talk/examples/call/muc.h
new file mode 100644
index 0000000..0e937ca
--- /dev/null
+++ b/talk/examples/call/muc.h
@@ -0,0 +1,66 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _MUC_H_
+#define _MUC_H_
+
+#include <map>
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/presencestatus.h"
+
+namespace buzz {
+
+class Muc {
+ public:
+   Muc(const Jid& jid, const std::string& nick) : state_(MUC_JOINING),
+       jid_(jid), local_jid_(Jid(jid.Str() + "/" + nick)) {}
+  ~Muc() {};
+
+  enum State { MUC_JOINING, MUC_JOINED, MUC_LEAVING };
+  State state() const { return state_; }
+  void set_state(State state) { state_ = state; }
+  const Jid & jid() const { return jid_; }
+  const Jid & local_jid() const { return local_jid_; }
+
+  typedef std::map<std::string, MucPresenceStatus> MemberMap;
+
+  // All the intelligence about how to manage the members is in
+  // CallClient, so we completely expose the map.
+  MemberMap& members() {
+    return members_;
+  }
+
+private:
+  State state_;
+  Jid jid_;
+  Jid local_jid_;
+  MemberMap members_;
+};
+
+}
+
+#endif
diff --git a/talk/examples/call/mucinviterecvtask.cc b/talk/examples/call/mucinviterecvtask.cc
new file mode 100644
index 0000000..061db74
--- /dev/null
+++ b/talk/examples/call/mucinviterecvtask.cc
@@ -0,0 +1,124 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/xmpp/constants.h"
+#include "talk/examples/call/mucinviterecvtask.h"
+
+namespace buzz {
+
+const char* types[] = {
+  "unknown",
+  "audio",
+  "video",
+};
+
+const char* statuses[] = {
+  "unknown",
+  "sendrecv",
+  "sendonly",
+  "recvonly",
+  "inactive",
+};
+
+const char*
+AvailableMediaEntry::TypeAsString(type_t type) {
+  // The values of the constants have been chosen such that this is correct.
+  return types[type];
+}
+
+const char*
+AvailableMediaEntry::StatusAsString(status_t status) {
+  // The values of the constants have been chosen such that this is correct.
+  return statuses[status];
+}
+
+int bodytext_to_array_pos(const XmlElement* elem, const char* array[],
+    int len, int defval = -1) {
+  if (elem) {
+    const std::string& body(elem->BodyText());
+    for (int i = 0; i < len; ++i) {
+      if (body == array[i]) {
+        // Found it.
+        return i;
+      }
+    }
+  }
+  // If we get here, it's not any value in the array.
+  return defval;
+}
+
+bool
+MucInviteRecvTask::HandleStanza(const XmlElement* stanza) {
+  // Figuring out that we want to handle this is a lot of the work of
+  // actually handling it, so we handle it right here instead of queueing it.
+  const XmlElement* xstanza;
+  const XmlElement* invite;
+  if (stanza->Name() != QN_MESSAGE) return false;
+  xstanza = stanza->FirstNamed(QN_MUC_USER_X);
+  if (!xstanza) return false;
+  invite = xstanza->FirstNamed(QN_MUC_USER_INVITE);
+  if (!invite) return false;
+  // Else it's an invite and we definitely want to handle it. Parse the
+  // available-media, if any.
+  std::vector<AvailableMediaEntry> v;
+  const XmlElement* avail =
+    invite->FirstNamed(QN_GOOGLE_MUC_USER_AVAILABLE_MEDIA);
+  if (avail) {
+    for (const XmlElement* entry = avail->FirstNamed(QN_GOOGLE_MUC_USER_ENTRY);
+        entry;
+        entry = entry->NextNamed(QN_GOOGLE_MUC_USER_ENTRY)) {
+      AvailableMediaEntry tmp;
+      // In the interest of debugging, we accept as much valid-looking data
+      // as we can.
+      tmp.label = atoi(entry->Attr(QN_LABEL).c_str());
+      tmp.type = static_cast<AvailableMediaEntry::type_t>(
+          bodytext_to_array_pos(
+              entry->FirstNamed(QN_GOOGLE_MUC_USER_TYPE),
+              types,
+              sizeof(types)/sizeof(const char*),
+              AvailableMediaEntry::TYPE_UNKNOWN));
+      tmp.status = static_cast<AvailableMediaEntry::status_t>(
+          bodytext_to_array_pos(
+              entry->FirstNamed(QN_GOOGLE_MUC_USER_STATUS),
+              statuses,
+              sizeof(statuses)/sizeof(const char*),
+              AvailableMediaEntry::STATUS_UNKNOWN));
+      v.push_back(tmp);
+    }
+  }
+  SignalInviteReceived(Jid(invite->Attr(QN_FROM)), Jid(stanza->Attr(QN_FROM)),
+      v);
+  return true;
+}
+
+int
+MucInviteRecvTask::ProcessStart() {
+  // We never queue anything so we are always blocked.
+  return STATE_BLOCKED;
+}
+
+}
diff --git a/talk/examples/call/mucinviterecvtask.h b/talk/examples/call/mucinviterecvtask.h
new file mode 100644
index 0000000..24f05e0
--- /dev/null
+++ b/talk/examples/call/mucinviterecvtask.h
@@ -0,0 +1,82 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _MUCINVITERECVTASK_H_
+#define _MUCINVITERECVTASK_H_
+
+#include <vector>
+
+#include "talk/base/sigslot.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+
+struct AvailableMediaEntry {
+  enum type_t {
+    // SIP defines other media types, but these are the only ones we use in
+    // multiway jingle.
+    // These numbers are important; see .cc file
+    TYPE_UNKNOWN = 0, // indicates invalid string
+    TYPE_AUDIO = 1,
+    TYPE_VIDEO = 2,
+  };
+
+  enum status_t {
+    // These numbers are important; see .cc file
+    STATUS_UNKNOWN = 0, // indicates invalid string
+    STATUS_SENDRECV = 1,
+    STATUS_SENDONLY = 2,
+    STATUS_RECVONLY = 3,
+    STATUS_INACTIVE = 4,
+  };
+
+  uint32 label;
+  type_t type;
+  status_t status;
+
+  static const char* TypeAsString(type_t type);
+  static const char* StatusAsString(status_t status);
+};
+
+class MucInviteRecvTask : public XmppTask {
+ public:
+  explicit MucInviteRecvTask(XmppTaskParentInterface* parent)
+      : XmppTask(parent, XmppEngine::HL_TYPE) {}
+  virtual int ProcessStart();
+
+  // First arg is inviter's JID; second is MUC's JID.
+  sigslot::signal3<const Jid&, const Jid&, const std::vector<AvailableMediaEntry>& > SignalInviteReceived;
+
+ protected:
+  virtual bool HandleStanza(const XmlElement* stanza);
+
+};
+
+}
+
+#endif
diff --git a/talk/examples/call/mucinvitesendtask.cc b/talk/examples/call/mucinvitesendtask.cc
new file mode 100644
index 0000000..d648fef
--- /dev/null
+++ b/talk/examples/call/mucinvitesendtask.cc
@@ -0,0 +1,63 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/examples/call/mucinvitesendtask.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/xmppclient.h"
+
+namespace buzz {
+
+XmppReturnStatus
+MucInviteSendTask::Send(const Jid& to, const Jid& invitee) {
+  if (GetState() != STATE_INIT && GetState() != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+
+  XmlElement* message = new XmlElement(QN_MESSAGE);
+  message->AddAttr(QN_TO, to.Str());
+  XmlElement* xstanza = new XmlElement(QN_MUC_USER_X);
+  XmlElement* invite = new XmlElement(QN_MUC_USER_INVITE);
+  invite->AddAttr(QN_TO, invitee.Str());
+  xstanza->AddElement(invite);
+  message->AddElement(xstanza);
+
+  QueueStanza(message);
+  return XMPP_RETURN_OK;
+}
+
+int
+MucInviteSendTask::ProcessStart() {
+  const XmlElement* stanza = NextStanza();
+  if (stanza == NULL)
+    return STATE_BLOCKED;
+
+  if (SendStanza(stanza) != XMPP_RETURN_OK)
+    return STATE_ERROR;
+
+  return STATE_START;
+}
+
+}
diff --git a/talk/examples/call/mucinvitesendtask.h b/talk/examples/call/mucinvitesendtask.h
new file mode 100644
index 0000000..2429b31
--- /dev/null
+++ b/talk/examples/call/mucinvitesendtask.h
@@ -0,0 +1,50 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _MUCINVITESENDTASK_H_
+#define _MUCINVITESENDTASK_H_
+
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+#include "talk/examples/call/muc.h"
+
+namespace buzz {
+
+class MucInviteSendTask : public XmppTask {
+public:
+  explicit MucInviteSendTask(XmppTaskParentInterface* parent)
+      : XmppTask(parent) {}
+  virtual ~MucInviteSendTask() {}
+
+  XmppReturnStatus Send(const Jid& to, const Jid& invitee);
+
+  virtual int ProcessStart();
+};
+
+}
+
+#endif
diff --git a/talk/examples/call/presencepushtask.cc b/talk/examples/call/presencepushtask.cc
new file mode 100644
index 0000000..af02b1f
--- /dev/null
+++ b/talk/examples/call/presencepushtask.cc
@@ -0,0 +1,222 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/examples/call/presencepushtask.h"
+
+#include "talk/base/stringencode.h"
+#include "talk/examples/call/muc.h"
+#include "talk/xmpp/constants.h"
+
+
+
+namespace buzz {
+
+// string helper functions -----------------------------------------------------
+
+static bool
+IsXmlSpace(int ch) {
+  return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
+}
+
+static bool ListContainsToken(const std::string & list,
+                              const std::string & token) {
+  size_t i = list.find(token);
+  if (i == std::string::npos || token.empty())
+    return false;
+  bool boundary_before = (i == 0 || IsXmlSpace(list[i - 1]));
+  bool boundary_after = (i == list.length() - token.length() ||
+                         IsXmlSpace(list[i + token.length()]));
+  return boundary_before && boundary_after;
+}
+
+
+bool PresencePushTask::HandleStanza(const XmlElement * stanza) {
+  if (stanza->Name() != QN_PRESENCE)
+    return false;
+  QueueStanza(stanza);
+  return true;
+}
+
+static bool IsUtf8FirstByte(int c) {
+  return (((c)&0x80)==0) || // is single byte
+    ((unsigned char)((c)-0xc0)<0x3e); // or is lead byte
+}
+
+int PresencePushTask::ProcessStart() {
+  const XmlElement * stanza = NextStanza();
+  if (stanza == NULL)
+    return STATE_BLOCKED;
+
+  Jid from(stanza->Attr(QN_FROM));
+  std::map<Jid, buzz::Muc*>::const_iterator elem =
+      client_->mucs().find(from.BareJid());
+  if (elem == client_->mucs().end()) {
+    HandlePresence(from, stanza);
+  } else {
+    HandleMucPresence(elem->second, from, stanza);
+  }
+
+  return STATE_START;
+}
+
+void PresencePushTask::HandlePresence(const Jid& from,
+                                      const XmlElement* stanza) {
+  if (stanza->Attr(QN_TYPE) == STR_ERROR)
+    return;
+
+  PresenceStatus s;
+  FillStatus(from, stanza, &s);
+  SignalStatusUpdate(s);
+}
+
+void PresencePushTask::HandleMucPresence(buzz::Muc* muc,
+                                         const Jid& from,
+                                         const XmlElement* stanza) {
+  if (from == muc->local_jid()) {
+    if (!stanza->HasAttr(QN_TYPE)) {
+      // We joined the MUC.
+      const XmlElement* elem = stanza->FirstNamed(QN_MUC_USER_X);
+      // Status code=110 or 100 is not guaranteed to be present, so we
+      // only check the item element and Muc join status.
+      if (elem) {
+        if (elem->FirstNamed(QN_MUC_USER_ITEM) &&
+            muc->state() == buzz::Muc::MUC_JOINING) {
+          SignalMucJoined(muc->jid());
+        }
+      }
+    } else {
+      // We've been kicked. Bye.
+      int error = 0;
+      if (stanza->Attr(QN_TYPE) == STR_ERROR) {
+        const XmlElement* elem = stanza->FirstNamed(QN_ERROR);
+        if (elem && elem->HasAttr(QN_CODE)) {
+          error = atoi(elem->Attr(QN_CODE).c_str());
+        }
+      }
+      SignalMucLeft(muc->jid(), error);
+    }
+  } else {
+    MucPresenceStatus s;
+    FillMucStatus(from, stanza, &s);
+    SignalMucStatusUpdate(muc->jid(), s);
+  }
+}
+
+void PresencePushTask::FillStatus(const Jid& from, const XmlElement* stanza,
+                                  PresenceStatus* s) {
+  s->set_jid(from);
+  if (stanza->Attr(QN_TYPE) == STR_UNAVAILABLE) {
+    s->set_available(false);
+  } else {
+    s->set_available(true);
+    const XmlElement * status = stanza->FirstNamed(QN_STATUS);
+    if (status != NULL) {
+      s->set_status(status->BodyText());
+
+      // Truncate status messages longer than 300 bytes
+      if (s->status().length() > 300) {
+        size_t len = 300;
+
+        // Be careful not to split legal utf-8 chars in half
+        while (!IsUtf8FirstByte(s->status()[len]) && len > 0) {
+          len -= 1;
+        }
+        std::string truncated(s->status(), 0, len);
+        s->set_status(truncated);
+      }
+    }
+
+    const XmlElement * priority = stanza->FirstNamed(QN_PRIORITY);
+    if (priority != NULL) {
+      int pri;
+      if (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(PresenceStatus::SHOW_ONLINE);
+    }
+    else {
+      if (show->BodyText() == "away") {
+        s->set_show(PresenceStatus::SHOW_AWAY);
+      }
+      else if (show->BodyText() == "xa") {
+        s->set_show(PresenceStatus::SHOW_XA);
+      }
+      else if (show->BodyText() == "dnd") {
+        s->set_show(PresenceStatus::SHOW_DND);
+      }
+      else if (show->BodyText() == "chat") {
+        s->set_show(PresenceStatus::SHOW_CHAT);
+      }
+      else {
+        s->set_show(PresenceStatus::SHOW_ONLINE);
+      }
+    }
+
+    const XmlElement * caps = stanza->FirstNamed(QN_CAPS_C);
+    if (caps != NULL) {
+      std::string node = caps->Attr(QN_NODE);
+      std::string ver = caps->Attr(QN_VER);
+      std::string exts = caps->Attr(QN_EXT);
+
+      s->set_know_capabilities(true);
+      s->set_caps_node(node);
+      s->set_version(ver);
+
+      if (ListContainsToken(exts, "voice-v1")) {
+        s->set_voice_capability(true);
+      }
+      if (ListContainsToken(exts, "video-v1")) {
+        s->set_video_capability(true);
+      }
+    }
+
+    const XmlElement* delay = stanza->FirstNamed(kQnDelayX);
+    if (delay != NULL) {
+      // Ideally we would parse this according to the Psuedo ISO-8601 rules
+      // that are laid out in JEP-0082:
+      // http://www.jabber.org/jeps/jep-0082.html
+      std::string stamp = delay->Attr(kQnStamp);
+      s->set_sent_time(stamp);
+    }
+
+    const XmlElement* nick = stanza->FirstNamed(QN_NICKNAME);
+    if (nick) {
+      s->set_nick(nick->BodyText());
+    }
+  }
+}
+
+void PresencePushTask::FillMucStatus(const Jid& from, const XmlElement* stanza,
+                                     MucPresenceStatus* s) {
+  FillStatus(from, stanza, s);
+}
+
+}
diff --git a/talk/examples/call/presencepushtask.h b/talk/examples/call/presencepushtask.h
new file mode 100644
index 0000000..9cd1b42
--- /dev/null
+++ b/talk/examples/call/presencepushtask.h
@@ -0,0 +1,70 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _PRESENCEPUSHTASK_H_
+#define _PRESENCEPUSHTASK_H_
+
+#include <vector>
+
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+#include "talk/xmpp/presencestatus.h"
+#include "talk/base/sigslot.h"
+#include "talk/examples/call/callclient.h"
+
+namespace buzz {
+
+class PresencePushTask : public XmppTask {
+ public:
+  PresencePushTask(XmppTaskParentInterface* parent, CallClient* client)
+    : XmppTask(parent, XmppEngine::HL_TYPE),
+      client_(client) {}
+  virtual int ProcessStart();
+
+  sigslot::signal1<const PresenceStatus&> SignalStatusUpdate;
+  sigslot::signal1<const Jid&> SignalMucJoined;
+  sigslot::signal2<const Jid&, int> SignalMucLeft;
+  sigslot::signal2<const Jid&, const MucPresenceStatus&> SignalMucStatusUpdate;
+
+ protected:
+  virtual bool HandleStanza(const XmlElement * stanza);
+  void HandlePresence(const Jid& from, const XmlElement * stanza);
+  void HandleMucPresence(buzz::Muc* muc,
+                         const Jid& from, const XmlElement * stanza);
+  static void FillStatus(const Jid& from, const XmlElement * stanza,
+                         PresenceStatus* status);
+  static void FillMucStatus(const Jid& from, const XmlElement * stanza,
+                            MucPresenceStatus* status);
+
+ private:
+  CallClient* client_;
+};
+
+
+}
+
+#endif
diff --git a/talk/examples/chat/Info.plist b/talk/examples/chat/Info.plist
new file mode 100644
index 0000000..ecd083a
--- /dev/null
+++ b/talk/examples/chat/Info.plist
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>CFBundleIdentifier</key>
+  <string>com.google.call</string>
+  <key>CFBundleName</key>
+  <string>chat</string>
+</dict>
+</plist>
+
diff --git a/talk/examples/chat/chat_main.cc b/talk/examples/chat/chat_main.cc
new file mode 100644
index 0000000..09a454e
--- /dev/null
+++ b/talk/examples/chat/chat_main.cc
@@ -0,0 +1,159 @@
+/*
+ * libjingle
+ * Copyright 2004--2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 simple text chat application, largely copied from examples/call.
+//
+
+#include <iostream>
+
+#include "talk/base/logging.h"
+#include "talk/base/ssladapter.h"
+
+#ifdef OSX
+#include "talk/base/maccocoasocketserver.h"
+#elif defined(WIN32)
+#include "talk/base/win32socketserver.h"
+#else
+#include "talk/base/physicalsocketserver.h"
+#endif
+
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/xmppauth.h"
+#include "talk/xmpp/xmppclientsettings.h"
+#include "talk/xmpp/xmpppump.h"
+#include "talk/xmpp/xmppsocket.h"
+
+#include "talk/examples/chat/chatapp.h"
+#include "talk/examples/chat/consoletask.h"
+
+static const int kDefaultPort = 5222;
+
+int main(int argc, char* argv[]) {
+  // TODO(pmclean): Remove duplication of code with examples/call.
+  // Set up debugging.
+  bool debug = true;
+  if (debug) {
+    talk_base::LogMessage::LogToDebug(talk_base::LS_VERBOSE);
+  }
+
+  // Set up the crypto subsystem.
+  talk_base::InitializeSSL();
+
+  // Parse username and password, if present.
+  buzz::Jid jid;
+  std::string username;
+  talk_base::InsecureCryptStringImpl pass;
+  if (argc > 1) {
+    username = argv[1];
+    if (argc > 2) {
+      pass.password() = argv[2];
+    }
+  }
+
+  // ... else prompt for them
+  if (username.empty()) {
+    printf("JID: ");
+    std::cin >> username;
+  }
+  if (username.find('@') == std::string::npos) {
+    username.append("@localhost");
+  }
+
+  jid = buzz::Jid(username);
+  if (!jid.IsValid() || jid.node() == "") {
+    printf("Invalid JID. JIDs should be in the form user@domain\n");
+    return 1;
+  }
+
+  if (pass.password().empty()) {
+    buzz::ConsoleTask::SetEcho(false);
+    printf("Password: ");
+    std::cin >> pass.password();
+    buzz::ConsoleTask::SetEcho(true);
+    printf("\n");
+  }
+
+  // OTP (this can be skipped)
+  std::string otp_token;
+  printf("OTP: ");
+  fflush(stdin);
+  std::getline(std::cin, otp_token);
+
+  // Setup the connection settings.
+  buzz::XmppClientSettings xcs;
+  xcs.set_user(jid.node());
+  xcs.set_resource("chat");
+  xcs.set_host(jid.domain());
+  bool allow_plain = false;
+  xcs.set_allow_plain(allow_plain);
+  xcs.set_use_tls(buzz::TLS_REQUIRED);
+  xcs.set_pass(talk_base::CryptString(pass));
+  if (!otp_token.empty() && *otp_token.c_str() != '\n') {
+    xcs.set_auth_token(buzz::AUTH_MECHANISM_OAUTH2, otp_token);
+  }
+
+  // Build the server spec
+  std::string host;
+  int port;
+
+  std::string server = "talk.google.com";
+  int colon = server.find(':');
+  if (colon == -1) {
+    host = server;
+    port = kDefaultPort;
+  } else {
+    host = server.substr(0, colon);
+    port = atoi(server.substr(colon + 1).c_str());
+  }
+  xcs.set_server(talk_base::SocketAddress(host, port));
+
+  talk_base::Thread* main_thread = talk_base::Thread::Current();
+#if WIN32
+  // Need to pump messages on our main thread on Windows.
+  talk_base::Win32Thread w32_thread;
+  talk_base::ThreadManager::Instance()->SetCurrentThread(&w32_thread);
+#elif defined(OSX)
+  talk_base::MacCocoaSocketServer ss;
+  talk_base::SocketServerScope ss_scope(&ss);
+#else
+  talk_base::PhysicalSocketServer ss;
+#endif
+
+  buzz::XmppPump* pump = new buzz::XmppPump();
+  ChatApp *client = new ChatApp(pump->client(), main_thread);
+
+  // Start pumping messages!
+  pump->DoLogin(xcs, new buzz::XmppSocket(buzz::TLS_REQUIRED), new XmppAuth());
+
+  main_thread->Run();
+  pump->DoDisconnect();
+
+  delete client;
+
+  return 0;
+}
diff --git a/talk/examples/chat/chatapp.cc b/talk/examples/chat/chatapp.cc
new file mode 100644
index 0000000..1b59910
--- /dev/null
+++ b/talk/examples/chat/chatapp.cc
@@ -0,0 +1,251 @@
+/*
+ * libjingle
+ * Copyright 2004--2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/chat/chatapp.h"
+
+#include "talk/examples/chat/consoletask.h"
+#include "talk/examples/chat/textchatsendtask.h"
+#include "talk/examples/chat/textchatreceivetask.h"
+#include "talk/xmpp/presenceouttask.h"
+#include "talk/xmpp/presencereceivetask.h"
+
+#ifdef WIN32
+#define snprintf _snprintf
+#endif
+
+ChatApp::ChatApp(buzz::XmppClient* xmpp_client, talk_base::Thread* main_thread)
+  : xmpp_client_(xmpp_client),
+    presence_out_task_(NULL),
+    presence_receive_task_(NULL),
+    message_send_task_(NULL),
+    message_received_task_(NULL),
+    console_task_(new buzz::ConsoleTask(main_thread)),
+    ui_state_(STATE_BASE) {
+  xmpp_client_->SignalStateChange.connect(this, &ChatApp::OnStateChange);
+
+  console_task_->TextInputHandler.connect(this, &ChatApp::OnConsoleMessage);
+  console_task_->Start();
+}
+
+ChatApp::~ChatApp() {
+  if (presence_out_task_ != NULL) {
+    // Check out
+    BroadcastPresence(away);
+  }
+}
+
+void ChatApp::Quit() {
+  talk_base::Thread::Current()->Quit();
+}
+
+void ChatApp::OnXmppOpen() {
+  presence_out_task_.reset(new buzz::PresenceOutTask(xmpp_client_));
+  presence_receive_task_.reset(new buzz::PresenceReceiveTask(xmpp_client_));
+  presence_receive_task_->PresenceUpdate.connect(this,
+                                                 &ChatApp::OnPresenceUpdate);
+  message_send_task_.reset(new buzz::TextChatSendTask(xmpp_client_));
+  message_received_task_.reset(new buzz::TextChatReceiveTask(xmpp_client_));
+  message_received_task_->SignalTextChatReceived.connect(
+      this, &ChatApp::OnTextMessage);
+
+  presence_out_task_->Start();
+  presence_receive_task_->Start();
+  message_send_task_->Start();
+  message_received_task_->Start();
+}
+
+void ChatApp::BroadcastPresence(PresenceState state) {
+  buzz::PresenceStatus status;
+  status.set_jid(xmpp_client_->jid());
+  status.set_available(state == online);
+  status.set_show(state == online ? buzz::PresenceStatus::SHOW_ONLINE
+                                  : buzz::PresenceStatus::SHOW_AWAY);
+  presence_out_task_->Send(status);
+}
+
+// UI Stuff
+static const char* kMenuChoiceQuit = "0";
+static const char* kMenuChoiceRoster = "1";
+static const char* kMenuChoiceChat = "2";
+
+static const char* kUIStrings[3][2] = {
+  {kMenuChoiceQuit, "Quit"},
+  {kMenuChoiceRoster, "Roster"},
+  {kMenuChoiceChat, "Send"}};
+
+void ChatApp::PrintMenu() {
+  char buff[128];
+  int numMenuItems = sizeof(kUIStrings) / sizeof(kUIStrings[0]);
+  for (int index = 0; index < numMenuItems; ++index) {
+    snprintf(buff, sizeof(buff), "%s) %s\n", kUIStrings[index][0],
+                                             kUIStrings[index][1]);
+    console_task_->Print(buff);
+  }
+  console_task_->Print("choice:");
+}
+
+void ChatApp::PrintRoster() {
+  int index = 0;
+  for (RosterList::iterator iter = roster_list_.begin();
+     iter != roster_list_.end(); ++iter) {
+       const buzz::Jid& jid = iter->second.jid();
+       console_task_->Print(
+         "%d: (*) %s@%s [%s] \n",
+         index++,
+         jid.node().c_str(),
+         jid.domain().c_str(),
+         jid.resource().c_str());
+  }
+}
+
+void ChatApp::PromptJid() {
+  PrintRoster();
+  console_task_->Print("choice:");
+}
+
+void ChatApp::PromptChatMessage() {
+  console_task_->Print(":");
+}
+
+bool ChatApp::GetRosterItem(int index, buzz::PresenceStatus* status) {
+  int found_index = 0;
+  for (RosterList::iterator iter = roster_list_.begin();
+     iter != roster_list_.end() && found_index <= index; ++iter) {
+    if (found_index == index) {
+      *status = iter->second;
+      return true;
+    }
+    found_index++;
+  }
+
+  return false;
+}
+
+void ChatApp::HandleBaseInput(const std::string& message) {
+  if (message == kMenuChoiceQuit) {
+    Quit();
+  } else if (message == kMenuChoiceRoster) {
+    PrintRoster();
+  } else if (message == kMenuChoiceChat) {
+    ui_state_ = STATE_PROMPTJID;
+    PromptJid();
+  } else if (message == "") {
+    PrintMenu();
+  }
+}
+
+void ChatApp::HandleJidInput(const std::string& message) {
+  if (isdigit(message[0])) {
+    // It's an index-based roster choice.
+    int index = 0;
+    buzz::PresenceStatus status;
+    if (!talk_base::FromString(message, &index) ||
+        !GetRosterItem(index, &status)) {
+      // fail, so drop back
+      ui_state_ = STATE_BASE;
+      return;
+    }
+
+    chat_dest_jid_ = status.jid();
+  } else {
+    // It's an explicit address.
+    chat_dest_jid_ = buzz::Jid(message.c_str());
+  }
+  ui_state_ = STATE_CHATTING;
+  PromptChatMessage();
+}
+
+void ChatApp::HandleChatInput(const std::string& message) {
+  if (message == "") {
+    ui_state_ = STATE_BASE;
+    PrintMenu();
+  } else {
+    message_send_task_->Send(chat_dest_jid_, message);
+    PromptChatMessage();
+  }
+}
+
+// Connection state notifications
+void ChatApp::OnStateChange(buzz::XmppEngine::State state) {
+  switch (state) {
+  // Nonexistent state
+  case buzz::XmppEngine::STATE_NONE:
+    break;
+
+  // Nonexistent state
+  case buzz::XmppEngine::STATE_START:
+    break;
+
+  // Exchanging stream headers, authenticating and so on.
+  case buzz::XmppEngine::STATE_OPENING:
+    break;
+
+  // Authenticated and bound.
+  case buzz::XmppEngine::STATE_OPEN:
+    OnXmppOpen();
+    BroadcastPresence(online);
+    PrintMenu();
+    break;
+
+  // Session closed, possibly due to error.
+  case buzz::XmppEngine::STATE_CLOSED:
+    break;
+  }
+}
+
+// Presence Notifications
+void ChatApp::OnPresenceUpdate(const buzz::PresenceStatus& status) {
+  if (status.available()) {
+    roster_list_[status.jid().Str()] = status;
+  } else {
+    RosterList::iterator iter = roster_list_.find(status.jid().Str());
+    if (iter != roster_list_.end()) {
+      roster_list_.erase(iter);
+    }
+  }
+}
+
+// Text message handlers
+void ChatApp::OnTextMessage(const buzz::Jid& from, const buzz::Jid& to,
+                            const std::string& message) {
+  console_task_->Print("%s says: %s\n", from.node().c_str(), message.c_str());
+}
+
+void ChatApp::OnConsoleMessage(const std::string &message) {
+  switch (ui_state_) {
+    case STATE_BASE:
+      HandleBaseInput(message);
+      break;
+
+    case STATE_PROMPTJID:
+      HandleJidInput(message);
+      break;
+
+    case STATE_CHATTING:
+      HandleChatInput(message);
+      break;
+  }
+}
diff --git a/talk/examples/chat/chatapp.h b/talk/examples/chat/chatapp.h
new file mode 100644
index 0000000..cc032a6
--- /dev/null
+++ b/talk/examples/chat/chatapp.h
@@ -0,0 +1,171 @@
+/*
+ * libjingle
+ * Copyright 2004--2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_CHAT_CHATAPP_H_
+#define TALK_EXAMPLES_CHAT_CHATAPP_H_
+
+#include "talk/base/thread.h"
+#include "talk/base/scoped_ptr.h"
+
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/xmppclient.h"
+
+namespace buzz {
+class XmppClient;
+class PresenceOutTask;
+class PresenceReceiveTask;
+class TextChatSendTask;
+class TextChatReceiveTask;
+class ConsoleTask;
+class PresenceStatus;
+}
+
+// This is an example chat app for libjingle, showing how to use xmpp tasks,
+// data, callbacks, etc.  It has a simple text-based UI for logging in,
+// sending and receiving messages, and printing the roster.
+class ChatApp: public sigslot::has_slots<> {
+ public:
+  // Arguments:
+  //   xmpp_client  Points to the XmppClient for the communication channel
+  //    (typically created by the XmppPump object).
+  //   main_thread  Wraps the application's main thread.  Subsidiary threads
+  //    for the various tasks will be forked off of this.
+  ChatApp(buzz::XmppClient* xmpp_client, talk_base::Thread* main_thread);
+
+  // Shuts down and releases all of the contained tasks/threads
+  ~ChatApp();
+
+  // Shuts down the current thread and quits
+  void Quit();
+
+ private:
+  //
+  // Initialization
+  //
+  // Called explicitly after the connection to the chat server is established.
+  void OnXmppOpen();
+
+  //
+  // UI Stuff
+  //
+  // Prints the app main menu on the console.
+  // Called when ui_state_ == STATE_BASE.
+  void PrintMenu();
+
+  // Prints a numbered list of the logged-in user's roster on the console.
+  void PrintRoster();
+
+  // Prints a prompt for the user to enter either the index from the
+  // roster list of the user they wish to chat with, or a fully-qualified
+  // (user@server.ext) jid.
+  // Called when when ui_state_ == STATE_PROMPTJID.
+  void PromptJid();
+
+  // Prints a prompt on the console for the user to enter a message to send.
+  // Called when when ui_state_ == STATE_CHATTING.
+  void PromptChatMessage();
+
+  // Sends our presence state to the chat server (and on to your roster list).
+  // Arguments:
+  //  state Specifies the presence state to show.
+  enum PresenceState {online, away};
+  void BroadcastPresence(PresenceState state);
+
+  // Returns the RosterItem associated with the specified index.
+  // Just a helper to select a roster item from a numbered list in the UI.
+  bool GetRosterItem(int index, buzz::PresenceStatus* status);
+
+  //
+  // Input Handling
+  //
+  // Receives input when ui_state_ == STATE_BASE.  Handles choices from the
+  // main menu.
+  void HandleBaseInput(const std::string& message);
+
+  // Receives input when ui_state_ == STATE_PROMPTJID.  Handles selection
+  // of a JID to chat to.
+  void HandleJidInput(const std::string& message);
+
+  // Receives input when ui_state_ == STATE_CHATTING.  Handles text messages.
+  void HandleChatInput(const std::string& message);
+
+  //
+  // signal/slot Callbacks
+  //
+  // Connected to the XmppClient::SignalStateChange slot.  Receives
+  // notifications of state changes of the connection.
+  void OnStateChange(buzz::XmppEngine::State state);
+
+  // Connected to the PresenceReceiveTask::PresenceUpdate slot.
+  // Receives status messages for the logged-in user's roster (i.e.
+  // an initial list from the server and people coming/going).
+  void OnPresenceUpdate(const buzz::PresenceStatus& status);
+
+  // Connected to the TextChatReceiveTask::SignalTextChatReceived slot.
+  // Called when we receive a text chat from someone else.
+  void OnTextMessage(const buzz::Jid& from, const buzz::Jid& to,
+                     const std::string& message);
+
+  // Receives text input from the console task.  This is where any input
+  // from the user comes in.
+  // Arguments:
+  //   message What the user typed.
+  void OnConsoleMessage(const std::string &message);
+
+  // The XmppClient object associated with this chat application instance.
+  buzz::XmppClient* xmpp_client_;
+
+  // We send presence information through this object.
+  talk_base::scoped_ptr<buzz::PresenceOutTask> presence_out_task_;
+
+  // We receive others presence information through this object.
+  talk_base::scoped_ptr<buzz::PresenceReceiveTask> presence_receive_task_;
+
+  // We send text messages though this object.
+  talk_base::scoped_ptr<buzz::TextChatSendTask> message_send_task_;
+
+  // We receive messages through this object.
+  talk_base::scoped_ptr<buzz::TextChatReceiveTask> message_received_task_;
+
+  // UI gets drawn and receives input through this task.
+  talk_base::scoped_ptr< buzz::ConsoleTask> console_task_;
+
+  // The list of JIDs for the people in the logged-in users roster.
+  // RosterList  roster_list_;
+  typedef std::map<std::string, buzz::PresenceStatus> RosterList;
+  RosterList roster_list_;
+
+  // The JID of the user currently being chatted with.
+  buzz::Jid chat_dest_jid_;
+
+  // UI State constants
+  enum UIState { STATE_BASE, STATE_PROMPTJID, STATE_CHATTING };
+  UIState ui_state_;
+};
+
+#endif  // TALK_EXAMPLES_CHAT_CHATAPP_H_
+
diff --git a/talk/examples/chat/consoletask.cc b/talk/examples/chat/consoletask.cc
new file mode 100644
index 0000000..2577c79
--- /dev/null
+++ b/talk/examples/chat/consoletask.cc
@@ -0,0 +1,177 @@
+/*
+ * libjingle
+ * Copyright 2004--2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// TODO(pmclean): Perhaps this should be unified with examples/call/console.cc
+// and refactor to talk/base.
+#include "talk/examples/chat/consoletask.h"
+
+#define _CRT_SECURE_NO_DEPRECATE 1
+
+#include <stdarg.h>
+#ifdef POSIX
+#include <signal.h>
+#include <termios.h>
+#include <unistd.h>
+#endif  // POSIX
+#include <cassert>
+
+#include "talk/base/logging.h"
+
+#ifdef POSIX
+static void DoNothing(int unused) {}
+#endif
+
+namespace buzz {
+
+ConsoleTask::ConsoleTask(talk_base::Thread *thread) :
+  client_thread_(thread),
+  console_thread_(new talk_base::Thread()) {
+}
+
+ConsoleTask::~ConsoleTask() {
+  Stop();
+}
+
+void ConsoleTask::Start() {
+  if (!console_thread_) {
+    // stdin was closed in Stop(), so we can't restart.
+    LOG(LS_ERROR) << "Cannot re-start";
+    return;
+  }
+  if (console_thread_->started()) {
+    LOG(LS_WARNING) << "Already started";
+    return;
+  }
+  console_thread_->Start();
+  console_thread_->Post(this, MSG_START);
+}
+
+void ConsoleTask::Stop() {
+  if (console_thread_ && console_thread_->started()) {
+#ifdef WIN32
+    CloseHandle(GetStdHandle(STD_INPUT_HANDLE));
+#else
+    close(fileno(stdin));
+    // This forces the read() in fgets() to return with errno = EINTR. fgets()
+    // will retry the read() and fail, thus returning.
+    pthread_kill(console_thread_->GetPThread(), SIGUSR1);
+#endif
+    console_thread_->Stop();
+    console_thread_.reset();
+  }
+}
+
+void ConsoleTask::SetEcho(bool on) {
+#ifdef WIN32
+  HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
+  if ((hIn == INVALID_HANDLE_VALUE) || (hIn == NULL))
+    return;
+
+  DWORD mode;
+  if (!GetConsoleMode(hIn, &mode))
+    return;
+
+  if (on) {
+    mode = mode | ENABLE_ECHO_INPUT;
+  } else {
+    mode = mode & ~ENABLE_ECHO_INPUT;
+  }
+
+  SetConsoleMode(hIn, mode);
+#else  // MAC & LINUX
+  const int fd = fileno(stdin);
+  if (fd == -1) {
+    return;
+  }
+
+  struct termios tcflags;
+  if (tcgetattr(fd, &tcflags) == -1) {
+    return;
+  }
+
+  if (on) {
+    tcflags.c_lflag |= ECHO;
+  } else {
+    tcflags.c_lflag &= ~ECHO;
+  }
+
+  tcsetattr(fd, TCSANOW, &tcflags);
+#endif
+}
+
+void ConsoleTask::Print(const char* format, ...) {
+  va_list ap;
+  va_start(ap, format);
+
+  char buf[4096];
+  int size = vsnprintf(buf, sizeof(buf), format, ap);
+  assert(size >= 0);
+  assert(size < static_cast<int>(sizeof(buf)));
+  buf[size] = '\0';
+  printf("%s", buf);
+  fflush(stdout);
+
+  va_end(ap);
+}
+
+void ConsoleTask::RunConsole() {
+  char input_buffer[128];
+  while (fgets(input_buffer, sizeof(input_buffer), stdin) != NULL) {
+    client_thread_->Post(this, MSG_INPUT,
+        new talk_base::TypedMessageData<std::string>(input_buffer));
+  }
+}
+
+void ConsoleTask::OnMessage(talk_base::Message *msg) {
+  switch (msg->message_id) {
+    case MSG_START:
+#ifdef POSIX
+      // Install a no-op signal so that we can abort RunConsole() by raising
+      // SIGUSR1.
+      struct sigaction act;
+      act.sa_handler = &DoNothing;
+      sigemptyset(&act.sa_mask);
+      act.sa_flags = 0;
+      if (sigaction(SIGUSR1, &act, NULL) < 0) {
+        LOG(LS_WARNING) << "Can't install signal";
+      }
+#endif
+      RunConsole();
+      break;
+
+    case MSG_INPUT:
+      talk_base::TypedMessageData<std::string> *data =
+          static_cast<talk_base::TypedMessageData<std::string>*>(msg->pdata);
+      // Trim off the .line-terminator to make processing easier.
+      std::string parsed_message =
+        data->data().substr(0, data->data().length() - 1);
+      TextInputHandler(parsed_message);
+      break;
+  }
+}
+
+}  // namespace buzz
diff --git a/talk/examples/chat/consoletask.h b/talk/examples/chat/consoletask.h
new file mode 100644
index 0000000..1d45b3a
--- /dev/null
+++ b/talk/examples/chat/consoletask.h
@@ -0,0 +1,92 @@
+/*
+ * libjingle
+ * Copyright 2004--2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_CHAT_CONSOLETASK_H_
+#define TALK_EXAMPLES_CHAT_CONSOLETASK_H_
+
+#include <cstdio>
+
+#include "talk/base/thread.h"
+#include "talk/base/sigslot.h"
+
+namespace buzz {
+
+//
+// Provides properly threaded console I/O.
+//
+class ConsoleTask : public talk_base::MessageHandler {
+ public:
+  // Arguments:
+  // thread The main application thread.  Input messages get posted through
+  //   this.
+  explicit ConsoleTask(talk_base::Thread *thread);
+
+  // Shuts down the thread associated with this task.
+  ~ConsoleTask();
+
+  // Slot for text inputs handler.
+  sigslot::signal1<const std::string&> TextInputHandler;
+
+  // Starts reading lines from the console and passes them to the
+  //  TextInputHandler.
+  void Start();
+
+  // Stops reading lines and shuts down the thread.  Cannot be restarted.
+  void Stop();
+
+  // Thread messages (especialy text-input messages) come in through here.
+  virtual void OnMessage(talk_base::Message *msg);
+
+  // printf() style output to the console.
+  void Print(const char* format, ...);
+
+  // Turns on/off the echo of input characters on the console.
+  // Arguments:
+  //   on If true turns echo on, off otherwise.
+  static void SetEcho(bool on);
+
+ private:
+  /** Message IDs (for OnMessage()). */
+  enum {
+    MSG_START,
+    MSG_INPUT,
+  };
+
+  // Starts up polling for console input
+  void RunConsole();
+
+  // The main application thread
+  talk_base::Thread *client_thread_;
+
+  // The tread associated with this console object
+  talk_base::scoped_ptr<talk_base::Thread> console_thread_;
+};
+
+}  // namespace buzz
+
+#endif  // TALK_EXAMPLES_CHAT_CONSOLETASK_H_
+
diff --git a/talk/examples/chat/textchatreceivetask.cc b/talk/examples/chat/textchatreceivetask.cc
new file mode 100644
index 0000000..cbd019c
--- /dev/null
+++ b/talk/examples/chat/textchatreceivetask.cc
@@ -0,0 +1,66 @@
+/*
+ * libjingle
+ * Copyright 2004--2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/chat/textchatreceivetask.h"
+
+#include "talk/xmpp/constants.h"
+
+namespace buzz {
+
+TextChatReceiveTask::TextChatReceiveTask(XmppTaskParentInterface* parent)
+  : XmppTask(parent, XmppEngine::HL_TYPE) {
+}
+
+TextChatReceiveTask::~TextChatReceiveTask() {
+  Stop();
+}
+
+bool TextChatReceiveTask::HandleStanza(const XmlElement* stanza) {
+  // Make sure that this stanza is a message
+  if (stanza->Name() != QN_MESSAGE) {
+    return false;
+  }
+
+  // see if there is any body
+  const XmlElement* message_body = stanza->FirstNamed(QN_BODY);
+  if (message_body == NULL) {
+    return false;
+  }
+
+  // Looks good, so send the message text along.
+  SignalTextChatReceived(Jid(stanza->Attr(QN_FROM)), Jid(stanza->Attr(QN_TO)),
+                         message_body->BodyText());
+
+  return true;
+}
+
+int TextChatReceiveTask::ProcessStart() {
+  // not queuing messages, so just block.
+  return STATE_BLOCKED;
+}
+
+}  // namespace buzz
diff --git a/talk/examples/chat/textchatreceivetask.h b/talk/examples/chat/textchatreceivetask.h
new file mode 100644
index 0000000..e277692
--- /dev/null
+++ b/talk/examples/chat/textchatreceivetask.h
@@ -0,0 +1,63 @@
+/*
+ * libjingle
+ * Copyright 2004--2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_CHAT_TEXTCHATRECEIVETASK_H_
+#define TALK_EXAMPLES_CHAT_TEXTCHATRECEIVETASK_H_
+
+#include "talk/base/sigslot.h"
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+
+// A class to receive chat messages from the XMPP server.
+class TextChatReceiveTask : public XmppTask {
+ public:
+  // Arguments:
+  //   parent a reference to task interface associated withe the XMPP client.
+  explicit TextChatReceiveTask(XmppTaskParentInterface* parent);
+
+  // Shuts down the thread associated with this task.
+  virtual ~TextChatReceiveTask();
+
+  // Starts pulling queued status messages and dispatching them to the
+  // PresenceUpdate() callback.
+  virtual int ProcessStart();
+
+  // Slot for chat message callbacks
+  sigslot::signal3<const Jid&, const Jid&, const std::string&>
+      SignalTextChatReceived;
+
+ protected:
+  // Called by the XMPP client when chat stanzas arrive.  We pull out the
+  // interesting parts and send them to the SignalTextCharReceived() slot.
+  virtual bool HandleStanza(const XmlElement* stanza);
+};
+
+}  // namespace buzz
+
+#endif  // TALK_EXAMPLES_CHAT_TEXTCHATRECEIVETASK_H_
+
diff --git a/talk/examples/chat/textchatsendtask.cc b/talk/examples/chat/textchatsendtask.cc
new file mode 100644
index 0000000..ba14453
--- /dev/null
+++ b/talk/examples/chat/textchatsendtask.cc
@@ -0,0 +1,81 @@
+/*
+ * libjingle
+ * Copyright 2004--2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/chat/textchatsendtask.h"
+
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/xmppclient.h"
+
+namespace buzz {
+TextChatSendTask::TextChatSendTask(XmppTaskParentInterface* parent)
+  : XmppTask(parent) {
+}
+
+TextChatSendTask::~TextChatSendTask() {
+  Stop();
+}
+
+XmppReturnStatus TextChatSendTask::Send(const Jid& to,
+                                        const std::string& textmessage) {
+  // Make sure we are actually connected.
+  if (GetState() != STATE_INIT && GetState() != STATE_START) {
+    return XMPP_RETURN_BADSTATE;
+  }
+
+  // Put together the chat stanza...
+  XmlElement* message_stanza = new XmlElement(QN_MESSAGE);
+
+  // ... and specify the required attributes...
+  message_stanza->AddAttr(QN_TO, to.Str());
+  message_stanza->AddAttr(QN_TYPE, "chat");
+  message_stanza->AddAttr(QN_LANG, "en");
+
+  // ... and fill out the body.
+  XmlElement* message_body = new XmlElement(QN_BODY);
+  message_body->AddText(textmessage);
+  message_stanza->AddElement(message_body);
+
+  // Now queue it up.
+  QueueStanza(message_stanza);
+
+  return XMPP_RETURN_OK;
+}
+
+int TextChatSendTask::ProcessStart() {
+  const XmlElement* stanza = NextStanza();
+  if (stanza == NULL) {
+    return STATE_BLOCKED;
+  }
+
+  if (SendStanza(stanza) != XMPP_RETURN_OK) {
+    return STATE_ERROR;
+  }
+
+  return STATE_START;
+}
+
+}  // namespace buzz
diff --git a/talk/examples/chat/textchatsendtask.h b/talk/examples/chat/textchatsendtask.h
new file mode 100644
index 0000000..9b18923
--- /dev/null
+++ b/talk/examples/chat/textchatsendtask.h
@@ -0,0 +1,56 @@
+/*
+ * libjingle
+ * Copyright 2004--2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_CHAT_TEXTCHATSENDTASK_H_
+#define TALK_EXAMPLES_CHAT_TEXTCHATSENDTASK_H_
+
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+
+// A class to send chat messages to the XMPP server.
+class TextChatSendTask : public XmppTask {
+ public:
+  // Arguments:
+  //   parent a reference to task interface associated withe the XMPP client.
+  explicit TextChatSendTask(XmppTaskParentInterface* parent);
+
+  // Shuts down the thread associated with this task.
+  virtual ~TextChatSendTask();
+
+  // Forms the XMPP "chat" stanza with the specified receipient and message
+  // and queues it up.
+  XmppReturnStatus Send(const Jid& to, const std::string& message);
+
+  // Picks up any "chat" stanzas from our queue and sends them to the server.
+  virtual int ProcessStart();
+};
+
+}  // namespace buzz
+
+#endif  // TALK_EXAMPLES_CHAT_TEXTCHATSENDTASK_H_
+
diff --git a/talk/examples/ios/AppRTCDemo.xcodeproj/project.pbxproj b/talk/examples/ios/AppRTCDemo.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..72de3b4
--- /dev/null
+++ b/talk/examples/ios/AppRTCDemo.xcodeproj/project.pbxproj
@@ -0,0 +1,570 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 46;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		4F995B31173B6937007F179A /* libaudio_coding_module.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B01173B6937007F179A /* libaudio_coding_module.a */; };
+		4F995B32173B6937007F179A /* libaudio_conference_mixer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B02173B6937007F179A /* libaudio_conference_mixer.a */; };
+		4F995B33173B6937007F179A /* libaudio_device.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B03173B6937007F179A /* libaudio_device.a */; };
+		4F995B34173B6938007F179A /* libaudio_processing.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B04173B6937007F179A /* libaudio_processing.a */; };
+		4F995B35173B6938007F179A /* libaudioproc_debug_proto.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B05173B6937007F179A /* libaudioproc_debug_proto.a */; };
+		4F995B36173B6938007F179A /* libbitrate_controller.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B06173B6937007F179A /* libbitrate_controller.a */; };
+		4F995B37173B6938007F179A /* libCNG.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B07173B6937007F179A /* libCNG.a */; };
+		4F995B38173B6938007F179A /* libcommon_video.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B08173B6937007F179A /* libcommon_video.a */; };
+		4F995B39173B6938007F179A /* libexpat.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B09173B6937007F179A /* libexpat.a */; };
+		4F995B3A173B6938007F179A /* libG711.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B0A173B6937007F179A /* libG711.a */; };
+		4F995B3B173B6938007F179A /* libG722.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B0B173B6937007F179A /* libG722.a */; };
+		4F995B3C173B6938007F179A /* libgunit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B0C173B6937007F179A /* libgunit.a */; };
+		4F995B3D173B6938007F179A /* libiLBC.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B0D173B6937007F179A /* libiLBC.a */; };
+		4F995B3E173B6938007F179A /* libiSAC.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B0E173B6937007F179A /* libiSAC.a */; };
+		4F995B3F173B6938007F179A /* libiSACFix.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B0F173B6937007F179A /* libiSACFix.a */; };
+		4F995B40173B6938007F179A /* libjingle_media.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B10173B6937007F179A /* libjingle_media.a */; };
+		4F995B41173B6938007F179A /* libjingle_p2p.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B11173B6937007F179A /* libjingle_p2p.a */; };
+		4F995B42173B6938007F179A /* libjingle_peerconnection_objc.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B12173B6937007F179A /* libjingle_peerconnection_objc.a */; };
+		4F995B43173B6938007F179A /* libjingle_peerconnection.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B13173B6937007F179A /* libjingle_peerconnection.a */; };
+		4F995B44173B6938007F179A /* libjingle_sound.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B14173B6937007F179A /* libjingle_sound.a */; };
+		4F995B45173B6938007F179A /* libjingle.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B15173B6937007F179A /* libjingle.a */; };
+		4F995B46173B6938007F179A /* libjsoncpp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B16173B6937007F179A /* libjsoncpp.a */; };
+		4F995B47173B6938007F179A /* libmedia_file.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B17173B6937007F179A /* libmedia_file.a */; };
+		4F995B48173B6938007F179A /* libNetEq.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B18173B6937007F179A /* libNetEq.a */; };
+		4F995B49173B6938007F179A /* libopenssl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B19173B6937007F179A /* libopenssl.a */; };
+		4F995B4A173B6938007F179A /* libopus.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B1A173B6937007F179A /* libopus.a */; };
+		4F995B4B173B6938007F179A /* libpaced_sender.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B1B173B6937007F179A /* libpaced_sender.a */; };
+		4F995B4C173B6938007F179A /* libPCM16B.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B1C173B6937007F179A /* libPCM16B.a */; };
+		4F995B4D173B6938007F179A /* libprotobuf_lite.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B1D173B6937007F179A /* libprotobuf_lite.a */; };
+		4F995B4E173B6938007F179A /* libremote_bitrate_estimator.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B1E173B6937007F179A /* libremote_bitrate_estimator.a */; };
+		4F995B4F173B6938007F179A /* libresampler.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B1F173B6937007F179A /* libresampler.a */; };
+		4F995B50173B6938007F179A /* librtp_rtcp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B20173B6937007F179A /* librtp_rtcp.a */; };
+		4F995B51173B6938007F179A /* libsignal_processing.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B21173B6937007F179A /* libsignal_processing.a */; };
+		4F995B52173B6938007F179A /* libsrtp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B22173B6937007F179A /* libsrtp.a */; };
+		4F995B53173B6938007F179A /* libsystem_wrappers.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B23173B6937007F179A /* libsystem_wrappers.a */; };
+		4F995B54173B6938007F179A /* libudp_transport.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B24173B6937007F179A /* libudp_transport.a */; };
+		4F995B55173B6938007F179A /* libvad.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B25173B6937007F179A /* libvad.a */; };
+		4F995B56173B6938007F179A /* libvideo_capture_module.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B26173B6937007F179A /* libvideo_capture_module.a */; };
+		4F995B57173B6938007F179A /* libvideo_coding_utility.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B27173B6937007F179A /* libvideo_coding_utility.a */; };
+		4F995B58173B6938007F179A /* libvideo_engine_core.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B28173B6937007F179A /* libvideo_engine_core.a */; };
+		4F995B59173B6938007F179A /* libvideo_processing.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B29173B6937007F179A /* libvideo_processing.a */; };
+		4F995B5A173B6938007F179A /* libvideo_render_module.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B2A173B6937007F179A /* libvideo_render_module.a */; };
+		4F995B5B173B6938007F179A /* libvoice_engine_core.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B2B173B6937007F179A /* libvoice_engine_core.a */; };
+		4F995B5C173B6938007F179A /* libwebrtc_i420.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B2C173B6937007F179A /* libwebrtc_i420.a */; };
+		4F995B5D173B6938007F179A /* libwebrtc_opus.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B2D173B6937007F179A /* libwebrtc_opus.a */; };
+		4F995B5E173B6938007F179A /* libwebrtc_utility.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B2E173B6937007F179A /* libwebrtc_utility.a */; };
+		4F995B5F173B6938007F179A /* libwebrtc_video_coding.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B2F173B6937007F179A /* libwebrtc_video_coding.a */; };
+		4F995B60173B6938007F179A /* libwebrtc_vp8.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B30173B6937007F179A /* libwebrtc_vp8.a */; };
+		4F995B62173B694B007F179A /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B61173B694B007F179A /* AVFoundation.framework */; };
+		4F995B64173B6956007F179A /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B63173B6956007F179A /* CoreMedia.framework */; };
+		4F995B66173B695C007F179A /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B65173B695C007F179A /* CoreVideo.framework */; };
+		4F995B68173B6970007F179A /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B67173B6970007F179A /* CoreAudio.framework */; };
+		4F995B91173C03A1007F179A /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F995B90173C03A1007F179A /* AudioToolbox.framework */; };
+		4F995B94173C0B82007F179A /* shim.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4F995B92173C0819007F179A /* shim.mm */; };
+		4FBCC04F1728E929004C8C0B /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4FBCC04E1728E929004C8C0B /* UIKit.framework */; };
+		4FBCC0511728E929004C8C0B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4FBCC0501728E929004C8C0B /* Foundation.framework */; };
+		4FBCC0531728E929004C8C0B /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4FBCC0521728E929004C8C0B /* CoreGraphics.framework */; };
+		4FBCC05B1728E929004C8C0B /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FBCC05A1728E929004C8C0B /* main.m */; };
+		4FBCC05F1728E929004C8C0B /* APPRTCAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FBCC05E1728E929004C8C0B /* APPRTCAppDelegate.m */; };
+		4FBCC0611728E929004C8C0B /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = 4FBCC0601728E929004C8C0B /* Default.png */; };
+		4FBCC0681728E929004C8C0B /* APPRTCViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FBCC0671728E929004C8C0B /* APPRTCViewController.m */; };
+		4FBCC06B1728E929004C8C0B /* APPRTCViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4FBCC0691728E929004C8C0B /* APPRTCViewController.xib */; };
+		4FBCC0731729B780004C8C0B /* GAEChannelClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FBCC0721729B780004C8C0B /* GAEChannelClient.m */; };
+		4FD7F5011732E1C2009295E5 /* APPRTCAppClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FD7F5001732E1C2009295E5 /* APPRTCAppClient.m */; };
+		4FEE3E531743C94D0005814A /* ios_channel.html in Resources */ = {isa = PBXBuildFile; fileRef = 4FEE3E511743C92D0005814A /* ios_channel.html */; };
+		4FEE3EB71746A3810005814A /* Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 4FEE3EB61746A3810005814A /* Icon.png */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+		4F995B01173B6937007F179A /* libaudio_coding_module.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libaudio_coding_module.a; path = libs/libaudio_coding_module.a; sourceTree = "<group>"; };
+		4F995B02173B6937007F179A /* libaudio_conference_mixer.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libaudio_conference_mixer.a; path = libs/libaudio_conference_mixer.a; sourceTree = "<group>"; };
+		4F995B03173B6937007F179A /* libaudio_device.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libaudio_device.a; path = libs/libaudio_device.a; sourceTree = "<group>"; };
+		4F995B04173B6937007F179A /* libaudio_processing.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libaudio_processing.a; path = libs/libaudio_processing.a; sourceTree = "<group>"; };
+		4F995B05173B6937007F179A /* libaudioproc_debug_proto.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libaudioproc_debug_proto.a; path = libs/libaudioproc_debug_proto.a; sourceTree = "<group>"; };
+		4F995B06173B6937007F179A /* libbitrate_controller.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libbitrate_controller.a; path = libs/libbitrate_controller.a; sourceTree = "<group>"; };
+		4F995B07173B6937007F179A /* libCNG.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libCNG.a; path = libs/libCNG.a; sourceTree = "<group>"; };
+		4F995B08173B6937007F179A /* libcommon_video.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libcommon_video.a; path = libs/libcommon_video.a; sourceTree = "<group>"; };
+		4F995B09173B6937007F179A /* libexpat.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libexpat.a; path = libs/libexpat.a; sourceTree = "<group>"; };
+		4F995B0A173B6937007F179A /* libG711.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libG711.a; path = libs/libG711.a; sourceTree = "<group>"; };
+		4F995B0B173B6937007F179A /* libG722.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libG722.a; path = libs/libG722.a; sourceTree = "<group>"; };
+		4F995B0C173B6937007F179A /* libgunit.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgunit.a; path = libs/libgunit.a; sourceTree = "<group>"; };
+		4F995B0D173B6937007F179A /* libiLBC.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libiLBC.a; path = libs/libiLBC.a; sourceTree = "<group>"; };
+		4F995B0E173B6937007F179A /* libiSAC.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libiSAC.a; path = libs/libiSAC.a; sourceTree = "<group>"; };
+		4F995B0F173B6937007F179A /* libiSACFix.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libiSACFix.a; path = libs/libiSACFix.a; sourceTree = "<group>"; };
+		4F995B10173B6937007F179A /* libjingle_media.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libjingle_media.a; path = libs/libjingle_media.a; sourceTree = "<group>"; };
+		4F995B11173B6937007F179A /* libjingle_p2p.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libjingle_p2p.a; path = libs/libjingle_p2p.a; sourceTree = "<group>"; };
+		4F995B12173B6937007F179A /* libjingle_peerconnection_objc.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libjingle_peerconnection_objc.a; path = libs/libjingle_peerconnection_objc.a; sourceTree = "<group>"; };
+		4F995B13173B6937007F179A /* libjingle_peerconnection.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libjingle_peerconnection.a; path = libs/libjingle_peerconnection.a; sourceTree = "<group>"; };
+		4F995B14173B6937007F179A /* libjingle_sound.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libjingle_sound.a; path = libs/libjingle_sound.a; sourceTree = "<group>"; };
+		4F995B15173B6937007F179A /* libjingle.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libjingle.a; path = libs/libjingle.a; sourceTree = "<group>"; };
+		4F995B16173B6937007F179A /* libjsoncpp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libjsoncpp.a; path = libs/libjsoncpp.a; sourceTree = "<group>"; };
+		4F995B17173B6937007F179A /* libmedia_file.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libmedia_file.a; path = libs/libmedia_file.a; sourceTree = "<group>"; };
+		4F995B18173B6937007F179A /* libNetEq.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libNetEq.a; path = libs/libNetEq.a; sourceTree = "<group>"; };
+		4F995B19173B6937007F179A /* libopenssl.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libopenssl.a; path = libs/libopenssl.a; sourceTree = "<group>"; };
+		4F995B1A173B6937007F179A /* libopus.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libopus.a; path = libs/libopus.a; sourceTree = "<group>"; };
+		4F995B1B173B6937007F179A /* libpaced_sender.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libpaced_sender.a; path = libs/libpaced_sender.a; sourceTree = "<group>"; };
+		4F995B1C173B6937007F179A /* libPCM16B.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libPCM16B.a; path = libs/libPCM16B.a; sourceTree = "<group>"; };
+		4F995B1D173B6937007F179A /* libprotobuf_lite.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libprotobuf_lite.a; path = libs/libprotobuf_lite.a; sourceTree = "<group>"; };
+		4F995B1E173B6937007F179A /* libremote_bitrate_estimator.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libremote_bitrate_estimator.a; path = libs/libremote_bitrate_estimator.a; sourceTree = "<group>"; };
+		4F995B1F173B6937007F179A /* libresampler.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libresampler.a; path = libs/libresampler.a; sourceTree = "<group>"; };
+		4F995B20173B6937007F179A /* librtp_rtcp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = librtp_rtcp.a; path = libs/librtp_rtcp.a; sourceTree = "<group>"; };
+		4F995B21173B6937007F179A /* libsignal_processing.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libsignal_processing.a; path = libs/libsignal_processing.a; sourceTree = "<group>"; };
+		4F995B22173B6937007F179A /* libsrtp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libsrtp.a; path = libs/libsrtp.a; sourceTree = "<group>"; };
+		4F995B23173B6937007F179A /* libsystem_wrappers.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libsystem_wrappers.a; path = libs/libsystem_wrappers.a; sourceTree = "<group>"; };
+		4F995B24173B6937007F179A /* libudp_transport.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libudp_transport.a; path = libs/libudp_transport.a; sourceTree = "<group>"; };
+		4F995B25173B6937007F179A /* libvad.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libvad.a; path = libs/libvad.a; sourceTree = "<group>"; };
+		4F995B26173B6937007F179A /* libvideo_capture_module.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libvideo_capture_module.a; path = libs/libvideo_capture_module.a; sourceTree = "<group>"; };
+		4F995B27173B6937007F179A /* libvideo_coding_utility.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libvideo_coding_utility.a; path = libs/libvideo_coding_utility.a; sourceTree = "<group>"; };
+		4F995B28173B6937007F179A /* libvideo_engine_core.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libvideo_engine_core.a; path = libs/libvideo_engine_core.a; sourceTree = "<group>"; };
+		4F995B29173B6937007F179A /* libvideo_processing.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libvideo_processing.a; path = libs/libvideo_processing.a; sourceTree = "<group>"; };
+		4F995B2A173B6937007F179A /* libvideo_render_module.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libvideo_render_module.a; path = libs/libvideo_render_module.a; sourceTree = "<group>"; };
+		4F995B2B173B6937007F179A /* libvoice_engine_core.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libvoice_engine_core.a; path = libs/libvoice_engine_core.a; sourceTree = "<group>"; };
+		4F995B2C173B6937007F179A /* libwebrtc_i420.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libwebrtc_i420.a; path = libs/libwebrtc_i420.a; sourceTree = "<group>"; };
+		4F995B2D173B6937007F179A /* libwebrtc_opus.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libwebrtc_opus.a; path = libs/libwebrtc_opus.a; sourceTree = "<group>"; };
+		4F995B2E173B6937007F179A /* libwebrtc_utility.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libwebrtc_utility.a; path = libs/libwebrtc_utility.a; sourceTree = "<group>"; };
+		4F995B2F173B6937007F179A /* libwebrtc_video_coding.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libwebrtc_video_coding.a; path = libs/libwebrtc_video_coding.a; sourceTree = "<group>"; };
+		4F995B30173B6937007F179A /* libwebrtc_vp8.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libwebrtc_vp8.a; path = libs/libwebrtc_vp8.a; sourceTree = "<group>"; };
+		4F995B61173B694B007F179A /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
+		4F995B63173B6956007F179A /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; };
+		4F995B65173B695C007F179A /* CoreVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreVideo.framework; path = System/Library/Frameworks/CoreVideo.framework; sourceTree = SDKROOT; };
+		4F995B67173B6970007F179A /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; };
+		4F995B90173C03A1007F179A /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
+		4F995B92173C0819007F179A /* shim.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = shim.mm; path = ../../../app/webrtc/objctests/ios/shim.mm; sourceTree = "<group>"; };
+		4FBCC04B1728E929004C8C0B /* AppRTCDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AppRTCDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		4FBCC04E1728E929004C8C0B /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
+		4FBCC0501728E929004C8C0B /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
+		4FBCC0521728E929004C8C0B /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
+		4FBCC0561728E929004C8C0B /* AppRTCDemo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "AppRTCDemo-Info.plist"; sourceTree = "<group>"; };
+		4FBCC05A1728E929004C8C0B /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+		4FBCC05C1728E929004C8C0B /* AppRTCDemo-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AppRTCDemo-Prefix.pch"; sourceTree = "<group>"; };
+		4FBCC05D1728E929004C8C0B /* APPRTCAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = APPRTCAppDelegate.h; sourceTree = "<group>"; };
+		4FBCC05E1728E929004C8C0B /* APPRTCAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = APPRTCAppDelegate.m; sourceTree = "<group>"; };
+		4FBCC0601728E929004C8C0B /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = "<group>"; };
+		4FBCC0661728E929004C8C0B /* APPRTCViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = APPRTCViewController.h; sourceTree = "<group>"; };
+		4FBCC0671728E929004C8C0B /* APPRTCViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = APPRTCViewController.m; sourceTree = "<group>"; };
+		4FBCC06A1728E929004C8C0B /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/APPRTCViewController.xib; sourceTree = "<group>"; };
+		4FBCC0711729B780004C8C0B /* GAEChannelClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAEChannelClient.h; sourceTree = "<group>"; };
+		4FBCC0721729B780004C8C0B /* GAEChannelClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAEChannelClient.m; sourceTree = "<group>"; };
+		4FD7F4FF1732E1C1009295E5 /* APPRTCAppClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = APPRTCAppClient.h; sourceTree = "<group>"; };
+		4FD7F5001732E1C2009295E5 /* APPRTCAppClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = APPRTCAppClient.m; sourceTree = "<group>"; };
+		4FEE3E511743C92D0005814A /* ios_channel.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = ios_channel.html; sourceTree = "<group>"; };
+		4FEE3EB61746A3810005814A /* Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Icon.png; path = ../Icon.png; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		4FBCC0481728E929004C8C0B /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				4F995B91173C03A1007F179A /* AudioToolbox.framework in Frameworks */,
+				4F995B62173B694B007F179A /* AVFoundation.framework in Frameworks */,
+				4F995B68173B6970007F179A /* CoreAudio.framework in Frameworks */,
+				4FBCC0531728E929004C8C0B /* CoreGraphics.framework in Frameworks */,
+				4F995B64173B6956007F179A /* CoreMedia.framework in Frameworks */,
+				4F995B66173B695C007F179A /* CoreVideo.framework in Frameworks */,
+				4FBCC0511728E929004C8C0B /* Foundation.framework in Frameworks */,
+				4FBCC04F1728E929004C8C0B /* UIKit.framework in Frameworks */,
+				4F995B31173B6937007F179A /* libaudio_coding_module.a in Frameworks */,
+				4F995B32173B6937007F179A /* libaudio_conference_mixer.a in Frameworks */,
+				4F995B33173B6937007F179A /* libaudio_device.a in Frameworks */,
+				4F995B34173B6938007F179A /* libaudio_processing.a in Frameworks */,
+				4F995B35173B6938007F179A /* libaudioproc_debug_proto.a in Frameworks */,
+				4F995B36173B6938007F179A /* libbitrate_controller.a in Frameworks */,
+				4F995B37173B6938007F179A /* libCNG.a in Frameworks */,
+				4F995B38173B6938007F179A /* libcommon_video.a in Frameworks */,
+				4F995B39173B6938007F179A /* libexpat.a in Frameworks */,
+				4F995B3A173B6938007F179A /* libG711.a in Frameworks */,
+				4F995B3B173B6938007F179A /* libG722.a in Frameworks */,
+				4F995B3C173B6938007F179A /* libgunit.a in Frameworks */,
+				4F995B3D173B6938007F179A /* libiLBC.a in Frameworks */,
+				4F995B3E173B6938007F179A /* libiSAC.a in Frameworks */,
+				4F995B40173B6938007F179A /* libjingle_media.a in Frameworks */,
+				4F995B41173B6938007F179A /* libjingle_p2p.a in Frameworks */,
+				4F995B42173B6938007F179A /* libjingle_peerconnection_objc.a in Frameworks */,
+				4F995B43173B6938007F179A /* libjingle_peerconnection.a in Frameworks */,
+				4F995B44173B6938007F179A /* libjingle_sound.a in Frameworks */,
+				4F995B45173B6938007F179A /* libjingle.a in Frameworks */,
+				4F995B46173B6938007F179A /* libjsoncpp.a in Frameworks */,
+				4F995B47173B6938007F179A /* libmedia_file.a in Frameworks */,
+				4F995B48173B6938007F179A /* libNetEq.a in Frameworks */,
+				4F995B49173B6938007F179A /* libopenssl.a in Frameworks */,
+				4F995B4A173B6938007F179A /* libopus.a in Frameworks */,
+				4F995B4B173B6938007F179A /* libpaced_sender.a in Frameworks */,
+				4F995B4C173B6938007F179A /* libPCM16B.a in Frameworks */,
+				4F995B4D173B6938007F179A /* libprotobuf_lite.a in Frameworks */,
+				4F995B4E173B6938007F179A /* libremote_bitrate_estimator.a in Frameworks */,
+				4F995B4F173B6938007F179A /* libresampler.a in Frameworks */,
+				4F995B50173B6938007F179A /* librtp_rtcp.a in Frameworks */,
+				4F995B51173B6938007F179A /* libsignal_processing.a in Frameworks */,
+				4F995B52173B6938007F179A /* libsrtp.a in Frameworks */,
+				4F995B53173B6938007F179A /* libsystem_wrappers.a in Frameworks */,
+				4F995B54173B6938007F179A /* libudp_transport.a in Frameworks */,
+				4F995B55173B6938007F179A /* libvad.a in Frameworks */,
+				4F995B56173B6938007F179A /* libvideo_capture_module.a in Frameworks */,
+				4F995B57173B6938007F179A /* libvideo_coding_utility.a in Frameworks */,
+				4F995B58173B6938007F179A /* libvideo_engine_core.a in Frameworks */,
+				4F995B59173B6938007F179A /* libvideo_processing.a in Frameworks */,
+				4F995B5A173B6938007F179A /* libvideo_render_module.a in Frameworks */,
+				4F995B5B173B6938007F179A /* libvoice_engine_core.a in Frameworks */,
+				4F995B5C173B6938007F179A /* libwebrtc_i420.a in Frameworks */,
+				4F995B5D173B6938007F179A /* libwebrtc_opus.a in Frameworks */,
+				4F995B5E173B6938007F179A /* libwebrtc_utility.a in Frameworks */,
+				4F995B5F173B6938007F179A /* libwebrtc_video_coding.a in Frameworks */,
+				4F995B60173B6938007F179A /* libwebrtc_vp8.a in Frameworks */,
+				4F995B3F173B6938007F179A /* libiSACFix.a in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		4F1CC122172F52E50090479F /* libs */ = {
+			isa = PBXGroup;
+			children = (
+				4F995B01173B6937007F179A /* libaudio_coding_module.a */,
+				4F995B02173B6937007F179A /* libaudio_conference_mixer.a */,
+				4F995B03173B6937007F179A /* libaudio_device.a */,
+				4F995B04173B6937007F179A /* libaudio_processing.a */,
+				4F995B05173B6937007F179A /* libaudioproc_debug_proto.a */,
+				4F995B06173B6937007F179A /* libbitrate_controller.a */,
+				4F995B07173B6937007F179A /* libCNG.a */,
+				4F995B08173B6937007F179A /* libcommon_video.a */,
+				4F995B09173B6937007F179A /* libexpat.a */,
+				4F995B0A173B6937007F179A /* libG711.a */,
+				4F995B0B173B6937007F179A /* libG722.a */,
+				4F995B0C173B6937007F179A /* libgunit.a */,
+				4F995B0D173B6937007F179A /* libiLBC.a */,
+				4F995B0E173B6937007F179A /* libiSAC.a */,
+				4F995B0F173B6937007F179A /* libiSACFix.a */,
+				4F995B10173B6937007F179A /* libjingle_media.a */,
+				4F995B11173B6937007F179A /* libjingle_p2p.a */,
+				4F995B12173B6937007F179A /* libjingle_peerconnection_objc.a */,
+				4F995B13173B6937007F179A /* libjingle_peerconnection.a */,
+				4F995B14173B6937007F179A /* libjingle_sound.a */,
+				4F995B15173B6937007F179A /* libjingle.a */,
+				4F995B16173B6937007F179A /* libjsoncpp.a */,
+				4F995B17173B6937007F179A /* libmedia_file.a */,
+				4F995B18173B6937007F179A /* libNetEq.a */,
+				4F995B19173B6937007F179A /* libopenssl.a */,
+				4F995B1A173B6937007F179A /* libopus.a */,
+				4F995B1B173B6937007F179A /* libpaced_sender.a */,
+				4F995B1C173B6937007F179A /* libPCM16B.a */,
+				4F995B1D173B6937007F179A /* libprotobuf_lite.a */,
+				4F995B1E173B6937007F179A /* libremote_bitrate_estimator.a */,
+				4F995B1F173B6937007F179A /* libresampler.a */,
+				4F995B20173B6937007F179A /* librtp_rtcp.a */,
+				4F995B21173B6937007F179A /* libsignal_processing.a */,
+				4F995B22173B6937007F179A /* libsrtp.a */,
+				4F995B23173B6937007F179A /* libsystem_wrappers.a */,
+				4F995B24173B6937007F179A /* libudp_transport.a */,
+				4F995B25173B6937007F179A /* libvad.a */,
+				4F995B26173B6937007F179A /* libvideo_capture_module.a */,
+				4F995B27173B6937007F179A /* libvideo_coding_utility.a */,
+				4F995B28173B6937007F179A /* libvideo_engine_core.a */,
+				4F995B29173B6937007F179A /* libvideo_processing.a */,
+				4F995B2A173B6937007F179A /* libvideo_render_module.a */,
+				4F995B2B173B6937007F179A /* libvoice_engine_core.a */,
+				4F995B2C173B6937007F179A /* libwebrtc_i420.a */,
+				4F995B2D173B6937007F179A /* libwebrtc_opus.a */,
+				4F995B2E173B6937007F179A /* libwebrtc_utility.a */,
+				4F995B2F173B6937007F179A /* libwebrtc_video_coding.a */,
+				4F995B30173B6937007F179A /* libwebrtc_vp8.a */,
+			);
+			name = libs;
+			sourceTree = "<group>";
+		};
+		4FBCC0421728E929004C8C0B = {
+			isa = PBXGroup;
+			children = (
+				4FBCC0541728E929004C8C0B /* AppRTCDemo */,
+				4FBCC04D1728E929004C8C0B /* Frameworks */,
+				4F1CC122172F52E50090479F /* libs */,
+				4FBCC04C1728E929004C8C0B /* Products */,
+			);
+			sourceTree = "<group>";
+		};
+		4FBCC04C1728E929004C8C0B /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				4FBCC04B1728E929004C8C0B /* AppRTCDemo.app */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		4FBCC04D1728E929004C8C0B /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+				4F995B90173C03A1007F179A /* AudioToolbox.framework */,
+				4F995B61173B694B007F179A /* AVFoundation.framework */,
+				4F995B67173B6970007F179A /* CoreAudio.framework */,
+				4FBCC0521728E929004C8C0B /* CoreGraphics.framework */,
+				4F995B63173B6956007F179A /* CoreMedia.framework */,
+				4F995B65173B695C007F179A /* CoreVideo.framework */,
+				4FBCC0501728E929004C8C0B /* Foundation.framework */,
+				4FBCC04E1728E929004C8C0B /* UIKit.framework */,
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
+		4FBCC0541728E929004C8C0B /* AppRTCDemo */ = {
+			isa = PBXGroup;
+			children = (
+				4FBCC0711729B780004C8C0B /* GAEChannelClient.h */,
+				4FBCC0721729B780004C8C0B /* GAEChannelClient.m */,
+				4FD7F4FF1732E1C1009295E5 /* APPRTCAppClient.h */,
+				4FD7F5001732E1C2009295E5 /* APPRTCAppClient.m */,
+				4FBCC05D1728E929004C8C0B /* APPRTCAppDelegate.h */,
+				4FBCC05E1728E929004C8C0B /* APPRTCAppDelegate.m */,
+				4FBCC0661728E929004C8C0B /* APPRTCViewController.h */,
+				4FBCC0671728E929004C8C0B /* APPRTCViewController.m */,
+				4FBCC0691728E929004C8C0B /* APPRTCViewController.xib */,
+				4FBCC0551728E929004C8C0B /* Supporting Files */,
+			);
+			path = AppRTCDemo;
+			sourceTree = "<group>";
+		};
+		4FBCC0551728E929004C8C0B /* Supporting Files */ = {
+			isa = PBXGroup;
+			children = (
+				4FBCC0561728E929004C8C0B /* AppRTCDemo-Info.plist */,
+				4FBCC05C1728E929004C8C0B /* AppRTCDemo-Prefix.pch */,
+				4FBCC0601728E929004C8C0B /* Default.png */,
+				4FEE3EB61746A3810005814A /* Icon.png */,
+				4FEE3E511743C92D0005814A /* ios_channel.html */,
+				4FBCC05A1728E929004C8C0B /* main.m */,
+				4F995B92173C0819007F179A /* shim.mm */,
+			);
+			name = "Supporting Files";
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		4FBCC04A1728E929004C8C0B /* AppRTCDemo */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 4FBCC06E1728E929004C8C0B /* Build configuration list for PBXNativeTarget "AppRTCDemo" */;
+			buildPhases = (
+				4FBCC0471728E929004C8C0B /* Sources */,
+				4FBCC0481728E929004C8C0B /* Frameworks */,
+				4FBCC0491728E929004C8C0B /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = AppRTCDemo;
+			productName = AppRTCDemo;
+			productReference = 4FBCC04B1728E929004C8C0B /* AppRTCDemo.app */;
+			productType = "com.apple.product-type.application";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		4FBCC0431728E929004C8C0B /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				CLASSPREFIX = RTC;
+				LastUpgradeCheck = 0460;
+				ORGANIZATIONNAME = Google;
+			};
+			buildConfigurationList = 4FBCC0461728E929004C8C0B /* Build configuration list for PBXProject "AppRTCDemo" */;
+			compatibilityVersion = "Xcode 3.2";
+			developmentRegion = English;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+			);
+			mainGroup = 4FBCC0421728E929004C8C0B;
+			productRefGroup = 4FBCC04C1728E929004C8C0B /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				4FBCC04A1728E929004C8C0B /* AppRTCDemo */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		4FBCC0491728E929004C8C0B /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				4FEE3E531743C94D0005814A /* ios_channel.html in Resources */,
+				4FBCC0611728E929004C8C0B /* Default.png in Resources */,
+				4FBCC06B1728E929004C8C0B /* APPRTCViewController.xib in Resources */,
+				4FEE3EB71746A3810005814A /* Icon.png in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		4FBCC0471728E929004C8C0B /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				4FBCC05B1728E929004C8C0B /* main.m in Sources */,
+				4FBCC05F1728E929004C8C0B /* APPRTCAppDelegate.m in Sources */,
+				4FBCC0681728E929004C8C0B /* APPRTCViewController.m in Sources */,
+				4FBCC0731729B780004C8C0B /* GAEChannelClient.m in Sources */,
+				4FD7F5011732E1C2009295E5 /* APPRTCAppClient.m in Sources */,
+				4F995B94173C0B82007F179A /* shim.mm in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+		4FBCC0691728E929004C8C0B /* APPRTCViewController.xib */ = {
+			isa = PBXVariantGroup;
+			children = (
+				4FBCC06A1728E929004C8C0B /* en */,
+			);
+			name = APPRTCViewController.xib;
+			sourceTree = "<group>";
+		};
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+		4FBCC06C1728E929004C8C0B /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 6.1;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = iphoneos;
+			};
+			name = Debug;
+		};
+		4FBCC06D1728E929004C8C0B /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 6.1;
+				OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1";
+				SDKROOT = iphoneos;
+				VALIDATE_PRODUCT = YES;
+			};
+			name = Release;
+		};
+		4FBCC06F1728E929004C8C0B /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ARCHS = "$(ARCHS_UNIVERSAL_IPHONE_OS)";
+				CLANG_CXX_LANGUAGE_STANDARD = "compiler-default";
+				CLANG_CXX_LIBRARY = "compiler-default";
+				GCC_CW_ASM_SYNTAX = NO;
+				GCC_C_LANGUAGE_STANDARD = c99;
+				GCC_ENABLE_CPP_EXCEPTIONS = NO;
+				GCC_ENABLE_CPP_RTTI = NO;
+				GCC_PRECOMPILE_PREFIX_HEADER = YES;
+				GCC_PREFIX_HEADER = "AppRTCDemo/AppRTCDemo-Prefix.pch";
+				GCC_THREADSAFE_STATICS = NO;
+				GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
+				HEADER_SEARCH_PATHS = (
+					../../app/webrtc/objc/public,
+					../../..,
+				);
+				INFOPLIST_FILE = "AppRTCDemo/AppRTCDemo-Info.plist";
+				LIBRARY_SEARCH_PATHS = (
+					"$(inherited)",
+					"\"$(SRCROOT)/libs\"",
+				);
+				ONLY_ACTIVE_ARCH = NO;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SDKROOT = iphoneos;
+				VALID_ARCHS = "armv7 i386";
+				WRAPPER_EXTENSION = app;
+			};
+			name = Debug;
+		};
+		4FBCC0701728E929004C8C0B /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ARCHS = "$(ARCHS_UNIVERSAL_IPHONE_OS)";
+				CLANG_CXX_LANGUAGE_STANDARD = "compiler-default";
+				CLANG_CXX_LIBRARY = "compiler-default";
+				GCC_CW_ASM_SYNTAX = NO;
+				GCC_C_LANGUAGE_STANDARD = c99;
+				GCC_ENABLE_CPP_EXCEPTIONS = NO;
+				GCC_ENABLE_CPP_RTTI = NO;
+				GCC_PRECOMPILE_PREFIX_HEADER = YES;
+				GCC_PREFIX_HEADER = "AppRTCDemo/AppRTCDemo-Prefix.pch";
+				GCC_THREADSAFE_STATICS = NO;
+				GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
+				HEADER_SEARCH_PATHS = (
+					../../app/webrtc/objc/public,
+					../../..,
+				);
+				INFOPLIST_FILE = "AppRTCDemo/AppRTCDemo-Info.plist";
+				LIBRARY_SEARCH_PATHS = (
+					"$(inherited)",
+					"\"$(SRCROOT)/libs\"",
+				);
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SDKROOT = iphoneos;
+				VALID_ARCHS = "armv7 i386";
+				WRAPPER_EXTENSION = app;
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		4FBCC0461728E929004C8C0B /* Build configuration list for PBXProject "AppRTCDemo" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				4FBCC06C1728E929004C8C0B /* Debug */,
+				4FBCC06D1728E929004C8C0B /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		4FBCC06E1728E929004C8C0B /* Build configuration list for PBXNativeTarget "AppRTCDemo" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				4FBCC06F1728E929004C8C0B /* Debug */,
+				4FBCC0701728E929004C8C0B /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 4FBCC0431728E929004C8C0B /* Project object */;
+}
diff --git a/talk/examples/ios/AppRTCDemo/APPRTCAppClient.h b/talk/examples/ios/AppRTCDemo/APPRTCAppClient.h
new file mode 100644
index 0000000..cac2f17
--- /dev/null
+++ b/talk/examples/ios/AppRTCDemo/APPRTCAppClient.h
@@ -0,0 +1,54 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "GAEChannelClient.h"
+
+// Called when there are RTCIceServers.
+@protocol IceServerDelegate <NSObject>
+
+- (void)onIceServers:(NSArray *)servers;
+
+@end
+
+// Negotiates signaling for chatting with apprtc.appspot.com "rooms".
+// Uses the client<->server specifics of the apprtc AppEngine webapp.
+//
+// To use: create an instance of this object (registering a message handler) and
+// call connectToRoom().  apprtc.appspot.com will signal that is successful via
+// onOpen through the browser channel.  Then you should call sendData() and wait
+// for the registered handler to be called with received messages.
+@interface APPRTCAppClient : NSObject<NSURLConnectionDataDelegate>
+
+@property(nonatomic, assign) id<IceServerDelegate>iceServerDelegate;
+@property(nonatomic, assign) id<GAEMessageHandler>messageHandler;
+
+- (void)connectToRoom:(NSURL *)room;
+- (void)sendData:(NSData *)data;
+
+@end
diff --git a/talk/examples/ios/AppRTCDemo/APPRTCAppClient.m b/talk/examples/ios/AppRTCDemo/APPRTCAppClient.m
new file mode 100644
index 0000000..bcc2329
--- /dev/null
+++ b/talk/examples/ios/AppRTCDemo/APPRTCAppClient.m
@@ -0,0 +1,333 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "APPRTCAppClient.h"
+
+#import <dispatch/dispatch.h>
+
+#import "GAEChannelClient.h"
+#import "RTCIceServer.h"
+
+@interface APPRTCAppClient ()
+
+@property(nonatomic, strong) dispatch_queue_t backgroundQueue;
+@property(nonatomic, copy) NSString *baseURL;
+@property(nonatomic, strong) GAEChannelClient *gaeChannel;
+@property(nonatomic, copy) NSString *postMessageUrl;
+@property(nonatomic, copy) NSString *pcConfig;
+@property(nonatomic, strong) NSMutableString *receivedData;
+@property(atomic, strong) NSMutableArray *sendQueue;
+@property(nonatomic, copy) NSString *token;
+
+@property(nonatomic, assign) BOOL verboseLogging;
+
+@end
+
+@implementation APPRTCAppClient
+
+- (id)init {
+  if (self = [super init]) {
+    _backgroundQueue = dispatch_queue_create("RTCBackgroundQueue", NULL);
+    _sendQueue = [NSMutableArray array];
+    // Uncomment to see Request/Response logging.
+    //_verboseLogging = YES;
+  }
+  return self;
+}
+
+#pragma mark - Public methods
+
+- (void)connectToRoom:(NSURL *)url {
+  NSURLRequest *request = [self getRequestFromUrl:url];
+  [NSURLConnection connectionWithRequest:request delegate:self];
+}
+
+- (void)sendData:(NSData *)data {
+  @synchronized(self) {
+    [self maybeLogMessage:@"Send message"];
+    [self.sendQueue addObject:[data copy]];
+  }
+  [self requestQueueDrainInBackground];
+}
+
+#pragma mark - Internal methods
+
+- (NSTextCheckingResult *)findMatch:(NSString *)regexpPattern
+                         withString:(NSString *)string
+                       errorMessage:(NSString *)errorMessage {
+  NSError *error;
+  NSRegularExpression *regexp =
+      [NSRegularExpression regularExpressionWithPattern:regexpPattern
+                                                options:0
+                                                  error:&error];
+  if (error) {
+    [self maybeLogMessage:
+            [NSString stringWithFormat:@"Failed to create regexp - %@",
+                [error description]]];
+    return nil;
+  }
+  NSRange fullRange = NSMakeRange(0, [string length]);
+  NSArray *matches = [regexp matchesInString:string options:0 range:fullRange];
+  if ([matches count] == 0) {
+    if ([errorMessage length] > 0) {
+      [self maybeLogMessage:string];
+      [self showMessage:
+              [NSString stringWithFormat:@"Missing %@ in HTML.", errorMessage]];
+    }
+    return nil;
+  } else if ([matches count] > 1) {
+    if ([errorMessage length] > 0) {
+      [self maybeLogMessage:string];
+      [self showMessage:[NSString stringWithFormat:@"Too many %@s in HTML.",
+                         errorMessage]];
+    }
+    return nil;
+  }
+  return matches[0];
+}
+
+- (NSURLRequest *)getRequestFromUrl:(NSURL *)url {
+  self.receivedData = [NSMutableString stringWithCapacity:20000];
+  NSString *path =
+      [NSString stringWithFormat:@"https:%@", [url resourceSpecifier]];
+  NSURLRequest *request =
+      [NSURLRequest requestWithURL:[NSURL URLWithString:path]];
+  return request;
+}
+
+- (void)maybeLogMessage:(NSString *)message {
+  if (self.verboseLogging) {
+    NSLog(@"%@", message);
+  }
+}
+
+- (void)requestQueueDrainInBackground {
+  dispatch_async(self.backgroundQueue, ^(void) {
+    // TODO(hughv): This can block the UI thread.  Fix.
+    @synchronized(self) {
+      if ([self.postMessageUrl length] < 1) {
+        return;
+      }
+      for (NSData *data in self.sendQueue) {
+        NSString *url = [NSString stringWithFormat:@"%@/%@",
+                         self.baseURL,
+                         self.postMessageUrl];
+        [self sendData:data withUrl:url];
+      }
+      [self.sendQueue removeAllObjects];
+    }
+  });
+}
+
+- (void)sendData:(NSData *)data withUrl:(NSString *)url {
+  NSMutableURLRequest *request =
+      [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
+  request.HTTPMethod = @"POST";
+  [request setHTTPBody:data];
+  NSURLResponse *response;
+  NSError *error;
+  NSData *responseData = [NSURLConnection sendSynchronousRequest:request
+                                               returningResponse:&response
+                                                           error:&error];
+  NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
+  int status = [httpResponse statusCode];
+  NSAssert(status == 200,
+           @"Bad response [%d] to message: %@\n\n%@",
+           status,
+           [NSString stringWithUTF8String:[data bytes]],
+           [NSString stringWithUTF8String:[responseData bytes]]);
+}
+
+- (void)showMessage:(NSString *)message {
+  NSLog(@"%@", message);
+  UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Unable to join"
+                                                      message:message
+                                                     delegate:nil
+                                            cancelButtonTitle:@"OK"
+                                            otherButtonTitles:nil];
+  [alertView show];
+}
+
+- (void)updateIceServers:(NSMutableArray *)iceServers
+          withTurnServer:(NSString *)turnServerUrl {
+  if ([turnServerUrl length] < 1) {
+    [self.iceServerDelegate onIceServers:iceServers];
+    return;
+  }
+  dispatch_async(self.backgroundQueue, ^(void) {
+    NSMutableURLRequest *request = [NSMutableURLRequest
+        requestWithURL:[NSURL URLWithString:turnServerUrl]];
+    [request addValue:@"Mozilla/5.0" forHTTPHeaderField:@"user-agent"];
+    [request addValue:@"https://apprtc.appspot.com"
+        forHTTPHeaderField:@"origin"];
+    NSURLResponse *response;
+    NSError *error;
+    NSData *responseData = [NSURLConnection sendSynchronousRequest:request
+                                                 returningResponse:&response
+                                                             error:&error];
+    if (!error) {
+      NSDictionary *json = [NSJSONSerialization JSONObjectWithData:responseData
+                                                           options:0
+                                                             error:&error];
+      NSAssert(!error, @"Unable to parse.  %@", error.localizedDescription);
+      NSString *username = json[@"username"];
+      NSString *turnServer = json[@"turn"];
+      NSString *password = json[@"password"];
+      NSString *fullUrl =
+          [NSString stringWithFormat:@"turn:%@@%@", username, turnServer];
+      RTCIceServer *iceServer =
+          [[RTCIceServer alloc] initWithUri:[NSURL URLWithString:fullUrl]
+                                   password:password];
+      [iceServers addObject:iceServer];
+    } else {
+      NSLog(@"Unable to get TURN server.  Error: %@", error.description);
+    }
+
+    dispatch_async(dispatch_get_main_queue(), ^(void) {
+      [self.iceServerDelegate onIceServers:iceServers];
+    });
+  });
+}
+
+#pragma mark - NSURLConnectionDataDelegate methods
+
+- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
+  NSString *roomHtml = [NSString stringWithUTF8String:[data bytes]];
+  [self maybeLogMessage:
+          [NSString stringWithFormat:@"Received %d chars", [roomHtml length]]];
+  [self.receivedData appendString:roomHtml];
+}
+
+- (void)connection:(NSURLConnection *)connection
+    didReceiveResponse:(NSURLResponse *)response {
+  NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
+  int statusCode = [httpResponse statusCode];
+  [self maybeLogMessage:
+          [NSString stringWithFormat:
+                  @"Response received\nURL\n%@\nStatus [%d]\nHeaders\n%@",
+              [httpResponse URL],
+              statusCode,
+              [httpResponse allHeaderFields]]];
+  NSAssert(statusCode == 200, @"Invalid response  of %d received.", statusCode);
+}
+
+- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
+  [self maybeLogMessage:[NSString stringWithFormat:@"finished loading %d chars",
+                         [self.receivedData length]]];
+  NSTextCheckingResult *result =
+      [self findMatch:@".*\n *Sorry, this room is full\\..*"
+            withString:self.receivedData
+          errorMessage:nil];
+  if (result) {
+    [self showMessage:@"Room full"];
+    return;
+  }
+
+  NSString *fullUrl = [[[connection originalRequest] URL] absoluteString];
+  NSRange queryRange = [fullUrl rangeOfString:@"?"];
+  self.baseURL = [fullUrl substringToIndex:queryRange.location];
+  [self maybeLogMessage:[NSString stringWithFormat:@"URL\n%@", self.baseURL]];
+
+  result = [self findMatch:@".*\n *openChannel\\('([^']*)'\\);\n.*"
+                withString:self.receivedData
+              errorMessage:@"channel token"];
+  if (!result) {
+    return;
+  }
+  self.token = [self.receivedData substringWithRange:[result rangeAtIndex:1]];
+  [self maybeLogMessage:[NSString stringWithFormat:@"Token\n%@", self.token]];
+
+  result =
+      [self findMatch:@".*\n *path = '/(message\\?r=.+)' \\+ '(&u=[0-9]+)';\n.*"
+            withString:self.receivedData
+          errorMessage:@"postMessage URL"];
+  if (!result) {
+    return;
+  }
+  self.postMessageUrl =
+      [NSString stringWithFormat:@"%@%@",
+          [self.receivedData substringWithRange:[result rangeAtIndex:1]],
+          [self.receivedData substringWithRange:[result rangeAtIndex:2]]];
+  [self maybeLogMessage:[NSString stringWithFormat:@"POST message URL\n%@",
+                         self.postMessageUrl]];
+
+  result = [self findMatch:@".*\n *var pc_config = (\\{[^\n]*\\});\n.*"
+                withString:self.receivedData
+              errorMessage:@"pc_config"];
+  if (!result) {
+    return;
+  }
+  NSString *pcConfig =
+      [self.receivedData substringWithRange:[result rangeAtIndex:1]];
+  [self maybeLogMessage:
+          [NSString stringWithFormat:@"PC Config JSON\n%@", pcConfig]];
+
+  result = [self findMatch:@".*\n *requestTurn\\('([^\n]*)'\\);\n.*"
+                withString:self.receivedData
+              errorMessage:@"channel token"];
+  NSString *turnServerUrl;
+  if (result) {
+    turnServerUrl =
+        [self.receivedData substringWithRange:[result rangeAtIndex:1]];
+    [self maybeLogMessage:
+            [NSString stringWithFormat:@"TURN server request URL\n%@",
+                turnServerUrl]];
+  }
+
+  NSError *error;
+  NSData *pcData = [pcConfig dataUsingEncoding:NSUTF8StringEncoding];
+  NSDictionary *json =
+      [NSJSONSerialization JSONObjectWithData:pcData options:0 error:&error];
+  NSAssert(!error, @"Unable to parse.  %@", error.localizedDescription);
+  NSArray *servers = [json objectForKey:@"iceServers"];
+  NSMutableArray *iceServers = [NSMutableArray array];
+  for (NSDictionary *server in servers) {
+    NSString *url = [server objectForKey:@"url"];
+    NSString *credential = [server objectForKey:@"credential"];
+    if (!credential) {
+      credential = @"";
+    }
+    [self maybeLogMessage:
+            [NSString stringWithFormat:@"url [%@] - credential [%@]",
+                url,
+                credential]];
+    RTCIceServer *iceServer =
+        [[RTCIceServer alloc] initWithUri:[NSURL URLWithString:url]
+                                 password:credential];
+    [iceServers addObject:iceServer];
+  }
+  [self updateIceServers:iceServers withTurnServer:turnServerUrl];
+
+  [self maybeLogMessage:
+          [NSString stringWithFormat:@"About to open GAE with token:  %@",
+              self.token]];
+  self.gaeChannel =
+      [[GAEChannelClient alloc] initWithToken:self.token
+                                     delegate:self.messageHandler];
+}
+
+@end
diff --git a/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.h b/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.h
new file mode 100644
index 0000000..82b07f0
--- /dev/null
+++ b/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.h
@@ -0,0 +1,53 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 <UIKit/UIKit.h>
+
+#import "GAEChannelClient.h"
+#import "APPRTCAppClient.h"
+#import "RTCSessionDescriptonDelegate.h"
+
+// Used to send a message to an apprtc.appspot.com "room".
+@protocol APPRTCSendMessage<NSObject>
+
+- (void)sendData:(NSData *)data;
+
+@end
+
+@class APPRTCViewController;
+
+// The main application class of the AppRTCDemo iOS app demonstrating
+// interoperability between the Objcective C implementation of PeerConnection
+// and the apprtc.appspot.com demo webapp.
+@interface APPRTCAppDelegate : UIResponder<IceServerDelegate,
+    GAEMessageHandler, APPRTCSendMessage, RTCSessionDescriptonDelegate,
+    UIApplicationDelegate>
+
+@property (strong, nonatomic) UIWindow *window;
+@property (strong, nonatomic) APPRTCViewController *viewController;
+
+@end
diff --git a/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.m b/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.m
new file mode 100644
index 0000000..0c429a0
--- /dev/null
+++ b/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.m
@@ -0,0 +1,370 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "APPRTCAppDelegate.h"
+
+#import "APPRTCViewController.h"
+#import "RTCIceCandidate.h"
+#import "RTCIceServer.h"
+#import "RTCMediaConstraints.h"
+#import "RTCMediaStream.h"
+#import "RTCPair.h"
+#import "RTCPeerConnection.h"
+#import "RTCPeerConnectionDelegate.h"
+#import "RTCPeerConnectionFactory.h"
+#import "RTCSessionDescription.h"
+
+@interface PCObserver : NSObject<RTCPeerConnectionDelegate>
+
+- (id)initWithDelegate:(id<APPRTCSendMessage>)delegate;
+
+@end
+
+@implementation PCObserver {
+  id<APPRTCSendMessage> _delegate;
+}
+
+- (id)initWithDelegate:(id<APPRTCSendMessage>)delegate {
+  if (self = [super init]) {
+    _delegate = delegate;
+  }
+  return self;
+}
+
+- (void)peerConnectionOnError:(RTCPeerConnection *)peerConnection {
+  NSLog(@"PCO onError.");
+  NSAssert(NO, @"PeerConnection failed.");
+}
+
+- (void)peerConnection:(RTCPeerConnection *)peerConnection
+    onSignalingStateChange:(RTCSignalingState)stateChanged {
+  NSLog(@"PCO onSignalingStateChange.");
+}
+
+- (void)peerConnection:(RTCPeerConnection *)peerConnection
+           onAddStream:(RTCMediaStream *)stream {
+  NSLog(@"PCO onAddStream.");
+  dispatch_async(dispatch_get_main_queue(), ^(void) {
+    NSAssert([stream.audioTracks count] >= 1,
+             @"Expected at least 1 audio stream");
+    //NSAssert([stream.videoTracks count] >= 1,
+    //         @"Expected at least 1 video stream");
+    // TODO(hughv): Add video support
+  });
+}
+
+- (void)peerConnection:(RTCPeerConnection *)peerConnection
+        onRemoveStream:(RTCMediaStream *)stream {
+  NSLog(@"PCO onRemoveStream.");
+  // TODO(hughv): Remove video track.
+}
+
+- (void)
+    peerConnectionOnRenegotiationNeeded:(RTCPeerConnection *)peerConnection {
+  NSLog(@"PCO onRenegotiationNeeded.");
+  // TODO(hughv): Handle this.
+}
+
+- (void)peerConnection:(RTCPeerConnection *)peerConnection
+        onIceCandidate:(RTCIceCandidate *)candidate {
+  NSLog(@"PCO onIceCandidate.\n  Mid[%@] Index[%d] Sdp[%@]",
+        candidate.sdpMid,
+        candidate.sdpMLineIndex,
+        candidate.sdp);
+  NSDictionary *json =
+      @{ @"type" : @"candidate",
+         @"label" : [NSNumber numberWithInt:candidate.sdpMLineIndex],
+         @"id" : candidate.sdpMid,
+         @"candidate" : candidate.sdp };
+  NSError *error;
+  NSData *data =
+      [NSJSONSerialization dataWithJSONObject:json options:0 error:&error];
+  if (!error) {
+    [_delegate sendData:data];
+  } else {
+    NSAssert(NO, @"Unable to serialize JSON object with error: %@",
+             error.localizedDescription);
+  }
+}
+
+- (void)peerConnection:(RTCPeerConnection *)peerConnection
+    onIceGatheringChange:(RTCIceGatheringState)newState {
+  NSLog(@"PCO onIceGatheringChange. %d", newState);
+}
+
+- (void)peerConnection:(RTCPeerConnection *)peerConnection
+    onIceConnectionChange:(RTCIceConnectionState)newState {
+  NSLog(@"PCO onIceConnectionChange. %d", newState);
+}
+
+@end
+
+@interface APPRTCAppDelegate ()
+
+@property(nonatomic, strong) APPRTCAppClient *client;
+@property(nonatomic, strong) PCObserver *pcObserver;
+@property(nonatomic, strong) RTCPeerConnection *peerConnection;
+@property(nonatomic, strong) RTCPeerConnectionFactory *peerConnectionFactory;
+@property(nonatomic, strong) NSMutableArray *queuedRemoteCandidates;
+
+@end
+
+@implementation APPRTCAppDelegate
+
+#pragma mark - UIApplicationDelegate methods
+
+- (BOOL)application:(UIApplication *)application
+    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+  self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
+  self.viewController =
+      [[APPRTCViewController alloc] initWithNibName:@"RTCViewController"
+                                             bundle:nil];
+  self.window.rootViewController = self.viewController;
+  [self.window makeKeyAndVisible];
+  return YES;
+}
+
+- (void)applicationWillResignActive:(UIApplication *)application {
+  [self displayLogMessage:@"Application lost focus, connection broken."];
+  [self disconnect];
+  [self.viewController resetUI];
+}
+
+- (void)applicationDidEnterBackground:(UIApplication *)application {
+}
+
+- (void)applicationWillEnterForeground:(UIApplication *)application {
+}
+
+- (void)applicationDidBecomeActive:(UIApplication *)application {
+}
+
+- (void)applicationWillTerminate:(UIApplication *)application {
+}
+
+- (BOOL)application:(UIApplication *)application
+              openURL:(NSURL *)url
+    sourceApplication:(NSString *)sourceApplication
+           annotation:(id)annotation {
+  if (self.client) {
+    return NO;
+  }
+  self.client = [[APPRTCAppClient alloc] init];
+  self.client.iceServerDelegate = self;
+  self.client.messageHandler = self;
+  [self.client connectToRoom:url];
+  return YES;
+}
+
+- (void)displayLogMessage:(NSString *)message {
+  NSLog(@"%@", message);
+  [self.viewController displayText:message];
+}
+
+#pragma mark - RTCSendMessage method
+
+- (void)sendData:(NSData *)data {
+  [self.client sendData:data];
+}
+
+#pragma mark - IceServerDelegate method
+
+- (void)onIceServers:(NSArray *)servers {
+  self.queuedRemoteCandidates = [NSMutableArray array];
+  self.peerConnectionFactory = [[RTCPeerConnectionFactory alloc] init];
+  RTCMediaConstraints *constraints = [[RTCMediaConstraints alloc] init];
+  self.pcObserver = [[PCObserver alloc] initWithDelegate:self];
+  self.peerConnection =
+      [self.peerConnectionFactory peerConnectionWithIceServers:servers
+                                                   constraints:constraints
+                                                      delegate:self.pcObserver];
+  RTCMediaStream *lms =
+      [self.peerConnectionFactory mediaStreamWithLabel:@"ARDAMS"];
+  // TODO(hughv): Add video.
+  [lms addAudioTrack:[self.peerConnectionFactory audioTrackWithId:@"ARDAMSa0"]];
+  [self.peerConnection addStream:lms withConstraints:constraints];
+  [self displayLogMessage:@"onIceServers - add local stream."];
+}
+
+#pragma mark - GAEMessageHandler methods
+
+- (void)onOpen {
+  [self displayLogMessage:@"GAE onOpen - create offer."];
+  RTCPair *audio =
+      [[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" value:@"true"];
+  // TODO(hughv): Add video.
+  //  RTCPair *video = [[RTCPair alloc] initWithKey:@"OfferToReceiveVideo"
+  //                                          value:@"true"];
+  NSArray *mandatory = @[ audio /*, video*/ ];
+  RTCMediaConstraints *constraints =
+      [[RTCMediaConstraints alloc] initWithMandatoryConstraints:mandatory
+                                            optionalConstraints:nil];
+  [self.peerConnection createOfferWithDelegate:self constraints:constraints];
+  [self displayLogMessage:@"PC - createOffer."];
+}
+
+- (void)onMessage:(NSString *)data {
+  NSString *message = [self unHTMLifyString:data];
+  NSError *error;
+  NSDictionary *objects = [NSJSONSerialization
+      JSONObjectWithData:[message dataUsingEncoding:NSUTF8StringEncoding]
+                 options:0
+                   error:&error];
+  NSAssert(!error,
+           @"%@",
+           [NSString stringWithFormat:@"Error: %@", error.description]);
+  NSAssert([objects count] > 0, @"Invalid JSON object");
+  NSString *value = [objects objectForKey:@"type"];
+  [self displayLogMessage:
+          [NSString stringWithFormat:@"GAE onMessage type - %@", value]];
+  if ([value compare:@"candidate"] == NSOrderedSame) {
+    NSString *mid = [objects objectForKey:@"id"];
+    NSNumber *sdpLineIndex = [objects objectForKey:@"label"];
+    NSString *sdp = [objects objectForKey:@"candidate"];
+    RTCIceCandidate *candidate =
+        [[RTCIceCandidate alloc] initWithMid:mid
+                                       index:sdpLineIndex.intValue
+                                         sdp:sdp];
+    if (self.queuedRemoteCandidates) {
+      [self.queuedRemoteCandidates addObject:candidate];
+    } else {
+      [self.peerConnection addIceCandidate:candidate];
+    }
+  } else if (([value compare:@"offer"] == NSOrderedSame) ||
+             ([value compare:@"answer"] == NSOrderedSame)) {
+    NSString *sdpString = [objects objectForKey:@"sdp"];
+    RTCSessionDescription *sdp =
+        [[RTCSessionDescription alloc] initWithType:value sdp:sdpString];
+    [self.peerConnection setRemoteDescriptionWithDelegate:self
+                                       sessionDescription:sdp];
+    [self displayLogMessage:@"PC - setRemoteDescription."];
+  } else if ([value compare:@"bye"] == NSOrderedSame) {
+    [self disconnect];
+  } else {
+    NSAssert(NO, @"Invalid message: %@", data);
+  }
+}
+
+- (void)onClose {
+  [self displayLogMessage:@"GAE onClose."];
+  [self disconnect];
+}
+
+- (void)onError:(int)code withDescription:(NSString *)description {
+  [self displayLogMessage:
+          [NSString stringWithFormat:@"GAE onError:  %@", description]];
+  [self disconnect];
+}
+
+#pragma mark - RTCSessionDescriptonDelegate methods
+
+- (void)peerConnection:(RTCPeerConnection *)peerConnection
+    createSessionDescriptionCompleted:(RTCSessionDescription *)sdp
+                            withError:(NSError *)error {
+  if (error) {
+    [self displayLogMessage:@"SDP onFailure."];
+    NSAssert(NO, error.description);
+    return;
+  }
+
+  [self displayLogMessage:@"SDP onSuccess(SDP) - set local description."];
+  [self.peerConnection setLocalDescriptionWithDelegate:self
+                                    sessionDescription:sdp];
+  [self displayLogMessage:@"PC setLocalDescription."];
+  dispatch_async(dispatch_get_main_queue(), ^(void) {
+    NSDictionary *json = @{ @"type" : sdp.type, @"sdp" : sdp.description };
+    NSError *error;
+    NSData *data =
+        [NSJSONSerialization dataWithJSONObject:json options:0 error:&error];
+    NSAssert(!error,
+             @"%@",
+             [NSString stringWithFormat:@"Error: %@", error.description]);
+    [self sendData:data];
+  });
+}
+
+- (void)peerConnection:(RTCPeerConnection *)peerConnection
+    setSessionDescriptionCompletedWithError:(NSError *)error {
+  if (error) {
+    [self displayLogMessage:@"SDP onFailure."];
+    NSAssert(NO, error.description);
+    return;
+  }
+
+  [self displayLogMessage:@"SDP onSuccess() - possibly drain candidates"];
+  dispatch_async(dispatch_get_main_queue(), ^(void) {
+    // TODO(hughv): Handle non-initiator case.  http://s10/46622051
+    if (self.peerConnection.remoteDescription) {
+      [self displayLogMessage:@"SDP onSuccess - drain candidates"];
+      [self drainRemoteCandidates];
+    }
+  });
+}
+
+#pragma mark - internal methods
+
+- (void)disconnect {
+  [self.client
+      sendData:[@"{\"type\": \"bye\"}" dataUsingEncoding:NSUTF8StringEncoding]];
+  self.peerConnection = nil;
+  self.peerConnectionFactory = nil;
+  self.pcObserver = nil;
+  self.client.iceServerDelegate = nil;
+  self.client.messageHandler = nil;
+  self.client = nil;
+}
+
+- (void)drainRemoteCandidates {
+  for (RTCIceCandidate *candidate in self.queuedRemoteCandidates) {
+    [self.peerConnection addIceCandidate:candidate];
+  }
+  self.queuedRemoteCandidates = nil;
+}
+
+- (NSString *)unHTMLifyString:(NSString *)base {
+  // TODO(hughv): Investigate why percent escapes are being added.  Removing
+  // them isn't necessary on Android.
+  // convert HTML escaped characters to UTF8.
+  NSString *removePercent =
+      [base stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+  // remove leading and trailing ".
+  NSRange range;
+  range.length = [removePercent length] - 2;
+  range.location = 1;
+  NSString *removeQuotes = [removePercent substringWithRange:range];
+  // convert \" to ".
+  NSString *removeEscapedQuotes =
+      [removeQuotes stringByReplacingOccurrencesOfString:@"\\\""
+                                              withString:@"\""];
+  // convert \\ to \.
+  NSString *removeBackslash =
+      [removeEscapedQuotes stringByReplacingOccurrencesOfString:@"\\\\"
+                                                     withString:@"\\"];
+  return removeBackslash;
+}
+
+@end
diff --git a/talk/examples/ios/AppRTCDemo/APPRTCViewController.h b/talk/examples/ios/AppRTCDemo/APPRTCViewController.h
new file mode 100644
index 0000000..6b107a5
--- /dev/null
+++ b/talk/examples/ios/AppRTCDemo/APPRTCViewController.h
@@ -0,0 +1,40 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 <UIKit/UIKit.h>
+
+// The view controller that is displayed when AppRTCDemo is loaded.
+@interface APPRTCViewController : UIViewController<UITextFieldDelegate>
+
+@property (weak, nonatomic) IBOutlet UITextField *textField;
+@property (weak, nonatomic) IBOutlet UITextView *textInstructions;
+@property (weak, nonatomic) IBOutlet UITextView *textOutput;
+
+- (void)displayText:(NSString *)text;
+- (void)resetUI;
+
+@end
diff --git a/talk/examples/ios/AppRTCDemo/APPRTCViewController.m b/talk/examples/ios/AppRTCDemo/APPRTCViewController.m
new file mode 100644
index 0000000..928686b
--- /dev/null
+++ b/talk/examples/ios/AppRTCDemo/APPRTCViewController.m
@@ -0,0 +1,83 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "APPRTCViewController.h"
+
+@interface APPRTCViewController ()
+
+@end
+
+@implementation APPRTCViewController
+
+- (void)viewDidLoad {
+  [super viewDidLoad];
+  self.textField.delegate = self;
+}
+
+- (void)displayText:(NSString *)text {
+  dispatch_async(dispatch_get_main_queue(), ^(void) {
+    NSString *output =
+        [NSString stringWithFormat:@"%@\n%@", self.textOutput.text, text];
+    self.textOutput.text = output;
+  });
+}
+
+- (void)resetUI {
+  self.textField.text = nil;
+  self.textField.hidden = NO;
+  self.textInstructions.hidden = NO;
+  self.textOutput.hidden = YES;
+  self.textOutput.text = nil;
+}
+
+#pragma mark - UITextFieldDelegate
+
+- (void)textFieldDidEndEditing:(UITextField *)textField {
+  NSString *room = textField.text;
+  if ([room length] == 0) {
+    return;
+  }
+  textField.hidden = YES;
+  self.textInstructions.hidden = YES;
+  self.textOutput.hidden = NO;
+  // TODO(hughv): Instead of launching a URL with apprtc scheme, change to
+  // prepopulating the textField with a valid URL missing the room.  This allows
+  // the user to have the simplicity of just entering the room or the ability to
+  // override to a custom appspot instance.  Remove apprtc:// when this is done.
+  NSString *url =
+      [NSString stringWithFormat:@"apprtc://apprtc.appspot.com/?r=%@", room];
+  [[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]];
+}
+
+- (BOOL)textFieldShouldReturn:(UITextField *)textField {
+  // There is no other control that can take focus, so manually resign focus
+  // when return (Join) is pressed to trigger |textFieldDidEndEditing|.
+  [textField resignFirstResponder];
+  return YES;
+}
+
+@end
diff --git a/talk/examples/ios/AppRTCDemo/AppRTCDemo-Info.plist b/talk/examples/ios/AppRTCDemo/AppRTCDemo-Info.plist
new file mode 100644
index 0000000..3ab57ed
--- /dev/null
+++ b/talk/examples/ios/AppRTCDemo/AppRTCDemo-Info.plist
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleDisplayName</key>
+	<string>${PRODUCT_NAME}</string>
+	<key>CFBundleExecutable</key>
+	<string>${EXECUTABLE_NAME}</string>
+	<key>CFBundleIcons</key>
+	<dict>
+		<key>CFBundlePrimaryIcon</key>
+		<dict>
+			<key>CFBundleIconFiles</key>
+			<array>
+				<string>Icon.png</string>
+			</array>
+		</dict>
+	</dict>
+	<key>CFBundleIdentifier</key>
+	<string>com.Google.${PRODUCT_NAME:rfc1034identifier}</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>${PRODUCT_NAME}</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleSignature</key>
+	<string>????</string>
+	<key>CFBundleURLTypes</key>
+	<array>
+		<dict>
+			<key>CFBundleTypeRole</key>
+			<string>Editor</string>
+			<key>CFBundleURLName</key>
+			<string>com.google.apprtcdemo</string>
+			<key>CFBundleURLSchemes</key>
+			<array>
+				<string>apprtc</string>
+			</array>
+		</dict>
+	</array>
+	<key>CFBundleVersion</key>
+	<string>1.0</string>
+	<key>LSRequiresIPhoneOS</key>
+	<true/>
+	<key>UIRequiredDeviceCapabilities</key>
+	<array>
+		<string>armv7</string>
+	</array>
+	<key>UIStatusBarTintParameters</key>
+	<dict>
+		<key>UINavigationBar</key>
+		<dict>
+			<key>Style</key>
+			<string>UIBarStyleDefault</string>
+			<key>Translucent</key>
+			<false/>
+		</dict>
+	</dict>
+	<key>UISupportedInterfaceOrientations</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+</dict>
+</plist>
diff --git a/talk/examples/ios/AppRTCDemo/AppRTCDemo-Prefix.pch b/talk/examples/ios/AppRTCDemo/AppRTCDemo-Prefix.pch
new file mode 100644
index 0000000..3ac2c3b
--- /dev/null
+++ b/talk/examples/ios/AppRTCDemo/AppRTCDemo-Prefix.pch
@@ -0,0 +1,40 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+//
+// Prefix header for all source files of the 'AppRTCDemo' target in the
+// 'AppRTCDemo' project
+//
+
+#import <Availability.h>
+
+#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0
+#warning "This project uses features only available in iOS SDK 6.0 and later."
+#endif
+
+#import <UIKit/UIKit.h>
+#import <Foundation/Foundation.h>
diff --git a/talk/examples/ios/AppRTCDemo/Default.png b/talk/examples/ios/AppRTCDemo/Default.png
new file mode 100644
index 0000000..4c8ca6f6
--- /dev/null
+++ b/talk/examples/ios/AppRTCDemo/Default.png
Binary files differ
diff --git a/talk/examples/ios/AppRTCDemo/GAEChannelClient.h b/talk/examples/ios/AppRTCDemo/GAEChannelClient.h
new file mode 100644
index 0000000..49a928d
--- /dev/null
+++ b/talk/examples/ios/AppRTCDemo/GAEChannelClient.h
@@ -0,0 +1,49 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 <UIKit/UIKit.h>
+
+// These methods will be called by the AppEngine chanel.  The documentation
+// for these methods is found here.  (Yes, it is a JS API.)
+// https://developers.google.com/appengine/docs/java/channel/javascript
+@protocol GAEMessageHandler<NSObject>
+
+- (void)onOpen;
+- (void)onMessage:(NSString *)data;
+- (void)onClose;
+- (void)onError:(int)code withDescription:(NSString *)description;
+
+@end
+
+// Initialize with a token for an AppRTC data channel.  This will load
+// ios_channel.html and use the token to establish a data channel between the
+// application and AppEngine.
+@interface GAEChannelClient : NSObject<UIWebViewDelegate>
+
+- (id)initWithToken:(NSString *)token delegate:(id<GAEMessageHandler>)delegate;
+
+@end
diff --git a/talk/examples/ios/AppRTCDemo/GAEChannelClient.m b/talk/examples/ios/AppRTCDemo/GAEChannelClient.m
new file mode 100644
index 0000000..9126f67
--- /dev/null
+++ b/talk/examples/ios/AppRTCDemo/GAEChannelClient.m
@@ -0,0 +1,104 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "GAEChannelClient.h"
+
+#import "RTCPeerConnectionFactory.h"
+
+@interface GAEChannelClient ()
+
+@property(nonatomic, assign) id<GAEMessageHandler> delegate;
+@property(nonatomic, strong) UIWebView *webView;
+
+@end
+
+@implementation GAEChannelClient
+
+- (id)initWithToken:(NSString *)token delegate:(id<GAEMessageHandler>)delegate {
+  self = [super init];
+  if (self) {
+    _webView = [[UIWebView alloc] init];
+    _webView.delegate = self;
+    _delegate = delegate;
+    NSString *htmlPath =
+        [[NSBundle mainBundle] pathForResource:@"ios_channel" ofType:@"html"];
+    NSURL *htmlUrl = [NSURL fileURLWithPath:htmlPath];
+    NSString *path = [NSString stringWithFormat:@"%@?token=%@",
+                      [htmlUrl absoluteString],
+                      token];
+
+    [_webView
+        loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:path]]];
+  }
+  return self;
+}
+
+- (void)dealloc {
+  _webView.delegate = nil;
+  [_webView stopLoading];
+}
+
+#pragma mark - UIWebViewDelegate method
+
+- (BOOL)webView:(UIWebView *)webView
+    shouldStartLoadWithRequest:(NSURLRequest *)request
+                navigationType:(UIWebViewNavigationType)navigationType {
+  NSString *scheme = [request.URL scheme];
+  if ([scheme compare:@"js-frame"] != NSOrderedSame) {
+    return YES;
+  }
+  NSString *resourceSpecifier = [request.URL resourceSpecifier];
+  NSRange range = [resourceSpecifier rangeOfString:@":"];
+  NSString *method;
+  NSString *message;
+  if (range.length == 0 && range.location == NSNotFound) {
+    method = resourceSpecifier;
+  } else {
+    method = [resourceSpecifier substringToIndex:range.location];
+    message = [resourceSpecifier substringFromIndex:range.location + 1];
+  }
+  dispatch_async(dispatch_get_main_queue(), ^(void) {
+    if ([method compare:@"onopen"] == NSOrderedSame) {
+      [self.delegate onOpen];
+    } else if ([method compare:@"onmessage"] == NSOrderedSame) {
+      [self.delegate onMessage:message];
+    } else if ([method compare:@"onclose"] == NSOrderedSame) {
+      [self.delegate onClose];
+    } else if ([method compare:@"onerror"] == NSOrderedSame) {
+      // TODO(hughv): Get error.
+      int code = -1;
+      NSString *description = message;
+      [self.delegate onError:code withDescription:description];
+    } else {
+      NSAssert(NO, @"Invalid message sent from UIWebView: %@",
+               resourceSpecifier);
+    }
+  });
+  return YES;
+}
+
+@end
diff --git a/talk/examples/ios/AppRTCDemo/en.lproj/APPRTCViewController.xib b/talk/examples/ios/AppRTCDemo/en.lproj/APPRTCViewController.xib
new file mode 100644
index 0000000..cd73ea6
--- /dev/null
+++ b/talk/examples/ios/AppRTCDemo/en.lproj/APPRTCViewController.xib
@@ -0,0 +1,529 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<archive type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="8.00">
+	<data>
+		<int key="IBDocument.SystemTarget">1552</int>
+		<string key="IBDocument.SystemVersion">12D78</string>
+		<string key="IBDocument.InterfaceBuilderVersion">3084</string>
+		<string key="IBDocument.AppKitVersion">1187.37</string>
+		<string key="IBDocument.HIToolboxVersion">626.00</string>
+		<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
+			<string key="NS.key.0">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+			<string key="NS.object.0">2083</string>
+		</object>
+		<array key="IBDocument.IntegratedClassDependencies">
+			<string>IBNSLayoutConstraint</string>
+			<string>IBProxyObject</string>
+			<string>IBUITextField</string>
+			<string>IBUITextView</string>
+			<string>IBUIView</string>
+		</array>
+		<array key="IBDocument.PluginDependencies">
+			<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+		</array>
+		<object class="NSMutableDictionary" key="IBDocument.Metadata">
+			<string key="NS.key.0">PluginDependencyRecalculationVersion</string>
+			<integer value="1" key="NS.object.0"/>
+		</object>
+		<array class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
+			<object class="IBProxyObject" id="372490531">
+				<string key="IBProxiedObjectIdentifier">IBFilesOwner</string>
+				<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+			</object>
+			<object class="IBProxyObject" id="843779117">
+				<string key="IBProxiedObjectIdentifier">IBFirstResponder</string>
+				<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+			</object>
+			<object class="IBUIView" id="774585933">
+				<nil key="NSNextResponder"/>
+				<int key="NSvFlags">274</int>
+				<array class="NSMutableArray" key="NSSubviews">
+					<object class="IBUITextView" id="176994284">
+						<reference key="NSNextResponder" ref="774585933"/>
+						<int key="NSvFlags">292</int>
+						<string key="NSFrame">{{20, 20}, {280, 141}}</string>
+						<reference key="NSSuperview" ref="774585933"/>
+						<reference key="NSNextKeyView" ref="546385578"/>
+						<string key="NSReuseIdentifierKey">_NS:9</string>
+						<object class="NSColor" key="IBUIBackgroundColor" id="621995359">
+							<int key="NSColorSpace">1</int>
+							<bytes key="NSRGB">MSAxIDEAA</bytes>
+						</object>
+						<bool key="IBUIClipsSubviews">YES</bool>
+						<bool key="IBUIUserInteractionEnabled">NO</bool>
+						<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+						<string key="IBUIText">Use Safari and open a URL with a scheme of apprtc to load the test app and connect.  i.e.  apprtc://apprtc.appspot.com/?r=12345678  Or just enter the room below to connect to apprtc.</string>
+						<object class="IBUITextInputTraits" key="IBUITextInputTraits">
+							<int key="IBUIAutocapitalizationType">2</int>
+							<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+						</object>
+						<object class="IBUIFontDescription" key="IBUIFontDescription" id="166497611">
+							<int key="type">1</int>
+							<double key="pointSize">14</double>
+						</object>
+						<object class="NSFont" key="IBUIFont" id="371333696">
+							<string key="NSName">Helvetica</string>
+							<double key="NSSize">14</double>
+							<int key="NSfFlags">16</int>
+						</object>
+					</object>
+					<object class="IBUITextField" id="546385578">
+						<reference key="NSNextResponder" ref="774585933"/>
+						<int key="NSvFlags">292</int>
+						<string key="NSFrame">{{20, 180}, {280, 30}}</string>
+						<reference key="NSSuperview" ref="774585933"/>
+						<reference key="NSNextKeyView" ref="634862110"/>
+						<string key="NSReuseIdentifierKey">_NS:9</string>
+						<bool key="IBUIOpaque">NO</bool>
+						<bool key="IBUIClipsSubviews">YES</bool>
+						<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+						<int key="IBUIContentVerticalAlignment">0</int>
+						<string key="IBUIText"/>
+						<int key="IBUIBorderStyle">3</int>
+						<string key="IBUIPlaceholder">apprtc room</string>
+						<object class="NSColor" key="IBUITextColor">
+							<int key="NSColorSpace">3</int>
+							<bytes key="NSWhite">MAA</bytes>
+							<object class="NSColorSpace" key="NSCustomColorSpace" id="14071810">
+								<int key="NSID">2</int>
+							</object>
+						</object>
+						<bool key="IBUIAdjustsFontSizeToFit">YES</bool>
+						<float key="IBUIMinimumFontSize">17</float>
+						<object class="IBUITextInputTraits" key="IBUITextInputTraits">
+							<int key="IBUIKeyboardType">2</int>
+							<int key="IBUIReturnKeyType">3</int>
+							<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+						</object>
+						<reference key="IBUIFontDescription" ref="166497611"/>
+						<reference key="IBUIFont" ref="371333696"/>
+					</object>
+					<object class="IBUITextView" id="634862110">
+						<reference key="NSNextResponder" ref="774585933"/>
+						<int key="NSvFlags">-2147483356</int>
+						<string key="NSFrame">{{20, 20}, {280, 508}}</string>
+						<reference key="NSSuperview" ref="774585933"/>
+						<string key="NSReuseIdentifierKey">_NS:9</string>
+						<reference key="IBUIBackgroundColor" ref="621995359"/>
+						<bool key="IBUIClipsSubviews">YES</bool>
+						<bool key="IBUIMultipleTouchEnabled">YES</bool>
+						<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+						<bool key="IBUIEditable">NO</bool>
+						<string key="IBUIText"/>
+						<object class="IBUITextInputTraits" key="IBUITextInputTraits">
+							<int key="IBUIAutocapitalizationType">2</int>
+							<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+						</object>
+						<reference key="IBUIFontDescription" ref="166497611"/>
+						<reference key="IBUIFont" ref="371333696"/>
+					</object>
+				</array>
+				<string key="NSFrame">{{0, 20}, {320, 548}}</string>
+				<reference key="NSNextKeyView" ref="176994284"/>
+				<object class="NSColor" key="IBUIBackgroundColor">
+					<int key="NSColorSpace">3</int>
+					<bytes key="NSWhite">MC43NQA</bytes>
+					<reference key="NSCustomColorSpace" ref="14071810"/>
+				</object>
+				<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
+				<object class="IBUISimulatedStatusBarMetrics" key="IBUISimulatedStatusBarMetrics"/>
+				<object class="IBUIScreenMetrics" key="IBUISimulatedDestinationMetrics">
+					<string key="IBUISimulatedSizeMetricsClass">IBUIScreenMetrics</string>
+					<object class="NSMutableDictionary" key="IBUINormalizedOrientationToSizeMap">
+						<bool key="EncodedWithXMLCoder">YES</bool>
+						<array key="dict.sortedKeys">
+							<integer value="1"/>
+							<integer value="3"/>
+						</array>
+						<array key="dict.values">
+							<string>{320, 568}</string>
+							<string>{568, 320}</string>
+						</array>
+					</object>
+					<string key="IBUITargetRuntime">IBCocoaTouchFramework</string>
+					<string key="IBUIDisplayName">Retina 4 Full Screen</string>
+					<int key="IBUIType">2</int>
+				</object>
+				<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+			</object>
+		</array>
+		<object class="IBObjectContainer" key="IBDocument.Objects">
+			<array class="NSMutableArray" key="connectionRecords">
+				<object class="IBConnectionRecord">
+					<object class="IBCocoaTouchOutletConnection" key="connection">
+						<string key="label">view</string>
+						<reference key="source" ref="372490531"/>
+						<reference key="destination" ref="774585933"/>
+					</object>
+					<int key="connectionID">7</int>
+				</object>
+				<object class="IBConnectionRecord">
+					<object class="IBCocoaTouchOutletConnection" key="connection">
+						<string key="label">textField</string>
+						<reference key="source" ref="372490531"/>
+						<reference key="destination" ref="546385578"/>
+					</object>
+					<int key="connectionID">108</int>
+				</object>
+				<object class="IBConnectionRecord">
+					<object class="IBCocoaTouchOutletConnection" key="connection">
+						<string key="label">textInstructions</string>
+						<reference key="source" ref="372490531"/>
+						<reference key="destination" ref="176994284"/>
+					</object>
+					<int key="connectionID">127</int>
+				</object>
+				<object class="IBConnectionRecord">
+					<object class="IBCocoaTouchOutletConnection" key="connection">
+						<string key="label">textOutput</string>
+						<reference key="source" ref="372490531"/>
+						<reference key="destination" ref="634862110"/>
+					</object>
+					<int key="connectionID">138</int>
+				</object>
+			</array>
+			<object class="IBMutableOrderedSet" key="objectRecords">
+				<array key="orderedObjects">
+					<object class="IBObjectRecord">
+						<int key="objectID">0</int>
+						<array key="object" id="0"/>
+						<reference key="children" ref="1000"/>
+						<nil key="parent"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">-1</int>
+						<reference key="object" ref="372490531"/>
+						<reference key="parent" ref="0"/>
+						<string key="objectName">File's Owner</string>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">-2</int>
+						<reference key="object" ref="843779117"/>
+						<reference key="parent" ref="0"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">6</int>
+						<reference key="object" ref="774585933"/>
+						<array class="NSMutableArray" key="children">
+							<object class="IBNSLayoutConstraint" id="117610664">
+								<reference key="firstItem" ref="774585933"/>
+								<int key="firstAttribute">6</int>
+								<int key="relation">0</int>
+								<reference key="secondItem" ref="546385578"/>
+								<int key="secondAttribute">6</int>
+								<float key="multiplier">1</float>
+								<object class="IBNSLayoutSymbolicConstant" key="constant">
+									<double key="value">20</double>
+								</object>
+								<float key="priority">1000</float>
+								<reference key="containingView" ref="774585933"/>
+								<int key="scoringType">8</int>
+								<float key="scoringTypeFloat">29</float>
+								<int key="contentType">3</int>
+							</object>
+							<object class="IBNSLayoutConstraint" id="555801739">
+								<reference key="firstItem" ref="546385578"/>
+								<int key="firstAttribute">3</int>
+								<int key="relation">0</int>
+								<reference key="secondItem" ref="774585933"/>
+								<int key="secondAttribute">3</int>
+								<float key="multiplier">1</float>
+								<object class="IBLayoutConstant" key="constant">
+									<double key="value">180</double>
+								</object>
+								<float key="priority">1000</float>
+								<reference key="containingView" ref="774585933"/>
+								<int key="scoringType">3</int>
+								<float key="scoringTypeFloat">9</float>
+								<int key="contentType">3</int>
+							</object>
+							<object class="IBNSLayoutConstraint" id="860801955">
+								<reference key="firstItem" ref="546385578"/>
+								<int key="firstAttribute">5</int>
+								<int key="relation">0</int>
+								<reference key="secondItem" ref="774585933"/>
+								<int key="secondAttribute">5</int>
+								<float key="multiplier">1</float>
+								<object class="IBNSLayoutSymbolicConstant" key="constant">
+									<double key="value">20</double>
+								</object>
+								<float key="priority">1000</float>
+								<reference key="containingView" ref="774585933"/>
+								<int key="scoringType">8</int>
+								<float key="scoringTypeFloat">29</float>
+								<int key="contentType">3</int>
+							</object>
+							<object class="IBNSLayoutConstraint" id="19985792">
+								<reference key="firstItem" ref="634862110"/>
+								<int key="firstAttribute">3</int>
+								<int key="relation">0</int>
+								<reference key="secondItem" ref="774585933"/>
+								<int key="secondAttribute">3</int>
+								<float key="multiplier">1</float>
+								<object class="IBNSLayoutSymbolicConstant" key="constant">
+									<double key="value">20</double>
+								</object>
+								<float key="priority">1000</float>
+								<reference key="containingView" ref="774585933"/>
+								<int key="scoringType">8</int>
+								<float key="scoringTypeFloat">29</float>
+								<int key="contentType">3</int>
+							</object>
+							<object class="IBNSLayoutConstraint" id="1001701893">
+								<reference key="firstItem" ref="774585933"/>
+								<int key="firstAttribute">6</int>
+								<int key="relation">0</int>
+								<reference key="secondItem" ref="634862110"/>
+								<int key="secondAttribute">6</int>
+								<float key="multiplier">1</float>
+								<object class="IBNSLayoutSymbolicConstant" key="constant">
+									<double key="value">20</double>
+								</object>
+								<float key="priority">1000</float>
+								<reference key="containingView" ref="774585933"/>
+								<int key="scoringType">8</int>
+								<float key="scoringTypeFloat">29</float>
+								<int key="contentType">3</int>
+							</object>
+							<object class="IBNSLayoutConstraint" id="914503793">
+								<reference key="firstItem" ref="774585933"/>
+								<int key="firstAttribute">4</int>
+								<int key="relation">0</int>
+								<reference key="secondItem" ref="634862110"/>
+								<int key="secondAttribute">4</int>
+								<float key="multiplier">1</float>
+								<object class="IBNSLayoutSymbolicConstant" key="constant">
+									<double key="value">20</double>
+								</object>
+								<float key="priority">1000</float>
+								<reference key="containingView" ref="774585933"/>
+								<int key="scoringType">8</int>
+								<float key="scoringTypeFloat">29</float>
+								<int key="contentType">3</int>
+							</object>
+							<object class="IBNSLayoutConstraint" id="858545289">
+								<reference key="firstItem" ref="634862110"/>
+								<int key="firstAttribute">5</int>
+								<int key="relation">0</int>
+								<reference key="secondItem" ref="774585933"/>
+								<int key="secondAttribute">5</int>
+								<float key="multiplier">1</float>
+								<object class="IBNSLayoutSymbolicConstant" key="constant">
+									<double key="value">20</double>
+								</object>
+								<float key="priority">1000</float>
+								<reference key="containingView" ref="774585933"/>
+								<int key="scoringType">8</int>
+								<float key="scoringTypeFloat">29</float>
+								<int key="contentType">3</int>
+							</object>
+							<object class="IBNSLayoutConstraint" id="1039342825">
+								<reference key="firstItem" ref="774585933"/>
+								<int key="firstAttribute">6</int>
+								<int key="relation">0</int>
+								<reference key="secondItem" ref="176994284"/>
+								<int key="secondAttribute">6</int>
+								<float key="multiplier">1</float>
+								<object class="IBNSLayoutSymbolicConstant" key="constant">
+									<double key="value">20</double>
+								</object>
+								<float key="priority">1000</float>
+								<reference key="containingView" ref="774585933"/>
+								<int key="scoringType">8</int>
+								<float key="scoringTypeFloat">29</float>
+								<int key="contentType">3</int>
+							</object>
+							<object class="IBNSLayoutConstraint" id="663764352">
+								<reference key="firstItem" ref="176994284"/>
+								<int key="firstAttribute">3</int>
+								<int key="relation">0</int>
+								<reference key="secondItem" ref="774585933"/>
+								<int key="secondAttribute">3</int>
+								<float key="multiplier">1</float>
+								<object class="IBNSLayoutSymbolicConstant" key="constant">
+									<double key="value">20</double>
+								</object>
+								<float key="priority">1000</float>
+								<reference key="containingView" ref="774585933"/>
+								<int key="scoringType">8</int>
+								<float key="scoringTypeFloat">29</float>
+								<int key="contentType">3</int>
+							</object>
+							<object class="IBNSLayoutConstraint" id="46028745">
+								<reference key="firstItem" ref="176994284"/>
+								<int key="firstAttribute">5</int>
+								<int key="relation">0</int>
+								<reference key="secondItem" ref="774585933"/>
+								<int key="secondAttribute">5</int>
+								<float key="multiplier">1</float>
+								<object class="IBNSLayoutSymbolicConstant" key="constant">
+									<double key="value">20</double>
+								</object>
+								<float key="priority">1000</float>
+								<reference key="containingView" ref="774585933"/>
+								<int key="scoringType">8</int>
+								<float key="scoringTypeFloat">29</float>
+								<int key="contentType">3</int>
+							</object>
+							<reference ref="176994284"/>
+							<reference ref="546385578"/>
+							<reference ref="634862110"/>
+						</array>
+						<reference key="parent" ref="0"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">57</int>
+						<reference key="object" ref="176994284"/>
+						<array class="NSMutableArray" key="children">
+							<object class="IBNSLayoutConstraint" id="234302232">
+								<reference key="firstItem" ref="176994284"/>
+								<int key="firstAttribute">8</int>
+								<int key="relation">0</int>
+								<nil key="secondItem"/>
+								<int key="secondAttribute">0</int>
+								<float key="multiplier">1</float>
+								<object class="IBLayoutConstant" key="constant">
+									<double key="value">141</double>
+								</object>
+								<float key="priority">1000</float>
+								<reference key="containingView" ref="176994284"/>
+								<int key="scoringType">3</int>
+								<float key="scoringTypeFloat">9</float>
+								<int key="contentType">1</int>
+							</object>
+						</array>
+						<reference key="parent" ref="774585933"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">62</int>
+						<reference key="object" ref="46028745"/>
+						<reference key="parent" ref="774585933"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">63</int>
+						<reference key="object" ref="663764352"/>
+						<reference key="parent" ref="774585933"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">66</int>
+						<reference key="object" ref="1039342825"/>
+						<reference key="parent" ref="774585933"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">104</int>
+						<reference key="object" ref="546385578"/>
+						<array class="NSMutableArray" key="children"/>
+						<reference key="parent" ref="774585933"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">107</int>
+						<reference key="object" ref="860801955"/>
+						<reference key="parent" ref="774585933"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">123</int>
+						<reference key="object" ref="234302232"/>
+						<reference key="parent" ref="176994284"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">124</int>
+						<reference key="object" ref="555801739"/>
+						<reference key="parent" ref="774585933"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">126</int>
+						<reference key="object" ref="117610664"/>
+						<reference key="parent" ref="774585933"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">128</int>
+						<reference key="object" ref="634862110"/>
+						<array class="NSMutableArray" key="children"/>
+						<reference key="parent" ref="774585933"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">133</int>
+						<reference key="object" ref="858545289"/>
+						<reference key="parent" ref="774585933"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">136</int>
+						<reference key="object" ref="914503793"/>
+						<reference key="parent" ref="774585933"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">137</int>
+						<reference key="object" ref="1001701893"/>
+						<reference key="parent" ref="774585933"/>
+					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">139</int>
+						<reference key="object" ref="19985792"/>
+						<reference key="parent" ref="774585933"/>
+					</object>
+				</array>
+			</object>
+			<dictionary class="NSMutableDictionary" key="flattenedProperties">
+				<string key="-1.CustomClassName">APPRTCViewController</string>
+				<string key="-1.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+				<string key="-2.CustomClassName">UIResponder</string>
+				<string key="-2.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+				<string key="104.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+				<boolean value="NO" key="104.IBViewMetadataTranslatesAutoresizingMaskIntoConstraints"/>
+				<string key="107.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+				<string key="123.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+				<string key="124.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+				<string key="126.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+				<string key="128.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+				<boolean value="NO" key="128.IBViewMetadataTranslatesAutoresizingMaskIntoConstraints"/>
+				<string key="133.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+				<string key="136.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+				<string key="137.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+				<string key="139.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+				<string key="57.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+				<array class="NSMutableArray" key="57.IBViewMetadataConstraints">
+					<reference ref="234302232"/>
+				</array>
+				<boolean value="NO" key="57.IBViewMetadataTranslatesAutoresizingMaskIntoConstraints"/>
+				<string key="6.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+				<array key="6.IBViewMetadataConstraints">
+					<reference ref="46028745"/>
+					<reference ref="663764352"/>
+					<reference ref="1039342825"/>
+					<reference ref="858545289"/>
+					<reference ref="914503793"/>
+					<reference ref="1001701893"/>
+					<reference ref="19985792"/>
+					<reference ref="860801955"/>
+					<reference ref="555801739"/>
+					<reference ref="117610664"/>
+				</array>
+				<string key="62.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+				<string key="63.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+				<string key="66.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+			</dictionary>
+			<dictionary class="NSMutableDictionary" key="unlocalizedProperties"/>
+			<nil key="activeLocalization"/>
+			<dictionary class="NSMutableDictionary" key="localizations"/>
+			<nil key="sourceID"/>
+			<int key="maxID">139</int>
+		</object>
+		<object class="IBClassDescriber" key="IBDocument.Classes">
+			<array class="NSMutableArray" key="referencedPartialClassDescriptions">
+				<object class="IBPartialClassDescription">
+					<string key="className">NSLayoutConstraint</string>
+					<string key="superclassName">NSObject</string>
+					<object class="IBClassDescriptionSource" key="sourceIdentifier">
+						<string key="majorKey">IBProjectSource</string>
+						<string key="minorKey">./Classes/NSLayoutConstraint.h</string>
+					</object>
+				</object>
+			</array>
+		</object>
+		<int key="IBDocument.localizationMode">0</int>
+		<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaTouchFramework</string>
+		<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
+		<int key="IBDocument.defaultPropertyAccessControl">3</int>
+		<bool key="IBDocument.UseAutolayout">YES</bool>
+		<string key="IBCocoaTouchPluginVersion">2083</string>
+	</data>
+</archive>
diff --git a/talk/examples/ios/AppRTCDemo/ios_channel.html b/talk/examples/ios/AppRTCDemo/ios_channel.html
new file mode 100644
index 0000000..a55b8f4
--- /dev/null
+++ b/talk/examples/ios/AppRTCDemo/ios_channel.html
@@ -0,0 +1,88 @@
+<html>
+  <head>
+    <script src="http://apprtc.appspot.com/_ah/channel/jsapi"></script>
+  </head>
+  <!--
+  Helper HTML that redirects Google AppEngine's Channel API to Objective C.
+  This is done by hosting this page in an iOS application.  The hosting
+  class creates a UIWebView control and implements the UIWebViewDelegate
+  protocol.  Then when there is a channel message, it is encoded in an IFRAME.
+  That IFRAME is added to the DOM which triggers a navigation event
+  |shouldStartLoadWithRequest| in Objective C which can then be routed in the
+  application as desired.
+  -->
+  <body onbeforeunload="closeSocket()" onload="openSocket()">
+    <script type="text/javascript">
+      // QueryString is copy/pasta from
+      // chromium's chrome/test/data/media/html/utils.js.
+      var QueryString = function () {
+        // Allows access to query parameters on the URL; e.g., given a URL like:
+        //    http://<url>/my.html?test=123&bob=123
+        // parameters can now be accessed via QueryString.test or
+        // QueryString.bob.
+        var params = {};
+
+        // RegEx to split out values by &.
+        var r = /([^&=]+)=?([^&]*)/g;
+
+        // Lambda function for decoding extracted match values. Replaces '+'
+        // with space so decodeURIComponent functions properly.
+        function d(s) { return decodeURIComponent(s.replace(/\+/g, ' ')); }
+
+        var match;
+        while (match = r.exec(window.location.search.substring(1)))
+          params[d(match[1])] = d(match[2]);
+
+        return params;
+      } ();
+
+      var channel = null;
+      var socket = null;
+
+      function openSocket() {
+        if (!QueryString.token || !QueryString.token.match(/^[A-z0-9_-]+$/)) {
+          // Send error back to ObjC.  This will assert in GAEChannelClient.m.
+          sendMessageToObjC("JSError:Missing/malformed token parameter " +
+                            QueryString.token);
+          throw "Missing/malformed token parameter: " + QueryString.token;
+        }
+        channel = new goog.appengine.Channel(QueryString.token);
+        socket = channel.open({
+          'onopen': function() {
+            sendMessageToObjC("onopen");
+          },
+          'onmessage': function(msg) {
+            sendMessageToObjC("onmessage:" +
+                              encodeURIComponent(JSON.stringify(msg.data)));
+          },
+          'onclose': function() {
+            sendMessageToObjC("onclose");
+          },
+          'onerror': function(err) {
+            sendMessageToObjC("onerror:" +
+                              encodeURIComponent(JSON.stringify(err.code)) +
+                              ":message:" +
+                              encodeURIComponent(JSON.stringify(err.description)));
+          }
+        });
+      }
+
+      function closeSocket() {
+        socket.close();
+      }
+
+      // Add an IFRAME to the DOM to trigger a navigation event.  Then remove
+      // it as it is no longer needed.  Only one event is generated.
+      function sendMessageToObjC(message) {
+        var iframe = document.createElement("IFRAME");
+        iframe.setAttribute("src", "js-frame:" + message);
+        // For some reason we need to set a non-empty size for the iOS6
+        // simulator...
+        iframe.setAttribute("height", "1px");
+        iframe.setAttribute("width", "1px");
+        document.documentElement.appendChild(iframe);
+        iframe.parentNode.removeChild(iframe);
+      }
+    </script>
+  </body>
+</html>
diff --git a/talk/examples/ios/AppRTCDemo/main.m b/talk/examples/ios/AppRTCDemo/main.m
new file mode 100644
index 0000000..bf35f4c
--- /dev/null
+++ b/talk/examples/ios/AppRTCDemo/main.m
@@ -0,0 +1,37 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 <UIKit/UIKit.h>
+
+#import "APPRTCAppDelegate.h"
+
+int main(int argc, char *argv[]) {
+  @autoreleasepool {
+    return UIApplicationMain(
+        argc, argv, nil, NSStringFromClass([APPRTCAppDelegate class]));
+  }
+}
diff --git a/talk/examples/ios/Icon.png b/talk/examples/ios/Icon.png
new file mode 100644
index 0000000..55773ca
--- /dev/null
+++ b/talk/examples/ios/Icon.png
Binary files differ
diff --git a/talk/examples/ios/README b/talk/examples/ios/README
new file mode 100644
index 0000000..9dbbd3f
--- /dev/null
+++ b/talk/examples/ios/README
@@ -0,0 +1,28 @@
+This directory contains an example iOS client for http://apprtc.appspot.com
+
+Example of building & using the app:
+
+cd <path/to/libjingle>/trunk/talk
+- Open libjingle.xcproj.  Select iPhone or iPad simulator and build everything.
+  Then switch to iOS device and build everything.  This creates x86 and ARM
+  archives.
+cd examples/ios
+./makeLibs.sh
+- This will generate fat archives containing both targets and copy them to
+  ./libs.
+- This step must be rerun every time you run gclient sync or build the API
+  libraries.
+- Open AppRTCDemo.xcodeproj, select your device or simulator and run.
+- If you have any problems deploying for the first time, check the project
+  properties to ensure that the Bundle Identifier matches your phone
+  provisioning profile.  Or use the simulator as it doesn't require a profile.
+
+- In desktop chrome, navigate to http://apprtc.appspot.com and note the r=<NNN>
+  room number in the resulting URL.
+
+- Enter that number into the text field on the phone.
+
+- Alternatively, you can background the app and launch Safari.  In Safari, open
+  the url apprtc://apprtc.appspot.com/?r=<NNN> where <NNN> is the room name.
+  Other options are to put the link in an email and send it to your self.
+  Clicking on it will launch AppRTCDemo and navigate to the room.
diff --git a/talk/examples/ios/makeLibs.sh b/talk/examples/ios/makeLibs.sh
new file mode 100755
index 0000000..b5bf3cd
--- /dev/null
+++ b/talk/examples/ios/makeLibs.sh
@@ -0,0 +1,30 @@
+#!/bin/bash -e
+
+if [ "$(dirname $0)" != "." ]; then
+  echo "$(basename $0) must be run from talk/examples/ios as ./$(basename $0)"
+  exit 1
+fi
+
+rm -rf libs
+mkdir libs
+
+cd ../../../xcodebuild/Debug-iphoneos
+for f in *.a; do
+  if [ -f "../Debug-iphonesimulator/$f" ]; then
+    echo "creating fat static library $f"
+    lipo -create "$f" "../Debug-iphonesimulator/$f" -output "../../talk/examples/ios/libs/$f"
+  else
+    echo ""
+    echo "$f was not built for the simulator."
+    echo ""
+  fi
+done
+
+cd ../Debug-iphonesimulator
+for f in *.a; do
+  if [ ! -f "../Debug-iphoneos/$f" ]; then
+    echo ""
+    echo "$f was not built for the iPhone."
+    echo ""
+  fi
+done
diff --git a/talk/examples/login/login_main.cc b/talk/examples/login/login_main.cc
new file mode 100644
index 0000000..55bb893
--- /dev/null
+++ b/talk/examples/login/login_main.cc
@@ -0,0 +1,66 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <cstdio>
+#include <iostream>
+
+#include "talk/base/thread.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/xmppclientsettings.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmppthread.h"
+
+int main(int argc, char **argv) {
+  std::cout << "OAuth Access Token: ";
+  std::string auth_token;
+  std::getline(std::cin, auth_token);
+
+  std::cout << "User Name: ";
+  std::string username;
+  std::getline(std::cin, username);
+
+  // Start xmpp on a different thread
+  buzz::XmppThread thread;
+  thread.Start();
+
+  buzz::XmppClientSettings xcs;
+  xcs.set_user(username.c_str());
+  xcs.set_host("gmail.com");
+  xcs.set_use_tls(buzz::TLS_DISABLED);
+  xcs.set_auth_token(buzz::AUTH_MECHANISM_OAUTH2,
+                     auth_token.c_str());
+  xcs.set_server(talk_base::SocketAddress("talk.google.com", 5222));
+  thread.Login(xcs);
+
+  // Use main thread for console input
+  std::string line;
+  while (std::getline(std::cin, line)) {
+    if (line == "quit")
+      break;
+  }
+  return 0;
+}
diff --git a/talk/examples/pcp/pcp_main.cc b/talk/examples/pcp/pcp_main.cc
new file mode 100644
index 0000000..1b8974d
--- /dev/null
+++ b/talk/examples/pcp/pcp_main.cc
@@ -0,0 +1,715 @@
+#define _CRT_SECURE_NO_DEPRECATE 1
+
+#include <time.h>
+
+#if defined(POSIX)
+#include <unistd.h>
+#endif
+
+#include <iomanip>
+#include <iostream>
+#include <string>
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif  // HAVE_CONFIG_H
+
+#include "talk/base/sslconfig.h"  // For SSL_USE_*
+
+#if SSL_USE_OPENSSL
+#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/p2p/base/sessionmanager.h"
+#include "talk/p2p/client/autoportallocator.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"
+#include "talk/xmpp/xmpppump.h"
+#include "talk/xmpp/xmppsocket.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 buzz::XmppPumpNotify, public buzz::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_) {
+      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_);
+    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 buzz::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..b35a054
--- /dev/null
+++ b/talk/examples/peerconnection/client/conductor.cc
@@ -0,0 +1,494 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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>
+
+#include "talk/app/webrtc/videosourceinterface.h"
+#include "talk/base/common.h"
+#include "talk/base/json.h"
+#include "talk/base/logging.h"
+#include "talk/examples/peerconnection/client/defaults.h"
+#include "talk/media/devices/devicemanager.h"
+
+// Names used for a IceCandidate JSON object.
+const char kCandidateSdpMidName[] = "sdpMid";
+const char kCandidateSdpMlineIndexName[] = "sdpMLineIndex";
+const char kCandidateSdpName[] = "candidate";
+
+// Names used for a SessionDescription JSON object.
+const char kSessionDescriptionTypeName[] = "type";
+const char kSessionDescriptionSdpName[] = "sdp";
+
+class DummySetSessionDescriptionObserver
+    : public webrtc::SetSessionDescriptionObserver {
+ public:
+  static DummySetSessionDescriptionObserver* Create() {
+    return
+        new talk_base::RefCountedObject<DummySetSessionDescriptionObserver>();
+  }
+  virtual void OnSuccess() {
+    LOG(INFO) << __FUNCTION__;
+  }
+  virtual void OnFailure(const std::string& error) {
+    LOG(INFO) << __FUNCTION__ << " " << error;
+  }
+
+ protected:
+  DummySetSessionDescriptionObserver() {}
+  ~DummySetSessionDescriptionObserver() {}
+};
+
+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;
+  }
+
+  webrtc::PeerConnectionInterface::IceServers servers;
+  webrtc::PeerConnectionInterface::IceServer server;
+  server.uri = GetPeerConnectionString();
+  servers.push_back(server);
+  peer_connection_ = peer_connection_factory_->CreatePeerConnection(servers,
+                                                                    NULL,
+                                                                    NULL,
+                                                                    this);
+  if (!peer_connection_.get()) {
+    main_wnd_->MessageBox("Error",
+        "CreatePeerConnection failed", true);
+    DeletePeerConnection();
+  }
+  AddStreams();
+  return peer_connection_.get() != NULL;
+}
+
+void Conductor::DeletePeerConnection() {
+  peer_connection_ = NULL;
+  active_streams_.clear();
+  main_wnd_->StopLocalRenderer();
+  main_wnd_->StopRemoteRenderer();
+  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);
+}
+
+// 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);
+}
+
+void Conductor::OnIceCandidate(const webrtc::IceCandidateInterface* candidate) {
+  LOG(INFO) << __FUNCTION__ << " " << candidate->sdp_mline_index();
+  Json::StyledWriter writer;
+  Json::Value jmessage;
+
+  jmessage[kCandidateSdpMidName] = candidate->sdp_mid();
+  jmessage[kCandidateSdpMlineIndexName] = candidate->sdp_mline_index();
+  std::string sdp;
+  if (!candidate->ToString(&sdp)) {
+    LOG(LS_ERROR) << "Failed to serialize candidate";
+    return;
+  }
+  jmessage[kCandidateSdpName] = sdp;
+  SendMessage(writer.write(jmessage));
+}
+
+//
+// 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;
+
+    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 a message from unknown peer while already in a "
+                    "conversation with a different peer.";
+    return;
+  }
+
+  Json::Reader reader;
+  Json::Value jmessage;
+  if (!reader.parse(message, jmessage)) {
+    LOG(WARNING) << "Received unknown message. " << message;
+    return;
+  }
+  std::string type;
+  std::string json_object;
+
+  GetStringFromJsonObject(jmessage, kSessionDescriptionTypeName, &type);
+  if (!type.empty()) {
+    std::string sdp;
+    if (!GetStringFromJsonObject(jmessage, kSessionDescriptionSdpName, &sdp)) {
+      LOG(WARNING) << "Can't parse received session description message.";
+      return;
+    }
+    webrtc::SessionDescriptionInterface* session_description(
+        webrtc::CreateSessionDescription(type, sdp));
+    if (!session_description) {
+      LOG(WARNING) << "Can't parse received session description message.";
+      return;
+    }
+    LOG(INFO) << " Received session description :" << message;
+    peer_connection_->SetRemoteDescription(
+        DummySetSessionDescriptionObserver::Create(), session_description);
+    if (session_description->type() ==
+        webrtc::SessionDescriptionInterface::kOffer) {
+      peer_connection_->CreateAnswer(this, NULL);
+    }
+    return;
+  } else {
+    std::string sdp_mid;
+    int sdp_mlineindex = 0;
+    std::string sdp;
+    if (!GetStringFromJsonObject(jmessage, kCandidateSdpMidName, &sdp_mid) ||
+        !GetIntFromJsonObject(jmessage, kCandidateSdpMlineIndexName,
+                              &sdp_mlineindex) ||
+        !GetStringFromJsonObject(jmessage, kCandidateSdpName, &sdp)) {
+      LOG(WARNING) << "Can't parse received message.";
+      return;
+    }
+    talk_base::scoped_ptr<webrtc::IceCandidateInterface> candidate(
+        webrtc::CreateIceCandidate(sdp_mid, sdp_mlineindex, sdp));
+    if (!candidate.get()) {
+      LOG(WARNING) << "Can't parse received candidate message.";
+      return;
+    }
+    if (!peer_connection_->AddIceCandidate(candidate.get())) {
+      LOG(WARNING) << "Failed to apply the received candidate";
+      return;
+    }
+    LOG(INFO) << " Received candidate :" << message;
+    return;
+  }
+}
+
+void Conductor::OnMessageSent(int err) {
+  // Process the next pending message if any.
+  main_wnd_->QueueUIThreadCallback(SEND_MESSAGE_TO_PEER, NULL);
+}
+
+void Conductor::OnServerConnectionFailure() {
+    main_wnd_->MessageBox("Error", ("Failed to connect to " + server_).c_str(),
+                          true);
+}
+
+//
+// MainWndCallback implementation.
+//
+
+void Conductor::StartLogin(const std::string& server, int port) {
+  if (client_->is_connected())
+    return;
+  server_ = server;
+  client_->Connect(server, port, GetPeerName());
+}
+
+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;
+    peer_connection_->CreateOffer(this, NULL);
+  } else {
+    main_wnd_->MessageBox("Error", "Failed to initialize PeerConnection", true);
+  }
+}
+
+cricket::VideoCapturer* Conductor::OpenVideoCaptureDevice() {
+  talk_base::scoped_ptr<cricket::DeviceManagerInterface> dev_manager(
+      cricket::DeviceManagerFactory::Create());
+  if (!dev_manager->Init()) {
+    LOG(LS_ERROR) << "Can't create device manager";
+    return NULL;
+  }
+  std::vector<cricket::Device> devs;
+  if (!dev_manager->GetVideoCaptureDevices(&devs)) {
+    LOG(LS_ERROR) << "Can't enumerate video devices";
+    return NULL;
+  }
+  std::vector<cricket::Device>::iterator dev_it = devs.begin();
+  cricket::VideoCapturer* capturer = NULL;
+  for (; dev_it != devs.end(); ++dev_it) {
+    capturer = dev_manager->CreateVideoCapturer(*dev_it);
+    if (capturer != NULL)
+      break;
+  }
+  return capturer;
+}
+
+void Conductor::AddStreams() {
+  if (active_streams_.find(kStreamLabel) != active_streams_.end())
+    return;  // Already added.
+
+  talk_base::scoped_refptr<webrtc::AudioTrackInterface> audio_track(
+      peer_connection_factory_->CreateAudioTrack(
+          kAudioLabel, peer_connection_factory_->CreateAudioSource(NULL)));
+
+  talk_base::scoped_refptr<webrtc::VideoTrackInterface> video_track(
+      peer_connection_factory_->CreateVideoTrack(
+          kVideoLabel,
+          peer_connection_factory_->CreateVideoSource(OpenVideoCaptureDevice(),
+                                                      NULL)));
+  main_wnd_->StartLocalRenderer(video_track);
+
+  talk_base::scoped_refptr<webrtc::MediaStreamInterface> stream =
+      peer_connection_factory_->CreateLocalMediaStream(kStreamLabel);
+
+  stream->AddTrack(audio_track);
+  stream->AddTrack(video_track);
+  if (!peer_connection_->AddStream(stream, NULL)) {
+    LOG(LS_ERROR) << "Adding stream to PeerConnection failed";
+  }
+  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_ERROR:
+      main_wnd_->MessageBox("Error", "an unknown error occurred", true);
+      break;
+
+    case NEW_STREAM_ADDED: {
+      webrtc::MediaStreamInterface* stream =
+          reinterpret_cast<webrtc::MediaStreamInterface*>(
+          data);
+      webrtc::VideoTrackVector tracks = stream->GetVideoTracks();
+      // Only render the first track.
+      if (!tracks.empty()) {
+        webrtc::VideoTrackInterface* track = tracks[0];
+        main_wnd_->StartRemoteRenderer(track);
+      }
+      stream->Release();
+      break;
+    }
+
+    case STREAM_REMOVED: {
+      // Remote peer stopped sending a stream.
+      webrtc::MediaStreamInterface* stream =
+          reinterpret_cast<webrtc::MediaStreamInterface*>(
+          data);
+      stream->Release();
+      break;
+    }
+
+    default:
+      ASSERT(false);
+      break;
+  }
+}
+
+void Conductor::OnSuccess(webrtc::SessionDescriptionInterface* desc) {
+  peer_connection_->SetLocalDescription(
+      DummySetSessionDescriptionObserver::Create(), desc);
+  Json::StyledWriter writer;
+  Json::Value jmessage;
+  jmessage[kSessionDescriptionTypeName] = desc->type();
+  std::string sdp;
+  desc->ToString(&sdp);
+  jmessage[kSessionDescriptionSdpName] = sdp;
+  SendMessage(writer.write(jmessage));
+}
+
+void Conductor::OnFailure(const std::string& error) {
+    LOG(LERROR) << error;
+}
+
+void Conductor::SendMessage(const std::string& json_object) {
+  std::string* msg = new std::string(json_object);
+  main_wnd_->QueueUIThreadCallback(SEND_MESSAGE_TO_PEER, msg);
+}
diff --git a/talk/examples/peerconnection/client/conductor.h b/talk/examples/peerconnection/client/conductor.h
new file mode 100644
index 0000000..f9fb393
--- /dev/null
+++ b/talk/examples/peerconnection/client/conductor.h
@@ -0,0 +1,144 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/mediastreaminterface.h"
+#include "talk/app/webrtc/peerconnectioninterface.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 webrtc::CreateSessionDescriptionObserver,
+    public PeerConnectionClientObserver,
+    public MainWndCallback {
+ public:
+  enum CallbackID {
+    MEDIA_CHANNELS_INITIALIZED = 1,
+    PEER_CONNECTION_CLOSED,
+    SEND_MESSAGE_TO_PEER,
+    PEER_CONNECTION_ERROR,
+    NEW_STREAM_ADDED,
+    STREAM_REMOVED,
+  };
+
+  Conductor(PeerConnectionClient* client, MainWindow* main_wnd);
+
+  bool connection_active() const;
+
+  virtual void Close();
+
+ protected:
+  ~Conductor();
+  bool InitializePeerConnection();
+  void DeletePeerConnection();
+  void EnsureStreamingUI();
+  void AddStreams();
+  cricket::VideoCapturer* OpenVideoCaptureDevice();
+
+  //
+  // PeerConnectionObserver implementation.
+  //
+  virtual void OnError();
+  virtual void OnStateChange(
+      webrtc::PeerConnectionObserver::StateType state_changed) {}
+  virtual void OnAddStream(webrtc::MediaStreamInterface* stream);
+  virtual void OnRemoveStream(webrtc::MediaStreamInterface* stream);
+  virtual void OnRenegotiationNeeded() {}
+  virtual void OnIceChange() {}
+  virtual void OnIceCandidate(const webrtc::IceCandidateInterface* candidate);
+
+  //
+  // 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);
+
+  virtual void OnServerConnectionFailure();
+
+  //
+  // MainWndCallback implementation.
+  //
+
+  virtual void 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);
+
+  // CreateSessionDescriptionObserver implementation.
+  virtual void OnSuccess(webrtc::SessionDescriptionInterface* desc);
+  virtual void OnFailure(const std::string& error);
+
+ protected:
+  // Send a message to the remote peer.
+  void SendMessage(const std::string& json_object);
+
+  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_;
+  std::string server_;
+};
+
+#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..40f3dd1
--- /dev/null
+++ b/talk/examples/peerconnection/client/defaults.cc
@@ -0,0 +1,75 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/flagdefs.h b/talk/examples/peerconnection/client/flagdefs.h
new file mode 100644
index 0000000..c135bbb
--- /dev/null
+++ b/talk/examples/peerconnection/client/flagdefs.h
@@ -0,0 +1,50 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_CLIENT_FLAGDEFS_H_
+#define TALK_EXAMPLES_PEERCONNECTION_CLIENT_FLAGDEFS_H_
+#pragma once
+
+#include "talk/base/flags.h"
+
+extern const uint16 kDefaultServerPort;  // From defaults.[h|cc]
+
+// Define flags for the peerconnect_client testing tool, in a separate
+// header file so that they can be shared across the different main.cc's
+// for each platform.
+
+DEFINE_bool(help, false, "Prints this message");
+DEFINE_bool(autoconnect, false, "Connect to the server without user "
+                                "intervention.");
+DEFINE_string(server, "localhost", "The server to connect to.");
+DEFINE_int(port, kDefaultServerPort,
+           "The port on which the server is listening.");
+DEFINE_bool(autocall, false, "Call the first available other client on "
+  "the server without user intervention.  Note: this flag should only be set "
+  "to true on one of the two clients.");
+
+#endif  // TALK_EXAMPLES_PEERCONNECTION_CLIENT_FLAGDEFS_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..aee1bb1
--- /dev/null
+++ b/talk/examples/peerconnection/client/linux/main.cc
@@ -0,0 +1,117 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/flagdefs.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);
+
+  FlagList::SetFlagsFromCommandLine(&argc, argv, true);
+  if (FLAG_help) {
+    FlagList::Print(NULL, false);
+    return 0;
+  }
+
+  // Abort if the user specifies a port that is outside the allowed
+  // range [1, 65535].
+  if ((FLAG_port < 1) || (FLAG_port > 65535)) {
+    printf("Error: %i is not a valid port.\n", FLAG_port);
+    return -1;
+  }
+
+  GtkMainWnd wnd(FLAG_server, FLAG_port, FLAG_autoconnect, FLAG_autocall);
+  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;
+  talk_base::scoped_refptr<Conductor> conductor(
+      new talk_base::RefCountedObject<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..0a2e1f6
--- /dev/null
+++ b/talk/examples/peerconnection/client/linux/main_wnd.cc
@@ -0,0 +1,520 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 SimulateButtonClick(gpointer button) {
+  g_signal_emit_by_name(button, "clicked");
+  return false;
+}
+
+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);
+}
+
+gboolean SimulateLastRowActivated(gpointer data) {
+  GtkTreeView* tree_view = reinterpret_cast<GtkTreeView*>(data);
+  GtkTreeModel* model = gtk_tree_view_get_model(tree_view);
+
+  // "if iter is NULL, then the number of toplevel nodes is returned."
+  int rows = gtk_tree_model_iter_n_children(model, NULL);
+  GtkTreePath* lastpath = gtk_tree_path_new_from_indices(rows - 1, -1);
+
+  // Select the last item in the list
+  GtkTreeSelection* selection = gtk_tree_view_get_selection(tree_view);
+  gtk_tree_selection_select_path(selection, lastpath);
+
+  // Our TreeView only has one column, so it is column 0.
+  GtkTreeViewColumn* column = gtk_tree_view_get_column(tree_view, 0);
+
+  gtk_tree_view_row_activated(tree_view, lastpath, column);
+
+  gtk_tree_path_free(lastpath);
+  return false;
+}
+
+// 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(const char* server, int port, bool autoconnect,
+                       bool autocall)
+    : window_(NULL), draw_area_(NULL), vbox_(NULL), server_edit_(NULL),
+      port_edit_(NULL), peer_list_(NULL), callback_(NULL),
+      server_(server), autoconnect_(autoconnect), autocall_(autocall) {
+  char buffer[10];
+  sprintfn(buffer, sizeof(buffer), "%i", port);
+  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;
+}
+
+
+void GtkMainWnd::StartLocalRenderer(webrtc::VideoTrackInterface* local_video) {
+  local_renderer_.reset(new VideoRenderer(this, local_video));
+}
+
+void GtkMainWnd::StopLocalRenderer() {
+  local_renderer_.reset();
+}
+
+void GtkMainWnd::StartRemoteRenderer(webrtc::VideoTrackInterface* remote_video) {
+  remote_renderer_.reset(new VideoRenderer(this, remote_video));
+}
+
+void GtkMainWnd::StopRemoteRenderer() {
+  remote_renderer_.reset();
+}
+
+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_);
+
+  if (autoconnect_)
+    g_idle_add(SimulateButtonClick, button);
+}
+
+void GtkMainWnd::SwitchToPeerList(const Peers& peers) {
+  LOG(INFO) << __FUNCTION__;
+
+  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);
+
+  if (autocall_ && peers.begin() != peers.end())
+    g_idle_add(SimulateLastRowActivated, peer_list_);
+}
+
+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) {
+  // Make the connect button insensitive, so that it cannot be clicked more than
+  // once.  Now that the connection includes auto-retry, it should not be
+  // necessary to click it more than once.
+  gtk_widget_set_sensitive(widget, false);
+  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 = remote_renderer_.get();
+  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 = local_renderer_.get();
+    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,
+    webrtc::VideoTrackInterface* track_to_render)
+    : width_(0),
+      height_(0),
+      main_wnd_(main_wnd),
+      rendered_track_(track_to_render) {
+  rendered_track_->AddRenderer(this);
+}
+
+GtkMainWnd::VideoRenderer::~VideoRenderer() {
+  rendered_track_->RemoveRenderer(this);
+}
+
+void GtkMainWnd::VideoRenderer::SetSize(int width, int height) {
+  gdk_threads_enter();
+  width_ = width;
+  height_ = height;
+  image_.reset(new uint8[width * height * 4]);
+  gdk_threads_leave();
+}
+
+void 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_);
+}
+
+
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..c23c116
--- /dev/null
+++ b/talk/examples/peerconnection/client/linux/main_wnd.h
@@ -0,0 +1,138 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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(const char* server, int port, bool autoconnect, bool autocall);
+  ~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 void StartLocalRenderer(webrtc::VideoTrackInterface* local_video);
+  virtual void StopLocalRenderer();
+  virtual void StartRemoteRenderer(webrtc::VideoTrackInterface* remote_video);
+  virtual void StopRemoteRenderer();
+
+  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 webrtc::VideoRendererInterface {
+   public:
+    VideoRenderer(GtkMainWnd* main_wnd,
+                  webrtc::VideoTrackInterface* track_to_render);
+    virtual ~VideoRenderer();
+
+    // VideoRendererInterface implementation
+    virtual void SetSize(int width, int height);
+    virtual void 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_;
+    talk_base::scoped_refptr<webrtc::VideoTrackInterface> rendered_track_;
+  };
+
+ 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_;
+  bool autoconnect_;
+  bool autocall_;
+  talk_base::scoped_ptr<VideoRenderer> local_renderer_;
+  talk_base::scoped_ptr<VideoRenderer> remote_renderer_;
+  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..bd0a5c3
--- /dev/null
+++ b/talk/examples/peerconnection/client/main.cc
@@ -0,0 +1,72 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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"
+#include "talk/base/win32socketserver.h"
+
+
+int PASCAL wWinMain(HINSTANCE instance, HINSTANCE prev_instance,
+                    wchar_t* cmd_line, int cmd_show) {
+  talk_base::EnsureWinsockInit();
+  talk_base::Win32Thread w32_thread;
+  talk_base::ThreadManager::Instance()->SetCurrentThread(&w32_thread);
+
+  MainWnd wnd;
+  if (!wnd.Create()) {
+    ASSERT(false);
+    return -1;
+  }
+
+  PeerConnectionClient client;
+  talk_base::scoped_refptr<Conductor> conductor(
+        new talk_base::RefCountedObject<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..0a22d03
--- /dev/null
+++ b/talk/examples/peerconnection/client/main_wnd.cc
@@ -0,0 +1,603 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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) {
+  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);
+}
+
+
+void MainWnd::StartLocalRenderer(webrtc::VideoTrackInterface* local_video) {
+  local_renderer_.reset(new VideoRenderer(handle(), 1, 1, local_video));
+}
+
+void MainWnd::StopLocalRenderer() {
+  local_renderer_.reset();
+}
+
+void MainWnd::StartRemoteRenderer(webrtc::VideoTrackInterface* remote_video) {
+  remote_renderer_.reset(new VideoRenderer(handle(), 1, 1, remote_video));
+}
+
+void MainWnd::StopRemoteRenderer() {
+  remote_renderer_.reset();
+}
+
+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);
+
+  VideoRenderer* local_renderer = local_renderer_.get();
+  VideoRenderer* remote_renderer = remote_renderer_.get();
+  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 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, GWLP_USERDATA));
+  if (!me && WM_CREATE == msg) {
+    CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lp);
+    me = reinterpret_cast<MainWnd*>(cs->lpCreateParams);
+    me->wnd_ = hwnd;
+    ::SetWindowLongPtr(hwnd, GWLP_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,
+    webrtc::VideoTrackInterface* track_to_render)
+    : wnd_(wnd), rendered_track_(track_to_render) {
+  ::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);
+  rendered_track_->AddRenderer(this);
+}
+
+MainWnd::VideoRenderer::~VideoRenderer() {
+  rendered_track_->RemoveRenderer(this);
+  ::DeleteCriticalSection(&buffer_lock_);
+}
+
+void MainWnd::VideoRenderer::SetSize(int width, int height) {
+  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]);
+}
+
+void MainWnd::VideoRenderer::RenderFrame(const cricket::VideoFrame* frame) {
+  if (!frame)
+    return;
+
+  {
+    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);
+}
diff --git a/talk/examples/peerconnection/client/main_wnd.h b/talk/examples/peerconnection/client/main_wnd.h
new file mode 100644
index 0000000..0ed0f14
--- /dev/null
+++ b/talk/examples/peerconnection/client/main_wnd.h
@@ -0,0 +1,213 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/mediastreaminterface.h"
+#include "talk/base/win32.h"
+#include "talk/examples/peerconnection/client/peer_connection_client.h"
+#include "talk/media/base/mediachannel.h"
+#include "talk/media/base/videocommon.h"
+#include "talk/media/base/videoframe.h"
+#include "talk/media/base/videorenderer.h"
+
+class MainWndCallback {
+ public:
+  virtual void 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 void StartLocalRenderer(webrtc::VideoTrackInterface* local_video) = 0;
+  virtual void StopLocalRenderer() = 0;
+  virtual void StartRemoteRenderer(webrtc::VideoTrackInterface* remote_video) = 0;
+  virtual void StopRemoteRenderer() = 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 void StartLocalRenderer(webrtc::VideoTrackInterface* local_video);
+  virtual void StopLocalRenderer();
+  virtual void StartRemoteRenderer(webrtc::VideoTrackInterface* remote_video);
+  virtual void StopRemoteRenderer();
+
+  virtual void QueueUIThreadCallback(int msg_id, void* data);
+
+  HWND handle() const { return wnd_; }
+
+  class VideoRenderer : public webrtc::VideoRendererInterface {
+   public:
+    VideoRenderer(HWND wnd, int width, int height,
+                  webrtc::VideoTrackInterface* track_to_render);
+    virtual ~VideoRenderer();
+
+    void Lock() {
+      ::EnterCriticalSection(&buffer_lock_);
+    }
+
+    void Unlock() {
+      ::LeaveCriticalSection(&buffer_lock_);
+    }
+
+    // VideoRendererInterface implementation
+    virtual void SetSize(int width, int height);
+    virtual void 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_;
+    talk_base::scoped_refptr<webrtc::VideoTrackInterface> rendered_track_;
+  };
+
+  // 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_ptr<VideoRenderer> local_renderer_;
+  talk_base::scoped_ptr<VideoRenderer> remote_renderer_;
+  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..dc66946
--- /dev/null
+++ b/talk/examples/peerconnection/client/peer_connection_client.cc
@@ -0,0 +1,531 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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";
+// Delay between server connection retries, in milliseconds
+const int kReconnectDelay = 2000;
+
+talk_base::AsyncSocket* CreateClientSocket(int family) {
+#ifdef WIN32
+  talk_base::Win32Socket* sock = new talk_base::Win32Socket();
+  sock->CreateT(family, SOCK_STREAM);
+  return sock;
+#elif defined(POSIX)
+  talk_base::Thread* thread = talk_base::Thread::Current();
+  ASSERT(thread != NULL);
+  return thread->socketserver()->CreateAsyncSocket(family, SOCK_STREAM);
+#else
+#error Platform not supported.
+#endif
+}
+
+}
+
+PeerConnectionClient::PeerConnectionClient()
+  : callback_(NULL),
+    resolver_(NULL),
+    state_(NOT_CONNECTED),
+    my_id_(-1) {
+}
+
+PeerConnectionClient::~PeerConnectionClient() {
+}
+
+void PeerConnectionClient::InitSocketSignals() {
+  ASSERT(control_socket_.get() != NULL);
+  ASSERT(hanging_get_.get() != NULL);
+  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);
+}
+
+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;
+}
+
+void 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()";
+    callback_->OnServerConnectionFailure();
+    return;
+  }
+
+  if (server.empty() || client_name.empty()) {
+    callback_->OnServerConnectionFailure();
+    return;
+  }
+
+  if (port <= 0)
+    port = kDefaultServerPort;
+
+  server_address_.SetIP(server);
+  server_address_.SetPort(port);
+  client_name_ = client_name;
+
+  if (server_address_.IsUnresolved()) {
+    state_ = RESOLVING;
+    resolver_ = new talk_base::AsyncResolver();
+    resolver_->SignalWorkDone.connect(this,
+                                      &PeerConnectionClient::OnResolveResult);
+    resolver_->set_address(server_address_);
+    resolver_->Start();
+  } else {
+    DoConnect();
+  }
+}
+
+void PeerConnectionClient::OnResolveResult(talk_base::SignalThread *t) {
+  if (resolver_->error() != 0) {
+    callback_->OnServerConnectionFailure();
+    resolver_->Destroy(false);
+    resolver_ = NULL;
+    state_ = NOT_CONNECTED;
+  } else {
+    server_address_ = resolver_->address();
+    DoConnect();
+  }
+}
+
+void PeerConnectionClient::DoConnect() {
+  control_socket_.reset(CreateClientSocket(server_address_.ipaddr().family()));
+  hanging_get_.reset(CreateClientSocket(server_address_.ipaddr().family()));
+  InitSocketSignals();
+  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;
+  if (!ret) {
+    callback_->OnServerConnectionFailure();
+  }
+}
+
+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();
+  if (resolver_ != NULL) {
+    resolver_->Destroy(false);
+    resolver_ = NULL;
+  }
+  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) {
+  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)) {
+      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) {
+  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) {
+  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) {
+        hanging_get_->Close();
+        hanging_get_->Connect(server_address_);
+      }
+    } else {
+      callback_->OnMessageSent(err);
+    }
+  } else {
+    if (socket == control_socket_.get()) {
+      LOG(WARNING) << "Connection refused; retrying in 2 seconds";
+      talk_base::Thread::Current()->PostDelayed(kReconnectDelay, this, 0);
+    } else {
+      Close();
+      callback_->OnDisconnected();
+    }
+  }
+}
+
+void PeerConnectionClient::OnMessage(talk_base::Message* msg) {
+  // ignore msg; there is currently only one supported message ("retry")
+  DoConnect();
+}
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..d31262b
--- /dev/null
+++ b/talk/examples/peerconnection/client/peer_connection_client.h
@@ -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.
+ */
+
+#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/nethelpers.h"
+#include "talk/base/signalthread.h"
+#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;
+  virtual void OnServerConnectionFailure() = 0;
+
+ protected:
+  virtual ~PeerConnectionClientObserver() {}
+};
+
+class PeerConnectionClient : public sigslot::has_slots<>,
+                             public talk_base::MessageHandler {
+ public:
+  enum State {
+    NOT_CONNECTED,
+    RESOLVING,
+    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);
+
+  void 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();
+
+  // implements the MessageHandler interface
+  void OnMessage(talk_base::Message* msg);
+
+ protected:
+  void DoConnect();
+  void Close();
+  void InitSocketSignals();
+  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);
+
+  void OnResolveResult(talk_base::SignalThread *t);
+
+  PeerConnectionClientObserver* callback_;
+  talk_base::SocketAddress server_address_;
+  talk_base::AsyncResolver* resolver_;
+  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_;
+  std::string client_name_;
+  Peers peers_;
+  State state_;
+  int my_id_;
+};
+
+#endif  // PEERCONNECTION_SAMPLES_CLIENT_PEER_CONNECTION_CLIENT_H_
diff --git a/talk/examples/peerconnection/peerconnection.scons b/talk/examples/peerconnection/peerconnection.scons
new file mode 100644
index 0000000..7a7d2f3
--- /dev/null
+++ b/talk/examples/peerconnection/peerconnection.scons
@@ -0,0 +1,64 @@
+# -*- Python -*-
+import talk
+
+Import('env')
+
+if env.Bit('have_webrtc_voice') and env.Bit('have_webrtc_video'):
+  talk.App(
+    env,
+    name = 'peerconnection_client',
+    # TODO: Build peerconnection_client on mac.
+    libs = [
+      'base',
+      'expat',
+      'json',
+      'p2p',
+      'peerconnection',
+      'phone',
+      'srtp',
+      'xmllite',
+      'xmpp',
+      'yuvscaler',
+    ],
+    win_srcs = [
+      'client/conductor.cc',
+      'client/defaults.cc',
+      'client/main.cc',
+      'client/main_wnd.cc',
+      'client/peer_connection_client.cc',
+    ],
+    posix_libs = [
+      'crypto',
+      'securetunnel',
+      'ssl',
+    ],
+    lin_srcs = [
+      'client/conductor.cc',
+      'client/defaults.cc',
+      'client/peer_connection_client.cc',
+      'client/linux/main.cc',
+      'client/linux/main_wnd.cc',
+    ],
+    lin_packages = [
+      'glib-2.0',
+      'gobject-2.0',
+      'gtk+-2.0',
+    ],
+    lin_libs = [
+      'sound',
+    ],
+    win_link_flags = [
+      ('', '/nodefaultlib:libcmt')[env.Bit('debug')],
+    ],
+  )
+
+  talk.App(
+    env,
+    name = 'peerconnection_server',
+    srcs = [
+      'server/data_socket.cc',
+      'server/main.cc',
+      'server/peer_channel.cc',
+      'server/utils.cc',
+    ],
+  )
diff --git a/talk/examples/peerconnection/server/data_socket.cc b/talk/examples/peerconnection/server/data_socket.cc
new file mode 100644
index 0000000..58370b4
--- /dev/null
+++ b/talk/examples/peerconnection/server/data_socket.cc
@@ -0,0 +1,306 @@
+/*
+ * 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>
+#if defined(POSIX)
+#include <unistd.h>
+#endif
+
+#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..40ede93
--- /dev/null
+++ b/talk/examples/peerconnection/server/main.cc
@@ -0,0 +1,190 @@
+/*
+ * 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/base/flags.h"
+#include "talk/examples/peerconnection/server/data_socket.h"
+#include "talk/examples/peerconnection/server/peer_channel.h"
+#include "talk/examples/peerconnection/server/utils.h"
+
+DEFINE_bool(help, false, "Prints this message");
+DEFINE_int(port, 8888, "The port on which to listen.");
+
+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) {
+  FlagList::SetFlagsFromCommandLine(&argc, argv, true);
+  if (FLAG_help) {
+    FlagList::Print(NULL, false);
+    return 0;
+  }
+
+  // Abort if the user specifies a port that is outside the allowed
+  // range [1, 65535].
+  if ((FLAG_port < 1) || (FLAG_port > 65535)) {
+    printf("Error: %i is not a valid port.\n", FLAG_port);
+    return -1;
+  }
+
+  ListeningSocket listener;
+  if (!listener.Create()) {
+    printf("Failed to create server socket\n");
+    return -1;
+  } else if (!listener.Listen(FLAG_port)) {
+    printf("Failed to listen on server socket\n");
+    return -1;
+  }
+
+  printf("Server listening on port %i\n", FLAG_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..2603465
--- /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_.front();
+    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..0155917
--- /dev/null
+++ b/talk/examples/peerconnection/server/server_test.html
@@ -0,0 +1,253 @@
+<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 offer with an answer.
+      data = data.replace("offer", "answer");
+      // Keep only the first crypto line for each m line in the answer.
+      var mlines = data.split("m=");
+      // Start from 1 because the first item in the array is not a m line.
+      for (var i = 1; i < mlines.length; ++i) {
+        var mline = mlines[i];
+        var cryptoBegin = mline.indexOf("a=crypto:", 0);
+        if (cryptoBegin == -1) {
+          // No crypto line found.
+          continue;
+        }
+        // Skip the first crypto line.
+        cryptoBegin = mline.indexOf("a=crypto:", cryptoBegin + 1);
+        while (cryptoBegin != -1) {
+          var cryptoEnd = mline.indexOf("\\n", cryptoBegin);
+          var crypto = mline.substring(cryptoBegin, cryptoEnd + 2);
+          data = data.replace(crypto, "");
+          // Search for the the next crypto line.
+          cryptoBegin = mline.indexOf("a=crypto:", cryptoBegin + 1);
+        }
+      }
+    }
+    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.gyp b/talk/libjingle.gyp
new file mode 100755
index 0000000..39ab843
--- /dev/null
+++ b/talk/libjingle.gyp
@@ -0,0 +1,1174 @@
+#
+# libjingle
+# Copyright 2012, Google Inc.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#  1. Redistributions of source code must retain the above copyright notice,
+#     this list of conditions and the following disclaimer.
+#  2. Redistributions in binary form must reproduce the above copyright notice,
+#     this list of conditions and the following disclaimer in the documentation
+#     and/or other materials provided with the distribution.
+#  3. The name of the author may not be used to endorse or promote products
+#     derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+{
+  'includes': ['build/common.gypi'],
+
+  'conditions': [
+    ['os_posix == 1 and OS != "mac" and OS != "ios"', {
+     'conditions': [
+       ['sysroot!=""', {
+         'variables': {
+           'pkg-config': '../../../build/linux/pkg-config-wrapper "<(sysroot)" "<(target_arch)"',
+         },
+       }, {
+         'variables': {
+           'pkg-config': 'pkg-config'
+         },
+       }],
+     ],
+    }],
+
+    ['OS=="linux" or OS=="android"', {
+      'targets': [
+        {
+          'target_name': 'libjingle_peerconnection_so',
+          'type': 'loadable_module',
+          'dependencies': [
+            'libjingle_peerconnection',
+            '<(DEPTH)/third_party/icu/icu.gyp:icuuc',
+          ],
+          'sources': [
+            'app/webrtc/java/jni/peerconnection_jni.cc'
+          ],
+          'conditions': [
+            ['OS=="linux"', {
+              'defines': [
+                'HAVE_GTK',
+              ],
+              'include_dirs': [
+                '<(java_home)/include',
+                '<(java_home)/include/linux',
+              ],
+              'link_settings': {
+                'libraries': [
+                  '<!@(pkg-config --libs-only-l gobject-2.0 gthread-2.0'
+                      ' gtk+-2.0)',
+                ],
+              },
+            }],
+          ],
+        },
+        {
+          'target_name': 'libjingle_peerconnection_jar',
+          'type': 'none',
+          'actions': [
+            {
+              'variables': {
+                'java_src_dir': 'app/webrtc/java/src',
+                'webrtc_modules_dir': '<(DEPTH)/third_party/webrtc/modules',
+                'peerconnection_java_files': [
+                  'app/webrtc/java/src/org/webrtc/AudioSource.java',
+                  'app/webrtc/java/src/org/webrtc/AudioTrack.java',
+                  'app/webrtc/java/src/org/webrtc/IceCandidate.java',
+                  'app/webrtc/java/src/org/webrtc/MediaConstraints.java',
+                  'app/webrtc/java/src/org/webrtc/MediaSource.java',
+                  'app/webrtc/java/src/org/webrtc/MediaStream.java',
+                  'app/webrtc/java/src/org/webrtc/MediaStreamTrack.java',
+                  'app/webrtc/java/src/org/webrtc/PeerConnectionFactory.java',
+                  'app/webrtc/java/src/org/webrtc/PeerConnection.java',
+                  'app/webrtc/java/src/org/webrtc/SdpObserver.java',
+                  'app/webrtc/java/src/org/webrtc/StatsObserver.java',
+                  'app/webrtc/java/src/org/webrtc/StatsReport.java',
+                  'app/webrtc/java/src/org/webrtc/SessionDescription.java',
+                  'app/webrtc/java/src/org/webrtc/VideoCapturer.java',
+                  'app/webrtc/java/src/org/webrtc/VideoRenderer.java',
+                  'app/webrtc/java/src/org/webrtc/VideoSource.java',
+                  'app/webrtc/java/src/org/webrtc/VideoTrack.java',
+                ],
+                # TODO(fischman): extract this into a webrtc gyp var that can be
+                # included here, or better yet, build a proper .jar in webrtc
+                # and include it here.
+                'android_java_files': [
+                  '<(webrtc_modules_dir)/audio_device/android/org/webrtc/voiceengine/WebRTCAudioDevice.java',
+                  '<(webrtc_modules_dir)/video_capture/android/java/org/webrtc/videoengine/CaptureCapabilityAndroid.java',
+                  '<(webrtc_modules_dir)/video_capture/android/java/org/webrtc/videoengine/VideoCaptureAndroid.java',
+                  '<(webrtc_modules_dir)/video_capture/android/java/org/webrtc/videoengine/VideoCaptureDeviceInfoAndroid.java',
+                  '<(webrtc_modules_dir)/video_render/android/java/org/webrtc/videoengine/ViEAndroidGLES20.java',
+                  '<(webrtc_modules_dir)/video_render/android/java/org/webrtc/videoengine/ViERenderer.java',
+                  '<(webrtc_modules_dir)/video_render/android/java/org/webrtc/videoengine/ViESurfaceRenderer.java',
+                ],
+              },
+              'action_name': 'create_jar',
+              'inputs': [
+                'build/build_jar.sh',
+                '<@(java_files)',
+              ],
+              'outputs': [
+                '<(PRODUCT_DIR)/libjingle_peerconnection.jar',
+              ],
+              'conditions': [
+                ['OS=="android"', {
+                  'variables': {
+                    'java_files': ['<@(peerconnection_java_files)', '<@(android_java_files)'],
+                    'build_classpath': '<(java_src_dir):<(DEPTH)/third_party/android_tools/sdk/platforms/android-<(android_sdk_version)/android.jar',
+                  },
+                }, {
+                  'variables': {
+                    'java_files': ['<@(peerconnection_java_files)'],
+                    'build_classpath': '<(java_src_dir)',
+                  },
+                }],
+              ],
+              'action': [
+                'build/build_jar.sh', '<(java_home)', '<@(_outputs)',
+                '<(INTERMEDIATE_DIR)',
+                '<(build_classpath)',
+                '<@(java_files)'
+              ],
+            },
+          ],
+          'dependencies': [
+            'libjingle_peerconnection_so',
+          ],
+        },
+      ],
+    }],
+    ['libjingle_objc == 1', {
+      'targets': [
+        {
+          'target_name': 'libjingle_peerconnection_objc',
+          'type': 'static_library',
+          'dependencies': [
+            'libjingle_peerconnection',
+          ],
+          'sources': [
+            'app/webrtc/objc/RTCAudioTrack+Internal.h',
+            'app/webrtc/objc/RTCAudioTrack.mm',
+            'app/webrtc/objc/RTCEnumConverter.h',
+            'app/webrtc/objc/RTCEnumConverter.mm',
+            'app/webrtc/objc/RTCI420Frame.mm',
+            'app/webrtc/objc/RTCIceCandidate+Internal.h',
+            'app/webrtc/objc/RTCIceCandidate.mm',
+            'app/webrtc/objc/RTCIceServer+Internal.h',
+            'app/webrtc/objc/RTCIceServer.mm',
+            'app/webrtc/objc/RTCMediaConstraints+Internal.h',
+            'app/webrtc/objc/RTCMediaConstraints.mm',
+            'app/webrtc/objc/RTCMediaConstraintsNative.cc',
+            'app/webrtc/objc/RTCMediaConstraintsNative.h',
+            'app/webrtc/objc/RTCMediaSource+Internal.h',
+            'app/webrtc/objc/RTCMediaSource.mm',
+            'app/webrtc/objc/RTCMediaStream+Internal.h',
+            'app/webrtc/objc/RTCMediaStream.mm',
+            'app/webrtc/objc/RTCMediaStreamTrack+Internal.h',
+            'app/webrtc/objc/RTCMediaStreamTrack.mm',
+            'app/webrtc/objc/RTCPair.m',
+            'app/webrtc/objc/RTCPeerConnection+Internal.h',
+            'app/webrtc/objc/RTCPeerConnection.mm',
+            'app/webrtc/objc/RTCPeerConnectionFactory.mm',
+            'app/webrtc/objc/RTCPeerConnectionObserver.h',
+            'app/webrtc/objc/RTCPeerConnectionObserver.mm',
+            'app/webrtc/objc/RTCSessionDescription+Internal.h',
+            'app/webrtc/objc/RTCSessionDescription.mm',
+            'app/webrtc/objc/RTCVideoCapturer+Internal.h',
+            'app/webrtc/objc/RTCVideoCapturer.mm',
+            'app/webrtc/objc/RTCVideoRenderer+Internal.h',
+            'app/webrtc/objc/RTCVideoRenderer.mm',
+            'app/webrtc/objc/RTCVideoSource+Internal.h',
+            'app/webrtc/objc/RTCVideoSource.mm',
+            'app/webrtc/objc/RTCVideoTrack+Internal.h',
+            'app/webrtc/objc/RTCVideoTrack.mm',
+            'app/webrtc/objc/public/RTCAudioSource.h',
+            'app/webrtc/objc/public/RTCAudioTrack.h',
+            'app/webrtc/objc/public/RTCI420Frame.h',
+            'app/webrtc/objc/public/RTCIceCandidate.h',
+            'app/webrtc/objc/public/RTCIceServer.h',
+            'app/webrtc/objc/public/RTCMediaConstraints.h',
+            'app/webrtc/objc/public/RTCMediaSource.h',
+            'app/webrtc/objc/public/RTCMediaStream.h',
+            'app/webrtc/objc/public/RTCMediaStreamTrack.h',
+            'app/webrtc/objc/public/RTCPair.h',
+            'app/webrtc/objc/public/RTCPeerConnection.h',
+            'app/webrtc/objc/public/RTCPeerConnectionDelegate.h',
+            'app/webrtc/objc/public/RTCPeerConnectionFactory.h',
+            'app/webrtc/objc/public/RTCSessionDescription.h',
+            'app/webrtc/objc/public/RTCSessionDescriptonDelegate.h',
+            'app/webrtc/objc/public/RTCTypes.h',
+            'app/webrtc/objc/public/RTCVideoCapturer.h',
+            'app/webrtc/objc/public/RTCVideoRenderer.h',
+            'app/webrtc/objc/public/RTCVideoRendererDelegate.h',
+            'app/webrtc/objc/public/RTCVideoSource.h',
+            'app/webrtc/objc/public/RTCVideoTrack.h',
+          ],
+          'include_dirs': [
+            '<(DEPTH)/talk/app/webrtc',
+            '<(DEPTH)/talk/app/webrtc/objc',
+            '<(DEPTH)/talk/app/webrtc/objc/public',
+          ],
+          'link_settings': {
+            'libraries': [
+              '$(SDKROOT)/System/Library/Frameworks/Foundation.framework',
+            ],
+          },
+          'xcode_settings': {
+            'CLANG_ENABLE_OBJC_ARC': 'YES',
+            'CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS': 'NO',
+            'CLANG_LINK_OBJC_RUNTIME': 'YES',
+          },
+        }
+      ]
+    }],
+  ],
+
+  'targets': [
+    {
+      'target_name': 'libjingle',
+      'type': 'static_library',
+      'dependencies': [
+        '<(DEPTH)/third_party/expat/expat.gyp:expat',
+        '<(DEPTH)/third_party/jsoncpp/jsoncpp.gyp:jsoncpp',
+      ],
+      'export_dependent_settings': [
+        '<(DEPTH)/third_party/expat/expat.gyp:expat',
+        '<(DEPTH)/third_party/jsoncpp/jsoncpp.gyp:jsoncpp',
+      ],
+      'sources': [
+        'base/asyncfile.cc',
+        'base/asyncfile.h',
+        'base/asynchttprequest.cc',
+        'base/asynchttprequest.h',
+        'base/asyncpacketsocket.h',
+        'base/asyncsocket.cc',
+        'base/asyncsocket.h',
+        'base/asynctcpsocket.cc',
+        'base/asynctcpsocket.h',
+        'base/asyncudpsocket.cc',
+        'base/asyncudpsocket.h',
+        'base/atomicops.h',
+        'base/autodetectproxy.cc',
+        'base/autodetectproxy.h',
+        'base/bandwidthsmoother.cc',
+        'base/bandwidthsmoother.h',
+        'base/base64.cc',
+        'base/base64.h',
+        'base/basicdefs.h',
+        'base/basictypes.h',
+        'base/bind.h',
+        'base/buffer.h',
+        'base/bytebuffer.cc',
+        'base/bytebuffer.h',
+        'base/byteorder.h',
+        'base/checks.cc',
+        'base/checks.h',
+        'base/common.cc',
+        'base/common.h',
+        'base/constructormagic.h',
+        'base/cpumonitor.cc',
+        'base/cpumonitor.h',
+        'base/crc32.cc',
+        'base/crc32.h',
+        'base/criticalsection.h',
+        'base/cryptstring.h',
+        'base/diskcache.cc',
+        'base/diskcache.h',
+        'base/event.cc',
+        'base/event.h',
+        'base/filelock.cc',
+        'base/filelock.h',
+        'base/fileutils.cc',
+        'base/fileutils.h',
+        'base/fileutils_mock.h',
+        'base/firewallsocketserver.cc',
+        'base/firewallsocketserver.h',
+        'base/flags.cc',
+        'base/flags.h',
+        'base/gunit_prod.h',
+        'base/helpers.cc',
+        'base/helpers.h',
+        'base/host.cc',
+        'base/host.h',
+        'base/httpbase.cc',
+        'base/httpbase.h',
+        'base/httpclient.cc',
+        'base/httpclient.h',
+        'base/httpcommon-inl.h',
+        'base/httpcommon.cc',
+        'base/httpcommon.h',
+        'base/httprequest.cc',
+        'base/httprequest.h',
+        'base/httpserver.cc',
+        'base/httpserver.h',
+        'base/ifaddrs-android.cc',
+        'base/ifaddrs-android.h',
+        'base/ipaddress.cc',
+        'base/ipaddress.h',
+        'base/json.cc',
+        'base/json.h',
+        'base/linked_ptr.h',
+        'base/linuxfdwalk.h',
+        'base/logging.cc',
+        'base/logging.h',
+        'base/maccocoathreadhelper.h',
+        'base/maccocoathreadhelper.mm',
+        'base/mathutils.h',
+        'base/md5.cc',
+        'base/md5.h',
+        'base/md5digest.h',
+        'base/messagedigest.cc',
+        'base/messagedigest.h',
+        'base/messagehandler.cc',
+        'base/messagehandler.h',
+        'base/messagequeue.cc',
+        'base/messagequeue.h',
+        'base/multipart.cc',
+        'base/multipart.h',
+        'base/natserver.cc',
+        'base/natserver.h',
+        'base/natsocketfactory.cc',
+        'base/natsocketfactory.h',
+        'base/nattypes.cc',
+        'base/nattypes.h',
+        'base/nethelpers.cc',
+        'base/nethelpers.h',
+        'base/network.cc',
+        'base/network.h',
+        'base/nssidentity.cc',
+        'base/nssidentity.h',
+        'base/nssstreamadapter.cc',
+        'base/nssstreamadapter.h',
+        'base/nullsocketserver.h',
+        'base/optionsfile.cc',
+        'base/optionsfile.h',
+        'base/pathutils.cc',
+        'base/pathutils.h',
+        'base/physicalsocketserver.cc',
+        'base/physicalsocketserver.h',
+        'base/profiler.cc',
+        'base/profiler.h',
+        'base/proxydetect.cc',
+        'base/proxydetect.h',
+        'base/proxyinfo.cc',
+        'base/proxyinfo.h',
+        'base/proxyserver.cc',
+        'base/proxyserver.h',
+        'base/ratelimiter.cc',
+        'base/ratelimiter.h',
+        'base/ratetracker.cc',
+        'base/ratetracker.h',
+        'base/refcount.h',
+        'base/referencecountedsingletonfactory.h',
+        'base/rollingaccumulator.h',
+        'base/scoped_autorelease_pool.h',
+        'base/scoped_ptr.h',
+        'base/scoped_ref_ptr.h',
+        'base/sec_buffer.h',
+        'base/sha1.cc',
+        'base/sha1.h',
+        'base/sha1digest.h',
+        'base/sharedexclusivelock.cc',
+        'base/sharedexclusivelock.h',
+        'base/signalthread.cc',
+        'base/signalthread.h',
+        'base/sigslot.h',
+        'base/sigslotrepeater.h',
+        'base/socket.h',
+        'base/socketadapters.cc',
+        'base/socketadapters.h',
+        'base/socketaddress.cc',
+        'base/socketaddress.h',
+        'base/socketaddresspair.cc',
+        'base/socketaddresspair.h',
+        'base/socketfactory.h',
+        'base/socketpool.cc',
+        'base/socketpool.h',
+        'base/socketserver.h',
+        'base/socketstream.cc',
+        'base/socketstream.h',
+        'base/ssladapter.cc',
+        'base/ssladapter.h',
+        'base/sslconfig.h',
+        'base/sslfingerprint.h',
+        'base/sslidentity.cc',
+        'base/sslidentity.h',
+        'base/sslroots.h',
+        'base/sslsocketfactory.cc',
+        'base/sslsocketfactory.h',
+        'base/sslstreamadapter.cc',
+        'base/sslstreamadapter.h',
+        'base/sslstreamadapterhelper.cc',
+        'base/sslstreamadapterhelper.h',
+        'base/stream.cc',
+        'base/stream.h',
+        'base/stringdigest.h',
+        'base/stringencode.cc',
+        'base/stringencode.h',
+        'base/stringutils.cc',
+        'base/stringutils.h',
+        'base/systeminfo.cc',
+        'base/systeminfo.h',
+        'base/task.cc',
+        'base/task.h',
+        'base/taskparent.cc',
+        'base/taskparent.h',
+        'base/taskrunner.cc',
+        'base/taskrunner.h',
+        'base/testclient.cc',
+        'base/testclient.h',
+        'base/thread.cc',
+        'base/thread.h',
+        'base/timeutils.cc',
+        'base/timeutils.h',
+        'base/timing.cc',
+        'base/timing.h',
+        'base/transformadapter.cc',
+        'base/transformadapter.h',
+        'base/urlencode.cc',
+        'base/urlencode.h',
+        'base/versionparsing.cc',
+        'base/versionparsing.h',
+        'base/virtualsocketserver.cc',
+        'base/virtualsocketserver.h',
+        'base/window.h',
+        'base/windowpicker.h',
+        'base/windowpickerfactory.h',
+        'base/worker.cc',
+        'base/worker.h',
+        'xmllite/qname.cc',
+        'xmllite/qname.h',
+        'xmllite/xmlbuilder.cc',
+        'xmllite/xmlbuilder.h',
+        'xmllite/xmlconstants.cc',
+        'xmllite/xmlconstants.h',
+        'xmllite/xmlelement.cc',
+        'xmllite/xmlelement.h',
+        'xmllite/xmlnsstack.cc',
+        'xmllite/xmlnsstack.h',
+        'xmllite/xmlparser.cc',
+        'xmllite/xmlparser.h',
+        'xmllite/xmlprinter.cc',
+        'xmllite/xmlprinter.h',
+        'xmpp/asyncsocket.h',
+        'xmpp/chatroommodule.h',
+        'xmpp/chatroommoduleimpl.cc',
+        'xmpp/constants.cc',
+        'xmpp/constants.h',
+        'xmpp/discoitemsquerytask.cc',
+        'xmpp/discoitemsquerytask.h',
+        'xmpp/hangoutpubsubclient.cc',
+        'xmpp/hangoutpubsubclient.h',
+        'xmpp/iqtask.cc',
+        'xmpp/iqtask.h',
+        'xmpp/jid.cc',
+        'xmpp/jid.h',
+        'xmpp/module.h',
+        'xmpp/moduleimpl.cc',
+        'xmpp/moduleimpl.h',
+        'xmpp/mucroomconfigtask.cc',
+        'xmpp/mucroomconfigtask.h',
+        'xmpp/mucroomdiscoverytask.cc',
+        'xmpp/mucroomdiscoverytask.h',
+        'xmpp/mucroomlookuptask.cc',
+        'xmpp/mucroomlookuptask.h',
+        'xmpp/mucroomuniquehangoutidtask.cc',
+        'xmpp/mucroomuniquehangoutidtask.h',
+        'xmpp/pingtask.cc',
+        'xmpp/pingtask.h',
+        'xmpp/plainsaslhandler.h',
+        'xmpp/presenceouttask.cc',
+        'xmpp/presenceouttask.h',
+        'xmpp/presencereceivetask.cc',
+        'xmpp/presencereceivetask.h',
+        'xmpp/presencestatus.cc',
+        'xmpp/presencestatus.h',
+        'xmpp/prexmppauth.h',
+        'xmpp/pubsub_task.cc',
+        'xmpp/pubsub_task.h',
+        'xmpp/pubsubclient.cc',
+        'xmpp/pubsubclient.h',
+        'xmpp/pubsubtasks.cc',
+        'xmpp/pubsubtasks.h',
+        'xmpp/receivetask.cc',
+        'xmpp/receivetask.h',
+        'xmpp/rostermodule.h',
+        'xmpp/rostermoduleimpl.cc',
+        'xmpp/rostermoduleimpl.h',
+        'xmpp/saslcookiemechanism.h',
+        'xmpp/saslhandler.h',
+        'xmpp/saslmechanism.cc',
+        'xmpp/saslmechanism.h',
+        'xmpp/saslplainmechanism.h',
+        'xmpp/xmppauth.cc',
+        'xmpp/xmppauth.h',
+        'xmpp/xmppclient.cc',
+        'xmpp/xmppclient.h',
+        'xmpp/xmppclientsettings.h',
+        'xmpp/xmppengine.h',
+        'xmpp/xmppengineimpl.cc',
+        'xmpp/xmppengineimpl.h',
+        'xmpp/xmppengineimpl_iq.cc',
+        'xmpp/xmpplogintask.cc',
+        'xmpp/xmpplogintask.h',
+        'xmpp/xmpppump.cc',
+        'xmpp/xmpppump.h',
+        'xmpp/xmppsocket.cc',
+        'xmpp/xmppsocket.h',
+        'xmpp/xmppstanzaparser.cc',
+        'xmpp/xmppstanzaparser.h',
+        'xmpp/xmpptask.cc',
+        'xmpp/xmpptask.h',
+        'xmpp/xmppthread.cc',
+        'xmpp/xmppthread.h',
+      ],
+      'conditions': [
+        ['OS=="mac" or OS=="ios" or OS=="win"', {
+          'dependencies': [
+            # The chromium copy of nss should NOT be used on platforms that
+            # have NSS as system libraries, such as linux.
+            '<(DEPTH)/third_party/nss/nss.gyp:nss',
+          ],
+        }],
+        ['OS=="android"', {
+          'sources': [
+            'base/ifaddrs-android.cc',
+          ],
+          'link_settings': {
+            'libraries': [
+              '-llog',
+              '-lGLESv2',
+            ],
+          },
+        }],
+        ['OS=="linux" or OS=="android"', {
+          'sources': [
+            'base/linux.cc',
+            'base/linux.h',
+          ],
+        }],
+        ['OS=="linux"', {
+          'sources': [
+            'base/dbus.cc',
+            'base/dbus.h',
+            'base/libdbusglibsymboltable.cc',
+            'base/libdbusglibsymboltable.h',
+            'base/linuxfdwalk.c',
+            'base/linuxwindowpicker.cc',
+            'base/linuxwindowpicker.h',
+          ],
+          'link_settings': {
+            'libraries': [
+              '-lcrypto',
+              '-ldl',
+              '-lrt',
+              '-lssl',
+              '-lXext',
+              '-lX11',
+              '-lXcomposite',
+              '-lXrender',
+              '<!@(<(pkg-config) --libs-only-l nss | sed -e "s/-lssl3//")',
+            ],
+          },
+          'cflags': [
+            '<!@(<(pkg-config) --cflags nss)',
+          ],
+          'ldflags': [
+            '<!@(<(pkg-config) --libs-only-L --libs-only-other nss)',
+          ],
+        }],
+        ['OS=="mac"', {
+          'conditions': [
+            [ 'libjingle_objc != 1', {
+              'sources': [
+                'base/macasyncsocket.cc',
+                'base/macasyncsocket.h',
+                'base/maccocoasocketserver.h',
+                'base/maccocoasocketserver.mm',
+                'base/macsocketserver.cc',
+                'base/macsocketserver.h',
+              ],
+              'link_settings' :{
+                'xcode_settings': {
+                  'OTHER_LDFLAGS': [
+                    '-framework Carbon',
+                  ],
+                },
+              },
+            }, {
+              'defines': [
+                'CARBON_DEPRECATED=YES',
+              ],
+            }],
+          ],
+          'sources': [
+            'base/macconversion.cc',
+            'base/macconversion.h',
+            'base/macutils.cc',
+            'base/macutils.h',
+            'base/macwindowpicker.cc',
+            'base/macwindowpicker.h',
+            'base/scoped_autorelease_pool.mm',
+          ],
+          'link_settings': {
+            'libraries': [
+             '$(SDKROOT)/usr/lib/libcrypto.dylib',
+             '$(SDKROOT)/usr/lib/libssl.dylib',
+            ],
+            'xcode_settings': {
+              'OTHER_LDFLAGS': [
+                '-framework Cocoa',
+                '-framework IOKit',
+                '-framework Security',
+                '-framework SystemConfiguration',
+              ],
+            },
+          },
+        }],
+        ['OS=="ios"', {
+          # 'dependencies': [
+          #   '<(DEPTH)/third_party/openssl/openssl.gyp:openssl',
+          # ],
+          # 'include_dirs': [
+          #   '<(DEPTH)/third_party/openssl/openssl/include',
+          # ],
+          'sources': [
+            'base/scoped_autorelease_pool.mm',
+          ],
+          'xcode_settings': {
+            'OTHER_LDFLAGS': [
+              '-framework IOKit',
+              '-framework Security',
+              '-framework SystemConfiguration',
+              '-framework UIKit',
+            ],
+          },
+          'defines': [
+	    'SSL_USE_NSS',
+          ],
+        }],
+        ['OS=="win"', {
+          'sources': [
+            'base/diskcache_win32.cc',
+            'base/diskcache_win32.h',
+            'base/schanneladapter.cc',
+            'base/schanneladapter.h',
+            'base/win32.cc',
+            'base/win32.h',
+            'base/win32filesystem.cc',
+            'base/win32filesystem.h',
+            'base/win32regkey.cc',
+            'base/win32regkey.h',
+            'base/win32securityerrors.cc',
+            'base/win32socketinit.cc',
+            'base/win32socketinit.h',
+            'base/win32socketserver.cc',
+            'base/win32socketserver.h',
+            'base/win32window.cc',
+            'base/win32window.h',
+            'base/win32windowpicker.cc',
+            'base/win32windowpicker.h',
+            'base/winfirewall.cc',
+            'base/winfirewall.h',
+            'base/winping.cc',
+            'base/winping.h',
+          ],
+          # Suppress warnings about WIN32_LEAN_AND_MEAN.
+          'msvs_disabled_warnings': [4005],
+          'msvs_settings': {
+            'VCLibrarianTool': {
+              'AdditionalDependencies': [
+                'crypt32.lib',
+                'iphlpapi.lib',
+                'secur32.lib',
+              ],
+            },
+          },
+        }],
+        ['os_posix==1', {
+          'sources': [
+            'base/latebindingsymboltable.cc',
+            'base/latebindingsymboltable.h',
+            'base/posix.cc',
+            'base/posix.h',
+            'base/unixfilesystem.cc',
+            'base/unixfilesystem.h',
+          ],
+          'conditions': [
+            ['OS=="linux" or OS=="android"', {
+              'dependencies': [
+                '<(DEPTH)/third_party/openssl/openssl.gyp:openssl',
+              ],
+            }],
+            ['OS!="ios"', {
+              'sources': [
+                'base/openssladapter.cc',
+                'base/openssladapter.h',
+                'base/openssldigest.cc',
+                'base/openssldigest.h',
+                'base/opensslidentity.cc',
+                'base/opensslidentity.h',
+                'base/opensslstreamadapter.cc',
+                'base/opensslstreamadapter.h',
+              ],
+            }],
+          ],
+        }],
+      ],  # conditions
+    },  # target libjingle
+    {
+      'target_name': 'libjingle_sound',
+      'type': 'static_library',
+      'dependencies': [
+        'libjingle',
+      ],
+      'sources': [
+        'sound/automaticallychosensoundsystem.h',
+        'sound/nullsoundsystem.cc',
+        'sound/nullsoundsystem.h',
+        'sound/nullsoundsystemfactory.cc',
+        'sound/nullsoundsystemfactory.h',
+        'sound/platformsoundsystem.cc',
+        'sound/platformsoundsystem.h',
+        'sound/platformsoundsystemfactory.cc',
+        'sound/platformsoundsystemfactory.h',
+        'sound/sounddevicelocator.h',
+        'sound/soundinputstreaminterface.h',
+        'sound/soundoutputstreaminterface.h',
+        'sound/soundsystemfactory.h',
+        'sound/soundsysteminterface.cc',
+        'sound/soundsysteminterface.h',
+        'sound/soundsystemproxy.cc',
+        'sound/soundsystemproxy.h',
+      ],
+      'conditions': [
+        ['OS=="linux"', {
+          'sources': [
+            'sound/alsasoundsystem.cc',
+            'sound/alsasoundsystem.h',
+            'sound/alsasymboltable.cc',
+            'sound/alsasymboltable.h',
+            'sound/linuxsoundsystem.cc',
+            'sound/linuxsoundsystem.h',
+            'sound/pulseaudiosoundsystem.cc',
+            'sound/pulseaudiosoundsystem.h',
+            'sound/pulseaudiosymboltable.cc',
+            'sound/pulseaudiosymboltable.h',
+          ],
+        }],
+      ],
+    },  # target libjingle_sound
+    {
+      'target_name': 'libjingle_media',
+      'type': 'static_library',
+      'dependencies': [
+        '<(DEPTH)/third_party/libyuv/libyuv.gyp:libyuv',
+        '<(DEPTH)/third_party/webrtc/modules/modules.gyp:video_capture_module',
+        '<(DEPTH)/third_party/webrtc/modules/modules.gyp:video_render_module',
+        '<(DEPTH)/third_party/webrtc/video_engine/video_engine.gyp:video_engine_core',
+        '<(DEPTH)/third_party/webrtc/voice_engine/voice_engine.gyp:voice_engine',
+        '<(DEPTH)/third_party/webrtc/system_wrappers/source/system_wrappers.gyp:system_wrappers',
+        'libjingle',
+        'libjingle_sound',
+      ],
+      'direct_dependent_settings': {
+        'include_dirs': [
+          '<(DEPTH)/third_party/libyuv/include',
+        ],
+      },
+      'sources': [
+        'media/base/audioframe.h',
+        'media/base/audiorenderer.h',
+        'media/base/capturemanager.cc',
+        'media/base/capturemanager.h',
+        'media/base/capturerenderadapter.cc',
+        'media/base/capturerenderadapter.h',
+        'media/base/codec.cc',
+        'media/base/codec.h',
+        'media/base/constants.cc',
+        'media/base/constants.h',
+        'media/base/cpuid.cc',
+        'media/base/cpuid.h',
+        'media/base/cryptoparams.h',
+        'media/base/filemediaengine.cc',
+        'media/base/filemediaengine.h',
+        'media/base/hybriddataengine.h',
+        'media/base/hybridvideoengine.cc',
+        'media/base/hybridvideoengine.h',
+        'media/base/mediachannel.h',
+        'media/base/mediacommon.h',
+        'media/base/mediaengine.cc',
+        'media/base/mediaengine.h',
+        'media/base/mutedvideocapturer.cc',
+        'media/base/mutedvideocapturer.h',
+        'media/base/rtpdataengine.cc',
+        'media/base/rtpdataengine.h',
+        'media/base/rtpdump.cc',
+        'media/base/rtpdump.h',
+        'media/base/rtputils.cc',
+        'media/base/rtputils.h',
+        'media/base/screencastid.h',
+        'media/base/streamparams.cc',
+        'media/base/streamparams.h',
+        'media/base/videoadapter.cc',
+        'media/base/videoadapter.h',
+        'media/base/videocapturer.cc',
+        'media/base/videocapturer.h',
+        'media/base/videocommon.cc',
+        'media/base/videocommon.h',
+        'media/base/videoframe.cc',
+        'media/base/videoframe.h',
+        'media/base/videoprocessor.h',
+        'media/base/videorenderer.h',
+        'media/base/voiceprocessor.h',
+        'media/devices/deviceinfo.h',
+        'media/devices/devicemanager.cc',
+        'media/devices/devicemanager.h',
+        'media/devices/dummydevicemanager.h',
+        'media/devices/filevideocapturer.cc',
+        'media/devices/filevideocapturer.h',
+        'media/devices/videorendererfactory.h',
+        'media/other/linphonemediaengine.h',
+        # TODO(ronghuawu): Enable when SCTP is ready.
+        # 'media/sctp/sctpdataengine.cc',
+        # 'media/sctp/sctpdataengine.h',
+        'media/webrtc/webrtccommon.h',
+        'media/webrtc/webrtcexport.h',
+        'media/webrtc/webrtcmediaengine.h',
+        'media/webrtc/webrtcpassthroughrender.cc',
+        'media/webrtc/webrtcpassthroughrender.h',
+        'media/webrtc/webrtcvideocapturer.cc',
+        'media/webrtc/webrtcvideocapturer.h',
+        'media/webrtc/webrtcvideodecoderfactory.h',
+        'media/webrtc/webrtcvideoencoderfactory.h',
+        'media/webrtc/webrtcvideoengine.cc',
+        'media/webrtc/webrtcvideoengine.h',
+        'media/webrtc/webrtcvideoframe.cc',
+        'media/webrtc/webrtcvideoframe.h',
+        'media/webrtc/webrtcvie.h',
+        'media/webrtc/webrtcvoe.h',
+        'media/webrtc/webrtcvoiceengine.cc',
+        'media/webrtc/webrtcvoiceengine.h',
+      ],
+      'conditions': [
+        ['OS=="linux"', {
+          'sources': [
+            'media/devices/gtkvideorenderer.cc',
+            'media/devices/gtkvideorenderer.h',
+            'media/devices/libudevsymboltable.cc',
+            'media/devices/libudevsymboltable.h',
+            'media/devices/linuxdeviceinfo.cc',
+            'media/devices/linuxdevicemanager.cc',
+            'media/devices/linuxdevicemanager.h',
+            'media/devices/v4llookup.cc',
+            'media/devices/v4llookup.h',
+          ],
+          'include_dirs': [
+            'third_party/libudev'
+          ],
+          'cflags': [
+            '<!@(pkg-config --cflags gobject-2.0 gthread-2.0 gtk+-2.0)',
+          ],
+          'libraries': [
+            '-lrt',
+            '-lXext',
+            '-lX11',
+          ],
+        }],
+        ['OS=="win"', {
+          'sources': [
+            'media/devices/gdivideorenderer.cc',
+            'media/devices/gdivideorenderer.h',
+            'media/devices/win32deviceinfo.cc',
+            'media/devices/win32devicemanager.cc',
+            'media/devices/win32devicemanager.h',
+          ],
+          'msvs_settings': {
+            'VCLibrarianTool': {
+              'AdditionalDependencies': [
+                'd3d9.lib',
+                'gdi32.lib',
+                'strmiids.lib',
+                'winmm.lib',
+              ],
+            },
+          },
+        }],
+        ['OS=="mac"', {
+          'sources': [
+            'media/devices/macdeviceinfo.cc',
+            'media/devices/macdevicemanager.cc',
+            'media/devices/macdevicemanager.h',
+            'media/devices/macdevicemanagermm.mm',
+          ],
+          'conditions': [
+            # TODO(hughv):  Investigate if this is needed.
+            [ 'libjingle_objc != 1', {
+              'sources': [
+                'media/devices/carbonvideorenderer.cc',
+                'media/devices/carbonvideorenderer.h',
+              ],
+            }],
+          ],
+          'xcode_settings': {
+            'WARNING_CFLAGS': [
+              # TODO(ronghuawu): Update macdevicemanager.cc to stop using
+              # deprecated functions and remove this flag.
+              '-Wno-deprecated-declarations',
+            ],
+          },
+          'link_settings': {
+            'xcode_settings': {
+              'OTHER_LDFLAGS': [
+                '-framework Cocoa',
+                '-framework CoreAudio',
+                '-framework CoreVideo',
+                '-framework OpenGL',
+                '-framework QTKit',
+              ],
+            },
+          },
+        }],
+        ['OS=="ios"', {
+          'sources': [
+            'media/devices/iosdeviceinfo.cc',
+            'media/devices/mobiledevicemanager.cc',
+          ],
+          'include_dirs': [
+            # TODO(sjlee) Remove when vp8 is building for iOS.  vp8 pulls in
+            # libjpeg which pulls in libyuv which currently disabled.
+            '../third_party/libyuv/include',
+          ],
+        }],
+        ['OS=="android"', {
+          'sources': [
+            'media/devices/mobiledevicemanager.cc',
+          ],
+        }],
+      ],
+    },  # target libjingle_media
+    {
+      'target_name': 'libjingle_p2p',
+      'type': 'static_library',
+      'dependencies': [
+        '<(DEPTH)/third_party/libsrtp/libsrtp.gyp:libsrtp',
+        'libjingle',
+        'libjingle_media',
+      ],
+      'include_dirs': [
+        '<(DEPTH)/third_party/gtest/include',
+      ],
+      'direct_dependent_settings': {
+        'include_dirs': [
+          '<(DEPTH)/third_party/gtest/include',
+        ],
+      },
+      'defines': [
+        # TODO(ronghuawu): enable SCTP when it's ready.
+        # 'HAVE_SCTP',
+      ],
+      'sources': [
+        'p2p/base/asyncstuntcpsocket.cc',
+        'p2p/base/asyncstuntcpsocket.h',
+        'p2p/base/basicpacketsocketfactory.cc',
+        'p2p/base/basicpacketsocketfactory.h',
+        'p2p/base/candidate.h',
+        'p2p/base/common.h',
+        'p2p/base/constants.cc',
+        'p2p/base/constants.h',
+        'p2p/base/dtlstransportchannel.cc',
+        'p2p/base/dtlstransportchannel.h',
+        'p2p/base/p2ptransport.cc',
+        'p2p/base/p2ptransport.h',
+        'p2p/base/p2ptransportchannel.cc',
+        'p2p/base/p2ptransportchannel.h',
+        'p2p/base/packetsocketfactory.h',
+        'p2p/base/parsing.cc',
+        'p2p/base/parsing.h',
+        'p2p/base/port.cc',
+        'p2p/base/port.h',
+        'p2p/base/portallocator.cc',
+        'p2p/base/portallocator.h',
+        'p2p/base/portallocatorsessionproxy.cc',
+        'p2p/base/portallocatorsessionproxy.h',
+        'p2p/base/portinterface.h',
+        'p2p/base/portproxy.cc',
+        'p2p/base/portproxy.h',
+        'p2p/base/pseudotcp.cc',
+        'p2p/base/pseudotcp.h',
+        'p2p/base/rawtransport.cc',
+        'p2p/base/rawtransport.h',
+        'p2p/base/rawtransportchannel.cc',
+        'p2p/base/rawtransportchannel.h',
+        'p2p/base/relayport.cc',
+        'p2p/base/relayport.h',
+        'p2p/base/relayserver.cc',
+        'p2p/base/relayserver.h',
+        'p2p/base/session.cc',
+        'p2p/base/session.h',
+        'p2p/base/sessionclient.h',
+        'p2p/base/sessiondescription.cc',
+        'p2p/base/sessiondescription.h',
+        'p2p/base/sessionid.h',
+        'p2p/base/sessionmanager.cc',
+        'p2p/base/sessionmanager.h',
+        'p2p/base/sessionmessages.cc',
+        'p2p/base/sessionmessages.h',
+        'p2p/base/stun.cc',
+        'p2p/base/stun.h',
+        'p2p/base/stunport.cc',
+        'p2p/base/stunport.h',
+        'p2p/base/stunrequest.cc',
+        'p2p/base/stunrequest.h',
+        'p2p/base/stunserver.cc',
+        'p2p/base/stunserver.h',
+        'p2p/base/tcpport.cc',
+        'p2p/base/tcpport.h',
+        'p2p/base/transport.cc',
+        'p2p/base/transport.h',
+        'p2p/base/transportchannel.cc',
+        'p2p/base/transportchannel.h',
+        'p2p/base/transportchannelimpl.h',
+        'p2p/base/transportchannelproxy.cc',
+        'p2p/base/transportchannelproxy.h',
+        'p2p/base/transportdescription.h',
+        'p2p/base/transportdescriptionfactory.cc',
+        'p2p/base/transportdescriptionfactory.h',
+        'p2p/base/transportinfo.h',
+        'p2p/base/turnport.cc',
+        'p2p/base/turnport.h',
+        'p2p/base/turnserver.cc',
+        'p2p/base/turnserver.h',
+        'p2p/base/udpport.h',
+        'p2p/client/autoportallocator.h',
+        'p2p/client/basicportallocator.cc',
+        'p2p/client/basicportallocator.h',
+        'p2p/client/connectivitychecker.cc',
+        'p2p/client/connectivitychecker.h',
+        'p2p/client/httpportallocator.cc',
+        'p2p/client/httpportallocator.h',
+        'p2p/client/sessionmanagertask.h',
+        'p2p/client/sessionsendtask.h',
+        'p2p/client/socketmonitor.cc',
+        'p2p/client/socketmonitor.h',
+        'session/tunnel/pseudotcpchannel.cc',
+        'session/tunnel/pseudotcpchannel.h',
+        'session/tunnel/tunnelsessionclient.cc',
+        'session/tunnel/tunnelsessionclient.h',
+        'session/tunnel/securetunnelsessionclient.cc',
+        'session/tunnel/securetunnelsessionclient.h',
+        'session/media/audiomonitor.cc',
+        'session/media/audiomonitor.h',
+        'session/media/call.cc',
+        'session/media/call.h',
+        'session/media/channel.cc',
+        'session/media/channel.h',
+        'session/media/channelmanager.cc',
+        'session/media/channelmanager.h',
+        'session/media/currentspeakermonitor.cc',
+        'session/media/currentspeakermonitor.h',
+        'session/media/mediamessages.cc',
+        'session/media/mediamessages.h',
+        'session/media/mediamonitor.cc',
+        'session/media/mediamonitor.h',
+        'session/media/mediarecorder.cc',
+        'session/media/mediarecorder.h',
+        'session/media/mediasession.cc',
+        'session/media/mediasession.h',
+        'session/media/mediasessionclient.cc',
+        'session/media/mediasessionclient.h',
+        'session/media/mediasink.h',
+        'session/media/rtcpmuxfilter.cc',
+        'session/media/rtcpmuxfilter.h',
+        'session/media/soundclip.cc',
+        'session/media/soundclip.h',
+        'session/media/srtpfilter.cc',
+        'session/media/srtpfilter.h',
+        'session/media/ssrcmuxfilter.cc',
+        'session/media/ssrcmuxfilter.h',
+        'session/media/typingmonitor.cc',
+        'session/media/typingmonitor.h',
+        'session/media/voicechannel.h',
+      ],
+    },  # target libjingle_p2p
+    {
+      'target_name': 'libjingle_peerconnection',
+      'type': 'static_library',
+      'dependencies': [
+        'libjingle',
+        'libjingle_media',
+        'libjingle_p2p',
+      ],
+      'sources': [
+        'app/webrtc/audiotrack.cc',
+        'app/webrtc/audiotrack.h',
+        'app/webrtc/audiotrackrenderer.cc',
+        'app/webrtc/audiotrackrenderer.h',
+        'app/webrtc/datachannel.cc',
+        'app/webrtc/datachannel.h',
+        'app/webrtc/datachannelinterface.h',
+        'app/webrtc/dtmfsender.cc',
+        'app/webrtc/dtmfsender.h',
+        'app/webrtc/dtmfsenderinterface.h',
+        'app/webrtc/fakeportallocatorfactory.h',
+        'app/webrtc/jsep.h',
+        'app/webrtc/jsepicecandidate.cc',
+        'app/webrtc/jsepicecandidate.h',
+        'app/webrtc/jsepsessiondescription.cc',
+        'app/webrtc/jsepsessiondescription.h',
+        'app/webrtc/localaudiosource.cc',
+        'app/webrtc/localaudiosource.h',
+        'app/webrtc/localvideosource.cc',
+        'app/webrtc/localvideosource.h',
+        'app/webrtc/mediaconstraintsinterface.cc',
+        'app/webrtc/mediaconstraintsinterface.h',
+        'app/webrtc/mediastream.cc',
+        'app/webrtc/mediastream.h',
+        'app/webrtc/mediastreamhandler.cc',
+        'app/webrtc/mediastreamhandler.h',
+        'app/webrtc/mediastreaminterface.h',
+        'app/webrtc/mediastreamprovider.h',
+        'app/webrtc/mediastreamproxy.h',
+        'app/webrtc/mediastreamsignaling.cc',
+        'app/webrtc/mediastreamsignaling.h',
+        'app/webrtc/mediastreamtrack.h',
+        'app/webrtc/mediastreamtrackproxy.h',
+        'app/webrtc/notifier.h',
+        'app/webrtc/peerconnection.cc',
+        'app/webrtc/peerconnection.h',
+        'app/webrtc/peerconnectionfactory.cc',
+        'app/webrtc/peerconnectionfactory.h',
+        'app/webrtc/peerconnectioninterface.h',
+        'app/webrtc/peerconnectionproxy.h',
+        'app/webrtc/portallocatorfactory.cc',
+        'app/webrtc/portallocatorfactory.h',
+        'app/webrtc/proxy.h',
+        'app/webrtc/statscollector.cc',
+        'app/webrtc/statscollector.h',
+        'app/webrtc/statstypes.h',
+        'app/webrtc/streamcollection.h',
+        'app/webrtc/videosourceinterface.h',
+        'app/webrtc/videosourceproxy.h',
+        'app/webrtc/videotrack.cc',
+        'app/webrtc/videotrack.h',
+        'app/webrtc/videotrackrenderers.cc',
+        'app/webrtc/videotrackrenderers.h',
+        'app/webrtc/webrtcsdp.cc',
+        'app/webrtc/webrtcsdp.h',
+        'app/webrtc/webrtcsession.cc',
+        'app/webrtc/webrtcsession.h',
+      ],
+    },  # target libjingle_peerconnection
+  ],
+}
diff --git a/talk/libjingle.scons b/talk/libjingle.scons
new file mode 100644
index 0000000..2bacf6b
--- /dev/null
+++ b/talk/libjingle.scons
@@ -0,0 +1,788 @@
+import talk
+Import("env")
+
+talk.Library(env, name = "expat",
+             cppdefines = [
+               "XML_STATIC",
+             ],
+             srcs = [
+               "third_party/expat-2.0.1/lib/xmlparse.c",
+               "third_party/expat-2.0.1/lib/xmlrole.c",
+               "third_party/expat-2.0.1/lib/xmltok.c",
+             ],
+             includedirs = [
+               "third_party/expat-2.0.1/lib",
+             ],
+             win_cppdefines = [
+               "COMPILED_FROM_DSP",
+             ],
+             posix_cppdefines = [
+               "HAVE_EXPAT_CONFIG_H",
+             ],
+)
+talk.Library(env, name = "gunit",
+             srcs = [
+               "third_party/gtest/src/gtest-all.cc",
+             ],
+             includedirs = [
+               "third_party/gtest/include",
+               "third_party/expat-2.0.1/lib",
+               "third_party/srtp",
+               "third_party/gtest",
+             ],
+             cppdefines = [
+               "EXPAT_RELATIVE_PATH",
+               "GTEST_RELATIVE_PATH",
+               "SRTP_RELATIVE_PATH",
+             ],
+)
+talk.Library(env, name = "srtp",
+             srcs = [
+               "third_party/srtp/crypto/cipher/aes.c",
+               "third_party/srtp/crypto/cipher/aes_cbc.c",
+               "third_party/srtp/crypto/cipher/aes_icm.c",
+               "third_party/srtp/crypto/cipher/cipher.c",
+               "third_party/srtp/crypto/cipher/null_cipher.c",
+               "third_party/srtp/crypto/hash/auth.c",
+               "third_party/srtp/crypto/hash/hmac.c",
+               "third_party/srtp/crypto/hash/null_auth.c",
+               "third_party/srtp/crypto/hash/sha1.c",
+               "third_party/srtp/crypto/replay/rdb.c",
+               "third_party/srtp/crypto/replay/rdbx.c",
+               "third_party/srtp/crypto/replay/ut_sim.c",
+               "third_party/srtp/crypto/math/datatypes.c",
+               "third_party/srtp/crypto/math/stat.c",
+               "third_party/srtp/crypto/kernel/alloc.c",
+               "third_party/srtp/crypto/kernel/crypto_kernel.c",
+               "third_party/srtp/crypto/kernel/err.c",
+               "third_party/srtp/crypto/kernel/key.c",
+               "third_party/srtp/crypto/rng/ctr_prng.c",
+               "third_party/srtp/crypto/rng/rand_source.c",
+               "third_party/srtp/srtp/ekt.c",
+               "third_party/srtp/srtp/srtp.c",
+             ],
+             includedirs = [
+               "third_party/srtp/include",
+               "third_party/srtp/crypto/include",
+             ],
+             win_ccflags = [
+               "/wd4701",
+               "/wd4702",
+             ],
+)
+# Set up the SSL/TLS includes
+if 'NSS_BUILD_PLATFORM' in env['ENV']:
+             SSL_INCLUDES = [
+               "third_party/mozilla/dist/public/nss",
+               "third_party/mozilla/dist/" + env['ENV']['NSS_BUILD_PLATFORM']+ "/include"
+             ]
+             SSL_LIBS = [
+               "ssl3",
+               "nss3",
+               "nssutil3",
+               "plc4",
+               "plds4",
+               "nspr4",
+             ]
+else:
+             SSL_INCLUDES = ["third_party/openssl/include"]
+             SSL_LIBS = ["crypto", "ssl"]
+
+talk.Library(env, name = "jingle",
+             lin_packages = [
+               "x11",
+               "xcomposite",
+               "xrender",
+             ],
+             lin_srcs = [
+               "base/latebindingsymboltable.cc",
+               "base/latebindingsymboltable.h.def",
+               "base/latebindingsymboltable.cc.def",
+               "base/linux.cc",
+               "base/linuxfdwalk.c",
+               "base/linuxwindowpicker.cc",
+               "media/devices/libudevsymboltable.cc",
+               "media/devices/linuxdeviceinfo.cc",
+               "media/devices/linuxdevicemanager.cc",
+               "media/devices/v4llookup.cc",
+               "sound/alsasoundsystem.cc",
+               "sound/alsasymboltable.cc",
+               "sound/linuxsoundsystem.cc",
+               "sound/pulseaudiosoundsystem.cc",
+               "sound/pulseaudiosymboltable.cc",
+             ],
+             dependent_target_settings = {
+               'lin_libs': [
+                 "dl",
+                 "pthread",
+                 "rt",
+                 "gthread-2.0",
+               ],
+               'mac_libs': SSL_LIBS,
+               'win_libs': [
+                 "winmm.lib",
+               ],
+             },
+             mac_srcs = [
+               "base/macasyncsocket.cc",
+               "base/maccocoasocketserver.mm",
+               "base/maccocoathreadhelper.mm",
+               "base/macconversion.cc",
+               "base/macsocketserver.cc",
+               "base/macutils.cc",
+               "base/macwindowpicker.cc",
+               "base/scoped_autorelease_pool.mm",
+               "media/devices/carbonvideorenderer.cc",
+               "media/devices/macdeviceinfo.cc",
+               "media/devices/macdevicemanager.cc",
+               "media/devices/macdevicemanagermm.mm",
+             ],
+             posix_srcs = [
+               "base/unixfilesystem.cc",
+               "base/posix.cc",
+             ],
+             linphone_srcs = [
+               "media/other/linphonemediaengine.cc",
+             ],
+             cppdefines = [
+               "FEATURE_ENABLE_VOICEMAIL",
+               "EXPAT_RELATIVE_PATH",
+               "GTEST_RELATIVE_PATH",
+               "SRTP_RELATIVE_PATH",
+               "XML_STATIC",
+             ],
+             srcs = [
+               "base/asyncfile.cc",
+               "base/asynchttprequest.cc",
+               "base/asyncsocket.cc",
+               "base/asynctcpsocket.cc",
+               "base/asyncudpsocket.cc",
+               "base/autodetectproxy.cc",
+               "base/bandwidthsmoother.cc",
+               "base/base64.cc",
+               "base/basicpacketsocketfactory.cc",
+               "base/bytebuffer.cc",
+               "base/checks.cc",
+               "base/common.cc",
+               "base/cpumonitor.cc",
+               "base/crc32.cc",
+               "base/diskcache.cc",
+               "base/event.cc",
+               "base/filelock.cc",
+               "base/fileutils.cc",
+               "base/firewallsocketserver.cc",
+               "base/flags.cc",
+               "base/helpers.cc",
+               "base/host.cc",
+               "base/httpbase.cc",
+               "base/httpclient.cc",
+               "base/httpcommon.cc",
+               "base/httprequest.cc",
+               "base/httpserver.cc",
+               "base/ipaddress.cc",
+               "base/logging.cc",
+               "base/md5.cc",
+               "base/messagedigest.cc",
+               "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/nssidentity.cc",
+               "base/nssstreamadapter.cc",
+               "base/openssladapter.cc",
+               "base/openssldigest.cc",
+               "base/opensslidentity.cc",
+               "base/opensslstreamadapter.cc",
+               "base/optionsfile.cc",
+               "base/pathutils.cc",
+               "base/physicalsocketserver.cc",
+               "base/profiler.cc",
+               "base/proxydetect.cc",
+               "base/proxyinfo.cc",
+               "base/proxyserver.cc",
+               "base/ratelimiter.cc",
+               "base/ratetracker.cc",
+               "base/sha1.cc",
+               "base/sharedexclusivelock.cc",
+               "base/signalthread.cc",
+               "base/socketadapters.cc",
+               "base/socketaddress.cc",
+               "base/socketaddresspair.cc",
+               "base/socketpool.cc",
+               "base/socketstream.cc",
+               "base/ssladapter.cc",
+               "base/sslsocketfactory.cc",
+               "base/sslidentity.cc",
+               "base/sslstreamadapter.cc",
+               "base/sslstreamadapterhelper.cc",
+               "base/stream.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/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/dtlstransportchannel.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",
+               "p2p/base/rawtransport.cc",
+               "p2p/base/rawtransportchannel.cc",
+               "p2p/base/session.cc",
+               "p2p/base/sessiondescription.cc",
+               "p2p/base/sessionmanager.cc",
+               "p2p/base/sessionmessages.cc",
+               "p2p/base/stun.cc",
+               "p2p/base/stunport.cc",
+               "p2p/base/stunrequest.cc",
+               "p2p/base/stunserver.cc",
+               "p2p/base/tcpport.cc",
+               "p2p/base/transport.cc",
+               "p2p/base/transportchannel.cc",
+               "p2p/base/transportchannelproxy.cc",
+               "p2p/base/transportdescriptionfactory.cc",
+               "p2p/base/turnport.cc",
+               "p2p/base/turnserver.cc",
+               "p2p/client/basicportallocator.cc",
+               "p2p/client/connectivitychecker.cc",
+               "p2p/client/httpportallocator.cc",
+               "p2p/client/socketmonitor.cc",
+               "session/tunnel/pseudotcpchannel.cc",
+               "session/tunnel/tunnelsessionclient.cc",
+               "session/tunnel/securetunnelsessionclient.cc",
+               "media/base/capturemanager.cc",
+               "media/base/capturerenderadapter.cc",
+               "media/base/codec.cc",
+               "media/base/constants.cc",
+               "media/base/cpuid.cc",
+               "media/base/filemediaengine.cc",
+               "media/base/hybridvideoengine.cc",
+               "media/base/mediaengine.cc",
+               "media/base/rtpdataengine.cc",
+               "media/base/rtpdump.cc",
+               "media/base/rtputils.cc",
+               "media/base/streamparams.cc",
+               "media/base/videoadapter.cc",
+               "media/base/videocapturer.cc",
+               "media/base/mutedvideocapturer.cc",
+               "media/base/videocommon.cc",
+               "media/base/videoframe.cc",
+               "media/devices/devicemanager.cc",
+               "media/devices/filevideocapturer.cc",
+               "session/media/audiomonitor.cc",
+               "session/media/call.cc",
+               "session/media/channel.cc",
+               "session/media/channelmanager.cc",
+               "session/media/currentspeakermonitor.cc",
+               "session/media/mediamessages.cc",
+               "session/media/mediamonitor.cc",
+               "session/media/mediarecorder.cc",
+               "session/media/mediasession.cc",
+               "session/media/mediasessionclient.cc",
+               "session/media/rtcpmuxfilter.cc",
+               "session/media/rtcpmuxfilter.cc",
+               "session/media/soundclip.cc",
+               "session/media/srtpfilter.cc",
+               "session/media/ssrcmuxfilter.cc",
+               "session/media/typingmonitor.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",
+               "xmllite/xmlelement.cc",
+               "xmllite/xmlnsstack.cc",
+               "xmllite/xmlparser.cc",
+               "xmllite/xmlprinter.cc",
+               "xmpp/chatroommoduleimpl.cc",
+               "xmpp/constants.cc",
+               "xmpp/discoitemsquerytask.cc",
+               "xmpp/hangoutpubsubclient.cc",
+               "xmpp/iqtask.cc",
+               "xmpp/jid.cc",
+               "xmpp/jingleinfotask.cc",
+               "xmpp/moduleimpl.cc",
+               "xmpp/mucroomconfigtask.cc",
+               "xmpp/mucroomdiscoverytask.cc",
+               "xmpp/mucroomlookuptask.cc",
+               "xmpp/mucroomuniquehangoutidtask.cc",
+               "xmpp/pingtask.cc",
+               "xmpp/presenceouttask.cc",
+               "xmpp/presencereceivetask.cc",
+               "xmpp/presencestatus.cc",
+               "xmpp/pubsubclient.cc",
+               "xmpp/pubsub_task.cc",
+               "xmpp/pubsubtasks.cc",
+               "xmpp/receivetask.cc",
+               "xmpp/rostermoduleimpl.cc",
+               "xmpp/saslmechanism.cc",
+               "xmpp/xmppclient.cc",
+               "xmpp/xmppengineimpl.cc",
+               "xmpp/xmppengineimpl_iq.cc",
+               "xmpp/xmpplogintask.cc",
+               "xmpp/xmppstanzaparser.cc",
+               "xmpp/xmpptask.cc",
+               "xmpp/xmppauth.cc",
+               "xmpp/xmpppump.cc",
+               "xmpp/xmppsocket.cc",
+               "xmpp/xmppthread.cc",
+             ],
+             includedirs = [
+               "third_party/libudev",
+               "third_party/expat-2.0.1/lib",
+               "third_party/gtest/include",
+               "third_party/srtp/include",
+               "third_party/srtp/crypto/include",
+             ] + SSL_INCLUDES,
+             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",
+               "media/devices/gdivideorenderer.cc",
+               "media/devices/win32deviceinfo.cc",
+               "media/devices/win32devicemanager.cc",
+             ],
+             mac_ccflags = [
+               "-Wno-deprecated-declarations",
+             ],
+             extra_srcs = [
+               "media/devices/dummydevicemanager.cc",
+               "base/dbus.cc",
+               "base/libdbusglibsymboltable.cc",
+               "base/json.cc",
+               "base/natserver_main.cc",
+             ],
+)
+talk.Library(env, name = "videorenderer",
+             lin_srcs = [
+               "media/devices/gtkvideorenderer.cc",
+             ],
+             lin_packages = [
+               "gobject-2.0",
+               "gthread-2.0",
+               "gtk+-2.0",
+             ],
+)
+talk.Library(env, name = "unittest_main",
+             libs = [
+               "gunit",
+             ],
+             srcs = [
+               "base/unittest_main.cc",
+             ],
+             includedirs = [
+               "third_party/gtest/include",
+               "third_party/expat-2.0.1/lib",
+               "third_party/srtp",
+               "third_party/gtest",
+             ],
+             cppdefines = [
+               "EXPAT_RELATIVE_PATH",
+               "GTEST_RELATIVE_PATH",
+               "SRTP_RELATIVE_PATH",
+             ],
+)
+talk.App(env, name = "login",
+         libs = [
+           "jingle",
+           "expat",
+         ],
+         srcs = [
+           "examples/login/login_main.cc",
+         ],
+         posix_libs = SSL_LIBS,
+         lin_libs = [
+           "videorenderer",
+         ],
+)
+talk.App(env, name = "chat",
+         libs = [
+           "jingle",
+           "expat",
+         ],
+         srcs = [
+           "examples/chat/chatapp.cc",
+           "examples/chat/chat_main.cc",
+           "examples/chat/consoletask.cc",
+           "examples/chat/textchatreceivetask.cc",
+           "examples/chat/textchatsendtask.cc",
+         ],
+         posix_libs = SSL_LIBS,
+)
+talk.App(env, name = "call",
+         mac_frameworks = [
+           "AudioToolbox",
+           "AudioUnit",
+           "Cocoa",
+           "CoreAudio",
+           "CoreFoundation",
+           "IOKit",
+           "QTKit",
+           "QuickTime",
+         ],
+         win_libs = [
+           "d3d9.lib",
+           "gdi32.lib",
+           "powrprof.lib",
+           "strmiids.lib",
+           "winmm.lib",
+         ],
+         posix_libs = SSL_LIBS,
+         lin_libs = [
+           "videorenderer",
+         ],
+         srcs = [
+           "examples/call/call_main.cc",
+           "examples/call/callclient.cc",
+           "examples/call/console.cc",
+           "examples/call/friendinvitesendtask.cc",
+           "examples/call/mediaenginefactory.cc",
+           "examples/call/mucinviterecvtask.cc",
+           "examples/call/mucinvitesendtask.cc",
+           "examples/call/presencepushtask.cc",
+         ],
+         libs = [
+           "jingle",
+           "expat",
+           "srtp",
+         ],
+)
+talk.App(env, name = "relayserver",
+         libs = [
+           "jingle",
+         ],
+         srcs = [
+           "p2p/base/relayserver_main.cc",
+         ],
+)
+talk.App(env, name = "stunserver",
+         libs = [
+           "jingle",
+         ],
+         srcs = [
+           "p2p/base/stunserver_main.cc",
+         ],
+)
+talk.App(env, name = "turnserver",
+         lin_libs = [
+           "crypto",
+           "ssl",
+         ],
+         srcs = [
+           "p2p/base/turnserver_main.cc",
+         ],
+         libs = [
+           "jingle",
+         ],
+)
+talk.Unittest(env, name = "base",
+              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 = [
+                "EXPAT_RELATIVE_PATH",
+                "GTEST_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/basictypes_unittest.cc",
+                "base/bind_unittest.cc",
+                "base/buffer_unittest.cc",
+                "base/bytebuffer_unittest.cc",
+                "base/byteorder_unittest.cc",
+                "base/cpumonitor_unittest.cc",
+                "base/crc32_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/md5digest_unittest.cc",
+                "base/messagedigest_unittest.cc",
+                "base/messagequeue_unittest.cc",
+                "base/multipart_unittest.cc",
+                "base/nat_unittest.cc",
+                "base/network_unittest.cc",
+                "base/nullsocketserver_unittest.cc",
+                "base/optionsfile_unittest.cc",
+                "base/pathutils_unittest.cc",
+                "base/physicalsocketserver_unittest.cc",
+                "base/profiler_unittest.cc",
+                "base/proxy_unittest.cc",
+                "base/proxydetect_unittest.cc",
+                "base/ratelimiter_unittest.cc",
+                "base/ratetracker_unittest.cc",
+                "base/referencecountedsingletonfactory_unittest.cc",
+                "base/rollingaccumulator_unittest.cc",
+                "base/sha1digest_unittest.cc",
+                "base/sharedexclusivelock_unittest.cc",
+                "base/signalthread_unittest.cc",
+                "base/sigslot_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/timeutils_unittest.cc",
+                "base/urlencode_unittest.cc",
+                "base/versionparsing_unittest.cc",
+                "base/virtualsocket_unittest.cc",
+                "base/windowpicker_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 = SSL_LIBS,
+              cppdefines = [
+                "EXPAT_RELATIVE_PATH",
+                "GTEST_RELATIVE_PATH",
+                "SRTP_RELATIVE_PATH",
+              ],
+              srcs = [
+                "p2p/base/dtlstransportchannel_unittest.cc",
+                "p2p/base/p2ptransportchannel_unittest.cc",
+                "p2p/base/port_unittest.cc",
+                "p2p/base/portallocatorsessionproxy_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/base/transportdescriptionfactory_unittest.cc",
+                "p2p/base/turnport_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",
+              win_libs = [
+                "winmm.lib",
+                "strmiids",
+              ],
+              cppdefines = [
+                "EXPAT_RELATIVE_PATH",
+                "GTEST_RELATIVE_PATH",
+                "SRTP_RELATIVE_PATH",
+              ],
+              srcs = [
+                "media/base/capturemanager_unittest.cc",
+                "media/base/codec_unittest.cc",
+                "media/base/filemediaengine_unittest.cc",
+                "media/base/rtpdataengine_unittest.cc",
+                "media/base/rtpdump_unittest.cc",
+                "media/base/rtputils_unittest.cc",
+                "media/base/testutils.cc",
+                "media/base/videocapturer_unittest.cc",
+                "media/base/videocommon_unittest.cc",
+                "media/devices/devicemanager_unittest.cc",
+                "media/devices/filevideocapturer_unittest.cc",
+                "session/media/channel_unittest.cc",
+                "session/media/channelmanager_unittest.cc",
+                "session/media/currentspeakermonitor_unittest.cc",
+                "session/media/mediarecorder_unittest.cc",
+                "session/media/mediamessages_unittest.cc",
+                "session/media/mediasession_unittest.cc",
+                "session/media/mediasessionclient_unittest.cc",
+                "session/media/rtcpmuxfilter_unittest.cc",
+                "session/media/srtpfilter_unittest.cc",
+                "session/media/ssrcmuxfilter_unittest.cc",
+              ],
+              includedirs = [
+                "third_party/gtest/include",
+                "third_party/expat-2.0.1/lib",
+                "third_party/srtp",
+                "third_party/gtest",
+              ],
+              libs = [
+                "jingle",
+                "expat",
+                "srtp",
+              ],
+              extra_srcs = [
+                "media/devices/dummydevicemanager_unittest.cc",
+              ],
+)
+talk.Unittest(env, name = "sound",
+              libs = [
+                "jingle",
+              ],
+              srcs = [
+                "sound/automaticallychosensoundsystem_unittest.cc",
+              ],
+              mac_libs = SSL_LIBS,
+
+              includedirs = [
+                "third_party/gtest/include",
+                "third_party/expat-2.0.1/lib",
+                "third_party/srtp",
+                "third_party/gtest",
+              ],
+              cppdefines = [
+                "EXPAT_RELATIVE_PATH",
+                "GTEST_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 = SSL_LIBS,
+              includedirs = [
+                "third_party/gtest/include",
+                "third_party/expat-2.0.1/lib",
+                "third_party/srtp",
+                "third_party/gtest",
+              ],
+              cppdefines = [
+                "EXPAT_RELATIVE_PATH",
+                "GTEST_RELATIVE_PATH",
+                "SRTP_RELATIVE_PATH",
+              ],
+)
+talk.Unittest(env, name = "xmpp",
+              mac_libs = SSL_LIBS,
+              cppdefines = [
+                "EXPAT_RELATIVE_PATH",
+                "GTEST_RELATIVE_PATH",
+                "SRTP_RELATIVE_PATH",
+              ],
+              srcs = [
+                "xmpp/hangoutpubsubclient_unittest.cc",
+                "xmpp/jid_unittest.cc",
+                "xmpp/mucroomconfigtask_unittest.cc",
+                "xmpp/mucroomdiscoverytask_unittest.cc",
+                "xmpp/mucroomlookuptask_unittest.cc",
+                "xmpp/mucroomuniquehangoutidtask_unittest.cc",
+                "xmpp/pingtask_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",
+                "xmpp/rostermodule_unittest.cc",
+              ],
+)
diff --git a/talk/libjingle_all.gyp b/talk/libjingle_all.gyp
new file mode 100644
index 0000000..1bfa2df
--- /dev/null
+++ b/talk/libjingle_all.gyp
@@ -0,0 +1,40 @@
+#
+# libjingle
+# Copyright 2012, Google Inc.
+#
+# Redistribution and use in source and binary forms, with or without 
+# modification, are permitted provided that the following conditions are met:
+#
+#  1. Redistributions of source code must retain the above copyright notice, 
+#     this list of conditions and the following disclaimer.
+#  2. Redistributions in binary form must reproduce the above copyright notice,
+#     this list of conditions and the following disclaimer in the documentation
+#     and/or other materials provided with the distribution.
+#  3. The name of the author may not be used to endorse or promote products 
+#     derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+{
+  'targets': [
+    {
+      'target_name': 'All', 
+      'type': 'none',   
+      'dependencies': [ 
+        'libjingle.gyp:*',  
+        'libjingle_examples.gyp:*',  
+        'libjingle_tests.gyp:*',  
+      ],
+    },
+  ],
+}
diff --git a/talk/libjingle_examples.gyp b/talk/libjingle_examples.gyp
new file mode 100755
index 0000000..3a33740
--- /dev/null
+++ b/talk/libjingle_examples.gyp
@@ -0,0 +1,274 @@
+#
+# libjingle
+# Copyright 2012, Google Inc.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#  1. Redistributions of source code must retain the above copyright notice,
+#     this list of conditions and the following disclaimer.
+#  2. Redistributions in binary form must reproduce the above copyright notice,
+#     this list of conditions and the following disclaimer in the documentation
+#     and/or other materials provided with the distribution.
+#  3. The name of the author may not be used to endorse or promote products
+#     derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+{
+  'includes': [
+    'build/common.gypi',
+  ],
+  'targets': [
+    {
+      'target_name': 'libjingle_xmpphelp',
+      'type': 'static_library',
+      'dependencies': [
+        '<(DEPTH)/third_party/expat/expat.gyp:expat',
+        'libjingle.gyp:libjingle',
+        'libjingle.gyp:libjingle_p2p',
+      ],
+      'sources': [
+        'xmpp/jingleinfotask.cc',
+        'xmpp/jingleinfotask.h',
+      ],
+    },  # target libjingle_xmpphelp
+    {
+      'target_name': 'relayserver',
+      'type': 'executable',
+      'dependencies': [
+        'libjingle.gyp:libjingle',
+        'libjingle.gyp:libjingle_p2p',
+      ],
+      'sources': [
+        'p2p/base/relayserver_main.cc',
+      ],
+    },  # target relayserver
+    {
+      'target_name': 'stunserver',
+      'type': 'executable',
+      'dependencies': [
+        'libjingle.gyp:libjingle',
+        'libjingle.gyp:libjingle_p2p',
+      ],
+      'sources': [
+        'p2p/base/stunserver_main.cc',
+      ],
+    },  # target stunserver
+    {
+      'target_name': 'turnserver',
+      'type': 'executable',
+      'dependencies': [
+        'libjingle.gyp:libjingle',
+        'libjingle.gyp:libjingle_p2p',
+      ],
+      'sources': [
+        'p2p/base/turnserver_main.cc',
+      ],
+    },  # target turnserver
+    {
+      'target_name': 'login',
+      'type': 'executable',
+      'dependencies': [
+        'libjingle_xmpphelp',
+      ],
+      'sources': [
+        'examples/login/login_main.cc',
+      ],
+    },  # target login
+    {
+      'target_name': 'peerconnection_server',
+      'type': 'executable',
+      'sources': [
+        'examples/peerconnection/server/data_socket.cc',
+        'examples/peerconnection/server/data_socket.h',
+        'examples/peerconnection/server/main.cc',
+        'examples/peerconnection/server/peer_channel.cc',
+        'examples/peerconnection/server/peer_channel.h',
+        'examples/peerconnection/server/utils.cc',
+        'examples/peerconnection/server/utils.h',
+      ],
+      'dependencies': [
+        'libjingle.gyp:libjingle',
+      ],
+      # TODO(ronghuawu): crbug.com/167187 fix size_t to int truncations.
+      'msvs_disabled_warnings': [ 4309, ],
+    }, # target peerconnection_server
+  ],
+  'conditions': [
+    # TODO(ronghuawu): Reenable building call.
+    # ['OS!="android"', {
+    #   'targets': [
+    #     {
+    #       'target_name': 'call',
+    #       'type': 'executable',
+    #       'dependencies': [
+    #         'libjingle.gyp:libjingle_p2p',
+    #         'libjingle_xmpphelp',
+    #       ],
+    #       'sources': [
+    #         'examples/call/call_main.cc',
+    #         'examples/call/callclient.cc',
+    #         'examples/call/callclient.h',
+    #         'examples/call/console.cc',
+    #         'examples/call/console.h',
+    #         'examples/call/friendinvitesendtask.cc',
+    #         'examples/call/friendinvitesendtask.h',
+    #         'examples/call/mediaenginefactory.cc',
+    #         'examples/call/mediaenginefactory.h',
+    #         'examples/call/muc.h',
+    #         'examples/call/mucinviterecvtask.cc',
+    #         'examples/call/mucinviterecvtask.h',
+    #         'examples/call/mucinvitesendtask.cc',
+    #         'examples/call/mucinvitesendtask.h',
+    #         'examples/call/presencepushtask.cc',
+    #         'examples/call/presencepushtask.h',
+    #       ],
+    #       'conditions': [
+    #         ['OS=="linux"', {
+    #           'link_settings': {
+    #             'libraries': [
+    #               '<!@(pkg-config --libs-only-l gobject-2.0 gthread-2.0'
+    #                   ' gtk+-2.0)',
+    #             ],
+    #           },
+    #         }],
+    #         ['OS=="win"', {
+    #           'msvs_settings': {
+    #             'VCLinkerTool': {
+    #               'AdditionalDependencies': [
+    #                 'strmiids.lib',
+    #               ],
+    #             },
+    #           },
+    #         }],
+    #       ],  # conditions
+    #     },  # target call
+    #   ], # targets
+    # }],  # OS!="android"
+    ['OS=="linux" or OS=="win"', {
+      'targets': [
+        {
+          'target_name': 'peerconnection_client',
+          'type': 'executable',
+          'sources': [
+            'examples/peerconnection/client/conductor.cc',
+            'examples/peerconnection/client/conductor.h',
+            'examples/peerconnection/client/defaults.cc',
+            'examples/peerconnection/client/defaults.h',
+            'examples/peerconnection/client/peer_connection_client.cc',
+            'examples/peerconnection/client/peer_connection_client.h',
+          ],
+          'dependencies': [
+            '<(DEPTH)/third_party/jsoncpp/jsoncpp.gyp:jsoncpp',
+            'libjingle.gyp:libjingle_peerconnection',
+          ],
+          'conditions': [
+            # TODO(ronghuawu): Move these files to a win/ directory then they
+            # can be excluded automatically.
+            ['OS=="win"', {
+              'sources': [
+                'examples/peerconnection/client/flagdefs.h',
+                'examples/peerconnection/client/main.cc',
+                'examples/peerconnection/client/main_wnd.cc',
+                'examples/peerconnection/client/main_wnd.h',
+              ],
+              'msvs_settings': {
+                'VCLinkerTool': {
+                 'SubSystem': '2',  # Windows
+                },
+              },
+            }],  # OS=="win"
+            ['OS=="linux"', {
+              'sources': [
+                'examples/peerconnection/client/linux/main.cc',
+                'examples/peerconnection/client/linux/main_wnd.cc',
+                'examples/peerconnection/client/linux/main_wnd.h',
+              ],
+              'cflags': [
+                '<!@(pkg-config --cflags glib-2.0 gobject-2.0 gtk+-2.0)',
+              ],
+              'link_settings': {
+                'ldflags': [
+                  '<!@(pkg-config --libs-only-L --libs-only-other glib-2.0'
+                      ' gobject-2.0 gthread-2.0 gtk+-2.0)',
+                ],
+                'libraries': [
+                  '<!@(pkg-config --libs-only-l glib-2.0 gobject-2.0'
+                      ' gthread-2.0 gtk+-2.0)',
+                  '-lX11',
+                  '-lXcomposite',
+                  '-lXext',
+                  '-lXrender',
+                ],
+              },
+            }],  # OS=="linux"
+          ],  # conditions
+        },  # target peerconnection_client
+      ], # targets
+    }],  # OS=="linux" or OS=="win"
+
+    ['OS=="android"', {
+      'targets': [
+        {
+          'target_name': 'AppRTCDemo',
+          'type': 'none',
+          'dependencies': [
+            'libjingle.gyp:libjingle_peerconnection_jar',
+          ],
+          'actions': [
+            {
+              # TODO(fischman): convert from a custom script to a standard gyp
+              # apk build once chromium's apk-building gyp machinery can be used
+              # (http://crbug.com/225101)
+              'action_name': 'build_apprtcdemo_apk',
+              'inputs' : [
+                '<(PRODUCT_DIR)/libjingle_peerconnection_so.so',
+                'examples/android/AndroidManifest.xml',
+                'examples/android/README',
+                'examples/android/ant.properties',
+                'examples/android/build.xml',
+                'examples/android/jni/Android.mk',
+                'examples/android/project.properties',
+                'examples/android/res/drawable-hdpi/ic_launcher.png',
+                'examples/android/res/drawable-ldpi/ic_launcher.png',
+                'examples/android/res/drawable-mdpi/ic_launcher.png',
+                'examples/android/res/drawable-xhdpi/ic_launcher.png',
+                'examples/android/res/values/strings.xml',
+                'examples/android/src/org/appspot/apprtc/AppRTCClient.java',
+                'examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java',
+                'examples/android/src/org/appspot/apprtc/FramePool.java',
+                'examples/android/src/org/appspot/apprtc/GAEChannelClient.java',
+                'examples/android/src/org/appspot/apprtc/VideoStreamsView.java',
+              ],
+              'outputs': [
+                '<(PRODUCT_DIR)/AppRTCDemo-debug.apk',
+              ],
+              'action': [
+                'bash', '-ec',
+                'rm -f <(_outputs) && '
+                'mkdir -p examples/android/libs/<(android_app_abi) && '
+                'cp <(PRODUCT_DIR)/libjingle_peerconnection.jar examples/android/libs/ &&'
+                '<(android_strip) -o examples/android/libs/<(android_app_abi)/libjingle_peerconnection_so.so  <(PRODUCT_DIR)/libjingle_peerconnection_so.so &&'
+                'cd examples/android && '
+                'ant debug && '
+                'cd - && '
+                'cp examples/android/bin/AppRTCDemo-debug.apk <(_outputs)'
+              ],
+            },
+          ],
+        },  # target AppRTCDemo
+      ],  # targets
+    }],  # OS=="android"
+  ],
+}
diff --git a/talk/libjingle_tests.gyp b/talk/libjingle_tests.gyp
new file mode 100755
index 0000000..72184ec
--- /dev/null
+++ b/talk/libjingle_tests.gyp
@@ -0,0 +1,529 @@
+#
+# libjingle
+# Copyright 2012, Google Inc.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#  1. Redistributions of source code must retain the above copyright notice,
+#     this list of conditions and the following disclaimer.
+#  2. Redistributions in binary form must reproduce the above copyright notice,
+#     this list of conditions and the following disclaimer in the documentation
+#     and/or other materials provided with the distribution.
+#  3. The name of the author may not be used to endorse or promote products
+#     derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+{
+  'includes': ['build/common.gypi'],
+  'targets': [
+    {
+      # TODO(ronghuawu): Use gtest.gyp from chromium.
+      'target_name': 'gunit',
+      'type': 'static_library',
+      'sources': [
+        '<(DEPTH)/third_party/gtest/src/gtest-all.cc',
+      ],
+      'include_dirs': [
+        '<(DEPTH)/third_party/gtest/include',
+        '<(DEPTH)/third_party/gtest',
+      ],
+      'direct_dependent_settings': {
+        'include_dirs': [
+          '<(DEPTH)/third_party/gtest/include',
+        ],
+      },
+      'conditions': [
+        ['OS=="android"', {
+          'include_dirs': [
+            '<(android_ndk_include)',
+          ]
+        }],
+      ],
+    },  # target gunit
+    {
+      'target_name': 'libjingle_unittest_main',
+      'type': 'static_library',
+      'dependencies': [
+        '<(DEPTH)/third_party/libyuv/libyuv.gyp:libyuv',
+        'gunit',
+      ],
+      'direct_dependent_settings': {
+        'include_dirs': [
+          '<(DEPTH)/third_party/libyuv/include',
+        ],
+      },
+      'sources': [
+        'base/unittest_main.cc',
+        # Also use this as a convenient dumping ground for misc files that are
+        # included by multiple targets below.
+        'base/fakecpumonitor.h',
+        'base/fakenetwork.h',
+        'base/fakesslidentity.h',
+        'base/faketaskrunner.h',
+        'base/gunit.h',
+        'base/testbase64.h',
+        'base/testechoserver.h',
+        'base/win32toolhelp.h',
+        'media/base/fakecapturemanager.h',
+        'media/base/fakemediaengine.h',
+        'media/base/fakemediaprocessor.h',
+        'media/base/fakenetworkinterface.h',
+        'media/base/fakertp.h',
+        'media/base/fakevideocapturer.h',
+        'media/base/fakevideorenderer.h',
+        'media/base/nullvideoframe.h',
+        'media/base/nullvideorenderer.h',
+        'media/base/testutils.cc',
+        'media/base/testutils.h',
+        'media/devices/fakedevicemanager.h',
+        'media/webrtc/fakewebrtccommon.h',
+        'media/webrtc/fakewebrtcdeviceinfo.h',
+        'media/webrtc/fakewebrtcvcmfactory.h',
+        'media/webrtc/fakewebrtcvideocapturemodule.h',
+        'media/webrtc/fakewebrtcvideoengine.h',
+        'media/webrtc/fakewebrtcvoiceengine.h',
+      ],
+    },  # target libjingle_unittest_main
+    {
+      'target_name': 'libjingle_unittest',
+      'type': 'executable',
+      'dependencies': [
+        'gunit',
+        'libjingle.gyp:libjingle',
+        'libjingle_unittest_main',
+      ],
+      'sources': [
+        'base/asynchttprequest_unittest.cc',
+        'base/atomicops_unittest.cc',
+        'base/autodetectproxy_unittest.cc',
+        'base/bandwidthsmoother_unittest.cc',
+        'base/base64_unittest.cc',
+        'base/basictypes_unittest.cc',
+        'base/bind_unittest.cc',
+        'base/buffer_unittest.cc',
+        'base/bytebuffer_unittest.cc',
+        'base/byteorder_unittest.cc',
+        'base/cpumonitor_unittest.cc',
+        'base/crc32_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/md5digest_unittest.cc',
+        'base/messagedigest_unittest.cc',
+        'base/messagequeue_unittest.cc',
+        'base/multipart_unittest.cc',
+        'base/nat_unittest.cc',
+        'base/network_unittest.cc',
+        'base/nullsocketserver_unittest.cc',
+        'base/optionsfile_unittest.cc',
+        'base/pathutils_unittest.cc',
+        'base/physicalsocketserver_unittest.cc',
+        'base/profiler_unittest.cc',
+        'base/proxy_unittest.cc',
+        'base/proxydetect_unittest.cc',
+        'base/ratelimiter_unittest.cc',
+        'base/ratetracker_unittest.cc',
+        'base/referencecountedsingletonfactory_unittest.cc',
+        'base/rollingaccumulator_unittest.cc',
+        'base/sha1digest_unittest.cc',
+        'base/sharedexclusivelock_unittest.cc',
+        'base/signalthread_unittest.cc',
+        'base/sigslot_unittest.cc',
+        'base/socket_unittest.cc',
+        'base/socket_unittest.h',
+        'base/socketaddress_unittest.cc',
+        'base/stream_unittest.cc',
+        'base/stringencode_unittest.cc',
+        'base/stringutils_unittest.cc',
+        # TODO(ronghuawu): Reenable this test.
+        # 'base/systeminfo_unittest.cc',
+        'base/task_unittest.cc',
+        'base/testclient_unittest.cc',
+        'base/thread_unittest.cc',
+        'base/timeutils_unittest.cc',
+        'base/urlencode_unittest.cc',
+        'base/versionparsing_unittest.cc',
+        'base/virtualsocket_unittest.cc',
+        # TODO(ronghuawu): Reenable this test.
+        # 'base/windowpicker_unittest.cc',
+        'xmllite/qname_unittest.cc',
+        'xmllite/xmlbuilder_unittest.cc',
+        'xmllite/xmlelement_unittest.cc',
+        'xmllite/xmlnsstack_unittest.cc',
+        'xmllite/xmlparser_unittest.cc',
+        'xmllite/xmlprinter_unittest.cc',
+        'xmpp/fakexmppclient.h',
+        'xmpp/hangoutpubsubclient_unittest.cc',
+        'xmpp/jid_unittest.cc',
+        'xmpp/mucroomconfigtask_unittest.cc',
+        'xmpp/mucroomdiscoverytask_unittest.cc',
+        'xmpp/mucroomlookuptask_unittest.cc',
+        'xmpp/mucroomuniquehangoutidtask_unittest.cc',
+        'xmpp/pingtask_unittest.cc',
+        'xmpp/pubsubclient_unittest.cc',
+        'xmpp/pubsubtasks_unittest.cc',
+        'xmpp/util_unittest.cc',
+        'xmpp/util_unittest.h',
+        'xmpp/xmppengine_unittest.cc',
+        'xmpp/xmpplogintask_unittest.cc',
+        'xmpp/xmppstanzaparser_unittest.cc',
+      ],  # sources
+      'conditions': [
+        ['OS=="linux"', {
+          'sources': [
+            'base/latebindingsymboltable_unittest.cc',
+            # TODO(ronghuawu): Reenable this test.
+            # 'base/linux_unittest.cc',
+            'base/linuxfdwalk_unittest.cc',
+          ],
+        }],
+        ['OS=="win"', {
+          'sources': [
+            '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',
+          ],
+          'sources!': [
+            # TODO(ronghuawu): Fix TestUdpReadyToSendIPv6 on windows bot
+            # then reenable these tests.
+            'base/physicalsocketserver_unittest.cc',
+            'base/socket_unittest.cc',
+            'base/win32socketserver_unittest.cc',
+            'base/win32windowpicker_unittest.cc',
+          ],
+        }],
+        ['OS=="mac"', {
+          'sources': [
+            'base/macsocketserver_unittest.cc',
+            'base/macutils_unittest.cc',
+            'base/macwindowpicker_unittest.cc',
+          ],
+        }],
+        ['os_posix==1', {
+          'sources': [
+            'base/sslidentity_unittest.cc',
+            # TODO(ronghuawu): reenable once fixed on build bots.
+            # 'base/sslstreamadapter_unittest.cc',
+          ],
+        }],
+      ],  # conditions
+    },  # target libjingle_unittest
+    {
+      'target_name': 'libjingle_sound_unittest',
+      'type': 'executable',
+      'dependencies': [
+        'gunit',
+        'libjingle.gyp:libjingle_sound',
+        'libjingle_unittest_main',
+      ],
+      'sources': [
+        'sound/automaticallychosensoundsystem_unittest.cc',
+      ],
+    },  # target libjingle_sound_unittest
+    {
+      'target_name': 'libjingle_media_unittest',
+      'type': 'executable',
+      'dependencies': [
+        'gunit',
+        'libjingle.gyp:libjingle_media',
+        'libjingle_unittest_main',
+      ],
+      # TODO(ronghuawu): Avoid the copies.
+      # https://code.google.com/p/libjingle/issues/detail?id=398
+      'copies': [
+        {
+          'destination': '<(DEPTH)/../talk/media/testdata',
+          'files': [
+            'media/testdata/1.frame_plus_1.byte',
+            'media/testdata/captured-320x240-2s-48.frames',
+            'media/testdata/h264-svc-99-640x360.rtpdump',
+            'media/testdata/video.rtpdump',
+            'media/testdata/voice.rtpdump',
+          ],
+        },
+      ],
+      'sources': [
+        # TODO(ronghuawu): Reenable this test.
+        # 'media/base/capturemanager_unittest.cc',
+        'media/base/codec_unittest.cc',
+        'media/base/filemediaengine_unittest.cc',
+        'media/base/rtpdataengine_unittest.cc',
+        'media/base/rtpdump_unittest.cc',
+        'media/base/rtputils_unittest.cc',
+        'media/base/testutils.cc',
+        'media/base/testutils.h',
+        'media/base/videocapturer_unittest.cc',
+        'media/base/videocommon_unittest.cc',
+        'media/base/videoengine_unittest.h',
+        'media/devices/dummydevicemanager_unittest.cc',
+        'media/devices/filevideocapturer_unittest.cc',
+        'media/webrtc/webrtcpassthroughrender_unittest.cc',
+        'media/webrtc/webrtcvideocapturer_unittest.cc',
+        # Omitted because depends on non-open-source testdata files.
+        # 'media/base/videoframe_unittest.h',
+        # 'media/webrtc/webrtcvideoframe_unittest.cc',
+
+        # Disabled because some tests fail.
+        # TODO(ronghuawu): Reenable these tests.
+        # 'media/devices/devicemanager_unittest.cc',
+        # 'media/webrtc/webrtcvideoengine_unittest.cc',
+        # 'media/webrtc/webrtcvoiceengine_unittest.cc',
+      ],
+      'conditions': [
+        ['OS=="win"', {
+          'msvs_settings': {
+            'VCLinkerTool': {
+              'AdditionalDependencies': [
+                # TODO(ronghuawu): Since we've included strmiids in
+                # libjingle_media target, we shouldn't need this here.
+                # Find out why it doesn't work without this.
+                'strmiids.lib',
+              ],
+            },
+          },
+        }],
+      ],
+    },  # target libjingle_media_unittest
+    {
+      'target_name': 'libjingle_p2p_unittest',
+      'type': 'executable',
+      'dependencies': [
+        '<(DEPTH)/third_party/libsrtp/libsrtp.gyp:libsrtp',
+        'gunit',
+        'libjingle.gyp:libjingle',
+        'libjingle.gyp:libjingle_p2p',
+        'libjingle_unittest_main',
+      ],
+      'include_dirs': [
+        '<(DEPTH)/third_party/libsrtp/srtp',
+      ],
+      'sources': [
+        'p2p/base/dtlstransportchannel_unittest.cc',
+        'p2p/base/fakesession.h',
+        'p2p/base/p2ptransportchannel_unittest.cc',
+        'p2p/base/port_unittest.cc',
+        'p2p/base/portallocatorsessionproxy_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/testrelayserver.h',
+        'p2p/base/teststunserver.h',
+        'p2p/base/testturnserver.h',
+        'p2p/base/transport_unittest.cc',
+        'p2p/base/transportdescriptionfactory_unittest.cc',
+        'p2p/client/connectivitychecker_unittest.cc',
+        'p2p/client/fakeportallocator.h',
+        'p2p/client/portallocator_unittest.cc',
+        'session/media/channel_unittest.cc',
+        'session/media/channelmanager_unittest.cc',
+        'session/media/currentspeakermonitor_unittest.cc',
+        'session/media/mediarecorder_unittest.cc',
+        'session/media/mediamessages_unittest.cc',
+        'session/media/mediasession_unittest.cc',
+        'session/media/mediasessionclient_unittest.cc',
+        'session/media/rtcpmuxfilter_unittest.cc',
+        'session/media/srtpfilter_unittest.cc',
+        'session/media/ssrcmuxfilter_unittest.cc',
+      ],
+      'conditions': [
+        ['OS=="win"', {
+          'msvs_settings': {
+            'VCLinkerTool': {
+              'AdditionalDependencies': [
+                'strmiids.lib',
+              ],
+            },
+          },
+        }],
+      ],
+    },  # target libjingle_p2p_unittest
+    {
+      'target_name': 'libjingle_peerconnection_unittest',
+      'type': 'executable',
+      'dependencies': [
+        'gunit',
+        'libjingle.gyp:libjingle',
+        'libjingle.gyp:libjingle_p2p',
+        'libjingle.gyp:libjingle_peerconnection',
+        'libjingle_unittest_main',
+      ],
+      # TODO(ronghuawu): Reenable below unit tests that require gmock.
+      'sources': [
+        'app/webrtc/dtmfsender_unittest.cc',
+        'app/webrtc/jsepsessiondescription_unittest.cc',
+        'app/webrtc/localaudiosource_unittest.cc',
+        'app/webrtc/localvideosource_unittest.cc',
+        # 'app/webrtc/mediastream_unittest.cc',
+        # 'app/webrtc/mediastreamhandler_unittest.cc',
+        'app/webrtc/mediastreamsignaling_unittest.cc',
+        'app/webrtc/peerconnection_unittest.cc',
+        'app/webrtc/peerconnectionfactory_unittest.cc',
+        'app/webrtc/peerconnectioninterface_unittest.cc',
+        # 'app/webrtc/peerconnectionproxy_unittest.cc',
+        'app/webrtc/test/fakeaudiocapturemodule.cc',
+        'app/webrtc/test/fakeaudiocapturemodule.h',
+        'app/webrtc/test/fakeaudiocapturemodule_unittest.cc',
+        'app/webrtc/test/fakeconstraints.h',
+        'app/webrtc/test/fakeperiodicvideocapturer.h',
+        'app/webrtc/test/fakevideotrackrenderer.h',
+        'app/webrtc/test/mockpeerconnectionobservers.h',
+        'app/webrtc/test/testsdpstrings.h',
+        'app/webrtc/videotrack_unittest.cc',
+        'app/webrtc/webrtcsdp_unittest.cc',
+        'app/webrtc/webrtcsession_unittest.cc',
+      ],
+    },  # target libjingle_peerconnection_unittest
+  ],
+  'conditions': [
+    ['OS=="linux"', {
+      'targets': [
+        {
+          'target_name': 'libjingle_peerconnection_test_jar',
+          'type': 'none',
+          'actions': [
+            {
+              'variables': {
+                'java_src_dir': 'app/webrtc/javatests/src',
+                'java_files': [
+                  'app/webrtc/javatests/src/org/webrtc/PeerConnectionTest.java',
+                ],
+              },
+              'action_name': 'create_jar',
+              'inputs': [
+                'build/build_jar.sh',
+                '<@(java_files)',
+                '<(PRODUCT_DIR)/libjingle_peerconnection.jar',
+                '<(DEPTH)/third_party/junit/junit-4.11.jar',
+              ],
+              'outputs': [
+                '<(PRODUCT_DIR)/libjingle_peerconnection_test.jar',
+              ],
+              'action': [
+                'build/build_jar.sh', '/usr', '<@(_outputs)',
+                '<(INTERMEDIATE_DIR)',
+                '<(java_src_dir):<(PRODUCT_DIR)/libjingle_peerconnection.jar:<(DEPTH)/third_party/junit/junit-4.11.jar',
+                '<@(java_files)'
+              ],
+            },
+          ],
+        },
+        {
+          'target_name': 'libjingle_peerconnection_java_unittest',
+          'type': 'none',
+          'actions': [
+            {
+              'action_name': 'copy libjingle_peerconnection_java_unittest',
+              'inputs': [
+                'app/webrtc/javatests/libjingle_peerconnection_java_unittest.sh',
+                '<(PRODUCT_DIR)/libjingle_peerconnection_test_jar',
+                '<(DEPTH)/third_party/junit/junit-4.11.jar',
+              ],
+              'outputs': [
+                '<(PRODUCT_DIR)/libjingle_peerconnection_java_unittest',
+              ],
+              'action': [
+                'bash', '-c',
+                'rm -f <(PRODUCT_DIR)/libjingle_peerconnection_java_unittest && '
+                'sed -e "s@GYP_JAVA_HOME@<(java_home)@" '
+                '< app/webrtc/javatests/libjingle_peerconnection_java_unittest.sh '
+                '> <(PRODUCT_DIR)/libjingle_peerconnection_java_unittest && '
+                'cp <(DEPTH)/third_party/junit/junit-4.11.jar <(PRODUCT_DIR) && '
+                'chmod u+x <(PRODUCT_DIR)/libjingle_peerconnection_java_unittest'
+              ],
+            },
+          ],
+        },
+      ],
+    }],
+    ['libjingle_objc == 1', {
+      'targets': [
+        {
+          'variables': {
+            'infoplist_file': './app/webrtc/objctests/Info.plist',
+          },
+          'target_name': 'libjingle_peerconnection_objc_test',
+          'type': 'executable',
+          'mac_bundle': 1,
+          'mac_bundle_resources': [
+            '<(infoplist_file)',
+          ],
+          # The plist is listed above so that it appears in XCode's file list,
+          # but we don't actually want to bundle it.
+          'mac_bundle_resources!': [
+            '<(infoplist_file)',
+          ],
+          'xcode_settings': {
+            'INFOPLIST_FILE': '<(infoplist_file)',
+          },
+          'dependencies': [
+            'gunit',
+            'libjingle.gyp:libjingle_peerconnection_objc',
+          ],
+          'FRAMEWORK_SEARCH_PATHS': [
+            '$(inherited)',
+            '$(SDKROOT)/Developer/Library/Frameworks',
+            '$(DEVELOPER_LIBRARY_DIR)/Frameworks',
+          ],
+          'sources': [
+            'app/webrtc/objctests/RTCPeerConnectionSyncObserver.h',
+            'app/webrtc/objctests/RTCPeerConnectionSyncObserver.m',
+            'app/webrtc/objctests/RTCPeerConnectionTest.mm',
+            'app/webrtc/objctests/RTCSessionDescriptionSyncObserver.h',
+            'app/webrtc/objctests/RTCSessionDescriptionSyncObserver.m',
+          ],
+          'include_dirs': [
+            '<(DEPTH)/talk/app/webrtc/objc/public',
+          ],
+          'conditions': [
+            [ 'OS=="mac"', {
+              'sources': [
+                'app/webrtc/objctests/mac/main.mm',
+              ],
+              'xcode_settings': {
+                'CLANG_ENABLE_OBJC_ARC': 'YES',
+                'CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS': 'NO',
+                'CLANG_LINK_OBJC_RUNTIME': 'YES',
+                # build/common.gypi disables ARC by default for back-compat
+                # reasons with OSX 10.6.   Enabling OBJC runtime and clearing
+                # LDPLUSPLUS and CC re-enables it.  Setting deployment target to
+                # 10.7 as there are no back-compat issues with ARC.
+                # https://code.google.com/p/chromium/issues/detail?id=156530
+                'CC': '',
+                'LDPLUSPLUS': '',
+                'macosx_deployment_target': '10.7',
+              },
+            }],
+          ],
+        },
+      ],
+    }],
+  ],
+}
diff --git a/talk/main.scons b/talk/main.scons
new file mode 100644
index 0000000..76b24af
--- /dev/null
+++ b/talk/main.scons
@@ -0,0 +1,889 @@
+# -*- Python -*-
+#
+#
+# All the helper functions are defined in:
+#  - site_scons/talk.py
+# Use 'import talk' in any .scons file to get access to it.
+# Add any new helper functions to it; unittest are available
+# in talk_unittest.py.
+#
+# Each 'component' that is built is defined in a .scons file.
+# See talk.Components(...) for further info on file naming convention.
+#
+# To add a new platform clone and modify the root_env object. Remember to add
+# the new environment object to the envs list otherwise it will not be included
+# in the build.
+#
+#
+#
+
+import talk
+import os
+import platform
+
+#-------------------------------------------------------------------------------
+# The build files/directories to 'build'.
+# If the name is the name of a directory then that directory shall contain a
+# .scons file with the same name as the directory itself:
+#  Ex: The directory session/phone contains a file called phone.scons
+# This list must be in order of library dependencies. e.g., if
+# session/phone/phone.scons defines a target that links to a library target
+# defined in sound/sound.scons, then 'sound' must come first.
+# When no particular order is imposed by library dependencies, try to keep in
+# mostly alphabetical order.
+#
+components = talk.Components("libjingle.scons")
+
+#-------------------------------------------------------------------------------
+# Build environments
+#
+
+# The list of build environments.
+envs = []
+
+# The root of all builds.
+root_env = Environment(
+  tools = [
+    'component_bits',
+    'component_setup',
+    'replace_strings',
+    'talk_noops',
+    #'talk_utils',
+  ],
+  BUILD_SCONSCRIPTS = components,
+  DESTINATION_ROOT = '$MAIN_DIR/build',
+  CPPPATH = [
+    '$OBJ_ROOT',     # generated headers are relative to here
+    '$MAIN_DIR/..',  # TODO(dape): how can we use GOOGLECLIENT instead?
+  ],
+  CPPDEFINES = [
+    'LOGGING=1',
+
+    # Feature selection
+    'FEATURE_ENABLE_SSL',
+    'FEATURE_ENABLE_VOICEMAIL',
+    'FEATURE_ENABLE_PSTN',
+    'HAVE_SRTP',
+  ],
+  # Ensure the os environment is captured for any scripts we call out to
+  ENV = os.environ,
+)
+
+# This is where we set common environments
+#
+# Detect if building on 64-bit or 32-bit platform.
+DeclareBit('build_platform_64bit', 'Platform of the build machine is 64-bit')
+if platform.architecture()[0] == "64bit":
+  root_env.SetBits('build_platform_64bit')
+
+# 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')
+
+# This bit denotes that we are cross-compiling using a sysroot.
+DeclareBit('cross_compile',
+           'Cross compiling using the SYSROOT environment variable')
+
+def CrossArch(env):
+  """Return whether or not the host platform architecture differs from the build
+     environment architecture."""
+  if env.Bit('cross_compile'):
+    # The architecture of the Python process may not match the architecture of
+    # the sysroot, so we just assume it's not a cross-arch build or that it
+    # doesn't matter. Currently it only matters if you try to build a cross-arch
+    # Debian package, so just don't do that.
+    return False
+  else:
+    return env.Bit('host_platform_64bit') != env.Bit('build_platform_64bit')
+root_env.AddMethod(CrossArch)
+
+DeclareBit('use_static_openssl', 'Build OpenSSL as a static library')
+
+DeclareBit('have_dbus_glib',
+           'Whether the build system has the dbus-glib-1 package')
+DeclareBit('have_libpulse',
+           'Whether the build system has the libpulse package')
+
+
+# List all the locales we localize to.
+root_env.AppendUnique(locales = [
+    'af', 'am', 'ar', 'bg', 'bn', 'ca', 'cs', 'da', 'de', 'el', 'en', 'en-GB',
+    'es', 'es-419', 'et', 'eu', 'fa', 'fi', 'fil', 'fr', 'fr-CA', 'gl', 'gu',
+    'hi', 'hr', 'hu', 'id', 'is', 'it', 'iw', 'ja', 'kn', 'ko', 'lt', 'lv',
+    'ml', 'mr', 'ms', 'nl', 'no', 'or', 'pl', 'pt-BR', 'pt-PT', 'ro', 'ru',
+    'sk', 'sl', 'sr', 'sv', 'sw', 'ta', 'te', 'th', 'tl', 'tr', 'uk', 'ur',
+    'vi', 'zh-CN', 'zh-HK', 'zh-TW', 'zu'])
+
+AddTargetGroup('all_breakpads', 'breakpad files can be built')
+
+AddTargetGroup('all_dsym', 'dsym debug packages can be built')
+
+#-------------------------------------------------------------------------------
+# W I N D O W S
+#
+win_env = root_env.Clone(
+  tools = [
+    'atlmfc_vc80',
+    #'code_signing',
+    'component_targets_msvs',
+    'directx_9_0_c',
+    #'grid_builder',
+    'midl',
+    'target_platform_windows'
+  ],
+  # Don't use default vc80 midl.exe.  It doesn't understand vista_sdk idl files.
+  MIDL = '$PLATFORM_SDK_VISTA_6_0_DIR/Bin/midl.exe ',
+  WIX_DIR = '$GOOGLECLIENT/third_party/wix/v3_0_2925/files',
+  # Flags for debug and optimization are added to CCFLAGS instead
+  CCPDBFLAGS = '',
+  CCFLAGS_DEBUG = '',
+  CCFLAGS_OPTIMIZED = '',
+  # We force a x86 target even when building on x64 Windows platforms.
+  TARGET_ARCH = 'x86',
+)
+
+
+win_env.Decider('MD5-timestamp')
+win_env.Append(
+  COMPONENT_LIBRARY_PUBLISH = True,  # Put dlls in output dir too
+  CCFLAGS = [
+    '/Fd${TARGET}.pdb', # pdb per object allows --jobs=
+    '/WX',          # warnings are errors
+    '/Zc:forScope', # handle 'for (int i = 0 ...)' right
+    '/EHs-c-',      # disable C++ EH
+    '/GR-',         # disable RTTI
+    '/Gy',          # enable function level linking
+    '/wd4996',      # ignore POSIX deprecated warnings
+
+    # promote certain level 4 warnings
+    '/w14701',     # potentially uninitialized var
+    '/w14702',     # unreachable code
+    '/w14706',     # assignment within a conditional
+    '/w14709',     # comma operator within array index
+    '/w14063',     # case 'identifier' is not a valid value for switch of enum
+    '/w14064',     # switch of incomplete enum 'enumeration'
+    '/w14057',     # 'identifier1' indirection to slightly different base
+                   #   types from 'identifier2'
+    '/w14263',     # member function does not override any base class virtual
+                   #   member function
+    '/w14266',     # no override available for virtual memberfunction from base
+                   #  'type'; function is hidden
+    '/w14296',     # expression is always false
+    '/w14355',     # 'this' : used in base member initializer list
+  ],
+  CPPDEFINES = [
+    '_ATL_CSTRING_EXPLICIT_CONSTRUCTORS',
+    # TODO(dape): encapsulate all string operations that are not based
+    # on std::string/std::wstring and make sure we use the safest versions
+    # available on all platforms.
+    '_CRT_SECURE_NO_WARNINGS',
+    '_USE_32BIT_TIME_T',
+    '_UNICODE',
+    'UNICODE',
+    '_HAS_EXCEPTIONS=0',
+    'WIN32',
+    # TODO(dape): remove this from logging.cc and enable here instead.
+    #'WIN32_LEAN_AND_MEAN',
+
+    'WINVER=0x0500',
+    '_WIN32_WINNT=0x0501',
+    '_WIN32_IE=0x0501',
+    # The Vista platform SDK 6.0 needs at least
+    # this NTDDI version or else the headers
+    # that LMI includes from it won't compile.
+    'NTDDI_VERSION=NTDDI_WINXP',
+
+    # npapi.h requires the following:
+    '_WINDOWS',
+  ],
+  CPPPATH = [
+    '$THIRD_PARTY/wtl_71/include',
+    '$PLATFORM_SDK_VISTA_6_0_DIR/Include',
+  ],
+  LIBPATH = [
+    '$PLATFORM_SDK_VISTA_6_0_DIR/Lib'
+  ],
+  LINKFLAGS = [
+    '-manifest', # TODO(thaloun): Why do we need this?
+    # Some of the third-party libraries we link in don't have public symbols, so
+    # ignore that linker warning.
+    '/ignore:4221',
+    '/nxcompat',    # Binary was tested to be be compatible with Windows DEP.
+    '/dynamicbase', # Use ASLR to dynamically rebase at load-time.
+    '/fixed:no',    # Binary can be loaded at any base-address.
+  ],
+  MIDLFLAGS = [
+    '/win32',
+    '/I$PLATFORM_SDK_VISTA_6_0_DIR/include'
+  ]
+)
+
+# TODO(dape): Figure out what this does; found it in
+# omaha/main.scons. This fixes the problem with redefinition
+# of OS_WINDOWS symbol.
+win_env.FilterOut(CPPDEFINES = ['OS_WINDOWS=OS_WINDOWS'])
+
+# Set up digital signing
+DeclareBit('test_signing', 'Sign binaries with the test certificate')
+win_env.SetBitFromOption('test_signing', False)
+if win_env.Bit('test_signing'):
+   win_env.Replace(
+     CERTIFICATE_PATH = win_env.File(
+         '$GOOGLECLIENT/tools/test_key/testkey.pfx').abspath,
+     CERTIFICATE_PASSWORD = 'test',
+   )
+AddTargetGroup('signed_binaries', 'digitally signed binaries can be built')
+
+win_dbg_env = win_env.Clone(
+  BUILD_TYPE = 'dbg',
+  BUILD_TYPE_DESCRIPTION = 'Windows debug build',
+  BUILD_GROUPS = ['default', 'all'],
+  tools = ['target_debug'],
+)
+
+win_dbg_env.Prepend(
+  CCFLAGS = [
+    '/ZI',     # enable debugging
+    '/Od',     # disable optimizations
+    '/MTd',    # link with LIBCMTD.LIB debug lib
+    '/RTC1',   # enable runtime checks
+  ],
+)
+
+envs.append(win_dbg_env)
+
+win_dbg64_env = win_dbg_env.Clone(
+  BUILD_TYPE = 'dbg64',
+  BUILD_TYPE_DESCRIPTION = 'Windows debug 64bit build',
+  BUILD_GROUPS = ['all'],
+)
+
+win_dbg64_env.FilterOut(CCFLAGS = ['/ZI'])
+
+win_dbg64_env.Append(
+  CCFLAGS = [
+    '/Zi',     # enable debugging that is 64 bit compatible.
+    # TODO(fbarchard): fix warnings and remove these disables.
+    '/wd4244', # disable cast warning
+    '/wd4267', # disable cast warning
+  ],
+  ARFLAGS = [
+    '/MACHINE:x64',
+  ],
+  CPPDEFINES = [
+    'WIN64',
+    'ARCH_CPU_64_BITS',
+  ],
+  LIBFLAGS = [
+    '/MACHINE:x64',
+  ],
+  LINKFLAGS = [
+    '/MACHINE:x64',
+  ],
+)
+
+win_dbg64_env.FilterOut(CPPDEFINES = ['_USE_32BIT_TIME_T'])
+
+win_dbg64_env.Prepend(
+  LIBPATH = [
+      '$VC80_DIR/vc/lib/amd64',
+      '$ATLMFC_VC80_DIR/lib/amd64',
+      '$PLATFORM_SDK_VISTA_6_0_DIR/Lib/x64',
+  ],
+)
+win_dbg64_env.PrependENVPath(
+  'PATH',
+  win_dbg64_env.Dir('$VC80_DIR/vc/bin/x86_amd64'))
+
+win_dbg64_env.SetBits('host_platform_64bit')
+
+envs.append(win_dbg64_env)
+
+win_coverage_env = win_dbg_env.Clone(
+  tools = ['code_coverage'],
+  BUILD_TYPE = 'coverage',
+  BUILD_TYPE_DESCRIPTION = 'Windows code coverage build',
+  BUILD_GROUPS = ['all'],
+)
+
+win_coverage_env.Append(
+  CPPDEFINES = [
+    'COVERAGE_ENABLED',
+  ],
+)
+
+envs.append(win_coverage_env)
+
+win_opt_env = win_env.Clone(
+  BUILD_TYPE = 'opt',
+  BUILD_TYPE_DESCRIPTION = 'Windows opt build',
+  BUILD_GROUPS = ['all'],
+  tools = ['target_optimized'],
+)
+
+win_opt_env.Prepend(
+  CCFLAGS=[
+      '/Zi',       # enable debugging
+      '/O1',       # optimize for size
+      '/fp:fast',  # float faster but less precise
+      '/MT',       # link with LIBCMT.LIB (multi-threaded, static linked crt)
+      '/GS',       # enable security checks
+  ],
+  LINKFLAGS = [
+    '/safeseh',     # protect against attacks against exception handlers
+    '/opt:ref',     # Remove unused references (functions/data).
+  ],
+)
+
+envs.append(win_opt_env)
+
+#-------------------------------------------------------------------------------
+# P O S I X
+#
+posix_env = root_env.Clone()
+posix_env.Append(
+  CPPDEFINES = [
+    'HASHNAMESPACE=__gnu_cxx',
+    'HASH_NAMESPACE=__gnu_cxx',
+    'POSIX',
+    'DISABLE_DYNAMIC_CAST',
+    # The POSIX standard says we have to define this.
+    '_REENTRANT',
+  ],
+  CCFLAGS = [
+    '-Wall',
+    '-Werror',
+    '-Wno-switch',
+    '-fno-exceptions',
+    # 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',
+    '-Wno-ctor-dtor-privacy',
+    '-fno-rtti',
+  ],
+)
+
+# Switch-hit between NSS and OpenSSL
+if 'NSS_BUILD_PLATFORM' in root_env['ENV']:
+   posix_env.AppendUnique(CPPDEFINES=['HAVE_NSS_SSL_H=1',
+                                      'NSS_SSL_RELATIVE_PATH'])
+else:
+   posix_env.AppendUnique(CPPDEFINES=['HAVE_OPENSSL_SSL_H=1'])
+
+
+#-------------------------------------------------------------------------------
+# M A C OSX
+#
+mac_env = posix_env.Clone(
+  tools = [
+    'target_platform_mac',
+    #'talk_mac',
+    #'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')
+
+# For libjingle we don't specify a sysroot or minimum OS version.
+mac_osx_version_min_32 = ""
+mac_osx_version_min_64 = ""
+
+# Generic mac environment common to all targets
+mac_env.Append(
+  CPPDEFINES = [
+    'OSX',
+  ],
+  CCFLAGS = [
+    '-arch', 'i386',
+    '-fasm-blocks',
+  ],
+  LINKFLAGS = [
+    '-Wl,-search_paths_first',
+    # This flag makes all members of a static library be included in the
+    # final exe - that increases the size of the exe, but without it
+    # Obj-C categories aren't properly included in the exe.
+    # TODO(thaloun): consider only defining for libs that actually have objc.
+    '-ObjC',
+    '-arch', 'i386',
+    '-dead_strip',
+  ],
+  FRAMEWORKS = [
+    'CoreServices',
+    'Security',
+    'SystemConfiguration',
+    'OpenGL',
+    'CoreAudio',
+    'Quartz',
+    'Cocoa',
+    'QTKit',
+  ]
+)
+
+if 'NSS_BUILD_PLATFORM' in root_env['ENV']:
+  mac_env.AppendUnique(LINKFLAGS = ['-Lthird_party/mozilla/dist/' + root_env['ENV']['NSS_BUILD_PLATFORM'] + '/lib'])
+else:
+  mac_env.AppendUnique(LINKFLAGS = ['-Lthird_party/openssl'])
+
+
+# add debug flags to environment
+def mac_debug_include(env):
+  env.Append(
+    CCFLAGS = [
+      '-O0',
+    ],
+    CPPDEFINES = [
+      'DEBUG=1',
+    ],
+  )
+  # Remove -g set by hammer, which is not what we want (we have set -g3 above).
+  env.FilterOut(CCFLAGS = ['-g'])
+
+# add 32/64 bit specific options to specified environment
+def mac_common_include_x86_32(env):
+  env.Append(
+    CCFLAGS = [
+      '-m32',
+    ],
+    LINKFLAGS = [
+      '-m32',
+    ],
+    FRAMEWORKS = [
+      'Carbon',
+      'QuickTime',
+    ],
+  )
+  envs.append(env)
+
+def mac_common_include_x86_64(env):
+  env.Append(
+    CCFLAGS = [
+      '-m64',
+      '-fPIC',
+    ],
+    CPPDEFINES = [
+      'ARCH_CPU_64_BITS',
+      'CARBON_DEPRECATED',
+    ],
+    LINKFLAGS = [
+      '-m64',
+    ],
+    FRAMEWORKS = [
+      'AppKit',
+    ],
+  )
+  env.SetBits('host_platform_64bit')
+  envs.append(env)
+
+def mac_osx_version_min(env, ver):
+  if ver != "":
+    sdk_path = '/Developer/SDKs/MacOSX%s.sdk' % ver
+    env.Append(
+      CCFLAGS = [
+        '-mmacosx-version-min=' + ver,
+        '-isysroot', sdk_path,
+      ],
+      LINKFLAGS = [
+        '-mmacosx-version-min=' + ver,
+        '-isysroot', sdk_path,
+      ],
+      osx_sdk_path = sdk_path,
+      osx_version_min = ver,
+    )
+
+# Create all environments
+mac_dbg_env = mac_env.Clone(
+  BUILD_TYPE = 'dbg',
+  BUILD_TYPE_DESCRIPTION = 'Mac debug build',
+  BUILD_GROUPS = ['default', 'all'],
+  tools = ['target_debug'],
+)
+
+mac_opt_env = mac_env.Clone(
+  BUILD_TYPE = 'opt',
+  BUILD_TYPE_DESCRIPTION = 'Mac opt build',
+  BUILD_GROUPS = ['all'],
+  tools = ['target_optimized'],
+)
+
+mac_dbg64_env = mac_dbg_env.Clone(
+  BUILD_TYPE = 'dbg64',
+  BUILD_TYPE_DESCRIPTION = 'Mac debug 64bit build',
+  BUILD_GROUPS = ['all'],
+)
+
+mac_opt64_env = mac_opt_env.Clone(
+  BUILD_TYPE = 'opt64',
+  BUILD_TYPE_DESCRIPTION = 'Mac opt 64bit build',
+  BUILD_GROUPS = ['all'],
+)
+
+mac_debug_include(mac_dbg_env)
+mac_debug_include(mac_dbg64_env)
+mac_common_include_x86_32(mac_dbg_env)
+mac_common_include_x86_32(mac_opt_env)
+mac_common_include_x86_64(mac_dbg64_env)
+mac_common_include_x86_64(mac_opt64_env)
+mac_osx_version_min(mac_dbg_env, mac_osx_version_min_32)
+mac_osx_version_min(mac_opt_env, mac_osx_version_min_32)
+mac_osx_version_min(mac_dbg64_env, mac_osx_version_min_64)
+mac_osx_version_min(mac_opt64_env, mac_osx_version_min_64)
+
+
+#-------------------------------------------------------------------------------
+# L I N U X
+#
+linux_common_env = posix_env.Clone(
+  tools = [
+    'target_platform_linux',
+    'talk_linux',
+  ],
+)
+
+linux_common_env.Append(
+  CPPDEFINES = [
+    'LINUX',
+  ],
+  CCFLAGS = [
+    # Needed for link-time dead-code removal to work properly.
+    '-ffunction-sections',
+    '-fdata-sections',
+  ],
+  LINKFLAGS = [
+    # Enable dead-code removal.
+    '-Wl,--gc-sections',
+    # Elide dependencies on shared libraries that we're not actually using.
+    '-Wl,--as-needed',
+    '-Wl,--start-group',
+  ],
+  _LIBFLAGS = ['-Wl,--end-group'],
+)
+
+# Remove default rpath set by Hammer. Hammer sets it to LIB_DIR, which is wrong.
+# The rpath is the _run-time_ library search path for the resulting binary, i.e.
+# the one used by ld.so at load time. Setting it equal to the path to build
+# output on the build machine is nonsense.
+linux_common_env.Replace(
+  RPATH = [],
+)
+
+# Enable the optional DBus-GLib code if the build machine has the required
+# dependency.
+linux_common_env.EnableFeatureWherePackagePresent('have_dbus_glib',
+                                                  'HAVE_DBUS_GLIB',
+                                                  'dbus-glib-1')
+
+def linux_common_include_x86_32(env):
+  """Include x86-32 settings into an env based on linux_common."""
+  env.Append(
+    CCFLAGS = [
+      '-m32',
+    ],
+    LINKFLAGS = [
+      '-m32',
+    ],
+  )
+
+def linux_common_include_x86_64(env):
+  """Include x86-64 settings into an env based on linux_common."""
+  env.Append(
+    CCFLAGS = [
+      '-m64',
+      '-fPIC',
+    ],
+    LINKFLAGS = [
+      '-m64',
+    ],
+  )
+  env.SetBits('host_platform_64bit')
+
+#-------------------------------------------------------------------------------
+# 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")),
+)
+linux_cross_common_env.SetBits('cross_compile')
+
+# 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(' '),
+  )
+if os.environ.get("LINKFLAGS"):
+  linux_cross_common_env.Prepend(
+    LINKFLAGS = os.environ.get("LINKFLAGS").split(' '),
+  )
+
+#-------------------------------------------------------------------------------
+# L I N U X -- T R A D I T I O N A L -- X 8 6
+#
+# Settings that are specific to our desktop Linux x86 targets.
+def linux_common_include_traditional(env):
+  """Include traditional Linux settings into an env based on linux_common."""
+  # OpenSSL has infamously poor ABI stability, so that building against one
+  # version and running against a different one often will not work. Since our
+  # non-ChromeOS Linux builds are used on many different distros and distro
+  # versions, this means we can't safely dynamically link to OpenSSL because the
+  # product would end up being broken on any computer with a different version
+  # installed. So instead we build it ourself and statically link to it.
+  env.SetBits('use_static_openssl')
+  # Enable the optional PulseAudio code if the build machine has the required
+  # dependency.
+  # TODO(?): This belongs in linux_common_env, but we can't safely move it there
+  # yet because pkg-config is not being used properly with ChromeOS builds (see
+  # TODO below).
+  env.EnableFeatureWherePackagePresent('have_libpulse',
+                                       'HAVE_LIBPULSE',
+                                       'libpulse')
+
+def linux_traditional_include_dbg(env):
+  """Include traditional Linux dbg settings into an env based on the above."""
+  # Remove -g set by hammer, which is not what we want (we have set -g3 above).
+  env.FilterOut(CCFLAGS = ['-g'])
+
+def linux_traditional_include_opt(env):
+  """Include traditional Linux opt settings into an env based on the above."""
+  # Remove -O2 set by hammer, which is not what we want.
+  env.FilterOut(CCFLAGS = ['-O2'])
+  env.Append(CCFLAGS = ['-Os'])
+
+def gen_linux_nonhermetic(linux_env, type_suffix, desc_suffix):
+  groups = ['nonhermetic']
+  if not linux_env.CrossArch():
+    groups = groups + ['nonhermetic-native']
+    # The non-hermetic, native-arch dbg build is the default.
+    dbg_groups = groups + ['default']
+    native_desc = ', native '
+    # No suffix for native modes.
+    type_suffix = ''
+  else:
+    groups = groups + ['nonhermetic-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%s%s' % (native_desc,
+        desc_suffix),
+    BUILD_GROUPS = dbg_groups,
+    tools = ['target_debug'],
+  )
+  linux_traditional_include_dbg(linux_dbg_env)
+  envs.append(linux_dbg_env)
+
+  linux_opt_env = linux_env.Clone(
+    BUILD_TYPE = 'opt' + type_suffix,
+    BUILD_TYPE_DESCRIPTION = 'Linux optimized build%s%s' % (native_desc,
+        desc_suffix),
+    BUILD_GROUPS = groups,
+    tools = ['target_optimized'],
+  )
+  linux_traditional_include_opt(linux_opt_env)
+  envs.append(linux_opt_env)
+
+linux_nonhermetic_common_env = linux_common_env.Clone()
+linux_common_include_traditional(linux_nonhermetic_common_env)
+
+linux_nonhermetic_x86_32_env = linux_nonhermetic_common_env.Clone()
+linux_common_include_x86_32(linux_nonhermetic_x86_32_env)
+gen_linux_nonhermetic(linux_nonhermetic_x86_32_env, '32', '32-bit')
+
+linux_nonhermetic_x86_64_env = linux_nonhermetic_common_env.Clone()
+linux_common_include_x86_64(linux_nonhermetic_x86_64_env)
+gen_linux_nonhermetic(linux_nonhermetic_x86_64_env, '64', '64-bit')
+
+def gen_linux_hermetic(linux_env, type_suffix, desc):
+  groups = ['hermetic']
+
+  linux_dbg_env = linux_env.Clone(
+    BUILD_TYPE = 'hermetic-dbg' + type_suffix,
+    BUILD_TYPE_DESCRIPTION = 'Hermetic %s Linux debug build' % desc,
+    BUILD_GROUPS = groups,
+    tools = ['target_debug'],
+  )
+  linux_traditional_include_dbg(linux_dbg_env)
+  envs.append(linux_dbg_env)
+
+  linux_opt_env = linux_env.Clone(
+    BUILD_TYPE = 'hermetic-opt' + type_suffix,
+    BUILD_TYPE_DESCRIPTION = 'Hermetic %s Linux optimized build' % desc,
+    BUILD_GROUPS = groups,
+    tools = ['target_optimized'],
+  )
+  linux_traditional_include_opt(linux_opt_env)
+  envs.append(linux_opt_env)
+
+linux_hermetic_common_env = linux_cross_common_env.Clone()
+linux_common_include_traditional(linux_hermetic_common_env)
+
+linux_hermetic_x86_32_env = linux_hermetic_common_env.Clone()
+linux_common_include_x86_32(linux_hermetic_x86_32_env)
+gen_linux_hermetic(linux_hermetic_x86_32_env, '32', '32-bit')
+
+linux_hermetic_x86_64_env = linux_hermetic_common_env.Clone()
+linux_common_include_x86_64(linux_hermetic_x86_64_env)
+gen_linux_hermetic(linux_hermetic_x86_64_env, '64', '64-bit')
+
+#-------------------------------------------------------------------------------
+# L I N U X -- C R O S S -- B U I L D -- A R M
+
+# TODO(noahric): All the following Linux builds are running against a sysroot
+# but improperly using the host machine's pkg-config environment. The ChromeOS
+# ones should probably be using
+# https://cs.corp.google.com/#chrome/src/build/linux/pkg-config-wrapper.
+
+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')
+
+# Detect hardfp from the -mfloat-abi build flag
+DeclareBit('arm_hardfp', 'ARM supporting hardfp')
+if '-mfloat-abi=hard' in linux_cross_arm_env['CFLAGS'] or \
+   '-mfloat-abi=hard' in linux_cross_arm_env['CCFLAGS'] or \
+   '-mfloat-abi=hard' in linux_cross_arm_env['CXXFLAGS']:
+  print "Building with hard floating point support."
+  linux_cross_arm_env.SetBits('arm_hardfp')
+
+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)
+
+
+
+# Create a group for installers
+AddTargetGroup('all_installers', 'installers that can be built')
+
+# Parse child .scons files
+BuildEnvironments(envs)
+
+# Explicitly set which targets to build when not stated on commandline
+Default(None)
+# Build the following, which excludes unit test output (ie running them)
+# To run unittests, specify the test to run, or run_all_tests.  See -h option.
+Default(['all_libraries', 'all_programs', 'all_test_programs'])
+
+# .sln creation code lifted from googleclient/bar/main.scons.  Must be after
+# the call to BuildEnvironments for all_foo aliases to be defined.
+# Run 'hammer --mode=all --vsproj' to generate
+DeclareBit('vsproj', 'Generate Visual Studio projects and solution files.')
+win_env.SetBitFromOption('vsproj', False)
+
+if win_env.Bit('vsproj'):
+  vs_env = win_env.Clone()
+  vs_env.Append(
+    COMPONENT_VS_SOURCE_SUFFIXES = [
+      '.def',
+      '.grd',
+      '.html',
+      '.idl',
+      '.mk',
+      '.txt',
+      '.py',
+      '.scons',
+      '.wxs.template',
+    ]
+  )
+
+  # Source project
+  p = vs_env.ComponentVSDirProject(
+    'flute_source',
+    ['$MAIN_DIR',
+    ],
+    COMPONENT_VS_SOURCE_FOLDERS = [
+      # Files are assigned to first matching folder. Folder names of None
+      # are filters.
+      (None, '$DESTINATION_ROOT'),
+      ('flute', '$MAIN_DIR'),
+      ('google3', '$GOOGLE3'),
+      ('third_party', '$THIRD_PARTY'),
+    ],
+    # Force source project to main dir, so that Visual Studio can find the
+    # source files corresponding to build errors.
+    COMPONENT_VS_PROJECT_DIR = '$MAIN_DIR',
+  )
+  vs_env.AlwaysBuild(p)
+
+  # Solution and target projects
+  s = vs_env.ComponentVSSolution(
+    # 'libjingle',  # Please uncomment this line if you build VS proj files.
+    ['all_libraries', 'all_programs', 'all_test_programs'],
+    projects = [p],
+  )
+
+  print '***Unfortunately the vsproj creator isn\'t smart enough to '
+  print '***automatically get the correct output locations.  It is very easy'
+  print '***though to change it in the properties pane to the following'
+  print '***$(SolutionDir)/build/<foo>/staging/<bar>.exe'
+  Default(None)
+  Default([s])
diff --git a/talk/media/base/audioframe.h b/talk/media/base/audioframe.h
new file mode 100755
index 0000000..b0c8b04
--- /dev/null
+++ b/talk/media/base/audioframe.h
@@ -0,0 +1,63 @@
+/*
+ * 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_MEDIA_BASE_AUDIOFRAME_H_
+#define TALK_MEDIA_BASE_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:
+  // TODO(janahan): currently the data is not owned by this class.
+  // add ownership when we come up with the first use case that requires it.
+  int16* audio10ms_;
+  size_t length_;
+  int sampling_frequency_;
+  bool stereo_;
+};
+
+}  // namespace cricket
+#endif  // TALK_MEDIA_BASE_AUDIOFRAME_H_
diff --git a/talk/media/base/audiorenderer.h b/talk/media/base/audiorenderer.h
new file mode 100644
index 0000000..23b17da
--- /dev/null
+++ b/talk/media/base/audiorenderer.h
@@ -0,0 +1,45 @@
+/*
+ * libjingle
+ * Copyright 2013 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_MEDIA_BASE_AUDIORENDERER_H_
+#define TALK_MEDIA_BASE_AUDIORENDERER_H_
+
+namespace cricket {
+
+// Abstract interface for holding the voice channel ID.
+class AudioRenderer {
+ public:
+  virtual void SetChannelId(int channel_id) = 0;
+  virtual int GetChannelId() const = 0;
+
+ protected:
+  virtual ~AudioRenderer() {}
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_AUDIORENDERER_H_
diff --git a/talk/media/base/capturemanager.cc b/talk/media/base/capturemanager.cc
new file mode 100644
index 0000000..5705bcd
--- /dev/null
+++ b/talk/media/base/capturemanager.cc
@@ -0,0 +1,389 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/media/base/capturemanager.h"
+
+#include <algorithm>
+
+#include "talk/base/logging.h"
+#include "talk/media/base/videocapturer.h"
+#include "talk/media/base/videoprocessor.h"
+#include "talk/media/base/videorenderer.h"
+
+namespace cricket {
+
+// CaptureManager helper class.
+class VideoCapturerState {
+ public:
+  static const VideoFormatPod kDefaultCaptureFormat;
+
+  static VideoCapturerState* Create(VideoCapturer* video_capturer);
+  ~VideoCapturerState() {}
+
+  void AddCaptureResolution(const VideoFormat& desired_format);
+  bool RemoveCaptureResolution(const VideoFormat& format);
+  VideoFormat GetHighestFormat(VideoCapturer* video_capturer) const;
+
+  int IncCaptureStartRef();
+  int DecCaptureStartRef();
+  CaptureRenderAdapter* adapter() { return adapter_.get(); }
+  VideoCapturer* GetVideoCapturer() { return adapter()->video_capturer(); }
+
+  int start_count() const { return start_count_; }
+
+ private:
+  struct CaptureResolutionInfo {
+    VideoFormat video_format;
+    int format_ref_count;
+  };
+  typedef std::vector<CaptureResolutionInfo> CaptureFormats;
+
+  explicit VideoCapturerState(CaptureRenderAdapter* adapter);
+
+  talk_base::scoped_ptr<CaptureRenderAdapter> adapter_;
+
+  int start_count_;
+  CaptureFormats capture_formats_;
+};
+
+const VideoFormatPod VideoCapturerState::kDefaultCaptureFormat = {
+  640, 360, FPS_TO_INTERVAL(30), FOURCC_ANY
+};
+
+VideoCapturerState::VideoCapturerState(CaptureRenderAdapter* adapter)
+    : adapter_(adapter), start_count_(1) {}
+
+VideoCapturerState* VideoCapturerState::Create(VideoCapturer* video_capturer) {
+  CaptureRenderAdapter* adapter = CaptureRenderAdapter::Create(video_capturer);
+  if (!adapter) {
+    return NULL;
+  }
+  return new VideoCapturerState(adapter);
+}
+
+void VideoCapturerState::AddCaptureResolution(
+    const VideoFormat& desired_format) {
+  for (CaptureFormats::iterator iter = capture_formats_.begin();
+       iter != capture_formats_.end(); ++iter) {
+    if (desired_format == iter->video_format) {
+      ++(iter->format_ref_count);
+      return;
+    }
+  }
+  CaptureResolutionInfo capture_resolution = { desired_format, 1 };
+  capture_formats_.push_back(capture_resolution);
+}
+
+bool VideoCapturerState::RemoveCaptureResolution(const VideoFormat& format) {
+  for (CaptureFormats::iterator iter = capture_formats_.begin();
+       iter != capture_formats_.end(); ++iter) {
+    if (format == iter->video_format) {
+      --(iter->format_ref_count);
+      if (iter->format_ref_count == 0) {
+        capture_formats_.erase(iter);
+      }
+      return true;
+    }
+  }
+  return false;
+}
+
+VideoFormat VideoCapturerState::GetHighestFormat(
+    VideoCapturer* video_capturer) const {
+  VideoFormat highest_format(0, 0, VideoFormat::FpsToInterval(1), FOURCC_ANY);
+  if (capture_formats_.empty()) {
+    VideoFormat default_format(kDefaultCaptureFormat);
+    return default_format;
+  }
+  for (CaptureFormats::const_iterator iter = capture_formats_.begin();
+       iter != capture_formats_.end(); ++iter) {
+    if (iter->video_format.width > highest_format.width) {
+      highest_format.width = iter->video_format.width;
+    }
+    if (iter->video_format.height > highest_format.height) {
+      highest_format.height = iter->video_format.height;
+    }
+    if (iter->video_format.interval < highest_format.interval) {
+      highest_format.interval = iter->video_format.interval;
+    }
+  }
+  return highest_format;
+}
+
+int VideoCapturerState::IncCaptureStartRef() { return ++start_count_; }
+
+int VideoCapturerState::DecCaptureStartRef() {
+  if (start_count_ > 0) {
+    // Start count may be 0 if a capturer was added but never started.
+    --start_count_;
+  }
+  return start_count_;
+}
+
+CaptureManager::~CaptureManager() {
+  while (!capture_states_.empty()) {
+    // There may have been multiple calls to StartVideoCapture which means that
+    // an equal number of calls to StopVideoCapture must be made. Note that
+    // StopVideoCapture will remove the element from |capture_states_| when a
+    // successfull stop has been made.
+    UnregisterVideoCapturer(capture_states_.begin()->second);
+  }
+}
+
+bool CaptureManager::StartVideoCapture(VideoCapturer* video_capturer,
+                                       const VideoFormat& desired_format) {
+  if (desired_format.width == 0 || desired_format.height == 0) {
+    return false;
+  }
+  if (!video_capturer) {
+    return false;
+  }
+  VideoCapturerState* capture_state = GetCaptureState(video_capturer);
+  if (capture_state) {
+    const int ref_count = capture_state->IncCaptureStartRef();
+    if (ref_count < 1) {
+      ASSERT(false);
+    }
+    // VideoCapturer has already been started. Don't start listening to
+    // callbacks since that has already been done.
+    capture_state->AddCaptureResolution(desired_format);
+    return true;
+  }
+  if (!RegisterVideoCapturer(video_capturer)) {
+    return false;
+  }
+  capture_state = GetCaptureState(video_capturer);
+  ASSERT(capture_state != NULL);
+  capture_state->AddCaptureResolution(desired_format);
+  if (!StartWithBestCaptureFormat(capture_state, video_capturer)) {
+    UnregisterVideoCapturer(capture_state);
+    return false;
+  }
+  return true;
+}
+
+bool CaptureManager::StopVideoCapture(VideoCapturer* video_capturer,
+                                      const VideoFormat& format) {
+  VideoCapturerState* capture_state = GetCaptureState(video_capturer);
+  if (!capture_state) {
+    return false;
+  }
+  if (!capture_state->RemoveCaptureResolution(format)) {
+    return false;
+  }
+
+  if (capture_state->DecCaptureStartRef() == 0) {
+    // Unregistering cannot fail as capture_state is not NULL.
+    UnregisterVideoCapturer(capture_state);
+  }
+  return true;
+}
+
+bool CaptureManager::RestartVideoCapture(
+    VideoCapturer* video_capturer,
+    const VideoFormat& previous_format,
+    const VideoFormat& desired_format,
+    CaptureManager::RestartOptions options) {
+  if (!IsCapturerRegistered(video_capturer)) {
+    LOG(LS_ERROR) << "RestartVideoCapture: video_capturer is not registered.";
+    return false;
+  }
+  // Start the new format first. This keeps the capturer running.
+  if (!StartVideoCapture(video_capturer, desired_format)) {
+    LOG(LS_ERROR) << "RestartVideoCapture: unable to start video capture with "
+        "desired_format=" << desired_format.ToString();
+    return false;
+  }
+  // Stop the old format.
+  if (!StopVideoCapture(video_capturer, previous_format)) {
+    LOG(LS_ERROR) << "RestartVideoCapture: unable to stop video capture with "
+        "previous_format=" << previous_format.ToString();
+    // Undo the start request we just performed.
+    StopVideoCapture(video_capturer, desired_format);
+    return false;
+  }
+
+  switch (options) {
+    case kForceRestart: {
+      VideoCapturerState* capture_state = GetCaptureState(video_capturer);
+      ASSERT(capture_state && capture_state->start_count() > 0);
+      // Try a restart using the new best resolution.
+      VideoFormat highest_asked_format =
+          capture_state->GetHighestFormat(video_capturer);
+      VideoFormat capture_format;
+      if (video_capturer->GetBestCaptureFormat(highest_asked_format,
+                                               &capture_format)) {
+        if (!video_capturer->Restart(capture_format)) {
+          LOG(LS_ERROR) << "RestartVideoCapture: Restart failed.";
+        }
+      } else {
+        LOG(LS_WARNING)
+            << "RestartVideoCapture: Couldn't find a best capture format for "
+            << highest_asked_format.ToString();
+      }
+      break;
+    }
+    case kRequestRestart:
+      // TODO(ryanpetrie): Support restart requests. Should this
+      // to-be-implemented logic be used for {Start,Stop}VideoCapture as well?
+      break;
+    default:
+      LOG(LS_ERROR) << "Unknown/unimplemented RestartOption";
+      break;
+  }
+  return true;
+}
+
+bool CaptureManager::AddVideoRenderer(VideoCapturer* video_capturer,
+                                      VideoRenderer* video_renderer) {
+  if (!video_capturer || !video_renderer) {
+    return false;
+  }
+  CaptureRenderAdapter* adapter = GetAdapter(video_capturer);
+  if (!adapter) {
+    return false;
+  }
+  return adapter->AddRenderer(video_renderer);
+}
+
+bool CaptureManager::RemoveVideoRenderer(VideoCapturer* video_capturer,
+                                         VideoRenderer* video_renderer) {
+  if (!video_capturer || !video_renderer) {
+    return false;
+  }
+  CaptureRenderAdapter* adapter = GetAdapter(video_capturer);
+  if (!adapter) {
+    return false;
+  }
+  return adapter->RemoveRenderer(video_renderer);
+}
+
+bool CaptureManager::AddVideoProcessor(VideoCapturer* video_capturer,
+                                       VideoProcessor* video_processor) {
+  if (!video_capturer || !video_processor) {
+    return false;
+  }
+  if (!IsCapturerRegistered(video_capturer)) {
+    return false;
+  }
+  video_capturer->AddVideoProcessor(video_processor);
+  return true;
+}
+
+bool CaptureManager::RemoveVideoProcessor(VideoCapturer* video_capturer,
+                                          VideoProcessor* video_processor) {
+  if (!video_capturer || !video_processor) {
+    return false;
+  }
+  if (!IsCapturerRegistered(video_capturer)) {
+    return false;
+  }
+  return video_capturer->RemoveVideoProcessor(video_processor);
+}
+
+bool CaptureManager::IsCapturerRegistered(VideoCapturer* video_capturer) const {
+  return GetCaptureState(video_capturer) != NULL;
+}
+
+bool CaptureManager::RegisterVideoCapturer(VideoCapturer* video_capturer) {
+  VideoCapturerState* capture_state =
+      VideoCapturerState::Create(video_capturer);
+  if (!capture_state) {
+    return false;
+  }
+  capture_states_[video_capturer] = capture_state;
+  SignalCapturerStateChange.repeat(video_capturer->SignalStateChange);
+  return true;
+}
+
+void CaptureManager::UnregisterVideoCapturer(
+    VideoCapturerState* capture_state) {
+  VideoCapturer* video_capturer = capture_state->GetVideoCapturer();
+  capture_states_.erase(video_capturer);
+  delete capture_state;
+
+  // When unregistering a VideoCapturer, the CaptureManager needs to unregister
+  // from all state change callbacks from the VideoCapturer. E.g. to avoid
+  // problems with multiple callbacks if registering the same VideoCapturer
+  // multiple times. The VideoCapturer will update the capturer state. However,
+  // this is done through Post-calls which means it may happen at any time. If
+  // the CaptureManager no longer is listening to the VideoCapturer it will not
+  // receive those callbacks. Here it is made sure that the the callback is
+  // indeed sent by letting the ChannelManager do the signaling. The downside is
+  // that the callback may happen before the VideoCapturer is stopped. However,
+  // for the CaptureManager it doesn't matter as it will no longer receive any
+  // frames from the VideoCapturer.
+  SignalCapturerStateChange.stop(video_capturer->SignalStateChange);
+  video_capturer->Stop();
+  SignalCapturerStateChange(video_capturer, CS_STOPPED);
+}
+
+bool CaptureManager::StartWithBestCaptureFormat(
+    VideoCapturerState* capture_state, VideoCapturer* video_capturer) {
+  VideoFormat highest_asked_format =
+      capture_state->GetHighestFormat(video_capturer);
+  VideoFormat capture_format;
+  if (!video_capturer->GetBestCaptureFormat(highest_asked_format,
+                                            &capture_format)) {
+    LOG(LS_WARNING) << "Unsupported format:"
+                    << " width=" << highest_asked_format.width
+                    << " height=" << highest_asked_format.height
+                    << ". Supported formats are:";
+    const std::vector<VideoFormat>* formats =
+        video_capturer->GetSupportedFormats();
+    ASSERT(formats != NULL);
+    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 false;
+  }
+  return video_capturer->StartCapturing(capture_format);
+}
+
+VideoCapturerState* CaptureManager::GetCaptureState(
+    VideoCapturer* video_capturer) const {
+  CaptureStates::const_iterator iter = capture_states_.find(video_capturer);
+  if (iter == capture_states_.end()) {
+    return NULL;
+  }
+  return iter->second;
+}
+
+CaptureRenderAdapter* CaptureManager::GetAdapter(
+    VideoCapturer* video_capturer) const {
+  VideoCapturerState* capture_state = GetCaptureState(video_capturer);
+  if (!capture_state) {
+    return NULL;
+  }
+  return capture_state->adapter();
+}
+
+}  // namespace cricket
diff --git a/talk/media/base/capturemanager.h b/talk/media/base/capturemanager.h
new file mode 100644
index 0000000..9c44395
--- /dev/null
+++ b/talk/media/base/capturemanager.h
@@ -0,0 +1,114 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// The CaptureManager class manages VideoCapturers to make it possible to share
+// the same VideoCapturers across multiple instances. E.g. if two instances of
+// some class want to listen to same VideoCapturer they can't individually stop
+// and start capturing as doing so will affect the other instance.
+// The class employs reference counting on starting and stopping of capturing of
+// frames such that if anyone is still listening it will not be stopped. The
+// class also provides APIs for attaching VideoRenderers to a specific capturer
+// such that the VideoRenderers are fed frames directly from the capturer. In
+// addition, these frames can be altered before being sent to the capturers by
+// way of VideoProcessors.
+// CaptureManager is Thread-unsafe. This means that none of its APIs may be
+// called concurrently. Note that callbacks are called by the VideoCapturer's
+// thread which is normally a separate unmarshalled thread and thus normally
+// require lock protection.
+
+#ifndef TALK_MEDIA_BASE_CAPTUREMANAGER_H_
+#define TALK_MEDIA_BASE_CAPTUREMANAGER_H_
+
+#include <map>
+#include <vector>
+
+#include "talk/base/sigslotrepeater.h"
+#include "talk/media/base/capturerenderadapter.h"
+#include "talk/media/base/videocommon.h"
+
+namespace cricket {
+
+class VideoCapturer;
+class VideoProcessor;
+class VideoRenderer;
+class VideoCapturerState;
+
+class CaptureManager : public sigslot::has_slots<> {
+ public:
+  enum RestartOptions {
+    kRequestRestart,
+    kForceRestart
+  };
+
+  CaptureManager() {}
+  virtual ~CaptureManager();
+
+  bool StartVideoCapture(VideoCapturer* video_capturer,
+                         const VideoFormat& desired_format);
+  bool StopVideoCapture(VideoCapturer* video_capturer,
+                        const VideoFormat& format);
+
+  // Possibly restarts the capturer. If |options| is set to kRequestRestart,
+  // the CaptureManager chooses whether this request can be handled with the
+  // current state or if a restart is actually needed. If |options| is set to
+  // kForceRestart, the capturer is restarted.
+  bool RestartVideoCapture(VideoCapturer* video_capturer,
+                           const VideoFormat& previous_format,
+                           const VideoFormat& desired_format,
+                           RestartOptions options);
+
+  virtual bool AddVideoRenderer(VideoCapturer* video_capturer,
+                                VideoRenderer* video_renderer);
+  virtual bool RemoveVideoRenderer(VideoCapturer* video_capturer,
+                                   VideoRenderer* video_renderer);
+
+  virtual bool AddVideoProcessor(VideoCapturer* video_capturer,
+                                 VideoProcessor* video_processor);
+  virtual bool RemoveVideoProcessor(VideoCapturer* video_capturer,
+                                    VideoProcessor* video_processor);
+
+  sigslot::repeater2<VideoCapturer*, CaptureState> SignalCapturerStateChange;
+
+ private:
+  typedef std::map<VideoCapturer*, VideoCapturerState*> CaptureStates;
+
+  bool IsCapturerRegistered(VideoCapturer* video_capturer) const;
+  bool RegisterVideoCapturer(VideoCapturer* video_capturer);
+  void UnregisterVideoCapturer(VideoCapturerState* capture_state);
+
+  bool StartWithBestCaptureFormat(VideoCapturerState* capture_info,
+                                  VideoCapturer* video_capturer);
+
+  VideoCapturerState* GetCaptureState(VideoCapturer* video_capturer) const;
+  CaptureRenderAdapter* GetAdapter(VideoCapturer* video_capturer) const;
+
+  CaptureStates capture_states_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_CAPTUREMANAGER_H_
diff --git a/talk/media/base/capturemanager_unittest.cc b/talk/media/base/capturemanager_unittest.cc
new file mode 100644
index 0000000..e9d9cfb
--- /dev/null
+++ b/talk/media/base/capturemanager_unittest.cc
@@ -0,0 +1,251 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/media/base/capturemanager.h"
+
+#include "talk/base/gunit.h"
+#include "talk/base/sigslot.h"
+#include "talk/media/base/fakemediaprocessor.h"
+#include "talk/media/base/fakevideocapturer.h"
+#include "talk/media/base/fakevideorenderer.h"
+
+const int kMsCallbackWait = 50;
+
+const int kFps = 30;
+const cricket::VideoFormatPod kCameraFormats[] = {
+  {640, 480, cricket::VideoFormat::FpsToInterval(kFps), cricket::FOURCC_I420},
+  {320, 240, cricket::VideoFormat::FpsToInterval(kFps), cricket::FOURCC_I420}
+};
+
+class CaptureManagerTest : public ::testing::Test, public sigslot::has_slots<> {
+ public:
+  CaptureManagerTest()
+      : capture_manager_(),
+        callback_count_(0),
+        format_vga_(kCameraFormats[0]),
+        format_qvga_(kCameraFormats[1]) {
+  }
+  virtual void SetUp() {
+    PopulateSupportedFormats();
+    capture_state_ = cricket::CS_STOPPED;
+    capture_manager_.SignalCapturerStateChange.connect(
+        this,
+        &CaptureManagerTest::OnCapturerStateChange);
+  }
+  void PopulateSupportedFormats() {
+    std::vector<cricket::VideoFormat> formats;
+    for (int i = 0; i < ARRAY_SIZE(kCameraFormats); ++i) {
+      formats.push_back(cricket::VideoFormat(kCameraFormats[i]));
+    }
+    video_capturer_.ResetSupportedFormats(formats);
+  }
+  int NumFramesProcessed() { return media_processor_.video_frame_count(); }
+  int NumFramesRendered() { return video_renderer_.num_rendered_frames(); }
+  bool WasRenderedResolution(cricket::VideoFormat format) {
+    return format.width == video_renderer_.width() &&
+        format.height == video_renderer_.height();
+  }
+  cricket::CaptureState capture_state() { return capture_state_; }
+  int callback_count() { return callback_count_; }
+  void OnCapturerStateChange(cricket::VideoCapturer* capturer,
+                             cricket::CaptureState capture_state) {
+    capture_state_ = capture_state;
+    ++callback_count_;
+  }
+
+ protected:
+  cricket::FakeMediaProcessor media_processor_;
+  cricket::FakeVideoCapturer video_capturer_;
+  cricket::FakeVideoRenderer video_renderer_;
+
+  cricket::CaptureManager capture_manager_;
+
+  cricket::CaptureState capture_state_;
+  int callback_count_;
+  cricket::VideoFormat format_vga_;
+  cricket::VideoFormat format_qvga_;
+};
+
+// Incorrect use cases.
+TEST_F(CaptureManagerTest, InvalidCallOrder) {
+  // Capturer must be registered before any of these calls.
+  EXPECT_FALSE(capture_manager_.AddVideoRenderer(&video_capturer_,
+                                                 &video_renderer_));
+  EXPECT_FALSE(capture_manager_.AddVideoProcessor(&video_capturer_,
+                                                  &media_processor_));
+}
+
+TEST_F(CaptureManagerTest, InvalidAddingRemoving) {
+  EXPECT_FALSE(capture_manager_.StopVideoCapture(&video_capturer_,
+                                                 cricket::VideoFormat()));
+  EXPECT_TRUE(capture_manager_.StartVideoCapture(&video_capturer_,
+                                                 format_vga_));
+  EXPECT_EQ_WAIT(cricket::CS_RUNNING, capture_state(), kMsCallbackWait);
+  EXPECT_EQ(1, callback_count());
+  EXPECT_FALSE(capture_manager_.AddVideoRenderer(&video_capturer_, NULL));
+  EXPECT_FALSE(capture_manager_.RemoveVideoRenderer(&video_capturer_,
+                                                    &video_renderer_));
+  EXPECT_FALSE(capture_manager_.AddVideoProcessor(&video_capturer_,
+                                                  NULL));
+  EXPECT_FALSE(capture_manager_.RemoveVideoProcessor(&video_capturer_,
+                                                     &media_processor_));
+  EXPECT_TRUE(capture_manager_.StopVideoCapture(&video_capturer_, format_vga_));
+}
+
+// Valid use cases
+TEST_F(CaptureManagerTest, ProcessorTest) {
+  EXPECT_TRUE(capture_manager_.StartVideoCapture(&video_capturer_,
+                                                 format_vga_));
+  EXPECT_EQ_WAIT(cricket::CS_RUNNING, capture_state(), kMsCallbackWait);
+  EXPECT_EQ(1, callback_count());
+  EXPECT_TRUE(capture_manager_.AddVideoRenderer(&video_capturer_,
+                                                &video_renderer_));
+  EXPECT_TRUE(capture_manager_.AddVideoProcessor(&video_capturer_,
+                                                 &media_processor_));
+  EXPECT_TRUE(video_capturer_.CaptureFrame());
+  EXPECT_EQ(1, NumFramesProcessed());
+  EXPECT_EQ(1, NumFramesRendered());
+  EXPECT_TRUE(capture_manager_.RemoveVideoProcessor(&video_capturer_,
+                                                    &media_processor_));
+  // Processor has been removed so no more frames should be processed.
+  EXPECT_TRUE(video_capturer_.CaptureFrame());
+  EXPECT_EQ(1, NumFramesProcessed());
+  EXPECT_EQ(2, NumFramesRendered());
+  EXPECT_TRUE(capture_manager_.StopVideoCapture(&video_capturer_, format_vga_));
+  EXPECT_EQ(2, callback_count());
+}
+
+TEST_F(CaptureManagerTest, KeepFirstResolutionHigh) {
+  EXPECT_TRUE(capture_manager_.StartVideoCapture(&video_capturer_,
+                                                 format_vga_));
+  EXPECT_EQ_WAIT(cricket::CS_RUNNING, capture_state(), kMsCallbackWait);
+  EXPECT_EQ(1, callback_count());
+  EXPECT_TRUE(capture_manager_.AddVideoRenderer(&video_capturer_,
+                                                &video_renderer_));
+  EXPECT_TRUE(video_capturer_.CaptureFrame());
+  EXPECT_EQ(1, NumFramesRendered());
+  // Renderer should be fed frames with the resolution of format_vga_.
+  EXPECT_TRUE(WasRenderedResolution(format_vga_));
+
+  // Start again with one more format.
+  EXPECT_TRUE(capture_manager_.StartVideoCapture(&video_capturer_,
+                                                 format_qvga_));
+  // Existing renderers should be fed frames with the resolution of format_vga_.
+  EXPECT_TRUE(video_capturer_.CaptureFrame());
+  EXPECT_TRUE(WasRenderedResolution(format_vga_));
+  EXPECT_TRUE(capture_manager_.StopVideoCapture(&video_capturer_, format_vga_));
+  EXPECT_TRUE(capture_manager_.StopVideoCapture(&video_capturer_,
+                                                format_qvga_));
+  EXPECT_FALSE(capture_manager_.StopVideoCapture(&video_capturer_,
+                                                 format_vga_));
+  EXPECT_FALSE(capture_manager_.StopVideoCapture(&video_capturer_,
+                                                 format_qvga_));
+}
+
+// Should pick the lowest resolution as the highest resolution is not chosen
+// until after capturing has started. This ensures that no particular resolution
+// is favored over others.
+TEST_F(CaptureManagerTest, KeepFirstResolutionLow) {
+  EXPECT_TRUE(capture_manager_.StartVideoCapture(&video_capturer_,
+                                                 format_qvga_));
+  EXPECT_TRUE(capture_manager_.StartVideoCapture(&video_capturer_,
+                                                 format_vga_));
+  EXPECT_TRUE(capture_manager_.AddVideoRenderer(&video_capturer_,
+                                                &video_renderer_));
+  EXPECT_EQ_WAIT(1, callback_count(), kMsCallbackWait);
+  EXPECT_TRUE(video_capturer_.CaptureFrame());
+  EXPECT_EQ(1, NumFramesRendered());
+  EXPECT_TRUE(WasRenderedResolution(format_qvga_));
+}
+
+// Ensure that the reference counting is working when multiple start and
+// multiple stop calls are made.
+TEST_F(CaptureManagerTest, MultipleStartStops) {
+  EXPECT_TRUE(capture_manager_.StartVideoCapture(&video_capturer_,
+                                                 format_vga_));
+  // Add video capturer but with different format.
+  EXPECT_TRUE(capture_manager_.StartVideoCapture(&video_capturer_,
+                                                 format_qvga_));
+  EXPECT_EQ_WAIT(cricket::CS_RUNNING, capture_state(), kMsCallbackWait);
+  EXPECT_EQ(1, callback_count());
+  EXPECT_TRUE(capture_manager_.AddVideoRenderer(&video_capturer_,
+                                                &video_renderer_));
+  // Ensure that a frame can be captured when two start calls have been made.
+  EXPECT_TRUE(video_capturer_.CaptureFrame());
+  EXPECT_EQ(1, NumFramesRendered());
+
+  EXPECT_TRUE(capture_manager_.StopVideoCapture(&video_capturer_, format_vga_));
+  // Video should still render since there has been two start calls but only
+  // one stop call.
+  EXPECT_TRUE(video_capturer_.CaptureFrame());
+  EXPECT_EQ(2, NumFramesRendered());
+
+  EXPECT_TRUE(capture_manager_.StopVideoCapture(&video_capturer_,
+                                                format_qvga_));
+  EXPECT_EQ_WAIT(cricket::CS_STOPPED, capture_state(), kMsCallbackWait);
+  EXPECT_EQ(2, callback_count());
+  // Last stop call should fail as it is one more than the number of start
+  // calls.
+  EXPECT_FALSE(capture_manager_.StopVideoCapture(&video_capturer_,
+                                                 format_vga_));
+}
+
+TEST_F(CaptureManagerTest, TestForceRestart) {
+  EXPECT_TRUE(capture_manager_.StartVideoCapture(&video_capturer_,
+                                                 format_qvga_));
+  EXPECT_TRUE(capture_manager_.AddVideoRenderer(&video_capturer_,
+                                                &video_renderer_));
+  EXPECT_EQ_WAIT(1, callback_count(), kMsCallbackWait);
+  EXPECT_TRUE(video_capturer_.CaptureFrame());
+  EXPECT_EQ(1, NumFramesRendered());
+  EXPECT_TRUE(WasRenderedResolution(format_qvga_));
+  // Now restart with vga.
+  EXPECT_TRUE(capture_manager_.RestartVideoCapture(
+      &video_capturer_, format_qvga_, format_vga_,
+      cricket::CaptureManager::kForceRestart));
+  EXPECT_TRUE(video_capturer_.CaptureFrame());
+  EXPECT_EQ(2, NumFramesRendered());
+  EXPECT_TRUE(WasRenderedResolution(format_vga_));
+}
+
+TEST_F(CaptureManagerTest, TestRequestRestart) {
+  EXPECT_TRUE(capture_manager_.StartVideoCapture(&video_capturer_,
+                                                 format_vga_));
+  EXPECT_TRUE(capture_manager_.AddVideoRenderer(&video_capturer_,
+                                                &video_renderer_));
+  EXPECT_EQ_WAIT(1, callback_count(), kMsCallbackWait);
+  EXPECT_TRUE(video_capturer_.CaptureFrame());
+  EXPECT_EQ(1, NumFramesRendered());
+  EXPECT_TRUE(WasRenderedResolution(format_vga_));
+  // Now request restart with qvga.
+  EXPECT_TRUE(capture_manager_.RestartVideoCapture(
+      &video_capturer_, format_vga_, format_qvga_,
+      cricket::CaptureManager::kRequestRestart));
+  EXPECT_TRUE(video_capturer_.CaptureFrame());
+  EXPECT_EQ(2, NumFramesRendered());
+  EXPECT_TRUE(WasRenderedResolution(format_vga_));
+}
diff --git a/talk/media/base/capturerenderadapter.cc b/talk/media/base/capturerenderadapter.cc
new file mode 100644
index 0000000..8fbf34e
--- /dev/null
+++ b/talk/media/base/capturerenderadapter.cc
@@ -0,0 +1,137 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/media/base/capturerenderadapter.h"
+
+#include "talk/base/logging.h"
+#include "talk/media/base/videocapturer.h"
+#include "talk/media/base/videoprocessor.h"
+#include "talk/media/base/videorenderer.h"
+
+namespace cricket {
+
+CaptureRenderAdapter::CaptureRenderAdapter(VideoCapturer* video_capturer)
+    : video_capturer_(video_capturer) {
+}
+
+CaptureRenderAdapter::~CaptureRenderAdapter() {
+  // Have to disconnect here since |video_capturer_| lives on past the
+  // destruction of this object.
+  if (video_capturer_) {
+    video_capturer_->SignalVideoFrame.disconnect(this);
+  }
+}
+
+CaptureRenderAdapter* CaptureRenderAdapter::Create(
+    VideoCapturer* video_capturer) {
+  if (!video_capturer) {
+    return NULL;
+  }
+  CaptureRenderAdapter* return_value = new CaptureRenderAdapter(video_capturer);
+  return_value->Init();  // Can't fail.
+  return return_value;
+}
+
+bool CaptureRenderAdapter::AddRenderer(VideoRenderer* video_renderer) {
+  if (!video_renderer) {
+    return false;
+  }
+  talk_base::CritScope cs(&capture_crit_);
+  if (IsRendererRegistered(*video_renderer)) {
+    return false;
+  }
+  video_renderers_.push_back(VideoRendererInfo(video_renderer));
+  return true;
+}
+
+bool CaptureRenderAdapter::RemoveRenderer(VideoRenderer* video_renderer) {
+  if (!video_renderer) {
+    return false;
+  }
+  talk_base::CritScope cs(&capture_crit_);
+  for (VideoRenderers::iterator iter = video_renderers_.begin();
+       iter != video_renderers_.end(); ++iter) {
+    if (video_renderer == iter->renderer) {
+      video_renderers_.erase(iter);
+      return true;
+    }
+  }
+  return false;
+}
+
+void CaptureRenderAdapter::Init() {
+  video_capturer_->SignalVideoFrame.connect(
+      this,
+      &CaptureRenderAdapter::OnVideoFrame);
+}
+
+void CaptureRenderAdapter::OnVideoFrame(VideoCapturer* capturer,
+                                        const VideoFrame* video_frame) {
+  talk_base::CritScope cs(&capture_crit_);
+  if (video_renderers_.empty()) {
+    return;
+  }
+  MaybeSetRenderingSize(video_frame);
+
+  for (VideoRenderers::iterator iter = video_renderers_.begin();
+       iter != video_renderers_.end(); ++iter) {
+    VideoRenderer* video_renderer = iter->renderer;
+    video_renderer->RenderFrame(video_frame);
+  }
+}
+
+// The renderer_crit_ lock needs to be taken when calling this function.
+void CaptureRenderAdapter::MaybeSetRenderingSize(const VideoFrame* frame) {
+  for (VideoRenderers::iterator iter = video_renderers_.begin();
+       iter != video_renderers_.end(); ++iter) {
+    const bool new_resolution = iter->render_width != frame->GetWidth() ||
+        iter->render_height != frame->GetHeight();
+    if (new_resolution) {
+      if (iter->renderer->SetSize(frame->GetWidth(), frame->GetHeight(), 0)) {
+        iter->render_width = frame->GetWidth();
+        iter->render_height = frame->GetHeight();
+      } else {
+        LOG(LS_ERROR) << "Captured frame size not supported by renderer: " <<
+            frame->GetWidth() << " x " << frame->GetHeight();
+      }
+    }
+  }
+}
+
+// The renderer_crit_ lock needs to be taken when calling this function.
+bool CaptureRenderAdapter::IsRendererRegistered(
+    const VideoRenderer& video_renderer) const {
+  for (VideoRenderers::const_iterator iter = video_renderers_.begin();
+       iter != video_renderers_.end(); ++iter) {
+    if (&video_renderer == iter->renderer) {
+      return true;
+    }
+  }
+  return false;
+}
+
+}  // namespace cricket
diff --git a/talk/media/base/capturerenderadapter.h b/talk/media/base/capturerenderadapter.h
new file mode 100644
index 0000000..1df9131
--- /dev/null
+++ b/talk/media/base/capturerenderadapter.h
@@ -0,0 +1,91 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 class CaptureRenderAdapter. The class connects a
+// VideoCapturer to any number of VideoRenders such that the former feeds the
+// latter.
+// CaptureRenderAdapter is Thread-unsafe. This means that none of its APIs may
+// be called concurrently.
+
+#ifndef TALK_MEDIA_BASE_CAPTURERENDERADAPTER_H_
+#define TALK_MEDIA_BASE_CAPTURERENDERADAPTER_H_
+
+#include <vector>
+
+#include "talk/base/criticalsection.h"
+#include "talk/base/sigslot.h"
+#include "talk/media/base/videocapturer.h"
+
+namespace cricket {
+
+class VideoCapturer;
+class VideoProcessor;
+class VideoRenderer;
+
+class CaptureRenderAdapter : public sigslot::has_slots<> {
+ public:
+  static CaptureRenderAdapter* Create(VideoCapturer* video_capturer);
+  ~CaptureRenderAdapter();
+
+  bool AddRenderer(VideoRenderer* video_renderer);
+  bool RemoveRenderer(VideoRenderer* video_renderer);
+
+  VideoCapturer* video_capturer() { return video_capturer_; }
+ private:
+  struct VideoRendererInfo {
+    explicit VideoRendererInfo(VideoRenderer* r)
+        : renderer(r),
+          render_width(0),
+          render_height(0) {
+    }
+    VideoRenderer* renderer;
+    size_t render_width;
+    size_t render_height;
+  };
+
+  // Just pointers since ownership is not handed over to this class.
+  typedef std::vector<VideoRendererInfo> VideoRenderers;
+
+  explicit CaptureRenderAdapter(VideoCapturer* video_capturer);
+  void Init();
+
+  // Callback for frames received from the capturer.
+  void OnVideoFrame(VideoCapturer* capturer, const VideoFrame* video_frame);
+
+  void MaybeSetRenderingSize(const VideoFrame* frame);
+
+  bool IsRendererRegistered(const VideoRenderer& video_renderer) const;
+
+  VideoRenderers video_renderers_;
+  VideoCapturer* video_capturer_;
+  // Critical section synchronizing the capture thread.
+  mutable talk_base::CriticalSection capture_crit_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_CAPTURERENDERADAPTER_H_
diff --git a/talk/media/base/codec.cc b/talk/media/base/codec.cc
new file mode 100644
index 0000000..2d54c99
--- /dev/null
+++ b/talk/media/base/codec.cc
@@ -0,0 +1,169 @@
+/*
+ * 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/media/base/codec.h"
+
+#include <algorithm>
+#include <sstream>
+
+#include "talk/base/common.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+
+namespace cricket {
+
+static const int kMaxStaticPayloadId = 95;
+
+bool FeedbackParam::operator==(const FeedbackParam& other) const {
+  return _stricmp(other.id().c_str(), id().c_str()) == 0 &&
+      _stricmp(other.param().c_str(), param().c_str()) == 0;
+}
+
+bool FeedbackParams::operator==(const FeedbackParams& other) const {
+  return params_ == other.params_;
+}
+
+bool FeedbackParams::Has(const FeedbackParam& param) const {
+  return std::find(params_.begin(), params_.end(), param) != params_.end();
+}
+
+void FeedbackParams::Add(const FeedbackParam& param) {
+  if (param.id().empty()) {
+    return;
+  }
+  if (Has(param)) {
+    // Param already in |this|.
+    return;
+  }
+  params_.push_back(param);
+  ASSERT(!HasDuplicateEntries());
+}
+
+void FeedbackParams::Intersect(const FeedbackParams& from) {
+  std::vector<FeedbackParam>::iterator iter_to = params_.begin();
+  while (iter_to != params_.end()) {
+    if (!from.Has(*iter_to)) {
+      iter_to = params_.erase(iter_to);
+    } else {
+      ++iter_to;
+    }
+  }
+}
+
+bool FeedbackParams::HasDuplicateEntries() const {
+  for (std::vector<FeedbackParam>::const_iterator iter = params_.begin();
+       iter != params_.end(); ++iter) {
+    for (std::vector<FeedbackParam>::const_iterator found = iter + 1;
+         found != params_.end(); ++found) {
+      if (*found == *iter) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+bool Codec::Matches(const Codec& codec) const {
+  // Match the codec id/name based on the typical static/dynamic name rules.
+  // Matching is case-insensitive.
+  return (codec.id <= kMaxStaticPayloadId) ?
+      (id == codec.id) : (_stricmp(name.c_str(), codec.name.c_str()) == 0);
+}
+
+bool Codec::GetParam(const std::string& name, std::string* out) const {
+  CodecParameterMap::const_iterator iter = params.find(name);
+  if (iter == params.end())
+    return false;
+  *out = iter->second;
+  return true;
+}
+
+bool Codec::GetParam(const std::string& name, int* out) const {
+  CodecParameterMap::const_iterator iter = params.find(name);
+  if (iter == params.end())
+    return false;
+  return talk_base::FromString(iter->second, out);
+}
+
+void Codec::SetParam(const std::string& name, const std::string& value) {
+  params[name] = value;
+}
+
+void Codec::SetParam(const std::string& name, int value)  {
+  params[name] = talk_base::ToString(value);
+}
+
+void Codec::AddFeedbackParam(const FeedbackParam& param) {
+  feedback_params.Add(param);
+}
+
+bool Codec::HasFeedbackParam(const FeedbackParam& param) const {
+  return feedback_params.Has(param);
+}
+
+void Codec::IntersectFeedbackParams(const Codec& other) {
+  feedback_params.Intersect(other.feedback_params);
+}
+
+bool AudioCodec::Matches(const AudioCodec& codec) const {
+  // If a nonzero clockrate is specified, it must match the actual clockrate.
+  // If a nonzero bitrate is specified, it must match the actual bitrate,
+  // unless the codec is VBR (0), where we just force the supplied value.
+  // The number of channels must match exactly, with the exception
+  // that channels=0 is treated synonymously as channels=1, per RFC
+  // 4566 section 6: " [The channels] parameter is OPTIONAL and may be
+  // omitted if the number of channels is one."
+  // Preference is ignored.
+  // TODO(juberti): Treat a zero clockrate as 8000Hz, the RTP default clockrate.
+  return Codec::Matches(codec) &&
+      ((codec.clockrate == 0 /*&& clockrate == 8000*/) ||
+          clockrate == codec.clockrate) &&
+      (codec.bitrate == 0 || bitrate <= 0 || bitrate == codec.bitrate) &&
+      ((codec.channels < 2 && channels < 2) || channels == codec.channels);
+}
+
+std::string AudioCodec::ToString() const {
+  std::ostringstream os;
+  os << "AudioCodec[" << id << ":" << name << ":" << clockrate << ":" << bitrate
+     << ":" << channels << ":" << preference << "]";
+  return os.str();
+}
+
+std::string VideoCodec::ToString() const {
+  std::ostringstream os;
+  os << "VideoCodec[" << id << ":" << name << ":" << width << ":" << height
+     << ":" << framerate << ":" << preference << "]";
+  return os.str();
+}
+
+std::string DataCodec::ToString() const {
+  std::ostringstream os;
+  os << "DataCodec[" << id << ":" << name << "]";
+  return os.str();
+}
+
+}  // namespace cricket
diff --git a/talk/media/base/codec.h b/talk/media/base/codec.h
new file mode 100644
index 0000000..d703001
--- /dev/null
+++ b/talk/media/base/codec.h
@@ -0,0 +1,301 @@
+/*
+ * 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_MEDIA_BASE_CODEC_H_
+#define TALK_MEDIA_BASE_CODEC_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "talk/media/base/constants.h"
+
+namespace cricket {
+
+typedef std::map<std::string, std::string> CodecParameterMap;
+
+class FeedbackParam {
+ public:
+  FeedbackParam(const std::string& id, const std::string& param)
+      : id_(id),
+        param_(param) {
+  }
+  bool operator==(const FeedbackParam& other) const;
+
+  const std::string& id() const { return id_; }
+  const std::string& param() const { return param_; }
+
+ private:
+  std::string id_;  // e.g. "nack", "ccm"
+  std::string param_;  // e.g. "", "rpsi", "fir"
+};
+
+class FeedbackParams {
+ public:
+  bool operator==(const FeedbackParams& other) const;
+
+  bool Has(const FeedbackParam& param) const;
+  void Add(const FeedbackParam& param);
+
+  void Intersect(const FeedbackParams& from);
+
+  const std::vector<FeedbackParam>& params() const { return params_; }
+ private:
+  bool HasDuplicateEntries() const;
+
+  std::vector<FeedbackParam> params_;
+};
+
+struct Codec {
+  int id;
+  std::string name;
+  int clockrate;
+  int preference;
+  CodecParameterMap params;
+  FeedbackParams feedback_params;
+
+  // Creates a codec with the given parameters.
+  Codec(int id, const std::string& name, int clockrate, int preference)
+      : id(id),
+        name(name),
+        clockrate(clockrate),
+        preference(preference) {
+  }
+
+  // Creates an empty codec.
+  Codec() : id(0), clockrate(0), preference(0) {}
+
+  // Indicates if this codec is compatible with the specified codec.
+  bool Matches(const Codec& codec) const;
+
+  // Find the parameter for |name| and write the value to |out|.
+  bool GetParam(const std::string& name, std::string* out) const;
+  bool GetParam(const std::string& name, int* out) const;
+
+  void SetParam(const std::string& name, const std::string& value);
+  void SetParam(const std::string& name, int value);
+
+  bool HasFeedbackParam(const FeedbackParam& param) const;
+  void AddFeedbackParam(const FeedbackParam& param);
+
+  static bool Preferable(const Codec& first, const Codec& other) {
+    return first.preference > other.preference;
+  }
+
+  // Filter |this| feedbacks params such that only those shared by both |this|
+  // and |other| are kept.
+  void IntersectFeedbackParams(const Codec& other);
+
+  Codec& operator=(const Codec& c) {
+    this->id = c.id;  // id is reserved in objective-c
+    name = c.name;
+    clockrate = c.clockrate;
+    preference = c.preference;
+    return *this;
+  }
+
+  bool operator==(const Codec& c) const {
+    return this->id == c.id &&  // id is reserved in objective-c
+        name == c.name &&
+        clockrate == c.clockrate &&
+        preference == c.preference;
+  }
+
+  bool operator!=(const Codec& c) const {
+    return !(*this == c);
+  }
+};
+
+struct AudioCodec : public Codec {
+  int bitrate;
+  int channels;
+
+  // Creates a codec with the given parameters.
+  AudioCodec(int pt, const std::string& nm, int cr, int br, int cs, int pr)
+      : Codec(pt, nm, cr, pr),
+        bitrate(br),
+        channels(cs) {
+  }
+
+  // Creates an empty codec.
+  AudioCodec() : Codec(), bitrate(0), channels(0) {}
+
+  // Indicates if this codec is compatible with the specified codec.
+  bool Matches(const AudioCodec& codec) const;
+
+  static bool Preferable(const AudioCodec& first, const AudioCodec& other) {
+    return first.preference > other.preference;
+  }
+
+  std::string ToString() const;
+
+  AudioCodec& operator=(const AudioCodec& c) {
+    this->id = c.id;  // id is reserved in objective-c
+    name = c.name;
+    clockrate = c.clockrate;
+    bitrate = c.bitrate;
+    channels = c.channels;
+    preference =  c.preference;
+    params = c.params;
+    feedback_params = c.feedback_params;
+    return *this;
+  }
+
+  bool operator==(const AudioCodec& c) const {
+    return this->id == c.id &&  // id is reserved in objective-c
+           name == c.name &&
+           clockrate == c.clockrate &&
+           bitrate == c.bitrate &&
+           channels == c.channels &&
+           preference == c.preference &&
+           params == c.params &&
+           feedback_params == c.feedback_params;
+  }
+
+  bool operator!=(const AudioCodec& c) const {
+    return !(*this == c);
+  }
+};
+
+struct VideoCodec : public Codec {
+  int width;
+  int height;
+  int framerate;
+
+  // Creates a codec with the given parameters.
+  VideoCodec(int pt, const std::string& nm, int w, int h, int fr, int pr)
+      : Codec(pt, nm, kVideoCodecClockrate, pr),
+        width(w),
+        height(h),
+        framerate(fr) {
+  }
+
+  // Creates an empty codec.
+  VideoCodec()
+      : Codec(),
+        width(0),
+        height(0),
+        framerate(0) {
+    clockrate = kVideoCodecClockrate;
+  }
+
+  static bool Preferable(const VideoCodec& first, const VideoCodec& other) {
+    return first.preference > other.preference;
+  }
+
+  std::string ToString() const;
+
+  VideoCodec& operator=(const VideoCodec& c) {
+    this->id = c.id;  // id is reserved in objective-c
+    name = c.name;
+    clockrate = c.clockrate;
+    width = c.width;
+    height = c.height;
+    framerate = c.framerate;
+    preference =  c.preference;
+    params = c.params;
+    feedback_params = c.feedback_params;
+    return *this;
+  }
+
+  bool operator==(const VideoCodec& c) const {
+    return this->id == c.id &&  // id is reserved in objective-c
+           name == c.name &&
+           clockrate == c.clockrate &&
+           width == c.width &&
+           height == c.height &&
+           framerate == c.framerate &&
+           preference == c.preference &&
+           params == c.params &&
+           feedback_params == c.feedback_params;
+  }
+
+  bool operator!=(const VideoCodec& c) const {
+    return !(*this == c);
+  }
+};
+
+struct DataCodec : public Codec {
+  DataCodec(int id, const std::string& name, int preference)
+      : Codec(id, name, kDataCodecClockrate, preference) {
+  }
+
+  DataCodec() : Codec() {
+    clockrate = kDataCodecClockrate;
+  }
+
+  std::string ToString() const;
+};
+
+struct VideoEncoderConfig {
+  static const int kDefaultMaxThreads = -1;
+  static const int kDefaultCpuProfile = -1;
+
+  VideoEncoderConfig()
+      : max_codec(),
+        num_threads(kDefaultMaxThreads),
+        cpu_profile(kDefaultCpuProfile) {
+  }
+
+  VideoEncoderConfig(const VideoCodec& c)
+      : max_codec(c),
+        num_threads(kDefaultMaxThreads),
+        cpu_profile(kDefaultCpuProfile) {
+  }
+
+  VideoEncoderConfig(const VideoCodec& c, int t, int p)
+      : max_codec(c),
+        num_threads(t),
+        cpu_profile(p) {
+  }
+
+  VideoEncoderConfig& operator=(const VideoEncoderConfig& config) {
+    max_codec = config.max_codec;
+    num_threads = config.num_threads;
+    cpu_profile = config.cpu_profile;
+    return *this;
+  }
+
+  bool operator==(const VideoEncoderConfig& config) const {
+    return max_codec == config.max_codec &&
+           num_threads == config.num_threads &&
+           cpu_profile == config.cpu_profile;
+  }
+
+  bool operator!=(const VideoEncoderConfig& config) const {
+    return !(*this == config);
+  }
+
+  VideoCodec max_codec;
+  int num_threads;
+  int cpu_profile;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_CODEC_H_
diff --git a/talk/media/base/codec_unittest.cc b/talk/media/base/codec_unittest.cc
new file mode 100644
index 0000000..f0ffd8f
--- /dev/null
+++ b/talk/media/base/codec_unittest.cc
@@ -0,0 +1,294 @@
+/*
+ * 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/media/base/codec.h"
+
+using cricket::AudioCodec;
+using cricket::Codec;
+using cricket::DataCodec;
+using cricket::FeedbackParam;
+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, 1, 3);
+  EXPECT_TRUE(c0.Matches(AudioCodec(95, "", 44100, 20000, 1, 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, 1, 0)));
+  EXPECT_FALSE(c0.Matches(AudioCodec(95, "", 55100, 20000, 1, 0)));
+  EXPECT_FALSE(c0.Matches(AudioCodec(95, "", 44100, 30000, 1, 0)));
+  EXPECT_FALSE(c0.Matches(AudioCodec(95, "", 44100, 20000, 2, 0)));
+  EXPECT_FALSE(c0.Matches(AudioCodec(95, "", 55100, 30000, 2, 0)));
+
+  // Test a codec with a dynamic payload type.
+  AudioCodec c1(96, "A", 44100, 20000, 1, 3);
+  EXPECT_TRUE(c1.Matches(AudioCodec(96, "A", 0, 0, 0, 0)));
+  EXPECT_TRUE(c1.Matches(AudioCodec(97, "A", 0, 0, 0, 0)));
+  EXPECT_TRUE(c1.Matches(AudioCodec(96, "a", 0, 0, 0, 0)));
+  EXPECT_TRUE(c1.Matches(AudioCodec(97, "a", 0, 0, 0, 0)));
+  EXPECT_FALSE(c1.Matches(AudioCodec(95, "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)));
+
+  // Stereo doesn't match channels = 0.
+  AudioCodec c3(96, "A", 44100, 20000, 2, 3);
+  EXPECT_TRUE(c3.Matches(AudioCodec(96, "A", 44100, 20000, 2, 3)));
+  EXPECT_FALSE(c3.Matches(AudioCodec(96, "A", 44100, 20000, 1, 3)));
+  EXPECT_FALSE(c3.Matches(AudioCodec(96, "A", 44100, 20000, 0, 3)));
+}
+
+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(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(VideoCodec(96, "V", 640, 400, 15, 0)));
+  EXPECT_TRUE(c1.Matches(VideoCodec(97, "V", 640, 400, 15, 0)));
+  EXPECT_TRUE(c1.Matches(VideoCodec(96, "v", 640, 400, 15, 0)));
+  EXPECT_TRUE(c1.Matches(VideoCodec(97, "v", 640, 400, 15, 0)));
+  EXPECT_FALSE(c1.Matches(VideoCodec(96, "", 320, 200, 30, 0)));
+  EXPECT_FALSE(c1.Matches(VideoCodec(95, "V", 640, 400, 15, 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);
+}
+
+TEST_F(CodecTest, TestDataCodecMatches) {
+  // Test a codec with a static payload type.
+  DataCodec c0(95, "D", 0);
+  EXPECT_TRUE(c0.Matches(DataCodec(95, "", 0)));
+  EXPECT_FALSE(c0.Matches(DataCodec(96, "", 0)));
+
+  // Test a codec with a dynamic payload type.
+  DataCodec c1(96, "D", 3);
+  EXPECT_TRUE(c1.Matches(DataCodec(96, "D", 0)));
+  EXPECT_TRUE(c1.Matches(DataCodec(97, "D", 0)));
+  EXPECT_TRUE(c1.Matches(DataCodec(96, "d", 0)));
+  EXPECT_TRUE(c1.Matches(DataCodec(97, "d", 0)));
+  EXPECT_FALSE(c1.Matches(DataCodec(96, "", 0)));
+  EXPECT_FALSE(c1.Matches(DataCodec(95, "D", 0)));
+}
+
+TEST_F(CodecTest, TestDataCodecOperators) {
+  DataCodec c0(96, "D", 3);
+  DataCodec c1(95, "D", 3);
+  DataCodec c2(96, "x", 3);
+  DataCodec c3(96, "D", 1);
+  EXPECT_TRUE(c0 != c1);
+  EXPECT_TRUE(c0 != c2);
+  EXPECT_TRUE(c0 != c3);
+
+  DataCodec c4;
+  DataCodec c5(0, "", 0);
+  DataCodec c6 = c0;
+  EXPECT_TRUE(c5 == c4);
+  EXPECT_TRUE(c6 != c4);
+  EXPECT_TRUE(c6 == c0);
+}
+
+TEST_F(CodecTest, TestSetParamAndGetParam) {
+  AudioCodec codec;
+  codec.SetParam("a", "1");
+  codec.SetParam("b", "x");
+
+  int int_value = 0;
+  EXPECT_TRUE(codec.GetParam("a", &int_value));
+  EXPECT_EQ(1, int_value);
+  EXPECT_FALSE(codec.GetParam("b", &int_value));
+  EXPECT_FALSE(codec.GetParam("c", &int_value));
+
+  std::string str_value;
+  EXPECT_TRUE(codec.GetParam("a", &str_value));
+  EXPECT_EQ("1", str_value);
+  EXPECT_TRUE(codec.GetParam("b", &str_value));
+  EXPECT_EQ("x", str_value);
+  EXPECT_FALSE(codec.GetParam("c", &str_value));
+}
+
+TEST_F(CodecTest, TestIntersectFeedbackParams) {
+  const FeedbackParam a1("a", "1");
+  const FeedbackParam b2("b", "2");
+  const FeedbackParam b3("b", "3");
+  const FeedbackParam c3("c", "3");
+  Codec c1;
+  c1.AddFeedbackParam(a1); // Only match with c2.
+  c1.AddFeedbackParam(b2); // Same param different values.
+  c1.AddFeedbackParam(c3); // Not in c2.
+  Codec c2;
+  c2.AddFeedbackParam(a1);
+  c2.AddFeedbackParam(b3);
+
+  c1.IntersectFeedbackParams(c2);
+  EXPECT_TRUE(c1.HasFeedbackParam(a1));
+  EXPECT_FALSE(c1.HasFeedbackParam(b2));
+  EXPECT_FALSE(c1.HasFeedbackParam(c3));
+}
diff --git a/talk/media/base/constants.cc b/talk/media/base/constants.cc
new file mode 100644
index 0000000..8fd1bdc
--- /dev/null
+++ b/talk/media/base/constants.cc
@@ -0,0 +1,95 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/media/base/constants.h"
+
+#include <string>
+
+namespace cricket {
+
+const int kVideoCodecClockrate = 90000;
+const int kDataCodecClockrate = 90000;
+const int kDataMaxBandwidth = 30720;  // bps
+
+const float kHighSystemCpuThreshold = 0.85f;
+const float kLowSystemCpuThreshold = 0.65f;
+const float kProcessCpuThreshold = 0.10f;
+
+const char* kRtxCodecName = "rtx";
+
+// RTP payload type is in the 0-127 range. Use 128 to indicate "all" payload
+// types.
+const int kWildcardPayloadType = -1;
+
+const char* kCodecParamAssociatedPayloadType = "apt";
+
+const char* kOpusCodecName = "opus";
+
+const char* kCodecParamPTime = "ptime";
+const char* kCodecParamMaxPTime = "maxptime";
+
+const char* kCodecParamMinPTime = "minptime";
+const char* kCodecParamSPropStereo = "sprop-stereo";
+const char* kCodecParamStereo = "stereo";
+const char* kCodecParamUseInbandFec = "useinbandfec";
+const char* kCodecParamSctpProtocol = "protocol";
+const char* kCodecParamSctpStreams = "streams";
+
+const char* kParamValueTrue = "1";
+const char* kParamValueEmpty = "";
+
+const int kOpusDefaultMaxPTime = 120;
+const int kOpusDefaultPTime = 20;
+const int kOpusDefaultMinPTime = 3;
+const int kOpusDefaultSPropStereo = 0;
+const int kOpusDefaultStereo = 0;
+const int kOpusDefaultUseInbandFec = 0;
+
+const int kPreferredMaxPTime = 60;
+const int kPreferredMinPTime = 10;
+const int kPreferredSPropStereo = 0;
+const int kPreferredStereo = 0;
+const int kPreferredUseInbandFec = 0;
+
+const char* kRtcpFbParamNack = "nack";
+const char* kRtcpFbParamRemb = "goog-remb";
+
+const char* kRtcpFbParamCcm = "ccm";
+const char* kRtcpFbCcmParamFir = "fir";
+const char* kCodecParamMaxBitrate = "x-google-max-bitrate";
+const char* kCodecParamMinBitrate = "x-google-min-bitrate";
+const char* kCodecParamMaxQuantization = "x-google-max-quantization";
+
+const int kGoogleRtpDataCodecId = 101;
+const char kGoogleRtpDataCodecName[] = "google-data";
+
+const int kGoogleSctpDataCodecId = 108;
+const char kGoogleSctpDataCodecName[] = "google-sctp-data";
+
+const char kComfortNoiseCodecName[] = "CN";
+
+}  // namespace cricket
diff --git a/talk/media/base/constants.h b/talk/media/base/constants.h
new file mode 100644
index 0000000..8df4805
--- /dev/null
+++ b/talk/media/base/constants.h
@@ -0,0 +1,118 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_MEDIA_BASE_CONSTANTS_H_
+#define TALK_MEDIA_BASE_CONSTANTS_H_
+
+#include <string>
+
+// This file contains constants related to media.
+
+namespace cricket {
+
+extern const int kVideoCodecClockrate;
+extern const int kDataCodecClockrate;
+extern const int kDataMaxBandwidth;  // bps
+
+// Default CPU thresholds.
+extern const float kHighSystemCpuThreshold;
+extern const float kLowSystemCpuThreshold;
+extern const float kProcessCpuThreshold;
+
+extern const char* kRtxCodecName;
+
+// Codec parameters
+extern const int kWildcardPayloadType;
+extern const char* kCodecParamAssociatedPayloadType;
+
+extern const char* kOpusCodecName;
+
+// Attribute parameters
+extern const char* kCodecParamPTime;
+extern const char* kCodecParamMaxPTime;
+// fmtp parameters
+extern const char* kCodecParamMinPTime;
+extern const char* kCodecParamSPropStereo;
+extern const char* kCodecParamStereo;
+extern const char* kCodecParamUseInbandFec;
+extern const char* kCodecParamSctpProtocol;
+extern const char* kCodecParamSctpStreams;
+
+extern const char* kParamValueTrue;
+// Parameters are stored as parameter/value pairs. For parameters who do not
+// have a value, |kParamValueEmpty| should be used as value.
+extern const char* kParamValueEmpty;
+
+// opus parameters.
+// Default value for maxptime according to
+// http://tools.ietf.org/html/draft-spittka-payload-rtp-opus-03
+extern const int kOpusDefaultMaxPTime;
+extern const int kOpusDefaultPTime;
+extern const int kOpusDefaultMinPTime;
+extern const int kOpusDefaultSPropStereo;
+extern const int kOpusDefaultStereo;
+extern const int kOpusDefaultUseInbandFec;
+// Prefered values in this code base. Note that they may differ from the default
+// values in http://tools.ietf.org/html/draft-spittka-payload-rtp-opus-03
+// Only frames larger or equal to 10 ms are currently supported in this code
+// base.
+extern const int kPreferredMaxPTime;
+extern const int kPreferredMinPTime;
+extern const int kPreferredSPropStereo;
+extern const int kPreferredStereo;
+extern const int kPreferredUseInbandFec;
+
+// rtcp-fb messages according to RFC 4585
+extern const char* kRtcpFbParamNack;
+// rtcp-fb messages according to
+// http://tools.ietf.org/html/draft-alvestrand-rmcat-remb-00
+extern const char* kRtcpFbParamRemb;
+// ccm submessages according to RFC 5104
+extern const char* kRtcpFbParamCcm;
+extern const char* kRtcpFbCcmParamFir;
+// Google specific parameters
+extern const char* kCodecParamMaxBitrate;
+extern const char* kCodecParamMinBitrate;
+extern const char* kCodecParamMaxQuantization;
+
+// We put the data codec names here so callers of
+// DataEngine::CreateChannel don't have to import rtpdataengine.h or
+// sctpdataengine.h to get the codec names they want to pass in.
+extern const int kGoogleRtpDataCodecId;
+extern const char kGoogleRtpDataCodecName[];
+
+// TODO(pthatcher): Find an id that won't conflict with anything.  On
+// the other hand, it really shouldn't matter since the id won't be
+// used on the wire.
+extern const int kGoogleSctpDataCodecId;
+extern const char kGoogleSctpDataCodecName[];
+
+extern const char kComfortNoiseCodecName[];
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_CONSTANTS_H_
diff --git a/talk/media/base/cpuid.cc b/talk/media/base/cpuid.cc
new file mode 100644
index 0000000..9fd7865
--- /dev/null
+++ b/talk/media/base/cpuid.cc
@@ -0,0 +1,88 @@
+/*
+ * 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/media/base/cpuid.h"
+
+#if !defined(DISABLE_YUV)
+#include "libyuv/cpu_id.h"
+#endif
+
+namespace cricket {
+
+bool CpuInfo::TestCpuFlag(int flag) {
+#if !defined(DISABLE_YUV)
+  return libyuv::TestCpuFlag(flag) ? true : false;
+#else
+  return false;
+#endif
+}
+
+void CpuInfo::MaskCpuFlagsForTest(int enable_flags) {
+#if !defined(DISABLE_YUV)
+  libyuv::MaskCpuFlags(enable_flags);
+#endif
+}
+
+// Detect an Intel Core I5 or better such as 4th generation Macbook Air.
+bool IsCoreIOrBetter() {
+#if !defined(DISABLE_YUV) && (defined(__i386__) || defined(__x86_64__) || \
+    defined(_M_IX86) || defined(_M_X64))
+  int cpu_info[4];
+  libyuv::CpuId(cpu_info, 0);  // Function 0: Vendor ID
+  if (cpu_info[1] == 0x756e6547 && cpu_info[3] == 0x49656e69 &&
+      cpu_info[2] == 0x6c65746e) {  // GenuineIntel
+    // Detect CPU Family and Model
+    // 3:0 - Stepping
+    // 7:4 - Model
+    // 11:8 - Family
+    // 13:12 - Processor Type
+    // 19:16 - Extended Model
+    // 27:20 - Extended Family
+    libyuv::CpuId(cpu_info, 1);  // Function 1: Family and Model
+    int family = ((cpu_info[0] >> 8) & 0x0f) | ((cpu_info[0] >> 16) & 0xff0);
+    int model = ((cpu_info[0] >> 4) & 0x0f) | ((cpu_info[0] >> 12) & 0xf0);
+    // CpuFamily | CpuModel |  Name
+    //         6 |       14 |  Yonah -- Core
+    //         6 |       15 |  Merom -- Core 2
+    //         6 |       23 |  Penryn -- Core 2 (most common)
+    //         6 |       26 |  Nehalem -- Core i*
+    //         6 |       28 |  Atom
+    //         6 |       30 |  Lynnfield -- Core i*
+    //         6 |       37 |  Westmere -- Core i*
+    const int kAtom = 28;
+    const int kCore2 = 23;
+    if (family < 6 || family == 15 ||
+        (family == 6 && (model == kAtom || model <= kCore2))) {
+      return false;
+    }
+    return true;
+  }
+#endif
+  return false;
+}
+
+}  // namespace cricket
diff --git a/talk/media/base/cpuid.h b/talk/media/base/cpuid.h
new file mode 100644
index 0000000..3b2aa76
--- /dev/null
+++ b/talk/media/base/cpuid.h
@@ -0,0 +1,76 @@
+/*
+ * 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_MEDIA_BASE_CPUID_H_
+#define TALK_MEDIA_BASE_CPUID_H_
+
+#include "talk/base/basictypes.h"  // For DISALLOW_IMPLICIT_CONSTRUCTORS
+
+namespace cricket {
+
+class CpuInfo {
+ public:
+  // The following flags must match libyuv/cpu_id.h values.
+  // Internal flag to indicate cpuid requires initialization.
+  static const int kCpuInit = 0x1;
+
+  // These flags are only valid on ARM processors.
+  static const int kCpuHasARM = 0x2;
+  static const int kCpuHasNEON = 0x4;
+  // 0x8 reserved for future ARM flag.
+
+  // These flags are only valid on x86 processors.
+  static const int kCpuHasX86 = 0x10;
+  static const int kCpuHasSSE2 = 0x20;
+  static const int kCpuHasSSSE3 = 0x40;
+  static const int kCpuHasSSE41 = 0x80;
+  static const int kCpuHasSSE42 = 0x100;
+  static const int kCpuHasAVX = 0x200;
+  static const int kCpuHasAVX2 = 0x400;
+  static const int kCpuHasERMS = 0x800;
+
+  // These flags are only valid on MIPS processors.
+  static const int kCpuHasMIPS = 0x1000;
+  static const int kCpuHasMIPS_DSP = 0x2000;
+  static const int kCpuHasMIPS_DSPR2 = 0x4000;
+
+  // Detect CPU has SSE2 etc.
+  static bool TestCpuFlag(int flag);
+
+  // For testing, allow CPU flags to be disabled.
+  static void MaskCpuFlagsForTest(int enable_flags);
+
+ private:
+  DISALLOW_IMPLICIT_CONSTRUCTORS(CpuInfo);
+};
+
+// Detect an Intel Core I5 or better such as 4th generation Macbook Air.
+bool IsCoreIOrBetter();
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_CPUID_H_
diff --git a/talk/media/base/cpuid_unittest.cc b/talk/media/base/cpuid_unittest.cc
new file mode 100644
index 0000000..e8fcc2c
--- /dev/null
+++ b/talk/media/base/cpuid_unittest.cc
@@ -0,0 +1,74 @@
+/*
+ * 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/media/base/cpuid.h"
+
+#include <iostream>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/gunit.h"
+#include "talk/base/systeminfo.h"
+
+TEST(CpuInfoTest, CpuId) {
+  LOG(LS_INFO) << "ARM: "
+      << cricket::CpuInfo::TestCpuFlag(cricket::CpuInfo::kCpuHasARM);
+  LOG(LS_INFO) << "NEON: "
+      << cricket::CpuInfo::TestCpuFlag(cricket::CpuInfo::kCpuHasNEON);
+  LOG(LS_INFO) << "X86: "
+      << cricket::CpuInfo::TestCpuFlag(cricket::CpuInfo::kCpuHasX86);
+  LOG(LS_INFO) << "SSE2: "
+      << cricket::CpuInfo::TestCpuFlag(cricket::CpuInfo::kCpuHasSSE2);
+  LOG(LS_INFO) << "SSSE3: "
+      << cricket::CpuInfo::TestCpuFlag(cricket::CpuInfo::kCpuHasSSSE3);
+  LOG(LS_INFO) << "SSE41: "
+      << cricket::CpuInfo::TestCpuFlag(cricket::CpuInfo::kCpuHasSSE41);
+  LOG(LS_INFO) << "SSE42: "
+      << cricket::CpuInfo::TestCpuFlag(cricket::CpuInfo::kCpuHasSSE42);
+  LOG(LS_INFO) << "AVX: "
+      << cricket::CpuInfo::TestCpuFlag(cricket::CpuInfo::kCpuHasAVX);
+  bool has_arm = cricket::CpuInfo::TestCpuFlag(cricket::CpuInfo::kCpuHasARM);
+  bool has_x86 = cricket::CpuInfo::TestCpuFlag(cricket::CpuInfo::kCpuHasX86);
+  EXPECT_FALSE(has_arm && has_x86);
+}
+
+TEST(CpuInfoTest, IsCoreIOrBetter) {
+  bool core_i_or_better = cricket::IsCoreIOrBetter();
+  // Tests the function is callable.  Run on known hardware to confirm.
+  LOG(LS_INFO) << "IsCoreIOrBetter: " << core_i_or_better;
+
+  // All Core I CPUs have SSE 4.1.
+  if (core_i_or_better) {
+    EXPECT_TRUE(cricket::CpuInfo::TestCpuFlag(cricket::CpuInfo::kCpuHasSSE41));
+    EXPECT_TRUE(cricket::CpuInfo::TestCpuFlag(cricket::CpuInfo::kCpuHasSSSE3));
+  }
+
+  // All CPUs that lack SSE 4.1 are not Core I CPUs.
+  if (!cricket::CpuInfo::TestCpuFlag(cricket::CpuInfo::kCpuHasSSE41)) {
+    EXPECT_FALSE(core_i_or_better);
+  }
+}
+
diff --git a/talk/media/base/cryptoparams.h b/talk/media/base/cryptoparams.h
new file mode 100644
index 0000000..9dd1db5
--- /dev/null
+++ b/talk/media/base/cryptoparams.h
@@ -0,0 +1,54 @@
+/*
+ * 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_MEDIA_BASE_CRYPTOPARAMS_H_
+#define TALK_MEDIA_BASE_CRYPTOPARAMS_H_
+
+#include <string>
+
+namespace cricket {
+
+// Parameters for SRTP negotiation, as described in RFC 4568.
+struct CryptoParams {
+  CryptoParams() : tag(0) {}
+  CryptoParams(int t, const std::string& cs,
+               const std::string& kp, const std::string& sp)
+      : tag(t), cipher_suite(cs), key_params(kp), session_params(sp) {}
+
+  bool Matches(const CryptoParams& params) const {
+    return (tag == params.tag && cipher_suite == params.cipher_suite);
+  }
+
+  int tag;
+  std::string cipher_suite;
+  std::string key_params;
+  std::string session_params;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_CRYPTOPARAMS_H_
diff --git a/talk/media/base/fakecapturemanager.h b/talk/media/base/fakecapturemanager.h
new file mode 100644
index 0000000..64c0f52
--- /dev/null
+++ b/talk/media/base/fakecapturemanager.h
@@ -0,0 +1,52 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_MEDIA_BASE_FAKECAPTUREMANAGER_H_
+#define TALK_MEDIA_BASE_FAKECAPTUREMANAGER_H_
+
+#include "talk/media/base/capturemanager.h"
+
+namespace cricket {
+
+class FakeCaptureManager : public CaptureManager {
+ public:
+  FakeCaptureManager() {}
+  ~FakeCaptureManager() {}
+
+  virtual bool AddVideoRenderer(VideoCapturer* video_capturer,
+                                VideoRenderer* video_renderer) {
+    return true;
+  }
+  virtual bool RemoveVideoRenderer(VideoCapturer* video_capturer,
+                                   VideoRenderer* video_renderer) {
+    return true;
+  }
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_FAKECAPTUREMANAGER_H_
diff --git a/talk/media/base/fakemediaengine.h b/talk/media/base/fakemediaengine.h
new file mode 100644
index 0000000..f0f83a0
--- /dev/null
+++ b/talk/media/base/fakemediaengine.h
@@ -0,0 +1,971 @@
+/*
+ * 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_MEDIA_BASE_FAKEMEDIAENGINE_H_
+#define TALK_MEDIA_BASE_FAKEMEDIAENGINE_H_
+
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "talk/base/buffer.h"
+#include "talk/base/stringutils.h"
+#include "talk/media/base/mediaengine.h"
+#include "talk/media/base/rtputils.h"
+#include "talk/media/base/streamparams.h"
+#include "talk/p2p/base/sessiondescription.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()
+      : sending_(false),
+        playout_(false),
+        fail_set_send_codecs_(false),
+        fail_set_recv_codecs_(false),
+        send_ssrc_(0),
+        ready_to_send_(false) {}
+  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_; }
+
+  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 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);
+  }
+  virtual bool MuteStream(uint32 ssrc, bool on) {
+    if (!HasSendStream(ssrc) && ssrc != 0)
+      return false;
+    if (on)
+      muted_streams_.insert(ssrc);
+    else
+      muted_streams_.erase(ssrc);
+    return true;
+  }
+  bool IsStreamMuted(uint32 ssrc) const {
+    bool ret = muted_streams_.find(ssrc) != muted_streams_.end();
+    // If |ssrc = 0| check if the first send stream is muted.
+    if (!ret && ssrc == 0 && !send_streams_.empty()) {
+      return muted_streams_.find(send_streams_[0].first_ssrc()) !=
+             muted_streams_.end();
+    }
+    return ret;
+  }
+  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);
+  }
+  bool HasSendStream(uint32 ssrc) const {
+    return GetStreamBySsrc(send_streams_, ssrc, NULL);
+  }
+  // TODO(perkj): 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(perkj): 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;
+  }
+
+  bool ready_to_send() const {
+    return ready_to_send_;
+  }
+
+ 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()));
+  }
+  virtual void OnReadyToSend(bool ready) {
+    ready_to_send_ = ready;
+  }
+  bool fail_set_send_codecs() const { return fail_set_send_codecs_; }
+  bool fail_set_recv_codecs() const { return fail_set_recv_codecs_; }
+
+ private:
+  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_;
+  std::set<uint32> muted_streams_;
+  bool fail_set_send_codecs_;
+  bool fail_set_recv_codecs_;
+  uint32 send_ssrc_;
+  std::string rtcp_cname_;
+  bool ready_to_send_;
+};
+
+class FakeVoiceMediaChannel : public RtpHelper<VoiceMediaChannel> {
+ public:
+  struct DtmfInfo {
+    DtmfInfo(uint32 ssrc, int event_code, int duration, int flags)
+        : ssrc(ssrc), event_code(event_code), duration(duration), flags(flags) {
+    }
+    uint32 ssrc;
+    int event_code;
+    int duration;
+    int flags;
+  };
+  explicit FakeVoiceMediaChannel(FakeVoiceEngine* engine)
+      : engine_(engine),
+        fail_set_send_(false),
+        ringback_tone_ssrc_(0),
+        ringback_tone_play_(false),
+        ringback_tone_loop_(false),
+        time_since_last_typing_(-1) {
+    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(); }
+  const std::vector<DtmfInfo>& dtmf_info_queue() const {
+    return dtmf_info_queue_;
+  }
+  const AudioOptions& options() const { return options_; }
+
+  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 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 SetRenderer(uint32 ssrc, AudioRenderer* renderer) {
+    // TODO(xians): Implement this.
+    return false;
+  }
+
+  virtual bool GetActiveStreams(AudioInfo::StreamList* streams) { return true; }
+  virtual int GetOutputLevel() { return 0; }
+  void set_time_since_last_typing(int ms) { time_since_last_typing_ = ms; }
+  virtual int GetTimeSinceLastTyping() { return time_since_last_typing_; }
+  virtual void SetTypingDetectionParameters(
+      int time_window, int cost_per_typing, int reporting_threshold,
+      int penalty_decay, int type_event_delay) {}
+
+  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 CanInsertDtmf() {
+    for (std::vector<AudioCodec>::const_iterator it = send_codecs_.begin();
+         it != send_codecs_.end(); ++it) {
+      // Find the DTMF telephone event "codec".
+      if (_stricmp(it->name.c_str(), "telephone-event") == 0) {
+        return true;
+      }
+    }
+    return false;
+  }
+  virtual bool InsertDtmf(uint32 ssrc, int event_code, int duration,
+                          int flags) {
+    dtmf_info_queue_.push_back(DtmfInfo(ssrc, event_code, duration, flags));
+    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);
+  }
+
+  virtual bool SetOptions(const AudioOptions& options) {
+    // Does a "merge" of current options and set options.
+    options_.SetAll(options);
+    return true;
+  }
+  virtual bool GetOptions(AudioOptions* options) const {
+    *options = options_;
+    return true;
+  }
+
+ 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_;
+  std::map<uint32, OutputScaling> output_scalings_;
+  std::vector<DtmfInfo> dtmf_info_queue_;
+  bool fail_set_send_;
+  uint32 ringback_tone_ssrc_;
+  bool ringback_tone_play_;
+  bool ringback_tone_loop_;
+  int time_since_last_typing_;
+  AudioOptions options_;
+};
+
+// A helper function to compare the FakeVoiceMediaChannel::DtmfInfo.
+inline bool CompareDtmfInfo(const FakeVoiceMediaChannel::DtmfInfo& info,
+                            uint32 ssrc, int event_code, int duration,
+                            int flags) {
+  return (info.duration == duration && info.event_code == event_code &&
+          info.flags == flags && info.ssrc == ssrc);
+}
+
+class FakeVideoMediaChannel : public RtpHelper<VideoMediaChannel> {
+ public:
+  explicit FakeVideoMediaChannel(FakeVideoEngine* engine)
+      : engine_(engine),
+        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 rendering() const { return playout(); }
+  const VideoOptions& options() const { return options_; }
+  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 GetSendCodec(VideoCodec* send_codec) {
+    if (send_codecs_.empty()) {
+      return false;
+    }
+    *send_codec = send_codecs_[0];
+    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 SetCapturer(uint32 ssrc, VideoCapturer* capturer) {
+    capturers_[ssrc] = capturer;
+    return true;
+  }
+  bool HasCapturer(uint32 ssrc) const {
+    return capturers_.find(ssrc) != capturers_.end();
+  }
+  virtual bool SetSendBandwidth(bool autobw, int bps) { 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;
+  }
+  virtual bool SetOptions(const VideoOptions& options) {
+    options_ = options;
+    return true;
+  }
+  virtual bool GetOptions(VideoOptions* options) const {
+    *options = options_;
+    return true;
+  }
+  virtual void UpdateAspectRatio(int ratio_w, int ratio_h) {}
+  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_; }
+
+ 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_;
+  std::map<uint32, VideoCapturer*> capturers_;
+  bool sent_intra_frame_;
+  bool requested_intra_frame_;
+  VideoOptions options_;
+};
+
+class FakeSoundclipMedia : public SoundclipMedia {
+ public:
+  virtual bool PlaySound(const char* buf, int len, int flags) { return true; }
+};
+
+class FakeDataMediaChannel : public RtpHelper<DataMediaChannel> {
+ public:
+  explicit FakeDataMediaChannel(void* unused)
+      : auto_bandwidth_(false), max_bps_(-1) {}
+  ~FakeDataMediaChannel() {}
+  const std::vector<DataCodec>& recv_codecs() const { return recv_codecs_; }
+  const std::vector<DataCodec>& send_codecs() const { return send_codecs_; }
+  const std::vector<DataCodec>& codecs() const { return send_codecs(); }
+  bool auto_bandwidth() const { return auto_bandwidth_; }
+  int max_bps() const { return max_bps_; }
+
+  virtual bool SetRecvCodecs(const std::vector<DataCodec>& codecs) {
+    if (fail_set_recv_codecs()) {
+      // Fake the failure in SetRecvCodecs.
+      return false;
+    }
+    recv_codecs_ = codecs;
+    return true;
+  }
+  virtual bool SetSendCodecs(const std::vector<DataCodec>& codecs) {
+    if (fail_set_send_codecs()) {
+      // Fake the failure in SetSendCodecs.
+      return false;
+    }
+    send_codecs_ = codecs;
+    return true;
+  }
+  virtual bool SetSend(bool send) { return set_sending(send); }
+  virtual bool SetReceive(bool receive) {
+    set_playout(receive);
+    return true;
+  }
+  virtual bool SetSendBandwidth(bool autobw, int bps) {
+    auto_bandwidth_ = autobw;
+    max_bps_ = bps;
+    return true;
+  }
+  virtual bool AddRecvStream(const StreamParams& sp) {
+    if (!RtpHelper<DataMediaChannel>::AddRecvStream(sp))
+      return false;
+    return true;
+  }
+  virtual bool RemoveRecvStream(uint32 ssrc) {
+    if (!RtpHelper<DataMediaChannel>::RemoveRecvStream(ssrc))
+      return false;
+    return true;
+  }
+
+  virtual bool SendData(const SendDataParams& params,
+                        const talk_base::Buffer& payload,
+                        SendDataResult* result) {
+    last_sent_data_params_ = params;
+    last_sent_data_ = std::string(payload.data(), payload.length());
+    return true;
+  }
+
+  SendDataParams last_sent_data_params() { return last_sent_data_params_; }
+  std::string last_sent_data() { return last_sent_data_; }
+
+ private:
+  std::vector<DataCodec> recv_codecs_;
+  std::vector<DataCodec> send_codecs_;
+  SendDataParams last_sent_data_params_;
+  std::string last_sent_data_;
+  bool auto_bandwidth_;
+  int max_bps_;
+};
+
+// 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(talk_base::Thread* worker_thread) { 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; }
+
+  const std::vector<RtpHeaderExtension>& rtp_header_extensions() const {
+    return rtp_header_extensions_;
+  }
+
+ protected:
+  int loglevel_;
+  std::string logfilter_;
+  int options_;
+  // Flag used by optionsmessagehandler_unittest for checking whether any
+  // relevant setting has been updated.
+  // TODO(thaloun): Replace with explicit checks of before & after values.
+  bool options_changed_;
+  bool fail_create_channel_;
+  std::vector<RtpHeaderExtension> rtp_header_extensions_;
+};
+
+class FakeVoiceEngine : public FakeBaseEngine {
+ public:
+  FakeVoiceEngine()
+      : output_volume_(-1),
+        delay_offset_(0),
+        rx_processor_(NULL),
+        tx_processor_(NULL) {
+    // Add a fake audio codec. Note that the name must not be "" as there are
+    // sanity checks against that.
+    codecs_.push_back(AudioCodec(101, "fake_audio_codec", 0, 0, 1, 0));
+  }
+  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 SetDelayOffset(int offset) {
+    delay_offset_ = offset;
+    return true;
+  }
+
+  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_;
+  int delay_offset_;
+  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) {
+    // Add a fake video codec. Note that the name must not be "" as there are
+    // sanity checks against that.
+    codecs_.push_back(VideoCodec(0, "fake_video_codec", 0, 0, 0, 0));
+  }
+  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*/) { return true; }
+  VideoCapturer* GetVideoCapturer() const { return NULL; }
+  bool SetCapture(bool capture) {
+    capture_ = capture;
+    return true;
+  }
+  VideoFormat GetStartCaptureFormat() const {
+    return VideoFormat(640, 480, cricket::VideoFormat::FpsToInterval(30),
+                       FOURCC_I420);
+  }
+
+  sigslot::repeater2<VideoCapturer*, CaptureState> SignalCaptureStateChange;
+
+ 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 audio_delay_offset() const { return voice_.delay_offset_; }
+  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_; }
+  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 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);
+  }
+}
+
+class FakeDataEngine : public DataEngineInterface {
+ public:
+  FakeDataEngine() : last_channel_type_(DCT_NONE) {}
+
+  virtual DataMediaChannel* CreateChannel(DataChannelType data_channel_type) {
+    last_channel_type_ = data_channel_type;
+    FakeDataMediaChannel* ch = new FakeDataMediaChannel(this);
+    channels_.push_back(ch);
+    return ch;
+  }
+
+  FakeDataMediaChannel* GetChannel(size_t index) {
+    return (channels_.size() > index) ? channels_[index] : NULL;
+  }
+
+  void UnregisterChannel(DataMediaChannel* channel) {
+    channels_.erase(std::find(channels_.begin(), channels_.end(), channel));
+  }
+
+  virtual void SetDataCodecs(const std::vector<DataCodec>& data_codecs) {
+    data_codecs_ = data_codecs;
+  }
+
+  virtual const std::vector<DataCodec>& data_codecs() { return data_codecs_; }
+
+  DataChannelType last_channel_type() const { return last_channel_type_; }
+
+ private:
+  std::vector<FakeDataMediaChannel*> channels_;
+  std::vector<DataCodec> data_codecs_;
+  DataChannelType last_channel_type_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_FAKEMEDIAENGINE_H_
diff --git a/talk/media/base/fakemediaprocessor.h b/talk/media/base/fakemediaprocessor.h
new file mode 100644
index 0000000..a1f5ac9
--- /dev/null
+++ b/talk/media/base/fakemediaprocessor.h
@@ -0,0 +1,79 @@
+/*
+ * 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_MEDIA_BASE_FAKEMEDIAPROCESSOR_H_
+#define TALK_MEDIA_BASE_FAKEMEDIAPROCESSOR_H_
+
+#include "talk/media/base/videoprocessor.h"
+#include "talk/media/base/voiceprocessor.h"
+
+namespace cricket {
+
+class AudioFrame;
+
+class FakeMediaProcessor : public VoiceProcessor, public VideoProcessor {
+ public:
+  FakeMediaProcessor()
+      : voice_frame_count_(0),
+        video_frame_count_(0),
+        drop_frames_(false),
+        dropped_frame_count_(0) {
+  }
+  virtual ~FakeMediaProcessor() {}
+
+  virtual void OnFrame(uint32 ssrc,
+                       MediaProcessorDirection direction,
+                       AudioFrame* frame) {
+    ++voice_frame_count_;
+  }
+  virtual void OnFrame(uint32 ssrc, VideoFrame* frame_ptr, bool* drop_frame) {
+    ++video_frame_count_;
+    if (drop_frames_) {
+      *drop_frame = true;
+      ++dropped_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_; }
+
+  void set_drop_frames(bool b) { drop_frames_ = b; }
+  int dropped_frame_count() const { return dropped_frame_count_; }
+
+ private:
+  // TODO(janahan): make is a map so that we can multiple ssrcs
+  int voice_frame_count_;
+  int video_frame_count_;
+  bool drop_frames_;
+  int dropped_frame_count_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_FAKEMEDIAPROCESSOR_H_
diff --git a/talk/media/base/fakenetworkinterface.h b/talk/media/base/fakenetworkinterface.h
new file mode 100644
index 0000000..25016844
--- /dev/null
+++ b/talk/media/base/fakenetworkinterface.h
@@ -0,0 +1,249 @@
+/*
+ * 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_MEDIA_BASE_FAKENETWORKINTERFACE_H_
+#define TALK_MEDIA_BASE_FAKENETWORKINTERFACE_H_
+
+#include <vector>
+#include <map>
+
+#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/media/base/mediachannel.h"
+#include "talk/media/base/rtputils.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) {
+    talk_base::CritScope cs(&crit_);
+    conf_ = conf;
+    conf_sent_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 NumRtpBytes(uint32 ssrc) {
+    talk_base::CritScope cs(&crit_);
+    int bytes = 0;
+    GetNumRtpBytesAndPackets(ssrc, &bytes, NULL);
+    return bytes;
+  }
+
+  int NumRtpPackets() {
+    talk_base::CritScope cs(&crit_);
+    return rtp_packets_.size();
+  }
+
+  int NumRtpPackets(uint32 ssrc) {
+    talk_base::CritScope cs(&crit_);
+    int packets = 0;
+    GetNumRtpBytesAndPackets(ssrc, NULL, &packets);
+    return packets;
+  }
+
+  int NumSentSsrcs() {
+    talk_base::CritScope cs(&crit_);
+    return sent_ssrcs_.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]);
+  }
+
+  // Indicate that |n|'th packet for |ssrc| should be dropped.
+  void AddPacketDrop(uint32 ssrc, uint32 n) {
+    drop_map_[ssrc].insert(n);
+  }
+
+  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_);
+
+    uint32 cur_ssrc = 0;
+    if (!GetRtpSsrc(packet->data(), packet->length(), &cur_ssrc)) {
+      return false;
+    }
+    sent_ssrcs_[cur_ssrc]++;
+
+    // Check if we need to drop this packet.
+    std::map<uint32, std::set<uint32> >::iterator itr =
+      drop_map_.find(cur_ssrc);
+    if (itr != drop_map_.end() &&
+        itr->second.count(sent_ssrcs_[cur_ssrc]) > 0) {
+        // "Drop" the packet.
+        return true;
+    }
+
+    rtp_packets_.push_back(*packet);
+    if (conf_) {
+      talk_base::Buffer buffer_copy(*packet);
+      for (size_t i = 0; i < conf_sent_ssrcs_.size(); ++i) {
+        if (!SetRtpSsrc(buffer_copy.data(), buffer_copy.length(),
+                        conf_sent_ssrcs_[i])) {
+          return false;
+        }
+        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:
+  void GetNumRtpBytesAndPackets(uint32 ssrc, int* bytes, int* packets) {
+    if (bytes) {
+      *bytes = 0;
+    }
+    if (packets) {
+      *packets = 0;
+    }
+    uint32 cur_ssrc = 0;
+    for (size_t i = 0; i < rtp_packets_.size(); ++i) {
+      if (!GetRtpSsrc(rtp_packets_[i].data(),
+                      rtp_packets_[i].length(), &cur_ssrc)) {
+        return;
+      }
+      if (ssrc == cur_ssrc) {
+        if (bytes) {
+          *bytes += rtp_packets_[i].length();
+        }
+        if (packets) {
+          ++(*packets);
+        }
+      }
+    }
+  }
+
+  talk_base::Thread* thread_;
+  MediaChannel* dest_;
+  bool conf_;
+  // The ssrcs used in sending out packets in conference mode.
+  std::vector<uint32> conf_sent_ssrcs_;
+  // Map to track counts of packets that have been sent per ssrc.
+  // This includes packets that are dropped.
+  std::map<uint32, uint32> sent_ssrcs_;
+  // Map to track packet-number that needs to be dropped per ssrc.
+  std::map<uint32, std::set<uint32> > drop_map_;
+  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_MEDIA_BASE_FAKENETWORKINTERFACE_H_
diff --git a/talk/media/base/fakertp.h b/talk/media/base/fakertp.h
new file mode 100644
index 0000000..7c56cba
--- /dev/null
+++ b/talk/media/base/fakertp.h
@@ -0,0 +1,104 @@
+/*
+ * 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.
+ */
+
+// Fake RTP and RTCP packets to use in unit tests.
+
+#ifndef TALK_MEDIA_BASE_FAKERTP_H_
+#define TALK_MEDIA_BASE_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,
+};
+
+// PT= 101, SN=2, TS=3, SSRC = 4
+static const unsigned char kDataPacket[] = {
+  0x80, 0x65, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04,
+  0x00, 0x00, 0x00, 0x00,
+  0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+};
+
+#endif  // TALK_MEDIA_BASE_FAKERTP_H_
diff --git a/talk/media/base/fakevideocapturer.h b/talk/media/base/fakevideocapturer.h
new file mode 100644
index 0000000..4f51b66
--- /dev/null
+++ b/talk/media/base/fakevideocapturer.h
@@ -0,0 +1,154 @@
+/*
+ * 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_MEDIA_BASE_FAKEVIDEOCAPTURER_H_
+#define TALK_MEDIA_BASE_FAKEVIDEOCAPTURER_H_
+
+#include <string.h>
+
+#include <vector>
+
+#include "talk/base/timeutils.h"
+#include "talk/media/base/videocapturer.h"
+#include "talk/media/base/videocommon.h"
+#include "talk/media/base/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),
+        initial_unix_timestamp_(time(NULL) * talk_base::kNumNanosecsPerSec),
+        next_timestamp_(talk_base::kNumNanosecsPerMillisec),
+        is_screencast_(false) {
+    // Default supported formats. Use ResetSupportedFormats to over write.
+    std::vector<cricket::VideoFormat> formats;
+    formats.push_back(cricket::VideoFormat(1280, 720,
+        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(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 or ARGB.
+    // TODO(fbarchard): Extend SizeOf to take fourcc.
+    uint32 size = 0u;
+    if (fourcc == cricket::FOURCC_ARGB) {
+      size = width * 4 * height;
+    } else if (fourcc == cricket::FOURCC_I420) {
+      size = cricket::VideoFrame::SizeOf(width, height);
+    } else {
+      return false;  // Unsupported FOURCC.
+    }
+    if (size == 0u) {
+      return false;  // Width and/or Height were zero.
+    }
+
+    cricket::CapturedFrame frame;
+    frame.width = width;
+    frame.height = height;
+    frame.fourcc = fourcc;
+    frame.data_size = size;
+    frame.elapsed_time = next_timestamp_;
+    frame.time_stamp = initial_unix_timestamp_ + next_timestamp_;
+    next_timestamp_ += 33333333;  // 30 fps
+
+    talk_base::scoped_array<char> data(new char[size]);
+    frame.data = data.get();
+    // Copy something non-zero into the buffer so Validate wont complain that
+    // the frame is all duplicate.
+    memset(frame.data, 1, size / 2);
+    memset(reinterpret_cast<uint8*>(frame.data) + (size / 2), 2,
+         size - (size / 2));
+    memcpy(frame.data, reinterpret_cast<const uint8*>(&fourcc), 4);
+    // TODO(zhurunz): SignalFrameCaptured carry returned value to be able to
+    // capture results from downstream.
+    SignalFrameCaptured(this, &frame);
+    return true;
+  }
+
+  sigslot::signal1<FakeVideoCapturer*> SignalDestroyed;
+
+  virtual cricket::CaptureState Start(const cricket::VideoFormat& format) {
+    cricket::VideoFormat supported;
+    if (GetBestCaptureFormat(format, &supported)) {
+      SetCaptureFormat(&supported);
+    }
+    running_ = true;
+    SetCaptureState(cricket::CS_RUNNING);
+    return cricket::CS_RUNNING;
+  }
+  virtual void Stop() {
+    running_ = false;
+    SetCaptureFormat(NULL);
+    SetCaptureState(cricket::CS_STOPPED);
+  }
+  virtual bool IsRunning() { return running_; }
+  void SetScreencast(bool is_screencast) {
+    is_screencast_ = is_screencast;
+  }
+  virtual bool IsScreencast() const { return is_screencast_; }
+  bool GetPreferredFourccs(std::vector<uint32>* fourccs) {
+    fourccs->push_back(cricket::FOURCC_I420);
+    fourccs->push_back(cricket::FOURCC_MJPG);
+    return true;
+  }
+
+ private:
+  bool running_;
+  int64 initial_unix_timestamp_;
+  int64 next_timestamp_;
+  bool is_screencast_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_FAKEVIDEOCAPTURER_H_
diff --git a/talk/media/base/fakevideorenderer.h b/talk/media/base/fakevideorenderer.h
new file mode 100644
index 0000000..4000d5e
--- /dev/null
+++ b/talk/media/base/fakevideorenderer.h
@@ -0,0 +1,142 @@
+/*
+ * 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_MEDIA_BASE_FAKEVIDEORENDERER_H_
+#define TALK_MEDIA_BASE_FAKEVIDEORENDERER_H_
+
+#include "talk/base/sigslot.h"
+#include "talk/media/base/videoframe.h"
+#include "talk/media/base/videorenderer.h"
+
+namespace cricket {
+
+// Faked video renderer that has a callback for actions on rendering.
+class FakeVideoRenderer : public VideoRenderer {
+ public:
+  FakeVideoRenderer()
+      : errors_(0),
+        width_(0),
+        height_(0),
+        num_set_sizes_(0),
+        num_rendered_frames_(0),
+        black_frame_(false) {
+  }
+
+  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) {
+    // TODO(zhurunz) Check with VP8 team to see if we can remove this
+    // tolerance on Y values.
+    black_frame_ = CheckFrameColorYuv(6, 48, 128, 128, 128, 128, frame);
+    // Treat unexpected frame size as error.
+    if (!frame ||
+        frame->GetWidth() != static_cast<size_t>(width_) ||
+        frame->GetHeight() != static_cast<size_t>(height_)) {
+      ++errors_;
+      return false;
+    }
+    ++num_rendered_frames_;
+    SignalRenderFrame(frame);
+    return true;
+  }
+
+  int errors() const { return errors_; }
+  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_; }
+  bool black_frame() const { return black_frame_; }
+
+  sigslot::signal3<int, int, int> SignalSetSize;
+  sigslot::signal1<const VideoFrame*> SignalRenderFrame;
+
+ private:
+  static bool CheckFrameColorYuv(uint8 y_min, uint8 y_max,
+                                 uint8 u_min, uint8 u_max,
+                                 uint8 v_min, uint8 v_max,
+                                 const cricket::VideoFrame* frame) {
+    if (!frame) {
+      return false;
+    }
+    // Y
+    size_t y_width = frame->GetWidth();
+    size_t y_height = frame->GetHeight();
+    const uint8* y_plane = frame->GetYPlane();
+    const uint8* y_pos = y_plane;
+    int32 y_pitch = frame->GetYPitch();
+    for (size_t i = 0; i < y_height; ++i) {
+      for (size_t j = 0; j < y_width; ++j) {
+        uint8 y_value = *(y_pos + j);
+        if (y_value < y_min || y_value > y_max) {
+          return false;
+        }
+      }
+      y_pos += y_pitch;
+    }
+    // U and V
+    size_t chroma_width = frame->GetChromaWidth();
+    size_t chroma_height = frame->GetChromaHeight();
+    const uint8* u_plane = frame->GetUPlane();
+    const uint8* v_plane = frame->GetVPlane();
+    const uint8* u_pos = u_plane;
+    const uint8* v_pos = v_plane;
+    int32 u_pitch = frame->GetUPitch();
+    int32 v_pitch = frame->GetVPitch();
+    for (size_t i = 0; i < chroma_height; ++i) {
+      for (size_t j = 0; j < chroma_width; ++j) {
+        uint8 u_value = *(u_pos + j);
+        if (u_value < u_min || u_value > u_max) {
+          return false;
+        }
+        uint8 v_value = *(v_pos + j);
+        if (v_value < v_min || v_value > v_max) {
+          return false;
+        }
+      }
+      u_pos += u_pitch;
+      v_pos += v_pitch;
+    }
+    return true;
+  }
+
+  int errors_;
+  int width_;
+  int height_;
+  int num_set_sizes_;
+  int num_rendered_frames_;
+  bool black_frame_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_FAKEVIDEORENDERER_H_
diff --git a/talk/media/base/filemediaengine.cc b/talk/media/base/filemediaengine.cc
new file mode 100644
index 0000000..fe48311
--- /dev/null
+++ b/talk/media/base/filemediaengine.cc
@@ -0,0 +1,342 @@
+// 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/media/base/filemediaengine.h"
+
+#include <climits>
+
+#include "talk/base/buffer.h"
+#include "talk/base/event.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+#include "talk/media/base/rtpdump.h"
+#include "talk/media/base/rtputils.h"
+#include "talk/media/base/streamparams.h"
+
+namespace cricket {
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of FileMediaEngine.
+///////////////////////////////////////////////////////////////////////////
+int FileMediaEngine::GetCapabilities() {
+  int capabilities = 0;
+  if (!voice_input_filename_.empty()) {
+    capabilities |= AUDIO_SEND;
+  }
+  if (!voice_output_filename_.empty()) {
+    capabilities |= AUDIO_RECV;
+  }
+  if (!video_input_filename_.empty()) {
+    capabilities |= VIDEO_SEND;
+  }
+  if (!video_output_filename_.empty()) {
+    capabilities |= VIDEO_RECV;
+  }
+  return capabilities;
+}
+
+VoiceMediaChannel* FileMediaEngine::CreateChannel() {
+  talk_base::FileStream* input_file_stream = NULL;
+  talk_base::FileStream* output_file_stream = NULL;
+
+  if (voice_input_filename_.empty() && voice_output_filename_.empty())
+    return NULL;
+  if (!voice_input_filename_.empty()) {
+    input_file_stream = talk_base::Filesystem::OpenFile(
+        talk_base::Pathname(voice_input_filename_), "rb");
+    if (!input_file_stream) {
+      LOG(LS_ERROR) << "Not able to open the input audio stream file.";
+      return NULL;
+    }
+  }
+
+  if (!voice_output_filename_.empty()) {
+    output_file_stream = talk_base::Filesystem::OpenFile(
+        talk_base::Pathname(voice_output_filename_), "wb");
+    if (!output_file_stream) {
+      delete input_file_stream;
+      LOG(LS_ERROR) << "Not able to open the output audio stream file.";
+      return NULL;
+    }
+  }
+
+  return new FileVoiceChannel(input_file_stream, output_file_stream);
+}
+
+VideoMediaChannel* FileMediaEngine::CreateVideoChannel(
+    VoiceMediaChannel* voice_ch) {
+  talk_base::FileStream* input_file_stream = NULL;
+  talk_base::FileStream* output_file_stream = NULL;
+
+  if (video_input_filename_.empty() && video_output_filename_.empty())
+      return NULL;
+
+  if (!video_input_filename_.empty()) {
+    input_file_stream = talk_base::Filesystem::OpenFile(
+        talk_base::Pathname(video_input_filename_), "rb");
+    if (!input_file_stream) {
+      LOG(LS_ERROR) << "Not able to open the input video stream file.";
+      return NULL;
+    }
+  }
+
+  if (!video_output_filename_.empty()) {
+    output_file_stream = talk_base::Filesystem::OpenFile(
+        talk_base::Pathname(video_output_filename_), "wb");
+    if (!output_file_stream) {
+      delete input_file_stream;
+      LOG(LS_ERROR) << "Not able to open the output video stream file.";
+      return NULL;
+    }
+  }
+
+  return new FileVideoChannel(input_file_stream, output_file_stream);
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Definition of RtpSenderReceiver.
+///////////////////////////////////////////////////////////////////////////
+class RtpSenderReceiver
+    : public talk_base::Thread, public talk_base::MessageHandler {
+ public:
+  RtpSenderReceiver(MediaChannel* channel,
+                    talk_base::StreamInterface* input_file_stream,
+                    talk_base::StreamInterface* output_file_stream);
+
+  // Called by media channel. Context: media channel thread.
+  bool SetSend(bool send);
+  void SetSendSsrc(uint32 ssrc);
+  void OnPacketReceived(talk_base::Buffer* packet);
+
+  // Override virtual method of parent MessageHandler. Context: Worker Thread.
+  virtual void OnMessage(talk_base::Message* pmsg);
+
+ private:
+  // Read the next RTP dump packet, whose RTP SSRC is the same as first_ssrc_.
+  // Return true if successful.
+  bool ReadNextPacket(RtpDumpPacket* packet);
+  // Send a RTP packet to the network. The input parameter data points to the
+  // start of the RTP packet and len is the packet size. Return true if the sent
+  // size is equal to len.
+  bool SendRtpPacket(const void* data, size_t len);
+
+  MediaChannel* media_channel_;
+  talk_base::scoped_ptr<talk_base::StreamInterface> input_stream_;
+  talk_base::scoped_ptr<talk_base::StreamInterface> output_stream_;
+  talk_base::scoped_ptr<RtpDumpLoopReader> rtp_dump_reader_;
+  talk_base::scoped_ptr<RtpDumpWriter> rtp_dump_writer_;
+  // RTP dump packet read from the input stream.
+  RtpDumpPacket rtp_dump_packet_;
+  uint32 start_send_time_;
+  bool sending_;
+  bool first_packet_;
+  uint32 first_ssrc_;
+
+  DISALLOW_COPY_AND_ASSIGN(RtpSenderReceiver);
+};
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of RtpSenderReceiver.
+///////////////////////////////////////////////////////////////////////////
+RtpSenderReceiver::RtpSenderReceiver(
+    MediaChannel* channel,
+    talk_base::StreamInterface* input_file_stream,
+    talk_base::StreamInterface* output_file_stream)
+    : media_channel_(channel),
+      sending_(false),
+      first_packet_(true) {
+  input_stream_.reset(input_file_stream);
+  if (input_stream_) {
+    rtp_dump_reader_.reset(new RtpDumpLoopReader(input_stream_.get()));
+    // Start the sender thread, which reads rtp dump records, waits based on
+    // the record timestamps, and sends the RTP packets to the network.
+    Thread::Start();
+  }
+
+  // Create a rtp dump writer for the output RTP dump stream.
+  output_stream_.reset(output_file_stream);
+  if (output_stream_) {
+    rtp_dump_writer_.reset(new RtpDumpWriter(output_stream_.get()));
+  }
+}
+
+bool RtpSenderReceiver::SetSend(bool send) {
+  bool was_sending = sending_;
+  sending_ = send;
+  if (!was_sending && sending_) {
+    PostDelayed(0, this);  // Wake up the send thread.
+    start_send_time_ = talk_base::Time();
+  }
+  return true;
+}
+
+void RtpSenderReceiver::SetSendSsrc(uint32 ssrc) {
+  if (rtp_dump_reader_) {
+    rtp_dump_reader_->SetSsrc(ssrc);
+  }
+}
+
+void RtpSenderReceiver::OnPacketReceived(talk_base::Buffer* packet) {
+  if (rtp_dump_writer_) {
+    rtp_dump_writer_->WriteRtpPacket(packet->data(), packet->length());
+  }
+}
+
+void RtpSenderReceiver::OnMessage(talk_base::Message* pmsg) {
+  if (!sending_) {
+    // If the sender thread is not sending, ignore this message. The thread goes
+    // to sleep until SetSend(true) wakes it up.
+    return;
+  }
+
+  if (!first_packet_) {
+    // Send the previously read packet.
+    SendRtpPacket(&rtp_dump_packet_.data[0], rtp_dump_packet_.data.size());
+  }
+
+  if (ReadNextPacket(&rtp_dump_packet_)) {
+    int wait = talk_base::TimeUntil(
+        start_send_time_ + rtp_dump_packet_.elapsed_time);
+    wait = talk_base::_max(0, wait);
+    PostDelayed(wait, this);
+  } else {
+    Quit();
+  }
+}
+
+bool RtpSenderReceiver::ReadNextPacket(RtpDumpPacket* packet) {
+  while (talk_base::SR_SUCCESS == rtp_dump_reader_->ReadPacket(packet)) {
+    uint32 ssrc;
+    if (!packet->GetRtpSsrc(&ssrc)) {
+      return false;
+    }
+    if (first_packet_) {
+      first_packet_ = false;
+      first_ssrc_ = ssrc;
+    }
+    if (ssrc == first_ssrc_) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool RtpSenderReceiver::SendRtpPacket(const void* data, size_t len) {
+  if (!media_channel_ || !media_channel_->network_interface()) {
+    return false;
+  }
+
+  talk_base::Buffer packet(data, len, kMaxRtpPacketLen);
+  return media_channel_->network_interface()->SendPacket(&packet);
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of FileVoiceChannel.
+///////////////////////////////////////////////////////////////////////////
+FileVoiceChannel::FileVoiceChannel(
+    talk_base::StreamInterface* input_file_stream,
+    talk_base::StreamInterface* output_file_stream)
+    : send_ssrc_(0),
+      rtp_sender_receiver_(new RtpSenderReceiver(this, input_file_stream,
+                                                 output_file_stream)) {}
+
+FileVoiceChannel::~FileVoiceChannel() {}
+
+bool FileVoiceChannel::SetSendCodecs(const std::vector<AudioCodec>& codecs) {
+  // TODO(whyuan): Check the format of RTP dump input.
+  return true;
+}
+
+bool FileVoiceChannel::SetSend(SendFlags flag) {
+  return rtp_sender_receiver_->SetSend(flag != SEND_NOTHING);
+}
+
+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) {
+  rtp_sender_receiver_->OnPacketReceived(packet);
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of FileVideoChannel.
+///////////////////////////////////////////////////////////////////////////
+FileVideoChannel::FileVideoChannel(
+    talk_base::StreamInterface* input_file_stream,
+    talk_base::StreamInterface* output_file_stream)
+    : send_ssrc_(0),
+      rtp_sender_receiver_(new RtpSenderReceiver(this, input_file_stream,
+                                                 output_file_stream)) {}
+
+FileVideoChannel::~FileVideoChannel() {}
+
+bool FileVideoChannel::SetSendCodecs(const std::vector<VideoCodec>& codecs) {
+  // TODO(whyuan): Check the format of RTP dump input.
+  return true;
+}
+
+bool FileVideoChannel::SetSend(bool send) {
+  return rtp_sender_receiver_->SetSend(send);
+}
+
+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) {
+  rtp_sender_receiver_->OnPacketReceived(packet);
+}
+
+}  // namespace cricket
diff --git a/talk/media/base/filemediaengine.h b/talk/media/base/filemediaengine.h
new file mode 100644
index 0000000..70335de
--- /dev/null
+++ b/talk/media/base/filemediaengine.h
@@ -0,0 +1,316 @@
+// 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_MEDIA_BASE_FILEMEDIAENGINE_H_
+#define TALK_MEDIA_BASE_FILEMEDIAENGINE_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stream.h"
+#include "talk/media/base/codec.h"
+#include "talk/media/base/mediachannel.h"
+#include "talk/media/base/mediaengine.h"
+
+namespace talk_base {
+class StreamInterface;
+}
+
+namespace cricket {
+
+// A media engine contains a capturer, an encoder, and a sender in the sender
+// side and a receiver, a decoder, and a renderer in the receiver side.
+// FileMediaEngine simulates the capturer and the encoder via an input RTP dump
+// stream and simulates the decoder and the renderer via an output RTP dump
+// stream. Depending on the parameters of the constructor, FileMediaEngine can
+// act as file voice engine, file video engine, or both. Currently, we use
+// only the RTP dump packets. TODO(whyuan): Enable RTCP packets.
+class FileMediaEngine : public MediaEngineInterface {
+ public:
+  FileMediaEngine() {}
+  virtual ~FileMediaEngine() {}
+
+  // Set the file name of the input or output RTP dump for voice or video.
+  // Should be called before the channel is created.
+  void set_voice_input_filename(const std::string& filename) {
+    voice_input_filename_ = filename;
+  }
+  void set_voice_output_filename(const std::string& filename) {
+    voice_output_filename_ = filename;
+  }
+  void set_video_input_filename(const std::string& filename) {
+    video_input_filename_ = filename;
+  }
+  void set_video_output_filename(const std::string& filename) {
+    video_output_filename_ = filename;
+  }
+
+  // Should be called before codecs() and video_codecs() are called. We need to
+  // set the voice and video codecs; otherwise, Jingle initiation will fail.
+  void set_voice_codecs(const std::vector<AudioCodec>& codecs) {
+    voice_codecs_ = codecs;
+  }
+  void set_video_codecs(const std::vector<VideoCodec>& codecs) {
+    video_codecs_ = codecs;
+  }
+
+  // Implement pure virtual methods of MediaEngine.
+  virtual bool Init(talk_base::Thread* worker_thread) {
+    return true;
+  }
+  virtual void Terminate() {}
+  virtual int GetCapabilities();
+  virtual VoiceMediaChannel* CreateChannel();
+  virtual VideoMediaChannel* CreateVideoChannel(VoiceMediaChannel* voice_ch);
+  virtual SoundclipMedia* CreateSoundclip() { return NULL; }
+  virtual bool SetAudioOptions(int options) { return true; }
+  virtual bool SetVideoOptions(int options) { return true; }
+  virtual bool SetAudioDelayOffset(int offset) { return true; }
+  virtual bool SetDefaultVideoEncoderConfig(const VideoEncoderConfig& config) {
+    return true;
+  }
+  virtual bool SetSoundDevices(const Device* in_dev, const Device* out_dev) {
+    return true;
+  }
+  virtual bool SetVideoCaptureDevice(const Device* cam_device) { return true; }
+  virtual bool SetVideoCapturer(VideoCapturer* /*capturer*/) {
+    return true;
+  }
+  virtual VideoCapturer* GetVideoCapturer() const {
+    return NULL;
+  }
+  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; }
+  virtual bool SetLocalRenderer(VideoRenderer* renderer) { return true; }
+  // TODO(whyuan): control channel send?
+  virtual bool SetVideoCapture(bool capture) { return true; }
+  virtual const std::vector<AudioCodec>& audio_codecs() {
+    return voice_codecs_;
+  }
+  virtual const std::vector<VideoCodec>& video_codecs() {
+    return video_codecs_;
+  }
+  virtual const std::vector<RtpHeaderExtension>& audio_rtp_header_extensions() {
+    return audio_rtp_header_extensions_;
+  }
+  virtual const std::vector<RtpHeaderExtension>& video_rtp_header_extensions() {
+    return video_rtp_header_extensions_;
+  }
+
+  virtual bool FindAudioCodec(const AudioCodec& codec) { return true; }
+  virtual bool FindVideoCodec(const VideoCodec& codec) { return true; }
+  virtual void SetVoiceLogging(int min_sev, const char* filter) {}
+  virtual void SetVideoLogging(int min_sev, const char* filter) {}
+
+  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;
+  }
+  VideoFormat GetStartCaptureFormat() const {
+    return VideoFormat();
+  }
+
+  virtual sigslot::repeater2<VideoCapturer*, CaptureState>&
+      SignalVideoCaptureStateChange() {
+    return signal_state_change_;
+  }
+
+ private:
+  std::string voice_input_filename_;
+  std::string voice_output_filename_;
+  std::string video_input_filename_;
+  std::string video_output_filename_;
+  std::vector<AudioCodec> voice_codecs_;
+  std::vector<VideoCodec> video_codecs_;
+  std::vector<RtpHeaderExtension> audio_rtp_header_extensions_;
+  std::vector<RtpHeaderExtension> video_rtp_header_extensions_;
+  sigslot::repeater2<VideoCapturer*, CaptureState>
+     signal_state_change_;
+
+  DISALLOW_COPY_AND_ASSIGN(FileMediaEngine);
+};
+
+class RtpSenderReceiver;  // Forward declaration. Defined in the .cc file.
+
+class FileVoiceChannel : public VoiceMediaChannel {
+ public:
+  FileVoiceChannel(talk_base::StreamInterface* input_file_stream,
+      talk_base::StreamInterface* output_file_stream);
+  virtual ~FileVoiceChannel();
+
+  // Implement pure virtual methods of VoiceMediaChannel.
+  virtual bool SetRecvCodecs(const std::vector<AudioCodec>& codecs) {
+    return true;
+  }
+  virtual bool SetSendCodecs(const std::vector<AudioCodec>& codecs);
+  virtual bool SetRecvRtpHeaderExtensions(
+      const std::vector<RtpHeaderExtension>& extensions) {
+    return true;
+  }
+  virtual bool SetSendRtpHeaderExtensions(
+      const std::vector<RtpHeaderExtension>& extensions) {
+    return true;
+  }
+  virtual bool SetPlayout(bool playout) { return true; }
+  virtual bool SetSend(SendFlags flag);
+  virtual bool SetRenderer(uint32 ssrc, AudioRenderer* renderer) {
+    return false;
+  }
+  virtual bool GetActiveStreams(AudioInfo::StreamList* actives) { return true; }
+  virtual int GetOutputLevel() { return 0; }
+  virtual int GetTimeSinceLastTyping() { return -1; }
+  virtual void SetTypingDetectionParameters(int time_window,
+    int cost_per_typing, int reporting_threshold, int penalty_decay,
+    int type_event_delay) {}
+
+  virtual bool SetOutputScaling(uint32 ssrc, double left, double right) {
+    return false;
+  }
+  virtual bool GetOutputScaling(uint32 ssrc, double* left, double* right) {
+    return false;
+  }
+  virtual bool SetRingbackTone(const char* buf, int len) { return true; }
+  virtual bool PlayRingbackTone(uint32 ssrc, bool play, bool loop) {
+    return true;
+  }
+  virtual bool InsertDtmf(uint32 ssrc, int event, int duration, int flags) {
+    return false;
+  }
+  virtual bool GetStats(VoiceMediaInfo* info) { return true; }
+
+  // Implement pure virtual methods of MediaChannel.
+  virtual void OnPacketReceived(talk_base::Buffer* packet);
+  virtual void OnRtcpReceived(talk_base::Buffer* packet) {}
+  virtual void OnReadyToSend(bool ready) {}
+  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 MuteStream(uint32 ssrc, bool on) { return false; }
+  virtual bool SetSendBandwidth(bool autobw, int bps) { return true; }
+  virtual bool SetOptions(const AudioOptions& options) {
+    options_ = options;
+    return true;
+  }
+  virtual bool GetOptions(AudioOptions* options) const {
+    *options = options_;
+    return true;
+  }
+
+ private:
+  uint32 send_ssrc_;
+  talk_base::scoped_ptr<RtpSenderReceiver> rtp_sender_receiver_;
+  AudioOptions options_;
+
+  DISALLOW_COPY_AND_ASSIGN(FileVoiceChannel);
+};
+
+class FileVideoChannel : public VideoMediaChannel {
+ public:
+  FileVideoChannel(talk_base::StreamInterface* input_file_stream,
+      talk_base::StreamInterface* output_file_stream);
+  virtual ~FileVideoChannel();
+
+  // Implement pure virtual methods of VideoMediaChannel.
+  virtual bool SetRecvCodecs(const std::vector<VideoCodec>& codecs) {
+    return true;
+  }
+  virtual bool SetSendCodecs(const std::vector<VideoCodec>& codecs);
+  virtual bool GetSendCodec(VideoCodec* send_codec) {
+    *send_codec = VideoCodec();
+    return true;
+  }
+  virtual bool SetSendStreamFormat(uint32 ssrc, const VideoFormat& format) {
+    return true;
+  }
+  virtual bool SetRecvRtpHeaderExtensions(
+      const std::vector<RtpHeaderExtension>& extensions) {
+    return true;
+  }
+  virtual bool SetSendRtpHeaderExtensions(
+      const std::vector<RtpHeaderExtension>& extensions) {
+    return true;
+  }
+  virtual bool SetRender(bool render) { return true; }
+  virtual bool SetSend(bool send);
+  virtual bool SetRenderer(uint32 ssrc, VideoRenderer* renderer) {
+    return true;
+  }
+  virtual bool SetCapturer(uint32 ssrc, VideoCapturer* capturer) {
+    return false;
+  }
+  virtual bool GetStats(VideoMediaInfo* info) { return true; }
+  virtual bool SendIntraFrame() { return false; }
+  virtual bool RequestIntraFrame() { return false; }
+
+  // Implement pure virtual methods of MediaChannel.
+  virtual void OnPacketReceived(talk_base::Buffer* packet);
+  virtual void OnRtcpReceived(talk_base::Buffer* packet) {}
+  virtual void OnReadyToSend(bool ready) {}
+  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 MuteStream(uint32 ssrc, bool on) { return false; }
+  virtual bool SetSendBandwidth(bool autobw, int bps) { return true; }
+  virtual bool SetOptions(const VideoOptions& options) {
+    options_ = options;
+    return true;
+  }
+  virtual bool GetOptions(VideoOptions* options) const {
+    *options = options_;
+    return true;
+  }
+  virtual void UpdateAspectRatio(int ratio_w, int ratio_h) {}
+
+ private:
+  uint32 send_ssrc_;
+  talk_base::scoped_ptr<RtpSenderReceiver> rtp_sender_receiver_;
+  VideoOptions options_;
+
+  DISALLOW_COPY_AND_ASSIGN(FileVideoChannel);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_FILEMEDIAENGINE_H_
diff --git a/talk/media/base/filemediaengine_unittest.cc b/talk/media/base/filemediaengine_unittest.cc
new file mode 100644
index 0000000..a2c91a1
--- /dev/null
+++ b/talk/media/base/filemediaengine_unittest.cc
@@ -0,0 +1,463 @@
+/*
+ * 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 <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/media/base/filemediaengine.h"
+#include "talk/media/base/rtpdump.h"
+#include "talk/media/base/streamparams.h"
+#include "talk/media/base/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(talk_base::Thread::Current()));
+  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_TRUE(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/media/base/hybriddataengine.h b/talk/media/base/hybriddataengine.h
new file mode 100644
index 0000000..bece492
--- /dev/null
+++ b/talk/media/base/hybriddataengine.h
@@ -0,0 +1,76 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc, and Robin Seggelmann
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_MEDIA_SCTP_HYBRIDDATAENGINE_H_
+#define TALK_MEDIA_SCTP_HYBRIDDATAENGINE_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/scoped_ptr.h"
+#include "talk/media/base/codec.h"
+#include "talk/media/base/mediachannel.h"
+#include "talk/media/base/mediaengine.h"
+
+namespace cricket {
+
+class HybridDataEngine : public DataEngineInterface {
+ public:
+  // Takes ownership.
+  HybridDataEngine(DataEngineInterface* first,
+                   DataEngineInterface* second)
+      : first_(first),
+        second_(second) {
+    codecs_ = first_->data_codecs();
+    codecs_.insert(
+        codecs_.end(),
+        second_->data_codecs().begin(),
+        second_->data_codecs().end());
+  }
+
+  virtual DataMediaChannel* CreateChannel(DataChannelType data_channel_type) {
+    DataMediaChannel* channel = NULL;
+    if (first_) {
+      channel = first_->CreateChannel(data_channel_type);
+    }
+    if (!channel && second_) {
+      channel = second_->CreateChannel(data_channel_type);
+    }
+    return channel;
+  }
+
+  virtual const std::vector<DataCodec>& data_codecs() { return codecs_; }
+
+ private:
+  talk_base::scoped_ptr<DataEngineInterface> first_;
+  talk_base::scoped_ptr<DataEngineInterface> second_;
+  std::vector<DataCodec> codecs_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_SCTP_HYBRIDDATAENGINE_H_
diff --git a/talk/media/base/hybridvideoengine.cc b/talk/media/base/hybridvideoengine.cc
new file mode 100644
index 0000000..a405f8d
--- /dev/null
+++ b/talk/media/base/hybridvideoengine.cc
@@ -0,0 +1,350 @@
+/*
+ * 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/media/base/hybridvideoengine.h"
+
+#include "talk/base/logging.h"
+
+namespace cricket {
+
+HybridVideoMediaChannel::HybridVideoMediaChannel(
+    HybridVideoEngineInterface* engine,
+    VideoMediaChannel* channel1,
+    VideoMediaChannel* channel2)
+    : engine_(engine),
+      channel1_(channel1),
+      channel2_(channel2),
+      active_channel_(NULL),
+      sending_(false) {
+}
+
+HybridVideoMediaChannel::~HybridVideoMediaChannel() {
+}
+
+void HybridVideoMediaChannel::SetInterface(NetworkInterface* iface) {
+  if (channel1_) {
+    channel1_->SetInterface(iface);
+  }
+  if (channel2_) {
+    channel2_->SetInterface(iface);
+  }
+}
+
+bool HybridVideoMediaChannel::SetOptions(const VideoOptions &options) {
+  bool ret = true;
+  if (channel1_) {
+    ret = channel1_->SetOptions(options);
+  }
+  if (channel2_ && ret) {
+    ret = channel2_->SetOptions(options);
+  }
+  return ret;
+}
+
+bool HybridVideoMediaChannel::GetOptions(VideoOptions *options) const {
+  if (active_channel_) {
+    return active_channel_->GetOptions(options);
+  }
+  if (channel1_) {
+    return channel1_->GetOptions(options);
+  }
+  if (channel2_) {
+    return channel2_->GetOptions(options);
+  }
+  return false;
+}
+
+bool HybridVideoMediaChannel::SetRecvCodecs(
+    const std::vector<VideoCodec>& codecs) {
+  // Only give each channel the codecs it knows about.
+  bool ret = true;
+  std::vector<VideoCodec> codecs1, codecs2;
+  SplitCodecs(codecs, &codecs1, &codecs2);
+  if (channel1_) {
+    ret = channel1_->SetRecvCodecs(codecs1);
+  }
+  if (channel2_ && ret) {
+    ret = channel2_->SetRecvCodecs(codecs2);
+  }
+  return ret;
+}
+
+bool HybridVideoMediaChannel::SetRecvRtpHeaderExtensions(
+    const std::vector<RtpHeaderExtension>& extensions) {
+  bool ret = true;
+  if (channel1_) {
+    ret = channel1_->SetRecvRtpHeaderExtensions(extensions);
+  }
+  if (channel2_ && ret) {
+    ret = channel2_->SetRecvRtpHeaderExtensions(extensions);
+  }
+  return ret;
+}
+
+bool HybridVideoMediaChannel::SetRenderer(uint32 ssrc,
+                                          VideoRenderer* renderer) {
+  bool ret = true;
+  if (channel1_) {
+    ret = channel1_->SetRenderer(ssrc, renderer);
+  }
+  if (channel2_ && ret) {
+    ret = channel2_->SetRenderer(ssrc, renderer);
+  }
+  return ret;
+}
+
+bool HybridVideoMediaChannel::SetRender(bool render) {
+  bool ret = true;
+  if (channel1_) {
+    ret = channel1_->SetRender(render);
+  }
+  if (channel2_ && ret) {
+    ret = channel2_->SetRender(render);
+  }
+  return ret;
+}
+
+bool HybridVideoMediaChannel::MuteStream(uint32 ssrc, bool muted) {
+  bool ret = true;
+  if (channel1_) {
+    ret = channel1_->MuteStream(ssrc, muted);
+  }
+  if (channel2_ && ret) {
+    ret = channel2_->MuteStream(ssrc, muted);
+  }
+  return ret;
+}
+
+bool HybridVideoMediaChannel::SetSendCodecs(
+    const std::vector<VideoCodec>& codecs) {
+  // Use the input to this function to decide what impl we're going to use.
+  if (!active_channel_ && !SelectActiveChannel(codecs)) {
+    LOG(LS_WARNING) << "Failed to select active channel";
+    return false;
+  }
+  // Only give the active channel the codecs it knows about.
+  std::vector<VideoCodec> codecs1, codecs2;
+  SplitCodecs(codecs, &codecs1, &codecs2);
+  const std::vector<VideoCodec>& codecs_to_set =
+      (active_channel_ == channel1_.get()) ? codecs1 : codecs2;
+  bool return_value = active_channel_->SetSendCodecs(codecs_to_set);
+  if (!return_value) {
+    return false;
+  }
+  VideoCodec send_codec;
+  return_value = active_channel_->GetSendCodec(&send_codec);
+  if (!return_value) {
+    return false;
+  }
+  engine_->OnNewSendResolution(send_codec.width, send_codec.height);
+  active_channel_->UpdateAspectRatio(send_codec.width, send_codec.height);
+  return true;
+}
+
+bool HybridVideoMediaChannel::GetSendCodec(VideoCodec* send_codec) {
+  if (!active_channel_) {
+    return false;
+  }
+  return active_channel_->GetSendCodec(send_codec);
+}
+
+bool HybridVideoMediaChannel::SetSendStreamFormat(uint32 ssrc,
+                                                  const VideoFormat& format) {
+  return active_channel_ && active_channel_->SetSendStreamFormat(ssrc, format);
+}
+
+bool HybridVideoMediaChannel::SetSendRtpHeaderExtensions(
+    const std::vector<RtpHeaderExtension>& extensions) {
+  return active_channel_ &&
+      active_channel_->SetSendRtpHeaderExtensions(extensions);
+}
+
+bool HybridVideoMediaChannel::SetSendBandwidth(bool autobw, int bps) {
+  return active_channel_ &&
+      active_channel_->SetSendBandwidth(autobw, bps);
+}
+
+bool HybridVideoMediaChannel::SetSend(bool send) {
+  if (send == sending()) {
+    return true;  // no action required if already set.
+  }
+
+  bool ret = active_channel_ &&
+      active_channel_->SetSend(send);
+
+  // Returns error and don't connect the signal if starting up.
+  // Disconnects the signal anyway if shutting down.
+  if (ret || !send) {
+    // TODO(juberti): Remove this hack that connects the WebRTC channel
+    // to the capturer.
+    if (active_channel_ == channel1_.get()) {
+      engine_->OnSendChange1(channel1_.get(), send);
+    } else {
+      engine_->OnSendChange2(channel2_.get(), send);
+    }
+    // If succeeded, remember the state as is.
+    // If failed to open, sending_ should be false.
+    // If failed to stop, sending_ should also be false, as we disconnect the
+    // capture anyway.
+    // The failure on SetSend(false) is a known issue in webrtc.
+    sending_ = send;
+  }
+  return ret;
+}
+
+bool HybridVideoMediaChannel::SetCapturer(uint32 ssrc,
+                                          VideoCapturer* capturer) {
+  bool ret = true;
+  if (channel1_.get()) {
+    ret = channel1_->SetCapturer(ssrc, capturer);
+  }
+  if (channel2_.get() && ret) {
+    ret = channel2_->SetCapturer(ssrc, capturer);
+  }
+  return ret;
+}
+
+bool HybridVideoMediaChannel::AddSendStream(const StreamParams& sp) {
+  bool ret = true;
+  if (channel1_) {
+    ret = channel1_->AddSendStream(sp);
+  }
+  if (channel2_ && ret) {
+    ret = channel2_->AddSendStream(sp);
+  }
+  return ret;
+}
+
+bool HybridVideoMediaChannel::RemoveSendStream(uint32 ssrc) {
+  bool ret = true;
+  if (channel1_) {
+    ret = channel1_->RemoveSendStream(ssrc);
+  }
+  if (channel2_ && ret) {
+    ret = channel2_->RemoveSendStream(ssrc);
+  }
+  return ret;
+}
+
+bool HybridVideoMediaChannel::AddRecvStream(const StreamParams& sp) {
+  return active_channel_ &&
+      active_channel_->AddRecvStream(sp);
+}
+
+bool HybridVideoMediaChannel::RemoveRecvStream(uint32 ssrc) {
+  return active_channel_ &&
+      active_channel_->RemoveRecvStream(ssrc);
+}
+
+bool HybridVideoMediaChannel::SendIntraFrame() {
+  return active_channel_ &&
+      active_channel_->SendIntraFrame();
+}
+
+bool HybridVideoMediaChannel::RequestIntraFrame() {
+  return active_channel_ &&
+      active_channel_->RequestIntraFrame();
+}
+
+bool HybridVideoMediaChannel::GetStats(VideoMediaInfo* info) {
+  // TODO(juberti): Ensure that returning no stats until SetSendCodecs is OK.
+  return active_channel_ &&
+      active_channel_->GetStats(info);
+}
+
+void HybridVideoMediaChannel::OnPacketReceived(talk_base::Buffer* packet) {
+  // Eat packets until we have an active channel;
+  if (active_channel_) {
+    active_channel_->OnPacketReceived(packet);
+  } else {
+    LOG(LS_INFO) << "HybridVideoChannel: Eating early RTP packet";
+  }
+}
+
+void HybridVideoMediaChannel::OnRtcpReceived(talk_base::Buffer* packet) {
+  // Eat packets until we have an active channel;
+  if (active_channel_) {
+    active_channel_->OnRtcpReceived(packet);
+  } else {
+    LOG(LS_INFO) << "HybridVideoChannel: Eating early RTCP packet";
+  }
+}
+
+void HybridVideoMediaChannel::OnReadyToSend(bool ready) {
+  if (channel1_) {
+    channel1_->OnReadyToSend(ready);
+  }
+  if (channel2_) {
+    channel2_->OnReadyToSend(ready);
+  }
+}
+
+void HybridVideoMediaChannel::UpdateAspectRatio(int ratio_w, int ratio_h) {
+  if (active_channel_) active_channel_->UpdateAspectRatio(ratio_w, ratio_h);
+}
+
+bool HybridVideoMediaChannel::SelectActiveChannel(
+    const std::vector<VideoCodec>& codecs) {
+  if (!active_channel_ && !codecs.empty()) {
+    if (engine_->HasCodec1(codecs[0])) {
+      channel2_.reset();
+      active_channel_ = channel1_.get();
+    } else if (engine_->HasCodec2(codecs[0])) {
+      channel1_.reset();
+      active_channel_ = channel2_.get();
+    }
+  }
+  if (NULL == active_channel_) {
+    return false;
+  }
+  // Connect signals from the active channel.
+  active_channel_->SignalMediaError.connect(
+      this,
+      &HybridVideoMediaChannel::OnMediaError);
+  return true;
+}
+
+void HybridVideoMediaChannel::SplitCodecs(
+    const std::vector<VideoCodec>& codecs,
+    std::vector<VideoCodec>* codecs1, std::vector<VideoCodec>* codecs2) {
+  codecs1->clear();
+  codecs2->clear();
+  for (size_t i = 0; i < codecs.size(); ++i) {
+    if (engine_->HasCodec1(codecs[i])) {
+      codecs1->push_back(codecs[i]);
+    }
+    if (engine_->HasCodec2(codecs[i])) {
+      codecs2->push_back(codecs[i]);
+    }
+  }
+}
+
+void HybridVideoMediaChannel::OnMediaError(uint32 ssrc, Error error) {
+  SignalMediaError(ssrc, error);
+}
+
+}  // namespace cricket
diff --git a/talk/media/base/hybridvideoengine.h b/talk/media/base/hybridvideoengine.h
new file mode 100644
index 0000000..1e43a30b7
--- /dev/null
+++ b/talk/media/base/hybridvideoengine.h
@@ -0,0 +1,275 @@
+/*
+ * 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_MEDIA_BASE_HYBRIDVIDEOENGINE_H_
+#define TALK_MEDIA_BASE_HYBRIDVIDEOENGINE_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/logging.h"
+#include "talk/base/sigslotrepeater.h"
+#include "talk/media/base/codec.h"
+#include "talk/media/base/mediachannel.h"
+#include "talk/media/base/videocapturer.h"
+#include "talk/media/base/videocommon.h"
+
+namespace cricket {
+
+struct Device;
+struct VideoFormat;
+class HybridVideoEngineInterface;
+class VideoCapturer;
+class VideoFrame;
+class VideoRenderer;
+
+// HybridVideoMediaChannels work with a HybridVideoEngine to combine
+// two unrelated VideoMediaChannel implementations into a single class.
+class HybridVideoMediaChannel : public VideoMediaChannel {
+ public:
+  HybridVideoMediaChannel(HybridVideoEngineInterface* engine,
+                          VideoMediaChannel* channel1,
+                          VideoMediaChannel* channel2);
+  virtual ~HybridVideoMediaChannel();
+
+  // VideoMediaChannel methods
+  virtual void SetInterface(NetworkInterface* iface);
+  virtual bool SetOptions(const VideoOptions& options);
+  virtual bool GetOptions(VideoOptions* options) const;
+  virtual bool AddSendStream(const StreamParams& sp);
+  virtual bool RemoveSendStream(uint32 ssrc);
+  virtual bool SetRenderer(uint32 ssrc, VideoRenderer* renderer);
+  virtual bool SetRender(bool render);
+  virtual bool MuteStream(uint32 ssrc, bool muted);
+
+  virtual bool SetRecvCodecs(const std::vector<VideoCodec>& codecs);
+  virtual bool SetRecvRtpHeaderExtensions(
+      const std::vector<RtpHeaderExtension>& extensions);
+
+  virtual bool SetSendCodecs(const std::vector<VideoCodec>& codecs);
+  virtual bool GetSendCodec(VideoCodec* codec);
+  virtual bool SetSendStreamFormat(uint32 ssrc, const VideoFormat& format);
+  virtual bool SetSendRtpHeaderExtensions(
+      const std::vector<RtpHeaderExtension>& extensions);
+  virtual bool SetSendBandwidth(bool autobw, int bps);
+  virtual bool SetSend(bool send);
+
+  virtual bool AddRecvStream(const StreamParams& sp);
+  virtual bool RemoveRecvStream(uint32 ssrc);
+  virtual bool SetCapturer(uint32 ssrc, VideoCapturer* capturer);
+
+  virtual bool SendIntraFrame();
+  virtual bool RequestIntraFrame();
+
+  virtual bool GetStats(VideoMediaInfo* info);
+
+  virtual void OnPacketReceived(talk_base::Buffer* packet);
+  virtual void OnRtcpReceived(talk_base::Buffer* packet);
+  virtual void OnReadyToSend(bool ready);
+
+  virtual void UpdateAspectRatio(int ratio_w, int ratio_h);
+
+  void OnLocalFrame(VideoCapturer*, const VideoFrame*);
+  void OnLocalFrameFormat(VideoCapturer*, const VideoFormat*);
+
+  bool sending() const { return sending_; }
+
+ private:
+  bool SelectActiveChannel(const std::vector<VideoCodec>& codecs);
+  void SplitCodecs(const std::vector<VideoCodec>& codecs,
+                   std::vector<VideoCodec>* codecs1,
+                   std::vector<VideoCodec>* codecs2);
+
+  void OnMediaError(uint32 ssrc, Error error);
+
+  HybridVideoEngineInterface* engine_;
+  talk_base::scoped_ptr<VideoMediaChannel> channel1_;
+  talk_base::scoped_ptr<VideoMediaChannel> channel2_;
+  VideoMediaChannel* active_channel_;
+  bool sending_;
+};
+
+// Interface class for HybridVideoChannels to talk to the engine.
+class HybridVideoEngineInterface {
+ public:
+  virtual ~HybridVideoEngineInterface() {}
+  virtual bool HasCodec1(const VideoCodec& codec) = 0;
+  virtual bool HasCodec2(const VideoCodec& codec) = 0;
+  virtual void OnSendChange1(VideoMediaChannel* channel1, bool send) = 0;
+  virtual void OnSendChange2(VideoMediaChannel* channel1, bool send) = 0;
+  virtual void OnNewSendResolution(int width, int height) = 0;
+};
+
+// The HybridVideoEngine class combines two unrelated VideoEngine impls
+// into a single class. It creates HybridVideoMediaChannels that also contain
+// a VideoMediaChannel implementation from each engine. Policy is then used
+// during call setup to determine which VideoMediaChannel should be used.
+// Currently, this policy is based on what codec the remote side wants to use.
+template<class VIDEO1, class VIDEO2>
+class HybridVideoEngine : public HybridVideoEngineInterface {
+ public:
+  HybridVideoEngine() {
+    // Unify the codec lists.
+    codecs_ = video1_.codecs();
+    codecs_.insert(codecs_.end(), video2_.codecs().begin(),
+                   video2_.codecs().end());
+
+    rtp_header_extensions_ = video1_.rtp_header_extensions();
+    rtp_header_extensions_.insert(rtp_header_extensions_.end(),
+                                  video2_.rtp_header_extensions().begin(),
+                                  video2_.rtp_header_extensions().end());
+
+    SignalCaptureStateChange.repeat(video2_.SignalCaptureStateChange);
+  }
+
+  bool Init(talk_base::Thread* worker_thread) {
+    if (!video1_.Init(worker_thread)) {
+      LOG(LS_ERROR) << "Failed to init VideoEngine1";
+      return false;
+    }
+    if (!video2_.Init(worker_thread)) {
+      LOG(LS_ERROR) << "Failed to init VideoEngine2";
+      video1_.Terminate();
+      return false;
+    }
+    return true;
+  }
+  void Terminate() {
+    video1_.Terminate();
+    video2_.Terminate();
+  }
+
+  int GetCapabilities() {
+    return (video1_.GetCapabilities() | video2_.GetCapabilities());
+  }
+  HybridVideoMediaChannel* CreateChannel(VoiceMediaChannel* channel) {
+    talk_base::scoped_ptr<VideoMediaChannel> channel1(
+        video1_.CreateChannel(channel));
+    if (!channel1) {
+      LOG(LS_ERROR) << "Failed to create VideoMediaChannel1";
+      return NULL;
+    }
+    talk_base::scoped_ptr<VideoMediaChannel> channel2(
+        video2_.CreateChannel(channel));
+    if (!channel2) {
+      LOG(LS_ERROR) << "Failed to create VideoMediaChannel2";
+      return NULL;
+    }
+    return new HybridVideoMediaChannel(this,
+        channel1.release(), channel2.release());
+  }
+
+  bool SetOptions(int o) {
+    return video1_.SetOptions(o) && video2_.SetOptions(o);
+  }
+  bool SetDefaultEncoderConfig(const VideoEncoderConfig& config) {
+    VideoEncoderConfig conf = config;
+    if (video1_.codecs().size() > 0) {
+      conf.max_codec.name = video1_.codecs()[0].name;
+      if (!video1_.SetDefaultEncoderConfig(conf)) {
+        LOG(LS_ERROR) << "Failed to SetDefaultEncoderConfig for video1";
+        return false;
+      }
+    }
+    if (video2_.codecs().size() > 0) {
+      conf.max_codec.name = video2_.codecs()[0].name;
+      if (!video2_.SetDefaultEncoderConfig(conf)) {
+        LOG(LS_ERROR) << "Failed to SetDefaultEncoderConfig for video2";
+        return false;
+      }
+    }
+    return true;
+  }
+  const std::vector<VideoCodec>& codecs() const {
+    return codecs_;
+  }
+  const std::vector<RtpHeaderExtension>& rtp_header_extensions() const {
+    return rtp_header_extensions_;
+  }
+  void SetLogging(int min_sev, const char* filter) {
+    video1_.SetLogging(min_sev, filter);
+    video2_.SetLogging(min_sev, filter);
+  }
+
+  VideoFormat GetStartCaptureFormat() const {
+    return video2_.GetStartCaptureFormat();
+  }
+
+  // TODO(juberti): Remove these functions after we do the capturer refactoring.
+  // For now they are set to always use the second engine for capturing, which
+  // is convenient given our intended use case.
+  bool SetCaptureDevice(const Device* device) {
+    return video2_.SetCaptureDevice(device);
+  }
+  bool SetVideoCapturer(VideoCapturer* capturer) {
+    return video2_.SetVideoCapturer(capturer);
+  }
+  VideoCapturer* GetVideoCapturer() const {
+    return video2_.GetVideoCapturer();
+  }
+  bool SetLocalRenderer(VideoRenderer* renderer) {
+    return video2_.SetLocalRenderer(renderer);
+  }
+  bool SetCapture(bool capture) {
+    return video2_.SetCapture(capture);
+  }
+  sigslot::repeater2<VideoCapturer*, CaptureState> SignalCaptureStateChange;
+
+  virtual bool HasCodec1(const VideoCodec& codec) {
+    return HasCodec(video1_, codec);
+  }
+  virtual bool HasCodec2(const VideoCodec& codec) {
+    return HasCodec(video2_, codec);
+  }
+  template<typename VIDEO>
+  bool HasCodec(const VIDEO& engine, const VideoCodec& codec) const {
+    for (std::vector<VideoCodec>::const_iterator i = engine.codecs().begin();
+         i != engine.codecs().end();
+         ++i) {
+      if (i->Matches(codec)) {
+        return true;
+      }
+    }
+    return false;
+  }
+  virtual void OnSendChange1(VideoMediaChannel* channel1, bool send) {
+  }
+  virtual void OnSendChange2(VideoMediaChannel* channel2, bool send) {
+  }
+  virtual void OnNewSendResolution(int width, int height) {
+  }
+
+ protected:
+  VIDEO1 video1_;
+  VIDEO2 video2_;
+  std::vector<VideoCodec> codecs_;
+  std::vector<RtpHeaderExtension> rtp_header_extensions_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_HYBRIDVIDEOENGINE_H_
diff --git a/talk/media/base/mediachannel.h b/talk/media/base/mediachannel.h
new file mode 100644
index 0000000..b20051e
--- /dev/null
+++ b/talk/media/base/mediachannel.h
@@ -0,0 +1,933 @@
+/*
+ * 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_MEDIA_BASE_MEDIACHANNEL_H_
+#define TALK_MEDIA_BASE_MEDIACHANNEL_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/buffer.h"
+#include "talk/base/logging.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socket.h"
+#include "talk/base/window.h"
+#include "talk/media/base/codec.h"
+#include "talk/media/base/constants.h"
+#include "talk/media/base/streamparams.h"
+// TODO(juberti): re-evaluate this include
+#include "talk/session/media/audiomonitor.h"
+
+namespace talk_base {
+class Buffer;
+class RateLimiter;
+class Timing;
+}
+
+namespace cricket {
+
+class AudioRenderer;
+struct RtpHeader;
+class ScreencastId;
+struct VideoFormat;
+class VideoCapturer;
+class VideoRenderer;
+
+const int kMinRtpHeaderExtensionId = 1;
+const int kMaxRtpHeaderExtensionId = 255;
+const int kScreencastDefaultFps = 5;
+
+// Used in AudioOptions and VideoOptions to signify "unset" values.
+template <class T>
+class Settable {
+ public:
+  Settable() : set_(false), val_() {}
+  explicit Settable(T val) : set_(true), val_(val) {}
+
+  bool IsSet() const {
+    return set_;
+  }
+
+  bool Get(T* out) const {
+    *out = val_;
+    return set_;
+  }
+
+  T GetWithDefaultIfUnset(const T& default_value) const {
+    return set_ ? val_ : default_value;
+  }
+
+  virtual void Set(T val) {
+    set_ = true;
+    val_ = val;
+  }
+
+  void Clear() {
+    Set(T());
+    set_ = false;
+  }
+
+  void SetFrom(const Settable<T>& o) {
+    // Set this value based on the value of o, iff o is set.  If this value is
+    // set and o is unset, the current value will be unchanged.
+    T val;
+    if (o.Get(&val)) {
+      Set(val);
+    }
+  }
+
+  std::string ToString() const {
+    return set_ ? talk_base::ToString(val_) : "";
+  }
+
+  bool operator==(const Settable<T>& o) const {
+    // Equal if both are unset with any value or both set with the same value.
+    return (set_ == o.set_) && (!set_ || (val_ == o.val_));
+  }
+
+  bool operator!=(const Settable<T>& o) const {
+    return !operator==(o);
+  }
+
+ protected:
+  void InitializeValue(const T &val) {
+    val_ = val;
+  }
+
+ private:
+  bool set_;
+  T val_;
+};
+
+class SettablePercent : public Settable<float> {
+ public:
+  virtual void Set(float val) {
+    if (val < 0) {
+      val = 0;
+    }
+    if (val >  1.0) {
+      val = 1.0;
+    }
+    Settable<float>::Set(val);
+  }
+};
+
+template <class T>
+static std::string ToStringIfSet(const char* key, const Settable<T>& val) {
+  std::string str;
+  if (val.IsSet()) {
+    str = key;
+    str += ": ";
+    str += val.ToString();
+    str += ", ";
+  }
+  return str;
+}
+
+// Options that can be applied to a VoiceMediaChannel or a VoiceMediaEngine.
+// Used to be flags, but that makes it hard to selectively apply options.
+// We are moving all of the setting of options to structs like this,
+// but some things currently still use flags.
+struct AudioOptions {
+  void SetAll(const AudioOptions& change) {
+    echo_cancellation.SetFrom(change.echo_cancellation);
+    auto_gain_control.SetFrom(change.auto_gain_control);
+    noise_suppression.SetFrom(change.noise_suppression);
+    highpass_filter.SetFrom(change.highpass_filter);
+    stereo_swapping.SetFrom(change.stereo_swapping);
+    typing_detection.SetFrom(change.typing_detection);
+    conference_mode.SetFrom(change.conference_mode);
+    adjust_agc_delta.SetFrom(change.adjust_agc_delta);
+    experimental_agc.SetFrom(change.experimental_agc);
+    experimental_aec.SetFrom(change.experimental_aec);
+    aec_dump.SetFrom(change.aec_dump);
+  }
+
+  bool operator==(const AudioOptions& o) const {
+    return echo_cancellation == o.echo_cancellation &&
+        auto_gain_control == o.auto_gain_control &&
+        noise_suppression == o.noise_suppression &&
+        highpass_filter == o.highpass_filter &&
+        stereo_swapping == o.stereo_swapping &&
+        typing_detection == o.typing_detection &&
+        conference_mode == o.conference_mode &&
+        experimental_agc == o.experimental_agc &&
+        experimental_aec == o.experimental_aec &&
+        adjust_agc_delta == o.adjust_agc_delta &&
+        aec_dump == o.aec_dump;
+  }
+
+  std::string ToString() const {
+    std::ostringstream ost;
+    ost << "AudioOptions {";
+    ost << ToStringIfSet("aec", echo_cancellation);
+    ost << ToStringIfSet("agc", auto_gain_control);
+    ost << ToStringIfSet("ns", noise_suppression);
+    ost << ToStringIfSet("hf", highpass_filter);
+    ost << ToStringIfSet("swap", stereo_swapping);
+    ost << ToStringIfSet("typing", typing_detection);
+    ost << ToStringIfSet("conference", conference_mode);
+    ost << ToStringIfSet("agc_delta", adjust_agc_delta);
+    ost << ToStringIfSet("experimental_agc", experimental_agc);
+    ost << ToStringIfSet("experimental_aec", experimental_aec);
+    ost << ToStringIfSet("aec_dump", aec_dump);
+    ost << "}";
+    return ost.str();
+  }
+
+  // Audio processing that attempts to filter away the output signal from
+  // later inbound pickup.
+  Settable<bool> echo_cancellation;
+  // Audio processing to adjust the sensitivity of the local mic dynamically.
+  Settable<bool> auto_gain_control;
+  // Audio processing to filter out background noise.
+  Settable<bool> noise_suppression;
+  // Audio processing to remove background noise of lower frequencies.
+  Settable<bool> highpass_filter;
+  // Audio processing to swap the left and right channels.
+  Settable<bool> stereo_swapping;
+  // Audio processing to detect typing.
+  Settable<bool> typing_detection;
+  Settable<bool> conference_mode;
+  Settable<int> adjust_agc_delta;
+  Settable<bool> experimental_agc;
+  Settable<bool> experimental_aec;
+  Settable<bool> aec_dump;
+};
+
+// Options that can be applied to a VideoMediaChannel or a VideoMediaEngine.
+// Used to be flags, but that makes it hard to selectively apply options.
+// We are moving all of the setting of options to structs like this,
+// but some things currently still use flags.
+struct VideoOptions {
+  VideoOptions() {
+    process_adaptation_threshhold.Set(kProcessCpuThreshold);
+    system_low_adaptation_threshhold.Set(kLowSystemCpuThreshold);
+    system_high_adaptation_threshhold.Set(kHighSystemCpuThreshold);
+  }
+
+  void SetAll(const VideoOptions& change) {
+    adapt_input_to_encoder.SetFrom(change.adapt_input_to_encoder);
+    adapt_input_to_cpu_usage.SetFrom(change.adapt_input_to_cpu_usage);
+    adapt_view_switch.SetFrom(change.adapt_view_switch);
+    video_noise_reduction.SetFrom(change.video_noise_reduction);
+    video_three_layers.SetFrom(change.video_three_layers);
+    video_enable_camera_list.SetFrom(change.video_enable_camera_list);
+    video_one_layer_screencast.SetFrom(change.video_one_layer_screencast);
+    video_high_bitrate.SetFrom(change.video_high_bitrate);
+    video_watermark.SetFrom(change.video_watermark);
+    video_temporal_layer_screencast.SetFrom(
+        change.video_temporal_layer_screencast);
+    video_leaky_bucket.SetFrom(change.video_leaky_bucket);
+    conference_mode.SetFrom(change.conference_mode);
+    process_adaptation_threshhold.SetFrom(change.process_adaptation_threshhold);
+    system_low_adaptation_threshhold.SetFrom(
+        change.system_low_adaptation_threshhold);
+    system_high_adaptation_threshhold.SetFrom(
+        change.system_high_adaptation_threshhold);
+    buffered_mode_latency.SetFrom(change.buffered_mode_latency);
+  }
+
+  bool operator==(const VideoOptions& o) const {
+    return adapt_input_to_encoder == o.adapt_input_to_encoder &&
+        adapt_input_to_cpu_usage == o.adapt_input_to_cpu_usage &&
+        adapt_view_switch == o.adapt_view_switch &&
+        video_noise_reduction == o.video_noise_reduction &&
+        video_three_layers == o.video_three_layers &&
+        video_enable_camera_list == o.video_enable_camera_list &&
+        video_one_layer_screencast == o.video_one_layer_screencast &&
+        video_high_bitrate == o.video_high_bitrate &&
+        video_watermark == o.video_watermark &&
+        video_temporal_layer_screencast == o.video_temporal_layer_screencast &&
+        video_leaky_bucket == o.video_leaky_bucket &&
+        conference_mode == o.conference_mode &&
+        process_adaptation_threshhold == o.process_adaptation_threshhold &&
+        system_low_adaptation_threshhold ==
+            o.system_low_adaptation_threshhold &&
+        system_high_adaptation_threshhold ==
+            o.system_high_adaptation_threshhold &&
+        buffered_mode_latency == o.buffered_mode_latency;
+  }
+
+  std::string ToString() const {
+    std::ostringstream ost;
+    ost << "VideoOptions {";
+    ost << ToStringIfSet("encoder adaption", adapt_input_to_encoder);
+    ost << ToStringIfSet("cpu adaption", adapt_input_to_cpu_usage);
+    ost << ToStringIfSet("adapt view switch", adapt_view_switch);
+    ost << ToStringIfSet("noise reduction", video_noise_reduction);
+    ost << ToStringIfSet("3 layers", video_three_layers);
+    ost << ToStringIfSet("camera list", video_enable_camera_list);
+    ost << ToStringIfSet("1 layer screencast",
+                        video_one_layer_screencast);
+    ost << ToStringIfSet("high bitrate", video_high_bitrate);
+    ost << ToStringIfSet("watermark", video_watermark);
+    ost << ToStringIfSet("video temporal layer screencast",
+                         video_temporal_layer_screencast);
+    ost << ToStringIfSet("leaky bucket", video_leaky_bucket);
+    ost << ToStringIfSet("conference mode", conference_mode);
+    ost << ToStringIfSet("process", process_adaptation_threshhold);
+    ost << ToStringIfSet("low", system_low_adaptation_threshhold);
+    ost << ToStringIfSet("high", system_high_adaptation_threshhold);
+    ost << ToStringIfSet("buffered mode latency", buffered_mode_latency);
+    ost << "}";
+    return ost.str();
+  }
+
+  // Encoder adaption, which is the gd callback in LMI, and TBA in WebRTC.
+  Settable<bool> adapt_input_to_encoder;
+  // Enable CPU adaptation?
+  Settable<bool> adapt_input_to_cpu_usage;
+  // Enable Adapt View Switch?
+  Settable<bool> adapt_view_switch;
+  // Enable denoising?
+  Settable<bool> video_noise_reduction;
+  // Experimental: Enable multi layer?
+  Settable<bool> video_three_layers;
+  // Experimental: Enable camera list?
+  Settable<bool> video_enable_camera_list;
+  // Experimental: Enable one layer screencast?
+  Settable<bool> video_one_layer_screencast;
+  // Experimental: Enable WebRtc higher bitrate?
+  Settable<bool> video_high_bitrate;
+  // Experimental: Add watermark to the rendered video image.
+  Settable<bool> video_watermark;
+  // Experimental: Enable WebRTC layered screencast.
+  Settable<bool> video_temporal_layer_screencast;
+  // Enable WebRTC leaky bucket when sending media packets.
+  Settable<bool> video_leaky_bucket;
+  // Use conference mode?
+  Settable<bool> conference_mode;
+  // Threshhold for process cpu adaptation.  (Process limit)
+  SettablePercent process_adaptation_threshhold;
+  // Low threshhold for cpu adaptation.  (Adapt up)
+  SettablePercent system_low_adaptation_threshhold;
+  // High threshhold for cpu adaptation.  (Adapt down)
+  SettablePercent system_high_adaptation_threshhold;
+  // Specify buffered mode latency in milliseconds.
+  Settable<int> buffered_mode_latency;
+};
+
+// A class for playing out soundclips.
+class SoundclipMedia {
+ public:
+  enum SoundclipFlags {
+    SF_LOOP = 1,
+  };
+
+  virtual ~SoundclipMedia() {}
+
+  // Plays a sound out to the speakers with the given audio stream. The stream
+  // must be 16-bit little-endian 16 kHz PCM. If a stream is already playing
+  // on this SoundclipMedia, it is stopped. If clip is NULL, nothing is played.
+  // Returns whether it was successful.
+  virtual bool PlaySound(const char *clip, int len, int flags) = 0;
+};
+
+struct RtpHeaderExtension {
+  RtpHeaderExtension() : id(0) {}
+  RtpHeaderExtension(const std::string& u, int i) : uri(u), id(i) {}
+  std::string uri;
+  int id;
+  // TODO(juberti): SendRecv direction;
+
+  bool operator==(const RtpHeaderExtension& ext) const {
+    // id is a reserved word in objective-c. Therefore the id attribute has to
+    // be a fully qualified name in order to compile on IOS.
+    return this->id == ext.id &&
+        uri == ext.uri;
+  }
+};
+
+// Returns the named header extension if found among all extensions, NULL
+// otherwise.
+inline const RtpHeaderExtension* FindHeaderExtension(
+    const std::vector<RtpHeaderExtension>& extensions,
+    const std::string& name) {
+  for (std::vector<RtpHeaderExtension>::const_iterator it = extensions.begin();
+       it != extensions.end(); ++it) {
+    if (it->uri == name)
+      return &(*it);
+  }
+  return NULL;
+}
+
+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
+};
+
+// DTMF flags to control if a DTMF tone should be played and/or sent.
+enum DtmfFlags {
+  DF_PLAY = 0x01,
+  DF_SEND = 0x02,
+};
+
+// Special purpose DTMF event code used by the VoiceMediaChannel::InsertDtmf.
+const int kDtmfDelay = -1;  // Insert a delay to the end of the DTMF queue.
+const int kDtmfReset = -2;  // Reset the DTMF queue.
+// The delay in ms when the InsertDtmf is called with kDtmfDelay.
+const int kDtmfDelayInMs = 2000;
+
+class MediaChannel : public sigslot::has_slots<> {
+ public:
+  class NetworkInterface {
+   public:
+    enum SocketType { ST_RTP, ST_RTCP };
+    virtual bool SendPacket(talk_base::Buffer* packet) = 0;
+    virtual bool SendRtcp(talk_base::Buffer* packet) = 0;
+    virtual int SetOption(SocketType type, talk_base::Socket::Option opt,
+                          int option) = 0;
+    virtual ~NetworkInterface() {}
+  };
+
+  MediaChannel() : network_interface_(NULL) {}
+  virtual ~MediaChannel() {}
+
+  // Gets/sets the abstract inteface class for sending RTP/RTCP data.
+  NetworkInterface *network_interface() { return network_interface_; }
+  virtual void SetInterface(NetworkInterface *iface) {
+    network_interface_ = iface;
+  }
+
+  // Called when a RTP packet is received.
+  virtual void OnPacketReceived(talk_base::Buffer* packet) = 0;
+  // Called when a RTCP packet is received.
+  virtual void OnRtcpReceived(talk_base::Buffer* packet) = 0;
+  // Called when the socket's ability to send has changed.
+  virtual void OnReadyToSend(bool ready) = 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 MuteStream(uint32 ssrc, bool on) = 0;
+
+  // Sets the RTP extension headers and IDs to use when sending RTP.
+  virtual bool SetRecvRtpHeaderExtensions(
+      const std::vector<RtpHeaderExtension>& extensions) = 0;
+  virtual bool SetSendRtpHeaderExtensions(
+      const std::vector<RtpHeaderExtension>& extensions) = 0;
+  // Sets the rate control to use when sending data.
+  virtual bool SetSendBandwidth(bool autobw, int bps) = 0;
+
+ protected:
+  NetworkInterface *network_interface_;
+};
+
+enum SendFlags {
+  SEND_NOTHING,
+  SEND_RINGBACKTONE,
+  SEND_MICROPHONE
+};
+
+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),
+        aec_quality_min(0.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;
+  int64 bytes_sent;
+  int packets_sent;
+  int packets_lost;
+  float fraction_lost;
+  int ext_seqnum;
+  int rtt_ms;
+  int jitter_ms;
+  int audio_level;
+  float aec_quality_min;
+  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),
+        expand_rate(0) {
+  }
+
+  uint32 ssrc;
+  int64 bytes_rcvd;
+  int packets_rcvd;
+  int packets_lost;
+  float fraction_lost;
+  int ext_seqnum;
+  int jitter_ms;
+  int jitter_buffer_ms;
+  int jitter_buffer_preferred_ms;
+  int delay_estimate_ms;
+  int audio_level;
+  // fraction of synthesized speech inserted through pre-emptive expansion
+  float expand_rate;
+};
+
+struct VideoSenderInfo {
+  VideoSenderInfo()
+      : 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),
+        adapt_reason(0) {
+  }
+
+  std::vector<uint32> ssrcs;
+  std::vector<SsrcGroup> ssrc_groups;
+  std::string codec_name;
+  int64 bytes_sent;
+  int packets_sent;
+  int packets_cached;
+  int packets_lost;
+  float fraction_lost;
+  int firs_rcvd;
+  int nacks_rcvd;
+  int rtt_ms;
+  int frame_width;
+  int frame_height;
+  int framerate_input;
+  int framerate_sent;
+  int nominal_bitrate;
+  int preferred_bitrate;
+  int adapt_reason;
+};
+
+struct VideoReceiverInfo {
+  VideoReceiverInfo()
+      : 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),
+        framerate_render_input(0),
+        framerate_render_output(0) {
+  }
+
+  std::vector<uint32> ssrcs;
+  std::vector<SsrcGroup> ssrc_groups;
+  int64 bytes_rcvd;
+  // vector<int> layer_bytes_rcvd;
+  int packets_rcvd;
+  int packets_lost;
+  int packets_concealed;
+  float fraction_lost;
+  int firs_sent;
+  int nacks_sent;
+  int frame_width;
+  int frame_height;
+  int framerate_rcvd;
+  int framerate_decoded;
+  int framerate_output;
+  // Framerate as sent to the renderer.
+  int framerate_render_input;
+  // Framerate that the renderer reports.
+  int framerate_render_output;
+};
+
+struct DataSenderInfo {
+  DataSenderInfo()
+      : ssrc(0),
+        bytes_sent(0),
+        packets_sent(0) {
+  }
+
+  uint32 ssrc;
+  std::string codec_name;
+  int64 bytes_sent;
+  int packets_sent;
+};
+
+struct DataReceiverInfo {
+  DataReceiverInfo()
+      : ssrc(0),
+        bytes_rcvd(0),
+        packets_rcvd(0) {
+  }
+
+  uint32 ssrc;
+  int64 bytes_rcvd;
+  int packets_rcvd;
+};
+
+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;
+  int actual_enc_bitrate;
+  int retransmit_bitrate;
+  int transmit_bitrate;
+  int bucket_delay;
+};
+
+struct VoiceMediaInfo {
+  void Clear() {
+    senders.clear();
+    receivers.clear();
+  }
+  std::vector<VoiceSenderInfo> senders;
+  std::vector<VoiceReceiverInfo> receivers;
+};
+
+struct VideoMediaInfo {
+  void Clear() {
+    senders.clear();
+    receivers.clear();
+    bw_estimations.clear();
+  }
+  std::vector<VideoSenderInfo> senders;
+  std::vector<VideoReceiverInfo> receivers;
+  std::vector<BandwidthEstimationInfo> bw_estimations;
+};
+
+struct DataMediaInfo {
+  void Clear() {
+    senders.clear();
+    receivers.clear();
+  }
+  std::vector<DataSenderInfo> senders;
+  std::vector<DataReceiverInfo> receivers;
+};
+
+class VoiceMediaChannel : public MediaChannel {
+ public:
+  enum Error {
+    ERROR_NONE = 0,                       // No error.
+    ERROR_OTHER,                          // Other errors.
+    ERROR_REC_DEVICE_OPEN_FAILED = 100,   // Could not open mic.
+    ERROR_REC_DEVICE_MUTED,               // Mic was muted by OS.
+    ERROR_REC_DEVICE_SILENT,              // No background noise picked up.
+    ERROR_REC_DEVICE_SATURATION,          // Mic input is clipping.
+    ERROR_REC_DEVICE_REMOVED,             // Mic was removed while active.
+    ERROR_REC_RUNTIME_ERROR,              // Processing is encountering errors.
+    ERROR_REC_SRTP_ERROR,                 // Generic SRTP failure.
+    ERROR_REC_SRTP_AUTH_FAILED,           // Failed to authenticate packets.
+    ERROR_REC_TYPING_NOISE_DETECTED,      // Typing noise is detected.
+    ERROR_PLAY_DEVICE_OPEN_FAILED = 200,  // Could not open playout.
+    ERROR_PLAY_DEVICE_MUTED,              // Playout muted by OS.
+    ERROR_PLAY_DEVICE_REMOVED,            // Playout removed while active.
+    ERROR_PLAY_RUNTIME_ERROR,             // Errors in voice processing.
+    ERROR_PLAY_SRTP_ERROR,                // Generic SRTP failure.
+    ERROR_PLAY_SRTP_AUTH_FAILED,          // Failed to authenticate packets.
+    ERROR_PLAY_SRTP_REPLAY,               // Packet replay detected.
+  };
+
+  VoiceMediaChannel() {}
+  virtual ~VoiceMediaChannel() {}
+  // Sets the codecs/payload types to be used for incoming media.
+  virtual bool SetRecvCodecs(const std::vector<AudioCodec>& codecs) = 0;
+  // Sets the codecs/payload types to be used for outgoing media.
+  virtual bool SetSendCodecs(const std::vector<AudioCodec>& codecs) = 0;
+  // Starts or stops playout of received audio.
+  virtual bool SetPlayout(bool playout) = 0;
+  // Starts or stops sending (and potentially capture) of local audio.
+  virtual bool SetSend(SendFlags flag) = 0;
+  // Sets the renderer object to be used for the specified audio stream.
+  virtual bool SetRenderer(uint32 ssrc, AudioRenderer* renderer) = 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.
+  virtual int GetOutputLevel() = 0;
+  // Get the time in milliseconds since last recorded keystroke, or negative.
+  virtual int GetTimeSinceLastTyping() = 0;
+  // Temporarily exposed field for tuning typing detect options.
+  virtual void SetTypingDetectionParameters(int time_window,
+    int cost_per_typing, int reporting_threshold, int penalty_decay,
+    int type_event_delay) = 0;
+  // Set left and right scale for speaker output volume of the specified ssrc.
+  virtual bool SetOutputScaling(uint32 ssrc, double left, double right) = 0;
+  // Get left and right scale for speaker output volume of the specified ssrc.
+  virtual bool GetOutputScaling(uint32 ssrc, double* left, double* right) = 0;
+  // Specifies a ringback tone to be played during call setup.
+  virtual bool SetRingbackTone(const char *buf, int len) = 0;
+  // Plays or stops the aforementioned ringback tone
+  virtual bool PlayRingbackTone(uint32 ssrc, bool play, bool loop) = 0;
+  // Returns if the telephone-event has been negotiated.
+  virtual bool CanInsertDtmf() { return false; }
+  // Send and/or play a DTMF |event| according to the |flags|.
+  // The DTMF out-of-band signal will be used on sending.
+  // The |ssrc| should be either 0 or a valid send stream ssrc.
+  // The valid value for the |event| are -2 to 15.
+  // kDtmfReset(-2) is used to reset the DTMF.
+  // kDtmfDelay(-1) is used to insert a delay to the end of the DTMF queue.
+  // 0 to 15 which corresponding to DTMF event 0-9, *, #, A-D.
+  virtual bool InsertDtmf(uint32 ssrc, int event, int duration, int flags) = 0;
+  // Gets quality stats for the channel.
+  virtual bool GetStats(VoiceMediaInfo* info) = 0;
+  // Gets last reported error for this media channel.
+  virtual void GetLastMediaError(uint32* ssrc,
+                                 VoiceMediaChannel::Error* error) {
+    ASSERT(error != NULL);
+    *error = ERROR_NONE;
+  }
+  // Sets the media options to use.
+  virtual bool SetOptions(const AudioOptions& options) = 0;
+  virtual bool GetOptions(AudioOptions* options) const = 0;
+
+  // Signal errors from MediaChannel.  Arguments are:
+  //     ssrc(uint32), and error(VoiceMediaChannel::Error).
+  sigslot::signal2<uint32, VoiceMediaChannel::Error> SignalMediaError;
+};
+
+class VideoMediaChannel : public MediaChannel {
+ public:
+  enum Error {
+    ERROR_NONE = 0,                       // No error.
+    ERROR_OTHER,                          // Other errors.
+    ERROR_REC_DEVICE_OPEN_FAILED = 100,   // Could not open camera.
+    ERROR_REC_DEVICE_NO_DEVICE,           // No camera.
+    ERROR_REC_DEVICE_IN_USE,              // Device is in already use.
+    ERROR_REC_DEVICE_REMOVED,             // Device is removed.
+    ERROR_REC_SRTP_ERROR,                 // Generic sender SRTP failure.
+    ERROR_REC_SRTP_AUTH_FAILED,           // Failed to authenticate packets.
+    ERROR_REC_CPU_MAX_CANT_DOWNGRADE,     // Can't downgrade capture anymore.
+    ERROR_PLAY_SRTP_ERROR = 200,          // Generic receiver SRTP failure.
+    ERROR_PLAY_SRTP_AUTH_FAILED,          // Failed to authenticate packets.
+    ERROR_PLAY_SRTP_REPLAY,               // Packet replay detected.
+  };
+
+  VideoMediaChannel() : renderer_(NULL) {}
+  virtual ~VideoMediaChannel() {}
+  // Sets the codecs/payload types to be used for incoming media.
+  virtual bool SetRecvCodecs(const std::vector<VideoCodec>& codecs) = 0;
+  // Sets the codecs/payload types to be used for outgoing media.
+  virtual bool SetSendCodecs(const std::vector<VideoCodec>& codecs) = 0;
+  // Gets the currently set codecs/payload types to be used for outgoing media.
+  virtual bool GetSendCodec(VideoCodec* send_codec) = 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;
+  // 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;
+  // If |ssrc| is 0, replace the default capturer (engine capturer) with
+  // |capturer|. If |ssrc| is non zero create a new stream with |ssrc| as SSRC.
+  virtual bool SetCapturer(uint32 ssrc, VideoCapturer* capturer) = 0;
+  // Gets quality stats for the channel.
+  virtual bool GetStats(VideoMediaInfo* info) = 0;
+
+  // Send an intra frame to the receivers.
+  virtual bool SendIntraFrame() = 0;
+  // Reuqest each of the remote senders to send an intra frame.
+  virtual bool RequestIntraFrame() = 0;
+  // Sets the media options to use.
+  virtual bool SetOptions(const VideoOptions& options) = 0;
+  virtual bool GetOptions(VideoOptions* options) const = 0;
+  virtual void UpdateAspectRatio(int ratio_w, int ratio_h) = 0;
+
+  // Signal errors from MediaChannel.  Arguments are:
+  //     ssrc(uint32), and error(VideoMediaChannel::Error).
+  sigslot::signal2<uint32, Error> SignalMediaError;
+
+ protected:
+  VideoRenderer *renderer_;
+};
+
+enum DataMessageType {
+  // TODO(pthatcher):  Make this enum match the SCTP PPIDs that WebRTC uses?
+  DMT_CONTROL = 0,
+  DMT_BINARY = 1,
+  DMT_TEXT = 2,
+};
+
+// Info about data received in DataMediaChannel.  For use in
+// DataMediaChannel::SignalDataReceived and in all of the signals that
+// signal fires, on up the chain.
+struct ReceiveDataParams {
+  // The in-packet stream indentifier.
+  // For SCTP, this is really SID, not SSRC.
+  uint32 ssrc;
+  // The type of message (binary, text, or control).
+  DataMessageType type;
+  // A per-stream value incremented per packet in the stream.
+  int seq_num;
+  // A per-stream value monotonically increasing with time.
+  int timestamp;
+
+  ReceiveDataParams() :
+      ssrc(0),
+      type(DMT_TEXT),
+      seq_num(0),
+      timestamp(0) {
+  }
+};
+
+struct SendDataParams {
+  // The in-packet stream indentifier.
+  // For SCTP, this is really SID, not SSRC.
+  uint32 ssrc;
+  // The type of message (binary, text, or control).
+  DataMessageType type;
+
+  // For SCTP, whether to send messages flagged as ordered or not.
+  // If false, messages can be received out of order.
+  bool ordered;
+  // For SCTP, whether the messages are sent reliably or not.
+  // If false, messages may be lost.
+  bool reliable;
+  // For SCTP, if reliable == false, provide partial reliability by
+  // resending up to this many times.  Either count or millis
+  // is supported, not both at the same time.
+  int max_rtx_count;
+  // For SCTP, if reliable == false, provide partial reliability by
+  // resending for up to this many milliseconds.  Either count or millis
+  // is supported, not both at the same time.
+  int max_rtx_ms;
+
+  SendDataParams() :
+      ssrc(0),
+      type(DMT_TEXT),
+      // TODO(pthatcher): Make these true by default?
+      ordered(false),
+      reliable(false),
+      max_rtx_count(0),
+      max_rtx_ms(0) {
+  }
+};
+
+enum SendDataResult { SDR_SUCCESS, SDR_ERROR, SDR_BLOCK };
+
+class DataMediaChannel : public MediaChannel {
+ public:
+  enum Error {
+    ERROR_NONE = 0,                       // No error.
+    ERROR_OTHER,                          // Other errors.
+    ERROR_SEND_SRTP_ERROR = 200,          // Generic SRTP failure.
+    ERROR_SEND_SRTP_AUTH_FAILED,          // Failed to authenticate packets.
+    ERROR_RECV_SRTP_ERROR,                // Generic SRTP failure.
+    ERROR_RECV_SRTP_AUTH_FAILED,          // Failed to authenticate packets.
+    ERROR_RECV_SRTP_REPLAY,               // Packet replay detected.
+  };
+
+  virtual ~DataMediaChannel() {}
+
+  virtual bool SetSendBandwidth(bool autobw, int bps) = 0;
+  virtual bool SetSendCodecs(const std::vector<DataCodec>& codecs) = 0;
+  virtual bool SetRecvCodecs(const std::vector<DataCodec>& codecs) = 0;
+  virtual bool SetRecvRtpHeaderExtensions(
+      const std::vector<RtpHeaderExtension>& extensions) = 0;
+  virtual bool SetSendRtpHeaderExtensions(
+      const std::vector<RtpHeaderExtension>& extensions) = 0;
+  virtual bool AddSendStream(const StreamParams& sp) = 0;
+  virtual bool RemoveSendStream(uint32 ssrc) = 0;
+  virtual bool AddRecvStream(const StreamParams& sp) = 0;
+  virtual bool RemoveRecvStream(uint32 ssrc) = 0;
+  virtual bool MuteStream(uint32 ssrc, bool on) { return false; }
+  // TODO(pthatcher): Implement this.
+  virtual bool GetStats(DataMediaInfo* info) { return true; }
+
+  virtual bool SetSend(bool send) = 0;
+  virtual bool SetReceive(bool receive) = 0;
+  virtual void OnPacketReceived(talk_base::Buffer* packet) = 0;
+  virtual void OnRtcpReceived(talk_base::Buffer* packet) = 0;
+
+  virtual bool SendData(
+      const SendDataParams& params,
+      const talk_base::Buffer& payload,
+      SendDataResult* result = NULL) = 0;
+  // Signals when data is received (params, data, len)
+  sigslot::signal3<const ReceiveDataParams&,
+                   const char*,
+                   size_t> SignalDataReceived;
+  // Signal errors from MediaChannel.  Arguments are:
+  //     ssrc(uint32), and error(DataMediaChannel::Error).
+  sigslot::signal2<uint32, DataMediaChannel::Error> SignalMediaError;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_MEDIACHANNEL_H_
diff --git a/talk/media/base/mediacommon.h b/talk/media/base/mediacommon.h
new file mode 100644
index 0000000..e0d7eca
--- /dev/null
+++ b/talk/media/base/mediacommon.h
@@ -0,0 +1,44 @@
+/*
+ * 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_MEDIA_BASE_MEDIACOMMON_H_
+#define TALK_MEDIA_BASE_MEDIACOMMON_H_
+
+#include "talk/base/stringencode.h"
+
+namespace cricket {
+
+enum MediaCapabilities {
+  AUDIO_RECV = 1 << 0,
+  AUDIO_SEND = 1 << 1,
+  VIDEO_RECV = 1 << 2,
+  VIDEO_SEND = 1 << 3,
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_MEDIACOMMON_H_
diff --git a/talk/media/base/mediaengine.cc b/talk/media/base/mediaengine.cc
new file mode 100644
index 0000000..021cf81
--- /dev/null
+++ b/talk/media/base/mediaengine.cc
@@ -0,0 +1,74 @@
+//
+// 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/media/base/mediaengine.h"
+
+namespace cricket {
+const int MediaEngineInterface::kDefaultAudioDelayOffset = 0;
+}
+
+#if !defined(DISABLE_MEDIA_ENGINE_FACTORY)
+
+#if defined(HAVE_LINPHONE)
+#include "talk/media/other/linphonemediaengine.h"
+#endif  // HAVE_LINPHONE
+#if defined(HAVE_WEBRTC_VOICE)
+#include "talk/media/webrtc/webrtcvoiceengine.h"
+#endif  // HAVE_WEBRTC_VOICE
+#if defined(HAVE_WEBRTC_VIDEO)
+#include "talk/media/webrtc/webrtcvideoengine.h"
+#endif  // HAVE_WEBRTC_VIDEO
+
+namespace cricket {
+#if defined(HAVE_WEBRTC_VOICE)
+#define AUDIO_ENG_NAME WebRtcVoiceEngine
+#else
+#define AUDIO_ENG_NAME NullVoiceEngine
+#endif
+
+#if defined(HAVE_WEBRTC_VIDEO)
+template<>
+CompositeMediaEngine<WebRtcVoiceEngine, WebRtcVideoEngine>::
+    CompositeMediaEngine() {
+  video_.SetVoiceEngine(&voice_);
+}
+#define VIDEO_ENG_NAME WebRtcVideoEngine
+#endif
+
+MediaEngineInterface* MediaEngineFactory::Create() {
+#if defined(HAVE_LINPHONE)
+  return new LinphoneMediaEngine("", "");
+#elif defined(AUDIO_ENG_NAME) && defined(VIDEO_ENG_NAME)
+  return new CompositeMediaEngine<AUDIO_ENG_NAME, VIDEO_ENG_NAME>();
+#else
+  return new NullMediaEngine();
+#endif
+}
+
+};  // namespace cricket
+
+#endif  // DISABLE_MEDIA_ENGINE_FACTORY
diff --git a/talk/media/base/mediaengine.h b/talk/media/base/mediaengine.h
new file mode 100644
index 0000000..5cfcb4d
--- /dev/null
+++ b/talk/media/base/mediaengine.h
@@ -0,0 +1,400 @@
+/*
+ * 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_MEDIA_BASE_MEDIAENGINE_H_
+#define TALK_MEDIA_BASE_MEDIAENGINE_H_
+
+#ifdef OSX
+#include <CoreAudio/CoreAudio.h>
+#endif
+
+#include <climits>
+#include <string>
+#include <vector>
+
+#include "talk/base/sigslotrepeater.h"
+#include "talk/media/base/codec.h"
+#include "talk/media/base/mediachannel.h"
+#include "talk/media/base/mediacommon.h"
+#include "talk/media/base/videocapturer.h"
+#include "talk/media/base/videocommon.h"
+#include "talk/media/base/videoprocessor.h"
+#include "talk/media/base/voiceprocessor.h"
+#include "talk/media/devices/devicemanager.h"
+
+#if defined(GOOGLE_CHROME_BUILD) || defined(CHROMIUM_BUILD)
+#define DISABLE_MEDIA_ENGINE_FACTORY
+#endif
+
+namespace cricket {
+
+class VideoCapturer;
+
+// MediaEngineInterface is an abstraction of a media engine which can be
+// subclassed to support different media componentry backends.
+// It supports voice and video operations in the same class to facilitate
+// proper synchronization between both media types.
+class MediaEngineInterface {
+ public:
+  // Bitmask flags for options that may be supported by the media engine
+  // implementation.  This can be converted to and from an
+  // AudioOptions struct for backwards compatibility with calls that
+  // use flags until we transition to using structs everywhere.
+  enum AudioFlags {
+    // Audio processing that attempts to filter away the output signal from
+    // later inbound pickup.
+    ECHO_CANCELLATION         = 1 << 0,
+    // Audio processing to adjust the sensitivity of the local mic dynamically.
+    AUTO_GAIN_CONTROL         = 1 << 1,
+    // Audio processing to filter out background noise.
+    NOISE_SUPPRESSION         = 1 << 2,
+    // Audio processing to remove background noise of lower frequencies.
+    HIGHPASS_FILTER           = 1 << 3,
+    // A switch to swap which captured signal is left and right in stereo mode.
+    STEREO_FLIPPING           = 1 << 4,
+    // Controls delegation echo cancellation to use the OS' facility.
+    SYSTEM_AEC_MODE           = 1 << 5,
+
+    ALL_AUDIO_OPTIONS         = (1 << 6) - 1,
+    DEFAULT_AUDIO_OPTIONS     = ECHO_CANCELLATION | AUTO_GAIN_CONTROL |
+                                NOISE_SUPPRESSION | HIGHPASS_FILTER,
+  };
+
+  // Default value to be used for SetAudioDelayOffset().
+  static const int kDefaultAudioDelayOffset;
+
+  virtual ~MediaEngineInterface() {}
+
+  // Initialization
+  // Starts the engine.
+  virtual bool Init(talk_base::Thread* worker_thread) = 0;
+  // Shuts down the engine.
+  virtual void Terminate() = 0;
+  // Returns what the engine is capable of, as a set of Capabilities, above.
+  virtual int GetCapabilities() = 0;
+
+  // MediaChannel creation
+  // Creates a voice media channel. Returns NULL on failure.
+  virtual VoiceMediaChannel *CreateChannel() = 0;
+  // Creates a video media channel, paired with the specified voice channel.
+  // Returns NULL on failure.
+  virtual VideoMediaChannel *CreateVideoChannel(
+      VoiceMediaChannel* voice_media_channel) = 0;
+
+  // Creates a soundclip object for playing sounds on. Returns NULL on failure.
+  virtual SoundclipMedia *CreateSoundclip() = 0;
+
+  // Configuration
+  // Sets global audio options. "options" are from AudioOptions, above.
+  virtual bool SetAudioOptions(int options) = 0;
+  // Sets global video options. "options" are from VideoOptions, above.
+  virtual bool SetVideoOptions(int options) = 0;
+  // Sets the value used by the echo canceller to offset delay values obtained
+  // from the OS.
+  virtual bool SetAudioDelayOffset(int offset) = 0;
+  // Sets the default (maximum) codec/resolution and encoder option to capture
+  // and encode video.
+  virtual bool SetDefaultVideoEncoderConfig(const VideoEncoderConfig& config)
+      = 0;
+
+  // Device selection
+  // TODO(tschmelcher): Add method for selecting the soundclip device.
+  virtual bool SetSoundDevices(const Device* in_device,
+                               const Device* out_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) = 0;
+  virtual VideoCapturer* GetVideoCapturer() const = 0;
+
+  // Device configuration
+  // Gets the current speaker volume, as a value between 0 and 255.
+  virtual bool GetOutputVolume(int* level) = 0;
+  // Sets the current speaker volume, as a value between 0 and 255.
+  virtual bool SetOutputVolume(int level) = 0;
+
+  // Local monitoring
+  // Gets the current microphone level, as a value between 0 and 10.
+  virtual int GetInputLevel() = 0;
+  // Starts or stops the local microphone. Useful if local mic info is needed
+  // prior to a call being connected; the mic will be started automatically
+  // when a VoiceMediaChannel starts sending.
+  virtual bool SetLocalMonitor(bool enable) = 0;
+  // Installs a callback for raw frames from the local camera.
+  virtual bool SetLocalRenderer(VideoRenderer* renderer) = 0;
+  // Starts/stops local camera.
+  virtual bool SetVideoCapture(bool capture) = 0;
+
+  virtual const std::vector<AudioCodec>& audio_codecs() = 0;
+  virtual const std::vector<RtpHeaderExtension>&
+      audio_rtp_header_extensions() = 0;
+  virtual const std::vector<VideoCodec>& video_codecs() = 0;
+  virtual const std::vector<RtpHeaderExtension>&
+      video_rtp_header_extensions() = 0;
+
+  // Logging control
+  virtual void SetVoiceLogging(int min_sev, const char* filter) = 0;
+  virtual void SetVideoLogging(int min_sev, const char* filter) = 0;
+
+  // Voice processors for effects.
+  virtual bool RegisterVoiceProcessor(uint32 ssrc,
+                                      VoiceProcessor* video_processor,
+                                      MediaProcessorDirection direction) = 0;
+  virtual bool UnregisterVoiceProcessor(uint32 ssrc,
+                                        VoiceProcessor* video_processor,
+                                        MediaProcessorDirection direction) = 0;
+
+  virtual VideoFormat GetStartCaptureFormat() const = 0;
+
+  virtual sigslot::repeater2<VideoCapturer*, CaptureState>&
+      SignalVideoCaptureStateChange() = 0;
+};
+
+
+#if !defined(DISABLE_MEDIA_ENGINE_FACTORY)
+class MediaEngineFactory {
+ public:
+  static MediaEngineInterface* Create();
+};
+#endif
+
+// CompositeMediaEngine constructs a MediaEngine from separate
+// voice and video engine classes.
+template<class VOICE, class VIDEO>
+class CompositeMediaEngine : public MediaEngineInterface {
+ public:
+  CompositeMediaEngine() {}
+  virtual ~CompositeMediaEngine() {}
+  virtual bool Init(talk_base::Thread* worker_thread) {
+    if (!voice_.Init(worker_thread))
+      return false;
+    if (!video_.Init(worker_thread)) {
+      voice_.Terminate();
+      return false;
+    }
+    SignalVideoCaptureStateChange().repeat(video_.SignalCaptureStateChange);
+    return true;
+  }
+  virtual void Terminate() {
+    video_.Terminate();
+    voice_.Terminate();
+  }
+
+  virtual int GetCapabilities() {
+    return (voice_.GetCapabilities() | video_.GetCapabilities());
+  }
+  virtual VoiceMediaChannel *CreateChannel() {
+    return voice_.CreateChannel();
+  }
+  virtual VideoMediaChannel *CreateVideoChannel(VoiceMediaChannel* channel) {
+    return video_.CreateChannel(channel);
+  }
+  virtual SoundclipMedia *CreateSoundclip() {
+    return voice_.CreateSoundclip();
+  }
+
+  virtual bool SetAudioOptions(int o) {
+    return voice_.SetOptions(o);
+  }
+  virtual bool SetVideoOptions(int o) {
+    return video_.SetOptions(o);
+  }
+  virtual bool SetAudioDelayOffset(int offset) {
+    return voice_.SetDelayOffset(offset);
+  }
+  virtual bool SetDefaultVideoEncoderConfig(const VideoEncoderConfig& config) {
+    return video_.SetDefaultEncoderConfig(config);
+  }
+
+  virtual bool SetSoundDevices(const Device* in_device,
+                               const Device* out_device) {
+    return voice_.SetDevices(in_device, out_device);
+  }
+  virtual bool SetVideoCapturer(VideoCapturer* capturer) {
+    return video_.SetVideoCapturer(capturer);
+  }
+  virtual VideoCapturer* GetVideoCapturer() const {
+    return video_.GetVideoCapturer();
+  }
+
+  virtual bool GetOutputVolume(int* level) {
+    return voice_.GetOutputVolume(level);
+  }
+  virtual bool SetOutputVolume(int level) {
+    return voice_.SetOutputVolume(level);
+  }
+
+  virtual int GetInputLevel() {
+    return voice_.GetInputLevel();
+  }
+  virtual bool SetLocalMonitor(bool enable) {
+    return voice_.SetLocalMonitor(enable);
+  }
+  virtual bool SetLocalRenderer(VideoRenderer* renderer) {
+    return video_.SetLocalRenderer(renderer);
+  }
+  virtual bool SetVideoCapture(bool capture) {
+    return video_.SetCapture(capture);
+  }
+
+  virtual const std::vector<AudioCodec>& audio_codecs() {
+    return voice_.codecs();
+  }
+  virtual const std::vector<RtpHeaderExtension>& audio_rtp_header_extensions() {
+    return voice_.rtp_header_extensions();
+  }
+  virtual const std::vector<VideoCodec>& video_codecs() {
+    return video_.codecs();
+  }
+  virtual const std::vector<RtpHeaderExtension>& video_rtp_header_extensions() {
+    return video_.rtp_header_extensions();
+  }
+
+  virtual void SetVoiceLogging(int min_sev, const char* filter) {
+    return voice_.SetLogging(min_sev, filter);
+  }
+  virtual void SetVideoLogging(int min_sev, const char* filter) {
+    return video_.SetLogging(min_sev, filter);
+  }
+
+  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);
+  }
+  virtual VideoFormat GetStartCaptureFormat() const {
+    return video_.GetStartCaptureFormat();
+  }
+  virtual sigslot::repeater2<VideoCapturer*, CaptureState>&
+      SignalVideoCaptureStateChange() {
+    return signal_state_change_;
+  }
+
+ protected:
+  VOICE voice_;
+  VIDEO video_;
+  sigslot::repeater2<VideoCapturer*, CaptureState> signal_state_change_;
+};
+
+// NullVoiceEngine can be used with CompositeMediaEngine in the case where only
+// a video engine is desired.
+class NullVoiceEngine {
+ public:
+  bool Init(talk_base::Thread* worker_thread) { return true; }
+  void Terminate() {}
+  int GetCapabilities() { return 0; }
+  // If you need this to return an actual channel, use FakeMediaEngine instead.
+  VoiceMediaChannel* CreateChannel() {
+    return NULL;
+  }
+  SoundclipMedia* CreateSoundclip() {
+    return NULL;
+  }
+  bool SetDelayOffset(int offset) { return true; }
+  bool SetOptions(int opts) { return true; }
+  bool SetDevices(const Device* in_device, const Device* out_device) {
+    return true;
+  }
+  bool GetOutputVolume(int* level) {
+    *level = 0;
+    return true;
+  }
+  bool SetOutputVolume(int level) { return true; }
+  int GetInputLevel() { return 0; }
+  bool SetLocalMonitor(bool enable) { return true; }
+  const std::vector<AudioCodec>& codecs() { return codecs_; }
+  const std::vector<RtpHeaderExtension>& rtp_header_extensions() {
+    return rtp_header_extensions_;
+  }
+  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_;
+  std::vector<RtpHeaderExtension> rtp_header_extensions_;
+};
+
+// NullVideoEngine can be used with CompositeMediaEngine in the case where only
+// a voice engine is desired.
+class NullVideoEngine {
+ public:
+  bool Init(talk_base::Thread* worker_thread) { return true; }
+  void Terminate() {}
+  int GetCapabilities() { return 0; }
+  // If you need this to return an actual channel, use FakeMediaEngine instead.
+  VideoMediaChannel* CreateChannel(
+      VoiceMediaChannel* voice_media_channel) {
+    return NULL;
+  }
+  bool SetOptions(int opts) { return true; }
+  bool SetDefaultEncoderConfig(const VideoEncoderConfig& config) {
+    return true;
+  }
+  bool SetLocalRenderer(VideoRenderer* renderer) { return true; }
+  bool SetCapture(bool capture) { return true;  }
+  const std::vector<VideoCodec>& codecs() { return codecs_; }
+  const std::vector<RtpHeaderExtension>& rtp_header_extensions() {
+    return rtp_header_extensions_;
+  }
+  void SetLogging(int min_sev, const char* filter) {}
+  VideoFormat GetStartCaptureFormat() const { return VideoFormat(); }
+  bool SetVideoCapturer(VideoCapturer* capturer) { return true; }
+  VideoCapturer* GetVideoCapturer() const { return NULL; }
+
+  sigslot::signal2<VideoCapturer*, CaptureState> SignalCaptureStateChange;
+ private:
+  std::vector<VideoCodec> codecs_;
+  std::vector<RtpHeaderExtension> rtp_header_extensions_;
+};
+
+typedef CompositeMediaEngine<NullVoiceEngine, NullVideoEngine> NullMediaEngine;
+
+enum DataChannelType {
+  DCT_NONE = 0,
+  DCT_RTP = 1,
+  DCT_SCTP = 2
+};
+
+class DataEngineInterface {
+ public:
+  virtual ~DataEngineInterface() {}
+  virtual DataMediaChannel* CreateChannel(DataChannelType type) = 0;
+  virtual const std::vector<DataCodec>& data_codecs() = 0;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_MEDIAENGINE_H_
diff --git a/talk/media/base/mutedvideocapturer.cc b/talk/media/base/mutedvideocapturer.cc
new file mode 100644
index 0000000..0c74b9f
--- /dev/null
+++ b/talk/media/base/mutedvideocapturer.cc
@@ -0,0 +1,135 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/logging.h"
+#include "talk/base/thread.h"
+#include "talk/media/base/mutedvideocapturer.h"
+#include "talk/media/base/videoframe.h"
+
+#if defined(HAVE_WEBRTC_VIDEO)
+#include "talk/media/webrtc/webrtcvideoframe.h"
+#endif  // HAVE_WEBRTC_VIDEO
+
+
+namespace cricket {
+
+const char MutedVideoCapturer::kCapturerId[] = "muted_camera";
+
+class MutedFramesGenerator : public talk_base::MessageHandler {
+ public:
+  explicit MutedFramesGenerator(const VideoFormat& format);
+  virtual ~MutedFramesGenerator();
+
+  // Called every |interval| ms. From |format|.interval given in the
+  // constructor.
+  sigslot::signal1<VideoFrame*> SignalFrame;
+
+ protected:
+  virtual void OnMessage(talk_base::Message* message);
+
+ private:
+  talk_base::Thread capture_thread_;
+  talk_base::scoped_ptr<VideoFrame> muted_frame_;
+  const VideoFormat format_;
+  const int interval_;
+  uint32 create_time_;
+};
+
+MutedFramesGenerator::MutedFramesGenerator(const VideoFormat& format)
+    : format_(format),
+      interval_(static_cast<int>(format.interval /
+                                 talk_base::kNumNanosecsPerMillisec)),
+      create_time_(talk_base::Time()) {
+  capture_thread_.Start();
+  capture_thread_.PostDelayed(interval_, this);
+}
+
+MutedFramesGenerator::~MutedFramesGenerator() { capture_thread_.Clear(this); }
+
+void MutedFramesGenerator::OnMessage(talk_base::Message* message) {
+  // Queue a new frame as soon as possible to minimize drift.
+  capture_thread_.PostDelayed(interval_, this);
+  if (!muted_frame_) {
+#if defined(HAVE_WEBRTC_VIDEO)
+#define VIDEO_FRAME_NAME WebRtcVideoFrame
+#endif
+#if defined(VIDEO_FRAME_NAME)
+    muted_frame_.reset(new VIDEO_FRAME_NAME());
+#else
+    return;
+#endif
+  }
+  uint32 current_timestamp = talk_base::Time();
+  // Delta between create time and current time will be correct even if there is
+  // a wraparound since they are unsigned integers.
+  uint32 elapsed_time = current_timestamp - create_time_;
+  if (!muted_frame_->InitToBlack(format_.width, format_.height, 1, 1,
+                                 elapsed_time, current_timestamp)) {
+    LOG(LS_ERROR) << "Failed to create a black frame.";
+  }
+  SignalFrame(muted_frame_.get());
+}
+
+MutedVideoCapturer::MutedVideoCapturer() { SetId(kCapturerId); }
+
+MutedVideoCapturer::~MutedVideoCapturer() { Stop(); }
+
+bool MutedVideoCapturer::GetBestCaptureFormat(const VideoFormat& desired,
+                                              VideoFormat* best_format) {
+  *best_format = desired;
+  return true;
+}
+
+CaptureState MutedVideoCapturer::Start(const VideoFormat& capture_format) {
+  if (frame_generator_.get()) {
+    return CS_RUNNING;
+  }
+  frame_generator_.reset(new MutedFramesGenerator(capture_format));
+  frame_generator_->SignalFrame
+      .connect(this, &MutedVideoCapturer::OnMutedFrame);
+  SetCaptureFormat(&capture_format);
+  return CS_RUNNING;
+}
+
+void MutedVideoCapturer::Stop() {
+  frame_generator_.reset();
+  SetCaptureFormat(NULL);
+}
+
+bool MutedVideoCapturer::IsRunning() { return frame_generator_.get() != NULL; }
+
+bool MutedVideoCapturer::GetPreferredFourccs(std::vector<uint32>* fourccs) {
+  fourccs->clear();
+  fourccs->push_back(cricket::FOURCC_I420);
+  return true;
+}
+
+void MutedVideoCapturer::OnMutedFrame(VideoFrame* muted_frame) {
+  SignalVideoFrame(this, muted_frame);
+}
+
+}  // namespace cricket
diff --git a/talk/media/base/mutedvideocapturer.h b/talk/media/base/mutedvideocapturer.h
new file mode 100644
index 0000000..fb249a9
--- /dev/null
+++ b/talk/media/base/mutedvideocapturer.h
@@ -0,0 +1,60 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_MEDIA_BASE_MUTEDVIDEOCAPTURER_H_
+#define TALK_MEDIA_BASE_MUTEDVIDEOCAPTURER_H_
+
+#include "talk/base/thread.h"
+#include "talk/media/base/videocapturer.h"
+
+namespace cricket {
+
+class MutedFramesGenerator;
+
+class MutedVideoCapturer : public VideoCapturer {
+ public:
+  static const char kCapturerId[];
+
+  MutedVideoCapturer();
+  virtual ~MutedVideoCapturer();
+  virtual bool GetBestCaptureFormat(const VideoFormat& desired,
+                                    VideoFormat* best_format);
+  virtual CaptureState Start(const VideoFormat& capture_format);
+  virtual void Stop();
+  virtual bool IsRunning();
+  virtual bool IsScreencast() const { return false; }
+  virtual bool GetPreferredFourccs(std::vector<uint32>* fourccs);
+
+ protected:
+  void OnMutedFrame(VideoFrame* muted_frame);
+
+  talk_base::scoped_ptr<MutedFramesGenerator> frame_generator_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_MUTEDVIDEOCAPTURER_H_
diff --git a/talk/media/base/mutedvideocapturer_unittest.cc b/talk/media/base/mutedvideocapturer_unittest.cc
new file mode 100644
index 0000000..dfb56df
--- /dev/null
+++ b/talk/media/base/mutedvideocapturer_unittest.cc
@@ -0,0 +1,96 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/media/base/mutedvideocapturer.h"
+
+#include "talk/base/gunit.h"
+#include "talk/media/base/videoframe.h"
+
+class MutedVideoCapturerTest : public sigslot::has_slots<>,
+                               public testing::Test {
+ protected:
+  void SetUp() {
+    frames_received_ = 0;
+    capturer_.SignalVideoFrame
+        .connect(this, &MutedVideoCapturerTest::OnVideoFrame);
+  }
+  void OnVideoFrame(cricket::VideoCapturer* capturer,
+                    const cricket::VideoFrame* muted_frame) {
+    EXPECT_EQ(capturer, &capturer_);
+    ++frames_received_;
+    received_width_ = muted_frame->GetWidth();
+    received_height_ = muted_frame->GetHeight();
+  }
+  int frames_received() { return frames_received_; }
+  bool ReceivedCorrectFormat() {
+    return (received_width_ == capturer_.GetCaptureFormat()->width) &&
+           (received_height_ == capturer_.GetCaptureFormat()->height);
+  }
+
+  cricket::MutedVideoCapturer capturer_;
+  int frames_received_;
+  cricket::VideoFormat capture_format_;
+  int received_width_;
+  int received_height_;
+};
+
+TEST_F(MutedVideoCapturerTest, GetBestCaptureFormat) {
+  cricket::VideoFormat format(640, 360, cricket::VideoFormat::FpsToInterval(30),
+                              cricket::FOURCC_I420);
+  cricket::VideoFormat best_format;
+  EXPECT_TRUE(capturer_.GetBestCaptureFormat(format, &best_format));
+  EXPECT_EQ(format.width, best_format.width);
+  EXPECT_EQ(format.height, best_format.height);
+  EXPECT_EQ(format.interval, best_format.interval);
+  EXPECT_EQ(format.fourcc, best_format.fourcc);
+}
+
+TEST_F(MutedVideoCapturerTest, IsScreencast) {
+  EXPECT_FALSE(capturer_.IsScreencast());
+}
+
+TEST_F(MutedVideoCapturerTest, GetPreferredFourccs) {
+  std::vector<uint32> fourccs;
+  EXPECT_TRUE(capturer_.GetPreferredFourccs(&fourccs));
+  EXPECT_EQ(fourccs.size(), 1u);
+  EXPECT_TRUE(capturer_.GetPreferredFourccs(&fourccs));
+  EXPECT_EQ(fourccs.size(), 1u);
+  EXPECT_EQ(fourccs[0], cricket::FOURCC_I420);
+}
+
+TEST_F(MutedVideoCapturerTest, Capturing) {
+  cricket::VideoFormat format(640, 360, cricket::VideoFormat::FpsToInterval(30),
+                              cricket::FOURCC_I420);
+  EXPECT_EQ(capturer_.Start(format), cricket::CS_RUNNING);
+  EXPECT_EQ(capturer_.Start(format), cricket::CS_RUNNING);
+  EXPECT_TRUE(capturer_.IsRunning());
+  // 100 ms should be enough to receive 3 frames at FPS of 30.
+  EXPECT_EQ_WAIT(frames_received(), 1, 100);
+  EXPECT_TRUE(ReceivedCorrectFormat());
+  capturer_.Stop();
+  EXPECT_FALSE(capturer_.IsRunning());
+}
diff --git a/talk/media/base/nullvideoframe.h b/talk/media/base/nullvideoframe.h
new file mode 100644
index 0000000..ff29129
--- /dev/null
+++ b/talk/media/base/nullvideoframe.h
@@ -0,0 +1,93 @@
+/*
+ * 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_MEDIA_BASE_NULLVIDEOFRAME_H_
+#define TALK_MEDIA_BASE_NULLVIDEOFRAME_H_
+
+#include "talk/media/base/videoframe.h"
+
+namespace cricket {
+
+// Simple subclass for use in mocks.
+class NullVideoFrame : public VideoFrame {
+ public:
+  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) {
+    return false;
+  }
+  virtual bool InitToBlack(int w, int h, size_t pixel_width,
+                           size_t pixel_height, int64 elapsed_time,
+                           int64 time_stamp) {
+    return false;
+  }
+  virtual size_t GetWidth() const { return 0; }
+  virtual size_t GetHeight() const { return 0; }
+  virtual const uint8 *GetYPlane() const { return NULL; }
+  virtual const uint8 *GetUPlane() const { return NULL; }
+  virtual const uint8 *GetVPlane() const { return NULL; }
+  virtual uint8 *GetYPlane() { return NULL; }
+  virtual uint8 *GetUPlane() { return NULL; }
+  virtual uint8 *GetVPlane() { return NULL; }
+  virtual int32 GetYPitch() const { return 0; }
+  virtual int32 GetUPitch() const { return 0; }
+  virtual int32 GetVPitch() const { return 0; }
+
+  virtual size_t GetPixelWidth() const { return 1; }
+  virtual size_t GetPixelHeight() const { return 1; }
+  virtual int64 GetElapsedTime() const { return 0; }
+  virtual int64 GetTimeStamp() const { return 0; }
+  virtual void SetElapsedTime(int64 elapsed_time) {}
+  virtual void SetTimeStamp(int64 time_stamp) {}
+  virtual int GetRotation() const { return 0; }
+
+  virtual VideoFrame *Copy() const { return NULL; }
+
+  virtual bool MakeExclusive() { return false; }
+
+  virtual size_t CopyToBuffer(uint8 *buffer, size_t size) const { return 0; }
+
+  virtual size_t ConvertToRgbBuffer(uint32 to_fourcc, uint8 *buffer,
+                                    size_t size, int stride_rgb) const {
+    return 0;
+  }
+
+  virtual void StretchToPlanes(
+      uint8 *y, uint8 *u, uint8 *v, int32 pitchY, int32 pitchU, int32 pitchV,
+      size_t width, size_t height, bool interpolate, bool crop) const {}
+
+  virtual VideoFrame *CreateEmptyFrame(int w, int h, size_t pixel_width,
+                                       size_t pixel_height, int64 elapsed_time,
+                                       int64 time_stamp) const {
+    return NULL;
+  }
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_NULLVIDEOFRAME_H_
diff --git a/talk/media/base/nullvideorenderer.h b/talk/media/base/nullvideorenderer.h
new file mode 100644
index 0000000..4652c47
--- /dev/null
+++ b/talk/media/base/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_MEDIA_BASE_NULLVIDEORENDERER_H_
+#define TALK_MEDIA_BASE_NULLVIDEORENDERER_H_
+
+#include "talk/media/base/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_MEDIA_BASE_NULLVIDEORENDERER_H_
diff --git a/talk/media/base/rtpdataengine.cc b/talk/media/base/rtpdataengine.cc
new file mode 100644
index 0000000..5a39a54
--- /dev/null
+++ b/talk/media/base/rtpdataengine.cc
@@ -0,0 +1,381 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/media/base/rtpdataengine.h"
+
+#include "talk/base/buffer.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/ratelimiter.h"
+#include "talk/base/timing.h"
+#include "talk/media/base/codec.h"
+#include "talk/media/base/constants.h"
+#include "talk/media/base/rtputils.h"
+#include "talk/media/base/streamparams.h"
+
+namespace cricket {
+
+// We want to avoid IP fragmentation.
+static const size_t kDataMaxRtpPacketLen = 1200U;
+// We reserve space after the RTP header for future wiggle room.
+static const unsigned char kReservedSpace[] = {
+  0x00, 0x00, 0x00, 0x00
+};
+
+// Amount of overhead SRTP may take.  We need to leave room in the
+// buffer for it, otherwise SRTP will fail later.  If SRTP ever uses
+// more than this, we need to increase this number.
+static const size_t kMaxSrtpHmacOverhead = 16;
+
+RtpDataEngine::RtpDataEngine() {
+  data_codecs_.push_back(
+      DataCodec(kGoogleRtpDataCodecId,
+                kGoogleRtpDataCodecName, 0));
+  SetTiming(new talk_base::Timing());
+}
+
+DataMediaChannel* RtpDataEngine::CreateChannel(
+    DataChannelType data_channel_type) {
+  if (data_channel_type != DCT_RTP) {
+    return NULL;
+  }
+  return new RtpDataMediaChannel(timing_.get());
+}
+
+// TODO(pthatcher): Should we move these find/get functions somewhere
+// common?
+bool FindCodecById(const std::vector<DataCodec>& codecs,
+                   int id, DataCodec* codec_out) {
+  std::vector<DataCodec>::const_iterator iter;
+  for (iter = codecs.begin(); iter != codecs.end(); ++iter) {
+    if (iter->id == id) {
+      *codec_out = *iter;
+      return true;
+    }
+  }
+  return false;
+}
+
+bool FindCodecByName(const std::vector<DataCodec>& codecs,
+                     const std::string& name, DataCodec* codec_out) {
+  std::vector<DataCodec>::const_iterator iter;
+  for (iter = codecs.begin(); iter != codecs.end(); ++iter) {
+    if (iter->name == name) {
+      *codec_out = *iter;
+      return true;
+    }
+  }
+  return false;
+}
+
+RtpDataMediaChannel::RtpDataMediaChannel(talk_base::Timing* timing) {
+  Construct(timing);
+}
+
+RtpDataMediaChannel::RtpDataMediaChannel() {
+  Construct(NULL);
+}
+
+void RtpDataMediaChannel::Construct(talk_base::Timing* timing) {
+  sending_ = false;
+  receiving_ = false;
+  timing_ = timing;
+  send_limiter_.reset(new talk_base::RateLimiter(kDataMaxBandwidth / 8, 1.0));
+}
+
+
+RtpDataMediaChannel::~RtpDataMediaChannel() {
+  std::map<uint32, RtpClock*>::const_iterator iter;
+  for (iter = rtp_clock_by_send_ssrc_.begin();
+       iter != rtp_clock_by_send_ssrc_.end();
+       ++iter) {
+    delete iter->second;
+  }
+}
+
+void RtpClock::Tick(
+    double now, int* seq_num, uint32* timestamp) {
+  *seq_num = ++last_seq_num_;
+  *timestamp = timestamp_offset_ + static_cast<uint32>(now * clockrate_);
+}
+
+const DataCodec* FindUnknownCodec(const std::vector<DataCodec>& codecs) {
+  DataCodec data_codec(kGoogleRtpDataCodecId, kGoogleRtpDataCodecName, 0);
+  std::vector<DataCodec>::const_iterator iter;
+  for (iter = codecs.begin(); iter != codecs.end(); ++iter) {
+    if (!iter->Matches(data_codec)) {
+      return &(*iter);
+    }
+  }
+  return NULL;
+}
+
+const DataCodec* FindKnownCodec(const std::vector<DataCodec>& codecs) {
+  DataCodec data_codec(kGoogleRtpDataCodecId, kGoogleRtpDataCodecName, 0);
+  std::vector<DataCodec>::const_iterator iter;
+  for (iter = codecs.begin(); iter != codecs.end(); ++iter) {
+    if (iter->Matches(data_codec)) {
+      return &(*iter);
+    }
+  }
+  return NULL;
+}
+
+bool RtpDataMediaChannel::SetRecvCodecs(const std::vector<DataCodec>& codecs) {
+  const DataCodec* unknown_codec = FindUnknownCodec(codecs);
+  if (unknown_codec) {
+    LOG(LS_WARNING) << "Failed to SetRecvCodecs because of unknown codec: "
+                    << unknown_codec->ToString();
+    return false;
+  }
+
+  recv_codecs_ = codecs;
+  return true;
+}
+
+bool RtpDataMediaChannel::SetSendCodecs(const std::vector<DataCodec>& codecs) {
+  const DataCodec* known_codec = FindKnownCodec(codecs);
+  if (!known_codec) {
+    LOG(LS_WARNING) <<
+        "Failed to SetSendCodecs because there is no known codec.";
+    return false;
+  }
+
+  send_codecs_ = codecs;
+  return true;
+}
+
+bool RtpDataMediaChannel::AddSendStream(const StreamParams& stream) {
+  if (!stream.has_ssrcs()) {
+    return false;
+  }
+
+  StreamParams found_stream;
+  if (GetStreamBySsrc(send_streams_, stream.first_ssrc(), &found_stream)) {
+    LOG(LS_WARNING) << "Not adding data send stream '" << stream.id
+                    << "' with ssrc=" << stream.first_ssrc()
+                    << " because stream already exists.";
+    return false;
+  }
+
+  send_streams_.push_back(stream);
+  // TODO(pthatcher): This should be per-stream, not per-ssrc.
+  // And we should probably allow more than one per stream.
+  rtp_clock_by_send_ssrc_[stream.first_ssrc()] = new RtpClock(
+      kDataCodecClockrate,
+      talk_base::CreateRandomNonZeroId(), talk_base::CreateRandomNonZeroId());
+
+  LOG(LS_INFO) << "Added data send stream '" << stream.id
+               << "' with ssrc=" << stream.first_ssrc();
+  return true;
+}
+
+bool RtpDataMediaChannel::RemoveSendStream(uint32 ssrc) {
+  StreamParams found_stream;
+  if (!GetStreamBySsrc(send_streams_, ssrc, &found_stream)) {
+    return false;
+  }
+
+  RemoveStreamBySsrc(&send_streams_, ssrc);
+  delete rtp_clock_by_send_ssrc_[ssrc];
+  rtp_clock_by_send_ssrc_.erase(ssrc);
+  return true;
+}
+
+bool RtpDataMediaChannel::AddRecvStream(const StreamParams& stream) {
+  if (!stream.has_ssrcs()) {
+    return false;
+  }
+
+  StreamParams found_stream;
+  if (GetStreamBySsrc(recv_streams_, stream.first_ssrc(), &found_stream)) {
+    LOG(LS_WARNING) << "Not adding data recv stream '" << stream.id
+                    << "' with ssrc=" << stream.first_ssrc()
+                    << " because stream already exists.";
+    return false;
+  }
+
+  recv_streams_.push_back(stream);
+  LOG(LS_INFO) << "Added data recv stream '" << stream.id
+               << "' with ssrc=" << stream.first_ssrc();
+  return true;
+}
+
+bool RtpDataMediaChannel::RemoveRecvStream(uint32 ssrc) {
+  RemoveStreamBySsrc(&recv_streams_, ssrc);
+  return true;
+}
+
+void RtpDataMediaChannel::OnPacketReceived(talk_base::Buffer* packet) {
+  RtpHeader header;
+  if (!GetRtpHeader(packet->data(), packet->length(), &header)) {
+    // Don't want to log for every corrupt packet.
+    // LOG(LS_WARNING) << "Could not read rtp header from packet of length "
+    //                 << packet->length() << ".";
+    return;
+  }
+
+  size_t header_length;
+  if (!GetRtpHeaderLen(packet->data(), packet->length(), &header_length)) {
+    // Don't want to log for every corrupt packet.
+    // LOG(LS_WARNING) << "Could not read rtp header"
+    //                 << length from packet of length "
+    //                 << packet->length() << ".";
+    return;
+  }
+  const char* data = packet->data() + header_length + sizeof(kReservedSpace);
+  size_t data_len = packet->length() - header_length - sizeof(kReservedSpace);
+
+  if (!receiving_) {
+    LOG(LS_WARNING) << "Not receiving packet "
+                    << header.ssrc << ":" << header.seq_num
+                    << " before SetReceive(true) called.";
+    return;
+  }
+
+  DataCodec codec;
+  if (!FindCodecById(recv_codecs_, header.payload_type, &codec)) {
+    LOG(LS_WARNING) << "Not receiving packet "
+                    << header.ssrc << ":" << header.seq_num
+                    << " (" << data_len << ")"
+                    << " because unknown payload id: " << header.payload_type;
+    return;
+  }
+
+  StreamParams found_stream;
+  if (!GetStreamBySsrc(recv_streams_, header.ssrc, &found_stream)) {
+    LOG(LS_WARNING) << "Received packet for unknown ssrc: " << header.ssrc;
+    return;
+  }
+
+  // Uncomment this for easy debugging.
+  // LOG(LS_INFO) << "Received packet"
+  //              << " groupid=" << found_stream.groupid
+  //              << ", ssrc=" << header.ssrc
+  //              << ", seqnum=" << header.seq_num
+  //              << ", timestamp=" << header.timestamp
+  //              << ", len=" << data_len;
+
+  ReceiveDataParams params;
+  params.ssrc = header.ssrc;
+  params.seq_num = header.seq_num;
+  params.timestamp = header.timestamp;
+  SignalDataReceived(params, data, data_len);
+}
+
+bool RtpDataMediaChannel::SetSendBandwidth(bool autobw, int bps) {
+  if (autobw || bps <= 0) {
+    bps = kDataMaxBandwidth;
+  }
+  send_limiter_.reset(new talk_base::RateLimiter(bps / 8, 1.0));
+  LOG(LS_INFO) << "RtpDataMediaChannel::SetSendBandwidth to " << bps << "bps.";
+  return true;
+}
+
+bool RtpDataMediaChannel::SendData(
+    const SendDataParams& params,
+    const talk_base::Buffer& payload,
+    SendDataResult* result) {
+  if (result) {
+    // If we return true, we'll set this to SDR_SUCCESS.
+    *result = SDR_ERROR;
+  }
+  if (!sending_) {
+    LOG(LS_WARNING) << "Not sending packet with ssrc=" << params.ssrc
+                    << " len=" << payload.length() << " before SetSend(true).";
+    return false;
+  }
+
+  if (params.type != cricket::DMT_TEXT) {
+    LOG(LS_WARNING) << "Not sending data because binary type is unsupported.";
+    return false;
+  }
+
+  StreamParams found_stream;
+  if (!GetStreamBySsrc(send_streams_, params.ssrc, &found_stream)) {
+    LOG(LS_WARNING) << "Not sending data because ssrc is unknown: "
+                    << params.ssrc;
+    return false;
+  }
+
+  DataCodec found_codec;
+  if (!FindCodecByName(send_codecs_, kGoogleRtpDataCodecName, &found_codec)) {
+    LOG(LS_WARNING) << "Not sending data because codec is unknown: "
+                    << kGoogleRtpDataCodecName;
+    return false;
+  }
+
+  size_t packet_len = (kMinRtpPacketLen + sizeof(kReservedSpace)
+                       + payload.length() + kMaxSrtpHmacOverhead);
+  if (packet_len > kDataMaxRtpPacketLen) {
+    return false;
+  }
+
+  double now = timing_->TimerNow();
+
+  if (!send_limiter_->CanUse(packet_len, now)) {
+    LOG(LS_VERBOSE) << "Dropped data packet of len=" << packet_len
+                    << "; already sent " << send_limiter_->used_in_period()
+                    << "/" << send_limiter_->max_per_period();
+    return false;
+  } else {
+    LOG(LS_VERBOSE) << "Sending data packet of len=" << packet_len
+                    << "; already sent " << send_limiter_->used_in_period()
+                    << "/" << send_limiter_->max_per_period();
+  }
+
+  RtpHeader header;
+  header.payload_type = found_codec.id;
+  header.ssrc = params.ssrc;
+  rtp_clock_by_send_ssrc_[header.ssrc]->Tick(
+      now, &header.seq_num, &header.timestamp);
+
+  talk_base::Buffer packet;
+  packet.SetCapacity(packet_len);
+  packet.SetLength(kMinRtpPacketLen);
+  if (!SetRtpHeader(packet.data(), packet.length(), header)) {
+    return false;
+  }
+  packet.AppendData(&kReservedSpace, sizeof(kReservedSpace));
+  packet.AppendData(payload.data(), payload.length());
+
+  // Uncomment this for easy debugging.
+  // LOG(LS_INFO) << "Sent packet: "
+  //              << " stream=" << found_stream.id
+  //              << ", seqnum=" << header.seq_num
+  //              << ", timestamp=" << header.timestamp
+  //              << ", len=" << data_len;
+
+  network_interface()->SendPacket(&packet);
+  send_limiter_->Use(packet_len, now);
+  if (result) {
+    *result = SDR_SUCCESS;
+  }
+  return true;
+}
+
+}  // namespace cricket
diff --git a/talk/media/base/rtpdataengine.h b/talk/media/base/rtpdataengine.h
new file mode 100644
index 0000000..bc7b667
--- /dev/null
+++ b/talk/media/base/rtpdataengine.h
@@ -0,0 +1,142 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_MEDIA_BASE_RTPDATAENGINE_H_
+#define TALK_MEDIA_BASE_RTPDATAENGINE_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/timing.h"
+#include "talk/media/base/constants.h"
+#include "talk/media/base/mediachannel.h"
+#include "talk/media/base/mediaengine.h"
+
+namespace cricket {
+
+struct DataCodec;
+
+class RtpDataEngine : public DataEngineInterface {
+ public:
+  RtpDataEngine();
+
+  virtual DataMediaChannel* CreateChannel(DataChannelType data_channel_type);
+
+  virtual const std::vector<DataCodec>& data_codecs() {
+    return data_codecs_;
+  }
+
+  // Mostly for testing with a fake clock.  Ownership is passed in.
+  void SetTiming(talk_base::Timing* timing) {
+    timing_.reset(timing);
+  }
+
+ private:
+  std::vector<DataCodec> data_codecs_;
+  talk_base::scoped_ptr<talk_base::Timing> timing_;
+};
+
+// Keep track of sequence number and timestamp of an RTP stream.  The
+// sequence number starts with a "random" value and increments.  The
+// timestamp starts with a "random" value and increases monotonically
+// according to the clockrate.
+class RtpClock {
+ public:
+  RtpClock(int clockrate, uint16 first_seq_num, uint32 timestamp_offset)
+      : clockrate_(clockrate),
+        last_seq_num_(first_seq_num),
+        timestamp_offset_(timestamp_offset) {
+  }
+
+  // Given the current time (in number of seconds which must be
+  // monotonically increasing), Return the next sequence number and
+  // timestamp.
+  void Tick(double now, int* seq_num, uint32* timestamp);
+
+ private:
+  int clockrate_;
+  uint16 last_seq_num_;
+  uint32 timestamp_offset_;
+};
+
+class RtpDataMediaChannel : public DataMediaChannel {
+ public:
+  // Timing* Used for the RtpClock
+  explicit RtpDataMediaChannel(talk_base::Timing* timing);
+  // Sets Timing == NULL, so you'll need to call set_timer() before
+  // using it.  This is needed by FakeMediaEngine.
+  RtpDataMediaChannel();
+  virtual ~RtpDataMediaChannel();
+
+  void set_timing(talk_base::Timing* timing) {
+    timing_ = timing;
+  }
+
+  virtual bool SetSendBandwidth(bool autobw, int bps);
+  virtual bool SetRecvRtpHeaderExtensions(
+      const std::vector<RtpHeaderExtension>& extensions) { return true; }
+  virtual bool SetSendRtpHeaderExtensions(
+      const std::vector<RtpHeaderExtension>& extensions) { return true; }
+  virtual bool SetSendCodecs(const std::vector<DataCodec>& codecs);
+  virtual bool SetRecvCodecs(const std::vector<DataCodec>& codecs);
+  virtual bool AddSendStream(const StreamParams& sp);
+  virtual bool RemoveSendStream(uint32 ssrc);
+  virtual bool AddRecvStream(const StreamParams& sp);
+  virtual bool RemoveRecvStream(uint32 ssrc);
+  virtual bool SetSend(bool send) {
+    sending_ = send;
+    return true;
+  }
+  virtual bool SetReceive(bool receive) {
+    receiving_ = receive;
+    return true;
+  }
+  virtual void OnPacketReceived(talk_base::Buffer* packet);
+  virtual void OnRtcpReceived(talk_base::Buffer* packet) {}
+  virtual void OnReadyToSend(bool ready) {}
+  virtual bool SendData(
+    const SendDataParams& params,
+    const talk_base::Buffer& payload,
+    SendDataResult* result);
+
+ private:
+  void Construct(talk_base::Timing* timing);
+
+  bool sending_;
+  bool receiving_;
+  talk_base::Timing* timing_;
+  std::vector<DataCodec> send_codecs_;
+  std::vector<DataCodec> recv_codecs_;
+  std::vector<StreamParams> send_streams_;
+  std::vector<StreamParams> recv_streams_;
+  std::map<uint32, RtpClock*> rtp_clock_by_send_ssrc_;
+  talk_base::scoped_ptr<talk_base::RateLimiter> send_limiter_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_RTPDATAENGINE_H_
diff --git a/talk/media/base/rtpdataengine_unittest.cc b/talk/media/base/rtpdataengine_unittest.cc
new file mode 100644
index 0000000..23b57d4
--- /dev/null
+++ b/talk/media/base/rtpdataengine_unittest.cc
@@ -0,0 +1,463 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/buffer.h"
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/timing.h"
+#include "talk/media/base/constants.h"
+#include "talk/media/base/fakenetworkinterface.h"
+#include "talk/media/base/rtpdataengine.h"
+#include "talk/media/base/rtputils.h"
+
+class FakeTiming : public talk_base::Timing {
+ public:
+  FakeTiming() : now_(0.0) {}
+
+  virtual double TimerNow() {
+    return now_;
+  }
+
+  void set_now(double now) {
+    now_ = now;
+  }
+
+ private:
+  double now_;
+};
+
+class FakeDataReceiver : public sigslot::has_slots<> {
+ public:
+  FakeDataReceiver() : has_received_data_(false) {}
+
+  void OnDataReceived(
+      const cricket::ReceiveDataParams& params,
+      const char* data, size_t len) {
+    has_received_data_ = true;
+    last_received_data_ = std::string(data, len);
+    last_received_data_len_ = len;
+    last_received_data_params_ = params;
+  }
+
+  bool has_received_data() const { return has_received_data_; }
+  std::string last_received_data() const { return last_received_data_; }
+  size_t last_received_data_len() const { return last_received_data_len_; }
+  cricket::ReceiveDataParams last_received_data_params() const {
+    return last_received_data_params_;
+  }
+
+ private:
+  bool has_received_data_;
+  std::string last_received_data_;
+  size_t last_received_data_len_;
+  cricket::ReceiveDataParams last_received_data_params_;
+};
+
+class RtpDataMediaChannelTest : public testing::Test {
+ protected:
+  virtual void SetUp() {
+    // Seed needed for each test to satisfy expectations.
+    iface_.reset(new cricket::FakeNetworkInterface());
+    timing_ = new FakeTiming();
+    dme_.reset(CreateEngine(timing_));
+    receiver_.reset(new FakeDataReceiver());
+  }
+
+  void SetNow(double now) {
+    timing_->set_now(now);
+  }
+
+  cricket::RtpDataEngine* CreateEngine(FakeTiming* timing) {
+    cricket::RtpDataEngine* dme = new cricket::RtpDataEngine();
+    dme->SetTiming(timing);
+    return dme;
+  }
+
+  cricket::RtpDataMediaChannel* CreateChannel() {
+    return CreateChannel(dme_.get());
+  }
+
+  cricket::RtpDataMediaChannel* CreateChannel(cricket::RtpDataEngine* dme) {
+    cricket::RtpDataMediaChannel* channel =
+        static_cast<cricket::RtpDataMediaChannel*>(dme->CreateChannel(
+            cricket::DCT_RTP));
+    channel->SetInterface(iface_.get());
+    channel->SignalDataReceived.connect(
+        receiver_.get(), &FakeDataReceiver::OnDataReceived);
+    return channel;
+  }
+
+  FakeDataReceiver* receiver() {
+    return receiver_.get();
+  }
+
+  bool HasReceivedData() {
+    return receiver_->has_received_data();
+  }
+
+  std::string GetReceivedData() {
+    return receiver_->last_received_data();
+  }
+
+  size_t GetReceivedDataLen() {
+    return receiver_->last_received_data_len();
+  }
+
+  cricket::ReceiveDataParams GetReceivedDataParams() {
+    return receiver_->last_received_data_params();
+  }
+
+  bool HasSentData(int count) {
+    return (iface_->NumRtpPackets() > count);
+  }
+
+  std::string GetSentData(int index) {
+    // Assume RTP header of length 12
+    const talk_base::Buffer* packet = iface_->GetRtpPacket(index);
+    if (packet->length() > 12) {
+      return std::string(packet->data() + 12, packet->length() - 12);
+    } else {
+      return "";
+    }
+  }
+
+  cricket::RtpHeader GetSentDataHeader(int index) {
+    const talk_base::Buffer* packet = iface_->GetRtpPacket(index);
+    cricket::RtpHeader header;
+    GetRtpHeader(packet->data(), packet->length(), &header);
+    return header;
+  }
+
+ private:
+  talk_base::scoped_ptr<cricket::RtpDataEngine> dme_;
+  // Timing passed into dme_.  Owned by dme_;
+  FakeTiming* timing_;
+  talk_base::scoped_ptr<cricket::FakeNetworkInterface> iface_;
+  talk_base::scoped_ptr<FakeDataReceiver> receiver_;
+};
+
+TEST_F(RtpDataMediaChannelTest, SetUnknownCodecs) {
+  talk_base::scoped_ptr<cricket::RtpDataMediaChannel> dmc(CreateChannel());
+
+  cricket::DataCodec known_codec;
+  known_codec.id = 103;
+  known_codec.name = "google-data";
+  cricket::DataCodec unknown_codec;
+  unknown_codec.id = 104;
+  unknown_codec.name = "unknown-data";
+
+  std::vector<cricket::DataCodec> known_codecs;
+  known_codecs.push_back(known_codec);
+
+  std::vector<cricket::DataCodec> unknown_codecs;
+  unknown_codecs.push_back(unknown_codec);
+
+  std::vector<cricket::DataCodec> mixed_codecs;
+  mixed_codecs.push_back(known_codec);
+  mixed_codecs.push_back(unknown_codec);
+
+  EXPECT_TRUE(dmc->SetSendCodecs(known_codecs));
+  EXPECT_FALSE(dmc->SetSendCodecs(unknown_codecs));
+  EXPECT_TRUE(dmc->SetSendCodecs(mixed_codecs));
+  EXPECT_TRUE(dmc->SetRecvCodecs(known_codecs));
+  EXPECT_FALSE(dmc->SetRecvCodecs(unknown_codecs));
+  EXPECT_FALSE(dmc->SetRecvCodecs(mixed_codecs));
+}
+
+TEST_F(RtpDataMediaChannelTest, AddRemoveSendStream) {
+  talk_base::scoped_ptr<cricket::RtpDataMediaChannel> dmc(CreateChannel());
+
+  cricket::StreamParams stream1;
+  stream1.add_ssrc(41);
+  EXPECT_TRUE(dmc->AddSendStream(stream1));
+  cricket::StreamParams stream2;
+  stream2.add_ssrc(42);
+  EXPECT_TRUE(dmc->AddSendStream(stream2));
+
+  EXPECT_TRUE(dmc->RemoveSendStream(41));
+  EXPECT_TRUE(dmc->RemoveSendStream(42));
+  EXPECT_FALSE(dmc->RemoveSendStream(43));
+}
+
+TEST_F(RtpDataMediaChannelTest, AddRemoveRecvStream) {
+  talk_base::scoped_ptr<cricket::RtpDataMediaChannel> dmc(CreateChannel());
+
+  cricket::StreamParams stream1;
+  stream1.add_ssrc(41);
+  EXPECT_TRUE(dmc->AddRecvStream(stream1));
+  cricket::StreamParams stream2;
+  stream2.add_ssrc(42);
+  EXPECT_TRUE(dmc->AddRecvStream(stream2));
+  EXPECT_FALSE(dmc->AddRecvStream(stream2));
+
+  EXPECT_TRUE(dmc->RemoveRecvStream(41));
+  EXPECT_TRUE(dmc->RemoveRecvStream(42));
+}
+
+TEST_F(RtpDataMediaChannelTest, SendData) {
+  talk_base::scoped_ptr<cricket::RtpDataMediaChannel> dmc(CreateChannel());
+
+  cricket::SendDataParams params;
+  params.ssrc = 42;
+  unsigned char data[] = "food";
+  talk_base::Buffer payload(data, 4);
+  unsigned char padded_data[] = {
+    0x00, 0x00, 0x00, 0x00,
+    'f', 'o', 'o', 'd',
+  };
+  cricket::SendDataResult result;
+
+  // Not sending
+  EXPECT_FALSE(dmc->SendData(params, payload, &result));
+  EXPECT_EQ(cricket::SDR_ERROR, result);
+  EXPECT_FALSE(HasSentData(0));
+  ASSERT_TRUE(dmc->SetSend(true));
+
+  // Unknown stream name.
+  EXPECT_FALSE(dmc->SendData(params, payload, &result));
+  EXPECT_EQ(cricket::SDR_ERROR, result);
+  EXPECT_FALSE(HasSentData(0));
+
+  cricket::StreamParams stream;
+  stream.add_ssrc(42);
+  ASSERT_TRUE(dmc->AddSendStream(stream));
+
+  // Unknown codec;
+  EXPECT_FALSE(dmc->SendData(params, payload, &result));
+  EXPECT_EQ(cricket::SDR_ERROR, result);
+  EXPECT_FALSE(HasSentData(0));
+
+  cricket::DataCodec codec;
+  codec.id = 103;
+  codec.name = cricket::kGoogleRtpDataCodecName;
+  std::vector<cricket::DataCodec> codecs;
+  codecs.push_back(codec);
+  ASSERT_TRUE(dmc->SetSendCodecs(codecs));
+
+  // Length too large;
+  std::string x10000(10000, 'x');
+  EXPECT_FALSE(dmc->SendData(
+      params, talk_base::Buffer(x10000.data(), x10000.length()), &result));
+  EXPECT_EQ(cricket::SDR_ERROR, result);
+  EXPECT_FALSE(HasSentData(0));
+
+  // Finally works!
+  EXPECT_TRUE(dmc->SendData(params, payload, &result));
+  EXPECT_EQ(cricket::SDR_SUCCESS, result);
+  ASSERT_TRUE(HasSentData(0));
+  EXPECT_EQ(sizeof(padded_data), GetSentData(0).length());
+  EXPECT_EQ(0, memcmp(
+      padded_data, GetSentData(0).data(), sizeof(padded_data)));
+  cricket::RtpHeader header0 = GetSentDataHeader(0);
+  EXPECT_NE(0, header0.seq_num);
+  EXPECT_NE(0U, header0.timestamp);
+  EXPECT_EQ(header0.ssrc, 42U);
+  EXPECT_EQ(header0.payload_type, 103);
+
+  // Should bump timestamp by 180000 because the clock rate is 90khz.
+  SetNow(2);
+
+  EXPECT_TRUE(dmc->SendData(params, payload, &result));
+  ASSERT_TRUE(HasSentData(1));
+  EXPECT_EQ(sizeof(padded_data), GetSentData(1).length());
+  EXPECT_EQ(0, memcmp(
+      padded_data, GetSentData(1).data(), sizeof(padded_data)));
+  cricket::RtpHeader header1 = GetSentDataHeader(1);
+  EXPECT_EQ(header1.ssrc, 42U);
+  EXPECT_EQ(header1.payload_type, 103);
+  EXPECT_EQ(header0.seq_num + 1, header1.seq_num);
+  EXPECT_EQ(header0.timestamp + 180000, header1.timestamp);
+}
+
+TEST_F(RtpDataMediaChannelTest, SendDataMultipleClocks) {
+  // Timings owned by RtpDataEngines.
+  FakeTiming* timing1 = new FakeTiming();
+  talk_base::scoped_ptr<cricket::RtpDataEngine> dme1(CreateEngine(timing1));
+  talk_base::scoped_ptr<cricket::RtpDataMediaChannel> dmc1(
+      CreateChannel(dme1.get()));
+  FakeTiming* timing2 = new FakeTiming();
+  talk_base::scoped_ptr<cricket::RtpDataEngine> dme2(CreateEngine(timing2));
+  talk_base::scoped_ptr<cricket::RtpDataMediaChannel> dmc2(
+      CreateChannel(dme2.get()));
+
+  ASSERT_TRUE(dmc1->SetSend(true));
+  ASSERT_TRUE(dmc2->SetSend(true));
+
+  cricket::StreamParams stream1;
+  stream1.add_ssrc(41);
+  ASSERT_TRUE(dmc1->AddSendStream(stream1));
+  cricket::StreamParams stream2;
+  stream2.add_ssrc(42);
+  ASSERT_TRUE(dmc2->AddSendStream(stream2));
+
+  cricket::DataCodec codec;
+  codec.id = 103;
+  codec.name = cricket::kGoogleRtpDataCodecName;
+  std::vector<cricket::DataCodec> codecs;
+  codecs.push_back(codec);
+  ASSERT_TRUE(dmc1->SetSendCodecs(codecs));
+  ASSERT_TRUE(dmc2->SetSendCodecs(codecs));
+
+  cricket::SendDataParams params1;
+  params1.ssrc = 41;
+  cricket::SendDataParams params2;
+  params2.ssrc = 42;
+
+  unsigned char data[] = "foo";
+  talk_base::Buffer payload(data, 3);
+  cricket::SendDataResult result;
+
+  EXPECT_TRUE(dmc1->SendData(params1, payload, &result));
+  EXPECT_TRUE(dmc2->SendData(params2, payload, &result));
+
+  // Should bump timestamp by 90000 because the clock rate is 90khz.
+  timing1->set_now(1);
+  // Should bump timestamp by 180000 because the clock rate is 90khz.
+  timing2->set_now(2);
+
+  EXPECT_TRUE(dmc1->SendData(params1, payload, &result));
+  EXPECT_TRUE(dmc2->SendData(params2, payload, &result));
+
+  ASSERT_TRUE(HasSentData(3));
+  cricket::RtpHeader header1a = GetSentDataHeader(0);
+  cricket::RtpHeader header2a = GetSentDataHeader(1);
+  cricket::RtpHeader header1b = GetSentDataHeader(2);
+  cricket::RtpHeader header2b = GetSentDataHeader(3);
+
+  EXPECT_EQ(header1a.seq_num + 1, header1b.seq_num);
+  EXPECT_EQ(header1a.timestamp + 90000, header1b.timestamp);
+  EXPECT_EQ(header2a.seq_num + 1, header2b.seq_num);
+  EXPECT_EQ(header2a.timestamp + 180000, header2b.timestamp);
+}
+
+TEST_F(RtpDataMediaChannelTest, SendDataRate) {
+  talk_base::scoped_ptr<cricket::RtpDataMediaChannel> dmc(CreateChannel());
+
+  ASSERT_TRUE(dmc->SetSend(true));
+
+  cricket::DataCodec codec;
+  codec.id = 103;
+  codec.name = cricket::kGoogleRtpDataCodecName;
+  std::vector<cricket::DataCodec> codecs;
+  codecs.push_back(codec);
+  ASSERT_TRUE(dmc->SetSendCodecs(codecs));
+
+  cricket::StreamParams stream;
+  stream.add_ssrc(42);
+  ASSERT_TRUE(dmc->AddSendStream(stream));
+
+  cricket::SendDataParams params;
+  params.ssrc = 42;
+  unsigned char data[] = "food";
+  talk_base::Buffer payload(data, 4);
+  cricket::SendDataResult result;
+
+  // With rtp overhead of 32 bytes, each one of our packets is 36
+  // bytes, or 288 bits.  So, a limit of 872bps will allow 3 packets,
+  // but not four.
+  dmc->SetSendBandwidth(false, 872);
+
+  EXPECT_TRUE(dmc->SendData(params, payload, &result));
+  EXPECT_TRUE(dmc->SendData(params, payload, &result));
+  EXPECT_TRUE(dmc->SendData(params, payload, &result));
+  EXPECT_FALSE(dmc->SendData(params, payload, &result));
+  EXPECT_FALSE(dmc->SendData(params, payload, &result));
+
+  SetNow(0.9);
+  EXPECT_FALSE(dmc->SendData(params, payload, &result));
+
+  SetNow(1.1);
+  EXPECT_TRUE(dmc->SendData(params, payload, &result));
+  EXPECT_TRUE(dmc->SendData(params, payload, &result));
+  SetNow(1.9);
+  EXPECT_TRUE(dmc->SendData(params, payload, &result));
+
+  SetNow(2.2);
+  EXPECT_TRUE(dmc->SendData(params, payload, &result));
+  EXPECT_TRUE(dmc->SendData(params, payload, &result));
+  EXPECT_TRUE(dmc->SendData(params, payload, &result));
+  EXPECT_FALSE(dmc->SendData(params, payload, &result));
+}
+
+TEST_F(RtpDataMediaChannelTest, ReceiveData) {
+  // PT= 103, SN=2, TS=3, SSRC = 4, data = "abcde"
+  unsigned char data[] = {
+    0x80, 0x67, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x2A,
+    0x00, 0x00, 0x00, 0x00,
+    'a', 'b', 'c', 'd', 'e'
+  };
+  talk_base::Buffer packet(data, sizeof(data));
+
+  talk_base::scoped_ptr<cricket::RtpDataMediaChannel> dmc(CreateChannel());
+
+  // SetReceived not called.
+  dmc->OnPacketReceived(&packet);
+  EXPECT_FALSE(HasReceivedData());
+
+  dmc->SetReceive(true);
+
+  // Unknown payload id
+  dmc->OnPacketReceived(&packet);
+  EXPECT_FALSE(HasReceivedData());
+
+  cricket::DataCodec codec;
+  codec.id = 103;
+  codec.name = cricket::kGoogleRtpDataCodecName;
+  std::vector<cricket::DataCodec> codecs;
+  codecs.push_back(codec);
+  ASSERT_TRUE(dmc->SetRecvCodecs(codecs));
+
+  // Unknown stream
+  dmc->OnPacketReceived(&packet);
+  EXPECT_FALSE(HasReceivedData());
+
+  cricket::StreamParams stream;
+  stream.add_ssrc(42);
+  ASSERT_TRUE(dmc->AddRecvStream(stream));
+
+  // Finally works!
+  dmc->OnPacketReceived(&packet);
+  EXPECT_TRUE(HasReceivedData());
+  EXPECT_EQ("abcde", GetReceivedData());
+  EXPECT_EQ(5U, GetReceivedDataLen());
+}
+
+TEST_F(RtpDataMediaChannelTest, InvalidRtpPackets) {
+  unsigned char data[] = {
+    0x80, 0x65, 0x00, 0x02
+  };
+  talk_base::Buffer packet(data, sizeof(data));
+
+  talk_base::scoped_ptr<cricket::RtpDataMediaChannel> dmc(CreateChannel());
+
+  // Too short
+  dmc->OnPacketReceived(&packet);
+  EXPECT_FALSE(HasReceivedData());
+}
diff --git a/talk/media/base/rtpdump.cc b/talk/media/base/rtpdump.cc
new file mode 100644
index 0000000..10c835c
--- /dev/null
+++ b/talk/media/base/rtpdump.cc
@@ -0,0 +1,424 @@
+/*
+ * 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/media/base/rtpdump.h"
+
+#include <ctype.h>
+
+#include <string>
+
+#include "talk/base/byteorder.h"
+#include "talk/base/logging.h"
+#include "talk/base/timeutils.h"
+#include "talk/media/base/rtputils.h"
+
+namespace {
+static const int kRtpSsrcOffset = 8;
+const int  kWarnSlowWritesDelayMs = 50;
+}  // namespace
+
+namespace cricket {
+
+const char RtpDumpFileHeader::kFirstLine[] = "#!rtpplay1.0 0.0.0.0/0\n";
+
+RtpDumpFileHeader::RtpDumpFileHeader(uint32 start_ms, uint32 s, uint16 p)
+    : start_sec(start_ms / 1000),
+      start_usec(start_ms % 1000 * 1000),
+      source(s),
+      port(p),
+      padding(0) {
+}
+
+void RtpDumpFileHeader::WriteToByteBuffer(talk_base::ByteBuffer* buf) {
+  buf->WriteUInt32(start_sec);
+  buf->WriteUInt32(start_usec);
+  buf->WriteUInt32(source);
+  buf->WriteUInt16(port);
+  buf->WriteUInt16(padding);
+}
+
+static const uint32 kDefaultTimeIncrease = 30;
+
+bool RtpDumpPacket::IsValidRtpPacket() const {
+  return original_data_len >= data.size() &&
+      data.size() >= kMinRtpPacketLen;
+}
+
+bool RtpDumpPacket::IsValidRtcpPacket() const {
+  return original_data_len == 0 &&
+      data.size() >= kMinRtcpPacketLen;
+}
+
+bool RtpDumpPacket::GetRtpPayloadType(int* pt) const {
+  return IsValidRtpPacket() &&
+      cricket::GetRtpPayloadType(&data[0], data.size(), pt);
+}
+
+bool RtpDumpPacket::GetRtpSeqNum(int* seq_num) const {
+  return IsValidRtpPacket() &&
+      cricket::GetRtpSeqNum(&data[0], data.size(), seq_num);
+}
+
+bool RtpDumpPacket::GetRtpTimestamp(uint32* ts) const {
+  return IsValidRtpPacket() &&
+      cricket::GetRtpTimestamp(&data[0], data.size(), ts);
+}
+
+bool RtpDumpPacket::GetRtpSsrc(uint32* ssrc) const {
+  return IsValidRtpPacket() &&
+      cricket::GetRtpSsrc(&data[0], data.size(), ssrc);
+}
+
+bool RtpDumpPacket::GetRtpHeaderLen(size_t* len) const {
+  return IsValidRtpPacket() &&
+      cricket::GetRtpHeaderLen(&data[0], data.size(), len);
+}
+
+bool RtpDumpPacket::GetRtcpType(int* type) const {
+  return IsValidRtcpPacket() &&
+      cricket::GetRtcpType(&data[0], data.size(), type);
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of RtpDumpReader.
+///////////////////////////////////////////////////////////////////////////
+
+void RtpDumpReader::SetSsrc(uint32 ssrc) {
+  ssrc_override_ = ssrc;
+}
+
+talk_base::StreamResult RtpDumpReader::ReadPacket(RtpDumpPacket* packet) {
+  if (!packet) return talk_base::SR_ERROR;
+
+  talk_base::StreamResult res = talk_base::SR_SUCCESS;
+  // Read the file header if it has not been read yet.
+  if (!file_header_read_) {
+    res = ReadFileHeader();
+    if (res != talk_base::SR_SUCCESS) {
+      return res;
+    }
+    file_header_read_ = true;
+  }
+
+  // Read the RTP dump packet header.
+  char header[RtpDumpPacket::kHeaderLength];
+  res = stream_->ReadAll(header, sizeof(header), NULL, NULL);
+  if (res != talk_base::SR_SUCCESS) {
+    return res;
+  }
+  talk_base::ByteBuffer buf(header, sizeof(header));
+  uint16 dump_packet_len;
+  uint16 data_len;
+  // Read the full length of the rtpdump packet, including the rtpdump header.
+  buf.ReadUInt16(&dump_packet_len);
+  packet->data.resize(dump_packet_len - sizeof(header));
+  // Read the size of the original packet, which may be larger than the size in
+  // the rtpdump file, in the event that only part of the packet (perhaps just
+  // the header) was recorded. Note that this field is set to zero for RTCP
+  // packets, which have their own internal length field.
+  buf.ReadUInt16(&data_len);
+  packet->original_data_len = data_len;
+  // Read the elapsed time for this packet (different than RTP timestamp).
+  buf.ReadUInt32(&packet->elapsed_time);
+
+  // Read the actual RTP or RTCP packet.
+  res = stream_->ReadAll(&packet->data[0], packet->data.size(), NULL, NULL);
+
+  // If the packet is RTP and we have specified a ssrc, replace the RTP ssrc
+  // with the specified ssrc.
+  if (res == talk_base::SR_SUCCESS &&
+      packet->IsValidRtpPacket() &&
+      ssrc_override_ != 0) {
+    talk_base::SetBE32(&packet->data[kRtpSsrcOffset], ssrc_override_);
+  }
+
+  return res;
+}
+
+talk_base::StreamResult RtpDumpReader::ReadFileHeader() {
+  // Read the first line.
+  std::string first_line;
+  talk_base::StreamResult res = stream_->ReadLine(&first_line);
+  if (res != talk_base::SR_SUCCESS) {
+    return res;
+  }
+  if (!CheckFirstLine(first_line)) {
+    return talk_base::SR_ERROR;
+  }
+
+  // Read the 16 byte file header.
+  char header[RtpDumpFileHeader::kHeaderLength];
+  res = stream_->ReadAll(header, sizeof(header), NULL, NULL);
+  if (res == talk_base::SR_SUCCESS) {
+    talk_base::ByteBuffer buf(header, sizeof(header));
+    uint32 start_sec;
+    uint32 start_usec;
+    buf.ReadUInt32(&start_sec);
+    buf.ReadUInt32(&start_usec);
+    start_time_ms_ = start_sec * 1000 + start_usec / 1000;
+    // Increase the length by 1 since first_line does not contain the ending \n.
+    first_line_and_file_header_len_ = first_line.size() + 1 + sizeof(header);
+  }
+  return res;
+}
+
+bool RtpDumpReader::CheckFirstLine(const std::string& first_line) {
+  // The first line is like "#!rtpplay1.0 address/port"
+  bool matched = (0 == first_line.find("#!rtpplay1.0 "));
+
+  // The address could be IP or hostname. We do not check it here. Instead, we
+  // check the port at the end.
+  size_t pos = first_line.find('/');
+  matched &= (pos != std::string::npos && pos < first_line.size() - 1);
+  for (++pos; pos < first_line.size() && matched; ++pos) {
+    matched &= (0 != isdigit(first_line[pos]));
+  }
+
+  return matched;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of RtpDumpLoopReader.
+///////////////////////////////////////////////////////////////////////////
+RtpDumpLoopReader::RtpDumpLoopReader(talk_base::StreamInterface* stream)
+    : RtpDumpReader(stream),
+      loop_count_(0),
+      elapsed_time_increases_(0),
+      rtp_seq_num_increase_(0),
+      rtp_timestamp_increase_(0),
+      packet_count_(0),
+      frame_count_(0),
+      first_elapsed_time_(0),
+      first_rtp_seq_num_(0),
+      first_rtp_timestamp_(0),
+      prev_elapsed_time_(0),
+      prev_rtp_seq_num_(0),
+      prev_rtp_timestamp_(0) {
+}
+
+talk_base::StreamResult RtpDumpLoopReader::ReadPacket(RtpDumpPacket* packet) {
+  if (!packet) return talk_base::SR_ERROR;
+
+  talk_base::StreamResult res = RtpDumpReader::ReadPacket(packet);
+  if (talk_base::SR_SUCCESS == res) {
+    if (0 == loop_count_) {
+      // During the first loop, we update the statistics of the input stream.
+      UpdateStreamStatistics(*packet);
+    }
+  } else if (talk_base::SR_EOS == res) {
+    if (0 == loop_count_) {
+      // At the end of the first loop, calculate elapsed_time_increases_,
+      // rtp_seq_num_increase_, and rtp_timestamp_increase_, which will be
+      // used during the second and later loops.
+      CalculateIncreases();
+    }
+
+    // Rewind the input stream to the first dump packet and read again.
+    ++loop_count_;
+    if (RewindToFirstDumpPacket()) {
+      res = RtpDumpReader::ReadPacket(packet);
+    }
+  }
+
+  if (talk_base::SR_SUCCESS == res && loop_count_ > 0) {
+    // During the second and later loops, we update the elapsed time of the dump
+    // packet. If the dumped packet is a RTP packet, we also update its RTP
+    // sequence number and timestamp.
+    UpdateDumpPacket(packet);
+  }
+
+  return res;
+}
+
+void RtpDumpLoopReader::UpdateStreamStatistics(const RtpDumpPacket& packet) {
+  // Get the RTP sequence number and timestamp of the dump packet.
+  int rtp_seq_num = 0;
+  packet.GetRtpSeqNum(&rtp_seq_num);
+  uint32 rtp_timestamp = 0;
+  packet.GetRtpTimestamp(&rtp_timestamp);
+
+  // Set the timestamps and sequence number for the first dump packet.
+  if (0 == packet_count_++) {
+    first_elapsed_time_ = packet.elapsed_time;
+    first_rtp_seq_num_ = rtp_seq_num;
+    first_rtp_timestamp_ = rtp_timestamp;
+    // The first packet belongs to a new payload frame.
+    ++frame_count_;
+  } else if (rtp_timestamp != prev_rtp_timestamp_) {
+    // The current and previous packets belong to different payload frames.
+    ++frame_count_;
+  }
+
+  prev_elapsed_time_ = packet.elapsed_time;
+  prev_rtp_timestamp_ = rtp_timestamp;
+  prev_rtp_seq_num_ = rtp_seq_num;
+}
+
+void RtpDumpLoopReader::CalculateIncreases() {
+  // At this time, prev_elapsed_time_, prev_rtp_seq_num_, and
+  // prev_rtp_timestamp_ are values of the last dump packet in the input stream.
+  rtp_seq_num_increase_ = prev_rtp_seq_num_ - first_rtp_seq_num_ + 1;
+  // If we have only one packet or frame, we use the default timestamp
+  // increase. Otherwise, we use the difference between the first and the last
+  // packets or frames.
+  elapsed_time_increases_ = packet_count_ <= 1 ? kDefaultTimeIncrease :
+      (prev_elapsed_time_ - first_elapsed_time_) * packet_count_ /
+      (packet_count_ - 1);
+  rtp_timestamp_increase_ = frame_count_ <= 1 ? kDefaultTimeIncrease :
+      (prev_rtp_timestamp_ - first_rtp_timestamp_) * frame_count_ /
+      (frame_count_ - 1);
+}
+
+void RtpDumpLoopReader::UpdateDumpPacket(RtpDumpPacket* packet) {
+  // Increase the elapsed time of the dump packet.
+  packet->elapsed_time += loop_count_ * elapsed_time_increases_;
+
+  if (packet->IsValidRtpPacket()) {
+    // Get the old RTP sequence number and timestamp.
+    int sequence = 0;
+    packet->GetRtpSeqNum(&sequence);
+    uint32 timestamp = 0;
+    packet->GetRtpTimestamp(&timestamp);
+    // Increase the RTP sequence number and timestamp.
+    sequence += loop_count_ * rtp_seq_num_increase_;
+    timestamp += loop_count_ * rtp_timestamp_increase_;
+    // Write the updated sequence number and timestamp back to the RTP packet.
+    talk_base::ByteBuffer buffer;
+    buffer.WriteUInt16(sequence);
+    buffer.WriteUInt32(timestamp);
+    memcpy(&packet->data[2], buffer.Data(), buffer.Length());
+  }
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of RtpDumpWriter.
+///////////////////////////////////////////////////////////////////////////
+
+RtpDumpWriter::RtpDumpWriter(talk_base::StreamInterface* stream)
+    : stream_(stream),
+      packet_filter_(PF_ALL),
+      file_header_written_(false),
+      start_time_ms_(talk_base::Time()),
+      warn_slow_writes_delay_(kWarnSlowWritesDelayMs) {
+}
+
+void RtpDumpWriter::set_packet_filter(int filter) {
+  packet_filter_ = filter;
+  LOG(LS_INFO) << "RtpDumpWriter set_packet_filter to " << packet_filter_;
+}
+
+uint32 RtpDumpWriter::GetElapsedTime() const {
+  return talk_base::TimeSince(start_time_ms_);
+}
+
+talk_base::StreamResult RtpDumpWriter::WriteFileHeader() {
+  talk_base::StreamResult res = WriteToStream(
+      RtpDumpFileHeader::kFirstLine,
+      strlen(RtpDumpFileHeader::kFirstLine));
+  if (res != talk_base::SR_SUCCESS) {
+    return res;
+  }
+
+  talk_base::ByteBuffer buf;
+  RtpDumpFileHeader file_header(talk_base::Time(), 0, 0);
+  file_header.WriteToByteBuffer(&buf);
+  return WriteToStream(buf.Data(), buf.Length());
+}
+
+talk_base::StreamResult RtpDumpWriter::WritePacket(
+    const void* data, size_t data_len, uint32 elapsed, bool rtcp) {
+  if (!stream_ || !data || 0 == data_len) return talk_base::SR_ERROR;
+
+  talk_base::StreamResult res = talk_base::SR_SUCCESS;
+  // Write the file header if it has not been written yet.
+  if (!file_header_written_) {
+    res = WriteFileHeader();
+    if (res != talk_base::SR_SUCCESS) {
+      return res;
+    }
+    file_header_written_ = true;
+  }
+
+  // Figure out what to write.
+  size_t write_len = FilterPacket(data, data_len, rtcp);
+  if (write_len == 0) {
+    return talk_base::SR_SUCCESS;
+  }
+
+  // Write the dump packet header.
+  talk_base::ByteBuffer buf;
+  buf.WriteUInt16(static_cast<uint16>(
+                      RtpDumpPacket::kHeaderLength + write_len));
+  buf.WriteUInt16(static_cast<uint16>(rtcp ? 0 : data_len));
+  buf.WriteUInt32(elapsed);
+  res = WriteToStream(buf.Data(), buf.Length());
+  if (res != talk_base::SR_SUCCESS) {
+    return res;
+  }
+
+  // Write the header or full packet as indicated by write_len.
+  return WriteToStream(data, write_len);
+}
+
+size_t RtpDumpWriter::FilterPacket(const void* data, size_t data_len,
+                                   bool rtcp) {
+  size_t filtered_len = 0;
+  if (!rtcp) {
+    if ((packet_filter_ & PF_RTPPACKET) == PF_RTPPACKET) {
+      // RTP header + payload
+      filtered_len = data_len;
+    } else if ((packet_filter_ & PF_RTPHEADER) == PF_RTPHEADER) {
+      // RTP header only
+      size_t header_len;
+      if (GetRtpHeaderLen(data, data_len, &header_len)) {
+        filtered_len = header_len;
+      }
+    }
+  } else {
+    if ((packet_filter_ & PF_RTCPPACKET) == PF_RTCPPACKET) {
+      // RTCP header + payload
+      filtered_len = data_len;
+    }
+  }
+
+  return filtered_len;
+}
+
+talk_base::StreamResult RtpDumpWriter::WriteToStream(
+    const void* data, size_t data_len) {
+  uint32 before = talk_base::Time();
+  talk_base::StreamResult result =
+      stream_->WriteAll(data, data_len, NULL, NULL);
+  uint32 delay = talk_base::TimeSince(before);
+  if (delay >= warn_slow_writes_delay_) {
+    LOG(LS_WARNING) << "Slow RtpDump: took " << delay << "ms to write "
+                    << data_len << " bytes.";
+  }
+  return result;
+}
+
+}  // namespace cricket
diff --git a/talk/media/base/rtpdump.h b/talk/media/base/rtpdump.h
new file mode 100644
index 0000000..9d7b679
--- /dev/null
+++ b/talk/media/base/rtpdump.h
@@ -0,0 +1,232 @@
+/*
+ * 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_MEDIA_BASE_RTPDUMP_H_
+#define TALK_MEDIA_BASE_RTPDUMP_H_
+
+#include <cstring>
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/bytebuffer.h"
+#include "talk/base/stream.h"
+
+namespace cricket {
+
+// We use the RTP dump file format compatible to the format used by rtptools
+// (http://www.cs.columbia.edu/irt/software/rtptools/) and Wireshark
+// (http://wiki.wireshark.org/rtpdump). In particular, the file starts with the
+// first line "#!rtpplay1.0 address/port\n", followed by a 16 byte file header.
+// For each packet, the file contains a 8 byte dump packet header, followed by
+// the actual RTP or RTCP packet.
+
+enum RtpDumpPacketFilter {
+  PF_NONE = 0x0,
+  PF_RTPHEADER = 0x1,
+  PF_RTPPACKET = 0x3,  // includes header
+  // PF_RTCPHEADER = 0x4,  // TODO(juberti)
+  PF_RTCPPACKET = 0xC,  // includes header
+  PF_ALL = 0xF
+};
+
+struct RtpDumpFileHeader {
+  RtpDumpFileHeader(uint32 start_ms, uint32 s, uint16 p);
+  void WriteToByteBuffer(talk_base::ByteBuffer* buf);
+
+  static const char kFirstLine[];
+  static const size_t kHeaderLength = 16;
+  uint32 start_sec;   // start of recording, the seconds part.
+  uint32 start_usec;  // start of recording, the microseconds part.
+  uint32 source;      // network source (multicast address).
+  uint16 port;        // UDP port.
+  uint16 padding;     // 2 bytes padding.
+};
+
+struct RtpDumpPacket {
+  RtpDumpPacket() {}
+
+  RtpDumpPacket(const void* d, size_t s, uint32 elapsed, bool rtcp)
+      : elapsed_time(elapsed),
+        original_data_len((rtcp) ? 0 : s) {
+    data.resize(s);
+    memcpy(&data[0], d, s);
+  }
+
+  // In the rtpdump file format, RTCP packets have their data len set to zero,
+  // since RTCP has an internal length field.
+  bool is_rtcp() const { return original_data_len == 0; }
+  bool IsValidRtpPacket() const;
+  bool IsValidRtcpPacket() const;
+  // Get the payload type, sequence number, timestampe, and SSRC of the RTP
+  // packet. Return true and set the output parameter if successful.
+  bool GetRtpPayloadType(int* pt) const;
+  bool GetRtpSeqNum(int* seq_num) const;
+  bool GetRtpTimestamp(uint32* ts) const;
+  bool GetRtpSsrc(uint32* ssrc) const;
+  bool GetRtpHeaderLen(size_t* len) const;
+  // Get the type of the RTCP packet. Return true and set the output parameter
+  // if successful.
+  bool GetRtcpType(int* type) const;
+
+  static const size_t kHeaderLength = 8;
+  uint32 elapsed_time;       // Milliseconds since the start of recording.
+  std::vector<uint8> data;   // The actual RTP or RTCP packet.
+  size_t original_data_len;  // The original length of the packet; may be
+                             // greater than data.size() if only part of the
+                             // packet was recorded.
+};
+
+class RtpDumpReader {
+ public:
+  explicit RtpDumpReader(talk_base::StreamInterface* stream)
+      : stream_(stream),
+        file_header_read_(false),
+        first_line_and_file_header_len_(0),
+        start_time_ms_(0),
+        ssrc_override_(0) {
+  }
+  virtual ~RtpDumpReader() {}
+
+  // Use the specified ssrc, rather than the ssrc from dump, for RTP packets.
+  void SetSsrc(uint32 ssrc);
+  virtual talk_base::StreamResult ReadPacket(RtpDumpPacket* packet);
+
+ protected:
+  talk_base::StreamResult ReadFileHeader();
+  bool RewindToFirstDumpPacket() {
+    return stream_->SetPosition(first_line_and_file_header_len_);
+  }
+
+ private:
+  // Check if its matches "#!rtpplay1.0 address/port\n".
+  bool CheckFirstLine(const std::string& first_line);
+
+  talk_base::StreamInterface* stream_;
+  bool file_header_read_;
+  size_t first_line_and_file_header_len_;
+  uint32 start_time_ms_;
+  uint32 ssrc_override_;
+
+  DISALLOW_COPY_AND_ASSIGN(RtpDumpReader);
+};
+
+// RtpDumpLoopReader reads RTP dump packets from the input stream and rewinds
+// the stream when it ends. RtpDumpLoopReader maintains the elapsed time, the
+// RTP sequence number and the RTP timestamp properly. RtpDumpLoopReader can
+// handle both RTP dump and RTCP dump. We assume that the dump does not mix
+// RTP packets and RTCP packets.
+class RtpDumpLoopReader : public RtpDumpReader {
+ public:
+  explicit RtpDumpLoopReader(talk_base::StreamInterface* stream);
+  virtual talk_base::StreamResult ReadPacket(RtpDumpPacket* packet);
+
+ private:
+  // During the first loop, update the statistics, including packet count, frame
+  // count, timestamps, and sequence number, of the input stream.
+  void UpdateStreamStatistics(const RtpDumpPacket& packet);
+
+  // At the end of first loop, calculate elapsed_time_increases_,
+  // rtp_seq_num_increase_, and rtp_timestamp_increase_.
+  void CalculateIncreases();
+
+  // During the second and later loops, update the elapsed time of the dump
+  // packet. If the dumped packet is a RTP packet, update its RTP sequence
+  // number and timestamp as well.
+  void UpdateDumpPacket(RtpDumpPacket* packet);
+
+  int loop_count_;
+  // How much to increase the elapsed time, RTP sequence number, RTP timestampe
+  // for each loop. They are calcualted with the variables below during the
+  // first loop.
+  uint32 elapsed_time_increases_;
+  int rtp_seq_num_increase_;
+  uint32 rtp_timestamp_increase_;
+  // How many RTP packets and how many payload frames in the input stream. RTP
+  // packets belong to the same frame have the same RTP timestamp, different
+  // dump timestamp, and different RTP sequence number.
+  uint32 packet_count_;
+  uint32 frame_count_;
+  // The elapsed time, RTP sequence number, and RTP timestamp of the first and
+  // the previous dump packets in the input stream.
+  uint32 first_elapsed_time_;
+  int first_rtp_seq_num_;
+  uint32 first_rtp_timestamp_;
+  uint32 prev_elapsed_time_;
+  int prev_rtp_seq_num_;
+  uint32 prev_rtp_timestamp_;
+
+  DISALLOW_COPY_AND_ASSIGN(RtpDumpLoopReader);
+};
+
+class RtpDumpWriter {
+ public:
+  explicit RtpDumpWriter(talk_base::StreamInterface* stream);
+
+  // Filter to control what packets we actually record.
+  void set_packet_filter(int filter);
+  // Write a RTP or RTCP packet. The parameters data points to the packet and
+  // data_len is its length.
+  talk_base::StreamResult WriteRtpPacket(const void* data, size_t data_len) {
+    return WritePacket(data, data_len, GetElapsedTime(), false);
+  }
+  talk_base::StreamResult WriteRtcpPacket(const void* data, size_t data_len) {
+    return WritePacket(data, data_len, GetElapsedTime(), true);
+  }
+  talk_base::StreamResult WritePacket(const RtpDumpPacket& packet) {
+    return WritePacket(&packet.data[0], packet.data.size(), packet.elapsed_time,
+                       packet.is_rtcp());
+  }
+  uint32 GetElapsedTime() const;
+
+  bool GetDumpSize(size_t* size) {
+    // Note that we use GetPosition(), rather than GetSize(), to avoid flush the
+    // stream per write.
+    return stream_ && size && stream_->GetPosition(size);
+  }
+
+ protected:
+  talk_base::StreamResult WriteFileHeader();
+
+ private:
+  talk_base::StreamResult WritePacket(const void* data, size_t data_len,
+                                      uint32 elapsed, bool rtcp);
+  size_t FilterPacket(const void* data, size_t data_len, bool rtcp);
+  talk_base::StreamResult WriteToStream(const void* data, size_t data_len);
+
+  talk_base::StreamInterface* stream_;
+  int packet_filter_;
+  bool file_header_written_;
+  uint32 start_time_ms_;  // Time when the record starts.
+  // If writing to the stream takes longer than this many ms, log a warning.
+  uint32 warn_slow_writes_delay_;
+  DISALLOW_COPY_AND_ASSIGN(RtpDumpWriter);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_RTPDUMP_H_
diff --git a/talk/media/base/rtpdump_unittest.cc b/talk/media/base/rtpdump_unittest.cc
new file mode 100644
index 0000000..c327189
--- /dev/null
+++ b/talk/media/base/rtpdump_unittest.cc
@@ -0,0 +1,297 @@
+/*
+ * 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/thread.h"
+#include "talk/media/base/rtpdump.h"
+#include "talk/media/base/rtputils.h"
+#include "talk/media/base/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_FALSE(rtp_packet.is_rtcp());
+  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_TRUE(rtcp_packet.is_rtcp());
+  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));
+    EXPECT_FALSE(packet.is_rtcp());
+    EXPECT_EQ(packet.original_data_len, packet.data.size());
+    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));
+    EXPECT_TRUE(packet.is_rtcp());
+    EXPECT_EQ(0U, packet.original_data_len);
+  }
+  // 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());
+    EXPECT_GT(packet.original_data_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());
+    EXPECT_EQ(0U, packet.original_data_len);
+  }
+  // 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/media/base/rtputils.cc b/talk/media/base/rtputils.cc
new file mode 100644
index 0000000..5215c3b
--- /dev/null
+++ b/talk/media/base/rtputils.cc
@@ -0,0 +1,226 @@
+/*
+ * 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/media/base/rtputils.h"
+
+namespace cricket {
+
+static const int kRtpVersion = 2;
+static const size_t kRtpFlagsOffset = 0;
+static const size_t kRtpPayloadTypeOffset = 1;
+static const size_t kRtpSeqNumOffset = 2;
+static const size_t kRtpTimestampOffset = 4;
+static const size_t kRtpSsrcOffset = 8;
+static const size_t kRtcpPayloadTypeOffset = 1;
+
+bool GetUint8(const void* data, size_t offset, int* value) {
+  if (!data || !value) {
+    return false;
+  }
+  *value = *(static_cast<const uint8*>(data) + offset);
+  return true;
+}
+
+bool GetUint16(const void* data, size_t offset, int* value) {
+  if (!data || !value) {
+    return false;
+  }
+  *value = static_cast<int>(
+      talk_base::GetBE16(static_cast<const uint8*>(data) + offset));
+  return true;
+}
+
+bool GetUint32(const void* data, size_t offset, uint32* value) {
+  if (!data || !value) {
+    return false;
+  }
+  *value = talk_base::GetBE32(static_cast<const uint8*>(data) + offset);
+  return true;
+}
+
+bool SetUint8(void* data, size_t offset, int value) {
+  if (!data) {
+    return false;
+  }
+  talk_base::Set8(data, offset, value);
+  return true;
+}
+
+bool SetUint16(void* data, size_t offset, int value) {
+  if (!data) {
+    return false;
+  }
+  talk_base::SetBE16(static_cast<uint8*>(data) + offset, value);
+  return true;
+}
+
+bool SetUint32(void* data, size_t offset, uint32 value) {
+  if (!data) {
+    return false;
+  }
+  talk_base::SetBE32(static_cast<uint8*>(data) + offset, value);
+  return true;
+}
+
+bool GetRtpFlags(const void* data, size_t len, int* value) {
+  if (len < kMinRtpPacketLen) {
+    return false;
+  }
+  return GetUint8(data, kRtpFlagsOffset, value);
+}
+
+bool GetRtpPayloadType(const void* data, size_t len, int* value) {
+  if (len < kMinRtpPacketLen) {
+    return false;
+  }
+  if (!GetUint8(data, kRtpPayloadTypeOffset, value)) {
+    return false;
+  }
+  *value &= 0x7F;
+  return true;
+}
+
+bool GetRtpSeqNum(const void* data, size_t len, int* value) {
+  if (len < kMinRtpPacketLen) {
+    return false;
+  }
+  return GetUint16(data, kRtpSeqNumOffset, value);
+}
+
+bool GetRtpTimestamp(const void* data, size_t len, uint32* value) {
+  if (len < kMinRtpPacketLen) {
+    return false;
+  }
+  return GetUint32(data, kRtpTimestampOffset, value);
+}
+
+bool GetRtpSsrc(const void* data, size_t len, uint32* value) {
+  if (len < kMinRtpPacketLen) {
+    return false;
+  }
+  return GetUint32(data, kRtpSsrcOffset, value);
+}
+
+bool GetRtpHeaderLen(const void* data, size_t len, size_t* value) {
+  if (!data || len < kMinRtpPacketLen || !value) return false;
+  const uint8* header = static_cast<const uint8*>(data);
+  // Get base header size + length of CSRCs (not counting extension yet).
+  size_t header_size = kMinRtpPacketLen + (header[0] & 0xF) * sizeof(uint32);
+  if (len < header_size) return false;
+  // If there's an extension, read and add in the extension size.
+  if (header[0] & 0x10) {
+    if (len < header_size + sizeof(uint32)) return false;
+    header_size += ((talk_base::GetBE16(header + header_size + 2) + 1) *
+                    sizeof(uint32));
+    if (len < header_size) return false;
+  }
+  *value = header_size;
+  return true;
+}
+
+bool GetRtpVersion(const void* data, size_t len, int* version) {
+  if (len == 0) {
+    return false;
+  }
+
+  const uint8 first = static_cast<const uint8*>(data)[0];
+  *version = static_cast<int>((first >> 6) & 0x3);
+  return true;
+}
+
+bool GetRtpHeader(const void* data, size_t len, RtpHeader* header) {
+  return (GetRtpPayloadType(data, len, &(header->payload_type)) &&
+          GetRtpSeqNum(data, len, &(header->seq_num)) &&
+          GetRtpTimestamp(data, len, &(header->timestamp)) &&
+          GetRtpSsrc(data, len, &(header->ssrc)));
+}
+
+bool GetRtcpType(const void* data, size_t len, int* value) {
+  if (len < kMinRtcpPacketLen) {
+    return false;
+  }
+  return GetUint8(data, kRtcpPayloadTypeOffset, value);
+}
+
+// This method returns SSRC first of RTCP packet, except if packet is SDES.
+// TODO(mallinath) - 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;
+}
+
+bool SetRtpHeaderFlags(
+    void* data, size_t len,
+    bool padding, bool extension, int csrc_count) {
+  if (csrc_count > 0x0F) {
+    return false;
+  }
+  int flags = 0;
+  flags |= (kRtpVersion << 6);
+  flags |= ((padding ? 1 : 0) << 5);
+  flags |= ((extension ? 1 : 0) << 4);
+  flags |= csrc_count;
+  return SetUint8(data, kRtpFlagsOffset, flags);
+}
+
+// Assumes marker bit is 0.
+bool SetRtpPayloadType(void* data, size_t len, int value) {
+  if (value >= 0x7F) {
+    return false;
+  }
+  return SetUint8(data, kRtpPayloadTypeOffset, value & 0x7F);
+}
+
+bool SetRtpSeqNum(void* data, size_t len, int value) {
+  return SetUint16(data, kRtpSeqNumOffset, value);
+}
+
+bool SetRtpTimestamp(void* data, size_t len, uint32 value) {
+  return SetUint32(data, kRtpTimestampOffset, value);
+}
+
+bool SetRtpSsrc(void* data, size_t len, uint32 value) {
+  return SetUint32(data, kRtpSsrcOffset, value);
+}
+
+// Assumes version 2, no padding, no extensions, no csrcs.
+bool SetRtpHeader(void* data, size_t len, const RtpHeader& header) {
+  return (SetRtpHeaderFlags(data, len, false, false, 0) &&
+          SetRtpPayloadType(data, len, header.payload_type) &&
+          SetRtpSeqNum(data, len, header.seq_num) &&
+          SetRtpTimestamp(data, len, header.timestamp) &&
+          SetRtpSsrc(data, len, header.ssrc));
+}
+
+}  // namespace cricket
diff --git a/talk/media/base/rtputils.h b/talk/media/base/rtputils.h
new file mode 100644
index 0000000..6f76866
--- /dev/null
+++ b/talk/media/base/rtputils.h
@@ -0,0 +1,79 @@
+/*
+ * 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_MEDIA_BASE_RTPUTILS_H_
+#define TALK_MEDIA_BASE_RTPUTILS_H_
+
+#include "talk/base/byteorder.h"
+
+namespace cricket {
+
+const size_t kMinRtpPacketLen = 12;
+const size_t kMaxRtpPacketLen = 2048;
+const size_t kMinRtcpPacketLen = 4;
+
+struct RtpHeader {
+  int payload_type;
+  int seq_num;
+  uint32 timestamp;
+  uint32 ssrc;
+};
+
+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 GetRtpVersion(const void* data, size_t len, int* version);
+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);
+bool GetRtpHeader(const void* data, size_t len, RtpHeader* header);
+
+// Assumes marker bit is 0.
+bool SetRtpHeaderFlags(
+    void* data, size_t len,
+    bool padding, bool extension, int csrc_count);
+bool SetRtpPayloadType(void* data, size_t len, int value);
+bool SetRtpSeqNum(void* data, size_t len, int value);
+bool SetRtpTimestamp(void* data, size_t len, uint32 value);
+bool SetRtpSsrc(void* data, size_t len, uint32 value);
+// Assumes version 2, no padding, no extensions, no csrcs.
+bool SetRtpHeader(void* data, size_t len, const RtpHeader& header);
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_RTPUTILS_H_
diff --git a/talk/media/base/rtputils_unittest.cc b/talk/media/base/rtputils_unittest.cc
new file mode 100644
index 0000000..d3ea521
--- /dev/null
+++ b/talk/media/base/rtputils_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 "talk/base/gunit.h"
+#include "talk/media/base/fakertp.h"
+#include "talk/media/base/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 ver;
+  EXPECT_TRUE(GetRtpVersion(kPcmuFrame, sizeof(kPcmuFrame), &ver));
+  EXPECT_EQ(2, ver);
+
+  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);
+
+  RtpHeader header;
+  EXPECT_TRUE(GetRtpHeader(kPcmuFrame, sizeof(kPcmuFrame), &header));
+  EXPECT_EQ(0, header.payload_type);
+  EXPECT_EQ(1, header.seq_num);
+  EXPECT_EQ(0u, header.timestamp);
+  EXPECT_EQ(1u, header.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, SetRtp) {
+  unsigned char packet[] = {
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+  };
+
+  EXPECT_TRUE(SetRtpHeaderFlags(packet, sizeof(packet), false, false, 0));
+  EXPECT_TRUE(SetRtpPayloadType(packet, sizeof(packet), 9u));
+  EXPECT_TRUE(SetRtpSeqNum(packet, sizeof(packet), 1111u));
+  EXPECT_TRUE(SetRtpTimestamp(packet, sizeof(packet), 2222u));
+  EXPECT_TRUE(SetRtpSsrc(packet, sizeof(packet), 3333u));
+
+  // Bits: 10 0 0 0000
+  EXPECT_EQ(128u, packet[0]);
+  size_t len;
+  EXPECT_TRUE(GetRtpHeaderLen(packet, sizeof(packet), &len));
+  EXPECT_EQ(12U, len);
+  RtpHeader header;
+  EXPECT_TRUE(GetRtpHeader(packet, sizeof(packet), &header));
+  EXPECT_EQ(9, header.payload_type);
+  EXPECT_EQ(1111, header.seq_num);
+  EXPECT_EQ(2222u, header.timestamp);
+  EXPECT_EQ(3333u, header.ssrc);
+
+  unsigned char packet2[] = {
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+  };
+
+  EXPECT_TRUE(SetRtpHeader(packet2, sizeof(packet2), header));
+
+  // Bits: 10 0 0 0000
+  EXPECT_EQ(128u, packet2[0]);
+  EXPECT_TRUE(GetRtpHeaderLen(packet2, sizeof(packet2), &len));
+  EXPECT_EQ(12U, len);
+  EXPECT_TRUE(GetRtpHeader(packet2, sizeof(packet2), &header));
+  EXPECT_EQ(9, header.payload_type);
+  EXPECT_EQ(1111, header.seq_num);
+  EXPECT_EQ(2222u, header.timestamp);
+  EXPECT_EQ(3333u, header.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/media/base/screencastid.h b/talk/media/base/screencastid.h
new file mode 100644
index 0000000..d1f84f3
--- /dev/null
+++ b/talk/media/base/screencastid.h
@@ -0,0 +1,88 @@
+// Copyright 2012 Google Inc.
+// Author: thorcarpenter@google.com (Thor Carpenter)
+//
+// Defines variant class ScreencastId that combines WindowId and DesktopId.
+
+#ifndef TALK_MEDIA_BASE_SCREENCASTID_H_
+#define TALK_MEDIA_BASE_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_MEDIA_BASE_SCREENCASTID_H_
diff --git a/talk/media/base/streamparams.cc b/talk/media/base/streamparams.cc
new file mode 100644
index 0000000..08eeea7
--- /dev/null
+++ b/talk/media/base/streamparams.cc
@@ -0,0 +1,182 @@
+/*
+ * 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/media/base/streamparams.h"
+
+#include <sstream>
+
+namespace cricket {
+
+const char kFecSsrcGroupSemantics[] = "FEC";
+const char kFidSsrcGroupSemantics[] = "FID";
+
+static std::string SsrcsToString(const std::vector<uint32>& ssrcs) {
+  std::ostringstream ost;
+  ost << "ssrcs:[";
+  for (std::vector<uint32>::const_iterator it = ssrcs.begin();
+       it != ssrcs.end(); ++it) {
+    if (it != ssrcs.begin()) {
+      ost << ",";
+    }
+    ost << *it;
+  }
+  ost << "]";
+  return ost.str();
+}
+
+bool SsrcGroup::has_semantics(const std::string& semantics_in) const {
+  return (semantics == semantics_in && ssrcs.size() > 0);
+}
+
+std::string SsrcGroup::ToString() const {
+  std::ostringstream ost;
+  ost << "{";
+  ost << "semantics:" << semantics << ";";
+  ost << SsrcsToString(ssrcs);
+  ost << "}";
+  return ost.str();
+}
+
+std::string StreamParams::ToString() const {
+  std::ostringstream ost;
+  ost << "{";
+  if (!groupid.empty()) {
+    ost << "groupid:" << groupid << ";";
+  }
+  if (!id.empty()) {
+    ost << "id:" << id << ";";
+  }
+  ost << SsrcsToString(ssrcs) << ";";
+  ost << "ssrc_groups:";
+  for (std::vector<SsrcGroup>::const_iterator it = ssrc_groups.begin();
+       it != ssrc_groups.end(); ++it) {
+    if (it != ssrc_groups.begin()) {
+      ost << ",";
+    }
+    ost << it->ToString();
+  }
+  ost << ";";
+  if (!type.empty()) {
+    ost << "type:" << type << ";";
+  }
+  if (!display.empty()) {
+    ost << "display:" << display << ";";
+  }
+  if (!cname.empty()) {
+    ost << "cname:" << cname << ";";
+  }
+  if (!sync_label.empty()) {
+    ost << "sync_label:" << sync_label;
+  }
+  ost << "}";
+  return ost.str();
+}
+
+bool StreamParams::AddSecondarySsrc(const std::string& semantics,
+                                    uint32 primary_ssrc,
+                                    uint32 secondary_ssrc) {
+  if (!has_ssrc(primary_ssrc)) {
+    return false;
+  }
+
+  ssrcs.push_back(secondary_ssrc);
+  std::vector<uint32> ssrc_vector;
+  ssrc_vector.push_back(primary_ssrc);
+  ssrc_vector.push_back(secondary_ssrc);
+  SsrcGroup ssrc_group = SsrcGroup(semantics, ssrc_vector);
+  ssrc_groups.push_back(ssrc_group);
+  return true;
+}
+
+bool StreamParams::GetSecondarySsrc(const std::string& semantics,
+                                    uint32 primary_ssrc,
+                                    uint32* secondary_ssrc) const {
+  for (std::vector<SsrcGroup>::const_iterator it = ssrc_groups.begin();
+       it != ssrc_groups.end(); ++it) {
+    if (it->has_semantics(semantics) &&
+          it->ssrcs.size() >= 2 &&
+          it->ssrcs[0] == primary_ssrc) {
+      *secondary_ssrc = it->ssrcs[1];
+      return true;
+    }
+  }
+  return false;
+}
+
+bool GetStream(const StreamParamsVec& streams,
+               const StreamSelector& selector,
+               StreamParams* stream_out) {
+  for (StreamParamsVec::const_iterator stream = streams.begin();
+       stream != streams.end(); ++stream) {
+    if (selector.Matches(*stream)) {
+      if (stream_out != NULL) {
+        *stream_out = *stream;
+      }
+      return true;
+    }
+  }
+  return false;
+}
+
+bool GetStreamBySsrc(const StreamParamsVec& streams, uint32 ssrc,
+                     StreamParams* stream_out) {
+  return GetStream(streams, StreamSelector(ssrc), stream_out);
+}
+
+bool GetStreamByIds(const StreamParamsVec& streams,
+                    const std::string& groupid,
+                    const std::string& id,
+                    StreamParams* stream_out) {
+  return GetStream(streams, StreamSelector(groupid, id), stream_out);
+}
+
+bool RemoveStream(StreamParamsVec* streams,
+                  const StreamSelector& selector) {
+  bool ret = false;
+  for (StreamParamsVec::iterator stream = streams->begin();
+       stream != streams->end(); ) {
+    if (selector.Matches(*stream)) {
+      stream = streams->erase(stream);
+      ret = true;
+    } else {
+      ++stream;
+    }
+  }
+  return ret;
+}
+
+bool RemoveStreamBySsrc(StreamParamsVec* streams, uint32 ssrc) {
+  return RemoveStream(streams, StreamSelector(ssrc));
+}
+
+bool RemoveStreamByIds(StreamParamsVec* streams,
+                       const std::string& groupid,
+                       const std::string& id) {
+  return RemoveStream(streams, StreamSelector(groupid, id));
+}
+
+}  // namespace cricket
diff --git a/talk/media/base/streamparams.h b/talk/media/base/streamparams.h
new file mode 100644
index 0000000..1561d6f
--- /dev/null
+++ b/talk/media/base/streamparams.h
@@ -0,0 +1,209 @@
+/*
+ * 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.
+
+#ifndef TALK_MEDIA_BASE_STREAMPARAMS_H_
+#define TALK_MEDIA_BASE_STREAMPARAMS_H_
+
+#include <algorithm>
+#include <string>
+#include <set>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+
+namespace cricket {
+
+extern const char kFecSsrcGroupSemantics[];
+extern const char kFidSsrcGroupSemantics[];
+
+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);
+  }
+
+  bool has_semantics(const std::string& semantics) const;
+
+  std::string ToString() const;
+
+  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 (groupid == other.groupid &&
+            id == other.id &&
+            ssrcs == other.ssrcs &&
+            ssrc_groups == other.ssrc_groups &&
+            type == other.type &&
+            display == other.display &&
+            cname == other.cname &&
+            sync_label == other.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);
+  }
+  bool has_ssrc_groups() const {
+    return !ssrc_groups.empty();
+  }
+  bool has_ssrc_group(const std::string& semantics) const {
+    return (get_ssrc_group(semantics) != NULL);
+  }
+  const SsrcGroup* get_ssrc_group(const std::string& semantics) const {
+    for (std::vector<SsrcGroup>::const_iterator it = ssrc_groups.begin();
+         it != ssrc_groups.end(); ++it) {
+      if (it->has_semantics(semantics)) {
+        return &(*it);
+      }
+    }
+    return NULL;
+  }
+
+  // Convenience function to add an FID ssrc for a primary_ssrc
+  // that's already been added.
+  inline bool AddFidSsrc(uint32 primary_ssrc, uint32 fid_ssrc) {
+    return AddSecondarySsrc(kFidSsrcGroupSemantics, primary_ssrc, fid_ssrc);
+  }
+
+  // Convenience function to lookup the FID ssrc for a primary_ssrc.
+  // Returns false if primary_ssrc not found or FID not defined for it.
+  inline bool GetFidSsrc(uint32 primary_ssrc, uint32* fid_ssrc) const {
+    return GetSecondarySsrc(kFidSsrcGroupSemantics, primary_ssrc, fid_ssrc);
+  }
+
+  std::string ToString() const;
+
+  // 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 groupid;
+  // Unique per-groupid, not across all groupids
+  std::string id;
+  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.
+
+ private:
+  bool AddSecondarySsrc(const std::string& semantics, uint32 primary_ssrc,
+                        uint32 secondary_ssrc);
+  bool GetSecondarySsrc(const std::string& semantics, uint32 primary_ssrc,
+                        uint32* secondary_ssrc) const;
+};
+
+// A Stream can be selected by either groupid+id or ssrc.
+struct StreamSelector {
+  explicit StreamSelector(uint32 ssrc) :
+      ssrc(ssrc) {
+  }
+
+  StreamSelector(const std::string& groupid,
+                 const std::string& streamid) :
+      ssrc(0),
+      groupid(groupid),
+      streamid(streamid) {
+  }
+
+  bool Matches(const StreamParams& stream) const {
+    if (ssrc == 0) {
+      return stream.groupid == groupid && stream.id == streamid;
+    } else {
+      return stream.has_ssrc(ssrc);
+    }
+  }
+
+  uint32 ssrc;
+  std::string groupid;
+  std::string streamid;
+};
+
+typedef std::vector<StreamParams> StreamParamsVec;
+
+// Finds the stream in streams.  Returns true if found.
+bool GetStream(const StreamParamsVec& streams,
+               const StreamSelector& selector,
+               StreamParams* stream_out);
+bool GetStreamBySsrc(const StreamParamsVec& streams, uint32 ssrc,
+                     StreamParams* stream_out);
+bool GetStreamByIds(const StreamParamsVec& streams,
+                    const std::string& groupid,
+                    const std::string& id,
+                    StreamParams* stream_out);
+
+// Removes the stream from streams. Returns true if a stream is
+// found and removed.
+bool RemoveStream(StreamParamsVec* streams,
+                  const StreamSelector& selector);
+bool RemoveStreamBySsrc(StreamParamsVec* streams, uint32 ssrc);
+bool RemoveStreamByIds(StreamParamsVec* streams,
+                       const std::string& groupid,
+                       const std::string& id);
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_STREAMPARAMS_H_
diff --git a/talk/media/base/streamparams_unittest.cc b/talk/media/base/streamparams_unittest.cc
new file mode 100644
index 0000000..99d1603
--- /dev/null
+++ b/talk/media/base/streamparams_unittest.cc
@@ -0,0 +1,165 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/media/base/streamparams.h"
+#include "talk/media/base/testutils.h"
+
+static const uint32 kSscrs1[] = {1};
+static const uint32 kSscrs2[] = {1, 2};
+
+static cricket::StreamParams CreateStreamParamsWithSsrcGroup(
+    const std::string& semantics, const uint32 ssrcs_in[], size_t len) {
+  cricket::StreamParams stream;
+  std::vector<uint32> ssrcs(ssrcs_in, ssrcs_in + len);
+  cricket::SsrcGroup sg(semantics, ssrcs);
+  stream.ssrcs = ssrcs;
+  stream.ssrc_groups.push_back(sg);
+  return stream;
+}
+
+TEST(SsrcGroup, EqualNotEqual) {
+  cricket::SsrcGroup ssrc_groups[] = {
+    cricket::SsrcGroup("ABC", MAKE_VECTOR(kSscrs1)),
+    cricket::SsrcGroup("ABC", MAKE_VECTOR(kSscrs2)),
+    cricket::SsrcGroup("Abc", MAKE_VECTOR(kSscrs2)),
+    cricket::SsrcGroup("abc", MAKE_VECTOR(kSscrs2)),
+  };
+
+  for (size_t i = 0; i < ARRAY_SIZE(ssrc_groups); ++i) {
+    for (size_t j = 0; j < ARRAY_SIZE(ssrc_groups); ++j) {
+      EXPECT_EQ((ssrc_groups[i] == ssrc_groups[j]), (i == j));
+      EXPECT_EQ((ssrc_groups[i] != ssrc_groups[j]), (i != j));
+    }
+  }
+}
+
+TEST(SsrcGroup, HasSemantics) {
+  cricket::SsrcGroup sg1("ABC", MAKE_VECTOR(kSscrs1));
+  EXPECT_TRUE(sg1.has_semantics("ABC"));
+
+  cricket::SsrcGroup sg2("Abc", MAKE_VECTOR(kSscrs1));
+  EXPECT_FALSE(sg2.has_semantics("ABC"));
+
+  cricket::SsrcGroup sg3("abc", MAKE_VECTOR(kSscrs1));
+  EXPECT_FALSE(sg3.has_semantics("ABC"));
+}
+
+TEST(SsrcGroup, ToString) {
+  cricket::SsrcGroup sg1("ABC", MAKE_VECTOR(kSscrs1));
+  EXPECT_STREQ("{semantics:ABC;ssrcs:[1]}", sg1.ToString().c_str());
+}
+
+TEST(StreamParams, CreateLegacy) {
+  const uint32 ssrc = 7;
+  cricket::StreamParams one_sp = cricket::StreamParams::CreateLegacy(ssrc);
+  EXPECT_EQ(1U, one_sp.ssrcs.size());
+  EXPECT_EQ(ssrc, one_sp.first_ssrc());
+  EXPECT_TRUE(one_sp.has_ssrcs());
+  EXPECT_TRUE(one_sp.has_ssrc(ssrc));
+  EXPECT_FALSE(one_sp.has_ssrc(ssrc+1));
+  EXPECT_FALSE(one_sp.has_ssrc_groups());
+  EXPECT_EQ(0U, one_sp.ssrc_groups.size());
+}
+
+TEST(StreamParams, HasSsrcGroup) {
+  cricket::StreamParams sp =
+      CreateStreamParamsWithSsrcGroup("XYZ", kSscrs2, ARRAY_SIZE(kSscrs2));
+  EXPECT_EQ(2U, sp.ssrcs.size());
+  EXPECT_EQ(kSscrs2[0], sp.first_ssrc());
+  EXPECT_TRUE(sp.has_ssrcs());
+  EXPECT_TRUE(sp.has_ssrc(kSscrs2[0]));
+  EXPECT_TRUE(sp.has_ssrc(kSscrs2[1]));
+  EXPECT_TRUE(sp.has_ssrc_group("XYZ"));
+  EXPECT_EQ(1U, sp.ssrc_groups.size());
+  EXPECT_EQ(2U, sp.ssrc_groups[0].ssrcs.size());
+  EXPECT_EQ(kSscrs2[0], sp.ssrc_groups[0].ssrcs[0]);
+  EXPECT_EQ(kSscrs2[1], sp.ssrc_groups[0].ssrcs[1]);
+}
+
+TEST(StreamParams, GetSsrcGroup) {
+  cricket::StreamParams sp =
+      CreateStreamParamsWithSsrcGroup("XYZ", kSscrs2, ARRAY_SIZE(kSscrs2));
+  EXPECT_EQ(NULL, sp.get_ssrc_group("xyz"));
+  EXPECT_EQ(&sp.ssrc_groups[0], sp.get_ssrc_group("XYZ"));
+}
+
+TEST(StreamParams, EqualNotEqual) {
+  cricket::StreamParams l1 = cricket::StreamParams::CreateLegacy(1);
+  cricket::StreamParams l2 = cricket::StreamParams::CreateLegacy(2);
+  cricket::StreamParams sg1 =
+      CreateStreamParamsWithSsrcGroup("ABC", kSscrs1, ARRAY_SIZE(kSscrs1));
+  cricket::StreamParams sg2 =
+      CreateStreamParamsWithSsrcGroup("ABC", kSscrs2, ARRAY_SIZE(kSscrs2));
+  cricket::StreamParams sg3 =
+      CreateStreamParamsWithSsrcGroup("Abc", kSscrs2, ARRAY_SIZE(kSscrs2));
+  cricket::StreamParams sg4 =
+      CreateStreamParamsWithSsrcGroup("abc", kSscrs2, ARRAY_SIZE(kSscrs2));
+  cricket::StreamParams sps[] = {l1, l2, sg1, sg2, sg3, sg4};
+
+  for (size_t i = 0; i < ARRAY_SIZE(sps); ++i) {
+    for (size_t j = 0; j < ARRAY_SIZE(sps); ++j) {
+      EXPECT_EQ((sps[i] == sps[j]), (i == j));
+      EXPECT_EQ((sps[i] != sps[j]), (i != j));
+    }
+  }
+}
+
+TEST(StreamParams, FidFunctions) {
+  uint32 fid_ssrc;
+
+  cricket::StreamParams sp = cricket::StreamParams::CreateLegacy(1);
+  EXPECT_FALSE(sp.AddFidSsrc(10, 20));
+  EXPECT_TRUE(sp.AddFidSsrc(1, 2));
+  EXPECT_TRUE(sp.GetFidSsrc(1, &fid_ssrc));
+  EXPECT_EQ(2u, fid_ssrc);
+  EXPECT_FALSE(sp.GetFidSsrc(15, &fid_ssrc));
+
+  sp.add_ssrc(20);
+  sp.AddFidSsrc(20, 30);
+  EXPECT_TRUE(sp.GetFidSsrc(20, &fid_ssrc));
+  EXPECT_EQ(30u, fid_ssrc);
+
+  // Manually create SsrcGroup to test bounds-checking
+  // in GetSecondarySsrc. We construct an invalid StreamParams
+  // for this.
+  std::vector<uint32> fid_vector;
+  fid_vector.push_back(13);
+  cricket::SsrcGroup invalid_fid_group(cricket::kFidSsrcGroupSemantics,
+                                        fid_vector);
+  cricket::StreamParams sp_invalid;
+  sp_invalid.add_ssrc(13);
+  sp_invalid.ssrc_groups.push_back(invalid_fid_group);
+  EXPECT_FALSE(sp_invalid.GetFidSsrc(13, &fid_ssrc));
+}
+
+TEST(StreamParams, ToString) {
+  cricket::StreamParams sp =
+      CreateStreamParamsWithSsrcGroup("XYZ", kSscrs2, ARRAY_SIZE(kSscrs2));
+  EXPECT_STREQ("{ssrcs:[1,2];ssrc_groups:{semantics:XYZ;ssrcs:[1,2]};}",
+               sp.ToString().c_str());
+}
diff --git a/talk/media/base/testutils.cc b/talk/media/base/testutils.cc
new file mode 100644
index 0000000..a5e2df9
--- /dev/null
+++ b/talk/media/base/testutils.cc
@@ -0,0 +1,338 @@
+/*
+ * 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/media/base/testutils.h"
+
+#include <math.h>
+
+#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/media/base/rtpdump.h"
+#include "talk/media/base/videocapturer.h"
+#include "talk/media/base/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)
+    : last_capture_state_(CS_STARTING),
+      frame_count_(0),
+      frame_fourcc_(0),
+      frame_width_(0),
+      frame_height_(0),
+      frame_size_(0),
+      resolution_changed_(false) {
+  capturer->SignalStateChange.connect(this,
+      &VideoCapturerListener::OnStateChange);
+  capturer->SignalFrameCaptured.connect(this,
+      &VideoCapturerListener::OnFrameCaptured);
+}
+
+void VideoCapturerListener::OnStateChange(VideoCapturer* capturer,
+                                          CaptureState result) {
+  last_capture_state_ = 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;
+  }
+}
+
+// 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("media");
+  path.AppendFolder("testdata");
+  path.SetFilename(filename);
+  return path.pathname();
+}
+
+// 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) {
+    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/media/base/testutils.h b/talk/media/base/testutils.h
new file mode 100644
index 0000000..7bc7dc3
--- /dev/null
+++ b/talk/media/base/testutils.h
@@ -0,0 +1,242 @@
+/*
+ * 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_MEDIA_BASE_TESTUTILS_H_
+#define TALK_MEDIA_BASE_TESTUTILS_H_
+
+#include <string>
+#include <vector>
+#if !defined(DISABLE_YUV)
+#include "libyuv/compare.h"
+#endif
+#include "talk/base/basictypes.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/window.h"
+#include "talk/media/base/mediachannel.h"
+#include "talk/media/base/videocapturer.h"
+#include "talk/media/base/videocommon.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);
+
+  CaptureState last_capture_state() const { return last_capture_state_; }
+  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 OnStateChange(VideoCapturer* capturer, CaptureState state);
+  void OnFrameCaptured(VideoCapturer* capturer, const CapturedFrame* frame);
+
+ private:
+  CaptureState last_capture_state_;
+  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
+static inline double ComputePSNR(double sse, double count) {
+#if !defined(DISABLE_YUV)
+  return libyuv::SumSquareErrorToPsnr(static_cast<uint64>(sse),
+                                      static_cast<uint64>(count));
+#else
+  if (sse <= 0.)
+    sse = 65025.0 * count / pow(10., 128./10.);  // produces max PSNR of 128
+  return 10.0 * log10(65025.0 * count / sse);
+#endif
+}
+
+static inline double ComputeSumSquareError(const uint8 *org, const uint8 *rec,
+                                           int size) {
+#if !defined(DISABLE_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);
+
+// 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);
+
+// Checks whether |codecs| contains |codec|; checks using Codec::Matches().
+template <class C>
+bool ContainsMatchingCodec(const std::vector<C>& codecs, const C& codec) {
+  typename std::vector<C>::const_iterator it;
+  for (it = codecs.begin(); it != codecs.end(); ++it) {
+    if (it->Matches(codec)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_TESTUTILS_H_
diff --git a/talk/media/base/videoadapter.cc b/talk/media/base/videoadapter.cc
new file mode 100644
index 0000000..1e5918a
--- /dev/null
+++ b/talk/media/base/videoadapter.cc
@@ -0,0 +1,588 @@
+// 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/media/base/videoadapter.h"
+
+#include <limits.h>  // For INT_MAX
+
+#include "talk/media/base/constants.h"
+#include "talk/base/logging.h"
+#include "talk/base/timeutils.h"
+#include "talk/media/base/videoframe.h"
+
+namespace cricket {
+
+// TODO(fbarchard): Make downgrades settable
+static const int kMaxCpuDowngrades = 2;  // Downgrade at most 2 times for CPU.
+static const int kDefaultDowngradeWaitTimeMs = 2000;
+
+// TODO(fbarchard): Consider making scale factor table settable, to allow
+// application to select quality vs performance tradeoff.
+// TODO(fbarchard): Add framerate scaling to tables for 1/2 framerate.
+// List of scale factors that adapter will scale by.
+#if defined(IOS) || defined(ANDROID)
+// Mobile needs 1/4 scale for VGA (640 x 360) to QQVGA (160 x 90)
+// or 1/4 scale for HVGA (480 x 270) to QQHVGA (120 x 67)
+static const int kMinNumPixels = 120 * 67;
+static float kScaleFactors[] = {
+  1.f / 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
+// Desktop needs 1/8 scale for HD (1280 x 720) to QQVGA (160 x 90)
+static const int kMinNumPixels = 160 * 100;
+static float kScaleFactors[] = {
+  1.f / 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
+
+static const int kNumScaleFactors = ARRAY_SIZE(kScaleFactors);
+
+// Find the scale factor that, when applied to width and height, is closest
+// 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 = kNumScaleFactors - 1;  // Default to max scale.
+  for (int i = 0; i < kNumScaleFactors; ++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 == 0) {  // Found exact match.
+        break;
+      }
+    }
+  }
+  return kScaleFactors[best_index];
+}
+
+// Finds the scale factor that, when applied to width and height, produces
+// fewer than num_pixels.
+float VideoAdapter::FindLowerScale(int width, int height,
+                                   int target_num_pixels) {
+  if (!target_num_pixels) {
+    return 0.f;
+  }
+  int best_distance = INT_MAX;
+  int best_index = kNumScaleFactors - 1;  // Default to max scale.
+  for (int i = 0; i < kNumScaleFactors; ++i) {
+    int test_num_pixels = static_cast<int>(width * kScaleFactors[i] *
+                                           height * kScaleFactors[i]);
+    int diff = target_num_pixels - test_num_pixels;
+    if (diff >= 0 && diff < best_distance) {
+      best_distance = diff;
+      best_index = i;
+      if (best_distance == 0) {  // 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_(INT_MAX),
+      black_output_(false),
+      is_black_(false),
+      interval_next_frame_(0) {
+}
+
+VideoAdapter::~VideoAdapter() {
+}
+
+void VideoAdapter::SetInputFormat(const VideoFrame& in_frame) {
+  talk_base::CritScope cs(&critical_section_);
+  input_format_.width = in_frame.GetWidth();
+  input_format_.height = in_frame.GetHeight();
+}
+
+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);
+}
+
+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_;
+}
+
+// TODO(fbarchard): Add AdaptFrameRate function that only drops frames but
+// not resolution.
+bool VideoAdapter::AdaptFrame(const VideoFrame* in_frame,
+                              const VideoFrame** out_frame) {
+  talk_base::CritScope cs(&critical_section_);
+  if (!in_frame || !out_frame) {
+    return false;
+  }
+
+  // Update input to actual frame dimensions.
+  SetInputFormat(*in_frame);
+
+  // 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 input fps and output fps.
+    // Normally output fps is less than input fps.
+    // TODO(fbarchard): Consider adjusting interval to reflect the adjusted
+    // interval between frames after dropping some frames.
+    interval_next_frame_ += input_format_.interval;
+    if (output_format_.interval > 0) {
+      if (interval_next_frame_ >= output_format_.interval) {
+        interval_next_frame_ %= output_format_.interval;
+      } else {
+        should_drop = true;
+      }
+    }
+  }
+  if (should_drop) {
+    *out_frame = NULL;
+    return true;
+  }
+
+  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 + .5f);
+    output_format_.height = static_cast<int>(in_frame->GetHeight() * scale +
+                                             .5f);
+  }
+
+  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_ ||
+      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_) {
+      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),
+      view_switch_(false),
+      cpu_downgrade_count_(0),
+      cpu_downgrade_wait_time_(0),
+      high_system_threshold_(kHighSystemCpuThreshold),
+      low_system_threshold_(kLowSystemCpuThreshold),
+      process_threshold_(kProcessCpuThreshold),
+      view_desired_num_pixels_(INT_MAX),
+      view_desired_interval_(0),
+      encoder_desired_num_pixels_(INT_MAX),
+      cpu_desired_num_pixels_(INT_MAX),
+      adapt_reason_(0) {
+}
+
+// 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 >= high_system_threshold_ * max_cpus &&
+      process_load >= process_threshold_ * current_cpus) {
+    return CoordinatedVideoAdapter::DOWNGRADE;
+  // Upgrade if system is low.
+  } else if (system_load < low_system_threshold_ * 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;
+  int new_width, new_height;
+  bool changed = AdaptToMinimumFormat(&new_width, &new_height);
+  LOG(LS_INFO) << "VAdapt View Request: "
+               << format.width << "x" << format.height
+               << " Pixels: " << view_desired_num_pixels_
+               << " Changed: " << (changed ? "true" : "false")
+               << " To: " << new_width << "x" << new_height;
+}
+
+// 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;
+  }
+  int old_encoder_desired_num_pixels = encoder_desired_num_pixels_;
+  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_);
+    }
+  }
+  int new_width, new_height;
+  bool changed = AdaptToMinimumFormat(&new_width, &new_height);
+
+  // Ignore up or keep if no change.
+  if (DOWNGRADE != request && view_switch_ && !changed) {
+    encoder_desired_num_pixels_ = old_encoder_desired_num_pixels;
+    LOG(LS_VERBOSE) << "VAdapt ignoring GD request.";
+  }
+
+  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")
+               << " To: " << new_width << "x" << new_height;
+}
+
+// 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";
+          SignalCpuAdaptationUnable();
+      }
+      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(fbarchard): compute stepping up/down from OutputNumPixels but
+    // clamp to inputpixels / 4 (2 steps)
+    cpu_desired_num_pixels_ =  cpu_downgrade_count_ == 0 ? INT_MAX :
+        static_cast<int>(input_format().width * input_format().height >>
+                         cpu_downgrade_count_);
+  }
+  int new_width, new_height;
+  bool changed = AdaptToMinimumFormat(&new_width, &new_height);
+  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")
+               << " To: " << new_width << "x" << new_height;
+}
+
+// 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 + .5f);
+  new_output.height = static_cast<int>(input.height * scale + .5f);
+  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* new_width,
+                                                   int* new_height) {
+  VideoFormat new_output = output_format();
+  VideoFormat input = input_format();
+  if (input_format().IsSize0x0()) {
+    input = new_output;
+  }
+  int old_num_pixels = GetOutputNumPixels();
+  // Find resolution that respects ViewRequest or less pixels.
+  int view_desired_num_pixels = view_desired_num_pixels_;
+  int min_num_pixels = view_desired_num_pixels_;
+  if (!input.IsSize0x0()) {
+    float scale = FindLowerScale(input.width, input.height, min_num_pixels);
+    min_num_pixels = view_desired_num_pixels =
+        static_cast<int>(input.width * input.height * scale * scale + .5f);
+  }
+  // Reduce resolution further, if necessary, based on encoder bandwidth (GD).
+  if (encoder_desired_num_pixels_ &&
+      (encoder_desired_num_pixels_ < min_num_pixels)) {
+    min_num_pixels = encoder_desired_num_pixels_;
+  }
+  // Reduce resolution further, if necessary, based on CPU.
+  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);
+  }
+
+  // Determine which factors are keeping adapter resolution low.
+  // Caveat: Does not consider framerate.
+  adapt_reason_ = static_cast<AdaptReason>(0);
+  if (view_desired_num_pixels == min_num_pixels) {
+    adapt_reason_ |= ADAPTREASON_VIEW;
+  }
+  if (encoder_desired_num_pixels_ == min_num_pixels) {
+    adapt_reason_ |= ADAPTREASON_BANDWIDTH;
+  }
+  if (cpu_desired_num_pixels_ == min_num_pixels) {
+    adapt_reason_ |= ADAPTREASON_CPU;
+  }
+
+  // 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.
+  float scale = 1.0f;
+  if (!input.IsSize0x0()) {
+    scale = FindClosestScale(input.width, input.height, min_num_pixels);
+  }
+  if (scale == 1.0f) {
+    adapt_reason_ = 0;
+  }
+  *new_width = new_output.width = static_cast<int>(input.width * scale + .5f);
+  *new_height = new_output.height = static_cast<int>(input.height * scale +
+                                                     .5f);
+  new_output.interval = view_desired_interval_;
+  SetOutputFormat(new_output);
+  int new_num_pixels = GetOutputNumPixels();
+  bool changed = new_num_pixels != old_num_pixels;
+
+  static const char* kReasons[8] = {
+    "None",
+    "CPU",
+    "BANDWIDTH",
+    "CPU+BANDWIDTH",
+    "VIEW",
+    "CPU+VIEW",
+    "BANDWIDTH+VIEW",
+    "CPU+BANDWIDTH+VIEW",
+  };
+
+  LOG(LS_VERBOSE) << "VAdapt Status View: " << view_desired_num_pixels_
+                  << " GD: " << encoder_desired_num_pixels_
+                  << " CPU: " << cpu_desired_num_pixels_
+                  << " Pixels: " << min_num_pixels
+                  << " Input: " << input.width
+                  << "x" << input.height
+                  << " Scale: " << scale
+                  << " Resolution: " << new_output.width
+                  << "x" << new_output.height
+                  << " Changed: " << (changed ? "true" : "false")
+                  << " Reason: " << kReasons[adapt_reason_];
+  return changed;
+}
+
+}  // namespace cricket
diff --git a/talk/media/base/videoadapter.h b/talk/media/base/videoadapter.h
new file mode 100644
index 0000000..14829ab
--- /dev/null
+++ b/talk/media/base/videoadapter.h
@@ -0,0 +1,213 @@
+// 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_MEDIA_BASE_VIDEOADAPTER_H_  // NOLINT
+#define TALK_MEDIA_BASE_VIDEOADAPTER_H_
+
+#include "talk/base/common.h"  // For ASSERT
+#include "talk/base/criticalsection.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#include "talk/media/base/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 VideoFrame& in_frame);
+  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);
+  float FindLowerScale(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 interval_next_frame_;
+  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 };
+  enum {
+    ADAPTREASON_CPU = 1,
+    ADAPTREASON_BANDWIDTH = 2,
+    ADAPTREASON_VIEW = 4
+  };
+  typedef int AdaptReason;
+
+  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_; }
+  // Enable or disable video adaptation to fast switch View
+  void set_view_switch(bool enable) { view_switch_ = enable; }
+  bool view_switch() const { return view_switch_; }
+
+  CoordinatedVideoAdapter::AdaptReason adapt_reason() const {
+    return adapt_reason_;
+  }
+
+  // When the video is decreased, set the waiting time for CPU adaptation to
+  // decrease video again.
+  void set_cpu_downgrade_wait_time(uint32 cpu_downgrade_wait_time) {
+    if (cpu_downgrade_wait_time_ != static_cast<int>(cpu_downgrade_wait_time)) {
+      LOG(LS_INFO) << "VAdapt Change Cpu Downgrade Wait Time from: "
+                   << cpu_downgrade_wait_time_ << " to "
+                   << cpu_downgrade_wait_time;
+      cpu_downgrade_wait_time_ = static_cast<int>(cpu_downgrade_wait_time);
+    }
+  }
+  // CPU system load high threshold for reducing resolution.  e.g. 0.85f
+  void set_high_system_threshold(float high_system_threshold) {
+    ASSERT(high_system_threshold <= 1.0f);
+    ASSERT(high_system_threshold >= 0.0f);
+    if (high_system_threshold_ != high_system_threshold) {
+      LOG(LS_INFO) << "VAdapt Change High System Threshold from: "
+                   << high_system_threshold_ << " to " << high_system_threshold;
+      high_system_threshold_ = high_system_threshold;
+    }
+  }
+  float high_system_threshold() const { return high_system_threshold_; }
+  // CPU system load low threshold for increasing resolution.  e.g. 0.70f
+  void set_low_system_threshold(float low_system_threshold) {
+    ASSERT(low_system_threshold <= 1.0f);
+    ASSERT(low_system_threshold >= 0.0f);
+    if (low_system_threshold_ != low_system_threshold) {
+      LOG(LS_INFO) << "VAdapt Change Low System Threshold from: "
+                   << low_system_threshold_ << " to " << low_system_threshold;
+      low_system_threshold_ = low_system_threshold;
+    }
+  }
+  float low_system_threshold() const { return low_system_threshold_; }
+  // CPU process load threshold for reducing resolution.  e.g. 0.10f
+  void set_process_threshold(float process_threshold) {
+    ASSERT(process_threshold <= 1.0f);
+    ASSERT(process_threshold >= 0.0f);
+    if (process_threshold_ != process_threshold) {
+      LOG(LS_INFO) << "VAdapt Change High Process Threshold from: "
+                   << process_threshold_ << " to " << process_threshold;
+      process_threshold_ = process_threshold;
+    }
+  }
+  float process_threshold() const { return process_threshold_; }
+
+  // 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);
+
+  sigslot::signal0<> SignalCpuAdaptationUnable;
+
+ 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(int* new_width, int* new_height);
+  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.
+  bool view_switch_;  // True if view switch is enabled.
+  int cpu_downgrade_count_;
+  int cpu_downgrade_wait_time_;
+  // cpu system load thresholds relative to max cpus.
+  float high_system_threshold_;
+  float low_system_threshold_;
+  // cpu process load thresholds relative to current cpus.
+  float process_threshold_;
+  // 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_;
+  CoordinatedVideoAdapter::AdaptReason adapt_reason_;
+  // The critical section to protect handling requests.
+  talk_base::CriticalSection request_critical_section_;
+
+  DISALLOW_COPY_AND_ASSIGN(CoordinatedVideoAdapter);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_VIDEOADAPTER_H_  // NOLINT
diff --git a/talk/media/base/videocapturer.cc b/talk/media/base/videocapturer.cc
new file mode 100644
index 0000000..3bc2373
--- /dev/null
+++ b/talk/media/base/videocapturer.cc
@@ -0,0 +1,580 @@
+// 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.
+//
+// Implementation file of class VideoCapturer.
+
+#include "talk/media/base/videocapturer.h"
+
+#include <algorithm>
+
+#if !defined(DISABLE_YUV)
+#include "libyuv/scale_argb.h"
+#endif
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/systeminfo.h"
+#include "talk/media/base/videoprocessor.h"
+
+#if defined(HAVE_WEBRTC_VIDEO)
+#include "talk/media/webrtc/webrtcvideoframe.h"
+#endif  // HAVE_WEBRTC_VIDEO
+
+
+namespace cricket {
+
+namespace {
+
+// TODO(thorcarpenter): This is a BIG hack to flush the system with black
+// frames. Frontends should coordinate to update the video state of a muted
+// user. When all frontends to this consider removing the black frame business.
+const int kNumBlackFramesOnMute = 30;
+
+// MessageHandler constants.
+enum {
+  MSG_DO_PAUSE = 0,
+  MSG_DO_UNPAUSE,
+  MSG_STATE_CHANGE
+};
+
+static const int64 kMaxDistance = ~(static_cast<int64>(1) << 63);
+static const int kYU12Penalty = 16;  // Needs to be higher than MJPG index.
+static const int kDefaultScreencastFps = 5;
+typedef talk_base::TypedMessageData<CaptureState> StateChangeParams;
+
+}  // namespace
+
+/////////////////////////////////////////////////////////////////////
+// Implementation of struct CapturedFrame
+/////////////////////////////////////////////////////////////////////
+CapturedFrame::CapturedFrame()
+    : width(0),
+      height(0),
+      fourcc(0),
+      pixel_width(0),
+      pixel_height(0),
+      elapsed_time(0),
+      time_stamp(0),
+      data_size(0),
+      rotation(0),
+      data(NULL) {}
+
+// TODO(fbarchard): Remove this function once lmimediaengine stops using it.
+bool CapturedFrame::GetDataSize(uint32* size) const {
+  if (!size || data_size == CapturedFrame::kUnknownDataSize) {
+    return false;
+  }
+  *size = data_size;
+  return true;
+}
+
+/////////////////////////////////////////////////////////////////////
+// Implementation of class VideoCapturer
+/////////////////////////////////////////////////////////////////////
+VideoCapturer::VideoCapturer() : thread_(talk_base::Thread::Current()) {
+  Construct();
+}
+
+VideoCapturer::VideoCapturer(talk_base::Thread* thread) : thread_(thread) {
+  Construct();
+}
+
+void VideoCapturer::Construct() {
+  ClearAspectRatio();
+  enable_camera_list_ = false;
+  capture_state_ = CS_STOPPED;
+  SignalFrameCaptured.connect(this, &VideoCapturer::OnFrameCaptured);
+  scaled_width_ = 0;
+  scaled_height_ = 0;
+  muted_ = false;
+  black_frame_count_down_ = kNumBlackFramesOnMute;
+}
+
+const std::vector<VideoFormat>* VideoCapturer::GetSupportedFormats() const {
+  return &filtered_supported_formats_;
+}
+
+bool VideoCapturer::StartCapturing(const VideoFormat& capture_format) {
+  CaptureState result = Start(capture_format);
+  const bool success = (result == CS_RUNNING) || (result == CS_STARTING);
+  if (!success) {
+    return false;
+  }
+  if (result == CS_RUNNING) {
+    SetCaptureState(result);
+  }
+  return true;
+}
+
+void VideoCapturer::UpdateAspectRatio(int ratio_w, int ratio_h) {
+  if (ratio_w == 0 || ratio_h == 0) {
+    LOG(LS_WARNING) << "UpdateAspectRatio ignored invalid ratio: "
+                    << ratio_w << "x" << ratio_h;
+    return;
+  }
+  ratio_w_ = ratio_w;
+  ratio_h_ = ratio_h;
+}
+
+void VideoCapturer::ClearAspectRatio() {
+  ratio_w_ = 0;
+  ratio_h_ = 0;
+}
+
+// Override this to have more control of how your device is started/stopped.
+bool VideoCapturer::Pause(bool pause) {
+  if (pause) {
+    if (capture_state() == CS_PAUSED) {
+      return true;
+    }
+    bool is_running = capture_state() == CS_STARTING ||
+        capture_state() == CS_RUNNING;
+    if (!is_running) {
+      LOG(LS_ERROR) << "Cannot pause a stopped camera.";
+      return false;
+    }
+    LOG(LS_INFO) << "Pausing a camera.";
+    talk_base::scoped_ptr<VideoFormat> capture_format_when_paused(
+        capture_format_ ? new VideoFormat(*capture_format_) : NULL);
+    Stop();
+    SetCaptureState(CS_PAUSED);
+    // If you override this function be sure to restore the capture format
+    // after calling Stop().
+    SetCaptureFormat(capture_format_when_paused.get());
+  } else {  // Unpause.
+    if (capture_state() != CS_PAUSED) {
+      LOG(LS_WARNING) << "Cannot unpause a camera that hasn't been paused.";
+      return false;
+    }
+    if (!capture_format_) {
+      LOG(LS_ERROR) << "Missing capture_format_, cannot unpause a camera.";
+      return false;
+    }
+    if (muted_) {
+      LOG(LS_WARNING) << "Camera cannot be unpaused while muted.";
+      return false;
+    }
+    LOG(LS_INFO) << "Unpausing a camera.";
+    if (!Start(*capture_format_)) {
+      LOG(LS_ERROR) << "Camera failed to start when unpausing.";
+      return false;
+    }
+  }
+  return true;
+}
+
+bool VideoCapturer::Restart(const VideoFormat& capture_format) {
+  if (!IsRunning()) {
+    return StartCapturing(capture_format);
+  }
+
+  if (GetCaptureFormat() != NULL && *GetCaptureFormat() == capture_format) {
+    // The reqested format is the same; nothing to do.
+    return true;
+  }
+
+  Stop();
+  return StartCapturing(capture_format);
+}
+
+bool VideoCapturer::MuteToBlackThenPause(bool muted) {
+  if (muted == IsMuted()) {
+    return true;
+  }
+
+  LOG(LS_INFO) << (muted ? "Muting" : "Unmuting") << " this video capturer.";
+  muted_ = muted;  // Do this before calling Pause().
+  if (muted) {
+    // Reset black frame count down.
+    black_frame_count_down_ = kNumBlackFramesOnMute;
+    // Following frames will be overritten with black, then the camera will be
+    // paused.
+    return true;
+  }
+  // Start the camera.
+  thread_->Clear(this, MSG_DO_PAUSE);
+  return Pause(false);
+}
+
+void VideoCapturer::SetSupportedFormats(
+    const std::vector<VideoFormat>& formats) {
+  supported_formats_ = formats;
+  UpdateFilteredSupportedFormats();
+}
+
+bool VideoCapturer::GetBestCaptureFormat(const VideoFormat& format,
+                                         VideoFormat* best_format) {
+  // TODO(fbarchard): Directly support max_format.
+  UpdateFilteredSupportedFormats();
+  const std::vector<VideoFormat>* supported_formats = GetSupportedFormats();
+
+  if (supported_formats->empty()) {
+    return false;
+  }
+  LOG(LS_INFO) << " Capture Requested " << format.ToString();
+  int64 best_distance = kMaxDistance;
+  std::vector<VideoFormat>::const_iterator best = supported_formats->end();
+  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
+    // relatively bug free.
+    LOG(LS_INFO) << " Supported " << i->ToString() << " distance " << distance;
+    if (distance < best_distance) {
+      best_distance = distance;
+      best = i;
+    }
+  }
+  if (supported_formats->end() == best) {
+    LOG(LS_ERROR) << " No acceptable camera format found";
+    return false;
+  }
+
+  if (best_format) {
+    best_format->width = best->width;
+    best_format->height = best->height;
+    best_format->fourcc = best->fourcc;
+    best_format->interval = talk_base::_max(format.interval, best->interval);
+    LOG(LS_INFO) << " Best " << best_format->ToString() << " Interval "
+                 << best_format->interval << " distance " << best_distance;
+  }
+  return true;
+}
+
+void VideoCapturer::AddVideoProcessor(VideoProcessor* video_processor) {
+  talk_base::CritScope cs(&crit_);
+  ASSERT(std::find(video_processors_.begin(), video_processors_.end(),
+                   video_processor) == video_processors_.end());
+  video_processors_.push_back(video_processor);
+}
+
+bool VideoCapturer::RemoveVideoProcessor(VideoProcessor* video_processor) {
+  talk_base::CritScope cs(&crit_);
+  VideoProcessors::iterator found = std::find(
+      video_processors_.begin(), video_processors_.end(), video_processor);
+  if (found == video_processors_.end()) {
+    return false;
+  }
+  video_processors_.erase(found);
+  return true;
+}
+
+void VideoCapturer::ConstrainSupportedFormats(const VideoFormat& max_format) {
+  max_format_.reset(new VideoFormat(max_format));
+  LOG(LS_VERBOSE) << " ConstrainSupportedFormats " << max_format.ToString();
+  UpdateFilteredSupportedFormats();
+}
+
+std::string VideoCapturer::ToString(const CapturedFrame* captured_frame) const {
+  std::string fourcc_name = GetFourccName(captured_frame->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 << captured_frame->width << "x" << captured_frame->height
+     << "x" << VideoFormat::IntervalToFps(captured_frame->elapsed_time);
+  return ss.str();
+}
+
+void VideoCapturer::OnFrameCaptured(VideoCapturer*,
+                                    const CapturedFrame* captured_frame) {
+  if (muted_) {
+    if (black_frame_count_down_ == 0) {
+      thread_->Post(this, MSG_DO_PAUSE, NULL);
+    } else {
+      --black_frame_count_down_;
+    }
+  }
+
+  if (SignalVideoFrame.is_empty()) {
+    return;
+  }
+#if defined(HAVE_WEBRTC_VIDEO)
+#define VIDEO_FRAME_NAME WebRtcVideoFrame
+#endif
+#if defined(VIDEO_FRAME_NAME)
+#if !defined(DISABLE_YUV)
+  if (IsScreencast()) {
+    int scaled_width, scaled_height;
+    int desired_screencast_fps = capture_format_.get() ?
+        VideoFormat::IntervalToFps(capture_format_->interval) :
+        kDefaultScreencastFps;
+    ComputeScale(captured_frame->width, captured_frame->height,
+                 desired_screencast_fps, &scaled_width, &scaled_height);
+
+    if (scaled_width != scaled_width_ || scaled_height != scaled_height_) {
+      LOG(LS_VERBOSE) << "Scaling Screencast from "
+                      << captured_frame->width << "x"
+                      << captured_frame->height << " to "
+                      << scaled_width << "x" << scaled_height;
+      scaled_width_ = scaled_width;
+      scaled_height_ = scaled_height;
+    }
+    if (FOURCC_ARGB == captured_frame->fourcc &&
+        (scaled_width != captured_frame->height ||
+         scaled_height != captured_frame->height)) {
+      CapturedFrame* scaled_frame = const_cast<CapturedFrame*>(captured_frame);
+      // Compute new width such that width * height is less than maximum but
+      // maintains original captured frame aspect ratio.
+      // Round down width to multiple of 4 so odd width won't round up beyond
+      // maximum, and so chroma channel is even width to simplify spatial
+      // resampling.
+      libyuv::ARGBScale(reinterpret_cast<const uint8*>(captured_frame->data),
+                        captured_frame->width * 4, captured_frame->width,
+                        captured_frame->height,
+                        reinterpret_cast<uint8*>(scaled_frame->data),
+                        scaled_width * 4, scaled_width, scaled_height,
+                        libyuv::kFilterBilinear);
+      scaled_frame->width = scaled_width;
+      scaled_frame->height = scaled_height;
+      scaled_frame->data_size = scaled_width * 4 * scaled_height;
+    }
+  }
+#endif  // !DISABLE_YUV
+        // Size to crop captured frame to.  This adjusts the captured frames
+        // aspect ratio to match the final view aspect ratio, considering pixel
+  // aspect ratio and rotation.  The final size may be scaled down by video
+  // adapter to better match ratio_w_ x ratio_h_.
+  // Note that abs() of frame height is passed in, because source may be
+  // inverted, but output will be positive.
+  int desired_width = captured_frame->width;
+  int desired_height = captured_frame->height;
+
+  // TODO(fbarchard): Improve logic to pad or crop.
+  // MJPG can crop vertically, but not horizontally.  This logic disables crop.
+  // Alternatively we could pad the image with black, or implement a 2 step
+  // crop.
+  bool can_crop = true;
+  if (captured_frame->fourcc == FOURCC_MJPG) {
+    float cam_aspect = static_cast<float>(captured_frame->width) /
+        static_cast<float>(captured_frame->height);
+    float view_aspect = static_cast<float>(ratio_w_) /
+        static_cast<float>(ratio_h_);
+    can_crop = cam_aspect <= view_aspect;
+  }
+  if (can_crop && !IsScreencast()) {
+    // TODO(ronghuawu): The capturer should always produce the native
+    // resolution and the cropping should be done in downstream code.
+    ComputeCrop(ratio_w_, ratio_h_, captured_frame->width,
+                abs(captured_frame->height), captured_frame->pixel_width,
+                captured_frame->pixel_height, captured_frame->rotation,
+                &desired_width, &desired_height);
+  }
+
+  VIDEO_FRAME_NAME i420_frame;
+  if (!i420_frame.Init(captured_frame, desired_width, desired_height)) {
+    // TODO(fbarchard): LOG more information about captured frame attributes.
+    LOG(LS_ERROR) << "Couldn't convert to I420! "
+                  << "From " << ToString(captured_frame) << " To "
+                  << desired_width << " x " << desired_height;
+    return;
+  }
+  if (!muted_ && !ApplyProcessors(&i420_frame)) {
+    // Processor dropped the frame.
+    return;
+  }
+  if (muted_) {
+    i420_frame.SetToBlack();
+  }
+  SignalVideoFrame(this, &i420_frame);
+#endif  // VIDEO_FRAME_NAME
+}
+
+void VideoCapturer::SetCaptureState(CaptureState state) {
+  if (state == capture_state_) {
+    // Don't trigger a state changed callback if the state hasn't changed.
+    return;
+  }
+  StateChangeParams* state_params = new StateChangeParams(state);
+  capture_state_ = state;
+  thread_->Post(this, MSG_STATE_CHANGE, state_params);
+}
+
+void VideoCapturer::OnMessage(talk_base::Message* message) {
+  switch (message->message_id) {
+    case MSG_STATE_CHANGE: {
+      talk_base::scoped_ptr<StateChangeParams> p(
+          static_cast<StateChangeParams*>(message->pdata));
+      SignalStateChange(this, p->data());
+      break;
+    }
+    case MSG_DO_PAUSE: {
+      Pause(true);
+      break;
+    }
+    case MSG_DO_UNPAUSE: {
+      Pause(false);
+      break;
+    }
+    default: {
+      ASSERT(false);
+    }
+  }
+}
+
+// Get the distance between the supported and desired formats.
+// Prioritization is done according to this algorithm:
+// 1) Width closeness. If not same, we prefer wider.
+// 2) Height closeness. If not same, we prefer higher.
+// 3) Framerate closeness. If not same, we prefer faster.
+// 4) Compression. If desired format has a specific fourcc, we need exact match;
+//                otherwise, we use preference.
+int64 VideoCapturer::GetFormatDistance(const VideoFormat& desired,
+                                       const VideoFormat& supported) {
+  int64 distance = kMaxDistance;
+
+  // Check fourcc.
+  uint32 supported_fourcc = CanonicalFourCC(supported.fourcc);
+  int64 delta_fourcc = kMaxDistance;
+  if (FOURCC_ANY == desired.fourcc) {
+    // Any fourcc is OK for the desired. Use preference to find best fourcc.
+    std::vector<uint32> preferred_fourccs;
+    if (!GetPreferredFourccs(&preferred_fourccs)) {
+      return distance;
+    }
+
+    for (size_t i = 0; i < preferred_fourccs.size(); ++i) {
+      if (supported_fourcc == CanonicalFourCC(preferred_fourccs[i])) {
+        delta_fourcc = i;
+#ifdef LINUX
+        // For HD avoid YU12 which is a software conversion and has 2 bugs
+        // b/7326348 b/6960899.  Reenable when fixed.
+        if (supported.height >= 720 && (supported_fourcc == FOURCC_YU12 ||
+                                        supported_fourcc == FOURCC_YV12)) {
+          delta_fourcc += kYU12Penalty;
+        }
+#endif
+        break;
+      }
+    }
+  } else if (supported_fourcc == CanonicalFourCC(desired.fourcc)) {
+    delta_fourcc = 0;  // Need exact match.
+  }
+
+  if (kMaxDistance == delta_fourcc) {
+    // Failed to match fourcc.
+    return distance;
+  }
+
+  // Check resolution and fps.
+  int desired_width = desired.width;
+  int desired_height = desired.height;
+  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 ? supported.width * desired_height / desired_width
+                    : desired_height;
+  int64 delta_h = supported.height - aspect_h;
+
+  distance = 0;
+  // Set high penalty if the supported format is lower than the desired format.
+  // 3x means we would prefer down to down to 3/4, than up to double.
+  // But we'd prefer up to double than down to 1/2.  This is conservative,
+  // 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.
+  static const int kDownPenalty = -3;
+  if (delta_w < 0) {
+    delta_w = delta_w * kDownPenalty;
+  }
+  if (delta_h < 0) {
+    delta_h = delta_h * kDownPenalty;
+  }
+  // Require camera fps to be at least 80% of what is requested if resolution
+  // matches.
+  // Require camera fps to be at least 96% of what is requested, or higher,
+  // if resolution differs. 96% allows for slight variations in fps. e.g. 29.97
+  if (delta_fps < 0) {
+    int64 min_desirable_fps = delta_w ?
+    VideoFormat::IntervalToFps(desired.interval) * 29 / 30 :
+    VideoFormat::IntervalToFps(desired.interval) * 24 / 30;
+    delta_fps = -delta_fps;
+    if (supported_fps < min_desirable_fps) {
+      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.
+  distance |=
+      (delta_w << 28) | (delta_h << 16) | (delta_fps << 8) | delta_fourcc;
+
+  return distance;
+}
+
+bool VideoCapturer::ApplyProcessors(VideoFrame* video_frame) {
+  bool drop_frame = false;
+  talk_base::CritScope cs(&crit_);
+  for (VideoProcessors::iterator iter = video_processors_.begin();
+       iter != video_processors_.end(); ++iter) {
+    (*iter)->OnFrame(kDummyVideoSsrc, video_frame, &drop_frame);
+    if (drop_frame) {
+      return false;
+    }
+  }
+  return true;
+}
+
+void VideoCapturer::UpdateFilteredSupportedFormats() {
+  filtered_supported_formats_.clear();
+  filtered_supported_formats_ = supported_formats_;
+  if (!max_format_) {
+    return;
+  }
+  std::vector<VideoFormat>::iterator iter = filtered_supported_formats_.begin();
+  while (iter != filtered_supported_formats_.end()) {
+    if (ShouldFilterFormat(*iter)) {
+      iter = filtered_supported_formats_.erase(iter);
+    } else {
+      ++iter;
+    }
+  }
+  if (filtered_supported_formats_.empty()) {
+    // The device only captures at resolutions higher than |max_format_| this
+    // indicates that |max_format_| should be ignored as it is better to capture
+    // at too high a resolution than to not capture at all.
+    filtered_supported_formats_ = supported_formats_;
+  }
+}
+
+bool VideoCapturer::ShouldFilterFormat(const VideoFormat& format) const {
+  if (!enable_camera_list_) {
+    return false;
+  }
+  return format.width > max_format_->width ||
+         format.height > max_format_->height;
+}
+
+}  // namespace cricket
diff --git a/talk/media/base/videocapturer.h b/talk/media/base/videocapturer.h
new file mode 100644
index 0000000..3997976
--- /dev/null
+++ b/talk/media/base/videocapturer.h
@@ -0,0 +1,327 @@
+// 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.
+//
+// Declaration of abstract class VideoCapturer
+
+#ifndef TALK_MEDIA_BASE_VIDEOCAPTURER_H_
+#define TALK_MEDIA_BASE_VIDEOCAPTURER_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/criticalsection.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/thread.h"
+#include "talk/media/base/videocommon.h"
+#include "talk/media/devices/devicemanager.h"
+
+
+namespace cricket {
+
+class VideoProcessor;
+
+// Current state of the capturer.
+// TODO(hellner): CS_NO_DEVICE is an error code not a capture state. Separate
+//                error codes and states.
+enum CaptureState {
+  CS_STOPPED,    // The capturer has been stopped or hasn't started yet.
+  CS_STARTING,   // The capturer is in the process of starting. Note, it may
+                 // still fail to start.
+  CS_RUNNING,    // The capturer has been started successfully and is now
+                 // capturing.
+  CS_PAUSED,     // The capturer has been paused.
+  CS_FAILED,     // The capturer failed to start.
+  CS_NO_DEVICE,  // The capturer has no device and consequently failed to start.
+};
+
+class VideoFrame;
+
+struct CapturedFrame {
+  static const uint32 kFrameHeaderSize = 40;  // Size from width to data_size.
+  static const uint32 kUnknownDataSize = 0xFFFFFFFF;
+
+  CapturedFrame();
+
+  // Get the number of bytes of the frame data. If data_size is known, return
+  // it directly. Otherwise, calculate the size based on width, height, and
+  // fourcc. Return true if succeeded.
+  bool GetDataSize(uint32* size) const;
+
+  // The width and height of the captured frame could be different from those
+  // of VideoFormat. Once the first frame is captured, the width, height,
+  // fourcc, pixel_width, and pixel_height should keep the same over frames.
+  int    width;         // in number of pixels
+  int    height;        // in number of pixels
+  uint32 fourcc;        // compression
+  uint32 pixel_width;   // width of a pixel, default is 1
+  uint32 pixel_height;  // height of a pixel, default is 1
+  int64  elapsed_time;  // elapsed time since the creation of the frame
+                        // source (that is, the camera), in nanoseconds.
+  int64  time_stamp;    // timestamp of when the frame was captured, in unix
+                        // time with nanosecond units.
+  uint32 data_size;     // number of bytes of the frame data
+  int    rotation;      // rotation in degrees of the frame (0, 90, 180, 270)
+  void*  data;          // pointer to the frame data. This object allocates the
+                        // memory or points to an existing memory.
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CapturedFrame);
+};
+
+// VideoCapturer is an abstract class that defines the interfaces for video
+// capturing. The subclasses implement the video capturer for various types of
+// capturers and various platforms.
+//
+// The captured frames may need to be adapted (for example, cropping). Adaptors
+// can be registered to the capturer or applied externally to the capturer.
+// If the adaptor is needed, it acts as the downstream of VideoCapturer, adapts
+// the captured frames, and delivers the adapted frames to other components
+// such as the encoder. Effects can also be registered to the capturer or
+// applied externally.
+//
+// Programming model:
+//   Create an object of a subclass of VideoCapturer
+//   Initialize
+//   SignalStateChange.connect()
+//   SignalFrameCaptured.connect()
+//   Find the capture format for Start() by either calling GetSupportedFormats()
+//   and selecting one of the supported or calling GetBestCaptureFormat().
+//   Start()
+//   GetCaptureFormat() optionally
+//   Stop()
+//
+// Assumption:
+//   The Start() and Stop() methods are called by a single thread (E.g., the
+//   media engine thread). Hence, the VideoCapture subclasses dont need to be
+//   thread safe.
+//
+class VideoCapturer
+    : public sigslot::has_slots<>,
+      public talk_base::MessageHandler {
+ public:
+  typedef std::vector<VideoProcessor*> VideoProcessors;
+
+  // All signals are marshalled to |thread| or the creating thread if
+  // none is provided.
+  VideoCapturer();
+  explicit VideoCapturer(talk_base::Thread* thread);
+  virtual ~VideoCapturer() {}
+
+  // Gets the id of the underlying device, which is available after the capturer
+  // is initialized. Can be used to determine if two capturers reference the
+  // same device.
+  const std::string& GetId() const { return id_; }
+
+  // Get the capture formats supported by the video capturer. The supported
+  // formats are non empty after the device has been opened successfully.
+  const std::vector<VideoFormat>* GetSupportedFormats() const;
+
+  // Get the best capture format for the desired format. The best format is the
+  // same as one of the supported formats except that the frame interval may be
+  // different. If the application asks for 16x9 and the camera does not support
+  // 16x9 HD or the application asks for 16x10, we find the closest 4x3 and then
+  // crop; Otherwise, we find what the application asks for. Note that we assume
+  // that for HD, the desired format is always 16x9. The subclasses can override
+  // the default implementation.
+  // Parameters
+  //   desired: the input desired format. If desired.fourcc is not kAnyFourcc,
+  //            the best capture format has the exactly same fourcc. Otherwise,
+  //            the best capture format uses a fourcc in GetPreferredFourccs().
+  //   best_format: the output of the best capture format.
+  // Return false if there is no such a best format, that is, the desired format
+  // is not supported.
+  virtual bool GetBestCaptureFormat(const VideoFormat& desired,
+                                    VideoFormat* best_format);
+
+  // TODO(hellner): deprecate (make private) the Start API in favor of this one.
+  //                Also remove CS_STARTING as it is implied by the return
+  //                value of StartCapturing().
+  bool StartCapturing(const VideoFormat& capture_format);
+  // Start the video capturer with the specified capture format.
+  // Parameter
+  //   capture_format: The caller got this parameter by either calling
+  //                   GetSupportedFormats() and selecting one of the supported
+  //                   or calling GetBestCaptureFormat().
+  // Return
+  //   CS_STARTING:  The capturer is trying to start. Success or failure will
+  //                 be notified via the |SignalStateChange| callback.
+  //   CS_RUNNING:   if the capturer is started and capturing.
+  //   CS_PAUSED:    Will never be returned.
+  //   CS_FAILED:    if the capturer failes to start..
+  //   CS_NO_DEVICE: if the capturer has no device and fails to start.
+  virtual CaptureState Start(const VideoFormat& capture_format) = 0;
+  // Sets the desired aspect ratio. If the capturer is capturing at another
+  // aspect ratio it will crop the width or the height so that asked for
+  // aspect ratio is acheived. Note that ratio_w and ratio_h do not need to be
+  // relatively prime.
+  void UpdateAspectRatio(int ratio_w, int ratio_h);
+  void ClearAspectRatio();
+
+  // Get the current capture format, which is set by the Start() call.
+  // Note that the width and height of the captured frames may differ from the
+  // capture format. For example, the capture format is HD but the captured
+  // frames may be smaller than HD.
+  const VideoFormat* GetCaptureFormat() const {
+    return capture_format_.get();
+  }
+
+  // Pause the video capturer.
+  virtual bool Pause(bool paused);
+  // Stop the video capturer.
+  virtual void Stop() = 0;
+  // Check if the video capturer is running.
+  virtual bool IsRunning() = 0;
+  // Restart the video capturer with the new |capture_format|.
+  // Default implementation stops and starts the capturer.
+  virtual bool Restart(const VideoFormat& capture_format);
+  // TODO(thorcarpenter): This behavior of keeping the camera open just to emit
+  // black frames is a total hack and should be fixed.
+  // When muting, produce black frames then pause the camera.
+  // When unmuting, start the camera. Camera starts unmuted.
+  virtual bool MuteToBlackThenPause(bool muted);
+  virtual bool IsMuted() const {
+    return muted_;
+  }
+  CaptureState capture_state() const {
+    return capture_state_;
+  }
+
+  // Adds a video processor that will be applied on VideoFrames returned by
+  // |SignalVideoFrame|. Multiple video processors can be added. The video
+  // processors will be applied in the order they were added.
+  void AddVideoProcessor(VideoProcessor* video_processor);
+  // Removes the |video_processor| from the list of video processors or
+  // returns false.
+  bool RemoveVideoProcessor(VideoProcessor* video_processor);
+
+  // Returns true if the capturer is screencasting. This can be used to
+  // implement screencast specific behavior.
+  virtual bool IsScreencast() const = 0;
+
+  // Caps the VideoCapturer's format according to max_format. It can e.g. be
+  // used to prevent cameras from capturing at a resolution or framerate that
+  // the capturer is capable of but not performing satisfactorily at.
+  // The capping is an upper bound for each component of the capturing format.
+  // The fourcc component is ignored.
+  void ConstrainSupportedFormats(const VideoFormat& max_format);
+
+  void set_enable_camera_list(bool enable_camera_list) {
+    enable_camera_list_ = enable_camera_list;
+  }
+  bool enable_camera_list() {
+    return enable_camera_list_;
+  }
+  // Signal all capture state changes that are not a direct result of calling
+  // Start().
+  sigslot::signal2<VideoCapturer*, CaptureState> SignalStateChange;
+  // TODO(hellner): rename |SignalFrameCaptured| to something like
+  //                |SignalRawFrame| or |SignalNativeFrame|.
+  // Frame callbacks are multithreaded to allow disconnect and connect to be
+  // called concurrently. It also ensures that it is safe to call disconnect
+  // at any time which is needed since the signal may be called from an
+  // unmarshalled thread owned by the VideoCapturer.
+  // Signal the captured frame to downstream.
+  sigslot::signal2<VideoCapturer*, const CapturedFrame*,
+                   sigslot::multi_threaded_local> SignalFrameCaptured;
+  // Signal the captured frame converted to I420 to downstream.
+  sigslot::signal2<VideoCapturer*, const VideoFrame*,
+                   sigslot::multi_threaded_local> SignalVideoFrame;
+
+  const VideoProcessors& video_processors() const { return video_processors_; }
+
+ protected:
+  // Callback attached to SignalFrameCaptured where SignalVideoFrames is called.
+  void OnFrameCaptured(VideoCapturer* video_capturer,
+                       const CapturedFrame* captured_frame);
+  void SetCaptureState(CaptureState state);
+
+  // Marshals SignalStateChange onto thread_.
+  void OnMessage(talk_base::Message* message);
+
+  // subclasses override this virtual method to provide a vector of fourccs, in
+  // order of preference, that are expected by the media engine.
+  virtual bool GetPreferredFourccs(std::vector<uint32>* fourccs) = 0;
+
+  // mutators to set private attributes
+  void SetId(const std::string& id) {
+    id_ = id;
+  }
+
+  void SetCaptureFormat(const VideoFormat* format) {
+    capture_format_.reset(format ? new VideoFormat(*format) : NULL);
+  }
+
+  void SetSupportedFormats(const std::vector<VideoFormat>& formats);
+
+ private:
+  void Construct();
+  // Get the distance between the desired format and the supported format.
+  // Return the max distance if they mismatch. See the implementation for
+  // details.
+  int64 GetFormatDistance(const VideoFormat& desired,
+                          const VideoFormat& supported);
+
+  // Convert captured frame to readable string for LOG messages.
+  std::string ToString(const CapturedFrame* frame) const;
+
+  // Applies all registered processors. If any of the processors signal that
+  // the frame should be dropped the return value will be false. Note that
+  // this frame should be dropped as it has not applied all processors.
+  bool ApplyProcessors(VideoFrame* video_frame);
+
+  // Updates filtered_supported_formats_ so that it contains the formats in
+  // supported_formats_ that fulfill all applied restrictions.
+  void UpdateFilteredSupportedFormats();
+  // Returns true if format doesn't fulfill all applied restrictions.
+  bool ShouldFilterFormat(const VideoFormat& format) const;
+
+  talk_base::Thread* thread_;
+  std::string id_;
+  CaptureState capture_state_;
+  talk_base::scoped_ptr<VideoFormat> capture_format_;
+  std::vector<VideoFormat> supported_formats_;
+  talk_base::scoped_ptr<VideoFormat> max_format_;
+  std::vector<VideoFormat> filtered_supported_formats_;
+
+  int ratio_w_;  // View resolution. e.g. 1280 x 720.
+  int ratio_h_;
+  bool enable_camera_list_;
+  int scaled_width_;  // Current output size from ComputeScale.
+  int scaled_height_;
+  bool muted_;
+  int black_frame_count_down_;
+
+  talk_base::CriticalSection crit_;
+  VideoProcessors video_processors_;
+
+  DISALLOW_COPY_AND_ASSIGN(VideoCapturer);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_VIDEOCAPTURER_H_
diff --git a/talk/media/base/videocapturer_unittest.cc b/talk/media/base/videocapturer_unittest.cc
new file mode 100644
index 0000000..a6ce3ba
--- /dev/null
+++ b/talk/media/base/videocapturer_unittest.cc
@@ -0,0 +1,683 @@
+// Copyright 2008 Google Inc.
+
+#include <stdio.h>
+#include <vector>
+
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/thread.h"
+#include "talk/media/base/fakemediaprocessor.h"
+#include "talk/media/base/fakevideocapturer.h"
+#include "talk/media/base/fakevideorenderer.h"
+#include "talk/media/base/testutils.h"
+#include "talk/media/base/videocapturer.h"
+#include "talk/media/base/videoprocessor.h"
+
+// If HAS_I420_FRAME is not defined the video capturer will not be able to
+// provide OnVideoFrame-callbacks since they require cricket::CapturedFrame to
+// be decoded as a cricket::VideoFrame (i.e. an I420 frame). This functionality
+// only exist if HAS_I420_FRAME is defined below. I420 frames are also a
+// requirement for the VideoProcessors so they will not be called either.
+#if defined(HAVE_WEBRTC_VIDEO)
+#define HAS_I420_FRAME
+#endif
+
+using cricket::FakeVideoCapturer;
+
+namespace {
+
+const int kMsCallbackWait = 500;
+// For HD only the height matters.
+const int kMinHdHeight = 720;
+const uint32 kTimeout = 5000U;
+
+}  // namespace
+
+// Sets the elapsed time in the video frame to 0.
+class VideoProcessor0 : public cricket::VideoProcessor {
+ public:
+  virtual void OnFrame(uint32 /*ssrc*/, cricket::VideoFrame* frame,
+                       bool* drop_frame) {
+    frame->SetElapsedTime(0u);
+  }
+};
+
+// Adds one to the video frame's elapsed time. Note that VideoProcessor0 and
+// VideoProcessor1 are not commutative.
+class VideoProcessor1 : public cricket::VideoProcessor {
+ public:
+  virtual void OnFrame(uint32 /*ssrc*/, cricket::VideoFrame* frame,
+                       bool* drop_frame) {
+    int64 elapsed_time = frame->GetElapsedTime();
+    frame->SetElapsedTime(elapsed_time + 1);
+  }
+};
+
+class VideoCapturerTest
+    : public sigslot::has_slots<>,
+      public testing::Test {
+ public:
+  VideoCapturerTest()
+      : capture_state_(cricket::CS_STOPPED),
+        num_state_changes_(0),
+        video_frames_received_(0),
+        last_frame_elapsed_time_(0) {
+    capturer_.SignalVideoFrame.connect(this, &VideoCapturerTest::OnVideoFrame);
+    capturer_.SignalStateChange.connect(this,
+                                        &VideoCapturerTest::OnStateChange);
+  }
+
+ protected:
+  void OnVideoFrame(cricket::VideoCapturer*, const cricket::VideoFrame* frame) {
+    ++video_frames_received_;
+    last_frame_elapsed_time_ = frame->GetElapsedTime();
+    renderer_.RenderFrame(frame);
+  }
+  void OnStateChange(cricket::VideoCapturer*,
+                     cricket::CaptureState capture_state) {
+    capture_state_ = capture_state;
+    ++num_state_changes_;
+  }
+  cricket::CaptureState capture_state() { return capture_state_; }
+  int num_state_changes() { return num_state_changes_; }
+  int video_frames_received() const {
+    return video_frames_received_;
+  }
+  int64 last_frame_elapsed_time() const { return last_frame_elapsed_time_; }
+
+  cricket::FakeVideoCapturer capturer_;
+  cricket::CaptureState capture_state_;
+  int num_state_changes_;
+  int video_frames_received_;
+  int64 last_frame_elapsed_time_;
+  cricket::FakeVideoRenderer renderer_;
+};
+
+TEST_F(VideoCapturerTest, CaptureState) {
+  EXPECT_EQ(cricket::CS_RUNNING, capturer_.Start(cricket::VideoFormat(
+      640,
+      480,
+      cricket::VideoFormat::FpsToInterval(30),
+      cricket::FOURCC_I420)));
+  EXPECT_TRUE(capturer_.IsRunning());
+  EXPECT_EQ_WAIT(cricket::CS_RUNNING, capture_state(), kMsCallbackWait);
+  EXPECT_EQ(1, num_state_changes());
+  capturer_.Stop();
+  EXPECT_EQ_WAIT(cricket::CS_STOPPED, capture_state(), kMsCallbackWait);
+  EXPECT_EQ(2, num_state_changes());
+  capturer_.Stop();
+  talk_base::Thread::Current()->ProcessMessages(100);
+  EXPECT_EQ(2, num_state_changes());
+}
+
+TEST_F(VideoCapturerTest, TestRestart) {
+  EXPECT_EQ(cricket::CS_RUNNING, capturer_.Start(cricket::VideoFormat(
+      640,
+      480,
+      cricket::VideoFormat::FpsToInterval(30),
+      cricket::FOURCC_I420)));
+  EXPECT_TRUE(capturer_.IsRunning());
+  EXPECT_EQ_WAIT(cricket::CS_RUNNING, capture_state(), kMsCallbackWait);
+  EXPECT_EQ(1, num_state_changes());
+  EXPECT_TRUE(capturer_.Restart(cricket::VideoFormat(
+      320,
+      240,
+      cricket::VideoFormat::FpsToInterval(30),
+      cricket::FOURCC_I420)));
+  EXPECT_EQ_WAIT(cricket::CS_RUNNING, capture_state(), kMsCallbackWait);
+  EXPECT_TRUE(capturer_.IsRunning());
+  EXPECT_GE(1, num_state_changes());
+  capturer_.Stop();
+  talk_base::Thread::Current()->ProcessMessages(100);
+  EXPECT_FALSE(capturer_.IsRunning());
+}
+
+TEST_F(VideoCapturerTest, TestStartingWithRestart) {
+  EXPECT_FALSE(capturer_.IsRunning());
+  EXPECT_TRUE(capturer_.Restart(cricket::VideoFormat(
+      640,
+      480,
+      cricket::VideoFormat::FpsToInterval(30),
+      cricket::FOURCC_I420)));
+  EXPECT_TRUE(capturer_.IsRunning());
+  EXPECT_EQ_WAIT(cricket::CS_RUNNING, capture_state(), kMsCallbackWait);
+}
+
+TEST_F(VideoCapturerTest, TestRestartWithSameFormat) {
+  cricket::VideoFormat format(640, 480,
+                              cricket::VideoFormat::FpsToInterval(30),
+                              cricket::FOURCC_I420);
+  EXPECT_EQ(cricket::CS_RUNNING, capturer_.Start(format));
+  EXPECT_TRUE(capturer_.IsRunning());
+  EXPECT_EQ_WAIT(cricket::CS_RUNNING, capture_state(), kMsCallbackWait);
+  EXPECT_EQ(1, num_state_changes());
+  EXPECT_TRUE(capturer_.Restart(format));
+  EXPECT_EQ(cricket::CS_RUNNING, capture_state());
+  EXPECT_TRUE(capturer_.IsRunning());
+  EXPECT_EQ(1, num_state_changes());
+}
+
+TEST_F(VideoCapturerTest, CameraOffOnMute) {
+  EXPECT_EQ(cricket::CS_RUNNING, capturer_.Start(cricket::VideoFormat(
+      640,
+      480,
+      cricket::VideoFormat::FpsToInterval(30),
+      cricket::FOURCC_I420)));
+  EXPECT_TRUE(capturer_.IsRunning());
+  EXPECT_EQ(0, video_frames_received());
+  EXPECT_TRUE(capturer_.CaptureFrame());
+  EXPECT_EQ(1, video_frames_received());
+  EXPECT_FALSE(capturer_.IsMuted());
+
+  // Mute the camera and expect black output frame.
+  capturer_.MuteToBlackThenPause(true);
+  EXPECT_TRUE(capturer_.IsMuted());
+  for (int i = 0; i < 31; ++i) {
+    EXPECT_TRUE(capturer_.CaptureFrame());
+    EXPECT_TRUE(renderer_.black_frame());
+  }
+  EXPECT_EQ(32, video_frames_received());
+  EXPECT_EQ_WAIT(cricket::CS_PAUSED,
+                 capturer_.capture_state(), kTimeout);
+
+  // Verify that the camera is off.
+  EXPECT_FALSE(capturer_.CaptureFrame());
+  EXPECT_EQ(32, video_frames_received());
+
+  // Unmute the camera and expect non-black output frame.
+  capturer_.MuteToBlackThenPause(false);
+  EXPECT_FALSE(capturer_.IsMuted());
+  EXPECT_EQ_WAIT(cricket::CS_RUNNING,
+                 capturer_.capture_state(), kTimeout);
+  EXPECT_TRUE(capturer_.CaptureFrame());
+  EXPECT_FALSE(renderer_.black_frame());
+  EXPECT_EQ(33, video_frames_received());
+}
+
+TEST_F(VideoCapturerTest, TestFourccMatch) {
+  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_F(VideoCapturerTest, TestResolutionMatch) {
+  cricket::VideoFormat desired(1920, 1080,
+                               cricket::VideoFormat::FpsToInterval(30),
+                               cricket::FOURCC_ANY);
+  cricket::VideoFormat best;
+  // Ask for 1920x1080. Get HD 1280x720 which is the highest.
+  EXPECT_TRUE(capturer_.GetBestCaptureFormat(desired, &best));
+  EXPECT_EQ(1280, best.width);
+  EXPECT_EQ(720, 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.
+  EXPECT_TRUE(capturer_.GetBestCaptureFormat(desired, &best));
+  EXPECT_EQ(320, best.width);
+  EXPECT_EQ(240, best.height);
+  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.
+  EXPECT_TRUE(capturer_.GetBestCaptureFormat(desired, &best));
+  EXPECT_EQ(320, best.width);
+  EXPECT_EQ(240, best.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+  desired.width = 80;
+  desired.height = 60;
+  // Ask for lower than QQVGA. Get QQVGA, which is the lowest.
+  EXPECT_TRUE(capturer_.GetBestCaptureFormat(desired, &best));
+  EXPECT_EQ(160, best.width);
+  EXPECT_EQ(120, best.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+}
+
+TEST_F(VideoCapturerTest, TestHDResolutionMatch) {
+  // Add some HD formats typical of a mediocre HD webcam.
+  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(24), cricket::FOURCC_I420));
+  formats.push_back(cricket::VideoFormat(1280, 720,
+      cricket::VideoFormat::FpsToInterval(15), cricket::FOURCC_I420));
+  formats.push_back(cricket::VideoFormat(2592, 1944,
+      cricket::VideoFormat::FpsToInterval(7), cricket::FOURCC_I420));
+  capturer_.ResetSupportedFormats(formats);
+
+  cricket::VideoFormat desired(960, 720,
+                               cricket::VideoFormat::FpsToInterval(30),
+                               cricket::FOURCC_ANY);
+  cricket::VideoFormat best;
+  // Ask for 960x720 30 fps. Get qHD 24 fps
+  EXPECT_TRUE(capturer_.GetBestCaptureFormat(desired, &best));
+  EXPECT_EQ(960, best.width);
+  EXPECT_EQ(544, best.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(24), best.interval);
+
+  desired.width = 960;
+  desired.height = 544;
+  desired.interval = cricket::VideoFormat::FpsToInterval(30);
+  // Ask for qHD 30 fps. Get qHD 24 fps
+  EXPECT_TRUE(capturer_.GetBestCaptureFormat(desired, &best));
+  EXPECT_EQ(960, best.width);
+  EXPECT_EQ(544, best.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(24), best.interval);
+
+  desired.width = 360;
+  desired.height = 250;
+  desired.interval = cricket::VideoFormat::FpsToInterval(30);
+  // Ask for a little higher than QVGA. Get QVGA.
+  EXPECT_TRUE(capturer_.GetBestCaptureFormat(desired, &best));
+  EXPECT_EQ(320, best.width);
+  EXPECT_EQ(240, best.height);
+  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.
+  EXPECT_TRUE(capturer_.GetBestCaptureFormat(desired, &best));
+  EXPECT_EQ(320, best.width);
+  EXPECT_EQ(240, best.height);
+  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));
+  EXPECT_EQ(320, best.width);
+  EXPECT_EQ(240, best.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+  desired.width = 1280;
+  desired.height = 720;
+  // Ask for HD. 720p fps is too low. Get VGA which has 30 fps.
+  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 = 1280;
+  desired.height = 720;
+  desired.interval = cricket::VideoFormat::FpsToInterval(15);
+  // Ask for HD 15 fps. Fps matches. Get HD
+  EXPECT_TRUE(capturer_.GetBestCaptureFormat(desired, &best));
+  EXPECT_EQ(1280, best.width);
+  EXPECT_EQ(720, best.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(15), best.interval);
+
+  desired.width = 1920;
+  desired.height = 1080;
+  desired.interval = cricket::VideoFormat::FpsToInterval(30);
+  // Ask for 1080p. Fps of HD formats is too low. Get VGA which can do 30 fps.
+  EXPECT_TRUE(capturer_.GetBestCaptureFormat(desired, &best));
+  EXPECT_EQ(640, best.width);
+  EXPECT_EQ(480, best.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+}
+
+// Some cameras support 320x240 and 320x640. Verify we choose 320x240.
+TEST_F(VideoCapturerTest, TestStrangeFormats) {
+  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_F(VideoCapturerTest, TestPoorFpsFormats) {
+  // 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));
+    EXPECT_EQ(required_formats[i].width, best.width);
+    EXPECT_EQ(required_formats[i].height, best.height);
+  }
+
+  // Increase framerate of 320x240. Expect low fps VGA avoided.
+  supported_formats.clear();
+  supported_formats.push_back(cricket::VideoFormat(320, 240,
+      cricket::VideoFormat::FpsToInterval(30), 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_F(VideoCapturerTest, TestSameSizeDifferentFpsFormats) {
+  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_F(VideoCapturerTest, TestFpsFormats) {
+  // 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 30 fps format
+  EXPECT_TRUE(capturer_.GetBestCaptureFormat(required_formats[0], &best));
+  EXPECT_EQ(640, best.width);
+  EXPECT_EQ(400, best.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+  // expect 20 fps to choose 20 fps format
+  EXPECT_TRUE(capturer_.GetBestCaptureFormat(required_formats[1], &best));
+  EXPECT_EQ(640, best.width);
+  EXPECT_EQ(400, best.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(20), 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_F(VideoCapturerTest, TestRequest16x10_9) {
+  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, 16x10, and 16x9 requests are respected.
+  for (size_t i = 0; i < required_formats.size(); ++i) {
+    EXPECT_TRUE(capturer_.GetBestCaptureFormat(required_formats[i], &best));
+    EXPECT_EQ(required_formats[i].width, best.width);
+    EXPECT_EQ(required_formats[i].height, 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, 16x10, and 16x9 requests are respected.
+  for (size_t i = 0; i < required_formats.size(); ++i) {
+    EXPECT_TRUE(capturer_.GetBestCaptureFormat(required_formats[i], &best));
+    EXPECT_EQ(required_formats[i].width, best.width);
+    EXPECT_EQ(required_formats[i].height, best.height);
+  }
+
+  // We support 16x9HD, Expect 4x3, 16x10, and 16x9 requests are respected.
+  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(required_formats[i].width, best.width);
+    EXPECT_EQ(required_formats[i].height, 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);
+}
+
+#if defined(HAS_I420_FRAME)
+TEST_F(VideoCapturerTest, VideoFrame) {
+  EXPECT_EQ(cricket::CS_RUNNING, capturer_.Start(cricket::VideoFormat(
+      640,
+      480,
+      cricket::VideoFormat::FpsToInterval(30),
+      cricket::FOURCC_I420)));
+  EXPECT_TRUE(capturer_.IsRunning());
+  EXPECT_EQ(0, video_frames_received());
+  EXPECT_TRUE(capturer_.CaptureFrame());
+  EXPECT_EQ(1, video_frames_received());
+}
+
+TEST_F(VideoCapturerTest, ProcessorChainTest) {
+  VideoProcessor0 processor0;
+  VideoProcessor1 processor1;
+  EXPECT_EQ(cricket::CS_RUNNING, capturer_.Start(cricket::VideoFormat(
+      640,
+      480,
+      cricket::VideoFormat::FpsToInterval(30),
+      cricket::FOURCC_I420)));
+  EXPECT_TRUE(capturer_.IsRunning());
+  EXPECT_EQ(0, video_frames_received());
+  // First processor sets elapsed time to 0.
+  capturer_.AddVideoProcessor(&processor0);
+  // Second processor adds 1 to the elapsed time. I.e. a frames elapsed time
+  // should now always be 1 (and not 0).
+  capturer_.AddVideoProcessor(&processor1);
+  EXPECT_TRUE(capturer_.CaptureFrame());
+  EXPECT_EQ(1, video_frames_received());
+  EXPECT_EQ(1u, last_frame_elapsed_time());
+  capturer_.RemoveVideoProcessor(&processor1);
+  EXPECT_TRUE(capturer_.CaptureFrame());
+  // Since processor1 has been removed the elapsed time should now be 0.
+  EXPECT_EQ(2, video_frames_received());
+  EXPECT_EQ(0u, last_frame_elapsed_time());
+}
+
+TEST_F(VideoCapturerTest, ProcessorDropFrame) {
+  cricket::FakeMediaProcessor dropping_processor_;
+  dropping_processor_.set_drop_frames(true);
+  EXPECT_EQ(cricket::CS_RUNNING, capturer_.Start(cricket::VideoFormat(
+      640,
+      480,
+      cricket::VideoFormat::FpsToInterval(30),
+      cricket::FOURCC_I420)));
+  EXPECT_TRUE(capturer_.IsRunning());
+  EXPECT_EQ(0, video_frames_received());
+  // Install a processor that always drop frames.
+  capturer_.AddVideoProcessor(&dropping_processor_);
+  EXPECT_TRUE(capturer_.CaptureFrame());
+  EXPECT_EQ(0, video_frames_received());
+}
+#endif  // HAS_I420_FRAME
+
+bool HdFormatInList(const std::vector<cricket::VideoFormat>& formats) {
+  for (std::vector<cricket::VideoFormat>::const_iterator found =
+           formats.begin(); found != formats.end(); ++found) {
+    if (found->height >= kMinHdHeight) {
+      return true;
+    }
+  }
+  return false;
+}
+
+TEST_F(VideoCapturerTest, Whitelist) {
+  // The definition of HD only applies to the height. Set the HD width to the
+  // smallest legal number to document this fact in this test.
+  const int kMinHdWidth = 1;
+  cricket::VideoFormat hd_format(kMinHdWidth,
+                                 kMinHdHeight,
+                                 cricket::VideoFormat::FpsToInterval(30),
+                                 cricket::FOURCC_I420);
+  cricket::VideoFormat vga_format(640, 480,
+                                  cricket::VideoFormat::FpsToInterval(30),
+                                  cricket::FOURCC_I420);
+  std::vector<cricket::VideoFormat> formats = *capturer_.GetSupportedFormats();
+  formats.push_back(hd_format);
+
+  // Enable whitelist. Expect HD not in list.
+  capturer_.set_enable_camera_list(true);
+  capturer_.ResetSupportedFormats(formats);
+  EXPECT_TRUE(HdFormatInList(*capturer_.GetSupportedFormats()));
+  capturer_.ConstrainSupportedFormats(vga_format);
+  EXPECT_FALSE(HdFormatInList(*capturer_.GetSupportedFormats()));
+
+  // Disable whitelist. Expect HD in list.
+  capturer_.set_enable_camera_list(false);
+  capturer_.ResetSupportedFormats(formats);
+  EXPECT_TRUE(HdFormatInList(*capturer_.GetSupportedFormats()));
+  capturer_.ConstrainSupportedFormats(vga_format);
+  EXPECT_TRUE(HdFormatInList(*capturer_.GetSupportedFormats()));
+}
diff --git a/talk/media/base/videocommon.cc b/talk/media/base/videocommon.cc
new file mode 100644
index 0000000..12f3bb3
--- /dev/null
+++ b/talk/media/base/videocommon.cc
@@ -0,0 +1,237 @@
+// 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/media/base/videocommon.h"
+
+#include <limits.h>  // For INT_MAX
+#include <math.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_YU16, FOURCC_I422},
+  {FOURCC_YU24, FOURCC_I444},
+  {FOURCC_YUYV, FOURCC_YUY2},
+  {FOURCC_YUVS, FOURCC_YUY2},
+  {FOURCC_HDYC, FOURCC_UYVY},
+  {FOURCC_2VUY, FOURCC_UYVY},
+  {FOURCC_JPEG, FOURCC_MJPG},  // Note: JPEG has DHT while MJPG does not.
+  {FOURCC_DMB1, FOURCC_MJPG},
+  {FOURCC_BA81, FOURCC_BGGR},
+  {FOURCC_RGB3, FOURCC_RAW},
+  {FOURCC_BGR3, FOURCC_24BG},
+  {FOURCC_CM32, FOURCC_BGRA},
+  {FOURCC_CM24, FOURCC_RAW},
+};
+
+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;
+}
+
+static float kScaleFactors[] = {
+  1.f / 1.f,  // Full size.
+  1.f / 2.f,  // 1/2 scale.
+  1.f / 4.f,  // 1/4 scale.
+  1.f / 8.f,  // 1/8 scale.
+  1.f / 16.f  // 1/16 scale.
+};
+
+static const int kNumScaleFactors = ARRAY_SIZE(kScaleFactors);
+
+// Finds the scale factor that, when applied to width and height, produces
+// fewer than num_pixels.
+static float FindLowerScale(int width, int height, int target_num_pixels) {
+  if (!target_num_pixels) {
+    return 0.f;
+  }
+  int best_distance = INT_MAX;
+  int best_index = kNumScaleFactors - 1;  // Default to max scale.
+  for (int i = 0; i < kNumScaleFactors; ++i) {
+    int test_num_pixels = static_cast<int>(width * kScaleFactors[i] *
+                                           height * kScaleFactors[i]);
+    int diff = target_num_pixels - test_num_pixels;
+    if (diff >= 0 && diff < best_distance) {
+      best_distance = diff;
+      best_index = i;
+      if (best_distance == 0) {  // Found exact match.
+        break;
+      }
+    }
+  }
+  return kScaleFactors[best_index];
+}
+
+// Compute a size to scale frames to that is below maximum compression
+// and rendering size with the same aspect ratio.
+void ComputeScale(int frame_width, int frame_height, int fps,
+                  int* scaled_width, int* scaled_height) {
+  ASSERT(scaled_width != NULL);
+  ASSERT(scaled_height != NULL);
+  // For VP8 the values for max width and height can be found here
+  // webrtc/src/video_engine/vie_defines.h (kViEMaxCodecWidth and
+  // kViEMaxCodecHeight)
+  const int kMaxWidth = 4096;
+  const int kMaxHeight = 3072;
+  // Maximum pixels limit is set to Retina MacBookPro 15" resolution of
+  // 2880 x 1800 as of 4/18/2013.
+  // For high fps, maximum pixels limit is set based on common 24" monitor
+  // resolution of 2048 x 1280 as of 6/13/2013. The Retina resolution is
+  // therefore reduced to 1440 x 900.
+  int kMaxPixels = (fps > 5) ? 2048 * 1280 : 2880 * 1800;
+  int new_frame_width = frame_width;
+  int new_frame_height = frame_height;
+
+  // Limit width.
+  if (new_frame_width > kMaxWidth) {
+    new_frame_height = new_frame_height * kMaxWidth / new_frame_width;
+    new_frame_width = kMaxWidth;
+  }
+  // Limit height.
+  if (new_frame_height > kMaxHeight) {
+    new_frame_width = new_frame_width * kMaxHeight / new_frame_height;
+    new_frame_height = kMaxHeight;
+  }
+  // Limit number of pixels.
+  if (new_frame_width * new_frame_height > kMaxPixels) {
+    // Compute new width such that width * height is less than maximum but
+    // maintains original captured frame aspect ratio.
+    new_frame_width = static_cast<int>(sqrtf(static_cast<float>(
+        kMaxPixels) * new_frame_width / new_frame_height));
+    new_frame_height = kMaxPixels / new_frame_width;
+  }
+  // Snap to a scale factor that is less than or equal to target pixels.
+  float scale = FindLowerScale(frame_width, frame_height,
+                               new_frame_width * new_frame_height);
+  *scaled_width = static_cast<int>(frame_width * scale + .5f);
+  *scaled_height = static_cast<int>(frame_height * scale + .5f);
+}
+
+// Compute size to crop video frame to.
+// If cropped_format_* is 0, return the frame_* size as is.
+void ComputeCrop(int cropped_format_width,
+                 int cropped_format_height,
+                 int frame_width, int frame_height,
+                 int pixel_width, int pixel_height,
+                 int rotation,
+                 int* cropped_width, int* cropped_height) {
+  ASSERT(cropped_format_width >= 0);
+  ASSERT(cropped_format_height >= 0);
+  ASSERT(frame_width > 0);
+  ASSERT(frame_height > 0);
+  ASSERT(pixel_width >= 0);
+  ASSERT(pixel_height >= 0);
+  ASSERT(rotation == 0 || rotation == 90 || rotation == 180 || rotation == 270);
+  ASSERT(cropped_width != NULL);
+  ASSERT(cropped_height != NULL);
+  if (!pixel_width) {
+    pixel_width = 1;
+  }
+  if (!pixel_height) {
+    pixel_height = 1;
+  }
+  // if cropped_format is 0x0 disable cropping.
+  if (!cropped_format_height) {
+    cropped_format_height = 1;
+  }
+  float frame_aspect = static_cast<float>(frame_width * pixel_width) /
+      static_cast<float>(frame_height * pixel_height);
+  float crop_aspect = static_cast<float>(cropped_format_width) /
+      static_cast<float>(cropped_format_height);
+  int new_frame_width = frame_width;
+  int new_frame_height = frame_height;
+  if (rotation == 90 || rotation == 270) {
+    frame_aspect = 1.0f / frame_aspect;
+    new_frame_width = frame_height;
+    new_frame_height = frame_width;
+  }
+
+  // kAspectThresh is the maximum aspect ratio difference that we'll accept
+  // for cropping.  The value 1.33 is based on 4:3 being cropped to 16:9.
+  // Set to zero to disable cropping entirely.
+  // TODO(fbarchard): crop to multiple of 16 width for better performance.
+  const float kAspectThresh = 16.f / 9.f / (4.f / 3.f) + 0.01f;  // 1.33
+  // Wide aspect - crop horizontally
+  if (frame_aspect > crop_aspect &&
+      frame_aspect < crop_aspect * kAspectThresh) {
+    // Round width down to multiple of 4 to avoid odd chroma width.
+    // Width a multiple of 4 allows a half size image to have chroma channel
+    // that avoids rounding errors.  lmi and webrtc have odd width limitations.
+    new_frame_width = static_cast<int>((crop_aspect * frame_height *
+        pixel_height) / pixel_width + 0.5f) & ~3;
+  } else if (crop_aspect > frame_aspect &&
+             crop_aspect < frame_aspect * kAspectThresh) {
+    new_frame_height = static_cast<int>((frame_width * pixel_width) /
+        (crop_aspect * pixel_height) + 0.5f) & ~1;
+  }
+
+  *cropped_width = new_frame_width;
+  *cropped_height = new_frame_height;
+  if (rotation == 90 || rotation == 270) {
+    *cropped_width = new_frame_height;
+    *cropped_height = new_frame_width;
+  }
+}
+
+// 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 Also:
+// 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/media/base/videocommon.h b/talk/media/base/videocommon.h
new file mode 100644
index 0000000..098651f
--- /dev/null
+++ b/talk/media/base/videocommon.h
@@ -0,0 +1,242 @@
+// 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.
+//
+// Common definition for video, including fourcc and VideoFormat.
+
+#ifndef TALK_MEDIA_BASE_VIDEOCOMMON_H_
+#define TALK_MEDIA_BASE_VIDEOCOMMON_H_
+
+#include <string>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/timeutils.h"
+
+namespace cricket {
+
+// TODO(janahan): 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;
+
+// Minimum interval is 10k fps.
+#define FPS_TO_INTERVAL(fps) \
+    (fps ? talk_base::kNumNanosecsPerSec / fps : \
+    talk_base::kNumNanosecsPerSec / 10000)
+
+//////////////////////////////////////////////////////////////////////////////
+// Definition of FourCC codes
+//////////////////////////////////////////////////////////////////////////////
+// Convert four characters to a FourCC code.
+// Needs to be a macro otherwise the OS X compiler complains when the kFormat*
+// constants are used in a switch.
+#define FOURCC(a, b, c, d) ( \
+    (static_cast<uint32>(a)) | (static_cast<uint32>(b) << 8) | \
+    (static_cast<uint32>(c) << 16) | (static_cast<uint32>(d) << 24))
+// 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
+//   http://msdn.microsoft.com/library/windows/desktop/dd206750.aspx#nv12
+//   http://people.xiph.org/~xiphmont/containers/nut/nut4cc.txt
+
+// FourCC codes grouped according to implementation efficiency.
+// Primary formats should convert in 1 efficient step.
+// Secondary formats are converted in 2 steps.
+// Auxilliary formats call primary converters.
+enum FourCC {
+  // 9 Primary YUV formats: 5 planar, 2 biplanar, 2 packed.
+  FOURCC_I420 = FOURCC('I', '4', '2', '0'),
+  FOURCC_I422 = FOURCC('I', '4', '2', '2'),
+  FOURCC_I444 = FOURCC('I', '4', '4', '4'),
+  FOURCC_I411 = FOURCC('I', '4', '1', '1'),
+  FOURCC_I400 = FOURCC('I', '4', '0', '0'),
+  FOURCC_NV21 = FOURCC('N', 'V', '2', '1'),
+  FOURCC_NV12 = FOURCC('N', 'V', '1', '2'),
+  FOURCC_YUY2 = FOURCC('Y', 'U', 'Y', '2'),
+  FOURCC_UYVY = FOURCC('U', 'Y', 'V', 'Y'),
+
+  // 2 Secondary YUV formats: row biplanar.
+  FOURCC_M420 = FOURCC('M', '4', '2', '0'),
+  FOURCC_Q420 = FOURCC('Q', '4', '2', '0'),
+
+  // 9 Primary RGB formats: 4 32 bpp, 2 24 bpp, 3 16 bpp.
+  FOURCC_ARGB = FOURCC('A', 'R', 'G', 'B'),
+  FOURCC_BGRA = FOURCC('B', 'G', 'R', 'A'),
+  FOURCC_ABGR = FOURCC('A', 'B', 'G', 'R'),
+  FOURCC_24BG = FOURCC('2', '4', 'B', 'G'),
+  FOURCC_RAW  = FOURCC('r', 'a', 'w', ' '),
+  FOURCC_RGBA = FOURCC('R', 'G', 'B', 'A'),
+  FOURCC_RGBP = FOURCC('R', 'G', 'B', 'P'),  // bgr565.
+  FOURCC_RGBO = FOURCC('R', 'G', 'B', 'O'),  // abgr1555.
+  FOURCC_R444 = FOURCC('R', '4', '4', '4'),  // argb4444.
+
+  // 4 Secondary RGB formats: 4 Bayer Patterns.
+  FOURCC_RGGB = FOURCC('R', 'G', 'G', 'B'),
+  FOURCC_BGGR = FOURCC('B', 'G', 'G', 'R'),
+  FOURCC_GRBG = FOURCC('G', 'R', 'B', 'G'),
+  FOURCC_GBRG = FOURCC('G', 'B', 'R', 'G'),
+
+  // 1 Primary Compressed YUV format.
+  FOURCC_MJPG = FOURCC('M', 'J', 'P', 'G'),
+
+  // 5 Auxiliary YUV variations: 3 with U and V planes are swapped, 1 Alias.
+  FOURCC_YV12 = FOURCC('Y', 'V', '1', '2'),
+  FOURCC_YV16 = FOURCC('Y', 'V', '1', '6'),
+  FOURCC_YV24 = FOURCC('Y', 'V', '2', '4'),
+  FOURCC_YU12 = FOURCC('Y', 'U', '1', '2'),  // Linux version of I420.
+  FOURCC_J420 = FOURCC('J', '4', '2', '0'),
+  FOURCC_J400 = FOURCC('J', '4', '0', '0'),
+
+  // 14 Auxiliary aliases.  CanonicalFourCC() maps these to canonical fourcc.
+  FOURCC_IYUV = FOURCC('I', 'Y', 'U', 'V'),  // 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.
+  FOURCC_2VUY = FOURCC('2', 'v', 'u', 'y'),  // Alias for UYVY on Mac.
+  FOURCC_JPEG = FOURCC('J', 'P', 'E', 'G'),  // Alias for MJPG.
+  FOURCC_DMB1 = FOURCC('d', 'm', 'b', '1'),  // Alias for MJPG on Mac.
+  FOURCC_BA81 = FOURCC('B', 'A', '8', '1'),  // Alias for BGGR.
+  FOURCC_RGB3 = FOURCC('R', 'G', 'B', '3'),  // Alias for RAW.
+  FOURCC_BGR3 = FOURCC('B', 'G', 'R', '3'),  // Alias for 24BG.
+  FOURCC_CM32 = FOURCC(0, 0, 0, 32),  // Alias for BGRA kCMPixelFormat_32ARGB
+  FOURCC_CM24 = FOURCC(0, 0, 0, 24),  // Alias for RAW kCMPixelFormat_24RGB
+
+  // 1 Auxiliary compressed YUV format set aside for capturer.
+  FOURCC_H264 = FOURCC('H', '2', '6', '4'),
+
+  // Match any fourcc.
+  FOURCC_ANY  = 0xFFFFFFFF,
+};
+
+// 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;
+}
+
+void ComputeScale(int frame_width, int frame_height, int fps,
+                  int* scaled_width, int* scaled_height);
+
+// Compute the frame size that conversion should crop to based on aspect ratio.
+// Ensures size is multiple of 2 due to I420 and conversion limitations.
+void ComputeCrop(int cropped_format_width, int cropped_format_height,
+                 int frame_width, int frame_height,
+                 int pixel_width, int pixel_height,
+                 int rotation,
+                 int* cropped_width, int* cropped_height);
+
+//////////////////////////////////////////////////////////////////////////////
+// Definition of VideoFormat.
+//////////////////////////////////////////////////////////////////////////////
+
+// VideoFormat with Plain Old Data for global variables.
+struct VideoFormatPod {
+  int width;  // Number of pixels.
+  int height;  // Number of pixels.
+  int64 interval;  // Nanoseconds.
+  uint32 fourcc;  // Color space. FOURCC_ANY means that any color space is OK.
+};
+
+struct VideoFormat : VideoFormatPod {
+  static const int64 kMinimumInterval =
+      talk_base::kNumNanosecsPerSec / 10000;  // 10k fps.
+
+  VideoFormat() {
+    Construct(0, 0, 0, 0);
+  }
+
+  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 ? talk_base::kNumNanosecsPerSec / fps : kMinimumInterval;
+  }
+
+  static int IntervalToFps(int64 interval) {
+    // Normalize the interval first.
+    interval = talk_base::_max(interval, kMinimumInterval);
+    return static_cast<int>(talk_base::kNumNanosecsPerSec / interval);
+  }
+
+  bool operator==(const VideoFormat& format) const {
+    return width == format.width && height == format.height &&
+        interval == format.interval && fourcc == format.fourcc;
+  }
+
+  bool operator!=(const VideoFormat& format) const {
+    return !(*this == format);
+  }
+
+  bool operator<(const VideoFormat& format) const {
+    return (fourcc < format.fourcc) ||
+        (fourcc == format.fourcc && width < format.width) ||
+        (fourcc == format.fourcc && width == format.width &&
+            height < format.height) ||
+        (fourcc == format.fourcc && width == format.width &&
+            height == format.height && interval > format.interval);
+  }
+
+  int framerate() const { return IntervalToFps(interval); }
+
+  // Check if both width and height are 0.
+  bool IsSize0x0() const { return 0 == width && 0 == height; }
+
+  // Check if this format is less than another one by comparing the resolution
+  // and frame rate.
+  bool IsPixelRateLess(const VideoFormat& format) const {
+    return width * height * framerate() <
+        format.width * format.height * format.framerate();
+  }
+
+  // Get a string presentation in the form of "fourcc width x height x fps"
+  std::string ToString() const;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_VIDEOCOMMON_H_
diff --git a/talk/media/base/videocommon_unittest.cc b/talk/media/base/videocommon_unittest.cc
new file mode 100644
index 0000000..e9cd26a
--- /dev/null
+++ b/talk/media/base/videocommon_unittest.cc
@@ -0,0 +1,290 @@
+/*
+ * 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/media/base/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_YUY2, CanonicalFourCC(FOURCC_YUYV));
+  EXPECT_EQ(FOURCC_YUY2, CanonicalFourCC(FOURCC_YUVS));
+  EXPECT_EQ(FOURCC_UYVY, CanonicalFourCC(FOURCC_HDYC));
+  EXPECT_EQ(FOURCC_UYVY, CanonicalFourCC(FOURCC_2VUY));
+  EXPECT_EQ(FOURCC_MJPG, CanonicalFourCC(FOURCC_JPEG));
+  EXPECT_EQ(FOURCC_MJPG, CanonicalFourCC(FOURCC_DMB1));
+  EXPECT_EQ(FOURCC_BGGR, CanonicalFourCC(FOURCC_BA81));
+  EXPECT_EQ(FOURCC_RAW, CanonicalFourCC(FOURCC_RGB3));
+  EXPECT_EQ(FOURCC_24BG, CanonicalFourCC(FOURCC_BGR3));
+  EXPECT_EQ(FOURCC_BGRA, CanonicalFourCC(FOURCC_CM32));
+  EXPECT_EQ(FOURCC_RAW, CanonicalFourCC(FOURCC_CM24));
+}
+
+// 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));
+}
+
+TEST(VideoCommonTest, TestComputeScaleWithLowFps) {
+  int scaled_width, scaled_height;
+
+  // Request small enough. Expect no change.
+  ComputeScale(2560, 1600, 5, &scaled_width, &scaled_height);
+  EXPECT_EQ(2560, scaled_width);
+  EXPECT_EQ(1600, scaled_height);
+
+  // Request too many pixels. Expect 1/2 size.
+  ComputeScale(4096, 2560, 5, &scaled_width, &scaled_height);
+  EXPECT_EQ(2048, scaled_width);
+  EXPECT_EQ(1280, scaled_height);
+
+  // Request too many pixels and too wide and tall. Expect 1/4 size.
+  ComputeScale(16000, 10000, 5, &scaled_width, &scaled_height);
+  EXPECT_EQ(2000, scaled_width);
+  EXPECT_EQ(1250, scaled_height);
+
+  // Request too wide. (two 30 inch monitors). Expect 1/2 size.
+  ComputeScale(5120, 1600, 5, &scaled_width, &scaled_height);
+  EXPECT_EQ(2560, scaled_width);
+  EXPECT_EQ(800, scaled_height);
+
+  // Request too wide but not too many pixels. Expect 1/2 size.
+  ComputeScale(8192, 1024, 5, &scaled_width, &scaled_height);
+  EXPECT_EQ(4096, scaled_width);
+  EXPECT_EQ(512, scaled_height);
+
+  // Request too tall. Expect 1/4 size.
+  ComputeScale(1024, 8192, 5, &scaled_width, &scaled_height);
+  EXPECT_EQ(256, scaled_width);
+  EXPECT_EQ(2048, scaled_height);
+}
+
+// Same as TestComputeScale but with 15 fps instead of 5 fps.
+TEST(VideoCommonTest, TestComputeScaleWithHighFps) {
+  int scaled_width, scaled_height;
+
+  // Request small enough but high fps. Expect 1/2 size.
+  ComputeScale(2560, 1600, 15, &scaled_width, &scaled_height);
+  EXPECT_EQ(1280, scaled_width);
+  EXPECT_EQ(800, scaled_height);
+
+  // Request too many pixels. Expect 1/2 size.
+  ComputeScale(4096, 2560, 15, &scaled_width, &scaled_height);
+  EXPECT_EQ(2048, scaled_width);
+  EXPECT_EQ(1280, scaled_height);
+
+  // Request too many pixels and too wide and tall. Expect 1/16 size.
+  ComputeScale(64000, 40000, 15, &scaled_width, &scaled_height);
+  EXPECT_EQ(4000, scaled_width);
+  EXPECT_EQ(2500, scaled_height);
+
+  // Request too wide. (two 30 inch monitors). Expect 1/2 size.
+  ComputeScale(5120, 1600, 15, &scaled_width, &scaled_height);
+  EXPECT_EQ(2560, scaled_width);
+  EXPECT_EQ(800, scaled_height);
+
+  // Request too wide but not too many pixels. Expect 1/2 size.
+  ComputeScale(8192, 1024, 15, &scaled_width, &scaled_height);
+  EXPECT_EQ(4096, scaled_width);
+  EXPECT_EQ(512, scaled_height);
+
+  // Request too tall. Expect 1/4 size.
+  ComputeScale(1024, 8192, 15, &scaled_width, &scaled_height);
+  EXPECT_EQ(256, scaled_width);
+  EXPECT_EQ(2048, scaled_height);
+}
+
+TEST(VideoCommonTest, TestComputeCrop) {
+  int cropped_width, cropped_height;
+
+  // Request 16:9 to 16:9.  Expect no cropping.
+  ComputeCrop(1280, 720,  // Crop size 16:9
+              640, 360,  // Frame is 4:3
+              1, 1,  // Normal 1:1 pixels
+              0,
+              &cropped_width, &cropped_height);
+  EXPECT_EQ(640, cropped_width);
+  EXPECT_EQ(360, cropped_height);
+
+  // Request 4:3 to 16:9.  Expect vertical.
+  ComputeCrop(640, 360,  // Crop size 16:9
+              640, 480,  // Frame is 4:3
+              1, 1,  // Normal 1:1 pixels
+              0,
+              &cropped_width, &cropped_height);
+  EXPECT_EQ(640, cropped_width);
+  EXPECT_EQ(360, cropped_height);
+
+  // Request 16:9 to 4:3.  Expect horizontal crop.
+  ComputeCrop(640, 480,  // Crop size 4:3
+              640, 360,  // Frame is 16:9
+              1, 1,  // Normal 1:1 pixels
+              0,
+              &cropped_width, &cropped_height);
+  EXPECT_EQ(480, cropped_width);
+  EXPECT_EQ(360, cropped_height);
+
+  // Request 16:9 but VGA has 3:8 pixel aspect ratio. Expect no crop.
+  // This occurs on HP4110 on OSX 10.5/10.6/10.7
+  ComputeCrop(640, 360,  // Crop size 16:9
+              640, 480,  // Frame is 4:3
+              3, 8,  // Pixel aspect ratio is tall
+              0,
+              &cropped_width, &cropped_height);
+  EXPECT_EQ(640, cropped_width);
+  EXPECT_EQ(480, cropped_height);
+
+  // Request 16:9 but QVGA has 15:11 pixel aspect ratio. Expect horizontal crop.
+  // This occurs on Logitech B910 on OSX 10.5/10.6/10.7 in Hangouts.
+  ComputeCrop(640, 360,  // Crop size 16:9
+              320, 240,  // Frame is 4:3
+              15, 11,  // Pixel aspect ratio is wide
+              0,
+              &cropped_width, &cropped_height);
+  EXPECT_EQ(312, cropped_width);
+  EXPECT_EQ(240, cropped_height);
+
+  // Request 16:10 but QVGA has 15:11 pixel aspect ratio.
+  // Expect horizontal crop.
+  // This occurs on Logitech B910 on OSX 10.5/10.6/10.7 in gmail.
+  ComputeCrop(640, 400,  // Crop size 16:10
+              320, 240,  // Frame is 4:3
+              15, 11,  // Pixel aspect ratio is wide
+              0,
+              &cropped_width, &cropped_height);
+  EXPECT_EQ(280, cropped_width);
+  EXPECT_EQ(240, cropped_height);
+
+  // Request 16:9 but VGA has 6:5 pixel aspect ratio. Expect vertical crop.
+  // This occurs on Logitech QuickCam Pro C9000 on OSX
+  ComputeCrop(640, 360,  // Crop size 16:9
+              640, 480,  // Frame is 4:3
+              6, 5,  // Pixel aspect ratio is wide
+              0,
+              &cropped_width, &cropped_height);
+  EXPECT_EQ(640, cropped_width);
+  EXPECT_EQ(432, cropped_height);
+
+  // Request 16:10 but HD is 16:9. Expect horizontal crop.
+  // This occurs in settings and local preview with HD experiment.
+  ComputeCrop(1280, 800,  // Crop size 16:10
+              1280, 720,  // Frame is 4:3
+              1, 1,  // Pixel aspect ratio is wide
+              0,
+              &cropped_width, &cropped_height);
+  EXPECT_EQ(1152, cropped_width);
+  EXPECT_EQ(720, cropped_height);
+
+  // Request 16:9 but HD has 3:4 pixel aspect ratio. Expect vertical crop.
+  // This occurs on Logitech B910 on OSX 10.5/10.6.7 but not OSX 10.6.8 or 10.7
+  ComputeCrop(1280, 720,  // Crop size 16:9
+              1280, 720,  // Frame is 4:3
+              3, 4,  // Pixel aspect ratio is wide
+              0,
+              &cropped_width, &cropped_height);
+  EXPECT_EQ(1280, cropped_width);
+  EXPECT_EQ(540, cropped_height);
+
+  // Request 16:9 to 3:4 (portrait).  Expect no cropping.
+  ComputeCrop(640, 360,  // Crop size 16:9
+              640, 480,  // Frame is 3:4 portrait
+              1, 1,  // Normal 1:1 pixels
+              90,
+              &cropped_width, &cropped_height);
+  EXPECT_EQ(640, cropped_width);
+  EXPECT_EQ(480, cropped_height);
+
+  // Cropped size 0x0.  Expect no cropping.
+  // This is used when adding multiple capturers
+  ComputeCrop(0, 0,  // Crop size 0x0
+              1024, 768,  // Frame is 3:4 portrait
+              1, 1,  // Normal 1:1 pixels
+              0,
+              &cropped_width, &cropped_height);
+  EXPECT_EQ(1024, cropped_width);
+  EXPECT_EQ(768, cropped_height);
+}
+
+}  // namespace cricket
diff --git a/talk/media/base/videoengine_unittest.h b/talk/media/base/videoengine_unittest.h
new file mode 100644
index 0000000..dcef4ac
--- /dev/null
+++ b/talk/media/base/videoengine_unittest.h
@@ -0,0 +1,1644 @@
+// libjingle
+// Copyright 2004 Google Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//  1. Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//  3. The name of the author may not be used to endorse or promote products
+//     derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_MEDIA_BASE_VIDEOENGINE_UNITTEST_H_
+#define TALK_MEDIA_BASE_VIDEOENGINE_UNITTEST_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/bytebuffer.h"
+#include "talk/base/gunit.h"
+#include "talk/base/timeutils.h"
+#include "talk/media/base/fakenetworkinterface.h"
+#include "talk/media/base/fakevideocapturer.h"
+#include "talk/media/base/fakevideorenderer.h"
+#include "talk/media/base/mediachannel.h"
+#include "talk/media/base/streamparams.h"
+
+#ifdef WIN32
+#include <objbase.h>  // NOLINT
+#endif
+
+#define EXPECT_FRAME_WAIT(c, w, h, t) \
+  EXPECT_EQ_WAIT((c), renderer_.num_rendered_frames(), (t)); \
+  EXPECT_EQ((w), renderer_.width()); \
+  EXPECT_EQ((h), renderer_.height()); \
+  EXPECT_EQ(0, renderer_.errors()); \
+
+#define EXPECT_FRAME_ON_RENDERER_WAIT(r, c, w, h, t) \
+  EXPECT_EQ_WAIT((c), (r).num_rendered_frames(), (t)); \
+  EXPECT_EQ((w), (r).width()); \
+  EXPECT_EQ((h), (r).height()); \
+  EXPECT_EQ(0, (r).errors()); \
+
+static const uint32 kTimeout = 5000U;
+static const uint32 kSsrc = 1234u;
+static const uint32 kRtxSsrc = 4321u;
+static const uint32 kSsrcs4[] = {1, 2, 3, 4};
+
+inline bool IsEqualRes(const cricket::VideoCodec& a, int w, int h, int fps) {
+  return a.width == w && a.height == h && a.framerate == fps;
+}
+
+inline bool IsEqualCodec(const cricket::VideoCodec& a,
+                         const cricket::VideoCodec& b) {
+  return a.id == b.id && a.name == b.name &&
+      IsEqualRes(a, b.width, b.height, b.framerate);
+}
+
+inline std::ostream& operator<<(std::ostream& s, const cricket::VideoCodec& c) {
+  s << "{" << c.name << "(" << c.id << "), "
+    << c.width << "x" << c.height << "x" << c.framerate << "}";
+  return s;
+}
+
+inline int TimeBetweenSend(const cricket::VideoCodec& codec) {
+  return static_cast<int> (
+      cricket::VideoFormat::FpsToInterval(codec.framerate) /
+      talk_base::kNumNanosecsPerMillisec);
+}
+
+// Fake video engine that makes it possible to test enabling and disabling
+// capturer (checking that the engine state is updated and that the capturer
+// is indeed capturing) without having to create a channel. It also makes it
+// possible to test that the media processors are indeed being called when
+// registered.
+template<class T>
+class VideoEngineOverride : public T {
+ public:
+  VideoEngineOverride() {
+  }
+  virtual ~VideoEngineOverride() {
+  }
+  bool is_camera_on() const { return T::GetVideoCapturer()->IsRunning(); }
+  void set_has_senders(bool has_senders) {
+    if (has_senders) {
+      this->RegisterSender(this,
+                           &VideoEngineOverride<T>::OnLocalFrame,
+                           &VideoEngineOverride<T>::OnLocalFrameFormat);
+    } else {
+      this->UnregisterSender(this);
+    }
+  }
+  void OnLocalFrame(cricket::VideoCapturer*,
+                    const cricket::VideoFrame*) {
+  }
+  void OnLocalFrameFormat(cricket::VideoCapturer*,
+                          const cricket::VideoFormat*) {
+  }
+
+  void TriggerMediaFrame(
+      uint32 ssrc, cricket::VideoFrame* frame, bool* drop_frame) {
+    T::SignalMediaFrame(ssrc, frame, drop_frame);
+  }
+};
+
+// Macroes that declare test functions for a given test class, before and after
+// Init().
+// To use, define a test function called FooBody and pass Foo to the macro.
+#define TEST_PRE_VIDEOENGINE_INIT(TestClass, func) \
+  TEST_F(TestClass, func##PreInit) { \
+    func##Body(); \
+  }
+#define TEST_POST_VIDEOENGINE_INIT(TestClass, func) \
+  TEST_F(TestClass, func##PostInit) { \
+    EXPECT_TRUE(engine_.Init(talk_base::Thread::Current())); \
+    func##Body(); \
+    engine_.Terminate(); \
+  }
+
+template<class E>
+class VideoEngineTest : public testing::Test {
+ protected:
+  // Tests starting and stopping the engine, and creating a channel.
+  void StartupShutdown() {
+    EXPECT_TRUE(engine_.Init(talk_base::Thread::Current()));
+    cricket::VideoMediaChannel* channel = engine_.CreateChannel(NULL);
+    EXPECT_TRUE(channel != NULL);
+    delete channel;
+    engine_.Terminate();
+  }
+
+#ifdef WIN32
+  // Tests that the COM reference count is not munged by the engine.
+  // Test to make sure LMI does not munge the CoInitialize reference count.
+  void CheckCoInitialize() {
+    // Initial refcount should be 0.
+    EXPECT_EQ(S_OK, CoInitializeEx(NULL, COINIT_MULTITHREADED));
+
+    // Engine should start even with COM already inited.
+    EXPECT_TRUE(engine_.Init(talk_base::Thread::Current()));
+    engine_.Terminate();
+    // Refcount after terminate should be 1; this tests if it is nonzero.
+    EXPECT_EQ(S_FALSE, CoInitializeEx(NULL, COINIT_MULTITHREADED));
+    // Decrement refcount to (hopefully) 0.
+    CoUninitialize();
+    CoUninitialize();
+
+    // Ensure refcount is 0.
+    EXPECT_EQ(S_OK, CoInitializeEx(NULL, COINIT_MULTITHREADED));
+    CoUninitialize();
+  }
+#endif
+
+  // Tests starting and stopping the capturer.
+  void SetCapture() {
+    EXPECT_FALSE(engine_.GetVideoCapturer());
+    EXPECT_TRUE(engine_.Init(talk_base::Thread::Current()));
+    ResetCapturer();
+    EXPECT_TRUE(engine_.GetVideoCapturer() != NULL);
+    EXPECT_FALSE(engine_.is_camera_on());
+    EXPECT_TRUE(engine_.SetCapture(true));
+    EXPECT_TRUE(engine_.is_camera_on());
+    EXPECT_TRUE(engine_.SetCapture(false));
+    EXPECT_FALSE(engine_.is_camera_on());
+    engine_.set_has_senders(true);
+    EXPECT_TRUE(engine_.is_camera_on());
+    EXPECT_TRUE(engine_.SetCapture(true));
+    EXPECT_TRUE(engine_.is_camera_on());
+    EXPECT_TRUE(engine_.SetCapture(false));
+    EXPECT_TRUE(engine_.is_camera_on());
+    engine_.set_has_senders(false);
+    EXPECT_FALSE(engine_.is_camera_on());
+    EXPECT_TRUE(engine_.SetCapture(true));
+    EXPECT_TRUE(engine_.is_camera_on());
+    EXPECT_TRUE(engine_.SetCapture(false));
+    EXPECT_FALSE(engine_.is_camera_on());
+    EXPECT_TRUE(engine_.SetVideoCapturer(NULL));
+    EXPECT_TRUE(engine_.GetVideoCapturer() == NULL);
+    engine_.Terminate();
+  }
+  void ResetCapturer() {
+    cricket::Device device("test", "device");
+    video_capturer_.reset(new cricket::FakeVideoCapturer);
+    EXPECT_TRUE(engine_.SetVideoCapturer(video_capturer_.get()));
+  }
+
+  void ConstrainNewCodecBody() {
+    cricket::VideoCodec empty, in, out;
+    cricket::VideoCodec max_settings(engine_.codecs()[0].id,
+                                     engine_.codecs()[0].name,
+                                     1280, 800, 30, 0);
+
+    // set max settings of 1280x960x30
+    EXPECT_TRUE(engine_.SetDefaultEncoderConfig(
+        cricket::VideoEncoderConfig(max_settings)));
+
+    // don't constrain the max resolution
+    in = max_settings;
+    EXPECT_TRUE(engine_.CanSendCodec(in, empty, &out));
+    EXPECT_PRED2(IsEqualCodec, out, in);
+
+    // constrain resolution greater than the max and wider aspect,
+    // picking best aspect (16:10)
+    in.width = 1380;
+    in.height = 800;
+    EXPECT_TRUE(engine_.CanSendCodec(in, empty, &out));
+    EXPECT_PRED4(IsEqualRes, out, 1280, 720, 30);
+
+    // constrain resolution greater than the max and narrow aspect,
+    // picking best aspect (16:9)
+    in.width = 1280;
+    in.height = 740;
+    EXPECT_TRUE(engine_.CanSendCodec(in, empty, &out));
+    EXPECT_PRED4(IsEqualRes, out, 1280, 720, 30);
+
+    // constrain resolution greater than the max, picking equal aspect (4:3)
+    in.width = 1280;
+    in.height = 960;
+    EXPECT_TRUE(engine_.CanSendCodec(in, empty, &out));
+    EXPECT_PRED4(IsEqualRes, out, 1280, 800, 30);
+
+    // constrain resolution greater than the max, picking equal aspect (16:10)
+    in.width = 1280;
+    in.height = 800;
+    EXPECT_TRUE(engine_.CanSendCodec(in, empty, &out));
+    EXPECT_PRED4(IsEqualRes, out, 1280, 800, 30);
+
+    // reduce max settings to 640x480x30
+    max_settings.width = 640;
+    max_settings.height = 480;
+    EXPECT_TRUE(engine_.SetDefaultEncoderConfig(
+        cricket::VideoEncoderConfig(max_settings)));
+
+    // don't constrain the max resolution
+    in = max_settings;
+    in.width = 640;
+    in.height = 480;
+    EXPECT_TRUE(engine_.CanSendCodec(in, empty, &out));
+    EXPECT_PRED2(IsEqualCodec, out, in);
+
+    // keep 16:10 if they request it
+    in.height = 400;
+    EXPECT_TRUE(engine_.CanSendCodec(in, empty, &out));
+    EXPECT_PRED2(IsEqualCodec, out, in);
+
+    // don't constrain lesser 4:3 resolutions
+    in.width = 320;
+    in.height = 240;
+    EXPECT_TRUE(engine_.CanSendCodec(in, empty, &out));
+    EXPECT_PRED2(IsEqualCodec, out, in);
+
+    // don't constrain lesser 16:10 resolutions
+    in.width = 320;
+    in.height = 200;
+    EXPECT_TRUE(engine_.CanSendCodec(in, empty, &out));
+    EXPECT_PRED2(IsEqualCodec, out, in);
+
+    // requested resolution of 0x0 succeeds
+    in.width = 0;
+    in.height = 0;
+    EXPECT_TRUE(engine_.CanSendCodec(in, empty, &out));
+    EXPECT_PRED2(IsEqualCodec, out, in);
+
+    // constrain resolution lesser than the max and wider aspect,
+    // picking best aspect (16:9)
+    in.width = 350;
+    in.height = 201;
+    EXPECT_TRUE(engine_.CanSendCodec(in, empty, &out));
+    EXPECT_PRED4(IsEqualRes, out, 320, 180, 30);
+
+    // constrain resolution greater than the max and narrow aspect,
+    // picking best aspect (4:3)
+    in.width = 350;
+    in.height = 300;
+    EXPECT_TRUE(engine_.CanSendCodec(in, empty, &out));
+    EXPECT_PRED4(IsEqualRes, out, 320, 240, 30);
+
+    // constrain resolution greater than the max and wider aspect,
+    // picking best aspect (16:9)
+    in.width = 1380;
+    in.height = 800;
+    EXPECT_TRUE(engine_.CanSendCodec(in, empty, &out));
+    EXPECT_PRED4(IsEqualRes, out, 640, 360, 30);
+
+    // constrain resolution greater than the max and narrow aspect,
+    // picking best aspect (4:3)
+    in.width = 1280;
+    in.height = 900;
+    EXPECT_TRUE(engine_.CanSendCodec(in, empty, &out));
+    EXPECT_PRED4(IsEqualRes, out, 640, 480, 30);
+
+    // constrain resolution greater than the max, picking equal aspect (4:3)
+    in.width = 1280;
+    in.height = 960;
+    EXPECT_TRUE(engine_.CanSendCodec(in, empty, &out));
+    EXPECT_PRED4(IsEqualRes, out, 640, 480, 30);
+
+    // constrain resolution greater than the max, picking equal aspect (16:10)
+    in.width = 1280;
+    in.height = 800;
+    EXPECT_TRUE(engine_.CanSendCodec(in, empty, &out));
+    EXPECT_PRED4(IsEqualRes, out, 640, 400, 30);
+
+    // constrain res & fps greater than the max
+    in.framerate = 50;
+    EXPECT_TRUE(engine_.CanSendCodec(in, empty, &out));
+    EXPECT_PRED4(IsEqualRes, out, 640, 400, 30);
+
+    // reduce max settings to 160x100x10
+    max_settings.width = 160;
+    max_settings.height = 100;
+    max_settings.framerate = 10;
+    EXPECT_TRUE(engine_.SetDefaultEncoderConfig(
+        cricket::VideoEncoderConfig(max_settings)));
+
+    // constrain res & fps to new max
+    EXPECT_TRUE(engine_.CanSendCodec(in, empty, &out));
+    EXPECT_PRED4(IsEqualRes, out, 160, 100, 10);
+
+    // allow 4:3 "comparable" resolutions
+    in.width = 160;
+    in.height = 120;
+    in.framerate = 10;
+    EXPECT_TRUE(engine_.CanSendCodec(in, empty, &out));
+    EXPECT_PRED4(IsEqualRes, out, 160, 120, 10);
+  }
+
+  void ConstrainRunningCodecBody() {
+    cricket::VideoCodec in, out, current;
+    cricket::VideoCodec max_settings(engine_.codecs()[0].id,
+                                     engine_.codecs()[0].name,
+                                     1280, 800, 30, 0);
+
+    // set max settings of 1280x960x30
+    EXPECT_TRUE(engine_.SetDefaultEncoderConfig(
+        cricket::VideoEncoderConfig(max_settings)));
+
+    // establish current call at 1280x800x30 (16:10)
+    current = max_settings;
+    current.height = 800;
+
+    // Don't constrain current resolution
+    in = current;
+    EXPECT_TRUE(engine_.CanSendCodec(in, current, &out));
+    EXPECT_PRED2(IsEqualCodec, out, in);
+
+    // requested resolution of 0x0 succeeds
+    in.width = 0;
+    in.height = 0;
+    EXPECT_TRUE(engine_.CanSendCodec(in, current, &out));
+    EXPECT_PRED2(IsEqualCodec, out, in);
+
+    // Reduce an intermediate resolution down to the next lowest one, preserving
+    // aspect ratio.
+    in.width = 800;
+    in.height = 600;
+    EXPECT_TRUE(engine_.CanSendCodec(in, current, &out));
+    EXPECT_PRED4(IsEqualRes, out, 640, 400, 30);
+
+    // Clamping by aspect ratio, but still never return a dimension higher than
+    // requested.
+    in.width = 1280;
+    in.height = 720;
+    EXPECT_TRUE(engine_.CanSendCodec(in, current, &out));
+    EXPECT_PRED4(IsEqualRes, out, 1280, 720, 30);
+
+    in.width = 1279;
+    EXPECT_TRUE(engine_.CanSendCodec(in, current, &out));
+    EXPECT_PRED4(IsEqualRes, out, 960, 600, 30);
+
+    in.width = 1281;
+    EXPECT_TRUE(engine_.CanSendCodec(in, current, &out));
+    EXPECT_PRED4(IsEqualRes, out, 1280, 720, 30);
+
+    // Clamp large resolutions down, always preserving aspect
+    in.width = 1920;
+    in.height = 1080;
+    EXPECT_TRUE(engine_.CanSendCodec(in, current, &out));
+    EXPECT_PRED4(IsEqualRes, out, 1280, 800, 30);
+
+    in.width = 1921;
+    EXPECT_TRUE(engine_.CanSendCodec(in, current, &out));
+    EXPECT_PRED4(IsEqualRes, out, 1280, 800, 30);
+
+    in.width = 1919;
+    EXPECT_TRUE(engine_.CanSendCodec(in, current, &out));
+    EXPECT_PRED4(IsEqualRes, out, 1280, 800, 30);
+
+    // reduce max settings to 640x480x30
+    max_settings.width = 640;
+    max_settings.height = 480;
+    EXPECT_TRUE(engine_.SetDefaultEncoderConfig(
+        cricket::VideoEncoderConfig(max_settings)));
+
+    // establish current call at 640x400x30 (16:10)
+    current = max_settings;
+    current.height = 400;
+
+    // Don't constrain current resolution
+    in = current;
+    EXPECT_TRUE(engine_.CanSendCodec(in, current, &out));
+    EXPECT_PRED2(IsEqualCodec, out, in);
+
+    // requested resolution of 0x0 succeeds
+    in.width = 0;
+    in.height = 0;
+    EXPECT_TRUE(engine_.CanSendCodec(in, current, &out));
+    EXPECT_PRED2(IsEqualCodec, out, in);
+
+    // Reduce an intermediate resolution down to the next lowest one, preserving
+    // aspect ratio.
+    in.width = 400;
+    in.height = 300;
+    EXPECT_TRUE(engine_.CanSendCodec(in, current, &out));
+    EXPECT_PRED4(IsEqualRes, out, 320, 200, 30);
+
+    // Clamping by aspect ratio, but still never return a dimension higher than
+    // requested.
+    in.width = 640;
+    in.height = 360;
+    EXPECT_TRUE(engine_.CanSendCodec(in, current, &out));
+    EXPECT_PRED4(IsEqualRes, out, 640, 360, 30);
+
+    in.width = 639;
+    EXPECT_TRUE(engine_.CanSendCodec(in, current, &out));
+    EXPECT_PRED4(IsEqualRes, out, 480, 300, 30);
+
+    in.width = 641;
+    EXPECT_TRUE(engine_.CanSendCodec(in, current, &out));
+    EXPECT_PRED4(IsEqualRes, out, 640, 360, 30);
+
+    // Clamp large resolutions down, always preserving aspect
+    in.width = 1280;
+    in.height = 800;
+    EXPECT_TRUE(engine_.CanSendCodec(in, current, &out));
+    EXPECT_PRED4(IsEqualRes, out, 640, 400, 30);
+
+    in.width = 1281;
+    EXPECT_TRUE(engine_.CanSendCodec(in, current, &out));
+    EXPECT_PRED4(IsEqualRes, out, 640, 400, 30);
+
+    in.width = 1279;
+    EXPECT_TRUE(engine_.CanSendCodec(in, current, &out));
+    EXPECT_PRED4(IsEqualRes, out, 640, 400, 30);
+
+    // Should fail for any that are smaller than our supported formats
+    in.width = 80;
+    in.height = 80;
+    EXPECT_FALSE(engine_.CanSendCodec(in, current, &out));
+
+    in.height = 50;
+    EXPECT_FALSE(engine_.CanSendCodec(in, current, &out));
+  }
+
+  VideoEngineOverride<E> engine_;
+  talk_base::scoped_ptr<cricket::FakeVideoCapturer> video_capturer_;
+};
+
+template<class E, class C>
+class VideoMediaChannelTest : public testing::Test,
+                              public sigslot::has_slots<> {
+ protected:
+  virtual cricket::VideoCodec DefaultCodec() = 0;
+
+  virtual cricket::StreamParams DefaultSendStreamParams() {
+    return cricket::StreamParams::CreateLegacy(kSsrc);
+  }
+
+  virtual void SetUp() {
+    cricket::Device device("test", "device");
+    EXPECT_TRUE(engine_.Init(talk_base::Thread::Current()));
+    video_capturer_.reset(new cricket::FakeVideoCapturer);
+    EXPECT_TRUE(video_capturer_.get() != NULL);
+    EXPECT_TRUE(engine_.SetVideoCapturer(video_capturer_.get()));
+    channel_.reset(engine_.CreateChannel(NULL));
+    EXPECT_TRUE(channel_.get() != NULL);
+    ConnectVideoChannelError();
+    network_interface_.SetDestination(channel_.get());
+    channel_->SetInterface(&network_interface_);
+    SetRendererAsDefault();
+    media_error_ = cricket::VideoMediaChannel::ERROR_NONE;
+    channel_->SetRecvCodecs(engine_.codecs());
+    EXPECT_TRUE(channel_->AddSendStream(DefaultSendStreamParams()));
+  }
+  void SetUpSecondStream() {
+    EXPECT_TRUE(channel_->AddRecvStream(
+        cricket::StreamParams::CreateLegacy(kSsrc)));
+    EXPECT_TRUE(channel_->AddRecvStream(
+        cricket::StreamParams::CreateLegacy(kSsrc+2)));
+    // SetUp() already added kSsrc make sure duplicate SSRCs cant be added.
+    EXPECT_FALSE(channel_->AddSendStream(
+        cricket::StreamParams::CreateLegacy(kSsrc)));
+    EXPECT_TRUE(channel_->AddSendStream(
+        cricket::StreamParams::CreateLegacy(kSsrc+2)));
+    // Make the second renderer available for use by a new stream.
+    EXPECT_TRUE(channel_->SetRenderer(kSsrc+2, &renderer2_));
+  }
+  virtual void TearDown() {
+    channel_.reset();
+    engine_.Terminate();
+  }
+  void ConnectVideoChannelError() {
+    channel_->SignalMediaError.connect(this,
+        &VideoMediaChannelTest<E, C>::OnVideoChannelError);
+  }
+  bool SetDefaultCodec() {
+    return SetOneCodec(DefaultCodec());
+  }
+  void SetRendererAsDefault() {
+    EXPECT_TRUE(channel_->SetRenderer(0, &renderer_));
+  }
+
+  bool SetOneCodec(int pt, const char* name, int w, int h, int fr) {
+    return SetOneCodec(cricket::VideoCodec(pt, name, w, h, fr, 0));
+  }
+  bool SetOneCodec(const cricket::VideoCodec& codec) {
+    std::vector<cricket::VideoCodec> codecs;
+    codecs.push_back(codec);
+    bool sending = channel_->sending();
+    bool success = SetSend(false);
+    if (success)
+      success = channel_->SetSendCodecs(codecs);
+    if (success)
+      success = SetSend(sending);
+    return success;
+  }
+  bool SetSend(bool send) {
+    return channel_->SetSend(send);
+  }
+  int DrainOutgoingPackets() {
+    int packets = 0;
+    do {
+      packets = NumRtpPackets();
+      // 100 ms should be long enough.
+      talk_base::Thread::Current()->ProcessMessages(100);
+    } while (NumRtpPackets() > packets);
+    return NumRtpPackets();
+  }
+  bool SendFrame() {
+    return video_capturer_.get() &&
+        video_capturer_->CaptureFrame();
+  }
+  bool WaitAndSendFrame(int wait_ms) {
+    bool ret = talk_base::Thread::Current()->ProcessMessages(wait_ms);
+    ret &= SendFrame();
+    return ret;
+  }
+  // Sends frames and waits for the decoder to be fully initialized.
+  // Returns the number of frames that were sent.
+  int WaitForDecoder() {
+#if defined(HAVE_OPENMAX)
+    // Send enough frames for the OpenMAX decoder to continue processing, and
+    // return the number of frames sent.
+    // Send frames for a full kTimeout's worth of 15fps video.
+    int frame_count = 0;
+    while (frame_count < static_cast<int>(kTimeout) / 66) {
+      EXPECT_TRUE(WaitAndSendFrame(66));
+      ++frame_count;
+    }
+    return frame_count;
+#else
+    return 0;
+#endif
+  }
+  bool SendCustomVideoFrame(int w, int h) {
+    if (!video_capturer_.get()) return false;
+    return video_capturer_->CaptureCustomFrame(w, h, cricket::FOURCC_I420);
+  }
+  int NumRtpBytes() {
+    return network_interface_.NumRtpBytes();
+  }
+  int NumRtpBytes(uint32 ssrc) {
+    return network_interface_.NumRtpBytes(ssrc);
+  }
+  int NumRtpPackets() {
+    return network_interface_.NumRtpPackets();
+  }
+  int NumRtpPackets(uint32 ssrc) {
+    return network_interface_.NumRtpPackets(ssrc);
+  }
+  int NumSentSsrcs() {
+    return network_interface_.NumSentSsrcs();
+  }
+  const talk_base::Buffer* GetRtpPacket(int index) {
+    return network_interface_.GetRtpPacket(index);
+  }
+  int NumRtcpPackets() {
+    return network_interface_.NumRtcpPackets();
+  }
+  const talk_base::Buffer* GetRtcpPacket(int index) {
+    return network_interface_.GetRtcpPacket(index);
+  }
+  static int GetPayloadType(const talk_base::Buffer* p) {
+    int pt = -1;
+    ParseRtpPacket(p, NULL, &pt, NULL, NULL, NULL, NULL);
+    return pt;
+  }
+  static bool ParseRtpPacket(const talk_base::Buffer* p, bool* x, int* pt,
+                             int* seqnum, uint32* tstamp, uint32* ssrc,
+                             std::string* payload) {
+    talk_base::ByteBuffer buf(p->data(), p->length());
+    uint8 u08 = 0;
+    uint16 u16 = 0;
+    uint32 u32 = 0;
+
+    // Read X and CC fields.
+    if (!buf.ReadUInt8(&u08)) return false;
+    bool extension = ((u08 & 0x10) != 0);
+    uint8 cc = (u08 & 0x0F);
+    if (x) *x = extension;
+
+    // Read PT field.
+    if (!buf.ReadUInt8(&u08)) return false;
+    if (pt) *pt = (u08 & 0x7F);
+
+    // Read Sequence Number field.
+    if (!buf.ReadUInt16(&u16)) return false;
+    if (seqnum) *seqnum = u16;
+
+    // Read Timestamp field.
+    if (!buf.ReadUInt32(&u32)) return false;
+    if (tstamp) *tstamp = u32;
+
+    // Read SSRC field.
+    if (!buf.ReadUInt32(&u32)) return false;
+    if (ssrc) *ssrc = u32;
+
+    // Skip CSRCs.
+    for (uint8 i = 0; i < cc; ++i) {
+      if (!buf.ReadUInt32(&u32)) return false;
+    }
+
+    // Skip extension header.
+    if (extension) {
+      // Read Profile-specific extension header ID
+      if (!buf.ReadUInt16(&u16)) return false;
+
+      // Read Extension header length
+      if (!buf.ReadUInt16(&u16)) return false;
+      uint16 ext_header_len = u16;
+
+      // Read Extension header
+      for (uint16 i = 0; i < ext_header_len; ++i) {
+        if (!buf.ReadUInt32(&u32)) return false;
+      }
+    }
+
+    if (payload) {
+      return buf.ReadString(payload, buf.Length());
+    }
+    return true;
+  }
+
+  // Parse all RTCP packet, from start_index to stop_index, and count how many
+  // FIR (PT=206 and FMT=4 according to RFC 5104). If successful, set the count
+  // and return true.
+  bool CountRtcpFir(int start_index, int stop_index, int* fir_count) {
+    int count = 0;
+    for (int i = start_index; i < stop_index; ++i) {
+      talk_base::scoped_ptr<const talk_base::Buffer> p(GetRtcpPacket(i));
+      talk_base::ByteBuffer buf(p->data(), p->length());
+      size_t total_len = 0;
+      // The packet may be a compound RTCP packet.
+      while (total_len < p->length()) {
+        // Read FMT, type and length.
+        uint8 fmt = 0;
+        uint8 type = 0;
+        uint16 length = 0;
+        if (!buf.ReadUInt8(&fmt)) return false;
+        fmt &= 0x1F;
+        if (!buf.ReadUInt8(&type)) return false;
+        if (!buf.ReadUInt16(&length)) return false;
+        buf.Consume(length * 4);  // Skip RTCP data.
+        total_len += (length + 1) * 4;
+        if ((192 == type) || ((206 == type) && (4 == fmt))) {
+          ++count;
+        }
+      }
+    }
+
+    if (fir_count) {
+      *fir_count = count;
+    }
+    return true;
+  }
+
+  void OnVideoChannelError(uint32 ssrc,
+                           cricket::VideoMediaChannel::Error error) {
+    media_error_ = error;
+  }
+
+  // Test that SetSend works.
+  void SetSend() {
+    EXPECT_FALSE(channel_->sending());
+    EXPECT_TRUE(SetOneCodec(DefaultCodec()));
+    EXPECT_FALSE(channel_->sending());
+    EXPECT_TRUE(SetSend(true));
+    EXPECT_TRUE(channel_->sending());
+    EXPECT_TRUE(SendFrame());
+    EXPECT_TRUE_WAIT(NumRtpPackets() > 0, kTimeout);
+    EXPECT_TRUE(SetSend(false));
+    EXPECT_FALSE(channel_->sending());
+  }
+  // Test that SetSend fails without codecs being set.
+  void SetSendWithoutCodecs() {
+    EXPECT_FALSE(channel_->sending());
+    EXPECT_FALSE(SetSend(true));
+    EXPECT_FALSE(channel_->sending());
+  }
+  // Test that we properly set the send and recv buffer sizes by the time
+  // SetSend is called.
+  void SetSendSetsTransportBufferSizes() {
+    EXPECT_TRUE(SetOneCodec(DefaultCodec()));
+    EXPECT_TRUE(SetSend(true));
+    // TODO(sriniv): Remove or re-enable this.
+    // As part of b/8030474, send-buffer is size now controlled through
+    // portallocator flags. Its not set by channels.
+    // EXPECT_EQ(64 * 1024, network_interface_.sendbuf_size());
+    EXPECT_EQ(64 * 1024, network_interface_.recvbuf_size());
+  }
+  // Tests that we can send frames and the right payload type is used.
+  void Send(const cricket::VideoCodec& codec) {
+    EXPECT_TRUE(SetOneCodec(codec));
+    EXPECT_TRUE(SetSend(true));
+    EXPECT_TRUE(SendFrame());
+    EXPECT_TRUE_WAIT(NumRtpPackets() > 0, kTimeout);
+    talk_base::scoped_ptr<const talk_base::Buffer> p(GetRtpPacket(0));
+    EXPECT_EQ(codec.id, GetPayloadType(p.get()));
+  }
+  // Tests that we can send and receive frames.
+  void SendAndReceive(const cricket::VideoCodec& codec) {
+    EXPECT_TRUE(SetOneCodec(codec));
+    EXPECT_TRUE(SetSend(true));
+    EXPECT_TRUE(channel_->SetRender(true));
+    EXPECT_EQ(0, renderer_.num_rendered_frames());
+    EXPECT_TRUE(SendFrame());
+    EXPECT_FRAME_WAIT(1, codec.width, codec.height, kTimeout);
+    talk_base::scoped_ptr<const talk_base::Buffer> p(GetRtpPacket(0));
+    EXPECT_EQ(codec.id, GetPayloadType(p.get()));
+  }
+  // Tests that we only get a VideoRenderer::SetSize() callback when needed.
+  void SendManyResizeOnce() {
+    cricket::VideoCodec codec(DefaultCodec());
+    EXPECT_TRUE(SetOneCodec(codec));
+    EXPECT_TRUE(SetSend(true));
+    EXPECT_TRUE(channel_->SetRender(true));
+    EXPECT_EQ(0, renderer_.num_rendered_frames());
+    EXPECT_TRUE(WaitAndSendFrame(30));
+    EXPECT_FRAME_WAIT(1, codec.width, codec.height, kTimeout);
+    EXPECT_TRUE(WaitAndSendFrame(30));
+    EXPECT_FRAME_WAIT(2, codec.width, codec.height, kTimeout);
+    talk_base::scoped_ptr<const talk_base::Buffer> p(GetRtpPacket(0));
+    EXPECT_EQ(codec.id, GetPayloadType(p.get()));
+    EXPECT_EQ(1, renderer_.num_set_sizes());
+
+    codec.width /= 2;
+    codec.height /= 2;
+    EXPECT_TRUE(SetOneCodec(codec));
+    EXPECT_TRUE(WaitAndSendFrame(30));
+    EXPECT_FRAME_WAIT(3, codec.width, codec.height, kTimeout);
+    EXPECT_EQ(2, renderer_.num_set_sizes());
+  }
+  // Test that stats work properly for a 1-1 call.
+  void GetStats() {
+    SendAndReceive(DefaultCodec());
+    cricket::VideoMediaInfo info;
+    EXPECT_TRUE(channel_->GetStats(&info));
+
+    ASSERT_EQ(1U, info.senders.size());
+    // TODO(whyuan): bytes_sent and bytes_rcvd are different. Are both payload?
+    EXPECT_GT(info.senders[0].bytes_sent, 0);
+    EXPECT_EQ(NumRtpPackets(), info.senders[0].packets_sent);
+    EXPECT_EQ(0.0, info.senders[0].fraction_lost);
+    EXPECT_EQ(0, info.senders[0].firs_rcvd);
+    EXPECT_EQ(0, info.senders[0].nacks_rcvd);
+    EXPECT_EQ(DefaultCodec().width, info.senders[0].frame_width);
+    EXPECT_EQ(DefaultCodec().height, info.senders[0].frame_height);
+    EXPECT_GT(info.senders[0].framerate_input, 0);
+    EXPECT_GT(info.senders[0].framerate_sent, 0);
+
+    ASSERT_EQ(1U, info.receivers.size());
+    EXPECT_EQ(1U, info.senders[0].ssrcs.size());
+    EXPECT_EQ(1U, info.receivers[0].ssrcs.size());
+    EXPECT_EQ(info.senders[0].ssrcs[0], info.receivers[0].ssrcs[0]);
+    EXPECT_EQ(NumRtpBytes(), info.receivers[0].bytes_rcvd);
+    EXPECT_EQ(NumRtpPackets(), info.receivers[0].packets_rcvd);
+    EXPECT_EQ(0.0, info.receivers[0].fraction_lost);
+    EXPECT_EQ(0, info.receivers[0].packets_lost);
+    EXPECT_EQ(0, info.receivers[0].packets_concealed);
+    EXPECT_EQ(0, info.receivers[0].firs_sent);
+    EXPECT_EQ(0, info.receivers[0].nacks_sent);
+    EXPECT_EQ(DefaultCodec().width, info.receivers[0].frame_width);
+    EXPECT_EQ(DefaultCodec().height, info.receivers[0].frame_height);
+    EXPECT_GT(info.receivers[0].framerate_rcvd, 0);
+    EXPECT_GT(info.receivers[0].framerate_decoded, 0);
+    EXPECT_GT(info.receivers[0].framerate_output, 0);
+  }
+  // Test that stats work properly for a conf call with multiple recv streams.
+  void GetStatsMultipleRecvStreams() {
+    cricket::FakeVideoRenderer renderer1, renderer2;
+    EXPECT_TRUE(SetOneCodec(DefaultCodec()));
+    cricket::VideoOptions vmo;
+    vmo.conference_mode.Set(true);
+    EXPECT_TRUE(channel_->SetOptions(vmo));
+    EXPECT_TRUE(SetSend(true));
+    EXPECT_TRUE(channel_->AddRecvStream(
+        cricket::StreamParams::CreateLegacy(1)));
+    EXPECT_TRUE(channel_->AddRecvStream(
+        cricket::StreamParams::CreateLegacy(2)));
+    EXPECT_TRUE(channel_->SetRenderer(1, &renderer1));
+    EXPECT_TRUE(channel_->SetRenderer(2, &renderer2));
+    EXPECT_TRUE(channel_->SetRender(true));
+    EXPECT_EQ(0, renderer1.num_rendered_frames());
+    EXPECT_EQ(0, renderer2.num_rendered_frames());
+    std::vector<uint32> ssrcs;
+    ssrcs.push_back(1);
+    ssrcs.push_back(2);
+    network_interface_.SetConferenceMode(true, ssrcs);
+    EXPECT_TRUE(SendFrame());
+    EXPECT_FRAME_ON_RENDERER_WAIT(
+        renderer1, 1, DefaultCodec().width, DefaultCodec().height, kTimeout);
+    EXPECT_FRAME_ON_RENDERER_WAIT(
+        renderer2, 1, DefaultCodec().width, DefaultCodec().height, kTimeout);
+    cricket::VideoMediaInfo info;
+    EXPECT_TRUE(channel_->GetStats(&info));
+
+    ASSERT_EQ(1U, info.senders.size());
+    // TODO(whyuan): bytes_sent and bytes_rcvd are different. Are both payload?
+    EXPECT_GT(info.senders[0].bytes_sent, 0);
+    EXPECT_EQ(NumRtpPackets(), info.senders[0].packets_sent);
+    EXPECT_EQ(0.0, info.senders[0].fraction_lost);
+    EXPECT_EQ(0, info.senders[0].firs_rcvd);
+    EXPECT_EQ(0, info.senders[0].nacks_rcvd);
+    EXPECT_EQ(DefaultCodec().width, info.senders[0].frame_width);
+    EXPECT_EQ(DefaultCodec().height, info.senders[0].frame_height);
+    EXPECT_GT(info.senders[0].framerate_input, 0);
+    EXPECT_GT(info.senders[0].framerate_sent, 0);
+
+    ASSERT_EQ(2U, info.receivers.size());
+    for (size_t i = 0; i < info.receivers.size(); ++i) {
+      EXPECT_EQ(1U, info.receivers[i].ssrcs.size());
+      EXPECT_EQ(i + 1, info.receivers[i].ssrcs[0]);
+      EXPECT_EQ(NumRtpBytes(), info.receivers[i].bytes_rcvd);
+      EXPECT_EQ(NumRtpPackets(), info.receivers[i].packets_rcvd);
+      EXPECT_EQ(0.0, info.receivers[i].fraction_lost);
+      EXPECT_EQ(0, info.receivers[i].packets_lost);
+      EXPECT_EQ(0, info.receivers[i].packets_concealed);
+      EXPECT_EQ(0, info.receivers[i].firs_sent);
+      EXPECT_EQ(0, info.receivers[i].nacks_sent);
+      EXPECT_EQ(DefaultCodec().width, info.receivers[i].frame_width);
+      EXPECT_EQ(DefaultCodec().height, info.receivers[i].frame_height);
+      EXPECT_GT(info.receivers[i].framerate_rcvd, 0);
+      EXPECT_GT(info.receivers[i].framerate_decoded, 0);
+      EXPECT_GT(info.receivers[i].framerate_output, 0);
+    }
+  }
+  // Test that stats work properly for a conf call with multiple send streams.
+  void GetStatsMultipleSendStreams() {
+    // Normal setup; note that we set the SSRC explicitly to ensure that
+    // it will come first in the senders map.
+    EXPECT_TRUE(SetOneCodec(DefaultCodec()));
+    cricket::VideoOptions vmo;
+    vmo.conference_mode.Set(true);
+    EXPECT_TRUE(channel_->SetOptions(vmo));
+    EXPECT_TRUE(channel_->AddRecvStream(
+        cricket::StreamParams::CreateLegacy(1234)));
+    EXPECT_TRUE(SetSend(true));
+    EXPECT_TRUE(channel_->SetRender(true));
+    EXPECT_TRUE(SendFrame());
+    EXPECT_TRUE_WAIT(NumRtpPackets() > 0, kTimeout);
+    EXPECT_FRAME_WAIT(1, DefaultCodec().width, DefaultCodec().height, kTimeout);
+
+    // Add an additional capturer, and hook up a renderer to receive it.
+    cricket::FakeVideoRenderer renderer1;
+    talk_base::scoped_ptr<cricket::FakeVideoCapturer> capturer(
+      new cricket::FakeVideoCapturer);
+    capturer->SetScreencast(true);
+    cricket::VideoFormat format(1024, 768,
+                                cricket::VideoFormat::FpsToInterval(5), 0);
+    EXPECT_EQ(cricket::CS_RUNNING, capturer->Start(format));
+    EXPECT_TRUE(channel_->AddSendStream(
+        cricket::StreamParams::CreateLegacy(5678)));
+    EXPECT_TRUE(channel_->SetCapturer(5678, capturer.get()));
+    EXPECT_TRUE(channel_->AddRecvStream(
+        cricket::StreamParams::CreateLegacy(5678)));
+    EXPECT_TRUE(channel_->SetRenderer(5678, &renderer1));
+    EXPECT_TRUE(capturer->CaptureCustomFrame(1024, 768, cricket::FOURCC_I420));
+    EXPECT_FRAME_ON_RENDERER_WAIT(renderer1, 1, 1024, 768, kTimeout);
+
+    // Get stats, and make sure they are correct for two senders.
+    cricket::VideoMediaInfo info;
+    EXPECT_TRUE(channel_->GetStats(&info));
+    ASSERT_EQ(2U, info.senders.size());
+    EXPECT_EQ(NumRtpPackets(),
+        info.senders[0].packets_sent + info.senders[1].packets_sent);
+    EXPECT_EQ(1U, info.senders[0].ssrcs.size());
+    EXPECT_EQ(1234U, info.senders[0].ssrcs[0]);
+    EXPECT_EQ(DefaultCodec().width, info.senders[0].frame_width);
+    EXPECT_EQ(DefaultCodec().height, info.senders[0].frame_height);
+    EXPECT_EQ(1U, info.senders[1].ssrcs.size());
+    EXPECT_EQ(5678U, info.senders[1].ssrcs[0]);
+    EXPECT_EQ(1024, info.senders[1].frame_width);
+    EXPECT_EQ(768, info.senders[1].frame_height);
+    // The capturer must be unregistered here as it runs out of it's scope next.
+    EXPECT_TRUE(channel_->SetCapturer(5678, NULL));
+  }
+
+  // Test that we can set the bandwidth to auto or a specific value.
+  void SetSendBandwidth() {
+    EXPECT_TRUE(channel_->SetSendBandwidth(true, -1));
+    EXPECT_TRUE(channel_->SetSendBandwidth(true, 128 * 1024));
+    EXPECT_TRUE(channel_->SetSendBandwidth(false, -1));
+    EXPECT_TRUE(channel_->SetSendBandwidth(false, 128 * 1024));
+  }
+  // Test that we can set the SSRC for the default send source.
+  void SetSendSsrc() {
+    EXPECT_TRUE(SetDefaultCodec());
+    EXPECT_TRUE(SetSend(true));
+    EXPECT_TRUE(SendFrame());
+    EXPECT_TRUE_WAIT(NumRtpPackets() > 0, kTimeout);
+    uint32 ssrc = 0;
+    talk_base::scoped_ptr<const talk_base::Buffer> p(GetRtpPacket(0));
+    ParseRtpPacket(p.get(), NULL, NULL, NULL, NULL, &ssrc, NULL);
+    EXPECT_EQ(kSsrc, ssrc);
+    EXPECT_EQ(NumRtpPackets(), NumRtpPackets(ssrc));
+    EXPECT_EQ(NumRtpBytes(), NumRtpBytes(ssrc));
+    EXPECT_EQ(1, NumSentSsrcs());
+    EXPECT_EQ(0, NumRtpPackets(kSsrc - 1));
+    EXPECT_EQ(0, NumRtpBytes(kSsrc - 1));
+  }
+  // Test that we can set the SSRC even after codecs are set.
+  void SetSendSsrcAfterSetCodecs() {
+    // Remove stream added in Setup.
+    EXPECT_TRUE(channel_->RemoveSendStream(kSsrc));
+    EXPECT_TRUE(SetDefaultCodec());
+    EXPECT_TRUE(channel_->AddSendStream(
+        cricket::StreamParams::CreateLegacy(999)));
+    EXPECT_TRUE(SetSend(true));
+    EXPECT_TRUE(WaitAndSendFrame(0));
+    EXPECT_TRUE_WAIT(NumRtpPackets() > 0, kTimeout);
+    uint32 ssrc = 0;
+    talk_base::scoped_ptr<const talk_base::Buffer> p(GetRtpPacket(0));
+    ParseRtpPacket(p.get(), NULL, NULL, NULL, NULL, &ssrc, NULL);
+    EXPECT_EQ(999u, ssrc);
+    EXPECT_EQ(NumRtpPackets(), NumRtpPackets(ssrc));
+    EXPECT_EQ(NumRtpBytes(), NumRtpBytes(ssrc));
+    EXPECT_EQ(1, NumSentSsrcs());
+    EXPECT_EQ(0, NumRtpPackets(kSsrc));
+    EXPECT_EQ(0, NumRtpBytes(kSsrc));
+  }
+  // Test that we can set the default video renderer before and after
+  // media is received.
+  void SetRenderer() {
+    uint8 data1[] = {
+        0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+    };
+
+    talk_base::Buffer packet1(data1, sizeof(data1));
+    talk_base::SetBE32(packet1.data() + 8, kSsrc);
+    channel_->SetRenderer(0, NULL);
+    EXPECT_TRUE(SetDefaultCodec());
+    EXPECT_TRUE(SetSend(true));
+    EXPECT_TRUE(channel_->SetRender(true));
+    EXPECT_EQ(0, renderer_.num_rendered_frames());
+    channel_->OnPacketReceived(&packet1);
+    SetRendererAsDefault();
+    EXPECT_TRUE(SendFrame());
+    EXPECT_FRAME_WAIT(1, DefaultCodec().width, DefaultCodec().height, kTimeout);
+  }
+
+  // Tests empty StreamParams is rejected.
+  void RejectEmptyStreamParams() {
+    // Remove the send stream that was added during Setup.
+    EXPECT_TRUE(channel_->RemoveSendStream(kSsrc));
+
+    cricket::StreamParams empty;
+    EXPECT_FALSE(channel_->AddSendStream(empty));
+    EXPECT_TRUE(channel_->AddSendStream(
+        cricket::StreamParams::CreateLegacy(789u)));
+  }
+
+  // Tests setting up and configuring a send stream.
+  void AddRemoveSendStreams() {
+    EXPECT_TRUE(SetOneCodec(DefaultCodec()));
+    EXPECT_TRUE(SetSend(true));
+    EXPECT_TRUE(channel_->SetRender(true));
+    EXPECT_TRUE(SendFrame());
+    EXPECT_FRAME_WAIT(1, DefaultCodec().width, DefaultCodec().height, kTimeout);
+    EXPECT_GE(2, NumRtpPackets());
+    uint32 ssrc = 0;
+    size_t last_packet = NumRtpPackets() - 1;
+    talk_base::scoped_ptr<const talk_base::Buffer> p(GetRtpPacket(last_packet));
+    ParseRtpPacket(p.get(), NULL, NULL, NULL, NULL, &ssrc, NULL);
+    EXPECT_EQ(kSsrc, ssrc);
+
+    // Remove the send stream that was added during Setup.
+    EXPECT_TRUE(channel_->RemoveSendStream(kSsrc));
+    int rtp_packets = NumRtpPackets();
+
+    EXPECT_TRUE(channel_->AddSendStream(
+        cricket::StreamParams::CreateLegacy(789u)));
+    EXPECT_EQ(rtp_packets, NumRtpPackets());
+    // Wait 30ms to guarantee the engine does not drop the frame.
+    EXPECT_TRUE(WaitAndSendFrame(30));
+    EXPECT_TRUE_WAIT(NumRtpPackets() > rtp_packets, kTimeout);
+
+    last_packet = NumRtpPackets() - 1;
+    p.reset(GetRtpPacket(last_packet));
+    ParseRtpPacket(p.get(), NULL, NULL, NULL, NULL, &ssrc, NULL);
+    EXPECT_EQ(789u, ssrc);
+  }
+
+  // Tests adding streams already exists returns false.
+  void AddRecvStreamsAlreadyExist() {
+    cricket::VideoOptions vmo;
+    vmo.conference_mode.Set(true);
+    EXPECT_TRUE(channel_->SetOptions(vmo));
+
+    EXPECT_FALSE(channel_->AddRecvStream(
+        cricket::StreamParams::CreateLegacy(0)));
+
+    EXPECT_TRUE(channel_->AddRecvStream(
+        cricket::StreamParams::CreateLegacy(1)));
+    EXPECT_FALSE(channel_->AddRecvStream(
+        cricket::StreamParams::CreateLegacy(1)));
+
+    EXPECT_TRUE(channel_->RemoveRecvStream(1));
+    EXPECT_FALSE(channel_->AddRecvStream(
+        cricket::StreamParams::CreateLegacy(0)));
+    EXPECT_TRUE(channel_->AddRecvStream(
+        cricket::StreamParams::CreateLegacy(1)));
+  }
+
+  // Tests setting up and configuring multiple incoming streams.
+  void AddRemoveRecvStreams() {
+    cricket::FakeVideoRenderer renderer1, renderer2;
+    cricket::VideoOptions vmo;
+    vmo.conference_mode.Set(true);
+    EXPECT_TRUE(channel_->SetOptions(vmo));
+    // Ensure we can't set the renderer on a non-existent stream.
+    EXPECT_FALSE(channel_->SetRenderer(1, &renderer1));
+    EXPECT_FALSE(channel_->SetRenderer(2, &renderer2));
+    cricket::VideoRenderer* renderer;
+    EXPECT_FALSE(channel_->GetRenderer(1, &renderer));
+    EXPECT_FALSE(channel_->GetRenderer(2, &renderer));
+
+    // Ensure we can add streams.
+    EXPECT_TRUE(channel_->AddRecvStream(
+        cricket::StreamParams::CreateLegacy(1)));
+    EXPECT_TRUE(channel_->AddRecvStream(
+        cricket::StreamParams::CreateLegacy(2)));
+    EXPECT_TRUE(channel_->GetRenderer(1, &renderer));
+    // Verify the first AddRecvStream hook up to the default renderer.
+    EXPECT_EQ(&renderer_, renderer);
+    EXPECT_TRUE(channel_->GetRenderer(2, &renderer));
+    EXPECT_TRUE(NULL == renderer);
+
+    // Ensure we can now set the renderers.
+    EXPECT_TRUE(channel_->SetRenderer(1, &renderer1));
+    EXPECT_TRUE(channel_->SetRenderer(2, &renderer2));
+    EXPECT_TRUE(channel_->GetRenderer(1, &renderer));
+    EXPECT_TRUE(&renderer1 == renderer);
+    EXPECT_TRUE(channel_->GetRenderer(2, &renderer));
+    EXPECT_TRUE(&renderer2 == renderer);
+
+    // Ensure we can change the renderers if needed.
+    EXPECT_TRUE(channel_->SetRenderer(1, &renderer2));
+    EXPECT_TRUE(channel_->SetRenderer(2, &renderer1));
+    EXPECT_TRUE(channel_->GetRenderer(1, &renderer));
+    EXPECT_TRUE(&renderer2 == renderer);
+    EXPECT_TRUE(channel_->GetRenderer(2, &renderer));
+    EXPECT_TRUE(&renderer1 == renderer);
+
+    EXPECT_TRUE(channel_->RemoveRecvStream(2));
+    EXPECT_TRUE(channel_->RemoveRecvStream(1));
+    EXPECT_FALSE(channel_->GetRenderer(1, &renderer));
+    EXPECT_FALSE(channel_->GetRenderer(2, &renderer));
+  }
+
+  // Tests setting up and configuring multiple incoming streams in a
+  // non-conference call.
+  void AddRemoveRecvStreamsNoConference() {
+    cricket::FakeVideoRenderer renderer1, renderer2;
+    // Ensure we can't set the renderer on a non-existent stream.
+    EXPECT_FALSE(channel_->SetRenderer(1, &renderer1));
+    EXPECT_FALSE(channel_->SetRenderer(2, &renderer2));
+    cricket::VideoRenderer* renderer;
+    EXPECT_FALSE(channel_->GetRenderer(1, &renderer));
+    EXPECT_FALSE(channel_->GetRenderer(2, &renderer));
+
+    // Ensure we can add streams.
+    EXPECT_TRUE(channel_->AddRecvStream(
+        cricket::StreamParams::CreateLegacy(1)));
+    EXPECT_TRUE(channel_->AddRecvStream(
+        cricket::StreamParams::CreateLegacy(2)));
+    EXPECT_TRUE(channel_->GetRenderer(1, &renderer));
+    // Verify the first AddRecvStream hook up to the default renderer.
+    EXPECT_EQ(&renderer_, renderer);
+    EXPECT_TRUE(channel_->GetRenderer(2, &renderer));
+    EXPECT_TRUE(NULL == renderer);
+
+    // Ensure we can now set the renderers.
+    EXPECT_TRUE(channel_->SetRenderer(1, &renderer1));
+    EXPECT_TRUE(channel_->SetRenderer(2, &renderer2));
+    EXPECT_TRUE(channel_->GetRenderer(1, &renderer));
+    EXPECT_TRUE(&renderer1 == renderer);
+    EXPECT_TRUE(channel_->GetRenderer(2, &renderer));
+    EXPECT_TRUE(&renderer2 == renderer);
+
+    // Ensure we can change the renderers if needed.
+    EXPECT_TRUE(channel_->SetRenderer(1, &renderer2));
+    EXPECT_TRUE(channel_->SetRenderer(2, &renderer1));
+    EXPECT_TRUE(channel_->GetRenderer(1, &renderer));
+    EXPECT_TRUE(&renderer2 == renderer);
+    EXPECT_TRUE(channel_->GetRenderer(2, &renderer));
+    EXPECT_TRUE(&renderer1 == renderer);
+
+    EXPECT_TRUE(channel_->RemoveRecvStream(2));
+    EXPECT_TRUE(channel_->RemoveRecvStream(1));
+    EXPECT_FALSE(channel_->GetRenderer(1, &renderer));
+    EXPECT_FALSE(channel_->GetRenderer(2, &renderer));
+  }
+
+  // Test that no frames are rendered after the receive stream have been
+  // removed.
+  void AddRemoveRecvStreamAndRender() {
+    cricket::FakeVideoRenderer renderer1;
+    EXPECT_TRUE(SetDefaultCodec());
+    EXPECT_TRUE(SetSend(true));
+    EXPECT_TRUE(channel_->SetRender(true));
+    EXPECT_TRUE(channel_->AddRecvStream(
+        cricket::StreamParams::CreateLegacy(kSsrc)));
+    EXPECT_TRUE(channel_->SetRenderer(kSsrc, &renderer1));
+
+    EXPECT_TRUE(SendFrame());
+    EXPECT_FRAME_ON_RENDERER_WAIT(
+        renderer1, 1, DefaultCodec().width, DefaultCodec().height, kTimeout);
+    EXPECT_TRUE(channel_->RemoveRecvStream(kSsrc));
+    // Send three more frames. This is to avoid that the test might be flaky
+    // due to frame dropping.
+    for (size_t i = 0; i < 3; ++i)
+      EXPECT_TRUE(WaitAndSendFrame(100));
+
+    // Test that no more frames have been rendered.
+    EXPECT_EQ(1, renderer1.num_rendered_frames());
+
+    // Re-add the stream again and make sure it renders.
+    EXPECT_TRUE(channel_->AddRecvStream(
+        cricket::StreamParams::CreateLegacy(kSsrc)));
+    // Force the next frame to be a key frame to make the receiving
+    // decoder happy.
+    EXPECT_TRUE(channel_->SendIntraFrame());
+
+    EXPECT_TRUE(channel_->SetRenderer(kSsrc, &renderer1));
+    EXPECT_TRUE(SendFrame());
+    EXPECT_FRAME_ON_RENDERER_WAIT(
+        renderer1, 2, DefaultCodec().width, DefaultCodec().height, kTimeout);
+  }
+
+  // Tests the behavior of incoming streams in a conference scenario.
+  void SimulateConference() {
+    cricket::FakeVideoRenderer renderer1, renderer2;
+    EXPECT_TRUE(SetDefaultCodec());
+    cricket::VideoOptions vmo;
+    vmo.conference_mode.Set(true);
+    EXPECT_TRUE(channel_->SetOptions(vmo));
+    EXPECT_TRUE(SetSend(true));
+    EXPECT_TRUE(channel_->SetRender(true));
+    EXPECT_TRUE(channel_->AddRecvStream(
+        cricket::StreamParams::CreateLegacy(1)));
+    EXPECT_TRUE(channel_->AddRecvStream(
+        cricket::StreamParams::CreateLegacy(2)));
+    EXPECT_TRUE(channel_->SetRenderer(1, &renderer1));
+    EXPECT_TRUE(channel_->SetRenderer(2, &renderer2));
+    EXPECT_EQ(0, renderer1.num_rendered_frames());
+    EXPECT_EQ(0, renderer2.num_rendered_frames());
+    std::vector<uint32> ssrcs;
+    ssrcs.push_back(1);
+    ssrcs.push_back(2);
+    network_interface_.SetConferenceMode(true, ssrcs);
+    EXPECT_TRUE(SendFrame());
+    EXPECT_FRAME_ON_RENDERER_WAIT(
+        renderer1, 1, DefaultCodec().width, DefaultCodec().height, kTimeout);
+    EXPECT_FRAME_ON_RENDERER_WAIT(
+        renderer2, 1, DefaultCodec().width, DefaultCodec().height, kTimeout);
+
+    talk_base::scoped_ptr<const talk_base::Buffer> p(GetRtpPacket(0));
+    EXPECT_EQ(DefaultCodec().id, GetPayloadType(p.get()));
+    EXPECT_EQ(DefaultCodec().width, renderer1.width());
+    EXPECT_EQ(DefaultCodec().height, renderer1.height());
+    EXPECT_EQ(DefaultCodec().width, renderer2.width());
+    EXPECT_EQ(DefaultCodec().height, renderer2.height());
+    EXPECT_TRUE(channel_->RemoveRecvStream(2));
+    EXPECT_TRUE(channel_->RemoveRecvStream(1));
+  }
+
+  // Tests that we can add and remove capturers and frames are sent out properly
+  void AddRemoveCapturer() {
+    const cricket::VideoCodec codec(DefaultCodec());
+    const int time_between_send = TimeBetweenSend(codec);
+    EXPECT_TRUE(SetDefaultCodec());
+    EXPECT_TRUE(SetSend(true));
+    EXPECT_TRUE(channel_->SetRender(true));
+    EXPECT_EQ(0, renderer_.num_rendered_frames());
+    EXPECT_TRUE(SendFrame());
+    EXPECT_FRAME_WAIT(1, codec.width, codec.height, kTimeout);
+    talk_base::scoped_ptr<cricket::FakeVideoCapturer> capturer(
+        new cricket::FakeVideoCapturer);
+    capturer->SetScreencast(true);
+    cricket::VideoFormat format(1024, 768,
+                                cricket::VideoFormat::FpsToInterval(30), 0);
+    EXPECT_EQ(cricket::CS_RUNNING, capturer->Start(format));
+    // All capturers start generating frames with the same timestamp. ViE does
+    // not allow the same timestamp to be used. Capture one frame before
+    // associating the capturer with the channel.
+    EXPECT_TRUE(capturer->CaptureCustomFrame(format.width, format.height,
+                                             cricket::FOURCC_I420));
+
+    int captured_frames = 1;
+    for (int iterations = 0; iterations < 2; ++iterations) {
+      EXPECT_TRUE(channel_->SetCapturer(kSsrc, capturer.get()));
+      talk_base::Thread::Current()->ProcessMessages(time_between_send);
+      EXPECT_TRUE(capturer->CaptureCustomFrame(format.width, format.height,
+                                               cricket::FOURCC_I420));
+      ++captured_frames;
+      EXPECT_FRAME_WAIT(captured_frames, format.width, format.height, kTimeout);
+      EXPECT_FALSE(renderer_.black_frame());
+      EXPECT_TRUE(channel_->SetCapturer(kSsrc, NULL));
+      // Make sure a black frame is generated as no new frame is captured.
+      // A black frame should be the resolution of the send codec.
+      ++captured_frames;
+      EXPECT_FRAME_WAIT(captured_frames, codec.width, codec.height, kTimeout);
+      EXPECT_TRUE(renderer_.black_frame());
+
+      // The black frame has the same timestamp as the next frame since it's
+      // timestamp is set to the last frame's timestamp + interval. WebRTC will
+      // not render a frame with the same timestamp so capture another frame
+      // with the frame capturer to increment the next frame's timestamp.
+      EXPECT_TRUE(capturer->CaptureCustomFrame(format.width, format.height,
+                                               cricket::FOURCC_I420));
+    }
+  }
+
+  // Tests that if RemoveCapturer is called without a capturer ever being
+  // added, the plugin shouldn't crash (and no black frame should be sent).
+  void RemoveCapturerWithoutAdd() {
+    EXPECT_TRUE(SetOneCodec(DefaultCodec()));
+    EXPECT_TRUE(SetSend(true));
+    EXPECT_TRUE(channel_->SetRender(true));
+    EXPECT_EQ(0, renderer_.num_rendered_frames());
+    EXPECT_TRUE(SendFrame());
+    EXPECT_FRAME_WAIT(1, 640, 400, kTimeout);
+    // No capturer was added, so this RemoveCapturer should
+    // fail.
+    EXPECT_FALSE(channel_->SetCapturer(kSsrc, NULL));
+    // Wait for kTimeout, to make sure no frames are sent
+    WAIT(renderer_.num_rendered_frames() != 1, kTimeout);
+    // Still a single frame, from the original SendFrame() call.
+    EXPECT_EQ(1, renderer_.num_rendered_frames());
+  }
+
+  // Tests that we can add and remove capturer as unique sources.
+  void AddRemoveCapturerMultipleSources() {
+    // WebRTC implementation will drop frames if pushed to quickly. Wait the
+    // interval time to avoid that.
+    const cricket::VideoFormat send_format(
+        1024,
+        768,
+        cricket::VideoFormat::FpsToInterval(30),
+        0);
+    // WebRTC implementation will drop frames if pushed to quickly. Wait the
+    // interval time to avoid that.
+    // Set up the stream associated with the engine.
+    EXPECT_TRUE(channel_->AddRecvStream(
+        cricket::StreamParams::CreateLegacy(kSsrc)));
+    EXPECT_TRUE(channel_->SetRenderer(kSsrc, &renderer_));
+    cricket::VideoFormat capture_format;  // default format
+    capture_format.interval = cricket::VideoFormat::FpsToInterval(30);
+    // Set up additional stream 1.
+    cricket::FakeVideoRenderer renderer1;
+    EXPECT_FALSE(channel_->SetRenderer(1, &renderer1));
+    EXPECT_TRUE(channel_->AddRecvStream(
+        cricket::StreamParams::CreateLegacy(1)));
+    EXPECT_TRUE(channel_->SetRenderer(1, &renderer1));
+    EXPECT_TRUE(channel_->AddSendStream(
+        cricket::StreamParams::CreateLegacy(1)));
+    talk_base::scoped_ptr<cricket::FakeVideoCapturer> capturer1(
+        new cricket::FakeVideoCapturer);
+    capturer1->SetScreencast(true);
+    EXPECT_EQ(cricket::CS_RUNNING, capturer1->Start(capture_format));
+    // Set up additional stream 2.
+    cricket::FakeVideoRenderer renderer2;
+    EXPECT_FALSE(channel_->SetRenderer(2, &renderer2));
+    EXPECT_TRUE(channel_->AddRecvStream(
+        cricket::StreamParams::CreateLegacy(2)));
+    EXPECT_TRUE(channel_->SetRenderer(2, &renderer2));
+    EXPECT_TRUE(channel_->AddSendStream(
+        cricket::StreamParams::CreateLegacy(2)));
+    talk_base::scoped_ptr<cricket::FakeVideoCapturer> capturer2(
+        new cricket::FakeVideoCapturer);
+    capturer2->SetScreencast(true);
+    EXPECT_EQ(cricket::CS_RUNNING, capturer2->Start(capture_format));
+    // State for all the streams.
+    EXPECT_TRUE(SetOneCodec(DefaultCodec()));
+    // A limitation in the lmi implementation requires that SetCapturer() is
+    // called after SetOneCodec().
+    // TODO(hellner): this seems like an unnecessary constraint, fix it.
+    EXPECT_TRUE(channel_->SetCapturer(1, capturer1.get()));
+    EXPECT_TRUE(channel_->SetCapturer(2, capturer2.get()));
+    EXPECT_TRUE(SetSend(true));
+    EXPECT_TRUE(channel_->SetRender(true));
+    // Test capturer associated with engine.
+    EXPECT_TRUE(capturer1->CaptureCustomFrame(1024, 768, cricket::FOURCC_I420));
+    EXPECT_FRAME_ON_RENDERER_WAIT(renderer1, 1, 1024, 768, kTimeout);
+    // Capture a frame with additional capturer2, frames should be received
+    EXPECT_TRUE(capturer2->CaptureCustomFrame(1024, 768, cricket::FOURCC_I420));
+    EXPECT_FRAME_ON_RENDERER_WAIT(renderer2, 1, 1024, 768, kTimeout);
+    EXPECT_FALSE(channel_->SetCapturer(kSsrc, NULL));
+    // The capturers must be unregistered here as it runs out of it's scope
+    // next.
+    EXPECT_TRUE(channel_->SetCapturer(1, NULL));
+    EXPECT_TRUE(channel_->SetCapturer(2, NULL));
+  }
+
+  void HighAspectHighHeightCapturer() {
+    const int kWidth  = 80;
+    const int kHeight = 10000;
+    const int kScaledWidth = 20;
+    const int kScaledHeight = 2500;
+
+    cricket::VideoCodec codec(DefaultCodec());
+    EXPECT_TRUE(SetOneCodec(codec));
+    EXPECT_TRUE(SetSend(true));
+
+    cricket::FakeVideoRenderer renderer;
+    EXPECT_TRUE(channel_->AddRecvStream(
+        cricket::StreamParams::CreateLegacy(kSsrc)));
+    EXPECT_TRUE(channel_->SetRenderer(kSsrc, &renderer));
+    EXPECT_TRUE(channel_->SetRender(true));
+    EXPECT_EQ(0, renderer.num_rendered_frames());
+
+    EXPECT_TRUE(SendFrame());
+    EXPECT_FRAME_ON_RENDERER_WAIT(renderer, 1, codec.width, codec.height,
+                                  kTimeout);
+
+    // Registering an external capturer is currently the same as screen casting
+    // (update the test when this changes).
+    talk_base::scoped_ptr<cricket::FakeVideoCapturer> capturer(
+        new cricket::FakeVideoCapturer);
+    capturer->SetScreencast(true);
+    const std::vector<cricket::VideoFormat>* formats =
+        capturer->GetSupportedFormats();
+    cricket::VideoFormat capture_format = (*formats)[0];
+    EXPECT_EQ(cricket::CS_RUNNING, capturer->Start(capture_format));
+    // Capture frame to not get same frame timestamps as previous capturer.
+    capturer->CaptureFrame();
+    EXPECT_TRUE(channel_->SetCapturer(kSsrc, capturer.get()));
+    EXPECT_TRUE(talk_base::Thread::Current()->ProcessMessages(30));
+    EXPECT_TRUE(capturer->CaptureCustomFrame(kWidth, kHeight,
+                                             cricket::FOURCC_ARGB));
+    EXPECT_TRUE(capturer->CaptureFrame());
+    EXPECT_FRAME_ON_RENDERER_WAIT(renderer, 2, kScaledWidth, kScaledHeight,
+                                  kTimeout);
+    EXPECT_TRUE(channel_->SetCapturer(kSsrc, NULL));
+  }
+
+  // Tests that we can adapt video resolution with 16:10 aspect ratio properly.
+  void AdaptResolution16x10() {
+    cricket::VideoCodec codec(DefaultCodec());
+    codec.width = 640;
+    codec.height = 400;
+    SendAndReceive(codec);
+    codec.width /= 2;
+    codec.height /= 2;
+    // Adapt the resolution.
+    EXPECT_TRUE(SetOneCodec(codec));
+    EXPECT_TRUE(WaitAndSendFrame(30));
+    EXPECT_FRAME_WAIT(2, codec.width, codec.height, kTimeout);
+  }
+  // Tests that we can adapt video resolution with 4:3 aspect ratio properly.
+  void AdaptResolution4x3() {
+    cricket::VideoCodec codec(DefaultCodec());
+    codec.width = 640;
+    codec.height = 400;
+    SendAndReceive(codec);
+    codec.width /= 2;
+    codec.height /= 2;
+    // Adapt the resolution.
+    EXPECT_TRUE(SetOneCodec(codec));
+    EXPECT_TRUE(WaitAndSendFrame(30));
+    EXPECT_FRAME_WAIT(2, codec.width, codec.height, kTimeout);
+  }
+  // Tests that we can drop all frames properly.
+  void AdaptDropAllFrames() {
+    // Set the channel codec's resolution to 0, which will require the adapter
+    // to drop all frames.
+    cricket::VideoCodec codec(DefaultCodec());
+    codec.width = codec.height = codec.framerate = 0;
+    EXPECT_TRUE(SetOneCodec(codec));
+    EXPECT_TRUE(SetSend(true));
+    EXPECT_TRUE(channel_->SetRender(true));
+    EXPECT_EQ(0, renderer_.num_rendered_frames());
+    EXPECT_TRUE(SendFrame());
+    EXPECT_TRUE(SendFrame());
+    talk_base::Thread::Current()->ProcessMessages(500);
+    EXPECT_EQ(0, renderer_.num_rendered_frames());
+  }
+  // Tests that we can reduce the frame rate on demand properly.
+  // TODO(fbarchard): This test is flakey on pulse.  Fix and re-enable
+  void AdaptFramerate() {
+    cricket::VideoCodec codec(DefaultCodec());
+    int frame_count = 0;
+    // The capturer runs at 30 fps. The channel requires 30 fps.
+    EXPECT_TRUE(SetOneCodec(codec));
+    EXPECT_TRUE(SetSend(true));
+    EXPECT_TRUE(channel_->SetRender(true));
+    EXPECT_EQ(frame_count, renderer_.num_rendered_frames());
+    EXPECT_TRUE(WaitAndSendFrame(0));  // Should be rendered.
+    EXPECT_TRUE(WaitAndSendFrame(30));  // Should be rendered.
+    frame_count += 2;
+    EXPECT_FRAME_WAIT(frame_count, codec.width, codec.height, kTimeout);
+    talk_base::scoped_ptr<const talk_base::Buffer> p(GetRtpPacket(0));
+    EXPECT_EQ(codec.id, GetPayloadType(p.get()));
+
+    // The channel requires 15 fps.
+    codec.framerate = 15;
+    EXPECT_TRUE(SetOneCodec(codec));
+    EXPECT_TRUE(WaitAndSendFrame(0));  // Should be rendered.
+    EXPECT_TRUE(WaitAndSendFrame(30));  // Should be dropped.
+    EXPECT_TRUE(WaitAndSendFrame(30));  // Should be rendered.
+    frame_count += 2;
+    EXPECT_EQ_WAIT(frame_count, renderer_.num_rendered_frames(), kTimeout);
+
+    // The channel requires 10 fps.
+    codec.framerate = 10;
+    EXPECT_TRUE(SetOneCodec(codec));
+    EXPECT_TRUE(WaitAndSendFrame(0));  // Should be rendered.
+    EXPECT_TRUE(WaitAndSendFrame(30));  // Should be dropped.
+    EXPECT_TRUE(WaitAndSendFrame(30));  // Should be dropped.
+    EXPECT_TRUE(WaitAndSendFrame(30));  // Should be rendered.
+    frame_count += 2;
+    EXPECT_EQ_WAIT(frame_count, renderer_.num_rendered_frames(), kTimeout);
+
+    // The channel requires 8 fps. The adapter adapts to 10 fps, which is the
+    // closest factor of 30.
+    codec.framerate = 8;
+    EXPECT_TRUE(SetOneCodec(codec));
+    EXPECT_TRUE(WaitAndSendFrame(0));  // Should be rendered.
+    EXPECT_TRUE(WaitAndSendFrame(30));  // Should be dropped.
+    EXPECT_TRUE(WaitAndSendFrame(30));  // Should be dropped.
+    EXPECT_TRUE(WaitAndSendFrame(30));  // Should be rendered.
+    frame_count += 2;
+    EXPECT_EQ_WAIT(frame_count, renderer_.num_rendered_frames(), kTimeout);
+  }
+  // Tests that we can set the send stream format properly.
+  void SetSendStreamFormat() {
+    cricket::VideoCodec codec(DefaultCodec());
+    SendAndReceive(codec);
+    int frame_count = 1;
+    EXPECT_FRAME_WAIT(frame_count, codec.width, codec.height, kTimeout);
+
+    // Adapt the resolution and frame rate to half.
+    cricket::VideoFormat format(
+        codec.width / 2,
+        codec.height / 2,
+        cricket::VideoFormat::FpsToInterval(codec.framerate / 2),
+        cricket::FOURCC_I420);
+    // The SSRC differs from the send SSRC.
+    EXPECT_FALSE(channel_->SetSendStreamFormat(kSsrc - 1, format));
+    EXPECT_TRUE(channel_->SetSendStreamFormat(kSsrc, format));
+
+    EXPECT_TRUE(WaitAndSendFrame(30));  // Should be dropped.
+    EXPECT_TRUE(WaitAndSendFrame(30));  // Should be rendered.
+    EXPECT_TRUE(WaitAndSendFrame(30));  // Should be dropped.
+    frame_count += 1;
+    EXPECT_FRAME_WAIT(frame_count, format.width, format.height, kTimeout);
+
+    // Adapt the resolution to 0x0, which should drop all frames.
+    format.width = 0;
+    format.height = 0;
+    EXPECT_TRUE(channel_->SetSendStreamFormat(kSsrc, format));
+    EXPECT_TRUE(SendFrame());
+    EXPECT_TRUE(SendFrame());
+    talk_base::Thread::Current()->ProcessMessages(500);
+    EXPECT_EQ(frame_count, renderer_.num_rendered_frames());
+  }
+  // Test that setting send stream format to 0x0 resolution will result in
+  // frames being dropped.
+  void SetSendStreamFormat0x0() {
+    EXPECT_TRUE(SetOneCodec(DefaultCodec()));
+    EXPECT_TRUE(SetSend(true));
+    EXPECT_TRUE(channel_->SetRender(true));
+    EXPECT_EQ(0, renderer_.num_rendered_frames());
+    // This frame should be received.
+    EXPECT_TRUE(SendFrame());
+    EXPECT_FRAME_WAIT(1, DefaultCodec().width, DefaultCodec().height, kTimeout);
+    const int64 interval = cricket::VideoFormat::FpsToInterval(
+        DefaultCodec().framerate);
+    cricket::VideoFormat format(
+        0,
+        0,
+        interval,
+        cricket::FOURCC_I420);
+    EXPECT_TRUE(channel_->SetSendStreamFormat(kSsrc, format));
+    // This frame should not be received.
+    EXPECT_TRUE(WaitAndSendFrame(
+        static_cast<int>(interval/talk_base::kNumNanosecsPerMillisec)));
+    talk_base::Thread::Current()->ProcessMessages(500);
+    EXPECT_EQ(1, renderer_.num_rendered_frames());
+  }
+
+  // Tests that we can mute and unmute the channel properly.
+  void MuteStream() {
+    int frame_count = 0;
+    EXPECT_TRUE(SetDefaultCodec());
+    cricket::FakeVideoCapturer video_capturer;
+    video_capturer.Start(
+        cricket::VideoFormat(
+            640, 480,
+            cricket::VideoFormat::FpsToInterval(30),
+            cricket::FOURCC_I420));
+    EXPECT_TRUE(channel_->SetCapturer(kSsrc, &video_capturer));
+    EXPECT_TRUE(SetSend(true));
+    EXPECT_TRUE(channel_->SetRender(true));
+    EXPECT_EQ(frame_count, renderer_.num_rendered_frames());
+
+    // Mute the channel and expect black output frame.
+    EXPECT_TRUE(channel_->MuteStream(kSsrc, true));
+    EXPECT_TRUE(video_capturer.CaptureFrame());
+    ++frame_count;
+    EXPECT_EQ_WAIT(frame_count, renderer_.num_rendered_frames(), kTimeout);
+    EXPECT_TRUE(renderer_.black_frame());
+
+    // Unmute the channel and expect non-black output frame.
+    EXPECT_TRUE(channel_->MuteStream(kSsrc, false));
+    EXPECT_TRUE(talk_base::Thread::Current()->ProcessMessages(30));
+    EXPECT_TRUE(video_capturer.CaptureFrame());
+    ++frame_count;
+    EXPECT_EQ_WAIT(frame_count, renderer_.num_rendered_frames(), kTimeout);
+    EXPECT_FALSE(renderer_.black_frame());
+
+    // Test that we can also Mute using the correct send stream SSRC.
+    EXPECT_TRUE(channel_->MuteStream(kSsrc, true));
+    EXPECT_TRUE(talk_base::Thread::Current()->ProcessMessages(30));
+    EXPECT_TRUE(video_capturer.CaptureFrame());
+    ++frame_count;
+    EXPECT_EQ_WAIT(frame_count, renderer_.num_rendered_frames(), kTimeout);
+    EXPECT_TRUE(renderer_.black_frame());
+
+    EXPECT_TRUE(channel_->MuteStream(kSsrc, false));
+    EXPECT_TRUE(talk_base::Thread::Current()->ProcessMessages(30));
+    EXPECT_TRUE(video_capturer.CaptureFrame());
+    ++frame_count;
+    EXPECT_EQ_WAIT(frame_count, renderer_.num_rendered_frames(), kTimeout);
+    EXPECT_FALSE(renderer_.black_frame());
+
+    // Test that muting an invalid stream fails.
+    EXPECT_FALSE(channel_->MuteStream(kSsrc+1, true));
+    EXPECT_TRUE(channel_->SetCapturer(kSsrc, NULL));
+  }
+
+  // Test that multiple send streams can be created and deleted properly.
+  void MultipleSendStreams() {
+    // Remove stream added in Setup. I.e. remove stream corresponding to default
+    // channel.
+    EXPECT_TRUE(channel_->RemoveSendStream(kSsrc));
+    const unsigned int kSsrcsSize = sizeof(kSsrcs4)/sizeof(kSsrcs4[0]);
+    for (unsigned int i = 0; i < kSsrcsSize; ++i) {
+      EXPECT_TRUE(channel_->AddSendStream(
+          cricket::StreamParams::CreateLegacy(kSsrcs4[i])));
+    }
+    // Delete one of the non default channel streams, let the destructor delete
+    // the remaining ones.
+    EXPECT_TRUE(channel_->RemoveSendStream(kSsrcs4[kSsrcsSize - 1]));
+    // Stream should already be deleted.
+    EXPECT_FALSE(channel_->RemoveSendStream(kSsrcs4[kSsrcsSize - 1]));
+  }
+
+
+  // Two streams one channel tests.
+
+  // Tests that we can send and receive frames.
+  void TwoStreamsSendAndReceive(const cricket::VideoCodec& codec) {
+    SetUpSecondStream();
+    // Test sending and receiving on first stream.
+    SendAndReceive(codec);
+    // Test sending and receiving on second stream.
+    EXPECT_EQ_WAIT(1, renderer2_.num_rendered_frames(), kTimeout);
+    EXPECT_EQ(2, NumRtpPackets());
+    EXPECT_EQ(1, renderer2_.num_rendered_frames());
+  }
+
+  // Disconnect the first stream and re-use it with another SSRC
+  void TwoStreamsReUseFirstStream(const cricket::VideoCodec& codec) {
+    SetUpSecondStream();
+    EXPECT_TRUE(channel_->RemoveRecvStream(kSsrc));
+    EXPECT_FALSE(channel_->RemoveRecvStream(kSsrc));
+    // SSRC 0 should map to the "default" stream. I.e. the first added stream.
+    EXPECT_TRUE(channel_->RemoveSendStream(0));
+    // Make sure that the first added stream was indeed the "default" stream.
+    EXPECT_FALSE(channel_->RemoveSendStream(kSsrc));
+    // Make sure that the "default" stream is indeed removed and that removing
+    // the default stream has an effect.
+    EXPECT_FALSE(channel_->RemoveSendStream(0));
+
+    SetRendererAsDefault();
+    EXPECT_TRUE(channel_->AddSendStream(
+        cricket::StreamParams::CreateLegacy(kSsrc)));
+    EXPECT_FALSE(channel_->AddSendStream(
+        cricket::StreamParams::CreateLegacy(kSsrc)));
+    EXPECT_TRUE(channel_->AddRecvStream(
+        cricket::StreamParams::CreateLegacy(kSsrc)));
+    EXPECT_FALSE(channel_->AddRecvStream(
+        cricket::StreamParams::CreateLegacy(kSsrc)));
+
+    SendAndReceive(codec);
+    EXPECT_TRUE(channel_->RemoveSendStream(0));
+  }
+
+  VideoEngineOverride<E> engine_;
+  talk_base::scoped_ptr<cricket::FakeVideoCapturer> video_capturer_;
+  talk_base::scoped_ptr<C> channel_;
+  cricket::FakeNetworkInterface network_interface_;
+  cricket::FakeVideoRenderer renderer_;
+  cricket::VideoMediaChannel::Error media_error_;
+
+  // Used by test cases where 2 streams are run on the same channel.
+  cricket::FakeVideoRenderer renderer2_;
+};
+
+#endif  // TALK_MEDIA_BASE_VIDEOENGINE_UNITTEST_H_
diff --git a/talk/media/base/videoframe.cc b/talk/media/base/videoframe.cc
new file mode 100644
index 0000000..f5b0b6d
--- /dev/null
+++ b/talk/media/base/videoframe.cc
@@ -0,0 +1,385 @@
+/*
+ * 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/media/base/videoframe.h"
+
+#include <cstring>
+
+#if !defined(DISABLE_YUV)
+#include "libyuv/compare.h"
+#include "libyuv/planar_functions.h"
+#include "libyuv/scale.h"
+#endif
+
+#include "talk/base/logging.h"
+#include "talk/media/base/videocommon.h"
+
+namespace cricket {
+
+// Round to 2 pixels because Chroma channels are half size.
+#define ROUNDTO2(v) (v & ~1)
+
+talk_base::StreamResult VideoFrame::Write(talk_base::StreamInterface* stream,
+                                          int* error) {
+  talk_base::StreamResult result = talk_base::SR_SUCCESS;
+  const uint8* src_y = GetYPlane();
+  const uint8* src_u = GetUPlane();
+  const uint8* src_v = GetVPlane();
+  if (!src_y || !src_u || !src_v) {
+    return result;  // Nothing to write.
+  }
+  const int32 y_pitch = GetYPitch();
+  const int32 u_pitch = GetUPitch();
+  const int32 v_pitch = GetVPitch();
+  const size_t width = GetWidth();
+  const size_t height = GetHeight();
+  const size_t half_width = (width + 1) >> 1;
+  const size_t half_height = (height + 1) >> 1;
+  // Write Y.
+  for (size_t row = 0; row < height; ++row) {
+    result = stream->Write(src_y + row * y_pitch, width, NULL, error);
+    if (result != talk_base::SR_SUCCESS) {
+      return result;
+    }
+  }
+  // Write U.
+  for (size_t row = 0; row < half_height; ++row) {
+    result = stream->Write(src_u + row * u_pitch, half_width, NULL, error);
+    if (result != talk_base::SR_SUCCESS) {
+      return result;
+    }
+  }
+  // Write V.
+  for (size_t row = 0; row < half_height; ++row) {
+    result = stream->Write(src_v + row * v_pitch, half_width, NULL, error);
+    if (result != talk_base::SR_SUCCESS) {
+      return result;
+    }
+  }
+  return result;
+}
+
+bool VideoFrame::CopyToPlanes(
+    uint8* dst_y, uint8* dst_u, uint8* dst_v,
+    int32 dst_pitch_y, int32 dst_pitch_u, int32 dst_pitch_v) const {
+#if !defined(DISABLE_YUV)
+  int32 src_width = GetWidth();
+  int32 src_height = GetHeight();
+  return libyuv::I420Copy(GetYPlane(), GetYPitch(),
+                          GetUPlane(), GetUPitch(),
+                          GetVPlane(), GetVPitch(),
+                          dst_y, dst_pitch_y,
+                          dst_u, dst_pitch_u,
+                          dst_v, dst_pitch_v,
+                          src_width, src_height) == 0;
+#else
+  int uv_size = GetUPitch() * GetChromaHeight();
+  memcpy(dst_y, GetYPlane(), GetWidth() * GetHeight());
+  memcpy(dst_u, GetUPlane(), uv_size);
+  memcpy(dst_v, GetVPlane(), uv_size);
+  return true;
+#endif
+}
+
+void VideoFrame::CopyToFrame(VideoFrame* dst) const {
+  if (!dst) {
+    LOG(LS_ERROR) << "NULL dst pointer.";
+    return;
+  }
+
+  CopyToPlanes(dst->GetYPlane(), dst->GetUPlane(), dst->GetVPlane(),
+               dst->GetYPitch(), dst->GetUPitch(), dst->GetVPitch());
+}
+
+// TODO(fbarchard): Handle odd width/height with rounding.
+void VideoFrame::StretchToPlanes(
+    uint8* dst_y, uint8* dst_u, uint8* dst_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 {
+  if (!GetYPlane() || !GetUPlane() || !GetVPlane()) {
+    LOG(LS_ERROR) << "NULL plane pointer.";
+    return;
+  }
+
+  size_t src_width = GetWidth();
+  size_t src_height = GetHeight();
+  if (width == src_width && height == src_height) {
+    CopyToPlanes(dst_y, dst_u, dst_v, dst_pitch_y, dst_pitch_u, dst_pitch_v);
+    return;
+  }
+  const uint8* src_y = GetYPlane();
+  const uint8* src_u = GetUPlane();
+  const uint8* src_v = GetVPlane();
+
+  if (vert_crop) {
+    // Adjust the input width:height ratio to be the same as the output ratio.
+    if (src_width * height > src_height * width) {
+      // Reduce the input width, but keep size/position aligned for YuvScaler
+      src_width = ROUNDTO2(src_height * width / height);
+      int32 iwidth_offset = ROUNDTO2((GetWidth() - src_width) / 2);
+      src_y += iwidth_offset;
+      src_u += iwidth_offset / 2;
+      src_v += iwidth_offset / 2;
+    } else if (src_width * height < src_height * width) {
+      // Reduce the input height.
+      src_height = src_width * height / width;
+      int32 iheight_offset = (GetHeight() - src_height) >> 2;
+      iheight_offset <<= 1;  // Ensure that iheight_offset is even.
+      src_y += iheight_offset * GetYPitch();
+      src_u += iheight_offset / 2 * GetUPitch();
+      src_v += iheight_offset / 2 * GetVPitch();
+    }
+  }
+
+  // TODO(fbarchard): Implement a simple scale for non-libyuv.
+#if !defined(DISABLE_YUV)
+  // Scale to the output I420 frame.
+  libyuv::Scale(src_y, src_u, src_v,
+                GetYPitch(), GetUPitch(), GetVPitch(),
+                src_width, src_height,
+                dst_y, dst_u, dst_v, dst_pitch_y, dst_pitch_u, dst_pitch_v,
+                width, height, interpolate);
+#endif
+}
+
+size_t VideoFrame::StretchToBuffer(size_t dst_width, size_t dst_height,
+                                   uint8* dst_buffer, size_t size,
+                                   bool interpolate, bool vert_crop) const {
+  if (!dst_buffer) {
+    LOG(LS_ERROR) << "NULL dst_buffer pointer.";
+    return 0;
+  }
+
+  size_t needed = SizeOf(dst_width, dst_height);
+  if (needed <= size) {
+    uint8* dst_y = dst_buffer;
+    uint8* dst_u = dst_y + dst_width * dst_height;
+    uint8* dst_v = dst_u + ((dst_width + 1) >> 1) * ((dst_height + 1) >> 1);
+    StretchToPlanes(dst_y, dst_u, dst_v,
+                    dst_width, (dst_width + 1) >> 1, (dst_width + 1) >> 1,
+                    dst_width, dst_height, interpolate, vert_crop);
+  }
+  return needed;
+}
+
+void VideoFrame::StretchToFrame(VideoFrame* dst,
+                                bool interpolate, bool vert_crop) const {
+  if (!dst) {
+    LOG(LS_ERROR) << "NULL dst pointer.";
+    return;
+  }
+
+  StretchToPlanes(dst->GetYPlane(), dst->GetUPlane(), dst->GetVPlane(),
+                  dst->GetYPitch(), dst->GetUPitch(), dst->GetVPitch(),
+                  dst->GetWidth(), dst->GetHeight(),
+                  interpolate, vert_crop);
+  dst->SetElapsedTime(GetElapsedTime());
+  dst->SetTimeStamp(GetTimeStamp());
+}
+
+VideoFrame* VideoFrame::Stretch(size_t dst_width, size_t dst_height,
+                                bool interpolate, bool vert_crop) const {
+  VideoFrame* dest = CreateEmptyFrame(dst_width, dst_height,
+                                      GetPixelWidth(), GetPixelHeight(),
+                                      GetElapsedTime(), GetTimeStamp());
+  if (dest) {
+    StretchToFrame(dest, interpolate, vert_crop);
+  }
+  return dest;
+}
+
+bool VideoFrame::SetToBlack() {
+#if !defined(DISABLE_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
+}
+
+static const size_t kMaxSampleSize = 1000000000u;
+// Returns whether a sample is valid
+bool VideoFrame::Validate(uint32 fourcc, int w, int h,
+                          const uint8 *sample, size_t sample_size) {
+  if (h < 0) {
+    h = -h;
+  }
+  // 16384 is maximum resolution for VP8 codec.
+  if (w < 1 || w > 16384 || h < 1 || h > 16384) {
+    LOG(LS_ERROR) << "Invalid dimensions: " << w << "x" << h;
+    return false;
+  }
+  uint32 format = CanonicalFourCC(fourcc);
+  int expected_bpp = 8;
+  switch (format) {
+    case FOURCC_I400:
+    case FOURCC_RGGB:
+    case FOURCC_BGGR:
+    case FOURCC_GRBG:
+    case FOURCC_GBRG:
+      expected_bpp = 8;
+      break;
+    case FOURCC_I420:
+    case FOURCC_I411:
+    case FOURCC_YU12:
+    case FOURCC_YV12:
+    case FOURCC_M420:
+    case FOURCC_Q420:
+    case FOURCC_NV21:
+    case FOURCC_NV12:
+      expected_bpp = 12;
+      break;
+    case FOURCC_I422:
+    case FOURCC_YV16:
+    case FOURCC_YUY2:
+    case FOURCC_UYVY:
+    case FOURCC_RGBP:
+    case FOURCC_RGBO:
+    case FOURCC_R444:
+      expected_bpp = 16;
+      break;
+    case FOURCC_I444:
+    case FOURCC_YV24:
+    case FOURCC_24BG:
+    case FOURCC_RAW:
+      expected_bpp = 24;
+      break;
+
+    case FOURCC_ABGR:
+    case FOURCC_BGRA:
+    case FOURCC_ARGB:
+      expected_bpp = 32;
+      break;
+
+    case FOURCC_MJPG:
+    case FOURCC_H264:
+      expected_bpp = 0;
+      break;
+    default:
+      expected_bpp = 8;  // Expect format is at least 8 bits per pixel.
+      break;
+  }
+  size_t expected_size = (w * expected_bpp + 7) / 8 * h;
+  // For compressed formats, expect 4 bits per 16 x 16 macro.  I420 would be
+  // 6 bits, but grey can be 4 bits.
+  if (expected_bpp == 0) {
+    expected_size = ((w + 15) / 16) * ((h + 15) / 16) * 4 / 8;
+  }
+  if (sample == NULL) {
+    LOG(LS_ERROR) << "NULL sample pointer."
+                  << " format: " << GetFourccName(format)
+                  << " bpp: " << expected_bpp
+                  << " size: " << w << "x" << h
+                  << " expected: " << expected_size
+                  << " " << sample_size;
+    return false;
+  }
+  if (sample_size < expected_size) {
+    LOG(LS_ERROR) << "Size field is too small."
+                  << " format: " << GetFourccName(format)
+                  << " bpp: " << expected_bpp
+                  << " size: " << w << "x" << h
+                  << " " << sample_size
+                  << " expected: " << expected_size
+                  << " sample[0..3]: " << static_cast<int>(sample[0])
+                  << ", " << static_cast<int>(sample[1])
+                  << ", " << static_cast<int>(sample[2])
+                  << ", " << static_cast<int>(sample[3]);
+    return false;
+  }
+  if (sample_size > kMaxSampleSize) {
+    LOG(LS_WARNING) << "Size field is invalid."
+                    << " format: " << GetFourccName(format)
+                    << " bpp: " << expected_bpp
+                    << " size: " << w << "x" << h
+                    << " " << sample_size
+                    << " expected: " << 2 * expected_size
+                    << " sample[0..3]: " << static_cast<int>(sample[0])
+                    << ", " << static_cast<int>(sample[1])
+                    << ", " << static_cast<int>(sample[2])
+                    << ", " << static_cast<int>(sample[3]);
+    return false;
+  }
+  // Show large size warning once every 100 frames.
+  static int large_warn100 = 0;
+  size_t large_expected_size = expected_size * 2;
+  if (expected_bpp >= 8 &&
+      (sample_size > large_expected_size || sample_size > kMaxSampleSize) &&
+      large_warn100 % 100 == 0) {
+    ++large_warn100;
+    LOG(LS_WARNING) << "Size field is too large."
+                    << " format: " << GetFourccName(format)
+                    << " bpp: " << expected_bpp
+                    << " size: " << w << "x" << h
+                    << " bytes: " << sample_size
+                    << " expected: " << large_expected_size
+                    << " sample[0..3]: " << static_cast<int>(sample[0])
+                    << ", " << static_cast<int>(sample[1])
+                    << ", " << static_cast<int>(sample[2])
+                    << ", " << static_cast<int>(sample[3]);
+  }
+  // Scan pages to ensure they are there and don't contain a single value and
+  // to generate an error.
+  if (!memcmp(sample + sample_size - 8, sample + sample_size - 4, 4) &&
+      !memcmp(sample, sample + 4, sample_size - 4)) {
+    LOG(LS_WARNING) << "Duplicate value for all pixels."
+                    << " format: " << GetFourccName(format)
+                    << " bpp: " << expected_bpp
+                    << " size: " << w << "x" << h
+                    << " bytes: " << sample_size
+                    << " expected: " << expected_size
+                    << " sample[0..3]: " << static_cast<int>(sample[0])
+                    << ", " << static_cast<int>(sample[1])
+                    << ", " << static_cast<int>(sample[2])
+                    << ", " << static_cast<int>(sample[3]);
+  }
+
+  static bool valid_once = true;
+  if (valid_once) {
+    valid_once = false;
+    LOG(LS_INFO) << "Validate frame passed."
+                 << " format: " << GetFourccName(format)
+                 << " bpp: " << expected_bpp
+                 << " size: " << w << "x" << h
+                 << " bytes: " << sample_size
+                 << " expected: " << expected_size
+                 << " sample[0..3]: " << static_cast<int>(sample[0])
+                 << ", " << static_cast<int>(sample[1])
+                 << ", " << static_cast<int>(sample[2])
+                 << ", " << static_cast<int>(sample[3]);
+  }
+  return true;
+}
+
+}  // namespace cricket
diff --git a/talk/media/base/videoframe.h b/talk/media/base/videoframe.h
new file mode 100644
index 0000000..2f641ff
--- /dev/null
+++ b/talk/media/base/videoframe.h
@@ -0,0 +1,188 @@
+/*
+ * 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_MEDIA_BASE_VIDEOFRAME_H_
+#define TALK_MEDIA_BASE_VIDEOFRAME_H_
+
+#include "talk/base/basictypes.h"
+#include "talk/base/stream.h"
+
+namespace cricket {
+
+// Simple rotation constants.
+enum {
+  ROTATION_0 = 0,
+  ROTATION_90 = 90,
+  ROTATION_180 = 180,
+  ROTATION_270 = 270
+};
+
+// Represents a YUV420 (a.k.a. I420) video frame.
+class VideoFrame {
+ public:
+  VideoFrame() {}
+  virtual ~VideoFrame() {}
+
+  virtual bool InitToBlack(int w, int h, size_t pixel_width,
+                           size_t pixel_height, int64 elapsed_time,
+                           int64 time_stamp) = 0;
+  // 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;
+  virtual uint8 *GetYPlane() = 0;
+  virtual uint8 *GetUPlane() = 0;
+  virtual uint8 *GetVPlane() = 0;
+  virtual int32 GetYPitch() const = 0;
+  virtual int32 GetUPitch() const = 0;
+  virtual int32 GetVPitch() const = 0;
+
+  // For retrieving the aspect ratio of each pixel. Usually this is 1x1, but
+  // the aspect_ratio_idc parameter of H.264 can specify non-square pixels.
+  virtual size_t GetPixelWidth() const = 0;
+  virtual size_t GetPixelHeight() const = 0;
+
+  virtual int64 GetElapsedTime() const = 0;
+  virtual int64 GetTimeStamp() const = 0;
+  virtual void SetElapsedTime(int64 elapsed_time) = 0;
+  virtual void SetTimeStamp(int64 time_stamp) = 0;
+
+  // Indicates the rotation angle in degrees.
+  virtual int GetRotation() const = 0;
+
+  // Make a shallow copy of the frame. The frame buffer itself is not copied.
+  // Both the current and new VideoFrame will share a single reference-counted
+  // frame buffer.
+  virtual VideoFrame *Copy() const = 0;
+
+  // Since VideoFrame supports shallow copy and the internal frame buffer might
+  // be shared, in case VideoFrame needs exclusive access of the frame buffer,
+  // user can call MakeExclusive() to make sure the frame buffer is exclusive
+  // accessable to the current object.  This might mean a deep copy of the frame
+  // buffer if it is currently shared by other objects.
+  virtual bool MakeExclusive() = 0;
+
+  // Writes the frame into the given frame buffer, provided that it is of
+  // sufficient size. Returns the frame's actual size, regardless of whether
+  // it was written or not (like snprintf). If there is insufficient space,
+  // nothing is written.
+  virtual size_t CopyToBuffer(uint8 *buffer, size_t size) const = 0;
+
+  // Writes the frame into the given planes, stretched to the given width and
+  // height. The parameter "interpolate" controls whether to interpolate or just
+  // take the nearest-point. The parameter "crop" controls whether to crop this
+  // frame to the aspect ratio of the given dimensions before stretching.
+  virtual bool CopyToPlanes(
+      uint8* dst_y, uint8* dst_u, uint8* dst_v,
+      int32 dst_pitch_y, int32 dst_pitch_u, int32 dst_pitch_v) const;
+
+  // Writes the frame into the target VideoFrame.
+  virtual void CopyToFrame(VideoFrame* target) const;
+
+  // Writes the frame into the given stream and returns the StreamResult.
+  // See talk/base/stream.h for a description of StreamResult and error.
+  // Error may be NULL. If a non-success value is returned from
+  // StreamInterface::Write(), we immediately return with that value.
+  virtual talk_base::StreamResult Write(talk_base::StreamInterface *stream,
+                                        int *error);
+
+  // 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 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, 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
+  // take the nearest-point. The parameter "crop" controls whether to crop this
+  // frame to the aspect ratio of the given dimensions before stretching.
+  virtual void StretchToPlanes(
+      uint8 *y, uint8 *u, uint8 *v, int32 pitchY, int32 pitchU, int32 pitchV,
+      size_t width, size_t height, bool interpolate, bool crop) const;
+
+  // Writes the frame into the given frame buffer, stretched to the given width
+  // and height, provided that it is of sufficient size. Returns the frame's
+  // actual size, regardless of whether it was written or not (like snprintf).
+  // If there is insufficient space, nothing is written. The parameter
+  // "interpolate" controls whether to interpolate or just take the
+  // nearest-point. The parameter "crop" controls whether to crop this frame to
+  // the aspect ratio of the given dimensions before stretching.
+  virtual size_t StretchToBuffer(size_t w, size_t h, uint8 *buffer, size_t size,
+                                 bool interpolate, bool crop) const;
+
+  // 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;
+
+  // 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;
+
+  // Sets the video frame to black.
+  bool SetToBlack();
+
+  // Tests if sample is valid.  Returns true if valid.
+  static bool Validate(uint32 fourcc, int w, int h, const uint8 *sample,
+                       size_t sample_size);
+
+  // Size of an I420 image of given dimensions when stored as a frame buffer.
+  static size_t SizeOf(size_t w, size_t h) {
+    return w * h + ((w + 1) / 2) * ((h + 1) / 2) * 2;
+  }
+
+ protected:
+  // 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;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_VIDEOFRAME_H_
diff --git a/talk/media/base/videoframe_unittest.h b/talk/media/base/videoframe_unittest.h
new file mode 100644
index 0000000..f70e567
--- /dev/null
+++ b/talk/media/base/videoframe_unittest.h
@@ -0,0 +1,2102 @@
+/*
+ * 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_MEDIA_BASE_VIDEOFRAME_UNITTEST_H_
+#define TALK_MEDIA_BASE_VIDEOFRAME_UNITTEST_H_
+
+#include <string>
+
+#include "libyuv/convert.h"
+#include "libyuv/convert_from.h"
+#include "libyuv/format_conversion.h"
+#include "libyuv/planar_functions.h"
+#include "libyuv/rotate.h"
+#include "talk/base/gunit.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringutils.h"
+#include "talk/media/base/testutils.h"
+#include "talk/media/base/videocommon.h"
+#include "talk/media/base/videoframe.h"
+
+#if defined(_MSC_VER)
+#define ALIGN16(var) __declspec(align(16)) var
+#else
+#define ALIGN16(var) var __attribute__((aligned(16)))
+#endif
+
+#define kImageFilename "faces.1280x720_P420.yuv"
+#define kJpeg420Filename "faces_I420.jpg"
+#define kJpeg422Filename "faces_I422.jpg"
+#define kJpeg444Filename "faces_I444.jpg"
+#define kJpeg411Filename "faces_I411.jpg"
+#define kJpeg400Filename "faces_I400.jpg"
+
+// Generic test class for testing various video frame implementations.
+template <class T>
+class VideoFrameTest : public testing::Test {
+ public:
+  VideoFrameTest() : repeat_(1) {}
+
+ protected:
+  static const int kWidth = 1280;
+  static const int kHeight = 720;
+  static const int kAlignment = 16;
+  static const int kMinWidthAll = 1;  // Constants for ConstructYUY2AllSizes.
+  static const int kMinHeightAll = 1;
+  static const int kMaxWidthAll = 17;
+  static const int kMaxHeightAll = 23;
+
+  // Load a video frame from disk.
+  bool LoadFrameNoRepeat(T* frame) {
+    int save_repeat = repeat_;  // This LoadFrame disables repeat.
+    repeat_ = 1;
+    bool success = LoadFrame(kImageFilename, cricket::FOURCC_I420,
+                            kWidth, kHeight, frame);
+    repeat_ = save_repeat;
+    return success;
+  }
+
+  bool LoadFrame(const std::string& filename, uint32 format,
+                 int32 width, int32 height, T* frame) {
+    return LoadFrame(filename, format, width, height,
+                     width, abs(height), 0, frame);
+  }
+  bool LoadFrame(const std::string& filename, uint32 format,
+                 int32 width, int32 height, int dw, int dh, int rotation,
+                 T* frame) {
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(LoadSample(filename));
+    return LoadFrame(ms.get(), format, width, height, dw, dh, rotation, frame);
+  }
+  // Load a video frame from a memory stream.
+  bool LoadFrame(talk_base::MemoryStream* ms, uint32 format,
+                 int32 width, int32 height, T* frame) {
+    return LoadFrame(ms, format, width, height,
+                     width, abs(height), 0, frame);
+  }
+  bool LoadFrame(talk_base::MemoryStream* ms, uint32 format,
+                 int32 width, int32 height, int dw, int dh, int rotation,
+                 T* frame) {
+    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, dw, dh, rotation, frame);
+    }
+    return ret;
+  }
+  // Load a frame from a raw buffer.
+  bool LoadFrame(uint8* sample, size_t sample_size, uint32 format,
+                 int32 width, int32 height, T* frame) {
+    return LoadFrame(sample, sample_size, format, width, height,
+                     width, abs(height), 0, frame);
+  }
+  bool LoadFrame(uint8* sample, size_t sample_size, uint32 format,
+                 int32 width, int32 height, int dw, int dh, int rotation,
+                 T* frame) {
+    bool ret = false;
+    for (int i = 0; i < repeat_; ++i) {
+      ret = frame->Init(format, width, height, dw, dh,
+                        sample, sample_size, 1, 1, 0, 0, rotation);
+    }
+    return ret;
+  }
+
+  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 cricket::VideoFrame& 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 in the desired color space.
+  // The image is a checkerboard pattern with 63x63 squares, which allows
+  // I420 chroma artifacts to easily be seen on the square boundaries.
+  // The pattern is { { green, orange }, { blue, purple } }
+  // There is also a gradient within each square to ensure that the luma
+  // values are handled properly.
+  talk_base::MemoryStream* CreateYuv422Sample(uint32 fourcc,
+                                              uint32 width, uint32 height) {
+    int y1_pos, y2_pos, u_pos, v_pos;
+    if (!GetYuv422Packing(fourcc, &y1_pos, &y2_pos, &u_pos, &v_pos)) {
+      return NULL;
+    }
+
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        new talk_base::MemoryStream);
+    int awidth = (width + 1) & ~1;
+    int size = awidth * 2 * height;
+    if (!ms->ReserveSize(size)) {
+      return NULL;
+    }
+    for (uint32 y = 0; y < height; ++y) {
+      for (int x = 0; x < awidth; x += 2) {
+        uint8 quad[4];
+        quad[y1_pos] = (x % 63 + y % 63) + 64;
+        quad[y2_pos] = ((x + 1) % 63 + y % 63) + 64;
+        quad[u_pos] = ((x / 63) & 1) ? 192 : 64;
+        quad[v_pos] = ((y / 63) & 1) ? 192 : 64;
+        ms->Write(quad, sizeof(quad), NULL, NULL);
+      }
+    }
+    return ms.release();
+  }
+
+  // Create a test image for YUV 420 formats with 12 bits per pixel.
+  talk_base::MemoryStream* CreateYuvSample(uint32 width, uint32 height,
+                                           uint32 bpp) {
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        new talk_base::MemoryStream);
+    if (!ms->ReserveSize(width * height * bpp / 8)) {
+      return NULL;
+    }
+
+    for (uint32 i = 0; i < width * height * bpp / 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();
+  }
+
+  // Simple conversion routines to verify the optimized VideoFrame routines.
+  // Converts from the specified colorspace to I420.
+  bool ConvertYuv422(const talk_base::MemoryStream* ms,
+                     uint32 fourcc, uint32 width, uint32 height,
+                     T* frame) {
+    int y1_pos, y2_pos, u_pos, v_pos;
+    if (!GetYuv422Packing(fourcc, &y1_pos, &y2_pos, &u_pos, &v_pos)) {
+      return false;
+    }
+
+    const uint8* start = reinterpret_cast<const uint8*>(ms->GetBuffer());
+    int awidth = (width + 1) & ~1;
+    frame->InitToBlack(width, height, 1, 1, 0, 0);
+    int stride_y = frame->GetYPitch();
+    int stride_u = frame->GetUPitch();
+    int stride_v = frame->GetVPitch();
+    for (uint32 y = 0; y < height; ++y) {
+      for (uint32 x = 0; x < width; x += 2) {
+        const uint8* quad1 = start + (y * awidth + x) * 2;
+        frame->GetYPlane()[stride_y * y + x] = quad1[y1_pos];
+        if ((x + 1) < width) {
+          frame->GetYPlane()[stride_y * y + x + 1] = quad1[y2_pos];
+        }
+        if ((y & 1) == 0) {
+          const uint8* quad2 = quad1 + awidth * 2;
+          if ((y + 1) >= height) {
+            quad2 = quad1;
+          }
+          frame->GetUPlane()[stride_u * (y / 2) + x / 2] =
+              (quad1[u_pos] + quad2[u_pos] + 1) / 2;
+          frame->GetVPlane()[stride_v * (y / 2) + x / 2] =
+              (quad1[v_pos] + quad2[v_pos] + 1) / 2;
+        }
+      }
+    }
+    return true;
+  }
+
+  // Convert RGB to 420.
+  // A negative height inverts the image.
+  bool ConvertRgb(const talk_base::MemoryStream* ms,
+                  uint32 fourcc, int32 width, int32 height,
+                  T* frame) {
+    int r_pos, g_pos, b_pos, bytes;
+    if (!GetRgbPacking(fourcc, &r_pos, &g_pos, &b_pos, &bytes)) {
+      return false;
+    }
+    int pitch = width * bytes;
+    const uint8* start = reinterpret_cast<const uint8*>(ms->GetBuffer());
+    if (height < 0) {
+      height = -height;
+      start = start + pitch * (height - 1);
+      pitch = -pitch;
+    }
+    frame->InitToBlack(width, height, 1, 1, 0, 0);
+    int stride_y = frame->GetYPitch();
+    int stride_u = frame->GetUPitch();
+    int stride_v = frame->GetVPitch();
+    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 * pitch + x * bytes;
+        rgb[1] = rgb[0] + ((x + 1) < width ? bytes : 0);
+        rgb[2] = rgb[0] + ((y + 1) < height ? pitch : 0);
+        rgb[3] = rgb[2] + ((x + 1) < width ? bytes : 0);
+        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()[stride_y * y + x] = yuv[0][0];
+        if ((x + 1) < width) {
+          frame->GetYPlane()[stride_y * y + x + 1] = yuv[1][0];
+        }
+        if ((y + 1) < height) {
+          frame->GetYPlane()[stride_y * (y + 1) + x] = yuv[2][0];
+          if ((x + 1) < width) {
+            frame->GetYPlane()[stride_y * (y + 1) + x + 1] = yuv[3][0];
+          }
+        }
+        frame->GetUPlane()[stride_u * (y / 2) + x / 2] =
+            (yuv[0][1] + yuv[1][1] + yuv[2][1] + yuv[3][1] + 2) / 4;
+        frame->GetVPlane()[stride_v * (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 GetYuv422Packing(uint32 fourcc,
+                        int* y1_pos, int* y2_pos, int* u_pos, int* v_pos) {
+    if (fourcc == cricket::FOURCC_YUY2) {
+      *y1_pos = 0; *u_pos = 1; *y2_pos = 2; *v_pos = 3;
+    } else if (fourcc == cricket::FOURCC_UYVY) {
+      *u_pos = 0; *y1_pos = 1; *v_pos = 2; *y2_pos = 3;
+    } else {
+      return false;
+    }
+    return true;
+  }
+
+  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 cricket::VideoFrame& frame) {
+    return !frame.GetYPlane();
+  }
+
+  static bool IsSize(const cricket::VideoFrame& frame,
+                     uint32 width, uint32 height) {
+    return !IsNull(frame) &&
+        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 IsEqual(const cricket::VideoFrame& 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) {
+    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 + 1) / 2, (height + 1) / 2, max_error) &&
+        IsPlaneEqual("v", frame.GetVPlane(), frame.GetVPitch(), v, vpitch,
+                     (width + 1) / 2, (height + 1) / 2, max_error);
+  }
+
+  static bool IsEqual(const cricket::VideoFrame& frame1,
+                      const cricket::VideoFrame& 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);
+  }
+
+  static bool IsEqualWithCrop(const cricket::VideoFrame& frame1,
+                              const cricket::VideoFrame& frame2,
+                              int hcrop, int vcrop, int max_error) {
+    return frame1.GetWidth() <= frame2.GetWidth() &&
+           frame1.GetHeight() <= frame2.GetHeight() &&
+           IsEqual(frame1,
+                   frame2.GetWidth() - hcrop * 2,
+                   frame2.GetHeight() - vcrop * 2,
+                   frame2.GetPixelWidth(), frame2.GetPixelHeight(),
+                   frame2.GetElapsedTime(), frame2.GetTimeStamp(),
+                   frame2.GetYPlane() + vcrop * frame2.GetYPitch()
+                       + hcrop,
+                   frame2.GetYPitch(),
+                   frame2.GetUPlane() + vcrop * frame2.GetUPitch() / 2
+                       + hcrop / 2,
+                   frame2.GetUPitch(),
+                   frame2.GetVPlane() + vcrop * frame2.GetVPitch() / 2
+                       + hcrop / 2,
+                   frame2.GetVPitch(),
+                   max_error);
+  }
+
+  static bool IsBlack(const cricket::VideoFrame& frame) {
+    return !IsNull(frame) &&
+        *frame.GetYPlane() == 16 &&
+        *frame.GetUPlane() == 128 &&
+        *frame.GetVPlane() == 128;
+  }
+
+  ////////////////////////
+  // Construction tests //
+  ////////////////////////
+
+  // Test constructing an image from a I420 buffer.
+  void ConstructI420() {
+    T frame;
+    EXPECT_TRUE(IsNull(frame));
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        CreateYuvSample(kWidth, kHeight, 12));
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_I420,
+                          kWidth, kHeight, &frame));
+
+    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 constructing an image from a YV12 buffer.
+  void ConstructYV12() {
+    T frame;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        CreateYuvSample(kWidth, kHeight, 12));
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YV12,
+                          kWidth, kHeight, &frame));
+
+    const uint8* y = reinterpret_cast<uint8*>(ms.get()->GetBuffer());
+    const uint8* v = y + kWidth * kHeight;
+    const uint8* u = v + kWidth * kHeight / 4;
+    EXPECT_TRUE(IsEqual(frame, kWidth, kHeight, 1, 1, 0, 0,
+                        y, kWidth, u, kWidth / 2, v, kWidth / 2, 0));
+  }
+
+  // Test constructing an image from a I422 buffer.
+  void ConstructI422() {
+    T frame1, frame2;
+    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
+    size_t buf_size = kWidth * kHeight * 2;
+    talk_base::scoped_array<uint8> buf(new uint8[buf_size + kAlignment]);
+    uint8* y = ALIGNP(buf.get(), kAlignment);
+    uint8* u = y + kWidth * kHeight;
+    uint8* v = u + (kWidth / 2) * kHeight;
+    EXPECT_EQ(0, libyuv::I420ToI422(frame1.GetYPlane(), frame1.GetYPitch(),
+                                    frame1.GetUPlane(), frame1.GetUPitch(),
+                                    frame1.GetVPlane(), frame1.GetVPitch(),
+                                    y, kWidth,
+                                    u, kWidth / 2,
+                                    v, kWidth / 2,
+                                    kWidth, kHeight));
+    EXPECT_TRUE(LoadFrame(y, buf_size, cricket::FOURCC_I422,
+                          kWidth, kHeight, &frame2));
+    EXPECT_TRUE(IsEqual(frame1, frame2, 0));
+  }
+
+  // Test constructing an image from a YUY2 buffer.
+  void ConstructYuy2() {
+    T frame1, frame2;
+    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
+    size_t buf_size = kWidth * kHeight * 2;
+    talk_base::scoped_array<uint8> buf(new uint8[buf_size + kAlignment]);
+    uint8* yuy2 = ALIGNP(buf.get(), kAlignment);
+    EXPECT_EQ(0, libyuv::I420ToYUY2(frame1.GetYPlane(), frame1.GetYPitch(),
+                                    frame1.GetUPlane(), frame1.GetUPitch(),
+                                    frame1.GetVPlane(), frame1.GetVPitch(),
+                                    yuy2, kWidth * 2,
+                                    kWidth, kHeight));
+    EXPECT_TRUE(LoadFrame(yuy2, buf_size, cricket::FOURCC_YUY2,
+                          kWidth, kHeight, &frame2));
+    EXPECT_TRUE(IsEqual(frame1, frame2, 0));
+  }
+
+  // Test constructing an image from a YUY2 buffer with buffer unaligned.
+  void ConstructYuy2Unaligned() {
+    T frame1, frame2;
+    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
+    size_t buf_size = kWidth * kHeight * 2;
+    talk_base::scoped_array<uint8> buf(new uint8[buf_size + kAlignment + 1]);
+    uint8* yuy2 = ALIGNP(buf.get(), kAlignment) + 1;
+    EXPECT_EQ(0, libyuv::I420ToYUY2(frame1.GetYPlane(), frame1.GetYPitch(),
+                                    frame1.GetUPlane(), frame1.GetUPitch(),
+                                    frame1.GetVPlane(), frame1.GetVPitch(),
+                                    yuy2, kWidth * 2,
+                                    kWidth, kHeight));
+    EXPECT_TRUE(LoadFrame(yuy2, buf_size, cricket::FOURCC_YUY2,
+                          kWidth, kHeight, &frame2));
+    EXPECT_TRUE(IsEqual(frame1, frame2, 0));
+  }
+
+  // Test constructing an image from a wide YUY2 buffer.
+  // Normal is 1280x720.  Wide is 12800x72
+  void ConstructYuy2Wide() {
+    T frame1, frame2;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        CreateYuv422Sample(cricket::FOURCC_YUY2, kWidth * 10, kHeight / 10));
+    ASSERT_TRUE(ms.get() != NULL);
+    EXPECT_TRUE(ConvertYuv422(ms.get(), cricket::FOURCC_YUY2,
+                              kWidth * 10, kHeight / 10,
+                              &frame1));
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUY2,
+                          kWidth * 10, kHeight / 10, &frame2));
+    EXPECT_TRUE(IsEqual(frame1, frame2, 0));
+  }
+
+  // Test constructing an image from a UYVY buffer.
+  void ConstructUyvy() {
+    T frame1, frame2;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        CreateYuv422Sample(cricket::FOURCC_UYVY, kWidth, kHeight));
+    ASSERT_TRUE(ms.get() != NULL);
+    EXPECT_TRUE(ConvertYuv422(ms.get(), cricket::FOURCC_UYVY, kWidth, kHeight,
+                              &frame1));
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_UYVY,
+                          kWidth, kHeight, &frame2));
+    EXPECT_TRUE(IsEqual(frame1, frame2, 0));
+  }
+
+  // Test constructing an image from a random buffer.
+  // We are merely verifying that the code succeeds and is free of crashes.
+  void ConstructM420() {
+    T frame;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        CreateYuvSample(kWidth, kHeight, 12));
+    ASSERT_TRUE(ms.get() != NULL);
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_M420,
+                          kWidth, kHeight, &frame));
+  }
+
+  void ConstructQ420() {
+    T frame;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        CreateYuvSample(kWidth, kHeight, 12));
+    ASSERT_TRUE(ms.get() != NULL);
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_Q420,
+                          kWidth, kHeight, &frame));
+  }
+
+  void ConstructNV21() {
+    T frame;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        CreateYuvSample(kWidth, kHeight, 12));
+    ASSERT_TRUE(ms.get() != NULL);
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_NV21,
+                          kWidth, kHeight, &frame));
+  }
+
+  void ConstructNV12() {
+    T frame;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        CreateYuvSample(kWidth, kHeight, 12));
+    ASSERT_TRUE(ms.get() != NULL);
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_NV12,
+                          kWidth, kHeight, &frame));
+  }
+
+  // Test constructing an image from a ABGR buffer
+  // Due to rounding, some pixels may differ slightly from the VideoFrame impl.
+  void ConstructABGR() {
+    T frame1, frame2;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        CreateRgbSample(cricket::FOURCC_ABGR, kWidth, kHeight));
+    ASSERT_TRUE(ms.get() != NULL);
+    EXPECT_TRUE(ConvertRgb(ms.get(), cricket::FOURCC_ABGR, kWidth, kHeight,
+                           &frame1));
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_ABGR,
+                          kWidth, kHeight, &frame2));
+    EXPECT_TRUE(IsEqual(frame1, frame2, 2));
+  }
+
+  // Test constructing an image from a ARGB buffer
+  // Due to rounding, some pixels may differ slightly from the VideoFrame impl.
+  void ConstructARGB() {
+    T frame1, frame2;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        CreateRgbSample(cricket::FOURCC_ARGB, kWidth, kHeight));
+    ASSERT_TRUE(ms.get() != NULL);
+    EXPECT_TRUE(ConvertRgb(ms.get(), cricket::FOURCC_ARGB, kWidth, kHeight,
+                           &frame1));
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_ARGB,
+                          kWidth, kHeight, &frame2));
+    EXPECT_TRUE(IsEqual(frame1, frame2, 2));
+  }
+
+  // Test constructing an image from a wide ARGB buffer
+  // Normal is 1280x720.  Wide is 12800x72
+  void ConstructARGBWide() {
+    T frame1, frame2;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        CreateRgbSample(cricket::FOURCC_ARGB, kWidth * 10, kHeight / 10));
+    ASSERT_TRUE(ms.get() != NULL);
+    EXPECT_TRUE(ConvertRgb(ms.get(), cricket::FOURCC_ARGB,
+                           kWidth * 10, kHeight / 10, &frame1));
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_ARGB,
+                          kWidth * 10, kHeight / 10, &frame2));
+    EXPECT_TRUE(IsEqual(frame1, frame2, 2));
+  }
+
+  // Test constructing an image from an BGRA buffer.
+  // Due to rounding, some pixels may differ slightly from the VideoFrame impl.
+  void ConstructBGRA() {
+    T frame1, frame2;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        CreateRgbSample(cricket::FOURCC_BGRA, kWidth, kHeight));
+    ASSERT_TRUE(ms.get() != NULL);
+    EXPECT_TRUE(ConvertRgb(ms.get(), cricket::FOURCC_BGRA, kWidth, kHeight,
+                           &frame1));
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_BGRA,
+                          kWidth, kHeight, &frame2));
+    EXPECT_TRUE(IsEqual(frame1, frame2, 2));
+  }
+
+  // Test constructing an image from a 24BG buffer.
+  // Due to rounding, some pixels may differ slightly from the VideoFrame impl.
+  void Construct24BG() {
+    T frame1, frame2;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        CreateRgbSample(cricket::FOURCC_24BG, kWidth, kHeight));
+    ASSERT_TRUE(ms.get() != NULL);
+    EXPECT_TRUE(ConvertRgb(ms.get(), cricket::FOURCC_24BG, kWidth, kHeight,
+                           &frame1));
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_24BG,
+                          kWidth, kHeight, &frame2));
+    EXPECT_TRUE(IsEqual(frame1, frame2, 2));
+  }
+
+  // Test constructing an image from a raw RGB buffer.
+  // Due to rounding, some pixels may differ slightly from the VideoFrame impl.
+  void ConstructRaw() {
+    T frame1, frame2;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        CreateRgbSample(cricket::FOURCC_RAW, kWidth, kHeight));
+    ASSERT_TRUE(ms.get() != NULL);
+    EXPECT_TRUE(ConvertRgb(ms.get(), cricket::FOURCC_RAW, kWidth, kHeight,
+                           &frame1));
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_RAW,
+                          kWidth, kHeight, &frame2));
+    EXPECT_TRUE(IsEqual(frame1, frame2, 2));
+  }
+
+  // Test constructing an image from a RGB565 buffer
+  void ConstructRGB565() {
+    T frame1, frame2;
+    size_t out_size = kWidth * kHeight * 2;
+    talk_base::scoped_array<uint8> outbuf(new uint8[out_size + kAlignment]);
+    uint8 *out = ALIGNP(outbuf.get(), kAlignment);
+    T frame;
+    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
+    EXPECT_EQ(out_size, frame1.ConvertToRgbBuffer(cricket::FOURCC_RGBP,
+                                                 out,
+                                                 out_size, kWidth * 2));
+    EXPECT_TRUE(LoadFrame(out, out_size, cricket::FOURCC_RGBP,
+                          kWidth, kHeight, &frame2));
+    EXPECT_TRUE(IsEqual(frame1, frame2, 20));
+  }
+
+  // Test constructing an image from a ARGB1555 buffer
+  void ConstructARGB1555() {
+    T frame1, frame2;
+    size_t out_size = kWidth * kHeight * 2;
+    talk_base::scoped_array<uint8> outbuf(new uint8[out_size + kAlignment]);
+    uint8 *out = ALIGNP(outbuf.get(), kAlignment);
+    T frame;
+    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
+    EXPECT_EQ(out_size, frame1.ConvertToRgbBuffer(cricket::FOURCC_RGBO,
+                                                 out,
+                                                 out_size, kWidth * 2));
+    EXPECT_TRUE(LoadFrame(out, out_size, cricket::FOURCC_RGBO,
+                          kWidth, kHeight, &frame2));
+    EXPECT_TRUE(IsEqual(frame1, frame2, 20));
+  }
+
+  // Test constructing an image from a ARGB4444 buffer
+  void ConstructARGB4444() {
+    T frame1, frame2;
+    size_t out_size = kWidth * kHeight * 2;
+    talk_base::scoped_array<uint8> outbuf(new uint8[out_size + kAlignment]);
+    uint8 *out = ALIGNP(outbuf.get(), kAlignment);
+    T frame;
+    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
+    EXPECT_EQ(out_size, frame1.ConvertToRgbBuffer(cricket::FOURCC_R444,
+                                                 out,
+                                                 out_size, kWidth * 2));
+    EXPECT_TRUE(LoadFrame(out, out_size, cricket::FOURCC_R444,
+                          kWidth, kHeight, &frame2));
+    EXPECT_TRUE(IsEqual(frame1, frame2, 20));
+  }
+
+  // Macro to help test different Bayer formats.
+  // Error threshold of 60 allows for Bayer format subsampling.
+  // TODO(fbarchard): Refactor this test to go from Bayer to I420 and
+  // back to bayer, which would be less lossy.
+  #define TEST_BYR(NAME, BAYER)                                                \
+  void NAME() {                                                                \
+    size_t bayer_size = kWidth * kHeight;                                      \
+    talk_base::scoped_array<uint8> bayerbuf(new uint8[                         \
+        bayer_size + kAlignment]);                                             \
+    uint8 *bayer = ALIGNP(bayerbuf.get(), kAlignment);                         \
+    T frame1, frame2;                                                          \
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(                         \
+        CreateRgbSample(cricket::FOURCC_ARGB, kWidth, kHeight));               \
+    ASSERT_TRUE(ms.get() != NULL);                                             \
+    libyuv::ARGBToBayer##BAYER(reinterpret_cast<uint8 *>(ms->GetBuffer()),     \
+                               kWidth * 4,                                     \
+                               bayer, kWidth,                                  \
+                               kWidth, kHeight);                               \
+    EXPECT_TRUE(LoadFrame(bayer, bayer_size, cricket::FOURCC_##BAYER,          \
+                          kWidth, kHeight,  &frame1));                         \
+    EXPECT_TRUE(ConvertRgb(ms.get(), cricket::FOURCC_ARGB, kWidth, kHeight,    \
+                           &frame2));                                          \
+    EXPECT_TRUE(IsEqual(frame1, frame2, 60));                                  \
+  }
+
+  // Test constructing an image from Bayer formats.
+  TEST_BYR(ConstructBayerGRBG, GRBG)
+  TEST_BYR(ConstructBayerGBRG, GBRG)
+  TEST_BYR(ConstructBayerBGGR, BGGR)
+  TEST_BYR(ConstructBayerRGGB, RGGB)
+
+
+// Macro to help test different rotations
+#define TEST_MIRROR(FOURCC, BPP)                                               \
+void Construct##FOURCC##Mirror() {                                             \
+    T frame1, frame2, frame3;                                                  \
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(                         \
+        CreateYuvSample(kWidth, kHeight, BPP));                                \
+    ASSERT_TRUE(ms.get() != NULL);                                             \
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_##FOURCC,                  \
+                          kWidth, -kHeight, kWidth, kHeight,                   \
+                          cricket::ROTATION_180, &frame1));                    \
+    size_t data_size;                                                          \
+    bool ret = ms->GetSize(&data_size);                                        \
+    EXPECT_TRUE(ret);                                                          \
+    EXPECT_TRUE(frame2.Init(cricket::FOURCC_##FOURCC,                          \
+                            kWidth, kHeight, kWidth, kHeight,                  \
+                            reinterpret_cast<uint8*>(ms->GetBuffer()),         \
+                            data_size,                                         \
+                            1, 1, 0, 0, 0));                                   \
+    int width_rotate = frame1.GetWidth();                                      \
+    int height_rotate = frame1.GetHeight();                                    \
+    EXPECT_TRUE(frame3.InitToBlack(width_rotate, height_rotate, 1, 1, 0, 0));  \
+    libyuv::I420Mirror(frame2.GetYPlane(), frame2.GetYPitch(),                 \
+                       frame2.GetUPlane(), frame2.GetUPitch(),                 \
+                       frame2.GetVPlane(), frame2.GetVPitch(),                 \
+                       frame3.GetYPlane(), frame3.GetYPitch(),                 \
+                       frame3.GetUPlane(), frame3.GetUPitch(),                 \
+                       frame3.GetVPlane(), frame3.GetVPitch(),                 \
+                       kWidth, kHeight);                                       \
+    EXPECT_TRUE(IsEqual(frame1, frame3, 0));                                   \
+  }
+
+  TEST_MIRROR(I420, 420)
+
+// Macro to help test different rotations
+#define TEST_ROTATE(FOURCC, BPP, ROTATE)                                       \
+void Construct##FOURCC##Rotate##ROTATE() {                                     \
+    T frame1, frame2, frame3;                                                  \
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(                         \
+        CreateYuvSample(kWidth, kHeight, BPP));                                \
+    ASSERT_TRUE(ms.get() != NULL);                                             \
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_##FOURCC,                  \
+                          kWidth, kHeight, kWidth, kHeight,                    \
+                          cricket::ROTATION_##ROTATE, &frame1));               \
+    size_t data_size;                                                          \
+    bool ret = ms->GetSize(&data_size);                                        \
+    EXPECT_TRUE(ret);                                                          \
+    EXPECT_TRUE(frame2.Init(cricket::FOURCC_##FOURCC,                          \
+                            kWidth, kHeight, kWidth, kHeight,                  \
+                            reinterpret_cast<uint8*>(ms->GetBuffer()),         \
+                            data_size,                                         \
+                            1, 1, 0, 0, 0));                                   \
+    int width_rotate = frame1.GetWidth();                                      \
+    int height_rotate = frame1.GetHeight();                                    \
+    EXPECT_TRUE(frame3.InitToBlack(width_rotate, height_rotate, 1, 1, 0, 0));  \
+    libyuv::I420Rotate(frame2.GetYPlane(), frame2.GetYPitch(),                 \
+                       frame2.GetUPlane(), frame2.GetUPitch(),                 \
+                       frame2.GetVPlane(), frame2.GetVPitch(),                 \
+                       frame3.GetYPlane(), frame3.GetYPitch(),                 \
+                       frame3.GetUPlane(), frame3.GetUPitch(),                 \
+                       frame3.GetVPlane(), frame3.GetVPitch(),                 \
+                       kWidth, kHeight, libyuv::kRotate##ROTATE);              \
+    EXPECT_TRUE(IsEqual(frame1, frame3, 0));                                   \
+  }
+
+  // Test constructing an image with rotation.
+  TEST_ROTATE(I420, 12, 0)
+  TEST_ROTATE(I420, 12, 90)
+  TEST_ROTATE(I420, 12, 180)
+  TEST_ROTATE(I420, 12, 270)
+  TEST_ROTATE(YV12, 12, 0)
+  TEST_ROTATE(YV12, 12, 90)
+  TEST_ROTATE(YV12, 12, 180)
+  TEST_ROTATE(YV12, 12, 270)
+  TEST_ROTATE(NV12, 12, 0)
+  TEST_ROTATE(NV12, 12, 90)
+  TEST_ROTATE(NV12, 12, 180)
+  TEST_ROTATE(NV12, 12, 270)
+  TEST_ROTATE(NV21, 12, 0)
+  TEST_ROTATE(NV21, 12, 90)
+  TEST_ROTATE(NV21, 12, 180)
+  TEST_ROTATE(NV21, 12, 270)
+  TEST_ROTATE(UYVY, 16, 0)
+  TEST_ROTATE(UYVY, 16, 90)
+  TEST_ROTATE(UYVY, 16, 180)
+  TEST_ROTATE(UYVY, 16, 270)
+  TEST_ROTATE(YUY2, 16, 0)
+  TEST_ROTATE(YUY2, 16, 90)
+  TEST_ROTATE(YUY2, 16, 180)
+  TEST_ROTATE(YUY2, 16, 270)
+
+  // Test constructing an image from a UYVY buffer rotated 90 degrees.
+  void ConstructUyvyRotate90() {
+    T frame2;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        CreateYuv422Sample(cricket::FOURCC_UYVY, kWidth, kHeight));
+    ASSERT_TRUE(ms.get() != NULL);
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_UYVY,
+                          kWidth, kHeight, kWidth, kHeight,
+                          cricket::ROTATION_90, &frame2));
+  }
+
+  // Test constructing an image from a UYVY buffer rotated 180 degrees.
+  void ConstructUyvyRotate180() {
+    T frame2;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        CreateYuv422Sample(cricket::FOURCC_UYVY, kWidth, kHeight));
+    ASSERT_TRUE(ms.get() != NULL);
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_UYVY,
+                          kWidth, kHeight, kWidth, kHeight,
+                          cricket::ROTATION_180, &frame2));
+  }
+
+  // Test constructing an image from a UYVY buffer rotated 270 degrees.
+  void ConstructUyvyRotate270() {
+    T frame2;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        CreateYuv422Sample(cricket::FOURCC_UYVY, kWidth, kHeight));
+    ASSERT_TRUE(ms.get() != NULL);
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_UYVY,
+                          kWidth, kHeight, kWidth, kHeight,
+                          cricket::ROTATION_270, &frame2));
+  }
+
+  // Test constructing an image from a YUY2 buffer rotated 90 degrees.
+  void ConstructYuy2Rotate90() {
+    T frame2;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        CreateYuv422Sample(cricket::FOURCC_YUY2, kWidth, kHeight));
+    ASSERT_TRUE(ms.get() != NULL);
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUY2,
+                          kWidth, kHeight, kWidth, kHeight,
+                          cricket::ROTATION_90, &frame2));
+  }
+
+  // Test constructing an image from a YUY2 buffer rotated 180 degrees.
+  void ConstructYuy2Rotate180() {
+    T frame2;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        CreateYuv422Sample(cricket::FOURCC_YUY2, kWidth, kHeight));
+    ASSERT_TRUE(ms.get() != NULL);
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUY2,
+                          kWidth, kHeight, kWidth, kHeight,
+                          cricket::ROTATION_180, &frame2));
+  }
+
+  // Test constructing an image from a YUY2 buffer rotated 270 degrees.
+  void ConstructYuy2Rotate270() {
+    T frame2;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        CreateYuv422Sample(cricket::FOURCC_YUY2, kWidth, kHeight));
+    ASSERT_TRUE(ms.get() != NULL);
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUY2,
+                          kWidth, kHeight, kWidth, kHeight,
+                          cricket::ROTATION_270, &frame2));
+  }
+
+  // Test 1 pixel edge case image I420 buffer.
+  void ConstructI4201Pixel() {
+    T frame;
+    uint8 pixel[3] = { 1, 2, 3 };
+    for (int i = 0; i < repeat_; ++i) {
+      EXPECT_TRUE(frame.Init(cricket::FOURCC_I420, 1, 1, 1, 1,
+                             pixel, sizeof(pixel),
+                             1, 1, 0, 0, 0));
+    }
+    const uint8* y = pixel;
+    const uint8* u = y + 1;
+    const uint8* v = u + 1;
+    EXPECT_TRUE(IsEqual(frame, 1, 1, 1, 1, 0, 0,
+                        y, 1, u, 1, v, 1, 0));
+  }
+
+  // Test 5 pixel edge case image I420 buffer rounds down to 4.
+  void ConstructI4205Pixel() {
+    T frame;
+    uint8 pixels5x5[5 * 5 + ((5 + 1) / 2 * (5 + 1) / 2) *  2];
+    memset(pixels5x5, 1, 5 * 5 + ((5 + 1) / 2 * (5 + 1) / 2) *  2);
+    for (int i = 0; i < repeat_; ++i) {
+      EXPECT_TRUE(frame.Init(cricket::FOURCC_I420, 5, 5, 5, 5,
+                             pixels5x5, sizeof(pixels5x5),
+                             1, 1, 0, 0, 0));
+    }
+    EXPECT_EQ(4u, frame.GetWidth());
+    EXPECT_EQ(4u, frame.GetHeight());
+    EXPECT_EQ(4, frame.GetYPitch());
+    EXPECT_EQ(2, frame.GetUPitch());
+    EXPECT_EQ(2, frame.GetVPitch());
+  }
+
+  // Test 1 pixel edge case image ARGB buffer.
+  void ConstructARGB1Pixel() {
+    T frame;
+    uint8 pixel[4] = { 64, 128, 192, 255 };
+    for (int i = 0; i < repeat_; ++i) {
+      EXPECT_TRUE(frame.Init(cricket::FOURCC_ARGB, 1, 1, 1, 1,
+                             pixel, sizeof(pixel),
+                             1, 1, 0, 0, 0));
+    }
+    // Convert back to ARGB.
+    size_t out_size = 4;
+    talk_base::scoped_array<uint8> outbuf(new uint8[out_size + kAlignment]);
+    uint8 *out = ALIGNP(outbuf.get(), kAlignment);
+
+    EXPECT_EQ(out_size, frame.ConvertToRgbBuffer(cricket::FOURCC_ARGB,
+                                                 out,
+                                                 out_size,    // buffer size
+                                                 out_size));  // stride
+  #ifdef USE_LMI_CONVERT
+    // TODO(fbarchard): Expected to fail, but not crash.
+    EXPECT_FALSE(IsPlaneEqual("argb", pixel, 4, out, 4, 3, 1, 2));
+  #else
+    // TODO(fbarchard): Check for overwrite.
+    EXPECT_TRUE(IsPlaneEqual("argb", pixel, 4, out, 4, 3, 1, 2));
+  #endif
+  }
+
+  // Test Black, White and Grey pixels.
+  void ConstructARGBBlackWhitePixel() {
+    T frame;
+    uint8 pixel[10 * 4] = { 0, 0, 0, 255,  // Black.
+                            0, 0, 0, 255,
+                            64, 64, 64, 255,  // Dark Grey.
+                            64, 64, 64, 255,
+                            128, 128, 128, 255,  // Grey.
+                            128, 128, 128, 255,
+                            196, 196, 196, 255,  // Light Grey.
+                            196, 196, 196, 255,
+                            255, 255, 255, 255,  // White.
+                            255, 255, 255, 255 };
+
+    for (int i = 0; i < repeat_; ++i) {
+      EXPECT_TRUE(frame.Init(cricket::FOURCC_ARGB, 10, 1, 10, 1,
+                             pixel, sizeof(pixel),
+                             1, 1, 0, 0, 0));
+    }
+    // Convert back to ARGB
+    size_t out_size = 10 * 4;
+    talk_base::scoped_array<uint8> outbuf(new uint8[out_size + kAlignment]);
+    uint8 *out = ALIGNP(outbuf.get(), kAlignment);
+
+    EXPECT_EQ(out_size, frame.ConvertToRgbBuffer(cricket::FOURCC_ARGB,
+                                                 out,
+                                                 out_size,    // buffer size.
+                                                 out_size));  // stride.
+    EXPECT_TRUE(IsPlaneEqual("argb", pixel, out_size,
+                             out, out_size,
+                             out_size, 1, 2));
+  }
+
+  // Test constructing an image from an I420 buffer with horizontal cropping.
+  void ConstructI420CropHorizontal() {
+    T frame1, frame2;
+    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
+    ASSERT_TRUE(LoadFrame(kImageFilename, cricket::FOURCC_I420, kWidth, kHeight,
+                          kWidth * 3 / 4, kHeight, 0, &frame2));
+    EXPECT_TRUE(IsEqualWithCrop(frame2, frame1, kWidth / 8, 0, 0));
+  }
+
+  // Test constructing an image from a YUY2 buffer with horizontal cropping.
+  void ConstructYuy2CropHorizontal() {
+    T frame1, frame2;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        CreateYuv422Sample(cricket::FOURCC_YUY2, kWidth, kHeight));
+    ASSERT_TRUE(ms.get() != NULL);
+    EXPECT_TRUE(ConvertYuv422(ms.get(), cricket::FOURCC_YUY2, kWidth, kHeight,
+                              &frame1));
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUY2, kWidth, kHeight,
+                          kWidth * 3 / 4, kHeight, 0, &frame2));
+    EXPECT_TRUE(IsEqualWithCrop(frame2, frame1, kWidth / 8, 0, 0));
+  }
+
+  // Test constructing an image from an ARGB buffer with horizontal cropping.
+  void ConstructARGBCropHorizontal() {
+    T frame1, frame2;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        CreateRgbSample(cricket::FOURCC_ARGB, kWidth, kHeight));
+    ASSERT_TRUE(ms.get() != NULL);
+    EXPECT_TRUE(ConvertRgb(ms.get(), cricket::FOURCC_ARGB, kWidth, kHeight,
+                           &frame1));
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_ARGB, kWidth, kHeight,
+                          kWidth * 3 / 4, kHeight, 0, &frame2));
+    EXPECT_TRUE(IsEqualWithCrop(frame2, frame1, kWidth / 8, 0, 2));
+  }
+
+  // Test constructing an image from an I420 buffer, cropping top and bottom.
+  void ConstructI420CropVertical() {
+    T frame1, frame2;
+    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
+    ASSERT_TRUE(LoadFrame(kImageFilename, cricket::FOURCC_I420, kWidth, kHeight,
+                          kWidth, kHeight * 3 / 4, 0, &frame2));
+    EXPECT_TRUE(IsEqualWithCrop(frame2, frame1, 0, kHeight / 8, 0));
+  }
+
+  // Test constructing an image from I420 synonymous formats.
+  void ConstructI420Aliases() {
+    T frame1, frame2, frame3;
+    ASSERT_TRUE(LoadFrame(kImageFilename, cricket::FOURCC_I420, kWidth, kHeight,
+                          &frame1));
+    ASSERT_TRUE(LoadFrame(kImageFilename, cricket::FOURCC_IYUV, kWidth, kHeight,
+                          &frame2));
+    ASSERT_TRUE(LoadFrame(kImageFilename, cricket::FOURCC_YU12, kWidth, kHeight,
+                          &frame3));
+    EXPECT_TRUE(IsEqual(frame1, frame2, 0));
+    EXPECT_TRUE(IsEqual(frame1, frame3, 0));
+  }
+
+  // Test constructing an image from an I420 MJPG buffer.
+  void ConstructMjpgI420() {
+    T frame1, frame2;
+    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
+    ASSERT_TRUE(LoadFrame(kJpeg420Filename,
+                          cricket::FOURCC_MJPG, kWidth, kHeight, &frame2));
+    EXPECT_TRUE(IsEqual(frame1, frame2, 32));
+  }
+
+  // Test constructing an image from an I422 MJPG buffer.
+  void ConstructMjpgI422() {
+    T frame1, frame2;
+    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
+    ASSERT_TRUE(LoadFrame(kJpeg422Filename,
+                          cricket::FOURCC_MJPG, kWidth, kHeight, &frame2));
+    EXPECT_TRUE(IsEqual(frame1, frame2, 32));
+  }
+
+  // Test constructing an image from an I444 MJPG buffer.
+  void ConstructMjpgI444() {
+    T frame1, frame2;
+    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
+    ASSERT_TRUE(LoadFrame(kJpeg444Filename,
+                          cricket::FOURCC_MJPG, kWidth, kHeight, &frame2));
+    EXPECT_TRUE(IsEqual(frame1, frame2, 32));
+  }
+
+  // Test constructing an image from an I444 MJPG buffer.
+  void ConstructMjpgI411() {
+    T frame1, frame2;
+    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
+    ASSERT_TRUE(LoadFrame(kJpeg411Filename,
+                          cricket::FOURCC_MJPG, kWidth, kHeight, &frame2));
+    EXPECT_TRUE(IsEqual(frame1, frame2, 32));
+  }
+
+  // Test constructing an image from an I400 MJPG buffer.
+  // TODO(fbarchard): Stronger compare on chroma.  Compare agaisnt a grey image.
+  void ConstructMjpgI400() {
+    T frame1, frame2;
+    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
+    ASSERT_TRUE(LoadFrame(kJpeg400Filename,
+                          cricket::FOURCC_MJPG, kWidth, kHeight, &frame2));
+    EXPECT_TRUE(IsPlaneEqual("y", frame1.GetYPlane(), frame1.GetYPitch(),
+                             frame2.GetYPlane(), frame2.GetYPitch(),
+                             kWidth, kHeight, 32));
+    EXPECT_TRUE(IsEqual(frame1, frame2, 128));
+  }
+
+  // Test constructing an image from an I420 MJPG buffer.
+  void ValidateFrame(const char* name, uint32 fourcc, int data_adjust,
+                     int size_adjust, bool expected_result) {
+    T frame;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(LoadSample(name));
+    const uint8* sample = reinterpret_cast<const uint8*>(ms.get()->GetBuffer());
+    size_t sample_size;
+    ms->GetSize(&sample_size);
+    // Optional adjust size to test invalid size.
+    size_t data_size = sample_size + data_adjust;
+
+    // Allocate a buffer with end page aligned.
+    const int kPadToHeapSized = 16 * 1024 * 1024;
+    talk_base::scoped_array<uint8> page_buffer(
+        new uint8[((data_size + kPadToHeapSized + 4095) & ~4095)]);
+    uint8* data_ptr = page_buffer.get();
+    if (!data_ptr) {
+      LOG(LS_WARNING) << "Failed to allocate memory for ValidateFrame test.";
+      EXPECT_FALSE(expected_result);  // NULL is okay if failure was expected.
+      return;
+    }
+    data_ptr += kPadToHeapSized + (-(static_cast<int>(data_size)) & 4095);
+    memcpy(data_ptr, sample, talk_base::_min(data_size, sample_size));
+    for (int i = 0; i < repeat_; ++i) {
+      EXPECT_EQ(expected_result, frame.Validate(fourcc, kWidth, kHeight,
+                                                data_ptr,
+                                                sample_size + size_adjust));
+    }
+  }
+
+  // Test validate for I420 MJPG buffer.
+  void ValidateMjpgI420() {
+    ValidateFrame(kJpeg420Filename, cricket::FOURCC_MJPG, 0, 0, true);
+  }
+
+  // Test validate for I422 MJPG buffer.
+  void ValidateMjpgI422() {
+    ValidateFrame(kJpeg422Filename, cricket::FOURCC_MJPG, 0, 0, true);
+  }
+
+  // Test validate for I444 MJPG buffer.
+  void ValidateMjpgI444() {
+    ValidateFrame(kJpeg444Filename, cricket::FOURCC_MJPG, 0, 0, true);
+  }
+
+  // Test validate for I411 MJPG buffer.
+  void ValidateMjpgI411() {
+    ValidateFrame(kJpeg411Filename, cricket::FOURCC_MJPG, 0, 0, true);
+  }
+
+  // Test validate for I400 MJPG buffer.
+  void ValidateMjpgI400() {
+    ValidateFrame(kJpeg400Filename, cricket::FOURCC_MJPG, 0, 0, true);
+  }
+
+  // Test validate for I420 buffer.
+  void ValidateI420() {
+    ValidateFrame(kImageFilename, cricket::FOURCC_I420, 0, 0, true);
+  }
+
+  // Test validate for I420 buffer where size is too small
+  void ValidateI420SmallSize() {
+    ValidateFrame(kImageFilename, cricket::FOURCC_I420, 0, -16384, false);
+  }
+
+  // Test validate for I420 buffer where size is too large (16 MB)
+  // Will produce warning but pass.
+  void ValidateI420LargeSize() {
+    ValidateFrame(kImageFilename, cricket::FOURCC_I420, 16000000, 16000000,
+                  true);
+  }
+
+  // Test validate for I420 buffer where size is 1 GB (not reasonable).
+  void ValidateI420HugeSize() {
+#ifndef WIN32  // TODO(fbarchard): Reenable when fixing bug 9603762.
+    ValidateFrame(kImageFilename, cricket::FOURCC_I420, 1000000000u,
+                  1000000000u, false);
+#endif
+  }
+
+  // The following test that Validate crashes if the size is greater than the
+  // actual buffer size.
+  // TODO(fbarchard): Consider moving a filter into the capturer/plugin.
+#if defined(_MSC_VER) && defined(_DEBUG)
+  int ExceptionFilter(unsigned int code, struct _EXCEPTION_POINTERS *ep) {
+    if (code == EXCEPTION_ACCESS_VIOLATION) {
+      LOG(LS_INFO) << "Caught EXCEPTION_ACCESS_VIOLATION as expected.";
+      return EXCEPTION_EXECUTE_HANDLER;
+    } else {
+      LOG(LS_INFO) << "Did not catch EXCEPTION_ACCESS_VIOLATION.  Unexpected.";
+      return EXCEPTION_CONTINUE_SEARCH;
+    }
+  }
+
+  // Test validate fails for truncated MJPG data buffer.  If ValidateFrame
+  // crashes the exception handler will return and unittest passes with OK.
+  void ValidateMjpgI420InvalidSize() {
+    __try {
+      ValidateFrame(kJpeg420Filename, cricket::FOURCC_MJPG, -16384, 0, false);
+      FAIL() << "Validate was expected to cause EXCEPTION_ACCESS_VIOLATION.";
+    } __except(ExceptionFilter(GetExceptionCode(), GetExceptionInformation())) {
+      return;  // Successfully crashed in ValidateFrame.
+    }
+  }
+
+  // Test validate fails for truncated I420 buffer.
+  void ValidateI420InvalidSize() {
+    __try {
+      ValidateFrame(kImageFilename, cricket::FOURCC_I420, -16384, 0, false);
+      FAIL() << "Validate was expected to cause EXCEPTION_ACCESS_VIOLATION.";
+    } __except(ExceptionFilter(GetExceptionCode(), GetExceptionInformation())) {
+      return;  // Successfully crashed in ValidateFrame.
+    }
+  }
+#endif
+
+  // Test constructing an image from a YUY2 buffer (and synonymous formats).
+  void ConstructYuy2Aliases() {
+    T frame1, frame2, frame3, frame4;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        CreateYuv422Sample(cricket::FOURCC_YUY2, kWidth, kHeight));
+    ASSERT_TRUE(ms.get() != NULL);
+    EXPECT_TRUE(ConvertYuv422(ms.get(), cricket::FOURCC_YUY2, kWidth, kHeight,
+                              &frame1));
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUY2,
+                          kWidth, kHeight, &frame2));
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUVS,
+                          kWidth, kHeight, &frame3));
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUYV,
+                          kWidth, kHeight, &frame4));
+    EXPECT_TRUE(IsEqual(frame1, frame2, 0));
+    EXPECT_TRUE(IsEqual(frame1, frame3, 0));
+    EXPECT_TRUE(IsEqual(frame1, frame4, 0));
+  }
+
+  // Test constructing an image from a UYVY buffer (and synonymous formats).
+  void ConstructUyvyAliases() {
+    T frame1, frame2, frame3, frame4;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        CreateYuv422Sample(cricket::FOURCC_UYVY, kWidth, kHeight));
+    ASSERT_TRUE(ms.get() != NULL);
+    EXPECT_TRUE(ConvertYuv422(ms.get(), cricket::FOURCC_UYVY, kWidth, kHeight,
+                              &frame1));
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_UYVY,
+                          kWidth, kHeight, &frame2));
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_2VUY,
+                          kWidth, kHeight, &frame3));
+    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_HDYC,
+                          kWidth, kHeight, &frame4));
+    EXPECT_TRUE(IsEqual(frame1, frame2, 0));
+    EXPECT_TRUE(IsEqual(frame1, frame3, 0));
+    EXPECT_TRUE(IsEqual(frame1, frame4, 0));
+  }
+
+  // Test creating a copy.
+  void ConstructCopy() {
+    T frame1, frame2;
+    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
+    for (int i = 0; i < repeat_; ++i) {
+      EXPECT_TRUE(frame2.Init(frame1));
+    }
+    EXPECT_TRUE(IsEqual(frame1, frame2, 0));
+  }
+
+  // Test creating a copy and check that it just increments the refcount.
+  void ConstructCopyIsRef() {
+    T frame1, frame2;
+    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
+    for (int i = 0; i < repeat_; ++i) {
+      EXPECT_TRUE(frame2.Init(frame1));
+    }
+    EXPECT_TRUE(IsEqual(frame1, frame2, 0));
+    EXPECT_EQ(frame1.GetYPlane(), frame2.GetYPlane());
+    EXPECT_EQ(frame1.GetUPlane(), frame2.GetUPlane());
+    EXPECT_EQ(frame1.GetVPlane(), frame2.GetVPlane());
+  }
+
+  // Test creating an empty image and initing it to black.
+  void ConstructBlack() {
+    T 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_TRUE(IsBlack(frame));
+  }
+
+  // Test constructing an image from a YUY2 buffer with a range of sizes.
+  // Only tests that conversion does not crash or corrupt heap.
+  void ConstructYuy2AllSizes() {
+    T frame1, frame2;
+    for (int height = kMinHeightAll; height <= kMaxHeightAll; ++height) {
+      for (int width = kMinWidthAll; width <= kMaxWidthAll; ++width) {
+        talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+            CreateYuv422Sample(cricket::FOURCC_YUY2, width, height));
+        ASSERT_TRUE(ms.get() != NULL);
+        EXPECT_TRUE(ConvertYuv422(ms.get(), cricket::FOURCC_YUY2, width, height,
+                                  &frame1));
+        EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUY2,
+                              width, height, &frame2));
+        EXPECT_TRUE(IsEqual(frame1, frame2, 0));
+      }
+    }
+  }
+
+  // Test constructing an image from a ARGB buffer with a range of sizes.
+  // Only tests that conversion does not crash or corrupt heap.
+  void ConstructARGBAllSizes() {
+    T frame1, frame2;
+    for (int height = kMinHeightAll; height <= kMaxHeightAll; ++height) {
+      for (int width = kMinWidthAll; width <= kMaxWidthAll; ++width) {
+        talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+            CreateRgbSample(cricket::FOURCC_ARGB, width, height));
+        ASSERT_TRUE(ms.get() != NULL);
+        EXPECT_TRUE(ConvertRgb(ms.get(), cricket::FOURCC_ARGB, width, height,
+                               &frame1));
+        EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_ARGB,
+                              width, height, &frame2));
+        EXPECT_TRUE(IsEqual(frame1, frame2, 64));
+      }
+    }
+    // Test a practical window size for screencasting usecase.
+    const int kOddWidth = 1228;
+    const int kOddHeight = 260;
+    for (int j = 0; j < 2; ++j) {
+      for (int i = 0; i < 2; ++i) {
+        talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        CreateRgbSample(cricket::FOURCC_ARGB, kOddWidth + i, kOddHeight + j));
+        ASSERT_TRUE(ms.get() != NULL);
+        EXPECT_TRUE(ConvertRgb(ms.get(), cricket::FOURCC_ARGB,
+                               kOddWidth + i, kOddHeight + j,
+                               &frame1));
+        EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_ARGB,
+                              kOddWidth + i, kOddHeight + j, &frame2));
+        EXPECT_TRUE(IsEqual(frame1, frame2, 64));
+      }
+    }
+  }
+
+  // Tests re-initing an existing image.
+  void Reset() {
+    T frame1, frame2;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        LoadSample(kImageFilename));
+    size_t data_size;
+    ms->GetSize(&data_size);
+    EXPECT_TRUE(frame1.InitToBlack(kWidth, kHeight, 1, 1, 0, 0));
+    EXPECT_TRUE(frame2.InitToBlack(kWidth, kHeight, 1, 1, 0, 0));
+    EXPECT_TRUE(IsBlack(frame1));
+    EXPECT_TRUE(IsEqual(frame1, frame2, 0));
+    EXPECT_TRUE(frame1.Reset(cricket::FOURCC_I420,
+                             kWidth, kHeight, kWidth, kHeight,
+                             reinterpret_cast<uint8*>(ms->GetBuffer()),
+                             data_size, 1, 1, 0, 0, 0));
+    EXPECT_FALSE(IsBlack(frame1));
+    EXPECT_FALSE(IsEqual(frame1, frame2, 0));
+  }
+
+  //////////////////////
+  // Conversion tests //
+  //////////////////////
+
+  enum ToFrom { TO, FROM };
+
+  // Helper function for test converting from I420 to packed formats.
+  inline void ConvertToBuffer(int bpp, int rowpad, bool invert, ToFrom to_from,
+      int error, uint32 fourcc,
+      int (*RGBToI420)(const uint8* src_frame, int src_stride_frame,
+                       uint8* dst_y, int dst_stride_y,
+                       uint8* dst_u, int dst_stride_u,
+                       uint8* dst_v, int dst_stride_v,
+                       int width, int height)) {
+    T frame1, frame2;
+    int repeat_to = (to_from == TO) ? repeat_ : 1;
+    int repeat_from  = (to_from == FROM) ? repeat_ : 1;
+
+    int astride = kWidth * bpp + rowpad;
+    size_t out_size = astride * kHeight;
+    talk_base::scoped_array<uint8> outbuf(new uint8[out_size + kAlignment + 1]);
+    memset(outbuf.get(), 0, out_size + kAlignment + 1);
+    uint8 *outtop = ALIGNP(outbuf.get(), kAlignment);
+    uint8 *out = outtop;
+    int stride = astride;
+    if (invert) {
+      out += (kHeight - 1) * stride;  // Point to last row.
+      stride = -stride;
+    }
+    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
+
+    for (int i = 0; i < repeat_to; ++i) {
+      EXPECT_EQ(out_size, frame1.ConvertToRgbBuffer(fourcc,
+                                                    out,
+                                                    out_size, stride));
+    }
+    EXPECT_TRUE(frame2.InitToBlack(kWidth, kHeight, 1, 1, 0, 0));
+    for (int i = 0; i < repeat_from; ++i) {
+      EXPECT_EQ(0, RGBToI420(out, stride,
+                             frame2.GetYPlane(), frame2.GetYPitch(),
+                             frame2.GetUPlane(), frame2.GetUPitch(),
+                             frame2.GetVPlane(), frame2.GetVPitch(),
+                             kWidth, kHeight));
+    }
+    if (rowpad) {
+      EXPECT_EQ(0, outtop[kWidth * bpp]);  // Ensure stride skipped end of row.
+      EXPECT_NE(0, outtop[astride]);       // Ensure pixel at start of 2nd row.
+    } else {
+      EXPECT_NE(0, outtop[kWidth * bpp]);  // Expect something to be here.
+    }
+    EXPECT_EQ(0, outtop[out_size]);      // Ensure no overrun.
+    EXPECT_TRUE(IsEqual(frame1, frame2, error));
+  }
+
+  static const int kError = 20;
+  static const int kErrorHigh = 40;
+  static const int kOddStride = 23;
+
+  // Tests ConvertToRGBBuffer formats.
+  void ConvertToARGBBuffer() {
+    ConvertToBuffer(4, 0, false, TO, kError,
+                    cricket::FOURCC_ARGB, libyuv::ARGBToI420);
+  }
+  void ConvertToBGRABuffer() {
+    ConvertToBuffer(4, 0, false, TO, kError,
+                    cricket::FOURCC_BGRA, libyuv::BGRAToI420);
+  }
+  void ConvertToABGRBuffer() {
+    ConvertToBuffer(4, 0, false, TO, kError,
+                    cricket::FOURCC_ABGR, libyuv::ABGRToI420);
+  }
+  void ConvertToRGB24Buffer() {
+    ConvertToBuffer(3, 0, false, TO, kError,
+                    cricket::FOURCC_24BG, libyuv::RGB24ToI420);
+  }
+  void ConvertToRAWBuffer() {
+    ConvertToBuffer(3, 0, false, TO, kError,
+                    cricket::FOURCC_RAW, libyuv::RAWToI420);
+  }
+  void ConvertToRGB565Buffer() {
+    ConvertToBuffer(2, 0, false, TO, kError,
+                    cricket::FOURCC_RGBP, libyuv::RGB565ToI420);
+  }
+  void ConvertToARGB1555Buffer() {
+    ConvertToBuffer(2, 0, false, TO, kError,
+                    cricket::FOURCC_RGBO, libyuv::ARGB1555ToI420);
+  }
+  void ConvertToARGB4444Buffer() {
+    ConvertToBuffer(2, 0, false, TO, kError,
+                    cricket::FOURCC_R444, libyuv::ARGB4444ToI420);
+  }
+  void ConvertToBayerBGGRBuffer() {
+    ConvertToBuffer(1, 0, false, TO, kErrorHigh,
+                    cricket::FOURCC_BGGR, libyuv::BayerBGGRToI420);
+  }
+  void ConvertToBayerGBRGBuffer() {
+    ConvertToBuffer(1, 0, false, TO, kErrorHigh,
+                    cricket::FOURCC_GBRG, libyuv::BayerGBRGToI420);
+  }
+  void ConvertToBayerGRBGBuffer() {
+    ConvertToBuffer(1, 0, false, TO, kErrorHigh,
+                    cricket::FOURCC_GRBG, libyuv::BayerGRBGToI420);
+  }
+  void ConvertToBayerRGGBBuffer() {
+    ConvertToBuffer(1, 0, false, TO, kErrorHigh,
+                    cricket::FOURCC_RGGB, libyuv::BayerRGGBToI420);
+  }
+  void ConvertToI400Buffer() {
+    ConvertToBuffer(1, 0, false, TO, 128,
+                    cricket::FOURCC_I400, libyuv::I400ToI420);
+  }
+  void ConvertToYUY2Buffer() {
+    ConvertToBuffer(2, 0, false, TO, kError,
+                    cricket::FOURCC_YUY2, libyuv::YUY2ToI420);
+  }
+  void ConvertToUYVYBuffer() {
+    ConvertToBuffer(2, 0, false, TO, kError,
+                    cricket::FOURCC_UYVY, libyuv::UYVYToI420);
+  }
+
+  // Tests ConvertToRGBBuffer formats with odd stride.
+  void ConvertToARGBBufferStride() {
+    ConvertToBuffer(4, kOddStride, false, TO, kError,
+                    cricket::FOURCC_ARGB, libyuv::ARGBToI420);
+  }
+  void ConvertToBGRABufferStride() {
+    ConvertToBuffer(4, kOddStride, false, TO, kError,
+                    cricket::FOURCC_BGRA, libyuv::BGRAToI420);
+  }
+  void ConvertToABGRBufferStride() {
+    ConvertToBuffer(4, kOddStride, false, TO, kError,
+                    cricket::FOURCC_ABGR, libyuv::ABGRToI420);
+  }
+  void ConvertToRGB24BufferStride() {
+    ConvertToBuffer(3, kOddStride, false, TO, kError,
+                    cricket::FOURCC_24BG, libyuv::RGB24ToI420);
+  }
+  void ConvertToRAWBufferStride() {
+    ConvertToBuffer(3, kOddStride, false, TO, kError,
+                    cricket::FOURCC_RAW, libyuv::RAWToI420);
+  }
+  void ConvertToRGB565BufferStride() {
+    ConvertToBuffer(2, kOddStride, false, TO, kError,
+                    cricket::FOURCC_RGBP, libyuv::RGB565ToI420);
+  }
+  void ConvertToARGB1555BufferStride() {
+    ConvertToBuffer(2, kOddStride, false, TO, kError,
+                    cricket::FOURCC_RGBO, libyuv::ARGB1555ToI420);
+  }
+  void ConvertToARGB4444BufferStride() {
+    ConvertToBuffer(2, kOddStride, false, TO, kError,
+                    cricket::FOURCC_R444, libyuv::ARGB4444ToI420);
+  }
+  void ConvertToBayerBGGRBufferStride() {
+    ConvertToBuffer(1, kOddStride, false, TO, kErrorHigh,
+                    cricket::FOURCC_BGGR, libyuv::BayerBGGRToI420);
+  }
+  void ConvertToBayerGBRGBufferStride() {
+    ConvertToBuffer(1, kOddStride, false, TO, kErrorHigh,
+                    cricket::FOURCC_GBRG, libyuv::BayerGBRGToI420);
+  }
+  void ConvertToBayerGRBGBufferStride() {
+    ConvertToBuffer(1, kOddStride, false, TO, kErrorHigh,
+                    cricket::FOURCC_GRBG, libyuv::BayerGRBGToI420);
+  }
+  void ConvertToBayerRGGBBufferStride() {
+    ConvertToBuffer(1, kOddStride, false, TO, kErrorHigh,
+                    cricket::FOURCC_RGGB, libyuv::BayerRGGBToI420);
+  }
+  void ConvertToI400BufferStride() {
+    ConvertToBuffer(1, kOddStride, false, TO, 128,
+                    cricket::FOURCC_I400, libyuv::I400ToI420);
+  }
+  void ConvertToYUY2BufferStride() {
+    ConvertToBuffer(2, kOddStride, false, TO, kError,
+                    cricket::FOURCC_YUY2, libyuv::YUY2ToI420);
+  }
+  void ConvertToUYVYBufferStride() {
+    ConvertToBuffer(2, kOddStride, false, TO, kError,
+                    cricket::FOURCC_UYVY, libyuv::UYVYToI420);
+  }
+
+  // Tests ConvertToRGBBuffer formats with negative stride to invert image.
+  void ConvertToARGBBufferInverted() {
+    ConvertToBuffer(4, 0, true, TO, kError,
+                    cricket::FOURCC_ARGB, libyuv::ARGBToI420);
+  }
+  void ConvertToBGRABufferInverted() {
+    ConvertToBuffer(4, 0, true, TO, kError,
+                    cricket::FOURCC_BGRA, libyuv::BGRAToI420);
+  }
+  void ConvertToABGRBufferInverted() {
+    ConvertToBuffer(4, 0, true, TO, kError,
+                    cricket::FOURCC_ABGR, libyuv::ABGRToI420);
+  }
+  void ConvertToRGB24BufferInverted() {
+    ConvertToBuffer(3, 0, true, TO, kError,
+                    cricket::FOURCC_24BG, libyuv::RGB24ToI420);
+  }
+  void ConvertToRAWBufferInverted() {
+    ConvertToBuffer(3, 0, true, TO, kError,
+                    cricket::FOURCC_RAW, libyuv::RAWToI420);
+  }
+  void ConvertToRGB565BufferInverted() {
+    ConvertToBuffer(2, 0, true, TO, kError,
+                    cricket::FOURCC_RGBP, libyuv::RGB565ToI420);
+  }
+  void ConvertToARGB1555BufferInverted() {
+    ConvertToBuffer(2, 0, true, TO, kError,
+                    cricket::FOURCC_RGBO, libyuv::ARGB1555ToI420);
+  }
+  void ConvertToARGB4444BufferInverted() {
+    ConvertToBuffer(2, 0, true, TO, kError,
+                    cricket::FOURCC_R444, libyuv::ARGB4444ToI420);
+  }
+  void ConvertToBayerBGGRBufferInverted() {
+    ConvertToBuffer(1, 0, true, TO, kErrorHigh,
+                    cricket::FOURCC_BGGR, libyuv::BayerBGGRToI420);
+  }
+  void ConvertToBayerGBRGBufferInverted() {
+    ConvertToBuffer(1, 0, true, TO, kErrorHigh,
+                    cricket::FOURCC_GBRG, libyuv::BayerGBRGToI420);
+  }
+  void ConvertToBayerGRBGBufferInverted() {
+    ConvertToBuffer(1, 0, true, TO, kErrorHigh,
+                    cricket::FOURCC_GRBG, libyuv::BayerGRBGToI420);
+  }
+  void ConvertToBayerRGGBBufferInverted() {
+    ConvertToBuffer(1, 0, true, TO, kErrorHigh,
+                    cricket::FOURCC_RGGB, libyuv::BayerRGGBToI420);
+  }
+  void ConvertToI400BufferInverted() {
+    ConvertToBuffer(1, 0, true, TO, 128,
+                    cricket::FOURCC_I400, libyuv::I400ToI420);
+  }
+  void ConvertToYUY2BufferInverted() {
+    ConvertToBuffer(2, 0, true, TO, kError,
+                    cricket::FOURCC_YUY2, libyuv::YUY2ToI420);
+  }
+  void ConvertToUYVYBufferInverted() {
+    ConvertToBuffer(2, 0, true, TO, kError,
+                    cricket::FOURCC_UYVY, libyuv::UYVYToI420);
+  }
+
+  // Tests ConvertFrom formats.
+  void ConvertFromARGBBuffer() {
+    ConvertToBuffer(4, 0, false, FROM, kError,
+                    cricket::FOURCC_ARGB, libyuv::ARGBToI420);
+  }
+  void ConvertFromBGRABuffer() {
+    ConvertToBuffer(4, 0, false, FROM, kError,
+                    cricket::FOURCC_BGRA, libyuv::BGRAToI420);
+  }
+  void ConvertFromABGRBuffer() {
+    ConvertToBuffer(4, 0, false, FROM, kError,
+                    cricket::FOURCC_ABGR, libyuv::ABGRToI420);
+  }
+  void ConvertFromRGB24Buffer() {
+    ConvertToBuffer(3, 0, false, FROM, kError,
+                    cricket::FOURCC_24BG, libyuv::RGB24ToI420);
+  }
+  void ConvertFromRAWBuffer() {
+    ConvertToBuffer(3, 0, false, FROM, kError,
+                    cricket::FOURCC_RAW, libyuv::RAWToI420);
+  }
+  void ConvertFromRGB565Buffer() {
+    ConvertToBuffer(2, 0, false, FROM, kError,
+                    cricket::FOURCC_RGBP, libyuv::RGB565ToI420);
+  }
+  void ConvertFromARGB1555Buffer() {
+    ConvertToBuffer(2, 0, false, FROM, kError,
+                    cricket::FOURCC_RGBO, libyuv::ARGB1555ToI420);
+  }
+  void ConvertFromARGB4444Buffer() {
+    ConvertToBuffer(2, 0, false, FROM, kError,
+                    cricket::FOURCC_R444, libyuv::ARGB4444ToI420);
+  }
+  void ConvertFromBayerBGGRBuffer() {
+    ConvertToBuffer(1, 0, false, FROM, kErrorHigh,
+                    cricket::FOURCC_BGGR, libyuv::BayerBGGRToI420);
+  }
+  void ConvertFromBayerGBRGBuffer() {
+    ConvertToBuffer(1, 0, false, FROM, kErrorHigh,
+                    cricket::FOURCC_GBRG, libyuv::BayerGBRGToI420);
+  }
+  void ConvertFromBayerGRBGBuffer() {
+    ConvertToBuffer(1, 0, false, FROM, kErrorHigh,
+                    cricket::FOURCC_GRBG, libyuv::BayerGRBGToI420);
+  }
+  void ConvertFromBayerRGGBBuffer() {
+    ConvertToBuffer(1, 0, false, FROM, kErrorHigh,
+                    cricket::FOURCC_RGGB, libyuv::BayerRGGBToI420);
+  }
+  void ConvertFromI400Buffer() {
+    ConvertToBuffer(1, 0, false, FROM, 128,
+                    cricket::FOURCC_I400, libyuv::I400ToI420);
+  }
+  void ConvertFromYUY2Buffer() {
+    ConvertToBuffer(2, 0, false, FROM, kError,
+                    cricket::FOURCC_YUY2, libyuv::YUY2ToI420);
+  }
+  void ConvertFromUYVYBuffer() {
+    ConvertToBuffer(2, 0, false, FROM, kError,
+                    cricket::FOURCC_UYVY, libyuv::UYVYToI420);
+  }
+
+  // Tests ConvertFrom formats with odd stride.
+  void ConvertFromARGBBufferStride() {
+    ConvertToBuffer(4, kOddStride, false, FROM, kError,
+                    cricket::FOURCC_ARGB, libyuv::ARGBToI420);
+  }
+  void ConvertFromBGRABufferStride() {
+    ConvertToBuffer(4, kOddStride, false, FROM, kError,
+                    cricket::FOURCC_BGRA, libyuv::BGRAToI420);
+  }
+  void ConvertFromABGRBufferStride() {
+    ConvertToBuffer(4, kOddStride, false, FROM, kError,
+                    cricket::FOURCC_ABGR, libyuv::ABGRToI420);
+  }
+  void ConvertFromRGB24BufferStride() {
+    ConvertToBuffer(3, kOddStride, false, FROM, kError,
+                    cricket::FOURCC_24BG, libyuv::RGB24ToI420);
+  }
+  void ConvertFromRAWBufferStride() {
+    ConvertToBuffer(3, kOddStride, false, FROM, kError,
+                    cricket::FOURCC_RAW, libyuv::RAWToI420);
+  }
+  void ConvertFromRGB565BufferStride() {
+    ConvertToBuffer(2, kOddStride, false, FROM, kError,
+                    cricket::FOURCC_RGBP, libyuv::RGB565ToI420);
+  }
+  void ConvertFromARGB1555BufferStride() {
+    ConvertToBuffer(2, kOddStride, false, FROM, kError,
+                    cricket::FOURCC_RGBO, libyuv::ARGB1555ToI420);
+  }
+  void ConvertFromARGB4444BufferStride() {
+    ConvertToBuffer(2, kOddStride, false, FROM, kError,
+                    cricket::FOURCC_R444, libyuv::ARGB4444ToI420);
+  }
+  void ConvertFromBayerBGGRBufferStride() {
+    ConvertToBuffer(1, kOddStride, false, FROM, kErrorHigh,
+                    cricket::FOURCC_BGGR, libyuv::BayerBGGRToI420);
+  }
+  void ConvertFromBayerGBRGBufferStride() {
+    ConvertToBuffer(1, kOddStride, false, FROM, kErrorHigh,
+                    cricket::FOURCC_GBRG, libyuv::BayerGBRGToI420);
+  }
+  void ConvertFromBayerGRBGBufferStride() {
+    ConvertToBuffer(1, kOddStride, false, FROM, kErrorHigh,
+                    cricket::FOURCC_GRBG, libyuv::BayerGRBGToI420);
+  }
+  void ConvertFromBayerRGGBBufferStride() {
+    ConvertToBuffer(1, kOddStride, false, FROM, kErrorHigh,
+                    cricket::FOURCC_RGGB, libyuv::BayerRGGBToI420);
+  }
+  void ConvertFromI400BufferStride() {
+    ConvertToBuffer(1, kOddStride, false, FROM, 128,
+                    cricket::FOURCC_I400, libyuv::I400ToI420);
+  }
+  void ConvertFromYUY2BufferStride() {
+    ConvertToBuffer(2, kOddStride, false, FROM, kError,
+                    cricket::FOURCC_YUY2, libyuv::YUY2ToI420);
+  }
+  void ConvertFromUYVYBufferStride() {
+    ConvertToBuffer(2, kOddStride, false, FROM, kError,
+                    cricket::FOURCC_UYVY, libyuv::UYVYToI420);
+  }
+
+  // Tests ConvertFrom formats with negative stride to invert image.
+  void ConvertFromARGBBufferInverted() {
+    ConvertToBuffer(4, 0, true, FROM, kError,
+                    cricket::FOURCC_ARGB, libyuv::ARGBToI420);
+  }
+  void ConvertFromBGRABufferInverted() {
+    ConvertToBuffer(4, 0, true, FROM, kError,
+                    cricket::FOURCC_BGRA, libyuv::BGRAToI420);
+  }
+  void ConvertFromABGRBufferInverted() {
+    ConvertToBuffer(4, 0, true, FROM, kError,
+                    cricket::FOURCC_ABGR, libyuv::ABGRToI420);
+  }
+  void ConvertFromRGB24BufferInverted() {
+    ConvertToBuffer(3, 0, true, FROM, kError,
+                    cricket::FOURCC_24BG, libyuv::RGB24ToI420);
+  }
+  void ConvertFromRAWBufferInverted() {
+    ConvertToBuffer(3, 0, true, FROM, kError,
+                    cricket::FOURCC_RAW, libyuv::RAWToI420);
+  }
+  void ConvertFromRGB565BufferInverted() {
+    ConvertToBuffer(2, 0, true, FROM, kError,
+                    cricket::FOURCC_RGBP, libyuv::RGB565ToI420);
+  }
+  void ConvertFromARGB1555BufferInverted() {
+    ConvertToBuffer(2, 0, true, FROM, kError,
+                    cricket::FOURCC_RGBO, libyuv::ARGB1555ToI420);
+  }
+  void ConvertFromARGB4444BufferInverted() {
+    ConvertToBuffer(2, 0, true, FROM, kError,
+                    cricket::FOURCC_R444, libyuv::ARGB4444ToI420);
+  }
+  void ConvertFromBayerBGGRBufferInverted() {
+    ConvertToBuffer(1, 0, true, FROM, kErrorHigh,
+                    cricket::FOURCC_BGGR, libyuv::BayerBGGRToI420);
+  }
+  void ConvertFromBayerGBRGBufferInverted() {
+    ConvertToBuffer(1, 0, true, FROM, kErrorHigh,
+                    cricket::FOURCC_GBRG, libyuv::BayerGBRGToI420);
+  }
+  void ConvertFromBayerGRBGBufferInverted() {
+    ConvertToBuffer(1, 0, true, FROM, kErrorHigh,
+                    cricket::FOURCC_GRBG, libyuv::BayerGRBGToI420);
+  }
+  void ConvertFromBayerRGGBBufferInverted() {
+    ConvertToBuffer(1, 0, true, FROM, kErrorHigh,
+                    cricket::FOURCC_RGGB, libyuv::BayerRGGBToI420);
+  }
+  void ConvertFromI400BufferInverted() {
+    ConvertToBuffer(1, 0, true, FROM, 128,
+                    cricket::FOURCC_I400, libyuv::I400ToI420);
+  }
+  void ConvertFromYUY2BufferInverted() {
+    ConvertToBuffer(2, 0, true, FROM, kError,
+                    cricket::FOURCC_YUY2, libyuv::YUY2ToI420);
+  }
+  void ConvertFromUYVYBufferInverted() {
+    ConvertToBuffer(2, 0, true, FROM, kError,
+                    cricket::FOURCC_UYVY, libyuv::UYVYToI420);
+  }
+
+  // Test converting from I420 to I422.
+  void ConvertToI422Buffer() {
+    T frame1, frame2;
+    size_t out_size = kWidth * kHeight * 2;
+    talk_base::scoped_array<uint8> buf(new uint8[out_size + kAlignment]);
+    uint8* y = ALIGNP(buf.get(), kAlignment);
+    uint8* u = y + kWidth * kHeight;
+    uint8* v = u + (kWidth / 2) * kHeight;
+    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
+    for (int i = 0; i < repeat_; ++i) {
+      EXPECT_EQ(0, libyuv::I420ToI422(frame1.GetYPlane(), frame1.GetYPitch(),
+                                      frame1.GetUPlane(), frame1.GetUPitch(),
+                                      frame1.GetVPlane(), frame1.GetVPitch(),
+                                      y, kWidth,
+                                      u, kWidth / 2,
+                                      v, kWidth / 2,
+                                      kWidth, kHeight));
+    }
+    EXPECT_TRUE(frame2.Init(cricket::FOURCC_I422,
+                            kWidth, kHeight, kWidth, kHeight,
+                            y,
+                            out_size,  1, 1, 0, 0, cricket::ROTATION_0));
+    EXPECT_TRUE(IsEqual(frame1, frame2, 0));
+  }
+
+  #define TEST_TOBYR(NAME, BAYER)                                              \
+  void NAME() {                                                                \
+    size_t bayer_size = kWidth * kHeight;                                      \
+    talk_base::scoped_array<uint8> bayerbuf(new uint8[                         \
+        bayer_size + kAlignment]);                                             \
+    uint8 *bayer = ALIGNP(bayerbuf.get(), kAlignment);                         \
+    T frame;                                                                   \
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(                         \
+        CreateRgbSample(cricket::FOURCC_ARGB, kWidth, kHeight));               \
+    ASSERT_TRUE(ms.get() != NULL);                                             \
+    for (int i = 0; i < repeat_; ++i) {                                        \
+      libyuv::ARGBToBayer##BAYER(reinterpret_cast<uint8*>(ms->GetBuffer()),    \
+                                 kWidth * 4,                                   \
+                                 bayer, kWidth,                                \
+                                 kWidth, kHeight);                             \
+    }                                                                          \
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms2(                        \
+        CreateRgbSample(cricket::FOURCC_ARGB, kWidth, kHeight));               \
+    size_t data_size;                                                          \
+    bool ret = ms2->GetSize(&data_size);                                       \
+    EXPECT_TRUE(ret);                                                          \
+    libyuv::Bayer##BAYER##ToARGB(bayer, kWidth,                                \
+                                 reinterpret_cast<uint8*>(ms2->GetBuffer()),   \
+                                 kWidth * 4,                                   \
+                                 kWidth, kHeight);                             \
+    EXPECT_TRUE(IsPlaneEqual("argb",                                           \
+        reinterpret_cast<uint8*>(ms->GetBuffer()), kWidth * 4,                 \
+        reinterpret_cast<uint8*>(ms2->GetBuffer()), kWidth * 4,                \
+        kWidth * 4, kHeight, 240));                                            \
+  }                                                                            \
+  void NAME##Unaligned() {                                                     \
+    size_t bayer_size = kWidth * kHeight;                                      \
+    talk_base::scoped_array<uint8> bayerbuf(new uint8[                         \
+        bayer_size + 1 + kAlignment]);                                         \
+    uint8 *bayer = ALIGNP(bayerbuf.get(), kAlignment) + 1;                     \
+    T frame;                                                                   \
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(                         \
+        CreateRgbSample(cricket::FOURCC_ARGB, kWidth, kHeight));               \
+    ASSERT_TRUE(ms.get() != NULL);                                             \
+    for (int i = 0; i < repeat_; ++i) {                                        \
+      libyuv::ARGBToBayer##BAYER(reinterpret_cast<uint8*>(ms->GetBuffer()),    \
+                                 kWidth * 4,                                   \
+                                 bayer, kWidth,                                \
+                                 kWidth, kHeight);                             \
+    }                                                                          \
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms2(                        \
+        CreateRgbSample(cricket::FOURCC_ARGB, kWidth, kHeight));               \
+    size_t data_size;                                                          \
+    bool ret = ms2->GetSize(&data_size);                                       \
+    EXPECT_TRUE(ret);                                                          \
+    libyuv::Bayer##BAYER##ToARGB(bayer, kWidth,                                \
+                           reinterpret_cast<uint8*>(ms2->GetBuffer()),         \
+                           kWidth * 4,                                         \
+                           kWidth, kHeight);                                   \
+    EXPECT_TRUE(IsPlaneEqual("argb",                                           \
+        reinterpret_cast<uint8*>(ms->GetBuffer()), kWidth * 4,                 \
+        reinterpret_cast<uint8*>(ms2->GetBuffer()), kWidth * 4,                \
+        kWidth * 4, kHeight, 240));                                            \
+  }
+
+  // Tests ARGB to Bayer formats.
+  TEST_TOBYR(ConvertARGBToBayerGRBG, GRBG)
+  TEST_TOBYR(ConvertARGBToBayerGBRG, GBRG)
+  TEST_TOBYR(ConvertARGBToBayerBGGR, BGGR)
+  TEST_TOBYR(ConvertARGBToBayerRGGB, RGGB)
+
+  #define TEST_BYRTORGB(NAME, BAYER)                                           \
+  void NAME() {                                                                \
+    size_t bayer_size = kWidth * kHeight;                                      \
+    talk_base::scoped_array<uint8> bayerbuf(new uint8[                         \
+        bayer_size + kAlignment]);                                             \
+    uint8 *bayer1 = ALIGNP(bayerbuf.get(), kAlignment);                        \
+    for (int i = 0; i < kWidth * kHeight; ++i) {                               \
+      bayer1[i] = static_cast<uint8>(i * 33u + 183u);                          \
+    }                                                                          \
+    T frame;                                                                   \
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(                         \
+        CreateRgbSample(cricket::FOURCC_ARGB, kWidth, kHeight));               \
+    ASSERT_TRUE(ms.get() != NULL);                                             \
+    for (int i = 0; i < repeat_; ++i) {                                        \
+      libyuv::Bayer##BAYER##ToARGB(bayer1, kWidth,                             \
+                             reinterpret_cast<uint8*>(ms->GetBuffer()),        \
+                             kWidth * 4,                                       \
+                             kWidth, kHeight);                                 \
+    }                                                                          \
+    talk_base::scoped_array<uint8> bayer2buf(new uint8[                        \
+        bayer_size + kAlignment]);                                             \
+    uint8 *bayer2 = ALIGNP(bayer2buf.get(), kAlignment);                       \
+    libyuv::ARGBToBayer##BAYER(reinterpret_cast<uint8*>(ms->GetBuffer()),      \
+                           kWidth * 4,                                         \
+                           bayer2, kWidth,                                     \
+                           kWidth, kHeight);                                   \
+    EXPECT_TRUE(IsPlaneEqual("bayer",                                          \
+        bayer1, kWidth,                                                        \
+        bayer2, kWidth,                                                        \
+        kWidth, kHeight, 0));                                                  \
+  }
+
+  // Tests Bayer formats to ARGB.
+  TEST_BYRTORGB(ConvertBayerGRBGToARGB, GRBG)
+  TEST_BYRTORGB(ConvertBayerGBRGToARGB, GBRG)
+  TEST_BYRTORGB(ConvertBayerBGGRToARGB, BGGR)
+  TEST_BYRTORGB(ConvertBayerRGGBToARGB, RGGB)
+
+  ///////////////////
+  // General tests //
+  ///////////////////
+
+  void Copy() {
+    talk_base::scoped_ptr<T> source(new T);
+    talk_base::scoped_ptr<cricket::VideoFrame> target;
+    ASSERT_TRUE(LoadFrameNoRepeat(source.get()));
+    target.reset(source->Copy());
+    EXPECT_TRUE(IsEqual(*source, *target, 0));
+    source.reset();
+    EXPECT_TRUE(target->GetYPlane() != NULL);
+  }
+
+  void CopyIsRef() {
+    talk_base::scoped_ptr<T> source(new T);
+    talk_base::scoped_ptr<cricket::VideoFrame> target;
+    ASSERT_TRUE(LoadFrameNoRepeat(source.get()));
+    target.reset(source->Copy());
+    EXPECT_TRUE(IsEqual(*source, *target, 0));
+    EXPECT_EQ(source->GetYPlane(), target->GetYPlane());
+    EXPECT_EQ(source->GetUPlane(), target->GetUPlane());
+    EXPECT_EQ(source->GetVPlane(), target->GetVPlane());
+  }
+
+  void MakeExclusive() {
+    talk_base::scoped_ptr<T> source(new T);
+    talk_base::scoped_ptr<cricket::VideoFrame> target;
+    ASSERT_TRUE(LoadFrameNoRepeat(source.get()));
+    target.reset(source->Copy());
+    EXPECT_TRUE(target->MakeExclusive());
+    EXPECT_TRUE(IsEqual(*source, *target, 0));
+    EXPECT_NE(target->GetYPlane(), source->GetYPlane());
+    EXPECT_NE(target->GetUPlane(), source->GetUPlane());
+    EXPECT_NE(target->GetVPlane(), source->GetVPlane());
+  }
+
+  void CopyToBuffer() {
+    T frame;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        LoadSample(kImageFilename));
+    ASSERT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_I420, kWidth, kHeight,
+                          &frame));
+    size_t out_size = kWidth * kHeight * 3 / 2;
+    talk_base::scoped_array<uint8> out(new uint8[out_size]);
+    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));
+  }
+
+  void CopyToFrame() {
+    T source;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        LoadSample(kImageFilename));
+    ASSERT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_I420, kWidth, kHeight,
+                          &source));
+
+    // Create the target frame by loading from a file.
+    T target;
+    ASSERT_TRUE(LoadFrameNoRepeat(&target));
+    EXPECT_FALSE(IsBlack(target));
+
+    // Stretch and check if the stretched target is black.
+    source.CopyToFrame(&target);
+
+    EXPECT_TRUE(IsEqual(source, target, 0));
+  }
+
+  void Write() {
+    T frame;
+    talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+        LoadSample(kImageFilename));
+    talk_base::MemoryStream ms2;
+    size_t size;
+    ASSERT_TRUE(ms->GetSize(&size));
+    ASSERT_TRUE(ms2.ReserveSize(size));
+    ASSERT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_I420, kWidth, kHeight,
+                          &frame));
+    for (int i = 0; i < repeat_; ++i) {
+      ms2.SetPosition(0u);  // Useful when repeat_ > 1.
+      int error;
+      EXPECT_EQ(talk_base::SR_SUCCESS, frame.Write(&ms2, &error));
+    }
+    size_t out_size = cricket::VideoFrame::SizeOf(kWidth, kHeight);
+    EXPECT_EQ(0, memcmp(ms2.GetBuffer(), ms->GetBuffer(), out_size));
+  }
+
+  void 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 };
+    T frame;
+    EXPECT_TRUE(frame.Init(cricket::FOURCC_I420, 1, 1, 1, 1,
+                           pixel, sizeof(pixel),
+                           1, 1, 0, 0, 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.
+  }
+
+  void StretchToFrame() {
+    // Create the source frame as a black frame.
+    T source;
+    EXPECT_TRUE(source.InitToBlack(kWidth * 2, kHeight * 2, 1, 1, 0, 0));
+    EXPECT_TRUE(IsSize(source, kWidth * 2, kHeight * 2));
+
+    // Create the target frame by loading from a file.
+    T target1;
+    ASSERT_TRUE(LoadFrameNoRepeat(&target1));
+    EXPECT_FALSE(IsBlack(target1));
+
+    // Stretch and check if the stretched target is black.
+    source.StretchToFrame(&target1, true, false);
+    EXPECT_TRUE(IsBlack(target1));
+
+    // Crop and stretch and check if the stretched target is black.
+    T target2;
+    ASSERT_TRUE(LoadFrameNoRepeat(&target2));
+    source.StretchToFrame(&target2, true, true);
+    EXPECT_TRUE(IsBlack(target2));
+    EXPECT_EQ(source.GetElapsedTime(), target2.GetElapsedTime());
+    EXPECT_EQ(source.GetTimeStamp(), target2.GetTimeStamp());
+  }
+
+  int repeat_;
+};
+
+#endif  // TALK_MEDIA_BASE_VIDEOFRAME_UNITTEST_H_
diff --git a/talk/media/base/videoprocessor.h b/talk/media/base/videoprocessor.h
new file mode 100755
index 0000000..412d989
--- /dev/null
+++ b/talk/media/base/videoprocessor.h
@@ -0,0 +1,50 @@
+/*
+ * 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_MEDIA_BASE_VIDEOPROCESSOR_H_
+#define TALK_MEDIA_BASE_VIDEOPROCESSOR_H_
+
+#include "talk/base/sigslot.h"
+#include "talk/media/base/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. VideoProcessors may be chained together and may decide
+  // that the current frame should be dropped. If *drop_frame is true,
+  // the current processor should skip processing. If the current processor
+  // decides it cannot process the current frame in a timely manner, it may set
+  // *drop_frame = true and the frame will be dropped.
+  virtual void OnFrame(uint32 ssrc, VideoFrame* frame, bool* drop_frame) = 0;
+};
+
+}  // namespace cricket
+#endif  // TALK_MEDIA_BASE_VIDEOPROCESSOR_H_
diff --git a/talk/media/base/videorenderer.h b/talk/media/base/videorenderer.h
new file mode 100644
index 0000000..ccbe978
--- /dev/null
+++ b/talk/media/base/videorenderer.h
@@ -0,0 +1,58 @@
+/*
+ * 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_MEDIA_BASE_VIDEORENDERER_H_
+#define TALK_MEDIA_BASE_VIDEORENDERER_H_
+
+#ifdef _DEBUG
+#include <string>
+#endif  // _DEBUG
+
+#include "talk/base/sigslot.h"
+
+namespace cricket {
+
+class VideoFrame;
+
+// Abstract interface for rendering VideoFrames.
+class VideoRenderer {
+ public:
+  virtual ~VideoRenderer() {}
+  // Called when the video has changed size.
+  virtual bool SetSize(int width, int height, int reserved) = 0;
+  // Called when a new frame is available for display.
+  virtual bool RenderFrame(const VideoFrame *frame) = 0;
+
+#ifdef _DEBUG
+  // Allow renderer dumping out rendered frames.
+  virtual bool SetDumpPath(const std::string &path) { return true; }
+#endif  // _DEBUG
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_BASE_VIDEORENDERER_H_
diff --git a/talk/media/base/voiceprocessor.h b/talk/media/base/voiceprocessor.h
new file mode 100755
index 0000000..576bdca
--- /dev/null
+++ b/talk/media/base/voiceprocessor.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_MEDIA_BASE_VOICEPROCESSOR_H_
+#define TALK_MEDIA_BASE_VOICEPROCESSOR_H_
+
+#include "talk/base/basictypes.h"
+#include "talk/base/sigslot.h"
+#include "talk/media/base/audioframe.h"
+
+namespace cricket {
+
+enum MediaProcessorDirection {
+    MPD_INVALID = 0,
+    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,
+                       MediaProcessorDirection direction,
+                       AudioFrame* frame) = 0;
+};
+
+}  // namespace cricket
+#endif  // TALK_MEDIA_BASE_VOICEPROCESSOR_H_
diff --git a/talk/media/devices/carbonvideorenderer.cc b/talk/media/devices/carbonvideorenderer.cc
new file mode 100644
index 0000000..71abf26
--- /dev/null
+++ b/talk/media/devices/carbonvideorenderer.cc
@@ -0,0 +1,182 @@
+// 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.
+//
+// Implementation of CarbonVideoRenderer
+
+#include "talk/media/devices/carbonvideorenderer.h"
+
+#include "talk/base/logging.h"
+#include "talk/media/base/videocommon.h"
+#include "talk/media/base/videoframe.h"
+
+namespace cricket {
+
+CarbonVideoRenderer::CarbonVideoRenderer(int x, int y)
+    : image_width_(0),
+      image_height_(0),
+      x_(x),
+      y_(y),
+      image_ref_(NULL),
+      window_ref_(NULL) {
+}
+
+CarbonVideoRenderer::~CarbonVideoRenderer() {
+  if (window_ref_) {
+    DisposeWindow(window_ref_);
+  }
+}
+
+// Called from the main event loop. All renderering needs to happen on
+// the main thread.
+OSStatus CarbonVideoRenderer::DrawEventHandler(EventHandlerCallRef handler,
+                                               EventRef event,
+                                               void* data) {
+  OSStatus status = noErr;
+  CarbonVideoRenderer* renderer = static_cast<CarbonVideoRenderer*>(data);
+  if (renderer != NULL) {
+    if (!renderer->DrawFrame()) {
+      LOG(LS_ERROR) << "Failed to draw frame.";
+    }
+  }
+  return status;
+}
+
+bool CarbonVideoRenderer::DrawFrame() {
+  // Grab the image lock to make sure it is not changed why we'll draw it.
+  talk_base::CritScope cs(&image_crit_);
+
+  if (image_.get() == NULL) {
+    // Nothing to draw, just return.
+    return true;
+  }
+  int width = image_width_;
+  int height = image_height_;
+  CGDataProviderRef provider =
+      CGDataProviderCreateWithData(NULL, image_.get(), width * height * 4,
+                                   NULL);
+  CGColorSpaceRef color_space_ref = CGColorSpaceCreateDeviceRGB();
+  CGBitmapInfo bitmap_info = kCGBitmapByteOrderDefault;
+  CGColorRenderingIntent rendering_intent = kCGRenderingIntentDefault;
+  CGImageRef image_ref = CGImageCreate(width, height, 8, 32, width * 4,
+                                       color_space_ref, bitmap_info, provider,
+                                       NULL, false, rendering_intent);
+  CGDataProviderRelease(provider);
+
+  if (image_ref == NULL) {
+    return false;
+  }
+  CGContextRef context;
+  SetPortWindowPort(window_ref_);
+  if (QDBeginCGContext(GetWindowPort(window_ref_), &context) != noErr) {
+    CGImageRelease(image_ref);
+    return false;
+  }
+  Rect window_bounds;
+  GetWindowPortBounds(window_ref_, &window_bounds);
+
+  // Anchor the image to the top left corner.
+  int x = 0;
+  int y = window_bounds.bottom - CGImageGetHeight(image_ref);
+  CGRect dst_rect = CGRectMake(x, y, CGImageGetWidth(image_ref),
+                               CGImageGetHeight(image_ref));
+  CGContextDrawImage(context, dst_rect, image_ref);
+  CGContextFlush(context);
+  QDEndCGContext(GetWindowPort(window_ref_), &context);
+  CGImageRelease(image_ref);
+  return true;
+}
+
+bool CarbonVideoRenderer::SetSize(int width, int height, int reserved) {
+  if (width != image_width_ || height != image_height_) {
+    // Grab the image lock while changing its size.
+    talk_base::CritScope cs(&image_crit_);
+    image_width_ = width;
+    image_height_ = height;
+    image_.reset(new uint8[width * height * 4]);
+    memset(image_.get(), 255, width * height * 4);
+  }
+  return true;
+}
+
+bool CarbonVideoRenderer::RenderFrame(const VideoFrame* frame) {
+  if (!frame) {
+    return false;
+  }
+  {
+    // Grab the image lock so we are not trashing up the image being drawn.
+    talk_base::CritScope cs(&image_crit_);
+    frame->ConvertToRgbBuffer(cricket::FOURCC_ABGR,
+                              image_.get(),
+                              frame->GetWidth() * frame->GetHeight() * 4,
+                              frame->GetWidth() * 4);
+  }
+
+  // Trigger a repaint event for the whole window.
+  Rect bounds;
+  InvalWindowRect(window_ref_, GetWindowPortBounds(window_ref_, &bounds));
+  return true;
+}
+
+bool CarbonVideoRenderer::Initialize() {
+  OSStatus err;
+  WindowAttributes attributes =
+      kWindowStandardDocumentAttributes |
+      kWindowLiveResizeAttribute |
+      kWindowFrameworkScaledAttribute |
+      kWindowStandardHandlerAttribute;
+
+  struct Rect bounds;
+  bounds.top = y_;
+  bounds.bottom = 480;
+  bounds.left = x_;
+  bounds.right = 640;
+  err = CreateNewWindow(kDocumentWindowClass, attributes,
+                        &bounds, &window_ref_);
+  if (!window_ref_ || err != noErr) {
+    LOG(LS_ERROR) << "CreateNewWindow failed, error code: " << err;
+    return false;
+  }
+  static const EventTypeSpec event_spec = {
+    kEventClassWindow,
+    kEventWindowDrawContent
+  };
+
+  err = InstallWindowEventHandler(
+      window_ref_,
+      NewEventHandlerUPP(CarbonVideoRenderer::DrawEventHandler),
+      GetEventTypeCount(event_spec),
+      &event_spec,
+      this,
+      NULL);
+  if (err != noErr) {
+    LOG(LS_ERROR) << "Failed to install event handler, error code: " << err;
+    return false;
+  }
+  SelectWindow(window_ref_);
+  ShowWindow(window_ref_);
+  return true;
+}
+
+}  // namespace cricket
diff --git a/talk/media/devices/carbonvideorenderer.h b/talk/media/devices/carbonvideorenderer.h
new file mode 100644
index 0000000..e091186
--- /dev/null
+++ b/talk/media/devices/carbonvideorenderer.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.
+//
+// Definition of class CarbonVideoRenderer that implements the abstract class
+// cricket::VideoRenderer via Carbon.
+
+#ifndef TALK_MEDIA_DEVICES_CARBONVIDEORENDERER_H_
+#define TALK_MEDIA_DEVICES_CARBONVIDEORENDERER_H_
+
+#include <Carbon/Carbon.h>
+
+#include "talk/base/criticalsection.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/media/base/videorenderer.h"
+
+namespace cricket {
+
+class CarbonVideoRenderer : public VideoRenderer {
+ public:
+  CarbonVideoRenderer(int x, int y);
+  virtual ~CarbonVideoRenderer();
+
+  // Implementation of pure virtual methods of VideoRenderer.
+  // These two methods may be executed in different threads.
+  // SetSize is called before RenderFrame.
+  virtual bool SetSize(int width, int height, int reserved);
+  virtual bool RenderFrame(const VideoFrame* frame);
+
+  // Needs to be called on the main thread.
+  bool Initialize();
+
+ private:
+  bool DrawFrame();
+
+  static OSStatus DrawEventHandler(EventHandlerCallRef handler,
+                                   EventRef event,
+                                   void* data);
+  talk_base::scoped_array<uint8> image_;
+  talk_base::CriticalSection image_crit_;
+  int image_width_;
+  int image_height_;
+  int x_;
+  int y_;
+  CGImageRef image_ref_;
+  WindowRef window_ref_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_DEVICES_CARBONVIDEORENDERER_H_
diff --git a/talk/media/devices/deviceinfo.h b/talk/media/devices/deviceinfo.h
new file mode 100644
index 0000000..86382f6
--- /dev/null
+++ b/talk/media/devices/deviceinfo.h
@@ -0,0 +1,42 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_MEDIA_DEVICES_DEVICEINFO_H_
+#define TALK_MEDIA_DEVICES_DEVICEINFO_H_
+
+#include <string>
+
+#include "talk/media/devices/devicemanager.h"
+
+namespace cricket {
+
+bool GetUsbId(const Device& device, std::string* usb_id);
+bool GetUsbVersion(const Device& device, std::string* usb_version);
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_DEVICES_DEVICEINFO_H_
diff --git a/talk/media/devices/devicemanager.cc b/talk/media/devices/devicemanager.cc
new file mode 100644
index 0000000..2ce5eb0
--- /dev/null
+++ b/talk/media/devices/devicemanager.cc
@@ -0,0 +1,389 @@
+/*
+ * 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/media/devices/devicemanager.h"
+
+#include "talk/base/fileutils.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+#include "talk/base/windowpicker.h"
+#include "talk/base/windowpickerfactory.h"
+#include "talk/media/base/mediacommon.h"
+#include "talk/media/devices/deviceinfo.h"
+#include "talk/media/devices/filevideocapturer.h"
+
+#if !defined(IOS)
+
+#if defined(HAVE_WEBRTC_VIDEO)
+#include "talk/media/webrtc/webrtcvideocapturer.h"
+#endif
+
+
+#if defined(HAVE_WEBRTC_VIDEO)
+#define VIDEO_CAPTURER_NAME WebRtcVideoCapturer
+#endif
+
+
+#endif
+
+namespace {
+
+bool StringMatchWithWildcard(
+    const std::pair<const std::basic_string<char>, cricket::VideoFormat> key,
+    const std::string& val) {
+  return talk_base::string_match(val.c_str(), key.first.c_str());
+}
+
+}  // namespace
+
+namespace cricket {
+
+// Initialize to empty string.
+const char DeviceManagerInterface::kDefaultDeviceName[] = "";
+
+
+class DefaultVideoCapturerFactory : public VideoCapturerFactory {
+ public:
+  DefaultVideoCapturerFactory() {}
+  virtual ~DefaultVideoCapturerFactory() {}
+
+  VideoCapturer* Create(const Device& device) {
+#if defined(VIDEO_CAPTURER_NAME)
+    VIDEO_CAPTURER_NAME* return_value = new VIDEO_CAPTURER_NAME;
+    if (!return_value->Init(device)) {
+      delete return_value;
+      return NULL;
+    }
+    return return_value;
+#else
+    return NULL;
+#endif
+  }
+};
+
+DeviceManager::DeviceManager()
+    : initialized_(false),
+      device_video_capturer_factory_(new DefaultVideoCapturerFactory),
+      window_picker_(talk_base::WindowPickerFactory::CreateWindowPicker()) {
+}
+
+DeviceManager::~DeviceManager() {
+  if (initialized()) {
+    Terminate();
+  }
+}
+
+bool DeviceManager::Init() {
+  if (!initialized()) {
+    if (!watcher()->Start()) {
+      return false;
+    }
+    set_initialized(true);
+  }
+  return true;
+}
+
+void DeviceManager::Terminate() {
+  if (initialized()) {
+    watcher()->Stop();
+    set_initialized(false);
+  }
+}
+
+int DeviceManager::GetCapabilities() {
+  std::vector<Device> devices;
+  int caps = VIDEO_RECV;
+  if (GetAudioInputDevices(&devices) && !devices.empty()) {
+    caps |= AUDIO_SEND;
+  }
+  if (GetAudioOutputDevices(&devices) && !devices.empty()) {
+    caps |= AUDIO_RECV;
+  }
+  if (GetVideoCaptureDevices(&devices) && !devices.empty()) {
+    caps |= VIDEO_SEND;
+  }
+  return caps;
+}
+
+bool DeviceManager::GetAudioInputDevices(std::vector<Device>* devices) {
+  return GetAudioDevices(true, devices);
+}
+
+bool DeviceManager::GetAudioOutputDevices(std::vector<Device>* devices) {
+  return GetAudioDevices(false, devices);
+}
+
+bool DeviceManager::GetAudioInputDevice(const std::string& name, Device* out) {
+  return GetAudioDevice(true, name, out);
+}
+
+bool DeviceManager::GetAudioOutputDevice(const std::string& name, Device* out) {
+  return GetAudioDevice(false, name, out);
+}
+
+bool DeviceManager::GetVideoCaptureDevices(std::vector<Device>* devices) {
+  devices->clear();
+#if defined(IOS)
+  // On iOS, 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);
+  return true;
+#else
+  return false;
+#endif
+}
+
+bool DeviceManager::GetVideoCaptureDevice(const std::string& name,
+                                          Device* out) {
+  // If the name is empty, return the default device.
+  if (name.empty() || name == kDefaultDeviceName) {
+    return GetDefaultVideoCaptureDevice(out);
+  }
+
+  std::vector<Device> devices;
+  if (!GetVideoCaptureDevices(&devices)) {
+    return false;
+  }
+
+  for (std::vector<Device>::const_iterator it = devices.begin();
+      it != devices.end(); ++it) {
+    if (name == it->name) {
+      *out = *it;
+      return true;
+    }
+  }
+
+  // If |name| is a valid name for a file, return a file video capturer device.
+  if (talk_base::Filesystem::IsFile(name)) {
+    *out = FileVideoCapturer::CreateFileVideoCapturerDevice(name);
+    return true;
+  }
+
+  return false;
+}
+
+void DeviceManager::SetVideoCaptureDeviceMaxFormat(
+    const std::string& usb_id,
+    const VideoFormat& max_format) {
+  max_formats_[usb_id] = max_format;
+}
+
+void DeviceManager::ClearVideoCaptureDeviceMaxFormat(
+    const std::string& usb_id) {
+  max_formats_.erase(usb_id);
+}
+
+VideoCapturer* DeviceManager::CreateVideoCapturer(const Device& device) const {
+#if defined(IOS)
+  LOG_F(LS_ERROR) << " should never be called!";
+  return NULL;
+#else
+  // TODO(hellner): Throw out the creation of a file video capturer once the
+  // refactoring is completed.
+  if (FileVideoCapturer::IsFileVideoCapturerDevice(device)) {
+    FileVideoCapturer* capturer = new FileVideoCapturer;
+    if (!capturer->Init(device)) {
+      delete capturer;
+      return NULL;
+    }
+    LOG(LS_INFO) << "Created file video capturer " << device.name;
+    capturer->set_repeat(talk_base::kForever);
+    return capturer;
+  }
+  VideoCapturer* capturer = device_video_capturer_factory_->Create(device);
+  if (!capturer) {
+    return NULL;
+  }
+  LOG(LS_INFO) << "Created VideoCapturer for " << device.name;
+  VideoFormat video_format;
+  bool has_max = GetMaxFormat(device, &video_format);
+  capturer->set_enable_camera_list(has_max);
+  if (has_max) {
+    capturer->ConstrainSupportedFormats(video_format);
+  }
+  return capturer;
+#endif
+}
+
+bool DeviceManager::GetWindows(
+    std::vector<talk_base::WindowDescription>* descriptions) {
+  if (!window_picker_) {
+    return false;
+  }
+  return window_picker_->GetWindowList(descriptions);
+}
+
+VideoCapturer* DeviceManager::CreateWindowCapturer(talk_base::WindowId window) {
+#if defined(WINDOW_CAPTURER_NAME)
+  WINDOW_CAPTURER_NAME* window_capturer = new WINDOW_CAPTURER_NAME();
+  if (!window_capturer->Init(window)) {
+    delete window_capturer;
+    return NULL;
+  }
+  return window_capturer;
+#else
+  return NULL;
+#endif
+}
+
+bool DeviceManager::GetDesktops(
+    std::vector<talk_base::DesktopDescription>* descriptions) {
+  if (!window_picker_) {
+    return false;
+  }
+  return window_picker_->GetDesktopList(descriptions);
+}
+
+VideoCapturer* DeviceManager::CreateDesktopCapturer(
+    talk_base::DesktopId desktop) {
+#if defined(DESKTOP_CAPTURER_NAME)
+  DESKTOP_CAPTURER_NAME* desktop_capturer = new DESKTOP_CAPTURER_NAME();
+  if (!desktop_capturer->Init(desktop.index())) {
+    delete desktop_capturer;
+    return NULL;
+  }
+  return desktop_capturer;
+#else
+  return NULL;
+#endif
+}
+
+bool DeviceManager::GetAudioDevices(bool input,
+                                    std::vector<Device>* devs) {
+  devs->clear();
+#if defined(IOS) || defined(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.
+  if (name.empty() || name == kDefaultDeviceName) {
+    *out = Device(name, -1);
+    return true;
+  }
+
+  std::vector<Device> devices;
+  bool ret = is_input ? GetAudioInputDevices(&devices) :
+                        GetAudioOutputDevices(&devices);
+  if (ret) {
+    ret = false;
+    for (size_t i = 0; i < devices.size(); ++i) {
+      if (devices[i].name == name) {
+        *out = devices[i];
+        ret = true;
+        break;
+      }
+    }
+  }
+  return ret;
+}
+
+bool DeviceManager::GetDefaultVideoCaptureDevice(Device* device) {
+  bool ret = false;
+  // We just return the first device.
+  std::vector<Device> devices;
+  ret = (GetVideoCaptureDevices(&devices) && !devices.empty());
+  if (ret) {
+    *device = devices[0];
+  }
+  return ret;
+}
+
+bool DeviceManager::IsInWhitelist(const std::string& key,
+                                  VideoFormat* video_format) const {
+  std::map<std::string, VideoFormat>::const_iterator found =
+      std::search_n(max_formats_.begin(), max_formats_.end(), 1, key,
+                    StringMatchWithWildcard);
+  if (found == max_formats_.end()) {
+    return false;
+  }
+  *video_format = found->second;
+  return true;
+}
+
+bool DeviceManager::GetMaxFormat(const Device& device,
+                                 VideoFormat* video_format) const {
+  // Match USB ID if available. Failing that, match device name.
+  std::string usb_id;
+  if (GetUsbId(device, &usb_id) && IsInWhitelist(usb_id, video_format)) {
+      return true;
+  }
+  return IsInWhitelist(device.name, video_format);
+}
+
+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;
+
+  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 false;
+}
+
+bool DeviceManager::FilterDevices(std::vector<Device>* devices,
+    const char* const exclusion_list[]) {
+  if (!devices) {
+    return false;
+  }
+
+  for (std::vector<Device>::iterator it = devices->begin();
+       it != devices->end(); ) {
+    if (ShouldDeviceBeIgnored(it->name, exclusion_list)) {
+      it = devices->erase(it);
+    } else {
+      ++it;
+    }
+  }
+  return true;
+}
+
+}  // namespace cricket
diff --git a/talk/media/devices/devicemanager.h b/talk/media/devices/devicemanager.h
new file mode 100644
index 0000000..90da891
--- /dev/null
+++ b/talk/media/devices/devicemanager.h
@@ -0,0 +1,214 @@
+/*
+ * 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_MEDIA_DEVICES_DEVICEMANAGER_H_
+#define TALK_MEDIA_DEVICES_DEVICEMANAGER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/window.h"
+#include "talk/media/base/videocommon.h"
+
+namespace talk_base {
+
+class DesktopDescription;
+class WindowDescription;
+class WindowPicker;
+
+}
+namespace cricket {
+
+class VideoCapturer;
+
+// Used to represent an audio or video capture or render device.
+struct Device {
+  Device() {}
+  Device(const std::string& first, int second)
+      : name(first),
+        id(talk_base::ToString(second)) {
+  }
+  Device(const std::string& first, const std::string& second)
+      : name(first), id(second) {}
+
+  std::string name;
+  std::string id;
+};
+
+class VideoCapturerFactory {
+ public:
+  VideoCapturerFactory() {}
+  virtual ~VideoCapturerFactory() {}
+
+  virtual VideoCapturer* Create(const Device& device) = 0;
+};
+
+// DeviceManagerInterface - interface to manage the audio and
+// video devices on the system.
+class DeviceManagerInterface {
+ public:
+  virtual ~DeviceManagerInterface() { }
+
+  // Initialization
+  virtual bool Init() = 0;
+  virtual void Terminate() = 0;
+
+  // Capabilities
+  virtual int GetCapabilities() = 0;
+
+  // Device enumeration
+  virtual bool GetAudioInputDevices(std::vector<Device>* devices) = 0;
+  virtual bool GetAudioOutputDevices(std::vector<Device>* devices) = 0;
+
+  virtual bool GetAudioInputDevice(const std::string& name, Device* out) = 0;
+  virtual bool GetAudioOutputDevice(const std::string& name, Device* out) = 0;
+
+  virtual bool GetVideoCaptureDevices(std::vector<Device>* devs) = 0;
+  virtual bool GetVideoCaptureDevice(const std::string& name, Device* out) = 0;
+
+  // Caps the capture format according to max format for capturers created
+  // by CreateVideoCapturer(). See ConstrainSupportedFormats() in
+  // videocapturer.h for more detail.
+  // Note that once a VideoCapturer has been created, calling this API will
+  // not affect it.
+  virtual void SetVideoCaptureDeviceMaxFormat(
+      const std::string& usb_id,
+      const VideoFormat& max_format) = 0;
+  virtual void ClearVideoCaptureDeviceMaxFormat(const std::string& usb_id) = 0;
+
+  // Device creation
+  virtual VideoCapturer* CreateVideoCapturer(const Device& device) const = 0;
+
+  virtual bool GetWindows(
+      std::vector<talk_base::WindowDescription>* descriptions) = 0;
+  virtual VideoCapturer* CreateWindowCapturer(talk_base::WindowId window) = 0;
+
+  virtual bool GetDesktops(
+      std::vector<talk_base::DesktopDescription>* descriptions) = 0;
+  virtual VideoCapturer* CreateDesktopCapturer(
+      talk_base::DesktopId desktop) = 0;
+
+  sigslot::signal0<> SignalDevicesChange;
+
+  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();
+
+ private:
+  DeviceManagerFactory() {}
+};
+
+class DeviceManager : public DeviceManagerInterface {
+ public:
+  DeviceManager();
+  virtual ~DeviceManager();
+
+  void set_device_video_capturer_factory(
+      VideoCapturerFactory* device_video_capturer_factory) {
+    device_video_capturer_factory_.reset(device_video_capturer_factory);
+  }
+
+  // Initialization
+  virtual bool Init();
+  virtual void Terminate();
+
+  // Capabilities
+  virtual int GetCapabilities();
+
+  // Device enumeration
+  virtual bool GetAudioInputDevices(std::vector<Device>* devices);
+  virtual bool GetAudioOutputDevices(std::vector<Device>* devices);
+
+  virtual bool GetAudioInputDevice(const std::string& name, Device* out);
+  virtual bool GetAudioOutputDevice(const std::string& name, Device* out);
+
+  virtual bool GetVideoCaptureDevices(std::vector<Device>* devs);
+  virtual bool GetVideoCaptureDevice(const std::string& name, Device* out);
+
+  virtual void SetVideoCaptureDeviceMaxFormat(const std::string& usb_id,
+                                              const VideoFormat& max_format);
+  virtual void ClearVideoCaptureDeviceMaxFormat(const std::string& usb_id);
+
+  virtual VideoCapturer* CreateVideoCapturer(const Device& device) const;
+
+  virtual bool GetWindows(
+      std::vector<talk_base::WindowDescription>* descriptions);
+  virtual VideoCapturer* CreateWindowCapturer(talk_base::WindowId window);
+
+  virtual bool GetDesktops(
+      std::vector<talk_base::DesktopDescription>* descriptions);
+  virtual VideoCapturer* CreateDesktopCapturer(talk_base::DesktopId desktop);
+
+  // 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_; }
+
+ 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);
+  bool IsInWhitelist(const std::string& key, VideoFormat* video_format) const;
+  virtual bool GetMaxFormat(const Device& device,
+                            VideoFormat* video_format) const;
+
+  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_;
+  talk_base::scoped_ptr<VideoCapturerFactory> device_video_capturer_factory_;
+  std::map<std::string, VideoFormat> max_formats_;
+  talk_base::scoped_ptr<DeviceWatcher> watcher_;
+  talk_base::scoped_ptr<talk_base::WindowPicker> window_picker_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_DEVICES_DEVICEMANAGER_H_
diff --git a/talk/media/devices/devicemanager_unittest.cc b/talk/media/devices/devicemanager_unittest.cc
new file mode 100644
index 0000000..d8564ea
--- /dev/null
+++ b/talk/media/devices/devicemanager_unittest.cc
@@ -0,0 +1,452 @@
+/*
+ * 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/media/devices/devicemanager.h"
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#include <objbase.h>
+#endif
+#include <string>
+
+#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/base/windowpickerfactory.h"
+#include "talk/media/base/fakevideocapturer.h"
+#include "talk/media/base/testutils.h"
+#include "talk/media/devices/filevideocapturer.h"
+#include "talk/media/devices/v4llookup.h"
+
+#ifdef LINUX
+// TODO(juberti): Figure out why this doesn't compile on Windows.
+#include "talk/base/fileutils_mock.h"
+#endif  // LINUX
+
+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;
+
+const cricket::VideoFormat kVgaFormat(640, 480,
+                                      cricket::VideoFormat::FpsToInterval(30),
+                                      cricket::FOURCC_I420);
+const cricket::VideoFormat kHdFormat(1280, 720,
+                                     cricket::VideoFormat::FpsToInterval(30),
+                                     cricket::FOURCC_I420);
+
+class FakeVideoCapturerFactory : public cricket::VideoCapturerFactory {
+ public:
+  FakeVideoCapturerFactory() {}
+  virtual ~FakeVideoCapturerFactory() {}
+
+  virtual cricket::VideoCapturer* Create(const cricket::Device& device) {
+    return new cricket::FakeVideoCapturer;
+  }
+};
+
+class DeviceManagerTestFake : public testing::Test {
+ public:
+  virtual void SetUp() {
+    dm_.reset(DeviceManagerFactory::Create());
+    EXPECT_TRUE(dm_->Init());
+    DeviceManager* device_manager = static_cast<DeviceManager*>(dm_.get());
+    device_manager->set_device_video_capturer_factory(
+        new FakeVideoCapturerFactory());
+  }
+
+  virtual void TearDown() {
+    dm_->Terminate();
+  }
+
+ protected:
+  scoped_ptr<DeviceManagerInterface> dm_;
+};
+
+
+// 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, GetVideoDeviceIds_File) {
+  scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create());
+  EXPECT_TRUE(dm->Init());
+  Device device;
+  const std::string test_file =
+      cricket::GetTestFilePath("captured-320x240-2s-48.frames");
+  EXPECT_TRUE(dm->GetVideoCaptureDevice(test_file, &device));
+  EXPECT_TRUE(cricket::FileVideoCapturer::IsFileVideoCapturerDevice(device));
+}
+
+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
+
+// TODO(noahric): These are flaky on windows on headless machines.
+#ifndef WIN32
+TEST(DeviceManagerTest, GetWindows) {
+  if (!talk_base::WindowPickerFactory::IsSupported()) {
+    LOG(LS_INFO) << "skipping test: window capturing is not supported with "
+                 << "current configuration.";
+    return;
+  }
+  scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create());
+  std::vector<talk_base::WindowDescription> descriptions;
+  EXPECT_TRUE(dm->Init());
+  if (!dm->GetWindows(&descriptions) || descriptions.empty()) {
+    LOG(LS_INFO) << "skipping test: window capturing. Does not have any "
+                 << "windows to capture.";
+    return;
+  }
+  scoped_ptr<cricket::VideoCapturer> capturer(dm->CreateWindowCapturer(
+      descriptions.front().id()));
+  EXPECT_FALSE(capturer.get() == NULL);
+  // TODO(hellner): creating a window capturer and immediately deleting it
+  // results in "Continuous Build and Test Mainline - Mac opt" failure (crash).
+  // Remove the following line as soon as this has been resolved.
+  talk_base::Thread::Current()->ProcessMessages(1);
+}
+
+TEST(DeviceManagerTest, GetDesktops) {
+  if (!talk_base::WindowPickerFactory::IsSupported()) {
+    LOG(LS_INFO) << "skipping test: desktop capturing is not supported with "
+                 << "current configuration.";
+    return;
+  }
+  scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create());
+  std::vector<talk_base::DesktopDescription> descriptions;
+  EXPECT_TRUE(dm->Init());
+  if (!dm->GetDesktops(&descriptions) || descriptions.empty()) {
+    LOG(LS_INFO) << "skipping test: desktop capturing. Does not have any "
+                 << "desktops to capture.";
+    return;
+  }
+  scoped_ptr<cricket::VideoCapturer> capturer(dm->CreateDesktopCapturer(
+      descriptions.front().id()));
+  EXPECT_FALSE(capturer.get() == NULL);
+}
+#endif  // !WIN32
+
+TEST_F(DeviceManagerTestFake, CaptureConstraintsWhitelisted) {
+  const Device device("white", "white_id");
+  dm_->SetVideoCaptureDeviceMaxFormat(device.name, kHdFormat);
+  scoped_ptr<cricket::VideoCapturer> capturer(
+      dm_->CreateVideoCapturer(device));
+  cricket::VideoFormat best_format;
+  capturer->set_enable_camera_list(true);
+  EXPECT_TRUE(capturer->GetBestCaptureFormat(kHdFormat, &best_format));
+  EXPECT_EQ(kHdFormat, best_format);
+}
+
+TEST_F(DeviceManagerTestFake, CaptureConstraintsNotWhitelisted) {
+  const Device device("regular", "regular_id");
+  scoped_ptr<cricket::VideoCapturer> capturer(
+      dm_->CreateVideoCapturer(device));
+  cricket::VideoFormat best_format;
+  capturer->set_enable_camera_list(true);
+  EXPECT_TRUE(capturer->GetBestCaptureFormat(kHdFormat, &best_format));
+  EXPECT_EQ(kHdFormat, best_format);
+}
+
+TEST_F(DeviceManagerTestFake, CaptureConstraintsUnWhitelisted) {
+  const Device device("un_white", "un_white_id");
+  dm_->SetVideoCaptureDeviceMaxFormat(device.name, kHdFormat);
+  dm_->ClearVideoCaptureDeviceMaxFormat(device.name);
+  scoped_ptr<cricket::VideoCapturer> capturer(
+      dm_->CreateVideoCapturer(device));
+  cricket::VideoFormat best_format;
+  capturer->set_enable_camera_list(true);
+  EXPECT_TRUE(capturer->GetBestCaptureFormat(kHdFormat, &best_format));
+  EXPECT_EQ(kHdFormat, best_format);
+}
+
+TEST_F(DeviceManagerTestFake, CaptureConstraintsWildcard) {
+  const Device device("any_device", "any_device");
+  dm_->SetVideoCaptureDeviceMaxFormat("*", kHdFormat);
+  scoped_ptr<cricket::VideoCapturer> capturer(
+      dm_->CreateVideoCapturer(device));
+  cricket::VideoFormat best_format;
+  capturer->set_enable_camera_list(true);
+  EXPECT_TRUE(capturer->GetBestCaptureFormat(kHdFormat, &best_format));
+  EXPECT_EQ(kHdFormat, best_format);
+}
diff --git a/talk/media/devices/dummydevicemanager.cc b/talk/media/devices/dummydevicemanager.cc
new file mode 100644
index 0000000..736258b
--- /dev/null
+++ b/talk/media/devices/dummydevicemanager.cc
@@ -0,0 +1,38 @@
+/*
+ * 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/media/devices/dummydevicemanager.h"
+
+namespace cricket {
+
+const char DeviceManagerInterface::kDefaultDeviceName[] = "";
+
+DeviceManagerInterface* DeviceManagerFactory::Create() {
+  return new DummyDeviceManager();
+}
+
+};  // namespace cricket
diff --git a/talk/media/devices/dummydevicemanager.h b/talk/media/devices/dummydevicemanager.h
new file mode 100644
index 0000000..7da8185
--- /dev/null
+++ b/talk/media/devices/dummydevicemanager.h
@@ -0,0 +1,51 @@
+/*
+ * libjingle
+ * Copyright 2011 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_MEDIA_DEVICES_DUMMYDEVICEMANAGER_H_
+#define TALK_MEDIA_DEVICES_DUMMYDEVICEMANAGER_H_
+
+#include <vector>
+
+#include "talk/media/base/mediacommon.h"
+#include "talk/media/devices/fakedevicemanager.h"
+
+namespace cricket {
+
+class DummyDeviceManager : public FakeDeviceManager {
+ public:
+  DummyDeviceManager() {
+    std::vector<std::string> devices;
+    devices.push_back(DeviceManagerInterface::kDefaultDeviceName);
+    SetAudioInputDevices(devices);
+    SetAudioOutputDevices(devices);
+    SetVideoCaptureDevices(devices);
+  }
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_DEVICES_DUMMYDEVICEMANAGER_H_
diff --git a/talk/media/devices/dummydevicemanager_unittest.cc b/talk/media/devices/dummydevicemanager_unittest.cc
new file mode 100644
index 0000000..1abf1ea
--- /dev/null
+++ b/talk/media/devices/dummydevicemanager_unittest.cc
@@ -0,0 +1,104 @@
+/*
+ * 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/media/devices/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/media/devices/fakedevicemanager.h b/talk/media/devices/fakedevicemanager.h
new file mode 100644
index 0000000..7000ef9
--- /dev/null
+++ b/talk/media/devices/fakedevicemanager.h
@@ -0,0 +1,219 @@
+/*
+ * 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_MEDIA_DEVICES_FAKEDEVICEMANAGER_H_
+#define TALK_MEDIA_DEVICES_FAKEDEVICEMANAGER_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/window.h"
+#include "talk/base/windowpicker.h"
+#include "talk/media/base/fakevideocapturer.h"
+#include "talk/media/base/mediacommon.h"
+#include "talk/media/devices/devicemanager.h"
+
+namespace cricket {
+
+class FakeDeviceManager : public DeviceManagerInterface {
+ public:
+  FakeDeviceManager() {}
+  virtual bool Init() {
+    return true;
+  }
+  virtual void Terminate() {
+  }
+  virtual int GetCapabilities() {
+    std::vector<Device> devices;
+    int caps = VIDEO_RECV;
+    if (!input_devices_.empty()) {
+      caps |= AUDIO_SEND;
+    }
+    if (!output_devices_.empty()) {
+      caps |= AUDIO_RECV;
+    }
+    if (!vidcap_devices_.empty()) {
+      caps |= VIDEO_SEND;
+    }
+    return caps;
+  }
+  virtual bool GetAudioInputDevices(std::vector<Device>* devs) {
+    *devs = input_devices_;
+    return true;
+  }
+  virtual bool GetAudioOutputDevices(std::vector<Device>* devs) {
+    *devs = output_devices_;
+    return true;
+  }
+  virtual bool GetAudioInputDevice(const std::string& name, Device* out) {
+    return GetAudioDevice(true, name, out);
+  }
+  virtual bool GetAudioOutputDevice(const std::string& name, Device* out) {
+    return GetAudioDevice(false, name, out);
+  }
+  virtual bool GetVideoCaptureDevices(std::vector<Device>* devs) {
+    *devs = vidcap_devices_;
+    return true;
+  }
+  virtual void SetVideoCaptureDeviceMaxFormat(const std::string& usb_id,
+                                              const VideoFormat& max_format) {
+    max_formats_[usb_id] = max_format;
+  }
+  bool IsMaxFormatForDevice(const std::string& usb_id,
+                            const VideoFormat& max_format) const {
+    std::map<std::string, VideoFormat>::const_iterator found =
+        max_formats_.find(usb_id);
+    return (found != max_formats_.end()) ?
+        max_format == found->second :
+        false;
+  }
+  virtual void ClearVideoCaptureDeviceMaxFormat(const std::string& usb_id) {
+    max_formats_.erase(usb_id);
+  }
+  virtual VideoCapturer* CreateVideoCapturer(const Device& device) const {
+    return new FakeVideoCapturer();
+  }
+  virtual bool GetWindows(
+      std::vector<talk_base::WindowDescription>* descriptions) {
+    descriptions->clear();
+    const uint32_t id = 1u;  // Note that 0 is not a valid ID.
+    const talk_base::WindowId window_id =
+        talk_base::WindowId::Cast(id);
+    std::string title = "FakeWindow";
+    talk_base::WindowDescription window_description(window_id, title);
+    descriptions->push_back(window_description);
+    return true;
+  }
+  virtual VideoCapturer* CreateWindowCapturer(talk_base::WindowId window) {
+    if (!window.IsValid()) {
+      return NULL;
+    }
+    return new FakeVideoCapturer;
+  }
+  virtual bool GetDesktops(
+      std::vector<talk_base::DesktopDescription>* descriptions) {
+    descriptions->clear();
+    const int id = 0;
+    const int valid_index = 0;
+    const talk_base::DesktopId desktop_id =
+        talk_base::DesktopId::Cast(id, valid_index);
+    std::string title = "FakeDesktop";
+    talk_base::DesktopDescription desktop_description(desktop_id, title);
+    descriptions->push_back(desktop_description);
+    return true;
+  }
+  virtual VideoCapturer* CreateDesktopCapturer(talk_base::DesktopId desktop) {
+    if (!desktop.IsValid()) {
+      return NULL;
+    }
+    return new FakeVideoCapturer;
+  }
+
+  virtual bool GetDefaultVideoCaptureDevice(Device* device) {
+    if (vidcap_devices_.empty()) {
+      return false;
+    }
+    *device = vidcap_devices_[0];
+    return true;
+  }
+
+#ifdef OSX
+  bool QtKitToSgDevice(const std::string& qtkit_name, Device* out) {
+    out->name = qtkit_name;
+    out->id = "sg:" + qtkit_name;
+    return true;
+  }
+#endif
+
+  void SetAudioInputDevices(const std::vector<std::string>& devices) {
+    input_devices_.clear();
+    for (size_t i = 0; i < devices.size(); ++i) {
+      input_devices_.push_back(Device(devices[i], i));
+    }
+    SignalDevicesChange();
+  }
+  void SetAudioOutputDevices(const std::vector<std::string>& devices) {
+    output_devices_.clear();
+    for (size_t i = 0; i < devices.size(); ++i) {
+      output_devices_.push_back(Device(devices[i], i));
+    }
+    SignalDevicesChange();
+  }
+  void SetVideoCaptureDevices(const std::vector<std::string>& devices) {
+    vidcap_devices_.clear();
+    for (size_t i = 0; i < devices.size(); ++i) {
+      vidcap_devices_.push_back(Device(devices[i], i));
+    }
+    SignalDevicesChange();
+  }
+  virtual bool GetVideoCaptureDevice(const std::string& name,
+                                     Device* out) {
+    if (vidcap_devices_.empty())
+      return false;
+
+    // If the name is empty, return the default device.
+    if (name.empty() || name == kDefaultDeviceName) {
+      *out = vidcap_devices_[0];
+      return true;
+    }
+
+    return FindDeviceByName(vidcap_devices_, name, out);
+  }
+  bool GetAudioDevice(bool is_input, const std::string& name,
+                      Device* out) {
+    // If the name is empty, return the default device.
+    if (name.empty() || name == kDefaultDeviceName) {
+      *out = Device(name, -1);
+      return true;
+    }
+
+    return FindDeviceByName((is_input ? input_devices_ : output_devices_),
+                            name, out);
+  }
+  static bool FindDeviceByName(const std::vector<Device>& devices,
+                               const std::string& name,
+                               Device* out) {
+    for (std::vector<Device>::const_iterator it = devices.begin();
+         it != devices.end(); ++it) {
+      if (name == it->name) {
+        *out = *it;
+        return true;
+      }
+    }
+    return false;
+  }
+
+ private:
+  std::vector<Device> input_devices_;
+  std::vector<Device> output_devices_;
+  std::vector<Device> vidcap_devices_;
+  std::map<std::string, VideoFormat> max_formats_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_DEVICES_FAKEDEVICEMANAGER_H_
diff --git a/talk/media/devices/filevideocapturer.cc b/talk/media/devices/filevideocapturer.cc
new file mode 100644
index 0000000..8946fea
--- /dev/null
+++ b/talk/media/devices/filevideocapturer.cc
@@ -0,0 +1,366 @@
+// 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.
+//
+// Implementation of VideoRecorder and FileVideoCapturer.
+
+#include "talk/media/devices/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;
+const char* FileVideoCapturer::kVideoFileDevicePrefix = "video-file:";
+
+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 Device& device) {
+  if (!FileVideoCapturer::IsFileVideoCapturerDevice(device)) {
+    return false;
+  }
+  std::string filename(device.name);
+  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(device.id);
+  SetSupportedFormats(supported);
+  return true;
+}
+
+bool FileVideoCapturer::Init(const std::string& filename) {
+  return Init(FileVideoCapturer::CreateFileVideoCapturerDevice(filename));
+}
+
+CaptureState FileVideoCapturer::Start(const VideoFormat& capture_format) {
+  if (IsRunning()) {
+    LOG(LS_ERROR) << "The file video capturer is already running";
+    return CS_FAILED;
+  }
+
+  if (talk_base::SS_CLOSED == video_file_.GetState()) {
+    LOG(LS_ERROR) << "File not opened yet";
+    return CS_NO_DEVICE;
+  } else if (!video_file_.SetPosition(0)) {
+    LOG(LS_ERROR) << "Failed to seek back to beginning of the file";
+    return CS_FAILED;
+  }
+
+  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 CS_RUNNING;
+  } else {
+    LOG(LS_ERROR) << "File video capturer '" << GetId() << "' failed to start";
+    return CS_FAILED;
+  }
+}
+
+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;
+  size_t bytes_read;
+  int error;
+  sr = video_file_.Read(header,
+                        CapturedFrame::kFrameHeaderSize,
+                        &bytes_read,
+                        &error);
+  LOG(LS_VERBOSE) << "Read frame header: stream_result = " << sr
+                  << ", bytes read = " << bytes_read << ", error = " << error;
+  if (talk_base::SR_SUCCESS == sr) {
+    if (CapturedFrame::kFrameHeaderSize != bytes_read) {
+      return talk_base::SR_EOS;
+    }
+    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/media/devices/filevideocapturer.h b/talk/media/devices/filevideocapturer.h
new file mode 100644
index 0000000..e3e39b4
--- /dev/null
+++ b/talk/media/devices/filevideocapturer.h
@@ -0,0 +1,156 @@
+// 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.
+//
+// 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_MEDIA_DEVICES_FILEVIDEOCAPTURER_H_
+#define TALK_MEDIA_DEVICES_FILEVIDEOCAPTURER_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/stream.h"
+#include "talk/base/stringutils.h"
+#include "talk/media/base/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();
+
+  // Determines if the given device is actually a video file, to be captured
+  // with a FileVideoCapturer.
+  static bool IsFileVideoCapturerDevice(const Device& device) {
+    return talk_base::starts_with(device.id.c_str(), kVideoFileDevicePrefix);
+  }
+
+  // Creates a fake device for the given filename.
+  static Device CreateFileVideoCapturerDevice(const std::string& filename) {
+    std::stringstream id;
+    id << kVideoFileDevicePrefix << filename;
+    return Device(filename, id.str());
+  }
+
+  // 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;
+  }
+
+  // Initializes the capturer with the given file.
+  bool Init(const std::string& filename);
+
+  // Initializes the capturer with the given device. This should only be used
+  // if IsFileVideoCapturerDevice returned true for the given device.
+  bool Init(const Device& device);
+
+  // Override virtual methods of parent class VideoCapturer.
+  virtual CaptureState Start(const VideoFormat& capture_format);
+  virtual void Stop();
+  virtual bool IsRunning();
+  virtual bool IsScreencast() const { return false; }
+
+ 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.
+
+  static const char* kVideoFileDevicePrefix;
+  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_MEDIA_DEVICES_FILEVIDEOCAPTURER_H_
diff --git a/talk/media/devices/filevideocapturer_unittest.cc b/talk/media/devices/filevideocapturer_unittest.cc
new file mode 100644
index 0000000..489f98d
--- /dev/null
+++ b/talk/media/devices/filevideocapturer_unittest.cc
@@ -0,0 +1,203 @@
+/*
+ * 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 <stdio.h>
+
+#include <string>
+#include <vector>
+
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/thread.h"
+#include "talk/media/base/testutils.h"
+#include "talk/media/devices/filevideocapturer.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_TRUE(capturer_->GetSupportedFormats()->empty());
+  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::CS_RUNNING, 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::CS_RUNNING, 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);
+}
+
+TEST_F(FileVideoCapturerTest, TestPartialFrameHeader) {
+  EXPECT_TRUE(OpenFile("1.frame_plus_1.byte"));
+  VideoCapturerListener listener;
+  capturer_->SignalFrameCaptured.connect(
+      &listener, &VideoCapturerListener::OnFrameCaptured);
+  capturer_->set_repeat(0);
+  capture_format_ = capturer_->GetSupportedFormats()->at(0);
+  EXPECT_EQ(cricket::CS_RUNNING, capturer_->Start(capture_format_));
+  EXPECT_TRUE_WAIT(!capturer_->IsRunning(), 1000);
+  EXPECT_EQ(1, listener.frame_count());
+}
+
+TEST_F(FileVideoCapturerTest, TestFileDevices) {
+  cricket::Device not_a_file("I'm a camera", "with an id");
+  EXPECT_FALSE(
+      cricket::FileVideoCapturer::IsFileVideoCapturerDevice(not_a_file));
+  const std::string test_file =
+      cricket::GetTestFilePath("captured-320x240-2s-48.frames");
+  cricket::Device file_device =
+      cricket::FileVideoCapturer::CreateFileVideoCapturerDevice(test_file);
+  EXPECT_TRUE(
+      cricket::FileVideoCapturer::IsFileVideoCapturerDevice(file_device));
+  EXPECT_TRUE(capturer_->Init(file_device));
+  EXPECT_EQ(file_device.id, capturer_->GetId());
+}
+
+}  // unnamed namespace
diff --git a/talk/media/devices/gdivideorenderer.cc b/talk/media/devices/gdivideorenderer.cc
new file mode 100755
index 0000000..d5edfc6
--- /dev/null
+++ b/talk/media/devices/gdivideorenderer.cc
@@ -0,0 +1,266 @@
+// 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.
+//
+// Implementation of GdiVideoRenderer on Windows
+
+#ifdef WIN32
+
+#include "talk/media/devices/gdivideorenderer.h"
+
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/thread.h"
+#include "talk/base/win32window.h"
+#include "talk/media/base/videocommon.h"
+#include "talk/media/base/videoframe.h"
+
+namespace cricket {
+
+/////////////////////////////////////////////////////////////////////////////
+// Definition of private class VideoWindow. We use a worker thread to manage
+// the window.
+/////////////////////////////////////////////////////////////////////////////
+class GdiVideoRenderer::VideoWindow : public talk_base::Win32Window {
+ public:
+  VideoWindow(int x, int y, int width, int height);
+  virtual ~VideoWindow();
+
+  // Called when the video size changes. If it is called the first time, we
+  // create and start the thread. Otherwise, we send kSetSizeMsg to the thread.
+  // Context: non-worker thread.
+  bool SetSize(int width, int height);
+
+  // Called when a new frame is available. Upon this call, we send
+  // kRenderFrameMsg to the window thread. Context: non-worker thread. It may be
+  // better to pass RGB bytes to VideoWindow. However, we pass VideoFrame to put
+  // all the thread synchronization within VideoWindow.
+  bool RenderFrame(const VideoFrame* frame);
+
+ protected:
+  // Override virtual method of talk_base::Win32Window. Context: worker Thread.
+  virtual bool OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam,
+                         LRESULT& result);
+
+ private:
+  enum { kSetSizeMsg = WM_USER, kRenderFrameMsg};
+
+  class WindowThread : public talk_base::Thread {
+   public:
+    explicit WindowThread(VideoWindow* window) : window_(window) {}
+
+    // Override virtual method of talk_base::Thread. Context: worker Thread.
+    virtual void Run() {
+      // Initialize the window
+      if (!window_ || !window_->Initialize()) {
+        return;
+      }
+      // Run the message loop
+      MSG msg;
+      while (GetMessage(&msg, NULL, 0, 0) > 0) {
+        TranslateMessage(&msg);
+        DispatchMessage(&msg);
+      }
+    }
+
+  private:
+    VideoWindow* window_;
+  };
+
+  // Context: worker Thread.
+  bool Initialize();
+  void OnPaint();
+  void OnSize(int width, int height, bool frame_changed);
+  void OnRenderFrame(const VideoFrame* frame);
+
+  BITMAPINFO bmi_;
+  talk_base::scoped_array<uint8> image_;
+  talk_base::scoped_ptr<WindowThread> window_thread_;
+  // The initial position of the window.
+  int initial_x_;
+  int initial_y_;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+// Implementation of class VideoWindow
+/////////////////////////////////////////////////////////////////////////////
+GdiVideoRenderer::VideoWindow::VideoWindow(
+    int x, int y, int width, int height)
+    : initial_x_(x),
+      initial_y_(y) {
+  memset(&bmi_.bmiHeader, 0, sizeof(bmi_.bmiHeader));
+  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 * 4;
+
+  image_.reset(new uint8[bmi_.bmiHeader.biSizeImage]);
+}
+
+GdiVideoRenderer::VideoWindow::~VideoWindow() {
+  // Context: caller Thread. We cannot call Destroy() since the window was
+  // created by another thread. Instead, we send WM_CLOSE message.
+  if (handle()) {
+    SendMessage(handle(), WM_CLOSE, 0, 0);
+  }
+}
+
+bool GdiVideoRenderer::VideoWindow::SetSize(int width, int height) {
+  if (!window_thread_.get()) {
+    // Create and start the window thread.
+    window_thread_.reset(new WindowThread(this));
+    return window_thread_->Start();
+  } else if (width != bmi_.bmiHeader.biWidth ||
+      height != -bmi_.bmiHeader.biHeight) {
+    SendMessage(handle(), kSetSizeMsg, 0, MAKELPARAM(width, height));
+  }
+  return true;
+}
+
+bool GdiVideoRenderer::VideoWindow::RenderFrame(const VideoFrame* frame) {
+  if (!handle()) {
+    return false;
+  }
+
+  SendMessage(handle(), kRenderFrameMsg, reinterpret_cast<WPARAM>(frame), 0);
+  return true;
+}
+
+bool GdiVideoRenderer::VideoWindow::OnMessage(UINT uMsg, WPARAM wParam,
+                                              LPARAM lParam, LRESULT& result) {
+  switch (uMsg) {
+    case WM_PAINT:
+      OnPaint();
+      return true;
+
+    case WM_DESTROY:
+      PostQuitMessage(0);  // post WM_QUIT to end the message loop in Run()
+      return false;
+
+    case WM_SIZE:  // The window UI was resized.
+      OnSize(LOWORD(lParam), HIWORD(lParam), false);
+      return true;
+
+    case kSetSizeMsg:  // The video resolution changed.
+      OnSize(LOWORD(lParam), HIWORD(lParam), true);
+      return true;
+
+    case kRenderFrameMsg:
+      OnRenderFrame(reinterpret_cast<const VideoFrame*>(wParam));
+      return true;
+  }
+  return false;
+}
+
+bool GdiVideoRenderer::VideoWindow::Initialize() {
+  if (!talk_base::Win32Window::Create(
+      NULL, L"Video Renderer",
+      WS_OVERLAPPEDWINDOW | WS_SIZEBOX,
+      WS_EX_APPWINDOW,
+      initial_x_, initial_y_,
+      bmi_.bmiHeader.biWidth, -bmi_.bmiHeader.biHeight)) {
+        return false;
+  }
+  OnSize(bmi_.bmiHeader.biWidth, -bmi_.bmiHeader.biHeight, false);
+  return true;
+}
+
+void GdiVideoRenderer::VideoWindow::OnPaint() {
+  RECT rcClient;
+  GetClientRect(handle(), &rcClient);
+  PAINTSTRUCT ps;
+  HDC hdc = BeginPaint(handle(), &ps);
+  StretchDIBits(hdc,
+    0, 0, rcClient.right, rcClient.bottom,  // destination rect
+    0, 0, bmi_.bmiHeader.biWidth, -bmi_.bmiHeader.biHeight,  // source rect
+    image_.get(), &bmi_, DIB_RGB_COLORS, SRCCOPY);
+  EndPaint(handle(), &ps);
+}
+
+void GdiVideoRenderer::VideoWindow::OnSize(int width, int height,
+                                           bool frame_changed) {
+  // Get window and client sizes
+  RECT rcClient, rcWindow;
+  GetClientRect(handle(), &rcClient);
+  GetWindowRect(handle(), &rcWindow);
+
+  // Find offset between window size and client size
+  POINT ptDiff;
+  ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
+  ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
+
+  // Resize client
+  MoveWindow(handle(), rcWindow.left, rcWindow.top,
+             width + ptDiff.x, height + ptDiff.y, false);
+  UpdateWindow(handle());
+  ShowWindow(handle(), SW_SHOW);
+
+  if (frame_changed && (width != bmi_.bmiHeader.biWidth ||
+    height != -bmi_.bmiHeader.biHeight)) {
+    // Update the bmi and image buffer
+    bmi_.bmiHeader.biWidth = width;
+    bmi_.bmiHeader.biHeight = -height;
+    bmi_.bmiHeader.biSizeImage = width * height * 4;
+    image_.reset(new uint8[bmi_.bmiHeader.biSizeImage]);
+  }
+}
+
+void GdiVideoRenderer::VideoWindow::OnRenderFrame(const VideoFrame* frame) {
+  if (!frame) {
+    return;
+  }
+  // Convert frame to ARGB format, which is accepted by GDI
+  frame->ConvertToRgbBuffer(cricket::FOURCC_ARGB, image_.get(),
+                            bmi_.bmiHeader.biSizeImage,
+                            bmi_.bmiHeader.biWidth * 4);
+  InvalidateRect(handle(), 0, 0);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Implementation of class GdiVideoRenderer
+/////////////////////////////////////////////////////////////////////////////
+GdiVideoRenderer::GdiVideoRenderer(int x, int y)
+    : initial_x_(x),
+      initial_y_(y) {
+}
+GdiVideoRenderer::~GdiVideoRenderer() {}
+
+bool GdiVideoRenderer::SetSize(int width, int height, int reserved) {
+  if (!window_.get()) {  // Create the window for the first frame
+    window_.reset(new VideoWindow(initial_x_, initial_y_, width, height));
+  }
+  return window_->SetSize(width, height);
+}
+
+bool GdiVideoRenderer::RenderFrame(const VideoFrame* frame) {
+  if (!frame || !window_.get()) {
+    return false;
+  }
+  return window_->RenderFrame(frame);
+}
+
+}  // namespace cricket
+#endif  // WIN32
diff --git a/talk/media/devices/gdivideorenderer.h b/talk/media/devices/gdivideorenderer.h
new file mode 100755
index 0000000..da3897d
--- /dev/null
+++ b/talk/media/devices/gdivideorenderer.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.
+//
+// Definition of class GdiVideoRenderer that implements the abstract class
+// cricket::VideoRenderer via GDI on Windows.
+
+#ifndef TALK_MEDIA_DEVICES_GDIVIDEORENDERER_H_
+#define TALK_MEDIA_DEVICES_GDIVIDEORENDERER_H_
+
+#ifdef WIN32
+#include "talk/base/scoped_ptr.h"
+#include "talk/media/base/videorenderer.h"
+
+namespace cricket {
+
+class GdiVideoRenderer : public VideoRenderer {
+ public:
+  GdiVideoRenderer(int x, int y);
+  virtual ~GdiVideoRenderer();
+
+  // Implementation of pure virtual methods of VideoRenderer.
+  // These two methods may be executed in different threads.
+  // SetSize is called before RenderFrame.
+  virtual bool SetSize(int width, int height, int reserved);
+  virtual bool RenderFrame(const VideoFrame* frame);
+
+ private:
+  class VideoWindow;  // forward declaration, defined in the .cc file
+  talk_base::scoped_ptr<VideoWindow> window_;
+  // The initial position of the window.
+  int initial_x_;
+  int initial_y_;
+};
+
+}  // namespace cricket
+
+#endif  // WIN32
+#endif  // TALK_MEDIA_DEVICES_GDIVIDEORENDERER_H_
diff --git a/talk/media/devices/gtkvideorenderer.cc b/talk/media/devices/gtkvideorenderer.cc
new file mode 100755
index 0000000..a926879
--- /dev/null
+++ b/talk/media/devices/gtkvideorenderer.cc
@@ -0,0 +1,156 @@
+// 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.
+//
+// Implementation of GtkVideoRenderer
+
+#include "talk/media/devices/gtkvideorenderer.h"
+
+#include <glib.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#include "talk/media/base/videocommon.h"
+#include "talk/media/base/videoframe.h"
+
+namespace cricket {
+
+class ScopedGdkLock {
+ public:
+  ScopedGdkLock() {
+    gdk_threads_enter();
+  }
+
+  ~ScopedGdkLock() {
+    gdk_threads_leave();
+  }
+};
+
+GtkVideoRenderer::GtkVideoRenderer(int x, int y)
+    : window_(NULL),
+      draw_area_(NULL),
+      initial_x_(x),
+      initial_y_(y) {
+  g_type_init();
+  g_thread_init(NULL);
+  gdk_threads_init();
+}
+
+GtkVideoRenderer::~GtkVideoRenderer() {
+  if (window_) {
+    ScopedGdkLock lock;
+    gtk_widget_destroy(window_);
+    // Run the Gtk main loop to tear down the window.
+    Pump();
+  }
+  // Don't need to destroy draw_area_ because it is not top-level, so it is
+  // implicitly destroyed by the above.
+}
+
+bool GtkVideoRenderer::SetSize(int width, int height, int reserved) {
+  ScopedGdkLock lock;
+
+  // For the first frame, initialize the GTK window
+  if ((!window_ && !Initialize(width, height)) || IsClosed()) {
+    return false;
+  }
+
+  image_.reset(new uint8[width * height * 4]);
+  gtk_widget_set_size_request(draw_area_, width, height);
+  return true;
+}
+
+bool GtkVideoRenderer::RenderFrame(const VideoFrame* frame) {
+  if (!frame) {
+    return false;
+  }
+
+  // convert I420 frame to ABGR format, which is accepted by GTK
+  frame->ConvertToRgbBuffer(cricket::FOURCC_ABGR,
+                            image_.get(),
+                            frame->GetWidth() * frame->GetHeight() * 4,
+                            frame->GetWidth() * 4);
+
+  ScopedGdkLock lock;
+
+  if (IsClosed()) {
+    return false;
+  }
+
+  // draw the ABGR image
+  gdk_draw_rgb_32_image(draw_area_->window,
+                        draw_area_->style->fg_gc[GTK_STATE_NORMAL],
+                        0,
+                        0,
+                        frame->GetWidth(),
+                        frame->GetHeight(),
+                        GDK_RGB_DITHER_MAX,
+                        image_.get(),
+                        frame->GetWidth() * 4);
+
+  // Run the Gtk main loop to refresh the window.
+  Pump();
+  return true;
+}
+
+bool GtkVideoRenderer::Initialize(int width, int height) {
+  gtk_init(NULL, NULL);
+  window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+  draw_area_ = gtk_drawing_area_new();
+  if (!window_ || !draw_area_) {
+    return false;
+  }
+
+  gtk_window_set_position(GTK_WINDOW(window_), GTK_WIN_POS_CENTER);
+  gtk_window_set_title(GTK_WINDOW(window_), "Video Renderer");
+  gtk_window_set_resizable(GTK_WINDOW(window_), FALSE);
+  gtk_widget_set_size_request(draw_area_, width, height);
+  gtk_container_add(GTK_CONTAINER(window_), draw_area_);
+  gtk_widget_show_all(window_);
+  gtk_window_move(GTK_WINDOW(window_), initial_x_, initial_y_);
+
+  image_.reset(new uint8[width * height * 4]);
+  return true;
+}
+
+void GtkVideoRenderer::Pump() {
+  while (gtk_events_pending()) {
+    gtk_main_iteration();
+  }
+}
+
+bool GtkVideoRenderer::IsClosed() const {
+  if (!window_) {
+    // Not initialized yet, so hasn't been closed.
+    return false;
+  }
+
+  if (!GTK_IS_WINDOW(window_) || !GTK_IS_DRAWING_AREA(draw_area_)) {
+    return true;
+  }
+
+  return false;
+}
+
+}  // namespace cricket
diff --git a/talk/media/devices/gtkvideorenderer.h b/talk/media/devices/gtkvideorenderer.h
new file mode 100755
index 0000000..6276b51
--- /dev/null
+++ b/talk/media/devices/gtkvideorenderer.h
@@ -0,0 +1,69 @@
+// 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.
+//
+// Definition of class GtkVideoRenderer that implements the abstract class
+// cricket::VideoRenderer via GTK.
+
+#ifndef TALK_MEDIA_DEVICES_GTKVIDEORENDERER_H_
+#define TALK_MEDIA_DEVICES_GTKVIDEORENDERER_H_
+
+#include "talk/base/basictypes.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/media/base/videorenderer.h"
+
+typedef struct _GtkWidget GtkWidget;  // forward declaration, defined in gtk.h
+
+namespace cricket {
+
+class GtkVideoRenderer : public VideoRenderer {
+ public:
+  GtkVideoRenderer(int x, int y);
+  virtual ~GtkVideoRenderer();
+
+  // Implementation of pure virtual methods of VideoRenderer.
+  // These two methods may be executed in different threads.
+  // SetSize is called before RenderFrame.
+  virtual bool SetSize(int width, int height, int reserved);
+  virtual bool RenderFrame(const VideoFrame* frame);
+
+ private:
+  // Initialize the attributes when the first frame arrives.
+  bool Initialize(int width, int height);
+  // Pump the Gtk event loop until there are no events left.
+  void Pump();
+  // Check if the window has been closed.
+  bool IsClosed() const;
+
+  talk_base::scoped_array<uint8> image_;
+  GtkWidget* window_;
+  GtkWidget* draw_area_;
+  // The initial position of the window.
+  int initial_x_;
+  int initial_y_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_DEVICES_GTKVIDEORENDERER_H_
diff --git a/talk/media/devices/iosdeviceinfo.cc b/talk/media/devices/iosdeviceinfo.cc
new file mode 100644
index 0000000..8b65c14
--- /dev/null
+++ b/talk/media/devices/iosdeviceinfo.cc
@@ -0,0 +1,40 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/media/devices/deviceinfo.h"
+
+namespace cricket {
+
+bool GetUsbId(const Device& device, std::string* usb_id) {
+  return false;
+}
+
+bool GetUsbVersion(const Device& device, std::string* usb_version) {
+  return false;
+}
+
+}  // namespace cricket
diff --git a/talk/media/devices/libudevsymboltable.cc b/talk/media/devices/libudevsymboltable.cc
new file mode 100644
index 0000000..b1d9d31
--- /dev/null
+++ b/talk/media/devices/libudevsymboltable.cc
@@ -0,0 +1,40 @@
+/*
+ * 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/media/devices/libudevsymboltable.h"
+
+namespace cricket {
+
+#define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME LIBUDEV_SYMBOLS_CLASS_NAME
+#define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST LIBUDEV_SYMBOLS_LIST
+#define LATE_BINDING_SYMBOL_TABLE_DLL_NAME "libudev.so.0"
+#include "talk/base/latebindingsymboltable.cc.def"
+#undef LATE_BINDING_SYMBOL_TABLE_CLASS_NAME
+#undef LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST
+#undef LATE_BINDING_SYMBOL_TABLE_DLL_NAME
+
+}  // namespace cricket
diff --git a/talk/media/devices/libudevsymboltable.h b/talk/media/devices/libudevsymboltable.h
new file mode 100644
index 0000000..046a4b0
--- /dev/null
+++ b/talk/media/devices/libudevsymboltable.h
@@ -0,0 +1,71 @@
+/*
+ * 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_MEDIA_DEVICES_LIBUDEVSYMBOLTABLE_H_
+#define TALK_MEDIA_DEVICES_LIBUDEVSYMBOLTABLE_H_
+
+#include <libudev.h>
+
+#include "talk/base/latebindingsymboltable.h"
+
+namespace cricket {
+
+#define LIBUDEV_SYMBOLS_CLASS_NAME LibUDevSymbolTable
+// The libudev symbols we need, as an X-Macro list.
+// This list must contain precisely every libudev function that is used in
+// linuxdevicemanager.cc.
+#define LIBUDEV_SYMBOLS_LIST \
+  X(udev_device_get_devnode) \
+  X(udev_device_get_parent_with_subsystem_devtype) \
+  X(udev_device_get_sysattr_value) \
+  X(udev_device_new_from_syspath) \
+  X(udev_device_unref) \
+  X(udev_enumerate_add_match_subsystem) \
+  X(udev_enumerate_get_list_entry) \
+  X(udev_enumerate_new) \
+  X(udev_enumerate_scan_devices) \
+  X(udev_enumerate_unref) \
+  X(udev_list_entry_get_name) \
+  X(udev_list_entry_get_next) \
+  X(udev_monitor_enable_receiving) \
+  X(udev_monitor_filter_add_match_subsystem_devtype) \
+  X(udev_monitor_get_fd) \
+  X(udev_monitor_new_from_netlink) \
+  X(udev_monitor_receive_device) \
+  X(udev_monitor_unref) \
+  X(udev_new) \
+  X(udev_unref)
+
+#define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME LIBUDEV_SYMBOLS_CLASS_NAME
+#define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST LIBUDEV_SYMBOLS_LIST
+#include "talk/base/latebindingsymboltable.h.def"
+#undef LATE_BINDING_SYMBOL_TABLE_CLASS_NAME
+#undef LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_DEVICES_LIBUDEVSYMBOLTABLE_H_
diff --git a/talk/media/devices/linuxdeviceinfo.cc b/talk/media/devices/linuxdeviceinfo.cc
new file mode 100644
index 0000000..7b52001
--- /dev/null
+++ b/talk/media/devices/linuxdeviceinfo.cc
@@ -0,0 +1,173 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/media/devices/deviceinfo.h"
+
+#include "talk/base/common.h"  // for ASSERT
+#include "talk/media/devices/libudevsymboltable.h"
+
+namespace cricket {
+
+class ScopedLibUdev {
+ public:
+  static ScopedLibUdev* Create() {
+    ScopedLibUdev* ret_val = new ScopedLibUdev();
+    if (!ret_val->Init()) {
+      delete ret_val;
+      return NULL;
+    }
+    return ret_val;
+  }
+  ~ScopedLibUdev() {
+    libudev_.Unload();
+  }
+
+  LibUDevSymbolTable* instance() { return &libudev_; }
+
+ private:
+  ScopedLibUdev() {}
+
+  bool Init() {
+    return libudev_.Load();
+  }
+
+  LibUDevSymbolTable libudev_;
+};
+
+class ScopedUdev {
+ public:
+  explicit ScopedUdev(LibUDevSymbolTable* libudev) : libudev_(libudev) {
+    udev_ = libudev_->udev_new()();
+  }
+  ~ScopedUdev() {
+    if (udev_) libudev_->udev_unref()(udev_);
+  }
+
+  udev* instance() { return udev_; }
+
+ private:
+  LibUDevSymbolTable* libudev_;
+  udev* udev_;
+};
+
+class ScopedUdevEnumerate {
+ public:
+  ScopedUdevEnumerate(LibUDevSymbolTable* libudev, udev* udev)
+      : libudev_(libudev) {
+    enumerate_ = libudev_->udev_enumerate_new()(udev);
+  }
+  ~ScopedUdevEnumerate() {
+    if (enumerate_) libudev_->udev_enumerate_unref()(enumerate_);
+  }
+
+  udev_enumerate* instance() { return enumerate_; }
+
+ private:
+  LibUDevSymbolTable* libudev_;
+  udev_enumerate* enumerate_;
+};
+
+bool GetUsbProperty(const Device& device, const char* property_name,
+                    std::string* property) {
+  talk_base::scoped_ptr<ScopedLibUdev> libudev_context(ScopedLibUdev::Create());
+  if (!libudev_context) {
+    return false;
+  }
+  ScopedUdev udev_context(libudev_context->instance());
+  if (!udev_context.instance()) {
+    return false;
+  }
+  ScopedUdevEnumerate enumerate_context(libudev_context->instance(),
+                                        udev_context.instance());
+  if (!enumerate_context.instance()) {
+    return false;
+  }
+  libudev_context->instance()->udev_enumerate_add_match_subsystem()(
+      enumerate_context.instance(), "video4linux");
+  libudev_context->instance()->udev_enumerate_scan_devices()(
+      enumerate_context.instance());
+  udev_list_entry* devices =
+      libudev_context->instance()->udev_enumerate_get_list_entry()(
+          enumerate_context.instance());
+  if (!devices) {
+    return false;
+  }
+  udev_list_entry* dev_list_entry = NULL;
+  const char* property_value = NULL;
+  // Macro that expands to a for-loop over the devices.
+  for (dev_list_entry = devices; dev_list_entry != NULL;
+       dev_list_entry = libudev_context->instance()->
+           udev_list_entry_get_next()(dev_list_entry)) {
+    const char* path = libudev_context->instance()->udev_list_entry_get_name()(
+        dev_list_entry);
+    if (!path) continue;
+    udev_device* dev =
+        libudev_context->instance()->udev_device_new_from_syspath()(
+            udev_context.instance(), path);
+    if (!dev) continue;
+    const char* device_node =
+        libudev_context->instance()->udev_device_get_devnode()(dev);
+    if (!device_node || device.id.compare(device_node) != 0) {
+      continue;
+    }
+    dev = libudev_context->instance()->
+        udev_device_get_parent_with_subsystem_devtype()(
+            dev, "usb", "usb_device");
+    if (!dev) continue;
+    property_value = libudev_context->instance()->
+        udev_device_get_sysattr_value()(
+            dev, property_name);
+    break;
+  }
+  if (!property_value) {
+    return false;
+  }
+  property->assign(property_value);
+  return true;
+}
+
+bool GetUsbId(const Device& device, std::string* usb_id) {
+  std::string id_vendor;
+  std::string id_product;
+  if (!GetUsbProperty(device, "idVendor", &id_vendor)) {
+    return false;
+  }
+  if (!GetUsbProperty(device, "idProduct", &id_product)) {
+    return false;
+  }
+  usb_id->clear();
+  usb_id->append(id_vendor);
+  usb_id->append(":");
+  usb_id->append(id_product);
+  return true;
+}
+
+bool GetUsbVersion(const Device& device, std::string* usb_version) {
+  return GetUsbProperty(device, "version", usb_version);
+}
+
+}  // namespace cricket
diff --git a/talk/media/devices/linuxdevicemanager.cc b/talk/media/devices/linuxdevicemanager.cc
new file mode 100644
index 0000000..2096eeb
--- /dev/null
+++ b/talk/media/devices/linuxdevicemanager.cc
@@ -0,0 +1,406 @@
+/*
+ * 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/media/devices/linuxdevicemanager.h"
+
+#include <unistd.h>
+#include "talk/base/fileutils.h"
+#include "talk/base/linux.h"
+#include "talk/base/logging.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/media/base/mediacommon.h"
+#include "talk/media/devices/libudevsymboltable.h"
+#include "talk/media/devices/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_;
+};
+
+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 webrtc 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) {
+    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_ = libudev_.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_ = libudev_.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 (libudev_.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 (libudev_.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_) {
+    libudev_.udev_monitor_unref()(udev_monitor_);
+    udev_monitor_ = NULL;
+  }
+  if (udev_) {
+    libudev_.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 = libudev_.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.
+  libudev_.udev_device_unref()(device);
+  manager_->SignalDevicesChange();
+}
+
+int LinuxDeviceWatcher::GetDescriptor() {
+  return libudev_.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/media/devices/linuxdevicemanager.h b/talk/media/devices/linuxdevicemanager.h
new file mode 100644
index 0000000..d8f1665
--- /dev/null
+++ b/talk/media/devices/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_MEDIA_DEVICES_LINUXDEVICEMANAGER_H_
+#define TALK_MEDIA_DEVICES_LINUXDEVICEMANAGER_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/sigslot.h"
+#include "talk/base/stringencode.h"
+#include "talk/media/devices/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_MEDIA_DEVICES_LINUXDEVICEMANAGER_H_
diff --git a/talk/media/devices/macdeviceinfo.cc b/talk/media/devices/macdeviceinfo.cc
new file mode 100644
index 0000000..f34932d
--- /dev/null
+++ b/talk/media/devices/macdeviceinfo.cc
@@ -0,0 +1,56 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/media/devices/deviceinfo.h"
+
+namespace cricket {
+
+bool GetUsbId(const Device& device, std::string* usb_id) {
+  // Both PID and VID are 4 characters.
+  const int id_size = 4;
+  if (device.id.size() < 2 * id_size) {
+    return false;
+  }
+
+  // The last characters of device id is a concatenation of VID and then PID.
+  const size_t vid_location = device.id.size() - 2 * id_size;
+  std::string id_vendor = device.id.substr(vid_location, id_size);
+  const size_t pid_location = device.id.size() - id_size;
+  std::string id_product = device.id.substr(pid_location, id_size);
+
+  usb_id->clear();
+  usb_id->append(id_vendor);
+  usb_id->append(":");
+  usb_id->append(id_product);
+  return true;
+}
+
+bool GetUsbVersion(const Device& device, std::string* usb_version) {
+  return false;
+}
+
+}  // namespace cricket
diff --git a/talk/media/devices/macdevicemanager.cc b/talk/media/devices/macdevicemanager.cc
new file mode 100644
index 0000000..10d85a0
--- /dev/null
+++ b/talk/media/devices/macdevicemanager.cc
@@ -0,0 +1,197 @@
+/*
+ * 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/media/devices/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/media/base/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(tommyw): 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[] =  {
+    "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(ronghuawu): 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/media/devices/macdevicemanager.h b/talk/media/devices/macdevicemanager.h
new file mode 100644
index 0000000..25fe4fc
--- /dev/null
+++ b/talk/media/devices/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_MEDIA_DEVICES_MACDEVICEMANAGER_H_
+#define TALK_MEDIA_DEVICES_MACDEVICEMANAGER_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/sigslot.h"
+#include "talk/base/stringencode.h"
+#include "talk/media/devices/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_MEDIA_DEVICES_MACDEVICEMANAGER_H_
diff --git a/talk/media/devices/macdevicemanagermm.mm b/talk/media/devices/macdevicemanagermm.mm
new file mode 100644
index 0000000..8cc7751
--- /dev/null
+++ b/talk/media/devices/macdevicemanagermm.mm
@@ -0,0 +1,140 @@
+/*
+ * 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.
+ */
+
+// support GCC compiler
+#ifndef __has_feature
+#  define __has_feature(x) 0
+#endif
+
+#include "talk/media/devices/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];
+#if !__has_feature(objc_arc)
+  [super dealloc];
+#endif
+}
+- (void)onDevicesChanged:(NSNotification *)notification {
+  manager_->SignalDevicesChange();
+}
+@end
+
+namespace cricket {
+
+DeviceWatcherImpl* CreateDeviceWatcherCallback(
+    DeviceManagerInterface* manager) {
+  DeviceWatcherImpl* impl;
+#if !__has_feature(objc_arc)
+  NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+#else
+  @autoreleasepool
+#endif
+  {
+    impl = [[DeviceWatcherImpl alloc] init:manager];
+  }
+#if !__has_feature(objc_arc)
+  [pool drain];
+#endif
+  return impl;
+}
+
+void ReleaseDeviceWatcherCallback(DeviceWatcherImpl* watcher) {
+#if !__has_feature(objc_arc)
+  NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+  [watcher release];
+  [pool drain];
+#endif
+}
+
+bool GetQTKitVideoDevices(std::vector<Device>* devices) {
+#if !__has_feature(objc_arc)
+  NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+#else
+  @autoreleasepool
+#endif
+  {
+    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]));
+    }
+  }
+#if !__has_feature(objc_arc)
+  [pool drain];
+#endif
+  return true;
+}
+
+}  // namespace cricket
diff --git a/talk/media/devices/mobiledevicemanager.cc b/talk/media/devices/mobiledevicemanager.cc
new file mode 100644
index 0000000..a08911b
--- /dev/null
+++ b/talk/media/devices/mobiledevicemanager.cc
@@ -0,0 +1,76 @@
+/*
+ * libjingle
+ * Copyright 2013 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/media/devices/devicemanager.h"
+#include "webrtc/modules/video_capture/include/video_capture_factory.h"
+
+namespace cricket {
+
+class MobileDeviceManager : public DeviceManager {
+ public:
+  MobileDeviceManager();
+  virtual ~MobileDeviceManager();
+  virtual bool GetVideoCaptureDevices(std::vector<Device>* devs);
+};
+
+MobileDeviceManager::MobileDeviceManager() {
+  // We don't expect available devices to change on Android/iOS, so use a
+  // do-nothing watcher.
+  set_watcher(new DeviceWatcher(this));
+}
+
+MobileDeviceManager::~MobileDeviceManager() {}
+
+bool MobileDeviceManager::GetVideoCaptureDevices(std::vector<Device>* devs) {
+  devs->clear();
+  talk_base::scoped_ptr<webrtc::VideoCaptureModule::DeviceInfo> info(
+      webrtc::VideoCaptureFactory::CreateDeviceInfo(0));
+  if (!info)
+    return false;
+
+  uint32 num_cams = info->NumberOfDevices();
+  char id[256];
+  char name[256];
+  for (uint32 i = 0; i < num_cams; ++i) {
+    if (info->GetDeviceName(i, name, ARRAY_SIZE(name), id, ARRAY_SIZE(id)))
+      continue;
+    devs->push_back(Device(name, id));
+  }
+  return true;
+}
+
+DeviceManagerInterface* DeviceManagerFactory::Create() {
+  return new MobileDeviceManager();
+}
+
+bool GetUsbId(const Device& device, std::string* usb_id) { return false; }
+
+bool GetUsbVersion(const Device& device, std::string* usb_version) {
+  return false;
+}
+
+}  // namespace cricket
diff --git a/talk/media/devices/v4llookup.cc b/talk/media/devices/v4llookup.cc
new file mode 100644
index 0000000..ff128a4
--- /dev/null
+++ b/talk/media/devices/v4llookup.cc
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2009 Google Inc.
+ * Author: lexnikitin@google.com (Alexey Nikitin)
+ *
+ * V4LLookup provides basic functionality to work with V2L2 devices in Linux
+ * The functionality is implemented as a class with virtual methods for
+ * the purpose of unit testing.
+ */
+#include "talk/media/devices/v4llookup.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/types.h>
+#include <linux/videodev2.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <cstring>
+
+#include "talk/base/logging.h"
+
+namespace cricket {
+
+V4LLookup *V4LLookup::v4l_lookup_ = NULL;
+
+bool V4LLookup::CheckIsV4L2Device(const std::string& device_path) {
+  // check device major/minor numbers are in the range for video devices.
+  struct stat s;
+
+  if (lstat(device_path.c_str(), &s) != 0 || !S_ISCHR(s.st_mode)) return false;
+
+  int video_fd = -1;
+  bool is_v4l2 = false;
+
+  // check major/minur device numbers are in range for video device
+  if (major(s.st_rdev) == 81) {
+    dev_t num = minor(s.st_rdev);
+    if (num <= 63) {
+      video_fd = ::open(device_path.c_str(), O_RDONLY | O_NONBLOCK);
+      if ((video_fd >= 0) || (errno == EBUSY)) {
+        ::v4l2_capability video_caps;
+        memset(&video_caps, 0, sizeof(video_caps));
+
+        if ((errno == EBUSY) ||
+            (::ioctl(video_fd, VIDIOC_QUERYCAP, &video_caps) >= 0 &&
+            (video_caps.capabilities & V4L2_CAP_VIDEO_CAPTURE))) {
+          LOG(LS_INFO) << "Found V4L2 capture device " << device_path;
+
+          is_v4l2 = true;
+        } else {
+          LOG(LS_ERROR) << "VIDIOC_QUERYCAP failed for " << device_path;
+        }
+      } else {
+        LOG(LS_ERROR) << "Failed to open " << device_path;
+      }
+    }
+  }
+
+  if (video_fd >= 0)
+    ::close(video_fd);
+
+  return is_v4l2;
+}
+
+};  // namespace cricket
diff --git a/talk/media/devices/v4llookup.h b/talk/media/devices/v4llookup.h
new file mode 100644
index 0000000..026eb0e
--- /dev/null
+++ b/talk/media/devices/v4llookup.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2009 Google Inc.
+ * Author: lexnikitin@google.com (Alexey Nikitin)
+ *
+ * V4LLookup provides basic functionality to work with V2L2 devices in Linux
+ * The functionality is implemented as a class with virtual methods for
+ * the purpose of unit testing.
+ */
+#ifndef TALK_MEDIA_DEVICES_V4LLOOKUP_H_
+#define TALK_MEDIA_DEVICES_V4LLOOKUP_H_
+
+#include <string>
+
+#ifdef LINUX
+namespace cricket {
+class V4LLookup {
+ public:
+  virtual ~V4LLookup() {}
+
+  static bool IsV4L2Device(const std::string& device_path) {
+    return GetV4LLookup()->CheckIsV4L2Device(device_path);
+  }
+
+  static void SetV4LLookup(V4LLookup* v4l_lookup) {
+    v4l_lookup_ = v4l_lookup;
+  }
+
+  static V4LLookup* GetV4LLookup() {
+    if (!v4l_lookup_) {
+      v4l_lookup_ = new V4LLookup();
+    }
+    return v4l_lookup_;
+  }
+
+ protected:
+  static V4LLookup* v4l_lookup_;
+  // Making virtual so it is easier to mock
+  virtual bool CheckIsV4L2Device(const std::string& device_path);
+};
+
+}  // namespace cricket
+
+#endif  // LINUX
+#endif  // TALK_MEDIA_DEVICES_V4LLOOKUP_H_
diff --git a/talk/media/devices/videorendererfactory.h b/talk/media/devices/videorendererfactory.h
new file mode 100644
index 0000000..64033c9
--- /dev/null
+++ b/talk/media/devices/videorendererfactory.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.
+//
+// A factory to create a GUI video renderer.
+
+#ifndef TALK_MEDIA_DEVICES_VIDEORENDERERFACTORY_H_
+#define TALK_MEDIA_DEVICES_VIDEORENDERERFACTORY_H_
+
+#include "talk/media/base/videorenderer.h"
+#if defined(LINUX) && defined(HAVE_GTK)
+#include "talk/media/devices/gtkvideorenderer.h"
+#elif defined(OSX) && !defined(CARBON_DEPRECATED)
+#include "talk/media/devices/carbonvideorenderer.h"
+#elif defined(WIN32)
+#include "talk/media/devices/gdivideorenderer.h"
+#endif
+
+namespace cricket {
+
+class VideoRendererFactory {
+ public:
+  static VideoRenderer* CreateGuiVideoRenderer(int x, int y) {
+  #if defined(LINUX) && defined(HAVE_GTK)
+    return new GtkVideoRenderer(x, y);
+  #elif defined(OSX) && !defined(CARBON_DEPRECATED)
+    CarbonVideoRenderer* renderer = new CarbonVideoRenderer(x, y);
+    // Needs to be initialized on the main thread.
+    if (renderer->Initialize()) {
+      return renderer;
+    } else {
+      delete renderer;
+      return NULL;
+    }
+  #elif defined(WIN32)
+    return new GdiVideoRenderer(x, y);
+  #else
+    return NULL;
+  #endif
+  }
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_DEVICES_VIDEORENDERERFACTORY_H_
diff --git a/talk/media/devices/win32deviceinfo.cc b/talk/media/devices/win32deviceinfo.cc
new file mode 100644
index 0000000..61a7759
--- /dev/null
+++ b/talk/media/devices/win32deviceinfo.cc
@@ -0,0 +1,62 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/media/devices/deviceinfo.h"
+
+namespace cricket {
+
+bool GetUsbId(const Device& device, std::string* usb_id) {
+  // Both PID and VID are 4 characters.
+  const int id_size = 4;
+  const char vid[] = "vid_";  // Also contains '\0'.
+  const size_t vid_location = device.id.find(vid);
+  if (vid_location == std::string::npos ||
+      vid_location + sizeof(vid) - 1 + id_size > device.id.size()) {
+    return false;
+  }
+  const char pid[] = "pid_";
+  const size_t pid_location = device.id.find(pid);
+  if (pid_location == std::string::npos ||
+      pid_location + sizeof(pid) - 1 + id_size > device.id.size()) {
+    return false;
+  }
+  std::string id_vendor = device.id.substr(vid_location + sizeof(vid) - 1,
+                                           id_size);
+  std::string id_product = device.id.substr(pid_location + sizeof(pid) -1,
+                                            id_size);
+  usb_id->clear();
+  usb_id->append(id_vendor);
+  usb_id->append(":");
+  usb_id->append(id_product);
+  return true;
+}
+
+bool GetUsbVersion(const Device& device, std::string* usb_version) {
+  return false;
+}
+
+}  // namespace cricket
diff --git a/talk/media/devices/win32devicemanager.cc b/talk/media/devices/win32devicemanager.cc
new file mode 100644
index 0000000..071f111
--- /dev/null
+++ b/talk/media/devices/win32devicemanager.cc
@@ -0,0 +1,404 @@
+/*
+ * 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/media/devices/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/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+#include "talk/base/win32.h"  // ToUtf8
+#include "talk/base/win32window.h"
+#include "talk/media/base/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[] =  {
+    "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/media/devices/win32devicemanager.h b/talk/media/devices/win32devicemanager.h
new file mode 100644
index 0000000..4854ec0
--- /dev/null
+++ b/talk/media/devices/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_MEDIA_DEVICES_WIN32DEVICEMANAGER_H_
+#define TALK_MEDIA_DEVICES_WIN32DEVICEMANAGER_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/sigslot.h"
+#include "talk/base/stringencode.h"
+#include "talk/media/devices/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_MEDIA_DEVICES_WIN32DEVICEMANAGER_H_
diff --git a/talk/media/other/linphonemediaengine.cc b/talk/media/other/linphonemediaengine.cc
new file mode 100644
index 0000000..3b97c0b
--- /dev/null
+++ b/talk/media/other/linphonemediaengine.cc
@@ -0,0 +1,276 @@
+/*
+ * 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 MSILBC_LIBRARY
+#define MSILBC_LIBRARY "/usr/lib/mediastreamer/plugins/libmsilbc.so"
+#endif
+
+// LinphoneMediaEngine is a Linphone implementation of MediaEngine
+extern "C" {
+#include <mediastreamer2/mediastream.h>
+#include <mediastreamer2/mssndcard.h>
+#include <mediastreamer2/msfilter.h>
+}
+
+#include "talk/media/other/linphonemediaengine.h"
+
+#include "talk/base/buffer.h"
+#include "talk/base/event.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+#include "talk/media/base/rtpdump.h"
+
+#ifndef WIN32
+#include <libgen.h>
+#endif
+
+namespace cricket {
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of LinphoneMediaEngine.
+///////////////////////////////////////////////////////////////////////////
+LinphoneMediaEngine::LinphoneMediaEngine(const std::string& ringWav,  const std::string& callWav) : ring_wav_(ringWav), call_wav_(callWav) { }
+
+bool LinphoneMediaEngine::Init() {
+  ortp_init();
+  ms_init();
+
+#ifdef HAVE_ILBC
+#ifndef WIN32
+  char * path = strdup(MSILBC_LIBRARY);
+  char * dirc = dirname(path);
+  ms_load_plugins(dirc);
+#endif
+  if (ms_filter_codec_supported("iLBC"))
+    have_ilbc = 1;
+  else
+    have_ilbc = 0;
+#else
+  have_ilbc = 0;
+#endif
+
+#ifdef HAVE_SPEEX
+  voice_codecs_.push_back(AudioCodec(110, payload_type_speex_wb.mime_type, payload_type_speex_wb.clock_rate, 0, 1, 8));
+  voice_codecs_.push_back(AudioCodec(111, payload_type_speex_nb.mime_type, payload_type_speex_nb.clock_rate, 0, 1, 7));
+#endif
+
+#ifdef HAVE_ILBC
+  if (have_ilbc)
+    voice_codecs_.push_back(AudioCodec(102, payload_type_ilbc.mime_type, payload_type_ilbc.clock_rate, 0, 1, 4));
+#endif
+
+  voice_codecs_.push_back(AudioCodec(0, payload_type_pcmu8000.mime_type, payload_type_pcmu8000.clock_rate, 0, 1, 2));
+  voice_codecs_.push_back(AudioCodec(101, payload_type_telephone_event.mime_type, payload_type_telephone_event.clock_rate, 0, 1, 1));
+  return true;
+}
+
+void LinphoneMediaEngine::Terminate() {
+  fflush(stdout);
+}
+
+
+int LinphoneMediaEngine::GetCapabilities() {
+  int capabilities = 0;
+  capabilities |= AUDIO_SEND;
+  capabilities |= AUDIO_RECV;
+  return capabilities;
+}
+
+VoiceMediaChannel* LinphoneMediaEngine::CreateChannel() {
+  return new LinphoneVoiceChannel(this);
+}
+
+VideoMediaChannel* LinphoneMediaEngine::CreateVideoChannel(VoiceMediaChannel* voice_ch) {
+  return NULL;
+}
+
+bool LinphoneMediaEngine::FindAudioCodec(const AudioCodec &c) {
+  if (c.id == 0)
+    return true;
+  if (c.name == payload_type_telephone_event.mime_type)
+    return true;
+#ifdef HAVE_SPEEX
+  if (c.name == payload_type_speex_wb.mime_type && c.clockrate == payload_type_speex_wb.clock_rate)
+    return true;
+  if (c.name == payload_type_speex_nb.mime_type && c.clockrate == payload_type_speex_nb.clock_rate)
+    return true;
+#endif
+#ifdef HAVE_ILBC
+  if (have_ilbc && c.name == payload_type_ilbc.mime_type)
+    return true;
+#endif
+  return false;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of LinphoneVoiceChannel.
+///////////////////////////////////////////////////////////////////////////
+LinphoneVoiceChannel::LinphoneVoiceChannel(LinphoneMediaEngine*eng)
+    : pt_(-1),
+      audio_stream_(0),
+      engine_(eng),
+      ring_stream_(0)
+{
+
+  talk_base::Thread *thread = talk_base::ThreadManager::CurrentThread();
+  talk_base::SocketServer *ss = thread->socketserver();
+  socket_.reset(ss->CreateAsyncSocket(SOCK_DGRAM));
+
+  socket_->Bind(talk_base::SocketAddress("localhost",3000));
+  socket_->SignalReadEvent.connect(this, &LinphoneVoiceChannel::OnIncomingData);
+
+}
+
+LinphoneVoiceChannel::~LinphoneVoiceChannel()
+{
+  fflush(stdout);
+  StopRing();
+
+  if (audio_stream_)
+    audio_stream_stop(audio_stream_);
+}
+
+bool LinphoneVoiceChannel::SetPlayout(bool playout) {
+  play_ = playout;
+  return true;
+}
+
+bool LinphoneVoiceChannel::SetSendCodecs(const std::vector<AudioCodec>& codecs) {
+
+  bool first = true;
+  std::vector<AudioCodec>::const_iterator i;
+
+  ortp_set_log_level_mask(ORTP_MESSAGE|ORTP_WARNING|ORTP_ERROR|ORTP_FATAL);
+
+  for (i = codecs.begin(); i < codecs.end(); i++) {
+
+    if (!engine_->FindAudioCodec(*i))
+      continue;
+#ifdef HAVE_ILBC
+    if (engine_->have_ilbc && i->name == payload_type_ilbc.mime_type) {
+      rtp_profile_set_payload(&av_profile, i->id, &payload_type_ilbc);
+    }
+#endif
+#ifdef HAVE_SPEEX
+    if (i->name == payload_type_speex_wb.mime_type && i->clockrate == payload_type_speex_wb.clock_rate) {
+      rtp_profile_set_payload(&av_profile, i->id, &payload_type_speex_wb);
+    } else if (i->name == payload_type_speex_nb.mime_type && i->clockrate == payload_type_speex_nb.clock_rate) {
+      rtp_profile_set_payload(&av_profile, i->id, &payload_type_speex_nb);
+    }
+#endif
+
+    if (i->id == 0)
+      rtp_profile_set_payload(&av_profile, 0, &payload_type_pcmu8000);
+
+    if (i->name == payload_type_telephone_event.mime_type) {
+      rtp_profile_set_payload(&av_profile, i->id, &payload_type_telephone_event);
+    }
+
+    if (first) {
+      StopRing();
+      LOG(LS_INFO) << "Using " << i->name << "/" << i->clockrate;
+      pt_ = i->id;
+      audio_stream_ = audio_stream_start(&av_profile, 2000, "127.0.0.1", 3000, i->id, 250, 0);
+      first = false;
+    }
+  }
+
+  if (first) {
+    StopRing();
+    // We're being asked to set an empty list of codecs. This will only happen when
+    // working with a buggy client; let's try PCMU.
+    LOG(LS_WARNING) << "Received empty list of codces; using PCMU/8000";
+    audio_stream_ = audio_stream_start(&av_profile, 2000, "127.0.0.1", 3000, 0, 250, 0);
+  }
+
+  return true;
+}
+
+bool LinphoneVoiceChannel::SetSend(SendFlags flag) {
+  mute_ = !flag;
+  return true;
+}
+
+void LinphoneVoiceChannel::OnPacketReceived(talk_base::Buffer* packet) {
+  const void* data = packet->data();
+  int len = packet->length();
+  uint8 buf[2048];
+  memcpy(buf, data, len);
+
+  /* We may receive packets with payload type 13: comfort noise. Linphone can't
+   * handle them, so let's ignore those packets.
+   */
+  int payloadtype = buf[1] & 0x7f;
+  if (play_ && payloadtype != 13)
+    socket_->SendTo(buf, len, talk_base::SocketAddress("localhost",2000));
+}
+
+void LinphoneVoiceChannel::StartRing(bool bIncomingCall)
+{
+  MSSndCard *sndcard = NULL;
+  sndcard=ms_snd_card_manager_get_default_card(ms_snd_card_manager_get());
+  if (sndcard)
+  {
+    if (bIncomingCall)
+    {
+      if (engine_->GetRingWav().size() > 0)
+      {
+        LOG(LS_VERBOSE) << "incoming ring. sound file: " << engine_->GetRingWav().c_str() << "\n";
+        ring_stream_ = ring_start (engine_->GetRingWav().c_str(), 1, sndcard);
+      }
+    }
+    else
+    {
+      if (engine_->GetCallWav().size() > 0)
+      {
+        LOG(LS_VERBOSE) << "outgoing ring. sound file: " << engine_->GetCallWav().c_str() << "\n";
+        ring_stream_ = ring_start (engine_->GetCallWav().c_str(), 1, sndcard);
+      }
+    }
+  }
+}
+
+void LinphoneVoiceChannel::StopRing()
+{
+  if (ring_stream_) {
+    ring_stop(ring_stream_);
+    ring_stream_ = 0;
+  }
+}
+
+void LinphoneVoiceChannel::OnIncomingData(talk_base::AsyncSocket *s)
+{
+  char *buf[2048];
+  int len;
+  len = s->Recv(buf, sizeof(buf));
+  talk_base::Buffer packet(buf, len);
+  if (network_interface_ && !mute_)
+    network_interface_->SendPacket(&packet);
+}
+
+}
diff --git a/talk/media/other/linphonemediaengine.h b/talk/media/other/linphonemediaengine.h
new file mode 100644
index 0000000..69b2d2f
--- /dev/null
+++ b/talk/media/other/linphonemediaengine.h
@@ -0,0 +1,173 @@
+/*
+ * 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.
+ */
+
+// LinphoneMediaEngine is a Linphone implementation of MediaEngine
+
+#ifndef TALK_SESSION_PHONE_LINPHONEMEDIAENGINE_H_
+#define TALK_SESSION_PHONE_LINPHONEMEDIAENGINE_H_
+
+#include <string>
+#include <vector>
+
+extern "C" {
+#include <mediastreamer2/mediastream.h>
+}
+
+#include "talk/base/scoped_ptr.h"
+#include "talk/media/base/codec.h"
+#include "talk/media/base/mediachannel.h"
+#include "talk/media/base/mediaengine.h"
+
+namespace talk_base {
+class StreamInterface;
+}
+
+namespace cricket {
+
+class LinphoneMediaEngine : public MediaEngineInterface {
+ public:
+  LinphoneMediaEngine(const std::string& ringWav,  const std::string& callWav);
+  virtual ~LinphoneMediaEngine() {}
+
+  // Should be called before codecs() and video_codecs() are called. We need to
+  // set the voice and video codecs; otherwise, Jingle initiation will fail.
+  void set_voice_codecs(const std::vector<AudioCodec>& codecs) {
+    voice_codecs_ = codecs;
+  }
+  void set_video_codecs(const std::vector<VideoCodec>& codecs) {
+    video_codecs_ = codecs;
+  }
+
+  // Implement pure virtual methods of MediaEngine.
+  virtual bool Init();
+  virtual void Terminate();
+  virtual int GetCapabilities();
+  virtual VoiceMediaChannel* CreateChannel();
+  virtual VideoMediaChannel* CreateVideoChannel(VoiceMediaChannel* voice_ch);
+  virtual SoundclipMedia* CreateSoundclip() { return NULL; }
+  virtual bool SetAudioOptions(int options) { return true; }
+  virtual bool SetVideoOptions(int options) { return true; }
+  virtual bool SetDefaultVideoEncoderConfig(const VideoEncoderConfig& config) {
+    return true;
+  }
+  virtual bool SetSoundDevices(const Device* in_dev, const Device* out_dev) {
+    return true;
+  }
+  virtual bool SetVideoCaptureDevice(const Device* cam_device) { return true; }
+  virtual bool SetOutputVolume(int level) { return true; }
+  virtual int GetInputLevel() { return 0; }
+  virtual bool SetLocalMonitor(bool enable) { return true; }
+  virtual bool SetLocalRenderer(VideoRenderer* renderer) { return true; }
+  // TODO: control channel send?
+  virtual bool SetVideoCapture(bool capture) { return true; }
+  virtual const std::vector<AudioCodec>& audio_codecs() {
+    return voice_codecs_;
+  }
+  virtual const std::vector<VideoCodec>& video_codecs() {
+    return video_codecs_;
+  }
+  virtual bool FindAudioCodec(const AudioCodec& codec);
+  virtual bool FindVideoCodec(const VideoCodec& codec) { return true; }
+  virtual void SetVoiceLogging(int min_sev, const char* filter) {}
+  virtual void SetVideoLogging(int min_sev, const char* filter) {}
+
+  std::string GetRingWav(){return ring_wav_;}
+  std::string GetCallWav(){return call_wav_;}
+
+  int have_ilbc;
+
+ private:
+  std::string voice_input_filename_;
+  std::string voice_output_filename_;
+  std::string video_input_filename_;
+  std::string video_output_filename_;
+  std::vector<AudioCodec> voice_codecs_;
+  std::vector<VideoCodec> video_codecs_;
+
+  std::string ring_wav_;
+  std::string call_wav_;
+
+  DISALLOW_COPY_AND_ASSIGN(LinphoneMediaEngine);
+};
+
+class LinphoneVoiceChannel : public VoiceMediaChannel {
+ public:
+  LinphoneVoiceChannel(LinphoneMediaEngine *eng);
+  virtual ~LinphoneVoiceChannel();
+
+  // Implement pure virtual methods of VoiceMediaChannel.
+  virtual bool SetRecvCodecs(const std::vector<AudioCodec>& codecs) { return true; }
+  virtual bool SetSendCodecs(const std::vector<AudioCodec>& codecs);
+  virtual bool SetPlayout(bool playout);
+  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) {
+    return false;
+  }
+  virtual bool GetOutputScaling(uint32 ssrc, double* left, double* right) {
+    return false;
+  }
+  virtual void SetRingbackTone(const char* buf, int len) {}
+  virtual bool PlayRingbackTone(bool play, bool loop) { return true; }
+  virtual bool PressDTMF(int event, bool playout) { return true; }
+  virtual bool GetStats(VoiceMediaInfo* info) { return true; }
+
+  // Implement pure virtual methods of MediaChannel.
+  virtual void OnPacketReceived(talk_base::Buffer* packet);
+  virtual void OnRtcpReceived(talk_base::Buffer* packet) {}
+  virtual void SetSendSsrc(uint32 id) {}  // TODO: change RTP packet?
+  virtual bool SetRtcpCName(const std::string& cname) { return true; }
+  virtual bool Mute(bool on) { return mute_; }
+  virtual bool SetSendBandwidth(bool autobw, int bps) { return true; }
+  virtual bool SetOptions(int options) { return true; }
+  virtual bool SetRecvRtpHeaderExtensions(
+      const std::vector<RtpHeaderExtension>& extensions) { return true; }
+  virtual bool SetSendRtpHeaderExtensions(
+      const std::vector<RtpHeaderExtension>& extensions) { return true; }
+
+  virtual void StartRing(bool bIncomingCall);
+  virtual void StopRing();
+
+ private:
+  int pt_;
+  bool mute_;
+  bool play_;
+  AudioStream *audio_stream_;
+  LinphoneMediaEngine *engine_;
+  RingStream* ring_stream_;
+  talk_base::scoped_ptr<talk_base::AsyncSocket> socket_;
+  void OnIncomingData(talk_base::AsyncSocket *s);
+
+  DISALLOW_COPY_AND_ASSIGN(LinphoneVoiceChannel);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_LINPHONEMEDIAENGINE_H_
diff --git a/talk/media/sctp/sctpdataengine.cc b/talk/media/sctp/sctpdataengine.cc
new file mode 100644
index 0000000..cb70182
--- /dev/null
+++ b/talk/media/sctp/sctpdataengine.cc
@@ -0,0 +1,684 @@
+/*
+ * libjingle SCTP
+ * Copyright 2012 Google Inc, and Robin Seggelmann
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/media/sctp/sctpdataengine.h"
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <vector>
+
+#include "talk/base/buffer.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/media/base/codec.h"
+#include "talk/media/base/constants.h"
+#include "talk/media/base/streamparams.h"
+#include "usrsctplib/usrsctp.h"
+
+#ifdef _WIN32
+// EINPROGRESS gets #defined to WSAEINPROGRESS in some headers above, which
+// is not 112.  112 is the value defined in <errno.h>.  usrsctp uses 112 for
+// EINPROGRESS.
+#undef EINPROGRESS
+#define EINPROGRESS (112)
+#endif
+
+namespace cricket {
+
+// This is the SCTP port to use. It is passed along the wire and the listener
+// and connector must be using the same port. It is not related to the ports at
+// the IP level. (Corresponds to: sockaddr_conn.sconn_port in usrsctp.h)
+//
+// TODO(ldixon): Allow port to be set from higher level code.
+static const int kSctpDefaultPort = 5001;
+// TODO(ldixon): Find where this is defined, and also check is Sctp really
+// respects this.
+static const size_t kSctpMtu = 1280;
+
+enum {
+  MSG_SCTPINBOUNDPACKET = 1,   // MessageData is SctpInboundPacket
+  MSG_SCTPOUTBOUNDPACKET = 2,  // MessageData is talk_base:Buffer
+};
+
+struct SctpInboundPacket {
+  talk_base::Buffer buffer;
+  ReceiveDataParams params;
+  // The |flags| parameter is used by SCTP to distinguish notification packets
+  // from other types of packets.
+  int flags;
+};
+
+// Helper for logging SCTP data. Given a buffer, returns a readable string.
+static void debug_sctp_printf(const char *format, ...) {
+  char s[255];
+  va_list ap;
+  va_start(ap, format);
+  vsnprintf(s, sizeof(s), format, ap);
+  LOG(LS_INFO) << s;
+  // vprintf(format, ap);
+  va_end(ap);
+}
+
+// Helper for make a string dump of some SCTP data. Used for LOG
+// debugging messages.
+static std::string SctpDataToDebugString(void* buffer, size_t length,
+                                         int dump_type) {
+  char *dump_buf = usrsctp_dumppacket(buffer, length, dump_type);
+  if (!dump_buf) {
+      return "";
+  }
+  std::string s = std::string(dump_buf);
+  usrsctp_freedumpbuffer(dump_buf);
+  return s;
+}
+
+// This is the callback usrsctp uses when there's data to send on the network
+// that has been wrapped appropriatly for the SCTP protocol.
+static int OnSctpOutboundPacket(void* addr, void* data, size_t length,
+                                uint8_t tos, uint8_t set_df) {
+  SctpDataMediaChannel* channel = static_cast<SctpDataMediaChannel*>(addr);
+  LOG(LS_VERBOSE) << "global OnSctpOutboundPacket():"
+                  << "addr: " << addr << "; length: " << length
+                  << "; tos: " << std::hex << static_cast<int>(tos)
+                  << "; set_df: " << std::hex << static_cast<int>(set_df)
+                  << "; data:" << SctpDataToDebugString(data, length,
+                                                        SCTP_DUMP_OUTBOUND);
+  // Note: We have to copy the data; the caller will delete it.
+  talk_base::Buffer* buffer = new talk_base::Buffer(data, length);
+  channel->worker_thread()->Post(channel, MSG_SCTPOUTBOUNDPACKET,
+                                 talk_base::WrapMessageData(buffer));
+  return 0;
+}
+
+// This is the callback called from usrsctp when data has been received, after
+// a packet has been interpreted and parsed by usrsctp and found to contain
+// payload data. It is called by a usrsctp thread. It is assumed this function
+// will free the memory used by 'data'.
+static int OnSctpInboundPacket(struct socket* sock, union sctp_sockstore addr,
+                               void* data, size_t length,
+                               struct sctp_rcvinfo rcv, int flags,
+                               void* ulp_info) {
+  LOG(LS_VERBOSE) << "global OnSctpInboundPacket... Msg of length "
+                  << length << " received via " << addr.sconn.sconn_addr << ":"
+                  << talk_base::NetworkToHost16(addr.sconn.sconn_port)
+                  << " on stream " << rcv.rcv_sid
+                  << " with SSN " << rcv.rcv_ssn
+                  << " and TSN " << rcv.rcv_tsn << ", PPID "
+                  << talk_base::NetworkToHost32(rcv.rcv_ppid)
+                  << ", context " << rcv.rcv_context
+                  << ", data: " << data
+                  << ", ulp_info:" << ulp_info
+                  << ", flags:" << std::hex << flags;
+  SctpDataMediaChannel* channel = static_cast<SctpDataMediaChannel*>(ulp_info);
+  // The second log call is useful when the defines flags are incorrect. In
+  // this case, ulp_info ends up being bad and the second log message will
+  // cause a crash.
+  LOG(LS_VERBOSE) << "global OnSctpInboundPacket. channel="
+                  << channel->debug_name() << "...";
+  // Post data to the channel's receiver thread (copying it).
+  // TODO(ldixon): Unclear if copy is needed as this method is responsible for
+  // memory cleanup. But this does simplify code.
+  SctpInboundPacket* packet = new SctpInboundPacket();
+  packet->buffer.SetData(data, length);
+  packet->params.ssrc = rcv.rcv_sid;
+  packet->params.seq_num = rcv.rcv_ssn;
+  packet->params.timestamp = rcv.rcv_tsn;
+  packet->flags = flags;
+  channel->worker_thread()->Post(channel, MSG_SCTPINBOUNDPACKET,
+                                 talk_base::WrapMessageData(packet));
+  free(data);
+  return 1;
+}
+
+// Set the initial value of the static SCTP Data Engines reference count.
+int SctpDataEngine::usrsctp_engines_count = 0;
+
+SctpDataEngine::SctpDataEngine() {
+  if (usrsctp_engines_count == 0) {
+    // First argument is udp_encapsulation_port, which is not releveant for our
+    // AF_CONN use of sctp.
+    usrsctp_init(0, cricket::OnSctpOutboundPacket, debug_sctp_printf);
+
+    // To turn on/off detailed SCTP debugging. You will also need to have the
+    // SCTP_DEBUG cpp defines flag.
+    // usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_ALL);
+
+    // TODO(ldixon): Consider turning this on/off.
+    usrsctp_sysctl_set_sctp_ecn_enable(0);
+
+    // TODO(ldixon): Consider turning this on/off.
+    // This is not needed right now (we don't do dynamic address changes):
+    // If SCTP Auto-ASCONF is enabled, the peer is informed automatically
+    // when a new address is added or removed. This feature is enabled by
+    // default.
+    // usrsctp_sysctl_set_sctp_auto_asconf(0);
+
+    // TODO(ldixon): Consider turning this on/off.
+    // Add a blackhole sysctl. Setting it to 1 results in no ABORTs
+    // being sent in response to INITs, setting it to 2 results
+    // in no ABORTs being sent for received OOTB packets.
+    // This is similar to the TCP sysctl.
+    //
+    // See: http://lakerest.net/pipermail/sctp-coders/2012-January/009438.html
+    // See: http://svnweb.freebsd.org/base?view=revision&revision=229805
+    // usrsctp_sysctl_set_sctp_blackhole(2);
+  }
+  usrsctp_engines_count++;
+
+  // We don't put in a codec because we don't want one offered when we
+  // use the hybrid data engine.
+  // codecs_.push_back(cricket::DataCodec( kGoogleSctpDataCodecId,
+  // kGoogleSctpDataCodecName, 0));
+}
+
+SctpDataEngine::~SctpDataEngine() {
+  // TODO(ldixon): There is currently a bug in teardown of usrsctp that blocks
+  // indefintely if a finish call made too soon after close calls. So teardown
+  // has been skipped. Once the bug is fixed, retest and enable teardown.
+  //
+  // usrsctp_engines_count--;
+  // LOG(LS_VERBOSE) << "usrsctp_engines_count:" << usrsctp_engines_count;
+  // if (usrsctp_engines_count == 0) {
+  //   if (usrsctp_finish() != 0) {
+  //     LOG(LS_WARNING) << "usrsctp_finish.";
+  //   }
+  // }
+}
+
+DataMediaChannel* SctpDataEngine::CreateChannel(
+    DataChannelType data_channel_type) {
+  if (data_channel_type != DCT_SCTP) {
+    return NULL;
+  }
+  return new SctpDataMediaChannel(talk_base::Thread::Current());
+}
+
+SctpDataMediaChannel::SctpDataMediaChannel(talk_base::Thread* thread)
+    : worker_thread_(thread),
+      local_port_(kSctpDefaultPort),
+      remote_port_(kSctpDefaultPort),
+      sock_(NULL),
+      sending_(false),
+      receiving_(false),
+      debug_name_("SctpDataMediaChannel") {
+}
+
+SctpDataMediaChannel::~SctpDataMediaChannel() {
+  CloseSctpSocket();
+}
+
+sockaddr_conn SctpDataMediaChannel::GetSctpSockAddr(int port) {
+  sockaddr_conn sconn = {0};
+  sconn.sconn_family = AF_CONN;
+#ifdef HAVE_SCONN_LEN
+  sconn.sconn_len = sizeof(sockaddr_conn);
+#endif
+  // Note: conversion from int to uint16_t happens here.
+  sconn.sconn_port = talk_base::HostToNetwork16(port);
+  sconn.sconn_addr = this;
+  return sconn;
+}
+
+bool SctpDataMediaChannel::OpenSctpSocket() {
+  if (sock_) {
+    LOG(LS_VERBOSE) << debug_name_
+                    << "->Ignoring attempt to re-create existing socket.";
+    return false;
+  }
+  sock_ = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP,
+                         cricket::OnSctpInboundPacket, NULL, 0, this);
+  if (!sock_) {
+    LOG_ERRNO(LS_ERROR) << debug_name_ << "Failed to create SCTP socket.";
+    return false;
+  }
+
+  // Make the socket non-blocking. Connect, close, shutdown etc will not block
+  // the thread waiting for the socket operation to complete.
+  if (usrsctp_set_non_blocking(sock_, 1) < 0) {
+    LOG_ERRNO(LS_ERROR) << debug_name_ << "Failed to set SCTP to non blocking.";
+    return false;
+  }
+
+  // This ensures that the usrsctp close call deletes the association. This
+  // prevents usrsctp from calling OnSctpOutboundPacket with references to
+  // this class as the address.
+  linger linger_opt;
+  linger_opt.l_onoff = 1;
+  linger_opt.l_linger = 0;
+  if (usrsctp_setsockopt(sock_, SOL_SOCKET, SO_LINGER, &linger_opt,
+                         sizeof(linger_opt))) {
+    LOG_ERRNO(LS_ERROR) << debug_name_ << "Failed to set SO_LINGER.";
+    return false;
+  }
+
+  // Subscribe to SCTP event notifications.
+  int event_types[] = {SCTP_ASSOC_CHANGE,
+                       SCTP_PEER_ADDR_CHANGE,
+                       SCTP_SEND_FAILED_EVENT};
+  struct sctp_event event = {0};
+  event.se_assoc_id = SCTP_ALL_ASSOC;
+  event.se_on = 1;
+  for (size_t i = 0; i < ARRAY_SIZE(event_types); i++) {
+    event.se_type = event_types[i];
+    if (usrsctp_setsockopt(sock_, IPPROTO_SCTP, SCTP_EVENT, &event,
+                           sizeof(event)) < 0) {
+      LOG_ERRNO(LS_ERROR) << debug_name_ << "Failed to set SCTP_EVENT type: "
+                          << event.se_type;
+      return false;
+    }
+  }
+
+  // Register this class as an address for usrsctp. This is used by SCTP to
+  // direct the packets received (by the created socket) to this class.
+  usrsctp_register_address(this);
+  sending_ = true;
+  return true;
+}
+
+void SctpDataMediaChannel::CloseSctpSocket() {
+  sending_ = false;
+  if (sock_) {
+    // We assume that SO_LINGER option is set to close the association when
+    // close is called. This means that any pending packets in usrsctp will be
+    // discarded instead of being sent.
+    usrsctp_close(sock_);
+    sock_ = NULL;
+    usrsctp_deregister_address(this);
+  }
+}
+
+bool SctpDataMediaChannel::Connect() {
+  LOG(LS_VERBOSE) << debug_name_ << "->Connect().";
+
+  // If we already have a socket connection, just return.
+  if (sock_) {
+    LOG(LS_WARNING) << debug_name_ << "->Connect(): Ignored as socket "
+                                      "is already established.";
+    return true;
+  }
+
+  // If no socket (it was closed) try to start it again. This can happen when
+  // the socket we are connecting to closes, does an sctp shutdown handshake,
+  // or behaves unexpectedly causing us to perform a CloseSctpSocket.
+  if (!sock_ && !OpenSctpSocket()) {
+    return false;
+  }
+
+  // Note: conversion from int to uint16_t happens on assignment.
+  sockaddr_conn local_sconn = GetSctpSockAddr(local_port_);
+  if (usrsctp_bind(sock_, reinterpret_cast<sockaddr *>(&local_sconn),
+                   sizeof(local_sconn)) < 0) {
+    LOG_ERRNO(LS_ERROR) << debug_name_ << "->Connect(): "
+                        << ("Failed usrsctp_bind");
+    CloseSctpSocket();
+    return false;
+  }
+
+  // Note: conversion from int to uint16_t happens on assignment.
+  sockaddr_conn remote_sconn = GetSctpSockAddr(remote_port_);
+  int connect_result = usrsctp_connect(
+      sock_, reinterpret_cast<sockaddr *>(&remote_sconn), sizeof(remote_sconn));
+  if (connect_result < 0 && errno != EINPROGRESS) {
+    LOG_ERRNO(LS_ERROR) << debug_name_ << "Failed usrsctp_connect";
+    CloseSctpSocket();
+    return false;
+  }
+  return true;
+}
+
+void SctpDataMediaChannel::Disconnect() {
+  // TODO(ldixon): Consider calling |usrsctp_shutdown(sock_, ...)| to do a
+  // shutdown handshake and remove the association.
+  CloseSctpSocket();
+}
+
+bool SctpDataMediaChannel::SetSend(bool send) {
+  if (!sending_ && send) {
+    return Connect();
+  }
+  if (sending_ && !send) {
+    Disconnect();
+  }
+  return true;
+}
+
+bool SctpDataMediaChannel::SetReceive(bool receive) {
+  receiving_ = receive;
+  return true;
+}
+
+bool SctpDataMediaChannel::AddSendStream(const StreamParams& stream) {
+  if (!stream.has_ssrcs()) {
+    return false;
+  }
+
+  StreamParams found_stream;
+  if (GetStreamBySsrc(send_streams_, stream.first_ssrc(), &found_stream)) {
+    LOG(LS_WARNING) << debug_name_ << "->AddSendStream(...): "
+                    << "Not adding data send stream '" << stream.id
+                    << "' with ssrc=" << stream.first_ssrc()
+                    << " because stream already exists.";
+    return false;
+  }
+
+  send_streams_.push_back(stream);
+  return true;
+}
+
+bool SctpDataMediaChannel::RemoveSendStream(uint32 ssrc) {
+  StreamParams found_stream;
+  if (!GetStreamBySsrc(send_streams_, ssrc, &found_stream)) {
+    return false;
+  }
+
+  RemoveStreamBySsrc(&send_streams_, ssrc);
+  return true;
+}
+
+// Note: expects exactly one ssrc.  If none are given, it will fail.  If more
+// than one are given, it will use the first.
+bool SctpDataMediaChannel::AddRecvStream(const StreamParams& stream) {
+  if (!stream.has_ssrcs()) {
+    return false;
+  }
+
+  StreamParams found_stream;
+  if (GetStreamBySsrc(recv_streams_, stream.first_ssrc(), &found_stream)) {
+    LOG(LS_WARNING) << debug_name_ << "->AddRecvStream(...): "
+                    << "Not adding data recv stream '" << stream.id
+                    << "' with ssrc=" << stream.first_ssrc()
+                    << " because stream already exists.";
+    return false;
+  }
+
+  recv_streams_.push_back(stream);
+  LOG(LS_VERBOSE) << debug_name_ << "->AddRecvStream(...): "
+                  << "Added data recv stream '" << stream.id
+                  << "' with ssrc=" << stream.first_ssrc();
+  return true;
+}
+
+bool SctpDataMediaChannel::RemoveRecvStream(uint32 ssrc) {
+  RemoveStreamBySsrc(&recv_streams_, ssrc);
+  return true;
+}
+
+bool SctpDataMediaChannel::SendData(
+    const SendDataParams& params,
+    const talk_base::Buffer& payload,
+    SendDataResult* result) {
+  if (result) {
+    // If we return true, we'll set this to SDR_SUCCESS.
+    *result = SDR_ERROR;
+  }
+
+  if (!sending_) {
+    LOG(LS_WARNING) << debug_name_ << "->SendData(...): "
+                    << "Not sending packet with ssrc=" << params.ssrc
+                    << " len=" << payload.length() << " before SetSend(true).";
+    return false;
+  }
+
+  StreamParams found_stream;
+  if (!GetStreamBySsrc(send_streams_, params.ssrc, &found_stream)) {
+    LOG(LS_WARNING) << debug_name_ << "->SendData(...): "
+                    << "Not sending data because ssrc is unknown: "
+                    << params.ssrc;
+    return false;
+  }
+
+  // TODO(ldixon): Experiment with sctp_sendv_spa instead of sctp_sndinfo. e.g.
+  // struct sctp_sendv_spa spa = {0};
+  // spa.sendv_flags |= SCTP_SEND_SNDINFO_VALID;
+  // spa.sendv_sndinfo.snd_sid = params.ssrc;
+  // spa.sendv_sndinfo.snd_context = 0;
+  // spa.sendv_sndinfo.snd_assoc_id = 0;
+  // TODO(pthatcher): Support different types of protocols (e.g. SSL) and
+  // messages (e.g. Binary) via SendDataParams.
+  // spa.sendv_sndinfo.snd_ppid = htonl(PPID_NONE);
+  // TODO(pthatcher): Support different reliability semantics.
+  // For reliable: Remove SCTP_UNORDERED.
+  // For partially-reliable: Add rtx or ttl.
+  // spa.sendv_sndinfo.snd_flags = SCTP_UNORDERED;
+  // TODO(phatcher): Try some of these things.
+  // spa.sendv_flags |= SCTP_SEND_PRINFO_VALID;
+  // spa.sendv_prinfo.pr_policy = SCTP_PR_SCTP_RTX;
+  // spa.sendv_prinfo.pr_value = htons(max_retransmit_count);
+  // spa.sendv_prinfo.pr_policy = SCTP_PR_SCTP_TTL;
+  // spa.sendv_prinfo.pr_value = htons(max_retransmit_time);
+  //
+  // Send data using SCTP.
+  sctp_sndinfo sndinfo = {0};
+  sndinfo.snd_sid = params.ssrc;
+  sndinfo.snd_flags = 0;
+  // TODO(pthatcher): Once data types are added to SendParams, this can be set
+  // from SendParams.
+  sndinfo.snd_ppid = talk_base::HostToNetwork32(PPID_NONE);
+  sndinfo.snd_context = 0;
+  sndinfo.snd_assoc_id = 0;
+  ssize_t res = usrsctp_sendv(sock_, payload.data(),
+                              static_cast<size_t>(payload.length()),
+                              NULL, 0, &sndinfo,
+                              static_cast<socklen_t>(sizeof(sndinfo)),
+                              SCTP_SENDV_SNDINFO, 0);
+  if (res < 0) {
+    LOG_ERRNO(LS_ERROR) << "ERROR:" << debug_name_
+                        << "SendData->(...): "
+                        << " usrsctp_sendv: ";
+    // TODO(pthatcher): Make result SDR_BLOCK if the error is because
+    // it would block.
+    return false;
+  }
+  if (result) {
+    // If we return true, we'll set this to SDR_SUCCESS.
+    *result = SDR_SUCCESS;
+  }
+  return true;
+}
+
+// Called by network interface when a packet has been received.
+void SctpDataMediaChannel::OnPacketReceived(talk_base::Buffer* packet) {
+  LOG(LS_VERBOSE) << debug_name_ << "->OnPacketReceived(...): "
+                  << " length=" << packet->length() << "; data="
+                  << SctpDataToDebugString(packet->data(), packet->length(),
+                                           SCTP_DUMP_INBOUND);
+  // Only give receiving packets to usrsctp after if connected. This enables two
+  // peers to each make a connect call, but for them not to receive an INIT
+  // packet before they have called connect; least the last receiver of the INIT
+  // packet will have called connect, and a connection will be established.
+  if (sending_) {
+    LOG(LS_VERBOSE) << debug_name_ << "->OnPacketReceived(...):"
+                    << " Passed packet to sctp.";
+    // Pass received packet to SCTP stack. Once processed by usrsctp, the data
+    // will be will be given to the global OnSctpInboundData, and then,
+    // marshalled by a Post and handled with OnMessage.
+    usrsctp_conninput(this, packet->data(), packet->length(), 0);
+  } else {
+    // TODO(ldixon): Consider caching the packet for very slightly better
+    // reliability.
+    LOG(LS_INFO) << debug_name_ << "->OnPacketReceived(...):"
+                 << " Threw packet (probably an INIT) away.";
+  }
+}
+
+void SctpDataMediaChannel::OnInboundPacketFromSctpToChannel(
+    SctpInboundPacket* packet) {
+  LOG(LS_VERBOSE) << debug_name_ << "->OnInboundPacketFromSctpToChannel(...): "
+                  << "Received SCTP data:"
+                  << " ssrc=" << packet->params.ssrc
+                  << " data='" << std::string(packet->buffer.data(),
+                                              packet->buffer.length())
+                  << " notification: " << (packet->flags & MSG_NOTIFICATION)
+                  << "' length=" << packet->buffer.length();
+  // Sending a packet with data == NULL (no data) is SCTPs "close the
+  // connection" message. This sets sock_ = NULL;
+  if (!packet->buffer.length() || !packet->buffer.data()) {
+    LOG(LS_INFO) << debug_name_ << "->OnInboundPacketFromSctpToChannel(...): "
+                                   "No data, closing.";
+    return;
+  }
+  if (packet->flags & MSG_NOTIFICATION) {
+    OnNotificationFromSctp(&packet->buffer);
+  } else {
+    OnDataFromSctpToChannel(packet->params, &packet->buffer);
+  }
+}
+
+void SctpDataMediaChannel::OnDataFromSctpToChannel(
+    const ReceiveDataParams& params, talk_base::Buffer* buffer) {
+  StreamParams found_stream;
+  if (!GetStreamBySsrc(recv_streams_, params.ssrc, &found_stream)) {
+    LOG(LS_WARNING) << debug_name_ << "->OnDataFromSctpToChannel(...): "
+                    << "Received packet for unknown ssrc: " << params.ssrc;
+    return;
+  }
+
+  if (receiving_) {
+    LOG(LS_VERBOSE) << debug_name_ << "->OnDataFromSctpToChannel(...): "
+                    << "Posting with length: " << buffer->length();
+    SignalDataReceived(params, buffer->data(), buffer->length());
+  } else {
+    LOG(LS_WARNING) << debug_name_ << "->OnDataFromSctpToChannel(...): "
+                    << "Not receiving packet with sid=" << params.ssrc
+                    << " len=" <<  buffer->length()
+                    << " before SetReceive(true).";
+  }
+}
+
+void SctpDataMediaChannel::OnNotificationFromSctp(
+    talk_base::Buffer* buffer) {
+  const sctp_notification& notification =
+      reinterpret_cast<const sctp_notification&>(*buffer->data());
+  ASSERT(notification.sn_header.sn_length == buffer->length());
+
+  // TODO(ldixon): handle notifications appropriately.
+  switch (notification.sn_header.sn_type) {
+    case SCTP_ASSOC_CHANGE:
+      LOG(LS_VERBOSE) << "SCTP_ASSOC_CHANGE";
+      OnNotificationAssocChange(notification.sn_assoc_change);
+      break;
+    case SCTP_REMOTE_ERROR:
+      LOG(LS_INFO) << "SCTP_REMOTE_ERROR";
+      break;
+    case SCTP_SHUTDOWN_EVENT:
+      LOG(LS_INFO) << "SCTP_SHUTDOWN_EVENT";
+      break;
+    case SCTP_ADAPTATION_INDICATION:
+      LOG(LS_INFO) << "SCTP_ADAPTATION_INIDICATION";
+      break;
+    case SCTP_PARTIAL_DELIVERY_EVENT:
+      LOG(LS_INFO) << "SCTP_PARTIAL_DELIVERY_EVENT";
+      break;
+    case SCTP_AUTHENTICATION_EVENT:
+      LOG(LS_INFO) << "SCTP_AUTHENTICATION_EVENT";
+      break;
+    case SCTP_SENDER_DRY_EVENT:
+      LOG(LS_INFO) << "SCTP_SENDER_DRY_EVENT";
+      break;
+    // TODO(ldixon): Unblock after congestion.
+    case SCTP_NOTIFICATIONS_STOPPED_EVENT:
+      LOG(LS_INFO) << "SCTP_NOTIFICATIONS_STOPPED_EVENT";
+      break;
+    case SCTP_SEND_FAILED_EVENT:
+      LOG(LS_INFO) << "SCTP_SEND_FAILED_EVENT";
+      break;
+    case SCTP_STREAM_RESET_EVENT:
+      LOG(LS_INFO) << "SCTP_STREAM_RESET_EVENT";
+      // TODO(ldixon): Notify up to channel that stream resent has happened,
+      // and write unit test for this case.
+      break;
+    case SCTP_ASSOC_RESET_EVENT:
+      LOG(LS_INFO) << "SCTP_ASSOC_RESET_EVENT";
+      break;
+    case SCTP_STREAM_CHANGE_EVENT:
+      LOG(LS_INFO) << "SCTP_STREAM_CHANGE_EVENT";
+      break;
+    default:
+      LOG(LS_WARNING) << "Unknown SCTP event: "
+                      << notification.sn_header.sn_type;
+      break;
+  }
+}
+
+void SctpDataMediaChannel::OnNotificationAssocChange(
+    const sctp_assoc_change& change) {
+  switch (change.sac_state) {
+    case SCTP_COMM_UP:
+      LOG(LS_VERBOSE) << "Association change SCTP_COMM_UP";
+      break;
+    case SCTP_COMM_LOST:
+      LOG(LS_INFO) << "Association change SCTP_COMM_LOST";
+      break;
+    case SCTP_RESTART:
+      LOG(LS_INFO) << "Association change SCTP_RESTART";
+      break;
+    case SCTP_SHUTDOWN_COMP:
+      LOG(LS_INFO) << "Association change SCTP_SHUTDOWN_COMP";
+      break;
+    case SCTP_CANT_STR_ASSOC:
+      LOG(LS_INFO) << "Association change SCTP_CANT_STR_ASSOC";
+      break;
+    default:
+      LOG(LS_INFO) << "Association change UNKNOWN";
+      break;
+  }
+}
+
+
+void SctpDataMediaChannel::OnPacketFromSctpToNetwork(
+    talk_base::Buffer* buffer) {
+  if (buffer->length() > kSctpMtu) {
+    LOG(LS_ERROR) << debug_name_ << "->OnPacketFromSctpToNetwork(...): "
+                  << "SCTP seems to have made a poacket that is bigger "
+                     "than its official MTU.";
+  }
+  network_interface()->SendPacket(buffer);
+}
+
+void SctpDataMediaChannel::OnMessage(talk_base::Message* msg) {
+  switch (msg->message_id) {
+    case MSG_SCTPINBOUNDPACKET: {
+      SctpInboundPacket* packet =
+          static_cast<talk_base::TypedMessageData<SctpInboundPacket*>*>(
+              msg->pdata)->data();
+      OnInboundPacketFromSctpToChannel(packet);
+      delete packet;
+      break;
+    }
+    case MSG_SCTPOUTBOUNDPACKET: {
+      talk_base::Buffer* buffer =
+          static_cast<talk_base::TypedMessageData<talk_base::Buffer*>*>(
+              msg->pdata)->data();
+      OnPacketFromSctpToNetwork(buffer);
+      delete buffer;
+      break;
+    }
+  }
+}
+
+}  // namespace cricket
diff --git a/talk/media/sctp/sctpdataengine.h b/talk/media/sctp/sctpdataengine.h
new file mode 100644
index 0000000..9f95666
--- /dev/null
+++ b/talk/media/sctp/sctpdataengine.h
@@ -0,0 +1,220 @@
+/*
+ * libjingle SCTP
+ * Copyright 2012 Google Inc, and Robin Seggelmann
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_MEDIA_SCTP_SCTPDATAENGINE_H_
+#define TALK_MEDIA_SCTP_SCTPDATAENGINE_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/buffer.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/media/base/codec.h"
+#include "talk/media/base/mediachannel.h"
+#include "talk/media/base/mediaengine.h"
+
+// Defined by "usrsctplib/usrsctp.h"
+struct sockaddr_conn;
+struct sctp_assoc_change;
+// Defined by <sys/socket.h>
+struct socket;
+
+namespace cricket {
+// A DataEngine that interacts with usrsctp.
+//
+// From channel calls, data flows like this:
+// [worker thread (although it can in princple be another thread)]
+//  1.  SctpDataMediaChannel::SendData(data)
+//  2.  usrsctp_sendv(data)
+// [worker thread returns; sctp thread then calls the following]
+//  3.  OnSctpOutboundPacket(wrapped_data)
+// [sctp thread returns having posted a message for the worker thread]
+//  4.  SctpDataMediaChannel::OnMessage(wrapped_data)
+//  5.  SctpDataMediaChannel::OnPacketFromSctpToNetwork(wrapped_data)
+//  6.  NetworkInterface::SendPacket(wrapped_data)
+//  7.  ... across network ... a packet is sent back ...
+//  8.  SctpDataMediaChannel::OnPacketReceived(wrapped_data)
+//  9.  usrsctp_conninput(wrapped_data)
+// [worker thread returns; sctp thread then calls the following]
+//  10.  OnSctpInboundData(data)
+// [sctp thread returns having posted a message fot the worker thread]
+//  11. SctpDataMediaChannel::OnMessage(inboundpacket)
+//  12. SctpDataMediaChannel::OnInboundPacketFromSctpToChannel(inboundpacket)
+//  13. SctpDataMediaChannel::OnDataFromSctpToChannel(data)
+//  14. SctpDataMediaChannel::SignalDataReceived(data)
+// [from the same thread, methods registered/connected to
+//  SctpDataMediaChannel are called with the recieved data]
+class SctpDataEngine : public DataEngineInterface {
+ public:
+  SctpDataEngine();
+  virtual ~SctpDataEngine();
+
+  virtual DataMediaChannel* CreateChannel(DataChannelType data_channel_type);
+
+  virtual const std::vector<DataCodec>& data_codecs() { return codecs_; }
+
+ private:
+  static int usrsctp_engines_count;
+  std::vector<DataCodec> codecs_;
+};
+
+// TODO(ldixon): Make into a special type of TypedMessageData.
+// Holds data to be passed on to a channel.
+struct SctpInboundPacket;
+
+class SctpDataMediaChannel : public DataMediaChannel,
+                             public talk_base::MessageHandler {
+ public:
+  // DataMessageType is used for the SCTP "Payload Protocol Identifier", as
+  // defined in http://tools.ietf.org/html/rfc4960#section-14.4
+  //
+  // For the list of IANA approved values see:
+  // http://www.iana.org/assignments/sctp-parameters/sctp-parameters.xml
+  // The value is not used by SCTP itself. It indicates the protocol running
+  // on top of SCTP.
+  enum PayloadProtocolIdentifier {
+    PPID_NONE = 0,  // No protocol is specified.
+    // Specified by Mozilla. Not clear that this is actually part of the
+    // standard. Use with caution!
+    // http://mxr.mozilla.org/mozilla-central/source/netwerk/sctp/datachannel/DataChannelProtocol.h#22
+    PPID_CONTROL = 50,
+    PPID_TEXT = 51,
+    PPID_BINARY = 52,
+  };
+
+  // Given a thread which will be used to post messages (received data) to this
+  // SctpDataMediaChannel instance.
+  explicit SctpDataMediaChannel(talk_base::Thread* thread);
+  virtual ~SctpDataMediaChannel();
+
+  // When SetSend is set to true, connects. When set to false, disconnects.
+  // Calling: "SetSend(true); SetSend(false); SetSend(true);" will connect,
+  // disconnect, and reconnect.
+  virtual bool SetSend(bool send);
+  // Unless SetReceive(true) is called, received packets will be discarded.
+  virtual bool SetReceive(bool receive);
+
+  virtual bool AddSendStream(const StreamParams& sp);
+  virtual bool RemoveSendStream(uint32 ssrc);
+  virtual bool AddRecvStream(const StreamParams& sp);
+  virtual bool RemoveRecvStream(uint32 ssrc);
+
+  // Called when Sctp gets data. The data may be a notification or data for
+  // OnSctpInboundData. Called from the worker thread.
+  virtual void OnMessage(talk_base::Message* msg);
+  // Send data down this channel (will be wrapped as SCTP packets then given to
+  // sctp that will then post the network interface by OnMessage).
+  // Returns true iff successful data somewhere on the send-queue/network.
+  virtual bool SendData(const SendDataParams& params,
+                        const talk_base::Buffer& payload,
+                        SendDataResult* result = NULL);
+  // A packet is received from the network interface. Posted to OnMessage.
+  virtual void OnPacketReceived(talk_base::Buffer* packet);
+
+  // Exposed to allow Post call from c-callbacks.
+  talk_base::Thread* worker_thread() const { return worker_thread_; }
+
+  // TODO(ldixon): add a DataOptions class to mediachannel.h
+  virtual bool SetOptions(int options) { return false; }
+  virtual int GetOptions() const { return 0; }
+
+  // Many of these things are unused by SCTP, but are needed to fulfill
+  // the MediaChannel interface.
+  // TODO(pthatcher): Cleanup MediaChannel interface, or at least
+  // don't try calling these and return false.  Right now, things
+  // don't work if we return false.
+  virtual bool SetSendBandwidth(bool autobw, int bps) { return true; }
+  virtual bool SetRecvRtpHeaderExtensions(
+      const std::vector<RtpHeaderExtension>& extensions) { return true; }
+  virtual bool SetSendRtpHeaderExtensions(
+      const std::vector<RtpHeaderExtension>& extensions) { return true; }
+  virtual bool SetSendCodecs(const std::vector<DataCodec>& codecs) {
+    return true;
+  }
+  virtual bool SetRecvCodecs(const std::vector<DataCodec>& codecs) {
+    return true;
+  }
+  virtual void OnRtcpReceived(talk_base::Buffer* packet) {}
+  virtual void OnReadyToSend(bool ready) {}
+
+  // Helper for debugging.
+  void set_debug_name(const std::string& debug_name) {
+    debug_name_ = debug_name;
+  }
+  const std::string& debug_name() const { return debug_name_; }
+
+ private:
+  sockaddr_conn GetSctpSockAddr(int port);
+
+  // Creates the socket and connects. Sets sending_ to true.
+  bool Connect();
+  // Closes the socket. Sets sending_ to false.
+  void Disconnect();
+
+  // Returns false when openning the socket failed; when successfull sets
+  // sending_ to true
+  bool OpenSctpSocket();
+  // Sets sending_ to false and sock_ to NULL.
+  void CloseSctpSocket();
+
+  // Called by OnMessage to send packet on the network.
+  void OnPacketFromSctpToNetwork(talk_base::Buffer* buffer);
+  // Called by OnMessage to decide what to do with the packet.
+  void OnInboundPacketFromSctpToChannel(SctpInboundPacket* packet);
+  void OnDataFromSctpToChannel(const ReceiveDataParams& params,
+                               talk_base::Buffer* buffer);
+  void OnNotificationFromSctp(talk_base::Buffer* buffer);
+  void OnNotificationAssocChange(const sctp_assoc_change& change);
+
+  // Responsible for marshalling incoming data to the channels listeners, and
+  // outgoing data to the network interface.
+  talk_base::Thread* worker_thread_;
+  // The local and remote SCTP port to use. These are passed along the wire
+  // and the listener and connector must be using the same port. It is not
+  // related to the ports at the IP level.
+  int local_port_;
+  int remote_port_;
+  // TODO(ldixon): investigate why removing 'struct' makes the compiler
+  // complain.
+  //
+  // The socket created by usrsctp_socket(...).
+  struct socket* sock_;
+
+  // sending_ is true iff there is a connected socket.
+  bool sending_;
+  // receiving_ controls whether inbound packets are thrown away.
+  bool receiving_;
+  std::vector<StreamParams> send_streams_;
+  std::vector<StreamParams> recv_streams_;
+
+  // A human-readable name for debugging messages.
+  std::string debug_name_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_SCTP_SCTPDATAENGINE_H_
diff --git a/talk/media/sctp/sctpdataengine_unittest.cc b/talk/media/sctp/sctpdataengine_unittest.cc
new file mode 100644
index 0000000..071fbbb
--- /dev/null
+++ b/talk/media/sctp/sctpdataengine_unittest.cc
@@ -0,0 +1,260 @@
+/*
+ * libjingle SCTP
+ * Copyright 2013 Google Inc
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string>
+
+#include "talk/base/buffer.h"
+#include "talk/base/criticalsection.h"
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/thread.h"
+#include "talk/media/base/constants.h"
+#include "talk/media/base/mediachannel.h"
+#include "talk/media/sctp/sctpdataengine.h"
+
+enum {
+  MSG_PACKET = 1,
+};
+
+// Fake NetworkInterface that sends/receives sctp packets.  The one in
+// talk/media/base/fakenetworkinterface.h only works with rtp/rtcp.
+class SctpFakeNetworkInterface : public cricket::MediaChannel::NetworkInterface,
+                                 public talk_base::MessageHandler {
+ public:
+  explicit SctpFakeNetworkInterface(talk_base::Thread* thread)
+    : thread_(thread),
+      dest_(NULL) {
+  }
+
+  void SetDestination(cricket::DataMediaChannel* dest) { dest_ = dest; }
+
+ protected:
+  // Called to send raw packet down the wire (e.g. SCTP an packet).
+  virtual bool SendPacket(talk_base::Buffer* packet) {
+    LOG(LS_VERBOSE) << "SctpFakeNetworkInterface::SendPacket";
+
+    // TODO(ldixon): Can/should we use Buffer.TransferTo here?
+    // Note: this assignment does a deep copy of data from packet.
+    talk_base::Buffer* buffer = new talk_base::Buffer(packet->data(),
+                                                      packet->length());
+    thread_->Post(this, MSG_PACKET, talk_base::WrapMessageData(buffer));
+    LOG(LS_VERBOSE) << "SctpFakeNetworkInterface::SendPacket, Posted message.";
+    return true;
+  }
+
+  // Called when a raw packet has been recieved. This passes the data to the
+  // code that will interpret the packet. e.g. to get the content payload from
+  // an SCTP packet.
+  virtual void OnMessage(talk_base::Message* msg) {
+    LOG(LS_VERBOSE) << "SctpFakeNetworkInterface::OnMessage";
+    talk_base::Buffer* buffer =
+        static_cast<talk_base::TypedMessageData<talk_base::Buffer*>*>(
+            msg->pdata)->data();
+    if (dest_) {
+      dest_->OnPacketReceived(buffer);
+    }
+    delete buffer;
+  }
+
+  // Unsupported functions required to exist by NetworkInterface.
+  // TODO(ldixon): Refactor parent NetworkInterface class so these are not
+  // required. They are RTC specific and should be in an appropriate subclass.
+  virtual bool SendRtcp(talk_base::Buffer* packet) {
+    LOG(LS_WARNING) << "Unsupported: SctpFakeNetworkInterface::SendRtcp.";
+    return false;
+  }
+  virtual int SetOption(SocketType type, talk_base::Socket::Option opt,
+                        int option) {
+    LOG(LS_WARNING) << "Unsupported: SctpFakeNetworkInterface::SetOption.";
+    return 0;
+  }
+
+ private:
+  // Not owned by this class.
+  talk_base::Thread* thread_;
+  cricket::DataMediaChannel* dest_;
+};
+
+// This is essentially a buffer to hold recieved data. It stores only the last
+// received data. Calling OnDataReceived twice overwrites old data with the
+// newer one.
+// TODO(ldixon): Implement constraints, and allow new data to be added to old
+// instead of replacing it.
+class SctpFakeDataReceiver : public sigslot::has_slots<> {
+ public:
+  SctpFakeDataReceiver() : received_(false) {}
+
+  void Clear() {
+    received_ = false;
+    last_data_ = "";
+    last_params_ = cricket::ReceiveDataParams();
+  }
+
+  virtual void OnDataReceived(const cricket::ReceiveDataParams& params,
+                              const char* data, size_t length) {
+    received_ = true;
+    last_data_ = std::string(data, length);
+    last_params_ = params;
+  }
+
+  bool received() const { return received_; }
+  std::string last_data() const { return last_data_; }
+  cricket::ReceiveDataParams last_params() const { return last_params_; }
+
+ private:
+  bool received_;
+  std::string last_data_;
+  cricket::ReceiveDataParams last_params_;
+};
+
+// SCTP Data Engine testing framework.
+class SctpDataMediaChannelTest : public testing::Test {
+ protected:
+  virtual void SetUp() {
+    engine_.reset(new cricket::SctpDataEngine());
+  }
+
+  cricket::SctpDataMediaChannel* CreateChannel(
+        SctpFakeNetworkInterface* net, SctpFakeDataReceiver* recv) {
+    cricket::SctpDataMediaChannel* channel =
+        static_cast<cricket::SctpDataMediaChannel*>(engine_->CreateChannel(
+            cricket::DCT_SCTP));
+    channel->SetInterface(net);
+    // When data is received, pass it to the SctpFakeDataReceiver.
+    channel->SignalDataReceived.connect(
+        recv, &SctpFakeDataReceiver::OnDataReceived);
+    return channel;
+  }
+
+  bool SendData(cricket::SctpDataMediaChannel* chan, uint32 ssrc,
+                const std::string& msg,
+                cricket::SendDataResult* result) {
+    cricket::SendDataParams params;
+    params.ssrc = ssrc;
+    return chan->SendData(params, talk_base::Buffer(msg.data(), msg.length()), result);
+  }
+
+  bool ReceivedData(const SctpFakeDataReceiver* recv, uint32 ssrc,
+                    const std::string& msg ) {
+    return (recv->received() &&
+            recv->last_params().ssrc == ssrc &&
+            recv->last_data() == msg);
+  }
+
+  bool ProcessMessagesUntilIdle() {
+    talk_base::Thread* thread = talk_base::Thread::Current();
+    while (!thread->empty()) {
+      talk_base::Message msg;
+      if (thread->Get(&msg, talk_base::kForever)) {
+        thread->Dispatch(&msg);
+      }
+    }
+    return !thread->IsQuitting();
+  }
+
+ private:
+  talk_base::scoped_ptr<cricket::SctpDataEngine> engine_;
+};
+
+TEST_F(SctpDataMediaChannelTest, SendData) {
+  talk_base::scoped_ptr<SctpFakeNetworkInterface> net1(
+      new SctpFakeNetworkInterface(talk_base::Thread::Current()));
+  talk_base::scoped_ptr<SctpFakeNetworkInterface> net2(
+      new SctpFakeNetworkInterface(talk_base::Thread::Current()));
+  talk_base::scoped_ptr<SctpFakeDataReceiver> recv1(
+      new SctpFakeDataReceiver());
+  talk_base::scoped_ptr<SctpFakeDataReceiver> recv2(
+      new SctpFakeDataReceiver());
+  talk_base::scoped_ptr<cricket::SctpDataMediaChannel> chan1(
+      CreateChannel(net1.get(), recv1.get()));
+  chan1->set_debug_name("chan1/connector");
+  talk_base::scoped_ptr<cricket::SctpDataMediaChannel> chan2(
+      CreateChannel(net2.get(), recv2.get()));
+  chan2->set_debug_name("chan2/listener");
+
+  net1->SetDestination(chan2.get());
+  net2->SetDestination(chan1.get());
+
+  LOG(LS_VERBOSE) << "Channel setup ----------------------------- ";
+  chan1->AddSendStream(cricket::StreamParams::CreateLegacy(1));
+  chan2->AddRecvStream(cricket::StreamParams::CreateLegacy(1));
+
+  chan2->AddSendStream(cricket::StreamParams::CreateLegacy(2));
+  chan1->AddRecvStream(cricket::StreamParams::CreateLegacy(2));
+
+  LOG(LS_VERBOSE) << "Connect the channels -----------------------------";
+  // chan1 wants to setup a data connection.
+  chan1->SetReceive(true);
+  // chan1 will have sent chan2 a request to setup a data connection. After
+  // chan2 accepts the offer, chan2 connects to chan1 with the following.
+  chan2->SetReceive(true);
+  chan2->SetSend(true);
+  // Makes sure that network packets are delivered and simulates a
+  // deterministic and realistic small timing delay between the SetSend calls.
+  ProcessMessagesUntilIdle();
+
+  // chan1 and chan2 are now connected so chan1 enables sending to complete
+  // the creation of the connection.
+  chan1->SetSend(true);
+
+  cricket::SendDataResult result;
+  LOG(LS_VERBOSE) << "chan1 sending: 'hello?' -----------------------------";
+  ASSERT_TRUE(SendData(chan1.get(), 1, "hello?", &result));
+  EXPECT_EQ(cricket::SDR_SUCCESS, result);
+  EXPECT_TRUE_WAIT(ReceivedData(recv2.get(), 1, "hello?"), 1000);
+  LOG(LS_VERBOSE) << "recv2.received=" << recv2->received()
+                  << "recv2.last_params.ssrc=" << recv2->last_params().ssrc
+                  << "recv2.last_params.timestamp="
+                  << recv2->last_params().ssrc
+                  << "recv2.last_params.seq_num="
+                  << recv2->last_params().seq_num
+                  << "recv2.last_data=" << recv2->last_data();
+
+  LOG(LS_VERBOSE) << "chan2 sending: 'hi chan1' -----------------------------";
+  ASSERT_TRUE(SendData(chan2.get(), 2, "hi chan1", &result));
+  EXPECT_EQ(cricket::SDR_SUCCESS, result);
+  EXPECT_TRUE_WAIT(ReceivedData(recv1.get(), 2, "hi chan1"), 1000);
+  LOG(LS_VERBOSE) << "recv1.received=" << recv1->received()
+                  << "recv1.last_params.ssrc=" << recv1->last_params().ssrc
+                  << "recv1.last_params.timestamp="
+                  << recv1->last_params().ssrc
+                  << "recv1.last_params.seq_num="
+                  << recv1->last_params().seq_num
+                  << "recv1.last_data=" << recv1->last_data();
+
+  LOG(LS_VERBOSE) << "Closing down. -----------------------------";
+  // Disconnects and closes socket, including setting receiving to false.
+  chan1->SetSend(false);
+  chan2->SetSend(false);
+  LOG(LS_VERBOSE) << "Cleaning up. -----------------------------";
+}
diff --git a/talk/media/testdata/1.frame_plus_1.byte b/talk/media/testdata/1.frame_plus_1.byte
new file mode 100644
index 0000000..b619ede
--- /dev/null
+++ b/talk/media/testdata/1.frame_plus_1.byte
Binary files differ
diff --git a/talk/media/testdata/captured-320x240-2s-48.frames b/talk/media/testdata/captured-320x240-2s-48.frames
new file mode 100644
index 0000000..292a170
--- /dev/null
+++ b/talk/media/testdata/captured-320x240-2s-48.frames
Binary files differ
diff --git a/talk/media/testdata/h264-svc-99-640x360.rtpdump b/talk/media/testdata/h264-svc-99-640x360.rtpdump
new file mode 100644
index 0000000..ffa521d
--- /dev/null
+++ b/talk/media/testdata/h264-svc-99-640x360.rtpdump
Binary files differ
diff --git a/talk/media/testdata/video.rtpdump b/talk/media/testdata/video.rtpdump
new file mode 100644
index 0000000..7be863e
--- /dev/null
+++ b/talk/media/testdata/video.rtpdump
Binary files differ
diff --git a/talk/media/testdata/voice.rtpdump b/talk/media/testdata/voice.rtpdump
new file mode 100644
index 0000000..8f0ec15
--- /dev/null
+++ b/talk/media/testdata/voice.rtpdump
Binary files differ
diff --git a/talk/media/webrtc/fakewebrtccommon.h b/talk/media/webrtc/fakewebrtccommon.h
new file mode 100644
index 0000000..026ad10
--- /dev/null
+++ b/talk/media/webrtc/fakewebrtccommon.h
@@ -0,0 +1,66 @@
+/*
+ * 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_
+
+#include "talk/base/common.h"
+
+namespace cricket {
+
+#define WEBRTC_STUB(method, args) \
+  virtual int method args OVERRIDE { return 0; }
+
+#define WEBRTC_STUB_CONST(method, args) \
+  virtual int method args const OVERRIDE { return 0; }
+
+#define WEBRTC_BOOL_STUB(method, args) \
+  virtual bool method args OVERRIDE { return true; }
+
+#define WEBRTC_VOID_STUB(method, args) \
+  virtual void method args OVERRIDE {}
+
+#define WEBRTC_FUNC(method, args) \
+  virtual int method args OVERRIDE
+
+#define WEBRTC_FUNC_CONST(method, args) \
+  virtual int method args const OVERRIDE
+
+#define WEBRTC_BOOL_FUNC(method, args) \
+  virtual bool method args OVERRIDE
+
+#define WEBRTC_VOID_FUNC(method, args) \
+  virtual void method args OVERRIDE
+
+#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/media/webrtc/fakewebrtcdeviceinfo.h b/talk/media/webrtc/fakewebrtcdeviceinfo.h
new file mode 100644
index 0000000..210792a
--- /dev/null
+++ b/talk/media/webrtc/fakewebrtcdeviceinfo.h
@@ -0,0 +1,123 @@
+// 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_FAKEWEBRTCDEVICEINFO_H_
+#define TALK_SESSION_PHONE_FAKEWEBRTCDEVICEINFO_H_
+
+#include <vector>
+
+#include "talk/base/stringutils.h"
+#include "talk/media/webrtc/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 char*>(device_id.c_str()));
+    if (!dev) return;
+    dev->caps.push_back(cap);
+  }
+  virtual uint32_t NumberOfDevices() {
+    return devices_.size();
+  }
+  virtual int32_t GetDeviceName(uint32_t device_num,
+                                char* device_name,
+                                uint32_t device_name_len,
+                                char* device_id,
+                                uint32_t device_id_len,
+                                char* product_id,
+                                uint32_t 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 int32_t NumberOfCapabilities(const char* device_id) {
+    Device* dev = GetDeviceById(device_id);
+    if (!dev) return -1;
+    return dev->caps.size();
+  }
+  virtual int32_t GetCapability(const char* device_id,
+                                const uint32_t 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 int32_t GetOrientation(const char* device_id,
+                                 webrtc::VideoCaptureRotation& rotation) {
+    return -1;  // not implemented
+  }
+  virtual int32_t GetBestMatchedCapability(
+      const char* device_id,
+      const webrtc::VideoCaptureCapability& requested,
+      webrtc::VideoCaptureCapability& resulting) {
+    return -1;  // not implemented
+  }
+  virtual int32_t DisplayCaptureSettingsDialogBox(
+      const char* device_id, const char* dialog_title,
+      void* parent, uint32_t x, uint32_t y) {
+    return -1;  // not implemented
+  }
+
+  Device* GetDeviceByIndex(size_t num) {
+    return (num < devices_.size()) ? &devices_[num] : NULL;
+  }
+  Device* GetDeviceById(const char* 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/media/webrtc/fakewebrtcvcmfactory.h b/talk/media/webrtc/fakewebrtcvcmfactory.h
new file mode 100644
index 0000000..38643f9
--- /dev/null
+++ b/talk/media/webrtc/fakewebrtcvcmfactory.h
@@ -0,0 +1,63 @@
+// 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_FAKEWEBRTCVCMFACTORY_H_
+#define TALK_SESSION_PHONE_FAKEWEBRTCVCMFACTORY_H_
+
+#include <vector>
+
+#include "talk/media/webrtc/fakewebrtcvideocapturemodule.h"
+#include "talk/media/webrtc/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 char* 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/media/webrtc/fakewebrtcvideocapturemodule.h b/talk/media/webrtc/fakewebrtcvideocapturemodule.h
new file mode 100644
index 0000000..b823bc1
--- /dev/null
+++ b/talk/media/webrtc/fakewebrtcvideocapturemodule.h
@@ -0,0 +1,159 @@
+// 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_FAKEWEBRTCVIDEOCAPTUREMODULE_H_
+#define TALK_SESSION_PHONE_FAKEWEBRTCVIDEOCAPTUREMODULE_H_
+
+#include <vector>
+
+#include "talk/media/base/testutils.h"
+#include "talk/media/webrtc/fakewebrtcdeviceinfo.h"
+#include "talk/media/webrtc/webrtcvideocapturer.h"
+
+class FakeWebRtcVcmFactory;
+
+// Fake class for mocking out webrtc::VideoCaptureModule.
+class FakeWebRtcVideoCaptureModule : public webrtc::VideoCaptureModule {
+ public:
+  FakeWebRtcVideoCaptureModule(FakeWebRtcVcmFactory* factory, int32_t 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 int32_t ChangeUniqueId(const int32_t id) {
+    id_ = id;
+    return 0;
+  }
+  virtual int32_t RegisterCaptureDataCallback(
+      webrtc::VideoCaptureDataCallback& callback) {
+    callback_ = &callback;
+    return 0;
+  }
+  virtual int32_t DeRegisterCaptureDataCallback() {
+    callback_ = NULL;
+    return 0;
+  }
+  virtual int32_t RegisterCaptureCallback(
+      webrtc::VideoCaptureFeedBack& callback) {
+    return -1;  // not implemented
+  }
+  virtual int32_t DeRegisterCaptureCallback() {
+    return 0;
+  }
+  virtual int32_t StartCapture(
+      const webrtc::VideoCaptureCapability& cap) {
+    if (running_) return -1;
+    cap_ = cap;
+    running_ = true;
+    return 0;
+  }
+  virtual int32_t StopCapture() {
+    running_ = false;
+    return 0;
+  }
+  virtual const char* CurrentDeviceName() const {
+    return NULL;  // not implemented
+  }
+  virtual bool CaptureStarted() {
+    return running_;
+  }
+  virtual int32_t CaptureSettings(
+      webrtc::VideoCaptureCapability& settings) {
+    if (!running_) return -1;
+    settings = cap_;
+    return 0;
+  }
+  virtual int32_t SetCaptureDelay(int32_t delay) {
+    delay_ = delay;
+    return 0;
+  }
+  virtual int32_t CaptureDelay() {
+    return delay_;
+  }
+  virtual int32_t SetCaptureRotation(
+      webrtc::VideoCaptureRotation rotation) {
+    return -1;  // not implemented
+  }
+  virtual VideoCaptureEncodeInterface* GetEncodeInterface(
+      const webrtc::VideoCodec& codec) {
+    return NULL;  // not implemented
+  }
+  virtual int32_t EnableFrameRateCallback(const bool enable) {
+    return -1;  // not implemented
+  }
+  virtual int32_t 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::I420VideoFrame sample;
+    // Setting stride based on width.
+    if (sample.CreateEmptyFrame(w, h, w, (w + 1) / 2, (w + 1) / 2) < 0) {
+      return false;
+    }
+    if (callback_) {
+      callback_->OnIncomingCapturedFrame(id_, sample);
+    }
+    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/media/webrtc/fakewebrtcvideoengine.h b/talk/media/webrtc/fakewebrtcvideoengine.h
new file mode 100644
index 0000000..5b7b6cb
--- /dev/null
+++ b/talk/media/webrtc/fakewebrtcvideoengine.h
@@ -0,0 +1,1100 @@
+/*
+ * 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_MEDIA_WEBRTC_FAKEWEBRTCVIDEOENGINE_H_
+#define TALK_MEDIA_WEBRTC_FAKEWEBRTCVIDEOENGINE_H_
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/stringutils.h"
+#include "talk/media/base/codec.h"
+#include "talk/media/webrtc/fakewebrtccommon.h"
+#include "talk/media/webrtc/webrtcvideodecoderfactory.h"
+#include "talk/media/webrtc/webrtcvideoencoderfactory.h"
+#include "talk/media/webrtc/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 kMinVideoBitrate = 100;
+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;
+
+// Fake class for mocking out webrtc::VideoDecoder
+class FakeWebRtcVideoDecoder : public webrtc::VideoDecoder {
+ public:
+  FakeWebRtcVideoDecoder()
+      : num_frames_received_(0) {
+  }
+
+  virtual int32 InitDecode(const webrtc::VideoCodec*, int32) {
+    return WEBRTC_VIDEO_CODEC_OK;
+  }
+
+  virtual int32 Decode(
+      const webrtc::EncodedImage&, bool, const webrtc::RTPFragmentationHeader*,
+      const webrtc::CodecSpecificInfo*, int64) {
+    num_frames_received_++;
+    return WEBRTC_VIDEO_CODEC_OK;
+  }
+
+  virtual int32 RegisterDecodeCompleteCallback(
+      webrtc::DecodedImageCallback*) {
+    return WEBRTC_VIDEO_CODEC_OK;
+  }
+
+  virtual int32 Release() {
+    return WEBRTC_VIDEO_CODEC_OK;
+  }
+
+  virtual int32 Reset() {
+    return WEBRTC_VIDEO_CODEC_OK;
+  }
+
+  int GetNumFramesReceived() const {
+    return num_frames_received_;
+  }
+
+ private:
+  int num_frames_received_;
+};
+
+// Fake class for mocking out WebRtcVideoDecoderFactory.
+class FakeWebRtcVideoDecoderFactory : public WebRtcVideoDecoderFactory {
+ public:
+  FakeWebRtcVideoDecoderFactory()
+      : num_created_decoders_(0) {
+  }
+
+  virtual webrtc::VideoDecoder* CreateVideoDecoder(
+      webrtc::VideoCodecType type) {
+    if (supported_codec_types_.count(type) == 0) {
+      return NULL;
+    }
+    FakeWebRtcVideoDecoder* decoder = new FakeWebRtcVideoDecoder();
+    decoders_.push_back(decoder);
+    num_created_decoders_++;
+    return decoder;
+  }
+
+  virtual void DestroyVideoDecoder(webrtc::VideoDecoder* decoder) {
+    decoders_.erase(
+        std::remove(decoders_.begin(), decoders_.end(), decoder),
+        decoders_.end());
+    delete decoder;
+  }
+
+  void AddSupportedVideoCodecType(webrtc::VideoCodecType type) {
+    supported_codec_types_.insert(type);
+  }
+
+  int GetNumCreatedDecoders() {
+    return num_created_decoders_;
+  }
+
+  const std::vector<FakeWebRtcVideoDecoder*>& decoders() {
+    return decoders_;
+  }
+
+ private:
+  std::set<webrtc::VideoCodecType> supported_codec_types_;
+  std::vector<FakeWebRtcVideoDecoder*> decoders_;
+  int num_created_decoders_;
+};
+
+// Fake class for mocking out webrtc::VideoEnoder
+class FakeWebRtcVideoEncoder : public webrtc::VideoEncoder {
+ public:
+  FakeWebRtcVideoEncoder() {}
+
+  virtual int32 InitEncode(const webrtc::VideoCodec* codecSettings,
+                           int32 numberOfCores,
+                           uint32 maxPayloadSize) {
+    return WEBRTC_VIDEO_CODEC_OK;
+  }
+
+  virtual int32 Encode(
+      const webrtc::I420VideoFrame& inputImage,
+            const webrtc::CodecSpecificInfo* codecSpecificInfo,
+            const std::vector<webrtc::VideoFrameType>* frame_types) {
+    return WEBRTC_VIDEO_CODEC_OK;
+  }
+
+  virtual int32 RegisterEncodeCompleteCallback(
+      webrtc::EncodedImageCallback* callback) {
+    return WEBRTC_VIDEO_CODEC_OK;
+  }
+
+  virtual int32 Release() {
+    return WEBRTC_VIDEO_CODEC_OK;
+  }
+
+  virtual int32 SetChannelParameters(uint32 packetLoss,
+                                     int rtt) {
+    return WEBRTC_VIDEO_CODEC_OK;
+  }
+
+  virtual int32 SetRates(uint32 newBitRate,
+                         uint32 frameRate) {
+    return WEBRTC_VIDEO_CODEC_OK;
+  }
+};
+
+// Fake class for mocking out WebRtcVideoEncoderFactory.
+class FakeWebRtcVideoEncoderFactory : public WebRtcVideoEncoderFactory {
+ public:
+  FakeWebRtcVideoEncoderFactory()
+      : num_created_encoders_(0) {
+  }
+
+  virtual webrtc::VideoEncoder* CreateVideoEncoder(
+      webrtc::VideoCodecType type) {
+    if (supported_codec_types_.count(type) == 0) {
+      return NULL;
+    }
+    FakeWebRtcVideoEncoder* encoder = new FakeWebRtcVideoEncoder();
+    encoders_.push_back(encoder);
+    num_created_encoders_++;
+    return encoder;
+  }
+
+  virtual void DestroyVideoEncoder(webrtc::VideoEncoder* encoder) {
+    encoders_.erase(
+        std::remove(encoders_.begin(), encoders_.end(), encoder),
+        encoders_.end());
+    delete encoder;
+  }
+
+  virtual void AddObserver(WebRtcVideoEncoderFactory::Observer* observer) {
+    bool inserted = observers_.insert(observer).second;
+    EXPECT_TRUE(inserted);
+  }
+
+  virtual void RemoveObserver(WebRtcVideoEncoderFactory::Observer* observer) {
+    size_t erased = observers_.erase(observer);
+    EXPECT_EQ(erased, 1UL);
+  }
+
+  virtual const std::vector<WebRtcVideoEncoderFactory::VideoCodec>& codecs()
+      const {
+    return codecs_;
+  }
+
+  void AddSupportedVideoCodecType(webrtc::VideoCodecType type,
+                                  const std::string& name) {
+    supported_codec_types_.insert(type);
+    codecs_.push_back(
+        WebRtcVideoEncoderFactory::VideoCodec(type, name, 1280, 720, 30));
+  }
+
+  void NotifyCodecsAvailable() {
+    std::set<WebRtcVideoEncoderFactory::Observer*>::iterator it;
+    for (it = observers_.begin(); it != observers_.end(); ++it)
+      (*it)->OnCodecsAvailable();
+  }
+
+  int GetNumCreatedEncoders() {
+    return num_created_encoders_;
+  }
+
+  const std::vector<FakeWebRtcVideoEncoder*>& encoders() {
+    return encoders_;
+  }
+
+ private:
+  std::set<webrtc::VideoCodecType> supported_codec_types_;
+  std::vector<WebRtcVideoEncoderFactory::VideoCodec> codecs_;
+  std::vector<FakeWebRtcVideoEncoder*> encoders_;
+  std::set<WebRtcVideoEncoderFactory::Observer*> observers_;
+  int num_created_encoders_;
+};
+
+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 webrtc::ViEExternalCodec {
+ public:
+  struct Channel {
+    Channel()
+        : capture_id_(-1),
+          original_channel_id_(-1),
+          has_renderer_(false),
+          render_started_(false),
+          send(false),
+          receive_(false),
+          can_transmit_(true),
+          rtcp_status_(webrtc::kRtcpNone),
+          key_frame_request_method_(webrtc::kViEKeyFrameRequestNone),
+          tmmbr_(false),
+          remb_contribute_(false),
+          remb_bw_partition_(false),
+          rtp_offset_send_id_(0),
+          rtp_offset_receive_id_(0),
+          rtp_absolute_send_time_send_id_(0),
+          rtp_absolute_send_time_receive_id_(0),
+          sender_target_delay_(0),
+          receiver_target_delay_(0),
+          transmission_smoothing_(false),
+          nack_(false),
+          hybrid_nack_fec_(false),
+          send_video_bitrate_(0),
+          send_fec_bitrate_(0),
+          send_nack_bitrate_(0),
+          send_bandwidth_(0),
+          receive_bandwidth_(0) {
+      ssrcs_[0] = 0;  // default ssrc.
+      memset(&send_codec, 0, sizeof(send_codec));
+    }
+    int capture_id_;
+    int original_channel_id_;
+    bool has_renderer_;
+    bool render_started_;
+    bool send;
+    bool receive_;
+    bool can_transmit_;
+    std::map<int, int> ssrcs_;
+    std::string cname_;
+    webrtc::ViERTCPMode rtcp_status_;
+    webrtc::ViEKeyFrameRequestMethod key_frame_request_method_;
+    bool tmmbr_;
+    bool remb_contribute_;   // This channel contributes to the remb report.
+    bool remb_bw_partition_; // This channel is allocated part of total bw.
+    int rtp_offset_send_id_;
+    int rtp_offset_receive_id_;
+    int rtp_absolute_send_time_send_id_;
+    int rtp_absolute_send_time_receive_id_;
+    int sender_target_delay_;
+    int receiver_target_delay_;
+    bool transmission_smoothing_;
+    bool nack_;
+    bool hybrid_nack_fec_;
+    std::vector<webrtc::VideoCodec> recv_codecs;
+    std::set<unsigned int> ext_decoder_pl_types_;
+    std::set<unsigned int> ext_encoder_pl_types_;
+    webrtc::VideoCodec send_codec;
+    unsigned int send_video_bitrate_;
+    unsigned int send_fec_bitrate_;
+    unsigned int send_nack_bitrate_;
+    unsigned int send_bandwidth_;
+    unsigned int receive_bandwidth_;
+  };
+  class Capturer : public webrtc::ViEExternalCapture {
+   public:
+    Capturer() : channel_id_(-1), denoising_(false), last_capture_time_(0) { }
+    int channel_id() const { return channel_id_; }
+    void set_channel_id(int channel_id) { channel_id_ = channel_id; }
+    bool denoising() const { return denoising_; }
+    void set_denoising(bool denoising) { denoising_ = denoising; }
+    int64 last_capture_time() { return last_capture_time_; }
+
+    // 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) {
+      last_capture_time_ = captureTime;
+      return 0;
+    }
+
+   private:
+    int channel_id_;
+    bool denoising_;
+    int64 last_capture_time_;
+  };
+
+  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),
+        num_set_send_codecs_(0) {
+  }
+
+  ~FakeWebRtcVideoEngine() {
+    ASSERT(0 == channels_.size());
+    ASSERT(0 == capturers_.size());
+  }
+  bool IsInited() const { return inited_; }
+
+  int GetLastChannel() const { return last_channel_; }
+  int GetChannelFromLocalSsrc(int local_ssrc) const {
+    // ssrcs_[0] is the default local ssrc.
+    for (std::map<int, Channel*>::const_iterator iter = channels_.begin();
+         iter != channels_.end(); ++iter) {
+      if (local_ssrc == iter->second->ssrcs_[0]) {
+        return iter->first;
+      }
+    }
+    return -1;
+  }
+
+  int GetNumChannels() const { return channels_.size(); }
+  bool IsChannel(int channel) const {
+    return (channels_.find(channel) != channels_.end());
+  }
+  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 num_set_send_codecs() const { return num_set_send_codecs_; }
+
+  int GetCaptureId(int channel) const {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->capture_id_;
+  }
+  int GetOriginalChannelId(int channel) const {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->original_channel_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();
+  }
+  bool GetCaptureDenoising(int capture_id) const {
+    WEBRTC_ASSERT_CAPTURER(capture_id);
+    return capturers_.find(capture_id)->second->denoising();
+  }
+  int64 GetCaptureLastTimestamp(int capture_id) const {
+    WEBRTC_ASSERT_CAPTURER(capture_id);
+    return capturers_.find(capture_id)->second->last_capture_time();
+  }
+  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 GetRembStatusBwPartition(int channel) const {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->remb_bw_partition_;
+  }
+  bool GetRembStatusContribute(int channel) const {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->remb_contribute_;
+  }
+  int GetSendRtpTimestampOffsetExtensionId(int channel) {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->rtp_offset_send_id_;
+  }
+  int GetReceiveRtpTimestampOffsetExtensionId(int channel) {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->rtp_offset_receive_id_;
+  }
+  int GetSendAbsoluteSendTimeExtensionId(int channel) {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->rtp_absolute_send_time_send_id_;
+  }
+  int GetReceiveAbsoluteSendTimeExtensionId(int channel) {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->rtp_absolute_send_time_receive_id_;
+  }
+  bool GetTransmissionSmoothingStatus(int channel) {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->transmission_smoothing_;
+  }
+  int GetSenderTargetDelay(int channel) {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->sender_target_delay_;
+  }
+  int GetReceiverTargetDelay(int channel) {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->receiver_target_delay_;
+  }
+  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_;
+  }
+  int GetNumSsrcs(int channel) const {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->ssrcs_.size();
+  }
+  bool GetIsTransmitting(int channel) const {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->can_transmit_;
+  }
+  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();
+  };
+  bool ExternalDecoderRegistered(int channel,
+                                 unsigned int pl_type) const {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->
+        ext_decoder_pl_types_.count(pl_type) != 0;
+  };
+  int GetNumExternalDecoderRegistered(int channel) const {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->ext_decoder_pl_types_.size();
+  };
+  bool ExternalEncoderRegistered(int channel,
+                                 unsigned int pl_type) const {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->
+        ext_encoder_pl_types_.count(pl_type) != 0;
+  };
+  int GetNumExternalEncoderRegistered(int channel) const {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->ext_encoder_pl_types_.size();
+  };
+  int GetTotalNumExternalEncoderRegistered() const {
+    std::map<int, Channel*>::const_iterator it;
+    int total_num_registered = 0;
+    for (it = channels_.begin(); it != channels_.end(); ++it)
+      total_num_registered += it->second->ext_encoder_pl_types_.size();
+    return total_num_registered;
+  }
+  void SetSendBitrates(int channel, unsigned int video_bitrate,
+                       unsigned int fec_bitrate, unsigned int nack_bitrate) {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    channels_[channel]->send_video_bitrate_ = video_bitrate;
+    channels_[channel]->send_fec_bitrate_ = fec_bitrate;
+    channels_[channel]->send_nack_bitrate_ = nack_bitrate;
+  }
+  void SetSendBandwidthEstimate(int channel, unsigned int send_bandwidth) {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    channels_[channel]->send_bandwidth_ = send_bandwidth;
+  }
+  void SetReceiveBandwidthEstimate(int channel,
+                                   unsigned int receive_bandwidth) {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    channels_[channel]->receive_bandwidth_ = receive_bandwidth;
+  };
+
+  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_FUNC(CreateChannel, (int& channel, int original_channel)) {
+    WEBRTC_CHECK_CHANNEL(original_channel);
+    if (CreateChannel(channel) != 0) {
+      return -1;
+    }
+    channels_[channel]->original_channel_id_ = original_channel;
+    return 0;
+  }
+  WEBRTC_FUNC(CreateReceiveChannel, (int& channel, int original_channel)) {
+    return CreateChannel(channel, original_channel);
+  }
+  WEBRTC_FUNC(DeleteChannel, (const int channel)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    // Make sure we deregister all the decoders before deleting a channel.
+    EXPECT_EQ(0, GetNumExternalDecoderRegistered(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_FUNC(StartReceive, (const int channel)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->receive_ = true;
+    return 0;
+  }
+  WEBRTC_FUNC(StopReceive, (const int channel)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->receive_ = false;
+    return 0;
+  }
+  WEBRTC_STUB(GetVersion, (char version[1024]));
+  WEBRTC_STUB(LastError, ());
+
+  // webrtc::ViECodec
+  WEBRTC_FUNC_CONST(NumberOfCodecs, ()) {
+    return num_codecs_;
+  };
+  WEBRTC_FUNC_CONST(GetCodec, (const unsigned char list_number,
+                               webrtc::VideoCodec& out_codec)) {
+    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 = kMinVideoBitrate;
+    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;
+    ++num_set_send_codecs_;
+    return 0;
+  };
+  WEBRTC_FUNC_CONST(GetSendCodec, (const int channel,
+                                   webrtc::VideoCodec& codec)) {  // 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&));
+  WEBRTC_STUB_CONST(GetReceiveSideDelay, (const int video_channel,
+                                          int* delay_ms));
+  WEBRTC_FUNC_CONST(GetCodecTargetBitrate, (const int channel,
+      unsigned int* codec_target_bitrate)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+
+    std::map<int, Channel*>::const_iterator it = channels_.find(channel);
+    if (it->second->send) {
+      // Assume the encoder produces the expected rate.
+      *codec_target_bitrate = it->second->send_video_bitrate_;
+    } else {
+      *codec_target_bitrate = 0;
+    }
+    return 0;
+  }
+  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));
+#ifdef USE_WEBRTC_DEV_BRANCH
+  WEBRTC_STUB(StartDebugRecording, (int, const char*));
+  WEBRTC_STUB(StopDebugRecording, (int));
+#endif
+
+  // 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_VOID_FUNC(SetNetworkTransmissionState, (const int channel,
+                                                 const bool is_transmitting)) {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    channels_[channel]->can_transmit_ = is_transmitting;
+  }
+  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));
+  // Not using WEBRTC_STUB due to bool return value
+  virtual bool IsIPv6Enabled(int channel) { return true; }
+  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::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(SetExpectedRenderDelay, (int render_id, int render_delay));
+  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 idx)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->ssrcs_[idx] = 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)) {
+    // ssrcs_[0] is the default local ssrc.
+    WEBRTC_CHECK_CHANNEL(channel);
+    ssrc = channels_.find(channel)->second->ssrcs_[0];
+    return 0;
+  }
+  WEBRTC_STUB_CONST(GetRemoteSSRC, (const int, unsigned int&));
+  WEBRTC_STUB_CONST(GetRemoteCSRCs, (const int, unsigned int*));
+
+  WEBRTC_STUB(SetRtxSendPayloadType, (const int, const uint8));
+  WEBRTC_STUB(SetRtxReceivePayloadType, (const int, const uint8));
+
+  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_ = enable;
+    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_FUNC(SetSenderBufferingMode, (int channel, int target_delay)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->sender_target_delay_ = target_delay;
+    return 0;
+  }
+  WEBRTC_FUNC(SetReceiverBufferingMode, (int channel, int target_delay)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->receiver_target_delay_ = target_delay;
+    return 0;
+  }
+  // |Send| and |receive| are stored locally in variables that more clearly
+  // explain what they mean.
+  WEBRTC_FUNC(SetRembStatus, (int channel, bool send, bool receive)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->remb_contribute_ = receive;
+    channels_[channel]->remb_bw_partition_ = send;
+    return 0;
+  }
+  WEBRTC_FUNC(SetTMMBRStatus, (const int channel, const bool enable)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->tmmbr_ = enable;
+    return 0;
+  }
+  WEBRTC_FUNC(SetSendTimestampOffsetStatus, (int channel, bool enable,
+      int id)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->rtp_offset_send_id_ = (enable) ? id : 0;
+    return 0;
+  }
+  WEBRTC_FUNC(SetReceiveTimestampOffsetStatus, (int channel, bool enable,
+      int id)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->rtp_offset_receive_id_ = (enable) ? id : 0;
+    return 0;
+  }
+  WEBRTC_FUNC(SetSendAbsoluteSendTimeStatus, (int channel, bool enable,
+      int id)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->rtp_absolute_send_time_send_id_ = (enable) ? id : 0;
+    return 0;
+  }
+  WEBRTC_FUNC(SetReceiveAbsoluteSendTimeStatus, (int channel, bool enable,
+      int id)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->rtp_absolute_send_time_receive_id_ = (enable) ? id : 0;
+    return 0;
+  }
+  WEBRTC_FUNC(SetTransmissionSmoothingStatus, (int channel, bool enable)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->transmission_smoothing_ = 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_FUNC_CONST(GetBandwidthUsage, (const int channel,
+      unsigned int& total_bitrate, unsigned int& video_bitrate,
+      unsigned int& fec_bitrate, unsigned int& nack_bitrate)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    std::map<int, Channel*>::const_iterator it = channels_.find(channel);
+    if (it->second->send) {
+      video_bitrate = it->second->send_video_bitrate_;
+      fec_bitrate = it->second->send_fec_bitrate_;
+      nack_bitrate = it->second->send_nack_bitrate_;
+      total_bitrate = video_bitrate + fec_bitrate + nack_bitrate;
+    } else {
+      total_bitrate = 0;
+      video_bitrate = 0;
+      fec_bitrate = 0;
+      nack_bitrate = 0;
+    }
+    return 0;
+  }
+  WEBRTC_FUNC_CONST(GetEstimatedSendBandwidth, (const int channel,
+      unsigned int* send_bandwidth_estimate)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    std::map<int, Channel*>::const_iterator it = channels_.find(channel);
+    // Assume the current video, fec and nack bitrate sums up to our estimate.
+    if (it->second->send) {
+      *send_bandwidth_estimate = it->second->send_bandwidth_;
+    } else {
+      *send_bandwidth_estimate = 0;
+    }
+    return 0;
+  }
+  WEBRTC_FUNC_CONST(GetEstimatedReceiveBandwidth, (const int channel,
+      unsigned int* receive_bandwidth_estimate)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    std::map<int, Channel*>::const_iterator it = channels_.find(channel);
+    if (it->second->receive_) {
+    // For simplicity, assume all channels receive half of max send rate.
+      *receive_bandwidth_estimate = it->second->receive_bandwidth_;
+    } else {
+      *receive_bandwidth_estimate = 0;
+    }
+    return 0;
+  }
+
+  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_FUNC(EnableDenoising, (const int capture_id, const bool denoising)) {
+    WEBRTC_CHECK_CAPTURER(capture_id);
+    capturers_[capture_id]->set_denoising(denoising);
+    return 0;
+  }
+  WEBRTC_STUB(EnableColorEnhancement, (const int, const bool));
+
+  // webrtc::ViEExternalCodec
+  WEBRTC_FUNC(RegisterExternalSendCodec,
+      (const int channel, const unsigned char pl_type, webrtc::VideoEncoder*,
+          bool)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->ext_encoder_pl_types_.insert(pl_type);
+    return 0;
+  }
+  WEBRTC_FUNC(DeRegisterExternalSendCodec,
+      (const int channel, const unsigned char pl_type)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->ext_encoder_pl_types_.erase(pl_type);
+    return 0;
+  }
+  WEBRTC_FUNC(RegisterExternalReceiveCodec,
+      (const int channel, const unsigned int pl_type, webrtc::VideoDecoder*,
+       bool, int)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->ext_decoder_pl_types_.insert(pl_type);
+    return 0;
+  }
+  WEBRTC_FUNC(DeRegisterExternalReceiveCodec,
+      (const int channel, const unsigned char pl_type)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->ext_decoder_pl_types_.erase(pl_type);
+    return 0;
+  }
+
+ 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_;
+  int num_set_send_codecs_;  // how many times we call SetSendCodec().
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_WEBRTC_FAKEWEBRTCVIDEOENGINE_H_
diff --git a/talk/media/webrtc/fakewebrtcvoiceengine.h b/talk/media/webrtc/fakewebrtcvoiceengine.h
new file mode 100644
index 0000000..7202e15
--- /dev/null
+++ b/talk/media/webrtc/fakewebrtcvoiceengine.h
@@ -0,0 +1,1010 @@
+/*
+ * 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/media/base/codec.h"
+#include "talk/media/base/voiceprocessor.h"
+#include "talk/media/webrtc/fakewebrtccommon.h"
+#include "talk/media/webrtc/webrtcvoe.h"
+
+namespace cricket {
+
+// Function returning stats will return these values
+// for all values based on type.
+const int kIntStatValue = 123;
+const float kFractionLostStatValue = 0.5;
+
+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 DtmfInfo {
+    DtmfInfo()
+      : dtmf_event_code(-1),
+        dtmf_out_of_band(false),
+        dtmf_length_ms(-1) {}
+    int dtmf_event_code;
+    bool dtmf_out_of_band;
+    int dtmf_length_ms;
+  };
+  struct Channel {
+    Channel()
+        : external_transport(false),
+          send(false),
+          playout(false),
+          volume_scale(1.0),
+          volume_pan_left(1.0),
+          volume_pan_right(1.0),
+          file(false),
+          vad(false),
+          fec(false),
+          nack(false),
+          media_processor_registered(false),
+          cn8_type(13),
+          cn16_type(105),
+          dtmf_type(106),
+          fec_type(117),
+          nack_max_packets(0),
+          send_ssrc(0),
+          level_header_ext_(-1) {
+      memset(&send_codec, 0, sizeof(send_codec));
+    }
+    bool external_transport;
+    bool send;
+    bool playout;
+    float volume_scale;
+    float volume_pan_left;
+    float volume_pan_right;
+    bool file;
+    bool vad;
+    bool fec;
+    bool nack;
+    bool media_processor_registered;
+    int cn8_type;
+    int cn16_type;
+    int dtmf_type;
+    int fec_type;
+    int nack_max_packets;
+    uint32 send_ssrc;
+    int level_header_ext_;
+    DtmfInfo dtmf_info;
+    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),
+        ec_metrics_enabled_(false),
+        cng_enabled_(false),
+        ns_enabled_(false),
+        agc_enabled_(false),
+        highpass_filter_enabled_(false),
+        stereo_swapping_enabled_(false),
+        typing_detection_enabled_(false),
+        ec_mode_(webrtc::kEcDefault),
+        aecm_mode_(webrtc::kAecmSpeakerphone),
+        ns_mode_(webrtc::kNsDefault),
+        agc_mode_(webrtc::kAgcDefault),
+        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;
+  }
+  bool GetNACK(int channel) {
+    return channels_[channel]->nack;
+  }
+  int GetNACKMaxPackets(int channel) {
+    return channels_[channel]->nack_max_packets;
+  }
+  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::kPlaybackAllChannelsMixed;
+    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_FUNC(Init, (webrtc::AudioDeviceModule* adm,
+                     webrtc::AudioProcessing* audioproc)) {
+    inited_ = true;
+    return 0;
+  }
+  WEBRTC_FUNC(Terminate, ()) {
+    inited_ = false;
+    return 0;
+  }
+  virtual webrtc::AudioProcessing* audio_processing() OVERRIDE {
+    return NULL;
+  }
+  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(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::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(SetSecondarySendCodec, (int channel,
+                                      const webrtc::CodecInst& codec,
+                                      int red_payload_type));
+  WEBRTC_STUB(RemoveSecondarySendCodec, (int channel));
+  WEBRTC_STUB(GetSecondarySendCodec, (int channel,
+                                      webrtc::CodecInst& codec));
+  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];
+    if (ch->playout)
+      return -1;  // Channel is in use.
+    // 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 &&
+            _stricmp(it->plname, codec.plname) != 0) {
+          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;
+        it->channels = codec.channels;
+        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->channels == codec.channels &&
+          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);
+    if (channels_[channel]->send_codec.channels == 2) {
+      // Replicating VoE behavior; VAD cannot be enabled for stereo.
+      return -1;
+    }
+    channels_[channel]->vad = enable;
+    return 0;
+  }
+  WEBRTC_STUB(GetVADStatus, (int channel, bool& enabled,
+                             webrtc::VadModes& mode, bool& disabledDTX));
+
+  // webrtc::VoEDtmf
+  WEBRTC_FUNC(SendTelephoneEvent, (int channel, int event_code,
+      bool out_of_band = true, int length_ms = 160, int attenuation_db = 10)) {
+    channels_[channel]->dtmf_info.dtmf_event_code = event_code;
+    channels_[channel]->dtmf_info.dtmf_out_of_band = out_of_band;
+    channels_[channel]->dtmf_info.dtmf_length_ms = length_ms;
+    return 0;
+  }
+
+  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(SetDtmfPlayoutStatus, (int channel, bool enable));
+  WEBRTC_STUB(GetDtmfPlayoutStatus, (int channel, bool& enabled));
+
+
+  WEBRTC_FUNC(PlayDtmfTone,
+      (int event_code, int length_ms = 200, int attenuation_db = 10)) {
+    dtmf_info_.dtmf_event_code = event_code;
+    dtmf_info_.dtmf_length_ms = length_ms;
+    return 0;
+  }
+  WEBRTC_STUB(StartPlayingDtmfTone,
+      (int eventCode, int attenuationDb = 10));
+  WEBRTC_STUB(StopPlayingDtmfTone, ());
+
+  // 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_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(SetLoudspeakerStatus, (bool enable));
+  WEBRTC_STUB(GetLoudspeakerStatus, (bool& enabled));
+  WEBRTC_STUB(SetRecordingSampleRate, (unsigned int samples_per_sec));
+  WEBRTC_STUB_CONST(RecordingSampleRate, (unsigned int* samples_per_sec));
+  WEBRTC_STUB(SetPlayoutSampleRate, (unsigned int samples_per_sec));
+  WEBRTC_STUB_CONST(PlayoutSampleRate, (unsigned int* samples_per_sec));
+  WEBRTC_STUB(EnableBuiltInAEC, (bool enable));
+  virtual bool BuiltInAECIsEnabled() const { return true; }
+
+  // webrtc::VoENetEqStats
+  WEBRTC_STUB(GetNetworkStatistics, (int, webrtc::NetworkStatistics&));
+
+  // 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));
+  // Not using WEBRTC_STUB due to bool return value
+  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::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);
+    if (enable && (id < 1 || id > 14)) {
+      // [RFC5285] The 4-bit ID is the local identifier of this element in
+      // the range 1-14 inclusive.
+      return -1;
+    }
+    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(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(GetRemoteRTCPSenderInfo, (int channel,
+                                        webrtc::SenderInfo* sender_info));
+  WEBRTC_FUNC(GetRemoteRTCPReportBlocks,
+              (int channel, std::vector<webrtc::ReportBlock>* receive_blocks)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    webrtc::ReportBlock block;
+    block.source_SSRC = channels_[channel]->send_ssrc;
+    webrtc::CodecInst send_codec = channels_[channel]->send_codec;
+    if (send_codec.pltype >= 0) {
+      block.fraction_lost = (unsigned char)(kFractionLostStatValue * 256);
+      if (send_codec.plfreq / 1000 > 0) {
+        block.interarrival_jitter = kIntStatValue * (send_codec.plfreq / 1000);
+      }
+      block.cumulative_num_packets_lost = kIntStatValue;
+      block.extended_highest_sequence_number = kIntStatValue;
+      receive_blocks->push_back(block);
+    }
+    return 0;
+  }
+  WEBRTC_STUB(SendApplicationDefinedRTCPPacket, (int channel,
+                                                 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_FUNC(GetRTCPStatistics, (int channel, webrtc::CallStatistics& stats)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    stats.fractionLost = static_cast<int16>(kIntStatValue);
+    stats.cumulativeLost = kIntStatValue;
+    stats.extendedMax = kIntStatValue;
+    stats.jitterSamples = kIntStatValue;
+    stats.rttMs = kIntStatValue;
+    stats.bytesSent = kIntStatValue;
+    stats.packetsSent = kIntStatValue;
+    stats.bytesReceived = kIntStatValue;
+    stats.packetsReceived = kIntStatValue;
+    return 0;
+  }
+  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_FUNC(SetNACKStatus, (int channel, bool enable, int maxNoPackets)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->nack = enable;
+    channels_[channel]->nack_max_packets = maxNoPackets;
+    return 0;
+  }
+  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_STUB(GetLastRemoteTimeStamp, (int channel,
+                                       uint32_t* lastRemoteTimeStamp));
+
+  // 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(SetInitialPlayoutDelay, (int channel, int delay_ms));
+  WEBRTC_STUB(GetDelayEstimate, (int channel, int* jitter_buffer_delay_ms,
+                                 int* playout_buffer_delay_ms));
+  WEBRTC_STUB_CONST(GetLeastRequiredDelayMs, (int channel));
+
+  // 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_FUNC(SetChannelOutputVolumeScaling, (int channel, float scale)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->volume_scale= scale;
+    return 0;
+  }
+  WEBRTC_FUNC(GetChannelOutputVolumeScaling, (int channel, float& scale)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    scale = channels_[channel]->volume_scale;
+    return 0;
+  }
+  WEBRTC_FUNC(SetOutputVolumePan, (int channel, float left, float right)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    channels_[channel]->volume_pan_left = left;
+    channels_[channel]->volume_pan_right = right;
+    return 0;
+  }
+  WEBRTC_FUNC(GetOutputVolumePan, (int channel, float& left, float& right)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    left = channels_[channel]->volume_pan_left;
+    right = channels_[channel]->volume_pan_right;
+    return 0;
+  }
+
+  // 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_FUNC(SetAgcStatus, (bool enable, webrtc::AgcModes mode)) {
+    agc_enabled_ = enable;
+    agc_mode_ = mode;
+    return 0;
+  }
+  WEBRTC_FUNC(GetAgcStatus, (bool& enabled, webrtc::AgcModes& mode)) {
+    enabled = agc_enabled_;
+    mode = agc_mode_;
+    return 0;
+  }
+
+  WEBRTC_FUNC(SetAgcConfig, (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(EnableDriftCompensation, (bool enable))
+  WEBRTC_BOOL_STUB(DriftCompensationEnabled, ())
+  WEBRTC_VOID_STUB(SetDelayOffsetMs, (int offset))
+  WEBRTC_STUB(DelayOffsetMs, ());
+  WEBRTC_FUNC(SetAecmMode, (webrtc::AecmModes mode, bool enableCNG)) {
+    aecm_mode_ = mode;
+    cng_enabled_ = enableCNG;
+    return 0;
+  }
+  WEBRTC_FUNC(GetAecmMode, (webrtc::AecmModes& mode, bool& enabledCNG)) {
+    mode = aecm_mode_;
+    enabledCNG = cng_enabled_;
+    return 0;
+  }
+  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, 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_FUNC(SetEcMetricsStatus, (bool enable)) {
+    ec_metrics_enabled_ = enable;
+    return 0;
+  }
+  WEBRTC_FUNC(GetEcMetricsStatus, (bool& enabled)) {
+    enabled = ec_metrics_enabled_;
+    return 0;
+  }
+  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_FUNC(SetTypingDetectionStatus, (bool enable)) {
+    typing_detection_enabled_ = enable;
+    return 0;
+  }
+  WEBRTC_FUNC(GetTypingDetectionStatus, (bool& enabled)) {
+    enabled = typing_detection_enabled_;
+    return 0;
+  }
+
+  WEBRTC_STUB(TimeSinceLastTyping, (int& seconds));
+  WEBRTC_STUB(SetTypingDetectionParameters, (int timeWindow,
+                                             int costPerTyping,
+                                             int reportingThreshold,
+                                             int penaltyDecay,
+                                             int typeEventDelay));
+  int EnableHighPassFilter(bool enable) {
+    highpass_filter_enabled_ = enable;
+    return 0;
+  }
+  bool IsHighPassFilterEnabled() {
+    return highpass_filter_enabled_;
+  }
+  bool IsStereoChannelSwappingEnabled() {
+    return stereo_swapping_enabled_;
+  }
+  void EnableStereoChannelSwapping(bool enable) {
+    stereo_swapping_enabled_ = enable;
+  }
+  bool WasSendTelephoneEventCalled(int channel, int event_code, int length_ms) {
+    return (channels_[channel]->dtmf_info.dtmf_event_code == event_code &&
+            channels_[channel]->dtmf_info.dtmf_out_of_band == true &&
+            channels_[channel]->dtmf_info.dtmf_length_ms == length_ms);
+  }
+  bool WasPlayDtmfToneCalled(int event_code, int length_ms) {
+    return (dtmf_info_.dtmf_event_code == event_code &&
+            dtmf_info_.dtmf_length_ms == length_ms);
+  }
+  // webrtc::VoEExternalMedia
+  WEBRTC_FUNC(RegisterExternalMediaProcessing,
+              (int channel, webrtc::ProcessingTypes type,
+               webrtc::VoEMediaProcess& processObject)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    if (channels_[channel]->media_processor_registered) {
+      return -1;
+    }
+    channels_[channel]->media_processor_registered = true;
+    media_processor_ = &processObject;
+    return 0;
+  }
+  WEBRTC_FUNC(DeRegisterExternalMediaProcessing,
+              (int channel, webrtc::ProcessingTypes type)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    if (!channels_[channel]->media_processor_registered) {
+      return -1;
+    }
+    channels_[channel]->media_processor_registered = false;
+    media_processor_ = NULL;
+    return 0;
+  }
+  WEBRTC_STUB(SetExternalRecordingStatus, (bool enable));
+  WEBRTC_STUB(SetExternalPlayoutStatus, (bool enable));
+  WEBRTC_STUB(ExternalRecordingInsertData,
+              (const int16_t speechData10ms[], int lengthSamples,
+               int samplingFreqHz, int current_delay_ms));
+  WEBRTC_STUB(ExternalPlayoutGetData,
+              (int16_t speechData10ms[], int samplingFreqHz,
+               int current_delay_ms, int& lengthSamples));
+  WEBRTC_STUB(GetAudioFrame, (int channel, int desired_sample_rate_hz,
+                              webrtc::AudioFrame* frame));
+  WEBRTC_STUB(SetExternalMixing, (int channel, bool enable));
+
+ 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 ec_metrics_enabled_;
+  bool cng_enabled_;
+  bool ns_enabled_;
+  bool agc_enabled_;
+  bool highpass_filter_enabled_;
+  bool stereo_swapping_enabled_;
+  bool typing_detection_enabled_;
+  webrtc::EcModes ec_mode_;
+  webrtc::AecmModes aecm_mode_;
+  webrtc::NsModes ns_mode_;
+  webrtc::AgcModes agc_mode_;
+  webrtc::AgcConfig agc_config_;
+  webrtc::VoiceEngineObserver* observer_;
+  int playout_fail_channel_;
+  int send_fail_channel_;
+  bool fail_start_recording_microphone_;
+  bool recording_microphone_;
+  DtmfInfo dtmf_info_;
+  webrtc::VoEMediaProcess* media_processor_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_PHONE_FAKEWEBRTCVOICEENGINE_H_
diff --git a/talk/media/webrtc/webrtccommon.h b/talk/media/webrtc/webrtccommon.h
new file mode 100644
index 0000000..3a557f1
--- /dev/null
+++ b/talk/media/webrtc/webrtccommon.h
@@ -0,0 +1,76 @@
+/*
+ * 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_MEDIA_WEBRTCCOMMON_H_
+#define TALK_MEDIA_WEBRTCCOMMON_H_
+
+#include "webrtc/common_types.h"
+
+namespace cricket {
+
+// Tracing helpers, for easy logging when WebRTC calls fail.
+// Example: "LOG_RTCERR1(StartSend, channel);" produces the trace
+//          "StartSend(1) failed, err=XXXX"
+// The method GetLastEngineError must be defined in the calling scope.
+#define LOG_RTCERR0(func) \
+    LOG_RTCERR0_EX(func, GetLastEngineError())
+#define LOG_RTCERR1(func, a1) \
+    LOG_RTCERR1_EX(func, a1, GetLastEngineError())
+#define LOG_RTCERR2(func, a1, a2) \
+    LOG_RTCERR2_EX(func, a1, a2, GetLastEngineError())
+#define LOG_RTCERR3(func, a1, a2, a3) \
+    LOG_RTCERR3_EX(func, a1, a2, a3, GetLastEngineError())
+#define LOG_RTCERR4(func, a1, a2, a3, a4) \
+    LOG_RTCERR4_EX(func, a1, a2, a3, a4, GetLastEngineError())
+#define LOG_RTCERR5(func, a1, a2, a3, a4, a5) \
+    LOG_RTCERR5_EX(func, a1, a2, a3, a4, a5, GetLastEngineError())
+#define LOG_RTCERR6(func, a1, a2, a3, a4, a5, a6) \
+    LOG_RTCERR6_EX(func, a1, a2, a3, a4, a5, a6, GetLastEngineError())
+#define LOG_RTCERR0_EX(func, err) LOG(LS_WARNING) \
+    << "" << #func << "() failed, err=" << err
+#define LOG_RTCERR1_EX(func, a1, err) LOG(LS_WARNING) \
+    << "" << #func << "(" << a1 << ") failed, err=" << err
+#define LOG_RTCERR2_EX(func, a1, a2, err) LOG(LS_WARNING) \
+    << "" << #func << "(" << a1 << ", " << a2 << ") failed, err=" \
+    << err
+#define LOG_RTCERR3_EX(func, a1, a2, a3, err) LOG(LS_WARNING) \
+    << "" << #func << "(" << a1 << ", " << a2 << ", " << a3 \
+    << ") failed, err=" << err
+#define LOG_RTCERR4_EX(func, a1, a2, a3, a4, err) LOG(LS_WARNING) \
+    << "" << #func << "(" << a1 << ", " << a2 << ", " << a3 \
+    << ", " << a4 << ") failed, err=" << err
+#define LOG_RTCERR5_EX(func, a1, a2, a3, a4, a5, err) LOG(LS_WARNING) \
+    << "" << #func << "(" << a1 << ", " << a2 << ", " << a3 \
+    << ", " << a4 << ", " << a5 << ") failed, err=" << err
+#define LOG_RTCERR6_EX(func, a1, a2, a3, a4, a5, a6, err) LOG(LS_WARNING) \
+    << "" << #func << "(" << a1 << ", " << a2 << ", " << a3 \
+    << ", " << a4 << ", " << a5 << ", " << a6 << ") failed, err=" << err
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_WEBRTCCOMMON_H_
diff --git a/talk/media/webrtc/webrtcexport.h b/talk/media/webrtc/webrtcexport.h
new file mode 100644
index 0000000..71ebe4e
--- /dev/null
+++ b/talk/media/webrtc/webrtcexport.h
@@ -0,0 +1,79 @@
+/*
+ * libjingle
+ * Copyright 2004--2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_MEDIA_WEBRTC_WEBRTCEXPORT_H_
+#define TALK_MEDIA_WEBRTC_WEBRTCEXPORT_H_
+
+#if !defined(GOOGLE_CHROME_BUILD) && !defined(CHROMIUM_BUILD)
+#define LIBPEERCONNECTION_LIB 1
+#endif
+
+#ifndef NON_EXPORTED_BASE
+#ifdef WIN32
+
+// MSVC_SUPPRESS_WARNING disables warning |n| for the remainder of the line and
+// for the next line of the source file.
+#define MSVC_SUPPRESS_WARNING(n) __pragma(warning(suppress:n))
+
+// Allows exporting a class that inherits from a non-exported base class.
+// This uses suppress instead of push/pop because the delimiter after the
+// declaration (either "," or "{") has to be placed before the pop macro.
+//
+// Example usage:
+// class EXPORT_API Foo : NON_EXPORTED_BASE(public Bar) {
+//
+// MSVC Compiler warning C4275:
+// non dll-interface class 'Bar' used as base for dll-interface class 'Foo'.
+// Note that this is intended to be used only when no access to the base class'
+// static data is done through derived classes or inline methods. For more info,
+// see http://msdn.microsoft.com/en-us/library/3tdb471s(VS.80).aspx
+#define NON_EXPORTED_BASE(code) MSVC_SUPPRESS_WARNING(4275) \
+                                code
+
+#else  // Not WIN32
+#define NON_EXPORTED_BASE(code) code
+#endif  // WIN32
+#endif  // NON_EXPORTED_BASE
+
+#if defined (LIBPEERCONNECTION_LIB)
+  #define WRME_EXPORT
+#else
+  #if defined(WIN32)
+    #if defined(LIBPEERCONNECTION_IMPLEMENTATION)
+      #define WRME_EXPORT __declspec(dllexport)
+    #else
+      #define WRME_EXPORT __declspec(dllimport)
+    #endif
+  #else // defined(WIN32)
+    #if defined(LIBPEERCONNECTION_IMPLEMENTATION)
+      #define WRME_EXPORT __attribute__((visibility("default")))
+    #else
+      #define WRME_EXPORT
+    #endif
+  #endif
+#endif  // LIBPEERCONNECTION_LIB
+
+#endif  // TALK_MEDIA_WEBRTC_WEBRTCEXPORT_H_
diff --git a/talk/media/webrtc/webrtcmediaengine.h b/talk/media/webrtc/webrtcmediaengine.h
new file mode 100644
index 0000000..1a2de8e
--- /dev/null
+++ b/talk/media/webrtc/webrtcmediaengine.h
@@ -0,0 +1,203 @@
+/*
+ * 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_MEDIA_WEBRTCMEDIAENGINE_H_
+#define TALK_MEDIA_WEBRTCMEDIAENGINE_H_
+
+#include "talk/media/base/mediaengine.h"
+#include "talk/media/webrtc/webrtcexport.h"
+
+namespace webrtc {
+class AudioDeviceModule;
+class VideoCaptureModule;
+}
+namespace cricket {
+class WebRtcVideoDecoderFactory;
+class WebRtcVideoEncoderFactory;
+}
+
+
+#if !defined(LIBPEERCONNECTION_LIB) && \
+    !defined(LIBPEERCONNECTION_IMPLEMENTATION)
+
+WRME_EXPORT
+cricket::MediaEngineInterface* CreateWebRtcMediaEngine(
+    webrtc::AudioDeviceModule* adm, webrtc::AudioDeviceModule* adm_sc,
+    cricket::WebRtcVideoEncoderFactory* encoder_factory,
+    cricket::WebRtcVideoDecoderFactory* decoder_factory);
+
+WRME_EXPORT
+void DestroyWebRtcMediaEngine(cricket::MediaEngineInterface* media_engine);
+
+namespace cricket {
+
+class WebRtcMediaEngine : public cricket::MediaEngineInterface {
+ public:
+  WebRtcMediaEngine(
+      webrtc::AudioDeviceModule* adm,
+      webrtc::AudioDeviceModule* adm_sc,
+      cricket::WebRtcVideoEncoderFactory* encoder_factory,
+      cricket::WebRtcVideoDecoderFactory* decoder_factory)
+      : delegate_(CreateWebRtcMediaEngine(
+          adm, adm_sc, encoder_factory, decoder_factory)) {
+  }
+  virtual ~WebRtcMediaEngine() {
+    DestroyWebRtcMediaEngine(delegate_);
+  }
+  virtual bool Init(talk_base::Thread* worker_thread) OVERRIDE {
+    return delegate_->Init(worker_thread);
+  }
+  virtual void Terminate() OVERRIDE {
+    delegate_->Terminate();
+  }
+  virtual int GetCapabilities() OVERRIDE {
+    return delegate_->GetCapabilities();
+  }
+  virtual VoiceMediaChannel* CreateChannel() OVERRIDE {
+    return delegate_->CreateChannel();
+  }
+  virtual VideoMediaChannel* CreateVideoChannel(
+      VoiceMediaChannel* voice_media_channel) OVERRIDE {
+    return delegate_->CreateVideoChannel(voice_media_channel);
+  }
+  virtual SoundclipMedia* CreateSoundclip() OVERRIDE {
+    return delegate_->CreateSoundclip();
+  }
+  virtual bool SetAudioOptions(int options) OVERRIDE {
+    return delegate_->SetAudioOptions(options);
+  }
+  virtual bool SetVideoOptions(int options) OVERRIDE {
+    return delegate_->SetVideoOptions(options);
+  }
+  virtual bool SetAudioDelayOffset(int offset) OVERRIDE {
+    return delegate_->SetAudioDelayOffset(offset);
+  }
+  virtual bool SetDefaultVideoEncoderConfig(
+      const VideoEncoderConfig& config) OVERRIDE {
+    return delegate_->SetDefaultVideoEncoderConfig(config);
+  }
+  virtual bool SetSoundDevices(
+      const Device* in_device, const Device* out_device) OVERRIDE {
+    return delegate_->SetSoundDevices(in_device, out_device);
+  }
+  virtual bool SetVideoCapturer(VideoCapturer* capturer) OVERRIDE {
+    return delegate_->SetVideoCapturer(capturer);
+  }
+  virtual VideoCapturer* GetVideoCapturer() const {
+    return delegate_->GetVideoCapturer();
+  }
+  virtual bool GetOutputVolume(int* level) OVERRIDE {
+    return delegate_->GetOutputVolume(level);
+  }
+  virtual bool SetOutputVolume(int level) OVERRIDE {
+    return delegate_->SetOutputVolume(level);
+  }
+  virtual int GetInputLevel() OVERRIDE {
+    return delegate_->GetInputLevel();
+  }
+  virtual bool SetLocalMonitor(bool enable) OVERRIDE {
+    return delegate_->SetLocalMonitor(enable);
+  }
+  virtual bool SetLocalRenderer(VideoRenderer* renderer) OVERRIDE {
+    return delegate_->SetLocalRenderer(renderer);
+  }
+  virtual bool SetVideoCapture(bool capture) OVERRIDE {
+    return delegate_->SetVideoCapture(capture);
+  }
+  virtual const std::vector<AudioCodec>& audio_codecs() OVERRIDE {
+    return delegate_->audio_codecs();
+  }
+  virtual const std::vector<RtpHeaderExtension>&
+      audio_rtp_header_extensions() OVERRIDE {
+    return delegate_->audio_rtp_header_extensions();
+  }
+  virtual const std::vector<VideoCodec>& video_codecs() OVERRIDE {
+    return delegate_->video_codecs();
+  }
+  virtual const std::vector<RtpHeaderExtension>&
+      video_rtp_header_extensions() OVERRIDE {
+    return delegate_->video_rtp_header_extensions();
+  }
+  virtual void SetVoiceLogging(int min_sev, const char* filter) OVERRIDE {
+    delegate_->SetVoiceLogging(min_sev, filter);
+  }
+  virtual void SetVideoLogging(int min_sev, const char* filter) OVERRIDE {
+    delegate_->SetVideoLogging(min_sev, filter);
+  }
+  virtual bool RegisterVoiceProcessor(
+      uint32 ssrc, VoiceProcessor* video_processor,
+      MediaProcessorDirection direction) OVERRIDE {
+    return delegate_->RegisterVoiceProcessor(ssrc, video_processor, direction);
+  }
+  virtual bool UnregisterVoiceProcessor(
+      uint32 ssrc, VoiceProcessor* video_processor,
+      MediaProcessorDirection direction) OVERRIDE {
+    return delegate_->UnregisterVoiceProcessor(ssrc, video_processor,
+        direction);
+  }
+  virtual VideoFormat GetStartCaptureFormat() const OVERRIDE {
+    return delegate_->GetStartCaptureFormat();
+  }
+  virtual sigslot::repeater2<VideoCapturer*, CaptureState>&
+      SignalVideoCaptureStateChange() {
+    return delegate_->SignalVideoCaptureStateChange();
+  }
+
+ private:
+  cricket::MediaEngineInterface* delegate_;
+};
+
+}  // namespace cricket
+#else
+
+#include "talk/media/webrtc/webrtcvideoengine.h"
+#include "talk/media/webrtc/webrtcvoiceengine.h"
+
+namespace cricket {
+typedef CompositeMediaEngine<WebRtcVoiceEngine, WebRtcVideoEngine>
+        WebRtcCompositeMediaEngine;
+
+class WebRtcMediaEngine : public WebRtcCompositeMediaEngine {
+ public:
+  WebRtcMediaEngine(webrtc::AudioDeviceModule* adm,
+      webrtc::AudioDeviceModule* adm_sc,
+      WebRtcVideoEncoderFactory* encoder_factory,
+      WebRtcVideoDecoderFactory* decoder_factory) {
+    voice_.SetAudioDeviceModule(adm, adm_sc);
+    video_.SetVoiceEngine(&voice_);
+    video_.EnableTimedRender();
+    video_.SetExternalEncoderFactory(encoder_factory);
+    video_.SetExternalDecoderFactory(decoder_factory);
+  }
+};
+
+}  // namespace cricket
+
+#endif  // !defined(LIBPEERCONNECTION_LIB) &&
+        // !defined(LIBPEERCONNECTION_IMPLEMENTATION)
+
+#endif  // TALK_MEDIA_WEBRTCMEDIAENGINE_H_
diff --git a/talk/media/webrtc/webrtcpassthroughrender.cc b/talk/media/webrtc/webrtcpassthroughrender.cc
new file mode 100644
index 0000000..b4e78b4
--- /dev/null
+++ b/talk/media/webrtc/webrtcpassthroughrender.cc
@@ -0,0 +1,176 @@
+/*
+ * 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/media/webrtc/webrtcpassthroughrender.h"
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+
+namespace cricket {
+
+#define LOG_FIND_STREAM_ERROR(func, id) LOG(LS_ERROR) \
+    << "" << func << " - Failed to find stream: " << id
+
+class PassthroughStream: public webrtc::VideoRenderCallback {
+ public:
+  explicit PassthroughStream(const uint32_t stream_id)
+      : stream_id_(stream_id),
+        running_(false) {
+  }
+  virtual ~PassthroughStream() {
+  }
+  virtual int32_t RenderFrame(const uint32_t stream_id,
+                              webrtc::I420VideoFrame& videoFrame) {
+    talk_base::CritScope cs(&stream_critical_);
+    // Send frame for rendering directly
+    if (running_ && renderer_) {
+      renderer_->RenderFrame(stream_id, videoFrame);
+    }
+    return 0;
+  }
+  int32_t SetRenderer(VideoRenderCallback* renderer) {
+    talk_base::CritScope cs(&stream_critical_);
+    renderer_ = renderer;
+    return 0;
+  }
+
+  int32_t StartRender() {
+    talk_base::CritScope cs(&stream_critical_);
+    running_ = true;
+    return 0;
+  }
+
+  int32_t StopRender() {
+    talk_base::CritScope cs(&stream_critical_);
+    running_ = false;
+    return 0;
+  }
+
+ private:
+  uint32_t stream_id_;
+  VideoRenderCallback* renderer_;
+  talk_base::CriticalSection stream_critical_;
+  bool running_;
+};
+
+WebRtcPassthroughRender::WebRtcPassthroughRender()
+    : window_(NULL) {
+}
+
+WebRtcPassthroughRender::~WebRtcPassthroughRender() {
+  while (!stream_render_map_.empty()) {
+    PassthroughStream* stream = stream_render_map_.begin()->second;
+    stream_render_map_.erase(stream_render_map_.begin());
+    delete stream;
+  }
+}
+
+webrtc::VideoRenderCallback* WebRtcPassthroughRender::AddIncomingRenderStream(
+    const uint32_t stream_id,
+    const uint32_t zOrder,
+    const float left, const float top,
+    const float right, const float bottom) {
+  talk_base::CritScope cs(&render_critical_);
+  // Stream already exist.
+  if (FindStream(stream_id) != NULL) {
+    LOG(LS_ERROR) << "AddIncomingRenderStream - Stream already exists: "
+                  << stream_id;
+    return NULL;
+  }
+
+  PassthroughStream* stream = new PassthroughStream(stream_id);
+  // Store the stream
+  stream_render_map_[stream_id] = stream;
+  return stream;
+}
+
+int32_t WebRtcPassthroughRender::DeleteIncomingRenderStream(
+    const uint32_t stream_id) {
+  talk_base::CritScope cs(&render_critical_);
+  PassthroughStream* stream = FindStream(stream_id);
+  if (stream == NULL) {
+    LOG_FIND_STREAM_ERROR("DeleteIncomingRenderStream", stream_id);
+    return -1;
+  }
+  delete stream;
+  stream_render_map_.erase(stream_id);
+  return 0;
+}
+
+int32_t WebRtcPassthroughRender::AddExternalRenderCallback(
+    const uint32_t stream_id,
+    webrtc::VideoRenderCallback* render_object) {
+  talk_base::CritScope cs(&render_critical_);
+  PassthroughStream* stream = FindStream(stream_id);
+  if (stream == NULL) {
+    LOG_FIND_STREAM_ERROR("AddExternalRenderCallback", stream_id);
+    return -1;
+  }
+  return stream->SetRenderer(render_object);
+}
+
+bool WebRtcPassthroughRender::HasIncomingRenderStream(
+    const uint32_t stream_id) const {
+  return (FindStream(stream_id) != NULL);
+}
+
+webrtc::RawVideoType WebRtcPassthroughRender::PreferredVideoType() const {
+  return webrtc::kVideoI420;
+}
+
+int32_t WebRtcPassthroughRender::StartRender(const uint32_t stream_id) {
+  talk_base::CritScope cs(&render_critical_);
+  PassthroughStream* stream = FindStream(stream_id);
+  if (stream == NULL) {
+    LOG_FIND_STREAM_ERROR("StartRender", stream_id);
+    return -1;
+  }
+  return stream->StartRender();
+}
+
+int32_t WebRtcPassthroughRender::StopRender(const uint32_t stream_id) {
+  talk_base::CritScope cs(&render_critical_);
+  PassthroughStream* stream = FindStream(stream_id);
+  if (stream == NULL) {
+    LOG_FIND_STREAM_ERROR("StopRender", stream_id);
+    return -1;
+  }
+  return stream->StopRender();
+}
+
+// TODO(ronghuawu): Is it ok to return non-const pointer to PassthroughStream
+// from this const function FindStream.
+PassthroughStream* WebRtcPassthroughRender::FindStream(
+    const uint32_t stream_id) const {
+  StreamMap::const_iterator it = stream_render_map_.find(stream_id);
+  if (it == stream_render_map_.end()) {
+    return NULL;
+  }
+  return it->second;
+}
+
+}  // namespace cricket
diff --git a/talk/media/webrtc/webrtcpassthroughrender.h b/talk/media/webrtc/webrtcpassthroughrender.h
new file mode 100644
index 0000000..967a29b
--- /dev/null
+++ b/talk/media/webrtc/webrtcpassthroughrender.h
@@ -0,0 +1,211 @@
+/*
+ * 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_MEDIA_WEBRTCPASSTHROUGHRENDER_H_
+#define TALK_MEDIA_WEBRTCPASSTHROUGHRENDER_H_
+
+#include <map>
+
+#include "talk/base/criticalsection.h"
+#include "webrtc/modules/video_render/include/video_render.h"
+
+namespace cricket {
+class PassthroughStream;
+
+class WebRtcPassthroughRender : public webrtc::VideoRender {
+ public:
+  WebRtcPassthroughRender();
+  virtual ~WebRtcPassthroughRender();
+
+  virtual int32_t Version(int8_t* version,
+                          uint32_t& remainingBufferInBytes,
+                          uint32_t& position) const {
+    return 0;
+  }
+
+  virtual int32_t ChangeUniqueId(const int32_t id) {
+    return 0;
+  }
+
+  virtual int32_t TimeUntilNextProcess() { return 0; }
+
+  virtual int32_t Process() { return 0; }
+
+  virtual void* Window() {
+    talk_base::CritScope cs(&render_critical_);
+    return window_;
+  }
+
+  virtual int32_t ChangeWindow(void* window) {
+    talk_base::CritScope cs(&render_critical_);
+    window_ = window;
+    return 0;
+  }
+
+  virtual webrtc::VideoRenderCallback* AddIncomingRenderStream(
+      const uint32_t stream_id,
+      const uint32_t zOrder,
+      const float left, const float top,
+      const float right, const float bottom);
+
+  virtual int32_t DeleteIncomingRenderStream(const uint32_t stream_id);
+
+  virtual int32_t AddExternalRenderCallback(
+      const uint32_t stream_id,
+      webrtc::VideoRenderCallback* render_object);
+
+  virtual int32_t GetIncomingRenderStreamProperties(
+      const uint32_t stream_id,
+      uint32_t& zOrder,
+      float& left, float& top,
+      float& right, float& bottom) const {
+    return -1;
+  }
+
+  virtual uint32_t GetIncomingFrameRate(
+      const uint32_t stream_id) {
+    return 0;
+  }
+
+  virtual uint32_t GetNumIncomingRenderStreams() const {
+    return stream_render_map_.size();
+  }
+
+  virtual bool HasIncomingRenderStream(const uint32_t stream_id) const;
+
+  virtual int32_t RegisterRawFrameCallback(
+      const uint32_t stream_id,
+      webrtc::VideoRenderCallback* callback_obj) {
+    return -1;
+  }
+
+  virtual int32_t GetLastRenderedFrame(
+      const uint32_t stream_id,
+      webrtc::I420VideoFrame &frame) const {
+    return -1;
+  }
+
+  virtual int32_t StartRender(
+      const uint32_t stream_id);
+
+  virtual int32_t StopRender(
+      const uint32_t stream_id);
+
+  virtual int32_t ResetRender() { return 0; }
+
+  virtual webrtc::RawVideoType PreferredVideoType() const;
+
+  virtual bool IsFullScreen() { return false; }
+
+  virtual int32_t GetScreenResolution(uint32_t& screenWidth,
+                                      uint32_t& screenHeight) const {
+    return -1;
+  }
+
+  virtual uint32_t RenderFrameRate(
+      const uint32_t stream_id) {
+    return 0;
+  }
+
+  virtual int32_t SetStreamCropping(
+      const uint32_t stream_id,
+      const float left, const float top,
+      const float right,
+      const float bottom) {
+    return -1;
+  }
+
+  virtual int32_t SetExpectedRenderDelay(uint32_t stream_id, int32_t delay_ms) {
+    return -1;
+  }
+
+  virtual int32_t ConfigureRenderer(
+      const uint32_t stream_id,
+      const unsigned int zOrder,
+      const float left, const float top,
+      const float right,
+      const float bottom) {
+    return -1;
+  }
+
+  virtual int32_t SetTransparentBackground(const bool enable) {
+    return -1;
+  }
+
+  virtual int32_t FullScreenRender(void* window, const bool enable) {
+    return -1;
+  }
+
+  virtual int32_t SetBitmap(const void* bitMap,
+      const uint8_t pictureId, const void* colorKey,
+      const float left, const float top,
+      const float right, const float bottom) {
+    return -1;
+  }
+
+  virtual int32_t SetText(const uint8_t textId,
+      const uint8_t* text,
+      const int32_t textLength,
+      const uint32_t textColorRef,
+      const uint32_t backgroundColorRef,
+      const float left, const float top,
+      const float right, const float bottom) {
+    return -1;
+  }
+
+  virtual int32_t SetStartImage(
+      const uint32_t stream_id,
+      const webrtc::I420VideoFrame& videoFrame) {
+    return -1;
+  }
+
+  virtual int32_t SetTimeoutImage(
+      const uint32_t stream_id,
+      const webrtc::I420VideoFrame& videoFrame,
+      const uint32_t timeout) {
+    return -1;
+  }
+
+  virtual int32_t MirrorRenderStream(const int renderId,
+                                     const bool enable,
+                                     const bool mirrorXAxis,
+                                     const bool mirrorYAxis) {
+    return -1;
+  }
+
+ private:
+  typedef std::map<uint32_t, PassthroughStream*> StreamMap;
+
+  PassthroughStream* FindStream(const uint32_t stream_id) const;
+
+  void* window_;
+  StreamMap stream_render_map_;
+  talk_base::CriticalSection render_critical_;
+};
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_WEBRTCPASSTHROUGHRENDER_H_
diff --git a/talk/media/webrtc/webrtcpassthroughrender_unittest.cc b/talk/media/webrtc/webrtcpassthroughrender_unittest.cc
new file mode 100644
index 0000000..4eb2892
--- /dev/null
+++ b/talk/media/webrtc/webrtcpassthroughrender_unittest.cc
@@ -0,0 +1,147 @@
+// Copyright 2008 Google Inc.
+//
+// Author: Ronghua Wu (ronghuawu@google.com)
+
+#include <string>
+
+#include "talk/base/gunit.h"
+#include "talk/media/base/testutils.h"
+#include "talk/media/webrtc/webrtcpassthroughrender.h"
+
+class WebRtcPassthroughRenderTest : public testing::Test {
+ public:
+  class ExternalRenderer : public webrtc::VideoRenderCallback {
+   public:
+    ExternalRenderer() : frame_num_(0) {
+    }
+
+    virtual ~ExternalRenderer() {
+    }
+
+    virtual int32_t RenderFrame(const uint32_t stream_id,
+                                webrtc::I420VideoFrame& videoFrame) {
+      ++frame_num_;
+      LOG(INFO) << "RenderFrame stream_id: " << stream_id
+                << " frame_num: " << frame_num_;
+      return 0;
+    }
+
+    int frame_num() const {
+      return frame_num_;
+    }
+
+   private:
+    int frame_num_;
+  };
+
+  WebRtcPassthroughRenderTest()
+      : renderer_(new cricket::WebRtcPassthroughRender()) {
+  }
+
+  ~WebRtcPassthroughRenderTest() {
+  }
+
+  webrtc::VideoRenderCallback* AddIncomingRenderStream(int stream_id) {
+    return renderer_->AddIncomingRenderStream(stream_id, 0, 0, 0, 0, 0);
+  }
+
+  bool HasIncomingRenderStream(int stream_id) {
+    return renderer_->HasIncomingRenderStream(stream_id);
+  }
+
+  bool DeleteIncomingRenderStream(int stream_id) {
+    return (renderer_->DeleteIncomingRenderStream(stream_id) == 0);
+  }
+
+  bool AddExternalRenderCallback(int stream_id,
+                                 webrtc::VideoRenderCallback* renderer) {
+    return (renderer_->AddExternalRenderCallback(stream_id, renderer) == 0);
+  }
+
+  bool StartRender(int stream_id) {
+    return (renderer_->StartRender(stream_id) == 0);
+  }
+
+  bool StopRender(int stream_id) {
+    return (renderer_->StopRender(stream_id) == 0);
+  }
+
+ private:
+  talk_base::scoped_ptr<cricket::WebRtcPassthroughRender> renderer_;
+};
+
+TEST_F(WebRtcPassthroughRenderTest, Streams) {
+  const int stream_id1 = 1234;
+  const int stream_id2 = 5678;
+  const int stream_id3 = 9012;  // A stream that doesn't exist.
+  webrtc::VideoRenderCallback* stream = NULL;
+  // Add a new stream
+  stream = AddIncomingRenderStream(stream_id1);
+  EXPECT_TRUE(stream != NULL);
+  EXPECT_TRUE(HasIncomingRenderStream(stream_id1));
+  // Tried to add a already existed stream should return null
+  stream =AddIncomingRenderStream(stream_id1);
+  EXPECT_TRUE(stream == NULL);
+  stream = AddIncomingRenderStream(stream_id2);
+  EXPECT_TRUE(stream != NULL);
+  EXPECT_TRUE(HasIncomingRenderStream(stream_id2));
+  // Remove the stream
+  EXPECT_FALSE(DeleteIncomingRenderStream(stream_id3));
+  EXPECT_TRUE(DeleteIncomingRenderStream(stream_id2));
+  EXPECT_TRUE(!HasIncomingRenderStream(stream_id2));
+  // Add back the removed stream
+  stream = AddIncomingRenderStream(stream_id2);
+  EXPECT_TRUE(stream != NULL);
+  EXPECT_TRUE(HasIncomingRenderStream(stream_id2));
+}
+
+TEST_F(WebRtcPassthroughRenderTest, Renderer) {
+  webrtc::I420VideoFrame frame;
+  const int stream_id1 = 1234;
+  const int stream_id2 = 5678;
+  const int stream_id3 = 9012;  // A stream that doesn't exist.
+  webrtc::VideoRenderCallback* stream1 = NULL;
+  webrtc::VideoRenderCallback* stream2 = NULL;
+  // Add two new stream
+  stream1 = AddIncomingRenderStream(stream_id1);
+  EXPECT_TRUE(stream1 != NULL);
+  EXPECT_TRUE(HasIncomingRenderStream(stream_id1));
+  stream2 = AddIncomingRenderStream(stream_id2);
+  EXPECT_TRUE(stream2 != NULL);
+  EXPECT_TRUE(HasIncomingRenderStream(stream_id2));
+  // Register the external renderer
+  WebRtcPassthroughRenderTest::ExternalRenderer renderer1;
+  WebRtcPassthroughRenderTest::ExternalRenderer renderer2;
+  EXPECT_FALSE(AddExternalRenderCallback(stream_id3, &renderer1));
+  EXPECT_TRUE(AddExternalRenderCallback(stream_id1, &renderer1));
+  EXPECT_TRUE(AddExternalRenderCallback(stream_id2, &renderer2));
+  int test_frame_num = 10;
+  // RenderFrame without starting the render
+  for (int i = 0; i < test_frame_num; ++i) {
+    stream1->RenderFrame(stream_id1, frame);
+  }
+  EXPECT_EQ(0, renderer1.frame_num());
+  // Start the render and test again.
+  EXPECT_FALSE(StartRender(stream_id3));
+  EXPECT_TRUE(StartRender(stream_id1));
+  for (int i = 0; i < test_frame_num; ++i) {
+    stream1->RenderFrame(stream_id1, frame);
+  }
+  EXPECT_EQ(test_frame_num, renderer1.frame_num());
+  // Stop the render and test again.
+  EXPECT_FALSE(StopRender(stream_id3));
+  EXPECT_TRUE(StopRender(stream_id1));
+  for (int i = 0; i < test_frame_num; ++i) {
+    stream1->RenderFrame(stream_id1, frame);
+  }
+  // The frame number should not have changed.
+  EXPECT_EQ(test_frame_num, renderer1.frame_num());
+
+  // Test on stream2 with a differnt number.
+  EXPECT_TRUE(StartRender(stream_id2));
+  test_frame_num = 30;
+  for (int i = 0; i < test_frame_num; ++i) {
+    stream2->RenderFrame(stream_id2, frame);
+  }
+  EXPECT_EQ(test_frame_num, renderer2.frame_num());
+}
diff --git a/talk/media/webrtc/webrtcvideocapturer.cc b/talk/media/webrtc/webrtcvideocapturer.cc
new file mode 100644
index 0000000..bcfda4e
--- /dev/null
+++ b/talk/media/webrtc/webrtcvideocapturer.cc
@@ -0,0 +1,366 @@
+// 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.
+//
+// Implementation of class WebRtcVideoCapturer.
+
+#include "talk/media/webrtc/webrtcvideocapturer.h"
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_WEBRTC_VIDEO
+#include "talk/base/logging.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+#include "talk/media/webrtc/webrtcvideoframe.h"
+
+#include "talk/base/win32.h"  // Need this to #include the impl files.
+#include "webrtc/modules/video_capture/include/video_capture_factory.h"
+
+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 },  // 24 bpp, slow conversion.
+};
+
+class WebRtcVcmFactory : public WebRtcVcmFactoryInterface {
+ public:
+  virtual webrtc::VideoCaptureModule* Create(int id, const char* 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(juberti): Figure what's needed to allow this.
+  int num_cams = info->NumberOfDevices();
+  char vcm_id[256] = "";
+  bool found = false;
+  for (int index = 0; index < num_cams; ++index) {
+    char 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(juberti): Find out why this starts/stops the camera...
+  std::vector<VideoFormat> supported;
+  int32_t num_caps = info->NumberOfCapabilities(vcm_id);
+  for (int32_t 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(juberti): 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)) {
+    // 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;
+}
+
+CaptureState WebRtcVideoCapturer::Start(const VideoFormat& capture_format) {
+  if (!module_) {
+    LOG(LS_ERROR) << "The capturer has not been initialized";
+    return CS_NO_DEVICE;
+  }
+
+  // TODO(hellner): weird to return failure when it is in fact actually running.
+  if (IsRunning()) {
+    LOG(LS_ERROR) << "The capturer is already running";
+    return CS_FAILED;
+  }
+
+  SetCaptureFormat(&capture_format);
+
+  webrtc::VideoCaptureCapability cap;
+  if (!FormatToCapability(capture_format, &cap)) {
+    LOG(LS_ERROR) << "Invalid capture format specified";
+    return CS_FAILED;
+  }
+
+  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 CS_FAILED;
+  }
+
+  LOG(LS_INFO) << "Camera '" << camera_id << "' started with format "
+               << capture_format.ToString() << ", elapsed time "
+               << talk_base::TimeSince(start) << " ms";
+
+  captured_frames_ = 0;
+  SetCaptureState(CS_RUNNING);
+  return CS_STARTING;
+}
+
+void WebRtcVideoCapturer::Stop() {
+  if (IsRunning()) {
+    talk_base::Thread::Current()->Clear(this);
+    module_->StopCapture();
+    module_->DeRegisterCaptureDataCallback();
+
+    // TODO(juberti): 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::OnIncomingCapturedFrame(const int32_t id,
+    webrtc::I420VideoFrame& sample) {
+  ASSERT(IsRunning());
+
+  ++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.
+  // The CapturedFrame class doesn't support planes. We have to ExtractBuffer
+  // to one block for it.
+  int length = webrtc::CalcBufferSize(webrtc::kI420,
+                                      sample.width(), sample.height());
+  if (!captured_frame_.get() ||
+      captured_frame_->length() != static_cast<size_t>(length)) {
+    captured_frame_.reset(new FrameBuffer(length));
+  }
+  // TODO(ronghuawu): Refactor the WebRtcVideoFrame to avoid memory copy.
+  webrtc::ExtractBuffer(sample, length,
+                        reinterpret_cast<uint8_t*>(captured_frame_->data()));
+  WebRtcCapturedFrame frame(sample, captured_frame_->data(), length);
+  SignalFrameCaptured(this, &frame);
+}
+
+void WebRtcVideoCapturer::OnCaptureDelayChanged(const int32_t id,
+                                                const int32_t delay) {
+  LOG(LS_INFO) << "Capture delay changed to " << delay << " ms";
+}
+
+// WebRtcCapturedFrame
+WebRtcCapturedFrame::WebRtcCapturedFrame(const webrtc::I420VideoFrame& sample,
+                                         void* buffer,
+                                         int length) {
+  width = sample.width();
+  height = sample.height();
+  fourcc = FOURCC_I420;
+  // TODO(hellner): Support pixel aspect ratio (for OSX).
+  pixel_width = 1;
+  pixel_height = 1;
+  // Convert units from VideoFrame RenderTimeMs to CapturedFrame (nanoseconds).
+  elapsed_time = sample.render_time_ms() * talk_base::kNumNanosecsPerMillisec;
+  time_stamp = elapsed_time;
+  data_size = length;
+  data = buffer;
+}
+
+}  // namespace cricket
+
+#endif  // HAVE_WEBRTC_VIDEO
diff --git a/talk/media/webrtc/webrtcvideocapturer.h b/talk/media/webrtc/webrtcvideocapturer.h
new file mode 100644
index 0000000..eb99956
--- /dev/null
+++ b/talk/media/webrtc/webrtcvideocapturer.h
@@ -0,0 +1,103 @@
+// 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_MEDIA_WEBRTCVIDEOCAPTURER_H_
+#define TALK_MEDIA_WEBRTCVIDEOCAPTURER_H_
+
+#ifdef HAVE_WEBRTC_VIDEO
+
+#include <string>
+#include <vector>
+
+#include "talk/base/messagehandler.h"
+#include "talk/media/base/videocapturer.h"
+#include "talk/media/webrtc/webrtcvideoframe.h"
+#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
+#include "webrtc/modules/video_capture/include/video_capture.h"
+
+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 char* 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 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 CaptureState Start(const VideoFormat& capture_format);
+  virtual void Stop();
+  virtual bool IsRunning();
+  virtual bool IsScreencast() const { return false; }
+
+ protected:
+  // Override virtual methods of the parent class VideoCapturer.
+  virtual bool GetPreferredFourccs(std::vector<uint32>* fourccs);
+
+ private:
+  // Callback when a frame is captured by camera.
+  virtual void OnIncomingCapturedFrame(const int32_t id,
+                                       webrtc::I420VideoFrame& frame);
+  virtual void OnIncomingCapturedEncodedFrame(const int32_t id,
+      webrtc::VideoFrame& frame,
+      webrtc::VideoCodecType codec_type) {
+  }
+  virtual void OnCaptureDelayChanged(const int32_t id,
+                                     const int32_t delay);
+
+  talk_base::scoped_ptr<WebRtcVcmFactoryInterface> factory_;
+  webrtc::VideoCaptureModule* module_;
+  int captured_frames_;
+  talk_base::scoped_ptr<FrameBuffer> captured_frame_;
+};
+
+struct WebRtcCapturedFrame : public CapturedFrame {
+ public:
+  WebRtcCapturedFrame(const webrtc::I420VideoFrame& frame,
+                      void* buffer, int length);
+};
+
+}  // namespace cricket
+
+#endif  // HAVE_WEBRTC_VIDEO
+#endif  // TALK_MEDIA_WEBRTCVIDEOCAPTURER_H_
diff --git a/talk/media/webrtc/webrtcvideocapturer_unittest.cc b/talk/media/webrtc/webrtcvideocapturer_unittest.cc
new file mode 100644
index 0000000..226aa4b
--- /dev/null
+++ b/talk/media/webrtc/webrtcvideocapturer_unittest.cc
@@ -0,0 +1,145 @@
+// 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 <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/media/base/testutils.h"
+#include "talk/media/base/videocommon.h"
+#include "talk/media/webrtc/fakewebrtcvcmfactory.h"
+#include "talk/media/webrtc/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_TRUE(capturer_->GetSupportedFormats()->empty());
+  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 char*>(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::CS_STARTING, capturer_->Start(format));
+  EXPECT_TRUE(capturer_->IsRunning());
+  ASSERT_TRUE(capturer_->GetCaptureFormat() != NULL);
+  EXPECT_EQ(format, *capturer_->GetCaptureFormat());
+  EXPECT_EQ_WAIT(cricket::CS_RUNNING, listener_.last_capture_state(), 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::CS_FAILED, 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 char*>(kTestDeviceId.c_str()))));
+  EXPECT_TRUE(capturer_->GetSupportedFormats()->empty());
+  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::CS_STARTING, capturer_->Start(format));
+  EXPECT_TRUE(capturer_->IsRunning());
+  ASSERT_TRUE(capturer_->GetCaptureFormat() != NULL);
+  EXPECT_EQ(format, *capturer_->GetCaptureFormat());
+  EXPECT_EQ_WAIT(cricket::CS_RUNNING, listener_.last_capture_state(), 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::CS_FAILED, capturer_->Start(format));
+  capturer_->Stop();
+  EXPECT_FALSE(capturer_->IsRunning());
+  EXPECT_TRUE(capturer_->GetCaptureFormat() == NULL);
+}
+
+TEST_F(WebRtcVideoCapturerTest, TestCaptureWithoutInit) {
+  cricket::VideoFormat format;
+  EXPECT_EQ(cricket::CS_NO_DEVICE, capturer_->Start(format));
+  EXPECT_TRUE(capturer_->GetCaptureFormat() == NULL);
+  EXPECT_FALSE(capturer_->IsRunning());
+}
diff --git a/talk/media/webrtc/webrtcvideodecoderfactory.h b/talk/media/webrtc/webrtcvideodecoderfactory.h
new file mode 100644
index 0000000..483bca7
--- /dev/null
+++ b/talk/media/webrtc/webrtcvideodecoderfactory.h
@@ -0,0 +1,53 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_MEDIA_WEBRTC_WEBRTCVIDEODECODERFACTORY_H_
+#define TALK_MEDIA_WEBRTC_WEBRTCVIDEODECODERFACTORY_H_
+
+#include "talk/base/refcount.h"
+#include "webrtc/common_types.h"
+
+namespace webrtc {
+class VideoDecoder;
+}
+
+namespace cricket {
+
+class WebRtcVideoDecoderFactory {
+ public:
+  // Caller takes the ownership of the returned object and it should be released
+  // by calling DestroyVideoDecoder().
+  virtual webrtc::VideoDecoder* CreateVideoDecoder(
+      webrtc::VideoCodecType type) = 0;
+  virtual ~WebRtcVideoDecoderFactory() {}
+
+  virtual void DestroyVideoDecoder(webrtc::VideoDecoder* decoder) = 0;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_WEBRTC_WEBRTCVIDEODECODERFACTORY_H_
diff --git a/talk/media/webrtc/webrtcvideoencoderfactory.h b/talk/media/webrtc/webrtcvideoencoderfactory.h
new file mode 100644
index 0000000..a844309
--- /dev/null
+++ b/talk/media/webrtc/webrtcvideoencoderfactory.h
@@ -0,0 +1,89 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_MEDIA_WEBRTC_WEBRTCVIDEOENCODERFACTORY_H_
+#define TALK_MEDIA_WEBRTC_WEBRTCVIDEOENCODERFACTORY_H_
+
+#include "talk/base/refcount.h"
+#include "talk/media/base/codec.h"
+#include "webrtc/common_types.h"
+
+namespace webrtc {
+class VideoEncoder;
+}
+
+namespace cricket {
+
+class WebRtcVideoEncoderFactory {
+ public:
+  struct VideoCodec {
+    webrtc::VideoCodecType type;
+    std::string name;
+    int max_width;
+    int max_height;
+    int max_fps;
+
+    VideoCodec(webrtc::VideoCodecType t, const std::string& nm, int w, int h,
+               int fr)
+        : type(t), name(nm), max_width(w), max_height(h), max_fps(fr) {
+    }
+  };
+
+  class Observer {
+   public:
+    // Invoked when the list of supported codecs becomes available.
+    // This will not be invoked if the list of codecs is already available when
+    // the factory is installed. Otherwise this will be invoked only once if the
+    // list of codecs is not yet available when the factory is installed.
+    virtual void OnCodecsAvailable() = 0;
+
+   protected:
+    virtual ~Observer() {}
+  };
+
+  // Caller takes the ownership of the returned object and it should be released
+  // by calling DestroyVideoEncoder().
+  virtual webrtc::VideoEncoder* CreateVideoEncoder(
+      webrtc::VideoCodecType type) = 0;
+  virtual ~WebRtcVideoEncoderFactory() {}
+
+  // Adds/removes observer to receive OnCodecsChanged notifications.
+  // Factory must outlive Observer. Observer is responsible for removing itself
+  // from the Factory by the time its dtor is done.
+  virtual void AddObserver(Observer* observer) = 0;
+  virtual void RemoveObserver(Observer* observer) = 0;
+
+  // Returns a list of supported codecs in order of preference.
+  // The list is empty if the list of codecs is not yet available.
+  virtual const std::vector<VideoCodec>& codecs() const = 0;
+
+  virtual void DestroyVideoEncoder(webrtc::VideoEncoder* encoder) = 0;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_WEBRTC_WEBRTCVIDEOENCODERFACTORY_H_
diff --git a/talk/media/webrtc/webrtcvideoengine.cc b/talk/media/webrtc/webrtcvideoengine.cc
new file mode 100644
index 0000000..5047974
--- /dev/null
+++ b/talk/media/webrtc/webrtcvideoengine.cc
@@ -0,0 +1,3626 @@
+/*
+ * 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.
+ */
+
+#ifdef HAVE_WEBRTC_VIDEO
+#include "talk/media/webrtc/webrtcvideoengine.h"
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <math.h>
+#include <set>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/buffer.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/cpumonitor.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+#include "talk/media/base/constants.h"
+#include "talk/media/base/rtputils.h"
+#include "talk/media/base/streamparams.h"
+#include "talk/media/base/videoadapter.h"
+#include "talk/media/base/videocapturer.h"
+#include "talk/media/base/videorenderer.h"
+#include "talk/media/devices/filevideocapturer.h"
+#include "talk/media/webrtc/webrtcvideodecoderfactory.h"
+#include "talk/media/webrtc/webrtcvideoencoderfactory.h"
+#include "talk/media/webrtc/webrtcpassthroughrender.h"
+#include "talk/media/webrtc/webrtcvideocapturer.h"
+#include "talk/media/webrtc/webrtcvideoframe.h"
+#include "talk/media/webrtc/webrtcvie.h"
+#include "talk/media/webrtc/webrtcvoe.h"
+#include "talk/media/webrtc/webrtcvoiceengine.h"
+
+#if !defined(LIBPEERCONNECTION_LIB)
+#ifndef HAVE_WEBRTC_VIDEO
+#error Need webrtc video
+#endif
+#include "talk/media/webrtc/webrtcmediaengine.h"
+
+WRME_EXPORT
+cricket::MediaEngineInterface* CreateWebRtcMediaEngine(
+    webrtc::AudioDeviceModule* adm, webrtc::AudioDeviceModule* adm_sc,
+    cricket::WebRtcVideoEncoderFactory* encoder_factory,
+    cricket::WebRtcVideoDecoderFactory* decoder_factory) {
+  return new cricket::WebRtcMediaEngine(adm, adm_sc, encoder_factory,
+                                        decoder_factory);
+}
+
+WRME_EXPORT
+void DestroyWebRtcMediaEngine(cricket::MediaEngineInterface* media_engine) {
+  delete static_cast<cricket::WebRtcMediaEngine*>(media_engine);
+}
+#endif
+
+
+namespace cricket {
+
+
+static const int kDefaultLogSeverity = talk_base::LS_WARNING;
+
+static const int kMinVideoBitrate = 50;
+static const int kStartVideoBitrate = 300;
+static const int kMaxVideoBitrate = 2000;
+static const int kDefaultConferenceModeMaxVideoBitrate = 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 = 1;  // 1:1
+
+static const int kTimestampDeltaInSecondsForWarning = 2;
+
+static const int kMaxExternalVideoCodecs = 8;
+static const int kExternalVideoPayloadTypeBase = 120;
+
+// Static allocation of payload type values for external video codec.
+static int GetExternalVideoPayloadType(int index) {
+  ASSERT(index >= 0 && index < kMaxExternalVideoCodecs);
+  return kExternalVideoPayloadTypeBase + index;
+}
+
+static void LogMultiline(talk_base::LoggingSeverity sev, char* text) {
+  const char* delim = "\r\n";
+  // TODO(fbarchard): Fix strtok lint warning.
+  for (char* tok = strtok(text, delim); tok; tok = strtok(NULL, delim)) {
+    LOG_V(sev) << tok;
+  }
+}
+
+// Severity is an integer because it comes is assumed to be from command line.
+static int SeverityToFilter(int severity) {
+  int filter = webrtc::kTraceNone;
+  switch (severity) {
+    case talk_base::LS_VERBOSE:
+      filter |= webrtc::kTraceAll;
+    case talk_base::LS_INFO:
+      filter |= (webrtc::kTraceStateInfo | webrtc::kTraceInfo);
+    case talk_base::LS_WARNING:
+      filter |= (webrtc::kTraceTerseInfo | webrtc::kTraceWarning);
+    case talk_base::LS_ERROR:
+      filter |= (webrtc::kTraceError | webrtc::kTraceCritical);
+  }
+  return filter;
+}
+
+static const int kCpuMonitorPeriodMs = 2000;  // 2 seconds.
+
+static const bool kNotSending = false;
+
+// Extension header for RTP timestamp offset, see RFC 5450 for details:
+// http://tools.ietf.org/html/rfc5450
+static const char kRtpTimestampOffsetHeaderExtension[] =
+    "urn:ietf:params:rtp-hdrext:toffset";
+static const int kRtpTimeOffsetExtensionId = 2;
+
+// Extension header for absolute send time, see url for details:
+// http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
+static const char kRtpAbsoluteSendTimeHeaderExtension[] =
+    "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time";
+static const int kRtpAbsoluteSendTimeExtensionId = 3;
+
+static bool IsNackEnabled(const VideoCodec& codec) {
+  return codec.HasFeedbackParam(FeedbackParam(kRtcpFbParamNack,
+                                              kParamValueEmpty));
+}
+
+// Returns true if Receiver Estimated Max Bitrate is enabled.
+static bool IsRembEnabled(const VideoCodec& codec) {
+  return codec.HasFeedbackParam(FeedbackParam(kRtcpFbParamRemb,
+                                              kParamValueEmpty));
+}
+
+struct FlushBlackFrameData : public talk_base::MessageData {
+  FlushBlackFrameData(uint32 s, int64 t) : ssrc(s), timestamp(t) {
+  }
+  uint32 ssrc;
+  int64 timestamp;
+};
+
+class WebRtcRenderAdapter : public webrtc::ExternalRenderer {
+ public:
+  explicit WebRtcRenderAdapter(VideoRenderer* renderer)
+      : renderer_(renderer), width_(0), height_(0), watermark_enabled_(false) {
+  }
+  virtual ~WebRtcRenderAdapter() {
+  }
+  void set_watermark_enabled(bool enable) {
+    talk_base::CritScope cs(&crit_);
+    watermark_enabled_ = enable;
+  }
+  void SetRenderer(VideoRenderer* renderer) {
+    talk_base::CritScope cs(&crit_);
+    renderer_ = renderer;
+    // FrameSizeChange may have already been called when renderer was not set.
+    // If so we should call SetSize here.
+    // TODO(ronghuawu): Add unit test for this case. Didn't do it now
+    // because the WebRtcRenderAdapter is currently hiding in cc file. No
+    // good way to get access to it from the unit test.
+    if (width_ > 0 && height_ > 0 && renderer_ != NULL) {
+      if (!renderer_->SetSize(width_, height_, 0)) {
+        LOG(LS_ERROR)
+            << "WebRtcRenderAdapter SetRenderer failed to SetSize to: "
+            << width_ << "x" << height_;
+      }
+    }
+  }
+  // Implementation of webrtc::ExternalRenderer.
+  virtual int FrameSizeChange(unsigned int width, unsigned int height,
+                              unsigned int /*number_of_streams*/) {
+    talk_base::CritScope cs(&crit_);
+    width_ = width;
+    height_ = height;
+    LOG(LS_INFO) << "WebRtcRenderAdapter frame size changed to: "
+                 << width << "x" << height;
+    if (renderer_ == NULL) {
+      LOG(LS_VERBOSE) << "WebRtcRenderAdapter the renderer has not been set. "
+                      << "SetSize will be called later in SetRenderer.";
+      return 0;
+    }
+    return renderer_->SetSize(width_, height_, 0) ? 0 : -1;
+  }
+  virtual int DeliverFrame(unsigned char* buffer, int buffer_size,
+                           uint32_t time_stamp, int64_t render_time) {
+    talk_base::CritScope cs(&crit_);
+    frame_rate_tracker_.Update(1);
+    if (renderer_ == NULL) {
+      return 0;
+    }
+    WebRtcVideoFrame video_frame;
+    // Convert 90K rtp timestamp to ns timestamp.
+    int64 rtp_time_stamp_in_ns = (time_stamp / 90) *
+        talk_base::kNumNanosecsPerMillisec;
+    // Convert milisecond render time to ns timestamp.
+    int64 render_time_stamp_in_ns = render_time *
+        talk_base::kNumNanosecsPerMillisec;
+    // Send the rtp timestamp to renderer as the VideoFrame timestamp.
+    // and the render timestamp as the VideoFrame elapsed_time.
+    video_frame.Attach(buffer, buffer_size, width_, height_,
+                       1, 1, render_time_stamp_in_ns,
+                       rtp_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;
+    video_frame.Detach(&buffer_temp, &buffer_size_temp);
+    return ret;
+  }
+
+  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_;
+  bool watermark_enabled_;
+};
+
+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:
+  WebRtcLocalStreamInfo()
+      : width_(0), height_(0), elapsed_time_(-1), time_stamp_(-1) {}
+  size_t width() const {
+    talk_base::CritScope cs(&crit_);
+    return width_;
+  }
+  size_t height() const {
+    talk_base::CritScope cs(&crit_);
+    return height_;
+  }
+  int64 elapsed_time() const {
+    talk_base::CritScope cs(&crit_);
+    return elapsed_time_;
+  }
+  int64 time_stamp() const {
+    talk_base::CritScope cs(&crit_);
+    return time_stamp_;
+  }
+  int framerate() {
+    talk_base::CritScope cs(&crit_);
+    return rate_tracker_.units_second();
+  }
+  void GetLastFrameInfo(
+      size_t* width, size_t* height, int64* elapsed_time) const {
+    talk_base::CritScope cs(&crit_);
+    *width = width_;
+    *height = height_;
+    *elapsed_time = elapsed_time_;
+  }
+
+  void UpdateFrame(const VideoFrame* frame) {
+    talk_base::CritScope cs(&crit_);
+
+    width_ = frame->GetWidth();
+    height_ = frame->GetHeight();
+    elapsed_time_ = frame->GetElapsedTime();
+    time_stamp_ = frame->GetTimeStamp();
+
+    rate_tracker_.Update(1);
+  }
+
+ private:
+  mutable talk_base::CriticalSection crit_;
+  size_t width_;
+  size_t height_;
+  int64 elapsed_time_;
+  int64 time_stamp_;
+  talk_base::RateTracker rate_tracker_;
+
+  DISALLOW_COPY_AND_ASSIGN(WebRtcLocalStreamInfo);
+};
+
+// WebRtcVideoChannelRecvInfo 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 WebRtcVideoChannelRecvInfo  {
+ public:
+  typedef std::map<int, webrtc::VideoDecoder*> DecoderMap;  // key: payload type
+  explicit WebRtcVideoChannelRecvInfo(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_; }
+  void RegisterDecoder(int pl_type, webrtc::VideoDecoder* decoder) {
+    ASSERT(!IsDecoderRegistered(pl_type));
+    registered_decoders_[pl_type] = decoder;
+  }
+  bool IsDecoderRegistered(int pl_type) {
+    return registered_decoders_.count(pl_type) != 0;
+  }
+  const DecoderMap& registered_decoders() {
+    return registered_decoders_;
+  }
+  void ClearRegisteredDecoders() {
+    registered_decoders_.clear();
+  }
+
+ private:
+  int channel_id_;  // Webrtc video channel number.
+  // Renderer for this channel.
+  WebRtcRenderAdapter render_adapter_;
+  WebRtcDecoderObserver decoder_observer_;
+  DecoderMap registered_decoders_;
+};
+
+class WebRtcVideoChannelSendInfo  {
+ public:
+  typedef std::map<int, webrtc::VideoEncoder*> EncoderMap;  // key: payload type
+  WebRtcVideoChannelSendInfo(int channel_id, int capture_id,
+                             webrtc::ViEExternalCapture* external_capture,
+                             talk_base::CpuMonitor* cpu_monitor)
+      : channel_id_(channel_id),
+        capture_id_(capture_id),
+        sending_(false),
+        muted_(false),
+        video_capturer_(NULL),
+        encoder_observer_(channel_id),
+        external_capture_(external_capture),
+        capturer_updated_(false),
+        interval_(0),
+        video_adapter_(new CoordinatedVideoAdapter) {
+    // TODO(asapersson):
+    // video_adapter_->SignalCpuAdaptationUnable.connect(
+    //     this, &WebRtcVideoChannelSendInfo::OnCpuAdaptationUnable);
+    if (cpu_monitor) {
+      cpu_monitor->SignalUpdate.connect(
+          video_adapter_.get(), &CoordinatedVideoAdapter::OnCpuLoadUpdated);
+    }
+  }
+
+  int channel_id() const { return channel_id_; }
+  int capture_id() const { return capture_id_; }
+  void set_sending(bool sending) { sending_ = sending; }
+  bool sending() const { return sending_; }
+  void set_muted(bool on) {
+    // TODO(asapersson): add support.
+    // video_adapter_->SetBlackOutput(on);
+    muted_ = on;
+  }
+  bool muted() {return muted_; }
+
+  WebRtcEncoderObserver* encoder_observer() { return &encoder_observer_; }
+  webrtc::ViEExternalCapture* external_capture() { return external_capture_; }
+  const VideoFormat& video_format() const {
+    return video_format_;
+  }
+  void set_video_format(const VideoFormat& video_format) {
+    video_format_ = video_format;
+    if (video_format_ != cricket::VideoFormat()) {
+      interval_ = video_format_.interval;
+    }
+    video_adapter_->OnOutputFormatRequest(video_format_);
+  }
+  void set_interval(int64 interval) {
+    if (video_format() == cricket::VideoFormat()) {
+      interval_ = interval;
+    }
+  }
+  int64 interval() { return interval_; }
+
+  void InitializeAdapterOutputFormat(const webrtc::VideoCodec& codec) {
+    VideoFormat format(codec.width, codec.height,
+                       VideoFormat::FpsToInterval(codec.maxFramerate),
+                       FOURCC_I420);
+    if (video_adapter_->output_format().IsSize0x0()) {
+      video_adapter_->SetOutputFormat(format);
+    }
+  }
+
+  bool AdaptFrame(const VideoFrame* in_frame, const VideoFrame** out_frame) {
+    *out_frame = NULL;
+    return video_adapter_->AdaptFrame(in_frame, out_frame);
+  }
+  int CurrentAdaptReason() const {
+    return video_adapter_->adapt_reason();
+  }
+
+  StreamParams* stream_params() { return stream_params_.get(); }
+  void set_stream_params(const StreamParams& sp) {
+    stream_params_.reset(new StreamParams(sp));
+  }
+  void ClearStreamParams() { stream_params_.reset(); }
+  bool has_ssrc(uint32 local_ssrc) const {
+    return !stream_params_ ? false :
+        stream_params_->has_ssrc(local_ssrc);
+  }
+  WebRtcLocalStreamInfo* local_stream_info() {
+    return &local_stream_info_;
+  }
+  VideoCapturer* video_capturer() {
+    return video_capturer_;
+  }
+  void set_video_capturer(VideoCapturer* video_capturer) {
+    if (video_capturer == video_capturer_) {
+      return;
+    }
+    capturer_updated_ = true;
+    video_capturer_ = video_capturer;
+    if (video_capturer && !video_capturer->IsScreencast()) {
+      const VideoFormat* capture_format = video_capturer->GetCaptureFormat();
+      if (capture_format) {
+        video_adapter_->SetInputFormat(*capture_format);
+      }
+    }
+  }
+
+  void ApplyCpuOptions(const VideoOptions& options) {
+    bool cpu_adapt;
+    float low, med, high;
+    if (options.adapt_input_to_cpu_usage.Get(&cpu_adapt)) {
+      video_adapter_->set_cpu_adaptation(cpu_adapt);
+    }
+    if (options.process_adaptation_threshhold.Get(&med)) {
+      video_adapter_->set_process_threshold(med);
+    }
+    if (options.system_low_adaptation_threshhold.Get(&low)) {
+      video_adapter_->set_low_system_threshold(low);
+    }
+    if (options.system_high_adaptation_threshhold.Get(&high)) {
+      video_adapter_->set_high_system_threshold(high);
+    }
+  }
+  void ProcessFrame(const VideoFrame& original_frame, bool mute,
+                    VideoFrame** processed_frame) {
+    if (!mute) {
+      *processed_frame = original_frame.Copy();
+    } else {
+      WebRtcVideoFrame* black_frame = new WebRtcVideoFrame();
+      black_frame->InitToBlack(original_frame.GetWidth(),
+                               original_frame.GetHeight(), 1, 1,
+                               original_frame.GetElapsedTime(),
+                               original_frame.GetTimeStamp());
+      *processed_frame = black_frame;
+    }
+    local_stream_info_.UpdateFrame(*processed_frame);
+  }
+  void RegisterEncoder(int pl_type, webrtc::VideoEncoder* encoder) {
+    ASSERT(!IsEncoderRegistered(pl_type));
+    registered_encoders_[pl_type] = encoder;
+  }
+  bool IsEncoderRegistered(int pl_type) {
+    return registered_encoders_.count(pl_type) != 0;
+  }
+  const EncoderMap& registered_encoders() {
+    return registered_encoders_;
+  }
+  void ClearRegisteredEncoders() {
+    registered_encoders_.clear();
+  }
+
+ private:
+  int channel_id_;
+  int capture_id_;
+  bool sending_;
+  bool muted_;
+  VideoCapturer* video_capturer_;
+  WebRtcEncoderObserver encoder_observer_;
+  webrtc::ViEExternalCapture* external_capture_;
+  EncoderMap registered_encoders_;
+
+  VideoFormat video_format_;
+
+  talk_base::scoped_ptr<StreamParams> stream_params_;
+
+  WebRtcLocalStreamInfo local_stream_info_;
+
+  bool capturer_updated_;
+
+  int64 interval_;
+
+  talk_base::scoped_ptr<CoordinatedVideoAdapter> video_adapter_;
+};
+
+const WebRtcVideoEngine::VideoCodecPref
+    WebRtcVideoEngine::kVideoCodecPrefs[] = {
+    {kVp8PayloadName, 100, 0},
+    {kRedPayloadName, 116, 1},
+    {kFecPayloadName, 117, 2},
+};
+
+// 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 VideoFormatPod WebRtcVideoEngine::kVideoFormats[] = {
+  {1280, 800, FPS_TO_INTERVAL(30), FOURCC_ANY},
+  {1280, 720, FPS_TO_INTERVAL(30), FOURCC_ANY},
+  {960, 600, FPS_TO_INTERVAL(30), FOURCC_ANY},
+  {960, 540, FPS_TO_INTERVAL(30), FOURCC_ANY},
+  {640, 400, FPS_TO_INTERVAL(30), FOURCC_ANY},
+  {640, 360, FPS_TO_INTERVAL(30), FOURCC_ANY},
+  {640, 480, FPS_TO_INTERVAL(30), FOURCC_ANY},
+  {480, 300, FPS_TO_INTERVAL(30), FOURCC_ANY},
+  {480, 270, FPS_TO_INTERVAL(30), FOURCC_ANY},
+  {480, 360, FPS_TO_INTERVAL(30), FOURCC_ANY},
+  {320, 200, FPS_TO_INTERVAL(30), FOURCC_ANY},
+  {320, 180, FPS_TO_INTERVAL(30), FOURCC_ANY},
+  {320, 240, FPS_TO_INTERVAL(30), FOURCC_ANY},
+  {240, 150, FPS_TO_INTERVAL(30), FOURCC_ANY},
+  {240, 135, FPS_TO_INTERVAL(30), FOURCC_ANY},
+  {240, 180, FPS_TO_INTERVAL(30), FOURCC_ANY},
+  {160, 100, FPS_TO_INTERVAL(30), FOURCC_ANY},
+  {160, 90, FPS_TO_INTERVAL(30), FOURCC_ANY},
+  {160, 120, FPS_TO_INTERVAL(30), FOURCC_ANY},
+};
+
+const VideoFormatPod WebRtcVideoEngine::kDefaultVideoFormat =
+  {640, 400, FPS_TO_INTERVAL(30), FOURCC_ANY};
+
+static void UpdateVideoCodec(const cricket::VideoFormat& video_format,
+                             webrtc::VideoCodec* target_codec) {
+  if ((target_codec == NULL) || (video_format == cricket::VideoFormat())) {
+    return;
+  }
+  target_codec->width = video_format.width;
+  target_codec->height = video_format.height;
+  target_codec->maxFramerate = cricket::VideoFormat::IntervalToFps(
+      video_format.interval);
+}
+
+WebRtcVideoEngine::WebRtcVideoEngine() {
+  Construct(new ViEWrapper(), new ViETraceWrapper(), NULL,
+      new talk_base::CpuMonitor(NULL));
+}
+
+WebRtcVideoEngine::WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine,
+                                     ViEWrapper* vie_wrapper,
+                                     talk_base::CpuMonitor* cpu_monitor) {
+  Construct(vie_wrapper, new ViETraceWrapper(), voice_engine, cpu_monitor);
+}
+
+WebRtcVideoEngine::WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine,
+                                     ViEWrapper* vie_wrapper,
+                                     ViETraceWrapper* tracing,
+                                     talk_base::CpuMonitor* cpu_monitor) {
+  Construct(vie_wrapper, tracing, voice_engine, cpu_monitor);
+}
+
+void WebRtcVideoEngine::Construct(ViEWrapper* vie_wrapper,
+                                  ViETraceWrapper* tracing,
+                                  WebRtcVoiceEngine* voice_engine,
+                                  talk_base::CpuMonitor* cpu_monitor) {
+  LOG(LS_INFO) << "WebRtcVideoEngine::WebRtcVideoEngine";
+  worker_thread_ = NULL;
+  vie_wrapper_.reset(vie_wrapper);
+  vie_wrapper_base_initialized_ = false;
+  tracing_.reset(tracing);
+  voice_engine_ = voice_engine;
+  initialized_ = false;
+  SetTraceFilter(SeverityToFilter(kDefaultLogSeverity));
+  render_module_.reset(new WebRtcPassthroughRender());
+  local_renderer_w_ = local_renderer_h_ = 0;
+  local_renderer_ = NULL;
+  video_capturer_ = NULL;
+  frame_listeners_ = 0;
+  capture_started_ = false;
+  decoder_factory_ = NULL;
+  encoder_factory_ = NULL;
+  cpu_monitor_.reset(cpu_monitor);
+
+  SetTraceOptions("");
+  if (tracing_->SetTraceCallback(this) != 0) {
+    LOG_RTCERR1(SetTraceCallback, this);
+  }
+
+  // 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.
+  VideoCodec max_codec(kVideoCodecPrefs[0].payload_type,
+                       kVideoCodecPrefs[0].name,
+                       kDefaultVideoFormat.width,
+                       kDefaultVideoFormat.height,
+                       VideoFormat::IntervalToFps(kDefaultVideoFormat.interval),
+                       0);
+  if (!SetDefaultCodec(max_codec)) {
+    LOG(LS_ERROR) << "Failed to initialize list of supported codec types";
+  }
+
+
+  // Load our RTP Header extensions.
+  rtp_header_extensions_.push_back(
+      RtpHeaderExtension(kRtpTimestampOffsetHeaderExtension,
+                         kRtpTimeOffsetExtensionId));
+  rtp_header_extensions_.push_back(
+      RtpHeaderExtension(kRtpAbsoluteSendTimeHeaderExtension,
+                         kRtpAbsoluteSendTimeExtensionId));
+}
+
+WebRtcVideoEngine::~WebRtcVideoEngine() {
+  ClearCapturer();
+  LOG(LS_INFO) << "WebRtcVideoEngine::~WebRtcVideoEngine";
+  if (initialized_) {
+    Terminate();
+  }
+  if (encoder_factory_) {
+    encoder_factory_->RemoveObserver(this);
+  }
+  tracing_->SetTraceCallback(NULL);
+  // Test to see if the media processor was deregistered properly.
+  ASSERT(SignalMediaFrame.is_empty());
+}
+
+bool WebRtcVideoEngine::Init(talk_base::Thread* worker_thread) {
+  LOG(LS_INFO) << "WebRtcVideoEngine::Init";
+  worker_thread_ = worker_thread;
+  ASSERT(worker_thread_ != NULL);
+
+  cpu_monitor_->set_thread(worker_thread_);
+  if (!cpu_monitor_->Start(kCpuMonitorPeriodMs)) {
+    LOG(LS_ERROR) << "Failed to start CPU monitor.";
+    cpu_monitor_.reset();
+  }
+
+  bool result = InitVideoEngine();
+  if (result) {
+    LOG(LS_INFO) << "VideoEngine Init done";
+  } else {
+    LOG(LS_ERROR) << "VideoEngine Init failed, releasing";
+    Terminate();
+  }
+  return result;
+}
+
+bool WebRtcVideoEngine::InitVideoEngine() {
+  LOG(LS_INFO) << "WebRtcVideoEngine::InitVideoEngine";
+
+  // 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(
+      voice_engine_->voe()->engine())) != 0) {
+    LOG_RTCERR0(SetVoiceEngine);
+    return false;
+  }
+
+  // Register our custom render module.
+  if (vie_wrapper_->render()->RegisterVideoRenderModule(
+      *render_module_.get()) != 0) {
+    LOG_RTCERR0(RegisterVideoRenderModule);
+    return false;
+  }
+
+  initialized_ = true;
+  return true;
+}
+
+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()->SetVoiceEngine(NULL) != 0) {
+    LOG_RTCERR0(SetVoiceEngine);
+  }
+
+  cpu_monitor_->Stop();
+}
+
+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::SetVideoCapturer(VideoCapturer* capturer) {
+  return SetCapturer(capturer);
+}
+
+VideoCapturer* WebRtcVideoEngine::GetVideoCapturer() const {
+  return video_capturer_;
+}
+
+bool WebRtcVideoEngine::SetLocalRenderer(VideoRenderer* renderer) {
+  local_renderer_w_ = local_renderer_h_ = 0;
+  local_renderer_ = renderer;
+  return true;
+}
+
+bool WebRtcVideoEngine::SetCapture(bool capture) {
+  bool old_capture = capture_started_;
+  capture_started_ = capture;
+  CaptureState result = UpdateCapturingState();
+  if (result == CS_FAILED || result == CS_NO_DEVICE) {
+    capture_started_ = old_capture;
+    return false;
+  }
+  return true;
+}
+
+CaptureState WebRtcVideoEngine::UpdateCapturingState() {
+  bool capture = capture_started_ && frame_listeners_;
+  CaptureState result = CS_RUNNING;
+  if (!IsCapturing() && capture) {  // Start capturing.
+    if (video_capturer_ == NULL) {
+      return CS_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 CS_FAILED;
+    }
+
+    // Start the video capturer.
+    result = video_capturer_->Start(capture_format);
+    if (CS_RUNNING != result && CS_STARTING != result) {
+      LOG(LS_ERROR) << "Failed to start the video capturer";
+      return result;
+    }
+  } else if (IsCapturing() && !capture) {  // Stop capturing.
+    video_capturer_->Stop();
+    result = CS_STOPPED;
+  }
+
+  return result;
+}
+
+bool WebRtcVideoEngine::IsCapturing() const {
+  return (video_capturer_ != NULL) && video_capturer_->IsRunning();
+}
+
+// TODO(thorcarpenter): Remove this fn, it's only used for unittests!
+void WebRtcVideoEngine::OnFrameCaptured(VideoCapturer* capturer,
+                                        const CapturedFrame* frame) {
+  // Crop to desired aspect ratio.
+  int cropped_width, cropped_height;
+  ComputeCrop(default_codec_format_.width, default_codec_format_.height,
+              frame->width, abs(frame->height),
+              frame->pixel_width, frame->pixel_height,
+              frame->rotation, &cropped_width, &cropped_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, cropped_width, cropped_height)) {
+    LOG(LS_ERROR) << "Couldn't convert to I420! "
+                  << cropped_width << " x " << cropped_height;
+    return;
+  }
+
+  // TODO(janahan): 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.
+  bool drop_frame = false;
+  {
+    talk_base::CritScope cs(&signal_media_critical_);
+    SignalMediaFrame(kDummyVideoSsrc, &i420_frame, &drop_frame);
+  }
+  if (drop_frame) {
+    LOG(LS_VERBOSE) << "Media Effects dropped a frame.";
+    return;
+  }
+
+  // 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(capturer, &i420_frame);
+  }
+}
+
+const std::vector<VideoCodec>& WebRtcVideoEngine::codecs() const {
+  return video_codecs_;
+}
+
+const std::vector<RtpHeaderExtension>&
+WebRtcVideoEngine::rtp_header_extensions() const {
+  return rtp_header_extensions_;
+}
+
+void WebRtcVideoEngine::SetLogging(int min_sev, const char* filter) {
+  // if min_sev == -1, we keep the current log level.
+  if (min_sev >= 0) {
+    SetTraceFilter(SeverityToFilter(min_sev));
+  }
+  SetTraceOptions(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)) {
+      if (encoder_factory_) {
+        const std::vector<WebRtcVideoEncoderFactory::VideoCodec>& codecs =
+            encoder_factory_->codecs();
+        for (size_t j = 0; j < codecs.size(); ++j) {
+          VideoCodec codec(GetExternalVideoPayloadType(j),
+                           codecs[j].name, 0, 0, 0, 0);
+          if (codec.Matches(in))
+            return true;
+        }
+      }
+      for (size_t 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)) {
+      continue;
+    }
+
+    out->id = requested.id;
+    out->name = requested.name;
+    out->preference = requested.preference;
+    out->params = requested.params;
+    out->framerate = talk_base::_min(requested.framerate, local_max->framerate);
+    out->width = 0;
+    out->height = 0;
+    out->params = requested.params;
+    out->feedback_params = requested.feedback_params;
+
+    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;
+}
+
+static void 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;
+  out_codec->SetParam(kCodecParamMinBitrate, in_codec.minBitrate);
+  out_codec->SetParam(kCodecParamMaxBitrate, in_codec.maxBitrate);
+  if (in_codec.qpMax) {
+    out_codec->SetParam(kCodecParamMaxQuantization, in_codec.qpMax);
+  }
+}
+
+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 not found, check if this is supported by external encoder factory.
+  if (!found && encoder_factory_) {
+    const std::vector<WebRtcVideoEncoderFactory::VideoCodec>& codecs =
+        encoder_factory_->codecs();
+    for (size_t i = 0; i < codecs.size(); ++i) {
+      if (_stricmp(in_codec.name.c_str(), codecs[i].name.c_str()) == 0) {
+        out_codec->codecType = codecs[i].type;
+        out_codec->plType = GetExternalVideoPayloadType(i);
+        talk_base::strcpyn(out_codec->plName, sizeof(out_codec->plName),
+                           codecs[i].name.c_str(), codecs[i].name.length());
+        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;
+
+  // Convert bitrate parameters.
+  int max_bitrate = kMaxVideoBitrate;
+  int min_bitrate = kMinVideoBitrate;
+  int start_bitrate = kStartVideoBitrate;
+
+  in_codec.GetParam(kCodecParamMinBitrate, &min_bitrate);
+  in_codec.GetParam(kCodecParamMaxBitrate, &max_bitrate);
+
+  if (max_bitrate < min_bitrate) {
+    return false;
+  }
+  start_bitrate = talk_base::_max(start_bitrate, min_bitrate);
+  start_bitrate = talk_base::_min(start_bitrate, max_bitrate);
+
+  out_codec->minBitrate = min_bitrate;
+  out_codec->startBitrate = start_bitrate;
+  out_codec->maxBitrate = max_bitrate;
+
+  // Convert general codec parameters.
+  int max_quantization = 0;
+  if (in_codec.GetParam(kCodecParamMaxQuantization, &max_quantization)) {
+    if (max_quantization < 0) {
+      return false;
+    }
+    out_codec->qpMax = max_quantization;
+  }
+  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;
+}
+
+void WebRtcVideoEngine::SetTraceFilter(int filter) {
+  tracing_->SetTraceFilter(filter);
+}
+
+// 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::SetTraceOptions(const std::string& options) {
+  // Set WebRTC trace file.
+  std::vector<std::string> opts;
+  talk_base::tokenize(options, ' ', '"', '"', &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);
+    }
+  }
+}
+
+static void AddDefaultFeedbackParams(VideoCodec* codec) {
+  const FeedbackParam kFir(kRtcpFbParamCcm, kRtcpFbCcmParamFir);
+  codec->AddFeedbackParam(kFir);
+  const FeedbackParam kNack(kRtcpFbParamNack, kParamValueEmpty);
+  codec->AddFeedbackParam(kNack);
+  const FeedbackParam kRemb(kRtcpFbParamRemb, kParamValueEmpty);
+  codec->AddFeedbackParam(kRemb);
+}
+
+// Rebuilds the codec list to be only those that are less intensive
+// than the specified codec.
+bool WebRtcVideoEngine::RebuildCodecList(const VideoCodec& in_codec) {
+  if (!FindCodec(in_codec))
+    return false;
+
+  video_codecs_.clear();
+
+  bool found = false;
+  std::set<std::string> external_codec_names;
+  if (encoder_factory_) {
+    const std::vector<WebRtcVideoEncoderFactory::VideoCodec>& codecs =
+        encoder_factory_->codecs();
+    for (size_t i = 0; i < codecs.size(); ++i) {
+      if (!found)
+        found = (in_codec.name == codecs[i].name);
+      VideoCodec codec(GetExternalVideoPayloadType(i),
+                       codecs[i].name,
+                       codecs[i].max_width,
+                       codecs[i].max_height,
+                       codecs[i].max_fps,
+                       codecs.size() + ARRAY_SIZE(kVideoCodecPrefs) - i);
+      AddDefaultFeedbackParams(&codec);
+      video_codecs_.push_back(codec);
+      external_codec_names.insert(codecs[i].name);
+    }
+  }
+  for (size_t i = 0; i < ARRAY_SIZE(kVideoCodecPrefs); ++i) {
+    const VideoCodecPref& pref(kVideoCodecPrefs[i]);
+    if (!found)
+      found = (in_codec.name == pref.name);
+    bool is_external_codec = external_codec_names.find(pref.name) !=
+        external_codec_names.end();
+    if (found && !is_external_codec) {
+      VideoCodec codec(pref.payload_type, pref.name,
+                       in_codec.width, in_codec.height, in_codec.framerate,
+                       ARRAY_SIZE(kVideoCodecPrefs) - i);
+      if (_stricmp(kVp8PayloadName, codec.name.c_str()) == 0) {
+        AddDefaultFeedbackParams(&codec);
+      }
+      video_codecs_.push_back(codec);
+    }
+  }
+  ASSERT(found);
+  return true;
+}
+
+bool WebRtcVideoEngine::SetCapturer(VideoCapturer* capturer) {
+  if (capturer == NULL) {
+    // Stop capturing before clearing the capturer.
+    if (!SetCapture(false)) {
+      LOG(LS_WARNING) << "Camera failed to stop";
+      return false;
+    }
+    ClearCapturer();
+    return true;
+  }
+
+  // Hook up signals and install the supplied capturer.
+  SignalCaptureStateChange.repeat(capturer->SignalStateChange);
+  capturer->SignalFrameCaptured.connect(this,
+      &WebRtcVideoEngine::OnFrameCaptured);
+  ClearCapturer();
+  video_capturer_ = capturer;
+  // Possibly restart the capturer if it is supposed to be running.
+  CaptureState result = UpdateCapturingState();
+  if (result == CS_FAILED || result == CS_NO_DEVICE) {
+    LOG(LS_WARNING) << "Camera failed to restart";
+    return false;
+  }
+  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;
+}
+
+int WebRtcVideoEngine::GetNumOfChannels() {
+  talk_base::CritScope cs(&channels_crit_);
+  return channels_.size();
+}
+
+void WebRtcVideoEngine::IncrementFrameListeners() {
+  if (++frame_listeners_ == 1) {
+    UpdateCapturingState();
+  }
+  // In the unlikely event of wrapparound.
+  ASSERT(frame_listeners_ >= 0);
+}
+
+void WebRtcVideoEngine::DecrementFrameListeners() {
+  if (--frame_listeners_ == 0) {
+    UpdateCapturingState();
+  }
+  ASSERT(frame_listeners_ >= 0);
+}
+
+void WebRtcVideoEngine::Print(webrtc::TraceLevel level, const char* trace,
+                              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;
+  else if (level == webrtc::kTraceTerseInfo)
+    sev = talk_base::LS_INFO;
+
+  // 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;
+    }
+  }
+}
+
+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;
+}
+
+webrtc::VideoDecoder* WebRtcVideoEngine::CreateExternalDecoder(
+    webrtc::VideoCodecType type) {
+  if (decoder_factory_ == NULL) {
+    return NULL;
+  }
+  return decoder_factory_->CreateVideoDecoder(type);
+}
+
+void WebRtcVideoEngine::DestroyExternalDecoder(webrtc::VideoDecoder* decoder) {
+  ASSERT(decoder_factory_ != NULL);
+  if (decoder_factory_ == NULL)
+    return;
+  decoder_factory_->DestroyVideoDecoder(decoder);
+}
+
+webrtc::VideoEncoder* WebRtcVideoEngine::CreateExternalEncoder(
+    webrtc::VideoCodecType type) {
+  if (encoder_factory_ == NULL) {
+    return NULL;
+  }
+  return encoder_factory_->CreateVideoEncoder(type);
+}
+
+void WebRtcVideoEngine::DestroyExternalEncoder(webrtc::VideoEncoder* encoder) {
+  ASSERT(encoder_factory_ != NULL);
+  if (encoder_factory_ == NULL)
+    return;
+  encoder_factory_->DestroyVideoEncoder(encoder);
+}
+
+bool WebRtcVideoEngine::IsExternalEncoderCodecType(
+    webrtc::VideoCodecType type) const {
+  if (!encoder_factory_)
+    return false;
+  const std::vector<WebRtcVideoEncoderFactory::VideoCodec>& codecs =
+      encoder_factory_->codecs();
+  std::vector<WebRtcVideoEncoderFactory::VideoCodec>::const_iterator it;
+  for (it = codecs.begin(); it != codecs.end(); ++it) {
+    if (it->type == type)
+      return true;
+  }
+  return false;
+}
+
+void WebRtcVideoEngine::ClearCapturer() {
+  video_capturer_ = NULL;
+}
+
+void WebRtcVideoEngine::SetExternalDecoderFactory(
+    WebRtcVideoDecoderFactory* decoder_factory) {
+  decoder_factory_ = decoder_factory;
+}
+
+void WebRtcVideoEngine::SetExternalEncoderFactory(
+    WebRtcVideoEncoderFactory* encoder_factory) {
+  if (encoder_factory_ == encoder_factory)
+    return;
+
+  if (encoder_factory_) {
+    encoder_factory_->RemoveObserver(this);
+  }
+  encoder_factory_ = encoder_factory;
+  if (encoder_factory_) {
+    encoder_factory_->AddObserver(this);
+  }
+
+  // Invoke OnCodecAvailable() here in case the list of codecs is already
+  // available when the encoder factory is installed. If not the encoder
+  // factory will invoke the callback later when the codecs become available.
+  OnCodecsAvailable();
+}
+
+void WebRtcVideoEngine::OnCodecsAvailable() {
+  // Rebuild codec list while reapplying the current default codec format.
+  VideoCodec max_codec(kVideoCodecPrefs[0].payload_type,
+                       kVideoCodecPrefs[0].name,
+                       video_codecs_[0].width,
+                       video_codecs_[0].height,
+                       video_codecs_[0].framerate,
+                       0);
+  if (!RebuildCodecList(max_codec)) {
+    LOG(LS_ERROR) << "Failed to initialize list of supported codec types";
+  }
+}
+
+// WebRtcVideoMediaChannel
+
+WebRtcVideoMediaChannel::WebRtcVideoMediaChannel(
+    WebRtcVideoEngine* engine,
+    VoiceMediaChannel* channel)
+    : engine_(engine),
+      voice_channel_(channel),
+      vie_channel_(-1),
+      nack_enabled_(true),
+      remb_enabled_(false),
+      render_started_(false),
+      first_receive_ssrc_(0),
+      send_red_type_(-1),
+      send_fec_type_(-1),
+      send_min_bitrate_(kMinVideoBitrate),
+      send_start_bitrate_(kStartVideoBitrate),
+      send_max_bitrate_(kMaxVideoBitrate),
+      sending_(false),
+      ratio_w_(0),
+      ratio_h_(0) {
+  engine->RegisterChannel(this);
+}
+
+bool WebRtcVideoMediaChannel::Init() {
+  const uint32 ssrc_key = 0;
+  return CreateChannel(ssrc_key, MD_SENDRECV, &vie_channel_);
+}
+
+WebRtcVideoMediaChannel::~WebRtcVideoMediaChannel() {
+  const bool send = false;
+  SetSend(send);
+  const bool render = false;
+  SetRender(render);
+
+  while (!send_channels_.empty()) {
+    if (!DeleteSendChannel(send_channels_.begin()->first)) {
+      LOG(LS_ERROR) << "Unable to delete channel with ssrc key "
+                    << send_channels_.begin()->first;
+      ASSERT(false);
+      break;
+    }
+  }
+
+  // Remove all receive streams and the default channel.
+  while (!recv_channels_.empty()) {
+    RemoveRecvStream(recv_channels_.begin()->first);
+  }
+
+  // Unregister the channel from the engine.
+  engine()->UnregisterChannel(this);
+  if (worker_thread()) {
+    worker_thread()->Clear(this);
+  }
+}
+
+bool WebRtcVideoMediaChannel::SetRecvCodecs(
+    const std::vector<VideoCodec>& codecs) {
+  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)) {
+        receive_codecs_.push_back(wcodec);
+      }
+    } else {
+      LOG(LS_INFO) << "Unknown codec " << iter->name;
+      return false;
+    }
+  }
+
+  for (RecvChannelMap::iterator it = recv_channels_.begin();
+      it != recv_channels_.end(); ++it) {
+    if (!SetReceiveCodecs(it->second))
+      return false;
+  }
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::SetSendCodecs(
+    const std::vector<VideoCodec>& codecs) {
+  // Match with local video codec list.
+  std::vector<webrtc::VideoCodec> send_codecs;
+  VideoCodec checked_codec;
+  VideoCodec current;  // defaults to 0x0
+  if (sending_) {
+    ConvertToCricketVideoCodec(*send_codec_, &current);
+  }
+  for (std::vector<VideoCodec>::const_iterator iter = codecs.begin();
+      iter != codecs.end(); ++iter) {
+    if (_stricmp(iter->name.c_str(), kRedPayloadName) == 0) {
+      send_red_type_ = iter->id;
+    } else if (_stricmp(iter->name.c_str(), kFecPayloadName) == 0) {
+      send_fec_type_ = iter->id;
+    } else if (engine()->CanSendCodec(*iter, current, &checked_codec)) {
+      webrtc::VideoCodec wcodec;
+      if (engine()->ConvertFromCricketVideoCodec(checked_codec, &wcodec)) {
+        if (send_codecs.empty()) {
+          nack_enabled_ = IsNackEnabled(checked_codec);
+          remb_enabled_ = IsRembEnabled(checked_codec);
+        }
+        send_codecs.push_back(wcodec);
+      }
+    } else {
+      LOG(LS_WARNING) << "Unknown codec " << iter->name;
+    }
+  }
+
+  // Fail if we don't have a match.
+  if (send_codecs.empty()) {
+    LOG(LS_WARNING) << "No matching codecs available";
+    return false;
+  }
+
+  // Recv protection.
+  for (RecvChannelMap::iterator it = recv_channels_.begin();
+      it != recv_channels_.end(); ++it) {
+    int channel_id = it->second->channel_id();
+    if (!SetNackFec(channel_id, send_red_type_, send_fec_type_,
+                    nack_enabled_)) {
+      return false;
+    }
+    if (engine_->vie()->rtp()->SetRembStatus(channel_id,
+                                             kNotSending,
+                                             remb_enabled_) != 0) {
+      LOG_RTCERR3(SetRembStatus, channel_id, kNotSending, remb_enabled_);
+      return false;
+    }
+  }
+
+  // Send settings.
+  for (SendChannelMap::iterator iter = send_channels_.begin();
+       iter != send_channels_.end(); ++iter) {
+    int channel_id = iter->second->channel_id();
+    if (!SetNackFec(channel_id, send_red_type_, send_fec_type_,
+                    nack_enabled_)) {
+      return false;
+    }
+    if (engine_->vie()->rtp()->SetRembStatus(channel_id,
+                                             remb_enabled_,
+                                             remb_enabled_) != 0) {
+      LOG_RTCERR3(SetRembStatus, channel_id, remb_enabled_, remb_enabled_);
+      return false;
+    }
+  }
+
+  // Select the first matched codec.
+  webrtc::VideoCodec& codec(send_codecs[0]);
+
+  if (!SetSendCodec(
+          codec, codec.minBitrate, codec.startBitrate, codec.maxBitrate)) {
+    return false;
+  }
+
+  for (SendChannelMap::iterator iter = send_channels_.begin();
+       iter != send_channels_.end(); ++iter) {
+    WebRtcVideoChannelSendInfo* send_channel = iter->second;
+    send_channel->InitializeAdapterOutputFormat(codec);
+  }
+
+  LogSendCodecChange("SetSendCodecs()");
+
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::GetSendCodec(VideoCodec* send_codec) {
+  if (!send_codec_) {
+    return false;
+  }
+  ConvertToCricketVideoCodec(*send_codec_, send_codec);
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::SetSendStreamFormat(uint32 ssrc,
+                                                  const VideoFormat& format) {
+  if (!send_codec_) {
+    LOG(LS_ERROR) << "The send codec has not been set yet.";
+    return false;
+  }
+  WebRtcVideoChannelSendInfo* send_channel = GetSendChannel(ssrc);
+  if (!send_channel) {
+    LOG(LS_ERROR) << "The specified ssrc " << ssrc << " is not in use.";
+    return false;
+  }
+  send_channel->set_video_format(format);
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::SetRender(bool render) {
+  if (render == render_started_) {
+    return true;  // no action required
+  }
+
+  bool ret = true;
+  for (RecvChannelMap::iterator it = recv_channels_.begin();
+      it != recv_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 {
+      if (engine()->vie()->render()->StopRender(
+          it->second->channel_id()) != 0) {
+        LOG_RTCERR1(StopRender, it->second->channel_id());
+        ret = false;
+      }
+    }
+  }
+  if (ret) {
+    render_started_ = render;
+  }
+
+  return ret;
+}
+
+bool WebRtcVideoMediaChannel::SetSend(bool send) {
+  if (!HasReadySendChannels() && send) {
+    LOG(LS_ERROR) << "No stream added";
+    return false;
+  }
+  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_) {
+      return false;
+    }
+    // Start send now.
+    if (!StartSend()) {
+      return false;
+    }
+  } else {
+    // We've been asked to stop sending.
+    if (!StopSend()) {
+      return false;
+    }
+  }
+  sending_ = send;
+
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::AddSendStream(const StreamParams& sp) {
+  LOG(LS_INFO) << "AddSendStream " << sp.ToString();
+
+  if (!IsOneSsrcStream(sp)) {
+      LOG(LS_ERROR) << "AddSendStream: bad local stream parameters";
+      return false;
+  }
+
+  uint32 ssrc_key;
+  if (!CreateSendChannelKey(sp.first_ssrc(), &ssrc_key)) {
+    LOG(LS_ERROR) << "Trying to register duplicate ssrc: " << sp.first_ssrc();
+    return false;
+  }
+  // If the default channel is already used for sending create a new channel
+  // otherwise use the default channel for sending.
+  int channel_id = -1;
+  if (send_channels_[0]->stream_params() == NULL) {
+    channel_id = vie_channel_;
+  } else {
+    if (!CreateChannel(ssrc_key, MD_SEND, &channel_id)) {
+      LOG(LS_ERROR) << "AddSendStream: unable to create channel";
+      return false;
+    }
+  }
+  WebRtcVideoChannelSendInfo* send_channel = send_channels_[ssrc_key];
+  // Set the send (local) SSRC.
+  // If there are multiple send SSRCs, we can only set the first one here, and
+  // the rest of the SSRC(s) need to be set after SetSendCodec has been called
+  // (with a codec requires multiple SSRC(s)).
+  if (engine()->vie()->rtp()->SetLocalSSRC(channel_id,
+                                           sp.first_ssrc()) != 0) {
+    LOG_RTCERR2(SetLocalSSRC, channel_id, sp.first_ssrc());
+    return false;
+  }
+
+  // Set RTCP CName.
+  if (engine()->vie()->rtp()->SetRTCPCName(channel_id,
+                                           sp.cname.c_str()) != 0) {
+    LOG_RTCERR2(SetRTCPCName, channel_id, sp.cname.c_str());
+    return false;
+  }
+
+  // At this point the channel's local SSRC has been updated. If the channel is
+  // the default channel make sure that all the receive channels are updated as
+  // well. Receive channels have to have the same SSRC as the default channel in
+  // order to send receiver reports with this SSRC.
+  if (IsDefaultChannel(channel_id)) {
+    for (RecvChannelMap::const_iterator it = recv_channels_.begin();
+         it != recv_channels_.end(); ++it) {
+      WebRtcVideoChannelRecvInfo* 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;
+      }
+    }
+  }
+
+  send_channel->set_stream_params(sp);
+
+  // Reset send codec after stream parameters changed.
+  if (send_codec_) {
+    if (!SetSendCodec(send_channel, *send_codec_, send_min_bitrate_,
+                      send_start_bitrate_, send_max_bitrate_)) {
+      return false;
+    }
+    LogSendCodecChange("SetSendStreamFormat()");
+  }
+
+  if (sending_) {
+    return StartSend(send_channel);
+  }
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::RemoveSendStream(uint32 ssrc) {
+  uint32 ssrc_key;
+  if (!GetSendChannelKey(ssrc, &ssrc_key)) {
+    LOG(LS_WARNING) << "Try to remove stream with ssrc " << ssrc
+                    << " which doesn't exist.";
+    return false;
+  }
+  WebRtcVideoChannelSendInfo* send_channel = send_channels_[ssrc_key];
+  int channel_id = send_channel->channel_id();
+  if (IsDefaultChannel(channel_id) && (send_channel->stream_params() == NULL)) {
+    // Default channel will still exist. However, if stream_params() is NULL
+    // there is no stream to remove.
+    return false;
+  }
+  if (sending_) {
+    StopSend(send_channel);
+  }
+
+  const WebRtcVideoChannelSendInfo::EncoderMap& encoder_map =
+      send_channel->registered_encoders();
+  for (WebRtcVideoChannelSendInfo::EncoderMap::const_iterator it =
+      encoder_map.begin(); it != encoder_map.end(); ++it) {
+    if (engine()->vie()->ext_codec()->DeRegisterExternalSendCodec(
+        channel_id, it->first) != 0) {
+      LOG_RTCERR1(DeregisterEncoderObserver, channel_id);
+    }
+    engine()->DestroyExternalEncoder(it->second);
+  }
+  send_channel->ClearRegisteredEncoders();
+
+  // The receive channels depend on the default channel, recycle it instead.
+  if (IsDefaultChannel(channel_id)) {
+    SetCapturer(GetDefaultChannelSsrc(), NULL);
+    send_channel->ClearStreamParams();
+  } else {
+    return DeleteSendChannel(ssrc_key);
+  }
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::AddRecvStream(const StreamParams& sp) {
+  // TODO(zhurunz) Remove this once BWE works properly across different send
+  // and receive channels.
+  // Reuse default channel for recv stream in 1:1 call.
+  if (!InConferenceMode() && first_receive_ssrc_ == 0) {
+    LOG(LS_INFO) << "Recv stream " << sp.first_ssrc()
+                 << " reuse default channel #"
+                 << vie_channel_;
+    first_receive_ssrc_ = sp.first_ssrc();
+    if (render_started_) {
+      if (engine()->vie()->render()->StartRender(vie_channel_) !=0) {
+        LOG_RTCERR1(StartRender, vie_channel_);
+      }
+    }
+    return true;
+  }
+
+  if (recv_channels_.find(sp.first_ssrc()) != recv_channels_.end() ||
+      first_receive_ssrc_ == sp.first_ssrc()) {
+    LOG(LS_ERROR) << "Stream already exists";
+    return false;
+  }
+
+  // TODO(perkj): 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.
+  // In order to get the bandwidth estimation work fine for
+  // receive only channels, we connect all receiving channels
+  // to our master send channel.
+  int channel_id = -1;
+  if (!CreateChannel(sp.first_ssrc(), MD_RECV, &channel_id)) {
+    return false;
+  }
+
+  // Get the default renderer.
+  VideoRenderer* default_renderer = NULL;
+  if (InConferenceMode()) {
+    // The recv_channels_ size start out being 1, so if it is two here this
+    // is the first receive channel created (vie_channel_ is not used for
+    // receiving in a conference call). This means that the renderer stored
+    // inside vie_channel_ should be used for the just created channel.
+    if (recv_channels_.size() == 2 &&
+        recv_channels_.find(0) != recv_channels_.end()) {
+      GetRenderer(0, &default_renderer);
+    }
+  }
+
+  // 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 << " and connected to channel #" << vie_channel_;
+
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::RemoveRecvStream(uint32 ssrc) {
+  RecvChannelMap::iterator it = recv_channels_.find(ssrc);
+
+  if (it == recv_channels_.end()) {
+    // TODO(perkj): 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;
+      // Need to stop the renderer and remove it since the render window can be
+      // deleted after this.
+      if (render_started_) {
+        if (engine()->vie()->render()->StopRender(vie_channel_) !=0) {
+          LOG_RTCERR1(StopRender, it->second->channel_id());
+        }
+      }
+      recv_channels_[0]->SetRenderer(NULL);
+      return true;
+    }
+    return false;
+  }
+  WebRtcVideoChannelRecvInfo* 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);
+  }
+
+  const WebRtcVideoChannelRecvInfo::DecoderMap& decoder_map =
+      info->registered_decoders();
+  for (WebRtcVideoChannelRecvInfo::DecoderMap::const_iterator it =
+       decoder_map.begin(); it != decoder_map.end(); ++it) {
+    if (engine()->vie()->ext_codec()->DeRegisterExternalReceiveCodec(
+        channel_id, it->first) != 0) {
+      LOG_RTCERR1(DeregisterDecoderObserver, channel_id);
+    }
+    engine()->DestroyExternalDecoder(it->second);
+  }
+  info->ClearRegisteredDecoders();
+
+  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 WebRtcVideoChannelRecvInfo owned by |it| but remove the channel
+    // from recv_channels_.
+    recv_channels_.erase(it);
+    return false;
+  }
+  // Delete the WebRtcVideoChannelRecvInfo pointed to by it->second.
+  delete info;
+  recv_channels_.erase(it);
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::StartSend() {
+  bool success = true;
+  for (SendChannelMap::iterator iter = send_channels_.begin();
+       iter != send_channels_.end(); ++iter) {
+    WebRtcVideoChannelSendInfo* send_channel = iter->second;
+    if (!StartSend(send_channel)) {
+      success = false;
+    }
+  }
+  return success;
+}
+
+bool WebRtcVideoMediaChannel::StartSend(
+    WebRtcVideoChannelSendInfo* send_channel) {
+  const int channel_id = send_channel->channel_id();
+  if (engine()->vie()->base()->StartSend(channel_id) != 0) {
+    LOG_RTCERR1(StartSend, channel_id);
+    return false;
+  }
+
+  send_channel->set_sending(true);
+  if (!send_channel->video_capturer()) {
+    engine_->IncrementFrameListeners();
+  }
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::StopSend() {
+  bool success = true;
+  for (SendChannelMap::iterator iter = send_channels_.begin();
+       iter != send_channels_.end(); ++iter) {
+    WebRtcVideoChannelSendInfo* send_channel = iter->second;
+    if (!StopSend(send_channel)) {
+      success = false;
+    }
+  }
+  return success;
+}
+
+bool WebRtcVideoMediaChannel::StopSend(
+    WebRtcVideoChannelSendInfo* send_channel) {
+  const int channel_id = send_channel->channel_id();
+  if (engine()->vie()->base()->StopSend(channel_id) != 0) {
+    LOG_RTCERR1(StopSend, channel_id);
+    return false;
+  }
+  send_channel->set_sending(false);
+  if (!send_channel->video_capturer()) {
+    engine_->DecrementFrameListeners();
+  }
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::SendIntraFrame() {
+  bool success = true;
+  for (SendChannelMap::iterator iter = send_channels_.begin();
+       iter != send_channels_.end();
+       ++iter) {
+    WebRtcVideoChannelSendInfo* send_channel = iter->second;
+    const int channel_id = send_channel->channel_id();
+    if (engine()->vie()->codec()->SendKeyFrame(channel_id) != 0) {
+      LOG_RTCERR1(SendKeyFrame, channel_id);
+      success = false;
+    }
+  }
+  return success;
+}
+
+bool WebRtcVideoMediaChannel::IsOneSsrcStream(const StreamParams& sp) {
+  return (sp.ssrcs.size() == 1 && sp.ssrc_groups.size() == 0);
+}
+
+bool WebRtcVideoMediaChannel::HasReadySendChannels() {
+  return !send_channels_.empty() &&
+      ((send_channels_.size() > 1) ||
+       (send_channels_[0]->stream_params() != NULL));
+}
+
+bool WebRtcVideoMediaChannel::GetSendChannelKey(uint32 local_ssrc,
+                                                uint32* key) {
+  *key = 0;
+  // If a send channel is not ready to send it will not have local_ssrc
+  // registered to it.
+  if (!HasReadySendChannels()) {
+    return false;
+  }
+  // The default channel is stored with key 0. The key therefore does not match
+  // the SSRC associated with the default channel. Check if the SSRC provided
+  // corresponds to the default channel's SSRC.
+  if (local_ssrc == GetDefaultChannelSsrc()) {
+    return true;
+  }
+  if (send_channels_.find(local_ssrc) == send_channels_.end()) {
+    for (SendChannelMap::iterator iter = send_channels_.begin();
+         iter != send_channels_.end(); ++iter) {
+      WebRtcVideoChannelSendInfo* send_channel = iter->second;
+      if (send_channel->has_ssrc(local_ssrc)) {
+        *key = iter->first;
+        return true;
+      }
+    }
+    return false;
+  }
+  // The key was found in the above std::map::find call. This means that the
+  // ssrc is the key.
+  *key = local_ssrc;
+  return true;
+}
+
+WebRtcVideoChannelSendInfo* WebRtcVideoMediaChannel::GetSendChannel(
+    VideoCapturer* video_capturer) {
+  for (SendChannelMap::iterator iter = send_channels_.begin();
+       iter != send_channels_.end(); ++iter) {
+    WebRtcVideoChannelSendInfo* send_channel = iter->second;
+    if (send_channel->video_capturer() == video_capturer) {
+      return send_channel;
+    }
+  }
+  return NULL;
+}
+
+WebRtcVideoChannelSendInfo* WebRtcVideoMediaChannel::GetSendChannel(
+    uint32 local_ssrc) {
+  uint32 key;
+  if (!GetSendChannelKey(local_ssrc, &key)) {
+    return NULL;
+  }
+  return send_channels_[key];
+}
+
+bool WebRtcVideoMediaChannel::CreateSendChannelKey(uint32 local_ssrc,
+                                                   uint32* key) {
+  if (GetSendChannelKey(local_ssrc, key)) {
+    // If there is a key corresponding to |local_ssrc|, the SSRC is already in
+    // use. SSRCs need to be unique in a session and at this point a duplicate
+    // SSRC has been detected.
+    return false;
+  }
+  if (send_channels_[0]->stream_params() == NULL) {
+    // key should be 0 here as the default channel should be re-used whenever it
+    // is not used.
+    *key = 0;
+    return true;
+  }
+  // SSRC is currently not in use and the default channel is already in use. Use
+  // the SSRC as key since it is supposed to be unique in a session.
+  *key = local_ssrc;
+  return true;
+}
+
+uint32 WebRtcVideoMediaChannel::GetDefaultChannelSsrc() {
+  WebRtcVideoChannelSendInfo* send_channel = send_channels_[0];
+  const StreamParams* sp = send_channel->stream_params();
+  if (sp == NULL) {
+    // This happens if no send stream is currently registered.
+    return 0;
+  }
+  return sp->first_ssrc();
+}
+
+bool WebRtcVideoMediaChannel::DeleteSendChannel(uint32 ssrc_key) {
+  if (send_channels_.find(ssrc_key) == send_channels_.end()) {
+    return false;
+  }
+  WebRtcVideoChannelSendInfo* send_channel = send_channels_[ssrc_key];
+  VideoCapturer* capturer = send_channel->video_capturer();
+  if (capturer != NULL) {
+    capturer->SignalVideoFrame.disconnect(this);
+    send_channel->set_video_capturer(NULL);
+  }
+
+  int channel_id = send_channel->channel_id();
+  int capture_id = send_channel->capture_id();
+  if (engine()->vie()->codec()->DeregisterEncoderObserver(
+          channel_id) != 0) {
+    LOG_RTCERR1(DeregisterEncoderObserver, channel_id);
+  }
+
+  // Destroy the external capture interface.
+  if (engine()->vie()->capture()->DisconnectCaptureDevice(
+          channel_id) != 0) {
+    LOG_RTCERR1(DisconnectCaptureDevice, channel_id);
+  }
+  if (engine()->vie()->capture()->ReleaseCaptureDevice(
+          capture_id) != 0) {
+    LOG_RTCERR1(ReleaseCaptureDevice, capture_id);
+  }
+
+  // The default channel is stored in both |send_channels_| and
+  // |recv_channels_|. To make sure it is only deleted once from vie let the
+  // delete call happen when tearing down |recv_channels_| and not here.
+  if (!IsDefaultChannel(channel_id)) {
+    engine_->vie()->base()->DeleteChannel(channel_id);
+  }
+  delete send_channel;
+  send_channels_.erase(ssrc_key);
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::RemoveCapturer(uint32 ssrc) {
+  WebRtcVideoChannelSendInfo* send_channel = GetSendChannel(ssrc);
+  if (!send_channel) {
+    return false;
+  }
+  VideoCapturer* capturer = send_channel->video_capturer();
+  if (capturer == NULL) {
+    return false;
+  }
+  capturer->SignalVideoFrame.disconnect(this);
+  send_channel->set_video_capturer(NULL);
+  if (send_channel->sending()) {
+    engine_->IncrementFrameListeners();
+  }
+  const int64 timestamp = send_channel->local_stream_info()->time_stamp();
+  if (send_codec_) {
+    QueueBlackFrame(ssrc, timestamp, send_codec_->maxFramerate);
+  }
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::SetRenderer(uint32 ssrc,
+                                          VideoRenderer* renderer) {
+  if (recv_channels_.find(ssrc) == recv_channels_.end()) {
+    // TODO(perkj): 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 &&
+        recv_channels_.find(0) != recv_channels_.end()) {
+      LOG(LS_INFO) << "SetRenderer " << ssrc
+                   << " reuse default channel #"
+                   << vie_channel_;
+      recv_channels_[0]->SetRenderer(renderer);
+      return true;
+    }
+    return false;
+  }
+
+  recv_channels_[ssrc]->SetRenderer(renderer);
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::GetStats(VideoMediaInfo* info) {
+  // Get sender statistics and build VideoSenderInfo.
+  unsigned int total_bitrate_sent = 0;
+  unsigned int video_bitrate_sent = 0;
+  unsigned int fec_bitrate_sent = 0;
+  unsigned int nack_bitrate_sent = 0;
+  unsigned int estimated_send_bandwidth = 0;
+  unsigned int target_enc_bitrate = 0;
+  if (send_codec_) {
+    for (SendChannelMap::const_iterator iter = send_channels_.begin();
+         iter != send_channels_.end(); ++iter) {
+      WebRtcVideoChannelSendInfo* send_channel = iter->second;
+      const int channel_id = send_channel->channel_id();
+      VideoSenderInfo sinfo;
+      const StreamParams* send_params = send_channel->stream_params();
+      if (send_params == NULL) {
+        // This should only happen if the default vie channel is not in use.
+        // This can happen if no streams have ever been added or the stream
+        // corresponding to the default channel has been removed. Note that
+        // there may be non-default vie channels in use when this happen so
+        // asserting send_channels_.size() == 1 is not correct and neither is
+        // breaking out of the loop.
+        ASSERT(channel_id == vie_channel_);
+        continue;
+      }
+      unsigned int bytes_sent, packets_sent, bytes_recv, packets_recv;
+      if (engine_->vie()->rtp()->GetRTPStatistics(channel_id, bytes_sent,
+                                                  packets_sent, bytes_recv,
+                                                  packets_recv) != 0) {
+        LOG_RTCERR1(GetRTPStatistics, vie_channel_);
+        continue;
+      }
+      WebRtcLocalStreamInfo* channel_stream_info =
+          send_channel->local_stream_info();
+
+      sinfo.ssrcs = send_params->ssrcs;
+      sinfo.codec_name = 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 = channel_stream_info->width();
+      sinfo.frame_height = channel_stream_info->height();
+      sinfo.framerate_input = channel_stream_info->framerate();
+      sinfo.framerate_sent = send_channel->encoder_observer()->framerate();
+      sinfo.nominal_bitrate = send_channel->encoder_observer()->bitrate();
+      sinfo.preferred_bitrate = send_max_bitrate_;
+      sinfo.adapt_reason = send_channel->CurrentAdaptReason();
+
+      // 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(
+              channel_id,
+              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);
+
+      unsigned int channel_total_bitrate_sent = 0;
+      unsigned int channel_video_bitrate_sent = 0;
+      unsigned int channel_fec_bitrate_sent = 0;
+      unsigned int channel_nack_bitrate_sent = 0;
+      if (engine_->vie()->rtp()->GetBandwidthUsage(
+          channel_id, channel_total_bitrate_sent, channel_video_bitrate_sent,
+          channel_fec_bitrate_sent, channel_nack_bitrate_sent) == 0) {
+        total_bitrate_sent += channel_total_bitrate_sent;
+        video_bitrate_sent += channel_video_bitrate_sent;
+        fec_bitrate_sent += channel_fec_bitrate_sent;
+        nack_bitrate_sent += channel_nack_bitrate_sent;
+      } else {
+        LOG_RTCERR1(GetBandwidthUsage, channel_id);
+      }
+
+      unsigned int estimated_stream_send_bandwidth = 0;
+      if (engine_->vie()->rtp()->GetEstimatedSendBandwidth(
+          channel_id, &estimated_stream_send_bandwidth) == 0) {
+        estimated_send_bandwidth += estimated_stream_send_bandwidth;
+      } else {
+        LOG_RTCERR1(GetEstimatedSendBandwidth, channel_id);
+      }
+      unsigned int target_enc_stream_bitrate = 0;
+      if (engine_->vie()->codec()->GetCodecTargetBitrate(
+          channel_id, &target_enc_stream_bitrate) == 0) {
+        target_enc_bitrate += target_enc_stream_bitrate;
+      } else {
+        LOG_RTCERR1(GetCodecTargetBitrate, channel_id);
+      }
+    }
+  } else {
+    LOG(LS_WARNING) << "GetStats: sender information not ready.";
+  }
+
+  // Get the SSRC and stats for each receiver, based on our own calculations.
+  unsigned int estimated_recv_bandwidth = 0;
+  for (RecvChannelMap::const_iterator it = recv_channels_.begin();
+       it != recv_channels_.end(); ++it) {
+    // Don't report receive statistics from the default channel if we have
+    // specified receive channels.
+    if (it->first == 0 && recv_channels_.size() > 1)
+      continue;
+    WebRtcVideoChannelRecvInfo* channel = it->second;
+
+    unsigned int ssrc;
+    // Get receiver statistics and build VideoReceiverInfo, if we have data.
+    if (engine_->vie()->rtp()->GetRemoteSSRC(channel->channel_id(), ssrc) != 0)
+      continue;
+
+    unsigned int bytes_sent, packets_sent, bytes_recv, packets_recv;
+    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.ssrcs.push_back(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);
+
+    unsigned int estimated_recv_stream_bandwidth = 0;
+    if (engine_->vie()->rtp()->GetEstimatedReceiveBandwidth(
+        channel->channel_id(), &estimated_recv_stream_bandwidth) == 0) {
+      estimated_recv_bandwidth += estimated_recv_stream_bandwidth;
+    } else {
+      LOG_RTCERR1(GetEstimatedReceiveBandwidth, channel->channel_id());
+    }
+  }
+
+  // Build BandwidthEstimationInfo.
+  // TODO(zhurunz): Add real unittest for this.
+  BandwidthEstimationInfo bwe;
+
+  // Calculations done above per send/receive stream.
+  bwe.actual_enc_bitrate = video_bitrate_sent;
+  bwe.transmit_bitrate = total_bitrate_sent;
+  bwe.retransmit_bitrate = nack_bitrate_sent;
+  bwe.available_send_bandwidth = estimated_send_bandwidth;
+  bwe.available_recv_bandwidth = estimated_recv_bandwidth;
+  bwe.target_enc_bitrate = target_enc_bitrate;
+
+  info->bw_estimations.push_back(bwe);
+
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::SetCapturer(uint32 ssrc,
+                                          VideoCapturer* capturer) {
+  ASSERT(ssrc != 0);
+  if (!capturer) {
+    return RemoveCapturer(ssrc);
+  }
+  WebRtcVideoChannelSendInfo* send_channel = GetSendChannel(ssrc);
+  if (!send_channel) {
+    return false;
+  }
+  VideoCapturer* old_capturer = send_channel->video_capturer();
+  if (send_channel->sending() && !old_capturer) {
+    engine_->DecrementFrameListeners();
+  }
+  if (old_capturer) {
+    old_capturer->SignalVideoFrame.disconnect(this);
+  }
+
+  send_channel->set_video_capturer(capturer);
+  capturer->SignalVideoFrame.connect(
+      this,
+      &WebRtcVideoMediaChannel::AdaptAndSendFrame);
+  if (!capturer->IsScreencast() && ratio_w_ != 0 && ratio_h_ != 0) {
+    capturer->UpdateAspectRatio(ratio_w_, ratio_h_);
+  }
+  const int64 timestamp = send_channel->local_stream_info()->time_stamp();
+  if (send_codec_) {
+    QueueBlackFrame(ssrc, timestamp, send_codec_->maxFramerate);
+  }
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::RequestIntraFrame() {
+  // There is no API exposed to application to request a key frame
+  // ViE does this internally when there are errors from decoder
+  return false;
+}
+
+void WebRtcVideoMediaChannel::OnPacketReceived(talk_base::Buffer* packet) {
+  // 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 = GetRecvChannelNum(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) {
+// 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.
+
+  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 = GetRecvChannelNum(ssrc);
+    if (which_channel != -1 && !IsDefaultChannel(which_channel)) {
+      engine_->vie()->network()->ReceivedRTCPPacket(which_channel,
+                                                    packet->data(),
+                                                    packet->length());
+    }
+  }
+  // SR may continue RR and any RR entry may correspond to any one of the send
+  // channels. So all RTCP packets must be forwarded all send channels. ViE
+  // will filter out RR internally.
+  for (SendChannelMap::iterator iter = send_channels_.begin();
+       iter != send_channels_.end(); ++iter) {
+    WebRtcVideoChannelSendInfo* send_channel = iter->second;
+    int channel_id = send_channel->channel_id();
+    engine_->vie()->network()->ReceivedRTCPPacket(channel_id,
+                                                  packet->data(),
+                                                  packet->length());
+  }
+}
+
+void WebRtcVideoMediaChannel::OnReadyToSend(bool ready) {
+  SetNetworkTransmissionState(ready);
+}
+
+bool WebRtcVideoMediaChannel::MuteStream(uint32 ssrc, bool muted) {
+  WebRtcVideoChannelSendInfo* send_channel = GetSendChannel(ssrc);
+  if (!send_channel) {
+    LOG(LS_ERROR) << "The specified ssrc " << ssrc << " is not in use.";
+    return false;
+  }
+  send_channel->set_muted(muted);
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::SetRecvRtpHeaderExtensions(
+    const std::vector<RtpHeaderExtension>& extensions) {
+  if (receive_extensions_ == extensions) {
+    return true;
+  }
+  receive_extensions_ = extensions;
+
+  const RtpHeaderExtension* offset_extension =
+      FindHeaderExtension(extensions, kRtpTimestampOffsetHeaderExtension);
+  const RtpHeaderExtension* send_time_extension =
+      FindHeaderExtension(extensions, kRtpAbsoluteSendTimeHeaderExtension);
+
+  // Loop through all receive channels and enable/disable the extensions.
+  for (RecvChannelMap::iterator channel_it = recv_channels_.begin();
+       channel_it != recv_channels_.end(); ++channel_it) {
+    int channel_id = channel_it->second->channel_id();
+    if (!SetHeaderExtension(
+        &webrtc::ViERTP_RTCP::SetReceiveTimestampOffsetStatus, channel_id,
+        offset_extension)) {
+      return false;
+    }
+    if (!SetHeaderExtension(
+        &webrtc::ViERTP_RTCP::SetReceiveAbsoluteSendTimeStatus, channel_id,
+        send_time_extension)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::SetSendRtpHeaderExtensions(
+    const std::vector<RtpHeaderExtension>& extensions) {
+  send_extensions_ = extensions;
+
+  const RtpHeaderExtension* offset_extension =
+      FindHeaderExtension(extensions, kRtpTimestampOffsetHeaderExtension);
+  const RtpHeaderExtension* send_time_extension =
+      FindHeaderExtension(extensions, kRtpAbsoluteSendTimeHeaderExtension);
+
+  // Loop through all send channels and enable/disable the extensions.
+  for (SendChannelMap::iterator channel_it = send_channels_.begin();
+       channel_it != send_channels_.end(); ++channel_it) {
+    int channel_id = channel_it->second->channel_id();
+    if (!SetHeaderExtension(
+        &webrtc::ViERTP_RTCP::SetSendTimestampOffsetStatus, channel_id,
+        offset_extension)) {
+      return false;
+    }
+    if (!SetHeaderExtension(
+        &webrtc::ViERTP_RTCP::SetSendAbsoluteSendTimeStatus, channel_id,
+        send_time_extension)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::SetSendBandwidth(bool autobw, int bps) {
+  LOG(LS_INFO) << "WebRtcVideoMediaChanne::SetSendBandwidth";
+
+  if (InConferenceMode()) {
+    LOG(LS_INFO) << "Conference mode ignores SetSendBandWidth";
+    return true;
+  }
+
+  if (!send_codec_) {
+    LOG(LS_INFO) << "The send codec has not been set up yet";
+    return true;
+  }
+
+  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;
+  }
+
+  if (!SetSendCodec(*send_codec_, min_bitrate, start_bitrate, max_bitrate)) {
+    return false;
+  }
+  LogSendCodecChange("SetSendBandwidth()");
+
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::SetOptions(const VideoOptions &options) {
+  // Always accept options that are unchanged.
+  if (options_ == options) {
+    return true;
+  }
+
+  // Trigger SetSendCodec to set correct noise reduction state if the option has
+  // changed.
+  bool denoiser_changed = options.video_noise_reduction.IsSet() &&
+      (options_.video_noise_reduction != options.video_noise_reduction);
+
+  bool leaky_bucket_changed = options.video_leaky_bucket.IsSet() &&
+      (options_.video_leaky_bucket != options.video_leaky_bucket);
+
+  bool buffer_latency_changed = options.buffered_mode_latency.IsSet() &&
+      (options_.buffered_mode_latency != options.buffered_mode_latency);
+
+  bool conference_mode_turned_off = false;
+  if (options_.conference_mode.IsSet() && options.conference_mode.IsSet() &&
+      options_.conference_mode.GetWithDefaultIfUnset(false) &&
+      !options.conference_mode.GetWithDefaultIfUnset(false)) {
+    conference_mode_turned_off = true;
+  }
+
+  // Save the options, to be interpreted where appropriate.
+  // Use options_.SetAll() instead of assignment so that unset value in options
+  // will not overwrite the previous option value.
+  options_.SetAll(options);
+
+  // Set CPU options for all send channels.
+  for (SendChannelMap::iterator iter = send_channels_.begin();
+       iter != send_channels_.end(); ++iter) {
+    WebRtcVideoChannelSendInfo* send_channel = iter->second;
+    send_channel->ApplyCpuOptions(options_);
+  }
+
+  // Adjust send codec bitrate if needed.
+  int conf_max_bitrate = kDefaultConferenceModeMaxVideoBitrate;
+
+  int expected_bitrate = send_max_bitrate_;
+  if (InConferenceMode()) {
+    expected_bitrate = conf_max_bitrate;
+  } else if (conference_mode_turned_off) {
+    // This is a special case for turning conference mode off.
+    // Max bitrate should go back to the default maximum value instead
+    // of the current maximum.
+    expected_bitrate = kMaxVideoBitrate;
+  }
+
+  if (send_codec_ &&
+      (send_max_bitrate_ != expected_bitrate || denoiser_changed)) {
+    // 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;
+    }
+    LogSendCodecChange("SetOptions()");
+  }
+  if (leaky_bucket_changed) {
+    bool enable_leaky_bucket =
+        options_.video_leaky_bucket.GetWithDefaultIfUnset(false);
+    for (SendChannelMap::iterator it = send_channels_.begin();
+        it != send_channels_.end(); ++it) {
+      if (engine()->vie()->rtp()->SetTransmissionSmoothingStatus(
+          it->second->channel_id(), enable_leaky_bucket) != 0) {
+        LOG_RTCERR2(SetTransmissionSmoothingStatus, it->second->channel_id(),
+                    enable_leaky_bucket);
+      }
+    }
+  }
+  if (buffer_latency_changed) {
+    int buffer_latency =
+        options_.buffered_mode_latency.GetWithDefaultIfUnset(
+            cricket::kBufferedModeDisabled);
+    for (SendChannelMap::iterator it = send_channels_.begin();
+        it != send_channels_.end(); ++it) {
+      if (engine()->vie()->rtp()->SetSenderBufferingMode(
+          it->second->channel_id(), buffer_latency) != 0) {
+        LOG_RTCERR2(SetSenderBufferingMode, it->second->channel_id(),
+                    buffer_latency);
+      }
+    }
+    for (RecvChannelMap::iterator it = recv_channels_.begin();
+        it != recv_channels_.end(); ++it) {
+      if (engine()->vie()->rtp()->SetReceiverBufferingMode(
+          it->second->channel_id(), buffer_latency) != 0) {
+        LOG_RTCERR2(SetReceiverBufferingMode, it->second->channel_id(),
+                    buffer_latency);
+      }
+    }
+  }
+  return true;
+}
+
+void WebRtcVideoMediaChannel::SetInterface(NetworkInterface* iface) {
+  MediaChannel::SetInterface(iface);
+  // Set the RTP recv/send buffer to a bigger size
+  if (network_interface_) {
+    network_interface_->SetOption(NetworkInterface::ST_RTP,
+                                  talk_base::Socket::OPT_RCVBUF,
+                                  kVideoRtpBufferSize);
+
+    // TODO(sriniv): Remove or re-enable this.
+    // As part of b/8030474, send-buffer is size now controlled through
+    // portallocator flags.
+    // network_interface_->SetOption(NetworkInterface::ST_RTP,
+    //                              talk_base::Socket::OPT_SNDBUF,
+    //                              kVideoRtpBufferSize);
+  }
+}
+
+void WebRtcVideoMediaChannel::UpdateAspectRatio(int ratio_w, int ratio_h) {
+  ASSERT(ratio_w != 0);
+  ASSERT(ratio_h != 0);
+  ratio_w_ = ratio_w;
+  ratio_h_ = ratio_h;
+  // For now assume that all streams want the same aspect ratio.
+  // TODO(hellner): remove the need for this assumption.
+  for (SendChannelMap::iterator iter = send_channels_.begin();
+       iter != send_channels_.end(); ++iter) {
+    WebRtcVideoChannelSendInfo* send_channel = iter->second;
+    VideoCapturer* capturer = send_channel->video_capturer();
+    if (capturer) {
+      capturer->UpdateAspectRatio(ratio_w, ratio_h);
+    }
+  }
+}
+
+bool WebRtcVideoMediaChannel::GetRenderer(uint32 ssrc,
+                                          VideoRenderer** renderer) {
+  RecvChannelMap::const_iterator it = recv_channels_.find(ssrc);
+  if (it == recv_channels_.end()) {
+    if (first_receive_ssrc_ == ssrc &&
+        recv_channels_.find(0) != recv_channels_.end()) {
+      LOG(LS_INFO) << " GetRenderer " << ssrc
+                   << " reuse default renderer #"
+                   << vie_channel_;
+      *renderer = recv_channels_[0]->render_adapter()->renderer();
+      return true;
+    }
+    return false;
+  }
+
+  *renderer = it->second->render_adapter()->renderer();
+  return true;
+}
+
+void WebRtcVideoMediaChannel::AdaptAndSendFrame(VideoCapturer* capturer,
+                                                const VideoFrame* frame) {
+  if (capturer->IsScreencast()) {
+    // Do not adapt frames that are screencast.
+    SendFrame(capturer, frame);
+    return;
+  }
+  WebRtcVideoChannelSendInfo* send_channel = GetSendChannel(capturer);
+  if (!send_channel) {
+    SendFrame(capturer, frame);
+    return;
+  }
+  const VideoFrame* output_frame = NULL;
+  send_channel->AdaptFrame(frame, &output_frame);
+  if (output_frame) {
+    SendFrame(send_channel, output_frame, capturer->IsScreencast());
+  }
+}
+
+// TODO(zhurunz): Add unittests to test this function.
+void WebRtcVideoMediaChannel::SendFrame(VideoCapturer* capturer,
+                                        const VideoFrame* frame) {
+  // If there's send channel registers to the |capturer|, then only send the
+  // frame to that channel and return. Otherwise send the frame to the default
+  // channel, which currently taking frames from the engine.
+  WebRtcVideoChannelSendInfo* send_channel = GetSendChannel(capturer);
+  if (send_channel) {
+    SendFrame(send_channel, frame, capturer->IsScreencast());
+    return;
+  }
+  // TODO(hellner): Remove below for loop once the captured frame no longer
+  // come from the engine, i.e. the engine no longer owns a capturer.
+  for (SendChannelMap::iterator iter = send_channels_.begin();
+       iter != send_channels_.end(); ++iter) {
+    WebRtcVideoChannelSendInfo* send_channel = iter->second;
+    if (send_channel->video_capturer() == NULL) {
+      SendFrame(send_channel, frame, capturer->IsScreencast());
+    }
+  }
+}
+
+bool WebRtcVideoMediaChannel::SendFrame(
+    WebRtcVideoChannelSendInfo* send_channel,
+    const VideoFrame* frame,
+    bool is_screencast) {
+  if (!send_channel) {
+    return false;
+  }
+  if (!send_codec_) {
+    // Send codec has not been set. No reason to process the frame any further.
+    return false;
+  }
+  const VideoFormat& video_format = send_channel->video_format();
+  // If the frame should be dropped.
+  const bool video_format_set = video_format != cricket::VideoFormat();
+  if (video_format_set &&
+      (video_format.width == 0 && video_format.height == 0)) {
+    return true;
+  }
+
+  // Checks if we need to reset vie send codec.
+  if (!MaybeResetVieSendCodec(send_channel, frame->GetWidth(),
+                              frame->GetHeight(), is_screencast, NULL)) {
+    LOG(LS_ERROR) << "MaybeResetVieSendCodec failed with "
+                  << frame->GetWidth() << "x" << frame->GetHeight();
+    return false;
+  }
+  const VideoFrame* frame_out = frame;
+  talk_base::scoped_ptr<VideoFrame> processed_frame;
+  // Disable muting for screencast.
+  const bool mute = (send_channel->muted() && !is_screencast);
+  send_channel->ProcessFrame(*frame_out, mute, processed_frame.use());
+  if (processed_frame) {
+    frame_out = processed_frame.get();
+  }
+
+  webrtc::ViEVideoFrameI420 frame_i420;
+  // TODO(ronghuawu): 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();
+
+  int64 timestamp_ntp_ms = 0;
+  // TODO(justinlin): Reenable after Windows issues with clock drift are fixed.
+  // Currently reverted to old behavior of discarding capture timestamp.
+#if 0
+  // If the frame timestamp is 0, we will use the deliver time.
+  const int64 frame_timestamp = frame->GetTimeStamp();
+  if (frame_timestamp != 0) {
+    if (abs(time(NULL) - frame_timestamp / talk_base::kNumNanosecsPerSec) >
+            kTimestampDeltaInSecondsForWarning) {
+      LOG(LS_WARNING) << "Frame timestamp differs by more than "
+                      << kTimestampDeltaInSecondsForWarning << " seconds from "
+                      << "current Unix timestamp.";
+    }
+
+    timestamp_ntp_ms =
+        talk_base::UnixTimestampNanosecsToNtpMillisecs(frame_timestamp);
+  }
+#endif
+
+  return send_channel->external_capture()->IncomingFrameI420(
+      frame_i420, timestamp_ntp_ms) == 0;
+}
+
+bool WebRtcVideoMediaChannel::CreateChannel(uint32 ssrc_key,
+                                            MediaDirection direction,
+                                            int* channel_id) {
+  // There are 3 types of channels. Sending only, receiving only and
+  // sending and receiving. The sending and receiving channel is the
+  // default channel and there is only one. All other channels that are created
+  // are associated with the default channel which must exist. The default
+  // channel id is stored in |vie_channel_|. All channels need to know about
+  // the default channel to properly handle remb which is why there are
+  // different ViE create channel calls.
+  // For this channel the local and remote ssrc key is 0. However, it may
+  // have a non-zero local and/or remote ssrc depending on if it is currently
+  // sending and/or receiving.
+  if ((vie_channel_ == -1 || direction == MD_SENDRECV) &&
+      (!send_channels_.empty() || !recv_channels_.empty())) {
+    ASSERT(false);
+    return false;
+  }
+
+  *channel_id = -1;
+  if (direction == MD_RECV) {
+    // All rec channels are associated with the default channel |vie_channel_|
+    if (engine_->vie()->base()->CreateReceiveChannel(*channel_id,
+                                                     vie_channel_) != 0) {
+      LOG_RTCERR2(CreateReceiveChannel, *channel_id, vie_channel_);
+      return false;
+    }
+  } else if (direction == MD_SEND) {
+    if (engine_->vie()->base()->CreateChannel(*channel_id,
+                                              vie_channel_) != 0) {
+      LOG_RTCERR2(CreateChannel, *channel_id, vie_channel_);
+      return false;
+    }
+  } else {
+    ASSERT(direction == MD_SENDRECV);
+    if (engine_->vie()->base()->CreateChannel(*channel_id) != 0) {
+      LOG_RTCERR1(CreateChannel, *channel_id);
+      return false;
+    }
+  }
+  if (!ConfigureChannel(*channel_id, direction, ssrc_key)) {
+    engine_->vie()->base()->DeleteChannel(*channel_id);
+    *channel_id = -1;
+    return false;
+  }
+
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::ConfigureChannel(int channel_id,
+                                               MediaDirection direction,
+                                               uint32 ssrc_key) {
+  const bool receiving = (direction == MD_RECV) || (direction == MD_SENDRECV);
+  const bool sending = (direction == MD_SEND) || (direction == MD_SENDRECV);
+  // 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;
+  }
+  if (!SetNackFec(channel_id, send_red_type_, send_fec_type_, nack_enabled_)) {
+    // Logged in SetNackFec. Don't spam the logs.
+    return false;
+  }
+  // Note that receiving must always be configured before sending to ensure
+  // that send and receive channel is configured correctly (ConfigureReceiving
+  // assumes no sending).
+  if (receiving) {
+    if (!ConfigureReceiving(channel_id, ssrc_key)) {
+      return false;
+    }
+  }
+  if (sending) {
+    if (!ConfigureSending(channel_id, ssrc_key)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::ConfigureReceiving(int channel_id,
+                                                 uint32 remote_ssrc_key) {
+  // Make sure that an SSRC/key isn't registered more than once.
+  if (recv_channels_.find(remote_ssrc_key) != recv_channels_.end()) {
+    return false;
+  }
+  // Connect the voice channel, if there is one.
+  // TODO(perkj): 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_key == 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<WebRtcVideoChannelRecvInfo> channel_info(
+      new WebRtcVideoChannelRecvInfo(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;
+  }
+
+
+  if (engine_->vie()->rtp()->SetRembStatus(channel_id,
+                                           kNotSending,
+                                           remb_enabled_) != 0) {
+    LOG_RTCERR3(SetRembStatus, channel_id, kNotSending, remb_enabled_);
+    return false;
+  }
+
+  if (!SetHeaderExtension(&webrtc::ViERTP_RTCP::SetReceiveTimestampOffsetStatus,
+      channel_id, receive_extensions_, kRtpTimestampOffsetHeaderExtension)) {
+    return false;
+  }
+
+  if (!SetHeaderExtension(
+      &webrtc::ViERTP_RTCP::SetReceiveAbsoluteSendTimeStatus, channel_id,
+      receive_extensions_, kRtpAbsoluteSendTimeHeaderExtension)) {
+    return false;
+  }
+
+  if (remote_ssrc_key != 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(GetLocalSSRC, vie_channel_, send_ssrc);
+      return false;
+    }
+    if (rtp->SetLocalSSRC(channel_id, send_ssrc) == -1) {
+      LOG_RTCERR2(SetLocalSSRC, 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_info.get())) {
+    return false;
+  }
+
+  int buffer_latency =
+      options_.buffered_mode_latency.GetWithDefaultIfUnset(
+          cricket::kBufferedModeDisabled);
+  if (buffer_latency != cricket::kBufferedModeDisabled) {
+    if (engine()->vie()->rtp()->SetReceiverBufferingMode(
+        channel_id, buffer_latency) != 0) {
+      LOG_RTCERR2(SetReceiverBufferingMode, channel_id, buffer_latency);
+    }
+  }
+
+  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;
+  }
+
+  recv_channels_[remote_ssrc_key] = channel_info.release();
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::ConfigureSending(int channel_id,
+                                               uint32 local_ssrc_key) {
+  // The ssrc key can be zero or correspond to an SSRC.
+  // Make sure the default channel isn't configured more than once.
+  if (local_ssrc_key == 0 && send_channels_.find(0) != send_channels_.end()) {
+    return false;
+  }
+  // Make sure that the SSRC is not already in use.
+  uint32 dummy_key;
+  if (GetSendChannelKey(local_ssrc_key, &dummy_key)) {
+    return false;
+  }
+  int vie_capture = 0;
+  webrtc::ViEExternalCapture* external_capture = NULL;
+  // Register external capture.
+  if (engine()->vie()->capture()->AllocateExternalCaptureDevice(
+      vie_capture, external_capture) != 0) {
+    LOG_RTCERR0(AllocateExternalCaptureDevice);
+    return false;
+  }
+
+  // Connect external capture.
+  if (engine()->vie()->capture()->ConnectCaptureDevice(
+      vie_capture, channel_id) != 0) {
+    LOG_RTCERR2(ConnectCaptureDevice, vie_capture, channel_id);
+    return false;
+  }
+  talk_base::scoped_ptr<WebRtcVideoChannelSendInfo> send_channel(
+      new WebRtcVideoChannelSendInfo(channel_id, vie_capture,
+                                     external_capture,
+                                     engine()->cpu_monitor()));
+  send_channel->ApplyCpuOptions(options_);
+
+  // Register encoder observer for outgoing framerate and bitrate.
+  if (engine()->vie()->codec()->RegisterEncoderObserver(
+      channel_id, *send_channel->encoder_observer()) != 0) {
+    LOG_RTCERR1(RegisterEncoderObserver, send_channel->encoder_observer());
+    return false;
+  }
+
+  if (!SetHeaderExtension(&webrtc::ViERTP_RTCP::SetSendTimestampOffsetStatus,
+      channel_id, send_extensions_, kRtpTimestampOffsetHeaderExtension)) {
+    return false;
+  }
+
+  if (!SetHeaderExtension(&webrtc::ViERTP_RTCP::SetSendAbsoluteSendTimeStatus,
+      channel_id, send_extensions_, kRtpAbsoluteSendTimeHeaderExtension)) {
+    return false;
+  }
+
+  if (options_.video_leaky_bucket.GetWithDefaultIfUnset(false)) {
+    if (engine()->vie()->rtp()->SetTransmissionSmoothingStatus(channel_id,
+                                                               true) != 0) {
+      LOG_RTCERR2(SetTransmissionSmoothingStatus, channel_id, true);
+      return false;
+    }
+  }
+
+  int buffer_latency =
+      options_.buffered_mode_latency.GetWithDefaultIfUnset(
+          cricket::kBufferedModeDisabled);
+  if (buffer_latency != cricket::kBufferedModeDisabled) {
+    if (engine()->vie()->rtp()->SetSenderBufferingMode(
+        channel_id, buffer_latency) != 0) {
+      LOG_RTCERR2(SetSenderBufferingMode, channel_id, buffer_latency);
+    }
+  }
+  // The remb status direction correspond to the RTP stream (and not the RTCP
+  // stream). I.e. if send remb is enabled it means it is receiving remote
+  // rembs and should use them to estimate bandwidth. Receive remb mean that
+  // remb packets will be generated and that the channel should be included in
+  // it. If remb is enabled all channels are allowed to contribute to the remb
+  // but only receive channels will ever end up actually contributing. This
+  // keeps the logic simple.
+  if (engine_->vie()->rtp()->SetRembStatus(channel_id,
+                                           remb_enabled_,
+                                           remb_enabled_) != 0) {
+    LOG_RTCERR3(SetRembStatus, channel_id, remb_enabled_, remb_enabled_);
+    return false;
+  }
+  if (!SetNackFec(channel_id, send_red_type_, send_fec_type_, nack_enabled_)) {
+    // Logged in SetNackFec. Don't spam the logs.
+    return false;
+  }
+
+  send_channels_[local_ssrc_key] = send_channel.release();
+
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::SetNackFec(int channel_id,
+                                         int red_payload_type,
+                                         int fec_payload_type,
+                                         bool nack_enabled) {
+  bool enable = (red_payload_type != -1 && fec_payload_type != -1 &&
+      !InConferenceMode());
+  if (enable) {
+    if (engine_->vie()->rtp()->SetHybridNACKFECStatus(
+        channel_id, nack_enabled, red_payload_type, fec_payload_type) != 0) {
+      LOG_RTCERR4(SetHybridNACKFECStatus,
+                  channel_id, nack_enabled, 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, nack_enabled) != 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) {
+  bool ret_val = true;
+  for (SendChannelMap::iterator iter = send_channels_.begin();
+       iter != send_channels_.end(); ++iter) {
+    WebRtcVideoChannelSendInfo* send_channel = iter->second;
+    ret_val = SetSendCodec(send_channel, codec, min_bitrate, start_bitrate,
+                           max_bitrate) && ret_val;
+  }
+  if (ret_val) {
+    // All SetSendCodec calls were successful. Update the global state
+    // accordingly.
+    send_codec_.reset(new webrtc::VideoCodec(codec));
+    send_min_bitrate_ = min_bitrate;
+    send_start_bitrate_ = start_bitrate;
+    send_max_bitrate_ = max_bitrate;
+  } else {
+    // At least one SetSendCodec call failed, rollback.
+    for (SendChannelMap::iterator iter = send_channels_.begin();
+         iter != send_channels_.end(); ++iter) {
+      WebRtcVideoChannelSendInfo* send_channel = iter->second;
+      if (send_codec_) {
+        SetSendCodec(send_channel, *send_codec_.get(), send_min_bitrate_,
+                     send_start_bitrate_, send_max_bitrate_);
+      }
+    }
+  }
+  return ret_val;
+}
+
+bool WebRtcVideoMediaChannel::SetSendCodec(
+    WebRtcVideoChannelSendInfo* send_channel,
+    const webrtc::VideoCodec& codec,
+    int min_bitrate,
+    int start_bitrate,
+    int max_bitrate) {
+  if (!send_channel) {
+    return false;
+  }
+  const int channel_id = send_channel->channel_id();
+  // 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;
+
+  // Set the default number of temporal layers for VP8.
+  if (webrtc::kVideoCodecVP8 == codec.codecType) {
+    target_codec.codecSpecific.VP8.numberOfTemporalLayers =
+        kDefaultNumberOfTemporalLayers;
+
+    // Turn off the VP8 error resilience
+    target_codec.codecSpecific.VP8.resilience = webrtc::kResilienceOff;
+
+    bool enable_denoising =
+        options_.video_noise_reduction.GetWithDefaultIfUnset(false);
+    target_codec.codecSpecific.VP8.denoisingOn = enable_denoising;
+  }
+
+  // Register external encoder if codec type is supported by encoder factory.
+  if (engine()->IsExternalEncoderCodecType(codec.codecType) &&
+      !send_channel->IsEncoderRegistered(target_codec.plType)) {
+    webrtc::VideoEncoder* encoder =
+        engine()->CreateExternalEncoder(codec.codecType);
+    if (encoder) {
+      if (engine()->vie()->ext_codec()->RegisterExternalSendCodec(
+          channel_id, target_codec.plType, encoder, false) == 0) {
+        send_channel->RegisterEncoder(target_codec.plType, encoder);
+      } else {
+        LOG_RTCERR2(RegisterExternalSendCodec, channel_id, target_codec.plName);
+        engine()->DestroyExternalEncoder(encoder);
+      }
+    }
+  }
+
+  // Resolution and framerate may vary for different send channels.
+  const VideoFormat& video_format = send_channel->video_format();
+  UpdateVideoCodec(video_format, &target_codec);
+
+  if (target_codec.width == 0 && target_codec.height == 0) {
+    const uint32 ssrc = send_channel->stream_params()->first_ssrc();
+    LOG(LS_INFO) << "0x0 resolution selected. Captured frames will be dropped "
+                 << "for ssrc: " << ssrc << ".";
+  } else {
+    MaybeChangeStartBitrate(channel_id, &target_codec);
+    if (0 != engine()->vie()->codec()->SetSendCodec(channel_id, target_codec)) {
+      LOG_RTCERR2(SetSendCodec, channel_id, target_codec.plName);
+      return false;
+    }
+
+  }
+  send_channel->set_interval(
+      cricket::VideoFormat::FpsToInterval(target_codec.maxFramerate));
+  return true;
+}
+
+
+static std::string ToString(webrtc::VideoCodecComplexity complexity) {
+  switch (complexity) {
+    case webrtc::kComplexityNormal:
+      return "normal";
+    case webrtc::kComplexityHigh:
+      return "high";
+    case webrtc::kComplexityHigher:
+      return "higher";
+    case webrtc::kComplexityMax:
+      return "max";
+    default:
+      return "unknown";
+  }
+}
+
+static std::string ToString(webrtc::VP8ResilienceMode resilience) {
+  switch (resilience) {
+    case webrtc::kResilienceOff:
+      return "off";
+    case webrtc::kResilientStream:
+      return "stream";
+    case webrtc::kResilientFrames:
+      return "frames";
+    default:
+      return "unknown";
+  }
+}
+
+void WebRtcVideoMediaChannel::LogSendCodecChange(const std::string& reason) {
+  webrtc::VideoCodec vie_codec;
+  if (engine()->vie()->codec()->GetSendCodec(vie_channel_, vie_codec) != 0) {
+    LOG_RTCERR1(GetSendCodec, vie_channel_);
+    return;
+  }
+
+  LOG(LS_INFO) << reason << " : selected video codec "
+               << vie_codec.plName << "/"
+               << vie_codec.width << "x" << vie_codec.height << "x"
+               << static_cast<int>(vie_codec.maxFramerate) << "fps"
+               << "@" << vie_codec.maxBitrate << "kbps"
+               << " (min=" << vie_codec.minBitrate << "kbps,"
+               << " start=" << vie_codec.startBitrate << "kbps)";
+  LOG(LS_INFO) << "Video max quantization: " << vie_codec.qpMax;
+  if (webrtc::kVideoCodecVP8 == vie_codec.codecType) {
+    LOG(LS_INFO) << "VP8 number of temporal layers: "
+                 << static_cast<int>(
+                     vie_codec.codecSpecific.VP8.numberOfTemporalLayers);
+    LOG(LS_INFO) << "VP8 options : "
+                 << "picture loss indication = "
+                 << vie_codec.codecSpecific.VP8.pictureLossIndicationOn
+                 << ", feedback mode = "
+                 << vie_codec.codecSpecific.VP8.feedbackModeOn
+                 << ", complexity = "
+                 << ToString(vie_codec.codecSpecific.VP8.complexity)
+                 << ", resilience = "
+                 << ToString(vie_codec.codecSpecific.VP8.resilience)
+                 << ", denoising = "
+                 << vie_codec.codecSpecific.VP8.denoisingOn
+                 << ", error concealment = "
+                 << vie_codec.codecSpecific.VP8.errorConcealmentOn
+                 << ", automatic resize = "
+                 << vie_codec.codecSpecific.VP8.automaticResizeOn
+                 << ", frame dropping = "
+                 << vie_codec.codecSpecific.VP8.frameDroppingOn
+                 << ", key frame interval = "
+                 << vie_codec.codecSpecific.VP8.keyFrameInterval;
+  }
+
+}
+
+bool WebRtcVideoMediaChannel::SetReceiveCodecs(
+    WebRtcVideoChannelRecvInfo* info) {
+  int red_type = -1;
+  int fec_type = -1;
+  int channel_id = info->channel_id();
+  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;
+    }
+    if (!info->IsDecoderRegistered(it->plType) &&
+        it->codecType != webrtc::kVideoCodecRED &&
+        it->codecType != webrtc::kVideoCodecULPFEC) {
+      webrtc::VideoDecoder* decoder =
+          engine()->CreateExternalDecoder(it->codecType);
+      if (decoder) {
+        if (engine()->vie()->ext_codec()->RegisterExternalReceiveCodec(
+            channel_id, it->plType, decoder) == 0) {
+          info->RegisterDecoder(it->plType, decoder);
+        } else {
+          LOG_RTCERR2(RegisterExternalReceiveCodec, channel_id, it->plName);
+          engine()->DestroyExternalDecoder(decoder);
+        }
+      }
+    }
+  }
+
+  // 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;
+}
+
+int WebRtcVideoMediaChannel::GetRecvChannelNum(uint32 ssrc) {
+  if (ssrc == first_receive_ssrc_) {
+    return vie_channel_;
+  }
+  RecvChannelMap::iterator it = recv_channels_.find(ssrc);
+  return (it != recv_channels_.end()) ? it->second->channel_id() : -1;
+}
+
+// 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(
+    WebRtcVideoChannelSendInfo* send_channel,
+    int new_width,
+    int new_height,
+    bool is_screencast,
+    bool* reset) {
+  if (reset) {
+    *reset = false;
+  }
+  ASSERT(send_codec_.get() != NULL);
+
+  webrtc::VideoCodec target_codec = *send_codec_.get();
+  const VideoFormat& video_format = send_channel->video_format();
+  UpdateVideoCodec(video_format, &target_codec);
+
+  // Vie send codec size should not exceed target_codec.
+  int target_width = new_width;
+  int target_height = new_height;
+  if (!is_screencast &&
+      (new_width > target_codec.width || new_height > target_codec.height)) {
+    target_width = target_codec.width;
+    target_height = target_codec.height;
+  }
+
+  // Get current vie codec.
+  webrtc::VideoCodec vie_codec;
+  const int channel_id = send_channel->channel_id();
+  if (engine()->vie()->codec()->GetSendCodec(channel_id, vie_codec) != 0) {
+    LOG_RTCERR1(GetSendCodec, channel_id);
+    return false;
+  }
+  const int cur_width = vie_codec.width;
+  const int cur_height = vie_codec.height;
+
+  // Only reset send codec when there is a size change. Additionally,
+  // automatic resize needs to be turned off when screencasting and on when
+  // not screencasting.
+  // Don't allow automatic resizing for screencasting.
+  bool automatic_resize = !is_screencast;
+  // Turn off VP8 frame dropping when screensharing as the current model does
+  // not work well at low fps.
+  bool vp8_frame_dropping = !is_screencast;
+  // Disable denoising for screencasting.
+  bool enable_denoising =
+      options_.video_noise_reduction.GetWithDefaultIfUnset(false);
+  bool denoising = !is_screencast && enable_denoising;
+  bool reset_send_codec =
+      target_width != cur_width || target_height != cur_height ||
+      automatic_resize != vie_codec.codecSpecific.VP8.automaticResizeOn ||
+      denoising != vie_codec.codecSpecific.VP8.denoisingOn ||
+      vp8_frame_dropping != vie_codec.codecSpecific.VP8.frameDroppingOn;
+
+  if (reset_send_codec) {
+    // Set the new codec on vie.
+    vie_codec.width = target_width;
+    vie_codec.height = target_height;
+    vie_codec.maxFramerate = target_codec.maxFramerate;
+    vie_codec.startBitrate = target_codec.startBitrate;
+    vie_codec.codecSpecific.VP8.automaticResizeOn = automatic_resize;
+    vie_codec.codecSpecific.VP8.denoisingOn = denoising;
+    vie_codec.codecSpecific.VP8.frameDroppingOn = vp8_frame_dropping;
+    // TODO(mflodman): Remove 'is_screencast' check when screen cast settings
+    // are treated correctly in WebRTC.
+    if (!is_screencast)
+      MaybeChangeStartBitrate(channel_id, &vie_codec);
+
+    if (engine()->vie()->codec()->SetSendCodec(channel_id, vie_codec) != 0) {
+      LOG_RTCERR1(SetSendCodec, channel_id);
+      return false;
+    }
+    if (reset) {
+      *reset = true;
+    }
+    LogSendCodecChange("Capture size changed");
+  }
+
+  return true;
+}
+
+void WebRtcVideoMediaChannel::MaybeChangeStartBitrate(
+  int channel_id, webrtc::VideoCodec* video_codec) {
+  if (video_codec->startBitrate < video_codec->minBitrate) {
+    video_codec->startBitrate = video_codec->minBitrate;
+  } else if (video_codec->startBitrate > video_codec->maxBitrate) {
+    video_codec->startBitrate = video_codec->maxBitrate;
+  }
+
+  // Use a previous target bitrate, if there is one.
+  unsigned int current_target_bitrate = 0;
+  if (engine()->vie()->codec()->GetCodecTargetBitrate(
+      channel_id, &current_target_bitrate) == 0) {
+    // Convert to kbps.
+    current_target_bitrate /= 1000;
+    if (current_target_bitrate > video_codec->maxBitrate) {
+      current_target_bitrate = video_codec->maxBitrate;
+    }
+    if (current_target_bitrate > video_codec->startBitrate) {
+      video_codec->startBitrate = current_target_bitrate;
+    }
+  }
+}
+
+void WebRtcVideoMediaChannel::OnMessage(talk_base::Message* msg) {
+  FlushBlackFrameData* black_frame_data =
+      static_cast<FlushBlackFrameData*> (msg->pdata);
+  FlushBlackFrame(black_frame_data->ssrc, black_frame_data->timestamp);
+  delete black_frame_data;
+}
+
+int WebRtcVideoMediaChannel::SendPacket(int channel, const void* data,
+                                        int len) {
+  if (!network_interface_) {
+    return -1;
+  }
+  talk_base::Buffer packet(data, len, kMaxRtpPacketLen);
+  return network_interface_->SendPacket(&packet) ? len : -1;
+}
+
+int WebRtcVideoMediaChannel::SendRTCPPacket(int channel,
+                                            const void* data,
+                                            int len) {
+  if (!network_interface_) {
+    return -1;
+  }
+  talk_base::Buffer packet(data, len, kMaxRtpPacketLen);
+  return network_interface_->SendRtcp(&packet) ? len : -1;
+}
+
+void WebRtcVideoMediaChannel::QueueBlackFrame(uint32 ssrc, int64 timestamp,
+                                              int framerate) {
+  if (timestamp) {
+    FlushBlackFrameData* black_frame_data = new FlushBlackFrameData(
+        ssrc,
+        timestamp);
+    const int delay_ms = static_cast<int> (
+        2 * cricket::VideoFormat::FpsToInterval(framerate) *
+        talk_base::kNumMillisecsPerSec / talk_base::kNumNanosecsPerSec);
+    worker_thread()->PostDelayed(delay_ms, this, 0, black_frame_data);
+  }
+}
+
+void WebRtcVideoMediaChannel::FlushBlackFrame(uint32 ssrc, int64 timestamp) {
+  WebRtcVideoChannelSendInfo* send_channel = GetSendChannel(ssrc);
+  if (!send_channel) {
+    return;
+  }
+  talk_base::scoped_ptr<const VideoFrame> black_frame_ptr;
+
+  const WebRtcLocalStreamInfo* channel_stream_info =
+      send_channel->local_stream_info();
+  int64 last_frame_time_stamp = channel_stream_info->time_stamp();
+  if (last_frame_time_stamp == timestamp) {
+    size_t last_frame_width = 0;
+    size_t last_frame_height = 0;
+    int64 last_frame_elapsed_time = 0;
+    channel_stream_info->GetLastFrameInfo(&last_frame_width, &last_frame_height,
+                                          &last_frame_elapsed_time);
+    if (!last_frame_width || !last_frame_height) {
+      return;
+    }
+    WebRtcVideoFrame black_frame;
+    // Black frame is not screencast.
+    const bool screencasting = false;
+    const int64 timestamp_delta = send_channel->interval();
+    if (!black_frame.InitToBlack(send_codec_->width, send_codec_->height, 1, 1,
+                                 last_frame_elapsed_time + timestamp_delta,
+                                 last_frame_time_stamp + timestamp_delta) ||
+        !SendFrame(send_channel, &black_frame, screencasting)) {
+      LOG(LS_ERROR) << "Failed to send black frame.";
+    }
+  }
+}
+
+void WebRtcVideoMediaChannel::SetNetworkTransmissionState(
+    bool is_transmitting) {
+  LOG(LS_INFO) << "SetNetworkTransmissionState: " << is_transmitting;
+  for (SendChannelMap::iterator iter = send_channels_.begin();
+       iter != send_channels_.end(); ++iter) {
+    WebRtcVideoChannelSendInfo* send_channel = iter->second;
+    int channel_id = send_channel->channel_id();
+    engine_->vie()->network()->SetNetworkTransmissionState(channel_id,
+                                                           is_transmitting);
+  }
+}
+
+bool WebRtcVideoMediaChannel::SetHeaderExtension(ExtensionSetterFunction setter,
+    int channel_id, const RtpHeaderExtension* extension) {
+  bool enable = false;
+  int id = 0;
+  if (extension) {
+    enable = true;
+    id = extension->id;
+  }
+  if ((engine_->vie()->rtp()->*setter)(channel_id, enable, id) != 0) {
+    LOG_RTCERR4(*setter, extension->uri, channel_id, enable, id);
+    return false;
+  }
+  return true;
+}
+
+bool WebRtcVideoMediaChannel::SetHeaderExtension(ExtensionSetterFunction setter,
+    int channel_id, const std::vector<RtpHeaderExtension>& extensions,
+    const char header_extension_uri[]) {
+  const RtpHeaderExtension* extension = FindHeaderExtension(extensions,
+      header_extension_uri);
+  return SetHeaderExtension(setter, channel_id, extension);
+}
+}  // namespace cricket
+
+#endif  // HAVE_WEBRTC_VIDEO
diff --git a/talk/media/webrtc/webrtcvideoengine.h b/talk/media/webrtc/webrtcvideoengine.h
new file mode 100644
index 0000000..f2dc18c
--- /dev/null
+++ b/talk/media/webrtc/webrtcvideoengine.h
@@ -0,0 +1,456 @@
+/*
+ * 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_MEDIA_WEBRTCVIDEOENGINE_H_
+#define TALK_MEDIA_WEBRTCVIDEOENGINE_H_
+
+#include <map>
+#include <vector>
+
+#include "talk/base/scoped_ptr.h"
+#include "talk/media/base/codec.h"
+#include "talk/media/base/videocommon.h"
+#include "talk/media/webrtc/webrtccommon.h"
+#include "talk/media/webrtc/webrtcexport.h"
+#include "talk/media/webrtc/webrtcvideoencoderfactory.h"
+#include "talk/session/media/channel.h"
+#include "webrtc/video_engine/include/vie_base.h"
+
+#if !defined(LIBPEERCONNECTION_LIB) && \
+    !defined(LIBPEERCONNECTION_IMPLEMENTATION)
+#error "Bogus include."
+#endif
+
+namespace webrtc {
+class VideoCaptureModule;
+class VideoDecoder;
+class VideoEncoder;
+class VideoRender;
+class ViEExternalCapture;
+class ViERTP_RTCP;
+}
+
+namespace talk_base {
+class CpuMonitor;
+}  // namespace talk_base
+
+namespace cricket {
+
+class VideoCapturer;
+class VideoFrame;
+class VideoProcessor;
+class VideoRenderer;
+class ViETraceWrapper;
+class ViEWrapper;
+class VoiceMediaChannel;
+class WebRtcDecoderObserver;
+class WebRtcEncoderObserver;
+class WebRtcLocalStreamInfo;
+class WebRtcRenderAdapter;
+class WebRtcVideoChannelRecvInfo;
+class WebRtcVideoChannelSendInfo;
+class WebRtcVideoDecoderFactory;
+class WebRtcVideoEncoderFactory;
+class WebRtcVideoMediaChannel;
+class WebRtcVoiceEngine;
+
+struct CapturedFrame;
+struct Device;
+
+class WebRtcVideoEngine : public sigslot::has_slots<>,
+                          public webrtc::TraceCallback,
+                          public WebRtcVideoEncoderFactory::Observer {
+ public:
+  // Creates the WebRtcVideoEngine with internal VideoCaptureModule.
+  WebRtcVideoEngine();
+  // For testing purposes. Allows the WebRtcVoiceEngine,
+  // ViEWrapper and CpuMonitor to be mocks.
+  // TODO(juberti): Remove the 3-arg ctor once fake tracing is implemented.
+  WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine,
+                    ViEWrapper* vie_wrapper,
+                    talk_base::CpuMonitor* cpu_monitor);
+  WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine,
+                    ViEWrapper* vie_wrapper,
+                    ViETraceWrapper* tracing,
+                    talk_base::CpuMonitor* cpu_monitor);
+  ~WebRtcVideoEngine();
+
+  // Basic video engine implementation.
+  bool Init(talk_base::Thread* worker_thread);
+  void Terminate();
+
+  int GetCapabilities();
+  bool SetOptions(int options);
+  bool SetDefaultEncoderConfig(const VideoEncoderConfig& config);
+
+  WebRtcVideoMediaChannel* CreateChannel(VoiceMediaChannel* voice_channel);
+
+  const std::vector<VideoCodec>& codecs() const;
+  const std::vector<RtpHeaderExtension>& rtp_header_extensions() const;
+  void SetLogging(int min_sev, const char* filter);
+
+  // If capturer is NULL, unregisters the capturer and stops capturing.
+  // Otherwise sets the capturer and starts capturing.
+  bool SetVideoCapturer(VideoCapturer* capturer);
+  VideoCapturer* GetVideoCapturer() const;
+  bool SetLocalRenderer(VideoRenderer* renderer);
+  bool SetCapture(bool capture);
+  sigslot::repeater2<VideoCapturer*, CaptureState> SignalCaptureStateChange;
+  CaptureState 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);
+  // Set a WebRtcVideoDecoderFactory for external decoding. Video engine does
+  // not take the ownership of |decoder_factory|. The caller needs to make sure
+  // that |decoder_factory| outlives the video engine.
+  void SetExternalDecoderFactory(WebRtcVideoDecoderFactory* decoder_factory);
+  // Set a WebRtcVideoEncoderFactory for external encoding. Video engine does
+  // not take the ownership of |encoder_factory|. The caller needs to make sure
+  // that |encoder_factory| outlives the video engine.
+  void SetExternalEncoderFactory(WebRtcVideoEncoderFactory* encoder_factory);
+  // Enable the render module with timing control.
+  bool EnableTimedRender();
+
+  bool RegisterProcessor(VideoProcessor* video_processor);
+  bool UnregisterProcessor(VideoProcessor* video_processor);
+
+  // Returns an external decoder for the given codec type. The return value
+  // can be NULL if decoder factory is not given or it does not support the
+  // codec type. The caller takes the ownership of the returned object.
+  webrtc::VideoDecoder* CreateExternalDecoder(webrtc::VideoCodecType type);
+  // Releases the decoder instance created by CreateExternalDecoder().
+  void DestroyExternalDecoder(webrtc::VideoDecoder* decoder);
+
+  // Returns an external encoder for the given codec type. The return value
+  // can be NULL if encoder factory is not given or it does not support the
+  // codec type. The caller takes the ownership of the returned object.
+  webrtc::VideoEncoder* CreateExternalEncoder(webrtc::VideoCodecType type);
+  // Releases the encoder instance created by CreateExternalEncoder().
+  void DestroyExternalEncoder(webrtc::VideoEncoder* encoder);
+
+  // Returns true if the codec type is supported by the external encoder.
+  bool IsExternalEncoderCodecType(webrtc::VideoCodecType type) const;
+
+  // Functions called by WebRtcVideoMediaChannel.
+  talk_base::Thread* worker_thread() { return worker_thread_; }
+  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);
+  bool ConvertFromCricketVideoCodec(const VideoCodec& in_codec,
+                                    webrtc::VideoCodec* out_codec);
+  // Check whether the supplied trace should be ignored.
+  bool ShouldIgnoreTrace(const std::string& trace);
+  int GetNumOfChannels();
+
+  void IncrementFrameListeners();
+  void DecrementFrameListeners();
+
+  VideoFormat GetStartCaptureFormat() const { return default_codec_format_; }
+
+  talk_base::CpuMonitor* cpu_monitor() { return cpu_monitor_.get(); }
+
+ protected:
+  // When a video processor registers with the engine.
+  // SignalMediaFrame will be invoked for every video frame.
+  // See videoprocessor.h for param reference.
+  sigslot::signal3<uint32, VideoFrame*, bool*> SignalMediaFrame;
+
+ private:
+  typedef std::vector<WebRtcVideoMediaChannel*> VideoChannels;
+  struct VideoCodecPref {
+    const char* name;
+    int payload_type;
+    int pref;
+  };
+
+  static const VideoCodecPref kVideoCodecPrefs[];
+  static const VideoFormatPod kVideoFormats[];
+  static const VideoFormatPod kDefaultVideoFormat;
+
+  void Construct(ViEWrapper* vie_wrapper,
+                 ViETraceWrapper* tracing,
+                 WebRtcVoiceEngine* voice_engine,
+                 talk_base::CpuMonitor* cpu_monitor);
+  bool SetDefaultCodec(const VideoCodec& codec);
+  bool RebuildCodecList(const VideoCodec& max_codec);
+  void SetTraceFilter(int filter);
+  void SetTraceOptions(const std::string& options);
+  bool InitVideoEngine();
+  bool SetCapturer(VideoCapturer* capturer);
+
+  // webrtc::TraceCallback implementation.
+  virtual void Print(webrtc::TraceLevel level, const char* trace, int length);
+  void ClearCapturer();
+
+  // WebRtcVideoEncoderFactory::Observer implementation.
+  virtual void OnCodecsAvailable();
+
+  talk_base::Thread* worker_thread_;
+  talk_base::scoped_ptr<ViEWrapper> vie_wrapper_;
+  bool vie_wrapper_base_initialized_;
+  talk_base::scoped_ptr<ViETraceWrapper> tracing_;
+  WebRtcVoiceEngine* voice_engine_;
+  talk_base::scoped_ptr<webrtc::VideoRender> render_module_;
+  WebRtcVideoEncoderFactory* encoder_factory_;
+  WebRtcVideoDecoderFactory* decoder_factory_;
+  std::vector<VideoCodec> video_codecs_;
+  std::vector<RtpHeaderExtension> rtp_header_extensions_;
+  VideoFormat default_codec_format_;
+
+  bool initialized_;
+  talk_base::CriticalSection channels_crit_;
+  VideoChannels channels_;
+
+  VideoCapturer* video_capturer_;
+  int frame_listeners_;
+  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_;
+
+  talk_base::scoped_ptr<talk_base::CpuMonitor> cpu_monitor_;
+};
+
+class WebRtcVideoMediaChannel : public talk_base::MessageHandler,
+                                public VideoMediaChannel,
+                                public webrtc::Transport {
+ public:
+  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 GetSendCodec(VideoCodec* send_codec);
+  virtual bool SetSendStreamFormat(uint32 ssrc, const VideoFormat& format);
+  virtual bool SetRender(bool render);
+  virtual bool SetSend(bool send);
+
+  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 SetCapturer(uint32 ssrc, VideoCapturer* capturer);
+  virtual bool SendIntraFrame();
+  virtual bool RequestIntraFrame();
+
+  virtual void OnPacketReceived(talk_base::Buffer* packet);
+  virtual void OnRtcpReceived(talk_base::Buffer* packet);
+  virtual void OnReadyToSend(bool ready);
+  virtual bool MuteStream(uint32 ssrc, bool on);
+  virtual bool SetRecvRtpHeaderExtensions(
+      const std::vector<RtpHeaderExtension>& extensions);
+  virtual bool SetSendRtpHeaderExtensions(
+      const std::vector<RtpHeaderExtension>& extensions);
+  virtual bool SetSendBandwidth(bool autobw, int bps);
+  virtual bool SetOptions(const VideoOptions &options);
+  virtual bool GetOptions(VideoOptions *options) const {
+    *options = options_;
+    return true;
+  }
+  virtual void SetInterface(NetworkInterface* iface);
+  virtual void UpdateAspectRatio(int ratio_w, int ratio_h);
+
+  // Public functions for use by tests and other specialized code.
+  uint32 send_ssrc() const { return 0; }
+  bool GetRenderer(uint32 ssrc, VideoRenderer** renderer);
+  void SendFrame(VideoCapturer* capturer, const VideoFrame* frame);
+  bool SendFrame(WebRtcVideoChannelSendInfo* channel_info,
+                 const VideoFrame* frame, bool is_screencast);
+
+  void AdaptAndSendFrame(VideoCapturer* capturer, const VideoFrame* frame);
+
+  // Thunk functions for use with HybridVideoEngine
+  void OnLocalFrame(VideoCapturer* capturer, const VideoFrame* frame) {
+    SendFrame(0u, frame, capturer->IsScreencast());
+  }
+  void OnLocalFrameFormat(VideoCapturer* capturer, const VideoFormat* format) {
+  }
+
+  virtual void OnMessage(talk_base::Message* msg);
+
+ protected:
+  int GetLastEngineError() { return engine()->GetLastEngineError(); }
+  virtual int SendPacket(int channel, const void* data, int len);
+  virtual int SendRTCPPacket(int channel, const void* data, int len);
+
+ private:
+  typedef std::map<uint32, WebRtcVideoChannelRecvInfo*> RecvChannelMap;
+  typedef std::map<uint32, WebRtcVideoChannelSendInfo*> SendChannelMap;
+  typedef int (webrtc::ViERTP_RTCP::* ExtensionSetterFunction)(int, bool, int);
+
+  enum MediaDirection { MD_RECV, MD_SEND, MD_SENDRECV };
+
+  // Creates and initializes a ViE channel. When successful |channel_id| will
+  // contain the new channel's ID. If |receiving| is true |ssrc| is the
+  // remote ssrc. If |sending| is true the ssrc is local ssrc. If both
+  // |receiving| and |sending| is true the ssrc must be 0 and the channel will
+  // be created as a default channel. The ssrc must be different for receive
+  // channels and it must be different for send channels. If the same SSRC is
+  // being used for creating channel more than once, this function will fail
+  // returning false.
+  bool CreateChannel(uint32 ssrc_key, MediaDirection direction,
+                     int* channel_id);
+  bool ConfigureChannel(int channel_id, MediaDirection direction,
+                        uint32 ssrc_key);
+  bool ConfigureReceiving(int channel_id, uint32 remote_ssrc_key);
+  bool ConfigureSending(int channel_id, uint32 local_ssrc_key);
+  bool SetNackFec(int channel_id, int red_payload_type, int fec_payload_type,
+                  bool nack_enabled);
+  bool SetSendCodec(const webrtc::VideoCodec& codec, int min_bitrate,
+                    int start_bitrate, int max_bitrate);
+  bool SetSendCodec(WebRtcVideoChannelSendInfo* send_channel,
+                    const webrtc::VideoCodec& codec, int min_bitrate,
+                    int start_bitrate, int max_bitrate);
+  void LogSendCodecChange(const std::string& reason);
+  // Prepares the channel with channel id |info->channel_id()| to receive all
+  // codecs in |receive_codecs_| and start receive packets.
+  bool SetReceiveCodecs(WebRtcVideoChannelRecvInfo* info);
+  // Returns the channel number that receives the stream with SSRC |ssrc|.
+  int GetRecvChannelNum(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(WebRtcVideoChannelSendInfo* send_channel,
+                              int new_width, int new_height, bool is_screencast,
+                              bool* reset);
+  // Checks the current bitrate estimate and modifies the start bitrate
+  // accordingly.
+  void MaybeChangeStartBitrate(int channel_id, webrtc::VideoCodec* video_codec);
+  // Helper function for starting the sending of media on all channels or
+  // |channel_id|. Note that these two function do not change |sending_|.
+  bool StartSend();
+  bool StartSend(WebRtcVideoChannelSendInfo* send_channel);
+  // Helper function for stop the sending of media on all channels or
+  // |channel_id|. Note that these two function do not change |sending_|.
+  bool StopSend();
+  bool StopSend(WebRtcVideoChannelSendInfo* send_channel);
+  bool SendIntraFrame(int channel_id);
+
+  // Send with one local SSRC. Normal case.
+  bool IsOneSsrcStream(const StreamParams& sp);
+
+  bool HasReadySendChannels();
+
+  // Send channel key returns the key corresponding to the provided local SSRC
+  // in |key|. The return value is true upon success.
+  // If the local ssrc correspond to that of the default channel the key is 0.
+  // For all other channels the returned key will be the same as the local ssrc.
+  bool GetSendChannelKey(uint32 local_ssrc, uint32* key);
+  WebRtcVideoChannelSendInfo* GetSendChannel(VideoCapturer* video_capturer);
+  WebRtcVideoChannelSendInfo* GetSendChannel(uint32 local_ssrc);
+  // Creates a new unique key that can be used for inserting a new send channel
+  // into |send_channels_|
+  bool CreateSendChannelKey(uint32 local_ssrc, uint32* key);
+
+  bool IsDefaultChannel(int channel_id) const {
+    return channel_id == vie_channel_;
+  }
+  uint32 GetDefaultChannelSsrc();
+
+  bool DeleteSendChannel(uint32 ssrc_key);
+
+  bool InConferenceMode() const {
+    return options_.conference_mode.GetWithDefaultIfUnset(false);
+  }
+  bool RemoveCapturer(uint32 ssrc);
+
+
+  talk_base::MessageQueue* worker_thread() { return engine_->worker_thread(); }
+  void QueueBlackFrame(uint32 ssrc, int64 timestamp, int framerate);
+  void FlushBlackFrame(uint32 ssrc, int64 timestamp);
+
+  void SetNetworkTransmissionState(bool is_transmitting);
+
+  bool SetHeaderExtension(ExtensionSetterFunction setter, int channel_id,
+                          const RtpHeaderExtension* extension);
+  bool SetHeaderExtension(ExtensionSetterFunction setter, int channel_id,
+                          const std::vector<RtpHeaderExtension>& extensions,
+                          const char header_extension_uri[]);
+
+  // Global state.
+  WebRtcVideoEngine* engine_;
+  VoiceMediaChannel* voice_channel_;
+  int vie_channel_;
+  bool nack_enabled_;
+  // Receiver Estimated Max Bitrate
+  bool remb_enabled_;
+  VideoOptions options_;
+
+  // Global recv side state.
+  // Note the default channel (vie_channel_), i.e. the send channel
+  // corresponding to all the receive channels (this must be done for REMB to
+  // work properly), resides in both recv_channels_ and send_channels_ with the
+  // ssrc key 0.
+  RecvChannelMap recv_channels_;  // Contains all receive channels.
+  std::vector<webrtc::VideoCodec> receive_codecs_;
+  bool render_started_;
+  uint32 first_receive_ssrc_;
+  std::vector<RtpHeaderExtension> receive_extensions_;
+
+  // Global send side state.
+  SendChannelMap send_channels_;
+  talk_base::scoped_ptr<webrtc::VideoCodec> send_codec_;
+  int send_red_type_;
+  int send_fec_type_;
+  int send_min_bitrate_;
+  int send_start_bitrate_;
+  int send_max_bitrate_;
+  bool sending_;
+  std::vector<RtpHeaderExtension> send_extensions_;
+
+  // The aspect ratio that the channel desires. 0 means there is no desired
+  // aspect ratio
+  int ratio_w_;
+  int ratio_h_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_WEBRTCVIDEOENGINE_H_
diff --git a/talk/media/webrtc/webrtcvideoengine_unittest.cc b/talk/media/webrtc/webrtcvideoengine_unittest.cc
new file mode 100644
index 0000000..37b212f
--- /dev/null
+++ b/talk/media/webrtc/webrtcvideoengine_unittest.cc
@@ -0,0 +1,1882 @@
+/*
+ * 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/fakecpumonitor.h"
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stream.h"
+#include "talk/media/base/constants.h"
+#include "talk/media/base/fakemediaprocessor.h"
+#include "talk/media/base/fakenetworkinterface.h"
+#include "talk/media/base/fakevideorenderer.h"
+#include "talk/media/base/mediachannel.h"
+#include "talk/media/base/testutils.h"
+#include "talk/media/base/videoengine_unittest.h"
+#include "talk/media/webrtc/fakewebrtcvideocapturemodule.h"
+#include "talk/media/webrtc/fakewebrtcvideoengine.h"
+#include "talk/media/webrtc/fakewebrtcvoiceengine.h"
+#include "talk/media/webrtc/webrtcvideocapturer.h"
+#include "talk/media/webrtc/webrtcvideoengine.h"
+#include "talk/media/webrtc/webrtcvideoframe.h"
+#include "talk/media/webrtc/webrtcvoiceengine.h"
+#include "talk/session/media/mediasession.h"
+#include "webrtc/system_wrappers/interface/trace.h"
+
+// Tests for the WebRtcVideoEngine/VideoChannel code.
+
+static const cricket::VideoCodec kVP8Codec720p(100, "VP8", 1280, 720, 30, 0);
+static const cricket::VideoCodec kVP8Codec360p(100, "VP8", 640, 360, 30, 0);
+static const cricket::VideoCodec kVP8Codec270p(100, "VP8", 480, 270, 30, 0);
+static const cricket::VideoCodec kVP8Codec180p(100, "VP8", 320, 180, 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 = 50;
+static const unsigned int kStartBandwidthKbps = 300;
+static const unsigned int kMaxBandwidthKbps = 2000;
+
+static const unsigned int kNumberOfTemporalLayers = 1;
+
+
+class FakeViEWrapper : public cricket::ViEWrapper {
+ public:
+  explicit FakeViEWrapper(cricket::FakeWebRtcVideoEngine* engine)
+      : cricket::ViEWrapper(engine,  // base
+                            engine,  // codec
+                            engine,  // capture
+                            engine,  // network
+                            engine,  // render
+                            engine,  // rtp
+                            engine,  // image
+                            engine) {  // external decoder
+  }
+};
+
+// Test fixture to test WebRtcVideoEngine with a fake webrtc::VideoEngine.
+// Useful for testing failure paths.
+class WebRtcVideoEngineTestFake : public testing::Test {
+ public:
+  WebRtcVideoEngineTestFake()
+      : vie_(kVideoCodecs, ARRAY_SIZE(kVideoCodecs)),
+        cpu_monitor_(new talk_base::FakeCpuMonitor(
+            talk_base::Thread::Current())),
+        engine_(NULL,  // cricket::WebRtcVoiceEngine
+                new FakeViEWrapper(&vie_), cpu_monitor_),
+        channel_(NULL),
+        voice_channel_(NULL) {
+  }
+  bool SetupEngine() {
+    bool result = engine_.Init(talk_base::Thread::Current());
+    if (result) {
+      channel_ = engine_.CreateChannel(voice_channel_);
+      result = (channel_ != NULL);
+    }
+    return result;
+  }
+  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_array<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;
+    }
+    cricket::FakeVideoCapturer capturer;
+    channel_->SendFrame(&capturer, &frame);
+    return true;
+  }
+  bool SendI420ScreencastFrame(int width, int height) {
+    return SendI420ScreencastFrameWithTimestamp(width, height, 0);
+  }
+  bool SendI420ScreencastFrameWithTimestamp(
+      int width, int height, int64 timestamp) {
+    if (NULL == channel_) {
+      return false;
+    }
+    cricket::WebRtcVideoFrame frame;
+    size_t size = width * height * 3 / 2;  // I420
+    talk_base::scoped_array<uint8> pixel(new uint8[size]);
+    if (!frame.Init(cricket::FOURCC_I420,
+                    width, height, width, height,
+                    pixel.get(), size, 1, 1, 0, timestamp, 0)) {
+      return false;
+    }
+    cricket::FakeVideoCapturer capturer;
+    capturer.SetScreencast(true);
+    channel_->SendFrame(&capturer, &frame);
+    return true;
+  }
+  void VerifyVP8SendCodec(int channel_num,
+                          unsigned int width,
+                          unsigned int height,
+                          unsigned int layers = 0,
+                          unsigned int max_bitrate = kMaxBandwidthKbps,
+                          unsigned int min_bitrate = kMinBandwidthKbps,
+                          unsigned int start_bitrate = kStartBandwidthKbps,
+                          unsigned int fps = 30,
+                          unsigned int max_quantization = 0
+                          ) {
+    webrtc::VideoCodec gcodec;
+    EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+
+    // Video codec properties.
+    EXPECT_EQ(webrtc::kVideoCodecVP8, gcodec.codecType);
+    EXPECT_STREQ("VP8", gcodec.plName);
+    EXPECT_EQ(100, gcodec.plType);
+    EXPECT_EQ(width, gcodec.width);
+    EXPECT_EQ(height, gcodec.height);
+    EXPECT_EQ(talk_base::_min(start_bitrate, max_bitrate), gcodec.startBitrate);
+    EXPECT_EQ(max_bitrate, gcodec.maxBitrate);
+    EXPECT_EQ(min_bitrate, gcodec.minBitrate);
+    EXPECT_EQ(fps, gcodec.maxFramerate);
+    // VP8 specific.
+    EXPECT_FALSE(gcodec.codecSpecific.VP8.pictureLossIndicationOn);
+    EXPECT_FALSE(gcodec.codecSpecific.VP8.feedbackModeOn);
+    EXPECT_EQ(webrtc::kComplexityNormal, gcodec.codecSpecific.VP8.complexity);
+    EXPECT_EQ(webrtc::kResilienceOff, gcodec.codecSpecific.VP8.resilience);
+    EXPECT_EQ(max_quantization, gcodec.qpMax);
+  }
+  virtual void TearDown() {
+    delete channel_;
+    engine_.Terminate();
+  }
+
+ protected:
+  cricket::FakeWebRtcVideoEngine vie_;
+  cricket::FakeWebRtcVideoDecoderFactory decoder_factory_;
+  cricket::FakeWebRtcVideoEncoderFactory encoder_factory_;
+  talk_base::FakeCpuMonitor* cpu_monitor_;
+  cricket::WebRtcVideoEngine engine_;
+  cricket::WebRtcVideoMediaChannel* channel_;
+  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(WebRtcVideoEngineTestFake, StartupShutdown) {
+  EXPECT_FALSE(vie_.IsInited());
+  EXPECT_TRUE(engine_.Init(talk_base::Thread::Current()));
+  EXPECT_TRUE(vie_.IsInited());
+  engine_.Terminate();
+}
+
+// Tests that webrtc logs are logged when they should be.
+TEST_F(WebRtcVideoEngineTest, WebRtcShouldLog) {
+  const char webrtc_log[] = "WebRtcVideoEngineTest.WebRtcShouldLog";
+  EXPECT_TRUE(engine_.Init(talk_base::Thread::Current()));
+  engine_.SetLogging(talk_base::LS_INFO, "");
+  std::string str;
+  talk_base::StringStream stream(str);
+  talk_base::LogMessage::AddLogToStream(&stream, talk_base::LS_INFO);
+  EXPECT_EQ(talk_base::LS_INFO, talk_base::LogMessage::GetLogToStream(&stream));
+  webrtc::Trace::Add(webrtc::kTraceStateInfo, webrtc::kTraceUndefined, 0,
+                     webrtc_log);
+  EXPECT_TRUE_WAIT(std::string::npos != str.find(webrtc_log), 10);
+  talk_base::LogMessage::RemoveLogToStream(&stream);
+}
+
+// Tests that webrtc logs are not logged when they should't be.
+TEST_F(WebRtcVideoEngineTest, WebRtcShouldNotLog) {
+  const char webrtc_log[] = "WebRtcVideoEngineTest.WebRtcShouldNotLog";
+  EXPECT_TRUE(engine_.Init(talk_base::Thread::Current()));
+  // WebRTC should never be logged lower than LS_INFO.
+  engine_.SetLogging(talk_base::LS_WARNING, "");
+  std::string str;
+  talk_base::StringStream stream(str);
+  // Make sure that WebRTC is not logged, even at lowest severity
+  talk_base::LogMessage::AddLogToStream(&stream, talk_base::LS_SENSITIVE);
+  EXPECT_EQ(talk_base::LS_SENSITIVE,
+            talk_base::LogMessage::GetLogToStream(&stream));
+  webrtc::Trace::Add(webrtc::kTraceStateInfo, webrtc::kTraceUndefined, 0,
+                     webrtc_log);
+  talk_base::Thread::Current()->ProcessMessages(10);
+  EXPECT_EQ(std::string::npos, str.find(webrtc_log));
+  talk_base::LogMessage::RemoveLogToStream(&stream);
+}
+
+// Tests that we can create and destroy a channel.
+TEST_F(WebRtcVideoEngineTestFake, CreateChannel) {
+  EXPECT_TRUE(engine_.Init(talk_base::Thread::Current()));
+  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(WebRtcVideoEngineTestFake, CreateChannelFail) {
+  vie_.set_fail_create_channel(true);
+  EXPECT_TRUE(engine_.Init(talk_base::Thread::Current()));
+  channel_ = engine_.CreateChannel(voice_channel_);
+  EXPECT_TRUE(channel_ == NULL);
+}
+
+// Tests that we properly handle failures in AllocateExternalCaptureDevice.
+TEST_F(WebRtcVideoEngineTestFake, AllocateExternalCaptureDeviceFail) {
+  vie_.set_fail_alloc_capturer(true);
+  EXPECT_TRUE(engine_.Init(talk_base::Thread::Current()));
+  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));
+  VerifyVP8SendCodec(channel_num, kVP8Codec.width, kVP8Codec.height);
+  EXPECT_TRUE(vie_.GetHybridNackFecStatus(channel_num));
+  EXPECT_FALSE(vie_.GetNackStatus(channel_num));
+  // TODO(juberti): Check RTCP, PLI, TMMBR.
+}
+
+TEST_F(WebRtcVideoEngineTestFake, SetSendCodecsWithMinMaxBitrate) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  std::vector<cricket::VideoCodec> codecs(engine_.codecs());
+  codecs[0].params[cricket::kCodecParamMinBitrate] = "10";
+  codecs[0].params[cricket::kCodecParamMaxBitrate] = "20";
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+
+  VerifyVP8SendCodec(
+      channel_num, kVP8Codec.width, kVP8Codec.height, 0, 20, 10, 20);
+
+  cricket::VideoCodec codec;
+  EXPECT_TRUE(channel_->GetSendCodec(&codec));
+  EXPECT_EQ("10", codec.params[cricket::kCodecParamMinBitrate]);
+  EXPECT_EQ("20", codec.params[cricket::kCodecParamMaxBitrate]);
+}
+
+TEST_F(WebRtcVideoEngineTestFake, SetSendCodecsWithMinMaxBitrateInvalid) {
+  EXPECT_TRUE(SetupEngine());
+  std::vector<cricket::VideoCodec> codecs(engine_.codecs());
+  codecs[0].params[cricket::kCodecParamMinBitrate] = "30";
+  codecs[0].params[cricket::kCodecParamMaxBitrate] = "20";
+  EXPECT_FALSE(channel_->SetSendCodecs(codecs));
+}
+
+TEST_F(WebRtcVideoEngineTestFake, SetSendCodecsWithLargeMinMaxBitrate) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  std::vector<cricket::VideoCodec> codecs(engine_.codecs());
+  codecs[0].params[cricket::kCodecParamMinBitrate] = "1000";
+  codecs[0].params[cricket::kCodecParamMaxBitrate] = "2000";
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+
+  VerifyVP8SendCodec(
+      channel_num, kVP8Codec.width, kVP8Codec.height, 0, 2000, 1000,
+      1000);
+}
+
+TEST_F(WebRtcVideoEngineTestFake, SetSendCodecsWithMaxQuantization) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  std::vector<cricket::VideoCodec> codecs(engine_.codecs());
+  codecs[0].params[cricket::kCodecParamMaxQuantization] = "21";
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+
+  VerifyVP8SendCodec(
+      channel_num, kVP8Codec.width, kVP8Codec.height, 0, 2000, 50, 300,
+      30, 21);
+
+  cricket::VideoCodec codec;
+  EXPECT_TRUE(channel_->GetSendCodec(&codec));
+  EXPECT_EQ("21", codec.params[cricket::kCodecParamMaxQuantization]);
+}
+
+TEST_F(WebRtcVideoEngineTestFake, SetOptionsWithMaxBitrate) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  std::vector<cricket::VideoCodec> codecs(engine_.codecs());
+  codecs[0].params[cricket::kCodecParamMinBitrate] = "10";
+  codecs[0].params[cricket::kCodecParamMaxBitrate] = "20";
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+
+  VerifyVP8SendCodec(
+      channel_num, kVP8Codec.width, kVP8Codec.height, 0, 20, 10, 20);
+
+  // Verify that max bitrate doesn't change after SetOptions().
+  cricket::VideoOptions options;
+  options.video_noise_reduction.Set(true);
+  EXPECT_TRUE(channel_->SetOptions(options));
+  VerifyVP8SendCodec(
+      channel_num, kVP8Codec.width, kVP8Codec.height, 0, 20, 10, 20);
+
+  options.video_noise_reduction.Set(false);
+  options.conference_mode.Set(false);
+  EXPECT_TRUE(channel_->SetOptions(options));
+  VerifyVP8SendCodec(
+      channel_num, kVP8Codec.width, kVP8Codec.height, 0, 20, 10, 20);
+}
+
+TEST_F(WebRtcVideoEngineTestFake, MaxBitrateResetWithConferenceMode) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  std::vector<cricket::VideoCodec> codecs(engine_.codecs());
+  codecs[0].params[cricket::kCodecParamMinBitrate] = "10";
+  codecs[0].params[cricket::kCodecParamMaxBitrate] = "20";
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+
+  VerifyVP8SendCodec(
+      channel_num, kVP8Codec.width, kVP8Codec.height, 0, 20, 10, 20);
+
+  cricket::VideoOptions options;
+  options.conference_mode.Set(true);
+  EXPECT_TRUE(channel_->SetOptions(options));
+  options.conference_mode.Set(false);
+  EXPECT_TRUE(channel_->SetOptions(options));
+  VerifyVP8SendCodec(
+      channel_num, kVP8Codec.width, kVP8Codec.height, 0,
+      kMaxBandwidthKbps, 10, 20);
+}
+
+// Verify the current send bitrate is used as start bitrate when reconfiguring
+// the send codec.
+TEST_F(WebRtcVideoEngineTestFake, StartSendBitrate) {
+  EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->AddSendStream(
+      cricket::StreamParams::CreateLegacy(1)));
+  int send_channel = vie_.GetLastChannel();
+  cricket::VideoCodec codec(kVP8Codec);
+  std::vector<cricket::VideoCodec> codec_list;
+  codec_list.push_back(codec);
+  EXPECT_TRUE(channel_->SetSendCodecs(codec_list));
+  const unsigned int kVideoMaxSendBitrateKbps = 2000;
+  const unsigned int kVideoMinSendBitrateKbps = 50;
+  const unsigned int kVideoDefaultStartSendBitrateKbps = 300;
+  VerifyVP8SendCodec(send_channel, kVP8Codec.width, kVP8Codec.height, 0,
+                     kVideoMaxSendBitrateKbps, kVideoMinSendBitrateKbps,
+                     kVideoDefaultStartSendBitrateKbps);
+  EXPECT_EQ(0, vie_.StartSend(send_channel));
+
+  // Increase the send bitrate and verify it is used as start bitrate.
+  const unsigned int kVideoSendBitrateBps = 768000;
+  vie_.SetSendBitrates(send_channel, kVideoSendBitrateBps, 0, 0);
+  EXPECT_TRUE(channel_->SetSendCodecs(codec_list));
+  VerifyVP8SendCodec(send_channel, kVP8Codec.width, kVP8Codec.height, 0,
+                     kVideoMaxSendBitrateKbps, kVideoMinSendBitrateKbps,
+                     kVideoSendBitrateBps / 1000);
+
+  // Never set a start bitrate higher than the max bitrate.
+  vie_.SetSendBitrates(send_channel, kVideoMaxSendBitrateKbps + 500, 0, 0);
+  EXPECT_TRUE(channel_->SetSendCodecs(codec_list));
+  VerifyVP8SendCodec(send_channel, kVP8Codec.width, kVP8Codec.height, 0,
+                     kVideoMaxSendBitrateKbps, kVideoMinSendBitrateKbps,
+                     kVideoDefaultStartSendBitrateKbps);
+
+  // Use the default start bitrate if the send bitrate is lower.
+  vie_.SetSendBitrates(send_channel, kVideoDefaultStartSendBitrateKbps - 50, 0,
+                       0);
+  EXPECT_TRUE(channel_->SetSendCodecs(codec_list));
+  VerifyVP8SendCodec(send_channel, kVP8Codec.width, kVP8Codec.height, 0,
+                     kVideoMaxSendBitrateKbps, kVideoMinSendBitrateKbps,
+                     kVideoDefaultStartSendBitrateKbps);
+}
+
+
+// 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));
+  VerifyVP8SendCodec(channel_num, kVP8Codec.width, kVP8Codec.height);
+}
+
+// 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;
+  // Set plType to something other than the value to test against ensuring
+  // that failure will happen if it is not changed.
+  gcodec.plType = 1;
+  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));
+  // Set plType to something other than the value to test against ensuring
+  // that failure will happen if it is not changed.
+  gcodec.plType = 1;
+  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;
+  // Set plType to something other than the value to test against ensuring
+  // that failure will happen if it is not changed.
+  gcodec.plType = 1;
+  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);
+  VerifyVP8SendCodec(channel_num, kVP8Codec.width / 2, kVP8Codec.height / 2);
+
+  // 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);
+  VerifyVP8SendCodec(channel_num, kVP8Codec.width, kVP8Codec.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 receive and send is enabled for the default channel in a 1:1
+// call.
+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_.GetRembStatusBwPartition(channel_num));
+  EXPECT_TRUE(channel_->SetSend(true));
+  EXPECT_TRUE(vie_.GetRembStatusBwPartition(channel_num));
+  EXPECT_TRUE(vie_.GetRembStatusContribute(channel_num));
+}
+
+// When in conference mode, test that remb is enabled on a receive channel but
+// not for the default channel and that it uses the default channel for sending
+// remb packets.
+TEST_F(WebRtcVideoEngineTestFake, RembEnabledOnReceiveChannels) {
+  EXPECT_TRUE(SetupEngine());
+  int default_channel = vie_.GetLastChannel();
+  cricket::VideoOptions options;
+  options.conference_mode.Set(true);
+  EXPECT_TRUE(channel_->SetOptions(options));
+  EXPECT_TRUE(channel_->AddSendStream(
+      cricket::StreamParams::CreateLegacy(1)));
+  EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+  EXPECT_TRUE(vie_.GetRembStatusBwPartition(default_channel));
+  EXPECT_TRUE(vie_.GetRembStatusContribute(default_channel));
+  EXPECT_TRUE(channel_->SetSend(true));
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+  int new_channel_num = vie_.GetLastChannel();
+  EXPECT_NE(default_channel, new_channel_num);
+
+  EXPECT_TRUE(vie_.GetRembStatusBwPartition(default_channel));
+  EXPECT_TRUE(vie_.GetRembStatusContribute(default_channel));
+  EXPECT_FALSE(vie_.GetRembStatusBwPartition(new_channel_num));
+  EXPECT_TRUE(vie_.GetRembStatusContribute(new_channel_num));
+}
+
+// Test support for RTP timestamp offset header extension.
+TEST_F(WebRtcVideoEngineTestFake, RtpTimestampOffsetHeaderExtensions) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  cricket::VideoOptions options;
+  options.conference_mode.Set(true);
+  EXPECT_TRUE(channel_->SetOptions(options));
+
+  // Verify extensions are off by default.
+  EXPECT_EQ(0, vie_.GetSendRtpTimestampOffsetExtensionId(channel_num));
+  EXPECT_EQ(0, vie_.GetReceiveRtpTimestampOffsetExtensionId(channel_num));
+
+  // Enable RTP timestamp extension.
+  const int id = 14;
+  std::vector<cricket::RtpHeaderExtension> extensions;
+  extensions.push_back(cricket::RtpHeaderExtension(
+      "urn:ietf:params:rtp-hdrext:toffset", id));
+
+  // Verify the send extension id.
+  EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(extensions));
+  EXPECT_EQ(id, vie_.GetSendRtpTimestampOffsetExtensionId(channel_num));
+
+  // Remove the extension id.
+  std::vector<cricket::RtpHeaderExtension> empty_extensions;
+  EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(empty_extensions));
+  EXPECT_EQ(0, vie_.GetSendRtpTimestampOffsetExtensionId(channel_num));
+
+  // Verify receive extension id.
+  EXPECT_TRUE(channel_->SetRecvRtpHeaderExtensions(extensions));
+  EXPECT_EQ(id, vie_.GetReceiveRtpTimestampOffsetExtensionId(channel_num));
+
+  // Add a new receive stream and verify the extension is set.
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
+  int new_channel_num = vie_.GetLastChannel();
+  EXPECT_NE(channel_num, new_channel_num);
+  EXPECT_EQ(id, vie_.GetReceiveRtpTimestampOffsetExtensionId(new_channel_num));
+
+  // Remove the extension id.
+  EXPECT_TRUE(channel_->SetRecvRtpHeaderExtensions(empty_extensions));
+  EXPECT_EQ(0, vie_.GetReceiveRtpTimestampOffsetExtensionId(channel_num));
+  EXPECT_EQ(0, vie_.GetReceiveRtpTimestampOffsetExtensionId(new_channel_num));
+}
+
+// Test support for absolute send time header extension.
+TEST_F(WebRtcVideoEngineTestFake, AbsoluteSendTimeHeaderExtensions) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  cricket::VideoOptions options;
+  options.conference_mode.Set(true);
+  EXPECT_TRUE(channel_->SetOptions(options));
+
+  // Verify extensions are off by default.
+  EXPECT_EQ(0, vie_.GetSendAbsoluteSendTimeExtensionId(channel_num));
+  EXPECT_EQ(0, vie_.GetReceiveAbsoluteSendTimeExtensionId(channel_num));
+
+  // Enable RTP timestamp extension.
+  const int id = 12;
+  std::vector<cricket::RtpHeaderExtension> extensions;
+  extensions.push_back(cricket::RtpHeaderExtension(
+      "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time", id));
+
+  // Verify the send extension id.
+  EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(extensions));
+  EXPECT_EQ(id, vie_.GetSendAbsoluteSendTimeExtensionId(channel_num));
+
+  // Remove the extension id.
+  std::vector<cricket::RtpHeaderExtension> empty_extensions;
+  EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(empty_extensions));
+  EXPECT_EQ(0, vie_.GetSendAbsoluteSendTimeExtensionId(channel_num));
+
+  // Verify receive extension id.
+  EXPECT_TRUE(channel_->SetRecvRtpHeaderExtensions(extensions));
+  EXPECT_EQ(id, vie_.GetReceiveAbsoluteSendTimeExtensionId(channel_num));
+
+  // Add a new receive stream and verify the extension is set.
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
+  int new_channel_num = vie_.GetLastChannel();
+  EXPECT_NE(channel_num, new_channel_num);
+  EXPECT_EQ(id, vie_.GetReceiveAbsoluteSendTimeExtensionId(new_channel_num));
+
+  // Remove the extension id.
+  EXPECT_TRUE(channel_->SetRecvRtpHeaderExtensions(empty_extensions));
+  EXPECT_EQ(0, vie_.GetReceiveAbsoluteSendTimeExtensionId(channel_num));
+  EXPECT_EQ(0, vie_.GetReceiveAbsoluteSendTimeExtensionId(new_channel_num));
+}
+
+TEST_F(WebRtcVideoEngineTestFake, LeakyBucketTest) {
+  EXPECT_TRUE(SetupEngine());
+
+  // Verify this is off by default.
+  EXPECT_TRUE(channel_->AddSendStream(cricket::StreamParams::CreateLegacy(1)));
+  int first_send_channel = vie_.GetLastChannel();
+  EXPECT_FALSE(vie_.GetTransmissionSmoothingStatus(first_send_channel));
+
+  // Enable the experiment and verify.
+  cricket::VideoOptions options;
+  options.conference_mode.Set(true);
+  options.video_leaky_bucket.Set(true);
+  EXPECT_TRUE(channel_->SetOptions(options));
+  EXPECT_TRUE(vie_.GetTransmissionSmoothingStatus(first_send_channel));
+
+  // Add a receive channel and verify leaky bucket isn't enabled.
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
+  int recv_channel_num = vie_.GetLastChannel();
+  EXPECT_NE(first_send_channel, recv_channel_num);
+  EXPECT_FALSE(vie_.GetTransmissionSmoothingStatus(recv_channel_num));
+
+  // Add a new send stream and verify leaky bucket is enabled from start.
+  EXPECT_TRUE(channel_->AddSendStream(cricket::StreamParams::CreateLegacy(3)));
+  int second_send_channel = vie_.GetLastChannel();
+  EXPECT_NE(first_send_channel, second_send_channel);
+  EXPECT_TRUE(vie_.GetTransmissionSmoothingStatus(second_send_channel));
+}
+
+TEST_F(WebRtcVideoEngineTestFake, BufferedModeLatency) {
+  EXPECT_TRUE(SetupEngine());
+
+  // Verify this is off by default.
+  EXPECT_TRUE(channel_->AddSendStream(cricket::StreamParams::CreateLegacy(1)));
+  int first_send_channel = vie_.GetLastChannel();
+  EXPECT_EQ(0, vie_.GetSenderTargetDelay(first_send_channel));
+  EXPECT_EQ(0, vie_.GetReceiverTargetDelay(first_send_channel));
+
+  // Enable the experiment and verify. The default channel will have both
+  // sender and receiver buffered mode enabled.
+  cricket::VideoOptions options;
+  options.conference_mode.Set(true);
+  options.buffered_mode_latency.Set(100);
+  EXPECT_TRUE(channel_->SetOptions(options));
+  EXPECT_EQ(100, vie_.GetSenderTargetDelay(first_send_channel));
+  EXPECT_EQ(100, vie_.GetReceiverTargetDelay(first_send_channel));
+
+  // Add a receive channel and verify sender buffered mode isn't enabled.
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
+  int recv_channel_num = vie_.GetLastChannel();
+  EXPECT_NE(first_send_channel, recv_channel_num);
+  EXPECT_EQ(0, vie_.GetSenderTargetDelay(recv_channel_num));
+  EXPECT_EQ(100, vie_.GetReceiverTargetDelay(recv_channel_num));
+
+  // Add a new send stream and verify sender buffered mode is enabled.
+  EXPECT_TRUE(channel_->AddSendStream(cricket::StreamParams::CreateLegacy(3)));
+  int second_send_channel = vie_.GetLastChannel();
+  EXPECT_NE(first_send_channel, second_send_channel);
+  EXPECT_EQ(100, vie_.GetSenderTargetDelay(second_send_channel));
+  EXPECT_EQ(0, vie_.GetReceiverTargetDelay(second_send_channel));
+
+  // Disable sender buffered mode and verify.
+  options.buffered_mode_latency.Set(cricket::kBufferedModeDisabled);
+  EXPECT_TRUE(channel_->SetOptions(options));
+  EXPECT_EQ(0, vie_.GetSenderTargetDelay(first_send_channel));
+  EXPECT_EQ(0, vie_.GetReceiverTargetDelay(first_send_channel));
+  EXPECT_EQ(0, vie_.GetSenderTargetDelay(second_send_channel));
+  EXPECT_EQ(0, vie_.GetReceiverTargetDelay(second_send_channel));
+  EXPECT_EQ(0, vie_.GetSenderTargetDelay(recv_channel_num));
+  EXPECT_EQ(0, vie_.GetReceiverTargetDelay(recv_channel_num));
+}
+
+TEST_F(WebRtcVideoEngineTestFake, AdditiveVideoOptions) {
+  EXPECT_TRUE(SetupEngine());
+
+  EXPECT_TRUE(channel_->AddSendStream(cricket::StreamParams::CreateLegacy(1)));
+  int first_send_channel = vie_.GetLastChannel();
+  EXPECT_EQ(0, vie_.GetSenderTargetDelay(first_send_channel));
+  EXPECT_EQ(0, vie_.GetReceiverTargetDelay(first_send_channel));
+
+  cricket::VideoOptions options1;
+  options1.buffered_mode_latency.Set(100);
+  EXPECT_TRUE(channel_->SetOptions(options1));
+  EXPECT_EQ(100, vie_.GetSenderTargetDelay(first_send_channel));
+  EXPECT_EQ(100, vie_.GetReceiverTargetDelay(first_send_channel));
+  EXPECT_FALSE(vie_.GetTransmissionSmoothingStatus(first_send_channel));
+
+  cricket::VideoOptions options2;
+  options2.video_leaky_bucket.Set(true);
+  EXPECT_TRUE(channel_->SetOptions(options2));
+  EXPECT_TRUE(vie_.GetTransmissionSmoothingStatus(first_send_channel));
+  // The buffered_mode_latency still takes effect.
+  EXPECT_EQ(100, vie_.GetSenderTargetDelay(first_send_channel));
+  EXPECT_EQ(100, vie_.GetReceiverTargetDelay(first_send_channel));
+
+  options1.buffered_mode_latency.Set(50);
+  EXPECT_TRUE(channel_->SetOptions(options1));
+  EXPECT_EQ(50, vie_.GetSenderTargetDelay(first_send_channel));
+  EXPECT_EQ(50, vie_.GetReceiverTargetDelay(first_send_channel));
+  // The video_leaky_bucket still takes effect.
+  EXPECT_TRUE(vie_.GetTransmissionSmoothingStatus(first_send_channel));
+}
+
+// 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_.GetRembStatusBwPartition(channel_num));
+  EXPECT_TRUE(vie_.GetRembStatusContribute(channel_num));
+  EXPECT_TRUE(channel_->SetSend(true));
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+  EXPECT_TRUE(vie_.GetRembStatusBwPartition(channel_num));
+  EXPECT_TRUE(vie_.GetRembStatusContribute(channel_num));
+}
+
+// Verify default REMB setting and that it can be turned on and off.
+TEST_F(WebRtcVideoEngineTestFake, RembOnOff) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  // Verify REMB sending is always off by default.
+  EXPECT_FALSE(vie_.GetRembStatusBwPartition(channel_num));
+
+  // Verify that REMB is turned on when setting default codecs since the
+  // default codecs have REMB enabled.
+  EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+  EXPECT_TRUE(vie_.GetRembStatusBwPartition(channel_num));
+
+  // Verify that REMB is turned off when codecs without REMB are set.
+  std::vector<cricket::VideoCodec> codecs = engine_.codecs();
+  // Clearing the codecs' FeedbackParams and setting send codecs should disable
+  // REMB.
+  for (std::vector<cricket::VideoCodec>::iterator iter = codecs.begin();
+       iter != codecs.end(); ++iter) {
+    // Intersecting with empty will clear the FeedbackParams.
+    cricket::FeedbackParams empty_params;
+    iter->feedback_params.Intersect(empty_params);
+    EXPECT_TRUE(iter->feedback_params.params().empty());
+  }
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_FALSE(vie_.GetRembStatusBwPartition(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();
+  cricket::VideoOptions options;
+  options.conference_mode.Set(true);
+  EXPECT_TRUE(channel_->SetOptions(options));
+  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 when AddRecvStream in conference mode, a new channel is created
+// for receiving. And the new channel's "original channel" is the send channel.
+TEST_F(WebRtcVideoEngineTestFake, AddRemoveRecvStreamConference) {
+  EXPECT_TRUE(SetupEngine());
+  // Setup the send channel.
+  int send_channel_num = vie_.GetLastChannel();
+  cricket::VideoOptions options;
+  options.conference_mode.Set(true);
+  EXPECT_TRUE(channel_->SetOptions(options));
+  // Add a receive stream.
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+  int receive_channel_num = vie_.GetLastChannel();
+  EXPECT_EQ(send_channel_num, vie_.GetOriginalChannelId(receive_channel_num));
+  EXPECT_TRUE(channel_->RemoveRecvStream(1));
+  EXPECT_FALSE(vie_.IsChannel(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));
+  VerifyVP8SendCodec(channel_num, kVP8Codec.width, kVP8Codec.height);
+}
+
+// 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));
+  VerifyVP8SendCodec(channel_num, kVP8Codec.width, kVP8Codec.height, 0, 768U);
+}
+
+// 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));
+  VerifyVP8SendCodec(channel_num, kVP8Codec.width, kVP8Codec.height, 0,
+                     768U, 768U, 768U);
+}
+
+// Test that SetSendBandwidth is ignored in conference mode.
+TEST_F(WebRtcVideoEngineTestFake, SetBandwidthInConference) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  cricket::VideoOptions options;
+  options.conference_mode.Set(true);
+  EXPECT_TRUE(channel_->SetOptions(options));
+  EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+  VerifyVP8SendCodec(channel_num, kVP8Codec.width, kVP8Codec.height);
+
+  // Set send bandwidth.
+  EXPECT_TRUE(channel_->SetSendBandwidth(false, 768000));
+
+  // Verify bitrate not changed.
+  webrtc::VideoCodec gcodec;
+  EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(kMinBandwidthKbps, gcodec.minBitrate);
+  EXPECT_EQ(kStartBandwidthKbps, gcodec.startBitrate);
+  EXPECT_EQ(kMaxBandwidthKbps, gcodec.maxBitrate);
+  EXPECT_NE(768U, gcodec.minBitrate);
+  EXPECT_NE(768U, gcodec.startBitrate);
+  EXPECT_NE(768U, gcodec.maxBitrate);
+}
+
+// Test that sending screencast frames doesn't change bitrate.
+TEST_F(WebRtcVideoEngineTestFake, SetBandwidthScreencast) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+
+  // Set send codec.
+  cricket::VideoCodec codec(kVP8Codec);
+  std::vector<cricket::VideoCodec> codec_list;
+  codec_list.push_back(codec);
+  EXPECT_TRUE(channel_->AddSendStream(
+      cricket::StreamParams::CreateLegacy(123)));
+  EXPECT_TRUE(channel_->SetSendCodecs(codec_list));
+  EXPECT_TRUE(channel_->SetSendBandwidth(false, 111000));
+  EXPECT_TRUE(channel_->SetSend(true));
+
+  SendI420ScreencastFrame(kVP8Codec.width, kVP8Codec.height);
+  VerifyVP8SendCodec(channel_num, kVP8Codec.width, kVP8Codec.height, 0,
+                     111, 111, 111);
+}
+
+
+// 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);
+  EXPECT_EQ(1, vie_.GetNumSsrcs(channel_num));
+
+  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);
+  EXPECT_EQ(1, vie_.GetNumSsrcs(send_channel_num));
+  ssrc = 0;
+  EXPECT_EQ(0, vie_.GetLocalSSRC(receive_channel_num, ssrc));
+  EXPECT_EQ(1234U, ssrc);
+  EXPECT_EQ(1, vie_.GetNumSsrcs(receive_channel_num));
+}
+
+
+// Test SetOptions with denoising flag.
+TEST_F(WebRtcVideoEngineTestFake, SetOptionsWithDenoising) {
+  EXPECT_TRUE(SetupEngine());
+  EXPECT_EQ(1, vie_.GetNumCapturers());
+  int channel_num = vie_.GetLastChannel();
+  int capture_id = vie_.GetCaptureId(channel_num);
+  // Set send codecs on the channel.
+  std::vector<cricket::VideoCodec> codecs;
+  codecs.push_back(kVP8Codec);
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+
+  // Set options with OPT_VIDEO_NOISE_REDUCTION flag.
+  cricket::VideoOptions options;
+  options.video_noise_reduction.Set(true);
+  EXPECT_TRUE(channel_->SetOptions(options));
+
+  // Verify capture has denoising turned on.
+  webrtc::VideoCodec send_codec;
+  memset(&send_codec, 0, sizeof(send_codec));  // avoid uninitialized warning
+  EXPECT_EQ(0, vie_.GetSendCodec(channel_num, send_codec));
+  EXPECT_TRUE(send_codec.codecSpecific.VP8.denoisingOn);
+  EXPECT_FALSE(vie_.GetCaptureDenoising(capture_id));
+
+  // Set options back to zero.
+  options.video_noise_reduction.Set(false);
+  EXPECT_TRUE(channel_->SetOptions(options));
+
+  // Verify capture has denoising turned off.
+  EXPECT_EQ(0, vie_.GetSendCodec(channel_num, send_codec));
+  EXPECT_FALSE(send_codec.codecSpecific.VP8.denoisingOn);
+  EXPECT_FALSE(vie_.GetCaptureDenoising(capture_id));
+}
+
+
+TEST_F(WebRtcVideoEngineTestFake, SendReceiveBitratesStats) {
+  EXPECT_TRUE(SetupEngine());
+  cricket::VideoOptions options;
+  options.conference_mode.Set(true);
+  EXPECT_TRUE(channel_->SetOptions(options));
+  EXPECT_TRUE(channel_->AddSendStream(
+      cricket::StreamParams::CreateLegacy(1)));
+  int send_channel = vie_.GetLastChannel();
+  cricket::VideoCodec codec(kVP8Codec720p);
+  std::vector<cricket::VideoCodec> codec_list;
+  codec_list.push_back(codec);
+  EXPECT_TRUE(channel_->SetSendCodecs(codec_list));
+
+  EXPECT_TRUE(channel_->AddRecvStream(
+      cricket::StreamParams::CreateLegacy(2)));
+  int first_receive_channel = vie_.GetLastChannel();
+  EXPECT_NE(send_channel, first_receive_channel);
+  EXPECT_TRUE(channel_->AddRecvStream(
+      cricket::StreamParams::CreateLegacy(3)));
+  int second_receive_channel = vie_.GetLastChannel();
+  EXPECT_NE(first_receive_channel, second_receive_channel);
+
+  cricket::VideoMediaInfo info;
+  EXPECT_TRUE(channel_->GetStats(&info));
+  ASSERT_EQ(1U, info.bw_estimations.size());
+  ASSERT_EQ(0, info.bw_estimations[0].actual_enc_bitrate);
+  ASSERT_EQ(0, info.bw_estimations[0].transmit_bitrate);
+  ASSERT_EQ(0, info.bw_estimations[0].retransmit_bitrate);
+  ASSERT_EQ(0, info.bw_estimations[0].available_send_bandwidth);
+  ASSERT_EQ(0, info.bw_estimations[0].available_recv_bandwidth);
+  ASSERT_EQ(0, info.bw_estimations[0].target_enc_bitrate);
+
+  // Start sending and receiving on one of the channels and verify bitrates.
+  EXPECT_EQ(0, vie_.StartSend(send_channel));
+  int send_video_bitrate = 800;
+  int send_fec_bitrate = 100;
+  int send_nack_bitrate = 20;
+  int send_total_bitrate = send_video_bitrate + send_fec_bitrate +
+      send_nack_bitrate;
+  int send_bandwidth = 950;
+  vie_.SetSendBitrates(send_channel, send_video_bitrate, send_fec_bitrate,
+                       send_nack_bitrate);
+  vie_.SetSendBandwidthEstimate(send_channel, send_bandwidth);
+
+  EXPECT_EQ(0, vie_.StartReceive(first_receive_channel));
+  int first_channel_receive_bandwidth = 600;
+  vie_.SetReceiveBandwidthEstimate(first_receive_channel,
+                                   first_channel_receive_bandwidth);
+
+  info.Clear();
+  EXPECT_TRUE(channel_->GetStats(&info));
+  ASSERT_EQ(1U, info.bw_estimations.size());
+  ASSERT_EQ(send_video_bitrate, info.bw_estimations[0].actual_enc_bitrate);
+  ASSERT_EQ(send_total_bitrate, info.bw_estimations[0].transmit_bitrate);
+  ASSERT_EQ(send_nack_bitrate, info.bw_estimations[0].retransmit_bitrate);
+  ASSERT_EQ(send_bandwidth, info.bw_estimations[0].available_send_bandwidth);
+  ASSERT_EQ(first_channel_receive_bandwidth,
+            info.bw_estimations[0].available_recv_bandwidth);
+  ASSERT_EQ(send_video_bitrate, info.bw_estimations[0].target_enc_bitrate);
+
+  // Start receiving on the second channel and verify received rate.
+  EXPECT_EQ(0, vie_.StartReceive(second_receive_channel));
+  int second_channel_receive_bandwidth = 100;
+  vie_.SetReceiveBandwidthEstimate(second_receive_channel,
+                                   second_channel_receive_bandwidth);
+
+  info.Clear();
+  EXPECT_TRUE(channel_->GetStats(&info));
+  ASSERT_EQ(1U, info.bw_estimations.size());
+  ASSERT_EQ(send_video_bitrate, info.bw_estimations[0].actual_enc_bitrate);
+  ASSERT_EQ(send_total_bitrate, info.bw_estimations[0].transmit_bitrate);
+  ASSERT_EQ(send_nack_bitrate, info.bw_estimations[0].retransmit_bitrate);
+  ASSERT_EQ(send_bandwidth, info.bw_estimations[0].available_send_bandwidth);
+  ASSERT_EQ(first_channel_receive_bandwidth + second_channel_receive_bandwidth,
+            info.bw_estimations[0].available_recv_bandwidth);
+  ASSERT_EQ(send_video_bitrate, info.bw_estimations[0].target_enc_bitrate);
+}
+
+TEST_F(WebRtcVideoEngineTestFake, TestSetAdaptInputToCpuUsage) {
+  EXPECT_TRUE(SetupEngine());
+  cricket::VideoOptions options_in, options_out;
+  bool cpu_adapt = false;
+  channel_->SetOptions(options_in);
+  EXPECT_TRUE(channel_->GetOptions(&options_out));
+  EXPECT_FALSE(options_out.adapt_input_to_cpu_usage.Get(&cpu_adapt));
+  // Set adapt input CPU usage option.
+  options_in.adapt_input_to_cpu_usage.Set(true);
+  EXPECT_TRUE(channel_->SetOptions(options_in));
+  EXPECT_TRUE(channel_->GetOptions(&options_out));
+  EXPECT_TRUE(options_out.adapt_input_to_cpu_usage.Get(&cpu_adapt));
+  EXPECT_TRUE(cpu_adapt);
+}
+
+TEST_F(WebRtcVideoEngineTestFake, TestSetCpuThreshold) {
+  EXPECT_TRUE(SetupEngine());
+  float low, high;
+  cricket::VideoOptions options_in, options_out;
+  // Verify that initial values are set.
+  EXPECT_TRUE(channel_->GetOptions(&options_out));
+  EXPECT_TRUE(options_out.system_low_adaptation_threshhold.Get(&low));
+  EXPECT_EQ(low, 0.65f);
+  EXPECT_TRUE(options_out.system_high_adaptation_threshhold.Get(&high));
+  EXPECT_EQ(high, 0.85f);
+  // Set new CPU threshold values.
+  options_in.system_low_adaptation_threshhold.Set(0.45f);
+  options_in.system_high_adaptation_threshhold.Set(0.95f);
+  EXPECT_TRUE(channel_->SetOptions(options_in));
+  EXPECT_TRUE(channel_->GetOptions(&options_out));
+  EXPECT_TRUE(options_out.system_low_adaptation_threshhold.Get(&low));
+  EXPECT_EQ(low, 0.45f);
+  EXPECT_TRUE(options_out.system_high_adaptation_threshhold.Get(&high));
+  EXPECT_EQ(high, 0.95f);
+}
+
+TEST_F(WebRtcVideoEngineTestFake, TestSetInvalidCpuThreshold) {
+  EXPECT_TRUE(SetupEngine());
+  float low, high;
+  cricket::VideoOptions options_in, options_out;
+  // Valid range is [0, 1].
+  options_in.system_low_adaptation_threshhold.Set(-1.5f);
+  options_in.system_high_adaptation_threshhold.Set(1.5f);
+  EXPECT_TRUE(channel_->SetOptions(options_in));
+  EXPECT_TRUE(channel_->GetOptions(&options_out));
+  EXPECT_TRUE(options_out.system_low_adaptation_threshhold.Get(&low));
+  EXPECT_EQ(low, 0.0f);
+  EXPECT_TRUE(options_out.system_high_adaptation_threshhold.Get(&high));
+  EXPECT_EQ(high, 1.0f);
+}
+
+
+/////////////////////////
+// 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(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));
+
+  cricket::VideoCodec vp8_diff_id(95, "VP8", 320, 200, 30, 0);
+  EXPECT_FALSE(engine_.FindCodec(vp8_diff_id));
+  vp8_diff_id.id = 97;
+  EXPECT_TRUE(engine_.FindCodec(vp8_diff_id));
+
+  cricket::VideoCodec vp8_diff_res(104, "VP8", 320, 111, 30, 0);
+  EXPECT_FALSE(engine_.FindCodec(vp8_diff_res));
+
+  // PeerConnection doesn't negotiate the resolution at this point.
+  // 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_F(WebRtcVideoEngineTest, StartupShutdown) {
+  EXPECT_TRUE(engine_.Init(talk_base::Thread::Current()));
+  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(juberti): 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(talk_base::Thread::Current()));
+  cricket::VideoMediaChannel* channel = engine_.CreateChannel(NULL);
+  EXPECT_TRUE(channel != NULL);
+  delete channel;
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, TestVideoProcessor_DropFrames) {
+  // Connect a video processor.
+  cricket::FakeMediaProcessor vp;
+  vp.set_drop_frames(false);
+  EXPECT_TRUE(engine_.RegisterProcessor(&vp));
+  EXPECT_EQ(0, vp.dropped_frame_count());
+  // Send the first frame with default codec.
+  int packets = NumRtpPackets();
+  cricket::VideoCodec codec(DefaultCodec());
+  EXPECT_TRUE(SetOneCodec(codec));
+  EXPECT_TRUE(SetSend(true));
+  EXPECT_TRUE(channel_->SetRender(true));
+  EXPECT_EQ(0, renderer_.num_rendered_frames());
+  EXPECT_TRUE(WaitAndSendFrame(30));
+  EXPECT_FRAME_WAIT(1, codec.width, codec.height, kTimeout);
+  // Verify frame was sent.
+  EXPECT_TRUE_WAIT(NumRtpPackets() > packets, kTimeout);
+  packets = NumRtpPackets();
+  EXPECT_EQ(0, vp.dropped_frame_count());
+  // Send another frame and expect it to be sent.
+  EXPECT_TRUE(WaitAndSendFrame(30));
+  EXPECT_FRAME_WAIT(2, codec.width, codec.height, kTimeout);
+  EXPECT_TRUE_WAIT(NumRtpPackets() > packets, kTimeout);
+  packets = NumRtpPackets();
+  EXPECT_EQ(0, vp.dropped_frame_count());
+  // Attempt to send a frame and expect it to be dropped.
+  vp.set_drop_frames(true);
+  EXPECT_TRUE(WaitAndSendFrame(30));
+  DrainOutgoingPackets();
+  EXPECT_FRAME_WAIT(2, codec.width, codec.height, kTimeout);
+  EXPECT_EQ(packets, NumRtpPackets());
+  EXPECT_EQ(1, vp.dropped_frame_count());
+  // Disconnect video processor.
+  EXPECT_TRUE(engine_.UnregisterProcessor(&vp));
+}
+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_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));
+}
+TEST_F(WebRtcVideoMediaChannelTest, SendManyResizeOnce) {
+  SendManyResizeOnce();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, SendVp8HdAndReceiveAdaptedVp8Vga) {
+  EXPECT_TRUE(engine_.SetVideoCapturer(NULL));
+  channel_->UpdateAspectRatio(1280, 720);
+  video_capturer_.reset(new cricket::FakeVideoCapturer);
+  const std::vector<cricket::VideoFormat>* formats =
+      video_capturer_->GetSupportedFormats();
+  cricket::VideoFormat capture_format_hd = (*formats)[0];
+  EXPECT_EQ(cricket::CS_RUNNING, video_capturer_->Start(capture_format_hd));
+  EXPECT_TRUE(channel_->SetCapturer(kSsrc, video_capturer_.get()));
+
+  // Capture format HD -> adapt (OnOutputFormatRequest VGA) -> VGA.
+  cricket::VideoCodec codec(100, "VP8", 1280, 720, 30, 0);
+  EXPECT_TRUE(SetOneCodec(codec));
+  codec.width /= 2;
+  codec.height /= 2;
+  EXPECT_TRUE(channel_->SetSendStreamFormat(kSsrc, cricket::VideoFormat(
+      codec.width, codec.height,
+      cricket::VideoFormat::FpsToInterval(codec.framerate),
+      cricket::FOURCC_ANY)));
+  EXPECT_TRUE(SetSend(true));
+  EXPECT_TRUE(channel_->SetRender(true));
+  EXPECT_EQ(0, renderer_.num_rendered_frames());
+  EXPECT_TRUE(SendFrame());
+  EXPECT_FRAME_WAIT(1, codec.width, codec.height, kTimeout);
+}
+
+// TODO(juberti): Fix this test to tolerate missing stats.
+TEST_F(WebRtcVideoMediaChannelTest, DISABLED_GetStats) {
+  Base::GetStats();
+}
+
+// TODO(juberti): Fix this test to tolerate missing stats.
+TEST_F(WebRtcVideoMediaChannelTest, DISABLED_GetStatsMultipleRecvStreams) {
+  Base::GetStatsMultipleRecvStreams();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, 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, AddRemoveRecvStreamAndRender) {
+  Base::AddRemoveRecvStreamAndRender();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, AddRemoveRecvStreamsNoConference) {
+  Base::AddRemoveRecvStreamsNoConference();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, AddRemoveSendStreams) {
+  Base::AddRemoveSendStreams();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, 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_.SetVideoCapturer(capturer.get()));
+  EXPECT_FALSE(engine_.IsCapturing());
+  EXPECT_TRUE(engine_.SetCapture(true));
+  cricket::VideoCodec codec(DefaultCodec());
+  EXPECT_TRUE(SetOneCodec(codec));
+  EXPECT_TRUE(channel_->SetSend(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));
+  EXPECT_FALSE(engine_.IsCapturing());
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, SimulateConference) {
+  Base::SimulateConference();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, AddRemoveCapturer) {
+  Base::AddRemoveCapturer();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, RemoveCapturerWithoutAdd) {
+  Base::RemoveCapturerWithoutAdd();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, AddRemoveCapturerMultipleSources) {
+  Base::AddRemoveCapturerMultipleSources();
+}
+
+
+TEST_F(WebRtcVideoMediaChannelTest, SetOptionsSucceedsWhenSending) {
+  cricket::VideoOptions options;
+  options.conference_mode.Set(true);
+  EXPECT_TRUE(channel_->SetOptions(options));
+
+  // Verify SetOptions returns true on a different options.
+  cricket::VideoOptions options2;
+  options2.adapt_input_to_cpu_usage.Set(true);
+  EXPECT_TRUE(channel_->SetOptions(options2));
+
+  // Set send codecs on the channel and start sending.
+  std::vector<cricket::VideoCodec> codecs;
+  codecs.push_back(kVP8Codec);
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_TRUE(channel_->SetSend(true));
+
+  // Verify SetOptions returns true if channel is already sending.
+  cricket::VideoOptions options3;
+  options3.conference_mode.Set(true);
+  EXPECT_TRUE(channel_->SetOptions(options3));
+}
+
+// Tests empty StreamParams is rejected.
+TEST_F(WebRtcVideoMediaChannelTest, RejectEmptyStreamParams) {
+  Base::RejectEmptyStreamParams();
+}
+
+
+TEST_F(WebRtcVideoMediaChannelTest, AdaptResolution16x10) {
+  Base::AdaptResolution16x10();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, AdaptResolution4x3) {
+  Base::AdaptResolution4x3();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, MuteStream) {
+  Base::MuteStream();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, MultipleSendStreams) {
+  Base::MultipleSendStreams();
+}
+
+// TODO(juberti): Restore this test once we support sending 0 fps.
+TEST_F(WebRtcVideoMediaChannelTest, DISABLED_AdaptDropAllFrames) {
+  Base::AdaptDropAllFrames();
+}
+// TODO(juberti): Understand why we get decode errors on this test.
+TEST_F(WebRtcVideoMediaChannelTest, DISABLED_AdaptFramerate) {
+  Base::AdaptFramerate();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, SetSendStreamFormat0x0) {
+  Base::SetSendStreamFormat0x0();
+}
+
+// TODO(zhurunz): Fix the flakey test.
+TEST_F(WebRtcVideoMediaChannelTest, DISABLED_SetSendStreamFormat) {
+  Base::SetSendStreamFormat();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, TwoStreamsSendAndReceive) {
+  Base::TwoStreamsSendAndReceive(cricket::VideoCodec(100, "VP8", 640, 400, 30,
+                                                     0));
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, TwoStreamsReUseFirstStream) {
+  Base::TwoStreamsReUseFirstStream(cricket::VideoCodec(100, "VP8", 640, 400, 30,
+                                                       0));
+}
+
+TEST_F(WebRtcVideoEngineTestFake, ResetCodecOnScreencast) {
+  EXPECT_TRUE(SetupEngine());
+  cricket::VideoOptions options;
+  options.video_noise_reduction.Set(true);
+  EXPECT_TRUE(channel_->SetOptions(options));
+
+  // Set send codec.
+  cricket::VideoCodec codec(kVP8Codec);
+  std::vector<cricket::VideoCodec> codec_list;
+  codec_list.push_back(codec);
+  EXPECT_TRUE(channel_->AddSendStream(
+      cricket::StreamParams::CreateLegacy(123)));
+  EXPECT_TRUE(channel_->SetSendCodecs(codec_list));
+  EXPECT_TRUE(channel_->SetSend(true));
+  EXPECT_EQ(1, vie_.num_set_send_codecs());
+
+  webrtc::VideoCodec gcodec;
+  memset(&gcodec, 0, sizeof(gcodec));
+  int channel_num = vie_.GetLastChannel();
+  EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+  EXPECT_TRUE(gcodec.codecSpecific.VP8.denoisingOn);
+
+  // Send a screencast frame with the same size.
+  // Verify that denoising is turned off.
+  SendI420ScreencastFrame(kVP8Codec.width, kVP8Codec.height);
+  EXPECT_EQ(2, vie_.num_set_send_codecs());
+  EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+  EXPECT_FALSE(gcodec.codecSpecific.VP8.denoisingOn);
+}
+
+
+TEST_F(WebRtcVideoEngineTestFake, DontRegisterDecoderIfFactoryIsNotGiven) {
+  engine_.SetExternalDecoderFactory(NULL);
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+
+  std::vector<cricket::VideoCodec> codecs;
+  codecs.push_back(kVP8Codec);
+  EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
+
+  EXPECT_EQ(0, vie_.GetNumExternalDecoderRegistered(channel_num));
+}
+
+TEST_F(WebRtcVideoEngineTestFake, RegisterDecoderIfFactoryIsGiven) {
+  decoder_factory_.AddSupportedVideoCodecType(webrtc::kVideoCodecVP8);
+  engine_.SetExternalDecoderFactory(&decoder_factory_);
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+
+  std::vector<cricket::VideoCodec> codecs;
+  codecs.push_back(kVP8Codec);
+  EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
+
+  EXPECT_TRUE(vie_.ExternalDecoderRegistered(channel_num, 100));
+  EXPECT_EQ(1, vie_.GetNumExternalDecoderRegistered(channel_num));
+}
+
+TEST_F(WebRtcVideoEngineTestFake, DontRegisterDecoderMultipleTimes) {
+  decoder_factory_.AddSupportedVideoCodecType(webrtc::kVideoCodecVP8);
+  engine_.SetExternalDecoderFactory(&decoder_factory_);
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+
+  std::vector<cricket::VideoCodec> codecs;
+  codecs.push_back(kVP8Codec);
+  EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
+
+  EXPECT_TRUE(vie_.ExternalDecoderRegistered(channel_num, 100));
+  EXPECT_EQ(1, vie_.GetNumExternalDecoderRegistered(channel_num));
+  EXPECT_EQ(1, decoder_factory_.GetNumCreatedDecoders());
+
+  EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
+  EXPECT_EQ(1, vie_.GetNumExternalDecoderRegistered(channel_num));
+  EXPECT_EQ(1, decoder_factory_.GetNumCreatedDecoders());
+}
+
+TEST_F(WebRtcVideoEngineTestFake, DontRegisterDecoderForNonVP8) {
+  decoder_factory_.AddSupportedVideoCodecType(webrtc::kVideoCodecVP8);
+  engine_.SetExternalDecoderFactory(&decoder_factory_);
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+
+  std::vector<cricket::VideoCodec> codecs;
+  codecs.push_back(kRedCodec);
+  EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
+
+  EXPECT_EQ(0, vie_.GetNumExternalDecoderRegistered(channel_num));
+}
+
+TEST_F(WebRtcVideoEngineTestFake, DontRegisterEncoderIfFactoryIsNotGiven) {
+  engine_.SetExternalEncoderFactory(NULL);
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+
+  std::vector<cricket::VideoCodec> codecs;
+  codecs.push_back(kVP8Codec);
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+
+  EXPECT_EQ(0, vie_.GetNumExternalEncoderRegistered(channel_num));
+}
+
+TEST_F(WebRtcVideoEngineTestFake, RegisterEncoderIfFactoryIsGiven) {
+  encoder_factory_.AddSupportedVideoCodecType(webrtc::kVideoCodecVP8, "VP8");
+  engine_.SetExternalEncoderFactory(&encoder_factory_);
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+
+  std::vector<cricket::VideoCodec> codecs;
+  codecs.push_back(kVP8Codec);
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+
+  EXPECT_TRUE(vie_.ExternalEncoderRegistered(channel_num, 100));
+  EXPECT_EQ(1, vie_.GetNumExternalEncoderRegistered(channel_num));
+}
+
+TEST_F(WebRtcVideoEngineTestFake, DontRegisterEncoderMultipleTimes) {
+  encoder_factory_.AddSupportedVideoCodecType(webrtc::kVideoCodecVP8, "VP8");
+  engine_.SetExternalEncoderFactory(&encoder_factory_);
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+
+  std::vector<cricket::VideoCodec> codecs;
+  codecs.push_back(kVP8Codec);
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+
+  EXPECT_TRUE(vie_.ExternalEncoderRegistered(channel_num, 100));
+  EXPECT_EQ(1, vie_.GetNumExternalEncoderRegistered(channel_num));
+  EXPECT_EQ(1, encoder_factory_.GetNumCreatedEncoders());
+
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_EQ(1, vie_.GetNumExternalEncoderRegistered(channel_num));
+  EXPECT_EQ(1, encoder_factory_.GetNumCreatedEncoders());
+}
+
+TEST_F(WebRtcVideoEngineTestFake, RegisterEncoderWithMultipleSendStreams) {
+  encoder_factory_.AddSupportedVideoCodecType(webrtc::kVideoCodecVP8, "VP8");
+  engine_.SetExternalEncoderFactory(&encoder_factory_);
+  EXPECT_TRUE(SetupEngine());
+
+  std::vector<cricket::VideoCodec> codecs;
+  codecs.push_back(kVP8Codec);
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_EQ(1, vie_.GetTotalNumExternalEncoderRegistered());
+
+  // When we add the first stream (1234), it reuses the default send channel,
+  // so it doesn't increase the registration count of external encoders.
+  EXPECT_TRUE(channel_->AddSendStream(
+      cricket::StreamParams::CreateLegacy(1234)));
+  EXPECT_EQ(1, vie_.GetTotalNumExternalEncoderRegistered());
+
+  // When we add the second stream (2345), it creates a new channel and
+  // increments the registration count.
+  EXPECT_TRUE(channel_->AddSendStream(
+      cricket::StreamParams::CreateLegacy(2345)));
+  EXPECT_EQ(2, vie_.GetTotalNumExternalEncoderRegistered());
+
+  // At this moment the total registration count is two, but only one encoder
+  // is registered per channel.
+  int channel_num = vie_.GetLastChannel();
+  EXPECT_EQ(1, vie_.GetNumExternalEncoderRegistered(channel_num));
+
+  // Removing send streams decrements the registration count.
+  EXPECT_TRUE(channel_->RemoveSendStream(1234));
+  EXPECT_EQ(1, vie_.GetTotalNumExternalEncoderRegistered());
+
+  // When we remove the last send stream, it also destroys the last send
+  // channel and causes the registration count to drop to zero. It is a little
+  // weird, but not a bug.
+  EXPECT_TRUE(channel_->RemoveSendStream(2345));
+  EXPECT_EQ(0, vie_.GetTotalNumExternalEncoderRegistered());
+}
+
+TEST_F(WebRtcVideoEngineTestFake, DontRegisterEncoderForNonVP8) {
+  encoder_factory_.AddSupportedVideoCodecType(webrtc::kVideoCodecGeneric,
+                                              "GENERIC");
+  engine_.SetExternalEncoderFactory(&encoder_factory_);
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+
+  // Note: unlike the SetRecvCodecs, we must set a valid video codec for
+  // channel_->SetSendCodecs() to succeed.
+  std::vector<cricket::VideoCodec> codecs;
+  codecs.push_back(kVP8Codec);
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+
+  EXPECT_EQ(0, vie_.GetNumExternalEncoderRegistered(channel_num));
+}
+
+// Test that NACK and REMB are enabled for external codec.
+TEST_F(WebRtcVideoEngineTestFake, FeedbackParamsForNonVP8) {
+  encoder_factory_.AddSupportedVideoCodecType(webrtc::kVideoCodecGeneric,
+                                              "GENERIC");
+  engine_.SetExternalEncoderFactory(&encoder_factory_);
+  encoder_factory_.NotifyCodecsAvailable();
+  EXPECT_TRUE(SetupEngine());
+
+  std::vector<cricket::VideoCodec> codecs(engine_.codecs());
+  EXPECT_EQ("GENERIC", codecs[0].name);
+  EXPECT_TRUE(codecs[0].HasFeedbackParam(
+      cricket::FeedbackParam(cricket::kRtcpFbParamNack,
+                             cricket::kParamValueEmpty)));
+  EXPECT_TRUE(codecs[0].HasFeedbackParam(
+      cricket::FeedbackParam(cricket::kRtcpFbParamRemb,
+                             cricket::kParamValueEmpty)));
+  EXPECT_TRUE(codecs[0].HasFeedbackParam(
+      cricket::FeedbackParam(cricket::kRtcpFbParamCcm,
+                             cricket::kRtcpFbCcmParamFir)));
+}
+
+TEST_F(WebRtcVideoEngineTestFake, UpdateEncoderCodecsAfterSetFactory) {
+  engine_.SetExternalEncoderFactory(&encoder_factory_);
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+
+  encoder_factory_.AddSupportedVideoCodecType(webrtc::kVideoCodecVP8, "VP8");
+  encoder_factory_.NotifyCodecsAvailable();
+  std::vector<cricket::VideoCodec> codecs;
+  codecs.push_back(kVP8Codec);
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+
+  EXPECT_TRUE(vie_.ExternalEncoderRegistered(channel_num, 100));
+  EXPECT_EQ(1, vie_.GetNumExternalEncoderRegistered(channel_num));
+  EXPECT_EQ(1, encoder_factory_.GetNumCreatedEncoders());
+}
+
+// Tests that OnReadyToSend will be propagated into ViE.
+TEST_F(WebRtcVideoEngineTestFake, OnReadyToSend) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  EXPECT_TRUE(vie_.GetIsTransmitting(channel_num));
+
+  channel_->OnReadyToSend(false);
+  EXPECT_FALSE(vie_.GetIsTransmitting(channel_num));
+
+  channel_->OnReadyToSend(true);
+  EXPECT_TRUE(vie_.GetIsTransmitting(channel_num));
+}
+
+#if 0
+TEST_F(WebRtcVideoEngineTestFake, CaptureFrameTimestampToNtpTimestamp) {
+  EXPECT_TRUE(SetupEngine());
+  int capture_id = vie_.GetCaptureId(vie_.GetLastChannel());
+
+  // Set send codec.
+  cricket::VideoCodec codec(kVP8Codec);
+  std::vector<cricket::VideoCodec> codec_list;
+  codec_list.push_back(codec);
+  EXPECT_TRUE(channel_->AddSendStream(
+      cricket::StreamParams::CreateLegacy(123)));
+  EXPECT_TRUE(channel_->SetSendCodecs(codec_list));
+  EXPECT_TRUE(channel_->SetSend(true));
+
+  int64 timestamp = time(NULL) * talk_base::kNumNanosecsPerSec;
+  SendI420ScreencastFrameWithTimestamp(
+      kVP8Codec.width, kVP8Codec.height, timestamp);
+  EXPECT_EQ(talk_base::UnixTimestampNanosecsToNtpMillisecs(timestamp),
+      vie_.GetCaptureLastTimestamp(capture_id));
+
+  SendI420ScreencastFrameWithTimestamp(kVP8Codec.width, kVP8Codec.height, 0);
+  EXPECT_EQ(0, vie_.GetCaptureLastTimestamp(capture_id));
+}
+#endif
diff --git a/talk/media/webrtc/webrtcvideoframe.cc b/talk/media/webrtc/webrtcvideoframe.cc
new file mode 100644
index 0000000..80f2481
--- /dev/null
+++ b/talk/media/webrtc/webrtcvideoframe.cc
@@ -0,0 +1,355 @@
+/*
+ * 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/media/webrtc/webrtcvideoframe.h"
+
+#include "libyuv/convert.h"
+#include "libyuv/convert_from.h"
+#include "libyuv/planar_functions.h"
+#include "talk/base/logging.h"
+#include "talk/media/base/videocapturer.h"
+#include "talk/media/base/videocommon.h"
+
+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;
+
+FrameBuffer::FrameBuffer() : length_(0) {}
+
+FrameBuffer::FrameBuffer(size_t length) : length_(0) {
+  char* buffer = new char[length];
+  SetData(buffer, length);
+}
+
+FrameBuffer::~FrameBuffer() {
+  // Make sure that the video_frame_ doesn't delete the buffer as it may be
+  // shared between multiple WebRtcVideoFrame.
+  uint8_t* new_memory = NULL;
+  uint32_t new_length = 0;
+  uint32_t new_size = 0;
+  video_frame_.Swap(new_memory, new_length, new_size);
+}
+
+void FrameBuffer::SetData(char* data, size_t length) {
+  data_.reset(data);
+  length_ = length;
+  uint8_t* new_memory = reinterpret_cast<uint8_t*>(data);
+  uint32_t new_length = length;
+  uint32_t new_size = length;
+  video_frame_.Swap(new_memory, new_length, new_size);
+}
+
+void FrameBuffer::ReturnData(char** data, size_t* length) {
+  uint8_t* old_memory = NULL;
+  uint32_t old_length = 0;
+  uint32_t old_size = 0;
+  video_frame_.Swap(old_memory, old_length, old_size);
+  data_.release();
+  length_ = 0;
+  *length = old_length;
+  *data = reinterpret_cast<char*>(old_memory);
+}
+
+char* FrameBuffer::data() { return data_.get(); }
+
+size_t FrameBuffer::length() const { return length_; }
+
+webrtc::VideoFrame* FrameBuffer::frame() { return &video_frame_; }
+
+const webrtc::VideoFrame* FrameBuffer::frame() const { return &video_frame_; }
+
+WebRtcVideoFrame::WebRtcVideoFrame()
+    : video_buffer_(new RefCountedBuffer()), is_black_(false) {}
+
+WebRtcVideoFrame::~WebRtcVideoFrame() {}
+
+bool WebRtcVideoFrame::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,
+    int64 elapsed_time, int64 time_stamp, int rotation) {
+  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 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) {
+  InitToEmptyBuffer(w, h, pixel_width, pixel_height, elapsed_time, time_stamp);
+  if (!is_black_) {
+    return SetToBlack();
+  }
+  return true;
+}
+
+void WebRtcVideoFrame::Attach(
+    uint8* buffer, size_t buffer_size, int w, int h, size_t pixel_width,
+    size_t pixel_height, int64 elapsed_time, int64 time_stamp, int rotation) {
+  talk_base::scoped_refptr<RefCountedBuffer> video_buffer(
+      new RefCountedBuffer());
+  video_buffer->SetData(reinterpret_cast<char*>(buffer), buffer_size);
+  Attach(video_buffer.get(), buffer_size, w, h, pixel_width, pixel_height,
+         elapsed_time, time_stamp, rotation);
+}
+
+void WebRtcVideoFrame::Detach(uint8** data, size_t* length) {
+  video_buffer_->ReturnData(reinterpret_cast<char**>(data), length);
+}
+
+size_t WebRtcVideoFrame::GetWidth() const { return frame()->Width(); }
+
+size_t WebRtcVideoFrame::GetHeight() const { return frame()->Height(); }
+
+const uint8* WebRtcVideoFrame::GetYPlane() const {
+  uint8_t* buffer = frame()->Buffer();
+  return buffer;
+}
+
+const uint8* WebRtcVideoFrame::GetUPlane() const {
+  uint8_t* buffer = frame()->Buffer();
+  if (buffer) {
+    buffer += (frame()->Width() * frame()->Height());
+  }
+  return buffer;
+}
+
+const uint8* WebRtcVideoFrame::GetVPlane() const {
+  uint8_t* buffer = frame()->Buffer();
+  if (buffer) {
+    int uv_size = GetChromaSize();
+    buffer += frame()->Width() * frame()->Height() + uv_size;
+  }
+  return buffer;
+}
+
+uint8* WebRtcVideoFrame::GetYPlane() {
+  uint8_t* buffer = frame()->Buffer();
+  return buffer;
+}
+
+uint8* WebRtcVideoFrame::GetUPlane() {
+  uint8_t* buffer = frame()->Buffer();
+  if (buffer) {
+    buffer += (frame()->Width() * frame()->Height());
+  }
+  return buffer;
+}
+
+uint8* WebRtcVideoFrame::GetVPlane() {
+  uint8_t* buffer = frame()->Buffer();
+  if (buffer) {
+    int uv_size = GetChromaSize();
+    buffer += frame()->Width() * frame()->Height() + uv_size;
+  }
+  return buffer;
+}
+
+VideoFrame* WebRtcVideoFrame::Copy() const {
+  const char* old_buffer = video_buffer_->data();
+  if (!old_buffer)
+    return NULL;
+  size_t new_buffer_size = video_buffer_->length();
+
+  WebRtcVideoFrame* ret_val = new WebRtcVideoFrame();
+  ret_val->Attach(video_buffer_.get(), new_buffer_size, frame()->Width(),
+                  frame()->Height(), pixel_width_, pixel_height_, elapsed_time_,
+                  time_stamp_, rotation_);
+  return ret_val;
+}
+
+bool WebRtcVideoFrame::MakeExclusive() {
+  const int length = video_buffer_->length();
+  RefCountedBuffer* exclusive_buffer = new RefCountedBuffer(length);
+  memcpy(exclusive_buffer->data(), video_buffer_->data(), length);
+  Attach(exclusive_buffer, length, frame()->Width(), frame()->Height(),
+         pixel_width_, pixel_height_, elapsed_time_, time_stamp_, rotation_);
+  return true;
+}
+
+size_t WebRtcVideoFrame::CopyToBuffer(uint8* buffer, size_t size) const {
+  if (!frame()->Buffer()) {
+    return 0;
+  }
+
+  size_t needed = frame()->Length();
+  if (needed <= size) {
+    memcpy(buffer, frame()->Buffer(), needed);
+  }
+  return needed;
+}
+
+// TODO(fbarchard): Refactor into base class and share with lmi
+size_t WebRtcVideoFrame::ConvertToRgbBuffer(uint32 to_fourcc, uint8* buffer,
+                                            size_t size, int stride_rgb) const {
+  if (!frame()->Buffer()) {
+    return 0;
+  }
+  size_t width = frame()->Width();
+  size_t height = frame()->Height();
+  size_t needed = (stride_rgb >= 0 ? stride_rgb : -stride_rgb) * height;
+  if (size < needed) {
+    LOG(LS_WARNING) << "RGB buffer is not large enough";
+    return needed;
+  }
+
+  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::Attach(
+    RefCountedBuffer* video_buffer, size_t buffer_size, int w, int h,
+    size_t pixel_width, size_t pixel_height, int64 elapsed_time,
+    int64 time_stamp, int rotation) {
+  if (video_buffer_.get() == video_buffer) {
+    return;
+  }
+  is_black_ = false;
+  video_buffer_ = video_buffer;
+  frame()->SetWidth(w);
+  frame()->SetHeight(h);
+  pixel_width_ = pixel_width;
+  pixel_height_ = pixel_height;
+  elapsed_time_ = elapsed_time;
+  time_stamp_ = time_stamp;
+  rotation_ = rotation;
+}
+
+// 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();
+
+  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;
+}
+
+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) {
+  if (!Validate(format, w, h, sample, sample_size)) {
+    return false;
+  }
+  // Translate aliases to standard enums (e.g., IYUV -> I420).
+  format = CanonicalFourCC(format);
+
+  // Round display width and height down to multiple of 4, to avoid webrtc
+  // size calculation error on odd sizes.
+  // TODO(Ronghua): Remove this once the webrtc allocator is fixed.
+  dw = (dw > 4) ? (dw & ~3) : dw;
+  dh = (dh > 4) ? (dh & ~3) : dh;
+
+  // Set up a new buffer.
+  // TODO(fbarchard): Support lazy allocation.
+  int new_width = dw;
+  int new_height = dh;
+  if (rotation == 90 || rotation == 270) {  // If rotated swap width, height.
+    new_width = dh;
+    new_height = dw;
+  }
+
+  size_t desired_size = SizeOf(new_width, new_height);
+  talk_base::scoped_refptr<RefCountedBuffer> video_buffer(
+      new RefCountedBuffer(desired_size));
+  // Since the libyuv::ConvertToI420 will handle the rotation, so the
+  // new frame's rotation should always be 0.
+  Attach(video_buffer.get(), desired_size, new_width, new_height, pixel_width,
+         pixel_height, elapsed_time, time_stamp, 0);
+
+  int horiz_crop = ((w - dw) / 2) & ~1;
+  // ARGB on Windows has negative height.
+  // The sample's layout in memory is normal, so just correct crop.
+  int vert_crop = ((abs(h) - dh) / 2) & ~1;
+  // Conversion functions expect negative height to flip the image.
+  int idh = (h < 0) ? -dh : dh;
+  uint8* y = GetYPlane();
+  int y_stride = GetYPitch();
+  uint8* u = GetUPlane();
+  int u_stride = GetUPitch();
+  uint8* v = GetVPlane();
+  int v_stride = GetVPitch();
+  int r = libyuv::ConvertToI420(
+      sample, sample_size, y, y_stride, u, u_stride, v, v_stride, horiz_crop,
+      vert_crop, w, h, dw, idh, static_cast<libyuv::RotationMode>(rotation),
+      format);
+  if (r) {
+    LOG(LS_ERROR) << "Error parsing format: " << GetFourccName(format)
+                  << " return code : " << r;
+    return false;
+  }
+  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);
+  talk_base::scoped_refptr<RefCountedBuffer> video_buffer(
+      new RefCountedBuffer(buffer_size));
+  Attach(video_buffer.get(), buffer_size, w, h, pixel_width, pixel_height,
+         elapsed_time, time_stamp, 0);
+}
+
+}  // namespace cricket
diff --git a/talk/media/webrtc/webrtcvideoframe.h b/talk/media/webrtc/webrtcvideoframe.h
new file mode 100644
index 0000000..03a3196
--- /dev/null
+++ b/talk/media/webrtc/webrtcvideoframe.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.
+ */
+
+#ifndef TALK_MEDIA_WEBRTCVIDEOFRAME_H_
+#define TALK_MEDIA_WEBRTCVIDEOFRAME_H_
+
+#include "talk/base/buffer.h"
+#include "talk/base/refcount.h"
+#include "talk/base/scoped_ref_ptr.h"
+#include "talk/media/base/videoframe.h"
+#include "webrtc/common_types.h"
+#include "webrtc/modules/interface/module_common_types.h"
+
+namespace cricket {
+
+struct CapturedFrame;
+
+// Class that takes ownership of the frame passed to it.
+class FrameBuffer {
+ public:
+  FrameBuffer();
+  explicit FrameBuffer(size_t length);
+  ~FrameBuffer();
+
+  void SetData(char* data, size_t length);
+  void ReturnData(char** data, size_t* length);
+  char* data();
+  size_t length() const;
+
+  webrtc::VideoFrame* frame();
+  const webrtc::VideoFrame* frame() const;
+
+ private:
+  talk_base::scoped_array<char> data_;
+  size_t length_;
+  webrtc::VideoFrame video_frame_;
+};
+
+class WebRtcVideoFrame : public VideoFrame {
+ public:
+  typedef talk_base::RefCountedObject<FrameBuffer> RefCountedBuffer;
+
+  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,
+            int64 elapsed_time, int64 time_stamp, int rotation);
+
+  bool Init(const CapturedFrame* frame, int dw, int dh);
+
+  bool InitToBlack(int w, int h, size_t pixel_width, size_t pixel_height,
+                   int64 elapsed_time, int64 time_stamp);
+
+  void Attach(uint8* buffer, size_t buffer_size, int w, int h,
+              size_t pixel_width, size_t pixel_height, int64 elapsed_time,
+              int64 time_stamp, int rotation);
+
+  void Detach(uint8** data, size_t* length);
+  bool AddWatermark();
+  webrtc::VideoFrame* frame() { return video_buffer_->frame(); }
+  webrtc::VideoFrame* frame() const { return video_buffer_->frame(); }
+
+  // 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;
+  virtual const uint8* GetYPlane() const;
+  virtual const uint8* GetUPlane() const;
+  virtual const uint8* GetVPlane() const;
+  virtual uint8* GetYPlane();
+  virtual uint8* GetUPlane();
+  virtual uint8* GetVPlane();
+  virtual int32 GetYPitch() const { return frame()->Width(); }
+  virtual int32 GetUPitch() const { return (frame()->Width() + 1) / 2; }
+  virtual int32 GetVPitch() const { return (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 time_stamp_; }
+  virtual void SetElapsedTime(int64 elapsed_time) {
+    elapsed_time_ = elapsed_time;
+  }
+  virtual void SetTimeStamp(int64 time_stamp) { time_stamp_ = time_stamp; }
+
+  virtual int GetRotation() const { return rotation_; }
+
+  virtual VideoFrame* Copy() const;
+  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, int stride_rgb) const;
+
+ private:
+  void Attach(RefCountedBuffer* video_buffer, size_t buffer_size, int w, int h,
+              size_t pixel_width, size_t pixel_height, int64 elapsed_time,
+              int64 time_stamp, int rotation);
+
+  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);
+
+  talk_base::scoped_refptr<RefCountedBuffer> video_buffer_;
+  bool is_black_;
+  size_t pixel_width_;
+  size_t pixel_height_;
+  int64 elapsed_time_;
+  int64 time_stamp_;
+  int rotation_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_WEBRTCVIDEOFRAME_H_
diff --git a/talk/media/webrtc/webrtcvideoframe_unittest.cc b/talk/media/webrtc/webrtcvideoframe_unittest.cc
new file mode 100644
index 0000000..2f0decb
--- /dev/null
+++ b/talk/media/webrtc/webrtcvideoframe_unittest.cc
@@ -0,0 +1,313 @@
+/*
+ * 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/media/base/videoframe_unittest.h"
+#include "talk/media/webrtc/webrtcvideoframe.h"
+
+extern int FLAG_yuvconverter_repeat;  // From lmivideoframe_unittest.cc.
+
+class WebRtcVideoFrameTest : public VideoFrameTest<cricket::WebRtcVideoFrame> {
+ public:
+  WebRtcVideoFrameTest() {
+    repeat_ = FLAG_yuvconverter_repeat;
+  }
+
+  void TestInit(int cropped_width, int cropped_height) {
+    const int frame_width = 1920;
+    const int frame_height = 1080;
+
+    // Build the CapturedFrame.
+    cricket::CapturedFrame captured_frame;
+    captured_frame.fourcc = cricket::FOURCC_I420;
+    captured_frame.pixel_width = 1;
+    captured_frame.pixel_height = 1;
+    captured_frame.elapsed_time = 1234;
+    captured_frame.time_stamp = 5678;
+    captured_frame.rotation = 0;
+    captured_frame.width = frame_width;
+    captured_frame.height = frame_height;
+    captured_frame.data_size = (frame_width * frame_height) +
+        ((frame_width + 1) / 2) * ((frame_height + 1) / 2) * 2;
+    talk_base::scoped_array<uint8> captured_frame_buffer(
+        new uint8[captured_frame.data_size]);
+    captured_frame.data = captured_frame_buffer.get();
+
+    // Create the new frame from the CapturedFrame.
+    cricket::WebRtcVideoFrame frame;
+    EXPECT_TRUE(frame.Init(&captured_frame, cropped_width, cropped_height));
+
+    // Verify the new frame.
+    EXPECT_EQ(1u, frame.GetPixelWidth());
+    EXPECT_EQ(1u, frame.GetPixelHeight());
+    EXPECT_EQ(1234, frame.GetElapsedTime());
+    EXPECT_EQ(5678, frame.GetTimeStamp());
+    EXPECT_EQ(0, frame.GetRotation());
+    // The size of the new frame should have been cropped to multiple of 4.
+    EXPECT_EQ(static_cast<size_t>(cropped_width & ~3), frame.GetWidth());
+    EXPECT_EQ(static_cast<size_t>(cropped_height & ~3), frame.GetHeight());
+  }
+};
+
+#define TEST_WEBRTCVIDEOFRAME(X) TEST_F(WebRtcVideoFrameTest, X) { \
+  VideoFrameTest<cricket::WebRtcVideoFrame>::X(); \
+}
+
+TEST_WEBRTCVIDEOFRAME(ConstructI420)
+TEST_WEBRTCVIDEOFRAME(ConstructI422)
+TEST_WEBRTCVIDEOFRAME(ConstructYuy2)
+TEST_WEBRTCVIDEOFRAME(ConstructYuy2Unaligned)
+TEST_WEBRTCVIDEOFRAME(ConstructYuy2Wide)
+TEST_WEBRTCVIDEOFRAME(ConstructYV12)
+TEST_WEBRTCVIDEOFRAME(ConstructUyvy)
+TEST_WEBRTCVIDEOFRAME(ConstructM420)
+TEST_WEBRTCVIDEOFRAME(ConstructQ420)
+TEST_WEBRTCVIDEOFRAME(ConstructNV21)
+TEST_WEBRTCVIDEOFRAME(ConstructNV12)
+TEST_WEBRTCVIDEOFRAME(ConstructABGR)
+TEST_WEBRTCVIDEOFRAME(ConstructARGB)
+TEST_WEBRTCVIDEOFRAME(ConstructARGBWide)
+TEST_WEBRTCVIDEOFRAME(ConstructBGRA)
+TEST_WEBRTCVIDEOFRAME(Construct24BG)
+TEST_WEBRTCVIDEOFRAME(ConstructRaw)
+TEST_WEBRTCVIDEOFRAME(ConstructRGB565)
+TEST_WEBRTCVIDEOFRAME(ConstructARGB1555)
+TEST_WEBRTCVIDEOFRAME(ConstructARGB4444)
+
+TEST_WEBRTCVIDEOFRAME(ConstructI420Mirror)
+TEST_WEBRTCVIDEOFRAME(ConstructI420Rotate0)
+TEST_WEBRTCVIDEOFRAME(ConstructI420Rotate90)
+TEST_WEBRTCVIDEOFRAME(ConstructI420Rotate180)
+TEST_WEBRTCVIDEOFRAME(ConstructI420Rotate270)
+TEST_WEBRTCVIDEOFRAME(ConstructYV12Rotate0)
+TEST_WEBRTCVIDEOFRAME(ConstructYV12Rotate90)
+TEST_WEBRTCVIDEOFRAME(ConstructYV12Rotate180)
+TEST_WEBRTCVIDEOFRAME(ConstructYV12Rotate270)
+TEST_WEBRTCVIDEOFRAME(ConstructNV12Rotate0)
+TEST_WEBRTCVIDEOFRAME(ConstructNV12Rotate90)
+TEST_WEBRTCVIDEOFRAME(ConstructNV12Rotate180)
+TEST_WEBRTCVIDEOFRAME(ConstructNV12Rotate270)
+TEST_WEBRTCVIDEOFRAME(ConstructNV21Rotate0)
+TEST_WEBRTCVIDEOFRAME(ConstructNV21Rotate90)
+TEST_WEBRTCVIDEOFRAME(ConstructNV21Rotate180)
+TEST_WEBRTCVIDEOFRAME(ConstructNV21Rotate270)
+TEST_WEBRTCVIDEOFRAME(ConstructUYVYRotate0)
+TEST_WEBRTCVIDEOFRAME(ConstructUYVYRotate90)
+TEST_WEBRTCVIDEOFRAME(ConstructUYVYRotate180)
+TEST_WEBRTCVIDEOFRAME(ConstructUYVYRotate270)
+TEST_WEBRTCVIDEOFRAME(ConstructYUY2Rotate0)
+TEST_WEBRTCVIDEOFRAME(ConstructYUY2Rotate90)
+TEST_WEBRTCVIDEOFRAME(ConstructYUY2Rotate180)
+TEST_WEBRTCVIDEOFRAME(ConstructYUY2Rotate270)
+TEST_WEBRTCVIDEOFRAME(ConstructI4201Pixel)
+TEST_WEBRTCVIDEOFRAME(ConstructI4205Pixel)
+// TODO(juberti): 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(juberti): WebRtcVideoFrame is not currently refcounted.
+// TEST_WEBRTCVIDEOFRAME(ConstructCopy)
+// TEST_WEBRTCVIDEOFRAME(ConstructCopyIsRef)
+TEST_WEBRTCVIDEOFRAME(ConstructBlack)
+// TODO(fbarchard): Implement Jpeg
+// TEST_WEBRTCVIDEOFRAME(ConstructMjpgI420)
+// TEST_WEBRTCVIDEOFRAME(ConstructMjpgI422)
+// TEST_WEBRTCVIDEOFRAME(ConstructMjpgI444)
+// TEST_WEBRTCVIDEOFRAME(ConstructMjpgI411)
+// TEST_WEBRTCVIDEOFRAME(ConstructMjpgI400)
+// TEST_WEBRTCVIDEOFRAME(ValidateMjpgI420)
+// TEST_WEBRTCVIDEOFRAME(ValidateMjpgI422)
+// TEST_WEBRTCVIDEOFRAME(ValidateMjpgI444)
+// TEST_WEBRTCVIDEOFRAME(ValidateMjpgI411)
+// TEST_WEBRTCVIDEOFRAME(ValidateMjpgI400)
+TEST_WEBRTCVIDEOFRAME(ValidateI420)
+TEST_WEBRTCVIDEOFRAME(ValidateI420SmallSize)
+TEST_WEBRTCVIDEOFRAME(ValidateI420LargeSize)
+TEST_WEBRTCVIDEOFRAME(ValidateI420HugeSize)
+// TEST_WEBRTCVIDEOFRAME(ValidateMjpgI420InvalidSize)
+// TEST_WEBRTCVIDEOFRAME(ValidateI420InvalidSize)
+
+// TODO(fbarchard): WebRtcVideoFrame does not support odd sizes.
+// Re-evaluate once WebRTC switches to libyuv
+// TEST_WEBRTCVIDEOFRAME(ConstructYuy2AllSizes)
+// TEST_WEBRTCVIDEOFRAME(ConstructARGBAllSizes)
+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(CopyToFrame)
+TEST_WEBRTCVIDEOFRAME(Write)
+TEST_WEBRTCVIDEOFRAME(CopyToBuffer1Pixel)
+// TEST_WEBRTCVIDEOFRAME(ConstructARGBBlackWhitePixel)
+
+TEST_WEBRTCVIDEOFRAME(StretchToFrame)
+TEST_WEBRTCVIDEOFRAME(Copy)
+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_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));
+}
+
+// Tests the Init function with different cropped size.
+TEST_F(WebRtcVideoFrameTest, InitEvenSize) {
+  TestInit(640, 360);
+}
+
+TEST_F(WebRtcVideoFrameTest, InitOddWidth) {
+  TestInit(601, 480);
+}
+
+TEST_F(WebRtcVideoFrameTest, InitOddHeight) {
+  TestInit(360, 765);
+}
+
+TEST_F(WebRtcVideoFrameTest, InitOddWidthHeight) {
+  TestInit(355, 1021);
+}
diff --git a/talk/media/webrtc/webrtcvie.h b/talk/media/webrtc/webrtcvie.h
new file mode 100644
index 0000000..9550962
--- /dev/null
+++ b/talk/media/webrtc/webrtcvie.h
@@ -0,0 +1,151 @@
+/*
+ * 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_MEDIA_WEBRTCVIE_H_
+#define TALK_MEDIA_WEBRTCVIE_H_
+
+#include "talk/base/common.h"
+#include "talk/media/webrtc/webrtccommon.h"
+#include "webrtc/common_types.h"
+#include "webrtc/modules/interface/module_common_types.h"
+#include "webrtc/modules/video_capture/include/video_capture.h"
+#include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h"
+#include "webrtc/modules/video_render/include/video_render.h"
+#include "webrtc/video_engine/include/vie_base.h"
+#include "webrtc/video_engine/include/vie_capture.h"
+#include "webrtc/video_engine/include/vie_codec.h"
+#include "webrtc/video_engine/include/vie_errors.h"
+#include "webrtc/video_engine/include/vie_external_codec.h"
+#include "webrtc/video_engine/include/vie_image_process.h"
+#include "webrtc/video_engine/include/vie_network.h"
+#include "webrtc/video_engine/include/vie_render.h"
+#include "webrtc/video_engine/include/vie_rtp_rtcp.h"
+
+namespace cricket {
+
+// all tracing macros should go to a common file
+
+// automatically handles lifetime of VideoEngine
+class scoped_vie_engine {
+ public:
+  explicit scoped_vie_engine(webrtc::VideoEngine* e) : ptr(e) {}
+  // VERIFY, to ensure that there are no leaks at shutdown
+  ~scoped_vie_engine() {
+    if (ptr) {
+      webrtc::VideoEngine::Delete(ptr);
+    }
+  }
+  webrtc::VideoEngine* get() const { return ptr; }
+ private:
+  webrtc::VideoEngine* ptr;
+};
+
+// scoped_ptr class to handle obtaining and releasing VideoEngine
+// interface pointers
+template<class T> class scoped_vie_ptr {
+ public:
+  explicit scoped_vie_ptr(const scoped_vie_engine& e)
+       : ptr(T::GetInterface(e.get())) {}
+  explicit scoped_vie_ptr(T* p) : ptr(p) {}
+  ~scoped_vie_ptr() { if (ptr) ptr->Release(); }
+  T* operator->() const { return ptr; }
+  T* get() const { return ptr; }
+ private:
+  T* ptr;
+};
+
+// Utility class for aggregating the various WebRTC interface.
+// Fake implementations can also be injected for testing.
+class ViEWrapper {
+ public:
+  ViEWrapper()
+      : engine_(webrtc::VideoEngine::Create()),
+        base_(engine_), codec_(engine_), capture_(engine_),
+        network_(engine_), render_(engine_), rtp_(engine_),
+        image_(engine_), ext_codec_(engine_) {
+  }
+
+  ViEWrapper(webrtc::ViEBase* base, webrtc::ViECodec* codec,
+             webrtc::ViECapture* capture, webrtc::ViENetwork* network,
+             webrtc::ViERender* render, webrtc::ViERTP_RTCP* rtp,
+             webrtc::ViEImageProcess* image,
+             webrtc::ViEExternalCodec* ext_codec)
+      : engine_(NULL),
+        base_(base),
+        codec_(codec),
+        capture_(capture),
+        network_(network),
+        render_(render),
+        rtp_(rtp),
+        image_(image),
+        ext_codec_(ext_codec) {
+  }
+
+  virtual ~ViEWrapper() {}
+  webrtc::VideoEngine* engine() { return engine_.get(); }
+  webrtc::ViEBase* base() { return base_.get(); }
+  webrtc::ViECodec* codec() { return codec_.get(); }
+  webrtc::ViECapture* capture() { return capture_.get(); }
+  webrtc::ViENetwork* network() { return network_.get(); }
+  webrtc::ViERender* render() { return render_.get(); }
+  webrtc::ViERTP_RTCP* rtp() { return rtp_.get(); }
+  webrtc::ViEImageProcess* image() { return image_.get(); }
+  webrtc::ViEExternalCodec* ext_codec() { return ext_codec_.get(); }
+  int error() { return base_->LastError(); }
+
+ private:
+  scoped_vie_engine engine_;
+  scoped_vie_ptr<webrtc::ViEBase> base_;
+  scoped_vie_ptr<webrtc::ViECodec> codec_;
+  scoped_vie_ptr<webrtc::ViECapture> capture_;
+  scoped_vie_ptr<webrtc::ViENetwork> network_;
+  scoped_vie_ptr<webrtc::ViERender> render_;
+  scoped_vie_ptr<webrtc::ViERTP_RTCP> rtp_;
+  scoped_vie_ptr<webrtc::ViEImageProcess> image_;
+  scoped_vie_ptr<webrtc::ViEExternalCodec> ext_codec_;
+};
+
+// 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_MEDIA_WEBRTCVIE_H_
diff --git a/talk/media/webrtc/webrtcvoe.h b/talk/media/webrtc/webrtcvoe.h
new file mode 100644
index 0000000..bc8358d
--- /dev/null
+++ b/talk/media/webrtc/webrtcvoe.h
@@ -0,0 +1,179 @@
+/*
+ * 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_MEDIA_WEBRTCVOE_H_
+#define TALK_MEDIA_WEBRTCVOE_H_
+
+#include "talk/base/common.h"
+#include "talk/media/webrtc/webrtccommon.h"
+
+#include "webrtc/common_types.h"
+#include "webrtc/modules/audio_device/include/audio_device.h"
+#include "webrtc/voice_engine/include/voe_audio_processing.h"
+#include "webrtc/voice_engine/include/voe_base.h"
+#include "webrtc/voice_engine/include/voe_codec.h"
+#include "webrtc/voice_engine/include/voe_dtmf.h"
+#include "webrtc/voice_engine/include/voe_errors.h"
+#include "webrtc/voice_engine/include/voe_external_media.h"
+#include "webrtc/voice_engine/include/voe_file.h"
+#include "webrtc/voice_engine/include/voe_hardware.h"
+#include "webrtc/voice_engine/include/voe_neteq_stats.h"
+#include "webrtc/voice_engine/include/voe_network.h"
+#include "webrtc/voice_engine/include/voe_rtp_rtcp.h"
+#include "webrtc/voice_engine/include/voe_video_sync.h"
+#include "webrtc/voice_engine/include/voe_volume_control.h"
+
+namespace cricket {
+// automatically handles lifetime of WebRtc VoiceEngine
+class scoped_voe_engine {
+ public:
+  explicit scoped_voe_engine(webrtc::VoiceEngine* e) : ptr(e) {}
+  // VERIFY, to ensure that there are no leaks at shutdown
+  ~scoped_voe_engine() { if (ptr) VERIFY(webrtc::VoiceEngine::Delete(ptr)); }
+  // Releases the current pointer.
+  void reset() {
+    if (ptr) {
+      VERIFY(webrtc::VoiceEngine::Delete(ptr));
+      ptr = NULL;
+    }
+  }
+  webrtc::VoiceEngine* get() const { return ptr; }
+ private:
+  webrtc::VoiceEngine* ptr;
+};
+
+// scoped_ptr class to handle obtaining and releasing WebRTC interface pointers
+template<class T>
+class scoped_voe_ptr {
+ public:
+  explicit scoped_voe_ptr(const scoped_voe_engine& e)
+      : ptr(T::GetInterface(e.get())) {}
+  explicit scoped_voe_ptr(T* p) : ptr(p) {}
+  ~scoped_voe_ptr() { if (ptr) ptr->Release(); }
+  T* operator->() const { return ptr; }
+  T* get() const { return ptr; }
+
+  // Releases the current pointer.
+  void reset() {
+    if (ptr) {
+      ptr->Release();
+      ptr = NULL;
+    }
+  }
+
+ private:
+  T* ptr;
+};
+
+// Utility class for aggregating the various WebRTC interface.
+// Fake implementations can also be injected for testing.
+class VoEWrapper {
+ public:
+  VoEWrapper()
+      : engine_(webrtc::VoiceEngine::Create()), processing_(engine_),
+        base_(engine_), codec_(engine_), dtmf_(engine_), file_(engine_),
+        hw_(engine_), media_(engine_), neteq_(engine_), network_(engine_),
+        rtp_(engine_), sync_(engine_), volume_(engine_) {
+  }
+  VoEWrapper(webrtc::VoEAudioProcessing* processing,
+             webrtc::VoEBase* base,
+             webrtc::VoECodec* codec,
+             webrtc::VoEDtmf* dtmf,
+             webrtc::VoEFile* file,
+             webrtc::VoEHardware* hw,
+             webrtc::VoEExternalMedia* media,
+             webrtc::VoENetEqStats* neteq,
+             webrtc::VoENetwork* network,
+             webrtc::VoERTP_RTCP* rtp,
+             webrtc::VoEVideoSync* sync,
+             webrtc::VoEVolumeControl* volume)
+      : engine_(NULL),
+        processing_(processing),
+        base_(base),
+        codec_(codec),
+        dtmf_(dtmf),
+        file_(file),
+        hw_(hw),
+        media_(media),
+        neteq_(neteq),
+        network_(network),
+        rtp_(rtp),
+        sync_(sync),
+        volume_(volume) {
+  }
+  ~VoEWrapper() {}
+  webrtc::VoiceEngine* engine() const { return engine_.get(); }
+  webrtc::VoEAudioProcessing* processing() const { return processing_.get(); }
+  webrtc::VoEBase* base() const { return base_.get(); }
+  webrtc::VoECodec* codec() const { return codec_.get(); }
+  webrtc::VoEDtmf* dtmf() const { return dtmf_.get(); }
+  webrtc::VoEFile* file() const { return file_.get(); }
+  webrtc::VoEHardware* hw() const { return hw_.get(); }
+  webrtc::VoEExternalMedia* media() const { return media_.get(); }
+  webrtc::VoENetEqStats* neteq() const { return neteq_.get(); }
+  webrtc::VoENetwork* network() const { return network_.get(); }
+  webrtc::VoERTP_RTCP* rtp() const { return rtp_.get(); }
+  webrtc::VoEVideoSync* sync() const { return sync_.get(); }
+  webrtc::VoEVolumeControl* volume() const { return volume_.get(); }
+  int error() { return base_->LastError(); }
+
+ private:
+  scoped_voe_engine engine_;
+  scoped_voe_ptr<webrtc::VoEAudioProcessing> processing_;
+  scoped_voe_ptr<webrtc::VoEBase> base_;
+  scoped_voe_ptr<webrtc::VoECodec> codec_;
+  scoped_voe_ptr<webrtc::VoEDtmf> dtmf_;
+  scoped_voe_ptr<webrtc::VoEFile> file_;
+  scoped_voe_ptr<webrtc::VoEHardware> hw_;
+  scoped_voe_ptr<webrtc::VoEExternalMedia> media_;
+  scoped_voe_ptr<webrtc::VoENetEqStats> neteq_;
+  scoped_voe_ptr<webrtc::VoENetwork> network_;
+  scoped_voe_ptr<webrtc::VoERTP_RTCP> rtp_;
+  scoped_voe_ptr<webrtc::VoEVideoSync> sync_;
+  scoped_voe_ptr<webrtc::VoEVolumeControl> volume_;
+};
+
+// Adds indirection to static WebRtc functions, allowing them to be mocked.
+class VoETraceWrapper {
+ public:
+  virtual ~VoETraceWrapper() {}
+
+  virtual int SetTraceFilter(const unsigned int filter) {
+    return webrtc::VoiceEngine::SetTraceFilter(filter);
+  }
+  virtual int SetTraceFile(const char* fileNameUTF8) {
+    return webrtc::VoiceEngine::SetTraceFile(fileNameUTF8);
+  }
+  virtual int SetTraceCallback(webrtc::TraceCallback* callback) {
+    return webrtc::VoiceEngine::SetTraceCallback(callback);
+  }
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_WEBRTCVOE_H_
diff --git a/talk/media/webrtc/webrtcvoiceengine.cc b/talk/media/webrtc/webrtcvoiceengine.cc
new file mode 100644
index 0000000..266cefc
--- /dev/null
+++ b/talk/media/webrtc/webrtcvoiceengine.cc
@@ -0,0 +1,2777 @@
+/*
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_WEBRTC_VOICE
+
+#include "talk/media/webrtc/webrtcvoiceengine.h"
+
+#include <algorithm>
+#include <cstdio>
+#include <string>
+#include <vector>
+
+#include "talk/base/base64.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+#include "talk/media/base/audiorenderer.h"
+#include "talk/media/base/constants.h"
+#include "talk/media/base/streamparams.h"
+#include "talk/media/base/voiceprocessor.h"
+#include "talk/media/webrtc/webrtcvoe.h"
+#include "webrtc/modules/audio_processing/include/audio_processing.h"
+
+#ifdef WIN32
+#include <objbase.h>  // NOLINT
+#endif
+
+namespace cricket {
+
+struct CodecPref {
+  const char* name;
+  int clockrate;
+  int channels;
+  int payload_type;
+  bool is_multi_rate;
+};
+
+static const CodecPref kCodecPrefs[] = {
+  { "OPUS",   48000,  2, 111, true },
+  { "ISAC",   16000,  1, 103, true },
+  { "ISAC",   32000,  1, 104, true },
+  { "CELT",   32000,  1, 109, true },
+  { "CELT",   32000,  2, 110, true },
+  { "G722",   16000,  1, 9,   false },
+  { "ILBC",   8000,   1, 102, false },
+  { "PCMU",   8000,   1, 0,   false },
+  { "PCMA",   8000,   1, 8,   false },
+  { "CN",     48000,  1, 107, false },
+  { "CN",     32000,  1, 106, false },
+  { "CN",     16000,  1, 105, false },
+  { "CN",     8000,   1, 13,  false },
+  { "red",    8000,   1, 127, false },
+  { "telephone-event", 8000, 1, 126, false },
+};
+
+// For Linux/Mac, using the default device is done by specifying index 0 for
+// VoE 4.0 and not -1 (which was the case for VoE 3.5).
+//
+// On Windows Vista and newer, Microsoft introduced the concept of "Default
+// Communications Device". This means that there are two types of default
+// devices (old Wave Audio style default and Default Communications Device).
+//
+// On Windows systems which only support Wave Audio style default, uses either
+// -1 or 0 to select the default device.
+//
+// On Windows systems which support both "Default Communication Device" and
+// old Wave Audio style default, use -1 for Default Communications Device and
+// -2 for Wave Audio style default, which is what we want to use for clips.
+// It's not clear yet whether the -2 index is handled properly on other OSes.
+
+#ifdef WIN32
+static const int kDefaultAudioDeviceId = -1;
+static const int kDefaultSoundclipDeviceId = -2;
+#else
+static const int kDefaultAudioDeviceId = 0;
+#endif
+
+// extension header for audio levels, as defined in
+// http://tools.ietf.org/html/draft-ietf-avtext-client-to-mixer-audio-level-03
+static const char kRtpAudioLevelHeaderExtension[] =
+    "urn:ietf:params:rtp-hdrext:ssrc-audio-level";
+static const int kRtpAudioLevelHeaderExtensionId = 1;
+
+static const char kIsacCodecName[] = "ISAC";
+static const char kL16CodecName[] = "L16";
+// Codec parameters for Opus.
+static const int kOpusMonoBitrate = 32000;
+// Parameter used for NACK.
+// This value is equivalent to 5 seconds of audio data at 20 ms per packet.
+static const int kNackMaxPackets = 250;
+static const int kOpusStereoBitrate = 64000;
+
+// Dumps an AudioCodec in RFC 2327-ish format.
+static std::string ToString(const AudioCodec& codec) {
+  std::stringstream ss;
+  ss << codec.name << "/" << codec.clockrate << "/" << codec.channels
+     << " (" << codec.id << ")";
+  return ss.str();
+}
+static std::string ToString(const webrtc::CodecInst& codec) {
+  std::stringstream ss;
+  ss << codec.plname << "/" << codec.plfreq << "/" << codec.channels
+     << " (" << codec.pltype << ")";
+  return ss.str();
+}
+
+static void LogMultiline(talk_base::LoggingSeverity sev, char* text) {
+  const char* delim = "\r\n";
+  for (char* tok = strtok(text, delim); tok; tok = strtok(NULL, delim)) {
+    LOG_V(sev) << tok;
+  }
+}
+
+// Severity is an integer because it comes is assumed to be from command line.
+static int SeverityToFilter(int severity) {
+  int filter = webrtc::kTraceNone;
+  switch (severity) {
+    case talk_base::LS_VERBOSE:
+      filter |= webrtc::kTraceAll;
+    case talk_base::LS_INFO:
+      filter |= (webrtc::kTraceStateInfo | webrtc::kTraceInfo);
+    case talk_base::LS_WARNING:
+      filter |= (webrtc::kTraceTerseInfo | webrtc::kTraceWarning);
+    case talk_base::LS_ERROR:
+      filter |= (webrtc::kTraceError | webrtc::kTraceCritical);
+  }
+  return filter;
+}
+
+static bool IsCodecMultiRate(const webrtc::CodecInst& codec) {
+  for (size_t i = 0; i < ARRAY_SIZE(kCodecPrefs); ++i) {
+    if (_stricmp(kCodecPrefs[i].name, codec.plname) == 0 &&
+        kCodecPrefs[i].clockrate == codec.plfreq) {
+      return kCodecPrefs[i].is_multi_rate;
+    }
+  }
+  return false;
+}
+
+static bool FindCodec(const std::vector<AudioCodec>& codecs,
+                      const AudioCodec& codec,
+                      AudioCodec* found_codec) {
+  for (std::vector<AudioCodec>::const_iterator it = codecs.begin();
+       it != codecs.end(); ++it) {
+    if (it->Matches(codec)) {
+      if (found_codec != NULL) {
+        *found_codec = *it;
+      }
+      return true;
+    }
+  }
+  return false;
+}
+static bool IsNackEnabled(const AudioCodec& codec) {
+  return codec.HasFeedbackParam(FeedbackParam(kRtcpFbParamNack,
+                                              kParamValueEmpty));
+}
+
+
+class WebRtcSoundclipMedia : public SoundclipMedia {
+ public:
+  explicit WebRtcSoundclipMedia(WebRtcVoiceEngine *engine)
+      : engine_(engine), webrtc_channel_(-1) {
+    engine_->RegisterSoundclip(this);
+  }
+
+  virtual ~WebRtcSoundclipMedia() {
+    engine_->UnregisterSoundclip(this);
+    if (webrtc_channel_ != -1) {
+      // We shouldn't have to call Disable() here. DeleteChannel() should call
+      // StopPlayout() while deleting the channel.  We should fix the bug
+      // inside WebRTC and remove the Disable() call bellow.  This work is
+      // tracked by bug http://b/issue?id=5382855.
+      PlaySound(NULL, 0, 0);
+      Disable();
+      if (engine_->voe_sc()->base()->DeleteChannel(webrtc_channel_)
+          == -1) {
+        LOG_RTCERR1(DeleteChannel, webrtc_channel_);
+      }
+    }
+  }
+
+  bool Init() {
+    webrtc_channel_ = engine_->voe_sc()->base()->CreateChannel();
+    if (webrtc_channel_ == -1) {
+      LOG_RTCERR0(CreateChannel);
+      return false;
+    }
+    return true;
+  }
+
+  bool Enable() {
+    if (engine_->voe_sc()->base()->StartPlayout(webrtc_channel_) == -1) {
+      LOG_RTCERR1(StartPlayout, webrtc_channel_);
+      return false;
+    }
+    return true;
+  }
+
+  bool Disable() {
+    if (engine_->voe_sc()->base()->StopPlayout(webrtc_channel_) == -1) {
+      LOG_RTCERR1(StopPlayout, webrtc_channel_);
+      return false;
+    }
+    return true;
+  }
+
+  virtual bool PlaySound(const char *buf, int len, int flags) {
+    // The voe file api is not available in chrome.
+    if (!engine_->voe_sc()->file()) {
+      return false;
+    }
+    // Must stop playing the current sound (if any), because we are about to
+    // modify the stream.
+    if (engine_->voe_sc()->file()->StopPlayingFileLocally(webrtc_channel_)
+        == -1) {
+      LOG_RTCERR1(StopPlayingFileLocally, webrtc_channel_);
+      return false;
+    }
+
+    if (buf) {
+      stream_.reset(new WebRtcSoundclipStream(buf, len));
+      stream_->set_loop((flags & SF_LOOP) != 0);
+      stream_->Rewind();
+
+      // Play it.
+      if (engine_->voe_sc()->file()->StartPlayingFileLocally(
+          webrtc_channel_, stream_.get()) == -1) {
+        LOG_RTCERR2(StartPlayingFileLocally, webrtc_channel_, stream_.get());
+        LOG(LS_ERROR) << "Unable to start soundclip";
+        return false;
+      }
+    } else {
+      stream_.reset();
+    }
+    return true;
+  }
+
+  int GetLastEngineError() const { return engine_->voe_sc()->error(); }
+
+ private:
+  WebRtcVoiceEngine *engine_;
+  int webrtc_channel_;
+  talk_base::scoped_ptr<WebRtcSoundclipStream> stream_;
+};
+
+WebRtcVoiceEngine::WebRtcVoiceEngine()
+    : voe_wrapper_(new VoEWrapper()),
+      voe_wrapper_sc_(new VoEWrapper()),
+      tracing_(new VoETraceWrapper()),
+      adm_(NULL),
+      adm_sc_(NULL),
+      log_filter_(SeverityToFilter(kDefaultLogSeverity)),
+      is_dumping_aec_(false),
+      desired_local_monitor_enable_(false),
+      tx_processor_ssrc_(0),
+      rx_processor_ssrc_(0) {
+  Construct();
+}
+
+WebRtcVoiceEngine::WebRtcVoiceEngine(VoEWrapper* voe_wrapper,
+                                     VoEWrapper* voe_wrapper_sc,
+                                     VoETraceWrapper* tracing)
+    : voe_wrapper_(voe_wrapper),
+      voe_wrapper_sc_(voe_wrapper_sc),
+      tracing_(tracing),
+      adm_(NULL),
+      adm_sc_(NULL),
+      log_filter_(SeverityToFilter(kDefaultLogSeverity)),
+      is_dumping_aec_(false),
+      desired_local_monitor_enable_(false),
+      tx_processor_ssrc_(0),
+      rx_processor_ssrc_(0) {
+  Construct();
+}
+
+void WebRtcVoiceEngine::Construct() {
+  SetTraceFilter(log_filter_);
+  initialized_ = false;
+  LOG(LS_VERBOSE) << "WebRtcVoiceEngine::WebRtcVoiceEngine";
+  SetTraceOptions("");
+  if (tracing_->SetTraceCallback(this) == -1) {
+    LOG_RTCERR0(SetTraceCallback);
+  }
+  if (voe_wrapper_->base()->RegisterVoiceEngineObserver(*this) == -1) {
+    LOG_RTCERR0(RegisterVoiceEngineObserver);
+  }
+  // Clear the default agc state.
+  memset(&default_agc_config_, 0, sizeof(default_agc_config_));
+
+  // Load our audio codec list.
+  ConstructCodecs();
+
+  // Load our RTP Header extensions.
+  rtp_header_extensions_.push_back(
+      RtpHeaderExtension(kRtpAudioLevelHeaderExtension,
+                         kRtpAudioLevelHeaderExtensionId));
+}
+
+static bool IsOpus(const AudioCodec& codec) {
+  return (_stricmp(codec.name.c_str(), kOpusCodecName) == 0);
+}
+
+static bool IsIsac(const AudioCodec& codec) {
+  return (_stricmp(codec.name.c_str(), kIsacCodecName) == 0);
+}
+
+// True if params["stereo"] == "1"
+static bool IsOpusStereoEnabled(const AudioCodec& codec) {
+  CodecParameterMap::const_iterator param =
+      codec.params.find(kCodecParamStereo);
+  if (param == codec.params.end()) {
+    return false;
+  }
+  return param->second == kParamValueTrue;
+}
+
+void WebRtcVoiceEngine::ConstructCodecs() {
+  LOG(LS_INFO) << "WebRtc VoiceEngine codecs:";
+  int ncodecs = voe_wrapper_->codec()->NumOfCodecs();
+  for (int i = 0; i < ncodecs; ++i) {
+    webrtc::CodecInst voe_codec;
+    if (voe_wrapper_->codec()->GetCodec(i, voe_codec) != -1) {
+      // Skip uncompressed formats.
+      if (_stricmp(voe_codec.plname, kL16CodecName) == 0) {
+        continue;
+      }
+
+      const CodecPref* pref = NULL;
+      for (size_t j = 0; j < ARRAY_SIZE(kCodecPrefs); ++j) {
+        if (_stricmp(kCodecPrefs[j].name, voe_codec.plname) == 0 &&
+            kCodecPrefs[j].clockrate == voe_codec.plfreq &&
+            kCodecPrefs[j].channels == voe_codec.channels) {
+          pref = &kCodecPrefs[j];
+          break;
+        }
+      }
+
+      if (pref) {
+        // Use the payload type that we've configured in our pref table;
+        // use the offset in our pref table to determine the sort order.
+        AudioCodec codec(pref->payload_type, voe_codec.plname, voe_codec.plfreq,
+                         voe_codec.rate, voe_codec.channels,
+                         ARRAY_SIZE(kCodecPrefs) - (pref - kCodecPrefs));
+        LOG(LS_INFO) << ToString(codec);
+        if (IsIsac(codec)) {
+          // Indicate auto-bandwidth in signaling.
+          codec.bitrate = 0;
+        }
+        if (IsOpus(codec)) {
+          // Only add fmtp parameters that differ from the spec.
+          if (kPreferredMinPTime != kOpusDefaultMinPTime) {
+            codec.params[kCodecParamMinPTime] =
+                talk_base::ToString(kPreferredMinPTime);
+          }
+          if (kPreferredMaxPTime != kOpusDefaultMaxPTime) {
+            codec.params[kCodecParamMaxPTime] =
+                talk_base::ToString(kPreferredMaxPTime);
+          }
+          // TODO(hellner): Add ptime, sprop-stereo, stereo and useinbandfec
+          // when they can be set to values other than the default.
+        }
+        codecs_.push_back(codec);
+      } else {
+        LOG(LS_WARNING) << "Unexpected codec: " << ToString(voe_codec);
+      }
+    }
+  }
+  // Make sure they are in local preference order.
+  std::sort(codecs_.begin(), codecs_.end(), &AudioCodec::Preferable);
+}
+
+WebRtcVoiceEngine::~WebRtcVoiceEngine() {
+  LOG(LS_VERBOSE) << "WebRtcVoiceEngine::~WebRtcVoiceEngine";
+  if (voe_wrapper_->base()->DeRegisterVoiceEngineObserver() == -1) {
+    LOG_RTCERR0(DeRegisterVoiceEngineObserver);
+  }
+  if (adm_) {
+    voe_wrapper_.reset();
+    adm_->Release();
+    adm_ = NULL;
+  }
+  if (adm_sc_) {
+    voe_wrapper_sc_.reset();
+    adm_sc_->Release();
+    adm_sc_ = NULL;
+  }
+
+  // Test to see if the media processor was deregistered properly
+  ASSERT(SignalRxMediaFrame.is_empty());
+  ASSERT(SignalTxMediaFrame.is_empty());
+
+  tracing_->SetTraceCallback(NULL);
+}
+
+bool WebRtcVoiceEngine::Init(talk_base::Thread* worker_thread) {
+  LOG(LS_INFO) << "WebRtcVoiceEngine::Init";
+  bool res = InitInternal();
+  if (res) {
+    LOG(LS_INFO) << "WebRtcVoiceEngine::Init Done!";
+  } else {
+    LOG(LS_ERROR) << "WebRtcVoiceEngine::Init failed";
+    Terminate();
+  }
+  return res;
+}
+
+bool WebRtcVoiceEngine::InitInternal() {
+  // Temporarily turn logging level up for the Init call
+  int old_filter = log_filter_;
+  int extended_filter = log_filter_ | SeverityToFilter(talk_base::LS_INFO);
+  SetTraceFilter(extended_filter);
+  SetTraceOptions("");
+
+  // Init WebRtc VoiceEngine.
+  if (voe_wrapper_->base()->Init(adm_) == -1) {
+    LOG_RTCERR0_EX(Init, voe_wrapper_->error());
+    SetTraceFilter(old_filter);
+    return false;
+  }
+
+  SetTraceFilter(old_filter);
+  SetTraceOptions(log_options_);
+
+  // Log the VoiceEngine version info
+  char buffer[1024] = "";
+  voe_wrapper_->base()->GetVersion(buffer);
+  LOG(LS_INFO) << "WebRtc VoiceEngine Version:";
+  LogMultiline(talk_base::LS_INFO, buffer);
+
+  // Save the default AGC configuration settings. This must happen before
+  // calling SetOptions or the default will be overwritten.
+  if (voe_wrapper_->processing()->GetAgcConfig(default_agc_config_) == -1) {
+    LOG_RTCERR0(GetAGCConfig);
+    return false;
+  }
+
+  if (!SetOptions(MediaEngineInterface::DEFAULT_AUDIO_OPTIONS)) {
+    return false;
+  }
+
+  // Print our codec list again for the call diagnostic log
+  LOG(LS_INFO) << "WebRtc VoiceEngine codecs:";
+  for (std::vector<AudioCodec>::const_iterator it = codecs_.begin();
+      it != codecs_.end(); ++it) {
+    LOG(LS_INFO) << ToString(*it);
+  }
+
+#if defined(LINUX) && !defined(HAVE_LIBPULSE)
+  voe_wrapper_sc_->hw()->SetAudioDeviceLayer(webrtc::kAudioLinuxAlsa);
+#endif
+
+  // Initialize the VoiceEngine instance that we'll use to play out sound clips.
+  if (voe_wrapper_sc_->base()->Init(adm_sc_) == -1) {
+    LOG_RTCERR0_EX(Init, voe_wrapper_sc_->error());
+    return false;
+  }
+
+  // On Windows, tell it to use the default sound (not communication) devices.
+  // First check whether there is a valid sound device for playback.
+  // TODO(juberti): Clean this up when we support setting the soundclip device.
+#ifdef WIN32
+  // The SetPlayoutDevice may not be implemented in the case of external ADM.
+  // TODO(ronghuawu): 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.";
+    }
+  }
+#endif
+
+  initialized_ = true;
+  return true;
+}
+
+void WebRtcVoiceEngine::Terminate() {
+  LOG(LS_INFO) << "WebRtcVoiceEngine::Terminate";
+  initialized_ = false;
+
+  StopAecDump();
+
+  voe_wrapper_sc_->base()->Terminate();
+  voe_wrapper_->base()->Terminate();
+  desired_local_monitor_enable_ = false;
+}
+
+int WebRtcVoiceEngine::GetCapabilities() {
+  return AUDIO_SEND | AUDIO_RECV;
+}
+
+VoiceMediaChannel *WebRtcVoiceEngine::CreateChannel() {
+  WebRtcVoiceMediaChannel* ch = new WebRtcVoiceMediaChannel(this);
+  if (!ch->valid()) {
+    delete ch;
+    ch = NULL;
+  }
+  return ch;
+}
+
+SoundclipMedia *WebRtcVoiceEngine::CreateSoundclip() {
+  WebRtcSoundclipMedia *soundclip = new WebRtcSoundclipMedia(this);
+  if (!soundclip->Init() || !soundclip->Enable()) {
+    delete soundclip;
+    return NULL;
+  }
+  return soundclip;
+}
+
+// TODO(zhurunz): Add a comprehensive unittests for SetOptions().
+bool WebRtcVoiceEngine::SetOptions(int flags) {
+  AudioOptions options;
+
+  // Convert flags to AudioOptions.
+  options.echo_cancellation.Set(
+      ((flags & MediaEngineInterface::ECHO_CANCELLATION) != 0));
+  options.auto_gain_control.Set(
+      ((flags & MediaEngineInterface::AUTO_GAIN_CONTROL) != 0));
+  options.noise_suppression.Set(
+      ((flags & MediaEngineInterface::NOISE_SUPPRESSION) != 0));
+  options.highpass_filter.Set(
+      ((flags & MediaEngineInterface::HIGHPASS_FILTER) != 0));
+  options.stereo_swapping.Set(
+      ((flags & MediaEngineInterface::STEREO_FLIPPING) != 0));
+
+  // Set defaults for flagless options here. Make sure they are all set so that
+  // ApplyOptions applies all of them when we clear overrides.
+  options.typing_detection.Set(true);
+  options.conference_mode.Set(false);
+  options.adjust_agc_delta.Set(0);
+  options.experimental_agc.Set(false);
+  options.experimental_aec.Set(false);
+  options.aec_dump.Set(false);
+
+  return SetAudioOptions(options);
+}
+
+bool WebRtcVoiceEngine::SetAudioOptions(const AudioOptions& options) {
+  if (!ApplyOptions(options)) {
+    return false;
+  }
+  options_ = options;
+  return true;
+}
+
+bool WebRtcVoiceEngine::SetOptionOverrides(const AudioOptions& overrides) {
+  LOG(LS_INFO) << "Setting option overrides: " << overrides.ToString();
+  if (!ApplyOptions(overrides)) {
+    return false;
+  }
+  option_overrides_ = overrides;
+  return true;
+}
+
+bool WebRtcVoiceEngine::ClearOptionOverrides() {
+  LOG(LS_INFO) << "Clearing option overrides.";
+  AudioOptions options = options_;
+  // Only call ApplyOptions if |options_overrides_| contains overrided options.
+  // ApplyOptions affects NS, AGC other options that is shared between
+  // all WebRtcVoiceEngineChannels.
+  if (option_overrides_ == AudioOptions()) {
+    return true;
+  }
+
+  if (!ApplyOptions(options)) {
+    return false;
+  }
+  option_overrides_ = AudioOptions();
+  return true;
+}
+
+// AudioOptions defaults are set in InitInternal (for options with corresponding
+// MediaEngineInterface flags) and in SetOptions(int) for flagless options.
+bool WebRtcVoiceEngine::ApplyOptions(const AudioOptions& options_in) {
+  AudioOptions options = options_in;  // The options are modified below.
+  // kEcConference is AEC with high suppression.
+  webrtc::EcModes ec_mode = webrtc::kEcConference;
+  webrtc::AecmModes aecm_mode = webrtc::kAecmSpeakerphone;
+  webrtc::AgcModes agc_mode = webrtc::kAgcAdaptiveAnalog;
+  webrtc::NsModes ns_mode = webrtc::kNsHighSuppression;
+  bool aecm_comfort_noise = false;
+
+#if defined(IOS)
+  // On iOS, VPIO provides built-in EC and AGC.
+  options.echo_cancellation.Set(false);
+  options.auto_gain_control.Set(false);
+#elif defined(ANDROID)
+  ec_mode = webrtc::kEcAecm;
+#endif
+
+#if defined(IOS) || defined(ANDROID)
+  // Set the AGC mode for iOS as well despite disabling it above, to avoid
+  // unsupported configuration errors from webrtc.
+  agc_mode = webrtc::kAgcFixedDigital;
+  options.typing_detection.Set(false);
+  options.experimental_agc.Set(false);
+  options.experimental_aec.Set(false);
+#endif
+
+  LOG(LS_INFO) << "Applying audio options: " << options.ToString();
+
+  webrtc::VoEAudioProcessing* voep = voe_wrapper_->processing();
+
+  bool echo_cancellation;
+  if (options.echo_cancellation.Get(&echo_cancellation)) {
+    if (voep->SetEcStatus(echo_cancellation, ec_mode) == -1) {
+      LOG_RTCERR2(SetEcStatus, echo_cancellation, ec_mode);
+      return false;
+    }
+#if !defined(ANDROID)
+    // TODO(ajm): Remove the error return on Android from webrtc.
+    if (voep->SetEcMetricsStatus(echo_cancellation) == -1) {
+      LOG_RTCERR1(SetEcMetricsStatus, echo_cancellation);
+      return false;
+    }
+#endif
+    if (ec_mode == webrtc::kEcAecm) {
+      if (voep->SetAecmMode(aecm_mode, aecm_comfort_noise) != 0) {
+        LOG_RTCERR2(SetAecmMode, aecm_mode, aecm_comfort_noise);
+        return false;
+      }
+    }
+  }
+
+  bool auto_gain_control;
+  if (options.auto_gain_control.Get(&auto_gain_control)) {
+    if (voep->SetAgcStatus(auto_gain_control, agc_mode) == -1) {
+      LOG_RTCERR2(SetAgcStatus, auto_gain_control, agc_mode);
+      return false;
+    }
+  }
+
+  bool noise_suppression;
+  if (options.noise_suppression.Get(&noise_suppression)) {
+    if (voep->SetNsStatus(noise_suppression, ns_mode) == -1) {
+      LOG_RTCERR2(SetNsStatus, noise_suppression, ns_mode);
+      return false;
+    }
+  }
+
+  bool highpass_filter;
+  if (options.highpass_filter.Get(&highpass_filter)) {
+    if (voep->EnableHighPassFilter(highpass_filter) == -1) {
+      LOG_RTCERR1(SetHighpassFilterStatus, highpass_filter);
+      return false;
+    }
+  }
+
+  bool stereo_swapping;
+  if (options.stereo_swapping.Get(&stereo_swapping)) {
+    voep->EnableStereoChannelSwapping(stereo_swapping);
+    if (voep->IsStereoChannelSwappingEnabled() != stereo_swapping) {
+      LOG_RTCERR1(EnableStereoChannelSwapping, stereo_swapping);
+      return false;
+    }
+  }
+
+  bool typing_detection;
+  if (options.typing_detection.Get(&typing_detection)) {
+    if (voep->SetTypingDetectionStatus(typing_detection) == -1) {
+      // In case of error, log the info and continue
+      LOG_RTCERR1(SetTypingDetectionStatus, typing_detection);
+    }
+  }
+
+  int adjust_agc_delta;
+  if (options.adjust_agc_delta.Get(&adjust_agc_delta)) {
+    if (!AdjustAgcLevel(adjust_agc_delta)) {
+      return false;
+    }
+  }
+
+  bool aec_dump;
+  if (options.aec_dump.Get(&aec_dump)) {
+    // TODO(grunell): Use a string in the options instead and let the embedder
+    // choose the filename.
+    if (aec_dump)
+      StartAecDump("audio.aecdump");
+    else
+      StopAecDump();
+  }
+
+
+  return true;
+}
+
+bool WebRtcVoiceEngine::SetDelayOffset(int offset) {
+  voe_wrapper_->processing()->SetDelayOffsetMs(offset);
+  if (voe_wrapper_->processing()->DelayOffsetMs() != offset) {
+    LOG_RTCERR1(SetDelayOffsetMs, offset);
+    return false;
+  }
+
+  return true;
+}
+
+struct ResumeEntry {
+  ResumeEntry(WebRtcVoiceMediaChannel *c, bool p, SendFlags s)
+      : channel(c),
+        playout(p),
+        send(s) {
+  }
+
+  WebRtcVoiceMediaChannel *channel;
+  bool playout;
+  SendFlags send;
+};
+
+// TODO(juberti): Refactor this so that the core logic can be used to set the
+// soundclip device. At that time, reinstate the soundclip pause/resume code.
+bool WebRtcVoiceEngine::SetDevices(const Device* in_device,
+                                   const Device* out_device) {
+#if !defined(IOS) && !defined(ANDROID)
+  int in_id = in_device ? talk_base::FromString<int>(in_device->id) :
+      kDefaultAudioDeviceId;
+  int out_id = out_device ? talk_base::FromString<int>(out_device->id) :
+      kDefaultAudioDeviceId;
+  // The device manager uses -1 as the default device, which was the case for
+  // VoE 3.5. VoE 4.0, however, uses 0 as the default in Linux and Mac.
+#ifndef WIN32
+  if (-1 == in_id) {
+    in_id = kDefaultAudioDeviceId;
+  }
+  if (-1 == out_id) {
+    out_id = kDefaultAudioDeviceId;
+  }
+#endif
+
+  std::string in_name = (in_id != kDefaultAudioDeviceId) ?
+      in_device->name : "Default device";
+  std::string out_name = (out_id != kDefaultAudioDeviceId) ?
+      out_device->name : "Default device";
+  LOG(LS_INFO) << "Setting microphone to (id=" << in_id << ", name=" << in_name
+            << ") and speaker to (id=" << out_id << ", name=" << out_name
+            << ")";
+
+  // If we're running the local monitor, we need to stop it first.
+  bool ret = true;
+  if (!PauseLocalMonitor()) {
+    LOG(LS_WARNING) << "Failed to pause local monitor";
+    ret = false;
+  }
+
+  // Must also pause all audio playback and capture.
+  for (ChannelList::const_iterator i = channels_.begin();
+       i != channels_.end(); ++i) {
+    WebRtcVoiceMediaChannel *channel = *i;
+    if (!channel->PausePlayout()) {
+      LOG(LS_WARNING) << "Failed to pause playout";
+      ret = false;
+    }
+    if (!channel->PauseSend()) {
+      LOG(LS_WARNING) << "Failed to pause send";
+      ret = false;
+    }
+  }
+
+  // Find the recording device id in VoiceEngine and set recording device.
+  if (!FindWebRtcAudioDeviceId(true, in_name, in_id, &in_id)) {
+    ret = false;
+  }
+  if (ret) {
+    if (voe_wrapper_->hw()->SetRecordingDevice(in_id) == -1) {
+      LOG_RTCERR2(SetRecordingDevice, in_device->name, in_id);
+      ret = false;
+    }
+  }
+
+  // Find the playout device id in VoiceEngine and set playout device.
+  if (!FindWebRtcAudioDeviceId(false, out_name, out_id, &out_id)) {
+    LOG(LS_WARNING) << "Failed to find VoiceEngine device id for " << out_name;
+    ret = false;
+  }
+  if (ret) {
+    if (voe_wrapper_->hw()->SetPlayoutDevice(out_id) == -1) {
+      LOG_RTCERR2(SetPlayoutDevice, out_device->name, out_id);
+      ret = false;
+    }
+  }
+
+  // Resume all audio playback and capture.
+  for (ChannelList::const_iterator i = channels_.begin();
+       i != channels_.end(); ++i) {
+    WebRtcVoiceMediaChannel *channel = *i;
+    if (!channel->ResumePlayout()) {
+      LOG(LS_WARNING) << "Failed to resume playout";
+      ret = false;
+    }
+    if (!channel->ResumeSend()) {
+      LOG(LS_WARNING) << "Failed to resume send";
+      ret = false;
+    }
+  }
+
+  // Resume local monitor.
+  if (!ResumeLocalMonitor()) {
+    LOG(LS_WARNING) << "Failed to resume local monitor";
+    ret = false;
+  }
+
+  if (ret) {
+    LOG(LS_INFO) << "Set microphone to (id=" << in_id <<" name=" << in_name
+                 << ") and speaker to (id="<< out_id << " name=" << out_name
+                 << ")";
+  }
+
+  return ret;
+#else
+  return true;
+#endif  // !IOS && !ANDROID
+}
+
+bool WebRtcVoiceEngine::FindWebRtcAudioDeviceId(
+  bool is_input, const std::string& dev_name, int dev_id, int* rtc_id) {
+  // In Linux, VoiceEngine uses the same device dev_id as the device manager.
+#ifdef LINUX
+  *rtc_id = dev_id;
+  return true;
+#else
+  // In Windows and Mac, we need to find the VoiceEngine device id by name
+  // unless the input dev_id is the default device id.
+  if (kDefaultAudioDeviceId == dev_id) {
+    *rtc_id = dev_id;
+    return true;
+  }
+
+  // Get the number of VoiceEngine audio devices.
+  int count = 0;
+  if (is_input) {
+    if (-1 == voe_wrapper_->hw()->GetNumOfRecordingDevices(count)) {
+      LOG_RTCERR0(GetNumOfRecordingDevices);
+      return false;
+    }
+  } else {
+    if (-1 == voe_wrapper_->hw()->GetNumOfPlayoutDevices(count)) {
+      LOG_RTCERR0(GetNumOfPlayoutDevices);
+      return false;
+    }
+  }
+
+  for (int i = 0; i < count; ++i) {
+    char name[128];
+    char guid[128];
+    if (is_input) {
+      voe_wrapper_->hw()->GetRecordingDeviceName(i, name, guid);
+      LOG(LS_VERBOSE) << "VoiceEngine microphone " << i << ": " << name;
+    } else {
+      voe_wrapper_->hw()->GetPlayoutDeviceName(i, name, guid);
+      LOG(LS_VERBOSE) << "VoiceEngine speaker " << i << ": " << name;
+    }
+
+    std::string webrtc_name(name);
+    if (dev_name.compare(0, webrtc_name.size(), webrtc_name) == 0) {
+      *rtc_id = i;
+      return true;
+    }
+  }
+  LOG(LS_WARNING) << "VoiceEngine cannot find device: " << dev_name;
+  return false;
+#endif
+}
+
+bool WebRtcVoiceEngine::GetOutputVolume(int* level) {
+  unsigned int ulevel;
+  if (voe_wrapper_->volume()->GetSpeakerVolume(ulevel) == -1) {
+    LOG_RTCERR1(GetSpeakerVolume, level);
+    return false;
+  }
+  *level = ulevel;
+  return true;
+}
+
+bool WebRtcVoiceEngine::SetOutputVolume(int level) {
+  ASSERT(level >= 0 && level <= 255);
+  if (voe_wrapper_->volume()->SetSpeakerVolume(level) == -1) {
+    LOG_RTCERR1(SetSpeakerVolume, level);
+    return false;
+  }
+  return true;
+}
+
+int WebRtcVoiceEngine::GetInputLevel() {
+  unsigned int ulevel;
+  return (voe_wrapper_->volume()->GetSpeechInputLevel(ulevel) != -1) ?
+      static_cast<int>(ulevel) : -1;
+}
+
+bool WebRtcVoiceEngine::SetLocalMonitor(bool enable) {
+  desired_local_monitor_enable_ = enable;
+  return ChangeLocalMonitor(desired_local_monitor_enable_);
+}
+
+bool WebRtcVoiceEngine::ChangeLocalMonitor(bool enable) {
+  // The voe file api is not available in chrome.
+  if (!voe_wrapper_->file()) {
+    return false;
+  }
+  if (enable && !monitor_) {
+    monitor_.reset(new WebRtcMonitorStream);
+    if (voe_wrapper_->file()->StartRecordingMicrophone(monitor_.get()) == -1) {
+      LOG_RTCERR1(StartRecordingMicrophone, monitor_.get());
+      // Must call Stop() because there are some cases where Start will report
+      // failure but still change the state, and if we leave VE in the on state
+      // then it could crash later when trying to invoke methods on our monitor.
+      voe_wrapper_->file()->StopRecordingMicrophone();
+      monitor_.reset();
+      return false;
+    }
+  } else if (!enable && monitor_) {
+    voe_wrapper_->file()->StopRecordingMicrophone();
+    monitor_.reset();
+  }
+  return true;
+}
+
+bool WebRtcVoiceEngine::PauseLocalMonitor() {
+  return ChangeLocalMonitor(false);
+}
+
+bool WebRtcVoiceEngine::ResumeLocalMonitor() {
+  return ChangeLocalMonitor(desired_local_monitor_enable_);
+}
+
+const std::vector<AudioCodec>& WebRtcVoiceEngine::codecs() {
+  return codecs_;
+}
+
+bool WebRtcVoiceEngine::FindCodec(const AudioCodec& in) {
+  return FindWebRtcCodec(in, NULL);
+}
+
+// Get the VoiceEngine codec that matches |in|, with the supplied settings.
+bool WebRtcVoiceEngine::FindWebRtcCodec(const AudioCodec& in,
+                                        webrtc::CodecInst* out) {
+  int ncodecs = voe_wrapper_->codec()->NumOfCodecs();
+  for (int i = 0; i < ncodecs; ++i) {
+    webrtc::CodecInst voe_codec;
+    if (voe_wrapper_->codec()->GetCodec(i, voe_codec) != -1) {
+      AudioCodec codec(voe_codec.pltype, voe_codec.plname, voe_codec.plfreq,
+                       voe_codec.rate, voe_codec.channels, 0);
+      bool multi_rate = IsCodecMultiRate(voe_codec);
+      // Allow arbitrary rates for ISAC to be specified.
+      if (multi_rate) {
+        // Set codec.bitrate to 0 so the check for codec.Matches() passes.
+        codec.bitrate = 0;
+      }
+      if (codec.Matches(in)) {
+        if (out) {
+          // Fixup the payload type.
+          voe_codec.pltype = in.id;
+
+          // Set bitrate if specified.
+          if (multi_rate && in.bitrate != 0) {
+            voe_codec.rate = in.bitrate;
+          }
+
+          // Apply codec-specific settings.
+          if (IsIsac(codec)) {
+            // If ISAC and an explicit bitrate is not specified,
+            // enable auto bandwidth adjustment.
+            voe_codec.rate = (in.bitrate > 0) ? in.bitrate : -1;
+          }
+          *out = voe_codec;
+        }
+        return true;
+      }
+    }
+  }
+  return false;
+}
+const std::vector<RtpHeaderExtension>&
+WebRtcVoiceEngine::rtp_header_extensions() const {
+  return rtp_header_extensions_;
+}
+
+void WebRtcVoiceEngine::SetLogging(int min_sev, const char* filter) {
+  // if min_sev == -1, we keep the current log level.
+  if (min_sev >= 0) {
+    SetTraceFilter(SeverityToFilter(min_sev));
+  }
+  log_options_ = filter;
+  SetTraceOptions(initialized_ ? log_options_ : "");
+}
+
+int WebRtcVoiceEngine::GetLastEngineError() {
+  return voe_wrapper_->error();
+}
+
+void WebRtcVoiceEngine::SetTraceFilter(int filter) {
+  log_filter_ = filter;
+  tracing_->SetTraceFilter(filter);
+}
+
+// We suppport three different logging settings for VoiceEngine:
+// 1. Observer callback that goes into talk diagnostic logfile.
+//    Use --logfile and --loglevel
+//
+// 2. Encrypted VoiceEngine log for debugging VoiceEngine.
+//    Use --voice_loglevel --voice_logfilter "tracefile file_name"
+//
+// 3. EC log and dump for debugging QualityEngine.
+//    Use --voice_loglevel --voice_logfilter "recordEC file_name"
+//
+// For more details see: "https://sites.google.com/a/google.com/wavelet/Home/
+//    Magic-Flute--RTC-Engine-/Magic-Flute-Command-Line-Parameters"
+void WebRtcVoiceEngine::SetTraceOptions(const std::string& options) {
+  // Set encrypted trace file.
+  std::vector<std::string> opts;
+  talk_base::tokenize(options, ' ', '"', '"', &opts);
+  std::vector<std::string>::iterator tracefile =
+      std::find(opts.begin(), opts.end(), "tracefile");
+  if (tracefile != opts.end() && ++tracefile != opts.end()) {
+    // Write encrypted debug output (at same loglevel) to file
+    // EncryptedTraceFile no longer supported.
+    if (tracing_->SetTraceFile(tracefile->c_str()) == -1) {
+      LOG_RTCERR1(SetTraceFile, *tracefile);
+    }
+  }
+
+  // Set AEC dump file
+  std::vector<std::string>::iterator recordEC =
+      std::find(opts.begin(), opts.end(), "recordEC");
+  if (recordEC != opts.end()) {
+    ++recordEC;
+    if (recordEC != opts.end())
+      StartAecDump(recordEC->c_str());
+    else
+      StopAecDump();
+  }
+}
+
+// Ignore spammy trace messages, mostly from the stats API when we haven't
+// gotten RTCP info yet from the remote side.
+bool WebRtcVoiceEngine::ShouldIgnoreTrace(const std::string& trace) {
+  static const char* kTracesToIgnore[] = {
+    "\tfailed to GetReportBlockInformation",
+    "GetRecCodec() failed to get received codec",
+    "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 from the RTP/RTCP module",
+    "SenderInfoReceived No received SR",
+    "StatisticsRTP() no statistics available",
+    "TransmitMixer::TypingDetection() VE_TYPING_NOISE_WARNING message has been posted",  // NOLINT
+    "TransmitMixer::TypingDetection() pending noise-saturation warning exists",  // NOLINT
+    "GetRecPayloadType() failed to retrieve RX payload type (error=10026)", // NOLINT
+    "StopPlayingFileAsMicrophone() isnot playing (error=8088)",
+    NULL
+  };
+  for (const char* const* p = kTracesToIgnore; *p; ++p) {
+    if (trace.find(*p) != std::string::npos) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void WebRtcVoiceEngine::Print(webrtc::TraceLevel level, const char* trace,
+                              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;
+  else if (level == webrtc::kTraceTerseInfo)
+    sev = talk_base::LS_INFO;
+
+  // 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: " << msg;
+    }
+  }
+}
+
+void WebRtcVoiceEngine::CallbackOnError(int channel_num, int err_code) {
+  talk_base::CritScope lock(&channels_cs_);
+  WebRtcVoiceMediaChannel* channel = NULL;
+  uint32 ssrc = 0;
+  LOG(LS_WARNING) << "VoiceEngine error " << err_code << " reported on channel "
+                  << 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 channel list when error reported.";
+  }
+}
+
+bool WebRtcVoiceEngine::FindChannelAndSsrc(
+    int channel_num, WebRtcVoiceMediaChannel** channel, uint32* ssrc) const {
+  ASSERT(channel != NULL && ssrc != NULL);
+
+  *channel = NULL;
+  *ssrc = 0;
+  // Find corresponding channel and ssrc
+  for (ChannelList::const_iterator it = channels_.begin();
+      it != channels_.end(); ++it) {
+    ASSERT(*it != NULL);
+    if ((*it)->FindSsrc(channel_num, ssrc)) {
+      *channel = *it;
+      return true;
+    }
+  }
+
+  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);
+  ASSERT(direction == MPD_RX || direction == MPD_TX);
+
+  *channel_num = -1;
+  // Find corresponding channel for ssrc.
+  for (ChannelList::const_iterator it = channels_.begin();
+      it != channels_.end(); ++it) {
+    ASSERT(*it != NULL);
+    if (direction & MPD_RX) {
+      *channel_num = (*it)->GetReceiveChannelNum(ssrc);
+    }
+    if (*channel_num == -1 && (direction & MPD_TX)) {
+      *channel_num = (*it)->GetSendChannelNum(ssrc);
+    }
+    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);
+}
+
+void WebRtcVoiceEngine::UnregisterChannel(WebRtcVoiceMediaChannel *channel) {
+  talk_base::CritScope lock(&channels_cs_);
+  ChannelList::iterator i = std::find(channels_.begin(),
+                                      channels_.end(),
+                                      channel);
+  if (i != channels_.end()) {
+    channels_.erase(i);
+  }
+}
+
+void WebRtcVoiceEngine::RegisterSoundclip(WebRtcSoundclipMedia *soundclip) {
+  soundclips_.push_back(soundclip);
+}
+
+void WebRtcVoiceEngine::UnregisterSoundclip(WebRtcSoundclipMedia *soundclip) {
+  SoundclipList::iterator i = std::find(soundclips_.begin(),
+                                        soundclips_.end(),
+                                        soundclip);
+  if (i != soundclips_.end()) {
+    soundclips_.erase(i);
+  }
+}
+
+// Adjusts the default AGC target level by the specified delta.
+// NB: If we start messing with other config fields, we'll want
+// to save the current webrtc::AgcConfig as well.
+bool WebRtcVoiceEngine::AdjustAgcLevel(int delta) {
+  webrtc::AgcConfig config = default_agc_config_;
+  config.targetLeveldBOv -= delta;
+
+  LOG(LS_INFO) << "Adjusting AGC level from default -"
+               << default_agc_config_.targetLeveldBOv << "dB to -"
+               << config.targetLeveldBOv << "dB";
+
+  if (voe_wrapper_->processing()->SetAgcConfig(config) == -1) {
+    LOG_RTCERR1(SetAgcConfig, config.targetLeveldBOv);
+    return false;
+  }
+  return true;
+}
+
+bool WebRtcVoiceEngine::SetAudioDeviceModule(webrtc::AudioDeviceModule* adm,
+    webrtc::AudioDeviceModule* adm_sc) {
+  if (initialized_) {
+    LOG(LS_WARNING) << "SetAudioDeviceModule can not be called after Init.";
+    return false;
+  }
+  if (adm_) {
+    adm_->Release();
+    adm_ = NULL;
+  }
+  if (adm) {
+    adm_ = adm;
+    adm_->AddRef();
+  }
+
+  if (adm_sc_) {
+    adm_sc_->Release();
+    adm_sc_ = NULL;
+  }
+  if (adm_sc) {
+    adm_sc_ = adm_sc;
+    adm_sc_->AddRef();
+  }
+  return true;
+}
+
+bool WebRtcVoiceEngine::RegisterProcessor(
+    uint32 ssrc,
+    VoiceProcessor* voice_processor,
+    MediaProcessorDirection direction) {
+  bool register_with_webrtc = false;
+  int channel_id = -1;
+  bool success = false;
+  uint32* processor_ssrc = NULL;
+  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;
+  }
+
+  webrtc::ProcessingTypes processing_type;
+  {
+    talk_base::CritScope cs(&signal_media_critical_);
+    if (direction == MPD_RX) {
+      processing_type = webrtc::kPlaybackAllChannelsMixed;
+      if (SignalRxMediaFrame.is_empty()) {
+        register_with_webrtc = true;
+        processor_ssrc = &rx_processor_ssrc_;
+      }
+      SignalRxMediaFrame.connect(voice_processor,
+                                 &VoiceProcessor::OnFrame);
+    } else {
+      processing_type = webrtc::kRecordingPerChannel;
+      if (SignalTxMediaFrame.is_empty()) {
+        register_with_webrtc = true;
+        processor_ssrc = &tx_processor_ssrc_;
+      }
+      SignalTxMediaFrame.connect(voice_processor,
+                                 &VoiceProcessor::OnFrame);
+    }
+  }
+  if (register_with_webrtc) {
+    // TODO(janahan): when registering consider instantiating a
+    // a VoeMediaProcess object and not make the engine extend the interface.
+    if (voe()->media() && voe()->media()->
+        RegisterExternalMediaProcessing(channel_id,
+                                        processing_type,
+                                        *this) != -1) {
+      LOG(LS_INFO) << "Media Processing Registration Succeeded. channel:"
+                   << channel_id;
+      *processor_ssrc = ssrc;
+      success = true;
+    } else {
+      LOG_RTCERR2(RegisterExternalMediaProcessing,
+                  channel_id,
+                  processing_type);
+      success = false;
+    }
+  } 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::UnregisterProcessorChannel(
+    MediaProcessorDirection channel_direction,
+    uint32 ssrc,
+    VoiceProcessor* voice_processor,
+    MediaProcessorDirection processor_direction) {
+  bool success = true;
+  FrameSignal* signal;
+  webrtc::ProcessingTypes processing_type;
+  uint32* processor_ssrc = NULL;
+  if (channel_direction == MPD_RX) {
+    signal = &SignalRxMediaFrame;
+    processing_type = webrtc::kPlaybackAllChannelsMixed;
+    processor_ssrc = &rx_processor_ssrc_;
+  } else {
+    signal = &SignalTxMediaFrame;
+    processing_type = webrtc::kRecordingPerChannel;
+    processor_ssrc = &tx_processor_ssrc_;
+  }
+
+  int deregister_id = -1;
+  {
+    talk_base::CritScope cs(&signal_media_critical_);
+    if ((processor_direction & channel_direction) != 0 && !signal->is_empty()) {
+      signal->disconnect(voice_processor);
+      int channel_id = -1;
+      bool found_channel = FindChannelNumFromSsrc(ssrc,
+                                                  channel_direction,
+                                                  &channel_id);
+      if (signal->is_empty() && found_channel) {
+        deregister_id = channel_id;
+      }
+    }
+  }
+  if (deregister_id != -1) {
+    if (voe()->media() &&
+        voe()->media()->DeRegisterExternalMediaProcessing(deregister_id,
+        processing_type) != -1) {
+      *processor_ssrc = 0;
+      LOG(LS_INFO) << "Media Processing DeRegistration Succeeded. channel:"
+                   << deregister_id;
+    } else {
+      LOG_RTCERR2(DeRegisterExternalMediaProcessing,
+                  deregister_id,
+                  processing_type);
+      success = false;
+    }
+  }
+  return success;
+}
+
+bool WebRtcVoiceEngine::UnregisterProcessor(
+    uint32 ssrc,
+    VoiceProcessor* voice_processor,
+    MediaProcessorDirection direction) {
+  bool success = true;
+  if (voice_processor == NULL) {
+    LOG(LS_WARNING) << "Media Processing Deregistration Failed. ssrc: "
+                    << ssrc;
+    return false;
+  }
+  if (!UnregisterProcessorChannel(MPD_RX, ssrc, voice_processor, direction)) {
+    success = false;
+  }
+  if (!UnregisterProcessorChannel(MPD_TX, ssrc, voice_processor, direction)) {
+    success = false;
+  }
+  return success;
+}
+
+// Implementing method from WebRtc VoEMediaProcess interface
+// Do not lock mux_channel_cs_ in this callback.
+void WebRtcVoiceEngine::Process(int channel,
+                                webrtc::ProcessingTypes type,
+                                int16_t audio10ms[],
+                                int length,
+                                int sampling_freq,
+                                bool is_stereo) {
+    talk_base::CritScope cs(&signal_media_critical_);
+    AudioFrame frame(audio10ms, length, sampling_freq, is_stereo);
+    if (type == webrtc::kPlaybackAllChannelsMixed) {
+      SignalRxMediaFrame(rx_processor_ssrc_, MPD_RX, &frame);
+    } else if (type == webrtc::kRecordingPerChannel) {
+      SignalTxMediaFrame(tx_processor_ssrc_, MPD_TX, &frame);
+    } else {
+      LOG(LS_WARNING) << "Media Processing invoked unexpectedly."
+                      << " channel: " << channel << " type: " << type
+                      << " tx_ssrc: " << tx_processor_ssrc_
+                      << " rx_ssrc: " << rx_processor_ssrc_;
+    }
+}
+
+void WebRtcVoiceEngine::StartAecDump(const std::string& filename) {
+  if (!is_dumping_aec_) {
+    // Start dumping AEC when we are not dumping.
+    if (voe_wrapper_->processing()->StartDebugRecording(
+        filename.c_str()) != webrtc::AudioProcessing::kNoError) {
+      LOG_RTCERR0(StartDebugRecording);
+    } else {
+      is_dumping_aec_ = true;
+    }
+  }
+}
+
+void WebRtcVoiceEngine::StopAecDump() {
+  if (is_dumping_aec_) {
+    // Stop dumping AEC when we are dumping.
+    if (voe_wrapper_->processing()->StopDebugRecording() !=
+        webrtc::AudioProcessing::kNoError) {
+      LOG_RTCERR0(StopDebugRecording);
+    }
+    is_dumping_aec_ = false;
+  }
+}
+
+// WebRtcVoiceMediaChannel
+WebRtcVoiceMediaChannel::WebRtcVoiceMediaChannel(WebRtcVoiceEngine *engine)
+    : WebRtcMediaChannel<VoiceMediaChannel, WebRtcVoiceEngine>(
+          engine,
+          engine->voe()->base()->CreateChannel()),
+      options_(),
+      dtmf_allowed_(false),
+      desired_playout_(false),
+      nack_enabled_(false),
+      playout_(false),
+      desired_send_(SEND_NOTHING),
+      send_(SEND_NOTHING),
+      send_ssrc_(0),
+      default_receive_ssrc_(0) {
+  engine->RegisterChannel(this);
+  LOG(LS_VERBOSE) << "WebRtcVoiceMediaChannel::WebRtcVoiceMediaChannel "
+                  << voe_channel();
+
+  // Register external transport
+  if (engine->voe()->network()->RegisterExternalTransport(
+      voe_channel(), *static_cast<Transport*>(this)) == -1) {
+    LOG_RTCERR2(RegisterExternalTransport, voe_channel(), this);
+  }
+
+  // Enable RTCP (for quality stats and feedback messages)
+  EnableRtcp(voe_channel());
+
+  // Reset all recv codecs; they will be enabled via SetRecvCodecs.
+  ResetRecvCodecs(voe_channel());
+
+  // Disable the DTMF playout when a tone is sent.
+  // PlayDtmfTone will be used if local playout is needed.
+  if (engine->voe()->dtmf()->SetDtmfFeedbackStatus(false) == -1) {
+    LOG_RTCERR1(SetDtmfFeedbackStatus, false);
+  }
+}
+
+WebRtcVoiceMediaChannel::~WebRtcVoiceMediaChannel() {
+  LOG(LS_VERBOSE) << "WebRtcVoiceMediaChannel::~WebRtcVoiceMediaChannel "
+                  << voe_channel();
+
+  // DeRegister external transport
+  if (engine()->voe()->network()->DeRegisterExternalTransport(
+      voe_channel()) == -1) {
+    LOG_RTCERR1(DeRegisterExternalTransport, voe_channel());
+  }
+
+  // Unregister ourselves from the engine.
+  engine()->UnregisterChannel(this);
+  // Remove any remaining streams.
+  while (!mux_channels_.empty()) {
+    RemoveRecvStream(mux_channels_.begin()->first);
+  }
+
+  // Delete the primary channel.
+  if (engine()->voe()->base()->DeleteChannel(voe_channel()) == -1) {
+    LOG_RTCERR1(DeleteChannel, voe_channel());
+  }
+}
+
+bool WebRtcVoiceMediaChannel::SetOptions(const AudioOptions& options) {
+  LOG(LS_INFO) << "Setting voice channel options: "
+               << options.ToString();
+
+  // We retain all of the existing options, and apply the given ones
+  // on top.  This means there is no way to "clear" options such that
+  // they go back to the engine default.
+  options_.SetAll(options);
+
+  if (send_ != SEND_NOTHING) {
+    if (!engine()->SetOptionOverrides(options_)) {
+      LOG(LS_WARNING) <<
+          "Failed to engine SetOptionOverrides during channel SetOptions.";
+      return false;
+    }
+  } else {
+    // Will be interpreted when appropriate.
+  }
+
+  LOG(LS_INFO) << "Set voice channel options.  Current options: "
+               << options_.ToString();
+  return true;
+}
+
+bool WebRtcVoiceMediaChannel::SetRecvCodecs(
+    const std::vector<AudioCodec>& codecs) {
+  // Set the payload types to be used for incoming media.
+  bool ret = true;
+  LOG(LS_INFO) << "Setting receive voice codecs:";
+
+  std::vector<AudioCodec> new_codecs;
+  // Find all new codecs. We allow adding new codecs but don't allow changing
+  // the payload type of codecs that is already configured since we might
+  // already be receiving packets with that payload type.
+  for (std::vector<AudioCodec>::const_iterator it = codecs.begin();
+       it != codecs.end() && ret; ++it) {
+    AudioCodec old_codec;
+    if (FindCodec(recv_codecs_, *it, &old_codec)) {
+      if (old_codec.id != it->id) {
+        LOG(LS_ERROR) << it->name << " payload type changed.";
+        return false;
+      }
+    } else {
+      new_codecs.push_back(*it);
+    }
+  }
+  if (new_codecs.empty()) {
+    // There are no new codecs to configure. Already configured codecs are
+    // never removed.
+    return true;
+  }
+
+  if (playout_) {
+    // Receive codecs can not be changed while playing. So we temporarily
+    // pause playout.
+    PausePlayout();
+  }
+
+  for (std::vector<AudioCodec>::const_iterator it = new_codecs.begin();
+       it != new_codecs.end() && ret; ++it) {
+    webrtc::CodecInst voe_codec;
+    if (engine()->FindWebRtcCodec(*it, &voe_codec)) {
+      LOG(LS_INFO) << ToString(*it);
+      voe_codec.pltype = it->id;
+      if (engine()->voe()->codec()->SetRecPayloadType(
+          voe_channel(), voe_codec) == -1) {
+        LOG_RTCERR2(SetRecPayloadType, voe_channel(), ToString(voe_codec));
+        ret = false;
+      }
+
+      // 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;
+    }
+  }
+  if (ret) {
+    recv_codecs_ = codecs;
+  }
+
+  if (desired_playout_ && !playout_) {
+    ResumePlayout();
+  }
+  return ret;
+}
+
+bool WebRtcVoiceMediaChannel::SetSendCodecs(
+    const std::vector<AudioCodec>& codecs) {
+  // Disable DTMF, VAD, and FEC unless we know the other side wants them.
+  dtmf_allowed_ = false;
+  engine()->voe()->codec()->SetVADStatus(voe_channel(), false);
+  engine()->voe()->rtp()->SetNACKStatus(voe_channel(), false, 0);
+  engine()->voe()->rtp()->SetFECStatus(voe_channel(), false);
+
+  // Scan through the list to figure out the codec to use for sending, along
+  // with the proper configuration for VAD and DTMF.
+  bool first = true;
+  webrtc::CodecInst send_codec;
+  memset(&send_codec, 0, sizeof(send_codec));
+
+  for (std::vector<AudioCodec>::const_iterator it = codecs.begin();
+       it != codecs.end(); ++it) {
+    // Ignore codecs we don't know about. The negotiation step should prevent
+    // this, but double-check to be sure.
+    webrtc::CodecInst voe_codec;
+    if (!engine()->FindWebRtcCodec(*it, &voe_codec)) {
+      LOG(LS_WARNING) << "Unknown codec " << ToString(voe_codec);
+      continue;
+    }
+
+    // If OPUS, change what we send according to the "stereo" codec
+    // parameter, and not the "channels" parameter.  We set
+    // voe_codec.channels to 2 if "stereo=1" and 1 otherwise.  If
+    // the bitrate is not specified, i.e. is zero, we set it to the
+    // appropriate default value for mono or stereo Opus.
+    if (IsOpus(*it)) {
+      if (IsOpusStereoEnabled(*it)) {
+        voe_codec.channels = 2;
+        if (it->bitrate == 0) {
+          voe_codec.rate = kOpusStereoBitrate;
+        }
+      } else {
+        voe_codec.channels = 1;
+        if (it->bitrate == 0) {
+          voe_codec.rate = kOpusMonoBitrate;
+        }
+      }
+    }
+
+    // Find the DTMF telephone event "codec" and tell VoiceEngine about it.
+    if (_stricmp(it->name.c_str(), "telephone-event") == 0 ||
+        _stricmp(it->name.c_str(), "audio/telephone-event") == 0) {
+      if (engine()->voe()->dtmf()->SetSendTelephoneEventPayloadType(
+          voe_channel(), it->id) == -1) {
+        LOG_RTCERR2(SetSendTelephoneEventPayloadType, voe_channel(), it->id);
+        return false;
+      }
+      dtmf_allowed_ = true;
+    }
+
+    // Turn voice activity detection/comfort noise on if supported.
+    // Set the wideband CN payload type appropriately.
+    // (narrowband always uses the static payload type 13).
+    if (_stricmp(it->name.c_str(), "CN") == 0) {
+      webrtc::PayloadFrequencies cn_freq;
+      switch (it->clockrate) {
+        case 8000:
+          cn_freq = webrtc::kFreq8000Hz;
+          break;
+        case 16000:
+          cn_freq = webrtc::kFreq16000Hz;
+          break;
+        case 32000:
+          cn_freq = webrtc::kFreq32000Hz;
+          break;
+        default:
+          LOG(LS_WARNING) << "CN frequency " << it->clockrate
+                          << " not supported.";
+          continue;
+      }
+      // The CN payload type for 8000 Hz clockrate is fixed at 13.
+      if (cn_freq != webrtc::kFreq8000Hz) {
+        if (engine()->voe()->codec()->SetSendCNPayloadType(voe_channel(),
+            it->id, cn_freq) == -1) {
+          LOG_RTCERR3(SetSendCNPayloadType, voe_channel(), it->id, cn_freq);
+          // TODO(ajm): This failure condition will be removed from VoE.
+          // Restore the return here when we update to a new enough webrtc.
+          //
+          // Not returning false because the SetSendCNPayloadType will fail if
+          // the channel is already sending.
+          // This can happen if the remote description is applied twice, for
+          // example in the case of ROAP on top of JSEP, where both side will
+          // send the offer.
+        }
+      }
+      // Only turn on VAD if we have a CN payload type that matches the
+      // clockrate for the codec we are going to use.
+      if (it->clockrate == send_codec.plfreq) {
+        LOG(LS_INFO) << "Enabling VAD";
+        if (engine()->voe()->codec()->SetVADStatus(voe_channel(), true) == -1) {
+          LOG_RTCERR2(SetVADStatus, voe_channel(), true);
+          return false;
+        }
+      }
+    }
+
+    // We'll use the first codec in the list to actually send audio data.
+    // Be sure to use the payload type requested by the remote side.
+    // "red", for FEC audio, is a special case where the actual codec to be
+    // used is specified in params.
+    if (first) {
+      if (_stricmp(it->name.c_str(), "red") == 0) {
+        // Parse out the RED parameters. If we fail, just ignore RED;
+        // we don't support all possible params/usage scenarios.
+        if (!GetRedSendCodec(*it, codecs, &send_codec)) {
+          continue;
+        }
+
+        // Enable redundant encoding of the specified codec. Treat any
+        // failure as a fatal internal error.
+        LOG(LS_INFO) << "Enabling FEC";
+        if (engine()->voe()->rtp()->SetFECStatus(voe_channel(),
+                                                 true, it->id) == -1) {
+          LOG_RTCERR3(SetFECStatus, voe_channel(), true, it->id);
+          return false;
+        }
+      } else {
+        send_codec = voe_codec;
+      nack_enabled_ = IsNackEnabled(*it);
+      SetNack(send_ssrc_, voe_channel(), nack_enabled_);
+      }
+      first = false;
+      // Set the codec immediately, since SetVADStatus() depends on whether
+      // the current codec is mono or stereo.
+      if (!SetSendCodec(send_codec))
+        return false;
+    }
+  }
+  for (ChannelMap::iterator it = mux_channels_.begin();
+       it != mux_channels_.end(); ++it) {
+    SetNack(it->first, it->second, nack_enabled_);
+  }
+
+
+  // If we're being asked to set an empty list of codecs, due to a buggy client,
+  // choose the most common format: PCMU
+  if (first) {
+    LOG(LS_WARNING) << "Received empty list of codecs; using PCMU/8000";
+    AudioCodec codec(0, "PCMU", 8000, 0, 1, 0);
+    engine()->FindWebRtcCodec(codec, &send_codec);
+    if (!SetSendCodec(send_codec))
+      return false;
+  }
+
+  return true;
+}
+void WebRtcVoiceMediaChannel::SetNack(uint32 ssrc, int channel,
+                                      bool nack_enabled) {
+  if (nack_enabled) {
+    LOG(LS_INFO) << "Enabling NACK for stream " << ssrc;
+    engine()->voe()->rtp()->SetNACKStatus(channel, true, kNackMaxPackets);
+  } else {
+    LOG(LS_INFO) << "Disabling NACK for stream " << ssrc;
+    engine()->voe()->rtp()->SetNACKStatus(channel, false, 0);
+  }
+}
+
+
+bool WebRtcVoiceMediaChannel::SetSendCodec(
+    const webrtc::CodecInst& send_codec) {
+  LOG(LS_INFO) << "Selected voice codec " << ToString(send_codec)
+               << ", bitrate=" << send_codec.rate;
+  if (engine()->voe()->codec()->SetSendCodec(voe_channel(),
+                                             send_codec) == -1) {
+    LOG_RTCERR2(SetSendCodec, voe_channel(), ToString(send_codec));
+    return false;
+  }
+  send_codec_.reset(new webrtc::CodecInst(send_codec));
+  return true;
+}
+
+bool WebRtcVoiceMediaChannel::SetRecvRtpHeaderExtensions(
+    const std::vector<RtpHeaderExtension>& extensions) {
+  // We don't support any incoming extensions headers right now.
+  return true;
+}
+
+bool WebRtcVoiceMediaChannel::SetSendRtpHeaderExtensions(
+    const std::vector<RtpHeaderExtension>& extensions) {
+  // Enable the audio level extension header if requested.
+  std::vector<RtpHeaderExtension>::const_iterator it;
+  for (it = extensions.begin(); it != extensions.end(); ++it) {
+    if (it->uri == kRtpAudioLevelHeaderExtension) {
+      break;
+    }
+  }
+
+  bool enable = (it != extensions.end());
+  int id = 0;
+
+  if (enable) {
+    id = it->id;
+    if (id < kMinRtpHeaderExtensionId ||
+        id > kMaxRtpHeaderExtensionId) {
+      LOG(LS_WARNING) << "Invalid RTP header extension id " << id;
+      return false;
+    }
+  }
+
+  LOG(LS_INFO) << "Enabling audio level header extension with ID " << id;
+  if (engine()->voe()->rtp()->SetRTPAudioLevelIndicationStatus(
+      voe_channel(), enable, id) == -1) {
+    LOG_RTCERR3(SetRTPAudioLevelIndicationStatus, voe_channel(), enable, id);
+    return false;
+  }
+
+  return true;
+}
+
+bool WebRtcVoiceMediaChannel::SetPlayout(bool playout) {
+  desired_playout_ = playout;
+  return ChangePlayout(desired_playout_);
+}
+
+bool WebRtcVoiceMediaChannel::PausePlayout() {
+  return ChangePlayout(false);
+}
+
+bool WebRtcVoiceMediaChannel::ResumePlayout() {
+  return ChangePlayout(desired_playout_);
+}
+
+bool WebRtcVoiceMediaChannel::ChangePlayout(bool playout) {
+  if (playout_ == playout) {
+    return true;
+  }
+
+  bool result = true;
+  if (mux_channels_.empty()) {
+    // Only toggle the default channel if we don't have any other channels.
+    result = SetPlayout(voe_channel(), playout);
+  }
+  for (ChannelMap::iterator it = mux_channels_.begin();
+       it != mux_channels_.end() && result; ++it) {
+    if (!SetPlayout(it->second, playout)) {
+      LOG(LS_ERROR) << "SetPlayout " << playout << " on channel " << it->second
+                    << " failed";
+      result = false;
+    }
+  }
+
+  if (result) {
+    playout_ = playout;
+  }
+  return result;
+}
+
+bool WebRtcVoiceMediaChannel::SetSend(SendFlags send) {
+  desired_send_ = send;
+  if (send_ssrc_ != 0)
+    return ChangeSend(desired_send_);
+  return true;
+}
+
+bool WebRtcVoiceMediaChannel::PauseSend() {
+  return ChangeSend(SEND_NOTHING);
+}
+
+bool WebRtcVoiceMediaChannel::ResumeSend() {
+  return ChangeSend(desired_send_);
+}
+
+bool WebRtcVoiceMediaChannel::ChangeSend(SendFlags send) {
+  if (send_ == send) {
+    return true;
+  }
+
+  if (send == SEND_MICROPHONE) {
+    engine()->SetOptionOverrides(options_);
+
+    // VoiceEngine resets sequence number when StopSend is called. This
+    // sometimes causes libSRTP to complain about packets being
+    // replayed. To get around this we store the last sent sequence
+    // number and initializes the channel with the next to continue on
+    // the same sequence.
+    if (sequence_number() != -1) {
+      LOG(LS_INFO) << "WebRtcVoiceMediaChannel restores seqnum="
+                   << sequence_number() + 1;
+      if (engine()->voe()->sync()->SetInitSequenceNumber(
+              voe_channel(), sequence_number() + 1) == -1) {
+        LOG_RTCERR2(SetInitSequenceNumber, voe_channel(),
+                    sequence_number() + 1);
+      }
+    }
+    if (engine()->voe()->base()->StartSend(voe_channel()) == -1) {
+      LOG_RTCERR1(StartSend, voe_channel());
+      return false;
+    }
+    // It's OK not to have file() here, since we don't need to call Stop if
+    // no file is playing.
+    if (engine()->voe()->file() &&
+        engine()->voe()->file()->StopPlayingFileAsMicrophone(
+            voe_channel()) == -1) {
+      LOG_RTCERR1(StopPlayingFileAsMicrophone, voe_channel());
+      return false;
+    }
+  } else if (send == SEND_RINGBACKTONE) {
+    ASSERT(ringback_tone_);
+    if (!ringback_tone_) {
+      return false;
+    }
+    if (engine()->voe()->file() &&
+        engine()->voe()->file()->StartPlayingFileAsMicrophone(
+        voe_channel(), ringback_tone_.get(), false) != -1) {
+      LOG(LS_INFO) << "File StartPlayingFileAsMicrophone Succeeded. channel:"
+                   << voe_channel();
+    } else {
+      LOG_RTCERR3(StartPlayingFileAsMicrophone, voe_channel(),
+                  ringback_tone_.get(), false);
+      return false;
+    }
+    // VoiceEngine resets sequence number when StopSend is called. This
+    // sometimes causes libSRTP to complain about packets being
+    // replayed. To get around this we store the last sent sequence
+    // number and initializes the channel with the next to continue on
+    // the same sequence.
+    if (sequence_number() != -1) {
+      LOG(LS_INFO) << "WebRtcVoiceMediaChannel restores seqnum="
+                   << sequence_number() + 1;
+      if (engine()->voe()->sync()->SetInitSequenceNumber(
+              voe_channel(), sequence_number() + 1) == -1) {
+        LOG_RTCERR2(SetInitSequenceNumber, voe_channel(),
+                    sequence_number() + 1);
+      }
+    }
+    if (engine()->voe()->base()->StartSend(voe_channel()) == -1) {
+      LOG_RTCERR1(StartSend, voe_channel());
+      return false;
+    }
+  } else {  // SEND_NOTHING
+    if (engine()->voe()->base()->StopSend(voe_channel()) == -1) {
+      LOG_RTCERR1(StopSend, voe_channel());
+    }
+
+    engine()->ClearOptionOverrides();
+  }
+  send_ = send;
+  return true;
+}
+
+bool WebRtcVoiceMediaChannel::AddSendStream(const StreamParams& sp) {
+  if (send_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;
+  }
+
+  send_ssrc_ = sp.first_ssrc();
+  if (desired_send_ != send_)
+    return ChangeSend(desired_send_);
+  return true;
+}
+
+bool WebRtcVoiceMediaChannel::RemoveSendStream(uint32 ssrc) {
+  if (ssrc != send_ssrc_) {
+    return false;
+  }
+  send_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.
+  bool conference_mode;
+  if (!options_.conference_mode.Get(&conference_mode) || !conference_mode) {
+    LOG(LS_INFO) << "Recv stream " << sp.first_ssrc()
+                 << " reuse default channel";
+    default_receive_ssrc_ = sp.first_ssrc();
+    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;
+  }
+
+  // Create a new channel for receiving audio data.
+  int channel = engine()->voe()->base()->CreateChannel();
+  if (channel == -1) {
+    LOG_RTCERR0(CreateChannel);
+    return false;
+  }
+
+  // Configure to use external transport, like our default channel.
+  if (engine()->voe()->network()->RegisterExternalTransport(
+          channel, *this) == -1) {
+    LOG_RTCERR2(SetExternalTransport, channel, this);
+    return false;
+  }
+
+  // Use the same SSRC as our default channel (so the RTCP reports are correct).
+  unsigned int send_ssrc;
+  webrtc::VoERTP_RTCP* rtp = engine()->voe()->rtp();
+  if (rtp->GetLocalSSRC(voe_channel(), send_ssrc) == -1) {
+    LOG_RTCERR2(GetSendSSRC, channel, send_ssrc);
+    return false;
+  }
+  if (rtp->SetLocalSSRC(channel, send_ssrc) == -1) {
+    LOG_RTCERR2(SetSendSSRC, channel, send_ssrc);
+    return false;
+  }
+
+  // Use the same recv payload types as our default channel.
+  ResetRecvCodecs(channel);
+  if (!recv_codecs_.empty()) {
+    for (std::vector<AudioCodec>::const_iterator it = recv_codecs_.begin();
+        it != recv_codecs_.end(); ++it) {
+      webrtc::CodecInst voe_codec;
+      if (engine()->FindWebRtcCodec(*it, &voe_codec)) {
+        voe_codec.pltype = it->id;
+        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 (mux_channels_.empty() && playout_) {
+    // This is the first stream in a multi user meeting. We can now
+    // disable playback of the default stream. This since the default
+    // stream will probably have received some initial packets before
+    // the new stream was added. This will mean that the CN state from
+    // the default channel will be mixed in with the other streams
+    // throughout the whole meeting, which might be disturbing.
+    LOG(LS_INFO) << "Disabling playback on the default voice channel";
+    SetPlayout(voe_channel(), false);
+  }
+  SetNack(ssrc, channel, nack_enabled_);
+
+  mux_channels_[ssrc] = channel;
+
+  // TODO(juberti): We should rollback the add if SetPlayout fails.
+  LOG(LS_INFO) << "New audio stream " << ssrc
+            << " registered to VoiceEngine channel #"
+            << channel << ".";
+  return SetPlayout(channel, playout_);
+}
+
+bool WebRtcVoiceMediaChannel::RemoveRecvStream(uint32 ssrc) {
+  talk_base::CritScope lock(&mux_channels_cs_);
+  ChannelMap::iterator it = mux_channels_.find(ssrc);
+
+  if (it != mux_channels_.end()) {
+    if (engine()->voe()->network()->DeRegisterExternalTransport(
+        it->second) == -1) {
+      LOG_RTCERR1(DeRegisterExternalTransport, it->second);
+    }
+
+    LOG(LS_INFO) << "Removing audio stream " << ssrc
+              << " with VoiceEngine channel #"
+              << it->second << ".";
+    if (engine()->voe()->base()->DeleteChannel(it->second) == -1) {
+      LOG_RTCERR1(DeleteChannel, voe_channel());
+      return false;
+    }
+
+    mux_channels_.erase(it);
+    if (mux_channels_.empty() && playout_) {
+      // The last stream was removed. We can now enable the default
+      // channel for new channels to be played out immediately without
+      // waiting for AddStream messages.
+      // TODO(oja): Does the default channel still have it's CN state?
+      LOG(LS_INFO) << "Enabling playback on the default voice channel";
+      SetPlayout(voe_channel(), true);
+    }
+  }
+  return true;
+}
+
+bool WebRtcVoiceMediaChannel::SetRenderer(uint32 ssrc,
+                                          AudioRenderer* renderer) {
+  ASSERT(renderer != NULL);
+  int channel = GetReceiveChannelNum(ssrc);
+  if (channel == -1)
+    return false;
+
+  renderer->SetChannelId(channel);
+  return true;
+}
+
+bool WebRtcVoiceMediaChannel::GetActiveStreams(
+    AudioInfo::StreamList* actives) {
+  actives->clear();
+  for (ChannelMap::iterator it = mux_channels_.begin();
+       it != mux_channels_.end(); ++it) {
+    int level = GetOutputLevel(it->second);
+    if (level > 0) {
+      actives->push_back(std::make_pair(it->first, level));
+    }
+  }
+  return true;
+}
+
+int WebRtcVoiceMediaChannel::GetOutputLevel() {
+  // return the highest output level of all streams
+  int highest = GetOutputLevel(voe_channel());
+  for (ChannelMap::iterator it = mux_channels_.begin();
+       it != mux_channels_.end(); ++it) {
+    int level = GetOutputLevel(it->second);
+    highest = talk_base::_max(level, highest);
+  }
+  return highest;
+}
+
+int WebRtcVoiceMediaChannel::GetTimeSinceLastTyping() {
+  int ret;
+  if (engine()->voe()->processing()->TimeSinceLastTyping(ret) == -1) {
+    // In case of error, log the info and continue
+    LOG_RTCERR0(TimeSinceLastTyping);
+    ret = -1;
+  } else {
+    ret *= 1000;  // We return ms, webrtc returns seconds.
+  }
+  return ret;
+}
+
+void WebRtcVoiceMediaChannel::SetTypingDetectionParameters(int time_window,
+    int cost_per_typing, int reporting_threshold, int penalty_decay,
+    int type_event_delay) {
+  if (engine()->voe()->processing()->SetTypingDetectionParameters(
+          time_window, cost_per_typing,
+          reporting_threshold, penalty_decay, type_event_delay) == -1) {
+    // In case of error, log the info and continue
+    LOG_RTCERR5(SetTypingDetectionParameters, time_window,
+                cost_per_typing, reporting_threshold, penalty_decay,
+                type_event_delay);
+  }
+}
+
+bool WebRtcVoiceMediaChannel::SetOutputScaling(
+    uint32 ssrc, double left, double right) {
+  talk_base::CritScope lock(&mux_channels_cs_);
+  // Collect the channels to scale the output volume.
+  std::vector<int> channels;
+  if (0 == ssrc) {  // Collect all channels, including the default one.
+    channels.push_back(voe_channel());
+    for (ChannelMap::const_iterator it = mux_channels_.begin();
+        it != mux_channels_.end(); ++it) {
+      channels.push_back(it->second);
+    }
+  } else {  // Collect only the channel of the specified ssrc.
+    int channel = GetReceiveChannelNum(ssrc);
+    if (-1 == channel) {
+      LOG(LS_WARNING) << "Cannot find channel for ssrc:" << ssrc;
+      return false;
+    }
+    channels.push_back(channel);
+  }
+
+  // Scale the output volume for the collected channels. We first normalize to
+  // scale the volume and then set the left and right pan.
+  float scale = static_cast<float>(talk_base::_max(left, right));
+  if (scale > 0.0001f) {
+    left /= scale;
+    right /= scale;
+  }
+  for (std::vector<int>::const_iterator it = channels.begin();
+      it != channels.end(); ++it) {
+    if (-1 == engine()->voe()->volume()->SetChannelOutputVolumeScaling(
+        *it, scale)) {
+      LOG_RTCERR2(SetChannelOutputVolumeScaling, *it, scale);
+      return false;
+    }
+    if (-1 == engine()->voe()->volume()->SetOutputVolumePan(
+        *it, static_cast<float>(left), static_cast<float>(right))) {
+      LOG_RTCERR3(SetOutputVolumePan, *it, left, right);
+      // Do not return if fails. SetOutputVolumePan is not available for all
+      // pltforms.
+    }
+    LOG(LS_INFO) << "SetOutputScaling to left=" << left * scale
+                 << " right=" << right * scale
+                 << " for channel " << *it << " and ssrc " << ssrc;
+  }
+  return true;
+}
+
+bool WebRtcVoiceMediaChannel::GetOutputScaling(
+    uint32 ssrc, double* left, double* right) {
+  if (!left || !right) return false;
+
+  talk_base::CritScope lock(&mux_channels_cs_);
+  // Determine which channel based on ssrc.
+  int channel = (0 == ssrc) ? voe_channel() : GetReceiveChannelNum(ssrc);
+  if (channel == -1) {
+    LOG(LS_WARNING) << "Cannot find channel for ssrc:" << ssrc;
+    return false;
+  }
+
+  float scaling;
+  if (-1 == engine()->voe()->volume()->GetChannelOutputVolumeScaling(
+      channel, scaling)) {
+    LOG_RTCERR2(GetChannelOutputVolumeScaling, channel, scaling);
+    return false;
+  }
+
+  float left_pan;
+  float right_pan;
+  if (-1 == engine()->voe()->volume()->GetOutputVolumePan(
+      channel, left_pan, right_pan)) {
+    LOG_RTCERR3(GetOutputVolumePan, channel, left_pan, right_pan);
+    // If GetOutputVolumePan fails, we use the default left and right pan.
+    left_pan = 1.0f;
+    right_pan = 1.0f;
+  }
+
+  *left = scaling * left_pan;
+  *right = scaling * right_pan;
+  return true;
+}
+
+bool WebRtcVoiceMediaChannel::SetRingbackTone(const char *buf, int len) {
+  ringback_tone_.reset(new WebRtcSoundclipStream(buf, len));
+  return true;
+}
+
+bool WebRtcVoiceMediaChannel::PlayRingbackTone(uint32 ssrc,
+                                             bool play, bool loop) {
+  if (!ringback_tone_) {
+    return false;
+  }
+
+  // The voe file api is not available in chrome.
+  if (!engine()->voe()->file()) {
+    return false;
+  }
+
+  // Determine which VoiceEngine channel to play on.
+  int channel = (ssrc == 0) ? voe_channel() : GetReceiveChannelNum(ssrc);
+  if (channel == -1) {
+    return false;
+  }
+
+  // Make sure the ringtone is cued properly, and play it out.
+  if (play) {
+    ringback_tone_->set_loop(loop);
+    ringback_tone_->Rewind();
+    if (engine()->voe()->file()->StartPlayingFileLocally(channel,
+        ringback_tone_.get()) == -1) {
+      LOG_RTCERR2(StartPlayingFileLocally, channel, ringback_tone_.get());
+      LOG(LS_ERROR) << "Unable to start ringback tone";
+      return false;
+    }
+    ringback_channels_.insert(channel);
+    LOG(LS_INFO) << "Started ringback on channel " << channel;
+  } else {
+    if (engine()->voe()->file()->IsPlayingFileLocally(channel) == 1 &&
+        engine()->voe()->file()->StopPlayingFileLocally(channel) == -1) {
+      LOG_RTCERR1(StopPlayingFileLocally, channel);
+      return false;
+    }
+    LOG(LS_INFO) << "Stopped ringback on channel " << channel;
+    ringback_channels_.erase(channel);
+  }
+
+  return true;
+}
+
+bool WebRtcVoiceMediaChannel::CanInsertDtmf() {
+  return dtmf_allowed_;
+}
+
+bool WebRtcVoiceMediaChannel::InsertDtmf(uint32 ssrc, int event,
+                                         int duration, int flags) {
+  if (!dtmf_allowed_) {
+    return false;
+  }
+
+  // TODO(ronghuawu): Remove this once the reset and delay are supported by VoE.
+  // https://code.google.com/p/webrtc/issues/detail?id=747
+  if (event == kDtmfReset || event == kDtmfDelay) {
+    return true;
+  }
+
+  // Send the event.
+  if (flags & cricket::DF_SEND) {
+    if (send_ssrc_ != ssrc && ssrc != 0) {
+      LOG(LS_WARNING) << "InsertDtmf - The specified ssrc "
+                      << ssrc << " is not in use.";
+      return false;
+    }
+    // Send DTMF using out-of-band DTMF. ("true", as 3rd arg)
+    if (engine()->voe()->dtmf()->SendTelephoneEvent(voe_channel(),
+        event, true, duration) == -1) {
+      LOG_RTCERR4(SendTelephoneEvent, voe_channel(), event, true, duration);
+      return false;
+    }
+  }
+
+  // Play the event.
+  if (flags & cricket::DF_PLAY) {
+    // Play DTMF tone locally.
+    if (engine()->voe()->dtmf()->PlayDtmfTone(event, duration) == -1) {
+      LOG_RTCERR2(PlayDtmfTone, event, duration);
+      return false;
+    }
+  }
+
+  return true;
+}
+
+void WebRtcVoiceMediaChannel::OnPacketReceived(talk_base::Buffer* packet) {
+  // 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 = GetReceiveChannelNum(
+      ParseSsrc(packet->data(), packet->length(), false));
+  if (which_channel == -1) {
+    which_channel = voe_channel();
+  }
+
+  // Stop any ringback that might be playing on the channel.
+  // It's possible the ringback has already stopped, ih which case we'll just
+  // use the opportunity to remove the channel from ringback_channels_.
+  if (engine()->voe()->file()) {
+    const std::set<int>::iterator it = ringback_channels_.find(which_channel);
+    if (it != ringback_channels_.end()) {
+      if (engine()->voe()->file()->IsPlayingFileLocally(
+          which_channel) == 1) {
+        engine()->voe()->file()->StopPlayingFileLocally(which_channel);
+        LOG(LS_INFO) << "Stopped ringback on channel " << which_channel
+                     << " due to incoming media";
+      }
+      ringback_channels_.erase(which_channel);
+    }
+  }
+
+  // Pass it off to the decoder.
+  engine()->voe()->network()->ReceivedRTPPacket(which_channel,
+                                                   packet->data(),
+                                                   packet->length());
+}
+
+void WebRtcVoiceMediaChannel::OnRtcpReceived(talk_base::Buffer* packet) {
+  // See above.
+  int which_channel = GetReceiveChannelNum(
+      ParseSsrc(packet->data(), packet->length(), true));
+  if (which_channel == -1) {
+    which_channel = voe_channel();
+  }
+
+  engine()->voe()->network()->ReceivedRTCPPacket(which_channel,
+                                                    packet->data(),
+                                                    packet->length());
+}
+
+bool WebRtcVoiceMediaChannel::MuteStream(uint32 ssrc, bool muted) {
+  if (send_ssrc_ != ssrc && ssrc != 0) {
+    LOG(LS_WARNING) << "The specified ssrc " << ssrc << " is not in use.";
+    return false;
+  }
+  if (engine()->voe()->volume()->SetInputMute(voe_channel(),
+      muted) == -1) {
+    LOG_RTCERR2(SetInputMute, voe_channel(), muted);
+    return false;
+  }
+  return true;
+}
+
+bool WebRtcVoiceMediaChannel::SetSendBandwidth(bool autobw, int bps) {
+  LOG(LS_INFO) << "WebRtcVoiceMediaChanne::SetSendBandwidth.";
+
+  if (!send_codec_) {
+    LOG(LS_INFO) << "The send codec has not been set up yet.";
+    return false;
+  }
+
+  // Bandwidth is auto by default.
+  if (autobw || bps <= 0)
+    return true;
+
+  webrtc::CodecInst codec = *send_codec_;
+  bool is_multi_rate = IsCodecMultiRate(codec);
+
+  if (is_multi_rate) {
+    // If codec is multi-rate then just set the bitrate.
+    codec.rate = bps;
+    if (!SetSendCodec(codec)) {
+      LOG(LS_INFO) << "Failed to set codec " << codec.plname
+                   << " to bitrate " << bps << " bps.";
+      return false;
+    }
+    return true;
+  } else {
+    // If codec is not multi-rate and |bps| is less than the fixed bitrate
+    // then fail. If codec is not multi-rate and |bps| exceeds or equal the
+    // fixed bitrate then ignore.
+    if (bps < codec.rate) {
+      LOG(LS_INFO) << "Failed to set codec " << codec.plname
+                   << " to bitrate " << bps << " bps"
+                   << ", requires at least " << codec.rate << " bps.";
+      return false;
+    }
+    return true;
+  }
+}
+
+bool WebRtcVoiceMediaChannel::GetStats(VoiceMediaInfo* info) {
+  // In VoiceEngine 3.5, GetRTCPStatistics will return 0 even when it fails,
+  // causing the stats to contain garbage information. To prevent this, we
+  // zero the stats structure before calling this API.
+  // TODO(juberti): Remove this workaround.
+  webrtc::CallStatistics cs;
+  unsigned int ssrc;
+  webrtc::CodecInst codec;
+  unsigned int level;
+
+  // 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;
+
+  // Data we obtain locally.
+  memset(&cs, 0, sizeof(cs));
+  if (engine()->voe()->rtp()->GetRTCPStatistics(voe_channel(), cs) == -1 ||
+      engine()->voe()->rtp()->GetLocalSSRC(voe_channel(), ssrc) == -1) {
+    return false;
+  }
+
+  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
+  // returns 0 to indicate an error value.
+  sinfo.rtt_ms = (cs.rttMs > 0) ? cs.rttMs : -1;
+
+  // Get data from the last remote RTCP report. Use default values if no data
+  // available.
+  sinfo.fraction_lost = -1.0;
+  sinfo.jitter_ms = -1;
+  sinfo.packets_lost = -1;
+  sinfo.ext_seqnum = -1;
+  std::vector<webrtc::ReportBlock> receive_blocks;
+  if (engine()->voe()->rtp()->GetRemoteRTCPReportBlocks(
+      voe_channel(), &receive_blocks) != -1 &&
+      engine()->voe()->codec()->GetSendCodec(voe_channel(),
+          codec) != -1) {
+    std::vector<webrtc::ReportBlock>::iterator iter;
+    for (iter = receive_blocks.begin(); iter != receive_blocks.end(); ++iter) {
+      // Lookup report for send ssrc only.
+      if (iter->source_SSRC == sinfo.ssrc) {
+        // Convert Q8 to floating point.
+        sinfo.fraction_lost = static_cast<float>(iter->fraction_lost) / 256;
+        // Convert samples to milliseconds.
+        if (codec.plfreq / 1000 > 0) {
+          sinfo.jitter_ms = iter->interarrival_jitter / (codec.plfreq / 1000);
+        }
+        sinfo.packets_lost = iter->cumulative_num_packets_lost;
+        sinfo.ext_seqnum = iter->extended_highest_sequence_number;
+        break;
+      }
+    }
+  }
+
+  // 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(ajm): 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.
+  std::vector<int> channels;
+  for (ChannelMap::const_iterator it = mux_channels_.begin();
+       it != mux_channels_.end(); ++it) {
+    channels.push_back(it->second);
+  }
+  if (channels.empty()) {
+    channels.push_back(voe_channel());
+  }
+
+  // Get the SSRC and stats for each receiver, based on our own calculations.
+  for (std::vector<int>::const_iterator it = channels.begin();
+       it != channels.end(); ++it) {
+    memset(&cs, 0, sizeof(cs));
+    if (engine()->voe()->rtp()->GetRemoteSSRC(*it, ssrc) != -1 &&
+        engine()->voe()->rtp()->GetRTCPStatistics(*it, cs) != -1 &&
+        engine()->voe()->codec()->GetRecCodec(*it, codec) != -1) {
+      VoiceReceiverInfo rinfo;
+      rinfo.ssrc = ssrc;
+      rinfo.bytes_rcvd = cs.bytesReceived;
+      rinfo.packets_rcvd = cs.packetsReceived;
+      // The next four fields are from the most recently sent RTCP report.
+      // Convert Q8 to floating point.
+      rinfo.fraction_lost = static_cast<float>(cs.fractionLost) / (1 << 8);
+      rinfo.packets_lost = cs.cumulativeLost;
+      rinfo.ext_seqnum = cs.extendedMax;
+      // Convert samples to milliseconds.
+      if (codec.plfreq / 1000 > 0) {
+        rinfo.jitter_ms = cs.jitterSamples / (codec.plfreq / 1000);
+      }
+
+      // Get jitter buffer and total delay (alg + jitter + playout) stats.
+      webrtc::NetworkStatistics ns;
+      if (engine()->voe()->neteq() &&
+          engine()->voe()->neteq()->GetNetworkStatistics(
+              *it, ns) != -1) {
+        rinfo.jitter_buffer_ms = ns.currentBufferSize;
+        rinfo.jitter_buffer_preferred_ms = ns.preferredBufferSize;
+        rinfo.expand_rate =
+            static_cast<float> (ns.currentExpandRate) / (1 << 14);
+      }
+      if (engine()->voe()->sync()) {
+        int playout_buffer_delay_ms = 0;
+        engine()->voe()->sync()->GetDelayEstimate(
+            *it, &rinfo.delay_estimate_ms, &playout_buffer_delay_ms);
+      }
+
+      // Get speech level.
+      rinfo.audio_level = (engine()->voe()->volume()->
+          GetSpeechOutputLevelFullRange(*it, level) != -1) ? level : -1;
+      info->receivers.push_back(rinfo);
+    }
+  }
+
+  return true;
+}
+
+void WebRtcVoiceMediaChannel::GetLastMediaError(
+    uint32* ssrc, VoiceMediaChannel::Error* error) {
+  ASSERT(ssrc != NULL);
+  ASSERT(error != NULL);
+  FindSsrc(voe_channel(), ssrc);
+  *error = WebRtcErrorToChannelError(GetLastEngineError());
+}
+
+bool WebRtcVoiceMediaChannel::FindSsrc(int channel_num, uint32* ssrc) {
+  talk_base::CritScope lock(&mux_channels_cs_);
+  ASSERT(ssrc != NULL);
+  if (channel_num == voe_channel()) {
+    unsigned local_ssrc = 0;
+    // This is a sending channel.
+    if (engine()->voe()->rtp()->GetLocalSSRC(
+        channel_num, local_ssrc) != -1) {
+      *ssrc = local_ssrc;
+    }
+    return true;
+  } else if (channel_num == -1 && send_ != SEND_NOTHING) {
+    // Sometimes the VoiceEngine core will throw error with channel_num = -1.
+    // This means the error is not limited to a specific channel.  Signal the
+    // message using ssrc=0.  If the current channel is sending, use this
+    // channel for sending the message.
+    *ssrc = 0;
+    return true;
+  } else {
+    // Check whether this is a receiving channel.
+    for (ChannelMap::const_iterator it = mux_channels_.begin();
+        it != mux_channels_.end(); ++it) {
+      if (it->second == channel_num) {
+        *ssrc = it->first;
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+void WebRtcVoiceMediaChannel::OnError(uint32 ssrc, int error) {
+  SignalMediaError(ssrc, WebRtcErrorToChannelError(error));
+}
+
+int WebRtcVoiceMediaChannel::GetOutputLevel(int channel) {
+  unsigned int ulevel;
+  int ret =
+      engine()->voe()->volume()->GetSpeechOutputLevel(channel, ulevel);
+  return (ret == 0) ? static_cast<int>(ulevel) : -1;
+}
+
+int WebRtcVoiceMediaChannel::GetReceiveChannelNum(uint32 ssrc) {
+  ChannelMap::iterator it = mux_channels_.find(ssrc);
+  if (it != mux_channels_.end())
+    return it->second;
+  return (ssrc == default_receive_ssrc_) ?  voe_channel() : -1;
+}
+
+int WebRtcVoiceMediaChannel::GetSendChannelNum(uint32 ssrc) {
+  return (ssrc == send_ssrc_) ?  voe_channel() : -1;
+}
+
+bool WebRtcVoiceMediaChannel::GetRedSendCodec(const AudioCodec& red_codec,
+    const std::vector<AudioCodec>& all_codecs, webrtc::CodecInst* send_codec) {
+  // Get the RED encodings from the parameter with no name. This may
+  // change based on what is discussed on the Jingle list.
+  // The encoding parameter is of the form "a/b"; we only support where
+  // a == b. Verify this and parse out the value into red_pt.
+  // If the parameter value is absent (as it will be until we wire up the
+  // signaling of this message), use the second codec specified (i.e. the
+  // one after "red") as the encoding parameter.
+  int red_pt = -1;
+  std::string red_params;
+  CodecParameterMap::const_iterator it = red_codec.params.find("");
+  if (it != red_codec.params.end()) {
+    red_params = it->second;
+    std::vector<std::string> red_pts;
+    if (talk_base::split(red_params, '/', &red_pts) != 2 ||
+        red_pts[0] != red_pts[1] ||
+        !talk_base::FromString(red_pts[0], &red_pt)) {
+      LOG(LS_WARNING) << "RED params " << red_params << " not supported.";
+      return false;
+    }
+  } else if (red_codec.params.empty()) {
+    LOG(LS_WARNING) << "RED params not present, using defaults";
+    if (all_codecs.size() > 1) {
+      red_pt = all_codecs[1].id;
+    }
+  }
+
+  // Try to find red_pt in |codecs|.
+  std::vector<AudioCodec>::const_iterator codec;
+  for (codec = all_codecs.begin(); codec != all_codecs.end(); ++codec) {
+    if (codec->id == red_pt)
+      break;
+  }
+
+  // If we find the right codec, that will be the codec we pass to
+  // SetSendCodec, with the desired payload type.
+  if (codec != all_codecs.end() &&
+    engine()->FindWebRtcCodec(*codec, send_codec)) {
+  } else {
+    LOG(LS_WARNING) << "RED params " << red_params << " are invalid.";
+    return false;
+  }
+
+  return true;
+}
+
+bool WebRtcVoiceMediaChannel::EnableRtcp(int channel) {
+  if (engine()->voe()->rtp()->SetRTCPStatus(channel, true) == -1) {
+    LOG_RTCERR2(SetRTCPStatus, voe_channel(), 1);
+    return false;
+  }
+  // TODO(juberti): Enable VQMon and RTCP XR reports, once we know what
+  // what we want to do with them.
+  // engine()->voe().EnableVQMon(voe_channel(), true);
+  // engine()->voe().EnableRTCP_XR(voe_channel(), true);
+  return true;
+}
+
+bool WebRtcVoiceMediaChannel::ResetRecvCodecs(int 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.pltype = -1;
+      if (engine()->voe()->codec()->SetRecPayloadType(
+          channel, voe_codec) == -1) {
+        LOG_RTCERR2(SetRecPayloadType, channel, ToString(voe_codec));
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+bool WebRtcVoiceMediaChannel::SetPlayout(int channel, bool playout) {
+  if (playout) {
+    LOG(LS_INFO) << "Starting playout for channel #" << channel;
+    if (engine()->voe()->base()->StartPlayout(channel) == -1) {
+      LOG_RTCERR1(StartPlayout, channel);
+      return false;
+    }
+  } else {
+    LOG(LS_INFO) << "Stopping playout for channel #" << channel;
+    engine()->voe()->base()->StopPlayout(channel);
+  }
+  return true;
+}
+
+uint32 WebRtcVoiceMediaChannel::ParseSsrc(const void* data, size_t len,
+                                        bool rtcp) {
+  size_t ssrc_pos = (!rtcp) ? 8 : 4;
+  uint32 ssrc = 0;
+  if (len >= (ssrc_pos + sizeof(ssrc))) {
+    ssrc = talk_base::GetBE32(static_cast<const char*>(data) + ssrc_pos);
+  }
+  return ssrc;
+}
+
+// Convert VoiceEngine error code into VoiceMediaChannel::Error enum.
+VoiceMediaChannel::Error
+    WebRtcVoiceMediaChannel::WebRtcErrorToChannelError(int err_code) {
+  switch (err_code) {
+    case 0:
+      return ERROR_NONE;
+    case VE_CANNOT_START_RECORDING:
+    case VE_MIC_VOL_ERROR:
+    case VE_GET_MIC_VOL_ERROR:
+    case VE_CANNOT_ACCESS_MIC_VOL:
+      return ERROR_REC_DEVICE_OPEN_FAILED;
+    case VE_SATURATION_WARNING:
+      return ERROR_REC_DEVICE_SATURATION;
+    case VE_REC_DEVICE_REMOVED:
+      return ERROR_REC_DEVICE_REMOVED;
+    case VE_RUNTIME_REC_WARNING:
+    case VE_RUNTIME_REC_ERROR:
+      return ERROR_REC_RUNTIME_ERROR;
+    case VE_CANNOT_START_PLAYOUT:
+    case VE_SPEAKER_VOL_ERROR:
+    case VE_GET_SPEAKER_VOL_ERROR:
+    case VE_CANNOT_ACCESS_SPEAKER_VOL:
+      return ERROR_PLAY_DEVICE_OPEN_FAILED;
+    case VE_RUNTIME_PLAY_WARNING:
+    case VE_RUNTIME_PLAY_ERROR:
+      return ERROR_PLAY_RUNTIME_ERROR;
+    case VE_TYPING_NOISE_WARNING:
+      return ERROR_REC_TYPING_NOISE_DETECTED;
+    default:
+      return VoiceMediaChannel::ERROR_OTHER;
+  }
+}
+
+int WebRtcSoundclipStream::Read(void *buf, int len) {
+  size_t res = 0;
+  mem_.Read(buf, len, &res, NULL);
+  return res;
+}
+
+int WebRtcSoundclipStream::Rewind() {
+  mem_.Rewind();
+  // Return -1 to keep VoiceEngine from looping.
+  return (loop_) ? 0 : -1;
+}
+
+}  // namespace cricket
+
+#endif  // HAVE_WEBRTC_VOICE
diff --git a/talk/media/webrtc/webrtcvoiceengine.h b/talk/media/webrtc/webrtcvoiceengine.h
new file mode 100644
index 0000000..edf07f4
--- /dev/null
+++ b/talk/media/webrtc/webrtcvoiceengine.h
@@ -0,0 +1,428 @@
+/*
+ * 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_MEDIA_WEBRTCVOICEENGINE_H_
+#define TALK_MEDIA_WEBRTCVOICEENGINE_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "talk/base/buffer.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stream.h"
+#include "talk/media/base/rtputils.h"
+#include "talk/media/webrtc/webrtccommon.h"
+#include "talk/media/webrtc/webrtcexport.h"
+#include "talk/media/webrtc/webrtcvoe.h"
+#include "talk/session/media/channel.h"
+
+#if !defined(LIBPEERCONNECTION_LIB) && \
+    !defined(LIBPEERCONNECTION_IMPLEMENTATION)
+#error "Bogus include."
+#endif
+
+
+namespace cricket {
+
+// WebRtcSoundclipStream is an adapter object that allows a memory stream to be
+// passed into WebRtc, and support looping.
+class WebRtcSoundclipStream : public webrtc::InStream {
+ public:
+  WebRtcSoundclipStream(const char* buf, size_t len)
+      : mem_(buf, len), loop_(true) {
+  }
+  void set_loop(bool loop) { loop_ = loop; }
+  virtual int Read(void* buf, int len);
+  virtual int Rewind();
+
+ private:
+  talk_base::MemoryStream mem_;
+  bool loop_;
+};
+
+// WebRtcMonitorStream is used to monitor a stream coming from WebRtc.
+// For now we just dump the data.
+class WebRtcMonitorStream : public webrtc::OutStream {
+  virtual bool Write(const void *buf, int len) {
+    return true;
+  }
+};
+
+class AudioDeviceModule;
+class VoETraceWrapper;
+class VoEWrapper;
+class VoiceProcessor;
+class WebRtcSoundclipMedia;
+class WebRtcVoiceMediaChannel;
+
+// WebRtcVoiceEngine is a class to be used with CompositeMediaEngine.
+// It uses the WebRtc VoiceEngine library for audio handling.
+class WebRtcVoiceEngine
+    : public webrtc::VoiceEngineObserver,
+      public webrtc::TraceCallback,
+      public webrtc::VoEMediaProcess  {
+ public:
+  WebRtcVoiceEngine();
+  // Dependency injection for testing.
+  WebRtcVoiceEngine(VoEWrapper* voe_wrapper,
+                    VoEWrapper* voe_wrapper_sc,
+                    VoETraceWrapper* tracing);
+  ~WebRtcVoiceEngine();
+  bool Init(talk_base::Thread* worker_thread);
+  void Terminate();
+
+  int GetCapabilities();
+  VoiceMediaChannel* CreateChannel();
+
+  SoundclipMedia* CreateSoundclip();
+
+  // TODO(pthatcher): Rename to SetOptions and replace the old
+  // flags-based SetOptions.
+  bool SetAudioOptions(const AudioOptions& options);
+  // Eventually, we will replace them with AudioOptions.
+  // In the meantime, we leave this here for backwards compat.
+  bool SetOptions(int flags);
+  // Overrides, when set, take precedence over the options on a
+  // per-option basis.  For example, if AGC is set in options and AEC
+  // is set in overrides, AGC and AEC will be both be set.  Overrides
+  // can also turn off options.  For example, if AGC is set to "on" in
+  // options and AGC is set to "off" in overrides, the result is that
+  // AGC will be off until different overrides are applied or until
+  // the overrides are cleared.  Only one set of overrides is present
+  // at a time (they do not "stack").  And when the overrides are
+  // cleared, the media engine's state reverts back to the options set
+  // via SetOptions.  This allows us to have both "persistent options"
+  // (the normal options) and "temporary options" (overrides).
+  bool SetOptionOverrides(const AudioOptions& options);
+  bool ClearOptionOverrides();
+  bool SetDelayOffset(int offset);
+  bool SetDevices(const Device* in_device, const Device* out_device);
+  bool GetOutputVolume(int* level);
+  bool SetOutputVolume(int level);
+  int GetInputLevel();
+  bool SetLocalMonitor(bool enable);
+
+  const std::vector<AudioCodec>& codecs();
+  bool FindCodec(const AudioCodec& codec);
+  bool FindWebRtcCodec(const AudioCodec& codec, webrtc::CodecInst* gcodec);
+
+  const std::vector<RtpHeaderExtension>& rtp_header_extensions() const;
+
+  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(int channel,
+                       webrtc::ProcessingTypes type,
+                       int16_t audio10ms[],
+                       int length,
+                       int sampling_freq,
+                       bool is_stereo);
+
+  // For tracking WebRtc channels. Needed because we have to pause them
+  // all when switching devices.
+  // May only be called by WebRtcVoiceMediaChannel.
+  void RegisterChannel(WebRtcVoiceMediaChannel *channel);
+  void UnregisterChannel(WebRtcVoiceMediaChannel *channel);
+
+  // May only be called by WebRtcSoundclipMedia.
+  void RegisterSoundclip(WebRtcSoundclipMedia *channel);
+  void UnregisterSoundclip(WebRtcSoundclipMedia *channel);
+
+  // Called by WebRtcVoiceMediaChannel to set a gain offset from
+  // the default AGC target level.
+  bool AdjustAgcLevel(int delta);
+
+  VoEWrapper* voe() { return voe_wrapper_.get(); }
+  VoEWrapper* voe_sc() { return voe_wrapper_sc_.get(); }
+  int GetLastEngineError();
+
+  // Set the external ADMs. This can only be called before Init.
+  bool SetAudioDeviceModule(webrtc::AudioDeviceModule* adm,
+                            webrtc::AudioDeviceModule* adm_sc);
+
+  // 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;
+  typedef sigslot::
+      signal3<uint32, MediaProcessorDirection, AudioFrame*> FrameSignal;
+
+  void Construct();
+  void ConstructCodecs();
+  bool InitInternal();
+  void SetTraceFilter(int filter);
+  void SetTraceOptions(const std::string& options);
+  // Applies either options or overrides.  Every option that is "set"
+  // will be applied.  Every option not "set" will be ignored.  This
+  // allows us to selectively turn on and off different options easily
+  // at any time.
+  bool ApplyOptions(const AudioOptions& options);
+  virtual void Print(webrtc::TraceLevel level, const char* trace, int length);
+  virtual void CallbackOnError(int channel, int errCode);
+  // Given the device type, name, and id, find device id. Return true and
+  // set the output parameter rtc_id if successful.
+  bool FindWebRtcAudioDeviceId(
+      bool is_input, const std::string& dev_name, int dev_id, int* rtc_id);
+  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();
+
+  bool UnregisterProcessorChannel(MediaProcessorDirection channel_direction,
+                                  uint32 ssrc,
+                                  VoiceProcessor* voice_processor,
+                                  MediaProcessorDirection processor_direction);
+
+  void StartAecDump(const std::string& filename);
+  void StopAecDump();
+
+  // 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.
+  FrameSignal SignalRxMediaFrame;
+  FrameSignal SignalTxMediaFrame;
+
+  static const int kDefaultLogSeverity = talk_base::LS_WARNING;
+
+  // The primary instance of WebRtc VoiceEngine.
+  talk_base::scoped_ptr<VoEWrapper> voe_wrapper_;
+  // A secondary instance, for playing out soundclips (on the 'ring' device).
+  talk_base::scoped_ptr<VoEWrapper> voe_wrapper_sc_;
+  talk_base::scoped_ptr<VoETraceWrapper> tracing_;
+  // The external audio device manager
+  webrtc::AudioDeviceModule* adm_;
+  webrtc::AudioDeviceModule* adm_sc_;
+  int log_filter_;
+  std::string log_options_;
+  bool is_dumping_aec_;
+  std::vector<AudioCodec> codecs_;
+  std::vector<RtpHeaderExtension> rtp_header_extensions_;
+  bool desired_local_monitor_enable_;
+  talk_base::scoped_ptr<WebRtcMonitorStream> monitor_;
+  SoundclipList soundclips_;
+  ChannelList channels_;
+  // channels_ can be read from WebRtc callback thread. We need a lock on that
+  // callback as well as the RegisterChannel/UnregisterChannel.
+  talk_base::CriticalSection channels_cs_;
+  webrtc::AgcConfig default_agc_config_;
+  bool initialized_;
+  // See SetOptions and SetOptionOverrides for a description of the
+  // difference between options and overrides.
+  // options_ are the base options, which combined with the
+  // option_overrides_, create the current options being used.
+  // options_ is stored so that when option_overrides_ is cleared, we
+  // can restore the options_ without the option_overrides.
+  AudioOptions options_;
+  AudioOptions option_overrides_;
+
+  // When the media processor registers with the engine, the ssrc is cached
+  // here so that a look up need not be made when the callback is invoked.
+  // This is necessary because the lookup results in mux_channels_cs lock being
+  // held and if a remote participant leaves the hangout at the same time
+  // we hit a deadlock.
+  uint32 tx_processor_ssrc_;
+  uint32 rx_processor_ssrc_;
+
+  talk_base::CriticalSection signal_media_critical_;
+};
+
+// WebRtcMediaChannel is a class that implements the common WebRtc channel
+// functionality.
+template <class T, class E>
+class WebRtcMediaChannel : public T, public webrtc::Transport {
+ public:
+  WebRtcMediaChannel(E *engine, int channel)
+      : engine_(engine), voe_channel_(channel), sequence_number_(-1) {}
+  E *engine() { return engine_; }
+  int voe_channel() const { return voe_channel_; }
+  bool valid() const { return voe_channel_ != -1; }
+
+ protected:
+  // implements Transport interface
+  virtual int SendPacket(int channel, const void *data, int len) {
+    if (!T::network_interface_) {
+      return -1;
+    }
+
+    // We need to store the sequence number to be able to pick up
+    // the same sequence when the device is restarted.
+    // TODO(oja): Remove when WebRtc has fixed the problem.
+    int seq_num;
+    if (!GetRtpSeqNum(data, len, &seq_num)) {
+      return -1;
+    }
+    if (sequence_number() == -1) {
+      LOG(INFO) << "WebRtcVoiceMediaChannel sends first packet seqnum="
+                << seq_num;
+    }
+    sequence_number_ = seq_num;
+
+    talk_base::Buffer packet(data, len, kMaxRtpPacketLen);
+    return T::network_interface_->SendPacket(&packet) ? len : -1;
+  }
+  virtual int SendRTCPPacket(int channel, const void *data, int len) {
+    if (!T::network_interface_) {
+      return -1;
+    }
+
+    talk_base::Buffer packet(data, len, kMaxRtpPacketLen);
+    return T::network_interface_->SendRtcp(&packet) ? len : -1;
+  }
+  int sequence_number() const {
+    return sequence_number_;
+  }
+
+ private:
+  E *engine_;
+  int voe_channel_;
+  int sequence_number_;
+};
+
+// WebRtcVoiceMediaChannel is an implementation of VoiceMediaChannel that uses
+// WebRtc Voice Engine.
+class WebRtcVoiceMediaChannel
+    : public WebRtcMediaChannel<VoiceMediaChannel, WebRtcVoiceEngine> {
+ public:
+  explicit WebRtcVoiceMediaChannel(WebRtcVoiceEngine *engine);
+  virtual ~WebRtcVoiceMediaChannel();
+  virtual bool SetOptions(const AudioOptions& options);
+  virtual bool GetOptions(AudioOptions* options) const {
+    *options = options_;
+    return true;
+  }
+  virtual bool SetRecvCodecs(const std::vector<AudioCodec> &codecs);
+  virtual bool SetSendCodecs(const std::vector<AudioCodec> &codecs);
+  virtual bool SetRecvRtpHeaderExtensions(
+      const std::vector<RtpHeaderExtension>& extensions);
+  virtual bool SetSendRtpHeaderExtensions(
+      const std::vector<RtpHeaderExtension>& extensions);
+  virtual bool SetPlayout(bool playout);
+  bool PausePlayout();
+  bool ResumePlayout();
+  virtual bool SetSend(SendFlags send);
+  bool PauseSend();
+  bool ResumeSend();
+  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, AudioRenderer* renderer);
+  virtual bool GetActiveStreams(AudioInfo::StreamList* actives);
+  virtual int GetOutputLevel();
+  virtual int GetTimeSinceLastTyping();
+  virtual void SetTypingDetectionParameters(int time_window,
+      int cost_per_typing, int reporting_threshold, int penalty_decay,
+      int type_event_delay);
+  virtual bool SetOutputScaling(uint32 ssrc, double left, double right);
+  virtual bool GetOutputScaling(uint32 ssrc, double* left, double* right);
+
+  virtual bool SetRingbackTone(const char *buf, int len);
+  virtual bool PlayRingbackTone(uint32 ssrc, bool play, bool loop);
+  virtual bool CanInsertDtmf();
+  virtual bool InsertDtmf(uint32 ssrc, int event, int duration, int flags);
+
+  virtual void OnPacketReceived(talk_base::Buffer* packet);
+  virtual void OnRtcpReceived(talk_base::Buffer* packet);
+  virtual void OnReadyToSend(bool ready) {}
+  virtual bool MuteStream(uint32 ssrc, bool on);
+  virtual bool SetSendBandwidth(bool autobw, int bps);
+  virtual bool GetStats(VoiceMediaInfo* info);
+  // Gets last reported error from WebRtc voice engine.  This should be only
+  // called in response a failure.
+  virtual void GetLastMediaError(uint32* ssrc,
+                                 VoiceMediaChannel::Error* error);
+  bool FindSsrc(int channel_num, uint32* ssrc);
+  void OnError(uint32 ssrc, int error);
+
+  bool sending() const { return send_ != SEND_NOTHING; }
+  int GetReceiveChannelNum(uint32 ssrc);
+  int GetSendChannelNum(uint32 ssrc);
+
+ protected:
+  int GetLastEngineError() { return engine()->GetLastEngineError(); }
+  int GetOutputLevel(int channel);
+  bool GetRedSendCodec(const AudioCodec& red_codec,
+                       const std::vector<AudioCodec>& all_codecs,
+                       webrtc::CodecInst* send_codec);
+  bool EnableRtcp(int channel);
+  bool ResetRecvCodecs(int channel);
+  bool SetPlayout(int channel, bool playout);
+  static uint32 ParseSsrc(const void* data, size_t len, bool rtcp);
+  static Error WebRtcErrorToChannelError(int err_code);
+
+ private:
+  void SetNack(uint32 ssrc, int channel, bool nack_enabled);
+  bool SetSendCodec(const webrtc::CodecInst& send_codec);
+  bool ChangePlayout(bool playout);
+  bool ChangeSend(SendFlags send);
+
+  typedef std::map<uint32, int> ChannelMap;
+  talk_base::scoped_ptr<WebRtcSoundclipStream> ringback_tone_;
+  std::set<int> ringback_channels_;  // channels playing ringback
+  std::vector<AudioCodec> recv_codecs_;
+  talk_base::scoped_ptr<webrtc::CodecInst> send_codec_;
+  AudioOptions options_;
+  bool dtmf_allowed_;
+  bool desired_playout_;
+  bool nack_enabled_;
+  bool playout_;
+  SendFlags desired_send_;
+  SendFlags send_;
+
+  uint32 send_ssrc_;
+  uint32 default_receive_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.
+  //
+  // Do not lock this on the VoE media processor thread; potential for deadlock
+  // exists.
+  mutable talk_base::CriticalSection mux_channels_cs_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_MEDIA_WEBRTCVOICEENGINE_H_
diff --git a/talk/media/webrtc/webrtcvoiceengine_unittest.cc b/talk/media/webrtc/webrtcvoiceengine_unittest.cc
new file mode 100644
index 0000000..41b81fc
--- /dev/null
+++ b/talk/media/webrtc/webrtcvoiceengine_unittest.cc
@@ -0,0 +1,2584 @@
+// Copyright 2008 Google Inc.
+//
+// Author: Justin Uberti (juberti@google.com)
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#include <objbase.h>
+#endif
+
+#include "talk/base/byteorder.h"
+#include "talk/base/gunit.h"
+#include "talk/media/base/constants.h"
+#include "talk/media/base/fakemediaengine.h"
+#include "talk/media/base/fakemediaprocessor.h"
+#include "talk/media/base/fakertp.h"
+#include "talk/media/webrtc/fakewebrtcvoiceengine.h"
+#include "talk/media/webrtc/webrtcvoiceengine.h"
+#include "talk/p2p/base/fakesession.h"
+#include "talk/session/media/channel.h"
+
+// Tests for the WebRtcVoiceEngine/VoiceChannel code.
+
+static const cricket::AudioCodec kPcmuCodec(0, "PCMU", 8000, 64000, 1, 0);
+static const cricket::AudioCodec kIsacCodec(103, "ISAC", 16000, 32000, 1, 0);
+static const cricket::AudioCodec kCeltCodec(110, "CELT", 32000, 64000, 2, 0);
+static const cricket::AudioCodec kOpusCodec(111, "opus", 48000, 64000, 2, 0);
+static const cricket::AudioCodec kRedCodec(117, "red", 8000, 0, 1, 0);
+static const cricket::AudioCodec kCn8000Codec(13, "CN", 8000, 0, 1, 0);
+static const cricket::AudioCodec kCn16000Codec(105, "CN", 16000, 0, 1, 0);
+static const cricket::AudioCodec
+    kTelephoneEventCodec(106, "telephone-event", 8000, 0, 1, 0);
+static const cricket::AudioCodec* const kAudioCodecs[] = {
+    &kPcmuCodec, &kIsacCodec, &kCeltCodec, &kOpusCodec, &kRedCodec,
+    &kCn8000Codec, &kCn16000Codec, &kTelephoneEventCodec,
+};
+const char kRingbackTone[] = "RIFF____WAVE____ABCD1234";
+static uint32 kSsrc1 = 0x99;
+static uint32 kSsrc2 = 0x98;
+
+class FakeVoEWrapper : public cricket::VoEWrapper {
+ public:
+  explicit FakeVoEWrapper(cricket::FakeWebRtcVoiceEngine* engine)
+      : cricket::VoEWrapper(engine,  // processing
+                            engine,  // base
+                            engine,  // codec
+                            engine,  // dtmf
+                            engine,  // file
+                            engine,  // hw
+                            engine,  // media
+                            engine,  // neteq
+                            engine,  // network
+                            engine,  // rtp
+                            engine,  // sync
+                            engine) {  // volume
+  }
+};
+
+class NullVoETraceWrapper : public cricket::VoETraceWrapper {
+ public:
+  virtual int SetTraceFilter(const unsigned int filter) {
+    return 0;
+  }
+  virtual int SetTraceFile(const char* fileNameUTF8) {
+    return 0;
+  }
+  virtual int SetTraceCallback(webrtc::TraceCallback* callback) {
+    return 0;
+  }
+};
+
+class WebRtcVoiceEngineTestFake : public testing::Test {
+ public:
+  class ChannelErrorListener : public sigslot::has_slots<> {
+   public:
+    explicit ChannelErrorListener(cricket::VoiceMediaChannel* channel)
+        : ssrc_(0), error_(cricket::VoiceMediaChannel::ERROR_NONE) {
+      ASSERT(channel != NULL);
+      channel->SignalMediaError.connect(
+          this, &ChannelErrorListener::OnVoiceChannelError);
+    }
+    void OnVoiceChannelError(uint32 ssrc,
+                             cricket::VoiceMediaChannel::Error error) {
+      ssrc_ = ssrc;
+      error_ = error;
+    }
+    void Reset() {
+      ssrc_ = 0;
+      error_ = cricket::VoiceMediaChannel::ERROR_NONE;
+    }
+    uint32 ssrc() const {
+      return ssrc_;
+    }
+    cricket::VoiceMediaChannel::Error error() const {
+      return error_;
+    }
+
+   private:
+    uint32 ssrc_;
+    cricket::VoiceMediaChannel::Error error_;
+  };
+
+  WebRtcVoiceEngineTestFake()
+      : voe_(kAudioCodecs, ARRAY_SIZE(kAudioCodecs)),
+        voe_sc_(kAudioCodecs, ARRAY_SIZE(kAudioCodecs)),
+        engine_(new FakeVoEWrapper(&voe_),
+                new FakeVoEWrapper(&voe_sc_),
+                new NullVoETraceWrapper()),
+        channel_(NULL), soundclip_(NULL) {
+    options_conference_.conference_mode.Set(true);
+    options_adjust_agc_.adjust_agc_delta.Set(-10);
+  }
+  bool SetupEngine() {
+    bool result = engine_.Init(talk_base::Thread::Current());
+    if (result) {
+      channel_ = engine_.CreateChannel();
+      result = (channel_ != NULL);
+    }
+    if (result) {
+      result = channel_->AddSendStream(
+          cricket::StreamParams::CreateLegacy(kSsrc1));
+    }
+    return result;
+  }
+  void DeliverPacket(const void* data, int len) {
+    talk_base::Buffer packet(data, len);
+    channel_->OnPacketReceived(&packet);
+  }
+  virtual void TearDown() {
+    delete soundclip_;
+    delete channel_;
+    engine_.Terminate();
+  }
+
+  void TestInsertDtmf(uint32 ssrc, int channel_id) {
+    // Test we can only InsertDtmf when the other side supports telephone-event.
+    std::vector<cricket::AudioCodec> codecs;
+    codecs.push_back(kPcmuCodec);
+    EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+    EXPECT_TRUE(channel_->SetSend(cricket::SEND_MICROPHONE));
+    EXPECT_FALSE(channel_->CanInsertDtmf());
+    EXPECT_FALSE(channel_->InsertDtmf(ssrc, 1, 111, cricket::DF_SEND));
+    codecs.push_back(kTelephoneEventCodec);
+    EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+    EXPECT_TRUE(channel_->CanInsertDtmf());
+    // Check we fail if the ssrc is invalid.
+    EXPECT_FALSE(channel_->InsertDtmf(-1, 1, 111, cricket::DF_SEND));
+
+    // Test send
+    EXPECT_FALSE(voe_.WasSendTelephoneEventCalled(channel_id, 2, 123));
+    EXPECT_TRUE(channel_->InsertDtmf(ssrc, 2, 123, cricket::DF_SEND));
+    EXPECT_TRUE(voe_.WasSendTelephoneEventCalled(channel_id, 2, 123));
+
+    // Test play
+    EXPECT_FALSE(voe_.WasPlayDtmfToneCalled(3, 134));
+    EXPECT_TRUE(channel_->InsertDtmf(ssrc, 3, 134, cricket::DF_PLAY));
+    EXPECT_TRUE(voe_.WasPlayDtmfToneCalled(3, 134));
+
+    // Test send and play
+    EXPECT_FALSE(voe_.WasSendTelephoneEventCalled(channel_id, 4, 145));
+    EXPECT_FALSE(voe_.WasPlayDtmfToneCalled(4, 145));
+    EXPECT_TRUE(channel_->InsertDtmf(ssrc, 4, 145,
+                                     cricket::DF_PLAY | cricket::DF_SEND));
+    EXPECT_TRUE(voe_.WasSendTelephoneEventCalled(channel_id, 4, 145));
+    EXPECT_TRUE(voe_.WasPlayDtmfToneCalled(4, 145));
+  }
+
+  // Test that send bandwidth is set correctly.
+  // |codec| is the codec under test.
+  // |default_bitrate| is the default bitrate for the codec.
+  // |auto_bitrate| is a parameter to set to SetSendBandwidth().
+  // |desired_bitrate| is a parameter to set to SetSendBandwidth().
+  // |expected_result| is expected results from SetSendBandwidth().
+  void TestSendBandwidth(const cricket::AudioCodec& codec,
+                         int default_bitrate,
+                         bool auto_bitrate,
+                         int desired_bitrate,
+                         bool expected_result) {
+    int channel_num = voe_.GetLastChannel();
+    std::vector<cricket::AudioCodec> codecs;
+
+    codecs.push_back(codec);
+    EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+
+    webrtc::CodecInst temp_codec;
+    EXPECT_FALSE(voe_.GetSendCodec(channel_num, temp_codec));
+    EXPECT_EQ(default_bitrate, temp_codec.rate);
+
+    bool result = channel_->SetSendBandwidth(auto_bitrate, desired_bitrate);
+    EXPECT_EQ(expected_result, result);
+
+    EXPECT_FALSE(voe_.GetSendCodec(channel_num, temp_codec));
+
+    if (result) {
+      // If SetSendBandwidth() returns true then bitrate is set correctly.
+      if (auto_bitrate) {
+        EXPECT_EQ(default_bitrate, temp_codec.rate);
+      } else {
+        EXPECT_EQ(desired_bitrate, temp_codec.rate);
+      }
+    } else {
+      // If SetSendBandwidth() returns false then bitrate is set to the
+      // default value.
+      EXPECT_EQ(default_bitrate, temp_codec.rate);
+    }
+  }
+
+
+ protected:
+  cricket::FakeWebRtcVoiceEngine voe_;
+  cricket::FakeWebRtcVoiceEngine voe_sc_;
+  cricket::WebRtcVoiceEngine engine_;
+  cricket::VoiceMediaChannel* channel_;
+  cricket::SoundclipMedia* soundclip_;
+
+  cricket::AudioOptions options_conference_;
+  cricket::AudioOptions options_adjust_agc_;
+};
+
+// Tests that our stub library "works".
+TEST_F(WebRtcVoiceEngineTestFake, StartupShutdown) {
+  EXPECT_FALSE(voe_.IsInited());
+  EXPECT_FALSE(voe_sc_.IsInited());
+  EXPECT_TRUE(engine_.Init(talk_base::Thread::Current()));
+  EXPECT_TRUE(voe_.IsInited());
+  EXPECT_TRUE(voe_sc_.IsInited());
+  engine_.Terminate();
+  EXPECT_FALSE(voe_.IsInited());
+  EXPECT_FALSE(voe_sc_.IsInited());
+}
+
+// Tests that we can create and destroy a channel.
+TEST_F(WebRtcVoiceEngineTestFake, CreateChannel) {
+  EXPECT_TRUE(engine_.Init(talk_base::Thread::Current()));
+  channel_ = engine_.CreateChannel();
+  EXPECT_TRUE(channel_ != NULL);
+}
+
+// Tests that we properly handle failures in CreateChannel.
+TEST_F(WebRtcVoiceEngineTestFake, CreateChannelFail) {
+  voe_.set_fail_create_channel(true);
+  EXPECT_TRUE(engine_.Init(talk_base::Thread::Current()));
+  channel_ = engine_.CreateChannel();
+  EXPECT_TRUE(channel_ == NULL);
+}
+
+// Tests that the list of supported codecs is created properly and ordered
+// correctly
+TEST_F(WebRtcVoiceEngineTestFake, CodecPreference) {
+  const std::vector<cricket::AudioCodec>& codecs = engine_.codecs();
+  ASSERT_FALSE(codecs.empty());
+  EXPECT_STRCASEEQ("opus", codecs[0].name.c_str());
+  EXPECT_EQ(48000, codecs[0].clockrate);
+  EXPECT_EQ(2, codecs[0].channels);
+  EXPECT_EQ(64000, codecs[0].bitrate);
+  int pref = codecs[0].preference;
+  for (size_t i = 1; i < codecs.size(); ++i) {
+    EXPECT_GT(pref, codecs[i].preference);
+    pref = codecs[i].preference;
+  }
+}
+
+// Tests that we can find codecs by name or id, and that we interpret the
+// clockrate and bitrate fields properly.
+TEST_F(WebRtcVoiceEngineTestFake, FindCodec) {
+  cricket::AudioCodec codec;
+  webrtc::CodecInst codec_inst;
+  // Find PCMU with explicit clockrate and bitrate.
+  EXPECT_TRUE(engine_.FindWebRtcCodec(kPcmuCodec, &codec_inst));
+  // Find ISAC with explicit clockrate and 0 bitrate.
+  EXPECT_TRUE(engine_.FindWebRtcCodec(kIsacCodec, &codec_inst));
+  // Find telephone-event with explicit clockrate and 0 bitrate.
+  EXPECT_TRUE(engine_.FindWebRtcCodec(kTelephoneEventCodec, &codec_inst));
+  // Find ISAC with a different payload id.
+  codec = kIsacCodec;
+  codec.id = 127;
+  EXPECT_TRUE(engine_.FindWebRtcCodec(codec, &codec_inst));
+  EXPECT_EQ(codec.id, codec_inst.pltype);
+  // Find PCMU with a 0 clockrate.
+  codec = kPcmuCodec;
+  codec.clockrate = 0;
+  EXPECT_TRUE(engine_.FindWebRtcCodec(codec, &codec_inst));
+  EXPECT_EQ(codec.id, codec_inst.pltype);
+  EXPECT_EQ(8000, codec_inst.plfreq);
+  // Find PCMU with a 0 bitrate.
+  codec = kPcmuCodec;
+  codec.bitrate = 0;
+  EXPECT_TRUE(engine_.FindWebRtcCodec(codec, &codec_inst));
+  EXPECT_EQ(codec.id, codec_inst.pltype);
+  EXPECT_EQ(64000, codec_inst.rate);
+  // Find ISAC with an explicit bitrate.
+  codec = kIsacCodec;
+  codec.bitrate = 32000;
+  EXPECT_TRUE(engine_.FindWebRtcCodec(codec, &codec_inst));
+  EXPECT_EQ(codec.id, codec_inst.pltype);
+  EXPECT_EQ(32000, codec_inst.rate);
+}
+
+// Test that we set our inbound codecs properly, including changing PT.
+TEST_F(WebRtcVoiceEngineTestFake, SetRecvCodecs) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kIsacCodec);
+  codecs.push_back(kPcmuCodec);
+  codecs.push_back(kTelephoneEventCodec);
+  codecs[0].id = 106;  // collide with existing telephone-event
+  codecs[2].id = 126;
+  EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  talk_base::strcpyn(gcodec.plname, ARRAY_SIZE(gcodec.plname), "ISAC");
+  gcodec.plfreq = 16000;
+  gcodec.channels = 1;
+  EXPECT_EQ(0, voe_.GetRecPayloadType(channel_num, gcodec));
+  EXPECT_EQ(106, gcodec.pltype);
+  EXPECT_STREQ("ISAC", gcodec.plname);
+  talk_base::strcpyn(gcodec.plname, ARRAY_SIZE(gcodec.plname),
+      "telephone-event");
+  gcodec.plfreq = 8000;
+  EXPECT_EQ(0, voe_.GetRecPayloadType(channel_num, gcodec));
+  EXPECT_EQ(126, gcodec.pltype);
+  EXPECT_STREQ("telephone-event", gcodec.plname);
+}
+
+// Test that we fail to set an unknown inbound codec.
+TEST_F(WebRtcVoiceEngineTestFake, SetRecvCodecsUnsupportedCodec) {
+  EXPECT_TRUE(SetupEngine());
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kIsacCodec);
+  codecs.push_back(cricket::AudioCodec(127, "XYZ", 32000, 0, 1, 0));
+  EXPECT_FALSE(channel_->SetRecvCodecs(codecs));
+}
+
+// Test that we fail if we have duplicate types in the inbound list.
+TEST_F(WebRtcVoiceEngineTestFake, SetRecvCodecsDuplicatePayloadType) {
+  EXPECT_TRUE(SetupEngine());
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kIsacCodec);
+  codecs.push_back(kCn16000Codec);
+  codecs[1].id = kIsacCodec.id;
+  EXPECT_FALSE(channel_->SetRecvCodecs(codecs));
+}
+
+// Test that we can decode OPUS without stereo parameters.
+TEST_F(WebRtcVoiceEngineTestFake, SetRecvCodecsWithOpusNoStereo) {
+  EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(options_conference_));
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kIsacCodec);
+  codecs.push_back(kPcmuCodec);
+  codecs.push_back(kOpusCodec);
+  EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
+  EXPECT_TRUE(channel_->AddRecvStream(
+      cricket::StreamParams::CreateLegacy(kSsrc1)));
+  int channel_num2 = voe_.GetLastChannel();
+  webrtc::CodecInst opus;
+  engine_.FindWebRtcCodec(kOpusCodec, &opus);
+  // Even without stereo parameters, recv codecs still specify channels = 2.
+  EXPECT_EQ(2, opus.channels);
+  EXPECT_EQ(111, opus.pltype);
+  EXPECT_STREQ("opus", opus.plname);
+  opus.pltype = 0;
+  EXPECT_EQ(0, voe_.GetRecPayloadType(channel_num2, opus));
+  EXPECT_EQ(111, opus.pltype);
+}
+
+// Test that we can decode OPUS with stereo = 0.
+TEST_F(WebRtcVoiceEngineTestFake, SetRecvCodecsWithOpus0Stereo) {
+  EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(options_conference_));
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kIsacCodec);
+  codecs.push_back(kPcmuCodec);
+  codecs.push_back(kOpusCodec);
+  codecs[2].params["stereo"] = "0";
+  EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
+  EXPECT_TRUE(channel_->AddRecvStream(
+      cricket::StreamParams::CreateLegacy(kSsrc1)));
+  int channel_num2 = voe_.GetLastChannel();
+  webrtc::CodecInst opus;
+  engine_.FindWebRtcCodec(kOpusCodec, &opus);
+  // Even when stereo is off, recv codecs still specify channels = 2.
+  EXPECT_EQ(2, opus.channels);
+  EXPECT_EQ(111, opus.pltype);
+  EXPECT_STREQ("opus", opus.plname);
+  opus.pltype = 0;
+  EXPECT_EQ(0, voe_.GetRecPayloadType(channel_num2, opus));
+  EXPECT_EQ(111, opus.pltype);
+}
+
+// Test that we can decode OPUS with stereo = 1.
+TEST_F(WebRtcVoiceEngineTestFake, SetRecvCodecsWithOpus1Stereo) {
+  EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(options_conference_));
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kIsacCodec);
+  codecs.push_back(kPcmuCodec);
+  codecs.push_back(kOpusCodec);
+  codecs[2].params["stereo"] = "1";
+  EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
+  EXPECT_TRUE(channel_->AddRecvStream(
+      cricket::StreamParams::CreateLegacy(kSsrc1)));
+  int channel_num2 = voe_.GetLastChannel();
+  webrtc::CodecInst opus;
+  engine_.FindWebRtcCodec(kOpusCodec, &opus);
+  EXPECT_EQ(2, opus.channels);
+  EXPECT_EQ(111, opus.pltype);
+  EXPECT_STREQ("opus", opus.plname);
+  opus.pltype = 0;
+  EXPECT_EQ(0, voe_.GetRecPayloadType(channel_num2, opus));
+  EXPECT_EQ(111, opus.pltype);
+}
+
+// Test that changes to recv codecs are applied to all streams.
+TEST_F(WebRtcVoiceEngineTestFake, SetRecvCodecsWithMultipleStreams) {
+  EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(options_conference_));
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kIsacCodec);
+  codecs.push_back(kPcmuCodec);
+  codecs.push_back(kTelephoneEventCodec);
+  codecs[0].id = 106;  // collide with existing telephone-event
+  codecs[2].id = 126;
+  EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
+  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");
+  gcodec.plfreq = 16000;
+  gcodec.channels = 1;
+  EXPECT_EQ(0, voe_.GetRecPayloadType(channel_num2, gcodec));
+  EXPECT_EQ(106, gcodec.pltype);
+  EXPECT_STREQ("ISAC", gcodec.plname);
+  talk_base::strcpyn(gcodec.plname, ARRAY_SIZE(gcodec.plname),
+      "telephone-event");
+  gcodec.plfreq = 8000;
+  gcodec.channels = 1;
+  EXPECT_EQ(0, voe_.GetRecPayloadType(channel_num2, gcodec));
+  EXPECT_EQ(126, gcodec.pltype);
+  EXPECT_STREQ("telephone-event", gcodec.plname);
+}
+
+TEST_F(WebRtcVoiceEngineTestFake, SetRecvCodecsAfterAddingStreams) {
+  EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(options_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;
+  gcodec.channels = 1;
+  EXPECT_EQ(0, voe_.GetRecPayloadType(channel_num2, gcodec));
+  EXPECT_EQ(106, gcodec.pltype);
+  EXPECT_STREQ("ISAC", gcodec.plname);
+}
+
+// Test that we can apply the same set of codecs again while playing.
+TEST_F(WebRtcVoiceEngineTestFake, SetRecvCodecsWhilePlaying) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kIsacCodec);
+  codecs.push_back(kCn16000Codec);
+  EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
+  EXPECT_TRUE(channel_->SetPlayout(true));
+  EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
+
+  // Changing the payload type of a codec should fail.
+  codecs[0].id = 127;
+  EXPECT_FALSE(channel_->SetRecvCodecs(codecs));
+  EXPECT_TRUE(voe_.GetPlayout(channel_num));
+}
+
+// Test that we can add a codec while playing.
+TEST_F(WebRtcVoiceEngineTestFake, AddRecvCodecsWhilePlaying) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kIsacCodec);
+  codecs.push_back(kCn16000Codec);
+  EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
+  EXPECT_TRUE(channel_->SetPlayout(true));
+
+  codecs.push_back(kOpusCodec);
+  EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
+  EXPECT_TRUE(voe_.GetPlayout(channel_num));
+  webrtc::CodecInst gcodec;
+  EXPECT_TRUE(engine_.FindWebRtcCodec(kOpusCodec, &gcodec));
+  EXPECT_EQ(kOpusCodec.id, gcodec.pltype);
+}
+
+TEST_F(WebRtcVoiceEngineTestFake, SetSendBandwidthAuto) {
+  EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+
+  // Test that when autobw is true, bitrate is kept as the default
+  // value. autobw is true for the following tests.
+
+  // ISAC, default bitrate == 32000.
+  TestSendBandwidth(kIsacCodec, 32000, true, 96000, true);
+
+  // PCMU, default bitrate == 64000.
+  TestSendBandwidth(kPcmuCodec, 64000, true, 96000, true);
+
+  // CELT, default bitrate == 64000.
+  TestSendBandwidth(kCeltCodec, 64000, true, 96000, true);
+
+  // opus, default bitrate == 64000.
+  TestSendBandwidth(kOpusCodec, 64000, true, 96000, true);
+}
+
+TEST_F(WebRtcVoiceEngineTestFake, SetSendBandwidthFixedMultiRate) {
+  EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+
+  // Test that we can set bitrate if a multi-rate codec is used.
+  // autobw is false for the following tests.
+
+  // ISAC, default bitrate == 32000.
+  TestSendBandwidth(kIsacCodec, 32000, false, 128000, true);
+
+  // CELT, default bitrate == 64000.
+  TestSendBandwidth(kCeltCodec, 64000, false, 96000, true);
+
+  // opus, default bitrate == 64000.
+  TestSendBandwidth(kOpusCodec, 64000, false, 96000, true);
+}
+
+// Test that bitrate cannot be set for CBR codecs.
+// Bitrate is ignored if it is higher than the fixed bitrate.
+// Bitrate less then the fixed bitrate is an error.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendBandwidthFixedCbr) {
+  EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+
+  webrtc::CodecInst codec;
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+
+  // PCMU, default bitrate == 64000.
+  codecs.push_back(kPcmuCodec);
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, codec));
+  EXPECT_EQ(64000, codec.rate);
+  EXPECT_TRUE(channel_->SetSendBandwidth(false, 128000));
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, codec));
+  EXPECT_EQ(64000, codec.rate);
+  EXPECT_FALSE(channel_->SetSendBandwidth(false, 128));
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, codec));
+  EXPECT_EQ(64000, codec.rate);
+}
+
+// Test that we apply codecs properly.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecs) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kIsacCodec);
+  codecs.push_back(kPcmuCodec);
+  codecs.push_back(kRedCodec);
+  codecs[0].id = 96;
+  codecs[0].bitrate = 48000;
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(96, gcodec.pltype);
+  EXPECT_EQ(48000, gcodec.rate);
+  EXPECT_STREQ("ISAC", gcodec.plname);
+  EXPECT_FALSE(voe_.GetVAD(channel_num));
+  EXPECT_FALSE(voe_.GetFEC(channel_num));
+  EXPECT_EQ(13, voe_.GetSendCNPayloadType(channel_num, false));
+  EXPECT_EQ(105, voe_.GetSendCNPayloadType(channel_num, true));
+  EXPECT_EQ(106, voe_.GetSendTelephoneEventPayloadType(channel_num));
+}
+
+// TODO(pthatcher): Change failure behavior to returning false rather
+// than defaulting to PCMU.
+// Test that if clockrate is not 48000 for opus, we fail by fallback to PCMU.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecOpusBadClockrate) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kOpusCodec);
+  codecs[0].bitrate = 0;
+  codecs[0].clockrate = 50000;
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_STREQ("PCMU", gcodec.plname);
+}
+
+// Test that if channels=0 for opus, we fail by falling back to PCMU.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecOpusBad0ChannelsNoStereo) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kOpusCodec);
+  codecs[0].bitrate = 0;
+  codecs[0].channels = 0;
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_STREQ("PCMU", gcodec.plname);
+}
+
+// Test that if channels=0 for opus, we fail by falling back to PCMU.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecOpusBad0Channels1Stereo) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kOpusCodec);
+  codecs[0].bitrate = 0;
+  codecs[0].channels = 0;
+  codecs[0].params["stereo"] = "1";
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_STREQ("PCMU", gcodec.plname);
+}
+
+// Test that if channel is 1 for opus and there's no stereo, we fail.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecOpus1ChannelNoStereo) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kOpusCodec);
+  codecs[0].bitrate = 0;
+  codecs[0].channels = 1;
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_STREQ("PCMU", gcodec.plname);
+}
+
+// Test that if channel is 1 for opus and stereo=0, we fail.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecOpusBad1Channel0Stereo) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kOpusCodec);
+  codecs[0].bitrate = 0;
+  codecs[0].channels = 1;
+  codecs[0].params["stereo"] = "0";
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_STREQ("PCMU", gcodec.plname);
+}
+
+// Test that if channel is 1 for opus and stereo=1, we fail.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecOpusBad1Channel1Stereo) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kOpusCodec);
+  codecs[0].bitrate = 0;
+  codecs[0].channels = 1;
+  codecs[0].params["stereo"] = "1";
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_STREQ("PCMU", gcodec.plname);
+}
+
+// Test that with bitrate=0 and no stereo,
+// channels and bitrate are 1 and 32000.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecOpusGood0BitrateNoStereo) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kOpusCodec);
+  codecs[0].bitrate = 0;
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_STREQ("opus", gcodec.plname);
+  EXPECT_EQ(1, gcodec.channels);
+  EXPECT_EQ(32000, gcodec.rate);
+}
+
+// Test that with bitrate=0 and stereo=0,
+// channels and bitrate are 1 and 32000.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecOpusGood0Bitrate0Stereo) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kOpusCodec);
+  codecs[0].bitrate = 0;
+  codecs[0].params["stereo"] = "0";
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_STREQ("opus", gcodec.plname);
+  EXPECT_EQ(1, gcodec.channels);
+  EXPECT_EQ(32000, gcodec.rate);
+}
+
+// Test that with bitrate=0 and stereo=1,
+// channels and bitrate are 2 and 64000.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecOpusGood0Bitrate1Stereo) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kOpusCodec);
+  codecs[0].bitrate = 0;
+  codecs[0].params["stereo"] = "1";
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_STREQ("opus", gcodec.plname);
+  EXPECT_EQ(2, gcodec.channels);
+  EXPECT_EQ(64000, gcodec.rate);
+}
+
+// Test that with bitrate=N and stereo unset,
+// channels and bitrate are 1 and N.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecOpusGoodNBitrateNoStereo) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kOpusCodec);
+  codecs[0].bitrate = 96000;
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(111, gcodec.pltype);
+  EXPECT_EQ(96000, gcodec.rate);
+  EXPECT_STREQ("opus", gcodec.plname);
+  EXPECT_EQ(1, gcodec.channels);
+  EXPECT_EQ(48000, gcodec.plfreq);
+}
+
+// Test that with bitrate=N and stereo=0,
+// channels and bitrate are 1 and N.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecOpusGoodNBitrate0Stereo) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kOpusCodec);
+  codecs[0].bitrate = 30000;
+  codecs[0].params["stereo"] = "0";
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(1, gcodec.channels);
+  EXPECT_EQ(30000, gcodec.rate);
+  EXPECT_STREQ("opus", gcodec.plname);
+}
+
+// Test that with bitrate=N and without any parameters,
+// channels and bitrate are 1 and N.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecOpusGoodNBitrateNoParameters) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kOpusCodec);
+  codecs[0].bitrate = 30000;
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(1, gcodec.channels);
+  EXPECT_EQ(30000, gcodec.rate);
+  EXPECT_STREQ("opus", gcodec.plname);
+}
+
+// Test that with bitrate=N and stereo=1,
+// channels and bitrate are 2 and N.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecOpusGoodNBitrate1Stereo) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kOpusCodec);
+  codecs[0].bitrate = 30000;
+  codecs[0].params["stereo"] = "1";
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(2, gcodec.channels);
+  EXPECT_EQ(30000, gcodec.rate);
+  EXPECT_STREQ("opus", gcodec.plname);
+}
+
+// Test that we can enable NACK with opus.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecEnableNack) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kOpusCodec);
+  codecs[0].AddFeedbackParam(cricket::FeedbackParam(cricket::kRtcpFbParamNack,
+                                                    cricket::kParamValueEmpty));
+  EXPECT_FALSE(voe_.GetNACK(channel_num));
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_TRUE(voe_.GetNACK(channel_num));
+}
+
+// Test that we can enable NACK on receive streams.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecEnableNackRecvStreams) {
+  EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(options_conference_));
+  int channel_num1 = voe_.GetLastChannel();
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
+  int channel_num2 = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kOpusCodec);
+  codecs[0].AddFeedbackParam(cricket::FeedbackParam(cricket::kRtcpFbParamNack,
+                                                    cricket::kParamValueEmpty));
+  EXPECT_FALSE(voe_.GetNACK(channel_num1));
+  EXPECT_FALSE(voe_.GetNACK(channel_num2));
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_TRUE(voe_.GetNACK(channel_num1));
+  EXPECT_TRUE(voe_.GetNACK(channel_num2));
+}
+
+// Test that we can disable NACK.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecDisableNack) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kOpusCodec);
+  codecs[0].AddFeedbackParam(cricket::FeedbackParam(cricket::kRtcpFbParamNack,
+                                                    cricket::kParamValueEmpty));
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_TRUE(voe_.GetNACK(channel_num));
+
+  codecs.clear();
+  codecs.push_back(kOpusCodec);
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_FALSE(voe_.GetNACK(channel_num));
+}
+
+// Test that we can disable NACK on receive streams.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecDisableNackRecvStreams) {
+  EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(options_conference_));
+  int channel_num1 = voe_.GetLastChannel();
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
+  int channel_num2 = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kOpusCodec);
+  codecs[0].AddFeedbackParam(cricket::FeedbackParam(cricket::kRtcpFbParamNack,
+                                                    cricket::kParamValueEmpty));
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_TRUE(voe_.GetNACK(channel_num1));
+  EXPECT_TRUE(voe_.GetNACK(channel_num2));
+
+  codecs.clear();
+  codecs.push_back(kOpusCodec);
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_FALSE(voe_.GetNACK(channel_num1));
+  EXPECT_FALSE(voe_.GetNACK(channel_num2));
+}
+
+// Test that NACK is enabled on a new receive stream.
+TEST_F(WebRtcVoiceEngineTestFake, AddRecvStreamEnableNack) {
+  EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(options_conference_));
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kIsacCodec);
+  codecs[0].AddFeedbackParam(cricket::FeedbackParam(cricket::kRtcpFbParamNack,
+                                                    cricket::kParamValueEmpty));
+  codecs.push_back(kCn16000Codec);
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_TRUE(voe_.GetNACK(channel_num));
+
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
+  channel_num = voe_.GetLastChannel();
+  EXPECT_TRUE(voe_.GetNACK(channel_num));
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(3)));
+  channel_num = voe_.GetLastChannel();
+  EXPECT_TRUE(voe_.GetNACK(channel_num));
+}
+
+// Test that we can apply CELT with stereo mode but fail with mono mode.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsCelt) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kCeltCodec);
+  codecs.push_back(kPcmuCodec);
+  codecs[0].id = 96;
+  codecs[0].channels = 2;
+  codecs[0].bitrate = 96000;
+  codecs[1].bitrate = 96000;
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(96, gcodec.pltype);
+  EXPECT_EQ(96000, gcodec.rate);
+  EXPECT_EQ(2, gcodec.channels);
+  EXPECT_STREQ("CELT", gcodec.plname);
+  // Doesn't support mono, expect it to fall back to the next codec in the list.
+  codecs[0].channels = 1;
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(0, gcodec.pltype);
+  EXPECT_EQ(1, gcodec.channels);
+  EXPECT_EQ(64000, gcodec.rate);
+  EXPECT_STREQ("PCMU", gcodec.plname);
+}
+
+// Test that we can switch back and forth between CELT and ISAC with CN.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsIsacCeltSwitching) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> celt_codecs;
+  celt_codecs.push_back(kCeltCodec);
+  EXPECT_TRUE(channel_->SetSendCodecs(celt_codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(110, gcodec.pltype);
+  EXPECT_STREQ("CELT", gcodec.plname);
+
+  std::vector<cricket::AudioCodec> isac_codecs;
+  isac_codecs.push_back(kIsacCodec);
+  isac_codecs.push_back(kCn16000Codec);
+  isac_codecs.push_back(kCeltCodec);
+  EXPECT_TRUE(channel_->SetSendCodecs(isac_codecs));
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(103, gcodec.pltype);
+  EXPECT_STREQ("ISAC", gcodec.plname);
+
+  EXPECT_TRUE(channel_->SetSendCodecs(celt_codecs));
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(110, gcodec.pltype);
+  EXPECT_STREQ("CELT", gcodec.plname);
+}
+
+// Test that we handle various ways of specifying bitrate.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsBitrate) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kIsacCodec);  // bitrate == 32000
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(103, gcodec.pltype);
+  EXPECT_STREQ("ISAC", gcodec.plname);
+  EXPECT_EQ(32000, gcodec.rate);
+
+  codecs[0].bitrate = 0;         // bitrate == default
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(103, gcodec.pltype);
+  EXPECT_STREQ("ISAC", gcodec.plname);
+  EXPECT_EQ(-1, gcodec.rate);
+
+  codecs[0].bitrate = 28000;     // bitrate == 28000
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(103, gcodec.pltype);
+  EXPECT_STREQ("ISAC", gcodec.plname);
+  EXPECT_EQ(28000, gcodec.rate);
+
+  codecs[0] = kPcmuCodec;        // bitrate == 64000
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(0, gcodec.pltype);
+  EXPECT_STREQ("PCMU", gcodec.plname);
+  EXPECT_EQ(64000, gcodec.rate);
+
+  codecs[0].bitrate = 0;         // bitrate == default
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(0, gcodec.pltype);
+  EXPECT_STREQ("PCMU", gcodec.plname);
+  EXPECT_EQ(64000, gcodec.rate);
+
+  codecs[0] = kOpusCodec;
+  codecs[0].bitrate = 0;         // bitrate == default
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(111, gcodec.pltype);
+  EXPECT_STREQ("opus", gcodec.plname);
+  EXPECT_EQ(32000, gcodec.rate);
+}
+
+// Test that we fall back to PCMU if no codecs are specified.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsNoCodecs) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(0, gcodec.pltype);
+  EXPECT_STREQ("PCMU", gcodec.plname);
+  EXPECT_FALSE(voe_.GetVAD(channel_num));
+  EXPECT_FALSE(voe_.GetFEC(channel_num));
+  EXPECT_EQ(13, voe_.GetSendCNPayloadType(channel_num, false));
+  EXPECT_EQ(105, voe_.GetSendCNPayloadType(channel_num, true));
+  EXPECT_EQ(106, voe_.GetSendTelephoneEventPayloadType(channel_num));
+}
+
+// Test that we set VAD and DTMF types correctly.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsCNandDTMF) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kIsacCodec);
+  codecs.push_back(kPcmuCodec);
+  // TODO(juberti): cn 32000
+  codecs.push_back(kCn16000Codec);
+  codecs.push_back(kCn8000Codec);
+  codecs.push_back(kTelephoneEventCodec);
+  codecs.push_back(kRedCodec);
+  codecs[0].id = 96;
+  codecs[2].id = 97;  // wideband CN
+  codecs[4].id = 98;  // DTMF
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(96, gcodec.pltype);
+  EXPECT_STREQ("ISAC", gcodec.plname);
+  EXPECT_TRUE(voe_.GetVAD(channel_num));
+  EXPECT_FALSE(voe_.GetFEC(channel_num));
+  EXPECT_EQ(13, voe_.GetSendCNPayloadType(channel_num, false));
+  EXPECT_EQ(97, voe_.GetSendCNPayloadType(channel_num, true));
+  EXPECT_EQ(98, voe_.GetSendTelephoneEventPayloadType(channel_num));
+}
+
+// Test that we only apply VAD if we have a CN codec that matches the
+// send codec clockrate.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsCNNoMatch) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  // Set ISAC(16K) and CN(16K). VAD should be activated.
+  codecs.push_back(kIsacCodec);
+  codecs.push_back(kCn16000Codec);
+  codecs[1].id = 97;
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_STREQ("ISAC", gcodec.plname);
+  EXPECT_TRUE(voe_.GetVAD(channel_num));
+  EXPECT_EQ(97, voe_.GetSendCNPayloadType(channel_num, true));
+  // Set PCMU(8K) and CN(16K). VAD should not be activated.
+  codecs[0] = kPcmuCodec;
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_STREQ("PCMU", gcodec.plname);
+  EXPECT_FALSE(voe_.GetVAD(channel_num));
+  // Set PCMU(8K) and CN(8K). VAD should be activated.
+  codecs[1] = kCn8000Codec;
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_STREQ("PCMU", gcodec.plname);
+  EXPECT_TRUE(voe_.GetVAD(channel_num));
+  EXPECT_EQ(13, voe_.GetSendCNPayloadType(channel_num, false));
+   // Set ISAC(16K) and CN(8K). VAD should not be activated.
+  codecs[0] = kIsacCodec;
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_STREQ("ISAC", gcodec.plname);
+  EXPECT_FALSE(voe_.GetVAD(channel_num));
+}
+
+// Test that we perform case-insensitive matching of codec names.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsCaseInsensitive) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kIsacCodec);
+  codecs.push_back(kPcmuCodec);
+  codecs.push_back(kCn16000Codec);
+  codecs.push_back(kCn8000Codec);
+  codecs.push_back(kTelephoneEventCodec);
+  codecs.push_back(kRedCodec);
+  codecs[0].name = "iSaC";
+  codecs[0].id = 96;
+  codecs[2].id = 97;  // wideband CN
+  codecs[4].id = 98;  // DTMF
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(96, gcodec.pltype);
+  EXPECT_STREQ("ISAC", gcodec.plname);
+  EXPECT_TRUE(voe_.GetVAD(channel_num));
+  EXPECT_FALSE(voe_.GetFEC(channel_num));
+  EXPECT_EQ(13, voe_.GetSendCNPayloadType(channel_num, false));
+  EXPECT_EQ(97, voe_.GetSendCNPayloadType(channel_num, true));
+  EXPECT_EQ(98, voe_.GetSendTelephoneEventPayloadType(channel_num));
+}
+
+// Test that we set up FEC correctly.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsRED) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kRedCodec);
+  codecs.push_back(kIsacCodec);
+  codecs.push_back(kPcmuCodec);
+  codecs[0].id = 127;
+  codecs[0].params[""] = "96/96";
+  codecs[1].id = 96;
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(96, gcodec.pltype);
+  EXPECT_STREQ("ISAC", gcodec.plname);
+  EXPECT_TRUE(voe_.GetFEC(channel_num));
+  EXPECT_EQ(127, voe_.GetSendFECPayloadType(channel_num));
+}
+
+// Test that we set up FEC correctly if params are omitted.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsREDNoParams) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kRedCodec);
+  codecs.push_back(kIsacCodec);
+  codecs.push_back(kPcmuCodec);
+  codecs[0].id = 127;
+  codecs[1].id = 96;
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(96, gcodec.pltype);
+  EXPECT_STREQ("ISAC", gcodec.plname);
+  EXPECT_TRUE(voe_.GetFEC(channel_num));
+  EXPECT_EQ(127, voe_.GetSendFECPayloadType(channel_num));
+}
+
+// Test that we ignore RED if the parameters aren't named the way we expect.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsBadRED1) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kRedCodec);
+  codecs.push_back(kIsacCodec);
+  codecs.push_back(kPcmuCodec);
+  codecs[0].id = 127;
+  codecs[0].params["ABC"] = "96/96";
+  codecs[1].id = 96;
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(96, gcodec.pltype);
+  EXPECT_STREQ("ISAC", gcodec.plname);
+  EXPECT_FALSE(voe_.GetFEC(channel_num));
+}
+
+// Test that we ignore RED if it uses different primary/secondary encoding.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsBadRED2) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kRedCodec);
+  codecs.push_back(kIsacCodec);
+  codecs.push_back(kPcmuCodec);
+  codecs[0].id = 127;
+  codecs[0].params[""] = "96/0";
+  codecs[1].id = 96;
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(96, gcodec.pltype);
+  EXPECT_STREQ("ISAC", gcodec.plname);
+  EXPECT_FALSE(voe_.GetFEC(channel_num));
+}
+
+// Test that we ignore RED if it uses more than 2 encodings.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsBadRED3) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kRedCodec);
+  codecs.push_back(kIsacCodec);
+  codecs.push_back(kPcmuCodec);
+  codecs[0].id = 127;
+  codecs[0].params[""] = "96/96/96";
+  codecs[1].id = 96;
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(96, gcodec.pltype);
+  EXPECT_STREQ("ISAC", gcodec.plname);
+  EXPECT_FALSE(voe_.GetFEC(channel_num));
+}
+
+// Test that we ignore RED if it has bogus codec ids.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsBadRED4) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kRedCodec);
+  codecs.push_back(kIsacCodec);
+  codecs.push_back(kPcmuCodec);
+  codecs[0].id = 127;
+  codecs[0].params[""] = "ABC/ABC";
+  codecs[1].id = 96;
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(96, gcodec.pltype);
+  EXPECT_STREQ("ISAC", gcodec.plname);
+  EXPECT_FALSE(voe_.GetFEC(channel_num));
+}
+
+// Test that we ignore RED if it refers to a codec that is not present.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsBadRED5) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kRedCodec);
+  codecs.push_back(kIsacCodec);
+  codecs.push_back(kPcmuCodec);
+  codecs[0].id = 127;
+  codecs[0].params[""] = "97/97";
+  codecs[1].id = 96;
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  webrtc::CodecInst gcodec;
+  EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+  EXPECT_EQ(96, gcodec.pltype);
+  EXPECT_STREQ("ISAC", gcodec.plname);
+  EXPECT_FALSE(voe_.GetFEC(channel_num));
+}
+
+// Test that we support setting an empty list of recv header extensions.
+TEST_F(WebRtcVoiceEngineTestFake, SetRecvRtpHeaderExtensions) {
+  EXPECT_TRUE(SetupEngine());
+  std::vector<cricket::RtpHeaderExtension> extensions;
+  int channel_num = voe_.GetLastChannel();
+  bool enable = false;
+  unsigned char id = 0;
+
+  // An empty list shouldn't cause audio-level headers to be enabled.
+  EXPECT_TRUE(channel_->SetRecvRtpHeaderExtensions(extensions));
+  EXPECT_EQ(0, voe_.GetRTPAudioLevelIndicationStatus(
+      channel_num, enable, id));
+  EXPECT_FALSE(enable);
+
+  // Nor should indicating we can receive the audio-level header.
+  extensions.push_back(cricket::RtpHeaderExtension(
+      "urn:ietf:params:rtp-hdrext:ssrc-audio-level", 8));
+  EXPECT_TRUE(channel_->SetRecvRtpHeaderExtensions(extensions));
+  EXPECT_EQ(0, voe_.GetRTPAudioLevelIndicationStatus(
+      channel_num, enable, id));
+  EXPECT_FALSE(enable);
+}
+
+// Test that we support setting certain send header extensions.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendRtpHeaderExtensions) {
+  EXPECT_TRUE(SetupEngine());
+  std::vector<cricket::RtpHeaderExtension> extensions;
+  int channel_num = voe_.GetLastChannel();
+  bool enable = false;
+  unsigned char id = 0;
+
+  // Ensure audio levels are off by default.
+  EXPECT_EQ(0, voe_.GetRTPAudioLevelIndicationStatus(
+      channel_num, enable, id));
+  EXPECT_FALSE(enable);
+
+  // Ensure unknown extentions won't cause an error.
+  extensions.push_back(cricket::RtpHeaderExtension(
+      "urn:ietf:params:unknowextention", 1));
+  EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(extensions));
+  EXPECT_EQ(0, voe_.GetRTPAudioLevelIndicationStatus(
+      channel_num, enable, id));
+  EXPECT_FALSE(enable);
+
+  // Ensure audio levels stay off with an empty list of headers.
+  EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(extensions));
+  EXPECT_EQ(0, voe_.GetRTPAudioLevelIndicationStatus(
+      channel_num, enable, id));
+  EXPECT_FALSE(enable);
+
+  // Ensure audio levels are enabled if the audio-level header is specified.
+  extensions.push_back(cricket::RtpHeaderExtension(
+      "urn:ietf:params:rtp-hdrext:ssrc-audio-level", 8));
+  EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(extensions));
+  EXPECT_EQ(0, voe_.GetRTPAudioLevelIndicationStatus(
+      channel_num, enable, id));
+  EXPECT_TRUE(enable);
+  EXPECT_EQ(8, id);
+
+  // Ensure audio levels go back off with an empty list.
+  extensions.clear();
+  EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(extensions));
+  EXPECT_EQ(0, voe_.GetRTPAudioLevelIndicationStatus(
+      channel_num, enable, id));
+  EXPECT_FALSE(enable);
+}
+
+// Test that we can create a channel and start sending/playing out on it.
+TEST_F(WebRtcVoiceEngineTestFake, SendAndPlayout) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kPcmuCodec);
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_TRUE(channel_->SetSend(cricket::SEND_MICROPHONE));
+  EXPECT_TRUE(voe_.GetSend(channel_num));
+  EXPECT_TRUE(channel_->SetPlayout(true));
+  EXPECT_TRUE(voe_.GetPlayout(channel_num));
+  EXPECT_TRUE(channel_->SetSend(cricket::SEND_NOTHING));
+  EXPECT_FALSE(voe_.GetSend(channel_num));
+  EXPECT_TRUE(channel_->SetPlayout(false));
+  EXPECT_FALSE(voe_.GetPlayout(channel_num));
+}
+
+// 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(WebRtcVoiceEngineTestFake, SendAndPlayoutWithMultipleStreams) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num1 = voe_.GetLastChannel();
+
+  // Start playout on the default channel.
+  EXPECT_TRUE(channel_->SetOptions(options_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_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
+  int channel_num2 = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kPcmuCodec);
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_TRUE(channel_->SetSend(cricket::SEND_MICROPHONE));
+  EXPECT_TRUE(voe_.GetSend(channel_num1));
+  EXPECT_FALSE(voe_.GetSend(channel_num2));
+
+  // Make sure only the new channel is played out.
+  EXPECT_FALSE(voe_.GetPlayout(channel_num1));
+  EXPECT_TRUE(voe_.GetPlayout(channel_num2));
+
+  // Adding yet another stream should have stream 2 and 3 enabled for playout.
+  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));
+  EXPECT_TRUE(voe_.GetPlayout(channel_num3));
+  EXPECT_FALSE(voe_.GetSend(channel_num3));
+
+  // Stop sending.
+  EXPECT_TRUE(channel_->SetSend(cricket::SEND_NOTHING));
+  EXPECT_FALSE(voe_.GetSend(channel_num1));
+  EXPECT_FALSE(voe_.GetSend(channel_num2));
+  EXPECT_FALSE(voe_.GetSend(channel_num3));
+
+  // Stop playout.
+  EXPECT_TRUE(channel_->SetPlayout(false));
+  EXPECT_FALSE(voe_.GetPlayout(channel_num1));
+  EXPECT_FALSE(voe_.GetPlayout(channel_num2));
+  EXPECT_FALSE(voe_.GetPlayout(channel_num3));
+
+  // Restart playout and make sure the default channel still is not played out.
+  EXPECT_TRUE(channel_->SetPlayout(true));
+  EXPECT_FALSE(voe_.GetPlayout(channel_num1));
+  EXPECT_TRUE(voe_.GetPlayout(channel_num2));
+  EXPECT_TRUE(voe_.GetPlayout(channel_num3));
+
+  // Now remove the new streams and verify that the default channel is
+  // played out again.
+  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(WebRtcVoiceEngineTestFake, SetDevices) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kPcmuCodec);
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+
+  cricket::Device default_dev(cricket::kFakeDefaultDeviceName,
+                              cricket::kFakeDefaultDeviceId);
+  cricket::Device dev(cricket::kFakeDeviceName,
+                      cricket::kFakeDeviceId);
+
+  // Test SetDevices() while not sending or playing.
+  EXPECT_TRUE(engine_.SetDevices(&default_dev, &default_dev));
+
+  // Test SetDevices() while sending and playing.
+  EXPECT_TRUE(engine_.SetLocalMonitor(true));
+  EXPECT_TRUE(channel_->SetSend(cricket::SEND_MICROPHONE));
+  EXPECT_TRUE(channel_->SetPlayout(true));
+  EXPECT_TRUE(voe_.GetRecordingMicrophone());
+  EXPECT_TRUE(voe_.GetSend(channel_num));
+  EXPECT_TRUE(voe_.GetPlayout(channel_num));
+
+  EXPECT_TRUE(engine_.SetDevices(&dev, &dev));
+
+  EXPECT_TRUE(voe_.GetRecordingMicrophone());
+  EXPECT_TRUE(voe_.GetSend(channel_num));
+  EXPECT_TRUE(voe_.GetPlayout(channel_num));
+
+  // Test that failure to open newly selected devices does not prevent opening
+  // ones after that.
+  voe_.set_fail_start_recording_microphone(true);
+  voe_.set_playout_fail_channel(channel_num);
+  voe_.set_send_fail_channel(channel_num);
+
+  EXPECT_FALSE(engine_.SetDevices(&default_dev, &default_dev));
+
+  EXPECT_FALSE(voe_.GetRecordingMicrophone());
+  EXPECT_FALSE(voe_.GetSend(channel_num));
+  EXPECT_FALSE(voe_.GetPlayout(channel_num));
+
+  voe_.set_fail_start_recording_microphone(false);
+  voe_.set_playout_fail_channel(-1);
+  voe_.set_send_fail_channel(-1);
+
+  EXPECT_TRUE(engine_.SetDevices(&dev, &dev));
+
+  EXPECT_TRUE(voe_.GetRecordingMicrophone());
+  EXPECT_TRUE(voe_.GetSend(channel_num));
+  EXPECT_TRUE(voe_.GetPlayout(channel_num));
+}
+
+// Test that we can set the devices to use even if we failed to
+// open the initial ones.
+TEST_F(WebRtcVoiceEngineTestFake, SetDevicesWithInitiallyBadDevices) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kPcmuCodec);
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+
+  cricket::Device default_dev(cricket::kFakeDefaultDeviceName,
+                              cricket::kFakeDefaultDeviceId);
+  cricket::Device dev(cricket::kFakeDeviceName,
+                      cricket::kFakeDeviceId);
+
+  // Test that failure to open devices selected before starting
+  // send/play does not prevent opening newly selected ones after that.
+  voe_.set_fail_start_recording_microphone(true);
+  voe_.set_playout_fail_channel(channel_num);
+  voe_.set_send_fail_channel(channel_num);
+
+  EXPECT_TRUE(engine_.SetDevices(&default_dev, &default_dev));
+
+  EXPECT_FALSE(engine_.SetLocalMonitor(true));
+  EXPECT_FALSE(channel_->SetSend(cricket::SEND_MICROPHONE));
+  EXPECT_FALSE(channel_->SetPlayout(true));
+  EXPECT_FALSE(voe_.GetRecordingMicrophone());
+  EXPECT_FALSE(voe_.GetSend(channel_num));
+  EXPECT_FALSE(voe_.GetPlayout(channel_num));
+
+  voe_.set_fail_start_recording_microphone(false);
+  voe_.set_playout_fail_channel(-1);
+  voe_.set_send_fail_channel(-1);
+
+  EXPECT_TRUE(engine_.SetDevices(&dev, &dev));
+
+  EXPECT_TRUE(voe_.GetRecordingMicrophone());
+  EXPECT_TRUE(voe_.GetSend(channel_num));
+  EXPECT_TRUE(voe_.GetPlayout(channel_num));
+}
+
+// Test that we can create a channel configured for multi-point conferences,
+// and start sending/playing out on it.
+TEST_F(WebRtcVoiceEngineTestFake, ConferenceSendAndPlayout) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  EXPECT_TRUE(channel_->SetOptions(options_conference_));
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kPcmuCodec);
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_TRUE(channel_->SetSend(cricket::SEND_MICROPHONE));
+  EXPECT_TRUE(voe_.GetSend(channel_num));
+}
+
+// Test that we can create a channel configured for Codian bridges,
+// and start sending/playing out on it.
+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(options_adjust_agc_));
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kPcmuCodec);
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+  EXPECT_TRUE(channel_->SetSend(cricket::SEND_MICROPHONE));
+  EXPECT_TRUE(voe_.GetSend(channel_num));
+  EXPECT_EQ(0, voe_.GetAgcConfig(agc_config));
+  EXPECT_EQ(agc_config.targetLeveldBOv, 10);  // level was attenuated
+  EXPECT_TRUE(channel_->SetPlayout(true));
+  EXPECT_TRUE(voe_.GetPlayout(channel_num));
+  EXPECT_TRUE(channel_->SetSend(cricket::SEND_NOTHING));
+  EXPECT_FALSE(voe_.GetSend(channel_num));
+  EXPECT_EQ(0, voe_.GetAgcConfig(agc_config));
+  EXPECT_EQ(0, agc_config.targetLeveldBOv);  // level was restored
+  EXPECT_TRUE(channel_->SetPlayout(false));
+  EXPECT_FALSE(voe_.GetPlayout(channel_num));
+}
+
+// Test that we can set the outgoing SSRC properly.
+// 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);
+  EXPECT_EQ(0, voe_.GetLocalSSRC(channel_num, send_ssrc));
+  EXPECT_EQ(kSsrc1, send_ssrc);
+}
+
+TEST_F(WebRtcVoiceEngineTestFake, GetStats) {
+  // Setup. We need send codec to be set to get all stats.
+  EXPECT_TRUE(SetupEngine());
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kPcmuCodec);
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+
+  cricket::VoiceMediaInfo info;
+  EXPECT_EQ(true, channel_->GetStats(&info));
+  EXPECT_EQ(1u, info.senders.size());
+  EXPECT_EQ(kSsrc1, info.senders[0].ssrc);
+  EXPECT_EQ(kPcmuCodec.name, info.senders[0].codec_name);
+  EXPECT_EQ(cricket::kIntStatValue, info.senders[0].bytes_sent);
+  EXPECT_EQ(cricket::kIntStatValue, info.senders[0].packets_sent);
+  EXPECT_EQ(cricket::kIntStatValue, info.senders[0].packets_lost);
+  EXPECT_EQ(cricket::kFractionLostStatValue, info.senders[0].fraction_lost);
+  EXPECT_EQ(cricket::kIntStatValue, info.senders[0].ext_seqnum);
+  EXPECT_EQ(cricket::kIntStatValue, info.senders[0].rtt_ms);
+  EXPECT_EQ(cricket::kIntStatValue, info.senders[0].jitter_ms);
+  // TODO(sriniv): Add testing for more fields. These are not populated
+  // in FakeWebrtcVoiceEngine yet.
+  // EXPECT_EQ(cricket::kIntStatValue, info.senders[0].audio_level);
+  // EXPECT_EQ(cricket::kIntStatValue, info.senders[0].echo_delay_median_ms);
+  // EXPECT_EQ(cricket::kIntStatValue, info.senders[0].echo_delay_std_ms);
+  // EXPECT_EQ(cricket::kIntStatValue, info.senders[0].echo_return_loss);
+  // EXPECT_EQ(cricket::kIntStatValue,
+  //           info.senders[0].echo_return_loss_enhancement);
+
+  EXPECT_EQ(1u, info.receivers.size());
+  // TODO(sriniv): Add testing for receiver fields.
+}
+
+// Test that we can set the outgoing SSRC properly with multiple streams.
+// SSRC is set in SetupEngine by calling AddSendStream.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendSsrcWithMultipleStreams) {
+  EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(options_conference_));
+  int channel_num1 = voe_.GetLastChannel();
+  unsigned int send_ssrc;
+  EXPECT_EQ(0, voe_.GetLocalSSRC(channel_num1, send_ssrc));
+  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(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(talk_base::Thread::Current()));
+  channel_ = engine_.CreateChannel();
+  EXPECT_TRUE(channel_->SetOptions(options_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(WebRtcVoiceEngineTestFake, Recv) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  DeliverPacket(kPcmuFrame, sizeof(kPcmuFrame));
+  EXPECT_TRUE(voe_.CheckPacket(channel_num, kPcmuFrame,
+                               sizeof(kPcmuFrame)));
+}
+
+// Test that we can properly receive packets on multiple streams.
+TEST_F(WebRtcVoiceEngineTestFake, RecvWithMultipleStreams) {
+  EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(options_conference_));
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+  int channel_num1 = voe_.GetLastChannel();
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
+  int channel_num2 = voe_.GetLastChannel();
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(3)));
+  int channel_num3 = voe_.GetLastChannel();
+  // Create packets with the right SSRCs.
+  char packets[4][sizeof(kPcmuFrame)];
+  for (size_t i = 0; i < ARRAY_SIZE(packets); ++i) {
+    memcpy(packets[i], kPcmuFrame, sizeof(kPcmuFrame));
+    talk_base::SetBE32(packets[i] + 8, i);
+  }
+  EXPECT_TRUE(voe_.CheckNoPacket(channel_num1));
+  EXPECT_TRUE(voe_.CheckNoPacket(channel_num2));
+  EXPECT_TRUE(voe_.CheckNoPacket(channel_num3));
+  DeliverPacket(packets[0], sizeof(packets[0]));
+  EXPECT_TRUE(voe_.CheckNoPacket(channel_num1));
+  EXPECT_TRUE(voe_.CheckNoPacket(channel_num2));
+  EXPECT_TRUE(voe_.CheckNoPacket(channel_num3));
+  DeliverPacket(packets[1], sizeof(packets[1]));
+  EXPECT_TRUE(voe_.CheckPacket(channel_num1, 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])));
+  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_->RemoveRecvStream(3));
+  EXPECT_TRUE(channel_->RemoveRecvStream(2));
+  EXPECT_TRUE(channel_->RemoveRecvStream(1));
+}
+
+// Test that we properly handle failures to add a stream.
+TEST_F(WebRtcVoiceEngineTestFake, AddStreamFail) {
+  EXPECT_TRUE(SetupEngine());
+  voe_.set_fail_create_channel(true);
+  EXPECT_TRUE(channel_->SetOptions(options_conference_));
+  EXPECT_FALSE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
+
+  // In 1:1 call, we should not try to create a new channel.
+  cricket::AudioOptions options_no_conference_;
+  options_no_conference_.conference_mode.Set(false);
+  EXPECT_TRUE(channel_->SetOptions(options_no_conference_));
+  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 after adding a recv stream, we do not decode more codecs than
+// those previously passed into SetRecvCodecs.
+TEST_F(WebRtcVoiceEngineTestFake, AddRecvStreamUnsupportedCodec) {
+  EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(options_conference_));
+  std::vector<cricket::AudioCodec> codecs;
+  codecs.push_back(kIsacCodec);
+  codecs.push_back(kPcmuCodec);
+  EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
+  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), "CELT");
+  gcodec.plfreq = 32000;
+  gcodec.channels = 2;
+  EXPECT_EQ(-1, voe_.GetRecPayloadType(channel_num2, gcodec));
+}
+
+// Test that we properly clean up any streams that were added, even if
+// not explicitly removed.
+TEST_F(WebRtcVoiceEngineTestFake, StreamCleanup) {
+  EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(options_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;
+  EXPECT_EQ(0, voe_.GetNumChannels());
+}
+
+// Test the InsertDtmf on default send stream.
+TEST_F(WebRtcVoiceEngineTestFake, InsertDtmfOnDefaultSendStream) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  TestInsertDtmf(0, channel_num);
+}
+
+// Test the InsertDtmf on specified send stream.
+TEST_F(WebRtcVoiceEngineTestFake, InsertDtmfOnSendStream) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  TestInsertDtmf(kSsrc1, channel_num);
+}
+
+// Test that we can play a ringback tone properly in a single-stream call.
+TEST_F(WebRtcVoiceEngineTestFake, PlayRingback) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  EXPECT_EQ(0, voe_.IsPlayingFileLocally(channel_num));
+  // Check we fail if no ringback tone specified.
+  EXPECT_FALSE(channel_->PlayRingbackTone(0, true, true));
+  EXPECT_EQ(0, voe_.IsPlayingFileLocally(channel_num));
+  // Check we can set and play a ringback tone.
+  EXPECT_TRUE(channel_->SetRingbackTone(kRingbackTone, strlen(kRingbackTone)));
+  EXPECT_TRUE(channel_->PlayRingbackTone(0, true, true));
+  EXPECT_EQ(1, voe_.IsPlayingFileLocally(channel_num));
+  // Check we can stop the tone manually.
+  EXPECT_TRUE(channel_->PlayRingbackTone(0, false, false));
+  EXPECT_EQ(0, voe_.IsPlayingFileLocally(channel_num));
+  // Check we stop the tone if a packet arrives.
+  EXPECT_TRUE(channel_->PlayRingbackTone(0, true, true));
+  EXPECT_EQ(1, voe_.IsPlayingFileLocally(channel_num));
+  DeliverPacket(kPcmuFrame, sizeof(kPcmuFrame));
+  EXPECT_EQ(0, voe_.IsPlayingFileLocally(channel_num));
+}
+
+// Test that we can play a ringback tone properly in a multi-stream call.
+TEST_F(WebRtcVoiceEngineTestFake, PlayRingbackWithMultipleStreams) {
+  EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(options_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.
+  EXPECT_FALSE(channel_->PlayRingbackTone(2, true, true));
+  EXPECT_EQ(0, voe_.IsPlayingFileLocally(channel_num));
+  // Check we can set and play a ringback tone on the correct ssrc.
+  EXPECT_TRUE(channel_->SetRingbackTone(kRingbackTone, strlen(kRingbackTone)));
+  EXPECT_FALSE(channel_->PlayRingbackTone(77, true, true));
+  EXPECT_TRUE(channel_->PlayRingbackTone(2, true, true));
+  EXPECT_EQ(1, voe_.IsPlayingFileLocally(channel_num));
+  // Check we can stop the tone manually.
+  EXPECT_TRUE(channel_->PlayRingbackTone(2, false, false));
+  EXPECT_EQ(0, voe_.IsPlayingFileLocally(channel_num));
+  // Check we stop the tone if a packet arrives, but only with the right SSRC.
+  EXPECT_TRUE(channel_->PlayRingbackTone(2, true, true));
+  EXPECT_EQ(1, voe_.IsPlayingFileLocally(channel_num));
+  // Send a packet with SSRC 1; the tone should not stop.
+  DeliverPacket(kPcmuFrame, sizeof(kPcmuFrame));
+  EXPECT_EQ(1, voe_.IsPlayingFileLocally(channel_num));
+  // Send a packet with SSRC 2; the tone should stop.
+  char packet[sizeof(kPcmuFrame)];
+  memcpy(packet, kPcmuFrame, sizeof(kPcmuFrame));
+  talk_base::SetBE32(packet + 8, 2);
+  DeliverPacket(packet, sizeof(packet));
+  EXPECT_EQ(0, voe_.IsPlayingFileLocally(channel_num));
+}
+
+// Tests creating soundclips, and make sure they come from the right engine.
+TEST_F(WebRtcVoiceEngineTestFake, CreateSoundclip) {
+  EXPECT_TRUE(engine_.Init(talk_base::Thread::Current()));
+  soundclip_ = engine_.CreateSoundclip();
+  ASSERT_TRUE(soundclip_ != NULL);
+  EXPECT_EQ(0, voe_.GetNumChannels());
+  EXPECT_EQ(1, voe_sc_.GetNumChannels());
+  int channel_num = voe_sc_.GetLastChannel();
+  EXPECT_TRUE(voe_sc_.GetPlayout(channel_num));
+  delete soundclip_;
+  soundclip_ = NULL;
+  EXPECT_EQ(0, voe_sc_.GetNumChannels());
+}
+
+// Tests playing out a fake sound.
+TEST_F(WebRtcVoiceEngineTestFake, PlaySoundclip) {
+  static const char kZeroes[16000] = {};
+  EXPECT_TRUE(engine_.Init(talk_base::Thread::Current()));
+  soundclip_ = engine_.CreateSoundclip();
+  ASSERT_TRUE(soundclip_ != NULL);
+  EXPECT_TRUE(soundclip_->PlaySound(kZeroes, sizeof(kZeroes), 0));
+}
+
+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(options_conference_));
+  EXPECT_TRUE(channel_->SetSend(cricket::SEND_MICROPHONE));
+
+  media_channel = static_cast<cricket::WebRtcVoiceMediaChannel*>(channel_);
+  listener.reset(new ChannelErrorListener(channel_));
+
+  // Test on WebRtc VoE channel.
+  voe_.TriggerCallbackOnError(media_channel->voe_channel(),
+                              VE_SATURATION_WARNING);
+  EXPECT_EQ(cricket::VoiceMediaChannel::ERROR_REC_DEVICE_SATURATION,
+            listener->error());
+  EXPECT_NE(-1, voe_.GetLocalSSRC(voe_.GetLastChannel(), ssrc));
+  EXPECT_EQ(ssrc, listener->ssrc());
+
+  listener->Reset();
+  voe_.TriggerCallbackOnError(-1, VE_TYPING_NOISE_WARNING);
+  EXPECT_EQ(cricket::VoiceMediaChannel::ERROR_REC_TYPING_NOISE_DETECTED,
+            listener->error());
+  EXPECT_EQ(0U, listener->ssrc());
+
+  // Add another stream and test on that.
+  ++ssrc;
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(
+      ssrc)));
+  listener->Reset();
+  voe_.TriggerCallbackOnError(voe_.GetLastChannel(),
+                              VE_SATURATION_WARNING);
+  EXPECT_EQ(cricket::VoiceMediaChannel::ERROR_REC_DEVICE_SATURATION,
+            listener->error());
+  EXPECT_EQ(ssrc, listener->ssrc());
+
+  // Testing a non-existing channel.
+  listener->Reset();
+  voe_.TriggerCallbackOnError(voe_.GetLastChannel() + 2,
+                              VE_SATURATION_WARNING);
+  EXPECT_EQ(0, listener->error());
+}
+
+TEST_F(WebRtcVoiceEngineTestFake, TestSetPlayoutError) {
+  EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(options_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_->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(options_conference_));
+  EXPECT_TRUE(channel_->AddRecvStream(
+      cricket::StreamParams::CreateLegacy(kSsrc2)));
+  cricket::FakeMediaProcessor vp_1;
+  cricket::FakeMediaProcessor vp_2;
+
+  EXPECT_FALSE(engine_.RegisterProcessor(kSsrc2, &vp_1, cricket::MPD_TX));
+  EXPECT_TRUE(engine_.RegisterProcessor(kSsrc2, &vp_1, cricket::MPD_RX));
+  EXPECT_TRUE(engine_.RegisterProcessor(kSsrc2, &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(kSsrc2,
+                                          &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(kSsrc2,
+                                          &vp_1,
+                                          cricket::MPD_RX));
+  voe_.TriggerProcessPacket(cricket::MPD_RX);
+  EXPECT_FALSE(voe_.IsExternalMediaProcessorRegistered());
+  EXPECT_EQ(2, vp_1.voice_frame_count());
+
+  EXPECT_FALSE(engine_.RegisterProcessor(kSsrc1, &vp_1, cricket::MPD_RX));
+  EXPECT_TRUE(engine_.RegisterProcessor(kSsrc1, &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(kSsrc1,
+                                          &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(kSsrc2));
+  EXPECT_FALSE(engine_.RegisterProcessor(kSsrc2, &vp_1, cricket::MPD_RX));
+  EXPECT_FALSE(voe_.IsExternalMediaProcessorRegistered());
+
+  // Test that we can register a processor on the receive channel on SSRC 0.
+  // This tests the 1:1 case when the receive SSRC is unknown.
+  EXPECT_TRUE(engine_.RegisterProcessor(0, &vp_1, cricket::MPD_RX));
+  voe_.TriggerProcessPacket(cricket::MPD_RX);
+  EXPECT_TRUE(voe_.IsExternalMediaProcessorRegistered());
+  EXPECT_EQ(4, vp_1.voice_frame_count());
+  EXPECT_TRUE(engine_.UnregisterProcessor(0,
+                                          &vp_1,
+                                          cricket::MPD_RX));
+
+  // 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(99,
+                                         &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));
+}
+
+TEST_F(WebRtcVoiceEngineTestFake, SetAudioOptions) {
+  EXPECT_TRUE(SetupEngine());
+
+  bool ec_enabled;
+  webrtc::EcModes ec_mode;
+  bool ec_metrics_enabled;
+  webrtc::AecmModes aecm_mode;
+  bool cng_enabled;
+  bool agc_enabled;
+  webrtc::AgcModes agc_mode;
+  webrtc::AgcConfig agc_config;
+  bool ns_enabled;
+  webrtc::NsModes ns_mode;
+  bool highpass_filter_enabled;
+  bool stereo_swapping_enabled;
+  bool typing_detection_enabled;
+  voe_.GetEcStatus(ec_enabled, ec_mode);
+  voe_.GetEcMetricsStatus(ec_metrics_enabled);
+  voe_.GetAecmMode(aecm_mode, cng_enabled);
+  voe_.GetAgcStatus(agc_enabled, agc_mode);
+  voe_.GetAgcConfig(agc_config);
+  voe_.GetNsStatus(ns_enabled, ns_mode);
+  highpass_filter_enabled = voe_.IsHighPassFilterEnabled();
+  stereo_swapping_enabled = voe_.IsStereoChannelSwappingEnabled();
+  voe_.GetTypingDetectionStatus(typing_detection_enabled);
+  EXPECT_TRUE(ec_enabled);
+  EXPECT_TRUE(ec_metrics_enabled);
+  EXPECT_FALSE(cng_enabled);
+  EXPECT_TRUE(agc_enabled);
+  EXPECT_EQ(0, agc_config.targetLeveldBOv);
+  EXPECT_TRUE(ns_enabled);
+  EXPECT_TRUE(highpass_filter_enabled);
+  EXPECT_FALSE(stereo_swapping_enabled);
+  EXPECT_TRUE(typing_detection_enabled);
+  EXPECT_EQ(ec_mode, webrtc::kEcConference);
+  EXPECT_EQ(ns_mode, webrtc::kNsHighSuppression);
+
+  // Nothing set, so all ignored.
+  cricket::AudioOptions options;
+  ASSERT_TRUE(engine_.SetAudioOptions(options));
+  voe_.GetEcStatus(ec_enabled, ec_mode);
+  voe_.GetEcMetricsStatus(ec_metrics_enabled);
+  voe_.GetAecmMode(aecm_mode, cng_enabled);
+  voe_.GetAgcStatus(agc_enabled, agc_mode);
+  voe_.GetAgcConfig(agc_config);
+  voe_.GetNsStatus(ns_enabled, ns_mode);
+  highpass_filter_enabled = voe_.IsHighPassFilterEnabled();
+  stereo_swapping_enabled = voe_.IsStereoChannelSwappingEnabled();
+  voe_.GetTypingDetectionStatus(typing_detection_enabled);
+  EXPECT_TRUE(ec_enabled);
+  EXPECT_TRUE(ec_metrics_enabled);
+  EXPECT_FALSE(cng_enabled);
+  EXPECT_TRUE(agc_enabled);
+  EXPECT_EQ(0, agc_config.targetLeveldBOv);
+  EXPECT_TRUE(ns_enabled);
+  EXPECT_TRUE(highpass_filter_enabled);
+  EXPECT_FALSE(stereo_swapping_enabled);
+  EXPECT_TRUE(typing_detection_enabled);
+  EXPECT_EQ(ec_mode, webrtc::kEcConference);
+  EXPECT_EQ(ns_mode, webrtc::kNsHighSuppression);
+
+  // Turn echo cancellation off
+  options.echo_cancellation.Set(false);
+  ASSERT_TRUE(engine_.SetAudioOptions(options));
+  voe_.GetEcStatus(ec_enabled, ec_mode);
+  EXPECT_FALSE(ec_enabled);
+
+  // Turn echo cancellation back on, with settings, and make sure
+  // nothing else changed.
+  options.echo_cancellation.Set(true);
+  ASSERT_TRUE(engine_.SetAudioOptions(options));
+  voe_.GetEcStatus(ec_enabled, ec_mode);
+  voe_.GetEcMetricsStatus(ec_metrics_enabled);
+  voe_.GetAecmMode(aecm_mode, cng_enabled);
+  voe_.GetAgcStatus(agc_enabled, agc_mode);
+  voe_.GetAgcConfig(agc_config);
+  voe_.GetNsStatus(ns_enabled, ns_mode);
+  highpass_filter_enabled = voe_.IsHighPassFilterEnabled();
+  stereo_swapping_enabled = voe_.IsStereoChannelSwappingEnabled();
+  voe_.GetTypingDetectionStatus(typing_detection_enabled);
+  EXPECT_TRUE(ec_enabled);
+  EXPECT_TRUE(ec_metrics_enabled);
+  EXPECT_TRUE(agc_enabled);
+  EXPECT_EQ(0, agc_config.targetLeveldBOv);
+  EXPECT_TRUE(ns_enabled);
+  EXPECT_TRUE(highpass_filter_enabled);
+  EXPECT_FALSE(stereo_swapping_enabled);
+  EXPECT_TRUE(typing_detection_enabled);
+  EXPECT_EQ(ec_mode, webrtc::kEcConference);
+  EXPECT_EQ(ns_mode, webrtc::kNsHighSuppression);
+
+  // Turn off AGC
+  options.auto_gain_control.Set(false);
+  ASSERT_TRUE(engine_.SetAudioOptions(options));
+  voe_.GetAgcStatus(agc_enabled, agc_mode);
+  EXPECT_FALSE(agc_enabled);
+
+  // Turn AGC back on
+  options.auto_gain_control.Set(true);
+  options.adjust_agc_delta.Clear();
+  ASSERT_TRUE(engine_.SetAudioOptions(options));
+  voe_.GetAgcStatus(agc_enabled, agc_mode);
+  EXPECT_TRUE(agc_enabled);
+  voe_.GetAgcConfig(agc_config);
+  EXPECT_EQ(0, agc_config.targetLeveldBOv);
+
+  // Turn off other options (and stereo swapping on).
+  options.noise_suppression.Set(false);
+  options.highpass_filter.Set(false);
+  options.typing_detection.Set(false);
+  options.stereo_swapping.Set(true);
+  ASSERT_TRUE(engine_.SetAudioOptions(options));
+  voe_.GetNsStatus(ns_enabled, ns_mode);
+  highpass_filter_enabled = voe_.IsHighPassFilterEnabled();
+  stereo_swapping_enabled = voe_.IsStereoChannelSwappingEnabled();
+  voe_.GetTypingDetectionStatus(typing_detection_enabled);
+  EXPECT_FALSE(ns_enabled);
+  EXPECT_FALSE(highpass_filter_enabled);
+  EXPECT_FALSE(typing_detection_enabled);
+  EXPECT_TRUE(stereo_swapping_enabled);
+
+  // Turn on "conference mode" to ensure it has no impact.
+  options.conference_mode.Set(true);
+  ASSERT_TRUE(engine_.SetAudioOptions(options));
+  voe_.GetEcStatus(ec_enabled, ec_mode);
+  voe_.GetNsStatus(ns_enabled, ns_mode);
+  EXPECT_TRUE(ec_enabled);
+  EXPECT_EQ(webrtc::kEcConference, ec_mode);
+  EXPECT_FALSE(ns_enabled);
+  EXPECT_EQ(webrtc::kNsHighSuppression, ns_mode);
+}
+
+TEST_F(WebRtcVoiceEngineTestFake, SetOptions) {
+  EXPECT_TRUE(SetupEngine());
+
+  bool ec_enabled;
+  webrtc::EcModes ec_mode;
+  bool ec_metrics_enabled;
+  bool agc_enabled;
+  webrtc::AgcModes agc_mode;
+  bool ns_enabled;
+  webrtc::NsModes ns_mode;
+  bool highpass_filter_enabled;
+  bool stereo_swapping_enabled;
+  bool typing_detection_enabled;
+
+  ASSERT_TRUE(engine_.SetOptions(0));
+  voe_.GetEcStatus(ec_enabled, ec_mode);
+  voe_.GetEcMetricsStatus(ec_metrics_enabled);
+  voe_.GetAgcStatus(agc_enabled, agc_mode);
+  voe_.GetNsStatus(ns_enabled, ns_mode);
+  highpass_filter_enabled = voe_.IsHighPassFilterEnabled();
+  stereo_swapping_enabled = voe_.IsStereoChannelSwappingEnabled();
+  voe_.GetTypingDetectionStatus(typing_detection_enabled);
+  EXPECT_FALSE(ec_enabled);
+  EXPECT_FALSE(agc_enabled);
+  EXPECT_FALSE(ns_enabled);
+  EXPECT_FALSE(highpass_filter_enabled);
+  EXPECT_FALSE(stereo_swapping_enabled);
+  EXPECT_TRUE(typing_detection_enabled);
+
+  ASSERT_TRUE(engine_.SetOptions(
+      cricket::MediaEngineInterface::ECHO_CANCELLATION));
+  voe_.GetEcStatus(ec_enabled, ec_mode);
+  voe_.GetEcMetricsStatus(ec_metrics_enabled);
+  voe_.GetAgcStatus(agc_enabled, agc_mode);
+  voe_.GetNsStatus(ns_enabled, ns_mode);
+  highpass_filter_enabled = voe_.IsHighPassFilterEnabled();
+  stereo_swapping_enabled = voe_.IsStereoChannelSwappingEnabled();
+  voe_.GetTypingDetectionStatus(typing_detection_enabled);
+  EXPECT_TRUE(ec_enabled);
+  EXPECT_FALSE(agc_enabled);
+  EXPECT_FALSE(ns_enabled);
+  EXPECT_FALSE(highpass_filter_enabled);
+  EXPECT_FALSE(stereo_swapping_enabled);
+  EXPECT_TRUE(typing_detection_enabled);
+
+  ASSERT_TRUE(engine_.SetOptions(
+      cricket::MediaEngineInterface::AUTO_GAIN_CONTROL));
+  voe_.GetEcStatus(ec_enabled, ec_mode);
+  voe_.GetEcMetricsStatus(ec_metrics_enabled);
+  voe_.GetAgcStatus(agc_enabled, agc_mode);
+  voe_.GetNsStatus(ns_enabled, ns_mode);
+  highpass_filter_enabled = voe_.IsHighPassFilterEnabled();
+  stereo_swapping_enabled = voe_.IsStereoChannelSwappingEnabled();
+  voe_.GetTypingDetectionStatus(typing_detection_enabled);
+  EXPECT_FALSE(ec_enabled);
+  EXPECT_TRUE(agc_enabled);
+  EXPECT_FALSE(ns_enabled);
+  EXPECT_FALSE(highpass_filter_enabled);
+  EXPECT_FALSE(stereo_swapping_enabled);
+  EXPECT_TRUE(typing_detection_enabled);
+
+  ASSERT_TRUE(engine_.SetOptions(
+      cricket::MediaEngineInterface::NOISE_SUPPRESSION));
+  voe_.GetEcStatus(ec_enabled, ec_mode);
+  voe_.GetEcMetricsStatus(ec_metrics_enabled);
+  voe_.GetAgcStatus(agc_enabled, agc_mode);
+  voe_.GetNsStatus(ns_enabled, ns_mode);
+  highpass_filter_enabled = voe_.IsHighPassFilterEnabled();
+  stereo_swapping_enabled = voe_.IsStereoChannelSwappingEnabled();
+  voe_.GetTypingDetectionStatus(typing_detection_enabled);
+  EXPECT_FALSE(ec_enabled);
+  EXPECT_FALSE(agc_enabled);
+  EXPECT_TRUE(ns_enabled);
+  EXPECT_FALSE(highpass_filter_enabled);
+  EXPECT_FALSE(stereo_swapping_enabled);
+  EXPECT_TRUE(typing_detection_enabled);
+
+  ASSERT_TRUE(engine_.SetOptions(
+      cricket::MediaEngineInterface::HIGHPASS_FILTER));
+  voe_.GetEcStatus(ec_enabled, ec_mode);
+  voe_.GetEcMetricsStatus(ec_metrics_enabled);
+  voe_.GetAgcStatus(agc_enabled, agc_mode);
+  voe_.GetNsStatus(ns_enabled, ns_mode);
+  highpass_filter_enabled = voe_.IsHighPassFilterEnabled();
+  stereo_swapping_enabled = voe_.IsStereoChannelSwappingEnabled();
+  voe_.GetTypingDetectionStatus(typing_detection_enabled);
+  EXPECT_FALSE(ec_enabled);
+  EXPECT_FALSE(agc_enabled);
+  EXPECT_FALSE(ns_enabled);
+  EXPECT_TRUE(highpass_filter_enabled);
+  EXPECT_FALSE(stereo_swapping_enabled);
+  EXPECT_TRUE(typing_detection_enabled);
+
+  ASSERT_TRUE(engine_.SetOptions(
+      cricket::MediaEngineInterface::STEREO_FLIPPING));
+  voe_.GetEcStatus(ec_enabled, ec_mode);
+  voe_.GetEcMetricsStatus(ec_metrics_enabled);
+  voe_.GetAgcStatus(agc_enabled, agc_mode);
+  voe_.GetNsStatus(ns_enabled, ns_mode);
+  highpass_filter_enabled = voe_.IsHighPassFilterEnabled();
+  stereo_swapping_enabled = voe_.IsStereoChannelSwappingEnabled();
+  voe_.GetTypingDetectionStatus(typing_detection_enabled);
+  EXPECT_FALSE(ec_enabled);
+  EXPECT_FALSE(agc_enabled);
+  EXPECT_FALSE(ns_enabled);
+  EXPECT_FALSE(highpass_filter_enabled);
+  EXPECT_TRUE(stereo_swapping_enabled);
+  EXPECT_TRUE(typing_detection_enabled);
+
+  ASSERT_TRUE(engine_.SetOptions(
+      cricket::MediaEngineInterface::DEFAULT_AUDIO_OPTIONS));
+  voe_.GetEcStatus(ec_enabled, ec_mode);
+  voe_.GetEcMetricsStatus(ec_metrics_enabled);
+  voe_.GetAgcStatus(agc_enabled, agc_mode);
+  voe_.GetNsStatus(ns_enabled, ns_mode);
+  highpass_filter_enabled = voe_.IsHighPassFilterEnabled();
+  stereo_swapping_enabled = voe_.IsStereoChannelSwappingEnabled();
+  voe_.GetTypingDetectionStatus(typing_detection_enabled);
+  EXPECT_TRUE(ec_enabled);
+  EXPECT_TRUE(agc_enabled);
+  EXPECT_TRUE(ns_enabled);
+  EXPECT_TRUE(highpass_filter_enabled);
+  EXPECT_FALSE(stereo_swapping_enabled);
+  EXPECT_TRUE(typing_detection_enabled);
+
+  ASSERT_TRUE(engine_.SetOptions(
+      cricket::MediaEngineInterface::ALL_AUDIO_OPTIONS));
+  voe_.GetEcStatus(ec_enabled, ec_mode);
+  voe_.GetEcMetricsStatus(ec_metrics_enabled);
+  voe_.GetAgcStatus(agc_enabled, agc_mode);
+  voe_.GetNsStatus(ns_enabled, ns_mode);
+  highpass_filter_enabled = voe_.IsHighPassFilterEnabled();
+  stereo_swapping_enabled = voe_.IsStereoChannelSwappingEnabled();
+  voe_.GetTypingDetectionStatus(typing_detection_enabled);
+  EXPECT_TRUE(ec_enabled);
+  EXPECT_TRUE(agc_enabled);
+  EXPECT_TRUE(ns_enabled);
+  EXPECT_TRUE(highpass_filter_enabled);
+  EXPECT_TRUE(stereo_swapping_enabled);
+  EXPECT_TRUE(typing_detection_enabled);
+}
+
+TEST_F(WebRtcVoiceEngineTestFake, InitDoesNotOverwriteDefaultAgcConfig) {
+  webrtc::AgcConfig set_config = {0};
+  set_config.targetLeveldBOv = 3;
+  set_config.digitalCompressionGaindB = 9;
+  set_config.limiterEnable = true;
+  EXPECT_EQ(0, voe_.SetAgcConfig(set_config));
+  EXPECT_TRUE(engine_.Init(talk_base::Thread::Current()));
+
+  webrtc::AgcConfig config = {0};
+  EXPECT_EQ(0, voe_.GetAgcConfig(config));
+  EXPECT_EQ(set_config.targetLeveldBOv, config.targetLeveldBOv);
+  EXPECT_EQ(set_config.digitalCompressionGaindB,
+            config.digitalCompressionGaindB);
+  EXPECT_EQ(set_config.limiterEnable, config.limiterEnable);
+}
+
+
+TEST_F(WebRtcVoiceEngineTestFake, SetOptionOverridesViaChannels) {
+  EXPECT_TRUE(SetupEngine());
+  talk_base::scoped_ptr<cricket::VoiceMediaChannel> channel1(
+      engine_.CreateChannel());
+  talk_base::scoped_ptr<cricket::VoiceMediaChannel> channel2(
+      engine_.CreateChannel());
+
+  // Have to add a stream to make SetSend work.
+  cricket::StreamParams stream1;
+  stream1.ssrcs.push_back(1);
+  channel1->AddSendStream(stream1);
+  cricket::StreamParams stream2;
+  stream2.ssrcs.push_back(2);
+  channel2->AddSendStream(stream2);
+
+  // AEC and AGC and NS
+  cricket::AudioOptions options_all;
+  options_all.echo_cancellation.Set(true);
+  options_all.auto_gain_control.Set(true);
+  options_all.noise_suppression.Set(true);
+
+  ASSERT_TRUE(channel1->SetOptions(options_all));
+  cricket::AudioOptions expected_options = options_all;
+  cricket::AudioOptions actual_options;
+  ASSERT_TRUE(channel1->GetOptions(&actual_options));
+  EXPECT_EQ(expected_options, actual_options);
+  ASSERT_TRUE(channel2->SetOptions(options_all));
+  ASSERT_TRUE(channel2->GetOptions(&actual_options));
+  EXPECT_EQ(expected_options, actual_options);
+
+  // unset NS
+  cricket::AudioOptions options_no_ns;
+  options_no_ns.noise_suppression.Set(false);
+  ASSERT_TRUE(channel1->SetOptions(options_no_ns));
+
+  expected_options.echo_cancellation.Set(true);
+  expected_options.auto_gain_control.Set(true);
+  expected_options.noise_suppression.Set(false);
+  ASSERT_TRUE(channel1->GetOptions(&actual_options));
+  EXPECT_EQ(expected_options, actual_options);
+
+  // unset AGC
+  cricket::AudioOptions options_no_agc;
+  options_no_agc.auto_gain_control.Set(false);
+  ASSERT_TRUE(channel2->SetOptions(options_no_agc));
+
+  expected_options.echo_cancellation.Set(true);
+  expected_options.auto_gain_control.Set(false);
+  expected_options.noise_suppression.Set(true);
+  ASSERT_TRUE(channel2->GetOptions(&actual_options));
+  EXPECT_EQ(expected_options, actual_options);
+
+  ASSERT_TRUE(engine_.SetAudioOptions(options_all));
+  bool ec_enabled;
+  webrtc::EcModes ec_mode;
+  bool agc_enabled;
+  webrtc::AgcModes agc_mode;
+  bool ns_enabled;
+  webrtc::NsModes ns_mode;
+  voe_.GetEcStatus(ec_enabled, ec_mode);
+  voe_.GetAgcStatus(agc_enabled, agc_mode);
+  voe_.GetNsStatus(ns_enabled, ns_mode);
+  EXPECT_TRUE(ec_enabled);
+  EXPECT_TRUE(agc_enabled);
+  EXPECT_TRUE(ns_enabled);
+
+  channel1->SetSend(cricket::SEND_MICROPHONE);
+  voe_.GetEcStatus(ec_enabled, ec_mode);
+  voe_.GetAgcStatus(agc_enabled, agc_mode);
+  voe_.GetNsStatus(ns_enabled, ns_mode);
+  EXPECT_TRUE(ec_enabled);
+  EXPECT_TRUE(agc_enabled);
+  EXPECT_FALSE(ns_enabled);
+
+  channel1->SetSend(cricket::SEND_NOTHING);
+  voe_.GetEcStatus(ec_enabled, ec_mode);
+  voe_.GetAgcStatus(agc_enabled, agc_mode);
+  voe_.GetNsStatus(ns_enabled, ns_mode);
+  EXPECT_TRUE(ec_enabled);
+  EXPECT_TRUE(agc_enabled);
+  EXPECT_TRUE(ns_enabled);
+
+  channel2->SetSend(cricket::SEND_MICROPHONE);
+  voe_.GetEcStatus(ec_enabled, ec_mode);
+  voe_.GetAgcStatus(agc_enabled, agc_mode);
+  voe_.GetNsStatus(ns_enabled, ns_mode);
+  EXPECT_TRUE(ec_enabled);
+  EXPECT_FALSE(agc_enabled);
+  EXPECT_TRUE(ns_enabled);
+
+  channel2->SetSend(cricket::SEND_NOTHING);
+  voe_.GetEcStatus(ec_enabled, ec_mode);
+  voe_.GetAgcStatus(agc_enabled, agc_mode);
+  voe_.GetNsStatus(ns_enabled, ns_mode);
+  EXPECT_TRUE(ec_enabled);
+  EXPECT_TRUE(agc_enabled);
+  EXPECT_TRUE(ns_enabled);
+
+  // Make sure settings take effect while we are sending.
+  ASSERT_TRUE(engine_.SetAudioOptions(options_all));
+  cricket::AudioOptions options_no_agc_nor_ns;
+  options_no_agc_nor_ns.auto_gain_control.Set(false);
+  options_no_agc_nor_ns.noise_suppression.Set(false);
+  channel2->SetSend(cricket::SEND_MICROPHONE);
+  channel2->SetOptions(options_no_agc_nor_ns);
+
+  expected_options.echo_cancellation.Set(true);
+  expected_options.auto_gain_control.Set(false);
+  expected_options.noise_suppression.Set(false);
+  ASSERT_TRUE(channel2->GetOptions(&actual_options));
+  EXPECT_EQ(expected_options, actual_options);
+  voe_.GetEcStatus(ec_enabled, ec_mode);
+  voe_.GetAgcStatus(agc_enabled, agc_mode);
+  voe_.GetNsStatus(ns_enabled, ns_mode);
+  EXPECT_TRUE(ec_enabled);
+  EXPECT_FALSE(agc_enabled);
+  EXPECT_FALSE(ns_enabled);
+}
+
+// Test that GetReceiveChannelNum returns the default channel for the first
+// recv stream in 1-1 calls.
+TEST_F(WebRtcVoiceEngineTestFake, TestGetReceiveChannelNumIn1To1Calls) {
+  EXPECT_TRUE(SetupEngine());
+  cricket::WebRtcVoiceMediaChannel* media_channel =
+        static_cast<cricket::WebRtcVoiceMediaChannel*>(channel_);
+  // Test that GetChannelNum returns the default channel if the SSRC is unknown.
+  EXPECT_EQ(media_channel->voe_channel(),
+            media_channel->GetReceiveChannelNum(0));
+  cricket::StreamParams stream;
+  stream.ssrcs.push_back(kSsrc2);
+  EXPECT_TRUE(channel_->AddRecvStream(stream));
+  EXPECT_EQ(media_channel->voe_channel(),
+            media_channel->GetReceiveChannelNum(kSsrc2));
+}
+
+// Test that GetReceiveChannelNum doesn't return the default channel for the
+// first recv stream in conference calls.
+TEST_F(WebRtcVoiceEngineTestFake, TestGetChannelNumInConferenceCalls) {
+  EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(options_conference_));
+  cricket::StreamParams stream;
+  stream.ssrcs.push_back(kSsrc2);
+  EXPECT_TRUE(channel_->AddRecvStream(stream));
+  cricket::WebRtcVoiceMediaChannel* media_channel =
+      static_cast<cricket::WebRtcVoiceMediaChannel*>(channel_);
+  EXPECT_LT(media_channel->voe_channel(),
+            media_channel->GetReceiveChannelNum(kSsrc2));
+}
+
+TEST_F(WebRtcVoiceEngineTestFake, SetOutputScaling) {
+  EXPECT_TRUE(SetupEngine());
+  double left, right;
+  EXPECT_TRUE(channel_->SetOutputScaling(0, 1, 2));
+  EXPECT_TRUE(channel_->GetOutputScaling(0, &left, &right));
+  EXPECT_DOUBLE_EQ(1, left);
+  EXPECT_DOUBLE_EQ(2, right);
+
+  EXPECT_FALSE(channel_->SetOutputScaling(kSsrc2, 1, 2));
+  cricket::StreamParams stream;
+  stream.ssrcs.push_back(kSsrc2);
+  EXPECT_TRUE(channel_->AddRecvStream(stream));
+
+  EXPECT_TRUE(channel_->SetOutputScaling(kSsrc2, 2, 1));
+  EXPECT_TRUE(channel_->GetOutputScaling(kSsrc2, &left, &right));
+  EXPECT_DOUBLE_EQ(2, left);
+  EXPECT_DOUBLE_EQ(1, right);
+}
+
+
+// Tests for the actual WebRtc VoE library.
+
+// Tests that the library initializes and shuts down properly.
+TEST(WebRtcVoiceEngineTest, StartupShutdown) {
+  cricket::WebRtcVoiceEngine engine;
+  EXPECT_TRUE(engine.Init(talk_base::Thread::Current()));
+  cricket::VoiceMediaChannel* channel = engine.CreateChannel();
+  EXPECT_TRUE(channel != NULL);
+  delete channel;
+  engine.Terminate();
+
+  // Reinit to catch regression where VoiceEngineObserver reference is lost
+  EXPECT_TRUE(engine.Init(talk_base::Thread::Current()));
+  engine.Terminate();
+}
+
+// Tests that the logging from the library is cleartext.
+TEST(WebRtcVoiceEngineTest, DISABLED_HasUnencryptedLogging) {
+  cricket::WebRtcVoiceEngine engine;
+  talk_base::scoped_ptr<talk_base::MemoryStream> stream(
+      new talk_base::MemoryStream);
+  size_t size = 0;
+  bool cleartext = true;
+  talk_base::LogMessage::AddLogToStream(stream.get(), talk_base::LS_VERBOSE);
+  engine.SetLogging(talk_base::LS_VERBOSE, "");
+  EXPECT_TRUE(engine.Init(talk_base::Thread::Current()));
+  EXPECT_TRUE(stream->GetSize(&size));
+  EXPECT_GT(size, 0U);
+  engine.Terminate();
+  talk_base::LogMessage::RemoveLogToStream(stream.get());
+  const char* buf = stream->GetBuffer();
+  for (size_t i = 0; i < size && cleartext; ++i) {
+    int ch = static_cast<int>(buf[i]);
+    ASSERT_GE(ch, 0) << "Out of bounds character in WebRtc VoE log: "
+                     << std::hex << ch;
+    cleartext = (isprint(ch) || isspace(ch));
+  }
+  EXPECT_TRUE(cleartext);
+}
+
+// Tests we do not see any references to a monitor thread being spun up
+// when initiating the engine.
+TEST(WebRtcVoiceEngineTest, HasNoMonitorThread) {
+  cricket::WebRtcVoiceEngine engine;
+  talk_base::scoped_ptr<talk_base::MemoryStream> stream(
+      new talk_base::MemoryStream);
+  talk_base::LogMessage::AddLogToStream(stream.get(), talk_base::LS_VERBOSE);
+  engine.SetLogging(talk_base::LS_VERBOSE, "");
+  EXPECT_TRUE(engine.Init(talk_base::Thread::Current()));
+  engine.Terminate();
+  talk_base::LogMessage::RemoveLogToStream(stream.get());
+
+  size_t size = 0;
+  EXPECT_TRUE(stream->GetSize(&size));
+  EXPECT_GT(size, 0U);
+  const std::string logs(stream->GetBuffer());
+  EXPECT_NE(std::string::npos, logs.find("ProcessThread"));
+}
+
+// Tests that the library is configured with the codecs we want.
+TEST(WebRtcVoiceEngineTest, HasCorrectCodecs) {
+  cricket::WebRtcVoiceEngine engine;
+  // Check codecs by name.
+  EXPECT_TRUE(engine.FindCodec(
+      cricket::AudioCodec(96, "OPUS", 48000, 0, 2, 0)));
+  EXPECT_TRUE(engine.FindCodec(
+      cricket::AudioCodec(96, "ISAC", 16000, 0, 1, 0)));
+  EXPECT_TRUE(engine.FindCodec(
+      cricket::AudioCodec(96, "ISAC", 32000, 0, 1, 0)));
+  // Check that name matching is case-insensitive.
+  EXPECT_TRUE(engine.FindCodec(
+      cricket::AudioCodec(96, "ILBC", 8000, 0, 1, 0)));
+  EXPECT_TRUE(engine.FindCodec(
+      cricket::AudioCodec(96, "iLBC", 8000, 0, 1, 0)));
+  EXPECT_TRUE(engine.FindCodec(
+      cricket::AudioCodec(96, "PCMU", 8000, 0, 1, 0)));
+  EXPECT_TRUE(engine.FindCodec(
+      cricket::AudioCodec(96, "PCMA", 8000, 0, 1, 0)));
+  EXPECT_TRUE(engine.FindCodec(
+      cricket::AudioCodec(96, "G722", 16000, 0, 1, 0)));
+  EXPECT_TRUE(engine.FindCodec(
+      cricket::AudioCodec(96, "red", 8000, 0, 1, 0)));
+  EXPECT_TRUE(engine.FindCodec(
+      cricket::AudioCodec(96, "CN", 48000, 0, 1, 0)));
+  EXPECT_TRUE(engine.FindCodec(
+      cricket::AudioCodec(96, "CN", 32000, 0, 1, 0)));
+  EXPECT_TRUE(engine.FindCodec(
+      cricket::AudioCodec(96, "CN", 16000, 0, 1, 0)));
+  EXPECT_TRUE(engine.FindCodec(
+      cricket::AudioCodec(96, "CN", 8000, 0, 1, 0)));
+  EXPECT_TRUE(engine.FindCodec(
+      cricket::AudioCodec(96, "telephone-event", 8000, 0, 1, 0)));
+  // Check codecs with an id by id.
+  EXPECT_TRUE(engine.FindCodec(
+      cricket::AudioCodec(0, "", 8000, 0, 1, 0)));   // PCMU
+  EXPECT_TRUE(engine.FindCodec(
+      cricket::AudioCodec(8, "", 8000, 0, 1, 0)));   // PCMA
+  EXPECT_TRUE(engine.FindCodec(
+      cricket::AudioCodec(9, "", 16000, 0, 1, 0)));  // G722
+  EXPECT_TRUE(engine.FindCodec(
+      cricket::AudioCodec(13, "", 8000, 0, 1, 0)));  // CN
+  // Check sample/bitrate matching.
+  EXPECT_TRUE(engine.FindCodec(
+      cricket::AudioCodec(0, "PCMU", 8000, 64000, 1, 0)));
+  // Check that bad codecs fail.
+  EXPECT_FALSE(engine.FindCodec(cricket::AudioCodec(99, "ABCD", 0, 0, 1, 0)));
+  EXPECT_FALSE(engine.FindCodec(cricket::AudioCodec(88, "", 0, 0, 1, 0)));
+  EXPECT_FALSE(engine.FindCodec(cricket::AudioCodec(0, "", 0, 0, 2, 0)));
+  EXPECT_FALSE(engine.FindCodec(cricket::AudioCodec(0, "", 5000, 0, 1, 0)));
+  EXPECT_FALSE(engine.FindCodec(cricket::AudioCodec(0, "", 0, 5000, 1, 0)));
+  // Check that there aren't any extra codecs lying around.
+  EXPECT_EQ(13U, engine.codecs().size());
+  // Verify the payload id of common audio codecs, including CN, ISAC, and G722.
+  for (std::vector<cricket::AudioCodec>::const_iterator it =
+      engine.codecs().begin(); it != engine.codecs().end(); ++it) {
+    if (it->name == "CN" && it->clockrate == 16000) {
+      EXPECT_EQ(105, it->id);
+    } else if (it->name == "CN" && it->clockrate == 32000) {
+      EXPECT_EQ(106, it->id);
+    } else if (it->name == "ISAC" && it->clockrate == 16000) {
+      EXPECT_EQ(103, it->id);
+    } else if (it->name == "ISAC" && it->clockrate == 32000) {
+      EXPECT_EQ(104, it->id);
+    } else if (it->name == "G722" && it->clockrate == 16000) {
+      EXPECT_EQ(9, it->id);
+    } else if (it->name == "telephone-event") {
+      EXPECT_EQ(126, it->id);
+    } else if (it->name == "red") {
+      EXPECT_EQ(127, it->id);
+    } else if (it->name == "opus") {
+      EXPECT_EQ(111, it->id);
+      ASSERT_NE(it->params.find("minptime"), it->params.end());
+      EXPECT_EQ("10", it->params.find("minptime")->second);
+      ASSERT_NE(it->params.find("maxptime"), it->params.end());
+      EXPECT_EQ("60", it->params.find("maxptime")->second);
+    }
+  }
+
+  engine.Terminate();
+}
+
+// Tests that VoE supports at least 32 channels
+TEST(WebRtcVoiceEngineTest, Has32Channels) {
+  cricket::WebRtcVoiceEngine engine;
+  EXPECT_TRUE(engine.Init(talk_base::Thread::Current()));
+
+  cricket::VoiceMediaChannel* channels[32];
+  int num_channels = 0;
+
+  while (num_channels < ARRAY_SIZE(channels)) {
+    cricket::VoiceMediaChannel* channel = engine.CreateChannel();
+    if (!channel)
+      break;
+
+    channels[num_channels++] = channel;
+  }
+
+  int expected = ARRAY_SIZE(channels);
+  EXPECT_EQ(expected, num_channels);
+
+  while (num_channels > 0) {
+    delete channels[--num_channels];
+  }
+
+  engine.Terminate();
+}
+
+// Test that we set our preferred codecs properly.
+TEST(WebRtcVoiceEngineTest, SetRecvCodecs) {
+  cricket::WebRtcVoiceEngine engine;
+  EXPECT_TRUE(engine.Init(talk_base::Thread::Current()));
+  cricket::WebRtcVoiceMediaChannel channel(&engine);
+  EXPECT_TRUE(channel.SetRecvCodecs(engine.codecs()));
+}
+
+#ifdef WIN32
+// Test our workarounds to WebRtc VoE' munging of the coinit count
+TEST(WebRtcVoiceEngineTest, CoInitialize) {
+  cricket::WebRtcVoiceEngine* engine = new cricket::WebRtcVoiceEngine();
+
+  // Initial refcount should be 0.
+  EXPECT_EQ(S_OK, CoInitializeEx(NULL, COINIT_MULTITHREADED));
+
+  // Engine should start even with COM already inited.
+  EXPECT_TRUE(engine->Init(talk_base::Thread::Current()));
+  engine->Terminate();
+  EXPECT_TRUE(engine->Init(talk_base::Thread::Current()));
+  engine->Terminate();
+
+  // Refcount after terminate should be 1 (in reality 3); test if it is nonzero.
+  EXPECT_EQ(S_FALSE, CoInitializeEx(NULL, COINIT_MULTITHREADED));
+  // Decrement refcount to (hopefully) 0.
+  CoUninitialize();
+  CoUninitialize();
+  delete engine;
+
+  // Ensure refcount is 0.
+  EXPECT_EQ(S_OK, CoInitializeEx(NULL, COINIT_MULTITHREADED));
+  CoUninitialize();
+}
+#endif
+
+
diff --git a/talk/p2p/base/asyncstuntcpsocket.cc b/talk/p2p/base/asyncstuntcpsocket.cc
new file mode 100644
index 0000000..2f61641
--- /dev/null
+++ b/talk/p2p/base/asyncstuntcpsocket.cc
@@ -0,0 +1,168 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/asyncstuntcpsocket.h"
+
+#include <cstring>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/p2p/base/stun.h"
+
+namespace cricket {
+
+static const size_t kMaxPacketSize = 64 * 1024;
+
+typedef uint16 PacketLength;
+static const size_t kPacketLenSize = sizeof(PacketLength);
+static const size_t kPacketLenOffset = 2;
+static const size_t kBufSize = kMaxPacketSize + kStunHeaderSize;
+static const size_t kTurnChannelDataHdrSize = 4;
+
+inline bool IsStunMessage(uint16 msg_type) {
+  // The first two bits of a channel data message are 0b01.
+  return (msg_type & 0xC000) ? false : true;
+}
+
+// AsyncStunTCPSocket
+// Binds and connects |socket| and creates AsyncTCPSocket for
+// it. Takes ownership of |socket|. Returns NULL if bind() or
+// connect() fail (|socket| is destroyed in that case).
+AsyncStunTCPSocket* AsyncStunTCPSocket::Create(
+    talk_base::AsyncSocket* socket,
+    const talk_base::SocketAddress& bind_address,
+    const talk_base::SocketAddress& remote_address) {
+  return new AsyncStunTCPSocket(AsyncTCPSocketBase::ConnectSocket(
+      socket, bind_address, remote_address), false);
+}
+
+AsyncStunTCPSocket::AsyncStunTCPSocket(
+    talk_base::AsyncSocket* socket, bool listen)
+    : talk_base::AsyncTCPSocketBase(socket, listen, kBufSize) {
+}
+
+int AsyncStunTCPSocket::Send(const void *pv, size_t cb) {
+  if (cb > kBufSize || cb < kPacketLenSize + kPacketLenOffset) {
+    SetError(EMSGSIZE);
+    return -1;
+  }
+
+  // If we are blocking on send, then silently drop this packet
+  if (!IsOutBufferEmpty())
+    return static_cast<int>(cb);
+
+  int pad_bytes;
+  size_t expected_pkt_len = GetExpectedLength(pv, cb, &pad_bytes);
+
+  // Accepts only complete STUN/ChannelData packets.
+  if (cb != expected_pkt_len)
+    return -1;
+
+  AppendToOutBuffer(pv, cb);
+
+  ASSERT(pad_bytes < 4);
+  char padding[4] = {0};
+  AppendToOutBuffer(padding, pad_bytes);
+
+  int res = FlushOutBuffer();
+  if (res <= 0) {
+    // drop packet if we made no progress
+    ClearOutBuffer();
+    return res;
+  }
+
+  // We claim to have sent the whole thing, even if we only sent partial
+  return static_cast<int>(cb);
+}
+
+void AsyncStunTCPSocket::ProcessInput(char* data, size_t* len) {
+  talk_base::SocketAddress remote_addr(GetRemoteAddress());
+  // STUN packet - First 4 bytes. Total header size is 20 bytes.
+  // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+  // |0 0|     STUN Message Type     |         Message Length        |
+  // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+  // TURN ChannelData
+  // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+  // |         Channel Number        |            Length             |
+  // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+  while (true) {
+    // We need at least 4 bytes to read the STUN or ChannelData packet length.
+    if (*len < kPacketLenOffset + kPacketLenSize)
+      return;
+
+    int pad_bytes;
+    size_t expected_pkt_len = GetExpectedLength(data, *len, &pad_bytes);
+    size_t actual_length = expected_pkt_len + pad_bytes;
+
+    if (*len < actual_length) {
+      return;
+    }
+
+    SignalReadPacket(this, data, expected_pkt_len, remote_addr);
+
+    *len -= actual_length;
+    if (*len > 0) {
+      memmove(data, data + actual_length, *len);
+    }
+  }
+}
+
+void AsyncStunTCPSocket::HandleIncomingConnection(
+    talk_base::AsyncSocket* socket) {
+  SignalNewConnection(this, new AsyncStunTCPSocket(socket, false));
+}
+
+size_t AsyncStunTCPSocket::GetExpectedLength(const void* data, size_t len,
+                                             int* pad_bytes) {
+  *pad_bytes = 0;
+  PacketLength pkt_len =
+      talk_base::GetBE16(static_cast<const char*>(data) + kPacketLenOffset);
+  size_t expected_pkt_len;
+  uint16 msg_type = talk_base::GetBE16(data);
+  if (IsStunMessage(msg_type)) {
+    // STUN message.
+    expected_pkt_len = kStunHeaderSize + pkt_len;
+  } else {
+    // TURN ChannelData message.
+    expected_pkt_len = kTurnChannelDataHdrSize + pkt_len;
+    // From RFC 5766 section 11.5
+    // Over TCP and TLS-over-TCP, the ChannelData message MUST be padded to
+    // a multiple of four bytes in order to ensure the alignment of
+    // subsequent messages.  The padding is not reflected in the length
+    // field of the ChannelData message, so the actual size of a ChannelData
+    // message (including padding) is (4 + Length) rounded up to the nearest
+    // multiple of 4.  Over UDP, the padding is not required but MAY be
+    // included.
+    if (expected_pkt_len % 4)
+      *pad_bytes = 4 - (expected_pkt_len % 4);
+  }
+  return expected_pkt_len;
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/asyncstuntcpsocket.h b/talk/p2p/base/asyncstuntcpsocket.h
new file mode 100644
index 0000000..2380c4c
--- /dev/null
+++ b/talk/p2p/base/asyncstuntcpsocket.h
@@ -0,0 +1,66 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_ASYNCSTUNTCPSOCKET_H_
+#define TALK_BASE_ASYNCSTUNTCPSOCKET_H_
+
+#include "talk/base/asynctcpsocket.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socketfactory.h"
+
+namespace cricket {
+
+class AsyncStunTCPSocket : public talk_base::AsyncTCPSocketBase {
+ public:
+  // Binds and connects |socket| and creates AsyncTCPSocket for
+  // it. Takes ownership of |socket|. Returns NULL if bind() or
+  // connect() fail (|socket| is destroyed in that case).
+  static AsyncStunTCPSocket* Create(
+      talk_base::AsyncSocket* socket,
+      const talk_base::SocketAddress& bind_address,
+      const talk_base::SocketAddress& remote_address);
+
+  AsyncStunTCPSocket(talk_base::AsyncSocket* socket, bool listen);
+  virtual ~AsyncStunTCPSocket() {}
+
+  virtual int Send(const void* pv, size_t cb);
+  virtual void ProcessInput(char* data, size_t* len);
+  virtual void HandleIncomingConnection(talk_base::AsyncSocket* socket);
+
+ private:
+  // This method returns the message hdr + length written in the header.
+  // This method also returns the number of padding bytes needed/added to the
+  // turn message. |pad_bytes| should be used only when |is_turn| is true.
+  size_t GetExpectedLength(const void* data, size_t len,
+                           int* pad_bytes);
+
+  DISALLOW_EVIL_CONSTRUCTORS(AsyncStunTCPSocket);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_BASE_ASYNCSTUNTCPSOCKET_H_
diff --git a/talk/p2p/base/asyncstuntcpsocket_unittest.cc b/talk/p2p/base/asyncstuntcpsocket_unittest.cc
new file mode 100644
index 0000000..a675712
--- /dev/null
+++ b/talk/p2p/base/asyncstuntcpsocket_unittest.cc
@@ -0,0 +1,277 @@
+/*
+ * libjingle
+ * Copyright 2013, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/asyncsocket.h"
+#include "talk/base/gunit.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/virtualsocketserver.h"
+#include "talk/p2p/base/asyncstuntcpsocket.h"
+
+namespace cricket {
+
+static unsigned char kStunMessageWithZeroLength[] = {
+  0x00, 0x01, 0x00, 0x00,  // length of 0 (last 2 bytes)
+  0x21, 0x12, 0xA4, 0x42,
+  '0', '1', '2', '3',
+  '4', '5', '6', '7',
+  '8', '9', 'a', 'b',
+};
+
+
+static unsigned char kTurnChannelDataMessageWithZeroLength[] = {
+  0x40, 0x00, 0x00, 0x00,  // length of 0 (last 2 bytes)
+};
+
+static unsigned char kTurnChannelDataMessage[] = {
+  0x40, 0x00, 0x00, 0x10,
+  0x21, 0x12, 0xA4, 0x42,
+  '0', '1', '2', '3',
+  '4', '5', '6', '7',
+  '8', '9', 'a', 'b',
+};
+
+static unsigned char kStunMessageWithInvalidLength[] = {
+  0x00, 0x01, 0x00, 0x10,
+  0x21, 0x12, 0xA4, 0x42,
+  '0', '1', '2', '3',
+  '4', '5', '6', '7',
+  '8', '9', 'a', 'b',
+};
+
+static unsigned char kTurnChannelDataMessageWithInvalidLength[] = {
+  0x80, 0x00, 0x00, 0x20,
+  0x21, 0x12, 0xA4, 0x42,
+  '0', '1', '2', '3',
+  '4', '5', '6', '7',
+  '8', '9', 'a', 'b',
+};
+
+static unsigned char kTurnChannelDataMessageWithOddLength[] = {
+  0x40, 0x00, 0x00, 0x05,
+  0x21, 0x12, 0xA4, 0x42,
+  '0',
+};
+
+
+static const talk_base::SocketAddress kClientAddr("11.11.11.11", 0);
+static const talk_base::SocketAddress kServerAddr("22.22.22.22", 0);
+
+class AsyncStunTCPSocketTest : public testing::Test,
+                               public sigslot::has_slots<> {
+ protected:
+  AsyncStunTCPSocketTest()
+      : vss_(new talk_base::VirtualSocketServer(NULL)),
+        ss_scope_(vss_.get()) {
+  }
+
+  virtual void SetUp() {
+    CreateSockets();
+  }
+
+  void CreateSockets() {
+    talk_base::AsyncSocket* server = vss_->CreateAsyncSocket(
+        kServerAddr.family(), SOCK_STREAM);
+    server->Bind(kServerAddr);
+    recv_socket_.reset(new AsyncStunTCPSocket(server, true));
+    recv_socket_->SignalNewConnection.connect(
+        this, &AsyncStunTCPSocketTest::OnNewConnection);
+
+    talk_base::AsyncSocket* client = vss_->CreateAsyncSocket(
+        kClientAddr.family(), SOCK_STREAM);
+    send_socket_.reset(AsyncStunTCPSocket::Create(
+        client, kClientAddr, recv_socket_->GetLocalAddress()));
+    ASSERT_TRUE(send_socket_.get() != NULL);
+    vss_->ProcessMessagesUntilIdle();
+  }
+
+  void OnReadPacket(talk_base::AsyncPacketSocket* socket, const char* data,
+                    size_t len, const talk_base::SocketAddress& remote_addr) {
+    recv_packets_.push_back(std::string(data, len));
+  }
+
+  void OnNewConnection(talk_base::AsyncPacketSocket* server,
+                       talk_base::AsyncPacketSocket* new_socket) {
+    listen_socket_.reset(new_socket);
+    new_socket->SignalReadPacket.connect(
+        this, &AsyncStunTCPSocketTest::OnReadPacket);
+  }
+
+  bool Send(const void* data, size_t len) {
+    size_t ret = send_socket_->Send(reinterpret_cast<const char*>(data), len);
+    vss_->ProcessMessagesUntilIdle();
+    return (ret == len);
+  }
+
+  bool CheckData(const void* data, int len) {
+    bool ret = false;
+    if (recv_packets_.size()) {
+      std::string packet =  recv_packets_.front();
+      recv_packets_.pop_front();
+      ret = (memcmp(data, packet.c_str(), len) == 0);
+    }
+    return ret;
+  }
+
+  talk_base::scoped_ptr<talk_base::VirtualSocketServer> vss_;
+  talk_base::SocketServerScope ss_scope_;
+  talk_base::scoped_ptr<AsyncStunTCPSocket> send_socket_;
+  talk_base::scoped_ptr<AsyncStunTCPSocket> recv_socket_;
+  talk_base::scoped_ptr<talk_base::AsyncPacketSocket> listen_socket_;
+  std::list<std::string> recv_packets_;
+};
+
+// Testing a stun packet sent/recv properly.
+TEST_F(AsyncStunTCPSocketTest, TestSingleStunPacket) {
+  EXPECT_TRUE(Send(kStunMessageWithZeroLength,
+                   sizeof(kStunMessageWithZeroLength)));
+  EXPECT_EQ(1u, recv_packets_.size());
+  EXPECT_TRUE(CheckData(kStunMessageWithZeroLength,
+                        sizeof(kStunMessageWithZeroLength)));
+}
+
+// Verify sending multiple packets.
+TEST_F(AsyncStunTCPSocketTest, TestMultipleStunPackets) {
+  EXPECT_TRUE(Send(kStunMessageWithZeroLength,
+                   sizeof(kStunMessageWithZeroLength)));
+  EXPECT_TRUE(Send(kStunMessageWithZeroLength,
+                   sizeof(kStunMessageWithZeroLength)));
+  EXPECT_TRUE(Send(kStunMessageWithZeroLength,
+                   sizeof(kStunMessageWithZeroLength)));
+  EXPECT_TRUE(Send(kStunMessageWithZeroLength,
+                   sizeof(kStunMessageWithZeroLength)));
+  EXPECT_EQ(4u, recv_packets_.size());
+}
+
+// Verifying TURN channel data message with zero length.
+TEST_F(AsyncStunTCPSocketTest, TestTurnChannelDataWithZeroLength) {
+  EXPECT_TRUE(Send(kTurnChannelDataMessageWithZeroLength,
+                   sizeof(kTurnChannelDataMessageWithZeroLength)));
+  EXPECT_EQ(1u, recv_packets_.size());
+  EXPECT_TRUE(CheckData(kTurnChannelDataMessageWithZeroLength,
+                        sizeof(kTurnChannelDataMessageWithZeroLength)));
+}
+
+// Verifying TURN channel data message.
+TEST_F(AsyncStunTCPSocketTest, TestTurnChannelData) {
+  EXPECT_TRUE(Send(kTurnChannelDataMessage,
+                   sizeof(kTurnChannelDataMessage)));
+  EXPECT_EQ(1u, recv_packets_.size());
+  EXPECT_TRUE(CheckData(kTurnChannelDataMessage,
+                        sizeof(kTurnChannelDataMessage)));
+}
+
+// Verifying TURN channel messages which needs padding handled properly.
+TEST_F(AsyncStunTCPSocketTest, TestTurnChannelDataPadding) {
+  EXPECT_TRUE(Send(kTurnChannelDataMessageWithOddLength,
+                   sizeof(kTurnChannelDataMessageWithOddLength)));
+  EXPECT_EQ(1u, recv_packets_.size());
+  EXPECT_TRUE(CheckData(kTurnChannelDataMessageWithOddLength,
+                        sizeof(kTurnChannelDataMessageWithOddLength)));
+}
+
+// Verifying stun message with invalid length.
+TEST_F(AsyncStunTCPSocketTest, TestStunInvalidLength) {
+  EXPECT_FALSE(Send(kStunMessageWithInvalidLength,
+                    sizeof(kStunMessageWithInvalidLength)));
+  EXPECT_EQ(0u, recv_packets_.size());
+
+  // Modify the message length to larger value.
+  kStunMessageWithInvalidLength[2] = 0xFF;
+  kStunMessageWithInvalidLength[3] = 0xFF;
+  EXPECT_FALSE(Send(kStunMessageWithInvalidLength,
+                    sizeof(kStunMessageWithInvalidLength)));
+
+  // Modify the message length to smaller value.
+  kStunMessageWithInvalidLength[2] = 0x00;
+  kStunMessageWithInvalidLength[3] = 0x01;
+  EXPECT_FALSE(Send(kStunMessageWithInvalidLength,
+                    sizeof(kStunMessageWithInvalidLength)));
+}
+
+// Verifying TURN channel data message with invalid length.
+TEST_F(AsyncStunTCPSocketTest, TestTurnChannelDataWithInvalidLength) {
+  EXPECT_FALSE(Send(kTurnChannelDataMessageWithInvalidLength,
+                   sizeof(kTurnChannelDataMessageWithInvalidLength)));
+  // Modify the length to larger value.
+  kTurnChannelDataMessageWithInvalidLength[2] = 0xFF;
+  kTurnChannelDataMessageWithInvalidLength[3] = 0xF0;
+  EXPECT_FALSE(Send(kTurnChannelDataMessageWithInvalidLength,
+                   sizeof(kTurnChannelDataMessageWithInvalidLength)));
+
+  // Modify the length to smaller value.
+  kTurnChannelDataMessageWithInvalidLength[2] = 0x00;
+  kTurnChannelDataMessageWithInvalidLength[3] = 0x00;
+  EXPECT_FALSE(Send(kTurnChannelDataMessageWithInvalidLength,
+                   sizeof(kTurnChannelDataMessageWithInvalidLength)));
+}
+
+// Verifying a small buffer handled (dropped) properly. This will be
+// a common one for both stun and turn.
+TEST_F(AsyncStunTCPSocketTest, TestTooSmallMessageBuffer) {
+  char data[1];
+  EXPECT_FALSE(Send(data, sizeof(data)));
+}
+
+// Verifying a legal large turn message.
+TEST_F(AsyncStunTCPSocketTest, TestMaximumSizeTurnPacket) {
+  // We have problem in getting the SignalWriteEvent from the virtual socket
+  // server. So increasing the send buffer to 64k.
+  // TODO(mallinath) - Remove this setting after we fix vss issue.
+  vss_->set_send_buffer_capacity(64 * 1024);
+  unsigned char packet[65539];
+  packet[0] = 0x40;
+  packet[1] = 0x00;
+  packet[2] = 0xFF;
+  packet[3] = 0xFF;
+  EXPECT_TRUE(Send(packet, sizeof(packet)));
+}
+
+// Verifying a legal large stun message.
+TEST_F(AsyncStunTCPSocketTest, TestMaximumSizeStunPacket) {
+  // We have problem in getting the SignalWriteEvent from the virtual socket
+  // server. So increasing the send buffer to 64k.
+  // TODO(mallinath) - Remove this setting after we fix vss issue.
+  vss_->set_send_buffer_capacity(64 * 1024);
+  unsigned char packet[65552];
+  packet[0] = 0x00;
+  packet[1] = 0x01;
+  packet[2] = 0xFF;
+  packet[3] = 0xFC;
+  EXPECT_TRUE(Send(packet, sizeof(packet)));
+}
+
+// Investigate why WriteEvent is not signaled from VSS.
+TEST_F(AsyncStunTCPSocketTest, DISABLED_TestWithSmallSendBuffer) {
+  vss_->set_send_buffer_capacity(1);
+  Send(kTurnChannelDataMessageWithOddLength,
+       sizeof(kTurnChannelDataMessageWithOddLength));
+  EXPECT_EQ(1u, recv_packets_.size());
+  EXPECT_TRUE(CheckData(kTurnChannelDataMessageWithOddLength,
+                        sizeof(kTurnChannelDataMessageWithOddLength)));
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/basicpacketsocketfactory.cc b/talk/p2p/base/basicpacketsocketfactory.cc
new file mode 100644
index 0000000..7142104
--- /dev/null
+++ b/talk/p2p/base/basicpacketsocketfactory.cc
@@ -0,0 +1,187 @@
+/*
+ * 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/p2p/base/basicpacketsocketfactory.h"
+
+#include "talk/base/asyncudpsocket.h"
+#include "talk/base/asynctcpsocket.h"
+#include "talk/base/logging.h"
+#include "talk/base/socketadapters.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/asyncstuntcpsocket.h"
+#include "talk/p2p/base/stun.h"
+
+namespace talk_base {
+
+BasicPacketSocketFactory::BasicPacketSocketFactory()
+    : thread_(Thread::Current()),
+      socket_factory_(NULL) {
+}
+
+BasicPacketSocketFactory::BasicPacketSocketFactory(Thread* thread)
+    : thread_(thread),
+      socket_factory_(NULL) {
+}
+
+BasicPacketSocketFactory::BasicPacketSocketFactory(
+    SocketFactory* socket_factory)
+    : thread_(NULL),
+      socket_factory_(socket_factory) {
+}
+
+BasicPacketSocketFactory::~BasicPacketSocketFactory() {
+}
+
+AsyncPacketSocket* BasicPacketSocketFactory::CreateUdpSocket(
+    const SocketAddress& address, int min_port, int max_port) {
+  // UDP sockets are simple.
+  talk_base::AsyncSocket* socket =
+      socket_factory()->CreateAsyncSocket(
+          address.family(), SOCK_DGRAM);
+  if (!socket) {
+    return NULL;
+  }
+  if (BindSocket(socket, address, min_port, max_port) < 0) {
+    LOG(LS_ERROR) << "UDP bind failed with error "
+                    << socket->GetError();
+    delete socket;
+    return NULL;
+  }
+  return new talk_base::AsyncUDPSocket(socket);
+}
+
+AsyncPacketSocket* BasicPacketSocketFactory::CreateServerTcpSocket(
+    const SocketAddress& local_address, int min_port, int max_port, int opts) {
+  talk_base::AsyncSocket* socket =
+      socket_factory()->CreateAsyncSocket(local_address.family(),
+                                          SOCK_STREAM);
+  if (!socket) {
+    return NULL;
+  }
+
+  if (BindSocket(socket, local_address, min_port, max_port) < 0) {
+    LOG(LS_ERROR) << "TCP bind failed with error "
+                  << socket->GetError();
+    delete socket;
+    return NULL;
+  }
+
+  // If using SSLTCP, wrap the TCP socket in a pseudo-SSL socket.
+  if (opts & PacketSocketFactory::OPT_SSLTCP) {
+    socket = new talk_base::AsyncSSLSocket(socket);
+  }
+
+  // Set TCP_NODELAY (via OPT_NODELAY) for improved performance.
+  // See http://go/gtalktcpnodelayexperiment
+  socket->SetOption(talk_base::Socket::OPT_NODELAY, 1);
+
+  if (opts & PacketSocketFactory::OPT_STUN)
+    return new cricket::AsyncStunTCPSocket(socket, true);
+
+  return new talk_base::AsyncTCPSocket(socket, true);
+}
+
+AsyncPacketSocket* BasicPacketSocketFactory::CreateClientTcpSocket(
+    const SocketAddress& local_address, const SocketAddress& remote_address,
+    const ProxyInfo& proxy_info, const std::string& user_agent, int opts) {
+  talk_base::AsyncSocket* socket =
+      socket_factory()->CreateAsyncSocket(local_address.family(), SOCK_STREAM);
+  if (!socket) {
+    return NULL;
+  }
+
+  if (BindSocket(socket, local_address, 0, 0) < 0) {
+    LOG(LS_ERROR) << "TCP bind failed with error "
+                  << socket->GetError();
+    delete socket;
+    return NULL;
+  }
+
+  // If using a proxy, wrap the socket in a proxy socket.
+  if (proxy_info.type == talk_base::PROXY_SOCKS5) {
+    socket = new talk_base::AsyncSocksProxySocket(
+        socket, proxy_info.address, proxy_info.username, proxy_info.password);
+  } else if (proxy_info.type == talk_base::PROXY_HTTPS) {
+    socket = new talk_base::AsyncHttpsProxySocket(
+        socket, user_agent, proxy_info.address,
+        proxy_info.username, proxy_info.password);
+  }
+
+  // If using SSLTCP, wrap the TCP socket in a pseudo-SSL socket.
+  if (opts & PacketSocketFactory::OPT_SSLTCP) {
+    socket = new talk_base::AsyncSSLSocket(socket);
+  }
+
+  if (socket->Connect(remote_address) < 0) {
+    LOG(LS_ERROR) << "TCP connect failed with error "
+                  << socket->GetError();
+    delete socket;
+    return NULL;
+  }
+
+  // Finally, wrap that socket in a TCP or STUN TCP packet socket.
+  AsyncPacketSocket* tcp_socket;
+  if (opts & PacketSocketFactory::OPT_STUN) {
+    tcp_socket = new cricket::AsyncStunTCPSocket(socket, false);
+  } else {
+    tcp_socket = new talk_base::AsyncTCPSocket(socket, false);
+  }
+
+  // Set TCP_NODELAY (via OPT_NODELAY) for improved performance.
+  // See http://go/gtalktcpnodelayexperiment
+  tcp_socket->SetOption(talk_base::Socket::OPT_NODELAY, 1);
+
+  return tcp_socket;
+}
+
+int BasicPacketSocketFactory::BindSocket(
+    AsyncSocket* socket, const SocketAddress& local_address,
+    int min_port, int max_port) {
+  int ret = -1;
+  if (min_port == 0 && max_port == 0) {
+    // If there's no port range, let the OS pick a port for us.
+    ret = socket->Bind(local_address);
+  } else {
+    // Otherwise, try to find a port in the provided range.
+    for (int port = min_port; ret < 0 && port <= max_port; ++port) {
+      ret = socket->Bind(talk_base::SocketAddress(local_address.ipaddr(),
+                                                  port));
+    }
+  }
+  return ret;
+}
+
+SocketFactory* BasicPacketSocketFactory::socket_factory() {
+  if (thread_) {
+    ASSERT(thread_ == Thread::Current());
+    return thread_->socketserver();
+  } else {
+    return socket_factory_;
+  }
+}
+
+}  // namespace talk_base
diff --git a/talk/p2p/base/basicpacketsocketfactory.h b/talk/p2p/base/basicpacketsocketfactory.h
new file mode 100644
index 0000000..d4e76e7
--- /dev/null
+++ b/talk/p2p/base/basicpacketsocketfactory.h
@@ -0,0 +1,66 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_BASICPACKETSOCKETFACTORY_H_
+#define TALK_BASE_BASICPACKETSOCKETFACTORY_H_
+
+#include "talk/p2p/base/packetsocketfactory.h"
+
+namespace talk_base {
+
+class AsyncSocket;
+class SocketFactory;
+class Thread;
+
+class BasicPacketSocketFactory : public PacketSocketFactory {
+ public:
+  BasicPacketSocketFactory();
+  explicit BasicPacketSocketFactory(Thread* thread);
+  explicit BasicPacketSocketFactory(SocketFactory* socket_factory);
+  virtual ~BasicPacketSocketFactory();
+
+  virtual AsyncPacketSocket* CreateUdpSocket(
+      const SocketAddress& local_address, int min_port, int max_port);
+  virtual AsyncPacketSocket* CreateServerTcpSocket(
+      const SocketAddress& local_address, int min_port, int max_port, int opts);
+  virtual AsyncPacketSocket* CreateClientTcpSocket(
+      const SocketAddress& local_address, const SocketAddress& remote_address,
+      const ProxyInfo& proxy_info, const std::string& user_agent, int opts);
+
+ private:
+  int BindSocket(AsyncSocket* socket, const SocketAddress& local_address,
+                 int min_port, int max_port);
+
+  SocketFactory* socket_factory();
+
+  Thread* thread_;
+  SocketFactory* socket_factory_;
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_BASICPACKETSOCKETFACTORY_H_
diff --git a/talk/p2p/base/candidate.h b/talk/p2p/base/candidate.h
new file mode 100644
index 0000000..19eed8c
--- /dev/null
+++ b/talk/p2p/base/candidate.h
@@ -0,0 +1,203 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_CANDIDATE_H_
+#define TALK_P2P_BASE_CANDIDATE_H_
+
+#include <climits>
+#include <cmath>
+#include <string>
+#include <sstream>
+#include <iomanip>
+#include "talk/base/basictypes.h"
+#include "talk/base/socketaddress.h"
+#include "talk/p2p/base/constants.h"
+
+namespace cricket {
+
+// Candidate for ICE based connection discovery.
+
+class Candidate {
+ public:
+  // TODO: Match the ordering and param list as per RFC 5245
+  // candidate-attribute syntax. http://tools.ietf.org/html/rfc5245#section-15.1
+  Candidate() : component_(0), priority_(0), generation_(0) {}
+  Candidate(const std::string& id, int component, const std::string& protocol,
+            const talk_base::SocketAddress& address, uint32 priority,
+            const std::string& username, const std::string& password,
+            const std::string& type, const std::string& network_name,
+            uint32 generation, const std::string& foundation)
+      : id_(id), component_(component), protocol_(protocol), address_(address),
+        priority_(priority), username_(username), password_(password),
+        type_(type), network_name_(network_name), generation_(generation),
+        foundation_(foundation) {
+  }
+
+  const std::string & id() const { return id_; }
+  void set_id(const std::string & id) { id_ = id; }
+
+  int component() const { return component_; }
+  void set_component(int component) { component_ = component; }
+
+  const std::string & protocol() const { return protocol_; }
+  void set_protocol(const std::string & protocol) { protocol_ = protocol; }
+
+  const talk_base::SocketAddress & address() const { return address_; }
+  void set_address(const talk_base::SocketAddress & address) {
+    address_ = address;
+  }
+
+  uint32 priority() const { return priority_; }
+  void set_priority(const uint32 priority) { priority_ = priority; }
+
+//  void set_type_preference(uint32 type_preference) {
+//    priority_ = GetPriority(type_preference);
+//  }
+
+  // Maps old preference (which was 0.0-1.0) to match priority (which
+  // is 0-2^32-1) to to match RFC 5245, section 4.1.2.1.  Also see
+  // https://docs.google.com/a/google.com/document/d/
+  // 1iNQDiwDKMh0NQOrCqbj3DKKRT0Dn5_5UJYhmZO-t7Uc/edit
+  float preference() const {
+    // The preference value is clamped to two decimal precision.
+    return static_cast<float>(((priority_ >> 24) * 100 / 127) / 100.0);
+  }
+
+  void set_preference(float preference) {
+    // Limiting priority to UINT_MAX when value exceeds uint32 max.
+    // This can happen for e.g. when preference = 3.
+    uint64 prio_val = static_cast<uint64>(preference * 127) << 24;
+    priority_ = static_cast<uint32>(
+      talk_base::_min(prio_val, static_cast<uint64>(UINT_MAX)));
+  }
+
+  const std::string & username() const { return username_; }
+  void set_username(const std::string & username) { username_ = username; }
+
+  const std::string & password() const { return password_; }
+  void set_password(const std::string & password) { password_ = password; }
+
+  const std::string & type() const { return type_; }
+  void set_type(const std::string & type) { type_ = type; }
+
+  const std::string & network_name() const { return network_name_; }
+  void set_network_name(const std::string & network_name) {
+    network_name_ = network_name;
+  }
+
+  // Candidates in a new generation replace those in the old generation.
+  uint32 generation() const { return generation_; }
+  void set_generation(uint32 generation) { generation_ = generation; }
+  const std::string generation_str() const {
+    std::ostringstream ost;
+    ost << generation_;
+    return ost.str();
+  }
+  void set_generation_str(const std::string& str) {
+    std::istringstream ist(str);
+    ist >> generation_;
+  }
+
+  const std::string& foundation() const {
+    return foundation_;
+  }
+
+  void set_foundation(const std::string& foundation) {
+    foundation_ = foundation;
+  }
+
+  const talk_base::SocketAddress & related_address() const {
+    return related_address_;
+  }
+  void set_related_address(
+      const talk_base::SocketAddress & related_address) {
+    related_address_ = related_address;
+  }
+
+  // Determines whether this candidate is equivalent to the given one.
+  bool IsEquivalent(const Candidate& c) const {
+    // We ignore the network name, since that is just debug information, and
+    // the priority, since that should be the same if the rest is (and it's
+    // a float so equality checking is always worrisome).
+    return (id_ == c.id_) &&
+           (component_ == c.component_) &&
+           (protocol_ == c.protocol_) &&
+           (address_ == c.address_) &&
+           (username_ == c.username_) &&
+           (password_ == c.password_) &&
+           (type_ == c.type_) &&
+           (generation_ == c.generation_) &&
+           (foundation_ == c.foundation_) &&
+           (related_address_ == c.related_address_);
+  }
+
+  std::string ToString() const {
+    return ToStringInternal(false);
+  }
+
+  std::string ToSensitiveString() const {
+    return ToStringInternal(true);
+  }
+
+  uint32 GetPriority(uint32 type_preference) const {
+    // RFC 5245 - 4.1.2.1.
+    // priority = (2^24)*(type preference) +
+    //            (2^8)*(local preference) +
+    //            (2^0)*(256 - component ID)
+    int addr_pref = IPAddressPrecedence(address_.ipaddr());
+    return (type_preference << 24) | (addr_pref << 8) | (256 - component_);
+  }
+
+ private:
+  std::string ToStringInternal(bool sensitive) const {
+    std::ostringstream ost;
+    std::string address = sensitive ? address_.ToSensitiveString() :
+                                      address_.ToString();
+    ost << "Cand[" << id_ << ":" << component_ << ":"
+        << type_ << ":" << protocol_ << ":"
+        << network_name_ << ":" << address << ":"
+        << username_ << ":" << password_ << "]";
+    return ost.str();
+  }
+
+  std::string id_;
+  int component_;
+  std::string protocol_;
+  talk_base::SocketAddress address_;
+  uint32 priority_;
+  std::string username_;
+  std::string password_;
+  std::string type_;
+  std::string network_name_;
+  uint32 generation_;
+  std::string foundation_;
+  talk_base::SocketAddress related_address_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_CANDIDATE_H_
diff --git a/talk/p2p/base/common.h b/talk/p2p/base/common.h
new file mode 100644
index 0000000..5a38180
--- /dev/null
+++ b/talk/p2p/base/common.h
@@ -0,0 +1,37 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_COMMON_H_
+#define TALK_P2P_BASE_COMMON_H_
+
+#include "talk/base/logging.h"
+
+// Common log description format for jingle messages
+#define LOG_J(sev, obj) LOG(sev) << "Jingle:" << obj->ToString() << ": "
+#define LOG_JV(sev, obj) LOG_V(sev) << "Jingle:" << obj->ToString() << ": "
+
+#endif  // TALK_P2P_BASE_COMMON_H_
diff --git a/talk/p2p/base/constants.cc b/talk/p2p/base/constants.cc
new file mode 100644
index 0000000..992fe2d
--- /dev/null
+++ b/talk/p2p/base/constants.cc
@@ -0,0 +1,263 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/constants.h"
+
+#include <string>
+
+#include "talk/xmllite/qname.h"
+
+namespace cricket {
+
+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::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::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 buzz::StaticQName QN_JINGLE_DRAFT_GROUP = { NS_JINGLE_DRAFT, "group" };
+const buzz::StaticQName QN_JINGLE_DRAFT_GROUP_TYPE = { NS_EMPTY, "type" };
+const char JINGLE_CONTENT_MEDIA_AUDIO[] = "audio";
+const char JINGLE_CONTENT_MEDIA_VIDEO[] = "video";
+const char JINGLE_CONTENT_MEDIA_DATA[] = "data";
+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::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 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 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 char CN_AUDIO[] = "audio";
+const char CN_VIDEO[] = "video";
+const char CN_DATA[] = "data";
+const char CN_OTHER[] = "main";
+// other SDP related strings
+const char GROUP_TYPE_BUNDLE[] = "BUNDLE";
+
+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 buzz::StaticQName QN_JINGLE_RTP_HDREXT =
+    { NS_JINGLE_RTP, "rtp-hdrext" };
+const buzz::StaticQName QN_URI = { NS_EMPTY, "uri" };
+
+const char NS_JINGLE_DRAFT_SCTP[] = "google:jingle:sctp";
+const buzz::StaticQName QN_JINGLE_DRAFT_SCTP_CONTENT =
+    { NS_JINGLE_DRAFT_SCTP, LN_DESCRIPTION };
+const buzz::StaticQName QN_JINGLE_DRAFT_SCTP_STREAM =
+    { NS_JINGLE_DRAFT_SCTP, "stream" };
+
+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::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 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 ICE_CANDIDATE_TYPE_PEER_STUN[] = "prflx";
+const char ICE_CANDIDATE_TYPE_SERVER_STUN[] = "srflx";
+// Minimum ufrag length is 4 characters as per RFC5245. We chose 16 because
+// some internal systems expect username to be 16 bytes.
+const int ICE_UFRAG_LENGTH = 16;
+// Minimum password length of 22 characters as per RFC5245. We chose 24 because
+// some internal systems expect password to be multiple of 4.
+const int ICE_PWD_LENGTH = 24;
+// TODO: This is media-specific, so might belong
+// somewhere like media/base/constants.h
+const int ICE_CANDIDATE_COMPONENT_RTP = 1;
+const int ICE_CANDIDATE_COMPONENT_RTCP = 2;
+const int ICE_CANDIDATE_COMPONENT_DEFAULT = 1;
+
+const buzz::StaticQName QN_FINGERPRINT = { cricket::NS_EMPTY, "fingerprint" };
+const buzz::StaticQName QN_FINGERPRINT_ALGORITHM =
+    { cricket::NS_EMPTY, "algorithm" };
+const buzz::StaticQName QN_FINGERPRINT_DIGEST = { cricket::NS_EMPTY, "digest" };
+
+const char NS_JINGLE_ICE_UDP[] = "urn:xmpp:jingle:transports:ice-udp:1";
+
+const char ICE_OPTION_GICE[] = "google-ice";
+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 GICE_CANDIDATE_TYPE_STUN[] = "stun";
+const char GICE_CHANNEL_NAME_RTP[] = "rtp";
+const char GICE_CHANNEL_NAME_RTCP[] = "rtcp";
+const char GICE_CHANNEL_NAME_VIDEO_RTP[] = "video_rtp";
+const char GICE_CHANNEL_NAME_VIDEO_RTCP[] = "video_rtcp";
+const char GICE_CHANNEL_NAME_DATA_RTP[] = "data_rtp";
+const char GICE_CHANNEL_NAME_DATA_RTCP[] = "data_rtcp";
+
+// terminate reasons and errors
+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 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 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 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" };
+
+const char NS_GINGLE_RAW[] = "http://www.google.com/transport/raw-udp";
+const buzz::StaticQName QN_GINGLE_RAW_TRANSPORT = { NS_GINGLE_RAW, "transport" };
+const buzz::StaticQName QN_GINGLE_RAW_CHANNEL = { NS_GINGLE_RAW, "channel" };
+
+// old stuff
+#ifdef FEATURE_ENABLE_VOICEMAIL
+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
new file mode 100644
index 0000000..1455801
--- /dev/null
+++ b/talk/p2p/base/constants.h
@@ -0,0 +1,264 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_CONSTANTS_H_
+#define TALK_P2P_BASE_CONSTANTS_H_
+
+#include <string>
+#include "talk/xmllite/qname.h"
+
+// This file contains constants related to signaling that are used in various
+// classes in this directory.
+
+namespace cricket {
+
+// NS_ == namespace
+// QN_ == buzz::QName (namespace + name)
+// LN_ == "local name" == QName::LocalPart()
+//   these are useful when you need to find a tag
+//   that has different namespaces (like <description> or <transport>)
+
+extern const char NS_EMPTY[];
+extern const char NS_JINGLE[];
+extern const char NS_JINGLE_DRAFT[];
+extern const char NS_GINGLE[];
+
+enum SignalingProtocol {
+  PROTOCOL_JINGLE,
+  PROTOCOL_GINGLE,
+  PROTOCOL_HYBRID,
+};
+
+// actions (aka Gingle <session> or Jingle <jingle>)
+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::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 buzz::StaticQName QN_JINGLE_DRAFT_GROUP;
+extern const buzz::StaticQName QN_JINGLE_DRAFT_GROUP_TYPE;
+extern const char JINGLE_CONTENT_MEDIA_AUDIO[];
+extern const char JINGLE_CONTENT_MEDIA_VIDEO[];
+extern const char JINGLE_CONTENT_MEDIA_DATA[];
+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::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 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 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 char CN_AUDIO[];
+extern const char CN_VIDEO[];
+extern const char CN_DATA[];
+extern const char CN_OTHER[];
+// other SDP related strings
+// GN stands for group name
+extern const char GROUP_TYPE_BUNDLE[];
+
+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 buzz::StaticQName QN_JINGLE_RTP_HDREXT;
+extern const buzz::StaticQName QN_URI;
+
+extern const char NS_JINGLE_DRAFT_SCTP[];
+extern const buzz::StaticQName QN_JINGLE_DRAFT_SCTP_CONTENT;
+extern const buzz::StaticQName QN_JINGLE_DRAFT_SCTP_STREAM;
+
+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::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 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 ICE_CANDIDATE_TYPE_PEER_STUN[];
+extern const char ICE_CANDIDATE_TYPE_SERVER_STUN[];
+extern const int ICE_UFRAG_LENGTH;
+extern const int ICE_PWD_LENGTH;
+extern const int ICE_CANDIDATE_COMPONENT_RTP;
+extern const int ICE_CANDIDATE_COMPONENT_RTCP;
+extern const int ICE_CANDIDATE_COMPONENT_DEFAULT;
+
+extern const buzz::StaticQName QN_FINGERPRINT;
+extern const buzz::StaticQName QN_FINGERPRINT_ALGORITHM;
+extern const buzz::StaticQName QN_FINGERPRINT_DIGEST;
+
+extern const char NS_JINGLE_ICE_UDP[];
+
+extern const char ICE_OPTION_GICE[];
+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 GICE_CHANNEL_NAME_RTP[];
+extern const char GICE_CHANNEL_NAME_RTCP[];
+extern const char GICE_CHANNEL_NAME_VIDEO_RTP[];
+extern const char GICE_CHANNEL_NAME_VIDEO_RTCP[];
+extern const char GICE_CHANNEL_NAME_DATA_RTP[];
+extern const char GICE_CHANNEL_NAME_DATA_RTCP[];
+
+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 char JINGLE_ERROR_BAD_REQUEST[];  // like parse error
+// got transport-info before session-initiate, for example
+extern const char JINGLE_ERROR_OUT_OF_ORDER[];
+extern const char JINGLE_ERROR_UNKNOWN_SESSION[];
+
+// Call terminate reasons from XEP-166
+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 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 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 char NS_VOICEMAIL[];
+extern const buzz::StaticQName QN_VOICEMAIL_REGARDING;
+#endif
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_CONSTANTS_H_
diff --git a/talk/p2p/base/dtlstransport.h b/talk/p2p/base/dtlstransport.h
new file mode 100644
index 0000000..5c5253d
--- /dev/null
+++ b/talk/p2p/base/dtlstransport.h
@@ -0,0 +1,144 @@
+/*
+ * libjingle
+ * Copyright 2012, Google, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_DTLSTRANSPORT_H_
+#define TALK_P2P_BASE_DTLSTRANSPORT_H_
+
+#include "talk/p2p/base/dtlstransportchannel.h"
+#include "talk/p2p/base/transport.h"
+
+namespace talk_base {
+class SSLIdentity;
+}
+
+namespace cricket {
+
+class PortAllocator;
+
+// Base should be a descendant of cricket::Transport
+template<class Base>
+class DtlsTransport : public Base {
+ public:
+  DtlsTransport(talk_base::Thread* signaling_thread,
+                talk_base::Thread* worker_thread,
+                const std::string& content_name,
+                PortAllocator* allocator,
+                talk_base::SSLIdentity* identity)
+      : Base(signaling_thread, worker_thread, content_name, allocator),
+        identity_(identity) {
+  }
+
+  ~DtlsTransport() {
+    Base::DestroyAllChannels();
+  }
+
+  virtual bool ApplyLocalTransportDescription_w(TransportChannelImpl*
+                                                channel) {
+    talk_base::SSLFingerprint* local_fp =
+        Base::local_description()->identity_fingerprint.get();
+
+    if (local_fp) {
+      // Sanity check local fingerprint.
+      if (identity_) {
+        talk_base::scoped_ptr<talk_base::SSLFingerprint> local_fp_tmp(
+            talk_base::SSLFingerprint::Create(local_fp->algorithm,
+                                              identity_));
+        ASSERT(local_fp_tmp.get() != NULL);
+        if (!(*local_fp_tmp == *local_fp)) {
+          LOG(LS_WARNING) << "Local fingerprint does not match identity";
+          return false;
+        }
+      } else {
+        LOG(LS_WARNING)
+            << "Local fingerprint provided but no identity available";
+        return false;
+      }
+    } else {
+      identity_ = NULL;
+    }
+
+    if (!channel->SetLocalIdentity(identity_))
+      return false;
+
+    // Apply the description in the base class.
+    return Base::ApplyLocalTransportDescription_w(channel);
+  }
+
+  virtual bool NegotiateTransportDescription_w(ContentAction local_role) {
+    talk_base::SSLFingerprint* local_fp =
+        Base::local_description()->identity_fingerprint.get();
+    talk_base::SSLFingerprint* remote_fp =
+        Base::remote_description()->identity_fingerprint.get();
+
+    if (remote_fp && local_fp) {
+      remote_fingerprint_.reset(new talk_base::SSLFingerprint(*remote_fp));
+    } else if (local_fp && (local_role == CA_ANSWER)) {
+      LOG(LS_ERROR)
+          << "Local fingerprint supplied when caller didn't offer DTLS";
+      return false;
+    } else {
+      // We are not doing DTLS
+      remote_fingerprint_.reset(new talk_base::SSLFingerprint(
+          "", NULL, 0));
+    }
+
+    // Now run the negotiation for the base class.
+    return Base::NegotiateTransportDescription_w(local_role);
+  }
+
+  virtual DtlsTransportChannelWrapper* CreateTransportChannel(int component) {
+    return new DtlsTransportChannelWrapper(
+        this, Base::CreateTransportChannel(component));
+  }
+
+  virtual void DestroyTransportChannel(TransportChannelImpl* channel) {
+    // Kind of ugly, but this lets us do the exact inverse of the create.
+    DtlsTransportChannelWrapper* dtls_channel =
+        static_cast<DtlsTransportChannelWrapper*>(channel);
+    TransportChannelImpl* base_channel = dtls_channel->channel();
+    delete dtls_channel;
+    Base::DestroyTransportChannel(base_channel);
+  }
+
+ private:
+  virtual void ApplyNegotiatedTransportDescription_w(
+      TransportChannelImpl* channel) {
+    channel->SetRemoteFingerprint(
+        remote_fingerprint_->algorithm,
+        reinterpret_cast<const uint8 *>(remote_fingerprint_->
+                                    digest.data()),
+        remote_fingerprint_->digest.length());
+    Base::ApplyNegotiatedTransportDescription_w(channel);
+  }
+
+  talk_base::SSLIdentity* identity_;
+  talk_base::scoped_ptr<talk_base::SSLFingerprint> remote_fingerprint_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_DTLSTRANSPORT_H_
diff --git a/talk/p2p/base/dtlstransportchannel.cc b/talk/p2p/base/dtlstransportchannel.cc
new file mode 100644
index 0000000..9d6b92e
--- /dev/null
+++ b/talk/p2p/base/dtlstransportchannel.cc
@@ -0,0 +1,563 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ * 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 "talk/p2p/base/dtlstransportchannel.h"
+
+#include "talk/base/buffer.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/stream.h"
+#include "talk/base/sslstreamadapter.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/common.h"
+
+namespace cricket {
+
+// We don't pull the RTP constants from rtputils.h, to avoid a layer violation.
+static const size_t kDtlsRecordHeaderLen = 13;
+static const size_t kMaxDtlsPacketLen = 2048;
+static const size_t kMinRtpPacketLen = 12;
+
+static bool IsDtlsPacket(const char* data, size_t len) {
+  const uint8* u = reinterpret_cast<const uint8*>(data);
+  return (len >= kDtlsRecordHeaderLen && (u[0] > 19 && u[0] < 64));
+}
+static bool IsRtpPacket(const char* data, size_t len) {
+  const uint8* u = reinterpret_cast<const uint8*>(data);
+  return (len >= kMinRtpPacketLen && (u[0] & 0xC0) == 0x80);
+}
+
+talk_base::StreamResult StreamInterfaceChannel::Read(void* buffer,
+                                                     size_t buffer_len,
+                                                     size_t* read,
+                                                     int* error) {
+  if (state_ == talk_base::SS_CLOSED)
+    return talk_base::SR_EOS;
+  if (state_ == talk_base::SS_OPENING)
+    return talk_base::SR_BLOCK;
+
+  return fifo_.Read(buffer, buffer_len, read, error);
+}
+
+talk_base::StreamResult StreamInterfaceChannel::Write(const void* data,
+                                                      size_t data_len,
+                                                      size_t* written,
+                                                      int* error) {
+  // Always succeeds, since this is an unreliable transport anyway.
+  // TODO: Should this block if channel_'s temporarily unwritable?
+  channel_->SendPacket(static_cast<const char*>(data), data_len);
+  if (written) {
+    *written = data_len;
+  }
+  return talk_base::SR_SUCCESS;
+}
+
+bool StreamInterfaceChannel::OnPacketReceived(const char* data, size_t size) {
+  // We force a read event here to ensure that we don't overflow our FIFO.
+  // Under high packet rate this can occur if we wait for the FIFO to post its
+  // own SE_READ.
+  bool ret = (fifo_.WriteAll(data, size, NULL, NULL) == talk_base::SR_SUCCESS);
+  if (ret) {
+    SignalEvent(this, talk_base::SE_READ, 0);
+  }
+  return ret;
+}
+
+void StreamInterfaceChannel::OnEvent(talk_base::StreamInterface* stream,
+                                     int sig, int err) {
+  SignalEvent(this, sig, err);
+}
+
+DtlsTransportChannelWrapper::DtlsTransportChannelWrapper(
+                                           Transport* transport,
+                                           TransportChannelImpl* channel)
+    : TransportChannelImpl(channel->content_name(), channel->component()),
+      transport_(transport),
+      worker_thread_(talk_base::Thread::Current()),
+      channel_(channel),
+      downward_(NULL),
+      dtls_state_(STATE_NONE),
+      local_identity_(NULL),
+      dtls_role_(talk_base::SSL_CLIENT) {
+  channel_->SignalReadableState.connect(this,
+      &DtlsTransportChannelWrapper::OnReadableState);
+  channel_->SignalWritableState.connect(this,
+      &DtlsTransportChannelWrapper::OnWritableState);
+  channel_->SignalReadPacket.connect(this,
+      &DtlsTransportChannelWrapper::OnReadPacket);
+  channel_->SignalReadyToSend.connect(this,
+      &DtlsTransportChannelWrapper::OnReadyToSend);
+  channel_->SignalRequestSignaling.connect(this,
+      &DtlsTransportChannelWrapper::OnRequestSignaling);
+  channel_->SignalCandidateReady.connect(this,
+      &DtlsTransportChannelWrapper::OnCandidateReady);
+  channel_->SignalCandidatesAllocationDone.connect(this,
+      &DtlsTransportChannelWrapper::OnCandidatesAllocationDone);
+  channel_->SignalRoleConflict.connect(this,
+      &DtlsTransportChannelWrapper::OnRoleConflict);
+  channel_->SignalRouteChange.connect(this,
+      &DtlsTransportChannelWrapper::OnRouteChange);
+}
+
+DtlsTransportChannelWrapper::~DtlsTransportChannelWrapper() {
+}
+
+void DtlsTransportChannelWrapper::Connect() {
+  // We should only get a single call to Connect.
+  ASSERT(dtls_state_ == STATE_NONE ||
+         dtls_state_ == STATE_OFFERED ||
+         dtls_state_ == STATE_ACCEPTED);
+  channel_->Connect();
+}
+
+void DtlsTransportChannelWrapper::Reset() {
+  channel_->Reset();
+  set_writable(false);
+  set_readable(false);
+
+  // Re-call SetupDtls()
+  if (!SetupDtls()) {
+    LOG_J(LS_ERROR, this) << "Error re-initializing DTLS";
+    dtls_state_ = STATE_CLOSED;
+    return;
+  }
+
+  dtls_state_ = STATE_ACCEPTED;
+}
+
+bool DtlsTransportChannelWrapper::SetLocalIdentity(talk_base::SSLIdentity*
+                                                   identity) {
+  // TODO(ekr@rtfm.com): Forbid this if Connect() has been called.
+  if (dtls_state_ != STATE_NONE) {
+    LOG_J(LS_ERROR, this) << "Can't set DTLS local identity in this state";
+    return false;
+  }
+
+  if (identity) {
+    local_identity_ = identity;
+    dtls_state_ = STATE_OFFERED;
+  } else {
+    LOG_J(LS_INFO, this) << "NULL DTLS identity supplied. Not doing DTLS";
+  }
+
+  return true;
+}
+
+void DtlsTransportChannelWrapper::SetRole(TransportRole role) {
+  // TODO(ekr@rtfm.com): Forbid this if Connect() has been called.
+  ASSERT(dtls_state_ < STATE_ACCEPTED);
+
+  // Set the role that is most conformant with RFC 5763, Section 5, bullet 1:
+  //     The endpoint that is the offerer MUST [...] be prepared to receive
+  //     a client_hello before it receives the answer.
+  // (IOW, the offerer is the server, and the answerer is the client).
+  dtls_role_ = (role == ROLE_CONTROLLING) ?
+      talk_base::SSL_SERVER : talk_base::SSL_CLIENT;
+
+  channel_->SetRole(role);
+}
+
+bool DtlsTransportChannelWrapper::SetRemoteFingerprint(const std::string&
+                                                       digest_alg,
+                                                       const uint8* digest,
+                                                       size_t digest_len) {
+  // Allow SetRemoteFingerprint with a NULL digest even if SetLocalIdentity
+  // hasn't been called.
+  if (dtls_state_ > STATE_OFFERED ||
+      (dtls_state_ == STATE_NONE && !digest_alg.empty())) {
+    LOG_J(LS_ERROR, this) << "Can't set DTLS remote settings in this state";
+    return false;
+  }
+
+  if (digest_alg.empty()) {
+    LOG_J(LS_INFO, this) << "Other side didn't support DTLS";
+    dtls_state_ = STATE_NONE;
+    return true;
+  }
+
+  // At this point we know we are doing DTLS
+  remote_fingerprint_value_.SetData(digest, digest_len);
+  remote_fingerprint_algorithm_ = digest_alg;
+
+  if (!SetupDtls()) {
+    dtls_state_ = STATE_CLOSED;
+    return false;
+  }
+
+  dtls_state_ = STATE_ACCEPTED;
+  return true;
+}
+
+bool DtlsTransportChannelWrapper::SetupDtls() {
+  StreamInterfaceChannel* downward =
+      new StreamInterfaceChannel(worker_thread_, channel_);
+
+  dtls_.reset(talk_base::SSLStreamAdapter::Create(downward));
+  if (!dtls_) {
+    LOG_J(LS_ERROR, this) << "Failed to create DTLS adapter";
+    delete downward;
+    return false;
+  }
+
+  downward_ = downward;
+
+  dtls_->SetIdentity(local_identity_->GetReference());
+  dtls_->SetMode(talk_base::SSL_MODE_DTLS);
+  dtls_->SetServerRole(dtls_role_);
+  dtls_->SignalEvent.connect(this, &DtlsTransportChannelWrapper::OnDtlsEvent);
+  if (!dtls_->SetPeerCertificateDigest(
+          remote_fingerprint_algorithm_,
+          reinterpret_cast<unsigned char *>(remote_fingerprint_value_.data()),
+          remote_fingerprint_value_.length())) {
+    LOG_J(LS_ERROR, this) << "Couldn't set DTLS certificate digest";
+    return false;
+  }
+
+  // Set up DTLS-SRTP, if it's been enabled.
+  if (!srtp_ciphers_.empty()) {
+    if (!dtls_->SetDtlsSrtpCiphers(srtp_ciphers_)) {
+      LOG_J(LS_ERROR, this) << "Couldn't set DTLS-SRTP ciphers";
+      return false;
+    }
+  } else {
+    LOG_J(LS_INFO, this) << "Not using DTLS";
+  }
+
+  LOG_J(LS_INFO, this) << "DTLS setup complete";
+  return true;
+}
+
+bool DtlsTransportChannelWrapper::SetSrtpCiphers(const std::vector<std::string>&
+                                                 ciphers) {
+  // SRTP ciphers must be set before the DTLS handshake starts.
+  // TODO(juberti): In multiplex situations, we may end up calling this function
+  // once for each muxed channel. Depending on the order of calls, this may
+  // result in slightly undesired results, e.g. 32 vs 80-bit MAC. The right way to
+  // fix this would be for the TransportProxyChannels to intersect the ciphers
+  // instead of overwriting, so that "80" followed by "32, 80" results in "80".
+  if (dtls_state_ != STATE_NONE &&
+      dtls_state_ != STATE_OFFERED &&
+      dtls_state_ != STATE_ACCEPTED) {
+    ASSERT(false);
+    return false;
+  }
+
+  srtp_ciphers_ = ciphers;
+  return true;
+}
+
+bool DtlsTransportChannelWrapper::GetSrtpCipher(std::string* cipher) {
+  if (dtls_state_ != STATE_OPEN) {
+    return false;
+  }
+
+  return dtls_->GetDtlsSrtpCipher(cipher);
+}
+
+
+// Called from upper layers to send a media packet.
+int DtlsTransportChannelWrapper::SendPacket(const char* data, size_t size,
+                                            int flags) {
+  int result = -1;
+
+  switch (dtls_state_) {
+    case STATE_OFFERED:
+      // We don't know if we are doing DTLS yet, so we can't send a packet.
+      // TODO(ekr@rtfm.com): assert here?
+      result = -1;
+      break;
+
+    case STATE_STARTED:
+    case STATE_ACCEPTED:
+      // Can't send data until the connection is active
+      result = -1;
+      break;
+
+    case STATE_OPEN:
+      if (flags & PF_SRTP_BYPASS) {
+        ASSERT(!srtp_ciphers_.empty());
+        if (!IsRtpPacket(data, size)) {
+          result = false;
+          break;
+        }
+
+        result = channel_->SendPacket(data, size);
+      } else {
+        result = (dtls_->WriteAll(data, size, NULL, NULL) ==
+          talk_base::SR_SUCCESS) ? static_cast<int>(size) : -1;
+      }
+      break;
+      // Not doing DTLS.
+    case STATE_NONE:
+      result = channel_->SendPacket(data, size);
+      break;
+
+    case STATE_CLOSED:  // Can't send anything when we're closed.
+      return -1;
+  }
+
+  return result;
+}
+
+// The state transition logic here is as follows:
+// (1) If we're not doing DTLS-SRTP, then the state is just the
+//     state of the underlying impl()
+// (2) If we're doing DTLS-SRTP:
+//     - Prior to the DTLS handshake, the state is neither readable or
+//       writable
+//     - When the impl goes writable for the first time we
+//       start the DTLS handshake
+//     - Once the DTLS handshake completes, the state is that of the
+//       impl again
+void DtlsTransportChannelWrapper::OnReadableState(TransportChannel* channel) {
+  ASSERT(talk_base::Thread::Current() == worker_thread_);
+  ASSERT(channel == channel_);
+  LOG_J(LS_VERBOSE, this)
+      << "DTLSTransportChannelWrapper: channel readable state changed";
+
+  if (dtls_state_ == STATE_NONE || dtls_state_ == STATE_OPEN) {
+    set_readable(channel_->readable());
+    // Note: SignalReadableState fired by set_readable.
+  }
+}
+
+void DtlsTransportChannelWrapper::OnWritableState(TransportChannel* channel) {
+  ASSERT(talk_base::Thread::Current() == worker_thread_);
+  ASSERT(channel == channel_);
+  LOG_J(LS_VERBOSE, this)
+      << "DTLSTransportChannelWrapper: channel writable state changed";
+
+  switch (dtls_state_) {
+    case STATE_NONE:
+    case STATE_OPEN:
+      set_writable(channel_->writable());
+      // Note: SignalWritableState fired by set_writable.
+      break;
+
+    case STATE_OFFERED:
+      // Do nothing
+      break;
+
+    case STATE_ACCEPTED:
+      if (!MaybeStartDtls()) {
+        // This should never happen:
+        // Because we are operating in a nonblocking mode and all
+        // incoming packets come in via OnReadPacket(), which rejects
+        // packets in this state, the incoming queue must be empty. We
+        // ignore write errors, thus any errors must be because of
+        // configuration and therefore are our fault.
+        // Note that in non-debug configurations, failure in
+        // MaybeStartDtls() changes the state to STATE_CLOSED.
+        ASSERT(false);
+      }
+      break;
+
+    case STATE_STARTED:
+      // Do nothing
+      break;
+
+    case STATE_CLOSED:
+      // Should not happen. Do nothing
+      break;
+  }
+}
+
+void DtlsTransportChannelWrapper::OnReadPacket(TransportChannel* channel,
+                                               const char* data, size_t size,
+                                               int flags) {
+  ASSERT(talk_base::Thread::Current() == worker_thread_);
+  ASSERT(channel == channel_);
+  ASSERT(flags == 0);
+
+  switch (dtls_state_) {
+    case STATE_NONE:
+      // We are not doing DTLS
+      SignalReadPacket(this, data, size, 0);
+      break;
+
+    case STATE_OFFERED:
+      // Currently drop the packet, but we might in future
+      // decide to take this as evidence that the other
+      // side is ready to do DTLS and start the handshake
+      // on our end
+      LOG_J(LS_WARNING, this) << "Received packet before we know if we are doing "
+                              << "DTLS or not; dropping";
+      break;
+
+    case STATE_ACCEPTED:
+      // Drop packets received before DTLS has actually started
+      LOG_J(LS_INFO, this) << "Dropping packet received before DTLS started";
+      break;
+
+    case STATE_STARTED:
+    case STATE_OPEN:
+      // We should only get DTLS or SRTP packets; STUN's already been demuxed.
+      // Is this potentially a DTLS packet?
+      if (IsDtlsPacket(data, size)) {
+        if (!HandleDtlsPacket(data, size)) {
+          LOG_J(LS_ERROR, this) << "Failed to handle DTLS packet";
+          return;
+        }
+      } else {
+        // Not a DTLS packet; our handshake should be complete by now.
+        if (dtls_state_ != STATE_OPEN) {
+          LOG_J(LS_ERROR, this) << "Received non-DTLS packet before DTLS complete";
+          return;
+        }
+
+        // And it had better be a SRTP packet.
+        if (!IsRtpPacket(data, size)) {
+          LOG_J(LS_ERROR, this) << "Received unexpected non-DTLS packet";
+          return;
+        }
+
+        // Sanity check.
+        ASSERT(!srtp_ciphers_.empty());
+
+        // Signal this upwards as a bypass packet.
+        SignalReadPacket(this, data, size, PF_SRTP_BYPASS);
+      }
+      break;
+    case STATE_CLOSED:
+      // This shouldn't be happening. Drop the packet
+      break;
+  }
+}
+
+void DtlsTransportChannelWrapper::OnReadyToSend(TransportChannel* channel) {
+  if (writable()) {
+    SignalReadyToSend(this);
+  }
+}
+
+void DtlsTransportChannelWrapper::OnDtlsEvent(talk_base::StreamInterface* dtls,
+                                              int sig, int err) {
+  ASSERT(talk_base::Thread::Current() == worker_thread_);
+  ASSERT(dtls == dtls_.get());
+  if (sig & talk_base::SE_OPEN) {
+    // This is the first time.
+    LOG_J(LS_INFO, this) << "DTLS handshake complete";
+    if (dtls_->GetState() == talk_base::SS_OPEN) {
+      // The check for OPEN shouldn't be necessary but let's make
+      // sure we don't accidentally frob the state if it's closed.
+      dtls_state_ = STATE_OPEN;
+
+      set_readable(true);
+      set_writable(true);
+    }
+  }
+  if (sig & talk_base::SE_READ) {
+    char buf[kMaxDtlsPacketLen];
+    size_t read;
+    if (dtls_->Read(buf, sizeof(buf), &read, NULL) == talk_base::SR_SUCCESS) {
+      SignalReadPacket(this, buf, read, 0);
+    }
+  }
+  if (sig & talk_base::SE_CLOSE) {
+    ASSERT(sig == talk_base::SE_CLOSE);  // SE_CLOSE should be by itself.
+    if (!err) {
+      LOG_J(LS_INFO, this) << "DTLS channel closed";
+    } else {
+      LOG_J(LS_INFO, this) << "DTLS channel error, code=" << err;
+    }
+
+    set_readable(false);
+    set_writable(false);
+    dtls_state_ = STATE_CLOSED;
+  }
+}
+
+bool DtlsTransportChannelWrapper::MaybeStartDtls() {
+  if (channel_->writable()) {
+    if (dtls_->StartSSLWithPeer()) {
+      LOG_J(LS_ERROR, this) << "Couldn't start DTLS handshake";
+      dtls_state_ = STATE_CLOSED;
+      return false;
+    }
+    LOG_J(LS_INFO, this)
+      << "DtlsTransportChannelWrapper: Started DTLS handshake";
+
+    dtls_state_ = STATE_STARTED;
+  }
+  return true;
+}
+
+// Called from OnReadPacket when a DTLS packet is received.
+bool DtlsTransportChannelWrapper::HandleDtlsPacket(const char* data,
+                                                   size_t size) {
+  // Sanity check we're not passing junk that
+  // just looks like DTLS.
+  const uint8* tmp_data = reinterpret_cast<const uint8* >(data);
+  size_t tmp_size = size;
+  while (tmp_size > 0) {
+    if (tmp_size < kDtlsRecordHeaderLen)
+      return false;  // Too short for the header
+
+    size_t record_len = (tmp_data[11] << 8) | (tmp_data[12]);
+    if ((record_len + kDtlsRecordHeaderLen) > tmp_size)
+      return false;  // Body too short
+
+    tmp_data += record_len + kDtlsRecordHeaderLen;
+    tmp_size -= record_len + kDtlsRecordHeaderLen;
+  }
+
+  // Looks good. Pass to the SIC which ends up being passed to
+  // the DTLS stack.
+  return downward_->OnPacketReceived(data, size);
+}
+
+void DtlsTransportChannelWrapper::OnRequestSignaling(
+    TransportChannelImpl* channel) {
+  ASSERT(channel == channel_);
+  SignalRequestSignaling(this);
+}
+
+void DtlsTransportChannelWrapper::OnCandidateReady(
+    TransportChannelImpl* channel, const Candidate& c) {
+  ASSERT(channel == channel_);
+  SignalCandidateReady(this, c);
+}
+
+void DtlsTransportChannelWrapper::OnCandidatesAllocationDone(
+    TransportChannelImpl* channel) {
+  ASSERT(channel == channel_);
+  SignalCandidatesAllocationDone(this);
+}
+
+void DtlsTransportChannelWrapper::OnRoleConflict(
+    TransportChannelImpl* channel) {
+  ASSERT(channel == channel_);
+  SignalRoleConflict(this);
+}
+
+void DtlsTransportChannelWrapper::OnRouteChange(
+    TransportChannel* channel, const Candidate& candidate) {
+  ASSERT(channel == channel_);
+  SignalRouteChange(this, candidate);
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/dtlstransportchannel.h b/talk/p2p/base/dtlstransportchannel.h
new file mode 100644
index 0000000..395df9b
--- /dev/null
+++ b/talk/p2p/base/dtlstransportchannel.h
@@ -0,0 +1,249 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ * 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.
+ */
+
+#ifndef TALK_P2P_BASE_DTLSTRANSPORTCHANNEL_H_
+#define TALK_P2P_BASE_DTLSTRANSPORTCHANNEL_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/buffer.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sslstreamadapter.h"
+#include "talk/base/stream.h"
+#include "talk/p2p/base/transportchannelimpl.h"
+
+namespace cricket {
+
+// A bridge between a packet-oriented/channel-type interface on
+// the bottom and a StreamInterface on the top.
+class StreamInterfaceChannel : public talk_base::StreamInterface,
+                               public sigslot::has_slots<> {
+ public:
+  StreamInterfaceChannel(talk_base::Thread* owner, TransportChannel* channel)
+      : channel_(channel),
+        state_(talk_base::SS_OPEN),
+        fifo_(kFifoSize, owner) {
+    fifo_.SignalEvent.connect(this, &StreamInterfaceChannel::OnEvent);
+  }
+
+  // Push in a packet; this gets pulled out from Read().
+  bool OnPacketReceived(const char* data, size_t size);
+
+  // Implementations of StreamInterface
+  virtual talk_base::StreamState GetState() const { return state_; }
+  virtual void Close() { state_ = talk_base::SS_CLOSED; }
+  virtual talk_base::StreamResult Read(void* buffer, size_t buffer_len,
+                                       size_t* read, int* error);
+  virtual talk_base::StreamResult Write(const void* data, size_t data_len,
+                                        size_t* written, int* error);
+
+ private:
+  static const size_t kFifoSize = 8192;
+
+  // Forward events
+  virtual void OnEvent(talk_base::StreamInterface* stream, int sig, int err);
+
+  TransportChannel* channel_;  // owned by DtlsTransportChannelWrapper
+  talk_base::StreamState state_;
+  talk_base::FifoBuffer fifo_;
+
+  DISALLOW_COPY_AND_ASSIGN(StreamInterfaceChannel);
+};
+
+
+// This class provides a DTLS SSLStreamAdapter inside a TransportChannel-style
+// packet-based interface, wrapping an existing TransportChannel instance
+// (e.g a P2PTransportChannel)
+// Here's the way this works:
+//
+//   DtlsTransportChannelWrapper {
+//       SSLStreamAdapter* dtls_ {
+//           StreamInterfaceChannel downward_ {
+//               TransportChannelImpl* channel_;
+//           }
+//       }
+//   }
+//
+//   - Data which comes into DtlsTransportChannelWrapper from the underlying
+//     channel_ via OnReadPacket() is checked for whether it is DTLS
+//     or not, and if it is, is passed to DtlsTransportChannelWrapper::
+//     HandleDtlsPacket, which pushes it into to downward_.
+//     dtls_ is listening for events on downward_, so it immediately calls
+//     downward_->Read().
+//
+//   - Data written to DtlsTransportChannelWrapper is passed either to
+//      downward_ or directly to channel_, depending on whether DTLS is
+//     negotiated and whether the flags include PF_SRTP_BYPASS
+//
+//   - The SSLStreamAdapter writes to downward_->Write()
+//     which translates it into packet writes on channel_.
+class DtlsTransportChannelWrapper : public TransportChannelImpl {
+ public:
+    enum State {
+      STATE_NONE,      // No state or rejected.
+      STATE_OFFERED,   // Our identity has been set.
+      STATE_ACCEPTED,  // The other side sent a fingerprint.
+      STATE_STARTED,   // We are negotiating.
+      STATE_OPEN,      // Negotiation complete.
+      STATE_CLOSED     // Connection closed.
+    };
+
+  // The parameters here are:
+  // transport -- the DtlsTransport that created us
+  // channel -- the TransportChannel we are wrapping
+  DtlsTransportChannelWrapper(Transport* transport,
+                              TransportChannelImpl* channel);
+  virtual ~DtlsTransportChannelWrapper();
+
+  virtual void SetRole(TransportRole role);
+  // Returns current transport role of the channel.
+  virtual TransportRole GetRole() const {
+    return channel_->GetRole();
+  }
+  virtual bool SetLocalIdentity(talk_base::SSLIdentity *identity);
+
+  virtual bool SetRemoteFingerprint(const std::string& digest_alg,
+                                    const uint8* digest,
+                                    size_t digest_len);
+  virtual bool IsDtlsActive() const { return dtls_state_ != STATE_NONE; }
+
+  // Called to send a packet (via DTLS, if turned on).
+  virtual int SendPacket(const char* data, size_t size, int flags);
+
+  // TransportChannel calls that we forward to the wrapped transport.
+  virtual int SetOption(talk_base::Socket::Option opt, int value) {
+    return channel_->SetOption(opt, value);
+  }
+  virtual int GetError() {
+    return channel_->GetError();
+  }
+  virtual bool GetStats(ConnectionInfos* infos) {
+    return channel_->GetStats(infos);
+  }
+  virtual void SetSessionId(const std::string& session_id) {
+    channel_->SetSessionId(session_id);
+  }
+  virtual const std::string& SessionId() const {
+    return channel_->SessionId();
+  }
+
+  // Set up the ciphers to use for DTLS-SRTP. If this method is not called
+  // before DTLS starts, or |ciphers| is empty, SRTP keys won't be negotiated.
+  // This method should be called before SetupDtls.
+  virtual bool SetSrtpCiphers(const std::vector<std::string>& ciphers);
+
+  // Find out which DTLS-SRTP cipher was negotiated
+  virtual bool GetSrtpCipher(std::string* cipher);
+
+  // Once DTLS has established (i.e., this channel is writable), this method
+  // extracts the keys negotiated during the DTLS handshake, for use in external
+  // encryption. DTLS-SRTP uses this to extract the needed SRTP keys.
+  // See the SSLStreamAdapter documentation for info on the specific parameters.
+  virtual bool ExportKeyingMaterial(const std::string& label,
+                                    const uint8* context,
+                                    size_t context_len,
+                                    bool use_context,
+                                    uint8* result,
+                                    size_t result_len) {
+    return (dtls_.get()) ? dtls_->ExportKeyingMaterial(label, context,
+                                                       context_len,
+                                                       use_context,
+                                                       result, result_len)
+        : false;
+  }
+
+  // TransportChannelImpl calls.
+  virtual Transport* GetTransport() {
+    return transport_;
+  }
+  virtual void SetTiebreaker(uint64 tiebreaker) {
+    channel_->SetTiebreaker(tiebreaker);
+  }
+  virtual void SetIceProtocolType(IceProtocolType type) {
+    channel_->SetIceProtocolType(type);
+  }
+  virtual void SetIceCredentials(const std::string& ice_ufrag,
+                                 const std::string& ice_pwd) {
+    channel_->SetIceCredentials(ice_ufrag, ice_pwd);
+  }
+  virtual void SetRemoteIceCredentials(const std::string& ice_ufrag,
+                                       const std::string& ice_pwd) {
+    channel_->SetRemoteIceCredentials(ice_ufrag, ice_pwd);
+  }
+  virtual void SetRemoteIceMode(IceMode mode) {
+    channel_->SetRemoteIceMode(mode);
+  }
+
+  virtual void Connect();
+  virtual void Reset();
+
+  virtual void OnSignalingReady() {
+    channel_->OnSignalingReady();
+  }
+  virtual void OnCandidate(const Candidate& candidate) {
+    channel_->OnCandidate(candidate);
+  }
+
+  // Needed by DtlsTransport.
+  TransportChannelImpl* channel() { return channel_; }
+
+ private:
+  void OnReadableState(TransportChannel* channel);
+  void OnWritableState(TransportChannel* channel);
+  void OnReadPacket(TransportChannel* channel, const char* data, size_t size,
+                    int flags);
+  void OnReadyToSend(TransportChannel* channel);
+  void OnDtlsEvent(talk_base::StreamInterface* stream_, int sig, int err);
+  bool SetupDtls();
+  bool MaybeStartDtls();
+  bool HandleDtlsPacket(const char* data, size_t size);
+  void OnRequestSignaling(TransportChannelImpl* channel);
+  void OnCandidateReady(TransportChannelImpl* channel, const Candidate& c);
+  void OnCandidatesAllocationDone(TransportChannelImpl* channel);
+  void OnRoleConflict(TransportChannelImpl* channel);
+  void OnRouteChange(TransportChannel* channel, const Candidate& candidate);
+
+  Transport* transport_;  // The transport_ that created us.
+  talk_base::Thread* worker_thread_;  // Everything should occur on this thread.
+  TransportChannelImpl* channel_;  // Underlying channel, owned by transport_.
+  talk_base::scoped_ptr<talk_base::SSLStreamAdapter> dtls_;  // The DTLS stream
+  StreamInterfaceChannel* downward_;  // Wrapper for channel_, owned by dtls_.
+  std::vector<std::string> srtp_ciphers_;  // SRTP ciphers to use with DTLS.
+  State dtls_state_;
+  talk_base::SSLIdentity* local_identity_;
+  talk_base::SSLRole dtls_role_;
+  talk_base::Buffer remote_fingerprint_value_;
+  std::string remote_fingerprint_algorithm_;
+
+  DISALLOW_COPY_AND_ASSIGN(DtlsTransportChannelWrapper);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_DTLSTRANSPORTCHANNEL_H_
diff --git a/talk/p2p/base/dtlstransportchannel_unittest.cc b/talk/p2p/base/dtlstransportchannel_unittest.cc
new file mode 100644
index 0000000..8de3b07
--- /dev/null
+++ b/talk/p2p/base/dtlstransportchannel_unittest.cc
@@ -0,0 +1,564 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ * 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 <set>
+
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/fakesession.h"
+#include "talk/base/ssladapter.h"
+#include "talk/base/sslidentity.h"
+#include "talk/base/sslstreamadapter.h"
+#include "talk/p2p/base/dtlstransport.h"
+
+#define MAYBE_SKIP_TEST(feature)                    \
+  if (!(talk_base::SSLStreamAdapter::feature())) {  \
+    LOG(LS_INFO) << "Feature disabled... skipping"; \
+    return;                                         \
+  }
+
+static const char AES_CM_128_HMAC_SHA1_80[] = "AES_CM_128_HMAC_SHA1_80";
+static const char kIceUfrag1[] = "TESTICEUFRAG0001";
+static const char kIcePwd1[] = "TESTICEPWD00000000000001";
+static const size_t kPacketNumOffset = 8;
+static const size_t kPacketHeaderLen = 12;
+
+static bool IsRtpLeadByte(uint8 b) {
+  return ((b & 0xC0) == 0x80);
+}
+
+class DtlsTestClient : public sigslot::has_slots<> {
+ public:
+  DtlsTestClient(const std::string& name,
+                 talk_base::Thread* signaling_thread,
+                 talk_base::Thread* worker_thread) :
+      name_(name),
+      signaling_thread_(signaling_thread),
+      worker_thread_(worker_thread),
+      protocol_(cricket::ICEPROTO_GOOGLE),
+      packet_size_(0),
+      use_dtls_srtp_(false),
+      negotiated_dtls_(false),
+      received_dtls_client_hello_(false),
+      received_dtls_server_hello_(false) {
+  }
+  void SetIceProtocol(cricket::TransportProtocol proto) {
+    protocol_ = proto;
+  }
+  void CreateIdentity() {
+    identity_.reset(talk_base::SSLIdentity::Generate(name_));
+  }
+  void SetupSrtp() {
+    ASSERT(identity_.get() != NULL);
+    use_dtls_srtp_ = true;
+  }
+  void SetupChannels(int count, cricket::TransportRole role) {
+    transport_.reset(new cricket::DtlsTransport<cricket::FakeTransport>(
+        signaling_thread_, worker_thread_, "dtls content name", NULL,
+        identity_.get()));
+    transport_->SetAsync(true);
+    transport_->SetRole(role);
+    transport_->SetTiebreaker((role == cricket::ROLE_CONTROLLING) ? 1 : 2);
+    transport_->SignalWritableState.connect(this,
+        &DtlsTestClient::OnTransportWritableState);
+
+    for (int i = 0; i < count; ++i) {
+      cricket::DtlsTransportChannelWrapper* channel =
+          static_cast<cricket::DtlsTransportChannelWrapper*>(
+              transport_->CreateChannel(i));
+      ASSERT_TRUE(channel != NULL);
+      channel->SignalWritableState.connect(this,
+        &DtlsTestClient::OnTransportChannelWritableState);
+      channel->SignalReadPacket.connect(this,
+        &DtlsTestClient::OnTransportChannelReadPacket);
+      channels_.push_back(channel);
+
+      // Hook the raw packets so that we can verify they are encrypted.
+      channel->channel()->SignalReadPacket.connect(
+          this, &DtlsTestClient::OnFakeTransportChannelReadPacket);
+    }
+  }
+  cricket::FakeTransportChannel* GetFakeChannel(int component) {
+    cricket::TransportChannelImpl* ch = transport_->GetChannel(component);
+    cricket::DtlsTransportChannelWrapper* wrapper =
+        static_cast<cricket::DtlsTransportChannelWrapper*>(ch);
+    return (wrapper) ?
+        static_cast<cricket::FakeTransportChannel*>(wrapper->channel()) : NULL;
+  }
+
+  // Offer DTLS if we have an identity; pass in a remote fingerprint only if
+  // both sides support DTLS.
+  void Negotiate(DtlsTestClient* peer) {
+    Negotiate(identity_.get(), (identity_) ? peer->identity_.get() : NULL);
+  }
+
+  // Allow any DTLS configuration to be specified (including invalid ones).
+  void Negotiate(talk_base::SSLIdentity* local_identity,
+                 talk_base::SSLIdentity* remote_identity) {
+    talk_base::scoped_ptr<talk_base::SSLFingerprint> local_fingerprint;
+    talk_base::scoped_ptr<talk_base::SSLFingerprint> remote_fingerprint;
+    if (local_identity) {
+      local_fingerprint.reset(talk_base::SSLFingerprint::Create(
+          talk_base::DIGEST_SHA_1, local_identity));
+      ASSERT_TRUE(local_fingerprint.get() != NULL);
+    }
+    if (remote_identity) {
+      remote_fingerprint.reset(talk_base::SSLFingerprint::Create(
+          talk_base::DIGEST_SHA_1, remote_identity));
+      ASSERT_TRUE(remote_fingerprint.get() != NULL);
+    }
+    if (use_dtls_srtp_) {
+      for (std::vector<cricket::DtlsTransportChannelWrapper*>::iterator it =
+           channels_.begin(); it != channels_.end(); ++it) {
+        std::vector<std::string> ciphers;
+        ciphers.push_back(AES_CM_128_HMAC_SHA1_80);
+        ASSERT_TRUE((*it)->SetSrtpCiphers(ciphers));
+      }
+    }
+
+    std::string transport_type = (protocol_ == cricket::ICEPROTO_GOOGLE) ?
+        cricket::NS_GINGLE_P2P : cricket::NS_JINGLE_ICE_UDP;
+    cricket::TransportDescription local_desc(
+        transport_type, std::vector<std::string>(), kIceUfrag1, kIcePwd1,
+        cricket::ICEMODE_FULL, local_fingerprint.release(),
+        cricket::Candidates());
+    ASSERT_TRUE(transport_->SetLocalTransportDescription(local_desc,
+                                                         cricket::CA_OFFER));
+    cricket::TransportDescription remote_desc(
+        transport_type, std::vector<std::string>(), kIceUfrag1, kIcePwd1,
+        cricket::ICEMODE_FULL, remote_fingerprint.release(),
+        cricket::Candidates());
+    ASSERT_TRUE(transport_->SetRemoteTransportDescription(remote_desc,
+                                                          cricket::CA_ANSWER));
+
+    negotiated_dtls_ = (local_identity && remote_identity);
+  }
+
+  bool Connect(DtlsTestClient* peer) {
+    transport_->ConnectChannels();
+    transport_->SetDestination(peer->transport_.get());
+    return true;
+  }
+
+  bool writable() const { return transport_->writable(); }
+
+  void CheckRole(talk_base::SSLRole role) {
+    if (role == talk_base::SSL_CLIENT) {
+      ASSERT_FALSE(received_dtls_client_hello_);
+      ASSERT_TRUE(received_dtls_server_hello_);
+    } else {
+      ASSERT_TRUE(received_dtls_client_hello_);
+      ASSERT_FALSE(received_dtls_server_hello_);
+    } 
+  }
+  
+  void CheckSrtp(const std::string& expected_cipher) {
+    for (std::vector<cricket::DtlsTransportChannelWrapper*>::iterator it =
+           channels_.begin(); it != channels_.end(); ++it) {
+      std::string cipher;
+
+      bool rv = (*it)->GetSrtpCipher(&cipher);
+      if (negotiated_dtls_ && !expected_cipher.empty()) {
+        ASSERT_TRUE(rv);
+
+        ASSERT_EQ(cipher, expected_cipher);
+      } else {
+        ASSERT_FALSE(rv);
+      }
+    }
+  }
+
+  void SendPackets(size_t channel, size_t size, size_t count, bool srtp) {
+    ASSERT(channel < channels_.size());
+    talk_base::scoped_array<char> packet(new char[size]);
+    size_t sent = 0;
+    do {
+      // Fill the packet with a known value and a sequence number to check
+      // against, and make sure that it doesn't look like DTLS.
+      memset(packet.get(), sent & 0xff, size);
+      packet[0] = (srtp) ? 0x80 : 0x00;
+      talk_base::SetBE32(packet.get() + kPacketNumOffset, sent);
+
+      // Only set the bypass flag if we've activated DTLS.
+      int flags = (identity_.get() && srtp) ? cricket::PF_SRTP_BYPASS : 0;
+      int rv = channels_[channel]->SendPacket(packet.get(), size, flags);
+      ASSERT_GT(rv, 0);
+      ASSERT_EQ(size, static_cast<size_t>(rv));
+      ++sent;
+    } while (sent < count);
+  }
+
+  void ExpectPackets(size_t channel, size_t size) {
+    packet_size_ = size;
+    received_.clear();
+  }
+
+  size_t NumPacketsReceived() {
+    return received_.size();
+  }
+
+  bool VerifyPacket(const char* data, size_t size, uint32* out_num) {
+    if (size != packet_size_ ||
+        (data[0] != 0 && static_cast<uint8>(data[0]) != 0x80)) {
+      return false;
+    }
+    uint32 packet_num = talk_base::GetBE32(data + kPacketNumOffset);
+    for (size_t i = kPacketHeaderLen; i < size; ++i) {
+      if (static_cast<uint8>(data[i]) != (packet_num & 0xff)) {
+        return false;
+      }
+    }
+    if (out_num) {
+      *out_num = packet_num;
+    }
+    return true;
+  }
+  bool VerifyEncryptedPacket(const char* data, size_t size) {
+    // This is an encrypted data packet; let's make sure it's mostly random;
+    // less than 10% of the bytes should be equal to the cleartext packet.
+    if (size <= packet_size_) {
+      return false;
+    }
+    uint32 packet_num = talk_base::GetBE32(data + kPacketNumOffset);
+    int num_matches = 0;
+    for (size_t i = kPacketNumOffset; i < size; ++i) {
+      if (static_cast<uint8>(data[i]) == (packet_num & 0xff)) {
+        ++num_matches;
+      }
+    }
+    return (num_matches < ((static_cast<int>(size) - 5) / 10));
+  }
+
+  // Transport callbacks
+  void OnTransportWritableState(cricket::Transport* transport) {
+    LOG(LS_INFO) << name_ << ": is writable";
+  }
+
+  // Transport channel callbacks
+  void OnTransportChannelWritableState(cricket::TransportChannel* channel) {
+    LOG(LS_INFO) << name_ << ": Channel '" << channel->component()
+                 << "' is writable";
+  }
+
+  void OnTransportChannelReadPacket(cricket::TransportChannel* channel,
+                                    const char* data, size_t size,
+                                    int flags) {
+    uint32 packet_num = 0;
+    ASSERT_TRUE(VerifyPacket(data, size, &packet_num));
+    received_.insert(packet_num);
+    // Only DTLS-SRTP packets should have the bypass flag set.
+    int expected_flags = (identity_.get() && IsRtpLeadByte(data[0])) ?
+        cricket::PF_SRTP_BYPASS : 0;
+    ASSERT_EQ(expected_flags, flags);
+  }
+
+  // Hook into the raw packet stream to make sure DTLS packets are encrypted.
+  void OnFakeTransportChannelReadPacket(cricket::TransportChannel* channel,
+                                        const char* data, size_t size,
+                                        int flags) {
+    // Flags shouldn't be set on the underlying TransportChannel packets.
+    ASSERT_EQ(0, flags);
+
+    // Look at the handshake packets to see what role we played.
+    // Check that non-handshake packets are DTLS data or SRTP bypass.
+    if (negotiated_dtls_) {
+      if (data[0] == 22 && size > 17) {
+        if (data[13] == 1) {
+          received_dtls_client_hello_ = true;
+        } else if (data[13] == 2) {
+          received_dtls_server_hello_ = true;
+        }
+      } else if (!(data[0] >= 20 && data[0] <= 22)) {
+        ASSERT_TRUE(data[0] == 23 || IsRtpLeadByte(data[0]));
+        if (data[0] == 23) {
+          ASSERT_TRUE(VerifyEncryptedPacket(data, size));
+        } else if (IsRtpLeadByte(data[0])) {
+          ASSERT_TRUE(VerifyPacket(data, size, NULL));
+        }
+      }
+    } 
+  }
+
+ private:
+  std::string name_;
+  talk_base::Thread* signaling_thread_;
+  talk_base::Thread* worker_thread_;
+  cricket::TransportProtocol protocol_;
+  talk_base::scoped_ptr<talk_base::SSLIdentity> identity_;
+  talk_base::scoped_ptr<cricket::FakeTransport> transport_;
+  std::vector<cricket::DtlsTransportChannelWrapper*> channels_;
+  size_t packet_size_;
+  std::set<int> received_;
+  bool use_dtls_srtp_;
+  bool negotiated_dtls_;
+  bool received_dtls_client_hello_;
+  bool received_dtls_server_hello_;
+};
+
+
+class DtlsTransportChannelTest : public testing::Test {
+ public:
+  static void SetUpTestCase() {
+    talk_base::InitializeSSL();
+  }
+
+  DtlsTransportChannelTest() :
+      client1_("P1", talk_base::Thread::Current(),
+               talk_base::Thread::Current()),
+      client2_("P2", talk_base::Thread::Current(),
+               talk_base::Thread::Current()),
+      channel_ct_(1),
+      use_dtls_(false),
+      use_dtls_srtp_(false) {
+  }
+
+  void SetChannelCount(size_t channel_ct) {
+    channel_ct_ = channel_ct;
+  }
+  void PrepareDtls(bool c1, bool c2) {
+    if (c1) {
+      client1_.CreateIdentity();
+    }
+    if (c2) {
+      client2_.CreateIdentity();
+    }
+    if (c1 && c2)
+      use_dtls_ = true;
+  }
+  void PrepareDtlsSrtp(bool c1, bool c2) {
+    if (!use_dtls_)
+      return;
+
+    if (c1)
+      client1_.SetupSrtp();
+    if (c2)
+      client2_.SetupSrtp();
+
+    if (c1 && c2)
+      use_dtls_srtp_ = true;
+  }
+
+  bool Connect() {
+    Negotiate();
+
+    bool rv = client1_.Connect(&client2_);
+    EXPECT_TRUE(rv);
+    if (!rv)
+      return false;
+
+    EXPECT_TRUE_WAIT(client1_.writable() && client2_.writable(), 10000);
+    if (!client1_.writable() || !client2_.writable())
+      return false;
+
+    // Check that we used the right roles.
+    if (use_dtls_) {
+      client1_.CheckRole(talk_base::SSL_SERVER);
+      client2_.CheckRole(talk_base::SSL_CLIENT);
+    }
+
+    // Check that we negotiated the right ciphers.
+    if (use_dtls_srtp_) {
+      client1_.CheckSrtp(AES_CM_128_HMAC_SHA1_80);
+      client2_.CheckSrtp(AES_CM_128_HMAC_SHA1_80);
+    } else {
+      client1_.CheckSrtp("");
+      client2_.CheckSrtp("");
+    }
+
+    return true;
+  }
+  void Negotiate() {
+    client1_.SetupChannels(channel_ct_, cricket::ROLE_CONTROLLING);
+    client2_.SetupChannels(channel_ct_, cricket::ROLE_CONTROLLED);
+    client2_.Negotiate(&client1_);
+    client1_.Negotiate(&client2_);
+  }
+
+  void TestTransfer(size_t channel, size_t size, size_t count, bool srtp) {
+    LOG(LS_INFO) << "Expect packets, size=" << size;
+    client2_.ExpectPackets(channel, size);
+    client1_.SendPackets(channel, size, count, srtp);
+    EXPECT_EQ_WAIT(count, client2_.NumPacketsReceived(), 10000);
+  }
+
+ protected:
+  DtlsTestClient client1_;
+  DtlsTestClient client2_;
+  int channel_ct_;
+  bool use_dtls_;
+  bool use_dtls_srtp_;
+};
+
+// Test that transport negotiation of ICE, no DTLS works properly.
+TEST_F(DtlsTransportChannelTest, TestChannelSetupIce) {
+  client1_.SetIceProtocol(cricket::ICEPROTO_RFC5245);
+  client2_.SetIceProtocol(cricket::ICEPROTO_RFC5245);
+  Negotiate();
+  cricket::FakeTransportChannel* channel1 = client1_.GetFakeChannel(0);
+  cricket::FakeTransportChannel* channel2 = client2_.GetFakeChannel(0);
+  ASSERT_TRUE(channel1 != NULL);
+  ASSERT_TRUE(channel2 != NULL);
+  EXPECT_EQ(cricket::ROLE_CONTROLLING, channel1->GetRole());
+  EXPECT_EQ(1U, channel1->tiebreaker());
+  EXPECT_EQ(cricket::ICEPROTO_RFC5245, channel1->protocol());
+  EXPECT_EQ(kIceUfrag1, channel1->ice_ufrag());
+  EXPECT_EQ(kIcePwd1, channel1->ice_pwd());
+  EXPECT_EQ(cricket::ROLE_CONTROLLED, channel2->GetRole());
+  EXPECT_EQ(2U, channel2->tiebreaker());
+  EXPECT_EQ(cricket::ICEPROTO_RFC5245, channel2->protocol());
+}
+
+// Test that transport negotiation of GICE, no DTLS works properly.
+TEST_F(DtlsTransportChannelTest, TestChannelSetupGice) {
+  client1_.SetIceProtocol(cricket::ICEPROTO_GOOGLE);
+  client2_.SetIceProtocol(cricket::ICEPROTO_GOOGLE);
+  Negotiate();
+  cricket::FakeTransportChannel* channel1 = client1_.GetFakeChannel(0);
+  cricket::FakeTransportChannel* channel2 = client2_.GetFakeChannel(0);
+  ASSERT_TRUE(channel1 != NULL);
+  ASSERT_TRUE(channel2 != NULL);
+  EXPECT_EQ(cricket::ROLE_CONTROLLING, channel1->GetRole());
+  EXPECT_EQ(1U, channel1->tiebreaker());
+  EXPECT_EQ(cricket::ICEPROTO_GOOGLE, channel1->protocol());
+  EXPECT_EQ(kIceUfrag1, channel1->ice_ufrag());
+  EXPECT_EQ(kIcePwd1, channel1->ice_pwd());
+  EXPECT_EQ(cricket::ROLE_CONTROLLED, channel2->GetRole());
+  EXPECT_EQ(2U, channel2->tiebreaker());
+  EXPECT_EQ(cricket::ICEPROTO_GOOGLE, channel2->protocol());
+}
+
+// Connect without DTLS, and transfer some data.
+TEST_F(DtlsTransportChannelTest, TestTransfer) {
+  ASSERT_TRUE(Connect());
+  TestTransfer(0, 1000, 100, false);
+}
+
+// Create two channels without DTLS, and transfer some data.
+TEST_F(DtlsTransportChannelTest, TestTransferTwoChannels) {
+  SetChannelCount(2);
+  ASSERT_TRUE(Connect());
+  TestTransfer(0, 1000, 100, false);
+  TestTransfer(1, 1000, 100, false);
+}
+
+// Connect without DTLS, and transfer SRTP data.
+TEST_F(DtlsTransportChannelTest, TestTransferSrtp) {
+  ASSERT_TRUE(Connect());
+  TestTransfer(0, 1000, 100, true);
+}
+
+// Create two channels without DTLS, and transfer SRTP data.
+TEST_F(DtlsTransportChannelTest, TestTransferSrtpTwoChannels) {
+  SetChannelCount(2);
+  ASSERT_TRUE(Connect());
+  TestTransfer(0, 1000, 100, true);
+  TestTransfer(1, 1000, 100, true);
+}
+
+// Connect with DTLS, and transfer some data.
+TEST_F(DtlsTransportChannelTest, TestTransferDtls) {
+  MAYBE_SKIP_TEST(HaveDtls);
+  PrepareDtls(true, true);
+  ASSERT_TRUE(Connect());
+  TestTransfer(0, 1000, 100, false);
+}
+
+// Create two channels with DTLS, and transfer some data.
+TEST_F(DtlsTransportChannelTest, TestTransferDtlsTwoChannels) {
+  MAYBE_SKIP_TEST(HaveDtls);
+  SetChannelCount(2);
+  PrepareDtls(true, true);
+  ASSERT_TRUE(Connect());
+  TestTransfer(0, 1000, 100, false);
+  TestTransfer(1, 1000, 100, false);
+}
+
+// Connect with A doing DTLS and B not, and transfer some data.
+TEST_F(DtlsTransportChannelTest, TestTransferDtlsRejected) {
+  PrepareDtls(true, false);
+  ASSERT_TRUE(Connect());
+  TestTransfer(0, 1000, 100, false);
+}
+
+// Connect with B doing DTLS and A not, and transfer some data.
+TEST_F(DtlsTransportChannelTest, TestTransferDtlsNotOffered) {
+  PrepareDtls(false, true);
+  ASSERT_TRUE(Connect());
+  TestTransfer(0, 1000, 100, false);
+}
+
+// Connect with DTLS, negotiate DTLS-SRTP, and transfer SRTP using bypass.
+TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtp) {
+  MAYBE_SKIP_TEST(HaveDtlsSrtp);
+  PrepareDtls(true, true);
+  PrepareDtlsSrtp(true, true);
+  ASSERT_TRUE(Connect());
+  TestTransfer(0, 1000, 100, true);
+}
+
+
+// Connect with DTLS. A does DTLS-SRTP but B does not.
+TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtpRejected) {
+  MAYBE_SKIP_TEST(HaveDtlsSrtp);
+  PrepareDtls(true, true);
+  PrepareDtlsSrtp(true, false);
+  ASSERT_TRUE(Connect());
+}
+
+// Connect with DTLS. B does DTLS-SRTP but A does not.
+TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtpNotOffered) {
+  MAYBE_SKIP_TEST(HaveDtlsSrtp);
+  PrepareDtls(true, true);
+  PrepareDtlsSrtp(false, true);
+  ASSERT_TRUE(Connect());
+}
+
+// Create two channels with DTLS, negotiate DTLS-SRTP, and transfer bypass SRTP.
+TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtpTwoChannels) {
+  MAYBE_SKIP_TEST(HaveDtlsSrtp);
+  SetChannelCount(2);
+  PrepareDtls(true, true);
+  PrepareDtlsSrtp(true, true);
+  ASSERT_TRUE(Connect());
+  TestTransfer(0, 1000, 100, true);
+  TestTransfer(1, 1000, 100, true);
+}
+
+// Create a single channel with DTLS, and send normal data and SRTP data on it.
+TEST_F(DtlsTransportChannelTest, TestTransferDtlsSrtpDemux) {
+  MAYBE_SKIP_TEST(HaveDtlsSrtp);
+  PrepareDtls(true, true);
+  PrepareDtlsSrtp(true, true);
+  ASSERT_TRUE(Connect());
+  TestTransfer(0, 1000, 100, false);
+  TestTransfer(0, 1000, 100, true);
+}
diff --git a/talk/p2p/base/fakesession.h b/talk/p2p/base/fakesession.h
new file mode 100644
index 0000000..3a825dd
--- /dev/null
+++ b/talk/p2p/base/fakesession.h
@@ -0,0 +1,445 @@
+/*
+ * 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_P2P_BASE_FAKESESSION_H_
+#define TALK_P2P_BASE_FAKESESSION_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "talk/base/buffer.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/sslfingerprint.h"
+#include "talk/base/messagequeue.h"
+#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;
+
+struct PacketMessageData : public talk_base::MessageData {
+  PacketMessageData(const char* data, size_t len) : packet(data, len) {
+  }
+  talk_base::Buffer packet;
+};
+
+// 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 talk_base::MessageHandler {
+ public:
+  explicit FakeTransportChannel(Transport* transport,
+                                const std::string& content_name,
+                                int component)
+      : TransportChannelImpl(content_name, component),
+        transport_(transport),
+        dest_(NULL),
+        state_(STATE_INIT),
+        async_(false),
+        identity_(NULL),
+        do_dtls_(false),
+        role_(ROLE_UNKNOWN),
+        tiebreaker_(0),
+        ice_proto_(ICEPROTO_HYBRID),
+        remote_ice_mode_(ICEMODE_FULL),
+        dtls_fingerprint_("", NULL, 0) {
+  }
+  ~FakeTransportChannel() {
+    Reset();
+  }
+
+  uint64 tiebreaker() const { return tiebreaker_; }
+  TransportProtocol protocol() const { return ice_proto_; }
+  IceMode remote_ice_mode() const { return remote_ice_mode_; }
+  const std::string& ice_ufrag() const { return ice_ufrag_; }
+  const std::string& ice_pwd() const { return ice_pwd_; }
+  const std::string& remote_ice_ufrag() const { return remote_ice_ufrag_; }
+  const std::string& remote_ice_pwd() const { return remote_ice_pwd_; }
+  const talk_base::SSLFingerprint& dtls_fingerprint() const {
+    return dtls_fingerprint_;
+  }
+
+  void SetAsync(bool async) {
+    async_ = async;
+  }
+
+  virtual Transport* GetTransport() {
+    return transport_;
+  }
+
+  virtual void SetRole(TransportRole role) { role_ = role; }
+  virtual TransportRole GetRole() const { return role_; }
+  virtual void SetTiebreaker(uint64 tiebreaker) { tiebreaker_ = tiebreaker; }
+  virtual void SetIceProtocolType(IceProtocolType type) { ice_proto_ = type; }
+  virtual void SetIceCredentials(const std::string& ice_ufrag,
+                                 const std::string& ice_pwd) {
+    ice_ufrag_ = ice_ufrag;
+    ice_pwd_ = ice_pwd;
+  }
+  virtual void SetRemoteIceCredentials(const std::string& ice_ufrag,
+                                       const std::string& ice_pwd) {
+    remote_ice_ufrag_ = ice_ufrag;
+    remote_ice_pwd_ = ice_pwd;
+  }
+
+  virtual void SetRemoteIceMode(IceMode mode) { remote_ice_mode_ = mode; }
+  virtual bool SetRemoteFingerprint(const std::string& alg, const uint8* digest,
+                                    size_t digest_len) {
+    dtls_fingerprint_ = talk_base::SSLFingerprint(alg, digest, digest_len);
+    return true;
+  }
+
+  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 SetWritable(bool writable) {
+    set_writable(writable);
+  }
+
+  void SetDestination(FakeTransportChannel* dest) {
+    if (state_ == STATE_CONNECTING && dest) {
+      // This simulates the delivery of candidates.
+      dest_ = dest;
+      dest_->dest_ = this;
+      if (identity_ && dest_->identity_) {
+        do_dtls_ = true;
+        dest_->do_dtls_ = true;
+        NegotiateSrtpCiphers();
+      }
+      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, int flags) {
+    if (state_ != STATE_CONNECTED) {
+      return -1;
+    }
+
+    if (flags != PF_SRTP_BYPASS && flags != 0) {
+      return -1;
+    }
+
+    PacketMessageData* packet = new PacketMessageData(data, len);
+    if (async_) {
+      talk_base::Thread::Current()->Post(this, 0, packet);
+    } else {
+      talk_base::Thread::Current()->Send(this, 0, packet);
+    }
+    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) {
+  }
+
+  virtual void OnMessage(talk_base::Message* msg) {
+    PacketMessageData* data = static_cast<PacketMessageData*>(
+        msg->pdata);
+    dest_->SignalReadPacket(dest_, data->packet.data(),
+                            data->packet.length(), 0);
+    delete data;
+  }
+
+  bool SetLocalIdentity(talk_base::SSLIdentity* identity) {
+    identity_ = identity;
+
+    return true;
+  }
+
+  bool IsDtlsActive() const {
+    return do_dtls_;
+  }
+
+  bool SetSrtpCiphers(const std::vector<std::string>& ciphers) {
+    srtp_ciphers_ = ciphers;
+    return true;
+  }
+
+  virtual bool GetSrtpCipher(std::string* cipher) {
+    if (!chosen_srtp_cipher_.empty()) {
+      *cipher = chosen_srtp_cipher_;
+      return true;
+    }
+    return false;
+  }
+
+  virtual bool ExportKeyingMaterial(const std::string& label,
+                                    const uint8* context,
+                                    size_t context_len,
+                                    bool use_context,
+                                    uint8* result,
+                                    size_t result_len) {
+    if (!chosen_srtp_cipher_.empty()) {
+      memset(result, 0xff, result_len);
+      return true;
+    }
+
+    return false;
+  }
+
+  virtual void NegotiateSrtpCiphers() {
+    for (std::vector<std::string>::const_iterator it1 = srtp_ciphers_.begin();
+        it1 != srtp_ciphers_.end(); ++it1) {
+      for (std::vector<std::string>::const_iterator it2 =
+              dest_->srtp_ciphers_.begin();
+          it2 != dest_->srtp_ciphers_.end(); ++it2) {
+        if (*it1 == *it2) {
+          chosen_srtp_cipher_ = *it1;
+          dest_->chosen_srtp_cipher_ = *it2;
+          return;
+        }
+      }
+    }
+  }
+
+  virtual bool GetStats(ConnectionInfos* infos) OVERRIDE {
+    ConnectionInfo info;
+    infos->clear();
+    infos->push_back(info);
+    return true;
+  }
+
+ private:
+  enum State { STATE_INIT, STATE_CONNECTING, STATE_CONNECTED };
+  Transport* transport_;
+  FakeTransportChannel* dest_;
+  State state_;
+  bool async_;
+  talk_base::SSLIdentity* identity_;
+  bool do_dtls_;
+  std::vector<std::string> srtp_ciphers_;
+  std::string chosen_srtp_cipher_;
+  TransportRole role_;
+  uint64 tiebreaker_;
+  IceProtocolType ice_proto_;
+  std::string ice_ufrag_;
+  std::string ice_pwd_;
+  std::string remote_ice_ufrag_;
+  std::string remote_ice_pwd_;
+  IceMode remote_ice_mode_;
+  talk_base::SSLFingerprint dtls_fingerprint_;
+};
+
+// 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<int, FakeTransportChannel*> ChannelMap;
+  FakeTransport(talk_base::Thread* signaling_thread,
+                talk_base::Thread* worker_thread,
+                const std::string& content_name,
+                PortAllocator* alllocator = NULL)
+      : Transport(signaling_thread, worker_thread,
+                  content_name, "test_type", NULL),
+      dest_(NULL),
+      async_(false),
+      identity_(NULL) {
+  }
+  ~FakeTransport() {
+    DestroyAllChannels();
+  }
+
+  const ChannelMap& channels() const { return channels_; }
+
+  void SetAsync(bool async) { async_ = async; }
+  void SetDestination(FakeTransport* dest) {
+    dest_ = dest;
+    for (ChannelMap::iterator it = channels_.begin(); it != channels_.end();
+         ++it) {
+      it->second->SetLocalIdentity(identity_);
+      SetChannelDestination(it->first, it->second);
+    }
+  }
+
+  void SetWritable(bool writable) {
+    for (ChannelMap::iterator it = channels_.begin(); it != channels_.end();
+         ++it) {
+      it->second->SetWritable(writable);
+    }
+  }
+
+  void set_identity(talk_base::SSLIdentity* identity) {
+    identity_ = identity;
+  }
+
+  using Transport::local_description;
+  using Transport::remote_description;
+
+ protected:
+  virtual TransportChannelImpl* CreateTransportChannel(int component) {
+    if (channels_.find(component) != channels_.end()) {
+      return NULL;
+    }
+    FakeTransportChannel* channel =
+        new FakeTransportChannel(this, content_name(), component);
+    channel->SetAsync(async_);
+    SetChannelDestination(component, channel);
+    channels_[component] = channel;
+    return channel;
+  }
+  virtual void DestroyTransportChannel(TransportChannelImpl* channel) {
+    channels_.erase(channel->component());
+    delete channel;
+  }
+
+ private:
+  FakeTransportChannel* GetFakeChannel(int component) {
+    ChannelMap::iterator it = channels_.find(component);
+    return (it != channels_.end()) ? it->second : NULL;
+  }
+  void SetChannelDestination(int component,
+                             FakeTransportChannel* channel) {
+    FakeTransportChannel* dest_channel = NULL;
+    if (dest_) {
+      dest_channel = dest_->GetFakeChannel(component);
+      if (dest_channel) {
+        dest_channel->SetLocalIdentity(dest_->identity_);
+      }
+    }
+    channel->SetDestination(dest_channel);
+  }
+
+  // Note, this is distinct from the Channel map owned by Transport.
+  // This map just tracks the FakeTransportChannels created by this class.
+  ChannelMap channels_;
+  FakeTransport* dest_;
+  bool async_;
+  talk_base::SSLIdentity* identity_;
+};
+
+// 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:
+  explicit FakeSession()
+      : BaseSession(talk_base::Thread::Current(),
+                    talk_base::Thread::Current(),
+                    NULL, "", "", true),
+      fail_create_channel_(false) {
+  }
+  explicit FakeSession(bool initiator)
+      : BaseSession(talk_base::Thread::Current(),
+                    talk_base::Thread::Current(),
+                    NULL, "", "", initiator),
+      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 TransportChannel* CreateChannel(
+      const std::string& content_name,
+      const std::string& channel_name,
+      int component) {
+    if (fail_create_channel_) {
+      return NULL;
+    }
+    return BaseSession::CreateChannel(content_name, channel_name, component);
+  }
+
+  void set_fail_channel_creation(bool fail_channel_creation) {
+    fail_create_channel_ = fail_channel_creation;
+  }
+
+  // TODO: Hoist this into Session when we re-work the Session code.
+  void set_ssl_identity(talk_base::SSLIdentity* identity) {
+    for (TransportMap::const_iterator it = transport_proxies().begin();
+        it != transport_proxies().end(); ++it) {
+      // We know that we have a FakeTransport*
+
+      static_cast<FakeTransport*>(it->second->impl())->set_identity
+          (identity);
+    }
+  }
+
+ protected:
+  virtual Transport* CreateTransport(const std::string& content_name) {
+    return new FakeTransport(signaling_thread(), worker_thread(), content_name);
+  }
+
+  void CompleteNegotiation() {
+    for (TransportMap::const_iterator it = transport_proxies().begin();
+        it != transport_proxies().end(); ++it) {
+      it->second->CompleteNegotiation();
+      it->second->ConnectChannels();
+    }
+  }
+
+ private:
+  bool fail_create_channel_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_FAKESESSION_H_
diff --git a/talk/p2p/base/p2ptransport.cc b/talk/p2p/base/p2ptransport.cc
new file mode 100644
index 0000000..7f53cff
--- /dev/null
+++ b/talk/p2p/base/p2ptransport.cc
@@ -0,0 +1,263 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/p2p/base/p2ptransport.h"
+
+#include <string>
+#include <vector>
+
+#include "talk/base/base64.h"
+#include "talk/base/common.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/p2ptransportchannel.h"
+#include "talk/p2p/base/parsing.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/base/sessionmessages.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+
+namespace {
+
+// Limits for GICE and ICE username sizes.
+const size_t kMaxGiceUsernameSize = 16;
+const size_t kMaxIceUsernameSize = 512;
+
+}  // namespace
+
+namespace cricket {
+
+static buzz::XmlElement* NewTransportElement(const std::string& name) {
+  return new buzz::XmlElement(buzz::QName(name, LN_TRANSPORT), true);
+}
+
+P2PTransport::P2PTransport(talk_base::Thread* signaling_thread,
+                           talk_base::Thread* worker_thread,
+                           const std::string& content_name,
+                           PortAllocator* allocator)
+    : Transport(signaling_thread, worker_thread,
+                content_name, NS_GINGLE_P2P, allocator) {
+}
+
+P2PTransport::~P2PTransport() {
+  DestroyAllChannels();
+}
+
+TransportChannelImpl* P2PTransport::CreateTransportChannel(int component) {
+  return new P2PTransportChannel(content_name(), component, this,
+                                 port_allocator());
+}
+
+void P2PTransport::DestroyTransportChannel(TransportChannelImpl* channel) {
+  delete channel;
+}
+
+bool P2PTransportParser::ParseTransportDescription(
+    const buzz::XmlElement* elem,
+    const CandidateTranslator* translator,
+    TransportDescription* desc,
+    ParseError* error) {
+  ASSERT(elem->Name().LocalPart() == LN_TRANSPORT);
+  desc->transport_type = elem->Name().Namespace();
+  if (desc->transport_type != NS_GINGLE_P2P)
+    return BadParse("Unsupported transport type", error);
+
+  for (const buzz::XmlElement* candidate_elem = elem->FirstElement();
+       candidate_elem != NULL;
+       candidate_elem = candidate_elem->NextElement()) {
+    // Only look at local part because the namespace might (eventually)
+    // be NS_GINGLE_P2P or NS_JINGLE_ICE_UDP.
+    if (candidate_elem->Name().LocalPart() == LN_CANDIDATE) {
+      Candidate candidate;
+      if (!ParseCandidate(ICEPROTO_GOOGLE, candidate_elem, translator,
+                          &candidate, error)) {
+        return false;
+      }
+
+      desc->candidates.push_back(candidate);
+    }
+  }
+  return true;
+}
+
+bool P2PTransportParser::WriteTransportDescription(
+    const TransportDescription& desc,
+    const CandidateTranslator* translator,
+    buzz::XmlElement** out_elem,
+    WriteError* error) {
+  TransportProtocol proto = TransportProtocolFromDescription(&desc);
+  talk_base::scoped_ptr<buzz::XmlElement> trans_elem(
+      NewTransportElement(desc.transport_type));
+
+  // Fail if we get HYBRID or ICE right now.
+  // TODO(juberti): Add ICE and HYBRID serialization.
+  if (proto != ICEPROTO_GOOGLE) {
+    LOG(LS_ERROR) << "Failed to serialize non-GICE TransportDescription";
+    return false;
+  }
+
+  for (std::vector<Candidate>::const_iterator iter = desc.candidates.begin();
+       iter != desc.candidates.end(); ++iter) {
+    talk_base::scoped_ptr<buzz::XmlElement> cand_elem(
+        new buzz::XmlElement(QN_GINGLE_P2P_CANDIDATE));
+    if (!WriteCandidate(proto, *iter, translator, cand_elem.get(), error)) {
+      return false;
+    }
+    trans_elem->AddElement(cand_elem.release());
+  }
+
+  *out_elem = trans_elem.release();
+  return true;
+}
+
+bool P2PTransportParser::ParseGingleCandidate(
+    const buzz::XmlElement* elem,
+    const CandidateTranslator* translator,
+    Candidate* candidate,
+    ParseError* error) {
+  return ParseCandidate(ICEPROTO_GOOGLE, elem, translator, candidate, error);
+}
+
+bool P2PTransportParser::WriteGingleCandidate(
+    const Candidate& candidate,
+    const CandidateTranslator* translator,
+    buzz::XmlElement** out_elem,
+    WriteError* error) {
+  talk_base::scoped_ptr<buzz::XmlElement> elem(
+      new buzz::XmlElement(QN_GINGLE_CANDIDATE));                                     
+  bool ret = WriteCandidate(ICEPROTO_GOOGLE, candidate, translator, elem.get(),
+                            error);
+  if (ret) {
+    *out_elem = elem.release();
+  }
+  return ret;
+}
+
+bool P2PTransportParser::VerifyUsernameFormat(TransportProtocol proto,
+                                              const std::string& username,
+                                              ParseError* error) {
+  if (proto == ICEPROTO_GOOGLE || proto == ICEPROTO_HYBRID) {
+    if (username.size() > kMaxGiceUsernameSize)
+      return BadParse("candidate username is too long", error);
+    if (!talk_base::Base64::IsBase64Encoded(username))
+      return BadParse("candidate username has non-base64 encoded characters",
+                      error);
+  } else if (proto == ICEPROTO_RFC5245) {
+    if (username.size() > kMaxIceUsernameSize)
+      return BadParse("candidate username is too long", error);
+  }
+  return true;
+}
+
+bool P2PTransportParser::ParseCandidate(TransportProtocol proto,
+                                        const buzz::XmlElement* elem,
+                                        const CandidateTranslator* translator,
+                                        Candidate* candidate,
+                                        ParseError* error) {
+  ASSERT(proto == ICEPROTO_GOOGLE);
+  ASSERT(translator != NULL);
+
+  if (!elem->HasAttr(buzz::QN_NAME) ||
+      !elem->HasAttr(QN_ADDRESS) ||
+      !elem->HasAttr(QN_PORT) ||
+      !elem->HasAttr(QN_USERNAME) ||
+      !elem->HasAttr(QN_PROTOCOL) ||
+      !elem->HasAttr(QN_GENERATION)) {
+    return BadParse("candidate missing required attribute", error);
+  }
+
+  talk_base::SocketAddress address;
+  if (!ParseAddress(elem, QN_ADDRESS, QN_PORT, &address, error))
+    return false;
+
+  std::string channel_name = elem->Attr(buzz::QN_NAME);
+  int component = 0;
+  if (!translator ||
+      !translator->GetComponentFromChannelName(channel_name, &component)) {
+    return BadParse("candidate has unknown channel name " + channel_name,
+                    error);
+  }
+
+  float preference = 0.0;
+  if (!GetXmlAttr(elem, QN_PREFERENCE, 0.0f, &preference)) {
+    return BadParse("candidate has unknown preference", error);
+  }
+
+  candidate->set_component(component);
+  candidate->set_address(address);
+  candidate->set_username(elem->Attr(QN_USERNAME));
+  candidate->set_preference(preference);
+  candidate->set_protocol(elem->Attr(QN_PROTOCOL));
+  candidate->set_generation_str(elem->Attr(QN_GENERATION));
+  if (elem->HasAttr(QN_PASSWORD))
+    candidate->set_password(elem->Attr(QN_PASSWORD));
+  if (elem->HasAttr(buzz::QN_TYPE))
+    candidate->set_type(elem->Attr(buzz::QN_TYPE));
+  if (elem->HasAttr(QN_NETWORK))
+    candidate->set_network_name(elem->Attr(QN_NETWORK));
+
+  if (!VerifyUsernameFormat(proto, candidate->username(), error))
+    return false;
+
+  return true;
+}
+
+bool P2PTransportParser::WriteCandidate(TransportProtocol proto,
+                                        const Candidate& candidate,
+                                        const CandidateTranslator* translator,
+                                        buzz::XmlElement* elem,
+                                        WriteError* error) {
+  ASSERT(proto == ICEPROTO_GOOGLE);
+  ASSERT(translator != NULL);
+
+  std::string channel_name;
+  if (!translator ||
+      !translator->GetChannelNameFromComponent(
+          candidate.component(), &channel_name)) {
+    return BadWrite("Cannot write candidate because of unknown component.",
+                    error);
+  }
+
+  elem->SetAttr(buzz::QN_NAME, channel_name);
+  elem->SetAttr(QN_ADDRESS, candidate.address().ipaddr().ToString());
+  elem->SetAttr(QN_PORT, candidate.address().PortAsString());
+  AddXmlAttr(elem, QN_PREFERENCE, candidate.preference());
+  elem->SetAttr(QN_USERNAME, candidate.username());
+  elem->SetAttr(QN_PROTOCOL, candidate.protocol());
+  elem->SetAttr(QN_GENERATION, candidate.generation_str());
+  if (!candidate.password().empty())
+    elem->SetAttr(QN_PASSWORD, candidate.password());
+  elem->SetAttr(buzz::QN_TYPE, candidate.type());
+  if (!candidate.network_name().empty())
+    elem->SetAttr(QN_NETWORK, candidate.network_name());
+
+  return true;
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/p2ptransport.h b/talk/p2p/base/p2ptransport.h
new file mode 100644
index 0000000..f2b10f8
--- /dev/null
+++ b/talk/p2p/base/p2ptransport.h
@@ -0,0 +1,103 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_P2PTRANSPORT_H_
+#define TALK_P2P_BASE_P2PTRANSPORT_H_
+
+#include <string>
+#include <vector>
+#include "talk/p2p/base/transport.h"
+
+namespace cricket {
+
+class P2PTransport : public Transport {
+ public:
+  P2PTransport(talk_base::Thread* signaling_thread,
+               talk_base::Thread* worker_thread,
+               const std::string& content_name,
+               PortAllocator* allocator);
+  virtual ~P2PTransport();
+
+ protected:
+  // Creates and destroys P2PTransportChannel.
+  virtual TransportChannelImpl* CreateTransportChannel(int component);
+  virtual void DestroyTransportChannel(TransportChannelImpl* channel);
+
+  friend class P2PTransportChannel;
+
+  DISALLOW_EVIL_CONSTRUCTORS(P2PTransport);
+};
+
+class P2PTransportParser : public TransportParser {
+ public:
+  P2PTransportParser() {}
+  // Translator may be null, in which case ParseCandidates should
+  // return false if there are candidates to parse.  We can't not call
+  // ParseCandidates because there's no way to know ahead of time if
+  // there are candidates or not.
+
+  // Jingle-specific functions; can be used with either ICE, GICE, or HYBRID.
+  virtual bool ParseTransportDescription(const buzz::XmlElement* elem,
+                                         const CandidateTranslator* translator,
+                                         TransportDescription* desc,
+                                         ParseError* error);
+  virtual bool WriteTransportDescription(const TransportDescription& desc,
+                                         const CandidateTranslator* translator,
+                                         buzz::XmlElement** elem,
+                                         WriteError* error);
+
+  // Legacy Gingle functions; only can be used with GICE.
+  virtual bool ParseGingleCandidate(const buzz::XmlElement* elem,
+                                    const CandidateTranslator* translator,
+                                    Candidate* candidate,
+                                    ParseError* error);
+  virtual bool WriteGingleCandidate(const Candidate& candidate,
+                                    const CandidateTranslator* translator,
+                                    buzz::XmlElement** elem,
+                                    WriteError* error);
+
+ private:
+  bool ParseCandidate(TransportProtocol proto,
+                      const buzz::XmlElement* elem,
+                      const CandidateTranslator* translator,
+                      Candidate* candidate,
+                      ParseError* error);
+  bool WriteCandidate(TransportProtocol proto,
+                      const Candidate& candidate,
+                      const CandidateTranslator* translator,
+                      buzz::XmlElement* elem,
+                      WriteError* error);
+  bool VerifyUsernameFormat(TransportProtocol proto,
+                            const std::string& username,
+                            ParseError* error);
+
+  DISALLOW_EVIL_CONSTRUCTORS(P2PTransportParser);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_P2PTRANSPORT_H_
diff --git a/talk/p2p/base/p2ptransportchannel.cc b/talk/p2p/base/p2ptransportchannel.cc
new file mode 100644
index 0000000..95a6198
--- /dev/null
+++ b/talk/p2p/base/p2ptransportchannel.cc
@@ -0,0 +1,1222 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/p2p/base/p2ptransportchannel.h"
+
+#include <set>
+#include "talk/base/common.h"
+#include "talk/base/crc32.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringencode.h"
+#include "talk/p2p/base/common.h"
+#include "talk/p2p/base/relayport.h"  // For RELAY_PORT_TYPE.
+#include "talk/p2p/base/stunport.h"  // For STUN_PORT_TYPE.
+
+namespace {
+
+// messages for queuing up work for ourselves
+enum {
+  MSG_SORT = 1,
+  MSG_PING,
+};
+
+// When the socket is unwritable, we will use 10 Kbps (ignoring IP+UDP headers)
+// for pinging.  When the socket is writable, we will use only 1 Kbps because
+// we don't want to degrade the quality on a modem.  These numbers should work
+// well on a 28.8K modem, which is the slowest connection on which the voice
+// quality is reasonable at all.
+static const uint32 PING_PACKET_SIZE = 60 * 8;
+static const uint32 WRITABLE_DELAY = 1000 * PING_PACKET_SIZE / 1000;  // 480ms
+static const uint32 UNWRITABLE_DELAY = 1000 * PING_PACKET_SIZE / 10000;  // 50ms
+
+// If there is a current writable connection, then we will also try hard to
+// make sure it is pinged at this rate.
+static const uint32 MAX_CURRENT_WRITABLE_DELAY = 900;  // 2*WRITABLE_DELAY - bit
+
+// The minimum improvement in RTT that justifies a switch.
+static const double kMinImprovement = 10;
+
+cricket::PortInterface::CandidateOrigin GetOrigin(cricket::PortInterface* port,
+                                         cricket::PortInterface* origin_port) {
+  if (!origin_port)
+    return cricket::PortInterface::ORIGIN_MESSAGE;
+  else if (port == origin_port)
+    return cricket::PortInterface::ORIGIN_THIS_PORT;
+  else
+    return cricket::PortInterface::ORIGIN_OTHER_PORT;
+}
+
+// Compares two connections based only on static information about them.
+int CompareConnectionCandidates(cricket::Connection* a,
+                                cricket::Connection* b) {
+  // Compare connection priority. Lower values get sorted last.
+  if (a->priority() > b->priority())
+    return 1;
+  if (a->priority() < b->priority())
+    return -1;
+
+  // If we're still tied at this point, prefer a younger generation.
+  return (a->remote_candidate().generation() + a->port()->generation()) -
+         (b->remote_candidate().generation() + b->port()->generation());
+}
+
+// Compare two connections based on their writability and static preferences.
+int CompareConnections(cricket::Connection *a, cricket::Connection *b) {
+  // Sort based on write-state.  Better states have lower values.
+  if (a->write_state() < b->write_state())
+    return 1;
+  if (a->write_state() > b->write_state())
+    return -1;
+
+  // Compare the candidate information.
+  return CompareConnectionCandidates(a, b);
+}
+
+// Wraps the comparison connection into a less than operator that puts higher
+// priority writable connections first.
+class ConnectionCompare {
+ public:
+  bool operator()(const cricket::Connection *ca,
+                  const cricket::Connection *cb) {
+    cricket::Connection* a = const_cast<cricket::Connection*>(ca);
+    cricket::Connection* b = const_cast<cricket::Connection*>(cb);
+
+    ASSERT(a->port()->IceProtocol() == b->port()->IceProtocol());
+
+    // Compare first on writability and static preferences.
+    int cmp = CompareConnections(a, b);
+    if (cmp > 0)
+      return true;
+    if (cmp < 0)
+      return false;
+
+    // Otherwise, sort based on latency estimate.
+    return a->rtt() < b->rtt();
+
+    // Should we bother checking for the last connection that last received
+    // data? It would help rendezvous on the connection that is also receiving
+    // packets.
+    //
+    // TODO: Yes we should definitely do this.  The TCP protocol gains
+    // efficiency by being used bidirectionally, as opposed to two separate
+    // unidirectional streams.  This test should probably occur before
+    // comparison of local prefs (assuming combined prefs are the same).  We
+    // need to be careful though, not to bounce back and forth with both sides
+    // trying to rendevous with the other.
+  }
+};
+
+// Determines whether we should switch between two connections, based first on
+// static preferences and then (if those are equal) on latency estimates.
+bool ShouldSwitch(cricket::Connection* a_conn, cricket::Connection* b_conn) {
+  if (a_conn == b_conn)
+    return false;
+
+  if (!a_conn || !b_conn)  // don't think the latter should happen
+    return true;
+
+  int prefs_cmp = CompareConnections(a_conn, b_conn);
+  if (prefs_cmp < 0)
+    return true;
+  if (prefs_cmp > 0)
+    return false;
+
+  return b_conn->rtt() <= a_conn->rtt() + kMinImprovement;
+}
+
+}  // unnamed namespace
+
+namespace cricket {
+
+P2PTransportChannel::P2PTransportChannel(const std::string& content_name,
+                                         int component,
+                                         P2PTransport* transport,
+                                         PortAllocator *allocator) :
+    TransportChannelImpl(content_name, component),
+    transport_(transport),
+    allocator_(allocator),
+    worker_thread_(talk_base::Thread::Current()),
+    incoming_only_(false),
+    waiting_for_signaling_(false),
+    error_(0),
+    best_connection_(NULL),
+    pending_best_connection_(NULL),
+    sort_dirty_(false),
+    was_writable_(false),
+    protocol_type_(ICEPROTO_GOOGLE),
+    remote_ice_mode_(ICEMODE_FULL),
+    role_(ROLE_UNKNOWN),
+    tiebreaker_(0),
+    remote_candidate_generation_(0) {
+}
+
+P2PTransportChannel::~P2PTransportChannel() {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  for (uint32 i = 0; i < allocator_sessions_.size(); ++i)
+    delete allocator_sessions_[i];
+}
+
+// Add the allocator session to our list so that we know which sessions
+// are still active.
+void P2PTransportChannel::AddAllocatorSession(PortAllocatorSession* session) {
+  session->set_generation(static_cast<uint32>(allocator_sessions_.size()));
+  allocator_sessions_.push_back(session);
+
+  // We now only want to apply new candidates that we receive to the ports
+  // created by this new session because these are replacing those of the
+  // previous sessions.
+  ports_.clear();
+
+  session->SignalPortReady.connect(this, &P2PTransportChannel::OnPortReady);
+  session->SignalCandidatesReady.connect(
+      this, &P2PTransportChannel::OnCandidatesReady);
+  session->SignalCandidatesAllocationDone.connect(
+      this, &P2PTransportChannel::OnCandidatesAllocationDone);
+  session->StartGettingPorts();
+}
+
+void P2PTransportChannel::AddConnection(Connection* connection) {
+  connections_.push_back(connection);
+  connection->set_remote_ice_mode(remote_ice_mode_);
+  connection->SignalReadPacket.connect(
+      this, &P2PTransportChannel::OnReadPacket);
+  connection->SignalReadyToSend.connect(
+      this, &P2PTransportChannel::OnReadyToSend);
+  connection->SignalStateChange.connect(
+      this, &P2PTransportChannel::OnConnectionStateChange);
+  connection->SignalDestroyed.connect(
+      this, &P2PTransportChannel::OnConnectionDestroyed);
+  connection->SignalUseCandidate.connect(
+      this, &P2PTransportChannel::OnUseCandidate);
+}
+
+void P2PTransportChannel::SetRole(TransportRole role) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  if (role_ != role) {
+    role_ = role;
+    for (std::vector<PortInterface *>::iterator it = ports_.begin();
+         it != ports_.end(); ++it) {
+      (*it)->SetRole(role_);
+    }
+  }
+}
+
+void P2PTransportChannel::SetTiebreaker(uint64 tiebreaker) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  if (!ports_.empty()) {
+    LOG(LS_ERROR)
+        << "Attempt to change tiebreaker after Port has been allocated.";
+    return;
+  }
+
+  tiebreaker_ = tiebreaker;
+}
+
+void P2PTransportChannel::SetIceProtocolType(IceProtocolType type) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  protocol_type_ = type;
+  for (std::vector<PortInterface *>::iterator it = ports_.begin();
+       it != ports_.end(); ++it) {
+    (*it)->SetIceProtocolType(protocol_type_);
+  }
+}
+
+void P2PTransportChannel::SetIceCredentials(const std::string& ice_ufrag,
+                                            const std::string& ice_pwd) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  bool ice_restart = false;
+  if (!ice_ufrag_.empty() && !ice_pwd_.empty()) {
+    // Restart candidate allocation if there is any change in either
+    // ice ufrag or password.
+    ice_restart = (ice_ufrag_ != ice_ufrag) || (ice_pwd_!= ice_pwd);
+  }
+
+  ice_ufrag_ = ice_ufrag;
+  ice_pwd_ = ice_pwd;
+
+  if (ice_restart) {
+    // Restart candidate gathering.
+    Allocate();
+  }
+}
+
+void P2PTransportChannel::SetRemoteIceCredentials(const std::string& ice_ufrag,
+                                                  const std::string& ice_pwd) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  bool ice_restart = false;
+  if (!remote_ice_ufrag_.empty() && !remote_ice_pwd_.empty()) {
+    ice_restart = (remote_ice_ufrag_ != ice_ufrag) ||
+                  (remote_ice_pwd_!= ice_pwd);
+  }
+
+  remote_ice_ufrag_ = ice_ufrag;
+  remote_ice_pwd_ = ice_pwd;
+
+  if (ice_restart) {
+    // |candidate.generation()| is not signaled in ICEPROTO_RFC5245.
+    // Therefore we need to keep track of the remote ice restart so
+    // newer connections are prioritized over the older.
+    ++remote_candidate_generation_;
+  }
+}
+
+void P2PTransportChannel::SetRemoteIceMode(IceMode mode) {
+  remote_ice_mode_ = mode;
+}
+
+// Go into the state of processing candidates, and running in general
+void P2PTransportChannel::Connect() {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  if (ice_ufrag_.empty() || ice_pwd_.empty()) {
+    ASSERT(false);
+    LOG(LS_ERROR) << "P2PTransportChannel::Connect: The ice_ufrag_ and the "
+                  << "ice_pwd_ are not set.";
+    return;
+  }
+
+  // Kick off an allocator session
+  Allocate();
+
+  // Start pinging as the ports come in.
+  thread()->Post(this, MSG_PING);
+}
+
+// Reset the socket, clear up any previous allocations and start over
+void P2PTransportChannel::Reset() {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  // Get rid of all the old allocators.  This should clean up everything.
+  for (uint32 i = 0; i < allocator_sessions_.size(); ++i)
+    delete allocator_sessions_[i];
+
+  allocator_sessions_.clear();
+  ports_.clear();
+  connections_.clear();
+  best_connection_ = NULL;
+
+  // Forget about all of the candidates we got before.
+  remote_candidates_.clear();
+
+  // Revert to the initial state.
+  set_readable(false);
+  set_writable(false);
+
+  // Reinitialize the rest of our state.
+  waiting_for_signaling_ = false;
+  sort_dirty_ = false;
+
+  // If we allocated before, start a new one now.
+  if (transport_->connect_requested())
+    Allocate();
+
+  // Start pinging as the ports come in.
+  thread()->Clear(this);
+  thread()->Post(this, MSG_PING);
+}
+
+// A new port is available, attempt to make connections for it
+void P2PTransportChannel::OnPortReady(PortAllocatorSession *session,
+                                      PortInterface* port) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  // Set in-effect options on the new port
+  for (OptionMap::const_iterator it = options_.begin();
+       it != options_.end();
+       ++it) {
+    int val = port->SetOption(it->first, it->second);
+    if (val < 0) {
+      LOG_J(LS_WARNING, port) << "SetOption(" << it->first
+                              << ", " << it->second
+                              << ") failed: " << port->GetError();
+    }
+  }
+
+  // Remember the ports and candidates, and signal that candidates are ready.
+  // The session will handle this, and send an initiate/accept/modify message
+  // if one is pending.
+
+  port->SetIceProtocolType(protocol_type_);
+  port->SetRole(role_);
+  port->SetTiebreaker(tiebreaker_);
+  ports_.push_back(port);
+  port->SignalUnknownAddress.connect(
+      this, &P2PTransportChannel::OnUnknownAddress);
+  port->SignalDestroyed.connect(this, &P2PTransportChannel::OnPortDestroyed);
+  port->SignalRoleConflict.connect(
+      this, &P2PTransportChannel::OnRoleConflict);
+
+  // Attempt to create a connection from this new port to all of the remote
+  // candidates that we were given so far.
+
+  std::vector<RemoteCandidate>::iterator iter;
+  for (iter = remote_candidates_.begin(); iter != remote_candidates_.end();
+       ++iter) {
+    CreateConnection(port, *iter, iter->origin_port(), false);
+  }
+
+  SortConnections();
+}
+
+// A new candidate is available, let listeners know
+void P2PTransportChannel::OnCandidatesReady(
+    PortAllocatorSession *session, const std::vector<Candidate>& candidates) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  for (size_t i = 0; i < candidates.size(); ++i) {
+    SignalCandidateReady(this, candidates[i]);
+  }
+}
+
+void P2PTransportChannel::OnCandidatesAllocationDone(
+    PortAllocatorSession* session) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  SignalCandidatesAllocationDone(this);
+}
+
+// Handle stun packets
+void P2PTransportChannel::OnUnknownAddress(
+    PortInterface* port,
+    const talk_base::SocketAddress& address, ProtocolType proto,
+    IceMessage* stun_msg, 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
+  // is currently available for. See if we already have a candidate with the
+  // address. If it isn't we need to create new candidate for it.
+
+  // Determine if the remote candidates use shared ufrag.
+  bool ufrag_per_port = false;
+  std::vector<RemoteCandidate>::iterator it;
+  if (remote_candidates_.size() > 0) {
+    it = remote_candidates_.begin();
+    std::string username = it->username();
+    for (; it != remote_candidates_.end(); ++it) {
+      if (it->username() != username) {
+        ufrag_per_port = true;
+        break;
+      }
+    }
+  }
+
+  const Candidate* candidate = NULL;
+  bool known_username = false;
+  std::string remote_password;
+  for (it = remote_candidates_.begin(); it != remote_candidates_.end(); ++it) {
+    if (it->username() == remote_username) {
+      remote_password = it->password();
+      known_username = true;
+      if (ufrag_per_port ||
+          (it->address() == address &&
+           it->protocol() == ProtoToString(proto))) {
+        candidate = &(*it);
+        break;
+      }
+      // We don't want to break here because we may find a match of the address
+      // later.
+    }
+  }
+
+  if (!known_username) {
+    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);
+    return;
+  }
+
+  Candidate new_remote_candidate;
+  if (candidate != NULL) {
+    new_remote_candidate = *candidate;
+    if (ufrag_per_port) {
+      new_remote_candidate.set_address(address);
+    }
+  } else {
+    // Create a new candidate with this address.
+
+    std::string type;
+    if (protocol_type_ == ICEPROTO_RFC5245) {
+      type = PRFLX_PORT_TYPE;
+    } else {
+      // G-ICE doesn't support prflx candidate.
+      // We set candidate type to STUN_PORT_TYPE if the binding request comes
+      // from a relay port or the shared socket is used. Otherwise we use the
+      // port's type as the candidate type.
+      if (port->Type() == RELAY_PORT_TYPE || port->SharedSocket()) {
+        type = STUN_PORT_TYPE;
+      } else {
+        type = port->Type();
+      }
+    }
+
+    std::string id = talk_base::CreateRandomString(8);
+    new_remote_candidate = Candidate(
+        id, component(), ProtoToString(proto), address,
+        0, remote_username, remote_password, type,
+        port->Network()->name(), 0U,
+        talk_base::ToString<uint32>(talk_base::ComputeCrc32(id)));
+    new_remote_candidate.set_priority(
+        new_remote_candidate.GetPriority(ICE_TYPE_PREFERENCE_SRFLX));
+  }
+
+  if (protocol_type_ == ICEPROTO_RFC5245) {
+    // RFC 5245
+    // If the source transport address of the request does not match any
+    // existing remote candidates, it represents a new peer reflexive remote
+    // candidate.
+
+    // The priority of the candidate is set to the PRIORITY attribute
+    // from the request.
+    const StunUInt32Attribute* priority_attr =
+        stun_msg->GetUInt32(STUN_ATTR_PRIORITY);
+    if (!priority_attr) {
+      LOG(LS_WARNING) << "P2PTransportChannel::OnUnknownAddress - "
+                      << "No STUN_ATTR_PRIORITY found in the "
+                      << "stun request message";
+      port->SendBindingErrorResponse(stun_msg, address,
+                                     STUN_ERROR_BAD_REQUEST,
+                                     STUN_ERROR_REASON_BAD_REQUEST);
+      return;
+    }
+    new_remote_candidate.set_priority(priority_attr->value());
+
+    // RFC5245, the agent constructs a pair whose local candidate is equal to
+    // the transport address on which the STUN request was received, and a
+    // remote candidate equal to the source transport address where the
+    // request came from.
+
+    // There shouldn't be an existing connection with this remote address.
+    ASSERT(port->GetConnection(new_remote_candidate.address()) == NULL);
+
+    Connection* connection = port->CreateConnection(
+        new_remote_candidate, cricket::PortInterface::ORIGIN_THIS_PORT);
+    if (!connection) {
+      ASSERT(false);
+      port->SendBindingErrorResponse(stun_msg, address,
+                                     STUN_ERROR_SERVER_ERROR,
+                                     STUN_ERROR_REASON_SERVER_ERROR);
+      return;
+    }
+
+    AddConnection(connection);
+    connection->ReceivedPing();
+
+    // Send the pinger a successful stun response.
+    port->SendBindingResponse(stun_msg, address);
+
+    // Update the list of connections since we just added another.  We do this
+    // after sending the response since it could (in principle) delete the
+    // connection in question.
+    SortConnections();
+  } else {
+    // Check for connectivity to this address. Create connections
+    // to this address across all local ports. First, add this as a new remote
+    // address
+    if (!CreateConnections(new_remote_candidate, port, true)) {
+      // Hopefully this won't occur, because changing a destination address
+      // shouldn't cause a new connection to fail
+      ASSERT(false);
+      port->SendBindingErrorResponse(stun_msg, address, STUN_ERROR_SERVER_ERROR,
+          STUN_ERROR_REASON_SERVER_ERROR);
+      return;
+    }
+
+    // Send the pinger a successful stun response.
+    port->SendBindingResponse(stun_msg, address);
+
+    // Update the list of connections since we just added another.  We do this
+    // after sending the response since it could (in principle) delete the
+    // connection in question.
+    SortConnections();
+  }
+}
+
+void P2PTransportChannel::OnRoleConflict(PortInterface* port) {
+  SignalRoleConflict(this);  // STUN ping will be sent when SetRole is called
+                             // from Transport.
+}
+
+// When the signalling channel is ready, we can really kick off the allocator
+void P2PTransportChannel::OnSignalingReady() {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  if (waiting_for_signaling_) {
+    waiting_for_signaling_ = false;
+    AddAllocatorSession(allocator_->CreateSession(
+        SessionId(), content_name(), component(), ice_ufrag_, ice_pwd_));
+  }
+}
+
+void P2PTransportChannel::OnUseCandidate(Connection* conn) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  ASSERT(role_ == ROLE_CONTROLLED);
+  ASSERT(protocol_type_ == ICEPROTO_RFC5245);
+  if (conn->write_state() == Connection::STATE_WRITABLE) {
+    if (best_connection_ != conn) {
+      pending_best_connection_ = NULL;
+      SwitchBestConnectionTo(conn);
+      // Now we have selected the best connection, time to prune other existing
+      // connections and update the read/write state of the channel.
+      RequestSort();
+    }
+  } else {
+    pending_best_connection_ = conn;
+  }
+}
+
+void P2PTransportChannel::OnCandidate(const Candidate& candidate) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  // Create connections to this remote candidate.
+  CreateConnections(candidate, NULL, false);
+
+  // Resort the connections list, which may have new elements.
+  SortConnections();
+}
+
+// Creates connections from all of the ports that we care about to the given
+// remote candidate.  The return value is true if we created a connection from
+// the origin port.
+bool P2PTransportChannel::CreateConnections(const Candidate &remote_candidate,
+                                            PortInterface* origin_port,
+                                            bool readable) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  Candidate new_remote_candidate(remote_candidate);
+  new_remote_candidate.set_generation(
+      GetRemoteCandidateGeneration(remote_candidate));
+  // ICE candidates don't need to have username and password set, but
+  // the code below this (specifically, ConnectionRequest::Prepare in
+  // port.cc) uses the remote candidates's username.  So, we set it
+  // here.
+  if (remote_candidate.username().empty()) {
+    new_remote_candidate.set_username(remote_ice_ufrag_);
+  }
+  if (remote_candidate.password().empty()) {
+    new_remote_candidate.set_password(remote_ice_pwd_);
+  }
+
+  // Add a new connection for this candidate to every port that allows such a
+  // connection (i.e., if they have compatible protocols) and that does not
+  // already have a connection to an equivalent candidate.  We must be careful
+  // to make sure that the origin port is included, even if it was pruned,
+  // since that may be the only port that can create this connection.
+
+  bool created = false;
+
+  std::vector<PortInterface *>::reverse_iterator it;
+  for (it = ports_.rbegin(); it != ports_.rend(); ++it) {
+    if (CreateConnection(*it, new_remote_candidate, origin_port, readable)) {
+      if (*it == origin_port)
+        created = true;
+    }
+  }
+
+  if ((origin_port != NULL) &&
+      std::find(ports_.begin(), ports_.end(), origin_port) == ports_.end()) {
+    if (CreateConnection(
+        origin_port, new_remote_candidate, origin_port, readable))
+      created = true;
+  }
+
+  // Remember this remote candidate so that we can add it to future ports.
+  RememberRemoteCandidate(new_remote_candidate, origin_port);
+
+  return created;
+}
+
+// Setup a connection object for the local and remote candidate combination.
+// And then listen to connection object for changes.
+bool P2PTransportChannel::CreateConnection(PortInterface* port,
+                                           const Candidate& remote_candidate,
+                                           PortInterface* origin_port,
+                                           bool readable) {
+  // Look for an existing connection with this remote address.  If one is not
+  // found, then we can create a new connection for this address.
+  Connection* connection = port->GetConnection(remote_candidate.address());
+  if (connection != NULL) {
+    // It is not legal to try to change any of the parameters of an existing
+    // connection; however, the other side can send a duplicate candidate.
+    if (!remote_candidate.IsEquivalent(connection->remote_candidate())) {
+      LOG(INFO) << "Attempt to change a remote candidate";
+      return false;
+    }
+  } else {
+    PortInterface::CandidateOrigin origin = GetOrigin(port, origin_port);
+
+    // Don't create connection if this is a candidate we received in a
+    // message and we are not allowed to make outgoing connections.
+    if (origin == cricket::PortInterface::ORIGIN_MESSAGE && incoming_only_)
+      return false;
+
+    connection = port->CreateConnection(remote_candidate, origin);
+    if (!connection)
+      return false;
+
+    AddConnection(connection);
+
+    LOG_J(LS_INFO, this) << "Created connection with origin=" << origin << ", ("
+                         << connections_.size() << " total)";
+  }
+
+  // If we are readable, it is because we are creating this in response to a
+  // ping from the other side.  This will cause the state to become readable.
+  if (readable)
+    connection->ReceivedPing();
+
+  return true;
+}
+
+bool P2PTransportChannel::FindConnection(
+    cricket::Connection* connection) const {
+  std::vector<Connection*>::const_iterator citer =
+      std::find(connections_.begin(), connections_.end(), connection);
+  return citer != connections_.end();
+}
+
+uint32 P2PTransportChannel::GetRemoteCandidateGeneration(
+    const Candidate& candidate) {
+  if (protocol_type_ == ICEPROTO_GOOGLE) {
+    // The Candidate.generation() can be trusted. Nothing needs to be done.
+    return candidate.generation();
+  }
+  // |candidate.generation()| is not signaled in ICEPROTO_RFC5245.
+  // Therefore we need to keep track of the remote ice restart so
+  // newer connections are prioritized over the older.
+  ASSERT(candidate.generation() == 0 ||
+         candidate.generation() == remote_candidate_generation_);
+  return remote_candidate_generation_;
+}
+
+// Maintain our remote candidate list, adding this new remote one.
+void P2PTransportChannel::RememberRemoteCandidate(
+    const Candidate& remote_candidate, PortInterface* origin_port) {
+  // Remove any candidates whose generation is older than this one.  The
+  // presence of a new generation indicates that the old ones are not useful.
+  uint32 i = 0;
+  while (i < remote_candidates_.size()) {
+    if (remote_candidates_[i].generation() < remote_candidate.generation()) {
+      LOG(INFO) << "Pruning candidate from old generation: "
+                << remote_candidates_[i].address().ToSensitiveString();
+      remote_candidates_.erase(remote_candidates_.begin() + i);
+    } else {
+      i += 1;
+    }
+  }
+
+  // Make sure this candidate is not a duplicate.
+  for (uint32 i = 0; i < remote_candidates_.size(); ++i) {
+    if (remote_candidates_[i].IsEquivalent(remote_candidate)) {
+      LOG(INFO) << "Duplicate candidate: "
+                << remote_candidate.address().ToSensitiveString();
+      return;
+    }
+  }
+
+  // Try this candidate for all future ports.
+  remote_candidates_.push_back(RemoteCandidate(remote_candidate, origin_port));
+}
+
+// Set options on ourselves is simply setting options on all of our available
+// port objects.
+int P2PTransportChannel::SetOption(talk_base::Socket::Option opt, int value) {
+  OptionMap::iterator it = options_.find(opt);
+  if (it == options_.end()) {
+    options_.insert(std::make_pair(opt, value));
+  } else if (it->second == value) {
+    return 0;
+  } else {
+    it->second = value;
+  }
+
+  for (uint32 i = 0; i < ports_.size(); ++i) {
+    int val = ports_[i]->SetOption(opt, value);
+    if (val < 0) {
+      // Because this also occurs deferred, probably no point in reporting an
+      // error
+      LOG(WARNING) << "SetOption(" << opt << ", " << value << ") failed: "
+                   << ports_[i]->GetError();
+    }
+  }
+  return 0;
+}
+
+// Send data to the other side, using our best connection.
+int P2PTransportChannel::SendPacket(const char *data, size_t len, int flags) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  if (flags != 0) {
+    error_ = EINVAL;
+    return -1;
+  }
+  if (best_connection_ == NULL) {
+    error_ = EWOULDBLOCK;
+    return -1;
+  }
+  int sent = best_connection_->Send(data, len);
+  if (sent <= 0) {
+    ASSERT(sent < 0);
+    error_ = best_connection_->GetError();
+  }
+  return sent;
+}
+
+bool P2PTransportChannel::GetStats(ConnectionInfos *infos) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  // Gather connection infos.
+  infos->clear();
+
+  std::vector<Connection *>::const_iterator it;
+  for (it = connections_.begin(); it != connections_.end(); ++it) {
+    Connection *connection = *it;
+    ConnectionInfo info;
+    info.best_connection = (best_connection_ == connection);
+    info.readable =
+        (connection->read_state() == Connection::STATE_READABLE);
+    info.writable =
+        (connection->write_state() == Connection::STATE_WRITABLE);
+    info.timeout =
+        (connection->write_state() == Connection::STATE_WRITE_TIMEOUT);
+    info.new_connection = !connection->reported();
+    connection->set_reported(true);
+    info.rtt = connection->rtt();
+    info.sent_total_bytes = connection->sent_total_bytes();
+    info.sent_bytes_second = connection->sent_bytes_second();
+    info.recv_total_bytes = connection->recv_total_bytes();
+    info.recv_bytes_second = connection->recv_bytes_second();
+    info.local_candidate = connection->local_candidate();
+    info.remote_candidate = connection->remote_candidate();
+    info.key = connection;
+    infos->push_back(info);
+  }
+
+  return true;
+}
+
+// Begin allocate (or immediately re-allocate, if MSG_ALLOCATE pending)
+void P2PTransportChannel::Allocate() {
+  // Time for a new allocator, lets make sure we have a signalling channel
+  // to communicate candidates through first.
+  waiting_for_signaling_ = true;
+  SignalRequestSignaling(this);
+}
+
+// Monitor connection states.
+void P2PTransportChannel::UpdateConnectionStates() {
+  uint32 now = talk_base::Time();
+
+  // We need to copy the list of connections since some may delete themselves
+  // when we call UpdateState.
+  for (uint32 i = 0; i < connections_.size(); ++i)
+    connections_[i]->UpdateState(now);
+}
+
+// Prepare for best candidate sorting.
+void P2PTransportChannel::RequestSort() {
+  if (!sort_dirty_) {
+    worker_thread_->Post(this, MSG_SORT);
+    sort_dirty_ = true;
+  }
+}
+
+// Sort the available connections to find the best one.  We also monitor
+// the number of available connections and the current state.
+void P2PTransportChannel::SortConnections() {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  // Make sure the connection states are up-to-date since this affects how they
+  // will be sorted.
+  UpdateConnectionStates();
+
+  // Any changes after this point will require a re-sort.
+  sort_dirty_ = false;
+
+  // Get a list of the networks that we are using.
+  std::set<talk_base::Network*> networks;
+  for (uint32 i = 0; i < connections_.size(); ++i)
+    networks.insert(connections_[i]->port()->Network());
+
+  // Find the best alternative connection by sorting.  It is important to note
+  // that amongst equal preference, writable connections, this will choose the
+  // one whose estimated latency is lowest.  So it is the only one that we
+  // need to consider switching to.
+
+  ConnectionCompare cmp;
+  std::stable_sort(connections_.begin(), connections_.end(), cmp);
+  LOG(LS_VERBOSE) << "Sorting available connections:";
+  for (uint32 i = 0; i < connections_.size(); ++i) {
+    LOG(LS_VERBOSE) << connections_[i]->ToString();
+  }
+
+  Connection* top_connection = NULL;
+  if (connections_.size() > 0)
+    top_connection = connections_[0];
+
+  // We don't want to pick the best connections if channel is using RFC5245
+  // and it's mode is CONTROLLED, as connections will be selected by the
+  // CONTROLLING agent.
+
+  // If necessary, switch to the new choice.
+  if (protocol_type_ != ICEPROTO_RFC5245 || role_ == ROLE_CONTROLLING) {
+    if (ShouldSwitch(best_connection_, top_connection))
+      SwitchBestConnectionTo(top_connection);
+  }
+
+  // We can prune any connection for which there is a writable connection on
+  // the same network with better or equal priority.  We leave those with
+  // better priority just in case they become writable later (at which point,
+  // we would prune out the current best connection).  We leave connections on
+  // other networks because they may not be using the same resources and they
+  // may represent very distinct paths over which we can switch.
+  std::set<talk_base::Network*>::iterator network;
+  for (network = networks.begin(); network != networks.end(); ++network) {
+    Connection* primier = GetBestConnectionOnNetwork(*network);
+    if (!primier || (primier->write_state() != Connection::STATE_WRITABLE))
+      continue;
+
+    for (uint32 i = 0; i < connections_.size(); ++i) {
+      if ((connections_[i] != primier) &&
+          (connections_[i]->port()->Network() == *network) &&
+          (CompareConnectionCandidates(primier, connections_[i]) >= 0)) {
+        connections_[i]->Prune();
+      }
+    }
+  }
+
+  // Check if all connections are timedout.
+  bool all_connections_timedout = true;
+  for (uint32 i = 0; i < connections_.size(); ++i) {
+    if (connections_[i]->write_state() != Connection::STATE_WRITE_TIMEOUT) {
+      all_connections_timedout = false;
+      break;
+    }
+  }
+
+  // Now update the writable state of the channel with the information we have
+  // so far.
+  if (best_connection_ && best_connection_->writable()) {
+    HandleWritable();
+  } else if (all_connections_timedout) {
+    HandleAllTimedOut();
+  } else {
+    HandleNotWritable();
+  }
+
+  // Update the state of this channel.  This method is called whenever the
+  // state of any connection changes, so this is a good place to do this.
+  UpdateChannelState();
+}
+
+
+// Track the best connection, and let listeners know
+void P2PTransportChannel::SwitchBestConnectionTo(Connection* conn) {
+  // Note: if conn is NULL, the previous best_connection_ has been destroyed,
+  // so don't use it.
+  Connection* old_best_connection = best_connection_;
+  best_connection_ = conn;
+  if (best_connection_) {
+    if (old_best_connection) {
+      LOG_J(LS_INFO, this) << "Previous best connection: "
+                           << old_best_connection->ToString();
+    }
+    LOG_J(LS_INFO, this) << "New best connection: "
+                         << best_connection_->ToString();
+    SignalRouteChange(this, best_connection_->remote_candidate());
+  } else {
+    LOG_J(LS_INFO, this) << "No best connection";
+  }
+}
+
+void P2PTransportChannel::UpdateChannelState() {
+  // The Handle* functions already set the writable state.  We'll just double-
+  // check it here.
+  bool writable = ((best_connection_ != NULL)  &&
+      (best_connection_->write_state() ==
+      Connection::STATE_WRITABLE));
+  ASSERT(writable == this->writable());
+  if (writable != this->writable())
+    LOG(LS_ERROR) << "UpdateChannelState: writable state mismatch";
+
+  bool readable = false;
+  for (uint32 i = 0; i < connections_.size(); ++i) {
+    if (connections_[i]->read_state() == Connection::STATE_READABLE)
+      readable = true;
+  }
+  set_readable(readable);
+}
+
+// We checked the status of our connections and we had at least one that
+// was writable, go into the writable state.
+void P2PTransportChannel::HandleWritable() {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  if (!writable()) {
+    for (uint32 i = 0; i < allocator_sessions_.size(); ++i) {
+      if (allocator_sessions_[i]->IsGettingPorts()) {
+        allocator_sessions_[i]->StopGettingPorts();
+      }
+    }
+  }
+
+  was_writable_ = true;
+  set_writable(true);
+}
+
+// Notify upper layer about channel not writable state, if it was before.
+void P2PTransportChannel::HandleNotWritable() {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  if (was_writable_) {
+    was_writable_ = false;
+    set_writable(false);
+  }
+}
+
+void P2PTransportChannel::HandleAllTimedOut() {
+  // Currently we are treating this as channel not writable.
+  HandleNotWritable();
+}
+
+// If we have a best connection, return it, otherwise return top one in the
+// list (later we will mark it best).
+Connection* P2PTransportChannel::GetBestConnectionOnNetwork(
+    talk_base::Network* network) {
+  // If the best connection is on this network, then it wins.
+  if (best_connection_ && (best_connection_->port()->Network() == network))
+    return best_connection_;
+
+  // Otherwise, we return the top-most in sorted order.
+  for (uint32 i = 0; i < connections_.size(); ++i) {
+    if (connections_[i]->port()->Network() == network)
+      return connections_[i];
+  }
+
+  return NULL;
+}
+
+// Handle any queued up requests
+void P2PTransportChannel::OnMessage(talk_base::Message *pmsg) {
+  switch (pmsg->message_id) {
+    case MSG_SORT:
+      OnSort();
+      break;
+    case MSG_PING:
+      OnPing();
+      break;
+    default:
+      ASSERT(false);
+      break;
+  }
+}
+
+// Handle queued up sort request
+void P2PTransportChannel::OnSort() {
+  // Resort the connections based on the new statistics.
+  SortConnections();
+}
+
+// Handle queued up ping request
+void P2PTransportChannel::OnPing() {
+  // Make sure the states of the connections are up-to-date (since this affects
+  // which ones are pingable).
+  UpdateConnectionStates();
+
+  // Find the oldest pingable connection and have it do a ping.
+  Connection* conn = FindNextPingableConnection();
+  if (conn)
+    PingConnection(conn);
+
+  // Post ourselves a message to perform the next ping.
+  uint32 delay = writable() ? WRITABLE_DELAY : UNWRITABLE_DELAY;
+  thread()->PostDelayed(delay, this, MSG_PING);
+}
+
+// Is the connection in a state for us to even consider pinging the other side?
+bool P2PTransportChannel::IsPingable(Connection* conn) {
+  // An unconnected connection cannot be written to at all, so pinging is out
+  // of the question.
+  if (!conn->connected())
+    return false;
+
+  if (writable()) {
+    // If we are writable, then we only want to ping connections that could be
+    // better than this one, i.e., the ones that were not pruned.
+    return (conn->write_state() != Connection::STATE_WRITE_TIMEOUT);
+  } else {
+    // If we are not writable, then we need to try everything that might work.
+    // This includes both connections that do not have write timeout as well as
+    // ones that do not have read timeout.  A connection could be readable but
+    // be in write-timeout if we pruned it before.  Since the other side is
+    // still pinging it, it very well might still work.
+    return (conn->write_state() != Connection::STATE_WRITE_TIMEOUT) ||
+           (conn->read_state() != Connection::STATE_READ_TIMEOUT);
+  }
+}
+
+// Returns the next pingable connection to ping.  This will be the oldest
+// pingable connection unless we have a writable connection that is past the
+// maximum acceptable ping delay.
+Connection* P2PTransportChannel::FindNextPingableConnection() {
+  uint32 now = talk_base::Time();
+  if (best_connection_ &&
+      (best_connection_->write_state() == Connection::STATE_WRITABLE) &&
+      (best_connection_->last_ping_sent()
+       + MAX_CURRENT_WRITABLE_DELAY <= now)) {
+    return best_connection_;
+  }
+
+  Connection* oldest_conn = NULL;
+  uint32 oldest_time = 0xFFFFFFFF;
+  for (uint32 i = 0; i < connections_.size(); ++i) {
+    if (IsPingable(connections_[i])) {
+      if (connections_[i]->last_ping_sent() < oldest_time) {
+        oldest_time = connections_[i]->last_ping_sent();
+        oldest_conn = connections_[i];
+      }
+    }
+  }
+  return oldest_conn;
+}
+
+// Apart from sending ping from |conn| this method also updates
+// |use_candidate_attr| flag. The criteria to update this flag is
+// explained below.
+// Set USE-CANDIDATE if doing ICE AND this channel is in CONTROLLING AND
+//    a) Channel is in FULL ICE AND
+//      a.1) |conn| is the best connection OR
+//      a.2) there is no best connection OR
+//      a.3) the best connection is unwritable OR
+//      a.4) |conn| has higher priority than best_connection.
+//    b) we're doing LITE ICE AND
+//      b.1) |conn| is the best_connection AND
+//      b.2) |conn| is writable.
+void P2PTransportChannel::PingConnection(Connection* conn) {
+  bool use_candidate = false;
+  if (protocol_type_ == ICEPROTO_RFC5245) {
+    if (remote_ice_mode_ == ICEMODE_FULL && role_ == ROLE_CONTROLLING) {
+      use_candidate = (conn == best_connection_) ||
+                      (best_connection_ == NULL) ||
+                      (!best_connection_->writable()) ||
+                      (conn->priority() > best_connection_->priority());
+    } else if (remote_ice_mode_ == ICEMODE_LITE && conn == best_connection_) {
+      use_candidate = best_connection_->writable();
+    }
+  }
+  conn->set_use_candidate_attr(use_candidate);
+  conn->Ping(talk_base::Time());
+}
+
+// When a connection's state changes, we need to figure out who to use as
+// the best connection again.  It could have become usable, or become unusable.
+void P2PTransportChannel::OnConnectionStateChange(Connection* connection) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  // Update the best connection if the state change is from pending best
+  // connection and role is controlled.
+  if (protocol_type_ == ICEPROTO_RFC5245 && role_ == ROLE_CONTROLLED) {
+    if (connection == pending_best_connection_ && connection->writable()) {
+      pending_best_connection_ = NULL;
+      SwitchBestConnectionTo(connection);
+    }
+  }
+
+  // We have to unroll the stack before doing this because we may be changing
+  // the state of connections while sorting.
+  RequestSort();
+}
+
+// When a connection is removed, edit it out, and then update our best
+// connection.
+void P2PTransportChannel::OnConnectionDestroyed(Connection* connection) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  // Note: the previous best_connection_ may be destroyed by now, so don't
+  // use it.
+
+  // Remove this connection from the list.
+  std::vector<Connection*>::iterator iter =
+      std::find(connections_.begin(), connections_.end(), connection);
+  ASSERT(iter != connections_.end());
+  connections_.erase(iter);
+
+  LOG_J(LS_INFO, this) << "Removed connection ("
+    << static_cast<int>(connections_.size()) << " remaining)";
+
+  if (pending_best_connection_ == connection) {
+    pending_best_connection_ = NULL;
+  }
+
+  // If this is currently the best connection, then we need to pick a new one.
+  // The call to SortConnections will pick a new one.  It looks at the current
+  // best connection in order to avoid switching between fairly similar ones.
+  // Since this connection is no longer an option, we can just set best to NULL
+  // and re-choose a best assuming that there was no best connection.
+  if (best_connection_ == connection) {
+    SwitchBestConnectionTo(NULL);
+    RequestSort();
+  }
+}
+
+// When a port is destroyed remove it from our list of ports to use for
+// connection attempts.
+void P2PTransportChannel::OnPortDestroyed(PortInterface* port) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  // Remove this port from the list (if we didn't drop it already).
+  std::vector<PortInterface*>::iterator iter =
+      std::find(ports_.begin(), ports_.end(), port);
+  if (iter != ports_.end())
+    ports_.erase(iter);
+
+  LOG(INFO) << "Removed port from p2p socket: "
+            << static_cast<int>(ports_.size()) << " remaining";
+}
+
+// We data is available, let listeners know
+void P2PTransportChannel::OnReadPacket(Connection *connection, const char *data,
+                                       size_t len) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  // 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, 0);
+}
+
+void P2PTransportChannel::OnReadyToSend(Connection* connection) {
+  if (connection == best_connection_ && writable()) {
+    SignalReadyToSend(this);
+  }
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/p2ptransportchannel.h b/talk/p2p/base/p2ptransportchannel.h
new file mode 100644
index 0000000..420769d
--- /dev/null
+++ b/talk/p2p/base/p2ptransportchannel.h
@@ -0,0 +1,196 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// P2PTransportChannel wraps up the state management of the connection between
+// two P2P clients.  Clients have candidate ports for connecting, and
+// connections which are combinations of candidates from each end (Alice and
+// Bob each have candidates, one candidate from Alice and one candidate from
+// Bob are used to make a connection, repeat to make many connections).
+//
+// When all of the available connections become invalid (non-writable), we
+// kick off a process of determining more candidates and more connections.
+//
+#ifndef TALK_P2P_BASE_P2PTRANSPORTCHANNEL_H_
+#define TALK_P2P_BASE_P2PTRANSPORTCHANNEL_H_
+
+#include <map>
+#include <vector>
+#include <string>
+#include "talk/base/sigslot.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/portinterface.h"
+#include "talk/p2p/base/portallocator.h"
+#include "talk/p2p/base/transport.h"
+#include "talk/p2p/base/transportchannelimpl.h"
+#include "talk/p2p/base/p2ptransport.h"
+
+namespace cricket {
+
+// Adds the port on which the candidate originated.
+class RemoteCandidate : public Candidate {
+ public:
+  RemoteCandidate(const Candidate& c, PortInterface* origin_port)
+      : Candidate(c), origin_port_(origin_port) {}
+
+  PortInterface* origin_port() { return origin_port_; }
+
+ private:
+  PortInterface* origin_port_;
+};
+
+// P2PTransportChannel manages the candidates and connection process to keep
+// two P2P clients connected to each other.
+class P2PTransportChannel : public TransportChannelImpl,
+                            public talk_base::MessageHandler {
+ public:
+  P2PTransportChannel(const std::string& content_name,
+                      int component,
+                      P2PTransport* transport,
+                      PortAllocator *allocator);
+  virtual ~P2PTransportChannel();
+
+  // From TransportChannelImpl:
+  virtual Transport* GetTransport() { return transport_; }
+  virtual void SetRole(TransportRole role);
+  virtual TransportRole GetRole() const { return role_; }
+  virtual void SetTiebreaker(uint64 tiebreaker);
+  virtual void SetIceProtocolType(IceProtocolType type);
+  virtual void SetIceCredentials(const std::string& ice_ufrag,
+                                 const std::string& ice_pwd);
+  virtual void SetRemoteIceCredentials(const std::string& ice_ufrag,
+                                       const std::string& ice_pwd);
+  virtual void SetRemoteIceMode(IceMode mode);
+  virtual void Connect();
+  virtual void Reset();
+  virtual void OnSignalingReady();
+  virtual void OnCandidate(const Candidate& candidate);
+
+  // From TransportChannel:
+  virtual int SendPacket(const char *data, size_t len, int flags);
+  virtual int SetOption(talk_base::Socket::Option opt, int value);
+  virtual int GetError() { return error_; }
+  virtual bool GetStats(std::vector<ConnectionInfo>* stats);
+
+  const Connection* best_connection() const { return best_connection_; }
+  void set_incoming_only(bool value) { incoming_only_ = value; }
+
+  // Note: This is only for testing purpose.
+  // |ports_| should not be changed from outside.
+  const std::vector<PortInterface *>& ports() { return ports_; }
+
+  IceMode remote_ice_mode() const { return remote_ice_mode_; }
+
+ private:
+  talk_base::Thread* thread() { return worker_thread_; }
+  PortAllocatorSession* allocator_session() {
+    return allocator_sessions_.back();
+  }
+
+  void Allocate();
+  void UpdateConnectionStates();
+  void RequestSort();
+  void SortConnections();
+  void SwitchBestConnectionTo(Connection* conn);
+  void UpdateChannelState();
+  void HandleWritable();
+  void HandleNotWritable();
+  void HandleAllTimedOut();
+
+  Connection* GetBestConnectionOnNetwork(talk_base::Network* network);
+  bool CreateConnections(const Candidate &remote_candidate,
+                         PortInterface* origin_port, bool readable);
+  bool CreateConnection(PortInterface* port, const Candidate& remote_candidate,
+                        PortInterface* origin_port, bool readable);
+  bool FindConnection(cricket::Connection* connection) const;
+
+  uint32 GetRemoteCandidateGeneration(const Candidate& candidate);
+  void RememberRemoteCandidate(const Candidate& remote_candidate,
+                               PortInterface* origin_port);
+  bool IsPingable(Connection* conn);
+  Connection* FindNextPingableConnection();
+  void PingConnection(Connection* conn);
+  void AddAllocatorSession(PortAllocatorSession* session);
+  void AddConnection(Connection* connection);
+
+  void OnPortReady(PortAllocatorSession *session, PortInterface* port);
+  void OnCandidatesReady(PortAllocatorSession *session,
+                         const std::vector<Candidate>& candidates);
+  void OnCandidatesAllocationDone(PortAllocatorSession* session);
+  void OnUnknownAddress(PortInterface* port,
+                        const talk_base::SocketAddress& addr,
+                        ProtocolType proto,
+                        IceMessage* stun_msg,
+                        const std::string& remote_username,
+                        bool port_muxed);
+  void OnPortDestroyed(PortInterface* port);
+  void OnRoleConflict(PortInterface* port);
+
+  void OnConnectionStateChange(Connection *connection);
+  void OnReadPacket(Connection *connection, const char *data, size_t len);
+  void OnReadyToSend(Connection* connection);
+  void OnConnectionDestroyed(Connection *connection);
+
+  void OnUseCandidate(Connection* conn);
+
+  virtual void OnMessage(talk_base::Message *pmsg);
+  void OnSort();
+  void OnPing();
+
+  P2PTransport* transport_;
+  PortAllocator *allocator_;
+  talk_base::Thread *worker_thread_;
+  bool incoming_only_;
+  bool waiting_for_signaling_;
+  int error_;
+  std::vector<PortAllocatorSession*> allocator_sessions_;
+  std::vector<PortInterface *> ports_;
+  std::vector<Connection *> connections_;
+  Connection* best_connection_;
+  // Connection selected by the controlling agent. This should be used only
+  // at controlled side when protocol type is RFC5245.
+  Connection* pending_best_connection_;
+  std::vector<RemoteCandidate> remote_candidates_;
+  bool sort_dirty_;  // indicates whether another sort is needed right now
+  bool was_writable_;
+  typedef std::map<talk_base::Socket::Option, int> OptionMap;
+  OptionMap options_;
+  std::string ice_ufrag_;
+  std::string ice_pwd_;
+  std::string remote_ice_ufrag_;
+  std::string remote_ice_pwd_;
+  IceProtocolType protocol_type_;
+  IceMode remote_ice_mode_;
+  TransportRole role_;
+  uint64 tiebreaker_;
+  uint32 remote_candidate_generation_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(P2PTransportChannel);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_P2PTRANSPORTCHANNEL_H_
diff --git a/talk/p2p/base/p2ptransportchannel_unittest.cc b/talk/p2p/base/p2ptransportchannel_unittest.cc
new file mode 100644
index 0000000..0496109
--- /dev/null
+++ b/talk/p2p/base/p2ptransportchannel_unittest.cc
@@ -0,0 +1,1461 @@
+/*
+ * 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 cricket::kDefaultPortAllocatorFlags;
+using cricket::kMinimumStepDelay;
+using cricket::kDefaultStepDelay;
+using cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG;
+using cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET;
+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);
+
+// Based on ICE_UFRAG_LENGTH
+static const char* kIceUfrag[4] = {"TESTICEUFRAG0000", "TESTICEUFRAG0001",
+                                   "TESTICEUFRAG0002", "TESTICEUFRAG0003"};
+// Based on ICE_PWD_LENGTH
+static const char* kIcePwd[4] = {"TESTICEPWD00000000000000",
+                                 "TESTICEPWD00000000000001",
+                                 "TESTICEPWD00000000000002",
+                                 "TESTICEPWD00000000000003"};
+
+static const uint64 kTiebreaker1 = 11111;
+static const uint64 kTiebreaker2 = 22222;
+
+// 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 talk_base::MessageHandler,
+                                    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]),
+        clear_remote_candidates_ufrag_pwd_(false) {
+    ep1_.role_ = cricket::ROLE_CONTROLLING;
+    ep2_.role_ = cricket::ROLE_CONTROLLED;
+    ep1_.allocator_.reset(new cricket::BasicPortAllocator(
+        &ep1_.network_manager_, kStunAddr, kRelayUdpIntAddr,
+        kRelayTcpIntAddr, kRelaySslTcpIntAddr));
+    ep2_.allocator_.reset(new cricket::BasicPortAllocator(
+        &ep2_.network_manager_, 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,
+           const std::string& lt2, const std::string& lp2,
+           const std::string& rt2, const std::string& rp2, int wait)
+        : local_type(lt), local_proto(lp), remote_type(rt), remote_proto(rp),
+          local_type2(lt2), local_proto2(lp2), remote_type2(rt2),
+          remote_proto2(rp2), connect_wait(wait) {
+    }
+    std::string local_type;
+    std::string local_proto;
+    std::string remote_type;
+    std::string remote_proto;
+    std::string local_type2;
+    std::string local_proto2;
+    std::string remote_type2;
+    std::string remote_proto2;
+    int connect_wait;
+  };
+
+  struct ChannelData {
+    bool CheckData(const char* data, int len) {
+      bool ret = false;
+      if (!ch_packets_.empty()) {
+        std::string packet =  ch_packets_.front();
+        ret = (packet == std::string(data, len));
+        ch_packets_.pop_front();
+      }
+      return ret;
+    }
+
+    std::string name_;  // TODO - Currently not used.
+    std::list<std::string> ch_packets_;
+    talk_base::scoped_ptr<cricket::P2PTransportChannel> ch_;
+  };
+
+  struct Endpoint {
+    Endpoint() : signaling_delay_(0), role_(cricket::ROLE_UNKNOWN),
+        tiebreaker_(0), role_conflict_(false),
+        protocol_type_(cricket::ICEPROTO_GOOGLE) {}
+    bool HasChannel(cricket::TransportChannel* ch) {
+      return (ch == cd1_.ch_.get() || ch == cd2_.ch_.get());
+    }
+    ChannelData* GetChannelData(cricket::TransportChannel* ch) {
+      if (!HasChannel(ch)) return NULL;
+      if (cd1_.ch_.get() == ch)
+        return &cd1_;
+      else
+        return &cd2_;
+    }
+    void SetSignalingDelay(int delay) { signaling_delay_ = delay; }
+
+    void SetRole(cricket::TransportRole role) { role_ = role; }
+    cricket::TransportRole role() { return role_; }
+    void SetIceProtocolType(cricket::IceProtocolType type) {
+      protocol_type_ = type;
+    }
+    cricket::IceProtocolType protocol_type() { return protocol_type_; }
+    void SetTiebreaker(uint64 tiebreaker) { tiebreaker_ = tiebreaker; }
+    uint64 GetTiebreaker() { return tiebreaker_; }
+    void OnRoleConflict(bool role_conflict) { role_conflict_ = role_conflict; }
+    bool role_conflict() { return role_conflict_; }
+    void SetAllocationStepDelay(uint32 delay) {
+      allocator_->set_step_delay(delay);
+    }
+
+    talk_base::FakeNetworkManager network_manager_;
+    talk_base::scoped_ptr<cricket::PortAllocator> allocator_;
+    ChannelData cd1_;
+    ChannelData cd2_;
+    int signaling_delay_;
+    cricket::TransportRole role_;
+    uint64 tiebreaker_;
+    bool role_conflict_;
+    cricket::IceProtocolType protocol_type_;
+  };
+
+  struct CandidateData : public talk_base::MessageData {
+    CandidateData(cricket::TransportChannel* ch, const cricket::Candidate& c)
+        : channel(ch), candidate(c) {
+    }
+    cricket::TransportChannel* channel;
+    cricket::Candidate candidate;
+  };
+
+  ChannelData* GetChannelData(cricket::TransportChannel* channel) {
+    if (ep1_.HasChannel(channel))
+      return ep1_.GetChannelData(channel);
+    else
+      return ep2_.GetChannelData(channel);
+  }
+
+  void CreateChannels(int num) {
+    std::string ice_ufrag_ep1_cd1_ch = kIceUfrag[0];
+    std::string ice_pwd_ep1_cd1_ch = kIcePwd[0];
+    std::string ice_ufrag_ep2_cd1_ch = kIceUfrag[1];
+    std::string ice_pwd_ep2_cd1_ch = kIcePwd[1];
+    ep1_.cd1_.ch_.reset(CreateChannel(
+        0, cricket::ICE_CANDIDATE_COMPONENT_DEFAULT,
+        ice_ufrag_ep1_cd1_ch, ice_pwd_ep1_cd1_ch,
+        ice_ufrag_ep2_cd1_ch, ice_pwd_ep2_cd1_ch));
+    ep2_.cd1_.ch_.reset(CreateChannel(
+        1, cricket::ICE_CANDIDATE_COMPONENT_DEFAULT,
+        ice_ufrag_ep2_cd1_ch, ice_pwd_ep2_cd1_ch,
+        ice_ufrag_ep1_cd1_ch, ice_pwd_ep1_cd1_ch));
+    if (num == 2) {
+      std::string ice_ufrag_ep1_cd2_ch = kIceUfrag[2];
+      std::string ice_pwd_ep1_cd2_ch = kIcePwd[2];
+      std::string ice_ufrag_ep2_cd2_ch = kIceUfrag[3];
+      std::string ice_pwd_ep2_cd2_ch = kIcePwd[3];
+      // In BUNDLE each endpoint must share common ICE credentials.
+      if (ep1_.allocator_->flags() & cricket::PORTALLOCATOR_ENABLE_BUNDLE) {
+        ice_ufrag_ep1_cd2_ch = ice_ufrag_ep1_cd1_ch;
+        ice_pwd_ep1_cd2_ch = ice_pwd_ep1_cd1_ch;
+      }
+      if (ep2_.allocator_->flags() & cricket::PORTALLOCATOR_ENABLE_BUNDLE) {
+        ice_ufrag_ep2_cd2_ch = ice_ufrag_ep2_cd1_ch;
+        ice_pwd_ep2_cd2_ch = ice_pwd_ep2_cd1_ch;
+      }
+      ep1_.cd2_.ch_.reset(CreateChannel(
+          0, cricket::ICE_CANDIDATE_COMPONENT_DEFAULT,
+          ice_ufrag_ep1_cd2_ch, ice_pwd_ep1_cd2_ch,
+          ice_ufrag_ep2_cd2_ch, ice_pwd_ep2_cd2_ch));
+      ep2_.cd2_.ch_.reset(CreateChannel(
+          1, cricket::ICE_CANDIDATE_COMPONENT_DEFAULT,
+          ice_ufrag_ep2_cd2_ch, ice_pwd_ep2_cd2_ch,
+          ice_ufrag_ep1_cd2_ch, ice_pwd_ep1_cd2_ch));
+    }
+  }
+  cricket::P2PTransportChannel* CreateChannel(
+      int endpoint,
+      int component,
+      const std::string& local_ice_ufrag,
+      const std::string& local_ice_pwd,
+      const std::string& remote_ice_ufrag,
+      const std::string& remote_ice_pwd) {
+    cricket::P2PTransportChannel* channel = new cricket::P2PTransportChannel(
+        "test content name", component, NULL, GetAllocator(endpoint));
+    channel->SignalRequestSignaling.connect(
+        this, &P2PTransportChannelTestBase::OnChannelRequestSignaling);
+    channel->SignalCandidateReady.connect(this,
+        &P2PTransportChannelTestBase::OnCandidate);
+    channel->SignalReadPacket.connect(
+        this, &P2PTransportChannelTestBase::OnReadPacket);
+    channel->SignalRoleConflict.connect(
+        this, &P2PTransportChannelTestBase::OnRoleConflict);
+    channel->SetIceProtocolType(GetEndpoint(endpoint)->protocol_type());
+    channel->SetIceCredentials(local_ice_ufrag, local_ice_pwd);
+    if (clear_remote_candidates_ufrag_pwd_) {
+      // This only needs to be set if we're clearing them from the
+      // candidates.  Some unit tests rely on this not being set.
+      channel->SetRemoteIceCredentials(remote_ice_ufrag, remote_ice_pwd);
+    }
+    channel->SetRole(GetEndpoint(endpoint)->role());
+    channel->SetTiebreaker(GetEndpoint(endpoint)->GetTiebreaker());
+    channel->Connect();
+    return channel;
+  }
+  void DestroyChannels() {
+    ep1_.cd1_.ch_.reset();
+    ep2_.cd1_.ch_.reset();
+    ep1_.cd2_.ch_.reset();
+    ep2_.cd2_.ch_.reset();
+  }
+  cricket::P2PTransportChannel* ep1_ch1() { return ep1_.cd1_.ch_.get(); }
+  cricket::P2PTransportChannel* ep1_ch2() { return ep1_.cd2_.ch_.get(); }
+  cricket::P2PTransportChannel* ep2_ch1() { return ep2_.cd1_.ch_.get(); }
+  cricket::P2PTransportChannel* ep2_ch2() { return ep2_.cd2_.ch_.get(); }
+
+  // Common results.
+  static const Result kLocalUdpToLocalUdp;
+  static const Result kLocalUdpToStunUdp;
+  static const Result kLocalUdpToPrflxUdp;
+  static const Result kPrflxUdpToLocalUdp;
+  static const Result kStunUdpToLocalUdp;
+  static const Result kStunUdpToStunUdp;
+  static const Result kPrflxUdpToStunUdp;
+  static const Result kLocalUdpToRelayUdp;
+  static const Result kPrflxUdpToRelayUdp;
+  static const Result kLocalTcpToLocalTcp;
+  static const Result kLocalTcpToPrflxTcp;
+  static const Result kPrflxTcpToLocalTcp;
+
+  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(); }
+
+  Endpoint* GetEndpoint(int endpoint) {
+    if (endpoint == 0) {
+      return &ep1_;
+    } else if (endpoint == 1) {
+      return &ep2_;
+    } else {
+      return NULL;
+    }
+  }
+  cricket::PortAllocator* GetAllocator(int endpoint) {
+    return GetEndpoint(endpoint)->allocator_.get();
+  }
+  void AddAddress(int endpoint, const SocketAddress& addr) {
+    GetEndpoint(endpoint)->network_manager_.AddInterface(addr);
+  }
+  void RemoveAddress(int endpoint, const SocketAddress& addr) {
+    GetEndpoint(endpoint)->network_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 SetSignalingDelay(int endpoint, int delay) {
+    GetEndpoint(endpoint)->SetSignalingDelay(delay);
+  }
+  void SetIceProtocol(int endpoint, cricket::IceProtocolType type) {
+    GetEndpoint(endpoint)->SetIceProtocolType(type);
+  }
+  void SetIceRole(int endpoint, cricket::TransportRole role) {
+    GetEndpoint(endpoint)->SetRole(role);
+  }
+  void SetTiebreaker(int endpoint, uint64 tiebreaker) {
+    GetEndpoint(endpoint)->SetTiebreaker(tiebreaker);
+  }
+  bool GetRoleConflict(int endpoint) {
+    return GetEndpoint(endpoint)->role_conflict();
+  }
+  void SetAllocationStepDelay(int endpoint, uint32 delay) {
+    return GetEndpoint(endpoint)->SetAllocationStepDelay(delay);
+  }
+
+  void Test(const Result& expected) {
+    int32 connect_start = talk_base::Time(), connect_time;
+
+    // Create the channels and wait for them to connect.
+    CreateChannels(1);
+    EXPECT_TRUE_WAIT_MARGIN(ep1_ch1() != NULL &&
+                            ep2_ch1() != NULL &&
+                            ep1_ch1()->readable() &&
+                            ep1_ch1()->writable() &&
+                            ep2_ch1()->readable() &&
+                            ep2_ch1()->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 (ep1_ch1()->best_connection() &&
+        ep2_ch1()->best_connection()) {
+      int32 converge_start = talk_base::Time(), converge_time;
+      int converge_wait = 2000;
+      EXPECT_TRUE_WAIT_MARGIN(
+          LocalCandidate(ep1_ch1())->type() == expected.local_type &&
+          LocalCandidate(ep1_ch1())->protocol() == expected.local_proto &&
+          RemoteCandidate(ep1_ch1())->type() == expected.remote_type &&
+          RemoteCandidate(ep1_ch1())->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(ep1_ch1())->type());
+      EXPECT_EQ(expected.local_proto, LocalCandidate(ep1_ch1())->protocol());
+      EXPECT_EQ(expected.remote_type, RemoteCandidate(ep1_ch1())->type());
+      EXPECT_EQ(expected.remote_proto, RemoteCandidate(ep1_ch1())->protocol());
+
+      // Verifying remote channel best connection information. This is done
+      // only for the RFC 5245 as controlled agent will use USE-CANDIDATE
+      // from controlling (ep1) agent. We can easily predict from EP1 result
+      // matrix.
+      if (ep2_.protocol_type_ == cricket::ICEPROTO_RFC5245) {
+        // Checking for best connection candidates information at remote.
+        EXPECT_TRUE_WAIT(
+            LocalCandidate(ep2_ch1())->type() == expected.local_type2 &&
+            LocalCandidate(ep2_ch1())->protocol() == expected.local_proto2 &&
+            RemoteCandidate(ep2_ch1())->protocol() == expected.remote_proto2,
+            kDefaultTimeout);
+
+        // For verbose
+        EXPECT_EQ(expected.local_type2, LocalCandidate(ep2_ch1())->type());
+        EXPECT_EQ(expected.local_proto2, LocalCandidate(ep2_ch1())->protocol());
+        EXPECT_EQ(expected.remote_proto2,
+                  RemoteCandidate(ep2_ch1())->protocol());
+        // Removed remote_type comparision aginst best connection remote
+        // candidate. This is done to handle remote type discrepancy from
+        // local to stun based on the test type.
+        // For example in case of Open -> NAT, ep2 channels will have LULU
+        // and in other cases like NAT -> NAT it will be LUSU. To avoid these
+        // mismatches and we are doing comparision in different way.
+        // i.e. when don't match its remote type is either local or stun.
+        // TODO(ronghuawu): Refine the test criteria.
+        // https://code.google.com/p/webrtc/issues/detail?id=1953
+        if (expected.remote_type2 != RemoteCandidate(ep2_ch1())->type())
+          EXPECT_TRUE(expected.remote_type2 == cricket::LOCAL_PORT_TYPE ||
+                      expected.remote_type2 == cricket::STUN_PORT_TYPE);
+          EXPECT_TRUE(
+              RemoteCandidate(ep2_ch1())->type() == cricket::LOCAL_PORT_TYPE ||
+              RemoteCandidate(ep2_ch1())->type() == cricket::STUN_PORT_TYPE ||
+              RemoteCandidate(ep2_ch1())->type() == cricket::PRFLX_PORT_TYPE);
+      }
+
+      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)";
+      }
+    }
+    // Try sending some data to other end.
+    TestSendRecv(1);
+
+    // Destroy the channels, and wait for them to be fully cleaned up.
+    DestroyChannels();
+  }
+
+  void TestSendRecv(int channels) {
+    for (int i = 0; i < 10; ++i) {
+    const char* data = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
+      int len = static_cast<int>(strlen(data));
+      // local_channel1 <==> remote_channel1
+      EXPECT_EQ_WAIT(len, SendData(ep1_ch1(), data, len), 1000);
+      EXPECT_TRUE_WAIT(CheckDataOnChannel(ep2_ch1(), data, len), 1000);
+      EXPECT_EQ_WAIT(len, SendData(ep2_ch1(), data, len), 1000);
+      EXPECT_TRUE_WAIT(CheckDataOnChannel(ep1_ch1(), data, len), 1000);
+      if (channels == 2 && ep1_ch2() && ep2_ch2()) {
+        // local_channel2 <==> remote_channel2
+        EXPECT_EQ_WAIT(len, SendData(ep1_ch2(), data, len), 1000);
+        EXPECT_TRUE_WAIT(CheckDataOnChannel(ep2_ch2(), data, len), 1000);
+        EXPECT_EQ_WAIT(len, SendData(ep2_ch2(), data, len), 1000);
+        EXPECT_TRUE_WAIT(CheckDataOnChannel(ep1_ch2(), data, len), 1000);
+      }
+    }
+  }
+
+  // This test waits for the transport to become readable and writable on both
+  // end points. Once they are, the end points set new local ice credentials to
+  // restart the ice gathering. Finally it waits for the transport to select a
+  // new connection using the newly generated ice candidates.
+  // Before calling this function the end points must be configured.
+  void TestHandleIceUfragPasswordChanged() {
+    ep1_ch1()->SetRemoteIceCredentials(kIceUfrag[1], kIcePwd[1]);
+    ep2_ch1()->SetRemoteIceCredentials(kIceUfrag[0], kIcePwd[0]);
+    EXPECT_TRUE_WAIT_MARGIN(ep1_ch1()->readable() && ep1_ch1()->writable() &&
+                            ep2_ch1()->readable() && ep2_ch1()->writable(),
+                            1000, 1000);
+
+    const cricket::Candidate* old_local_candidate1 = LocalCandidate(ep1_ch1());
+    const cricket::Candidate* old_local_candidate2 = LocalCandidate(ep2_ch1());
+    const cricket::Candidate* old_remote_candidate1 =
+        RemoteCandidate(ep1_ch1());
+    const cricket::Candidate* old_remote_candidate2 =
+        RemoteCandidate(ep2_ch1());
+
+    ep1_ch1()->SetIceCredentials(kIceUfrag[2], kIcePwd[2]);
+    ep1_ch1()->SetRemoteIceCredentials(kIceUfrag[3], kIcePwd[3]);
+    ep2_ch1()->SetIceCredentials(kIceUfrag[3], kIcePwd[3]);
+    ep2_ch1()->SetRemoteIceCredentials(kIceUfrag[2], kIcePwd[2]);
+
+    EXPECT_TRUE_WAIT_MARGIN(LocalCandidate(ep1_ch1())->generation() !=
+                            old_local_candidate1->generation(),
+                            1000, 1000);
+    EXPECT_TRUE_WAIT_MARGIN(LocalCandidate(ep2_ch1())->generation() !=
+                            old_local_candidate2->generation(),
+                            1000, 1000);
+    EXPECT_TRUE_WAIT_MARGIN(RemoteCandidate(ep1_ch1())->generation() !=
+                            old_remote_candidate1->generation(),
+                            1000, 1000);
+    EXPECT_TRUE_WAIT_MARGIN(RemoteCandidate(ep2_ch1())->generation() !=
+                            old_remote_candidate2->generation(),
+                            1000, 1000);
+    EXPECT_EQ(1u, RemoteCandidate(ep2_ch1())->generation());
+    EXPECT_EQ(1u, RemoteCandidate(ep1_ch1())->generation());
+  }
+
+  void TestSignalRoleConflict() {
+    SetIceProtocol(0, cricket::ICEPROTO_RFC5245);
+    SetTiebreaker(0, kTiebreaker1);  // Default EP1 is in controlling state.
+
+    SetIceProtocol(1, cricket::ICEPROTO_RFC5245);
+    SetIceRole(1, cricket::ROLE_CONTROLLING);
+    SetTiebreaker(1, kTiebreaker2);
+
+    // Creating channels with both channels role set to CONTROLLING.
+    CreateChannels(1);
+    // Since both the channels initiated with controlling state and channel2
+    // has higher tiebreaker value, channel1 should receive SignalRoleConflict.
+    EXPECT_TRUE_WAIT(GetRoleConflict(0), 1000);
+    EXPECT_FALSE(GetRoleConflict(1));
+
+    EXPECT_TRUE_WAIT(ep1_ch1()->readable() &&
+                     ep1_ch1()->writable() &&
+                     ep2_ch1()->readable() &&
+                     ep2_ch1()->writable(),
+                     1000);
+
+    EXPECT_TRUE(ep1_ch1()->best_connection() &&
+                ep2_ch1()->best_connection());
+
+    TestSendRecv(1);
+  }
+
+  void OnChannelRequestSignaling(cricket::TransportChannelImpl* channel) {
+    channel->OnSignalingReady();
+  }
+  // We pass the candidates directly to the other side.
+  void OnCandidate(cricket::TransportChannelImpl* ch,
+                   const cricket::Candidate& c) {
+    main_->PostDelayed(GetEndpoint(ch)->signaling_delay_, this, 0,
+                       new CandidateData(ch, c));
+  }
+  void OnMessage(talk_base::Message* msg) {
+    talk_base::scoped_ptr<CandidateData> data(
+        static_cast<CandidateData*>(msg->pdata));
+    cricket::P2PTransportChannel* rch = GetRemoteChannel(data->channel);
+    cricket::Candidate c = data->candidate;
+    if (clear_remote_candidates_ufrag_pwd_) {
+      c.set_username("");
+      c.set_password("");
+    }
+    LOG(LS_INFO) << "Candidate(" << data->channel->component() << "->"
+                 << rch->component() << "): " << c.type() << ", " << c.protocol()
+                 << ", " << c.address().ToString() << ", " << c.username()
+                 << ", " << c.generation();
+    rch->OnCandidate(c);
+  }
+  void OnReadPacket(cricket::TransportChannel* channel, const char* data,
+                    size_t len, int flags) {
+    std::list<std::string>& packets = GetPacketList(channel);
+    packets.push_front(std::string(data, len));
+  }
+  void OnRoleConflict(cricket::TransportChannelImpl* channel) {
+    GetEndpoint(channel)->OnRoleConflict(true);
+    cricket::TransportRole new_role =
+        GetEndpoint(channel)->role() == cricket::ROLE_CONTROLLING ?
+            cricket::ROLE_CONTROLLED : cricket::ROLE_CONTROLLING;
+    channel->SetRole(new_role);
+  }
+  int SendData(cricket::TransportChannel* channel,
+               const char* data, size_t len) {
+    return channel->SendPacket(data, len, 0);
+  }
+  bool CheckDataOnChannel(cricket::TransportChannel* channel,
+                          const char* data, int len) {
+    return GetChannelData(channel)->CheckData(data, len);
+  }
+  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;
+  }
+  Endpoint* GetEndpoint(cricket::TransportChannel* ch) {
+    if (ep1_.HasChannel(ch)) {
+      return &ep1_;
+    } else if (ep2_.HasChannel(ch)) {
+      return &ep2_;
+    } else {
+      return NULL;
+    }
+  }
+  cricket::P2PTransportChannel* GetRemoteChannel(
+      cricket::TransportChannel* ch) {
+    if (ch == ep1_ch1())
+      return ep2_ch1();
+    else if (ch == ep1_ch2())
+      return ep2_ch2();
+    else if (ch == ep2_ch1())
+      return ep1_ch1();
+    else if (ch == ep2_ch2())
+      return ep1_ch2();
+    else
+      return NULL;
+  }
+  std::list<std::string>& GetPacketList(cricket::TransportChannel* ch) {
+    return GetChannelData(ch)->ch_packets_;
+  }
+
+  void set_clear_remote_candidates_ufrag_pwd(bool clear) {
+    clear_remote_candidates_ufrag_pwd_ = clear;
+  }
+
+ private:
+  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_;
+  Endpoint ep1_;
+  Endpoint ep2_;
+  bool clear_remote_candidates_ufrag_pwd_;
+};
+
+// The tests have only a few outcomes, which we predefine.
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+    kLocalUdpToLocalUdp("local", "udp", "local", "udp",
+                        "local", "udp", "local", "udp", 1000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+    kLocalUdpToStunUdp("local", "udp", "stun", "udp",
+                       "local", "udp", "stun", "udp", 1000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+    kLocalUdpToPrflxUdp("local", "udp", "prflx", "udp",
+                        "prflx", "udp", "local", "udp", 1000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+    kPrflxUdpToLocalUdp("prflx", "udp", "local", "udp",
+                        "local", "udp", "prflx", "udp", 1000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+    kStunUdpToLocalUdp("stun", "udp", "local", "udp",
+                       "local", "udp", "stun", "udp", 1000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+    kStunUdpToStunUdp("stun", "udp", "stun", "udp",
+                      "stun", "udp", "stun", "udp", 1000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+    kPrflxUdpToStunUdp("prflx", "udp", "stun", "udp",
+                       "local", "udp", "prflx", "udp", 1000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+    kLocalUdpToRelayUdp("local", "udp", "relay", "udp",
+                        "relay", "udp", "local", "udp", 2000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+    kPrflxUdpToRelayUdp("prflx", "udp", "relay", "udp",
+                        "relay", "udp", "prflx", "udp", 2000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+    kLocalTcpToLocalTcp("local", "tcp", "local", "tcp",
+                        "local", "tcp", "local", "tcp", 3000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+    kLocalTcpToPrflxTcp("local", "tcp", "prflx", "tcp",
+                        "prflx", "tcp", "local", "tcp", 3000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+    kPrflxTcpToLocalTcp("prflx", "tcp", "local", "tcp",
+                        "local", "tcp", "prflx", "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];
+  static const Result* kMatrixSharedUfrag[NUM_CONFIGS][NUM_CONFIGS];
+  static const Result* kMatrixSharedSocketAsGice[NUM_CONFIGS][NUM_CONFIGS];
+  static const Result* kMatrixSharedSocketAsIce[NUM_CONFIGS][NUM_CONFIGS];
+  void ConfigureEndpoints(Config config1, Config config2,
+      int allocator_flags1, int allocator_flags2,
+      int delay1, int delay2,
+      cricket::IceProtocolType type) {
+    ConfigureEndpoint(0, config1);
+    SetIceProtocol(0, type);
+    SetAllocatorFlags(0, allocator_flags1);
+    SetAllocationStepDelay(0, delay1);
+    ConfigureEndpoint(1, config2);
+    SetIceProtocol(1, type);
+    SetAllocatorFlags(1, allocator_flags2);
+    SetAllocationStepDelay(1, delay2);
+  }
+  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 LUPU &kLocalUdpToPrflxUdp
+#define PULU &kPrflxUdpToLocalUdp
+#define SULU &kStunUdpToLocalUdp
+#define SUSU &kStunUdpToStunUdp
+#define PUSU &kPrflxUdpToStunUdp
+#define LURU &kLocalUdpToRelayUdp
+#define PURU &kPrflxUdpToRelayUdp
+#define LTLT &kLocalTcpToLocalTcp
+#define LTPT &kLocalTcpToPrflxTcp
+#define PTLT &kPrflxTcpToLocalTcp
+// 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.
+
+// Currently the p2ptransportchannel.cc (specifically the
+// P2PTransportChannel::OnUnknownAddress) operates in 2 modes depend on the
+// remote candidates - ufrag per port or shared ufrag.
+// For example, if the remote candidates have the shared ufrag, for the unknown
+// address reaches the OnUnknownAddress, we will try to find the matched
+// remote candidate based on the address and protocol, if not found, a new
+// remote candidate will be created for this address. But if the remote
+// candidates have different ufrags, we will try to find the matched remote
+// candidate by comparing the ufrag. If not found, an error will be returned.
+// Because currently the shared ufrag feature is under the experiment and will
+// be rolled out gradually. We want to test the different combinations of peers
+// with/without the shared ufrag enabled. And those different combinations have
+// different expectation of the best connection. For example in the OpenToCONE
+// case, an unknown address will be updated to a "host" remote candidate if the
+// remote peer uses different ufrag per port. But in the shared ufrag case,
+// a "stun" (should be peer-reflexive eventually) candidate will be created for
+// that. So the expected best candidate will be LUSU instead of LULU.
+// With all these, we have to keep 2 test matrixes for the tests:
+// kMatrix - for the tests that the remote peer uses different ufrag per port.
+// kMatrixSharedUfrag - for the tests that remote peer uses shared ufrag.
+// The different between the two matrixes are on:
+// OPToCONE, OPTo2CON,
+// COToCONE, COToADDR, COToPORT, COToSYMM, COTo2CON, COToSCON,
+// ADToCONE, ADToADDR, ADTo2CON,
+// POToADDR,
+// SYToADDR,
+// 2CToCONE, 2CToADDR, 2CToPORT, 2CToSYMM, 2CTo2CON, 2CToSCON,
+// SCToADDR,
+
+// 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.
+// TODO(ronghuawu): Keep only one test matrix once the shared ufrag is enabled.
+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, LUSU, SUSU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT},
+/*SY*/ {LULU, LUSU, LUSU, 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, LUSU, 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},
+};
+const P2PTransportChannelTest::Result*
+    P2PTransportChannelTest::kMatrixSharedUfrag[NUM_CONFIGS][NUM_CONFIGS] = {
+//      OPEN  CONE  ADDR  PORT  SYMM  2CON  SCON  !UDP  !TCP  HTTP  PRXH  PRXS
+/*OP*/ {LULU, LUSU, LULU, LULU, LULU, LUSU, LULU, LTLT, LTLT, LSRS, NULL, LTLT},
+/*CO*/ {LULU, LUSU, LUSU, SUSU, SUSU, LUSU, SUSU, NULL, NULL, LSRS, NULL, LTRT},
+/*AD*/ {LULU, LUSU, LUSU, SUSU, SUSU, LUSU, SUSU, NULL, NULL, LSRS, NULL, LTRT},
+/*PO*/ {LULU, LUSU, LUSU, SUSU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT},
+/*SY*/ {LULU, LUSU, LUSU, LURU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT},
+/*2C*/ {LULU, LUSU, LUSU, SUSU, SUSU, LUSU, SUSU, NULL, NULL, LSRS, NULL, LTRT},
+/*SC*/ {LULU, LUSU, LUSU, 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},
+};
+const P2PTransportChannelTest::Result*
+    P2PTransportChannelTest::kMatrixSharedSocketAsGice
+        [NUM_CONFIGS][NUM_CONFIGS] = {
+//      OPEN  CONE  ADDR  PORT  SYMM  2CON  SCON  !UDP  !TCP  HTTP  PRXH  PRXS
+/*OP*/ {LULU, LUSU, LUSU, LUSU, LUSU, LUSU, LUSU, LTLT, LTLT, LSRS, NULL, LTLT},
+/*CO*/ {LULU, LUSU, LUSU, LUSU, LUSU, LUSU, LUSU, NULL, NULL, LSRS, NULL, LTRT},
+/*AD*/ {LULU, LUSU, LUSU, LUSU, LUSU, LUSU, LUSU, NULL, NULL, LSRS, NULL, LTRT},
+/*PO*/ {LULU, LUSU, LUSU, LUSU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT},
+/*SY*/ {LULU, LUSU, LUSU, LURU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT},
+/*2C*/ {LULU, LUSU, LUSU, LUSU, LUSU, LUSU, LUSU, NULL, NULL, LSRS, NULL, LTRT},
+/*SC*/ {LULU, LUSU, LUSU, 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},
+};
+const P2PTransportChannelTest::Result*
+    P2PTransportChannelTest::kMatrixSharedSocketAsIce
+        [NUM_CONFIGS][NUM_CONFIGS] = {
+//      OPEN  CONE  ADDR  PORT  SYMM  2CON  SCON  !UDP  !TCP  HTTP  PRXH  PRXS
+/*OP*/ {LULU, LUSU, LUSU, LUSU, LUPU, LUSU, LUPU, PTLT, LTPT, LSRS, NULL, PTLT},
+/*CO*/ {LULU, LUSU, LUSU, LUSU, LUPU, LUSU, LUPU, NULL, NULL, LSRS, NULL, LTRT},
+/*AD*/ {LULU, LUSU, LUSU, LUSU, LUPU, LUSU, LUPU, NULL, NULL, LSRS, NULL, LTRT},
+/*PO*/ {LULU, LUSU, LUSU, LUSU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT},
+/*SY*/ {PULU, PUSU, PUSU, PURU, PURU, PUSU, PURU, NULL, NULL, LSRS, NULL, LTRT},
+/*2C*/ {LULU, LUSU, LUSU, LUSU, LUPU, LUSU, LUPU, NULL, NULL, LSRS, NULL, LTRT},
+/*SC*/ {PULU, PUSU, PUSU, PURU, PURU, PUSU, PURU, NULL, NULL, LSRS, NULL, LTRT},
+/*!U*/ {PTLT, NULL, NULL, NULL, NULL, NULL, NULL, PTLT, LTPT, LSRS, NULL, LTRT},
+/*!T*/ {LTRT, NULL, NULL, NULL, NULL, NULL, NULL, PTLT, 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
+// Same test case is run in both GICE and ICE mode.
+// kDefaultStepDelay - is used for all Gice cases.
+// kMinimumStepDelay - is used when both end points have
+//                     PORTALLOCATOR_ENABLE_SHARED_UFRAG flag enabled.
+// Technically we should be able to use kMinimumStepDelay irrespective of
+// protocol type. But which might need modifications to current result matrices
+// for tests in this file.
+#define P2P_TEST_DECLARATION(x, y, z) \
+  TEST_F(P2PTransportChannelTest, z##Test##x##To##y##AsGiceNoneSharedUfrag) { \
+    ConfigureEndpoints(x, y, kDefaultPortAllocatorFlags, \
+                       kDefaultPortAllocatorFlags, \
+                       kDefaultStepDelay, kDefaultStepDelay, \
+                       cricket::ICEPROTO_GOOGLE); \
+    if (kMatrix[x][y] != NULL) \
+      Test(*kMatrix[x][y]); \
+    else \
+      LOG(LS_WARNING) << "Not yet implemented"; \
+  } \
+  TEST_F(P2PTransportChannelTest, z##Test##x##To##y##AsGiceP0SharedUfrag) { \
+    ConfigureEndpoints(x, y, PORTALLOCATOR_ENABLE_SHARED_UFRAG, \
+                       kDefaultPortAllocatorFlags, \
+                       kDefaultStepDelay, kDefaultStepDelay, \
+                       cricket::ICEPROTO_GOOGLE); \
+    if (kMatrix[x][y] != NULL) \
+      Test(*kMatrix[x][y]); \
+    else \
+      LOG(LS_WARNING) << "Not yet implemented"; \
+  } \
+  TEST_F(P2PTransportChannelTest, z##Test##x##To##y##AsGiceP1SharedUfrag) { \
+    ConfigureEndpoints(x, y, kDefaultPortAllocatorFlags, \
+                       PORTALLOCATOR_ENABLE_SHARED_UFRAG, \
+                       kDefaultStepDelay, kDefaultStepDelay, \
+                       cricket::ICEPROTO_GOOGLE); \
+    if (kMatrixSharedUfrag[x][y] != NULL) \
+      Test(*kMatrixSharedUfrag[x][y]); \
+    else \
+      LOG(LS_WARNING) << "Not yet implemented"; \
+  } \
+  TEST_F(P2PTransportChannelTest, z##Test##x##To##y##AsGiceBothSharedUfrag) { \
+    ConfigureEndpoints(x, y, PORTALLOCATOR_ENABLE_SHARED_UFRAG, \
+                       PORTALLOCATOR_ENABLE_SHARED_UFRAG, \
+                       kDefaultStepDelay, kDefaultStepDelay, \
+                       cricket::ICEPROTO_GOOGLE); \
+    if (kMatrixSharedUfrag[x][y] != NULL) \
+      Test(*kMatrixSharedUfrag[x][y]); \
+    else \
+      LOG(LS_WARNING) << "Not yet implemented"; \
+  } \
+  TEST_F(P2PTransportChannelTest, \
+         z##Test##x##To##y##AsGiceBothSharedUfragWithMinimumStepDelay) { \
+    ConfigureEndpoints(x, y, PORTALLOCATOR_ENABLE_SHARED_UFRAG, \
+                       PORTALLOCATOR_ENABLE_SHARED_UFRAG, \
+                       kMinimumStepDelay, kMinimumStepDelay, \
+                       cricket::ICEPROTO_GOOGLE); \
+    if (kMatrixSharedUfrag[x][y] != NULL) \
+      Test(*kMatrixSharedUfrag[x][y]); \
+    else \
+      LOG(LS_WARNING) << "Not yet implemented"; \
+  } \
+  TEST_F(P2PTransportChannelTest, \
+         z##Test##x##To##y##AsGiceBothSharedUfragSocket) { \
+    ConfigureEndpoints(x, y, PORTALLOCATOR_ENABLE_SHARED_UFRAG | \
+                       PORTALLOCATOR_ENABLE_SHARED_SOCKET, \
+                       PORTALLOCATOR_ENABLE_SHARED_UFRAG | \
+                       PORTALLOCATOR_ENABLE_SHARED_SOCKET, \
+                       kMinimumStepDelay, kMinimumStepDelay, \
+                       cricket::ICEPROTO_GOOGLE); \
+    if (kMatrixSharedSocketAsGice[x][y] != NULL) \
+      Test(*kMatrixSharedSocketAsGice[x][y]); \
+    else \
+    LOG(LS_WARNING) << "Not yet implemented"; \
+  } \
+  TEST_F(P2PTransportChannelTest, z##Test##x##To##y##AsIce) { \
+    ConfigureEndpoints(x, y, PORTALLOCATOR_ENABLE_SHARED_UFRAG | \
+                       PORTALLOCATOR_ENABLE_SHARED_SOCKET, \
+                       PORTALLOCATOR_ENABLE_SHARED_UFRAG | \
+                       PORTALLOCATOR_ENABLE_SHARED_SOCKET, \
+                       kMinimumStepDelay, kMinimumStepDelay, \
+                       cricket::ICEPROTO_RFC5245); \
+    if (kMatrixSharedSocketAsIce[x][y] != NULL) \
+      Test(*kMatrixSharedSocketAsIce[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) \
+  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)
+P2P_TEST_SET(NAT_ADDR_RESTRICTED)
+P2P_TEST_SET(NAT_PORT_RESTRICTED)
+P2P_TEST_SET(NAT_SYMMETRIC)
+P2P_TEST_SET(NAT_DOUBLE_CONE)
+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 we restart candidate allocation when local ufrag&pwd changed.
+// Standard Ice protocol is used.
+TEST_F(P2PTransportChannelTest, HandleUfragPwdChangeAsIce) {
+  ConfigureEndpoints(OPEN, OPEN,
+                     PORTALLOCATOR_ENABLE_SHARED_UFRAG,
+                     PORTALLOCATOR_ENABLE_SHARED_UFRAG,
+                     kMinimumStepDelay, kMinimumStepDelay,
+                     cricket::ICEPROTO_RFC5245);
+  CreateChannels(1);
+  TestHandleIceUfragPasswordChanged();
+}
+
+// Test that we restart candidate allocation when local ufrag&pwd changed.
+// Standard Ice protocol is used.
+TEST_F(P2PTransportChannelTest, HandleUfragPwdChangeBundleAsIce) {
+  ConfigureEndpoints(OPEN, OPEN,
+                     PORTALLOCATOR_ENABLE_SHARED_UFRAG,
+                     PORTALLOCATOR_ENABLE_SHARED_UFRAG,
+                     kMinimumStepDelay, kMinimumStepDelay,
+                     cricket::ICEPROTO_RFC5245);
+  SetAllocatorFlags(0, cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+  SetAllocatorFlags(1, cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+
+  CreateChannels(2);
+  TestHandleIceUfragPasswordChanged();
+}
+
+// Test that we restart candidate allocation when local ufrag&pwd changed.
+// Google Ice protocol is used.
+TEST_F(P2PTransportChannelTest, HandleUfragPwdChangeAsGice) {
+  ConfigureEndpoints(OPEN, OPEN,
+                     PORTALLOCATOR_ENABLE_SHARED_UFRAG,
+                     PORTALLOCATOR_ENABLE_SHARED_UFRAG,
+                     kDefaultStepDelay, kDefaultStepDelay,
+                     cricket::ICEPROTO_GOOGLE);
+  CreateChannels(1);
+  TestHandleIceUfragPasswordChanged();
+}
+
+// Test that ICE restart works when bundle is enabled.
+// Google Ice protocol is used.
+TEST_F(P2PTransportChannelTest, HandleUfragPwdChangeBundleAsGice) {
+  ConfigureEndpoints(OPEN, OPEN,
+                     PORTALLOCATOR_ENABLE_SHARED_UFRAG,
+                     PORTALLOCATOR_ENABLE_SHARED_UFRAG,
+                     kDefaultStepDelay, kDefaultStepDelay,
+                     cricket::ICEPROTO_GOOGLE);
+  SetAllocatorFlags(0, cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+  SetAllocatorFlags(1, cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+
+  CreateChannels(2);
+  TestHandleIceUfragPasswordChanged();
+}
+
+// Test the operation of GetStats.
+TEST_F(P2PTransportChannelTest, GetStats) {
+  ConfigureEndpoints(OPEN, OPEN,
+                     kDefaultPortAllocatorFlags,
+                     kDefaultPortAllocatorFlags,
+                     kDefaultStepDelay, kDefaultStepDelay,
+                     cricket::ICEPROTO_GOOGLE);
+  CreateChannels(1);
+  EXPECT_TRUE_WAIT_MARGIN(ep1_ch1()->readable() && ep1_ch1()->writable() &&
+                          ep2_ch1()->readable() && ep2_ch1()->writable(),
+                          1000, 1000);
+  TestSendRecv(1);
+  cricket::ConnectionInfos infos;
+  ASSERT_TRUE(ep1_ch1()->GetStats(&infos));
+  ASSERT_EQ(1U, infos.size());
+  EXPECT_TRUE(infos[0].new_connection);
+  EXPECT_TRUE(infos[0].best_connection);
+  EXPECT_TRUE(infos[0].readable);
+  EXPECT_TRUE(infos[0].writable);
+  EXPECT_FALSE(infos[0].timeout);
+  EXPECT_EQ(10 * 36U, infos[0].sent_total_bytes);
+  EXPECT_EQ(10 * 36U, infos[0].recv_total_bytes);
+  EXPECT_GT(infos[0].rtt, 0U);
+  DestroyChannels();
+}
+
+// Test that we properly handle getting a STUN error due to slow signaling.
+TEST_F(P2PTransportChannelTest, SlowSignaling) {
+  ConfigureEndpoints(OPEN, NAT_SYMMETRIC,
+                     kDefaultPortAllocatorFlags,
+                     kDefaultPortAllocatorFlags,
+                     kDefaultStepDelay, kDefaultStepDelay,
+                     cricket::ICEPROTO_GOOGLE);
+  // Make signaling from the callee take 500ms, so that the initial STUN pings
+  // from the callee beat the signaling, and so the caller responds with a
+  // unknown username error. We should just eat that and carry on; mishandling
+  // this will instead cause all the callee's connections to be discarded.
+  SetSignalingDelay(1, 1000);
+  CreateChannels(1);
+  const cricket::Connection* best_connection = NULL;
+  // Wait until the callee's connections are created.
+  WAIT((best_connection = ep2_ch1()->best_connection()) != NULL, 1000);
+  // Wait to see if they get culled; they shouldn't.
+  WAIT(ep2_ch1()->best_connection() != best_connection, 1000);
+  EXPECT_TRUE(ep2_ch1()->best_connection() == best_connection);
+  DestroyChannels();
+}
+
+// Test that if remote candidates don't have ufrag and pwd, we still work.
+TEST_F(P2PTransportChannelTest, RemoteCandidatesWithoutUfragPwd) {
+  set_clear_remote_candidates_ufrag_pwd(true);
+  ConfigureEndpoints(OPEN, OPEN,
+                     PORTALLOCATOR_ENABLE_SHARED_UFRAG,
+                     PORTALLOCATOR_ENABLE_SHARED_UFRAG,
+                     kMinimumStepDelay, kMinimumStepDelay,
+                     cricket::ICEPROTO_GOOGLE);
+  CreateChannels(1);
+  const cricket::Connection* best_connection = NULL;
+  // Wait until the callee's connections are created.
+  WAIT((best_connection = ep2_ch1()->best_connection()) != NULL, 1000);
+  // Wait to see if they get culled; they shouldn't.
+  WAIT(ep2_ch1()->best_connection() != best_connection, 1000);
+  EXPECT_TRUE(ep2_ch1()->best_connection() == best_connection);
+  DestroyChannels();
+}
+
+// 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,
+                     kDefaultPortAllocatorFlags,
+                     kDefaultPortAllocatorFlags,
+                     kDefaultStepDelay, kDefaultStepDelay,
+                     cricket::ICEPROTO_GOOGLE);
+
+  SetAllocatorFlags(0, kOnlyLocalPorts);
+  CreateChannels(1);
+  ep1_ch1()->set_incoming_only(true);
+
+  // Pump for 1 second and verify that the channels are not connected.
+  talk_base::Thread::Current()->ProcessMessages(1000);
+
+  EXPECT_FALSE(ep1_ch1()->readable());
+  EXPECT_FALSE(ep1_ch1()->writable());
+  EXPECT_FALSE(ep2_ch1()->readable());
+  EXPECT_FALSE(ep2_ch1()->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,
+                     kDefaultPortAllocatorFlags,
+                     kDefaultPortAllocatorFlags,
+                     kDefaultStepDelay, kDefaultStepDelay,
+                     cricket::ICEPROTO_GOOGLE);
+
+  SetAllocatorFlags(0, kOnlyLocalPorts);
+  CreateChannels(1);
+  ep1_ch1()->set_incoming_only(true);
+
+  EXPECT_TRUE_WAIT_MARGIN(ep1_ch1() != NULL && ep2_ch1() != NULL &&
+                          ep1_ch1()->readable() && ep1_ch1()->writable() &&
+                          ep2_ch1()->readable() && ep2_ch1()->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(kLocalUdpToStunUdp);
+}
+
+// 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(1);
+  EXPECT_TRUE_WAIT(ep1_ch1()->readable() && ep1_ch1()->writable() &&
+                   ep2_ch1()->readable() && ep2_ch1()->writable(),
+                   1000);
+  EXPECT_TRUE(
+      ep1_ch1()->best_connection() && ep2_ch1()->best_connection() &&
+      LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]) &&
+      RemoteCandidate(ep1_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(!ep1_ch1()->writable(), 7000);
+
+  // We should switch over to use the alternate addr immediately
+  // when we lose writability.
+  EXPECT_TRUE_WAIT(
+      ep1_ch1()->best_connection() && ep2_ch1()->best_connection() &&
+      LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]) &&
+      RemoteCandidate(ep1_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(1);
+  EXPECT_TRUE_WAIT(ep1_ch1()->readable() && ep1_ch1()->writable() &&
+                   ep2_ch1()->readable() && ep2_ch1()->writable(),
+                   1000);
+  EXPECT_TRUE(
+      ep1_ch1()->best_connection() && ep2_ch1()->best_connection() &&
+      LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]) &&
+      RemoteCandidate(ep1_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]);
+  ep2_ch1()->Connect();
+
+  // We should switch over to use the alternate address after
+  // an exchange of pings.
+  EXPECT_TRUE_WAIT(
+      ep1_ch1()->best_connection() && ep2_ch1()->best_connection() &&
+      LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]) &&
+      RemoteCandidate(ep1_ch1())->address().EqualIPs(kAlternateAddrs[1]),
+      3000);
+
+  DestroyChannels();
+}
+
+TEST_F(P2PTransportChannelTest, TestBundleAllocatorToBundleAllocator) {
+  AddAddress(0, kPublicAddrs[0]);
+  AddAddress(1, kPublicAddrs[1]);
+  SetAllocatorFlags(0, cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+  SetAllocatorFlags(1, cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+
+  CreateChannels(2);
+
+  EXPECT_TRUE_WAIT(ep1_ch1()->readable() &&
+                   ep1_ch1()->writable() &&
+                   ep2_ch1()->readable() &&
+                   ep2_ch1()->writable(),
+                   1000);
+  EXPECT_TRUE(ep1_ch1()->best_connection() &&
+              ep2_ch1()->best_connection());
+
+  EXPECT_FALSE(ep1_ch2()->readable());
+  EXPECT_FALSE(ep1_ch2()->writable());
+  EXPECT_FALSE(ep2_ch2()->readable());
+  EXPECT_FALSE(ep2_ch2()->writable());
+
+  TestSendRecv(1);  // Only 1 channel is writable per Endpoint.
+  DestroyChannels();
+}
+
+TEST_F(P2PTransportChannelTest, TestBundleAllocatorToNonBundleAllocator) {
+  AddAddress(0, kPublicAddrs[0]);
+  AddAddress(1, kPublicAddrs[1]);
+  // Enable BUNDLE flag at one side.
+  SetAllocatorFlags(0, cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+
+  CreateChannels(2);
+
+  EXPECT_TRUE_WAIT(ep1_ch1()->readable() &&
+                   ep1_ch1()->writable() &&
+                   ep2_ch1()->readable() &&
+                   ep2_ch1()->writable(),
+                   1000);
+  EXPECT_TRUE_WAIT(ep1_ch2()->readable() &&
+                   ep1_ch2()->writable() &&
+                   ep2_ch2()->readable() &&
+                   ep2_ch2()->writable(),
+                   1000);
+
+  EXPECT_TRUE(ep1_ch1()->best_connection() &&
+              ep2_ch1()->best_connection());
+  EXPECT_TRUE(ep1_ch2()->best_connection() &&
+              ep2_ch2()->best_connection());
+
+  TestSendRecv(2);
+  DestroyChannels();
+}
+
+TEST_F(P2PTransportChannelTest, TestIceRoleConflictWithoutBundle) {
+  AddAddress(0, kPublicAddrs[0]);
+  AddAddress(1, kPublicAddrs[1]);
+  TestSignalRoleConflict();
+}
+
+TEST_F(P2PTransportChannelTest, TestIceRoleConflictWithBundle) {
+  AddAddress(0, kPublicAddrs[0]);
+  AddAddress(1, kPublicAddrs[1]);
+  SetAllocatorFlags(0, cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+  SetAllocatorFlags(1, cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+  TestSignalRoleConflict();
+}
+
+// Tests that the ice configs (protocol, tiebreaker and role can be passed down
+// to ports.
+TEST_F(P2PTransportChannelTest, TestIceConfigWillPassDownToPort) {
+  AddAddress(0, kPublicAddrs[0]);
+  AddAddress(1, kPublicAddrs[1]);
+
+  SetIceRole(0, cricket::ROLE_CONTROLLING);
+  SetIceProtocol(0, cricket::ICEPROTO_GOOGLE);
+  SetTiebreaker(0, kTiebreaker1);
+  SetIceRole(1, cricket::ROLE_CONTROLLING);
+  SetIceProtocol(1, cricket::ICEPROTO_RFC5245);
+  SetTiebreaker(1, kTiebreaker2);
+
+  CreateChannels(1);
+
+  EXPECT_EQ_WAIT(2u, ep1_ch1()->ports().size(), 1000);
+
+  const std::vector<cricket::PortInterface *> ports_before = ep1_ch1()->ports();
+  for (size_t i = 0; i < ports_before.size(); ++i) {
+    EXPECT_EQ(cricket::ROLE_CONTROLLING, ports_before[i]->Role());
+    EXPECT_EQ(cricket::ICEPROTO_GOOGLE, ports_before[i]->IceProtocol());
+    EXPECT_EQ(kTiebreaker1, ports_before[i]->Tiebreaker());
+  }
+
+  ep1_ch1()->SetRole(cricket::ROLE_CONTROLLED);
+  ep1_ch1()->SetIceProtocolType(cricket::ICEPROTO_RFC5245);
+  ep1_ch1()->SetTiebreaker(kTiebreaker2);
+
+  const std::vector<cricket::PortInterface *> ports_after = ep1_ch1()->ports();
+  for (size_t i = 0; i < ports_after.size(); ++i) {
+    EXPECT_EQ(cricket::ROLE_CONTROLLED, ports_before[i]->Role());
+    EXPECT_EQ(cricket::ICEPROTO_RFC5245, ports_before[i]->IceProtocol());
+    // SetTiebreaker after Connect() has been called will fail. So expect the
+    // original value.
+    EXPECT_EQ(kTiebreaker1, ports_before[i]->Tiebreaker());
+  }
+
+  EXPECT_TRUE_WAIT(ep1_ch1()->readable() &&
+                   ep1_ch1()->writable() &&
+                   ep2_ch1()->readable() &&
+                   ep2_ch1()->writable(),
+                   1000);
+
+  EXPECT_TRUE(ep1_ch1()->best_connection() &&
+              ep2_ch1()->best_connection());
+
+  TestSendRecv(1);
+}
diff --git a/talk/p2p/base/packetsocketfactory.h b/talk/p2p/base/packetsocketfactory.h
new file mode 100644
index 0000000..b8a9846
--- /dev/null
+++ b/talk/p2p/base/packetsocketfactory.h
@@ -0,0 +1,65 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_PACKETSOCKETFACTORY_H_
+#define TALK_BASE_PACKETSOCKETFACTORY_H_
+
+#include "talk/base/proxyinfo.h"
+
+namespace talk_base {
+
+class AsyncPacketSocket;
+
+class PacketSocketFactory {
+ public:
+  enum Options {
+    OPT_SSLTCP = 0x01,
+    OPT_STUN = 0x02,
+  };
+
+  PacketSocketFactory() { }
+  virtual ~PacketSocketFactory() { }
+
+  virtual AsyncPacketSocket* CreateUdpSocket(
+      const SocketAddress& address, int min_port, int max_port) = 0;
+  virtual AsyncPacketSocket* CreateServerTcpSocket(
+      const SocketAddress& local_address, int min_port, int max_port,
+      int opts) = 0;
+
+  // 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 ProxyInfo& proxy_info, const std::string& user_agent, int opts) = 0;
+
+ private:
+  DISALLOW_EVIL_CONSTRUCTORS(PacketSocketFactory);
+};
+
+}  // namespace talk_base
+
+#endif  // TALK_BASE_PACKETSOCKETFACTORY_H_
diff --git a/talk/p2p/base/parsing.cc b/talk/p2p/base/parsing.cc
new file mode 100644
index 0000000..ebe0596
--- /dev/null
+++ b/talk/p2p/base/parsing.cc
@@ -0,0 +1,158 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/p2p/base/parsing.h"
+
+#include <algorithm>
+#include <stdlib.h>
+#include "talk/base/stringutils.h"
+
+namespace {
+static const char kTrue[] = "true";
+static const char kOne[] = "1";
+}
+
+namespace cricket {
+
+bool BadParse(const std::string& text, ParseError* err) {
+  if (err != NULL) {
+    err->text = text;
+  }
+  return false;
+}
+
+bool BadWrite(const std::string& text, WriteError* err) {
+  if (err != NULL) {
+    err->text = text;
+  }
+  return false;
+}
+
+std::string GetXmlAttr(const buzz::XmlElement* elem,
+                       const buzz::QName& name,
+                       const std::string& def) {
+  std::string val = elem->Attr(name);
+  return val.empty() ? def : val;
+}
+
+std::string GetXmlAttr(const buzz::XmlElement* elem,
+                       const buzz::QName& name,
+                       const char* def) {
+    return GetXmlAttr(elem, name, std::string(def));
+}
+
+bool GetXmlAttr(const buzz::XmlElement* elem,
+                const buzz::QName& name, bool def) {
+  std::string val = elem->Attr(name);
+  std::transform(val.begin(), val.end(), val.begin(), tolower);
+
+  return val.empty() ? def : (val == kTrue || val == kOne);
+}
+
+int GetXmlAttr(const buzz::XmlElement* elem,
+               const buzz::QName& name, int def) {
+  std::string val = elem->Attr(name);
+  return val.empty() ? def : atoi(val.c_str());
+}
+
+const buzz::XmlElement* GetXmlChild(const buzz::XmlElement* parent,
+                                    const std::string& name) {
+  for (const buzz::XmlElement* child = parent->FirstElement();
+       child != NULL;
+       child = child->NextElement()) {
+    if (child->Name().LocalPart() == name) {
+      return child;
+    }
+  }
+  return NULL;
+}
+
+bool RequireXmlChild(const buzz::XmlElement* parent,
+                     const std::string& name,
+                     const buzz::XmlElement** child,
+                     ParseError* error) {
+  *child = GetXmlChild(parent, name);
+  if (*child == NULL) {
+    return BadParse("element '" + parent->Name().Merged() +
+                    "' missing required child '" + name,
+                    error);
+  } else {
+    return true;
+  }
+}
+
+bool RequireXmlAttr(const buzz::XmlElement* elem,
+                    const buzz::QName& name,
+                    std::string* value,
+                    ParseError* error) {
+  if (!elem->HasAttr(name)) {
+    return BadParse("element '" + elem->Name().Merged() +
+                    "' missing required attribute '"
+                    + name.Merged() + "'",
+                    error);
+  } else {
+    *value = elem->Attr(name);
+    return true;
+  }
+}
+
+void 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();
+       iter != children.end();
+       iter++) {
+    parent->AddElement(*iter);
+  }
+}
+
+void CopyXmlChildren(const buzz::XmlElement* source, buzz::XmlElement* dest) {
+  for (const buzz::XmlElement* child = source->FirstElement();
+       child != NULL;
+       child = child->NextElement()) {
+    dest->AddElement(new buzz::XmlElement(*child));
+  }
+}
+
+std::vector<buzz::XmlElement*> CopyOfXmlChildren(const buzz::XmlElement* elem) {
+  std::vector<buzz::XmlElement*> children;
+  for (const buzz::XmlElement* child = elem->FirstElement();
+       child != NULL;
+       child = child->NextElement()) {
+    children.push_back(new buzz::XmlElement(*child));
+  }
+  return children;
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/parsing.h b/talk/p2p/base/parsing.h
new file mode 100644
index 0000000..c820056
--- /dev/null
+++ b/talk/p2p/base/parsing.h
@@ -0,0 +1,157 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_PARSING_H_
+#define TALK_P2P_BASE_PARSING_H_
+
+#include <string>
+#include <vector>
+#include "talk/base/basictypes.h"
+#include "talk/base/stringencode.h"
+#include "talk/xmllite/xmlelement.h"  // Needed to delete ParseError.extra.
+
+namespace cricket {
+
+typedef std::vector<buzz::XmlElement*> XmlElements;
+
+// We decided "bool Parse(in, out*, error*)" is generally the best
+// parse signature.  "out Parse(in)" doesn't allow for errors.
+// "error* Parse(in, out*)" doesn't allow flexible memory management.
+
+// The error type for parsing.
+struct ParseError {
+ public:
+  // explains the error
+  std::string text;
+  // provide details about what wasn't parsable
+  const buzz::XmlElement* extra;
+
+  ParseError() : extra(NULL) {}
+
+  ~ParseError() {
+    delete extra;
+  }
+
+  void SetText(const std::string& text) {
+    this->text = text;
+  }
+};
+
+// The error type for writing.
+struct WriteError {
+  std::string text;
+
+  void SetText(const std::string& text) {
+    this->text = text;
+  }
+};
+
+// Convenience method for returning a message when parsing fails.
+bool BadParse(const std::string& text, ParseError* err);
+
+// Convenience method for returning a message when writing fails.
+bool BadWrite(const std::string& text, WriteError* error);
+
+// helper XML functions
+std::string GetXmlAttr(const buzz::XmlElement* elem,
+                       const buzz::QName& name,
+                       const std::string& def);
+std::string GetXmlAttr(const buzz::XmlElement* elem,
+                       const buzz::QName& name,
+                       const char* def);
+// Return true if the value is "true" or "1".
+bool GetXmlAttr(const buzz::XmlElement* elem,
+                const buzz::QName& name, bool def);
+int GetXmlAttr(const buzz::XmlElement* elem,
+               const buzz::QName& name, int def);
+
+template <class T>
+bool GetXmlAttr(const buzz::XmlElement* elem,
+                const buzz::QName& name,
+                T* val_out) {
+  if (!elem->HasAttr(name)) {
+    return false;
+  }
+  std::string unparsed = elem->Attr(name);
+  return talk_base::FromString(unparsed, val_out);
+}
+
+template <class T>
+bool GetXmlAttr(const buzz::XmlElement* elem,
+                const buzz::QName& name,
+                const T& def,
+                T* val_out) {
+  if (!elem->HasAttr(name)) {
+    *val_out = def;
+    return true;
+  }
+  return GetXmlAttr(elem, name, val_out);
+}
+
+template <class T>
+bool AddXmlAttr(buzz::XmlElement* elem,
+                const buzz::QName& name, const T& val) {
+  std::string buf;
+  if (!talk_base::ToString(val, &buf)) {
+    return false;
+  }
+  elem->AddAttr(name, buf);
+  return true;
+}
+
+template <class T>
+bool SetXmlBody(buzz::XmlElement* elem, const T& val) {
+  std::string buf;
+  if (!talk_base::ToString(val, &buf)) {
+    return false;
+  }
+  elem->SetBodyText(buf);
+  return true;
+}
+
+const buzz::XmlElement* GetXmlChild(const buzz::XmlElement* parent,
+                                    const std::string& name);
+
+bool RequireXmlChild(const buzz::XmlElement* parent,
+                     const std::string& name,
+                     const buzz::XmlElement** child,
+                     ParseError* error);
+bool RequireXmlAttr(const buzz::XmlElement* elem,
+                    const buzz::QName& name,
+                    std::string* value,
+                    ParseError* error);
+void 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);
+std::vector<buzz::XmlElement*> CopyOfXmlChildren(const buzz::XmlElement* elem);
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_PARSING_H_
diff --git a/talk/p2p/base/port.cc b/talk/p2p/base/port.cc
new file mode 100644
index 0000000..b310fea
--- /dev/null
+++ b/talk/p2p/base/port.cc
@@ -0,0 +1,1400 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/p2p/base/port.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "talk/base/base64.h"
+#include "talk/base/crc32.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/messagedigest.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+#include "talk/p2p/base/common.h"
+
+namespace {
+
+// Determines whether we have seen at least the given maximum number of
+// pings fail to have a response.
+inline bool TooManyFailures(
+    const std::vector<uint32>& pings_since_last_response,
+    uint32 maximum_failures,
+    uint32 rtt_estimate,
+    uint32 now) {
+
+  // If we haven't sent that many pings, then we can't have failed that many.
+  if (pings_since_last_response.size() < maximum_failures)
+    return false;
+
+  // Check if the window in which we would expect a response to the ping has
+  // already elapsed.
+  return pings_since_last_response[maximum_failures - 1] + rtt_estimate < now;
+}
+
+// Determines whether we have gone too long without seeing any response.
+inline bool TooLongWithoutResponse(
+    const std::vector<uint32>& pings_since_last_response,
+    uint32 maximum_time,
+    uint32 now) {
+
+  if (pings_since_last_response.size() == 0)
+    return false;
+
+  return pings_since_last_response[0] + maximum_time < now;
+}
+
+// GICE(ICEPROTO_GOOGLE) requires different username for RTP and RTCP.
+// This function generates a different username by +1 on the last character of
+// the given username (|rtp_ufrag|).
+std::string GetRtcpUfragFromRtpUfrag(const std::string& rtp_ufrag) {
+  ASSERT(!rtp_ufrag.empty());
+  if (rtp_ufrag.empty()) {
+    return rtp_ufrag;
+  }
+  // Change the last character to the one next to it in the base64 table.
+  char new_last_char;
+  if (!talk_base::Base64::GetNextBase64Char(rtp_ufrag[rtp_ufrag.size() - 1],
+                                            &new_last_char)) {
+    // Should not be here.
+    ASSERT(false);
+  }
+  std::string rtcp_ufrag = rtp_ufrag;
+  rtcp_ufrag[rtcp_ufrag.size() - 1] = new_last_char;
+  ASSERT(rtcp_ufrag != rtp_ufrag);
+  return rtcp_ufrag;
+}
+
+// We will restrict RTT estimates (when used for determining state) to be
+// within a reasonable range.
+const uint32 MINIMUM_RTT = 100;   // 0.1 seconds
+const uint32 MAXIMUM_RTT = 3000;  // 3 seconds
+
+// When we don't have any RTT data, we have to pick something reasonable.  We
+// use a large value just in case the connection is really slow.
+const uint32 DEFAULT_RTT = MAXIMUM_RTT;
+
+// Computes our estimate of the RTT given the current estimate.
+inline uint32 ConservativeRTTEstimate(uint32 rtt) {
+  return talk_base::_max(MINIMUM_RTT, talk_base::_min(MAXIMUM_RTT, 2 * rtt));
+}
+
+// Weighting of the old rtt value to new data.
+const int RTT_RATIO = 3;  // 3 : 1
+
+// The delay before we begin checking if this port is useless.
+const int kPortTimeoutDelay = 30 * 1000;  // 30 seconds
+
+const uint32 MSG_CHECKTIMEOUT = 1;
+const uint32 MSG_DELETE = 1;
+}
+
+namespace cricket {
+
+// TODO(ronghuawu): Use "host", "srflx", "prflx" and "relay". But this requires
+// the signaling part be updated correspondingly as well.
+const char LOCAL_PORT_TYPE[] = "local";
+const char STUN_PORT_TYPE[] = "stun";
+const char PRFLX_PORT_TYPE[] = "prflx";
+const char RELAY_PORT_TYPE[] = "relay";
+
+const char UDP_PROTOCOL_NAME[] = "udp";
+const char TCP_PROTOCOL_NAME[] = "tcp";
+const char SSLTCP_PROTOCOL_NAME[] = "ssltcp";
+
+static const char* const PROTO_NAMES[] = { UDP_PROTOCOL_NAME,
+                                           TCP_PROTOCOL_NAME,
+                                           SSLTCP_PROTOCOL_NAME };
+
+const char* ProtoToString(ProtocolType proto) {
+  return PROTO_NAMES[proto];
+}
+
+bool StringToProto(const char* value, ProtocolType* proto) {
+  for (size_t i = 0; i <= PROTO_LAST; ++i) {
+    if (_stricmp(PROTO_NAMES[i], value) == 0) {
+      *proto = static_cast<ProtocolType>(i);
+      return true;
+    }
+  }
+  return false;
+}
+
+// Foundation:  An arbitrary string that is the same for two candidates
+//   that have the same type, base IP address, protocol (UDP, TCP,
+//   etc.), and STUN or TURN server.  If any of these are different,
+//   then the foundation will be different.  Two candidate pairs with
+//   the same foundation pairs are likely to have similar network
+//   characteristics.  Foundations are used in the frozen algorithm.
+static std::string ComputeFoundation(
+    const std::string& type,
+    const std::string& protocol,
+    const talk_base::SocketAddress& base_address) {
+  std::ostringstream ost;
+  ost << type << base_address.ipaddr().ToString() << protocol;
+  return talk_base::ToString<uint32>(talk_base::ComputeCrc32(ost.str()));
+}
+
+Port::Port(talk_base::Thread* thread, talk_base::Network* network,
+           const talk_base::IPAddress& ip,
+           const std::string& username_fragment, const std::string& password)
+    : thread_(thread),
+      factory_(NULL),
+      send_retransmit_count_attribute_(false),
+      network_(network),
+      ip_(ip),
+      min_port_(0),
+      max_port_(0),
+      component_(ICE_CANDIDATE_COMPONENT_DEFAULT),
+      generation_(0),
+      ice_username_fragment_(username_fragment),
+      password_(password),
+      lifetime_(LT_PRESTART),
+      enable_port_packets_(false),
+      ice_protocol_(ICEPROTO_GOOGLE),
+      role_(ROLE_UNKNOWN),
+      tiebreaker_(0),
+      shared_socket_(true) {
+  Construct();
+}
+
+Port::Port(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, const std::string& username_fragment,
+           const std::string& password)
+    : thread_(thread),
+      factory_(factory),
+      type_(type),
+      send_retransmit_count_attribute_(false),
+      network_(network),
+      ip_(ip),
+      min_port_(min_port),
+      max_port_(max_port),
+      component_(ICE_CANDIDATE_COMPONENT_DEFAULT),
+      generation_(0),
+      ice_username_fragment_(username_fragment),
+      password_(password),
+      lifetime_(LT_PRESTART),
+      enable_port_packets_(false),
+      ice_protocol_(ICEPROTO_GOOGLE),
+      role_(ROLE_UNKNOWN),
+      tiebreaker_(0),
+      shared_socket_(false) {
+  ASSERT(factory_ != NULL);
+  Construct();
+}
+
+void Port::Construct() {
+  // If the username_fragment and password are empty, we should just create one.
+  if (ice_username_fragment_.empty()) {
+    ASSERT(password_.empty());
+    ice_username_fragment_ = talk_base::CreateRandomString(ICE_UFRAG_LENGTH);
+    password_ = talk_base::CreateRandomString(ICE_PWD_LENGTH);
+  }
+  LOG_J(LS_INFO, this) << "Port created";
+}
+
+Port::~Port() {
+  // Delete all of the remaining connections.  We copy the list up front
+  // because each deletion will cause it to be modified.
+
+  std::vector<Connection*> list;
+
+  AddressMap::iterator iter = connections_.begin();
+  while (iter != connections_.end()) {
+    list.push_back(iter->second);
+    ++iter;
+  }
+
+  for (uint32 i = 0; i < list.size(); i++)
+    delete list[i];
+}
+
+Connection* Port::GetConnection(const talk_base::SocketAddress& remote_addr) {
+  AddressMap::const_iterator iter = connections_.find(remote_addr);
+  if (iter != connections_.end())
+    return iter->second;
+  else
+    return NULL;
+}
+
+void Port::AddAddress(const talk_base::SocketAddress& address,
+                      const talk_base::SocketAddress& base_address,
+                      const std::string& protocol,
+                      const std::string& type,
+                      uint32 type_preference,
+                      bool final) {
+  Candidate c;
+  c.set_id(talk_base::CreateRandomString(8));
+  c.set_component(component_);
+  c.set_type(type);
+  c.set_protocol(protocol);
+  c.set_address(address);
+  c.set_priority(c.GetPriority(type_preference));
+  c.set_username(username_fragment());
+  c.set_password(password_);
+  c.set_network_name(network_->name());
+  c.set_generation(generation_);
+  c.set_related_address(related_address_);
+  c.set_foundation(ComputeFoundation(type, protocol, base_address));
+  candidates_.push_back(c);
+  SignalCandidateReady(this, c);
+
+  if (final) {
+    SignalPortComplete(this);
+  }
+}
+
+void Port::AddConnection(Connection* conn) {
+  connections_[conn->remote_candidate().address()] = conn;
+  conn->SignalDestroyed.connect(this, &Port::OnConnectionDestroyed);
+  SignalConnectionCreated(this, conn);
+}
+
+void Port::OnReadPacket(
+    const char* data, size_t size, const talk_base::SocketAddress& addr,
+    ProtocolType proto) {
+  // If the user has enabled port packets, just hand this over.
+  if (enable_port_packets_) {
+    SignalReadPacket(this, data, size, addr);
+    return;
+  }
+
+  // If this is an authenticated STUN request, then signal unknown address and
+  // send back a proper binding response.
+  talk_base::scoped_ptr<IceMessage> msg;
+  std::string remote_username;
+  if (!GetStunMessage(data, size, addr, msg.accept(), &remote_username)) {
+    LOG_J(LS_ERROR, this) << "Received non-STUN packet from unknown address ("
+                          << addr.ToSensitiveString() << ")";
+  } else if (!msg) {
+    // STUN message handled already
+  } else if (msg->type() == STUN_BINDING_REQUEST) {
+    // Check for role conflicts.
+    if (IsStandardIce() &&
+        !MaybeIceRoleConflict(addr, msg.get(), remote_username)) {
+      LOG(LS_INFO) << "Received conflicting role from the peer.";
+      return;
+    }
+
+    SignalUnknownAddress(this, addr, proto, msg.get(), 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,
+    // because we then get back responses for them, which this code correctly
+    // does not handle.
+    if (msg->type() != STUN_BINDING_RESPONSE) {
+      LOG_J(LS_ERROR, this) << "Received unexpected STUN message type ("
+                            << msg->type() << ") from unknown address ("
+                            << addr.ToSensitiveString() << ")";
+    }
+  }
+}
+
+void Port::OnReadyToSend() {
+  AddressMap::iterator iter = connections_.begin();
+  for (; iter != connections_.end(); ++iter) {
+    iter->second->OnReadyToSend();
+  }
+}
+
+size_t Port::AddPrflxCandidate(const Candidate& local) {
+  candidates_.push_back(local);
+  return (candidates_.size() - 1);
+}
+
+bool Port::IsStandardIce() const {
+  return (ice_protocol_ == ICEPROTO_RFC5245);
+}
+
+bool Port::IsGoogleIce() const {
+  return (ice_protocol_ == ICEPROTO_GOOGLE);
+}
+
+bool Port::GetStunMessage(const char* data, size_t size,
+                          const talk_base::SocketAddress& addr,
+                          IceMessage** out_msg, std::string* out_username) {
+  // NOTE: This could clearly be optimized to avoid allocating any memory.
+  //       However, at the data rates we'll be looking at on the client side,
+  //       this probably isn't worth worrying about.
+  ASSERT(out_msg != NULL);
+  ASSERT(out_username != NULL);
+  *out_msg = NULL;
+  out_username->clear();
+
+  // Don't bother parsing the packet if we can tell it's not STUN.
+  // In ICE mode, all STUN packets will have a valid fingerprint.
+  if (IsStandardIce() && !StunMessage::ValidateFingerprint(data, size)) {
+    return false;
+  }
+
+  // Parse the request message.  If the packet is not a complete and correct
+  // STUN message, then ignore it.
+  talk_base::scoped_ptr<IceMessage> stun_msg(new IceMessage());
+  talk_base::ByteBuffer buf(data, size);
+  if (!stun_msg->Read(&buf) || (buf.Length() > 0)) {
+    return false;
+  }
+
+  if (stun_msg->type() == STUN_BINDING_REQUEST) {
+    // Check for the presence of USERNAME and MESSAGE-INTEGRITY (if ICE) first.
+    // If not present, fail with a 400 Bad Request.
+    if (!stun_msg->GetByteString(STUN_ATTR_USERNAME) ||
+        (IsStandardIce() &&
+         !stun_msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY))) {
+      LOG_J(LS_ERROR, this) << "Received STUN request without username/M-I "
+                            << "from " << addr.ToSensitiveString();
+      SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_BAD_REQUEST,
+                               STUN_ERROR_REASON_BAD_REQUEST);
+      return true;
+    }
+
+    // If the username is bad or unknown, fail with a 401 Unauthorized.
+    std::string local_ufrag;
+    std::string remote_ufrag;
+    if (!ParseStunUsername(stun_msg.get(), &local_ufrag, &remote_ufrag) ||
+        local_ufrag != username_fragment()) {
+      LOG_J(LS_ERROR, this) << "Received STUN request with bad local username "
+                            << local_ufrag << " from "
+                            << addr.ToSensitiveString();
+      SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_UNAUTHORIZED,
+                               STUN_ERROR_REASON_UNAUTHORIZED);
+      return true;
+    }
+
+    // If ICE, and the MESSAGE-INTEGRITY is bad, fail with a 401 Unauthorized
+    if (IsStandardIce() &&
+        !stun_msg->ValidateMessageIntegrity(data, size, password_)) {
+      LOG_J(LS_ERROR, this) << "Received STUN request with bad M-I "
+                            << "from " << addr.ToSensitiveString();
+      SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_UNAUTHORIZED,
+                               STUN_ERROR_REASON_UNAUTHORIZED);
+      return true;
+    }
+    out_username->assign(remote_ufrag);
+  } else if ((stun_msg->type() == STUN_BINDING_RESPONSE) ||
+             (stun_msg->type() == STUN_BINDING_ERROR_RESPONSE)) {
+    if (stun_msg->type() == STUN_BINDING_ERROR_RESPONSE) {
+      if (const StunErrorCodeAttribute* error_code = stun_msg->GetErrorCode()) {
+        LOG_J(LS_ERROR, this) << "Received STUN binding error:"
+                              << " class=" << error_code->eclass()
+                              << " number=" << error_code->number()
+                              << " reason='" << error_code->reason() << "'"
+                              << " from " << addr.ToSensitiveString();
+        // Return message to allow error-specific processing
+      } else {
+        LOG_J(LS_ERROR, this) << "Received STUN binding error without a error "
+                              << "code from " << addr.ToSensitiveString();
+        return true;
+      }
+    }
+    // NOTE: Username should not be used in verifying response messages.
+    out_username->clear();
+  } else if (stun_msg->type() == STUN_BINDING_INDICATION) {
+    LOG_J(LS_VERBOSE, this) << "Received STUN binding indication:"
+                            << " from " << addr.ToSensitiveString();
+    out_username->clear();
+    // No stun attributes will be verified, if it's stun indication message.
+    // Returning from end of the this method.
+  } else {
+    LOG_J(LS_ERROR, this) << "Received STUN packet with invalid type ("
+                          << stun_msg->type() << ") from "
+                          << addr.ToSensitiveString();
+    return true;
+  }
+
+  // Return the STUN message found.
+  *out_msg = stun_msg.release();
+  return true;
+}
+
+bool Port::IsCompatibleAddress(const talk_base::SocketAddress& addr) {
+  int family = ip().family();
+  // We use single-stack sockets, so families must match.
+  if (addr.family() != family) {
+    return false;
+  }
+  // Link-local IPv6 ports can only connect to other link-local IPv6 ports.
+  if (family == AF_INET6 && (IPIsPrivate(ip()) != IPIsPrivate(addr.ipaddr()))) {
+    return false;
+  }
+  return true;
+}
+
+bool Port::ParseStunUsername(const StunMessage* stun_msg,
+                             std::string* local_ufrag,
+                             std::string* remote_ufrag) const {
+  // The packet must include a username that either begins or ends with our
+  // fragment.  It should begin with our fragment if it is a request and it
+  // should end with our fragment if it is a response.
+  local_ufrag->clear();
+  remote_ufrag->clear();
+  const StunByteStringAttribute* username_attr =
+        stun_msg->GetByteString(STUN_ATTR_USERNAME);
+  if (username_attr == NULL)
+    return false;
+
+  const std::string username_attr_str = username_attr->GetString();
+  if (IsStandardIce()) {
+    size_t colon_pos = username_attr_str.find(":");
+    if (colon_pos != std::string::npos) {  // RFRAG:LFRAG
+      *local_ufrag = username_attr_str.substr(0, colon_pos);
+      *remote_ufrag = username_attr_str.substr(
+          colon_pos + 1, username_attr_str.size());
+    } else {
+      return false;
+    }
+  } else if (IsGoogleIce()) {
+    int remote_frag_len = username_attr_str.size();
+    remote_frag_len -= static_cast<int>(username_fragment().size());
+    if (remote_frag_len < 0)
+      return false;
+
+    *local_ufrag = username_attr_str.substr(0, username_fragment().size());
+    *remote_ufrag = username_attr_str.substr(
+        username_fragment().size(), username_attr_str.size());
+  }
+  return true;
+}
+
+bool Port::MaybeIceRoleConflict(
+    const talk_base::SocketAddress& addr, IceMessage* stun_msg,
+    const std::string& remote_ufrag) {
+  // Validate ICE_CONTROLLING or ICE_CONTROLLED attributes.
+  bool ret = true;
+  TransportRole remote_ice_role = ROLE_UNKNOWN;
+  uint64 remote_tiebreaker = 0;
+  const StunUInt64Attribute* stun_attr =
+      stun_msg->GetUInt64(STUN_ATTR_ICE_CONTROLLING);
+  if (stun_attr) {
+    remote_ice_role = ROLE_CONTROLLING;
+    remote_tiebreaker = stun_attr->value();
+  }
+
+  // If |remote_ufrag| is same as port local username fragment and
+  // tie breaker value received in the ping message matches port
+  // tiebreaker value this must be a loopback call.
+  // We will treat this as valid scenario.
+  if (remote_ice_role == ROLE_CONTROLLING &&
+      username_fragment() == remote_ufrag &&
+      remote_tiebreaker == Tiebreaker()) {
+    return true;
+  }
+
+  stun_attr = stun_msg->GetUInt64(STUN_ATTR_ICE_CONTROLLED);
+  if (stun_attr) {
+    remote_ice_role = ROLE_CONTROLLED;
+    remote_tiebreaker = stun_attr->value();
+  }
+
+  switch (role_) {
+    case ROLE_CONTROLLING:
+      if (ROLE_CONTROLLING == remote_ice_role) {
+        if (remote_tiebreaker >= tiebreaker_) {
+          SignalRoleConflict(this);
+        } else {
+          // Send Role Conflict (487) error response.
+          SendBindingErrorResponse(stun_msg, addr,
+              STUN_ERROR_ROLE_CONFLICT, STUN_ERROR_REASON_ROLE_CONFLICT);
+          ret = false;
+        }
+      }
+      break;
+    case ROLE_CONTROLLED:
+      if (ROLE_CONTROLLED == remote_ice_role) {
+        if (remote_tiebreaker < tiebreaker_) {
+          SignalRoleConflict(this);
+        } else {
+          // Send Role Conflict (487) error response.
+          SendBindingErrorResponse(stun_msg, addr,
+              STUN_ERROR_ROLE_CONFLICT, STUN_ERROR_REASON_ROLE_CONFLICT);
+          ret = false;
+        }
+      }
+      break;
+    default:
+      ASSERT(false);
+  }
+  return ret;
+}
+
+void Port::CreateStunUsername(const std::string& remote_username,
+                              std::string* stun_username_attr_str) const {
+  stun_username_attr_str->clear();
+  *stun_username_attr_str = remote_username;
+  if (IsStandardIce()) {
+    // Connectivity checks from L->R will have username RFRAG:LFRAG.
+    stun_username_attr_str->append(":");
+  }
+  stun_username_attr_str->append(username_fragment());
+}
+
+void Port::SendBindingResponse(StunMessage* request,
+                               const talk_base::SocketAddress& addr) {
+  ASSERT(request->type() == STUN_BINDING_REQUEST);
+
+  // Retrieve the username from the request.
+  const StunByteStringAttribute* username_attr =
+      request->GetByteString(STUN_ATTR_USERNAME);
+  ASSERT(username_attr != NULL);
+  if (username_attr == NULL) {
+    // No valid username, skip the response.
+    return;
+  }
+
+  // Fill in the response message.
+  StunMessage response;
+  response.SetType(STUN_BINDING_RESPONSE);
+  response.SetTransactionID(request->transaction_id());
+  const StunUInt32Attribute* retransmit_attr =
+      request->GetUInt32(STUN_ATTR_RETRANSMIT_COUNT);
+  if (retransmit_attr) {
+    // Inherit the incoming retransmit value in the response so the other side
+    // can see our view of lost pings.
+    response.AddAttribute(new StunUInt32Attribute(
+        STUN_ATTR_RETRANSMIT_COUNT, retransmit_attr->value()));
+
+    if (retransmit_attr->value() > CONNECTION_WRITE_CONNECT_FAILURES) {
+      LOG_J(LS_INFO, this)
+          << "Received a remote ping with high retransmit count: "
+          << retransmit_attr->value();
+    }
+  }
+
+  // Only GICE messages have USERNAME and MAPPED-ADDRESS in the response.
+  // ICE messages use XOR-MAPPED-ADDRESS, and add MESSAGE-INTEGRITY.
+  if (IsStandardIce()) {
+    response.AddAttribute(
+        new StunXorAddressAttribute(STUN_ATTR_XOR_MAPPED_ADDRESS, addr));
+    response.AddMessageIntegrity(password_);
+    response.AddFingerprint();
+  } else if (IsGoogleIce()) {
+    response.AddAttribute(
+        new StunAddressAttribute(STUN_ATTR_MAPPED_ADDRESS, addr));
+    response.AddAttribute(new StunByteStringAttribute(
+        STUN_ATTR_USERNAME, username_attr->GetString()));
+  }
+
+  // Send the response message.
+  talk_base::ByteBuffer buf;
+  response.Write(&buf);
+  if (SendTo(buf.Data(), buf.Length(), addr, false) < 0) {
+    LOG_J(LS_ERROR, this) << "Failed to send STUN ping response to "
+                          << addr.ToSensitiveString();
+  }
+
+  // The fact that we received a successful request means that this connection
+  // (if one exists) should now be readable.
+  Connection* conn = GetConnection(addr);
+  ASSERT(conn != NULL);
+  if (conn)
+    conn->ReceivedPing();
+}
+
+void Port::SendBindingErrorResponse(StunMessage* request,
+                                    const talk_base::SocketAddress& addr,
+                                    int error_code, const std::string& reason) {
+  ASSERT(request->type() == STUN_BINDING_REQUEST);
+
+  // Fill in the response message.
+  StunMessage response;
+  response.SetType(STUN_BINDING_ERROR_RESPONSE);
+  response.SetTransactionID(request->transaction_id());
+
+  // When doing GICE, we need to write out the error code incorrectly to
+  // maintain backwards compatiblility.
+  StunErrorCodeAttribute* error_attr = StunAttribute::CreateErrorCode();
+  if (IsStandardIce()) {
+    error_attr->SetCode(error_code);
+  } else if (IsGoogleIce()) {
+    error_attr->SetClass(error_code / 256);
+    error_attr->SetNumber(error_code % 256);
+  }
+  error_attr->SetReason(reason);
+  response.AddAttribute(error_attr);
+
+  if (IsStandardIce()) {
+    // Per Section 10.1.2, certain error cases don't get a MESSAGE-INTEGRITY,
+    // because we don't have enough information to determine the shared secret.
+    if (error_code != STUN_ERROR_BAD_REQUEST &&
+        error_code != STUN_ERROR_UNAUTHORIZED)
+      response.AddMessageIntegrity(password_);
+    response.AddFingerprint();
+  } else if (IsGoogleIce()) {
+    // GICE responses include a username, if one exists.
+    const StunByteStringAttribute* username_attr =
+        request->GetByteString(STUN_ATTR_USERNAME);
+    if (username_attr)
+      response.AddAttribute(new StunByteStringAttribute(
+          STUN_ATTR_USERNAME, username_attr->GetString()));
+  }
+
+  // Send the response message.
+  talk_base::ByteBuffer buf;
+  response.Write(&buf);
+  SendTo(buf.Data(), buf.Length(), addr, false);
+  LOG_J(LS_INFO, this) << "Sending STUN binding error: reason=" << reason
+                       << " to " << addr.ToSensitiveString();
+}
+
+void Port::OnMessage(talk_base::Message *pmsg) {
+  ASSERT(pmsg->message_id == MSG_CHECKTIMEOUT);
+  ASSERT(lifetime_ == LT_PRETIMEOUT);
+  lifetime_ = LT_POSTTIMEOUT;
+  CheckTimeout();
+}
+
+std::string Port::ToString() const {
+  std::stringstream ss;
+  ss << "Port[" << content_name_ << ":" << component_
+     << ":" << generation_ << ":" << type_
+     << ":" << network_->ToString() << "]";
+  return ss.str();
+}
+
+void Port::EnablePortPackets() {
+  enable_port_packets_ = true;
+}
+
+void Port::Start() {
+  // The port sticks around for a minimum lifetime, after which
+  // we destroy it when it drops to zero connections.
+  if (lifetime_ == LT_PRESTART) {
+    lifetime_ = LT_PRETIMEOUT;
+    thread_->PostDelayed(kPortTimeoutDelay, this, MSG_CHECKTIMEOUT);
+  } else {
+    LOG_J(LS_WARNING, this) << "Port restart attempted";
+  }
+}
+
+void Port::OnConnectionDestroyed(Connection* conn) {
+  AddressMap::iterator iter =
+      connections_.find(conn->remote_candidate().address());
+  ASSERT(iter != connections_.end());
+  connections_.erase(iter);
+
+  CheckTimeout();
+}
+
+void Port::Destroy() {
+  ASSERT(connections_.empty());
+  LOG_J(LS_INFO, this) << "Port deleted";
+  SignalDestroyed(this);
+  delete this;
+}
+
+void Port::CheckTimeout() {
+  // If this port has no connections, then there's no reason to keep it around.
+  // When the connections time out (both read and write), they will delete
+  // themselves, so if we have any connections, they are either readable or
+  // writable (or still connecting).
+  if ((lifetime_ == LT_POSTTIMEOUT) && connections_.empty()) {
+    Destroy();
+  }
+}
+
+const std::string Port::username_fragment() const {
+  if (IsGoogleIce() &&
+      component_ == ICE_CANDIDATE_COMPONENT_RTCP) {
+    // In GICE mode, we should adjust username fragment for rtcp component.
+    return GetRtcpUfragFromRtpUfrag(ice_username_fragment_);
+  } else {
+    return ice_username_fragment_;
+  }
+}
+
+// A ConnectionRequest is a simple STUN ping used to determine writability.
+class ConnectionRequest : public StunRequest {
+ public:
+  explicit ConnectionRequest(Connection* connection)
+      : StunRequest(new IceMessage()),
+        connection_(connection) {
+  }
+
+  virtual ~ConnectionRequest() {
+  }
+
+  virtual void Prepare(StunMessage* request) {
+    request->SetType(STUN_BINDING_REQUEST);
+    std::string username;
+    connection_->port()->CreateStunUsername(
+        connection_->remote_candidate().username(), &username);
+    request->AddAttribute(
+        new StunByteStringAttribute(STUN_ATTR_USERNAME, username));
+
+    // connection_ already holds this ping, so subtract one from count.
+    if (connection_->port()->send_retransmit_count_attribute()) {
+      request->AddAttribute(new StunUInt32Attribute(STUN_ATTR_RETRANSMIT_COUNT,
+          connection_->pings_since_last_response_.size() - 1));
+    }
+
+    // Adding ICE-specific attributes to the STUN request message.
+    if (connection_->port()->IsStandardIce()) {
+      // Adding ICE_CONTROLLED or ICE_CONTROLLING attribute based on the role.
+      if (connection_->port()->Role() == ROLE_CONTROLLING) {
+        request->AddAttribute(new StunUInt64Attribute(
+            STUN_ATTR_ICE_CONTROLLING, connection_->port()->Tiebreaker()));
+        // Since we are trying aggressive nomination, sending USE-CANDIDATE
+        // attribute in every ping.
+        // If we are dealing with a ice-lite end point, nomination flag
+        // in Connection will be set to false by default. Once the connection
+        // becomes "best connection", nomination flag will be turned on.
+        if (connection_->use_candidate_attr()) {
+          request->AddAttribute(new StunByteStringAttribute(
+              STUN_ATTR_USE_CANDIDATE));
+        }
+      } else if (connection_->port()->Role() == ROLE_CONTROLLED) {
+        request->AddAttribute(new StunUInt64Attribute(
+            STUN_ATTR_ICE_CONTROLLED, connection_->port()->Tiebreaker()));
+      } else {
+        ASSERT(false);
+      }
+
+      // Adding PRIORITY Attribute.
+      // Changing the type preference to Peer Reflexive and local preference
+      // and component id information is unchanged from the original priority.
+      // priority = (2^24)*(type preference) +
+      //           (2^8)*(local preference) +
+      //           (2^0)*(256 - component ID)
+      uint32 prflx_priority = ICE_TYPE_PREFERENCE_PRFLX << 24 |
+          (connection_->local_candidate().priority() & 0x00FFFFFF);
+      request->AddAttribute(
+          new StunUInt32Attribute(STUN_ATTR_PRIORITY, prflx_priority));
+
+      // Adding Message Integrity attribute.
+      request->AddMessageIntegrity(connection_->remote_candidate().password());
+      // Adding Fingerprint.
+      request->AddFingerprint();
+    }
+  }
+
+  virtual void OnResponse(StunMessage* response) {
+    connection_->OnConnectionRequestResponse(this, response);
+  }
+
+  virtual void OnErrorResponse(StunMessage* response) {
+    connection_->OnConnectionRequestErrorResponse(this, response);
+  }
+
+  virtual void OnTimeout() {
+    connection_->OnConnectionRequestTimeout(this);
+  }
+
+  virtual int GetNextDelay() {
+    // Each request is sent only once.  After a single delay , the request will
+    // time out.
+    timeout_ = true;
+    return CONNECTION_RESPONSE_TIMEOUT;
+  }
+
+ private:
+  Connection* connection_;
+};
+
+//
+// Connection
+//
+
+Connection::Connection(Port* port, size_t index,
+                       const Candidate& remote_candidate)
+  : port_(port), local_candidate_index_(index),
+    remote_candidate_(remote_candidate), read_state_(STATE_READ_INIT),
+    write_state_(STATE_WRITE_INIT), connected_(true), pruned_(false),
+    use_candidate_attr_(false), remote_ice_mode_(ICEMODE_FULL),
+    requests_(port->thread()), rtt_(DEFAULT_RTT), last_ping_sent_(0),
+    last_ping_received_(0), last_data_received_(0),
+    last_ping_response_received_(0), reported_(false), state_(STATE_WAITING) {
+  // All of our connections start in WAITING state.
+  // TODO(mallinath) - Start connections from STATE_FROZEN.
+  // Wire up to send stun packets
+  requests_.SignalSendPacket.connect(this, &Connection::OnSendStunPacket);
+  LOG_J(LS_INFO, this) << "Connection created";
+}
+
+Connection::~Connection() {
+}
+
+const Candidate& Connection::local_candidate() const {
+  ASSERT(local_candidate_index_ < port_->Candidates().size());
+  return port_->Candidates()[local_candidate_index_];
+}
+
+uint64 Connection::priority() const {
+  uint64 priority = 0;
+  // RFC 5245 - 5.7.2.  Computing Pair Priority and Ordering Pairs
+  // Let G be the priority for the candidate provided by the controlling
+  // agent.  Let D be the priority for the candidate provided by the
+  // controlled agent.
+  // pair priority = 2^32*MIN(G,D) + 2*MAX(G,D) + (G>D?1:0)
+  TransportRole role = port_->Role();
+  if (role != ROLE_UNKNOWN) {
+    uint32 g = 0;
+    uint32 d = 0;
+    if (role == ROLE_CONTROLLING) {
+      g = local_candidate().priority();
+      d = remote_candidate_.priority();
+    } else {
+      g = remote_candidate_.priority();
+      d = local_candidate().priority();
+    }
+    priority = talk_base::_min(g, d);
+    priority = priority << 32;
+    priority += 2 * talk_base::_max(g, d) + (g > d ? 1 : 0);
+  }
+  return priority;
+}
+
+void Connection::set_read_state(ReadState value) {
+  ReadState old_value = read_state_;
+  read_state_ = value;
+  if (value != old_value) {
+    LOG_J(LS_VERBOSE, this) << "set_read_state";
+    SignalStateChange(this);
+    CheckTimeout();
+  }
+}
+
+void Connection::set_write_state(WriteState value) {
+  WriteState old_value = write_state_;
+  write_state_ = value;
+  if (value != old_value) {
+    LOG_J(LS_VERBOSE, this) << "set_write_state";
+    SignalStateChange(this);
+    CheckTimeout();
+  }
+}
+
+void Connection::set_state(State state) {
+  State old_state = state_;
+  state_ = state;
+  if (state != old_state) {
+    LOG_J(LS_VERBOSE, this) << "set_state";
+  }
+}
+
+void Connection::set_connected(bool value) {
+  bool old_value = connected_;
+  connected_ = value;
+  if (value != old_value) {
+    LOG_J(LS_VERBOSE, this) << "set_connected";
+  }
+}
+
+void Connection::set_use_candidate_attr(bool enable) {
+  use_candidate_attr_ = enable;
+}
+
+void Connection::OnSendStunPacket(const void* data, size_t size,
+                                  StunRequest* req) {
+  if (port_->SendTo(data, size, remote_candidate_.address(), false) < 0) {
+    LOG_J(LS_WARNING, this) << "Failed to send STUN ping " << req->id();
+  }
+}
+
+void Connection::OnReadPacket(const char* data, size_t size) {
+  talk_base::scoped_ptr<IceMessage> msg;
+  std::string remote_ufrag;
+  const talk_base::SocketAddress& addr(remote_candidate_.address());
+  if (!port_->GetStunMessage(data, size, addr, msg.accept(), &remote_ufrag)) {
+    // The packet did not parse as a valid STUN message
+
+    // If this connection is readable, then pass along the packet.
+    if (read_state_ == STATE_READABLE) {
+      // readable means data from this address is acceptable
+      // Send it on!
+
+      last_data_received_ = talk_base::Time();
+      recv_rate_tracker_.Update(size);
+      SignalReadPacket(this, data, size);
+
+      // If timed out sending writability checks, start up again
+      if (!pruned_ && (write_state_ == STATE_WRITE_TIMEOUT)) {
+        LOG(LS_WARNING) << "Received a data packet on a timed-out Connection. "
+                        << "Resetting state to STATE_WRITE_INIT.";
+        set_write_state(STATE_WRITE_INIT);
+      }
+    } else {
+      // Not readable means the remote address hasn't sent a valid
+      // binding request yet.
+
+      LOG_J(LS_WARNING, this)
+        << "Received non-STUN packet from an unreadable connection.";
+    }
+  } else if (!msg) {
+    // The packet was STUN, but failed a check and was handled internally.
+  } else {
+    // The packet is STUN and passed the Port checks.
+    // Perform our own checks to ensure this packet is valid.
+    // If this is a STUN request, then update the readable bit and respond.
+    // If this is a STUN response, then update the writable bit.
+    switch (msg->type()) {
+      case STUN_BINDING_REQUEST:
+        if (remote_ufrag == remote_candidate_.username()) {
+          // Check for role conflicts.
+          if (port_->IsStandardIce() &&
+              !port_->MaybeIceRoleConflict(addr, msg.get(), remote_ufrag)) {
+            // Received conflicting role from the peer.
+            LOG(LS_INFO) << "Received conflicting role from the peer.";
+            return;
+          }
+
+          // Incoming, validated stun request from remote peer.
+          // This call will also set the connection readable.
+          port_->SendBindingResponse(msg.get(), addr);
+
+          // If timed out sending writability checks, start up again
+          if (!pruned_ && (write_state_ == STATE_WRITE_TIMEOUT))
+            set_write_state(STATE_WRITE_INIT);
+
+          if ((port_->IsStandardIce()) &&
+              (port_->Role() == ROLE_CONTROLLED)) {
+            const StunByteStringAttribute* use_candidate_attr =
+                msg->GetByteString(STUN_ATTR_USE_CANDIDATE);
+            if (use_candidate_attr)
+              SignalUseCandidate(this);
+          }
+        } else {
+          // The packet had the right local username, but the remote username
+          // was not the right one for the remote address.
+          LOG_J(LS_ERROR, this)
+            << "Received STUN request with bad remote username "
+            << remote_ufrag;
+          port_->SendBindingErrorResponse(msg.get(), addr,
+                                          STUN_ERROR_UNAUTHORIZED,
+                                          STUN_ERROR_REASON_UNAUTHORIZED);
+
+        }
+        break;
+
+      // Response from remote peer. Does it match request sent?
+      // This doesn't just check, it makes callbacks if transaction
+      // id's match.
+      case STUN_BINDING_RESPONSE:
+      case STUN_BINDING_ERROR_RESPONSE:
+        if (port_->IceProtocol() == ICEPROTO_GOOGLE ||
+            msg->ValidateMessageIntegrity(
+                data, size, remote_candidate().password())) {
+          requests_.CheckResponse(msg.get());
+        }
+        // Otherwise silently discard the response message.
+        break;
+
+      // Remote end point sent an STUN indication instead of regular
+      // binding request. In this case |last_ping_received_| will be updated.
+      // Otherwise we can mark connection to read timeout. No response will be
+      // sent in this scenario.
+      case STUN_BINDING_INDICATION:
+        if (port_->IsStandardIce() && read_state_ == STATE_READABLE) {
+          ReceivedPing();
+        } else {
+          LOG_J(LS_WARNING, this) << "Received STUN binding indication "
+                                  << "from an unreadable connection.";
+        }
+        break;
+
+      default:
+        ASSERT(false);
+        break;
+    }
+  }
+}
+
+void Connection::OnReadyToSend() {
+  if (write_state_ == STATE_WRITABLE) {
+    SignalReadyToSend(this);
+  }
+}
+
+void Connection::Prune() {
+  if (!pruned_) {
+    LOG_J(LS_VERBOSE, this) << "Connection pruned";
+    pruned_ = true;
+    requests_.Clear();
+    set_write_state(STATE_WRITE_TIMEOUT);
+  }
+}
+
+void Connection::Destroy() {
+  LOG_J(LS_VERBOSE, this) << "Connection destroyed";
+  set_read_state(STATE_READ_TIMEOUT);
+  set_write_state(STATE_WRITE_TIMEOUT);
+}
+
+void Connection::UpdateState(uint32 now) {
+  uint32 rtt = ConservativeRTTEstimate(rtt_);
+
+  std::string pings;
+  for (size_t i = 0; i < pings_since_last_response_.size(); ++i) {
+    char buf[32];
+    talk_base::sprintfn(buf, sizeof(buf), "%u",
+        pings_since_last_response_[i]);
+    pings.append(buf).append(" ");
+  }
+  LOG_J(LS_VERBOSE, this) << "UpdateState(): pings_since_last_response_=" <<
+      pings << ", rtt=" << rtt << ", now=" << now;
+
+  // Check the readable state.
+  //
+  // Since we don't know how many pings the other side has attempted, the best
+  // test we can do is a simple window.
+  // If other side has not sent ping after connection has become readable, use
+  // |last_data_received_| as the indication.
+  if ((read_state_ == STATE_READABLE) &&
+      (last_ping_received_ + CONNECTION_READ_TIMEOUT <= now) &&
+      (last_data_received_ + CONNECTION_READ_TIMEOUT <= now)) {
+    LOG_J(LS_INFO, this) << "Unreadable after "
+                         << now - last_ping_received_
+                         << " ms without a ping,"
+                         << " ms since last received response="
+                         << now - last_ping_response_received_
+                         << " ms since last received data="
+                         << now - last_data_received_
+                         << " rtt=" << rtt;
+    set_read_state(STATE_READ_TIMEOUT);
+  }
+
+  // Check the writable state.  (The order of these checks is important.)
+  //
+  // Before becoming unwritable, we allow for a fixed number of pings to fail
+  // (i.e., receive no response).  We also have to give the response time to
+  // get back, so we include a conservative estimate of this.
+  //
+  // Before timing out writability, we give a fixed amount of time.  This is to
+  // allow for changes in network conditions.
+
+  if ((write_state_ == STATE_WRITABLE) &&
+      TooManyFailures(pings_since_last_response_,
+                      CONNECTION_WRITE_CONNECT_FAILURES,
+                      rtt,
+                      now) &&
+      TooLongWithoutResponse(pings_since_last_response_,
+                             CONNECTION_WRITE_CONNECT_TIMEOUT,
+                             now)) {
+    uint32 max_pings = CONNECTION_WRITE_CONNECT_FAILURES;
+    LOG_J(LS_INFO, this) << "Unwritable after " << max_pings
+                         << " ping failures and "
+                         << now - pings_since_last_response_[0]
+                         << " ms without a response,"
+                         << " ms since last received ping="
+                         << now - last_ping_received_
+                         << " ms since last received data="
+                         << now - last_data_received_
+                         << " rtt=" << rtt;
+    set_write_state(STATE_WRITE_UNRELIABLE);
+  }
+
+  if ((write_state_ == STATE_WRITE_UNRELIABLE ||
+       write_state_ == STATE_WRITE_INIT) &&
+      TooLongWithoutResponse(pings_since_last_response_,
+                             CONNECTION_WRITE_TIMEOUT,
+                             now)) {
+    LOG_J(LS_INFO, this) << "Timed out after "
+                         << now - pings_since_last_response_[0]
+                         << " ms without a response, rtt=" << rtt;
+    set_write_state(STATE_WRITE_TIMEOUT);
+  }
+}
+
+void Connection::Ping(uint32 now) {
+  ASSERT(connected_);
+  last_ping_sent_ = now;
+  pings_since_last_response_.push_back(now);
+  ConnectionRequest *req = new ConnectionRequest(this);
+  LOG_J(LS_VERBOSE, this) << "Sending STUN ping " << req->id() << " at " << now;
+  requests_.Send(req);
+  state_ = STATE_INPROGRESS;
+}
+
+void Connection::ReceivedPing() {
+  last_ping_received_ = talk_base::Time();
+  set_read_state(STATE_READABLE);
+}
+
+std::string Connection::ToString() const {
+  const char CONNECT_STATE_ABBREV[2] = {
+    '-',  // not connected (false)
+    'C',  // connected (true)
+  };
+  const char READ_STATE_ABBREV[3] = {
+    '-',  // STATE_READ_INIT
+    'R',  // STATE_READABLE
+    'x',  // STATE_READ_TIMEOUT
+  };
+  const char WRITE_STATE_ABBREV[4] = {
+    'W',  // STATE_WRITABLE
+    'w',  // STATE_WRITE_UNRELIABLE
+    '-',  // STATE_WRITE_INIT
+    'x',  // STATE_WRITE_TIMEOUT
+  };
+  const std::string ICESTATE[4] = {
+    "W",  // STATE_WAITING
+    "I",  // STATE_INPROGRESS
+    "S",  // STATE_SUCCEEDED
+    "F"   // STATE_FAILED
+  };
+  const Candidate& local = local_candidate();
+  const Candidate& remote = remote_candidate();
+  std::stringstream ss;
+  ss << "Conn[" << port_->content_name()
+     << ":" << local.id() << ":" << local.component()
+     << ":" << local.generation()
+     << ":" << local.type() << ":" << local.protocol()
+     << ":" << local.address().ToSensitiveString()
+     << "->" << remote.id() << ":" << remote.component()
+     << ":" << remote.generation()
+     << ":" << remote.type() << ":"
+     << remote.protocol() << ":" << remote.address().ToSensitiveString()
+     << "|"
+     << CONNECT_STATE_ABBREV[connected()]
+     << READ_STATE_ABBREV[read_state()]
+     << WRITE_STATE_ABBREV[write_state()]
+     << ICESTATE[state()]
+     << "|";
+  if (rtt_ < DEFAULT_RTT) {
+    ss << rtt_ << "]";
+  } else {
+    ss << "-]";
+  }
+  return ss.str();
+}
+
+std::string Connection::ToSensitiveString() const {
+  return ToString();
+}
+
+void Connection::OnConnectionRequestResponse(ConnectionRequest* request,
+                                             StunMessage* response) {
+  // We've already validated that this is a STUN binding response with
+  // the correct local and remote username for this connection.
+  // So if we're not already, become writable. We may be bringing a pruned
+  // connection back to life, but if we don't really want it, we can always
+  // prune it again.
+  uint32 rtt = request->Elapsed();
+  set_write_state(STATE_WRITABLE);
+  set_state(STATE_SUCCEEDED);
+
+  if (remote_ice_mode_ == ICEMODE_LITE) {
+    // A ice-lite end point never initiates ping requests. This will allow
+    // us to move to STATE_READABLE.
+    ReceivedPing();
+  }
+
+  std::string pings;
+  for (size_t i = 0; i < pings_since_last_response_.size(); ++i) {
+    char buf[32];
+    talk_base::sprintfn(buf, sizeof(buf), "%u",
+        pings_since_last_response_[i]);
+    pings.append(buf).append(" ");
+  }
+
+  talk_base::LoggingSeverity level =
+      (pings_since_last_response_.size() > CONNECTION_WRITE_CONNECT_FAILURES) ?
+          talk_base::LS_INFO : talk_base::LS_VERBOSE;
+
+  LOG_JV(level, this) << "Received STUN ping response " << request->id()
+                      << ", pings_since_last_response_=" << pings
+                      << ", rtt=" << rtt;
+
+  pings_since_last_response_.clear();
+  last_ping_response_received_ = talk_base::Time();
+  rtt_ = (RTT_RATIO * rtt_ + rtt) / (RTT_RATIO + 1);
+
+  // Peer reflexive candidate is only for RFC 5245 ICE.
+  if (port_->IsStandardIce()) {
+    MaybeAddPrflxCandidate(request, response);
+  }
+}
+
+void Connection::OnConnectionRequestErrorResponse(ConnectionRequest* request,
+                                                  StunMessage* response) {
+  const StunErrorCodeAttribute* error_attr = response->GetErrorCode();
+  int error_code = STUN_ERROR_GLOBAL_FAILURE;
+  if (error_attr) {
+    if (port_->IsGoogleIce()) {
+      // When doing GICE, the error code is written out incorrectly, so we need
+      // to unmunge it here.
+      error_code = error_attr->eclass() * 256 + error_attr->number();
+    } else {
+      error_code = error_attr->code();
+    }
+  }
+
+  if (error_code == STUN_ERROR_UNKNOWN_ATTRIBUTE ||
+      error_code == STUN_ERROR_SERVER_ERROR ||
+      error_code == STUN_ERROR_UNAUTHORIZED) {
+    // Recoverable error, retry
+  } else if (error_code == STUN_ERROR_STALE_CREDENTIALS) {
+    // Race failure, retry
+  } else if (error_code == STUN_ERROR_ROLE_CONFLICT) {
+    HandleRoleConflictFromPeer();
+  } else {
+    // This is not a valid connection.
+    LOG_J(LS_ERROR, this) << "Received STUN error response, code="
+                          << error_code << "; killing connection";
+    set_state(STATE_FAILED);
+    set_write_state(STATE_WRITE_TIMEOUT);
+  }
+}
+
+void Connection::OnConnectionRequestTimeout(ConnectionRequest* request) {
+  // Log at LS_INFO if we miss a ping on a writable connection.
+  talk_base::LoggingSeverity sev = (write_state_ == STATE_WRITABLE) ?
+      talk_base::LS_INFO : talk_base::LS_VERBOSE;
+  LOG_JV(sev, this) << "Timing-out STUN ping " << request->id()
+                    << " after " << request->Elapsed() << " ms";
+}
+
+void Connection::CheckTimeout() {
+  // If both read and write have timed out or read has never initialized, then
+  // this connection can contribute no more to p2p socket unless at some later
+  // date readability were to come back.  However, we gave readability a long
+  // time to timeout, so at this point, it seems fair to get rid of this
+  // connection.
+  if ((read_state_ == STATE_READ_TIMEOUT ||
+       read_state_ == STATE_READ_INIT) &&
+      write_state_ == STATE_WRITE_TIMEOUT) {
+    port_->thread()->Post(this, MSG_DELETE);
+  }
+}
+
+void Connection::HandleRoleConflictFromPeer() {
+  port_->SignalRoleConflict(port_);
+}
+
+void Connection::OnMessage(talk_base::Message *pmsg) {
+  ASSERT(pmsg->message_id == MSG_DELETE);
+
+  LOG_J(LS_INFO, this) << "Connection deleted";
+  SignalDestroyed(this);
+  delete this;
+}
+
+size_t Connection::recv_bytes_second() {
+  return recv_rate_tracker_.units_second();
+}
+
+size_t Connection::recv_total_bytes() {
+  return recv_rate_tracker_.total_units();
+}
+
+size_t Connection::sent_bytes_second() {
+  return send_rate_tracker_.units_second();
+}
+
+size_t Connection::sent_total_bytes() {
+  return send_rate_tracker_.total_units();
+}
+
+void Connection::MaybeAddPrflxCandidate(ConnectionRequest* request,
+                                        StunMessage* response) {
+  // RFC 5245
+  // The agent checks the mapped address from the STUN response.  If the
+  // transport address does not match any of the local candidates that the
+  // agent knows about, the mapped address represents a new candidate -- a
+  // peer reflexive candidate.
+  const StunAddressAttribute* addr =
+      response->GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+  if (!addr) {
+    LOG(LS_WARNING) << "Connection::OnConnectionRequestResponse - "
+                    << "No MAPPED-ADDRESS or XOR-MAPPED-ADDRESS found in the "
+                    << "stun response message";
+    return;
+  }
+
+  bool known_addr = false;
+  for (size_t i = 0; i < port_->Candidates().size(); ++i) {
+    if (port_->Candidates()[i].address() == addr->GetAddress()) {
+      known_addr = true;
+      break;
+    }
+  }
+  if (known_addr) {
+    return;
+  }
+
+  // RFC 5245
+  // Its priority is set equal to the value of the PRIORITY attribute
+  // in the Binding request.
+  const StunUInt32Attribute* priority_attr =
+      request->msg()->GetUInt32(STUN_ATTR_PRIORITY);
+  if (!priority_attr) {
+    LOG(LS_WARNING) << "Connection::OnConnectionRequestResponse - "
+                    << "No STUN_ATTR_PRIORITY found in the "
+                    << "stun response message";
+    return;
+  }
+  const uint32 priority = priority_attr->value();
+  std::string id = talk_base::CreateRandomString(8);
+
+  Candidate new_local_candidate;
+  new_local_candidate.set_id(id);
+  new_local_candidate.set_component(local_candidate().component());
+  new_local_candidate.set_type(PRFLX_PORT_TYPE);
+  new_local_candidate.set_protocol(local_candidate().protocol());
+  new_local_candidate.set_address(addr->GetAddress());
+  new_local_candidate.set_priority(priority);
+  new_local_candidate.set_username(local_candidate().username());
+  new_local_candidate.set_password(local_candidate().password());
+  new_local_candidate.set_network_name(local_candidate().network_name());
+  new_local_candidate.set_related_address(local_candidate().address());
+  new_local_candidate.set_foundation(
+      ComputeFoundation(PRFLX_PORT_TYPE, local_candidate().protocol(),
+                        local_candidate().address()));
+
+  // Change the local candidate of this Connection to the new prflx candidate.
+  local_candidate_index_ = port_->AddPrflxCandidate(new_local_candidate);
+
+  // SignalStateChange to force a re-sort in P2PTransportChannel as this
+  // Connection's local candidate has changed.
+  SignalStateChange(this);
+}
+
+ProxyConnection::ProxyConnection(Port* port, size_t index,
+                                 const Candidate& candidate)
+  : Connection(port, index, candidate), error_(0) {
+}
+
+int ProxyConnection::Send(const void* data, size_t size) {
+  if (write_state_ == STATE_WRITE_INIT || write_state_ == STATE_WRITE_TIMEOUT) {
+    error_ = EWOULDBLOCK;
+    return SOCKET_ERROR;
+  }
+  int sent = port_->SendTo(data, size, remote_candidate_.address(), true);
+  if (sent <= 0) {
+    ASSERT(sent < 0);
+    error_ = port_->GetError();
+  } else {
+    send_rate_tracker_.Update(sent);
+  }
+  return sent;
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/port.h b/talk/p2p/base/port.h
new file mode 100644
index 0000000..5e795ec
--- /dev/null
+++ b/talk/p2p/base/port.h
@@ -0,0 +1,585 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_PORT_H_
+#define TALK_P2P_BASE_PORT_H_
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include "talk/base/network.h"
+#include "talk/base/proxyinfo.h"
+#include "talk/base/ratetracker.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/packetsocketfactory.h"
+#include "talk/p2p/base/portinterface.h"
+#include "talk/p2p/base/stun.h"
+#include "talk/p2p/base/stunrequest.h"
+#include "talk/p2p/base/transport.h"
+
+namespace talk_base {
+class AsyncPacketSocket;
+}
+
+namespace cricket {
+
+class Connection;
+class ConnectionRequest;
+
+extern const char LOCAL_PORT_TYPE[];
+extern const char STUN_PORT_TYPE[];
+extern const char PRFLX_PORT_TYPE[];
+extern const char RELAY_PORT_TYPE[];
+
+extern const char UDP_PROTOCOL_NAME[];
+extern const char TCP_PROTOCOL_NAME[];
+extern const char SSLTCP_PROTOCOL_NAME[];
+
+// The length of time we wait before timing out readability on a connection.
+const uint32 CONNECTION_READ_TIMEOUT = 30 * 1000;   // 30 seconds
+
+// The length of time we wait before timing out writability on a connection.
+const uint32 CONNECTION_WRITE_TIMEOUT = 15 * 1000;  // 15 seconds
+
+// The length of time we wait before we become unwritable.
+const uint32 CONNECTION_WRITE_CONNECT_TIMEOUT = 5 * 1000;  // 5 seconds
+
+// The number of pings that must fail to respond before we become unwritable.
+const uint32 CONNECTION_WRITE_CONNECT_FAILURES = 5;
+
+// This is the length of time that we wait for a ping response to come back.
+const int CONNECTION_RESPONSE_TIMEOUT = 5 * 1000;   // 5 seconds
+
+enum RelayType {
+  RELAY_GTURN,   // Legacy google relay service.
+  RELAY_TURN     // Standard (TURN) relay service.
+};
+
+enum IcePriorityValue {
+  // The reason we are choosing Relay preference 2 is because, we can run
+  // Relay from client to server on UDP/TCP/TLS. To distinguish the transport
+  // protocol, we prefer UDP over TCP over TLS.
+  // For UDP ICE_TYPE_PREFERENCE_RELAY will be 2.
+  // For TCP ICE_TYPE_PREFERENCE_RELAY will be 1.
+  // For TLS ICE_TYPE_PREFERENCE_RELAY will be 0.
+  // Check turnport.cc for setting these values.
+  ICE_TYPE_PREFERENCE_RELAY = 2,
+  ICE_TYPE_PREFERENCE_HOST_TCP = 90,
+  ICE_TYPE_PREFERENCE_SRFLX = 100,
+  ICE_TYPE_PREFERENCE_PRFLX = 110,
+  ICE_TYPE_PREFERENCE_HOST = 126
+};
+
+const char* ProtoToString(ProtocolType proto);
+bool StringToProto(const char* value, ProtocolType* proto);
+
+struct ProtocolAddress {
+  talk_base::SocketAddress address;
+  ProtocolType proto;
+
+  ProtocolAddress(const talk_base::SocketAddress& a, ProtocolType p)
+    : address(a), proto(p) { }
+};
+
+// Represents a local communication mechanism that can be used to create
+// connections to similar mechanisms of the other client.  Subclasses of this
+// one add support for specific mechanisms like local UDP ports.
+class Port : public PortInterface, public talk_base::MessageHandler,
+             public sigslot::has_slots<> {
+ public:
+  Port(talk_base::Thread* thread, talk_base::Network* network,
+       const talk_base::IPAddress& ip,
+       const std::string& username_fragment, const std::string& password);
+  Port(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, const std::string& username_fragment,
+       const std::string& password);
+  virtual ~Port();
+
+  virtual const std::string& Type() const { return type_; }
+  virtual talk_base::Network* Network() const { return network_; }
+
+  // This method will set the flag which enables standard ICE/STUN procedures
+  // in STUN connectivity checks. Currently this method does
+  // 1. Add / Verify MI attribute in STUN binding requests.
+  // 2. Username attribute in STUN binding request will be RFRAF:LFRAG,
+  // as opposed to RFRAGLFRAG.
+  virtual void SetIceProtocolType(IceProtocolType protocol) {
+    ice_protocol_ = protocol;
+  }
+  virtual IceProtocolType IceProtocol() const { return ice_protocol_; }
+
+  // Methods to set/get ICE role and tiebreaker values.
+  void SetRole(TransportRole role) { role_ = role; }
+  TransportRole Role() const { return role_; }
+
+  void SetTiebreaker(uint64 tiebreaker) { tiebreaker_ = tiebreaker; }
+  uint64 Tiebreaker() const { return tiebreaker_; }
+
+  virtual bool SharedSocket() const { return shared_socket_; }
+
+  // The thread on which this port performs its I/O.
+  talk_base::Thread* thread() { return thread_; }
+
+  // The factory used to create the sockets of this port.
+  talk_base::PacketSocketFactory* socket_factory() const { return factory_; }
+  void set_socket_factory(talk_base::PacketSocketFactory* factory) {
+    factory_ = factory;
+  }
+
+  // For debugging purposes.
+  const std::string& content_name() const { return content_name_; }
+  void set_content_name(const std::string& content_name) {
+    content_name_ = content_name;
+  }
+
+  int component() const { return component_; }
+  void set_component(int component) { component_ = component; }
+
+  bool send_retransmit_count_attribute() const {
+    return send_retransmit_count_attribute_;
+  }
+  void set_send_retransmit_count_attribute(bool enable) {
+    send_retransmit_count_attribute_ = enable;
+  }
+
+  const talk_base::SocketAddress& related_address() const {
+    return related_address_;
+  }
+  void set_related_address(const talk_base::SocketAddress& address) {
+    related_address_ = address;
+  }
+
+  // Identifies the generation that this port was created in.
+  uint32 generation() { return generation_; }
+  void set_generation(uint32 generation) { generation_ = generation; }
+
+  // ICE requires a single username/password per content/media line. So the
+  // |ice_username_fragment_| of the ports that belongs to the same content will
+  // be the same. However this causes a small complication with our relay
+  // server, which expects different username for RTP and RTCP.
+  //
+  // To resolve this problem, we implemented the username_fragment(),
+  // which returns a different username (calculated from
+  // |ice_username_fragment_|) for RTCP in the case of ICEPROTO_GOOGLE. And the
+  // username_fragment() simply returns |ice_username_fragment_| when running
+  // in ICEPROTO_RFC5245.
+  //
+  // As a result the ICEPROTO_GOOGLE will use different usernames for RTP and
+  // RTCP. And the ICEPROTO_RFC5245 will use same username for both RTP and
+  // RTCP.
+  const std::string username_fragment() const;
+  const std::string& password() const { return password_; }
+
+  // Fired when candidates are discovered by the port. When all candidates
+  // are discovered that belong to port SignalAddressReady is fired.
+  sigslot::signal2<Port*, const Candidate&> SignalCandidateReady;
+
+  // Provides all of the above information in one handy object.
+  virtual const std::vector<Candidate>& Candidates() const {
+    return candidates_;
+  }
+
+  // SignalPortComplete is sent when port completes the task of candidates
+  // allocation.
+  sigslot::signal1<Port*> SignalPortComplete;
+  // This signal sent when port fails to allocate candidates and this port
+  // can't be used in establishing the connections. When port is in shared mode
+  // and port fails to allocate one of the candidates, port shouldn't send
+  // this signal as other candidates might be usefull in establishing the
+  // connection.
+  sigslot::signal1<Port*> SignalPortError;
+
+  // Returns a map containing all of the connections of this port, keyed by the
+  // remote address.
+  typedef std::map<talk_base::SocketAddress, Connection*> AddressMap;
+  const AddressMap& connections() { return connections_; }
+
+  // Returns the connection to the given address or NULL if none exists.
+  virtual Connection* GetConnection(
+      const talk_base::SocketAddress& remote_addr);
+
+  // Called each time a connection is created.
+  sigslot::signal2<Port*, Connection*> SignalConnectionCreated;
+
+  // In a shared socket mode each port which shares the socket will decide
+  // to accept the packet based on the |remote_addr|. Currently only UDP
+  // port implemented this method.
+  // TODO(mallinath) - Make it pure virtual.
+  virtual bool HandleIncomingPacket(
+      talk_base::AsyncPacketSocket* socket, const char* data, size_t size,
+      const talk_base::SocketAddress& remote_addr) {
+    ASSERT(false);
+    return false;
+  }
+
+  // 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.
+  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);
+
+  void set_proxy(const std::string& user_agent,
+                 const talk_base::ProxyInfo& proxy) {
+    user_agent_ = user_agent;
+    proxy_ = proxy;
+  }
+  const std::string& user_agent() { return user_agent_; }
+  const talk_base::ProxyInfo& proxy() { return proxy_; }
+
+  virtual void EnablePortPackets();
+
+  // Indicates to the port that its official use has now begun.  This will
+  // start the timer that checks to see if the port is being used.
+  void Start();
+
+  // Called if the port has no connections and is no longer useful.
+  void Destroy();
+
+  virtual void OnMessage(talk_base::Message *pmsg);
+
+  // Debugging description of this port
+  virtual std::string ToString() const;
+  talk_base::IPAddress& ip() { return ip_; }
+  int min_port() { return min_port_; }
+  int max_port() { return max_port_; }
+
+  // This method will return local and remote username fragements from the
+  // stun username attribute if present.
+  bool ParseStunUsername(const StunMessage* stun_msg,
+                         std::string* local_username,
+                         std::string* remote_username) const;
+  void CreateStunUsername(const std::string& remote_username,
+                          std::string* stun_username_attr_str) const;
+
+  bool MaybeIceRoleConflict(const talk_base::SocketAddress& addr,
+                            IceMessage* stun_msg,
+                            const std::string& remote_ufrag);
+
+  // Called when the socket is currently able to send.
+  void OnReadyToSend();
+
+  // Called when the Connection discovers a local peer reflexive candidate.
+  // Returns the index of the new local candidate.
+  size_t AddPrflxCandidate(const Candidate& local);
+
+  // Returns if RFC 5245 ICE protocol is used.
+  bool IsStandardIce() const;
+
+  // Returns if Google ICE protocol is used.
+  bool IsGoogleIce() const;
+
+ protected:
+  void set_type(const std::string& type) { type_ = type; }
+  // Fills in the local address of the port.
+  void AddAddress(const talk_base::SocketAddress& address,
+                  const talk_base::SocketAddress& base_address,
+                  const std::string& protocol, const std::string& type,
+                  uint32 type_preference, bool final);
+
+  // Adds the given connection to the list.  (Deleting removes them.)
+  void AddConnection(Connection* conn);
+
+  // Called when a packet is received from an unknown address that is not
+  // currently a connection.  If this is an authenticated STUN binding request,
+  // then we will signal the client.
+  void OnReadPacket(const char* data, size_t size,
+                    const talk_base::SocketAddress& addr,
+                    ProtocolType proto);
+
+  // If the given data comprises a complete and correct STUN message then the
+  // return value is true, otherwise false. If the message username corresponds
+  // with this port's username fragment, msg will contain the parsed STUN
+  // message.  Otherwise, the function may send a STUN response internally.
+  // remote_username contains the remote fragment of the STUN username.
+  bool GetStunMessage(const char* data, size_t size,
+                      const talk_base::SocketAddress& addr,
+                      IceMessage** out_msg, std::string* out_username);
+
+  // Checks if the address in addr is compatible with the port's ip.
+  bool IsCompatibleAddress(const talk_base::SocketAddress& addr);
+
+ private:
+  void Construct();
+  // Called when one of our connections deletes itself.
+  void OnConnectionDestroyed(Connection* conn);
+
+  // Checks if this port is useless, and hence, should be destroyed.
+  void CheckTimeout();
+
+  talk_base::Thread* thread_;
+  talk_base::PacketSocketFactory* factory_;
+  std::string type_;
+  bool send_retransmit_count_attribute_;
+  talk_base::Network* network_;
+  talk_base::IPAddress ip_;
+  int min_port_;
+  int max_port_;
+  std::string content_name_;
+  int component_;
+  uint32 generation_;
+  talk_base::SocketAddress related_address_;
+  // In order to establish a connection to this Port (so that real data can be
+  // sent through), the other side must send us a STUN binding request that is
+  // authenticated with this username_fragment and password.
+  // PortAllocatorSession will provide these username_fragment and password.
+  //
+  // Note: we should always use username_fragment() instead of using
+  // |ice_username_fragment_| directly. For the details see the comment on
+  // username_fragment().
+  std::string ice_username_fragment_;
+  std::string password_;
+  std::vector<Candidate> candidates_;
+  AddressMap connections_;
+  enum Lifetime { LT_PRESTART, LT_PRETIMEOUT, LT_POSTTIMEOUT } lifetime_;
+  bool enable_port_packets_;
+  IceProtocolType ice_protocol_;
+  TransportRole role_;
+  uint64 tiebreaker_;
+  bool shared_socket_;
+
+  // Information to use when going through a proxy.
+  std::string user_agent_;
+  talk_base::ProxyInfo proxy_;
+
+  friend class Connection;
+};
+
+// Represents a communication link between a port on the local client and a
+// port on the remote client.
+class Connection : public talk_base::MessageHandler,
+    public sigslot::has_slots<> {
+ public:
+  // States are from RFC 5245. http://tools.ietf.org/html/rfc5245#section-5.7.4
+  enum State {
+    STATE_WAITING = 0,  // Check has not been performed, Waiting pair on CL.
+    STATE_INPROGRESS,   // Check has been sent, transaction is in progress.
+    STATE_SUCCEEDED,    // Check already done, produced a successful result.
+    STATE_FAILED        // Check for this connection failed.
+  };
+
+  virtual ~Connection();
+
+  // The local port where this connection sends and receives packets.
+  Port* port() { return port_; }
+  const Port* port() const { return port_; }
+
+  // Returns the description of the local port
+  virtual const Candidate& local_candidate() const;
+
+  // Returns the description of the remote port to which we communicate.
+  const Candidate& remote_candidate() const { return remote_candidate_; }
+
+  // Returns the pair priority.
+  uint64 priority() const;
+
+  enum ReadState {
+    STATE_READ_INIT    = 0,  // we have yet to receive a ping
+    STATE_READABLE     = 1,  // we have received pings recently
+    STATE_READ_TIMEOUT = 2,  // we haven't received pings in a while
+  };
+
+  ReadState read_state() const { return read_state_; }
+  bool readable() const { return read_state_ == STATE_READABLE; }
+
+  enum WriteState {
+    STATE_WRITABLE          = 0,  // we have received ping responses recently
+    STATE_WRITE_UNRELIABLE  = 1,  // we have had a few ping failures
+    STATE_WRITE_INIT        = 2,  // we have yet to receive a ping response
+    STATE_WRITE_TIMEOUT     = 3,  // we have had a large number of ping failures
+  };
+
+  WriteState write_state() const { return write_state_; }
+  bool writable() const { return write_state_ == STATE_WRITABLE; }
+
+  // Determines whether the connection has finished connecting.  This can only
+  // be false for TCP connections.
+  bool connected() const { return connected_; }
+
+  // Estimate of the round-trip time over this connection.
+  uint32 rtt() const { return rtt_; }
+
+  size_t sent_total_bytes();
+  size_t sent_bytes_second();
+  size_t recv_total_bytes();
+  size_t recv_bytes_second();
+  sigslot::signal1<Connection*> SignalStateChange;
+
+  // Sent when the connection has decided that it is no longer of value.  It
+  // will delete itself immediately after this call.
+  sigslot::signal1<Connection*> SignalDestroyed;
+
+  // The connection can send and receive packets asynchronously.  This matches
+  // the interface of AsyncPacketSocket, which may use UDP or TCP under the
+  // covers.
+  virtual int Send(const void* data, size_t size) = 0;
+
+  // Error if Send() returns < 0
+  virtual int GetError() = 0;
+
+  sigslot::signal3<Connection*, const char*, size_t> SignalReadPacket;
+
+  sigslot::signal1<Connection*> SignalReadyToSend;
+
+  // Called when a packet is received on this connection.
+  void OnReadPacket(const char* data, size_t size);
+
+  // Called when the socket is currently able to send.
+  void OnReadyToSend();
+
+  // Called when a connection is determined to be no longer useful to us.  We
+  // still keep it around in case the other side wants to use it.  But we can
+  // safely stop pinging on it and we can allow it to time out if the other
+  // side stops using it as well.
+  bool pruned() const { return pruned_; }
+  void Prune();
+
+  bool use_candidate_attr() const { return use_candidate_attr_; }
+  void set_use_candidate_attr(bool enable);
+
+  void set_remote_ice_mode(IceMode mode) {
+    remote_ice_mode_ = mode;
+  }
+
+  // Makes the connection go away.
+  void Destroy();
+
+  // Checks that the state of this connection is up-to-date.  The argument is
+  // the current time, which is compared against various timeouts.
+  void UpdateState(uint32 now);
+
+  // Called when this connection should try checking writability again.
+  uint32 last_ping_sent() const { return last_ping_sent_; }
+  void Ping(uint32 now);
+
+  // Called whenever a valid ping is received on this connection.  This is
+  // public because the connection intercepts the first ping for us.
+  uint32 last_ping_received() const { return last_ping_received_; }
+  void ReceivedPing();
+
+  // Debugging description of this connection
+  std::string ToString() const;
+  std::string ToSensitiveString() const;
+
+  bool reported() const { return reported_; }
+  void set_reported(bool reported) { reported_ = reported;}
+
+  // This flag will be set if this connection is the chosen one for media
+  // transmission. This connection will send STUN ping with USE-CANDIDATE
+  // attribute.
+  sigslot::signal1<Connection*> SignalUseCandidate;
+  // Invoked when Connection receives STUN error response with 487 code.
+  void HandleRoleConflictFromPeer();
+
+  State state() const { return state_; }
+
+  IceMode remote_ice_mode() const { return remote_ice_mode_; }
+
+ protected:
+  // Constructs a new connection to the given remote port.
+  Connection(Port* port, size_t index, const Candidate& candidate);
+
+  // Called back when StunRequestManager has a stun packet to send
+  void OnSendStunPacket(const void* data, size_t size, StunRequest* req);
+
+  // Callbacks from ConnectionRequest
+  void OnConnectionRequestResponse(ConnectionRequest* req,
+                                   StunMessage* response);
+  void OnConnectionRequestErrorResponse(ConnectionRequest* req,
+                                        StunMessage* response);
+  void OnConnectionRequestTimeout(ConnectionRequest* req);
+
+  // Changes the state and signals if necessary.
+  void set_read_state(ReadState value);
+  void set_write_state(WriteState value);
+  void set_state(State state);
+  void set_connected(bool value);
+
+  // Checks if this connection is useless, and hence, should be destroyed.
+  void CheckTimeout();
+
+  void OnMessage(talk_base::Message *pmsg);
+
+  Port* port_;
+  size_t local_candidate_index_;
+  Candidate remote_candidate_;
+  ReadState read_state_;
+  WriteState write_state_;
+  bool connected_;
+  bool pruned_;
+  // By default |use_candidate_attr_| flag will be true,
+  // as we will be using agrressive nomination.
+  // But when peer is ice-lite, this flag "must" be initialized to false and
+  // turn on when connection becomes "best connection".
+  bool use_candidate_attr_;
+  IceMode remote_ice_mode_;
+  StunRequestManager requests_;
+  uint32 rtt_;
+  uint32 last_ping_sent_;      // last time we sent a ping to the other side
+  uint32 last_ping_received_;  // last time we received a ping from the other
+                               // side
+  uint32 last_data_received_;
+  uint32 last_ping_response_received_;
+  std::vector<uint32> pings_since_last_response_;
+
+  talk_base::RateTracker recv_rate_tracker_;
+  talk_base::RateTracker send_rate_tracker_;
+
+ private:
+  void MaybeAddPrflxCandidate(ConnectionRequest* request,
+                              StunMessage* response);
+
+  bool reported_;
+  State state_;
+
+  friend class Port;
+  friend class ConnectionRequest;
+};
+
+// ProxyConnection defers all the interesting work to the port
+class ProxyConnection : public Connection {
+ public:
+  ProxyConnection(Port* port, size_t index, const Candidate& candidate);
+
+  virtual int Send(const void* data, size_t size);
+  virtual int GetError() { return error_; }
+
+ private:
+  int error_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_PORT_H_
diff --git a/talk/p2p/base/port_unittest.cc b/talk/p2p/base/port_unittest.cc
new file mode 100644
index 0000000..d6aa92d
--- /dev/null
+++ b/talk/p2p/base/port_unittest.cc
@@ -0,0 +1,2261 @@
+/*
+ * 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/crc32.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/basicpacketsocketfactory.h"
+#include "talk/p2p/base/portproxy.h"
+#include "talk/p2p/base/relayport.h"
+#include "talk/p2p/base/stunport.h"
+#include "talk/p2p/base/tcpport.h"
+#include "talk/p2p/base/testrelayserver.h"
+#include "talk/p2p/base/teststunserver.h"
+#include "talk/p2p/base/testturnserver.h"
+#include "talk/p2p/base/transport.h"
+#include "talk/p2p/base/turnport.h"
+
+using talk_base::AsyncPacketSocket;
+using talk_base::ByteBuffer;
+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("192.168.1.2", 0);
+static const SocketAddress kLocalAddr2("192.168.1.3", 0);
+static const SocketAddress kNatAddr1("77.77.77.77", talk_base::NAT_SERVER_PORT);
+static const SocketAddress kNatAddr2("88.88.88.88", talk_base::NAT_SERVER_PORT);
+static const SocketAddress kStunAddr("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 const SocketAddress kTurnUdpIntAddr("99.99.99.4", STUN_SERVER_PORT);
+static const SocketAddress kTurnUdpExtAddr("99.99.99.5", 0);
+static const RelayCredentials kRelayCredentials("test", "test");
+
+// TODO: Update these when RFC5245 is completely supported.
+// Magic value of 30 is from RFC3484, for IPv4 addresses.
+static const uint32 kDefaultPrflxPriority = ICE_TYPE_PREFERENCE_PRFLX << 24 |
+             30 << 8 | (256 - ICE_CANDIDATE_COMPONENT_DEFAULT);
+static const int STUN_ERROR_BAD_REQUEST_AS_GICE =
+    STUN_ERROR_BAD_REQUEST / 256 * 100 + STUN_ERROR_BAD_REQUEST % 256;
+static const int STUN_ERROR_UNAUTHORIZED_AS_GICE =
+    STUN_ERROR_UNAUTHORIZED / 256 * 100 + STUN_ERROR_UNAUTHORIZED % 256;
+static const int STUN_ERROR_SERVER_ERROR_AS_GICE =
+    STUN_ERROR_SERVER_ERROR / 256 * 100 + STUN_ERROR_SERVER_ERROR % 256;
+
+static const int kTiebreaker1 = 11111;
+static const int kTiebreaker2 = 22222;
+
+static Candidate GetCandidate(Port* port) {
+  assert(port->Candidates().size() == 1);
+  return port->Candidates()[0];
+}
+
+static SocketAddress GetAddress(Port* port) {
+  return GetCandidate(port).address();
+}
+
+static IceMessage* CopyStunMessage(const IceMessage* src) {
+  IceMessage* dst = new IceMessage();
+  ByteBuffer buf;
+  src->Write(&buf);
+  dst->Read(&buf);
+  return dst;
+}
+
+static bool WriteStunMessage(const StunMessage* msg, ByteBuffer* buf) {
+  buf->Resize(0);  // clear out any existing buffer contents
+  return msg->Write(buf);
+}
+
+// Stub port class for testing STUN generation and processing.
+class TestPort : public Port {
+ public:
+  TestPort(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,
+           const std::string& username_fragment, const std::string& password)
+      : Port(thread, type, factory, network, ip,
+             min_port, max_port, username_fragment, password) {
+  }
+  ~TestPort() {}
+
+  // Expose GetStunMessage so that we can test it.
+  using cricket::Port::GetStunMessage;
+
+  // The last StunMessage that was sent on this Port.
+  // TODO: Make these const; requires changes to SendXXXXResponse.
+  ByteBuffer* last_stun_buf() { return last_stun_buf_.get(); }
+  IceMessage* last_stun_msg() { return last_stun_msg_.get(); }
+  int last_stun_error_code() {
+    int code = 0;
+    if (last_stun_msg_) {
+      const StunErrorCodeAttribute* error_attr = last_stun_msg_->GetErrorCode();
+      if (error_attr) {
+        code = error_attr->code();
+      }
+    }
+    return code;
+  }
+
+  virtual void PrepareAddress() {
+    talk_base::SocketAddress addr(ip(), min_port());
+    AddAddress(addr, addr, "udp", Type(), ICE_TYPE_PREFERENCE_HOST, true);
+  }
+
+  // Exposed for testing candidate building.
+  void AddCandidateAddress(const talk_base::SocketAddress& addr) {
+    AddAddress(addr, addr, "udp", Type(), type_preference_, false);
+  }
+  void AddCandidateAddress(const talk_base::SocketAddress& addr,
+                           const talk_base::SocketAddress& base_address,
+                           const std::string& type,
+                           int type_preference,
+                           bool final) {
+    AddAddress(addr, base_address, "udp", type,
+               type_preference, final);
+  }
+
+  virtual Connection* CreateConnection(const Candidate& remote_candidate,
+                                       CandidateOrigin origin) {
+    Connection* conn = new ProxyConnection(this, 0, remote_candidate);
+    AddConnection(conn);
+    // Set use-candidate attribute flag as this will add USE-CANDIDATE attribute
+    // in STUN binding requests.
+    conn->set_use_candidate_attr(true);
+    return conn;
+  }
+  virtual int SendTo(
+      const void* data, size_t size, const talk_base::SocketAddress& addr,
+      bool payload) {
+    if (!payload) {
+      IceMessage* msg = new IceMessage;
+      ByteBuffer* buf = new ByteBuffer(static_cast<const char*>(data), size);
+      ByteBuffer::ReadPosition pos(buf->GetReadPosition());
+      if (!msg->Read(buf)) {
+        delete msg;
+        delete buf;
+        return -1;
+      }
+      buf->SetReadPosition(pos);
+      last_stun_buf_.reset(buf);
+      last_stun_msg_.reset(msg);
+    }
+    return size;
+  }
+  virtual int SetOption(talk_base::Socket::Option opt, int value) {
+    return 0;
+  }
+  virtual int GetOption(talk_base::Socket::Option opt, int* value) {
+    return -1;
+  }
+  virtual int GetError() {
+    return 0;
+  }
+  void Reset() {
+    last_stun_buf_.reset();
+    last_stun_msg_.reset();
+  }
+  void set_type_preference(int type_preference) {
+    type_preference_ = type_preference;
+  }
+
+ private:
+  talk_base::scoped_ptr<ByteBuffer> last_stun_buf_;
+  talk_base::scoped_ptr<IceMessage> last_stun_msg_;
+  int type_preference_;
+};
+
+class TestChannel : public sigslot::has_slots<> {
+ public:
+  TestChannel(Port* p1, Port* p2)
+      : ice_mode_(ICEMODE_FULL), src_(p1), dst_(p2), complete_count_(0),
+	conn_(NULL), remote_request_(NULL), nominated_(false) {
+    src_->SignalPortComplete.connect(
+        this, &TestChannel::OnPortComplete);
+    src_->SignalUnknownAddress.connect(this, &TestChannel::OnUnknownAddress);
+  }
+
+  int complete_count() { return complete_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);
+    IceMode remote_ice_mode =
+        (ice_mode_ == ICEMODE_FULL) ? ICEMODE_LITE : ICEMODE_FULL;
+    conn_->set_remote_ice_mode(remote_ice_mode);
+    conn_->set_use_candidate_attr(remote_ice_mode == ICEMODE_FULL);
+    conn_->SignalStateChange.connect(
+        this, &TestChannel::OnConnectionStateChange);
+  }
+  void OnConnectionStateChange(Connection* conn) {
+    if (conn->write_state() == Connection::STATE_WRITABLE) {
+      conn->set_use_candidate_attr(true);
+      nominated_ = true;
+    }
+  }
+  void AcceptConnection() {
+    ASSERT_TRUE(remote_request_.get() != NULL);
+    Candidate c = GetCandidate(dst_);
+    c.set_address(remote_address_);
+    conn_ = src_->CreateConnection(c, Port::ORIGIN_MESSAGE);
+    src_->SendBindingResponse(remote_request_.get(), remote_address_);
+    remote_request_.reset();
+  }
+  void Ping() {
+    Ping(0);
+  }
+  void Ping(uint32 now) {
+    conn_->Ping(now);
+  }
+  void Stop() {
+    conn_->SignalDestroyed.connect(this, &TestChannel::OnDestroyed);
+    conn_->Destroy();
+  }
+
+  void OnPortComplete(Port* port) {
+    complete_count_++;
+  }
+  void SetIceMode(IceMode ice_mode) {
+    ice_mode_ = ice_mode;
+  }
+
+  void OnUnknownAddress(PortInterface* port, const SocketAddress& addr,
+                        ProtocolType proto,
+                        IceMessage* msg, const std::string& rf,
+                        bool /*port_muxed*/) {
+    ASSERT_EQ(src_.get(), port);
+    if (!remote_address_.IsNil()) {
+      ASSERT_EQ(remote_address_, addr);
+    }
+    // MI and PRIORITY attribute should be present in ping requests when port
+    // is in ICEPROTO_RFC5245 mode.
+    const cricket::StunUInt32Attribute* priority_attr =
+        msg->GetUInt32(STUN_ATTR_PRIORITY);
+    const cricket::StunByteStringAttribute* mi_attr =
+        msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY);
+    const cricket::StunUInt32Attribute* fingerprint_attr =
+        msg->GetUInt32(STUN_ATTR_FINGERPRINT);
+    if (src_->IceProtocol() == cricket::ICEPROTO_RFC5245) {
+      EXPECT_TRUE(priority_attr != NULL);
+      EXPECT_TRUE(mi_attr != NULL);
+      EXPECT_TRUE(fingerprint_attr != NULL);
+    } else {
+      EXPECT_TRUE(priority_attr == NULL);
+      EXPECT_TRUE(mi_attr == NULL);
+      EXPECT_TRUE(fingerprint_attr == NULL);
+    }
+    remote_address_ = addr;
+    remote_request_.reset(CopyStunMessage(msg));
+    remote_frag_ = rf;
+  }
+
+  void OnDestroyed(Connection* conn) {
+    ASSERT_EQ(conn_, conn);
+    conn_ = NULL;
+  }
+
+  bool nominated() const { return nominated_; }
+
+ private:
+  IceMode ice_mode_;
+  talk_base::scoped_ptr<Port> src_;
+  Port* dst_;
+
+  int complete_count_;
+  Connection* conn_;
+  SocketAddress remote_address_;
+  talk_base::scoped_ptr<StunMessage> remote_request_;
+  std::string remote_frag_;
+  bool nominated_;
+};
+
+class PortTest : public testing::Test, public sigslot::has_slots<> {
+ 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), 32),
+        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),
+        turn_server_(main_, kTurnUdpIntAddr, kTurnUdpExtAddr),
+        relay_server_(main_, kRelayUdpIntAddr, kRelayUdpExtAddr,
+                      kRelayTcpIntAddr, kRelayTcpExtAddr,
+                      kRelaySslTcpIntAddr, kRelaySslTcpExtAddr),
+        username_(talk_base::CreateRandomString(ICE_UFRAG_LENGTH)),
+        password_(talk_base::CreateRandomString(ICE_PWD_LENGTH)),
+        ice_protocol_(cricket::ICEPROTO_GOOGLE),
+        role_conflict_(false) {
+    network_.AddIP(talk_base::IPAddress(INADDR_ANY));
+  }
+
+ protected:
+  static void SetUpTestCase() {
+    // Ensure the RNG is inited.
+    talk_base::InitRandom(NULL, 0);
+  }
+
+  void TestLocalToLocal() {
+    Port* port1 = CreateUdpPort(kLocalAddr1);
+    Port* port2 = CreateUdpPort(kLocalAddr2);
+    TestConnectivity("udp", port1, "udp", port2, true, true, true, true);
+  }
+  void TestLocalToStun(NATType ntype) {
+    Port* port1 = CreateUdpPort(kLocalAddr1);
+    nat_server2_.reset(CreateNatServer(kNatAddr2, ntype));
+    Port* port2 = CreateStunPort(kLocalAddr2, &nat_socket_factory2_);
+    TestConnectivity("udp", port1, StunName(ntype), port2,
+                     ntype == NAT_OPEN_CONE, true,
+                     ntype != NAT_SYMMETRIC, true);
+  }
+  void TestLocalToRelay(RelayType rtype, ProtocolType proto) {
+    Port* port1 = CreateUdpPort(kLocalAddr1);
+    Port* port2 = CreateRelayPort(kLocalAddr2, rtype, proto, PROTO_UDP);
+    TestConnectivity("udp", port1, RelayName(rtype, proto), port2,
+                     rtype == RELAY_GTURN, true, true, true);
+  }
+  void TestStunToLocal(NATType ntype) {
+    nat_server1_.reset(CreateNatServer(kNatAddr1, ntype));
+    Port* port1 = CreateStunPort(kLocalAddr1, &nat_socket_factory1_);
+    Port* port2 = CreateUdpPort(kLocalAddr2);
+    TestConnectivity(StunName(ntype), port1, "udp", port2,
+                     true, ntype != NAT_SYMMETRIC, true, true);
+  }
+  void TestStunToStun(NATType ntype1, NATType ntype2) {
+    nat_server1_.reset(CreateNatServer(kNatAddr1, ntype1));
+    Port* port1 = CreateStunPort(kLocalAddr1, &nat_socket_factory1_);
+    nat_server2_.reset(CreateNatServer(kNatAddr2, ntype2));
+    Port* port2 = CreateStunPort(kLocalAddr2, &nat_socket_factory2_);
+    TestConnectivity(StunName(ntype1), port1, StunName(ntype2), port2,
+                     ntype2 == NAT_OPEN_CONE,
+                     ntype1 != NAT_SYMMETRIC, ntype2 != NAT_SYMMETRIC,
+                     ntype1 + ntype2 < (NAT_PORT_RESTRICTED + NAT_SYMMETRIC));
+  }
+  void TestStunToRelay(NATType ntype, RelayType rtype, ProtocolType proto) {
+    nat_server1_.reset(CreateNatServer(kNatAddr1, ntype));
+    Port* port1 = CreateStunPort(kLocalAddr1, &nat_socket_factory1_);
+    Port* port2 = CreateRelayPort(kLocalAddr2, rtype, proto, PROTO_UDP);
+    TestConnectivity(StunName(ntype), port1, RelayName(rtype, proto), port2,
+                     rtype == RELAY_GTURN, ntype != NAT_SYMMETRIC, true, true);
+  }
+  void TestTcpToTcp() {
+    Port* port1 = CreateTcpPort(kLocalAddr1);
+    Port* port2 = CreateTcpPort(kLocalAddr2);
+    TestConnectivity("tcp", port1, "tcp", port2, true, false, true, true);
+  }
+  void TestTcpToRelay(RelayType rtype, ProtocolType proto) {
+    Port* port1 = CreateTcpPort(kLocalAddr1);
+    Port* port2 = CreateRelayPort(kLocalAddr2, rtype, proto, PROTO_TCP);
+    TestConnectivity("tcp", port1, RelayName(rtype, proto), port2,
+                     rtype == RELAY_GTURN, false, true, true);
+  }
+  void TestSslTcpToRelay(RelayType rtype, ProtocolType proto) {
+    Port* port1 = CreateTcpPort(kLocalAddr1);
+    Port* port2 = CreateRelayPort(kLocalAddr2, rtype, proto, PROTO_SSLTCP);
+    TestConnectivity("ssltcp", port1, RelayName(rtype, proto), port2,
+                     rtype == RELAY_GTURN, 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) {
+    UDPPort* port = UDPPort::Create(main_, socket_factory, &network_,
+                                    addr.ipaddr(), 0, 0, username_, password_);
+    port->SetIceProtocolType(ice_protocol_);
+    return port;
+  }
+  TCPPort* CreateTcpPort(const SocketAddress& addr) {
+    TCPPort* port = CreateTcpPort(addr, &socket_factory_);
+    port->SetIceProtocolType(ice_protocol_);
+    return port;
+  }
+  TCPPort* CreateTcpPort(const SocketAddress& addr,
+                        PacketSocketFactory* socket_factory) {
+    TCPPort* port = TCPPort::Create(main_, socket_factory, &network_,
+                                    addr.ipaddr(), 0, 0, username_, password_,
+                                    true);
+    port->SetIceProtocolType(ice_protocol_);
+    return port;
+  }
+  StunPort* CreateStunPort(const SocketAddress& addr,
+                           talk_base::PacketSocketFactory* factory) {
+    StunPort* port = StunPort::Create(main_, factory, &network_,
+                                      addr.ipaddr(), 0, 0,
+                                      username_, password_, kStunAddr);
+    port->SetIceProtocolType(ice_protocol_);
+    return port;
+  }
+  Port* CreateRelayPort(const SocketAddress& addr, RelayType rtype,
+                        ProtocolType int_proto, ProtocolType ext_proto) {
+    if (rtype == RELAY_TURN) {
+      return CreateTurnPort(addr, &socket_factory_, int_proto, ext_proto);
+    } else {
+      return CreateGturnPort(addr, int_proto, ext_proto);
+    }
+  }
+  TurnPort* CreateTurnPort(const SocketAddress& addr,
+                           PacketSocketFactory* socket_factory,
+                           ProtocolType int_proto, ProtocolType ext_proto) {
+    TurnPort* port = TurnPort::Create(main_, socket_factory, &network_,
+                                      addr.ipaddr(), 0, 0,
+                                      username_, password_, ProtocolAddress(
+                                          kTurnUdpIntAddr, PROTO_UDP),
+                                      kRelayCredentials);
+    port->SetIceProtocolType(ice_protocol_);
+    return port;
+  }
+  RelayPort* CreateGturnPort(const SocketAddress& addr,
+                             ProtocolType int_proto, ProtocolType ext_proto) {
+    RelayPort* port = CreateGturnPort(addr);
+    SocketAddress addrs[] =
+        { kRelayUdpIntAddr, kRelayTcpIntAddr, kRelaySslTcpIntAddr };
+    port->AddServerAddress(ProtocolAddress(addrs[int_proto], int_proto));
+    return port;
+  }
+  RelayPort* CreateGturnPort(const SocketAddress& addr) {
+    RelayPort* port = RelayPort::Create(main_, &socket_factory_, &network_,
+                                        addr.ipaddr(), 0, 0,
+                                        username_, password_);
+    // TODO: Add an external address for ext_proto, so that the
+    // other side can connect to this port using a non-UDP protocol.
+    port->SetIceProtocolType(ice_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(RelayType type, ProtocolType proto) {
+    if (type == RELAY_TURN) {
+      switch (proto) {
+        case PROTO_UDP:           return "turn(udp)";
+        case PROTO_TCP:           return "turn(tcp)";
+        case PROTO_SSLTCP:        return "turn(ssltcp)";
+        default:                  return "turn(?)";
+      }
+    } else {
+      switch (proto) {
+        case PROTO_UDP:           return "gturn(udp)";
+        case PROTO_TCP:           return "gturn(tcp)";
+        case PROTO_SSLTCP:        return "gturn(ssltcp)";
+        default:                  return "gturn(?)";
+      }
+    }
+  }
+
+  void TestCrossFamilyPorts(int type);
+
+  // 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);
+
+  void SetIceProtocolType(cricket::IceProtocolType protocol) {
+    ice_protocol_ = protocol;
+  }
+
+  IceMessage* CreateStunMessage(int type) {
+    IceMessage* msg = new IceMessage();
+    msg->SetType(type);
+    msg->SetTransactionID("TESTTESTTEST");
+    return msg;
+  }
+  IceMessage* CreateStunMessageWithUsername(int type,
+                                            const std::string& username) {
+    IceMessage* msg = CreateStunMessage(type);
+    msg->AddAttribute(
+        new StunByteStringAttribute(STUN_ATTR_USERNAME, username));
+    return msg;
+  }
+  TestPort* CreateTestPort(const talk_base::SocketAddress& addr,
+                           const std::string& username,
+                           const std::string& password) {
+    TestPort* port =  new TestPort(main_, "test", &socket_factory_, &network_,
+                                   addr.ipaddr(), 0, 0, username, password);
+    port->SignalRoleConflict.connect(this, &PortTest::OnRoleConflict);
+    return port;
+  }
+  TestPort* CreateTestPort(const talk_base::SocketAddress& addr,
+                           const std::string& username,
+                           const std::string& password,
+                           cricket::IceProtocolType type,
+                           cricket::TransportRole role,
+                           int tiebreaker) {
+    TestPort* port = CreateTestPort(addr, username, password);
+    port->SetIceProtocolType(type);
+    port->SetRole(role);
+    port->SetTiebreaker(tiebreaker);
+    return port;
+  }
+
+  void OnRoleConflict(PortInterface* port) {
+    role_conflict_ = true;
+  }
+  bool role_conflict() const { return role_conflict_; }
+
+  talk_base::BasicPacketSocketFactory* nat_socket_factory1() {
+    return &nat_socket_factory1_;
+  }
+
+ 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_;
+  TestTurnServer turn_server_;
+  TestRelayServer relay_server_;
+  std::string username_;
+  std::string password_;
+  cricket::IceProtocolType ice_protocol_;
+  bool role_conflict_;
+};
+
+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_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT);
+  port2->set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT);
+
+  // Set up channels.
+  TestChannel ch1(port1, port2);
+  TestChannel ch2(port2, port1);
+  EXPECT_EQ(0, ch1.complete_count());
+  EXPECT_EQ(0, ch2.complete_count());
+
+  // Acquire addresses.
+  ch1.Start();
+  ch2.Start();
+  ASSERT_EQ_WAIT(1, ch1.complete_count(), kTimeout);
+  ASSERT_EQ_WAIT(1, ch2.complete_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().IsNil(), 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().IsNil());
+    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().IsNil());
+    EXPECT_TRUE(ch2.remote_address().IsNil());
+
+    // 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_INIT) {
+        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().IsNil());
+      EXPECT_TRUE(ch2.remote_address().IsNil());
+
+      // 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().IsNil(), kTimeout);
+      EXPECT_EQ(Connection::STATE_READ_INIT, ch2.conn()->read_state());
+      EXPECT_TRUE(ch1.remote_address().IsNil());
+
+      // 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().IsNil());
+      EXPECT_EQ(Connection::STATE_READ_INIT, 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().IsNil());
+      EXPECT_TRUE(ch2.remote_address().IsNil());
+      ch1.Ping();
+      WAIT(!ch2.remote_address().IsNil(), kTimeout);
+      EXPECT_TRUE(ch1.remote_address().IsNil());
+      EXPECT_TRUE(ch2.remote_address().IsNil());
+    }
+  }
+
+  // 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,
+      int opts) {
+    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, int opts) {
+    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, TestLocalToTurn) {
+  TestLocalToRelay(RELAY_TURN, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestLocalToGturn) {
+  TestLocalToRelay(RELAY_GTURN, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestLocalToTcpGturn) {
+  TestLocalToRelay(RELAY_GTURN, PROTO_TCP);
+}
+
+TEST_F(PortTest, TestLocalToSslTcpGturn) {
+  TestLocalToRelay(RELAY_GTURN, 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, TestConeNatToTurn) {
+  TestStunToRelay(NAT_OPEN_CONE, RELAY_TURN, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestConeNatToGturn) {
+  TestStunToRelay(NAT_OPEN_CONE, RELAY_GTURN, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestConeNatToTcpGturn) {
+  TestStunToRelay(NAT_OPEN_CONE, RELAY_GTURN, 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, TestARNatToTurn) {
+  TestStunToRelay(NAT_ADDR_RESTRICTED, RELAY_TURN, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestARNatToGturn) {
+  TestStunToRelay(NAT_ADDR_RESTRICTED, RELAY_GTURN, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestARNATNatToTcpGturn) {
+  TestStunToRelay(NAT_ADDR_RESTRICTED, RELAY_GTURN, 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, TestPRNatToTurn) {
+  TestStunToRelay(NAT_PORT_RESTRICTED, RELAY_TURN, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestPRNatToGturn) {
+  TestStunToRelay(NAT_PORT_RESTRICTED, RELAY_GTURN, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestPRNatToTcpGturn) {
+  TestStunToRelay(NAT_PORT_RESTRICTED, RELAY_GTURN, 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, TestSymNatToTurn) {
+  TestStunToRelay(NAT_SYMMETRIC, RELAY_TURN, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestSymNatToGturn) {
+  TestStunToRelay(NAT_SYMMETRIC, RELAY_GTURN, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestSymNatToTcpGturn) {
+  TestStunToRelay(NAT_SYMMETRIC, RELAY_GTURN, 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);
+}
+*/
+
+// This test case verifies standard ICE features in STUN messages. Currently it
+// verifies Message Integrity attribute in STUN messages and username in STUN
+// binding request will have colon (":") between remote and local username.
+TEST_F(PortTest, TestLocalToLocalAsIce) {
+  SetIceProtocolType(cricket::ICEPROTO_RFC5245);
+  UDPPort* port1 = CreateUdpPort(kLocalAddr1);
+  port1->SetRole(cricket::ROLE_CONTROLLING);
+  port1->SetTiebreaker(kTiebreaker1);
+  ASSERT_EQ(cricket::ICEPROTO_RFC5245, port1->IceProtocol());
+  UDPPort* port2 = CreateUdpPort(kLocalAddr2);
+  port2->SetRole(cricket::ROLE_CONTROLLED);
+  port2->SetTiebreaker(kTiebreaker2);
+  ASSERT_EQ(cricket::ICEPROTO_RFC5245, port2->IceProtocol());
+  // Same parameters as TestLocalToLocal above.
+  TestConnectivity("udp", port1, "udp", port2, true, true, true, true);
+}
+
+// This test is trying to validate a successful and failure scenario in a
+// loopback test when protocol is RFC5245. For success tiebreaker, username
+// should remain equal to the request generated by the port and role of port
+// must be in controlling.
+TEST_F(PortTest, TestLoopbackCallAsIce) {
+  talk_base::scoped_ptr<TestPort> lport(
+      CreateTestPort(kLocalAddr1, "lfrag", "lpass"));
+  lport->SetIceProtocolType(ICEPROTO_RFC5245);
+  lport->SetRole(cricket::ROLE_CONTROLLING);
+  lport->SetTiebreaker(kTiebreaker1);
+  lport->PrepareAddress();
+  ASSERT_FALSE(lport->Candidates().empty());
+  Connection* conn = lport->CreateConnection(lport->Candidates()[0],
+                                             Port::ORIGIN_MESSAGE);
+  conn->Ping(0);
+
+  ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000);
+  IceMessage* msg = lport->last_stun_msg();
+  EXPECT_EQ(STUN_BINDING_REQUEST, msg->type());
+  conn->OnReadPacket(lport->last_stun_buf()->Data(),
+                     lport->last_stun_buf()->Length());
+  ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000);
+  msg = lport->last_stun_msg();
+  EXPECT_EQ(STUN_BINDING_RESPONSE, msg->type());
+
+  // If the tiebreaker value is different from port, we expect a error response.
+  lport->Reset();
+  lport->AddCandidateAddress(kLocalAddr2);
+  // Creating a different connection as |conn| is in STATE_READABLE.
+  Connection* conn1 = lport->CreateConnection(lport->Candidates()[1],
+                                              Port::ORIGIN_MESSAGE);
+  conn1->Ping(0);
+
+  ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000);
+  msg = lport->last_stun_msg();
+  EXPECT_EQ(STUN_BINDING_REQUEST, msg->type());
+  IceMessage* modified_req = CreateStunMessage(STUN_BINDING_REQUEST);
+  const StunByteStringAttribute* username_attr = msg->GetByteString(
+      STUN_ATTR_USERNAME);
+  modified_req->AddAttribute(new StunByteStringAttribute(
+      STUN_ATTR_USERNAME, username_attr->GetString()));
+  // To make sure we receive error response, adding tiebreaker less than
+  // what's present in request.
+  modified_req->AddAttribute(new StunUInt64Attribute(
+      STUN_ATTR_ICE_CONTROLLING, kTiebreaker1 - 1));
+  modified_req->AddMessageIntegrity("lpass");
+  modified_req->AddFingerprint();
+
+  lport->Reset();
+  talk_base::scoped_ptr<ByteBuffer> buf(new ByteBuffer());
+  WriteStunMessage(modified_req, buf.get());
+  conn1->OnReadPacket(buf->Data(), buf->Length());
+  ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000);
+  msg = lport->last_stun_msg();
+  EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, msg->type());
+}
+
+// This test verifies role conflict signal is received when there is
+// conflict in the role. In this case both ports are in controlling and
+// |rport| has higher tiebreaker value than |lport|. Since |lport| has lower
+// value of tiebreaker, when it receives ping request from |rport| it will
+// send role conflict signal.
+TEST_F(PortTest, TestIceRoleConflict) {
+  talk_base::scoped_ptr<TestPort> lport(
+      CreateTestPort(kLocalAddr1, "lfrag", "lpass"));
+  lport->SetIceProtocolType(ICEPROTO_RFC5245);
+  lport->SetRole(cricket::ROLE_CONTROLLING);
+  lport->SetTiebreaker(kTiebreaker1);
+  talk_base::scoped_ptr<TestPort> rport(
+      CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+  rport->SetIceProtocolType(ICEPROTO_RFC5245);
+  rport->SetRole(cricket::ROLE_CONTROLLING);
+  rport->SetTiebreaker(kTiebreaker2);
+
+  lport->PrepareAddress();
+  rport->PrepareAddress();
+  ASSERT_FALSE(lport->Candidates().empty());
+  ASSERT_FALSE(rport->Candidates().empty());
+  Connection* lconn = lport->CreateConnection(rport->Candidates()[0],
+                                              Port::ORIGIN_MESSAGE);
+  Connection* rconn = rport->CreateConnection(lport->Candidates()[0],
+                                              Port::ORIGIN_MESSAGE);
+  rconn->Ping(0);
+
+  ASSERT_TRUE_WAIT(rport->last_stun_msg() != NULL, 1000);
+  IceMessage* msg = rport->last_stun_msg();
+  EXPECT_EQ(STUN_BINDING_REQUEST, msg->type());
+  // Send rport binding request to lport.
+  lconn->OnReadPacket(rport->last_stun_buf()->Data(),
+                      rport->last_stun_buf()->Length());
+
+  ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000);
+  EXPECT_EQ(STUN_BINDING_RESPONSE, lport->last_stun_msg()->type());
+  EXPECT_TRUE(role_conflict());
+}
+
+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());
+}
+
+void PortTest::TestCrossFamilyPorts(int type) {
+  FakePacketSocketFactory factory;
+  scoped_ptr<Port> ports[4];
+  SocketAddress addresses[4] = {SocketAddress("192.168.1.3", 0),
+                                SocketAddress("192.168.1.4", 0),
+                                SocketAddress("2001:db8::1", 0),
+                                SocketAddress("2001:db8::2", 0)};
+  for (int i = 0; i < 4; i++) {
+    FakeAsyncPacketSocket *socket = new FakeAsyncPacketSocket();
+    if (type == SOCK_DGRAM) {
+      factory.set_next_udp_socket(socket);
+      ports[i].reset(CreateUdpPort(addresses[i], &factory));
+    } else if (type == SOCK_STREAM) {
+      factory.set_next_server_tcp_socket(socket);
+      ports[i].reset(CreateTcpPort(addresses[i], &factory));
+    }
+    socket->set_state(AsyncPacketSocket::STATE_BINDING);
+    socket->SignalAddressReady(socket, addresses[i]);
+    ports[i]->PrepareAddress();
+  }
+
+  // IPv4 Port, connects to IPv6 candidate and then to IPv4 candidate.
+  if (type == SOCK_STREAM) {
+    FakeAsyncPacketSocket* clientsocket = new FakeAsyncPacketSocket();
+    factory.set_next_client_tcp_socket(clientsocket);
+  }
+  Connection* c = ports[0]->CreateConnection(GetCandidate(ports[2].get()),
+                                             Port::ORIGIN_MESSAGE);
+  EXPECT_TRUE(NULL == c);
+  EXPECT_EQ(0U, ports[0]->connections().size());
+  c = ports[0]->CreateConnection(GetCandidate(ports[1].get()),
+                                 Port::ORIGIN_MESSAGE);
+  EXPECT_FALSE(NULL == c);
+  EXPECT_EQ(1U, ports[0]->connections().size());
+
+  // IPv6 Port, connects to IPv4 candidate and to IPv6 candidate.
+  if (type == SOCK_STREAM) {
+    FakeAsyncPacketSocket* clientsocket = new FakeAsyncPacketSocket();
+    factory.set_next_client_tcp_socket(clientsocket);
+  }
+  c = ports[2]->CreateConnection(GetCandidate(ports[0].get()),
+                                 Port::ORIGIN_MESSAGE);
+  EXPECT_TRUE(NULL == c);
+  EXPECT_EQ(0U, ports[2]->connections().size());
+  c = ports[2]->CreateConnection(GetCandidate(ports[3].get()),
+                                 Port::ORIGIN_MESSAGE);
+  EXPECT_FALSE(NULL == c);
+  EXPECT_EQ(1U, ports[2]->connections().size());
+}
+
+TEST_F(PortTest, TestSkipCrossFamilyTcp) {
+  TestCrossFamilyPorts(SOCK_STREAM);
+}
+
+TEST_F(PortTest, TestSkipCrossFamilyUdp) {
+  TestCrossFamilyPorts(SOCK_DGRAM);
+}
+
+// Test sending STUN messages in GICE format.
+TEST_F(PortTest, TestSendStunMessageAsGice) {
+  talk_base::scoped_ptr<TestPort> lport(
+      CreateTestPort(kLocalAddr1, "lfrag", "lpass"));
+  talk_base::scoped_ptr<TestPort> rport(
+      CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+  lport->SetIceProtocolType(ICEPROTO_GOOGLE);
+  rport->SetIceProtocolType(ICEPROTO_GOOGLE);
+
+  // Send a fake ping from lport to rport.
+  lport->PrepareAddress();
+  rport->PrepareAddress();
+  ASSERT_FALSE(rport->Candidates().empty());
+  Connection* conn = lport->CreateConnection(rport->Candidates()[0],
+      Port::ORIGIN_MESSAGE);
+  rport->CreateConnection(lport->Candidates()[0], Port::ORIGIN_MESSAGE);
+  conn->Ping(0);
+
+  // Check that it's a proper BINDING-REQUEST.
+  ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000);
+  IceMessage* msg = lport->last_stun_msg();
+  EXPECT_EQ(STUN_BINDING_REQUEST, msg->type());
+  EXPECT_FALSE(msg->IsLegacy());
+  const StunByteStringAttribute* username_attr = msg->GetByteString(
+      STUN_ATTR_USERNAME);
+  ASSERT_TRUE(username_attr != NULL);
+  EXPECT_EQ("rfraglfrag", username_attr->GetString());
+  EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) == NULL);
+  EXPECT_TRUE(msg->GetByteString(STUN_ATTR_PRIORITY) == NULL);
+  EXPECT_TRUE(msg->GetByteString(STUN_ATTR_FINGERPRINT) == NULL);
+
+  // Save a copy of the BINDING-REQUEST for use below.
+  talk_base::scoped_ptr<IceMessage> request(CopyStunMessage(msg));
+
+  // Respond with a BINDING-RESPONSE.
+  rport->SendBindingResponse(request.get(), lport->Candidates()[0].address());
+  msg = rport->last_stun_msg();
+  ASSERT_TRUE(msg != NULL);
+  EXPECT_EQ(STUN_BINDING_RESPONSE, msg->type());
+  EXPECT_FALSE(msg->IsLegacy());
+  username_attr = msg->GetByteString(STUN_ATTR_USERNAME);
+  ASSERT_TRUE(username_attr != NULL);  // GICE has a username in the response.
+  EXPECT_EQ("rfraglfrag", username_attr->GetString());
+  const StunAddressAttribute* addr_attr = msg->GetAddress(
+      STUN_ATTR_MAPPED_ADDRESS);
+  ASSERT_TRUE(addr_attr != NULL);
+  EXPECT_EQ(lport->Candidates()[0].address(), addr_attr->GetAddress());
+  EXPECT_TRUE(msg->GetByteString(STUN_ATTR_XOR_MAPPED_ADDRESS) == NULL);
+  EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) == NULL);
+  EXPECT_TRUE(msg->GetByteString(STUN_ATTR_PRIORITY) == NULL);
+  EXPECT_TRUE(msg->GetByteString(STUN_ATTR_FINGERPRINT) == NULL);
+
+  // Respond with a BINDING-ERROR-RESPONSE. This wouldn't happen in real life,
+  // but we can do it here.
+  rport->SendBindingErrorResponse(request.get(),
+                                  rport->Candidates()[0].address(),
+                                  STUN_ERROR_SERVER_ERROR,
+                                  STUN_ERROR_REASON_SERVER_ERROR);
+  msg = rport->last_stun_msg();
+  ASSERT_TRUE(msg != NULL);
+  EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, msg->type());
+  EXPECT_FALSE(msg->IsLegacy());
+  username_attr = msg->GetByteString(STUN_ATTR_USERNAME);
+  ASSERT_TRUE(username_attr != NULL);  // GICE has a username in the response.
+  EXPECT_EQ("rfraglfrag", username_attr->GetString());
+  const StunErrorCodeAttribute* error_attr = msg->GetErrorCode();
+  ASSERT_TRUE(error_attr != NULL);
+  // The GICE wire format for error codes is incorrect.
+  EXPECT_EQ(STUN_ERROR_SERVER_ERROR_AS_GICE, error_attr->code());
+  EXPECT_EQ(STUN_ERROR_SERVER_ERROR / 256, error_attr->eclass());
+  EXPECT_EQ(STUN_ERROR_SERVER_ERROR % 256, error_attr->number());
+  EXPECT_EQ(std::string(STUN_ERROR_REASON_SERVER_ERROR), error_attr->reason());
+  EXPECT_TRUE(msg->GetByteString(STUN_ATTR_PRIORITY) == NULL);
+  EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) == NULL);
+  EXPECT_TRUE(msg->GetByteString(STUN_ATTR_FINGERPRINT) == NULL);
+}
+
+// Test sending STUN messages in ICE format.
+TEST_F(PortTest, TestSendStunMessageAsIce) {
+  talk_base::scoped_ptr<TestPort> lport(
+      CreateTestPort(kLocalAddr1, "lfrag", "lpass"));
+  talk_base::scoped_ptr<TestPort> rport(
+      CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+  lport->SetIceProtocolType(ICEPROTO_RFC5245);
+  lport->SetRole(cricket::ROLE_CONTROLLING);
+  lport->SetTiebreaker(kTiebreaker1);
+  rport->SetIceProtocolType(ICEPROTO_RFC5245);
+  rport->SetRole(cricket::ROLE_CONTROLLED);
+  rport->SetTiebreaker(kTiebreaker2);
+
+  // Send a fake ping from lport to rport.
+  lport->PrepareAddress();
+  rport->PrepareAddress();
+  ASSERT_FALSE(rport->Candidates().empty());
+  Connection* lconn = lport->CreateConnection(
+      rport->Candidates()[0], Port::ORIGIN_MESSAGE);
+  Connection* rconn = rport->CreateConnection(
+      lport->Candidates()[0], Port::ORIGIN_MESSAGE);
+  lconn->Ping(0);
+
+  // Check that it's a proper BINDING-REQUEST.
+  ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000);
+  IceMessage* msg = lport->last_stun_msg();
+  EXPECT_EQ(STUN_BINDING_REQUEST, msg->type());
+  EXPECT_FALSE(msg->IsLegacy());
+  const StunByteStringAttribute* username_attr =
+      msg->GetByteString(STUN_ATTR_USERNAME);
+  ASSERT_TRUE(username_attr != NULL);
+  const StunUInt32Attribute* priority_attr = msg->GetUInt32(STUN_ATTR_PRIORITY);
+  ASSERT_TRUE(priority_attr != NULL);
+  EXPECT_EQ(kDefaultPrflxPriority, priority_attr->value());
+  EXPECT_EQ("rfrag:lfrag", username_attr->GetString());
+  EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL);
+  EXPECT_TRUE(StunMessage::ValidateMessageIntegrity(
+      lport->last_stun_buf()->Data(), lport->last_stun_buf()->Length(),
+      "rpass"));
+  const StunUInt64Attribute* ice_controlling_attr =
+      msg->GetUInt64(STUN_ATTR_ICE_CONTROLLING);
+  ASSERT_TRUE(ice_controlling_attr != NULL);
+  EXPECT_EQ(lport->Tiebreaker(), ice_controlling_attr->value());
+  EXPECT_TRUE(msg->GetByteString(STUN_ATTR_ICE_CONTROLLED) == NULL);
+  EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USE_CANDIDATE) != NULL);
+  EXPECT_TRUE(msg->GetUInt32(STUN_ATTR_FINGERPRINT) != NULL);
+  EXPECT_TRUE(StunMessage::ValidateFingerprint(
+      lport->last_stun_buf()->Data(), lport->last_stun_buf()->Length()));
+
+  // Request should not include ping count.
+  ASSERT_TRUE(msg->GetUInt32(STUN_ATTR_RETRANSMIT_COUNT) == NULL);
+
+  // Save a copy of the BINDING-REQUEST for use below.
+  talk_base::scoped_ptr<IceMessage> request(CopyStunMessage(msg));
+
+  // Respond with a BINDING-RESPONSE.
+  rport->SendBindingResponse(request.get(), lport->Candidates()[0].address());
+  msg = rport->last_stun_msg();
+  ASSERT_TRUE(msg != NULL);
+  EXPECT_EQ(STUN_BINDING_RESPONSE, msg->type());
+
+
+  EXPECT_FALSE(msg->IsLegacy());
+  const StunAddressAttribute* addr_attr = msg->GetAddress(
+      STUN_ATTR_XOR_MAPPED_ADDRESS);
+  ASSERT_TRUE(addr_attr != NULL);
+  EXPECT_EQ(lport->Candidates()[0].address(), addr_attr->GetAddress());
+  EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL);
+  EXPECT_TRUE(StunMessage::ValidateMessageIntegrity(
+      rport->last_stun_buf()->Data(), rport->last_stun_buf()->Length(),
+      "rpass"));
+  EXPECT_TRUE(msg->GetUInt32(STUN_ATTR_FINGERPRINT) != NULL);
+  EXPECT_TRUE(StunMessage::ValidateFingerprint(
+      lport->last_stun_buf()->Data(), lport->last_stun_buf()->Length()));
+  // No USERNAME or PRIORITY in ICE responses.
+  EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USERNAME) == NULL);
+  EXPECT_TRUE(msg->GetByteString(STUN_ATTR_PRIORITY) == NULL);
+  EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MAPPED_ADDRESS) == NULL);
+  EXPECT_TRUE(msg->GetByteString(STUN_ATTR_ICE_CONTROLLING) == NULL);
+  EXPECT_TRUE(msg->GetByteString(STUN_ATTR_ICE_CONTROLLED) == NULL);
+  EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USE_CANDIDATE) == NULL);
+
+  // Response should not include ping count.
+  ASSERT_TRUE(msg->GetUInt32(STUN_ATTR_RETRANSMIT_COUNT) == NULL);
+
+  // Respond with a BINDING-ERROR-RESPONSE. This wouldn't happen in real life,
+  // but we can do it here.
+  rport->SendBindingErrorResponse(request.get(),
+                                  lport->Candidates()[0].address(),
+                                  STUN_ERROR_SERVER_ERROR,
+                                  STUN_ERROR_REASON_SERVER_ERROR);
+  msg = rport->last_stun_msg();
+  ASSERT_TRUE(msg != NULL);
+  EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, msg->type());
+  EXPECT_FALSE(msg->IsLegacy());
+  const StunErrorCodeAttribute* error_attr = msg->GetErrorCode();
+  ASSERT_TRUE(error_attr != NULL);
+  EXPECT_EQ(STUN_ERROR_SERVER_ERROR, error_attr->code());
+  EXPECT_EQ(std::string(STUN_ERROR_REASON_SERVER_ERROR), error_attr->reason());
+  EXPECT_TRUE(msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL);
+  EXPECT_TRUE(StunMessage::ValidateMessageIntegrity(
+      rport->last_stun_buf()->Data(), rport->last_stun_buf()->Length(),
+      "rpass"));
+  EXPECT_TRUE(msg->GetUInt32(STUN_ATTR_FINGERPRINT) != NULL);
+  EXPECT_TRUE(StunMessage::ValidateFingerprint(
+      lport->last_stun_buf()->Data(), lport->last_stun_buf()->Length()));
+  // No USERNAME with ICE.
+  EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USERNAME) == NULL);
+  EXPECT_TRUE(msg->GetByteString(STUN_ATTR_PRIORITY) == NULL);
+
+  // Testing STUN binding requests from rport --> lport, having ICE_CONTROLLED
+  // and (incremented) RETRANSMIT_COUNT attributes.
+  rport->Reset();
+  rport->set_send_retransmit_count_attribute(true);
+  rconn->Ping(0);
+  rconn->Ping(0);
+  rconn->Ping(0);
+  ASSERT_TRUE_WAIT(rport->last_stun_msg() != NULL, 1000);
+  msg = rport->last_stun_msg();
+  EXPECT_EQ(STUN_BINDING_REQUEST, msg->type());
+  const StunUInt64Attribute* ice_controlled_attr =
+      msg->GetUInt64(STUN_ATTR_ICE_CONTROLLED);
+  ASSERT_TRUE(ice_controlled_attr != NULL);
+  EXPECT_EQ(rport->Tiebreaker(), ice_controlled_attr->value());
+  EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USE_CANDIDATE) == NULL);
+
+  // Request should include ping count.
+  const StunUInt32Attribute* retransmit_attr =
+      msg->GetUInt32(STUN_ATTR_RETRANSMIT_COUNT);
+  ASSERT_TRUE(retransmit_attr != NULL);
+  EXPECT_EQ(2U, retransmit_attr->value());
+
+  // Respond with a BINDING-RESPONSE.
+  request.reset(CopyStunMessage(msg));
+  lport->SendBindingResponse(request.get(), rport->Candidates()[0].address());
+  msg = lport->last_stun_msg();
+
+  // Response should include same ping count.
+  retransmit_attr = msg->GetUInt32(STUN_ATTR_RETRANSMIT_COUNT);
+  ASSERT_TRUE(retransmit_attr != NULL);
+  EXPECT_EQ(2U, retransmit_attr->value());
+}
+
+TEST_F(PortTest, TestUseCandidateAttribute) {
+  talk_base::scoped_ptr<TestPort> lport(
+      CreateTestPort(kLocalAddr1, "lfrag", "lpass"));
+  talk_base::scoped_ptr<TestPort> rport(
+      CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+  lport->SetIceProtocolType(ICEPROTO_RFC5245);
+  lport->SetRole(cricket::ROLE_CONTROLLING);
+  lport->SetTiebreaker(kTiebreaker1);
+  rport->SetIceProtocolType(ICEPROTO_RFC5245);
+  rport->SetRole(cricket::ROLE_CONTROLLED);
+  rport->SetTiebreaker(kTiebreaker2);
+
+  // Send a fake ping from lport to rport.
+  lport->PrepareAddress();
+  rport->PrepareAddress();
+  ASSERT_FALSE(rport->Candidates().empty());
+  Connection* lconn = lport->CreateConnection(
+      rport->Candidates()[0], Port::ORIGIN_MESSAGE);
+  lconn->Ping(0);
+  ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000);
+  IceMessage* msg = lport->last_stun_msg();
+  const StunUInt64Attribute* ice_controlling_attr =
+      msg->GetUInt64(STUN_ATTR_ICE_CONTROLLING);
+  ASSERT_TRUE(ice_controlling_attr != NULL);
+  const StunByteStringAttribute* use_candidate_attr = msg->GetByteString(
+      STUN_ATTR_USE_CANDIDATE);
+  ASSERT_TRUE(use_candidate_attr != NULL);
+}
+
+// Test handling STUN messages in GICE format.
+TEST_F(PortTest, TestHandleStunMessageAsGice) {
+  // Our port will act as the "remote" port.
+  talk_base::scoped_ptr<TestPort> port(
+      CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+  port->SetIceProtocolType(ICEPROTO_GOOGLE);
+
+  talk_base::scoped_ptr<IceMessage> in_msg, out_msg;
+  talk_base::scoped_ptr<ByteBuffer> buf(new ByteBuffer());
+  talk_base::SocketAddress addr(kLocalAddr1);
+  std::string username;
+
+  // BINDING-REQUEST from local to remote with valid GICE username and no M-I.
+  in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST,
+                                             "rfraglfrag"));
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                   out_msg.accept(), &username));
+  EXPECT_TRUE(out_msg.get() != NULL);  // Succeeds, since this is GICE.
+  EXPECT_EQ("lfrag", username);
+
+  // Add M-I; should be ignored and rest of message parsed normally.
+  in_msg->AddMessageIntegrity("password");
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                   out_msg.accept(), &username));
+  EXPECT_TRUE(out_msg.get() != NULL);
+  EXPECT_EQ("lfrag", username);
+
+  // BINDING-RESPONSE with username, as done in GICE. Should succeed.
+  in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_RESPONSE,
+                                             "rfraglfrag"));
+  in_msg->AddAttribute(
+      new StunAddressAttribute(STUN_ATTR_MAPPED_ADDRESS, kLocalAddr2));
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                   out_msg.accept(), &username));
+  EXPECT_TRUE(out_msg.get() != NULL);
+  EXPECT_EQ("", username);
+
+  // BINDING-RESPONSE without username. Should be tolerated as well.
+  in_msg.reset(CreateStunMessage(STUN_BINDING_RESPONSE));
+  in_msg->AddAttribute(
+      new StunAddressAttribute(STUN_ATTR_MAPPED_ADDRESS, kLocalAddr2));
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                   out_msg.accept(), &username));
+  EXPECT_TRUE(out_msg.get() != NULL);
+  EXPECT_EQ("", username);
+
+  // BINDING-ERROR-RESPONSE with username and error code.
+  in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_ERROR_RESPONSE,
+                                             "rfraglfrag"));
+  in_msg->AddAttribute(new StunErrorCodeAttribute(STUN_ATTR_ERROR_CODE,
+      STUN_ERROR_SERVER_ERROR_AS_GICE, STUN_ERROR_REASON_SERVER_ERROR));
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                   out_msg.accept(), &username));
+  ASSERT_TRUE(out_msg.get() != NULL);
+  EXPECT_EQ("", username);
+  ASSERT_TRUE(out_msg->GetErrorCode() != NULL);
+  // GetStunMessage doesn't unmunge the GICE error code (happens downstream).
+  EXPECT_EQ(STUN_ERROR_SERVER_ERROR_AS_GICE, out_msg->GetErrorCode()->code());
+  EXPECT_EQ(std::string(STUN_ERROR_REASON_SERVER_ERROR),
+      out_msg->GetErrorCode()->reason());
+}
+
+// Test handling STUN messages in ICE format.
+TEST_F(PortTest, TestHandleStunMessageAsIce) {
+  // Our port will act as the "remote" port.
+  talk_base::scoped_ptr<TestPort> port(
+      CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+  port->SetIceProtocolType(ICEPROTO_RFC5245);
+
+  talk_base::scoped_ptr<IceMessage> in_msg, out_msg;
+  talk_base::scoped_ptr<ByteBuffer> buf(new ByteBuffer());
+  talk_base::SocketAddress addr(kLocalAddr1);
+  std::string username;
+
+  // BINDING-REQUEST from local to remote with valid ICE username,
+  // MESSAGE-INTEGRITY, and FINGERPRINT.
+  in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST,
+                                             "rfrag:lfrag"));
+  in_msg->AddMessageIntegrity("rpass");
+  in_msg->AddFingerprint();
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                   out_msg.accept(), &username));
+  EXPECT_TRUE(out_msg.get() != NULL);
+  EXPECT_EQ("lfrag", username);
+
+  // BINDING-RESPONSE without username, with MESSAGE-INTEGRITY and FINGERPRINT.
+  in_msg.reset(CreateStunMessage(STUN_BINDING_RESPONSE));
+  in_msg->AddAttribute(
+      new StunXorAddressAttribute(STUN_ATTR_XOR_MAPPED_ADDRESS, kLocalAddr2));
+  in_msg->AddMessageIntegrity("rpass");
+  in_msg->AddFingerprint();
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                   out_msg.accept(), &username));
+  EXPECT_TRUE(out_msg.get() != NULL);
+  EXPECT_EQ("", username);
+
+  // BINDING-ERROR-RESPONSE without username, with error, M-I, and FINGERPRINT.
+  in_msg.reset(CreateStunMessage(STUN_BINDING_ERROR_RESPONSE));
+  in_msg->AddAttribute(new StunErrorCodeAttribute(STUN_ATTR_ERROR_CODE,
+      STUN_ERROR_SERVER_ERROR, STUN_ERROR_REASON_SERVER_ERROR));
+  in_msg->AddFingerprint();
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                   out_msg.accept(), &username));
+  EXPECT_TRUE(out_msg.get() != NULL);
+  EXPECT_EQ("", username);
+  ASSERT_TRUE(out_msg->GetErrorCode() != NULL);
+  EXPECT_EQ(STUN_ERROR_SERVER_ERROR, out_msg->GetErrorCode()->code());
+  EXPECT_EQ(std::string(STUN_ERROR_REASON_SERVER_ERROR),
+      out_msg->GetErrorCode()->reason());
+}
+
+// Tests handling of GICE binding requests with missing or incorrect usernames.
+TEST_F(PortTest, TestHandleStunMessageAsGiceBadUsername) {
+  talk_base::scoped_ptr<TestPort> port(
+      CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+  port->SetIceProtocolType(ICEPROTO_GOOGLE);
+
+  talk_base::scoped_ptr<IceMessage> in_msg, out_msg;
+  talk_base::scoped_ptr<ByteBuffer> buf(new ByteBuffer());
+  talk_base::SocketAddress addr(kLocalAddr1);
+  std::string username;
+
+  // BINDING-REQUEST with no username.
+  in_msg.reset(CreateStunMessage(STUN_BINDING_REQUEST));
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                   out_msg.accept(), &username));
+  EXPECT_TRUE(out_msg.get() == NULL);
+  EXPECT_EQ("", username);
+  EXPECT_EQ(STUN_ERROR_BAD_REQUEST_AS_GICE, port->last_stun_error_code());
+
+  // BINDING-REQUEST with empty username.
+  in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, ""));
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                   out_msg.accept(), &username));
+  EXPECT_TRUE(out_msg.get() == NULL);
+  EXPECT_EQ("", username);
+  EXPECT_EQ(STUN_ERROR_UNAUTHORIZED_AS_GICE, port->last_stun_error_code());
+
+  // BINDING-REQUEST with too-short username.
+  in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "lfra"));
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                   out_msg.accept(), &username));
+  EXPECT_TRUE(out_msg.get() == NULL);
+  EXPECT_EQ("", username);
+  EXPECT_EQ(STUN_ERROR_UNAUTHORIZED_AS_GICE, port->last_stun_error_code());
+
+  // BINDING-REQUEST with reversed username.
+  in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST,
+                                             "lfragrfrag"));
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                   out_msg.accept(), &username));
+  EXPECT_TRUE(out_msg.get() == NULL);
+  EXPECT_EQ("", username);
+  EXPECT_EQ(STUN_ERROR_UNAUTHORIZED_AS_GICE, port->last_stun_error_code());
+
+  // BINDING-REQUEST with garbage username.
+  in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST,
+                                             "abcdefgh"));
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                   out_msg.accept(), &username));
+  EXPECT_TRUE(out_msg.get() == NULL);
+  EXPECT_EQ("", username);
+  EXPECT_EQ(STUN_ERROR_UNAUTHORIZED_AS_GICE, port->last_stun_error_code());
+}
+
+// Tests handling of ICE binding requests with missing or incorrect usernames.
+TEST_F(PortTest, TestHandleStunMessageAsIceBadUsername) {
+  talk_base::scoped_ptr<TestPort> port(
+      CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+  port->SetIceProtocolType(ICEPROTO_RFC5245);
+
+  talk_base::scoped_ptr<IceMessage> in_msg, out_msg;
+  talk_base::scoped_ptr<ByteBuffer> buf(new ByteBuffer());
+  talk_base::SocketAddress addr(kLocalAddr1);
+  std::string username;
+
+  // BINDING-REQUEST with no username.
+  in_msg.reset(CreateStunMessage(STUN_BINDING_REQUEST));
+  in_msg->AddMessageIntegrity("rpass");
+  in_msg->AddFingerprint();
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                   out_msg.accept(), &username));
+  EXPECT_TRUE(out_msg.get() == NULL);
+  EXPECT_EQ("", username);
+  EXPECT_EQ(STUN_ERROR_BAD_REQUEST, port->last_stun_error_code());
+
+  // BINDING-REQUEST with empty username.
+  in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, ""));
+  in_msg->AddMessageIntegrity("rpass");
+  in_msg->AddFingerprint();
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                   out_msg.accept(), &username));
+  EXPECT_TRUE(out_msg.get() == NULL);
+  EXPECT_EQ("", username);
+  EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code());
+
+  // BINDING-REQUEST with too-short username.
+  in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST, "rfra"));
+  in_msg->AddMessageIntegrity("rpass");
+  in_msg->AddFingerprint();
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                   out_msg.accept(), &username));
+  EXPECT_TRUE(out_msg.get() == NULL);
+  EXPECT_EQ("", username);
+  EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code());
+
+  // BINDING-REQUEST with reversed username.
+  in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST,
+                                            "lfrag:rfrag"));
+  in_msg->AddMessageIntegrity("rpass");
+  in_msg->AddFingerprint();
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                   out_msg.accept(), &username));
+  EXPECT_TRUE(out_msg.get() == NULL);
+  EXPECT_EQ("", username);
+  EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code());
+
+  // BINDING-REQUEST with garbage username.
+  in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST,
+                                             "abcd:efgh"));
+  in_msg->AddMessageIntegrity("rpass");
+  in_msg->AddFingerprint();
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                   out_msg.accept(), &username));
+  EXPECT_TRUE(out_msg.get() == NULL);
+  EXPECT_EQ("", username);
+  EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code());
+}
+
+// Test handling STUN messages (as ICE) with missing or malformed M-I.
+TEST_F(PortTest, TestHandleStunMessageAsIceBadMessageIntegrity) {
+  // Our port will act as the "remote" port.
+  talk_base::scoped_ptr<TestPort> port(
+      CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+  port->SetIceProtocolType(ICEPROTO_RFC5245);
+
+  talk_base::scoped_ptr<IceMessage> in_msg, out_msg;
+  talk_base::scoped_ptr<ByteBuffer> buf(new ByteBuffer());
+  talk_base::SocketAddress addr(kLocalAddr1);
+  std::string username;
+
+  // BINDING-REQUEST from local to remote with valid ICE username and
+  // FINGERPRINT, but no MESSAGE-INTEGRITY.
+  in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST,
+                                             "rfrag:lfrag"));
+  in_msg->AddFingerprint();
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                   out_msg.accept(), &username));
+  EXPECT_TRUE(out_msg.get() == NULL);
+  EXPECT_EQ("", username);
+  EXPECT_EQ(STUN_ERROR_BAD_REQUEST, port->last_stun_error_code());
+
+  // BINDING-REQUEST from local to remote with valid ICE username and
+  // FINGERPRINT, but invalid MESSAGE-INTEGRITY.
+  in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST,
+                                             "rfrag:lfrag"));
+  in_msg->AddMessageIntegrity("invalid");
+  in_msg->AddFingerprint();
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_TRUE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                   out_msg.accept(), &username));
+  EXPECT_TRUE(out_msg.get() == NULL);
+  EXPECT_EQ("", username);
+  EXPECT_EQ(STUN_ERROR_UNAUTHORIZED, port->last_stun_error_code());
+
+  // TODO: BINDING-RESPONSES and BINDING-ERROR-RESPONSES are checked
+  // by the Connection, not the Port, since they require the remote username.
+  // Change this test to pass in data via Connection::OnReadPacket instead.
+}
+
+// Test handling STUN messages (as ICE) with missing or malformed FINGERPRINT.
+TEST_F(PortTest, TestHandleStunMessageAsIceBadFingerprint) {
+  // Our port will act as the "remote" port.
+  talk_base::scoped_ptr<TestPort> port(
+      CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+  port->SetIceProtocolType(ICEPROTO_RFC5245);
+
+  talk_base::scoped_ptr<IceMessage> in_msg, out_msg;
+  talk_base::scoped_ptr<ByteBuffer> buf(new ByteBuffer());
+  talk_base::SocketAddress addr(kLocalAddr1);
+  std::string username;
+
+  // BINDING-REQUEST from local to remote with valid ICE username and
+  // MESSAGE-INTEGRITY, but no FINGERPRINT; GetStunMessage should fail.
+  in_msg.reset(CreateStunMessageWithUsername(STUN_BINDING_REQUEST,
+                                             "rfrag:lfrag"));
+  in_msg->AddMessageIntegrity("rpass");
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                    out_msg.accept(), &username));
+  EXPECT_EQ(0, port->last_stun_error_code());
+
+  // Now, add a fingerprint, but munge the message so it's not valid.
+  in_msg->AddFingerprint();
+  in_msg->SetTransactionID("TESTTESTBADD");
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                    out_msg.accept(), &username));
+  EXPECT_EQ(0, port->last_stun_error_code());
+
+  // Valid BINDING-RESPONSE, except no FINGERPRINT.
+  in_msg.reset(CreateStunMessage(STUN_BINDING_RESPONSE));
+  in_msg->AddAttribute(
+      new StunXorAddressAttribute(STUN_ATTR_XOR_MAPPED_ADDRESS, kLocalAddr2));
+  in_msg->AddMessageIntegrity("rpass");
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                    out_msg.accept(), &username));
+  EXPECT_EQ(0, port->last_stun_error_code());
+
+  // Now, add a fingerprint, but munge the message so it's not valid.
+  in_msg->AddFingerprint();
+  in_msg->SetTransactionID("TESTTESTBADD");
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                    out_msg.accept(), &username));
+  EXPECT_EQ(0, port->last_stun_error_code());
+
+  // Valid BINDING-ERROR-RESPONSE, except no FINGERPRINT.
+  in_msg.reset(CreateStunMessage(STUN_BINDING_ERROR_RESPONSE));
+  in_msg->AddAttribute(new StunErrorCodeAttribute(STUN_ATTR_ERROR_CODE,
+      STUN_ERROR_SERVER_ERROR, STUN_ERROR_REASON_SERVER_ERROR));
+  in_msg->AddMessageIntegrity("rpass");
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                    out_msg.accept(), &username));
+  EXPECT_EQ(0, port->last_stun_error_code());
+
+  // Now, add a fingerprint, but munge the message so it's not valid.
+  in_msg->AddFingerprint();
+  in_msg->SetTransactionID("TESTTESTBADD");
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_FALSE(port->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                    out_msg.accept(), &username));
+  EXPECT_EQ(0, port->last_stun_error_code());
+}
+
+// Test handling of STUN binding indication messages (as ICE). STUN binding
+// indications are allowed only to the connection which is in read mode.
+TEST_F(PortTest, TestHandleStunBindingIndication) {
+  talk_base::scoped_ptr<TestPort> lport(
+      CreateTestPort(kLocalAddr2, "lfrag", "lpass"));
+  lport->SetIceProtocolType(ICEPROTO_RFC5245);
+  lport->SetRole(cricket::ROLE_CONTROLLING);
+  lport->SetTiebreaker(kTiebreaker1);
+
+  // Verifying encoding and decoding STUN indication message.
+  talk_base::scoped_ptr<IceMessage> in_msg, out_msg;
+  talk_base::scoped_ptr<ByteBuffer> buf(new ByteBuffer());
+  talk_base::SocketAddress addr(kLocalAddr1);
+  std::string username;
+
+  in_msg.reset(CreateStunMessage(STUN_BINDING_INDICATION));
+  in_msg->AddFingerprint();
+  WriteStunMessage(in_msg.get(), buf.get());
+  EXPECT_TRUE(lport->GetStunMessage(buf->Data(), buf->Length(), addr,
+                                    out_msg.accept(), &username));
+  EXPECT_TRUE(out_msg.get() != NULL);
+  EXPECT_EQ(out_msg->type(), STUN_BINDING_INDICATION);
+  EXPECT_EQ("", username);
+
+  // Verify connection can handle STUN indication and updates
+  // last_ping_received.
+  talk_base::scoped_ptr<TestPort> rport(
+      CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+  rport->SetIceProtocolType(ICEPROTO_RFC5245);
+  rport->SetRole(cricket::ROLE_CONTROLLED);
+  rport->SetTiebreaker(kTiebreaker2);
+
+  lport->PrepareAddress();
+  rport->PrepareAddress();
+  ASSERT_FALSE(lport->Candidates().empty());
+  ASSERT_FALSE(rport->Candidates().empty());
+
+  Connection* lconn = lport->CreateConnection(rport->Candidates()[0],
+                                              Port::ORIGIN_MESSAGE);
+  Connection* rconn = rport->CreateConnection(lport->Candidates()[0],
+                                              Port::ORIGIN_MESSAGE);
+  rconn->Ping(0);
+
+  ASSERT_TRUE_WAIT(rport->last_stun_msg() != NULL, 1000);
+  IceMessage* msg = rport->last_stun_msg();
+  EXPECT_EQ(STUN_BINDING_REQUEST, msg->type());
+  // Send rport binding request to lport.
+  lconn->OnReadPacket(rport->last_stun_buf()->Data(),
+                      rport->last_stun_buf()->Length());
+  ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000);
+  EXPECT_EQ(STUN_BINDING_RESPONSE, lport->last_stun_msg()->type());
+  uint32 last_ping_received1 = lconn->last_ping_received();
+
+  // Adding a delay of 100ms.
+  talk_base::Thread::Current()->ProcessMessages(100);
+  // Pinging lconn using stun indication message.
+  lconn->OnReadPacket(buf->Data(), buf->Length());
+  uint32 last_ping_received2 = lconn->last_ping_received();
+  EXPECT_GT(last_ping_received2, last_ping_received1);
+}
+
+TEST_F(PortTest, TestComputeCandidatePriority) {
+  talk_base::scoped_ptr<TestPort> port(
+      CreateTestPort(kLocalAddr1, "name", "pass"));
+  port->set_type_preference(90);
+  port->set_component(177);
+  port->AddCandidateAddress(SocketAddress("192.168.1.4", 1234));
+  port->AddCandidateAddress(SocketAddress("2001:db8::1234", 1234));
+  port->AddCandidateAddress(SocketAddress("fc12:3456::1234", 1234));
+  port->AddCandidateAddress(SocketAddress("::ffff:192.168.1.4", 1234));
+  port->AddCandidateAddress(SocketAddress("::192.168.1.4", 1234));
+  port->AddCandidateAddress(SocketAddress("2002::1234:5678", 1234));
+  port->AddCandidateAddress(SocketAddress("2001::1234:5678", 1234));
+  port->AddCandidateAddress(SocketAddress("fecf::1234:5678", 1234));
+  port->AddCandidateAddress(SocketAddress("3ffe::1234:5678", 1234));
+  // These should all be:
+  // (90 << 24) | ([rfc3484 pref value] << 8) | (256 - 177)
+  uint32 expected_priority_v4 = 1509957199U;
+  uint32 expected_priority_v6 = 1509959759U;
+  uint32 expected_priority_ula = 1509962319U;
+  uint32 expected_priority_v4mapped = expected_priority_v4;
+  uint32 expected_priority_v4compat = 1509949775U;
+  uint32 expected_priority_6to4 = 1509954639U;
+  uint32 expected_priority_teredo = 1509952079U;
+  uint32 expected_priority_sitelocal = 1509949775U;
+  uint32 expected_priority_6bone = 1509949775U;
+  ASSERT_EQ(expected_priority_v4, port->Candidates()[0].priority());
+  ASSERT_EQ(expected_priority_v6, port->Candidates()[1].priority());
+  ASSERT_EQ(expected_priority_ula, port->Candidates()[2].priority());
+  ASSERT_EQ(expected_priority_v4mapped, port->Candidates()[3].priority());
+  ASSERT_EQ(expected_priority_v4compat, port->Candidates()[4].priority());
+  ASSERT_EQ(expected_priority_6to4, port->Candidates()[5].priority());
+  ASSERT_EQ(expected_priority_teredo, port->Candidates()[6].priority());
+  ASSERT_EQ(expected_priority_sitelocal, port->Candidates()[7].priority());
+  ASSERT_EQ(expected_priority_6bone, port->Candidates()[8].priority());
+}
+
+TEST_F(PortTest, TestPortProxyProperties) {
+  talk_base::scoped_ptr<TestPort> port(
+      CreateTestPort(kLocalAddr1, "name", "pass"));
+  port->SetRole(cricket::ROLE_CONTROLLING);
+  port->SetTiebreaker(kTiebreaker1);
+
+  // Create a proxy port.
+  talk_base::scoped_ptr<PortProxy> proxy(new PortProxy());
+  proxy->set_impl(port.get());
+  EXPECT_EQ(port->Type(), proxy->Type());
+  EXPECT_EQ(port->Network(), proxy->Network());
+  EXPECT_EQ(port->Role(), proxy->Role());
+  EXPECT_EQ(port->Tiebreaker(), proxy->Tiebreaker());
+}
+
+// In the case of shared socket, one port may be shared by local and stun.
+// Test that candidates with different types will have different foundation.
+TEST_F(PortTest, TestFoundation) {
+  talk_base::scoped_ptr<TestPort> testport(
+      CreateTestPort(kLocalAddr1, "name", "pass"));
+  testport->AddCandidateAddress(kLocalAddr1, kLocalAddr1,
+                                LOCAL_PORT_TYPE,
+                                cricket::ICE_TYPE_PREFERENCE_HOST, false);
+  testport->AddCandidateAddress(kLocalAddr2, kLocalAddr1,
+                                STUN_PORT_TYPE,
+                                cricket::ICE_TYPE_PREFERENCE_SRFLX, true);
+  EXPECT_NE(testport->Candidates()[0].foundation(),
+            testport->Candidates()[1].foundation());
+}
+
+// This test verifies the foundation of different types of ICE candidates.
+TEST_F(PortTest, TestCandidateFoundation) {
+  talk_base::scoped_ptr<talk_base::NATServer> nat_server(
+      CreateNatServer(kNatAddr1, NAT_OPEN_CONE));
+  talk_base::scoped_ptr<UDPPort> udpport1(CreateUdpPort(kLocalAddr1));
+  udpport1->PrepareAddress();
+  talk_base::scoped_ptr<UDPPort> udpport2(CreateUdpPort(kLocalAddr1));
+  udpport2->PrepareAddress();
+  EXPECT_EQ(udpport1->Candidates()[0].foundation(),
+            udpport2->Candidates()[0].foundation());
+  talk_base::scoped_ptr<TCPPort> tcpport1(CreateTcpPort(kLocalAddr1));
+  tcpport1->PrepareAddress();
+  talk_base::scoped_ptr<TCPPort> tcpport2(CreateTcpPort(kLocalAddr1));
+  tcpport2->PrepareAddress();
+  EXPECT_EQ(tcpport1->Candidates()[0].foundation(),
+            tcpport2->Candidates()[0].foundation());
+  talk_base::scoped_ptr<Port> stunport(
+      CreateStunPort(kLocalAddr1, nat_socket_factory1()));
+  stunport->PrepareAddress();
+  ASSERT_EQ_WAIT(1U, stunport->Candidates().size(), kTimeout);
+  EXPECT_NE(tcpport1->Candidates()[0].foundation(),
+            stunport->Candidates()[0].foundation());
+  EXPECT_NE(tcpport2->Candidates()[0].foundation(),
+            stunport->Candidates()[0].foundation());
+  EXPECT_NE(udpport1->Candidates()[0].foundation(),
+            stunport->Candidates()[0].foundation());
+  EXPECT_NE(udpport2->Candidates()[0].foundation(),
+            stunport->Candidates()[0].foundation());
+  // Verify GTURN candidate foundation.
+  talk_base::scoped_ptr<RelayPort> relayport(
+      CreateGturnPort(kLocalAddr1));
+  relayport->AddServerAddress(
+      cricket::ProtocolAddress(kRelayUdpIntAddr, cricket::PROTO_UDP));
+  relayport->PrepareAddress();
+  ASSERT_EQ_WAIT(1U, relayport->Candidates().size(), kTimeout);
+  EXPECT_NE(udpport1->Candidates()[0].foundation(),
+            relayport->Candidates()[0].foundation());
+  EXPECT_NE(udpport2->Candidates()[0].foundation(),
+            relayport->Candidates()[0].foundation());
+  // Verifying TURN candidate foundation.
+  talk_base::scoped_ptr<Port> turnport(CreateTurnPort(
+      kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP));
+  turnport->PrepareAddress();
+  ASSERT_EQ_WAIT(1U, turnport->Candidates().size(), kTimeout);
+  EXPECT_NE(udpport1->Candidates()[0].foundation(),
+            turnport->Candidates()[0].foundation());
+  EXPECT_NE(udpport2->Candidates()[0].foundation(),
+            turnport->Candidates()[0].foundation());
+  EXPECT_NE(stunport->Candidates()[0].foundation(),
+            turnport->Candidates()[0].foundation());
+}
+
+// This test verifies the related addresses of different types of
+// ICE candiates.
+TEST_F(PortTest, TestCandidateRelatedAddress) {
+  talk_base::scoped_ptr<talk_base::NATServer> nat_server(
+      CreateNatServer(kNatAddr1, NAT_OPEN_CONE));
+  talk_base::scoped_ptr<UDPPort> udpport(CreateUdpPort(kLocalAddr1));
+  udpport->PrepareAddress();
+  // For UDPPort, related address will be empty.
+  EXPECT_TRUE(udpport->Candidates()[0].related_address().IsNil());
+  // Testing related address for stun candidates.
+  // For stun candidate related address must be equal to the base
+  // socket address.
+  talk_base::scoped_ptr<StunPort> stunport(
+      CreateStunPort(kLocalAddr1, nat_socket_factory1()));
+  stunport->PrepareAddress();
+  ASSERT_EQ_WAIT(1U, stunport->Candidates().size(), kTimeout);
+  // Check STUN candidate address.
+  EXPECT_EQ(stunport->Candidates()[0].address().ipaddr(),
+            kNatAddr1.ipaddr());
+  // Check STUN candidate related address.
+  EXPECT_EQ(stunport->Candidates()[0].related_address(),
+            stunport->GetLocalAddress());
+  // Verifying the related address for the GTURN candidates.
+  // NOTE: In case of GTURN related address will be equal to the mapped
+  // address, but address(mapped) will not be XOR.
+  talk_base::scoped_ptr<RelayPort> relayport(
+      CreateGturnPort(kLocalAddr1));
+  relayport->AddServerAddress(
+      cricket::ProtocolAddress(kRelayUdpIntAddr, cricket::PROTO_UDP));
+  relayport->PrepareAddress();
+  ASSERT_EQ_WAIT(1U, relayport->Candidates().size(), kTimeout);
+  // For Gturn related address is set to "0.0.0.0:0"
+  EXPECT_EQ(talk_base::SocketAddress(),
+            relayport->Candidates()[0].related_address());
+  // Verifying the related address for TURN candidate.
+  // For TURN related address must be equal to the mapped address.
+  talk_base::scoped_ptr<Port> turnport(CreateTurnPort(
+      kLocalAddr1, nat_socket_factory1(), PROTO_UDP, PROTO_UDP));
+  turnport->PrepareAddress();
+  ASSERT_EQ_WAIT(1U, turnport->Candidates().size(), kTimeout);
+  EXPECT_EQ(kTurnUdpExtAddr.ipaddr(),
+            turnport->Candidates()[0].address().ipaddr());
+  EXPECT_EQ(kNatAddr1.ipaddr(),
+            turnport->Candidates()[0].related_address().ipaddr());
+}
+
+// Test priority value overflow handling when preference is set to 3.
+TEST_F(PortTest, TestCandidatePreference) {
+  cricket::Candidate cand1;
+  cand1.set_preference(3);
+  cricket::Candidate cand2;
+  cand2.set_preference(1);
+  EXPECT_TRUE(cand1.preference() > cand2.preference());
+}
+
+// Test the Connection priority is calculated correctly.
+TEST_F(PortTest, TestConnectionPriority) {
+  talk_base::scoped_ptr<TestPort> lport(
+      CreateTestPort(kLocalAddr1, "lfrag", "lpass"));
+  lport->set_type_preference(cricket::ICE_TYPE_PREFERENCE_HOST);
+  talk_base::scoped_ptr<TestPort> rport(
+      CreateTestPort(kLocalAddr2, "rfrag", "rpass"));
+  rport->set_type_preference(cricket::ICE_TYPE_PREFERENCE_RELAY);
+  lport->set_component(123);
+  lport->AddCandidateAddress(SocketAddress("192.168.1.4", 1234));
+  rport->set_component(23);
+  rport->AddCandidateAddress(SocketAddress("10.1.1.100", 1234));
+
+  EXPECT_EQ(0x7E001E85U, lport->Candidates()[0].priority());
+  EXPECT_EQ(0x2001EE9U, rport->Candidates()[0].priority());
+
+  // RFC 5245
+  // pair priority = 2^32*MIN(G,D) + 2*MAX(G,D) + (G>D?1:0)
+  lport->SetRole(cricket::ROLE_CONTROLLING);
+  rport->SetRole(cricket::ROLE_CONTROLLED);
+  Connection* lconn = lport->CreateConnection(
+      rport->Candidates()[0], Port::ORIGIN_MESSAGE);
+#if defined(WIN32)
+  EXPECT_EQ(0x2001EE9FC003D0BU, lconn->priority());
+#else
+  EXPECT_EQ(0x2001EE9FC003D0BLLU, lconn->priority());
+#endif
+
+  lport->SetRole(cricket::ROLE_CONTROLLED);
+  rport->SetRole(cricket::ROLE_CONTROLLING);
+  Connection* rconn = rport->CreateConnection(
+      lport->Candidates()[0], Port::ORIGIN_MESSAGE);
+#if defined(WIN32)
+  EXPECT_EQ(0x2001EE9FC003D0AU, rconn->priority());
+#else
+  EXPECT_EQ(0x2001EE9FC003D0ALLU, rconn->priority());
+#endif
+}
+
+TEST_F(PortTest, TestWritableState) {
+  UDPPort* port1 = CreateUdpPort(kLocalAddr1);
+  UDPPort* port2 = CreateUdpPort(kLocalAddr2);
+
+  // Set up channels.
+  TestChannel ch1(port1, port2);
+  TestChannel ch2(port2, port1);
+
+  // Acquire addresses.
+  ch1.Start();
+  ch2.Start();
+  ASSERT_EQ_WAIT(1, ch1.complete_count(), kTimeout);
+  ASSERT_EQ_WAIT(1, ch2.complete_count(), kTimeout);
+
+  // Send a ping from src to dst.
+  ch1.CreateConnection();
+  ASSERT_TRUE(ch1.conn() != NULL);
+  EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state());
+  EXPECT_TRUE_WAIT(ch1.conn()->connected(), kTimeout);  // for TCP connect
+  ch1.Ping();
+  WAIT(!ch2.remote_address().IsNil(), kTimeout);
+
+  // Data should be unsendable until the connection is accepted.
+  char data[] = "abcd";
+  int data_size = ARRAY_SIZE(data);
+  EXPECT_EQ(SOCKET_ERROR, ch1.conn()->Send(data, data_size));
+
+  // Accept the connection to return the binding response, transition to
+  // writable, and allow data to be sent.
+  ch2.AcceptConnection();
+  EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch1.conn()->write_state(),
+                 kTimeout);
+  EXPECT_EQ(data_size, ch1.conn()->Send(data, data_size));
+
+  // Ask the connection to update state as if enough time has passed to lose
+  // full writability and 5 pings went unresponded to. We'll accomplish the
+  // latter by sending pings but not pumping messages.
+  for (uint32 i = 1; i <= CONNECTION_WRITE_CONNECT_FAILURES; ++i) {
+    ch1.Ping(i);
+  }
+  uint32 unreliable_timeout_delay = CONNECTION_WRITE_CONNECT_TIMEOUT + 500u;
+  ch1.conn()->UpdateState(unreliable_timeout_delay);
+  EXPECT_EQ(Connection::STATE_WRITE_UNRELIABLE, ch1.conn()->write_state());
+
+  // Data should be able to be sent in this state.
+  EXPECT_EQ(data_size, ch1.conn()->Send(data, data_size));
+
+  // And now allow the other side to process the pings and send binding
+  // responses.
+  EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch1.conn()->write_state(),
+                 kTimeout);
+
+  // Wait long enough for a full timeout (past however long we've already
+  // waited).
+  for (uint32 i = 1; i <= CONNECTION_WRITE_CONNECT_FAILURES; ++i) {
+    ch1.Ping(unreliable_timeout_delay + i);
+  }
+  ch1.conn()->UpdateState(unreliable_timeout_delay + CONNECTION_WRITE_TIMEOUT +
+                          500u);
+  EXPECT_EQ(Connection::STATE_WRITE_TIMEOUT, ch1.conn()->write_state());
+
+  // Now that the connection has completely timed out, data send should fail.
+  EXPECT_EQ(SOCKET_ERROR, ch1.conn()->Send(data, data_size));
+
+  ch1.Stop();
+  ch2.Stop();
+}
+
+TEST_F(PortTest, TestTimeoutForNeverWritable) {
+  UDPPort* port1 = CreateUdpPort(kLocalAddr1);
+  UDPPort* port2 = CreateUdpPort(kLocalAddr2);
+
+  // Set up channels.
+  TestChannel ch1(port1, port2);
+  TestChannel ch2(port2, port1);
+
+  // Acquire addresses.
+  ch1.Start();
+  ch2.Start();
+
+  ch1.CreateConnection();
+  ASSERT_TRUE(ch1.conn() != NULL);
+  EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state());
+
+  // Attempt to go directly to write timeout.
+  for (uint32 i = 1; i <= CONNECTION_WRITE_CONNECT_FAILURES; ++i) {
+    ch1.Ping(i);
+  }
+  ch1.conn()->UpdateState(CONNECTION_WRITE_TIMEOUT + 500u);
+  EXPECT_EQ(Connection::STATE_WRITE_TIMEOUT, ch1.conn()->write_state());
+}
+
+// This test verifies the connection setup between ICEMODE_FULL
+// and ICEMODE_LITE.
+// In this test |ch1| behaves like FULL mode client and we have created
+// port which responds to the ping message just like LITE client.
+TEST_F(PortTest, TestIceLiteConnectivity) {
+  TestPort* ice_full_port = CreateTestPort(
+      kLocalAddr1, "lfrag", "lpass", cricket::ICEPROTO_RFC5245,
+      cricket::ROLE_CONTROLLING, kTiebreaker1);
+
+  talk_base::scoped_ptr<TestPort> ice_lite_port(CreateTestPort(
+      kLocalAddr2, "rfrag", "rpass", cricket::ICEPROTO_RFC5245,
+      cricket::ROLE_CONTROLLED, kTiebreaker2));
+  // Setup TestChannel. This behaves like FULL mode client.
+  TestChannel ch1(ice_full_port, ice_lite_port.get());
+  ch1.SetIceMode(ICEMODE_FULL);
+
+  // Start gathering candidates.
+  ch1.Start();
+  ice_lite_port->PrepareAddress();
+
+  ASSERT_EQ_WAIT(1, ch1.complete_count(), kTimeout);
+  ASSERT_FALSE(ice_lite_port->Candidates().empty());
+
+  ch1.CreateConnection();
+  ASSERT_TRUE(ch1.conn() != NULL);
+  EXPECT_EQ(Connection::STATE_WRITE_INIT, ch1.conn()->write_state());
+
+  // Send ping from full mode client.
+  // This ping must not have USE_CANDIDATE_ATTR.
+  ch1.Ping();
+
+  // Verify stun ping is without USE_CANDIDATE_ATTR. Getting message directly
+  // from port.
+  ASSERT_TRUE_WAIT(ice_full_port->last_stun_msg() != NULL, 1000);
+  IceMessage* msg = ice_full_port->last_stun_msg();
+  EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USE_CANDIDATE) == NULL);
+
+  // Respond with a BINDING-RESPONSE from litemode client.
+  // NOTE: Ideally we should't create connection at this stage from lite
+  // port, as it should be done only after receiving ping with USE_CANDIDATE.
+  // But we need a connection to send a response message.
+  ice_lite_port->CreateConnection(
+      ice_full_port->Candidates()[0], cricket::Port::ORIGIN_MESSAGE);
+  talk_base::scoped_ptr<IceMessage> request(CopyStunMessage(msg));
+  ice_lite_port->SendBindingResponse(
+      request.get(), ice_full_port->Candidates()[0].address());
+
+  // Feeding the respone message from litemode to the full mode connection.
+  ch1.conn()->OnReadPacket(ice_lite_port->last_stun_buf()->Data(),
+                           ice_lite_port->last_stun_buf()->Length());
+  // Verifying full mode connection becomes writable from the response.
+  EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch1.conn()->write_state(),
+                 kTimeout);
+  EXPECT_TRUE_WAIT(ch1.nominated(), kTimeout);
+
+  // Clear existing stun messsages. Otherwise we will process old stun
+  // message right after we send ping.
+  ice_full_port->Reset();
+  // Send ping. This must have USE_CANDIDATE_ATTR.
+  ch1.Ping();
+  ASSERT_TRUE_WAIT(ice_full_port->last_stun_msg() != NULL, 1000);
+  msg = ice_full_port->last_stun_msg();
+  EXPECT_TRUE(msg->GetByteString(STUN_ATTR_USE_CANDIDATE) != NULL);
+  ch1.Stop();
+}
+
diff --git a/talk/p2p/base/portallocator.cc b/talk/p2p/base/portallocator.cc
new file mode 100644
index 0000000..00ba4d8
--- /dev/null
+++ b/talk/p2p/base/portallocator.cc
@@ -0,0 +1,108 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 {
+
+PortAllocatorSession::PortAllocatorSession(const std::string& content_name,
+                                           int component,
+                                           const std::string& ice_ufrag,
+                                           const std::string& ice_pwd,
+                                           uint32 flags)
+    : content_name_(content_name),
+      component_(component),
+      flags_(flags),
+      // If PORTALLOCATOR_ENABLE_SHARED_UFRAG flag is not enabled, ignore the
+      // incoming ufrag and pwd, which will cause each Port to generate one
+      // by itself.
+      username_(flags_ & PORTALLOCATOR_ENABLE_SHARED_UFRAG ? ice_ufrag : ""),
+      password_(flags_ & PORTALLOCATOR_ENABLE_SHARED_UFRAG ? ice_pwd : "") {
+}
+
+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& content_name,
+    int component,
+    const std::string& ice_ufrag,
+    const std::string& ice_pwd) {
+  if (flags_ & PORTALLOCATOR_ENABLE_BUNDLE) {
+    // If we just use |sid| as key in identifying PortAllocatorSessionMuxer,
+    // ICE restart will not result in different candidates, as |sid| will
+    // be same. To yield different candiates we are using combination of
+    // |ice_ufrag| and |ice_pwd|.
+    // Ideally |ice_ufrag| and |ice_pwd| should change together, but
+    // there can be instances where only ice_pwd will be changed.
+    std::string key_str = ice_ufrag + ":" + ice_pwd;
+    PortAllocatorSessionMuxer* muxer = GetSessionMuxer(key_str);
+    if (!muxer) {
+      PortAllocatorSession* session_impl = CreateSessionInternal(
+          content_name, component, ice_ufrag, ice_pwd);
+      // Create PortAllocatorSessionMuxer object for |session_impl|.
+      muxer = new PortAllocatorSessionMuxer(session_impl);
+      muxer->SignalDestroyed.connect(
+          this, &PortAllocator::OnSessionMuxerDestroyed);
+      // Add PortAllocatorSession to the map.
+      muxers_[key_str] = muxer;
+    }
+    PortAllocatorSessionProxy* proxy =
+        new PortAllocatorSessionProxy(content_name, component, flags_);
+    muxer->RegisterSessionProxy(proxy);
+    return proxy;
+  }
+  return CreateSessionInternal(content_name, component, ice_ufrag, ice_pwd);
+}
+
+PortAllocatorSessionMuxer* PortAllocator::GetSessionMuxer(
+    const std::string& key) const {
+  SessionMuxerMap::const_iterator iter = muxers_.find(key);
+  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
new file mode 100644
index 0000000..7568d45
--- /dev/null
+++ b/talk/p2p/base/portallocator.h
@@ -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.
+ */
+
+#ifndef TALK_P2P_BASE_PORTALLOCATOR_H_
+#define TALK_P2P_BASE_PORTALLOCATOR_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/helpers.h"
+#include "talk/base/proxyinfo.h"
+#include "talk/base/sigslot.h"
+#include "talk/p2p/base/portinterface.h"
+
+namespace cricket {
+
+// PortAllocator is responsible for allocating Port types for a given
+// P2PSocket. It also handles port freeing.
+//
+// Clients can override this class to control port allocation, including
+// what kinds of ports are allocated.
+
+const uint32 PORTALLOCATOR_DISABLE_UDP = 0x01;
+const uint32 PORTALLOCATOR_DISABLE_STUN = 0x02;
+const uint32 PORTALLOCATOR_DISABLE_RELAY = 0x04;
+const uint32 PORTALLOCATOR_DISABLE_TCP = 0x08;
+const uint32 PORTALLOCATOR_ENABLE_SHAKER = 0x10;
+const uint32 PORTALLOCATOR_ENABLE_BUNDLE = 0x20;
+const uint32 PORTALLOCATOR_ENABLE_IPV6 = 0x40;
+const uint32 PORTALLOCATOR_ENABLE_SHARED_UFRAG = 0x80;
+const uint32 PORTALLOCATOR_ENABLE_SHARED_SOCKET = 0x100;
+const uint32 PORTALLOCATOR_ENABLE_STUN_RETRANSMIT_ATTRIBUTE = 0x200;
+const uint32 PORTALLOCATOR_USE_LARGE_SOCKET_SEND_BUFFERS = 0x400;
+
+const uint32 kDefaultPortAllocatorFlags = 0;
+
+const uint32 kDefaultStepDelay = 1000;  // 1 sec step delay.
+// As per RFC 5245 Appendix B.1, STUN transactions need to be paced at certain
+// internal. Less than 20ms is not acceptable. We choose 50ms as our default.
+const uint32 kMinimumStepDelay = 50;
+
+class PortAllocatorSessionMuxer;
+
+class PortAllocatorSession : public sigslot::has_slots<> {
+ public:
+  // Content name passed in mostly for logging and debugging.
+  // TODO(mallinath) - Change username and password to ice_ufrag and ice_pwd.
+  PortAllocatorSession(const std::string& content_name,
+                       int component,
+                       const std::string& username,
+                       const std::string& password,
+                       uint32 flags);
+
+  // Subclasses should clean up any ports created.
+  virtual ~PortAllocatorSession() {}
+
+  uint32 flags() const { return flags_; }
+  void set_flags(uint32 flags) { flags_ = flags; }
+  std::string content_name() const { return content_name_; }
+  int component() const { return component_; }
+
+  // Starts gathering STUN and Relay configurations.
+  virtual void StartGettingPorts() = 0;
+  virtual void StopGettingPorts() = 0;
+  virtual bool IsGettingPorts() = 0;
+
+  sigslot::signal2<PortAllocatorSession*, PortInterface*> SignalPortReady;
+  sigslot::signal2<PortAllocatorSession*,
+                   const std::vector<Candidate>&> SignalCandidatesReady;
+  sigslot::signal1<PortAllocatorSession*> SignalCandidatesAllocationDone;
+
+  virtual uint32 generation() { return generation_; }
+  virtual void set_generation(uint32 generation) { generation_ = generation; }
+  sigslot::signal1<PortAllocatorSession*> SignalDestroyed;
+
+ protected:
+  const std::string& username() const { return username_; }
+  const std::string& password() const { return password_; }
+
+  std::string content_name_;
+  int component_;
+
+ private:
+  uint32 flags_;
+  uint32 generation_;
+  std::string username_;
+  std::string password_;
+};
+
+class PortAllocator : public sigslot::has_slots<> {
+ public:
+  PortAllocator() :
+      flags_(kDefaultPortAllocatorFlags),
+      min_port_(0),
+      max_port_(0),
+      step_delay_(kDefaultStepDelay) {
+    // This will allow us to have old behavior on non webrtc clients.
+  }
+  virtual ~PortAllocator();
+
+  PortAllocatorSession* CreateSession(
+      const std::string& sid,
+      const std::string& content_name,
+      int component,
+      const std::string& ice_ufrag,
+      const std::string& ice_pwd);
+
+  PortAllocatorSessionMuxer* GetSessionMuxer(const std::string& key) const;
+  void OnSessionMuxerDestroyed(PortAllocatorSessionMuxer* session);
+
+  uint32 flags() const { return flags_; }
+  void set_flags(uint32 flags) { flags_ = flags; }
+
+  const std::string& user_agent() const { return agent_; }
+  const talk_base::ProxyInfo& proxy() const { return proxy_; }
+  void set_proxy(const std::string& agent, const talk_base::ProxyInfo& proxy) {
+    agent_ = agent;
+    proxy_ = proxy;
+  }
+
+  // Gets/Sets the port range to use when choosing client ports.
+  int min_port() const { return min_port_; }
+  int max_port() const { return max_port_; }
+  bool SetPortRange(int min_port, int max_port) {
+    if (min_port > max_port) {
+      return false;
+    }
+
+    min_port_ = min_port;
+    max_port_ = max_port;
+    return true;
+  }
+
+  void set_step_delay(uint32 delay) {
+    ASSERT(delay >= kMinimumStepDelay);
+    step_delay_ = delay;
+  }
+  uint32 step_delay() const { return step_delay_; }
+
+ protected:
+  virtual PortAllocatorSession* CreateSessionInternal(
+      const std::string& content_name,
+      int component,
+      const std::string& ice_ufrag,
+      const std::string& ice_pwd) = 0;
+
+  typedef std::map<std::string, PortAllocatorSessionMuxer*> SessionMuxerMap;
+
+  uint32 flags_;
+  std::string agent_;
+  talk_base::ProxyInfo proxy_;
+  int min_port_;
+  int max_port_;
+  uint32 step_delay_;
+  SessionMuxerMap muxers_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_PORTALLOCATOR_H_
diff --git a/talk/p2p/base/portallocatorsessionproxy.cc b/talk/p2p/base/portallocatorsessionproxy.cc
new file mode 100644
index 0000000..1a201d3
--- /dev/null
+++ b/talk/p2p/base/portallocatorsessionproxy.cc
@@ -0,0 +1,239 @@
+/*
+ * 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/base/thread.h"
+#include "talk/p2p/base/portallocator.h"
+#include "talk/p2p/base/portproxy.h"
+
+namespace cricket {
+
+enum {
+  MSG_SEND_ALLOCATION_DONE = 1,
+  MSG_SEND_ALLOCATED_PORTS,
+};
+
+typedef talk_base::TypedMessageData<PortAllocatorSessionProxy*> ProxyObjData;
+
+PortAllocatorSessionMuxer::PortAllocatorSessionMuxer(
+    PortAllocatorSession* session)
+    : worker_thread_(talk_base::Thread::Current()),
+      session_(session),
+      candidate_done_signal_received_(false) {
+  session_->SignalPortReady.connect(
+      this, &PortAllocatorSessionMuxer::OnPortReady);
+  session_->SignalCandidatesAllocationDone.connect(
+      this, &PortAllocatorSessionMuxer::OnCandidatesAllocationDone);
+}
+
+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());
+
+  // Populate new proxy session with the information available in the actual
+  // implementation.
+  if (!ports_.empty()) {
+    worker_thread_->Post(
+        this, MSG_SEND_ALLOCATED_PORTS, new ProxyObjData(session_proxy));
+  }
+
+  if (candidate_done_signal_received_) {
+    worker_thread_->Post(
+        this, MSG_SEND_ALLOCATION_DONE, new ProxyObjData(session_proxy));
+  }
+}
+
+void PortAllocatorSessionMuxer::OnCandidatesAllocationDone(
+    PortAllocatorSession* session) {
+  candidate_done_signal_received_ = true;
+}
+
+void PortAllocatorSessionMuxer::OnPortReady(PortAllocatorSession* session,
+                                            PortInterface* port) {
+  ASSERT(session == session_.get());
+  ports_.push_back(port);
+  port->SignalDestroyed.connect(
+      this, &PortAllocatorSessionMuxer::OnPortDestroyed);
+}
+
+void PortAllocatorSessionMuxer::OnPortDestroyed(PortInterface* port) {
+  std::vector<PortInterface*>::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;
+  }
+}
+
+void PortAllocatorSessionMuxer::OnMessage(talk_base::Message *pmsg) {
+  ProxyObjData* proxy = static_cast<ProxyObjData*> (pmsg->pdata);
+  switch (pmsg->message_id) {
+    case MSG_SEND_ALLOCATION_DONE:
+      SendAllocationDone_w(proxy->data());
+      delete proxy;
+      break;
+    case MSG_SEND_ALLOCATED_PORTS:
+      SendAllocatedPorts_w(proxy->data());
+      delete proxy;
+      break;
+    default:
+      ASSERT(false);
+      break;
+  }
+}
+
+void PortAllocatorSessionMuxer::SendAllocationDone_w(
+    PortAllocatorSessionProxy* proxy) {
+  std::vector<PortAllocatorSessionProxy*>::iterator iter =
+      std::find(session_proxies_.begin(), session_proxies_.end(), proxy);
+  if (iter != session_proxies_.end()) {
+    proxy->OnCandidatesAllocationDone(session_.get());
+  }
+}
+
+void PortAllocatorSessionMuxer::SendAllocatedPorts_w(
+    PortAllocatorSessionProxy* proxy) {
+  std::vector<PortAllocatorSessionProxy*>::iterator iter =
+      std::find(session_proxies_.begin(), session_proxies_.end(), proxy);
+  if (iter != session_proxies_.end()) {
+    for (size_t i = 0; i < ports_.size(); ++i) {
+      PortInterface* port = ports_[i];
+      proxy->OnPortReady(session_.get(), port);
+      // If port already has candidates, send this to the clients of proxy
+      // session. This can happen if proxy is created later than the actual
+      // implementation.
+      if (!port->Candidates().empty()) {
+        proxy->OnCandidatesReady(session_.get(), port->Candidates());
+      }
+    }
+  }
+}
+
+PortAllocatorSessionProxy::~PortAllocatorSessionProxy() {
+  std::map<PortInterface*, 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);
+  impl_->SignalCandidatesAllocationDone.connect(
+      this, &PortAllocatorSessionProxy::OnCandidatesAllocationDone);
+}
+
+void PortAllocatorSessionProxy::StartGettingPorts() {
+  ASSERT(impl_ != NULL);
+  // Since all proxies share a common PortAllocatorSession, this check will
+  // prohibit sending multiple STUN ping messages to the stun server, which
+  // is a problem on Chrome. GetInitialPorts() and StartGetAllPorts() called
+  // from the worker thread and are called together from TransportChannel,
+  // checking for IsGettingAllPorts() for GetInitialPorts() will not be a
+  // problem.
+  if (!impl_->IsGettingPorts()) {
+    impl_->StartGettingPorts();
+  }
+}
+
+void PortAllocatorSessionProxy::StopGettingPorts() {
+  ASSERT(impl_ != NULL);
+  if (impl_->IsGettingPorts()) {
+    impl_->StopGettingPorts();
+  }
+}
+
+bool PortAllocatorSessionProxy::IsGettingPorts() {
+  ASSERT(impl_ != NULL);
+  return impl_->IsGettingPorts();
+}
+
+void PortAllocatorSessionProxy::OnPortReady(PortAllocatorSession* session,
+                                            PortInterface* port) {
+  ASSERT(session == impl_);
+
+  PortProxy* proxy_port = new PortProxy();
+  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_component(component_);
+    our_candidates.push_back(new_local_candidate);
+  }
+  SignalCandidatesReady(this, our_candidates);
+}
+
+void PortAllocatorSessionProxy::OnCandidatesAllocationDone(
+    PortAllocatorSession* session) {
+  ASSERT(session == impl_);
+  SignalCandidatesAllocationDone(this);
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/portallocatorsessionproxy.h b/talk/p2p/base/portallocatorsessionproxy.h
new file mode 100644
index 0000000..990ea8a
--- /dev/null
+++ b/talk/p2p/base/portallocatorsessionproxy.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_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 talk_base::MessageHandler,
+                                  public sigslot::has_slots<> {
+ public:
+  explicit PortAllocatorSessionMuxer(PortAllocatorSession* session);
+  virtual ~PortAllocatorSessionMuxer();
+
+  void RegisterSessionProxy(PortAllocatorSessionProxy* session_proxy);
+
+  void OnPortReady(PortAllocatorSession* session, PortInterface* port);
+  void OnPortDestroyed(PortInterface* port);
+  void OnCandidatesAllocationDone(PortAllocatorSession* session);
+
+  const std::vector<PortInterface*>& ports() { return ports_; }
+
+  sigslot::signal1<PortAllocatorSessionMuxer*> SignalDestroyed;
+
+ private:
+  virtual void OnMessage(talk_base::Message *pmsg);
+  void OnSessionProxyDestroyed(PortAllocatorSession* proxy);
+  void SendAllocationDone_w(PortAllocatorSessionProxy* proxy);
+  void SendAllocatedPorts_w(PortAllocatorSessionProxy* proxy);
+
+  // Port will be deleted when SignalDestroyed received, otherwise delete
+  // happens when PortAllocatorSession dtor is called.
+  talk_base::Thread* worker_thread_;
+  std::vector<PortInterface*> ports_;
+  talk_base::scoped_ptr<PortAllocatorSession> session_;
+  std::vector<PortAllocatorSessionProxy*> session_proxies_;
+  bool candidate_done_signal_received_;
+};
+
+class PortAllocatorSessionProxy : public PortAllocatorSession {
+ public:
+  PortAllocatorSessionProxy(const std::string& content_name,
+                            int component,
+                            uint32 flags)
+        // Use empty string as the ufrag and pwd because the proxy always uses
+        // the ufrag and pwd from the underlying implementation.
+      : PortAllocatorSession(content_name, component, "", "", flags),
+        impl_(NULL) {
+  }
+
+  virtual ~PortAllocatorSessionProxy();
+
+  PortAllocatorSession* impl() { return impl_; }
+  void set_impl(PortAllocatorSession* session);
+
+  // Forwards call to the actual PortAllocatorSession.
+  virtual void StartGettingPorts();
+  virtual void StopGettingPorts();
+  virtual bool IsGettingPorts();
+
+  virtual void set_generation(uint32 generation) {
+    ASSERT(impl_ != NULL);
+    impl_->set_generation(generation);
+  }
+
+  virtual uint32 generation() {
+    ASSERT(impl_ != NULL);
+    return impl_->generation();
+  }
+
+ private:
+  void OnPortReady(PortAllocatorSession* session, PortInterface* port);
+  void OnCandidatesReady(PortAllocatorSession* session,
+                         const std::vector<Candidate>& candidates);
+  void OnPortDestroyed(PortInterface* port);
+  void OnCandidatesAllocationDone(PortAllocatorSession* session);
+
+  // This is the actual PortAllocatorSession, owned by PortAllocator.
+  PortAllocatorSession* impl_;
+  std::map<PortInterface*, PortProxy*> proxy_ports_;
+
+  friend class PortAllocatorSessionMuxer;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_PORTALLOCATORSESSIONPROXY_H_
diff --git a/talk/p2p/base/portallocatorsessionproxy_unittest.cc b/talk/p2p/base/portallocatorsessionproxy_unittest.cc
new file mode 100644
index 0000000..fc6dc59
--- /dev/null
+++ b/talk/p2p/base/portallocatorsessionproxy_unittest.cc
@@ -0,0 +1,160 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/fakenetwork.h"
+#include "talk/base/gunit.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/basicpacketsocketfactory.h"
+#include "talk/p2p/base/portallocatorsessionproxy.h"
+#include "talk/p2p/client/basicportallocator.h"
+#include "talk/p2p/client/fakeportallocator.h"
+
+using cricket::Candidate;
+using cricket::PortAllocatorSession;
+using cricket::PortAllocatorSessionMuxer;
+using cricket::PortAllocatorSessionProxy;
+
+// Based on ICE_UFRAG_LENGTH
+static const char kIceUfrag0[] = "TESTICEUFRAG0000";
+// Based on ICE_PWD_LENGTH
+static const char kIcePwd0[] = "TESTICEPWD00000000000000";
+
+class TestSessionChannel : public sigslot::has_slots<> {
+ public:
+  explicit TestSessionChannel(PortAllocatorSessionProxy* proxy)
+      : proxy_session_(proxy),
+        candidates_count_(0),
+        allocation_complete_(false),
+        ports_count_(0) {
+    proxy_session_->SignalCandidatesAllocationDone.connect(
+        this, &TestSessionChannel::OnCandidatesAllocationDone);
+    proxy_session_->SignalCandidatesReady.connect(
+        this, &TestSessionChannel::OnCandidatesReady);
+    proxy_session_->SignalPortReady.connect(
+        this, &TestSessionChannel::OnPortReady);
+  }
+  virtual ~TestSessionChannel() {}
+  void OnCandidatesReady(PortAllocatorSession* session,
+                         const std::vector<Candidate>& candidates) {
+    EXPECT_EQ(proxy_session_, session);
+    candidates_count_ += candidates.size();
+  }
+  void OnCandidatesAllocationDone(PortAllocatorSession* session) {
+    EXPECT_EQ(proxy_session_, session);
+    allocation_complete_ = true;
+  }
+  void OnPortReady(PortAllocatorSession* session,
+                   cricket::PortInterface* port) {
+    EXPECT_EQ(proxy_session_, session);
+    ++ports_count_;
+  }
+  int candidates_count() { return candidates_count_; }
+  bool allocation_complete() { return allocation_complete_; }
+  int ports_count() { return ports_count_; }
+
+  void StartGettingPorts() {
+    proxy_session_->StartGettingPorts();
+  }
+
+  void StopGettingPorts() {
+    proxy_session_->StopGettingPorts();
+  }
+
+  bool IsGettingPorts() {
+    return proxy_session_->IsGettingPorts();
+  }
+
+ private:
+  PortAllocatorSessionProxy* proxy_session_;
+  int candidates_count_;
+  bool allocation_complete_;
+  int ports_count_;
+};
+
+class PortAllocatorSessionProxyTest : public testing::Test {
+ public:
+  PortAllocatorSessionProxyTest()
+      : socket_factory_(talk_base::Thread::Current()),
+        allocator_(talk_base::Thread::Current(), NULL),
+        session_(talk_base::Thread::Current(), &socket_factory_,
+                 "test content", 1,
+                 kIceUfrag0, kIcePwd0),
+        session_muxer_(new PortAllocatorSessionMuxer(&session_)) {
+  }
+  virtual ~PortAllocatorSessionProxyTest() {}
+  void RegisterSessionProxy(PortAllocatorSessionProxy* proxy) {
+    session_muxer_->RegisterSessionProxy(proxy);
+  }
+
+  TestSessionChannel* CreateChannel() {
+    PortAllocatorSessionProxy* proxy =
+        new PortAllocatorSessionProxy("test content", 1, 0);
+    TestSessionChannel* channel = new TestSessionChannel(proxy);
+    session_muxer_->RegisterSessionProxy(proxy);
+    channel->StartGettingPorts();
+    return channel;
+  }
+
+ protected:
+  talk_base::BasicPacketSocketFactory socket_factory_;
+  cricket::FakePortAllocator allocator_;
+  cricket::FakePortAllocatorSession session_;
+  // Muxer object will be delete itself after all registered session proxies
+  // are deleted.
+  PortAllocatorSessionMuxer* session_muxer_;
+};
+
+TEST_F(PortAllocatorSessionProxyTest, TestBasic) {
+  TestSessionChannel* channel = CreateChannel();
+  EXPECT_EQ_WAIT(1, channel->candidates_count(), 1000);
+  EXPECT_EQ(1, channel->ports_count());
+  EXPECT_TRUE(channel->allocation_complete());
+  delete channel;
+}
+
+TEST_F(PortAllocatorSessionProxyTest, TestLateBinding) {
+  TestSessionChannel* channel1 = CreateChannel();
+  EXPECT_EQ_WAIT(1, channel1->candidates_count(), 1000);
+  EXPECT_EQ(1, channel1->ports_count());
+  EXPECT_TRUE(channel1->allocation_complete());
+  EXPECT_EQ(1, session_.port_config_count());
+  // Creating another PortAllocatorSessionProxy and it also should receive
+  // already happened events.
+  PortAllocatorSessionProxy* proxy =
+      new PortAllocatorSessionProxy("test content", 2, 0);
+  TestSessionChannel* channel2 = new TestSessionChannel(proxy);
+  session_muxer_->RegisterSessionProxy(proxy);
+  EXPECT_TRUE(channel2->IsGettingPorts());
+  EXPECT_EQ_WAIT(1, channel2->candidates_count(), 1000);
+  EXPECT_EQ(1, channel2->ports_count());
+  EXPECT_TRUE_WAIT(channel2->allocation_complete(), 1000);
+  EXPECT_EQ(1, session_.port_config_count());
+  delete channel1;
+  delete channel2;
+}
diff --git a/talk/p2p/base/portinterface.h b/talk/p2p/base/portinterface.h
new file mode 100644
index 0000000..ec34bf1
--- /dev/null
+++ b/talk/p2p/base/portinterface.h
@@ -0,0 +1,143 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_PORTINTERFACE_H_
+#define TALK_P2P_BASE_PORTINTERFACE_H_
+
+#include <string>
+
+#include "talk/base/socketaddress.h"
+#include "talk/p2p/base/transport.h"
+
+namespace talk_base {
+class Network;
+class PacketSocketFactory;
+}
+
+namespace cricket {
+class Connection;
+class IceMessage;
+class StunMessage;
+
+enum ProtocolType {
+  PROTO_UDP,
+  PROTO_TCP,
+  PROTO_SSLTCP,
+  PROTO_LAST = PROTO_SSLTCP
+};
+
+// Defines the interface for a port, which represents a local communication
+// mechanism that can be used to create connections to similar mechanisms of
+// the other client. Various types of ports will implement this interface.
+class PortInterface {
+ public:
+  virtual ~PortInterface() {}
+
+  virtual const std::string& Type() const = 0;
+  virtual talk_base::Network* Network() const = 0;
+
+  virtual void SetIceProtocolType(IceProtocolType protocol) = 0;
+  virtual IceProtocolType IceProtocol() const = 0;
+
+  // Methods to set/get ICE role and tiebreaker values.
+  virtual void SetRole(TransportRole role) = 0;
+  virtual TransportRole Role() const = 0;
+
+  virtual void SetTiebreaker(uint64 tiebreaker) = 0;
+  virtual uint64 Tiebreaker() const = 0;
+
+  virtual bool SharedSocket() const = 0;
+
+  // PrepareAddress will attempt to get an address for this port that other
+  // clients can send to.  It may take some time before the address is ready.
+  // Once it is ready, we will send SignalAddressReady.  If errors are
+  // preventing the port from getting an address, it may send
+  // SignalAddressError.
+  virtual void PrepareAddress() = 0;
+
+  // Returns the connection to the given address or NULL if none exists.
+  virtual Connection* GetConnection(
+      const talk_base::SocketAddress& remote_addr) = 0;
+
+  // Creates a new connection to the given address.
+  enum CandidateOrigin { ORIGIN_THIS_PORT, ORIGIN_OTHER_PORT, ORIGIN_MESSAGE };
+  virtual Connection* CreateConnection(
+      const Candidate& remote_candidate, CandidateOrigin origin) = 0;
+
+  // Functions on the underlying socket(s).
+  virtual int SetOption(talk_base::Socket::Option opt, int value) = 0;
+  virtual int GetError() = 0;
+
+  virtual int GetOption(talk_base::Socket::Option opt, int* value) = 0;
+
+  virtual const std::vector<Candidate>& Candidates() const = 0;
+
+  // Sends the given packet to the given address, provided that the address is
+  // that of a connection or an address that has sent to us already.
+  virtual int SendTo(const void* data, size_t size,
+                     const talk_base::SocketAddress& addr, bool payload) = 0;
+
+  // Indicates that we received a successful STUN binding request from an
+  // address that doesn't correspond to any current connection.  To turn this
+  // into a real connection, call CreateConnection.
+  sigslot::signal6<PortInterface*, const talk_base::SocketAddress&,
+                   ProtocolType, IceMessage*, 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.
+  virtual void SendBindingResponse(StunMessage* request,
+                                   const talk_base::SocketAddress& addr) = 0;
+  virtual void SendBindingErrorResponse(
+      StunMessage* request, const talk_base::SocketAddress& addr,
+      int error_code, const std::string& reason) = 0;
+
+  // Signaled when this port decides to delete itself because it no longer has
+  // any usefulness.
+  sigslot::signal1<PortInterface*> SignalDestroyed;
+
+  // Signaled when Port discovers ice role conflict with the peer.
+  sigslot::signal1<PortInterface*> SignalRoleConflict;
+
+  // Normally, packets arrive through a connection (or they result signaling of
+  // unknown address).  Calling this method turns off delivery of packets
+  // through their respective connection and instead delivers every packet
+  // through this port.
+  virtual void EnablePortPackets() = 0;
+  sigslot::signal4<PortInterface*, const char*, size_t,
+                   const talk_base::SocketAddress&> SignalReadPacket;
+
+  virtual std::string ToString() const = 0;
+
+ protected:
+  PortInterface() {}
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_PORTINTERFACE_H_
diff --git a/talk/p2p/base/portproxy.cc b/talk/p2p/base/portproxy.cc
new file mode 100644
index 0000000..63fd026
--- /dev/null
+++ b/talk/p2p/base/portproxy.cc
@@ -0,0 +1,180 @@
+/*
+ * 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(PortInterface* port) {
+  impl_ = port;
+  impl_->SignalUnknownAddress.connect(
+      this, &PortProxy::OnUnknownAddress);
+  impl_->SignalDestroyed.connect(this, &PortProxy::OnPortDestroyed);
+  impl_->SignalRoleConflict.connect(this, &PortProxy::OnRoleConflict);
+}
+
+const std::string& PortProxy::Type() const {
+  ASSERT(impl_ != NULL);
+  return impl_->Type();
+}
+
+talk_base::Network* PortProxy::Network() const {
+  ASSERT(impl_ != NULL);
+  return impl_->Network();
+}
+
+void PortProxy::SetIceProtocolType(IceProtocolType protocol) {
+  ASSERT(impl_ != NULL);
+  impl_->SetIceProtocolType(protocol);
+}
+
+IceProtocolType PortProxy::IceProtocol() const {
+  ASSERT(impl_ != NULL);
+  return impl_->IceProtocol();
+}
+
+// Methods to set/get ICE role and tiebreaker values.
+void PortProxy::SetRole(TransportRole role) {
+  ASSERT(impl_ != NULL);
+  impl_->SetRole(role);
+}
+
+TransportRole PortProxy::Role() const {
+  ASSERT(impl_ != NULL);
+  return impl_->Role();
+}
+
+void PortProxy::SetTiebreaker(uint64 tiebreaker) {
+  ASSERT(impl_ != NULL);
+  impl_->SetTiebreaker(tiebreaker);
+}
+
+uint64 PortProxy::Tiebreaker() const {
+  ASSERT(impl_ != NULL);
+  return impl_->Tiebreaker();
+}
+
+bool PortProxy::SharedSocket() const {
+  ASSERT(impl_ != NULL);
+  return impl_->SharedSocket();
+}
+
+void PortProxy::PrepareAddress() {
+  ASSERT(impl_ != NULL);
+  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::GetOption(talk_base::Socket::Option opt,
+                         int* value) {
+  ASSERT(impl_ != NULL);
+  return impl_->GetOption(opt, value);
+}
+
+
+int PortProxy::GetError() {
+  ASSERT(impl_ != NULL);
+  return impl_->GetError();
+}
+
+const std::vector<Candidate>& PortProxy::Candidates() const {
+  ASSERT(impl_ != NULL);
+  return impl_->Candidates();
+}
+
+void PortProxy::SendBindingResponse(
+    StunMessage* request, const talk_base::SocketAddress& addr) {
+  ASSERT(impl_ != NULL);
+  impl_->SendBindingResponse(request, addr);
+}
+
+Connection* PortProxy::GetConnection(
+    const talk_base::SocketAddress& remote_addr) {
+  ASSERT(impl_ != NULL);
+  return impl_->GetConnection(remote_addr);
+}
+
+void PortProxy::SendBindingErrorResponse(
+    StunMessage* request, const talk_base::SocketAddress& addr,
+    int error_code, const std::string& reason) {
+  ASSERT(impl_ != NULL);
+  impl_->SendBindingErrorResponse(request, addr, error_code, reason);
+}
+
+void PortProxy::EnablePortPackets() {
+  ASSERT(impl_ != NULL);
+  impl_->EnablePortPackets();
+}
+
+std::string PortProxy::ToString() const {
+  ASSERT(impl_ != NULL);
+  return impl_->ToString();
+}
+
+void PortProxy::OnUnknownAddress(
+    PortInterface *port,
+    const talk_base::SocketAddress &addr,
+    ProtocolType proto,
+    IceMessage *stun_msg,
+    const std::string &remote_username,
+    bool port_muxed) {
+  ASSERT(port == impl_);
+  ASSERT(!port_muxed);
+  SignalUnknownAddress(this, addr, proto, stun_msg, remote_username, true);
+}
+
+void PortProxy::OnRoleConflict(PortInterface* port) {
+  ASSERT(port == impl_);
+  SignalRoleConflict(this);
+}
+
+void PortProxy::OnPortDestroyed(PortInterface* 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..8653c09
--- /dev/null
+++ b/talk/p2p/base/portproxy.h
@@ -0,0 +1,102 @@
+/*
+ * 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/base/sigslot.h"
+#include "talk/p2p/base/portinterface.h"
+
+namespace talk_base {
+class Network;
+}
+
+namespace cricket {
+
+class PortProxy : public PortInterface, public sigslot::has_slots<> {
+ public:
+  PortProxy() {}
+  virtual ~PortProxy() {}
+
+  PortInterface* impl() { return impl_; }
+  void set_impl(PortInterface* port);
+
+  virtual const std::string& Type() const;
+  virtual talk_base::Network* Network() const;
+
+  virtual void SetIceProtocolType(IceProtocolType protocol);
+  virtual IceProtocolType IceProtocol() const;
+
+  // Methods to set/get ICE role and tiebreaker values.
+  virtual void SetRole(TransportRole role);
+  virtual TransportRole Role() const;
+
+  virtual void SetTiebreaker(uint64 tiebreaker);
+  virtual uint64 Tiebreaker() const;
+
+  virtual bool SharedSocket() const;
+
+  // Forwards call to the actual Port.
+  virtual void PrepareAddress();
+  virtual Connection* CreateConnection(const Candidate& remote_candidate,
+                                       CandidateOrigin origin);
+  virtual Connection* GetConnection(
+      const talk_base::SocketAddress& remote_addr);
+
+  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 GetOption(talk_base::Socket::Option opt, int* value);
+  virtual int GetError();
+
+  virtual const std::vector<Candidate>& Candidates() const;
+
+  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);
+
+  virtual void EnablePortPackets();
+  virtual std::string ToString() const;
+
+ private:
+  void OnUnknownAddress(PortInterface *port,
+                        const talk_base::SocketAddress &addr,
+                        ProtocolType proto,
+                        IceMessage *stun_msg,
+                        const std::string &remote_username,
+                        bool port_muxed);
+  void OnRoleConflict(PortInterface* port);
+  void OnPortDestroyed(PortInterface* port);
+
+  PortInterface* impl_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_PORTPROXY_H_
diff --git a/talk/p2p/base/pseudotcp.cc b/talk/p2p/base/pseudotcp.cc
new file mode 100644
index 0000000..2cf2799
--- /dev/null
+++ b/talk/p2p/base/pseudotcp.cc
@@ -0,0 +1,1296 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/p2p/base/pseudotcp.h"
+
+#include <cstdio>
+#include <cstdlib>
+#include <set>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/bytebuffer.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/socket.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/timeutils.h"
+
+// The following logging is for detailed (packet-level) analysis only.
+#define _DBG_NONE     0
+#define _DBG_NORMAL   1
+#define _DBG_VERBOSE  2
+#define _DEBUGMSG _DBG_NONE
+
+namespace cricket {
+
+//////////////////////////////////////////////////////////////////////
+// Network Constants
+//////////////////////////////////////////////////////////////////////
+
+// Standard MTUs
+const uint16 PACKET_MAXIMUMS[] = {
+  65535,    // Theoretical maximum, Hyperchannel
+  32000,    // Nothing
+  17914,    // 16Mb IBM Token Ring
+  8166,   // IEEE 802.4
+  //4464,   // IEEE 802.5 (4Mb max)
+  4352,   // FDDI
+  //2048,   // Wideband Network
+  2002,   // IEEE 802.5 (4Mb recommended)
+  //1536,   // Expermental Ethernet Networks
+  //1500,   // Ethernet, Point-to-Point (default)
+  1492,   // IEEE 802.3
+  1006,   // SLIP, ARPANET
+  //576,    // X.25 Networks
+  //544,    // DEC IP Portal
+  //512,    // NETBIOS
+  508,    // IEEE 802/Source-Rt Bridge, ARCNET
+  296,    // Point-to-Point (low delay)
+  //68,     // Official minimum
+  0,      // End of list marker
+};
+
+const uint32 MAX_PACKET = 65535;
+// Note: we removed lowest level because packet overhead was larger!
+const uint32 MIN_PACKET = 296;
+
+const uint32 IP_HEADER_SIZE = 20; // (+ up to 40 bytes of options?)
+const uint32 ICMP_HEADER_SIZE = 8;
+const uint32 UDP_HEADER_SIZE = 8;
+// TODO: Make JINGLE_HEADER_SIZE transparent to this code?
+const uint32 JINGLE_HEADER_SIZE = 64; // when relay framing is in use
+
+// Default size for receive and send buffer.
+const uint32 DEFAULT_RCV_BUF_SIZE = 60 * 1024;
+const uint32 DEFAULT_SND_BUF_SIZE = 90 * 1024;
+
+//////////////////////////////////////////////////////////////////////
+// Global Constants and Functions
+//////////////////////////////////////////////////////////////////////
+//
+//    0                   1                   2                   3
+//    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//  0 |                      Conversation Number                      |
+//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//  4 |                        Sequence Number                        |
+//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//  8 |                     Acknowledgment Number                     |
+//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//    |               |   |U|A|P|R|S|F|                               |
+// 12 |    Control    |   |R|C|S|S|Y|I|            Window             |
+//    |               |   |G|K|H|T|N|N|                               |
+//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// 16 |                       Timestamp sending                       |
+//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// 20 |                      Timestamp receiving                      |
+//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// 24 |                             data                              |
+//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//
+//////////////////////////////////////////////////////////////////////
+
+#define PSEUDO_KEEPALIVE 0
+
+const uint32 MAX_SEQ = 0xFFFFFFFF;
+const uint32 HEADER_SIZE = 24;
+const uint32 PACKET_OVERHEAD = HEADER_SIZE + UDP_HEADER_SIZE + IP_HEADER_SIZE + JINGLE_HEADER_SIZE;
+
+const uint32 MIN_RTO   =   250; // 250 ms (RFC1122, Sec 4.2.3.1 "fractions of a second")
+const uint32 DEF_RTO   =  3000; // 3 seconds (RFC1122, Sec 4.2.3.1)
+const uint32 MAX_RTO   = 60000; // 60 seconds
+const uint32 DEF_ACK_DELAY = 100; // 100 milliseconds
+
+const uint8 FLAG_CTL = 0x02;
+const uint8 FLAG_RST = 0x04;
+
+const uint8 CTL_CONNECT = 0;
+//const uint8 CTL_REDIRECT = 1;
+const uint8 CTL_EXTRA = 255;
+
+// TCP options.
+const uint8 TCP_OPT_EOL = 0;  // End of list.
+const uint8 TCP_OPT_NOOP = 1;  // No-op.
+const uint8 TCP_OPT_MSS = 2;  // Maximum segment size.
+const uint8 TCP_OPT_WND_SCALE = 3;  // Window scale factor.
+
+/*
+const uint8 FLAG_FIN = 0x01;
+const uint8 FLAG_SYN = 0x02;
+const uint8 FLAG_ACK = 0x10;
+*/
+
+const uint32 CTRL_BOUND = 0x80000000;
+
+const long DEFAULT_TIMEOUT = 4000; // If there are no pending clocks, wake up every 4 seconds
+const long CLOSED_TIMEOUT = 60 * 1000; // If the connection is closed, once per minute
+
+#if PSEUDO_KEEPALIVE
+// !?! Rethink these times
+const uint32 IDLE_PING = 20 * 1000; // 20 seconds (note: WinXP SP2 firewall udp timeout is 90 seconds)
+const uint32 IDLE_TIMEOUT = 90 * 1000; // 90 seconds;
+#endif // PSEUDO_KEEPALIVE
+
+//////////////////////////////////////////////////////////////////////
+// Helper Functions
+//////////////////////////////////////////////////////////////////////
+
+inline void long_to_bytes(uint32 val, void* buf) {
+  *static_cast<uint32*>(buf) = talk_base::HostToNetwork32(val);
+}
+
+inline void short_to_bytes(uint16 val, void* buf) {
+  *static_cast<uint16*>(buf) = talk_base::HostToNetwork16(val);
+}
+
+inline uint32 bytes_to_long(const void* buf) {
+  return talk_base::NetworkToHost32(*static_cast<const uint32*>(buf));
+}
+
+inline uint16 bytes_to_short(const void* buf) {
+  return talk_base::NetworkToHost16(*static_cast<const uint16*>(buf));
+}
+
+uint32 bound(uint32 lower, uint32 middle, uint32 upper) {
+  return talk_base::_min(talk_base::_max(lower, middle), upper);
+}
+
+//////////////////////////////////////////////////////////////////////
+// Debugging Statistics
+//////////////////////////////////////////////////////////////////////
+
+#if 0  // Not used yet
+
+enum Stat {
+  S_SENT_PACKET,   // All packet sends
+  S_RESENT_PACKET, // All packet sends that are retransmits
+  S_RECV_PACKET,   // All packet receives
+  S_RECV_NEW,      // All packet receives that are too new
+  S_RECV_OLD,      // All packet receives that are too old
+  S_NUM_STATS
+};
+
+const char* const STAT_NAMES[S_NUM_STATS] = {
+  "snt",
+  "snt-r",
+  "rcv"
+  "rcv-n",
+  "rcv-o"
+};
+
+int g_stats[S_NUM_STATS];
+inline void Incr(Stat s) { ++g_stats[s]; }
+void ReportStats() {
+  char buffer[256];
+  size_t len = 0;
+  for (int i = 0; i < S_NUM_STATS; ++i) {
+    len += talk_base::sprintfn(buffer, ARRAY_SIZE(buffer), "%s%s:%d",
+                               (i == 0) ? "" : ",", STAT_NAMES[i], g_stats[i]);
+    g_stats[i] = 0;
+  }
+  LOG(LS_INFO) << "Stats[" << buffer << "]";
+}
+
+#endif
+
+//////////////////////////////////////////////////////////////////////
+// PseudoTcp
+//////////////////////////////////////////////////////////////////////
+
+uint32 PseudoTcp::Now() {
+#if 0  // Use this to synchronize timers with logging timestamps (easier debug)
+  return talk_base::TimeSince(StartTime());
+#else
+  return talk_base::Time();
+#endif
+}
+
+PseudoTcp::PseudoTcp(IPseudoTcpNotify* notify, uint32 conv)
+    : m_notify(notify),
+      m_shutdown(SD_NONE),
+      m_error(0),
+      m_rbuf_len(DEFAULT_RCV_BUF_SIZE),
+      m_rbuf(m_rbuf_len),
+      m_sbuf_len(DEFAULT_SND_BUF_SIZE),
+      m_sbuf(m_sbuf_len) {
+
+  // Sanity check on buffer sizes (needed for OnTcpWriteable notification logic)
+  ASSERT(m_rbuf_len + MIN_PACKET < m_sbuf_len);
+
+  uint32 now = Now();
+
+  m_state = TCP_LISTEN;
+  m_conv = conv;
+  m_rcv_wnd = m_rbuf_len;
+  m_rwnd_scale = m_swnd_scale = 0;
+  m_snd_nxt = 0;
+  m_snd_wnd = 1;
+  m_snd_una = m_rcv_nxt = 0;
+  m_bReadEnable = true;
+  m_bWriteEnable = false;
+  m_t_ack = 0;
+
+  m_msslevel = 0;
+  m_largest = 0;
+  ASSERT(MIN_PACKET > PACKET_OVERHEAD);
+  m_mss = MIN_PACKET - PACKET_OVERHEAD;
+  m_mtu_advise = MAX_PACKET;
+
+  m_rto_base = 0;
+
+  m_cwnd = 2 * m_mss;
+  m_ssthresh = m_rbuf_len;
+  m_lastrecv = m_lastsend = m_lasttraffic = now;
+  m_bOutgoing = false;
+
+  m_dup_acks = 0;
+  m_recover = 0;
+
+  m_ts_recent = m_ts_lastack = 0;
+
+  m_rx_rto = DEF_RTO;
+  m_rx_srtt = m_rx_rttvar = 0;
+
+  m_use_nagling = true;
+  m_ack_delay = DEF_ACK_DELAY;
+  m_support_wnd_scale = true;
+}
+
+PseudoTcp::~PseudoTcp() {
+}
+
+int PseudoTcp::Connect() {
+  if (m_state != TCP_LISTEN) {
+    m_error = EINVAL;
+    return -1;
+  }
+
+  m_state = TCP_SYN_SENT;
+  LOG(LS_INFO) << "State: TCP_SYN_SENT";
+
+  queueConnectMessage();
+  attemptSend();
+
+  return 0;
+}
+
+void PseudoTcp::NotifyMTU(uint16 mtu) {
+  m_mtu_advise = mtu;
+  if (m_state == TCP_ESTABLISHED) {
+    adjustMTU();
+  }
+}
+
+void PseudoTcp::NotifyClock(uint32 now) {
+  if (m_state == TCP_CLOSED)
+    return;
+
+    // Check if it's time to retransmit a segment
+  if (m_rto_base && (talk_base::TimeDiff(m_rto_base + m_rx_rto, now) <= 0)) {
+    if (m_slist.empty()) {
+      ASSERT(false);
+    } else {
+      // Note: (m_slist.front().xmit == 0)) {
+      // retransmit segments
+#if _DEBUGMSG >= _DBG_NORMAL
+      LOG(LS_INFO) << "timeout retransmit (rto: " << m_rx_rto
+                   << ") (rto_base: " << m_rto_base
+                   << ") (now: " << now
+                   << ") (dup_acks: " << static_cast<unsigned>(m_dup_acks)
+                   << ")";
+#endif // _DEBUGMSG
+      if (!transmit(m_slist.begin(), now)) {
+        closedown(ECONNABORTED);
+        return;
+      }
+
+      uint32 nInFlight = m_snd_nxt - m_snd_una;
+      m_ssthresh = talk_base::_max(nInFlight / 2, 2 * m_mss);
+      //LOG(LS_INFO) << "m_ssthresh: " << m_ssthresh << "  nInFlight: " << nInFlight << "  m_mss: " << m_mss;
+      m_cwnd = m_mss;
+
+      // Back off retransmit timer.  Note: the limit is lower when connecting.
+      uint32 rto_limit = (m_state < TCP_ESTABLISHED) ? DEF_RTO : MAX_RTO;
+      m_rx_rto = talk_base::_min(rto_limit, m_rx_rto * 2);
+      m_rto_base = now;
+    }
+  }
+
+  // Check if it's time to probe closed windows
+  if ((m_snd_wnd == 0)
+        && (talk_base::TimeDiff(m_lastsend + m_rx_rto, now) <= 0)) {
+    if (talk_base::TimeDiff(now, m_lastrecv) >= 15000) {
+      closedown(ECONNABORTED);
+      return;
+    }
+
+    // probe the window
+    packet(m_snd_nxt - 1, 0, 0, 0);
+    m_lastsend = now;
+
+    // back off retransmit timer
+    m_rx_rto = talk_base::_min(MAX_RTO, m_rx_rto * 2);
+  }
+
+  // Check if it's time to send delayed acks
+  if (m_t_ack && (talk_base::TimeDiff(m_t_ack + m_ack_delay, now) <= 0)) {
+    packet(m_snd_nxt, 0, 0, 0);
+  }
+
+#if PSEUDO_KEEPALIVE
+  // Check for idle timeout
+  if ((m_state == TCP_ESTABLISHED) && (TimeDiff(m_lastrecv + IDLE_TIMEOUT, now) <= 0)) {
+    closedown(ECONNABORTED);
+    return;
+  }
+
+  // Check for ping timeout (to keep udp mapping open)
+  if ((m_state == TCP_ESTABLISHED) && (TimeDiff(m_lasttraffic + (m_bOutgoing ? IDLE_PING * 3/2 : IDLE_PING), now) <= 0)) {
+    packet(m_snd_nxt, 0, 0, 0);
+  }
+#endif // PSEUDO_KEEPALIVE
+}
+
+bool PseudoTcp::NotifyPacket(const char* buffer, size_t len) {
+  if (len > MAX_PACKET) {
+    LOG_F(WARNING) << "packet too large";
+    return false;
+  }
+  return parse(reinterpret_cast<const uint8 *>(buffer), uint32(len));
+}
+
+bool PseudoTcp::GetNextClock(uint32 now, long& timeout) {
+  return clock_check(now, timeout);
+}
+
+void PseudoTcp::GetOption(Option opt, int* value) {
+  if (opt == OPT_NODELAY) {
+    *value = m_use_nagling ? 0 : 1;
+  } else if (opt == OPT_ACKDELAY) {
+    *value = m_ack_delay;
+  } else if (opt == OPT_SNDBUF) {
+    *value = m_sbuf_len;
+  } else if (opt == OPT_RCVBUF) {
+    *value = m_rbuf_len;
+  } else {
+    ASSERT(false);
+  }
+}
+void PseudoTcp::SetOption(Option opt, int value) {
+  if (opt == OPT_NODELAY) {
+    m_use_nagling = value == 0;
+  } else if (opt == OPT_ACKDELAY) {
+    m_ack_delay = value;
+  } else if (opt == OPT_SNDBUF) {
+    ASSERT(m_state == TCP_LISTEN);
+    resizeSendBuffer(value);
+  } else if (opt == OPT_RCVBUF) {
+    ASSERT(m_state == TCP_LISTEN);
+    resizeReceiveBuffer(value);
+  } else {
+    ASSERT(false);
+  }
+}
+
+uint32 PseudoTcp::GetCongestionWindow() const {
+  return m_cwnd;
+}
+
+uint32 PseudoTcp::GetBytesInFlight() const {
+  return m_snd_nxt - m_snd_una;
+}
+
+uint32 PseudoTcp::GetBytesBufferedNotSent() const {
+  size_t buffered_bytes = 0;
+  m_sbuf.GetBuffered(&buffered_bytes);
+  return m_snd_una + buffered_bytes - m_snd_nxt;
+}
+
+uint32 PseudoTcp::GetRoundTripTimeEstimateMs() const {
+  return m_rx_srtt;
+}
+
+//
+// IPStream Implementation
+//
+
+int PseudoTcp::Recv(char* buffer, size_t len) {
+  if (m_state != TCP_ESTABLISHED) {
+    m_error = ENOTCONN;
+    return SOCKET_ERROR;
+  }
+
+  size_t read = 0;
+  talk_base::StreamResult result = m_rbuf.Read(buffer, len, &read, NULL);
+
+  // If there's no data in |m_rbuf|.
+  if (result == talk_base::SR_BLOCK) {
+    m_bReadEnable = true;
+    m_error = EWOULDBLOCK;
+    return SOCKET_ERROR;
+  }
+  ASSERT(result == talk_base::SR_SUCCESS);
+
+  size_t available_space = 0;
+  m_rbuf.GetWriteRemaining(&available_space);
+
+  if (uint32(available_space) - m_rcv_wnd >=
+      talk_base::_min<uint32>(m_rbuf_len / 2, m_mss)) {
+    bool bWasClosed = (m_rcv_wnd == 0); // !?! Not sure about this was closed business
+    m_rcv_wnd = available_space;
+
+    if (bWasClosed) {
+      attemptSend(sfImmediateAck);
+    }
+  }
+
+  return read;
+}
+
+int PseudoTcp::Send(const char* buffer, size_t len) {
+  if (m_state != TCP_ESTABLISHED) {
+    m_error = ENOTCONN;
+    return SOCKET_ERROR;
+  }
+
+  size_t available_space = 0;
+  m_sbuf.GetWriteRemaining(&available_space);
+
+  if (!available_space) {
+    m_bWriteEnable = true;
+    m_error = EWOULDBLOCK;
+    return SOCKET_ERROR;
+  }
+
+  int written = queue(buffer, uint32(len), false);
+  attemptSend();
+  return written;
+}
+
+void PseudoTcp::Close(bool force) {
+  LOG_F(LS_VERBOSE) << "(" << (force ? "true" : "false") << ")";
+  m_shutdown = force ? SD_FORCEFUL : SD_GRACEFUL;
+}
+
+int PseudoTcp::GetError() {
+  return m_error;
+}
+
+//
+// Internal Implementation
+//
+
+uint32 PseudoTcp::queue(const char* data, uint32 len, bool bCtrl) {
+  size_t available_space = 0;
+  m_sbuf.GetWriteRemaining(&available_space);
+
+  if (len > static_cast<uint32>(available_space)) {
+    ASSERT(!bCtrl);
+    len = static_cast<uint32>(available_space);
+  }
+
+  // We can concatenate data if the last segment is the same type
+  // (control v. regular data), and has not been transmitted yet
+  if (!m_slist.empty() && (m_slist.back().bCtrl == bCtrl) && (m_slist.back().xmit == 0)) {
+    m_slist.back().len += len;
+  } else {
+    size_t snd_buffered = 0;
+    m_sbuf.GetBuffered(&snd_buffered);
+    SSegment sseg(m_snd_una + snd_buffered, len, bCtrl);
+    m_slist.push_back(sseg);
+  }
+
+  size_t written = 0;
+  m_sbuf.Write(data, len, &written, NULL);
+  return written;
+}
+
+IPseudoTcpNotify::WriteResult PseudoTcp::packet(uint32 seq, uint8 flags,
+                                                uint32 offset, uint32 len) {
+  ASSERT(HEADER_SIZE + len <= MAX_PACKET);
+
+  uint32 now = Now();
+
+  uint8 buffer[MAX_PACKET];
+  long_to_bytes(m_conv, buffer);
+  long_to_bytes(seq, buffer + 4);
+  long_to_bytes(m_rcv_nxt, buffer + 8);
+  buffer[12] = 0;
+  buffer[13] = flags;
+  short_to_bytes(static_cast<uint16>(m_rcv_wnd >> m_rwnd_scale), buffer + 14);
+
+  // Timestamp computations
+  long_to_bytes(now, buffer + 16);
+  long_to_bytes(m_ts_recent, buffer + 20);
+  m_ts_lastack = m_rcv_nxt;
+
+  if (len) {
+    size_t bytes_read = 0;
+    talk_base::StreamResult result = m_sbuf.ReadOffset(buffer + HEADER_SIZE,
+                                                       len,
+                                                       offset,
+                                                       &bytes_read);
+    UNUSED(result);
+    ASSERT(result == talk_base::SR_SUCCESS);
+    ASSERT(static_cast<uint32>(bytes_read) == len);
+  }
+
+#if _DEBUGMSG >= _DBG_VERBOSE
+  LOG(LS_INFO) << "<-- <CONV=" << m_conv
+               << "><FLG=" << static_cast<unsigned>(flags)
+               << "><SEQ=" << seq << ":" << seq + len
+               << "><ACK=" << m_rcv_nxt
+               << "><WND=" << m_rcv_wnd
+               << "><TS="  << (now % 10000)
+               << "><TSR=" << (m_ts_recent % 10000)
+               << "><LEN=" << len << ">";
+#endif // _DEBUGMSG
+
+  IPseudoTcpNotify::WriteResult wres = m_notify->TcpWritePacket(this, reinterpret_cast<char *>(buffer), len + HEADER_SIZE);
+  // Note: When len is 0, this is an ACK packet.  We don't read the return value for those,
+  // and thus we won't retry.  So go ahead and treat the packet as a success (basically simulate
+  // as if it were dropped), which will prevent our timers from being messed up.
+  if ((wres != IPseudoTcpNotify::WR_SUCCESS) && (0 != len))
+    return wres;
+
+  m_t_ack = 0;
+  if (len > 0) {
+    m_lastsend = now;
+  }
+  m_lasttraffic = now;
+  m_bOutgoing = true;
+
+  return IPseudoTcpNotify::WR_SUCCESS;
+}
+
+bool PseudoTcp::parse(const uint8* buffer, uint32 size) {
+  if (size < 12)
+    return false;
+
+  Segment seg;
+  seg.conv = bytes_to_long(buffer);
+  seg.seq = bytes_to_long(buffer + 4);
+  seg.ack = bytes_to_long(buffer + 8);
+  seg.flags = buffer[13];
+  seg.wnd = bytes_to_short(buffer + 14);
+
+  seg.tsval = bytes_to_long(buffer + 16);
+  seg.tsecr = bytes_to_long(buffer + 20);
+
+  seg.data = reinterpret_cast<const char *>(buffer) + HEADER_SIZE;
+  seg.len = size - HEADER_SIZE;
+
+#if _DEBUGMSG >= _DBG_VERBOSE
+  LOG(LS_INFO) << "--> <CONV=" << seg.conv
+               << "><FLG=" << static_cast<unsigned>(seg.flags)
+               << "><SEQ=" << seg.seq << ":" << seg.seq + seg.len
+               << "><ACK=" << seg.ack
+               << "><WND=" << seg.wnd
+               << "><TS="  << (seg.tsval % 10000)
+               << "><TSR=" << (seg.tsecr % 10000)
+               << "><LEN=" << seg.len << ">";
+#endif // _DEBUGMSG
+
+  return process(seg);
+}
+
+bool PseudoTcp::clock_check(uint32 now, long& nTimeout) {
+  if (m_shutdown == SD_FORCEFUL)
+    return false;
+
+  size_t snd_buffered = 0;
+  m_sbuf.GetBuffered(&snd_buffered);
+  if ((m_shutdown == SD_GRACEFUL)
+      && ((m_state != TCP_ESTABLISHED)
+          || ((snd_buffered == 0) && (m_t_ack == 0)))) {
+    return false;
+  }
+
+  if (m_state == TCP_CLOSED) {
+    nTimeout = CLOSED_TIMEOUT;
+    return true;
+  }
+
+  nTimeout = DEFAULT_TIMEOUT;
+
+  if (m_t_ack) {
+    nTimeout = talk_base::_min<int32>(nTimeout,
+      talk_base::TimeDiff(m_t_ack + m_ack_delay, now));
+  }
+  if (m_rto_base) {
+    nTimeout = talk_base::_min<int32>(nTimeout,
+      talk_base::TimeDiff(m_rto_base + m_rx_rto, now));
+  }
+  if (m_snd_wnd == 0) {
+    nTimeout = talk_base::_min<int32>(nTimeout, talk_base::TimeDiff(m_lastsend + m_rx_rto, now));
+  }
+#if PSEUDO_KEEPALIVE
+  if (m_state == TCP_ESTABLISHED) {
+    nTimeout = talk_base::_min<int32>(nTimeout,
+      talk_base::TimeDiff(m_lasttraffic + (m_bOutgoing ? IDLE_PING * 3/2 : IDLE_PING), now));
+  }
+#endif // PSEUDO_KEEPALIVE
+  return true;
+}
+
+bool PseudoTcp::process(Segment& seg) {
+  // If this is the wrong conversation, send a reset!?! (with the correct conversation?)
+  if (seg.conv != m_conv) {
+    //if ((seg.flags & FLAG_RST) == 0) {
+    //  packet(tcb, seg.ack, 0, FLAG_RST, 0, 0);
+    //}
+    LOG_F(LS_ERROR) << "wrong conversation";
+    return false;
+  }
+
+  uint32 now = Now();
+  m_lasttraffic = m_lastrecv = now;
+  m_bOutgoing = false;
+
+  if (m_state == TCP_CLOSED) {
+    // !?! send reset?
+    LOG_F(LS_ERROR) << "closed";
+    return false;
+  }
+
+  // Check if this is a reset segment
+  if (seg.flags & FLAG_RST) {
+    closedown(ECONNRESET);
+    return false;
+  }
+
+  // Check for control data
+  bool bConnect = false;
+  if (seg.flags & FLAG_CTL) {
+    if (seg.len == 0) {
+      LOG_F(LS_ERROR) << "Missing control code";
+      return false;
+    } else if (seg.data[0] == CTL_CONNECT) {
+      bConnect = true;
+
+      // TCP options are in the remainder of the payload after CTL_CONNECT.
+      parseOptions(&seg.data[1], seg.len - 1);
+
+      if (m_state == TCP_LISTEN) {
+        m_state = TCP_SYN_RECEIVED;
+        LOG(LS_INFO) << "State: TCP_SYN_RECEIVED";
+        //m_notify->associate(addr);
+        queueConnectMessage();
+      } else if (m_state == TCP_SYN_SENT) {
+        m_state = TCP_ESTABLISHED;
+        LOG(LS_INFO) << "State: TCP_ESTABLISHED";
+        adjustMTU();
+        if (m_notify) {
+          m_notify->OnTcpOpen(this);
+        }
+        //notify(evOpen);
+      }
+    } else {
+      LOG_F(LS_WARNING) << "Unknown control code: " << seg.data[0];
+      return false;
+    }
+  }
+
+  // Update timestamp
+  if ((seg.seq <= m_ts_lastack) && (m_ts_lastack < seg.seq + seg.len)) {
+    m_ts_recent = seg.tsval;
+  }
+
+  // Check if this is a valuable ack
+  if ((seg.ack > m_snd_una) && (seg.ack <= m_snd_nxt)) {
+    // Calculate round-trip time
+    if (seg.tsecr) {
+      long rtt = talk_base::TimeDiff(now, seg.tsecr);
+      if (rtt >= 0) {
+        if (m_rx_srtt == 0) {
+          m_rx_srtt = rtt;
+          m_rx_rttvar = rtt / 2;
+        } else {
+          m_rx_rttvar = (3 * m_rx_rttvar + abs(long(rtt - m_rx_srtt))) / 4;
+          m_rx_srtt = (7 * m_rx_srtt + rtt) / 8;
+        }
+        m_rx_rto = bound(MIN_RTO, m_rx_srtt +
+            talk_base::_max<uint32>(1, 4 * m_rx_rttvar), MAX_RTO);
+#if _DEBUGMSG >= _DBG_VERBOSE
+        LOG(LS_INFO) << "rtt: " << rtt
+                     << "  srtt: " << m_rx_srtt
+                     << "  rto: " << m_rx_rto;
+#endif // _DEBUGMSG
+      } else {
+        ASSERT(false);
+      }
+    }
+
+    m_snd_wnd = static_cast<uint32>(seg.wnd) << m_swnd_scale;
+
+    uint32 nAcked = seg.ack - m_snd_una;
+    m_snd_una = seg.ack;
+
+    m_rto_base = (m_snd_una == m_snd_nxt) ? 0 : now;
+
+    m_sbuf.ConsumeReadData(nAcked);
+
+    for (uint32 nFree = nAcked; nFree > 0; ) {
+      ASSERT(!m_slist.empty());
+      if (nFree < m_slist.front().len) {
+        m_slist.front().len -= nFree;
+        nFree = 0;
+      } else {
+        if (m_slist.front().len > m_largest) {
+          m_largest = m_slist.front().len;
+        }
+        nFree -= m_slist.front().len;
+        m_slist.pop_front();
+      }
+    }
+
+    if (m_dup_acks >= 3) {
+      if (m_snd_una >= m_recover) { // NewReno
+        uint32 nInFlight = m_snd_nxt - m_snd_una;
+        m_cwnd = talk_base::_min(m_ssthresh, nInFlight + m_mss); // (Fast Retransmit)
+#if _DEBUGMSG >= _DBG_NORMAL
+        LOG(LS_INFO) << "exit recovery";
+#endif // _DEBUGMSG
+        m_dup_acks = 0;
+      } else {
+#if _DEBUGMSG >= _DBG_NORMAL
+        LOG(LS_INFO) << "recovery retransmit";
+#endif // _DEBUGMSG
+        if (!transmit(m_slist.begin(), now)) {
+          closedown(ECONNABORTED);
+          return false;
+        }
+        m_cwnd += m_mss - talk_base::_min(nAcked, m_cwnd);
+      }
+    } else {
+      m_dup_acks = 0;
+      // Slow start, congestion avoidance
+      if (m_cwnd < m_ssthresh) {
+        m_cwnd += m_mss;
+      } else {
+        m_cwnd += talk_base::_max<uint32>(1, m_mss * m_mss / m_cwnd);
+      }
+    }
+  } else if (seg.ack == m_snd_una) {
+    // !?! Note, tcp says don't do this... but otherwise how does a closed window become open?
+    m_snd_wnd = static_cast<uint32>(seg.wnd) << m_swnd_scale;
+
+    // Check duplicate acks
+    if (seg.len > 0) {
+      // it's a dup ack, but with a data payload, so don't modify m_dup_acks
+    } else if (m_snd_una != m_snd_nxt) {
+      m_dup_acks += 1;
+      if (m_dup_acks == 3) { // (Fast Retransmit)
+#if _DEBUGMSG >= _DBG_NORMAL
+        LOG(LS_INFO) << "enter recovery";
+        LOG(LS_INFO) << "recovery retransmit";
+#endif // _DEBUGMSG
+        if (!transmit(m_slist.begin(), now)) {
+          closedown(ECONNABORTED);
+          return false;
+        }
+        m_recover = m_snd_nxt;
+        uint32 nInFlight = m_snd_nxt - m_snd_una;
+        m_ssthresh = talk_base::_max(nInFlight / 2, 2 * m_mss);
+        //LOG(LS_INFO) << "m_ssthresh: " << m_ssthresh << "  nInFlight: " << nInFlight << "  m_mss: " << m_mss;
+        m_cwnd = m_ssthresh + 3 * m_mss;
+      } else if (m_dup_acks > 3) {
+        m_cwnd += m_mss;
+      }
+    } else {
+      m_dup_acks = 0;
+    }
+  }
+
+  // !?! A bit hacky
+  if ((m_state == TCP_SYN_RECEIVED) && !bConnect) {
+    m_state = TCP_ESTABLISHED;
+    LOG(LS_INFO) << "State: TCP_ESTABLISHED";
+    adjustMTU();
+    if (m_notify) {
+      m_notify->OnTcpOpen(this);
+    }
+    //notify(evOpen);
+  }
+
+  // If we make room in the send queue, notify the user
+  // The goal it to make sure we always have at least enough data to fill the
+  // window.  We'd like to notify the app when we are halfway to that point.
+  const uint32 kIdealRefillSize = (m_sbuf_len + m_rbuf_len) / 2;
+  size_t snd_buffered = 0;
+  m_sbuf.GetBuffered(&snd_buffered);
+  if (m_bWriteEnable && static_cast<uint32>(snd_buffered) < kIdealRefillSize) {
+    m_bWriteEnable = false;
+    if (m_notify) {
+      m_notify->OnTcpWriteable(this);
+    }
+    //notify(evWrite);
+  }
+
+  // Conditions were acks must be sent:
+  // 1) Segment is too old (they missed an ACK) (immediately)
+  // 2) Segment is too new (we missed a segment) (immediately)
+  // 3) Segment has data (so we need to ACK!) (delayed)
+  // ... so the only time we don't need to ACK, is an empty segment that points to rcv_nxt!
+
+  SendFlags sflags = sfNone;
+  if (seg.seq != m_rcv_nxt) {
+    sflags = sfImmediateAck; // (Fast Recovery)
+  } else if (seg.len != 0) {
+    if (m_ack_delay == 0) {
+      sflags = sfImmediateAck;
+    } else {
+      sflags = sfDelayedAck;
+    }
+  }
+#if _DEBUGMSG >= _DBG_NORMAL
+  if (sflags == sfImmediateAck) {
+    if (seg.seq > m_rcv_nxt) {
+      LOG_F(LS_INFO) << "too new";
+    } else if (seg.seq + seg.len <= m_rcv_nxt) {
+      LOG_F(LS_INFO) << "too old";
+    }
+  }
+#endif // _DEBUGMSG
+
+  // Adjust the incoming segment to fit our receive buffer
+  if (seg.seq < m_rcv_nxt) {
+    uint32 nAdjust = m_rcv_nxt - seg.seq;
+    if (nAdjust < seg.len) {
+      seg.seq += nAdjust;
+      seg.data += nAdjust;
+      seg.len -= nAdjust;
+    } else {
+      seg.len = 0;
+    }
+  }
+
+  size_t available_space = 0;
+  m_rbuf.GetWriteRemaining(&available_space);
+
+  if ((seg.seq + seg.len - m_rcv_nxt) > static_cast<uint32>(available_space)) {
+    uint32 nAdjust = seg.seq + seg.len - m_rcv_nxt - static_cast<uint32>(available_space);
+    if (nAdjust < seg.len) {
+      seg.len -= nAdjust;
+    } else {
+      seg.len = 0;
+    }
+  }
+
+  bool bIgnoreData = (seg.flags & FLAG_CTL) || (m_shutdown != SD_NONE);
+  bool bNewData = false;
+
+  if (seg.len > 0) {
+    if (bIgnoreData) {
+      if (seg.seq == m_rcv_nxt) {
+        m_rcv_nxt += seg.len;
+      }
+    } else {
+      uint32 nOffset = seg.seq - m_rcv_nxt;
+
+      talk_base::StreamResult result = m_rbuf.WriteOffset(seg.data, seg.len,
+                                                          nOffset, NULL);
+      ASSERT(result == talk_base::SR_SUCCESS);
+      UNUSED(result);
+
+      if (seg.seq == m_rcv_nxt) {
+        m_rbuf.ConsumeWriteBuffer(seg.len);
+        m_rcv_nxt += seg.len;
+        m_rcv_wnd -= seg.len;
+        bNewData = true;
+
+        RList::iterator it = m_rlist.begin();
+        while ((it != m_rlist.end()) && (it->seq <= m_rcv_nxt)) {
+          if (it->seq + it->len > m_rcv_nxt) {
+            sflags = sfImmediateAck; // (Fast Recovery)
+            uint32 nAdjust = (it->seq + it->len) - m_rcv_nxt;
+#if _DEBUGMSG >= _DBG_NORMAL
+            LOG(LS_INFO) << "Recovered " << nAdjust << " bytes (" << m_rcv_nxt << " -> " << m_rcv_nxt + nAdjust << ")";
+#endif // _DEBUGMSG
+            m_rbuf.ConsumeWriteBuffer(nAdjust);
+            m_rcv_nxt += nAdjust;
+            m_rcv_wnd -= nAdjust;
+          }
+          it = m_rlist.erase(it);
+        }
+      } else {
+#if _DEBUGMSG >= _DBG_NORMAL
+        LOG(LS_INFO) << "Saving " << seg.len << " bytes (" << seg.seq << " -> " << seg.seq + seg.len << ")";
+#endif // _DEBUGMSG
+        RSegment rseg;
+        rseg.seq = seg.seq;
+        rseg.len = seg.len;
+        RList::iterator it = m_rlist.begin();
+        while ((it != m_rlist.end()) && (it->seq < rseg.seq)) {
+          ++it;
+        }
+        m_rlist.insert(it, rseg);
+      }
+    }
+  }
+
+  attemptSend(sflags);
+
+  // If we have new data, notify the user
+  if (bNewData && m_bReadEnable) {
+    m_bReadEnable = false;
+    if (m_notify) {
+      m_notify->OnTcpReadable(this);
+    }
+    //notify(evRead);
+  }
+
+  return true;
+}
+
+bool PseudoTcp::transmit(const SList::iterator& seg, uint32 now) {
+  if (seg->xmit >= ((m_state == TCP_ESTABLISHED) ? 15 : 30)) {
+    LOG_F(LS_VERBOSE) << "too many retransmits";
+    return false;
+  }
+
+  uint32 nTransmit = talk_base::_min(seg->len, m_mss);
+
+  while (true) {
+    uint32 seq = seg->seq;
+    uint8 flags = (seg->bCtrl ? FLAG_CTL : 0);
+    IPseudoTcpNotify::WriteResult wres = packet(seq,
+                                                flags,
+                                                seg->seq - m_snd_una,
+                                                nTransmit);
+
+    if (wres == IPseudoTcpNotify::WR_SUCCESS)
+      break;
+
+    if (wres == IPseudoTcpNotify::WR_FAIL) {
+      LOG_F(LS_VERBOSE) << "packet failed";
+      return false;
+    }
+
+    ASSERT(wres == IPseudoTcpNotify::WR_TOO_LARGE);
+
+    while (true) {
+      if (PACKET_MAXIMUMS[m_msslevel + 1] == 0) {
+        LOG_F(LS_VERBOSE) << "MTU too small";
+        return false;
+      }
+      // !?! We need to break up all outstanding and pending packets and then retransmit!?!
+
+      m_mss = PACKET_MAXIMUMS[++m_msslevel] - PACKET_OVERHEAD;
+      m_cwnd = 2 * m_mss; // I added this... haven't researched actual formula
+      if (m_mss < nTransmit) {
+        nTransmit = m_mss;
+        break;
+      }
+    }
+#if _DEBUGMSG >= _DBG_NORMAL
+    LOG(LS_INFO) << "Adjusting mss to " << m_mss << " bytes";
+#endif // _DEBUGMSG
+  }
+
+  if (nTransmit < seg->len) {
+    LOG_F(LS_VERBOSE) << "mss reduced to " << m_mss;
+
+    SSegment subseg(seg->seq + nTransmit, seg->len - nTransmit, seg->bCtrl);
+    //subseg.tstamp = seg->tstamp;
+    subseg.xmit = seg->xmit;
+    seg->len = nTransmit;
+
+    SList::iterator next = seg;
+    m_slist.insert(++next, subseg);
+  }
+
+  if (seg->xmit == 0) {
+    m_snd_nxt += seg->len;
+  }
+  seg->xmit += 1;
+  //seg->tstamp = now;
+  if (m_rto_base == 0) {
+    m_rto_base = now;
+  }
+
+  return true;
+}
+
+void PseudoTcp::attemptSend(SendFlags sflags) {
+  uint32 now = Now();
+
+  if (talk_base::TimeDiff(now, m_lastsend) > static_cast<long>(m_rx_rto)) {
+    m_cwnd = m_mss;
+  }
+
+#if _DEBUGMSG
+  bool bFirst = true;
+  UNUSED(bFirst);
+#endif // _DEBUGMSG
+
+  while (true) {
+    uint32 cwnd = m_cwnd;
+    if ((m_dup_acks == 1) || (m_dup_acks == 2)) { // Limited Transmit
+      cwnd += m_dup_acks * m_mss;
+    }
+    uint32 nWindow = talk_base::_min(m_snd_wnd, cwnd);
+    uint32 nInFlight = m_snd_nxt - m_snd_una;
+    uint32 nUseable = (nInFlight < nWindow) ? (nWindow - nInFlight) : 0;
+
+    size_t snd_buffered = 0;
+    m_sbuf.GetBuffered(&snd_buffered);
+    uint32 nAvailable =
+        talk_base::_min(static_cast<uint32>(snd_buffered) - nInFlight, m_mss);
+
+    if (nAvailable > nUseable) {
+      if (nUseable * 4 < nWindow) {
+        // RFC 813 - avoid SWS
+        nAvailable = 0;
+      } else {
+        nAvailable = nUseable;
+      }
+    }
+
+#if _DEBUGMSG >= _DBG_VERBOSE
+    if (bFirst) {
+      size_t available_space = 0;
+      m_sbuf.GetWriteRemaining(&available_space);
+
+      bFirst = false;
+      LOG(LS_INFO) << "[cwnd: " << m_cwnd
+                   << "  nWindow: " << nWindow
+                   << "  nInFlight: " << nInFlight
+                   << "  nAvailable: " << nAvailable
+                   << "  nQueued: " << snd_buffered
+                   << "  nEmpty: " << available_space
+                   << "  ssthresh: " << m_ssthresh << "]";
+    }
+#endif // _DEBUGMSG
+
+    if (nAvailable == 0) {
+      if (sflags == sfNone)
+        return;
+
+      // If this is an immediate ack, or the second delayed ack
+      if ((sflags == sfImmediateAck) || m_t_ack) {
+        packet(m_snd_nxt, 0, 0, 0);
+      } else {
+        m_t_ack = Now();
+      }
+      return;
+    }
+
+    // Nagle's algorithm.
+    // If there is data already in-flight, and we haven't a full segment of
+    // data ready to send then hold off until we get more to send, or the
+    // in-flight data is acknowledged.
+    if (m_use_nagling && (m_snd_nxt > m_snd_una) && (nAvailable < m_mss))  {
+      return;
+    }
+
+    // Find the next segment to transmit
+    SList::iterator it = m_slist.begin();
+    while (it->xmit > 0) {
+      ++it;
+      ASSERT(it != m_slist.end());
+    }
+    SList::iterator seg = it;
+
+    // If the segment is too large, break it into two
+    if (seg->len > nAvailable) {
+      SSegment subseg(seg->seq + nAvailable, seg->len - nAvailable, seg->bCtrl);
+      seg->len = nAvailable;
+      m_slist.insert(++it, subseg);
+    }
+
+    if (!transmit(seg, now)) {
+      LOG_F(LS_VERBOSE) << "transmit failed";
+      // TODO: consider closing socket
+      return;
+    }
+
+    sflags = sfNone;
+  }
+}
+
+void
+PseudoTcp::closedown(uint32 err) {
+  LOG(LS_INFO) << "State: TCP_CLOSED";
+  m_state = TCP_CLOSED;
+  if (m_notify) {
+    m_notify->OnTcpClosed(this, err);
+  }
+  //notify(evClose, err);
+}
+
+void
+PseudoTcp::adjustMTU() {
+  // Determine our current mss level, so that we can adjust appropriately later
+  for (m_msslevel = 0; PACKET_MAXIMUMS[m_msslevel + 1] > 0; ++m_msslevel) {
+    if (static_cast<uint16>(PACKET_MAXIMUMS[m_msslevel]) <= m_mtu_advise) {
+      break;
+    }
+  }
+  m_mss = m_mtu_advise - PACKET_OVERHEAD;
+  // !?! Should we reset m_largest here?
+#if _DEBUGMSG >= _DBG_NORMAL
+  LOG(LS_INFO) << "Adjusting mss to " << m_mss << " bytes";
+#endif // _DEBUGMSG
+  // Enforce minimums on ssthresh and cwnd
+  m_ssthresh = talk_base::_max(m_ssthresh, 2 * m_mss);
+  m_cwnd = talk_base::_max(m_cwnd, m_mss);
+}
+
+bool
+PseudoTcp::isReceiveBufferFull() const {
+  size_t available_space = 0;
+  m_rbuf.GetWriteRemaining(&available_space);
+  return !available_space;
+}
+
+void
+PseudoTcp::disableWindowScale() {
+  m_support_wnd_scale = false;
+}
+
+void
+PseudoTcp::queueConnectMessage() {
+  talk_base::ByteBuffer buf(talk_base::ByteBuffer::ORDER_NETWORK);
+
+  buf.WriteUInt8(CTL_CONNECT);
+  if (m_support_wnd_scale) {
+    buf.WriteUInt8(TCP_OPT_WND_SCALE);
+    buf.WriteUInt8(1);
+    buf.WriteUInt8(m_rwnd_scale);
+  }
+  m_snd_wnd = buf.Length();
+  queue(buf.Data(), buf.Length(), true);
+}
+
+void
+PseudoTcp::parseOptions(const char* data, uint32 len) {
+  std::set<uint8> options_specified;
+
+  // See http://www.freesoft.org/CIE/Course/Section4/8.htm for
+  // parsing the options list.
+  talk_base::ByteBuffer buf(data, len);
+  while (buf.Length()) {
+    uint8 kind = TCP_OPT_EOL;
+    buf.ReadUInt8(&kind);
+
+    if (kind == TCP_OPT_EOL) {
+      // End of option list.
+      break;
+    } else if (kind == TCP_OPT_NOOP) {
+      // No op.
+      continue;
+    }
+
+    // Length of this option.
+    ASSERT(len != 0);
+    UNUSED(len);
+    uint8 opt_len = 0;
+    buf.ReadUInt8(&opt_len);
+
+    // Content of this option.
+    if (opt_len <= buf.Length()) {
+      applyOption(kind, buf.Data(), opt_len);
+      buf.Consume(opt_len);
+    } else {
+      LOG(LS_ERROR) << "Invalid option length received.";
+      return;
+    }
+    options_specified.insert(kind);
+  }
+
+  if (options_specified.find(TCP_OPT_WND_SCALE) == options_specified.end()) {
+    LOG(LS_WARNING) << "Peer doesn't support window scaling";
+
+    if (m_rwnd_scale > 0) {
+      // Peer doesn't support TCP options and window scaling.
+      // Revert receive buffer size to default value.
+      resizeReceiveBuffer(DEFAULT_RCV_BUF_SIZE);
+      m_swnd_scale = 0;
+    }
+  }
+}
+
+void
+PseudoTcp::applyOption(char kind, const char* data, uint32 len) {
+  if (kind == TCP_OPT_MSS) {
+    LOG(LS_WARNING) << "Peer specified MSS option which is not supported.";
+    // TODO: Implement.
+  } else if (kind == TCP_OPT_WND_SCALE) {
+    // Window scale factor.
+    // http://www.ietf.org/rfc/rfc1323.txt
+    if (len != 1) {
+      LOG_F(WARNING) << "Invalid window scale option received.";
+      return;
+    }
+    applyWindowScaleOption(data[0]);
+  }
+}
+
+void
+PseudoTcp::applyWindowScaleOption(uint8 scale_factor) {
+  m_swnd_scale = scale_factor;
+}
+
+void
+PseudoTcp::resizeSendBuffer(uint32 new_size) {
+  m_sbuf_len = new_size;
+  m_sbuf.SetCapacity(new_size);
+}
+
+void
+PseudoTcp::resizeReceiveBuffer(uint32 new_size) {
+  uint8 scale_factor = 0;
+
+  // Determine the scale factor such that the scaled window size can fit
+  // in a 16-bit unsigned integer.
+  while (new_size > 0xFFFF) {
+    ++scale_factor;
+    new_size >>= 1;
+  }
+
+  // Determine the proper size of the buffer.
+  new_size <<= scale_factor;
+  bool result = m_rbuf.SetCapacity(new_size);
+
+  // Make sure the new buffer is large enough to contain data in the old
+  // buffer. This should always be true because this method is called either
+  // before connection is established or when peers are exchanging connect
+  // messages.
+  ASSERT(result);
+  UNUSED(result);
+  m_rbuf_len = new_size;
+  m_rwnd_scale = scale_factor;
+  m_ssthresh = new_size;
+
+  size_t available_space = 0;
+  m_rbuf.GetWriteRemaining(&available_space);
+  m_rcv_wnd = available_space;
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/pseudotcp.h b/talk/p2p/base/pseudotcp.h
new file mode 100644
index 0000000..edd861b
--- /dev/null
+++ b/talk/p2p/base/pseudotcp.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 TALK_P2P_BASE_PSEUDOTCP_H_
+#define TALK_P2P_BASE_PSEUDOTCP_H_
+
+#include <list>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/stream.h"
+
+namespace cricket {
+
+//////////////////////////////////////////////////////////////////////
+// IPseudoTcpNotify
+//////////////////////////////////////////////////////////////////////
+
+class PseudoTcp;
+
+class IPseudoTcpNotify {
+ public:
+  // Notification of tcp events
+  virtual void OnTcpOpen(PseudoTcp* tcp) = 0;
+  virtual void OnTcpReadable(PseudoTcp* tcp) = 0;
+  virtual void OnTcpWriteable(PseudoTcp* tcp) = 0;
+  virtual void OnTcpClosed(PseudoTcp* tcp, uint32 error) = 0;
+
+  // Write the packet onto the network
+  enum WriteResult { WR_SUCCESS, WR_TOO_LARGE, WR_FAIL };
+  virtual WriteResult TcpWritePacket(PseudoTcp* tcp,
+                                     const char* buffer, size_t len) = 0;
+
+ protected:
+  virtual ~IPseudoTcpNotify() {}
+};
+
+//////////////////////////////////////////////////////////////////////
+// PseudoTcp
+//////////////////////////////////////////////////////////////////////
+
+class PseudoTcp {
+ public:
+  static uint32 Now();
+
+  PseudoTcp(IPseudoTcpNotify* notify, uint32 conv);
+  virtual ~PseudoTcp();
+
+  int Connect();
+  int Recv(char* buffer, size_t len);
+  int Send(const char* buffer, size_t len);
+  void Close(bool force);
+  int GetError();
+
+  enum TcpState {
+    TCP_LISTEN, TCP_SYN_SENT, TCP_SYN_RECEIVED, TCP_ESTABLISHED, TCP_CLOSED
+  };
+  TcpState State() const { return m_state; }
+
+  // Call this when the PMTU changes.
+  void NotifyMTU(uint16 mtu);
+
+  // Call this based on timeout value returned from GetNextClock.
+  // It's ok to call this too frequently.
+  void NotifyClock(uint32 now);
+
+  // Call this whenever a packet arrives.
+  // Returns true if the packet was processed successfully.
+  bool NotifyPacket(const char * buffer, size_t len);
+
+  // Call this to determine the next time NotifyClock should be called.
+  // Returns false if the socket is ready to be destroyed.
+  bool GetNextClock(uint32 now, long& timeout);
+
+  // Call these to get/set option values to tailor this PseudoTcp
+  // instance's behaviour for the kind of data it will carry.
+  // If an unrecognized option is set or got, an assertion will fire.
+  //
+  // Setting options for OPT_RCVBUF or OPT_SNDBUF after Connect() is called
+  // will result in an assertion.
+  enum Option {
+    OPT_NODELAY,      // Whether to enable Nagle's algorithm (0 == off)
+    OPT_ACKDELAY,     // The Delayed ACK timeout (0 == off).
+    OPT_RCVBUF,       // Set the receive buffer size, in bytes.
+    OPT_SNDBUF,       // Set the send buffer size, in bytes.
+  };
+  void GetOption(Option opt, int* value);
+  void SetOption(Option opt, int value);
+
+  // Returns current congestion window in bytes.
+  uint32 GetCongestionWindow() const;
+
+  // Returns amount of data in bytes that has been sent, but haven't
+  // been acknowledged.
+  uint32 GetBytesInFlight() const;
+
+  // Returns number of bytes that were written in buffer and haven't
+  // been sent.
+  uint32 GetBytesBufferedNotSent() const;
+
+  // Returns current round-trip time estimate in milliseconds.
+  uint32 GetRoundTripTimeEstimateMs() const;
+
+ protected:
+  enum SendFlags { sfNone, sfDelayedAck, sfImmediateAck };
+
+  struct Segment {
+    uint32 conv, seq, ack;
+    uint8 flags;
+    uint16 wnd;
+    const char * data;
+    uint32 len;
+    uint32 tsval, tsecr;
+  };
+
+  struct SSegment {
+    SSegment(uint32 s, uint32 l, bool c)
+        : seq(s), len(l), /*tstamp(0),*/ xmit(0), bCtrl(c) {
+    }
+    uint32 seq, len;
+    //uint32 tstamp;
+    uint8 xmit;
+    bool bCtrl;
+  };
+  typedef std::list<SSegment> SList;
+
+  struct RSegment {
+    uint32 seq, len;
+  };
+
+  uint32 queue(const char* data, uint32 len, bool bCtrl);
+
+  // Creates a packet and submits it to the network. This method can either
+  // send payload or just an ACK packet.
+  //
+  // |seq| is the sequence number of this packet.
+  // |flags| is the flags for sending this packet.
+  // |offset| is the offset to read from |m_sbuf|.
+  // |len| is the number of bytes to read from |m_sbuf| as payload. If this
+  // value is 0 then this is an ACK packet, otherwise this packet has payload.
+  IPseudoTcpNotify::WriteResult packet(uint32 seq, uint8 flags,
+                                       uint32 offset, uint32 len);
+  bool parse(const uint8* buffer, uint32 size);
+
+  void attemptSend(SendFlags sflags = sfNone);
+
+  void closedown(uint32 err = 0);
+
+  bool clock_check(uint32 now, long& nTimeout);
+
+  bool process(Segment& seg);
+  bool transmit(const SList::iterator& seg, uint32 now);
+
+  void adjustMTU();
+
+ protected:
+  // This method is used in test only to query receive buffer state.
+  bool isReceiveBufferFull() const;
+
+  // This method is only used in tests, to disable window scaling
+  // support for testing backward compatibility.
+  void disableWindowScale();
+
+ private:
+  // Queue the connect message with TCP options.
+  void queueConnectMessage();
+
+  // Parse TCP options in the header.
+  void parseOptions(const char* data, uint32 len);
+
+  // Apply a TCP option that has been read from the header.
+  void applyOption(char kind, const char* data, uint32 len);
+
+  // Apply window scale option.
+  void applyWindowScaleOption(uint8 scale_factor);
+
+  // Resize the send buffer with |new_size| in bytes.
+  void resizeSendBuffer(uint32 new_size);
+
+  // Resize the receive buffer with |new_size| in bytes. This call adjusts
+  // window scale factor |m_swnd_scale| accordingly.
+  void resizeReceiveBuffer(uint32 new_size);
+
+  IPseudoTcpNotify* m_notify;
+  enum Shutdown { SD_NONE, SD_GRACEFUL, SD_FORCEFUL } m_shutdown;
+  int m_error;
+
+  // TCB data
+  TcpState m_state;
+  uint32 m_conv;
+  bool m_bReadEnable, m_bWriteEnable, m_bOutgoing;
+  uint32 m_lasttraffic;
+
+  // Incoming data
+  typedef std::list<RSegment> RList;
+  RList m_rlist;
+  uint32 m_rbuf_len, m_rcv_nxt, m_rcv_wnd, m_lastrecv;
+  uint8 m_rwnd_scale;  // Window scale factor.
+  talk_base::FifoBuffer m_rbuf;
+
+  // Outgoing data
+  SList m_slist;
+  uint32 m_sbuf_len, m_snd_nxt, m_snd_wnd, m_lastsend, m_snd_una;
+  uint8 m_swnd_scale;  // Window scale factor.
+  talk_base::FifoBuffer m_sbuf;
+
+  // Maximum segment size, estimated protocol level, largest segment sent
+  uint32 m_mss, m_msslevel, m_largest, m_mtu_advise;
+  // Retransmit timer
+  uint32 m_rto_base;
+
+  // Timestamp tracking
+  uint32 m_ts_recent, m_ts_lastack;
+
+  // Round-trip calculation
+  uint32 m_rx_rttvar, m_rx_srtt, m_rx_rto;
+
+  // Congestion avoidance, Fast retransmit/recovery, Delayed ACKs
+  uint32 m_ssthresh, m_cwnd;
+  uint8 m_dup_acks;
+  uint32 m_recover;
+  uint32 m_t_ack;
+
+  // Configuration options
+  bool m_use_nagling;
+  uint32 m_ack_delay;
+
+  // This is used by unit tests to test backward compatibility of
+  // PseudoTcp implementations that don't support window scaling.
+  bool m_support_wnd_scale;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_PSEUDOTCP_H_
diff --git a/talk/p2p/base/pseudotcp_unittest.cc b/talk/p2p/base/pseudotcp_unittest.cc
new file mode 100644
index 0000000..09eac16
--- /dev/null
+++ b/talk/p2p/base/pseudotcp_unittest.cc
@@ -0,0 +1,857 @@
+/*
+ * 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 = EstimateReceiveWindowSize();
+
+    // 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);
+    // At this point, we've filled up the available space in the send queue.
+
+    int message_queue_size = talk_base::Thread::Current()->size();
+    // The message queue will always have at least 2 messages, an RCLOCK and
+    // an LCLOCK, since they are added back on the delay queue at the same time
+    // they are pulled off and therefore are never really removed.
+    if (message_queue_size > 2) {
+      // If there are non-clock messages remaining, attempt to continue sending
+      // after giving those messages time to process, which should free up the
+      // send buffer.
+      talk_base::Thread::Current()->PostDelayed(10, this, MSG_WRITE);
+    } else {
+      if (!remote_.isReceiveBufferFull()) {
+        LOG(LS_ERROR) << "This shouldn't happen - the send buffer is full, "
+                      << "the receive buffer is not, and there are no "
+                      << "remaining messages to process.";
+      }
+      send_stream_.GetPosition(&position);
+      send_position_.push_back(position);
+
+      // Drain the receiver buffer.
+      ReadUntilIOPending();
+    }
+  }
+
+ 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/rawtransport.cc b/talk/p2p/base/rawtransport.cc
new file mode 100644
index 0000000..fe4f3a2
--- /dev/null
+++ b/talk/p2p/base/rawtransport.cc
@@ -0,0 +1,132 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+#include <vector>
+#include "talk/p2p/base/rawtransport.h"
+#include "talk/base/common.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/parsing.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/base/rawtransportchannel.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+
+#if defined(FEATURE_ENABLE_PSTN)
+namespace cricket {
+
+RawTransport::RawTransport(talk_base::Thread* signaling_thread,
+                           talk_base::Thread* worker_thread,
+                           const std::string& content_name,
+                           PortAllocator* allocator)
+    : Transport(signaling_thread, worker_thread,
+                content_name, NS_GINGLE_RAW, allocator) {
+}
+
+RawTransport::~RawTransport() {
+  DestroyAllChannels();
+}
+
+bool RawTransport::ParseCandidates(SignalingProtocol protocol,
+                                   const buzz::XmlElement* elem,
+                                   const CandidateTranslator* translator,
+                                   Candidates* candidates,
+                                   ParseError* error) {
+  for (const buzz::XmlElement* cand_elem = elem->FirstElement();
+       cand_elem != NULL;
+       cand_elem = cand_elem->NextElement()) {
+    if (cand_elem->Name() == QN_GINGLE_RAW_CHANNEL) {
+      if (!cand_elem->HasAttr(buzz::QN_NAME)) {
+        return BadParse("no channel name given", error);
+      }
+      if (type() != cand_elem->Attr(buzz::QN_NAME)) {
+        return BadParse("channel named does not exist", error);
+      }
+      talk_base::SocketAddress addr;
+      if (!ParseRawAddress(cand_elem, &addr, error))
+        return false;
+
+      Candidate candidate;
+      candidate.set_component(1);
+      candidate.set_address(addr);
+      candidates->push_back(candidate);
+    }
+  }
+  return true;
+}
+
+bool RawTransport::WriteCandidates(SignalingProtocol protocol,
+                                   const Candidates& candidates,
+                                   const CandidateTranslator* translator,
+                                   XmlElements* candidate_elems,
+                                   WriteError* error) {
+  for (std::vector<Candidate>::const_iterator
+       cand = candidates.begin();
+       cand != candidates.end();
+       ++cand) {
+    ASSERT(cand->component() == 1);
+    ASSERT(cand->protocol() == "udp");
+    talk_base::SocketAddress addr = cand->address();
+
+    buzz::XmlElement* elem = new buzz::XmlElement(QN_GINGLE_RAW_CHANNEL);
+    elem->SetAttr(buzz::QN_NAME, type());
+    elem->SetAttr(QN_ADDRESS, addr.ipaddr().ToString());
+    elem->SetAttr(QN_PORT, addr.PortAsString());
+    candidate_elems->push_back(elem);
+  }
+  return true;
+}
+
+bool RawTransport::ParseRawAddress(const buzz::XmlElement* elem,
+                                   talk_base::SocketAddress* addr,
+                                   ParseError* error) {
+  // Make sure the required attributes exist
+  if (!elem->HasAttr(QN_ADDRESS) ||
+      !elem->HasAttr(QN_PORT)) {
+    return BadParse("channel missing required attribute", error);
+  }
+
+  // Parse the address.
+  if (!ParseAddress(elem, QN_ADDRESS, QN_PORT, addr, error))
+    return false;
+
+  return true;
+}
+
+TransportChannelImpl* RawTransport::CreateTransportChannel(int component) {
+  return new RawTransportChannel(content_name(), component, this,
+                                 worker_thread(),
+                                 port_allocator());
+}
+
+void RawTransport::DestroyTransportChannel(TransportChannelImpl* channel) {
+  delete channel;
+}
+
+}  // namespace cricket
+#endif  // defined(FEATURE_ENABLE_PSTN)
diff --git a/talk/p2p/base/rawtransport.h b/talk/p2p/base/rawtransport.h
new file mode 100644
index 0000000..6bb04fe
--- /dev/null
+++ b/talk/p2p/base/rawtransport.h
@@ -0,0 +1,81 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_RAWTRANSPORT_H_
+#define TALK_P2P_BASE_RAWTRANSPORT_H_
+
+#include <string>
+#include "talk/p2p/base/transport.h"
+
+#if defined(FEATURE_ENABLE_PSTN)
+namespace cricket {
+
+// Implements a transport that only sends raw packets, no STUN.  As a result,
+// it cannot do pings to determine connectivity, so it only uses a single port
+// that it thinks will work.
+class RawTransport : public Transport, public TransportParser {
+ public:
+  RawTransport(talk_base::Thread* signaling_thread,
+               talk_base::Thread* worker_thread,
+               const std::string& content_name,
+               PortAllocator* allocator);
+  virtual ~RawTransport();
+
+  virtual bool ParseCandidates(SignalingProtocol protocol,
+                               const buzz::XmlElement* elem,
+                               const CandidateTranslator* translator,
+                               Candidates* candidates,
+                               ParseError* error);
+  virtual bool WriteCandidates(SignalingProtocol protocol,
+                               const Candidates& candidates,
+                               const CandidateTranslator* translator,
+                               XmlElements* candidate_elems,
+                               WriteError* error);
+
+ protected:
+  // Creates and destroys raw channels.
+  virtual TransportChannelImpl* CreateTransportChannel(int component);
+  virtual void DestroyTransportChannel(TransportChannelImpl* channel);
+
+ private:
+  // Parses the given element, which should describe the address to use for a
+  // given channel.  This will return false and signal an error if the address
+  // or channel name is bad.
+  bool ParseRawAddress(const buzz::XmlElement* elem,
+                       talk_base::SocketAddress* addr,
+                       ParseError* error);
+
+  friend class RawTransportChannel;  // For ParseAddress.
+
+  DISALLOW_EVIL_CONSTRUCTORS(RawTransport);
+};
+
+}  // namespace cricket
+
+#endif  // defined(FEATURE_ENABLE_PSTN)
+
+#endif  // TALK_P2P_BASE_RAWTRANSPORT_H_
diff --git a/talk/p2p/base/rawtransportchannel.cc b/talk/p2p/base/rawtransportchannel.cc
new file mode 100644
index 0000000..54adab1
--- /dev/null
+++ b/talk/p2p/base/rawtransportchannel.cc
@@ -0,0 +1,275 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/p2p/base/rawtransportchannel.h"
+
+#include <string>
+#include <vector>
+#include "talk/base/common.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/portallocator.h"
+#include "talk/p2p/base/portinterface.h"
+#include "talk/p2p/base/rawtransport.h"
+#include "talk/p2p/base/relayport.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/base/stunport.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+
+#if defined(FEATURE_ENABLE_PSTN)
+
+namespace {
+
+const uint32 MSG_DESTROY_UNUSED_PORTS = 1;
+
+}  // namespace
+
+namespace cricket {
+
+RawTransportChannel::RawTransportChannel(const std::string& content_name,
+                                         int component,
+                                         RawTransport* transport,
+                                         talk_base::Thread *worker_thread,
+                                         PortAllocator *allocator)
+  : TransportChannelImpl(content_name, component),
+    raw_transport_(transport),
+    allocator_(allocator),
+    allocator_session_(NULL),
+    stun_port_(NULL),
+    relay_port_(NULL),
+    port_(NULL),
+    use_relay_(false) {
+  if (worker_thread == NULL)
+    worker_thread_ = raw_transport_->worker_thread();
+  else
+    worker_thread_ = worker_thread;
+}
+
+RawTransportChannel::~RawTransportChannel() {
+  delete allocator_session_;
+}
+
+int RawTransportChannel::SendPacket(const char *data, size_t size, int flags) {
+  if (port_ == NULL)
+    return -1;
+  if (remote_address_.IsNil())
+    return -1;
+  if (flags != 0)
+    return -1;
+  return port_->SendTo(data, size, remote_address_, true);
+}
+
+int RawTransportChannel::SetOption(talk_base::Socket::Option opt, int value) {
+  // TODO: allow these to be set before we have a port
+  if (port_ == NULL)
+    return -1;
+  return port_->SetOption(opt, value);
+}
+
+int RawTransportChannel::GetError() {
+  return (port_ != NULL) ? port_->GetError() : 0;
+}
+
+void RawTransportChannel::Connect() {
+  // Create an allocator that only returns stun and relay ports.
+  // Use empty string for ufrag and pwd here. There won't be any STUN or relay
+  // interactions when using RawTC.
+  // TODO: Change raw to only use local udp ports.
+  allocator_session_ = allocator_->CreateSession(
+      SessionId(), content_name(), component(), "", "");
+
+  uint32 flags = PORTALLOCATOR_DISABLE_UDP | PORTALLOCATOR_DISABLE_TCP;
+
+#if !defined(FEATURE_ENABLE_STUN_CLASSIFICATION)
+  flags |= PORTALLOCATOR_DISABLE_RELAY;
+#endif
+  allocator_session_->set_flags(flags);
+  allocator_session_->SignalPortReady.connect(
+      this, &RawTransportChannel::OnPortReady);
+  allocator_session_->SignalCandidatesReady.connect(
+      this, &RawTransportChannel::OnCandidatesReady);
+
+  // The initial ports will include stun.
+  allocator_session_->StartGettingPorts();
+}
+
+void RawTransportChannel::Reset() {
+  set_readable(false);
+  set_writable(false);
+
+  delete allocator_session_;
+
+  allocator_session_ = NULL;
+  stun_port_ = NULL;
+  relay_port_ = NULL;
+  port_ = NULL;
+  remote_address_ = talk_base::SocketAddress();
+}
+
+void RawTransportChannel::OnCandidate(const Candidate& candidate) {
+  remote_address_ = candidate.address();
+  ASSERT(!remote_address_.IsNil());
+  set_readable(true);
+
+  // We can write once we have a port and a remote address.
+  if (port_ != NULL)
+    SetWritable();
+}
+
+void RawTransportChannel::OnRemoteAddress(
+    const talk_base::SocketAddress& remote_address) {
+  remote_address_ = remote_address;
+  set_readable(true);
+
+  if (port_ != NULL)
+    SetWritable();
+}
+
+// Note about stun classification
+// Code to classify our NAT type and use the relay port if we are behind an
+// asymmetric NAT is under a FEATURE_ENABLE_STUN_CLASSIFICATION #define.
+// To turn this one we will have to enable a second stun address and make sure
+// that the relay server works for raw UDP.
+//
+// Another option is to classify the NAT type early and not offer the raw
+// transport type at all if we can't support it.
+
+void RawTransportChannel::OnPortReady(
+    PortAllocatorSession* session, PortInterface* port) {
+  ASSERT(session == allocator_session_);
+
+  if (port->Type() == STUN_PORT_TYPE) {
+    stun_port_ = static_cast<StunPort*>(port);
+  } else if (port->Type() == RELAY_PORT_TYPE) {
+    relay_port_ = static_cast<RelayPort*>(port);
+  } else {
+    ASSERT(false);
+  }
+}
+
+void RawTransportChannel::OnCandidatesReady(
+    PortAllocatorSession *session, const std::vector<Candidate>& candidates) {
+  ASSERT(session == allocator_session_);
+  ASSERT(candidates.size() >= 1);
+
+  // The most recent candidate is the one we haven't seen yet.
+  Candidate c = candidates[candidates.size() - 1];
+
+  if (c.type() == STUN_PORT_TYPE) {
+    ASSERT(stun_port_ != NULL);
+
+#if defined(FEATURE_ENABLE_STUN_CLASSIFICATION)
+    // We need to wait until we have two addresses.
+    if (stun_port_->candidates().size() < 2)
+      return;
+
+    // This is the second address.  If these addresses are the same, then we
+    // are not behind a symmetric NAT.  Hence, a stun port should be sufficient.
+    if (stun_port_->candidates()[0].address() ==
+        stun_port_->candidates()[1].address()) {
+      SetPort(stun_port_);
+      return;
+    }
+
+    // We will need to use relay.
+    use_relay_ = true;
+
+    // If we already have a relay address, we're good.  Otherwise, we will need
+    // to wait until one arrives.
+    if (relay_port_->candidates().size() > 0)
+      SetPort(relay_port_);
+#else  // defined(FEATURE_ENABLE_STUN_CLASSIFICATION)
+    // Always use the stun port.  We don't classify right now so just assume it
+    // will work fine.
+    SetPort(stun_port_);
+#endif
+  } else if (c.type() == RELAY_PORT_TYPE) {
+    if (use_relay_)
+      SetPort(relay_port_);
+  } else {
+    ASSERT(false);
+  }
+}
+
+void RawTransportChannel::SetPort(PortInterface* port) {
+  ASSERT(port_ == NULL);
+  port_ = port;
+
+  // We don't need any ports other than the one we picked.
+  allocator_session_->StopGettingPorts();
+  worker_thread_->Post(
+      this, MSG_DESTROY_UNUSED_PORTS, NULL);
+
+  // Send a message to the other client containing our address.
+
+  ASSERT(port_->Candidates().size() >= 1);
+  ASSERT(port_->Candidates()[0].protocol() == "udp");
+  SignalCandidateReady(this, port_->Candidates()[0]);
+
+  // Read all packets from this port.
+  port_->EnablePortPackets();
+  port_->SignalReadPacket.connect(this, &RawTransportChannel::OnReadPacket);
+
+  // We can write once we have a port and a remote address.
+  if (!remote_address_.IsAny())
+    SetWritable();
+}
+
+void RawTransportChannel::SetWritable() {
+  ASSERT(port_ != NULL);
+  ASSERT(!remote_address_.IsAny());
+
+  set_writable(true);
+
+  Candidate remote_candidate;
+  remote_candidate.set_address(remote_address_);
+  SignalRouteChange(this, remote_candidate);
+}
+
+void RawTransportChannel::OnReadPacket(
+    PortInterface* port, const char* data, size_t size,
+    const talk_base::SocketAddress& addr) {
+  ASSERT(port_ == port);
+  SignalReadPacket(this, data, size, 0);
+}
+
+void RawTransportChannel::OnMessage(talk_base::Message* msg) {
+  ASSERT(msg->message_id == MSG_DESTROY_UNUSED_PORTS);
+  ASSERT(port_ != NULL);
+  if (port_ != stun_port_) {
+    stun_port_->Destroy();
+    stun_port_ = NULL;
+  }
+  if (port_ != relay_port_ && relay_port_ != NULL) {
+    relay_port_->Destroy();
+    relay_port_ = NULL;
+  }
+}
+
+}  // namespace cricket
+#endif  // defined(FEATURE_ENABLE_PSTN)
diff --git a/talk/p2p/base/rawtransportchannel.h b/talk/p2p/base/rawtransportchannel.h
new file mode 100644
index 0000000..3c9c330
--- /dev/null
+++ b/talk/p2p/base/rawtransportchannel.h
@@ -0,0 +1,143 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_RAWTRANSPORTCHANNEL_H_
+#define TALK_P2P_BASE_RAWTRANSPORTCHANNEL_H_
+
+#include <string>
+#include <vector>
+#include "talk/base/messagequeue.h"
+#include "talk/p2p/base/transportchannelimpl.h"
+#include "talk/p2p/base/rawtransport.h"
+#include "talk/p2p/base/candidate.h"
+
+#if defined(FEATURE_ENABLE_PSTN)
+
+namespace talk_base {
+class Thread;
+}
+
+namespace cricket {
+
+class Connection;
+class PortAllocator;
+class PortAllocatorSession;
+class PortInterface;
+class RelayPort;
+class StunPort;
+
+// Implements a channel that just sends bare packets once we have received the
+// address of the other side.  We pick a single address to send them based on
+// a simple investigation of NAT type.
+class RawTransportChannel : public TransportChannelImpl,
+    public talk_base::MessageHandler {
+ public:
+  RawTransportChannel(const std::string& content_name,
+                      int component,
+                      RawTransport* transport,
+                      talk_base::Thread *worker_thread,
+                      PortAllocator *allocator);
+  virtual ~RawTransportChannel();
+
+  // Implementation of normal channel packet sending.
+  virtual int SendPacket(const char *data, size_t len, int flags);
+  virtual int SetOption(talk_base::Socket::Option opt, int value);
+  virtual int GetError();
+
+  // Implements TransportChannelImpl.
+  virtual Transport* GetTransport() { return raw_transport_; }
+  virtual void SetIceCredentials(const std::string& ice_ufrag,
+                                 const std::string& ice_pwd) {}
+  virtual void SetRemoteIceCredentials(const std::string& ice_ufrag,
+                                       const std::string& ice_pwd) {}
+  virtual TransportRole GetRole() const { return ROLE_UNKNOWN; }
+
+  // Creates an allocator session to start figuring out which type of
+  // port we should send to the other client.  This will send
+  // SignalAvailableCandidate once we have decided.
+  virtual void Connect();
+
+  // Resets state back to unconnected.
+  virtual void Reset();
+
+  // We don't actually worry about signaling since we can't send new candidates.
+  virtual void OnSignalingReady() {}
+
+  // Handles a message setting the remote address.  We are writable once we
+  // have this since we now know where to send.
+  virtual void OnCandidate(const Candidate& candidate);
+
+  void OnRemoteAddress(const talk_base::SocketAddress& remote_address);
+
+  // Below ICE specific virtual methods not implemented.
+  virtual void SetRole(TransportRole role) {}
+  virtual void SetTiebreaker(uint64 tiebreaker) {}
+  virtual void SetIceProtocolType(IceProtocolType type) {}
+  virtual void SetIceUfrag(const std::string& ice_ufrag) {}
+  virtual void SetIcePwd(const std::string& ice_pwd) {}
+  virtual void SetRemoteIceMode(IceMode mode) {}
+
+ private:
+  RawTransport* raw_transport_;
+  talk_base::Thread *worker_thread_;
+  PortAllocator* allocator_;
+  PortAllocatorSession* allocator_session_;
+  StunPort* stun_port_;
+  RelayPort* relay_port_;
+  PortInterface* port_;
+  bool use_relay_;
+  talk_base::SocketAddress remote_address_;
+
+  // Called when the allocator creates another port.
+  void OnPortReady(PortAllocatorSession* session, PortInterface* port);
+
+  // Called when one of the ports we are using has determined its address.
+  void OnCandidatesReady(PortAllocatorSession *session,
+                         const std::vector<Candidate>& candidates);
+
+  // Called once we have chosen the port to use for communication with the
+  // other client.  This will send its address and prepare the port for use.
+  void SetPort(PortInterface* port);
+
+  // Called once we have a port and a remote address.  This will set mark the
+  // channel as writable and signal the route to the client.
+  void SetWritable();
+
+  // Called when we receive a packet from the other client.
+  void OnReadPacket(PortInterface* port, const char* data, size_t size,
+                    const talk_base::SocketAddress& addr);
+
+  // Handles a message to destroy unused ports.
+  virtual void OnMessage(talk_base::Message *msg);
+
+  DISALLOW_EVIL_CONSTRUCTORS(RawTransportChannel);
+};
+
+}  // namespace cricket
+
+#endif  // defined(FEATURE_ENABLE_PSTN)
+#endif  // TALK_P2P_BASE_RAWTRANSPORTCHANNEL_H_
diff --git a/talk/p2p/base/relayport.cc b/talk/p2p/base/relayport.cc
new file mode 100644
index 0000000..7abe942
--- /dev/null
+++ b/talk/p2p/base/relayport.cc
@@ -0,0 +1,820 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/asyncpacketsocket.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/p2p/base/relayport.h"
+
+namespace cricket {
+
+static const uint32 kMessageConnectTimeout = 1;
+static const int kKeepAliveDelay           = 10 * 60 * 1000;
+static const int kRetryTimeout             = 50 * 1000;  // ICE says 50 secs
+// How long to wait for a socket to connect to remote host in milliseconds
+// before trying another connection.
+static const int kSoftConnectTimeoutMs     = 3 * 1000;
+
+// Handles a connection to one address/port/protocol combination for a
+// particular RelayEntry.
+class RelayConnection : public sigslot::has_slots<> {
+ public:
+  RelayConnection(const ProtocolAddress* protocol_address,
+                  talk_base::AsyncPacketSocket* socket,
+                  talk_base::Thread* thread);
+  ~RelayConnection();
+  talk_base::AsyncPacketSocket* socket() const { return socket_; }
+
+  const ProtocolAddress* protocol_address() {
+    return protocol_address_;
+  }
+
+  talk_base::SocketAddress GetAddress() const {
+    return protocol_address_->address;
+  }
+
+  ProtocolType GetProtocol() const {
+    return protocol_address_->proto;
+  }
+
+  int SetSocketOption(talk_base::Socket::Option opt, int value);
+
+  // Validates a response to a STUN allocate request.
+  bool CheckResponse(StunMessage* msg);
+
+  // Sends data to the relay server.
+  int Send(const void* pv, size_t cb);
+
+  // Sends a STUN allocate request message to the relay server.
+  void SendAllocateRequest(RelayEntry* entry, int delay);
+
+  // Return the latest error generated by the socket.
+  int GetError() { return socket_->GetError(); }
+
+  // Called on behalf of a StunRequest to write data to the socket.  This is
+  // already STUN intended for the server, so no wrapping is necessary.
+  void OnSendPacket(const void* data, size_t size, StunRequest* req);
+
+ private:
+  talk_base::AsyncPacketSocket* socket_;
+  const ProtocolAddress* protocol_address_;
+  StunRequestManager *request_manager_;
+};
+
+// Manages a number of connections to the relayserver, one for each
+// available protocol. We aim to use each connection for only a
+// specific destination address so that we can avoid wrapping every
+// packet in a STUN send / data indication.
+class RelayEntry : public talk_base::MessageHandler,
+                   public sigslot::has_slots<> {
+ public:
+  RelayEntry(RelayPort* port, const talk_base::SocketAddress& ext_addr);
+  ~RelayEntry();
+
+  RelayPort* port() { return port_; }
+
+  const talk_base::SocketAddress& address() const { return ext_addr_; }
+  void set_address(const talk_base::SocketAddress& addr) { ext_addr_ = addr; }
+
+  bool connected() const { return connected_; }
+  bool locked() const { return locked_; }
+
+  // Returns the last error on the socket of this entry.
+  int GetError();
+
+  // Returns the most preferred connection of the given
+  // ones. Connections are rated based on protocol in the order of:
+  // UDP, TCP and SSLTCP, where UDP is the most preferred protocol
+  static RelayConnection* GetBestConnection(RelayConnection* conn1,
+                                            RelayConnection* conn2);
+
+  // Sends the STUN requests to the server to initiate this connection.
+  void Connect();
+
+  // Called when this entry becomes connected.  The address given is the one
+  // exposed to the outside world on the relay server.
+  void OnConnect(const talk_base::SocketAddress& mapped_addr,
+                 RelayConnection* socket);
+
+  // Sends a packet to the given destination address using the socket of this
+  // entry.  This will wrap the packet in STUN if necessary.
+  int SendTo(const void* data, size_t size,
+    const talk_base::SocketAddress& addr);
+
+  // Schedules a keep-alive allocate request.
+  void ScheduleKeepAlive();
+
+  void SetServerIndex(size_t sindex) { server_index_ = sindex; }
+
+  // Sets this option on the socket of each connection.
+  int SetSocketOption(talk_base::Socket::Option opt, int value);
+
+  size_t ServerIndex() const { return server_index_; }
+
+  // Try a different server address
+  void HandleConnectFailure(talk_base::AsyncPacketSocket* socket);
+
+  // Implementation of the MessageHandler Interface.
+  virtual void OnMessage(talk_base::Message *pmsg);
+
+ private:
+  RelayPort* port_;
+  talk_base::SocketAddress ext_addr_;
+  size_t server_index_;
+  bool connected_;
+  bool locked_;
+  RelayConnection* current_connection_;
+
+  // Called when a TCP connection is established or fails
+  void OnSocketConnect(talk_base::AsyncPacketSocket* socket);
+  void OnSocketClose(talk_base::AsyncPacketSocket* socket, int error);
+
+  // Called when a packet is received on this socket.
+  void OnReadPacket(talk_base::AsyncPacketSocket* socket,
+                    const char* data, size_t size,
+                    const talk_base::SocketAddress& remote_addr);
+
+  // Called when the socket is currently able to send.
+  void OnReadyToSend(talk_base::AsyncPacketSocket* socket);
+
+  // Sends the given data on the socket to the server with no wrapping.  This
+  // returns the number of bytes written or -1 if an error occurred.
+  int SendPacket(const void* data, size_t size);
+};
+
+// Handles an allocate request for a particular RelayEntry.
+class AllocateRequest : public StunRequest {
+ public:
+  AllocateRequest(RelayEntry* entry, RelayConnection* connection);
+  virtual ~AllocateRequest() {}
+
+  virtual void Prepare(StunMessage* request);
+
+  virtual int GetNextDelay();
+
+  virtual void OnResponse(StunMessage* response);
+  virtual void OnErrorResponse(StunMessage* response);
+  virtual void OnTimeout();
+
+ private:
+  RelayEntry* entry_;
+  RelayConnection* connection_;
+  uint32 start_time_;
+};
+
+RelayPort::RelayPort(
+    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)
+    : Port(thread, RELAY_PORT_TYPE, factory, network, ip, min_port, max_port,
+           username, password),
+      ready_(false),
+      error_(0) {
+  entries_.push_back(
+      new RelayEntry(this, talk_base::SocketAddress()));
+  // TODO: set local preference value for TCP based candidates.
+}
+
+RelayPort::~RelayPort() {
+  for (size_t i = 0; i < entries_.size(); ++i)
+    delete entries_[i];
+  thread()->Clear(this);
+}
+
+void RelayPort::AddServerAddress(const ProtocolAddress& addr) {
+  // Since HTTP proxies usually only allow 443,
+  // let's up the priority on PROTO_SSLTCP
+  if (addr.proto == PROTO_SSLTCP &&
+      (proxy().type == talk_base::PROXY_HTTPS ||
+       proxy().type == talk_base::PROXY_UNKNOWN)) {
+    server_addr_.push_front(addr);
+  } else {
+    server_addr_.push_back(addr);
+  }
+}
+
+void RelayPort::AddExternalAddress(const ProtocolAddress& addr) {
+  std::string proto_name = ProtoToString(addr.proto);
+  for (std::vector<ProtocolAddress>::iterator it = external_addr_.begin();
+       it != external_addr_.end(); ++it) {
+    if ((it->address == addr.address) && (it->proto == addr.proto)) {
+      LOG(INFO) << "Redundant relay address: " << proto_name
+                << " @ " << addr.address.ToSensitiveString();
+      return;
+    }
+  }
+  external_addr_.push_back(addr);
+}
+
+void RelayPort::SetReady() {
+  if (!ready_) {
+    std::vector<ProtocolAddress>::iterator iter;
+    for (iter = external_addr_.begin();
+         iter != external_addr_.end(); ++iter) {
+      std::string proto_name = ProtoToString(iter->proto);
+      AddAddress(iter->address, iter->address, proto_name,
+                 RELAY_PORT_TYPE, ICE_TYPE_PREFERENCE_RELAY, false);
+    }
+    ready_ = true;
+    SignalPortComplete(this);
+  }
+}
+
+const ProtocolAddress * RelayPort::ServerAddress(size_t index) const {
+  if (index < server_addr_.size())
+    return &server_addr_[index];
+  return NULL;
+}
+
+bool RelayPort::HasMagicCookie(const char* data, size_t size) {
+  if (size < 24 + sizeof(TURN_MAGIC_COOKIE_VALUE)) {
+    return false;
+  } else {
+    return 0 == std::memcmp(data + 24, TURN_MAGIC_COOKIE_VALUE,
+                            sizeof(TURN_MAGIC_COOKIE_VALUE));
+  }
+}
+
+void RelayPort::PrepareAddress() {
+  // We initiate a connect on the first entry.  If this completes, it will fill
+  // in the server address as the address of this port.
+  ASSERT(entries_.size() == 1);
+  entries_[0]->Connect();
+  ready_ = false;
+}
+
+Connection* RelayPort::CreateConnection(const Candidate& address,
+                                        CandidateOrigin origin) {
+  // We only create conns to non-udp sockets if they are incoming on this port
+  if ((address.protocol() != UDP_PROTOCOL_NAME) &&
+      (origin != ORIGIN_THIS_PORT)) {
+    return 0;
+  }
+
+  // We don't support loopback on relays
+  if (address.type() == Type()) {
+    return 0;
+  }
+
+  if (!IsCompatibleAddress(address.address())) {
+    return 0;
+  }
+
+  size_t index = 0;
+  for (size_t i = 0; i < Candidates().size(); ++i) {
+    const Candidate& local = Candidates()[i];
+    if (local.protocol() == address.protocol()) {
+      index = i;
+      break;
+    }
+  }
+
+  Connection * conn = new ProxyConnection(this, index, address);
+  AddConnection(conn);
+  return conn;
+}
+
+int RelayPort::SendTo(const void* data, size_t size,
+                      const talk_base::SocketAddress& addr, bool payload) {
+  // Try to find an entry for this specific address.  Note that the first entry
+  // created was not given an address initially, so it can be set to the first
+  // address that comes along.
+  RelayEntry* entry = 0;
+
+  for (size_t i = 0; i < entries_.size(); ++i) {
+    if (entries_[i]->address().IsNil() && payload) {
+      entry = entries_[i];
+      entry->set_address(addr);
+      break;
+    } else if (entries_[i]->address() == addr) {
+      entry = entries_[i];
+      break;
+    }
+  }
+
+  // If we did not find one, then we make a new one.  This will not be useable
+  // until it becomes connected, however.
+  if (!entry && payload) {
+    entry = new RelayEntry(this, addr);
+    if (!entries_.empty()) {
+      entry->SetServerIndex(entries_[0]->ServerIndex());
+    }
+    entry->Connect();
+    entries_.push_back(entry);
+  }
+
+  // If the entry is connected, then we can send on it (though wrapping may
+  // still be necessary).  Otherwise, we can't yet use this connection, so we
+  // default to the first one.
+  if (!entry || !entry->connected()) {
+    ASSERT(!entries_.empty());
+    entry = entries_[0];
+    if (!entry->connected()) {
+      error_ = EWOULDBLOCK;
+      return SOCKET_ERROR;
+    }
+  }
+
+  // Send the actual contents to the server using the usual mechanism.
+  int sent = entry->SendTo(data, size, addr);
+  if (sent <= 0) {
+    ASSERT(sent < 0);
+    error_ = entry->GetError();
+    return SOCKET_ERROR;
+  }
+  // The caller of the function is expecting the number of user data bytes,
+  // rather than the size of the packet.
+  return size;
+}
+
+int RelayPort::SetOption(talk_base::Socket::Option opt, int value) {
+  int result = 0;
+  for (size_t i = 0; i < entries_.size(); ++i) {
+    if (entries_[i]->SetSocketOption(opt, value) < 0) {
+      result = -1;
+      error_ = entries_[i]->GetError();
+    }
+  }
+  options_.push_back(OptionValue(opt, value));
+  return result;
+}
+
+int RelayPort::GetOption(talk_base::Socket::Option opt, int* value) {
+  std::vector<OptionValue>::iterator it;
+  for (it = options_.begin(); it < options_.end(); ++it) {
+    if (it->first == opt) {
+      *value = it->second;
+      return 0;
+    }
+  }
+  return SOCKET_ERROR;
+}
+
+int RelayPort::GetError() {
+  return error_;
+}
+
+void RelayPort::OnReadPacket(
+    const char* data, size_t size,
+    const talk_base::SocketAddress& remote_addr, ProtocolType proto) {
+  if (Connection* conn = GetConnection(remote_addr)) {
+    conn->OnReadPacket(data, size);
+  } else {
+    Port::OnReadPacket(data, size, remote_addr, proto);
+  }
+}
+
+RelayConnection::RelayConnection(const ProtocolAddress* protocol_address,
+                                 talk_base::AsyncPacketSocket* socket,
+                                 talk_base::Thread* thread)
+    : socket_(socket),
+      protocol_address_(protocol_address) {
+  request_manager_ = new StunRequestManager(thread);
+  request_manager_->SignalSendPacket.connect(this,
+                                             &RelayConnection::OnSendPacket);
+}
+
+RelayConnection::~RelayConnection() {
+  delete request_manager_;
+  delete socket_;
+}
+
+int RelayConnection::SetSocketOption(talk_base::Socket::Option opt,
+                                     int value) {
+  if (socket_) {
+    return socket_->SetOption(opt, value);
+  }
+  return 0;
+}
+
+bool RelayConnection::CheckResponse(StunMessage* msg) {
+  return request_manager_->CheckResponse(msg);
+}
+
+void RelayConnection::OnSendPacket(const void* data, size_t size,
+                                   StunRequest* req) {
+  int sent = socket_->SendTo(data, size, GetAddress());
+  if (sent <= 0) {
+    LOG(LS_VERBOSE) << "OnSendPacket: failed sending to " << GetAddress() <<
+        std::strerror(socket_->GetError());
+    ASSERT(sent < 0);
+  }
+}
+
+int RelayConnection::Send(const void* pv, size_t cb) {
+  return socket_->SendTo(pv, cb, GetAddress());
+}
+
+void RelayConnection::SendAllocateRequest(RelayEntry* entry, int delay) {
+  request_manager_->SendDelayed(new AllocateRequest(entry, this), delay);
+}
+
+RelayEntry::RelayEntry(RelayPort* port,
+                       const talk_base::SocketAddress& ext_addr)
+    : port_(port), ext_addr_(ext_addr),
+      server_index_(0), connected_(false), locked_(false),
+      current_connection_(NULL) {
+}
+
+RelayEntry::~RelayEntry() {
+  // Remove all RelayConnections and dispose sockets.
+  delete current_connection_;
+  current_connection_ = NULL;
+}
+
+void RelayEntry::Connect() {
+  // If we're already connected, return.
+  if (connected_)
+    return;
+
+  // If we've exhausted all options, bail out.
+  const ProtocolAddress* ra = port()->ServerAddress(server_index_);
+  if (!ra) {
+    LOG(LS_WARNING) << "No more relay addresses left to try";
+    return;
+  }
+
+  // Remove any previous connection.
+  if (current_connection_) {
+    port()->thread()->Dispose(current_connection_);
+    current_connection_ = NULL;
+  }
+
+  // Try to set up our new socket.
+  LOG(LS_INFO) << "Connecting to relay via " << ProtoToString(ra->proto) <<
+      " @ " << ra->address.ToSensitiveString();
+
+  talk_base::AsyncPacketSocket* socket = NULL;
+
+  if (ra->proto == PROTO_UDP) {
+    // UDP sockets are simple.
+    socket = port_->socket_factory()->CreateUdpSocket(
+        talk_base::SocketAddress(port_->ip(), 0),
+        port_->min_port(), port_->max_port());
+  } else if (ra->proto == PROTO_TCP || ra->proto == PROTO_SSLTCP) {
+    int opts = (ra->proto == PROTO_SSLTCP) ?
+     talk_base::PacketSocketFactory::OPT_SSLTCP : 0;
+    socket = port_->socket_factory()->CreateClientTcpSocket(
+        talk_base::SocketAddress(port_->ip(), 0), ra->address,
+        port_->proxy(), port_->user_agent(), opts);
+  } else {
+    LOG(LS_WARNING) << "Unknown protocol (" << ra->proto << ")";
+  }
+
+  if (!socket) {
+    LOG(LS_WARNING) << "Socket creation failed";
+  }
+
+  // If we failed to get a socket, move on to the next protocol.
+  if (!socket) {
+    port()->thread()->Post(this, kMessageConnectTimeout);
+    return;
+  }
+
+  // Otherwise, create the new connection and configure any socket options.
+  socket->SignalReadPacket.connect(this, &RelayEntry::OnReadPacket);
+  socket->SignalReadyToSend.connect(this, &RelayEntry::OnReadyToSend);
+  current_connection_ = new RelayConnection(ra, socket, port()->thread());
+  for (size_t i = 0; i < port_->options().size(); ++i) {
+    current_connection_->SetSocketOption(port_->options()[i].first,
+                                         port_->options()[i].second);
+  }
+
+  // If we're trying UDP, start binding requests.
+  // If we're trying TCP, wait for connection with a fixed timeout.
+  if ((ra->proto == PROTO_TCP) || (ra->proto == PROTO_SSLTCP)) {
+    socket->SignalClose.connect(this, &RelayEntry::OnSocketClose);
+    socket->SignalConnect.connect(this, &RelayEntry::OnSocketConnect);
+    port()->thread()->PostDelayed(kSoftConnectTimeoutMs, this,
+                                  kMessageConnectTimeout);
+  } else {
+    current_connection_->SendAllocateRequest(this, 0);
+  }
+}
+
+int RelayEntry::GetError() {
+  if (current_connection_ != NULL) {
+    return current_connection_->GetError();
+  }
+  return 0;
+}
+
+RelayConnection* RelayEntry::GetBestConnection(RelayConnection* conn1,
+                                               RelayConnection* conn2) {
+  return conn1->GetProtocol() <= conn2->GetProtocol() ? conn1 : conn2;
+}
+
+void RelayEntry::OnConnect(const talk_base::SocketAddress& mapped_addr,
+                           RelayConnection* connection) {
+  // We are connected, notify our parent.
+  ProtocolType proto = PROTO_UDP;
+  LOG(INFO) << "Relay allocate succeeded: " << ProtoToString(proto)
+            << " @ " << mapped_addr.ToSensitiveString();
+  connected_ = true;
+
+  // In case of Gturn related address is set to null socket address.
+  // This is due to mapped address stun attribute is used for allocated
+  // address.
+  port_->set_related_address(talk_base::SocketAddress());
+  port_->AddExternalAddress(ProtocolAddress(mapped_addr, proto));
+  port_->SetReady();
+}
+
+int RelayEntry::SendTo(const void* data, size_t size,
+                       const talk_base::SocketAddress& addr) {
+  // If this connection is locked to the address given, then we can send the
+  // packet with no wrapper.
+  if (locked_ && (ext_addr_ == addr))
+    return SendPacket(data, size);
+
+  // Otherwise, we must wrap the given data in a STUN SEND request so that we
+  // can communicate the destination address to the server.
+  //
+  // Note that we do not use a StunRequest here.  This is because there is
+  // likely no reason to resend this packet. If it is late, we just drop it.
+  // The next send to this address will try again.
+
+  RelayMessage request;
+  request.SetType(STUN_SEND_REQUEST);
+
+  StunByteStringAttribute* magic_cookie_attr =
+      StunAttribute::CreateByteString(STUN_ATTR_MAGIC_COOKIE);
+  magic_cookie_attr->CopyBytes(TURN_MAGIC_COOKIE_VALUE,
+                               sizeof(TURN_MAGIC_COOKIE_VALUE));
+  VERIFY(request.AddAttribute(magic_cookie_attr));
+
+  StunByteStringAttribute* username_attr =
+      StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
+  username_attr->CopyBytes(port_->username_fragment().c_str(),
+                           port_->username_fragment().size());
+  VERIFY(request.AddAttribute(username_attr));
+
+  StunAddressAttribute* addr_attr =
+      StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS);
+  addr_attr->SetIP(addr.ipaddr());
+  addr_attr->SetPort(addr.port());
+  VERIFY(request.AddAttribute(addr_attr));
+
+  // Attempt to lock
+  if (ext_addr_ == addr) {
+    StunUInt32Attribute* options_attr =
+      StunAttribute::CreateUInt32(STUN_ATTR_OPTIONS);
+    options_attr->SetValue(0x1);
+    VERIFY(request.AddAttribute(options_attr));
+  }
+
+  StunByteStringAttribute* data_attr =
+      StunAttribute::CreateByteString(STUN_ATTR_DATA);
+  data_attr->CopyBytes(data, size);
+  VERIFY(request.AddAttribute(data_attr));
+
+  // TODO: compute the HMAC.
+
+  talk_base::ByteBuffer buf;
+  request.Write(&buf);
+
+  return SendPacket(buf.Data(), buf.Length());
+}
+
+void RelayEntry::ScheduleKeepAlive() {
+  if (current_connection_) {
+    current_connection_->SendAllocateRequest(this, kKeepAliveDelay);
+  }
+}
+
+int RelayEntry::SetSocketOption(talk_base::Socket::Option opt, int value) {
+  // Set the option on all available sockets.
+  int socket_error = 0;
+  if (current_connection_) {
+    socket_error = current_connection_->SetSocketOption(opt, value);
+  }
+  return socket_error;
+}
+
+void RelayEntry::HandleConnectFailure(
+    talk_base::AsyncPacketSocket* socket) {
+  // Make sure it's the current connection that has failed, it might
+  // be an old socked that has not yet been disposed.
+  if (!socket ||
+      (current_connection_ && socket == current_connection_->socket())) {
+    if (current_connection_)
+      port()->SignalConnectFailure(current_connection_->protocol_address());
+
+    // Try to connect to the next server address.
+    server_index_ += 1;
+    Connect();
+  }
+}
+
+void RelayEntry::OnMessage(talk_base::Message *pmsg) {
+  ASSERT(pmsg->message_id == kMessageConnectTimeout);
+  if (current_connection_) {
+    const ProtocolAddress* ra = current_connection_->protocol_address();
+    LOG(LS_WARNING) << "Relay " << ra->proto << " connection to " <<
+        ra->address << " timed out";
+
+    // Currently we connect to each server address in sequence. If we
+    // have more addresses to try, treat this is an error and move on to
+    // the next address, otherwise give this connection more time and
+    // await the real timeout.
+    //
+    // TODO: Connect to servers in parallel to speed up connect time
+    // and to avoid giving up too early.
+    port_->SignalSoftTimeout(ra);
+    HandleConnectFailure(current_connection_->socket());
+  } else {
+    HandleConnectFailure(NULL);
+  }
+}
+
+void RelayEntry::OnSocketConnect(talk_base::AsyncPacketSocket* socket) {
+  LOG(INFO) << "relay tcp connected to " <<
+      socket->GetRemoteAddress().ToSensitiveString();
+  if (current_connection_ != NULL) {
+    current_connection_->SendAllocateRequest(this, 0);
+  }
+}
+
+void RelayEntry::OnSocketClose(talk_base::AsyncPacketSocket* socket,
+                               int error) {
+  PLOG(LERROR, error) << "Relay connection failed: socket closed";
+  HandleConnectFailure(socket);
+}
+
+void RelayEntry::OnReadPacket(talk_base::AsyncPacketSocket* socket,
+                              const char* data, size_t size,
+                              const talk_base::SocketAddress& remote_addr) {
+  // ASSERT(remote_addr == port_->server_addr());
+  // TODO: are we worried about this?
+
+  if (current_connection_ == NULL || socket != current_connection_->socket()) {
+    // This packet comes from an unknown address.
+    LOG(WARNING) << "Dropping packet: unknown address";
+    return;
+  }
+
+  // If the magic cookie is not present, then this is an unwrapped packet sent
+  // by the server,  The actual remote address is the one we recorded.
+  if (!port_->HasMagicCookie(data, size)) {
+    if (locked_) {
+      port_->OnReadPacket(data, size, ext_addr_, PROTO_UDP);
+    } else {
+      LOG(WARNING) << "Dropping packet: entry not locked";
+    }
+    return;
+  }
+
+  talk_base::ByteBuffer buf(data, size);
+  RelayMessage msg;
+  if (!msg.Read(&buf)) {
+    LOG(INFO) << "Incoming packet was not STUN";
+    return;
+  }
+
+  // The incoming packet should be a STUN ALLOCATE response, SEND response, or
+  // DATA indication.
+  if (current_connection_->CheckResponse(&msg)) {
+    return;
+  } else if (msg.type() == STUN_SEND_RESPONSE) {
+    if (const StunUInt32Attribute* options_attr =
+        msg.GetUInt32(STUN_ATTR_OPTIONS)) {
+      if (options_attr->value() & 0x1) {
+        locked_ = true;
+      }
+    }
+    return;
+  } else if (msg.type() != STUN_DATA_INDICATION) {
+    LOG(INFO) << "Received BAD stun type from server: " << msg.type();
+    return;
+  }
+
+  // This must be a data indication.
+
+  const StunAddressAttribute* addr_attr =
+      msg.GetAddress(STUN_ATTR_SOURCE_ADDRESS2);
+  if (!addr_attr) {
+    LOG(INFO) << "Data indication has no source address";
+    return;
+  } else if (addr_attr->family() != 1) {
+    LOG(INFO) << "Source address has bad family";
+    return;
+  }
+
+  talk_base::SocketAddress remote_addr2(addr_attr->ipaddr(), addr_attr->port());
+
+  const StunByteStringAttribute* data_attr = msg.GetByteString(STUN_ATTR_DATA);
+  if (!data_attr) {
+    LOG(INFO) << "Data indication has no data";
+    return;
+  }
+
+  // Process the actual data and remote address in the normal manner.
+  port_->OnReadPacket(data_attr->bytes(), data_attr->length(), remote_addr2,
+                      PROTO_UDP);
+}
+
+void RelayEntry::OnReadyToSend(talk_base::AsyncPacketSocket* socket) {
+  if (connected()) {
+    port_->OnReadyToSend();
+  }
+}
+
+int RelayEntry::SendPacket(const void* data, size_t size) {
+  int sent = 0;
+  if (current_connection_) {
+    // We are connected, no need to send packets anywere else than to
+    // the current connection.
+    sent = current_connection_->Send(data, size);
+  }
+  return sent;
+}
+
+AllocateRequest::AllocateRequest(RelayEntry* entry,
+                                 RelayConnection* connection)
+    : StunRequest(new RelayMessage()),
+      entry_(entry),
+      connection_(connection) {
+  start_time_ = talk_base::Time();
+}
+
+void AllocateRequest::Prepare(StunMessage* request) {
+  request->SetType(STUN_ALLOCATE_REQUEST);
+
+  StunByteStringAttribute* username_attr =
+      StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
+  username_attr->CopyBytes(
+      entry_->port()->username_fragment().c_str(),
+      entry_->port()->username_fragment().size());
+  VERIFY(request->AddAttribute(username_attr));
+}
+
+int AllocateRequest::GetNextDelay() {
+  int delay = 100 * talk_base::_max(1 << count_, 2);
+  count_ += 1;
+  if (count_ == 5)
+    timeout_ = true;
+  return delay;
+}
+
+void AllocateRequest::OnResponse(StunMessage* response) {
+  const StunAddressAttribute* addr_attr =
+      response->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+  if (!addr_attr) {
+    LOG(INFO) << "Allocate response missing mapped address.";
+  } else if (addr_attr->family() != 1) {
+    LOG(INFO) << "Mapped address has bad family";
+  } else {
+    talk_base::SocketAddress addr(addr_attr->ipaddr(), addr_attr->port());
+    entry_->OnConnect(addr, connection_);
+  }
+
+  // We will do a keep-alive regardless of whether this request suceeds.
+  // This should have almost no impact on network usage.
+  entry_->ScheduleKeepAlive();
+}
+
+void AllocateRequest::OnErrorResponse(StunMessage* response) {
+  const StunErrorCodeAttribute* attr = response->GetErrorCode();
+  if (!attr) {
+    LOG(INFO) << "Bad allocate response error code";
+  } else {
+    LOG(INFO) << "Allocate error response:"
+              << " code=" << attr->code()
+              << " reason='" << attr->reason() << "'";
+  }
+
+  if (talk_base::TimeSince(start_time_) <= kRetryTimeout)
+    entry_->ScheduleKeepAlive();
+}
+
+void AllocateRequest::OnTimeout() {
+  LOG(INFO) << "Allocate request timed out";
+  entry_->HandleConnectFailure(connection_->socket());
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/relayport.h b/talk/p2p/base/relayport.h
new file mode 100644
index 0000000..a2bfb74
--- /dev/null
+++ b/talk/p2p/base/relayport.h
@@ -0,0 +1,115 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_RELAYPORT_H_
+#define TALK_P2P_BASE_RELAYPORT_H_
+
+#include <deque>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "talk/p2p/base/port.h"
+#include "talk/p2p/base/stunrequest.h"
+
+namespace cricket {
+
+class RelayEntry;
+class RelayConnection;
+
+// Communicates using an allocated port on the relay server. For each
+// remote candidate that we try to send data to a RelayEntry instance
+// is created. The RelayEntry will try to reach the remote destination
+// by connecting to all available server addresses in a pre defined
+// order with a small delay in between. When a connection is
+// successful all other connection attemts are aborted.
+class RelayPort : public Port {
+ public:
+  typedef std::pair<talk_base::Socket::Option, int> OptionValue;
+
+  // RelayPort doesn't yet do anything fancy in the ctor.
+  static RelayPort* Create(
+      talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
+      talk_base::Network* network, const talk_base::IPAddress& ip,
+      int min_port, int max_port, const std::string& username,
+      const std::string& password) {
+    return new RelayPort(thread, factory, network, ip, min_port, max_port,
+                         username, password);
+  }
+  virtual ~RelayPort();
+
+  void AddServerAddress(const ProtocolAddress& addr);
+  void AddExternalAddress(const ProtocolAddress& addr);
+
+  const std::vector<OptionValue>& options() const { return options_; }
+  bool HasMagicCookie(const char* data, size_t size);
+
+  virtual void PrepareAddress();
+  virtual Connection* CreateConnection(const Candidate& address,
+                                       CandidateOrigin origin);
+  virtual int SetOption(talk_base::Socket::Option opt, int value);
+  virtual int GetOption(talk_base::Socket::Option opt, int* value);
+  virtual int GetError();
+
+  const ProtocolAddress * ServerAddress(size_t index) const;
+  bool IsReady() { return ready_; }
+
+  // Used for testing.
+  sigslot::signal1<const ProtocolAddress*> SignalConnectFailure;
+  sigslot::signal1<const ProtocolAddress*> SignalSoftTimeout;
+
+ protected:
+  RelayPort(talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
+            talk_base::Network*, const talk_base::IPAddress& ip,
+            int min_port, int max_port, const std::string& username,
+            const std::string& password);
+  bool Init();
+
+  void SetReady();
+
+  virtual int SendTo(const void* data, size_t size,
+                     const talk_base::SocketAddress& addr, bool payload);
+
+  // Dispatches the given packet to the port or connection as appropriate.
+  void OnReadPacket(const char* data, size_t size,
+                    const talk_base::SocketAddress& remote_addr,
+                    ProtocolType proto);
+
+ private:
+  friend class RelayEntry;
+
+  std::deque<ProtocolAddress> server_addr_;
+  std::vector<ProtocolAddress> external_addr_;
+  bool ready_;
+  std::vector<RelayEntry*> entries_;
+  std::vector<OptionValue> options_;
+  int error_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_RELAYPORT_H_
diff --git a/talk/p2p/base/relayport_unittest.cc b/talk/p2p/base/relayport_unittest.cc
new file mode 100644
index 0000000..ced8c58
--- /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/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/basicpacketsocketfactory.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), 32),
+        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
new file mode 100644
index 0000000..0470e9e
--- /dev/null
+++ b/talk/p2p/base/relayserver.cc
@@ -0,0 +1,756 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/p2p/base/relayserver.h"
+
+#ifdef POSIX
+#include <errno.h>
+#endif  // POSIX
+
+#include <algorithm>
+
+#include "talk/base/asynctcpsocket.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/socketadapters.h"
+
+namespace cricket {
+
+// By default, we require a ping every 90 seconds.
+const int MAX_LIFETIME = 15 * 60 * 1000;
+
+// The number of bytes in each of the usernames we use.
+const uint32 USERNAME_LENGTH = 16;
+
+static const uint32 kMessageAcceptConnection = 1;
+
+// Calls SendTo on the given socket and logs any bad results.
+void Send(talk_base::AsyncPacketSocket* socket, const char* bytes, size_t size,
+          const talk_base::SocketAddress& addr) {
+  int result = socket->SendTo(bytes, size, addr);
+  if (result < static_cast<int>(size)) {
+    LOG(LS_ERROR) << "SendTo wrote only " << result << " of " << size
+                  << " bytes";
+  } else if (result < 0) {
+    LOG_ERR(LS_ERROR) << "SendTo";
+  }
+}
+
+// Sends the given STUN message on the given socket.
+void SendStun(const StunMessage& msg,
+              talk_base::AsyncPacketSocket* socket,
+              const talk_base::SocketAddress& addr) {
+  talk_base::ByteBuffer buf;
+  msg.Write(&buf);
+  Send(socket, buf.Data(), buf.Length(), addr);
+}
+
+// Constructs a STUN error response and sends it on the given socket.
+void SendStunError(const StunMessage& msg, talk_base::AsyncPacketSocket* socket,
+                   const talk_base::SocketAddress& remote_addr, int error_code,
+                   const char* error_desc, const std::string& magic_cookie) {
+  RelayMessage err_msg;
+  err_msg.SetType(GetStunErrorResponseType(msg.type()));
+  err_msg.SetTransactionID(msg.transaction_id());
+
+  StunByteStringAttribute* magic_cookie_attr =
+      StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE);
+  if (magic_cookie.size() == 0) {
+    magic_cookie_attr->CopyBytes(cricket::TURN_MAGIC_COOKIE_VALUE,
+                                 sizeof(cricket::TURN_MAGIC_COOKIE_VALUE));
+  } else {
+    magic_cookie_attr->CopyBytes(magic_cookie.c_str(), magic_cookie.size());
+  }
+  err_msg.AddAttribute(magic_cookie_attr);
+
+  StunErrorCodeAttribute* err_code = StunAttribute::CreateErrorCode();
+  err_code->SetClass(error_code / 100);
+  err_code->SetNumber(error_code % 100);
+  err_code->SetReason(error_desc);
+  err_msg.AddAttribute(err_code);
+
+  SendStun(err_msg, socket, remote_addr);
+}
+
+RelayServer::RelayServer(talk_base::Thread* thread)
+  : thread_(thread), log_bindings_(true) {
+}
+
+RelayServer::~RelayServer() {
+  // Deleting the binding will cause it to be removed from the map.
+  while (!bindings_.empty())
+    delete bindings_.begin()->second;
+  for (size_t i = 0; i < internal_sockets_.size(); ++i)
+    delete internal_sockets_[i];
+  for (size_t i = 0; i < external_sockets_.size(); ++i)
+    delete external_sockets_[i];
+  while (!server_sockets_.empty()) {
+    talk_base::AsyncSocket* socket = server_sockets_.begin()->first;
+    server_sockets_.erase(server_sockets_.begin()->first);
+    delete socket;
+  }
+}
+
+void RelayServer::AddInternalSocket(talk_base::AsyncPacketSocket* socket) {
+  ASSERT(internal_sockets_.end() ==
+      std::find(internal_sockets_.begin(), internal_sockets_.end(), socket));
+  internal_sockets_.push_back(socket);
+  socket->SignalReadPacket.connect(this, &RelayServer::OnInternalPacket);
+}
+
+void RelayServer::RemoveInternalSocket(talk_base::AsyncPacketSocket* socket) {
+  SocketList::iterator iter =
+      std::find(internal_sockets_.begin(), internal_sockets_.end(), socket);
+  ASSERT(iter != internal_sockets_.end());
+  internal_sockets_.erase(iter);
+  socket->SignalReadPacket.disconnect(this);
+}
+
+void RelayServer::AddExternalSocket(talk_base::AsyncPacketSocket* socket) {
+  ASSERT(external_sockets_.end() ==
+      std::find(external_sockets_.begin(), external_sockets_.end(), socket));
+  external_sockets_.push_back(socket);
+  socket->SignalReadPacket.connect(this, &RelayServer::OnExternalPacket);
+}
+
+void RelayServer::RemoveExternalSocket(talk_base::AsyncPacketSocket* socket) {
+  SocketList::iterator iter =
+      std::find(external_sockets_.begin(), external_sockets_.end(), socket);
+  ASSERT(iter != external_sockets_.end());
+  external_sockets_.erase(iter);
+  socket->SignalReadPacket.disconnect(this);
+}
+
+void RelayServer::AddInternalServerSocket(talk_base::AsyncSocket* socket,
+                                          cricket::ProtocolType proto) {
+  ASSERT(server_sockets_.end() ==
+         server_sockets_.find(socket));
+  server_sockets_[socket] = proto;
+  socket->SignalReadEvent.connect(this, &RelayServer::OnReadEvent);
+}
+
+void RelayServer::RemoveInternalServerSocket(
+    talk_base::AsyncSocket* socket) {
+  ServerSocketMap::iterator iter = server_sockets_.find(socket);
+  ASSERT(iter != server_sockets_.end());
+  server_sockets_.erase(iter);
+  socket->SignalReadEvent.disconnect(this);
+}
+
+int RelayServer::GetConnectionCount() const {
+  return connections_.size();
+}
+
+talk_base::SocketAddressPair RelayServer::GetConnection(int connection) const {
+  int i = 0;
+  for (ConnectionMap::const_iterator it = connections_.begin();
+       it != connections_.end(); ++it) {
+    if (i == connection) {
+      return it->second->addr_pair();
+    }
+    ++i;
+  }
+  return talk_base::SocketAddressPair();
+}
+
+bool RelayServer::HasConnection(const talk_base::SocketAddress& address) const {
+  for (ConnectionMap::const_iterator it = connections_.begin();
+       it != connections_.end(); ++it) {
+    if (it->second->addr_pair().destination() == address) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void RelayServer::OnReadEvent(talk_base::AsyncSocket* socket) {
+  ASSERT(server_sockets_.find(socket) != server_sockets_.end());
+  AcceptConnection(socket);
+}
+
+void RelayServer::OnInternalPacket(
+    talk_base::AsyncPacketSocket* socket, const char* bytes, size_t size,
+    const talk_base::SocketAddress& remote_addr) {
+
+  // Get the address of the connection we just received on.
+  talk_base::SocketAddressPair ap(remote_addr, socket->GetLocalAddress());
+  ASSERT(!ap.destination().IsNil());
+
+  // If this did not come from an existing connection, it should be a STUN
+  // allocate request.
+  ConnectionMap::iterator piter = connections_.find(ap);
+  if (piter == connections_.end()) {
+    HandleStunAllocate(bytes, size, ap, socket);
+    return;
+  }
+
+  RelayServerConnection* int_conn = piter->second;
+
+  // Handle STUN requests to the server itself.
+  if (int_conn->binding()->HasMagicCookie(bytes, size)) {
+    HandleStun(int_conn, bytes, size);
+    return;
+  }
+
+  // Otherwise, this is a non-wrapped packet that we are to forward.  Make sure
+  // that this connection has been locked.  (Otherwise, we would not know what
+  // address to forward to.)
+  if (!int_conn->locked()) {
+    LOG(LS_WARNING) << "Dropping packet: connection not locked";
+    return;
+  }
+
+  // Forward this to the destination address into the connection.
+  RelayServerConnection* ext_conn = int_conn->binding()->GetExternalConnection(
+      int_conn->default_destination());
+  if (ext_conn && ext_conn->locked()) {
+    // TODO: Check the HMAC.
+    ext_conn->Send(bytes, size);
+  } else {
+    // This happens very often and is not an error.
+    LOG(LS_INFO) << "Dropping packet: no external connection";
+  }
+}
+
+void RelayServer::OnExternalPacket(
+    talk_base::AsyncPacketSocket* socket, const char* bytes, size_t size,
+    const talk_base::SocketAddress& remote_addr) {
+
+  // Get the address of the connection we just received on.
+  talk_base::SocketAddressPair ap(remote_addr, socket->GetLocalAddress());
+  ASSERT(!ap.destination().IsNil());
+
+  // If this connection already exists, then forward the traffic.
+  ConnectionMap::iterator piter = connections_.find(ap);
+  if (piter != connections_.end()) {
+    // TODO: Check the HMAC.
+    RelayServerConnection* ext_conn = piter->second;
+    RelayServerConnection* int_conn =
+        ext_conn->binding()->GetInternalConnection(
+            ext_conn->addr_pair().source());
+    ASSERT(int_conn != NULL);
+    int_conn->Send(bytes, size, ext_conn->addr_pair().source());
+    ext_conn->Lock();  // allow outgoing packets
+    return;
+  }
+
+  // The first packet should always be a STUN / TURN packet.  If it isn't, then
+  // we should just ignore this packet.
+  RelayMessage msg;
+  talk_base::ByteBuffer buf(bytes, size);
+  if (!msg.Read(&buf)) {
+    LOG(LS_WARNING) << "Dropping packet: first packet not STUN";
+    return;
+  }
+
+  // The initial packet should have a username (which identifies the binding).
+  const StunByteStringAttribute* username_attr =
+      msg.GetByteString(STUN_ATTR_USERNAME);
+  if (!username_attr) {
+    LOG(LS_WARNING) << "Dropping packet: no username";
+    return;
+  }
+
+  uint32 length = talk_base::_min(static_cast<uint32>(username_attr->length()),
+                                  USERNAME_LENGTH);
+  std::string username(username_attr->bytes(), length);
+  // TODO: Check the HMAC.
+
+  // The binding should already be present.
+  BindingMap::iterator biter = bindings_.find(username);
+  if (biter == bindings_.end()) {
+    LOG(LS_WARNING) << "Dropping packet: no binding with username";
+    return;
+  }
+
+  // Add this authenticted connection to the binding.
+  RelayServerConnection* ext_conn =
+      new RelayServerConnection(biter->second, ap, socket);
+  ext_conn->binding()->AddExternalConnection(ext_conn);
+  AddConnection(ext_conn);
+
+  // We always know where external packets should be forwarded, so we can lock
+  // them from the beginning.
+  ext_conn->Lock();
+
+  // Send this message on the appropriate internal connection.
+  RelayServerConnection* int_conn = ext_conn->binding()->GetInternalConnection(
+      ext_conn->addr_pair().source());
+  ASSERT(int_conn != NULL);
+  int_conn->Send(bytes, size, ext_conn->addr_pair().source());
+}
+
+bool RelayServer::HandleStun(
+    const char* bytes, size_t size, const talk_base::SocketAddress& remote_addr,
+    talk_base::AsyncPacketSocket* socket, std::string* username,
+    StunMessage* msg) {
+
+  // Parse this into a stun message. Eat the message if this fails.
+  talk_base::ByteBuffer buf(bytes, size);
+  if (!msg->Read(&buf)) {
+    return false;
+  }
+
+  // The initial packet should have a username (which identifies the binding).
+  const StunByteStringAttribute* username_attr =
+      msg->GetByteString(STUN_ATTR_USERNAME);
+  if (!username_attr) {
+    SendStunError(*msg, socket, remote_addr, 432, "Missing Username", "");
+    return false;
+  }
+
+  // Record the username if requested.
+  if (username)
+    username->append(username_attr->bytes(), username_attr->length());
+
+  // TODO: Check for unknown attributes (<= 0x7fff)
+
+  return true;
+}
+
+void RelayServer::HandleStunAllocate(
+    const char* bytes, size_t size, const talk_base::SocketAddressPair& ap,
+    talk_base::AsyncPacketSocket* socket) {
+
+  // Make sure this is a valid STUN request.
+  RelayMessage request;
+  std::string username;
+  if (!HandleStun(bytes, size, ap.source(), socket, &username, &request))
+    return;
+
+  // Make sure this is a an allocate request.
+  if (request.type() != STUN_ALLOCATE_REQUEST) {
+    SendStunError(request,
+                  socket,
+                  ap.source(),
+                  600,
+                  "Operation Not Supported",
+                  "");
+    return;
+  }
+
+  // TODO: Check the HMAC.
+
+  // Find or create the binding for this username.
+
+  RelayServerBinding* binding;
+
+  BindingMap::iterator biter = bindings_.find(username);
+  if (biter != bindings_.end()) {
+    binding = biter->second;
+  } else {
+    // NOTE: In the future, bindings will be created by the bot only.  This
+    //       else-branch will then disappear.
+
+    // Compute the appropriate lifetime for this binding.
+    uint32 lifetime = MAX_LIFETIME;
+    const StunUInt32Attribute* lifetime_attr =
+        request.GetUInt32(STUN_ATTR_LIFETIME);
+    if (lifetime_attr)
+      lifetime = talk_base::_min(lifetime, lifetime_attr->value() * 1000);
+
+    binding = new RelayServerBinding(this, username, "0", lifetime);
+    binding->SignalTimeout.connect(this, &RelayServer::OnTimeout);
+    bindings_[username] = binding;
+
+    if (log_bindings_) {
+      LOG(LS_INFO) << "Added new binding " << username << ", "
+                   << bindings_.size() << " total";
+    }
+  }
+
+  // Add this connection to the binding.  It starts out unlocked.
+  RelayServerConnection* int_conn =
+      new RelayServerConnection(binding, ap, socket);
+  binding->AddInternalConnection(int_conn);
+  AddConnection(int_conn);
+
+  // Now that we have a connection, this other method takes over.
+  HandleStunAllocate(int_conn, request);
+}
+
+void RelayServer::HandleStun(
+    RelayServerConnection* int_conn, const char* bytes, size_t size) {
+
+  // Make sure this is a valid STUN request.
+  RelayMessage request;
+  std::string username;
+  if (!HandleStun(bytes, size, int_conn->addr_pair().source(),
+                  int_conn->socket(), &username, &request))
+    return;
+
+  // Make sure the username is the one were were expecting.
+  if (username != int_conn->binding()->username()) {
+    int_conn->SendStunError(request, 430, "Stale Credentials");
+    return;
+  }
+
+  // TODO: Check the HMAC.
+
+  // Send this request to the appropriate handler.
+  if (request.type() == STUN_SEND_REQUEST)
+    HandleStunSend(int_conn, request);
+  else if (request.type() == STUN_ALLOCATE_REQUEST)
+    HandleStunAllocate(int_conn, request);
+  else
+    int_conn->SendStunError(request, 600, "Operation Not Supported");
+}
+
+void RelayServer::HandleStunAllocate(
+    RelayServerConnection* int_conn, const StunMessage& request) {
+
+  // Create a response message that includes an address with which external
+  // clients can communicate.
+
+  RelayMessage response;
+  response.SetType(STUN_ALLOCATE_RESPONSE);
+  response.SetTransactionID(request.transaction_id());
+
+  StunByteStringAttribute* magic_cookie_attr =
+      StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE);
+  magic_cookie_attr->CopyBytes(int_conn->binding()->magic_cookie().c_str(),
+                               int_conn->binding()->magic_cookie().size());
+  response.AddAttribute(magic_cookie_attr);
+
+  size_t index = rand() % external_sockets_.size();
+  talk_base::SocketAddress ext_addr =
+      external_sockets_[index]->GetLocalAddress();
+
+  StunAddressAttribute* addr_attr =
+      StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
+  addr_attr->SetIP(ext_addr.ipaddr());
+  addr_attr->SetPort(ext_addr.port());
+  response.AddAttribute(addr_attr);
+
+  StunUInt32Attribute* res_lifetime_attr =
+      StunAttribute::CreateUInt32(STUN_ATTR_LIFETIME);
+  res_lifetime_attr->SetValue(int_conn->binding()->lifetime() / 1000);
+  response.AddAttribute(res_lifetime_attr);
+
+  // TODO: Support transport-prefs (preallocate RTCP port).
+  // TODO: Support bandwidth restrictions.
+  // TODO: Add message integrity check.
+
+  // Send a response to the caller.
+  int_conn->SendStun(response);
+}
+
+void RelayServer::HandleStunSend(
+    RelayServerConnection* int_conn, const StunMessage& request) {
+
+  const StunAddressAttribute* addr_attr =
+      request.GetAddress(STUN_ATTR_DESTINATION_ADDRESS);
+  if (!addr_attr) {
+    int_conn->SendStunError(request, 400, "Bad Request");
+    return;
+  }
+
+  const StunByteStringAttribute* data_attr =
+      request.GetByteString(STUN_ATTR_DATA);
+  if (!data_attr) {
+    int_conn->SendStunError(request, 400, "Bad Request");
+    return;
+  }
+
+  talk_base::SocketAddress ext_addr(addr_attr->ipaddr(), addr_attr->port());
+  RelayServerConnection* ext_conn =
+      int_conn->binding()->GetExternalConnection(ext_addr);
+  if (!ext_conn) {
+    // Create a new connection to establish the relationship with this binding.
+    ASSERT(external_sockets_.size() == 1);
+    talk_base::AsyncPacketSocket* socket = external_sockets_[0];
+    talk_base::SocketAddressPair ap(ext_addr, socket->GetLocalAddress());
+    ext_conn = new RelayServerConnection(int_conn->binding(), ap, socket);
+    ext_conn->binding()->AddExternalConnection(ext_conn);
+    AddConnection(ext_conn);
+  }
+
+  // If this connection has pinged us, then allow outgoing traffic.
+  if (ext_conn->locked())
+    ext_conn->Send(data_attr->bytes(), data_attr->length());
+
+  const StunUInt32Attribute* options_attr =
+      request.GetUInt32(STUN_ATTR_OPTIONS);
+  if (options_attr && (options_attr->value() & 0x01)) {
+    int_conn->set_default_destination(ext_addr);
+    int_conn->Lock();
+
+    RelayMessage response;
+    response.SetType(STUN_SEND_RESPONSE);
+    response.SetTransactionID(request.transaction_id());
+
+    StunByteStringAttribute* magic_cookie_attr =
+        StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE);
+    magic_cookie_attr->CopyBytes(int_conn->binding()->magic_cookie().c_str(),
+                                 int_conn->binding()->magic_cookie().size());
+    response.AddAttribute(magic_cookie_attr);
+
+    StunUInt32Attribute* options2_attr =
+      StunAttribute::CreateUInt32(cricket::STUN_ATTR_OPTIONS);
+    options2_attr->SetValue(0x01);
+    response.AddAttribute(options2_attr);
+
+    int_conn->SendStun(response);
+  }
+}
+
+void RelayServer::AddConnection(RelayServerConnection* conn) {
+  ASSERT(connections_.find(conn->addr_pair()) == connections_.end());
+  connections_[conn->addr_pair()] = conn;
+}
+
+void RelayServer::RemoveConnection(RelayServerConnection* conn) {
+  ConnectionMap::iterator iter = connections_.find(conn->addr_pair());
+  ASSERT(iter != connections_.end());
+  connections_.erase(iter);
+}
+
+void RelayServer::RemoveBinding(RelayServerBinding* binding) {
+  BindingMap::iterator iter = bindings_.find(binding->username());
+  ASSERT(iter != bindings_.end());
+  bindings_.erase(iter);
+
+  if (log_bindings_) {
+    LOG(LS_INFO) << "Removed binding " << binding->username() << ", "
+                 << bindings_.size() << " remaining";
+  }
+}
+
+void RelayServer::OnMessage(talk_base::Message *pmsg) {
+  ASSERT(pmsg->message_id == kMessageAcceptConnection);
+  talk_base::MessageData* data = pmsg->pdata;
+  talk_base::AsyncSocket* socket =
+      static_cast <talk_base::TypedMessageData<talk_base::AsyncSocket*>*>
+      (data)->data();
+  AcceptConnection(socket);
+  delete data;
+}
+
+void RelayServer::OnTimeout(RelayServerBinding* binding) {
+  // This call will result in all of the necessary clean-up. We can't call
+  // delete here, because you can't delete an object that is signaling you.
+  thread_->Dispose(binding);
+}
+
+void RelayServer::AcceptConnection(talk_base::AsyncSocket* server_socket) {
+  // Check if someone is trying to connect to us.
+  talk_base::SocketAddress accept_addr;
+  talk_base::AsyncSocket* accepted_socket =
+      server_socket->Accept(&accept_addr);
+  if (accepted_socket != NULL) {
+    // We had someone trying to connect, now check which protocol to
+    // use and create a packet socket.
+    ASSERT(server_sockets_[server_socket] == cricket::PROTO_TCP ||
+           server_sockets_[server_socket] == cricket::PROTO_SSLTCP);
+    if (server_sockets_[server_socket] == cricket::PROTO_SSLTCP) {
+      accepted_socket = new talk_base::AsyncSSLServerSocket(accepted_socket);
+    }
+    talk_base::AsyncTCPSocket* tcp_socket =
+        new talk_base::AsyncTCPSocket(accepted_socket, false);
+
+    // Finally add the socket so it can start communicating with the client.
+    AddInternalSocket(tcp_socket);
+  }
+}
+
+RelayServerConnection::RelayServerConnection(
+    RelayServerBinding* binding, const talk_base::SocketAddressPair& addrs,
+    talk_base::AsyncPacketSocket* socket)
+  : binding_(binding), addr_pair_(addrs), socket_(socket), locked_(false) {
+  // The creation of a new connection constitutes a use of the binding.
+  binding_->NoteUsed();
+}
+
+RelayServerConnection::~RelayServerConnection() {
+  // Remove this connection from the server's map (if it exists there).
+  binding_->server()->RemoveConnection(this);
+}
+
+void RelayServerConnection::Send(const char* data, size_t size) {
+  // Note that the binding has been used again.
+  binding_->NoteUsed();
+
+  cricket::Send(socket_, data, size, addr_pair_.source());
+}
+
+void RelayServerConnection::Send(
+    const char* data, size_t size, const talk_base::SocketAddress& from_addr) {
+  // If the from address is known to the client, we don't need to send it.
+  if (locked() && (from_addr == default_dest_)) {
+    Send(data, size);
+    return;
+  }
+
+  // Wrap the given data in a data-indication packet.
+
+  RelayMessage msg;
+  msg.SetType(STUN_DATA_INDICATION);
+
+  StunByteStringAttribute* magic_cookie_attr =
+      StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE);
+  magic_cookie_attr->CopyBytes(binding_->magic_cookie().c_str(),
+                               binding_->magic_cookie().size());
+  msg.AddAttribute(magic_cookie_attr);
+
+  StunAddressAttribute* addr_attr =
+      StunAttribute::CreateAddress(STUN_ATTR_SOURCE_ADDRESS2);
+  addr_attr->SetIP(from_addr.ipaddr());
+  addr_attr->SetPort(from_addr.port());
+  msg.AddAttribute(addr_attr);
+
+  StunByteStringAttribute* data_attr =
+      StunAttribute::CreateByteString(STUN_ATTR_DATA);
+  ASSERT(size <= 65536);
+  data_attr->CopyBytes(data, uint16(size));
+  msg.AddAttribute(data_attr);
+
+  SendStun(msg);
+}
+
+void RelayServerConnection::SendStun(const StunMessage& msg) {
+  // Note that the binding has been used again.
+  binding_->NoteUsed();
+
+  cricket::SendStun(msg, socket_, addr_pair_.source());
+}
+
+void RelayServerConnection::SendStunError(
+      const StunMessage& request, int error_code, const char* error_desc) {
+  // An error does not indicate use.  If no legitimate use off the binding
+  // occurs, we want it to be cleaned up even if errors are still occuring.
+
+  cricket::SendStunError(
+      request, socket_, addr_pair_.source(), error_code, error_desc,
+      binding_->magic_cookie());
+}
+
+void RelayServerConnection::Lock() {
+  locked_ = true;
+}
+
+void RelayServerConnection::Unlock() {
+  locked_ = false;
+}
+
+// IDs used for posted messages:
+const uint32 MSG_LIFETIME_TIMER = 1;
+
+RelayServerBinding::RelayServerBinding(
+    RelayServer* server, const std::string& username,
+    const std::string& password, uint32 lifetime)
+  : server_(server), username_(username), password_(password),
+    lifetime_(lifetime) {
+  // For now, every connection uses the standard magic cookie value.
+  magic_cookie_.append(
+      reinterpret_cast<const char*>(TURN_MAGIC_COOKIE_VALUE),
+      sizeof(TURN_MAGIC_COOKIE_VALUE));
+
+  // Initialize the last-used time to now.
+  NoteUsed();
+
+  // Set the first timeout check.
+  server_->thread()->PostDelayed(lifetime_, this, MSG_LIFETIME_TIMER);
+}
+
+RelayServerBinding::~RelayServerBinding() {
+  // Clear the outstanding timeout check.
+  server_->thread()->Clear(this);
+
+  // Clean up all of the connections.
+  for (size_t i = 0; i < internal_connections_.size(); ++i)
+    delete internal_connections_[i];
+  for (size_t i = 0; i < external_connections_.size(); ++i)
+    delete external_connections_[i];
+
+  // Remove this binding from the server's map.
+  server_->RemoveBinding(this);
+}
+
+void RelayServerBinding::AddInternalConnection(RelayServerConnection* conn) {
+  internal_connections_.push_back(conn);
+}
+
+void RelayServerBinding::AddExternalConnection(RelayServerConnection* conn) {
+  external_connections_.push_back(conn);
+}
+
+void RelayServerBinding::NoteUsed() {
+  last_used_ = talk_base::Time();
+}
+
+bool RelayServerBinding::HasMagicCookie(const char* bytes, size_t size) const {
+  if (size < 24 + magic_cookie_.size()) {
+    return false;
+  } else {
+    return 0 == std::memcmp(
+        bytes + 24, magic_cookie_.c_str(), magic_cookie_.size());
+  }
+}
+
+RelayServerConnection* RelayServerBinding::GetInternalConnection(
+    const talk_base::SocketAddress& ext_addr) {
+
+  // Look for an internal connection that is locked to this address.
+  for (size_t i = 0; i < internal_connections_.size(); ++i) {
+    if (internal_connections_[i]->locked() &&
+        (ext_addr == internal_connections_[i]->default_destination()))
+      return internal_connections_[i];
+  }
+
+  // If one was not found, we send to the first connection.
+  ASSERT(internal_connections_.size() > 0);
+  return internal_connections_[0];
+}
+
+RelayServerConnection* RelayServerBinding::GetExternalConnection(
+    const talk_base::SocketAddress& ext_addr) {
+  for (size_t i = 0; i < external_connections_.size(); ++i) {
+    if (ext_addr == external_connections_[i]->addr_pair().source())
+      return external_connections_[i];
+  }
+  return 0;
+}
+
+void RelayServerBinding::OnMessage(talk_base::Message *pmsg) {
+  if (pmsg->message_id == MSG_LIFETIME_TIMER) {
+    ASSERT(!pmsg->pdata);
+
+    // If the lifetime timeout has been exceeded, then send a signal.
+    // Otherwise, just keep waiting.
+    if (talk_base::Time() >= last_used_ + lifetime_) {
+      LOG(LS_INFO) << "Expiring binding " << username_;
+      SignalTimeout(this);
+    } else {
+      server_->thread()->PostDelayed(lifetime_, this, MSG_LIFETIME_TIMER);
+    }
+
+  } else {
+    ASSERT(false);
+  }
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/relayserver.h b/talk/p2p/base/relayserver.h
new file mode 100644
index 0000000..a5fcc24
--- /dev/null
+++ b/talk/p2p/base/relayserver.h
@@ -0,0 +1,249 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_RELAYSERVER_H_
+#define TALK_P2P_BASE_RELAYSERVER_H_
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include "talk/base/asyncudpsocket.h"
+#include "talk/base/socketaddresspair.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+#include "talk/p2p/base/port.h"
+#include "talk/p2p/base/stun.h"
+
+namespace cricket {
+
+class RelayServerBinding;
+class RelayServerConnection;
+
+// Relays traffic between connections to the server that are "bound" together.
+// All connections created with the same username/password are bound together.
+class RelayServer : public talk_base::MessageHandler,
+                    public sigslot::has_slots<> {
+ public:
+  // Creates a server, which will use this thread to post messages to itself.
+  explicit RelayServer(talk_base::Thread* thread);
+  ~RelayServer();
+
+  talk_base::Thread* thread() { return thread_; }
+
+  // Indicates whether we will print updates of the number of bindings.
+  bool log_bindings() const { return log_bindings_; }
+  void set_log_bindings(bool log_bindings) { log_bindings_ = log_bindings; }
+
+  // Updates the set of sockets that the server uses to talk to "internal"
+  // clients.  These are clients that do the "port allocations".
+  void AddInternalSocket(talk_base::AsyncPacketSocket* socket);
+  void RemoveInternalSocket(talk_base::AsyncPacketSocket* socket);
+
+  // Updates the set of sockets that the server uses to talk to "external"
+  // clients.  These are the clients that do not do allocations.  They do not
+  // know that these addresses represent a relay server.
+  void AddExternalSocket(talk_base::AsyncPacketSocket* socket);
+  void RemoveExternalSocket(talk_base::AsyncPacketSocket* socket);
+
+  // Starts listening for connections on this sockets. When someone
+  // tries to connect, the connection will be accepted and a new
+  // internal socket will be added.
+  void AddInternalServerSocket(talk_base::AsyncSocket* socket,
+                               cricket::ProtocolType proto);
+
+  // Removes this server socket from the list.
+  void RemoveInternalServerSocket(talk_base::AsyncSocket* socket);
+
+  // Methods for testing and debuging.
+  int GetConnectionCount() const;
+  talk_base::SocketAddressPair GetConnection(int connection) const;
+  bool HasConnection(const talk_base::SocketAddress& address) const;
+
+ private:
+  typedef std::vector<talk_base::AsyncPacketSocket*> SocketList;
+  typedef std::map<talk_base::AsyncSocket*,
+                   cricket::ProtocolType> ServerSocketMap;
+  typedef std::map<std::string, RelayServerBinding*> BindingMap;
+  typedef std::map<talk_base::SocketAddressPair,
+                   RelayServerConnection*> ConnectionMap;
+
+  talk_base::Thread* thread_;
+  bool log_bindings_;
+  SocketList internal_sockets_;
+  SocketList external_sockets_;
+  ServerSocketMap server_sockets_;
+  BindingMap bindings_;
+  ConnectionMap connections_;
+
+  // Called when a packet is received by the server on one of its sockets.
+  void OnInternalPacket(talk_base::AsyncPacketSocket* socket,
+                        const char* bytes, size_t size,
+                        const talk_base::SocketAddress& remote_addr);
+  void OnExternalPacket(talk_base::AsyncPacketSocket* socket,
+                        const char* bytes, size_t size,
+                        const talk_base::SocketAddress& remote_addr);
+
+  void OnReadEvent(talk_base::AsyncSocket* socket);
+
+  // Processes the relevant STUN request types from the client.
+  bool HandleStun(const char* bytes, size_t size,
+                  const talk_base::SocketAddress& remote_addr,
+                  talk_base::AsyncPacketSocket* socket,
+                  std::string* username, StunMessage* msg);
+  void HandleStunAllocate(const char* bytes, size_t size,
+                          const talk_base::SocketAddressPair& ap,
+                          talk_base::AsyncPacketSocket* socket);
+  void HandleStun(RelayServerConnection* int_conn, const char* bytes,
+                  size_t size);
+  void HandleStunAllocate(RelayServerConnection* int_conn,
+                          const StunMessage& msg);
+  void HandleStunSend(RelayServerConnection* int_conn, const StunMessage& msg);
+
+  // Adds/Removes the a connection or binding.
+  void AddConnection(RelayServerConnection* conn);
+  void RemoveConnection(RelayServerConnection* conn);
+  void RemoveBinding(RelayServerBinding* binding);
+
+  // Handle messages in our worker thread.
+  void OnMessage(talk_base::Message *pmsg);
+
+  // Called when the timer for checking lifetime times out.
+  void OnTimeout(RelayServerBinding* binding);
+
+  // Accept connections on this server socket.
+  void AcceptConnection(talk_base::AsyncSocket* server_socket);
+
+  friend class RelayServerConnection;
+  friend class RelayServerBinding;
+};
+
+// Maintains information about a connection to the server.  Each connection is
+// part of one and only one binding.
+class RelayServerConnection {
+ public:
+  RelayServerConnection(RelayServerBinding* binding,
+                        const talk_base::SocketAddressPair& addrs,
+                        talk_base::AsyncPacketSocket* socket);
+  ~RelayServerConnection();
+
+  RelayServerBinding* binding() { return binding_; }
+  talk_base::AsyncPacketSocket* socket() { return socket_; }
+
+  // Returns a pair where the source is the remote address and the destination
+  // is the local address.
+  const talk_base::SocketAddressPair& addr_pair() { return addr_pair_; }
+
+  // Sends a packet to the connected client.  If an address is provided, then
+  // we make sure the internal client receives it, wrapping if necessary.
+  void Send(const char* data, size_t size);
+  void Send(const char* data, size_t size,
+            const talk_base::SocketAddress& ext_addr);
+
+  // Sends a STUN message to the connected client with no wrapping.
+  void SendStun(const StunMessage& msg);
+  void SendStunError(const StunMessage& request, int code, const char* desc);
+
+  // A locked connection is one for which we know the intended destination of
+  // any raw packet received.
+  bool locked() const { return locked_; }
+  void Lock();
+  void Unlock();
+
+  // Records the address that raw packets should be forwarded to (for internal
+  // packets only; for external, we already know where they go).
+  const talk_base::SocketAddress& default_destination() const {
+    return default_dest_;
+  }
+  void set_default_destination(const talk_base::SocketAddress& addr) {
+    default_dest_ = addr;
+  }
+
+ private:
+  RelayServerBinding* binding_;
+  talk_base::SocketAddressPair addr_pair_;
+  talk_base::AsyncPacketSocket* socket_;
+  bool locked_;
+  talk_base::SocketAddress default_dest_;
+};
+
+// Records a set of internal and external connections that we relay between,
+// or in other words, that are "bound" together.
+class RelayServerBinding : public talk_base::MessageHandler {
+ public:
+  RelayServerBinding(
+      RelayServer* server, const std::string& username,
+      const std::string& password, uint32 lifetime);
+  virtual ~RelayServerBinding();
+
+  RelayServer* server() { return server_; }
+  uint32 lifetime() { return lifetime_; }
+  const std::string& username() { return username_; }
+  const std::string& password() { return password_; }
+  const std::string& magic_cookie() { return magic_cookie_; }
+
+  // Adds/Removes a connection into the binding.
+  void AddInternalConnection(RelayServerConnection* conn);
+  void AddExternalConnection(RelayServerConnection* conn);
+
+  // We keep track of the use of each binding.  If we detect that it was not
+  // used for longer than the lifetime, then we send a signal.
+  void NoteUsed();
+  sigslot::signal1<RelayServerBinding*> SignalTimeout;
+
+  // Determines whether the given packet has the magic cookie present (in the
+  // right place).
+  bool HasMagicCookie(const char* bytes, size_t size) const;
+
+  // Determines the connection to use to send packets to or from the given
+  // external address.
+  RelayServerConnection* GetInternalConnection(
+      const talk_base::SocketAddress& ext_addr);
+  RelayServerConnection* GetExternalConnection(
+      const talk_base::SocketAddress& ext_addr);
+
+  // MessageHandler:
+  void OnMessage(talk_base::Message *pmsg);
+
+ private:
+  RelayServer* server_;
+
+  std::string username_;
+  std::string password_;
+  std::string magic_cookie_;
+
+  std::vector<RelayServerConnection*> internal_connections_;
+  std::vector<RelayServerConnection*> external_connections_;
+
+  uint32 lifetime_;
+  uint32 last_used_;
+  // TODO: bandwidth
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_RELAYSERVER_H_
diff --git a/talk/p2p/base/relayserver_main.cc b/talk/p2p/base/relayserver_main.cc
new file mode 100644
index 0000000..11e8a5b
--- /dev/null
+++ b/talk/p2p/base/relayserver_main.cc
@@ -0,0 +1,80 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <iostream>  // NOLINT
+
+#include "talk/base/thread.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/p2p/base/relayserver.h"
+
+int main(int argc, char **argv) {
+  if (argc != 3) {
+    std::cerr << "usage: relayserver internal-address external-address"
+              << std::endl;
+    return 1;
+  }
+
+  talk_base::SocketAddress int_addr;
+  if (!int_addr.FromString(argv[1])) {
+    std::cerr << "Unable to parse IP address: " << argv[1];
+    return 1;
+  }
+
+  talk_base::SocketAddress ext_addr;
+  if (!ext_addr.FromString(argv[2])) {
+    std::cerr << "Unable to parse IP address: " << argv[2];
+    return 1;
+  }
+
+  talk_base::Thread *pthMain = talk_base::Thread::Current();
+
+  talk_base::scoped_ptr<talk_base::AsyncUDPSocket> int_socket(
+      talk_base::AsyncUDPSocket::Create(pthMain->socketserver(), int_addr));
+  if (!int_socket) {
+    std::cerr << "Failed to create a UDP socket bound at"
+              << int_addr.ToString() << std::endl;
+    return 1;
+  }
+
+  talk_base::scoped_ptr<talk_base::AsyncUDPSocket> ext_socket(
+      talk_base::AsyncUDPSocket::Create(pthMain->socketserver(), ext_addr));
+  if (!ext_socket) {
+    std::cerr << "Failed to create a UDP socket bound at"
+              << ext_addr.ToString() << std::endl;
+    return 1;
+  }
+
+  cricket::RelayServer server(pthMain);
+  server.AddInternalSocket(int_socket.get());
+  server.AddExternalSocket(ext_socket.get());
+
+  std::cout << "Listening internally at " << int_addr.ToString() << std::endl;
+  std::cout << "Listening externally at " << ext_addr.ToString() << std::endl;
+
+  pthMain->Run();
+  return 0;
+}
diff --git a/talk/p2p/base/relayserver_unittest.cc b/talk/p2p/base/relayserver_unittest.cc
new file mode 100644
index 0000000..349fe08
--- /dev/null
+++ b/talk/p2p/base/relayserver_unittest.cc
@@ -0,0 +1,539 @@
+/*
+ * 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 RelayMessage();
+      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(int type) {
+    StunMessage* msg = new RelayMessage();
+    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 eaten.
+TEST_F(RelayServerTest, TestBadRequest) {
+  talk_base::scoped_ptr<StunMessage> res;
+
+  SendRaw1(bad, std::strlen(bad));
+  res.reset(Receive1());
+
+  ASSERT_TRUE(!res);
+}
+
+// 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);
+  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->eclass());
+  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);
+  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->eclass());
+  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);
+  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);
+  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);
+  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);
+  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->eclass());
+  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);
+  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->eclass());
+  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);
+  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->eclass());
+  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);
+  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->eclass());
+  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);
+  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->eclass());
+  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);
+    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->eclass());
+  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
new file mode 100644
index 0000000..3478a33
--- /dev/null
+++ b/talk/p2p/base/session.cc
@@ -0,0 +1,1659 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/p2p/base/session.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/helpers.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sslstreamadapter.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/jid.h"
+#include "talk/p2p/base/dtlstransport.h"
+#include "talk/p2p/base/p2ptransport.h"
+#include "talk/p2p/base/sessionclient.h"
+#include "talk/p2p/base/transport.h"
+#include "talk/p2p/base/transportchannelproxy.h"
+#include "talk/p2p/base/transportinfo.h"
+
+#include "talk/p2p/base/constants.h"
+
+namespace cricket {
+
+bool BadMessage(const buzz::QName type,
+                const std::string& text,
+                MessageError* err) {
+  err->SetType(type);
+  err->SetText(text);
+  return false;
+}
+
+TransportProxy::~TransportProxy() {
+  for (ChannelMap::iterator iter = channels_.begin();
+       iter != channels_.end(); ++iter) {
+    iter->second->SignalDestroyed(iter->second);
+    delete iter->second;
+  }
+}
+
+std::string TransportProxy::type() const {
+  return transport_->get()->type();
+}
+
+TransportChannel* TransportProxy::GetChannel(int component) {
+  return GetChannelProxy(component);
+}
+
+TransportChannel* TransportProxy::CreateChannel(
+    const std::string& name, int component) {
+  ASSERT(GetChannel(component) == NULL);
+  ASSERT(!transport_->get()->HasChannel(component));
+
+  // We always create a proxy in case we need to change out the transport later.
+  TransportChannelProxy* channel =
+      new TransportChannelProxy(content_name(), name, component);
+  channels_[component] = channel;
+
+  // If we're already negotiated, create an impl and hook it up to the proxy
+  // channel. If we're connecting, create an impl but don't hook it up yet.
+  if (negotiated_) {
+    SetChannelProxyImpl(component, channel);
+  } else if (connecting_) {
+    GetOrCreateChannelProxyImpl(component);
+  }
+  return channel;
+}
+
+bool TransportProxy::HasChannel(int component) {
+  return transport_->get()->HasChannel(component);
+}
+
+void TransportProxy::DestroyChannel(int component) {
+  TransportChannel* channel = GetChannel(component);
+  if (channel) {
+    // If the state of TransportProxy is not NEGOTIATED
+    // then TransportChannelProxy and its impl are not
+    // connected. Both must be connected before
+    // deletion.
+    if (!negotiated_) {
+      SetChannelProxyImpl(component, GetChannelProxy(component));
+    }
+
+    channels_.erase(component);
+    channel->SignalDestroyed(channel);
+    delete channel;
+  }
+}
+
+void TransportProxy::ConnectChannels() {
+  if (!connecting_) {
+    if (!negotiated_) {
+      for (ChannelMap::iterator iter = channels_.begin();
+           iter != channels_.end(); ++iter) {
+        GetOrCreateChannelProxyImpl(iter->first);
+      }
+    }
+    connecting_ = true;
+  }
+  // TODO(juberti): Right now Transport::ConnectChannels doesn't work if we
+  // don't have any channels yet, so we need to allow this method to be called
+  // multiple times. Once we fix Transport, we can move this call inside the
+  // if (!connecting_) block.
+  transport_->get()->ConnectChannels();
+}
+
+void TransportProxy::CompleteNegotiation() {
+  if (!negotiated_) {
+    for (ChannelMap::iterator iter = channels_.begin();
+         iter != channels_.end(); ++iter) {
+      SetChannelProxyImpl(iter->first, iter->second);
+    }
+    negotiated_ = true;
+  }
+}
+
+void TransportProxy::AddSentCandidates(const Candidates& candidates) {
+  for (Candidates::const_iterator cand = candidates.begin();
+       cand != candidates.end(); ++cand) {
+    sent_candidates_.push_back(*cand);
+  }
+}
+
+void TransportProxy::AddUnsentCandidates(const Candidates& candidates) {
+  for (Candidates::const_iterator cand = candidates.begin();
+       cand != candidates.end(); ++cand) {
+    unsent_candidates_.push_back(*cand);
+  }
+}
+
+bool TransportProxy::GetChannelNameFromComponent(
+    int component, std::string* channel_name) const {
+  const TransportChannelProxy* channel = GetChannelProxy(component);
+  if (channel == NULL) {
+    return false;
+  }
+
+  *channel_name = channel->name();
+  return true;
+}
+
+bool TransportProxy::GetComponentFromChannelName(
+    const std::string& channel_name, int* component) const {
+  const TransportChannelProxy* channel = GetChannelProxyByName(channel_name);
+  if (channel == NULL) {
+    return false;
+  }
+
+  *component = channel->component();
+  return true;
+}
+
+TransportChannelProxy* TransportProxy::GetChannelProxy(int component) const {
+  ChannelMap::const_iterator iter = channels_.find(component);
+  return (iter != channels_.end()) ? iter->second : NULL;
+}
+
+TransportChannelProxy* TransportProxy::GetChannelProxyByName(
+    const std::string& name) const {
+  for (ChannelMap::const_iterator iter = channels_.begin();
+       iter != channels_.end();
+       ++iter) {
+    if (iter->second->name() == name) {
+      return iter->second;
+    }
+  }
+  return NULL;
+}
+
+TransportChannelImpl* TransportProxy::GetOrCreateChannelProxyImpl(
+    int component) {
+  TransportChannelImpl* impl = transport_->get()->GetChannel(component);
+  if (impl == NULL) {
+    impl = transport_->get()->CreateChannel(component);
+    impl->SetSessionId(sid_);
+  }
+  return impl;
+}
+
+void TransportProxy::SetChannelProxyImpl(
+    int component, TransportChannelProxy* transproxy) {
+  TransportChannelImpl* impl = GetOrCreateChannelProxyImpl(component);
+  ASSERT(impl != NULL);
+  transproxy->SetImplementation(impl);
+}
+
+// This function muxes |this| onto |target| by repointing |this| at
+// |target|'s transport and setting our TransportChannelProxies
+// to point to |target|'s underlying implementations.
+bool TransportProxy::SetupMux(TransportProxy* target) {
+  // Bail out if there's nothing to do.
+  if (transport_ == target->transport_) {
+    return true;
+  }
+
+  // Run through all channels and remove any non-rtp transport channels before
+  // setting target transport channels.
+  for (ChannelMap::const_iterator iter = channels_.begin();
+       iter != channels_.end(); ++iter) {
+    if (!target->transport_->get()->HasChannel(iter->first)) {
+      // Remove if channel doesn't exist in |transport_|.
+      iter->second->SetImplementation(NULL);
+    } else {
+      // Replace the impl for all the TransportProxyChannels with the channels
+      // from |target|'s transport. Fail if there's not an exact match.
+      iter->second->SetImplementation(
+          target->transport_->get()->CreateChannel(iter->first));
+    }
+  }
+
+  // Now replace our transport. Must happen afterwards because
+  // it deletes all impls as a side effect.
+  transport_ = target->transport_;
+  transport_->get()->SignalCandidatesReady.connect(
+      this, &TransportProxy::OnTransportCandidatesReady);
+  set_candidates_allocated(target->candidates_allocated());
+  return true;
+}
+
+void TransportProxy::SetRole(TransportRole role) {
+  transport_->get()->SetRole(role);
+}
+
+bool TransportProxy::SetLocalTransportDescription(
+    const TransportDescription& description, ContentAction action) {
+  // If this is an answer, finalize the negotiation.
+  if (action == CA_ANSWER) {
+    CompleteNegotiation();
+  }
+  return transport_->get()->SetLocalTransportDescription(description, action);
+}
+
+bool TransportProxy::SetRemoteTransportDescription(
+    const TransportDescription& description, ContentAction action) {
+  // If this is an answer, finalize the negotiation.
+  if (action == CA_ANSWER) {
+    CompleteNegotiation();
+  }
+  return transport_->get()->SetRemoteTransportDescription(description, action);
+}
+
+void TransportProxy::OnSignalingReady() {
+  // If we're starting a new allocation sequence, reset our state.
+  set_candidates_allocated(false);
+  transport_->get()->OnSignalingReady();
+}
+
+bool TransportProxy::OnRemoteCandidates(const Candidates& candidates,
+                                        std::string* error) {
+  // Ensure the transport is negotiated before handling candidates.
+  // TODO(juberti): Remove this once everybody calls SetLocalTD.
+  CompleteNegotiation();
+
+  // Verify each candidate before passing down to transport layer.
+  for (Candidates::const_iterator cand = candidates.begin();
+       cand != candidates.end(); ++cand) {
+    if (!transport_->get()->VerifyCandidate(*cand, error))
+      return false;
+    if (!HasChannel(cand->component())) {
+      *error = "Candidate has unknown component: " + cand->ToString() +
+               " for content: " + content_name_;
+      return false;
+    }
+  }
+  transport_->get()->OnRemoteCandidates(candidates);
+  return true;
+}
+
+std::string BaseSession::StateToString(State state) {
+  switch (state) {
+    case Session::STATE_INIT:
+      return "STATE_INIT";
+    case Session::STATE_SENTINITIATE:
+      return "STATE_SENTINITIATE";
+    case Session::STATE_RECEIVEDINITIATE:
+      return "STATE_RECEIVEDINITIATE";
+    case Session::STATE_SENTPRACCEPT:
+      return "STATE_SENTPRACCEPT";
+    case Session::STATE_SENTACCEPT:
+      return "STATE_SENTACCEPT";
+    case Session::STATE_RECEIVEDPRACCEPT:
+      return "STATE_RECEIVEDPRACCEPT";
+    case Session::STATE_RECEIVEDACCEPT:
+      return "STATE_RECEIVEDACCEPT";
+    case Session::STATE_SENTMODIFY:
+      return "STATE_SENTMODIFY";
+    case Session::STATE_RECEIVEDMODIFY:
+      return "STATE_RECEIVEDMODIFY";
+    case Session::STATE_SENTREJECT:
+      return "STATE_SENTREJECT";
+    case Session::STATE_RECEIVEDREJECT:
+      return "STATE_RECEIVEDREJECT";
+    case Session::STATE_SENTREDIRECT:
+      return "STATE_SENTREDIRECT";
+    case Session::STATE_SENTTERMINATE:
+      return "STATE_SENTTERMINATE";
+    case Session::STATE_RECEIVEDTERMINATE:
+      return "STATE_RECEIVEDTERMINATE";
+    case Session::STATE_INPROGRESS:
+      return "STATE_INPROGRESS";
+    case Session::STATE_DEINIT:
+      return "STATE_DEINIT";
+    default:
+      break;
+  }
+  return "STATE_" + talk_base::ToString(state);
+}
+
+BaseSession::BaseSession(talk_base::Thread* signaling_thread,
+                         talk_base::Thread* worker_thread,
+                         PortAllocator* port_allocator,
+                         const std::string& sid,
+                         const std::string& content_type,
+                         bool initiator)
+    : state_(STATE_INIT),
+      error_(ERROR_NONE),
+      signaling_thread_(signaling_thread),
+      worker_thread_(worker_thread),
+      port_allocator_(port_allocator),
+      sid_(sid),
+      content_type_(content_type),
+      transport_type_(NS_GINGLE_P2P),
+      initiator_(initiator),
+      identity_(NULL),
+      local_description_(NULL),
+      remote_description_(NULL),
+      ice_tiebreaker_(talk_base::CreateRandomId64()),
+      role_switch_(false) {
+  ASSERT(signaling_thread->IsCurrent());
+}
+
+BaseSession::~BaseSession() {
+  ASSERT(signaling_thread()->IsCurrent());
+
+  ASSERT(state_ != STATE_DEINIT);
+  LogState(state_, STATE_DEINIT);
+  state_ = STATE_DEINIT;
+  SignalState(this, state_);
+
+  for (TransportMap::iterator iter = transports_.begin();
+       iter != transports_.end(); ++iter) {
+    delete iter->second;
+  }
+
+  delete remote_description_;
+  delete local_description_;
+}
+
+bool BaseSession::PushdownTransportDescription(ContentSource source,
+                                               ContentAction action) {
+  if (source == CS_LOCAL) {
+    return PushdownLocalTransportDescription(local_description_, action);
+  }
+  return PushdownRemoteTransportDescription(remote_description_, action);
+}
+
+bool BaseSession::PushdownLocalTransportDescription(
+    const SessionDescription* sdesc,
+    ContentAction action) {
+  // Update the Transports with the right information, and trigger them to
+  // start connecting.
+  for (TransportMap::iterator iter = transports_.begin();
+       iter != transports_.end(); ++iter) {
+    // If no transport info was in this session description, ret == false
+    // and we just skip this one.
+    TransportDescription tdesc;
+    bool ret = GetTransportDescription(
+        sdesc, iter->second->content_name(), &tdesc);
+    if (ret) {
+      if (!iter->second->SetLocalTransportDescription(tdesc, action)) {
+        return false;
+      }
+
+      iter->second->ConnectChannels();
+    }
+  }
+
+  return true;
+}
+
+bool BaseSession::PushdownRemoteTransportDescription(
+    const SessionDescription* sdesc,
+    ContentAction action) {
+  // Update the Transports with the right information.
+  for (TransportMap::iterator iter = transports_.begin();
+       iter != transports_.end(); ++iter) {
+    TransportDescription tdesc;
+
+    // If no transport info was in this session description, ret == false
+    // and we just skip this one.
+    bool ret = GetTransportDescription(
+        sdesc, iter->second->content_name(), &tdesc);
+    if (ret) {
+      if (!iter->second->SetRemoteTransportDescription(tdesc, action)) {
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
+TransportChannel* BaseSession::CreateChannel(const std::string& content_name,
+                                             const std::string& channel_name,
+                                             int component) {
+  // We create the proxy "on demand" here because we need to support
+  // creating channels at any time, even before we send or receive
+  // initiate messages, which is before we create the transports.
+  TransportProxy* transproxy = GetOrCreateTransportProxy(content_name);
+  return transproxy->CreateChannel(channel_name, component);
+}
+
+TransportChannel* BaseSession::GetChannel(const std::string& content_name,
+                                          int component) {
+  TransportProxy* transproxy = GetTransportProxy(content_name);
+  if (transproxy == NULL)
+    return NULL;
+  else
+    return transproxy->GetChannel(component);
+}
+
+void BaseSession::DestroyChannel(const std::string& content_name,
+                                 int component) {
+  TransportProxy* transproxy = GetTransportProxy(content_name);
+  ASSERT(transproxy != NULL);
+  transproxy->DestroyChannel(component);
+}
+
+TransportProxy* BaseSession::GetOrCreateTransportProxy(
+    const std::string& content_name) {
+  TransportProxy* transproxy = GetTransportProxy(content_name);
+  if (transproxy)
+    return transproxy;
+
+  Transport* transport = CreateTransport(content_name);
+  transport->SetRole(initiator_ ? ROLE_CONTROLLING : ROLE_CONTROLLED);
+  transport->SetTiebreaker(ice_tiebreaker_);
+  // TODO: Connect all the Transport signals to TransportProxy
+  // then to the BaseSession.
+  transport->SignalConnecting.connect(
+      this, &BaseSession::OnTransportConnecting);
+  transport->SignalWritableState.connect(
+      this, &BaseSession::OnTransportWritable);
+  transport->SignalRequestSignaling.connect(
+      this, &BaseSession::OnTransportRequestSignaling);
+  transport->SignalTransportError.connect(
+      this, &BaseSession::OnTransportSendError);
+  transport->SignalRouteChange.connect(
+      this, &BaseSession::OnTransportRouteChange);
+  transport->SignalCandidatesAllocationDone.connect(
+      this, &BaseSession::OnTransportCandidatesAllocationDone);
+  transport->SignalRoleConflict.connect(
+      this, &BaseSession::OnRoleConflict);
+
+  transproxy = new TransportProxy(sid_, content_name,
+                                  new TransportWrapper(transport));
+  transproxy->SignalCandidatesReady.connect(
+      this, &BaseSession::OnTransportProxyCandidatesReady);
+  transports_[content_name] = transproxy;
+
+  return transproxy;
+}
+
+Transport* BaseSession::GetTransport(const std::string& content_name) {
+  TransportProxy* transproxy = GetTransportProxy(content_name);
+  if (transproxy == NULL)
+    return NULL;
+  return transproxy->impl();
+}
+
+TransportProxy* BaseSession::GetTransportProxy(
+    const std::string& content_name) {
+  TransportMap::iterator iter = transports_.find(content_name);
+  return (iter != transports_.end()) ? iter->second : NULL;
+}
+
+TransportProxy* BaseSession::GetTransportProxy(const Transport* transport) {
+  for (TransportMap::iterator iter = transports_.begin();
+       iter != transports_.end(); ++iter) {
+    TransportProxy* transproxy = iter->second;
+    if (transproxy->impl() == transport) {
+      return transproxy;
+    }
+  }
+  return NULL;
+}
+
+TransportProxy* BaseSession::GetFirstTransportProxy() {
+  if (transports_.empty())
+    return NULL;
+  return transports_.begin()->second;
+}
+
+void BaseSession::DestroyTransportProxy(
+    const std::string& content_name) {
+  TransportMap::iterator iter = transports_.find(content_name);
+  if (iter != transports_.end()) {
+    delete iter->second;
+    transports_.erase(content_name);
+  }
+}
+
+cricket::Transport* BaseSession::CreateTransport(
+    const std::string& content_name) {
+  ASSERT(transport_type_ == NS_GINGLE_P2P);
+  return new cricket::DtlsTransport<P2PTransport>(
+      signaling_thread(), worker_thread(), content_name,
+      port_allocator(), identity_);
+}
+
+bool BaseSession::GetStats(SessionStats* stats) {
+  for (TransportMap::iterator iter = transports_.begin();
+       iter != transports_.end(); ++iter) {
+    std::string proxy_id = iter->second->content_name();
+    // We are ignoring not-yet-instantiated transports.
+    if (iter->second->impl()) {
+      std::string transport_id = iter->second->impl()->content_name();
+      stats->proxy_to_transport[proxy_id] = transport_id;
+      if (stats->transport_stats.find(transport_id)
+          == stats->transport_stats.end()) {
+        TransportStats subinfos;
+        if (!iter->second->impl()->GetStats(&subinfos)) {
+          return false;
+        }
+        stats->transport_stats[transport_id] = subinfos;
+      }
+    }
+  }
+  return true;
+}
+
+void BaseSession::SetState(State state) {
+  ASSERT(signaling_thread_->IsCurrent());
+  if (state != state_) {
+    LogState(state_, state);
+    state_ = state;
+    SignalState(this, state_);
+    signaling_thread_->Post(this, MSG_STATE);
+  }
+  SignalNewDescription();
+}
+
+void BaseSession::SetError(Error error) {
+  ASSERT(signaling_thread_->IsCurrent());
+  if (error != error_) {
+    error_ = error;
+    SignalError(this, error);
+  }
+}
+
+void BaseSession::OnSignalingReady() {
+  ASSERT(signaling_thread()->IsCurrent());
+  for (TransportMap::iterator iter = transports_.begin();
+       iter != transports_.end(); ++iter) {
+    iter->second->OnSignalingReady();
+  }
+}
+
+// TODO(juberti): Since PushdownLocalTD now triggers the connection process to
+// start, remove this method once everyone calls PushdownLocalTD.
+void BaseSession::SpeculativelyConnectAllTransportChannels() {
+  // Put all transports into the connecting state.
+  for (TransportMap::iterator iter = transports_.begin();
+       iter != transports_.end(); ++iter) {
+    iter->second->ConnectChannels();
+  }
+}
+
+bool BaseSession::OnRemoteCandidates(const std::string& content_name,
+                                     const Candidates& candidates,
+                                     std::string* error) {
+  // Give candidates to the appropriate transport, and tell that transport
+  // to start connecting, if it's not already doing so.
+  TransportProxy* transproxy = GetTransportProxy(content_name);
+  if (!transproxy) {
+    *error = "Unknown content name " + content_name;
+    return false;
+  }
+  if (!transproxy->OnRemoteCandidates(candidates, error)) {
+    return false;
+  }
+  // TODO(juberti): Remove this call once we can be sure that we always have
+  // a local transport description (which will trigger the connection).
+  transproxy->ConnectChannels();
+  return true;
+}
+
+bool BaseSession::MaybeEnableMuxingSupport() {
+  // We need both a local and remote description to decide if we should mux.
+  if ((state_ == STATE_SENTINITIATE ||
+      state_ == STATE_RECEIVEDINITIATE) &&
+      ((local_description_ == NULL) ||
+      (remote_description_ == NULL))) {
+    return false;
+  }
+
+  // In order to perform the multiplexing, we need all proxies to be in the
+  // negotiated state, i.e. to have implementations underneath.
+  // Ensure that this is the case, regardless of whether we are going to mux.
+  for (TransportMap::iterator iter = transports_.begin();
+       iter != transports_.end(); ++iter) {
+    ASSERT(iter->second->negotiated());
+    if (!iter->second->negotiated())
+      return false;
+  }
+
+  // If both sides agree to BUNDLE, mux all the specified contents onto the
+  // transport belonging to the first content name in the BUNDLE group.
+  // If the contents are already muxed, this will be a no-op.
+  // TODO(juberti): Should this check that local and remote have configured
+  // BUNDLE the same way?
+  bool candidates_allocated = IsCandidateAllocationDone();
+  const ContentGroup* local_bundle_group =
+      local_description()->GetGroupByName(GROUP_TYPE_BUNDLE);
+  const ContentGroup* remote_bundle_group =
+      remote_description()->GetGroupByName(GROUP_TYPE_BUNDLE);
+  if (local_bundle_group && remote_bundle_group &&
+      local_bundle_group->FirstContentName()) {
+    const std::string* content_name = local_bundle_group->FirstContentName();
+    const ContentInfo* content =
+        local_description_->GetContentByName(*content_name);
+    ASSERT(content != NULL);
+    if (!SetSelectedProxy(content->name, local_bundle_group)) {
+      LOG(LS_WARNING) << "Failed to set up BUNDLE";
+      return false;
+    }
+
+    // If we weren't done gathering before, we might be done now, as a result
+    // of enabling mux.
+    LOG(LS_INFO) << "Enabling BUNDLE, bundling onto transport: "
+                 << *content_name;
+    if (!candidates_allocated) {
+      MaybeCandidateAllocationDone();
+    }
+  } else {
+    LOG(LS_INFO) << "No BUNDLE information, not bundling.";
+  }
+  return true;
+}
+
+bool BaseSession::SetSelectedProxy(const std::string& content_name,
+                                   const ContentGroup* muxed_group) {
+  TransportProxy* selected_proxy = GetTransportProxy(content_name);
+  if (!selected_proxy) {
+    return false;
+  }
+
+  ASSERT(selected_proxy->negotiated());
+  for (TransportMap::iterator iter = transports_.begin();
+       iter != transports_.end(); ++iter) {
+    // If content is part of the mux group, then repoint its proxy at the
+    // transport object that we have chosen to mux onto. If the proxy
+    // is already pointing at the right object, it will be a no-op.
+    if (muxed_group->HasContentName(iter->first) &&
+        !iter->second->SetupMux(selected_proxy)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+void BaseSession::OnTransportCandidatesAllocationDone(Transport* transport) {
+  // TODO(juberti): This is a clunky way of processing the done signal. Instead,
+  // TransportProxy should receive the done signal directly, set its allocated
+  // flag internally, and then reissue the done signal to Session.
+  // Overall we should make TransportProxy receive *all* the signals from
+  // Transport, since this removes the need to manually iterate over all
+  // the transports, as is needed to make sure signals are handled properly
+  // when BUNDLEing.
+#if 0
+  ASSERT(!IsCandidateAllocationDone());
+#endif
+  for (TransportMap::iterator iter = transports_.begin();
+       iter != transports_.end(); ++iter) {
+    if (iter->second->impl() == transport) {
+      iter->second->set_candidates_allocated(true);
+    }
+  }
+  MaybeCandidateAllocationDone();
+}
+
+bool BaseSession::IsCandidateAllocationDone() const {
+  for (TransportMap::const_iterator iter = transports_.begin();
+       iter != transports_.end(); ++iter) {
+    if (!iter->second->candidates_allocated())
+      return false;
+  }
+  return true;
+}
+
+void BaseSession::MaybeCandidateAllocationDone() {
+  if (IsCandidateAllocationDone()) {
+    LOG(LS_INFO) << "Candidate gathering is complete.";
+    OnCandidatesAllocationDone();
+  }
+}
+
+void BaseSession::OnRoleConflict() {
+  if (role_switch_) {
+    LOG(LS_WARNING) << "Repeat of role conflict signal from Transport.";
+    return;
+  }
+
+  role_switch_ = true;
+  for (TransportMap::iterator iter = transports_.begin();
+       iter != transports_.end(); ++iter) {
+    // Role will be reverse of initial role setting.
+    TransportRole role = initiator_ ? ROLE_CONTROLLED : ROLE_CONTROLLING;
+    iter->second->SetRole(role);
+  }
+}
+
+void BaseSession::LogState(State old_state, State new_state) {
+  LOG(LS_INFO) << "Session:" << id()
+               << " Old state:" << StateToString(old_state)
+               << " New state:" << StateToString(new_state)
+               << " Type:" << content_type()
+               << " Transport:" << transport_type();
+}
+
+bool BaseSession::GetTransportDescription(const SessionDescription* description,
+                                          const std::string& content_name,
+                                          TransportDescription* tdesc) {
+  if (!description || !tdesc) {
+    return false;
+  }
+  const TransportInfo* transport_info =
+      description->GetTransportInfoByName(content_name);
+  if (!transport_info) {
+    return false;
+  }
+  *tdesc = transport_info->description;
+  return true;
+}
+
+void BaseSession::SignalNewDescription() {
+  ContentAction action;
+  ContentSource source;
+  if (!GetContentAction(&action, &source)) {
+    return;
+  }
+  if (source == CS_LOCAL) {
+    SignalNewLocalDescription(this, action);
+  } else {
+    SignalNewRemoteDescription(this, action);
+  }
+}
+
+bool BaseSession::GetContentAction(ContentAction* action,
+                                   ContentSource* source) {
+  switch (state_) {
+    // new local description
+    case STATE_SENTINITIATE:
+      *action = CA_OFFER;
+      *source = CS_LOCAL;
+      break;
+    case STATE_SENTPRACCEPT:
+      *action = CA_PRANSWER;
+      *source = CS_LOCAL;
+      break;
+    case STATE_SENTACCEPT:
+      *action = CA_ANSWER;
+      *source = CS_LOCAL;
+      break;
+    // new remote description
+    case STATE_RECEIVEDINITIATE:
+      *action = CA_OFFER;
+      *source = CS_REMOTE;
+      break;
+    case STATE_RECEIVEDPRACCEPT:
+      *action = CA_PRANSWER;
+      *source = CS_REMOTE;
+      break;
+    case STATE_RECEIVEDACCEPT:
+      *action = CA_ANSWER;
+      *source = CS_REMOTE;
+      break;
+    default:
+      return false;
+  }
+  return true;
+}
+
+void BaseSession::OnMessage(talk_base::Message *pmsg) {
+  switch (pmsg->message_id) {
+  case MSG_TIMEOUT:
+    // Session timeout has occured.
+    SetError(ERROR_TIME);
+    break;
+
+  case MSG_STATE:
+    switch (state_) {
+    case STATE_SENTACCEPT:
+    case STATE_RECEIVEDACCEPT:
+      SetState(STATE_INPROGRESS);
+      break;
+
+    default:
+      // Explicitly ignoring some states here.
+      break;
+    }
+    break;
+  }
+}
+
+Session::Session(SessionManager* session_manager,
+                 const std::string& local_name,
+                 const std::string& initiator_name,
+                 const std::string& sid,
+                 const std::string& content_type,
+                 SessionClient* client)
+    : BaseSession(session_manager->signaling_thread(),
+                  session_manager->worker_thread(),
+                  session_manager->port_allocator(),
+                  sid, content_type, initiator_name == local_name) {
+  ASSERT(client != NULL);
+  session_manager_ = session_manager;
+  local_name_ = local_name;
+  initiator_name_ = initiator_name;
+  transport_parser_ = new P2PTransportParser();
+  client_ = client;
+  initiate_acked_ = false;
+  current_protocol_ = PROTOCOL_HYBRID;
+}
+
+Session::~Session() {
+  delete transport_parser_;
+}
+
+bool Session::Initiate(const std::string &to,
+                       const SessionDescription* sdesc) {
+  ASSERT(signaling_thread()->IsCurrent());
+  SessionError error;
+
+  // Only from STATE_INIT
+  if (state() != STATE_INIT)
+    return false;
+
+  // Setup for signaling.
+  set_remote_name(to);
+  set_local_description(sdesc);
+  if (!CreateTransportProxies(GetEmptyTransportInfos(sdesc->contents()),
+                              &error)) {
+    LOG(LS_ERROR) << "Could not create transports: " << error.text;
+    return false;
+  }
+
+  if (!SendInitiateMessage(sdesc, &error)) {
+    LOG(LS_ERROR) << "Could not send initiate message: " << error.text;
+    return false;
+  }
+
+  // We need to connect transport proxy and impl here so that we can process
+  // the TransportDescriptions.
+  SpeculativelyConnectAllTransportChannels();
+
+  PushdownTransportDescription(CS_LOCAL, CA_OFFER);
+  SetState(Session::STATE_SENTINITIATE);
+  return true;
+}
+
+bool Session::Accept(const SessionDescription* sdesc) {
+  ASSERT(signaling_thread()->IsCurrent());
+
+  // Only if just received initiate
+  if (state() != STATE_RECEIVEDINITIATE)
+    return false;
+
+  // Setup for signaling.
+  set_local_description(sdesc);
+
+  SessionError error;
+  if (!SendAcceptMessage(sdesc, &error)) {
+    LOG(LS_ERROR) << "Could not send accept message: " << error.text;
+    return false;
+  }
+  // TODO(juberti): Add BUNDLE support to transport-info messages.
+  PushdownTransportDescription(CS_LOCAL, CA_ANSWER);
+  MaybeEnableMuxingSupport();  // Enable transport channel mux if supported.
+  SetState(Session::STATE_SENTACCEPT);
+  return true;
+}
+
+bool Session::Reject(const std::string& reason) {
+  ASSERT(signaling_thread()->IsCurrent());
+
+  // Reject is sent in response to an initiate or modify, to reject the
+  // request
+  if (state() != STATE_RECEIVEDINITIATE && state() != STATE_RECEIVEDMODIFY)
+    return false;
+
+  SessionError error;
+  if (!SendRejectMessage(reason, &error)) {
+    LOG(LS_ERROR) << "Could not send reject message: " << error.text;
+    return false;
+  }
+
+  SetState(STATE_SENTREJECT);
+  return true;
+}
+
+bool Session::TerminateWithReason(const std::string& reason) {
+  ASSERT(signaling_thread()->IsCurrent());
+
+  // Either side can terminate, at any time.
+  switch (state()) {
+    case STATE_SENTTERMINATE:
+    case STATE_RECEIVEDTERMINATE:
+      return false;
+
+    case STATE_SENTREJECT:
+    case STATE_RECEIVEDREJECT:
+      // We don't need to send terminate if we sent or received a reject...
+      // it's implicit.
+      break;
+
+    default:
+      SessionError error;
+      if (!SendTerminateMessage(reason, &error)) {
+        LOG(LS_ERROR) << "Could not send terminate message: " << error.text;
+        return false;
+      }
+      break;
+  }
+
+  SetState(STATE_SENTTERMINATE);
+  return true;
+}
+
+bool Session::SendInfoMessage(const XmlElements& elems) {
+  ASSERT(signaling_thread()->IsCurrent());
+  SessionError error;
+  if (!SendMessage(ACTION_SESSION_INFO, elems, &error)) {
+    LOG(LS_ERROR) << "Could not send info message " << error.text;
+    return false;
+  }
+  return true;
+}
+
+bool Session::SendDescriptionInfoMessage(const ContentInfos& contents) {
+  XmlElements elems;
+  WriteError write_error;
+  if (!WriteDescriptionInfo(current_protocol_,
+                            contents,
+                            GetContentParsers(),
+                            &elems, &write_error)) {
+    LOG(LS_ERROR) << "Could not write description info message: "
+                  << write_error.text;
+    return false;
+  }
+  SessionError error;
+  if (!SendMessage(ACTION_DESCRIPTION_INFO, elems, &error)) {
+    LOG(LS_ERROR) << "Could not send description info message: "
+                  << error.text;
+    return false;
+  }
+  return true;
+}
+
+TransportInfos Session::GetEmptyTransportInfos(
+    const ContentInfos& contents) const {
+  TransportInfos tinfos;
+  for (ContentInfos::const_iterator content = contents.begin();
+       content != contents.end(); ++content) {
+    tinfos.push_back(
+        TransportInfo(content->name,
+            TransportDescription(transport_type(), Candidates())));
+  }
+  return tinfos;
+}
+
+bool Session::OnRemoteCandidates(
+    const TransportInfos& tinfos, ParseError* error) {
+  for (TransportInfos::const_iterator tinfo = tinfos.begin();
+       tinfo != tinfos.end(); ++tinfo) {
+    std::string str_error;
+    if (!BaseSession::OnRemoteCandidates(
+        tinfo->content_name, tinfo->description.candidates, &str_error)) {
+      return BadParse(str_error, error);
+    }
+  }
+  return true;
+}
+
+bool Session::CreateTransportProxies(const TransportInfos& tinfos,
+                                     SessionError* error) {
+  for (TransportInfos::const_iterator tinfo = tinfos.begin();
+       tinfo != tinfos.end(); ++tinfo) {
+    if (tinfo->description.transport_type != transport_type()) {
+      error->SetText("No supported transport in offer.");
+      return false;
+    }
+
+    GetOrCreateTransportProxy(tinfo->content_name);
+  }
+  return true;
+}
+
+TransportParserMap Session::GetTransportParsers() {
+  TransportParserMap parsers;
+  parsers[transport_type()] = transport_parser_;
+  return parsers;
+}
+
+CandidateTranslatorMap Session::GetCandidateTranslators() {
+  CandidateTranslatorMap translators;
+  // NOTE: This technique makes it impossible to parse G-ICE
+  // candidates in session-initiate messages because the channels
+  // aren't yet created at that point.  Since we don't use candidates
+  // in session-initiate messages, we should be OK.  Once we switch to
+  // ICE, this translation shouldn't be necessary.
+  for (TransportMap::const_iterator iter = transport_proxies().begin();
+       iter != transport_proxies().end(); ++iter) {
+    translators[iter->first] = iter->second;
+  }
+  return translators;
+}
+
+ContentParserMap Session::GetContentParsers() {
+  ContentParserMap parsers;
+  parsers[content_type()] = client_;
+  // We need to be able parse both RTP-based and SCTP-based Jingle
+  // with the same client.
+  if (content_type() == NS_JINGLE_RTP) {
+    parsers[NS_JINGLE_DRAFT_SCTP] = client_;
+  }
+  return parsers;
+}
+
+void Session::OnTransportRequestSignaling(Transport* transport) {
+  ASSERT(signaling_thread()->IsCurrent());
+  TransportProxy* transproxy = GetTransportProxy(transport);
+  ASSERT(transproxy != NULL);
+  if (transproxy) {
+    // Reset candidate allocation status for the transport proxy.
+    transproxy->set_candidates_allocated(false);
+  }
+  SignalRequestSignaling(this);
+}
+
+void Session::OnTransportConnecting(Transport* transport) {
+  // This is an indication that we should begin watching the writability
+  // state of the transport.
+  OnTransportWritable(transport);
+}
+
+void Session::OnTransportWritable(Transport* transport) {
+  ASSERT(signaling_thread()->IsCurrent());
+
+  // If the transport is not writable, start a timer to make sure that it
+  // becomes writable within a reasonable amount of time.  If it does not, we
+  // terminate since we can't actually send data.  If the transport is writable,
+  // cancel the timer.  Note that writability transitions may occur repeatedly
+  // during the lifetime of the session.
+  signaling_thread()->Clear(this, MSG_TIMEOUT);
+  if (transport->HasChannels() && !transport->writable()) {
+    signaling_thread()->PostDelayed(
+        session_manager_->session_timeout() * 1000, this, MSG_TIMEOUT);
+  }
+}
+
+void Session::OnTransportProxyCandidatesReady(TransportProxy* transproxy,
+                                              const Candidates& candidates) {
+  ASSERT(signaling_thread()->IsCurrent());
+  if (transproxy != NULL) {
+    if (initiator() && !initiate_acked_) {
+      // TODO: This is to work around server re-ordering
+      // messages.  We send the candidates once the session-initiate
+      // is acked.  Once we have fixed the server to guarantee message
+      // order, we can remove this case.
+      transproxy->AddUnsentCandidates(candidates);
+    } else {
+      if (!transproxy->negotiated()) {
+        transproxy->AddSentCandidates(candidates);
+      }
+      SessionError error;
+      if (!SendTransportInfoMessage(transproxy, candidates, &error)) {
+        LOG(LS_ERROR) << "Could not send transport info message: "
+                      << error.text;
+        return;
+      }
+    }
+  }
+}
+
+void Session::OnTransportSendError(Transport* transport,
+                                   const buzz::XmlElement* stanza,
+                                   const buzz::QName& name,
+                                   const std::string& type,
+                                   const std::string& text,
+                                   const buzz::XmlElement* extra_info) {
+  ASSERT(signaling_thread()->IsCurrent());
+  SignalErrorMessage(this, stanza, name, type, text, extra_info);
+}
+
+void Session::OnIncomingMessage(const SessionMessage& msg) {
+  ASSERT(signaling_thread()->IsCurrent());
+  ASSERT(state() == STATE_INIT || msg.from == remote_name());
+
+  if (current_protocol_== PROTOCOL_HYBRID) {
+    if (msg.protocol == PROTOCOL_GINGLE) {
+      current_protocol_ = PROTOCOL_GINGLE;
+    } else {
+      current_protocol_ = PROTOCOL_JINGLE;
+    }
+  }
+
+  bool valid = false;
+  MessageError error;
+  switch (msg.type) {
+    case ACTION_SESSION_INITIATE:
+      valid = OnInitiateMessage(msg, &error);
+      break;
+    case ACTION_SESSION_INFO:
+      valid = OnInfoMessage(msg);
+      break;
+    case ACTION_SESSION_ACCEPT:
+      valid = OnAcceptMessage(msg, &error);
+      break;
+    case ACTION_SESSION_REJECT:
+      valid = OnRejectMessage(msg, &error);
+      break;
+    case ACTION_SESSION_TERMINATE:
+      valid = OnTerminateMessage(msg, &error);
+      break;
+    case ACTION_TRANSPORT_INFO:
+      valid = OnTransportInfoMessage(msg, &error);
+      break;
+    case ACTION_TRANSPORT_ACCEPT:
+      valid = OnTransportAcceptMessage(msg, &error);
+      break;
+    case ACTION_DESCRIPTION_INFO:
+      valid = OnDescriptionInfoMessage(msg, &error);
+      break;
+    default:
+      valid = BadMessage(buzz::QN_STANZA_BAD_REQUEST,
+                         "unknown session message type",
+                         &error);
+  }
+
+  if (valid) {
+    SendAcknowledgementMessage(msg.stanza);
+  } else {
+    SignalErrorMessage(this, msg.stanza, error.type,
+                       "modify", error.text, NULL);
+  }
+}
+
+void Session::OnIncomingResponse(const buzz::XmlElement* orig_stanza,
+                                 const buzz::XmlElement* response_stanza,
+                                 const SessionMessage& msg) {
+  ASSERT(signaling_thread()->IsCurrent());
+
+  if (msg.type == ACTION_SESSION_INITIATE) {
+    OnInitiateAcked();
+  }
+}
+
+void Session::OnInitiateAcked() {
+    // TODO: This is to work around server re-ordering
+    // messages.  We send the candidates once the session-initiate
+    // is acked.  Once we have fixed the server to guarantee message
+    // order, we can remove this case.
+  if (!initiate_acked_) {
+    initiate_acked_ = true;
+    SessionError error;
+    SendAllUnsentTransportInfoMessages(&error);
+  }
+}
+
+void Session::OnFailedSend(const buzz::XmlElement* orig_stanza,
+                           const buzz::XmlElement* error_stanza) {
+  ASSERT(signaling_thread()->IsCurrent());
+
+  SessionMessage msg;
+  ParseError parse_error;
+  if (!ParseSessionMessage(orig_stanza, &msg, &parse_error)) {
+    LOG(LS_ERROR) << "Error parsing failed send: " << parse_error.text
+                  << ":" << orig_stanza;
+    return;
+  }
+
+  // If the error is a session redirect, call OnRedirectError, which will
+  // continue the session with a new remote JID.
+  SessionRedirect redirect;
+  if (FindSessionRedirect(error_stanza, &redirect)) {
+    SessionError error;
+    if (!OnRedirectError(redirect, &error)) {
+      // TODO: Should we send a message back?  The standard
+      // says nothing about it.
+      LOG(LS_ERROR) << "Failed to redirect: " << error.text;
+      SetError(ERROR_RESPONSE);
+    }
+    return;
+  }
+
+  std::string error_type = "cancel";
+
+  const buzz::XmlElement* error = error_stanza->FirstNamed(buzz::QN_ERROR);
+  if (error) {
+    error_type = error->Attr(buzz::QN_TYPE);
+
+    LOG(LS_ERROR) << "Session error:\n" << error->Str() << "\n"
+                  << "in response to:\n" << orig_stanza->Str();
+  } else {
+    // don't crash if <error> is missing
+    LOG(LS_ERROR) << "Session error without <error/> element, ignoring";
+    return;
+  }
+
+  if (msg.type == ACTION_TRANSPORT_INFO) {
+    // Transport messages frequently generate errors because they are sent right
+    // when we detect a network failure.  For that reason, we ignore such
+    // errors, because if we do not establish writability again, we will
+    // terminate anyway.  The exceptions are transport-specific error tags,
+    // which we pass on to the respective transport.
+  } else if ((error_type != "continue") && (error_type != "wait")) {
+    // We do not set an error if the other side said it is okay to continue
+    // (possibly after waiting).  These errors can be ignored.
+    SetError(ERROR_RESPONSE);
+  }
+}
+
+bool Session::OnInitiateMessage(const SessionMessage& msg,
+                                MessageError* error) {
+  if (!CheckState(STATE_INIT, error))
+    return false;
+
+  SessionInitiate init;
+  if (!ParseSessionInitiate(msg.protocol, msg.action_elem,
+                            GetContentParsers(), GetTransportParsers(),
+                            GetCandidateTranslators(),
+                            &init, error))
+    return false;
+
+  SessionError session_error;
+  if (!CreateTransportProxies(init.transports, &session_error)) {
+    return BadMessage(buzz::QN_STANZA_NOT_ACCEPTABLE,
+                      session_error.text, error);
+  }
+
+  set_remote_name(msg.from);
+  set_initiator_name(msg.initiator);
+  set_remote_description(new SessionDescription(init.ClearContents(),
+                                                init.transports,
+                                                init.groups));
+  // Updating transport with TransportDescription.
+  PushdownTransportDescription(CS_REMOTE, CA_OFFER);
+  SetState(STATE_RECEIVEDINITIATE);
+
+  // Users of Session may listen to state change and call Reject().
+  if (state() != STATE_SENTREJECT) {
+    if (!OnRemoteCandidates(init.transports, error))
+      return false;
+
+    // TODO(juberti): Auto-generate and push down the local transport answer.
+    // This is necessary for trickling to work with RFC 5245 ICE.
+  }
+  return true;
+}
+
+bool Session::OnAcceptMessage(const SessionMessage& msg, MessageError* error) {
+  if (!CheckState(STATE_SENTINITIATE, error))
+    return false;
+
+  SessionAccept accept;
+  if (!ParseSessionAccept(msg.protocol, msg.action_elem,
+                          GetContentParsers(), GetTransportParsers(),
+                          GetCandidateTranslators(),
+                          &accept, error)) {
+    return false;
+  }
+
+  // If we get an accept, we can assume the initiate has been
+  // received, even if we haven't gotten an IQ response.
+  OnInitiateAcked();
+
+  set_remote_description(new SessionDescription(accept.ClearContents(),
+                                                accept.transports,
+                                                accept.groups));
+  // Updating transport with TransportDescription.
+  PushdownTransportDescription(CS_REMOTE, CA_ANSWER);
+  MaybeEnableMuxingSupport();  // Enable transport channel mux if supported.
+  SetState(STATE_RECEIVEDACCEPT);
+
+  if (!OnRemoteCandidates(accept.transports, error))
+    return false;
+
+  return true;
+}
+
+bool Session::OnRejectMessage(const SessionMessage& msg, MessageError* error) {
+  if (!CheckState(STATE_SENTINITIATE, error))
+    return false;
+
+  SetState(STATE_RECEIVEDREJECT);
+  return true;
+}
+
+bool Session::OnInfoMessage(const SessionMessage& msg) {
+  SignalInfoMessage(this, msg.action_elem);
+  return true;
+}
+
+bool Session::OnTerminateMessage(const SessionMessage& msg,
+                                 MessageError* error) {
+  SessionTerminate term;
+  if (!ParseSessionTerminate(msg.protocol, msg.action_elem, &term, error))
+    return false;
+
+  SignalReceivedTerminateReason(this, term.reason);
+  if (term.debug_reason != buzz::STR_EMPTY) {
+    LOG(LS_VERBOSE) << "Received error on call: " << term.debug_reason;
+  }
+
+  SetState(STATE_RECEIVEDTERMINATE);
+  return true;
+}
+
+bool Session::OnTransportInfoMessage(const SessionMessage& msg,
+                                     MessageError* error) {
+  TransportInfos tinfos;
+  if (!ParseTransportInfos(msg.protocol, msg.action_elem,
+                           initiator_description()->contents(),
+                           GetTransportParsers(), GetCandidateTranslators(),
+                           &tinfos, error))
+    return false;
+
+  if (!OnRemoteCandidates(tinfos, error))
+    return false;
+
+  return true;
+}
+
+bool Session::OnTransportAcceptMessage(const SessionMessage& msg,
+                                       MessageError* error) {
+  // TODO: Currently here only for compatibility with
+  // Gingle 1.1 clients (notably, Google Voice).
+  return true;
+}
+
+bool Session::OnDescriptionInfoMessage(const SessionMessage& msg,
+                              MessageError* error) {
+  if (!CheckState(STATE_INPROGRESS, error))
+    return false;
+
+  DescriptionInfo description_info;
+  if (!ParseDescriptionInfo(msg.protocol, msg.action_elem,
+                            GetContentParsers(), GetTransportParsers(),
+                            GetCandidateTranslators(),
+                            &description_info, error)) {
+    return false;
+  }
+
+  ContentInfos updated_contents = description_info.ClearContents();
+
+  // 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;
+  //   }
+  // }
+
+  // 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);
+  //   }
+  // }
+
+  SignalRemoteDescriptionUpdate(this, updated_contents);
+  return true;
+}
+
+bool BareJidsEqual(const std::string& name1,
+                   const std::string& name2) {
+  buzz::Jid jid1(name1);
+  buzz::Jid jid2(name2);
+
+  return jid1.IsValid() && jid2.IsValid() && jid1.BareEquals(jid2);
+}
+
+bool Session::OnRedirectError(const SessionRedirect& redirect,
+                              SessionError* error) {
+  MessageError message_error;
+  if (!CheckState(STATE_SENTINITIATE, &message_error)) {
+    return BadWrite(message_error.text, error);
+  }
+
+  if (!BareJidsEqual(remote_name(), redirect.target))
+    return BadWrite("Redirection not allowed: must be the same bare jid.",
+                    error);
+
+  // When we receive a redirect, we point the session at the new JID
+  // and resend the candidates.
+  set_remote_name(redirect.target);
+  return (SendInitiateMessage(local_description(), error) &&
+          ResendAllTransportInfoMessages(error));
+}
+
+bool Session::CheckState(State expected, MessageError* error) {
+  if (state() != expected) {
+    // The server can deliver messages out of order/repeated for various
+    // reasons. For example, if the server does not recive our iq response,
+    // it could assume that the iq it sent was lost, and will then send
+    // it again. Ideally, we should implement reliable messaging with
+    // duplicate elimination.
+    return BadMessage(buzz::QN_STANZA_NOT_ALLOWED,
+                      "message not allowed in current state",
+                      error);
+  }
+  return true;
+}
+
+void Session::SetError(Error error) {
+  BaseSession::SetError(error);
+  if (error != ERROR_NONE)
+    signaling_thread()->Post(this, MSG_ERROR);
+}
+
+void Session::OnMessage(talk_base::Message* pmsg) {
+  // preserve this because BaseSession::OnMessage may modify it
+  State orig_state = state();
+
+  BaseSession::OnMessage(pmsg);
+
+  switch (pmsg->message_id) {
+  case MSG_ERROR:
+    TerminateWithReason(STR_TERMINATE_ERROR);
+    break;
+
+  case MSG_STATE:
+    switch (orig_state) {
+    case STATE_SENTREJECT:
+    case STATE_RECEIVEDREJECT:
+      // Assume clean termination.
+      Terminate();
+      break;
+
+    case STATE_SENTTERMINATE:
+    case STATE_RECEIVEDTERMINATE:
+      session_manager_->DestroySession(this);
+      break;
+
+    default:
+      // Explicitly ignoring some states here.
+      break;
+    }
+    break;
+  }
+}
+
+bool Session::SendInitiateMessage(const SessionDescription* sdesc,
+                                  SessionError* error) {
+  SessionInitiate init;
+  init.contents = sdesc->contents();
+  init.transports = GetEmptyTransportInfos(init.contents);
+  init.groups = sdesc->groups();
+  return SendMessage(ACTION_SESSION_INITIATE, init, error);
+}
+
+bool Session::WriteSessionAction(
+    SignalingProtocol protocol, const SessionInitiate& init,
+    XmlElements* elems, WriteError* error) {
+  return WriteSessionInitiate(protocol, init.contents, init.transports,
+                              GetContentParsers(), GetTransportParsers(),
+                              GetCandidateTranslators(), init.groups,
+                              elems, error);
+}
+
+bool Session::SendAcceptMessage(const SessionDescription* sdesc,
+                                SessionError* error) {
+  XmlElements elems;
+  if (!WriteSessionAccept(current_protocol_,
+                          sdesc->contents(),
+                          GetEmptyTransportInfos(sdesc->contents()),
+                          GetContentParsers(), GetTransportParsers(),
+                          GetCandidateTranslators(), sdesc->groups(),
+                          &elems, error)) {
+    return false;
+  }
+  return SendMessage(ACTION_SESSION_ACCEPT, elems, error);
+}
+
+bool Session::SendRejectMessage(const std::string& reason,
+                                SessionError* error) {
+  SessionTerminate term(reason);
+  return SendMessage(ACTION_SESSION_REJECT, term, error);
+}
+
+bool Session::SendTerminateMessage(const std::string& reason,
+                                   SessionError* error) {
+  SessionTerminate term(reason);
+  return SendMessage(ACTION_SESSION_TERMINATE, term, error);
+}
+
+bool Session::WriteSessionAction(SignalingProtocol protocol,
+                                 const SessionTerminate& term,
+                                 XmlElements* elems, WriteError* error) {
+  WriteSessionTerminate(protocol, term, elems);
+  return true;
+}
+
+bool Session::SendTransportInfoMessage(const TransportInfo& tinfo,
+                                       SessionError* error) {
+  return SendMessage(ACTION_TRANSPORT_INFO, tinfo, error);
+}
+
+bool Session::SendTransportInfoMessage(const TransportProxy* transproxy,
+                                       const Candidates& candidates,
+                                       SessionError* error) {
+  return SendTransportInfoMessage(TransportInfo(transproxy->content_name(),
+      TransportDescription(transproxy->type(), candidates)), error);
+}
+
+bool Session::WriteSessionAction(SignalingProtocol protocol,
+                                 const TransportInfo& tinfo,
+                                 XmlElements* elems, WriteError* error) {
+  TransportInfos tinfos;
+  tinfos.push_back(tinfo);
+  return WriteTransportInfos(protocol, tinfos,
+                             GetTransportParsers(), GetCandidateTranslators(),
+                             elems, error);
+}
+
+bool Session::ResendAllTransportInfoMessages(SessionError* error) {
+  for (TransportMap::const_iterator iter = transport_proxies().begin();
+       iter != transport_proxies().end(); ++iter) {
+    TransportProxy* transproxy = iter->second;
+    if (transproxy->sent_candidates().size() > 0) {
+      if (!SendTransportInfoMessage(
+              transproxy, transproxy->sent_candidates(), error)) {
+        LOG(LS_ERROR) << "Could not resend transport info messages: "
+                      << error->text;
+        return false;
+      }
+      transproxy->ClearSentCandidates();
+    }
+  }
+  return true;
+}
+
+bool Session::SendAllUnsentTransportInfoMessages(SessionError* error) {
+  for (TransportMap::const_iterator iter = transport_proxies().begin();
+       iter != transport_proxies().end(); ++iter) {
+    TransportProxy* transproxy = iter->second;
+    if (transproxy->unsent_candidates().size() > 0) {
+      if (!SendTransportInfoMessage(
+              transproxy, transproxy->unsent_candidates(), error)) {
+        LOG(LS_ERROR) << "Could not send unsent transport info messages: "
+                      << error->text;
+        return false;
+      }
+      transproxy->ClearUnsentCandidates();
+    }
+  }
+  return true;
+}
+
+bool Session::SendMessage(ActionType type, const XmlElements& action_elems,
+                          SessionError* error) {
+  talk_base::scoped_ptr<buzz::XmlElement> stanza(
+      new buzz::XmlElement(buzz::QN_IQ));
+
+  SessionMessage msg(current_protocol_, type, id(), initiator_name());
+  msg.to = remote_name();
+  WriteSessionMessage(msg, action_elems, stanza.get());
+
+  SignalOutgoingMessage(this, stanza.get());
+  return true;
+}
+
+template <typename Action>
+bool Session::SendMessage(ActionType type, const Action& action,
+                          SessionError* error) {
+  talk_base::scoped_ptr<buzz::XmlElement> stanza(
+      new buzz::XmlElement(buzz::QN_IQ));
+  if (!WriteActionMessage(type, action, stanza.get(), error))
+    return false;
+
+  SignalOutgoingMessage(this, stanza.get());
+  return true;
+}
+
+template <typename Action>
+bool Session::WriteActionMessage(ActionType type, const Action& action,
+                                 buzz::XmlElement* stanza,
+                                 WriteError* error) {
+  if (current_protocol_ == PROTOCOL_HYBRID) {
+    if (!WriteActionMessage(PROTOCOL_JINGLE, type, action, stanza, error))
+      return false;
+    if (!WriteActionMessage(PROTOCOL_GINGLE, type, action, stanza, error))
+      return false;
+  } else {
+    if (!WriteActionMessage(current_protocol_, type, action, stanza, error))
+      return false;
+  }
+  return true;
+}
+
+template <typename Action>
+bool Session::WriteActionMessage(SignalingProtocol protocol,
+                                 ActionType type, const Action& action,
+                                 buzz::XmlElement* stanza, WriteError* error) {
+  XmlElements action_elems;
+  if (!WriteSessionAction(protocol, action, &action_elems, error))
+    return false;
+
+  SessionMessage msg(protocol, type, id(), initiator_name());
+  msg.to = remote_name();
+
+  WriteSessionMessage(msg, action_elems, stanza);
+  return true;
+}
+
+void Session::SendAcknowledgementMessage(const buzz::XmlElement* stanza) {
+  talk_base::scoped_ptr<buzz::XmlElement> ack(
+      new buzz::XmlElement(buzz::QN_IQ));
+  ack->SetAttr(buzz::QN_TO, remote_name());
+  ack->SetAttr(buzz::QN_ID, stanza->Attr(buzz::QN_ID));
+  ack->SetAttr(buzz::QN_TYPE, "result");
+
+  SignalOutgoingMessage(this, ack.get());
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/session.h b/talk/p2p/base/session.h
new file mode 100644
index 0000000..5f06518
--- /dev/null
+++ b/talk/p2p/base/session.h
@@ -0,0 +1,723 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_SESSION_H_
+#define TALK_P2P_BASE_SESSION_H_
+
+#include <list>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "talk/base/refcount.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/scoped_ref_ptr.h"
+#include "talk/base/socketaddress.h"
+#include "talk/p2p/base/parsing.h"
+#include "talk/p2p/base/port.h"
+#include "talk/p2p/base/sessionclient.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/base/sessionmessages.h"
+#include "talk/p2p/base/transport.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+
+namespace cricket {
+
+class BaseSession;
+class P2PTransportChannel;
+class Transport;
+class TransportChannel;
+class TransportChannelProxy;
+class TransportChannelImpl;
+
+typedef talk_base::RefCountedObject<talk_base::scoped_ptr<Transport> >
+TransportWrapper;
+
+// Used for errors that will send back a specific error message to the
+// remote peer.  We add "type" to the errors because it's needed for
+// SignalErrorMessage.
+struct MessageError : ParseError {
+  buzz::QName type;
+
+  // if unset, assume type is a parse error
+  MessageError() : ParseError(), type(buzz::QN_STANZA_BAD_REQUEST) {}
+
+  void SetType(const buzz::QName type) {
+    this->type = type;
+  }
+};
+
+// Used for errors that may be returned by public session methods that
+// can fail.
+// TODO: Use this error in Session::Initiate and
+// Session::Accept.
+struct SessionError : WriteError {
+};
+
+// Bundles a Transport and ChannelMap together. ChannelMap is used to
+// create transport channels before receiving or sending a session
+// initiate, and for speculatively connecting channels.  Previously, a
+// session had one ChannelMap and transport.  Now, with multiple
+// transports per session, we need multiple ChannelMaps as well.
+
+typedef std::map<int, TransportChannelProxy*> ChannelMap;
+
+class TransportProxy : public sigslot::has_slots<>,
+                       public CandidateTranslator {
+ public:
+  TransportProxy(
+      const std::string& sid,
+      const std::string& content_name,
+      TransportWrapper* transport)
+      : sid_(sid),
+        content_name_(content_name),
+        transport_(transport),
+        connecting_(false),
+        negotiated_(false),
+        sent_candidates_(false),
+        candidates_allocated_(false) {
+    transport_->get()->SignalCandidatesReady.connect(
+        this, &TransportProxy::OnTransportCandidatesReady);
+  }
+  ~TransportProxy();
+
+  std::string content_name() const { return content_name_; }
+  // TODO(juberti): It's not good form to expose the object you're wrapping,
+  // since callers can mutate it. Can we make this return a const Transport*?
+  Transport* impl() const { return transport_->get(); }
+
+  std::string type() const;
+  bool negotiated() const { return negotiated_; }
+  const Candidates& sent_candidates() const { return sent_candidates_; }
+  const Candidates& unsent_candidates() const { return unsent_candidates_; }
+  bool candidates_allocated() const { return candidates_allocated_; }
+  void set_candidates_allocated(bool allocated) {
+    candidates_allocated_ = allocated;
+  }
+
+  TransportChannel* GetChannel(int component);
+  TransportChannel* CreateChannel(const std::string& channel_name,
+                                  int component);
+  bool HasChannel(int component);
+  void DestroyChannel(int component);
+
+  void AddSentCandidates(const Candidates& candidates);
+  void AddUnsentCandidates(const Candidates& candidates);
+  void ClearSentCandidates() { sent_candidates_.clear(); }
+  void ClearUnsentCandidates() { unsent_candidates_.clear(); }
+
+  // Start the connection process for any channels, creating impls if needed.
+  void ConnectChannels();
+  // Hook up impls to the proxy channels. Doesn't change connect state.
+  void CompleteNegotiation();
+
+  // Mux this proxy onto the specified proxy's transport.
+  bool SetupMux(TransportProxy* proxy);
+
+  // Simple functions that thunk down to the same functions on Transport.
+  void SetRole(TransportRole role);
+  bool SetLocalTransportDescription(const TransportDescription& description,
+                                    ContentAction action);
+  bool SetRemoteTransportDescription(const TransportDescription& description,
+                                     ContentAction action);
+  void OnSignalingReady();
+  bool OnRemoteCandidates(const Candidates& candidates, std::string* error);
+
+  // CandidateTranslator methods.
+  virtual bool GetChannelNameFromComponent(
+      int component, std::string* channel_name) const;
+  virtual bool GetComponentFromChannelName(
+      const std::string& channel_name, int* component) const;
+
+  // Called when a transport signals that it has new candidates.
+  void OnTransportCandidatesReady(cricket::Transport* transport,
+                                  const Candidates& candidates) {
+    SignalCandidatesReady(this, candidates);
+  }
+
+  // Handles sending of ready candidates and receiving of remote candidates.
+  sigslot::signal2<TransportProxy*,
+                         const std::vector<Candidate>&> SignalCandidatesReady;
+
+ private:
+  TransportChannelProxy* GetChannelProxy(int component) const;
+  TransportChannelProxy* GetChannelProxyByName(const std::string& name) const;
+  void ReplaceChannelProxyImpl(TransportChannelProxy* channel_proxy,
+                               size_t index);
+  TransportChannelImpl* GetOrCreateChannelProxyImpl(int component);
+  void SetChannelProxyImpl(int component,
+                           TransportChannelProxy* proxy);
+
+  std::string sid_;
+  std::string content_name_;
+  talk_base::scoped_refptr<TransportWrapper> transport_;
+  bool connecting_;
+  bool negotiated_;
+  ChannelMap channels_;
+  Candidates sent_candidates_;
+  Candidates unsent_candidates_;
+  bool candidates_allocated_;
+};
+
+typedef std::map<std::string, TransportProxy*> TransportMap;
+
+// Statistics for all the transports of this session.
+typedef std::map<std::string, TransportStats> TransportStatsMap;
+typedef std::map<std::string, std::string> ProxyTransportMap;
+
+struct SessionStats {
+  ProxyTransportMap proxy_to_transport;
+  TransportStatsMap transport_stats;
+};
+
+// A BaseSession manages general session state. This includes negotiation
+// of both the application-level and network-level protocols:  the former
+// defines what will be sent and the latter defines how it will be sent.  Each
+// network-level protocol is represented by a Transport object.  Each Transport
+// participates in the network-level negotiation.  The individual streams of
+// packets are represented by TransportChannels.  The application-level protocol
+// is represented by SessionDecription objects.
+class BaseSession : public sigslot::has_slots<>,
+                    public talk_base::MessageHandler {
+ public:
+  enum {
+    MSG_TIMEOUT = 0,
+    MSG_ERROR,
+    MSG_STATE,
+  };
+
+  enum State {
+    STATE_INIT = 0,
+    STATE_SENTINITIATE,       // sent initiate, waiting for Accept or Reject
+    STATE_RECEIVEDINITIATE,   // received an initiate. Call Accept or Reject
+    STATE_SENTPRACCEPT,       // sent provisional Accept
+    STATE_SENTACCEPT,         // sent accept. begin connecting transport
+    STATE_RECEIVEDPRACCEPT,   // received provisional Accept, waiting for Accept
+    STATE_RECEIVEDACCEPT,     // received accept. begin connecting transport
+    STATE_SENTMODIFY,         // sent modify, waiting for Accept or Reject
+    STATE_RECEIVEDMODIFY,     // received modify, call Accept or Reject
+    STATE_SENTREJECT,         // sent reject after receiving initiate
+    STATE_RECEIVEDREJECT,     // received reject after sending initiate
+    STATE_SENTREDIRECT,       // sent direct after receiving initiate
+    STATE_SENTTERMINATE,      // sent terminate (any time / either side)
+    STATE_RECEIVEDTERMINATE,  // received terminate (any time / either side)
+    STATE_INPROGRESS,         // session accepted and in progress
+    STATE_DEINIT,             // session is being destroyed
+  };
+
+  enum Error {
+    ERROR_NONE = 0,       // no error
+    ERROR_TIME = 1,       // no response to signaling
+    ERROR_RESPONSE = 2,   // error during signaling
+    ERROR_NETWORK = 3,    // network error, could not allocate network resources
+    ERROR_CONTENT = 4,    // channel errors in SetLocalContent/SetRemoteContent
+    ERROR_TRANSPORT = 5,  // transport error of some kind
+  };
+
+  // Convert State to a readable string.
+  static std::string StateToString(State state);
+
+  BaseSession(talk_base::Thread* signaling_thread,
+              talk_base::Thread* worker_thread,
+              PortAllocator* port_allocator,
+              const std::string& sid,
+              const std::string& content_type,
+              bool initiator);
+  virtual ~BaseSession();
+
+  talk_base::Thread* signaling_thread() { return signaling_thread_; }
+  talk_base::Thread* worker_thread() { return worker_thread_; }
+  PortAllocator* port_allocator() { return port_allocator_; }
+
+  // The ID of this session.
+  const std::string& id() const { return sid_; }
+
+  // TODO(juberti): This data is largely redundant, as it can now be obtained
+  // from local/remote_description(). Remove these functions and members.
+  // Returns the XML namespace identifying the type of this session.
+  const std::string& content_type() const { return content_type_; }
+  // Returns the XML namespace identifying the transport used for this session.
+  const std::string& transport_type() const { return transport_type_; }
+
+  // Indicates whether we initiated this session.
+  bool initiator() const { return initiator_; }
+
+  // Returns the application-level description given by our client.
+  // If we are the recipient, this will be NULL until we send an accept.
+  const SessionDescription* local_description() const {
+    return local_description_;
+  }
+  // Returns the application-level description given by the other client.
+  // If we are the initiator, this will be NULL until we receive an accept.
+  const SessionDescription* remote_description() const {
+    return remote_description_;
+  }
+  SessionDescription* remote_description() {
+    return remote_description_;
+  }
+
+  // Takes ownership of SessionDescription*
+  bool set_local_description(const SessionDescription* sdesc) {
+    if (sdesc != local_description_) {
+      delete local_description_;
+      local_description_ = sdesc;
+    }
+    return true;
+  }
+
+  // Takes ownership of SessionDescription*
+  bool set_remote_description(SessionDescription* sdesc) {
+    if (sdesc != remote_description_) {
+      delete remote_description_;
+      remote_description_ = sdesc;
+    }
+    return true;
+  }
+
+  const SessionDescription* initiator_description() const {
+    if (initiator_) {
+      return local_description_;
+    } else {
+      return remote_description_;
+    }
+  }
+
+  // Returns the current state of the session.  See the enum above for details.
+  // Each time the state changes, we will fire this signal.
+  State state() const { return state_; }
+  sigslot::signal2<BaseSession* , State> SignalState;
+
+  // Returns the last error in the session.  See the enum above for details.
+  // Each time the an error occurs, we will fire this signal.
+  Error error() const { return error_; }
+  sigslot::signal2<BaseSession* , Error> SignalError;
+
+  // Updates the state, signaling if necessary.
+  virtual void SetState(State state);
+
+  // Updates the error state, signaling if necessary.
+  virtual void SetError(Error error);
+
+  // Fired when the remote description is updated, with the updated
+  // contents.
+  sigslot::signal2<BaseSession* , const ContentInfos&>
+      SignalRemoteDescriptionUpdate;
+
+  // Fired when SetState is called (regardless if there's a state change), which
+  // indicates the session description might have be updated.
+  sigslot::signal2<BaseSession*, ContentAction> SignalNewLocalDescription;
+
+  // Fired when SetState is called (regardless if there's a state change), which
+  // indicates the session description might have be updated.
+  sigslot::signal2<BaseSession*, ContentAction> SignalNewRemoteDescription;
+
+  // Returns the transport that has been negotiated or NULL if
+  // negotiation is still in progress.
+  Transport* GetTransport(const std::string& content_name);
+
+  // Creates a new channel with the given names.  This method may be called
+  // immediately after creating the session.  However, the actual
+  // implementation may not be fixed until transport negotiation completes.
+  // This will usually be called from the worker thread, but that
+  // shouldn't be an issue since the main thread will be blocked in
+  // Send when doing so.
+  virtual TransportChannel* CreateChannel(const std::string& content_name,
+                                          const std::string& channel_name,
+                                          int component);
+
+  // Returns the channel with the given names.
+  virtual TransportChannel* GetChannel(const std::string& content_name,
+                                       int component);
+
+  // Destroys the channel with the given names.
+  // This will usually be called from the worker thread, but that
+  // shouldn't be an issue since the main thread will be blocked in
+  // Send when doing so.
+  virtual void DestroyChannel(const std::string& content_name,
+                              int component);
+
+  // Returns stats for all channels of all transports.
+  // This avoids exposing the internal structures used to track them.
+  virtual bool GetStats(SessionStats* stats);
+
+ protected:
+  bool PushdownTransportDescription(ContentSource source,
+                                    ContentAction action);
+  void set_initiator(bool initiator) { initiator_ = initiator; }
+
+  talk_base::SSLIdentity* identity() { return identity_; }
+  // Specifies the identity to use in this session.
+  void set_identity(talk_base::SSLIdentity* identity) { identity_ = identity; }
+
+  const TransportMap& transport_proxies() const { return transports_; }
+  // Get a TransportProxy by content_name or transport. NULL if not found.
+  TransportProxy* GetTransportProxy(const std::string& content_name);
+  TransportProxy* GetTransportProxy(const Transport* transport);
+  TransportProxy* GetFirstTransportProxy();
+  void DestroyTransportProxy(const std::string& content_name);
+  // TransportProxy is owned by session.  Return proxy just for convenience.
+  TransportProxy* GetOrCreateTransportProxy(const std::string& content_name);
+  // Creates the actual transport object. Overridable for testing.
+  virtual Transport* CreateTransport(const std::string& content_name);
+
+  void OnSignalingReady();
+  void SpeculativelyConnectAllTransportChannels();
+  // Helper method to provide remote candidates to the transport.
+  bool OnRemoteCandidates(const std::string& content_name,
+                          const Candidates& candidates,
+                          std::string* error);
+
+  // 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) {
+  }
+
+  // Called when the first channel of a transport begins connecting.  We use
+  // this to start a timer, to make sure that the connection completes in a
+  // reasonable amount of time.
+  virtual void OnTransportConnecting(Transport* transport) {
+  }
+
+  // Called when a transport changes its writable state.  We track this to make
+  // sure that the transport becomes writable within a reasonable amount of
+  // time.  If this does not occur, we signal an error.
+  virtual void OnTransportWritable(Transport* transport) {
+  }
+  virtual void OnTransportReadable(Transport* transport) {
+  }
+
+  // Called when a transport signals that it has new candidates.
+  virtual void OnTransportProxyCandidatesReady(TransportProxy* proxy,
+                                               const Candidates& candidates) {
+  }
+
+  // Called when a transport signals that it found an error in an incoming
+  // message.
+  virtual void OnTransportSendError(Transport* transport,
+                                    const buzz::XmlElement* stanza,
+                                    const buzz::QName& name,
+                                    const std::string& type,
+                                    const std::string& text,
+                                    const buzz::XmlElement* extra_info) {
+  }
+
+  virtual void OnTransportRouteChange(
+      Transport* transport,
+      int component,
+      const cricket::Candidate& remote_candidate) {
+  }
+
+  virtual void OnTransportCandidatesAllocationDone(Transport* transport);
+
+  // Called when all transport channels allocated required candidates.
+  // This method should be used as an indication of candidates gathering process
+  // is completed and application can now send local candidates list to remote.
+  virtual void OnCandidatesAllocationDone() {
+  }
+
+  // Handles the ice role change callback from Transport. This must be
+  // propagated to all the transports.
+  virtual void OnRoleConflict();
+
+  // Handles messages posted to us.
+  virtual void OnMessage(talk_base::Message *pmsg);
+
+ protected:
+  State state_;
+  Error error_;
+
+ private:
+  // Helper methods to push local and remote transport descriptions.
+  bool PushdownLocalTransportDescription(
+      const SessionDescription* sdesc, ContentAction action);
+  bool PushdownRemoteTransportDescription(
+      const SessionDescription* sdesc, ContentAction action);
+
+  bool IsCandidateAllocationDone() const;
+  void MaybeCandidateAllocationDone();
+
+  // This method will delete the Transport and TransportChannelImpls 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.
+  bool SetSelectedProxy(const std::string& content_name,
+                        const ContentGroup* muxed_group);
+  // Log session state.
+  void LogState(State old_state, State new_state);
+
+  // Returns true and the TransportInfo of the given |content_name|
+  // from |description|. Returns false if it's not available.
+  bool GetTransportDescription(const SessionDescription* description,
+                               const std::string& content_name,
+                               TransportDescription* info);
+
+  // Fires the new description signal according to the current state.
+  void SignalNewDescription();
+
+  // Gets the ContentAction and ContentSource according to the session state.
+  bool GetContentAction(ContentAction* action, ContentSource* source);
+
+  talk_base::Thread* signaling_thread_;
+  talk_base::Thread* worker_thread_;
+  PortAllocator* port_allocator_;
+  std::string sid_;
+  std::string content_type_;
+  std::string transport_type_;
+  bool initiator_;
+  talk_base::SSLIdentity* identity_;
+  const SessionDescription* local_description_;
+  SessionDescription* remote_description_;
+  uint64 ice_tiebreaker_;
+  // This flag will be set to true after the first role switch. This flag
+  // will enable us to stop any role switch during the call.
+  bool role_switch_;
+  TransportMap transports_;
+};
+
+// A specific Session created by the SessionManager, using XMPP for protocol.
+class Session : public BaseSession {
+ public:
+  // Returns the manager that created and owns this session.
+  SessionManager* session_manager() const { return session_manager_; }
+
+  // Returns the client that is handling the application data of this session.
+  SessionClient* client() const { return client_; }
+
+    // Returns the JID of this client.
+  const std::string& local_name() const { return local_name_; }
+
+  // Returns the JID of the other peer in this session.
+  const std::string& remote_name() const { return remote_name_; }
+
+  // Set the JID of the other peer in this session.
+  // Typically the remote_name_ is set when the session is initiated.
+  // However, sometimes (e.g when a proxy is used) the peer name is
+  // known after the BaseSession has been initiated and it must be updated
+  // explicitly.
+  void set_remote_name(const std::string& name) { remote_name_ = name; }
+
+  // Set the JID of the initiator of this session. Allows for the overriding
+  // of the initiator to be a third-party, eg. the MUC JID when creating p2p
+  // sessions.
+  void set_initiator_name(const std::string& name) { initiator_name_ = name; }
+
+  // Indicates the JID of the entity who initiated this session.
+  // In special cases, may be different than both local_name and remote_name.
+  const std::string& initiator_name() const { return initiator_name_; }
+
+  SignalingProtocol current_protocol() const { return current_protocol_; }
+
+  void set_current_protocol(SignalingProtocol protocol) {
+    current_protocol_ = protocol;
+  }
+
+  // Updates the error state, signaling if necessary.
+  virtual void SetError(Error error);
+
+  // When the session needs to send signaling messages, it beings by requesting
+  // signaling.  The client should handle this by calling OnSignalingReady once
+  // it is ready to send the messages.
+  // (These are called only by SessionManager.)
+  sigslot::signal1<Session*> SignalRequestSignaling;
+  void OnSignalingReady() { BaseSession::OnSignalingReady(); }
+
+  // Takes ownership of session description.
+  // TODO: Add an error argument to pass back to the caller.
+  bool Initiate(const std::string& to,
+                const SessionDescription* sdesc);
+
+  // When we receive an initiate, we create a session in the
+  // RECEIVEDINITIATE state and respond by accepting or rejecting.
+  // Takes ownership of session description.
+  // TODO: Add an error argument to pass back to the caller.
+  bool Accept(const SessionDescription* sdesc);
+  bool Reject(const std::string& reason);
+  bool Terminate() {
+    return TerminateWithReason(STR_TERMINATE_SUCCESS);
+  }
+  bool TerminateWithReason(const std::string& reason);
+  // Fired whenever we receive a terminate message along with a reason
+  sigslot::signal2<Session*, const std::string&> SignalReceivedTerminateReason;
+
+  // The two clients in the session may also send one another
+  // arbitrary XML messages, which are called "info" messages. Sending
+  // takes ownership of the given elements.  The signal does not; the
+  // parent element will be deleted after the signal.
+  bool SendInfoMessage(const XmlElements& elems);
+  bool SendDescriptionInfoMessage(const ContentInfos& contents);
+  sigslot::signal2<Session*, const buzz::XmlElement*> SignalInfoMessage;
+
+ private:
+  // Creates or destroys a session.  (These are called only SessionManager.)
+  Session(SessionManager *session_manager,
+          const std::string& local_name, const std::string& initiator_name,
+          const std::string& sid, const std::string& content_type,
+          SessionClient* client);
+  ~Session();
+  // For each transport info, create a transport proxy.  Can fail for
+  // incompatible transport types.
+  bool CreateTransportProxies(const TransportInfos& tinfos,
+                              SessionError* error);
+  bool OnRemoteCandidates(const TransportInfos& tinfos,
+                          ParseError* error);
+  // Returns a TransportInfo without candidates for each content name.
+  // Uses the transport_type_ of the session.
+  TransportInfos GetEmptyTransportInfos(const ContentInfos& contents) const;
+
+    // Maps passed to serialization functions.
+  TransportParserMap GetTransportParsers();
+  ContentParserMap GetContentParsers();
+  CandidateTranslatorMap GetCandidateTranslators();
+
+  virtual void OnTransportRequestSignaling(Transport* transport);
+  virtual void OnTransportConnecting(Transport* transport);
+  virtual void OnTransportWritable(Transport* transport);
+  virtual void OnTransportProxyCandidatesReady(TransportProxy* proxy,
+                                               const Candidates& candidates);
+  virtual void OnTransportSendError(Transport* transport,
+                                    const buzz::XmlElement* stanza,
+                                    const buzz::QName& name,
+                                    const std::string& type,
+                                    const std::string& text,
+                                    const buzz::XmlElement* extra_info);
+  virtual void OnMessage(talk_base::Message *pmsg);
+
+  // Send various kinds of session messages.
+  bool SendInitiateMessage(const SessionDescription* sdesc,
+                           SessionError* error);
+  bool SendAcceptMessage(const SessionDescription* sdesc, SessionError* error);
+  bool SendRejectMessage(const std::string& reason, SessionError* error);
+  bool SendTerminateMessage(const std::string& reason, SessionError* error);
+  bool SendTransportInfoMessage(const TransportInfo& tinfo,
+                                SessionError* error);
+  bool SendTransportInfoMessage(const TransportProxy* transproxy,
+                                const Candidates& candidates,
+                                SessionError* error);
+
+  bool ResendAllTransportInfoMessages(SessionError* error);
+  bool SendAllUnsentTransportInfoMessages(SessionError* error);
+
+  // Both versions of SendMessage send a message of the given type to
+  // the other client.  Can pass either a set of elements or an
+  // "action", which must have a WriteSessionAction method to go along
+  // with it.  Sending with an action supports sending a "hybrid"
+  // message.  Sending with elements must be sent as Jingle or Gingle.
+
+  // When passing elems, must be either Jingle or Gingle protocol.
+  // Takes ownership of action_elems.
+  bool SendMessage(ActionType type, const XmlElements& action_elems,
+                   SessionError* error);
+  // When passing an action, may be Hybrid protocol.
+  template <typename Action>
+  bool SendMessage(ActionType type, const Action& action,
+                   SessionError* error);
+
+  // Helper methods to write the session message stanza.
+  template <typename Action>
+  bool WriteActionMessage(ActionType type, const Action& action,
+                          buzz::XmlElement* stanza, WriteError* error);
+  template <typename Action>
+  bool WriteActionMessage(SignalingProtocol protocol,
+                          ActionType type, const Action& action,
+                          buzz::XmlElement* stanza, WriteError* error);
+
+  // Sending messages in hybrid form requires being able to write them
+  // on a per-protocol basis with a common method signature, which all
+  // of these have.
+  bool WriteSessionAction(SignalingProtocol protocol,
+                          const SessionInitiate& init,
+                          XmlElements* elems, WriteError* error);
+  bool WriteSessionAction(SignalingProtocol protocol,
+                          const TransportInfo& tinfo,
+                          XmlElements* elems, WriteError* error);
+  bool WriteSessionAction(SignalingProtocol protocol,
+                          const SessionTerminate& term,
+                          XmlElements* elems, WriteError* error);
+
+  // Sends a message back to the other client indicating that we have received
+  // and accepted their message.
+  void SendAcknowledgementMessage(const buzz::XmlElement* stanza);
+
+  // Once signaling is ready, the session will use this signal to request the
+  // sending of each message.  When messages are received by the other client,
+  // they should be handed to OnIncomingMessage.
+  // (These are called only by SessionManager.)
+  sigslot::signal2<Session* , const buzz::XmlElement*> SignalOutgoingMessage;
+  void OnIncomingMessage(const SessionMessage& msg);
+
+  void OnIncomingResponse(const buzz::XmlElement* orig_stanza,
+                          const buzz::XmlElement* response_stanza,
+                          const SessionMessage& msg);
+  void OnInitiateAcked();
+  void OnFailedSend(const buzz::XmlElement* orig_stanza,
+                    const buzz::XmlElement* error_stanza);
+
+  // Invoked when an error is found in an incoming message.  This is translated
+  // into the appropriate XMPP response by SessionManager.
+  sigslot::signal6<BaseSession*,
+                   const buzz::XmlElement*,
+                   const buzz::QName&,
+                   const std::string&,
+                   const std::string&,
+                   const buzz::XmlElement*> SignalErrorMessage;
+
+  // Handlers for the various types of messages.  These functions may take
+  // pointers to the whole stanza or to just the session element.
+  bool OnInitiateMessage(const SessionMessage& msg, MessageError* error);
+  bool OnAcceptMessage(const SessionMessage& msg, MessageError* error);
+  bool OnRejectMessage(const SessionMessage& msg, MessageError* error);
+  bool OnInfoMessage(const SessionMessage& msg);
+  bool OnTerminateMessage(const SessionMessage& msg, MessageError* error);
+  bool OnTransportInfoMessage(const SessionMessage& msg, MessageError* error);
+  bool OnTransportAcceptMessage(const SessionMessage& msg, MessageError* error);
+  bool 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.
+  bool CheckState(State state, MessageError* error);
+
+  SessionManager* session_manager_;
+  bool initiate_acked_;
+  std::string local_name_;
+  std::string initiator_name_;
+  std::string remote_name_;
+  SessionClient* client_;
+  TransportParser* transport_parser_;
+  // Keeps track of what protocol we are speaking.
+  SignalingProtocol current_protocol_;
+
+  friend class SessionManager;  // For access to constructor, destructor,
+                                // and signaling related methods.
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_SESSION_H_
diff --git a/talk/p2p/base/session_unittest.cc b/talk/p2p/base/session_unittest.cc
new file mode 100644
index 0000000..73933bb
--- /dev/null
+++ b/talk/p2p/base/session_unittest.cc
@@ -0,0 +1,2464 @@
+/*
+ * 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/base64.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/basicpacketsocketfactory.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 = "9254631414740579489";
+// 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) {
+  // Port will update the rtcp username by +1 on the last character. So we need
+  // to compensate here. See Port::username_fragment() for detail.
+  std::string username = GetUsername(port_index);
+  // TODO: Use the component id instead of the channel name to
+  // determinte if we need to covert the username here.
+  if (name == "rtcp" || name == "video_rtcp" || name == "chanb") {
+    char next_ch = username[username.size() - 1];
+    ASSERT(username.size() > 0);
+    talk_base::Base64::GetNextBase64Char(next_ch, &next_ch);
+    username[username.size() - 1] = next_ch;
+  }
+  return "<candidate"
+      " name=\"" + name + "\""
+      " address=\"127.0.0.1\""
+      " port=\"" + GetPortString(port_index) + "\""
+      " preference=\"0.99\""
+      " username=\"" + username + "\""
+      " 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 JingleGroupInfoXml(const std::string& content_name_a,
+                               const std::string& content_name_b) {
+  std::string group_info = "<jin:group"
+      " type=\"BUNDLE\""
+      " xmlns:jin=\"google:jingle\""
+      ">";
+  if (!content_name_a.empty())
+    group_info += "<content name=\"" + content_name_a + "\""
+    "/>";
+  if (!content_name_b.empty())
+    group_info += "<content name=\"" + content_name_b + "\""
+    "/>";
+  group_info += "</jin:group>";
+  return group_info;
+}
+
+
+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,
+                              bool bundle = false) {
+  std::string content_xml;
+  if (content_name_b.empty()) {
+    content_xml = JingleEmptyContentXml(
+        content_name_a, content_type_a, kTransportType);
+  } else {
+    content_xml = JingleEmptyContentXml(
+           content_name_a, content_type_a, kTransportType) +
+       JingleEmptyContentXml(
+           content_name_b, content_type_b, kTransportType);
+    if (bundle) {
+      content_xml += JingleGroupInfoXml(content_name_a, content_name_b);
+    }
+  }
+  return JingleInitiateActionXml(content_xml);
+}
+
+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,
+                            bool bundle = false) {
+  std::string content_xml;
+  if (content_name_b.empty()) {
+    content_xml = JingleEmptyContentXml(
+        content_name_a, content_type_a, kTransportType);
+  } else {
+    content_xml = JingleEmptyContentXml(
+        content_name_a, content_type_a, kTransportType) +
+        JingleEmptyContentXml(
+            content_name_b, content_type_b, kTransportType);
+  }
+  if (bundle) {
+    content_xml += JingleGroupInfoXml(content_name_a, content_name_b);
+  }
+
+  return JingleActionXml("session-accept", content_xml);
+}
+
+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,
+                        bool bundle = false) {
+  switch (protocol) {
+    case PROTOCOL_JINGLE:
+      return JingleInitiateXml(content_name_a, content_type_a,
+                               content_name_b, content_type_b,
+                               bundle);
+    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,
+                      bool bundle = false) {
+  switch (protocol) {
+    case PROTOCOL_JINGLE:
+      return JingleAcceptXml(content_name_a, content_type_a,
+                             content_name_b, content_type_b, bundle);
+    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,
+                      bool bundle = false) {
+  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);
+    default:
+      break;
+  }
+  return "";
+}
+
+// TODO: Break out and join with fakeportallocator.h
+class TestPortAllocatorSession : public cricket::PortAllocatorSession {
+ public:
+  TestPortAllocatorSession(const std::string& content_name,
+                           int component,
+                           const std::string& ice_ufrag,
+                           const std::string& ice_pwd,
+                           const int port_offset)
+      : PortAllocatorSession(content_name, component, ice_ufrag, ice_pwd, 0),
+        port_offset_(port_offset),
+        ports_(kNumPorts),
+        address_("127.0.0.1", 0),
+        network_("network", "unittest",
+                 talk_base::IPAddress(INADDR_LOOPBACK), 8),
+        socket_factory_(talk_base::Thread::Current()),
+        running_(false),
+        port_(28653) {
+    network_.AddIP(address_.ipaddr());
+  }
+
+  ~TestPortAllocatorSession() {
+    for (size_t i = 0; i < ports_.size(); i++)
+      delete ports_[i];
+  }
+
+  virtual void StartGettingPorts() {
+    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),
+          GetUsername(index), GetPassword(index));
+      AddPort(ports_[i]);
+    }
+    running_ = true;
+  }
+
+  virtual void StopGettingPorts() { running_ = false; }
+  virtual bool IsGettingPorts() { return running_; }
+
+  void AddPort(cricket::Port* port) {
+    port->set_component(component_);
+    port->set_generation(0);
+    port->SignalDestroyed.connect(
+        this, &TestPortAllocatorSession::OnPortDestroyed);
+    port->SignalPortComplete.connect(
+        this, &TestPortAllocatorSession::OnPortComplete);
+    port->PrepareAddress();
+    SignalPortReady(this, port);
+  }
+
+  void OnPortDestroyed(cricket::PortInterface* port) {
+    for (size_t i = 0; i < ports_.size(); i++) {
+      if (ports_[i] == port)
+        ports_[i] = NULL;
+    }
+  }
+
+  void OnPortComplete(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*
+  CreateSessionInternal(
+                const std::string& content_name,
+                int component,
+                const std::string& ice_ufrag,
+                const std::string& ice_pwd) {
+    port_offset_ += 2;
+    return new TestPortAllocatorSession(content_name, component,
+                                        ice_ufrag, ice_pwd, 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) {
+  }
+  virtual ContentDescription* Copy() const {
+    return new TestContentDescription(*this);
+  }
+
+  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));
+  cricket::TransportDescription desc(cricket::NS_GINGLE_P2P,
+                                     cricket::Candidates());
+  offer->AddTransportInfo(cricket::TransportInfo(content_name_a, desc));
+
+  if (content_name_a != content_name_b) {
+    offer->AddContent(content_name_b, content_type_b,
+                      new TestContentDescription(gingle_content_type,
+                                                 content_type_b));
+    offer->AddTransportInfo(cricket::TransportInfo(content_name_b, desc));
+  }
+  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));
+  offer->AddTransportInfo(cricket::TransportInfo
+                          (content_name, cricket::TransportDescription(
+                          cricket::NS_GINGLE_P2P,
+                          cricket::Candidates())));
+  return offer;
+}
+
+struct TestSessionClient: public cricket::SessionClient,
+                          public sigslot::has_slots<> {
+ public:
+  TestSessionClient() {
+  }
+
+  ~TestSessionClient() {
+  }
+
+  virtual bool ParseContent(SignalingProtocol protocol,
+                            const buzz::XmlElement* elem,
+                            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, const std::string& name)
+    : channel(p), last_readable(false), last_writable(false), data_count(0),
+      last_size(0), name(name) {
+    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, int flags) {
+    if (memcmp(buf, name.c_str(), name.size()) != 0)
+      return;  // drop packet if packet doesn't belong to this channel. This
+               // can happen when transport channels are muxed together.
+    buf += name.size();  // Remove channel name from the message.
+    size -= name.size();  // Decrement size by channel name string 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) {
+    std::string data_with_id(name);
+    data_with_id += data;
+    int result = channel->SendPacket(data_with_id.c_str(), data_with_id.size(),
+                                     0);
+    EXPECT_EQ(static_cast<int>(data_with_id.size()), result);
+  }
+
+  cricket::TransportChannel* channel;
+  bool last_readable, last_writable;
+  int data_count;
+  char last_data[4096];
+  size_t last_size;
+  std::string name;
+};
+
+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:
+  // TODO: Add channel_component_a/b as inputs to the ctor.
+  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,
+              content_name_b, channel_name_b);
+  }
+
+  ~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& cont_name_b,
+                 const std::string& chan_name_b) {
+    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;
+    content_name_b = cont_name_b;
+    channel_name_b = chan_name_b;
+    session_created_count = 0;
+    session_destroyed_count = 0;
+    session_remote_description_update_count = 0;
+    new_local_description = false;
+    new_remote_description = false;
+    last_content_action = cricket::CA_OFFER;
+    last_content_source = cricket::CS_LOCAL;
+    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;
+
+    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,
+                  int component) const {
+    ASSERT(session != NULL);
+    const cricket::TransportChannel* channel =
+        session->GetChannel(content_name, component);
+    return channel != NULL && (component == channel->component());
+  }
+
+  cricket::TransportChannel* GetChannel(const std::string& content_name,
+                                        int component) const {
+    ASSERT(session != NULL);
+    return session->GetChannel(content_name, component);
+  }
+
+  void OnSessionCreate(cricket::Session* created_session, bool initiate) {
+    session_created_count += 1;
+
+    session = created_session;
+    session->set_current_protocol(start_protocol);
+    session->SignalState.connect(this, &TestClient::OnSessionState);
+    session->SignalError.connect(this, &TestClient::OnSessionError);
+    session->SignalRemoteDescriptionUpdate.connect(
+        this, &TestClient::OnSessionRemoteDescriptionUpdate);
+    session->SignalNewLocalDescription.connect(
+        this, &TestClient::OnNewLocalDescription);
+    session->SignalNewRemoteDescription.connect(
+        this, &TestClient::OnNewRemoteDescription);
+
+    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 OnNewLocalDescription(cricket::BaseSession* session,
+                             cricket::ContentAction action) {
+    new_local_description = true;
+    last_content_action = action;
+    last_content_source = cricket::CS_LOCAL;
+  }
+
+  void OnNewRemoteDescription(cricket::BaseSession* session,
+                              cricket::ContentAction action) {
+    new_remote_description = true;
+    last_content_action = action;
+    last_content_source = cricket::CS_REMOTE;
+  }
+
+  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);
+    // We either have a single content with multiple components (RTP/RTCP), or
+    // multiple contents with single components, but not both.
+    int component_a = 1;
+    int component_b = (content_name_a == content_name_b) ? 2 : 1;
+    chan_a = new ChannelHandler(
+        session->CreateChannel(content_name_a, channel_name_a, component_a),
+        channel_name_a);
+    chan_b = new ChannelHandler(
+        session->CreateChannel(content_name_b, channel_name_b, component_b),
+        channel_name_b);
+  }
+
+  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 content_name_b;
+  std::string channel_name_b;
+
+  uint32 session_created_count;
+  uint32 session_destroyed_count;
+  uint32 session_remote_description_update_count;
+  bool new_local_description;
+  bool new_remote_description;
+  cricket::ContentAction last_content_action;
+  cricket::ContentSource last_content_source;
+  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_b;
+  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,
+                   bool bundle = false) {
+    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());
+
+    // See comment in CreateChannels about how we choose component IDs.
+    int component_a = 1;
+    int component_b = (content_name_a == content_name_b) ? 2 : 1;
+    EXPECT_TRUE(initiator->HasTransport(content_name_a));
+    EXPECT_TRUE(initiator->HasChannel(content_name_a, component_a));
+    EXPECT_TRUE(initiator->HasTransport(content_name_b));
+    EXPECT_TRUE(initiator->HasChannel(content_name_b, component_b));
+
+    // Initiate and expect initiate message sent.
+    cricket::SessionDescription* offer = NewTestSessionDescription(
+        gingle_content_type,
+        content_name_a, content_type,
+        content_name_b, content_type);
+    if (bundle) {
+      cricket::ContentGroup group(cricket::GROUP_TYPE_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);
+    }
+    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, component_a));
+    EXPECT_TRUE(responder->HasTransport(content_name_b));
+    EXPECT_TRUE(responder->HasChannel(content_name_b, component_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);
+    if (bundle) {
+      cricket::ContentGroup group(cricket::GROUP_TYPE_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));
+      answer->AddGroup(group);
+    }
+    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);
+    if (bundle) {
+      cricket::TransportChannel* initiator_chan_a = initiator->chan_a->channel;
+      cricket::TransportChannel* initiator_chan_b = initiator->chan_b->channel;
+
+      // Since we know these are TransportChannelProxy, type cast it.
+      cricket::TransportChannelProxy* initiator_proxy_chan_a =
+          static_cast<cricket::TransportChannelProxy*>(initiator_chan_a);
+      cricket::TransportChannelProxy* initiator_proxy_chan_b =
+              static_cast<cricket::TransportChannelProxy*>(initiator_chan_b);
+      EXPECT_TRUE(initiator_proxy_chan_a->impl() != NULL);
+      EXPECT_TRUE(initiator_proxy_chan_b->impl() != NULL);
+      EXPECT_EQ(initiator_proxy_chan_a->impl(), initiator_proxy_chan_b->impl());
+
+      cricket::TransportChannel* responder_chan_a = responder->chan_a->channel;
+      cricket::TransportChannel* responder_chan_b = responder->chan_b->channel;
+
+      // Since we know these are TransportChannelProxy, type cast it.
+      cricket::TransportChannelProxy* responder_proxy_chan_a =
+          static_cast<cricket::TransportChannelProxy*>(responder_chan_a);
+      cricket::TransportChannelProxy* responder_proxy_chan_b =
+              static_cast<cricket::TransportChannelProxy*>(responder_chan_b);
+      EXPECT_TRUE(responder_proxy_chan_a->impl() != NULL);
+      EXPECT_TRUE(responder_proxy_chan_b->impl() != NULL);
+      EXPECT_EQ(responder_proxy_chan_a->impl(), responder_proxy_chan_b->impl());
+    }
+    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 = "rtp";
+    std::string content_name_b = content_name;
+    std::string channel_name_b = "rtcp";
+    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 = "rtp";
+    std::string channel_name_b = "rtcp";
+    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 = "rtp";
+    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, 1));
+    EXPECT_TRUE(initiator->HasChannel(content_name, 2));
+
+    // 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, 1));
+    EXPECT_TRUE(initiator->HasChannel(content_name, 2));
+
+    // 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, 1));
+    EXPECT_TRUE(responder->HasChannel(content_name, 2));
+
+    // 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 = "rtp";
+    std::string channel_name_b = "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,  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, 1));
+    EXPECT_TRUE(initiator->HasTransport(content_name));
+    EXPECT_TRUE(initiator->HasChannel(content_name, 2));
+
+    // 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, 1));
+    EXPECT_TRUE(responder->HasTransport(content_name));
+    EXPECT_TRUE(responder->HasChannel(content_name, 2));
+
+    // 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() {
+    SignalingProtocol initiator_protocol = PROTOCOL_JINGLE;
+    SignalingProtocol responder_protocol = PROTOCOL_JINGLE;
+    SignalingProtocol resulting_protocol = PROTOCOL_JINGLE;
+    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 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, true);
+    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, true);
+
+    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,
+                true);
+  }
+
+  void TestSendDescriptionInfo() {
+    talk_base::scoped_ptr<cricket::PortAllocator> allocator(
+        new TestPortAllocator());
+    int next_message_id = 0;
+
+    std::string content_name = "content-name";
+    std::string content_type = "content-type";
+    talk_base::scoped_ptr<TestClient> initiator(
+        new TestClient(allocator.get(), &next_message_id,
+                       kInitiator, PROTOCOL_JINGLE,
+                       content_type,
+                       content_name, "",
+                       "",  ""));
+
+    initiator->CreateSession();
+    cricket::SessionDescription* offer = NewTestSessionDescription(
+        content_name, content_type);
+    std::string initiate_xml = InitiateXml(
+        PROTOCOL_JINGLE, content_name, content_type);
+
+    cricket::ContentInfos contents;
+    TestContentDescription content(content_type, content_type);
+    contents.push_back(
+        cricket::ContentInfo(content_name, content_type, &content));
+    std::string description_info_xml = JingleDescriptionInfoXml(
+        content_name, content_type);
+
+    EXPECT_TRUE(initiator->session->Initiate(kResponder, offer));
+    initiator->ExpectSentStanza(
+        IqSet("0", kInitiator, kResponder, initiate_xml));
+
+    EXPECT_TRUE(initiator->session->SendDescriptionInfoMessage(contents));
+    initiator->ExpectSentStanza(
+        IqSet("1", kInitiator, kResponder, description_info_xml));
+  }
+
+  void DoTestSignalNewDescription(
+      TestClient* client,
+      cricket::BaseSession::State state,
+      cricket::ContentAction expected_content_action,
+      cricket::ContentSource expected_content_source) {
+    // Clean up before the new test.
+    client->new_local_description = false;
+    client->new_remote_description = false;
+
+    client->SetSessionState(state);
+    EXPECT_EQ((expected_content_source == cricket::CS_LOCAL),
+               client->new_local_description);
+    EXPECT_EQ((expected_content_source == cricket::CS_REMOTE),
+               client->new_remote_description);
+    EXPECT_EQ(expected_content_action, client->last_content_action);
+    EXPECT_EQ(expected_content_source, client->last_content_source);
+  }
+
+  void TestCallerSignalNewDescription() {
+    talk_base::scoped_ptr<cricket::PortAllocator> allocator(
+        new TestPortAllocator());
+    int next_message_id = 0;
+
+    std::string content_name = "content-name";
+    std::string content_type = "content-type";
+    talk_base::scoped_ptr<TestClient> initiator(
+        new TestClient(allocator.get(), &next_message_id,
+                       kInitiator, PROTOCOL_JINGLE,
+                       content_type,
+                       content_name, "",
+                       "",  ""));
+
+    initiator->CreateSession();
+
+    // send offer -> send update offer ->
+    // receive pr answer -> receive update pr answer ->
+    // receive answer
+    DoTestSignalNewDescription(
+        initiator.get(), cricket::BaseSession::STATE_SENTINITIATE,
+        cricket::CA_OFFER, cricket::CS_LOCAL);
+
+    DoTestSignalNewDescription(
+        initiator.get(), cricket::BaseSession::STATE_SENTINITIATE,
+        cricket::CA_OFFER, cricket::CS_LOCAL);
+
+    DoTestSignalNewDescription(
+        initiator.get(), cricket::BaseSession::STATE_RECEIVEDPRACCEPT,
+        cricket::CA_PRANSWER, cricket::CS_REMOTE);
+
+    DoTestSignalNewDescription(
+        initiator.get(), cricket::BaseSession::STATE_RECEIVEDPRACCEPT,
+        cricket::CA_PRANSWER, cricket::CS_REMOTE);
+
+    DoTestSignalNewDescription(
+        initiator.get(), cricket::BaseSession::STATE_RECEIVEDACCEPT,
+        cricket::CA_ANSWER, cricket::CS_REMOTE);
+  }
+
+  void TestCalleeSignalNewDescription() {
+    talk_base::scoped_ptr<cricket::PortAllocator> allocator(
+        new TestPortAllocator());
+    int next_message_id = 0;
+
+    std::string content_name = "content-name";
+    std::string content_type = "content-type";
+    talk_base::scoped_ptr<TestClient> initiator(
+        new TestClient(allocator.get(), &next_message_id,
+                       kInitiator, PROTOCOL_JINGLE,
+                       content_type,
+                       content_name, "",
+                       "",  ""));
+
+    initiator->CreateSession();
+
+    // receive offer -> receive update offer ->
+    // send pr answer -> send update pr answer ->
+    // send answer
+    DoTestSignalNewDescription(
+        initiator.get(), cricket::BaseSession::STATE_RECEIVEDINITIATE,
+        cricket::CA_OFFER, cricket::CS_REMOTE);
+
+    DoTestSignalNewDescription(
+        initiator.get(), cricket::BaseSession::STATE_RECEIVEDINITIATE,
+        cricket::CA_OFFER, cricket::CS_REMOTE);
+
+    DoTestSignalNewDescription(
+        initiator.get(), cricket::BaseSession::STATE_SENTPRACCEPT,
+        cricket::CA_PRANSWER, cricket::CS_LOCAL);
+
+    DoTestSignalNewDescription(
+        initiator.get(), cricket::BaseSession::STATE_SENTPRACCEPT,
+        cricket::CA_PRANSWER, cricket::CS_LOCAL);
+
+    DoTestSignalNewDescription(
+        initiator.get(), cricket::BaseSession::STATE_SENTACCEPT,
+        cricket::CA_ANSWER, cricket::CS_LOCAL);
+  }
+
+  void TestGetTransportStats() {
+    talk_base::scoped_ptr<cricket::PortAllocator> allocator(
+        new TestPortAllocator());
+    int next_message_id = 0;
+
+    std::string content_name = "content-name";
+    std::string content_type = "content-type";
+    talk_base::scoped_ptr<TestClient> initiator(
+        new TestClient(allocator.get(), &next_message_id,
+                       kInitiator, PROTOCOL_JINGLE,
+                       content_type,
+                       content_name, "",
+                       "",  ""));
+    initiator->CreateSession();
+
+    cricket::SessionStats stats;
+    EXPECT_TRUE(initiator->session->GetStats(&stats));
+    // At initiation, there are 2 transports.
+    EXPECT_EQ(2ul, stats.proxy_to_transport.size());
+    EXPECT_EQ(2ul, stats.transport_stats.size());
+  }
+};
+
+// 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();
+}
+
+TEST_F(SessionTest, TestSendDescriptionInfo) {
+  TestSendDescriptionInfo();
+}
+
+TEST_F(SessionTest, TestCallerSignalNewDescription) {
+  TestCallerSignalNewDescription();
+}
+
+TEST_F(SessionTest, TestCalleeSignalNewDescription) {
+  TestCalleeSignalNewDescription();
+}
+
+TEST_F(SessionTest, TestGetTransportStats) {
+  TestGetTransportStats();
+}
diff --git a/talk/p2p/base/sessionclient.h b/talk/p2p/base/sessionclient.h
new file mode 100644
index 0000000..10b0c92
--- /dev/null
+++ b/talk/p2p/base/sessionclient.h
@@ -0,0 +1,95 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_SESSIONCLIENT_H_
+#define TALK_P2P_BASE_SESSIONCLIENT_H_
+
+#include "talk/p2p/base/constants.h"
+
+namespace buzz {
+class XmlElement;
+}
+
+namespace cricket {
+
+struct ParseError;
+class Session;
+class ContentDescription;
+
+class ContentParser {
+ public:
+  virtual bool ParseContent(SignalingProtocol protocol,
+                            const buzz::XmlElement* elem,
+                            ContentDescription** content,
+                            ParseError* error) = 0;
+  // If not IsWriteable, then a given content should be "skipped" when
+  // writing in the given protocol, as if it didn't exist.  We assume
+  // most things are writeable.  We do this to avoid strange cases
+  // like data contents in Gingle, which aren't writable.
+  virtual bool IsWritable(SignalingProtocol protocol,
+                          const ContentDescription* content) {
+    return true;
+  }
+  virtual bool WriteContent(SignalingProtocol protocol,
+                            const ContentDescription* content,
+                            buzz::XmlElement** elem,
+                            WriteError* error) = 0;
+  virtual ~ContentParser() {}
+};
+
+// A SessionClient exists in 1-1 relation with each session.  The implementor
+// of this interface is the one that understands *what* the two sides are
+// trying to send to one another.  The lower-level layers only know how to send
+// data; they do not know what is being sent.
+class SessionClient : public ContentParser {
+ public:
+  // Notifies the client of the creation / destruction of sessions of this type.
+  //
+  // IMPORTANT: The SessionClient, in its handling of OnSessionCreate, must
+  // create whatever channels are indicate in the description.  This is because
+  // the remote client may already be attempting to connect those channels. If
+  // we do not create our channel right away, then connection may fail or be
+  // delayed.
+  virtual void OnSessionCreate(Session* session, bool received_initiate) = 0;
+  virtual void OnSessionDestroy(Session* session) = 0;
+
+  virtual bool ParseContent(SignalingProtocol protocol,
+                            const buzz::XmlElement* elem,
+                            ContentDescription** content,
+                            ParseError* error) = 0;
+  virtual bool WriteContent(SignalingProtocol protocol,
+                            const ContentDescription* content,
+                            buzz::XmlElement** elem,
+                            WriteError* error) = 0;
+ protected:
+  // The SessionClient interface explicitly does not include destructor
+  virtual ~SessionClient() { }
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_SESSIONCLIENT_H_
diff --git a/talk/p2p/base/sessiondescription.cc b/talk/p2p/base/sessiondescription.cc
new file mode 100644
index 0000000..7009aa8
--- /dev/null
+++ b/talk/p2p/base/sessiondescription.cc
@@ -0,0 +1,239 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/p2p/base/sessiondescription.h"
+
+#include "talk/xmllite/xmlelement.h"
+
+namespace cricket {
+
+ContentInfo* FindContentInfoByName(
+    ContentInfos& contents, const std::string& name) {
+  for (ContentInfos::iterator content = contents.begin();
+       content != contents.end(); ++content) {
+    if (content->name == name) {
+      return &(*content);
+    }
+  }
+  return NULL;
+}
+
+const ContentInfo* FindContentInfoByName(
+    const ContentInfos& contents, const std::string& name) {
+  for (ContentInfos::const_iterator content = contents.begin();
+       content != contents.end(); ++content) {
+    if (content->name == name) {
+      return &(*content);
+    }
+  }
+  return NULL;
+}
+
+const ContentInfo* FindContentInfoByType(
+    const ContentInfos& contents, const std::string& type) {
+  for (ContentInfos::const_iterator content = contents.begin();
+       content != contents.end(); ++content) {
+    if (content->type == type) {
+      return &(*content);
+    }
+  }
+  return NULL;
+}
+
+const std::string* ContentGroup::FirstContentName() const {
+  return (!content_names_.empty()) ? &(*content_names_.begin()) : NULL;
+}
+
+bool ContentGroup::HasContentName(const std::string& content_name) const {
+  return (std::find(content_names_.begin(), content_names_.end(),
+                    content_name) != content_names_.end());
+}
+
+void ContentGroup::AddContentName(const std::string& content_name) {
+  if (!HasContentName(content_name)) {
+    content_names_.push_back(content_name);
+  }
+}
+
+bool ContentGroup::RemoveContentName(const std::string& content_name) {
+  ContentNames::iterator iter = std::find(
+      content_names_.begin(), content_names_.end(), content_name);
+  if (iter == content_names_.end()) {
+    return false;
+  }
+  content_names_.erase(iter);
+  return true;
+}
+
+SessionDescription* SessionDescription::Copy() const {
+  SessionDescription* copy = new SessionDescription(*this);
+  // Copy all ContentDescriptions.
+  for (ContentInfos::iterator content = copy->contents_.begin();
+      content != copy->contents().end(); ++content) {
+    content->description = content->description->Copy();
+  }
+  return copy;
+}
+
+const ContentInfo* SessionDescription::GetContentByName(
+    const std::string& name) const {
+  return FindContentInfoByName(contents_, name);
+}
+
+ContentInfo* SessionDescription::GetContentByName(
+    const std::string& name)  {
+  return FindContentInfoByName(contents_, name);
+}
+
+const ContentDescription* SessionDescription::GetContentDescriptionByName(
+    const std::string& name) const {
+  const ContentInfo* cinfo = FindContentInfoByName(contents_, name);
+  if (cinfo == NULL) {
+    return NULL;
+  }
+
+  return cinfo->description;
+}
+
+ContentDescription* SessionDescription::GetContentDescriptionByName(
+    const std::string& name) {
+  ContentInfo* cinfo = FindContentInfoByName(contents_, name);
+  if (cinfo == NULL) {
+    return NULL;
+  }
+
+  return cinfo->description;
+}
+
+const ContentInfo* SessionDescription::FirstContentByType(
+    const std::string& type) const {
+  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,
+                                    ContentDescription* description) {
+  contents_.push_back(ContentInfo(name, type, description));
+}
+
+void SessionDescription::AddContent(const std::string& name,
+                                    const std::string& type,
+                                    bool rejected,
+                                    ContentDescription* description) {
+  contents_.push_back(ContentInfo(name, type, rejected, description));
+}
+
+bool SessionDescription::RemoveContentByName(const std::string& name) {
+  for (ContentInfos::iterator content = contents_.begin();
+       content != contents_.end(); ++content) {
+    if (content->name == name) {
+      delete content->description;
+      contents_.erase(content);
+      return true;
+    }
+  }
+
+  return false;
+}
+
+bool SessionDescription::AddTransportInfo(const TransportInfo& transport_info) {
+  if (GetTransportInfoByName(transport_info.content_name) != NULL) {
+    return false;
+  }
+  transport_infos_.push_back(transport_info);
+  return true;
+}
+
+bool SessionDescription::RemoveTransportInfoByName(const std::string& name) {
+  for (TransportInfos::iterator transport_info = transport_infos_.begin();
+       transport_info != transport_infos_.end(); ++transport_info) {
+    if (transport_info->content_name == name) {
+      transport_infos_.erase(transport_info);
+      return true;
+    }
+  }
+  return false;
+}
+
+const TransportInfo* SessionDescription::GetTransportInfoByName(
+    const std::string& name) const {
+  for (TransportInfos::const_iterator iter = transport_infos_.begin();
+       iter != transport_infos_.end(); ++iter) {
+    if (iter->content_name == name) {
+      return &(*iter);
+    }
+  }
+  return NULL;
+}
+
+TransportInfo* SessionDescription::GetTransportInfoByName(
+    const std::string& name) {
+  for (TransportInfos::iterator iter = transport_infos_.begin();
+       iter != transport_infos_.end(); ++iter) {
+    if (iter->content_name == name) {
+      return &(*iter);
+    }
+  }
+  return NULL;
+}
+
+void SessionDescription::RemoveGroupByName(const std::string& name) {
+  for (ContentGroups::iterator iter = content_groups_.begin();
+       iter != content_groups_.end(); ++iter) {
+    if (iter->semantics() == name) {
+      content_groups_.erase(iter);
+      break;
+    }
+  }
+}
+
+bool SessionDescription::HasGroup(const std::string& name) const {
+  for (ContentGroups::const_iterator iter = content_groups_.begin();
+       iter != content_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 = content_groups_.begin();
+       iter != content_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
new file mode 100644
index 0000000..d33b4c3
--- /dev/null
+++ b/talk/p2p/base/sessiondescription.h
@@ -0,0 +1,202 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_SESSIONDESCRIPTION_H_
+#define TALK_P2P_BASE_SESSIONDESCRIPTION_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/constructormagic.h"
+#include "talk/p2p/base/transportinfo.h"
+
+namespace cricket {
+
+// Describes a session content. Individual content types inherit from
+// this class.  Analagous to a <jingle><content><description> or
+// <session><description>.
+class ContentDescription {
+ public:
+  virtual ~ContentDescription() {}
+  virtual ContentDescription* Copy() const = 0;
+};
+
+// Analagous to a <jingle><content> or <session><description>.
+// name = name of <content name="...">
+// type = xmlns of <content>
+struct ContentInfo {
+  ContentInfo() : description(NULL) {}
+  ContentInfo(const std::string& name,
+              const std::string& type,
+              ContentDescription* description) :
+      name(name), type(type), rejected(false), description(description) {}
+  ContentInfo(const std::string& name,
+              const std::string& type,
+              bool rejected,
+              ContentDescription* description) :
+      name(name), type(type), rejected(rejected), description(description) {}
+  std::string name;
+  std::string type;
+  bool rejected;
+  ContentDescription* description;
+};
+
+typedef std::vector<std::string> ContentNames;
+
+// 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) {}
+
+  const std::string& semantics() const { return semantics_; }
+  const ContentNames& content_names() const { return content_names_; }
+
+  const std::string* FirstContentName() const;
+  bool HasContentName(const std::string& content_name) const;
+  void AddContentName(const std::string& content_name);
+  bool RemoveContentName(const std::string& content_name);
+
+ private:
+  std::string semantics_;
+  ContentNames content_names_;
+};
+
+typedef std::vector<ContentInfo> ContentInfos;
+typedef std::vector<ContentGroup> ContentGroups;
+
+const ContentInfo* FindContentInfoByName(
+    const ContentInfos& contents, const std::string& name);
+const ContentInfo* FindContentInfoByType(
+    const ContentInfos& contents, const std::string& type);
+
+// Describes a collection of contents, each with its own name and
+// type.  Analogous to a <jingle> or <session> stanza.  Assumes that
+// contents are unique be name, but doesn't enforce that.
+class SessionDescription {
+ public:
+  SessionDescription() {}
+  explicit SessionDescription(const ContentInfos& contents) :
+      contents_(contents) {}
+  SessionDescription(const ContentInfos& contents,
+                     const ContentGroups& groups) :
+      contents_(contents),
+      content_groups_(groups) {}
+  SessionDescription(const ContentInfos& contents,
+                     const TransportInfos& transports,
+                     const ContentGroups& groups) :
+      contents_(contents),
+      transport_infos_(transports),
+      content_groups_(groups) {}
+  ~SessionDescription() {
+    for (ContentInfos::iterator content = contents_.begin();
+         content != contents_.end(); ++content) {
+      delete content->description;
+    }
+  }
+
+  SessionDescription* Copy() const;
+
+  // Content accessors.
+  const ContentInfos& contents() const { return contents_; }
+  ContentInfos& contents() { return contents_; }
+  const ContentInfo* GetContentByName(const std::string& name) const;
+  ContentInfo* GetContentByName(const std::string& name);
+  const ContentDescription* GetContentDescriptionByName(
+      const std::string& name) const;
+  ContentDescription* GetContentDescriptionByName(const std::string& name);
+  const ContentInfo* FirstContentByType(const std::string& type) const;
+  const ContentInfo* FirstContent() const;
+
+  // Content mutators.
+  // Adds a content to this description. Takes ownership of ContentDescription*.
+  void AddContent(const std::string& name,
+                  const std::string& type,
+                  ContentDescription* description);
+  void AddContent(const std::string& name,
+                  const std::string& type,
+                  bool rejected,
+                  ContentDescription* description);
+  bool RemoveContentByName(const std::string& name);
+
+  // Transport accessors.
+  const TransportInfos& transport_infos() const { return transport_infos_; }
+  TransportInfos& transport_infos() { return transport_infos_; }
+  const TransportInfo* GetTransportInfoByName(
+      const std::string& name) const;
+  TransportInfo* GetTransportInfoByName(const std::string& name);
+  const TransportDescription* GetTransportDescriptionByName(
+      const std::string& name) const {
+    const TransportInfo* tinfo = GetTransportInfoByName(name);
+    return tinfo ? &tinfo->description : NULL;
+  }
+
+  // Transport mutators.
+  void set_transport_infos(const TransportInfos& transport_infos) {
+    transport_infos_ = transport_infos;
+  }
+  // Adds a TransportInfo to this description.
+  // Returns false if a TransportInfo with the same name already exists.
+  bool AddTransportInfo(const TransportInfo& transport_info);
+  bool RemoveTransportInfoByName(const std::string& name);
+
+  // Group accessors.
+  const ContentGroups& groups() const { return content_groups_; }
+  const ContentGroup* GetGroupByName(const std::string& name) const;
+  bool HasGroup(const std::string& name) const;
+
+  // Group mutators.
+  void AddGroup(const ContentGroup& group) { content_groups_.push_back(group); }
+  // Remove the first group with the same semantics specified by |name|.
+  void RemoveGroupByName(const std::string& name);
+
+ private:
+  ContentInfos contents_;
+  TransportInfos transport_infos_;
+  ContentGroups content_groups_;
+};
+
+// Indicates whether a ContentDescription was an offer or an answer, as
+// described in http://www.ietf.org/rfc/rfc3264.txt. CA_UPDATE
+// indicates a jingle update message which contains a subset of a full
+// session description
+enum ContentAction {
+  CA_OFFER, CA_PRANSWER, CA_ANSWER, CA_UPDATE
+};
+
+// Indicates whether a ContentDescription was sent by the local client
+// or received from the remote client.
+enum ContentSource {
+  CS_LOCAL, CS_REMOTE
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_SESSIONDESCRIPTION_H_
diff --git a/talk/p2p/base/sessionid.h b/talk/p2p/base/sessionid.h
new file mode 100644
index 0000000..6942942
--- /dev/null
+++ b/talk/p2p/base/sessionid.h
@@ -0,0 +1,37 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_SESSIONID_H_
+#define TALK_P2P_BASE_SESSIONID_H_
+
+// TODO: Remove this file.
+
+namespace cricket {
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_SESSIONID_H_
diff --git a/talk/p2p/base/sessionmanager.cc b/talk/p2p/base/sessionmanager.cc
new file mode 100644
index 0000000..6b3d60a
--- /dev/null
+++ b/talk/p2p/base/sessionmanager.cc
@@ -0,0 +1,313 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/p2p/base/sessionmanager.h"
+
+#include "talk/base/common.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stringencode.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/session.h"
+#include "talk/p2p/base/sessionmessages.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/jid.h"
+
+namespace cricket {
+
+SessionManager::SessionManager(PortAllocator *allocator,
+                               talk_base::Thread *worker) {
+  allocator_ = allocator;
+  signaling_thread_ = talk_base::Thread::Current();
+  if (worker == NULL) {
+    worker_thread_ = talk_base::Thread::Current();
+  } else {
+    worker_thread_ = worker;
+  }
+  timeout_ = 50;
+}
+
+SessionManager::~SessionManager() {
+  // Note: Session::Terminate occurs asynchronously, so it's too late to
+  // delete them now.  They better be all gone.
+  ASSERT(session_map_.empty());
+  // TerminateAll();
+  SignalDestroyed();
+}
+
+void SessionManager::AddClient(const std::string& content_type,
+                               SessionClient* client) {
+  ASSERT(client_map_.find(content_type) == client_map_.end());
+  client_map_[content_type] = client;
+}
+
+void SessionManager::RemoveClient(const std::string& content_type) {
+  ClientMap::iterator iter = client_map_.find(content_type);
+  ASSERT(iter != client_map_.end());
+  client_map_.erase(iter);
+}
+
+SessionClient* SessionManager::GetClient(const std::string& content_type) {
+  ClientMap::iterator iter = client_map_.find(content_type);
+  return (iter != client_map_.end()) ? iter->second : NULL;
+}
+
+Session* SessionManager::CreateSession(const std::string& local_name,
+                                       const std::string& content_type) {
+  return CreateSession(local_name, local_name,
+                       talk_base::ToString(talk_base::CreateRandomId64()),
+                       content_type, false);
+}
+
+Session* SessionManager::CreateSession(
+    const std::string& local_name, const std::string& initiator_name,
+    const std::string& sid, const std::string& content_type,
+    bool received_initiate) {
+  SessionClient* client = GetClient(content_type);
+  ASSERT(client != NULL);
+
+  Session* session = new Session(this, local_name, initiator_name,
+                                 sid, content_type, client);
+  session->set_identity(transport_desc_factory_.identity());
+  session_map_[session->id()] = session;
+  session->SignalRequestSignaling.connect(
+      this, &SessionManager::OnRequestSignaling);
+  session->SignalOutgoingMessage.connect(
+      this, &SessionManager::OnOutgoingMessage);
+  session->SignalErrorMessage.connect(this, &SessionManager::OnErrorMessage);
+  SignalSessionCreate(session, received_initiate);
+  session->client()->OnSessionCreate(session, received_initiate);
+  return session;
+}
+
+void SessionManager::DestroySession(Session* session) {
+  if (session != NULL) {
+    SessionMap::iterator it = session_map_.find(session->id());
+    if (it != session_map_.end()) {
+      SignalSessionDestroy(session);
+      session->client()->OnSessionDestroy(session);
+      session_map_.erase(it);
+      delete session;
+    }
+  }
+}
+
+Session* SessionManager::GetSession(const std::string& sid) {
+  SessionMap::iterator it = session_map_.find(sid);
+  if (it != session_map_.end())
+    return it->second;
+  return NULL;
+}
+
+void SessionManager::TerminateAll() {
+  while (session_map_.begin() != session_map_.end()) {
+    Session* session = session_map_.begin()->second;
+    session->Terminate();
+  }
+}
+
+bool SessionManager::IsSessionMessage(const buzz::XmlElement* stanza) {
+  return cricket::IsSessionMessage(stanza);
+}
+
+Session* SessionManager::FindSession(const std::string& sid,
+                                     const std::string& remote_name) {
+  SessionMap::iterator iter = session_map_.find(sid);
+  if (iter == session_map_.end())
+    return NULL;
+
+  Session* session = iter->second;
+  if (buzz::Jid(remote_name) != buzz::Jid(session->remote_name()))
+    return NULL;
+
+  return session;
+}
+
+void SessionManager::OnIncomingMessage(const buzz::XmlElement* stanza) {
+  SessionMessage msg;
+  ParseError error;
+
+  if (!ParseSessionMessage(stanza, &msg, &error)) {
+    SendErrorMessage(stanza, buzz::QN_STANZA_BAD_REQUEST, "modify",
+                     error.text, NULL);
+    return;
+  }
+
+  Session* session = FindSession(msg.sid, msg.from);
+  if (session) {
+    session->OnIncomingMessage(msg);
+    return;
+  }
+  if (msg.type != ACTION_SESSION_INITIATE) {
+    SendErrorMessage(stanza, buzz::QN_STANZA_BAD_REQUEST, "modify",
+                     "unknown session", NULL);
+    return;
+  }
+
+  std::string content_type;
+  if (!ParseContentType(msg.protocol, msg.action_elem,
+                        &content_type, &error)) {
+    SendErrorMessage(stanza, buzz::QN_STANZA_BAD_REQUEST, "modify",
+                     error.text, NULL);
+    return;
+  }
+
+  if (!GetClient(content_type)) {
+    SendErrorMessage(stanza, buzz::QN_STANZA_BAD_REQUEST, "modify",
+                     "unknown content type: " + content_type, NULL);
+    return;
+  }
+
+  session = CreateSession(msg.to, msg.initiator, msg.sid,
+                          content_type, true);
+  session->OnIncomingMessage(msg);
+}
+
+void SessionManager::OnIncomingResponse(const buzz::XmlElement* orig_stanza,
+    const buzz::XmlElement* response_stanza) {
+  if (orig_stanza == NULL || response_stanza == NULL) {
+    return;
+  }
+
+  SessionMessage msg;
+  ParseError error;
+  if (!ParseSessionMessage(orig_stanza, &msg, &error)) {
+    LOG(LS_WARNING) << "Error parsing incoming response: " << error.text
+                    << ":" << orig_stanza;
+    return;
+  }
+
+  Session* session = FindSession(msg.sid, msg.to);
+  if (session) {
+    session->OnIncomingResponse(orig_stanza, response_stanza, msg);
+  }
+}
+
+void SessionManager::OnFailedSend(const buzz::XmlElement* orig_stanza,
+                                  const buzz::XmlElement* error_stanza) {
+  SessionMessage msg;
+  ParseError error;
+  if (!ParseSessionMessage(orig_stanza, &msg, &error)) {
+    return;  // TODO: log somewhere?
+  }
+
+  Session* session = FindSession(msg.sid, msg.to);
+  if (session) {
+    talk_base::scoped_ptr<buzz::XmlElement> synthetic_error;
+    if (!error_stanza) {
+      // A failed send is semantically equivalent to an error response, so we
+      // can just turn the former into the latter.
+      synthetic_error.reset(
+        CreateErrorMessage(orig_stanza, buzz::QN_STANZA_ITEM_NOT_FOUND,
+                           "cancel", "Recipient did not respond", NULL));
+      error_stanza = synthetic_error.get();
+    }
+
+    session->OnFailedSend(orig_stanza, error_stanza);
+  }
+}
+
+void SessionManager::SendErrorMessage(const buzz::XmlElement* stanza,
+                                      const buzz::QName& name,
+                                      const std::string& type,
+                                      const std::string& text,
+                                      const buzz::XmlElement* extra_info) {
+  talk_base::scoped_ptr<buzz::XmlElement> msg(
+      CreateErrorMessage(stanza, name, type, text, extra_info));
+  SignalOutgoingMessage(this, msg.get());
+}
+
+buzz::XmlElement* SessionManager::CreateErrorMessage(
+    const buzz::XmlElement* stanza,
+    const buzz::QName& name,
+    const std::string& type,
+    const std::string& text,
+    const buzz::XmlElement* extra_info) {
+  buzz::XmlElement* iq = new buzz::XmlElement(buzz::QN_IQ);
+  iq->SetAttr(buzz::QN_TO, stanza->Attr(buzz::QN_FROM));
+  iq->SetAttr(buzz::QN_ID, stanza->Attr(buzz::QN_ID));
+  iq->SetAttr(buzz::QN_TYPE, "error");
+
+  CopyXmlChildren(stanza, iq);
+
+  buzz::XmlElement* error = new buzz::XmlElement(buzz::QN_ERROR);
+  error->SetAttr(buzz::QN_TYPE, type);
+  iq->AddElement(error);
+
+  // If the error name is not in the standard namespace, we have to first add
+  // some error from that namespace.
+  if (name.Namespace() != buzz::NS_STANZA) {
+     error->AddElement(
+         new buzz::XmlElement(buzz::QN_STANZA_UNDEFINED_CONDITION));
+  }
+  error->AddElement(new buzz::XmlElement(name));
+
+  if (extra_info)
+    error->AddElement(new buzz::XmlElement(*extra_info));
+
+  if (text.size() > 0) {
+    // It's okay to always use English here.  This text is for debugging
+    // purposes only.
+    buzz::XmlElement* text_elem = new buzz::XmlElement(buzz::QN_STANZA_TEXT);
+    text_elem->SetAttr(buzz::QN_XML_LANG, "en");
+    text_elem->SetBodyText(text);
+    error->AddElement(text_elem);
+  }
+
+  // TODO: Should we include error codes as well for SIP compatibility?
+
+  return iq;
+}
+
+void SessionManager::OnOutgoingMessage(Session* session,
+                                       const buzz::XmlElement* stanza) {
+  SignalOutgoingMessage(this, stanza);
+}
+
+void SessionManager::OnErrorMessage(BaseSession* session,
+                                    const buzz::XmlElement* stanza,
+                                    const buzz::QName& name,
+                                    const std::string& type,
+                                    const std::string& text,
+                                    const buzz::XmlElement* extra_info) {
+  SendErrorMessage(stanza, name, type, text, extra_info);
+}
+
+void SessionManager::OnSignalingReady() {
+  for (SessionMap::iterator it = session_map_.begin();
+      it != session_map_.end();
+      ++it) {
+    it->second->OnSignalingReady();
+  }
+}
+
+void SessionManager::OnRequestSignaling(Session* session) {
+  SignalRequestSignaling();
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/sessionmanager.h b/talk/p2p/base/sessionmanager.h
new file mode 100644
index 0000000..dcdf1ed
--- /dev/null
+++ b/talk/p2p/base/sessionmanager.h
@@ -0,0 +1,207 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_SESSIONMANAGER_H_
+#define TALK_P2P_BASE_SESSIONMANAGER_H_
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "talk/base/sigslot.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/portallocator.h"
+#include "talk/p2p/base/transportdescriptionfactory.h"
+
+namespace buzz {
+class QName;
+class XmlElement;
+}
+
+namespace cricket {
+
+class Session;
+class BaseSession;
+class SessionClient;
+
+// SessionManager manages session instances.
+class SessionManager : public sigslot::has_slots<> {
+ public:
+  SessionManager(PortAllocator *allocator,
+                 talk_base::Thread *worker_thread = NULL);
+  virtual ~SessionManager();
+
+  PortAllocator *port_allocator() const { return allocator_; }
+  talk_base::Thread *worker_thread() const { return worker_thread_; }
+  talk_base::Thread *signaling_thread() const { return signaling_thread_; }
+
+  int session_timeout() const { return timeout_; }
+  void set_session_timeout(int timeout) { timeout_ = timeout; }
+
+  // Set what transport protocol we want to default to.
+  void set_transport_protocol(TransportProtocol proto) {
+     transport_desc_factory_.set_protocol(proto);
+  }
+
+  // Control use of DTLS. An identity must be supplied if DTLS is enabled.
+  void set_secure(SecurePolicy policy) {
+    transport_desc_factory_.set_secure(policy);
+  }
+  void set_identity(talk_base::SSLIdentity* identity) {
+    transport_desc_factory_.set_identity(identity);
+  }
+  const TransportDescriptionFactory* transport_desc_factory() const {
+    return &transport_desc_factory_;
+  }
+
+  // Registers support for the given client.  If we receive an initiate
+  // describing a session of the given type, we will automatically create a
+  // Session object and notify this client.  The client may then accept or
+  // reject the session.
+  void AddClient(const std::string& content_type, SessionClient* client);
+  void RemoveClient(const std::string& content_type);
+  SessionClient* GetClient(const std::string& content_type);
+
+  // Creates a new session.  The given name is the JID of the client on whose
+  // behalf we initiate the session.
+  Session *CreateSession(const std::string& local_name,
+                         const std::string& content_type);
+
+  // Destroys the given session.
+  void DestroySession(Session *session);
+
+  // Returns the session with the given ID or NULL if none exists.
+  Session *GetSession(const std::string& sid);
+
+  // Terminates all of the sessions created by this manager.
+  void TerminateAll();
+
+  // These are signaled whenever the set of existing sessions changes.
+  sigslot::signal2<Session *, bool> SignalSessionCreate;
+  sigslot::signal1<Session *> SignalSessionDestroy;
+
+  // Determines whether the given stanza is intended for some session.
+  bool IsSessionMessage(const buzz::XmlElement* stanza);
+
+  // Given a sid, initiator, and remote_name, this finds the matching Session
+  Session* FindSession(const std::string& sid,
+                       const std::string& remote_name);
+
+  // Called when we receive a stanza for which IsSessionMessage is true.
+  void OnIncomingMessage(const buzz::XmlElement* stanza);
+
+  // Called when we get a response to a message that we sent.
+  void OnIncomingResponse(const buzz::XmlElement* orig_stanza,
+                          const buzz::XmlElement* response_stanza);
+
+  // Called if an attempted to send times out or an error is returned.  In the
+  // timeout case error_stanza will be NULL
+  void OnFailedSend(const buzz::XmlElement* orig_stanza,
+                    const buzz::XmlElement* error_stanza);
+
+  // Signalled each time a session generates a signaling message to send.
+  // Also signalled on errors, but with a NULL session.
+  sigslot::signal2<SessionManager*,
+                   const buzz::XmlElement*> SignalOutgoingMessage;
+
+  // Signaled before sessions try to send certain signaling messages.  The
+  // client should call OnSignalingReady once it is safe to send them.  These
+  // steps are taken so that we don't send signaling messages trying to
+  // re-establish the connectivity of a session when the client cannot send
+  // the messages (and would probably just drop them on the floor).
+  //
+  // Note: you can connect this directly to OnSignalingReady(), if a signalling
+  // check is not supported.
+  sigslot::signal0<> SignalRequestSignaling;
+  void OnSignalingReady();
+
+  // Signaled when this SessionManager is deleted.
+  sigslot::signal0<> SignalDestroyed;
+
+ private:
+  typedef std::map<std::string, Session*> SessionMap;
+  typedef std::map<std::string, SessionClient*> ClientMap;
+
+  // Helper function for CreateSession.  This is also invoked when we receive
+  // a message attempting to initiate a session with this client.
+  Session *CreateSession(const std::string& local_name,
+                         const std::string& initiator,
+                         const std::string& sid,
+                         const std::string& content_type,
+                         bool received_initiate);
+
+  // Attempts to find a registered session type whose description appears as
+  // a child of the session element.  Such a child should be present indicating
+  // the application they hope to initiate.
+  std::string FindClient(const buzz::XmlElement* session);
+
+  // Sends a message back to the other client indicating that we found an error
+  // in the stanza they sent.  name identifies the error, type is one of the
+  // standard XMPP types (cancel, continue, modify, auth, wait), and text is a
+  // description for debugging purposes.
+  void SendErrorMessage(const buzz::XmlElement* stanza,
+                        const buzz::QName& name,
+                        const std::string& type,
+                        const std::string& text,
+                        const buzz::XmlElement* extra_info);
+
+  // Creates and returns an error message from the given components.  The
+  // caller is responsible for deleting this.
+  buzz::XmlElement* CreateErrorMessage(
+      const buzz::XmlElement* stanza,
+      const buzz::QName& name,
+      const std::string& type,
+      const std::string& text,
+      const buzz::XmlElement* extra_info);
+
+  // Called each time a session requests signaling.
+  void OnRequestSignaling(Session* session);
+
+  // Called each time a session has an outgoing message.
+  void OnOutgoingMessage(Session* session, const buzz::XmlElement* stanza);
+
+  // Called each time a session has an error to send.
+  void OnErrorMessage(BaseSession* session,
+                      const buzz::XmlElement* stanza,
+                      const buzz::QName& name,
+                      const std::string& type,
+                      const std::string& text,
+                      const buzz::XmlElement* extra_info);
+
+  PortAllocator *allocator_;
+  talk_base::Thread *signaling_thread_;
+  talk_base::Thread *worker_thread_;
+  int timeout_;
+  TransportDescriptionFactory transport_desc_factory_;
+  SessionMap session_map_;
+  ClientMap client_map_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_SESSIONMANAGER_H_
diff --git a/talk/p2p/base/sessionmessages.cc b/talk/p2p/base/sessionmessages.cc
new file mode 100644
index 0000000..031c3d6
--- /dev/null
+++ b/talk/p2p/base/sessionmessages.cc
@@ -0,0 +1,1147 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/p2p/base/sessionmessages.h"
+
+#include <stdio.h>
+#include <string>
+
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stringutils.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/p2ptransport.h"
+#include "talk/p2p/base/parsing.h"
+#include "talk/p2p/base/sessionclient.h"
+#include "talk/p2p/base/sessiondescription.h"
+#include "talk/p2p/base/transport.h"
+#include "talk/xmllite/xmlconstants.h"
+#include "talk/xmpp/constants.h"
+
+namespace cricket {
+
+ActionType ToActionType(const std::string& type) {
+  if (type == GINGLE_ACTION_INITIATE)
+    return ACTION_SESSION_INITIATE;
+  if (type == GINGLE_ACTION_INFO)
+    return ACTION_SESSION_INFO;
+  if (type == GINGLE_ACTION_ACCEPT)
+    return ACTION_SESSION_ACCEPT;
+  if (type == GINGLE_ACTION_REJECT)
+    return ACTION_SESSION_REJECT;
+  if (type == GINGLE_ACTION_TERMINATE)
+    return ACTION_SESSION_TERMINATE;
+  if (type == GINGLE_ACTION_CANDIDATES)
+    return ACTION_TRANSPORT_INFO;
+  if (type == JINGLE_ACTION_SESSION_INITIATE)
+    return ACTION_SESSION_INITIATE;
+  if (type == JINGLE_ACTION_TRANSPORT_INFO)
+    return ACTION_TRANSPORT_INFO;
+  if (type == JINGLE_ACTION_TRANSPORT_ACCEPT)
+    return ACTION_TRANSPORT_ACCEPT;
+  if (type == JINGLE_ACTION_SESSION_INFO)
+    return ACTION_SESSION_INFO;
+  if (type == JINGLE_ACTION_SESSION_ACCEPT)
+    return ACTION_SESSION_ACCEPT;
+  if (type == JINGLE_ACTION_SESSION_TERMINATE)
+    return ACTION_SESSION_TERMINATE;
+  if (type == JINGLE_ACTION_TRANSPORT_INFO)
+    return ACTION_TRANSPORT_INFO;
+  if (type == JINGLE_ACTION_TRANSPORT_ACCEPT)
+    return ACTION_TRANSPORT_ACCEPT;
+  if (type == JINGLE_ACTION_DESCRIPTION_INFO)
+    return ACTION_DESCRIPTION_INFO;
+  if (type == GINGLE_ACTION_UPDATE)
+    return ACTION_DESCRIPTION_INFO;
+
+  return ACTION_UNKNOWN;
+}
+
+std::string ToJingleString(ActionType type) {
+  switch (type) {
+    case ACTION_SESSION_INITIATE:
+      return JINGLE_ACTION_SESSION_INITIATE;
+    case ACTION_SESSION_INFO:
+      return JINGLE_ACTION_SESSION_INFO;
+    case ACTION_DESCRIPTION_INFO:
+      return JINGLE_ACTION_DESCRIPTION_INFO;
+    case ACTION_SESSION_ACCEPT:
+      return JINGLE_ACTION_SESSION_ACCEPT;
+    // Notice that reject and terminate both go to
+    // "session-terminate", but there is no "session-reject".
+    case ACTION_SESSION_REJECT:
+    case ACTION_SESSION_TERMINATE:
+      return JINGLE_ACTION_SESSION_TERMINATE;
+    case ACTION_TRANSPORT_INFO:
+      return JINGLE_ACTION_TRANSPORT_INFO;
+    case ACTION_TRANSPORT_ACCEPT:
+      return JINGLE_ACTION_TRANSPORT_ACCEPT;
+    default:
+      return "";
+  }
+}
+
+std::string ToGingleString(ActionType type) {
+  switch (type) {
+    case ACTION_SESSION_INITIATE:
+      return GINGLE_ACTION_INITIATE;
+    case ACTION_SESSION_INFO:
+      return GINGLE_ACTION_INFO;
+    case ACTION_SESSION_ACCEPT:
+      return GINGLE_ACTION_ACCEPT;
+    case ACTION_SESSION_REJECT:
+      return GINGLE_ACTION_REJECT;
+    case ACTION_SESSION_TERMINATE:
+      return GINGLE_ACTION_TERMINATE;
+    case ACTION_TRANSPORT_INFO:
+      return GINGLE_ACTION_CANDIDATES;
+    default:
+      return "";
+  }
+}
+
+
+bool IsJingleMessage(const buzz::XmlElement* stanza) {
+  const buzz::XmlElement* jingle = stanza->FirstNamed(QN_JINGLE);
+  if (jingle == NULL)
+    return false;
+
+  return (jingle->HasAttr(buzz::QN_ACTION) && jingle->HasAttr(QN_SID));
+}
+
+bool IsGingleMessage(const buzz::XmlElement* stanza) {
+  const buzz::XmlElement* session = stanza->FirstNamed(QN_GINGLE_SESSION);
+  if (session == NULL)
+    return false;
+
+  return (session->HasAttr(buzz::QN_TYPE) &&
+          session->HasAttr(buzz::QN_ID)   &&
+          session->HasAttr(QN_INITIATOR));
+}
+
+bool IsSessionMessage(const buzz::XmlElement* stanza) {
+  return (stanza->Name() == buzz::QN_IQ &&
+          stanza->Attr(buzz::QN_TYPE) == buzz::STR_SET &&
+          (IsJingleMessage(stanza) ||
+           IsGingleMessage(stanza)));
+}
+
+bool ParseGingleSessionMessage(const buzz::XmlElement* session,
+                               SessionMessage* msg,
+                               ParseError* error) {
+  msg->protocol = PROTOCOL_GINGLE;
+  std::string type_string = session->Attr(buzz::QN_TYPE);
+  msg->type = ToActionType(type_string);
+  msg->sid = session->Attr(buzz::QN_ID);
+  msg->initiator = session->Attr(QN_INITIATOR);
+  msg->action_elem = session;
+
+  if (msg->type == ACTION_UNKNOWN)
+    return BadParse("unknown action: " + type_string, error);
+
+  return true;
+}
+
+bool ParseJingleSessionMessage(const buzz::XmlElement* jingle,
+                               SessionMessage* msg,
+                               ParseError* error) {
+  msg->protocol = PROTOCOL_JINGLE;
+  std::string type_string = jingle->Attr(buzz::QN_ACTION);
+  msg->type = ToActionType(type_string);
+  msg->sid = jingle->Attr(QN_SID);
+  msg->initiator = GetXmlAttr(jingle, QN_INITIATOR, buzz::STR_EMPTY);
+  msg->action_elem = jingle;
+
+  if (msg->type == ACTION_UNKNOWN)
+    return BadParse("unknown action: " + type_string, error);
+
+  return true;
+}
+
+bool ParseHybridSessionMessage(const buzz::XmlElement* jingle,
+                               SessionMessage* msg,
+                               ParseError* error) {
+  if (!ParseJingleSessionMessage(jingle, msg, error))
+    return false;
+  msg->protocol = PROTOCOL_HYBRID;
+
+  return true;
+}
+
+bool ParseSessionMessage(const buzz::XmlElement* stanza,
+                         SessionMessage* msg,
+                         ParseError* error) {
+  msg->id = stanza->Attr(buzz::QN_ID);
+  msg->from = stanza->Attr(buzz::QN_FROM);
+  msg->to = stanza->Attr(buzz::QN_TO);
+  msg->stanza = stanza;
+
+  const buzz::XmlElement* jingle = stanza->FirstNamed(QN_JINGLE);
+  const buzz::XmlElement* session = stanza->FirstNamed(QN_GINGLE_SESSION);
+  if (jingle && session)
+    return ParseHybridSessionMessage(jingle, msg, error);
+  if (jingle != NULL)
+    return ParseJingleSessionMessage(jingle, msg, error);
+  if (session != NULL)
+    return ParseGingleSessionMessage(session, msg, error);
+  return false;
+}
+
+buzz::XmlElement* WriteGingleAction(const SessionMessage& msg,
+                                    const XmlElements& action_elems) {
+  buzz::XmlElement* session = new buzz::XmlElement(QN_GINGLE_SESSION, true);
+  session->AddAttr(buzz::QN_TYPE, ToGingleString(msg.type));
+  session->AddAttr(buzz::QN_ID, msg.sid);
+  session->AddAttr(QN_INITIATOR, msg.initiator);
+  AddXmlChildren(session, action_elems);
+  return session;
+}
+
+buzz::XmlElement* WriteJingleAction(const SessionMessage& msg,
+                                    const XmlElements& action_elems) {
+  buzz::XmlElement* jingle = new buzz::XmlElement(QN_JINGLE, true);
+  jingle->AddAttr(buzz::QN_ACTION, ToJingleString(msg.type));
+  jingle->AddAttr(QN_SID, msg.sid);
+  if (msg.type == ACTION_SESSION_INITIATE) {
+    jingle->AddAttr(QN_INITIATOR, msg.initiator);
+  }
+  AddXmlChildren(jingle, action_elems);
+  return jingle;
+}
+
+void WriteSessionMessage(const SessionMessage& msg,
+                         const XmlElements& action_elems,
+                         buzz::XmlElement* stanza) {
+  stanza->SetAttr(buzz::QN_TO, msg.to);
+  stanza->SetAttr(buzz::QN_TYPE, buzz::STR_SET);
+
+  if (msg.protocol == PROTOCOL_GINGLE) {
+    stanza->AddElement(WriteGingleAction(msg, action_elems));
+  } else {
+    stanza->AddElement(WriteJingleAction(msg, action_elems));
+  }
+}
+
+
+TransportParser* GetTransportParser(const TransportParserMap& trans_parsers,
+                                    const std::string& transport_type) {
+  TransportParserMap::const_iterator map = trans_parsers.find(transport_type);
+  if (map == trans_parsers.end()) {
+    return NULL;
+  } else {
+    return map->second;
+  }
+}
+
+CandidateTranslator* GetCandidateTranslator(
+    const CandidateTranslatorMap& translators,
+    const std::string& content_name) {
+  CandidateTranslatorMap::const_iterator map = translators.find(content_name);
+  if (map == translators.end()) {
+    return NULL;
+  } else {
+    return map->second;
+  }
+}
+
+bool GetParserAndTranslator(const TransportParserMap& trans_parsers,
+                            const CandidateTranslatorMap& translators,
+                            const std::string& transport_type,
+                            const std::string& content_name,
+                            TransportParser** parser,
+                            CandidateTranslator** translator,
+                            ParseError* error) {
+  *parser = GetTransportParser(trans_parsers, transport_type);
+  if (*parser == NULL) {
+    return BadParse("unknown transport type: " + transport_type, error);
+  }
+  // Not having a translator isn't fatal when parsing. If this is called for an
+  // initiate message, we won't have our proxies set up to do the translation.
+  // Fortunately, for the cases where translation is needed, candidates are
+  // never sent in initiates.
+  *translator = GetCandidateTranslator(translators, content_name);
+  return true;
+}
+
+bool GetParserAndTranslator(const TransportParserMap& trans_parsers,
+                            const CandidateTranslatorMap& translators,
+                            const std::string& transport_type,
+                            const std::string& content_name,
+                            TransportParser** parser,
+                            CandidateTranslator** translator,
+                            WriteError* error) {
+  *parser = GetTransportParser(trans_parsers, transport_type);
+  if (*parser == NULL) {
+    return BadWrite("unknown transport type: " + transport_type, error);
+  }
+  *translator = GetCandidateTranslator(translators, content_name);
+  if (*translator == NULL) {
+    return BadWrite("unknown content name: " + content_name, error);
+  }
+  return true;
+}
+
+bool ParseGingleCandidate(const buzz::XmlElement* candidate_elem,
+                          const TransportParserMap& trans_parsers,
+                          const CandidateTranslatorMap& translators,
+                          const std::string& content_name,
+                          Candidates* candidates,
+                          ParseError* error) {
+  TransportParser* trans_parser;
+  CandidateTranslator* translator;
+  if (!GetParserAndTranslator(trans_parsers, translators,
+                              NS_GINGLE_P2P, content_name,
+                              &trans_parser, &translator, error))
+    return false;
+
+  Candidate candidate;
+  if (!trans_parser->ParseGingleCandidate(
+          candidate_elem, translator, &candidate, error)) {
+    return false;
+  }
+
+  candidates->push_back(candidate);
+  return true;
+}
+
+bool ParseGingleCandidates(const buzz::XmlElement* parent,
+                           const TransportParserMap& trans_parsers,
+                           const CandidateTranslatorMap& translators,
+                           const std::string& content_name,
+                           Candidates* candidates,
+                           ParseError* error) {
+  for (const buzz::XmlElement* candidate_elem = parent->FirstElement();
+       candidate_elem != NULL;
+       candidate_elem = candidate_elem->NextElement()) {
+    if (candidate_elem->Name().LocalPart() == LN_CANDIDATE) {
+      if (!ParseGingleCandidate(candidate_elem, trans_parsers, translators,
+                                content_name, candidates, error)) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+bool ParseGingleTransportInfos(const buzz::XmlElement* action_elem,
+                               const ContentInfos& contents,
+                               const TransportParserMap& trans_parsers,
+                               const CandidateTranslatorMap& translators,
+                               TransportInfos* tinfos,
+                               ParseError* error) {
+  bool has_audio = FindContentInfoByName(contents, CN_AUDIO) != NULL;
+  bool has_video = FindContentInfoByName(contents, CN_VIDEO) != NULL;
+
+  // If we don't have media, no need to separate the candidates.
+  if (!has_audio && !has_video) {
+    TransportInfo tinfo(CN_OTHER,
+        TransportDescription(NS_GINGLE_P2P, Candidates()));
+    if (!ParseGingleCandidates(action_elem, trans_parsers, translators,
+                               CN_OTHER, &tinfo.description.candidates,
+                               error)) {
+      return false;
+    }
+
+    tinfos->push_back(tinfo);
+    return true;
+  }
+
+  // If we have media, separate the candidates.
+  TransportInfo audio_tinfo(CN_AUDIO,
+                            TransportDescription(NS_GINGLE_P2P, Candidates()));
+  TransportInfo video_tinfo(CN_VIDEO,
+                            TransportDescription(NS_GINGLE_P2P, Candidates()));
+  for (const buzz::XmlElement* candidate_elem = action_elem->FirstElement();
+       candidate_elem != NULL;
+       candidate_elem = candidate_elem->NextElement()) {
+    if (candidate_elem->Name().LocalPart() == LN_CANDIDATE) {
+      const std::string& channel_name = candidate_elem->Attr(buzz::QN_NAME);
+      if (has_audio &&
+          (channel_name == GICE_CHANNEL_NAME_RTP ||
+           channel_name == GICE_CHANNEL_NAME_RTCP)) {
+        if (!ParseGingleCandidate(
+                candidate_elem, trans_parsers,
+                translators, CN_AUDIO,
+                &audio_tinfo.description.candidates, error)) {
+          return false;
+        }
+      } else if (has_video &&
+                 (channel_name == GICE_CHANNEL_NAME_VIDEO_RTP ||
+                  channel_name == GICE_CHANNEL_NAME_VIDEO_RTCP)) {
+        if (!ParseGingleCandidate(
+                candidate_elem, trans_parsers,
+                translators, CN_VIDEO,
+                &video_tinfo.description.candidates, error)) {
+          return false;
+        }
+      } else {
+        return BadParse("Unknown channel name: " + channel_name, error);
+      }
+    }
+  }
+
+  if (has_audio) {
+    tinfos->push_back(audio_tinfo);
+  }
+  if (has_video) {
+    tinfos->push_back(video_tinfo);
+  }
+  return true;
+}
+
+bool ParseJingleTransportInfo(const buzz::XmlElement* trans_elem,
+                              const std::string& content_name,
+                              const TransportParserMap& trans_parsers,
+                              const CandidateTranslatorMap& translators,
+                              TransportInfo* tinfo,
+                              ParseError* error) {
+  TransportParser* trans_parser;
+  CandidateTranslator* translator;
+  if (!GetParserAndTranslator(trans_parsers, translators,
+                              trans_elem->Name().Namespace(), content_name,
+                              &trans_parser, &translator, error))
+    return false;
+
+  TransportDescription tdesc;
+  if (!trans_parser->ParseTransportDescription(trans_elem, translator,
+                                               &tdesc, error))
+    return false;
+
+  *tinfo = TransportInfo(content_name, tdesc);
+  return true;
+}
+
+bool ParseJingleTransportInfos(const buzz::XmlElement* jingle,
+                               const ContentInfos& contents,
+                               const TransportParserMap trans_parsers,
+                               const CandidateTranslatorMap& translators,
+                               TransportInfos* tinfos,
+                               ParseError* error) {
+  for (const buzz::XmlElement* pair_elem
+           = jingle->FirstNamed(QN_JINGLE_CONTENT);
+       pair_elem != NULL;
+       pair_elem = pair_elem->NextNamed(QN_JINGLE_CONTENT)) {
+    std::string content_name;
+    if (!RequireXmlAttr(pair_elem, QN_JINGLE_CONTENT_NAME,
+                        &content_name, error))
+      return false;
+
+    const ContentInfo* content = FindContentInfoByName(contents, content_name);
+    if (!content)
+      return BadParse("Unknown content name: " + content_name, error);
+
+    const buzz::XmlElement* trans_elem;
+    if (!RequireXmlChild(pair_elem, LN_TRANSPORT, &trans_elem, error))
+      return false;
+
+    TransportInfo tinfo;
+    if (!ParseJingleTransportInfo(trans_elem, content->name,
+                                  trans_parsers, translators,
+                                  &tinfo, error))
+      return false;
+
+    tinfos->push_back(tinfo);
+  }
+
+  return true;
+}
+
+buzz::XmlElement* NewTransportElement(const std::string& name) {
+  return new buzz::XmlElement(buzz::QName(name, LN_TRANSPORT), true);
+}
+
+bool WriteGingleCandidates(const Candidates& candidates,
+                           const TransportParserMap& trans_parsers,
+                           const std::string& transport_type,
+                           const CandidateTranslatorMap& translators,
+                           const std::string& content_name,
+                           XmlElements* elems,
+                           WriteError* error) {
+  TransportParser* trans_parser;
+  CandidateTranslator* translator;
+  if (!GetParserAndTranslator(trans_parsers, translators,
+                              transport_type, content_name,
+                              &trans_parser, &translator, error))
+    return false;
+
+  for (size_t i = 0; i < candidates.size(); ++i) {
+    talk_base::scoped_ptr<buzz::XmlElement> element;
+    if (!trans_parser->WriteGingleCandidate(candidates[i], translator,
+                                            element.accept(), error)) {
+      return false;
+    }
+
+    elems->push_back(element.release());
+  }
+
+  return true;
+}
+
+bool WriteGingleTransportInfos(const TransportInfos& tinfos,
+                               const TransportParserMap& trans_parsers,
+                               const CandidateTranslatorMap& translators,
+                               XmlElements* elems,
+                               WriteError* error) {
+  for (TransportInfos::const_iterator tinfo = tinfos.begin();
+       tinfo != tinfos.end(); ++tinfo) {
+    if (!WriteGingleCandidates(tinfo->description.candidates,
+                               trans_parsers, tinfo->description.transport_type,
+                               translators, tinfo->content_name,
+                               elems, error))
+      return false;
+  }
+
+  return true;
+}
+
+bool WriteJingleTransportInfo(const TransportInfo& tinfo,
+                              const TransportParserMap& trans_parsers,
+                              const CandidateTranslatorMap& translators,
+                              XmlElements* elems,
+                              WriteError* error) {
+  std::string transport_type = tinfo.description.transport_type;
+  TransportParser* trans_parser;
+  CandidateTranslator* translator;
+  if (!GetParserAndTranslator(trans_parsers, translators,
+                              transport_type, tinfo.content_name,
+                              &trans_parser, &translator, error))
+    return false;
+
+  buzz::XmlElement* trans_elem;
+  if (!trans_parser->WriteTransportDescription(tinfo.description, translator,
+                                               &trans_elem, error)) {
+    return false;
+  }
+
+  elems->push_back(trans_elem);
+  return true;
+}
+
+void WriteJingleContent(const std::string name,
+                        const XmlElements& child_elems,
+                        XmlElements* elems) {
+  buzz::XmlElement* content_elem = new buzz::XmlElement(QN_JINGLE_CONTENT);
+  content_elem->SetAttr(QN_JINGLE_CONTENT_NAME, name);
+  content_elem->SetAttr(QN_CREATOR, LN_INITIATOR);
+  AddXmlChildren(content_elem, child_elems);
+
+  elems->push_back(content_elem);
+}
+
+bool WriteJingleTransportInfos(const TransportInfos& tinfos,
+                               const TransportParserMap& trans_parsers,
+                               const CandidateTranslatorMap& translators,
+                               XmlElements* elems,
+                               WriteError* error) {
+  for (TransportInfos::const_iterator tinfo = tinfos.begin();
+       tinfo != tinfos.end(); ++tinfo) {
+    XmlElements content_child_elems;
+    if (!WriteJingleTransportInfo(*tinfo, trans_parsers, translators,
+                                  &content_child_elems, error))
+
+      return false;
+
+    WriteJingleContent(tinfo->content_name, content_child_elems, elems);
+  }
+
+  return true;
+}
+
+ContentParser* GetContentParser(const ContentParserMap& content_parsers,
+                                const std::string& type) {
+  ContentParserMap::const_iterator map = content_parsers.find(type);
+  if (map == content_parsers.end()) {
+    return NULL;
+  } else {
+    return map->second;
+  }
+}
+
+bool ParseContentInfo(SignalingProtocol protocol,
+                      const std::string& name,
+                      const std::string& type,
+                      const buzz::XmlElement* elem,
+                      const ContentParserMap& parsers,
+                      ContentInfos* contents,
+                      ParseError* error) {
+  ContentParser* parser = GetContentParser(parsers, type);
+  if (parser == NULL)
+    return BadParse("unknown application content: " + type, error);
+
+  ContentDescription* desc;
+  if (!parser->ParseContent(protocol, elem, &desc, error))
+    return false;
+
+  contents->push_back(ContentInfo(name, type, desc));
+  return true;
+}
+
+bool ParseContentType(const buzz::XmlElement* parent_elem,
+                      std::string* content_type,
+                      const buzz::XmlElement** content_elem,
+                      ParseError* error) {
+  if (!RequireXmlChild(parent_elem, LN_DESCRIPTION, content_elem, error))
+    return false;
+
+  *content_type = (*content_elem)->Name().Namespace();
+  return true;
+}
+
+bool ParseGingleContentInfos(const buzz::XmlElement* session,
+                             const ContentParserMap& content_parsers,
+                             ContentInfos* contents,
+                             ParseError* error) {
+  std::string content_type;
+  const buzz::XmlElement* content_elem;
+  if (!ParseContentType(session, &content_type, &content_elem, error))
+    return false;
+
+  if (content_type == NS_GINGLE_VIDEO) {
+    // A parser parsing audio or video content should look at the
+    // namespace and only parse the codecs relevant to that namespace.
+    // We use this to control which codecs get parsed: first audio,
+    // then video.
+    talk_base::scoped_ptr<buzz::XmlElement> audio_elem(
+        new buzz::XmlElement(QN_GINGLE_AUDIO_CONTENT));
+    CopyXmlChildren(content_elem, audio_elem.get());
+    if (!ParseContentInfo(PROTOCOL_GINGLE, CN_AUDIO, NS_JINGLE_RTP,
+                          audio_elem.get(), content_parsers,
+                          contents, error))
+      return false;
+
+    if (!ParseContentInfo(PROTOCOL_GINGLE, CN_VIDEO, NS_JINGLE_RTP,
+                          content_elem, content_parsers,
+                          contents, error))
+      return false;
+  } else if (content_type == NS_GINGLE_AUDIO) {
+    if (!ParseContentInfo(PROTOCOL_GINGLE, CN_AUDIO, NS_JINGLE_RTP,
+                          content_elem, content_parsers,
+                          contents, error))
+      return false;
+  } else {
+    if (!ParseContentInfo(PROTOCOL_GINGLE, CN_OTHER, content_type,
+                          content_elem, content_parsers,
+                          contents, error))
+      return false;
+  }
+  return true;
+}
+
+bool ParseJingleContentInfos(const buzz::XmlElement* jingle,
+                             const ContentParserMap& content_parsers,
+                             ContentInfos* contents,
+                             ParseError* error) {
+  for (const buzz::XmlElement* pair_elem
+           = jingle->FirstNamed(QN_JINGLE_CONTENT);
+       pair_elem != NULL;
+       pair_elem = pair_elem->NextNamed(QN_JINGLE_CONTENT)) {
+    std::string content_name;
+    if (!RequireXmlAttr(pair_elem, QN_JINGLE_CONTENT_NAME,
+                        &content_name, error))
+      return false;
+
+    std::string content_type;
+    const buzz::XmlElement* content_elem;
+    if (!ParseContentType(pair_elem, &content_type, &content_elem, error))
+      return false;
+
+    if (!ParseContentInfo(PROTOCOL_JINGLE, content_name, content_type,
+                          content_elem, content_parsers,
+                          contents, error))
+      return false;
+  }
+  return true;
+}
+
+bool ParseJingleGroupInfos(const buzz::XmlElement* jingle,
+                           ContentGroups* groups,
+                           ParseError* error) {
+  for (const buzz::XmlElement* pair_elem
+           = jingle->FirstNamed(QN_JINGLE_DRAFT_GROUP);
+       pair_elem != NULL;
+       pair_elem = pair_elem->NextNamed(QN_JINGLE_DRAFT_GROUP)) {
+    std::string group_name;
+    if (!RequireXmlAttr(pair_elem, QN_JINGLE_DRAFT_GROUP_TYPE,
+                        &group_name, error))
+      return false;
+
+    ContentGroup group(group_name);
+    for (const buzz::XmlElement* child_elem
+             = pair_elem->FirstNamed(QN_JINGLE_CONTENT);
+        child_elem != NULL;
+        child_elem = child_elem->NextNamed(QN_JINGLE_CONTENT)) {
+      std::string content_name;
+      if (!RequireXmlAttr(child_elem, QN_JINGLE_CONTENT_NAME,
+                          &content_name, error))
+        return false;
+      group.AddContentName(content_name);
+    }
+    groups->push_back(group);
+  }
+  return true;
+}
+
+buzz::XmlElement* WriteContentInfo(SignalingProtocol protocol,
+                                   const ContentInfo& content,
+                                   const ContentParserMap& parsers,
+                                   WriteError* error) {
+  ContentParser* parser = GetContentParser(parsers, content.type);
+  if (parser == NULL) {
+    BadWrite("unknown content type: " + content.type, error);
+    return NULL;
+  }
+
+  buzz::XmlElement* elem = NULL;
+  if (!parser->WriteContent(protocol, content.description, &elem, error))
+    return NULL;
+
+  return elem;
+}
+
+bool IsWritable(SignalingProtocol protocol,
+                const ContentInfo& content,
+                const ContentParserMap& parsers) {
+  ContentParser* parser = GetContentParser(parsers, content.type);
+  if (parser == NULL) {
+    return false;
+  }
+
+  return parser->IsWritable(protocol, content.description);
+}
+
+bool WriteGingleContentInfos(const ContentInfos& contents,
+                             const ContentParserMap& parsers,
+                             XmlElements* elems,
+                             WriteError* error) {
+  if (contents.size() == 1 ||
+      (contents.size() == 2 &&
+       !IsWritable(PROTOCOL_GINGLE, contents.at(1), parsers))) {
+    if (contents.front().rejected) {
+      return BadWrite("Gingle protocol may not reject individual contents.",
+                      error);
+    }
+    buzz::XmlElement* elem = WriteContentInfo(
+        PROTOCOL_GINGLE, contents.front(), parsers, error);
+    if (!elem)
+      return false;
+
+    elems->push_back(elem);
+  } else if (contents.size() >= 2 &&
+             contents.at(0).type == NS_JINGLE_RTP &&
+             contents.at(1).type == NS_JINGLE_RTP) {
+     // Special-case audio + video contents so that they are "merged"
+     // into one "video" content.
+    if (contents.at(0).rejected || contents.at(1).rejected) {
+      return BadWrite("Gingle protocol may not reject individual contents.",
+                      error);
+    }
+    buzz::XmlElement* audio = WriteContentInfo(
+        PROTOCOL_GINGLE, contents.at(0), parsers, error);
+    if (!audio)
+      return false;
+
+    buzz::XmlElement* video = WriteContentInfo(
+        PROTOCOL_GINGLE, contents.at(1), parsers, error);
+    if (!video) {
+      delete audio;
+      return false;
+    }
+
+    CopyXmlChildren(audio, video);
+    elems->push_back(video);
+    delete audio;
+  } else {
+    return BadWrite("Gingle protocol may only have one content.", error);
+  }
+
+  return true;
+}
+
+const TransportInfo* GetTransportInfoByContentName(
+    const TransportInfos& tinfos, const std::string& content_name) {
+  for (TransportInfos::const_iterator tinfo = tinfos.begin();
+       tinfo != tinfos.end(); ++tinfo) {
+    if (content_name == tinfo->content_name) {
+      return &*tinfo;
+    }
+  }
+  return NULL;
+}
+
+bool WriteJingleContents(const ContentInfos& contents,
+                         const ContentParserMap& content_parsers,
+                         const TransportInfos& tinfos,
+                         const TransportParserMap& trans_parsers,
+                         const CandidateTranslatorMap& translators,
+                         XmlElements* elems,
+                         WriteError* error) {
+  for (ContentInfos::const_iterator content = contents.begin();
+       content != contents.end(); ++content) {
+    if (content->rejected) {
+      continue;
+    }
+    const TransportInfo* tinfo =
+        GetTransportInfoByContentName(tinfos, content->name);
+    if (!tinfo)
+      return BadWrite("No transport for content: " + content->name, error);
+
+    XmlElements pair_elems;
+    buzz::XmlElement* elem = WriteContentInfo(
+        PROTOCOL_JINGLE, *content, content_parsers, error);
+    if (!elem)
+      return false;
+    pair_elems.push_back(elem);
+
+    if (!WriteJingleTransportInfo(*tinfo, trans_parsers, translators,
+                                  &pair_elems, error))
+      return false;
+
+    WriteJingleContent(content->name, pair_elems, elems);
+  }
+  return true;
+}
+
+bool WriteJingleContentInfos(const ContentInfos& contents,
+                             const ContentParserMap& content_parsers,
+                             XmlElements* elems,
+                             WriteError* error) {
+  for (ContentInfos::const_iterator content = contents.begin();
+       content != contents.end(); ++content) {
+    if (content->rejected) {
+      continue;
+    }
+    XmlElements content_child_elems;
+    buzz::XmlElement* elem = WriteContentInfo(
+        PROTOCOL_JINGLE, *content, content_parsers, error);
+    if (!elem)
+      return false;
+    content_child_elems.push_back(elem);
+    WriteJingleContent(content->name, content_child_elems, elems);
+  }
+  return true;
+}
+
+bool WriteJingleGroupInfo(const ContentInfos& contents,
+                          const ContentGroups& groups,
+                          XmlElements* elems,
+                          WriteError* error) {
+  if (!groups.empty()) {
+    buzz::XmlElement* pair_elem = new buzz::XmlElement(QN_JINGLE_DRAFT_GROUP);
+    pair_elem->SetAttr(QN_JINGLE_DRAFT_GROUP_TYPE, GROUP_TYPE_BUNDLE);
+
+    XmlElements pair_elems;
+    for (ContentInfos::const_iterator content = contents.begin();
+         content != contents.end(); ++content) {
+      buzz::XmlElement* child_elem =
+          new buzz::XmlElement(QN_JINGLE_CONTENT, false);
+      child_elem->SetAttr(QN_JINGLE_CONTENT_NAME, content->name);
+      pair_elems.push_back(child_elem);
+    }
+    AddXmlChildren(pair_elem, pair_elems);
+    elems->push_back(pair_elem);
+  }
+  return true;
+}
+
+bool ParseContentType(SignalingProtocol protocol,
+                      const buzz::XmlElement* action_elem,
+                      std::string* content_type,
+                      ParseError* error) {
+  const buzz::XmlElement* content_elem;
+  if (protocol == PROTOCOL_GINGLE) {
+    if (!ParseContentType(action_elem, content_type, &content_elem, error))
+      return false;
+
+    // Internally, we only use NS_JINGLE_RTP.
+    if (*content_type == NS_GINGLE_AUDIO ||
+        *content_type == NS_GINGLE_VIDEO)
+      *content_type = NS_JINGLE_RTP;
+  } else {
+    const buzz::XmlElement* pair_elem
+        = action_elem->FirstNamed(QN_JINGLE_CONTENT);
+    if (pair_elem == NULL)
+      return BadParse("No contents found", error);
+
+    if (!ParseContentType(pair_elem, content_type, &content_elem, error))
+      return false;
+  }
+
+  return true;
+}
+
+static bool ParseContentMessage(
+    SignalingProtocol protocol,
+    const buzz::XmlElement* action_elem,
+    bool expect_transports,
+    const ContentParserMap& content_parsers,
+    const TransportParserMap& trans_parsers,
+    const CandidateTranslatorMap& translators,
+    SessionInitiate* init,
+    ParseError* error) {
+  init->owns_contents = true;
+  if (protocol == PROTOCOL_GINGLE) {
+    if (!ParseGingleContentInfos(action_elem, content_parsers,
+                                 &init->contents, error))
+      return false;
+
+    if (expect_transports &&
+        !ParseGingleTransportInfos(action_elem, init->contents,
+                                   trans_parsers, translators,
+                                   &init->transports, error))
+      return false;
+  } else {
+    if (!ParseJingleContentInfos(action_elem, content_parsers,
+                                 &init->contents, error))
+      return false;
+    if (!ParseJingleGroupInfos(action_elem, &init->groups, error))
+      return false;
+
+    if (expect_transports &&
+        !ParseJingleTransportInfos(action_elem, init->contents,
+                                   trans_parsers, translators,
+                                   &init->transports, error))
+      return false;
+  }
+
+  return true;
+}
+
+static bool WriteContentMessage(
+    SignalingProtocol protocol,
+    const ContentInfos& contents,
+    const TransportInfos& tinfos,
+    const ContentParserMap& content_parsers,
+    const TransportParserMap& transport_parsers,
+    const CandidateTranslatorMap& translators,
+    const ContentGroups& groups,
+    XmlElements* elems,
+    WriteError* error) {
+  if (protocol == PROTOCOL_GINGLE) {
+    if (!WriteGingleContentInfos(contents, content_parsers, elems, error))
+      return false;
+
+    if (!WriteGingleTransportInfos(tinfos, transport_parsers, translators,
+                                   elems, error))
+      return false;
+  } else {
+    if (!WriteJingleContents(contents, content_parsers,
+                             tinfos, transport_parsers, translators,
+                             elems, error))
+      return false;
+    if (!WriteJingleGroupInfo(contents, groups, elems, error))
+      return false;
+  }
+
+  return true;
+}
+
+bool ParseSessionInitiate(SignalingProtocol protocol,
+                          const buzz::XmlElement* action_elem,
+                          const ContentParserMap& content_parsers,
+                          const TransportParserMap& trans_parsers,
+                          const CandidateTranslatorMap& translators,
+                          SessionInitiate* init,
+                          ParseError* error) {
+  bool expect_transports = true;
+  return ParseContentMessage(protocol, action_elem, expect_transports,
+                             content_parsers, trans_parsers, translators,
+                             init, error);
+}
+
+
+bool WriteSessionInitiate(SignalingProtocol protocol,
+                          const ContentInfos& contents,
+                          const TransportInfos& tinfos,
+                          const ContentParserMap& content_parsers,
+                          const TransportParserMap& transport_parsers,
+                          const CandidateTranslatorMap& translators,
+                          const ContentGroups& groups,
+                          XmlElements* elems,
+                          WriteError* error) {
+  return WriteContentMessage(protocol, contents, tinfos,
+                             content_parsers, transport_parsers, translators,
+                             groups,
+                             elems, error);
+}
+
+bool ParseSessionAccept(SignalingProtocol protocol,
+                        const buzz::XmlElement* action_elem,
+                        const ContentParserMap& content_parsers,
+                        const TransportParserMap& transport_parsers,
+                        const CandidateTranslatorMap& translators,
+                        SessionAccept* accept,
+                        ParseError* error) {
+  bool expect_transports = true;
+  return ParseContentMessage(protocol, action_elem, expect_transports,
+                             content_parsers, transport_parsers, translators,
+                             accept, error);
+}
+
+bool WriteSessionAccept(SignalingProtocol protocol,
+                        const ContentInfos& contents,
+                        const TransportInfos& tinfos,
+                        const ContentParserMap& content_parsers,
+                        const TransportParserMap& transport_parsers,
+                        const CandidateTranslatorMap& translators,
+                        const ContentGroups& groups,
+                        XmlElements* elems,
+                        WriteError* error) {
+  return WriteContentMessage(protocol, contents, tinfos,
+                             content_parsers, transport_parsers, translators,
+                             groups,
+                             elems, error);
+}
+
+bool ParseSessionTerminate(SignalingProtocol protocol,
+                           const buzz::XmlElement* action_elem,
+                           SessionTerminate* term,
+                           ParseError* error) {
+  if (protocol == PROTOCOL_GINGLE) {
+    const buzz::XmlElement* reason_elem = action_elem->FirstElement();
+    if (reason_elem != NULL) {
+      term->reason = reason_elem->Name().LocalPart();
+      const buzz::XmlElement *debug_elem = reason_elem->FirstElement();
+      if (debug_elem != NULL) {
+        term->debug_reason = debug_elem->Name().LocalPart();
+      }
+    }
+    return true;
+  } else {
+    const buzz::XmlElement* reason_elem =
+        action_elem->FirstNamed(QN_JINGLE_REASON);
+    if (reason_elem) {
+      reason_elem = reason_elem->FirstElement();
+      if (reason_elem) {
+        term->reason = reason_elem->Name().LocalPart();
+      }
+    }
+    return true;
+  }
+}
+
+void WriteSessionTerminate(SignalingProtocol protocol,
+                           const SessionTerminate& term,
+                           XmlElements* elems) {
+  if (protocol == PROTOCOL_GINGLE) {
+    elems->push_back(new buzz::XmlElement(buzz::QName(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(NS_JINGLE, term.reason)));
+      elems->push_back(reason_elem);
+    }
+  }
+}
+
+bool ParseDescriptionInfo(SignalingProtocol protocol,
+                          const buzz::XmlElement* action_elem,
+                          const ContentParserMap& content_parsers,
+                          const TransportParserMap& transport_parsers,
+                          const CandidateTranslatorMap& translators,
+                          DescriptionInfo* description_info,
+                          ParseError* error) {
+  bool expect_transports = false;
+  return ParseContentMessage(protocol, action_elem, expect_transports,
+                             content_parsers, transport_parsers, translators,
+                             description_info, error);
+}
+
+bool WriteDescriptionInfo(SignalingProtocol protocol,
+                          const ContentInfos& contents,
+                          const ContentParserMap& content_parsers,
+                          XmlElements* elems,
+                          WriteError* error) {
+  if (protocol == PROTOCOL_GINGLE) {
+    return WriteGingleContentInfos(contents, content_parsers, elems, error);
+  } else {
+    return WriteJingleContentInfos(contents, content_parsers, elems, error);
+  }
+}
+
+bool ParseTransportInfos(SignalingProtocol protocol,
+                         const buzz::XmlElement* action_elem,
+                         const ContentInfos& contents,
+                         const TransportParserMap& trans_parsers,
+                         const CandidateTranslatorMap& translators,
+                         TransportInfos* tinfos,
+                         ParseError* error) {
+  if (protocol == PROTOCOL_GINGLE) {
+    return ParseGingleTransportInfos(
+        action_elem, contents, trans_parsers, translators, tinfos, error);
+  } else {
+    return ParseJingleTransportInfos(
+        action_elem, contents, trans_parsers, translators, tinfos, error);
+  }
+}
+
+bool WriteTransportInfos(SignalingProtocol protocol,
+                         const TransportInfos& tinfos,
+                         const TransportParserMap& trans_parsers,
+                         const CandidateTranslatorMap& translators,
+                         XmlElements* elems,
+                         WriteError* error) {
+  if (protocol == PROTOCOL_GINGLE) {
+    return WriteGingleTransportInfos(tinfos, trans_parsers, translators,
+                                     elems, error);
+  } else {
+    return WriteJingleTransportInfos(tinfos, trans_parsers, translators,
+                                     elems, error);
+  }
+}
+
+bool GetUriTarget(const std::string& prefix, const std::string& str,
+                  std::string* after) {
+  size_t pos = str.find(prefix);
+  if (pos == std::string::npos)
+    return false;
+
+  *after = str.substr(pos + prefix.size(), std::string::npos);
+  return true;
+}
+
+bool FindSessionRedirect(const buzz::XmlElement* stanza,
+                         SessionRedirect* redirect) {
+  const buzz::XmlElement* error_elem = GetXmlChild(stanza, LN_ERROR);
+  if (error_elem == NULL)
+    return false;
+
+  const buzz::XmlElement* redirect_elem =
+      error_elem->FirstNamed(QN_GINGLE_REDIRECT);
+  if (redirect_elem == NULL)
+    redirect_elem = error_elem->FirstNamed(buzz::QN_STANZA_REDIRECT);
+  if (redirect_elem == NULL)
+    return false;
+
+  if (!GetUriTarget(STR_REDIRECT_PREFIX, redirect_elem->BodyText(),
+                    &redirect->target))
+    return false;
+
+  return true;
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/sessionmessages.h b/talk/p2p/base/sessionmessages.h
new file mode 100644
index 0000000..5cd565c
--- /dev/null
+++ b/talk/p2p/base/sessionmessages.h
@@ -0,0 +1,243 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_SESSIONMESSAGES_H_
+#define TALK_P2P_BASE_SESSIONMESSAGES_H_
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include "talk/base/basictypes.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/parsing.h"
+#include "talk/p2p/base/sessiondescription.h"  // Needed to delete contents.
+#include "talk/p2p/base/transportinfo.h"
+#include "talk/xmllite/xmlelement.h"
+
+namespace cricket {
+
+struct ParseError;
+struct WriteError;
+class Candidate;
+class ContentParser;
+class TransportParser;
+
+typedef std::vector<Candidate> Candidates;
+typedef std::map<std::string, ContentParser*> ContentParserMap;
+typedef std::map<std::string, TransportParser*> TransportParserMap;
+
+enum ActionType {
+  ACTION_UNKNOWN,
+
+  ACTION_SESSION_INITIATE,
+  ACTION_SESSION_INFO,
+  ACTION_SESSION_ACCEPT,
+  ACTION_SESSION_REJECT,
+  ACTION_SESSION_TERMINATE,
+
+  ACTION_TRANSPORT_INFO,
+  ACTION_TRANSPORT_ACCEPT,
+
+  ACTION_DESCRIPTION_INFO,
+};
+
+// Abstraction of a <jingle> element within an <iq> stanza, per XMPP
+// standard XEP-166.  Can be serialized into multiple protocols,
+// including the standard (Jingle) and the draft standard (Gingle).
+// In general, used to communicate actions related to a p2p session,
+// such accept, initiate, terminate, etc.
+
+struct SessionMessage {
+  SessionMessage() : action_elem(NULL), stanza(NULL) {}
+
+  SessionMessage(SignalingProtocol protocol, ActionType type,
+                 const std::string& sid, const std::string& initiator) :
+      protocol(protocol), type(type), sid(sid), initiator(initiator),
+      action_elem(NULL), stanza(NULL) {}
+
+  std::string id;
+  std::string from;
+  std::string to;
+  SignalingProtocol protocol;
+  ActionType type;
+  std::string sid;  // session id
+  std::string initiator;
+
+  // Used for further parsing when necessary.
+  // Represents <session> or <jingle>.
+  const buzz::XmlElement* action_elem;
+  // Mostly used for debugging.
+  const buzz::XmlElement* stanza;
+};
+
+// TODO: Break up this class so we don't have to typedef it into
+// different classes.
+struct ContentMessage {
+  ContentMessage() : owns_contents(false) {}
+
+  ~ContentMessage() {
+    if (owns_contents) {
+      for (ContentInfos::iterator content = contents.begin();
+           content != contents.end(); content++) {
+        delete content->description;
+      }
+    }
+  }
+
+  // Caller takes ownership of contents.
+  ContentInfos ClearContents() {
+    ContentInfos out;
+    contents.swap(out);
+    owns_contents = false;
+    return out;
+  }
+
+  bool owns_contents;
+  ContentInfos contents;
+  TransportInfos transports;
+  ContentGroups groups;
+};
+
+typedef ContentMessage SessionInitiate;
+typedef ContentMessage SessionAccept;
+// Note that a DescriptionInfo does not have TransportInfos.
+typedef ContentMessage DescriptionInfo;
+
+struct SessionTerminate {
+  SessionTerminate() {}
+
+  explicit SessionTerminate(const std::string& reason) :
+      reason(reason) {}
+
+  std::string reason;
+  std::string debug_reason;
+};
+
+struct SessionRedirect {
+  std::string target;
+};
+
+// Used during parsing and writing to map component to channel name
+// and back.  This is primarily for converting old G-ICE candidate
+// signalling to new ICE candidate classes.
+class CandidateTranslator {
+ public:
+  virtual bool GetChannelNameFromComponent(
+      int component, std::string* channel_name) const = 0;
+  virtual bool GetComponentFromChannelName(
+      const std::string& channel_name, int* component) const = 0;
+};
+
+// Content name => translator
+typedef std::map<std::string, CandidateTranslator*> CandidateTranslatorMap;
+
+bool IsSessionMessage(const buzz::XmlElement* stanza);
+bool ParseSessionMessage(const buzz::XmlElement* stanza,
+                         SessionMessage* msg,
+                         ParseError* error);
+// Will return an error if there is more than one content type.
+bool ParseContentType(SignalingProtocol protocol,
+                      const buzz::XmlElement* action_elem,
+                      std::string* content_type,
+                      ParseError* error);
+void WriteSessionMessage(const SessionMessage& msg,
+                         const XmlElements& action_elems,
+                         buzz::XmlElement* stanza);
+bool ParseSessionInitiate(SignalingProtocol protocol,
+                          const buzz::XmlElement* action_elem,
+                          const ContentParserMap& content_parsers,
+                          const TransportParserMap& transport_parsers,
+                          const CandidateTranslatorMap& translators,
+                          SessionInitiate* init,
+                          ParseError* error);
+bool WriteSessionInitiate(SignalingProtocol protocol,
+                          const ContentInfos& contents,
+                          const TransportInfos& tinfos,
+                          const ContentParserMap& content_parsers,
+                          const TransportParserMap& transport_parsers,
+                          const CandidateTranslatorMap& translators,
+                          const ContentGroups& groups,
+                          XmlElements* elems,
+                          WriteError* error);
+bool ParseSessionAccept(SignalingProtocol protocol,
+                        const buzz::XmlElement* action_elem,
+                        const ContentParserMap& content_parsers,
+                        const TransportParserMap& transport_parsers,
+                        const CandidateTranslatorMap& translators,
+                        SessionAccept* accept,
+                        ParseError* error);
+bool WriteSessionAccept(SignalingProtocol protocol,
+                        const ContentInfos& contents,
+                        const TransportInfos& tinfos,
+                        const ContentParserMap& content_parsers,
+                        const TransportParserMap& transport_parsers,
+                        const CandidateTranslatorMap& translators,
+                        const ContentGroups& groups,
+                        XmlElements* elems,
+                        WriteError* error);
+bool ParseSessionTerminate(SignalingProtocol protocol,
+                           const buzz::XmlElement* action_elem,
+                           SessionTerminate* term,
+                           ParseError* error);
+void WriteSessionTerminate(SignalingProtocol protocol,
+                           const SessionTerminate& term,
+                           XmlElements* elems);
+bool ParseDescriptionInfo(SignalingProtocol protocol,
+                          const buzz::XmlElement* action_elem,
+                          const ContentParserMap& content_parsers,
+                          const TransportParserMap& transport_parsers,
+                          const CandidateTranslatorMap& translators,
+                          DescriptionInfo* description_info,
+                          ParseError* error);
+bool WriteDescriptionInfo(SignalingProtocol protocol,
+                          const ContentInfos& contents,
+                          const ContentParserMap& content_parsers,
+                          XmlElements* elems,
+                          WriteError* error);
+// Since a TransportInfo is not a transport-info message, and a
+// transport-info message is just a collection of TransportInfos, we
+// say Parse/Write TransportInfos for transport-info messages.
+bool ParseTransportInfos(SignalingProtocol protocol,
+                         const buzz::XmlElement* action_elem,
+                         const ContentInfos& contents,
+                         const TransportParserMap& trans_parsers,
+                         const CandidateTranslatorMap& translators,
+                         TransportInfos* tinfos,
+                         ParseError* error);
+bool WriteTransportInfos(SignalingProtocol protocol,
+                         const TransportInfos& tinfos,
+                         const TransportParserMap& trans_parsers,
+                         const CandidateTranslatorMap& translators,
+                         XmlElements* elems,
+                         WriteError* error);
+// Handles both Gingle and Jingle syntax.
+bool FindSessionRedirect(const buzz::XmlElement* stanza,
+                         SessionRedirect* redirect);
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_SESSIONMESSAGES_H_
diff --git a/talk/p2p/base/stun.cc b/talk/p2p/base/stun.cc
new file mode 100644
index 0000000..06a71a1
--- /dev/null
+++ b/talk/p2p/base/stun.cc
@@ -0,0 +1,928 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/p2p/base/stun.h"
+
+#include <cstring>
+
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/crc32.h"
+#include "talk/base/logging.h"
+#include "talk/base/messagedigest.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stringencode.h"
+
+using talk_base::ByteBuffer;
+
+namespace cricket {
+
+const char STUN_ERROR_REASON_BAD_REQUEST[] = "Bad Request";
+const char STUN_ERROR_REASON_UNAUTHORIZED[] = "Unauthorized";
+const char STUN_ERROR_REASON_FORBIDDEN[] = "Forbidden";
+const char STUN_ERROR_REASON_STALE_CREDENTIALS[] = "Stale Credentials";
+const char STUN_ERROR_REASON_ALLOCATION_MISMATCH[] = "Allocation Mismatch";
+const char STUN_ERROR_REASON_STALE_NONCE[] = "Stale Nonce";
+const char STUN_ERROR_REASON_WRONG_CREDENTIALS[] = "Wrong Credentials";
+const char STUN_ERROR_REASON_UNSUPPORTED_PROTOCOL[] = "Unsupported Protocol";
+const char STUN_ERROR_REASON_ROLE_CONFLICT[] = "Role Conflict";
+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";
+const uint32 STUN_FINGERPRINT_XOR_VALUE = 0x5354554E;
+
+// StunMessage
+
+StunMessage::StunMessage()
+    : type_(0),
+      length_(0),
+      transaction_id_(EMPTY_TRANSACTION_ID) {
+  ASSERT(IsValidTransactionId(transaction_id_));
+  attrs_ = new std::vector<StunAttribute*>();
+}
+
+StunMessage::~StunMessage() {
+  for (size_t i = 0; i < attrs_->size(); i++)
+    delete (*attrs_)[i];
+  delete attrs_;
+}
+
+bool StunMessage::IsLegacy() const {
+  if (transaction_id_.size() == kStunLegacyTransactionIdLength)
+    return true;
+  ASSERT(transaction_id_.size() == kStunTransactionIdLength);
+  return false;
+}
+
+bool StunMessage::SetTransactionID(const std::string& str) {
+  if (!IsValidTransactionId(str)) {
+    return false;
+  }
+  transaction_id_ = str;
+  return true;
+}
+
+bool StunMessage::AddAttribute(StunAttribute* attr) {
+  // Fail any attributes that aren't valid for this type of message.
+  if (attr->value_type() != GetAttributeValueType(attr->type())) {
+    return false;
+  }
+  attrs_->push_back(attr);
+  attr->SetOwner(this);
+  size_t attr_length = attr->length();
+  if (attr_length % 4 != 0) {
+    attr_length += (4 - (attr_length % 4));
+  }
+  length_ += attr_length + 4;
+  return true;
+}
+
+const StunAddressAttribute* StunMessage::GetAddress(int type) const {
+  switch (type) {
+    case STUN_ATTR_MAPPED_ADDRESS: {
+      // Return XOR-MAPPED-ADDRESS when MAPPED-ADDRESS attribute is
+      // missing.
+      const StunAttribute* mapped_address =
+          GetAttribute(STUN_ATTR_MAPPED_ADDRESS);
+      if (!mapped_address)
+        mapped_address = GetAttribute(STUN_ATTR_XOR_MAPPED_ADDRESS);
+      return reinterpret_cast<const StunAddressAttribute*>(mapped_address);
+    }
+
+    default:
+      return static_cast<const StunAddressAttribute*>(GetAttribute(type));
+  }
+}
+
+const StunUInt32Attribute* StunMessage::GetUInt32(int type) const {
+  return static_cast<const StunUInt32Attribute*>(GetAttribute(type));
+}
+
+const StunUInt64Attribute* StunMessage::GetUInt64(int type) const {
+  return static_cast<const StunUInt64Attribute*>(GetAttribute(type));
+}
+
+const StunByteStringAttribute* StunMessage::GetByteString(int type) const {
+  return static_cast<const StunByteStringAttribute*>(GetAttribute(type));
+}
+
+const StunErrorCodeAttribute* StunMessage::GetErrorCode() const {
+  return static_cast<const StunErrorCodeAttribute*>(
+      GetAttribute(STUN_ATTR_ERROR_CODE));
+}
+
+const StunUInt16ListAttribute* StunMessage::GetUnknownAttributes() const {
+  return static_cast<const StunUInt16ListAttribute*>(
+      GetAttribute(STUN_ATTR_UNKNOWN_ATTRIBUTES));
+}
+
+// Verifies a STUN message has a valid MESSAGE-INTEGRITY attribute, using the
+// procedure outlined in RFC 5389, section 15.4.
+bool StunMessage::ValidateMessageIntegrity(const char* data, size_t size,
+                                           const std::string& password) {
+  // Verifying the size of the message.
+  if ((size % 4) != 0) {
+    return false;
+  }
+
+  // Getting the message length from the STUN header.
+  uint16 msg_length = talk_base::GetBE16(&data[2]);
+  if (size != (msg_length + kStunHeaderSize)) {
+    return false;
+  }
+
+  // Finding Message Integrity attribute in stun message.
+  size_t current_pos = kStunHeaderSize;
+  bool has_message_integrity_attr = false;
+  while (current_pos < size) {
+    uint16 attr_type, attr_length;
+    // Getting attribute type and length.
+    attr_type = talk_base::GetBE16(&data[current_pos]);
+    attr_length = talk_base::GetBE16(&data[current_pos + sizeof(attr_type)]);
+
+    // If M-I, sanity check it, and break out.
+    if (attr_type == STUN_ATTR_MESSAGE_INTEGRITY) {
+      if (attr_length != kStunMessageIntegritySize ||
+          current_pos + attr_length > size) {
+        return false;
+      }
+      has_message_integrity_attr = true;
+      break;
+    }
+
+    // Otherwise, skip to the next attribute.
+    current_pos += sizeof(attr_type) + sizeof(attr_length) + attr_length;
+    if ((attr_length % 4) != 0) {
+      current_pos += (4 - (attr_length % 4));
+    }
+  }
+
+  if (!has_message_integrity_attr) {
+    return false;
+  }
+
+  // Getting length of the message to calculate Message Integrity.
+  size_t mi_pos = current_pos;
+  talk_base::scoped_array<char> temp_data(new char[current_pos]);
+  memcpy(temp_data.get(), data, current_pos);
+  if (size > mi_pos + kStunAttributeHeaderSize + kStunMessageIntegritySize) {
+    // Stun message has other attributes after message integrity.
+    // Adjust the length parameter in stun message to calculate HMAC.
+    size_t extra_offset = size -
+        (mi_pos + kStunAttributeHeaderSize + kStunMessageIntegritySize);
+    size_t new_adjusted_len = size - extra_offset - kStunHeaderSize;
+
+    // Writing new length of the STUN message @ Message Length in temp buffer.
+    //      0                   1                   2                   3
+    //      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+    //     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+    //     |0 0|     STUN Message Type     |         Message Length        |
+    //     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+    talk_base::SetBE16(temp_data.get() + 2, new_adjusted_len);
+  }
+
+  char hmac[kStunMessageIntegritySize];
+  size_t ret = talk_base::ComputeHmac(talk_base::DIGEST_SHA_1,
+                                      password.c_str(), password.size(),
+                                      temp_data.get(), mi_pos,
+                                      hmac, sizeof(hmac));
+  ASSERT(ret == sizeof(hmac));
+  if (ret != sizeof(hmac))
+    return false;
+
+  // Comparing the calculated HMAC with the one present in the message.
+  return (std::memcmp(data + current_pos + kStunAttributeHeaderSize,
+                      hmac, sizeof(hmac)) == 0);
+}
+
+bool StunMessage::AddMessageIntegrity(const std::string& password) {
+  return AddMessageIntegrity(password.c_str(), password.size());
+}
+
+bool StunMessage::AddMessageIntegrity(const char* key,
+                                      size_t keylen) {
+  // Add the attribute with a dummy value. Since this is a known attribute, it
+  // can't fail.
+  StunByteStringAttribute* msg_integrity_attr =
+      new StunByteStringAttribute(STUN_ATTR_MESSAGE_INTEGRITY,
+          std::string(kStunMessageIntegritySize, '0'));
+  VERIFY(AddAttribute(msg_integrity_attr));
+
+  // Calculate the HMAC for the message.
+  talk_base::ByteBuffer buf;
+  if (!Write(&buf))
+    return false;
+
+  int msg_len_for_hmac = buf.Length() -
+      kStunAttributeHeaderSize - msg_integrity_attr->length();
+  char hmac[kStunMessageIntegritySize];
+  size_t ret = talk_base::ComputeHmac(talk_base::DIGEST_SHA_1,
+                                      key, keylen,
+                                      buf.Data(), msg_len_for_hmac,
+                                      hmac, sizeof(hmac));
+  ASSERT(ret == sizeof(hmac));
+  if (ret != sizeof(hmac)) {
+    LOG(LS_ERROR) << "HMAC computation failed. Message-Integrity "
+                  << "has dummy value.";
+    return false;
+  }
+
+  // Insert correct HMAC into the attribute.
+  msg_integrity_attr->CopyBytes(hmac, sizeof(hmac));
+  return true;
+}
+
+// Verifies a message is in fact a STUN message, by performing the checks
+// outlined in RFC 5389, section 7.3, including the FINGERPRINT check detailed
+// in section 15.5.
+bool StunMessage::ValidateFingerprint(const char* data, size_t size) {
+  // Check the message length.
+  size_t fingerprint_attr_size =
+      kStunAttributeHeaderSize + StunUInt32Attribute::SIZE;
+  if (size % 4 != 0 || size < kStunHeaderSize + fingerprint_attr_size)
+    return false;
+
+  // Skip the rest if the magic cookie isn't present.
+  const char* magic_cookie =
+      data + kStunTransactionIdOffset - kStunMagicCookieLength;
+  if (talk_base::GetBE32(magic_cookie) != kStunMagicCookie)
+    return false;
+
+  // Check the fingerprint type and length.
+  const char* fingerprint_attr_data = data + size - fingerprint_attr_size;
+  if (talk_base::GetBE16(fingerprint_attr_data) != STUN_ATTR_FINGERPRINT ||
+      talk_base::GetBE16(fingerprint_attr_data + sizeof(uint16)) !=
+          StunUInt32Attribute::SIZE)
+    return false;
+
+  // Check the fingerprint value.
+  uint32 fingerprint =
+      talk_base::GetBE32(fingerprint_attr_data + kStunAttributeHeaderSize);
+  return ((fingerprint ^ STUN_FINGERPRINT_XOR_VALUE) ==
+      talk_base::ComputeCrc32(data, size - fingerprint_attr_size));
+}
+
+bool StunMessage::AddFingerprint() {
+  // Add the attribute with a dummy value. Since this is a known attribute,
+  // it can't fail.
+  StunUInt32Attribute* fingerprint_attr =
+     new StunUInt32Attribute(STUN_ATTR_FINGERPRINT, 0);
+  VERIFY(AddAttribute(fingerprint_attr));
+
+  // Calculate the CRC-32 for the message and insert it.
+  talk_base::ByteBuffer buf;
+  if (!Write(&buf))
+    return false;
+
+  int msg_len_for_crc32 = buf.Length() -
+      kStunAttributeHeaderSize - fingerprint_attr->length();
+  uint32 c = talk_base::ComputeCrc32(buf.Data(), msg_len_for_crc32);
+
+  // Insert the correct CRC-32, XORed with a constant, into the attribute.
+  fingerprint_attr->SetValue(c ^ STUN_FINGERPRINT_XOR_VALUE);
+  return true;
+}
+
+bool StunMessage::Read(ByteBuffer* buf) {
+  if (!buf->ReadUInt16(&type_))
+    return false;
+
+  if (type_ & 0x8000) {
+    // RTP and RTCP set the MSB of first byte, since first two bits are version,
+    // and version is always 2 (10). If set, this is not a STUN packet.
+    return false;
+  }
+
+  if (!buf->ReadUInt16(&length_))
+    return false;
+
+  std::string magic_cookie;
+  if (!buf->ReadString(&magic_cookie, kStunMagicCookieLength))
+    return false;
+
+  std::string transaction_id;
+  if (!buf->ReadString(&transaction_id, kStunTransactionIdLength))
+    return false;
+
+  uint32 magic_cookie_int =
+      *reinterpret_cast<const uint32*>(magic_cookie.data());
+  if (talk_base::NetworkToHost32(magic_cookie_int) != kStunMagicCookie) {
+    // If magic cookie is invalid it means that the peer implements
+    // RFC3489 instead of RFC5389.
+    transaction_id.insert(0, magic_cookie);
+  }
+  ASSERT(IsValidTransactionId(transaction_id));
+  transaction_id_ = transaction_id;
+
+  if (length_ != buf->Length())
+    return false;
+
+  attrs_->resize(0);
+
+  size_t rest = buf->Length() - length_;
+  while (buf->Length() > rest) {
+    uint16 attr_type, attr_length;
+    if (!buf->ReadUInt16(&attr_type))
+      return false;
+    if (!buf->ReadUInt16(&attr_length))
+      return false;
+
+    StunAttribute* attr = CreateAttribute(attr_type, attr_length);
+    if (!attr) {
+      // Skip any unknown or malformed attributes.
+      if ((attr_length % 4) != 0) {
+        attr_length += (4 - (attr_length % 4));
+      }
+      if (!buf->Consume(attr_length))
+        return false;
+    } else {
+      if (!attr->Read(buf))
+        return false;
+      attrs_->push_back(attr);
+    }
+  }
+
+  ASSERT(buf->Length() == rest);
+  return true;
+}
+
+bool StunMessage::Write(ByteBuffer* buf) const {
+  buf->WriteUInt16(type_);
+  buf->WriteUInt16(length_);
+  if (!IsLegacy())
+    buf->WriteUInt32(kStunMagicCookie);
+  buf->WriteString(transaction_id_);
+
+  for (size_t i = 0; i < attrs_->size(); ++i) {
+    buf->WriteUInt16((*attrs_)[i]->type());
+    buf->WriteUInt16((*attrs_)[i]->length());
+    if (!(*attrs_)[i]->Write(buf))
+      return false;
+  }
+
+  return true;
+}
+
+StunAttributeValueType StunMessage::GetAttributeValueType(int type) const {
+  switch (type) {
+    case STUN_ATTR_MAPPED_ADDRESS:      return STUN_VALUE_ADDRESS;
+    case STUN_ATTR_USERNAME:            return STUN_VALUE_BYTE_STRING;
+    case STUN_ATTR_MESSAGE_INTEGRITY:   return STUN_VALUE_BYTE_STRING;
+    case STUN_ATTR_ERROR_CODE:          return STUN_VALUE_ERROR_CODE;
+    case STUN_ATTR_UNKNOWN_ATTRIBUTES:  return STUN_VALUE_UINT16_LIST;
+    case STUN_ATTR_REALM:               return STUN_VALUE_BYTE_STRING;
+    case STUN_ATTR_NONCE:               return STUN_VALUE_BYTE_STRING;
+    case STUN_ATTR_XOR_MAPPED_ADDRESS:  return STUN_VALUE_XOR_ADDRESS;
+    case STUN_ATTR_SOFTWARE:            return STUN_VALUE_BYTE_STRING;
+    case STUN_ATTR_ALTERNATE_SERVER:    return STUN_VALUE_BYTE_STRING;
+    case STUN_ATTR_FINGERPRINT:         return STUN_VALUE_UINT32;
+    case STUN_ATTR_RETRANSMIT_COUNT:    return STUN_VALUE_UINT32;
+    default:                            return STUN_VALUE_UNKNOWN;
+  }
+}
+
+StunAttribute* StunMessage::CreateAttribute(int type, size_t length) /*const*/ {
+  StunAttributeValueType value_type = GetAttributeValueType(type);
+  return StunAttribute::Create(value_type, type, length, this);
+}
+
+const StunAttribute* StunMessage::GetAttribute(int type) const {
+  for (size_t i = 0; i < attrs_->size(); ++i) {
+    if ((*attrs_)[i]->type() == type)
+      return (*attrs_)[i];
+  }
+  return NULL;
+}
+
+bool StunMessage::IsValidTransactionId(const std::string& transaction_id) {
+  return transaction_id.size() == kStunTransactionIdLength ||
+      transaction_id.size() == kStunLegacyTransactionIdLength;
+}
+
+// StunAttribute
+
+StunAttribute::StunAttribute(uint16 type, uint16 length)
+    : type_(type), length_(length) {
+}
+
+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);
+  }
+}
+
+StunAttribute* StunAttribute::Create(StunAttributeValueType value_type,
+                                     uint16 type, uint16 length,
+                                     StunMessage* owner) {
+  switch (value_type) {
+    case STUN_VALUE_ADDRESS:
+      return new StunAddressAttribute(type, length);
+    case STUN_VALUE_XOR_ADDRESS:
+      return new StunXorAddressAttribute(type, length, owner);
+    case STUN_VALUE_UINT32:
+      return new StunUInt32Attribute(type);
+    case STUN_VALUE_UINT64:
+      return new StunUInt64Attribute(type);
+    case STUN_VALUE_BYTE_STRING:
+      return new StunByteStringAttribute(type, length);
+    case STUN_VALUE_ERROR_CODE:
+      return new StunErrorCodeAttribute(type, length);
+    case STUN_VALUE_UINT16_LIST:
+      return new StunUInt16ListAttribute(type, length);
+    default:
+      return NULL;
+  }
+}
+
+StunAddressAttribute* StunAttribute::CreateAddress(uint16 type) {
+  return new StunAddressAttribute(type, 0);
+}
+
+StunXorAddressAttribute* StunAttribute::CreateXorAddress(uint16 type) {
+  return new StunXorAddressAttribute(type, 0, NULL);
+}
+
+StunUInt64Attribute* StunAttribute::CreateUInt64(uint16 type) {
+  return new StunUInt64Attribute(type);
+}
+
+StunUInt32Attribute* StunAttribute::CreateUInt32(uint16 type) {
+  return new StunUInt32Attribute(type);
+}
+
+StunByteStringAttribute* StunAttribute::CreateByteString(uint16 type) {
+  return new StunByteStringAttribute(type, 0);
+}
+
+StunErrorCodeAttribute* StunAttribute::CreateErrorCode() {
+  return new StunErrorCodeAttribute(
+      STUN_ATTR_ERROR_CODE, StunErrorCodeAttribute::MIN_SIZE);
+}
+
+StunUInt16ListAttribute* StunAttribute::CreateUnknownAttributes() {
+  return new StunUInt16ListAttribute(STUN_ATTR_UNKNOWN_ATTRIBUTES, 0);
+}
+
+StunAddressAttribute::StunAddressAttribute(uint16 type,
+   const talk_base::SocketAddress& addr)
+   : StunAttribute(type, 0) {
+  SetAddress(addr);
+}
+
+StunAddressAttribute::StunAddressAttribute(uint16 type, uint16 length)
+    : StunAttribute(type, length) {
+}
+
+bool StunAddressAttribute::Read(ByteBuffer* buf) {
+  uint8 dummy;
+  if (!buf->ReadUInt8(&dummy))
+    return false;
+
+  uint8 stun_family;
+  if (!buf->ReadUInt8(&stun_family)) {
+    return false;
+  }
+  uint16 port;
+  if (!buf->ReadUInt16(&port))
+    return false;
+  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;
+}
+
+bool StunAddressAttribute::Write(ByteBuffer* buf) const {
+  StunAddressFamily address_family = family();
+  if (address_family == STUN_ADDRESS_UNDEF) {
+    LOG(LS_ERROR) << "Error writing address attribute: unknown family.";
+    return false;
+  }
+  buf->WriteUInt8(0);
+  buf->WriteUInt8(address_family);
+  buf->WriteUInt16(address_.port());
+  switch (address_.family()) {
+    case AF_INET: {
+      in_addr v4addr = address_.ipaddr().ipv4_address();
+      buf->WriteBytes(reinterpret_cast<char*>(&v4addr), sizeof(v4addr));
+      break;
+    }
+    case AF_INET6: {
+      in6_addr v6addr = address_.ipaddr().ipv6_address();
+      buf->WriteBytes(reinterpret_cast<char*>(&v6addr), sizeof(v6addr));
+      break;
+    }
+  }
+  return true;
+}
+
+StunXorAddressAttribute::StunXorAddressAttribute(uint16 type,
+    const talk_base::SocketAddress& addr)
+    : StunAddressAttribute(type, addr), 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);
+      }
+      case AF_INET6: {
+        in6_addr v6addr = ip.ipv6_address();
+        const std::string& transaction_id = owner_->transaction_id();
+        if (transaction_id.length() == kStunTransactionIdLength) {
+          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;
+  uint16 xoredport = port() ^ (kStunMagicCookie >> 16);
+  talk_base::IPAddress xored_ip = GetXoredIP();
+  SetAddress(talk_base::SocketAddress(xored_ip, xoredport));
+  return true;
+}
+
+bool StunXorAddressAttribute::Write(ByteBuffer* buf) const {
+  StunAddressFamily address_family = family();
+  if (address_family == STUN_ADDRESS_UNDEF) {
+    LOG(LS_ERROR) << "Error writing xor-address attribute: unknown family.";
+    return false;
+  }
+  talk_base::IPAddress xored_ip = GetXoredIP();
+  if (xored_ip.family() == AF_UNSPEC) {
+    return false;
+  }
+  buf->WriteUInt8(0);
+  buf->WriteUInt8(family());
+  buf->WriteUInt16(port() ^ (kStunMagicCookie >> 16));
+  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;
+    }
+  }
+  return true;
+}
+
+StunUInt32Attribute::StunUInt32Attribute(uint16 type, uint32 value)
+    : StunAttribute(type, SIZE), bits_(value) {
+}
+
+StunUInt32Attribute::StunUInt32Attribute(uint16 type)
+    : StunAttribute(type, SIZE), bits_(0) {
+}
+
+bool StunUInt32Attribute::GetBit(size_t index) const {
+  ASSERT(index < 32);
+  return static_cast<bool>((bits_ >> index) & 0x1);
+}
+
+void StunUInt32Attribute::SetBit(size_t index, bool value) {
+  ASSERT(index < 32);
+  bits_ &= ~(1 << index);
+  bits_ |= value ? (1 << index) : 0;
+}
+
+bool StunUInt32Attribute::Read(ByteBuffer* buf) {
+  if (length() != SIZE || !buf->ReadUInt32(&bits_))
+    return false;
+  return true;
+}
+
+bool StunUInt32Attribute::Write(ByteBuffer* buf) const {
+  buf->WriteUInt32(bits_);
+  return true;
+}
+
+StunUInt64Attribute::StunUInt64Attribute(uint16 type, uint64 value)
+    : StunAttribute(type, SIZE), bits_(value) {
+}
+
+StunUInt64Attribute::StunUInt64Attribute(uint16 type)
+    : StunAttribute(type, SIZE), bits_(0) {
+}
+
+bool StunUInt64Attribute::Read(ByteBuffer* buf) {
+  if (length() != SIZE || !buf->ReadUInt64(&bits_))
+    return false;
+  return true;
+}
+
+bool StunUInt64Attribute::Write(ByteBuffer* buf) const {
+  buf->WriteUInt64(bits_);
+  return true;
+}
+
+StunByteStringAttribute::StunByteStringAttribute(uint16 type)
+    : StunAttribute(type, 0), bytes_(NULL) {
+}
+
+StunByteStringAttribute::StunByteStringAttribute(uint16 type,
+                                                 const std::string& str)
+    : StunAttribute(type, 0), bytes_(NULL) {
+  CopyBytes(str.c_str(), str.size());
+}
+
+StunByteStringAttribute::StunByteStringAttribute(uint16 type,
+                                                 const void* bytes,
+                                                 size_t length)
+    : StunAttribute(type, 0), bytes_(NULL) {
+  CopyBytes(bytes, length);
+}
+
+StunByteStringAttribute::StunByteStringAttribute(uint16 type, uint16 length)
+    : StunAttribute(type, length), bytes_(NULL) {
+}
+
+StunByteStringAttribute::~StunByteStringAttribute() {
+  delete [] bytes_;
+}
+
+void StunByteStringAttribute::CopyBytes(const char* bytes) {
+  CopyBytes(bytes, strlen(bytes));
+}
+
+void StunByteStringAttribute::CopyBytes(const void* bytes, size_t length) {
+  char* new_bytes = new char[length];
+  std::memcpy(new_bytes, bytes, length);
+  SetBytes(new_bytes, length);
+}
+
+uint8 StunByteStringAttribute::GetByte(size_t index) const {
+  ASSERT(bytes_ != NULL);
+  ASSERT(index < length());
+  return static_cast<uint8>(bytes_[index]);
+}
+
+void StunByteStringAttribute::SetByte(size_t index, uint8 value) {
+  ASSERT(bytes_ != NULL);
+  ASSERT(index < length());
+  bytes_[index] = value;
+}
+
+bool StunByteStringAttribute::Read(ByteBuffer* buf) {
+  bytes_ = new char[length()];
+  if (!buf->ReadBytes(bytes_, length())) {
+    return false;
+  }
+
+  ConsumePadding(buf);
+  return true;
+}
+
+bool StunByteStringAttribute::Write(ByteBuffer* buf) const {
+  buf->WriteBytes(bytes_, length());
+  WritePadding(buf);
+  return true;
+}
+
+void StunByteStringAttribute::SetBytes(char* bytes, size_t length) {
+  delete [] bytes_;
+  bytes_ = bytes;
+  SetLength(length);
+}
+
+StunErrorCodeAttribute::StunErrorCodeAttribute(uint16 type, int code,
+                                               const std::string& reason)
+    : StunAttribute(type, 0) {
+  SetCode(code);
+  SetReason(reason);
+}
+
+StunErrorCodeAttribute::StunErrorCodeAttribute(uint16 type, uint16 length)
+    : StunAttribute(type, length), class_(0), number_(0) {
+}
+
+StunErrorCodeAttribute::~StunErrorCodeAttribute() {
+}
+
+int StunErrorCodeAttribute::code() const {
+  return class_ * 100 + number_;
+}
+
+void StunErrorCodeAttribute::SetCode(int code) {
+  class_ = static_cast<uint8>(code / 100);
+  number_ = static_cast<uint8>(code % 100);
+}
+
+void StunErrorCodeAttribute::SetReason(const std::string& reason) {
+  SetLength(MIN_SIZE + static_cast<uint16>(reason.size()));
+  reason_ = reason;
+}
+
+bool StunErrorCodeAttribute::Read(ByteBuffer* buf) {
+  uint32 val;
+  if (length() < MIN_SIZE || !buf->ReadUInt32(&val))
+    return false;
+
+  if ((val >> 11) != 0)
+    LOG(LS_ERROR) << "error-code bits not zero";
+
+  class_ = ((val >> 8) & 0x7);
+  number_ = (val & 0xff);
+
+  if (!buf->ReadString(&reason_, length() - 4))
+    return false;
+
+  ConsumePadding(buf);
+  return true;
+}
+
+bool StunErrorCodeAttribute::Write(ByteBuffer* buf) const {
+  buf->WriteUInt32(class_ << 8 | number_);
+  buf->WriteString(reason_);
+  WritePadding(buf);
+  return true;
+}
+
+StunUInt16ListAttribute::StunUInt16ListAttribute(uint16 type, uint16 length)
+    : StunAttribute(type, length) {
+  attr_types_ = new std::vector<uint16>();
+}
+
+StunUInt16ListAttribute::~StunUInt16ListAttribute() {
+  delete attr_types_;
+}
+
+size_t StunUInt16ListAttribute::Size() const {
+  return attr_types_->size();
+}
+
+uint16 StunUInt16ListAttribute::GetType(int index) const {
+  return (*attr_types_)[index];
+}
+
+void StunUInt16ListAttribute::SetType(int index, uint16 value) {
+  (*attr_types_)[index] = value;
+}
+
+void StunUInt16ListAttribute::AddType(uint16 value) {
+  attr_types_->push_back(value);
+  SetLength(static_cast<uint16>(attr_types_->size() * 2));
+}
+
+bool StunUInt16ListAttribute::Read(ByteBuffer* buf) {
+  if (length() % 2)
+    return false;
+
+  for (size_t i = 0; i < length() / 2; i++) {
+    uint16 attr;
+    if (!buf->ReadUInt16(&attr))
+      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;
+}
+
+bool StunUInt16ListAttribute::Write(ByteBuffer* buf) const {
+  for (size_t i = 0; i < attr_types_->size(); ++i) {
+    buf->WriteUInt16((*attr_types_)[i]);
+  }
+  WritePadding(buf);
+  return true;
+}
+
+int GetStunSuccessResponseType(int req_type) {
+  return IsStunRequestType(req_type) ? (req_type | 0x100) : -1;
+}
+
+int GetStunErrorResponseType(int req_type) {
+  return IsStunRequestType(req_type) ? (req_type | 0x110) : -1;
+}
+
+bool IsStunRequestType(int msg_type) {
+  return ((msg_type & kStunTypeMask) == 0x000);
+}
+
+bool IsStunIndicationType(int msg_type) {
+  return ((msg_type & kStunTypeMask) == 0x010);
+}
+
+bool IsStunSuccessResponseType(int msg_type) {
+  return ((msg_type & kStunTypeMask) == 0x100);
+}
+
+bool IsStunErrorResponseType(int msg_type) {
+  return ((msg_type & kStunTypeMask) == 0x110);
+}
+
+bool ComputeStunCredentialHash(const std::string& username,
+                               const std::string& realm,
+                               const std::string& password,
+                               std::string* hash) {
+  // http://tools.ietf.org/html/rfc5389#section-15.4
+  // long-term credentials will be calculated using the key and key is
+  // key = MD5(username ":" realm ":" SASLprep(password))
+  std::string input = username;
+  input += ':';
+  input += realm;
+  input += ':';
+  input += password;
+
+  char digest[talk_base::MessageDigest::kMaxSize];
+  size_t size = talk_base::ComputeDigest(
+      talk_base::DIGEST_MD5, input.c_str(), input.size(),
+      digest, sizeof(digest));
+  if (size == 0) {
+    return false;
+  }
+
+  *hash = std::string(digest, size);
+  return true;
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/stun.h b/talk/p2p/base/stun.h
new file mode 100644
index 0000000..6416e51
--- /dev/null
+++ b/talk/p2p/base/stun.h
@@ -0,0 +1,648 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_STUN_H_
+#define TALK_P2P_BASE_STUN_H_
+
+// This file contains classes for dealing with the STUN protocol, as specified
+// in RFC 5389, and its descendants.
+
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/bytebuffer.h"
+#include "talk/base/socketaddress.h"
+
+namespace cricket {
+
+// These are the types of STUN messages defined in RFC 5389.
+enum StunMessageType {
+  STUN_BINDING_REQUEST                  = 0x0001,
+  STUN_BINDING_INDICATION               = 0x0011,
+  STUN_BINDING_RESPONSE                 = 0x0101,
+  STUN_BINDING_ERROR_RESPONSE           = 0x0111,
+};
+
+// These are all known STUN attributes, defined in RFC 5389 and elsewhere.
+// Next to each is the name of the class (T is StunTAttribute) that implements
+// that type.
+// RETRANSMIT_COUNT is the number of outstanding pings without a response at
+// the time the packet is generated.
+enum StunAttributeType {
+  STUN_ATTR_MAPPED_ADDRESS              = 0x0001,  // Address
+  STUN_ATTR_USERNAME                    = 0x0006,  // ByteString
+  STUN_ATTR_MESSAGE_INTEGRITY           = 0x0008,  // ByteString, 20 bytes
+  STUN_ATTR_ERROR_CODE                  = 0x0009,  // ErrorCode
+  STUN_ATTR_UNKNOWN_ATTRIBUTES          = 0x000a,  // UInt16List
+  STUN_ATTR_REALM                       = 0x0014,  // ByteString
+  STUN_ATTR_NONCE                       = 0x0015,  // ByteString
+  STUN_ATTR_XOR_MAPPED_ADDRESS          = 0x0020,  // XorAddress
+  STUN_ATTR_SOFTWARE                    = 0x8022,  // ByteString
+  STUN_ATTR_ALTERNATE_SERVER            = 0x8023,  // ByteString
+  STUN_ATTR_FINGERPRINT                 = 0x8028,  // UInt32
+  STUN_ATTR_RETRANSMIT_COUNT            = 0xFF00   // UInt32
+};
+
+// These are the types of the values associated with the attributes above.
+// This allows us to perform some basic validation when reading or adding
+// attributes. Note that these values are for our own use, and not defined in
+// RFC 5389.
+enum StunAttributeValueType {
+  STUN_VALUE_UNKNOWN                    = 0,
+  STUN_VALUE_ADDRESS                    = 1,
+  STUN_VALUE_XOR_ADDRESS                = 2,
+  STUN_VALUE_UINT32                     = 3,
+  STUN_VALUE_UINT64                     = 4,
+  STUN_VALUE_BYTE_STRING                = 5,
+  STUN_VALUE_ERROR_CODE                 = 6,
+  STUN_VALUE_UINT16_LIST                = 7
+};
+
+// These are the types of STUN addresses defined in RFC 5389.
+enum StunAddressFamily {
+  // NB: UNDEF is not part of the STUN spec.
+  STUN_ADDRESS_UNDEF                    = 0,
+  STUN_ADDRESS_IPV4                     = 1,
+  STUN_ADDRESS_IPV6                     = 2
+};
+
+// These are the types of STUN error codes defined in RFC 5389.
+enum StunErrorCode {
+  STUN_ERROR_TRY_ALTERNATE              = 300,
+  STUN_ERROR_BAD_REQUEST                = 400,
+  STUN_ERROR_UNAUTHORIZED               = 401,
+  STUN_ERROR_UNKNOWN_ATTRIBUTE          = 420,
+  STUN_ERROR_STALE_CREDENTIALS          = 430,  // GICE only
+  STUN_ERROR_STALE_NONCE                = 438,
+  STUN_ERROR_SERVER_ERROR               = 500,
+  STUN_ERROR_GLOBAL_FAILURE             = 600
+};
+
+// Strings for the error codes above.
+extern const char STUN_ERROR_REASON_BAD_REQUEST[];
+extern const char STUN_ERROR_REASON_UNAUTHORIZED[];
+extern const char STUN_ERROR_REASON_UNKNOWN_ATTRIBUTE[];
+extern const char STUN_ERROR_REASON_STALE_CREDENTIALS[];
+extern const char STUN_ERROR_REASON_STALE_NONCE[];
+extern const char STUN_ERROR_REASON_SERVER_ERROR[];
+
+// The mask used to determine whether a STUN message is a request/response etc.
+const uint32 kStunTypeMask = 0x0110;
+
+// STUN Attribute header length.
+const size_t kStunAttributeHeaderSize = 4;
+
+// Following values correspond to RFC5389.
+const size_t kStunHeaderSize = 20;
+const size_t kStunTransactionIdOffset = 8;
+const size_t kStunTransactionIdLength = 12;
+const uint32 kStunMagicCookie = 0x2112A442;
+const size_t kStunMagicCookieLength = sizeof(kStunMagicCookie);
+
+// Following value corresponds to an earlier version of STUN from
+// RFC3489.
+const size_t kStunLegacyTransactionIdLength = 16;
+
+// STUN Message Integrity HMAC length.
+const size_t kStunMessageIntegritySize = 20;
+
+class StunAttribute;
+class StunAddressAttribute;
+class StunXorAddressAttribute;
+class StunUInt32Attribute;
+class StunUInt64Attribute;
+class StunByteStringAttribute;
+class StunErrorCodeAttribute;
+class StunUInt16ListAttribute;
+
+// Records a complete STUN/TURN message.  Each message consists of a type and
+// any number of attributes.  Each attribute is parsed into an instance of an
+// appropriate class (see above).  The Get* methods will return instances of
+// that attribute class.
+class StunMessage {
+ public:
+  StunMessage();
+  virtual ~StunMessage();
+
+  int type() const { return type_; }
+  size_t length() const { return length_; }
+  const std::string& transaction_id() const { return transaction_id_; }
+
+  // Returns true if the message confirms to RFC3489 rather than
+  // RFC5389. The main difference between two version of the STUN
+  // protocol is the presence of the magic cookie and different length
+  // of transaction ID. For outgoing packets version of the protocol
+  // is determined by the lengths of the transaction ID.
+  bool IsLegacy() const;
+
+  void SetType(int type) { type_ = static_cast<uint16>(type); }
+  bool SetTransactionID(const std::string& str);
+
+  // Gets the desired attribute value, or NULL if no such attribute type exists.
+  const StunAddressAttribute* GetAddress(int type) const;
+  const StunUInt32Attribute* GetUInt32(int type) const;
+  const StunUInt64Attribute* GetUInt64(int type) const;
+  const StunByteStringAttribute* GetByteString(int type) const;
+
+  // Gets these specific attribute values.
+  const StunErrorCodeAttribute* GetErrorCode() const;
+  const StunUInt16ListAttribute* GetUnknownAttributes() const;
+
+  // Takes ownership of the specified attribute, verifies it is of the correct
+  // type, and adds it to the message. The return value indicates whether this
+  // was successful.
+  bool AddAttribute(StunAttribute* attr);
+
+  // Validates that a raw STUN message has a correct MESSAGE-INTEGRITY value.
+  // This can't currently be done on a StunMessage, since it is affected by
+  // padding data (which we discard when reading a StunMessage).
+  static bool ValidateMessageIntegrity(const char* data, size_t size,
+                                       const std::string& password);
+  // Adds a MESSAGE-INTEGRITY attribute that is valid for the current message.
+  bool AddMessageIntegrity(const std::string& password);
+  bool AddMessageIntegrity(const char* key, size_t keylen);
+
+  // Verifies that a given buffer is STUN by checking for a correct FINGERPRINT.
+  static bool ValidateFingerprint(const char* data, size_t size);
+
+  // Adds a FINGERPRINT attribute that is valid for the current message.
+  bool AddFingerprint();
+
+  // Parses the STUN packet in the given buffer and records it here. The
+  // return value indicates whether this was successful.
+  bool Read(talk_base::ByteBuffer* buf);
+
+  // Writes this object into a STUN packet. The return value indicates whether
+  // this was successful.
+  bool Write(talk_base::ByteBuffer* buf) const;
+
+  // Creates an empty message. Overridable by derived classes.
+  virtual StunMessage* CreateNew() const { return new StunMessage(); }
+
+ protected:
+  // Verifies that the given attribute is allowed for this message.
+  virtual StunAttributeValueType GetAttributeValueType(int type) const;
+
+ private:
+  StunAttribute* CreateAttribute(int type, size_t length) /* const*/;
+  const StunAttribute* GetAttribute(int type) const;
+  static bool IsValidTransactionId(const std::string& transaction_id);
+
+  uint16 type_;
+  uint16 length_;
+  std::string transaction_id_;
+  std::vector<StunAttribute*>* attrs_;
+};
+
+// Base class for all STUN/TURN attributes.
+class StunAttribute {
+ public:
+  virtual ~StunAttribute() {
+  }
+
+  int type() const { return type_; }
+  size_t length() const { return length_; }
+
+  // Return the type of this attribute.
+  virtual StunAttributeValueType value_type() const = 0;
+
+  // 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;
+
+  // Writes the body (not the type or length) to the given buffer.  Return
+  // value is true if successful.
+  virtual bool Write(talk_base::ByteBuffer* buf) const = 0;
+
+  // Creates an attribute object with the given type and smallest length.
+  static StunAttribute* Create(StunAttributeValueType value_type, uint16 type,
+                               uint16 length, StunMessage* owner);
+  // TODO: Allow these create functions to take parameters, to reduce
+  // the amount of work callers need to do to initialize attributes.
+  static StunAddressAttribute* CreateAddress(uint16 type);
+  static StunXorAddressAttribute* CreateXorAddress(uint16 type);
+  static StunUInt32Attribute* CreateUInt32(uint16 type);
+  static StunUInt64Attribute* CreateUInt64(uint16 type);
+  static StunByteStringAttribute* CreateByteString(uint16 type);
+  static StunErrorCodeAttribute* CreateErrorCode();
+  static StunUInt16ListAttribute* CreateUnknownAttributes();
+
+ 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:
+  uint16 type_;
+  uint16 length_;
+};
+
+// Implements STUN attributes that record an Internet address.
+class StunAddressAttribute : public StunAttribute {
+ public:
+  static const uint16 SIZE_UNDEF = 0;
+  static const uint16 SIZE_IP4 = 8;
+  static const uint16 SIZE_IP6 = 20;
+  StunAddressAttribute(uint16 type, const talk_base::SocketAddress& addr);
+  StunAddressAttribute(uint16 type, uint16 length);
+
+  virtual StunAttributeValueType value_type() const {
+    return STUN_VALUE_ADDRESS;
+  }
+
+  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;
+  }
+
+  const talk_base::SocketAddress& GetAddress() const { return address_; }
+  const talk_base::IPAddress& ipaddr() const { return address_.ipaddr(); }
+  uint16 port() const { return address_.port(); }
+
+  void SetAddress(const talk_base::SocketAddress& addr) {
+    address_ = addr;
+    EnsureAddressLength();
+  }
+  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 bool Write(talk_base::ByteBuffer* buf) const;
+
+ 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 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:
+  StunXorAddressAttribute(uint16 type, const talk_base::SocketAddress& addr);
+  StunXorAddressAttribute(uint16 type, uint16 length,
+                          StunMessage* owner);
+
+  virtual StunAttributeValueType value_type() const {
+    return STUN_VALUE_XOR_ADDRESS;
+  }
+  virtual void SetOwner(StunMessage* owner) {
+    owner_ = owner;
+  }
+  virtual bool Read(talk_base::ByteBuffer* buf);
+  virtual bool Write(talk_base::ByteBuffer* buf) const;
+
+ private:
+  talk_base::IPAddress GetXoredIP() const;
+  StunMessage* owner_;
+};
+
+// Implements STUN attributes that record a 32-bit integer.
+class StunUInt32Attribute : public StunAttribute {
+ public:
+  static const uint16 SIZE = 4;
+  StunUInt32Attribute(uint16 type, uint32 value);
+  explicit StunUInt32Attribute(uint16 type);
+
+  virtual StunAttributeValueType value_type() const {
+    return STUN_VALUE_UINT32;
+  }
+
+  uint32 value() const { return bits_; }
+  void SetValue(uint32 bits) { bits_ = bits; }
+
+  bool GetBit(size_t index) const;
+  void SetBit(size_t index, bool value);
+
+  virtual bool Read(talk_base::ByteBuffer* buf);
+  virtual bool Write(talk_base::ByteBuffer* buf) const;
+
+ private:
+  uint32 bits_;
+};
+
+class StunUInt64Attribute : public StunAttribute {
+ public:
+  static const uint16 SIZE = 8;
+  StunUInt64Attribute(uint16 type, uint64 value);
+  explicit StunUInt64Attribute(uint16 type);
+
+  virtual StunAttributeValueType value_type() const {
+    return STUN_VALUE_UINT64;
+  }
+
+  uint64 value() const { return bits_; }
+  void SetValue(uint64 bits) { bits_ = bits; }
+
+  virtual bool Read(talk_base::ByteBuffer* buf);
+  virtual bool Write(talk_base::ByteBuffer* buf) const;
+
+ private:
+  uint64 bits_;
+};
+
+// Implements STUN attributes that record an arbitrary byte string.
+class StunByteStringAttribute : public StunAttribute {
+ public:
+  explicit StunByteStringAttribute(uint16 type);
+  StunByteStringAttribute(uint16 type, const std::string& str);
+  StunByteStringAttribute(uint16 type, const void* bytes, size_t length);
+  StunByteStringAttribute(uint16 type, uint16 length);
+  ~StunByteStringAttribute();
+
+  virtual StunAttributeValueType value_type() const {
+    return STUN_VALUE_BYTE_STRING;
+  }
+
+  const char* bytes() const { return bytes_; }
+  std::string GetString() const { return std::string(bytes_, length()); }
+
+  void CopyBytes(const char* bytes);  // uses strlen
+  void CopyBytes(const void* bytes, size_t length);
+
+  uint8 GetByte(size_t index) const;
+  void SetByte(size_t index, uint8 value);
+
+  virtual bool Read(talk_base::ByteBuffer* buf);
+  virtual bool Write(talk_base::ByteBuffer* buf) const;
+
+ private:
+  void SetBytes(char* bytes, size_t length);
+
+  char* bytes_;
+};
+
+// Implements STUN attributes that record an error code.
+class StunErrorCodeAttribute : public StunAttribute {
+ public:
+  static const uint16 MIN_SIZE = 4;
+  StunErrorCodeAttribute(uint16 type, int code, const std::string& reason);
+  StunErrorCodeAttribute(uint16 type, uint16 length);
+  ~StunErrorCodeAttribute();
+
+  virtual StunAttributeValueType value_type() const {
+    return STUN_VALUE_ERROR_CODE;
+  }
+
+  // The combined error and class, e.g. 0x400.
+  int code() const;
+  void SetCode(int code);
+
+  // The individual error components.
+  int eclass() const { return class_; }
+  int number() const { return number_; }
+  const std::string& reason() const { return reason_; }
+  void SetClass(uint8 eclass) { class_ = eclass; }
+  void SetNumber(uint8 number) { number_ = number; }
+  void SetReason(const std::string& reason);
+
+  bool Read(talk_base::ByteBuffer* buf);
+  bool Write(talk_base::ByteBuffer* buf) const;
+
+ private:
+  uint8 class_;
+  uint8 number_;
+  std::string reason_;
+};
+
+// Implements STUN attributes that record a list of attribute names.
+class StunUInt16ListAttribute : public StunAttribute {
+ public:
+  StunUInt16ListAttribute(uint16 type, uint16 length);
+  ~StunUInt16ListAttribute();
+
+  virtual StunAttributeValueType value_type() const {
+    return STUN_VALUE_UINT16_LIST;
+  }
+
+  size_t Size() const;
+  uint16 GetType(int index) const;
+  void SetType(int index, uint16 value);
+  void AddType(uint16 value);
+
+  bool Read(talk_base::ByteBuffer* buf);
+  bool Write(talk_base::ByteBuffer* buf) const;
+
+ private:
+  std::vector<uint16>* attr_types_;
+};
+
+// Returns the (successful) response type for the given request type.
+// Returns -1 if |request_type| is not a valid request type.
+int GetStunSuccessResponseType(int request_type);
+
+// Returns the error response type for the given request type.
+// Returns -1 if |request_type| is not a valid request type.
+int GetStunErrorResponseType(int request_type);
+
+// Returns whether a given message is a request type.
+bool IsStunRequestType(int msg_type);
+
+// Returns whether a given message is an indication type.
+bool IsStunIndicationType(int msg_type);
+
+// Returns whether a given response is a success type.
+bool IsStunSuccessResponseType(int msg_type);
+
+// Returns whether a given response is an error type.
+bool IsStunErrorResponseType(int msg_type);
+
+// Computes the STUN long-term credential hash.
+bool ComputeStunCredentialHash(const std::string& username,
+    const std::string& realm, const std::string& password, std::string* hash);
+
+// TODO: Move the TURN/ICE stuff below out to separate files.
+extern const char TURN_MAGIC_COOKIE_VALUE[4];
+
+// "GTURN" STUN methods.
+// TODO: Rename these methods to GTURN_ to make it clear they aren't
+// part of standard STUN/TURN.
+enum RelayMessageType {
+  // For now, using the same defs from TurnMessageType below.
+  // STUN_ALLOCATE_REQUEST              = 0x0003,
+  // STUN_ALLOCATE_RESPONSE             = 0x0103,
+  // STUN_ALLOCATE_ERROR_RESPONSE       = 0x0113,
+  STUN_SEND_REQUEST                     = 0x0004,
+  STUN_SEND_RESPONSE                    = 0x0104,
+  STUN_SEND_ERROR_RESPONSE              = 0x0114,
+  STUN_DATA_INDICATION                  = 0x0115,
+};
+
+// "GTURN"-specific STUN attributes.
+// TODO: Rename these attributes to GTURN_ to avoid conflicts.
+enum RelayAttributeType {
+  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_OPTIONS                     = 0x8001,  // UInt32
+};
+
+// A "GTURN" STUN message.
+class RelayMessage : public StunMessage {
+ protected:
+  virtual StunAttributeValueType GetAttributeValueType(int type) const {
+    switch (type) {
+      case STUN_ATTR_LIFETIME:            return STUN_VALUE_UINT32;
+      case STUN_ATTR_MAGIC_COOKIE:        return STUN_VALUE_BYTE_STRING;
+      case STUN_ATTR_BANDWIDTH:           return STUN_VALUE_UINT32;
+      case STUN_ATTR_DESTINATION_ADDRESS: return STUN_VALUE_ADDRESS;
+      case STUN_ATTR_SOURCE_ADDRESS2:     return STUN_VALUE_ADDRESS;
+      case STUN_ATTR_DATA:                return STUN_VALUE_BYTE_STRING;
+      case STUN_ATTR_OPTIONS:             return STUN_VALUE_UINT32;
+      default: return StunMessage::GetAttributeValueType(type);
+    }
+  }
+  virtual StunMessage* CreateNew() const { return new RelayMessage(); }
+};
+
+// Defined in TURN RFC 5766.
+enum TurnMessageType {
+  STUN_ALLOCATE_REQUEST                 = 0x0003,
+  STUN_ALLOCATE_RESPONSE                = 0x0103,
+  STUN_ALLOCATE_ERROR_RESPONSE          = 0x0113,
+  TURN_REFRESH_REQUEST                  = 0x0004,
+  TURN_REFRESH_RESPONSE                 = 0x0104,
+  TURN_REFRESH_ERROR_RESPONSE           = 0x0114,
+  TURN_SEND_INDICATION                  = 0x0016,
+  TURN_DATA_INDICATION                  = 0x0017,
+  TURN_CREATE_PERMISSION_REQUEST        = 0x0008,
+  TURN_CREATE_PERMISSION_RESPONSE       = 0x0108,
+  TURN_CREATE_PERMISSION_ERROR_RESPONSE = 0x0118,
+  TURN_CHANNEL_BIND_REQUEST             = 0x0009,
+  TURN_CHANNEL_BIND_RESPONSE            = 0x0109,
+  TURN_CHANNEL_BIND_ERROR_RESPONSE      = 0x0119,
+};
+
+enum TurnAttributeType {
+  STUN_ATTR_CHANNEL_NUMBER              = 0x000C,  // UInt32
+  STUN_ATTR_TURN_LIFETIME               = 0x000d,  // UInt32
+  STUN_ATTR_XOR_PEER_ADDRESS            = 0x0012,  // XorAddress
+  // TODO(mallinath) - Uncomment after RelayAttributes are renamed.
+  // STUN_ATTR_DATA                     = 0x0013,  // ByteString
+  STUN_ATTR_XOR_RELAYED_ADDRESS         = 0x0016,  // XorAddress
+  STUN_ATTR_EVEN_PORT                   = 0x0018,  // ByteString, 1 byte.
+  STUN_ATTR_REQUESTED_TRANSPORT         = 0x0019,  // UInt32
+  STUN_ATTR_DONT_FRAGMENT               = 0x001A,  // No content, Length = 0
+  STUN_ATTR_RESERVATION_TOKEN           = 0x0022,  // ByteString, 8 bytes.
+  // TODO(mallinath) - Rename STUN_ATTR_TURN_LIFETIME to STUN_ATTR_LIFETIME and
+  // STUN_ATTR_TURN_DATA to STUN_ATTR_DATA. Also rename RelayMessage attributes
+  // by appending G to attribute name.
+};
+
+// RFC 5766-defined errors.
+enum TurnErrorType {
+  STUN_ERROR_FORBIDDEN                  = 403,
+  STUN_ERROR_ALLOCATION_MISMATCH        = 437,
+  STUN_ERROR_WRONG_CREDENTIALS          = 441,
+  STUN_ERROR_UNSUPPORTED_PROTOCOL       = 442
+};
+extern const char STUN_ERROR_REASON_FORBIDDEN[];
+extern const char STUN_ERROR_REASON_ALLOCATION_MISMATCH[];
+extern const char STUN_ERROR_REASON_WRONG_CREDENTIALS[];
+extern const char STUN_ERROR_REASON_UNSUPPORTED_PROTOCOL[];
+class TurnMessage : public StunMessage {
+ protected:
+  virtual StunAttributeValueType GetAttributeValueType(int type) const {
+    switch (type) {
+      case STUN_ATTR_CHANNEL_NUMBER:      return STUN_VALUE_UINT32;
+      case STUN_ATTR_TURN_LIFETIME:       return STUN_VALUE_UINT32;
+      case STUN_ATTR_XOR_PEER_ADDRESS:    return STUN_VALUE_XOR_ADDRESS;
+      case STUN_ATTR_DATA:                return STUN_VALUE_BYTE_STRING;
+      case STUN_ATTR_XOR_RELAYED_ADDRESS: return STUN_VALUE_XOR_ADDRESS;
+      case STUN_ATTR_EVEN_PORT:           return STUN_VALUE_BYTE_STRING;
+      case STUN_ATTR_REQUESTED_TRANSPORT: return STUN_VALUE_UINT32;
+      case STUN_ATTR_DONT_FRAGMENT:       return STUN_VALUE_BYTE_STRING;
+      case STUN_ATTR_RESERVATION_TOKEN:   return STUN_VALUE_BYTE_STRING;
+      default: return StunMessage::GetAttributeValueType(type);
+    }
+  }
+  virtual StunMessage* CreateNew() const { return new TurnMessage(); }
+};
+
+// RFC 5245 ICE STUN attributes.
+enum IceAttributeType {
+  STUN_ATTR_PRIORITY                    = 0x0024,  // UInt32
+  STUN_ATTR_USE_CANDIDATE               = 0x0025,  // No content, Length = 0
+  STUN_ATTR_ICE_CONTROLLED              = 0x8029,  // UInt64
+  STUN_ATTR_ICE_CONTROLLING             = 0x802A   // UInt64
+};
+
+// RFC 5245-defined errors.
+enum IceErrorCode {
+  STUN_ERROR_ROLE_CONFLICT              = 487,
+};
+extern const char STUN_ERROR_REASON_ROLE_CONFLICT[];
+
+// A RFC 5245 ICE STUN message.
+class IceMessage : public StunMessage {
+ protected:
+  virtual StunAttributeValueType GetAttributeValueType(int type) const {
+    switch (type) {
+      case STUN_ATTR_PRIORITY:        return STUN_VALUE_UINT32;
+      case STUN_ATTR_USE_CANDIDATE:   return STUN_VALUE_BYTE_STRING;
+      case STUN_ATTR_ICE_CONTROLLED:  return STUN_VALUE_UINT64;
+      case STUN_ATTR_ICE_CONTROLLING: return STUN_VALUE_UINT64;
+      default: return StunMessage::GetAttributeValueType(type);
+    }
+  }
+  virtual StunMessage* CreateNew() const { return new IceMessage(); }
+};
+
+}  // 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..43db959
--- /dev/null
+++ b/talk/p2p/base/stun_unittest.cc
@@ -0,0 +1,1468 @@
+/*
+ * 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/messagedigest.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(length, msg.transaction_id().size());
+    ASSERT_EQ(length == kStunTransactionIdLength + 4, msg.IsLegacy());
+    ASSERT_EQ(length == kStunTransactionIdLength, !msg.IsLegacy());
+    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, 0x06, 0x00, 0x03,  // Followed by a known attribute we can
+  0x61, 0x62, 0x63, 0x00   // check for (username of length 3)
+};
+
+// 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, 0x11, 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,
+};
+
+// 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,
+};
+
+// RFC5769 Test Vectors
+// Software name (request):  "STUN test client" (without quotes)
+// Software name (response): "test vector" (without quotes)
+// Username:  "evtj:h6vY" (without quotes)
+// Password:  "VOkJxbRl1RmTxUk/WvJxBt" (without quotes)
+static const unsigned char kRfc5769SampleMsgTransactionId[] = {
+  0xb7, 0xe7, 0xa7, 0x01, 0xbc, 0x34, 0xd6, 0x86, 0xfa, 0x87, 0xdf, 0xae
+};
+static const char kRfc5769SampleMsgClientSoftware[] = "STUN test client";
+static const char kRfc5769SampleMsgServerSoftware[] = "test vector";
+static const char kRfc5769SampleMsgUsername[] = "evtj:h6vY";
+static const char kRfc5769SampleMsgPassword[] = "VOkJxbRl1RmTxUk/WvJxBt";
+static const talk_base::SocketAddress kRfc5769SampleMsgMappedAddress(
+    "192.0.2.1", 32853);
+static const talk_base::SocketAddress kRfc5769SampleMsgIPv6MappedAddress(
+    "2001:db8:1234:5678:11:2233:4455:6677", 32853);
+
+static const unsigned char kRfc5769SampleMsgWithAuthTransactionId[] = {
+  0x78, 0xad, 0x34, 0x33, 0xc6, 0xad, 0x72, 0xc0, 0x29, 0xda, 0x41, 0x2e
+};
+static const char kRfc5769SampleMsgWithAuthUsername[] =
+    "\xe3\x83\x9e\xe3\x83\x88\xe3\x83\xaa\xe3\x83\x83\xe3\x82\xaf\xe3\x82\xb9";
+static const char kRfc5769SampleMsgWithAuthPassword[] = "TheMatrIX";
+static const char kRfc5769SampleMsgWithAuthNonce[] =
+    "f//499k954d6OL34oL9FSTvy64sA";
+static const char kRfc5769SampleMsgWithAuthRealm[] = "example.org";
+
+// 2.1.  Sample Request
+static const unsigned char kRfc5769SampleRequest[] = {
+  0x00, 0x01, 0x00, 0x58,   //    Request type and message length
+  0x21, 0x12, 0xa4, 0x42,   //    Magic cookie
+  0xb7, 0xe7, 0xa7, 0x01,   // }
+  0xbc, 0x34, 0xd6, 0x86,   // }  Transaction ID
+  0xfa, 0x87, 0xdf, 0xae,   // }
+  0x80, 0x22, 0x00, 0x10,   //    SOFTWARE attribute header
+  0x53, 0x54, 0x55, 0x4e,   // }
+  0x20, 0x74, 0x65, 0x73,   // }  User-agent...
+  0x74, 0x20, 0x63, 0x6c,   // }  ...name
+  0x69, 0x65, 0x6e, 0x74,   // }
+  0x00, 0x24, 0x00, 0x04,   //    PRIORITY attribute header
+  0x6e, 0x00, 0x01, 0xff,   //    ICE priority value
+  0x80, 0x29, 0x00, 0x08,   //    ICE-CONTROLLED attribute header
+  0x93, 0x2f, 0xf9, 0xb1,   // }  Pseudo-random tie breaker...
+  0x51, 0x26, 0x3b, 0x36,   // }   ...for ICE control
+  0x00, 0x06, 0x00, 0x09,   //    USERNAME attribute header
+  0x65, 0x76, 0x74, 0x6a,   // }
+  0x3a, 0x68, 0x36, 0x76,   // }  Username (9 bytes) and padding (3 bytes)
+  0x59, 0x20, 0x20, 0x20,   // }
+  0x00, 0x08, 0x00, 0x14,   //    MESSAGE-INTEGRITY attribute header
+  0x9a, 0xea, 0xa7, 0x0c,   // }
+  0xbf, 0xd8, 0xcb, 0x56,   // }
+  0x78, 0x1e, 0xf2, 0xb5,   // }  HMAC-SHA1 fingerprint
+  0xb2, 0xd3, 0xf2, 0x49,   // }
+  0xc1, 0xb5, 0x71, 0xa2,   // }
+  0x80, 0x28, 0x00, 0x04,   //    FINGERPRINT attribute header
+  0xe5, 0x7a, 0x3b, 0xcf    //    CRC32 fingerprint
+};
+
+// 2.2.  Sample IPv4 Response
+static const unsigned char kRfc5769SampleResponse[] = {
+  0x01, 0x01, 0x00, 0x3c,  //     Response type and message length
+  0x21, 0x12, 0xa4, 0x42,  //     Magic cookie
+  0xb7, 0xe7, 0xa7, 0x01,  // }
+  0xbc, 0x34, 0xd6, 0x86,  // }  Transaction ID
+  0xfa, 0x87, 0xdf, 0xae,  // }
+  0x80, 0x22, 0x00, 0x0b,  //    SOFTWARE attribute header
+  0x74, 0x65, 0x73, 0x74,  // }
+  0x20, 0x76, 0x65, 0x63,  // }  UTF-8 server name
+  0x74, 0x6f, 0x72, 0x20,  // }
+  0x00, 0x20, 0x00, 0x08,  //    XOR-MAPPED-ADDRESS attribute header
+  0x00, 0x01, 0xa1, 0x47,  //    Address family (IPv4) and xor'd mapped port
+  0xe1, 0x12, 0xa6, 0x43,  //    Xor'd mapped IPv4 address
+  0x00, 0x08, 0x00, 0x14,  //    MESSAGE-INTEGRITY attribute header
+  0x2b, 0x91, 0xf5, 0x99,  // }
+  0xfd, 0x9e, 0x90, 0xc3,  // }
+  0x8c, 0x74, 0x89, 0xf9,  // }  HMAC-SHA1 fingerprint
+  0x2a, 0xf9, 0xba, 0x53,  // }
+  0xf0, 0x6b, 0xe7, 0xd7,  // }
+  0x80, 0x28, 0x00, 0x04,  //    FINGERPRINT attribute header
+  0xc0, 0x7d, 0x4c, 0x96   //    CRC32 fingerprint
+};
+
+// 2.3.  Sample IPv6 Response
+static const unsigned char kRfc5769SampleResponseIPv6[] = {
+  0x01, 0x01, 0x00, 0x48,  //    Response type and message length
+  0x21, 0x12, 0xa4, 0x42,  //    Magic cookie
+  0xb7, 0xe7, 0xa7, 0x01,  // }
+  0xbc, 0x34, 0xd6, 0x86,  // }  Transaction ID
+  0xfa, 0x87, 0xdf, 0xae,  // }
+  0x80, 0x22, 0x00, 0x0b,  //    SOFTWARE attribute header
+  0x74, 0x65, 0x73, 0x74,  // }
+  0x20, 0x76, 0x65, 0x63,  // }  UTF-8 server name
+  0x74, 0x6f, 0x72, 0x20,  // }
+  0x00, 0x20, 0x00, 0x14,  //    XOR-MAPPED-ADDRESS attribute header
+  0x00, 0x02, 0xa1, 0x47,  //    Address family (IPv6) and xor'd mapped port.
+  0x01, 0x13, 0xa9, 0xfa,  // }
+  0xa5, 0xd3, 0xf1, 0x79,  // }  Xor'd mapped IPv6 address
+  0xbc, 0x25, 0xf4, 0xb5,  // }
+  0xbe, 0xd2, 0xb9, 0xd9,  // }
+  0x00, 0x08, 0x00, 0x14,  //    MESSAGE-INTEGRITY attribute header
+  0xa3, 0x82, 0x95, 0x4e,  // }
+  0x4b, 0xe6, 0x7b, 0xf1,  // }
+  0x17, 0x84, 0xc9, 0x7c,  // }  HMAC-SHA1 fingerprint
+  0x82, 0x92, 0xc2, 0x75,  // }
+  0xbf, 0xe3, 0xed, 0x41,  // }
+  0x80, 0x28, 0x00, 0x04,  //    FINGERPRINT attribute header
+  0xc8, 0xfb, 0x0b, 0x4c   //    CRC32 fingerprint
+};
+
+// 2.4.  Sample Request with Long-Term Authentication
+static const unsigned char kRfc5769SampleRequestLongTermAuth[] = {
+  0x00, 0x01, 0x00, 0x60,  //    Request type and message length
+  0x21, 0x12, 0xa4, 0x42,  //    Magic cookie
+  0x78, 0xad, 0x34, 0x33,  // }
+  0xc6, 0xad, 0x72, 0xc0,  // }  Transaction ID
+  0x29, 0xda, 0x41, 0x2e,  // }
+  0x00, 0x06, 0x00, 0x12,  //    USERNAME attribute header
+  0xe3, 0x83, 0x9e, 0xe3,  // }
+  0x83, 0x88, 0xe3, 0x83,  // }
+  0xaa, 0xe3, 0x83, 0x83,  // }  Username value (18 bytes) and padding (2 bytes)
+  0xe3, 0x82, 0xaf, 0xe3,  // }
+  0x82, 0xb9, 0x00, 0x00,  // }
+  0x00, 0x15, 0x00, 0x1c,  //    NONCE attribute header
+  0x66, 0x2f, 0x2f, 0x34,  // }
+  0x39, 0x39, 0x6b, 0x39,  // }
+  0x35, 0x34, 0x64, 0x36,  // }
+  0x4f, 0x4c, 0x33, 0x34,  // }  Nonce value
+  0x6f, 0x4c, 0x39, 0x46,  // }
+  0x53, 0x54, 0x76, 0x79,  // }
+  0x36, 0x34, 0x73, 0x41,  // }
+  0x00, 0x14, 0x00, 0x0b,  //    REALM attribute header
+  0x65, 0x78, 0x61, 0x6d,  // }
+  0x70, 0x6c, 0x65, 0x2e,  // }  Realm value (11 bytes) and padding (1 byte)
+  0x6f, 0x72, 0x67, 0x00,  // }
+  0x00, 0x08, 0x00, 0x14,  //    MESSAGE-INTEGRITY attribute header
+  0xf6, 0x70, 0x24, 0x65,  // }
+  0x6d, 0xd6, 0x4a, 0x3e,  // }
+  0x02, 0xb8, 0xe0, 0x71,  // }  HMAC-SHA1 fingerprint
+  0x2e, 0x85, 0xc9, 0xa2,  // }
+  0x8c, 0xa8, 0x96, 0x66   // }
+};
+
+// Length parameter is changed to 0x38 from 0x58.
+// AddMessageIntegrity will add MI information and update the length param
+// accordingly.
+static const unsigned char kRfc5769SampleRequestWithoutMI[] = {
+  0x00, 0x01, 0x00, 0x38,  //    Request type and message length
+  0x21, 0x12, 0xa4, 0x42,  //    Magic cookie
+  0xb7, 0xe7, 0xa7, 0x01,  // }
+  0xbc, 0x34, 0xd6, 0x86,  // }  Transaction ID
+  0xfa, 0x87, 0xdf, 0xae,  // }
+  0x80, 0x22, 0x00, 0x10,  //    SOFTWARE attribute header
+  0x53, 0x54, 0x55, 0x4e,  // }
+  0x20, 0x74, 0x65, 0x73,  // }  User-agent...
+  0x74, 0x20, 0x63, 0x6c,  // }  ...name
+  0x69, 0x65, 0x6e, 0x74,  // }
+  0x00, 0x24, 0x00, 0x04,  //    PRIORITY attribute header
+  0x6e, 0x00, 0x01, 0xff,  //    ICE priority value
+  0x80, 0x29, 0x00, 0x08,  //    ICE-CONTROLLED attribute header
+  0x93, 0x2f, 0xf9, 0xb1,  // }  Pseudo-random tie breaker...
+  0x51, 0x26, 0x3b, 0x36,  // }   ...for ICE control
+  0x00, 0x06, 0x00, 0x09,  //    USERNAME attribute header
+  0x65, 0x76, 0x74, 0x6a,  // }
+  0x3a, 0x68, 0x36, 0x76,  // }  Username (9 bytes) and padding (3 bytes)
+  0x59, 0x20, 0x20, 0x20   // }
+};
+
+// This HMAC differs from the RFC 5769 SampleRequest message. This differs
+// because spec uses 0x20 for the padding where as our implementation uses 0.
+static const unsigned char kCalculatedHmac1[] = {
+  0x79, 0x07, 0xc2, 0xd2,  // }
+  0xed, 0xbf, 0xea, 0x48,  // }
+  0x0e, 0x4c, 0x76, 0xd8,  // }  HMAC-SHA1 fingerprint
+  0x29, 0x62, 0xd5, 0xc3,  // }
+  0x74, 0x2a, 0xf9, 0xe3   // }
+};
+
+// Length parameter is changed to 0x1c from 0x3c.
+// AddMessageIntegrity will add MI information and update the length param
+// accordingly.
+static const unsigned char kRfc5769SampleResponseWithoutMI[] = {
+  0x01, 0x01, 0x00, 0x1c,  //    Response type and message length
+  0x21, 0x12, 0xa4, 0x42,  //    Magic cookie
+  0xb7, 0xe7, 0xa7, 0x01,  // }
+  0xbc, 0x34, 0xd6, 0x86,  // }  Transaction ID
+  0xfa, 0x87, 0xdf, 0xae,  // }
+  0x80, 0x22, 0x00, 0x0b,  //    SOFTWARE attribute header
+  0x74, 0x65, 0x73, 0x74,  // }
+  0x20, 0x76, 0x65, 0x63,  // }  UTF-8 server name
+  0x74, 0x6f, 0x72, 0x20,  // }
+  0x00, 0x20, 0x00, 0x08,  //    XOR-MAPPED-ADDRESS attribute header
+  0x00, 0x01, 0xa1, 0x47,  //    Address family (IPv4) and xor'd mapped port
+  0xe1, 0x12, 0xa6, 0x43   //    Xor'd mapped IPv4 address
+};
+
+// This HMAC differs from the RFC 5769 SampleResponse message. This differs
+// because spec uses 0x20 for the padding where as our implementation uses 0.
+static const unsigned char kCalculatedHmac2[] = {
+  0x5d, 0x6b, 0x58, 0xbe,  // }
+  0xad, 0x94, 0xe0, 0x7e,  // }
+  0xef, 0x0d, 0xfc, 0x12,  // }  HMAC-SHA1 fingerprint
+  0x82, 0xa2, 0xbd, 0x08,  // }
+  0x43, 0x14, 0x10, 0x28   // }
+};
+
+// 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 kTestErrorCode = 401;
+
+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 that the GetStun*Type and IsStun*Type methods work as expected.
+TEST_F(StunTest, MessageTypes) {
+  EXPECT_EQ(STUN_BINDING_RESPONSE,
+      GetStunSuccessResponseType(STUN_BINDING_REQUEST));
+  EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE,
+      GetStunErrorResponseType(STUN_BINDING_REQUEST));
+  EXPECT_EQ(-1, GetStunSuccessResponseType(STUN_BINDING_INDICATION));
+  EXPECT_EQ(-1, GetStunSuccessResponseType(STUN_BINDING_RESPONSE));
+  EXPECT_EQ(-1, GetStunSuccessResponseType(STUN_BINDING_ERROR_RESPONSE));
+  EXPECT_EQ(-1, GetStunErrorResponseType(STUN_BINDING_INDICATION));
+  EXPECT_EQ(-1, GetStunErrorResponseType(STUN_BINDING_RESPONSE));
+  EXPECT_EQ(-1, GetStunErrorResponseType(STUN_BINDING_ERROR_RESPONSE));
+
+  int types[] = {
+    STUN_BINDING_REQUEST, STUN_BINDING_INDICATION,
+    STUN_BINDING_RESPONSE, STUN_BINDING_ERROR_RESPONSE
+  };
+  for (int i = 0; i < ARRAY_SIZE(types); ++i) {
+    EXPECT_EQ(i == 0, IsStunRequestType(types[i]));
+    EXPECT_EQ(i == 1, IsStunIndicationType(types[i]));
+    EXPECT_EQ(i == 2, IsStunSuccessResponseType(types[i]));
+    EXPECT_EQ(i == 3, IsStunErrorResponseType(types[i]));
+    EXPECT_EQ(1, types[i] & 0xFEEF);
+  }
+}
+
+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);
+}
+
+// Read the RFC5389 fields from the RFC5769 sample STUN request.
+TEST_F(StunTest, ReadRfc5769RequestMessage) {
+  StunMessage msg;
+  size_t size = ReadStunMessage(&msg, kRfc5769SampleRequest);
+  CheckStunHeader(msg, STUN_BINDING_REQUEST, size);
+  CheckStunTransactionID(msg, kRfc5769SampleMsgTransactionId,
+                         kStunTransactionIdLength);
+
+  const StunByteStringAttribute* software =
+      msg.GetByteString(STUN_ATTR_SOFTWARE);
+  ASSERT_TRUE(software != NULL);
+  EXPECT_EQ(kRfc5769SampleMsgClientSoftware, software->GetString());
+
+  const StunByteStringAttribute* username =
+      msg.GetByteString(STUN_ATTR_USERNAME);
+  ASSERT_TRUE(username != NULL);
+  EXPECT_EQ(kRfc5769SampleMsgUsername, username->GetString());
+
+  // Actual M-I value checked in a later test.
+  ASSERT_TRUE(msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL);
+
+  // Fingerprint checked in a later test, but double-check the value here.
+  const StunUInt32Attribute* fingerprint =
+      msg.GetUInt32(STUN_ATTR_FINGERPRINT);
+  ASSERT_TRUE(fingerprint != NULL);
+  EXPECT_EQ(0xe57a3bcf, fingerprint->value());
+}
+
+// Read the RFC5389 fields from the RFC5769 sample STUN response.
+TEST_F(StunTest, ReadRfc5769ResponseMessage) {
+  StunMessage msg;
+  size_t size = ReadStunMessage(&msg, kRfc5769SampleResponse);
+  CheckStunHeader(msg, STUN_BINDING_RESPONSE, size);
+  CheckStunTransactionID(msg, kRfc5769SampleMsgTransactionId,
+                         kStunTransactionIdLength);
+
+  const StunByteStringAttribute* software =
+      msg.GetByteString(STUN_ATTR_SOFTWARE);
+  ASSERT_TRUE(software != NULL);
+  EXPECT_EQ(kRfc5769SampleMsgServerSoftware, software->GetString());
+
+  const StunAddressAttribute* mapped_address =
+      msg.GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+  ASSERT_TRUE(mapped_address != NULL);
+  EXPECT_EQ(kRfc5769SampleMsgMappedAddress, mapped_address->GetAddress());
+
+  // Actual M-I and fingerprint checked in later tests.
+  ASSERT_TRUE(msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL);
+  ASSERT_TRUE(msg.GetUInt32(STUN_ATTR_FINGERPRINT) != NULL);
+}
+
+// Read the RFC5389 fields from the RFC5769 sample STUN response for IPv6.
+TEST_F(StunTest, ReadRfc5769ResponseMessageIPv6) {
+  StunMessage msg;
+  size_t size = ReadStunMessage(&msg, kRfc5769SampleResponseIPv6);
+  CheckStunHeader(msg, STUN_BINDING_RESPONSE, size);
+  CheckStunTransactionID(msg, kRfc5769SampleMsgTransactionId,
+                         kStunTransactionIdLength);
+
+  const StunByteStringAttribute* software =
+      msg.GetByteString(STUN_ATTR_SOFTWARE);
+  ASSERT_TRUE(software != NULL);
+  EXPECT_EQ(kRfc5769SampleMsgServerSoftware, software->GetString());
+
+  const StunAddressAttribute* mapped_address =
+      msg.GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+  ASSERT_TRUE(mapped_address != NULL);
+  EXPECT_EQ(kRfc5769SampleMsgIPv6MappedAddress, mapped_address->GetAddress());
+
+  // Actual M-I and fingerprint checked in later tests.
+  ASSERT_TRUE(msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL);
+  ASSERT_TRUE(msg.GetUInt32(STUN_ATTR_FINGERPRINT) != NULL);
+}
+
+// Read the RFC5389 fields from the RFC5769 sample STUN response with auth.
+TEST_F(StunTest, ReadRfc5769RequestMessageLongTermAuth) {
+  StunMessage msg;
+  size_t size = ReadStunMessage(&msg, kRfc5769SampleRequestLongTermAuth);
+  CheckStunHeader(msg, STUN_BINDING_REQUEST, size);
+  CheckStunTransactionID(msg, kRfc5769SampleMsgWithAuthTransactionId,
+                         kStunTransactionIdLength);
+
+  const StunByteStringAttribute* username =
+      msg.GetByteString(STUN_ATTR_USERNAME);
+  ASSERT_TRUE(username != NULL);
+  EXPECT_EQ(kRfc5769SampleMsgWithAuthUsername, username->GetString());
+
+  const StunByteStringAttribute* nonce =
+      msg.GetByteString(STUN_ATTR_NONCE);
+  ASSERT_TRUE(nonce != NULL);
+  EXPECT_EQ(kRfc5769SampleMsgWithAuthNonce, nonce->GetString());
+
+  const StunByteStringAttribute* realm =
+      msg.GetByteString(STUN_ATTR_REALM);
+  ASSERT_TRUE(realm != NULL);
+  EXPECT_EQ(kRfc5769SampleMsgWithAuthRealm, realm->GetString());
+
+  // No fingerprint, actual M-I checked in later tests.
+  ASSERT_TRUE(msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != NULL);
+  ASSERT_TRUE(msg.GetUInt32(STUN_ATTR_FINGERPRINT) == NULL);
+}
+
+// The RFC3489 packet in this test is the same as
+// kStunMessageWithIPv4MappedAddress, but with a different value where the
+// magic cookie was.
+TEST_F(StunTest, ReadLegacyMessage) {
+  unsigned char rfc3489_packet[sizeof(kStunMessageWithIPv4MappedAddress)];
+  memcpy(rfc3489_packet, kStunMessageWithIPv4MappedAddress,
+      sizeof(kStunMessageWithIPv4MappedAddress));
+  // Overwrite the magic cookie here.
+  memcpy(&rfc3489_packet[4], "ABCD", 4);
+
+  StunMessage msg;
+  size_t size = ReadStunMessage(&msg, rfc3489_packet);
+  CheckStunHeader(msg, STUN_BINDING_RESPONSE, size);
+  CheckStunTransactionID(msg, &rfc3489_packet[4], kStunTransactionIdLength + 4);
+
+  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, 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, NULL);
+  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;
+  EXPECT_TRUE(addr->Write(&correct_buf));
+  EXPECT_TRUE(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 fail and write nothing.
+  addr2.SetOwner(NULL);
+  ASSERT_EQ(addr2.ipaddr(), addr->ipaddr());
+  wrong_buf.Consume(wrong_buf.Length());
+  EXPECT_FALSE(addr2.Write(&wrong_buf));
+  ASSERT_EQ(0U, 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, NULL);
+  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;
+  EXPECT_TRUE(addr->Write(&correct_buf));
+  EXPECT_TRUE(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.
+  EXPECT_TRUE(addr2.Read(&correct_buf));
+  ASSERT_EQ(addr->ipaddr(), addr2.ipaddr());
+
+  // However, no owner is still an error, should fail and write nothing.
+  addr2.SetOwner(NULL);
+  ASSERT_EQ(addr2.ipaddr(), addr->ipaddr());
+  wrong_buf.Consume(wrong_buf.Length());
+  EXPECT_FALSE(addr2.Write(&wrong_buf));
+}
+
+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 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;
+}
+
+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);
+  EXPECT_TRUE(msg.AddAttribute(addr));
+
+  CheckStunHeader(msg, STUN_BINDING_REQUEST, (size - 20));
+
+  talk_base::ByteBuffer out;
+  EXPECT_TRUE(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);
+  EXPECT_TRUE(msg.AddAttribute(addr));
+
+  CheckStunHeader(msg, STUN_BINDING_RESPONSE, (size - 20));
+
+  talk_base::ByteBuffer out;
+  EXPECT_TRUE(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::CreateXorAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+  talk_base::SocketAddress test_addr(test_ip, kTestMessagePort1);
+  addr->SetAddress(test_addr);
+  EXPECT_TRUE(msg.AddAttribute(addr));
+
+  CheckStunHeader(msg, STUN_BINDING_RESPONSE, (size - 20));
+
+  talk_base::ByteBuffer out;
+  EXPECT_TRUE(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::CreateXorAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+  talk_base::SocketAddress test_addr(test_ip, kTestMessagePort3);
+  addr->SetAddress(test_addr);
+  EXPECT_TRUE(msg.AddAttribute(addr));
+
+  CheckStunHeader(msg, STUN_BINDING_RESPONSE, (size - 20));
+
+  talk_base::ByteBuffer out;
+  EXPECT_TRUE(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_TRUE(username != NULL);
+  EXPECT_EQ(kTestUserName1, username->GetString());
+}
+
+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_TRUE(username != NULL);
+  EXPECT_EQ(kTestUserName2, username->GetString());
+}
+
+TEST_F(StunTest, ReadErrorCodeAttribute) {
+  StunMessage msg;
+  size_t size = ReadStunMessage(&msg, kStunMessageWithErrorAttribute);
+
+  CheckStunHeader(msg, STUN_BINDING_ERROR_RESPONSE, size);
+  CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+  const StunErrorCodeAttribute* errorcode = msg.GetErrorCode();
+  ASSERT_TRUE(errorcode != NULL);
+  EXPECT_EQ(kTestErrorClass, errorcode->eclass());
+  EXPECT_EQ(kTestErrorNumber, errorcode->number());
+  EXPECT_EQ(kTestErrorReason, errorcode->reason());
+  EXPECT_EQ(kTestErrorCode, errorcode->code());
+}
+
+TEST_F(StunTest, ReadMessageWithAUInt16ListAttribute) {
+  StunMessage msg;
+  size_t size = ReadStunMessage(&msg, kStunMessageWithUInt16ListAttribute);
+  CheckStunHeader(msg, STUN_BINDING_REQUEST, size);
+  const StunUInt16ListAttribute* types = msg.GetUnknownAttributes();
+  ASSERT_TRUE(types != NULL);
+  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, ReadMessageWithAnUnknownAttribute) {
+  StunMessage msg;
+  size_t size = ReadStunMessage(&msg, kStunMessageWithUnknownAttribute);
+  CheckStunHeader(msg, STUN_BINDING_REQUEST, size);
+
+  // Parsing should have succeeded and there should be a USERNAME attribute
+  const StunByteStringAttribute* username =
+      msg.GetByteString(STUN_ATTR_USERNAME);
+  ASSERT_TRUE(username != NULL);
+  EXPECT_EQ(kTestUserName2, username->GetString());
+}
+
+TEST_F(StunTest, WriteMessageWithAnErrorCodeAttribute) {
+  StunMessage msg;
+  size_t size = sizeof(kStunMessageWithErrorAttribute);
+
+  msg.SetType(STUN_BINDING_ERROR_RESPONSE);
+  msg.SetTransactionID(
+      std::string(reinterpret_cast<const char*>(kTestTransactionId1),
+                  kStunTransactionIdLength));
+  CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+  StunErrorCodeAttribute* errorcode = StunAttribute::CreateErrorCode();
+  errorcode->SetCode(kTestErrorCode);
+  errorcode->SetReason(kTestErrorReason);
+  EXPECT_TRUE(msg.AddAttribute(errorcode));
+  CheckStunHeader(msg, STUN_BINDING_ERROR_RESPONSE, (size - 20));
+
+  talk_base::ByteBuffer out;
+  EXPECT_TRUE(msg.Write(&out));
+  ASSERT_EQ(size, out.Length());
+  // No padding.
+  ASSERT_EQ(0, std::memcmp(out.Data(), kStunMessageWithErrorAttribute, size));
+}
+
+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);
+  EXPECT_TRUE(msg.AddAttribute(list));
+  CheckStunHeader(msg, STUN_BINDING_REQUEST, (size - 20));
+
+  talk_base::ByteBuffer out;
+  EXPECT_TRUE(msg.Write(&out));
+  ASSERT_EQ(size, out.Length());
+  // Check everything up to the padding.
+  ASSERT_EQ(0, std::memcmp(out.Data(), kStunMessageWithUInt16ListAttribute,
+                           size - 2));
+}
+
+// Test that we fail to read messages with invalid lengths.
+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);
+}
+
+// Test that we properly fail to read a non-STUN message.
+TEST_F(StunTest, FailToReadRtcpPacket) {
+  CheckFailureToRead(kRtcpPacket, sizeof(kRtcpPacket));
+}
+
+// Check our STUN message validation code against the RFC5769 test messages.
+TEST_F(StunTest, ValidateMessageIntegrity) {
+  // Try the messages from RFC 5769.
+  EXPECT_TRUE(StunMessage::ValidateMessageIntegrity(
+      reinterpret_cast<const char*>(kRfc5769SampleRequest),
+      sizeof(kRfc5769SampleRequest),
+      kRfc5769SampleMsgPassword));
+  EXPECT_FALSE(StunMessage::ValidateMessageIntegrity(
+      reinterpret_cast<const char*>(kRfc5769SampleRequest),
+      sizeof(kRfc5769SampleRequest),
+      "InvalidPassword"));
+
+  EXPECT_TRUE(StunMessage::ValidateMessageIntegrity(
+      reinterpret_cast<const char*>(kRfc5769SampleResponse),
+      sizeof(kRfc5769SampleResponse),
+      kRfc5769SampleMsgPassword));
+  EXPECT_FALSE(StunMessage::ValidateMessageIntegrity(
+      reinterpret_cast<const char*>(kRfc5769SampleResponse),
+      sizeof(kRfc5769SampleResponse),
+      "InvalidPassword"));
+
+  EXPECT_TRUE(StunMessage::ValidateMessageIntegrity(
+      reinterpret_cast<const char*>(kRfc5769SampleResponseIPv6),
+      sizeof(kRfc5769SampleResponseIPv6),
+      kRfc5769SampleMsgPassword));
+  EXPECT_FALSE(StunMessage::ValidateMessageIntegrity(
+      reinterpret_cast<const char*>(kRfc5769SampleResponseIPv6),
+      sizeof(kRfc5769SampleResponseIPv6),
+      "InvalidPassword"));
+
+  // We first need to compute the key for the long-term authentication HMAC.
+  std::string key;
+  ComputeStunCredentialHash(kRfc5769SampleMsgWithAuthUsername,
+      kRfc5769SampleMsgWithAuthRealm, kRfc5769SampleMsgWithAuthPassword, &key);
+  EXPECT_TRUE(StunMessage::ValidateMessageIntegrity(
+      reinterpret_cast<const char*>(kRfc5769SampleRequestLongTermAuth),
+      sizeof(kRfc5769SampleRequestLongTermAuth), key));
+  EXPECT_FALSE(StunMessage::ValidateMessageIntegrity(
+      reinterpret_cast<const char*>(kRfc5769SampleRequestLongTermAuth),
+      sizeof(kRfc5769SampleRequestLongTermAuth),
+      "InvalidPassword"));
+
+  // Try some edge cases.
+  EXPECT_FALSE(StunMessage::ValidateMessageIntegrity(
+      reinterpret_cast<const char*>(kStunMessageWithZeroLength),
+      sizeof(kStunMessageWithZeroLength),
+      kRfc5769SampleMsgPassword));
+  EXPECT_FALSE(StunMessage::ValidateMessageIntegrity(
+      reinterpret_cast<const char*>(kStunMessageWithExcessLength),
+      sizeof(kStunMessageWithExcessLength),
+      kRfc5769SampleMsgPassword));
+  EXPECT_FALSE(StunMessage::ValidateMessageIntegrity(
+      reinterpret_cast<const char*>(kStunMessageWithSmallLength),
+      sizeof(kStunMessageWithSmallLength),
+      kRfc5769SampleMsgPassword));
+
+  // Test that munging a single bit anywhere in the message causes the
+  // message-integrity check to fail, unless it is after the M-I attribute.
+  char buf[sizeof(kRfc5769SampleRequest)];
+  memcpy(buf, kRfc5769SampleRequest, sizeof(kRfc5769SampleRequest));
+  for (size_t i = 0; i < sizeof(buf); ++i) {
+    buf[i] ^= 0x01;
+    if (i > 0)
+      buf[i - 1] ^= 0x01;
+    EXPECT_EQ(i >= sizeof(buf) - 8, StunMessage::ValidateMessageIntegrity(
+        buf, sizeof(buf), kRfc5769SampleMsgPassword));
+  }
+}
+
+// Validate that we generate correct MESSAGE-INTEGRITY attributes.
+// Note the use of IceMessage instead of StunMessage; this is necessary because
+// the RFC5769 test messages used include attributes not found in basic STUN.
+TEST_F(StunTest, AddMessageIntegrity) {
+  IceMessage msg;
+  talk_base::ByteBuffer buf(
+      reinterpret_cast<const char*>(kRfc5769SampleRequestWithoutMI),
+      sizeof(kRfc5769SampleRequestWithoutMI));
+  EXPECT_TRUE(msg.Read(&buf));
+  EXPECT_TRUE(msg.AddMessageIntegrity(kRfc5769SampleMsgPassword));
+  const StunByteStringAttribute* mi_attr =
+      msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY);
+  EXPECT_EQ(20U, mi_attr->length());
+  EXPECT_EQ(0, std::memcmp(
+      mi_attr->bytes(), kCalculatedHmac1, sizeof(kCalculatedHmac1)));
+
+  talk_base::ByteBuffer buf1;
+  EXPECT_TRUE(msg.Write(&buf1));
+  EXPECT_TRUE(StunMessage::ValidateMessageIntegrity(
+        reinterpret_cast<const char*>(buf1.Data()), buf1.Length(),
+        kRfc5769SampleMsgPassword));
+
+  IceMessage msg2;
+  talk_base::ByteBuffer buf2(
+      reinterpret_cast<const char*>(kRfc5769SampleResponseWithoutMI),
+      sizeof(kRfc5769SampleResponseWithoutMI));
+  EXPECT_TRUE(msg2.Read(&buf2));
+  EXPECT_TRUE(msg2.AddMessageIntegrity(kRfc5769SampleMsgPassword));
+  const StunByteStringAttribute* mi_attr2 =
+      msg2.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY);
+  EXPECT_EQ(20U, mi_attr2->length());
+  EXPECT_EQ(0, std::memcmp(
+      mi_attr2->bytes(), kCalculatedHmac2, sizeof(kCalculatedHmac2)));
+
+  talk_base::ByteBuffer buf3;
+  EXPECT_TRUE(msg2.Write(&buf3));
+  EXPECT_TRUE(StunMessage::ValidateMessageIntegrity(
+        reinterpret_cast<const char*>(buf3.Data()), buf3.Length(),
+        kRfc5769SampleMsgPassword));
+}
+
+// Check our STUN message validation code against the RFC5769 test messages.
+TEST_F(StunTest, ValidateFingerprint) {
+  EXPECT_TRUE(StunMessage::ValidateFingerprint(
+      reinterpret_cast<const char*>(kRfc5769SampleRequest),
+      sizeof(kRfc5769SampleRequest)));
+  EXPECT_TRUE(StunMessage::ValidateFingerprint(
+      reinterpret_cast<const char*>(kRfc5769SampleResponse),
+      sizeof(kRfc5769SampleResponse)));
+  EXPECT_TRUE(StunMessage::ValidateFingerprint(
+      reinterpret_cast<const char*>(kRfc5769SampleResponseIPv6),
+      sizeof(kRfc5769SampleResponseIPv6)));
+
+  EXPECT_FALSE(StunMessage::ValidateFingerprint(
+      reinterpret_cast<const char*>(kStunMessageWithZeroLength),
+      sizeof(kStunMessageWithZeroLength)));
+  EXPECT_FALSE(StunMessage::ValidateFingerprint(
+      reinterpret_cast<const char*>(kStunMessageWithExcessLength),
+      sizeof(kStunMessageWithExcessLength)));
+  EXPECT_FALSE(StunMessage::ValidateFingerprint(
+      reinterpret_cast<const char*>(kStunMessageWithSmallLength),
+      sizeof(kStunMessageWithSmallLength)));
+
+  // Test that munging a single bit anywhere in the message causes the
+  // fingerprint check to fail.
+  char buf[sizeof(kRfc5769SampleRequest)];
+  memcpy(buf, kRfc5769SampleRequest, sizeof(kRfc5769SampleRequest));
+  for (size_t i = 0; i < sizeof(buf); ++i) {
+    buf[i] ^= 0x01;
+    if (i > 0)
+      buf[i - 1] ^= 0x01;
+    EXPECT_FALSE(StunMessage::ValidateFingerprint(buf, sizeof(buf)));
+  }
+  // Put them all back to normal and the check should pass again.
+  buf[sizeof(buf) - 1] ^= 0x01;
+  EXPECT_TRUE(StunMessage::ValidateFingerprint(buf, sizeof(buf)));
+}
+
+TEST_F(StunTest, AddFingerprint) {
+  IceMessage msg;
+  talk_base::ByteBuffer buf(
+      reinterpret_cast<const char*>(kRfc5769SampleRequestWithoutMI),
+      sizeof(kRfc5769SampleRequestWithoutMI));
+  EXPECT_TRUE(msg.Read(&buf));
+  EXPECT_TRUE(msg.AddFingerprint());
+
+  talk_base::ByteBuffer buf1;
+  EXPECT_TRUE(msg.Write(&buf1));
+  EXPECT_TRUE(StunMessage::ValidateFingerprint(
+      reinterpret_cast<const char*>(buf1.Data()), buf1.Length()));
+}
+
+// Sample "GTURN" relay message.
+static const unsigned char kRelayMessage[] = {
+  0x00, 0x01, 0x00, 88,    // 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, 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.
+};
+
+// Test that we can read the GTURN-specific fields.
+TEST_F(StunTest, ReadRelayMessage) {
+  RelayMessage msg, msg2;
+
+  const char* input = reinterpret_cast<const char*>(kRelayMessage);
+  size_t size = sizeof(kRelayMessage);
+  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("0123456789ab", msg.transaction_id());
+
+  msg2.SetType(STUN_BINDING_REQUEST);
+  msg2.SetTransactionID("0123456789ab");
+
+  in_addr legacy_in_addr;
+  legacy_in_addr.s_addr = htonl(17U);
+  talk_base::IPAddress legacy_ip(legacy_in_addr);
+
+  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);
+  EXPECT_TRUE(msg2.AddAttribute(addr2));
+
+  const StunByteStringAttribute* bytes = msg.GetByteString(STUN_ATTR_USERNAME);
+  ASSERT_TRUE(bytes != NULL);
+  EXPECT_EQ(12U, bytes->length());
+  EXPECT_EQ("abcdefghijkl", bytes->GetString());
+
+  StunByteStringAttribute* bytes2 =
+  StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
+  bytes2->CopyBytes("abcdefghijkl");
+  EXPECT_TRUE(msg2.AddAttribute(bytes2));
+
+  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);
+  EXPECT_TRUE(msg2.AddAttribute(uval2));
+
+  bytes = msg.GetByteString(STUN_ATTR_MAGIC_COOKIE);
+  ASSERT_TRUE(bytes != NULL);
+  EXPECT_EQ(4U, 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));
+  EXPECT_TRUE(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);
+  EXPECT_TRUE(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);
+  EXPECT_TRUE(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);
+  EXPECT_TRUE(msg2.AddAttribute(addr2));
+
+  bytes = msg.GetByteString(STUN_ATTR_DATA);
+  ASSERT_TRUE(bytes != NULL);
+  EXPECT_EQ(7U, bytes->length());
+  EXPECT_EQ("abcdefg", bytes->GetString());
+
+  bytes2 = StunAttribute::CreateByteString(STUN_ATTR_DATA);
+  bytes2->CopyBytes("abcdefg");
+  EXPECT_TRUE(msg2.AddAttribute(bytes2));
+
+  talk_base::ByteBuffer out;
+  EXPECT_TRUE(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;
+  EXPECT_TRUE(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));
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/stunport.cc b/talk/p2p/base/stunport.cc
new file mode 100644
index 0000000..e182a51
--- /dev/null
+++ b/talk/p2p/base/stunport.cc
@@ -0,0 +1,353 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/p2p/base/stunport.h"
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/helpers.h"
+#include "talk/base/nethelpers.h"
+#include "talk/p2p/base/common.h"
+#include "talk/p2p/base/stun.h"
+
+namespace cricket {
+
+// TODO: Move these to a common place (used in relayport too)
+const int KEEPALIVE_DELAY = 10 * 1000;  // 10 seconds - sort timeouts
+const int RETRY_DELAY = 50;             // 50ms, from ICE spec
+const int RETRY_TIMEOUT = 50 * 1000;    // ICE says 50 secs
+
+// Handles a binding request sent to the STUN server.
+class StunBindingRequest : public StunRequest {
+ public:
+  StunBindingRequest(UDPPort* port, bool keep_alive,
+                     const talk_base::SocketAddress& addr)
+    : port_(port), keep_alive_(keep_alive), server_addr_(addr) {
+    start_time_ = talk_base::Time();
+  }
+
+  virtual ~StunBindingRequest() {
+  }
+
+  const talk_base::SocketAddress& server_addr() const { return server_addr_; }
+
+  virtual void Prepare(StunMessage* request) {
+    request->SetType(STUN_BINDING_REQUEST);
+  }
+
+  virtual void OnResponse(StunMessage* response) {
+    const StunAddressAttribute* addr_attr =
+        response->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+    if (!addr_attr) {
+      LOG(LS_ERROR) << "Binding response missing mapped address.";
+    } else if (addr_attr->family() != STUN_ADDRESS_IPV4 &&
+               addr_attr->family() != STUN_ADDRESS_IPV6) {
+      LOG(LS_ERROR) << "Binding address has bad family";
+    } else {
+      talk_base::SocketAddress addr(addr_attr->ipaddr(), addr_attr->port());
+      port_->OnStunBindingRequestSucceeded(addr);
+    }
+
+    // We will do a keep-alive regardless of whether this request suceeds.
+    // This should have almost no impact on network usage.
+    if (keep_alive_) {
+      port_->requests_.SendDelayed(
+          new StunBindingRequest(port_, true, server_addr_),
+          port_->stun_keepalive_delay());
+    }
+  }
+
+  virtual void OnErrorResponse(StunMessage* response) {
+    const StunErrorCodeAttribute* attr = response->GetErrorCode();
+    if (!attr) {
+      LOG(LS_ERROR) << "Bad allocate response error code";
+    } else {
+      LOG(LS_ERROR) << "Binding error response:"
+                 << " class=" << attr->eclass()
+                 << " number=" << attr->number()
+                 << " reason='" << attr->reason() << "'";
+    }
+
+    port_->OnStunBindingOrResolveRequestFailed();
+
+    if (keep_alive_
+        && (talk_base::TimeSince(start_time_) <= RETRY_TIMEOUT)) {
+      port_->requests_.SendDelayed(
+          new StunBindingRequest(port_, true, server_addr_),
+          port_->stun_keepalive_delay());
+    }
+  }
+
+  virtual void OnTimeout() {
+    LOG(LS_ERROR) << "Binding request timed out from "
+      << port_->GetLocalAddress().ToSensitiveString()
+      << " (" << port_->Network()->name() << ")";
+
+    port_->OnStunBindingOrResolveRequestFailed();
+
+    if (keep_alive_
+        && (talk_base::TimeSince(start_time_) <= RETRY_TIMEOUT)) {
+      port_->requests_.SendDelayed(
+          new StunBindingRequest(port_, true, server_addr_),
+          RETRY_DELAY);
+    }
+  }
+
+ private:
+  UDPPort* port_;
+  bool keep_alive_;
+  talk_base::SocketAddress server_addr_;
+  uint32 start_time_;
+};
+
+UDPPort::UDPPort(talk_base::Thread* thread,
+                 talk_base::Network* network,
+                 talk_base::AsyncPacketSocket* socket,
+                 const std::string& username, const std::string& password)
+    : Port(thread, network, socket->GetLocalAddress().ipaddr(),
+           username, password),
+      requests_(thread),
+      socket_(socket),
+      error_(0),
+      resolver_(NULL),
+      ready_(false),
+      stun_keepalive_delay_(KEEPALIVE_DELAY) {
+}
+
+UDPPort::UDPPort(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)
+    : Port(thread, LOCAL_PORT_TYPE, factory, network, ip, min_port, max_port,
+           username, password),
+      requests_(thread),
+      socket_(NULL),
+      error_(0),
+      resolver_(NULL),
+      ready_(false),
+      stun_keepalive_delay_(KEEPALIVE_DELAY) {
+}
+
+bool UDPPort::Init() {
+  if (!SharedSocket()) {
+    ASSERT(socket_ == NULL);
+    socket_ = socket_factory()->CreateUdpSocket(
+        talk_base::SocketAddress(ip(), 0), min_port(), max_port());
+    if (!socket_) {
+      LOG_J(LS_WARNING, this) << "UDP socket creation failed";
+      return false;
+    }
+    socket_->SignalReadPacket.connect(this, &UDPPort::OnReadPacket);
+  }
+  socket_->SignalReadyToSend.connect(this, &UDPPort::OnReadyToSend);
+  socket_->SignalAddressReady.connect(this, &UDPPort::OnLocalAddressReady);
+  requests_.SignalSendPacket.connect(this, &UDPPort::OnSendPacket);
+  return true;
+}
+
+UDPPort::~UDPPort() {
+  if (resolver_) {
+    resolver_->Destroy(false);
+  }
+  if (!SharedSocket())
+    delete socket_;
+}
+
+void UDPPort::PrepareAddress() {
+  ASSERT(requests_.empty());
+  if (socket_->GetState() == talk_base::AsyncPacketSocket::STATE_BOUND) {
+    OnLocalAddressReady(socket_, socket_->GetLocalAddress());
+  }
+}
+
+void UDPPort::MaybePrepareStunCandidate() {
+  // Sending binding request to the STUN server if address is available to
+  // prepare STUN candidate.
+  if (!server_addr_.IsNil()) {
+    SendStunBindingRequest();
+  } else {
+    // Processing host candidate address.
+    SetResult(true);
+  }
+}
+
+Connection* UDPPort::CreateConnection(const Candidate& address,
+                                       CandidateOrigin origin) {
+  if (address.protocol() != "udp")
+    return NULL;
+
+  if (!IsCompatibleAddress(address.address())) {
+    return NULL;
+  }
+
+  if (SharedSocket() && Candidates()[0].type() != LOCAL_PORT_TYPE) {
+    ASSERT(false);
+    return NULL;
+  }
+
+  Connection* conn = new ProxyConnection(this, 0, address);
+  AddConnection(conn);
+  return conn;
+}
+
+int UDPPort::SendTo(const void* data, size_t size,
+                     const talk_base::SocketAddress& addr, bool payload) {
+  int sent = socket_->SendTo(data, size, addr);
+  if (sent < 0) {
+    error_ = socket_->GetError();
+    LOG_J(LS_ERROR, this) << "UDP send of " << size
+                          << " bytes failed with error " << error_;
+  }
+  return sent;
+}
+
+int UDPPort::SetOption(talk_base::Socket::Option opt, int value) {
+  return socket_->SetOption(opt, value);
+}
+
+int UDPPort::GetOption(talk_base::Socket::Option opt, int* value) {
+  return socket_->GetOption(opt, value);
+}
+
+int UDPPort::GetError() {
+  return error_;
+}
+
+void UDPPort::OnLocalAddressReady(talk_base::AsyncPacketSocket* socket,
+                                  const talk_base::SocketAddress& address) {
+  AddAddress(address, address, UDP_PROTOCOL_NAME, LOCAL_PORT_TYPE,
+             ICE_TYPE_PREFERENCE_HOST, false);
+  MaybePrepareStunCandidate();
+}
+
+void UDPPort::OnReadPacket(talk_base::AsyncPacketSocket* socket,
+                           const char* data, size_t size,
+                           const talk_base::SocketAddress& remote_addr) {
+  ASSERT(socket == socket_);
+
+  // Look for a response from the STUN server.
+  // Even if the response doesn't match one of our outstanding requests, we
+  // will eat it because it might be a response to a retransmitted packet, and
+  // we already cleared the request when we got the first response.
+  ASSERT(!server_addr_.IsUnresolved());
+  if (remote_addr == server_addr_) {
+    requests_.CheckResponse(data, size);
+    return;
+  }
+
+  if (Connection* conn = GetConnection(remote_addr)) {
+    conn->OnReadPacket(data, size);
+  } else {
+    Port::OnReadPacket(data, size, remote_addr, PROTO_UDP);
+  }
+}
+
+void UDPPort::OnReadyToSend(talk_base::AsyncPacketSocket* socket) {
+  Port::OnReadyToSend();
+}
+
+void UDPPort::SendStunBindingRequest() {
+  // We will keep pinging the stun server to make sure our NAT pin-hole stays
+  // open during the call.
+  // TODO: Support multiple stun servers, or make ResolveStunAddress find a
+  // server with the correct family, or something similar.
+  ASSERT(requests_.empty());
+  if (server_addr_.IsUnresolved()) {
+    ResolveStunAddress();
+  } else if (socket_->GetState() == talk_base::AsyncPacketSocket::STATE_BOUND) {
+    if (server_addr_.family() == ip().family()) {
+      requests_.Send(new StunBindingRequest(this, true, server_addr_));
+    }
+  }
+}
+
+void UDPPort::ResolveStunAddress() {
+  if (resolver_)
+    return;
+
+  resolver_ = new talk_base::AsyncResolver();
+  resolver_->SignalWorkDone.connect(this, &UDPPort::OnResolveResult);
+  resolver_->set_address(server_addr_);
+  resolver_->Start();
+}
+
+void UDPPort::OnResolveResult(talk_base::SignalThread* t) {
+  ASSERT(t == resolver_);
+  if (resolver_->error() != 0) {
+    LOG_J(LS_WARNING, this) << "StunPort: stun host lookup received error "
+                            << resolver_->error();
+    OnStunBindingOrResolveRequestFailed();
+  }
+
+  server_addr_ = resolver_->address();
+  SendStunBindingRequest();
+}
+
+void UDPPort::OnStunBindingRequestSucceeded(
+    const talk_base::SocketAddress& stun_addr) {
+  if (ready_)  // Discarding the binding response if port is already enabled.
+    return;
+
+  if (!SharedSocket() || stun_addr != socket_->GetLocalAddress()) {
+    // If socket is shared and |stun_addr| is equal to local socket
+    // address then discarding the stun address.
+    // Setting related address before STUN candidate is added. For STUN
+    // related address is local socket address.
+    set_related_address(socket_->GetLocalAddress());
+    AddAddress(stun_addr, socket_->GetLocalAddress(), UDP_PROTOCOL_NAME,
+               STUN_PORT_TYPE, ICE_TYPE_PREFERENCE_PRFLX, false);
+  }
+  SetResult(true);
+}
+
+void UDPPort::OnStunBindingOrResolveRequestFailed() {
+  if (ready_)  // Discarding failure response if port is already enabled.
+    return;
+
+  // If socket is shared, we should process local udp candidate.
+  SetResult(SharedSocket());
+}
+
+void UDPPort::SetResult(bool success) {
+  // Setting ready status.
+  ready_ = true;
+  if (success) {
+    SignalPortComplete(this);
+  } else {
+    SignalPortError(this);
+  }
+}
+
+// TODO: merge this with SendTo above.
+void UDPPort::OnSendPacket(const void* data, size_t size, StunRequest* req) {
+  StunBindingRequest* sreq = static_cast<StunBindingRequest*>(req);
+  if (socket_->SendTo(data, size, sreq->server_addr()) < 0)
+    PLOG(LERROR, socket_->GetError()) << "sendto";
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/stunport.h b/talk/p2p/base/stunport.h
new file mode 100644
index 0000000..3f982d5
--- /dev/null
+++ b/talk/p2p/base/stunport.h
@@ -0,0 +1,208 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_STUNPORT_H_
+#define TALK_P2P_BASE_STUNPORT_H_
+
+#include <string>
+
+#include "talk/base/asyncpacketsocket.h"
+#include "talk/p2p/base/port.h"
+#include "talk/p2p/base/stunrequest.h"
+
+// TODO(mallinath) - Rename stunport.cc|h to udpport.cc|h.
+namespace talk_base {
+class AsyncResolver;
+class SignalThread;
+}
+
+namespace cricket {
+
+// Communicates using the address on the outside of a NAT.
+class UDPPort : public Port {
+ public:
+  static UDPPort* Create(talk_base::Thread* thread,
+                         talk_base::Network* network,
+                         talk_base::AsyncPacketSocket* socket,
+                         const std::string& username,
+                         const std::string& password) {
+    UDPPort* port = new UDPPort(thread, network, socket, username, password);
+    if (!port->Init()) {
+      delete port;
+      port = NULL;
+    }
+    return port;
+  }
+
+  static UDPPort* Create(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) {
+    UDPPort* port = new UDPPort(thread, factory, network,
+                                 ip, min_port, max_port,
+                                 username, password);
+    if (!port->Init()) {
+      delete port;
+      port = NULL;
+    }
+    return port;
+  }
+  virtual ~UDPPort();
+
+  talk_base::SocketAddress GetLocalAddress() const {
+    return socket_->GetLocalAddress();
+  }
+
+  const talk_base::SocketAddress& server_addr() const { return server_addr_; }
+  void set_server_addr(const talk_base::SocketAddress& addr) {
+    server_addr_ = addr;
+  }
+
+  virtual void PrepareAddress();
+
+  virtual Connection* CreateConnection(const Candidate& address,
+                                       CandidateOrigin origin);
+  virtual int SetOption(talk_base::Socket::Option opt, int value);
+  virtual int GetOption(talk_base::Socket::Option opt, int* value);
+  virtual int GetError();
+
+  virtual bool HandleIncomingPacket(
+      talk_base::AsyncPacketSocket* socket, const char* data, size_t size,
+      const talk_base::SocketAddress& remote_addr) {
+    // All packets given to UDP port will be consumed.
+    OnReadPacket(socket, data, size, remote_addr);
+    return true;
+  }
+
+  void set_stun_keepalive_delay(int delay) {
+    stun_keepalive_delay_ = delay;
+  }
+  int stun_keepalive_delay() const {
+    return stun_keepalive_delay_;
+  }
+
+ protected:
+  UDPPort(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);
+
+  UDPPort(talk_base::Thread* thread, talk_base::Network* network,
+          talk_base::AsyncPacketSocket* socket,
+          const std::string& username, const std::string& password);
+
+  bool Init();
+
+  virtual int SendTo(const void* data, size_t size,
+                     const talk_base::SocketAddress& addr, bool payload);
+
+  void OnLocalAddressReady(talk_base::AsyncPacketSocket* socket,
+                           const talk_base::SocketAddress& address);
+  void OnReadPacket(talk_base::AsyncPacketSocket* socket,
+                    const char* data, size_t size,
+                    const talk_base::SocketAddress& remote_addr);
+  void OnReadyToSend(talk_base::AsyncPacketSocket* socket);
+
+  // This method will send STUN binding request if STUN server address is set.
+  void MaybePrepareStunCandidate();
+
+  void SendStunBindingRequest();
+
+
+ private:
+  // DNS resolution of the STUN server.
+  void ResolveStunAddress();
+  void OnResolveResult(talk_base::SignalThread* thread);
+
+  // Below methods handles binding request responses.
+  void OnStunBindingRequestSucceeded(const talk_base::SocketAddress& stun_addr);
+  void OnStunBindingOrResolveRequestFailed();
+
+  // Sends STUN requests to the server.
+  void OnSendPacket(const void* data, size_t size, StunRequest* req);
+
+  // TODO(mallinaht) - Move this up to cricket::Port when SignalAddressReady is
+  // changed to SignalPortReady.
+  void SetResult(bool success);
+
+  talk_base::SocketAddress server_addr_;
+  StunRequestManager requests_;
+  talk_base::AsyncPacketSocket* socket_;
+  int error_;
+  talk_base::AsyncResolver* resolver_;
+  bool ready_;
+  int stun_keepalive_delay_;
+
+  friend class StunBindingRequest;
+};
+
+class StunPort : public UDPPort {
+ public:
+  static StunPort* Create(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 talk_base::SocketAddress& server_addr) {
+    StunPort* port = new StunPort(thread, factory, network,
+                                  ip, min_port, max_port,
+                                  username, password, server_addr);
+    if (!port->Init()) {
+      delete port;
+      port = NULL;
+    }
+    return port;
+  }
+
+  virtual ~StunPort() {}
+
+  virtual void PrepareAddress() {
+    SendStunBindingRequest();
+  }
+
+ protected:
+  StunPort(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 talk_base::SocketAddress& server_address)
+     : UDPPort(thread, factory, network, ip, min_port, max_port, username,
+               password) {
+    // UDPPort will set these to local udp, updating these to STUN.
+    set_type(STUN_PORT_TYPE);
+    set_server_addr(server_address);
+  }
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_STUNPORT_H_
diff --git a/talk/p2p/base/stunport_unittest.cc b/talk/p2p/base/stunport_unittest.cc
new file mode 100644
index 0000000..ba36c48
--- /dev/null
+++ b/talk/p2p/base/stunport_unittest.cc
@@ -0,0 +1,166 @@
+/*
+ * 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/helpers.h"
+#include "talk/base/socketaddress.h"
+#include "talk/p2p/base/basicpacketsocketfactory.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), 32),
+        socket_factory_(talk_base::Thread::Current()),
+        stun_server_(new cricket::TestStunServer(
+          talk_base::Thread::Current(), kStunAddr)),
+        done_(false), error_(false), stun_keepalive_delay_(0) {
+  }
+
+  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, talk_base::CreateRandomString(16),
+        talk_base::CreateRandomString(22), server_addr));
+    stun_port_->set_stun_keepalive_delay(stun_keepalive_delay_);
+    stun_port_->SignalPortComplete.connect(this,
+        &StunPortTest::OnPortComplete);
+    stun_port_->SignalPortError.connect(this,
+        &StunPortTest::OnPortError);
+  }
+
+  void PrepareAddress() {
+    stun_port_->PrepareAddress();
+  }
+
+ protected:
+  static void SetUpTestCase() {
+    // Ensure the RNG is inited.
+    talk_base::InitRandom(NULL, 0);
+  }
+
+  void OnPortComplete(cricket::Port* port) {
+    done_ = true;
+    error_ = false;
+  }
+  void OnPortError(cricket::Port* port) {
+    done_ = true;
+    error_ = true;
+  }
+  void SetKeepaliveDelay(int delay) {
+    stun_keepalive_delay_ = delay;
+  }
+
+ 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_;
+  int stun_keepalive_delay_;
+};
+
+// 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()));
+
+  // TODO: Add IPv6 tests here, once either physicalsocketserver supports
+  // IPv6, or this test is changed to use VirtualSocketServer.
+}
+
+// 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());
+}
+
+// This test verifies keepalive response messages don't result in
+// additional candidate generation.
+TEST_F(StunPortTest, TestKeepAliveResponse) {
+  SetKeepaliveDelay(500);  // 500ms of keepalive delay.
+  CreateStunPort(kStunHostnameAddr);
+  PrepareAddress();
+  EXPECT_TRUE_WAIT(done(), kTimeoutMs);
+  ASSERT_EQ(1U, port()->Candidates().size());
+  EXPECT_TRUE(kLocalAddr.EqualIPs(port()->Candidates()[0].address()));
+  // Waiting for 1 seond, which will allow us to process
+  // response for keepalive binding request. 500 ms is the keepalive delay.
+  talk_base::Thread::Current()->ProcessMessages(1000);
+  ASSERT_EQ(1U, port()->Candidates().size());
+}
+
diff --git a/talk/p2p/base/stunrequest.cc b/talk/p2p/base/stunrequest.cc
new file mode 100644
index 0000000..b3b1118
--- /dev/null
+++ b/talk/p2p/base/stunrequest.cc
@@ -0,0 +1,210 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/p2p/base/stunrequest.h"
+
+#include "talk/base/common.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+
+namespace cricket {
+
+const uint32 MSG_STUN_SEND = 1;
+
+const int MAX_SENDS = 9;
+const int DELAY_UNIT = 100;  // 100 milliseconds
+const int DELAY_MAX_FACTOR = 16;
+
+StunRequestManager::StunRequestManager(talk_base::Thread* thread)
+    : thread_(thread) {
+}
+
+StunRequestManager::~StunRequestManager() {
+  while (requests_.begin() != requests_.end()) {
+    StunRequest *request = requests_.begin()->second;
+    requests_.erase(requests_.begin());
+    delete request;
+  }
+}
+
+void StunRequestManager::Send(StunRequest* request) {
+  SendDelayed(request, 0);
+}
+
+void StunRequestManager::SendDelayed(StunRequest* request, int delay) {
+  request->set_manager(this);
+  ASSERT(requests_.find(request->id()) == requests_.end());
+  request->Construct();
+  requests_[request->id()] = request;
+  thread_->PostDelayed(delay, request, MSG_STUN_SEND, NULL);
+}
+
+void StunRequestManager::Remove(StunRequest* request) {
+  ASSERT(request->manager() == this);
+  RequestMap::iterator iter = requests_.find(request->id());
+  if (iter != requests_.end()) {
+    ASSERT(iter->second == request);
+    requests_.erase(iter);
+    thread_->Clear(request);
+  }
+}
+
+void StunRequestManager::Clear() {
+  std::vector<StunRequest*> requests;
+  for (RequestMap::iterator i = requests_.begin(); i != requests_.end(); ++i)
+    requests.push_back(i->second);
+
+  for (uint32 i = 0; i < requests.size(); ++i) {
+    // StunRequest destructor calls Remove() which deletes requests
+    // from |requests_|.
+    delete requests[i];
+  }
+}
+
+bool StunRequestManager::CheckResponse(StunMessage* msg) {
+  RequestMap::iterator iter = requests_.find(msg->transaction_id());
+  if (iter == requests_.end())
+    return false;
+
+  StunRequest* request = iter->second;
+  if (msg->type() == GetStunSuccessResponseType(request->type())) {
+    request->OnResponse(msg);
+  } else if (msg->type() == GetStunErrorResponseType(request->type())) {
+    request->OnErrorResponse(msg);
+  } else {
+    LOG(LERROR) << "Received response with wrong type: " << msg->type()
+                << " (expecting "
+                << GetStunSuccessResponseType(request->type()) << ")";
+    return false;
+  }
+
+  delete request;
+  return true;
+}
+
+bool StunRequestManager::CheckResponse(const char* data, size_t size) {
+  // Check the appropriate bytes of the stream to see if they match the
+  // transaction ID of a response we are expecting.
+
+  if (size < 20)
+    return false;
+
+  std::string id;
+  id.append(data + kStunTransactionIdOffset, kStunTransactionIdLength);
+
+  RequestMap::iterator iter = requests_.find(id);
+  if (iter == requests_.end())
+    return false;
+
+  // Parse the STUN message and continue processing as usual.
+
+  talk_base::ByteBuffer buf(data, size);
+  talk_base::scoped_ptr<StunMessage> response(iter->second->msg_->CreateNew());
+  if (!response->Read(&buf))
+    return false;
+
+  return CheckResponse(response.get());
+}
+
+StunRequest::StunRequest()
+    : count_(0), timeout_(false), manager_(0),
+      msg_(new StunMessage()), tstamp_(0) {
+  msg_->SetTransactionID(
+      talk_base::CreateRandomString(kStunTransactionIdLength));
+}
+
+StunRequest::StunRequest(StunMessage* request)
+    : count_(0), timeout_(false), manager_(0),
+      msg_(request), tstamp_(0) {
+  msg_->SetTransactionID(
+      talk_base::CreateRandomString(kStunTransactionIdLength));
+}
+
+StunRequest::~StunRequest() {
+  ASSERT(manager_ != NULL);
+  if (manager_) {
+    manager_->Remove(this);
+    manager_->thread_->Clear(this);
+  }
+  delete msg_;
+}
+
+void StunRequest::Construct() {
+  if (msg_->type() == 0) {
+    Prepare(msg_);
+    ASSERT(msg_->type() != 0);
+  }
+}
+
+int StunRequest::type() {
+  ASSERT(msg_ != NULL);
+  return msg_->type();
+}
+
+const StunMessage* StunRequest::msg() const {
+  return msg_;
+}
+
+uint32 StunRequest::Elapsed() const {
+  return talk_base::TimeSince(tstamp_);
+}
+
+
+void StunRequest::set_manager(StunRequestManager* manager) {
+  ASSERT(!manager_);
+  manager_ = manager;
+}
+
+void StunRequest::OnMessage(talk_base::Message* pmsg) {
+  ASSERT(manager_ != NULL);
+  ASSERT(pmsg->message_id == MSG_STUN_SEND);
+
+  if (timeout_) {
+    OnTimeout();
+    delete this;
+    return;
+  }
+
+  tstamp_ = talk_base::Time();
+
+  talk_base::ByteBuffer buf;
+  msg_->Write(&buf);
+  manager_->SignalSendPacket(buf.Data(), buf.Length(), this);
+
+  int delay = GetNextDelay();
+  manager_->thread_->PostDelayed(delay, this, MSG_STUN_SEND, NULL);
+}
+
+int StunRequest::GetNextDelay() {
+  int delay = DELAY_UNIT * talk_base::_min(1 << count_, DELAY_MAX_FACTOR);
+  count_ += 1;
+  if (count_ == MAX_SENDS)
+    timeout_ = true;
+  return delay;
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/stunrequest.h b/talk/p2p/base/stunrequest.h
new file mode 100644
index 0000000..f2c85b3
--- /dev/null
+++ b/talk/p2p/base/stunrequest.h
@@ -0,0 +1,133 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_STUNREQUEST_H_
+#define TALK_P2P_BASE_STUNREQUEST_H_
+
+#include "talk/base/sigslot.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/stun.h"
+#include <map>
+#include <string>
+
+namespace cricket {
+
+class StunRequest;
+
+// Manages a set of STUN requests, sending and resending until we receive a
+// response or determine that the request has timed out.
+class StunRequestManager {
+public:
+  StunRequestManager(talk_base::Thread* thread);
+  ~StunRequestManager();
+
+  // Starts sending the given request (perhaps after a delay).
+  void Send(StunRequest* request);
+  void SendDelayed(StunRequest* request, int delay);
+
+  // Removes a stun request that was added previously.  This will happen
+  // automatically when a request succeeds, fails, or times out.
+  void Remove(StunRequest* request);
+
+  // Removes all stun requests that were added previously.
+  void Clear();
+
+  // Determines whether the given message is a response to one of the
+  // outstanding requests, and if so, processes it appropriately.
+  bool CheckResponse(StunMessage* msg);
+  bool CheckResponse(const char* data, size_t size);
+
+  bool empty() { return requests_.empty(); }
+
+  // Raised when there are bytes to be sent.
+  sigslot::signal3<const void*, size_t, StunRequest*> SignalSendPacket;
+
+private:
+  typedef std::map<std::string, StunRequest*> RequestMap;
+
+  talk_base::Thread* thread_;
+  RequestMap requests_;
+
+  friend class StunRequest;
+};
+
+// Represents an individual request to be sent.  The STUN message can either be
+// constructed beforehand or built on demand.
+class StunRequest : public talk_base::MessageHandler {
+public:
+  StunRequest();
+  StunRequest(StunMessage* request);
+  virtual ~StunRequest();
+
+  // Causes our wrapped StunMessage to be Prepared
+  void Construct();
+
+  // The manager handling this request (if it has been scheduled for sending).
+  StunRequestManager* manager() { return manager_; }
+
+  // Returns the transaction ID of this request.
+  const std::string& id() { return msg_->transaction_id(); }
+
+  // Returns the STUN type of the request message.
+  int type();
+
+  // Returns a const pointer to |msg_|.
+  const StunMessage* msg() const;
+
+  // Time elapsed since last send (in ms)
+  uint32 Elapsed() const;
+
+protected:
+  int count_;
+  bool timeout_;
+
+  // Fills in a request object to be sent.  Note that request's transaction ID
+  // will already be set and cannot be changed.
+  virtual void Prepare(StunMessage* request) {}
+
+  // Called when the message receives a response or times out.
+  virtual void OnResponse(StunMessage* response) {}
+  virtual void OnErrorResponse(StunMessage* response) {}
+  virtual void OnTimeout() {}
+  virtual int GetNextDelay();
+
+private:
+  void set_manager(StunRequestManager* manager);
+
+  // Handles messages for sending and timeout.
+  void OnMessage(talk_base::Message* pmsg);
+
+  StunRequestManager* manager_;
+  StunMessage* msg_;
+  uint32 tstamp_;
+
+  friend class StunRequestManager;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_STUNREQUEST_H_
diff --git a/talk/p2p/base/stunrequest_unittest.cc b/talk/p2p/base/stunrequest_unittest.cc
new file mode 100644
index 0000000..b641585
--- /dev/null
+++ b/talk/p2p/base/stunrequest_unittest.cc
@@ -0,0 +1,222 @@
+/*
+ * 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());
+    }
+    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);
+
+  manager_.Send(new StunRequestThunker(req, this));
+  StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, req);
+  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);
+
+  manager_.Send(new StunRequestThunker(req, this));
+  StunMessage* res = CreateStunMessage(STUN_BINDING_ERROR_RESPONSE, req);
+  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);
+
+  manager_.Send(new StunRequestThunker(req, this));
+  StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, NULL);
+  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);
+
+  uint32 start = talk_base::Time();
+  manager_.Send(new StunRequestThunker(req, this));
+  StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, req);
+  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
new file mode 100644
index 0000000..05292e8
--- /dev/null
+++ b/talk/p2p/base/stunserver.cc
@@ -0,0 +1,109 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/stunserver.h"
+
+#include "talk/base/bytebuffer.h"
+#include "talk/base/logging.h"
+
+namespace cricket {
+
+StunServer::StunServer(talk_base::AsyncUDPSocket* socket) : socket_(socket) {
+  socket_->SignalReadPacket.connect(this, &StunServer::OnPacket);
+}
+
+StunServer::~StunServer() {
+  socket_->SignalReadPacket.disconnect(this);
+}
+
+void StunServer::OnPacket(
+    talk_base::AsyncPacketSocket* socket, const char* buf, size_t size,
+    const talk_base::SocketAddress& remote_addr) {
+  // Parse the STUN message; eat any messages that fail to parse.
+  talk_base::ByteBuffer bbuf(buf, size);
+  StunMessage msg;
+  if (!msg.Read(&bbuf)) {
+    return;
+  }
+
+  // TODO: If unknown non-optional (<= 0x7fff) attributes are found, send a
+  //       420 "Unknown Attribute" response.
+
+  // Send the message to the appropriate handler function.
+  switch (msg.type()) {
+    case STUN_BINDING_REQUEST:
+      OnBindingRequest(&msg, remote_addr);
+      break;
+
+    default:
+      SendErrorResponse(msg, remote_addr, 600, "Operation Not Supported");
+  }
+}
+
+void StunServer::OnBindingRequest(
+    StunMessage* msg, const talk_base::SocketAddress& remote_addr) {
+  StunMessage response;
+  response.SetType(STUN_BINDING_RESPONSE);
+  response.SetTransactionID(msg->transaction_id());
+
+  // Tell the user the address that we received their request from.
+  StunAddressAttribute* mapped_addr;
+  if (!msg->IsLegacy()) {
+    mapped_addr = StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
+  } else {
+    mapped_addr = StunAttribute::CreateXorAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+  }
+  mapped_addr->SetAddress(remote_addr);
+  response.AddAttribute(mapped_addr);
+
+  SendResponse(response, remote_addr);
+}
+
+void StunServer::SendErrorResponse(
+    const StunMessage& msg, const talk_base::SocketAddress& addr,
+    int error_code, const char* error_desc) {
+  StunMessage err_msg;
+  err_msg.SetType(GetStunErrorResponseType(msg.type()));
+  err_msg.SetTransactionID(msg.transaction_id());
+
+  StunErrorCodeAttribute* err_code = StunAttribute::CreateErrorCode();
+  err_code->SetCode(error_code);
+  err_code->SetReason(error_desc);
+  err_msg.AddAttribute(err_code);
+
+  SendResponse(err_msg, addr);
+}
+
+void StunServer::SendResponse(
+    const StunMessage& msg, const talk_base::SocketAddress& addr) {
+  talk_base::ByteBuffer buf;
+  msg.Write(&buf);
+  if (socket_->SendTo(buf.Data(), buf.Length(), addr) < 0)
+    LOG_ERR(LS_ERROR) << "sendto";
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/stunserver.h b/talk/p2p/base/stunserver.h
new file mode 100644
index 0000000..6e51ad1
--- /dev/null
+++ b/talk/p2p/base/stunserver.h
@@ -0,0 +1,77 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_STUNSERVER_H_
+#define TALK_P2P_BASE_STUNSERVER_H_
+
+#include "talk/base/asyncudpsocket.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/p2p/base/stun.h"
+
+namespace cricket {
+
+const int STUN_SERVER_PORT = 3478;
+
+class StunServer : public sigslot::has_slots<> {
+ public:
+  // Creates a STUN server, which will listen on the given socket.
+  explicit StunServer(talk_base::AsyncUDPSocket* socket);
+  // Removes the STUN server from the socket and deletes the socket.
+  ~StunServer();
+
+ protected:
+  // Slot for AsyncSocket.PacketRead:
+  void OnPacket(
+      talk_base::AsyncPacketSocket* socket, const char* buf, size_t size,
+      const talk_base::SocketAddress& remote_addr);
+
+  // Handlers for the different types of STUN/TURN requests:
+  void OnBindingRequest(StunMessage* msg,
+      const talk_base::SocketAddress& addr);
+  void OnAllocateRequest(StunMessage* msg,
+      const talk_base::SocketAddress& addr);
+  void OnSharedSecretRequest(StunMessage* msg,
+      const talk_base::SocketAddress& addr);
+  void OnSendRequest(StunMessage* msg,
+      const talk_base::SocketAddress& addr);
+
+  // Sends an error response to the given message back to the user.
+  void SendErrorResponse(
+      const StunMessage& msg, const talk_base::SocketAddress& addr,
+      int error_code, const char* error_desc);
+
+  // Sends the given message to the appropriate destination.
+  void SendResponse(const StunMessage& msg,
+       const talk_base::SocketAddress& addr);
+
+ private:
+  talk_base::scoped_ptr<talk_base::AsyncUDPSocket> socket_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_STUNSERVER_H_
diff --git a/talk/p2p/base/stunserver_main.cc b/talk/p2p/base/stunserver_main.cc
new file mode 100644
index 0000000..e697728
--- /dev/null
+++ b/talk/p2p/base/stunserver_main.cc
@@ -0,0 +1,69 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef POSIX
+#include <errno.h>
+#endif  // POSIX
+
+#include <iostream>
+
+#include "talk/base/host.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/stunserver.h"
+
+using namespace cricket;
+
+int main(int argc, char* argv[]) {
+  if (argc != 2) {
+    std::cerr << "usage: stunserver address" << std::endl;
+    return 1;
+  }
+
+  talk_base::SocketAddress server_addr;
+  if (!server_addr.FromString(argv[1])) {
+    std::cerr << "Unable to parse IP address: " << argv[1];
+    return 1;
+  }
+
+  talk_base::Thread *pthMain = talk_base::Thread::Current();
+
+  talk_base::AsyncUDPSocket* server_socket =
+      talk_base::AsyncUDPSocket::Create(pthMain->socketserver(), server_addr);
+  if (!server_socket) {
+    std::cerr << "Failed to create a UDP socket" << std::endl;
+    return 1;
+  }
+
+  StunServer* server = new StunServer(server_socket);
+
+  std::cout << "Listening at " << server_addr.ToString() << std::endl;
+
+  pthMain->Run();
+
+  delete server;
+  return 0;
+}
diff --git a/talk/p2p/base/stunserver_unittest.cc b/talk/p2p/base/stunserver_unittest.cc
new file mode 100644
index 0000000..1d2cd0d
--- /dev/null
+++ b/talk/p2p/base/stunserver_unittest.cc
@@ -0,0 +1,120 @@
+/*
+ * 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);
+}
diff --git a/talk/p2p/base/tcpport.cc b/talk/p2p/base/tcpport.cc
new file mode 100644
index 0000000..356dd67
--- /dev/null
+++ b/talk/p2p/base/tcpport.cc
@@ -0,0 +1,307 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/p2p/base/tcpport.h"
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/p2p/base/common.h"
+
+namespace cricket {
+
+TCPPort::TCPPort(talk_base::Thread* thread,
+                 talk_base::PacketSocketFactory* factory,
+                 talk_base::Network* network, const talk_base::IPAddress& ip,
+                 int min_port, int max_port, const std::string& username,
+                 const std::string& password, bool allow_listen)
+    : Port(thread, LOCAL_PORT_TYPE, factory, network, ip, min_port, max_port,
+           username, password),
+      incoming_only_(false),
+      allow_listen_(allow_listen),
+      socket_(NULL),
+      error_(0) {
+  // TODO(mallinath) - Set preference value as per RFC 6544.
+  // http://b/issue?id=7141794
+}
+
+bool TCPPort::Init() {
+  if (allow_listen_) {
+    // Treat failure to create or bind a TCP socket as fatal.  This
+    // should never happen.
+    socket_ = socket_factory()->CreateServerTcpSocket(
+        talk_base::SocketAddress(ip(), 0), min_port(), max_port(),
+        false /* ssl */);
+    if (!socket_) {
+      LOG_J(LS_ERROR, this) << "TCP socket creation failed.";
+      return false;
+    }
+    socket_->SignalNewConnection.connect(this, &TCPPort::OnNewConnection);
+    socket_->SignalAddressReady.connect(this, &TCPPort::OnAddressReady);
+  }
+  return true;
+}
+
+TCPPort::~TCPPort() {
+  delete socket_;
+}
+
+Connection* TCPPort::CreateConnection(const Candidate& address,
+                                      CandidateOrigin origin) {
+  // We only support TCP protocols
+  if ((address.protocol() != TCP_PROTOCOL_NAME) &&
+      (address.protocol() != SSLTCP_PROTOCOL_NAME)) {
+    return NULL;
+  }
+
+  // We can't accept TCP connections incoming on other ports
+  if (origin == ORIGIN_OTHER_PORT)
+    return NULL;
+
+  // Check if we are allowed to make outgoing TCP connections
+  if (incoming_only_ && (origin == ORIGIN_MESSAGE))
+    return NULL;
+
+  // We don't know how to act as an ssl server yet
+  if ((address.protocol() == SSLTCP_PROTOCOL_NAME) &&
+      (origin == ORIGIN_THIS_PORT)) {
+    return NULL;
+  }
+
+  if (!IsCompatibleAddress(address.address())) {
+    return NULL;
+  }
+
+  TCPConnection* conn = NULL;
+  if (talk_base::AsyncPacketSocket* socket =
+      GetIncoming(address.address(), true)) {
+    socket->SignalReadPacket.disconnect(this);
+    conn = new TCPConnection(this, address, socket);
+  } else {
+    conn = new TCPConnection(this, address);
+  }
+  AddConnection(conn);
+  return conn;
+}
+
+void TCPPort::PrepareAddress() {
+  if (socket_) {
+    // If socket isn't bound yet the address will be added in
+    // OnAddressReady(). Socket may be in the CLOSED state if Listen()
+    // failed, we still want ot add the socket address.
+    LOG(LS_VERBOSE) << "Preparing TCP address, current state: "
+                    << socket_->GetState();
+    if (socket_->GetState() == talk_base::AsyncPacketSocket::STATE_BOUND ||
+        socket_->GetState() == talk_base::AsyncPacketSocket::STATE_CLOSED)
+      AddAddress(socket_->GetLocalAddress(), socket_->GetLocalAddress(),
+                 TCP_PROTOCOL_NAME, LOCAL_PORT_TYPE,
+                 ICE_TYPE_PREFERENCE_HOST_TCP, true);
+  } else {
+    LOG_J(LS_INFO, this) << "Not listening due to firewall restrictions.";
+    // Sending error signal as we can't allocate tcp candidate.
+    SignalPortError(this);
+  }
+}
+
+int TCPPort::SendTo(const void* data, size_t size,
+                    const talk_base::SocketAddress& addr, bool payload) {
+  talk_base::AsyncPacketSocket * socket = NULL;
+  if (TCPConnection * conn = static_cast<TCPConnection*>(GetConnection(addr))) {
+    socket = conn->socket();
+  } else {
+    socket = GetIncoming(addr);
+  }
+  if (!socket) {
+    LOG_J(LS_ERROR, this) << "Attempted to send to an unknown destination, "
+                          << addr.ToSensitiveString();
+    return -1;  // TODO: Set error_
+  }
+
+  int sent = socket->Send(data, size);
+  if (sent < 0) {
+    error_ = socket->GetError();
+    LOG_J(LS_ERROR, this) << "TCP send of " << size
+                          << " bytes failed with error " << error_;
+  }
+  return sent;
+}
+
+int TCPPort::GetOption(talk_base::Socket::Option opt, int* value) {
+  if (socket_) {
+    return socket_->GetOption(opt, value);
+  } else {
+    return SOCKET_ERROR;
+  }
+}
+
+int TCPPort::SetOption(talk_base::Socket::Option opt, int value) {
+  if (socket_) {
+    return socket_->SetOption(opt, value);
+  } else {
+    return SOCKET_ERROR;
+  }
+}
+
+int TCPPort::GetError() {
+  return error_;
+}
+
+void TCPPort::OnNewConnection(talk_base::AsyncPacketSocket* socket,
+                              talk_base::AsyncPacketSocket* new_socket) {
+  ASSERT(socket == socket_);
+
+  Incoming incoming;
+  incoming.addr = new_socket->GetRemoteAddress();
+  incoming.socket = new_socket;
+  incoming.socket->SignalReadPacket.connect(this, &TCPPort::OnReadPacket);
+  incoming.socket->SignalReadyToSend.connect(this, &TCPPort::OnReadyToSend);
+
+  LOG_J(LS_VERBOSE, this) << "Accepted connection from "
+                          << incoming.addr.ToSensitiveString();
+  incoming_.push_back(incoming);
+}
+
+talk_base::AsyncPacketSocket* TCPPort::GetIncoming(
+    const talk_base::SocketAddress& addr, bool remove) {
+  talk_base::AsyncPacketSocket* socket = NULL;
+  for (std::list<Incoming>::iterator it = incoming_.begin();
+       it != incoming_.end(); ++it) {
+    if (it->addr == addr) {
+      socket = it->socket;
+      if (remove)
+        incoming_.erase(it);
+      break;
+    }
+  }
+  return socket;
+}
+
+void TCPPort::OnReadPacket(talk_base::AsyncPacketSocket* socket,
+                           const char* data, size_t size,
+                           const talk_base::SocketAddress& remote_addr) {
+  Port::OnReadPacket(data, size, remote_addr, PROTO_TCP);
+}
+
+void TCPPort::OnReadyToSend(talk_base::AsyncPacketSocket* socket) {
+  Port::OnReadyToSend();
+}
+
+void TCPPort::OnAddressReady(talk_base::AsyncPacketSocket* socket,
+                             const talk_base::SocketAddress& address) {
+  AddAddress(address, address, "tcp",
+             LOCAL_PORT_TYPE, ICE_TYPE_PREFERENCE_HOST_TCP,
+             true);
+}
+
+TCPConnection::TCPConnection(TCPPort* port, const Candidate& candidate,
+                             talk_base::AsyncPacketSocket* socket)
+    : Connection(port, 0, candidate), socket_(socket), error_(0) {
+  bool outgoing = (socket_ == NULL);
+  if (outgoing) {
+    // TODO: Handle failures here (unlikely since TCP).
+    int opts = (candidate.protocol() == SSLTCP_PROTOCOL_NAME) ?
+        talk_base::PacketSocketFactory::OPT_SSLTCP : 0;
+    socket_ = port->socket_factory()->CreateClientTcpSocket(
+        talk_base::SocketAddress(port_->Network()->ip(), 0),
+        candidate.address(), port->proxy(), port->user_agent(), opts);
+    if (socket_) {
+      LOG_J(LS_VERBOSE, this) << "Connecting from "
+                              << socket_->GetLocalAddress().ToSensitiveString()
+                              << " to "
+                              << candidate.address().ToSensitiveString();
+      set_connected(false);
+      socket_->SignalConnect.connect(this, &TCPConnection::OnConnect);
+    } else {
+      LOG_J(LS_WARNING, this) << "Failed to create connection to "
+                              << candidate.address().ToSensitiveString();
+    }
+  } else {
+    // Incoming connections should match the network address.
+    ASSERT(socket_->GetLocalAddress().ipaddr() == port->ip());
+  }
+
+  if (socket_) {
+    socket_->SignalReadPacket.connect(this, &TCPConnection::OnReadPacket);
+    socket_->SignalReadyToSend.connect(this, &TCPConnection::OnReadyToSend);
+    socket_->SignalClose.connect(this, &TCPConnection::OnClose);
+  }
+}
+
+TCPConnection::~TCPConnection() {
+  delete socket_;
+}
+
+int TCPConnection::Send(const void* data, size_t size) {
+  if (!socket_) {
+    error_ = ENOTCONN;
+    return SOCKET_ERROR;
+  }
+
+  if (write_state() != STATE_WRITABLE) {
+    // TODO: Should STATE_WRITE_TIMEOUT return a non-blocking error?
+    error_ = EWOULDBLOCK;
+    return SOCKET_ERROR;
+  }
+  int sent = socket_->Send(data, size);
+  if (sent < 0) {
+    error_ = socket_->GetError();
+  } else {
+    send_rate_tracker_.Update(sent);
+  }
+  return sent;
+}
+
+int TCPConnection::GetError() {
+  return error_;
+}
+
+void TCPConnection::OnConnect(talk_base::AsyncPacketSocket* socket) {
+  ASSERT(socket == socket_);
+  LOG_J(LS_VERBOSE, this) << "Connection established to "
+                          << socket->GetRemoteAddress().ToSensitiveString();
+  set_connected(true);
+}
+
+void TCPConnection::OnClose(talk_base::AsyncPacketSocket* socket, int error) {
+  ASSERT(socket == socket_);
+  LOG_J(LS_VERBOSE, this) << "Connection closed with error " << error;
+  set_connected(false);
+  set_write_state(STATE_WRITE_TIMEOUT);
+}
+
+void TCPConnection::OnReadPacket(talk_base::AsyncPacketSocket* socket,
+                                 const char* data, size_t size,
+                                 const talk_base::SocketAddress& remote_addr) {
+  ASSERT(socket == socket_);
+  Connection::OnReadPacket(data, size);
+}
+
+void TCPConnection::OnReadyToSend(talk_base::AsyncPacketSocket* socket) {
+  ASSERT(socket == socket_);
+  Connection::OnReadyToSend();
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/tcpport.h b/talk/p2p/base/tcpport.h
new file mode 100644
index 0000000..8136176
--- /dev/null
+++ b/talk/p2p/base/tcpport.h
@@ -0,0 +1,148 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_TCPPORT_H_
+#define TALK_P2P_BASE_TCPPORT_H_
+
+#include <string>
+#include <list>
+#include "talk/base/asyncpacketsocket.h"
+#include "talk/p2p/base/port.h"
+
+namespace cricket {
+
+class TCPConnection;
+
+// Communicates using a local TCP port.
+//
+// This class is designed to allow subclasses to take advantage of the
+// connection management provided by this class.  A subclass should take of all
+// packet sending and preparation, but when a packet is received, it should
+// call this TCPPort::OnReadPacket (3 arg) to dispatch to a connection.
+class TCPPort : public Port {
+ public:
+  static TCPPort* Create(talk_base::Thread* thread,
+                         talk_base::PacketSocketFactory* factory,
+                         talk_base::Network* network,
+                         const talk_base::IPAddress& ip,
+                         int min_port, int max_port,
+                         const std::string& username,
+                         const std::string& password,
+                         bool allow_listen) {
+    TCPPort* port = new TCPPort(thread, factory, network,
+                                ip, min_port, max_port,
+                                username, password, allow_listen);
+    if (!port->Init()) {
+      delete port;
+      port = NULL;
+    }
+    return port;
+  }
+  virtual ~TCPPort();
+
+  virtual Connection* CreateConnection(const Candidate& address,
+                                       CandidateOrigin origin);
+
+  virtual void PrepareAddress();
+
+  virtual int GetOption(talk_base::Socket::Option opt, int* value);
+  virtual int SetOption(talk_base::Socket::Option opt, int value);
+  virtual int GetError();
+
+ protected:
+  TCPPort(talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
+          talk_base::Network* network, const talk_base::IPAddress& ip,
+          int min_port, int max_port, const std::string& username,
+          const std::string& password, bool allow_listen);
+  bool Init();
+
+  // Handles sending using the local TCP socket.
+  virtual int SendTo(const void* data, size_t size,
+                     const talk_base::SocketAddress& addr, bool payload);
+
+  // Accepts incoming TCP connection.
+  void OnNewConnection(talk_base::AsyncPacketSocket* socket,
+                       talk_base::AsyncPacketSocket* new_socket);
+
+ private:
+  struct Incoming {
+    talk_base::SocketAddress addr;
+    talk_base::AsyncPacketSocket* socket;
+  };
+
+  talk_base::AsyncPacketSocket* GetIncoming(
+      const talk_base::SocketAddress& addr, bool remove = false);
+
+  // Receives packet signal from the local TCP Socket.
+  void OnReadPacket(talk_base::AsyncPacketSocket* socket,
+                    const char* data, size_t size,
+                    const talk_base::SocketAddress& remote_addr);
+
+  void OnReadyToSend(talk_base::AsyncPacketSocket* socket);
+
+  void OnAddressReady(talk_base::AsyncPacketSocket* socket,
+                      const talk_base::SocketAddress& address);
+
+  // TODO: Is this still needed?
+  bool incoming_only_;
+  bool allow_listen_;
+  talk_base::AsyncPacketSocket* socket_;
+  int error_;
+  std::list<Incoming> incoming_;
+
+  friend class TCPConnection;
+};
+
+class TCPConnection : public Connection {
+ public:
+  // Connection is outgoing unless socket is specified
+  TCPConnection(TCPPort* port, const Candidate& candidate,
+                talk_base::AsyncPacketSocket* socket = 0);
+  virtual ~TCPConnection();
+
+  virtual int Send(const void* data, size_t size);
+  virtual int GetError();
+
+  talk_base::AsyncPacketSocket* socket() { return socket_; }
+
+ private:
+  void OnConnect(talk_base::AsyncPacketSocket* socket);
+  void OnClose(talk_base::AsyncPacketSocket* socket, int error);
+  void OnReadPacket(talk_base::AsyncPacketSocket* socket,
+                    const char* data, size_t size,
+                    const talk_base::SocketAddress& remote_addr);
+  void OnReadyToSend(talk_base::AsyncPacketSocket* socket);
+
+  talk_base::AsyncPacketSocket* socket_;
+  int error_;
+
+  friend class TCPPort;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_TCPPORT_H_
diff --git a/talk/p2p/base/testrelayserver.h b/talk/p2p/base/testrelayserver.h
new file mode 100644
index 0000000..29e9fe4
--- /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(addr.family(), 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..67bac21
--- /dev/null
+++ b/talk/p2p/base/teststunserver.h
@@ -0,0 +1,55 @@
+/*
+ * 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(addr.family(),
+                                                          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/testturnserver.h b/talk/p2p/base/testturnserver.h
new file mode 100644
index 0000000..32251c8
--- /dev/null
+++ b/talk/p2p/base/testturnserver.h
@@ -0,0 +1,78 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_TESTTURNSERVER_H_
+#define TALK_P2P_BASE_TESTTURNSERVER_H_
+
+#include <string>
+
+#include "talk/base/asyncudpsocket.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/basicpacketsocketfactory.h"
+#include "talk/p2p/base/stun.h"
+#include "talk/p2p/base/turnserver.h"
+
+namespace cricket {
+
+static const char kTestRealm[] = "example.org";
+static const char kTestSoftware[] = "TestTurnServer";
+
+class TestTurnServer : public TurnAuthInterface {
+ public:
+  TestTurnServer(talk_base::Thread* thread,
+                 const talk_base::SocketAddress& udp_int_addr,
+                 const talk_base::SocketAddress& udp_ext_addr)
+      : server_(thread) {
+    server_.AddInternalSocket(talk_base::AsyncUDPSocket::Create(
+        thread->socketserver(), udp_int_addr), PROTO_UDP);
+    server_.SetExternalSocketFactory(new talk_base::BasicPacketSocketFactory(),
+        udp_ext_addr);
+    server_.set_realm(kTestRealm);
+    server_.set_software(kTestSoftware);
+    server_.set_auth_hook(this);
+  }
+
+  void set_enable_otu_nonce(bool enable) {
+    server_.set_enable_otu_nonce(enable);
+  }
+
+  TurnServer* server() { return &server_; }
+
+ private:
+  // For this test server, succeed if the password is the same as the username.
+  // Obviously, do not use this in a production environment.
+  virtual bool GetKey(const std::string& username, const std::string& realm,
+                      std::string* key) {
+    return ComputeStunCredentialHash(username, realm, username, key);
+  }
+
+  TurnServer server_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_TESTTURNSERVER_H_
diff --git a/talk/p2p/base/transport.cc b/talk/p2p/base/transport.cc
new file mode 100644
index 0000000..d2b7965
--- /dev/null
+++ b/talk/p2p/base/transport.cc
@@ -0,0 +1,838 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/p2p/base/transport.h"
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/base/parsing.h"
+#include "talk/p2p/base/transportchannelimpl.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+
+namespace cricket {
+
+enum {
+  MSG_CREATECHANNEL = 1,
+  MSG_DESTROYCHANNEL = 2,
+  MSG_DESTROYALLCHANNELS = 3,
+  MSG_CONNECTCHANNELS = 4,
+  MSG_RESETCHANNELS = 5,
+  MSG_ONSIGNALINGREADY = 6,
+  MSG_ONREMOTECANDIDATE = 7,
+  MSG_READSTATE = 8,
+  MSG_WRITESTATE = 9,
+  MSG_REQUESTSIGNALING = 10,
+  MSG_CANDIDATEREADY = 11,
+  MSG_ROUTECHANGE = 12,
+  MSG_CONNECTING = 13,
+  MSG_CANDIDATEALLOCATIONCOMPLETE = 14,
+  MSG_ROLECONFLICT = 15,
+  MSG_SETROLE = 16,
+  MSG_SETLOCALDESCRIPTION = 17,
+  MSG_SETREMOTEDESCRIPTION = 18,
+  MSG_GETSTATS = 19
+};
+
+struct ChannelParams : public talk_base::MessageData {
+  ChannelParams() : channel(NULL), candidate(NULL) {}
+  explicit ChannelParams(int component)
+      : component(component), channel(NULL), candidate(NULL) {}
+  explicit ChannelParams(Candidate* candidate)
+      : channel(NULL), candidate(candidate) {
+  }
+
+  ~ChannelParams() {
+    delete candidate;
+  }
+
+  std::string name;
+  int component;
+  TransportChannelImpl* channel;
+  Candidate* candidate;
+};
+
+struct TransportDescriptionParams : public talk_base::MessageData {
+  TransportDescriptionParams(const TransportDescription& desc,
+                             ContentAction action)
+      : desc(desc), action(action), result(false) {}
+  const TransportDescription& desc;
+  ContentAction action;
+  bool result;
+};
+
+struct TransportRoleParam : public talk_base::MessageData {
+  explicit TransportRoleParam(TransportRole role) : role(role) {}
+
+  TransportRole role;
+};
+
+struct StatsParam : public talk_base::MessageData {
+  explicit StatsParam(TransportStats* stats)
+      : stats(stats), result(false) {}
+
+  TransportStats* stats;
+  bool result;
+};
+
+Transport::Transport(talk_base::Thread* signaling_thread,
+                     talk_base::Thread* worker_thread,
+                     const std::string& content_name,
+                     const std::string& type,
+                     PortAllocator* allocator)
+  : signaling_thread_(signaling_thread),
+    worker_thread_(worker_thread),
+    content_name_(content_name),
+    type_(type),
+    allocator_(allocator),
+    destroyed_(false),
+    readable_(TRANSPORT_STATE_NONE),
+    writable_(TRANSPORT_STATE_NONE),
+    was_writable_(false),
+    connect_requested_(false),
+    role_(ROLE_UNKNOWN),
+    tiebreaker_(0),
+    protocol_(ICEPROTO_HYBRID),
+    remote_ice_mode_(ICEMODE_FULL) {
+}
+
+Transport::~Transport() {
+  ASSERT(signaling_thread_->IsCurrent());
+  ASSERT(destroyed_);
+}
+
+void Transport::SetRole(TransportRole role) {
+  TransportRoleParam param(role);
+  worker_thread()->Send(this, MSG_SETROLE, &param);
+}
+
+bool Transport::SetLocalTransportDescription(
+    const TransportDescription& description, ContentAction action) {
+  TransportDescriptionParams params(description, action);
+  worker_thread()->Send(this, MSG_SETLOCALDESCRIPTION, &params);
+  return params.result;
+}
+
+bool Transport::SetRemoteTransportDescription(
+    const TransportDescription& description, ContentAction action) {
+  TransportDescriptionParams params(description, action);
+  worker_thread()->Send(this, MSG_SETREMOTEDESCRIPTION, &params);
+  return params.result;
+}
+
+TransportChannelImpl* Transport::CreateChannel(int component) {
+  ChannelParams params(component);
+  worker_thread()->Send(this, MSG_CREATECHANNEL, &params);
+  return params.channel;
+}
+
+TransportChannelImpl* Transport::CreateChannel_w(int component) {
+  ASSERT(worker_thread()->IsCurrent());
+  TransportChannelImpl *impl;
+  talk_base::CritScope cs(&crit_);
+
+  // Create the entry if it does not exist.
+  bool impl_exists = false;
+  if (channels_.find(component) == channels_.end()) {
+    impl = CreateTransportChannel(component);
+    channels_[component] = ChannelMapEntry(impl);
+  } else {
+    impl = channels_[component].get();
+    impl_exists = true;
+  }
+
+  // Increase the ref count.
+  channels_[component].AddRef();
+  destroyed_ = false;
+
+  if (impl_exists) {
+    // If this is an existing channel, we should just return it without
+    // connecting to all the signal again.
+    return impl;
+  }
+
+  // Push down our transport state to the new channel.
+  impl->SetRole(role_);
+  impl->SetTiebreaker(tiebreaker_);
+  if (local_description_) {
+    ApplyLocalTransportDescription_w(impl);
+    if (remote_description_) {
+      ApplyRemoteTransportDescription_w(impl);
+      ApplyNegotiatedTransportDescription_w(impl);
+    }
+  }
+
+  impl->SignalReadableState.connect(this, &Transport::OnChannelReadableState);
+  impl->SignalWritableState.connect(this, &Transport::OnChannelWritableState);
+  impl->SignalRequestSignaling.connect(
+      this, &Transport::OnChannelRequestSignaling);
+  impl->SignalCandidateReady.connect(this, &Transport::OnChannelCandidateReady);
+  impl->SignalRouteChange.connect(this, &Transport::OnChannelRouteChange);
+  impl->SignalCandidatesAllocationDone.connect(
+      this, &Transport::OnChannelCandidatesAllocationDone);
+  impl->SignalRoleConflict.connect(this, &Transport::OnRoleConflict);
+
+  if (connect_requested_) {
+    impl->Connect();
+    if (channels_.size() == 1) {
+      // If this is the first channel, then indicate that we have started
+      // connecting.
+      signaling_thread()->Post(this, MSG_CONNECTING, NULL);
+    }
+  }
+  return impl;
+}
+
+TransportChannelImpl* Transport::GetChannel(int component) {
+  talk_base::CritScope cs(&crit_);
+  ChannelMap::iterator iter = channels_.find(component);
+  return (iter != channels_.end()) ? iter->second.get() : NULL;
+}
+
+bool Transport::HasChannels() {
+  talk_base::CritScope cs(&crit_);
+  return !channels_.empty();
+}
+
+void Transport::DestroyChannel(int component) {
+  ChannelParams params(component);
+  worker_thread()->Send(this, MSG_DESTROYCHANNEL, &params);
+}
+
+void Transport::DestroyChannel_w(int component) {
+  ASSERT(worker_thread()->IsCurrent());
+
+  TransportChannelImpl* impl = NULL;
+  {
+    talk_base::CritScope cs(&crit_);
+    ChannelMap::iterator iter = channels_.find(component);
+    if (iter == channels_.end())
+      return;
+
+    iter->second.DecRef();
+    if (!iter->second.ref()) {
+      impl = iter->second.get();
+      channels_.erase(iter);
+    }
+  }
+
+  if (connect_requested_ && channels_.empty()) {
+    // We're no longer attempting to connect.
+    signaling_thread()->Post(this, MSG_CONNECTING, NULL);
+  }
+
+  if (impl) {
+    // Check in case the deleted channel was the only non-writable channel.
+    OnChannelWritableState(impl);
+    DestroyTransportChannel(impl);
+  }
+}
+
+void Transport::ConnectChannels() {
+  ASSERT(signaling_thread()->IsCurrent());
+  worker_thread()->Send(this, MSG_CONNECTCHANNELS, NULL);
+}
+
+void Transport::ConnectChannels_w() {
+  ASSERT(worker_thread()->IsCurrent());
+  if (connect_requested_ || channels_.empty())
+    return;
+  connect_requested_ = true;
+  signaling_thread()->Post(
+      this, MSG_CANDIDATEREADY, NULL);
+
+  if (!local_description_) {
+    // TOOD(mallinath) : TransportDescription(TD) shouldn't be generated here.
+    // As Transport must know TD is offer or answer and cricket::Transport
+    // doesn't have the capability to decide it. This should be set by the
+    // Session.
+    // Session must generate local TD before remote candidates pushed when
+    // initiate request initiated by the remote.
+    LOG(LS_INFO) << "Transport::ConnectChannels_w: No local description has "
+                 << "been set. Will generate one.";
+    TransportDescription desc(NS_GINGLE_P2P, std::vector<std::string>(),
+                              talk_base::CreateRandomString(ICE_UFRAG_LENGTH),
+                              talk_base::CreateRandomString(ICE_PWD_LENGTH),
+                              ICEMODE_FULL, NULL, Candidates());
+    SetLocalTransportDescription_w(desc, CA_OFFER);
+  }
+
+  CallChannels_w(&TransportChannelImpl::Connect);
+  if (!channels_.empty()) {
+    signaling_thread()->Post(this, MSG_CONNECTING, NULL);
+  }
+}
+
+void Transport::OnConnecting_s() {
+  ASSERT(signaling_thread()->IsCurrent());
+  SignalConnecting(this);
+}
+
+void Transport::DestroyAllChannels() {
+  ASSERT(signaling_thread()->IsCurrent());
+  worker_thread()->Send(this, MSG_DESTROYALLCHANNELS, NULL);
+  worker_thread()->Clear(this);
+  signaling_thread()->Clear(this);
+  destroyed_ = true;
+}
+
+void Transport::DestroyAllChannels_w() {
+  ASSERT(worker_thread()->IsCurrent());
+  std::vector<TransportChannelImpl*> impls;
+  {
+    talk_base::CritScope cs(&crit_);
+    for (ChannelMap::iterator iter = channels_.begin();
+         iter != channels_.end();
+         ++iter) {
+      iter->second.DecRef();
+      if (!iter->second.ref())
+        impls.push_back(iter->second.get());
+      }
+    }
+  channels_.clear();
+
+
+  for (size_t i = 0; i < impls.size(); ++i)
+    DestroyTransportChannel(impls[i]);
+}
+
+void Transport::ResetChannels() {
+  ASSERT(signaling_thread()->IsCurrent());
+  worker_thread()->Send(this, MSG_RESETCHANNELS, NULL);
+}
+
+void Transport::ResetChannels_w() {
+  ASSERT(worker_thread()->IsCurrent());
+
+  // We are no longer attempting to connect
+  connect_requested_ = false;
+
+  // Clear out the old messages, they aren't relevant
+  talk_base::CritScope cs(&crit_);
+  ready_candidates_.clear();
+
+  // Reset all of the channels
+  CallChannels_w(&TransportChannelImpl::Reset);
+}
+
+void Transport::OnSignalingReady() {
+  ASSERT(signaling_thread()->IsCurrent());
+  if (destroyed_) return;
+
+  worker_thread()->Post(this, MSG_ONSIGNALINGREADY, NULL);
+
+  // Notify the subclass.
+  OnTransportSignalingReady();
+}
+
+void Transport::CallChannels_w(TransportChannelFunc func) {
+  ASSERT(worker_thread()->IsCurrent());
+  talk_base::CritScope cs(&crit_);
+  for (ChannelMap::iterator iter = channels_.begin();
+       iter != channels_.end();
+       ++iter) {
+    ((iter->second.get())->*func)();
+  }
+}
+
+bool Transport::VerifyCandidate(const Candidate& cand, std::string* error) {
+  // No address zero.
+  if (cand.address().IsNil() || cand.address().IsAny()) {
+    *error = "candidate has address of zero";
+    return false;
+  }
+
+  // Disallow all ports below 1024, except for 80 and 443 on public addresses.
+  int port = cand.address().port();
+  if (port < 1024) {
+    if ((port != 80) && (port != 443)) {
+      *error = "candidate has port below 1024, but not 80 or 443";
+      return false;
+    }
+
+    if (cand.address().IsPrivateIP()) {
+      *error = "candidate has port of 80 or 443 with private IP address";
+      return false;
+    }
+  }
+
+  return true;
+}
+
+
+bool Transport::GetStats(TransportStats* stats) {
+  ASSERT(signaling_thread()->IsCurrent());
+  StatsParam params(stats);
+  worker_thread()->Send(this, MSG_GETSTATS, &params);
+  return params.result;
+}
+
+bool Transport::GetStats_w(TransportStats* stats) {
+  ASSERT(worker_thread()->IsCurrent());
+  stats->content_name = content_name();
+  stats->channel_stats.clear();
+  for (ChannelMap::iterator iter = channels_.begin();
+       iter != channels_.end();
+       ++iter) {
+    TransportChannelStats substats;
+    substats.component = iter->second->component();
+    if (!iter->second->GetStats(&substats.connection_infos)) {
+      return false;
+    }
+    stats->channel_stats.push_back(substats);
+  }
+  return true;
+}
+
+void Transport::OnRemoteCandidates(const std::vector<Candidate>& candidates) {
+  for (std::vector<Candidate>::const_iterator iter = candidates.begin();
+       iter != candidates.end();
+       ++iter) {
+    OnRemoteCandidate(*iter);
+  }
+}
+
+void Transport::OnRemoteCandidate(const Candidate& candidate) {
+  ASSERT(signaling_thread()->IsCurrent());
+  if (destroyed_) return;
+
+  if (!HasChannel(candidate.component())) {
+    LOG(LS_WARNING) << "Ignoring candidate for unknown component "
+                    << candidate.component();
+    return;
+  }
+
+  ChannelParams* params = new ChannelParams(new Candidate(candidate));
+  worker_thread()->Post(this, MSG_ONREMOTECANDIDATE, params);
+}
+
+void Transport::OnRemoteCandidate_w(const Candidate& candidate) {
+  ASSERT(worker_thread()->IsCurrent());
+  ChannelMap::iterator iter = channels_.find(candidate.component());
+  // It's ok for a channel to go away while this message is in transit.
+  if (iter != channels_.end()) {
+    iter->second->OnCandidate(candidate);
+  }
+}
+
+void Transport::OnChannelReadableState(TransportChannel* channel) {
+  ASSERT(worker_thread()->IsCurrent());
+  signaling_thread()->Post(this, MSG_READSTATE, NULL);
+}
+
+void Transport::OnChannelReadableState_s() {
+  ASSERT(signaling_thread()->IsCurrent());
+  TransportState readable = GetTransportState_s(true);
+  if (readable_ != readable) {
+    readable_ = readable;
+    SignalReadableState(this);
+  }
+}
+
+void Transport::OnChannelWritableState(TransportChannel* channel) {
+  ASSERT(worker_thread()->IsCurrent());
+  signaling_thread()->Post(this, MSG_WRITESTATE, NULL);
+}
+
+void Transport::OnChannelWritableState_s() {
+  ASSERT(signaling_thread()->IsCurrent());
+  TransportState writable = GetTransportState_s(false);
+  if (writable_ != writable) {
+    was_writable_ = (writable_ == TRANSPORT_STATE_ALL);
+    writable_ = writable;
+    SignalWritableState(this);
+  }
+}
+
+TransportState Transport::GetTransportState_s(bool read) {
+  ASSERT(signaling_thread()->IsCurrent());
+  talk_base::CritScope cs(&crit_);
+  bool any = false;
+  bool all = !channels_.empty();
+  for (ChannelMap::iterator iter = channels_.begin();
+       iter != channels_.end();
+       ++iter) {
+    bool b = (read ? iter->second->readable() :
+      iter->second->writable());
+    any = any || b;
+    all = all && b;
+  }
+  if (all) {
+    return TRANSPORT_STATE_ALL;
+  } else if (any) {
+    return TRANSPORT_STATE_SOME;
+  } else {
+    return TRANSPORT_STATE_NONE;
+  }
+}
+
+void Transport::OnChannelRequestSignaling(TransportChannelImpl* channel) {
+  ASSERT(worker_thread()->IsCurrent());
+  ChannelParams* params = new ChannelParams(channel->component());
+  signaling_thread()->Post(this, MSG_REQUESTSIGNALING, params);
+}
+
+void Transport::OnChannelRequestSignaling_s(int component) {
+  ASSERT(signaling_thread()->IsCurrent());
+  LOG(LS_INFO) << "Transport: " << content_name_ << ", allocating candidates";
+  // Resetting ICE state for the channel.
+  {
+    talk_base::CritScope cs(&crit_);
+    ChannelMap::iterator iter = channels_.find(component);
+    if (iter != channels_.end())
+      iter->second.set_candidates_allocated(false);
+  }
+  SignalRequestSignaling(this);
+}
+
+void Transport::OnChannelCandidateReady(TransportChannelImpl* channel,
+                                        const Candidate& candidate) {
+  ASSERT(worker_thread()->IsCurrent());
+  talk_base::CritScope cs(&crit_);
+  ready_candidates_.push_back(candidate);
+
+  // We hold any messages until the client lets us connect.
+  if (connect_requested_) {
+    signaling_thread()->Post(
+        this, MSG_CANDIDATEREADY, NULL);
+  }
+}
+
+void Transport::OnChannelCandidateReady_s() {
+  ASSERT(signaling_thread()->IsCurrent());
+  ASSERT(connect_requested_);
+
+  std::vector<Candidate> candidates;
+  {
+    talk_base::CritScope cs(&crit_);
+    candidates.swap(ready_candidates_);
+  }
+
+  // we do the deleting of Candidate* here to keep the new above and
+  // delete below close to each other
+  if (!candidates.empty()) {
+    SignalCandidatesReady(this, candidates);
+  }
+}
+
+void Transport::OnChannelRouteChange(TransportChannel* channel,
+                                     const Candidate& remote_candidate) {
+  ASSERT(worker_thread()->IsCurrent());
+  ChannelParams* params = new ChannelParams(new Candidate(remote_candidate));
+  params->channel = static_cast<cricket::TransportChannelImpl*>(channel);
+  signaling_thread()->Post(this, MSG_ROUTECHANGE, params);
+}
+
+void Transport::OnChannelRouteChange_s(const TransportChannel* channel,
+                                       const Candidate& remote_candidate) {
+  ASSERT(signaling_thread()->IsCurrent());
+  SignalRouteChange(this, remote_candidate.component(), remote_candidate);
+}
+
+void Transport::OnChannelCandidatesAllocationDone(
+    TransportChannelImpl* channel) {
+  ASSERT(worker_thread()->IsCurrent());
+  talk_base::CritScope cs(&crit_);
+  ChannelMap::iterator iter = channels_.find(channel->component());
+  ASSERT(iter != channels_.end());
+  LOG(LS_INFO) << "Transport: " << content_name_ << ", component "
+               << channel->component() << " allocation complete";
+  iter->second.set_candidates_allocated(true);
+
+  // If all channels belonging to this Transport got signal, then
+  // forward this signal to upper layer.
+  // Can this signal arrive before all transport channels are created?
+  for (iter = channels_.begin(); iter != channels_.end(); ++iter) {
+    if (!iter->second.candidates_allocated())
+      return;
+  }
+  signaling_thread_->Post(this, MSG_CANDIDATEALLOCATIONCOMPLETE);
+}
+
+void Transport::OnChannelCandidatesAllocationDone_s() {
+  ASSERT(signaling_thread()->IsCurrent());
+  LOG(LS_INFO) << "Transport: " << content_name_ << " allocation complete";
+  SignalCandidatesAllocationDone(this);
+}
+
+void Transport::OnRoleConflict(TransportChannelImpl* channel) {
+  signaling_thread_->Post(this, MSG_ROLECONFLICT);
+}
+
+void Transport::SetRole_w(TransportRole role) {
+  talk_base::CritScope cs(&crit_);
+  role_ = role;
+  for (ChannelMap::iterator iter = channels_.begin();
+       iter != channels_.end(); ++iter) {
+    iter->second->SetRole(role_);
+  }
+}
+
+void Transport::SetRemoteIceMode_w(IceMode mode) {
+  talk_base::CritScope cs(&crit_);
+  remote_ice_mode_ = mode;
+  // Shouldn't channels be created after this method executed?
+  for (ChannelMap::iterator iter = channels_.begin();
+       iter != channels_.end(); ++iter) {
+    iter->second->SetRemoteIceMode(remote_ice_mode_);
+  }
+}
+
+bool Transport::SetLocalTransportDescription_w(
+    const TransportDescription& desc, ContentAction action) {
+  bool ret = true;
+  talk_base::CritScope cs(&crit_);
+  local_description_.reset(new TransportDescription(desc));
+
+  for (ChannelMap::iterator iter = channels_.begin();
+       iter != channels_.end(); ++iter) {
+    ret &= ApplyLocalTransportDescription_w(iter->second.get());
+  }
+  if (!ret)
+    return false;
+
+  // If PRANSWER/ANSWER is set, we should decide transport protocol type.
+  if (action == CA_PRANSWER || action == CA_ANSWER) {
+    ret &= NegotiateTransportDescription_w(action);
+  }
+  return ret;
+}
+
+bool Transport::SetRemoteTransportDescription_w(
+    const TransportDescription& desc, ContentAction action) {
+  bool ret = true;
+  talk_base::CritScope cs(&crit_);
+  remote_description_.reset(new TransportDescription(desc));
+
+  for (ChannelMap::iterator iter = channels_.begin();
+       iter != channels_.end(); ++iter) {
+    ret &= ApplyRemoteTransportDescription_w(iter->second.get());
+  }
+
+  // If PRANSWER/ANSWER is set, we should decide transport protocol type.
+  if (action == CA_PRANSWER || action == CA_ANSWER) {
+    ret = NegotiateTransportDescription_w(CA_OFFER);
+  }
+  return ret;
+}
+
+bool Transport::ApplyLocalTransportDescription_w(TransportChannelImpl* ch) {
+  ch->SetIceCredentials(local_description_->ice_ufrag,
+                        local_description_->ice_pwd);
+  return true;
+}
+
+bool Transport::ApplyRemoteTransportDescription_w(TransportChannelImpl* ch) {
+  ch->SetRemoteIceCredentials(remote_description_->ice_ufrag,
+                              remote_description_->ice_pwd);
+  return true;
+}
+
+void Transport::ApplyNegotiatedTransportDescription_w(
+    TransportChannelImpl* channel) {
+  channel->SetIceProtocolType(protocol_);
+  channel->SetRemoteIceMode(remote_ice_mode_);
+}
+
+bool Transport::NegotiateTransportDescription_w(ContentAction local_role_) {
+  // TODO(ekr@rtfm.com): This is ICE-specific stuff. Refactor into
+  // P2PTransport.
+  const TransportDescription* offer;
+  const TransportDescription* answer;
+
+  if (local_role_ == CA_OFFER) {
+    offer = local_description_.get();
+    answer = remote_description_.get();
+  } else {
+    offer = remote_description_.get();
+    answer = local_description_.get();
+  }
+
+  TransportProtocol offer_proto = TransportProtocolFromDescription(offer);
+  TransportProtocol answer_proto = TransportProtocolFromDescription(answer);
+
+  // If offered protocol is gice/ice, then we expect to receive matching
+  // protocol in answer, anything else is treated as an error.
+  // HYBRID is not an option when offered specific protocol.
+  // If offered protocol is HYBRID and answered protocol is HYBRID then
+  // gice is preferred protocol.
+  // TODO(mallinath) - Answer from local or remote should't have both ice
+  // and gice support. It should always pick which protocol it wants to use.
+  // Once WebRTC stops supporting gice (for backward compatibility), HYBRID in
+  // answer must be treated as error.
+  if ((offer_proto == ICEPROTO_GOOGLE || offer_proto == ICEPROTO_RFC5245) &&
+      (offer_proto != answer_proto)) {
+    return false;
+  }
+  protocol_ = answer_proto == ICEPROTO_HYBRID ? ICEPROTO_GOOGLE : answer_proto;
+
+  // If transport is in ROLE_CONTROLLED and remote end point supports only
+  // ice_lite, this local end point should take CONTROLLING role.
+  if (role_ == ROLE_CONTROLLED &&
+      remote_description_->ice_mode == ICEMODE_LITE) {
+    SetRole_w(ROLE_CONTROLLING);
+  }
+
+  // Update remote ice_mode to all existing channels.
+  remote_ice_mode_ = remote_description_->ice_mode;
+
+  // Now that we have negotiated everything, push it downward.
+  // Note that we cache the result so that if we have race conditions
+  // between future SetRemote/SetLocal invocations and new channel
+  // creation, we have the negotiation state saved until a new
+  // negotiation happens.
+  for (ChannelMap::iterator iter = channels_.begin();
+       iter != channels_.end();
+       ++iter) {
+    ApplyNegotiatedTransportDescription_w(iter->second.get());
+  }
+  return true;
+}
+
+void Transport::OnMessage(talk_base::Message* msg) {
+  switch (msg->message_id) {
+    case MSG_CREATECHANNEL: {
+        ChannelParams* params = static_cast<ChannelParams*>(msg->pdata);
+        params->channel = CreateChannel_w(params->component);
+      }
+      break;
+    case MSG_DESTROYCHANNEL: {
+        ChannelParams* params = static_cast<ChannelParams*>(msg->pdata);
+        DestroyChannel_w(params->component);
+      }
+      break;
+    case MSG_CONNECTCHANNELS:
+      ConnectChannels_w();
+      break;
+    case MSG_RESETCHANNELS:
+      ResetChannels_w();
+      break;
+    case MSG_DESTROYALLCHANNELS:
+      DestroyAllChannels_w();
+      break;
+    case MSG_ONSIGNALINGREADY:
+      CallChannels_w(&TransportChannelImpl::OnSignalingReady);
+      break;
+    case MSG_ONREMOTECANDIDATE: {
+        ChannelParams* params = static_cast<ChannelParams*>(msg->pdata);
+        OnRemoteCandidate_w(*params->candidate);
+        delete params;
+      }
+      break;
+    case MSG_CONNECTING:
+      OnConnecting_s();
+      break;
+    case MSG_READSTATE:
+      OnChannelReadableState_s();
+      break;
+    case MSG_WRITESTATE:
+      OnChannelWritableState_s();
+      break;
+    case MSG_REQUESTSIGNALING: {
+        ChannelParams* params = static_cast<ChannelParams*>(msg->pdata);
+        OnChannelRequestSignaling_s(params->component);
+        delete params;
+      }
+      break;
+    case MSG_CANDIDATEREADY:
+      OnChannelCandidateReady_s();
+      break;
+    case MSG_ROUTECHANGE: {
+        ChannelParams* params = static_cast<ChannelParams*>(msg->pdata);
+        OnChannelRouteChange_s(params->channel, *params->candidate);
+        delete params;
+      }
+      break;
+    case MSG_CANDIDATEALLOCATIONCOMPLETE:
+      OnChannelCandidatesAllocationDone_s();
+      break;
+    case MSG_ROLECONFLICT:
+      SignalRoleConflict();
+      break;
+    case MSG_SETROLE: {
+        TransportRoleParam* param =
+            static_cast<TransportRoleParam*>(msg->pdata);
+        SetRole_w(param->role);
+      }
+      break;
+    case MSG_SETLOCALDESCRIPTION: {
+        TransportDescriptionParams* params =
+            static_cast<TransportDescriptionParams*>(msg->pdata);
+        params->result = SetLocalTransportDescription_w(params->desc,
+                                                        params->action);
+      }
+      break;
+    case MSG_SETREMOTEDESCRIPTION: {
+        TransportDescriptionParams* params =
+            static_cast<TransportDescriptionParams*>(msg->pdata);
+        params->result = SetRemoteTransportDescription_w(params->desc,
+                                                         params->action);
+      }
+      break;
+    case MSG_GETSTATS: {
+        StatsParam* params = static_cast<StatsParam*>(msg->pdata);
+        params->result = GetStats_w(params->stats);
+      }
+      break;
+  }
+}
+
+bool TransportParser::ParseAddress(const buzz::XmlElement* elem,
+                                   const buzz::QName& address_name,
+                                   const buzz::QName& port_name,
+                                   talk_base::SocketAddress* address,
+                                   ParseError* error) {
+  if (!elem->HasAttr(address_name))
+    return BadParse("address does not have " + address_name.LocalPart(), error);
+  if (!elem->HasAttr(port_name))
+    return BadParse("address does not have " + port_name.LocalPart(), error);
+
+  address->SetIP(elem->Attr(address_name));
+  std::istringstream ist(elem->Attr(port_name));
+  int port = 0;
+  ist >> port;
+  address->SetPort(port);
+
+  return true;
+}
+
+// We're GICE if the namespace is NS_GOOGLE_P2P, or if NS_JINGLE_ICE_UDP is
+// used and the GICE ice-option is set.
+TransportProtocol TransportProtocolFromDescription(
+    const TransportDescription* desc) {
+  ASSERT(desc != NULL);
+  if (desc->transport_type == NS_JINGLE_ICE_UDP) {
+    return (desc->HasOption(ICE_OPTION_GICE)) ?
+        ICEPROTO_HYBRID : ICEPROTO_RFC5245;
+  }
+  return ICEPROTO_GOOGLE;
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/transport.h b/talk/p2p/base/transport.h
new file mode 100644
index 0000000..b93513e
--- /dev/null
+++ b/talk/p2p/base/transport.h
@@ -0,0 +1,481 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// A Transport manages a set of named channels of the same type.
+//
+// Subclasses choose the appropriate class to instantiate for each channel;
+// however, this base class keeps track of the channels by name, watches their
+// state changes (in order to update the manager's state), and forwards
+// requests to begin connecting or to reset to each of the channels.
+//
+// On Threading:  Transport performs work on both the signaling and worker
+// threads.  For subclasses, the rule is that all signaling related calls will
+// be made on the signaling thread and all channel related calls (including
+// signaling for a channel) will be made on the worker thread.  When
+// information needs to be sent between the two threads, this class should do
+// the work (e.g., OnRemoteCandidate).
+//
+// Note: Subclasses must call DestroyChannels() in their own constructors.
+// It is not possible to do so here because the subclass constructor will
+// already have run.
+
+#ifndef TALK_P2P_BASE_TRANSPORT_H_
+#define TALK_P2P_BASE_TRANSPORT_H_
+
+#include <string>
+#include <map>
+#include <vector>
+#include "talk/base/criticalsection.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/sigslot.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/sessiondescription.h"
+#include "talk/p2p/base/transportinfo.h"
+
+namespace talk_base {
+class Thread;
+}
+
+namespace buzz {
+class QName;
+class XmlElement;
+}
+
+namespace cricket {
+
+struct ParseError;
+struct WriteError;
+class CandidateTranslator;
+class PortAllocator;
+class SessionManager;
+class Session;
+class TransportChannel;
+class TransportChannelImpl;
+
+typedef std::vector<buzz::XmlElement*> XmlElements;
+typedef std::vector<Candidate> Candidates;
+
+// Used to parse and serialize (write) transport candidates.  For
+// convenience of old code, Transports will implement TransportParser.
+// Parse/Write seems better than Serialize/Deserialize or
+// Create/Translate.
+class TransportParser {
+ public:
+  // The incoming Translator value may be null, in which case
+  // ParseCandidates should return false if there are candidates to
+  // parse (indicating a failure to parse).  If the Translator is null
+  // and there are no candidates to parse, then return true,
+  // indicating a successful parse of 0 candidates.
+
+  // Parse or write a transport description, including ICE credentials and
+  // any DTLS fingerprint. Since only Jingle has transport descriptions, these
+  // functions are only used when serializing to Jingle.
+  virtual bool ParseTransportDescription(const buzz::XmlElement* elem,
+                                         const CandidateTranslator* translator,
+                                         TransportDescription* tdesc,
+                                         ParseError* error) = 0;
+  virtual bool WriteTransportDescription(const TransportDescription& tdesc,
+                                         const CandidateTranslator* translator,
+                                         buzz::XmlElement** tdesc_elem,
+                                         WriteError* error) = 0;
+
+
+  // Parse a single candidate. This must be used when parsing Gingle
+  // candidates, since there is no enclosing transport description.
+  virtual bool ParseGingleCandidate(const buzz::XmlElement* elem,
+                                    const CandidateTranslator* translator,
+                                    Candidate* candidates,
+                                    ParseError* error) = 0;
+  virtual bool WriteGingleCandidate(const Candidate& candidate,
+                                    const CandidateTranslator* translator,
+                                    buzz::XmlElement** candidate_elem,
+                                    WriteError* error) = 0;
+
+  // Helper function to parse an element describing an address.  This
+  // retrieves the IP and port from the given element and verifies
+  // that they look like plausible values.
+  bool ParseAddress(const buzz::XmlElement* elem,
+                    const buzz::QName& address_name,
+                    const buzz::QName& port_name,
+                    talk_base::SocketAddress* address,
+                    ParseError* error);
+
+  virtual ~TransportParser() {}
+};
+
+// Whether our side of the call is driving the negotiation, or the other side.
+enum TransportRole {
+  ROLE_CONTROLLING = 0,
+  ROLE_CONTROLLED,
+  ROLE_UNKNOWN
+};
+
+// For "writable" and "readable", we need to differentiate between
+// none, all, and some.
+enum TransportState {
+  TRANSPORT_STATE_NONE = 0,
+  TRANSPORT_STATE_SOME,
+  TRANSPORT_STATE_ALL
+};
+
+// Stats that we can return about the connections for a transport channel.
+// TODO(hta): Rename to ConnectionStats
+struct ConnectionInfo {
+  bool best_connection;        // Is this the best connection we have?
+  bool writable;               // Has this connection received a STUN response?
+  bool readable;               // Has this connection received a STUN request?
+  bool timeout;                // Has this connection timed out?
+  bool new_connection;         // Is this a newly created connection?
+  size_t rtt;                  // The STUN RTT for this connection.
+  size_t sent_total_bytes;     // Total bytes sent on this connection.
+  size_t sent_bytes_second;    // Bps over the last measurement interval.
+  size_t recv_total_bytes;     // Total bytes received on this connection.
+  size_t recv_bytes_second;    // Bps over the last measurement interval.
+  Candidate local_candidate;   // The local candidate for this connection.
+  Candidate remote_candidate;  // The remote candidate for this connection.
+  void* key;                   // A static value that identifies this conn.
+};
+
+// Information about all the connections of a channel.
+typedef std::vector<ConnectionInfo> ConnectionInfos;
+
+// Information about a specific channel
+struct TransportChannelStats {
+  int component;
+  ConnectionInfos connection_infos;
+};
+
+// Information about all the channels of a transport.
+// TODO(hta): Consider if a simple vector is as good as a map.
+typedef std::vector<TransportChannelStats> TransportChannelStatsList;
+
+// Information about the stats of a transport.
+struct TransportStats {
+  std::string content_name;
+  TransportChannelStatsList channel_stats;
+};
+
+class Transport : public talk_base::MessageHandler,
+                  public sigslot::has_slots<> {
+ public:
+  Transport(talk_base::Thread* signaling_thread,
+            talk_base::Thread* worker_thread,
+            const std::string& content_name,
+            const std::string& type,
+            PortAllocator* allocator);
+  virtual ~Transport();
+
+  // Returns the signaling thread. The app talks to Transport on this thread.
+  talk_base::Thread* signaling_thread() { return signaling_thread_; }
+  // Returns the worker thread. The actual networking is done on this thread.
+  talk_base::Thread* worker_thread() { return worker_thread_; }
+
+  // Returns the content_name of this transport.
+  const std::string& content_name() const { return content_name_; }
+  // Returns the type of this transport.
+  const std::string& type() const { return type_; }
+
+  // Returns the port allocator object for this transport.
+  PortAllocator* port_allocator() { return allocator_; }
+
+  // Returns the readable and states of this manager.  These bits are the ORs
+  // of the corresponding bits on the managed channels.  Each time one of these
+  // states changes, a signal is raised.
+  // TODO: Replace uses of readable() and writable() with
+  // any_channels_readable() and any_channels_writable().
+  bool readable() const { return any_channels_readable(); }
+  bool writable() const { return any_channels_writable(); }
+  bool was_writable() const { return was_writable_; }
+  bool any_channels_readable() const {
+    return (readable_ == TRANSPORT_STATE_SOME ||
+            readable_ == TRANSPORT_STATE_ALL);
+  }
+  bool any_channels_writable() const {
+    return (writable_ == TRANSPORT_STATE_SOME ||
+            writable_ == TRANSPORT_STATE_ALL);
+  }
+  bool all_channels_readable() const {
+    return (readable_ == TRANSPORT_STATE_ALL);
+  }
+  bool all_channels_writable() const {
+    return (writable_ == TRANSPORT_STATE_ALL);
+  }
+  sigslot::signal1<Transport*> SignalReadableState;
+  sigslot::signal1<Transport*> SignalWritableState;
+
+  // Returns whether the client has requested the channels to connect.
+  bool connect_requested() const { return connect_requested_; }
+
+  void SetRole(TransportRole role);
+  TransportRole role() const { return role_; }
+
+  void SetTiebreaker(uint64 tiebreaker) { tiebreaker_ = tiebreaker; }
+  uint64 tiebreaker() { return tiebreaker_; }
+
+  TransportProtocol protocol() const { return protocol_; }
+
+  // Create, destroy, and lookup the channels of this type by their components.
+  TransportChannelImpl* CreateChannel(int component);
+  // Note: GetChannel may lead to race conditions, since the mutex is not held
+  // after the pointer is returned.
+  TransportChannelImpl* GetChannel(int component);
+  // Note: HasChannel does not lead to race conditions, unlike GetChannel.
+  bool HasChannel(int component) {
+    return (NULL != GetChannel(component));
+  }
+  bool HasChannels();
+  void DestroyChannel(int component);
+
+  // Set the local TransportDescription to be used by TransportChannels.
+  // This should be called before ConnectChannels().
+  bool SetLocalTransportDescription(const TransportDescription& description,
+                                    ContentAction action);
+
+  // Set the remote TransportDescription to be used by TransportChannels.
+  bool SetRemoteTransportDescription(const TransportDescription& description,
+                                     ContentAction action);
+
+  // Tells all current and future channels to start connecting.  When the first
+  // channel begins connecting, the following signal is raised.
+  void ConnectChannels();
+  sigslot::signal1<Transport*> SignalConnecting;
+
+  // Resets all of the channels back to their initial state.  They are no
+  // longer connecting.
+  void ResetChannels();
+
+  // Destroys every channel created so far.
+  void DestroyAllChannels();
+
+  bool GetStats(TransportStats* stats);
+
+  // Before any stanza is sent, the manager will request signaling.  Once
+  // signaling is available, the client should call OnSignalingReady.  Once
+  // this occurs, the transport (or its channels) can send any waiting stanzas.
+  // OnSignalingReady invokes OnTransportSignalingReady and then forwards this
+  // signal to each channel.
+  sigslot::signal1<Transport*> SignalRequestSignaling;
+  void OnSignalingReady();
+
+  // Handles sending of ready candidates and receiving of remote candidates.
+  sigslot::signal2<Transport*,
+                   const std::vector<Candidate>&> SignalCandidatesReady;
+
+  sigslot::signal1<Transport*> SignalCandidatesAllocationDone;
+  void OnRemoteCandidates(const std::vector<Candidate>& candidates);
+
+  // If candidate is not acceptable, returns false and sets error.
+  // Call this before calling OnRemoteCandidates.
+  virtual bool VerifyCandidate(const Candidate& candidate,
+                               std::string* error);
+
+  // Signals when the best connection for a channel changes.
+  sigslot::signal3<Transport*,
+                   int,  // component
+                   const Candidate&> SignalRouteChange;
+
+  // A transport message has generated an transport-specific error.  The
+  // stanza that caused the error is available in session_msg.  If false is
+  // returned, the error is considered unrecoverable, and the session is
+  // terminated.
+  // TODO(juberti): Remove these obsolete functions once Session no longer
+  // references them.
+  virtual void OnTransportError(const buzz::XmlElement* error) {}
+  sigslot::signal6<Transport*, const buzz::XmlElement*, const buzz::QName&,
+                   const std::string&, const std::string&,
+                   const buzz::XmlElement*>
+      SignalTransportError;
+
+  // Forwards the signal from TransportChannel to BaseSession.
+  sigslot::signal0<> SignalRoleConflict;
+
+ protected:
+  // These are called by Create/DestroyChannel above in order to create or
+  // destroy the appropriate type of channel.
+  virtual TransportChannelImpl* CreateTransportChannel(int component) = 0;
+  virtual void DestroyTransportChannel(TransportChannelImpl* channel) = 0;
+
+  // Informs the subclass that we received the signaling ready message.
+  virtual void OnTransportSignalingReady() {}
+
+  // The current local transport description, for use by derived classes
+  // when performing transport description negotiation.
+  const TransportDescription* local_description() const {
+    return local_description_.get();
+  }
+
+  // The current remote transport description, for use by derived classes
+  // when performing transport description negotiation.
+  const TransportDescription* remote_description() const {
+    return remote_description_.get();
+  }
+
+  // Pushes down the transport parameters from the local description, such
+  // as the ICE ufrag and pwd.
+  // Derived classes can override, but must call the base as well.
+  virtual bool ApplyLocalTransportDescription_w(TransportChannelImpl*
+                                                channel);
+
+  // Pushes down remote ice credentials from the remote description to the
+  // transport channel.
+  virtual bool ApplyRemoteTransportDescription_w(TransportChannelImpl* ch);
+
+  // Negotiates the transport parameters based on the current local and remote
+  // transport description, such at the version of ICE to use, and whether DTLS
+  // should be activated.
+  // Derived classes can negotiate their specific parameters here, but must call
+  // the base as well.
+  virtual bool NegotiateTransportDescription_w(ContentAction local_role);
+
+  // Pushes down the transport parameters obtained via negotiation.
+  // Derived classes can set their specific parameters here, but must call the
+  // base as well.
+  virtual void ApplyNegotiatedTransportDescription_w(
+      TransportChannelImpl* channel);
+
+ private:
+  struct ChannelMapEntry {
+    ChannelMapEntry() : impl_(NULL), candidates_allocated_(false), ref_(0) {}
+    explicit ChannelMapEntry(TransportChannelImpl *impl)
+        : impl_(impl),
+          candidates_allocated_(false),
+          ref_(0) {
+    }
+
+    void AddRef() { ++ref_; }
+    void DecRef() {
+      ASSERT(ref_ > 0);
+      --ref_;
+    }
+    int ref() const { return ref_; }
+
+    TransportChannelImpl* get() const { return impl_; }
+    TransportChannelImpl* operator->() const  { return impl_; }
+    void set_candidates_allocated(bool status) {
+      candidates_allocated_ = status;
+    }
+    bool candidates_allocated() const { return candidates_allocated_; }
+
+  private:
+    TransportChannelImpl *impl_;
+    bool candidates_allocated_;
+    int ref_;
+  };
+
+  // Candidate component => ChannelMapEntry
+  typedef std::map<int, ChannelMapEntry> ChannelMap;
+
+  // Called when the state of a channel changes.
+  void OnChannelReadableState(TransportChannel* channel);
+  void OnChannelWritableState(TransportChannel* channel);
+
+  // Called when a channel requests signaling.
+  void OnChannelRequestSignaling(TransportChannelImpl* channel);
+
+  // Called when a candidate is ready from remote peer.
+  void OnRemoteCandidate(const Candidate& candidate);
+  // Called when a candidate is ready from channel.
+  void OnChannelCandidateReady(TransportChannelImpl* channel,
+                               const Candidate& candidate);
+  void OnChannelRouteChange(TransportChannel* channel,
+                            const Candidate& remote_candidate);
+  void OnChannelCandidatesAllocationDone(TransportChannelImpl* channel);
+  // Called when there is ICE role change.
+  void OnRoleConflict(TransportChannelImpl* channel);
+
+  // Dispatches messages to the appropriate handler (below).
+  void OnMessage(talk_base::Message* msg);
+
+  // These are versions of the above methods that are called only on a
+  // particular thread (s = signaling, w = worker).  The above methods post or
+  // send a message to invoke this version.
+  TransportChannelImpl* CreateChannel_w(int component);
+  void DestroyChannel_w(int component);
+  void ConnectChannels_w();
+  void ResetChannels_w();
+  void DestroyAllChannels_w();
+  void OnRemoteCandidate_w(const Candidate& candidate);
+  void OnChannelReadableState_s();
+  void OnChannelWritableState_s();
+  void OnChannelRequestSignaling_s(int component);
+  void OnConnecting_s();
+  void OnChannelRouteChange_s(const TransportChannel* channel,
+                              const Candidate& remote_candidate);
+  void OnChannelCandidatesAllocationDone_s();
+
+  // Helper function that invokes the given function on every channel.
+  typedef void (TransportChannelImpl::* TransportChannelFunc)();
+  void CallChannels_w(TransportChannelFunc func);
+
+  // Computes the OR of the channel's read or write state (argument picks).
+  TransportState GetTransportState_s(bool read);
+
+  void OnChannelCandidateReady_s();
+
+  void SetRole_w(TransportRole role);
+  void SetRemoteIceMode_w(IceMode mode);
+  bool SetLocalTransportDescription_w(const TransportDescription& desc,
+                                      ContentAction action);
+  bool SetRemoteTransportDescription_w(const TransportDescription& desc,
+                                       ContentAction action);
+  bool GetStats_w(TransportStats* infos);
+
+  talk_base::Thread* signaling_thread_;
+  talk_base::Thread* worker_thread_;
+  std::string content_name_;
+  std::string type_;
+  PortAllocator* allocator_;
+  bool destroyed_;
+  TransportState readable_;
+  TransportState writable_;
+  bool was_writable_;
+  bool connect_requested_;
+  TransportRole role_;
+  uint64 tiebreaker_;
+  TransportProtocol protocol_;
+  IceMode remote_ice_mode_;
+  talk_base::scoped_ptr<TransportDescription> local_description_;
+  talk_base::scoped_ptr<TransportDescription> remote_description_;
+
+  ChannelMap channels_;
+  // Buffers the ready_candidates so that SignalCanidatesReady can
+  // provide them in multiples.
+  std::vector<Candidate> ready_candidates_;
+  // Protects changes to channels and messages
+  talk_base::CriticalSection crit_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(Transport);
+};
+
+// Extract a TransportProtocol from a TransportDescription.
+TransportProtocol TransportProtocolFromDescription(
+    const TransportDescription* desc);
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_TRANSPORT_H_
diff --git a/talk/p2p/base/transport_unittest.cc b/talk/p2p/base/transport_unittest.cc
new file mode 100644
index 0000000..446fda5
--- /dev/null
+++ b/talk/p2p/base/transport_unittest.cc
@@ -0,0 +1,309 @@
+/*
+ * 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/fakesslidentity.h"
+#include "talk/base/gunit.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/fakesession.h"
+#include "talk/p2p/base/parsing.h"
+#include "talk/p2p/base/p2ptransport.h"
+#include "talk/p2p/base/rawtransport.h"
+#include "talk/p2p/base/sessionmessages.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+
+using cricket::Candidate;
+using cricket::Candidates;
+using cricket::Transport;
+using cricket::FakeTransport;
+using cricket::TransportChannel;
+using cricket::FakeTransportChannel;
+using cricket::TransportRole;
+using cricket::TransportDescription;
+using cricket::WriteError;
+using cricket::ParseError;
+using talk_base::SocketAddress;
+
+static const char kIceUfrag1[] = "TESTICEUFRAG0001";
+static const char kIcePwd1[] = "TESTICEPWD00000000000001";
+
+class TransportTest : public testing::Test,
+                      public sigslot::has_slots<> {
+ public:
+  TransportTest()
+      : thread_(talk_base::Thread::Current()),
+        transport_(new FakeTransport(
+            thread_, thread_, "test content name", NULL)),
+        channel_(NULL),
+        connecting_signalled_(false) {
+    transport_->SignalConnecting.connect(this, &TransportTest::OnConnecting);
+  }
+  ~TransportTest() {
+    transport_->DestroyAllChannels();
+  }
+  bool SetupChannel() {
+    channel_ = CreateChannel(1);
+    return (channel_ != NULL);
+  }
+  FakeTransportChannel* CreateChannel(int component) {
+    return static_cast<FakeTransportChannel*>(
+        transport_->CreateChannel(component));
+  }
+  void DestroyChannel() {
+    transport_->DestroyChannel(1);
+    channel_ = NULL;
+  }
+
+ protected:
+  void OnConnecting(Transport* transport) {
+    connecting_signalled_ = true;
+  }
+
+  talk_base::Thread* thread_;
+  talk_base::scoped_ptr<FakeTransport> transport_;
+  FakeTransportChannel* channel_;
+  bool connecting_signalled_;
+};
+
+class FakeCandidateTranslator : public cricket::CandidateTranslator {
+ public:
+  void AddMapping(int component, const std::string& channel_name) {
+    name_to_component[channel_name] = component;
+    component_to_name[component] = channel_name;
+  }
+
+  bool GetChannelNameFromComponent(
+      int component, std::string* channel_name) const {
+    if (component_to_name.find(component) == component_to_name.end()) {
+      return false;
+    }
+    *channel_name = component_to_name.find(component)->second;
+    return true;
+  }
+  bool GetComponentFromChannelName(
+      const std::string& channel_name, int* component) const {
+    if (name_to_component.find(channel_name) == name_to_component.end()) {
+      return false;
+    }
+    *component = name_to_component.find(channel_name)->second;
+    return true;
+  }
+
+  std::map<std::string, int> name_to_component;
+  std::map<int, std::string> component_to_name;
+};
+
+// Test that calling ConnectChannels triggers an OnConnecting signal.
+TEST_F(TransportTest, TestConnectChannelsDoesSignal) {
+  EXPECT_TRUE(SetupChannel());
+  transport_->ConnectChannels();
+  EXPECT_FALSE(connecting_signalled_);
+
+  EXPECT_TRUE_WAIT(connecting_signalled_, 100);
+}
+
+// Test that DestroyAllChannels kills any pending OnConnecting signals.
+TEST_F(TransportTest, TestDestroyAllClearsPosts) {
+  EXPECT_TRUE(transport_->CreateChannel(1) != NULL);
+
+  transport_->ConnectChannels();
+  transport_->DestroyAllChannels();
+
+  thread_->ProcessMessages(0);
+  EXPECT_FALSE(connecting_signalled_);
+}
+
+// This test verifies channels are created with proper ICE
+// role, tiebreaker and remote ice mode and credentials after offer and answer
+// negotiations.
+TEST_F(TransportTest, TestChannelIceParameters) {
+  transport_->SetRole(cricket::ROLE_CONTROLLING);
+  transport_->SetTiebreaker(99U);
+  cricket::TransportDescription local_desc(
+      cricket::NS_JINGLE_ICE_UDP, std::vector<std::string>(),
+      kIceUfrag1, kIcePwd1, cricket::ICEMODE_FULL, NULL, cricket::Candidates());
+  ASSERT_TRUE(transport_->SetLocalTransportDescription(local_desc,
+                                                       cricket::CA_OFFER));
+  EXPECT_EQ(cricket::ROLE_CONTROLLING, transport_->role());
+  EXPECT_TRUE(SetupChannel());
+  EXPECT_EQ(cricket::ROLE_CONTROLLING, channel_->GetRole());
+  EXPECT_EQ(cricket::ICEMODE_FULL, channel_->remote_ice_mode());
+  EXPECT_EQ(kIceUfrag1, channel_->ice_ufrag());
+  EXPECT_EQ(kIcePwd1, channel_->ice_pwd());
+
+  cricket::TransportDescription remote_desc(
+      cricket::NS_JINGLE_ICE_UDP, std::vector<std::string>(),
+      kIceUfrag1, kIcePwd1, cricket::ICEMODE_FULL, NULL, cricket::Candidates());
+  ASSERT_TRUE(transport_->SetRemoteTransportDescription(remote_desc,
+                                                        cricket::CA_ANSWER));
+  EXPECT_EQ(cricket::ROLE_CONTROLLING, channel_->GetRole());
+  EXPECT_EQ(99U, channel_->tiebreaker());
+  EXPECT_EQ(cricket::ICEMODE_FULL, channel_->remote_ice_mode());
+  // Changing the transport role from CONTROLLING to CONTROLLED.
+  transport_->SetRole(cricket::ROLE_CONTROLLED);
+  EXPECT_EQ(cricket::ROLE_CONTROLLED, channel_->GetRole());
+  EXPECT_EQ(cricket::ICEMODE_FULL, channel_->remote_ice_mode());
+  EXPECT_EQ(kIceUfrag1, channel_->remote_ice_ufrag());
+  EXPECT_EQ(kIcePwd1, channel_->remote_ice_pwd());
+}
+
+// Tests channel role is reversed after receiving ice-lite from remote.
+TEST_F(TransportTest, TestSetRemoteIceLiteInOffer) {
+  transport_->SetRole(cricket::ROLE_CONTROLLED);
+  cricket::TransportDescription remote_desc(
+      cricket::NS_JINGLE_ICE_UDP, std::vector<std::string>(),
+      kIceUfrag1, kIcePwd1, cricket::ICEMODE_LITE, NULL, cricket::Candidates());
+  ASSERT_TRUE(transport_->SetRemoteTransportDescription(remote_desc,
+                                                        cricket::CA_OFFER));
+  cricket::TransportDescription local_desc(
+      cricket::NS_JINGLE_ICE_UDP, std::vector<std::string>(),
+      kIceUfrag1, kIcePwd1, cricket::ICEMODE_FULL, NULL, cricket::Candidates());
+  ASSERT_TRUE(transport_->SetLocalTransportDescription(local_desc,
+                                                       cricket::CA_ANSWER));
+  EXPECT_EQ(cricket::ROLE_CONTROLLING, transport_->role());
+  EXPECT_TRUE(SetupChannel());
+  EXPECT_EQ(cricket::ROLE_CONTROLLING, channel_->GetRole());
+  EXPECT_EQ(cricket::ICEMODE_LITE, channel_->remote_ice_mode());
+}
+
+// Tests ice-lite in remote answer.
+TEST_F(TransportTest, TestSetRemoteIceLiteInAnswer) {
+  transport_->SetRole(cricket::ROLE_CONTROLLING);
+  cricket::TransportDescription local_desc(
+      cricket::NS_JINGLE_ICE_UDP, std::vector<std::string>(),
+      kIceUfrag1, kIcePwd1, cricket::ICEMODE_FULL, NULL, cricket::Candidates());
+  ASSERT_TRUE(transport_->SetLocalTransportDescription(local_desc,
+                                                       cricket::CA_OFFER));
+  EXPECT_EQ(cricket::ROLE_CONTROLLING, transport_->role());
+  EXPECT_TRUE(SetupChannel());
+  EXPECT_EQ(cricket::ROLE_CONTROLLING, channel_->GetRole());
+  // Channels will be created in ICEFULL_MODE.
+  EXPECT_EQ(cricket::ICEMODE_FULL, channel_->remote_ice_mode());
+  cricket::TransportDescription remote_desc(
+      cricket::NS_JINGLE_ICE_UDP, std::vector<std::string>(),
+      kIceUfrag1, kIcePwd1, cricket::ICEMODE_LITE, NULL, cricket::Candidates());
+  ASSERT_TRUE(transport_->SetRemoteTransportDescription(remote_desc,
+                                                        cricket::CA_ANSWER));
+  EXPECT_EQ(cricket::ROLE_CONTROLLING, channel_->GetRole());
+  // After receiving remote description with ICEMODE_LITE, channel should
+  // have mode set to ICEMODE_LITE.
+  EXPECT_EQ(cricket::ICEMODE_LITE, channel_->remote_ice_mode());
+}
+
+// Tests that we can properly serialize/deserialize candidates.
+TEST_F(TransportTest, TestP2PTransportWriteAndParseCandidate) {
+  Candidate test_candidate(
+      "", 1, "udp",
+      talk_base::SocketAddress("2001:db8:fefe::1", 9999),
+      738197504, "abcdef", "ghijkl", "foo", "testnet", 50, "");
+  Candidate test_candidate2(
+      "", 2, "tcp",
+      talk_base::SocketAddress("192.168.7.1", 9999),
+      1107296256, "mnopqr", "stuvwx", "bar", "testnet2", 100, "");
+  talk_base::SocketAddress host_address("www.google.com", 24601);
+  host_address.SetResolvedIP(talk_base::IPAddress(0x0A000001));
+  Candidate test_candidate3(
+      "", 3, "spdy", host_address, 1476395008, "yzabcd",
+      "efghij", "baz", "testnet3", 150, "");
+  WriteError write_error;
+  ParseError parse_error;
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  cricket::Candidate parsed_candidate;
+  cricket::P2PTransportParser parser;
+
+  FakeCandidateTranslator translator;
+  translator.AddMapping(1, "test");
+  translator.AddMapping(2, "test2");
+  translator.AddMapping(3, "test3");
+
+  EXPECT_TRUE(parser.WriteGingleCandidate(test_candidate, &translator,
+                                          elem.accept(), &write_error));
+  EXPECT_EQ("", write_error.text);
+  EXPECT_EQ("test", elem->Attr(buzz::QN_NAME));
+  EXPECT_EQ("udp", elem->Attr(cricket::QN_PROTOCOL));
+  EXPECT_EQ("2001:db8:fefe::1", elem->Attr(cricket::QN_ADDRESS));
+  EXPECT_EQ("9999", elem->Attr(cricket::QN_PORT));
+  EXPECT_EQ("0.34", elem->Attr(cricket::QN_PREFERENCE));
+  EXPECT_EQ("abcdef", elem->Attr(cricket::QN_USERNAME));
+  EXPECT_EQ("ghijkl", elem->Attr(cricket::QN_PASSWORD));
+  EXPECT_EQ("foo", elem->Attr(cricket::QN_TYPE));
+  EXPECT_EQ("testnet", elem->Attr(cricket::QN_NETWORK));
+  EXPECT_EQ("50", elem->Attr(cricket::QN_GENERATION));
+
+  EXPECT_TRUE(parser.ParseGingleCandidate(elem.get(), &translator,
+                                          &parsed_candidate, &parse_error));
+  EXPECT_TRUE(test_candidate.IsEquivalent(parsed_candidate));
+
+  EXPECT_TRUE(parser.WriteGingleCandidate(test_candidate2, &translator,
+                                          elem.accept(), &write_error));
+  EXPECT_EQ("test2", elem->Attr(buzz::QN_NAME));
+  EXPECT_EQ("tcp", elem->Attr(cricket::QN_PROTOCOL));
+  EXPECT_EQ("192.168.7.1", elem->Attr(cricket::QN_ADDRESS));
+  EXPECT_EQ("9999", elem->Attr(cricket::QN_PORT));
+  EXPECT_EQ("0.51", elem->Attr(cricket::QN_PREFERENCE));
+  EXPECT_EQ("mnopqr", elem->Attr(cricket::QN_USERNAME));
+  EXPECT_EQ("stuvwx", elem->Attr(cricket::QN_PASSWORD));
+  EXPECT_EQ("bar", elem->Attr(cricket::QN_TYPE));
+  EXPECT_EQ("testnet2", elem->Attr(cricket::QN_NETWORK));
+  EXPECT_EQ("100", elem->Attr(cricket::QN_GENERATION));
+
+  EXPECT_TRUE(parser.ParseGingleCandidate(elem.get(), &translator,
+                                          &parsed_candidate, &parse_error));
+  EXPECT_TRUE(test_candidate2.IsEquivalent(parsed_candidate));
+
+  // Check that an ip is preferred over hostname.
+  EXPECT_TRUE(parser.WriteGingleCandidate(test_candidate3, &translator,
+                                          elem.accept(), &write_error));
+  EXPECT_EQ("test3", elem->Attr(cricket::QN_NAME));
+  EXPECT_EQ("spdy", elem->Attr(cricket::QN_PROTOCOL));
+  EXPECT_EQ("10.0.0.1", elem->Attr(cricket::QN_ADDRESS));
+  EXPECT_EQ("24601", elem->Attr(cricket::QN_PORT));
+  EXPECT_EQ("0.69", elem->Attr(cricket::QN_PREFERENCE));
+  EXPECT_EQ("yzabcd", elem->Attr(cricket::QN_USERNAME));
+  EXPECT_EQ("efghij", elem->Attr(cricket::QN_PASSWORD));
+  EXPECT_EQ("baz", elem->Attr(cricket::QN_TYPE));
+  EXPECT_EQ("testnet3", elem->Attr(cricket::QN_NETWORK));
+  EXPECT_EQ("150", elem->Attr(cricket::QN_GENERATION));
+
+  EXPECT_TRUE(parser.ParseGingleCandidate(elem.get(), &translator,
+                                          &parsed_candidate, &parse_error));
+  EXPECT_TRUE(test_candidate3.IsEquivalent(parsed_candidate));
+}
+
+TEST_F(TransportTest, TestGetStats) {
+  EXPECT_TRUE(SetupChannel());
+  cricket::TransportStats stats;
+  EXPECT_TRUE(transport_->GetStats(&stats));
+  // Note that this tests the behavior of a FakeTransportChannel.
+  ASSERT_EQ(1U, stats.channel_stats.size());
+  EXPECT_EQ(1, stats.channel_stats[0].component);
+  transport_->ConnectChannels();
+  EXPECT_TRUE(transport_->GetStats(&stats));
+  ASSERT_EQ(1U, stats.channel_stats.size());
+  EXPECT_EQ(1, stats.channel_stats[0].component);
+}
diff --git a/talk/p2p/base/transportchannel.cc b/talk/p2p/base/transportchannel.cc
new file mode 100644
index 0000000..50ebfb9
--- /dev/null
+++ b/talk/p2p/base/transportchannel.cc
@@ -0,0 +1,60 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sstream>
+#include "talk/p2p/base/transportchannel.h"
+
+namespace cricket {
+
+std::string TransportChannel::ToString() const {
+  const char READABLE_ABBREV[2] = { '_', 'R' };
+  const char WRITABLE_ABBREV[2] = { '_', 'W' };
+  std::stringstream ss;
+  ss << "Channel[" << content_name_
+     << "|" << component_
+     << "|" << READABLE_ABBREV[readable_] << WRITABLE_ABBREV[writable_] << "]";
+  return ss.str();
+}
+
+void TransportChannel::set_readable(bool readable) {
+  if (readable_ != readable) {
+    readable_ = readable;
+    SignalReadableState(this);
+  }
+}
+
+void TransportChannel::set_writable(bool writable) {
+  if (writable_ != writable) {
+    writable_ = writable;
+    if (writable_) {
+      SignalReadyToSend(this);
+    }
+    SignalWritableState(this);
+  }
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/transportchannel.h b/talk/p2p/base/transportchannel.h
new file mode 100644
index 0000000..4027e76
--- /dev/null
+++ b/talk/p2p/base/transportchannel.h
@@ -0,0 +1,164 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_TRANSPORTCHANNEL_H_
+#define TALK_P2P_BASE_TRANSPORTCHANNEL_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socket.h"
+#include "talk/base/sslidentity.h"
+#include "talk/base/sslstreamadapter.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/transport.h"
+#include "talk/p2p/base/transportdescription.h"
+
+namespace cricket {
+
+class Candidate;
+
+// Flags for SendPacket/SignalReadPacket.
+enum PacketFlags {
+  PF_NORMAL       = 0x00,  // A normal packet.
+  PF_SRTP_BYPASS  = 0x01,  // An encrypted SRTP packet; bypass any additional
+                           // crypto provided by the transport (e.g. DTLS)
+};
+
+// A TransportChannel represents one logical stream of packets that are sent
+// between the two sides of a session.
+class TransportChannel : public sigslot::has_slots<> {
+ public:
+  explicit TransportChannel(const std::string& content_name, int component)
+      : content_name_(content_name),
+        component_(component),
+        readable_(false), writable_(false) {}
+  virtual ~TransportChannel() {}
+
+  // Returns the session id of this channel.
+  virtual const std::string& SessionId() const { return session_id_; }
+  // Sets session id which created this transport channel.
+  // This is called from TransportProxy::GetOrCreateImpl.
+  virtual void SetSessionId(const std::string& session_id) {
+    session_id_ = session_id;
+  }
+  const std::string& content_name() const { return content_name_; }
+  int component() const { return component_; }
+
+  // Returns the readable and states of this channel.  Each time one of these
+  // states changes, a signal is raised.  These states are aggregated by the
+  // TransportManager.
+  bool readable() const { return readable_; }
+  bool writable() const { return writable_; }
+  sigslot::signal1<TransportChannel*> SignalReadableState;
+  sigslot::signal1<TransportChannel*> SignalWritableState;
+  // Emitted when the TransportChannel's ability to send has changed.
+  sigslot::signal1<TransportChannel*> SignalReadyToSend;
+
+  // Attempts to send the given packet.  The return value is < 0 on failure.
+  // TODO: Remove the default argument once channel code is updated.
+  virtual int SendPacket(const char* data, size_t len, int flags = 0) = 0;
+
+  // Sets a socket option on this channel.  Note that not all options are
+  // supported by all transport types.
+  virtual int SetOption(talk_base::Socket::Option opt, int value) = 0;
+
+  // Returns the most recent error that occurred on this channel.
+  virtual int GetError() = 0;
+
+  // Returns current transportchannel ICE role.
+  virtual TransportRole GetRole() const = 0;
+
+  // Returns the current stats for this connection.
+  virtual bool GetStats(ConnectionInfos* infos) {
+    return false;
+  }
+
+  // Is DTLS active?
+  virtual bool IsDtlsActive() const {
+    return false;
+  }
+
+  // Set up the ciphers to use for DTLS-SRTP.
+  virtual bool SetSrtpCiphers(const std::vector<std::string>& ciphers) {
+    return false;
+  }
+
+  // Find out which DTLS-SRTP cipher was negotiated
+  virtual bool GetSrtpCipher(std::string* cipher) {
+    return false;
+  }
+
+  // Allows key material to be extracted for external encryption.
+  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;
+  }
+
+  // Signalled each time a packet is received on this channel.
+  sigslot::signal4<TransportChannel*, const char*,
+                   size_t, int> SignalReadPacket;
+
+  // This signal occurs when there is a change in the way that packets are
+  // being routed, i.e. to a different remote location. The candidate
+  // indicates where and how we are currently sending media.
+  sigslot::signal2<TransportChannel*, const Candidate&> SignalRouteChange;
+
+  // Invoked when the channel is being destroyed.
+  sigslot::signal1<TransportChannel*> SignalDestroyed;
+
+  // Debugging description of this transport channel.
+  std::string ToString() const;
+
+ protected:
+  // Sets the readable state, signaling if necessary.
+  void set_readable(bool readable);
+
+  // Sets the writable state, signaling if necessary.
+  void set_writable(bool writable);
+
+
+ private:
+  std::string session_id_;
+  // Used mostly for debugging.
+  std::string content_name_;
+  int component_;
+  bool readable_;
+  bool writable_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(TransportChannel);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_TRANSPORTCHANNEL_H_
diff --git a/talk/p2p/base/transportchannelimpl.h b/talk/p2p/base/transportchannelimpl.h
new file mode 100644
index 0000000..f1b84cb
--- /dev/null
+++ b/talk/p2p/base/transportchannelimpl.h
@@ -0,0 +1,120 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_TRANSPORTCHANNELIMPL_H_
+#define TALK_P2P_BASE_TRANSPORTCHANNELIMPL_H_
+
+#include <string>
+#include "talk/p2p/base/transport.h"
+#include "talk/p2p/base/transportchannel.h"
+
+namespace buzz { class XmlElement; }
+
+namespace cricket {
+
+class Candidate;
+
+// Base class for real implementations of TransportChannel.  This includes some
+// methods called only by Transport, which do not need to be exposed to the
+// client.
+class TransportChannelImpl : public TransportChannel {
+ public:
+  explicit TransportChannelImpl(const std::string& content_name, int component)
+      : TransportChannel(content_name, component) {}
+
+  // Returns the transport that created this channel.
+  virtual Transport* GetTransport() = 0;
+
+  // For ICE channels.
+  virtual void SetRole(TransportRole role) = 0;
+  virtual void SetTiebreaker(uint64 tiebreaker) = 0;
+  // To toggle G-ICE/ICE.
+  virtual void SetIceProtocolType(IceProtocolType type) = 0;
+  // SetIceCredentials only need to be implemented by the ICE
+  // transport channels. Non-ICE transport channels can just ignore.
+  // The ufrag and pwd should be set before the Connect() is called.
+  virtual void SetIceCredentials(const std::string& ice_ufrag,
+                                 const std::string& ice_pwd)  = 0;
+  // SetRemoteIceCredentials only need to be implemented by the ICE
+  // transport channels. Non-ICE transport channels can just ignore.
+  virtual void SetRemoteIceCredentials(const std::string& ice_ufrag,
+                                       const std::string& ice_pwd) = 0;
+
+  // SetRemoteIceMode must be implemented only by the ICE transport channels.
+  virtual void SetRemoteIceMode(IceMode mode) = 0;
+
+  // Begins the process of attempting to make a connection to the other client.
+  virtual void Connect() = 0;
+
+  // Resets this channel back to the initial state (i.e., not connecting).
+  virtual void Reset() = 0;
+
+  // Allows an individual channel to request signaling and be notified when it
+  // is ready.  This is useful if the individual named channels have need to
+  // send their own transport-info stanzas.
+  sigslot::signal1<TransportChannelImpl*> SignalRequestSignaling;
+  virtual void OnSignalingReady() = 0;
+
+  // Handles sending and receiving of candidates.  The Transport
+  // receives the candidates and may forward them to the relevant
+  // channel.
+  //
+  // Note: Since candidates are delivered asynchronously to the
+  // channel, they cannot return an error if the message is invalid.
+  // It is assumed that the Transport will have checked validity
+  // before forwarding.
+  sigslot::signal2<TransportChannelImpl*,
+                   const Candidate&> SignalCandidateReady;
+  virtual void OnCandidate(const Candidate& candidate) = 0;
+
+  // DTLS methods
+  // Set DTLS local identity.
+  virtual bool SetLocalIdentity(talk_base::SSLIdentity* identity) {
+    return false;
+  }
+
+  // Set DTLS Remote fingerprint. Must be after local identity set.
+  virtual bool SetRemoteFingerprint(const std::string& digest_alg,
+    const uint8* digest,
+    size_t digest_len) {
+    return false;
+  }
+
+  // TransportChannel is forwarding this signal from PortAllocatorSession.
+  sigslot::signal1<TransportChannelImpl*> SignalCandidatesAllocationDone;
+
+  // Invoked when there is conflict in the ICE role between local and remote
+  // agents.
+  sigslot::signal1<TransportChannelImpl*> SignalRoleConflict;
+
+ private:
+  DISALLOW_EVIL_CONSTRUCTORS(TransportChannelImpl);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_TRANSPORTCHANNELIMPL_H_
diff --git a/talk/p2p/base/transportchannelproxy.cc b/talk/p2p/base/transportchannelproxy.cc
new file mode 100644
index 0000000..fd8fe6a
--- /dev/null
+++ b/talk/p2p/base/transportchannelproxy.cc
@@ -0,0 +1,223 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/p2p/base/transportchannelproxy.h"
+#include "talk/base/common.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/transport.h"
+#include "talk/p2p/base/transportchannelimpl.h"
+
+namespace cricket {
+
+enum {
+  MSG_UPDATESTATE,
+};
+
+TransportChannelProxy::TransportChannelProxy(const std::string& content_name,
+                                             const std::string& name,
+                                             int component)
+    : TransportChannel(content_name, component),
+      name_(name),
+      impl_(NULL) {
+  worker_thread_ = talk_base::Thread::Current();
+}
+
+TransportChannelProxy::~TransportChannelProxy() {
+  // Clearing any pending signal.
+  worker_thread_->Clear(this);
+  if (impl_)
+    impl_->GetTransport()->DestroyChannel(impl_->component());
+}
+
+void TransportChannelProxy::SetImplementation(TransportChannelImpl* impl) {
+  // TODO(juberti): Fix this to occur on the correct thread.
+  // ASSERT(talk_base::Thread::Current() == worker_thread_);
+
+  // Destroy any existing impl_.
+  if (impl_) {
+    impl_->GetTransport()->DestroyChannel(impl_->component());
+  }
+
+  // Adopt the supplied impl, and connect to its signals.
+  impl_ = impl;
+
+  if (impl_) {
+    impl_->SignalReadableState.connect(
+        this, &TransportChannelProxy::OnReadableState);
+    impl_->SignalWritableState.connect(
+        this, &TransportChannelProxy::OnWritableState);
+    impl_->SignalReadPacket.connect(
+        this, &TransportChannelProxy::OnReadPacket);
+    impl_->SignalReadyToSend.connect(
+        this, &TransportChannelProxy::OnReadyToSend);
+    impl_->SignalRouteChange.connect(
+        this, &TransportChannelProxy::OnRouteChange);
+    for (OptionList::iterator it = pending_options_.begin();
+         it != pending_options_.end();
+         ++it) {
+      impl_->SetOption(it->first, it->second);
+    }
+
+    // Push down the SRTP ciphers, if any were set.
+    if (!pending_srtp_ciphers_.empty()) {
+      impl_->SetSrtpCiphers(pending_srtp_ciphers_);
+    }
+    pending_options_.clear();
+  }
+
+  // Post ourselves a message to see if we need to fire state callbacks.
+  worker_thread_->Post(this, MSG_UPDATESTATE);
+}
+
+int TransportChannelProxy::SendPacket(const char* data, size_t len, int flags) {
+  ASSERT(talk_base::Thread::Current() == worker_thread_);
+  // Fail if we don't have an impl yet.
+  if (!impl_) {
+    return -1;
+  }
+  return impl_->SendPacket(data, len, flags);
+}
+
+int TransportChannelProxy::SetOption(talk_base::Socket::Option opt, int value) {
+  ASSERT(talk_base::Thread::Current() == worker_thread_);
+  if (!impl_) {
+    pending_options_.push_back(OptionPair(opt, value));
+    return 0;
+  }
+  return impl_->SetOption(opt, value);
+}
+
+int TransportChannelProxy::GetError() {
+  ASSERT(talk_base::Thread::Current() == worker_thread_);
+  if (!impl_) {
+    return 0;
+  }
+  return impl_->GetError();
+}
+
+bool TransportChannelProxy::GetStats(ConnectionInfos* infos) {
+  ASSERT(talk_base::Thread::Current() == worker_thread_);
+  if (!impl_) {
+    return false;
+  }
+  return impl_->GetStats(infos);
+}
+
+bool TransportChannelProxy::IsDtlsActive() const {
+  ASSERT(talk_base::Thread::Current() == worker_thread_);
+  if (!impl_) {
+    return false;
+  }
+  return impl_->IsDtlsActive();
+}
+
+bool TransportChannelProxy::SetSrtpCiphers(const std::vector<std::string>&
+                                           ciphers) {
+  ASSERT(talk_base::Thread::Current() == worker_thread_);
+  pending_srtp_ciphers_ = ciphers;  // Cache so we can send later, but always
+                                    // set so it stays consistent.
+  if (impl_) {
+    return impl_->SetSrtpCiphers(ciphers);
+  }
+  return true;
+}
+
+bool TransportChannelProxy::GetSrtpCipher(std::string* cipher) {
+  ASSERT(talk_base::Thread::Current() == worker_thread_);
+  if (!impl_) {
+    return false;
+  }
+  return impl_->GetSrtpCipher(cipher);
+}
+
+bool TransportChannelProxy::ExportKeyingMaterial(const std::string& label,
+                                                 const uint8* context,
+                                                 size_t context_len,
+                                                 bool use_context,
+                                                 uint8* result,
+                                                 size_t result_len) {
+  ASSERT(talk_base::Thread::Current() == worker_thread_);
+  if (!impl_) {
+    return false;
+  }
+  return impl_->ExportKeyingMaterial(label, context, context_len, use_context,
+                                     result, result_len);
+}
+
+TransportRole TransportChannelProxy::GetRole() const {
+  ASSERT(talk_base::Thread::Current() == worker_thread_);
+  if (!impl_) {
+    return ROLE_UNKNOWN;
+  }
+  return impl_->GetRole();
+}
+
+void TransportChannelProxy::OnReadableState(TransportChannel* channel) {
+  ASSERT(talk_base::Thread::Current() == worker_thread_);
+  ASSERT(channel == impl_);
+  set_readable(impl_->readable());
+  // Note: SignalReadableState fired by set_readable.
+}
+
+void TransportChannelProxy::OnWritableState(TransportChannel* channel) {
+  ASSERT(talk_base::Thread::Current() == worker_thread_);
+  ASSERT(channel == impl_);
+  set_writable(impl_->writable());
+  // Note: SignalWritableState fired by set_readable.
+}
+
+void TransportChannelProxy::OnReadPacket(TransportChannel* channel,
+                                         const char* data, size_t size,
+                                         int flags) {
+  ASSERT(talk_base::Thread::Current() == worker_thread_);
+  ASSERT(channel == impl_);
+  SignalReadPacket(this, data, size, flags);
+}
+
+void TransportChannelProxy::OnReadyToSend(TransportChannel* channel) {
+  ASSERT(talk_base::Thread::Current() == worker_thread_);
+  ASSERT(channel == impl_);
+  SignalReadyToSend(this);
+}
+
+void TransportChannelProxy::OnRouteChange(TransportChannel* channel,
+                                          const Candidate& candidate) {
+  ASSERT(talk_base::Thread::Current() == worker_thread_);
+  ASSERT(channel == impl_);
+  SignalRouteChange(this, candidate);
+}
+
+void TransportChannelProxy::OnMessage(talk_base::Message* msg) {
+  ASSERT(talk_base::Thread::Current() == worker_thread_);
+  if (msg->message_id == MSG_UPDATESTATE) {
+     // If impl_ is already readable or writable, push up those signals.
+     set_readable(impl_ ? impl_->readable() : false);
+     set_writable(impl_ ? impl_->writable() : false);
+  }
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/transportchannelproxy.h b/talk/p2p/base/transportchannelproxy.h
new file mode 100644
index 0000000..03d95b4
--- /dev/null
+++ b/talk/p2p/base/transportchannelproxy.h
@@ -0,0 +1,106 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_TRANSPORTCHANNELPROXY_H_
+#define TALK_P2P_BASE_TRANSPORTCHANNELPROXY_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "talk/base/messagehandler.h"
+#include "talk/p2p/base/transportchannel.h"
+
+namespace talk_base {
+class Thread;
+}
+
+namespace cricket {
+
+class TransportChannelImpl;
+
+// Proxies calls between the client and the transport channel implementation.
+// This is needed because clients are allowed to create channels before the
+// network negotiation is complete.  Hence, we create a proxy up front, and
+// when negotiation completes, connect the proxy to the implementaiton.
+class TransportChannelProxy : public TransportChannel,
+                              public talk_base::MessageHandler {
+ public:
+  TransportChannelProxy(const std::string& content_name,
+                        const std::string& name,
+                        int component);
+  virtual ~TransportChannelProxy();
+
+  const std::string& name() const { return name_; }
+  TransportChannelImpl* impl() { return impl_; }
+
+  // Sets the implementation to which we will proxy.
+  void SetImplementation(TransportChannelImpl* impl);
+
+  // Implementation of the TransportChannel interface.  These simply forward to
+  // the implementation.
+  virtual int SendPacket(const char* data, size_t len, int flags);
+  virtual int SetOption(talk_base::Socket::Option opt, int value);
+  virtual int GetError();
+  virtual TransportRole GetRole() const;
+  virtual bool GetStats(ConnectionInfos* infos);
+  virtual bool IsDtlsActive() const;
+  virtual bool SetSrtpCiphers(const std::vector<std::string>& ciphers);
+  virtual bool GetSrtpCipher(std::string* cipher);
+  virtual bool ExportKeyingMaterial(const std::string& label,
+                            const uint8* context,
+                            size_t context_len,
+                            bool use_context,
+                            uint8* result,
+                            size_t result_len);
+
+ private:
+  // Catch signals from the implementation channel.  These just forward to the
+  // client (after updating our state to match).
+  void OnReadableState(TransportChannel* channel);
+  void OnWritableState(TransportChannel* channel);
+  void OnReadPacket(TransportChannel* channel, const char* data, size_t size,
+                    int flags);
+  void OnReadyToSend(TransportChannel* channel);
+  void OnRouteChange(TransportChannel* channel, const Candidate& candidate);
+
+  void OnMessage(talk_base::Message* message);
+
+  typedef std::pair<talk_base::Socket::Option, int> OptionPair;
+  typedef std::vector<OptionPair> OptionList;
+  std::string name_;
+  talk_base::Thread* worker_thread_;
+  TransportChannelImpl* impl_;
+  OptionList pending_options_;
+  std::vector<std::string> pending_srtp_ciphers_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(TransportChannelProxy);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_TRANSPORTCHANNELPROXY_H_
diff --git a/talk/p2p/base/transportdescription.h b/talk/p2p/base/transportdescription.h
new file mode 100644
index 0000000..bbca295
--- /dev/null
+++ b/talk/p2p/base/transportdescription.h
@@ -0,0 +1,150 @@
+/*
+ * libjingle
+ * Copyright 2012, The Libjingle Authors.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_TRANSPORTDESCRIPTION_H_
+#define TALK_P2P_BASE_TRANSPORTDESCRIPTION_H_
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sslfingerprint.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/constants.h"
+
+namespace cricket {
+
+// SEC_ENABLED and SEC_REQUIRED should only be used if the session
+// was negotiated over TLS, to protect the inline crypto material
+// exchange.
+// SEC_DISABLED: No crypto in outgoing offer, ignore any supplied crypto.
+// SEC_ENABLED:  Crypto in outgoing offer and answer (if supplied in offer).
+// SEC_REQUIRED: Crypto in outgoing offer and answer. Fail any offer with absent
+//               or unsupported crypto.
+enum SecurePolicy {
+  SEC_DISABLED,
+  SEC_ENABLED,
+  SEC_REQUIRED
+};
+
+// The transport protocol we've elected to use.
+enum TransportProtocol {
+  ICEPROTO_GOOGLE,  // Google version of ICE protocol.
+  ICEPROTO_HYBRID,  // ICE, but can fall back to the Google version.
+  ICEPROTO_RFC5245  // Standard RFC 5245 version of ICE.
+};
+// The old name for TransportProtocol.
+// TODO(juberti): remove this.
+typedef TransportProtocol IceProtocolType;
+
+// ICE RFC 5245 implementation type.
+enum IceMode {
+  ICEMODE_FULL,  // As defined in http://tools.ietf.org/html/rfc5245#section-4.1
+  ICEMODE_LITE   // As defined in http://tools.ietf.org/html/rfc5245#section-4.2
+};
+
+typedef std::vector<Candidate> Candidates;
+
+struct TransportDescription {
+  TransportDescription() {}
+
+  TransportDescription(const std::string& transport_type,
+                       const std::vector<std::string>& transport_options,
+                       const std::string& ice_ufrag,
+                       const std::string& ice_pwd,
+                       IceMode ice_mode,
+                       const talk_base::SSLFingerprint* identity_fingerprint,
+                       const Candidates& candidates)
+      : transport_type(transport_type),
+        transport_options(transport_options),
+        ice_ufrag(ice_ufrag),
+        ice_pwd(ice_pwd),
+        ice_mode(ice_mode),
+        identity_fingerprint(CopyFingerprint(identity_fingerprint)),
+        candidates(candidates) {}
+  TransportDescription(const std::string& transport_type,
+                       const Candidates& candidates)
+      : transport_type(transport_type),
+        ice_mode(ICEMODE_FULL),
+        candidates(candidates) {}
+  TransportDescription(const TransportDescription& from)
+      : transport_type(from.transport_type),
+        transport_options(from.transport_options),
+        ice_ufrag(from.ice_ufrag),
+        ice_pwd(from.ice_pwd),
+        ice_mode(from.ice_mode),
+        identity_fingerprint(CopyFingerprint(from.identity_fingerprint.get())),
+        candidates(from.candidates) {}
+
+  TransportDescription& operator=(const TransportDescription& from) {
+    // Self-assignment
+    if (this == &from)
+      return *this;
+
+    transport_type = from.transport_type;
+    transport_options = from.transport_options;
+    ice_ufrag = from.ice_ufrag;
+    ice_pwd = from.ice_pwd;
+    ice_mode = from.ice_mode;
+
+    identity_fingerprint.reset(CopyFingerprint(
+        from.identity_fingerprint.get()));
+    candidates = from.candidates;
+    return *this;
+  }
+
+  bool HasOption(const std::string& option) const {
+    return (std::find(transport_options.begin(), transport_options.end(),
+                      option) != transport_options.end());
+  }
+  void AddOption(const std::string& option) {
+    transport_options.push_back(option);
+  }
+  bool secure() { return identity_fingerprint != NULL; }
+
+  static talk_base::SSLFingerprint* CopyFingerprint(
+      const talk_base::SSLFingerprint* from) {
+    if (!from)
+      return NULL;
+
+    return new talk_base::SSLFingerprint(*from);
+  }
+
+  std::string transport_type;  // xmlns of <transport>
+  std::vector<std::string> transport_options;
+  std::string ice_ufrag;
+  std::string ice_pwd;
+  IceMode ice_mode;
+
+  talk_base::scoped_ptr<talk_base::SSLFingerprint> identity_fingerprint;
+  Candidates candidates;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_TRANSPORTDESCRIPTION_H_
diff --git a/talk/p2p/base/transportdescriptionfactory.cc b/talk/p2p/base/transportdescriptionfactory.cc
new file mode 100644
index 0000000..8fbfff1
--- /dev/null
+++ b/talk/p2p/base/transportdescriptionfactory.cc
@@ -0,0 +1,161 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/transportdescriptionfactory.h"
+
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/messagedigest.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sslfingerprint.h"
+#include "talk/p2p/base/transportdescription.h"
+
+namespace cricket {
+
+static TransportProtocol kDefaultProtocol = ICEPROTO_GOOGLE;
+static const char* kDefaultDigestAlg = talk_base::DIGEST_SHA_1;
+
+TransportDescriptionFactory::TransportDescriptionFactory()
+    : protocol_(kDefaultProtocol),
+      secure_(SEC_DISABLED),
+      identity_(NULL),
+      digest_alg_(kDefaultDigestAlg) {
+}
+
+TransportDescription* TransportDescriptionFactory::CreateOffer(
+    const TransportOptions& options,
+    const TransportDescription* current_description) const {
+  talk_base::scoped_ptr<TransportDescription> desc(new TransportDescription());
+
+  // Set the transport type depending on the selected protocol.
+  if (protocol_ == ICEPROTO_RFC5245) {
+    desc->transport_type = NS_JINGLE_ICE_UDP;
+  } else if (protocol_ == ICEPROTO_HYBRID) {
+    desc->transport_type = NS_JINGLE_ICE_UDP;
+    desc->AddOption(ICE_OPTION_GICE);
+  } else if (protocol_ == ICEPROTO_GOOGLE) {
+    desc->transport_type = NS_GINGLE_P2P;
+  }
+
+  // Generate the ICE credentials if we don't already have them.
+  if (!current_description || options.ice_restart) {
+    desc->ice_ufrag = talk_base::CreateRandomString(ICE_UFRAG_LENGTH);
+    desc->ice_pwd = talk_base::CreateRandomString(ICE_PWD_LENGTH);
+  } else {
+    desc->ice_ufrag = current_description->ice_ufrag;
+    desc->ice_pwd = current_description->ice_pwd;
+  }
+
+  // If we are trying to establish a secure transport, add a fingerprint.
+  if (secure_ == SEC_ENABLED || secure_ == SEC_REQUIRED) {
+    // Fail if we can't create the fingerprint.
+    if (!CreateIdentityDigest(desc.get())) {
+      return NULL;
+    }
+  }
+  return desc.release();
+}
+
+TransportDescription* TransportDescriptionFactory::CreateAnswer(
+    const TransportDescription* offer,
+    const TransportOptions& options,
+    const TransportDescription* current_description) const {
+  // A NULL offer is treated as a GICE transport description.
+  // TODO(juberti): Figure out why we get NULL offers, and fix this upstream.
+  talk_base::scoped_ptr<TransportDescription> desc(new TransportDescription());
+
+  // Figure out which ICE variant to negotiate; prefer RFC 5245 ICE, but fall
+  // back to G-ICE if needed. Note that we never create a hybrid answer, since
+  // we know what the other side can support already.
+  if (offer && offer->transport_type == NS_JINGLE_ICE_UDP &&
+      (protocol_ == ICEPROTO_RFC5245 || protocol_ == ICEPROTO_HYBRID)) {
+    // Offer is ICE or hybrid, we support ICE or hybrid: use ICE.
+    desc->transport_type = NS_JINGLE_ICE_UDP;
+  } else if (offer && offer->transport_type == NS_JINGLE_ICE_UDP &&
+             offer->HasOption(ICE_OPTION_GICE) &&
+             protocol_ == ICEPROTO_GOOGLE) {
+    desc->transport_type = NS_GINGLE_P2P;
+    // Offer is hybrid, we support GICE: use GICE.
+  } else if ((!offer || offer->transport_type == NS_GINGLE_P2P) &&
+           (protocol_ == ICEPROTO_HYBRID || protocol_ == ICEPROTO_GOOGLE)) {
+    // Offer is GICE, we support hybrid or GICE: use GICE.
+    desc->transport_type = NS_GINGLE_P2P;
+  } else {
+    // Mismatch.
+    LOG(LS_WARNING) << "Failed to create TransportDescription answer "
+                       "because of incompatible transport types";
+    return NULL;
+  }
+
+  // Generate the ICE credentials if we don't already have them or ice is
+  // being restarted.
+  if (!current_description || options.ice_restart) {
+    desc->ice_ufrag = talk_base::CreateRandomString(ICE_UFRAG_LENGTH);
+    desc->ice_pwd = talk_base::CreateRandomString(ICE_PWD_LENGTH);
+  } else {
+    desc->ice_ufrag = current_description->ice_ufrag;
+    desc->ice_pwd = current_description->ice_pwd;
+  }
+
+  // Negotiate security params.
+  if (offer && offer->identity_fingerprint.get()) {
+    // The offer supports DTLS, so answer with DTLS, as long as we support it.
+    if (secure_ == SEC_ENABLED || secure_ == SEC_REQUIRED) {
+      // Fail if we can't create the fingerprint.
+      if (!CreateIdentityDigest(desc.get())) {
+        return NULL;
+      }
+    }
+  } else if (secure_ == SEC_REQUIRED) {
+    // We require DTLS, but the other side didn't offer it. Fail.
+    LOG(LS_WARNING) << "Failed to create TransportDescription answer "
+                       "because of incompatible security settings";
+    return NULL;
+  }
+
+  return desc.release();
+}
+
+bool TransportDescriptionFactory::CreateIdentityDigest(
+    TransportDescription* desc) const {
+  if (!identity_) {
+    LOG(LS_ERROR) << "Cannot create identity digest with no identity";
+    return false;
+  }
+
+  desc->identity_fingerprint.reset(
+      talk_base::SSLFingerprint::Create(digest_alg_, identity_));
+  if (!desc->identity_fingerprint.get()) {
+    LOG(LS_ERROR) << "Failed to create identity digest, alg=" << digest_alg_;
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace cricket
+
diff --git a/talk/p2p/base/transportdescriptionfactory.h b/talk/p2p/base/transportdescriptionfactory.h
new file mode 100644
index 0000000..32836f3
--- /dev/null
+++ b/talk/p2p/base/transportdescriptionfactory.h
@@ -0,0 +1,84 @@
+/*
+ * libjingle
+ * Copyright 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_TRANSPORTDESCRIPTIONFACTORY_H_
+#define TALK_P2P_BASE_TRANSPORTDESCRIPTIONFACTORY_H_
+
+#include "talk/p2p/base/transportdescription.h"
+
+namespace talk_base {
+class SSLIdentity;
+}
+
+namespace cricket {
+
+struct TransportOptions {
+  TransportOptions() : ice_restart(false) {}
+  bool ice_restart;
+};
+
+// Creates transport descriptions according to the supplied configuration.
+// When creating answers, performs the appropriate negotiation
+// of the various fields to determine the proper result.
+class TransportDescriptionFactory {
+ public:
+  // Default ctor; use methods below to set configuration.
+  TransportDescriptionFactory();
+  SecurePolicy secure() const { return secure_; }
+  // The identity to use when setting up DTLS.
+  talk_base::SSLIdentity* identity() const { return identity_; }
+
+  // Specifies the transport protocol to be use.
+  void set_protocol(TransportProtocol protocol) { protocol_ = protocol; }
+  // Specifies the transport security policy to use.
+  void set_secure(SecurePolicy s) { secure_ = s; }
+  // Specifies the identity to use (only used when secure is not SEC_DISABLED).
+  void set_identity(talk_base::SSLIdentity* identity) { identity_ = identity; }
+  // Specifies the algorithm to use when creating an identity digest.
+  void set_digest_algorithm(const std::string& alg) { digest_alg_ = alg; }
+
+  // Creates a transport description suitable for use in an offer.
+  TransportDescription* CreateOffer(const TransportOptions& options,
+      const TransportDescription* current_description) const;
+  // Create a transport description that is a response to an offer.
+  TransportDescription* CreateAnswer(
+      const TransportDescription* offer,
+      const TransportOptions& options,
+      const TransportDescription* current_description) const;
+
+ private:
+  bool CreateIdentityDigest(TransportDescription* description) const;
+
+  TransportProtocol protocol_;
+  SecurePolicy secure_;
+  talk_base::SSLIdentity* identity_;
+  std::string digest_alg_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_TRANSPORTDESCRIPTIONFACTORY_H_
diff --git a/talk/p2p/base/transportdescriptionfactory_unittest.cc b/talk/p2p/base/transportdescriptionfactory_unittest.cc
new file mode 100644
index 0000000..c0c8829
--- /dev/null
+++ b/talk/p2p/base/transportdescriptionfactory_unittest.cc
@@ -0,0 +1,388 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/fakesslidentity.h"
+#include "talk/base/gunit.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/transportdescription.h"
+#include "talk/p2p/base/transportdescriptionfactory.h"
+
+using talk_base::scoped_ptr;
+using cricket::TransportDescriptionFactory;
+using cricket::TransportDescription;
+using cricket::TransportOptions;
+
+// TODO(juberti): Change this to SHA-256 once we have Win32 using OpenSSL.
+static const char* kDefaultDigestAlg = talk_base::DIGEST_SHA_1;
+
+class TransportDescriptionFactoryTest : public testing::Test {
+ public:
+  TransportDescriptionFactoryTest()
+      : id1_(new talk_base::FakeSSLIdentity("User1")),
+        id2_(new talk_base::FakeSSLIdentity("User2")) {
+    f1_.set_digest_algorithm(kDefaultDigestAlg);
+    f2_.set_digest_algorithm(kDefaultDigestAlg);
+  }
+  void CheckDesc(const TransportDescription* desc, const std::string& type,
+                 const std::string& opt, const std::string& ice_ufrag,
+                 const std::string& ice_pwd, const std::string& dtls_alg) {
+    ASSERT_TRUE(desc != NULL);
+    EXPECT_EQ(type, desc->transport_type);
+    EXPECT_EQ(!opt.empty(), desc->HasOption(opt));
+    if (ice_ufrag.empty() && ice_pwd.empty()) {
+      EXPECT_EQ(static_cast<size_t>(cricket::ICE_UFRAG_LENGTH),
+                desc->ice_ufrag.size());
+      EXPECT_EQ(static_cast<size_t>(cricket::ICE_PWD_LENGTH),
+                desc->ice_pwd.size());
+    } else {
+      EXPECT_EQ(ice_ufrag, desc->ice_ufrag);
+      EXPECT_EQ(ice_pwd, desc->ice_pwd);
+    }
+    if (dtls_alg.empty()) {
+      EXPECT_TRUE(desc->identity_fingerprint.get() == NULL);
+    } else {
+      ASSERT_TRUE(desc->identity_fingerprint.get() != NULL);
+      EXPECT_EQ(desc->identity_fingerprint->algorithm, dtls_alg);
+      EXPECT_GT(desc->identity_fingerprint->digest.length(), 0U);
+    }
+  }
+
+  // This test ice restart by doing two offer answer exchanges. On the second
+  // exchange ice is restarted. The test verifies that the ufrag and password
+  // in the offer and answer is changed.
+  // If |dtls| is true, the test verifies that the finger print is not changed.
+  void TestIceRestart(bool dtls) {
+    if (dtls) {
+      f1_.set_secure(cricket::SEC_ENABLED);
+      f2_.set_secure(cricket::SEC_ENABLED);
+      f1_.set_identity(id1_.get());
+      f2_.set_identity(id2_.get());
+    } else {
+      f1_.set_secure(cricket::SEC_DISABLED);
+      f2_.set_secure(cricket::SEC_DISABLED);
+    }
+
+    cricket::TransportOptions options;
+    // The initial offer / answer exchange.
+    talk_base::scoped_ptr<TransportDescription> offer(f1_.CreateOffer(
+        options, NULL));
+    talk_base::scoped_ptr<TransportDescription> answer(
+        f2_.CreateAnswer(offer.get(),
+                         options, NULL));
+
+    // Create an updated offer where we restart ice.
+    options.ice_restart = true;
+    talk_base::scoped_ptr<TransportDescription> restart_offer(f1_.CreateOffer(
+        options, offer.get()));
+
+    VerifyUfragAndPasswordChanged(dtls, offer.get(), restart_offer.get());
+
+    // Create a new answer. The transport ufrag and password is changed since
+    // |options.ice_restart == true|
+    talk_base::scoped_ptr<TransportDescription> restart_answer(
+        f2_.CreateAnswer(restart_offer.get(), options, answer.get()));
+    ASSERT_TRUE(restart_answer.get() != NULL);
+
+    VerifyUfragAndPasswordChanged(dtls, answer.get(), restart_answer.get());
+  }
+
+  void VerifyUfragAndPasswordChanged(bool dtls,
+                                     const TransportDescription* org_desc,
+                                     const TransportDescription* restart_desc) {
+    EXPECT_NE(org_desc->ice_pwd, restart_desc->ice_pwd);
+    EXPECT_NE(org_desc->ice_ufrag, restart_desc->ice_ufrag);
+    EXPECT_EQ(static_cast<size_t>(cricket::ICE_UFRAG_LENGTH),
+              restart_desc->ice_ufrag.size());
+    EXPECT_EQ(static_cast<size_t>(cricket::ICE_PWD_LENGTH),
+              restart_desc->ice_pwd.size());
+    // If DTLS is enabled, make sure the finger print is unchanged.
+    if (dtls) {
+      EXPECT_FALSE(
+          org_desc->identity_fingerprint->GetRfc4572Fingerprint().empty());
+      EXPECT_EQ(org_desc->identity_fingerprint->GetRfc4572Fingerprint(),
+                restart_desc->identity_fingerprint->GetRfc4572Fingerprint());
+    }
+  }
+
+ protected:
+  TransportDescriptionFactory f1_;
+  TransportDescriptionFactory f2_;
+  scoped_ptr<talk_base::SSLIdentity> id1_;
+  scoped_ptr<talk_base::SSLIdentity> id2_;
+};
+
+// Test that in the default case, we generate the expected G-ICE offer.
+TEST_F(TransportDescriptionFactoryTest, TestOfferGice) {
+  f1_.set_protocol(cricket::ICEPROTO_GOOGLE);
+  scoped_ptr<TransportDescription> desc(f1_.CreateOffer(
+      TransportOptions(), NULL));
+  CheckDesc(desc.get(), cricket::NS_GINGLE_P2P, "", "", "", "");
+}
+
+// Test generating a hybrid offer.
+TEST_F(TransportDescriptionFactoryTest, TestOfferHybrid) {
+  f1_.set_protocol(cricket::ICEPROTO_HYBRID);
+  scoped_ptr<TransportDescription> desc(f1_.CreateOffer(
+      TransportOptions(), NULL));
+  CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "google-ice", "", "", "");
+}
+
+// Test generating an ICE-only offer.
+TEST_F(TransportDescriptionFactoryTest, TestOfferIce) {
+  f1_.set_protocol(cricket::ICEPROTO_RFC5245);
+  scoped_ptr<TransportDescription> desc(f1_.CreateOffer(
+      TransportOptions(), NULL));
+  CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "", "");
+}
+
+// Test generating a hybrid offer with DTLS.
+TEST_F(TransportDescriptionFactoryTest, TestOfferHybridDtls) {
+  f1_.set_protocol(cricket::ICEPROTO_HYBRID);
+  f1_.set_secure(cricket::SEC_ENABLED);
+  f1_.set_identity(id1_.get());
+  scoped_ptr<TransportDescription> desc(f1_.CreateOffer(
+      TransportOptions(), NULL));
+  CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "google-ice", "", "",
+            kDefaultDigestAlg);
+  // Ensure it also works with SEC_REQUIRED.
+  f1_.set_secure(cricket::SEC_REQUIRED);
+  desc.reset(f1_.CreateOffer(TransportOptions(), NULL));
+  CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "google-ice", "", "",
+            kDefaultDigestAlg);
+}
+
+// Test generating a hybrid offer with DTLS fails with no identity.
+TEST_F(TransportDescriptionFactoryTest, TestOfferHybridDtlsWithNoIdentity) {
+  f1_.set_protocol(cricket::ICEPROTO_HYBRID);
+  f1_.set_secure(cricket::SEC_ENABLED);
+  scoped_ptr<TransportDescription> desc(f1_.CreateOffer(
+      TransportOptions(), NULL));
+  ASSERT_TRUE(desc.get() == NULL);
+}
+
+// Test generating a hybrid offer with DTLS fails with an unsupported digest.
+TEST_F(TransportDescriptionFactoryTest, TestOfferHybridDtlsWithBadDigestAlg) {
+  f1_.set_protocol(cricket::ICEPROTO_HYBRID);
+  f1_.set_secure(cricket::SEC_ENABLED);
+  f1_.set_identity(id1_.get());
+  f1_.set_digest_algorithm("bogus");
+  scoped_ptr<TransportDescription> desc(f1_.CreateOffer(
+      TransportOptions(), NULL));
+  ASSERT_TRUE(desc.get() == NULL);
+}
+
+// Test updating a hybrid offer with DTLS to pick ICE.
+// The ICE credentials should stay the same in the new offer.
+TEST_F(TransportDescriptionFactoryTest, TestOfferHybridDtlsReofferIceDtls) {
+  f1_.set_protocol(cricket::ICEPROTO_HYBRID);
+  f1_.set_secure(cricket::SEC_ENABLED);
+  f1_.set_identity(id1_.get());
+  scoped_ptr<TransportDescription> old_desc(f1_.CreateOffer(
+      TransportOptions(), NULL));
+  ASSERT_TRUE(old_desc.get() != NULL);
+  f1_.set_protocol(cricket::ICEPROTO_RFC5245);
+  scoped_ptr<TransportDescription> desc(
+      f1_.CreateOffer(TransportOptions(), old_desc.get()));
+  CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "",
+            old_desc->ice_ufrag, old_desc->ice_pwd, kDefaultDigestAlg);
+}
+
+// Test that we can answer a GICE offer with GICE.
+TEST_F(TransportDescriptionFactoryTest, TestAnswerGiceToGice) {
+  f1_.set_protocol(cricket::ICEPROTO_GOOGLE);
+  f2_.set_protocol(cricket::ICEPROTO_GOOGLE);
+  scoped_ptr<TransportDescription> offer(f1_.CreateOffer(
+      TransportOptions(), NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+  scoped_ptr<TransportDescription> desc(f2_.CreateAnswer(
+      offer.get(), TransportOptions(), NULL));
+  CheckDesc(desc.get(), cricket::NS_GINGLE_P2P, "", "", "", "");
+  // Should get the same result when answering as hybrid.
+  f2_.set_protocol(cricket::ICEPROTO_HYBRID);
+  desc.reset(f2_.CreateAnswer(offer.get(), TransportOptions(),
+                              NULL));
+  CheckDesc(desc.get(), cricket::NS_GINGLE_P2P, "", "", "", "");
+}
+
+// Test that we can answer a hybrid offer with GICE.
+TEST_F(TransportDescriptionFactoryTest, TestAnswerGiceToHybrid) {
+  f1_.set_protocol(cricket::ICEPROTO_HYBRID);
+  f2_.set_protocol(cricket::ICEPROTO_GOOGLE);
+  scoped_ptr<TransportDescription> offer(f1_.CreateOffer(
+      TransportOptions(), NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+  scoped_ptr<TransportDescription> desc(
+      f2_.CreateAnswer(offer.get(), TransportOptions(), NULL));
+  CheckDesc(desc.get(), cricket::NS_GINGLE_P2P, "", "", "", "");
+}
+
+// Test that we can answer a hybrid offer with ICE.
+TEST_F(TransportDescriptionFactoryTest, TestAnswerIceToHybrid) {
+  f1_.set_protocol(cricket::ICEPROTO_HYBRID);
+  f2_.set_protocol(cricket::ICEPROTO_RFC5245);
+  scoped_ptr<TransportDescription> offer(f1_.CreateOffer(
+      TransportOptions(), NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+  scoped_ptr<TransportDescription> desc(
+      f2_.CreateAnswer(offer.get(), TransportOptions(), NULL));
+  CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "", "");
+  // Should get the same result when answering as hybrid.
+  f2_.set_protocol(cricket::ICEPROTO_HYBRID);
+  desc.reset(f2_.CreateAnswer(offer.get(), TransportOptions(),
+                              NULL));
+  CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "", "");
+}
+
+// Test that we can answer an ICE offer with ICE.
+TEST_F(TransportDescriptionFactoryTest, TestAnswerIceToIce) {
+  f1_.set_protocol(cricket::ICEPROTO_RFC5245);
+  f2_.set_protocol(cricket::ICEPROTO_RFC5245);
+  scoped_ptr<TransportDescription> offer(f1_.CreateOffer(
+      TransportOptions(), NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+  scoped_ptr<TransportDescription> desc(f2_.CreateAnswer(
+      offer.get(), TransportOptions(), NULL));
+  CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "", "");
+  // Should get the same result when answering as hybrid.
+  f2_.set_protocol(cricket::ICEPROTO_HYBRID);
+  desc.reset(f2_.CreateAnswer(offer.get(), TransportOptions(),
+                              NULL));
+  CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "", "");
+}
+
+// Test that we can't answer a GICE offer with ICE.
+TEST_F(TransportDescriptionFactoryTest, TestAnswerIceToGice) {
+  f1_.set_protocol(cricket::ICEPROTO_GOOGLE);
+  f2_.set_protocol(cricket::ICEPROTO_RFC5245);
+  scoped_ptr<TransportDescription> offer(
+      f1_.CreateOffer(TransportOptions(), NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+  scoped_ptr<TransportDescription> desc(
+      f2_.CreateAnswer(offer.get(), TransportOptions(), NULL));
+  ASSERT_TRUE(desc.get() == NULL);
+}
+
+// Test that we can't answer an ICE offer with GICE.
+TEST_F(TransportDescriptionFactoryTest, TestAnswerGiceToIce) {
+  f1_.set_protocol(cricket::ICEPROTO_RFC5245);
+  f2_.set_protocol(cricket::ICEPROTO_GOOGLE);
+  scoped_ptr<TransportDescription> offer(
+      f1_.CreateOffer(TransportOptions(), NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+  scoped_ptr<TransportDescription> desc(f2_.CreateAnswer(
+      offer.get(), TransportOptions(), NULL));
+  ASSERT_TRUE(desc.get() == NULL);
+}
+
+// Test that we can update an answer properly; ICE credentials shouldn't change.
+TEST_F(TransportDescriptionFactoryTest, TestAnswerIceToIceReanswer) {
+  f1_.set_protocol(cricket::ICEPROTO_RFC5245);
+  f2_.set_protocol(cricket::ICEPROTO_RFC5245);
+  scoped_ptr<TransportDescription> offer(
+      f1_.CreateOffer(TransportOptions(), NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+  scoped_ptr<TransportDescription> old_desc(
+      f2_.CreateAnswer(offer.get(), TransportOptions(), NULL));
+  ASSERT_TRUE(old_desc.get() != NULL);
+  scoped_ptr<TransportDescription> desc(
+      f2_.CreateAnswer(offer.get(), TransportOptions(),
+                       old_desc.get()));
+  ASSERT_TRUE(desc.get() != NULL);
+  CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "",
+            old_desc->ice_ufrag, old_desc->ice_pwd, "");
+}
+
+// Test that we handle answering an offer with DTLS with no DTLS.
+TEST_F(TransportDescriptionFactoryTest, TestAnswerHybridToHybridDtls) {
+  f1_.set_protocol(cricket::ICEPROTO_HYBRID);
+  f1_.set_secure(cricket::SEC_ENABLED);
+  f1_.set_identity(id1_.get());
+  f2_.set_protocol(cricket::ICEPROTO_HYBRID);
+  scoped_ptr<TransportDescription> offer(
+      f1_.CreateOffer(TransportOptions(), NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+  scoped_ptr<TransportDescription> desc(
+      f2_.CreateAnswer(offer.get(), TransportOptions(), NULL));
+  CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "", "");
+}
+
+// Test that we handle answering an offer without DTLS if we have DTLS enabled,
+// but fail if we require DTLS.
+TEST_F(TransportDescriptionFactoryTest, TestAnswerHybridDtlsToHybrid) {
+  f1_.set_protocol(cricket::ICEPROTO_HYBRID);
+  f2_.set_protocol(cricket::ICEPROTO_HYBRID);
+  f2_.set_secure(cricket::SEC_ENABLED);
+  f2_.set_identity(id2_.get());
+  scoped_ptr<TransportDescription> offer(
+      f1_.CreateOffer(TransportOptions(), NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+  scoped_ptr<TransportDescription> desc(
+      f2_.CreateAnswer(offer.get(), TransportOptions(), NULL));
+  CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "", "");
+  f2_.set_secure(cricket::SEC_REQUIRED);
+  desc.reset(f2_.CreateAnswer(offer.get(), TransportOptions(),
+                              NULL));
+  ASSERT_TRUE(desc.get() == NULL);
+}
+
+// Test that we handle answering an DTLS offer with DTLS, both if we have
+// DTLS enabled and required.
+TEST_F(TransportDescriptionFactoryTest, TestAnswerHybridDtlsToHybridDtls) {
+  f1_.set_protocol(cricket::ICEPROTO_HYBRID);
+  f1_.set_secure(cricket::SEC_ENABLED);
+  f1_.set_identity(id1_.get());
+  f2_.set_protocol(cricket::ICEPROTO_HYBRID);
+  f2_.set_secure(cricket::SEC_ENABLED);
+  f2_.set_identity(id2_.get());
+  scoped_ptr<TransportDescription> offer(
+      f1_.CreateOffer(TransportOptions(), NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+  scoped_ptr<TransportDescription> desc(
+      f2_.CreateAnswer(offer.get(), TransportOptions(), NULL));
+  CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "",
+            kDefaultDigestAlg);
+  f2_.set_secure(cricket::SEC_REQUIRED);
+  desc.reset(f2_.CreateAnswer(offer.get(), TransportOptions(),
+                              NULL));
+  CheckDesc(desc.get(), cricket::NS_JINGLE_ICE_UDP, "", "", "",
+            kDefaultDigestAlg);
+}
+
+// Test that ice ufrag and password is changed in an updated offer and answer
+// if |TransportDescriptionOptions::ice_restart| is true.
+TEST_F(TransportDescriptionFactoryTest, TestIceRestart) {
+  TestIceRestart(false);
+}
+
+// Test that ice ufrag and password is changed in an updated offer and answer
+// if |TransportDescriptionOptions::ice_restart| is true and DTLS is enabled.
+TEST_F(TransportDescriptionFactoryTest, TestIceRestartWithDtls) {
+  TestIceRestart(true);
+}
diff --git a/talk/p2p/base/transportinfo.h b/talk/p2p/base/transportinfo.h
new file mode 100644
index 0000000..ad8b6a2
--- /dev/null
+++ b/talk/p2p/base/transportinfo.h
@@ -0,0 +1,60 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_TRANSPORTINFO_H_
+#define TALK_P2P_BASE_TRANSPORTINFO_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/helpers.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/transportdescription.h"
+
+namespace cricket {
+
+// A TransportInfo is NOT a transport-info message.  It is comparable
+// to a "ContentInfo". A transport-infos message is basically just a
+// collection of TransportInfos.
+struct TransportInfo {
+  TransportInfo() {}
+
+  TransportInfo(const std::string& content_name,
+                const TransportDescription& description)
+      : content_name(content_name),
+        description(description) {}
+
+  std::string content_name;
+  TransportDescription description;
+};
+
+typedef std::vector<TransportInfo> TransportInfos;
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_TRANSPORTINFO_H_
diff --git a/talk/p2p/base/turnport.cc b/talk/p2p/base/turnport.cc
new file mode 100644
index 0000000..9fad274
--- /dev/null
+++ b/talk/p2p/base/turnport.cc
@@ -0,0 +1,953 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/turnport.h"
+
+#include <functional>
+
+#include "talk/base/asyncpacketsocket.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/nethelpers.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/stringencode.h"
+#include "talk/p2p/base/common.h"
+#include "talk/p2p/base/stun.h"
+
+namespace cricket {
+
+// TODO(juberti): Move to stun.h when relay messages have been renamed.
+static const int TURN_ALLOCATE_REQUEST = STUN_ALLOCATE_REQUEST;
+static const int TURN_ALLOCATE_ERROR_RESPONSE = STUN_ALLOCATE_ERROR_RESPONSE;
+
+// TODO(juberti): Extract to turnmessage.h
+static const int TURN_DEFAULT_PORT = 3478;
+static const int TURN_CHANNEL_NUMBER_START = 0x4000;
+static const int TURN_PERMISSION_TIMEOUT = 5 * 60 * 1000;  // 5 minutes
+
+static const size_t TURN_CHANNEL_HEADER_SIZE = 4U;
+
+enum {
+  MSG_PORT_ERROR = 1
+};
+
+inline bool IsTurnChannelData(uint16 msg_type) {
+  return ((msg_type & 0xC000) == 0x4000);  // MSB are 0b01
+}
+
+static int GetRelayPreference(cricket::ProtocolType proto) {
+  int relay_preference = ICE_TYPE_PREFERENCE_RELAY;
+  if (proto == cricket::PROTO_TCP)
+    relay_preference -= 1;
+  else if (proto == cricket::PROTO_SSLTCP)
+    relay_preference -= 2;
+
+  ASSERT(relay_preference >= 0);
+  return relay_preference;
+}
+
+class TurnAllocateRequest : public StunRequest {
+ public:
+  explicit TurnAllocateRequest(TurnPort* port);
+  virtual void Prepare(StunMessage* request);
+  virtual void OnResponse(StunMessage* response);
+  virtual void OnErrorResponse(StunMessage* response);
+  virtual void OnTimeout();
+
+ private:
+  // Handles authentication challenge from the server.
+  void OnAuthChallenge(StunMessage* response, int code);
+  void OnUnknownAttribute(StunMessage* response);
+
+  TurnPort* port_;
+};
+
+class TurnRefreshRequest : public StunRequest {
+ public:
+  explicit TurnRefreshRequest(TurnPort* port);
+  virtual void Prepare(StunMessage* request);
+  virtual void OnResponse(StunMessage* response);
+  virtual void OnErrorResponse(StunMessage* response);
+  virtual void OnTimeout();
+
+ private:
+  TurnPort* port_;
+};
+
+class TurnCreatePermissionRequest : public StunRequest,
+                                    public sigslot::has_slots<> {
+ public:
+  TurnCreatePermissionRequest(TurnPort* port, TurnEntry* entry,
+                              const talk_base::SocketAddress& ext_addr);
+  virtual void Prepare(StunMessage* request);
+  virtual void OnResponse(StunMessage* response);
+  virtual void OnErrorResponse(StunMessage* response);
+  virtual void OnTimeout();
+
+ private:
+  void OnEntryDestroyed(TurnEntry* entry);
+
+  TurnPort* port_;
+  TurnEntry* entry_;
+  talk_base::SocketAddress ext_addr_;
+};
+
+class TurnChannelBindRequest : public StunRequest,
+                               public sigslot::has_slots<> {
+ public:
+  TurnChannelBindRequest(TurnPort* port, TurnEntry* entry, int channel_id,
+                         const talk_base::SocketAddress& ext_addr);
+  virtual void Prepare(StunMessage* request);
+  virtual void OnResponse(StunMessage* response);
+  virtual void OnErrorResponse(StunMessage* response);
+  virtual void OnTimeout();
+
+ private:
+  void OnEntryDestroyed(TurnEntry* entry);
+
+  TurnPort* port_;
+  TurnEntry* entry_;
+  int channel_id_;
+  talk_base::SocketAddress ext_addr_;
+};
+
+// Manages a "connection" to a remote destination. We will attempt to bring up
+// a channel for this remote destination to reduce the overhead of sending data.
+class TurnEntry : public sigslot::has_slots<> {
+ public:
+  enum BindState { STATE_UNBOUND, STATE_BINDING, STATE_BOUND };
+  TurnEntry(TurnPort* port, int channel_id,
+            const talk_base::SocketAddress& ext_addr);
+
+  TurnPort* port() { return port_; }
+
+  int channel_id() const { return channel_id_; }
+  const talk_base::SocketAddress& address() const { return ext_addr_; }
+  BindState state() const { return state_; }
+
+  // Helper methods to send permission and channel bind requests.
+  void SendCreatePermissionRequest();
+  void SendChannelBindRequest(int delay);
+  // Sends a packet to the given destination address.
+  // This will wrap the packet in STUN if necessary.
+  int Send(const void* data, size_t size, bool payload);
+
+  void OnCreatePermissionSuccess();
+  void OnCreatePermissionError(StunMessage* response, int code);
+  void OnChannelBindSuccess();
+  void OnChannelBindError(StunMessage* response, int code);
+  // Signal sent when TurnEntry is destroyed.
+  sigslot::signal1<TurnEntry*> SignalDestroyed;
+
+ private:
+  TurnPort* port_;
+  int channel_id_;
+  talk_base::SocketAddress ext_addr_;
+  BindState state_;
+};
+
+TurnPort::TurnPort(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 ProtocolAddress& server_address,
+                   const RelayCredentials& credentials)
+    : Port(thread, RELAY_PORT_TYPE, factory, network, ip, min_port, max_port,
+           username, password),
+      server_address_(server_address),
+      credentials_(credentials),
+      resolver_(NULL),
+      error_(0),
+      request_manager_(thread),
+      next_channel_number_(TURN_CHANNEL_NUMBER_START),
+      connected_(false) {
+  request_manager_.SignalSendPacket.connect(this, &TurnPort::OnSendStunPacket);
+}
+
+TurnPort::~TurnPort() {
+  // TODO(juberti): Should this even be necessary?
+  while (!entries_.empty()) {
+    DestroyEntry(entries_.front()->address());
+  }
+}
+
+void TurnPort::PrepareAddress() {
+  if (credentials_.username.empty() ||
+      credentials_.password.empty()) {
+    LOG(LS_ERROR) << "Allocation can't be started without setting the"
+                  << " TURN server credentials for the user.";
+    OnAllocateError();
+    return;
+  }
+
+  if (!server_address_.address.port()) {
+    // We will set default TURN port, if no port is set in the address.
+    server_address_.address.SetPort(TURN_DEFAULT_PORT);
+  }
+
+  if (server_address_.address.IsUnresolved()) {
+    ResolveTurnAddress(server_address_.address);
+  } else {
+    LOG_J(LS_INFO, this) << "Trying to connect to TURN server via "
+                         << ProtoToString(server_address_.proto) << " @ "
+                         << server_address_.address.ToSensitiveString();
+    if (server_address_.proto == PROTO_UDP) {
+      socket_.reset(socket_factory()->CreateUdpSocket(
+          talk_base::SocketAddress(ip(), 0), min_port(), max_port()));
+    } else if (server_address_.proto == PROTO_TCP) {
+      socket_.reset(socket_factory()->CreateClientTcpSocket(
+          talk_base::SocketAddress(ip(), 0), server_address_.address,
+          proxy(), user_agent(), talk_base::PacketSocketFactory::OPT_STUN));
+    }
+
+    if (!socket_) {
+      OnAllocateError();
+      return;
+    }
+
+    // Apply options if any.
+    for (SocketOptionsMap::iterator iter = socket_options_.begin();
+         iter != socket_options_.end(); ++iter) {
+      socket_->SetOption(iter->first, iter->second);
+    }
+
+    socket_->SignalReadPacket.connect(this, &TurnPort::OnReadPacket);
+    socket_->SignalReadyToSend.connect(this, &TurnPort::OnReadyToSend);
+
+    if (server_address_.proto == PROTO_TCP) {
+      socket_->SignalConnect.connect(this, &TurnPort::OnSocketConnect);
+      socket_->SignalClose.connect(this, &TurnPort::OnSocketClose);
+    } else {
+      // If its UDP, send AllocateRequest now.
+      // For TCP and TLS AllcateRequest will be sent by OnSocketConnect.
+      SendRequest(new TurnAllocateRequest(this), 0);
+    }
+  }
+}
+
+void TurnPort::OnSocketConnect(talk_base::AsyncPacketSocket* socket) {
+  LOG(LS_INFO) << "TurnPort connected to " << socket->GetRemoteAddress()
+               << " using tcp.";
+  SendRequest(new TurnAllocateRequest(this), 0);
+}
+
+void TurnPort::OnSocketClose(talk_base::AsyncPacketSocket* socket, int error) {
+  LOG_J(LS_WARNING, this) << "Connection with server failed, error=" << error;
+  if (!connected_) {
+    OnAllocateError();
+  }
+}
+
+Connection* TurnPort::CreateConnection(const Candidate& address,
+                                       CandidateOrigin origin) {
+  // TURN-UDP can only connect to UDP candidates.
+  if (address.protocol() != UDP_PROTOCOL_NAME) {
+    return NULL;
+  }
+
+  if (!IsCompatibleAddress(address.address())) {
+    return NULL;
+  }
+
+  // Create an entry, if needed, so we can get our permissions set up correctly.
+  CreateEntry(address.address());
+
+  // TODO(juberti): The '0' index will need to change if we start gathering STUN
+  // candidates on this port.
+  ProxyConnection* conn = new ProxyConnection(this, 0, address);
+  conn->SignalDestroyed.connect(this, &TurnPort::OnConnectionDestroyed);
+  AddConnection(conn);
+  return conn;
+}
+
+int TurnPort::SetOption(talk_base::Socket::Option opt, int value) {
+  if (!socket_) {
+    // If socket is not created yet, these options will be applied during socket
+    // creation.
+    socket_options_[opt] = value;
+    return 0;
+  }
+  return socket_->SetOption(opt, value);
+}
+
+int TurnPort::GetOption(talk_base::Socket::Option opt, int* value) {
+  if (!socket_)
+    return -1;
+
+  return socket_->GetOption(opt, value);
+}
+
+int TurnPort::GetError() {
+  return error_;
+}
+
+int TurnPort::SendTo(const void* data, size_t size,
+                     const talk_base::SocketAddress& addr,
+                     bool payload) {
+  // Try to find an entry for this specific address; we should have one.
+  TurnEntry* entry = FindEntry(addr);
+  ASSERT(entry != NULL);
+  if (!entry) {
+    return 0;
+  }
+
+  if (!connected()) {
+    error_ = EWOULDBLOCK;
+    return SOCKET_ERROR;
+  }
+
+  // Send the actual contents to the server using the usual mechanism.
+  int sent = entry->Send(data, size, payload);
+  if (sent <= 0) {
+    return SOCKET_ERROR;
+  }
+
+  // The caller of the function is expecting the number of user data bytes,
+  // rather than the size of the packet.
+  return size;
+}
+
+void TurnPort::OnReadPacket(talk_base::AsyncPacketSocket* socket,
+                           const char* data, size_t size,
+                           const talk_base::SocketAddress& remote_addr) {
+  ASSERT(socket == socket_.get());
+  ASSERT(remote_addr == server_address_.address);
+
+  // The message must be at least the size of a channel header.
+  if (size < TURN_CHANNEL_HEADER_SIZE) {
+    LOG_J(LS_WARNING, this) << "Received TURN message that was too short";
+    return;
+  }
+
+  // Check the message type, to see if is a Channel Data message.
+  // The message will either be channel data, a TURN data indication, or
+  // a response to a previous request.
+  uint16 msg_type = talk_base::GetBE16(data);
+  if (IsTurnChannelData(msg_type)) {
+    HandleChannelData(msg_type, data, size);
+  } else if (msg_type == TURN_DATA_INDICATION) {
+    HandleDataIndication(data, size);
+  } else {
+    // This must be a response for one of our requests.
+    // Check success responses, but not errors, for MESSAGE-INTEGRITY.
+    if (IsStunSuccessResponseType(msg_type) &&
+        !StunMessage::ValidateMessageIntegrity(data, size, hash())) {
+      LOG_J(LS_WARNING, this) << "Received TURN message with invalid "
+                              << "message integrity, msg_type=" << msg_type;
+      return;
+    }
+    request_manager_.CheckResponse(data, size);
+  }
+}
+
+void TurnPort::OnReadyToSend(talk_base::AsyncPacketSocket* socket) {
+  if (connected_) {
+    Port::OnReadyToSend();
+  }
+}
+
+void TurnPort::ResolveTurnAddress(const talk_base::SocketAddress& address) {
+  if (resolver_)
+    return;
+
+  resolver_ = new talk_base::AsyncResolver();
+  resolver_->SignalWorkDone.connect(this, &TurnPort::OnResolveResult);
+  resolver_->set_address(address);
+  resolver_->Start();
+}
+
+void TurnPort::OnResolveResult(talk_base::SignalThread* signal_thread) {
+  ASSERT(signal_thread == resolver_);
+  if (resolver_->error() != 0) {
+    LOG_J(LS_WARNING, this) << "TURN host lookup received error "
+                            << resolver_->error();
+    OnAllocateError();
+    return;
+  }
+
+  server_address_.address = resolver_->address();
+  PrepareAddress();
+}
+
+void TurnPort::OnSendStunPacket(const void* data, size_t size,
+                                StunRequest* request) {
+  if (Send(data, size) < 0) {
+    LOG_J(LS_ERROR, this) << "Failed to send TURN message, err="
+                          << socket_->GetError();
+  }
+}
+
+void TurnPort::OnStunAddress(const talk_base::SocketAddress& address) {
+  // For relay, mapped address is rel-addr.
+  set_related_address(address);
+}
+
+void TurnPort::OnAllocateSuccess(const talk_base::SocketAddress& address) {
+  connected_ = true;
+  AddAddress(address, socket_->GetLocalAddress(), "udp",
+             RELAY_PORT_TYPE, GetRelayPreference(server_address_.proto), true);
+}
+
+void TurnPort::OnAllocateError() {
+  // We will send SignalPortError asynchronously as this can be sent during
+  // port initialization. This way it will not be blocking other port
+  // creation.
+  thread()->Post(this, MSG_PORT_ERROR);
+}
+
+void TurnPort::OnMessage(talk_base::Message* message) {
+  if (message->message_id == MSG_PORT_ERROR) {
+    SignalPortError(this);
+  } else {
+    Port::OnMessage(message);
+  }
+}
+
+void TurnPort::OnAllocateRequestTimeout() {
+  OnAllocateError();
+}
+
+void TurnPort::HandleDataIndication(const char* data, size_t size) {
+  // Read in the message, and process according to RFC5766, Section 10.4.
+  talk_base::ByteBuffer buf(data, size);
+  TurnMessage msg;
+  if (!msg.Read(&buf)) {
+    LOG_J(LS_WARNING, this) << "Received invalid TURN data indication";
+    return;
+  }
+
+  // Check mandatory attributes.
+  const StunAddressAttribute* addr_attr =
+      msg.GetAddress(STUN_ATTR_XOR_PEER_ADDRESS);
+  if (!addr_attr) {
+    LOG_J(LS_WARNING, this) << "Missing STUN_ATTR_XOR_PEER_ADDRESS attribute "
+                            << "in data indication.";
+    return;
+  }
+
+  const StunByteStringAttribute* data_attr =
+      msg.GetByteString(STUN_ATTR_DATA);
+  if (!data_attr) {
+    LOG_J(LS_WARNING, this) << "Missing STUN_ATTR_DATA attribute in "
+                            << "data indication.";
+    return;
+  }
+
+  // Verify that the data came from somewhere we think we have a permission for.
+  talk_base::SocketAddress ext_addr(addr_attr->GetAddress());
+  if (!HasPermission(ext_addr.ipaddr())) {
+    LOG_J(LS_WARNING, this) << "Received TURN data indication with invalid "
+                            << "peer address, addr="
+                            << ext_addr.ToSensitiveString();
+    return;
+  }
+
+  DispatchPacket(data_attr->bytes(), data_attr->length(), ext_addr, PROTO_UDP);
+}
+
+void TurnPort::HandleChannelData(int channel_id, const char* data,
+                                 size_t size) {
+  // Read the message, and process according to RFC5766, Section 11.6.
+  //    0                   1                   2                   3
+  //    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+  //   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+  //   |         Channel Number        |            Length             |
+  //   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+  //   |                                                               |
+  //   /                       Application Data                        /
+  //   /                                                               /
+  //   |                                                               |
+  //   |                               +-------------------------------+
+  //   |                               |
+  //   +-------------------------------+
+
+  // Extract header fields from the message.
+  uint16 len = talk_base::GetBE16(data + 2);
+  if (len > size - TURN_CHANNEL_HEADER_SIZE) {
+    LOG_J(LS_WARNING, this) << "Received TURN channel data message with "
+                            << "incorrect length, len=" << len;
+    return;
+  }
+  // Allowing messages larger than |len|, as ChannelData can be padded.
+
+  TurnEntry* entry = FindEntry(channel_id);
+  if (!entry) {
+    LOG_J(LS_WARNING, this) << "Received TURN channel data message for invalid "
+                            << "channel, channel_id=" << channel_id;
+    return;
+  }
+
+  DispatchPacket(data + TURN_CHANNEL_HEADER_SIZE, len, entry->address(),
+                 PROTO_UDP);
+}
+
+void TurnPort::DispatchPacket(const char* data, size_t size,
+    const talk_base::SocketAddress& remote_addr, ProtocolType proto) {
+  if (Connection* conn = GetConnection(remote_addr)) {
+    conn->OnReadPacket(data, size);
+  } else {
+    Port::OnReadPacket(data, size, remote_addr, proto);
+  }
+}
+
+bool TurnPort::ScheduleRefresh(int lifetime) {
+  // Lifetime is in seconds; we schedule a refresh for one minute less.
+  if (lifetime < 2 * 60) {
+    LOG_J(LS_WARNING, this) << "Received response with lifetime that was "
+                            << "too short, lifetime=" << lifetime;
+    return false;
+  }
+
+  SendRequest(new TurnRefreshRequest(this), (lifetime - 60) * 1000);
+  return true;
+}
+
+void TurnPort::SendRequest(StunRequest* req, int delay) {
+  request_manager_.SendDelayed(req, delay);
+}
+
+void TurnPort::AddRequestAuthInfo(StunMessage* msg) {
+  // If we've gotten the necessary data from the server, add it to our request.
+  VERIFY(!hash_.empty());
+  VERIFY(msg->AddAttribute(new StunByteStringAttribute(
+      STUN_ATTR_USERNAME, credentials_.username)));
+  VERIFY(msg->AddAttribute(new StunByteStringAttribute(
+      STUN_ATTR_REALM, realm_)));
+  VERIFY(msg->AddAttribute(new StunByteStringAttribute(
+      STUN_ATTR_NONCE, nonce_)));
+  VERIFY(msg->AddMessageIntegrity(hash()));
+}
+
+int TurnPort::Send(const void* data, size_t len) {
+  return socket_->SendTo(data, len, server_address_.address);
+}
+
+void TurnPort::UpdateHash() {
+  VERIFY(ComputeStunCredentialHash(credentials_.username, realm_,
+                                   credentials_.password, &hash_));
+}
+
+bool TurnPort::UpdateNonce(StunMessage* response) {
+  // When stale nonce error received, we should update
+  // hash and store realm and nonce.
+  // Check the mandatory attributes.
+  const StunByteStringAttribute* realm_attr =
+      response->GetByteString(STUN_ATTR_REALM);
+  if (!realm_attr) {
+    LOG(LS_ERROR) << "Missing STUN_ATTR_REALM attribute in "
+                  << "stale nonce error response.";
+    return false;
+  }
+  set_realm(realm_attr->GetString());
+
+  const StunByteStringAttribute* nonce_attr =
+      response->GetByteString(STUN_ATTR_NONCE);
+  if (!nonce_attr) {
+    LOG(LS_ERROR) << "Missing STUN_ATTR_NONCE attribute in "
+                  << "stale nonce error response.";
+    return false;
+  }
+  set_nonce(nonce_attr->GetString());
+  return true;
+}
+
+static bool MatchesIP(TurnEntry* e, talk_base::IPAddress ipaddr) {
+  return e->address().ipaddr() == ipaddr;
+}
+bool TurnPort::HasPermission(const talk_base::IPAddress& ipaddr) const {
+  return (std::find_if(entries_.begin(), entries_.end(),
+      std::bind2nd(std::ptr_fun(MatchesIP), ipaddr)) != entries_.end());
+}
+
+static bool MatchesAddress(TurnEntry* e, talk_base::SocketAddress addr) {
+  return e->address() == addr;
+}
+TurnEntry* TurnPort::FindEntry(const talk_base::SocketAddress& addr) const {
+  EntryList::const_iterator it = std::find_if(entries_.begin(), entries_.end(),
+      std::bind2nd(std::ptr_fun(MatchesAddress), addr));
+  return (it != entries_.end()) ? *it : NULL;
+}
+
+static bool MatchesChannelId(TurnEntry* e, int id) {
+  return e->channel_id() == id;
+}
+TurnEntry* TurnPort::FindEntry(int channel_id) const {
+  EntryList::const_iterator it = std::find_if(entries_.begin(), entries_.end(),
+      std::bind2nd(std::ptr_fun(MatchesChannelId), channel_id));
+  return (it != entries_.end()) ? *it : NULL;
+}
+
+TurnEntry* TurnPort::CreateEntry(const talk_base::SocketAddress& addr) {
+  ASSERT(FindEntry(addr) == NULL);
+  TurnEntry* entry = new TurnEntry(this, next_channel_number_++, addr);
+  entries_.push_back(entry);
+  return entry;
+}
+
+void TurnPort::DestroyEntry(const talk_base::SocketAddress& addr) {
+  TurnEntry* entry = FindEntry(addr);
+  ASSERT(entry != NULL);
+  entry->SignalDestroyed(entry);
+  entries_.remove(entry);
+  delete entry;
+}
+
+void TurnPort::OnConnectionDestroyed(Connection* conn) {
+  // Destroying TurnEntry for the connection, which is already destroyed.
+  DestroyEntry(conn->remote_candidate().address());
+}
+
+TurnAllocateRequest::TurnAllocateRequest(TurnPort* port)
+    : StunRequest(new TurnMessage()),
+      port_(port) {
+}
+
+void TurnAllocateRequest::Prepare(StunMessage* request) {
+  // Create the request as indicated in RFC 5766, Section 6.1.
+  request->SetType(TURN_ALLOCATE_REQUEST);
+  StunUInt32Attribute* transport_attr = StunAttribute::CreateUInt32(
+      STUN_ATTR_REQUESTED_TRANSPORT);
+  transport_attr->SetValue(IPPROTO_UDP << 24);
+  VERIFY(request->AddAttribute(transport_attr));
+  if (!port_->hash().empty()) {
+    port_->AddRequestAuthInfo(request);
+  }
+}
+
+void TurnAllocateRequest::OnResponse(StunMessage* response) {
+  // Check mandatory attributes as indicated in RFC5766, Section 6.3.
+  const StunAddressAttribute* mapped_attr =
+      response->GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+  if (!mapped_attr) {
+    LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_XOR_MAPPED_ADDRESS "
+                             << "attribute in allocate success response";
+    return;
+  }
+
+  // TODO(mallinath) - Use mapped address for STUN candidate.
+  port_->OnStunAddress(mapped_attr->GetAddress());
+
+  const StunAddressAttribute* relayed_attr =
+      response->GetAddress(STUN_ATTR_XOR_RELAYED_ADDRESS);
+  if (!relayed_attr) {
+    LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_XOR_RELAYED_ADDRESS "
+                             << "attribute in allocate success response";
+    return;
+  }
+
+  const StunUInt32Attribute* lifetime_attr =
+      response->GetUInt32(STUN_ATTR_TURN_LIFETIME);
+  if (!lifetime_attr) {
+    LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_TURN_LIFETIME attribute in "
+                             << "allocate success response";
+    return;
+  }
+  // Notify the port the allocate succeeded, and schedule a refresh request.
+  port_->OnAllocateSuccess(relayed_attr->GetAddress());
+  port_->ScheduleRefresh(lifetime_attr->value());
+}
+
+void TurnAllocateRequest::OnErrorResponse(StunMessage* response) {
+  // Process error response according to RFC5766, Section 6.4.
+  const StunErrorCodeAttribute* error_code = response->GetErrorCode();
+  switch (error_code->code()) {
+    case STUN_ERROR_UNAUTHORIZED:       // Unauthrorized.
+      OnAuthChallenge(response, error_code->code());
+      break;
+    default:
+      LOG_J(LS_WARNING, port_) << "Allocate response error, code="
+                               << error_code->code();
+      port_->OnAllocateError();
+  }
+}
+
+void TurnAllocateRequest::OnTimeout() {
+  LOG_J(LS_WARNING, port_) << "Allocate request timeout";
+  port_->OnAllocateRequestTimeout();
+}
+
+void TurnAllocateRequest::OnAuthChallenge(StunMessage* response, int code) {
+  // If we failed to authenticate even after we sent our credentials, fail hard.
+  if (code == STUN_ERROR_UNAUTHORIZED && !port_->hash().empty()) {
+    LOG_J(LS_WARNING, port_) << "Failed to authenticate with the server "
+                             << "after challenge.";
+    port_->OnAllocateError();
+    return;
+  }
+
+  // Check the mandatory attributes.
+  const StunByteStringAttribute* realm_attr =
+      response->GetByteString(STUN_ATTR_REALM);
+  if (!realm_attr) {
+    LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_REALM attribute in "
+                             << "allocate unauthorized response.";
+    return;
+  }
+  port_->set_realm(realm_attr->GetString());
+
+  const StunByteStringAttribute* nonce_attr =
+      response->GetByteString(STUN_ATTR_NONCE);
+  if (!nonce_attr) {
+    LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_NONCE attribute in "
+                             << "allocate unauthorized response.";
+    return;
+  }
+  port_->set_nonce(nonce_attr->GetString());
+
+  // Send another allocate request, with the received realm and nonce values.
+  port_->SendRequest(new TurnAllocateRequest(port_), 0);
+}
+
+TurnRefreshRequest::TurnRefreshRequest(TurnPort* port)
+    : StunRequest(new TurnMessage()),
+      port_(port) {
+}
+
+void TurnRefreshRequest::Prepare(StunMessage* request) {
+  // Create the request as indicated in RFC 5766, Section 7.1.
+  // No attributes need to be included.
+  request->SetType(TURN_REFRESH_REQUEST);
+  port_->AddRequestAuthInfo(request);
+}
+
+void TurnRefreshRequest::OnResponse(StunMessage* response) {
+  // Check mandatory attributes as indicated in RFC5766, Section 7.3.
+  const StunUInt32Attribute* lifetime_attr =
+      response->GetUInt32(STUN_ATTR_TURN_LIFETIME);
+  if (!lifetime_attr) {
+    LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_TURN_LIFETIME attribute in "
+                             << "refresh success response.";
+    return;
+  }
+
+  // Schedule a refresh based on the returned lifetime value.
+  port_->ScheduleRefresh(lifetime_attr->value());
+}
+
+void TurnRefreshRequest::OnErrorResponse(StunMessage* response) {
+  // TODO(juberti): Handle 437 error response as a success.
+  const StunErrorCodeAttribute* error_code = response->GetErrorCode();
+  LOG_J(LS_WARNING, port_) << "Refresh response error, code="
+                           << error_code->code();
+
+  if (error_code->code() == STUN_ERROR_STALE_NONCE) {
+    if (port_->UpdateNonce(response)) {
+      // Send RefreshRequest immediately.
+      port_->SendRequest(new TurnRefreshRequest(port_), 0);
+    }
+  }
+}
+
+void TurnRefreshRequest::OnTimeout() {
+}
+
+TurnCreatePermissionRequest::TurnCreatePermissionRequest(
+    TurnPort* port, TurnEntry* entry,
+    const talk_base::SocketAddress& ext_addr)
+    : StunRequest(new TurnMessage()),
+      port_(port),
+      entry_(entry),
+      ext_addr_(ext_addr) {
+  entry_->SignalDestroyed.connect(
+      this, &TurnCreatePermissionRequest::OnEntryDestroyed);
+}
+
+void TurnCreatePermissionRequest::Prepare(StunMessage* request) {
+  // Create the request as indicated in RFC5766, Section 9.1.
+  request->SetType(TURN_CREATE_PERMISSION_REQUEST);
+  VERIFY(request->AddAttribute(new StunXorAddressAttribute(
+      STUN_ATTR_XOR_PEER_ADDRESS, ext_addr_)));
+  port_->AddRequestAuthInfo(request);
+}
+
+void TurnCreatePermissionRequest::OnResponse(StunMessage* response) {
+  if (entry_) {
+    entry_->OnCreatePermissionSuccess();
+  }
+}
+
+void TurnCreatePermissionRequest::OnErrorResponse(StunMessage* response) {
+  if (entry_) {
+    const StunErrorCodeAttribute* error_code = response->GetErrorCode();
+    entry_->OnCreatePermissionError(response, error_code->code());
+  }
+}
+
+void TurnCreatePermissionRequest::OnTimeout() {
+  LOG_J(LS_WARNING, port_) << "Create permission timeout";
+}
+
+void TurnCreatePermissionRequest::OnEntryDestroyed(TurnEntry* entry) {
+  ASSERT(entry_ == entry);
+  entry_ = NULL;
+}
+
+TurnChannelBindRequest::TurnChannelBindRequest(
+    TurnPort* port, TurnEntry* entry,
+    int channel_id, const talk_base::SocketAddress& ext_addr)
+    : StunRequest(new TurnMessage()),
+      port_(port),
+      entry_(entry),
+      channel_id_(channel_id),
+      ext_addr_(ext_addr) {
+  entry_->SignalDestroyed.connect(
+      this, &TurnChannelBindRequest::OnEntryDestroyed);
+}
+
+void TurnChannelBindRequest::Prepare(StunMessage* request) {
+  // Create the request as indicated in RFC5766, Section 11.1.
+  request->SetType(TURN_CHANNEL_BIND_REQUEST);
+  VERIFY(request->AddAttribute(new StunUInt32Attribute(
+      STUN_ATTR_CHANNEL_NUMBER, channel_id_ << 16)));
+  VERIFY(request->AddAttribute(new StunXorAddressAttribute(
+      STUN_ATTR_XOR_PEER_ADDRESS, ext_addr_)));
+  port_->AddRequestAuthInfo(request);
+}
+
+void TurnChannelBindRequest::OnResponse(StunMessage* response) {
+  if (entry_) {
+    entry_->OnChannelBindSuccess();
+    // Refresh the channel binding just under the permission timeout
+    // threshold. The channel binding has a longer lifetime, but
+    // this is the easiest way to keep both the channel and the
+    // permission from expiring.
+    entry_->SendChannelBindRequest(TURN_PERMISSION_TIMEOUT - 60 * 1000);
+  }
+}
+
+void TurnChannelBindRequest::OnErrorResponse(StunMessage* response) {
+  if (entry_) {
+    const StunErrorCodeAttribute* error_code = response->GetErrorCode();
+    entry_->OnChannelBindError(response, error_code->code());
+  }
+}
+
+void TurnChannelBindRequest::OnTimeout() {
+  LOG_J(LS_WARNING, port_) << "Channel bind timeout";
+}
+
+void TurnChannelBindRequest::OnEntryDestroyed(TurnEntry* entry) {
+  ASSERT(entry_ == entry);
+  entry_ = NULL;
+}
+
+TurnEntry::TurnEntry(TurnPort* port, int channel_id,
+                     const talk_base::SocketAddress& ext_addr)
+    : port_(port),
+      channel_id_(channel_id),
+      ext_addr_(ext_addr),
+      state_(STATE_UNBOUND) {
+  // Creating permission for |ext_addr_|.
+  SendCreatePermissionRequest();
+}
+
+void TurnEntry::SendCreatePermissionRequest() {
+  port_->SendRequest(new TurnCreatePermissionRequest(
+      port_, this, ext_addr_), 0);
+}
+
+void TurnEntry::SendChannelBindRequest(int delay) {
+  port_->SendRequest(new TurnChannelBindRequest(
+      port_, this, channel_id_, ext_addr_), delay);
+}
+
+int TurnEntry::Send(const void* data, size_t size, bool payload) {
+  talk_base::ByteBuffer buf;
+  if (state_ != STATE_BOUND) {
+    // If we haven't bound the channel yet, we have to use a Send Indication.
+    TurnMessage msg;
+    msg.SetType(TURN_SEND_INDICATION);
+    msg.SetTransactionID(
+        talk_base::CreateRandomString(kStunTransactionIdLength));
+    VERIFY(msg.AddAttribute(new StunXorAddressAttribute(
+        STUN_ATTR_XOR_PEER_ADDRESS, ext_addr_)));
+    VERIFY(msg.AddAttribute(new StunByteStringAttribute(
+        STUN_ATTR_DATA, data, size)));
+    VERIFY(msg.Write(&buf));
+
+    // If we're sending real data, request a channel bind that we can use later.
+    if (state_ == STATE_UNBOUND && payload) {
+      SendChannelBindRequest(0);
+      state_ = STATE_BINDING;
+    }
+  } else {
+    // If the channel is bound, we can send the data as a Channel Message.
+    buf.WriteUInt16(channel_id_);
+    buf.WriteUInt16(size);
+    buf.WriteBytes(reinterpret_cast<const char*>(data), size);
+  }
+  return port_->Send(buf.Data(), buf.Length());
+}
+
+void TurnEntry::OnCreatePermissionSuccess() {
+  LOG_J(LS_INFO, port_) << "Create permission for "
+                        << ext_addr_.ToSensitiveString()
+                        << " succeeded";
+  // For success result code will be 0.
+  port_->SignalCreatePermissionResult(port_, ext_addr_, 0);
+}
+
+void TurnEntry::OnCreatePermissionError(StunMessage* response, int code) {
+  LOG_J(LS_WARNING, port_) << "Create permission for "
+                           << ext_addr_.ToSensitiveString()
+                           << " failed, code=" << code;
+  if (code == STUN_ERROR_STALE_NONCE) {
+    if (port_->UpdateNonce(response)) {
+      SendCreatePermissionRequest();
+    }
+  } else {
+    // Send signal with error code.
+    port_->SignalCreatePermissionResult(port_, ext_addr_, code);
+  }
+}
+
+void TurnEntry::OnChannelBindSuccess() {
+  LOG_J(LS_INFO, port_) << "Channel bind for " << ext_addr_.ToSensitiveString()
+                        << " succeeded";
+  ASSERT(state_ == STATE_BINDING || state_ == STATE_BOUND);
+  state_ = STATE_BOUND;
+}
+
+void TurnEntry::OnChannelBindError(StunMessage* response, int code) {
+  // TODO(mallinath) - Implement handling of error response for channel
+  // bind request as per http://tools.ietf.org/html/rfc5766#section-11.3
+  LOG_J(LS_WARNING, port_) << "Channel bind for "
+                           << ext_addr_.ToSensitiveString()
+                           << " failed, code=" << code;
+  if (code == STUN_ERROR_STALE_NONCE) {
+    if (port_->UpdateNonce(response)) {
+      // Send channel bind request with fresh nonce.
+      SendChannelBindRequest(0);
+    }
+  }
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/turnport.h b/talk/p2p/base/turnport.h
new file mode 100644
index 0000000..fa23d53
--- /dev/null
+++ b/talk/p2p/base/turnport.h
@@ -0,0 +1,179 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_TURNPORT_H_
+#define TALK_P2P_BASE_TURNPORT_H_
+
+#include <stdio.h>
+#include <string>
+#include <list>
+
+#include "talk/p2p/base/port.h"
+#include "talk/p2p/client/basicportallocator.h"
+
+namespace talk_base {
+class AsyncPacketSocket;
+class AsyncResolver;
+class SignalThread;
+}
+
+namespace cricket {
+
+extern const char TURN_PORT_TYPE[];
+class TurnAllocateRequest;
+class TurnEntry;
+
+class TurnPort : public Port {
+ public:
+  static TurnPort* Create(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,  // ice username.
+                          const std::string& password,  // ice password.
+                          const ProtocolAddress& server_address,
+                          const RelayCredentials& credentials) {
+    return new TurnPort(thread, factory, network, ip, min_port, max_port,
+                        username, password, server_address, credentials);
+  }
+
+  virtual ~TurnPort();
+
+  const ProtocolAddress& server_address() const { return server_address_; }
+
+  bool connected() const { return connected_; }
+  const RelayCredentials& credentials() const { return credentials_; }
+
+  virtual void PrepareAddress();
+  virtual Connection* CreateConnection(
+      const Candidate& c, PortInterface::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 GetOption(talk_base::Socket::Option opt, int* value);
+  virtual int GetError();
+  virtual void OnReadPacket(talk_base::AsyncPacketSocket* socket,
+                            const char* data, size_t size,
+                            const talk_base::SocketAddress& remote_addr);
+  virtual void OnReadyToSend(talk_base::AsyncPacketSocket* socket);
+
+  void OnSocketConnect(talk_base::AsyncPacketSocket* socket);
+  void OnSocketClose(talk_base::AsyncPacketSocket* socket, int error);
+
+
+  const std::string& hash() const { return hash_; }
+  const std::string& nonce() const { return nonce_; }
+
+  // This signal is only for testing purpose.
+  sigslot::signal3<TurnPort*, const talk_base::SocketAddress&, int>
+      SignalCreatePermissionResult;
+
+ protected:
+  TurnPort(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 ProtocolAddress& server_address,
+           const RelayCredentials& credentials);
+
+ private:
+  typedef std::list<TurnEntry*> EntryList;
+  typedef std::map<talk_base::Socket::Option, int> SocketOptionsMap;
+
+  virtual void OnMessage(talk_base::Message* pmsg);
+
+  void set_nonce(const std::string& nonce) { nonce_ = nonce; }
+  void set_realm(const std::string& realm) {
+    if (realm != realm_) {
+      realm_ = realm;
+      UpdateHash();
+    }
+  }
+
+  void ResolveTurnAddress(const talk_base::SocketAddress& address);
+  void OnResolveResult(talk_base::SignalThread* signal_thread);
+
+  void AddRequestAuthInfo(StunMessage* msg);
+  void OnSendStunPacket(const void* data, size_t size, StunRequest* request);
+  // Stun address from allocate success response.
+  // Currently used only for testing.
+  void OnStunAddress(const talk_base::SocketAddress& address);
+  void OnAllocateSuccess(const talk_base::SocketAddress& address);
+  void OnAllocateError();
+  void OnAllocateRequestTimeout();
+
+  void HandleDataIndication(const char* data, size_t size);
+  void HandleChannelData(int channel_id, const char* data, size_t size);
+  void DispatchPacket(const char* data, size_t size,
+      const talk_base::SocketAddress& remote_addr, ProtocolType proto);
+
+  bool ScheduleRefresh(int lifetime);
+  void SendRequest(StunRequest* request, int delay);
+  int Send(const void* data, size_t size);
+  void UpdateHash();
+  bool UpdateNonce(StunMessage* response);
+
+  bool HasPermission(const talk_base::IPAddress& ipaddr) const;
+  TurnEntry* FindEntry(const talk_base::SocketAddress& address) const;
+  TurnEntry* FindEntry(int channel_id) const;
+  TurnEntry* CreateEntry(const talk_base::SocketAddress& address);
+  void DestroyEntry(const talk_base::SocketAddress& address);
+  void OnConnectionDestroyed(Connection* conn);
+
+  ProtocolAddress server_address_;
+  RelayCredentials credentials_;
+
+  talk_base::scoped_ptr<talk_base::AsyncPacketSocket> socket_;
+  SocketOptionsMap socket_options_;
+  talk_base::AsyncResolver* resolver_;
+  int error_;
+
+  StunRequestManager request_manager_;
+  std::string realm_;       // From 401/438 response message.
+  std::string nonce_;       // From 401/438 response message.
+  std::string hash_;        // Digest of username:realm:password
+
+  int next_channel_number_;
+  EntryList entries_;
+
+  bool connected_;
+
+  friend class TurnEntry;
+  friend class TurnAllocateRequest;
+  friend class TurnRefreshRequest;
+  friend class TurnCreatePermissionRequest;
+  friend class TurnChannelBindRequest;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_TURNPORT_H_
diff --git a/talk/p2p/base/turnport_unittest.cc b/talk/p2p/base/turnport_unittest.cc
new file mode 100644
index 0000000..42ae312
--- /dev/null
+++ b/talk/p2p/base/turnport_unittest.cc
@@ -0,0 +1,331 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/asynctcpsocket.h"
+#include "talk/base/buffer.h"
+#include "talk/base/firewallsocketserver.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/socketaddress.h"
+#include "talk/base/thread.h"
+#include "talk/base/virtualsocketserver.h"
+#include "talk/p2p/base/basicpacketsocketfactory.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/tcpport.h"
+#include "talk/p2p/base/testturnserver.h"
+#include "talk/p2p/base/turnport.h"
+#include "talk/p2p/base/udpport.h"
+
+using talk_base::SocketAddress;
+using cricket::Connection;
+using cricket::Port;
+using cricket::PortInterface;
+using cricket::TurnPort;
+using cricket::UDPPort;
+
+static const SocketAddress kLocalAddr1("11.11.11.11", 0);
+static const SocketAddress kLocalAddr2("22.22.22.22", 0);
+static const SocketAddress kTurnUdpIntAddr("99.99.99.3",
+                                           cricket::TURN_SERVER_PORT);
+static const SocketAddress kTurnTcpIntAddr("99.99.99.4",
+                                           cricket::TURN_SERVER_PORT);
+static const SocketAddress kTurnUdpExtAddr("99.99.99.5", 0);
+
+static const char kIceUfrag1[] = "TESTICEUFRAG0001";
+static const char kIceUfrag2[] = "TESTICEUFRAG0002";
+static const char kIcePwd1[] = "TESTICEPWD00000000000001";
+static const char kIcePwd2[] = "TESTICEPWD00000000000002";
+static const char kTurnUsername[] = "test";
+static const char kTurnPassword[] = "test";
+static const int kTimeout = 1000;
+
+static const cricket::ProtocolAddress kTurnUdpProtoAddr(kTurnUdpIntAddr,
+                                                        cricket::PROTO_UDP);
+static const cricket::ProtocolAddress kTurnTcpProtoAddr(kTurnTcpIntAddr,
+                                                        cricket::PROTO_TCP);
+
+class TurnPortTest : public testing::Test,
+                     public sigslot::has_slots<> {
+ public:
+  TurnPortTest()
+      : 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), 32),
+        socket_factory_(talk_base::Thread::Current()),
+        turn_server_(main_, kTurnUdpIntAddr, kTurnUdpExtAddr),
+        turn_ready_(false),
+        turn_error_(false),
+        turn_unknown_address_(false),
+        turn_create_permission_success_(false),
+        udp_ready_(false) {
+    network_.AddIP(talk_base::IPAddress(INADDR_ANY));
+  }
+
+  void OnTurnPortComplete(Port* port) {
+    turn_ready_ = true;
+  }
+  void OnTurnPortError(Port* port) {
+    turn_error_ = true;
+  }
+  void OnTurnUnknownAddress(PortInterface* port, const SocketAddress& addr,
+                            cricket::ProtocolType proto,
+                            cricket::IceMessage* msg, const std::string& rf,
+                            bool /*port_muxed*/) {
+    turn_unknown_address_ = true;
+  }
+  void OnTurnCreatePermissionResult(TurnPort* port, const SocketAddress& addr,
+                                     int code) {
+    // Ignoring the address.
+    if (code == 0) {
+      turn_create_permission_success_ = true;
+    }
+  }
+  void OnTurnReadPacket(Connection* conn, const char* data, size_t size) {
+    turn_packets_.push_back(talk_base::Buffer(data, size));
+  }
+  void OnUdpPortComplete(Port* port) {
+    udp_ready_ = true;
+  }
+  void OnUdpReadPacket(Connection* conn, const char* data, size_t size) {
+    udp_packets_.push_back(talk_base::Buffer(data, size));
+  }
+
+  talk_base::AsyncSocket* CreateServerSocket(const SocketAddress addr) {
+    talk_base::AsyncSocket* socket = ss_->CreateAsyncSocket(SOCK_STREAM);
+    EXPECT_GE(socket->Bind(addr), 0);
+    EXPECT_GE(socket->Listen(5), 0);
+    return socket;
+  }
+
+  void CreateTurnPort(const std::string& username,
+                      const std::string& password,
+                      const cricket::ProtocolAddress& server_address) {
+    cricket::RelayCredentials credentials(username, password);
+    turn_port_.reset(TurnPort::Create(main_, &socket_factory_, &network_,
+                                 kLocalAddr1.ipaddr(), 0, 0,
+                                 kIceUfrag1, kIcePwd1,
+                                 server_address, credentials));
+    turn_port_->SignalPortComplete.connect(this,
+        &TurnPortTest::OnTurnPortComplete);
+    turn_port_->SignalPortError.connect(this,
+        &TurnPortTest::OnTurnPortError);
+    turn_port_->SignalUnknownAddress.connect(this,
+        &TurnPortTest::OnTurnUnknownAddress);
+    turn_port_->SignalCreatePermissionResult.connect(this,
+        &TurnPortTest::OnTurnCreatePermissionResult);
+  }
+  void CreateUdpPort() {
+    udp_port_.reset(UDPPort::Create(main_, &socket_factory_, &network_,
+                                    kLocalAddr2.ipaddr(), 0, 0,
+                                    kIceUfrag2, kIcePwd2));
+    udp_port_->SignalPortComplete.connect(
+        this, &TurnPortTest::OnUdpPortComplete);
+  }
+
+  void TestTurnConnection() {
+    // Create ports and prepare addresses.
+    ASSERT_TRUE(turn_port_ != NULL);
+    turn_port_->PrepareAddress();
+    ASSERT_TRUE_WAIT(turn_ready_, kTimeout);
+    CreateUdpPort();
+    udp_port_->PrepareAddress();
+    ASSERT_TRUE_WAIT(udp_ready_, kTimeout);
+
+    // Send ping from UDP to TURN.
+    Connection* conn1 = udp_port_->CreateConnection(
+                    turn_port_->Candidates()[0], Port::ORIGIN_MESSAGE);
+    ASSERT_TRUE(conn1 != NULL);
+    conn1->Ping(0);
+    WAIT(!turn_unknown_address_, kTimeout);
+    EXPECT_FALSE(turn_unknown_address_);
+    EXPECT_EQ(Connection::STATE_READ_INIT, conn1->read_state());
+    EXPECT_EQ(Connection::STATE_WRITE_INIT, conn1->write_state());
+
+    // Send ping from TURN to UDP.
+    Connection* conn2 = turn_port_->CreateConnection(
+                    udp_port_->Candidates()[0], Port::ORIGIN_MESSAGE);
+    ASSERT_TRUE(conn2 != NULL);
+    ASSERT_TRUE_WAIT(turn_create_permission_success_, kTimeout);
+    conn2->Ping(0);
+
+    EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, conn2->write_state(), kTimeout);
+    EXPECT_EQ(Connection::STATE_READABLE, conn1->read_state());
+    EXPECT_EQ(Connection::STATE_READ_INIT, conn2->read_state());
+    EXPECT_EQ(Connection::STATE_WRITE_INIT, conn1->write_state());
+
+    // Send another ping from UDP to TURN.
+    conn1->Ping(0);
+    EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, conn1->write_state(), kTimeout);
+    EXPECT_EQ(Connection::STATE_READABLE, conn2->read_state());
+  }
+
+  void TestTurnSendData() {
+    turn_port_->PrepareAddress();
+    EXPECT_TRUE_WAIT(turn_ready_, kTimeout);
+    CreateUdpPort();
+    udp_port_->PrepareAddress();
+    EXPECT_TRUE_WAIT(udp_ready_, kTimeout);
+    // Create connections and send pings.
+    Connection* conn1 = turn_port_->CreateConnection(
+        udp_port_->Candidates()[0], Port::ORIGIN_MESSAGE);
+    Connection* conn2 = udp_port_->CreateConnection(
+        turn_port_->Candidates()[0], Port::ORIGIN_MESSAGE);
+    ASSERT_TRUE(conn1 != NULL);
+    ASSERT_TRUE(conn2 != NULL);
+    conn1->SignalReadPacket.connect(static_cast<TurnPortTest*>(this),
+                                    &TurnPortTest::OnTurnReadPacket);
+    conn2->SignalReadPacket.connect(static_cast<TurnPortTest*>(this),
+                                    &TurnPortTest::OnUdpReadPacket);
+    conn1->Ping(0);
+    EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, conn1->write_state(), kTimeout);
+    conn2->Ping(0);
+    EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, conn2->write_state(), kTimeout);
+
+    // Send some data.
+    size_t num_packets = 256;
+    for (size_t i = 0; i < num_packets; ++i) {
+      char buf[256];
+      for (size_t j = 0; j < i + 1; ++j) {
+        buf[j] = 0xFF - j;
+      }
+      conn1->Send(buf, i + 1);
+      conn2->Send(buf, i + 1);
+      main_->ProcessMessages(0);
+    }
+
+    // Check the data.
+    ASSERT_EQ_WAIT(num_packets, turn_packets_.size(), kTimeout);
+    ASSERT_EQ_WAIT(num_packets, udp_packets_.size(), kTimeout);
+    for (size_t i = 0; i < num_packets; ++i) {
+      EXPECT_EQ(i + 1, turn_packets_[i].length());
+      EXPECT_EQ(i + 1, udp_packets_[i].length());
+      EXPECT_EQ(turn_packets_[i], udp_packets_[i]);
+    }
+  }
+
+ protected:
+  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_;
+  cricket::TestTurnServer turn_server_;
+  talk_base::scoped_ptr<TurnPort> turn_port_;
+  talk_base::scoped_ptr<UDPPort> udp_port_;
+  bool turn_ready_;
+  bool turn_error_;
+  bool turn_unknown_address_;
+  bool turn_create_permission_success_;
+  bool udp_ready_;
+  std::vector<talk_base::Buffer> turn_packets_;
+  std::vector<talk_base::Buffer> udp_packets_;
+};
+
+// Do a normal TURN allocation.
+TEST_F(TurnPortTest, TestTurnAllocate) {
+  CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+  EXPECT_EQ(0, turn_port_->SetOption(talk_base::Socket::OPT_SNDBUF, 10*1024));
+  turn_port_->PrepareAddress();
+  EXPECT_TRUE_WAIT(turn_ready_, kTimeout);
+  ASSERT_EQ(1U, turn_port_->Candidates().size());
+  EXPECT_EQ(kTurnUdpExtAddr.ipaddr(),
+            turn_port_->Candidates()[0].address().ipaddr());
+  EXPECT_NE(0, turn_port_->Candidates()[0].address().port());
+}
+
+TEST_F(TurnPortTest, TestTurnTcpAllocate) {
+  talk_base::AsyncSocket* tcp_server_socket =
+      CreateServerSocket(kTurnTcpIntAddr);
+  turn_server_.server()->AddInternalServerSocket(
+      tcp_server_socket, cricket::PROTO_TCP);
+  CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
+  EXPECT_EQ(0, turn_port_->SetOption(talk_base::Socket::OPT_SNDBUF, 10*1024));
+  turn_port_->PrepareAddress();
+  EXPECT_TRUE_WAIT(turn_ready_, kTimeout);
+  ASSERT_EQ(1U, turn_port_->Candidates().size());
+  EXPECT_EQ(kTurnUdpExtAddr.ipaddr(),
+            turn_port_->Candidates()[0].address().ipaddr());
+  EXPECT_NE(0, turn_port_->Candidates()[0].address().port());
+}
+
+// Try to do a TURN allocation with an invalid password.
+TEST_F(TurnPortTest, TestTurnAllocateBadPassword) {
+  CreateTurnPort(kTurnUsername, "bad", kTurnUdpProtoAddr);
+  turn_port_->PrepareAddress();
+  EXPECT_TRUE_WAIT(turn_error_, kTimeout);
+  ASSERT_EQ(0U, turn_port_->Candidates().size());
+}
+
+// Do a TURN allocation and try to send a packet to it from the outside.
+// The packet should be dropped. Then, try to send a packet from TURN to the
+// outside. It should reach its destination. Finally, try again from the
+// outside. It should now work as well.
+TEST_F(TurnPortTest, TestTurnConnection) {
+  CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+  TestTurnConnection();
+}
+
+TEST_F(TurnPortTest, TestTurnTcpConnection) {
+  talk_base::AsyncSocket* tcp_server_socket =
+      CreateServerSocket(kTurnTcpIntAddr);
+  turn_server_.server()->AddInternalServerSocket(
+      tcp_server_socket, cricket::PROTO_TCP);
+  CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
+  TestTurnConnection();
+}
+
+// Run TurnConnectionTest with one-time-use nonce feature.
+// Here server will send a 438 STALE_NONCE error message for
+// every TURN transaction.
+TEST_F(TurnPortTest, TestTurnConnectionUsingOTUNonce) {
+  turn_server_.set_enable_otu_nonce(true);
+  CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+  TestTurnConnection();
+}
+
+// Do a TURN allocation, establish a connection, and send some data.
+TEST_F(TurnPortTest, TestTurnSendDataTurnUdpToUdp) {
+  // Create ports and prepare addresses.
+  CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr);
+  TestTurnSendData();
+}
+
+TEST_F(TurnPortTest, TestTurnSendDataTurnTcpToUdp) {
+  talk_base::AsyncSocket* tcp_server_socket =
+      CreateServerSocket(kTurnTcpIntAddr);
+  turn_server_.server()->AddInternalServerSocket(
+      tcp_server_socket, cricket::PROTO_TCP);
+  // Create ports and prepare addresses.
+  CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr);
+  TestTurnSendData();
+}
diff --git a/talk/p2p/base/turnserver.cc b/talk/p2p/base/turnserver.cc
new file mode 100644
index 0000000..e82455a
--- /dev/null
+++ b/talk/p2p/base/turnserver.cc
@@ -0,0 +1,1006 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/turnserver.h"
+
+#include "talk/base/bytebuffer.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/messagedigest.h"
+#include "talk/base/socketadapters.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/asyncstuntcpsocket.h"
+#include "talk/p2p/base/common.h"
+#include "talk/p2p/base/packetsocketfactory.h"
+#include "talk/p2p/base/stun.h"
+
+namespace cricket {
+
+// TODO(juberti): Move this all to a future turnmessage.h
+//static const int IPPROTO_UDP = 17;
+static const int kNonceTimeout = 60 * 60 * 1000;              // 60 minutes
+static const int kDefaultAllocationTimeout = 10 * 60 * 1000;  // 10 minutes
+static const int kPermissionTimeout = 5 * 60 * 1000;          //  5 minutes
+static const int kChannelTimeout = 10 * 60 * 1000;            // 10 minutes
+
+static const int kMinChannelNumber = 0x4000;
+static const int kMaxChannelNumber = 0x7FFF;
+
+static const size_t kNonceKeySize = 16;
+static const size_t kNonceSize = 40;
+
+static const size_t TURN_CHANNEL_HEADER_SIZE = 4U;
+
+// TODO(mallinath) - Move these to a common place.
+static const size_t kMaxPacketSize = 64 * 1024;
+
+inline bool IsTurnChannelData(uint16 msg_type) {
+  // The first two bits of a channel data message are 0b01.
+  return ((msg_type & 0xC000) == 0x4000);
+}
+
+// IDs used for posted messages.
+enum {
+  MSG_TIMEOUT,
+};
+
+// Encapsulates a TURN allocation.
+// The object is created when an allocation request is received, and then
+// handles TURN messages (via HandleTurnMessage) and channel data messages
+// (via HandleChannelData) for this allocation when received by the server.
+// The object self-deletes and informs the server if its lifetime timer expires.
+class TurnServer::Allocation : public talk_base::MessageHandler,
+                               public sigslot::has_slots<> {
+ public:
+  Allocation(TurnServer* server_,
+             talk_base::Thread* thread, const Connection& conn,
+             talk_base::AsyncPacketSocket* server_socket,
+             const std::string& key);
+  virtual ~Allocation();
+
+  Connection* conn() { return &conn_; }
+  const std::string& key() const { return key_; }
+  const std::string& transaction_id() const { return transaction_id_; }
+  const std::string& username() const { return username_; }
+  const std::string& last_nonce() const { return last_nonce_; }
+  void set_last_nonce(const std::string& nonce) { last_nonce_ = nonce; }
+
+  std::string ToString() const;
+
+  void HandleTurnMessage(const TurnMessage* msg);
+  void HandleChannelData(const char* data, size_t size);
+
+  sigslot::signal1<Allocation*> SignalDestroyed;
+
+ private:
+  typedef std::list<Permission*> PermissionList;
+  typedef std::list<Channel*> ChannelList;
+
+  void HandleAllocateRequest(const TurnMessage* msg);
+  void HandleRefreshRequest(const TurnMessage* msg);
+  void HandleSendIndication(const TurnMessage* msg);
+  void HandleCreatePermissionRequest(const TurnMessage* msg);
+  void HandleChannelBindRequest(const TurnMessage* msg);
+
+  void OnExternalPacket(talk_base::AsyncPacketSocket* socket,
+                        const char* data, size_t size,
+                        const talk_base::SocketAddress& addr);
+
+  static int ComputeLifetime(const TurnMessage* msg);
+  bool HasPermission(const talk_base::IPAddress& addr);
+  void AddPermission(const talk_base::IPAddress& addr);
+  Permission* FindPermission(const talk_base::IPAddress& addr) const;
+  Channel* FindChannel(int channel_id) const;
+  Channel* FindChannel(const talk_base::SocketAddress& addr) const;
+
+  void SendResponse(TurnMessage* msg);
+  void SendBadRequestResponse(const TurnMessage* req);
+  void SendErrorResponse(const TurnMessage* req, int code,
+                         const std::string& reason);
+  void SendExternal(const void* data, size_t size,
+                    const talk_base::SocketAddress& peer);
+
+  void OnPermissionDestroyed(Permission* perm);
+  void OnChannelDestroyed(Channel* channel);
+  virtual void OnMessage(talk_base::Message* msg);
+
+  TurnServer* server_;
+  talk_base::Thread* thread_;
+  Connection conn_;
+  talk_base::scoped_ptr<talk_base::AsyncPacketSocket> external_socket_;
+  std::string key_;
+  std::string transaction_id_;
+  std::string username_;
+  std::string last_nonce_;
+  PermissionList perms_;
+  ChannelList channels_;
+};
+
+// Encapsulates a TURN permission.
+// The object is created when a create permission request is received by an
+// allocation, and self-deletes when its lifetime timer expires.
+class TurnServer::Permission : public talk_base::MessageHandler {
+ public:
+  Permission(talk_base::Thread* thread, const talk_base::IPAddress& peer);
+  ~Permission();
+
+  const talk_base::IPAddress& peer() const { return peer_; }
+  void Refresh();
+
+  sigslot::signal1<Permission*> SignalDestroyed;
+
+ private:
+  virtual void OnMessage(talk_base::Message* msg);
+
+  talk_base::Thread* thread_;
+  talk_base::IPAddress peer_;
+};
+
+// Encapsulates a TURN channel binding.
+// The object is created when a channel bind request is received by an
+// allocation, and self-deletes when its lifetime timer expires.
+class TurnServer::Channel : public talk_base::MessageHandler {
+ public:
+  Channel(talk_base::Thread* thread, int id,
+                     const talk_base::SocketAddress& peer);
+  ~Channel();
+
+  int id() const { return id_; }
+  const talk_base::SocketAddress& peer() const { return peer_; }
+  void Refresh();
+
+  sigslot::signal1<Channel*> SignalDestroyed;
+
+ private:
+  virtual void OnMessage(talk_base::Message* msg);
+
+  talk_base::Thread* thread_;
+  int id_;
+  talk_base::SocketAddress peer_;
+};
+
+static bool InitResponse(const StunMessage* req, StunMessage* resp) {
+  int resp_type = (req) ? GetStunSuccessResponseType(req->type()) : -1;
+  if (resp_type == -1)
+    return false;
+  resp->SetType(resp_type);
+  resp->SetTransactionID(req->transaction_id());
+  return true;
+}
+
+static bool InitErrorResponse(const StunMessage* req, int code,
+                              const std::string& reason, StunMessage* resp) {
+  int resp_type = (req) ? GetStunErrorResponseType(req->type()) : -1;
+  if (resp_type == -1)
+    return false;
+  resp->SetType(resp_type);
+  resp->SetTransactionID(req->transaction_id());
+  VERIFY(resp->AddAttribute(new cricket::StunErrorCodeAttribute(
+      STUN_ATTR_ERROR_CODE, code, reason)));
+  return true;
+}
+
+TurnServer::TurnServer(talk_base::Thread* thread)
+    : thread_(thread),
+      nonce_key_(talk_base::CreateRandomString(kNonceKeySize)),
+      auth_hook_(NULL),
+      enable_otu_nonce_(false) {
+}
+
+TurnServer::~TurnServer() {
+  for (AllocationMap::iterator it = allocations_.begin();
+       it != allocations_.end(); ++it) {
+    delete it->second;
+  }
+
+  for (InternalSocketMap::iterator it = server_sockets_.begin();
+       it != server_sockets_.end(); ++it) {
+    talk_base::AsyncPacketSocket* socket = it->first;
+    delete socket;
+  }
+
+  for (ServerSocketMap::iterator it = server_listen_sockets_.begin();
+       it != server_listen_sockets_.end(); ++it) {
+    talk_base::AsyncSocket* socket = it->first;
+    delete socket;
+  }
+}
+
+void TurnServer::AddInternalSocket(talk_base::AsyncPacketSocket* socket,
+                                   ProtocolType proto) {
+  ASSERT(server_sockets_.end() == server_sockets_.find(socket));
+  server_sockets_[socket] = proto;
+  socket->SignalReadPacket.connect(this, &TurnServer::OnInternalPacket);
+}
+
+void TurnServer::AddInternalServerSocket(talk_base::AsyncSocket* socket,
+                                         ProtocolType proto) {
+  ASSERT(server_listen_sockets_.end() ==
+      server_listen_sockets_.find(socket));
+  server_listen_sockets_[socket] = proto;
+  socket->SignalReadEvent.connect(this, &TurnServer::OnNewInternalConnection);
+}
+
+void TurnServer::SetExternalSocketFactory(
+    talk_base::PacketSocketFactory* factory,
+    const talk_base::SocketAddress& external_addr) {
+  external_socket_factory_.reset(factory);
+  external_addr_ = external_addr;
+}
+
+void TurnServer::OnNewInternalConnection(talk_base::AsyncSocket* socket) {
+  ASSERT(server_listen_sockets_.find(socket) != server_listen_sockets_.end());
+  AcceptConnection(socket);
+}
+
+void TurnServer::AcceptConnection(talk_base::AsyncSocket* server_socket) {
+  // Check if someone is trying to connect to us.
+  talk_base::SocketAddress accept_addr;
+  talk_base::AsyncSocket* accepted_socket = server_socket->Accept(&accept_addr);
+  if (accepted_socket != NULL) {
+    ProtocolType proto = server_listen_sockets_[server_socket];
+    if (proto == PROTO_SSLTCP) {
+      accepted_socket = new talk_base::AsyncSSLServerSocket(accepted_socket);
+    }
+    cricket::AsyncStunTCPSocket* tcp_socket =
+        new cricket::AsyncStunTCPSocket(accepted_socket, false);
+
+    tcp_socket->SignalClose.connect(this, &TurnServer::OnInternalSocketClose);
+    // Finally add the socket so it can start communicating with the client.
+    AddInternalSocket(tcp_socket, proto);
+  }
+}
+
+void TurnServer::OnInternalSocketClose(talk_base::AsyncPacketSocket* socket,
+                                       int err) {
+  DestroyInternalSocket(socket);
+}
+
+void TurnServer::OnInternalPacket(talk_base::AsyncPacketSocket* socket,
+                                  const char* data, size_t size,
+                                  const talk_base::SocketAddress& addr) {
+  // Fail if the packet is too small to even contain a channel header.
+  if (size < TURN_CHANNEL_HEADER_SIZE) {
+   return;
+  }
+  InternalSocketMap::iterator iter = server_sockets_.find(socket);
+  ASSERT(iter != server_sockets_.end());
+  Connection conn(addr, iter->second, socket);
+  uint16 msg_type = talk_base::GetBE16(data);
+  if (!IsTurnChannelData(msg_type)) {
+    // This is a STUN message.
+    HandleStunMessage(&conn, data, size);
+  } else {
+    // This is a channel message; let the allocation handle it.
+    Allocation* allocation = FindAllocation(&conn);
+    if (allocation) {
+      allocation->HandleChannelData(data, size);
+    }
+  }
+}
+
+void TurnServer::HandleStunMessage(Connection* conn, const char* data,
+                                   size_t size) {
+  TurnMessage msg;
+  talk_base::ByteBuffer buf(data, size);
+  if (!msg.Read(&buf) || (buf.Length() > 0)) {
+    LOG(LS_WARNING) << "Received invalid STUN message";
+    return;
+  }
+
+  // If it's a STUN binding request, handle that specially.
+  if (msg.type() == STUN_BINDING_REQUEST) {
+    HandleBindingRequest(conn, &msg);
+    return;
+  }
+
+  // Look up the key that we'll use to validate the M-I. If we have an
+  // existing allocation, the key will already be cached.
+  Allocation* allocation = FindAllocation(conn);
+  std::string key;
+  if (!allocation) {
+    GetKey(&msg, &key);
+  } else {
+    key = allocation->key();
+  }
+
+  // Ensure the message is authorized; only needed for requests.
+  if (IsStunRequestType(msg.type())) {
+    if (!CheckAuthorization(conn, &msg, data, size, key)) {
+      return;
+    }
+  }
+
+  if (!allocation && msg.type() == STUN_ALLOCATE_REQUEST) {
+    // This is a new allocate request.
+    HandleAllocateRequest(conn, &msg, key);
+  } else if (allocation &&
+             (msg.type() != STUN_ALLOCATE_REQUEST ||
+              msg.transaction_id() == allocation->transaction_id())) {
+    // This is a non-allocate request, or a retransmit of an allocate.
+    // Check that the username matches the previous username used.
+    if (IsStunRequestType(msg.type()) &&
+        msg.GetByteString(STUN_ATTR_USERNAME)->GetString() !=
+            allocation->username()) {
+      SendErrorResponse(conn, &msg, STUN_ERROR_WRONG_CREDENTIALS,
+                        STUN_ERROR_REASON_WRONG_CREDENTIALS);
+      return;
+    }
+    allocation->HandleTurnMessage(&msg);
+  } else {
+    // Allocation mismatch.
+    SendErrorResponse(conn, &msg, STUN_ERROR_ALLOCATION_MISMATCH,
+                      STUN_ERROR_REASON_ALLOCATION_MISMATCH);
+  }
+}
+
+bool TurnServer::GetKey(const StunMessage* msg, std::string* key) {
+  const StunByteStringAttribute* username_attr =
+      msg->GetByteString(STUN_ATTR_USERNAME);
+  if (!username_attr) {
+    return false;
+  }
+
+  std::string username = username_attr->GetString();
+  return (auth_hook_ != NULL && auth_hook_->GetKey(username, realm_, key));
+}
+
+bool TurnServer::CheckAuthorization(Connection* conn,
+                                    const StunMessage* msg,
+                                    const char* data, size_t size,
+                                    const std::string& key) {
+  // RFC 5389, 10.2.2.
+  ASSERT(IsStunRequestType(msg->type()));
+  const StunByteStringAttribute* mi_attr =
+      msg->GetByteString(STUN_ATTR_MESSAGE_INTEGRITY);
+  const StunByteStringAttribute* username_attr =
+      msg->GetByteString(STUN_ATTR_USERNAME);
+  const StunByteStringAttribute* realm_attr =
+      msg->GetByteString(STUN_ATTR_REALM);
+  const StunByteStringAttribute* nonce_attr =
+      msg->GetByteString(STUN_ATTR_NONCE);
+
+  // Fail if no M-I.
+  if (!mi_attr) {
+    SendErrorResponseWithRealmAndNonce(conn, msg, STUN_ERROR_UNAUTHORIZED,
+                                       STUN_ERROR_REASON_UNAUTHORIZED);
+    return false;
+  }
+
+  // Fail if there is M-I but no username, nonce, or realm.
+  if (!username_attr || !realm_attr || !nonce_attr) {
+    SendErrorResponse(conn, msg, STUN_ERROR_BAD_REQUEST,
+                      STUN_ERROR_REASON_BAD_REQUEST);
+    return false;
+  }
+
+  // Fail if bad nonce.
+  if (!ValidateNonce(nonce_attr->GetString())) {
+    SendErrorResponseWithRealmAndNonce(conn, msg, STUN_ERROR_STALE_NONCE,
+                                       STUN_ERROR_REASON_STALE_NONCE);
+    return false;
+  }
+
+  // Fail if bad username or M-I.
+  // We need |data| and |size| for the call to ValidateMessageIntegrity.
+  if (key.empty() || !StunMessage::ValidateMessageIntegrity(data, size, key)) {
+    SendErrorResponseWithRealmAndNonce(conn, msg, STUN_ERROR_UNAUTHORIZED,
+                                       STUN_ERROR_REASON_UNAUTHORIZED);
+    return false;
+  }
+
+  // Fail if one-time-use nonce feature is enabled.
+  Allocation* allocation = FindAllocation(conn);
+  if (enable_otu_nonce_ && allocation &&
+      allocation->last_nonce() == nonce_attr->GetString()) {
+    SendErrorResponseWithRealmAndNonce(conn, msg, STUN_ERROR_STALE_NONCE,
+                                       STUN_ERROR_REASON_STALE_NONCE);
+    return false;
+  }
+
+  if (allocation) {
+    allocation->set_last_nonce(nonce_attr->GetString());
+  }
+  // Success.
+  return true;
+}
+
+void TurnServer::HandleBindingRequest(Connection* conn,
+                                      const StunMessage* req) {
+  StunMessage response;
+  InitResponse(req, &response);
+
+  // Tell the user the address that we received their request from.
+  StunAddressAttribute* mapped_addr_attr;
+  mapped_addr_attr = new StunXorAddressAttribute(
+      STUN_ATTR_XOR_MAPPED_ADDRESS, conn->src());
+  VERIFY(response.AddAttribute(mapped_addr_attr));
+
+  SendStun(conn, &response);
+}
+
+void TurnServer::HandleAllocateRequest(Connection* conn,
+                                       const TurnMessage* msg,
+                                       const std::string& key) {
+  // Check the parameters in the request.
+  const StunUInt32Attribute* transport_attr =
+      msg->GetUInt32(STUN_ATTR_REQUESTED_TRANSPORT);
+  if (!transport_attr) {
+    SendErrorResponse(conn, msg, STUN_ERROR_BAD_REQUEST,
+                      STUN_ERROR_REASON_BAD_REQUEST);
+    return;
+  }
+
+  // Only UDP is supported right now.
+  int proto = transport_attr->value() >> 24;
+  if (proto != IPPROTO_UDP) {
+    SendErrorResponse(conn, msg, STUN_ERROR_UNSUPPORTED_PROTOCOL,
+                      STUN_ERROR_REASON_UNSUPPORTED_PROTOCOL);
+    return;
+  }
+
+  // Create the allocation and let it send the success response.
+  // If the actual socket allocation fails, send an internal error.
+  Allocation* alloc = CreateAllocation(conn, proto, key);
+  if (alloc) {
+    alloc->HandleTurnMessage(msg);
+  } else {
+    SendErrorResponse(conn, msg, STUN_ERROR_SERVER_ERROR,
+                      "Failed to allocate socket");
+  }
+}
+
+std::string TurnServer::GenerateNonce() const {
+  // Generate a nonce of the form hex(now + HMAC-MD5(nonce_key_, now))
+  uint32 now = talk_base::Time();
+  std::string input(reinterpret_cast<const char*>(&now), sizeof(now));
+  std::string nonce = talk_base::hex_encode(input.c_str(), input.size());
+  nonce += talk_base::ComputeHmac(talk_base::DIGEST_MD5, nonce_key_, input);
+  ASSERT(nonce.size() == kNonceSize);
+  return nonce;
+}
+
+bool TurnServer::ValidateNonce(const std::string& nonce) const {
+  // Check the size.
+  if (nonce.size() != kNonceSize) {
+    return false;
+  }
+
+  // Decode the timestamp.
+  uint32 then;
+  char* p = reinterpret_cast<char*>(&then);
+  size_t len = talk_base::hex_decode(p, sizeof(then),
+      nonce.substr(0, sizeof(then) * 2));
+  if (len != sizeof(then)) {
+    return false;
+  }
+
+  // Verify the HMAC.
+  if (nonce.substr(sizeof(then) * 2) != talk_base::ComputeHmac(
+      talk_base::DIGEST_MD5, nonce_key_, std::string(p, sizeof(then)))) {
+    return false;
+  }
+
+  // Validate the timestamp.
+  return talk_base::TimeSince(then) < kNonceTimeout;
+}
+
+TurnServer::Allocation* TurnServer::FindAllocation(Connection* conn) {
+  AllocationMap::const_iterator it = allocations_.find(*conn);
+  return (it != allocations_.end()) ? it->second : NULL;
+}
+
+TurnServer::Allocation* TurnServer::CreateAllocation(Connection* conn,
+                                                     int proto,
+                                                     const std::string& key) {
+  talk_base::AsyncPacketSocket* external_socket = (external_socket_factory_) ?
+      external_socket_factory_->CreateUdpSocket(external_addr_, 0, 0) : NULL;
+  if (!external_socket) {
+    return NULL;
+  }
+
+  // The Allocation takes ownership of the socket.
+  Allocation* allocation = new Allocation(this,
+      thread_, *conn, external_socket, key);
+  allocation->SignalDestroyed.connect(this, &TurnServer::OnAllocationDestroyed);
+  allocations_[*conn] = allocation;
+  return allocation;
+}
+
+void TurnServer::SendErrorResponse(Connection* conn,
+                                   const StunMessage* req,
+                                   int code, const std::string& reason) {
+  TurnMessage resp;
+  InitErrorResponse(req, code, reason, &resp);
+  LOG(LS_INFO) << "Sending error response, type=" << resp.type()
+               << ", code=" << code << ", reason=" << reason;
+  SendStun(conn, &resp);
+}
+
+void TurnServer::SendErrorResponseWithRealmAndNonce(
+    Connection* conn, const StunMessage* msg,
+    int code, const std::string& reason) {
+  TurnMessage resp;
+  InitErrorResponse(msg, code, reason, &resp);
+  VERIFY(resp.AddAttribute(new StunByteStringAttribute(
+      STUN_ATTR_NONCE, GenerateNonce())));
+  VERIFY(resp.AddAttribute(new StunByteStringAttribute(
+      STUN_ATTR_REALM, realm_)));
+  SendStun(conn, &resp);
+}
+
+void TurnServer::SendStun(Connection* conn, StunMessage* msg) {
+  talk_base::ByteBuffer buf;
+  // Add a SOFTWARE attribute if one is set.
+  if (!software_.empty()) {
+    VERIFY(msg->AddAttribute(
+        new StunByteStringAttribute(STUN_ATTR_SOFTWARE, software_)));
+  }
+  msg->Write(&buf);
+  Send(conn, buf);
+}
+
+void TurnServer::Send(Connection* conn,
+                      const talk_base::ByteBuffer& buf) {
+  conn->socket()->SendTo(buf.Data(), buf.Length(), conn->src());
+}
+
+void TurnServer::OnAllocationDestroyed(Allocation* allocation) {
+  // Removing the internal socket if the connection is not udp.
+  talk_base::AsyncPacketSocket* socket = allocation->conn()->socket();
+  InternalSocketMap::iterator iter = server_sockets_.find(socket);
+  ASSERT(iter != server_sockets_.end());
+  // Skip if the socket serving this allocation is UDP, as this will be shared
+  // by all allocations.
+  if (iter->second != cricket::PROTO_UDP) {
+    DestroyInternalSocket(socket);
+  }
+
+  AllocationMap::iterator it = allocations_.find(*(allocation->conn()));
+  if (it != allocations_.end())
+    allocations_.erase(it);
+}
+
+void TurnServer::DestroyInternalSocket(talk_base::AsyncPacketSocket* socket) {
+  InternalSocketMap::iterator iter = server_sockets_.find(socket);
+  if (iter != server_sockets_.end()) {
+    talk_base::AsyncPacketSocket* socket = iter->first;
+    delete socket;
+    server_sockets_.erase(iter);
+  }
+}
+
+TurnServer::Connection::Connection(const talk_base::SocketAddress& src,
+                                   ProtocolType proto,
+                                   talk_base::AsyncPacketSocket* socket)
+    : src_(src),
+      dst_(socket->GetRemoteAddress()),
+      proto_(proto),
+      socket_(socket) {
+}
+
+bool TurnServer::Connection::operator==(const Connection& c) const {
+  return src_ == c.src_ && dst_ == c.dst_ && proto_ == c.proto_;
+}
+
+bool TurnServer::Connection::operator<(const Connection& c) const {
+  return src_ < c.src_ || dst_ < c.dst_ || proto_ < c.proto_;
+}
+
+std::string TurnServer::Connection::ToString() const {
+  const char* const kProtos[] = {
+      "unknown", "udp", "tcp", "ssltcp"
+  };
+  std::ostringstream ost;
+  ost << src_.ToString() << "-" << dst_.ToString() << ":"<< kProtos[proto_];
+  return ost.str();
+}
+
+TurnServer::Allocation::Allocation(TurnServer* server,
+                                   talk_base::Thread* thread,
+                                   const Connection& conn,
+                                   talk_base::AsyncPacketSocket* socket,
+                                   const std::string& key)
+    : server_(server),
+      thread_(thread),
+      conn_(conn),
+      external_socket_(socket),
+      key_(key) {
+  external_socket_->SignalReadPacket.connect(
+      this, &TurnServer::Allocation::OnExternalPacket);
+}
+
+TurnServer::Allocation::~Allocation() {
+  for (ChannelList::iterator it = channels_.begin();
+       it != channels_.end(); ++it) {
+    delete *it;
+  }
+  for (PermissionList::iterator it = perms_.begin();
+       it != perms_.end(); ++it) {
+    delete *it;
+  }
+  thread_->Clear(this, MSG_TIMEOUT);
+  LOG_J(LS_INFO, this) << "Allocation destroyed";
+}
+
+std::string TurnServer::Allocation::ToString() const {
+  std::ostringstream ost;
+  ost << "Alloc[" << conn_.ToString() << "]";
+  return ost.str();
+}
+
+void TurnServer::Allocation::HandleTurnMessage(const TurnMessage* msg) {
+  ASSERT(msg != NULL);
+  switch (msg->type()) {
+    case STUN_ALLOCATE_REQUEST:
+      HandleAllocateRequest(msg);
+      break;
+    case TURN_REFRESH_REQUEST:
+      HandleRefreshRequest(msg);
+      break;
+    case TURN_SEND_INDICATION:
+      HandleSendIndication(msg);
+      break;
+    case TURN_CREATE_PERMISSION_REQUEST:
+      HandleCreatePermissionRequest(msg);
+      break;
+    case TURN_CHANNEL_BIND_REQUEST:
+      HandleChannelBindRequest(msg);
+      break;
+    default:
+      // Not sure what to do with this, just eat it.
+      LOG_J(LS_WARNING, this) << "Invalid TURN message type received: "
+                              << msg->type();
+  }
+}
+
+void TurnServer::Allocation::HandleAllocateRequest(const TurnMessage* msg) {
+  // Copy the important info from the allocate request.
+  transaction_id_ = msg->transaction_id();
+  const StunByteStringAttribute* username_attr =
+      msg->GetByteString(STUN_ATTR_USERNAME);
+  ASSERT(username_attr != NULL);
+  username_ = username_attr->GetString();
+
+  // Figure out the lifetime and start the allocation timer.
+  int lifetime_secs = ComputeLifetime(msg);
+  thread_->PostDelayed(lifetime_secs * 1000, this, MSG_TIMEOUT);
+
+  LOG_J(LS_INFO, this) << "Created allocation, lifetime=" << lifetime_secs;
+
+  // We've already validated all the important bits; just send a response here.
+  TurnMessage response;
+  InitResponse(msg, &response);
+
+  StunAddressAttribute* mapped_addr_attr =
+      new StunXorAddressAttribute(STUN_ATTR_XOR_MAPPED_ADDRESS, conn_.src());
+  StunAddressAttribute* relayed_addr_attr =
+      new StunXorAddressAttribute(STUN_ATTR_XOR_RELAYED_ADDRESS,
+          external_socket_->GetLocalAddress());
+  StunUInt32Attribute* lifetime_attr =
+      new StunUInt32Attribute(STUN_ATTR_LIFETIME, lifetime_secs);
+  VERIFY(response.AddAttribute(mapped_addr_attr));
+  VERIFY(response.AddAttribute(relayed_addr_attr));
+  VERIFY(response.AddAttribute(lifetime_attr));
+
+  SendResponse(&response);
+}
+
+void TurnServer::Allocation::HandleRefreshRequest(const TurnMessage* msg) {
+  // Figure out the new lifetime.
+  int lifetime_secs = ComputeLifetime(msg);
+
+  // Reset the expiration timer.
+  thread_->Clear(this, MSG_TIMEOUT);
+  thread_->PostDelayed(lifetime_secs * 1000, this, MSG_TIMEOUT);
+
+  LOG_J(LS_INFO, this) << "Refreshed allocation, lifetime=" << lifetime_secs;
+
+  // Send a success response with a LIFETIME attribute.
+  TurnMessage response;
+  InitResponse(msg, &response);
+
+  StunUInt32Attribute* lifetime_attr =
+      new StunUInt32Attribute(STUN_ATTR_LIFETIME, lifetime_secs);
+  VERIFY(response.AddAttribute(lifetime_attr));
+
+  SendResponse(&response);
+}
+
+void TurnServer::Allocation::HandleSendIndication(const TurnMessage* msg) {
+  // Check mandatory attributes.
+  const StunByteStringAttribute* data_attr = msg->GetByteString(STUN_ATTR_DATA);
+  const StunAddressAttribute* peer_attr =
+      msg->GetAddress(STUN_ATTR_XOR_PEER_ADDRESS);
+  if (!data_attr || !peer_attr) {
+    LOG_J(LS_WARNING, this) << "Received invalid send indication";
+    return;
+  }
+
+  // If a permission exists, send the data on to the peer.
+  if (HasPermission(peer_attr->GetAddress().ipaddr())) {
+    SendExternal(data_attr->bytes(), data_attr->length(),
+                 peer_attr->GetAddress());
+  } else {
+    LOG_J(LS_WARNING, this) << "Received send indication without permission"
+                            << "peer=" << peer_attr->GetAddress();
+  }
+}
+
+void TurnServer::Allocation::HandleCreatePermissionRequest(
+    const TurnMessage* msg) {
+  // Check mandatory attributes.
+  const StunAddressAttribute* peer_attr =
+      msg->GetAddress(STUN_ATTR_XOR_PEER_ADDRESS);
+  if (!peer_attr) {
+    SendBadRequestResponse(msg);
+    return;
+  }
+
+  // Add this permission.
+  AddPermission(peer_attr->GetAddress().ipaddr());
+
+  LOG_J(LS_INFO, this) << "Created permission, peer="
+                       << peer_attr->GetAddress();
+
+  // Send a success response.
+  TurnMessage response;
+  InitResponse(msg, &response);
+  SendResponse(&response);
+}
+
+void TurnServer::Allocation::HandleChannelBindRequest(const TurnMessage* msg) {
+  // Check mandatory attributes.
+  const StunUInt32Attribute* channel_attr =
+      msg->GetUInt32(STUN_ATTR_CHANNEL_NUMBER);
+  const StunAddressAttribute* peer_attr =
+      msg->GetAddress(STUN_ATTR_XOR_PEER_ADDRESS);
+  if (!channel_attr || !peer_attr) {
+    SendBadRequestResponse(msg);
+    return;
+  }
+
+  // Check that channel id is valid.
+  int channel_id = channel_attr->value() >> 16;
+  if (channel_id < kMinChannelNumber || channel_id > kMaxChannelNumber) {
+    SendBadRequestResponse(msg);
+    return;
+  }
+
+  // Check that this channel id isn't bound to another transport address, and
+  // that this transport address isn't bound to another channel id.
+  Channel* channel1 = FindChannel(channel_id);
+  Channel* channel2 = FindChannel(peer_attr->GetAddress());
+  if (channel1 != channel2) {
+    SendBadRequestResponse(msg);
+    return;
+  }
+
+  // Add or refresh this channel.
+  if (!channel1) {
+    channel1 = new Channel(thread_, channel_id, peer_attr->GetAddress());
+    channel1->SignalDestroyed.connect(this,
+        &TurnServer::Allocation::OnChannelDestroyed);
+    channels_.push_back(channel1);
+  } else {
+    channel1->Refresh();
+  }
+
+  // Channel binds also refresh permissions.
+  AddPermission(peer_attr->GetAddress().ipaddr());
+
+  LOG_J(LS_INFO, this) << "Bound channel, id=" << channel_id
+                       << ", peer=" << peer_attr->GetAddress();
+
+  // Send a success response.
+  TurnMessage response;
+  InitResponse(msg, &response);
+  SendResponse(&response);
+}
+
+void TurnServer::Allocation::HandleChannelData(const char* data, size_t size) {
+  // Extract the channel number from the data.
+  uint16 channel_id = talk_base::GetBE16(data);
+  Channel* channel = FindChannel(channel_id);
+  if (channel) {
+    // Send the data to the peer address.
+    SendExternal(data + TURN_CHANNEL_HEADER_SIZE,
+                 size - TURN_CHANNEL_HEADER_SIZE, channel->peer());
+  } else {
+    LOG_J(LS_WARNING, this) << "Received channel data for invalid channel, id="
+                            << channel_id;
+  }
+}
+
+void TurnServer::Allocation::OnExternalPacket(
+    talk_base::AsyncPacketSocket* socket,
+    const char* data, size_t size,
+    const talk_base::SocketAddress& addr) {
+  ASSERT(external_socket_.get() == socket);
+  Channel* channel = FindChannel(addr);
+  if (channel) {
+    // There is a channel bound to this address. Send as a channel message.
+    talk_base::ByteBuffer buf;
+    buf.WriteUInt16(channel->id());
+    buf.WriteUInt16(size);
+    buf.WriteBytes(data, size);
+    server_->Send(&conn_, buf);
+  } else if (HasPermission(addr.ipaddr())) {
+    // No channel, but a permission exists. Send as a data indication.
+    TurnMessage msg;
+    msg.SetType(TURN_DATA_INDICATION);
+    msg.SetTransactionID(
+        talk_base::CreateRandomString(kStunTransactionIdLength));
+    VERIFY(msg.AddAttribute(new StunXorAddressAttribute(
+        STUN_ATTR_XOR_PEER_ADDRESS, addr)));
+    VERIFY(msg.AddAttribute(new StunByteStringAttribute(
+        STUN_ATTR_DATA, data, size)));
+    server_->SendStun(&conn_, &msg);
+  } else {
+    LOG_J(LS_WARNING, this) << "Received external packet without permission, "
+                            << "peer=" << addr;
+  }
+}
+
+int TurnServer::Allocation::ComputeLifetime(const TurnMessage* msg) {
+  // Return the smaller of our default lifetime and the requested lifetime.
+  uint32 lifetime = kDefaultAllocationTimeout / 1000;  // convert to seconds
+  const StunUInt32Attribute* lifetime_attr = msg->GetUInt32(STUN_ATTR_LIFETIME);
+  if (lifetime_attr && lifetime_attr->value() < lifetime) {
+    lifetime = lifetime_attr->value();
+  }
+  return lifetime;
+}
+
+bool TurnServer::Allocation::HasPermission(const talk_base::IPAddress& addr) {
+  return (FindPermission(addr) != NULL);
+}
+
+void TurnServer::Allocation::AddPermission(const talk_base::IPAddress& addr) {
+  Permission* perm = FindPermission(addr);
+  if (!perm) {
+    perm = new Permission(thread_, addr);
+    perm->SignalDestroyed.connect(
+        this, &TurnServer::Allocation::OnPermissionDestroyed);
+    perms_.push_back(perm);
+  } else {
+    perm->Refresh();
+  }
+}
+
+TurnServer::Permission* TurnServer::Allocation::FindPermission(
+    const talk_base::IPAddress& addr) const {
+  for (PermissionList::const_iterator it = perms_.begin();
+       it != perms_.end(); ++it) {
+    if ((*it)->peer() == addr)
+      return *it;
+  }
+  return NULL;
+}
+
+TurnServer::Channel* TurnServer::Allocation::FindChannel(int channel_id) const {
+  for (ChannelList::const_iterator it = channels_.begin();
+       it != channels_.end(); ++it) {
+    if ((*it)->id() == channel_id)
+      return *it;
+  }
+  return NULL;
+}
+
+TurnServer::Channel* TurnServer::Allocation::FindChannel(
+    const talk_base::SocketAddress& addr) const {
+  for (ChannelList::const_iterator it = channels_.begin();
+       it != channels_.end(); ++it) {
+    if ((*it)->peer() == addr)
+      return *it;
+  }
+  return NULL;
+}
+
+void TurnServer::Allocation::SendResponse(TurnMessage* msg) {
+  // Success responses always have M-I.
+  msg->AddMessageIntegrity(key_);
+  server_->SendStun(&conn_, msg);
+}
+
+void TurnServer::Allocation::SendBadRequestResponse(const TurnMessage* req) {
+  SendErrorResponse(req, STUN_ERROR_BAD_REQUEST, STUN_ERROR_REASON_BAD_REQUEST);
+}
+
+void TurnServer::Allocation::SendErrorResponse(const TurnMessage* req, int code,
+                                       const std::string& reason) {
+  server_->SendErrorResponse(&conn_, req, code, reason);
+}
+
+void TurnServer::Allocation::SendExternal(const void* data, size_t size,
+                                  const talk_base::SocketAddress& peer) {
+  external_socket_->SendTo(data, size, peer);
+}
+
+void TurnServer::Allocation::OnMessage(talk_base::Message* msg) {
+  ASSERT(msg->message_id == MSG_TIMEOUT);
+  SignalDestroyed(this);
+  delete this;
+}
+
+void TurnServer::Allocation::OnPermissionDestroyed(Permission* perm) {
+  PermissionList::iterator it = std::find(perms_.begin(), perms_.end(), perm);
+  ASSERT(it != perms_.end());
+  perms_.erase(it);
+}
+
+void TurnServer::Allocation::OnChannelDestroyed(Channel* channel) {
+  ChannelList::iterator it =
+      std::find(channels_.begin(), channels_.end(), channel);
+  ASSERT(it != channels_.end());
+  channels_.erase(it);
+}
+
+TurnServer::Permission::Permission(talk_base::Thread* thread,
+                                   const talk_base::IPAddress& peer)
+    : thread_(thread), peer_(peer) {
+  Refresh();
+}
+
+TurnServer::Permission::~Permission() {
+  thread_->Clear(this, MSG_TIMEOUT);
+}
+
+void TurnServer::Permission::Refresh() {
+  thread_->Clear(this, MSG_TIMEOUT);
+  thread_->PostDelayed(kPermissionTimeout, this, MSG_TIMEOUT);
+}
+
+void TurnServer::Permission::OnMessage(talk_base::Message* msg) {
+  ASSERT(msg->message_id == MSG_TIMEOUT);
+  SignalDestroyed(this);
+  delete this;
+}
+
+TurnServer::Channel::Channel(talk_base::Thread* thread, int id,
+                             const talk_base::SocketAddress& peer)
+    : thread_(thread), id_(id), peer_(peer) {
+  Refresh();
+}
+
+TurnServer::Channel::~Channel() {
+  thread_->Clear(this, MSG_TIMEOUT);
+}
+
+void TurnServer::Channel::Refresh() {
+  thread_->Clear(this, MSG_TIMEOUT);
+  thread_->PostDelayed(kChannelTimeout, this, MSG_TIMEOUT);
+}
+
+void TurnServer::Channel::OnMessage(talk_base::Message* msg) {
+  ASSERT(msg->message_id == MSG_TIMEOUT);
+  SignalDestroyed(this);
+  delete this;
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/turnserver.h b/talk/p2p/base/turnserver.h
new file mode 100644
index 0000000..56ce2fc
--- /dev/null
+++ b/talk/p2p/base/turnserver.h
@@ -0,0 +1,186 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_TURNSERVER_H_
+#define TALK_P2P_BASE_TURNSERVER_H_
+
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+
+#include "talk/base/messagequeue.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socketaddress.h"
+#include "talk/p2p/base/portinterface.h"
+
+namespace talk_base {
+class AsyncPacketSocket;
+class ByteBuffer;
+class PacketSocketFactory;
+class Thread;
+}
+
+namespace cricket {
+
+class StunMessage;
+class TurnMessage;
+
+// The default server port for TURN, as specified in RFC5766.
+const int TURN_SERVER_PORT = 3478;
+
+// An interface through which the MD5 credential hash can be retrieved.
+class TurnAuthInterface {
+ public:
+  // Gets HA1 for the specified user and realm.
+  // HA1 = MD5(A1) = MD5(username:realm:password).
+  // Return true if the given username and realm are valid, or false if not.
+  virtual bool GetKey(const std::string& username, const std::string& realm,
+                      std::string* key) = 0;
+};
+
+// The core TURN server class. Give it a socket to listen on via
+// AddInternalServerSocket, and a factory to create external sockets via
+// SetExternalSocketFactory, and it's ready to go.
+// Not yet wired up: TCP support.
+class TurnServer : public sigslot::has_slots<> {
+ public:
+  explicit TurnServer(talk_base::Thread* thread);
+  ~TurnServer();
+
+  // Gets/sets the realm value to use for the server.
+  const std::string& realm() const { return realm_; }
+  void set_realm(const std::string& realm) { realm_ = realm; }
+
+  // Gets/sets the value for the SOFTWARE attribute for TURN messages.
+  const std::string& software() const { return software_; }
+  void set_software(const std::string& software) { software_ = software; }
+
+  // Sets the authentication callback; does not take ownership.
+  void set_auth_hook(TurnAuthInterface* auth_hook) { auth_hook_ = auth_hook; }
+
+  void set_enable_otu_nonce(bool enable) { enable_otu_nonce_ = enable; }
+
+  // Starts listening for packets from internal clients.
+  void AddInternalSocket(talk_base::AsyncPacketSocket* socket,
+                         ProtocolType proto);
+  // Starts listening for the connections on this socket. When someone tries
+  // to connect, the connection will be accepted and a new internal socket
+  // will be added.
+  void AddInternalServerSocket(talk_base::AsyncSocket* socket,
+                               ProtocolType proto);
+  // Specifies the factory to use for creating external sockets.
+  void SetExternalSocketFactory(talk_base::PacketSocketFactory* factory,
+                                const talk_base::SocketAddress& address);
+
+ private:
+  // Encapsulates the client's connection to the server.
+  class Connection {
+   public:
+    Connection() : proto_(PROTO_UDP), socket_(NULL) {}
+    Connection(const talk_base::SocketAddress& src,
+               ProtocolType proto,
+               talk_base::AsyncPacketSocket* socket);
+    const talk_base::SocketAddress& src() const { return src_; }
+    talk_base::AsyncPacketSocket* socket() { return socket_; }
+    bool operator==(const Connection& t) const;
+    bool operator<(const Connection& t) const;
+    std::string ToString() const;
+
+   private:
+    talk_base::SocketAddress src_;
+    talk_base::SocketAddress dst_;
+    cricket::ProtocolType proto_;
+    talk_base::AsyncPacketSocket* socket_;
+  };
+  class Allocation;
+  class Permission;
+  class Channel;
+  typedef std::map<Connection, Allocation*> AllocationMap;
+
+  void OnInternalPacket(talk_base::AsyncPacketSocket* socket, const char* data,
+                        size_t size, const talk_base::SocketAddress& address);
+
+  void OnNewInternalConnection(talk_base::AsyncSocket* socket);
+
+  // Accept connections on this server socket.
+  void AcceptConnection(talk_base::AsyncSocket* server_socket);
+  void OnInternalSocketClose(talk_base::AsyncPacketSocket* socket, int err);
+
+  void HandleStunMessage(Connection* conn, const char* data, size_t size);
+  void HandleBindingRequest(Connection* conn, const StunMessage* msg);
+  void HandleAllocateRequest(Connection* conn, const TurnMessage* msg,
+                             const std::string& key);
+
+  bool GetKey(const StunMessage* msg, std::string* key);
+  bool CheckAuthorization(Connection* conn, const StunMessage* msg,
+                          const char* data, size_t size,
+                          const std::string& key);
+  std::string GenerateNonce() const;
+  bool ValidateNonce(const std::string& nonce) const;
+
+  Allocation* FindAllocation(Connection* conn);
+  Allocation* CreateAllocation(Connection* conn, int proto,
+                               const std::string& key);
+
+  void SendErrorResponse(Connection* conn, const StunMessage* req,
+                         int code, const std::string& reason);
+
+  void SendErrorResponseWithRealmAndNonce(Connection* conn,
+                                          const StunMessage* req,
+                                          int code,
+                                          const std::string& reason);
+  void SendStun(Connection* conn, StunMessage* msg);
+  void Send(Connection* conn, const talk_base::ByteBuffer& buf);
+
+  void OnAllocationDestroyed(Allocation* allocation);
+  void DestroyInternalSocket(talk_base::AsyncPacketSocket* socket);
+
+  typedef std::map<talk_base::AsyncPacketSocket*,
+                   ProtocolType> InternalSocketMap;
+  typedef std::map<talk_base::AsyncSocket*,
+                   ProtocolType> ServerSocketMap;
+
+  talk_base::Thread* thread_;
+  std::string nonce_key_;
+  std::string realm_;
+  std::string software_;
+  TurnAuthInterface* auth_hook_;
+  // otu - one-time-use. Server will respond with 438 if it's
+  // sees the same nonce in next transaction.
+  bool enable_otu_nonce_;
+  InternalSocketMap server_sockets_;
+  ServerSocketMap server_listen_sockets_;
+  talk_base::scoped_ptr<talk_base::PacketSocketFactory>
+      external_socket_factory_;
+  talk_base::SocketAddress external_addr_;
+  AllocationMap allocations_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_TURNSERVER_H_
diff --git a/talk/p2p/base/turnserver_main.cc b/talk/p2p/base/turnserver_main.cc
new file mode 100644
index 0000000..d40fede
--- /dev/null
+++ b/talk/p2p/base/turnserver_main.cc
@@ -0,0 +1,102 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <iostream>  // NOLINT
+
+#include "talk/base/asyncudpsocket.h"
+#include "talk/base/optionsfile.h"
+#include "talk/base/thread.h"
+#include "talk/base/stringencode.h"
+#include "talk/p2p/base/basicpacketsocketfactory.h"
+#include "talk/p2p/base/turnserver.h"
+
+static const char kSoftware[] = "libjingle TurnServer";
+
+class TurnFileAuth : public cricket::TurnAuthInterface {
+ public:
+  explicit TurnFileAuth(const std::string& path) : file_(path) {
+    file_.Load();
+  }
+  virtual bool GetKey(const std::string& username, const std::string& realm,
+                      std::string* key) {
+    // File is stored as lines of <username>=<HA1>.
+    // Generate HA1 via "echo -n "<username>:<realm>:<password>" | md5sum"
+    std::string hex;
+    bool ret = file_.GetStringValue(username, &hex);
+    if (ret) {
+      char buf[32];
+      size_t len = talk_base::hex_decode(buf, sizeof(buf), hex);
+      *key = std::string(buf, len);
+    }
+    return ret;
+  }
+ private:
+  talk_base::OptionsFile file_;
+};
+
+int main(int argc, char **argv) {
+  if (argc != 5) {
+    std::cerr << "usage: turnserver int-addr ext-ip realm auth-file"
+              << std::endl;
+    return 1;
+  }
+
+  talk_base::SocketAddress int_addr;
+  if (!int_addr.FromString(argv[1])) {
+    std::cerr << "Unable to parse IP address: " << argv[1] << std::endl;
+    return 1;
+  }
+
+  talk_base::IPAddress ext_addr;
+  if (!IPFromString(argv[2], &ext_addr)) {
+    std::cerr << "Unable to parse IP address: " << argv[2] << std::endl;
+    return 1;
+  }
+
+  talk_base::Thread* main = talk_base::Thread::Current();
+  talk_base::AsyncUDPSocket* int_socket =
+      talk_base::AsyncUDPSocket::Create(main->socketserver(), int_addr);
+  if (!int_socket) {
+    std::cerr << "Failed to create a UDP socket bound at"
+              << int_addr.ToString() << std::endl;
+    return 1;
+  }
+
+  cricket::TurnServer server(main);
+  TurnFileAuth auth(argv[4]);
+  server.set_realm(argv[3]);
+  server.set_software(kSoftware);
+  server.set_auth_hook(&auth);
+  server.AddInternalSocket(int_socket, cricket::PROTO_UDP);
+  server.SetExternalSocketFactory(new talk_base::BasicPacketSocketFactory(),
+                                  talk_base::SocketAddress(ext_addr, 0));
+
+  std::cout << "Listening internally at " << int_addr.ToString() << std::endl;
+
+  main->Run();
+  return 0;
+}
diff --git a/talk/p2p/base/udpport.h b/talk/p2p/base/udpport.h
new file mode 100644
index 0000000..fc981fd
--- /dev/null
+++ b/talk/p2p/base/udpport.h
@@ -0,0 +1,34 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_UDPPORT_H_
+#define TALK_P2P_BASE_UDPPORT_H_
+
+// StunPort will be handling UDPPort functionality.
+#include "talk/p2p/base/stunport.h"
+
+#endif  // TALK_P2P_BASE_UDPPORT_H_
diff --git a/talk/p2p/client/autoportallocator.h b/talk/p2p/client/autoportallocator.h
new file mode 100644
index 0000000..4ec324b
--- /dev/null
+++ b/talk/p2p/client/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/base/sigslot.h"
+#include "talk/p2p/client/httpportallocator.h"
+#include "talk/xmpp/jingleinfotask.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/p2p/client/basicportallocator.cc b/talk/p2p/client/basicportallocator.cc
new file mode 100644
index 0000000..7a61093
--- /dev/null
+++ b/talk/p2p/client/basicportallocator.cc
@@ -0,0 +1,1072 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/p2p/client/basicportallocator.h"
+
+#include <string>
+#include <vector>
+
+#include "talk/base/common.h"
+#include "talk/base/helpers.h"
+#include "talk/base/host.h"
+#include "talk/base/logging.h"
+#include "talk/p2p/base/basicpacketsocketfactory.h"
+#include "talk/p2p/base/common.h"
+#include "talk/p2p/base/port.h"
+#include "talk/p2p/base/relayport.h"
+#include "talk/p2p/base/stunport.h"
+#include "talk/p2p/base/tcpport.h"
+#include "talk/p2p/base/turnport.h"
+#include "talk/p2p/base/udpport.h"
+
+using talk_base::CreateRandomId;
+using talk_base::CreateRandomString;
+
+namespace {
+
+const uint32 MSG_CONFIG_START = 1;
+const uint32 MSG_CONFIG_READY = 2;
+const uint32 MSG_ALLOCATE = 3;
+const uint32 MSG_ALLOCATION_PHASE = 4;
+const uint32 MSG_SHAKE = 5;
+const uint32 MSG_SEQUENCEOBJECTS_CREATED = 6;
+const uint32 MSG_CONFIG_STOP = 7;
+
+const uint32 ALLOCATE_DELAY = 250;
+const uint32 ALLOCATION_STEP_DELAY = 1 * 1000;
+
+const int PHASE_UDP = 0;
+const int PHASE_RELAY = 1;
+const int PHASE_TCP = 2;
+const int PHASE_SSLTCP = 3;
+
+const int kNumPhases = 4;
+
+// Both these values are in bytes.
+const int kLargeSocketSendBufferSize = 128 * 1024;
+const int kNormalSocketSendBufferSize = 64 * 1024;
+
+const int SHAKE_MIN_DELAY = 45 * 1000;  // 45 seconds
+const int SHAKE_MAX_DELAY = 90 * 1000;  // 90 seconds
+
+int ShakeDelay() {
+  int range = SHAKE_MAX_DELAY - SHAKE_MIN_DELAY + 1;
+  return SHAKE_MIN_DELAY + CreateRandomId() % range;
+}
+
+}  // namespace
+
+namespace cricket {
+
+const uint32 DISABLE_ALL_PHASES =
+  PORTALLOCATOR_DISABLE_UDP
+  | PORTALLOCATOR_DISABLE_TCP
+  | PORTALLOCATOR_DISABLE_STUN
+  | PORTALLOCATOR_DISABLE_RELAY;
+
+// Performs the allocation of ports, in a sequenced (timed) manner, for a given
+// network and IP address.
+class AllocationSequence : public talk_base::MessageHandler,
+                           public sigslot::has_slots<> {
+ public:
+  enum State {
+    kInit,       // Initial state.
+    kRunning,    // Started allocating ports.
+    kStopped,    // Stopped from running.
+    kCompleted,  // All ports are allocated.
+
+    // kInit --> kRunning --> {kCompleted|kStopped}
+  };
+
+  AllocationSequence(BasicPortAllocatorSession* session,
+                     talk_base::Network* network,
+                     PortConfiguration* config,
+                     uint32 flags);
+  ~AllocationSequence();
+  bool Init();
+
+  State state() const { return state_; }
+
+  // Disables the phases for a new sequence that this one already covers for an
+  // equivalent network setup.
+  void DisableEquivalentPhases(talk_base::Network* network,
+      PortConfiguration* config, uint32* flags);
+
+  // Starts and stops the sequence.  When started, it will continue allocating
+  // new ports on its own timed schedule.
+  void Start();
+  void Stop();
+
+  // MessageHandler
+  void OnMessage(talk_base::Message* msg);
+
+  void EnableProtocol(ProtocolType proto);
+  bool ProtocolEnabled(ProtocolType proto) const;
+
+  // Signal from AllocationSequence, when it's done with allocating ports.
+  // This signal is useful, when port allocation fails which doesn't result
+  // in any candidates. Using this signal BasicPortAllocatorSession can send
+  // its candidate discovery conclusion signal. Without this signal,
+  // BasicPortAllocatorSession doesn't have any event to trigger signal. This
+  // can also be achieved by starting timer in BPAS.
+  sigslot::signal1<AllocationSequence*> SignalPortAllocationComplete;
+
+ private:
+  typedef std::vector<ProtocolType> ProtocolList;
+
+  bool IsFlagSet(uint32 flag) {
+    return ((flags_ & flag) != 0);
+  }
+  void CreateUDPPorts();
+  void CreateTCPPorts();
+  void CreateStunPorts();
+  void CreateRelayPorts();
+  void CreateGturnPort(const RelayServerConfig& config);
+  void CreateTurnPort(const RelayServerConfig& config);
+
+  void OnReadPacket(talk_base::AsyncPacketSocket* socket,
+                    const char* data, size_t size,
+                    const talk_base::SocketAddress& remote_addr);
+  void OnPortDestroyed(PortInterface* port);
+
+  BasicPortAllocatorSession* session_;
+  talk_base::Network* network_;
+  talk_base::IPAddress ip_;
+  PortConfiguration* config_;
+  State state_;
+  uint32 flags_;
+  ProtocolList protocols_;
+  talk_base::scoped_ptr<talk_base::AsyncPacketSocket> udp_socket_;
+  // Keeping a list of all UDP based ports.
+  std::deque<Port*> ports;
+  int phase_;
+};
+
+// BasicPortAllocator
+BasicPortAllocator::BasicPortAllocator(
+    talk_base::NetworkManager* network_manager,
+    talk_base::PacketSocketFactory* socket_factory)
+    : network_manager_(network_manager),
+      socket_factory_(socket_factory) {
+  ASSERT(socket_factory_ != NULL);
+  Construct();
+}
+
+BasicPortAllocator::BasicPortAllocator(
+    talk_base::NetworkManager* network_manager)
+    : network_manager_(network_manager),
+      socket_factory_(NULL) {
+  Construct();
+}
+
+BasicPortAllocator::BasicPortAllocator(
+    talk_base::NetworkManager* network_manager,
+    talk_base::PacketSocketFactory* socket_factory,
+    const talk_base::SocketAddress& stun_address)
+    : network_manager_(network_manager),
+      socket_factory_(socket_factory),
+      stun_address_(stun_address) {
+  ASSERT(socket_factory_ != NULL);
+  Construct();
+}
+
+BasicPortAllocator::BasicPortAllocator(
+    talk_base::NetworkManager* network_manager,
+    const talk_base::SocketAddress& stun_address,
+    const talk_base::SocketAddress& relay_address_udp,
+    const talk_base::SocketAddress& relay_address_tcp,
+    const talk_base::SocketAddress& relay_address_ssl)
+    : network_manager_(network_manager),
+      socket_factory_(NULL),
+      stun_address_(stun_address) {
+
+  RelayServerConfig config(RELAY_GTURN);
+  if (!relay_address_udp.IsAny())
+    config.ports.push_back(ProtocolAddress(relay_address_udp, PROTO_UDP));
+  if (!relay_address_tcp.IsAny())
+    config.ports.push_back(ProtocolAddress(relay_address_tcp, PROTO_TCP));
+  if (!relay_address_ssl.IsAny())
+    config.ports.push_back(ProtocolAddress(relay_address_ssl, PROTO_SSLTCP));
+  AddRelay(config);
+
+  Construct();
+}
+
+void BasicPortAllocator::Construct() {
+  allow_tcp_listen_ = true;
+}
+
+BasicPortAllocator::~BasicPortAllocator() {
+}
+
+PortAllocatorSession *BasicPortAllocator::CreateSessionInternal(
+    const std::string& content_name, int component,
+    const std::string& ice_ufrag, const std::string& ice_pwd) {
+  return new BasicPortAllocatorSession(this, content_name, component,
+                                       ice_ufrag, ice_pwd);
+}
+
+// BasicPortAllocatorSession
+BasicPortAllocatorSession::BasicPortAllocatorSession(
+    BasicPortAllocator *allocator,
+    const std::string& content_name,
+    int component,
+    const std::string& ice_ufrag,
+    const std::string& ice_pwd)
+    : PortAllocatorSession(content_name, component,
+                           ice_ufrag, ice_pwd, allocator->flags()),
+      allocator_(allocator), network_thread_(NULL),
+      socket_factory_(allocator->socket_factory()),
+      configuration_done_(false),
+      allocation_started_(false),
+      network_manager_started_(false),
+      running_(false),
+      allocation_sequences_created_(false) {
+  allocator_->network_manager()->SignalNetworksChanged.connect(
+      this, &BasicPortAllocatorSession::OnNetworksChanged);
+  allocator_->network_manager()->StartUpdating();
+}
+
+BasicPortAllocatorSession::~BasicPortAllocatorSession() {
+  allocator_->network_manager()->StopUpdating();
+  if (network_thread_ != NULL)
+    network_thread_->Clear(this);
+
+  std::vector<PortData>::iterator it;
+  for (it = ports_.begin(); it != ports_.end(); it++)
+    delete it->port();
+
+  for (uint32 i = 0; i < configs_.size(); ++i)
+    delete configs_[i];
+
+  for (uint32 i = 0; i < sequences_.size(); ++i)
+    delete sequences_[i];
+}
+
+void BasicPortAllocatorSession::StartGettingPorts() {
+  network_thread_ = talk_base::Thread::Current();
+  if (!socket_factory_) {
+    owned_socket_factory_.reset(
+        new talk_base::BasicPacketSocketFactory(network_thread_));
+    socket_factory_ = owned_socket_factory_.get();
+  }
+
+  running_ = true;
+  network_thread_->Post(this, MSG_CONFIG_START);
+
+  if (flags() & PORTALLOCATOR_ENABLE_SHAKER)
+    network_thread_->PostDelayed(ShakeDelay(), this, MSG_SHAKE);
+}
+
+void BasicPortAllocatorSession::StopGettingPorts() {
+  ASSERT(talk_base::Thread::Current() == network_thread_);
+  running_ = false;
+  network_thread_->Clear(this, MSG_ALLOCATE);
+  for (uint32 i = 0; i < sequences_.size(); ++i)
+    sequences_[i]->Stop();
+  network_thread_->Post(this, MSG_CONFIG_STOP);
+}
+
+void BasicPortAllocatorSession::OnMessage(talk_base::Message *message) {
+  switch (message->message_id) {
+  case MSG_CONFIG_START:
+    ASSERT(talk_base::Thread::Current() == network_thread_);
+    GetPortConfigurations();
+    break;
+
+  case MSG_CONFIG_READY:
+    ASSERT(talk_base::Thread::Current() == network_thread_);
+    OnConfigReady(static_cast<PortConfiguration*>(message->pdata));
+    break;
+
+  case MSG_ALLOCATE:
+    ASSERT(talk_base::Thread::Current() == network_thread_);
+    OnAllocate();
+    break;
+
+  case MSG_SHAKE:
+    ASSERT(talk_base::Thread::Current() == network_thread_);
+    OnShake();
+    break;
+  case MSG_SEQUENCEOBJECTS_CREATED:
+    ASSERT(talk_base::Thread::Current() == network_thread_);
+    OnAllocationSequenceObjectsCreated();
+    break;
+  case MSG_CONFIG_STOP:
+    ASSERT(talk_base::Thread::Current() == network_thread_);
+    OnConfigStop();
+    break;
+  default:
+    ASSERT(false);
+  }
+}
+
+void BasicPortAllocatorSession::GetPortConfigurations() {
+  PortConfiguration* config = new PortConfiguration(allocator_->stun_address(),
+                                                    username(),
+                                                    password());
+
+  for (size_t i = 0; i < allocator_->relays().size(); ++i) {
+    config->AddRelay(allocator_->relays()[i]);
+  }
+  ConfigReady(config);
+}
+
+void BasicPortAllocatorSession::ConfigReady(PortConfiguration* config) {
+  network_thread_->Post(this, MSG_CONFIG_READY, config);
+}
+
+// Adds a configuration to the list.
+void BasicPortAllocatorSession::OnConfigReady(PortConfiguration* config) {
+  if (config)
+    configs_.push_back(config);
+
+  AllocatePorts();
+}
+
+void BasicPortAllocatorSession::OnConfigStop() {
+  ASSERT(talk_base::Thread::Current() == network_thread_);
+
+  // If any of the allocated ports have not completed the candidates allocation,
+  // mark those as error. Since session doesn't need any new candidates
+  // at this stage of the allocation, it's safe to discard any new candidates.
+  bool send_signal = false;
+  for (std::vector<PortData>::iterator it = ports_.begin();
+       it != ports_.end(); ++it) {
+    if (!it->complete()) {
+      // Updating port state to error, which didn't finish allocating candidates
+      // yet.
+      it->set_error();
+      send_signal = true;
+    }
+  }
+
+  // Did we stop any running sequences?
+  for (std::vector<AllocationSequence*>::iterator it = sequences_.begin();
+       it != sequences_.end() && !send_signal; ++it) {
+    if ((*it)->state() == AllocationSequence::kStopped) {
+      send_signal = true;
+    }
+  }
+
+  // If we stopped anything that was running, send a done signal now.
+  if (send_signal) {
+    MaybeSignalCandidatesAllocationDone();
+  }
+}
+
+void BasicPortAllocatorSession::AllocatePorts() {
+  ASSERT(talk_base::Thread::Current() == network_thread_);
+  network_thread_->Post(this, MSG_ALLOCATE);
+}
+
+void BasicPortAllocatorSession::OnAllocate() {
+  if (network_manager_started_)
+    DoAllocate();
+
+  allocation_started_ = true;
+  if (running_)
+    network_thread_->PostDelayed(ALLOCATE_DELAY, this, MSG_ALLOCATE);
+}
+
+// For each network, see if we have a sequence that covers it already.  If not,
+// create a new sequence to create the appropriate ports.
+void BasicPortAllocatorSession::DoAllocate() {
+  bool done_signal_needed = false;
+  std::vector<talk_base::Network*> networks;
+  allocator_->network_manager()->GetNetworks(&networks);
+  if (networks.empty()) {
+    LOG(LS_WARNING) << "Machine has no networks; no ports will be allocated";
+    done_signal_needed = true;
+  } else {
+    for (uint32 i = 0; i < networks.size(); ++i) {
+      PortConfiguration* config = NULL;
+      if (configs_.size() > 0)
+        config = configs_.back();
+
+      uint32 sequence_flags = flags();
+      if ((sequence_flags & DISABLE_ALL_PHASES) == DISABLE_ALL_PHASES) {
+        // If all the ports are disabled we should just fire the allocation
+        // done event and return.
+        done_signal_needed = true;
+        break;
+      }
+
+      // Disables phases that are not specified in this config.
+      if (!config || config->stun_address.IsNil()) {
+        // No STUN ports specified in this config.
+        sequence_flags |= PORTALLOCATOR_DISABLE_STUN;
+      }
+      if (!config || config->relays.empty()) {
+        // No relay ports specified in this config.
+        sequence_flags |= PORTALLOCATOR_DISABLE_RELAY;
+      }
+
+      if (!(sequence_flags & PORTALLOCATOR_ENABLE_IPV6) &&
+          networks[i]->ip().family() == AF_INET6) {
+        // Skip IPv6 networks unless the flag's been set.
+        continue;
+      }
+
+      // Disable phases that would only create ports equivalent to
+      // ones that we have already made.
+      DisableEquivalentPhases(networks[i], config, &sequence_flags);
+
+      if ((sequence_flags & DISABLE_ALL_PHASES) == DISABLE_ALL_PHASES) {
+        // New AllocationSequence would have nothing to do, so don't make it.
+        continue;
+      }
+
+      AllocationSequence* sequence =
+          new AllocationSequence(this, networks[i], config, sequence_flags);
+      if (!sequence->Init()) {
+        delete sequence;
+        continue;
+      }
+      done_signal_needed = true;
+      sequence->SignalPortAllocationComplete.connect(
+          this, &BasicPortAllocatorSession::OnPortAllocationComplete);
+      if (running_)
+        sequence->Start();
+      sequences_.push_back(sequence);
+    }
+  }
+  if (done_signal_needed) {
+    network_thread_->Post(this, MSG_SEQUENCEOBJECTS_CREATED);
+  }
+}
+
+void BasicPortAllocatorSession::OnNetworksChanged() {
+  network_manager_started_ = true;
+  if (allocation_started_)
+    DoAllocate();
+}
+
+void BasicPortAllocatorSession::DisableEquivalentPhases(
+    talk_base::Network* network, PortConfiguration* config, uint32* flags) {
+  for (uint32 i = 0; i < sequences_.size() &&
+      (*flags & DISABLE_ALL_PHASES) != DISABLE_ALL_PHASES; ++i) {
+    sequences_[i]->DisableEquivalentPhases(network, config, flags);
+  }
+}
+
+void BasicPortAllocatorSession::AddAllocatedPort(Port* port,
+                                                 AllocationSequence * seq,
+                                                 bool prepare_address) {
+  if (!port)
+    return;
+
+  LOG(LS_INFO) << "Adding allocated port for " << content_name();
+  port->set_content_name(content_name());
+  port->set_component(component_);
+  port->set_generation(generation());
+  if (allocator_->proxy().type != talk_base::PROXY_NONE)
+    port->set_proxy(allocator_->user_agent(), allocator_->proxy());
+  port->set_send_retransmit_count_attribute((allocator_->flags() &
+      PORTALLOCATOR_ENABLE_STUN_RETRANSMIT_ATTRIBUTE) != 0);
+
+  if (content_name().compare(CN_VIDEO) == 0 &&
+      component_ == cricket::ICE_CANDIDATE_COMPONENT_RTP) {
+    // For video RTP alone, we set send-buffer sizes. This used to be set in the
+    // engines/channels.
+    int sendBufSize = (flags() & PORTALLOCATOR_USE_LARGE_SOCKET_SEND_BUFFERS)
+                      ? kLargeSocketSendBufferSize
+                      : kNormalSocketSendBufferSize;
+    port->SetOption(talk_base::Socket::OPT_SNDBUF, sendBufSize);
+  }
+
+  PortData data(port, seq);
+  ports_.push_back(data);
+
+  port->SignalCandidateReady.connect(
+      this, &BasicPortAllocatorSession::OnCandidateReady);
+  port->SignalPortComplete.connect(this,
+      &BasicPortAllocatorSession::OnPortComplete);
+  port->SignalDestroyed.connect(this,
+      &BasicPortAllocatorSession::OnPortDestroyed);
+  port->SignalPortError.connect(
+      this, &BasicPortAllocatorSession::OnPortError);
+  LOG_J(LS_INFO, port) << "Added port to allocator";
+
+  if (prepare_address)
+    port->PrepareAddress();
+  if (running_)
+    port->Start();
+}
+
+void BasicPortAllocatorSession::OnAllocationSequenceObjectsCreated() {
+  allocation_sequences_created_ = true;
+  // Send candidate allocation complete signal if we have no sequences.
+  MaybeSignalCandidatesAllocationDone();
+}
+
+void BasicPortAllocatorSession::OnCandidateReady(
+    Port* port, const Candidate& c) {
+  ASSERT(talk_base::Thread::Current() == network_thread_);
+  PortData* data = FindPort(port);
+  ASSERT(data != NULL);
+  // Discarding any candidate signal if port allocation status is
+  // already in completed state.
+  if (data->complete())
+    return;
+
+  // Send candidates whose protocol is enabled.
+  std::vector<Candidate> candidates;
+  ProtocolType pvalue;
+  if (StringToProto(c.protocol().c_str(), &pvalue) &&
+      data->sequence()->ProtocolEnabled(pvalue)) {
+    candidates.push_back(c);
+  }
+
+  if (!candidates.empty()) {
+    SignalCandidatesReady(this, candidates);
+  }
+
+  // Moving to READY state as we have atleast one candidate from the port.
+  // Since this port has atleast one candidate we should forward this port
+  // to listners, to allow connections from this port.
+  if (!data->ready()) {
+    data->set_ready();
+    SignalPortReady(this, port);
+  }
+}
+
+void BasicPortAllocatorSession::OnPortComplete(Port* port) {
+  ASSERT(talk_base::Thread::Current() == network_thread_);
+  PortData* data = FindPort(port);
+  ASSERT(data != NULL);
+
+  // Ignore any late signals.
+  if (data->complete())
+    return;
+
+  // Moving to COMPLETE state.
+  data->set_complete();
+  // Send candidate allocation complete signal if this was the last port.
+  MaybeSignalCandidatesAllocationDone();
+}
+
+void BasicPortAllocatorSession::OnPortError(Port* port) {
+  ASSERT(talk_base::Thread::Current() == network_thread_);
+  PortData* data = FindPort(port);
+  ASSERT(data != NULL);
+  // We might have already given up on this port and stopped it.
+  if (data->complete())
+    return;
+
+  // SignalAddressError is currently sent from StunPort/TurnPort.
+  // But this signal itself is generic.
+  data->set_error();
+  // Send candidate allocation complete signal if this was the last port.
+  MaybeSignalCandidatesAllocationDone();
+}
+
+void BasicPortAllocatorSession::OnProtocolEnabled(AllocationSequence* seq,
+                                                  ProtocolType proto) {
+  std::vector<Candidate> candidates;
+  for (std::vector<PortData>::iterator it = ports_.begin();
+       it != ports_.end(); ++it) {
+    if (it->sequence() != seq)
+      continue;
+
+    const std::vector<Candidate>& potentials = it->port()->Candidates();
+    for (size_t i = 0; i < potentials.size(); ++i) {
+      ProtocolType pvalue;
+      if (!StringToProto(potentials[i].protocol().c_str(), &pvalue))
+        continue;
+      if (pvalue == proto) {
+        candidates.push_back(potentials[i]);
+      }
+    }
+  }
+
+  if (!candidates.empty()) {
+    SignalCandidatesReady(this, candidates);
+  }
+}
+
+void BasicPortAllocatorSession::OnPortAllocationComplete(
+    AllocationSequence* seq) {
+  // Send candidate allocation complete signal if all ports are done.
+  MaybeSignalCandidatesAllocationDone();
+}
+
+void BasicPortAllocatorSession::MaybeSignalCandidatesAllocationDone() {
+  // Send signal only if all required AllocationSequence objects
+  // are created.
+  if (!allocation_sequences_created_)
+    return;
+
+  // Check that all port allocation sequences are complete.
+  for (std::vector<AllocationSequence*>::iterator it = sequences_.begin();
+       it != sequences_.end(); ++it) {
+    if ((*it)->state() == AllocationSequence::kRunning)
+      return;
+  }
+
+  // If all allocated ports are in complete state, session must have got all
+  // expected candidates. Session will trigger candidates allocation complete
+  // signal.
+  for (std::vector<PortData>::iterator it = ports_.begin();
+       it != ports_.end(); ++it) {
+    if (!it->complete())
+      return;
+  }
+  LOG(LS_INFO) << "All candidates gathered for " << content_name_ << ":"
+               << component_ << ":" << generation();
+  SignalCandidatesAllocationDone(this);
+}
+
+void BasicPortAllocatorSession::OnPortDestroyed(
+    PortInterface* port) {
+  ASSERT(talk_base::Thread::Current() == network_thread_);
+  for (std::vector<PortData>::iterator iter = ports_.begin();
+       iter != ports_.end(); ++iter) {
+    if (port == iter->port()) {
+      ports_.erase(iter);
+      LOG_J(LS_INFO, port) << "Removed port from allocator ("
+                           << static_cast<int>(ports_.size()) << " remaining)";
+      return;
+    }
+  }
+  ASSERT(false);
+}
+
+void BasicPortAllocatorSession::OnShake() {
+  LOG(INFO) << ">>>>> SHAKE <<<<< >>>>> SHAKE <<<<< >>>>> SHAKE <<<<<";
+
+  std::vector<Port*> ports;
+  std::vector<Connection*> connections;
+
+  for (size_t i = 0; i < ports_.size(); ++i) {
+    if (ports_[i].ready())
+      ports.push_back(ports_[i].port());
+  }
+
+  for (size_t i = 0; i < ports.size(); ++i) {
+    Port::AddressMap::const_iterator iter;
+    for (iter = ports[i]->connections().begin();
+         iter != ports[i]->connections().end();
+         ++iter) {
+      connections.push_back(iter->second);
+    }
+  }
+
+  LOG(INFO) << ">>>>> Destroying " << ports.size() << " ports and "
+            << connections.size() << " connections";
+
+  for (size_t i = 0; i < connections.size(); ++i)
+    connections[i]->Destroy();
+
+  if (running_ || (ports.size() > 0) || (connections.size() > 0))
+    network_thread_->PostDelayed(ShakeDelay(), this, MSG_SHAKE);
+}
+
+BasicPortAllocatorSession::PortData* BasicPortAllocatorSession::FindPort(
+    Port* port) {
+  for (std::vector<PortData>::iterator it = ports_.begin();
+       it != ports_.end(); ++it) {
+    if (it->port() == port) {
+      return &*it;
+    }
+  }
+  return NULL;
+}
+
+// AllocationSequence
+
+AllocationSequence::AllocationSequence(BasicPortAllocatorSession* session,
+                                       talk_base::Network* network,
+                                       PortConfiguration* config,
+                                       uint32 flags)
+    : session_(session),
+      network_(network),
+      ip_(network->ip()),
+      config_(config),
+      state_(kInit),
+      flags_(flags),
+      udp_socket_(NULL),
+      phase_(0) {
+}
+
+bool AllocationSequence::Init() {
+  if (IsFlagSet(PORTALLOCATOR_ENABLE_SHARED_SOCKET) &&
+      !IsFlagSet(PORTALLOCATOR_ENABLE_SHARED_UFRAG)) {
+    LOG(LS_ERROR) << "Shared socket option can't be set without "
+                  << "shared ufrag.";
+    ASSERT(false);
+    return false;
+  }
+
+  if (IsFlagSet(PORTALLOCATOR_ENABLE_SHARED_SOCKET)) {
+    udp_socket_.reset(session_->socket_factory()->CreateUdpSocket(
+        talk_base::SocketAddress(ip_, 0), session_->allocator()->min_port(),
+        session_->allocator()->max_port()));
+    if (udp_socket_) {
+      udp_socket_->SignalReadPacket.connect(
+          this, &AllocationSequence::OnReadPacket);
+    }
+    // Continuing if |udp_socket_| is NULL, as local TCP and RelayPort using TCP
+    // are next available options to setup a communication channel.
+  }
+  return true;
+}
+
+AllocationSequence::~AllocationSequence() {
+  session_->network_thread()->Clear(this);
+}
+
+void AllocationSequence::DisableEquivalentPhases(talk_base::Network* network,
+    PortConfiguration* config, uint32* flags) {
+  if (!((network == network_) && (ip_ == network->ip()))) {
+    // Different network setup; nothing is equivalent.
+    return;
+  }
+
+  // Else turn off the stuff that we've already got covered.
+
+  // Every config implicitly specifies local, so turn that off right away.
+  *flags |= PORTALLOCATOR_DISABLE_UDP;
+  *flags |= PORTALLOCATOR_DISABLE_TCP;
+
+  if (config_ && config) {
+    if (config_->stun_address == config->stun_address) {
+      // Already got this STUN server covered.
+      *flags |= PORTALLOCATOR_DISABLE_STUN;
+    }
+    if (!config_->relays.empty()) {
+      // Already got relays covered.
+      // NOTE: This will even skip a _different_ set of relay servers if we
+      // were to be given one, but that never happens in our codebase. Should
+      // probably get rid of the list in PortConfiguration and just keep a
+      // single relay server in each one.
+      *flags |= PORTALLOCATOR_DISABLE_RELAY;
+    }
+  }
+}
+
+void AllocationSequence::Start() {
+  state_ = kRunning;
+  session_->network_thread()->Post(this, MSG_ALLOCATION_PHASE);
+}
+
+void AllocationSequence::Stop() {
+  // If the port is completed, don't set it to stopped.
+  if (state_ == kRunning) {
+    state_ = kStopped;
+    session_->network_thread()->Clear(this, MSG_ALLOCATION_PHASE);
+  }
+}
+
+void AllocationSequence::OnMessage(talk_base::Message* msg) {
+  ASSERT(talk_base::Thread::Current() == session_->network_thread());
+  ASSERT(msg->message_id == MSG_ALLOCATION_PHASE);
+
+  const char* const PHASE_NAMES[kNumPhases] = {
+    "Udp", "Relay", "Tcp", "SslTcp"
+  };
+
+  // Perform all of the phases in the current step.
+  LOG_J(LS_INFO, network_) << "Allocation Phase="
+                           << PHASE_NAMES[phase_];
+
+  switch (phase_) {
+    case PHASE_UDP:
+      CreateUDPPorts();
+      CreateStunPorts();
+      EnableProtocol(PROTO_UDP);
+      break;
+
+    case PHASE_RELAY:
+      CreateRelayPorts();
+      break;
+
+    case PHASE_TCP:
+      CreateTCPPorts();
+      EnableProtocol(PROTO_TCP);
+      break;
+
+    case PHASE_SSLTCP:
+      state_ = kCompleted;
+      EnableProtocol(PROTO_SSLTCP);
+      break;
+
+    default:
+      ASSERT(false);
+  }
+
+  if (state() == kRunning) {
+    ++phase_;
+    session_->network_thread()->PostDelayed(
+        session_->allocator()->step_delay(),
+        this, MSG_ALLOCATION_PHASE);
+  } else {
+    // If all phases in AllocationSequence are completed, no allocation
+    // steps needed further. Canceling  pending signal.
+    session_->network_thread()->Clear(this, MSG_ALLOCATION_PHASE);
+    SignalPortAllocationComplete(this);
+  }
+}
+
+void AllocationSequence::EnableProtocol(ProtocolType proto) {
+  if (!ProtocolEnabled(proto)) {
+    protocols_.push_back(proto);
+    session_->OnProtocolEnabled(this, proto);
+  }
+}
+
+bool AllocationSequence::ProtocolEnabled(ProtocolType proto) const {
+  for (ProtocolList::const_iterator it = protocols_.begin();
+       it != protocols_.end(); ++it) {
+    if (*it == proto)
+      return true;
+  }
+  return false;
+}
+
+void AllocationSequence::CreateUDPPorts() {
+  if (IsFlagSet(PORTALLOCATOR_DISABLE_UDP)) {
+    LOG(LS_VERBOSE) << "AllocationSequence: UDP ports disabled, skipping.";
+    return;
+  }
+
+  // TODO(mallinath) - Remove UDPPort creating socket after shared socket
+  // is enabled completely.
+  UDPPort* port = NULL;
+  if (IsFlagSet(PORTALLOCATOR_ENABLE_SHARED_SOCKET) && udp_socket_) {
+    port = UDPPort::Create(session_->network_thread(), network_,
+                           udp_socket_.get(),
+                           session_->username(), session_->password());
+  } else {
+    port = UDPPort::Create(session_->network_thread(),
+                           session_->socket_factory(),
+                           network_, ip_,
+                           session_->allocator()->min_port(),
+                           session_->allocator()->max_port(),
+                           session_->username(), session_->password());
+  }
+
+  if (port) {
+    ports.push_back(port);
+    // If shared socket is enabled, STUN candidate will be allocated by the
+    // UDPPort.
+    if (IsFlagSet(PORTALLOCATOR_ENABLE_SHARED_SOCKET) &&
+        !IsFlagSet(PORTALLOCATOR_DISABLE_STUN)) {
+      ASSERT(config_ && !config_->stun_address.IsNil());
+      if (!(config_ && !config_->stun_address.IsNil())) {
+        LOG(LS_WARNING)
+            << "AllocationSequence: No STUN server configured, skipping.";
+        return;
+      }
+      port->set_server_addr(config_->stun_address);
+    }
+
+    session_->AddAllocatedPort(port, this, true);
+    port->SignalDestroyed.connect(this, &AllocationSequence::OnPortDestroyed);
+  }
+}
+
+void AllocationSequence::CreateTCPPorts() {
+  if (IsFlagSet(PORTALLOCATOR_DISABLE_TCP)) {
+    LOG(LS_VERBOSE) << "AllocationSequence: TCP ports disabled, skipping.";
+    return;
+  }
+
+  Port* port = TCPPort::Create(session_->network_thread(),
+                               session_->socket_factory(),
+                               network_, ip_,
+                               session_->allocator()->min_port(),
+                               session_->allocator()->max_port(),
+                               session_->username(), session_->password(),
+                               session_->allocator()->allow_tcp_listen());
+  if (port) {
+    session_->AddAllocatedPort(port, this, true);
+    // Since TCPPort is not created using shared socket, |port| will not be
+    // added to the dequeue.
+  }
+}
+
+void AllocationSequence::CreateStunPorts() {
+  if (IsFlagSet(PORTALLOCATOR_DISABLE_STUN)) {
+    LOG(LS_VERBOSE) << "AllocationSequence: STUN ports disabled, skipping.";
+    return;
+  }
+
+  if (IsFlagSet(PORTALLOCATOR_ENABLE_SHARED_SOCKET)) {
+    LOG(LS_INFO) << "AllocationSequence: "
+                 << "UDPPort will be handling the STUN candidate generation.";
+    return;
+  }
+
+  // If BasicPortAllocatorSession::OnAllocate left STUN ports enabled then we
+  // ought to have an address for them here.
+  ASSERT(config_ && !config_->stun_address.IsNil());
+  if (!(config_ && !config_->stun_address.IsNil())) {
+    LOG(LS_WARNING)
+        << "AllocationSequence: No STUN server configured, skipping.";
+    return;
+  }
+
+  StunPort* port = StunPort::Create(session_->network_thread(),
+                                session_->socket_factory(),
+                                network_, ip_,
+                                session_->allocator()->min_port(),
+                                session_->allocator()->max_port(),
+                                session_->username(), session_->password(),
+                                config_->stun_address);
+  if (port) {
+    session_->AddAllocatedPort(port, this, true);
+    // Since StunPort is not created using shared socket, |port| will not be
+    // added to the dequeue.
+  }
+}
+
+void AllocationSequence::CreateRelayPorts() {
+  if (IsFlagSet(PORTALLOCATOR_DISABLE_RELAY)) {
+     LOG(LS_VERBOSE) << "AllocationSequence: Relay ports disabled, skipping.";
+     return;
+  }
+
+  // If BasicPortAllocatorSession::OnAllocate left relay ports enabled then we
+  // ought to have a relay list for them here.
+  ASSERT(config_ && !config_->relays.empty());
+  if (!(config_ && !config_->relays.empty())) {
+    LOG(LS_WARNING)
+        << "AllocationSequence: No relay server configured, skipping.";
+    return;
+  }
+
+  PortConfiguration::RelayList::const_iterator relay;
+  for (relay = config_->relays.begin();
+       relay != config_->relays.end(); ++relay) {
+    if (relay->type == RELAY_GTURN) {
+      CreateGturnPort(*relay);
+    } else if (relay->type == RELAY_TURN) {
+      CreateTurnPort(*relay);
+    } else {
+      ASSERT(false);
+    }
+  }
+}
+
+void AllocationSequence::CreateGturnPort(const RelayServerConfig& config) {
+  // TODO(mallinath) - Rename RelayPort to GTurnPort.
+  RelayPort* port = RelayPort::Create(session_->network_thread(),
+                                      session_->socket_factory(),
+                                      network_, ip_,
+                                      session_->allocator()->min_port(),
+                                      session_->allocator()->max_port(),
+                                      config_->username, config_->password);
+  if (port) {
+    // Since RelayPort is not created using shared socket, |port| will not be
+    // added to the dequeue.
+    // Note: We must add the allocated port before we add addresses because
+    //       the latter will create candidates that need name and preference
+    //       settings.  However, we also can't prepare the address (normally
+    //       done by AddAllocatedPort) until we have these addresses.  So we
+    //       wait to do that until below.
+    session_->AddAllocatedPort(port, this, false);
+
+    // Add the addresses of this protocol.
+    PortList::const_iterator relay_port;
+    for (relay_port = config.ports.begin();
+         relay_port != config.ports.end();
+         ++relay_port) {
+      port->AddServerAddress(*relay_port);
+      port->AddExternalAddress(*relay_port);
+    }
+    // Start fetching an address for this port.
+    port->PrepareAddress();
+  }
+}
+
+void AllocationSequence::CreateTurnPort(const RelayServerConfig& config) {
+  PortList::const_iterator relay_port;
+  for (relay_port = config.ports.begin();
+       relay_port != config.ports.end(); ++relay_port) {
+    TurnPort* port = TurnPort::Create(session_->network_thread(),
+                                      session_->socket_factory(),
+                                      network_, ip_,
+                                      session_->allocator()->min_port(),
+                                      session_->allocator()->max_port(),
+                                      session_->username(),
+                                      session_->password(),
+                                      *relay_port, config.credentials);
+    if (port) {
+      session_->AddAllocatedPort(port, this, true);
+    }
+  }
+}
+
+void AllocationSequence::OnReadPacket(
+    talk_base::AsyncPacketSocket* socket, const char* data, size_t size,
+    const talk_base::SocketAddress& remote_addr) {
+  ASSERT(socket == udp_socket_.get());
+  for (std::deque<Port*>::iterator iter = ports.begin();
+       iter != ports.end(); ++iter) {
+    // We have only one port in the queue.
+    // TODO(mallinath) - Add shared socket support to Relay and Turn ports.
+    if ((*iter)->HandleIncomingPacket(socket, data, size, remote_addr)) {
+      break;
+    }
+  }
+}
+
+void AllocationSequence::OnPortDestroyed(PortInterface* port) {
+  std::deque<Port*>::iterator iter =
+      std::find(ports.begin(), ports.end(), port);
+  ASSERT(iter != ports.end());
+  ports.erase(iter);
+}
+
+// PortConfiguration
+PortConfiguration::PortConfiguration(
+    const talk_base::SocketAddress& stun_address,
+    const std::string& username,
+    const std::string& password)
+    : stun_address(stun_address),
+      username(username),
+      password(password) {
+}
+
+void PortConfiguration::AddRelay(const RelayServerConfig& config) {
+  relays.push_back(config);
+}
+
+bool PortConfiguration::SupportsProtocol(
+    const RelayServerConfig& relay, ProtocolType type) {
+  PortList::const_iterator relay_port;
+  for (relay_port = relay.ports.begin();
+        relay_port != relay.ports.end();
+        ++relay_port) {
+    if (relay_port->proto == type)
+      return true;
+  }
+  return false;
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/client/basicportallocator.h b/talk/p2p/client/basicportallocator.h
new file mode 100644
index 0000000..0d5f642
--- /dev/null
+++ b/talk/p2p/client/basicportallocator.h
@@ -0,0 +1,249 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_CLIENT_BASICPORTALLOCATOR_H_
+#define TALK_P2P_CLIENT_BASICPORTALLOCATOR_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/messagequeue.h"
+#include "talk/base/network.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/port.h"
+#include "talk/p2p/base/portallocator.h"
+
+namespace cricket {
+
+struct RelayCredentials {
+  RelayCredentials() {}
+  RelayCredentials(const std::string& username,
+                   const std::string& password)
+      : username(username),
+        password(password) {
+  }
+
+  std::string username;
+  std::string password;
+};
+
+typedef std::vector<ProtocolAddress> PortList;
+struct RelayServerConfig {
+  RelayServerConfig(RelayType type) : type(type) {}
+
+  RelayType type;
+  PortList ports;
+  RelayCredentials credentials;
+};
+
+class BasicPortAllocator : public PortAllocator {
+ public:
+  BasicPortAllocator(talk_base::NetworkManager* network_manager,
+                     talk_base::PacketSocketFactory* socket_factory);
+  explicit BasicPortAllocator(talk_base::NetworkManager* network_manager);
+  BasicPortAllocator(talk_base::NetworkManager* network_manager,
+                     talk_base::PacketSocketFactory* socket_factory,
+                     const talk_base::SocketAddress& stun_server);
+  BasicPortAllocator(talk_base::NetworkManager* network_manager,
+                     const talk_base::SocketAddress& stun_server,
+                     const talk_base::SocketAddress& relay_server_udp,
+                     const talk_base::SocketAddress& relay_server_tcp,
+                     const talk_base::SocketAddress& relay_server_ssl);
+  virtual ~BasicPortAllocator();
+
+  talk_base::NetworkManager* network_manager() { return network_manager_; }
+
+  // If socket_factory() is set to NULL each PortAllocatorSession
+  // creates its own socket factory.
+  talk_base::PacketSocketFactory* socket_factory() { return socket_factory_; }
+
+  const talk_base::SocketAddress& stun_address() const {
+    return stun_address_;
+  }
+
+  const std::vector<RelayServerConfig>& relays() const {
+    return relays_;
+  }
+  virtual void AddRelay(const RelayServerConfig& relay) {
+    relays_.push_back(relay);
+  }
+
+  virtual PortAllocatorSession* CreateSessionInternal(
+      const std::string& content_name,
+      int component,
+      const std::string& ice_ufrag,
+      const std::string& ice_pwd);
+
+  bool allow_tcp_listen() const {
+    return allow_tcp_listen_;
+  }
+  void set_allow_tcp_listen(bool allow_tcp_listen) {
+    allow_tcp_listen_ = allow_tcp_listen;
+  }
+
+ private:
+  void Construct();
+
+  talk_base::NetworkManager* network_manager_;
+  talk_base::PacketSocketFactory* socket_factory_;
+  const talk_base::SocketAddress stun_address_;
+  std::vector<RelayServerConfig> relays_;
+  bool allow_tcp_listen_;
+};
+
+struct PortConfiguration;
+class AllocationSequence;
+
+class BasicPortAllocatorSession : public PortAllocatorSession,
+                                  public talk_base::MessageHandler {
+ public:
+  BasicPortAllocatorSession(BasicPortAllocator* allocator,
+                            const std::string& content_name,
+                            int component,
+                            const std::string& ice_ufrag,
+                            const std::string& ice_pwd);
+  ~BasicPortAllocatorSession();
+
+  virtual BasicPortAllocator* allocator() { return allocator_; }
+  talk_base::Thread* network_thread() { return network_thread_; }
+  talk_base::PacketSocketFactory* socket_factory() { return socket_factory_; }
+
+  virtual void StartGettingPorts();
+  virtual void StopGettingPorts();
+  virtual bool IsGettingPorts() { return running_; }
+
+ protected:
+  // Starts the process of getting the port configurations.
+  virtual void GetPortConfigurations();
+
+  // Adds a port configuration that is now ready.  Once we have one for each
+  // network (or a timeout occurs), we will start allocating ports.
+  virtual void ConfigReady(PortConfiguration* config);
+
+  // MessageHandler.  Can be overriden if message IDs do not conflict.
+  virtual void OnMessage(talk_base::Message *message);
+
+ private:
+  class PortData {
+   public:
+    PortData() : port_(NULL), sequence_(NULL), state_(STATE_INIT) {}
+    PortData(Port* port, AllocationSequence* seq)
+    : port_(port), sequence_(seq), state_(STATE_INIT) {
+    }
+
+    Port* port() { return port_; }
+    AllocationSequence* sequence() { return sequence_; }
+    bool ready() const { return state_ == STATE_READY; }
+    bool complete() const {
+      // Returns true if candidate allocation has completed one way or another.
+      return ((state_ == STATE_COMPLETE) || (state_ == STATE_ERROR));
+    }
+
+    void set_ready() { ASSERT(state_ == STATE_INIT); state_ = STATE_READY; }
+    void set_complete() {
+      ASSERT(state_ == STATE_READY);
+      state_ = STATE_COMPLETE;
+    }
+    void set_error() {
+      ASSERT(state_ == STATE_INIT || state_ == STATE_READY);
+      state_ = STATE_ERROR;
+    }
+
+   private:
+    enum State {
+      STATE_INIT,      // No candidates allocated yet.
+      STATE_READY,     // At least one candidate is ready for process.
+      STATE_COMPLETE,  // All candidates allocated and ready for process.
+      STATE_ERROR      // Error in gathering candidates.
+    };
+    Port* port_;
+    AllocationSequence* sequence_;
+    State state_;
+  };
+
+  void OnConfigReady(PortConfiguration* config);
+  void OnConfigStop();
+  void AllocatePorts();
+  void OnAllocate();
+  void DoAllocate();
+  void OnNetworksChanged();
+  void OnAllocationSequenceObjectsCreated();
+  void DisableEquivalentPhases(talk_base::Network* network,
+                               PortConfiguration* config, uint32* flags);
+  void AddAllocatedPort(Port* port, AllocationSequence* seq,
+                        bool prepare_address);
+  void OnCandidateReady(Port* port, const Candidate& c);
+  void OnPortComplete(Port* port);
+  void OnPortError(Port* port);
+  void OnProtocolEnabled(AllocationSequence* seq, ProtocolType proto);
+  void OnPortDestroyed(PortInterface* port);
+  void OnShake();
+  void MaybeSignalCandidatesAllocationDone();
+  void OnPortAllocationComplete(AllocationSequence* seq);
+  PortData* FindPort(Port* port);
+
+  BasicPortAllocator* allocator_;
+  talk_base::Thread* network_thread_;
+  talk_base::scoped_ptr<talk_base::PacketSocketFactory> owned_socket_factory_;
+  talk_base::PacketSocketFactory* socket_factory_;
+  bool configuration_done_;
+  bool allocation_started_;
+  bool network_manager_started_;
+  bool running_;  // set when StartGetAllPorts is called
+  bool allocation_sequences_created_;
+  std::vector<PortConfiguration*> configs_;
+  std::vector<AllocationSequence*> sequences_;
+  std::vector<PortData> ports_;
+
+  friend class AllocationSequence;
+};
+
+// Records configuration information useful in creating ports.
+struct PortConfiguration : public talk_base::MessageData {
+  talk_base::SocketAddress stun_address;
+  std::string username;
+  std::string password;
+
+  typedef std::vector<RelayServerConfig> RelayList;
+  RelayList relays;
+
+  PortConfiguration(const talk_base::SocketAddress& stun_address,
+                    const std::string& username,
+                    const std::string& password);
+
+  // Adds another relay server, with the given ports and modifier, to the list.
+  void AddRelay(const RelayServerConfig& config);
+
+  // Determines whether the given relay server supports the given protocol.
+  static bool SupportsProtocol(const RelayServerConfig& relay,
+                               ProtocolType type);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_CLIENT_BASICPORTALLOCATOR_H_
diff --git a/talk/p2p/client/connectivitychecker.cc b/talk/p2p/client/connectivitychecker.cc
new file mode 100644
index 0000000..4cc73af
--- /dev/null
+++ b/talk/p2p/client/connectivitychecker.cc
@@ -0,0 +1,516 @@
+// 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/helpers.h"
+#include "talk/base/httpcommon.h"
+#include "talk/base/httpcommon-inl.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/constants.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* CreateSessionInternal(
+      const std::string& content_name,
+      int component,
+      const std::string& ice_ufrag,
+      const std::string& ice_pwd) {
+    return new TestHttpPortAllocatorSession(this, content_name, component,
+                                            ice_ufrag, ice_pwd,
+                                            stun_hosts(), relay_hosts(),
+                                            relay_token(), user_agent());
+  }
+};
+
+void TestHttpPortAllocatorSession::ConfigReady(PortConfiguration* config) {
+  SignalConfigReady(username(), password(), 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),
+      started_(false) {
+}
+
+ConnectivityChecker::~ConnectivityChecker() {
+  if (started_) {
+    // We try to clear the TIMEOUT below. But worker may still handle it and
+    // cause SignalCheckDone to happen on main-thread. So we finally clear any
+    // pending SIGNAL_RESULTS.
+    worker_->Clear(this, MSG_TIMEOUT);
+    worker_->Send(this, MSG_STOP);
+    nics_.clear();
+    main_->Clear(this, MSG_SIGNAL_RESULTS);
+  }
+}
+
+bool ConnectivityChecker::Initialize() {
+  network_manager_.reset(CreateNetworkManager());
+  socket_factory_.reset(CreateSocketFactory(worker_));
+  port_allocator_.reset(CreatePortAllocator(network_manager_.get(),
+                                            user_agent_, relay_token_));
+  uint32 new_allocator_flags = port_allocator_->flags();
+  new_allocator_flags |= cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG;
+  port_allocator_->set_flags(new_allocator_flags);
+  return true;
+}
+
+void ConnectivityChecker::Start() {
+  main_ = talk_base::Thread::Current();
+  worker_->Post(this, MSG_START);
+  started_ = true;
+}
+
+void ConnectivityChecker::CleanUp() {
+  ASSERT(worker_ == talk_base::Thread::Current());
+  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're being stopped, free resources.
+      CleanUp();
+      break;
+    case MSG_TIMEOUT:
+      // We need to signal results on the main thread.
+      main_->Post(this, MSG_SIGNAL_RESULTS);
+      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::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 std::string& username, const std::string& password,
+    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(username, password, config, proxy_info);
+}
+
+void ConnectivityChecker::OnRelayPortComplete(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::OnStunPortComplete(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::OnStunPortError(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::OnRelayPortError(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 std::string& username, const std::string& password,
+    const PortConfiguration* config, talk_base::Network* network) {
+  return StunPort::Create(worker_, socket_factory_.get(),
+                          network, network->ip(), 0, 0,
+                          username, password, config->stun_address);
+}
+
+RelayPort* ConnectivityChecker::CreateRelayPort(
+    const std::string& username, const std::string& password,
+    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(),
+                           username, password);
+}
+
+void ConnectivityChecker::CreateRelayPorts(
+    const std::string& username, const std::string& password,
+    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.
+        PortList::const_iterator relay_port;
+        for (relay_port = relay->ports.begin();
+             relay_port != relay->ports.end();
+             ++relay_port) {
+          RelayPort* port = CreateRelayPort(username, password,
+                                            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->SignalPortComplete.connect(
+              this, &ConnectivityChecker::OnRelayPortComplete);
+          port->SignalPortError.connect(
+              this, &ConnectivityChecker::OnRelayPortError);
+
+          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() {
+  const std::string username = talk_base::CreateRandomString(ICE_UFRAG_LENGTH);
+  const std::string password = talk_base::CreateRandomString(ICE_PWD_LENGTH);
+  PortConfiguration config(stun_address_, username, password);
+  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(username, password, &config, networks[i]);
+      if (port) {
+
+        // Listen to network events.
+        port->SignalPortComplete.connect(
+            this, &ConnectivityChecker::OnStunPortComplete);
+        port->SignalPortError.connect(
+            this, &ConnectivityChecker::OnStunPortError);
+
+        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_->CreateSessionInternal(
+              "connectivity checker test content",
+              ICE_CANDIDATE_COMPONENT_RTP,
+              talk_base::CreateRandomString(ICE_UFRAG_LENGTH),
+              talk_base::CreateRandomString(ICE_PWD_LENGTH)));
+  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..95b736d
--- /dev/null
+++ b/talk/p2p/client/connectivitychecker.h
@@ -0,0 +1,274 @@
+// 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/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/base/basicpacketsocketfactory.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& content_name,
+      int component,
+      const std::string& ice_ufrag,
+      const std::string& ice_pwd,
+      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, content_name, component, ice_ufrag, ice_pwd, 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::signal4<const std::string&, const std::string&,
+                   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();
+
+  // MessageHandler implementation.
+  virtual void OnMessage(talk_base::Message *msg);
+
+  // Instruct checker to stop and wait until that's done.
+  // Virtual for gMock.
+  virtual void Stop() {
+    worker_->Stop();
+  }
+
+  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_;
+  }
+
+  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 std::string& username, const std::string& password,
+      const PortConfiguration* config, talk_base::Network* network);
+  virtual RelayPort* CreateRelayPort(
+      const std::string& username, const std::string& password,
+      const PortConfiguration* config, talk_base::Network* network);
+  virtual void InitiateProxyDetection();
+  virtual void SetProxyInfo(const talk_base::ProxyInfo& info);
+  virtual talk_base::ProxyInfo GetProxyInfo() const;
+
+  talk_base::Thread* worker() {
+    return worker_;
+  }
+
+ private:
+  bool AddNic(const talk_base::IPAddress& ip,
+              const talk_base::SocketAddress& proxy_address);
+  void AllocatePorts();
+  void AllocateRelayPorts();
+  void CheckNetworks();
+  void CreateRelayPorts(
+      const std::string& username, const std::string& password,
+      const PortConfiguration* config, const talk_base::ProxyInfo& proxy_info);
+
+  // Must be called by the worker thread.
+  void CleanUp();
+
+  void OnRequestDone(talk_base::AsyncHttpRequest* request);
+  void OnRelayPortComplete(Port* port);
+  void OnStunPortComplete(Port* port);
+  void OnRelayPortError(Port* port);
+  void OnStunPortError(Port* port);
+  void OnNetworksChanged();
+  void OnProxyDetect(talk_base::SignalThread* thread);
+  void OnConfigReady(
+      const std::string& username, const std::string& password,
+      const PortConfiguration* config, 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_;
+  bool started_;
+};
+
+}  // 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..fe1cb9b
--- /dev/null
+++ b/talk/p2p/client/connectivitychecker_unittest.cc
@@ -0,0 +1,353 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+
+
+#include <string>
+
+#include "talk/base/asynchttprequest.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/basicpacketsocketfactory.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 kChannelName[] = "rtp_test";
+static const int kComponent = 1;
+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)
+      : RelayPort(thread, factory, network, ip, min_port, max_port,
+                  username, password) {
+  }
+
+  // Just signal that we are done.
+  virtual void PrepareAddress() {
+    SignalPortComplete(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 std::string& username, const std::string& password,
+               const talk_base::SocketAddress& server_addr)
+      : StunPort(thread, factory, network, ip, min_port, max_port,
+                 username, password, server_addr) {
+  }
+
+  // Just set external address and signal that we are done.
+  virtual void PrepareAddress() {
+    AddAddress(kExternalAddr, kExternalAddr, "udp",
+               STUN_PORT_TYPE, ICE_TYPE_PREFERENCE_SRFLX, true);
+    SignalPortComplete(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& content_name,
+      int component,
+      const std::string& ice_ufrag, const std::string& ice_pwd,
+      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,
+                                     content_name,
+                                     component,
+                                     ice_ufrag,
+                                     ice_pwd,
+                                     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.ipaddr().ToString() << 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* CreateSessionInternal(
+      const std::string& content_name, int component,
+      const std::string& ice_ufrag, const std::string& ice_pwd) {
+    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,
+                                            content_name,
+                                            component,
+                                            ice_ufrag,
+                                            ice_pwd,
+                                            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 std::string& username, const std::string& password,
+      const PortConfiguration* config, talk_base::Network* network) {
+    return new FakeStunPort(worker(), socket_factory_,
+                            network, network->ip(),
+                            kMinPort, kMaxPort,
+                            username, password,
+                            config->stun_address);
+  }
+  virtual RelayPort* CreateRelayPort(
+      const std::string& username, const std::string& password,
+      const PortConfiguration* config, talk_base::Network* network) {
+    return new FakeRelayPort(worker(), socket_factory_,
+                             network, network->ip(),
+                             kMinPort, kMaxPort,
+                             username, password);
+  }
+  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/fakeportallocator.h b/talk/p2p/client/fakeportallocator.h
new file mode 100644
index 0000000..2368948
--- /dev/null
+++ b/talk/p2p/client/fakeportallocator.h
@@ -0,0 +1,107 @@
+// Copyright 2010 Google Inc. All Rights Reserved,
+//
+// Author: Justin Uberti (juberti@google.com)
+
+#ifndef TALK_P2P_CLIENT_FAKEPORTALLOCATOR_H_
+#define TALK_P2P_CLIENT_FAKEPORTALLOCATOR_H_
+
+#include <string>
+#include "talk/base/scoped_ptr.h"
+#include "talk/p2p/base/basicpacketsocketfactory.h"
+#include "talk/p2p/base/portallocator.h"
+#include "talk/p2p/base/udpport.h"
+
+namespace talk_base {
+class SocketFactory;
+class Thread;
+}
+
+namespace cricket {
+
+class FakePortAllocatorSession : public PortAllocatorSession {
+ public:
+  FakePortAllocatorSession(talk_base::Thread* worker_thread,
+                           talk_base::PacketSocketFactory* factory,
+                           const std::string& content_name,
+                           int component,
+                           const std::string& ice_ufrag,
+                           const std::string& ice_pwd)
+      : PortAllocatorSession(content_name, component, ice_ufrag, ice_pwd,
+                             cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG),
+        worker_thread_(worker_thread),
+        factory_(factory),
+        network_("network", "unittest",
+                 talk_base::IPAddress(INADDR_LOOPBACK), 8),
+        port_(NULL), running_(false),
+        port_config_count_(0) {
+    network_.AddIP(talk_base::IPAddress(INADDR_LOOPBACK));
+  }
+
+  virtual void StartGettingPorts() {
+    if (!port_) {
+      port_.reset(cricket::UDPPort::Create(worker_thread_, factory_,
+                      &network_, network_.ip(), 0, 0,
+                      username(),
+                      password()));
+      AddPort(port_.get());
+    }
+    ++port_config_count_;
+    running_ = true;
+  }
+
+  virtual void StopGettingPorts() { running_ = false; }
+  virtual bool IsGettingPorts() { return running_; }
+  int port_config_count() { return port_config_count_; }
+
+  void AddPort(cricket::Port* port) {
+    port->set_component(component_);
+    port->set_generation(0);
+    port->SignalPortComplete.connect(
+        this, &FakePortAllocatorSession::OnPortComplete);
+    port->PrepareAddress();
+    SignalPortReady(this, port);
+  }
+  void OnPortComplete(cricket::Port* port) {
+    SignalCandidatesReady(this, port->Candidates());
+    SignalCandidatesAllocationDone(this);
+  }
+
+ private:
+  talk_base::Thread* worker_thread_;
+  talk_base::PacketSocketFactory* factory_;
+  talk_base::Network network_;
+  talk_base::scoped_ptr<cricket::Port> port_;
+  bool running_;
+  int port_config_count_;
+};
+
+class FakePortAllocator : public cricket::PortAllocator {
+ public:
+  FakePortAllocator(talk_base::Thread* worker_thread,
+                    talk_base::PacketSocketFactory* factory)
+      : worker_thread_(worker_thread), factory_(factory) {
+    if (factory_ == NULL) {
+      owned_factory_.reset(new talk_base::BasicPacketSocketFactory(
+          worker_thread_));
+      factory_ = owned_factory_.get();
+    }
+  }
+
+  virtual cricket::PortAllocatorSession* CreateSessionInternal(
+      const std::string& content_name,
+      int component,
+      const std::string& ice_ufrag,
+      const std::string& ice_pwd) {
+    return new FakePortAllocatorSession(
+        worker_thread_, factory_, content_name, component, ice_ufrag, ice_pwd);
+  }
+
+ private:
+  talk_base::Thread* worker_thread_;
+  talk_base::PacketSocketFactory* factory_;
+  talk_base::scoped_ptr<talk_base::BasicPacketSocketFactory> owned_factory_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_CLIENT_FAKEPORTALLOCATOR_H_
diff --git a/talk/p2p/client/httpportallocator.cc b/talk/p2p/client/httpportallocator.cc
new file mode 100644
index 0000000..e54acba
--- /dev/null
+++ b/talk/p2p/client/httpportallocator.cc
@@ -0,0 +1,334 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/p2p/client/httpportallocator.h"
+
+#include <algorithm>
+#include <map>
+
+#include "talk/base/asynchttprequest.h"
+#include "talk/base/basicdefs.h"
+#include "talk/base/common.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/nethelpers.h"
+#include "talk/base/signalthread.h"
+#include "talk/base/stringencode.h"
+
+namespace {
+
+const uint32 MSG_TIMEOUT = 100;  // must not conflict
+  // with BasicPortAllocator.cpp
+
+// Helper routine to remove whitespace from the ends of a string.
+void Trim(std::string& str) {
+  size_t first = str.find_first_not_of(" \t\r\n");
+  if (first == std::string::npos) {
+    str.clear();
+    return;
+  }
+
+  ASSERT(str.find_last_not_of(" \t\r\n") != std::string::npos);
+}
+
+// Parses the lines in the result of the HTTP request that are of the form
+// 'a=b' and returns them in a map.
+typedef std::map<std::string, std::string> StringMap;
+void ParseMap(const std::string& string, StringMap& map) {
+  size_t start_of_line = 0;
+  size_t end_of_line = 0;
+
+  for (;;) {  // for each line
+    start_of_line = string.find_first_not_of("\r\n", end_of_line);
+    if (start_of_line == std::string::npos)
+      break;
+
+    end_of_line = string.find_first_of("\r\n", start_of_line);
+    if (end_of_line == std::string::npos) {
+      end_of_line = string.length();
+    }
+
+    size_t equals = string.find('=', start_of_line);
+    if ((equals >= end_of_line) || (equals == std::string::npos))
+      continue;
+
+    std::string key(string, start_of_line, equals - start_of_line);
+    std::string value(string, equals + 1, end_of_line - equals - 1);
+
+    Trim(key);
+    Trim(value);
+
+    if ((key.size() > 0) && (value.size() > 0))
+      map[key] = value;
+  }
+}
+
+}  // namespace
+
+namespace cricket {
+
+// HttpPortAllocatorBase
+
+const int HttpPortAllocatorBase::kNumRetries = 5;
+
+const char HttpPortAllocatorBase::kCreateSessionURL[] = "/create_session";
+
+HttpPortAllocatorBase::HttpPortAllocatorBase(
+    talk_base::NetworkManager* network_manager,
+    talk_base::PacketSocketFactory* socket_factory,
+    const std::string &user_agent)
+    : BasicPortAllocator(network_manager, socket_factory), agent_(user_agent) {
+  relay_hosts_.push_back("relay.google.com");
+  stun_hosts_.push_back(
+      talk_base::SocketAddress("stun.l.google.com", 19302));
+}
+
+HttpPortAllocatorBase::HttpPortAllocatorBase(
+    talk_base::NetworkManager* network_manager,
+    const std::string &user_agent)
+    : BasicPortAllocator(network_manager), agent_(user_agent) {
+  relay_hosts_.push_back("relay.google.com");
+  stun_hosts_.push_back(
+      talk_base::SocketAddress("stun.l.google.com", 19302));
+}
+
+HttpPortAllocatorBase::~HttpPortAllocatorBase() {
+}
+
+// HttpPortAllocatorSessionBase
+
+HttpPortAllocatorSessionBase::HttpPortAllocatorSessionBase(
+    HttpPortAllocatorBase* allocator,
+    const std::string& content_name,
+    int component,
+    const std::string& ice_ufrag,
+    const std::string& ice_pwd,
+    const std::vector<talk_base::SocketAddress>& stun_hosts,
+    const std::vector<std::string>& relay_hosts,
+    const std::string& relay_token,
+    const std::string& user_agent)
+    : BasicPortAllocatorSession(allocator, content_name, component,
+                                ice_ufrag, ice_pwd),
+      relay_hosts_(relay_hosts), stun_hosts_(stun_hosts),
+      relay_token_(relay_token), agent_(user_agent), attempts_(0) {
+}
+
+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
+  // configs will have unresolved stun ips and will be discarded by the
+  // AllocationSequence.
+  PortConfiguration* config = new PortConfiguration(stun_hosts_[0],
+                                                    username(),
+                                                    password());
+  ConfigReady(config);
+  TryCreateRelaySession();
+}
+
+void HttpPortAllocatorSessionBase::TryCreateRelaySession() {
+  if (allocator()->flags() & PORTALLOCATOR_DISABLE_RELAY) {
+    LOG(LS_VERBOSE) << "HttpPortAllocator: Relay ports disabled, skipping.";
+    return;
+  }
+
+  if (attempts_ == HttpPortAllocator::kNumRetries) {
+    LOG(LS_ERROR) << "HttpPortAllocator: maximum number of requests reached; "
+                  << "giving up on relay.";
+    return;
+  }
+
+  if (relay_hosts_.size() == 0) {
+    LOG(LS_ERROR) << "HttpPortAllocator: no relay hosts configured.";
+    return;
+  }
+
+  // Choose the next host to try.
+  std::string host = relay_hosts_[attempts_ % relay_hosts_.size()];
+  attempts_++;
+  LOG(LS_INFO) << "HTTPPortAllocator: sending to relay host " << host;
+  if (relay_token_.empty()) {
+    LOG(LS_WARNING) << "No relay auth token found.";
+  }
+
+  SendSessionRequest(host, talk_base::HTTP_SECURE_PORT);
+}
+
+std::string HttpPortAllocatorSessionBase::GetSessionRequestUrl() {
+  std::string url = std::string(HttpPortAllocator::kCreateSessionURL);
+  if (allocator()->flags() & PORTALLOCATOR_ENABLE_SHARED_UFRAG) {
+    ASSERT(!username().empty());
+    ASSERT(!password().empty());
+    url = url + "?username=" + talk_base::s_url_encode(username()) +
+        "&password=" + talk_base::s_url_encode(password());
+  }
+  return url;
+}
+
+void HttpPortAllocatorSessionBase::ReceiveSessionResponse(
+    const std::string& response) {
+
+  StringMap map;
+  ParseMap(response, map);
+
+  if (!username().empty() && map["username"] != username()) {
+    LOG(LS_WARNING) << "Received unexpected username value from relay server.";
+  }
+  if (!password().empty() && map["password"] != password()) {
+    LOG(LS_WARNING) << "Received unexpected password value from relay server.";
+  }
+
+  std::string relay_ip = map["relay.ip"];
+  std::string relay_udp_port = map["relay.udp_port"];
+  std::string relay_tcp_port = map["relay.tcp_port"];
+  std::string relay_ssltcp_port = map["relay.ssltcp_port"];
+
+  PortConfiguration* config = new PortConfiguration(stun_hosts_[0],
+                                                    map["username"],
+                                                    map["password"]);
+
+  RelayServerConfig relay_config(RELAY_GTURN);
+  if (!relay_udp_port.empty()) {
+    talk_base::SocketAddress address(relay_ip, atoi(relay_udp_port.c_str()));
+    relay_config.ports.push_back(ProtocolAddress(address, PROTO_UDP));
+  }
+  if (!relay_tcp_port.empty()) {
+    talk_base::SocketAddress address(relay_ip, atoi(relay_tcp_port.c_str()));
+    relay_config.ports.push_back(ProtocolAddress(address, PROTO_TCP));
+  }
+  if (!relay_ssltcp_port.empty()) {
+    talk_base::SocketAddress address(relay_ip, atoi(relay_ssltcp_port.c_str()));
+    relay_config.ports.push_back(ProtocolAddress(address, PROTO_SSLTCP));
+  }
+  config->AddRelay(relay_config);
+  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::CreateSessionInternal(
+    const std::string& content_name,
+    int component,
+    const std::string& ice_ufrag, const std::string& ice_pwd) {
+  return new HttpPortAllocatorSession(this, content_name, component,
+                                      ice_ufrag, ice_pwd, stun_hosts(),
+                                      relay_hosts(), relay_token(),
+                                      user_agent());
+}
+
+// HttpPortAllocatorSession
+
+HttpPortAllocatorSession::HttpPortAllocatorSession(
+    HttpPortAllocator* allocator,
+    const std::string& content_name,
+    int component,
+    const std::string& ice_ufrag,
+    const std::string& ice_pwd,
+    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, content_name, component,
+                                   ice_ufrag, ice_pwd, 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 = GetSessionRequestUrl();
+  request->request().addHeader("X-Talk-Google-Relay-Auth", relay_token(), true);
+  request->request().addHeader("X-Stream-Type", "video_rtp", 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
new file mode 100644
index 0000000..cb4c8f8
--- /dev/null
+++ b/talk/p2p/client/httpportallocator.h
@@ -0,0 +1,192 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_CLIENT_HTTPPORTALLOCATOR_H_
+#define TALK_P2P_CLIENT_HTTPPORTALLOCATOR_H_
+
+#include <list>
+#include <string>
+#include <vector>
+
+#include "talk/base/gunit_prod.h"
+#include "talk/p2p/client/basicportallocator.h"
+
+class HttpPortAllocatorTest_TestSessionRequestUrl_Test;
+
+namespace talk_base {
+class AsyncHttpRequest;
+class SignalThread;
+}
+
+namespace cricket {
+
+class HttpPortAllocatorBase : public BasicPortAllocator {
+ public:
+  // 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[];
+
+  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();
+
+  // CreateSession is defined in BasicPortAllocator but is
+  // redefined here as pure virtual.
+  virtual PortAllocatorSession* CreateSessionInternal(
+      const std::string& content_name,
+      int component,
+      const std::string& ice_ufrag,
+      const std::string& ice_pwd) = 0;
+
+  void SetStunHosts(const std::vector<talk_base::SocketAddress>& hosts) {
+    if (!hosts.empty()) {
+      stun_hosts_ = hosts;
+    }
+  }
+  void SetRelayHosts(const std::vector<std::string>& hosts) {
+    if (!hosts.empty()) {
+      relay_hosts_ = hosts;
+    }
+  }
+  void SetRelayToken(const std::string& relay) { relay_token_ = relay; }
+
+  const std::vector<talk_base::SocketAddress>& stun_hosts() const {
+    return stun_hosts_;
+  }
+
+  const std::vector<std::string>& relay_hosts() const {
+    return relay_hosts_;
+  }
+
+  const std::string& relay_token() const {
+    return relay_token_;
+  }
+
+  const std::string& user_agent() const {
+    return agent_;
+  }
+
+ private:
+  std::vector<talk_base::SocketAddress> stun_hosts_;
+  std::vector<std::string> relay_hosts_;
+  std::string relay_token_;
+  std::string agent_;
+};
+
+class RequestData;
+
+class HttpPortAllocatorSessionBase : public BasicPortAllocatorSession {
+ public:
+  HttpPortAllocatorSessionBase(
+      HttpPortAllocatorBase* allocator,
+      const std::string& content_name,
+      int component,
+      const std::string& ice_ufrag,
+      const std::string& ice_pwd,
+      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());
+  }
+
+  std::string GetSessionRequestUrl();
+
+ private:
+  FRIEND_TEST(::HttpPortAllocatorTest, TestSessionRequestUrl);
+
+  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* CreateSessionInternal(
+      const std::string& content_name,
+      int component,
+      const std::string& ice_ufrag, const std::string& ice_pwd);
+};
+
+class HttpPortAllocatorSession : public HttpPortAllocatorSessionBase {
+ public:
+  HttpPortAllocatorSession(
+      HttpPortAllocator* allocator,
+      const std::string& content_name,
+      int component,
+      const std::string& ice_ufrag,
+      const std::string& ice_pwd,
+      const std::vector<talk_base::SocketAddress>& stun_hosts,
+      const std::vector<std::string>& relay_hosts,
+      const std::string& relay,
+      const std::string& agent);
+  virtual ~HttpPortAllocatorSession();
+
+  virtual void SendSessionRequest(const std::string& host, int port);
+
+ protected:
+  // Protected for diagnostics.
+  virtual void OnRequestDone(talk_base::SignalThread* request);
+
+ private:
+  std::list<talk_base::AsyncHttpRequest*> requests_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_CLIENT_HTTPPORTALLOCATOR_H_
diff --git a/talk/p2p/client/portallocator_unittest.cc b/talk/p2p/client/portallocator_unittest.cc
new file mode 100644
index 0000000..2113103
--- /dev/null
+++ b/talk/p2p/client/portallocator_unittest.cc
@@ -0,0 +1,822 @@
+/*
+ * 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/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/basicpacketsocketfactory.h"
+#include "talk/p2p/base/constants.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 kNatAddr("77.77.77.77", talk_base::NAT_SERVER_PORT);
+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;
+
+// Based on ICE_UFRAG_LENGTH
+static const char kIceUfrag0[] = "TESTICEUFRAG0000";
+// Based on ICE_PWD_LENGTH
+static const char kIcePwd0[] = "TESTICEPWD00000000000000";
+
+static const char kContentName[] = "test content";
+
+static const int kDefaultAllocationTimeout = 1000;
+
+namespace cricket {
+
+// Helper for dumping candidates
+std::ostream& operator<<(std::ostream& os, const cricket::Candidate& c) {
+  os << c.ToString();
+  return os;
+}
+
+}  // namespace cricket
+
+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()),
+        nat_factory_(vss_.get(), kNatAddr),
+        nat_socket_factory_(&nat_factory_),
+        stun_server_(Thread::Current(), kStunAddr),
+        relay_server_(Thread::Current(), kRelayUdpIntAddr, kRelayUdpExtAddr,
+                      kRelayTcpIntAddr, kRelayTcpExtAddr,
+                      kRelaySslTcpIntAddr, kRelaySslTcpExtAddr),
+        allocator_(new cricket::BasicPortAllocator(
+            &network_manager_, kStunAddr,
+            kRelayUdpIntAddr, kRelayTcpIntAddr, kRelaySslTcpIntAddr)),
+        candidate_allocation_done_(false) {
+    allocator_->set_step_delay(cricket::kMinimumStepDelay);
+  }
+
+  void AddInterface(const SocketAddress& addr) {
+    network_manager_.AddInterface(addr);
+  }
+  bool SetPortRange(int min_port, int max_port) {
+    return allocator_->SetPortRange(min_port, max_port);
+  }
+  talk_base::NATServer* CreateNatServer(const SocketAddress& addr,
+                                        talk_base::NATType type) {
+    return new talk_base::NATServer(type, vss_.get(), addr, vss_.get(), addr);
+  }
+
+  bool CreateSession(int component) {
+    session_.reset(CreateSession("session", component));
+    if (!session_)
+      return false;
+    return true;
+  }
+
+  bool CreateSession(int component, const std::string& content_name) {
+    session_.reset(CreateSession("session", content_name, component));
+    if (!session_)
+      return false;
+    return true;
+  }
+
+  cricket::PortAllocatorSession* CreateSession(
+      const std::string& sid, int component) {
+    return CreateSession(sid, kContentName, component);
+  }
+
+  cricket::PortAllocatorSession* CreateSession(
+      const std::string& sid, const std::string& content_name, int component) {
+    return CreateSession(sid, content_name, component, kIceUfrag0, kIcePwd0);
+  }
+
+  cricket::PortAllocatorSession* CreateSession(
+      const std::string& sid, const std::string& content_name, int component,
+      const std::string& ice_ufrag, const std::string& ice_pwd) {
+    cricket::PortAllocatorSession* session =
+        allocator_->CreateSession(
+            sid, content_name, component, ice_ufrag, ice_pwd);
+    session->SignalPortReady.connect(this,
+            &PortAllocatorTest::OnPortReady);
+    session->SignalCandidatesReady.connect(this,
+        &PortAllocatorTest::OnCandidatesReady);
+    session->SignalCandidatesAllocationDone.connect(this,
+        &PortAllocatorTest::OnCandidatesAllocationDone);
+    return session;
+  }
+
+  static bool CheckCandidate(const cricket::Candidate& c,
+                             int component, const std::string& type,
+                             const std::string& proto,
+                             const SocketAddress& addr) {
+    return (c.component() == component && c.type() == type &&
+        c.protocol() == proto && c.address().ipaddr() == addr.ipaddr() &&
+        ((addr.port() == 0 && (c.address().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);
+  }
+
+  void OnCandidatesAllocationDone(cricket::PortAllocatorSession* session) {
+    // We should only get this callback once, except in the mux test where
+    // we have multiple port allocation sessions.
+    if (session == session_.get()) {
+      ASSERT_FALSE(candidate_allocation_done_);
+      candidate_allocation_done_ = true;
+    }
+  }
+
+  // Check if all ports allocated have send-buffer size |expected|. If
+  // |expected| == -1, check if GetOptions returns SOCKET_ERROR.
+  void CheckSendBufferSizesOfAllPorts(int expected) {
+    std::vector<cricket::PortInterface*>::iterator it;
+    for (it = ports_.begin(); it < ports_.end(); ++it) {
+      int send_buffer_size;
+      if (expected == -1) {
+        EXPECT_EQ(SOCKET_ERROR,
+                  (*it)->GetOption(talk_base::Socket::OPT_SNDBUF,
+                                   &send_buffer_size));
+      } else {
+        EXPECT_EQ(0, (*it)->GetOption(talk_base::Socket::OPT_SNDBUF,
+                                      &send_buffer_size));
+        ASSERT_EQ(expected, send_buffer_size);
+      }
+    }
+  }
+
+ protected:
+  cricket::BasicPortAllocator& allocator() {
+    return *allocator_;
+  }
+
+  void OnPortReady(cricket::PortAllocatorSession* ses,
+                   cricket::PortInterface* 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]);
+    }
+  }
+
+  bool HasRelayAddress(const cricket::ProtocolAddress& proto_addr) {
+    for (size_t i = 0; i < allocator_->relays().size(); ++i) {
+      cricket::RelayServerConfig server_config = allocator_->relays()[i];
+      cricket::PortList::const_iterator relay_port;
+      for (relay_port = server_config.ports.begin();
+          relay_port != server_config.ports.end(); ++relay_port) {
+        if (proto_addr.address == relay_port->address &&
+            proto_addr.proto == relay_port->proto)
+          return true;
+      }
+    }
+    return false;
+  }
+
+  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_;
+  talk_base::NATSocketFactory nat_factory_;
+  talk_base::BasicPacketSocketFactory nat_socket_factory_;
+  cricket::TestStunServer stun_server_;
+  cricket::TestRelayServer relay_server_;
+  talk_base::FakeNetworkManager network_manager_;
+  talk_base::scoped_ptr<cricket::BasicPortAllocator> allocator_;
+  talk_base::scoped_ptr<cricket::PortAllocatorSession> session_;
+  std::vector<cricket::PortInterface*> ports_;
+  std::vector<cricket::Candidate> candidates_;
+  bool candidate_allocation_done_;
+};
+
+// 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());
+  ASSERT_EQ(1u, allocator().relays().size());
+  EXPECT_EQ(cricket::RELAY_GTURN, allocator().relays()[0].type);
+  // Empty relay credentials are used for GTURN.
+  EXPECT_TRUE(allocator().relays()[0].credentials.username.empty());
+  EXPECT_TRUE(allocator().relays()[0].credentials.password.empty());
+  EXPECT_TRUE(HasRelayAddress(cricket::ProtocolAddress(
+      kRelayUdpIntAddr, cricket::PROTO_UDP)));
+  EXPECT_TRUE(HasRelayAddress(cricket::ProtocolAddress(
+      kRelayTcpIntAddr, cricket::PROTO_TCP)));
+  EXPECT_TRUE(HasRelayAddress(cricket::ProtocolAddress(
+      kRelaySslTcpIntAddr, cricket::PROTO_SSLTCP)));
+  EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP));
+}
+
+// Tests that we can get all the desired addresses successfully.
+TEST_F(PortAllocatorTest, TestGetAllPortsWithMinimumStepDelay) {
+  AddInterface(kClientAddr);
+  EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP));
+  session_->StartGettingPorts();
+  ASSERT_EQ_WAIT(7U, candidates_.size(), kDefaultAllocationTimeout);
+  EXPECT_EQ(4U, ports_.size());
+  EXPECT_PRED5(CheckCandidate, candidates_[0],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "udp", kClientAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[1],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "stun", "udp", kClientAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[2],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "udp", kRelayUdpIntAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[3],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "udp", kRelayUdpExtAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[4],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "tcp", kRelayTcpIntAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[5],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "tcp", kClientAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[6],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP,
+      "relay", "ssltcp", kRelaySslTcpIntAddr);
+  EXPECT_TRUE(candidate_allocation_done_);
+}
+
+// Verify candidates with default step delay of 1sec.
+TEST_F(PortAllocatorTest, TestGetAllPortsWithOneSecondStepDelay) {
+  AddInterface(kClientAddr);
+  allocator_->set_step_delay(cricket::kDefaultStepDelay);
+  EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP));
+  session_->StartGettingPorts();
+  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],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "udp", kRelayUdpIntAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[3],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "udp", kRelayUdpExtAddr);
+  ASSERT_EQ_WAIT(6U, candidates_.size(), 1500);
+  EXPECT_PRED5(CheckCandidate, candidates_[4],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "tcp", kRelayTcpIntAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[5],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "tcp", kClientAddr);
+  EXPECT_EQ(4U, ports_.size());
+  ASSERT_EQ_WAIT(7U, candidates_.size(), 2000);
+  EXPECT_PRED5(CheckCandidate, candidates_[6],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP,
+               "relay", "ssltcp", kRelaySslTcpIntAddr);
+  EXPECT_EQ(4U, ports_.size());
+  EXPECT_TRUE(candidate_allocation_done_);
+  // If we Stop gathering now, we shouldn't get a second "done" callback.
+  session_->StopGettingPorts();
+}
+
+TEST_F(PortAllocatorTest, TestSetupVideoRtpPortsWithNormalSendBuffers) {
+  AddInterface(kClientAddr);
+  EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP,
+                            cricket::CN_VIDEO));
+  session_->StartGettingPorts();
+  ASSERT_EQ_WAIT(7U, candidates_.size(), kDefaultAllocationTimeout);
+  EXPECT_TRUE(candidate_allocation_done_);
+  // If we Stop gathering now, we shouldn't get a second "done" callback.
+  session_->StopGettingPorts();
+
+  // All ports should have normal send-buffer sizes (64KB).
+  CheckSendBufferSizesOfAllPorts(64 * 1024);
+}
+
+TEST_F(PortAllocatorTest, TestSetupVideoRtpPortsWithLargeSendBuffers) {
+  AddInterface(kClientAddr);
+  allocator_->set_flags(allocator_->flags() |
+                        cricket::PORTALLOCATOR_USE_LARGE_SOCKET_SEND_BUFFERS);
+  EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP,
+                            cricket::CN_VIDEO));
+  session_->StartGettingPorts();
+  ASSERT_EQ_WAIT(7U, candidates_.size(), kDefaultAllocationTimeout);
+  EXPECT_TRUE(candidate_allocation_done_);
+  // If we Stop gathering now, we shouldn't get a second "done" callback.
+  session_->StopGettingPorts();
+
+  // All ports should have large send-buffer sizes (128KB).
+  CheckSendBufferSizesOfAllPorts(128 * 1024);
+}
+
+TEST_F(PortAllocatorTest, TestSetupVideoRtcpPortsAndCheckSendBuffers) {
+  AddInterface(kClientAddr);
+  allocator_->set_flags(allocator_->flags() |
+                        cricket::PORTALLOCATOR_USE_LARGE_SOCKET_SEND_BUFFERS);
+  EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTCP,
+                            cricket::CN_DATA));
+  session_->StartGettingPorts();
+  ASSERT_EQ_WAIT(7U, candidates_.size(), kDefaultAllocationTimeout);
+  EXPECT_TRUE(candidate_allocation_done_);
+  // If we Stop gathering now, we shouldn't get a second "done" callback.
+  session_->StopGettingPorts();
+
+  // No ports should have send-buffer size set.
+  CheckSendBufferSizesOfAllPorts(-1);
+}
+
+
+TEST_F(PortAllocatorTest, TestSetupNonVideoPortsAndCheckSendBuffers) {
+  AddInterface(kClientAddr);
+  allocator_->set_flags(allocator_->flags() |
+                        cricket::PORTALLOCATOR_USE_LARGE_SOCKET_SEND_BUFFERS);
+  EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP,
+                            cricket::CN_DATA));
+  session_->StartGettingPorts();
+  ASSERT_EQ_WAIT(7U, candidates_.size(), kDefaultAllocationTimeout);
+  EXPECT_TRUE(candidate_allocation_done_);
+  // If we Stop gathering now, we shouldn't get a second "done" callback.
+  session_->StopGettingPorts();
+
+  // No ports should have send-buffer size set.
+  CheckSendBufferSizesOfAllPorts(-1);
+}
+
+// Tests that we can get callback after StopGetAllPorts.
+TEST_F(PortAllocatorTest, TestStopGetAllPorts) {
+  AddInterface(kClientAddr);
+  EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP));
+  session_->StartGettingPorts();
+  ASSERT_EQ_WAIT(2U, candidates_.size(), kDefaultAllocationTimeout);
+  EXPECT_EQ(2U, ports_.size());
+  session_->StopGettingPorts();
+  EXPECT_TRUE_WAIT(candidate_allocation_done_, kDefaultAllocationTimeout);
+}
+
+// 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(cricket::ICE_CANDIDATE_COMPONENT_RTP));
+  session_->StartGettingPorts();
+  ASSERT_EQ_WAIT(7U, candidates_.size(), kDefaultAllocationTimeout);
+  EXPECT_EQ(4U, 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);
+  // Check the port number used to connect to the relay server.
+  EXPECT_PRED3(CheckPort, relay_server_.GetConnection(0).source(),
+               kMinPort, kMaxPort);
+  // Check the port number for the TCP port object.
+  EXPECT_PRED3(CheckPort, candidates_[5].address(), kMinPort, kMaxPort);
+  EXPECT_TRUE(candidate_allocation_done_);
+}
+
+// Test that we don't crash or malfunction if we have no network adapters.
+TEST_F(PortAllocatorTest, TestGetAllPortsNoAdapters) {
+  EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP));
+  session_->StartGettingPorts();
+  talk_base::Thread::Current()->ProcessMessages(100);
+  // Without network adapter, we should not get any candidate.
+  EXPECT_EQ(0U, candidates_.size());
+  EXPECT_TRUE(candidate_allocation_done_);
+}
+
+// Test that we can get OnCandidatesAllocationDone callback when all the ports
+// are disabled.
+TEST_F(PortAllocatorTest, TestDisableAllPorts) {
+  AddInterface(kClientAddr);
+  EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP));
+  session_->set_flags(cricket::PORTALLOCATOR_DISABLE_UDP |
+                      cricket::PORTALLOCATOR_DISABLE_STUN |
+                      cricket::PORTALLOCATOR_DISABLE_RELAY |
+                      cricket::PORTALLOCATOR_DISABLE_TCP);
+  session_->StartGettingPorts();
+  talk_base::Thread::Current()->ProcessMessages(100);
+  EXPECT_EQ(0U, candidates_.size());
+  EXPECT_TRUE(candidate_allocation_done_);
+}
+
+// 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(1));
+  session_->StartGettingPorts();
+  ASSERT_EQ_WAIT(5U, candidates_.size(), kDefaultAllocationTimeout);
+  EXPECT_EQ(2U, ports_.size());
+  EXPECT_PRED5(CheckCandidate, candidates_[0],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "udp", kRelayUdpIntAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[1],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "udp", kRelayUdpExtAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[2],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "tcp", kRelayTcpIntAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[3],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "tcp", kClientAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[4],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP,
+      "relay", "ssltcp", kRelaySslTcpIntAddr);
+  EXPECT_TRUE(candidate_allocation_done_);
+}
+
+// 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(1));
+  session_->StartGettingPorts();
+  ASSERT_EQ_WAIT(5U, candidates_.size(), kDefaultAllocationTimeout);
+  EXPECT_EQ(2U, ports_.size());
+  EXPECT_PRED5(CheckCandidate, candidates_[0],
+      1, "relay", "udp", kRelayUdpIntAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[1],
+      1, "relay", "udp", kRelayUdpExtAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[2],
+      1, "relay", "tcp", kRelayTcpIntAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[3],
+      1, "local", "tcp", kClientAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[4],
+      1, "relay", "ssltcp", kRelaySslTcpIntAddr);
+  EXPECT_TRUE(candidate_allocation_done_);
+}
+
+// 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(cricket::ICE_CANDIDATE_COMPONENT_RTP));
+  session_->StartGettingPorts();
+  WAIT(candidates_.size() > 0, 2000);
+  // TODO - Check candidate_allocation_done signal.
+  // In case of Relay, ports creation will succeed but sockets will fail.
+  // There is no error reporting from RelayEntry to handle this failure.
+}
+
+TEST_F(PortAllocatorTest, TestTcpPortNoListenAllowed) {
+  AddInterface(kClientAddr);
+  allocator().set_flags(cricket::PORTALLOCATOR_DISABLE_UDP |
+                        cricket::PORTALLOCATOR_DISABLE_STUN |
+                        cricket::PORTALLOCATOR_DISABLE_RELAY);
+  allocator().set_allow_tcp_listen(false);
+  EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP));
+  session_->StartGettingPorts();
+  EXPECT_TRUE_WAIT(candidate_allocation_done_, kDefaultAllocationTimeout);
+  EXPECT_TRUE(candidates_.empty());
+}
+
+// Testing STUN timeout.
+TEST_F(PortAllocatorTest, TestGetAllPortsNoUdpAllowed) {
+  fss_->AddRule(false, talk_base::FP_UDP, talk_base::FD_ANY, kClientAddr);
+  AddInterface(kClientAddr);
+  EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP));
+  session_->StartGettingPorts();
+  EXPECT_EQ_WAIT(2U, candidates_.size(), kDefaultAllocationTimeout);
+  EXPECT_EQ(2U, ports_.size());
+  EXPECT_PRED5(CheckCandidate, candidates_[0],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "udp", kClientAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[1],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "tcp", kClientAddr);
+  // RelayPort connection timeout is 3sec. TCP connection with RelayServer
+  // will be tried after 3 seconds.
+  EXPECT_EQ_WAIT(6U, candidates_.size(), 4000);
+  EXPECT_EQ(3U, ports_.size());
+  EXPECT_PRED5(CheckCandidate, candidates_[2],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "udp", kRelayUdpIntAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[3],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "tcp", kRelayTcpIntAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[4],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "ssltcp",
+      kRelaySslTcpIntAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[5],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "udp", kRelayUdpExtAddr);
+  // Stun Timeout is 9sec.
+  EXPECT_TRUE_WAIT(candidate_allocation_done_, 9000);
+}
+
+// Test to verify ICE restart process.
+TEST_F(PortAllocatorTest, TestGetAllPortsRestarts) {
+  AddInterface(kClientAddr);
+  EXPECT_TRUE(CreateSession(1));
+  session_->StartGettingPorts();
+  EXPECT_EQ_WAIT(7U, candidates_.size(), kDefaultAllocationTimeout);
+  EXPECT_EQ(4U, ports_.size());
+  EXPECT_TRUE(candidate_allocation_done_);
+  // TODO - Extend this to verify ICE restart.
+}
+
+TEST_F(PortAllocatorTest, TestBasicMuxFeatures) {
+  AddInterface(kClientAddr);
+  allocator().set_flags(cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+  // Session ID - session1.
+  talk_base::scoped_ptr<cricket::PortAllocatorSession> session1(
+      CreateSession("session1", cricket::ICE_CANDIDATE_COMPONENT_RTP));
+  talk_base::scoped_ptr<cricket::PortAllocatorSession> session2(
+      CreateSession("session1", cricket::ICE_CANDIDATE_COMPONENT_RTCP));
+  session1->StartGettingPorts();
+  session2->StartGettingPorts();
+  // Each session should receive two proxy ports of local and stun.
+  ASSERT_EQ_WAIT(14U, candidates_.size(), kDefaultAllocationTimeout);
+  EXPECT_EQ(8U, ports_.size());
+
+  talk_base::scoped_ptr<cricket::PortAllocatorSession> session3(
+      CreateSession("session1", cricket::ICE_CANDIDATE_COMPONENT_RTP));
+  session3->StartGettingPorts();
+  // Already allocated candidates and ports will be sent to the newly
+  // allocated proxy session.
+  ASSERT_EQ_WAIT(21U, candidates_.size(), kDefaultAllocationTimeout);
+  EXPECT_EQ(12U, ports_.size());
+}
+
+// This test verifies by changing ice_ufrag and/or ice_pwd
+// will result in different set of candidates when BUNDLE is enabled.
+// If BUNDLE is disabled, CreateSession will always allocate new
+// set of candidates.
+TEST_F(PortAllocatorTest, TestBundleIceRestart) {
+  AddInterface(kClientAddr);
+  allocator().set_flags(cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+  // Session ID - session1.
+  talk_base::scoped_ptr<cricket::PortAllocatorSession> session1(
+      CreateSession("session1", kContentName,
+                    cricket::ICE_CANDIDATE_COMPONENT_RTP,
+                    kIceUfrag0, kIcePwd0));
+  session1->StartGettingPorts();
+  ASSERT_EQ_WAIT(7U, candidates_.size(), kDefaultAllocationTimeout);
+  EXPECT_EQ(4U, ports_.size());
+
+  // Allocate a different session with sid |session1| and different ice_ufrag.
+  talk_base::scoped_ptr<cricket::PortAllocatorSession> session2(
+      CreateSession("session1", kContentName,
+                    cricket::ICE_CANDIDATE_COMPONENT_RTP,
+                    "TestIceUfrag", kIcePwd0));
+  session2->StartGettingPorts();
+  ASSERT_EQ_WAIT(14U, candidates_.size(), kDefaultAllocationTimeout);
+  EXPECT_EQ(8U, ports_.size());
+  // Verifying the candidate address different from previously allocated
+  // address.
+  // Skipping verification of component id and candidate type.
+  EXPECT_NE(candidates_[0].address(), candidates_[7].address());
+  EXPECT_NE(candidates_[1].address(), candidates_[8].address());
+
+  // Allocating a different session with sid |session1| and
+  // different ice_pwd.
+  talk_base::scoped_ptr<cricket::PortAllocatorSession> session3(
+      CreateSession("session1", kContentName,
+                    cricket::ICE_CANDIDATE_COMPONENT_RTP,
+                    kIceUfrag0, "TestIcePwd"));
+  session3->StartGettingPorts();
+  ASSERT_EQ_WAIT(21U, candidates_.size(), kDefaultAllocationTimeout);
+  EXPECT_EQ(12U, ports_.size());
+  // Verifying the candidate address different from previously
+  // allocated address.
+  EXPECT_NE(candidates_[7].address(), candidates_[14].address());
+  EXPECT_NE(candidates_[8].address(), candidates_[15].address());
+
+  // Allocating a session with by changing both ice_ufrag and ice_pwd.
+  talk_base::scoped_ptr<cricket::PortAllocatorSession> session4(
+      CreateSession("session1", kContentName,
+                    cricket::ICE_CANDIDATE_COMPONENT_RTP,
+                    "TestIceUfrag", "TestIcePwd"));
+  session4->StartGettingPorts();
+  ASSERT_EQ_WAIT(28U, candidates_.size(), kDefaultAllocationTimeout);
+  EXPECT_EQ(16U, ports_.size());
+  // Verifying the candidate address different from previously
+  // allocated address.
+  EXPECT_NE(candidates_[14].address(), candidates_[21].address());
+  EXPECT_NE(candidates_[15].address(), candidates_[22].address());
+}
+
+// Test that when the PORTALLOCATOR_ENABLE_SHARED_UFRAG is enabled we got same
+// ufrag and pwd for the collected candidates.
+TEST_F(PortAllocatorTest, TestEnableSharedUfrag) {
+  allocator().set_flags(allocator().flags() |
+                        cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG);
+  AddInterface(kClientAddr);
+  EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP));
+  session_->StartGettingPorts();
+  ASSERT_EQ_WAIT(7U, candidates_.size(), kDefaultAllocationTimeout);
+  EXPECT_PRED5(CheckCandidate, candidates_[0],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "udp", kClientAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[1],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "stun", "udp", kClientAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[5],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "tcp", kClientAddr);
+  EXPECT_EQ(4U, ports_.size());
+  EXPECT_EQ(kIceUfrag0, candidates_[0].username());
+  EXPECT_EQ(kIceUfrag0, candidates_[1].username());
+  EXPECT_EQ(kIceUfrag0, candidates_[2].username());
+  EXPECT_EQ(kIcePwd0, candidates_[0].password());
+  EXPECT_EQ(kIcePwd0, candidates_[1].password());
+  EXPECT_TRUE(candidate_allocation_done_);
+}
+
+// Test that when the PORTALLOCATOR_ENABLE_SHARED_UFRAG isn't enabled we got
+// different ufrag and pwd for the collected candidates.
+TEST_F(PortAllocatorTest, TestDisableSharedUfrag) {
+  allocator().set_flags(allocator().flags() &
+                        ~cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG);
+  AddInterface(kClientAddr);
+  EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP));
+  session_->StartGettingPorts();
+  ASSERT_EQ_WAIT(7U, candidates_.size(), kDefaultAllocationTimeout);
+  EXPECT_PRED5(CheckCandidate, candidates_[0],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "udp", kClientAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[1],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "stun", "udp", kClientAddr);
+  EXPECT_EQ(4U, ports_.size());
+  // Port should generate random ufrag and pwd.
+  EXPECT_NE(kIceUfrag0, candidates_[0].username());
+  EXPECT_NE(kIceUfrag0, candidates_[1].username());
+  EXPECT_NE(candidates_[0].username(), candidates_[1].username());
+  EXPECT_NE(kIcePwd0, candidates_[0].password());
+  EXPECT_NE(kIcePwd0, candidates_[1].password());
+  EXPECT_NE(candidates_[0].password(), candidates_[1].password());
+  EXPECT_TRUE(candidate_allocation_done_);
+}
+
+// Test that when PORTALLOCATOR_ENABLE_SHARED_SOCKET is enabled only one port
+// is allocated for udp and stun. Also verify there is only one candidate
+// (local) if stun candidate is same as local candidate, which will be the case
+// in a public network like the below test.
+TEST_F(PortAllocatorTest, TestEnableSharedSocketWithoutNat) {
+  AddInterface(kClientAddr);
+  allocator_->set_flags(allocator().flags() |
+                        cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG |
+                        cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET);
+  EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP));
+  session_->StartGettingPorts();
+  ASSERT_EQ_WAIT(6U, candidates_.size(), kDefaultAllocationTimeout);
+  EXPECT_EQ(3U, ports_.size());
+  EXPECT_PRED5(CheckCandidate, candidates_[0],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "udp", kClientAddr);
+  EXPECT_TRUE_WAIT(candidate_allocation_done_, kDefaultAllocationTimeout);
+}
+
+// Test that when PORTALLOCATOR_ENABLE_SHARED_SOCKET is enabled only one port
+// is allocated for udp and stun. In this test we should expect both stun and
+// local candidates as client behind a nat.
+TEST_F(PortAllocatorTest, TestEnableSharedSocketWithNat) {
+  AddInterface(kClientAddr);
+  talk_base::scoped_ptr<talk_base::NATServer> nat_server(
+      CreateNatServer(kNatAddr, talk_base::NAT_OPEN_CONE));
+  allocator_.reset(new cricket::BasicPortAllocator(
+      &network_manager_, &nat_socket_factory_, kStunAddr));
+  allocator_->set_step_delay(cricket::kMinimumStepDelay);
+  allocator_->set_flags(allocator().flags() |
+                        cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG |
+                        cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET);
+  EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP));
+  session_->StartGettingPorts();
+  ASSERT_EQ_WAIT(3U, candidates_.size(), kDefaultAllocationTimeout);
+  ASSERT_EQ(2U, ports_.size());
+  EXPECT_PRED5(CheckCandidate, candidates_[0],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "udp", kClientAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[1],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "stun", "udp",
+      talk_base::SocketAddress(kNatAddr.ipaddr(), 0));
+  EXPECT_TRUE_WAIT(candidate_allocation_done_, kDefaultAllocationTimeout);
+  EXPECT_EQ(3U, candidates_.size());
+}
+
+// This test verifies when PORTALLOCATOR_ENABLE_SHARED_SOCKET flag is enabled
+// and fail to generate STUN candidate, local UDP candidate is generated
+// properly.
+TEST_F(PortAllocatorTest, TestEnableSharedSocketNoUdpAllowed) {
+  allocator().set_flags(allocator().flags() |
+                        cricket::PORTALLOCATOR_DISABLE_RELAY |
+                        cricket::PORTALLOCATOR_DISABLE_TCP |
+                        cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG |
+                        cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET);
+  fss_->AddRule(false, talk_base::FP_UDP, talk_base::FD_ANY, kClientAddr);
+  AddInterface(kClientAddr);
+  EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP));
+  session_->StartGettingPorts();
+  ASSERT_EQ_WAIT(1U, ports_.size(), kDefaultAllocationTimeout);
+  EXPECT_EQ(1U, candidates_.size());
+  EXPECT_PRED5(CheckCandidate, candidates_[0],
+      cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "udp", kClientAddr);
+  // STUN timeout is 9sec. We need to wait to get candidate done signal.
+  EXPECT_TRUE_WAIT(candidate_allocation_done_, 10000);
+  EXPECT_EQ(1U, candidates_.size());
+}
+
+// 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 that the HttpPortAllocator uses correct URL to create sessions.
+TEST(HttpPortAllocatorTest, TestSessionRequestUrl) {
+  talk_base::FakeNetworkManager network_manager;
+  cricket::HttpPortAllocator alloc(&network_manager, "unit test agent");
+
+  // Disable PORTALLOCATOR_ENABLE_SHARED_UFRAG.
+  alloc.set_flags(alloc.flags() & ~cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG);
+  talk_base::scoped_ptr<cricket::HttpPortAllocatorSessionBase> session(
+      static_cast<cricket::HttpPortAllocatorSession*>(
+          alloc.CreateSessionInternal(
+              "test content", 0, kIceUfrag0, kIcePwd0)));
+  std::string url = session->GetSessionRequestUrl();
+  LOG(LS_INFO) << "url: " << url;
+  EXPECT_EQ(std::string(cricket::HttpPortAllocator::kCreateSessionURL), url);
+
+  // Enable PORTALLOCATOR_ENABLE_SHARED_UFRAG.
+  alloc.set_flags(alloc.flags() | cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG);
+  session.reset(static_cast<cricket::HttpPortAllocatorSession*>(
+      alloc.CreateSessionInternal("test content", 0, kIceUfrag0, kIcePwd0)));
+  url = session->GetSessionRequestUrl();
+  LOG(LS_INFO) << "url: " << url;
+  std::vector<std::string> parts;
+  talk_base::split(url, '?', &parts);
+  ASSERT_EQ(2U, parts.size());
+
+  std::vector<std::string> args_parts;
+  talk_base::split(parts[1], '&', &args_parts);
+
+  std::map<std::string, std::string> args;
+  for (std::vector<std::string>::iterator it = args_parts.begin();
+       it != args_parts.end(); ++it) {
+    std::vector<std::string> parts;
+    talk_base::split(*it, '=', &parts);
+    ASSERT_EQ(2U, parts.size());
+    args[talk_base::s_url_decode(parts[0])] = talk_base::s_url_decode(parts[1]);
+  }
+
+  EXPECT_EQ(kIceUfrag0, args["username"]);
+  EXPECT_EQ(kIcePwd0, args["password"]);
+}
diff --git a/talk/p2p/client/sessionmanagertask.h b/talk/p2p/client/sessionmanagertask.h
new file mode 100644
index 0000000..d7d9733
--- /dev/null
+++ b/talk/p2p/client/sessionmanagertask.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 _SESSIONMANAGERTASK_H_
+#define _SESSIONMANAGERTASK_H_
+
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/client/sessionsendtask.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+
+namespace cricket {
+
+// This class handles sending and receiving XMPP messages on behalf of the
+// SessionManager.  The sending part is handed over to SessionSendTask.
+
+class SessionManagerTask : public buzz::XmppTask {
+ public:
+  SessionManagerTask(buzz::XmppTaskParentInterface* parent,
+                     SessionManager* session_manager)
+      : buzz::XmppTask(parent, buzz::XmppEngine::HL_SINGLE),
+        session_manager_(session_manager) {
+  }
+
+  ~SessionManagerTask() {
+  }
+
+  // Turns on simple support for sending messages, using SessionSendTask.
+  void EnableOutgoingMessages() {
+    session_manager_->SignalOutgoingMessage.connect(
+        this, &SessionManagerTask::OnOutgoingMessage);
+    session_manager_->SignalRequestSignaling.connect(
+        session_manager_, &SessionManager::OnSignalingReady);
+  }
+
+  virtual int ProcessStart() {
+    const buzz::XmlElement *stanza = NextStanza();
+    if (stanza == NULL)
+      return STATE_BLOCKED;
+    session_manager_->OnIncomingMessage(stanza);
+    return STATE_START;
+  }
+
+ protected:
+  virtual bool HandleStanza(const buzz::XmlElement *stanza) {
+    if (!session_manager_->IsSessionMessage(stanza))
+      return false;
+    // Responses are handled by the SessionSendTask that sent the request.
+    //if (stanza->Attr(buzz::QN_TYPE) != buzz::STR_SET)
+    //  return false;
+    QueueStanza(stanza);
+    return true;
+  }
+
+ private:
+  void OnOutgoingMessage(SessionManager* manager,
+                         const buzz::XmlElement* stanza) {
+    cricket::SessionSendTask* sender =
+        new cricket::SessionSendTask(parent_, session_manager_);
+    sender->Send(stanza);
+    sender->Start();
+  }
+
+  SessionManager* session_manager_;
+};
+
+}  // namespace cricket
+
+#endif // _SESSIONMANAGERTASK_H_
diff --git a/talk/p2p/client/sessionsendtask.h b/talk/p2p/client/sessionsendtask.h
new file mode 100644
index 0000000..6c7508a
--- /dev/null
+++ b/talk/p2p/client/sessionsendtask.h
@@ -0,0 +1,145 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_CLIENT_SESSIONSENDTASK_H_
+#define TALK_P2P_CLIENT_SESSIONSENDTASK_H_
+
+#include "talk/base/common.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+#include "talk/p2p/base/sessionmanager.h"
+
+namespace cricket {
+
+// The job of this task is to send an IQ stanza out (after stamping it with
+// an ID attribute) and then wait for a response.  If not response happens
+// within 5 seconds, it will signal failure on a SessionManager.  If an error
+// happens it will also signal failure.  If, however, the send succeeds this
+// task will quietly go away.
+
+class SessionSendTask : public buzz::XmppTask {
+ public:
+  SessionSendTask(buzz::XmppTaskParentInterface* parent,
+                  SessionManager* session_manager)
+    : buzz::XmppTask(parent, buzz::XmppEngine::HL_SINGLE),
+      session_manager_(session_manager) {
+    set_timeout_seconds(15);
+    session_manager_->SignalDestroyed.connect(
+        this, &SessionSendTask::OnSessionManagerDestroyed);
+  }
+
+  virtual ~SessionSendTask() {
+    SignalDone(this);
+  }
+
+  void Send(const buzz::XmlElement* stanza) {
+    ASSERT(stanza_.get() == NULL);
+
+    // This should be an IQ of type set, result, or error.  In the first case,
+    // we supply an ID.  In the others, it should be present.
+    ASSERT(stanza->Name() == buzz::QN_IQ);
+    ASSERT(stanza->HasAttr(buzz::QN_TYPE));
+    if (stanza->Attr(buzz::QN_TYPE) == "set") {
+      ASSERT(!stanza->HasAttr(buzz::QN_ID));
+    } else {
+      ASSERT((stanza->Attr(buzz::QN_TYPE) == "result") ||
+             (stanza->Attr(buzz::QN_TYPE) == "error"));
+      ASSERT(stanza->HasAttr(buzz::QN_ID));
+    }
+
+    stanza_.reset(new buzz::XmlElement(*stanza));
+    if (stanza_->HasAttr(buzz::QN_ID)) {
+      set_task_id(stanza_->Attr(buzz::QN_ID));
+    } else {
+      stanza_->SetAttr(buzz::QN_ID, task_id());
+    }
+  }
+
+  void OnSessionManagerDestroyed() {
+    // If the session manager doesn't exist anymore, we should still try to
+    // send the message, but avoid calling back into the SessionManager.
+    session_manager_ = NULL;
+  }
+
+  sigslot::signal1<SessionSendTask *> SignalDone;
+
+ protected:
+  virtual int OnTimeout() {
+    if (session_manager_ != NULL) {
+      session_manager_->OnFailedSend(stanza_.get(), NULL);
+    }
+
+    return XmppTask::OnTimeout();
+  }
+
+  virtual int ProcessStart() {
+    SendStanza(stanza_.get());
+    if (stanza_->Attr(buzz::QN_TYPE) == buzz::STR_SET) {
+      return STATE_RESPONSE;
+    } else {
+      return STATE_DONE;
+    }
+  }
+
+  virtual int ProcessResponse() {
+    const buzz::XmlElement* next = NextStanza();
+    if (next == NULL)
+      return STATE_BLOCKED;
+
+    if (session_manager_ != NULL) {
+      if (next->Attr(buzz::QN_TYPE) == buzz::STR_RESULT) {
+        session_manager_->OnIncomingResponse(stanza_.get(), next);
+      } else {
+        session_manager_->OnFailedSend(stanza_.get(), next);
+      }
+    }
+
+    return STATE_DONE;
+  }
+
+  virtual bool HandleStanza(const buzz::XmlElement *stanza) {
+    if (!MatchResponseIq(stanza,
+                         buzz::Jid(stanza_->Attr(buzz::QN_TO)), task_id()))
+      return false;
+    if (stanza->Attr(buzz::QN_TYPE) == buzz::STR_RESULT ||
+        stanza->Attr(buzz::QN_TYPE) == buzz::STR_ERROR) {
+      QueueStanza(stanza);
+      return true;
+    }
+    return false;
+  }
+
+ private:
+  SessionManager *session_manager_;
+  talk_base::scoped_ptr<buzz::XmlElement> stanza_;
+};
+
+}
+
+#endif // TALK_P2P_CLIENT_SESSIONSENDTASK_H_
diff --git a/talk/p2p/client/socketmonitor.cc b/talk/p2p/client/socketmonitor.cc
new file mode 100644
index 0000000..e0c75d4
--- /dev/null
+++ b/talk/p2p/client/socketmonitor.cc
@@ -0,0 +1,114 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/p2p/client/socketmonitor.h"
+
+#include "talk/base/common.h"
+
+namespace cricket {
+
+enum {
+  MSG_MONITOR_POLL,
+  MSG_MONITOR_START,
+  MSG_MONITOR_STOP,
+  MSG_MONITOR_SIGNAL
+};
+
+SocketMonitor::SocketMonitor(TransportChannel* channel,
+                             talk_base::Thread* worker_thread,
+                             talk_base::Thread* monitor_thread) {
+  channel_ = channel;
+  channel_thread_ = worker_thread;
+  monitoring_thread_ = monitor_thread;
+  monitoring_ = false;
+}
+
+SocketMonitor::~SocketMonitor() {
+  channel_thread_->Clear(this);
+  monitoring_thread_->Clear(this);
+}
+
+void SocketMonitor::Start(int milliseconds) {
+  rate_ = milliseconds;
+  if (rate_ < 250)
+    rate_ = 250;
+  channel_thread_->Post(this, MSG_MONITOR_START);
+}
+
+void SocketMonitor::Stop() {
+  channel_thread_->Post(this, MSG_MONITOR_STOP);
+}
+
+void SocketMonitor::OnMessage(talk_base::Message *message) {
+  talk_base::CritScope cs(&crit_);
+  switch (message->message_id) {
+    case MSG_MONITOR_START:
+      ASSERT(talk_base::Thread::Current() == channel_thread_);
+      if (!monitoring_) {
+        monitoring_ = true;
+        PollSocket(true);
+      }
+      break;
+
+    case MSG_MONITOR_STOP:
+      ASSERT(talk_base::Thread::Current() == channel_thread_);
+      if (monitoring_) {
+        monitoring_ = false;
+        channel_thread_->Clear(this);
+      }
+      break;
+
+    case MSG_MONITOR_POLL:
+      ASSERT(talk_base::Thread::Current() == channel_thread_);
+      PollSocket(true);
+      break;
+
+    case MSG_MONITOR_SIGNAL: {
+      ASSERT(talk_base::Thread::Current() == monitoring_thread_);
+      std::vector<ConnectionInfo> infos = connection_infos_;
+      crit_.Leave();
+      SignalUpdate(this, infos);
+      crit_.Enter();
+      break;
+    }
+  }
+}
+
+void SocketMonitor::PollSocket(bool poll) {
+  ASSERT(talk_base::Thread::Current() == channel_thread_);
+  talk_base::CritScope cs(&crit_);
+
+  // Gather connection infos
+  channel_->GetStats(&connection_infos_);
+
+  // Signal the monitoring thread, start another poll timer
+  monitoring_thread_->Post(this, MSG_MONITOR_SIGNAL);
+  if (poll)
+    channel_thread_->PostDelayed(rate_, this, MSG_MONITOR_POLL);
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/client/socketmonitor.h b/talk/p2p/client/socketmonitor.h
new file mode 100644
index 0000000..f24ad66
--- /dev/null
+++ b/talk/p2p/client/socketmonitor.h
@@ -0,0 +1,71 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_CLIENT_SOCKETMONITOR_H_
+#define TALK_P2P_CLIENT_SOCKETMONITOR_H_
+
+#include <vector>
+
+#include "talk/base/criticalsection.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/transportchannel.h"
+
+namespace cricket {
+
+class SocketMonitor : public talk_base::MessageHandler,
+                      public sigslot::has_slots<> {
+ public:
+  SocketMonitor(TransportChannel* channel,
+                talk_base::Thread* worker_thread,
+                talk_base::Thread* monitor_thread);
+  ~SocketMonitor();
+
+  void Start(int cms);
+  void Stop();
+
+  talk_base::Thread* monitor_thread() { return monitoring_thread_; }
+
+  sigslot::signal2<SocketMonitor*,
+                   const std::vector<ConnectionInfo>&> SignalUpdate;
+
+ protected:
+  void OnMessage(talk_base::Message* message);
+  void PollSocket(bool poll);
+
+  std::vector<ConnectionInfo> connection_infos_;
+  TransportChannel* channel_;
+  talk_base::Thread* channel_thread_;
+  talk_base::Thread* monitoring_thread_;
+  talk_base::CriticalSection crit_;
+  uint32 rate_;
+  bool monitoring_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_CLIENT_SOCKETMONITOR_H_
diff --git a/talk/session/media/audiomonitor.cc b/talk/session/media/audiomonitor.cc
new file mode 100644
index 0000000..385702f
--- /dev/null
+++ b/talk/session/media/audiomonitor.cc
@@ -0,0 +1,121 @@
+/*
+ * 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/media/audiomonitor.h"
+#include "talk/session/media/voicechannel.h"
+#include <cassert>
+
+namespace cricket {
+
+const uint32 MSG_MONITOR_POLL = 1;
+const uint32 MSG_MONITOR_START = 2;
+const uint32 MSG_MONITOR_STOP = 3;
+const uint32 MSG_MONITOR_SIGNAL = 4;
+
+AudioMonitor::AudioMonitor(VoiceChannel *voice_channel,
+                           talk_base::Thread *monitor_thread) {
+  voice_channel_ = voice_channel;
+  monitoring_thread_ = monitor_thread;
+  monitoring_ = false;
+}
+
+AudioMonitor::~AudioMonitor() {
+  voice_channel_->worker_thread()->Clear(this);
+  monitoring_thread_->Clear(this);
+}
+
+void AudioMonitor::Start(int milliseconds) {
+  rate_ = milliseconds;
+  if (rate_ < 100)
+    rate_ = 100;
+  voice_channel_->worker_thread()->Post(this, MSG_MONITOR_START);
+}
+
+void AudioMonitor::Stop() {
+  voice_channel_->worker_thread()->Post(this, MSG_MONITOR_STOP);
+}
+
+void AudioMonitor::OnMessage(talk_base::Message *message) {
+  talk_base::CritScope cs(&crit_);
+
+  switch (message->message_id) {
+  case MSG_MONITOR_START:
+    assert(talk_base::Thread::Current() == voice_channel_->worker_thread());
+    if (!monitoring_) {
+      monitoring_ = true;
+      PollVoiceChannel();
+    }
+    break;
+
+  case MSG_MONITOR_STOP:
+    assert(talk_base::Thread::Current() == voice_channel_->worker_thread());
+    if (monitoring_) {
+      monitoring_ = false;
+      voice_channel_->worker_thread()->Clear(this);
+    }
+    break;
+
+  case MSG_MONITOR_POLL:
+    assert(talk_base::Thread::Current() == voice_channel_->worker_thread());
+    PollVoiceChannel();
+    break;
+
+  case MSG_MONITOR_SIGNAL:
+    {
+      assert(talk_base::Thread::Current() == monitoring_thread_);
+      AudioInfo info = audio_info_;
+      crit_.Leave();
+      SignalUpdate(this, info);
+      crit_.Enter();
+    }
+    break;
+  }
+}
+
+void AudioMonitor::PollVoiceChannel() {
+  talk_base::CritScope cs(&crit_);
+  assert(talk_base::Thread::Current() == voice_channel_->worker_thread());
+
+  // Gather connection infos
+  audio_info_.input_level = voice_channel_->GetInputLevel_w();
+  audio_info_.output_level = voice_channel_->GetOutputLevel_w();
+  voice_channel_->GetActiveStreams_w(&audio_info_.active_streams);
+
+  // Signal the monitoring thread, start another poll timer
+  monitoring_thread_->Post(this, MSG_MONITOR_SIGNAL);
+  voice_channel_->worker_thread()->PostDelayed(rate_, this, MSG_MONITOR_POLL);
+}
+
+VoiceChannel *AudioMonitor::voice_channel() {
+  return voice_channel_;
+}
+
+talk_base::Thread *AudioMonitor::monitor_thread() {
+  return monitoring_thread_;
+}
+
+}
diff --git a/talk/session/media/audiomonitor.h b/talk/session/media/audiomonitor.h
new file mode 100644
index 0000000..5aff8fd
--- /dev/null
+++ b/talk/session/media/audiomonitor.h
@@ -0,0 +1,75 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_MEDIA_AUDIOMONITOR_H_
+#define TALK_SESSION_MEDIA_AUDIOMONITOR_H_
+
+#include "talk/base/sigslot.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/port.h"
+#include <vector>
+
+namespace cricket {
+
+class VoiceChannel;
+
+struct AudioInfo {
+  int input_level;
+  int output_level;
+  typedef std::vector<std::pair<uint32, int> > StreamList;
+  StreamList active_streams; // ssrcs contributing to output_level
+};
+
+class AudioMonitor : public talk_base::MessageHandler,
+    public sigslot::has_slots<> {
+ public:
+  AudioMonitor(VoiceChannel* voice_channel, talk_base::Thread *monitor_thread);
+  ~AudioMonitor();
+
+  void Start(int cms);
+  void Stop();
+
+  VoiceChannel* voice_channel();
+  talk_base::Thread *monitor_thread();
+
+  sigslot::signal2<AudioMonitor*, const AudioInfo&> SignalUpdate;
+
+ protected:
+  void OnMessage(talk_base::Message *message);
+  void PollVoiceChannel();
+
+  AudioInfo audio_info_;
+  VoiceChannel* voice_channel_;
+  talk_base::Thread* monitoring_thread_;
+  talk_base::CriticalSection crit_;
+  uint32 rate_;
+  bool monitoring_;
+};
+
+}
+
+#endif  // TALK_SESSION_MEDIA_AUDIOMONITOR_H_
diff --git a/talk/session/media/call.cc b/talk/session/media/call.cc
new file mode 100644
index 0000000..bd22aab
--- /dev/null
+++ b/talk/session/media/call.cc
@@ -0,0 +1,1027 @@
+/*
+ * 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/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/thread.h"
+#include "talk/base/window.h"
+#include "talk/media/base/constants.h"
+#include "talk/media/base/screencastid.h"
+#include "talk/p2p/base/parsing.h"
+#include "talk/session/media/call.h"
+#include "talk/session/media/mediasessionclient.h"
+
+namespace cricket {
+
+const uint32 MSG_CHECKAUTODESTROY = 1;
+const uint32 MSG_TERMINATECALL = 2;
+const uint32 MSG_PLAYDTMF = 3;
+
+namespace {
+const int kDTMFDelay = 300;  // msec
+const size_t kMaxDTMFDigits = 30;
+const int kSendToVoicemailTimeout = 1000*20;
+const int kNoVoicemailTimeout = 1000*180;
+const int kMediaMonitorInterval = 1000*15;
+// In order to be the same as the server-side switching, this must be 100.
+const int kAudioMonitorPollPeriodMillis = 100;
+
+// V is a pointer type.
+template<class K, class V>
+V FindOrNull(const std::map<K, V>& map,
+             const K& key) {
+  typename std::map<K, V>::const_iterator it = map.find(key);
+  return (it != map.end()) ? it->second : NULL;
+}
+
+}
+
+Call::Call(MediaSessionClient* session_client)
+    : id_(talk_base::CreateRandomId()),
+      session_client_(session_client),
+      local_renderer_(NULL),
+      has_video_(false),
+      has_data_(false),
+      muted_(false),
+      video_muted_(false),
+      send_to_voicemail_(true),
+      playing_dtmf_(false) {
+}
+
+Call::~Call() {
+  while (media_session_map_.begin() != media_session_map_.end()) {
+    Session* session = media_session_map_.begin()->second.session;
+    RemoveSession(session);
+    session_client_->session_manager()->DestroySession(session);
+  }
+  talk_base::Thread::Current()->Clear(this);
+}
+
+Session* Call::InitiateSession(const buzz::Jid& to,
+                               const buzz::Jid& initiator,
+                               const CallOptions& options) {
+  const SessionDescription* offer = session_client_->CreateOffer(options);
+
+  Session* session = session_client_->CreateSession(this);
+  session->set_initiator_name(initiator.Str());
+
+  AddSession(session, offer);
+  session->Initiate(to.Str(), offer);
+
+  // After this timeout, terminate the call because the callee isn't
+  // answering
+  session_client_->session_manager()->signaling_thread()->Clear(this,
+      MSG_TERMINATECALL);
+  session_client_->session_manager()->signaling_thread()->PostDelayed(
+    send_to_voicemail_ ? kSendToVoicemailTimeout : kNoVoicemailTimeout,
+    this, MSG_TERMINATECALL);
+  return session;
+}
+
+void Call::IncomingSession(Session* session, const SessionDescription* offer) {
+  AddSession(session, offer);
+
+  // Make sure the session knows about the incoming ssrcs. This needs to be done
+  // prior to the SignalSessionState call, because that may trigger handling of
+  // these new SSRCs, so they need to be registered before then.
+  UpdateRemoteMediaStreams(session, offer->contents(), false);
+
+  // Missed the first state, the initiate, which is needed by
+  // call_client.
+  SignalSessionState(this, session, Session::STATE_RECEIVEDINITIATE);
+}
+
+void Call::AcceptSession(Session* session,
+                         const cricket::CallOptions& options) {
+  MediaSessionMap::iterator it = media_session_map_.find(session->id());
+  if (it != media_session_map_.end()) {
+    const SessionDescription* answer = session_client_->CreateAnswer(
+        session->remote_description(), options);
+    it->second.session->Accept(answer);
+  }
+}
+
+void Call::RejectSession(Session* session) {
+  // Assume polite decline.
+  MediaSessionMap::iterator it = media_session_map_.find(session->id());
+  if (it != media_session_map_.end())
+    it->second.session->Reject(STR_TERMINATE_DECLINE);
+}
+
+void Call::TerminateSession(Session* session) {
+  MediaSessionMap::iterator it = media_session_map_.find(session->id());
+  if (it != media_session_map_.end()) {
+    // Assume polite terminations.
+    it->second.session->Terminate();
+  }
+}
+
+void Call::Terminate() {
+  // Copy the list so that we can iterate over it in a stable way
+  std::vector<Session*> sessions = this->sessions();
+
+  // There may be more than one session to terminate
+  std::vector<Session*>::iterator it;
+  for (it = sessions.begin(); it != sessions.end(); ++it) {
+    TerminateSession(*it);
+  }
+}
+
+bool Call::SendViewRequest(Session* session,
+                           const ViewRequest& view_request) {
+  StaticVideoViews::const_iterator it;
+  for (it = view_request.static_video_views.begin();
+       it != view_request.static_video_views.end(); ++it) {
+    StreamParams found_stream;
+    bool found = false;
+    MediaStreams* recv_streams = GetMediaStreams(session);
+    if (recv_streams)
+      found = recv_streams->GetVideoStream(it->selector, &found_stream);
+    if (!found) {
+      LOG(LS_WARNING) << "Trying to send view request for ("
+                      << it->selector.ssrc << ", '"
+                      << it->selector.groupid << "', '"
+                      << it->selector.streamid << "'"
+                      << ") is not in the local streams.";
+      return false;
+    }
+  }
+
+  XmlElements elems;
+  WriteError error;
+  if (!WriteJingleViewRequest(CN_VIDEO, view_request, &elems, &error)) {
+    LOG(LS_ERROR) << "Couldn't write out view request: " << error.text;
+    return false;
+  }
+
+  return session->SendInfoMessage(elems);
+}
+
+void Call::SetLocalRenderer(VideoRenderer* renderer) {
+  local_renderer_ = renderer;
+  if (session_client_->GetFocus() == this) {
+    session_client_->channel_manager()->SetLocalRenderer(renderer);
+  }
+}
+
+void Call::SetVideoRenderer(Session* session, uint32 ssrc,
+                            VideoRenderer* renderer) {
+  VideoChannel* video_channel = GetVideoChannel(session);
+  if (video_channel) {
+    video_channel->SetRenderer(ssrc, renderer);
+    LOG(LS_INFO) << "Set renderer of ssrc " << ssrc
+                 << " to " << renderer << ".";
+  } else {
+    LOG(LS_INFO) << "Failed to set renderer of ssrc " << ssrc << ".";
+  }
+}
+
+void Call::OnMessage(talk_base::Message* message) {
+  switch (message->message_id) {
+  case MSG_CHECKAUTODESTROY:
+    // If no more sessions for this call, delete it
+    if (media_session_map_.empty())
+      session_client_->DestroyCall(this);
+    break;
+  case MSG_TERMINATECALL:
+    // Signal to the user that a timeout has happened and the call should
+    // be sent to voicemail.
+    if (send_to_voicemail_) {
+      SignalSetupToCallVoicemail();
+    }
+
+    // Callee didn't answer - terminate call
+    Terminate();
+    break;
+  case MSG_PLAYDTMF:
+    ContinuePlayDTMF();
+  }
+}
+
+std::vector<Session*> Call::sessions() {
+  std::vector<Session*> sessions;
+  MediaSessionMap::iterator it;
+  for (it = media_session_map_.begin(); it != media_session_map_.end(); ++it)
+    sessions.push_back(it->second.session);
+
+  return sessions;
+}
+
+bool Call::AddSession(Session* session, const SessionDescription* offer) {
+  bool succeeded = true;
+  MediaSession media_session;
+  media_session.session = session;
+  media_session.voice_channel = NULL;
+  media_session.video_channel = NULL;
+  media_session.data_channel = NULL;
+  media_session.recv_streams = NULL;
+
+  const ContentInfo* audio_offer = GetFirstAudioContent(offer);
+  const ContentInfo* video_offer = GetFirstVideoContent(offer);
+  const ContentInfo* data_offer = GetFirstDataContent(offer);
+  has_video_ = (video_offer != NULL);
+  has_data_ = (data_offer != NULL);
+
+  ASSERT(audio_offer != NULL);
+  // Create voice channel and start a media monitor.
+  media_session.voice_channel =
+      session_client_->channel_manager()->CreateVoiceChannel(
+          session, audio_offer->name, has_video_);
+  // voice_channel can be NULL in case of NullVoiceEngine.
+  if (media_session.voice_channel) {
+    media_session.voice_channel->SignalMediaMonitor.connect(
+        this, &Call::OnMediaMonitor);
+    media_session.voice_channel->StartMediaMonitor(kMediaMonitorInterval);
+  } else {
+    succeeded = false;
+  }
+
+  // If desired, create video channel and start a media monitor.
+  if (has_video_ && succeeded) {
+    media_session.video_channel =
+        session_client_->channel_manager()->CreateVideoChannel(
+            session, video_offer->name, true, media_session.voice_channel);
+    // video_channel can be NULL in case of NullVideoEngine.
+    if (media_session.video_channel) {
+      media_session.video_channel->SignalMediaMonitor.connect(
+          this, &Call::OnMediaMonitor);
+      media_session.video_channel->StartMediaMonitor(kMediaMonitorInterval);
+    } else {
+      succeeded = false;
+    }
+  }
+
+  // If desired, create data channel.
+  if (has_data_ && succeeded) {
+    const DataContentDescription* data = GetFirstDataContentDescription(offer);
+    if (data == NULL) {
+      succeeded = false;
+    } else {
+      DataChannelType data_channel_type = DCT_RTP;
+      if ((data->protocol() == kMediaProtocolSctp) ||
+          (data->protocol() == kMediaProtocolDtlsSctp)) {
+        data_channel_type = DCT_SCTP;
+      }
+
+      bool rtcp = false;
+      media_session.data_channel =
+          session_client_->channel_manager()->CreateDataChannel(
+              session, data_offer->name, rtcp, data_channel_type);
+      if (media_session.data_channel) {
+        media_session.data_channel->SignalDataReceived.connect(
+            this, &Call::OnDataReceived);
+      } else {
+        succeeded = false;
+      }
+    }
+  }
+
+  if (succeeded) {
+    // Add session to list, create channels for this session.
+    media_session.recv_streams = new MediaStreams;
+    media_session_map_[session->id()] = media_session;
+    session->SignalState.connect(this, &Call::OnSessionState);
+    session->SignalError.connect(this, &Call::OnSessionError);
+    session->SignalInfoMessage.connect(
+        this, &Call::OnSessionInfoMessage);
+    session->SignalRemoteDescriptionUpdate.connect(
+        this, &Call::OnRemoteDescriptionUpdate);
+    session->SignalReceivedTerminateReason
+      .connect(this, &Call::OnReceivedTerminateReason);
+
+    // If this call has the focus, enable this session's channels.
+    if (session_client_->GetFocus() == this) {
+      EnableSessionChannels(session, true);
+    }
+
+    // Signal client.
+    SignalAddSession(this, session);
+  }
+
+  return succeeded;
+}
+
+void Call::RemoveSession(Session* session) {
+  MediaSessionMap::iterator it = media_session_map_.find(session->id());
+  if (it == media_session_map_.end())
+    return;
+
+  // Remove all the screencasts, if they haven't been already.
+  while (!it->second.started_screencasts.empty()) {
+    uint32 ssrc = it->second.started_screencasts.begin()->first;
+    if (!StopScreencastWithoutSendingUpdate(it->second.session, ssrc)) {
+      LOG(LS_ERROR) << "Unable to stop screencast with ssrc " << ssrc;
+      ASSERT(false);
+    }
+  }
+
+  // Destroy video channel
+  VideoChannel* video_channel = it->second.video_channel;
+  if (video_channel != NULL)
+    session_client_->channel_manager()->DestroyVideoChannel(video_channel);
+
+  // Destroy voice channel
+  VoiceChannel* voice_channel = it->second.voice_channel;
+  if (voice_channel != NULL)
+    session_client_->channel_manager()->DestroyVoiceChannel(voice_channel);
+
+  // Destroy data channel
+  DataChannel* data_channel = it->second.data_channel;
+  if (data_channel != NULL)
+    session_client_->channel_manager()->DestroyDataChannel(data_channel);
+
+  delete it->second.recv_streams;
+  media_session_map_.erase(it);
+
+  // Destroy speaker monitor
+  StopSpeakerMonitor(session);
+
+  // Signal client
+  SignalRemoveSession(this, session);
+
+  // The call auto destroys when the last session is removed
+  talk_base::Thread::Current()->Post(this, MSG_CHECKAUTODESTROY);
+}
+
+VoiceChannel* Call::GetVoiceChannel(Session* session) const {
+  MediaSessionMap::const_iterator it = media_session_map_.find(session->id());
+  return (it != media_session_map_.end()) ? it->second.voice_channel : NULL;
+}
+
+VideoChannel* Call::GetVideoChannel(Session* session) const {
+  MediaSessionMap::const_iterator it = media_session_map_.find(session->id());
+  return (it != media_session_map_.end()) ? it->second.video_channel : NULL;
+}
+
+DataChannel* Call::GetDataChannel(Session* session) const {
+  MediaSessionMap::const_iterator it = media_session_map_.find(session->id());
+  return (it != media_session_map_.end()) ? it->second.data_channel : NULL;
+}
+
+MediaStreams* Call::GetMediaStreams(Session* session) const {
+  MediaSessionMap::const_iterator it = media_session_map_.find(session->id());
+  return (it != media_session_map_.end()) ? it->second.recv_streams : NULL;
+}
+
+void Call::EnableChannels(bool enable) {
+  MediaSessionMap::iterator it;
+  for (it = media_session_map_.begin(); it != media_session_map_.end(); ++it) {
+    EnableSessionChannels(it->second.session, enable);
+  }
+  session_client_->channel_manager()->SetLocalRenderer(
+      (enable) ? local_renderer_ : NULL);
+}
+
+void Call::EnableSessionChannels(Session* session, bool enable) {
+  MediaSessionMap::iterator it = media_session_map_.find(session->id());
+  if (it == media_session_map_.end())
+    return;
+
+  VoiceChannel* voice_channel = it->second.voice_channel;
+  VideoChannel* video_channel = it->second.video_channel;
+  DataChannel* data_channel = it->second.data_channel;
+  if (voice_channel != NULL)
+    voice_channel->Enable(enable);
+  if (video_channel != NULL)
+    video_channel->Enable(enable);
+  if (data_channel != NULL)
+    data_channel->Enable(enable);
+}
+
+void Call::Mute(bool mute) {
+  muted_ = mute;
+  MediaSessionMap::iterator it;
+  for (it = media_session_map_.begin(); it != media_session_map_.end(); ++it) {
+    if (it->second.voice_channel != NULL)
+      it->second.voice_channel->MuteStream(0, mute);
+  }
+}
+
+void Call::MuteVideo(bool mute) {
+  video_muted_ = mute;
+  MediaSessionMap::iterator it;
+  for (it = media_session_map_.begin(); it != media_session_map_.end(); ++it) {
+    if (it->second.video_channel != NULL)
+      it->second.video_channel->MuteStream(0, mute);
+  }
+}
+
+bool Call::SendData(Session* session,
+                    const SendDataParams& params,
+                    const talk_base::Buffer& payload,
+                    SendDataResult* result) {
+  DataChannel* data_channel = GetDataChannel(session);
+  if (!data_channel) {
+    LOG(LS_WARNING) << "Could not send data: no data channel.";
+    return false;
+  }
+
+  return data_channel->SendData(params, payload, result);
+}
+
+void Call::PressDTMF(int event) {
+  // Queue up this digit
+  if (queued_dtmf_.size() < kMaxDTMFDigits) {
+    LOG(LS_INFO) << "Call::PressDTMF(" << event << ")";
+
+    queued_dtmf_.push_back(event);
+
+    if (!playing_dtmf_) {
+      ContinuePlayDTMF();
+    }
+  }
+}
+
+cricket::VideoFormat ScreencastFormatFromFps(int fps) {
+  // The capturer pretty much ignore this, but just in case we give it
+  // a resolution big enough to cover any expected desktop.  In any
+  // case, it can't be 0x0, or the CaptureManager will fail to use it.
+  return cricket::VideoFormat(
+      1, 1,
+      cricket::VideoFormat::FpsToInterval(fps), cricket::FOURCC_ANY);
+}
+
+bool Call::StartScreencast(Session* session,
+                           const std::string& streamid, uint32 ssrc,
+                           const ScreencastId& screencastid, int fps) {
+  MediaSessionMap::iterator it = media_session_map_.find(session->id());
+  if (it == media_session_map_.end()) {
+    return false;
+  }
+
+  VideoChannel *video_channel = GetVideoChannel(session);
+  if (!video_channel) {
+    LOG(LS_WARNING) << "Cannot add screencast"
+                    << " because there is no video channel.";
+    return false;
+  }
+
+  VideoCapturer *capturer = video_channel->AddScreencast(ssrc, screencastid);
+  if (capturer == NULL) {
+    LOG(LS_WARNING) << "Could not create screencast capturer.";
+    return false;
+  }
+
+  VideoFormat format = ScreencastFormatFromFps(fps);
+  if (!session_client_->channel_manager()->StartVideoCapture(
+          capturer, format)) {
+    LOG(LS_WARNING) << "Could not start video capture.";
+    video_channel->RemoveScreencast(ssrc);
+    return false;
+  }
+
+  if (!video_channel->SetCapturer(ssrc, capturer)) {
+    LOG(LS_WARNING) << "Could not start sending screencast.";
+    session_client_->channel_manager()->StopVideoCapture(
+        capturer, ScreencastFormatFromFps(fps));
+    video_channel->RemoveScreencast(ssrc);
+  }
+
+  // TODO(pthatcher): Once the CaptureManager has a nicer interface
+  // for removing captures (such as having StartCapture return a
+  // handle), remove this StartedCapture stuff.
+  it->second.started_screencasts.insert(
+      std::make_pair(ssrc, StartedCapture(capturer, format)));
+
+  // TODO(pthatcher): Verify we aren't re-using an existing id or
+  // ssrc.
+  StreamParams stream;
+  stream.id = streamid;
+  stream.ssrcs.push_back(ssrc);
+  VideoContentDescription* video = CreateVideoStreamUpdate(stream);
+
+  // TODO(pthatcher): Wait until view request before sending video.
+  video_channel->SetLocalContent(video, CA_UPDATE);
+  SendVideoStreamUpdate(session, video);
+  return true;
+}
+
+bool Call::StopScreencast(Session* session,
+                          const std::string& streamid, uint32 ssrc) {
+  if (!StopScreencastWithoutSendingUpdate(session, ssrc)) {
+    return false;
+  }
+
+  VideoChannel *video_channel = GetVideoChannel(session);
+  if (!video_channel) {
+    LOG(LS_WARNING) << "Cannot add screencast"
+                    << " because there is no video channel.";
+    return false;
+  }
+
+  StreamParams stream;
+  stream.id = streamid;
+  // No ssrcs
+  VideoContentDescription* video = CreateVideoStreamUpdate(stream);
+
+  video_channel->SetLocalContent(video, CA_UPDATE);
+  SendVideoStreamUpdate(session, video);
+  return true;
+}
+
+bool Call::StopScreencastWithoutSendingUpdate(
+    Session* session, uint32 ssrc) {
+  MediaSessionMap::iterator it = media_session_map_.find(session->id());
+  if (it == media_session_map_.end()) {
+    return false;
+  }
+
+  VideoChannel *video_channel = GetVideoChannel(session);
+  if (!video_channel) {
+    LOG(LS_WARNING) << "Cannot remove screencast"
+                    << " because there is no video channel.";
+    return false;
+  }
+
+  StartedScreencastMap::const_iterator screencast_iter =
+      it->second.started_screencasts.find(ssrc);
+  if (screencast_iter == it->second.started_screencasts.end()) {
+    LOG(LS_WARNING) << "Could not stop screencast " << ssrc
+                    << " because there is no capturer.";
+    return false;
+  }
+
+  VideoCapturer* capturer = screencast_iter->second.capturer;
+  VideoFormat format = screencast_iter->second.format;
+  video_channel->SetCapturer(ssrc, NULL);
+  if (!session_client_->channel_manager()->StopVideoCapture(
+          capturer, format)) {
+    LOG(LS_WARNING) << "Could not stop screencast " << ssrc
+                    << " because could not stop capture.";
+    return false;
+  }
+  video_channel->RemoveScreencast(ssrc);
+  it->second.started_screencasts.erase(ssrc);
+  return true;
+}
+
+VideoContentDescription* Call::CreateVideoStreamUpdate(
+    const StreamParams& stream) {
+  VideoContentDescription* video = new VideoContentDescription();
+  video->set_multistream(true);
+  video->set_partial(true);
+  video->AddStream(stream);
+  return video;
+}
+
+void Call::SendVideoStreamUpdate(
+    Session* session, VideoContentDescription* video) {
+  const ContentInfo* video_info =
+      GetFirstVideoContent(session->local_description());
+  if (video_info == NULL) {
+    LOG(LS_WARNING) << "Cannot send stream update for video.";
+    delete video;
+    return;
+  }
+
+  std::vector<ContentInfo> contents;
+  contents.push_back(
+      ContentInfo(video_info->name, video_info->type, video));
+
+  session->SendDescriptionInfoMessage(contents);
+}
+
+void Call::ContinuePlayDTMF() {
+  playing_dtmf_ = false;
+
+  // Check to see if we have a queued tone
+  if (queued_dtmf_.size() > 0) {
+    playing_dtmf_ = true;
+
+    int tone = queued_dtmf_.front();
+    queued_dtmf_.pop_front();
+
+    LOG(LS_INFO) << "Call::ContinuePlayDTMF(" << tone << ")";
+    for (MediaSessionMap::iterator it = media_session_map_.begin();
+         it != media_session_map_.end(); ++it) {
+      if (it->second.voice_channel != NULL) {
+        it->second.voice_channel->PressDTMF(tone, true);
+      }
+    }
+
+    // Post a message to play the next tone or at least clear the playing_dtmf_
+    // bit.
+    talk_base::Thread::Current()->PostDelayed(kDTMFDelay, this, MSG_PLAYDTMF);
+  }
+}
+
+void Call::Join(Call* call, bool enable) {
+  for (MediaSessionMap::iterator it = call->media_session_map_.begin();
+       it != call->media_session_map_.end(); ++it) {
+    // Shouldn't already exist.
+    ASSERT(media_session_map_.find(it->first) == media_session_map_.end());
+    media_session_map_[it->first] = it->second;
+
+    it->second.session->SignalState.connect(this, &Call::OnSessionState);
+    it->second.session->SignalError.connect(this, &Call::OnSessionError);
+    it->second.session->SignalReceivedTerminateReason
+      .connect(this, &Call::OnReceivedTerminateReason);
+
+    EnableSessionChannels(it->second.session, enable);
+  }
+
+  // Moved all the sessions over, so the other call should no longer have any.
+  call->media_session_map_.clear();
+}
+
+void Call::StartConnectionMonitor(Session* session, int cms) {
+  VoiceChannel* voice_channel = GetVoiceChannel(session);
+  if (voice_channel) {
+    voice_channel->SignalConnectionMonitor.connect(this,
+        &Call::OnConnectionMonitor);
+    voice_channel->StartConnectionMonitor(cms);
+  }
+
+  VideoChannel* video_channel = GetVideoChannel(session);
+  if (video_channel) {
+    video_channel->SignalConnectionMonitor.connect(this,
+        &Call::OnConnectionMonitor);
+    video_channel->StartConnectionMonitor(cms);
+  }
+}
+
+void Call::StopConnectionMonitor(Session* session) {
+  VoiceChannel* voice_channel = GetVoiceChannel(session);
+  if (voice_channel) {
+    voice_channel->StopConnectionMonitor();
+    voice_channel->SignalConnectionMonitor.disconnect(this);
+  }
+
+  VideoChannel* video_channel = GetVideoChannel(session);
+  if (video_channel) {
+    video_channel->StopConnectionMonitor();
+    video_channel->SignalConnectionMonitor.disconnect(this);
+  }
+}
+
+void Call::StartAudioMonitor(Session* session, int cms) {
+  VoiceChannel* voice_channel = GetVoiceChannel(session);
+  if (voice_channel) {
+    voice_channel->SignalAudioMonitor.connect(this, &Call::OnAudioMonitor);
+    voice_channel->StartAudioMonitor(cms);
+  }
+}
+
+void Call::StopAudioMonitor(Session* session) {
+  VoiceChannel* voice_channel = GetVoiceChannel(session);
+  if (voice_channel) {
+    voice_channel->StopAudioMonitor();
+    voice_channel->SignalAudioMonitor.disconnect(this);
+  }
+}
+
+bool Call::IsAudioMonitorRunning(Session* session) {
+  VoiceChannel* voice_channel = GetVoiceChannel(session);
+  if (voice_channel) {
+    return voice_channel->IsAudioMonitorRunning();
+  } else {
+    return false;
+  }
+}
+
+void Call::StartSpeakerMonitor(Session* session) {
+  if (speaker_monitor_map_.find(session->id()) == speaker_monitor_map_.end()) {
+    if (!IsAudioMonitorRunning(session)) {
+      StartAudioMonitor(session, kAudioMonitorPollPeriodMillis);
+    }
+    CurrentSpeakerMonitor* speaker_monitor =
+        new cricket::CurrentSpeakerMonitor(this, session);
+    speaker_monitor->SignalUpdate.connect(this, &Call::OnSpeakerMonitor);
+    speaker_monitor->Start();
+    speaker_monitor_map_[session->id()] = speaker_monitor;
+  } else {
+    LOG(LS_WARNING) << "Already started speaker monitor for session "
+                    << session->id() << ".";
+  }
+}
+
+void Call::StopSpeakerMonitor(Session* session) {
+  if (speaker_monitor_map_.find(session->id()) == speaker_monitor_map_.end()) {
+    LOG(LS_WARNING) << "Speaker monitor for session "
+                    << session->id() << " already stopped.";
+  } else {
+    CurrentSpeakerMonitor* monitor = speaker_monitor_map_[session->id()];
+    monitor->Stop();
+    speaker_monitor_map_.erase(session->id());
+    delete monitor;
+  }
+}
+
+void Call::OnConnectionMonitor(VoiceChannel* channel,
+                               const std::vector<ConnectionInfo> &infos) {
+  SignalConnectionMonitor(this, infos);
+}
+
+void Call::OnMediaMonitor(VoiceChannel* channel, const VoiceMediaInfo& info) {
+  last_voice_media_info_ = info;
+  SignalMediaMonitor(this, info);
+}
+
+void Call::OnAudioMonitor(VoiceChannel* channel, const AudioInfo& info) {
+  SignalAudioMonitor(this, info);
+}
+
+void Call::OnSpeakerMonitor(CurrentSpeakerMonitor* monitor, uint32 ssrc) {
+  Session* session = static_cast<Session*>(monitor->session());
+  MediaStreams* recv_streams = GetMediaStreams(session);
+  if (recv_streams) {
+    StreamParams stream;
+    recv_streams->GetAudioStream(StreamSelector(ssrc), &stream);
+    SignalSpeakerMonitor(this, session, stream);
+  }
+}
+
+void Call::OnConnectionMonitor(VideoChannel* channel,
+                               const std::vector<ConnectionInfo> &infos) {
+  SignalVideoConnectionMonitor(this, infos);
+}
+
+void Call::OnMediaMonitor(VideoChannel* channel, const VideoMediaInfo& info) {
+  SignalVideoMediaMonitor(this, info);
+}
+
+void Call::OnDataReceived(DataChannel* channel,
+                          const ReceiveDataParams& params,
+                          const talk_base::Buffer& payload) {
+  SignalDataReceived(this, params, payload);
+}
+
+uint32 Call::id() {
+  return id_;
+}
+
+void Call::OnSessionState(BaseSession* base_session, BaseSession::State state) {
+  Session* session = static_cast<Session*>(base_session);
+  switch (state) {
+    case Session::STATE_RECEIVEDACCEPT:
+      UpdateRemoteMediaStreams(session,
+          session->remote_description()->contents(), false);
+      session_client_->session_manager()->signaling_thread()->Clear(this,
+          MSG_TERMINATECALL);
+      break;
+    case Session::STATE_RECEIVEDREJECT:
+    case Session::STATE_RECEIVEDTERMINATE:
+      session_client_->session_manager()->signaling_thread()->Clear(this,
+          MSG_TERMINATECALL);
+      break;
+    default:
+      break;
+  }
+  SignalSessionState(this, session, state);
+}
+
+void Call::OnSessionError(BaseSession* base_session, Session::Error error) {
+  session_client_->session_manager()->signaling_thread()->Clear(this,
+      MSG_TERMINATECALL);
+  SignalSessionError(this, static_cast<Session*>(base_session), error);
+}
+
+void Call::OnSessionInfoMessage(Session* session,
+                                const buzz::XmlElement* action_elem) {
+  if (!IsJingleViewRequest(action_elem)) {
+    return;
+  }
+
+  ViewRequest view_request;
+  ParseError error;
+  if (!ParseJingleViewRequest(action_elem, &view_request, &error)) {
+    LOG(LS_WARNING) << "Failed to parse view request: " << error.text;
+    return;
+  }
+
+  VideoChannel* video_channel = GetVideoChannel(session);
+  if (video_channel == NULL) {
+    LOG(LS_WARNING) << "Ignore view request since we have no video channel.";
+    return;
+  }
+
+  if (!video_channel->ApplyViewRequest(view_request)) {
+    LOG(LS_WARNING) << "Failed to ApplyViewRequest.";
+  }
+}
+
+void Call::OnRemoteDescriptionUpdate(BaseSession* base_session,
+                                     const ContentInfos& updated_contents) {
+  Session* session = static_cast<Session*>(base_session);
+
+  const ContentInfo* audio_content = GetFirstAudioContent(updated_contents);
+  if (audio_content) {
+    const AudioContentDescription* audio_update =
+        static_cast<const AudioContentDescription*>(audio_content->description);
+    if (!audio_update->codecs().empty()) {
+      UpdateVoiceChannelRemoteContent(session, audio_update);
+    }
+  }
+
+  const ContentInfo* video_content = GetFirstVideoContent(updated_contents);
+  if (video_content) {
+    const VideoContentDescription* video_update =
+        static_cast<const VideoContentDescription*>(video_content->description);
+    if (!video_update->codecs().empty()) {
+      UpdateVideoChannelRemoteContent(session, video_update);
+    }
+  }
+
+  const ContentInfo* data_content = GetFirstDataContent(updated_contents);
+  if (data_content) {
+    const DataContentDescription* data_update =
+        static_cast<const DataContentDescription*>(data_content->description);
+    if (!data_update->codecs().empty()) {
+      UpdateDataChannelRemoteContent(session, data_update);
+    }
+  }
+
+  UpdateRemoteMediaStreams(session, updated_contents, true);
+}
+
+bool Call::UpdateVoiceChannelRemoteContent(
+    Session* session, const AudioContentDescription* audio) {
+  VoiceChannel* voice_channel = GetVoiceChannel(session);
+  if (!voice_channel->SetRemoteContent(audio, CA_UPDATE)) {
+    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;
+}
+
+bool Call::UpdateDataChannelRemoteContent(
+    Session* session, const DataContentDescription* data) {
+  DataChannel* data_channel = GetDataChannel(session);
+  if (!data_channel->SetRemoteContent(data, CA_UPDATE)) {
+    LOG(LS_ERROR) << "Failure in data SetRemoteContent with CA_UPDATE";
+    session->SetError(BaseSession::ERROR_CONTENT);
+    return false;
+  }
+  return true;
+}
+
+void Call::UpdateRemoteMediaStreams(Session* session,
+                                    const ContentInfos& updated_contents,
+                                    bool update_channels) {
+  MediaStreams* recv_streams = GetMediaStreams(session);
+  if (!recv_streams)
+    return;
+
+  cricket::MediaStreams added_streams;
+  cricket::MediaStreams removed_streams;
+
+  const ContentInfo* audio_content = GetFirstAudioContent(updated_contents);
+  if (audio_content) {
+    const AudioContentDescription* audio_update =
+        static_cast<const AudioContentDescription*>(audio_content->description);
+    UpdateRecvStreams(audio_update->streams(),
+                      update_channels ? GetVoiceChannel(session) : NULL,
+                      recv_streams->mutable_audio(),
+                      added_streams.mutable_audio(),
+                      removed_streams.mutable_audio());
+  }
+
+  const ContentInfo* video_content = GetFirstVideoContent(updated_contents);
+  if (video_content) {
+    const VideoContentDescription* video_update =
+        static_cast<const VideoContentDescription*>(video_content->description);
+    UpdateRecvStreams(video_update->streams(),
+                      update_channels ? GetVideoChannel(session) : NULL,
+                      recv_streams->mutable_video(),
+                      added_streams.mutable_video(),
+                      removed_streams.mutable_video());
+  }
+
+  const ContentInfo* data_content = GetFirstDataContent(updated_contents);
+  if (data_content) {
+    const DataContentDescription* data_update =
+        static_cast<const DataContentDescription*>(data_content->description);
+    UpdateRecvStreams(data_update->streams(),
+                      update_channels ? GetDataChannel(session) : NULL,
+                      recv_streams->mutable_data(),
+                      added_streams.mutable_data(),
+                      removed_streams.mutable_data());
+  }
+
+  if (!added_streams.empty() || !removed_streams.empty()) {
+    SignalMediaStreamsUpdate(this, session, added_streams, removed_streams);
+  }
+}
+
+void FindStreamChanges(const std::vector<StreamParams>& streams,
+                       const std::vector<StreamParams>& updates,
+                       std::vector<StreamParams>* added_streams,
+                       std::vector<StreamParams>* removed_streams) {
+  for (std::vector<StreamParams>::const_iterator update = updates.begin();
+       update != updates.end(); ++update) {
+    StreamParams stream;
+    if (GetStreamByIds(streams, update->groupid, update->id, &stream)) {
+      if (!update->has_ssrcs()) {
+        removed_streams->push_back(stream);
+      }
+    } else {
+      // There's a bug on reflector that will send <stream>s even
+      // though there is not ssrc (which means there isn't really a
+      // stream).  To work around it, we simply ignore new <stream>s
+      // that don't have any ssrcs.
+      if (update->has_ssrcs()) {
+        added_streams->push_back(*update);
+      }
+    }
+  }
+}
+
+void Call::UpdateRecvStreams(const std::vector<StreamParams>& update_streams,
+                             BaseChannel* channel,
+                             std::vector<StreamParams>* recv_streams,
+                             std::vector<StreamParams>* added_streams,
+                             std::vector<StreamParams>* removed_streams) {
+  FindStreamChanges(*recv_streams,
+                    update_streams, added_streams, removed_streams);
+  AddRecvStreams(*added_streams,
+                 channel, recv_streams);
+  RemoveRecvStreams(*removed_streams,
+                    channel, recv_streams);
+}
+
+void Call::AddRecvStreams(const std::vector<StreamParams>& added_streams,
+                          BaseChannel* channel,
+                          std::vector<StreamParams>* recv_streams) {
+  std::vector<StreamParams>::const_iterator stream;
+  for (stream = added_streams.begin();
+       stream != added_streams.end();
+       ++stream) {
+    AddRecvStream(*stream, channel, recv_streams);
+  }
+}
+
+void Call::AddRecvStream(const StreamParams& stream,
+                         BaseChannel* channel,
+                         std::vector<StreamParams>* recv_streams) {
+  if (channel && stream.has_ssrcs()) {
+    channel->AddRecvStream(stream);
+  }
+  recv_streams->push_back(stream);
+}
+
+void Call::RemoveRecvStreams(const std::vector<StreamParams>& removed_streams,
+                             BaseChannel* channel,
+                             std::vector<StreamParams>* recv_streams) {
+  std::vector<StreamParams>::const_iterator stream;
+  for (stream = removed_streams.begin();
+       stream != removed_streams.end();
+       ++stream) {
+    RemoveRecvStream(*stream, channel, recv_streams);
+  }
+}
+
+void Call::RemoveRecvStream(const StreamParams& stream,
+                            BaseChannel* channel,
+                            std::vector<StreamParams>* recv_streams) {
+  if (channel && stream.has_ssrcs()) {
+    // TODO(pthatcher): Change RemoveRecvStream to take a stream argument.
+    channel->RemoveRecvStream(stream.first_ssrc());
+  }
+  RemoveStreamByIds(recv_streams, stream.groupid, stream.id);
+}
+
+void Call::OnReceivedTerminateReason(Session* session,
+                                     const std::string& reason) {
+  session_client_->session_manager()->signaling_thread()->Clear(this,
+    MSG_TERMINATECALL);
+  SignalReceivedTerminateReason(this, session, reason);
+}
+
+}  // namespace cricket
diff --git a/talk/session/media/call.h b/talk/session/media/call.h
new file mode 100644
index 0000000..a604a2b
--- /dev/null
+++ b/talk/session/media/call.h
@@ -0,0 +1,275 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_MEDIA_CALL_H_
+#define TALK_SESSION_MEDIA_CALL_H_
+
+#include <deque>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "talk/base/messagequeue.h"
+#include "talk/media/base/mediachannel.h"
+#include "talk/media/base/screencastid.h"
+#include "talk/media/base/streamparams.h"
+#include "talk/media/base/videocommon.h"
+#include "talk/p2p/base/session.h"
+#include "talk/p2p/client/socketmonitor.h"
+#include "talk/session/media/audiomonitor.h"
+#include "talk/session/media/currentspeakermonitor.h"
+#include "talk/session/media/mediamessages.h"
+#include "talk/session/media/mediasession.h"
+#include "talk/xmpp/jid.h"
+
+namespace cricket {
+
+class MediaSessionClient;
+class BaseChannel;
+class VoiceChannel;
+class VideoChannel;
+class DataChannel;
+
+// Can't typedef this easily since it's forward declared as struct elsewhere.
+struct CallOptions : public MediaSessionOptions {
+};
+
+class Call : public talk_base::MessageHandler, public sigslot::has_slots<> {
+ public:
+  explicit Call(MediaSessionClient* session_client);
+  ~Call();
+
+  // |initiator| can be empty.
+  Session* InitiateSession(const buzz::Jid& to, const buzz::Jid& initiator,
+                           const CallOptions& options);
+  void AcceptSession(Session* session, const CallOptions& options);
+  void RejectSession(Session* session);
+  void TerminateSession(Session* session);
+  void Terminate();
+  bool SendViewRequest(Session* session,
+                       const ViewRequest& view_request);
+  void SetLocalRenderer(VideoRenderer* renderer);
+  void SetVideoRenderer(Session* session, uint32 ssrc,
+                        VideoRenderer* renderer);
+  void StartConnectionMonitor(Session* session, int cms);
+  void StopConnectionMonitor(Session* session);
+  void StartAudioMonitor(Session* session, int cms);
+  void StopAudioMonitor(Session* session);
+  bool IsAudioMonitorRunning(Session* session);
+  void StartSpeakerMonitor(Session* session);
+  void StopSpeakerMonitor(Session* session);
+  void Mute(bool mute);
+  void MuteVideo(bool mute);
+  bool SendData(Session* session,
+                const SendDataParams& params,
+                const talk_base::Buffer& payload,
+                SendDataResult* result);
+  void PressDTMF(int event);
+  bool StartScreencast(Session* session,
+                       const std::string& stream_name, uint32 ssrc,
+                       const ScreencastId& screencastid, int fps);
+  bool StopScreencast(Session* session,
+                      const std::string& stream_name, uint32 ssrc);
+
+  std::vector<Session*> sessions();
+  uint32 id();
+  bool has_video() const { return has_video_; }
+  bool has_data() const { return has_data_; }
+  bool muted() const { return muted_; }
+  bool video_muted() const { return video_muted_; }
+  const std::vector<StreamParams>* GetDataRecvStreams(Session* session) const {
+    MediaStreams* recv_streams = GetMediaStreams(session);
+    return recv_streams ? &recv_streams->data() : NULL;
+  }
+  const std::vector<StreamParams>* GetVideoRecvStreams(Session* session) const {
+    MediaStreams* recv_streams = GetMediaStreams(session);
+    return recv_streams ? &recv_streams->video() : NULL;
+  }
+  const std::vector<StreamParams>* GetAudioRecvStreams(Session* session) const {
+    MediaStreams* recv_streams = GetMediaStreams(session);
+    return recv_streams ? &recv_streams->audio() : NULL;
+  }
+  // Public just for unit tests
+  VideoContentDescription* CreateVideoStreamUpdate(const StreamParams& stream);
+  // Takes ownership of video.
+  void SendVideoStreamUpdate(Session* session, VideoContentDescription* video);
+
+  // Setting this to false will cause the call to have a longer timeout and
+  // for the SignalSetupToCallVoicemail to never fire.
+  void set_send_to_voicemail(bool send_to_voicemail) {
+    send_to_voicemail_ = send_to_voicemail;
+  }
+  bool send_to_voicemail() { return send_to_voicemail_; }
+  const VoiceMediaInfo& last_voice_media_info() const {
+    return last_voice_media_info_;
+  }
+
+  // Sets a flag on the chatapp that will redirect the call to voicemail once
+  // the call has been terminated
+  sigslot::signal0<> SignalSetupToCallVoicemail;
+  sigslot::signal2<Call*, Session*> SignalAddSession;
+  sigslot::signal2<Call*, Session*> SignalRemoveSession;
+  sigslot::signal3<Call*, Session*, Session::State>
+      SignalSessionState;
+  sigslot::signal3<Call*, Session*, Session::Error>
+      SignalSessionError;
+  sigslot::signal3<Call*, Session*, const std::string &>
+      SignalReceivedTerminateReason;
+  sigslot::signal2<Call*, const std::vector<ConnectionInfo> &>
+      SignalConnectionMonitor;
+  sigslot::signal2<Call*, const VoiceMediaInfo&> SignalMediaMonitor;
+  sigslot::signal2<Call*, const AudioInfo&> SignalAudioMonitor;
+  // Empty nick on StreamParams means "unknown".
+  // No ssrcs in StreamParams means "no current speaker".
+  sigslot::signal3<Call*,
+                   Session*,
+                   const StreamParams&> SignalSpeakerMonitor;
+  sigslot::signal2<Call*, const std::vector<ConnectionInfo> &>
+      SignalVideoConnectionMonitor;
+  sigslot::signal2<Call*, const VideoMediaInfo&> SignalVideoMediaMonitor;
+  // Gives added streams and removed streams, in that order.
+  sigslot::signal4<Call*,
+                   Session*,
+                   const MediaStreams&,
+                   const MediaStreams&> SignalMediaStreamsUpdate;
+  sigslot::signal3<Call*,
+                   const ReceiveDataParams&,
+                   const talk_base::Buffer&> SignalDataReceived;
+
+ private:
+  void OnMessage(talk_base::Message* message);
+  void OnSessionState(BaseSession* base_session, BaseSession::State state);
+  void OnSessionError(BaseSession* base_session, Session::Error error);
+  void OnSessionInfoMessage(
+      Session* session, const buzz::XmlElement* action_elem);
+  void OnViewRequest(
+      Session* session, const ViewRequest& view_request);
+  void OnRemoteDescriptionUpdate(
+      BaseSession* base_session, const ContentInfos& updated_contents);
+  void OnReceivedTerminateReason(Session* session, const std::string &reason);
+  void IncomingSession(Session* session, const SessionDescription* offer);
+  // Returns true on success.
+  bool AddSession(Session* session, const SessionDescription* offer);
+  void RemoveSession(Session* session);
+  void EnableChannels(bool enable);
+  void EnableSessionChannels(Session* session, bool enable);
+  void Join(Call* call, bool enable);
+  void OnConnectionMonitor(VoiceChannel* channel,
+                           const std::vector<ConnectionInfo> &infos);
+  void OnMediaMonitor(VoiceChannel* channel, const VoiceMediaInfo& info);
+  void OnAudioMonitor(VoiceChannel* channel, const AudioInfo& info);
+  void OnSpeakerMonitor(CurrentSpeakerMonitor* monitor, uint32 ssrc);
+  void OnConnectionMonitor(VideoChannel* channel,
+                           const std::vector<ConnectionInfo> &infos);
+  void OnMediaMonitor(VideoChannel* channel, const VideoMediaInfo& info);
+  void OnDataReceived(DataChannel* channel,
+                      const ReceiveDataParams& params,
+                      const talk_base::Buffer& payload);
+  VoiceChannel* GetVoiceChannel(Session* session) const;
+  VideoChannel* GetVideoChannel(Session* session) const;
+  DataChannel* GetDataChannel(Session* session) const;
+  MediaStreams* GetMediaStreams(Session* session) const;
+  void UpdateRemoteMediaStreams(Session* session,
+                                const ContentInfos& updated_contents,
+                                bool update_channels);
+  bool UpdateVoiceChannelRemoteContent(Session* session,
+                                       const AudioContentDescription* audio);
+  bool UpdateVideoChannelRemoteContent(Session* session,
+                                       const VideoContentDescription* video);
+  bool UpdateDataChannelRemoteContent(Session* session,
+                                      const DataContentDescription* data);
+  void UpdateRecvStreams(const std::vector<StreamParams>& update_streams,
+                         BaseChannel* channel,
+                         std::vector<StreamParams>* recv_streams,
+                         std::vector<StreamParams>* added_streams,
+                         std::vector<StreamParams>* removed_streams);
+  void AddRecvStreams(const std::vector<StreamParams>& added_streams,
+                      BaseChannel* channel,
+                      std::vector<StreamParams>* recv_streams);
+  void AddRecvStream(const StreamParams& stream,
+                     BaseChannel* channel,
+                     std::vector<StreamParams>* recv_streams);
+  void RemoveRecvStreams(const std::vector<StreamParams>& removed_streams,
+                         BaseChannel* channel,
+                         std::vector<StreamParams>* recv_streams);
+  void RemoveRecvStream(const StreamParams& stream,
+                        BaseChannel* channel,
+                        std::vector<StreamParams>* recv_streams);
+  void ContinuePlayDTMF();
+  bool StopScreencastWithoutSendingUpdate(Session* session, uint32 ssrc);
+  bool StopAllScreencastsWithoutSendingUpdate(Session* session);
+
+  uint32 id_;
+  MediaSessionClient* session_client_;
+
+  struct StartedCapture {
+    StartedCapture(cricket::VideoCapturer* capturer,
+                   const cricket::VideoFormat& format) :
+        capturer(capturer),
+        format(format) {
+    }
+    cricket::VideoCapturer* capturer;
+    cricket::VideoFormat format;
+  };
+  typedef std::map<uint32, StartedCapture> StartedScreencastMap;
+
+  struct MediaSession {
+    Session* session;
+    VoiceChannel* voice_channel;
+    VideoChannel* video_channel;
+    DataChannel* data_channel;
+    MediaStreams* recv_streams;
+    StartedScreencastMap started_screencasts;
+  };
+
+  // Create a map of media sessions, keyed off session->id().
+  typedef std::map<std::string, MediaSession> MediaSessionMap;
+  MediaSessionMap media_session_map_;
+
+  std::map<std::string, CurrentSpeakerMonitor*> speaker_monitor_map_;
+  VideoRenderer* local_renderer_;
+  bool has_video_;
+  bool has_data_;
+  bool muted_;
+  bool video_muted_;
+  bool send_to_voicemail_;
+
+  // DTMF tones have to be queued up so that we don't flood the call.  We
+  // keep a deque (doubely ended queue) of them around.  While one is playing we
+  // set the playing_dtmf_ bit and schedule a message in XX msec to clear that
+  // bit or start the next tone playing.
+  std::deque<int> queued_dtmf_;
+  bool playing_dtmf_;
+
+  VoiceMediaInfo last_voice_media_info_;
+
+  friend class MediaSessionClient;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_MEDIA_CALL_H_
diff --git a/talk/session/media/channel.cc b/talk/session/media/channel.cc
new file mode 100644
index 0000000..1048cdf
--- /dev/null
+++ b/talk/session/media/channel.cc
@@ -0,0 +1,2704 @@
+/*
+ * 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/media/channel.h"
+
+#include "talk/base/buffer.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/media/base/rtputils.h"
+#include "talk/p2p/base/transportchannel.h"
+#include "talk/session/media/channelmanager.h"
+#include "talk/session/media/mediamessages.h"
+#include "talk/session/media/rtcpmuxfilter.h"
+#include "talk/session/media/typingmonitor.h"
+
+
+namespace cricket {
+
+enum {
+  MSG_ENABLE = 1,
+  MSG_DISABLE,
+  MSG_MUTESTREAM,
+  MSG_ISSTREAMMUTED,
+  MSG_SETREMOTECONTENT,
+  MSG_SETLOCALCONTENT,
+  MSG_EARLYMEDIATIMEOUT,
+  MSG_CANINSERTDTMF,
+  MSG_INSERTDTMF,
+  MSG_GETSTATS,
+  MSG_SETRENDERER,
+  MSG_ADDRECVSTREAM,
+  MSG_REMOVERECVSTREAM,
+  MSG_SETRINGBACKTONE,
+  MSG_PLAYRINGBACKTONE,
+  MSG_SETMAXSENDBANDWIDTH,
+  MSG_ADDSCREENCAST,
+  MSG_REMOVESCREENCAST,
+  MSG_SENDINTRAFRAME,
+  MSG_REQUESTINTRAFRAME,
+  MSG_SCREENCASTWINDOWEVENT,
+  MSG_RTPPACKET,
+  MSG_RTCPPACKET,
+  MSG_CHANNEL_ERROR,
+  MSG_SETCHANNELOPTIONS,
+  MSG_SCALEVOLUME,
+  MSG_HANDLEVIEWREQUEST,
+  MSG_READYTOSENDDATA,
+  MSG_SENDDATA,
+  MSG_DATARECEIVED,
+  MSG_SETCAPTURER,
+  MSG_ISSCREENCASTING,
+  MSG_SCREENCASTFPS,
+  MSG_SETSCREENCASTFACTORY,
+  MSG_FIRSTPACKETRECEIVED,
+  MSG_SESSION_ERROR,
+};
+
+// Value specified in RFC 5764.
+static const char kDtlsSrtpExporterLabel[] = "EXTRACTOR-dtls_srtp";
+
+static const int kAgcMinus10db = -10;
+
+// TODO(hellner): use the device manager for creation of screen capturers when
+// the cl enabling it has landed.
+class NullScreenCapturerFactory : public VideoChannel::ScreenCapturerFactory {
+ public:
+  VideoCapturer* CreateScreenCapturer(const ScreencastId& window) {
+    return NULL;
+  }
+};
+
+
+VideoChannel::ScreenCapturerFactory* CreateScreenCapturerFactory() {
+  return new NullScreenCapturerFactory();
+}
+
+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;
+};
+typedef talk_base::TypedMessageData<bool> BoolMessageData;
+struct DtmfMessageData : public talk_base::MessageData {
+  DtmfMessageData(uint32 ssrc, int event, int duration, int flags)
+      : ssrc(ssrc),
+        event(event),
+        duration(duration),
+        flags(flags),
+        result(false) {
+  }
+  uint32 ssrc;
+  int event;
+  int duration;
+  int flags;
+  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 VoiceStatsMessageData : public talk_base::MessageData {
+  explicit VoiceStatsMessageData(VoiceMediaInfo* stats)
+      : result(false),
+        stats(stats) {
+  }
+  bool result;
+  VoiceMediaInfo* stats;
+};
+
+struct VideoStatsMessageData : public talk_base::MessageData {
+  explicit VideoStatsMessageData(VideoMediaInfo* stats)
+      : result(false),
+        stats(stats) {
+  }
+  bool result;
+  VideoMediaInfo* stats;
+};
+
+struct PacketMessageData : public talk_base::MessageData {
+  talk_base::Buffer packet;
+};
+
+struct AudioRenderMessageData: public talk_base::MessageData {
+  AudioRenderMessageData(uint32 s, AudioRenderer* r)
+      : ssrc(s), renderer(r), result(false) {}
+  uint32 ssrc;
+  AudioRenderer* renderer;
+  bool result;
+};
+
+struct VideoRenderMessageData : public talk_base::MessageData {
+  VideoRenderMessageData(uint32 s, VideoRenderer* r) : ssrc(s), renderer(r) {}
+  uint32 ssrc;
+  VideoRenderer* renderer;
+};
+
+struct AddScreencastMessageData : public talk_base::MessageData {
+  AddScreencastMessageData(uint32 s, const ScreencastId& id)
+      : ssrc(s),
+        window_id(id),
+        result(NULL) {
+  }
+  uint32 ssrc;
+  ScreencastId window_id;
+  VideoCapturer* result;
+};
+
+struct RemoveScreencastMessageData : public talk_base::MessageData {
+  explicit RemoveScreencastMessageData(uint32 s) : ssrc(s), result(false) {}
+  uint32 ssrc;
+  bool result;
+};
+
+struct ScreencastEventMessageData : public talk_base::MessageData {
+  ScreencastEventMessageData(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)
+      : ssrc(in_ssrc),
+        error(in_error) {
+  }
+  uint32 ssrc;
+  VoiceMediaChannel::Error error;
+};
+
+struct VideoChannelErrorMessageData : public talk_base::MessageData {
+  VideoChannelErrorMessageData(uint32 in_ssrc,
+                               VideoMediaChannel::Error in_error)
+      : ssrc(in_ssrc),
+        error(in_error) {
+  }
+  uint32 ssrc;
+  VideoMediaChannel::Error error;
+};
+
+struct DataChannelErrorMessageData : public talk_base::MessageData {
+  DataChannelErrorMessageData(uint32 in_ssrc,
+                              DataMediaChannel::Error in_error)
+      : ssrc(in_ssrc),
+        error(in_error) {}
+  uint32 ssrc;
+  DataMediaChannel::Error error;
+};
+
+struct SessionErrorMessageData : public talk_base::MessageData {
+  explicit SessionErrorMessageData(cricket::BaseSession::Error error)
+      : error_(error) {}
+
+  BaseSession::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& in_sp)
+      : sp(in_sp),
+        result(false) {
+  }
+  StreamParams sp;
+  bool result;
+};
+
+struct MuteStreamData : public talk_base::MessageData {
+  MuteStreamData(uint32 ssrc, bool mute)
+      : ssrc(ssrc), mute(mute), result(false) {}
+  uint32 ssrc;
+  bool mute;
+  bool result;
+};
+
+struct AudioOptionsMessageData : public talk_base::MessageData {
+  explicit AudioOptionsMessageData(const AudioOptions& options)
+      : options(options),
+        result(false) {
+  }
+  AudioOptions options;
+  bool result;
+};
+
+struct VideoOptionsMessageData : public talk_base::MessageData {
+  explicit VideoOptionsMessageData(const VideoOptions& options)
+      : options(options),
+        result(false) {
+  }
+  VideoOptions options;
+  bool result;
+};
+
+struct SetCapturerMessageData : public talk_base::MessageData {
+  SetCapturerMessageData(uint32 s, VideoCapturer* c)
+      : ssrc(s),
+        capturer(c),
+        result(false) {
+  }
+  uint32 ssrc;
+  VideoCapturer* capturer;
+  bool result;
+};
+
+struct IsScreencastingMessageData : public talk_base::MessageData {
+  IsScreencastingMessageData()
+      : result(false) {
+  }
+  bool result;
+};
+
+struct ScreencastFpsMessageData : public talk_base::MessageData {
+  explicit ScreencastFpsMessageData(uint32 s)
+      : ssrc(s), result(0) {
+  }
+  uint32 ssrc;
+  int result;
+};
+
+struct SetScreenCaptureFactoryMessageData : public talk_base::MessageData {
+  explicit SetScreenCaptureFactoryMessageData(
+      VideoChannel::ScreenCapturerFactory* f)
+      : screencapture_factory(f) {
+  }
+  VideoChannel::ScreenCapturerFactory* screencapture_factory;
+};
+
+static const char* PacketType(bool rtcp) {
+  return (!rtcp) ? "RTP" : "RTCP";
+}
+
+static bool ValidPacket(bool rtcp, const talk_base::Buffer* packet) {
+  // Check the packet size. We could check the header too if needed.
+  return (packet &&
+      packet->length() >= (!rtcp ? kMinRtpPacketLen : kMinRtcpPacketLen) &&
+      packet->length() <= kMaxRtpPacketLen);
+}
+
+static bool IsReceiveContentDirection(MediaContentDirection direction) {
+  return direction == MD_SENDRECV || direction == MD_RECVONLY;
+}
+
+static bool IsSendContentDirection(MediaContentDirection direction) {
+  return direction == MD_SENDRECV || direction == MD_SENDONLY;
+}
+
+static const MediaContentDescription* GetContentDescription(
+    const ContentInfo* cinfo) {
+  if (cinfo == NULL)
+    return NULL;
+  return static_cast<const MediaContentDescription*>(cinfo->description);
+}
+
+BaseChannel::BaseChannel(talk_base::Thread* thread,
+                         MediaEngineInterface* media_engine,
+                         MediaChannel* media_channel, BaseSession* session,
+                         const std::string& content_name, bool rtcp)
+    : worker_thread_(thread),
+      media_engine_(media_engine),
+      session_(session),
+      media_channel_(media_channel),
+      content_name_(content_name),
+      rtcp_(rtcp),
+      transport_channel_(NULL),
+      rtcp_transport_channel_(NULL),
+      enabled_(false),
+      writable_(false),
+      rtp_ready_to_send_(false),
+      rtcp_ready_to_send_(false),
+      optimistic_data_send_(false),
+      was_ever_writable_(false),
+      local_content_direction_(MD_INACTIVE),
+      remote_content_direction_(MD_INACTIVE),
+      has_received_packet_(false),
+      dtls_keyed_(false),
+      secure_required_(false) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  LOG(LS_INFO) << "Created channel for " << content_name;
+}
+
+BaseChannel::~BaseChannel() {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  StopConnectionMonitor();
+  FlushRtcpMessages();  // Send any outstanding RTCP packets.
+  Clear();  // eats any outstanding messages or packets
+  // We must destroy the media channel before the transport channel, otherwise
+  // the media channel may try to send on the dead transport channel. NULLing
+  // is not an effective strategy since the sends will come on another thread.
+  delete media_channel_;
+  set_rtcp_transport_channel(NULL);
+  if (transport_channel_ != NULL)
+    session_->DestroyChannel(content_name_, transport_channel_->component());
+  LOG(LS_INFO) << "Destroyed channel";
+}
+
+bool BaseChannel::Init(TransportChannel* transport_channel,
+                       TransportChannel* rtcp_transport_channel) {
+  if (transport_channel == NULL) {
+    return false;
+  }
+  if (rtcp() && rtcp_transport_channel == NULL) {
+    return false;
+  }
+  transport_channel_ = transport_channel;
+
+  if (!SetDtlsSrtpCiphers(transport_channel_, false)) {
+    return false;
+  }
+
+  media_channel_->SetInterface(this);
+  transport_channel_->SignalWritableState.connect(
+      this, &BaseChannel::OnWritableState);
+  transport_channel_->SignalReadPacket.connect(
+      this, &BaseChannel::OnChannelRead);
+  transport_channel_->SignalReadyToSend.connect(
+      this, &BaseChannel::OnReadyToSend);
+
+  session_->SignalNewLocalDescription.connect(
+      this, &BaseChannel::OnNewLocalDescription);
+  session_->SignalNewRemoteDescription.connect(
+      this, &BaseChannel::OnNewRemoteDescription);
+
+  set_rtcp_transport_channel(rtcp_transport_channel);
+  return true;
+}
+
+// Can be called from thread other than worker thread
+bool BaseChannel::Enable(bool enable) {
+  Send(enable ? MSG_ENABLE : MSG_DISABLE);
+  return true;
+}
+
+// Can be called from thread other than worker thread
+bool BaseChannel::MuteStream(uint32 ssrc, bool mute) {
+  MuteStreamData data(ssrc, mute);
+  Send(MSG_MUTESTREAM, &data);
+  return data.result;
+}
+
+bool BaseChannel::IsStreamMuted(uint32 ssrc) {
+  SsrcMessageData data(ssrc);
+  Send(MSG_ISSTREAMMUTED, &data);
+  return data.result;
+}
+
+bool BaseChannel::AddRecvStream(const StreamParams& sp) {
+  StreamMessageData data(sp);
+  Send(MSG_ADDRECVSTREAM, &data);
+  return data.result;
+}
+
+bool BaseChannel::RemoveRecvStream(uint32 ssrc) {
+  SsrcMessageData data(ssrc);
+  Send(MSG_REMOVERECVSTREAM, &data);
+  return data.result;
+}
+
+bool BaseChannel::SetLocalContent(const MediaContentDescription* content,
+                                  ContentAction action) {
+  SetContentData data(content, action);
+  Send(MSG_SETLOCALCONTENT, &data);
+  return data.result;
+}
+
+bool BaseChannel::SetRemoteContent(const MediaContentDescription* content,
+                                   ContentAction action) {
+  SetContentData data(content, action);
+  Send(MSG_SETREMOTECONTENT, &data);
+  return data.result;
+}
+
+bool BaseChannel::SetMaxSendBandwidth(int max_bandwidth) {
+  SetBandwidthData data(max_bandwidth);
+  Send(MSG_SETMAXSENDBANDWIDTH, &data);
+  return data.result;
+}
+
+void BaseChannel::StartConnectionMonitor(int cms) {
+  socket_monitor_.reset(new SocketMonitor(transport_channel_,
+                                          worker_thread(),
+                                          talk_base::Thread::Current()));
+  socket_monitor_->SignalUpdate.connect(
+      this, &BaseChannel::OnConnectionMonitorUpdate);
+  socket_monitor_->Start(cms);
+}
+
+void BaseChannel::StopConnectionMonitor() {
+  if (socket_monitor_) {
+    socket_monitor_->Stop();
+    socket_monitor_.reset();
+  }
+}
+
+void BaseChannel::set_rtcp_transport_channel(TransportChannel* channel) {
+  if (rtcp_transport_channel_ != channel) {
+    if (rtcp_transport_channel_) {
+      session_->DestroyChannel(
+          content_name_, rtcp_transport_channel_->component());
+    }
+    rtcp_transport_channel_ = channel;
+    if (rtcp_transport_channel_) {
+      // TODO(juberti): Propagate this error code
+      VERIFY(SetDtlsSrtpCiphers(rtcp_transport_channel_, true));
+      rtcp_transport_channel_->SignalWritableState.connect(
+          this, &BaseChannel::OnWritableState);
+      rtcp_transport_channel_->SignalReadPacket.connect(
+          this, &BaseChannel::OnChannelRead);
+      rtcp_transport_channel_->SignalReadyToSend.connect(
+          this, &BaseChannel::OnReadyToSend);
+    }
+  }
+}
+
+bool BaseChannel::IsReadyToReceive() const {
+  // Receive data if we are enabled and have local content,
+  return enabled() && IsReceiveContentDirection(local_content_direction_);
+}
+
+bool BaseChannel::IsReadyToSend() const {
+  // Send outgoing data if we are enabled, have local and remote content,
+  // and we have had some form of connectivity.
+  return enabled() &&
+         IsReceiveContentDirection(remote_content_direction_) &&
+         IsSendContentDirection(local_content_direction_) &&
+         was_ever_writable();
+}
+
+bool BaseChannel::SendPacket(talk_base::Buffer* packet) {
+  return SendPacket(false, packet);
+}
+
+bool BaseChannel::SendRtcp(talk_base::Buffer* packet) {
+  return SendPacket(true, packet);
+}
+
+int BaseChannel::SetOption(SocketType type, talk_base::Socket::Option opt,
+                           int value) {
+  switch (type) {
+    case ST_RTP: return transport_channel_->SetOption(opt, value);
+    case ST_RTCP: return rtcp_transport_channel_->SetOption(opt, value);
+    default: return -1;
+  }
+}
+
+void BaseChannel::OnWritableState(TransportChannel* channel) {
+  ASSERT(channel == transport_channel_ || channel == rtcp_transport_channel_);
+  if (transport_channel_->writable()
+      && (!rtcp_transport_channel_ || rtcp_transport_channel_->writable())) {
+    ChannelWritable_w();
+  } else {
+    ChannelNotWritable_w();
+  }
+}
+
+void BaseChannel::OnChannelRead(TransportChannel* channel,
+                                const char* data, size_t len, int flags) {
+  // OnChannelRead gets called from P2PSocket; now pass data to MediaEngine
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  // When using RTCP multiplexing we might get RTCP packets on the RTP
+  // transport. We feed RTP traffic into the demuxer to determine if it is RTCP.
+  bool rtcp = PacketIsRtcp(channel, data, len);
+  talk_base::Buffer packet(data, len);
+  HandlePacket(rtcp, &packet);
+}
+
+void BaseChannel::OnReadyToSend(TransportChannel* channel) {
+  SetReadyToSend(channel, true);
+}
+
+void BaseChannel::SetReadyToSend(TransportChannel* channel, bool ready) {
+  ASSERT(channel == transport_channel_ || channel == rtcp_transport_channel_);
+  if (channel == transport_channel_) {
+    rtp_ready_to_send_ = ready;
+  }
+  if (channel == rtcp_transport_channel_) {
+    rtcp_ready_to_send_ = ready;
+  }
+
+  if (!ready) {
+    // Notify the MediaChannel when either rtp or rtcp channel can't send.
+    media_channel_->OnReadyToSend(false);
+  } else if (rtp_ready_to_send_ &&
+             // In the case of rtcp mux |rtcp_transport_channel_| will be null.
+             (rtcp_ready_to_send_ || !rtcp_transport_channel_)) {
+    // Notify the MediaChannel when both rtp and rtcp channel can send.
+    media_channel_->OnReadyToSend(true);
+  }
+}
+
+bool BaseChannel::PacketIsRtcp(const TransportChannel* channel,
+                               const char* data, size_t len) {
+  return (channel == rtcp_transport_channel_ ||
+          rtcp_mux_filter_.DemuxRtcp(data, len));
+}
+
+bool BaseChannel::SendPacket(bool rtcp, talk_base::Buffer* packet) {
+  // Unless we're sending optimistically, we only allow packets through when we
+  // are completely writable.
+  if (!optimistic_data_send_ && !writable_) {
+    return false;
+  }
+
+  // SendPacket gets called from MediaEngine, typically on an encoder thread.
+  // If the thread is not our worker thread, we will post to our worker
+  // so that the real work happens on our worker. This avoids us having to
+  // synchronize access to all the pieces of the send path, including
+  // SRTP and the inner workings of the transport channels.
+  // The only downside is that we can't return a proper failure code if
+  // needed. Since UDP is unreliable anyway, this should be a non-issue.
+  if (talk_base::Thread::Current() != worker_thread_) {
+    // Avoid a copy by transferring the ownership of the packet data.
+    int message_id = (!rtcp) ? MSG_RTPPACKET : MSG_RTCPPACKET;
+    PacketMessageData* data = new PacketMessageData;
+    packet->TransferTo(&data->packet);
+    worker_thread_->Post(this, message_id, data);
+    return true;
+  }
+
+  // Now that we are on the correct thread, ensure we have a place to send this
+  // packet before doing anything. (We might get RTCP packets that we don't
+  // intend to send.) If we've negotiated RTCP mux, send RTCP over the RTP
+  // transport.
+  TransportChannel* channel = (!rtcp || rtcp_mux_filter_.IsActive()) ?
+      transport_channel_ : rtcp_transport_channel_;
+  if (!channel || (!optimistic_data_send_ && !channel->writable())) {
+    return false;
+  }
+
+  // Protect ourselves against crazy data.
+  if (!ValidPacket(rtcp, packet)) {
+    LOG(LS_ERROR) << "Dropping outgoing " << content_name_ << " "
+                  << PacketType(rtcp) << " packet: wrong size="
+                  << packet->length();
+    return false;
+  }
+
+  // Signal to the media sink before protecting the packet.
+  {
+    talk_base::CritScope cs(&signal_send_packet_cs_);
+    SignalSendPacketPreCrypto(packet->data(), packet->length(), rtcp);
+  }
+
+  // Protect if needed.
+  if (srtp_filter_.IsActive()) {
+    bool res;
+    char* data = packet->data();
+    int len = packet->length();
+    if (!rtcp) {
+      res = srtp_filter_.ProtectRtp(data, len, packet->capacity(), &len);
+      if (!res) {
+        int seq_num = -1;
+        uint32 ssrc = 0;
+        GetRtpSeqNum(data, len, &seq_num);
+        GetRtpSsrc(data, len, &ssrc);
+        LOG(LS_ERROR) << "Failed to protect " << content_name_
+                      << " RTP packet: size=" << len
+                      << ", seqnum=" << seq_num << ", SSRC=" << ssrc;
+        return false;
+      }
+    } else {
+      res = srtp_filter_.ProtectRtcp(data, len, packet->capacity(), &len);
+      if (!res) {
+        int type = -1;
+        GetRtcpType(data, len, &type);
+        LOG(LS_ERROR) << "Failed to protect " << content_name_
+                      << " RTCP packet: size=" << len << ", type=" << type;
+        return false;
+      }
+    }
+
+    // Update the length of the packet now that we've added the auth tag.
+    packet->SetLength(len);
+  } else if (secure_required_) {
+    // This is a double check for something that supposedly can't happen.
+    LOG(LS_ERROR) << "Can't send outgoing " << PacketType(rtcp)
+                  << " packet when SRTP is inactive and crypto is required";
+
+    ASSERT(false);
+    return false;
+  }
+
+  // Signal to the media sink after protecting the packet.
+  {
+    talk_base::CritScope cs(&signal_send_packet_cs_);
+    SignalSendPacketPostCrypto(packet->data(), packet->length(), rtcp);
+  }
+
+  // Bon voyage.
+  int ret = channel->SendPacket(packet->data(), packet->length(),
+      (secure() && secure_dtls()) ? PF_SRTP_BYPASS : 0);
+  if (ret != static_cast<int>(packet->length())) {
+    if (channel->GetError() == EWOULDBLOCK) {
+      LOG(LS_WARNING) << "Got EWOULDBLOCK from socket.";
+      SetReadyToSend(channel, false);
+    }
+    return false;
+  }
+  return true;
+}
+
+bool BaseChannel::WantsPacket(bool rtcp, talk_base::Buffer* packet) {
+  // Protect ourselves against crazy data.
+  if (!ValidPacket(rtcp, packet)) {
+    LOG(LS_ERROR) << "Dropping incoming " << content_name_ << " "
+                  << PacketType(rtcp) << " packet: wrong size="
+                  << packet->length();
+    return false;
+  }
+  // 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 false;
+  }
+
+  return true;
+}
+
+void BaseChannel::HandlePacket(bool rtcp, talk_base::Buffer* packet) {
+  if (!WantsPacket(rtcp, packet)) {
+    return;
+  }
+
+  if (!has_received_packet_) {
+    has_received_packet_ = true;
+    signaling_thread()->Post(this, MSG_FIRSTPACKETRECEIVED);
+  }
+
+  // Signal to the media sink before unprotecting the packet.
+  {
+    talk_base::CritScope cs(&signal_recv_packet_cs_);
+    SignalRecvPacketPostCrypto(packet->data(), packet->length(), rtcp);
+  }
+
+  // Unprotect the packet, if needed.
+  if (srtp_filter_.IsActive()) {
+    char* data = packet->data();
+    int len = packet->length();
+    bool res;
+    if (!rtcp) {
+      res = srtp_filter_.UnprotectRtp(data, len, &len);
+      if (!res) {
+        int seq_num = -1;
+        uint32 ssrc = 0;
+        GetRtpSeqNum(data, len, &seq_num);
+        GetRtpSsrc(data, len, &ssrc);
+        LOG(LS_ERROR) << "Failed to unprotect " << content_name_
+                      << " RTP packet: size=" << len
+                      << ", seqnum=" << seq_num << ", SSRC=" << ssrc;
+        return;
+      }
+    } else {
+      res = srtp_filter_.UnprotectRtcp(data, len, &len);
+      if (!res) {
+        int type = -1;
+        GetRtcpType(data, len, &type);
+        LOG(LS_ERROR) << "Failed to unprotect " << content_name_
+                      << " RTCP packet: size=" << len << ", type=" << type;
+        return;
+      }
+    }
+
+    packet->SetLength(len);
+  } else if (secure_required_) {
+    // Our session description indicates that SRTP is required, but we got a
+    // packet before our SRTP filter is active. This means either that
+    // a) we got SRTP packets before we received the SDES keys, in which case
+    //    we can't decrypt it anyway, or
+    // b) we got SRTP packets before DTLS completed on both the RTP and RTCP
+    //    channels, so we haven't yet extracted keys, even if DTLS did complete
+    //    on the channel that the packets are being sent on. It's really good
+    //    practice to wait for both RTP and RTCP to be good to go before sending
+    //    media, to prevent weird failure modes, so it's fine for us to just eat
+    //    packets here. This is all sidestepped if RTCP mux is used anyway.
+    LOG(LS_WARNING) << "Can't process incoming " << PacketType(rtcp)
+                    << " packet when SRTP is inactive and crypto is required";
+    return;
+  }
+
+  // Signal to the media sink after unprotecting the packet.
+  {
+    talk_base::CritScope cs(&signal_recv_packet_cs_);
+    SignalRecvPacketPreCrypto(packet->data(), packet->length(), rtcp);
+  }
+
+  // Push it down to the media channel.
+  if (!rtcp) {
+    media_channel_->OnPacketReceived(packet);
+  } else {
+    media_channel_->OnRtcpReceived(packet);
+  }
+}
+
+void BaseChannel::OnNewLocalDescription(
+    BaseSession* session, ContentAction action) {
+  const ContentInfo* content_info =
+      GetFirstContent(session->local_description());
+  const MediaContentDescription* content_desc =
+      GetContentDescription(content_info);
+  if (content_desc && content_info && !content_info->rejected &&
+      !SetLocalContent(content_desc, action)) {
+    LOG(LS_ERROR) << "Failure in SetLocalContent with action " << action;
+    session->SetError(BaseSession::ERROR_CONTENT);
+  }
+}
+
+void BaseChannel::OnNewRemoteDescription(
+    BaseSession* session, ContentAction action) {
+  const ContentInfo* content_info =
+      GetFirstContent(session->remote_description());
+  const MediaContentDescription* content_desc =
+      GetContentDescription(content_info);
+  if (content_desc && content_info && !content_info->rejected &&
+      !SetRemoteContent(content_desc, action)) {
+    LOG(LS_ERROR) << "Failure in SetRemoteContent with  action " << action;
+    session->SetError(BaseSession::ERROR_CONTENT);
+  }
+}
+
+void BaseChannel::EnableMedia_w() {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  if (enabled_)
+    return;
+
+  LOG(LS_INFO) << "Channel enabled";
+  enabled_ = true;
+  ChangeState();
+}
+
+void BaseChannel::DisableMedia_w() {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  if (!enabled_)
+    return;
+
+  LOG(LS_INFO) << "Channel disabled";
+  enabled_ = false;
+  ChangeState();
+}
+
+bool BaseChannel::MuteStream_w(uint32 ssrc, bool mute) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  bool ret = media_channel()->MuteStream(ssrc, mute);
+  if (ret) {
+    if (mute)
+      muted_streams_.insert(ssrc);
+    else
+      muted_streams_.erase(ssrc);
+  }
+  return ret;
+}
+
+bool BaseChannel::IsStreamMuted_w(uint32 ssrc) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  return muted_streams_.find(ssrc) != muted_streams_.end();
+}
+
+void BaseChannel::ChannelWritable_w() {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  if (writable_)
+    return;
+
+  LOG(LS_INFO) << "Channel socket writable ("
+               << transport_channel_->content_name() << ", "
+               << transport_channel_->component() << ")"
+               << (was_ever_writable_ ? "" : " for the first time");
+
+  std::vector<ConnectionInfo> infos;
+  transport_channel_->GetStats(&infos);
+  for (std::vector<ConnectionInfo>::const_iterator it = infos.begin();
+       it != infos.end(); ++it) {
+    if (it->best_connection) {
+      LOG(LS_INFO) << "Using " << it->local_candidate.ToSensitiveString()
+                   << "->" << it->remote_candidate.ToSensitiveString();
+      break;
+    }
+  }
+
+  // If we're doing DTLS-SRTP, now is the time.
+  if (!was_ever_writable_ && ShouldSetupDtlsSrtp()) {
+    if (!SetupDtlsSrtp(false)) {
+      LOG(LS_ERROR) << "Couldn't finish DTLS-SRTP on RTP channel";
+      SessionErrorMessageData data(BaseSession::ERROR_TRANSPORT);
+      // Sent synchronously.
+      signaling_thread()->Send(this, MSG_SESSION_ERROR, &data);
+      return;
+    }
+
+    if (rtcp_transport_channel_) {
+      if (!SetupDtlsSrtp(true)) {
+        LOG(LS_ERROR) << "Couldn't finish DTLS-SRTP on RTCP channel";
+        SessionErrorMessageData data(BaseSession::ERROR_TRANSPORT);
+        // Sent synchronously.
+        signaling_thread()->Send(this, MSG_SESSION_ERROR, &data);
+        return;
+      }
+    }
+  }
+
+  was_ever_writable_ = true;
+  writable_ = true;
+  ChangeState();
+}
+
+bool BaseChannel::SetDtlsSrtpCiphers(TransportChannel *tc, bool rtcp) {
+  std::vector<std::string> ciphers;
+  // We always use the default SRTP ciphers for RTCP, but we may use different
+  // ciphers for RTP depending on the media type.
+  if (!rtcp) {
+    GetSrtpCiphers(&ciphers);
+  } else {
+    GetSupportedDefaultCryptoSuites(&ciphers);
+  }
+  return tc->SetSrtpCiphers(ciphers);
+}
+
+bool BaseChannel::ShouldSetupDtlsSrtp() const {
+  return true;
+}
+
+// This function returns true if either DTLS-SRTP is not in use
+// *or* DTLS-SRTP is successfully set up.
+bool BaseChannel::SetupDtlsSrtp(bool rtcp_channel) {
+  bool ret = false;
+
+  TransportChannel *channel = rtcp_channel ?
+      rtcp_transport_channel_ : transport_channel_;
+
+  // No DTLS
+  if (!channel->IsDtlsActive())
+    return true;
+
+  std::string selected_cipher;
+
+  if (!channel->GetSrtpCipher(&selected_cipher)) {
+    LOG(LS_ERROR) << "No DTLS-SRTP selected cipher";
+    return false;
+  }
+
+  LOG(LS_INFO) << "Installing keys from DTLS-SRTP on "
+               << content_name() << " "
+               << PacketType(rtcp_channel);
+
+  // OK, we're now doing DTLS (RFC 5764)
+  std::vector<unsigned char> dtls_buffer(SRTP_MASTER_KEY_KEY_LEN * 2 +
+                                         SRTP_MASTER_KEY_SALT_LEN * 2);
+
+  // RFC 5705 exporter using the RFC 5764 parameters
+  if (!channel->ExportKeyingMaterial(
+          kDtlsSrtpExporterLabel,
+          NULL, 0, false,
+          &dtls_buffer[0], dtls_buffer.size())) {
+    LOG(LS_WARNING) << "DTLS-SRTP key export failed";
+    ASSERT(false);  // This should never happen
+    return false;
+  }
+
+  // Sync up the keys with the DTLS-SRTP interface
+  std::vector<unsigned char> client_write_key(SRTP_MASTER_KEY_KEY_LEN +
+    SRTP_MASTER_KEY_SALT_LEN);
+  std::vector<unsigned char> server_write_key(SRTP_MASTER_KEY_KEY_LEN +
+    SRTP_MASTER_KEY_SALT_LEN);
+  size_t offset = 0;
+  memcpy(&client_write_key[0], &dtls_buffer[offset],
+    SRTP_MASTER_KEY_KEY_LEN);
+  offset += SRTP_MASTER_KEY_KEY_LEN;
+  memcpy(&server_write_key[0], &dtls_buffer[offset],
+    SRTP_MASTER_KEY_KEY_LEN);
+  offset += SRTP_MASTER_KEY_KEY_LEN;
+  memcpy(&client_write_key[SRTP_MASTER_KEY_KEY_LEN],
+    &dtls_buffer[offset], SRTP_MASTER_KEY_SALT_LEN);
+  offset += SRTP_MASTER_KEY_SALT_LEN;
+  memcpy(&server_write_key[SRTP_MASTER_KEY_KEY_LEN],
+    &dtls_buffer[offset], SRTP_MASTER_KEY_SALT_LEN);
+
+  std::vector<unsigned char> *send_key, *recv_key;
+
+  if (channel->GetRole() == ROLE_CONTROLLING) {
+    send_key = &server_write_key;
+    recv_key = &client_write_key;
+  } else {
+    send_key = &client_write_key;
+    recv_key = &server_write_key;
+  }
+
+  if (rtcp_channel) {
+    ret = srtp_filter_.SetRtcpParams(selected_cipher,
+      &(*send_key)[0], send_key->size(),
+      selected_cipher,
+      &(*recv_key)[0], recv_key->size());
+  } else {
+    ret = srtp_filter_.SetRtpParams(selected_cipher,
+      &(*send_key)[0], send_key->size(),
+      selected_cipher,
+      &(*recv_key)[0], recv_key->size());
+  }
+
+  if (!ret)
+    LOG(LS_WARNING) << "DTLS-SRTP key installation failed";
+  else
+    dtls_keyed_ = true;
+
+  return ret;
+}
+
+void BaseChannel::ChannelNotWritable_w() {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  if (!writable_)
+    return;
+
+  LOG(LS_INFO) << "Channel socket not writable ("
+               << transport_channel_->content_name() << ", "
+               << transport_channel_->component() << ")";
+  writable_ = false;
+  ChangeState();
+}
+
+// Sets the maximum video bandwidth for automatic bandwidth adjustment.
+bool BaseChannel::SetMaxSendBandwidth_w(int max_bandwidth) {
+  return media_channel()->SetSendBandwidth(true, max_bandwidth);
+}
+
+bool BaseChannel::SetSrtp_w(const std::vector<CryptoParams>& cryptos,
+                            ContentAction action, ContentSource src) {
+  bool ret = false;
+  switch (action) {
+    case CA_OFFER:
+      ret = srtp_filter_.SetOffer(cryptos, src);
+      break;
+    case CA_PRANSWER:
+      // If we're doing DTLS-SRTP, we don't want to update the filter
+      // with an answer, because we already have SRTP parameters.
+      if (transport_channel_->IsDtlsActive()) {
+        LOG(LS_INFO) <<
+          "Ignoring SDES answer parameters because we are using DTLS-SRTP";
+        ret = true;
+      } else {
+        ret = srtp_filter_.SetProvisionalAnswer(cryptos, src);
+      }
+      break;
+    case CA_ANSWER:
+      // If we're doing DTLS-SRTP, we don't want to update the filter
+      // with an answer, because we already have SRTP parameters.
+      if (transport_channel_->IsDtlsActive()) {
+        LOG(LS_INFO) <<
+          "Ignoring SDES answer parameters because we are using DTLS-SRTP";
+        ret = true;
+      } else {
+        ret = srtp_filter_.SetAnswer(cryptos, src);
+      }
+      break;
+    case CA_UPDATE:
+      // no crypto params.
+      ret = true;
+      break;
+    default:
+      break;
+  }
+  return ret;
+}
+
+bool BaseChannel::SetRtcpMux_w(bool enable, ContentAction action,
+                               ContentSource src) {
+  bool ret = false;
+  switch (action) {
+    case CA_OFFER:
+      ret = rtcp_mux_filter_.SetOffer(enable, src);
+      break;
+    case CA_PRANSWER:
+      ret = rtcp_mux_filter_.SetProvisionalAnswer(enable, src);
+      break;
+    case CA_ANSWER:
+      ret = rtcp_mux_filter_.SetAnswer(enable, src);
+      if (ret && rtcp_mux_filter_.IsActive()) {
+        // We activated RTCP mux, close down the RTCP transport.
+        set_rtcp_transport_channel(NULL);
+      }
+      break;
+    case CA_UPDATE:
+      // No RTCP mux info.
+      ret = true;
+    default:
+      break;
+  }
+  // |rtcp_mux_filter_| can be active if |action| is CA_PRANSWER or
+  // CA_ANSWER, but we only want to tear down the RTCP transport channel if we
+  // received a final answer.
+  if (ret && rtcp_mux_filter_.IsActive()) {
+    // If the RTP transport is already writable, then so are we.
+    if (transport_channel_->writable()) {
+      ChannelWritable_w();
+    }
+  }
+
+  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_PRANSWER || 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 = GetStreamByIds(local_streams_, it->groupid,
+                                         it->id, &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 (!VERIFY(action == CA_OFFER || action == CA_ANSWER ||
+              action == CA_PRANSWER || 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_exists = GetStreamByIds(remote_streams_, it->groupid,
+                                          it->id, &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."
+                        << " Stream exists? " << stream_exists
+                        << " existing stream = " << existing_stream.ToString()
+                        << " new stream = " << it->ToString();
+      }
+    }
+    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) {
+  // Cache secure_required_ for belt and suspenders check on SendPacket
+  secure_required_ = content->crypto_required();
+  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());
+  }
+  set_local_content_direction(content->direction());
+  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());
+  }
+  if (content->bandwidth() != kAutoBandwidth) {
+    ret &= media_channel()->SetSendBandwidth(false, content->bandwidth());
+  }
+  set_remote_content_direction(content->direction());
+  return ret;
+}
+
+void BaseChannel::OnMessage(talk_base::Message *pmsg) {
+  switch (pmsg->message_id) {
+    case MSG_ENABLE:
+      EnableMedia_w();
+      break;
+    case MSG_DISABLE:
+      DisableMedia_w();
+      break;
+    case MSG_MUTESTREAM: {
+      MuteStreamData* data = static_cast<MuteStreamData*>(pmsg->pdata);
+      data->result = MuteStream_w(data->ssrc, data->mute);
+      break;
+    }
+    case MSG_ISSTREAMMUTED: {
+      SsrcMessageData* data = static_cast<SsrcMessageData*>(pmsg->pdata);
+      data->result = IsStreamMuted_w(data->ssrc);
+      break;
+    }
+    case MSG_SETLOCALCONTENT: {
+      SetContentData* data = static_cast<SetContentData*>(pmsg->pdata);
+      data->result = SetLocalContent_w(data->content, data->action);
+      break;
+    }
+    case MSG_SETREMOTECONTENT: {
+      SetContentData* data = static_cast<SetContentData*>(pmsg->pdata);
+      data->result = SetRemoteContent_w(data->content, data->action);
+      break;
+    }
+    case MSG_ADDRECVSTREAM: {
+      StreamMessageData* data = static_cast<StreamMessageData*>(pmsg->pdata);
+      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);
+      break;
+    }
+
+    case MSG_RTPPACKET:
+    case MSG_RTCPPACKET: {
+      PacketMessageData* data = static_cast<PacketMessageData*>(pmsg->pdata);
+      SendPacket(pmsg->message_id == MSG_RTCPPACKET, &data->packet);
+      delete data;  // because it is Posted
+      break;
+    }
+    case MSG_FIRSTPACKETRECEIVED: {
+      SignalFirstPacketReceived(this);
+      break;
+    }
+    case MSG_SESSION_ERROR: {
+      SessionErrorMessageData* data = static_cast<SessionErrorMessageData*>
+          (pmsg->pdata);
+      session_->SetError(data->error_);
+      break;
+    }
+  }
+}
+
+void BaseChannel::Send(uint32 id, talk_base::MessageData *pdata) {
+  worker_thread_->Send(this, id, pdata);
+}
+
+void BaseChannel::Post(uint32 id, talk_base::MessageData *pdata) {
+  worker_thread_->Post(this, id, pdata);
+}
+
+void BaseChannel::PostDelayed(int cmsDelay, uint32 id,
+                              talk_base::MessageData *pdata) {
+  worker_thread_->PostDelayed(cmsDelay, this, id, pdata);
+}
+
+void BaseChannel::Clear(uint32 id, talk_base::MessageList* removed) {
+  worker_thread_->Clear(this, id, removed);
+}
+
+void BaseChannel::FlushRtcpMessages() {
+  // Flush all remaining RTCP messages. This should only be called in
+  // destructor.
+  ASSERT(talk_base::Thread::Current() == worker_thread_);
+  talk_base::MessageList rtcp_messages;
+  Clear(MSG_RTCPPACKET, &rtcp_messages);
+  for (talk_base::MessageList::iterator it = rtcp_messages.begin();
+       it != rtcp_messages.end(); ++it) {
+    Send(MSG_RTCPPACKET, it->pdata);
+  }
+}
+
+VoiceChannel::VoiceChannel(talk_base::Thread* thread,
+                           MediaEngineInterface* media_engine,
+                           VoiceMediaChannel* media_channel,
+                           BaseSession* session,
+                           const std::string& content_name,
+                           bool rtcp)
+    : BaseChannel(thread, media_engine, media_channel, session, content_name,
+                  rtcp),
+      received_media_(false) {
+}
+
+VoiceChannel::~VoiceChannel() {
+  StopAudioMonitor();
+  StopMediaMonitor();
+  // this can't be done in the base class, since it calls a virtual
+  DisableMedia_w();
+}
+
+bool VoiceChannel::Init() {
+  TransportChannel* rtcp_channel = rtcp() ? session()->CreateChannel(
+      content_name(), "rtcp", ICE_CANDIDATE_COMPONENT_RTCP) : NULL;
+  if (!BaseChannel::Init(session()->CreateChannel(
+          content_name(), "rtp", ICE_CANDIDATE_COMPONENT_RTP),
+          rtcp_channel)) {
+    return false;
+  }
+  media_channel()->SignalMediaError.connect(
+      this, &VoiceChannel::OnVoiceChannelError);
+  srtp_filter()->SignalSrtpError.connect(
+      this, &VoiceChannel::OnSrtpError);
+  return true;
+}
+
+bool VoiceChannel::SetRenderer(uint32 ssrc, AudioRenderer* renderer) {
+  AudioRenderMessageData data(ssrc, renderer);
+  Send(MSG_SETRENDERER, &data);
+  return data.result;
+}
+
+bool VoiceChannel::SetRingbackTone(const void* buf, int len) {
+  SetRingbackToneMessageData data(buf, len);
+  Send(MSG_SETRINGBACKTONE, &data);
+  return data.result;
+}
+
+// TODO(juberti): Handle early media the right way. We should get an explicit
+// ringing message telling us to start playing local ringback, which we cancel
+// if any early media actually arrives. For now, we do the opposite, which is
+// to wait 1 second for early media, and start playing local ringback if none
+// arrives.
+void VoiceChannel::SetEarlyMedia(bool enable) {
+  if (enable) {
+    // Start the early media timeout
+    PostDelayed(kEarlyMediaTimeout, MSG_EARLYMEDIATIMEOUT);
+  } else {
+    // Stop the timeout if currently going.
+    Clear(MSG_EARLYMEDIATIMEOUT);
+  }
+}
+
+bool VoiceChannel::PlayRingbackTone(uint32 ssrc, bool play, bool loop) {
+  PlayRingbackToneMessageData data(ssrc, play, loop);
+  Send(MSG_PLAYRINGBACKTONE, &data);
+  return data.result;
+}
+
+bool VoiceChannel::PressDTMF(int digit, bool playout) {
+  int flags = DF_SEND;
+  if (playout) {
+    flags |= DF_PLAY;
+  }
+  int duration_ms = 160;
+  return InsertDtmf(0, digit, duration_ms, flags);
+}
+
+bool VoiceChannel::CanInsertDtmf() {
+  BoolMessageData data(false);
+  Send(MSG_CANINSERTDTMF, &data);
+  return data.data();
+}
+
+bool VoiceChannel::InsertDtmf(uint32 ssrc, int event_code, int duration,
+                              int flags) {
+  DtmfMessageData data(ssrc, event_code, duration, flags);
+  Send(MSG_INSERTDTMF, &data);
+  return data.result;
+}
+
+bool VoiceChannel::SetOutputScaling(uint32 ssrc, double left, double right) {
+  ScaleVolumeMessageData data(ssrc, left, right);
+  Send(MSG_SCALEVOLUME, &data);
+  return data.result;
+}
+bool VoiceChannel::GetStats(VoiceMediaInfo* stats) {
+  VoiceStatsMessageData data(stats);
+  Send(MSG_GETSTATS, &data);
+  return data.result;
+}
+
+void VoiceChannel::StartMediaMonitor(int cms) {
+  media_monitor_.reset(new VoiceMediaMonitor(media_channel(), worker_thread(),
+      talk_base::Thread::Current()));
+  media_monitor_->SignalUpdate.connect(
+      this, &VoiceChannel::OnMediaMonitorUpdate);
+  media_monitor_->Start(cms);
+}
+
+void VoiceChannel::StopMediaMonitor() {
+  if (media_monitor_) {
+    media_monitor_->Stop();
+    media_monitor_->SignalUpdate.disconnect(this);
+    media_monitor_.reset();
+  }
+}
+
+void VoiceChannel::StartAudioMonitor(int cms) {
+  audio_monitor_.reset(new AudioMonitor(this, talk_base::Thread::Current()));
+  audio_monitor_
+    ->SignalUpdate.connect(this, &VoiceChannel::OnAudioMonitorUpdate);
+  audio_monitor_->Start(cms);
+}
+
+void VoiceChannel::StopAudioMonitor() {
+  if (audio_monitor_) {
+    audio_monitor_->Stop();
+    audio_monitor_.reset();
+  }
+}
+
+bool VoiceChannel::IsAudioMonitorRunning() const {
+  return (audio_monitor_.get() != NULL);
+}
+
+void VoiceChannel::StartTypingMonitor(const TypingMonitorOptions& settings) {
+  typing_monitor_.reset(new TypingMonitor(this, worker_thread(), settings));
+  SignalAutoMuted.repeat(typing_monitor_->SignalMuted);
+}
+
+void VoiceChannel::StopTypingMonitor() {
+  typing_monitor_.reset();
+}
+
+bool VoiceChannel::IsTypingMonitorRunning() const {
+  return typing_monitor_;
+}
+
+bool VoiceChannel::MuteStream_w(uint32 ssrc, bool mute) {
+  bool ret = BaseChannel::MuteStream_w(ssrc, mute);
+  if (typing_monitor_ && mute)
+    typing_monitor_->OnChannelMuted();
+  return ret;
+}
+
+int VoiceChannel::GetInputLevel_w() {
+  return media_engine()->GetInputLevel();
+}
+
+int VoiceChannel::GetOutputLevel_w() {
+  return media_channel()->GetOutputLevel();
+}
+
+void VoiceChannel::GetActiveStreams_w(AudioInfo::StreamList* actives) {
+  media_channel()->GetActiveStreams(actives);
+}
+
+void VoiceChannel::OnChannelRead(TransportChannel* channel,
+                                 const char* data, size_t len, int flags) {
+  BaseChannel::OnChannelRead(channel, data, len, flags);
+
+  // Set a flag when we've received an RTP packet. If we're waiting for early
+  // media, this will disable the timeout.
+  if (!received_media_ && !PacketIsRtcp(channel, data, len)) {
+    received_media_ = true;
+  }
+}
+
+void VoiceChannel::ChangeState() {
+  // Render incoming data if we're the active call, and we have the local
+  // content. We receive data on the default channel and multiplexed streams.
+  bool recv = IsReadyToReceive();
+  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 = IsReadyToSend();
+  SendFlags send_flag = send ? SEND_MICROPHONE : SEND_NOTHING;
+  if (!media_channel()->SetSend(send_flag)) {
+    LOG(LS_ERROR) << "Failed to SetSend " << send_flag << " on voice channel";
+    SendLastMediaError();
+  }
+
+  LOG(LS_INFO) << "Changing voice state, recv=" << recv << " send=" << send;
+}
+
+const ContentInfo* VoiceChannel::GetFirstContent(
+    const SessionDescription* sdesc) {
+  return GetFirstAudioContent(sdesc);
+}
+
+bool VoiceChannel::SetLocalContent_w(const MediaContentDescription* content,
+                                     ContentAction action) {
+  ASSERT(worker_thread() == talk_base::Thread::Current());
+  LOG(LS_INFO) << "Setting local voice description";
+
+  const AudioContentDescription* audio =
+      static_cast<const AudioContentDescription*>(content);
+  ASSERT(audio != NULL);
+  if (!audio) return false;
+
+  bool ret = SetBaseLocalContent_w(content, action);
+  // Set local audio codecs (what we want to receive).
+  // TODO(whyuan): 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());
+  }
+
+  // If everything worked, see if we can start receiving.
+  if (ret) {
+    ChangeState();
+  } else {
+    LOG(LS_WARNING) << "Failed to set local voice description";
+  }
+  return ret;
+}
+
+bool VoiceChannel::SetRemoteContent_w(const MediaContentDescription* content,
+                                      ContentAction action) {
+  ASSERT(worker_thread() == talk_base::Thread::Current());
+  LOG(LS_INFO) << "Setting remote voice description";
+
+  const AudioContentDescription* audio =
+      static_cast<const AudioContentDescription*>(content);
+  ASSERT(audio != NULL);
+  if (!audio) return false;
+
+  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());
+  }
+
+  ret &= SetBaseRemoteContent_w(content, action);
+
+  if (action != CA_UPDATE) {
+    // Tweak our audio processing settings, if needed.
+    AudioOptions audio_options;
+    if (!media_channel()->GetOptions(&audio_options)) {
+      LOG(LS_WARNING) << "Can not set audio options from on remote content.";
+    } else {
+      if (audio->conference_mode()) {
+        audio_options.conference_mode.Set(true);
+      }
+      if (audio->agc_minus_10db()) {
+        audio_options.adjust_agc_delta.Set(kAgcMinus10db);
+      }
+      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";
+      }
+    }
+  }
+
+  // If everything worked, see if we can start sending.
+  if (ret) {
+    ChangeState();
+  } else {
+    LOG(LS_WARNING) << "Failed to set remote voice description";
+  }
+  return ret;
+}
+
+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);
+}
+
+bool VoiceChannel::PlayRingbackTone_w(uint32 ssrc, bool play, bool loop) {
+  ASSERT(worker_thread() == talk_base::Thread::Current());
+  if (play) {
+    LOG(LS_INFO) << "Playing ringback tone, loop=" << loop;
+  } else {
+    LOG(LS_INFO) << "Stopping ringback tone";
+  }
+  return media_channel()->PlayRingbackTone(ssrc, play, loop);
+}
+
+void VoiceChannel::HandleEarlyMediaTimeout() {
+  // This occurs on the main thread, not the worker thread.
+  if (!received_media_) {
+    LOG(LS_INFO) << "No early media received before timeout";
+    SignalEarlyMediaTimeout(this);
+  }
+}
+
+bool VoiceChannel::CanInsertDtmf_w() {
+  return media_channel()->CanInsertDtmf();
+}
+
+bool VoiceChannel::InsertDtmf_w(uint32 ssrc, int event, int duration,
+                                int flags) {
+  if (!enabled()) {
+    return false;
+  }
+
+  return media_channel()->InsertDtmf(ssrc, event, duration, flags);
+}
+
+bool VoiceChannel::SetOutputScaling_w(uint32 ssrc, double left, double right) {
+  return media_channel()->SetOutputScaling(ssrc, left, right);
+}
+
+bool VoiceChannel::GetStats_w(VoiceMediaInfo* stats) {
+  return media_channel()->GetStats(stats);
+}
+
+bool VoiceChannel::SetChannelOptions(const AudioOptions& options) {
+  AudioOptionsMessageData data(options);
+  Send(MSG_SETCHANNELOPTIONS, &data);
+  return data.result;
+}
+
+bool VoiceChannel::SetChannelOptions_w(const AudioOptions& options) {
+  return media_channel()->SetOptions(options);
+}
+
+bool VoiceChannel::SetRenderer_w(uint32 ssrc, AudioRenderer* renderer) {
+  return media_channel()->SetRenderer(ssrc, renderer);
+}
+
+void VoiceChannel::OnMessage(talk_base::Message *pmsg) {
+  switch (pmsg->message_id) {
+    case MSG_SETRINGBACKTONE: {
+      SetRingbackToneMessageData* data =
+          static_cast<SetRingbackToneMessageData*>(pmsg->pdata);
+      data->result = SetRingbackTone_w(data->buf, data->len);
+      break;
+    }
+    case MSG_PLAYRINGBACKTONE: {
+      PlayRingbackToneMessageData* data =
+          static_cast<PlayRingbackToneMessageData*>(pmsg->pdata);
+      data->result = PlayRingbackTone_w(data->ssrc, data->play, data->loop);
+      break;
+    }
+    case MSG_EARLYMEDIATIMEOUT:
+      HandleEarlyMediaTimeout();
+      break;
+    case MSG_CANINSERTDTMF: {
+      BoolMessageData* data =
+          static_cast<BoolMessageData*>(pmsg->pdata);
+      data->data() = CanInsertDtmf_w();
+      break;
+    }
+    case MSG_INSERTDTMF: {
+      DtmfMessageData* data =
+          static_cast<DtmfMessageData*>(pmsg->pdata);
+      data->result = InsertDtmf_w(data->ssrc, data->event, data->duration,
+                                  data->flags);
+      break;
+    }
+    case MSG_SCALEVOLUME: {
+      ScaleVolumeMessageData* data =
+          static_cast<ScaleVolumeMessageData*>(pmsg->pdata);
+      data->result = SetOutputScaling_w(data->ssrc, data->left, data->right);
+      break;
+    }
+    case MSG_GETSTATS: {
+      VoiceStatsMessageData* data =
+          static_cast<VoiceStatsMessageData*>(pmsg->pdata);
+      data->result = GetStats_w(data->stats);
+      break;
+    }
+    case MSG_CHANNEL_ERROR: {
+      VoiceChannelErrorMessageData* data =
+          static_cast<VoiceChannelErrorMessageData*>(pmsg->pdata);
+      SignalMediaError(this, data->ssrc, data->error);
+      delete data;
+      break;
+    }
+    case MSG_SETCHANNELOPTIONS: {
+      AudioOptionsMessageData* data =
+          static_cast<AudioOptionsMessageData*>(pmsg->pdata);
+      data->result = SetChannelOptions_w(data->options);
+      break;
+    }
+    case MSG_SETRENDERER: {
+      AudioRenderMessageData* data =
+          static_cast<AudioRenderMessageData*>(pmsg->pdata);
+      data->result = SetRenderer_w(data->ssrc, data->renderer);
+      break;
+    }
+    default:
+      BaseChannel::OnMessage(pmsg);
+      break;
+  }
+}
+
+void VoiceChannel::OnConnectionMonitorUpdate(
+    SocketMonitor* monitor, const std::vector<ConnectionInfo>& infos) {
+  SignalConnectionMonitor(this, infos);
+}
+
+void VoiceChannel::OnMediaMonitorUpdate(
+    VoiceMediaChannel* media_channel, const VoiceMediaInfo& info) {
+  ASSERT(media_channel == this->media_channel());
+  SignalMediaMonitor(this, info);
+}
+
+void VoiceChannel::OnAudioMonitorUpdate(AudioMonitor* monitor,
+                                        const AudioInfo& info) {
+  SignalAudioMonitor(this, info);
+}
+
+void VoiceChannel::OnVoiceChannelError(
+    uint32 ssrc, VoiceMediaChannel::Error err) {
+  VoiceChannelErrorMessageData* data = new VoiceChannelErrorMessageData(
+      ssrc, err);
+  signaling_thread()->Post(this, MSG_CHANNEL_ERROR, data);
+}
+
+void VoiceChannel::OnSrtpError(uint32 ssrc, SrtpFilter::Mode mode,
+                               SrtpFilter::Error error) {
+  switch (error) {
+    case SrtpFilter::ERROR_FAIL:
+      OnVoiceChannelError(ssrc, (mode == SrtpFilter::PROTECT) ?
+                          VoiceMediaChannel::ERROR_REC_SRTP_ERROR :
+                          VoiceMediaChannel::ERROR_PLAY_SRTP_ERROR);
+      break;
+    case SrtpFilter::ERROR_AUTH:
+      OnVoiceChannelError(ssrc, (mode == SrtpFilter::PROTECT) ?
+                          VoiceMediaChannel::ERROR_REC_SRTP_AUTH_FAILED :
+                          VoiceMediaChannel::ERROR_PLAY_SRTP_AUTH_FAILED);
+      break;
+    case SrtpFilter::ERROR_REPLAY:
+      // Only receving channel should have this error.
+      ASSERT(mode == SrtpFilter::UNPROTECT);
+      OnVoiceChannelError(ssrc, VoiceMediaChannel::ERROR_PLAY_SRTP_REPLAY);
+      break;
+    default:
+      break;
+  }
+}
+
+void VoiceChannel::GetSrtpCiphers(std::vector<std::string>* ciphers) const {
+  GetSupportedAudioCryptoSuites(ciphers);
+}
+
+VideoChannel::VideoChannel(talk_base::Thread* thread,
+                           MediaEngineInterface* media_engine,
+                           VideoMediaChannel* media_channel,
+                           BaseSession* session,
+                           const std::string& content_name,
+                           bool rtcp,
+                           VoiceChannel* voice_channel)
+    : BaseChannel(thread, media_engine, media_channel, session, content_name,
+                  rtcp),
+      voice_channel_(voice_channel),
+      renderer_(NULL),
+      screencapture_factory_(CreateScreenCapturerFactory()),
+      previous_we_(talk_base::WE_CLOSE) {
+}
+
+bool VideoChannel::Init() {
+  TransportChannel* rtcp_channel = rtcp() ? session()->CreateChannel(
+      content_name(), "video_rtcp", ICE_CANDIDATE_COMPONENT_RTCP) : NULL;
+  if (!BaseChannel::Init(session()->CreateChannel(
+          content_name(), "video_rtp", ICE_CANDIDATE_COMPONENT_RTP),
+          rtcp_channel)) {
+    return false;
+  }
+  media_channel()->SignalMediaError.connect(
+      this, &VideoChannel::OnVideoChannelError);
+  srtp_filter()->SignalSrtpError.connect(
+      this, &VideoChannel::OnSrtpError);
+  return true;
+}
+
+void VoiceChannel::SendLastMediaError() {
+  uint32 ssrc;
+  VoiceMediaChannel::Error error;
+  media_channel()->GetLastMediaError(&ssrc, &error);
+  SignalMediaError(this, ssrc, error);
+}
+
+VideoChannel::~VideoChannel() {
+  std::vector<uint32> screencast_ssrcs;
+  ScreencastMap::iterator iter;
+  while (!screencast_capturers_.empty()) {
+    if (!RemoveScreencast(screencast_capturers_.begin()->first)) {
+      LOG(LS_ERROR) << "Unable to delete screencast with ssrc "
+                    << screencast_capturers_.begin()->first;
+      ASSERT(false);
+      break;
+    }
+  }
+
+  StopMediaMonitor();
+  // this can't be done in the base class, since it calls a virtual
+  DisableMedia_w();
+}
+
+bool VideoChannel::SetRenderer(uint32 ssrc, VideoRenderer* renderer) {
+  VideoRenderMessageData 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;
+}
+
+VideoCapturer* VideoChannel::AddScreencast(
+    uint32 ssrc, const ScreencastId& id) {
+  AddScreencastMessageData data(ssrc, id);
+  Send(MSG_ADDSCREENCAST, &data);
+  return data.result;
+}
+
+bool VideoChannel::SetCapturer(uint32 ssrc, VideoCapturer* capturer) {
+  SetCapturerMessageData data(ssrc, capturer);
+  Send(MSG_SETCAPTURER, &data);
+  return data.result;
+}
+
+bool VideoChannel::RemoveScreencast(uint32 ssrc) {
+  RemoveScreencastMessageData data(ssrc);
+  Send(MSG_REMOVESCREENCAST, &data);
+  return data.result;
+}
+
+bool VideoChannel::IsScreencasting() {
+  IsScreencastingMessageData data;
+  Send(MSG_ISSCREENCASTING, &data);
+  return data.result;
+}
+
+int VideoChannel::ScreencastFps(uint32 ssrc) {
+  ScreencastFpsMessageData data(ssrc);
+  Send(MSG_SCREENCASTFPS, &data);
+  return data.result;
+}
+
+bool VideoChannel::SendIntraFrame() {
+  Send(MSG_SENDINTRAFRAME);
+  return true;
+}
+
+bool VideoChannel::RequestIntraFrame() {
+  Send(MSG_REQUESTINTRAFRAME);
+  return true;
+}
+
+void VideoChannel::SetScreenCaptureFactory(
+    ScreenCapturerFactory* screencapture_factory) {
+  SetScreenCaptureFactoryMessageData data(screencapture_factory);
+  Send(MSG_SETSCREENCASTFACTORY, &data);
+}
+
+void VideoChannel::ChangeState() {
+  // Render incoming data if we're the active call, and we have the local
+  // content. We receive data on the default channel and multiplexed streams.
+  bool recv = IsReadyToReceive();
+  if (!media_channel()->SetRender(recv)) {
+    LOG(LS_ERROR) << "Failed to SetRender on video channel";
+    // TODO(gangji): Report error back to server.
+  }
+
+  // Send outgoing data if we're the active call, we have the remote content,
+  // and we have had some form of connectivity.
+  bool send = IsReadyToSend();
+  if (!media_channel()->SetSend(send)) {
+    LOG(LS_ERROR) << "Failed to SetSend on video channel";
+    // TODO(gangji): Report error back to server.
+  }
+
+  LOG(LS_INFO) << "Changing video state, recv=" << recv << " send=" << send;
+}
+
+bool VideoChannel::GetStats(VideoMediaInfo* stats) {
+  VideoStatsMessageData data(stats);
+  Send(MSG_GETSTATS, &data);
+  return data.result;
+}
+
+void VideoChannel::StartMediaMonitor(int cms) {
+  media_monitor_.reset(new VideoMediaMonitor(media_channel(), worker_thread(),
+      talk_base::Thread::Current()));
+  media_monitor_->SignalUpdate.connect(
+      this, &VideoChannel::OnMediaMonitorUpdate);
+  media_monitor_->Start(cms);
+}
+
+void VideoChannel::StopMediaMonitor() {
+  if (media_monitor_) {
+    media_monitor_->Stop();
+    media_monitor_.reset();
+  }
+}
+
+const ContentInfo* VideoChannel::GetFirstContent(
+    const SessionDescription* sdesc) {
+  return GetFirstVideoContent(sdesc);
+}
+
+bool VideoChannel::SetLocalContent_w(const MediaContentDescription* content,
+                                     ContentAction action) {
+  ASSERT(worker_thread() == talk_base::Thread::Current());
+  LOG(LS_INFO) << "Setting local video description";
+
+  const VideoContentDescription* video =
+      static_cast<const VideoContentDescription*>(content);
+  ASSERT(video != NULL);
+  if (!video) return false;
+
+  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());
+  }
+
+  if (action != CA_UPDATE) {
+    VideoOptions video_options;
+    media_channel()->GetOptions(&video_options);
+    video_options.buffered_mode_latency.Set(video->buffered_mode_latency());
+
+    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";
+    }
+  }
+
+  // If everything worked, see if we can start receiving.
+  if (ret) {
+    ChangeState();
+  } else {
+    LOG(LS_WARNING) << "Failed to set local video description";
+  }
+  return ret;
+}
+
+bool VideoChannel::SetRemoteContent_w(const MediaContentDescription* content,
+                                      ContentAction action) {
+  ASSERT(worker_thread() == talk_base::Thread::Current());
+  LOG(LS_INFO) << "Setting remote video description";
+
+  const VideoContentDescription* video =
+      static_cast<const VideoContentDescription*>(content);
+  ASSERT(video != NULL);
+  if (!video) return false;
+
+  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());
+  }
+
+  ret &= SetBaseRemoteContent_w(content, action);
+
+  if (action != CA_UPDATE) {
+    // Tweak our video processing settings, if needed.
+    VideoOptions video_options;
+    media_channel()->GetOptions(&video_options);
+    video_options.conference_mode.Set(video->conference_mode());
+    video_options.buffered_mode_latency.Set(video->buffered_mode_latency());
+
+    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";
+    }
+  }
+
+  // If everything worked, see if we can start sending.
+  if (ret) {
+    ChangeState();
+  } else {
+    LOG(LS_WARNING) << "Failed to set remote video description";
+  }
+  return ret;
+}
+
+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) {
+      if (view->selector.Matches(*it)) {
+        format.width = view->width;
+        format.height = view->height;
+        format.interval = cricket::VideoFormat::FpsToInterval(view->framerate);
+        break;
+      }
+    }
+
+    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 (!GetStream(local_streams(), it->selector, NULL)) {
+      LOG(LS_WARNING) << "View request for ("
+                      << it->selector.ssrc << ", '"
+                      << it->selector.groupid << "', '"
+                      << it->selector.streamid << "'"
+                      << ") is not in the local streams.";
+    }
+  }
+
+  return ret;
+}
+
+void VideoChannel::SetRenderer_w(uint32 ssrc, VideoRenderer* renderer) {
+  media_channel()->SetRenderer(ssrc, renderer);
+}
+
+VideoCapturer* VideoChannel::AddScreencast_w(
+    uint32 ssrc, const ScreencastId& id) {
+  if (screencast_capturers_.find(ssrc) != screencast_capturers_.end()) {
+    return NULL;
+  }
+  VideoCapturer* screen_capturer =
+      screencapture_factory_->CreateScreenCapturer(id);
+  if (!screen_capturer) {
+    return NULL;
+  }
+  screen_capturer->SignalStateChange.connect(this,
+                                             &VideoChannel::OnStateChange);
+  screencast_capturers_[ssrc] = screen_capturer;
+  return screen_capturer;
+}
+
+bool VideoChannel::SetCapturer_w(uint32 ssrc, VideoCapturer* capturer) {
+  return media_channel()->SetCapturer(ssrc, capturer);
+}
+
+bool VideoChannel::RemoveScreencast_w(uint32 ssrc) {
+  ScreencastMap::iterator iter = screencast_capturers_.find(ssrc);
+  if (iter  == screencast_capturers_.end()) {
+    return false;
+  }
+  // Clean up VideoCapturer.
+  delete iter->second;
+  screencast_capturers_.erase(iter);
+  return true;
+}
+
+bool VideoChannel::IsScreencasting_w() const {
+  return !screencast_capturers_.empty();
+}
+
+int VideoChannel::ScreencastFps_w(uint32 ssrc) const {
+  ScreencastMap::const_iterator iter = screencast_capturers_.find(ssrc);
+  if (iter == screencast_capturers_.end()) {
+    return 0;
+  }
+  VideoCapturer* capturer = iter->second;
+  const VideoFormat* video_format = capturer->GetCaptureFormat();
+  return VideoFormat::IntervalToFps(video_format->interval);
+}
+
+void VideoChannel::SetScreenCaptureFactory_w(
+    ScreenCapturerFactory* screencapture_factory) {
+  if (screencapture_factory == NULL) {
+    screencapture_factory_.reset(CreateScreenCapturerFactory());
+  } else {
+    screencapture_factory_.reset(screencapture_factory);
+  }
+}
+
+bool VideoChannel::GetStats_w(VideoMediaInfo* stats) {
+  return media_channel()->GetStats(stats);
+}
+
+void VideoChannel::OnScreencastWindowEvent_s(uint32 ssrc,
+                                             talk_base::WindowEvent we) {
+  ASSERT(signaling_thread() == talk_base::Thread::Current());
+  SignalScreencastWindowEvent(ssrc, we);
+}
+
+bool VideoChannel::SetChannelOptions(const VideoOptions &options) {
+  VideoOptionsMessageData data(options);
+  Send(MSG_SETCHANNELOPTIONS, &data);
+  return data.result;
+}
+
+bool VideoChannel::SetChannelOptions_w(const VideoOptions &options) {
+  return media_channel()->SetOptions(options);
+}
+
+void VideoChannel::OnMessage(talk_base::Message *pmsg) {
+  switch (pmsg->message_id) {
+    case MSG_SETRENDERER: {
+      const VideoRenderMessageData* data =
+          static_cast<VideoRenderMessageData*>(pmsg->pdata);
+      SetRenderer_w(data->ssrc, data->renderer);
+      break;
+    }
+    case MSG_ADDSCREENCAST: {
+      AddScreencastMessageData* data =
+          static_cast<AddScreencastMessageData*>(pmsg->pdata);
+      data->result = AddScreencast_w(data->ssrc, data->window_id);
+      break;
+    }
+    case MSG_SETCAPTURER: {
+      SetCapturerMessageData* data =
+          static_cast<SetCapturerMessageData*>(pmsg->pdata);
+      data->result = SetCapturer_w(data->ssrc, data->capturer);
+      break;
+    }
+    case MSG_REMOVESCREENCAST: {
+      RemoveScreencastMessageData* data =
+          static_cast<RemoveScreencastMessageData*>(pmsg->pdata);
+      data->result = RemoveScreencast_w(data->ssrc);
+      break;
+    }
+    case MSG_SCREENCASTWINDOWEVENT: {
+      const ScreencastEventMessageData* data =
+          static_cast<ScreencastEventMessageData*>(pmsg->pdata);
+      OnScreencastWindowEvent_s(data->ssrc, data->event);
+      delete data;
+      break;
+    }
+    case  MSG_ISSCREENCASTING: {
+      IsScreencastingMessageData* data =
+          static_cast<IsScreencastingMessageData*>(pmsg->pdata);
+      data->result = IsScreencasting_w();
+      break;
+    }
+    case MSG_SCREENCASTFPS: {
+      ScreencastFpsMessageData* data =
+          static_cast<ScreencastFpsMessageData*>(pmsg->pdata);
+      data->result = ScreencastFps_w(data->ssrc);
+      break;
+    }
+    case MSG_SENDINTRAFRAME: {
+      SendIntraFrame_w();
+      break;
+    }
+    case MSG_REQUESTINTRAFRAME: {
+      RequestIntraFrame_w();
+      break;
+    }
+    case MSG_SETCHANNELOPTIONS: {
+      VideoOptionsMessageData* data =
+         static_cast<VideoOptionsMessageData*>(pmsg->pdata);
+      data->result = SetChannelOptions_w(data->options);
+      break;
+    }
+    case MSG_CHANNEL_ERROR: {
+      const VideoChannelErrorMessageData* data =
+          static_cast<VideoChannelErrorMessageData*>(pmsg->pdata);
+      SignalMediaError(this, data->ssrc, data->error);
+      delete data;
+      break;
+    }
+    case MSG_HANDLEVIEWREQUEST: {
+      ViewRequestMessageData* data =
+          static_cast<ViewRequestMessageData*>(pmsg->pdata);
+      data->result = ApplyViewRequest_w(data->request);
+      break;
+    }
+    case MSG_SETSCREENCASTFACTORY: {
+      SetScreenCaptureFactoryMessageData* data =
+          static_cast<SetScreenCaptureFactoryMessageData*>(pmsg->pdata);
+      SetScreenCaptureFactory_w(data->screencapture_factory);
+    }
+    case MSG_GETSTATS: {
+      VideoStatsMessageData* data =
+          static_cast<VideoStatsMessageData*>(pmsg->pdata);
+      data->result = GetStats_w(data->stats);
+      break;
+    }
+    default:
+      BaseChannel::OnMessage(pmsg);
+      break;
+  }
+}
+
+void VideoChannel::OnConnectionMonitorUpdate(
+    SocketMonitor *monitor, const std::vector<ConnectionInfo> &infos) {
+  SignalConnectionMonitor(this, infos);
+}
+
+// TODO(pthatcher): Look into removing duplicate code between
+// audio, video, and data, perhaps by using templates.
+void VideoChannel::OnMediaMonitorUpdate(
+    VideoMediaChannel* media_channel, const VideoMediaInfo &info) {
+  ASSERT(media_channel == this->media_channel());
+  SignalMediaMonitor(this, info);
+}
+
+void VideoChannel::OnScreencastWindowEvent(uint32 ssrc,
+                                           talk_base::WindowEvent event) {
+  ScreencastEventMessageData* pdata =
+      new ScreencastEventMessageData(ssrc, event);
+  signaling_thread()->Post(this, MSG_SCREENCASTWINDOWEVENT, pdata);
+}
+
+void VideoChannel::OnStateChange(VideoCapturer* capturer, CaptureState ev) {
+  // Map capturer events to window events. In the future we may want to simply
+  // pass these events up directly.
+  talk_base::WindowEvent we;
+  if (ev == CS_STOPPED) {
+    we = talk_base::WE_CLOSE;
+  } else if (ev == CS_PAUSED) {
+    we = talk_base::WE_MINIMIZE;
+  } else if (ev == CS_RUNNING && previous_we_ == talk_base::WE_MINIMIZE) {
+    we = talk_base::WE_RESTORE;
+  } else {
+    return;
+  }
+  previous_we_ = we;
+
+  uint32 ssrc = 0;
+  if (!GetLocalSsrc(capturer, &ssrc)) {
+    return;
+  }
+  ScreencastEventMessageData* pdata =
+      new ScreencastEventMessageData(ssrc, we);
+  signaling_thread()->Post(this, MSG_SCREENCASTWINDOWEVENT, pdata);
+}
+
+bool VideoChannel::GetLocalSsrc(const VideoCapturer* capturer, uint32* ssrc) {
+  *ssrc = 0;
+  for (ScreencastMap::iterator iter = screencast_capturers_.begin();
+       iter != screencast_capturers_.end(); ++iter) {
+    if (iter->second == capturer) {
+      *ssrc = iter->first;
+      return true;
+    }
+  }
+  return false;
+}
+
+void VideoChannel::OnVideoChannelError(uint32 ssrc,
+                                       VideoMediaChannel::Error error) {
+  VideoChannelErrorMessageData* data = new VideoChannelErrorMessageData(
+      ssrc, error);
+  signaling_thread()->Post(this, MSG_CHANNEL_ERROR, data);
+}
+
+void VideoChannel::OnSrtpError(uint32 ssrc, SrtpFilter::Mode mode,
+                               SrtpFilter::Error error) {
+  switch (error) {
+    case SrtpFilter::ERROR_FAIL:
+      OnVideoChannelError(ssrc, (mode == SrtpFilter::PROTECT) ?
+                          VideoMediaChannel::ERROR_REC_SRTP_ERROR :
+                          VideoMediaChannel::ERROR_PLAY_SRTP_ERROR);
+      break;
+    case SrtpFilter::ERROR_AUTH:
+      OnVideoChannelError(ssrc, (mode == SrtpFilter::PROTECT) ?
+                          VideoMediaChannel::ERROR_REC_SRTP_AUTH_FAILED :
+                          VideoMediaChannel::ERROR_PLAY_SRTP_AUTH_FAILED);
+      break;
+    case SrtpFilter::ERROR_REPLAY:
+      // Only receving channel should have this error.
+      ASSERT(mode == SrtpFilter::UNPROTECT);
+      // TODO(gangji): Turn on the signaling of replay error once we have
+      // switched to the new mechanism for doing video retransmissions.
+      // OnVideoChannelError(ssrc, VideoMediaChannel::ERROR_PLAY_SRTP_REPLAY);
+      break;
+    default:
+      break;
+  }
+}
+
+
+void VideoChannel::GetSrtpCiphers(std::vector<std::string>* ciphers) const {
+  GetSupportedVideoCryptoSuites(ciphers);
+}
+
+DataChannel::DataChannel(talk_base::Thread* thread,
+                         DataMediaChannel* media_channel,
+                         BaseSession* session,
+                         const std::string& content_name,
+                         bool rtcp)
+    // MediaEngine is NULL
+    : BaseChannel(thread, NULL, media_channel, session, content_name, rtcp),
+      data_channel_type_(cricket::DCT_NONE) {
+}
+
+DataChannel::~DataChannel() {
+  StopMediaMonitor();
+  // this can't be done in the base class, since it calls a virtual
+  DisableMedia_w();
+}
+
+bool DataChannel::Init() {
+  TransportChannel* rtcp_channel = rtcp() ? session()->CreateChannel(
+      content_name(), "data_rtcp", ICE_CANDIDATE_COMPONENT_RTCP) : NULL;
+  if (!BaseChannel::Init(session()->CreateChannel(
+          content_name(), "data_rtp", ICE_CANDIDATE_COMPONENT_RTP),
+          rtcp_channel)) {
+    return false;
+  }
+  media_channel()->SignalDataReceived.connect(
+      this, &DataChannel::OnDataReceived);
+  media_channel()->SignalMediaError.connect(
+      this, &DataChannel::OnDataChannelError);
+  srtp_filter()->SignalSrtpError.connect(
+      this, &DataChannel::OnSrtpError);
+  return true;
+}
+
+bool DataChannel::SendData(const SendDataParams& params,
+                           const talk_base::Buffer& payload,
+                           SendDataResult* result) {
+  SendDataMessageData message_data(params, &payload, result);
+  Send(MSG_SENDDATA, &message_data);
+  return message_data.succeeded;
+}
+
+const ContentInfo* DataChannel::GetFirstContent(
+    const SessionDescription* sdesc) {
+  return GetFirstDataContent(sdesc);
+}
+
+
+static bool IsRtpPacket(const talk_base::Buffer* packet) {
+  int version;
+  if (!GetRtpVersion(packet->data(), packet->length(), &version)) {
+    return false;
+  }
+
+  return version == 2;
+}
+
+bool DataChannel::WantsPacket(bool rtcp, talk_base::Buffer* packet) {
+  if (data_channel_type_ == DCT_SCTP) {
+    // TODO(pthatcher): Do this in a more robust way by checking for
+    // SCTP or DTLS.
+    return !IsRtpPacket(packet);
+  } else if (data_channel_type_ == DCT_RTP) {
+    return BaseChannel::WantsPacket(rtcp, packet);
+  }
+  return false;
+}
+
+// Sets the maximum bandwidth.  Anything over this will be dropped.
+bool DataChannel::SetMaxSendBandwidth_w(int max_bps) {
+  LOG(LS_INFO) << "DataChannel: Setting max bandwidth to " << max_bps;
+  return media_channel()->SetSendBandwidth(false, max_bps);
+}
+
+bool DataChannel::SetDataChannelType(DataChannelType new_data_channel_type) {
+  // It hasn't been set before, so set it now.
+  if (data_channel_type_ == DCT_NONE) {
+    data_channel_type_ = new_data_channel_type;
+    return true;
+  }
+
+  // It's been set before, but doesn't match.  That's bad.
+  if (data_channel_type_ != new_data_channel_type) {
+    LOG(LS_WARNING) << "Data channel type mismatch."
+                    << " Expected " << data_channel_type_
+                    << " Got " << new_data_channel_type;
+    return false;
+  }
+
+  // It's hasn't changed.  Nothing to do.
+  return true;
+}
+
+bool DataChannel::SetDataChannelTypeFromContent(
+    const DataContentDescription* content) {
+  bool is_sctp = ((content->protocol() == kMediaProtocolSctp) ||
+                  (content->protocol() == kMediaProtocolDtlsSctp));
+  DataChannelType data_channel_type = is_sctp ? DCT_SCTP : DCT_RTP;
+  return SetDataChannelType(data_channel_type);
+}
+
+bool DataChannel::SetLocalContent_w(const MediaContentDescription* content,
+                                    ContentAction action) {
+  ASSERT(worker_thread() == talk_base::Thread::Current());
+  LOG(LS_INFO) << "Setting local data description";
+
+  const DataContentDescription* data =
+      static_cast<const DataContentDescription*>(content);
+  ASSERT(data != NULL);
+  if (!data) return false;
+
+  bool ret = false;
+  if (!SetDataChannelTypeFromContent(data)) {
+    return false;
+  }
+
+  if (data_channel_type_ == DCT_SCTP) {
+    // SCTP data channels don't need the rest of the stuff.
+    ret = UpdateLocalStreams_w(data->streams(), action);
+    if (ret) {
+      set_local_content_direction(content->direction());
+    }
+  } else {
+    ret = SetBaseLocalContent_w(content, action);
+
+    if (action != CA_UPDATE || data->has_codecs()) {
+      ret &= media_channel()->SetRecvCodecs(data->codecs());
+    }
+  }
+
+  // If everything worked, see if we can start receiving.
+  if (ret) {
+    ChangeState();
+  } else {
+    LOG(LS_WARNING) << "Failed to set local data description";
+  }
+  return ret;
+}
+
+bool DataChannel::SetRemoteContent_w(const MediaContentDescription* content,
+                                     ContentAction action) {
+  ASSERT(worker_thread() == talk_base::Thread::Current());
+
+  const DataContentDescription* data =
+      static_cast<const DataContentDescription*>(content);
+  ASSERT(data != NULL);
+  if (!data) return false;
+
+  bool ret = true;
+  if (!SetDataChannelTypeFromContent(data)) {
+    return false;
+  }
+
+  if (data_channel_type_ == DCT_SCTP) {
+    LOG(LS_INFO) << "Setting SCTP remote data description";
+    // SCTP data channels don't need the rest of the stuff.
+    ret = UpdateRemoteStreams_w(content->streams(), action);
+    if (ret) {
+      set_remote_content_direction(content->direction());
+    }
+  } else {
+    // If the remote data doesn't have codecs and isn't an update, it
+    // must be empty, so ignore it.
+    if (action != CA_UPDATE && !data->has_codecs()) {
+      return true;
+    }
+    LOG(LS_INFO) << "Setting remote data description";
+
+    // Set remote video codecs (what the other side wants to receive).
+    if (action != CA_UPDATE || data->has_codecs()) {
+      ret &= media_channel()->SetSendCodecs(data->codecs());
+    }
+
+    if (ret) {
+      ret &= SetBaseRemoteContent_w(content, action);
+    }
+
+    if (action != CA_UPDATE) {
+      int bandwidth_bps = data->bandwidth();
+      bool auto_bandwidth = (bandwidth_bps == kAutoBandwidth);
+      ret &= media_channel()->SetSendBandwidth(auto_bandwidth, bandwidth_bps);
+    }
+  }
+
+  // If everything worked, see if we can start sending.
+  if (ret) {
+    ChangeState();
+  } else {
+    LOG(LS_WARNING) << "Failed to set remote data description";
+  }
+  return ret;
+}
+
+void DataChannel::ChangeState() {
+  // Render incoming data if we're the active call, and we have the local
+  // content. We receive data on the default channel and multiplexed streams.
+  bool recv = IsReadyToReceive();
+  if (!media_channel()->SetReceive(recv)) {
+    LOG(LS_ERROR) << "Failed to SetReceive on data channel";
+  }
+
+  // Send outgoing data if we're the active call, we have the remote content,
+  // and we have had some form of connectivity.
+  bool send = IsReadyToSend();
+  if (!media_channel()->SetSend(send)) {
+    LOG(LS_ERROR) << "Failed to SetSend on data channel";
+  }
+
+  // Post to trigger SignalReadyToSendData.
+  signaling_thread()->Post(this, MSG_READYTOSENDDATA,
+                           new BoolMessageData(send));
+
+  LOG(LS_INFO) << "Changing data state, recv=" << recv << " send=" << send;
+}
+
+void DataChannel::OnMessage(talk_base::Message *pmsg) {
+  switch (pmsg->message_id) {
+    case MSG_READYTOSENDDATA: {
+      BoolMessageData* data = static_cast<BoolMessageData*>(pmsg->pdata);
+      SignalReadyToSendData(data->data());
+      delete data;
+      break;
+    }
+    case MSG_SENDDATA: {
+      SendDataMessageData* msg =
+          static_cast<SendDataMessageData*>(pmsg->pdata);
+      msg->succeeded = media_channel()->SendData(
+          msg->params, *(msg->payload), msg->result);
+      break;
+    }
+    case MSG_DATARECEIVED: {
+      DataReceivedMessageData* data =
+          static_cast<DataReceivedMessageData*>(pmsg->pdata);
+      SignalDataReceived(this, data->params, data->payload);
+      delete data;
+      break;
+    }
+    case MSG_CHANNEL_ERROR: {
+      const DataChannelErrorMessageData* data =
+          static_cast<DataChannelErrorMessageData*>(pmsg->pdata);
+      SignalMediaError(this, data->ssrc, data->error);
+      delete data;
+      break;
+    }
+    default:
+      BaseChannel::OnMessage(pmsg);
+      break;
+  }
+}
+
+void DataChannel::OnConnectionMonitorUpdate(
+    SocketMonitor* monitor, const std::vector<ConnectionInfo>& infos) {
+  SignalConnectionMonitor(this, infos);
+}
+
+void DataChannel::StartMediaMonitor(int cms) {
+  media_monitor_.reset(new DataMediaMonitor(media_channel(), worker_thread(),
+      talk_base::Thread::Current()));
+  media_monitor_->SignalUpdate.connect(
+      this, &DataChannel::OnMediaMonitorUpdate);
+  media_monitor_->Start(cms);
+}
+
+void DataChannel::StopMediaMonitor() {
+  if (media_monitor_) {
+    media_monitor_->Stop();
+    media_monitor_->SignalUpdate.disconnect(this);
+    media_monitor_.reset();
+  }
+}
+
+void DataChannel::OnMediaMonitorUpdate(
+    DataMediaChannel* media_channel, const DataMediaInfo& info) {
+  ASSERT(media_channel == this->media_channel());
+  SignalMediaMonitor(this, info);
+}
+
+void DataChannel::OnDataReceived(
+    const ReceiveDataParams& params, const char* data, size_t len) {
+  DataReceivedMessageData* msg = new DataReceivedMessageData(
+      params, data, len);
+  signaling_thread()->Post(this, MSG_DATARECEIVED, msg);
+}
+
+void DataChannel::OnDataChannelError(
+    uint32 ssrc, DataMediaChannel::Error err) {
+  DataChannelErrorMessageData* data = new DataChannelErrorMessageData(
+      ssrc, err);
+  signaling_thread()->Post(this, MSG_CHANNEL_ERROR, data);
+}
+
+void DataChannel::OnSrtpError(uint32 ssrc, SrtpFilter::Mode mode,
+                              SrtpFilter::Error error) {
+  switch (error) {
+    case SrtpFilter::ERROR_FAIL:
+      OnDataChannelError(ssrc, (mode == SrtpFilter::PROTECT) ?
+                         DataMediaChannel::ERROR_SEND_SRTP_ERROR :
+                         DataMediaChannel::ERROR_RECV_SRTP_ERROR);
+      break;
+    case SrtpFilter::ERROR_AUTH:
+      OnDataChannelError(ssrc, (mode == SrtpFilter::PROTECT) ?
+                         DataMediaChannel::ERROR_SEND_SRTP_AUTH_FAILED :
+                         DataMediaChannel::ERROR_RECV_SRTP_AUTH_FAILED);
+      break;
+    case SrtpFilter::ERROR_REPLAY:
+      // Only receving channel should have this error.
+      ASSERT(mode == SrtpFilter::UNPROTECT);
+      OnDataChannelError(ssrc, DataMediaChannel::ERROR_RECV_SRTP_REPLAY);
+      break;
+    default:
+      break;
+  }
+}
+
+void DataChannel::GetSrtpCiphers(std::vector<std::string>* ciphers) const {
+  GetSupportedDataCryptoSuites(ciphers);
+}
+
+bool DataChannel::ShouldSetupDtlsSrtp() const {
+  return (data_channel_type_ == DCT_RTP);
+}
+
+}  // namespace cricket
diff --git a/talk/session/media/channel.h b/talk/session/media/channel.h
new file mode 100644
index 0000000..ddf7c67
--- /dev/null
+++ b/talk/session/media/channel.h
@@ -0,0 +1,688 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_MEDIA_CHANNEL_H_
+#define TALK_SESSION_MEDIA_CHANNEL_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/asyncudpsocket.h"
+#include "talk/base/criticalsection.h"
+#include "talk/base/network.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/window.h"
+#include "talk/media/base/mediachannel.h"
+#include "talk/media/base/mediaengine.h"
+#include "talk/media/base/screencastid.h"
+#include "talk/media/base/streamparams.h"
+#include "talk/media/base/videocapturer.h"
+#include "talk/p2p/base/session.h"
+#include "talk/p2p/client/socketmonitor.h"
+#include "talk/session/media/audiomonitor.h"
+#include "talk/session/media/mediamonitor.h"
+#include "talk/session/media/mediasession.h"
+#include "talk/session/media/rtcpmuxfilter.h"
+#include "talk/session/media/srtpfilter.h"
+#include "talk/session/media/ssrcmuxfilter.h"
+
+namespace cricket {
+
+struct CryptoParams;
+class MediaContentDescription;
+struct TypingMonitorOptions;
+class TypingMonitor;
+struct ViewRequest;
+
+enum SinkType {
+  SINK_PRE_CRYPTO,  // Sink packets before encryption or after decryption.
+  SINK_POST_CRYPTO  // Sink packets after encryption or before decryption.
+};
+
+// BaseChannel contains logic common to voice and video, including
+// enable/mute, marshaling calls to a worker thread, and
+// connection and media monitors.
+class BaseChannel
+    : public talk_base::MessageHandler, public sigslot::has_slots<>,
+      public MediaChannel::NetworkInterface {
+ public:
+  BaseChannel(talk_base::Thread* thread, MediaEngineInterface* media_engine,
+              MediaChannel* channel, BaseSession* session,
+              const std::string& content_name, bool rtcp);
+  virtual ~BaseChannel();
+  bool Init(TransportChannel* transport_channel,
+            TransportChannel* rtcp_transport_channel);
+
+  talk_base::Thread* worker_thread() const { return worker_thread_; }
+  BaseSession* session() const { return session_; }
+  const std::string& content_name() { return content_name_; }
+  TransportChannel* transport_channel() const {
+    return transport_channel_;
+  }
+  TransportChannel* rtcp_transport_channel() const {
+    return rtcp_transport_channel_;
+  }
+  bool enabled() const { return enabled_; }
+  // Set to true to have the channel optimistically allow data to be sent even
+  // when the channel isn't fully writable.
+  void set_optimistic_data_send(bool value) { optimistic_data_send_ = value; }
+  bool optimistic_data_send() const { return optimistic_data_send_; }
+
+  // This function returns true if we are using SRTP.
+  bool secure() const { return srtp_filter_.IsActive(); }
+  // The following function returns true if we are using
+  // DTLS-based keying. If you turned off SRTP later, however
+  // you could have secure() == false and dtls_secure() == true.
+  bool secure_dtls() const { return dtls_keyed_; }
+  // This function returns true if we require secure channel for call setup.
+  bool secure_required() const { return secure_required_; }
+
+  bool writable() const { return writable_; }
+  bool IsStreamMuted(uint32 ssrc);
+
+  // Channel control
+  bool SetLocalContent(const MediaContentDescription* content,
+                       ContentAction action);
+  bool SetRemoteContent(const MediaContentDescription* content,
+                        ContentAction action);
+  bool SetMaxSendBandwidth(int max_bandwidth);
+
+  bool Enable(bool enable);
+  // Mute sending media on the stream with SSRC |ssrc|
+  // If there is only one sending stream SSRC 0 can be used.
+  bool MuteStream(uint32 ssrc, bool mute);
+
+  // Multiplexing
+  bool AddRecvStream(const StreamParams& sp);
+  bool RemoveRecvStream(uint32 ssrc);
+
+  // Monitoring
+  void StartConnectionMonitor(int cms);
+  void StopConnectionMonitor();
+
+  void set_srtp_signal_silent_time(uint32 silent_time) {
+    srtp_filter_.set_signal_silent_time(silent_time);
+  }
+
+  void set_content_name(const std::string& content_name) {
+    ASSERT(signaling_thread()->IsCurrent());
+    ASSERT(!writable_);
+    if (session_->state() != BaseSession::STATE_INIT) {
+      LOG(LS_ERROR) << "Content name for a channel can be changed only "
+                    << "when BaseSession is in STATE_INIT state.";
+      return;
+    }
+    content_name_ = content_name;
+  }
+
+  template <class T>
+  void RegisterSendSink(T* sink,
+                        void (T::*OnPacket)(const void*, size_t, bool),
+                        SinkType type) {
+    talk_base::CritScope cs(&signal_send_packet_cs_);
+    if (SINK_POST_CRYPTO == type) {
+      SignalSendPacketPostCrypto.disconnect(sink);
+      SignalSendPacketPostCrypto.connect(sink, OnPacket);
+    } else {
+      SignalSendPacketPreCrypto.disconnect(sink);
+      SignalSendPacketPreCrypto.connect(sink, OnPacket);
+    }
+  }
+
+  void UnregisterSendSink(sigslot::has_slots<>* sink,
+                          SinkType type) {
+    talk_base::CritScope cs(&signal_send_packet_cs_);
+    if (SINK_POST_CRYPTO == type) {
+      SignalSendPacketPostCrypto.disconnect(sink);
+    } else {
+      SignalSendPacketPreCrypto.disconnect(sink);
+    }
+  }
+
+  bool HasSendSinks(SinkType type) {
+    talk_base::CritScope cs(&signal_send_packet_cs_);
+    if (SINK_POST_CRYPTO == type) {
+      return !SignalSendPacketPostCrypto.is_empty();
+    } else {
+      return !SignalSendPacketPreCrypto.is_empty();
+    }
+  }
+
+  template <class T>
+  void RegisterRecvSink(T* sink,
+                        void (T::*OnPacket)(const void*, size_t, bool),
+                        SinkType type) {
+    talk_base::CritScope cs(&signal_recv_packet_cs_);
+    if (SINK_POST_CRYPTO == type) {
+      SignalRecvPacketPostCrypto.disconnect(sink);
+      SignalRecvPacketPostCrypto.connect(sink, OnPacket);
+    } else {
+      SignalRecvPacketPreCrypto.disconnect(sink);
+      SignalRecvPacketPreCrypto.connect(sink, OnPacket);
+    }
+  }
+
+  void UnregisterRecvSink(sigslot::has_slots<>* sink,
+                          SinkType type) {
+    talk_base::CritScope cs(&signal_recv_packet_cs_);
+    if (SINK_POST_CRYPTO == type) {
+      SignalRecvPacketPostCrypto.disconnect(sink);
+    } else {
+      SignalRecvPacketPreCrypto.disconnect(sink);
+    }
+  }
+
+  bool HasRecvSinks(SinkType type) {
+    talk_base::CritScope cs(&signal_recv_packet_cs_);
+    if (SINK_POST_CRYPTO == type) {
+      return !SignalRecvPacketPostCrypto.is_empty();
+    } else {
+      return !SignalRecvPacketPreCrypto.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_;
+  }
+
+  // Used for latency measurements.
+  sigslot::signal1<BaseChannel*> SignalFirstPacketReceived;
+
+  // Used to alert UI when the muted status changes, perhaps autonomously.
+  sigslot::repeater2<BaseChannel*, bool> SignalAutoMuted;
+
+  // Made public for easier testing.
+  void SetReadyToSend(TransportChannel* channel, bool ready);
+
+ protected:
+  MediaEngineInterface* media_engine() const { return media_engine_; }
+  virtual MediaChannel* media_channel() const { return media_channel_; }
+  void set_rtcp_transport_channel(TransportChannel* transport);
+  bool was_ever_writable() const { return was_ever_writable_; }
+  void set_local_content_direction(MediaContentDirection direction) {
+    local_content_direction_ = direction;
+  }
+  void set_remote_content_direction(MediaContentDirection direction) {
+    remote_content_direction_ = direction;
+  }
+  bool IsReadyToReceive() const;
+  bool IsReadyToSend() const;
+  talk_base::Thread* signaling_thread() { return session_->signaling_thread(); }
+  SrtpFilter* srtp_filter() { return &srtp_filter_; }
+  bool rtcp() const { return rtcp_; }
+
+  void Send(uint32 id, talk_base::MessageData* pdata = NULL);
+  void Post(uint32 id, talk_base::MessageData* pdata = NULL);
+  void PostDelayed(int cmsDelay, uint32 id = 0,
+                   talk_base::MessageData* pdata = NULL);
+  void Clear(uint32 id = talk_base::MQID_ANY,
+             talk_base::MessageList* removed = NULL);
+  void FlushRtcpMessages();
+
+  // NetworkInterface implementation, called by MediaEngine
+  virtual bool SendPacket(talk_base::Buffer* packet);
+  virtual bool SendRtcp(talk_base::Buffer* packet);
+  virtual int SetOption(SocketType type, talk_base::Socket::Option o, int val);
+
+  // From TransportChannel
+  void OnWritableState(TransportChannel* channel);
+  virtual void OnChannelRead(TransportChannel* channel, const char* data,
+                             size_t len, int flags);
+  void OnReadyToSend(TransportChannel* channel);
+
+  bool PacketIsRtcp(const TransportChannel* channel, const char* data,
+                    size_t len);
+  bool SendPacket(bool rtcp, talk_base::Buffer* packet);
+  virtual bool WantsPacket(bool rtcp, talk_base::Buffer* packet);
+  void HandlePacket(bool rtcp, talk_base::Buffer* packet);
+
+  // Apply the new local/remote session description.
+  void OnNewLocalDescription(BaseSession* session, ContentAction action);
+  void OnNewRemoteDescription(BaseSession* session, ContentAction action);
+
+  void EnableMedia_w();
+  void DisableMedia_w();
+  virtual bool MuteStream_w(uint32 ssrc, bool mute);
+  bool IsStreamMuted_w(uint32 ssrc);
+  void ChannelWritable_w();
+  void ChannelNotWritable_w();
+  bool AddRecvStream_w(const StreamParams& sp);
+  bool RemoveRecvStream_w(uint32 ssrc);
+  virtual bool ShouldSetupDtlsSrtp() const;
+  // Do the DTLS key expansion and impose it on the SRTP/SRTCP filters.
+  // |rtcp_channel| indicates whether to set up the RTP or RTCP filter.
+  bool SetupDtlsSrtp(bool rtcp_channel);
+  // Set the DTLS-SRTP cipher policy on this channel as appropriate.
+  bool SetDtlsSrtpCiphers(TransportChannel *tc, bool rtcp);
+
+  virtual void ChangeState() = 0;
+
+  // Gets the content info appropriate to the channel (audio or video).
+  virtual const ContentInfo* 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;
+
+  bool SetSrtp_w(const std::vector<CryptoParams>& params, ContentAction action,
+                 ContentSource src);
+  bool SetRtcpMux_w(bool enable, ContentAction action, ContentSource src);
+
+  virtual bool SetMaxSendBandwidth_w(int max_bandwidth);
+
+  // From MessageHandler
+  virtual void OnMessage(talk_base::Message* pmsg);
+
+  // Handled in derived classes
+  // Get the SRTP ciphers to use for RTP media
+  virtual void GetSrtpCiphers(std::vector<std::string>* ciphers) const = 0;
+  virtual void OnConnectionMonitorUpdate(SocketMonitor* monitor,
+      const std::vector<ConnectionInfo>& infos) = 0;
+
+ private:
+  sigslot::signal3<const void*, size_t, bool> SignalSendPacketPreCrypto;
+  sigslot::signal3<const void*, size_t, bool> SignalSendPacketPostCrypto;
+  sigslot::signal3<const void*, size_t, bool> SignalRecvPacketPreCrypto;
+  sigslot::signal3<const void*, size_t, bool> SignalRecvPacketPostCrypto;
+  talk_base::CriticalSection signal_send_packet_cs_;
+  talk_base::CriticalSection signal_recv_packet_cs_;
+
+  talk_base::Thread* worker_thread_;
+  MediaEngineInterface* media_engine_;
+  BaseSession* session_;
+  MediaChannel* media_channel_;
+  std::vector<StreamParams> local_streams_;
+  std::vector<StreamParams> remote_streams_;
+
+  std::string content_name_;
+  bool rtcp_;
+  TransportChannel* transport_channel_;
+  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_;
+  bool rtp_ready_to_send_;
+  bool rtcp_ready_to_send_;
+  bool optimistic_data_send_;
+  bool was_ever_writable_;
+  MediaContentDirection local_content_direction_;
+  MediaContentDirection remote_content_direction_;
+  std::set<uint32> muted_streams_;
+  bool has_received_packet_;
+  bool dtls_keyed_;
+  bool secure_required_;
+};
+
+// VoiceChannel is a specialization that adds support for early media, DTMF,
+// and input/output level monitoring.
+class VoiceChannel : public BaseChannel {
+ public:
+  VoiceChannel(talk_base::Thread* thread, MediaEngineInterface* media_engine,
+               VoiceMediaChannel* channel, BaseSession* session,
+               const std::string& content_name, bool rtcp);
+  ~VoiceChannel();
+  bool Init();
+  bool SetRenderer(uint32 ssrc, AudioRenderer* renderer);
+
+  // downcasts a MediaChannel
+  virtual VoiceMediaChannel* media_channel() const {
+    return static_cast<VoiceMediaChannel*>(BaseChannel::media_channel());
+  }
+
+  bool SetRingbackTone(const void* buf, int len);
+  void SetEarlyMedia(bool enable);
+  // This signal is emitted when we have gone a period of time without
+  // receiving early media. When received, a UI should start playing its
+  // own ringing sound
+  sigslot::signal1<VoiceChannel*> SignalEarlyMediaTimeout;
+
+  bool PlayRingbackTone(uint32 ssrc, bool play, bool loop);
+  // TODO(ronghuawu): Replace PressDTMF with InsertDtmf.
+  bool PressDTMF(int digit, bool playout);
+  // Returns if the telephone-event has been negotiated.
+  bool CanInsertDtmf();
+  // Send and/or play a DTMF |event| according to the |flags|.
+  // The DTMF out-of-band signal will be used on sending.
+  // The |ssrc| should be either 0 or a valid send stream ssrc.
+  // The valid value for the |event| are -2 to 15.
+  // kDtmfReset(-2) is used to reset the DTMF.
+  // kDtmfDelay(-1) is used to insert a delay to the end of the DTMF queue.
+  // 0 to 15 which corresponding to DTMF event 0-9, *, #, A-D.
+  bool InsertDtmf(uint32 ssrc, int event_code, int duration, int flags);
+  bool SetOutputScaling(uint32 ssrc, double left, double right);
+  // Get statistics about the current media session.
+  bool GetStats(VoiceMediaInfo* stats);
+
+  // Monitoring functions
+  sigslot::signal2<VoiceChannel*, const std::vector<ConnectionInfo>&>
+      SignalConnectionMonitor;
+
+  void StartMediaMonitor(int cms);
+  void StopMediaMonitor();
+  sigslot::signal2<VoiceChannel*, const VoiceMediaInfo&> SignalMediaMonitor;
+
+  void StartAudioMonitor(int cms);
+  void StopAudioMonitor();
+  bool IsAudioMonitorRunning() const;
+  sigslot::signal2<VoiceChannel*, const AudioInfo&> SignalAudioMonitor;
+
+  void StartTypingMonitor(const TypingMonitorOptions& settings);
+  void StopTypingMonitor();
+  bool IsTypingMonitorRunning() const;
+
+  // Overrides BaseChannel::MuteStream_w.
+  virtual bool MuteStream_w(uint32 ssrc, bool mute);
+
+  int GetInputLevel_w();
+  int GetOutputLevel_w();
+  void GetActiveStreams_w(AudioInfo::StreamList* actives);
+
+  // Signal errors from VoiceMediaChannel.  Arguments are:
+  //     ssrc(uint32), and error(VoiceMediaChannel::Error).
+  sigslot::signal3<VoiceChannel*, uint32, VoiceMediaChannel::Error>
+      SignalMediaError;
+
+  // Configuration and setting.
+  bool SetChannelOptions(const AudioOptions& options);
+
+ private:
+  // overrides from BaseChannel
+  virtual void OnChannelRead(TransportChannel* channel,
+                             const char* data, size_t len, int flags);
+  virtual void ChangeState();
+  virtual const ContentInfo* GetFirstContent(const SessionDescription* sdesc);
+  virtual bool SetLocalContent_w(const MediaContentDescription* content,
+                                 ContentAction action);
+  virtual bool SetRemoteContent_w(const MediaContentDescription* content,
+                                  ContentAction action);
+  bool SetRingbackTone_w(const void* buf, int len);
+  bool PlayRingbackTone_w(uint32 ssrc, bool play, bool loop);
+  void HandleEarlyMediaTimeout();
+  bool CanInsertDtmf_w();
+  bool InsertDtmf_w(uint32 ssrc, int event, int duration, int flags);
+  bool SetOutputScaling_w(uint32 ssrc, double left, double right);
+  bool GetStats_w(VoiceMediaInfo* stats);
+
+  virtual void OnMessage(talk_base::Message* pmsg);
+  virtual void GetSrtpCiphers(std::vector<std::string>* ciphers) const;
+  virtual void OnConnectionMonitorUpdate(
+      SocketMonitor* monitor, const std::vector<ConnectionInfo>& infos);
+  virtual void OnMediaMonitorUpdate(
+      VoiceMediaChannel* media_channel, const VoiceMediaInfo& info);
+  void OnAudioMonitorUpdate(AudioMonitor* monitor, const AudioInfo& info);
+  void OnVoiceChannelError(uint32 ssrc, VoiceMediaChannel::Error error);
+  void SendLastMediaError();
+  void OnSrtpError(uint32 ssrc, SrtpFilter::Mode mode, SrtpFilter::Error error);
+  // Configuration and setting.
+  bool SetChannelOptions_w(const AudioOptions& options);
+  bool SetRenderer_w(uint32 ssrc, AudioRenderer* renderer);
+
+  static const int kEarlyMediaTimeout = 1000;
+  bool received_media_;
+  talk_base::scoped_ptr<VoiceMediaMonitor> media_monitor_;
+  talk_base::scoped_ptr<AudioMonitor> audio_monitor_;
+  talk_base::scoped_ptr<TypingMonitor> typing_monitor_;
+};
+
+// VideoChannel is a specialization for video.
+class VideoChannel : public BaseChannel {
+ public:
+  // Make screen capturer virtual so that it can be overriden in testing.
+  // E.g. used to test that window events are triggered correctly.
+  class ScreenCapturerFactory {
+   public:
+    virtual VideoCapturer* CreateScreenCapturer(const ScreencastId& window) = 0;
+    virtual ~ScreenCapturerFactory() {}
+  };
+
+  VideoChannel(talk_base::Thread* thread, MediaEngineInterface* media_engine,
+               VideoMediaChannel* channel, BaseSession* session,
+               const std::string& content_name, bool rtcp,
+               VoiceChannel* voice_channel);
+  ~VideoChannel();
+  bool Init();
+
+  bool SetRenderer(uint32 ssrc, VideoRenderer* renderer);
+  bool ApplyViewRequest(const ViewRequest& request);
+
+  // TODO(pthatcher): Refactor to use a "capture id" instead of an
+  // ssrc here as the "key".
+  VideoCapturer* AddScreencast(uint32 ssrc, const ScreencastId& id);
+  VideoCapturer* GetScreencastCapturer(uint32 ssrc);
+  bool SetCapturer(uint32 ssrc, VideoCapturer* capturer);
+  bool RemoveScreencast(uint32 ssrc);
+  // True if we've added a screencast.  Doesn't matter if the capturer
+  // has been started or not.
+  bool IsScreencasting();
+  int ScreencastFps(uint32 ssrc);
+  // Get statistics about the current media session.
+  bool GetStats(VideoMediaInfo* stats);
+
+  sigslot::signal2<VideoChannel*, const std::vector<ConnectionInfo>&>
+      SignalConnectionMonitor;
+
+  void StartMediaMonitor(int cms);
+  void StopMediaMonitor();
+  sigslot::signal2<VideoChannel*, const VideoMediaInfo&> SignalMediaMonitor;
+  sigslot::signal2<uint32, talk_base::WindowEvent> SignalScreencastWindowEvent;
+
+  bool SendIntraFrame();
+  bool RequestIntraFrame();
+  sigslot::signal3<VideoChannel*, uint32, VideoMediaChannel::Error>
+      SignalMediaError;
+
+  void SetScreenCaptureFactory(
+      ScreenCapturerFactory* screencapture_factory);
+
+  // Configuration and setting.
+  bool SetChannelOptions(const VideoOptions& options);
+
+ protected:
+  // downcasts a MediaChannel
+  virtual VideoMediaChannel* media_channel() const {
+    return static_cast<VideoMediaChannel*>(BaseChannel::media_channel());
+  }
+
+ private:
+  typedef std::map<uint32, VideoCapturer*> ScreencastMap;
+
+  // overrides from BaseChannel
+  virtual void ChangeState();
+  virtual const ContentInfo* GetFirstContent(const SessionDescription* sdesc);
+  virtual bool SetLocalContent_w(const MediaContentDescription* content,
+                                 ContentAction action);
+  virtual bool SetRemoteContent_w(const MediaContentDescription* content,
+                                  ContentAction action);
+  void SendIntraFrame_w() {
+    media_channel()->SendIntraFrame();
+  }
+  void RequestIntraFrame_w() {
+    media_channel()->RequestIntraFrame();
+  }
+
+  bool ApplyViewRequest_w(const ViewRequest& request);
+  void SetRenderer_w(uint32 ssrc, VideoRenderer* renderer);
+
+  VideoCapturer* AddScreencast_w(uint32 ssrc, const ScreencastId& id);
+  VideoCapturer* GetScreencastCapturer_w(uint32 ssrc);
+  bool SetCapturer_w(uint32 ssrc, VideoCapturer* capturer);
+  bool RemoveScreencast_w(uint32 ssrc);
+  void OnScreencastWindowEvent_s(uint32 ssrc, talk_base::WindowEvent we);
+  bool IsScreencasting_w() const;
+  int ScreencastFps_w(uint32 ssrc) const;
+  void SetScreenCaptureFactory_w(
+      ScreenCapturerFactory* screencapture_factory);
+  bool GetStats_w(VideoMediaInfo* stats);
+
+  virtual void OnMessage(talk_base::Message* pmsg);
+  virtual void GetSrtpCiphers(std::vector<std::string>* ciphers) const;
+  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);
+  virtual void OnStateChange(VideoCapturer* capturer, CaptureState ev);
+  bool GetLocalSsrc(const VideoCapturer* capturer, uint32* ssrc);
+
+  void OnVideoChannelError(uint32 ssrc, VideoMediaChannel::Error error);
+  void OnSrtpError(uint32 ssrc, SrtpFilter::Mode mode, SrtpFilter::Error error);
+  // Configuration and setting.
+  bool SetChannelOptions_w(const VideoOptions& options);
+
+  VoiceChannel* voice_channel_;
+  VideoRenderer* renderer_;
+  talk_base::scoped_ptr<ScreenCapturerFactory> screencapture_factory_;
+  ScreencastMap screencast_capturers_;
+  talk_base::scoped_ptr<VideoMediaMonitor> media_monitor_;
+
+  talk_base::WindowEvent previous_we_;
+};
+
+// DataChannel is a specialization for data.
+class DataChannel : public BaseChannel {
+ public:
+  DataChannel(talk_base::Thread* thread,
+              DataMediaChannel* media_channel,
+              BaseSession* session,
+              const std::string& content_name,
+              bool rtcp);
+  ~DataChannel();
+  bool Init();
+
+  // downcasts a MediaChannel
+  virtual DataMediaChannel* media_channel() const {
+    return static_cast<DataMediaChannel*>(BaseChannel::media_channel());
+  }
+
+  bool SendData(const SendDataParams& params,
+                const talk_base::Buffer& payload,
+                SendDataResult* result);
+
+  void StartMediaMonitor(int cms);
+  void StopMediaMonitor();
+
+  sigslot::signal2<DataChannel*, const DataMediaInfo&> SignalMediaMonitor;
+  sigslot::signal2<DataChannel*, const std::vector<ConnectionInfo>&>
+      SignalConnectionMonitor;
+  sigslot::signal3<DataChannel*, uint32, DataMediaChannel::Error>
+      SignalMediaError;
+  sigslot::signal3<DataChannel*,
+                   const ReceiveDataParams&,
+                   const talk_base::Buffer&>
+      SignalDataReceived;
+  // Signal for notifying when the channel becomes ready to send data.
+  // That occurs when the channel is enabled, the transport is writable and
+  // both local and remote descriptions are set.
+  // TODO(perkj): Signal this per SSRC stream.
+  sigslot::signal1<bool> SignalReadyToSendData;
+
+ private:
+  struct SendDataMessageData : public talk_base::MessageData {
+    SendDataMessageData(const SendDataParams& params,
+                        const talk_base::Buffer* payload,
+                        SendDataResult* result)
+        : params(params),
+          payload(payload),
+          result(result),
+          succeeded(false) {
+    }
+
+    const SendDataParams& params;
+    const talk_base::Buffer* payload;
+    SendDataResult* result;
+    bool succeeded;
+  };
+
+  struct DataReceivedMessageData : public talk_base::MessageData {
+    // We copy the data because the data will become invalid after we
+    // handle DataMediaChannel::SignalDataReceived but before we fire
+    // SignalDataReceived.
+    DataReceivedMessageData(
+        const ReceiveDataParams& params, const char* data, size_t len)
+        : params(params),
+          payload(data, len) {
+    }
+    const ReceiveDataParams params;
+    const talk_base::Buffer payload;
+  };
+
+  // overrides from BaseChannel
+  virtual const ContentInfo* GetFirstContent(const SessionDescription* sdesc);
+  // If data_channel_type_ is DCT_NONE, set it.  Otherwise, check that
+  // it's the same as what was set previously.  Returns false if it's
+  // set to one type one type and changed to another type later.
+  bool SetDataChannelType(DataChannelType new_data_channel_type);
+  // Same as SetDataChannelType, but extracts the type from the
+  // DataContentDescription.
+  bool SetDataChannelTypeFromContent(const DataContentDescription* content);
+  virtual bool SetMaxSendBandwidth_w(int max_bandwidth);
+  virtual bool SetLocalContent_w(const MediaContentDescription* content,
+                                 ContentAction action);
+  virtual bool SetRemoteContent_w(const MediaContentDescription* content,
+                                  ContentAction action);
+  virtual void ChangeState();
+  virtual bool WantsPacket(bool rtcp, talk_base::Buffer* packet);
+
+  virtual void OnMessage(talk_base::Message* pmsg);
+  virtual void GetSrtpCiphers(std::vector<std::string>* ciphers) const;
+  virtual void OnConnectionMonitorUpdate(
+      SocketMonitor* monitor, const std::vector<ConnectionInfo>& infos);
+  virtual void OnMediaMonitorUpdate(
+      DataMediaChannel* media_channel, const DataMediaInfo& info);
+  virtual bool ShouldSetupDtlsSrtp() const;
+  void OnDataReceived(
+      const ReceiveDataParams& params, const char* data, size_t len);
+  void OnDataChannelError(uint32 ssrc, DataMediaChannel::Error error);
+  void OnSrtpError(uint32 ssrc, SrtpFilter::Mode mode, SrtpFilter::Error error);
+
+  talk_base::scoped_ptr<DataMediaMonitor> media_monitor_;
+  // TODO(pthatcher): Make a separate SctpDataChannel and
+  // RtpDataChannel instead of using this.
+  DataChannelType data_channel_type_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_MEDIA_CHANNEL_H_
diff --git a/talk/session/media/channel_unittest.cc b/talk/session/media/channel_unittest.cc
new file mode 100644
index 0000000..8c02505
--- /dev/null
+++ b/talk/session/media/channel_unittest.cc
@@ -0,0 +1,2895 @@
+// 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/base/ssladapter.h"
+#include "talk/base/sslidentity.h"
+#include "talk/base/window.h"
+#include "talk/media/base/fakemediaengine.h"
+#include "talk/media/base/fakertp.h"
+#include "talk/media/base/fakevideocapturer.h"
+#include "talk/media/base/mediachannel.h"
+#include "talk/media/base/rtpdump.h"
+#include "talk/media/base/screencastid.h"
+#include "talk/media/base/testutils.h"
+#include "talk/p2p/base/fakesession.h"
+#include "talk/session/media/channel.h"
+#include "talk/session/media/mediamessages.h"
+#include "talk/session/media/mediarecorder.h"
+#include "talk/session/media/mediasessionclient.h"
+#include "talk/session/media/typingmonitor.h"
+
+#define MAYBE_SKIP_TEST(feature)                    \
+  if (!(talk_base::SSLStreamAdapter::feature())) {  \
+    LOG(LS_INFO) << "Feature disabled... skipping"; \
+    return;                                         \
+  }
+
+using cricket::CA_OFFER;
+using cricket::CA_PRANSWER;
+using cricket::CA_ANSWER;
+using cricket::CA_UPDATE;
+using cricket::FakeVoiceMediaChannel;
+using cricket::kDtmfDelay;
+using cricket::kDtmfReset;
+using cricket::ScreencastId;
+using cricket::StreamParams;
+using cricket::TransportChannel;
+using talk_base::WindowId;
+
+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 cricket::DataCodec kGoogleDataCodec(101, "google-data", 0);
+static const uint32 kSsrc1 = 0x1111;
+static const uint32 kSsrc2 = 0x2222;
+static const uint32 kSsrc3 = 0x3333;
+static const char kCName[] = "a@b.com";
+
+template<class ChannelT,
+         class MediaChannelT,
+         class ContentT,
+         class CodecT,
+         class MediaInfoT>
+class Traits {
+ public:
+  typedef ChannelT Channel;
+  typedef MediaChannelT MediaChannel;
+  typedef ContentT Content;
+  typedef CodecT Codec;
+  typedef MediaInfoT MediaInfo;
+};
+
+class FakeScreenCaptureFactory
+    : public cricket::VideoChannel::ScreenCapturerFactory,
+      public sigslot::has_slots<> {
+ public:
+  FakeScreenCaptureFactory()
+      : window_capturer_(NULL),
+        capture_state_(cricket::CS_STOPPED) {}
+
+  virtual cricket::VideoCapturer* CreateScreenCapturer(
+      const ScreencastId& window) {
+    if (window_capturer_ != NULL) {
+      // Class is only designed to handle one fake screencapturer.
+      ADD_FAILURE();
+      return NULL;
+    }
+    window_capturer_ = new cricket::FakeVideoCapturer;
+    window_capturer_->SignalDestroyed.connect(
+        this,
+        &FakeScreenCaptureFactory::OnWindowCapturerDestroyed);
+    window_capturer_->SignalStateChange.connect(
+        this,
+        &FakeScreenCaptureFactory::OnStateChange);
+    return window_capturer_;
+  }
+
+  cricket::FakeVideoCapturer* window_capturer() { return window_capturer_; }
+
+  cricket::CaptureState capture_state() { return capture_state_; }
+
+ private:
+  void OnWindowCapturerDestroyed(cricket::FakeVideoCapturer* capturer) {
+    if (capturer == window_capturer_) {
+      window_capturer_ = NULL;
+    }
+  }
+  void OnStateChange(cricket::VideoCapturer*, cricket::CaptureState state) {
+    capture_state_ = state;
+  }
+
+  cricket::FakeVideoCapturer* window_capturer_;
+  cricket::CaptureState capture_state_;
+};
+
+// 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;
+
+class VoiceTraits : public Traits<cricket::VoiceChannel,
+                                  cricket::FakeVoiceMediaChannel,
+                                  cricket::AudioContentDescription,
+                                  cricket::AudioCodec,
+                                  cricket::VoiceMediaInfo> {
+};
+
+class VideoTraits : public Traits<cricket::VideoChannel,
+                                  cricket::FakeVideoMediaChannel,
+                                  cricket::VideoContentDescription,
+                                  cricket::VideoCodec,
+                                  cricket::VideoMediaInfo> {
+};
+
+class DataTraits : public Traits<cricket::DataChannel,
+                                 cricket::FakeDataMediaChannel,
+                                 cricket::DataContentDescription,
+                                 cricket::DataCodec,
+                                 cricket::DataMediaInfo> {
+};
+
+
+talk_base::StreamInterface* Open(const std::string& path) {
+  return talk_base::Filesystem::OpenFile(
+      talk_base::Pathname(path), "wb");
+}
+
+// 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,
+               DTLS = 0x10 };
+
+  ChannelTest(const uint8* rtp_data, int rtp_len,
+              const uint8* rtcp_data, int rtcp_len)
+      : session1_(true),
+        session2_(false),
+        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_(),
+        mute_callback_recved_(false),
+        mute_callback_value_(false),
+        ssrc_(0),
+        error_(T::MediaChannel::ERROR_NONE) {
+  }
+
+  static void SetUpTestCase() {
+    talk_base::InitializeSSL();
+  }
+
+  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);
+    channel1_->SignalAutoMuted.connect(
+        this, &ChannelTest<T>::OnMediaMuted);
+    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_);
+
+    if (flags1 & DTLS) {
+      identity1_.reset(talk_base::SSLIdentity::Generate("session1"));
+      session1_.set_ssl_identity(identity1_.get());
+    }
+    if (flags2 & DTLS) {
+      identity2_.reset(talk_base::SSLIdentity::Generate("session2"));
+      session2_.set_ssl_identity(identity2_.get());
+    }
+
+    // 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) {
+        session1_.Connect(&session2_);
+
+        result = channel2_->SetLocalContent(&local_media_content2_, CA_ANSWER);
+      }
+    }
+    return result;
+  }
+
+  bool SendAccept() {
+    channel2_->Enable(true);
+    return channel1_->SetRemoteContent(&remote_media_content2_, CA_ANSWER);
+  }
+
+  bool SendOffer() {
+    bool result = channel1_->SetLocalContent(&local_media_content1_, CA_OFFER);
+    if (result) {
+      channel1_->Enable(true);
+      result = channel2_->SetRemoteContent(&remote_media_content1_, CA_OFFER);
+    }
+    return result;
+  }
+
+  bool SendProvisionalAnswer() {
+    bool result = channel2_->SetLocalContent(&local_media_content2_,
+                                             CA_PRANSWER);
+    if (result) {
+      channel2_->Enable(true);
+      result = channel1_->SetRemoteContent(&remote_media_content2_,
+                                           CA_PRANSWER);
+      session1_.Connect(&session2_);
+    }
+    return result;
+  }
+
+  bool SendFinalAnswer() {
+    bool result = channel2_->SetLocalContent(&local_media_content2_, CA_ANSWER);
+    if (result)
+      result = channel1_->SetRemoteContent(&remote_media_content2_, CA_ANSWER);
+    return result;
+  }
+
+  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, int sequence_number) {
+    std::string data(CreateRtpData(ssrc, sequence_number));
+    return media_channel1_->SendRtp(data.c_str(), data.size());
+  }
+  bool SendCustomRtp2(uint32 ssrc, int sequence_number) {
+    std::string data(CreateRtpData(ssrc, sequence_number));
+    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, int sequence_number) {
+    std::string data(CreateRtpData(ssrc, sequence_number));
+    return media_channel1_->CheckRtp(data.c_str(), data.size());
+  }
+  bool CheckCustomRtp2(uint32 ssrc, int sequence_number) {
+    std::string data(CreateRtpData(ssrc, sequence_number));
+    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, int sequence_number) {
+    std::string data(rtp_packet_);
+    // Set SSRC in the rtp packet copy.
+    talk_base::SetBE32(const_cast<char*>(data.c_str()) + 8, ssrc);
+    talk_base::SetBE16(const_cast<char*>(data.c_str()) + 2, sequence_number);
+    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
+  }
+
+  void SetOptimisticDataSend(bool optimistic_data_send) {
+    channel1_->set_optimistic_data_send(optimistic_data_send);
+    channel2_->set_optimistic_data_send(optimistic_data_send);
+  }
+
+  // Creates a cricket::SessionDescription with one MediaContent and one stream.
+  // kPcmuCodec is used as audio codec and kH264Codec is used as video codec.
+  cricket::SessionDescription* CreateSessionDescriptionWithStream(uint32 ssrc) {
+     typename T::Content content;
+     cricket::SessionDescription* sdesc = new cricket::SessionDescription();
+     CreateContent(SECURE, kPcmuCodec, kH264Codec, &content);
+     AddLegacyStreamInContent(ssrc, 0, &content);
+     sdesc->AddContent("DUMMY_CONTENT_NAME",
+                       cricket::NS_JINGLE_RTP, content.Copy());
+     return sdesc;
+  }
+
+  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 OnMediaMuted(cricket::BaseChannel* channel, bool muted) {
+    mute_callback_recved_ = true;
+    mute_callback_value_ = muted;
+  }
+
+  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 SetLocalContent and SetRemoteContent properly set RTCP
+  // mux when a provisional answer is received.
+  void TestSetContentsRtcpMuxWithPrAnswer() {
+    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);
+    content.set_rtcp_mux(true);
+    EXPECT_TRUE(channel1_->SetLocalContent(&content, CA_OFFER));
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content, CA_PRANSWER));
+    EXPECT_TRUE(channel1_->rtcp_transport_channel() != NULL);
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content, CA_ANSWER));
+    // Both sides agree on mux. Should no longer be a separate RTCP channel.
+    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_PRANSWER));
+    EXPECT_TRUE(channel2_->SetRemoteContent(&content, CA_ANSWER));
+    EXPECT_TRUE(channel2_->rtcp_transport_channel() != NULL);
+  }
+
+  // Test that SetLocalContent and SetRemoteContent properly set
+  // video options to the media channel.
+  void TestSetContentsVideoOptions() {
+    CreateChannels(0, 0);
+    typename T::Content content;
+    CreateContent(0, kPcmuCodec, kH264Codec, &content);
+    content.set_buffered_mode_latency(101);
+    EXPECT_TRUE(channel1_->SetLocalContent(&content, CA_OFFER));
+    EXPECT_EQ(0U, media_channel1_->codecs().size());
+    cricket::VideoOptions options;
+    ASSERT_TRUE(media_channel1_->GetOptions(&options));
+    int latency = 0;
+    EXPECT_TRUE(options.buffered_mode_latency.Get(&latency));
+    EXPECT_EQ(101, latency);
+    content.set_buffered_mode_latency(102);
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content, CA_ANSWER));
+    ASSERT_EQ(1U, media_channel1_->codecs().size());
+    EXPECT_TRUE(CodecMatches(content.codecs()[0],
+                             media_channel1_->codecs()[0]));
+    ASSERT_TRUE(media_channel1_->GetOptions(&options));
+    EXPECT_TRUE(options.buffered_mode_latency.Get(&latency));
+    EXPECT_EQ(102, latency);
+  }
+
+  // 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.groupid = "group1";
+    stream1.id = "stream1";
+    stream1.ssrcs.push_back(kSsrc1);
+    stream1.cname = "stream1_cname";
+
+    cricket::StreamParams stream2;
+    stream2.groupid = "group2";
+    stream2.id = "stream2";
+    stream2.ssrcs.push_back(kSsrc2);
+    stream2.cname = "stream2_cname";
+
+    cricket::StreamParams stream3;
+    stream3.groupid = "group3";
+    stream3.id = "stream3";
+    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.id = "Stream1";
+    stream1.groupid = "1";
+    stream1.ssrcs.push_back(kSsrc1);
+    stream1.cname = "stream1_cname";
+
+    cricket::StreamParams stream2;
+    stream2.id = "Stream2";
+    stream2.groupid = "2";
+    stream2.ssrcs.push_back(kSsrc2);
+    stream2.cname = "stream2_cname";
+
+    cricket::StreamParams stream3;
+    stream3.id = "Stream3";
+    stream3.groupid = "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.groupid = "group1";
+    stream1.id = "stream1";
+    stream1.ssrcs.push_back(kSsrc1);
+    stream1.cname = "stream1_cname";
+
+    cricket::StreamParams stream2;
+    stream2.groupid = "group1";
+    stream2.id = "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, 0));
+    EXPECT_TRUE(CheckCustomRtp2(kSsrc1, 0));
+
+    // 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, 0));
+    EXPECT_TRUE(CheckCustomRtp1(kSsrc2, 0));
+  }
+
+  // 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());
+  }
+
+  void TestMuteStream() {
+    CreateChannels(0, 0);
+    // Test that we can Mute the default channel even though the sending SSRC is
+    // unknown.
+    EXPECT_FALSE(media_channel1_->IsStreamMuted(0));
+    EXPECT_TRUE(channel1_->MuteStream(0, true));
+    EXPECT_TRUE(media_channel1_->IsStreamMuted(0));
+    EXPECT_TRUE(channel1_->MuteStream(0, false));
+    EXPECT_FALSE(media_channel1_->IsStreamMuted(0));
+
+    // Test that we can not mute an unknown SSRC.
+    EXPECT_FALSE(channel1_->MuteStream(kSsrc1, true));
+
+    SendInitiate();
+    // After the local session description has been set, we can mute a stream
+    // with its SSRC.
+    EXPECT_TRUE(channel1_->MuteStream(kSsrc1, true));
+    EXPECT_TRUE(media_channel1_->IsStreamMuted(kSsrc1));
+    EXPECT_TRUE(channel1_->MuteStream(kSsrc1, false));
+    EXPECT_FALSE(media_channel1_->IsStreamMuted(kSsrc1));
+  }
+
+  // Test that changing the MediaContentDirection in the local and remote
+  // session description start playout and sending at the right time.
+  void TestMediaContentDirection() {
+    CreateChannels(0, 0);
+    typename T::Content content1;
+    CreateContent(0, kPcmuCodec, kH264Codec, &content1);
+    typename T::Content content2;
+    CreateContent(0, kPcmuCodec, kH264Codec, &content2);
+    // Set |content2| to be InActive.
+    content2.set_direction(cricket::MD_INACTIVE);
+
+    EXPECT_TRUE(channel1_->Enable(true));
+    EXPECT_TRUE(channel2_->Enable(true));
+    EXPECT_FALSE(media_channel1_->playout());
+    EXPECT_FALSE(media_channel1_->sending());
+    EXPECT_FALSE(media_channel2_->playout());
+    EXPECT_FALSE(media_channel2_->sending());
+
+    EXPECT_TRUE(channel1_->SetLocalContent(&content1, CA_OFFER));
+    EXPECT_TRUE(channel2_->SetRemoteContent(&content1, CA_OFFER));
+    EXPECT_TRUE(channel2_->SetLocalContent(&content2, CA_PRANSWER));
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content2, CA_PRANSWER));
+    session1_.Connect(&session2_);
+
+    EXPECT_TRUE(media_channel1_->playout());
+    EXPECT_FALSE(media_channel1_->sending());  // remote InActive
+    EXPECT_FALSE(media_channel2_->playout());  // local InActive
+    EXPECT_FALSE(media_channel2_->sending());  // local InActive
+
+    // Update |content2| to be RecvOnly.
+    content2.set_direction(cricket::MD_RECVONLY);
+    EXPECT_TRUE(channel2_->SetLocalContent(&content2, CA_PRANSWER));
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content2, CA_PRANSWER));
+
+    EXPECT_TRUE(media_channel1_->playout());
+    EXPECT_TRUE(media_channel1_->sending());
+    EXPECT_TRUE(media_channel2_->playout());  // local RecvOnly
+    EXPECT_FALSE(media_channel2_->sending());  // local RecvOnly
+
+    // Update |content2| to be SendRecv.
+    content2.set_direction(cricket::MD_SENDRECV);
+    EXPECT_TRUE(channel2_->SetLocalContent(&content2, CA_ANSWER));
+    EXPECT_TRUE(channel1_->SetRemoteContent(&content2, CA_ANSWER));
+
+    EXPECT_TRUE(media_channel1_->playout());
+    EXPECT_TRUE(media_channel1_->sending());
+    EXPECT_TRUE(media_channel2_->playout());
+    EXPECT_TRUE(media_channel2_->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.
+  // You can pass in DTLS and/or RTCP_MUX as flags.
+  void SendSrtpToSrtp(int flags1_in = 0, int flags2_in = 0) {
+    ASSERT((flags1_in & ~(RTCP_MUX | DTLS)) == 0);
+    ASSERT((flags2_in & ~(RTCP_MUX | DTLS)) == 0);
+
+    int flags1 = RTCP | SECURE | flags1_in;
+    int flags2 = RTCP | SECURE | flags2_in;
+    bool dtls1 = !!(flags1_in & DTLS);
+    bool dtls2 = !!(flags2_in & DTLS);
+    CreateChannels(flags1, flags2);
+    EXPECT_FALSE(channel1_->secure());
+    EXPECT_FALSE(channel2_->secure());
+    EXPECT_TRUE(SendInitiate());
+    EXPECT_TRUE_WAIT(channel1_->writable(), kEventTimeout);
+    EXPECT_TRUE_WAIT(channel2_->writable(), kEventTimeout);
+    EXPECT_TRUE(SendAccept());
+    EXPECT_TRUE(channel1_->secure());
+    EXPECT_TRUE(channel2_->secure());
+    EXPECT_EQ(dtls1 && dtls2, channel1_->secure_dtls());
+    EXPECT_EQ(dtls1 && dtls2, channel2_->secure_dtls());
+    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 can send and receive early media when a provisional answer is
+  // sent and received. The test uses SRTP, RTCP mux and SSRC mux.
+  void SendEarlyMediaUsingRtcpMuxSrtp() {
+      int sequence_number1_1 = 0, sequence_number2_2 = 0;
+
+      CreateChannels(SSRC_MUX | RTCP | RTCP_MUX | SECURE,
+                     SSRC_MUX | RTCP | RTCP_MUX | SECURE);
+      EXPECT_TRUE(SendOffer());
+      EXPECT_TRUE(SendProvisionalAnswer());
+      EXPECT_TRUE(channel1_->secure());
+      EXPECT_TRUE(channel2_->secure());
+      EXPECT_EQ(2U, GetTransport1()->channels().size());
+      EXPECT_EQ(2U, GetTransport2()->channels().size());
+      EXPECT_TRUE(SendCustomRtcp1(kSsrc1));
+      EXPECT_TRUE(CheckCustomRtcp2(kSsrc1));
+      EXPECT_TRUE(SendCustomRtp1(kSsrc1, ++sequence_number1_1));
+      EXPECT_TRUE(CheckCustomRtp2(kSsrc1, sequence_number1_1));
+
+      // Send packets from callee and verify that it is received.
+      EXPECT_TRUE(SendCustomRtcp2(kSsrc2));
+      EXPECT_TRUE(CheckCustomRtcp1(kSsrc2));
+      EXPECT_TRUE(SendCustomRtp2(kSsrc2, ++sequence_number2_2));
+      EXPECT_TRUE(CheckCustomRtp1(kSsrc2, sequence_number2_2));
+
+      // Complete call setup and ensure everything is still OK.
+      EXPECT_TRUE(SendFinalAnswer());
+      EXPECT_EQ(1U, GetTransport1()->channels().size());
+      EXPECT_EQ(1U, GetTransport2()->channels().size());
+      EXPECT_TRUE(channel1_->secure());
+      EXPECT_TRUE(channel2_->secure());
+      EXPECT_TRUE(SendCustomRtcp1(kSsrc1));
+      EXPECT_TRUE(CheckCustomRtcp2(kSsrc1));
+      EXPECT_TRUE(SendCustomRtp1(kSsrc1, ++sequence_number1_1));
+      EXPECT_TRUE(CheckCustomRtp2(kSsrc1, sequence_number1_1));
+      EXPECT_TRUE(SendCustomRtcp2(kSsrc2));
+      EXPECT_TRUE(CheckCustomRtcp1(kSsrc2));
+      EXPECT_TRUE(SendCustomRtp2(kSsrc2, ++sequence_number2_2));
+      EXPECT_TRUE(CheckCustomRtp1(kSsrc2, sequence_number2_2));
+  }
+
+  // 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());
+
+    // Lose writability, with optimistic send
+    SetOptimisticDataSend(true);
+    GetTransport1()->SetWritable(false);
+    EXPECT_TRUE(media_channel1_->sending());
+    EXPECT_TRUE(SendRtp1());
+    EXPECT_TRUE(SendRtp2());
+    EXPECT_TRUE(CheckRtp1());
+    EXPECT_TRUE(CheckRtp2());
+    EXPECT_TRUE(CheckNoRtp1());
+    EXPECT_TRUE(CheckNoRtp2());
+
+    // Check again with optimistic send off, which should fail.
+    SetOptimisticDataSend(false);
+    EXPECT_FALSE(SendRtp1());
+    EXPECT_TRUE(SendRtp2());
+    EXPECT_TRUE(CheckRtp1());
+    EXPECT_TRUE(CheckNoRtp2());
+
+    // Regain writability
+    GetTransport1()->SetWritable(true);
+    EXPECT_TRUE(media_channel1_->sending());
+    EXPECT_TRUE(SendRtp1());
+    EXPECT_TRUE(SendRtp2());
+    EXPECT_TRUE(CheckRtp1());
+    EXPECT_TRUE(CheckRtp2());
+    EXPECT_TRUE(CheckNoRtp1());
+    EXPECT_TRUE(CheckNoRtp2());
+
+    // Lose writability completely
+    GetTransport1()->SetDestination(NULL);
+    EXPECT_TRUE(media_channel1_->sending());
+
+    // Should fail regardless of optimistic send at this point.
+    SetOptimisticDataSend(true);
+    EXPECT_FALSE(SendRtp1());
+    EXPECT_TRUE(SendRtp2());
+    EXPECT_TRUE(CheckRtp1());
+    EXPECT_TRUE(CheckNoRtp2());
+    SetOptimisticDataSend(false);
+    EXPECT_FALSE(SendRtp1());
+    EXPECT_TRUE(SendRtp2());
+    EXPECT_TRUE(CheckRtp1());
+    EXPECT_TRUE(CheckNoRtp2());
+
+    // Gain writability back
+    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() {
+    int sequence_number1_1 = 0, sequence_number2_2 = 0;
+    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, ++sequence_number1_1));
+    EXPECT_TRUE(SendCustomRtp2(kSsrc2, ++sequence_number2_2));
+    EXPECT_TRUE(SendCustomRtcp1(kSsrc1));
+    EXPECT_TRUE(SendCustomRtcp2(kSsrc2));
+    EXPECT_TRUE(CheckCustomRtp1(kSsrc2, sequence_number2_2));
+    EXPECT_TRUE(CheckNoRtp1());
+    EXPECT_TRUE(CheckCustomRtp2(kSsrc1, sequence_number1_1));
+    EXPECT_TRUE(CheckNoRtp2());
+    EXPECT_TRUE(CheckCustomRtcp1(kSsrc2));
+    EXPECT_TRUE(CheckNoRtcp1());
+    EXPECT_TRUE(CheckCustomRtcp2(kSsrc1));
+    EXPECT_TRUE(CheckNoRtcp2());
+  }
+
+  void SendSsrcMuxToSsrcMux() {
+    int sequence_number1_1 = 0, sequence_number2_2 = 0;
+    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, ++sequence_number1_1));
+    EXPECT_TRUE(SendCustomRtp2(kSsrc2, ++sequence_number2_2));
+    EXPECT_TRUE(SendCustomRtcp1(kSsrc1));
+    EXPECT_TRUE(SendCustomRtcp2(kSsrc2));
+    EXPECT_TRUE(CheckCustomRtp1(kSsrc2, sequence_number2_2));
+    EXPECT_FALSE(CheckCustomRtp1(kSsrc1, sequence_number2_2));
+    EXPECT_TRUE(CheckCustomRtp2(kSsrc1, sequence_number1_1));
+    EXPECT_FALSE(CheckCustomRtp2(kSsrc2, sequence_number1_1));
+    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(cricket::SINK_POST_CRYPTO));
+    EXPECT_FALSE(channel1_->HasRecvSinks(cricket::SINK_POST_CRYPTO));
+    EXPECT_FALSE(channel1_->HasSendSinks(cricket::SINK_PRE_CRYPTO));
+    EXPECT_FALSE(channel1_->HasRecvSinks(cricket::SINK_PRE_CRYPTO));
+
+    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(Open(path.pathname())));
+    sink->set_packet_filter(cricket::PF_ALL);
+    EXPECT_TRUE(sink->Enable(true));
+    channel1_->RegisterSendSink(
+        sink.get(), &cricket::RtpDumpSink::OnPacket, cricket::SINK_POST_CRYPTO);
+    EXPECT_TRUE(channel1_->HasSendSinks(cricket::SINK_POST_CRYPTO));
+    EXPECT_FALSE(channel1_->HasRecvSinks(cricket::SINK_POST_CRYPTO));
+    EXPECT_FALSE(channel1_->HasSendSinks(cricket::SINK_PRE_CRYPTO));
+    EXPECT_FALSE(channel1_->HasRecvSinks(cricket::SINK_PRE_CRYPTO));
+
+    // 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(), cricket::SINK_POST_CRYPTO);
+    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_.SetState(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_.SetState(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_.SetState(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_.SetState(cricket::Session::STATE_RECEIVEDACCEPT);
+    EXPECT_EQ(cricket::BaseSession::ERROR_CONTENT, session1_.error());
+  }
+
+  void TestSendTwoOffers() {
+    CreateChannels(0, 0);
+
+    // Set up the initial session description.
+    cricket::SessionDescription* sdesc = CreateSessionDescriptionWithStream(1);
+    EXPECT_TRUE(session1_.set_local_description(sdesc));
+
+    session1_.SetError(cricket::BaseSession::ERROR_NONE);
+    session1_.SetState(cricket::Session::STATE_SENTINITIATE);
+    EXPECT_EQ(cricket::BaseSession::ERROR_NONE, session1_.error());
+    EXPECT_TRUE(media_channel1_->HasSendStream(1));
+
+    // Update the local description and set the state again.
+    sdesc = CreateSessionDescriptionWithStream(2);
+    EXPECT_TRUE(session1_.set_local_description(sdesc));
+
+    session1_.SetState(cricket::Session::STATE_SENTINITIATE);
+    EXPECT_EQ(cricket::BaseSession::ERROR_NONE, session1_.error());
+    EXPECT_FALSE(media_channel1_->HasSendStream(1));
+    EXPECT_TRUE(media_channel1_->HasSendStream(2));
+  }
+
+  void TestReceiveTwoOffers() {
+    CreateChannels(0, 0);
+
+    // Set up the initial session description.
+    cricket::SessionDescription* sdesc = CreateSessionDescriptionWithStream(1);
+    EXPECT_TRUE(session1_.set_remote_description(sdesc));
+
+    session1_.SetError(cricket::BaseSession::ERROR_NONE);
+    session1_.SetState(cricket::Session::STATE_RECEIVEDINITIATE);
+    EXPECT_EQ(cricket::BaseSession::ERROR_NONE, session1_.error());
+    EXPECT_TRUE(media_channel1_->HasRecvStream(1));
+
+    sdesc = CreateSessionDescriptionWithStream(2);
+    EXPECT_TRUE(session1_.set_remote_description(sdesc));
+    session1_.SetState(cricket::Session::STATE_RECEIVEDINITIATE);
+    EXPECT_EQ(cricket::BaseSession::ERROR_NONE, session1_.error());
+    EXPECT_FALSE(media_channel1_->HasRecvStream(1));
+    EXPECT_TRUE(media_channel1_->HasRecvStream(2));
+  }
+
+  void TestSendPrAnswer() {
+    CreateChannels(0, 0);
+
+    // Set up the initial session description.
+    cricket::SessionDescription* sdesc = CreateSessionDescriptionWithStream(1);
+    EXPECT_TRUE(session1_.set_remote_description(sdesc));
+
+    session1_.SetError(cricket::BaseSession::ERROR_NONE);
+    session1_.SetState(cricket::Session::STATE_RECEIVEDINITIATE);
+    EXPECT_EQ(cricket::BaseSession::ERROR_NONE, session1_.error());
+    EXPECT_TRUE(media_channel1_->HasRecvStream(1));
+
+    // Send PRANSWER
+    sdesc = CreateSessionDescriptionWithStream(2);
+    EXPECT_TRUE(session1_.set_local_description(sdesc));
+
+    session1_.SetState(cricket::Session::STATE_SENTPRACCEPT);
+    EXPECT_EQ(cricket::BaseSession::ERROR_NONE, session1_.error());
+    EXPECT_TRUE(media_channel1_->HasRecvStream(1));
+    EXPECT_TRUE(media_channel1_->HasSendStream(2));
+
+    // Send ACCEPT
+    sdesc = CreateSessionDescriptionWithStream(3);
+    EXPECT_TRUE(session1_.set_local_description(sdesc));
+
+    session1_.SetState(cricket::Session::STATE_SENTACCEPT);
+    EXPECT_EQ(cricket::BaseSession::ERROR_NONE, session1_.error());
+    EXPECT_TRUE(media_channel1_->HasRecvStream(1));
+    EXPECT_FALSE(media_channel1_->HasSendStream(2));
+    EXPECT_TRUE(media_channel1_->HasSendStream(3));
+  }
+
+  void TestReceivePrAnswer() {
+    CreateChannels(0, 0);
+
+    // Set up the initial session description.
+    cricket::SessionDescription* sdesc = CreateSessionDescriptionWithStream(1);
+    EXPECT_TRUE(session1_.set_local_description(sdesc));
+
+    session1_.SetError(cricket::BaseSession::ERROR_NONE);
+    session1_.SetState(cricket::Session::STATE_SENTINITIATE);
+    EXPECT_EQ(cricket::BaseSession::ERROR_NONE, session1_.error());
+    EXPECT_TRUE(media_channel1_->HasSendStream(1));
+
+    // Receive PRANSWER
+    sdesc = CreateSessionDescriptionWithStream(2);
+    EXPECT_TRUE(session1_.set_remote_description(sdesc));
+
+    session1_.SetState(cricket::Session::STATE_RECEIVEDPRACCEPT);
+    EXPECT_EQ(cricket::BaseSession::ERROR_NONE, session1_.error());
+    EXPECT_TRUE(media_channel1_->HasSendStream(1));
+    EXPECT_TRUE(media_channel1_->HasRecvStream(2));
+
+    // Receive ACCEPT
+    sdesc = CreateSessionDescriptionWithStream(3);
+    EXPECT_TRUE(session1_.set_remote_description(sdesc));
+
+    session1_.SetState(cricket::Session::STATE_RECEIVEDACCEPT);
+    EXPECT_EQ(cricket::BaseSession::ERROR_NONE, session1_.error());
+    EXPECT_TRUE(media_channel1_->HasSendStream(1));
+    EXPECT_FALSE(media_channel1_->HasRecvStream(2));
+    EXPECT_TRUE(media_channel1_->HasRecvStream(3));
+  }
+
+  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), 0);
+    EXPECT_EQ_WAIT(T::MediaChannel::ERROR_PLAY_SRTP_AUTH_FAILED, error_, 500);
+  }
+
+  void TestOnReadyToSend() {
+    CreateChannels(RTCP, RTCP);
+    TransportChannel* rtp = channel1_->transport_channel();
+    TransportChannel* rtcp = channel1_->rtcp_transport_channel();
+    EXPECT_FALSE(media_channel1_->ready_to_send());
+    rtp->SignalReadyToSend(rtp);
+    EXPECT_FALSE(media_channel1_->ready_to_send());
+    rtcp->SignalReadyToSend(rtcp);
+    // MediaChannel::OnReadyToSend only be called when both rtp and rtcp
+    // channel are ready to send.
+    EXPECT_TRUE(media_channel1_->ready_to_send());
+
+    // rtp channel becomes not ready to send will be propagated to mediachannel
+    channel1_->SetReadyToSend(rtp, false);
+    EXPECT_FALSE(media_channel1_->ready_to_send());
+    channel1_->SetReadyToSend(rtp, true);
+    EXPECT_TRUE(media_channel1_->ready_to_send());
+
+    // rtcp channel becomes not ready to send will be propagated to mediachannel
+    channel1_->SetReadyToSend(rtcp, false);
+    EXPECT_FALSE(media_channel1_->ready_to_send());
+    channel1_->SetReadyToSend(rtcp, true);
+    EXPECT_TRUE(media_channel1_->ready_to_send());
+  }
+
+  void TestOnReadyToSendWithRtcpMux() {
+    CreateChannels(RTCP, RTCP);
+    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);
+    TransportChannel* rtp = channel1_->transport_channel();
+    EXPECT_FALSE(media_channel1_->ready_to_send());
+    // In the case of rtcp mux, the SignalReadyToSend() from rtp channel
+    // should trigger the MediaChannel's OnReadyToSend.
+    rtp->SignalReadyToSend(rtp);
+    EXPECT_TRUE(media_channel1_->ready_to_send());
+    channel1_->SetReadyToSend(rtp, false);
+    EXPECT_FALSE(media_channel1_->ready_to_send());
+  }
+
+ 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_;
+  talk_base::scoped_ptr<talk_base::SSLIdentity> identity1_;
+  talk_base::scoped_ptr<talk_base::SSLIdentity> identity2_;
+  // 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_;
+  bool mute_callback_recved_;
+  bool mute_callback_value_;
+
+  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)) {
+  }
+
+  void TestSetChannelOptions() {
+    CreateChannels(0, 0);
+
+    cricket::AudioOptions options1;
+    options1.echo_cancellation.Set(false);
+    cricket::AudioOptions options2;
+    options2.echo_cancellation.Set(true);
+
+    channel1_->SetChannelOptions(options1);
+    channel2_->SetChannelOptions(options1);
+    cricket::AudioOptions actual_options;
+    ASSERT_TRUE(media_channel1_->GetOptions(&actual_options));
+    EXPECT_EQ(options1, actual_options);
+    ASSERT_TRUE(media_channel2_->GetOptions(&actual_options));
+    EXPECT_EQ(options1, actual_options);
+
+    channel1_->SetChannelOptions(options2);
+    channel2_->SetChannelOptions(options2);
+    ASSERT_TRUE(media_channel1_->GetOptions(&actual_options));
+    EXPECT_EQ(options2, actual_options);
+    ASSERT_TRUE(media_channel2_->GetOptions(&actual_options));
+    EXPECT_EQ(options2, actual_options);
+  }
+};
+
+// 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)) {
+  }
+
+  void TestSetChannelOptions() {
+    CreateChannels(0, 0);
+
+    cricket::VideoOptions o1, o2;
+    o1.video_noise_reduction.Set(true);
+
+    channel1_->SetChannelOptions(o1);
+    channel2_->SetChannelOptions(o1);
+    EXPECT_TRUE(media_channel1_->GetOptions(&o2));
+    EXPECT_EQ(o1, o2);
+    EXPECT_TRUE(media_channel2_->GetOptions(&o2));
+    EXPECT_EQ(o1, o2);
+
+    o1.video_leaky_bucket.Set(true);
+    channel1_->SetChannelOptions(o1);
+    channel2_->SetChannelOptions(o1);
+    EXPECT_TRUE(media_channel1_->GetOptions(&o2));
+    EXPECT_EQ(o1, o2);
+    EXPECT_TRUE(media_channel2_->GetOptions(&o2));
+    EXPECT_EQ(o1, o2);
+  }
+};
+
+
+// VoiceChannelTest
+
+TEST_F(VoiceChannelTest, TestInit) {
+  Base::TestInit();
+  EXPECT_FALSE(media_channel1_->IsStreamMuted(0));
+  EXPECT_TRUE(media_channel1_->dtmf_info_queue().empty());
+}
+
+TEST_F(VoiceChannelTest, TestSetContents) {
+  Base::TestSetContents();
+}
+
+TEST_F(VoiceChannelTest, TestSetContentsNullOffer) {
+  Base::TestSetContentsNullOffer();
+}
+
+TEST_F(VoiceChannelTest, TestSetContentsRtcpMux) {
+  Base::TestSetContentsRtcpMux();
+}
+
+TEST_F(VoiceChannelTest, TestSetContentsRtcpMuxWithPrAnswer) {
+  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, TestMuteStream) {
+  Base::TestMuteStream();
+}
+
+TEST_F(VoiceChannelTest, TestMediaContentDirection) {
+  Base::TestMediaContentDirection();
+}
+
+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, SendSrtpToSrtpRtcpMux) {
+  Base::SendSrtpToSrtp(RTCP_MUX, RTCP_MUX);
+}
+
+TEST_F(VoiceChannelTest, SendSrtpToRtp) {
+  Base::SendSrtpToSrtp();
+}
+
+TEST_F(VoiceChannelTest, SendSrtcpMux) {
+  Base::SendSrtpToSrtp(RTCP_MUX, RTCP_MUX);
+}
+
+TEST_F(VoiceChannelTest, SendDtlsSrtpToSrtp) {
+  MAYBE_SKIP_TEST(HaveDtlsSrtp);
+  Base::SendSrtpToSrtp(DTLS, 0);
+}
+
+TEST_F(VoiceChannelTest, SendDtlsSrtpToDtlsSrtp) {
+  MAYBE_SKIP_TEST(HaveDtlsSrtp);
+  Base::SendSrtpToSrtp(DTLS, DTLS);
+}
+
+TEST_F(VoiceChannelTest, SendDtlsSrtpToDtlsSrtpRtcpMux) {
+  MAYBE_SKIP_TEST(HaveDtlsSrtp);
+  Base::SendSrtpToSrtp(DTLS | RTCP_MUX, DTLS | RTCP_MUX);
+}
+
+TEST_F(VoiceChannelTest, SendEarlyMediaUsingRtcpMuxSrtp) {
+  Base::SendEarlyMediaUsingRtcpMuxSrtp();
+}
+
+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 MuteStream properly forwards to the media channel and does
+// not signal.
+TEST_F(VoiceChannelTest, TestVoiceSpecificMuteStream) {
+  CreateChannels(0, 0);
+  EXPECT_FALSE(media_channel1_->IsStreamMuted(0));
+  EXPECT_FALSE(mute_callback_recved_);
+  EXPECT_TRUE(channel1_->MuteStream(0, true));
+  EXPECT_TRUE(media_channel1_->IsStreamMuted(0));
+  EXPECT_FALSE(mute_callback_recved_);
+  EXPECT_TRUE(channel1_->MuteStream(0, false));
+  EXPECT_FALSE(media_channel1_->IsStreamMuted(0));
+  EXPECT_FALSE(mute_callback_recved_);
+}
+
+// Test that keyboard automute works correctly and signals upwards.
+TEST_F(VoiceChannelTest, TestKeyboardMute) {
+  CreateChannels(0, 0);
+  EXPECT_FALSE(media_channel1_->IsStreamMuted(0));
+  EXPECT_EQ(cricket::VoiceMediaChannel::ERROR_NONE, error_);
+
+  cricket::VoiceMediaChannel::Error e =
+      cricket::VoiceMediaChannel::ERROR_REC_TYPING_NOISE_DETECTED;
+
+  // Typing doesn't mute automatically unless typing monitor has been installed
+  media_channel1_->TriggerError(0, e);
+  talk_base::Thread::Current()->ProcessMessages(0);
+  EXPECT_EQ(e, error_);
+  EXPECT_FALSE(media_channel1_->IsStreamMuted(0));
+  EXPECT_FALSE(mute_callback_recved_);
+
+  cricket::TypingMonitorOptions o = {0};
+  o.mute_period = 1500;
+  channel1_->StartTypingMonitor(o);
+  media_channel1_->TriggerError(0, e);
+  talk_base::Thread::Current()->ProcessMessages(0);
+  EXPECT_TRUE(media_channel1_->IsStreamMuted(0));
+  EXPECT_TRUE(mute_callback_recved_);
+}
+
+// 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_info_queue().size());
+
+  EXPECT_TRUE(channel1_->PressDTMF(1, true));
+  EXPECT_TRUE(channel1_->PressDTMF(8, false));
+
+  ASSERT_EQ(2U, media_channel1_->dtmf_info_queue().size());
+  EXPECT_TRUE(CompareDtmfInfo(media_channel1_->dtmf_info_queue()[0],
+                              0, 1, 160, cricket::DF_PLAY | cricket::DF_SEND));
+  EXPECT_TRUE(CompareDtmfInfo(media_channel1_->dtmf_info_queue()[1],
+                              0, 8, 160, cricket::DF_SEND));
+}
+
+// Test that InsertDtmf properly forwards to the media channel.
+TEST_F(VoiceChannelTest, TestInsertDtmf) {
+  CreateChannels(0, 0);
+  EXPECT_TRUE(SendInitiate());
+  EXPECT_TRUE(SendAccept());
+  EXPECT_EQ(0U, media_channel1_->dtmf_info_queue().size());
+
+  EXPECT_TRUE(channel1_->InsertDtmf(-1, kDtmfReset, -1, cricket::DF_SEND));
+  EXPECT_TRUE(channel1_->InsertDtmf(0, kDtmfDelay, 90, cricket::DF_PLAY));
+  EXPECT_TRUE(channel1_->InsertDtmf(1, 3, 100, cricket::DF_SEND));
+  EXPECT_TRUE(channel1_->InsertDtmf(2, 5, 110, cricket::DF_PLAY));
+  EXPECT_TRUE(channel1_->InsertDtmf(3, 7, 120,
+                                    cricket::DF_PLAY | cricket::DF_SEND));
+
+  ASSERT_EQ(5U, media_channel1_->dtmf_info_queue().size());
+  EXPECT_TRUE(CompareDtmfInfo(media_channel1_->dtmf_info_queue()[0],
+                              -1, kDtmfReset, -1, cricket::DF_SEND));
+  EXPECT_TRUE(CompareDtmfInfo(media_channel1_->dtmf_info_queue()[1],
+                              0, kDtmfDelay, 90, cricket::DF_PLAY));
+  EXPECT_TRUE(CompareDtmfInfo(media_channel1_->dtmf_info_queue()[2],
+                              1, 3, 100, cricket::DF_SEND));
+  EXPECT_TRUE(CompareDtmfInfo(media_channel1_->dtmf_info_queue()[3],
+                              2, 5, 110, cricket::DF_PLAY));
+  EXPECT_TRUE(CompareDtmfInfo(media_channel1_->dtmf_info_queue()[4],
+                              3, 7, 120, cricket::DF_PLAY | cricket::DF_SEND));
+}
+
+TEST_F(VoiceChannelTest, TestMediaSinks) {
+  Base::TestMediaSinks();
+}
+
+TEST_F(VoiceChannelTest, TestSetContentFailure) {
+  Base::TestSetContentFailure();
+}
+
+TEST_F(VoiceChannelTest, TestSendTwoOffers) {
+  Base::TestSendTwoOffers();
+}
+
+TEST_F(VoiceChannelTest, TestReceiveTwoOffers) {
+  Base::TestReceiveTwoOffers();
+}
+
+TEST_F(VoiceChannelTest, TestSendPrAnswer) {
+  Base::TestSendPrAnswer();
+}
+
+TEST_F(VoiceChannelTest, TestReceivePrAnswer) {
+  Base::TestReceivePrAnswer();
+}
+
+TEST_F(VoiceChannelTest, TestFlushRtcp) {
+  Base::TestFlushRtcp();
+}
+
+TEST_F(VoiceChannelTest, TestChangeStateError) {
+  Base::TestChangeStateError();
+}
+
+TEST_F(VoiceChannelTest, TestSrtpError) {
+  Base::TestSrtpError();
+}
+
+TEST_F(VoiceChannelTest, TestOnReadyToSend) {
+  Base::TestOnReadyToSend();
+}
+
+TEST_F(VoiceChannelTest, TestOnReadyToSendWithRtcpMux) {
+  Base::TestOnReadyToSendWithRtcpMux();
+}
+
+// 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();
+}
+
+TEST_F(VoiceChannelTest, TestSetChannelOptions) {
+  TestSetChannelOptions();
+}
+
+// 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, TestSetContentsRtcpMuxWithPrAnswer) {
+  Base::TestSetContentsRtcpMux();
+}
+
+TEST_F(VideoChannelTest, TestSetContentsVideoOptions) {
+  Base::TestSetContentsVideoOptions();
+}
+
+TEST_F(VideoChannelTest, TestSetRemoteContentUpdate) {
+  Base::TestSetRemoteContentUpdate();
+}
+
+TEST_F(VideoChannelTest, TestStreams) {
+  Base::TestStreams();
+}
+
+TEST_F(VideoChannelTest, TestScreencastEvents) {
+  const int kTimeoutMs = 500;
+  TestInit();
+  FakeScreenCaptureFactory* screencapture_factory =
+      new FakeScreenCaptureFactory();
+  channel1_->SetScreenCaptureFactory(screencapture_factory);
+  cricket::ScreencastEventCatcher catcher;
+  channel1_->SignalScreencastWindowEvent.connect(
+      &catcher,
+      &cricket::ScreencastEventCatcher::OnEvent);
+  EXPECT_TRUE(channel1_->AddScreencast(0, ScreencastId(WindowId(0))) != NULL);
+  ASSERT_TRUE(screencapture_factory->window_capturer() != NULL);
+  EXPECT_EQ_WAIT(cricket::CS_STOPPED, screencapture_factory->capture_state(),
+                 kTimeoutMs);
+  screencapture_factory->window_capturer()->SignalStateChange(
+      screencapture_factory->window_capturer(), cricket::CS_PAUSED);
+  EXPECT_EQ_WAIT(talk_base::WE_MINIMIZE, catcher.event(), kTimeoutMs);
+  screencapture_factory->window_capturer()->SignalStateChange(
+      screencapture_factory->window_capturer(), cricket::CS_RUNNING);
+  EXPECT_EQ_WAIT(talk_base::WE_RESTORE, catcher.event(), kTimeoutMs);
+  screencapture_factory->window_capturer()->SignalStateChange(
+      screencapture_factory->window_capturer(), cricket::CS_STOPPED);
+  EXPECT_EQ_WAIT(talk_base::WE_CLOSE, catcher.event(), kTimeoutMs);
+  EXPECT_TRUE(channel1_->RemoveScreencast(0));
+  ASSERT_TRUE(screencapture_factory->window_capturer() == NULL);
+}
+
+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, TestMuteStream) {
+  Base::TestMuteStream();
+}
+
+TEST_F(VideoChannelTest, TestMediaContentDirection) {
+  Base::TestMediaContentDirection();
+}
+
+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, SendDtlsSrtpToSrtp) {
+  MAYBE_SKIP_TEST(HaveDtlsSrtp);
+  Base::SendSrtpToSrtp(DTLS, 0);
+}
+
+TEST_F(VideoChannelTest, SendDtlsSrtpToDtlsSrtp) {
+  MAYBE_SKIP_TEST(HaveDtlsSrtp);
+  Base::SendSrtpToSrtp(DTLS, DTLS);
+}
+
+TEST_F(VideoChannelTest, SendDtlsSrtpToDtlsSrtpRtcpMux) {
+  MAYBE_SKIP_TEST(HaveDtlsSrtp);
+  Base::SendSrtpToSrtp(DTLS | RTCP_MUX, DTLS | RTCP_MUX);
+}
+
+TEST_F(VideoChannelTest, SendSrtcpMux) {
+  Base::SendSrtpToSrtp(RTCP_MUX, RTCP_MUX);
+}
+
+TEST_F(VideoChannelTest, SendEarlyMediaUsingRtcpMuxSrtp) {
+  Base::SendEarlyMediaUsingRtcpMuxSrtp();
+}
+
+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, TestSendTwoOffers) {
+  Base::TestSendTwoOffers();
+}
+
+TEST_F(VideoChannelTest, TestReceiveTwoOffers) {
+  Base::TestReceiveTwoOffers();
+}
+
+TEST_F(VideoChannelTest, TestSendPrAnswer) {
+  Base::TestSendPrAnswer();
+}
+
+TEST_F(VideoChannelTest, TestReceivePrAnswer) {
+  Base::TestReceivePrAnswer();
+}
+
+TEST_F(VideoChannelTest, TestFlushRtcp) {
+  Base::TestFlushRtcp();
+}
+
+TEST_F(VideoChannelTest, SendSsrcMuxToSsrcMux) {
+  Base::SendSsrcMuxToSsrcMux();
+}
+
+TEST_F(VideoChannelTest, SendSsrcMuxToSsrcMuxWithRtcpMux) {
+  Base::SendSsrcMuxToSsrcMuxWithRtcpMux();
+}
+
+// TODO(gangji): Add VideoChannelTest.TestChangeStateError.
+
+TEST_F(VideoChannelTest, TestSrtpError) {
+  Base::TestSrtpError();
+}
+
+TEST_F(VideoChannelTest, TestOnReadyToSend) {
+  Base::TestOnReadyToSend();
+}
+
+TEST_F(VideoChannelTest, TestOnReadyToSendWithRtcpMux) {
+  Base::TestOnReadyToSendWithRtcpMux();
+}
+
+TEST_F(VideoChannelTest, TestApplyViewRequest) {
+  CreateChannels(0, 0);
+  cricket::StreamParams stream2;
+  stream2.id = "stream2";
+  stream2.ssrcs.push_back(2222);
+  local_media_content1_.AddStream(stream2);
+
+  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;
+  // stream1: 320x200x15; stream2: 0x0x0
+  request.static_video_views.push_back(cricket::StaticVideoView(
+      cricket::StreamSelector(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);
+  EXPECT_TRUE(media_channel1_->GetSendStreamFormat(2222, &send_format));
+  EXPECT_EQ(0, send_format.width);
+  EXPECT_EQ(0, send_format.height);
+
+  // stream1: 160x100x8; stream2: 0x0x0
+  request.static_video_views.clear();
+  request.static_video_views.push_back(cricket::StaticVideoView(
+      cricket::StreamSelector(kSsrc1), 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);
+
+  // stream1: 0x0x0; stream2: 640x400x30
+  request.static_video_views.clear();
+  request.static_video_views.push_back(cricket::StaticVideoView(
+      cricket::StreamSelector("", stream2.id), 640, 400, 30));
+  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);
+  EXPECT_TRUE(media_channel1_->GetSendStreamFormat(2222, &send_format));
+  EXPECT_EQ(640, send_format.width);
+  EXPECT_EQ(400, send_format.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), send_format.interval);
+
+  // stream1: 0x0x0; stream2: 0x0x0
+  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);
+}
+
+TEST_F(VideoChannelTest, TestSetChannelOptions) {
+  TestSetChannelOptions();
+}
+
+
+// DataChannelTest
+
+class DataChannelTest
+    : public ChannelTest<DataTraits> {
+ public:
+  typedef ChannelTest<DataTraits>
+  Base;
+  DataChannelTest() : Base(kDataPacket, sizeof(kDataPacket),
+                           kRtcpReport, sizeof(kRtcpReport)) {
+  }
+};
+
+// Override to avoid engine channel parameter.
+template<>
+cricket::DataChannel* ChannelTest<DataTraits>::CreateChannel(
+    talk_base::Thread* thread, cricket::MediaEngineInterface* engine,
+    cricket::FakeDataMediaChannel* ch, cricket::BaseSession* session,
+    bool rtcp) {
+  cricket::DataChannel* channel = new cricket::DataChannel(
+      thread, ch, session, cricket::CN_DATA, rtcp);
+  if (!channel->Init()) {
+    delete channel;
+    channel = NULL;
+  }
+  return channel;
+}
+
+template<>
+void ChannelTest<DataTraits>::CreateContent(
+    int flags,
+    const cricket::AudioCodec& audio_codec,
+    const cricket::VideoCodec& video_codec,
+    cricket::DataContentDescription* data) {
+  data->AddCodec(kGoogleDataCodec);
+  data->set_rtcp_mux((flags & RTCP_MUX) != 0);
+  if (flags & SECURE) {
+    data->AddCrypto(cricket::CryptoParams(
+        1, cricket::CS_AES_CM_128_HMAC_SHA1_32,
+        "inline:" + talk_base::CreateRandomString(40), ""));
+  }
+}
+
+template<>
+void ChannelTest<DataTraits>::CopyContent(
+    const cricket::DataContentDescription& source,
+    cricket::DataContentDescription* data) {
+  *data = source;
+}
+
+template<>
+bool ChannelTest<DataTraits>::CodecMatches(const cricket::DataCodec& c1,
+                                           const cricket::DataCodec& c2) {
+  return c1.name == c2.name;
+}
+
+template<>
+void ChannelTest<DataTraits>::AddLegacyStreamInContent(
+    uint32 ssrc, int flags, cricket::DataContentDescription* data) {
+  data->AddLegacyStream(ssrc);
+}
+
+TEST_F(DataChannelTest, TestInit) {
+  Base::TestInit();
+  EXPECT_FALSE(media_channel1_->IsStreamMuted(0));
+}
+
+TEST_F(DataChannelTest, TestSetContents) {
+  Base::TestSetContents();
+}
+
+TEST_F(DataChannelTest, TestSetContentsNullOffer) {
+  Base::TestSetContentsNullOffer();
+}
+
+TEST_F(DataChannelTest, TestSetContentsRtcpMux) {
+  Base::TestSetContentsRtcpMux();
+}
+
+TEST_F(DataChannelTest, TestSetRemoteContentUpdate) {
+  Base::TestSetRemoteContentUpdate();
+}
+
+TEST_F(DataChannelTest, TestStreams) {
+  Base::TestStreams();
+}
+
+TEST_F(DataChannelTest, TestUpdateStreamsInLocalContent) {
+  Base::TestUpdateStreamsInLocalContent();
+}
+
+TEST_F(DataChannelTest, TestUpdateRemoteStreamsInContent) {
+  Base::TestUpdateStreamsInRemoteContent();
+}
+
+TEST_F(DataChannelTest, TestChangeStreamParamsInContent) {
+  Base::TestChangeStreamParamsInContent();
+}
+
+TEST_F(DataChannelTest, TestPlayoutAndSendingStates) {
+  Base::TestPlayoutAndSendingStates();
+}
+
+TEST_F(DataChannelTest, TestMediaContentDirection) {
+  Base::TestMediaContentDirection();
+}
+
+TEST_F(DataChannelTest, TestCallSetup) {
+  Base::TestCallSetup();
+}
+
+TEST_F(DataChannelTest, TestCallTeardownRtcpMux) {
+  Base::TestCallTeardownRtcpMux();
+}
+
+TEST_F(DataChannelTest, TestOnReadyToSend) {
+  Base::TestOnReadyToSend();
+}
+
+TEST_F(DataChannelTest, TestOnReadyToSendWithRtcpMux) {
+  Base::TestOnReadyToSendWithRtcpMux();
+}
+
+TEST_F(DataChannelTest, SendRtpToRtp) {
+  Base::SendRtpToRtp();
+}
+
+TEST_F(DataChannelTest, SendNoRtcpToNoRtcp) {
+  Base::SendNoRtcpToNoRtcp();
+}
+
+TEST_F(DataChannelTest, SendNoRtcpToRtcp) {
+  Base::SendNoRtcpToRtcp();
+}
+
+TEST_F(DataChannelTest, SendRtcpToNoRtcp) {
+  Base::SendRtcpToNoRtcp();
+}
+
+TEST_F(DataChannelTest, SendRtcpToRtcp) {
+  Base::SendRtcpToRtcp();
+}
+
+TEST_F(DataChannelTest, SendRtcpMuxToRtcp) {
+  Base::SendRtcpMuxToRtcp();
+}
+
+TEST_F(DataChannelTest, SendRtcpMuxToRtcpMux) {
+  Base::SendRtcpMuxToRtcpMux();
+}
+
+TEST_F(DataChannelTest, SendEarlyRtcpMuxToRtcp) {
+  Base::SendEarlyRtcpMuxToRtcp();
+}
+
+TEST_F(DataChannelTest, SendEarlyRtcpMuxToRtcpMux) {
+  Base::SendEarlyRtcpMuxToRtcpMux();
+}
+
+TEST_F(DataChannelTest, SendSrtpToSrtp) {
+  Base::SendSrtpToSrtp();
+}
+
+TEST_F(DataChannelTest, SendSrtpToRtp) {
+  Base::SendSrtpToSrtp();
+}
+
+TEST_F(DataChannelTest, SendSrtcpMux) {
+  Base::SendSrtpToSrtp(RTCP_MUX, RTCP_MUX);
+}
+
+TEST_F(DataChannelTest, SendRtpToRtpOnThread) {
+  Base::SendRtpToRtpOnThread();
+}
+
+TEST_F(DataChannelTest, SendSrtpToSrtpOnThread) {
+  Base::SendSrtpToSrtpOnThread();
+}
+
+TEST_F(DataChannelTest, SendWithWritabilityLoss) {
+  Base::SendWithWritabilityLoss();
+}
+
+TEST_F(DataChannelTest, TestMediaMonitor) {
+  Base::TestMediaMonitor();
+}
+
+TEST_F(DataChannelTest, TestSendData) {
+  CreateChannels(0, 0);
+  EXPECT_TRUE(SendInitiate());
+  EXPECT_TRUE(SendAccept());
+
+  cricket::SendDataParams params;
+  params.ssrc = 42;
+  unsigned char data[] = {
+    'f', 'o', 'o'
+  };
+  talk_base::Buffer payload(data, 3);
+  cricket::SendDataResult result;
+  ASSERT_TRUE(media_channel1_->SendData(params, payload, &result));
+  EXPECT_EQ(params.ssrc,
+            media_channel1_->last_sent_data_params().ssrc);
+  EXPECT_EQ("foo", media_channel1_->last_sent_data());
+}
+
+// TODO(pthatcher): TestSetReceiver?
diff --git a/talk/session/media/channelmanager.cc b/talk/session/media/channelmanager.cc
new file mode 100644
index 0000000..529ceea
--- /dev/null
+++ b/talk/session/media/channelmanager.cc
@@ -0,0 +1,931 @@
+/*
+ * 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/media/channelmanager.h"
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <algorithm>
+
+#include "talk/base/bind.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/sigslotrepeater.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+#include "talk/media/base/capturemanager.h"
+#include "talk/media/base/hybriddataengine.h"
+#include "talk/media/base/rtpdataengine.h"
+#include "talk/media/base/videocapturer.h"
+#ifdef HAVE_SCTP
+#include "talk/media/sctp/sctpdataengine.h"
+#endif
+#include "talk/session/media/soundclip.h"
+
+namespace cricket {
+
+enum {
+  MSG_VIDEOCAPTURESTATE = 1,
+};
+
+using talk_base::Bind;
+
+static const int kNotSetOutputVolume = -1;
+
+struct CaptureStateParams : public talk_base::MessageData {
+  CaptureStateParams(cricket::VideoCapturer* c, cricket::CaptureState s)
+      : capturer(c),
+        state(s) {}
+  cricket::VideoCapturer* capturer;
+  cricket::CaptureState state;
+};
+
+static DataEngineInterface* ConstructDataEngine() {
+#ifdef HAVE_SCTP
+  return new HybridDataEngine(new RtpDataEngine(), new SctpDataEngine());
+#else
+  return new RtpDataEngine();
+#endif
+}
+
+#if !defined(DISABLE_MEDIA_ENGINE_FACTORY)
+ChannelManager::ChannelManager(talk_base::Thread* worker_thread) {
+  Construct(MediaEngineFactory::Create(),
+            ConstructDataEngine(),
+            cricket::DeviceManagerFactory::Create(),
+            new CaptureManager(),
+            worker_thread);
+}
+#endif
+
+ChannelManager::ChannelManager(MediaEngineInterface* me,
+                               DataEngineInterface* dme,
+                               DeviceManagerInterface* dm,
+                               CaptureManager* cm,
+                               talk_base::Thread* worker_thread) {
+  Construct(me, dme, dm, cm, worker_thread);
+}
+
+ChannelManager::ChannelManager(MediaEngineInterface* me,
+                               DeviceManagerInterface* dm,
+                               talk_base::Thread* worker_thread) {
+  Construct(me,
+            ConstructDataEngine(),
+            dm,
+            new CaptureManager(),
+            worker_thread);
+}
+
+void ChannelManager::Construct(MediaEngineInterface* me,
+                               DataEngineInterface* dme,
+                               DeviceManagerInterface* dm,
+                               CaptureManager* cm,
+                               talk_base::Thread* worker_thread) {
+  media_engine_.reset(me);
+  data_media_engine_.reset(dme);
+  device_manager_.reset(dm);
+  capture_manager_.reset(cm);
+  initialized_ = false;
+  main_thread_ = talk_base::Thread::Current();
+  worker_thread_ = worker_thread;
+  audio_in_device_ = DeviceManagerInterface::kDefaultDeviceName;
+  audio_out_device_ = DeviceManagerInterface::kDefaultDeviceName;
+  audio_options_ = MediaEngineInterface::DEFAULT_AUDIO_OPTIONS;
+  audio_delay_offset_ = MediaEngineInterface::kDefaultAudioDelayOffset;
+  audio_output_volume_ = kNotSetOutputVolume;
+  local_renderer_ = NULL;
+  capturing_ = false;
+  monitoring_ = false;
+  enable_rtx_ = false;
+
+  // Init the device manager immediately, and set up our default video device.
+  SignalDevicesChange.repeat(device_manager_->SignalDevicesChange);
+  device_manager_->Init();
+
+  // Camera is started asynchronously, request callbacks when startup
+  // completes to be able to forward them to the rendering manager.
+  media_engine_->SignalVideoCaptureStateChange().connect(
+      this, &ChannelManager::OnVideoCaptureStateChange);
+  capture_manager_->SignalCapturerStateChange.connect(
+      this, &ChannelManager::OnVideoCaptureStateChange);
+}
+
+ChannelManager::~ChannelManager() {
+  if (initialized_)
+    Terminate();
+}
+
+bool ChannelManager::SetVideoRtxEnabled(bool enable) {
+  // To be safe, this call is only allowed before initialization. Apps like
+  // Flute only have a singleton ChannelManager and we don't want this flag to
+  // be toggled between calls or when there's concurrent calls. We expect apps
+  // to enable this at startup and retain that setting for the lifetime of the
+  // app.
+  if (!initialized_) {
+    enable_rtx_ = enable;
+    return true;
+  } else {
+    LOG(LS_WARNING) << "Cannot toggle rtx after initialization!";
+    return false;
+  }
+}
+
+int ChannelManager::GetCapabilities() {
+  return media_engine_->GetCapabilities() & device_manager_->GetCapabilities();
+}
+
+void ChannelManager::GetSupportedAudioCodecs(
+    std::vector<AudioCodec>* codecs) const {
+  codecs->clear();
+
+  for (std::vector<AudioCodec>::const_iterator it =
+           media_engine_->audio_codecs().begin();
+      it != media_engine_->audio_codecs().end(); ++it) {
+    codecs->push_back(*it);
+  }
+}
+
+void ChannelManager::GetSupportedAudioRtpHeaderExtensions(
+    RtpHeaderExtensions* ext) const {
+  *ext = media_engine_->audio_rtp_header_extensions();
+}
+
+void ChannelManager::GetSupportedVideoCodecs(
+    std::vector<VideoCodec>* codecs) const {
+  codecs->clear();
+
+  std::vector<VideoCodec>::const_iterator it;
+  for (it = media_engine_->video_codecs().begin();
+      it != media_engine_->video_codecs().end(); ++it) {
+    if (!enable_rtx_ && _stricmp(kRtxCodecName, it->name.c_str()) == 0) {
+      continue;
+    }
+    codecs->push_back(*it);
+  }
+}
+
+void ChannelManager::GetSupportedVideoRtpHeaderExtensions(
+    RtpHeaderExtensions* ext) const {
+  *ext = media_engine_->video_rtp_header_extensions();
+}
+
+void ChannelManager::GetSupportedDataCodecs(
+    std::vector<DataCodec>* codecs) const {
+  *codecs = data_media_engine_->data_codecs();
+}
+
+bool ChannelManager::Init() {
+  ASSERT(!initialized_);
+  if (initialized_) {
+    return false;
+  }
+
+  ASSERT(worker_thread_ != NULL);
+  if (worker_thread_ && worker_thread_->started()) {
+    if (media_engine_->Init(worker_thread_)) {
+      initialized_ = true;
+
+      // Now that we're initialized, apply any stored preferences. A preferred
+      // device might have been unplugged. In this case, we fallback to the
+      // default device but keep the user preferences. The preferences are
+      // changed only when the Javascript FE changes them.
+      const std::string preferred_audio_in_device = audio_in_device_;
+      const std::string preferred_audio_out_device = audio_out_device_;
+      const std::string preferred_camera_device = camera_device_;
+      Device device;
+      if (!device_manager_->GetAudioInputDevice(audio_in_device_, &device)) {
+        LOG(LS_WARNING) << "The preferred microphone '" << audio_in_device_
+                        << "' is unavailable. Fall back to the default.";
+        audio_in_device_ = DeviceManagerInterface::kDefaultDeviceName;
+      }
+      if (!device_manager_->GetAudioOutputDevice(audio_out_device_, &device)) {
+        LOG(LS_WARNING) << "The preferred speaker '" << audio_out_device_
+                        << "' is unavailable. Fall back to the default.";
+        audio_out_device_ = DeviceManagerInterface::kDefaultDeviceName;
+      }
+      if (!device_manager_->GetVideoCaptureDevice(camera_device_, &device)) {
+        if (!camera_device_.empty()) {
+          LOG(LS_WARNING) << "The preferred camera '" << camera_device_
+                          << "' is unavailable. Fall back to the default.";
+        }
+        camera_device_ = DeviceManagerInterface::kDefaultDeviceName;
+      }
+
+      if (!SetAudioOptions(audio_in_device_, audio_out_device_,
+                           audio_options_, audio_delay_offset_)) {
+        LOG(LS_WARNING) << "Failed to SetAudioOptions with"
+                        << " microphone: " << audio_in_device_
+                        << " speaker: " << audio_out_device_
+                        << " options: " << audio_options_
+                        << " delay: " << audio_delay_offset_;
+      }
+
+      // 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 (!SetCaptureDevice(camera_device_) && !camera_device_.empty()) {
+        LOG(LS_WARNING) << "Failed to SetCaptureDevice with camera: "
+                        << camera_device_;
+      }
+
+      // Restore the user preferences.
+      audio_in_device_ = preferred_audio_in_device;
+      audio_out_device_ = preferred_audio_out_device;
+      camera_device_ = preferred_camera_device;
+
+      // Now apply the default video codec that has been set earlier.
+      if (default_video_encoder_config_.max_codec.id != 0) {
+        SetDefaultVideoEncoderConfig(default_video_encoder_config_);
+      }
+      // And the local renderer.
+      if (local_renderer_) {
+        SetLocalRenderer(local_renderer_);
+      }
+    }
+  }
+  return initialized_;
+}
+
+void ChannelManager::Terminate() {
+  ASSERT(initialized_);
+  if (!initialized_) {
+    return;
+  }
+  worker_thread_->Invoke<void>(Bind(&ChannelManager::Terminate_w, this));
+  media_engine_->Terminate();
+  initialized_ = false;
+}
+
+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());
+  }
+  while (!voice_channels_.empty()) {
+    DestroyVoiceChannel_w(voice_channels_.back());
+  }
+  while (!soundclips_.empty()) {
+    DestroySoundclip_w(soundclips_.back());
+  }
+  if (!SetCaptureDevice_w(NULL)) {
+    LOG(LS_WARNING) << "failed to delete video capturer";
+  }
+}
+
+VoiceChannel* ChannelManager::CreateVoiceChannel(
+    BaseSession* session, const std::string& content_name, bool rtcp) {
+  return worker_thread_->Invoke<VoiceChannel*>(
+      Bind(&ChannelManager::CreateVoiceChannel_w, this,
+           session, content_name, rtcp));
+}
+
+VoiceChannel* ChannelManager::CreateVoiceChannel_w(
+    BaseSession* session, const std::string& content_name, bool rtcp) {
+  // This is ok to alloc from a thread other than the worker thread
+  ASSERT(initialized_);
+  VoiceMediaChannel* media_channel = media_engine_->CreateChannel();
+  if (media_channel == NULL)
+    return NULL;
+
+  VoiceChannel* voice_channel = new VoiceChannel(
+      worker_thread_, media_engine_.get(), media_channel,
+      session, content_name, rtcp);
+  if (!voice_channel->Init()) {
+    delete voice_channel;
+    return NULL;
+  }
+  voice_channels_.push_back(voice_channel);
+  return voice_channel;
+}
+
+void ChannelManager::DestroyVoiceChannel(VoiceChannel* voice_channel) {
+  if (voice_channel) {
+    worker_thread_->Invoke<void>(
+        Bind(&ChannelManager::DestroyVoiceChannel_w, this, voice_channel));
+  }
+}
+
+void ChannelManager::DestroyVoiceChannel_w(VoiceChannel* voice_channel) {
+  // Destroy voice channel.
+  ASSERT(initialized_);
+  VoiceChannels::iterator it = std::find(voice_channels_.begin(),
+      voice_channels_.end(), voice_channel);
+  ASSERT(it != voice_channels_.end());
+  if (it == voice_channels_.end())
+    return;
+
+  voice_channels_.erase(it);
+  delete voice_channel;
+}
+
+VideoChannel* ChannelManager::CreateVideoChannel(
+    BaseSession* session, const std::string& content_name, bool rtcp,
+    VoiceChannel* voice_channel) {
+  return worker_thread_->Invoke<VideoChannel*>(
+      Bind(&ChannelManager::CreateVideoChannel_w, this, session,
+           content_name, rtcp, voice_channel));
+}
+
+VideoChannel* ChannelManager::CreateVideoChannel_w(
+    BaseSession* session, const std::string& content_name, bool rtcp,
+    VoiceChannel* voice_channel) {
+  // This is ok to alloc from a thread other than the worker thread
+  ASSERT(initialized_);
+  VideoMediaChannel* media_channel =
+      // voice_channel can be NULL in case of NullVoiceEngine.
+      media_engine_->CreateVideoChannel(voice_channel ?
+          voice_channel->media_channel() : NULL);
+  if (media_channel == NULL)
+    return NULL;
+
+  VideoChannel* video_channel = new VideoChannel(
+      worker_thread_, media_engine_.get(), media_channel,
+      session, content_name, rtcp, voice_channel);
+  if (!video_channel->Init()) {
+    delete video_channel;
+    return NULL;
+  }
+  video_channels_.push_back(video_channel);
+  return video_channel;
+}
+
+void ChannelManager::DestroyVideoChannel(VideoChannel* video_channel) {
+  if (video_channel) {
+    worker_thread_->Invoke<void>(
+        Bind(&ChannelManager::DestroyVideoChannel_w, this, video_channel));
+  }
+}
+
+void ChannelManager::DestroyVideoChannel_w(VideoChannel* video_channel) {
+  // Destroy video channel.
+  ASSERT(initialized_);
+  VideoChannels::iterator it = std::find(video_channels_.begin(),
+      video_channels_.end(), video_channel);
+  ASSERT(it != video_channels_.end());
+  if (it == video_channels_.end())
+    return;
+
+  video_channels_.erase(it);
+  delete video_channel;
+}
+
+DataChannel* ChannelManager::CreateDataChannel(
+    BaseSession* session, const std::string& content_name,
+    bool rtcp, DataChannelType channel_type) {
+  return worker_thread_->Invoke<DataChannel*>(
+      Bind(&ChannelManager::CreateDataChannel_w, this, session, content_name,
+           rtcp, channel_type));
+}
+
+DataChannel* ChannelManager::CreateDataChannel_w(
+    BaseSession* session, const std::string& content_name,
+    bool rtcp, DataChannelType data_channel_type) {
+  // This is ok to alloc from a thread other than the worker thread.
+  ASSERT(initialized_);
+  DataMediaChannel* media_channel = data_media_engine_->CreateChannel(
+      data_channel_type);
+  if (!media_channel) {
+    LOG(LS_WARNING) << "Failed to create data channel of type "
+                    << data_channel_type;
+    return NULL;
+  }
+
+  DataChannel* data_channel = new DataChannel(
+      worker_thread_, media_channel,
+      session, content_name, rtcp);
+  if (!data_channel->Init()) {
+    LOG(LS_WARNING) << "Failed to init data channel.";
+    delete data_channel;
+    return NULL;
+  }
+  data_channels_.push_back(data_channel);
+  return data_channel;
+}
+
+void ChannelManager::DestroyDataChannel(DataChannel* data_channel) {
+  if (data_channel) {
+    worker_thread_->Invoke<void>(
+        Bind(&ChannelManager::DestroyDataChannel_w, this, data_channel));
+  }
+}
+
+void ChannelManager::DestroyDataChannel_w(DataChannel* data_channel) {
+  // Destroy data channel.
+  ASSERT(initialized_);
+  DataChannels::iterator it = std::find(data_channels_.begin(),
+      data_channels_.end(), data_channel);
+  ASSERT(it != data_channels_.end());
+  if (it == data_channels_.end())
+    return;
+
+  data_channels_.erase(it);
+  delete data_channel;
+}
+
+Soundclip* ChannelManager::CreateSoundclip() {
+  return worker_thread_->Invoke<Soundclip*>(
+      Bind(&ChannelManager::CreateSoundclip_w, this));
+}
+
+Soundclip* ChannelManager::CreateSoundclip_w() {
+  ASSERT(initialized_);
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+  SoundclipMedia* soundclip_media = media_engine_->CreateSoundclip();
+  if (!soundclip_media) {
+    return NULL;
+  }
+
+  Soundclip* soundclip = new Soundclip(worker_thread_, soundclip_media);
+  soundclips_.push_back(soundclip);
+  return soundclip;
+}
+
+void ChannelManager::DestroySoundclip(Soundclip* soundclip) {
+  if (soundclip) {
+    worker_thread_->Invoke<void>(
+        Bind(&ChannelManager::DestroySoundclip_w, this, soundclip));
+  }
+}
+
+void ChannelManager::DestroySoundclip_w(Soundclip* soundclip) {
+  // Destroy soundclip.
+  ASSERT(initialized_);
+  Soundclips::iterator it = std::find(soundclips_.begin(),
+      soundclips_.end(), soundclip);
+  ASSERT(it != soundclips_.end());
+  if (it == soundclips_.end())
+    return;
+
+  soundclips_.erase(it);
+  delete soundclip;
+}
+
+bool ChannelManager::GetAudioOptions(std::string* in_name,
+                                     std::string* out_name, int* opts) {
+  if (in_name)
+    *in_name = audio_in_device_;
+  if (out_name)
+    *out_name = audio_out_device_;
+  if (opts)
+    *opts = audio_options_;
+  return true;
+}
+
+bool ChannelManager::SetAudioOptions(const std::string& in_name,
+                                     const std::string& out_name, int opts) {
+  return SetAudioOptions(in_name, out_name, opts, audio_delay_offset_);
+}
+
+bool ChannelManager::SetAudioOptions(const std::string& in_name,
+                                     const std::string& out_name, int opts,
+                                     int delay_offset) {
+  // Get device ids from DeviceManager.
+  Device in_dev, out_dev;
+  if (!device_manager_->GetAudioInputDevice(in_name, &in_dev)) {
+    LOG(LS_WARNING) << "Failed to GetAudioInputDevice: " << in_name;
+    return false;
+  }
+  if (!device_manager_->GetAudioOutputDevice(out_name, &out_dev)) {
+    LOG(LS_WARNING) << "Failed to GetAudioOutputDevice: " << out_name;
+    return false;
+  }
+
+  // If we're initialized, pass the settings to the media engine.
+  bool ret = true;
+  if (initialized_) {
+    ret = worker_thread_->Invoke<bool>(
+        Bind(&ChannelManager::SetAudioOptions_w, this,
+             opts, delay_offset, &in_dev, &out_dev));
+  }
+
+  // If all worked well, save the values for use in GetAudioOptions.
+  if (ret) {
+    audio_options_ = opts;
+    audio_in_device_ = in_name;
+    audio_out_device_ = out_name;
+    audio_delay_offset_ = delay_offset;
+  }
+  return ret;
+}
+
+bool ChannelManager::SetAudioOptions_w(int opts, int delay_offset,
+    const Device* in_dev, const Device* out_dev) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  ASSERT(initialized_);
+
+  // Set audio options
+  bool ret = media_engine_->SetAudioOptions(opts);
+
+  if (ret) {
+    ret = media_engine_->SetAudioDelayOffset(delay_offset);
+  }
+
+  // Set the audio devices
+  if (ret) {
+    ret = media_engine_->SetSoundDevices(in_dev, out_dev);
+  }
+
+  return ret;
+}
+
+bool ChannelManager::GetOutputVolume(int* level) {
+  if (!initialized_) {
+    return false;
+  }
+  return worker_thread_->Invoke<bool>(
+      Bind(&MediaEngineInterface::GetOutputVolume, media_engine_.get(), level));
+}
+
+bool ChannelManager::SetOutputVolume(int level) {
+  bool ret = level >= 0 && level <= 255;
+  if (initialized_) {
+    ret &= worker_thread_->Invoke<bool>(
+        Bind(&MediaEngineInterface::SetOutputVolume,
+             media_engine_.get(), level));
+  }
+
+  if (ret) {
+    audio_output_volume_ = level;
+  }
+
+  return ret;
+}
+
+bool ChannelManager::IsSameCapturer(const std::string& capturer_name,
+                                    VideoCapturer* capturer) {
+  if (capturer == NULL) {
+    return false;
+  }
+  Device device;
+  if (!device_manager_->GetVideoCaptureDevice(capturer_name, &device)) {
+    return false;
+  }
+  return capturer->GetId() == device.id;
+}
+
+bool ChannelManager::GetCaptureDevice(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::SetCaptureDevice(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;
+    }
+    ret = false;
+  }
+
+  // If we're running, tell the media engine about it.
+  if (initialized_ && ret) {
+    ret = worker_thread_->Invoke<bool>(
+        Bind(&ChannelManager::SetCaptureDevice_w, this, &device));
+  }
+
+  // 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;
+}
+
+VideoCapturer* ChannelManager::CreateVideoCapturer() {
+  Device device;
+  if (!device_manager_->GetVideoCaptureDevice(camera_device_, &device)) {
+    if (!camera_device_.empty()) {
+      LOG(LS_WARNING) << "Device manager can't find camera: " << camera_device_;
+    }
+    return NULL;
+  }
+  return device_manager_->CreateVideoCapturer(device);
+}
+
+bool ChannelManager::SetCaptureDevice_w(const Device* cam_device) {
+  ASSERT(worker_thread_ == talk_base::Thread::Current());
+  ASSERT(initialized_);
+
+  if (!cam_device) {
+    video_device_name_.clear();
+    return true;
+  }
+  video_device_name_ = cam_device->name;
+  return true;
+}
+
+bool ChannelManager::SetDefaultVideoEncoderConfig(const VideoEncoderConfig& c) {
+  bool ret = true;
+  if (initialized_) {
+    ret = worker_thread_->Invoke<bool>(
+        Bind(&MediaEngineInterface::SetDefaultVideoEncoderConfig,
+             media_engine_.get(), c));
+  }
+  if (ret) {
+    default_video_encoder_config_ = c;
+  }
+  return ret;
+}
+
+bool ChannelManager::SetLocalMonitor(bool enable) {
+  bool ret = initialized_ && worker_thread_->Invoke<bool>(
+      Bind(&MediaEngineInterface::SetLocalMonitor,
+           media_engine_.get(), enable));
+  if (ret) {
+    monitoring_ = enable;
+  }
+  return ret;
+}
+
+bool ChannelManager::SetLocalRenderer(VideoRenderer* renderer) {
+  bool ret = true;
+  if (initialized_) {
+    ret = worker_thread_->Invoke<bool>(
+        Bind(&MediaEngineInterface::SetLocalRenderer,
+             media_engine_.get(), renderer));
+  }
+  if (ret) {
+    local_renderer_ = renderer;
+  }
+  return ret;
+}
+
+bool ChannelManager::SetVideoCapturer(VideoCapturer* capturer) {
+  bool ret = true;
+  if (initialized_) {
+    ret = worker_thread_->Invoke<bool>(
+        Bind(&MediaEngineInterface::SetVideoCapturer,
+             media_engine_.get(), capturer));
+  }
+  return ret;
+}
+
+bool ChannelManager::SetVideoCapture(bool capture) {
+  bool ret = initialized_ && worker_thread_->Invoke<bool>(
+      Bind(&MediaEngineInterface::SetVideoCapture,
+           media_engine_.get(), capture));
+  if (ret) {
+    capturing_ = capture;
+  }
+  return ret;
+}
+
+void ChannelManager::SetVoiceLogging(int level, const char* filter) {
+  if (initialized_) {
+    worker_thread_->Invoke<void>(
+        Bind(&MediaEngineInterface::SetVoiceLogging,
+             media_engine_.get(), level, filter));
+  } else {
+    media_engine_->SetVoiceLogging(level, filter);
+  }
+}
+
+void ChannelManager::SetVideoLogging(int level, const char* filter) {
+  if (initialized_) {
+    worker_thread_->Invoke<void>(
+        Bind(&MediaEngineInterface::SetVideoLogging,
+             media_engine_.get(), level, filter));
+  } else {
+    media_engine_->SetVideoLogging(level, filter);
+  }
+}
+
+// TODO(janahan): 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(VideoCapturer* capturer,
+                                            VideoProcessor* processor) {
+  return initialized_ && worker_thread_->Invoke<bool>(
+      Bind(&ChannelManager::RegisterVideoProcessor_w, this,
+           capturer, processor));
+}
+
+bool ChannelManager::RegisterVideoProcessor_w(VideoCapturer* capturer,
+                                              VideoProcessor* processor) {
+  return capture_manager_->AddVideoProcessor(capturer, processor);
+}
+
+bool ChannelManager::UnregisterVideoProcessor(VideoCapturer* capturer,
+                                              VideoProcessor* processor) {
+  return initialized_ && worker_thread_->Invoke<bool>(
+      Bind(&ChannelManager::UnregisterVideoProcessor_w, this,
+           capturer, processor));
+}
+
+bool ChannelManager::UnregisterVideoProcessor_w(VideoCapturer* capturer,
+                                                VideoProcessor* processor) {
+  return capture_manager_->RemoveVideoProcessor(capturer, processor);
+}
+
+bool ChannelManager::RegisterVoiceProcessor(
+    uint32 ssrc,
+    VoiceProcessor* processor,
+    MediaProcessorDirection direction) {
+  return initialized_ && worker_thread_->Invoke<bool>(
+      Bind(&MediaEngineInterface::RegisterVoiceProcessor, media_engine_.get(),
+           ssrc, processor, direction));
+}
+
+bool ChannelManager::UnregisterVoiceProcessor(
+    uint32 ssrc,
+    VoiceProcessor* processor,
+    MediaProcessorDirection direction) {
+  return initialized_ && worker_thread_->Invoke<bool>(
+      Bind(&MediaEngineInterface::UnregisterVoiceProcessor,
+           media_engine_.get(), ssrc, processor, direction));
+}
+
+// The following are done in the new "CaptureManager" style that
+// all local video capturers, processors, and managers should move
+// to.
+// TODO(pthatcher): Add more of the CaptureManager interface.
+bool ChannelManager::StartVideoCapture(
+    VideoCapturer* capturer, const VideoFormat& video_format) {
+  return initialized_ && worker_thread_->Invoke<bool>(
+      Bind(&CaptureManager::StartVideoCapture,
+           capture_manager_.get(), capturer, video_format));
+}
+
+bool ChannelManager::MuteToBlackThenPause(
+    VideoCapturer* video_capturer, bool muted) {
+  if (!initialized_) {
+    return false;
+  }
+  worker_thread_->Invoke<void>(
+      Bind(&VideoCapturer::MuteToBlackThenPause, video_capturer, muted));
+  return true;
+}
+
+bool ChannelManager::StopVideoCapture(
+    VideoCapturer* capturer, const VideoFormat& video_format) {
+  return initialized_ && worker_thread_->Invoke<bool>(
+      Bind(&CaptureManager::StopVideoCapture,
+           capture_manager_.get(), capturer, video_format));
+}
+
+bool ChannelManager::RestartVideoCapture(
+    VideoCapturer* video_capturer,
+    const VideoFormat& previous_format,
+    const VideoFormat& desired_format,
+    CaptureManager::RestartOptions options) {
+  return initialized_ && worker_thread_->Invoke<bool>(
+      Bind(&CaptureManager::RestartVideoCapture, capture_manager_.get(),
+           video_capturer, previous_format, desired_format, options));
+}
+
+bool ChannelManager::AddVideoRenderer(
+    VideoCapturer* capturer, VideoRenderer* renderer) {
+  return initialized_ && worker_thread_->Invoke<bool>(
+      Bind(&CaptureManager::AddVideoRenderer,
+           capture_manager_.get(), capturer, renderer));
+}
+
+bool ChannelManager::RemoveVideoRenderer(
+    VideoCapturer* capturer, VideoRenderer* renderer) {
+  return initialized_ && worker_thread_->Invoke<bool>(
+      Bind(&CaptureManager::RemoveVideoRenderer,
+           capture_manager_.get(), capturer, renderer));
+}
+
+bool ChannelManager::IsScreencastRunning() const {
+  return initialized_ && worker_thread_->Invoke<bool>(
+      Bind(&ChannelManager::IsScreencastRunning_w, this));
+}
+
+bool ChannelManager::IsScreencastRunning_w() const {
+  VideoChannels::const_iterator it = video_channels_.begin();
+  for ( ; it != video_channels_.end(); ++it) {
+    if ((*it) && (*it)->IsScreencasting()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void ChannelManager::OnVideoCaptureStateChange(VideoCapturer* capturer,
+                                               CaptureState result) {
+  // TODO(whyuan): Check capturer and signal failure only for camera video, not
+  // screencast.
+  capturing_ = result == CS_RUNNING;
+  main_thread_->Post(this, MSG_VIDEOCAPTURESTATE,
+                     new CaptureStateParams(capturer, result));
+}
+
+void ChannelManager::OnMessage(talk_base::Message* message) {
+  switch (message->message_id) {
+    case MSG_VIDEOCAPTURESTATE: {
+      CaptureStateParams* data =
+          static_cast<CaptureStateParams*>(message->pdata);
+      SignalVideoCaptureStateChange(data->capturer, data->state);
+      delete data;
+      break;
+    }
+  }
+}
+
+
+static void GetDeviceNames(const std::vector<Device>& devs,
+                           std::vector<std::string>* names) {
+  names->clear();
+  for (size_t i = 0; i < devs.size(); ++i) {
+    names->push_back(devs[i].name);
+  }
+}
+
+bool ChannelManager::GetAudioInputDevices(std::vector<std::string>* names) {
+  names->clear();
+  std::vector<Device> devs;
+  bool ret = device_manager_->GetAudioInputDevices(&devs);
+  if (ret)
+    GetDeviceNames(devs, names);
+
+  return ret;
+}
+
+bool ChannelManager::GetAudioOutputDevices(std::vector<std::string>* names) {
+  names->clear();
+  std::vector<Device> devs;
+  bool ret = device_manager_->GetAudioOutputDevices(&devs);
+  if (ret)
+    GetDeviceNames(devs, names);
+
+  return ret;
+}
+
+bool ChannelManager::GetVideoCaptureDevices(std::vector<std::string>* names) {
+  names->clear();
+  std::vector<Device> devs;
+  bool ret = device_manager_->GetVideoCaptureDevices(&devs);
+  if (ret)
+    GetDeviceNames(devs, names);
+
+  return ret;
+}
+
+void ChannelManager::SetVideoCaptureDeviceMaxFormat(
+    const std::string& usb_id,
+    const VideoFormat& max_format) {
+  device_manager_->SetVideoCaptureDeviceMaxFormat(usb_id, max_format);
+}
+
+VideoFormat ChannelManager::GetStartCaptureFormat() {
+  return worker_thread_->Invoke<VideoFormat>(
+      Bind(&MediaEngineInterface::GetStartCaptureFormat, media_engine_.get()));
+}
+
+}  // namespace cricket
diff --git a/talk/session/media/channelmanager.h b/talk/session/media/channelmanager.h
new file mode 100644
index 0000000..0c57737
--- /dev/null
+++ b/talk/session/media/channelmanager.h
@@ -0,0 +1,310 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_MEDIA_CHANNELMANAGER_H_
+#define TALK_SESSION_MEDIA_CHANNELMANAGER_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/criticalsection.h"
+#include "talk/base/sigslotrepeater.h"
+#include "talk/base/thread.h"
+#include "talk/media/base/capturemanager.h"
+#include "talk/media/base/mediaengine.h"
+#include "talk/p2p/base/session.h"
+#include "talk/session/media/voicechannel.h"
+
+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
+// voice and video channels; by doing so, it can temporarily pause all the
+// channels when a new audio or video device is chosen. The voice and video
+// channels are stored in separate vectors, to easily allow operations on just
+// voice or just video channels.
+// ChannelManager also allows the application to discover what devices it has
+// using device manager.
+class ChannelManager : public talk_base::MessageHandler,
+                       public sigslot::has_slots<> {
+ public:
+#if !defined(DISABLE_MEDIA_ENGINE_FACTORY)
+  // Creates the channel manager, and specifies the worker thread to use.
+  explicit ChannelManager(talk_base::Thread* worker);
+#endif
+
+  // For testing purposes. Allows the media engine and data media
+  // engine and dev manager to be mocks.  The ChannelManager takes
+  // ownership of these objects.
+  ChannelManager(MediaEngineInterface* me,
+                 DataEngineInterface* dme,
+                 DeviceManagerInterface* dm,
+                 CaptureManager* cm,
+                 talk_base::Thread* worker);
+  // Same as above, but gives an easier default DataEngine.
+  ChannelManager(MediaEngineInterface* me,
+                 DeviceManagerInterface* dm,
+                 talk_base::Thread* worker);
+  ~ChannelManager();
+
+  // Accessors for the worker thread, allowing it to be set after construction,
+  // but before Init. set_worker_thread will return false if called after Init.
+  talk_base::Thread* worker_thread() const { return worker_thread_; }
+  bool set_worker_thread(talk_base::Thread* thread) {
+    if (initialized_) return false;
+    worker_thread_ = thread;
+    return true;
+  }
+
+  // Gets capabilities. Can be called prior to starting the media engine.
+  int GetCapabilities();
+
+  // Retrieves the list of supported audio & video codec types.
+  // Can be called before starting the media engine.
+  void GetSupportedAudioCodecs(std::vector<AudioCodec>* codecs) const;
+  void GetSupportedAudioRtpHeaderExtensions(RtpHeaderExtensions* ext) const;
+  void GetSupportedVideoCodecs(std::vector<VideoCodec>* codecs) const;
+  void GetSupportedVideoRtpHeaderExtensions(RtpHeaderExtensions* ext) const;
+  void GetSupportedDataCodecs(std::vector<DataCodec>* codecs) const;
+
+  // Indicates whether the media engine is started.
+  bool initialized() const { return initialized_; }
+  // Starts up the media engine.
+  bool Init();
+  // Shuts down the media engine.
+  void Terminate();
+
+  // The operations below all occur on the worker thread.
+
+  // Creates a voice channel, to be associated with the specified session.
+  VoiceChannel* CreateVoiceChannel(
+      BaseSession* session, const std::string& content_name, bool rtcp);
+  // Destroys a voice channel created with the Create API.
+  void DestroyVoiceChannel(VoiceChannel* voice_channel);
+  // Creates a video channel, synced with the specified voice channel, and
+  // associated with the specified session.
+  VideoChannel* CreateVideoChannel(
+      BaseSession* session, const std::string& content_name, bool rtcp,
+      VoiceChannel* voice_channel);
+  // Destroys a video channel created with the Create API.
+  void DestroyVideoChannel(VideoChannel* video_channel);
+  DataChannel* CreateDataChannel(
+      BaseSession* session, const std::string& content_name,
+      bool rtcp, DataChannelType data_channel_type);
+  // Destroys a data channel created with the Create API.
+  void DestroyDataChannel(DataChannel* data_channel);
+
+  // Creates a soundclip.
+  Soundclip* CreateSoundclip();
+  // Destroys a soundclip created with the Create API.
+  void DestroySoundclip(Soundclip* soundclip);
+
+  // Indicates whether any channels exist.
+  bool has_channels() const {
+    return (!voice_channels_.empty() || !video_channels_.empty() ||
+            !soundclips_.empty());
+  }
+
+  // Configures the audio and video devices. A null pointer can be passed to
+  // GetAudioOptions() for any parameter of no interest.
+  bool GetAudioOptions(std::string* wave_in_device,
+                       std::string* wave_out_device, int* opts);
+  bool SetAudioOptions(const std::string& wave_in_device,
+                       const std::string& wave_out_device, int opts);
+  bool GetOutputVolume(int* level);
+  bool SetOutputVolume(int level);
+  bool IsSameCapturer(const std::string& capturer_name,
+                      VideoCapturer* capturer);
+  bool GetCaptureDevice(std::string* cam_device);
+  // Create capturer based on what has been set in SetCaptureDevice().
+  VideoCapturer* CreateVideoCapturer();
+  bool SetCaptureDevice(const std::string& cam_device);
+  bool SetDefaultVideoEncoderConfig(const VideoEncoderConfig& config);
+  // RTX will be enabled/disabled in engines that support it. The supporting
+  // engines will start offering an RTX codec. Must be called before Init().
+  bool SetVideoRtxEnabled(bool enable);
+
+  // Starts/stops the local microphone and enables polling of the input level.
+  bool SetLocalMonitor(bool enable);
+  bool monitoring() const { return monitoring_; }
+  // Sets the local renderer where to renderer the local camera.
+  bool SetLocalRenderer(VideoRenderer* renderer);
+  // 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);
+  // Starts and stops the local camera and renders it to the local renderer.
+  bool SetVideoCapture(bool capture);
+  bool capturing() const { return capturing_; }
+
+  // Configures the logging output of the mediaengine(s).
+  void SetVoiceLogging(int level, const char* filter);
+  void SetVideoLogging(int level, const char* filter);
+
+  // The 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(VideoCapturer* capturer,
+                              VideoProcessor* processor);
+  bool UnregisterVideoProcessor(VideoCapturer* capturer,
+                                VideoProcessor* processor);
+  bool RegisterVoiceProcessor(uint32 ssrc,
+                              VoiceProcessor* processor,
+                              MediaProcessorDirection direction);
+  bool UnregisterVoiceProcessor(uint32 ssrc,
+                                VoiceProcessor* processor,
+                                MediaProcessorDirection direction);
+  // The following are done in the new "CaptureManager" style that
+  // all local video capturers, processors, and managers should move to.
+  // TODO(pthatcher): Make methods nicer by having start return a handle that
+  // can be used for stop and restart, rather than needing to pass around
+  // formats a a pseudo-handle.
+  bool StartVideoCapture(VideoCapturer* video_capturer,
+                         const VideoFormat& video_format);
+  // When muting, produce black frames then pause the camera.
+  // When unmuting, start the camera. Camera starts unmuted.
+  bool MuteToBlackThenPause(VideoCapturer* video_capturer, bool muted);
+  bool StopVideoCapture(VideoCapturer* video_capturer,
+                        const VideoFormat& video_format);
+  bool RestartVideoCapture(VideoCapturer* video_capturer,
+                           const VideoFormat& previous_format,
+                           const VideoFormat& desired_format,
+                           CaptureManager::RestartOptions options);
+
+  bool AddVideoRenderer(VideoCapturer* capturer, VideoRenderer* renderer);
+  bool RemoveVideoRenderer(VideoCapturer* capturer, VideoRenderer* renderer);
+  bool IsScreencastRunning() const;
+
+  // The operations below occur on the main thread.
+
+  bool GetAudioInputDevices(std::vector<std::string>* names);
+  bool GetAudioOutputDevices(std::vector<std::string>* names);
+  bool GetVideoCaptureDevices(std::vector<std::string>* names);
+  void SetVideoCaptureDeviceMaxFormat(const std::string& usb_id,
+                                      const VideoFormat& max_format);
+
+  sigslot::repeater0<> SignalDevicesChange;
+  sigslot::signal2<VideoCapturer*, CaptureState> SignalVideoCaptureStateChange;
+
+  // Returns the current selected device. Note: Subtly different from
+  // GetCaptureDevice(). See member video_device_ for more details.
+  // This API is mainly a hook used by unittests.
+  const std::string& video_device_name() const { return video_device_name_; }
+
+  // TODO(hellner): Remove this function once the engine capturer has been
+  // removed.
+  VideoFormat GetStartCaptureFormat();
+ protected:
+  // Adds non-transient parameters which can only be changed through the
+  // options store.
+  bool SetAudioOptions(const std::string& wave_in_device,
+                       const std::string& wave_out_device, int opts,
+                       int delay_offset);
+  int audio_delay_offset() const { return audio_delay_offset_; }
+
+ private:
+  typedef std::vector<VoiceChannel*> VoiceChannels;
+  typedef std::vector<VideoChannel*> VideoChannels;
+  typedef std::vector<DataChannel*> DataChannels;
+  typedef std::vector<Soundclip*> Soundclips;
+
+  void Construct(MediaEngineInterface* me,
+                 DataEngineInterface* dme,
+                 DeviceManagerInterface* dm,
+                 CaptureManager* cm,
+                 talk_base::Thread* worker_thread);
+  void Terminate_w();
+  VoiceChannel* CreateVoiceChannel_w(
+      BaseSession* session, const std::string& content_name, bool rtcp);
+  void DestroyVoiceChannel_w(VoiceChannel* voice_channel);
+  VideoChannel* CreateVideoChannel_w(
+      BaseSession* session, const std::string& content_name, bool rtcp,
+      VoiceChannel* voice_channel);
+  void DestroyVideoChannel_w(VideoChannel* video_channel);
+  DataChannel* CreateDataChannel_w(
+      BaseSession* session, const std::string& content_name,
+      bool rtcp, DataChannelType data_channel_type);
+  void DestroyDataChannel_w(DataChannel* data_channel);
+  Soundclip* CreateSoundclip_w();
+  void DestroySoundclip_w(Soundclip* soundclip);
+  bool SetAudioOptions_w(int opts, int delay_offset, const Device* in_dev,
+                         const Device* out_dev);
+  bool SetCaptureDevice_w(const Device* cam_device);
+  void OnVideoCaptureStateChange(VideoCapturer* capturer,
+                                 CaptureState result);
+  bool RegisterVideoProcessor_w(VideoCapturer* capturer,
+                                VideoProcessor* processor);
+  bool UnregisterVideoProcessor_w(VideoCapturer* capturer,
+                                  VideoProcessor* processor);
+  bool IsScreencastRunning_w() const;
+  virtual void OnMessage(talk_base::Message *message);
+
+  talk_base::scoped_ptr<MediaEngineInterface> media_engine_;
+  talk_base::scoped_ptr<DataEngineInterface> data_media_engine_;
+  talk_base::scoped_ptr<DeviceManagerInterface> device_manager_;
+  talk_base::scoped_ptr<CaptureManager> capture_manager_;
+  bool initialized_;
+  talk_base::Thread* main_thread_;
+  talk_base::Thread* worker_thread_;
+
+  VoiceChannels voice_channels_;
+  VideoChannels video_channels_;
+  DataChannels data_channels_;
+  Soundclips soundclips_;
+
+  std::string audio_in_device_;
+  std::string audio_out_device_;
+  int audio_options_;
+  int audio_delay_offset_;
+  int audio_output_volume_;
+  std::string camera_device_;
+  VideoEncoderConfig default_video_encoder_config_;
+  VideoRenderer* local_renderer_;
+  bool enable_rtx_;
+
+  bool capturing_;
+  bool monitoring_;
+
+  talk_base::scoped_ptr<VideoCapturer> video_capturer_;
+
+  // String containing currently set device. Note that this string is subtly
+  // different from camera_device_. E.g. camera_device_ will list unplugged
+  // but selected devices while this sting will be empty or contain current
+  // selected device.
+  // TODO(hellner): refactor the code such that there is no need to keep two
+  // strings for video devices that have subtle differences in behavior.
+  std::string video_device_name_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_MEDIA_CHANNELMANAGER_H_
diff --git a/talk/session/media/channelmanager_unittest.cc b/talk/session/media/channelmanager_unittest.cc
new file mode 100644
index 0000000..20db58d
--- /dev/null
+++ b/talk/session/media/channelmanager_unittest.cc
@@ -0,0 +1,610 @@
+// 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/media/base/fakecapturemanager.h"
+#include "talk/media/base/fakemediaengine.h"
+#include "talk/media/base/fakemediaprocessor.h"
+#include "talk/media/base/nullvideorenderer.h"
+#include "talk/media/devices/fakedevicemanager.h"
+#include "talk/media/base/testutils.h"
+#include "talk/p2p/base/fakesession.h"
+#include "talk/session/media/channelmanager.h"
+
+namespace cricket {
+
+static const AudioCodec kAudioCodecs[] = {
+  AudioCodec(97, "voice", 1, 2, 3, 0),
+  AudioCodec(110, "CELT", 32000, 48000, 2, 0),
+  AudioCodec(111, "OPUS", 48000, 32000, 2, 0),
+};
+
+static const VideoCodec kVideoCodecs[] = {
+  VideoCodec(99, "H264", 100, 200, 300, 0),
+  VideoCodec(100, "VP8", 100, 200, 300, 0),
+  VideoCodec(96, "rtx", 100, 200, 300, 0),
+};
+
+class ChannelManagerTest : public testing::Test {
+ protected:
+  ChannelManagerTest() : fme_(NULL), fdm_(NULL), fcm_(NULL), cm_(NULL) {
+  }
+
+  virtual void SetUp() {
+    fme_ = new cricket::FakeMediaEngine();
+    fme_->SetAudioCodecs(MAKE_VECTOR(kAudioCodecs));
+    fme_->SetVideoCodecs(MAKE_VECTOR(kVideoCodecs));
+    fdme_ = new cricket::FakeDataEngine();
+    fdm_ = new cricket::FakeDeviceManager();
+    fcm_ = new cricket::FakeCaptureManager();
+    cm_ = new cricket::ChannelManager(
+        fme_, fdme_, fdm_, fcm_, talk_base::Thread::Current());
+    session_ = new cricket::FakeSession(true);
+
+    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;
+    fcm_ = NULL;
+    fdme_ = NULL;
+    fme_ = NULL;
+  }
+
+  talk_base::Thread worker_;
+  cricket::FakeMediaEngine* fme_;
+  cricket::FakeDataEngine* fdme_;
+  cricket::FakeDeviceManager* fdm_;
+  cricket::FakeCaptureManager* fcm_;
+  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);
+  cricket::DataChannel* data_channel =
+      cm_->CreateDataChannel(session_, cricket::CN_DATA,
+                             false, cricket::DCT_RTP);
+  EXPECT_TRUE(data_channel != NULL);
+  cm_->DestroyVideoChannel(video_channel);
+  cm_->DestroyVoiceChannel(voice_channel);
+  cm_->DestroyDataChannel(data_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);
+  cricket::DataChannel* data_channel =
+      cm_->CreateDataChannel(session_, cricket::CN_DATA,
+                             false, cricket::DCT_RTP);
+  EXPECT_TRUE(data_channel != NULL);
+  cm_->DestroyVideoChannel(video_channel);
+  cm_->DestroyVoiceChannel(voice_channel);
+  cm_->DestroyDataChannel(data_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", cricket::ICE_CANDIDATE_COMPONENT_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);
+  cricket::DataChannel* data_channel =
+      cm_->CreateDataChannel(session_, cricket::CN_DATA,
+                             false, cricket::DCT_RTP);
+  EXPECT_TRUE(data_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());
+  EXPECT_EQ(0, fme_->audio_delay_offset());
+  EXPECT_EQ(cricket::MediaEngineInterface::kDefaultAudioDelayOffset,
+            fme_->audio_delay_offset());
+}
+
+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, GetAudioOptionsWithNullParameters) {
+  std::string audio_in, audio_out;
+  int opts;
+  EXPECT_TRUE(cm_->SetAudioOptions("audio-in2", "audio-out2", 0x1));
+  EXPECT_TRUE(cm_->GetAudioOptions(&audio_in, NULL, NULL));
+  EXPECT_EQ("audio-in2", audio_in);
+  EXPECT_TRUE(cm_->GetAudioOptions(NULL, &audio_out, NULL));
+  EXPECT_EQ("audio-out2", audio_out);
+  EXPECT_TRUE(cm_->GetAudioOptions(NULL, NULL, &opts));
+  EXPECT_EQ(0x1, opts);
+}
+
+TEST_F(ChannelManagerTest, SetAudioOptions) {
+  // Test initial state.
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_EQ(std::string(cricket::DeviceManagerInterface::kDefaultDeviceName),
+            fme_->audio_in_device());
+  EXPECT_EQ(std::string(cricket::DeviceManagerInterface::kDefaultDeviceName),
+            fme_->audio_out_device());
+  EXPECT_EQ(cricket::MediaEngineInterface::DEFAULT_AUDIO_OPTIONS,
+            fme_->audio_options());
+  EXPECT_EQ(cricket::MediaEngineInterface::kDefaultAudioDelayOffset,
+            fme_->audio_delay_offset());
+  // Test setting defaults.
+  EXPECT_TRUE(cm_->SetAudioOptions("", "",
+      cricket::MediaEngineInterface::DEFAULT_AUDIO_OPTIONS));
+  EXPECT_EQ("", fme_->audio_in_device());
+  EXPECT_EQ("", fme_->audio_out_device());
+  EXPECT_EQ(cricket::MediaEngineInterface::DEFAULT_AUDIO_OPTIONS,
+            fme_->audio_options());
+  EXPECT_EQ(cricket::MediaEngineInterface::kDefaultAudioDelayOffset,
+            fme_->audio_delay_offset());
+  // 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());
+  EXPECT_EQ(cricket::MediaEngineInterface::kDefaultAudioDelayOffset,
+            fme_->audio_delay_offset());
+  // 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, SetCaptureDeviceBeforeInit) {
+  // Test that values that we set before Init are applied.
+  EXPECT_TRUE(cm_->SetCaptureDevice("video-in2"));
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_EQ("video-in2", cm_->video_device_name());
+}
+
+TEST_F(ChannelManagerTest, GetCaptureDeviceBeforeInit) {
+  std::string video_in;
+  // Test that GetCaptureDevice works before Init.
+  EXPECT_TRUE(cm_->SetCaptureDevice("video-in1"));
+  EXPECT_TRUE(cm_->GetCaptureDevice(&video_in));
+  EXPECT_EQ("video-in1", video_in);
+  // Test that options set before Init can be gotten after Init.
+  EXPECT_TRUE(cm_->SetCaptureDevice("video-in2"));
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_TRUE(cm_->GetCaptureDevice(&video_in));
+  EXPECT_EQ("video-in2", video_in);
+}
+
+TEST_F(ChannelManagerTest, SetCaptureDevice) {
+  // Test setting defaults.
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_TRUE(cm_->SetCaptureDevice(""));  // will use DeviceManager default
+  EXPECT_EQ("video-in1", cm_->video_device_name());
+  // Test setting specific values.
+  EXPECT_TRUE(cm_->SetCaptureDevice("video-in2"));
+  EXPECT_EQ("video-in2", cm_->video_device_name());
+  // TODO(juberti): 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;
+  EXPECT_TRUE(cm_->GetAudioOptions(&audio_in, &audio_out, NULL));
+  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, NULL));
+  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, SetCaptureDeviceUnplugPlugOneCamera) {
+  // 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_->SetCaptureDevice("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("", cm_->video_device_name());
+  // The channel manager keeps the user preference "video-in".
+  std::string video_in;
+  EXPECT_TRUE(cm_->GetCaptureDevice(&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", cm_->video_device_name());
+  EXPECT_TRUE(cm_->GetCaptureDevice(&video_in));
+  EXPECT_EQ("video-in1", video_in);
+}
+
+// We have multiple cameras. Unplug the preferred, fall back to another camera.
+TEST_F(ChannelManagerTest, SetCaptureDeviceUnplugPlugTwoDevices) {
+  // Set video device to "video-in1" before init.
+  EXPECT_TRUE(cm_->SetCaptureDevice("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", cm_->video_device_name());
+  // The channel manager keeps the user preference "video-in".
+  std::string video_in;
+  EXPECT_TRUE(cm_->GetCaptureDevice(&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", cm_->video_device_name());
+  EXPECT_TRUE(cm_->GetCaptureDevice(&video_in));
+  EXPECT_EQ("video-in1", video_in);
+}
+
+TEST_F(ChannelManagerTest, GetCaptureDevice) {
+  std::string video_in;
+  // Test setting/getting defaults.
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_TRUE(cm_->SetCaptureDevice(""));
+  EXPECT_TRUE(cm_->GetCaptureDevice(&video_in));
+  EXPECT_EQ("video-in1", video_in);
+  // Test setting/getting specific values.
+  EXPECT_TRUE(cm_->SetCaptureDevice("video-in2"));
+  EXPECT_TRUE(cm_->GetCaptureDevice(&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_FALSE(cm_->SetVideoCapture(true));
+  EXPECT_FALSE(fme_->capture());
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_FALSE(fme_->capture());
+  EXPECT_TRUE(cm_->SetVideoCapture(true));
+  EXPECT_TRUE(fme_->capture());
+  EXPECT_TRUE(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_->voice_processor_registered(cricket::MPD_TX));
+  EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_RX));
+
+  EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_TX));
+  EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_RX));
+
+  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_->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_->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_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_->voice_processor_registered(cricket::MPD_TX));
+  EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_RX));
+}
+
+TEST_F(ChannelManagerTest, SetVideoRtxEnabled) {
+  std::vector<VideoCodec> codecs;
+  const VideoCodec rtx_codec(96, "rtx", 0, 0, 0, 0);
+
+  // By default RTX is disabled.
+  cm_->GetSupportedVideoCodecs(&codecs);
+  EXPECT_FALSE(ContainsMatchingCodec(codecs, rtx_codec));
+
+  // Enable and check.
+  EXPECT_TRUE(cm_->SetVideoRtxEnabled(true));
+  cm_->GetSupportedVideoCodecs(&codecs);
+  EXPECT_TRUE(ContainsMatchingCodec(codecs, rtx_codec));
+
+  // Disable and check.
+  EXPECT_TRUE(cm_->SetVideoRtxEnabled(false));
+  cm_->GetSupportedVideoCodecs(&codecs);
+  EXPECT_FALSE(ContainsMatchingCodec(codecs, rtx_codec));
+
+  // Cannot toggle rtx after initialization.
+  EXPECT_TRUE(cm_->Init());
+  EXPECT_FALSE(cm_->SetVideoRtxEnabled(true));
+  EXPECT_FALSE(cm_->SetVideoRtxEnabled(false));
+
+  // Can set again after terminate.
+  cm_->Terminate();
+  EXPECT_TRUE(cm_->SetVideoRtxEnabled(true));
+  cm_->GetSupportedVideoCodecs(&codecs);
+  EXPECT_TRUE(ContainsMatchingCodec(codecs, rtx_codec));
+}
+
+}  // namespace cricket
diff --git a/talk/session/media/currentspeakermonitor.cc b/talk/session/media/currentspeakermonitor.cc
new file mode 100644
index 0000000..1f3e093
--- /dev/null
+++ b/talk/session/media/currentspeakermonitor.cc
@@ -0,0 +1,208 @@
+/*
+ * 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/media/currentspeakermonitor.h"
+
+#include "talk/base/logging.h"
+#include "talk/session/media/call.h"
+
+namespace cricket {
+
+namespace {
+const int kMaxAudioLevel = 9;
+// To avoid overswitching, we disable switching for a period of time after a
+// switch is done.
+const int kDefaultMinTimeBetweenSwitches = 1000;
+}
+
+CurrentSpeakerMonitor::CurrentSpeakerMonitor(Call* call, BaseSession* session)
+    : started_(false),
+      call_(call),
+      session_(session),
+      current_speaker_ssrc_(0),
+      earliest_permitted_switch_time_(0),
+      min_time_between_switches_(kDefaultMinTimeBetweenSwitches) {
+}
+
+CurrentSpeakerMonitor::~CurrentSpeakerMonitor() {
+  Stop();
+}
+
+void CurrentSpeakerMonitor::Start() {
+  if (!started_) {
+    call_->SignalAudioMonitor.connect(
+        this, &CurrentSpeakerMonitor::OnAudioMonitor);
+    call_->SignalMediaStreamsUpdate.connect(
+        this, &CurrentSpeakerMonitor::OnMediaStreamsUpdate);
+
+    started_ = true;
+  }
+}
+
+void CurrentSpeakerMonitor::Stop() {
+  if (started_) {
+    call_->SignalAudioMonitor.disconnect(this);
+    call_->SignalMediaStreamsUpdate.disconnect(this);
+
+    started_ = false;
+    ssrc_to_speaking_state_map_.clear();
+    current_speaker_ssrc_ = 0;
+    earliest_permitted_switch_time_ = 0;
+  }
+}
+
+void CurrentSpeakerMonitor::set_min_time_between_switches(
+    uint32 min_time_between_switches) {
+  min_time_between_switches_ = min_time_between_switches;
+}
+
+void CurrentSpeakerMonitor::OnAudioMonitor(Call* call, const AudioInfo& info) {
+  std::map<uint32, int> active_ssrc_to_level_map;
+  cricket::AudioInfo::StreamList::const_iterator stream_list_it;
+  for (stream_list_it = info.active_streams.begin();
+       stream_list_it != info.active_streams.end(); ++stream_list_it) {
+    uint32 ssrc = stream_list_it->first;
+    active_ssrc_to_level_map[ssrc] = stream_list_it->second;
+
+    // It's possible we haven't yet added this source to our map.  If so,
+    // add it now with a "not speaking" state.
+    if (ssrc_to_speaking_state_map_.find(ssrc) ==
+        ssrc_to_speaking_state_map_.end()) {
+      ssrc_to_speaking_state_map_[ssrc] = SS_NOT_SPEAKING;
+    }
+  }
+
+  int max_level = 0;
+  uint32 loudest_speaker_ssrc = 0;
+
+  // Update the speaking states of all participants based on the new audio
+  // level information.  Also retain loudest speaker.
+  std::map<uint32, SpeakingState>::iterator state_it;
+  for (state_it = ssrc_to_speaking_state_map_.begin();
+       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. 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);
+    // Note that the stream map only contains streams with non-zero audio
+    // levels.
+    int level = (level_it != active_ssrc_to_level_map.end()) ?
+        level_it->second : 0;
+    switch (state_it->second) {
+      case SS_NOT_SPEAKING:
+        if (level > 0) {
+          // Reset level because we don't think they're really speaking.
+          level = 0;
+          state_it->second = SS_MIGHT_BE_SPEAKING;
+        } else {
+          // State unchanged.
+        }
+        break;
+      case SS_MIGHT_BE_SPEAKING:
+        if (level > 0) {
+          state_it->second = SS_SPEAKING;
+        } else {
+          state_it->second = SS_NOT_SPEAKING;
+        }
+        break;
+      case SS_SPEAKING:
+        if (level > 0) {
+          // State unchanged.
+        } else {
+          state_it->second = SS_WAS_SPEAKING_RECENTLY1;
+          if (is_previous_speaker) {
+            // Assume this is an inter-word silence and assign him the highest
+            // volume.
+            level = kMaxAudioLevel;
+          }
+        }
+        break;
+      case SS_WAS_SPEAKING_RECENTLY1:
+        if (level > 0) {
+          state_it->second = SS_SPEAKING;
+        } else {
+          state_it->second = SS_WAS_SPEAKING_RECENTLY2;
+          if (is_previous_speaker) {
+            // Assume this is an inter-word silence and assign him the highest
+            // volume.
+            level = kMaxAudioLevel;
+          }
+        }
+        break;
+      case SS_WAS_SPEAKING_RECENTLY2:
+        if (level > 0) {
+          state_it->second = SS_SPEAKING;
+        } else {
+          state_it->second = SS_NOT_SPEAKING;
+        }
+        break;
+    }
+
+    if (level > max_level) {
+      loudest_speaker_ssrc = state_it->first;
+      max_level = level;
+    } else if (level > 0 && level == max_level && is_previous_speaker) {
+      // Favor continuity of loudest speakers if audio levels are equal.
+      loudest_speaker_ssrc = state_it->first;
+    }
+  }
+
+  // We avoid over-switching by disabling switching for a period of time after
+  // a switch is done.
+  uint32 now = talk_base::Time();
+  if (earliest_permitted_switch_time_ <= now &&
+      current_speaker_ssrc_ != loudest_speaker_ssrc) {
+    current_speaker_ssrc_ = loudest_speaker_ssrc;
+    LOG(LS_INFO) << "Current speaker changed to " << current_speaker_ssrc_;
+    earliest_permitted_switch_time_ = now + min_time_between_switches_;
+    SignalUpdate(this, current_speaker_ssrc_);
+  }
+}
+
+void CurrentSpeakerMonitor::OnMediaStreamsUpdate(Call* call,
+                                                 Session* session,
+                                                 const MediaStreams& added,
+                                                 const MediaStreams& removed) {
+  if (call == call_ && session == session_) {
+    // 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;
+    }
+  }
+}
+
+}  // namespace cricket
diff --git a/talk/session/media/currentspeakermonitor.h b/talk/session/media/currentspeakermonitor.h
new file mode 100644
index 0000000..1781db5
--- /dev/null
+++ b/talk/session/media/currentspeakermonitor.h
@@ -0,0 +1,100 @@
+/*
+ * 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.
+ */
+
+// CurrentSpeakerMonitor monitors the audio levels for a session and determines
+// which participant is currently speaking.
+
+#ifndef TALK_SESSION_MEDIA_CURRENTSPEAKERMONITOR_H_
+#define TALK_SESSION_MEDIA_CURRENTSPEAKERMONITOR_H_
+
+#include <map>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/sigslot.h"
+
+namespace cricket {
+
+class BaseSession;
+class Call;
+class Session;
+struct AudioInfo;
+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.
+class CurrentSpeakerMonitor : public sigslot::has_slots<> {
+ public:
+  CurrentSpeakerMonitor(Call* call, BaseSession* session);
+  ~CurrentSpeakerMonitor();
+
+  BaseSession* session() const { return session_; }
+
+  void Start();
+  void Stop();
+
+  // Used by tests.  Note that the actual minimum time between switches
+  // enforced by the monitor will be the given value plus or minus the
+  // resolution of the system clock.
+  void set_min_time_between_switches(uint32 min_time_between_switches);
+
+  // This is fired when the current speaker changes, and provides his audio
+  // SSRC.  This only fires after the audio monitor on the underlying Call has
+  // been started.
+  sigslot::signal2<CurrentSpeakerMonitor*, uint32> SignalUpdate;
+
+ private:
+  void OnAudioMonitor(Call* call, const AudioInfo& info);
+  void OnMediaStreamsUpdate(Call* call,
+                            Session* session,
+                            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
+  // "twitchiness".
+  enum SpeakingState {
+    SS_NOT_SPEAKING,
+    SS_MIGHT_BE_SPEAKING,
+    SS_SPEAKING,
+    SS_WAS_SPEAKING_RECENTLY1,
+    SS_WAS_SPEAKING_RECENTLY2
+  };
+
+  bool started_;
+  Call* call_;
+  BaseSession* session_;
+  std::map<uint32, SpeakingState> ssrc_to_speaking_state_map_;
+  uint32 current_speaker_ssrc_;
+  // To prevent overswitching, switching is disabled for some time after a
+  // switch is made.  This gives us the earliest time a switch is permitted.
+  uint32 earliest_permitted_switch_time_;
+  uint32 min_time_between_switches_;
+};
+
+}
+
+#endif  // TALK_SESSION_MEDIA_CURRENTSPEAKERMONITOR_H_
diff --git a/talk/session/media/currentspeakermonitor_unittest.cc b/talk/session/media/currentspeakermonitor_unittest.cc
new file mode 100644
index 0000000..1306f89
--- /dev/null
+++ b/talk/session/media/currentspeakermonitor_unittest.cc
@@ -0,0 +1,232 @@
+/*
+ * 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/thread.h"
+#include "talk/session/media/call.h"
+#include "talk/session/media/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/media/mediamessages.cc b/talk/session/media/mediamessages.cc
new file mode 100644
index 0000000..6b5d03c
--- /dev/null
+++ b/talk/session/media/mediamessages.cc
@@ -0,0 +1,394 @@
+/*
+ * libjingle
+ * Copyright 2010 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Documentation is in mediamessages.h.
+ */
+
+#include "talk/session/media/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/media/mediasessionclient.h"
+#include "talk/xmllite/xmlelement.h"
+
+namespace cricket {
+
+namespace {
+
+// NOTE: There is no check here for duplicate streams, so check before
+// adding.
+void AddStream(std::vector<StreamParams>* streams, const StreamParams& stream) {
+  streams->push_back(stream);
+}
+
+bool ParseSsrc(const std::string& string, uint32* ssrc) {
+  return talk_base::FromString(string, ssrc);
+}
+
+bool ParseSsrc(const buzz::XmlElement* element, uint32* ssrc) {
+  if (element == NULL) {
+    return false;
+  }
+  return ParseSsrc(element->BodyText(), ssrc);
+}
+
+// Builds a <view> element according to the following spec:
+// goto/jinglemuc
+buzz::XmlElement* CreateViewElem(const std::string& name,
+                                 const std::string& type) {
+  buzz::XmlElement* view_elem =
+      new buzz::XmlElement(QN_JINGLE_DRAFT_VIEW, true);
+  view_elem->AddAttr(QN_NAME, name);
+  view_elem->SetAttr(QN_TYPE, type);
+  return view_elem;
+}
+
+buzz::XmlElement* CreateVideoViewElem(const std::string& content_name,
+                                      const std::string& type) {
+  return CreateViewElem(content_name, type);
+}
+
+buzz::XmlElement* CreateNoneVideoViewElem(const std::string& content_name) {
+  return CreateVideoViewElem(content_name, STR_JINGLE_DRAFT_VIEW_TYPE_NONE);
+}
+
+buzz::XmlElement* CreateStaticVideoViewElem(const std::string& content_name,
+                                            const StaticVideoView& view) {
+  buzz::XmlElement* view_elem =
+      CreateVideoViewElem(content_name, STR_JINGLE_DRAFT_VIEW_TYPE_STATIC);
+  AddXmlAttr(view_elem, QN_SSRC, view.selector.ssrc);
+
+  buzz::XmlElement* params_elem = new buzz::XmlElement(QN_JINGLE_DRAFT_PARAMS);
+  AddXmlAttr(params_elem, QN_WIDTH, view.width);
+  AddXmlAttr(params_elem, QN_HEIGHT, view.height);
+  AddXmlAttr(params_elem, QN_FRAMERATE, view.framerate);
+  AddXmlAttr(params_elem, QN_PREFERENCE, view.preference);
+  view_elem->AddElement(params_elem);
+
+  return view_elem;
+}
+
+}  //  namespace
+
+bool MediaStreams::GetAudioStream(
+    const StreamSelector& selector, StreamParams* stream) {
+  return GetStream(audio_, selector, stream);
+}
+
+bool MediaStreams::GetVideoStream(
+    const StreamSelector& selector, StreamParams* stream) {
+  return GetStream(video_, selector, stream);
+}
+
+bool MediaStreams::GetDataStream(
+    const StreamSelector& selector, StreamParams* stream) {
+  return GetStream(data_, selector, stream);
+}
+
+void MediaStreams::CopyFrom(const MediaStreams& streams) {
+  audio_ = streams.audio_;
+  video_ = streams.video_;
+  data_ = streams.data_;
+}
+
+void MediaStreams::AddAudioStream(const StreamParams& stream) {
+  AddStream(&audio_, stream);
+}
+
+void MediaStreams::AddVideoStream(const StreamParams& stream) {
+  AddStream(&video_, stream);
+}
+
+void MediaStreams::AddDataStream(const StreamParams& stream) {
+  AddStream(&data_, stream);
+}
+
+bool MediaStreams::RemoveAudioStream(
+    const StreamSelector& selector) {
+  return RemoveStream(&audio_, selector);
+}
+
+bool MediaStreams::RemoveVideoStream(
+    const StreamSelector& selector) {
+  return RemoveStream(&video_, selector);
+}
+
+bool MediaStreams::RemoveDataStream(
+    const StreamSelector& selector) {
+  return RemoveStream(&data_, selector);
+}
+
+bool IsJingleViewRequest(const buzz::XmlElement* action_elem) {
+  return action_elem->FirstNamed(QN_JINGLE_DRAFT_VIEW) != NULL;
+}
+
+bool ParseStaticVideoView(const buzz::XmlElement* view_elem,
+                          StaticVideoView* view,
+                          ParseError* error) {
+  uint32 ssrc;
+  if (!ParseSsrc(view_elem->Attr(QN_SSRC), &ssrc)) {
+    return BadParse("Invalid or missing view ssrc.", error);
+  }
+  view->selector = StreamSelector(ssrc);
+
+  const buzz::XmlElement* params_elem =
+      view_elem->FirstNamed(QN_JINGLE_DRAFT_PARAMS);
+  if (params_elem) {
+    view->width = GetXmlAttr(params_elem, QN_WIDTH, 0);
+    view->height = GetXmlAttr(params_elem, QN_HEIGHT, 0);
+    view->framerate = GetXmlAttr(params_elem, QN_FRAMERATE, 0);
+    view->preference = GetXmlAttr(params_elem, QN_PREFERENCE, 0);
+  } else {
+    return BadParse("Missing view params.", error);
+  }
+
+  return true;
+}
+
+bool ParseJingleViewRequest(const buzz::XmlElement* action_elem,
+                            ViewRequest* view_request,
+                            ParseError* error) {
+  for (const buzz::XmlElement* view_elem =
+           action_elem->FirstNamed(QN_JINGLE_DRAFT_VIEW);
+       view_elem != NULL;
+       view_elem = view_elem->NextNamed(QN_JINGLE_DRAFT_VIEW)) {
+    std::string type = view_elem->Attr(QN_TYPE);
+    if (STR_JINGLE_DRAFT_VIEW_TYPE_NONE == type) {
+      view_request->static_video_views.clear();
+      return true;
+    } else if (STR_JINGLE_DRAFT_VIEW_TYPE_STATIC == type) {
+      StaticVideoView static_video_view(StreamSelector(0), 0, 0, 0);
+      if (!ParseStaticVideoView(view_elem, &static_video_view, error)) {
+        return false;
+      }
+      view_request->static_video_views.push_back(static_video_view);
+    } else {
+      LOG(LS_INFO) << "Ingnoring unknown view type: " << type;
+    }
+  }
+  return true;
+}
+
+bool WriteJingleViewRequest(const std::string& content_name,
+                            const ViewRequest& request,
+                            XmlElements* elems,
+                            WriteError* error) {
+  if (request.static_video_views.empty()) {
+    elems->push_back(CreateNoneVideoViewElem(content_name));
+  } else {
+    for (StaticVideoViews::const_iterator view =
+             request.static_video_views.begin();
+         view != request.static_video_views.end(); ++view) {
+      elems->push_back(CreateStaticVideoViewElem(content_name, *view));
+    }
+  }
+  return true;
+}
+
+bool ParseSsrcAsLegacyStream(const buzz::XmlElement* desc_elem,
+                             std::vector<StreamParams>* streams,
+                             ParseError* error) {
+  const std::string ssrc_str = desc_elem->Attr(QN_SSRC);
+  if (!ssrc_str.empty()) {
+    uint32 ssrc;
+    if (!ParseSsrc(ssrc_str, &ssrc)) {
+      return BadParse("Missing or invalid ssrc.", error);
+    }
+
+    streams->push_back(StreamParams::CreateLegacy(ssrc));
+  }
+  return true;
+}
+
+bool ParseSsrcs(const buzz::XmlElement* parent_elem,
+                std::vector<uint32>* ssrcs,
+                ParseError* error) {
+  for (const buzz::XmlElement* ssrc_elem =
+           parent_elem->FirstNamed(QN_JINGLE_DRAFT_SSRC);
+       ssrc_elem != NULL;
+       ssrc_elem = ssrc_elem->NextNamed(QN_JINGLE_DRAFT_SSRC)) {
+    uint32 ssrc;
+    if (!ParseSsrc(ssrc_elem->BodyText(), &ssrc)) {
+      return BadParse("Missing or invalid ssrc.", error);
+    }
+
+    ssrcs->push_back(ssrc);
+  }
+  return true;
+}
+
+bool ParseSsrcGroups(const buzz::XmlElement* parent_elem,
+                     std::vector<SsrcGroup>* ssrc_groups,
+                     ParseError* error) {
+  for (const buzz::XmlElement* group_elem =
+           parent_elem->FirstNamed(QN_JINGLE_DRAFT_SSRC_GROUP);
+       group_elem != NULL;
+       group_elem = group_elem->NextNamed(QN_JINGLE_DRAFT_SSRC_GROUP)) {
+    std::string semantics = group_elem->Attr(QN_SEMANTICS);
+    std::vector<uint32> ssrcs;
+    if (!ParseSsrcs(group_elem, &ssrcs, error)) {
+      return false;
+    }
+    ssrc_groups->push_back(SsrcGroup(semantics, ssrcs));
+  }
+  return true;
+}
+
+bool ParseJingleStream(const buzz::XmlElement* stream_elem,
+                       std::vector<StreamParams>* streams,
+                       ParseError* error) {
+  StreamParams stream;
+  // We treat the nick as a stream groupid.
+  stream.groupid = stream_elem->Attr(QN_NICK);
+  stream.id = stream_elem->Attr(QN_NAME);
+  stream.type = stream_elem->Attr(QN_TYPE);
+  stream.display = stream_elem->Attr(QN_DISPLAY);
+  stream.cname = stream_elem->Attr(QN_CNAME);
+  if (!ParseSsrcs(stream_elem, &(stream.ssrcs), error)) {
+    return false;
+  }
+  std::vector<SsrcGroup> ssrc_groups;
+  if (!ParseSsrcGroups(stream_elem, &(stream.ssrc_groups), error)) {
+    return false;
+  }
+  streams->push_back(stream);
+  return true;
+}
+
+bool ParseJingleRtpHeaderExtensions(const buzz::XmlElement* parent_elem,
+                                    std::vector<RtpHeaderExtension>* hdrexts,
+                                    ParseError* error) {
+  for (const buzz::XmlElement* hdrext_elem =
+           parent_elem->FirstNamed(QN_JINGLE_RTP_HDREXT);
+       hdrext_elem != NULL;
+       hdrext_elem = hdrext_elem->NextNamed(QN_JINGLE_RTP_HDREXT)) {
+    std::string uri = hdrext_elem->Attr(QN_URI);
+    int id = GetXmlAttr(hdrext_elem, QN_ID, 0);
+    if (id <= 0) {
+      return BadParse("Invalid RTP header extension id.", error);
+    }
+    hdrexts->push_back(RtpHeaderExtension(uri, id));
+  }
+  return true;
+}
+
+bool HasJingleStreams(const buzz::XmlElement* desc_elem) {
+  const buzz::XmlElement* streams_elem =
+      desc_elem->FirstNamed(QN_JINGLE_DRAFT_STREAMS);
+  return (streams_elem != NULL);
+}
+
+bool ParseJingleStreams(const buzz::XmlElement* desc_elem,
+                        std::vector<StreamParams>* streams,
+                        ParseError* error) {
+  const buzz::XmlElement* streams_elem =
+      desc_elem->FirstNamed(QN_JINGLE_DRAFT_STREAMS);
+  if (streams_elem == NULL) {
+    return BadParse("Missing streams element.", error);
+  }
+  for (const buzz::XmlElement* stream_elem =
+           streams_elem->FirstNamed(QN_JINGLE_DRAFT_STREAM);
+       stream_elem != NULL;
+       stream_elem = stream_elem->NextNamed(QN_JINGLE_DRAFT_STREAM)) {
+    if (!ParseJingleStream(stream_elem, streams, error)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+void WriteSsrcs(const std::vector<uint32>& ssrcs,
+                buzz::XmlElement* parent_elem) {
+  for (std::vector<uint32>::const_iterator ssrc = ssrcs.begin();
+       ssrc != ssrcs.end(); ++ssrc) {
+    buzz::XmlElement* ssrc_elem =
+        new buzz::XmlElement(QN_JINGLE_DRAFT_SSRC, false);
+    SetXmlBody(ssrc_elem, *ssrc);
+
+    parent_elem->AddElement(ssrc_elem);
+  }
+}
+
+void WriteSsrcGroups(const std::vector<SsrcGroup>& groups,
+                     buzz::XmlElement* parent_elem) {
+  for (std::vector<SsrcGroup>::const_iterator group = groups.begin();
+       group != groups.end(); ++group) {
+    buzz::XmlElement* group_elem =
+        new buzz::XmlElement(QN_JINGLE_DRAFT_SSRC_GROUP, false);
+    AddXmlAttrIfNonEmpty(group_elem, QN_SEMANTICS, group->semantics);
+    WriteSsrcs(group->ssrcs, group_elem);
+
+    parent_elem->AddElement(group_elem);
+  }
+}
+
+void WriteJingleStream(const StreamParams& stream,
+                       buzz::XmlElement* parent_elem) {
+  buzz::XmlElement* stream_elem =
+      new buzz::XmlElement(QN_JINGLE_DRAFT_STREAM, false);
+  // We treat the nick as a stream groupid.
+  AddXmlAttrIfNonEmpty(stream_elem, QN_NICK, stream.groupid);
+  AddXmlAttrIfNonEmpty(stream_elem, QN_NAME, stream.id);
+  AddXmlAttrIfNonEmpty(stream_elem, QN_TYPE, stream.type);
+  AddXmlAttrIfNonEmpty(stream_elem, QN_DISPLAY, stream.display);
+  AddXmlAttrIfNonEmpty(stream_elem, QN_CNAME, stream.cname);
+  WriteSsrcs(stream.ssrcs, stream_elem);
+  WriteSsrcGroups(stream.ssrc_groups, stream_elem);
+
+  parent_elem->AddElement(stream_elem);
+}
+
+void WriteJingleStreams(const std::vector<StreamParams>& streams,
+                        buzz::XmlElement* parent_elem) {
+  buzz::XmlElement* streams_elem =
+      new buzz::XmlElement(QN_JINGLE_DRAFT_STREAMS, true);
+  for (std::vector<StreamParams>::const_iterator stream = streams.begin();
+       stream != streams.end(); ++stream) {
+    WriteJingleStream(*stream, streams_elem);
+  }
+
+  parent_elem->AddElement(streams_elem);
+}
+
+void WriteJingleRtpHeaderExtensions(
+    const std::vector<RtpHeaderExtension>& hdrexts,
+    buzz::XmlElement* parent_elem) {
+  for (std::vector<RtpHeaderExtension>::const_iterator hdrext = hdrexts.begin();
+       hdrext != hdrexts.end(); ++hdrext) {
+    buzz::XmlElement* hdrext_elem =
+      new buzz::XmlElement(QN_JINGLE_RTP_HDREXT, false);
+    AddXmlAttr(hdrext_elem, QN_URI, hdrext->uri);
+    AddXmlAttr(hdrext_elem, QN_ID, hdrext->id);
+    parent_elem->AddElement(hdrext_elem);
+  }
+}
+
+
+}  // namespace cricket
diff --git a/talk/session/media/mediamessages.h b/talk/session/media/mediamessages.h
new file mode 100644
index 0000000..dcb48a8
--- /dev/null
+++ b/talk/session/media/mediamessages.h
@@ -0,0 +1,169 @@
+/*
+ * libjingle
+ * Copyright 2010 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * A collection of functions and types for serializing and
+ * deserializing Jingle session messages related to media.
+ * Specificially, the <notify> and <view> messages.  They are not yet
+ * standardized, but their current documentation can be found at:
+ * goto/jinglemuc
+ */
+
+#ifndef TALK_SESSION_MEDIA_MEDIAMESSAGES_H_
+#define TALK_SESSION_MEDIA_MEDIAMESSAGES_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/media/base/mediachannel.h"  // For RtpHeaderExtension
+#include "talk/media/base/streamparams.h"
+#include "talk/p2p/base/parsing.h"
+#include "talk/p2p/base/sessiondescription.h"
+
+namespace cricket {
+
+// A collection of audio and video and data streams. Most of the
+// methods are merely for convenience. Many of these methods are keyed
+// by ssrc, which is the source identifier in the RTP spec
+// (http://tools.ietf.org/html/rfc3550).
+struct MediaStreams {
+ public:
+  MediaStreams() {}
+  void CopyFrom(const MediaStreams& sources);
+
+  bool empty() const {
+    return audio_.empty() && video_.empty() && data_.empty();
+  }
+
+  std::vector<StreamParams>* mutable_audio() { return &audio_; }
+  std::vector<StreamParams>* mutable_video() { return &video_; }
+  std::vector<StreamParams>* mutable_data() { return &data_; }
+  const std::vector<StreamParams>& audio() const { return audio_; }
+  const std::vector<StreamParams>& video() const { return video_; }
+  const std::vector<StreamParams>& data() const { return data_; }
+
+  // Gets a stream, returning true if found.
+  bool GetAudioStream(
+      const StreamSelector& selector, StreamParams* stream);
+  bool GetVideoStream(
+      const StreamSelector& selector, StreamParams* stream);
+  bool GetDataStream(
+      const StreamSelector& selector, StreamParams* stream);
+  // Adds a stream.
+  void AddAudioStream(const StreamParams& stream);
+  void AddVideoStream(const StreamParams& stream);
+  void AddDataStream(const StreamParams& stream);
+  // Removes a stream, returning true if found and removed.
+  bool RemoveAudioStream(const StreamSelector& selector);
+  bool RemoveVideoStream(const StreamSelector& selector);
+  bool RemoveDataStream(const StreamSelector& selector);
+
+ private:
+  std::vector<StreamParams> audio_;
+  std::vector<StreamParams> video_;
+  std::vector<StreamParams> data_;
+
+  DISALLOW_COPY_AND_ASSIGN(MediaStreams);
+};
+
+// In a <view> message, there are a number of views specified.  This
+// represents one such view.  We currently only support "static"
+// views.
+struct StaticVideoView {
+  StaticVideoView(const StreamSelector& selector,
+                  int width, int height, int framerate)
+      : selector(selector),
+        width(width),
+        height(height),
+        framerate(framerate),
+        preference(0) {
+  }
+
+  StreamSelector selector;
+  int width;
+  int height;
+  int framerate;
+  int preference;
+};
+
+typedef std::vector<StaticVideoView> StaticVideoViews;
+
+// Represents a whole view request message, which contains many views.
+struct ViewRequest {
+  StaticVideoViews static_video_views;
+};
+
+// If the parent element (usually <jingle>) is a jingle view.
+bool IsJingleViewRequest(const buzz::XmlElement* action_elem);
+
+// Parses a view request from the parent element (usually
+// <jingle>). If it fails, it returns false and fills an error
+// message.
+bool ParseJingleViewRequest(const buzz::XmlElement* action_elem,
+                            ViewRequest* view_request,
+                            ParseError* error);
+
+// Serializes a view request to XML.  If it fails, returns false and
+// fills in an error message.
+bool WriteJingleViewRequest(const std::string& content_name,
+                            const ViewRequest& view,
+                            XmlElements* elems,
+                            WriteError* error);
+
+// TODO(pthatcher): Get rid of legacy source notify and replace with
+// description-info as soon as reflector is capable of sending it.
+bool IsSourcesNotify(const buzz::XmlElement* action_elem);
+
+// If the given elem has <streams>.
+bool HasJingleStreams(const buzz::XmlElement* desc_elem);
+
+// Parses streams from a jingle <description>.  If it fails, returns
+// false and fills an error message.
+bool ParseJingleStreams(const buzz::XmlElement* desc_elem,
+                        std::vector<StreamParams>* streams,
+                        ParseError* error);
+
+// Write a <streams> element to the parent_elem.
+void WriteJingleStreams(const std::vector<StreamParams>& streams,
+                        buzz::XmlElement* parent_elem);
+
+// Parses rtp header extensions from a jingle <description>.  If it
+// fails, returns false and fills an error message.
+bool ParseJingleRtpHeaderExtensions(
+    const buzz::XmlElement* desc_elem,
+    std::vector<RtpHeaderExtension>* hdrexts,
+    ParseError* error);
+
+// Writes <rtp-hdrext> elements to the parent_elem.
+void WriteJingleRtpHeaderExtensions(
+    const std::vector<RtpHeaderExtension>& hdrexts,
+    buzz::XmlElement* parent_elem);
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_MEDIA_MEDIAMESSAGES_H_
diff --git a/talk/session/media/mediamessages_unittest.cc b/talk/session/media/mediamessages_unittest.cc
new file mode 100644
index 0000000..4c76be3
--- /dev/null
+++ b/talk/session/media/mediamessages_unittest.cc
@@ -0,0 +1,352 @@
+/*
+ * 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/media/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/media/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.groupid = nick;
+    stream.id = name;
+    stream.ssrcs.push_back(ssrc1);
+    stream.ssrcs.push_back(ssrc2);
+    stream.ssrc_groups.push_back(
+        cricket::SsrcGroup(semantics, stream.ssrcs));
+    stream.type = type;
+    stream.display = display;
+    return stream;
+  }
+
+  static std::string StreamsXml(const std::string& stream1,
+                                const std::string& stream2) {
+    return "<streams xmlns='google:jingle'>"
+           + stream1
+           + stream2 +
+           "</streams>";
+  }
+
+
+  static std::string StreamXml(const std::string& nick,
+                               const std::string& name,
+                               const std::string& ssrc1,
+                               const std::string& ssrc2,
+                               const std::string& semantics,
+                               const std::string& type,
+                               const std::string& display) {
+    return "<stream"
+           " nick='" + nick + "'"
+           " name='" + name + "'"
+           " type='" + type + "'"
+           " display='" + display + "'"
+           ">"
+           "<ssrc>" + ssrc1 + "</ssrc>"
+           "<ssrc>" + ssrc2 + "</ssrc>"
+           "<ssrc-group"
+           "  semantics='" + semantics + "'"
+           ">"
+           "<ssrc>" + ssrc1 + "</ssrc>"
+           "<ssrc>" + ssrc2 + "</ssrc>"
+           "</ssrc-group>"
+           "</stream>";
+  }
+
+  static std::string HeaderExtensionsXml(const std::string& hdrext1,
+                                         const std::string& hdrext2) {
+    return "<rtp:description xmlns:rtp=\"urn:xmpp:jingle:apps:rtp:1\">"
+           + hdrext1
+           + hdrext2 +
+           "</rtp:description>";
+  }
+
+  static std::string HeaderExtensionXml(const std::string& uri,
+                                        const std::string& id) {
+    return "<rtp:rtp-hdrext"
+           " uri='" + uri + "'"
+           " id='" + id + "'"
+           "/>";
+  }
+
+  static cricket::SessionDescription* CreateMediaSessionDescription(
+      const std::string& audio_content_name,
+      const std::string& video_content_name) {
+    cricket::SessionDescription* desc = new cricket::SessionDescription();
+    desc->AddContent(audio_content_name, cricket::NS_JINGLE_RTP,
+                     new cricket::AudioContentDescription());
+    desc->AddContent(video_content_name, cricket::NS_JINGLE_RTP,
+                     new cricket::VideoContentDescription());
+    return desc;
+  }
+
+  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(
+      cricket::StreamSelector(1234), 640, 480, 30));
+  view_request.static_video_views.push_back(cricket::StaticVideoView(
+      cricket::StreamSelector(2468), 640, 480, 30));
+
+  ASSERT_TRUE(cricket::WriteJingleViewRequest(
+      "video1", view_request, &actual_view_elems, &error));
+
+  ASSERT_EQ(2U, actual_view_elems.size());
+  EXPECT_EQ(expected_view_elem1->Str(), actual_view_elems[0]->Str());
+  EXPECT_EQ(expected_view_elem2->Str(), actual_view_elems[1]->Str());
+
+  view_request.static_video_views.clear();
+  cricket::ParseError parse_error;
+  EXPECT_TRUE(cricket::IsJingleViewRequest(action_elem.get()));
+  ASSERT_TRUE(cricket::ParseJingleViewRequest(
+      action_elem.get(), &view_request, &parse_error));
+  EXPECT_EQ(2U, view_request.static_video_views.size());
+  EXPECT_EQ(1234U, view_request.static_video_views[0].selector.ssrc);
+  EXPECT_EQ(640, view_request.static_video_views[0].width);
+  EXPECT_EQ(480, view_request.static_video_views[0].height);
+  EXPECT_EQ(30, view_request.static_video_views[0].framerate);
+  EXPECT_EQ(2468U, view_request.static_video_views[1].selector.ssrc);
+}
+
+// Test deserializing bad view XML.
+TEST_F(MediaMessagesTest, ParseBadViewXml) {
+  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", "stream1", "101", "102",
+                        "semantics1", "type1", "display1"),
+              StreamXml("nick2", "stream2", "201", "202",
+                        "semantics2", "type2", "display2"))));
+
+  std::vector<cricket::StreamParams> expected_streams;
+  expected_streams.push_back(CreateStream("nick1", "stream1", 101U, 102U,
+                                          "semantics1", "type1", "display1"));
+  expected_streams.push_back(CreateStream("nick2", "stream2", 201U, 202U,
+                                          "semantics2", "type2", "display2"));
+
+  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));
+}
+
+// Test serializing/deserializing typical RTP Header Extension xml.
+TEST_F(MediaMessagesTest, HeaderExtensionsToFromXml) {
+  talk_base::scoped_ptr<buzz::XmlElement> expected_desc_elem(
+      buzz::XmlElement::ForStr(
+          HeaderExtensionsXml(
+              HeaderExtensionXml("abc", "123"),
+              HeaderExtensionXml("def", "456"))));
+
+  std::vector<cricket::RtpHeaderExtension> expected_hdrexts;
+  expected_hdrexts.push_back(RtpHeaderExtension("abc", 123));
+  expected_hdrexts.push_back(RtpHeaderExtension("def", 456));
+
+  talk_base::scoped_ptr<buzz::XmlElement> actual_desc_elem(
+      new buzz::XmlElement(QN_JINGLE_RTP_CONTENT));
+  cricket::WriteJingleRtpHeaderExtensions(expected_hdrexts, actual_desc_elem.get());
+
+  ASSERT_TRUE(actual_desc_elem != NULL);
+  EXPECT_EQ(expected_desc_elem->Str(), actual_desc_elem->Str());
+
+  std::vector<cricket::RtpHeaderExtension> actual_hdrexts;
+  cricket::ParseError parse_error;
+  ASSERT_TRUE(cricket::ParseJingleRtpHeaderExtensions(
+      expected_desc_elem.get(), &actual_hdrexts, &parse_error));
+  EXPECT_EQ(2U, actual_hdrexts.size());
+  EXPECT_EQ(expected_hdrexts[0], actual_hdrexts[0]);
+  EXPECT_EQ(expected_hdrexts[1], actual_hdrexts[1]);
+}
+
+// Test deserializing bad RTP header extension xml.
+TEST_F(MediaMessagesTest, HeaderExtensionsFromBadXml) {
+  std::vector<cricket::RtpHeaderExtension> actual_hdrexts;
+  cricket::ParseError parse_error;
+
+  talk_base::scoped_ptr<buzz::XmlElement> desc_elem(
+      buzz::XmlElement::ForStr(
+          HeaderExtensionsXml(
+              HeaderExtensionXml("abc", "123"),
+              HeaderExtensionXml("def", "not-an-id"))));
+  ASSERT_FALSE(cricket::ParseJingleRtpHeaderExtensions(
+      desc_elem.get(), &actual_hdrexts, &parse_error));
+
+  desc_elem.reset(
+      buzz::XmlElement::ForStr(
+          HeaderExtensionsXml(
+              HeaderExtensionXml("abc", "123"),
+              HeaderExtensionXml("def", "-1"))));
+  ASSERT_FALSE(cricket::ParseJingleRtpHeaderExtensions(
+      desc_elem.get(), &actual_hdrexts, &parse_error));
+}
+
+}  // namespace cricket
diff --git a/talk/session/media/mediamonitor.cc b/talk/session/media/mediamonitor.cc
new file mode 100644
index 0000000..844180e
--- /dev/null
+++ b/talk/session/media/mediamonitor.cc
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ */
+
+#include "talk/base/common.h"
+#include "talk/session/media/channelmanager.h"
+#include "talk/session/media/mediamonitor.h"
+
+namespace cricket {
+
+enum {
+  MSG_MONITOR_POLL = 1,
+  MSG_MONITOR_START = 2,
+  MSG_MONITOR_STOP = 3,
+  MSG_MONITOR_SIGNAL = 4
+};
+
+MediaMonitor::MediaMonitor(talk_base::Thread* worker_thread,
+                           talk_base::Thread* monitor_thread)
+    : worker_thread_(worker_thread),
+      monitor_thread_(monitor_thread), monitoring_(false), rate_(0) {
+}
+
+MediaMonitor::~MediaMonitor() {
+  monitoring_ = false;
+  monitor_thread_->Clear(this);
+  worker_thread_->Clear(this);
+}
+
+void MediaMonitor::Start(uint32 milliseconds) {
+  rate_ = milliseconds;
+  if (rate_ < 100)
+    rate_ = 100;
+  worker_thread_->Post(this, MSG_MONITOR_START);
+}
+
+void MediaMonitor::Stop() {
+  worker_thread_->Post(this, MSG_MONITOR_STOP);
+  rate_ = 0;
+}
+
+void MediaMonitor::OnMessage(talk_base::Message* message) {
+  talk_base::CritScope cs(&crit_);
+
+  switch (message->message_id) {
+  case MSG_MONITOR_START:
+    ASSERT(talk_base::Thread::Current() == worker_thread_);
+    if (!monitoring_) {
+      monitoring_ = true;
+      PollMediaChannel();
+    }
+    break;
+
+  case MSG_MONITOR_STOP:
+    ASSERT(talk_base::Thread::Current() == worker_thread_);
+    if (monitoring_) {
+      monitoring_ = false;
+      worker_thread_->Clear(this);
+    }
+    break;
+
+  case MSG_MONITOR_POLL:
+    ASSERT(talk_base::Thread::Current() == worker_thread_);
+    PollMediaChannel();
+    break;
+
+  case MSG_MONITOR_SIGNAL:
+    ASSERT(talk_base::Thread::Current() == monitor_thread_);
+    Update();
+    break;
+  }
+}
+
+void MediaMonitor::PollMediaChannel() {
+  talk_base::CritScope cs(&crit_);
+  ASSERT(talk_base::Thread::Current() == worker_thread_);
+
+  GetStats();
+
+  // Signal the monitoring thread, start another poll timer
+  monitor_thread_->Post(this, MSG_MONITOR_SIGNAL);
+  worker_thread_->PostDelayed(rate_, this, MSG_MONITOR_POLL);
+}
+
+}
diff --git a/talk/session/media/mediamonitor.h b/talk/session/media/mediamonitor.h
new file mode 100644
index 0000000..a9ce889
--- /dev/null
+++ b/talk/session/media/mediamonitor.h
@@ -0,0 +1,98 @@
+/*
+ * 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.
+ */
+
+// Class to collect statistics from a media channel
+
+#ifndef TALK_SESSION_MEDIA_MEDIAMONITOR_H_
+#define TALK_SESSION_MEDIA_MEDIAMONITOR_H_
+
+#include "talk/base/criticalsection.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/thread.h"
+#include "talk/media/base/mediachannel.h"
+
+namespace cricket {
+
+// The base MediaMonitor class, independent of voice and video.
+class MediaMonitor : public talk_base::MessageHandler,
+    public sigslot::has_slots<> {
+ public:
+  MediaMonitor(talk_base::Thread* worker_thread,
+               talk_base::Thread* monitor_thread);
+  ~MediaMonitor();
+
+  void Start(uint32 milliseconds);
+  void Stop();
+
+ protected:
+  void OnMessage(talk_base::Message *message);
+  void PollMediaChannel();
+  virtual void GetStats() = 0;
+  virtual void Update() = 0;
+
+  talk_base::CriticalSection crit_;
+  talk_base::Thread* worker_thread_;
+  talk_base::Thread* monitor_thread_;
+  bool monitoring_;
+  uint32 rate_;
+};
+
+// Templatized MediaMonitor that can deal with different kinds of media.
+template<class MC, class MI>
+class MediaMonitorT : public MediaMonitor {
+ public:
+  MediaMonitorT(MC* media_channel, talk_base::Thread* worker_thread,
+                talk_base::Thread* monitor_thread)
+      : MediaMonitor(worker_thread, monitor_thread),
+        media_channel_(media_channel) {}
+  sigslot::signal2<MC*, const MI&> SignalUpdate;
+
+ protected:
+  // These routines assume the crit_ lock is held by the calling thread.
+  virtual void GetStats() {
+    media_info_.Clear();
+    media_channel_->GetStats(&media_info_);
+  }
+  virtual void Update() {
+    MI stats(media_info_);
+    crit_.Leave();
+    SignalUpdate(media_channel_, stats);
+    crit_.Enter();
+  }
+
+ private:
+  MC* media_channel_;
+  MI media_info_;
+};
+
+typedef MediaMonitorT<VoiceMediaChannel, VoiceMediaInfo> VoiceMediaMonitor;
+typedef MediaMonitorT<VideoMediaChannel, VideoMediaInfo> VideoMediaMonitor;
+typedef MediaMonitorT<DataMediaChannel, DataMediaInfo> DataMediaMonitor;
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_MEDIA_MEDIAMONITOR_H_
diff --git a/talk/session/media/mediarecorder.cc b/talk/session/media/mediarecorder.cc
new file mode 100644
index 0000000..0aed63a
--- /dev/null
+++ b/talk/session/media/mediarecorder.cc
@@ -0,0 +1,224 @@
+/*
+ * 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/media/mediarecorder.h"
+
+#include <limits.h>
+
+#include <string>
+
+#include "talk/base/fileutils.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+#include "talk/media/base/rtpdump.h"
+
+
+namespace cricket {
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of RtpDumpSink.
+///////////////////////////////////////////////////////////////////////////
+RtpDumpSink::RtpDumpSink(talk_base::StreamInterface* stream)
+    : max_size_(INT_MAX),
+      recording_(false),
+      packet_filter_(PF_NONE) {
+  stream_.reset(stream);
+}
+
+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_) {
+    if (!stream_) {
+      return false;
+    }
+    writer_.reset(new RtpDumpWriter(stream_.get()));
+    writer_->set_packet_filter(packet_filter_);
+  } else if (!recording_ && stream_) {
+    stream_->Flush();
+  }
+  return true;
+}
+
+void RtpDumpSink::OnPacket(const void* data, size_t size, bool rtcp) {
+  talk_base::CritScope cs(&critical_section_);
+
+  if (recording_ && writer_) {
+    size_t current_size;
+    if (writer_->GetDumpSize(&current_size) &&
+        current_size + RtpDumpPacket::kHeaderLength + size <= max_size_) {
+      if (!rtcp) {
+        writer_->WriteRtpPacket(data, size);
+      } else {
+        // TODO(whyuan): Enable recording RTCP.
+      }
+    }
+  }
+}
+
+void RtpDumpSink::set_packet_filter(int filter) {
+  talk_base::CritScope cs(&critical_section_);
+  packet_filter_ = filter;
+  if (writer_) {
+    writer_->set_packet_filter(packet_filter_);
+  }
+}
+
+void RtpDumpSink::Flush() {
+  talk_base::CritScope cs(&critical_section_);
+  if (stream_) {
+    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,
+                               talk_base::StreamInterface* send_stream,
+                               talk_base::StreamInterface* recv_stream,
+                               int filter) {
+  return InternalAddChannel(channel, false, send_stream, recv_stream,
+                            filter);
+}
+bool MediaRecorder::AddChannel(VideoChannel* channel,
+                               talk_base::StreamInterface* send_stream,
+                               talk_base::StreamInterface* recv_stream,
+                               int filter) {
+  return InternalAddChannel(channel, true, send_stream, recv_stream,
+                            filter);
+}
+
+bool MediaRecorder::InternalAddChannel(BaseChannel* channel,
+                                       bool video_channel,
+                                       talk_base::StreamInterface* send_stream,
+                                       talk_base::StreamInterface* recv_stream,
+                                       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_stream));
+  sink_pair->send_sink->set_packet_filter(filter);
+  sink_pair->recv_sink.reset(new RtpDumpSink(recv_stream));
+  sink_pair->recv_sink->set_packet_filter(filter);
+  sinks_[channel] = sink_pair;
+
+  return true;
+}
+
+void MediaRecorder::RemoveChannel(BaseChannel* channel,
+                                  SinkType type) {
+  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(), type);
+    channel->UnregisterRecvSink(itr->second->recv_sink.get(), type);
+    delete itr->second;
+    sinks_.erase(itr);
+  }
+}
+
+bool MediaRecorder::EnableChannel(
+    BaseChannel* channel, bool enable_send, bool enable_recv,
+    SinkType type) {
+  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, type);
+  } else {
+    channel->UnregisterSendSink(sink, type);
+  }
+
+  sink = sink_pair->recv_sink.get();
+  sink->Enable(enable_recv);
+  if (enable_recv) {
+    channel->RegisterRecvSink(sink, &RtpDumpSink::OnPacket, type);
+  } else {
+    channel->UnregisterRecvSink(sink, type);
+  }
+
+  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/media/mediarecorder.h b/talk/session/media/mediarecorder.h
new file mode 100644
index 0000000..df22e98
--- /dev/null
+++ b/talk/session/media/mediarecorder.h
@@ -0,0 +1,119 @@
+/*
+ * 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_MEDIA_MEDIARECORDER_H_
+#define TALK_SESSION_MEDIA_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/media/channel.h"
+#include "talk/session/media/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:
+  // Takes ownership of stream.
+  explicit RtpDumpSink(talk_base::StreamInterface* stream);
+  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_;
+  talk_base::scoped_ptr<talk_base::StreamInterface> 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,
+                  talk_base::StreamInterface* send_stream,
+                  talk_base::StreamInterface* recv_stream,
+                  int filter);
+  bool AddChannel(VideoChannel* channel,
+                  talk_base::StreamInterface* send_stream,
+                  talk_base::StreamInterface* recv_stream,
+                  int filter);
+  void RemoveChannel(BaseChannel* channel, SinkType type);
+  bool EnableChannel(BaseChannel* channel, bool enable_send, bool enable_recv,
+                     SinkType type);
+  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,
+                          talk_base::StreamInterface* send_stream,
+                          talk_base::StreamInterface* recv_stream,
+                          int filter);
+
+  std::map<BaseChannel*, SinkPair*> sinks_;
+  talk_base::CriticalSection critical_section_;
+
+  DISALLOW_COPY_AND_ASSIGN(MediaRecorder);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_MEDIA_MEDIARECORDER_H_
diff --git a/talk/session/media/mediarecorder_unittest.cc b/talk/session/media/mediarecorder_unittest.cc
new file mode 100644
index 0000000..5155e6d
--- /dev/null
+++ b/talk/session/media/mediarecorder_unittest.cc
@@ -0,0 +1,358 @@
+// 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/media/base/fakemediaengine.h"
+#include "talk/media/base/rtpdump.h"
+#include "talk/media/base/testutils.h"
+#include "talk/p2p/base/fakesession.h"
+#include "talk/session/media/channel.h"
+#include "talk/session/media/mediarecorder.h"
+
+namespace cricket {
+
+talk_base::StreamInterface* Open(const std::string& path) {
+  return talk_base::Filesystem::OpenFile(
+      talk_base::Pathname(path), "wb");
+}
+
+/////////////////////////////////////////////////////////////////////////
+// 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(Open(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, SINK_PRE_CRYPTO));
+  EXPECT_FALSE(channel->HasSendSinks(SINK_PRE_CRYPTO));
+  EXPECT_FALSE(channel->HasRecvSinks(SINK_PRE_CRYPTO));
+  EXPECT_FALSE(channel->HasSendSinks(SINK_POST_CRYPTO));
+  EXPECT_FALSE(channel->HasRecvSinks(SINK_POST_CRYPTO));
+
+  // 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),
+                                     Open(send_file), Open(recv_file), filter));
+  } else {
+    EXPECT_TRUE(recorder->AddChannel(static_cast<VoiceChannel*>(channel),
+                                     Open(send_file), Open(recv_file), filter));
+  }
+
+  // Enable recording only the sent media.
+  EXPECT_TRUE(recorder->EnableChannel(channel, true, false, SINK_PRE_CRYPTO));
+  EXPECT_TRUE(channel->HasSendSinks(SINK_PRE_CRYPTO));
+  EXPECT_FALSE(channel->HasRecvSinks(SINK_POST_CRYPTO));
+  EXPECT_FALSE(channel->HasSendSinks(SINK_POST_CRYPTO));
+  EXPECT_FALSE(channel->HasRecvSinks(SINK_POST_CRYPTO));
+  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, SINK_PRE_CRYPTO));
+  EXPECT_FALSE(channel->HasSendSinks(SINK_PRE_CRYPTO));
+  EXPECT_TRUE(channel->HasRecvSinks(SINK_PRE_CRYPTO));
+  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, SINK_PRE_CRYPTO));
+  EXPECT_TRUE(channel->HasSendSinks(SINK_PRE_CRYPTO));
+  EXPECT_TRUE(channel->HasRecvSinks(SINK_PRE_CRYPTO));
+
+  // 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, SINK_PRE_CRYPTO));
+  EXPECT_TRUE(channel->HasSendSinks(SINK_PRE_CRYPTO));
+  EXPECT_TRUE(channel->HasRecvSinks(SINK_PRE_CRYPTO));
+  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, SINK_PRE_CRYPTO);
+  EXPECT_FALSE(channel->HasSendSinks(SINK_PRE_CRYPTO));
+  EXPECT_FALSE(channel->HasRecvSinks(SINK_PRE_CRYPTO));
+
+  // 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),
+        Open(send_header_file), Open(recv_header_file), PF_RTPHEADER));
+  } else {
+    EXPECT_TRUE(header_recorder->AddChannel(
+        static_cast<VoiceChannel*>(channel),
+        Open(send_header_file), Open(recv_header_file), PF_RTPHEADER));
+  }
+
+  // Enable recording both sent and received.
+  EXPECT_TRUE(
+      header_recorder->EnableChannel(channel, true, true, SINK_POST_CRYPTO));
+  EXPECT_TRUE(channel->HasSendSinks(SINK_POST_CRYPTO));
+  EXPECT_TRUE(channel->HasRecvSinks(SINK_POST_CRYPTO));
+  EXPECT_FALSE(channel->HasSendSinks(SINK_PRE_CRYPTO));
+  EXPECT_FALSE(channel->HasRecvSinks(SINK_PRE_CRYPTO));
+  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),
+        Open(send_file), Open(recv_file), PF_RTPPACKET));
+  } else {
+    EXPECT_TRUE(recorder->AddChannel(
+        static_cast<VoiceChannel*>(channel),
+        Open(send_file), Open(recv_file), PF_RTPPACKET));
+  }
+
+  // Enable recording both sent and received.
+  EXPECT_TRUE(recorder->EnableChannel(channel, true, true, SINK_PRE_CRYPTO));
+  EXPECT_TRUE(channel->HasSendSinks(SINK_POST_CRYPTO));
+  EXPECT_TRUE(channel->HasRecvSinks(SINK_POST_CRYPTO));
+  EXPECT_TRUE(channel->HasSendSinks(SINK_PRE_CRYPTO));
+  EXPECT_TRUE(channel->HasRecvSinks(SINK_PRE_CRYPTO));
+  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.
+  FakeSession session(true);
+  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.
+  FakeSession session(true);
+  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/media/mediasession.cc b/talk/session/media/mediasession.cc
new file mode 100644
index 0000000..3d00418
--- /dev/null
+++ b/talk/session/media/mediasession.cc
@@ -0,0 +1,1657 @@
+/*
+ * 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/media/mediasession.h"
+
+#include <functional>
+#include <map>
+#include <set>
+#include <utility>
+
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stringutils.h"
+#include "talk/media/base/constants.h"
+#include "talk/media/base/cryptoparams.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/session/media/channelmanager.h"
+#include "talk/session/media/srtpfilter.h"
+#include "talk/xmpp/constants.h"
+
+namespace {
+const char kInline[] = "inline:";
+}
+
+namespace cricket {
+
+using talk_base::scoped_ptr;
+
+// RTP Profile names
+// http://www.iana.org/assignments/rtp-parameters/rtp-parameters.xml
+// RFC4585
+const char kMediaProtocolAvpf[] = "RTP/AVPF";
+// RFC5124
+const char kMediaProtocolSavpf[] = "RTP/SAVPF";
+
+const char kMediaProtocolRtpPrefix[] = "RTP/";
+
+const char kMediaProtocolSctp[] = "SCTP";
+const char kMediaProtocolDtlsSctp[] = "DTLS/SCTP";
+
+static bool IsMediaContentOfType(const ContentInfo* content,
+                                 MediaType media_type) {
+  if (!IsMediaContent(content)) {
+    return false;
+  }
+
+  const MediaContentDescription* mdesc =
+      static_cast<const MediaContentDescription*>(content->description);
+  return mdesc && mdesc->type() == media_type;
+}
+
+static bool CreateCryptoParams(int tag, const std::string& cipher,
+                               CryptoParams *out) {
+  std::string key;
+  key.reserve(SRTP_MASTER_KEY_BASE64_LEN);
+
+  if (!talk_base::CreateRandomString(SRTP_MASTER_KEY_BASE64_LEN, &key)) {
+    return false;
+  }
+  out->tag = tag;
+  out->cipher_suite = cipher;
+  out->key_params = kInline;
+  out->key_params += key;
+  return true;
+}
+
+#ifdef HAVE_SRTP
+static bool AddCryptoParams(const std::string& cipher_suite,
+                            CryptoParamsVec *out) {
+  int size = out->size();
+
+  out->resize(size + 1);
+  return CreateCryptoParams(size, cipher_suite, &out->at(size));
+}
+
+void AddMediaCryptos(const CryptoParamsVec& cryptos,
+                     MediaContentDescription* media) {
+  for (CryptoParamsVec::const_iterator crypto = cryptos.begin();
+       crypto != cryptos.end(); ++crypto) {
+    media->AddCrypto(*crypto);
+  }
+}
+
+bool CreateMediaCryptos(const std::vector<std::string>& crypto_suites,
+                        MediaContentDescription* media) {
+  CryptoParamsVec cryptos;
+  for (std::vector<std::string>::const_iterator it = crypto_suites.begin();
+       it != crypto_suites.end(); ++it) {
+    if (!AddCryptoParams(*it, &cryptos)) {
+      return false;
+    }
+  }
+  AddMediaCryptos(cryptos, media);
+  return true;
+}
+#endif
+
+const CryptoParamsVec* GetCryptos(const MediaContentDescription* media) {
+  if (!media) {
+    return NULL;
+  }
+  return &media->cryptos();
+}
+
+bool FindMatchingCrypto(const CryptoParamsVec& cryptos,
+                        const CryptoParams& crypto,
+                        CryptoParams* out) {
+  for (CryptoParamsVec::const_iterator it = cryptos.begin();
+       it != cryptos.end(); ++it) {
+    if (crypto.Matches(*it)) {
+      *out = *it;
+      return true;
+    }
+  }
+  return false;
+}
+
+// For audio, HMAC 32 is prefered because of the low overhead.
+void GetSupportedAudioCryptoSuites(
+    std::vector<std::string>* crypto_suites) {
+#ifdef HAVE_SRTP
+  crypto_suites->push_back(CS_AES_CM_128_HMAC_SHA1_32);
+  crypto_suites->push_back(CS_AES_CM_128_HMAC_SHA1_80);
+#endif
+}
+
+void GetSupportedVideoCryptoSuites(
+    std::vector<std::string>* crypto_suites) {
+  GetSupportedDefaultCryptoSuites(crypto_suites);
+}
+
+void GetSupportedDataCryptoSuites(
+    std::vector<std::string>* crypto_suites) {
+  GetSupportedDefaultCryptoSuites(crypto_suites);
+}
+
+void GetSupportedDefaultCryptoSuites(
+    std::vector<std::string>* crypto_suites) {
+#ifdef HAVE_SRTP
+  crypto_suites->push_back(CS_AES_CM_128_HMAC_SHA1_80);
+#endif
+}
+
+// For video support only 80-bit SHA1 HMAC. For audio 32-bit HMAC is
+// tolerated unless bundle is enabled because it is low overhead. Pick the
+// crypto in the list that is supported.
+static bool SelectCrypto(const MediaContentDescription* offer,
+                         bool bundle,
+                         CryptoParams *crypto) {
+  bool audio = offer->type() == MEDIA_TYPE_AUDIO;
+  const CryptoParamsVec& cryptos = offer->cryptos();
+
+  for (CryptoParamsVec::const_iterator i = cryptos.begin();
+       i != cryptos.end(); ++i) {
+    if (CS_AES_CM_128_HMAC_SHA1_80 == i->cipher_suite ||
+        (CS_AES_CM_128_HMAC_SHA1_32 == i->cipher_suite && audio && !bundle)) {
+      return CreateCryptoParams(i->tag, i->cipher_suite, crypto);
+    }
+  }
+  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;
+    // groupid is empty for StreamParams generated using
+    // MediaSessionDescriptionFactory.
+    if (GetStreamByIds(params_vec, "", stream_it->id,
+                       &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 random SSRC values that are not already present in |params_vec|.
+// Either 2 or 1 ssrcs will be generated based on |include_rtx_stream| being
+// true or false. The generated values are added to |ssrcs|.
+static void GenerateSsrcs(const StreamParamsVec& params_vec,
+                          bool include_rtx_stream,
+                          std::vector<uint32>* ssrcs) {
+  unsigned int num_ssrcs = include_rtx_stream ? 2 : 1;
+  for (unsigned int i = 0; i < num_ssrcs; i++) {
+    uint32 candidate;
+    do {
+      candidate = talk_base::CreateRandomNonZeroId();
+    } while (GetStreamBySsrc(params_vec, candidate, NULL) ||
+             std::count(ssrcs->begin(), ssrcs->end(), candidate) > 0);
+    ssrcs->push_back(candidate);
+  }
+}
+
+// Returns false if we exhaust the range of SIDs.
+static bool GenerateSctpSid(const StreamParamsVec& params_vec,
+                            uint32* sid) {
+  if (params_vec.size() > kMaxSctpSid) {
+    LOG(LS_WARNING) <<
+        "Could not generate an SCTP SID: too many SCTP streams.";
+    return false;
+  }
+  while (true) {
+    uint32 candidate = talk_base::CreateRandomNonZeroId() % kMaxSctpSid;
+    if (!GetStreamBySsrc(params_vec, candidate, NULL)) {
+      *sid = candidate;
+      return true;
+    }
+  }
+}
+
+static bool GenerateSctpSids(const StreamParamsVec& params_vec,
+                             std::vector<uint32>* sids) {
+  uint32 sid;
+  if (!GenerateSctpSid(params_vec, &sid)) {
+    LOG(LS_WARNING) << "Could not generated an SCTP SID.";
+    return false;
+  }
+  sids->push_back(sid);
+  return true;
+}
+
+// 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 (!IsMediaContent(&*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);
+    }
+  }
+}
+
+template <typename IdStruct>
+class UsedIds {
+ public:
+  UsedIds(int min_allowed_id, int max_allowed_id)
+      : min_allowed_id_(min_allowed_id),
+        max_allowed_id_(max_allowed_id),
+        next_id_(max_allowed_id) {
+  }
+
+  // Loops through all Id in |ids| and changes its id if it is
+  // already in use by another IdStruct. Call this methods with all Id
+  // in a session description to make sure no duplicate ids exists.
+  // Note that typename Id must be a type of IdStruct.
+  template <typename Id>
+  void FindAndSetIdUsed(std::vector<Id>* ids) {
+    for (typename std::vector<Id>::iterator it = ids->begin();
+         it != ids->end(); ++it) {
+      FindAndSetIdUsed(&*it);
+    }
+  }
+
+  // Finds and sets an unused id if the |idstruct| id is already in use.
+  void FindAndSetIdUsed(IdStruct* idstruct) {
+    const int original_id = idstruct->id;
+    int new_id = idstruct->id;
+
+    if (original_id > max_allowed_id_ || original_id < min_allowed_id_) {
+      // If the original id is not in range - this is an id that can't be
+      // dynamically changed.
+      return;
+    }
+
+    if (IsIdUsed(original_id)) {
+      new_id = FindUnusedId();
+      LOG(LS_WARNING) << "Duplicate id found. Reassigning from " << original_id
+          << " to " << new_id;
+      idstruct->id = new_id;
+    }
+    SetIdUsed(new_id);
+  }
+
+ private:
+  // Returns the first unused id in reverse order.
+  // This hopefully reduce the risk of more collisions. We want to change the
+  // default ids as little as possible.
+  int FindUnusedId() {
+    while (IsIdUsed(next_id_) && next_id_ >= min_allowed_id_) {
+      --next_id_;
+    }
+    ASSERT(next_id_ >= min_allowed_id_);
+    return next_id_;
+  }
+
+  bool IsIdUsed(int new_id) {
+    return id_set_.find(new_id) != id_set_.end();
+  }
+
+  void SetIdUsed(int new_id) {
+    id_set_.insert(new_id);
+  }
+
+  const int min_allowed_id_;
+  const int max_allowed_id_;
+  int next_id_;
+  std::set<int> id_set_;
+};
+
+// Helper class used for finding duplicate RTP payload types among audio, video
+// and data codecs. When bundle is used the payload types may not collide.
+class UsedPayloadTypes : public UsedIds<Codec> {
+ public:
+  UsedPayloadTypes()
+      : UsedIds<Codec>(kDynamicPayloadTypeMin, kDynamicPayloadTypeMax) {
+  }
+
+
+ private:
+  static const int kDynamicPayloadTypeMin = 96;
+  static const int kDynamicPayloadTypeMax = 127;
+};
+
+// Helper class used for finding duplicate RTP Header extension ids among
+// audio and video extensions.
+class UsedRtpHeaderExtensionIds : public UsedIds<RtpHeaderExtension> {
+ public:
+  UsedRtpHeaderExtensionIds()
+      : UsedIds<RtpHeaderExtension>(kLocalIdMin, kLocalIdMax) {
+  }
+
+ private:
+  // Min and Max local identifier as specified by RFC5285.
+  static const int kLocalIdMin = 1;
+  static const int kLocalIdMax = 255;
+};
+
+static bool IsSctp(const MediaContentDescription* desc) {
+  return ((desc->protocol() == kMediaProtocolSctp) ||
+          (desc->protocol() == kMediaProtocolDtlsSctp));
+}
+
+// Adds a StreamParams for each Stream in Streams with media type
+// media_type to content_description.
+// |current_params| - All currently known StreamParams of any media type.
+template <class C>
+static bool AddStreamParams(
+    MediaType media_type,
+    const MediaSessionOptions::Streams& streams,
+    StreamParamsVec* current_streams,
+    MediaContentDescriptionImpl<C>* content_description,
+    const bool add_legacy_stream) {
+  const bool include_rtx_stream =
+    ContainsRtxCodec(content_description->codecs());
+
+  if (streams.empty() && add_legacy_stream) {
+    // TODO(perkj): Remove this legacy stream when all apps use StreamParams.
+    std::vector<uint32> ssrcs;
+    if (IsSctp(content_description)) {
+      GenerateSctpSids(*current_streams, &ssrcs);
+    } else {
+      GenerateSsrcs(*current_streams, include_rtx_stream, &ssrcs);
+    }
+    if (include_rtx_stream) {
+      content_description->AddLegacyStream(ssrcs[0], ssrcs[1]);
+      content_description->set_multistream(true);
+    } else {
+      content_description->AddLegacyStream(ssrcs[0]);
+    }
+    return true;
+  }
+
+  MediaSessionOptions::Streams::const_iterator stream_it;
+  for (stream_it = streams.begin();
+       stream_it != streams.end(); ++stream_it) {
+    if (stream_it->type != media_type)
+      continue;  // Wrong media type.
+
+    StreamParams param;
+    // groupid is empty for StreamParams generated using
+    // MediaSessionDescriptionFactory.
+    if (!GetStreamByIds(*current_streams, "", stream_it->id,
+                        &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_streams, streams, stream_it->sync_label,
+                         &cname)) {
+        return false;
+      }
+
+      std::vector<uint32> ssrcs;
+      if (IsSctp(content_description)) {
+        GenerateSctpSids(*current_streams, &ssrcs);
+      } else {
+        GenerateSsrcs(*current_streams, include_rtx_stream, &ssrcs);
+      }
+      StreamParams stream_param;
+      stream_param.id = stream_it->id;
+      stream_param.ssrcs.push_back(ssrcs[0]);
+      if (include_rtx_stream) {
+        stream_param.AddFidSsrc(ssrcs[0], ssrcs[1]);
+        content_description->set_multistream(true);
+      }
+      stream_param.cname = cname;
+      stream_param.sync_label = stream_it->sync_label;
+      content_description->AddStream(stream_param);
+
+      // Store the new StreamParams in current_streams.
+      // This is necessary so that we can use the CNAME for other media types.
+      current_streams->push_back(stream_param);
+    } else {
+      content_description->AddStream(param);
+    }
+  }
+  return true;
+}
+
+// Updates the transport infos of the |sdesc| according to the given
+// |bundle_group|. The transport infos of the content names within the
+// |bundle_group| should be updated to use the ufrag and pwd of the first
+// content within the |bundle_group|.
+static bool UpdateTransportInfoForBundle(const ContentGroup& bundle_group,
+                                         SessionDescription* sdesc) {
+  // The bundle should not be empty.
+  if (!sdesc || !bundle_group.FirstContentName()) {
+    return false;
+  }
+
+  // We should definitely have a transport for the first content.
+  std::string selected_content_name = *bundle_group.FirstContentName();
+  const TransportInfo* selected_transport_info =
+      sdesc->GetTransportInfoByName(selected_content_name);
+  if (!selected_transport_info) {
+    return false;
+  }
+
+  // Set the other contents to use the same ICE credentials.
+  const std::string selected_ufrag =
+      selected_transport_info->description.ice_ufrag;
+  const std::string selected_pwd =
+      selected_transport_info->description.ice_pwd;
+  for (TransportInfos::iterator it =
+           sdesc->transport_infos().begin();
+       it != sdesc->transport_infos().end(); ++it) {
+    if (bundle_group.HasContentName(it->content_name) &&
+        it->content_name != selected_content_name) {
+      it->description.ice_ufrag = selected_ufrag;
+      it->description.ice_pwd = selected_pwd;
+    }
+  }
+  return true;
+}
+
+// Gets the CryptoParamsVec of the given |content_name| from |sdesc|, and
+// sets it to |cryptos|.
+static bool GetCryptosByName(const SessionDescription* sdesc,
+                             const std::string& content_name,
+                             CryptoParamsVec* cryptos) {
+  if (!sdesc || !cryptos) {
+    return false;
+  }
+
+  const ContentInfo* content = sdesc->GetContentByName(content_name);
+  if (!IsMediaContent(content) || !content->description) {
+    return false;
+  }
+
+  const MediaContentDescription* media_desc =
+      static_cast<const MediaContentDescription*>(content->description);
+  *cryptos = media_desc->cryptos();
+  return true;
+}
+
+// Predicate function used by the remove_if.
+// Returns true if the |crypto|'s cipher_suite is not found in |filter|.
+static bool CryptoNotFound(const CryptoParams crypto,
+                           const CryptoParamsVec* filter) {
+  if (filter == NULL) {
+    return true;
+  }
+  for (CryptoParamsVec::const_iterator it = filter->begin();
+       it != filter->end(); ++it) {
+    if (it->cipher_suite == crypto.cipher_suite) {
+      return false;
+    }
+  }
+  return true;
+}
+
+// Prunes the |target_cryptos| by removing the crypto params (cipher_suite)
+// which are not available in |filter|.
+static void PruneCryptos(const CryptoParamsVec& filter,
+                         CryptoParamsVec* target_cryptos) {
+  if (!target_cryptos) {
+    return;
+  }
+  target_cryptos->erase(std::remove_if(target_cryptos->begin(),
+                                       target_cryptos->end(),
+                                       bind2nd(ptr_fun(CryptoNotFound),
+                                               &filter)),
+                        target_cryptos->end());
+}
+
+static bool IsRtpContent(SessionDescription* sdesc,
+                         const std::string& content_name) {
+  bool is_rtp = false;
+  ContentInfo* content = sdesc->GetContentByName(content_name);
+  if (IsMediaContent(content)) {
+    MediaContentDescription* media_desc =
+        static_cast<MediaContentDescription*>(content->description);
+    if (!media_desc) {
+      return false;
+    }
+    is_rtp = media_desc->protocol().empty() ||
+             talk_base::starts_with(media_desc->protocol().data(),
+                                    kMediaProtocolRtpPrefix);
+  }
+  return is_rtp;
+}
+
+// Updates the crypto parameters of the |sdesc| according to the given
+// |bundle_group|. The crypto parameters of all the contents within the
+// |bundle_group| should be updated to use the common subset of the
+// available cryptos.
+static bool UpdateCryptoParamsForBundle(const ContentGroup& bundle_group,
+                                        SessionDescription* sdesc) {
+  // The bundle should not be empty.
+  if (!sdesc || !bundle_group.FirstContentName()) {
+    return false;
+  }
+
+  // Get the common cryptos.
+  const ContentNames& content_names = bundle_group.content_names();
+  CryptoParamsVec common_cryptos;
+  for (ContentNames::const_iterator it = content_names.begin();
+       it != content_names.end(); ++it) {
+    if (!IsRtpContent(sdesc, *it)) {
+      continue;
+    }
+    if (it == content_names.begin()) {
+      // Initial the common_cryptos with the first content in the bundle group.
+      if (!GetCryptosByName(sdesc, *it, &common_cryptos)) {
+        return false;
+      }
+      if (common_cryptos.empty()) {
+        // If there's no crypto params, we should just return.
+        return true;
+      }
+    } else {
+      CryptoParamsVec cryptos;
+      if (!GetCryptosByName(sdesc, *it, &cryptos)) {
+        return false;
+      }
+      PruneCryptos(cryptos, &common_cryptos);
+    }
+  }
+
+  if (common_cryptos.empty()) {
+    return false;
+  }
+
+  // Update to use the common cryptos.
+  for (ContentNames::const_iterator it = content_names.begin();
+       it != content_names.end(); ++it) {
+    if (!IsRtpContent(sdesc, *it)) {
+      continue;
+    }
+    ContentInfo* content = sdesc->GetContentByName(*it);
+    if (IsMediaContent(content)) {
+      MediaContentDescription* media_desc =
+          static_cast<MediaContentDescription*>(content->description);
+      if (!media_desc) {
+        return false;
+      }
+      media_desc->set_cryptos(common_cryptos);
+    }
+  }
+  return true;
+}
+
+template <class C>
+static bool ContainsRtxCodec(const std::vector<C>& codecs) {
+  typename std::vector<C>::const_iterator it;
+  for (it = codecs.begin(); it != codecs.end(); ++it) {
+    if (IsRtxCodec(*it)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+template <class C>
+static bool IsRtxCodec(const C& codec) {
+  return stricmp(codec.name.c_str(), kRtxCodecName) == 0;
+}
+
+// Create a media content to be offered in a session-initiate,
+// according to the given options.rtcp_mux, options.is_muc,
+// options.streams, codecs, secure_transport, crypto, and streams.  If we don't
+// currently have crypto (in current_cryptos) and it is enabled (in
+// secure_policy), crypto is created (according to crypto_suites).  If
+// add_legacy_stream is true, and current_streams is empty, a legacy
+// stream is created.  The created content is added to the offer.
+template <class C>
+static bool CreateMediaContentOffer(
+    const MediaSessionOptions& options,
+    const std::vector<C>& codecs,
+    const SecureMediaPolicy& secure_policy,
+    const CryptoParamsVec* current_cryptos,
+    const std::vector<std::string>& crypto_suites,
+    const RtpHeaderExtensions& rtp_extensions,
+    bool add_legacy_stream,
+    StreamParamsVec* current_streams,
+    MediaContentDescriptionImpl<C>* offer) {
+  offer->AddCodecs(codecs);
+  offer->SortCodecs();
+
+  offer->set_crypto_required(secure_policy == SEC_REQUIRED);
+  offer->set_rtcp_mux(options.rtcp_mux_enabled);
+  offer->set_multistream(options.is_muc);
+  offer->set_rtp_header_extensions(rtp_extensions);
+
+  if (!AddStreamParams(
+          offer->type(), options.streams, current_streams,
+          offer, add_legacy_stream)) {
+    return false;
+  }
+
+#ifdef HAVE_SRTP
+  if (secure_policy != SEC_DISABLED) {
+    if (current_cryptos) {
+      AddMediaCryptos(*current_cryptos, offer);
+    }
+    if (offer->cryptos().empty()) {
+      if (!CreateMediaCryptos(crypto_suites, offer)) {
+        return false;
+      }
+    }
+  }
+#endif
+
+  if (offer->crypto_required() && offer->cryptos().empty()) {
+    return false;
+  }
+  return true;
+}
+
+template <class C>
+static void NegotiateCodecs(const std::vector<C>& local_codecs,
+                            const std::vector<C>& offered_codecs,
+                            std::vector<C>* negotiated_codecs) {
+  typename std::vector<C>::const_iterator ours;
+  for (ours = local_codecs.begin();
+       ours != local_codecs.end(); ++ours) {
+    typename std::vector<C>::const_iterator theirs;
+    for (theirs = offered_codecs.begin();
+         theirs != offered_codecs.end(); ++theirs) {
+      if (ours->Matches(*theirs)) {
+        C negotiated = *ours;
+        negotiated.IntersectFeedbackParams(*theirs);
+        if (IsRtxCodec(negotiated)) {
+          // Only negotiate RTX if kCodecParamAssociatedPayloadType has been
+          // set.
+          std::string apt_value;
+          if (!theirs->GetParam(kCodecParamAssociatedPayloadType, &apt_value)) {
+            LOG(LS_WARNING) << "RTX missing associated payload type.";
+            continue;
+          }
+          negotiated.SetParam(kCodecParamAssociatedPayloadType, apt_value);
+        }
+        negotiated.id = theirs->id;
+        negotiated_codecs->push_back(negotiated);
+      }
+    }
+  }
+}
+
+template <class C>
+static bool FindMatchingCodec(const std::vector<C>& codecs,
+                              const C& codec_to_match,
+                              C* found_codec) {
+  for (typename std::vector<C>::const_iterator it = codecs.begin();
+       it  != codecs.end(); ++it) {
+    if (it->Matches(codec_to_match)) {
+      if (found_codec != NULL) {
+        *found_codec= *it;
+      }
+      return true;
+    }
+  }
+  return false;
+}
+
+// Adds all codecs from |reference_codecs| to |offered_codecs| that dont'
+// already exist in |offered_codecs| and ensure the payload types don't
+// collide.
+template <class C>
+static void FindCodecsToOffer(
+    const std::vector<C>& reference_codecs,
+    std::vector<C>* offered_codecs,
+    UsedPayloadTypes* used_pltypes) {
+
+  typedef std::map<int, C> RtxCodecReferences;
+  RtxCodecReferences new_rtx_codecs;
+
+  // Find all new RTX codecs.
+  for (typename std::vector<C>::const_iterator it = reference_codecs.begin();
+       it != reference_codecs.end(); ++it) {
+    if (!FindMatchingCodec<C>(*offered_codecs, *it, NULL) && IsRtxCodec(*it)) {
+      C rtx_codec = *it;
+      int referenced_pl_type =
+          talk_base::FromString<int>(
+              rtx_codec.params[kCodecParamAssociatedPayloadType]);
+      new_rtx_codecs.insert(std::pair<int, C>(referenced_pl_type,
+                                              rtx_codec));
+    }
+  }
+
+  // Add all new codecs that are not RTX codecs.
+  for (typename std::vector<C>::const_iterator it = reference_codecs.begin();
+       it != reference_codecs.end(); ++it) {
+    if (!FindMatchingCodec<C>(*offered_codecs, *it, NULL) && !IsRtxCodec(*it)) {
+      C codec = *it;
+      int original_payload_id = codec.id;
+      used_pltypes->FindAndSetIdUsed(&codec);
+      offered_codecs->push_back(codec);
+
+      // If this codec is referenced by a new RTX codec, update the reference
+      // in the RTX codec with the new payload type.
+      typename RtxCodecReferences::iterator rtx_it =
+          new_rtx_codecs.find(original_payload_id);
+      if (rtx_it != new_rtx_codecs.end()) {
+        C& rtx_codec = rtx_it->second;
+        rtx_codec.params[kCodecParamAssociatedPayloadType] =
+            talk_base::ToString(codec.id);
+      }
+    }
+  }
+
+  // Add all new RTX codecs.
+  for (typename RtxCodecReferences::iterator it = new_rtx_codecs.begin();
+       it != new_rtx_codecs.end(); ++it) {
+    C& rtx_codec = it->second;
+    used_pltypes->FindAndSetIdUsed(&rtx_codec);
+    offered_codecs->push_back(rtx_codec);
+  }
+}
+
+
+static bool FindByUri(const RtpHeaderExtensions& extensions,
+                      const RtpHeaderExtension& ext_to_match,
+                      RtpHeaderExtension* found_extension) {
+  for (RtpHeaderExtensions::const_iterator it = extensions.begin();
+       it  != extensions.end(); ++it) {
+    // We assume that all URIs are given in a canonical format.
+    if (it->uri == ext_to_match.uri) {
+      if (found_extension != NULL) {
+        *found_extension= *it;
+      }
+      return true;
+    }
+  }
+  return false;
+}
+
+static void FindAndSetRtpHdrExtUsed(
+  const RtpHeaderExtensions& reference_extensions,
+  RtpHeaderExtensions* offered_extensions,
+  UsedRtpHeaderExtensionIds* used_extensions) {
+  for (RtpHeaderExtensions::const_iterator it = reference_extensions.begin();
+      it != reference_extensions.end(); ++it) {
+    if (!FindByUri(*offered_extensions, *it, NULL)) {
+      RtpHeaderExtension ext = *it;
+      used_extensions->FindAndSetIdUsed(&ext);
+      offered_extensions->push_back(ext);
+    }
+  }
+}
+
+static void NegotiateRtpHeaderExtensions(
+    const RtpHeaderExtensions& local_extensions,
+    const RtpHeaderExtensions& offered_extensions,
+    RtpHeaderExtensions* negotiated_extenstions) {
+  RtpHeaderExtensions::const_iterator ours;
+  for (ours = local_extensions.begin();
+       ours != local_extensions.end(); ++ours) {
+    RtpHeaderExtension theirs;
+    if (FindByUri(offered_extensions, *ours, &theirs)) {
+      // We respond with their RTP header extension id.
+      negotiated_extenstions->push_back(theirs);
+    }
+  }
+}
+
+static void StripCNCodecs(AudioCodecs* audio_codecs) {
+  AudioCodecs::iterator iter = audio_codecs->begin();
+  while (iter != audio_codecs->end()) {
+    if (stricmp(iter->name.c_str(), kComfortNoiseCodecName) == 0) {
+      iter = audio_codecs->erase(iter);
+    } else {
+      ++iter;
+    }
+  }
+}
+
+// Create a media content to be answered in a session-accept,
+// according to the given options.rtcp_mux, options.streams, codecs,
+// crypto, and streams.  If we don't currently have crypto (in
+// current_cryptos) and it is enabled (in secure_policy), crypto is
+// created (according to crypto_suites).  If add_legacy_stream is
+// true, and current_streams is empty, a legacy stream is created.
+// The codecs, rtcp_mux, and crypto are all negotiated with the offer
+// from the incoming session-initiate.  If the negotiation fails, this
+// method returns false.  The created content is added to the offer.
+template <class C>
+static bool CreateMediaContentAnswer(
+    const MediaContentDescriptionImpl<C>* offer,
+    const MediaSessionOptions& options,
+    const std::vector<C>& local_codecs,
+    const SecureMediaPolicy& sdes_policy,
+    const CryptoParamsVec* current_cryptos,
+    const RtpHeaderExtensions& local_rtp_extenstions,
+    StreamParamsVec* current_streams,
+    bool add_legacy_stream,
+    bool bundle_enabled,
+    MediaContentDescriptionImpl<C>* answer) {
+  std::vector<C> negotiated_codecs;
+  NegotiateCodecs(local_codecs, offer->codecs(), &negotiated_codecs);
+  answer->AddCodecs(negotiated_codecs);
+  answer->SortCodecs();
+  answer->set_protocol(offer->protocol());
+  RtpHeaderExtensions negotiated_rtp_extensions;
+  NegotiateRtpHeaderExtensions(local_rtp_extenstions,
+                               offer->rtp_header_extensions(),
+                               &negotiated_rtp_extensions);
+  answer->set_rtp_header_extensions(negotiated_rtp_extensions);
+
+  answer->set_rtcp_mux(options.rtcp_mux_enabled && offer->rtcp_mux());
+
+  if (sdes_policy != SEC_DISABLED) {
+    CryptoParams crypto;
+    if (SelectCrypto(offer, bundle_enabled, &crypto)) {
+      if (current_cryptos) {
+        FindMatchingCrypto(*current_cryptos, crypto, &crypto);
+      }
+      answer->AddCrypto(crypto);
+    }
+  }
+
+  if (answer->cryptos().empty() &&
+      (offer->crypto_required() || sdes_policy == SEC_REQUIRED)) {
+    return false;
+  }
+
+  if (!AddStreamParams(
+          answer->type(), options.streams, current_streams,
+          answer, add_legacy_stream)) {
+    return false;  // Something went seriously wrong.
+  }
+
+  // Make sure the answer media content direction is per default set as
+  // described in RFC3264 section 6.1.
+  switch (offer->direction()) {
+    case MD_INACTIVE:
+      answer->set_direction(MD_INACTIVE);
+      break;
+    case MD_SENDONLY:
+      answer->set_direction(MD_RECVONLY);
+      break;
+    case MD_RECVONLY:
+      answer->set_direction(MD_SENDONLY);
+      break;
+    case MD_SENDRECV:
+      answer->set_direction(MD_SENDRECV);
+      break;
+    default:
+      break;
+  }
+
+  return true;
+}
+
+static bool IsMediaProtocolSupported(MediaType type,
+                                     const std::string& protocol) {
+  // Data channels can have a protocol of SCTP or SCTP/DTLS.
+  if (type == MEDIA_TYPE_DATA &&
+      (protocol == kMediaProtocolSctp ||
+       protocol == kMediaProtocolDtlsSctp)) {
+    return true;
+  }
+  // Since not all applications serialize and deserialize the media protocol,
+  // we will have to accept |protocol| to be empty.
+  return protocol == kMediaProtocolAvpf || protocol == kMediaProtocolSavpf ||
+      protocol.empty();
+}
+
+static void SetMediaProtocol(bool secure_transport,
+                             MediaContentDescription* desc) {
+  if (!desc->cryptos().empty() || secure_transport)
+    desc->set_protocol(kMediaProtocolSavpf);
+  else
+    desc->set_protocol(kMediaProtocolAvpf);
+}
+
+void MediaSessionOptions::AddStream(MediaType type,
+                                    const std::string& id,
+                                    const std::string& sync_label) {
+  streams.push_back(Stream(type, id, sync_label));
+
+  if (type == MEDIA_TYPE_VIDEO)
+    has_video = true;
+  else if (type == MEDIA_TYPE_AUDIO)
+    has_audio = true;
+  // If we haven't already set the data_channel_type, and we add a
+  // stream, we assume it's an RTP data stream.
+  else if (type == MEDIA_TYPE_DATA && data_channel_type == DCT_NONE)
+    data_channel_type = DCT_RTP;
+}
+
+void MediaSessionOptions::RemoveStream(MediaType type,
+                                       const std::string& id) {
+  Streams::iterator stream_it = streams.begin();
+  for (; stream_it != streams.end(); ++stream_it) {
+    if (stream_it->type == type && stream_it->id == id) {
+      streams.erase(stream_it);
+      return;
+    }
+  }
+  ASSERT(false);
+}
+
+MediaSessionDescriptionFactory::MediaSessionDescriptionFactory(
+    const TransportDescriptionFactory* transport_desc_factory)
+    : secure_(SEC_DISABLED),
+      add_legacy_(true),
+      transport_desc_factory_(transport_desc_factory) {
+}
+
+MediaSessionDescriptionFactory::MediaSessionDescriptionFactory(
+    ChannelManager* channel_manager,
+    const TransportDescriptionFactory* transport_desc_factory)
+    : secure_(SEC_DISABLED),
+      add_legacy_(true),
+      transport_desc_factory_(transport_desc_factory) {
+  channel_manager->GetSupportedAudioCodecs(&audio_codecs_);
+  channel_manager->GetSupportedAudioRtpHeaderExtensions(&audio_rtp_extensions_);
+  channel_manager->GetSupportedVideoCodecs(&video_codecs_);
+  channel_manager->GetSupportedVideoRtpHeaderExtensions(&video_rtp_extensions_);
+  channel_manager->GetSupportedDataCodecs(&data_codecs_);
+}
+
+SessionDescription* MediaSessionDescriptionFactory::CreateOffer(
+    const MediaSessionOptions& options,
+    const SessionDescription* current_description) const {
+  bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED);
+
+  scoped_ptr<SessionDescription> offer(new SessionDescription());
+
+  StreamParamsVec current_streams;
+  GetCurrentStreamParams(current_description, &current_streams);
+
+  AudioCodecs audio_codecs;
+  VideoCodecs video_codecs;
+  DataCodecs data_codecs;
+  GetCodecsToOffer(current_description, &audio_codecs, &video_codecs,
+                   &data_codecs);
+
+  if (!options.vad_enabled) {
+    // If application doesn't want CN codecs in offer.
+    StripCNCodecs(&audio_codecs);
+  }
+
+  RtpHeaderExtensions audio_rtp_extensions;
+  RtpHeaderExtensions video_rtp_extensions;
+  GetRtpHdrExtsToOffer(current_description, &audio_rtp_extensions,
+                       &video_rtp_extensions);
+
+  // Handle m=audio.
+  if (options.has_audio) {
+    scoped_ptr<AudioContentDescription> audio(new AudioContentDescription());
+    std::vector<std::string> crypto_suites;
+    GetSupportedAudioCryptoSuites(&crypto_suites);
+    if (!CreateMediaContentOffer(
+            options,
+            audio_codecs,
+            secure(),
+            GetCryptos(GetFirstAudioContentDescription(current_description)),
+            crypto_suites,
+            audio_rtp_extensions,
+            add_legacy_,
+            &current_streams,
+            audio.get())) {
+      return NULL;
+    }
+
+    audio->set_lang(lang_);
+    SetMediaProtocol(secure_transport, audio.get());
+    offer->AddContent(CN_AUDIO, NS_JINGLE_RTP, audio.release());
+    if (!AddTransportOffer(CN_AUDIO, options.transport_options,
+                           current_description, offer.get())) {
+      return NULL;
+    }
+  }
+
+  // Handle m=video.
+  if (options.has_video) {
+    scoped_ptr<VideoContentDescription> video(new VideoContentDescription());
+    std::vector<std::string> crypto_suites;
+    GetSupportedVideoCryptoSuites(&crypto_suites);
+    if (!CreateMediaContentOffer(
+            options,
+            video_codecs,
+            secure(),
+            GetCryptos(GetFirstVideoContentDescription(current_description)),
+            crypto_suites,
+            video_rtp_extensions,
+            add_legacy_,
+            &current_streams,
+            video.get())) {
+      return NULL;
+    }
+
+    video->set_bandwidth(options.video_bandwidth);
+    SetMediaProtocol(secure_transport, video.get());
+    offer->AddContent(CN_VIDEO, NS_JINGLE_RTP, video.release());
+    if (!AddTransportOffer(CN_VIDEO, options.transport_options,
+                           current_description, offer.get())) {
+      return NULL;
+    }
+  }
+
+  // Handle m=data.
+  if (options.has_data()) {
+    scoped_ptr<DataContentDescription> data(new DataContentDescription());
+    bool is_sctp = (options.data_channel_type == DCT_SCTP);
+
+    std::vector<std::string> crypto_suites;
+    cricket::SecurePolicy sdes_policy = secure();
+    if (is_sctp) {
+      // SDES doesn't make sense for SCTP, so we disable it, and we only
+      // get SDES crypto suites for RTP-based data channels.
+      sdes_policy = cricket::SEC_DISABLED;
+      // Unlike SetMediaProtocol below, we need to set the protocol
+      // before we call CreateMediaContentOffer.  Otherwise,
+      // CreateMediaContentOffer won't know this is SCTP and will
+      // generate SSRCs rather than SIDs.
+      data->set_protocol(
+          secure_transport ? kMediaProtocolDtlsSctp : kMediaProtocolSctp);
+    } else {
+      GetSupportedDataCryptoSuites(&crypto_suites);
+    }
+
+    if (!CreateMediaContentOffer(
+            options,
+            data_codecs,
+            sdes_policy,
+            GetCryptos(GetFirstDataContentDescription(current_description)),
+            crypto_suites,
+            RtpHeaderExtensions(),
+            add_legacy_,
+            &current_streams,
+            data.get())) {
+      return NULL;
+    }
+
+    if (is_sctp) {
+      offer->AddContent(CN_DATA, NS_JINGLE_DRAFT_SCTP, data.release());
+    } else {
+      data->set_bandwidth(options.data_bandwidth);
+      SetMediaProtocol(secure_transport, data.get());
+      offer->AddContent(CN_DATA, NS_JINGLE_RTP, data.release());
+    }
+    if (!AddTransportOffer(CN_DATA, options.transport_options,
+                           current_description, offer.get())) {
+      return NULL;
+    }
+  }
+
+  // Bundle the contents together, if we've been asked to do so, and update any
+  // parameters that need to be tweaked for BUNDLE.
+  if (options.bundle_enabled) {
+    ContentGroup offer_bundle(GROUP_TYPE_BUNDLE);
+    for (ContentInfos::const_iterator content = offer->contents().begin();
+       content != offer->contents().end(); ++content) {
+      offer_bundle.AddContentName(content->name);
+    }
+    offer->AddGroup(offer_bundle);
+    if (!UpdateTransportInfoForBundle(offer_bundle, offer.get())) {
+      LOG(LS_ERROR) << "CreateOffer failed to UpdateTransportInfoForBundle.";
+      return NULL;
+    }
+    if (!UpdateCryptoParamsForBundle(offer_bundle, offer.get())) {
+      LOG(LS_ERROR) << "CreateOffer failed to UpdateCryptoParamsForBundle.";
+      return NULL;
+    }
+  }
+
+  return offer.release();
+}
+
+SessionDescription* MediaSessionDescriptionFactory::CreateAnswer(
+    const SessionDescription* offer, const MediaSessionOptions& options,
+    const SessionDescription* current_description) const {
+  // 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.
+  scoped_ptr<SessionDescription> answer(new SessionDescription());
+
+  StreamParamsVec current_streams;
+  GetCurrentStreamParams(current_description, &current_streams);
+
+  bool bundle_enabled =
+      offer->HasGroup(GROUP_TYPE_BUNDLE) && options.bundle_enabled;
+
+  // Handle m=audio.
+  const ContentInfo* audio_content = GetFirstAudioContent(offer);
+  if (audio_content) {
+    scoped_ptr<TransportDescription> audio_transport(
+        CreateTransportAnswer(audio_content->name, offer,
+                              options.transport_options,
+                              current_description));
+    if (!audio_transport) {
+      return NULL;
+    }
+
+    AudioCodecs audio_codecs = audio_codecs_;
+    if (!options.vad_enabled) {
+      StripCNCodecs(&audio_codecs);
+    }
+
+    scoped_ptr<AudioContentDescription> audio_answer(
+        new AudioContentDescription());
+    // Do not require or create SDES cryptos if DTLS is used.
+    cricket::SecurePolicy sdes_policy =
+        audio_transport->secure() ? cricket::SEC_DISABLED : secure();
+    if (!CreateMediaContentAnswer(
+            static_cast<const AudioContentDescription*>(
+                audio_content->description),
+            options,
+            audio_codecs,
+            sdes_policy,
+            GetCryptos(GetFirstAudioContentDescription(current_description)),
+            audio_rtp_extensions_,
+            &current_streams,
+            add_legacy_,
+            bundle_enabled,
+            audio_answer.get())) {
+      return NULL;  // Fails the session setup.
+    }
+
+    bool rejected = !options.has_audio || audio_content->rejected ||
+          !IsMediaProtocolSupported(MEDIA_TYPE_AUDIO,
+                                    audio_answer->protocol());
+    if (!rejected) {
+      AddTransportAnswer(audio_content->name, *(audio_transport.get()),
+                         answer.get());
+    } else {
+      // RFC 3264
+      // The answer MUST contain the same number of m-lines as the offer.
+      LOG(LS_INFO) << "Audio is not supported in the answer.";
+    }
+
+    answer->AddContent(audio_content->name, audio_content->type, rejected,
+                       audio_answer.release());
+  } else {
+    LOG(LS_INFO) << "Audio is not available in the offer.";
+  }
+
+  // Handle m=video.
+  const ContentInfo* video_content = GetFirstVideoContent(offer);
+  if (video_content) {
+    scoped_ptr<TransportDescription> video_transport(
+        CreateTransportAnswer(video_content->name, offer,
+                              options.transport_options,
+                              current_description));
+    if (!video_transport) {
+      return NULL;
+    }
+
+    scoped_ptr<VideoContentDescription> video_answer(
+        new VideoContentDescription());
+    // Do not require or create SDES cryptos if DTLS is used.
+    cricket::SecurePolicy sdes_policy =
+        video_transport->secure() ? cricket::SEC_DISABLED : secure();
+    if (!CreateMediaContentAnswer(
+            static_cast<const VideoContentDescription*>(
+                video_content->description),
+            options,
+            video_codecs_,
+            sdes_policy,
+            GetCryptos(GetFirstVideoContentDescription(current_description)),
+            video_rtp_extensions_,
+            &current_streams,
+            add_legacy_,
+            bundle_enabled,
+            video_answer.get())) {
+      return NULL;
+    }
+    bool rejected = !options.has_video || video_content->rejected ||
+        !IsMediaProtocolSupported(MEDIA_TYPE_VIDEO, video_answer->protocol());
+    if (!rejected) {
+      if (!AddTransportAnswer(video_content->name, *(video_transport.get()),
+                              answer.get())) {
+        return NULL;
+      }
+      video_answer->set_bandwidth(options.video_bandwidth);
+    } else {
+      // RFC 3264
+      // The answer MUST contain the same number of m-lines as the offer.
+      LOG(LS_INFO) << "Video is not supported in the answer.";
+    }
+    answer->AddContent(video_content->name, video_content->type, rejected,
+                       video_answer.release());
+  } else {
+    LOG(LS_INFO) << "Video is not available in the offer.";
+  }
+
+  // Handle m=data.
+  const ContentInfo* data_content = GetFirstDataContent(offer);
+  if (data_content) {
+    scoped_ptr<TransportDescription> data_transport(
+        CreateTransportAnswer(data_content->name, offer,
+                              options.transport_options,
+                              current_description));
+    if (!data_transport) {
+      return NULL;
+    }
+    scoped_ptr<DataContentDescription> data_answer(
+        new DataContentDescription());
+    // Do not require or create SDES cryptos if DTLS is used.
+    cricket::SecurePolicy sdes_policy =
+        data_transport->secure() ? cricket::SEC_DISABLED : secure();
+    if (!CreateMediaContentAnswer(
+            static_cast<const DataContentDescription*>(
+                data_content->description),
+            options,
+            data_codecs_,
+            sdes_policy,
+            GetCryptos(GetFirstDataContentDescription(current_description)),
+            RtpHeaderExtensions(),
+            &current_streams,
+            add_legacy_,
+            bundle_enabled,
+            data_answer.get())) {
+      return NULL;  // Fails the session setup.
+    }
+
+    bool rejected = !options.has_data() || data_content->rejected ||
+        !IsMediaProtocolSupported(MEDIA_TYPE_DATA, data_answer->protocol());
+    if (!rejected) {
+      data_answer->set_bandwidth(options.data_bandwidth);
+      if (!AddTransportAnswer(data_content->name, *(data_transport.get()),
+                              answer.get())) {
+        return NULL;
+      }
+    } else {
+      // RFC 3264
+      // The answer MUST contain the same number of m-lines as the offer.
+      LOG(LS_INFO) << "Data is not supported in the answer.";
+    }
+    answer->AddContent(data_content->name, data_content->type, rejected,
+                       data_answer.release());
+  } else {
+    LOG(LS_INFO) << "Data is not available in the offer.";
+  }
+
+  // If the offer supports BUNDLE, and we want to use it too, create a BUNDLE
+  // group in the answer with the appropriate content names.
+  if (offer->HasGroup(GROUP_TYPE_BUNDLE) && options.bundle_enabled) {
+    const ContentGroup* offer_bundle = offer->GetGroupByName(GROUP_TYPE_BUNDLE);
+    ContentGroup answer_bundle(GROUP_TYPE_BUNDLE);
+    for (ContentInfos::const_iterator content = answer->contents().begin();
+       content != answer->contents().end(); ++content) {
+      if (!content->rejected && offer_bundle->HasContentName(content->name)) {
+        answer_bundle.AddContentName(content->name);
+      }
+    }
+    if (answer_bundle.FirstContentName()) {
+      answer->AddGroup(answer_bundle);
+
+      // Share the same ICE credentials and crypto params across all contents,
+      // as BUNDLE requires.
+      if (!UpdateTransportInfoForBundle(answer_bundle, answer.get())) {
+        LOG(LS_ERROR) << "CreateAnswer failed to UpdateTransportInfoForBundle.";
+        return NULL;
+      }
+
+      if (!UpdateCryptoParamsForBundle(answer_bundle, answer.get())) {
+        LOG(LS_ERROR) << "CreateAnswer failed to UpdateCryptoParamsForBundle.";
+        return NULL;
+      }
+    }
+  }
+
+  return answer.release();
+}
+
+// Gets the TransportInfo of the given |content_name| from the
+// |current_description|. If doesn't exist, returns a new one.
+static const TransportDescription* GetTransportDescription(
+    const std::string& content_name,
+    const SessionDescription* current_description) {
+  const TransportDescription* desc = NULL;
+  if (current_description) {
+    const TransportInfo* info =
+        current_description->GetTransportInfoByName(content_name);
+    if (info) {
+      desc = &info->description;
+    }
+  }
+  return desc;
+}
+
+void MediaSessionDescriptionFactory::GetCodecsToOffer(
+    const SessionDescription* current_description,
+    AudioCodecs* audio_codecs,
+    VideoCodecs* video_codecs,
+    DataCodecs* data_codecs) const {
+  UsedPayloadTypes used_pltypes;
+  audio_codecs->clear();
+  video_codecs->clear();
+  data_codecs->clear();
+
+
+  // First - get all codecs from the current description if the media type
+  // is used.
+  // Add them to |used_pltypes| so the payloadtype is not reused if a new media
+  // type is added.
+  if (current_description) {
+    const AudioContentDescription* audio =
+        GetFirstAudioContentDescription(current_description);
+    if (audio) {
+      *audio_codecs = audio->codecs();
+      used_pltypes.FindAndSetIdUsed<AudioCodec>(audio_codecs);
+    }
+    const VideoContentDescription* video =
+        GetFirstVideoContentDescription(current_description);
+    if (video) {
+      *video_codecs = video->codecs();
+      used_pltypes.FindAndSetIdUsed<VideoCodec>(video_codecs);
+    }
+    const DataContentDescription* data =
+        GetFirstDataContentDescription(current_description);
+    if (data) {
+      *data_codecs = data->codecs();
+      used_pltypes.FindAndSetIdUsed<DataCodec>(data_codecs);
+    }
+  }
+
+  // Add our codecs that are not in |current_description|.
+  FindCodecsToOffer<AudioCodec>(audio_codecs_, audio_codecs, &used_pltypes);
+  FindCodecsToOffer<VideoCodec>(video_codecs_, video_codecs, &used_pltypes);
+  FindCodecsToOffer<DataCodec>(data_codecs_, data_codecs, &used_pltypes);
+}
+
+void MediaSessionDescriptionFactory::GetRtpHdrExtsToOffer(
+    const SessionDescription* current_description,
+    RtpHeaderExtensions* audio_extensions,
+    RtpHeaderExtensions* video_extensions) const {
+  UsedRtpHeaderExtensionIds used_ids;
+  audio_extensions->clear();
+  video_extensions->clear();
+
+  // First - get all extensions from the current description if the media type
+  // is used.
+  // Add them to |used_ids| so the local ids are not reused if a new media
+  // type is added.
+  if (current_description) {
+    const AudioContentDescription* audio =
+        GetFirstAudioContentDescription(current_description);
+    if (audio) {
+      *audio_extensions = audio->rtp_header_extensions();
+      used_ids.FindAndSetIdUsed(audio_extensions);
+    }
+    const VideoContentDescription* video =
+        GetFirstVideoContentDescription(current_description);
+    if (video) {
+      *video_extensions = video->rtp_header_extensions();
+      used_ids.FindAndSetIdUsed(video_extensions);
+    }
+  }
+
+  // Add our default RTP header extensions that are not in
+  // |current_description|.
+  FindAndSetRtpHdrExtUsed(audio_rtp_header_extensions(), audio_extensions,
+                          &used_ids);
+  FindAndSetRtpHdrExtUsed(video_rtp_header_extensions(), video_extensions,
+                          &used_ids);
+}
+
+bool MediaSessionDescriptionFactory::AddTransportOffer(
+  const std::string& content_name,
+  const TransportOptions& transport_options,
+  const SessionDescription* current_desc,
+  SessionDescription* offer_desc) const {
+  if (!transport_desc_factory_)
+     return false;
+  const TransportDescription* current_tdesc =
+      GetTransportDescription(content_name, current_desc);
+  talk_base::scoped_ptr<TransportDescription> new_tdesc(
+      transport_desc_factory_->CreateOffer(transport_options, current_tdesc));
+  bool ret = (new_tdesc.get() != NULL &&
+      offer_desc->AddTransportInfo(TransportInfo(content_name, *new_tdesc)));
+  if (!ret) {
+    LOG(LS_ERROR)
+        << "Failed to AddTransportOffer, content name=" << content_name;
+  }
+  return ret;
+}
+
+TransportDescription* MediaSessionDescriptionFactory::CreateTransportAnswer(
+    const std::string& content_name,
+    const SessionDescription* offer_desc,
+    const TransportOptions& transport_options,
+    const SessionDescription* current_desc) const {
+  if (!transport_desc_factory_)
+    return NULL;
+  const TransportDescription* offer_tdesc =
+      GetTransportDescription(content_name, offer_desc);
+  const TransportDescription* current_tdesc =
+      GetTransportDescription(content_name, current_desc);
+  return
+      transport_desc_factory_->CreateAnswer(offer_tdesc, transport_options,
+                                            current_tdesc);
+}
+
+bool MediaSessionDescriptionFactory::AddTransportAnswer(
+    const std::string& content_name,
+    const TransportDescription& transport_desc,
+    SessionDescription* answer_desc) const {
+  if (!answer_desc->AddTransportInfo(TransportInfo(content_name,
+                                                   transport_desc))) {
+    LOG(LS_ERROR)
+        << "Failed to AddTransportAnswer, content name=" << content_name;
+    return false;
+  }
+  return true;
+}
+
+bool IsMediaContent(const ContentInfo* content) {
+  return (content &&
+          (content->type == NS_JINGLE_RTP ||
+           content->type == NS_JINGLE_DRAFT_SCTP));
+}
+
+bool IsAudioContent(const ContentInfo* content) {
+  return IsMediaContentOfType(content, MEDIA_TYPE_AUDIO);
+}
+
+bool IsVideoContent(const ContentInfo* content) {
+  return IsMediaContentOfType(content, MEDIA_TYPE_VIDEO);
+}
+
+bool IsDataContent(const ContentInfo* content) {
+  return IsMediaContentOfType(content, MEDIA_TYPE_DATA);
+}
+
+static const ContentInfo* GetFirstMediaContent(const ContentInfos& contents,
+                                               MediaType media_type) {
+  for (ContentInfos::const_iterator content = contents.begin();
+       content != contents.end(); content++) {
+    if (IsMediaContentOfType(&*content, media_type)) {
+      return &*content;
+    }
+  }
+  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);
+}
+
+const ContentInfo* GetFirstDataContent(const ContentInfos& contents) {
+  return GetFirstMediaContent(contents, MEDIA_TYPE_DATA);
+}
+
+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);
+}
+
+const ContentInfo* GetFirstVideoContent(const SessionDescription* sdesc) {
+  return GetFirstMediaContent(sdesc, MEDIA_TYPE_VIDEO);
+}
+
+const ContentInfo* GetFirstDataContent(const SessionDescription* sdesc) {
+  return GetFirstMediaContent(sdesc, MEDIA_TYPE_DATA);
+}
+
+const MediaContentDescription* GetFirstMediaContentDescription(
+    const SessionDescription* sdesc, MediaType media_type) {
+  const ContentInfo* content = GetFirstMediaContent(sdesc, media_type);
+  const ContentDescription* description = content ? content->description : NULL;
+  return static_cast<const MediaContentDescription*>(description);
+}
+
+const AudioContentDescription* GetFirstAudioContentDescription(
+    const SessionDescription* sdesc) {
+  return static_cast<const AudioContentDescription*>(
+      GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_AUDIO));
+}
+
+const VideoContentDescription* GetFirstVideoContentDescription(
+    const SessionDescription* sdesc) {
+  return static_cast<const VideoContentDescription*>(
+      GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_VIDEO));
+}
+
+const DataContentDescription* GetFirstDataContentDescription(
+    const SessionDescription* sdesc) {
+  return static_cast<const DataContentDescription*>(
+      GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_DATA));
+}
+
+bool GetMediaChannelNameFromComponent(
+    int component, MediaType media_type, std::string* channel_name) {
+  if (media_type == MEDIA_TYPE_AUDIO) {
+    if (component == ICE_CANDIDATE_COMPONENT_RTP) {
+      *channel_name = GICE_CHANNEL_NAME_RTP;
+      return true;
+    } else if (component == ICE_CANDIDATE_COMPONENT_RTCP) {
+      *channel_name = GICE_CHANNEL_NAME_RTCP;
+      return true;
+    }
+  } else if (media_type == MEDIA_TYPE_VIDEO) {
+    if (component == ICE_CANDIDATE_COMPONENT_RTP) {
+      *channel_name = GICE_CHANNEL_NAME_VIDEO_RTP;
+      return true;
+    } else if (component == ICE_CANDIDATE_COMPONENT_RTCP) {
+      *channel_name = GICE_CHANNEL_NAME_VIDEO_RTCP;
+      return true;
+    }
+  } else if (media_type == MEDIA_TYPE_DATA) {
+    if (component == ICE_CANDIDATE_COMPONENT_RTP) {
+      *channel_name = GICE_CHANNEL_NAME_DATA_RTP;
+      return true;
+    } else if (component == ICE_CANDIDATE_COMPONENT_RTCP) {
+      *channel_name = GICE_CHANNEL_NAME_DATA_RTCP;
+      return true;
+    }
+  }
+
+  return false;
+}
+
+bool GetMediaComponentFromChannelName(
+    const std::string& channel_name, int* component) {
+  if (channel_name == GICE_CHANNEL_NAME_RTP ||
+      channel_name == GICE_CHANNEL_NAME_VIDEO_RTP ||
+      channel_name == GICE_CHANNEL_NAME_DATA_RTP) {
+    *component = ICE_CANDIDATE_COMPONENT_RTP;
+    return true;
+  } else if (channel_name == GICE_CHANNEL_NAME_RTCP ||
+             channel_name == GICE_CHANNEL_NAME_VIDEO_RTCP ||
+             channel_name == GICE_CHANNEL_NAME_DATA_RTP) {
+    *component = ICE_CANDIDATE_COMPONENT_RTCP;
+    return true;
+  }
+
+  return false;
+}
+
+bool GetMediaTypeFromChannelName(
+    const std::string& channel_name, MediaType* media_type) {
+  if (channel_name == GICE_CHANNEL_NAME_RTP ||
+      channel_name == GICE_CHANNEL_NAME_RTCP) {
+    *media_type = MEDIA_TYPE_AUDIO;
+    return true;
+  } else if (channel_name == GICE_CHANNEL_NAME_VIDEO_RTP ||
+             channel_name == GICE_CHANNEL_NAME_VIDEO_RTCP) {
+    *media_type = MEDIA_TYPE_VIDEO;
+    return true;
+  } else if (channel_name == GICE_CHANNEL_NAME_DATA_RTP ||
+             channel_name == GICE_CHANNEL_NAME_DATA_RTCP) {
+    *media_type = MEDIA_TYPE_DATA;
+    return true;
+  }
+
+  return false;
+}
+
+}  // namespace cricket
diff --git a/talk/session/media/mediasession.h b/talk/session/media/mediasession.h
new file mode 100644
index 0000000..3274804
--- /dev/null
+++ b/talk/session/media/mediasession.h
@@ -0,0 +1,497 @@
+/*
+ * 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.
+ */
+
+// Types and classes used in media session descriptions.
+
+#ifndef TALK_SESSION_MEDIA_MEDIASESSION_H_
+#define TALK_SESSION_MEDIA_MEDIASESSION_H_
+
+#include <string>
+#include <vector>
+#include <algorithm>
+
+#include "talk/base/scoped_ptr.h"
+#include "talk/media/base/codec.h"
+#include "talk/media/base/constants.h"
+#include "talk/media/base/cryptoparams.h"
+#include "talk/media/base/mediachannel.h"
+#include "talk/media/base/mediaengine.h"  // For DataChannelType
+#include "talk/media/base/streamparams.h"
+#include "talk/p2p/base/sessiondescription.h"
+#include "talk/p2p/base/transport.h"
+#include "talk/p2p/base/transportdescriptionfactory.h"
+
+namespace cricket {
+
+class ChannelManager;
+typedef std::vector<AudioCodec> AudioCodecs;
+typedef std::vector<VideoCodec> VideoCodecs;
+typedef std::vector<DataCodec> DataCodecs;
+typedef std::vector<CryptoParams> CryptoParamsVec;
+typedef std::vector<RtpHeaderExtension> RtpHeaderExtensions;
+
+// TODO(juberti): Replace SecureMediaPolicy with SecurePolicy everywhere.
+typedef SecurePolicy SecureMediaPolicy;
+
+enum MediaType {
+  MEDIA_TYPE_AUDIO,
+  MEDIA_TYPE_VIDEO,
+  MEDIA_TYPE_DATA
+};
+
+enum MediaContentDirection {
+  MD_INACTIVE,
+  MD_SENDONLY,
+  MD_RECVONLY,
+  MD_SENDRECV
+};
+
+// RTC4585 RTP/AVPF
+extern const char kMediaProtocolAvpf[];
+// RFC5124 RTP/SAVPF
+extern const char kMediaProtocolSavpf[];
+
+extern const char kMediaProtocolRtpPrefix[];
+
+extern const char kMediaProtocolSctp[];
+extern const char kMediaProtocolDtlsSctp[];
+
+// Options to control how session descriptions are generated.
+const int kAutoBandwidth = -1;
+const int kBufferedModeDisabled = 0;
+// TODO(pthatcher): This is imposed by usrsctp lib.  I have no idea
+// why it is 9.  Figure out why, and make it bigger, hopefully up to
+// 2^16-1.
+const uint32 kMaxSctpSid = 9;
+
+struct MediaSessionOptions {
+  MediaSessionOptions() :
+      has_audio(true),  // Audio enabled by default.
+      has_video(false),
+      data_channel_type(DCT_NONE),
+      is_muc(false),
+      vad_enabled(true),  // When disabled, removes all CN codecs from SDP.
+      rtcp_mux_enabled(true),
+      bundle_enabled(false),
+      video_bandwidth(kAutoBandwidth),
+      data_bandwidth(kDataMaxBandwidth) {
+  }
+
+  bool has_data() const { return data_channel_type != DCT_NONE; }
+
+  // Add a stream with MediaType type and id.
+  // All streams with the same sync_label will get the same CNAME.
+  // All ids must be unique.
+  void AddStream(MediaType type,
+                 const std::string& id,
+                 const std::string& sync_label);
+  void RemoveStream(MediaType type, const std::string& id);
+
+  bool has_audio;
+  bool has_video;
+  DataChannelType data_channel_type;
+  bool is_muc;
+  bool vad_enabled;
+  bool rtcp_mux_enabled;
+  bool bundle_enabled;
+  // bps. -1 == auto.
+  int video_bandwidth;
+  int data_bandwidth;
+  TransportOptions transport_options;
+
+  struct Stream {
+    Stream(MediaType type,
+           const std::string& id,
+           const std::string& sync_label)
+        : type(type), id(id), sync_label(sync_label) {
+    }
+    MediaType type;
+    std::string id;
+    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()
+      : rtcp_mux_(false),
+        bandwidth_(kAutoBandwidth),
+        crypto_required_(false),
+        rtp_header_extensions_set_(false),
+        multistream_(false),
+        conference_mode_(false),
+        partial_(false),
+        buffered_mode_latency_(kBufferedModeDisabled),
+        direction_(MD_SENDRECV) {
+  }
+
+  virtual MediaType type() const = 0;
+  virtual bool has_codecs() const = 0;
+
+  // |protocol| is the expected media transport protocol, such as RTP/AVPF,
+  // RTP/SAVPF or SCTP/DTLS.
+  std::string protocol() const { return protocol_; }
+  void set_protocol(const std::string& protocol) { protocol_ = protocol; }
+
+  MediaContentDirection direction() const { return direction_; }
+  void set_direction(MediaContentDirection direction) {
+    direction_ = direction;
+  }
+
+  bool rtcp_mux() const { return rtcp_mux_; }
+  void set_rtcp_mux(bool mux) { rtcp_mux_ = mux; }
+
+  int bandwidth() const { return bandwidth_; }
+  void set_bandwidth(int bandwidth) { bandwidth_ = bandwidth; }
+
+  const std::vector<CryptoParams>& cryptos() const { return cryptos_; }
+  void AddCrypto(const CryptoParams& params) {
+    cryptos_.push_back(params);
+  }
+  void set_cryptos(const std::vector<CryptoParams>& cryptos) {
+    cryptos_ = cryptos;
+  }
+  bool crypto_required() const { return crypto_required_; }
+  void set_crypto_required(bool crypto) {
+    crypto_required_ = crypto;
+  }
+
+  const RtpHeaderExtensions& rtp_header_extensions() const {
+    return rtp_header_extensions_;
+  }
+  void set_rtp_header_extensions(const RtpHeaderExtensions& extensions) {
+    rtp_header_extensions_ = extensions;
+    rtp_header_extensions_set_ = true;
+  }
+  void AddRtpHeaderExtension(const RtpHeaderExtension& ext) {
+    rtp_header_extensions_.push_back(ext);
+    rtp_header_extensions_set_ = true;
+  }
+  void ClearRtpHeaderExtensions() {
+    rtp_header_extensions_.clear();
+    rtp_header_extensions_set_ = true;
+  }
+  // We can't always tell if an empty list of header extensions is
+  // because the other side doesn't support them, or just isn't hooked up to
+  // signal them. For now we assume an empty list means no signaling, but
+  // provide the ClearRtpHeaderExtensions method to allow "no support" to be
+  // clearly indicated (i.e. when derived from other information).
+  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(pthatcher): 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));
+  }
+  void AddLegacyStream(uint32 ssrc, uint32 fid_ssrc) {
+    StreamParams sp = StreamParams::CreateLegacy(ssrc);
+    sp.AddFidSsrc(ssrc, fid_ssrc);
+    streams_.push_back(sp);
+  }
+  // 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_;  }
+
+  void set_buffered_mode_latency(int latency) {
+    buffered_mode_latency_ = latency;
+  }
+  int buffered_mode_latency() const { return buffered_mode_latency_; }
+
+ protected:
+  bool rtcp_mux_;
+  int bandwidth_;
+  std::string protocol_;
+  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_;
+  int buffered_mode_latency_;
+  MediaContentDirection direction_;
+};
+
+template <class C>
+class MediaContentDescriptionImpl : public MediaContentDescription {
+ public:
+  struct PreferenceSort {
+    bool operator()(C a, C b) { return a.preference > b.preference; }
+  };
+
+  const std::vector<C>& codecs() const { return codecs_; }
+  void set_codecs(const std::vector<C>& codecs) { codecs_ = codecs; }
+  virtual bool has_codecs() const { return !codecs_.empty(); }
+  bool HasCodec(int id) {
+    bool found = false;
+    for (typename std::vector<C>::iterator iter = codecs_.begin();
+         iter != codecs_.end(); ++iter) {
+      if (iter->id == id) {
+        found = true;
+        break;
+      }
+    }
+    return found;
+  }
+  void AddCodec(const C& codec) {
+    codecs_.push_back(codec);
+  }
+  void AddCodecs(const std::vector<C>& codecs) {
+    typename std::vector<C>::const_iterator codec;
+    for (codec = codecs.begin(); codec != codecs.end(); ++codec) {
+      AddCodec(*codec);
+    }
+  }
+  void SortCodecs() {
+    std::sort(codecs_.begin(), codecs_.end(), PreferenceSort());
+  }
+
+ private:
+  std::vector<C> codecs_;
+};
+
+class AudioContentDescription : public MediaContentDescriptionImpl<AudioCodec> {
+ public:
+  AudioContentDescription() :
+      agc_minus_10db_(false) {}
+
+  virtual ContentDescription* Copy() const {
+    return new AudioContentDescription(*this);
+  }
+  virtual MediaType type() const { return MEDIA_TYPE_AUDIO; }
+
+  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 agc_minus_10db_;
+
+ private:
+  std::string lang_;
+};
+
+class VideoContentDescription : public MediaContentDescriptionImpl<VideoCodec> {
+ public:
+  virtual ContentDescription* Copy() const {
+    return new VideoContentDescription(*this);
+  }
+  virtual MediaType type() const { return MEDIA_TYPE_VIDEO; }
+};
+
+class DataContentDescription : public MediaContentDescriptionImpl<DataCodec> {
+ public:
+  virtual ContentDescription* Copy() const {
+    return new DataContentDescription(*this);
+  }
+  virtual MediaType type() const { return MEDIA_TYPE_DATA; }
+};
+
+// Creates media session descriptions according to the supplied codecs and
+// other fields, as well as the supplied per-call options.
+// When creating answers, performs the appropriate negotiation
+// of the various fields to determine the proper result.
+class MediaSessionDescriptionFactory {
+ public:
+  // Default ctor; use methods below to set configuration.
+  // The TransportDescriptionFactory is not owned by MediaSessionDescFactory,
+  // so it must be kept alive by the user of this class.
+  explicit MediaSessionDescriptionFactory(
+      const TransportDescriptionFactory* factory);
+  // This helper automatically sets up the factory to get its configuration
+  // from the specified ChannelManager.
+  MediaSessionDescriptionFactory(ChannelManager* cmanager,
+                                 const TransportDescriptionFactory* factory);
+
+  const AudioCodecs& audio_codecs() const { return audio_codecs_; }
+  void set_audio_codecs(const AudioCodecs& codecs) { audio_codecs_ = codecs; }
+  void set_audio_rtp_header_extensions(const RtpHeaderExtensions& extensions) {
+    audio_rtp_extensions_ = extensions;
+  }
+  const RtpHeaderExtensions& audio_rtp_header_extensions() const {
+    return audio_rtp_extensions_;
+  }
+  const VideoCodecs& video_codecs() const { return video_codecs_; }
+  void set_video_codecs(const VideoCodecs& codecs) { video_codecs_ = codecs; }
+  void set_video_rtp_header_extensions(const RtpHeaderExtensions& extensions) {
+    video_rtp_extensions_ = extensions;
+  }
+  const RtpHeaderExtensions& video_rtp_header_extensions() const {
+    return video_rtp_extensions_;
+  }
+  const DataCodecs& data_codecs() const { return data_codecs_; }
+  void set_data_codecs(const DataCodecs& codecs) { data_codecs_ = codecs; }
+  SecurePolicy secure() const { return secure_; }
+  void set_secure(SecurePolicy 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,
+      const SessionDescription* current_description) const;
+  SessionDescription* CreateAnswer(
+        const SessionDescription* offer,
+        const MediaSessionOptions& options,
+        const SessionDescription* current_description) const;
+
+ private:
+  void GetCodecsToOffer(const SessionDescription* current_description,
+                        AudioCodecs* audio_codecs,
+                        VideoCodecs* video_codecs,
+                        DataCodecs* data_codecs) const;
+  void GetRtpHdrExtsToOffer(const SessionDescription* current_description,
+                            RtpHeaderExtensions* audio_extensions,
+                            RtpHeaderExtensions* video_extensions) const;
+  bool AddTransportOffer(
+      const std::string& content_name,
+      const TransportOptions& transport_options,
+      const SessionDescription* current_desc,
+      SessionDescription* offer) const;
+
+  TransportDescription* CreateTransportAnswer(
+      const std::string& content_name,
+      const SessionDescription* offer_desc,
+      const TransportOptions& transport_options,
+      const SessionDescription* current_desc) const;
+
+  bool AddTransportAnswer(
+      const std::string& content_name,
+      const TransportDescription& transport_desc,
+      SessionDescription* answer_desc) const;
+
+  AudioCodecs audio_codecs_;
+  RtpHeaderExtensions audio_rtp_extensions_;
+  VideoCodecs video_codecs_;
+  RtpHeaderExtensions video_rtp_extensions_;
+  DataCodecs data_codecs_;
+  SecurePolicy secure_;
+  bool add_legacy_;
+  std::string lang_;
+  const TransportDescriptionFactory* transport_desc_factory_;
+};
+
+// Convenience functions.
+bool IsMediaContent(const ContentInfo* content);
+bool IsAudioContent(const ContentInfo* content);
+bool IsVideoContent(const ContentInfo* content);
+bool IsDataContent(const ContentInfo* content);
+const ContentInfo* GetFirstAudioContent(const ContentInfos& contents);
+const ContentInfo* GetFirstVideoContent(const ContentInfos& contents);
+const ContentInfo* GetFirstDataContent(const ContentInfos& contents);
+const ContentInfo* GetFirstAudioContent(const SessionDescription* sdesc);
+const ContentInfo* GetFirstVideoContent(const SessionDescription* sdesc);
+const ContentInfo* GetFirstDataContent(const SessionDescription* sdesc);
+const AudioContentDescription* GetFirstAudioContentDescription(
+    const SessionDescription* sdesc);
+const VideoContentDescription* GetFirstVideoContentDescription(
+    const SessionDescription* sdesc);
+const DataContentDescription* GetFirstDataContentDescription(
+    const SessionDescription* sdesc);
+bool GetStreamBySsrc(
+    const SessionDescription* sdesc, MediaType media_type,
+    uint32 ssrc, StreamParams* stream_out);
+bool GetStreamByIds(
+    const SessionDescription* sdesc, MediaType media_type,
+    const std::string& groupid, const std::string& id,
+    StreamParams* stream_out);
+
+// Functions for translating media candidate names.
+
+// For converting between media ICE component and G-ICE channel
+// names.  For example:
+// "rtp" <=> 1
+// "rtcp" <=> 2
+// "video_rtp" <=> 1
+// "video_rtcp" <=> 2
+// Will not convert in the general case of arbitrary channel names,
+// but is useful for cases where we have candidates for media
+// channels.
+// returns false if there is no mapping.
+bool GetMediaChannelNameFromComponent(
+    int component, cricket::MediaType media_type, std::string* channel_name);
+bool GetMediaComponentFromChannelName(
+    const std::string& channel_name, int* component);
+bool GetMediaTypeFromChannelName(
+    const std::string& channel_name, cricket::MediaType* media_type);
+
+void GetSupportedAudioCryptoSuites(std::vector<std::string>* crypto_suites);
+void GetSupportedVideoCryptoSuites(std::vector<std::string>* crypto_suites);
+void GetSupportedDataCryptoSuites(std::vector<std::string>* crypto_suites);
+void GetSupportedDefaultCryptoSuites(std::vector<std::string>* crypto_suites);
+}  // namespace cricket
+
+#endif  // TALK_SESSION_MEDIA_MEDIASESSION_H_
diff --git a/talk/session/media/mediasession_unittest.cc b/talk/session/media/mediasession_unittest.cc
new file mode 100644
index 0000000..5b0a859
--- /dev/null
+++ b/talk/session/media/mediasession_unittest.cc
@@ -0,0 +1,1905 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+#include <vector>
+
+#include "talk/base/gunit.h"
+#include "talk/base/fakesslidentity.h"
+#include "talk/base/messagedigest.h"
+#include "talk/media/base/codec.h"
+#include "talk/media/base/testutils.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/transportdescription.h"
+#include "talk/p2p/base/transportinfo.h"
+#include "talk/session/media/mediasession.h"
+#include "talk/session/media/srtpfilter.h"
+
+#ifdef HAVE_SRTP
+#define ASSERT_CRYPTO(cd, s, cs) \
+    ASSERT_FALSE(cd->crypto_required()); \
+    ASSERT_EQ(s, cd->cryptos().size()); \
+    ASSERT_EQ(std::string(cs), cd->cryptos()[0].cipher_suite)
+#else
+#define ASSERT_CRYPTO(cd, s, cs) \
+  ASSERT_FALSE(cd->crypto_required()); \
+  ASSERT_EQ(0U, cd->cryptos().size());
+#endif
+
+typedef std::vector<cricket::Candidate> Candidates;
+
+using cricket::MediaContentDescription;
+using cricket::MediaSessionDescriptionFactory;
+using cricket::MediaSessionOptions;
+using cricket::MediaType;
+using cricket::SessionDescription;
+using cricket::SsrcGroup;
+using cricket::StreamParams;
+using cricket::StreamParamsVec;
+using cricket::TransportDescription;
+using cricket::TransportDescriptionFactory;
+using cricket::TransportInfo;
+using cricket::ContentInfo;
+using cricket::CryptoParamsVec;
+using cricket::AudioContentDescription;
+using cricket::VideoContentDescription;
+using cricket::DataContentDescription;
+using cricket::GetFirstAudioContentDescription;
+using cricket::GetFirstVideoContentDescription;
+using cricket::GetFirstDataContentDescription;
+using cricket::kAutoBandwidth;
+using cricket::AudioCodec;
+using cricket::VideoCodec;
+using cricket::DataCodec;
+using cricket::NS_JINGLE_RTP;
+using cricket::MEDIA_TYPE_AUDIO;
+using cricket::MEDIA_TYPE_VIDEO;
+using cricket::MEDIA_TYPE_DATA;
+using cricket::RtpHeaderExtension;
+using cricket::SEC_DISABLED;
+using cricket::SEC_ENABLED;
+using cricket::SEC_REQUIRED;
+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, 6),
+  AudioCodec(102, "iLBC",   8000,  13300, 1, 5),
+  AudioCodec(0,   "PCMU",   8000,  64000, 1, 4),
+  AudioCodec(8,   "PCMA",   8000,  64000, 1, 3),
+  AudioCodec(117, "red",    8000,  0,     1, 2),
+  AudioCodec(107, "CN",     48000, 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 DataCodec kDataCodecs1[] = {
+  DataCodec(98, "binary-data", 2),
+  DataCodec(99, "utf8-text", 1)
+};
+
+static const DataCodec kDataCodecs2[] = {
+  DataCodec(126, "binary-data", 2),
+  DataCodec(127, "utf8-text", 1)
+};
+
+static const DataCodec kDataCodecsAnswer[] = {
+  DataCodec(98, "binary-data", 2),
+  DataCodec(99, "utf8-text", 1)
+};
+
+static const RtpHeaderExtension kAudioRtpExtension1[] = {
+  RtpHeaderExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level", 8),
+  RtpHeaderExtension("http://google.com/testing/audio_something", 10),
+};
+
+static const RtpHeaderExtension kAudioRtpExtension2[] = {
+  RtpHeaderExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level", 2),
+  RtpHeaderExtension("http://google.com/testing/audio_something_else", 8),
+};
+
+static const RtpHeaderExtension kAudioRtpExtensionAnswer[] = {
+  RtpHeaderExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level", 8),
+};
+
+static const RtpHeaderExtension kVideoRtpExtension1[] = {
+  RtpHeaderExtension("urn:ietf:params:rtp-hdrext:toffset", 14),
+  RtpHeaderExtension("http://google.com/testing/video_something", 15),
+};
+
+static const RtpHeaderExtension kVideoRtpExtension2[] = {
+  RtpHeaderExtension("urn:ietf:params:rtp-hdrext:toffset", 2),
+  RtpHeaderExtension("http://google.com/testing/video_something_else", 14),
+};
+
+static const RtpHeaderExtension kVideoRtpExtensionAnswer[] = {
+  RtpHeaderExtension("urn:ietf:params:rtp-hdrext:toffset", 14),
+};
+
+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";
+static const char kDataTrack1[] = "data_1";
+static const char kDataTrack2[] = "data_2";
+static const char kDataTrack3[] = "data_3";
+
+class MediaSessionDescriptionFactoryTest : public testing::Test {
+ public:
+  MediaSessionDescriptionFactoryTest()
+      : f1_(&tdf1_), f2_(&tdf2_), id1_("id1"), id2_("id2") {
+    f1_.set_audio_codecs(MAKE_VECTOR(kAudioCodecs1));
+    f1_.set_video_codecs(MAKE_VECTOR(kVideoCodecs1));
+    f1_.set_data_codecs(MAKE_VECTOR(kDataCodecs1));
+    f2_.set_audio_codecs(MAKE_VECTOR(kAudioCodecs2));
+    f2_.set_video_codecs(MAKE_VECTOR(kVideoCodecs2));
+    f2_.set_data_codecs(MAKE_VECTOR(kDataCodecs2));
+    tdf1_.set_identity(&id1_);
+    tdf2_.set_identity(&id2_);
+  }
+
+
+  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;
+  }
+
+  void TestTransportInfo(bool offer, const MediaSessionOptions& options,
+                         bool has_current_desc) {
+    const std::string current_audio_ufrag = "current_audio_ufrag";
+    const std::string current_audio_pwd = "current_audio_pwd";
+    const std::string current_video_ufrag = "current_video_ufrag";
+    const std::string current_video_pwd = "current_video_pwd";
+    const std::string current_data_ufrag = "current_data_ufrag";
+    const std::string current_data_pwd = "current_data_pwd";
+    talk_base::scoped_ptr<SessionDescription> current_desc;
+    talk_base::scoped_ptr<SessionDescription> desc;
+    if (has_current_desc) {
+      current_desc.reset(new SessionDescription());
+      EXPECT_TRUE(current_desc->AddTransportInfo(
+          TransportInfo("audio",
+                        TransportDescription("", std::vector<std::string>(),
+                                             current_audio_ufrag,
+                                             current_audio_pwd,
+                                             cricket::ICEMODE_FULL,
+                                             NULL, Candidates()))));
+      EXPECT_TRUE(current_desc->AddTransportInfo(
+          TransportInfo("video",
+                        TransportDescription("", std::vector<std::string>(),
+                                             current_video_ufrag,
+                                             current_video_pwd,
+                                             cricket::ICEMODE_FULL,
+                                             NULL, Candidates()))));
+      EXPECT_TRUE(current_desc->AddTransportInfo(
+          TransportInfo("data",
+                        TransportDescription("", std::vector<std::string>(),
+                                             current_data_ufrag,
+                                             current_data_pwd,
+                                             cricket::ICEMODE_FULL,
+                                             NULL, Candidates()))));
+    }
+    if (offer) {
+      desc.reset(f1_.CreateOffer(options, current_desc.get()));
+    } else {
+      talk_base::scoped_ptr<SessionDescription> offer;
+      offer.reset(f1_.CreateOffer(options, NULL));
+      desc.reset(f1_.CreateAnswer(offer.get(), options, current_desc.get()));
+    }
+    ASSERT_TRUE(desc.get() != NULL);
+    const TransportInfo* ti_audio = desc->GetTransportInfoByName("audio");
+    if (options.has_audio) {
+      EXPECT_TRUE(ti_audio != NULL);
+      if (has_current_desc) {
+        EXPECT_EQ(current_audio_ufrag, ti_audio->description.ice_ufrag);
+        EXPECT_EQ(current_audio_pwd, ti_audio->description.ice_pwd);
+      } else {
+        EXPECT_EQ(static_cast<size_t>(cricket::ICE_UFRAG_LENGTH),
+                  ti_audio->description.ice_ufrag.size());
+        EXPECT_EQ(static_cast<size_t>(cricket::ICE_PWD_LENGTH),
+                  ti_audio->description.ice_pwd.size());
+      }
+
+    } else {
+      EXPECT_TRUE(ti_audio == NULL);
+    }
+    const TransportInfo* ti_video = desc->GetTransportInfoByName("video");
+    if (options.has_video) {
+      EXPECT_TRUE(ti_video != NULL);
+      if (options.bundle_enabled) {
+        EXPECT_EQ(ti_audio->description.ice_ufrag,
+                  ti_video->description.ice_ufrag);
+        EXPECT_EQ(ti_audio->description.ice_pwd,
+                  ti_video->description.ice_pwd);
+      } else {
+        if (has_current_desc) {
+          EXPECT_EQ(current_video_ufrag, ti_video->description.ice_ufrag);
+          EXPECT_EQ(current_video_pwd, ti_video->description.ice_pwd);
+        } else {
+          EXPECT_EQ(static_cast<size_t>(cricket::ICE_UFRAG_LENGTH),
+                    ti_video->description.ice_ufrag.size());
+          EXPECT_EQ(static_cast<size_t>(cricket::ICE_PWD_LENGTH),
+                    ti_video->description.ice_pwd.size());
+        }
+      }
+    } else {
+      EXPECT_TRUE(ti_video == NULL);
+    }
+    const TransportInfo* ti_data = desc->GetTransportInfoByName("data");
+    if (options.has_data()) {
+      EXPECT_TRUE(ti_data != NULL);
+      if (options.bundle_enabled) {
+        EXPECT_EQ(ti_audio->description.ice_ufrag,
+                  ti_data->description.ice_ufrag);
+        EXPECT_EQ(ti_audio->description.ice_pwd,
+                  ti_data->description.ice_pwd);
+      } else {
+        if (has_current_desc) {
+          EXPECT_EQ(current_data_ufrag, ti_data->description.ice_ufrag);
+          EXPECT_EQ(current_data_pwd, ti_data->description.ice_pwd);
+        } else {
+          EXPECT_EQ(static_cast<size_t>(cricket::ICE_UFRAG_LENGTH),
+                    ti_data->description.ice_ufrag.size());
+          EXPECT_EQ(static_cast<size_t>(cricket::ICE_PWD_LENGTH),
+                    ti_data->description.ice_pwd.size());
+        }
+      }
+    } else {
+      EXPECT_TRUE(ti_video == NULL);
+    }
+  }
+
+  void TestCryptoWithBundle(bool offer) {
+    f1_.set_secure(SEC_ENABLED);
+    MediaSessionOptions options;
+    options.has_audio = true;
+    options.has_video = true;
+    options.data_channel_type = cricket::DCT_RTP;
+    talk_base::scoped_ptr<SessionDescription> ref_desc;
+    talk_base::scoped_ptr<SessionDescription> desc;
+    if (offer) {
+      options.bundle_enabled = false;
+      ref_desc.reset(f1_.CreateOffer(options, NULL));
+      options.bundle_enabled = true;
+      desc.reset(f1_.CreateOffer(options, ref_desc.get()));
+    } else {
+      options.bundle_enabled = true;
+      ref_desc.reset(f1_.CreateOffer(options, NULL));
+      desc.reset(f1_.CreateAnswer(ref_desc.get(), options, NULL));
+    }
+    ASSERT_TRUE(desc.get() != NULL);
+    const cricket::MediaContentDescription* audio_media_desc =
+        static_cast<const cricket::MediaContentDescription*> (
+            desc.get()->GetContentDescriptionByName("audio"));
+    ASSERT_TRUE(audio_media_desc != NULL);
+    const cricket::MediaContentDescription* video_media_desc =
+        static_cast<const cricket::MediaContentDescription*> (
+            desc.get()->GetContentDescriptionByName("video"));
+    ASSERT_TRUE(video_media_desc != NULL);
+    EXPECT_TRUE(CompareCryptoParams(audio_media_desc->cryptos(),
+                                    video_media_desc->cryptos()));
+    EXPECT_EQ(1u, audio_media_desc->cryptos().size());
+    EXPECT_EQ(std::string(CS_AES_CM_128_HMAC_SHA1_80),
+              audio_media_desc->cryptos()[0].cipher_suite);
+
+    // Verify the selected crypto is one from the reference audio
+    // media content.
+    const cricket::MediaContentDescription* ref_audio_media_desc =
+        static_cast<const cricket::MediaContentDescription*> (
+            ref_desc.get()->GetContentDescriptionByName("audio"));
+    bool found = false;
+    for (size_t i = 0; i < ref_audio_media_desc->cryptos().size(); ++i) {
+      if (ref_audio_media_desc->cryptos()[i].Matches(
+          audio_media_desc->cryptos()[0])) {
+        found = true;
+        break;
+      }
+    }
+    EXPECT_TRUE(found);
+  }
+
+  // This test that the audio and video media direction is set to
+  // |expected_direction_in_answer| in an answer if the offer direction is set
+  // to |direction_in_offer|.
+  void TestMediaDirectionInAnswer(
+      cricket::MediaContentDirection direction_in_offer,
+      cricket::MediaContentDirection expected_direction_in_answer) {
+    MediaSessionOptions opts;
+    opts.has_video = true;
+    talk_base::scoped_ptr<SessionDescription> offer(
+        f1_.CreateOffer(opts, NULL));
+    ASSERT_TRUE(offer.get() != NULL);
+    ContentInfo* ac_offer= offer->GetContentByName("audio");
+    ASSERT_TRUE(ac_offer != NULL);
+    AudioContentDescription* acd_offer =
+        static_cast<AudioContentDescription*>(ac_offer->description);
+    acd_offer->set_direction(direction_in_offer);
+    ContentInfo* vc_offer= offer->GetContentByName("video");
+    ASSERT_TRUE(vc_offer != NULL);
+    VideoContentDescription* vcd_offer =
+        static_cast<VideoContentDescription*>(vc_offer->description);
+    vcd_offer->set_direction(direction_in_offer);
+
+    talk_base::scoped_ptr<SessionDescription> answer(
+        f2_.CreateAnswer(offer.get(), opts, NULL));
+    const AudioContentDescription* acd_answer =
+        GetFirstAudioContentDescription(answer.get());
+    EXPECT_EQ(expected_direction_in_answer, acd_answer->direction());
+    const VideoContentDescription* vcd_answer =
+        GetFirstVideoContentDescription(answer.get());
+    EXPECT_EQ(expected_direction_in_answer, vcd_answer->direction());
+  }
+
+  bool VerifyNoCNCodecs(const cricket::ContentInfo* content) {
+    const cricket::ContentDescription* description = content->description;
+    ASSERT(description != NULL);
+    const cricket::AudioContentDescription* audio_content_desc =
+        static_cast<const cricket::AudioContentDescription*> (description);
+    ASSERT(audio_content_desc != NULL);
+    for (size_t i = 0; i < audio_content_desc->codecs().size(); ++i) {
+      if (audio_content_desc->codecs()[i].name == "CN")
+        return false;
+    }
+    return true;
+  }
+
+ protected:
+  MediaSessionDescriptionFactory f1_;
+  MediaSessionDescriptionFactory f2_;
+  TransportDescriptionFactory tdf1_;
+  TransportDescriptionFactory tdf2_;
+  talk_base::FakeSSLIdentity id1_;
+  talk_base::FakeSSLIdentity id2_;
+};
+
+// 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, 2U, CS_AES_CM_128_HMAC_SHA1_32);
+  EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), acd->protocol());
+}
+
+// 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, 2U, CS_AES_CM_128_HMAC_SHA1_32);
+  EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), acd->protocol());
+  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, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+  EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), vcd->protocol());
+}
+
+// Test creating an offer with bundle where the Codecs have the same dynamic
+// RTP playlod type. The test verifies that the offer don't contain the
+// duplicate RTP payload types.
+TEST_F(MediaSessionDescriptionFactoryTest, TestBundleOfferWithSameCodecPlType) {
+  const VideoCodec& offered_video_codec = f2_.video_codecs()[0];
+  const AudioCodec& offered_audio_codec = f2_.audio_codecs()[0];
+  const DataCodec& offered_data_codec = f2_.data_codecs()[0];
+  ASSERT_EQ(offered_video_codec.id, offered_audio_codec.id);
+  ASSERT_EQ(offered_video_codec.id, offered_data_codec.id);
+
+  MediaSessionOptions opts;
+  opts.has_audio = true;
+  opts.has_video = true;
+  opts.data_channel_type = cricket::DCT_RTP;
+  opts.bundle_enabled = true;
+  talk_base::scoped_ptr<SessionDescription>
+  offer(f2_.CreateOffer(opts, NULL));
+  const VideoContentDescription* vcd =
+      GetFirstVideoContentDescription(offer.get());
+  const AudioContentDescription* acd =
+      GetFirstAudioContentDescription(offer.get());
+  const DataContentDescription* dcd =
+      GetFirstDataContentDescription(offer.get());
+  ASSERT_TRUE(NULL != vcd);
+  ASSERT_TRUE(NULL != acd);
+  ASSERT_TRUE(NULL != dcd);
+  EXPECT_NE(vcd->codecs()[0].id, acd->codecs()[0].id);
+  EXPECT_NE(vcd->codecs()[0].id, dcd->codecs()[0].id);
+  EXPECT_NE(acd->codecs()[0].id, dcd->codecs()[0].id);
+  EXPECT_EQ(vcd->codecs()[0].name, offered_video_codec.name);
+  EXPECT_EQ(acd->codecs()[0].name, offered_audio_codec.name);
+  EXPECT_EQ(dcd->codecs()[0].name, offered_data_codec.name);
+}
+
+// Test creating an updated offer with with bundle, audio, video and data
+// after an audio only session has been negotiated.
+TEST_F(MediaSessionDescriptionFactoryTest,
+       TestCreateUpdatedVideoOfferWithBundle) {
+  f1_.set_secure(SEC_ENABLED);
+  f2_.set_secure(SEC_ENABLED);
+  MediaSessionOptions opts;
+  opts.has_audio = true;
+  opts.has_video = false;
+  opts.data_channel_type = cricket::DCT_NONE;
+  opts.bundle_enabled = true;
+  talk_base::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
+  talk_base::scoped_ptr<SessionDescription> answer(
+      f2_.CreateAnswer(offer.get(), opts, NULL));
+
+  MediaSessionOptions updated_opts;
+  updated_opts.has_audio = true;
+  updated_opts.has_video = true;
+  updated_opts.data_channel_type = cricket::DCT_RTP;
+  updated_opts.bundle_enabled = true;
+  talk_base::scoped_ptr<SessionDescription> updated_offer(f1_.CreateOffer(
+      updated_opts, answer.get()));
+
+  const AudioContentDescription* acd =
+      GetFirstAudioContentDescription(updated_offer.get());
+  const VideoContentDescription* vcd =
+      GetFirstVideoContentDescription(updated_offer.get());
+  const DataContentDescription* dcd =
+      GetFirstDataContentDescription(updated_offer.get());
+  EXPECT_TRUE(NULL != vcd);
+  EXPECT_TRUE(NULL != acd);
+  EXPECT_TRUE(NULL != dcd);
+
+  ASSERT_CRYPTO(acd, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+  EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), acd->protocol());
+  ASSERT_CRYPTO(vcd, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+  EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), vcd->protocol());
+  ASSERT_CRYPTO(dcd, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+  EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), dcd->protocol());
+}
+// Create a typical data offer, and ensure it matches what we expect.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataOffer) {
+  MediaSessionOptions opts;
+  opts.data_channel_type = cricket::DCT_RTP;
+  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* dc = offer->GetContentByName("data");
+  ASSERT_TRUE(ac != NULL);
+  ASSERT_TRUE(dc != NULL);
+  EXPECT_EQ(std::string(NS_JINGLE_RTP), ac->type);
+  EXPECT_EQ(std::string(NS_JINGLE_RTP), dc->type);
+  const AudioContentDescription* acd =
+      static_cast<const AudioContentDescription*>(ac->description);
+  const DataContentDescription* dcd =
+      static_cast<const DataContentDescription*>(dc->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, 2U, CS_AES_CM_128_HMAC_SHA1_32);
+  EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), acd->protocol());
+  EXPECT_EQ(MEDIA_TYPE_DATA, dcd->type());
+  EXPECT_EQ(f1_.data_codecs(), dcd->codecs());
+  EXPECT_NE(0U, dcd->first_ssrc());             // a random nonzero ssrc
+  EXPECT_EQ(cricket::kDataMaxBandwidth,
+            dcd->bandwidth());                  // default bandwidth (auto)
+  EXPECT_TRUE(dcd->rtcp_mux());                 // rtcp-mux defaults on
+  ASSERT_CRYPTO(dcd, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+  EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), dcd->protocol());
+}
+
+// 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, 1U, CS_AES_CM_128_HMAC_SHA1_32);
+  EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), acd->protocol());
+}
+
+// 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, 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, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+  EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), vcd->protocol());
+}
+
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswer) {
+  MediaSessionOptions opts;
+  opts.data_channel_type = cricket::DCT_RTP;
+  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("data");
+  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 DataContentDescription* vcd =
+      static_cast<const DataContentDescription*>(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, 1U, CS_AES_CM_128_HMAC_SHA1_32);
+  EXPECT_EQ(MEDIA_TYPE_DATA, vcd->type());
+  EXPECT_EQ(MAKE_VECTOR(kDataCodecsAnswer), vcd->codecs());
+  EXPECT_NE(0U, vcd->first_ssrc());             // a random nonzero ssrc
+  EXPECT_TRUE(vcd->rtcp_mux());                 // negotiated rtcp-mux
+  ASSERT_CRYPTO(vcd, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+  EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), vcd->protocol());
+}
+
+// This test that the media direction is set to send/receive in an answer if
+// the offer is send receive.
+TEST_F(MediaSessionDescriptionFactoryTest, CreateAnswerToSendReceiveOffer) {
+  TestMediaDirectionInAnswer(cricket::MD_SENDRECV, cricket::MD_SENDRECV);
+}
+
+// This test that the media direction is set to receive only in an answer if
+// the offer is send only.
+TEST_F(MediaSessionDescriptionFactoryTest, CreateAnswerToSendOnlyOffer) {
+  TestMediaDirectionInAnswer(cricket::MD_SENDONLY, cricket::MD_RECVONLY);
+}
+
+// This test that the media direction is set to send only in an answer if
+// the offer is recv only.
+TEST_F(MediaSessionDescriptionFactoryTest, CreateAnswerToRecvOnlyOffer) {
+  TestMediaDirectionInAnswer(cricket::MD_RECVONLY, cricket::MD_SENDONLY);
+}
+
+// This test that the media direction is set to inactive in an answer if
+// the offer is inactive.
+TEST_F(MediaSessionDescriptionFactoryTest, CreateAnswerToInactiveOffer) {
+  TestMediaDirectionInAnswer(cricket::MD_INACTIVE, cricket::MD_INACTIVE);
+}
+
+// Test that a data content with an unknown protocol is rejected in an answer.
+TEST_F(MediaSessionDescriptionFactoryTest,
+       CreateDataAnswerToOfferWithUnknownProtocol) {
+  MediaSessionOptions opts;
+  opts.data_channel_type = cricket::DCT_RTP;
+  opts.has_audio = false;
+  f1_.set_secure(SEC_ENABLED);
+  f2_.set_secure(SEC_ENABLED);
+  talk_base::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
+  ContentInfo* dc_offer= offer->GetContentByName("data");
+  ASSERT_TRUE(dc_offer != NULL);
+  DataContentDescription* dcd_offer =
+      static_cast<DataContentDescription*>(dc_offer->description);
+  ASSERT_TRUE(dcd_offer != NULL);
+  std::string protocol = "a weird unknown protocol";
+  dcd_offer->set_protocol(protocol);
+
+  talk_base::scoped_ptr<SessionDescription> answer(
+      f2_.CreateAnswer(offer.get(), opts, NULL));
+
+  const ContentInfo* dc_answer = answer->GetContentByName("data");
+  ASSERT_TRUE(dc_answer != NULL);
+  EXPECT_TRUE(dc_answer->rejected);
+  const DataContentDescription* dcd_answer =
+      static_cast<const DataContentDescription*>(dc_answer->description);
+  ASSERT_TRUE(dcd_answer != NULL);
+  EXPECT_EQ(protocol, dcd_answer->protocol());
+}
+
+// Test that the media protocol is RTP/AVPF if DTLS and SDES are disabled.
+TEST_F(MediaSessionDescriptionFactoryTest, AudioOfferAnswerWithCryptoDisabled) {
+  MediaSessionOptions opts;
+  f1_.set_secure(SEC_DISABLED);
+  f2_.set_secure(SEC_DISABLED);
+  tdf1_.set_secure(SEC_DISABLED);
+  tdf2_.set_secure(SEC_DISABLED);
+
+  talk_base::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
+  const AudioContentDescription* offer_acd =
+      GetFirstAudioContentDescription(offer.get());
+  ASSERT_TRUE(offer_acd != NULL);
+  EXPECT_EQ(std::string(cricket::kMediaProtocolAvpf), offer_acd->protocol());
+
+  talk_base::scoped_ptr<SessionDescription> answer(
+      f2_.CreateAnswer(offer.get(), opts, NULL));
+
+  const ContentInfo* ac_answer = answer->GetContentByName("audio");
+  ASSERT_TRUE(ac_answer != NULL);
+  EXPECT_FALSE(ac_answer->rejected);
+
+  const AudioContentDescription* answer_acd =
+      GetFirstAudioContentDescription(answer.get());
+  ASSERT_TRUE(answer_acd != NULL);
+  EXPECT_EQ(std::string(cricket::kMediaProtocolAvpf), answer_acd->protocol());
+}
+
+// Create a video offer and answer and ensure the RTP header extensions
+// matches what we expect.
+TEST_F(MediaSessionDescriptionFactoryTest, TestOfferAnswerWithRtpExtensions) {
+  MediaSessionOptions opts;
+  opts.has_video = true;
+
+  f1_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension1));
+  f1_.set_video_rtp_header_extensions(MAKE_VECTOR(kVideoRtpExtension1));
+  f2_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension2));
+  f2_.set_video_rtp_header_extensions(MAKE_VECTOR(kVideoRtpExtension2));
+
+  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));
+
+  EXPECT_EQ(MAKE_VECTOR(kAudioRtpExtension1),
+            GetFirstAudioContentDescription(
+                offer.get())->rtp_header_extensions());
+  EXPECT_EQ(MAKE_VECTOR(kVideoRtpExtension1),
+            GetFirstVideoContentDescription(
+                offer.get())->rtp_header_extensions());
+  EXPECT_EQ(MAKE_VECTOR(kAudioRtpExtensionAnswer),
+            GetFirstAudioContentDescription(
+                answer.get())->rtp_header_extensions());
+  EXPECT_EQ(MAKE_VECTOR(kVideoRtpExtensionAnswer),
+            GetFirstVideoContentDescription(
+                answer.get())->rtp_header_extensions());
+}
+
+// Create an audio, video, data answer without legacy StreamParams.
+TEST_F(MediaSessionDescriptionFactoryTest,
+       TestCreateAnswerWithoutLegacyStreams) {
+  MediaSessionOptions opts;
+  opts.has_video = true;
+  opts.data_channel_type = cricket::DCT_RTP;
+  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");
+  const ContentInfo* dc = answer->GetContentByName("data");
+  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);
+  const DataContentDescription* dcd =
+      static_cast<const DataContentDescription*>(dc->description);
+
+  EXPECT_FALSE(acd->has_ssrcs());  // No StreamParams.
+  EXPECT_FALSE(vcd->has_ssrcs());  // No StreamParams.
+  EXPECT_FALSE(dcd->has_ssrcs());  // No StreamParams.
+}
+
+TEST_F(MediaSessionDescriptionFactoryTest, TestPartial) {
+  MediaSessionOptions opts;
+  opts.has_video = true;
+  opts.data_channel_type = cricket::DCT_RTP;
+  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");
+  const ContentInfo* dc = offer->GetContentByName("data");
+  AudioContentDescription* acd = const_cast<AudioContentDescription*>(
+      static_cast<const AudioContentDescription*>(ac->description));
+  VideoContentDescription* vcd = const_cast<VideoContentDescription*>(
+      static_cast<const VideoContentDescription*>(vc->description));
+  DataContentDescription* dcd = const_cast<DataContentDescription*>(
+      static_cast<const DataContentDescription*>(dc->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());
+
+  EXPECT_FALSE(dcd->partial());  // default is false.
+  dcd->set_partial(true);
+  EXPECT_TRUE(dcd->partial());
+  dcd->set_partial(false);
+  EXPECT_FALSE(dcd->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;
+  answer_opts.data_channel_type = cricket::DCT_RTP;
+  offer_opts.data_channel_type = cricket::DCT_RTP;
+
+  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 != GetFirstDataContentDescription(offer.get()));
+  ASSERT_TRUE(NULL != GetFirstAudioContentDescription(answer.get()));
+  ASSERT_TRUE(NULL != GetFirstVideoContentDescription(answer.get()));
+  ASSERT_TRUE(NULL != GetFirstDataContentDescription(answer.get()));
+  EXPECT_TRUE(GetFirstAudioContentDescription(offer.get())->rtcp_mux());
+  EXPECT_TRUE(GetFirstVideoContentDescription(offer.get())->rtcp_mux());
+  EXPECT_TRUE(GetFirstDataContentDescription(offer.get())->rtcp_mux());
+  EXPECT_TRUE(GetFirstAudioContentDescription(answer.get())->rtcp_mux());
+  EXPECT_TRUE(GetFirstVideoContentDescription(answer.get())->rtcp_mux());
+  EXPECT_TRUE(GetFirstDataContentDescription(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 != GetFirstDataContentDescription(offer.get()));
+  ASSERT_TRUE(NULL != GetFirstAudioContentDescription(answer.get()));
+  ASSERT_TRUE(NULL != GetFirstVideoContentDescription(answer.get()));
+  ASSERT_TRUE(NULL != GetFirstDataContentDescription(answer.get()));
+  EXPECT_TRUE(GetFirstAudioContentDescription(offer.get())->rtcp_mux());
+  EXPECT_TRUE(GetFirstVideoContentDescription(offer.get())->rtcp_mux());
+  EXPECT_TRUE(GetFirstDataContentDescription(offer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstAudioContentDescription(answer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstVideoContentDescription(answer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstDataContentDescription(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 != GetFirstDataContentDescription(offer.get()));
+  ASSERT_TRUE(NULL != GetFirstAudioContentDescription(answer.get()));
+  ASSERT_TRUE(NULL != GetFirstVideoContentDescription(answer.get()));
+  ASSERT_TRUE(NULL != GetFirstDataContentDescription(answer.get()));
+  EXPECT_FALSE(GetFirstAudioContentDescription(offer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstVideoContentDescription(offer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstDataContentDescription(offer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstAudioContentDescription(answer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstVideoContentDescription(answer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstDataContentDescription(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 != GetFirstDataContentDescription(offer.get()));
+  ASSERT_TRUE(NULL != GetFirstAudioContentDescription(answer.get()));
+  ASSERT_TRUE(NULL != GetFirstVideoContentDescription(answer.get()));
+  ASSERT_TRUE(NULL != GetFirstDataContentDescription(answer.get()));
+  EXPECT_FALSE(GetFirstAudioContentDescription(offer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstVideoContentDescription(offer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstDataContentDescription(offer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstAudioContentDescription(answer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstVideoContentDescription(answer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstDataContentDescription(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);
+  ASSERT_TRUE(vc->description != NULL);
+  EXPECT_TRUE(vc->rejected);
+}
+
+// Create an audio-only answer to an offer with data.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateNoDataAnswerToDataOffer) {
+  MediaSessionOptions opts;
+  opts.data_channel_type = cricket::DCT_RTP;
+  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* dc = answer->GetContentByName("data");
+  ASSERT_TRUE(ac != NULL);
+  ASSERT_TRUE(dc != NULL);
+  ASSERT_TRUE(dc->description != NULL);
+  EXPECT_TRUE(dc->rejected);
+}
+
+// Create an answer that rejects the contents which are rejected in the offer.
+TEST_F(MediaSessionDescriptionFactoryTest,
+       CreateAnswerToOfferWithRejectedMedia) {
+  MediaSessionOptions opts;
+  opts.has_video = true;
+  opts.data_channel_type = cricket::DCT_RTP;
+  talk_base::scoped_ptr<SessionDescription>
+      offer(f1_.CreateOffer(opts, NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+  ContentInfo* ac = offer->GetContentByName("audio");
+  ContentInfo* vc = offer->GetContentByName("video");
+  ContentInfo* dc = offer->GetContentByName("data");
+  ASSERT_TRUE(ac != NULL);
+  ASSERT_TRUE(vc != NULL);
+  ASSERT_TRUE(dc != NULL);
+  ac->rejected = true;
+  vc->rejected = true;
+  dc->rejected = true;
+  talk_base::scoped_ptr<SessionDescription> answer(
+      f2_.CreateAnswer(offer.get(), opts, NULL));
+  ac = answer->GetContentByName("audio");
+  vc = answer->GetContentByName("video");
+  dc = answer->GetContentByName("data");
+  ASSERT_TRUE(ac != NULL);
+  ASSERT_TRUE(vc != NULL);
+  ASSERT_TRUE(dc != NULL);
+  EXPECT_TRUE(ac->rejected);
+  EXPECT_TRUE(vc->rejected);
+  EXPECT_TRUE(dc->rejected);
+}
+
+// Create an audio and video offer with:
+// - one video track
+// - two audio tracks
+// - two data 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);
+  opts.data_channel_type = cricket::DCT_RTP;
+  opts.AddStream(MEDIA_TYPE_DATA, kDataTrack1, kMediaStream1);
+  opts.AddStream(MEDIA_TYPE_DATA, kDataTrack2, 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");
+  const ContentInfo* dc = offer->GetContentByName("data");
+  ASSERT_TRUE(ac != NULL);
+  ASSERT_TRUE(vc != NULL);
+  ASSERT_TRUE(dc != NULL);
+  const AudioContentDescription* acd =
+      static_cast<const AudioContentDescription*>(ac->description);
+  const VideoContentDescription* vcd =
+      static_cast<const VideoContentDescription*>(vc->description);
+  const DataContentDescription* dcd =
+      static_cast<const DataContentDescription*>(dc->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].id);
+  ASSERT_EQ(1U, audio_streams[0].ssrcs.size());
+  EXPECT_NE(0U, audio_streams[0].ssrcs[0]);
+  EXPECT_EQ(kAudioTrack2, audio_streams[1].id);
+  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, 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, 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].id);
+  EXPECT_EQ(kAutoBandwidth, vcd->bandwidth());  // default bandwidth (auto)
+  EXPECT_TRUE(vcd->rtcp_mux());                 // rtcp-mux defaults on
+
+  EXPECT_EQ(MEDIA_TYPE_DATA, dcd->type());
+  EXPECT_EQ(f1_.data_codecs(), dcd->codecs());
+  ASSERT_CRYPTO(dcd, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+
+  const StreamParamsVec& data_streams = dcd->streams();
+  ASSERT_EQ(2U, data_streams.size());
+  EXPECT_EQ(data_streams[0].cname , data_streams[1].cname);
+  EXPECT_EQ(kDataTrack1, data_streams[0].id);
+  ASSERT_EQ(1U, data_streams[0].ssrcs.size());
+  EXPECT_NE(0U, data_streams[0].ssrcs[0]);
+  EXPECT_EQ(kDataTrack2, data_streams[1].id);
+  ASSERT_EQ(1U, data_streams[1].ssrcs.size());
+  EXPECT_NE(0U, data_streams[1].ssrcs[0]);
+
+  EXPECT_EQ(cricket::kDataMaxBandwidth,
+            dcd->bandwidth());                  // default bandwidth (auto)
+  EXPECT_TRUE(dcd->rtcp_mux());                 // rtcp-mux defaults on
+  ASSERT_CRYPTO(dcd, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+
+
+  // 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);
+  opts.RemoveStream(MEDIA_TYPE_DATA, kDataTrack2);
+  opts.AddStream(MEDIA_TYPE_DATA, kDataTrack3, 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");
+  dc = updated_offer->GetContentByName("data");
+  ASSERT_TRUE(ac != NULL);
+  ASSERT_TRUE(vc != NULL);
+  ASSERT_TRUE(dc != NULL);
+  const AudioContentDescription* updated_acd =
+      static_cast<const AudioContentDescription*>(ac->description);
+  const VideoContentDescription* updated_vcd =
+      static_cast<const VideoContentDescription*>(vc->description);
+  const DataContentDescription* updated_dcd =
+      static_cast<const DataContentDescription*>(dc->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());
+  EXPECT_EQ(dcd->type(), updated_dcd->type());
+  EXPECT_EQ(dcd->codecs(), updated_dcd->codecs());
+  ASSERT_CRYPTO(updated_acd, 2U, CS_AES_CM_128_HMAC_SHA1_32);
+  EXPECT_TRUE(CompareCryptoParams(acd->cryptos(), updated_acd->cryptos()));
+  ASSERT_CRYPTO(updated_vcd, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+  EXPECT_TRUE(CompareCryptoParams(vcd->cryptos(), updated_vcd->cryptos()));
+  ASSERT_CRYPTO(updated_dcd, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+  EXPECT_TRUE(CompareCryptoParams(dcd->cryptos(), updated_dcd->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].id);  // 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].id);
+  EXPECT_NE(updated_video_streams[1].cname, updated_video_streams[0].cname);
+
+  const StreamParamsVec& updated_data_streams = updated_dcd->streams();
+  ASSERT_EQ(2U, updated_data_streams.size());
+  EXPECT_EQ(data_streams[0], updated_data_streams[0]);
+  EXPECT_EQ(kDataTrack3, updated_data_streams[1].id);  // New data track.
+  ASSERT_EQ(1U, updated_data_streams[1].ssrcs.size());
+  EXPECT_NE(0U, updated_data_streams[1].ssrcs[0]);
+  EXPECT_EQ(updated_data_streams[0].cname, updated_data_streams[1].cname);
+}
+
+// Create an audio and video answer to a standard video offer with:
+// - one video track
+// - two audio tracks
+// - two data 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;
+  offer_opts.data_channel_type = cricket::DCT_RTP;
+  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);
+  opts.data_channel_type = cricket::DCT_RTP;
+  opts.AddStream(MEDIA_TYPE_DATA, kDataTrack1, kMediaStream1);
+  opts.AddStream(MEDIA_TYPE_DATA, kDataTrack2, 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");
+  const ContentInfo* dc = answer->GetContentByName("data");
+  ASSERT_TRUE(ac != NULL);
+  ASSERT_TRUE(vc != NULL);
+  ASSERT_TRUE(dc != NULL);
+  const AudioContentDescription* acd =
+      static_cast<const AudioContentDescription*>(ac->description);
+  const VideoContentDescription* vcd =
+      static_cast<const VideoContentDescription*>(vc->description);
+  const DataContentDescription* dcd =
+      static_cast<const DataContentDescription*>(dc->description);
+  ASSERT_CRYPTO(acd, 1U, CS_AES_CM_128_HMAC_SHA1_32);
+  ASSERT_CRYPTO(vcd, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+  ASSERT_CRYPTO(dcd, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+
+  EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
+  EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs());
+
+  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].id);
+  ASSERT_EQ(1U, audio_streams[0].ssrcs.size());
+  EXPECT_NE(0U, audio_streams[0].ssrcs[0]);
+  EXPECT_EQ(kAudioTrack2, audio_streams[1].id);
+  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].id);
+  EXPECT_EQ(kAutoBandwidth, vcd->bandwidth());  // default bandwidth (auto)
+  EXPECT_TRUE(vcd->rtcp_mux());                 // rtcp-mux defaults on
+
+  EXPECT_EQ(MEDIA_TYPE_DATA, dcd->type());
+  EXPECT_EQ(MAKE_VECTOR(kDataCodecsAnswer), dcd->codecs());
+
+  const StreamParamsVec& data_streams = dcd->streams();
+  ASSERT_EQ(2U, data_streams.size());
+  EXPECT_TRUE(data_streams[0].cname ==  data_streams[1].cname);
+  EXPECT_EQ(kDataTrack1, data_streams[0].id);
+  ASSERT_EQ(1U, data_streams[0].ssrcs.size());
+  EXPECT_NE(0U, data_streams[0].ssrcs[0]);
+  EXPECT_EQ(kDataTrack2, data_streams[1].id);
+  ASSERT_EQ(1U, data_streams[1].ssrcs.size());
+  EXPECT_NE(0U, data_streams[1].ssrcs[0]);
+
+  EXPECT_EQ(cricket::kDataMaxBandwidth,
+            dcd->bandwidth());                  // default bandwidth (auto)
+  EXPECT_TRUE(dcd->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);
+  opts.RemoveStream(MEDIA_TYPE_DATA, kDataTrack2);
+  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");
+  dc = updated_answer->GetContentByName("data");
+  ASSERT_TRUE(ac != NULL);
+  ASSERT_TRUE(vc != NULL);
+  ASSERT_TRUE(dc != NULL);
+  const AudioContentDescription* updated_acd =
+      static_cast<const AudioContentDescription*>(ac->description);
+  const VideoContentDescription* updated_vcd =
+      static_cast<const VideoContentDescription*>(vc->description);
+  const DataContentDescription* updated_dcd =
+      static_cast<const DataContentDescription*>(dc->description);
+
+  ASSERT_CRYPTO(updated_acd, 1U, CS_AES_CM_128_HMAC_SHA1_32);
+  EXPECT_TRUE(CompareCryptoParams(acd->cryptos(), updated_acd->cryptos()));
+  ASSERT_CRYPTO(updated_vcd, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+  EXPECT_TRUE(CompareCryptoParams(vcd->cryptos(), updated_vcd->cryptos()));
+  ASSERT_CRYPTO(updated_dcd, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+  EXPECT_TRUE(CompareCryptoParams(dcd->cryptos(), updated_dcd->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());
+  EXPECT_EQ(dcd->type(), updated_dcd->type());
+  EXPECT_EQ(dcd->codecs(), updated_dcd->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].id);
+  EXPECT_NE(updated_video_streams[1].cname, updated_video_streams[0].cname);
+
+  const StreamParamsVec& updated_data_streams = updated_dcd->streams();
+  ASSERT_EQ(1U, updated_data_streams.size());
+  EXPECT_TRUE(data_streams[0] == updated_data_streams[0]);
+}
+
+
+// Create an updated offer after creating an answer to the original offer and
+// verify that the codecs that were part of the original answer are not changed
+// in the updated offer.
+TEST_F(MediaSessionDescriptionFactoryTest,
+       RespondentCreatesOfferAfterCreatingAnswer) {
+  MediaSessionOptions opts;
+  opts.has_audio = true;
+  opts.has_video = true;
+
+  talk_base::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
+  talk_base::scoped_ptr<SessionDescription> answer(
+      f2_.CreateAnswer(offer.get(), opts, NULL));
+
+  const AudioContentDescription* acd =
+      GetFirstAudioContentDescription(answer.get());
+  EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs());
+
+  const VideoContentDescription* vcd =
+      GetFirstVideoContentDescription(answer.get());
+  EXPECT_EQ(MAKE_VECTOR(kVideoCodecsAnswer), vcd->codecs());
+
+  talk_base::scoped_ptr<SessionDescription> updated_offer(
+      f2_.CreateOffer(opts, answer.get()));
+
+  // The expected audio codecs are the common audio codecs from the first
+  // offer/answer exchange plus the audio codecs only |f2_| offer, sorted in
+  // preference order.
+  const AudioCodec kUpdatedAudioCodecOffer[] = {
+    kAudioCodecs2[0],
+    kAudioCodecsAnswer[0],
+    kAudioCodecsAnswer[1],
+  };
+
+  // The expected video codecs are the common video codecs from the first
+  // offer/answer exchange plus the video codecs only |f2_| offer, sorted in
+  // preference order.
+  const VideoCodec kUpdatedVideoCodecOffer[] = {
+    kVideoCodecsAnswer[0],
+    kVideoCodecs2[1],
+  };
+
+  const AudioContentDescription* updated_acd =
+      GetFirstAudioContentDescription(updated_offer.get());
+  EXPECT_EQ(MAKE_VECTOR(kUpdatedAudioCodecOffer), updated_acd->codecs());
+
+  const VideoContentDescription* updated_vcd =
+      GetFirstVideoContentDescription(updated_offer.get());
+  EXPECT_EQ(MAKE_VECTOR(kUpdatedVideoCodecOffer), updated_vcd->codecs());
+}
+
+// Create an updated offer after creating an answer to the original offer and
+// verify that the codecs that were part of the original answer are not changed
+// in the updated offer. In this test Rtx is enabled.
+TEST_F(MediaSessionDescriptionFactoryTest,
+       RespondentCreatesOfferAfterCreatingAnswerWithRtx) {
+  MediaSessionOptions opts;
+  opts.has_video = true;
+  opts.has_audio = false;
+  std::vector<VideoCodec> f1_codecs = MAKE_VECTOR(kVideoCodecs1);
+  VideoCodec rtx_f1;
+  rtx_f1.id = 126;
+  rtx_f1.name = cricket::kRtxCodecName;
+
+  // This creates rtx for H264 with the payload type |f1_| uses.
+  rtx_f1.params[cricket::kCodecParamAssociatedPayloadType] =
+      talk_base::ToString<int>(kVideoCodecs1[1].id);
+  f1_codecs.push_back(rtx_f1);
+  f1_.set_video_codecs(f1_codecs);
+
+  std::vector<VideoCodec> f2_codecs = MAKE_VECTOR(kVideoCodecs2);
+  VideoCodec rtx_f2;
+  rtx_f2.id = 127;
+  rtx_f2.name = cricket::kRtxCodecName;
+
+  // This creates rtx for H264 with the payload type |f2_| uses.
+  rtx_f2.params[cricket::kCodecParamAssociatedPayloadType] =
+      talk_base::ToString<int>(kVideoCodecs2[0].id);
+  f2_codecs.push_back(rtx_f2);
+  f2_.set_video_codecs(f2_codecs);
+
+  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 VideoContentDescription* vcd =
+      GetFirstVideoContentDescription(answer.get());
+
+  std::vector<VideoCodec> expected_codecs = MAKE_VECTOR(kVideoCodecsAnswer);
+  expected_codecs.push_back(rtx_f1);
+
+  EXPECT_EQ(expected_codecs, vcd->codecs());
+
+  // Now, make sure we get same result, except for the preference order,
+  // if |f2_| creates an updated offer even though the default payload types
+  // are different from |f1_|.
+  expected_codecs[0].preference = f1_codecs[1].preference;
+
+  talk_base::scoped_ptr<SessionDescription> updated_offer(
+      f2_.CreateOffer(opts, answer.get()));
+  ASSERT_TRUE(updated_offer);
+  talk_base::scoped_ptr<SessionDescription> updated_answer(
+      f1_.CreateAnswer(updated_offer.get(), opts, answer.get()));
+
+  const VideoContentDescription* updated_vcd =
+      GetFirstVideoContentDescription(updated_answer.get());
+
+  EXPECT_EQ(expected_codecs, updated_vcd->codecs());
+}
+
+// Create an updated offer that adds video after creating an audio only answer
+// to the original offer. This test verifies that if a video codec and the RTX
+// codec have the same default payload type as an audio codec that is already in
+// use, the added codecs payload types are changed.
+TEST_F(MediaSessionDescriptionFactoryTest,
+       RespondentCreatesOfferWithVideoAndRtxAfterCreatingAudioAnswer) {
+  std::vector<VideoCodec> f1_codecs = MAKE_VECTOR(kVideoCodecs1);
+  VideoCodec rtx_f1;
+  rtx_f1.id = 126;
+  rtx_f1.name = cricket::kRtxCodecName;
+
+  // This creates rtx for H264 with the payload type |f1_| uses.
+  rtx_f1.params[cricket::kCodecParamAssociatedPayloadType] =
+      talk_base::ToString<int>(kVideoCodecs1[1].id);
+  f1_codecs.push_back(rtx_f1);
+  f1_.set_video_codecs(f1_codecs);
+
+  MediaSessionOptions opts;
+  opts.has_audio = true;
+  opts.has_video = false;
+
+  talk_base::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
+  talk_base::scoped_ptr<SessionDescription> answer(
+      f2_.CreateAnswer(offer.get(), opts, NULL));
+
+  const AudioContentDescription* acd =
+      GetFirstAudioContentDescription(answer.get());
+  EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs());
+
+  // Now - let |f2_| add video with RTX and let the payload type the RTX codec
+  // reference  be the same as an audio codec that was negotiated in the
+  // first offer/answer exchange.
+  opts.has_audio = true;
+  opts.has_video = true;
+
+  std::vector<VideoCodec> f2_codecs = MAKE_VECTOR(kVideoCodecs2);
+  int used_pl_type = acd->codecs()[0].id;
+  f2_codecs[0].id = used_pl_type;  // Set the payload type for H264.
+  VideoCodec rtx_f2;
+  rtx_f2.id = 127;
+  rtx_f2.name = cricket::kRtxCodecName;
+  rtx_f2.params[cricket::kCodecParamAssociatedPayloadType] =
+      talk_base::ToString<int>(used_pl_type);
+  f2_codecs.push_back(rtx_f2);
+  f2_.set_video_codecs(f2_codecs);
+
+  talk_base::scoped_ptr<SessionDescription> updated_offer(
+      f2_.CreateOffer(opts, answer.get()));
+  ASSERT_TRUE(updated_offer);
+  talk_base::scoped_ptr<SessionDescription> updated_answer(
+      f1_.CreateAnswer(updated_offer.get(), opts, answer.get()));
+
+  const AudioContentDescription* updated_acd =
+      GetFirstAudioContentDescription(answer.get());
+  EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), updated_acd->codecs());
+
+  const VideoContentDescription* updated_vcd =
+      GetFirstVideoContentDescription(updated_answer.get());
+
+  ASSERT_EQ("H264", updated_vcd->codecs()[0].name);
+  ASSERT_EQ(cricket::kRtxCodecName, updated_vcd->codecs()[1].name);
+  int new_h264_pl_type =  updated_vcd->codecs()[0].id;
+  EXPECT_NE(used_pl_type, new_h264_pl_type);
+  VideoCodec rtx = updated_vcd->codecs()[1];
+  int pt_referenced_by_rtx = talk_base::FromString<int>(
+      rtx.params[cricket::kCodecParamAssociatedPayloadType]);
+  EXPECT_EQ(new_h264_pl_type, pt_referenced_by_rtx);
+}
+
+// Test that RTX is ignored when there is no associated payload type parameter.
+TEST_F(MediaSessionDescriptionFactoryTest, RtxWithoutApt) {
+  MediaSessionOptions opts;
+  opts.has_video = true;
+  opts.has_audio = false;
+  std::vector<VideoCodec> f1_codecs = MAKE_VECTOR(kVideoCodecs1);
+  VideoCodec rtx_f1;
+  rtx_f1.id = 126;
+  rtx_f1.name = cricket::kRtxCodecName;
+
+  f1_codecs.push_back(rtx_f1);
+  f1_.set_video_codecs(f1_codecs);
+
+  std::vector<VideoCodec> f2_codecs = MAKE_VECTOR(kVideoCodecs2);
+  VideoCodec rtx_f2;
+  rtx_f2.id = 127;
+  rtx_f2.name = cricket::kRtxCodecName;
+
+  // This creates rtx for H264 with the payload type |f2_| uses.
+  rtx_f2.SetParam(cricket::kCodecParamAssociatedPayloadType,
+                  talk_base::ToString<int>(kVideoCodecs2[0].id));
+  f2_codecs.push_back(rtx_f2);
+  f2_.set_video_codecs(f2_codecs);
+
+  talk_base::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+  // kCodecParamAssociatedPayloadType will always be added to the offer when RTX
+  // is selected. Manually remove kCodecParamAssociatedPayloadType so that it
+  // is possible to test that that RTX is dropped when
+  // kCodecParamAssociatedPayloadType is missing in the offer.
+  VideoContentDescription* desc =
+      static_cast<cricket::VideoContentDescription*>(
+          offer->GetContentDescriptionByName(cricket::CN_VIDEO));
+  ASSERT_TRUE(desc != NULL);
+  std::vector<VideoCodec> codecs = desc->codecs();
+  for (std::vector<VideoCodec>::iterator iter = codecs.begin();
+       iter != codecs.end(); ++iter) {
+    if (iter->name.find(cricket::kRtxCodecName) == 0) {
+      iter->params.clear();
+    }
+  }
+  desc->set_codecs(codecs);
+
+  talk_base::scoped_ptr<SessionDescription> answer(
+      f2_.CreateAnswer(offer.get(), opts, NULL));
+
+  const VideoContentDescription* vcd =
+      GetFirstVideoContentDescription(answer.get());
+
+  for (std::vector<VideoCodec>::const_iterator iter = vcd->codecs().begin();
+       iter != vcd->codecs().end(); ++iter) {
+    ASSERT_STRNE(iter->name.c_str(), cricket::kRtxCodecName);
+  }
+}
+
+// Create an updated offer after creating an answer to the original offer and
+// verify that the RTP header extensions that were part of the original answer
+// are not changed in the updated offer.
+TEST_F(MediaSessionDescriptionFactoryTest,
+       RespondentCreatesOfferAfterCreatingAnswerWithRtpExtensions) {
+  MediaSessionOptions opts;
+  opts.has_audio = true;
+  opts.has_video = true;
+
+  f1_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension1));
+  f1_.set_video_rtp_header_extensions(MAKE_VECTOR(kVideoRtpExtension1));
+  f2_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension2));
+  f2_.set_video_rtp_header_extensions(MAKE_VECTOR(kVideoRtpExtension2));
+
+  talk_base::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
+  talk_base::scoped_ptr<SessionDescription> answer(
+      f2_.CreateAnswer(offer.get(), opts, NULL));
+
+  EXPECT_EQ(MAKE_VECTOR(kAudioRtpExtensionAnswer),
+            GetFirstAudioContentDescription(
+                answer.get())->rtp_header_extensions());
+  EXPECT_EQ(MAKE_VECTOR(kVideoRtpExtensionAnswer),
+            GetFirstVideoContentDescription(
+                answer.get())->rtp_header_extensions());
+
+  talk_base::scoped_ptr<SessionDescription> updated_offer(
+      f2_.CreateOffer(opts, answer.get()));
+
+  // The expected RTP header extensions in the new offer are the resulting
+  // extensions from the first offer/answer exchange plus the extensions only
+  // |f2_| offer.
+  // Since the default local extension id |f2_| uses has already been used by
+  // |f1_| for another extensions, it is changed to 255.
+  const RtpHeaderExtension kUpdatedAudioRtpExtensions[] = {
+    kAudioRtpExtensionAnswer[0],
+    RtpHeaderExtension(kAudioRtpExtension2[1].uri, 255),
+  };
+
+  // Since the default local extension id |f2_| uses has already been used by
+  // |f1_| for another extensions, is is changed to 254.
+  const RtpHeaderExtension kUpdatedVideoRtpExtensions[] = {
+    kVideoRtpExtensionAnswer[0],
+    RtpHeaderExtension(kVideoRtpExtension2[1].uri, 254),
+  };
+
+  const AudioContentDescription* updated_acd =
+      GetFirstAudioContentDescription(updated_offer.get());
+  EXPECT_EQ(MAKE_VECTOR(kUpdatedAudioRtpExtensions),
+            updated_acd->rtp_header_extensions());
+
+  const VideoContentDescription* updated_vcd =
+      GetFirstVideoContentDescription(updated_offer.get());
+  EXPECT_EQ(MAKE_VECTOR(kUpdatedVideoRtpExtensions),
+            updated_vcd->rtp_header_extensions());
+}
+
+TEST(MediaSessionDescription, CopySessionDescription) {
+  SessionDescription source;
+  cricket::ContentGroup group(cricket::CN_AUDIO);
+  source.AddGroup(group);
+  AudioContentDescription* acd(new AudioContentDescription());
+  acd->set_codecs(MAKE_VECTOR(kAudioCodecs1));
+  acd->AddLegacyStream(1);
+  source.AddContent(cricket::CN_AUDIO, cricket::NS_JINGLE_RTP, acd);
+  VideoContentDescription* vcd(new VideoContentDescription());
+  vcd->set_codecs(MAKE_VECTOR(kVideoCodecs1));
+  vcd->AddLegacyStream(2);
+  source.AddContent(cricket::CN_VIDEO, cricket::NS_JINGLE_RTP, vcd);
+
+  talk_base::scoped_ptr<SessionDescription> copy(source.Copy());
+  ASSERT_TRUE(copy.get() != NULL);
+  EXPECT_TRUE(copy->HasGroup(cricket::CN_AUDIO));
+  const ContentInfo* ac = copy->GetContentByName("audio");
+  const ContentInfo* vc = copy->GetContentByName("video");
+  ASSERT_TRUE(ac != NULL);
+  ASSERT_TRUE(vc != NULL);
+  EXPECT_EQ(std::string(NS_JINGLE_RTP), ac->type);
+  const AudioContentDescription* acd_copy =
+      static_cast<const AudioContentDescription*>(ac->description);
+  EXPECT_EQ(acd->codecs(), acd_copy->codecs());
+  EXPECT_EQ(1u, acd->first_ssrc());
+
+  EXPECT_EQ(std::string(NS_JINGLE_RTP), vc->type);
+  const VideoContentDescription* vcd_copy =
+      static_cast<const VideoContentDescription*>(vc->description);
+  EXPECT_EQ(vcd->codecs(), vcd_copy->codecs());
+  EXPECT_EQ(2u, vcd->first_ssrc());
+}
+
+// The below TestTransportInfoXXX tests create different offers/answers, and
+// ensure the TransportInfo in the SessionDescription matches what we expect.
+TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferAudio) {
+  MediaSessionOptions options;
+  options.has_audio = true;
+  TestTransportInfo(true, options, false);
+}
+
+TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferAudioCurrent) {
+  MediaSessionOptions options;
+  options.has_audio = true;
+  TestTransportInfo(true, options, true);
+}
+
+TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferMultimedia) {
+  MediaSessionOptions options;
+  options.has_audio = true;
+  options.has_video = true;
+  options.data_channel_type = cricket::DCT_RTP;
+  TestTransportInfo(true, options, false);
+}
+
+TEST_F(MediaSessionDescriptionFactoryTest,
+    TestTransportInfoOfferMultimediaCurrent) {
+  MediaSessionOptions options;
+  options.has_audio = true;
+  options.has_video = true;
+  options.data_channel_type = cricket::DCT_RTP;
+  TestTransportInfo(true, options, true);
+}
+
+TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferBundle) {
+  MediaSessionOptions options;
+  options.has_audio = true;
+  options.has_video = true;
+  options.data_channel_type = cricket::DCT_RTP;
+  options.bundle_enabled = true;
+  TestTransportInfo(true, options, false);
+}
+
+TEST_F(MediaSessionDescriptionFactoryTest,
+       TestTransportInfoOfferBundleCurrent) {
+  MediaSessionOptions options;
+  options.has_audio = true;
+  options.has_video = true;
+  options.data_channel_type = cricket::DCT_RTP;
+  options.bundle_enabled = true;
+  TestTransportInfo(true, options, true);
+}
+
+TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoAnswerAudio) {
+  MediaSessionOptions options;
+  options.has_audio = true;
+  TestTransportInfo(false, options, false);
+}
+
+TEST_F(MediaSessionDescriptionFactoryTest,
+    TestTransportInfoAnswerAudioCurrent) {
+  MediaSessionOptions options;
+  options.has_audio = true;
+  TestTransportInfo(false, options, true);
+}
+
+TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoAnswerMultimedia) {
+  MediaSessionOptions options;
+  options.has_audio = true;
+  options.has_video = true;
+  options.data_channel_type = cricket::DCT_RTP;
+  TestTransportInfo(false, options, false);
+}
+
+TEST_F(MediaSessionDescriptionFactoryTest,
+    TestTransportInfoAnswerMultimediaCurrent) {
+  MediaSessionOptions options;
+  options.has_audio = true;
+  options.has_video = true;
+  options.data_channel_type = cricket::DCT_RTP;
+  TestTransportInfo(false, options, true);
+}
+
+TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoAnswerBundle) {
+  MediaSessionOptions options;
+  options.has_audio = true;
+  options.has_video = true;
+  options.data_channel_type = cricket::DCT_RTP;
+  options.bundle_enabled = true;
+  TestTransportInfo(false, options, false);
+}
+
+TEST_F(MediaSessionDescriptionFactoryTest,
+    TestTransportInfoAnswerBundleCurrent) {
+  MediaSessionOptions options;
+  options.has_audio = true;
+  options.has_video = true;
+  options.data_channel_type = cricket::DCT_RTP;
+  options.bundle_enabled = true;
+  TestTransportInfo(false, options, true);
+}
+
+// Create an offer with bundle enabled and verify the crypto parameters are
+// the common set of the available cryptos.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCryptoWithOfferBundle) {
+  TestCryptoWithBundle(true);
+}
+
+// Create an answer with bundle enabled and verify the crypto parameters are
+// the common set of the available cryptos.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCryptoWithAnswerBundle) {
+  TestCryptoWithBundle(false);
+}
+
+// Test that we include both SDES and DTLS in the offer, but only include SDES
+// in the answer if DTLS isn't negotiated.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCryptoDtls) {
+  f1_.set_secure(SEC_ENABLED);
+  f2_.set_secure(SEC_ENABLED);
+  tdf1_.set_secure(SEC_ENABLED);
+  tdf2_.set_secure(SEC_DISABLED);
+  MediaSessionOptions options;
+  options.has_audio = true;
+  options.has_video = true;
+  talk_base::scoped_ptr<SessionDescription> offer, answer;
+  const cricket::MediaContentDescription* audio_media_desc;
+  const cricket::MediaContentDescription* video_media_desc;
+  const cricket::TransportDescription* audio_trans_desc;
+  const cricket::TransportDescription* video_trans_desc;
+
+  // Generate an offer with SDES and DTLS support.
+  offer.reset(f1_.CreateOffer(options, NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+
+  audio_media_desc = static_cast<const cricket::MediaContentDescription*>(
+      offer->GetContentDescriptionByName("audio"));
+  ASSERT_TRUE(audio_media_desc != NULL);
+  video_media_desc = static_cast<const cricket::MediaContentDescription*> (
+      offer->GetContentDescriptionByName("video"));
+  ASSERT_TRUE(video_media_desc != NULL);
+  EXPECT_EQ(2u, audio_media_desc->cryptos().size());
+  EXPECT_EQ(1u, video_media_desc->cryptos().size());
+
+  audio_trans_desc = offer->GetTransportDescriptionByName("audio");
+  ASSERT_TRUE(audio_trans_desc != NULL);
+  video_trans_desc = offer->GetTransportDescriptionByName("video");
+  ASSERT_TRUE(video_trans_desc != NULL);
+  ASSERT_TRUE(audio_trans_desc->identity_fingerprint.get() != NULL);
+  ASSERT_TRUE(video_trans_desc->identity_fingerprint.get() != NULL);
+
+  // Generate an answer with only SDES support, since tdf2 has crypto disabled.
+  answer.reset(f2_.CreateAnswer(offer.get(), options, NULL));
+  ASSERT_TRUE(answer.get() != NULL);
+
+  audio_media_desc = static_cast<const cricket::MediaContentDescription*> (
+      answer->GetContentDescriptionByName("audio"));
+  ASSERT_TRUE(audio_media_desc != NULL);
+  video_media_desc = static_cast<const cricket::MediaContentDescription*> (
+      answer->GetContentDescriptionByName("video"));
+  ASSERT_TRUE(video_media_desc != NULL);
+  EXPECT_EQ(1u, audio_media_desc->cryptos().size());
+  EXPECT_EQ(1u, video_media_desc->cryptos().size());
+
+  audio_trans_desc = answer->GetTransportDescriptionByName("audio");
+  ASSERT_TRUE(audio_trans_desc != NULL);
+  video_trans_desc = answer->GetTransportDescriptionByName("video");
+  ASSERT_TRUE(video_trans_desc != NULL);
+  ASSERT_TRUE(audio_trans_desc->identity_fingerprint.get() == NULL);
+  ASSERT_TRUE(video_trans_desc->identity_fingerprint.get() == NULL);
+
+  // Enable DTLS; the answer should now only have DTLS support.
+  tdf2_.set_secure(SEC_ENABLED);
+  answer.reset(f2_.CreateAnswer(offer.get(), options, NULL));
+  ASSERT_TRUE(answer.get() != NULL);
+
+  audio_media_desc = static_cast<const cricket::MediaContentDescription*> (
+      answer->GetContentDescriptionByName("audio"));
+  ASSERT_TRUE(audio_media_desc != NULL);
+  video_media_desc = static_cast<const cricket::MediaContentDescription*> (
+      answer->GetContentDescriptionByName("video"));
+  ASSERT_TRUE(video_media_desc != NULL);
+  EXPECT_TRUE(audio_media_desc->cryptos().empty());
+  EXPECT_TRUE(video_media_desc->cryptos().empty());
+  EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf),
+            audio_media_desc->protocol());
+  EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf),
+            video_media_desc->protocol());
+
+  audio_trans_desc = answer->GetTransportDescriptionByName("audio");
+  ASSERT_TRUE(audio_trans_desc != NULL);
+  video_trans_desc = answer->GetTransportDescriptionByName("video");
+  ASSERT_TRUE(video_trans_desc != NULL);
+  ASSERT_TRUE(audio_trans_desc->identity_fingerprint.get() != NULL);
+  ASSERT_TRUE(video_trans_desc->identity_fingerprint.get() != NULL);
+}
+
+// Test that an answer can't be created if cryptos are required but the offer is
+// unsecure.
+TEST_F(MediaSessionDescriptionFactoryTest, TestSecureAnswerToUnsecureOffer) {
+  MediaSessionOptions options;
+  f1_.set_secure(SEC_DISABLED);
+  tdf1_.set_secure(SEC_DISABLED);
+  f2_.set_secure(SEC_REQUIRED);
+  tdf1_.set_secure(SEC_ENABLED);
+
+  talk_base::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(options,
+                                                                  NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+  talk_base::scoped_ptr<SessionDescription> answer(
+      f2_.CreateAnswer(offer.get(), options, NULL));
+  EXPECT_TRUE(answer.get() == NULL);
+}
+
+// Test that we accept a DTLS offer without SDES and create an appropriate
+// answer.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCryptoOfferDtlsButNotSdes) {
+  f1_.set_secure(SEC_DISABLED);
+  f2_.set_secure(SEC_ENABLED);
+  tdf1_.set_secure(SEC_ENABLED);
+  tdf2_.set_secure(SEC_ENABLED);
+  MediaSessionOptions options;
+  options.has_audio = true;
+  options.has_video = true;
+  options.data_channel_type = cricket::DCT_RTP;
+
+  talk_base::scoped_ptr<SessionDescription> offer, answer;
+
+  // Generate an offer with DTLS but without SDES.
+  offer.reset(f1_.CreateOffer(options, NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+
+  const AudioContentDescription* audio_offer =
+      GetFirstAudioContentDescription(offer.get());
+  ASSERT_TRUE(audio_offer->cryptos().empty());
+  const VideoContentDescription* video_offer =
+      GetFirstVideoContentDescription(offer.get());
+  ASSERT_TRUE(video_offer->cryptos().empty());
+  const DataContentDescription* data_offer =
+      GetFirstDataContentDescription(offer.get());
+  ASSERT_TRUE(data_offer->cryptos().empty());
+
+  const cricket::TransportDescription* audio_offer_trans_desc =
+      offer->GetTransportDescriptionByName("audio");
+  ASSERT_TRUE(audio_offer_trans_desc->identity_fingerprint.get() != NULL);
+  const cricket::TransportDescription* video_offer_trans_desc =
+      offer->GetTransportDescriptionByName("video");
+  ASSERT_TRUE(video_offer_trans_desc->identity_fingerprint.get() != NULL);
+  const cricket::TransportDescription* data_offer_trans_desc =
+      offer->GetTransportDescriptionByName("data");
+  ASSERT_TRUE(data_offer_trans_desc->identity_fingerprint.get() != NULL);
+
+  // Generate an answer with DTLS.
+  answer.reset(f2_.CreateAnswer(offer.get(), options, NULL));
+  ASSERT_TRUE(answer.get() != NULL);
+
+  const cricket::TransportDescription* audio_answer_trans_desc =
+      answer->GetTransportDescriptionByName("audio");
+  EXPECT_TRUE(audio_answer_trans_desc->identity_fingerprint.get() != NULL);
+  const cricket::TransportDescription* video_answer_trans_desc =
+      answer->GetTransportDescriptionByName("video");
+  EXPECT_TRUE(video_answer_trans_desc->identity_fingerprint.get() != NULL);
+  const cricket::TransportDescription* data_answer_trans_desc =
+      answer->GetTransportDescriptionByName("data");
+  EXPECT_TRUE(data_answer_trans_desc->identity_fingerprint.get() != NULL);
+}
+
+// Verifies if vad_enabled option is set to false, CN codecs are not present in
+// offer or answer.
+TEST_F(MediaSessionDescriptionFactoryTest, TestVADEnableOption) {
+  MediaSessionOptions options;
+  options.has_audio = true;
+  options.has_video = true;
+  talk_base::scoped_ptr<SessionDescription> offer(
+      f1_.CreateOffer(options, NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+  const ContentInfo* audio_content = offer->GetContentByName("audio");
+  EXPECT_FALSE(VerifyNoCNCodecs(audio_content));
+
+  options.vad_enabled = false;
+  offer.reset(f1_.CreateOffer(options, NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+  audio_content = offer->GetContentByName("audio");
+  EXPECT_TRUE(VerifyNoCNCodecs(audio_content));
+  talk_base::scoped_ptr<SessionDescription> answer(
+      f1_.CreateAnswer(offer.get(), options, NULL));
+  ASSERT_TRUE(answer.get() != NULL);
+  audio_content = answer->GetContentByName("audio");
+  EXPECT_TRUE(VerifyNoCNCodecs(audio_content));
+}
diff --git a/talk/session/media/mediasessionclient.cc b/talk/session/media/mediasessionclient.cc
new file mode 100644
index 0000000..18407ca
--- /dev/null
+++ b/talk/session/media/mediasessionclient.cc
@@ -0,0 +1,1085 @@
+/*
+ * 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/session/media/mediasessionclient.h"
+
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+#include "talk/media/base/cryptoparams.h"
+#include "talk/media/base/capturemanager.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/parsing.h"
+#include "talk/session/media/mediamessages.h"
+#include "talk/session/media/srtpfilter.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlconstants.h"
+#include "talk/xmpp/constants.h"
+
+namespace cricket {
+
+#if !defined(DISABLE_MEDIA_ENGINE_FACTORY)
+MediaSessionClient::MediaSessionClient(
+    const buzz::Jid& jid, SessionManager *manager)
+    : jid_(jid),
+      session_manager_(manager),
+      focus_call_(NULL),
+      channel_manager_(new ChannelManager(session_manager_->worker_thread())),
+      desc_factory_(channel_manager_,
+          session_manager_->transport_desc_factory()) {
+  Construct();
+}
+#endif
+
+MediaSessionClient::MediaSessionClient(
+    const buzz::Jid& jid, SessionManager *manager,
+    MediaEngineInterface* media_engine,
+    DataEngineInterface* data_media_engine,
+    DeviceManagerInterface* device_manager)
+    : jid_(jid),
+      session_manager_(manager),
+      focus_call_(NULL),
+      channel_manager_(new ChannelManager(
+          media_engine, data_media_engine,
+          device_manager, new CaptureManager(),
+          session_manager_->worker_thread())),
+      desc_factory_(channel_manager_,
+                    session_manager_->transport_desc_factory()) {
+  Construct();
+}
+
+void MediaSessionClient::Construct() {
+  // Register ourselves as the handler of audio and video sessions.
+  session_manager_->AddClient(NS_JINGLE_RTP, this);
+  // Forward device notifications.
+  SignalDevicesChange.repeat(channel_manager_->SignalDevicesChange);
+  // Bring up the channel manager.
+  // In previous versions of ChannelManager, this was done automatically
+  // in the constructor.
+  channel_manager_->Init();
+}
+
+MediaSessionClient::~MediaSessionClient() {
+  // Destroy all calls
+  std::map<uint32, Call *>::iterator it;
+  while (calls_.begin() != calls_.end()) {
+    std::map<uint32, Call *>::iterator it = calls_.begin();
+    DestroyCall((*it).second);
+  }
+
+  // Delete channel manager. This will wait for the channels to exit
+  delete channel_manager_;
+
+  // Remove ourselves from the client map.
+  session_manager_->RemoveClient(NS_JINGLE_RTP);
+}
+
+Call *MediaSessionClient::CreateCall() {
+  Call *call = new Call(this);
+  calls_[call->id()] = call;
+  SignalCallCreate(call);
+  return call;
+}
+
+void MediaSessionClient::OnSessionCreate(Session *session,
+                                         bool received_initiate) {
+  if (received_initiate) {
+    session->SignalState.connect(this, &MediaSessionClient::OnSessionState);
+  }
+}
+
+void MediaSessionClient::OnSessionState(BaseSession* base_session,
+                                        BaseSession::State state) {
+  // MediaSessionClient can only be used with a Session*, so it's
+  // safe to cast here.
+  Session* session = static_cast<Session*>(base_session);
+
+  if (state == Session::STATE_RECEIVEDINITIATE) {
+    // The creation of the call must happen after the session has
+    // processed the initiate message because we need the
+    // remote_description to know what content names to use in the
+    // call.
+
+    // If our accept would have no codecs, then we must reject this call.
+    const SessionDescription* offer = session->remote_description();
+    const SessionDescription* accept = CreateAnswer(offer, CallOptions());
+    const ContentInfo* audio_content = GetFirstAudioContent(accept);
+    bool audio_rejected = (!audio_content) ? true : audio_content->rejected;
+    const AudioContentDescription* audio_desc = (!audio_content) ? NULL :
+        static_cast<const AudioContentDescription*>(audio_content->description);
+
+    // For some reason, we need a call even if we reject. So, either find a
+    // matching call or create a new one.
+    // The matching of existing calls is used to support the multi-session mode
+    // required for p2p handoffs: ie. once a MUC call is established, a new
+    // session may be established for the same call but is direct between the
+    // clients. To indicate that this is the case, the initiator of the incoming
+    // session is set to be the same as the remote name of the MUC for the
+    // existing session, thus the client can know that this is a new session for
+    // the existing call, rather than a whole new call.
+    Call* call = NULL;
+    if (multisession_enabled_) {
+      call = FindCallByRemoteName(session->initiator_name());
+    }
+
+    if (call == NULL) {
+      // Could not find a matching call, so create a new one.
+      call = CreateCall();
+    }
+
+    session_map_[session->id()] = call;
+    call->IncomingSession(session, offer);
+
+    if (audio_rejected || !audio_desc || audio_desc->codecs().size() == 0) {
+      session->Reject(STR_TERMINATE_INCOMPATIBLE_PARAMETERS);
+    }
+    delete accept;
+  }
+}
+
+void MediaSessionClient::DestroyCall(Call *call) {
+  // Change focus away, signal destruction
+
+  if (call == focus_call_)
+    SetFocus(NULL);
+  SignalCallDestroy(call);
+
+  // Remove it from calls_ map and delete
+
+  std::map<uint32, Call *>::iterator it = calls_.find(call->id());
+  if (it != calls_.end())
+    calls_.erase(it);
+
+  delete call;
+}
+
+void MediaSessionClient::OnSessionDestroy(Session *session) {
+  // Find the call this session is in, remove it
+  SessionMap::iterator it = session_map_.find(session->id());
+  ASSERT(it != session_map_.end());
+  if (it != session_map_.end()) {
+    Call *call = (*it).second;
+    session_map_.erase(it);
+    call->RemoveSession(session);
+  }
+}
+
+Call *MediaSessionClient::GetFocus() {
+  return focus_call_;
+}
+
+void MediaSessionClient::SetFocus(Call *call) {
+  Call *old_focus_call = focus_call_;
+  if (focus_call_ != call) {
+    if (focus_call_ != NULL)
+      focus_call_->EnableChannels(false);
+    focus_call_ = call;
+    if (focus_call_ != NULL)
+      focus_call_->EnableChannels(true);
+    SignalFocus(focus_call_, old_focus_call);
+  }
+}
+
+void MediaSessionClient::JoinCalls(Call *call_to_join, Call *call) {
+  // Move all sessions from call to call_to_join, delete call.
+  // If call_to_join has focus, added sessions should have enabled channels.
+
+  if (focus_call_ == call)
+    SetFocus(NULL);
+  call_to_join->Join(call, focus_call_ == call_to_join);
+  DestroyCall(call);
+}
+
+Session *MediaSessionClient::CreateSession(Call *call) {
+  const std::string& type = NS_JINGLE_RTP;
+  Session *session = session_manager_->CreateSession(jid().Str(), type);
+  session_map_[session->id()] = call;
+  return session;
+}
+
+Call *MediaSessionClient::FindCallByRemoteName(const std::string &remote_name) {
+  SessionMap::const_iterator call;
+  for (call = session_map_.begin(); call != session_map_.end(); ++call) {
+    std::vector<Session *> sessions = call->second->sessions();
+    std::vector<Session *>::const_iterator session;
+    for (session = sessions.begin(); session != sessions.end(); ++session) {
+      if (remote_name == (*session)->remote_name()) {
+        return call->second;
+      }
+    }
+  }
+
+  return NULL;
+}
+
+// TODO(pthatcher): Move all of the parsing and writing functions into
+// mediamessages.cc, with unit tests.
+bool ParseGingleAudioCodec(const buzz::XmlElement* element, AudioCodec* out) {
+  int id = GetXmlAttr(element, QN_ID, -1);
+  if (id < 0)
+    return false;
+
+  std::string name = GetXmlAttr(element, QN_NAME, buzz::STR_EMPTY);
+  int clockrate = GetXmlAttr(element, QN_CLOCKRATE, 0);
+  int bitrate = GetXmlAttr(element, QN_BITRATE, 0);
+  int channels = GetXmlAttr(element, QN_CHANNELS, 1);
+  *out = AudioCodec(id, name, clockrate, bitrate, channels, 0);
+  return true;
+}
+
+bool ParseGingleVideoCodec(const buzz::XmlElement* element, VideoCodec* out) {
+  int id = GetXmlAttr(element, QN_ID, -1);
+  if (id < 0)
+    return false;
+
+  std::string name = GetXmlAttr(element, QN_NAME, buzz::STR_EMPTY);
+  int width = GetXmlAttr(element, QN_WIDTH, 0);
+  int height = GetXmlAttr(element, QN_HEIGHT, 0);
+  int framerate = GetXmlAttr(element, QN_FRAMERATE, 0);
+
+  *out = VideoCodec(id, name, width, height, framerate, 0);
+  return true;
+}
+
+// Parses an ssrc string as a legacy stream.  If it fails, returns
+// false and fills an error message.
+bool ParseSsrcAsLegacyStream(const std::string& ssrc_str,
+                             std::vector<StreamParams>* streams,
+                             ParseError* error) {
+  if (!ssrc_str.empty()) {
+    uint32 ssrc;
+    if (!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* media) {
+  const buzz::XmlElement* ssrc_elem = parent_elem->FirstNamed(name);
+  if (ssrc_elem) {
+    ParseError error;
+    ParseSsrcAsLegacyStream(
+        ssrc_elem->BodyText(), &(media->mutable_streams()), &error);
+  }
+}
+
+bool ParseCryptoParams(const buzz::XmlElement* element,
+                       CryptoParams* out,
+                       ParseError* error) {
+  if (!element->HasAttr(QN_CRYPTO_SUITE)) {
+    return BadParse("crypto: crypto-suite attribute missing ", error);
+  } else if (!element->HasAttr(QN_CRYPTO_KEY_PARAMS)) {
+    return BadParse("crypto: key-params attribute missing ", error);
+  } else if (!element->HasAttr(QN_CRYPTO_TAG)) {
+    return BadParse("crypto: tag attribute missing ", error);
+  }
+
+  const std::string& crypto_suite = element->Attr(QN_CRYPTO_SUITE);
+  const std::string& key_params = element->Attr(QN_CRYPTO_KEY_PARAMS);
+  const int tag = GetXmlAttr(element, QN_CRYPTO_TAG, 0);
+  const std::string& session_params =
+      element->Attr(QN_CRYPTO_SESSION_PARAMS);  // Optional.
+
+  *out = CryptoParams(tag, crypto_suite, key_params, session_params);
+  return true;
+}
+
+
+// Parse the first encryption element found with a matching 'usage'
+// element.
+// <usage/> is specific to Gingle. In Jingle, <crypto/> is already
+// scoped to a content.
+// Return false if there was an encryption element and it could not be
+// parsed.
+bool ParseGingleEncryption(const buzz::XmlElement* desc,
+                           const buzz::QName& usage,
+                           MediaContentDescription* media,
+                           ParseError* error) {
+  for (const buzz::XmlElement* encryption = desc->FirstNamed(QN_ENCRYPTION);
+       encryption != NULL;
+       encryption = encryption->NextNamed(QN_ENCRYPTION)) {
+    if (encryption->FirstNamed(usage) != NULL) {
+      media->set_crypto_required(
+          GetXmlAttr(encryption, QN_ENCRYPTION_REQUIRED, false));
+      for (const buzz::XmlElement* crypto = encryption->FirstNamed(QN_CRYPTO);
+           crypto != NULL;
+           crypto = crypto->NextNamed(QN_CRYPTO)) {
+        CryptoParams params;
+        if (!ParseCryptoParams(crypto, &params, error)) {
+          return false;
+        }
+        media->AddCrypto(params);
+      }
+      break;
+    }
+  }
+  return true;
+}
+
+void ParseBandwidth(const buzz::XmlElement* parent_elem,
+                    MediaContentDescription* media) {
+  const buzz::XmlElement* bw_elem = GetXmlChild(parent_elem, LN_BANDWIDTH);
+  int bandwidth_kbps = -1;
+  if (bw_elem && talk_base::FromString(bw_elem->BodyText(), &bandwidth_kbps)) {
+    if (bandwidth_kbps >= 0) {
+      media->set_bandwidth(bandwidth_kbps * 1000);
+    }
+  }
+}
+
+bool ParseGingleAudioContent(const buzz::XmlElement* content_elem,
+                             ContentDescription** content,
+                             ParseError* error) {
+  AudioContentDescription* audio = new AudioContentDescription();
+
+  if (content_elem->FirstElement()) {
+    for (const buzz::XmlElement* codec_elem =
+             content_elem->FirstNamed(QN_GINGLE_AUDIO_PAYLOADTYPE);
+         codec_elem != NULL;
+         codec_elem = codec_elem->NextNamed(QN_GINGLE_AUDIO_PAYLOADTYPE)) {
+      AudioCodec codec;
+      if (ParseGingleAudioCodec(codec_elem, &codec)) {
+        audio->AddCodec(codec);
+      }
+    }
+  } else {
+    // For backward compatibility, we can assume the other client is
+    // an old version of Talk if it has no audio payload types at all.
+    audio->AddCodec(AudioCodec(103, "ISAC", 16000, -1, 1, 1));
+    audio->AddCodec(AudioCodec(0, "PCMU", 8000, 64000, 1, 0));
+  }
+
+  ParseGingleSsrc(content_elem, QN_GINGLE_AUDIO_SRCID, audio);
+
+  if (!ParseGingleEncryption(content_elem, QN_GINGLE_AUDIO_CRYPTO_USAGE,
+                             audio, error)) {
+    return false;
+  }
+
+  *content = audio;
+  return true;
+}
+
+bool ParseGingleVideoContent(const buzz::XmlElement* content_elem,
+                             ContentDescription** content,
+                             ParseError* error) {
+  VideoContentDescription* video = new VideoContentDescription();
+
+  for (const buzz::XmlElement* codec_elem =
+           content_elem->FirstNamed(QN_GINGLE_VIDEO_PAYLOADTYPE);
+       codec_elem != NULL;
+       codec_elem = codec_elem->NextNamed(QN_GINGLE_VIDEO_PAYLOADTYPE)) {
+    VideoCodec codec;
+    if (ParseGingleVideoCodec(codec_elem, &codec)) {
+      video->AddCodec(codec);
+    }
+  }
+
+  ParseGingleSsrc(content_elem, QN_GINGLE_VIDEO_SRCID, video);
+  ParseBandwidth(content_elem, video);
+
+  if (!ParseGingleEncryption(content_elem, QN_GINGLE_VIDEO_CRYPTO_USAGE,
+                             video, error)) {
+    return false;
+  }
+
+  *content = video;
+  return true;
+}
+
+void ParsePayloadTypeParameters(const buzz::XmlElement* element,
+                                std::map<std::string, std::string>* paramap) {
+  for (const buzz::XmlElement* param = element->FirstNamed(QN_PARAMETER);
+       param != NULL; param = param->NextNamed(QN_PARAMETER)) {
+    std::string name  = GetXmlAttr(param, QN_PAYLOADTYPE_PARAMETER_NAME,
+                                   buzz::STR_EMPTY);
+    std::string value = GetXmlAttr(param, QN_PAYLOADTYPE_PARAMETER_VALUE,
+                                   buzz::STR_EMPTY);
+    if (!name.empty() && !value.empty()) {
+      paramap->insert(make_pair(name, value));
+    }
+  }
+}
+
+int FindWithDefault(const std::map<std::string, std::string>& map,
+                    const std::string& key, const int def) {
+  std::map<std::string, std::string>::const_iterator iter = map.find(key);
+  return (iter == map.end()) ? def : atoi(iter->second.c_str());
+}
+
+
+// Parse the first encryption element found.
+// Return false if there was an encryption element and it could not be
+// parsed.
+bool ParseJingleEncryption(const buzz::XmlElement* content_elem,
+                           MediaContentDescription* media,
+                           ParseError* error) {
+  const buzz::XmlElement* encryption =
+          content_elem->FirstNamed(QN_ENCRYPTION);
+  if (encryption == NULL) {
+      return true;
+  }
+
+  media->set_crypto_required(
+      GetXmlAttr(encryption, QN_ENCRYPTION_REQUIRED, false));
+
+  for (const buzz::XmlElement* crypto = encryption->FirstNamed(QN_CRYPTO);
+       crypto != NULL;
+       crypto = crypto->NextNamed(QN_CRYPTO)) {
+    CryptoParams params;
+    if (!ParseCryptoParams(crypto, &params, error)) {
+      return false;
+    }
+    media->AddCrypto(params);
+  }
+  return true;
+}
+
+bool ParseJingleAudioCodec(const buzz::XmlElement* elem, AudioCodec* codec) {
+  int id = GetXmlAttr(elem, QN_ID, -1);
+  if (id < 0)
+    return false;
+
+  std::string name = GetXmlAttr(elem, QN_NAME, buzz::STR_EMPTY);
+  int clockrate = GetXmlAttr(elem, QN_CLOCKRATE, 0);
+  int channels = GetXmlAttr(elem, QN_CHANNELS, 1);
+
+  std::map<std::string, std::string> paramap;
+  ParsePayloadTypeParameters(elem, &paramap);
+  int bitrate = FindWithDefault(paramap, PAYLOADTYPE_PARAMETER_BITRATE, 0);
+
+  *codec = AudioCodec(id, name, clockrate, bitrate, channels, 0);
+  return true;
+}
+
+bool ParseJingleVideoCodec(const buzz::XmlElement* elem, VideoCodec* codec) {
+  int id = GetXmlAttr(elem, QN_ID, -1);
+  if (id < 0)
+    return false;
+
+  std::string name = GetXmlAttr(elem, QN_NAME, buzz::STR_EMPTY);
+
+  std::map<std::string, std::string> paramap;
+  ParsePayloadTypeParameters(elem, &paramap);
+  int width = FindWithDefault(paramap, PAYLOADTYPE_PARAMETER_WIDTH, 0);
+  int height = FindWithDefault(paramap, PAYLOADTYPE_PARAMETER_HEIGHT, 0);
+  int framerate = FindWithDefault(paramap, PAYLOADTYPE_PARAMETER_FRAMERATE, 0);
+
+  *codec = VideoCodec(id, name, width, height, framerate, 0);
+  codec->params = paramap;
+  return true;
+}
+
+bool ParseJingleDataCodec(const buzz::XmlElement* elem, DataCodec* codec) {
+  int id = GetXmlAttr(elem, QN_ID, -1);
+  if (id < 0)
+    return false;
+
+  std::string name = GetXmlAttr(elem, QN_NAME, buzz::STR_EMPTY);
+
+  *codec = DataCodec(id, name, 0);
+  return true;
+}
+
+bool ParseJingleStreamsOrLegacySsrc(const buzz::XmlElement* desc_elem,
+                                    MediaContentDescription* media,
+                                    ParseError* error) {
+  if (HasJingleStreams(desc_elem)) {
+    if (!ParseJingleStreams(desc_elem, &(media->mutable_streams()), error)) {
+      return false;
+    }
+  } else {
+    const std::string ssrc_str = desc_elem->Attr(QN_SSRC);
+    if (!ParseSsrcAsLegacyStream(
+            ssrc_str, &(media->mutable_streams()), error)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool ParseJingleAudioContent(const buzz::XmlElement* content_elem,
+                             ContentDescription** content,
+                             ParseError* error) {
+  AudioContentDescription* audio = new AudioContentDescription();
+
+  for (const buzz::XmlElement* payload_elem =
+           content_elem->FirstNamed(QN_JINGLE_RTP_PAYLOADTYPE);
+      payload_elem != NULL;
+      payload_elem = payload_elem->NextNamed(QN_JINGLE_RTP_PAYLOADTYPE)) {
+    AudioCodec codec;
+    if (ParseJingleAudioCodec(payload_elem, &codec)) {
+      audio->AddCodec(codec);
+    }
+  }
+
+  if (!ParseJingleStreamsOrLegacySsrc(content_elem, audio, error)) {
+    return false;
+  }
+
+  if (!ParseJingleEncryption(content_elem, audio, error)) {
+    return false;
+  }
+
+  audio->set_rtcp_mux(content_elem->FirstNamed(QN_JINGLE_RTCP_MUX) != NULL);
+
+  RtpHeaderExtensions hdrexts;
+  if (!ParseJingleRtpHeaderExtensions(content_elem, &hdrexts, error)) {
+    return false;
+  }
+  audio->set_rtp_header_extensions(hdrexts);
+
+  *content = audio;
+  return true;
+}
+
+bool ParseJingleVideoContent(const buzz::XmlElement* content_elem,
+                             ContentDescription** content,
+                             ParseError* error) {
+  VideoContentDescription* video = new VideoContentDescription();
+
+  for (const buzz::XmlElement* payload_elem =
+           content_elem->FirstNamed(QN_JINGLE_RTP_PAYLOADTYPE);
+      payload_elem != NULL;
+      payload_elem = payload_elem->NextNamed(QN_JINGLE_RTP_PAYLOADTYPE)) {
+    VideoCodec codec;
+    if (ParseJingleVideoCodec(payload_elem, &codec)) {
+      video->AddCodec(codec);
+    }
+  }
+
+  if (!ParseJingleStreamsOrLegacySsrc(content_elem, video, error)) {
+    return false;
+  }
+  ParseBandwidth(content_elem, video);
+
+  if (!ParseJingleEncryption(content_elem, video, error)) {
+    return false;
+  }
+
+  video->set_rtcp_mux(content_elem->FirstNamed(QN_JINGLE_RTCP_MUX) != NULL);
+
+  RtpHeaderExtensions hdrexts;
+  if (!ParseJingleRtpHeaderExtensions(content_elem, &hdrexts, error)) {
+    return false;
+  }
+  video->set_rtp_header_extensions(hdrexts);
+
+  *content = video;
+  return true;
+}
+
+bool ParseJingleSctpDataContent(const buzz::XmlElement* content_elem,
+                                ContentDescription** content,
+                                ParseError* error) {
+  DataContentDescription* data = new DataContentDescription();
+  data->set_protocol(kMediaProtocolSctp);
+
+  for (const buzz::XmlElement* stream_elem =
+           content_elem->FirstNamed(QN_JINGLE_DRAFT_SCTP_STREAM);
+       stream_elem != NULL;
+       stream_elem = stream_elem->NextNamed(QN_JINGLE_DRAFT_SCTP_STREAM)) {
+    StreamParams stream;
+    stream.groupid = stream_elem->Attr(QN_NICK);
+    stream.id = stream_elem->Attr(QN_NAME);
+    uint32 sid;
+    if (!talk_base::FromString(stream_elem->Attr(QN_SID), &sid)) {
+      return BadParse("Missing or invalid sid.", error);
+    }
+    if (sid > kMaxSctpSid) {
+      return BadParse("SID is greater than max value.", error);
+    }
+
+    stream.ssrcs.push_back(sid);
+    data->mutable_streams().push_back(stream);
+  }
+
+  *content = data;
+  return true;
+}
+
+bool ParseJingleRtpDataContent(const buzz::XmlElement* content_elem,
+                               ContentDescription** content,
+                               ParseError* error) {
+  DataContentDescription* data = new DataContentDescription();
+
+  for (const buzz::XmlElement* payload_elem =
+           content_elem->FirstNamed(QN_JINGLE_RTP_PAYLOADTYPE);
+      payload_elem != NULL;
+      payload_elem = payload_elem->NextNamed(QN_JINGLE_RTP_PAYLOADTYPE)) {
+    DataCodec codec;
+    if (ParseJingleDataCodec(payload_elem, &codec)) {
+      data->AddCodec(codec);
+    }
+  }
+
+  if (!ParseJingleStreamsOrLegacySsrc(content_elem, data, error)) {
+    return false;
+  }
+  ParseBandwidth(content_elem, data);
+
+  if (!ParseJingleEncryption(content_elem, data, error)) {
+    return false;
+  }
+
+  data->set_rtcp_mux(content_elem->FirstNamed(QN_JINGLE_RTCP_MUX) != NULL);
+
+  *content = data;
+  return true;
+}
+
+bool MediaSessionClient::ParseContent(SignalingProtocol protocol,
+                                      const buzz::XmlElement* content_elem,
+                                      ContentDescription** content,
+                                      ParseError* error) {
+  if (protocol == PROTOCOL_GINGLE) {
+    const std::string& content_type = content_elem->Name().Namespace();
+    if (NS_GINGLE_AUDIO == content_type) {
+      return ParseGingleAudioContent(content_elem, content, error);
+    } else if (NS_GINGLE_VIDEO == content_type) {
+      return ParseGingleVideoContent(content_elem, content, error);
+    } else {
+      return BadParse("Unknown content type: " + content_type, error);
+    }
+  } else {
+    const std::string& content_type = content_elem->Name().Namespace();
+    // We use the XMLNS of the <description> element to determine if
+    // it's RTP or SCTP.
+    if (content_type == NS_JINGLE_DRAFT_SCTP) {
+      return ParseJingleSctpDataContent(content_elem, content, error);
+    }
+
+    std::string media;
+    if (!RequireXmlAttr(content_elem, QN_JINGLE_CONTENT_MEDIA, &media, error))
+      return false;
+
+    if (media == JINGLE_CONTENT_MEDIA_AUDIO) {
+      return ParseJingleAudioContent(content_elem, content, error);
+    } else if (media == JINGLE_CONTENT_MEDIA_VIDEO) {
+      return ParseJingleVideoContent(content_elem, content, error);
+    } else if (media == JINGLE_CONTENT_MEDIA_DATA) {
+      return ParseJingleRtpDataContent(content_elem, content, error);
+    } else {
+      return BadParse("Unknown media: " + media, error);
+    }
+  }
+}
+
+buzz::XmlElement* CreateGingleAudioCodecElem(const AudioCodec& codec) {
+  buzz::XmlElement* payload_type =
+      new buzz::XmlElement(QN_GINGLE_AUDIO_PAYLOADTYPE, true);
+  AddXmlAttr(payload_type, QN_ID, codec.id);
+  payload_type->AddAttr(QN_NAME, codec.name);
+  if (codec.clockrate > 0)
+    AddXmlAttr(payload_type, QN_CLOCKRATE, codec.clockrate);
+  if (codec.bitrate > 0)
+    AddXmlAttr(payload_type, QN_BITRATE, codec.bitrate);
+  if (codec.channels > 1)
+    AddXmlAttr(payload_type, QN_CHANNELS, codec.channels);
+  return payload_type;
+}
+
+buzz::XmlElement* CreateGingleVideoCodecElem(const VideoCodec& codec) {
+  buzz::XmlElement* payload_type =
+      new buzz::XmlElement(QN_GINGLE_VIDEO_PAYLOADTYPE, true);
+  AddXmlAttr(payload_type, QN_ID, codec.id);
+  payload_type->AddAttr(QN_NAME, codec.name);
+  AddXmlAttr(payload_type, QN_WIDTH, codec.width);
+  AddXmlAttr(payload_type, QN_HEIGHT, codec.height);
+  AddXmlAttr(payload_type, QN_FRAMERATE, codec.framerate);
+  return payload_type;
+}
+
+buzz::XmlElement* CreateGingleSsrcElem(const buzz::QName& name, uint32 ssrc) {
+  buzz::XmlElement* elem = new buzz::XmlElement(name, true);
+  if (ssrc) {
+    SetXmlBody(elem, ssrc);
+  }
+  return elem;
+}
+
+buzz::XmlElement* CreateBandwidthElem(const buzz::QName& name, int bps) {
+  int kbps = bps / 1000;
+  buzz::XmlElement* elem = new buzz::XmlElement(name);
+  elem->AddAttr(buzz::QN_TYPE, "AS");
+  SetXmlBody(elem, kbps);
+  return elem;
+}
+
+// For Jingle, usage_qname is empty.
+buzz::XmlElement* CreateJingleEncryptionElem(const CryptoParamsVec& cryptos,
+                                             bool required) {
+  buzz::XmlElement* encryption_elem = new buzz::XmlElement(QN_ENCRYPTION);
+
+  if (required) {
+    encryption_elem->SetAttr(QN_ENCRYPTION_REQUIRED, "true");
+  }
+
+  for (CryptoParamsVec::const_iterator i = cryptos.begin();
+       i != cryptos.end();
+       ++i) {
+    buzz::XmlElement* crypto_elem = new buzz::XmlElement(QN_CRYPTO);
+
+    AddXmlAttr(crypto_elem, QN_CRYPTO_TAG, i->tag);
+    crypto_elem->AddAttr(QN_CRYPTO_SUITE, i->cipher_suite);
+    crypto_elem->AddAttr(QN_CRYPTO_KEY_PARAMS, i->key_params);
+    if (!i->session_params.empty()) {
+      crypto_elem->AddAttr(QN_CRYPTO_SESSION_PARAMS, i->session_params);
+    }
+    encryption_elem->AddElement(crypto_elem);
+  }
+  return encryption_elem;
+}
+
+buzz::XmlElement* CreateGingleEncryptionElem(const CryptoParamsVec& cryptos,
+                                             const buzz::QName& usage_qname,
+                                             bool required) {
+  buzz::XmlElement* encryption_elem =
+      CreateJingleEncryptionElem(cryptos, required);
+
+  if (required) {
+    encryption_elem->SetAttr(QN_ENCRYPTION_REQUIRED, "true");
+  }
+
+  buzz::XmlElement* usage_elem = new buzz::XmlElement(usage_qname);
+  encryption_elem->AddElement(usage_elem);
+
+  return encryption_elem;
+}
+
+buzz::XmlElement* CreateGingleAudioContentElem(
+    const AudioContentDescription* audio,
+    bool crypto_required) {
+  buzz::XmlElement* elem =
+      new buzz::XmlElement(QN_GINGLE_AUDIO_CONTENT, true);
+
+  for (AudioCodecs::const_iterator codec = audio->codecs().begin();
+       codec != audio->codecs().end(); ++codec) {
+    elem->AddElement(CreateGingleAudioCodecElem(*codec));
+  }
+  if (audio->has_ssrcs()) {
+    elem->AddElement(CreateGingleSsrcElem(
+        QN_GINGLE_AUDIO_SRCID, audio->first_ssrc()));
+  }
+
+  const CryptoParamsVec& cryptos = audio->cryptos();
+  if (!cryptos.empty()) {
+    elem->AddElement(CreateGingleEncryptionElem(cryptos,
+                                                QN_GINGLE_AUDIO_CRYPTO_USAGE,
+                                                crypto_required));
+  }
+  return elem;
+}
+
+buzz::XmlElement* CreateGingleVideoContentElem(
+    const VideoContentDescription* video,
+    bool crypto_required) {
+  buzz::XmlElement* elem =
+      new buzz::XmlElement(QN_GINGLE_VIDEO_CONTENT, true);
+
+  for (VideoCodecs::const_iterator codec = video->codecs().begin();
+       codec != video->codecs().end(); ++codec) {
+    elem->AddElement(CreateGingleVideoCodecElem(*codec));
+  }
+  if (video->has_ssrcs()) {
+    elem->AddElement(CreateGingleSsrcElem(
+        QN_GINGLE_VIDEO_SRCID, video->first_ssrc()));
+  }
+  if (video->bandwidth() != kAutoBandwidth) {
+    elem->AddElement(CreateBandwidthElem(QN_GINGLE_VIDEO_BANDWIDTH,
+                                         video->bandwidth()));
+  }
+
+  const CryptoParamsVec& cryptos = video->cryptos();
+  if (!cryptos.empty()) {
+    elem->AddElement(CreateGingleEncryptionElem(cryptos,
+                                                QN_GINGLE_VIDEO_CRYPTO_USAGE,
+                                                crypto_required));
+  }
+
+  return elem;
+}
+
+template <class T>
+buzz::XmlElement* CreatePayloadTypeParameterElem(
+    const std::string& name, T value) {
+  buzz::XmlElement* elem = new buzz::XmlElement(QN_PARAMETER);
+
+  elem->AddAttr(QN_PAYLOADTYPE_PARAMETER_NAME, name);
+  AddXmlAttr(elem, QN_PAYLOADTYPE_PARAMETER_VALUE, value);
+
+  return elem;
+}
+
+buzz::XmlElement* CreateJingleAudioCodecElem(const AudioCodec& codec) {
+  buzz::XmlElement* elem = new buzz::XmlElement(QN_JINGLE_RTP_PAYLOADTYPE);
+
+  AddXmlAttr(elem, QN_ID, codec.id);
+  elem->AddAttr(QN_NAME, codec.name);
+  if (codec.clockrate > 0) {
+    AddXmlAttr(elem, QN_CLOCKRATE, codec.clockrate);
+  }
+  if (codec.bitrate > 0) {
+    elem->AddElement(CreatePayloadTypeParameterElem(
+        PAYLOADTYPE_PARAMETER_BITRATE, codec.bitrate));
+  }
+  if (codec.channels > 1) {
+    AddXmlAttr(elem, QN_CHANNELS, codec.channels);
+  }
+
+  return elem;
+}
+
+buzz::XmlElement* CreateJingleVideoCodecElem(const VideoCodec& codec) {
+  buzz::XmlElement* elem = new buzz::XmlElement(QN_JINGLE_RTP_PAYLOADTYPE);
+
+  AddXmlAttr(elem, QN_ID, codec.id);
+  elem->AddAttr(QN_NAME, codec.name);
+  elem->AddElement(CreatePayloadTypeParameterElem(
+      PAYLOADTYPE_PARAMETER_WIDTH, codec.width));
+  elem->AddElement(CreatePayloadTypeParameterElem(
+      PAYLOADTYPE_PARAMETER_HEIGHT, codec.height));
+  elem->AddElement(CreatePayloadTypeParameterElem(
+      PAYLOADTYPE_PARAMETER_FRAMERATE, codec.framerate));
+  CodecParameterMap::const_iterator param_iter;
+  for (param_iter = codec.params.begin(); param_iter != codec.params.end();
+       ++param_iter) {
+    elem->AddElement(CreatePayloadTypeParameterElem(param_iter->first,
+                                                    param_iter->second));
+  }
+
+  return elem;
+}
+
+buzz::XmlElement* CreateJingleDataCodecElem(const DataCodec& codec) {
+  buzz::XmlElement* elem = new buzz::XmlElement(QN_JINGLE_RTP_PAYLOADTYPE);
+
+  AddXmlAttr(elem, QN_ID, codec.id);
+  elem->AddAttr(QN_NAME, codec.name);
+
+  return elem;
+}
+
+void WriteLegacyJingleSsrc(const MediaContentDescription* media,
+                           buzz::XmlElement* elem) {
+  if (media->has_ssrcs()) {
+    AddXmlAttr(elem, QN_SSRC, media->first_ssrc());
+  }
+}
+
+void WriteJingleStreamsOrLegacySsrc(const MediaContentDescription* media,
+                                    buzz::XmlElement* desc_elem) {
+  if (!media->multistream()) {
+    WriteLegacyJingleSsrc(media, desc_elem);
+  } else {
+    WriteJingleStreams(media->streams(), desc_elem);
+  }
+}
+
+buzz::XmlElement* CreateJingleAudioContentElem(
+    const AudioContentDescription* audio, bool crypto_required) {
+  buzz::XmlElement* elem =
+      new buzz::XmlElement(QN_JINGLE_RTP_CONTENT, true);
+
+  elem->SetAttr(QN_JINGLE_CONTENT_MEDIA, JINGLE_CONTENT_MEDIA_AUDIO);
+  WriteJingleStreamsOrLegacySsrc(audio, elem);
+
+  for (AudioCodecs::const_iterator codec = audio->codecs().begin();
+       codec != audio->codecs().end(); ++codec) {
+    elem->AddElement(CreateJingleAudioCodecElem(*codec));
+  }
+
+  const CryptoParamsVec& cryptos = audio->cryptos();
+  if (!cryptos.empty()) {
+    elem->AddElement(CreateJingleEncryptionElem(cryptos, crypto_required));
+  }
+
+  if (audio->rtcp_mux()) {
+    elem->AddElement(new buzz::XmlElement(QN_JINGLE_RTCP_MUX));
+  }
+
+  WriteJingleRtpHeaderExtensions(audio->rtp_header_extensions(), elem);
+
+  return elem;
+}
+
+buzz::XmlElement* CreateJingleVideoContentElem(
+    const VideoContentDescription* video, bool crypto_required) {
+  buzz::XmlElement* elem =
+      new buzz::XmlElement(QN_JINGLE_RTP_CONTENT, true);
+
+  elem->SetAttr(QN_JINGLE_CONTENT_MEDIA, JINGLE_CONTENT_MEDIA_VIDEO);
+  WriteJingleStreamsOrLegacySsrc(video, elem);
+
+  for (VideoCodecs::const_iterator codec = video->codecs().begin();
+       codec != video->codecs().end(); ++codec) {
+    elem->AddElement(CreateJingleVideoCodecElem(*codec));
+  }
+
+  const CryptoParamsVec& cryptos = video->cryptos();
+  if (!cryptos.empty()) {
+    elem->AddElement(CreateJingleEncryptionElem(cryptos, crypto_required));
+  }
+
+  if (video->rtcp_mux()) {
+    elem->AddElement(new buzz::XmlElement(QN_JINGLE_RTCP_MUX));
+  }
+
+  if (video->bandwidth() != kAutoBandwidth) {
+    elem->AddElement(CreateBandwidthElem(QN_JINGLE_RTP_BANDWIDTH,
+                                         video->bandwidth()));
+  }
+
+  WriteJingleRtpHeaderExtensions(video->rtp_header_extensions(), elem);
+
+  return elem;
+}
+
+buzz::XmlElement* CreateJingleSctpDataContentElem(
+    const DataContentDescription* data) {
+  buzz::XmlElement* content_elem =
+      new buzz::XmlElement(QN_JINGLE_DRAFT_SCTP_CONTENT, true);
+  for (std::vector<StreamParams>::const_iterator
+           stream = data->streams().begin();
+       stream != data->streams().end(); ++stream) {
+    buzz::XmlElement* stream_elem =
+      new buzz::XmlElement(QN_JINGLE_DRAFT_SCTP_STREAM, false);
+    AddXmlAttrIfNonEmpty(stream_elem, QN_NICK, stream->groupid);
+    AddXmlAttrIfNonEmpty(stream_elem, QN_NAME, stream->id);
+    if (!stream->ssrcs.empty()) {
+      AddXmlAttr(stream_elem, QN_SID, stream->ssrcs[0]);
+    }
+    content_elem->AddElement(stream_elem);
+  }
+  return content_elem;;
+}
+
+buzz::XmlElement* CreateJingleRtpDataContentElem(
+    const DataContentDescription* data, bool crypto_required) {
+
+  buzz::XmlElement* elem =
+      new buzz::XmlElement(QN_JINGLE_RTP_CONTENT, true);
+
+  elem->SetAttr(QN_JINGLE_CONTENT_MEDIA, JINGLE_CONTENT_MEDIA_DATA);
+  WriteJingleStreamsOrLegacySsrc(data, elem);
+
+  for (DataCodecs::const_iterator codec = data->codecs().begin();
+       codec != data->codecs().end(); ++codec) {
+    elem->AddElement(CreateJingleDataCodecElem(*codec));
+  }
+
+  const CryptoParamsVec& cryptos = data->cryptos();
+  if (!cryptos.empty()) {
+    elem->AddElement(CreateJingleEncryptionElem(cryptos, crypto_required));
+  }
+
+  if (data->rtcp_mux()) {
+    elem->AddElement(new buzz::XmlElement(QN_JINGLE_RTCP_MUX));
+  }
+
+  if (data->bandwidth() != kAutoBandwidth) {
+    elem->AddElement(CreateBandwidthElem(QN_JINGLE_RTP_BANDWIDTH,
+                                         data->bandwidth()));
+  }
+
+  return elem;
+}
+
+bool IsSctp(const DataContentDescription* data) {
+  return (data->protocol() == kMediaProtocolSctp ||
+    data->protocol() == kMediaProtocolDtlsSctp);
+}
+
+buzz::XmlElement* CreateJingleDataContentElem(
+    const DataContentDescription* data, bool crypto_required) {
+  if (IsSctp(data)) {
+    return CreateJingleSctpDataContentElem(data);
+  } else {
+    return CreateJingleRtpDataContentElem(data, crypto_required);
+  }
+}
+
+bool MediaSessionClient::IsWritable(SignalingProtocol protocol,
+                                    const ContentDescription* content) {
+  const MediaContentDescription* media =
+      static_cast<const MediaContentDescription*>(content);
+  if (protocol == PROTOCOL_GINGLE &&
+      media->type() == MEDIA_TYPE_DATA) {
+    return false;
+  }
+  return true;
+}
+
+bool MediaSessionClient::WriteContent(SignalingProtocol protocol,
+                                      const ContentDescription* content,
+                                      buzz::XmlElement** elem,
+                                      WriteError* error) {
+  const MediaContentDescription* media =
+      static_cast<const MediaContentDescription*>(content);
+  bool crypto_required = secure() == SEC_REQUIRED;
+
+  if (media->type() == MEDIA_TYPE_AUDIO) {
+    const AudioContentDescription* audio =
+        static_cast<const AudioContentDescription*>(media);
+    if (protocol == PROTOCOL_GINGLE) {
+      *elem = CreateGingleAudioContentElem(audio, crypto_required);
+    } else {
+      *elem = CreateJingleAudioContentElem(audio, crypto_required);
+    }
+  } else if (media->type() == MEDIA_TYPE_VIDEO) {
+    const VideoContentDescription* video =
+        static_cast<const VideoContentDescription*>(media);
+    if (protocol == PROTOCOL_GINGLE) {
+      *elem = CreateGingleVideoContentElem(video, crypto_required);
+    } else {
+      *elem = CreateJingleVideoContentElem(video, crypto_required);
+    }
+  } else if (media->type() == MEDIA_TYPE_DATA) {
+    const DataContentDescription* data =
+        static_cast<const DataContentDescription*>(media);
+    if (protocol == PROTOCOL_GINGLE) {
+      return BadWrite("Data channel not supported with Gingle.", error);
+    } else {
+      *elem = CreateJingleDataContentElem(data, crypto_required);
+    }
+  } else {
+    return BadWrite("Unknown content type: " +
+                    talk_base::ToString<int>(media->type()), error);
+  }
+
+  return true;
+}
+
+}  // namespace cricket
diff --git a/talk/session/media/mediasessionclient.h b/talk/session/media/mediasessionclient.h
new file mode 100644
index 0000000..c76f0e9
--- /dev/null
+++ b/talk/session/media/mediasessionclient.h
@@ -0,0 +1,174 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_MEDIA_MEDIASESSIONCLIENT_H_
+#define TALK_SESSION_MEDIA_MEDIASESSIONCLIENT_H_
+
+#include <string>
+#include <vector>
+#include <map>
+#include <algorithm>
+#include "talk/base/messagequeue.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/sigslotrepeater.h"
+#include "talk/base/thread.h"
+#include "talk/media/base/cryptoparams.h"
+#include "talk/p2p/base/session.h"
+#include "talk/p2p/base/sessionclient.h"
+#include "talk/p2p/base/sessiondescription.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/session/media/call.h"
+#include "talk/session/media/channelmanager.h"
+#include "talk/session/media/mediasession.h"
+
+namespace cricket {
+
+class Call;
+
+class MediaSessionClient : public SessionClient, public sigslot::has_slots<> {
+ public:
+#if !defined(DISABLE_MEDIA_ENGINE_FACTORY)
+  MediaSessionClient(const buzz::Jid& jid, SessionManager *manager);
+#endif
+  // Alternative constructor, allowing injection of media_engine
+  // and device_manager.
+  MediaSessionClient(const buzz::Jid& jid, SessionManager *manager,
+                     MediaEngineInterface* media_engine,
+                     DataEngineInterface* data_media_engine,
+                     DeviceManagerInterface* device_manager);
+  ~MediaSessionClient();
+
+  const buzz::Jid &jid() const { return jid_; }
+  SessionManager* session_manager() const { return session_manager_; }
+  ChannelManager* channel_manager() const { return channel_manager_; }
+
+  // Return mapping of call ids to Calls.
+  const std::map<uint32, Call *>& calls() const { return calls_; }
+
+  // The settings below combine with the settings on SessionManager to choose
+
+  // whether SDES-SRTP, DTLS-SRTP, or no security should be used. The possible
+  // combinations are shown in the following table. Note that where either DTLS
+  // or SDES is possible, DTLS is preferred. Thus to require either SDES or
+  // DTLS, but not mandate DTLS, set SDES to require and DTLS to enable.
+  //
+  //              | SDES:Disable   | SDES:Enable    | SDES:Require   |
+  // ----------------------------------------------------------------|
+  // DTLS:Disable | No SRTP        | SDES Optional  | SDES Mandatory |
+  // DTLS:Enable  | DTLS Optional  | DTLS/SDES Opt  | DTLS/SDES Mand |
+  // DTLS:Require | DTLS Mandatory | DTLS Mandatory | DTLS Mandatory |
+
+  // Control use of SDES-SRTP.
+  SecurePolicy secure() const { return desc_factory_.secure(); }
+  void set_secure(SecurePolicy s) { desc_factory_.set_secure(s); }
+
+  // Control use of multiple sessions in a call.
+  void set_multisession_enabled(bool multisession_enabled) {
+    multisession_enabled_ = multisession_enabled;
+  }
+
+  int GetCapabilities() { return channel_manager_->GetCapabilities(); }
+
+  Call *CreateCall();
+  void DestroyCall(Call *call);
+
+  Call *GetFocus();
+  void SetFocus(Call *call);
+
+  void JoinCalls(Call *call_to_join, Call *call);
+
+  bool GetAudioInputDevices(std::vector<std::string>* names) {
+    return channel_manager_->GetAudioInputDevices(names);
+  }
+  bool GetAudioOutputDevices(std::vector<std::string>* names) {
+    return channel_manager_->GetAudioOutputDevices(names);
+  }
+  bool GetVideoCaptureDevices(std::vector<std::string>* names) {
+    return channel_manager_->GetVideoCaptureDevices(names);
+  }
+
+  bool SetAudioOptions(const std::string& in_name, const std::string& out_name,
+                       int opts) {
+    return channel_manager_->SetAudioOptions(in_name, out_name, opts);
+  }
+  bool SetOutputVolume(int level) {
+    return channel_manager_->SetOutputVolume(level);
+  }
+  bool SetCaptureDevice(const std::string& cam_device) {
+    return channel_manager_->SetCaptureDevice(cam_device);
+  }
+
+  SessionDescription* CreateOffer(const CallOptions& options) {
+    return desc_factory_.CreateOffer(options, NULL);
+  }
+  SessionDescription* CreateAnswer(const SessionDescription* offer,
+                                   const CallOptions& options) {
+    return desc_factory_.CreateAnswer(offer, options, NULL);
+  }
+
+  sigslot::signal2<Call *, Call *> SignalFocus;
+  sigslot::signal1<Call *> SignalCallCreate;
+  sigslot::signal1<Call *> SignalCallDestroy;
+  sigslot::repeater0<> SignalDevicesChange;
+
+  virtual bool ParseContent(SignalingProtocol protocol,
+                            const buzz::XmlElement* elem,
+                            ContentDescription** content,
+                            ParseError* error);
+  virtual bool IsWritable(SignalingProtocol protocol,
+                          const ContentDescription* content);
+  virtual bool WriteContent(SignalingProtocol protocol,
+                            const ContentDescription* content,
+                            buzz::XmlElement** elem,
+                            WriteError* error);
+
+ private:
+  void Construct();
+  void OnSessionCreate(Session *session, bool received_initiate);
+  void OnSessionState(BaseSession *session, BaseSession::State state);
+  void OnSessionDestroy(Session *session);
+  Session *CreateSession(Call *call);
+  Call *FindCallByRemoteName(const std::string &remote_name);
+
+  buzz::Jid jid_;
+  SessionManager* session_manager_;
+  Call *focus_call_;
+  ChannelManager *channel_manager_;
+  MediaSessionDescriptionFactory desc_factory_;
+  bool multisession_enabled_;
+  std::map<uint32, Call *> calls_;
+
+  // Maintain a mapping of session id to call.
+  typedef std::map<std::string, Call *> SessionMap;
+  SessionMap session_map_;
+
+  friend class Call;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_MEDIA_MEDIASESSIONCLIENT_H_
diff --git a/talk/session/media/mediasessionclient_unittest.cc b/talk/session/media/mediasessionclient_unittest.cc
new file mode 100644
index 0000000..f9b747b
--- /dev/null
+++ b/talk/session/media/mediasessionclient_unittest.cc
@@ -0,0 +1,3310 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+#include <vector>
+
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/media/base/fakemediaengine.h"
+#include "talk/media/devices/fakedevicemanager.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/client/basicportallocator.h"
+#include "talk/session/media/mediasessionclient.h"
+#include "talk/xmllite/xmlbuilder.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmllite/xmlprinter.h"
+#include "talk/xmpp/constants.h"
+
+// 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)
+};
+
+static const cricket::DataCodec kDataCodecs[] = {
+  cricket::DataCodec(127, "google-data", 0)
+};
+
+const std::string kGingleCryptoOffer = \
+    "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1'>   "  \
+    "  <usage/>                                                "  \
+    "  <rtp:crypto tag='145' crypto-suite='AES_CM_128_HMAC_SHA1_32'" \
+    "  key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
+    "  <rtp:crypto tag='51' crypto-suite='AES_CM_128_HMAC_SHA1_80'" \
+    "  key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
+    "</rtp:encryption>                                         ";
+
+// Jingle offer does not have any <usage> element.
+const std::string kJingleCryptoOffer = \
+    "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1'>   "  \
+    "  <rtp:crypto tag='145' crypto-suite='AES_CM_128_HMAC_SHA1_32'" \
+    "  key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
+    "  <rtp:crypto tag='51' crypto-suite='AES_CM_128_HMAC_SHA1_80'" \
+    "  key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
+    "</rtp:encryption>                                         ";
+
+
+const std::string kGingleRequiredCryptoOffer = \
+    "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1' required='true'> "\
+    "  <usage/>                                                "  \
+    "  <rtp:crypto tag='145' crypto-suite='AES_CM_128_HMAC_SHA1_32'" \
+    "  key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
+    "  <rtp:crypto tag='51' crypto-suite='AES_CM_128_HMAC_SHA1_80'" \
+    "  key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
+    "</rtp:encryption>                                         ";
+
+const std::string kJingleRequiredCryptoOffer = \
+    "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1' required='true'> "\
+    "  <rtp:crypto tag='145' crypto-suite='AES_CM_128_HMAC_SHA1_32'" \
+    "  key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
+    "  <rtp:crypto tag='51' crypto-suite='AES_CM_128_HMAC_SHA1_80'" \
+    "  key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
+    "</rtp:encryption>                                         ";
+
+
+const std::string kGingleUnsupportedCryptoOffer = \
+    "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1'>   "  \
+    "  <usage/>                                                "  \
+    "  <rtp:crypto tag='145' crypto-suite='NOT_SUPPORTED_1'" \
+    "  key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
+    "  <rtp:crypto tag='51' crypto-suite='NOT_SUPPORTED_2'" \
+    "  key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
+    "</rtp:encryption>                                         ";
+
+const std::string kJingleUnsupportedCryptoOffer =                 \
+    "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1'>   "  \
+    "  <rtp:crypto tag='145' crypto-suite='NOT_SUPPORTED_1'" \
+    "  key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
+    "  <rtp:crypto tag='51' crypto-suite='NOT_SUPPORTED_2'" \
+    "  key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
+    "</rtp:encryption>                                         ";
+
+
+// With unsupported but with required="true"
+const std::string kGingleRequiredUnsupportedCryptoOffer =         \
+    "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1' required='true'>" \
+    "  <usage/>                                                "  \
+    "  <rtp:crypto tag='145' crypto-suite='NOT_SUPPORTED_1'"      \
+    "  key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
+    "  <rtp:crypto tag='51' crypto-suite='NOT_SUPPORTED_2'" \
+    "  key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
+    "</rtp:encryption>                                         ";
+
+const std::string kJingleRequiredUnsupportedCryptoOffer =                     \
+    "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1' required='true'>" \
+    "  <rtp:crypto tag='145' crypto-suite='NOT_SUPPORTED_1'                 " \
+    "  key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>       " \
+    "  <rtp:crypto tag='51' crypto-suite='NOT_SUPPORTED_2'                  " \
+    "  key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>"        \
+    "</rtp:encryption>                                         ";
+
+const std::string kGingleInitiate(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <session xmlns='http://www.google.com/session' type='initiate'" \
+     "    id='abcdef' initiator='me@domain.com/resource'>             " \
+     "    <description xmlns='http://www.google.com/session/phone'>   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='103' name='ISAC' clockrate='16000' />               " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='104' name='ISAC' clockrate='32000' />               " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='119' name='ISACLC' clockrate='16000' bitrate='40000' />  " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='99' name='speex' clockrate='16000' bitrate='22000' />    " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='97' name='IPCMWB' clockrate='16000' bitrate='80000' />   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='9' name='G722' clockrate='16000' bitrate='64000' /> " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='102' name='iLBC' clockrate='8000' bitrate='13300' />" \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='98' name='speex' clockrate='8000' bitrate='11000' />" \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='3' name='GSM' clockrate='8000' bitrate='13000' />   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='100' name='EG711U' clockrate='8000' bitrate='64000' />   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='101' name='EG711A' clockrate='8000' bitrate='64000' />   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='0' name='PCMU' clockrate='8000' bitrate='64000' />  " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='8' name='PCMA' clockrate='8000' bitrate='64000' />  " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='126' name='CN' clockrate='32000' />                 " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='105' name='CN' clockrate='16000' />                 " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='13' name='CN' clockrate='8000' />                   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='117' name='red' clockrate='8000' />                 " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='106' name='telephone-event' clockrate='8000' />     " \
+     "    </description>                                              " \
+     "  </session>                                                    " \
+     "</iq>                                                           ");
+
+const std::string kJingleInitiate(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'           " \
+     "    to='user@domain.com/resource' type='set' id='123'>            " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'     " \
+     "          sid='abcdef' initiator='me@domain.com/resource'>        " \
+     "    <content name='test audio'>                                   " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
+     "        <payload-type id='103' name='ISAC' clockrate='16000'/>    " \
+     "        <payload-type id='104' name='ISAC' clockrate='32000'/>    " \
+     "        <payload-type                                             " \
+     "          id='119' name='ISACLC' clockrate='16000'>               " \
+     "          <parameter name='bitrate' value='40000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='99' name='speex' clockrate='16000'>                 " \
+     "          <parameter name='bitrate' value='22000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='97' name='IPCMWB' clockrate='16000'>                " \
+     "          <parameter name='bitrate' value='80000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='9' name='G722' clockrate='16000'>                   " \
+     "          <parameter name='bitrate' value='64000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='102' name='iLBC' clockrate='8000'>                  " \
+     "          <parameter name='bitrate' value='13300'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='98' name='speex' clockrate='8000'>                  " \
+     "          <parameter name='bitrate' value='11000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='3' name='GSM' clockrate='8000'>                     " \
+     "          <parameter name='bitrate' value='13000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='100' name='EG711U' clockrate='8000'>                " \
+     "          <parameter name='bitrate' value='64000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='101' name='EG711A' clockrate='8000'>                " \
+     "          <parameter name='bitrate' value='64000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='0' name='PCMU' clockrate='8000'>                    " \
+     "          <parameter name='bitrate' value='64000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='8' name='PCMA' clockrate='8000'>                    " \
+     "          <parameter name='bitrate' value='64000'/>               " \
+     "        </payload-type>                                           " \
+     "        <payload-type                                             " \
+     "          id='126' name='CN' clockrate='32000' />                 " \
+     "        <payload-type                                             " \
+     "          id='105' name='CN' clockrate='16000' />                 " \
+     "        <payload-type                                             " \
+     "          id='13' name='CN' clockrate='8000' />                   " \
+     "        <payload-type                                             " \
+     "          id='117' name='red' clockrate='8000' />                 " \
+     "        <payload-type                                             " \
+     "          id='106' name='telephone-event' clockrate='8000' />     " \
+     "      </description>                                              " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
+     "    </content>                                                    " \
+     "  </jingle>                                                       " \
+     "</iq>                                                             ");
+
+// 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 kJingleVideoInitiateWithRtpData(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'           " \
+     "    to='user@domain.com/resource' type='set' id='123'>            " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'     " \
+     "          sid='abcdef' initiator='me@domain.com/resource'>        " \
+     "    <content name='test audio'>                                   " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
+     "        <payload-type id='103' name='ISAC' clockrate='16000'/>    " \
+     "      </description>                                              " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
+     "    </content>                                                    " \
+     "    <content name='test video'>                                   " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='video'> " \
+     "        <payload-type id='99' name='H264-SVC'>                    " \
+     "          <parameter name='height' value='200'/>                  " \
+     "          <parameter name='width' value='320'/>                   " \
+     "          <parameter name='framerate' value='30'/>                " \
+     "        </payload-type>                                           " \
+     "      </description>                                              " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
+     "    </content>                                                    " \
+     "    <content name='test data'>                                    " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='data'> " \
+     "        <payload-type id='127' name='google-data'/>               " \
+     "        <rtcp-mux/>                                               " \
+     "      </description>                                              " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
+     "    </content>                                                    " \
+     "  </jingle>                                                       " \
+     "</iq>                                                             ");
+
+const std::string kJingleVideoInitiateWithSctpData(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'           " \
+     "    to='user@domain.com/resource' type='set' id='123'>            " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'     " \
+     "          sid='abcdef' initiator='me@domain.com/resource'>        " \
+     "    <content name='test audio'>                                   " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
+     "        <payload-type id='103' name='ISAC' clockrate='16000'/>    " \
+     "      </description>                                              " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
+     "    </content>                                                    " \
+     "    <content name='test video'>                                   " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='video'> " \
+     "        <payload-type id='99' name='H264-SVC'>                    " \
+     "          <parameter name='height' value='200'/>                  " \
+     "          <parameter name='width' value='320'/>                   " \
+     "          <parameter name='framerate' value='30'/>                " \
+     "        </payload-type>                                           " \
+     "      </description>                                              " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
+     "    </content>                                                    " \
+     "    <content name='test data'>                                    " \
+     "      <description xmlns='google:jingle:sctp' media='data'>       " \
+     "        <stream sid='1'/>                                         " \
+     "      </description>                                              " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
+     "    </content>                                                    " \
+     "  </jingle>                                                       " \
+     "</iq>                                                             ");
+
+const std::string kJingleVideoInitiateWithBandwidth(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'           " \
+     "    to='user@domain.com/resource' type='set' id='123'>            " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'     " \
+     "         sid='abcdef' initiator='me@domain.com/resource'>         " \
+     "    <content name='test audio'>                                   " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
+     "        <payload-type id='103' name='ISAC' clockrate='16000'/>    " \
+     "      </description>                                              " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
+     "    </content>                                                    " \
+     "    <content name='test video'>                                   " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='video'> " \
+     "        <payload-type id='99' name='H264-SVC'>                    " \
+     "          <parameter name='height' value='200'/>                  " \
+     "          <parameter name='width' value='320'/>                   " \
+     "          <parameter name='framerate' value='30'/>                " \
+     "        </payload-type>                                           " \
+     "        <bandwidth type='AS'>42</bandwidth>                       " \
+     "      </description>                                              " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
+     "    </content>                                                    " \
+     "  </jingle>                                                       " \
+     "</iq>                                                             ");
+
+const std::string kJingleVideoInitiateWithRtcpMux(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'           " \
+     "    to='user@domain.com/resource' type='set' id='123'>            " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'     " \
+     "         sid='abcdef' initiator='me@domain.com/resource'>         " \
+     "    <content name='test audio'>                                   " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
+     "        <payload-type id='103' name='ISAC' clockrate='16000'/>    " \
+     "      </description>                                              " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
+     "    </content>                                                    " \
+     "    <content name='test video'>                                   " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='video'> " \
+     "        <payload-type id='99' name='H264-SVC'>                    " \
+     "          <parameter name='height' value='200'/>                  " \
+     "          <parameter name='width' value='320'/>                   " \
+     "          <parameter name='framerate' value='30'/>                " \
+     "        </payload-type>                                           " \
+     "        <rtcp-mux/>                                               " \
+     "      </description>                                              " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
+     "    </content>                                                    " \
+     "  </jingle>                                                       " \
+     "</iq>                                                             ");
+
+// Initiate string with a combination of supported and unsupported codecs
+// Should accept the supported ones
+const std::string kGingleInitiateSomeUnsupported(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <session xmlns='http://www.google.com/session' type='initiate'" \
+     "    id='abcdef' initiator='me@domain.com/resource'>             " \
+     "    <description xmlns='http://www.google.com/session/phone'>   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='103' name='ISAC' clockrate='16000' />               " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='97' name='ASDFDS' />                                " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='102' name='1010' />                                 " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='107' name='DFAS' />                                 " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='100' name='EG711U' />                               " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='101' name='EG711A' />                               " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='0' name='PCMU' />                                   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='110' name=':)' />                                   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='13' name='CN' />                                    " \
+     "    </description>                                              " \
+     "  </session>                                                    " \
+     "</iq>                                                           ");
+
+const std::string kJingleInitiateSomeUnsupported(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'   " \
+     "          sid='abcdef' initiator='me@domain.com/resource'>      " \
+     "    <content name='test audio'>                                 " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
+     "        <payload-type                                           " \
+     "          id='103' name='ISAC' clockrate='16000' />             " \
+     "        <payload-type                                           " \
+     "          id='97' name='ASDFDS' />                              " \
+     "        <payload-type                                           " \
+     "          id='102' name='1010' />                               " \
+     "        <payload-type                                           " \
+     "          id='107' name='DFAS' />                               " \
+     "        <payload-type                                           " \
+     "          id='100' name='EG711U' />                             " \
+     "        <payload-type                                           " \
+     "          id='101' name='EG711A' />                             " \
+     "        <payload-type                                           " \
+     "          id='0' name='PCMU' />                                 " \
+     "        <payload-type                                           " \
+     "          id='110' name=':)' />                                 " \
+     "        <payload-type                                           " \
+     "          id='13' name='CN' />                                  " \
+     "      </description>                                            " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
+     "    </content>                                                  " \
+     "  </jingle>                                                     " \
+     "</iq>                                                           ");
+
+const std::string kGingleVideoInitiateWithBandwidth(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <session xmlns='http://www.google.com/session' type='initiate'" \
+     "    id='abcdef' initiator='me@domain.com/resource'>             " \
+     "    <description xmlns='http://www.google.com/session/video'>   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='103' name='ISAC' clockrate='16000' />               " \
+     "      <payload-type xmlns='http://www.google.com/session/video' " \
+     "        id='99' name='H264-SVC' framerate='30'                  " \
+     "        height='200' width='320'/>                              " \
+     "      <bandwidth type='AS'>42</bandwidth>                       " \
+     "    </description>                                              " \
+     "  </session>                                                    " \
+     "</iq>                                                           ");
+
+// Initiate string without any supported codecs. Should send a reject.
+const std::string kGingleInitiateNoSupportedAudioCodecs(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <session xmlns='http://www.google.com/session' type='initiate'" \
+     "    id='abcdef' initiator='me@domain.com/resource'>             " \
+     "    <description xmlns='http://www.google.com/session/phone'>   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='123' name='Supercodec6000' />                       " \
+     "    </description>                                              " \
+     "  </session>                                                    " \
+     "</iq>                                                           ");
+
+const std::string kJingleInitiateNoSupportedAudioCodecs(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'   " \
+     "          sid='abcdef' initiator='me@domain.com/resource'>      " \
+     "    <content name='test audio'>                                 " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
+     "        <payload-type                                           " \
+     "          id='123' name='Supercodec6000' />                     " \
+     "      </description>                                            " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>  " \
+     "    </content>                                                  " \
+     "  </jingle>                                                     " \
+     "</iq>                                                           ");
+
+// Initiate string without any codecs. Assumes ancient version of Cricket
+// and tries a session with ISAC and PCMU
+const std::string kGingleInitiateNoAudioCodecs(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <session xmlns='http://www.google.com/session' type='initiate'" \
+     "    id='abcdef' initiator='me@domain.com/resource'>             " \
+     "    <description xmlns='http://www.google.com/session/phone'>   " \
+     "    </description>                                              " \
+     "  </session>                                                    " \
+     "</iq>                                                           ");
+
+const std::string kJingleInitiateNoAudioCodecs(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'   " \
+     "          sid='abcdef' initiator='me@domain.com/resource'>      " \
+     "    <content name='test audio'>                                 " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
+     "      </description>                                            " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>  " \
+     "    </content>                                                  " \
+     "  </jingle>                                                     " \
+     "</iq>                                                           ");
+
+// The codecs are supported, but not at the given clockrates. Should send
+// a reject.
+const std::string kGingleInitiateWrongClockrates(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <session xmlns='http://www.google.com/session' type='initiate'" \
+     "    id='abcdef' initiator='me@domain.com/resource'>             " \
+     "    <description xmlns='http://www.google.com/session/phone'>   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='103' name='ISAC' clockrate='8000'/>                 " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='97' name='IPCMWB' clockrate='1337'/>                " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='102' name='iLBC' clockrate='1982' />                " \
+     "    </description>                                              " \
+     "  </session>                                                    " \
+     "</iq>                                                           ");
+
+const std::string kJingleInitiateWrongClockrates(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'           " \
+     "    to='user@domain.com/resource' type='set' id='123'>            " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'     " \
+     "          sid='abcdef' initiator='me@domain.com/resource'>        " \
+     "    <content name='test audio'>                                   " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
+     "        <payload-type                                             " \
+     "          id='103' name='ISAC' clockrate='8000'/>                 " \
+     "        <payload-type                                             " \
+     "          id='97' name='IPCMWB' clockrate='1337'/>                " \
+     "       <payload-type                                              " \
+     "          id='102' name='iLBC' clockrate='1982' />                " \
+     "      </description>                                              " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>  " \
+     "    </content>                                                    " \
+     "  </jingle>                                                       " \
+     "</iq>                                                             ");
+
+// The codecs are supported, but not with the given number of channels.
+// Should send a reject.
+const std::string kGingleInitiateWrongChannels(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <session xmlns='http://www.google.com/session' type='initiate'" \
+     "    id='abcdef' initiator='me@domain.com/resource'>             " \
+     "    <description xmlns='http://www.google.com/session/phone'>   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='103' name='ISAC' channels='2'/>                     " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='97' name='IPCMWB' channels='3'/>                    " \
+     "    </description>                                              " \
+     "  </session>                                                    " \
+     "</iq>                                                           ");
+
+const std::string kJingleInitiateWrongChannels(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'           " \
+     "    to='user@domain.com/resource' type='set' id='123'>            " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'>    " \
+     "    <content name='test audio'>                                   " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
+     "        <payload-type                                             " \
+     "          id='103' name='ISAC' channels='2'/>                     " \
+     "        <payload-type                                             " \
+     "          id='97' name='IPCMWB' channels='3'/>                    " \
+     "      </description>                                              " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
+     "    </content>                                                    " \
+     "  </jingle>                                                       " \
+     "</iq>                                                             ");
+
+// Initiate with a dynamic codec not using webrtc default payload id. Should
+// accept with provided payload id.
+const std::string kGingleInitiateDynamicAudioCodecs(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <session xmlns='http://www.google.com/session' type='initiate'" \
+     "    id='abcdef' initiator='me@domain.com/resource'>             " \
+     "    <description xmlns='http://www.google.com/session/phone'>   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='123' name='speex' clockrate='16000'/>               " \
+     "    </description>                                              " \
+     "  </session>                                                    " \
+     "</iq>                                                           ");
+
+const std::string kJingleInitiateDynamicAudioCodecs(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'           " \
+     "    to='user@domain.com/resource' type='set' id='123'>            " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'     " \
+     "          sid='abcdef' initiator='me@domain.com/resource'>        " \
+     "    <content name='test audio'>                                   " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
+     "        <payload-type                                             " \
+     "          id='123' name='speex' clockrate='16000'/>               " \
+     "      </description>                                              " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>   " \
+     "    </content>                                                    " \
+     "  </jingle>                                                       " \
+     "</iq>                                                             ");
+
+// Initiate string with nothing but static codec id's. Should accept.
+const std::string kGingleInitiateStaticAudioCodecs(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <session xmlns='http://www.google.com/session' type='initiate'" \
+     "    id='abcdef' initiator='me@domain.com/resource'>             " \
+     "    <description xmlns='http://www.google.com/session/phone'>   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='3' />                                               " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='0' />                                               " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='8' />                                               " \
+     "    </description>                                              " \
+     "  </session>                                                    " \
+     "</iq>                                                           ");
+
+const std::string kJingleInitiateStaticAudioCodecs(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'   " \
+     "          sid='abcdef' initiator='me@domain.com/resource'>      " \
+     "    <content name='test audio'>                                 " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
+     "        <payload-type id='3' />                                 " \
+     "        <payload-type id='0' />                                 " \
+     "        <payload-type id='8' />                                 " \
+     "      </description>                                            " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
+     "    </content>                                                  " \
+     "  </jingle>                                                     " \
+     "</iq>                                                           ");
+
+// Initiate with payload type-less codecs. Should reject.
+const std::string kGingleInitiateNoPayloadTypes(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <session xmlns='http://www.google.com/session' type='initiate'" \
+     "    id='abcdef' initiator='me@domain.com/resource'>             " \
+     "    <description xmlns='http://www.google.com/session/phone'>   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "       name='ISAC' clockrate='16000'/>                          " \
+     "    </description>                                              " \
+     "  </session>                                                    " \
+     "</iq>                                                           ");
+
+const std::string kJingleInitiateNoPayloadTypes(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'>  " \
+     "          sid='abcdef' initiator='me@domain.com/resource'>      " \
+     "    <content name='test audio'>                                 " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
+     "        <payload-type  name='ISAC' clockrate='16000'/>          " \
+     "      </description>                                            " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
+     "    </content>                                                  " \
+     "  </jingle>                                                     " \
+     "</iq>                                                           ");
+
+// Initiate with unnamed dynamic codces. Should reject.
+const std::string kGingleInitiateDynamicWithoutNames(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <session xmlns='http://www.google.com/session' type='initiate'" \
+     "    id='abcdef' initiator='me@domain.com/resource'>             " \
+     "    <description xmlns='http://www.google.com/session/phone'>   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "       id='100' clockrate='16000'/>                             " \
+     "    </description>                                              " \
+     "  </session>                                                    " \
+     "</iq>                                                           ");
+
+const std::string kJingleInitiateDynamicWithoutNames(
+     "<iq xmlns='jabber:client' from='me@domain.com/resource'         " \
+     "    to='user@domain.com/resource' type='set' id='123'>          " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'>  " \
+     "          sid='abcdef' initiator='me@domain.com/resource'>      " \
+     "    <content name='test audio'>                                 " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
+     "        <payload-type id='100' clockrate='16000'/>              " \
+     "      </description>                                            " \
+     "     <transport xmlns=\"http://www.google.com/transport/p2p\"/>  " \
+     "    </content>                                                  " \
+     "  </jingle>                                                     " \
+     "</iq>                                                           ");
+
+const uint32 kAudioSsrc = 4294967295U;
+const uint32 kVideoSsrc = 87654321;
+const uint32 kDataSsrc = 1010101;
+const uint32 kDataSid = 0;
+// Note that this message does not specify a session ID. It must be populated
+// before use.
+const std::string kGingleAcceptWithSsrcs(
+     "<iq xmlns='jabber:client' from='me@mydomain.com'                " \
+     "    to='user@domain.com/resource' type='set' id='150'>          " \
+     "  <session xmlns='http://www.google.com/session' type='accept'  " \
+     "    initiator='me@domain.com/resource'>                         " \
+     "    <description xmlns='http://www.google.com/session/video'>   " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='103' name='ISAC' clockrate='16000' />               " \
+     "      <payload-type xmlns='http://www.google.com/session/phone' " \
+     "        id='104' name='ISAC' clockrate='32000' />               " \
+     "      <src-id xmlns='http://www.google.com/session/phone'>      " \
+     "        4294967295</src-id>                                       " \
+     "      <src-id>87654321</src-id>                                 " \
+     "    </description>                                              " \
+     "  </session>                                                    " \
+     "</iq>                                                           ");
+
+const std::string kJingleAcceptWithSsrcs(
+     "<iq xmlns='jabber:client' from='me@mydomain.com'                " \
+     "    to='user@domain.com/resource' type='set' id='150'>          " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-accept'     " \
+     "          initiator='me@domain.com/resource'>                   " \
+     "    <content name='audio'>                                      " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1'           " \
+     "          media='audio' ssrc='4294967295'>                      " \
+     "        <payload-type id='103' name='ISAC' clockrate='16000'/>  " \
+     "        <payload-type id='104' name='ISAC' clockrate='32000'/>  " \
+     "      </description>                                            " \
+     "     <transport xmlns='http://www.google.com/transport/p2p'/>   " \
+     "    </content>                                                  " \
+     "    <content name='video'>                                      " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1'           " \
+     "          media='video' ssrc='87654321'>                        " \
+     "      </description>                                            " \
+     "     <transport xmlns='http://www.google.com/transport/p2p'/>   " \
+     "    </content>                                                  " \
+     "  </jingle>                                                     " \
+     "</iq>                                                           ");
+
+const std::string kJingleAcceptWithRtpDataSsrcs(
+     "<iq xmlns='jabber:client' from='me@mydomain.com'                " \
+     "    to='user@domain.com/resource' type='set' id='150'>          " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-accept'     " \
+     "          initiator='me@domain.com/resource'>                   " \
+     "    <content name='audio'>                                      " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1'           " \
+     "          media='audio' ssrc='4294967295'>                      " \
+     "        <payload-type id='103' name='ISAC' clockrate='16000'/>  " \
+     "        <payload-type id='104' name='ISAC' clockrate='32000'/>  " \
+     "      </description>                                            " \
+     "     <transport xmlns='http://www.google.com/transport/p2p'/>   " \
+     "    </content>                                                  " \
+     "    <content name='video'>                                      " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1'           " \
+     "          media='video' ssrc='87654321'>                        " \
+     "      </description>                                            " \
+     "     <transport xmlns='http://www.google.com/transport/p2p'/>   " \
+     "    </content>                                                  " \
+     "    <content name='data'>                                       " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1'           " \
+     "          media='data' ssrc='1010101'>                          " \
+     "      </description>                                            " \
+     "     <transport xmlns='http://www.google.com/transport/p2p'/>   " \
+     "    </content>                                                  " \
+     "  </jingle>                                                     " \
+     "</iq>                                                           ");
+
+const std::string kJingleAcceptWithSctpData(
+     "<iq xmlns='jabber:client' from='me@mydomain.com'                " \
+     "    to='user@domain.com/resource' type='set' id='150'>          " \
+     "  <jingle xmlns='urn:xmpp:jingle:1' action='session-accept'     " \
+     "          initiator='me@domain.com/resource'>                   " \
+     "    <content name='audio'>                                      " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1'           " \
+     "          media='audio' ssrc='4294967295'>                      " \
+     "        <payload-type id='103' name='ISAC' clockrate='16000'/>  " \
+     "        <payload-type id='104' name='ISAC' clockrate='32000'/>  " \
+     "      </description>                                            " \
+     "     <transport xmlns='http://www.google.com/transport/p2p'/>   " \
+     "    </content>                                                  " \
+     "    <content name='video'>                                      " \
+     "      <description xmlns='urn:xmpp:jingle:apps:rtp:1'           " \
+     "          media='video' ssrc='87654321'>                        " \
+     "      </description>                                            " \
+     "     <transport xmlns='http://www.google.com/transport/p2p'/>   " \
+     "    </content>                                                  " \
+     "    <content name='data'>                                       " \
+     "      <description xmlns='google:jingle:sctp'>                  " \
+     "        <stream sid='1'/>                                       " \
+     "      </description>                                            " \
+     "     <transport xmlns='http://www.google.com/transport/p2p'/>   " \
+     "    </content>                                                  " \
+     "  </jingle>                                                     " \
+     "</iq>                                                           ");
+
+std::string JingleView(const std::string& ssrc,
+                       const std::string& width,
+                       const std::string& height,
+                       const std::string& framerate) {
+  // We have some slightly weird whitespace formatting to make the
+  // actual XML generated match the expected XML here.
+  return \
+      "<cli:iq"
+      "  to='me@mydomain.com'"
+      "  type='set'"
+      "  xmlns:cli='jabber:client'>"
+        "<jingle"
+      "    xmlns='urn:xmpp:jingle:1'"
+      "    action='session-info'"
+      "    sid=''>"
+          "<view xmlns='google:jingle'"
+      "      name='video'"
+      "      type='static'"
+      "      ssrc='" + ssrc + "'>"
+            "<params"
+      "        width='" + width + "'"
+      "        height='" + height + "'"
+      "        framerate='" + framerate + "'"
+      "        preference='0'/>"
+          "</view>"
+        "</jingle>"
+      "</cli:iq>";
+}
+
+std::string JingleStreamAdd(const std::string& content_name,
+                            const std::string& nick,
+                            const std::string& name,
+                            const std::string& ssrc) {
+  return \
+      "<iq"
+      "  xmlns='jabber:client'"
+      "  from='me@mydomain.com'"
+      "  to='user@domain.com/resource'"
+      "  type='set'"
+      "  id='150'>"
+      "  <jingle"
+      "    xmlns='urn:xmpp:jingle:1'"
+      "    action='description-info'>"
+      "    <content"
+      "      xmlns='urn:xmpp:jingle:1'"
+      "      name='" + content_name + "'>"
+      "      <description"
+      "        xmlns='urn:xmpp:jingle:apps:rtp:1'"
+      "        media='" + content_name + "'>"
+      "        <streams"
+      "          xmlns='google:jingle'>"
+      "          <stream"
+      "            nick='" + nick + "'"
+      "            name='" + name + "'>"
+      "            <ssrc>"  + ssrc + "</ssrc>"
+      "          </stream>"
+      "        </streams>"
+      "      </description>"
+      "    </content>"
+      "  </jingle>"
+      "</iq>";
+}
+
+std::string JingleOutboundStreamRemove(const std::string& sid,
+                                       const std::string& content_name,
+                                       const std::string& name) {
+  return \
+      "<cli:iq"
+      " to='me@mydomain.com'"
+      " type='set'"
+      " xmlns:cli='jabber:client'>"
+      "<jingle"
+      " xmlns='urn:xmpp:jingle:1'"
+      " action='description-info'"
+      " sid='" + sid + "'>"
+      "<content"
+      " name='" + content_name + "'"
+      " creator='initiator'>"
+      "<description"
+      " xmlns='urn:xmpp:jingle:apps:rtp:1'"
+      " media='" + content_name + "'>"
+      "<streams"
+      " xmlns='google:jingle'>"
+      "<stream"
+      " name='" + name + "'>"
+      "</stream>"
+      "</streams>"
+      "</description>"
+      "</content>"
+      "</jingle>"
+      "</cli:iq>";
+}
+
+std::string JingleOutboundStreamAdd(const std::string& sid,
+                                    const std::string& content_name,
+                                    const std::string& name,
+                                    const std::string& ssrc) {
+  return \
+      "<cli:iq"
+      " to='me@mydomain.com'"
+      " type='set'"
+      " xmlns:cli='jabber:client'>"
+      "<jingle"
+      " xmlns='urn:xmpp:jingle:1'"
+      " action='description-info'"
+      " sid='" + sid + "'>"
+      "<content"
+      " name='" + content_name + "'"
+      " creator='initiator'>"
+      "<description"
+      " xmlns='urn:xmpp:jingle:apps:rtp:1'"
+      " media='" + content_name + "'>"
+      "<streams"
+      " xmlns='google:jingle'>"
+      "<stream"
+      " name='" + name + "'>"
+      "<ssrc>" + ssrc + "</ssrc>"
+      "</stream>"
+      "</streams>"
+      "</description>"
+      "</content>"
+      "</jingle>"
+      "</cli:iq>";
+}
+
+std::string JingleStreamAddWithoutSsrc(const std::string& content_name,
+                                       const std::string& nick,
+                                       const std::string& name) {
+  return \
+      "<iq"
+      "  xmlns='jabber:client'"
+      "  from='me@mydomain.com'"
+      "  to='user@domain.com/resource'"
+      "  type='set'"
+      "  id='150'>"
+      "  <jingle"
+      "    xmlns='urn:xmpp:jingle:1'"
+      "    action='description-info'>"
+      "    <content"
+      "      xmlns='urn:xmpp:jingle:1'"
+      "      name='" + content_name + "'>"
+      "      <description"
+      "        xmlns='urn:xmpp:jingle:apps:rtp:1'"
+      "        media='" + content_name + "'>"
+      "        <streams"
+      "          xmlns='google:jingle'>"
+      "          <stream"
+      "            nick='" + nick + "'"
+      "            name='" + name + "'>"
+       "          </stream>"
+      "        </streams>"
+      "      </description>"
+      "    </content>"
+      "  </jingle>"
+      "</iq>";
+}
+
+std::string JingleStreamRemove(const std::string& content_name,
+                               const std::string& nick,
+                               const std::string& name) {
+  return \
+      "<iq"
+      "  xmlns='jabber:client'"
+      "  from='me@mydomain.com'"
+      "  to='user@domain.com/resource'"
+      "  type='set'"
+      "  id='150'>"
+      "  <jingle"
+      "    xmlns='urn:xmpp:jingle:1'"
+      "    action='description-info'>"
+      "    <content"
+      "      xmlns='urn:xmpp:jingle:1'"
+      "      name='" + content_name + "'>"
+      "      <description"
+      "        xmlns='urn:xmpp:jingle:apps:rtp:1'"
+      "        media='" + content_name + "'>"
+      "        <streams"
+      "          xmlns='google:jingle'>"
+      "          <stream"
+      "            nick='" + nick + "'"
+      "            name='" + name + "'/>"
+      "        </streams>"
+      "      </description>"
+      "    </content>"
+      "  </jingle>"
+      "</iq>";
+}
+
+// Convenience function to get CallOptions that have audio enabled,
+// but not video or data.
+static cricket::CallOptions AudioCallOptions() {
+  cricket::CallOptions options;
+  options.has_audio = true;
+  options.has_video = false;
+  options.data_channel_type = cricket::DCT_NONE;
+  return options;
+}
+
+// Convenience function to get CallOptions that have audio and video
+// enabled, but not data.
+static cricket::CallOptions VideoCallOptions() {
+  cricket::CallOptions options;
+  options.has_audio = true;
+  options.has_video = true;
+  options.data_channel_type = cricket::DCT_NONE;
+  return options;
+}
+
+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 cricket::DataCodec DataCodecFromPayloadType(
+      const buzz::XmlElement* payload_type) = 0;
+  virtual buzz::XmlElement* EncryptionFromContent(
+      buzz::XmlElement* content) = 0;
+  virtual buzz::XmlElement* NextFromEncryption(
+      buzz::XmlElement* encryption) = 0;
+  virtual const buzz::XmlElement* BandwidthFromContent(
+      buzz::XmlElement* content) = 0;
+  virtual const buzz::XmlElement* RtcpMuxFromContent(
+      buzz::XmlElement* content) = 0;
+  virtual bool ActionIsTerminate(const buzz::XmlElement* action) = 0;
+  virtual ~MediaSessionTestParser() {}
+};
+
+class JingleSessionTestParser : public MediaSessionTestParser {
+ public:
+  JingleSessionTestParser() : 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);
+  }
+
+  cricket::DataCodec DataCodecFromPayloadType(
+      const buzz::XmlElement* payload_type) {
+    int id = 0;
+    if (payload_type->HasAttr(cricket::QN_ID))
+      id = atoi(payload_type->Attr(cricket::QN_ID).c_str());
+
+    std::string name;
+    if (payload_type->HasAttr(cricket::QN_NAME))
+      name = payload_type->Attr(cricket::QN_NAME);
+
+    return cricket::DataCodec(id, name, 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);
+  }
+
+  cricket::DataCodec DataCodecFromPayloadType(
+      const buzz::XmlElement* payload_type) {
+    // Gingle can't do data codecs.
+    return cricket::DataCodec(0, "", 0);
+  }
+
+  buzz::XmlElement* EncryptionFromContent(
+      buzz::XmlElement* content) {
+    return content->FirstNamed(cricket::QN_ENCRYPTION);
+  }
+
+  buzz::XmlElement* NextFromEncryption(buzz::XmlElement* encryption) {
+    return encryption->NextNamed(cricket::QN_ENCRYPTION);
+  }
+
+  const buzz::XmlElement* BandwidthFromContent(buzz::XmlElement* content) {
+    return content->FirstNamed(cricket::QN_GINGLE_VIDEO_BANDWIDTH);
+  }
+
+  const buzz::XmlElement* RtcpMuxFromContent(buzz::XmlElement* content) {
+    return NULL;
+  }
+
+  bool ActionIsTerminate(const buzz::XmlElement* session) {
+    return (session->HasAttr(buzz::QN_TYPE) &&
+            session->Attr(buzz::QN_TYPE) == "terminate");
+  }
+
+  int found_content_count_;
+};
+
+class MediaSessionClientTest : public sigslot::has_slots<> {
+ public:
+  explicit MediaSessionClientTest(MediaSessionTestParser* parser,
+                                  cricket::SignalingProtocol initial_protocol) {
+    nm_ = new talk_base::BasicNetworkManager();
+    pa_ = new cricket::BasicPortAllocator(nm_);
+    sm_ = new cricket::SessionManager(pa_, NULL);
+    fme_ = new cricket::FakeMediaEngine();
+    fdme_ = new cricket::FakeDataEngine();
+
+    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);
+    std::vector<cricket::DataCodec>
+        data_codecs(kDataCodecs, kDataCodecs + ARRAY_SIZE(kDataCodecs));
+    fdme_->SetDataCodecs(data_codecs);
+
+    client_ = new cricket::MediaSessionClient(
+        buzz::Jid("user@domain.com/resource"), sm_,
+        fme_, fdme_, new cricket::FakeDeviceManager());
+    client_->session_manager()->SignalOutgoingMessage.connect(
+        this, &MediaSessionClientTest::OnSendStanza);
+    client_->session_manager()->SignalSessionCreate.connect(
+        this, &MediaSessionClientTest::OnSessionCreate);
+    client_->SignalCallCreate.connect(
+        this, &MediaSessionClientTest::OnCallCreate);
+    client_->SignalCallDestroy.connect(
+        this, &MediaSessionClientTest::OnCallDestroy);
+
+    call_ = NULL;
+    parser_ = parser;
+    initial_protocol_ = initial_protocol;
+    expect_incoming_crypto_ = false;
+    expect_outgoing_crypto_ = false;
+    expected_video_bandwidth_ = cricket::kAutoBandwidth;
+    expected_video_rtcp_mux_ = false;
+  }
+
+  ~MediaSessionClientTest() {
+    delete client_;
+    delete sm_;
+    delete pa_;
+    delete nm_;
+    delete parser_;
+  }
+
+  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());
+    }
+  }
+
+  virtual void CheckRtpDataContent(buzz::XmlElement* content) {
+    if (initial_protocol_) {
+      // Gingle can not write out data content.
+      return;
+    }
+
+    buzz::XmlElement* e = PayloadTypeFromContent(content);
+    ASSERT_TRUE(e != NULL);
+    cricket::DataCodec codec = parser_->DataCodecFromPayloadType(e);
+    EXPECT_EQ(127, codec.id);
+    EXPECT_EQ("google-data", codec.name);
+
+    CheckDataRtcpMux(true, call_->sessions()[0]->local_description());
+    CheckDataRtcpMux(true, call_->sessions()[0]->remote_description());
+    if (expect_outgoing_crypto_) {
+      content = parser_->NextContent(content);
+      buzz::XmlElement* encryption = EncryptionFromContent(content);
+      ASSERT_TRUE(encryption != NULL);
+      // TODO(pthatcher): Check encryption parameters?
+    }
+  }
+
+  virtual void CheckSctpDataContent(buzz::XmlElement* content) {
+    if (initial_protocol_) {
+      // Gingle can not write out data content.
+      return;
+    }
+
+    buzz::XmlElement* payload_type = PayloadTypeFromContent(content);
+    ASSERT_TRUE(payload_type == NULL);
+    buzz::XmlElement* encryption = EncryptionFromContent(content);
+    ASSERT_TRUE(encryption == NULL);
+    // TODO(pthatcher): Check for <streams>.
+  }
+
+  void CheckDataRtcpMux(bool expected_data_rtcp_mux,
+                        const cricket::SessionDescription* sdesc) {
+    const cricket::DataContentDescription* data =
+        GetFirstDataContentDescription(sdesc);
+    if (data != NULL) {
+      ASSERT_EQ(expected_data_rtcp_mux, data->rtcp_mux());
+    }
+  }
+
+  void CheckAudioSsrcForIncomingAccept(const cricket::Session* session) {
+    const 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 CheckDataSsrcForIncomingAccept(const cricket::Session* session) {
+    const cricket::DataContentDescription* data =
+        GetFirstDataContentDescription(session->remote_description());
+    ASSERT_TRUE(data != NULL);
+    ASSERT_EQ(kDataSsrc, data->first_ssrc());
+  }
+
+  void TestGoodIncomingInitiate(const std::string& initiate_string,
+                                const cricket::CallOptions& options,
+                                buzz::XmlElement** element) {
+    *element = NULL;
+
+    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(pthatcher): Add tests for sending <bandwidth> in accept.
+    call_->AcceptSession(call_->sessions()[0], options);
+    ASSERT_EQ(cricket::Session::STATE_SENTACCEPT,
+              call_->sessions()[0]->state());
+    ASSERT_EQ(1U, stanzas_.size());
+    ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
+    ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
+    ASSERT_EQ(std::string(buzz::STR_SET), stanzas_[0]->Attr(buzz::QN_TYPE));
+
+    buzz::XmlElement* e = ActionFromStanza(stanzas_[0]);
+    ASSERT_TRUE(e != NULL);
+    ASSERT_TRUE(ContentFromAction(e) != NULL);
+    *element = CopyElement(ContentFromAction(e));
+    ASSERT_TRUE(*element != NULL);
+    delete stanzas_[0];
+    stanzas_.clear();
+    if (expect_outgoing_crypto_) {
+      CheckCryptoForGoodOutgoingAccept(call_->sessions()[0]);
+    }
+
+  if (options.data_channel_type == cricket::DCT_RTP) {
+      CheckDataRtcpMux(true, call_->sessions()[0]->local_description());
+      CheckDataRtcpMux(true, call_->sessions()[0]->remote_description());
+      // TODO(pthatcher): Check rtcpmux and crypto?
+    }
+
+    call_->Terminate();
+    ASSERT_EQ(cricket::Session::STATE_SENTTERMINATE,
+              call_->sessions()[0]->state());
+    ASSERT_EQ(1U, stanzas_.size());
+    ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
+    ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
+    ASSERT_EQ(std::string(buzz::STR_SET), stanzas_[0]->Attr(buzz::QN_TYPE));
+    e = ActionFromStanza(stanzas_[0]);
+    ASSERT_TRUE(e != NULL);
+    ASSERT_TRUE(parser_->ActionIsTerminate(e));
+    delete stanzas_[0];
+    stanzas_.clear();
+  }
+
+  void TestRejectOffer(const std::string &initiate_string,
+                       const cricket::CallOptions& options,
+                       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();
+
+    call_->AcceptSession(call_->sessions()[0], options);
+    ASSERT_EQ(cricket::Session::STATE_SENTACCEPT,
+              call_->sessions()[0]->state());
+    ASSERT_EQ(1U, stanzas_.size());
+    ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
+    ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
+    ASSERT_EQ(std::string(buzz::STR_SET), stanzas_[0]->Attr(buzz::QN_TYPE));
+
+    buzz::XmlElement* e = ActionFromStanza(stanzas_[0]);
+    ASSERT_TRUE(e != NULL);
+    ASSERT_TRUE(ContentFromAction(e) != NULL);
+    *element = CopyElement(ContentFromAction(e));
+    ASSERT_TRUE(*element != NULL);
+    delete stanzas_[0];
+    stanzas_.clear();
+
+    buzz::XmlElement* content = *element;
+    // The NextContent method actually returns the second content. So we
+    // can't handle the case when audio, video and data are all enabled. But
+    // since we are testing rejection, it won't be the case.
+    if (options.has_audio) {
+      ASSERT_TRUE(content != NULL);
+      ASSERT_EQ("test audio", content->Attr(buzz::QName("", "name")));
+      content = parser_->NextContent(content);
+    }
+
+    if (options.has_video) {
+      ASSERT_TRUE(content != NULL);
+      ASSERT_EQ("test video", content->Attr(buzz::QName("", "name")));
+      content = parser_->NextContent(content);
+    }
+
+    if (options.has_data()) {
+      ASSERT_TRUE(content != NULL);
+      ASSERT_EQ("test data", content->Attr(buzz::QName("", "name")));
+      content = parser_->NextContent(content);
+    }
+
+    call_->Terminate();
+    ASSERT_EQ(cricket::Session::STATE_SENTTERMINATE,
+              call_->sessions()[0]->state());
+    ASSERT_EQ(1U, stanzas_.size());
+    ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
+    ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
+    ASSERT_EQ(std::string(buzz::STR_SET), stanzas_[0]->Attr(buzz::QN_TYPE));
+    e = ActionFromStanza(stanzas_[0]);
+    ASSERT_TRUE(e != NULL);
+    ASSERT_TRUE(parser_->ActionIsTerminate(e));
+    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(const cricket::CallOptions& options) {
+    client_->CreateCall();
+    ASSERT_TRUE(call_ != NULL);
+    call_->InitiateSession(buzz::Jid("me@mydomain.com"),
+                           buzz::Jid("me@mydomain.com"), options);
+    ASSERT_TRUE(call_->sessions()[0] != NULL);
+    ASSERT_EQ(cricket::Session::STATE_SENTINITIATE,
+              call_->sessions()[0]->state());
+    ASSERT_EQ(1U, stanzas_.size());
+    ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
+    ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
+    ASSERT_EQ(std::string(buzz::STR_SET), stanzas_[0]->Attr(buzz::QN_TYPE));
+    buzz::XmlElement* action = ActionFromStanza(stanzas_[0]);
+    ASSERT_TRUE(action != NULL);
+    buzz::XmlElement* content = ContentFromAction(action);
+    ASSERT_TRUE(content != NULL);
+
+    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());
+      }
+    }
+
+    if (options.data_channel_type == cricket::DCT_RTP) {
+      content = parser_->NextContent(content);
+      CheckRtpDataContent(content);
+    }
+
+    if (options.data_channel_type == cricket::DCT_SCTP) {
+      content = parser_->NextContent(content);
+      CheckSctpDataContent(content);
+    }
+
+    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,
+      cricket::CallOptions& options) {
+    client_->CreateCall();
+    ASSERT_TRUE(call_ != NULL);
+
+    call_->InitiateSession(buzz::Jid("me@mydomain.com"),
+                           buzz::Jid("me@mydomain.com"), options);
+    ASSERT_TRUE(call_->sessions()[0] != NULL);
+    ASSERT_EQ(cricket::Session::STATE_SENTINITIATE,
+              call_->sessions()[0]->state());
+    ASSERT_EQ(1U, stanzas_.size());
+    ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
+    ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
+    ASSERT_EQ(std::string(buzz::STR_SET), stanzas_[0]->Attr(buzz::QN_TYPE));
+    buzz::XmlElement* action = ActionFromStanza(stanzas_[0]);
+    ASSERT_TRUE(action != NULL);
+    buzz::XmlElement* content = ContentFromAction(action);
+    ASSERT_TRUE(content != NULL);
+    if (initial_protocol_ == cricket::PROTOCOL_JINGLE) {
+      buzz::XmlElement* content_desc =
+          content->FirstNamed(cricket::QN_JINGLE_RTP_CONTENT);
+      ASSERT_TRUE(content_desc != NULL);
+      ASSERT_EQ("", content_desc->Attr(cricket::QN_SSRC));
+    }
+    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]);
+    if (options.data_channel_type == cricket::DCT_RTP) {
+      CheckDataSsrcForIncomingAccept(call_->sessions()[0]);
+    }
+    // TODO(pthatcher): Check kDataSid if DCT_SCTP.
+  }
+
+  size_t ClearStanzas() {
+    size_t size = stanzas_.size();
+    for (size_t i = 0; i < size; i++) {
+      delete stanzas_[i];
+    }
+    stanzas_.clear();
+    return size;
+  }
+
+  buzz::XmlElement* SetJingleSid(buzz::XmlElement* stanza) {
+    buzz::XmlElement* jingle =
+        stanza->FirstNamed(cricket::QN_JINGLE);
+    jingle->SetAttr(cricket::QN_SID, call_->sessions()[0]->id());
+    return stanza;
+  }
+
+  void TestSendVideoStreamUpdate() {
+    cricket::CallOptions options = VideoCallOptions();
+    options.is_muc = true;
+
+    client_->CreateCall();
+    call_->InitiateSession(buzz::Jid("me@mydomain.com"),
+                           buzz::Jid("me@mydomain.com"), options);
+    ClearStanzas();
+
+    cricket::StreamParams stream;
+    stream.id = "test-stream";
+    stream.ssrcs.push_back(1001);
+    talk_base::scoped_ptr<buzz::XmlElement> expected_stream_add(
+        buzz::XmlElement::ForStr(
+            JingleOutboundStreamAdd(
+                call_->sessions()[0]->id(),
+                "video", stream.id, "1001")));
+    talk_base::scoped_ptr<buzz::XmlElement> expected_stream_remove(
+        buzz::XmlElement::ForStr(
+            JingleOutboundStreamRemove(
+                call_->sessions()[0]->id(),
+                "video", stream.id)));
+
+    call_->SendVideoStreamUpdate(call_->sessions()[0],
+                                 call_->CreateVideoStreamUpdate(stream));
+    ASSERT_EQ(1U, stanzas_.size());
+    EXPECT_EQ(expected_stream_add->Str(), stanzas_[0]->Str());
+    ClearStanzas();
+
+    stream.ssrcs.clear();
+    call_->SendVideoStreamUpdate(call_->sessions()[0],
+                                 call_->CreateVideoStreamUpdate(stream));
+    ASSERT_EQ(1U, stanzas_.size());
+    EXPECT_EQ(expected_stream_remove->Str(), stanzas_[0]->Str());
+    ClearStanzas();
+  }
+
+  void TestStreamsUpdateAndViewRequests() {
+    cricket::CallOptions options = VideoCallOptions();
+    options.is_muc = true;
+
+    client_->CreateCall();
+    call_->InitiateSession(buzz::Jid("me@mydomain.com"),
+                           buzz::Jid("me@mydomain.com"), options);
+    ASSERT_EQ(1U, ClearStanzas());
+    ASSERT_EQ(0U, last_streams_added_.audio().size());
+    ASSERT_EQ(0U, last_streams_added_.video().size());
+    ASSERT_EQ(0U, last_streams_removed_.audio().size());
+    ASSERT_EQ(0U, last_streams_removed_.video().size());
+
+    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();
+    // Need to clear the added streams, because they are populated when
+    // receiving an accept message now.
+    last_streams_added_.mutable_video()->clear();
+    last_streams_added_.mutable_audio()->clear();
+
+    call_->sessions()[0]->SetState(cricket::Session::STATE_INPROGRESS);
+
+    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(pthatcher): Figure out how to make this an ERROR rather than RESULT.
+    ASSERT_EQ(std::string(buzz::STR_ERROR), stanzas_[0]->Attr(buzz::QN_TYPE));
+    ClearStanzas();
+    ASSERT_EQ(0U, last_streams_added_.audio().size());
+    ASSERT_EQ(0U, last_streams_added_.video().size());
+    ASSERT_EQ(0U, last_streams_removed_.audio().size());
+    ASSERT_EQ(0U, last_streams_removed_.video().size());
+
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamAdd("audio", "Bob", "audio1", "1234")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(1U, last_streams_added_.audio().size());
+    ASSERT_EQ("Bob", last_streams_added_.audio()[0].groupid);
+    ASSERT_EQ(1U, last_streams_added_.audio()[0].ssrcs.size());
+    ASSERT_EQ(1234U, last_streams_added_.audio()[0].first_ssrc());
+
+    // Ignores adds without ssrcs.
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamAddWithoutSsrc("audio", "Bob", "audioX")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(1U, last_streams_added_.audio().size());
+    ASSERT_EQ(1234U, last_streams_added_.audio()[0].first_ssrc());
+
+    // Ignores stream updates with unknown content names. (Don't terminate).
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamAddWithoutSsrc("foo", "Bob", "foo")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamAdd("audio", "Joe", "audio1", "2468")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(1U, last_streams_added_.audio().size());
+    ASSERT_EQ("Joe", last_streams_added_.audio()[0].groupid);
+    ASSERT_EQ(1U, last_streams_added_.audio()[0].ssrcs.size());
+    ASSERT_EQ(2468U, last_streams_added_.audio()[0].first_ssrc());
+
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamAdd("video", "Bob", "video1", "5678")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(1U, last_streams_added_.video().size());
+    ASSERT_EQ("Bob", last_streams_added_.video()[0].groupid);
+    ASSERT_EQ(1U, last_streams_added_.video()[0].ssrcs.size());
+    ASSERT_EQ(5678U, last_streams_added_.video()[0].first_ssrc());
+
+    // We're testing that a "duplicate" is effectively ignored.
+    last_streams_added_.mutable_video()->clear();
+    last_streams_removed_.mutable_video()->clear();
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamAdd("video", "Bob", "video1", "5678")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(0U, last_streams_added_.video().size());
+    ASSERT_EQ(0U, last_streams_removed_.video().size());
+
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamAdd("video", "Bob", "video2", "5679")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(1U, last_streams_added_.video().size());
+    ASSERT_EQ("Bob", last_streams_added_.video()[0].groupid);
+    ASSERT_EQ(1U, last_streams_added_.video()[0].ssrcs.size());
+    ASSERT_EQ(5679U, last_streams_added_.video()[0].first_ssrc());
+
+    cricket::FakeVoiceMediaChannel* voice_channel = fme_->GetVoiceChannel(0);
+    ASSERT_TRUE(voice_channel != NULL);
+    ASSERT_TRUE(voice_channel->HasRecvStream(1234U));
+    ASSERT_TRUE(voice_channel->HasRecvStream(2468U));
+    cricket::FakeVideoMediaChannel* video_channel = fme_->GetVideoChannel(0);
+    ASSERT_TRUE(video_channel != NULL);
+    ASSERT_TRUE(video_channel->HasRecvStream(5678U));
+    ClearStanzas();
+
+    cricket::ViewRequest viewRequest;
+    cricket::StaticVideoView staticVideoView(
+        cricket::StreamSelector(5678U), 640, 480, 30);
+    viewRequest.static_video_views.push_back(staticVideoView);
+    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::FakeDataEngine* fdme_;
+  cricket::MediaSessionClient* client_;
+
+  cricket::Call* call_;
+  std::vector<buzz::XmlElement* > stanzas_;
+  MediaSessionTestParser* parser_;
+  cricket::SignalingProtocol initial_protocol_;
+  bool expect_incoming_crypto_;
+  bool expect_outgoing_crypto_;
+  int expected_video_bandwidth_;
+  bool expected_video_rtcp_mux_;
+  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, VideoCallOptions(), 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, VideoCallOptions(), 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, VideoCallOptions(), elem.use());
+}
+
+TEST(MediaSessionTest, JingleGoodVideoInitiateWithRtpData) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  cricket::CallOptions options = VideoCallOptions();
+  options.data_channel_type = cricket::DCT_RTP;
+  test->TestGoodIncomingInitiate(
+      AddEncryption(kJingleVideoInitiateWithRtpData, kJingleCryptoOffer),
+      options,
+      elem.use());
+}
+
+TEST(MediaSessionTest, JingleGoodVideoInitiateWithSctpData) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  cricket::CallOptions options = VideoCallOptions();
+  options.data_channel_type = cricket::DCT_SCTP;
+  test->TestGoodIncomingInitiate(kJingleVideoInitiateWithSctpData,
+                                 options,
+                                 elem.use());
+}
+
+TEST(MediaSessionTest, JingleRejectAudio) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  cricket::CallOptions options = VideoCallOptions();
+  options.has_audio = false;
+  options.data_channel_type = cricket::DCT_RTP;
+  test->TestRejectOffer(kJingleVideoInitiateWithRtpData, options, elem.use());
+}
+
+TEST(MediaSessionTest, JingleRejectVideo) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  cricket::CallOptions options = AudioCallOptions();
+  options.data_channel_type = cricket::DCT_RTP;
+  test->TestRejectOffer(kJingleVideoInitiateWithRtpData, options, elem.use());
+}
+
+TEST(MediaSessionTest, JingleRejectData) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  test->TestRejectOffer(
+      kJingleVideoInitiateWithRtpData, VideoCallOptions(), elem.use());
+}
+
+TEST(MediaSessionTest, JingleRejectVideoAndData) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  test->TestRejectOffer(
+      kJingleVideoInitiateWithRtpData, AudioCallOptions(), elem.use());
+}
+
+TEST(MediaSessionTest, JingleGoodInitiateAllSupportedAudioCodecs) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  test->TestGoodIncomingInitiate(
+      kJingleInitiate, AudioCallOptions(), 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, AudioCallOptions(), 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, AudioCallOptions(), 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, AudioCallOptions(), 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, AudioCallOptions(), 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(AudioCallOptions());
+}
+
+TEST(MediaSessionTest, JingleGoodOutgoingInitiateWithBandwidth) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  cricket::CallOptions options = VideoCallOptions();
+  options.video_bandwidth = 42000;
+  test->TestGoodOutgoingInitiate(options);
+}
+
+TEST(MediaSessionTest, JingleGoodOutgoingInitiateWithRtcpMux) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  cricket::CallOptions options = VideoCallOptions();
+  options.rtcp_mux_enabled = true;
+  test->TestGoodOutgoingInitiate(options);
+}
+
+TEST(MediaSessionTest, JingleGoodOutgoingInitiateWithRtpData) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  cricket::CallOptions options;
+  options.data_channel_type = cricket::DCT_RTP;
+  test->ExpectCrypto(cricket::SEC_ENABLED);
+  test->TestGoodOutgoingInitiate(options);
+}
+
+TEST(MediaSessionTest, JingleGoodOutgoingInitiateWithSctpData) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  cricket::CallOptions options;
+  options.data_channel_type = cricket::DCT_SCTP;
+  test->TestGoodOutgoingInitiate(options);
+}
+
+// Crypto related tests.
+
+// Offer has crypto but the session is not secured, just ignore it.
+TEST(MediaSessionTest, JingleInitiateWithCryptoIsIgnoredWhenNotSecured) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  test->TestGoodIncomingInitiate(
+      AddEncryption(kJingleVideoInitiate, kJingleCryptoOffer),
+      VideoCallOptions(),
+      elem.use());
+}
+
+// Offer has crypto required but the session is not secure, fail.
+TEST(MediaSessionTest, JingleInitiateWithCryptoRequiredWhenNotSecured) {
+  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),
+      VideoCallOptions(),
+      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),
+      VideoCallOptions(),
+      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),
+      VideoCallOptions(),
+      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(AudioCallOptions());
+}
+
+TEST(MediaSessionTest, JingleGoodOutgoingInitiateWithCryptoRequired) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->ExpectCrypto(cricket::SEC_REQUIRED);
+  test->TestGoodOutgoingInitiate(AudioCallOptions());
+}
+
+TEST(MediaSessionTest, JingleIncomingAcceptWithSsrcs) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  cricket::CallOptions options = VideoCallOptions();
+  options.is_muc = true;
+  test->TestIncomingAcceptWithSsrcs(kJingleAcceptWithSsrcs, options);
+}
+
+TEST(MediaSessionTest, JingleIncomingAcceptWithRtpDataSsrcs) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  cricket::CallOptions options = VideoCallOptions();
+  options.is_muc = true;
+  options.data_channel_type = cricket::DCT_RTP;
+  test->TestIncomingAcceptWithSsrcs(kJingleAcceptWithRtpDataSsrcs, options);
+}
+
+TEST(MediaSessionTest, JingleIncomingAcceptWithSctpData) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  cricket::CallOptions options = VideoCallOptions();
+  options.is_muc = true;
+  options.data_channel_type = cricket::DCT_SCTP;
+  test->TestIncomingAcceptWithSsrcs(kJingleAcceptWithSctpData, options);
+}
+
+TEST(MediaSessionTest, JingleStreamsUpdateAndView) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->TestStreamsUpdateAndViewRequests();
+}
+
+TEST(MediaSessionTest, JingleSendVideoStreamUpdate) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+  test->TestSendVideoStreamUpdate();
+}
+
+// Gingle tests
+
+TEST(MediaSessionTest, GingleGoodVideoInitiate) {
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestGoodIncomingInitiate(
+      kGingleVideoInitiate, VideoCallOptions(), 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, VideoCallOptions(), elem.use());
+}
+
+TEST(MediaSessionTest, GingleGoodInitiateAllSupportedAudioCodecs) {
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestGoodIncomingInitiate(
+      kGingleInitiate, AudioCallOptions(), 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),
+      AudioCallOptions(),
+      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, AudioCallOptions(), 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, AudioCallOptions(), 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, AudioCallOptions(), 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, AudioCallOptions(), 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, AudioCallOptions(), 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(AudioCallOptions());
+}
+
+TEST(MediaSessionTest, GingleGoodOutgoingInitiateWithBandwidth) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  cricket::CallOptions options = VideoCallOptions();
+  options.video_bandwidth = 42000;
+  test->TestGoodOutgoingInitiate(options);
+}
+
+// Crypto related tests.
+
+// Offer has crypto but the session is not secured, just ignore it.
+TEST(MediaSessionTest, GingleInitiateWithCryptoIsIgnoredWhenNotSecured) {
+  talk_base::scoped_ptr<buzz::XmlElement> elem;
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->TestGoodIncomingInitiate(
+      AddEncryption(kGingleInitiate, kGingleCryptoOffer),
+      VideoCallOptions(),
+      elem.use());
+}
+
+// Offer has crypto required but the session is not secure, fail.
+TEST(MediaSessionTest, GingleInitiateWithCryptoRequiredWhenNotSecured) {
+  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),
+      VideoCallOptions(),
+      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),
+      VideoCallOptions(),
+      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),
+      VideoCallOptions(),
+      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(AudioCallOptions());
+}
+
+TEST(MediaSessionTest, GingleGoodOutgoingInitiateWithCryptoRequired) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  test->ExpectCrypto(cricket::SEC_REQUIRED);
+  test->TestGoodOutgoingInitiate(AudioCallOptions());
+}
+
+TEST(MediaSessionTest, GingleIncomingAcceptWithSsrcs) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  cricket::CallOptions options = VideoCallOptions();
+  options.is_muc = true;
+  test->TestIncomingAcceptWithSsrcs(kGingleAcceptWithSsrcs, options);
+}
+
+TEST(MediaSessionTest, GingleGoodOutgoingInitiateWithRtpData) {
+  talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+  cricket::CallOptions options;
+  options.data_channel_type = cricket::DCT_RTP;
+  test->ExpectCrypto(cricket::SEC_ENABLED);
+  test->TestGoodOutgoingInitiate(options);
+}
diff --git a/talk/session/media/mediasink.h b/talk/session/media/mediasink.h
new file mode 100644
index 0000000..fb0e06b
--- /dev/null
+++ b/talk/session/media/mediasink.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_MEDIA_MEDIASINK_H_
+#define TALK_SESSION_MEDIA_MEDIASINK_H_
+
+namespace cricket {
+
+// MediaSinkInterface is a sink to handle RTP and RTCP packets that are sent or
+// received by a channel.
+class MediaSinkInterface {
+ public:
+  virtual ~MediaSinkInterface() {}
+
+  virtual void SetMaxSize(size_t size) = 0;
+  virtual bool Enable(bool enable) = 0;
+  virtual bool IsEnabled() const = 0;
+  virtual void OnPacket(const void* data, size_t size, bool rtcp) = 0;
+  virtual void set_packet_filter(int filter) = 0;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_MEDIA_MEDIASINK_H_
diff --git a/talk/session/media/rtcpmuxfilter.cc b/talk/session/media/rtcpmuxfilter.cc
new file mode 100644
index 0000000..7091952
--- /dev/null
+++ b/talk/session/media/rtcpmuxfilter.cc
@@ -0,0 +1,132 @@
+/*
+ * 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/media/rtcpmuxfilter.h"
+
+#include "talk/base/logging.h"
+
+namespace cricket {
+
+RtcpMuxFilter::RtcpMuxFilter() : state_(ST_INIT), offer_enable_(false) {
+}
+
+bool RtcpMuxFilter::IsActive() const {
+  return state_ == ST_SENTPRANSWER ||
+         state_ == ST_RECEIVEDPRANSWER ||
+         state_ == ST_ACTIVE;
+}
+
+bool RtcpMuxFilter::SetOffer(bool offer_enable, ContentSource src) {
+  if (!ExpectOffer(offer_enable, src)) {
+    LOG(LS_ERROR) << "Invalid state for change of RTCP mux offer";
+    return false;
+  }
+
+  offer_enable_ = offer_enable;
+  state_ = (src == CS_LOCAL) ? ST_SENTOFFER : ST_RECEIVEDOFFER;
+  return true;
+}
+
+bool RtcpMuxFilter::SetProvisionalAnswer(bool answer_enable,
+                                         ContentSource src) {
+  if (!ExpectAnswer(src)) {
+    LOG(LS_ERROR) << "Invalid state for RTCP mux provisional answer";
+    return false;
+  }
+
+  if (offer_enable_) {
+    if (answer_enable) {
+      if (src == CS_REMOTE)
+        state_ = ST_RECEIVEDPRANSWER;
+      else  // CS_LOCAL
+        state_ = ST_SENTPRANSWER;
+    } else {
+      // The provisional answer doesn't want to use RTCP mux.
+      // Go back to the original state after the offer was set and wait for next
+      // provisional or final answer.
+      if (src == CS_REMOTE)
+        state_ = ST_SENTOFFER;
+      else  // CS_LOCAL
+        state_ = ST_RECEIVEDOFFER;
+    }
+  } else if (answer_enable) {
+    // If the offer didn't specify RTCP mux, the answer shouldn't either.
+    LOG(LS_WARNING) << "Invalid parameters in RTCP mux provisional answer";
+    return false;
+  }
+
+  return true;
+}
+
+bool RtcpMuxFilter::SetAnswer(bool answer_enable, ContentSource src) {
+  if (!ExpectAnswer(src)) {
+    LOG(LS_ERROR) << "Invalid state for RTCP mux answer";
+    return false;
+  }
+
+  if (offer_enable_ && answer_enable) {
+    state_ = ST_ACTIVE;
+  } else if (answer_enable) {
+    // If the offer didn't specify RTCP mux, the answer shouldn't either.
+    LOG(LS_WARNING) << "Invalid parameters in RTCP mux answer";
+    return false;
+  } else {
+    state_ = ST_INIT;
+  }
+  return true;
+}
+
+bool RtcpMuxFilter::DemuxRtcp(const char* data, int len) {
+  // If we're muxing RTP/RTCP, we must inspect each packet delivered and
+  // determine whether it is RTP or RTCP. We do so by checking the packet type,
+  // and assuming RTP if type is 0-63 or 96-127. For additional details, see
+  // http://tools.ietf.org/html/rfc5761.
+  // Note that if we offer RTCP mux, we may receive muxed RTCP before we
+  // receive the answer, so we operate in that state too.
+  if (!offer_enable_ || state_ < ST_SENTOFFER) {
+    return false;
+  }
+
+  int type = (len >= 2) ? (static_cast<uint8>(data[1]) & 0x7F) : 0;
+  return (type >= 64 && type < 96);
+}
+
+bool RtcpMuxFilter::ExpectOffer(bool offer_enable, ContentSource source) {
+  return ((state_ == ST_INIT) ||
+          (state_ == ST_ACTIVE && offer_enable == offer_enable_) ||
+          (state_ == ST_SENTOFFER && source == CS_LOCAL) ||
+          (state_ == ST_RECEIVEDOFFER && source == CS_REMOTE));
+}
+
+bool RtcpMuxFilter::ExpectAnswer(ContentSource source) {
+  return ((state_ == ST_SENTOFFER && source == CS_REMOTE) ||
+          (state_ == ST_RECEIVEDOFFER && source == CS_LOCAL) ||
+          (state_ == ST_SENTPRANSWER && source == CS_LOCAL) ||
+          (state_ == ST_RECEIVEDPRANSWER && source == CS_REMOTE));
+}
+
+}  // namespace cricket
diff --git a/talk/session/media/rtcpmuxfilter.h b/talk/session/media/rtcpmuxfilter.h
new file mode 100644
index 0000000..a5bb85e
--- /dev/null
+++ b/talk/session/media/rtcpmuxfilter.h
@@ -0,0 +1,86 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_MEDIA_RTCPMUXFILTER_H_
+#define TALK_SESSION_MEDIA_RTCPMUXFILTER_H_
+
+#include "talk/base/basictypes.h"
+#include "talk/p2p/base/sessiondescription.h"
+
+namespace cricket {
+
+// RTCP Muxer, as defined in RFC 5761 (http://tools.ietf.org/html/rfc5761)
+class RtcpMuxFilter {
+ public:
+  RtcpMuxFilter();
+
+  // Whether the filter is active, i.e. has RTCP mux been properly negotiated.
+  bool IsActive() const;
+
+  // Specifies whether the offer indicates the use of RTCP mux.
+  bool SetOffer(bool offer_enable, ContentSource src);
+
+  // Specifies whether the provisional answer indicates the use of RTCP mux.
+  bool SetProvisionalAnswer(bool answer_enable, ContentSource src);
+
+  // Specifies whether the answer indicates the use of RTCP mux.
+  bool SetAnswer(bool answer_enable, ContentSource src);
+
+  // Determines whether the specified packet is RTCP.
+  bool DemuxRtcp(const char* data, int len);
+
+ private:
+  bool ExpectOffer(bool offer_enable, ContentSource source);
+  bool ExpectAnswer(ContentSource source);
+  enum State {
+    // RTCP mux filter unused.
+    ST_INIT,
+    // Offer with RTCP mux enabled received.
+    // RTCP mux filter is not active.
+    ST_RECEIVEDOFFER,
+    // Offer with RTCP mux enabled sent.
+    // RTCP mux filter can demux incoming packets but is not active.
+    ST_SENTOFFER,
+    // RTCP mux filter is active but the sent answer is only provisional.
+    // When the final answer is set, the state transitions to ST_ACTIVE or
+    // ST_INIT.
+    ST_SENTPRANSWER,
+    // RTCP mux filter is active but the received answer is only provisional.
+    // When the final answer is set, the state transitions to ST_ACTIVE or
+    // ST_INIT.
+    ST_RECEIVEDPRANSWER,
+    // Offer and answer set, RTCP mux enabled. It is not possible to de-activate
+    // the filter.
+    ST_ACTIVE
+  };
+  State state_;
+  bool offer_enable_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_MEDIA_RTCPMUXFILTER_H_
diff --git a/talk/session/media/rtcpmuxfilter_unittest.cc b/talk/session/media/rtcpmuxfilter_unittest.cc
new file mode 100644
index 0000000..ad33498
--- /dev/null
+++ b/talk/session/media/rtcpmuxfilter_unittest.cc
@@ -0,0 +1,212 @@
+// 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/media/rtcpmuxfilter.h"
+
+#include "talk/base/gunit.h"
+#include "talk/media/base/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, DemuxRtcpSenderProvisionalAnswer) {
+  cricket::RtcpMuxFilter filter;
+  const char data[] = { 0, 73, 0, 0 };
+  const int len = 4;
+
+  filter.SetOffer(true, cricket::CS_REMOTE);
+  // Received provisional answer without mux enabled.
+  filter.SetProvisionalAnswer(false, cricket::CS_LOCAL);
+  EXPECT_FALSE(filter.DemuxRtcp(data, len));
+  // Received provisional answer with mux enabled.
+  filter.SetProvisionalAnswer(true, cricket::CS_LOCAL);
+  EXPECT_TRUE(filter.DemuxRtcp(data, len));
+  // Remote accepted, demux should be enabled.
+  filter.SetAnswer(true, cricket::CS_LOCAL);
+  EXPECT_TRUE(filter.DemuxRtcp(data, len));
+}
+
+TEST(RtcpMuxFilterTest, DemuxRtcpReceiverProvisionalAnswer) {
+  cricket::RtcpMuxFilter filter;
+  const char data[] = { 0, 73, 0, 0 };
+  const int len = 4;
+
+  filter.SetOffer(true, cricket::CS_LOCAL);
+  // Received provisional answer without mux enabled.
+  filter.SetProvisionalAnswer(false, cricket::CS_REMOTE);
+  // After sent offer, demux should be enabled until we have received a
+  // final answer.
+  EXPECT_TRUE(filter.DemuxRtcp(data, len));
+  // Received provisional answer with mux enabled.
+  filter.SetProvisionalAnswer(true, cricket::CS_REMOTE);
+  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, 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 that we can receive provisional answer and final answer.
+TEST(RtcpMuxFilterTest, ReceivePrAnswer) {
+  cricket::RtcpMuxFilter filter;
+  filter.SetOffer(true, cricket::CS_LOCAL);
+  // Received provisional answer with mux enabled.
+  EXPECT_TRUE(filter.SetProvisionalAnswer(true, cricket::CS_REMOTE));
+  // We are now active since both sender and receiver support mux.
+  EXPECT_TRUE(filter.IsActive());
+  // Received provisional answer with mux disabled.
+  EXPECT_TRUE(filter.SetProvisionalAnswer(false, cricket::CS_REMOTE));
+  // We are now inactive since the receiver doesn't support mux.
+  EXPECT_FALSE(filter.IsActive());
+  // Received final answer with mux enabled.
+  EXPECT_TRUE(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 send provisional answer and final answer.
+TEST(RtcpMuxFilterTest, SendPrAnswer) {
+  cricket::RtcpMuxFilter filter;
+  filter.SetOffer(true, cricket::CS_REMOTE);
+  // Send provisional answer with mux enabled.
+  EXPECT_TRUE(filter.SetProvisionalAnswer(true, cricket::CS_LOCAL));
+  EXPECT_TRUE(filter.IsActive());
+  // Received provisional answer with mux disabled.
+  EXPECT_TRUE(filter.SetProvisionalAnswer(false, cricket::CS_LOCAL));
+  EXPECT_FALSE(filter.IsActive());
+  // Send final answer with mux enabled.
+  EXPECT_TRUE(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 SetOffer can be called twice.
+TEST(RtcpMuxFilterTest, SetOfferTwice) {
+  cricket::RtcpMuxFilter filter;
+
+  EXPECT_TRUE(filter.SetOffer(true, cricket::CS_REMOTE));
+  EXPECT_TRUE(filter.SetOffer(true, cricket::CS_REMOTE));
+  EXPECT_TRUE(filter.SetAnswer(true, cricket::CS_LOCAL));
+  EXPECT_TRUE(filter.IsActive());
+
+  cricket::RtcpMuxFilter filter2;
+  EXPECT_TRUE(filter2.SetOffer(false, cricket::CS_LOCAL));
+  EXPECT_TRUE(filter2.SetOffer(false, cricket::CS_LOCAL));
+  EXPECT_TRUE(filter2.SetAnswer(false, cricket::CS_REMOTE));
+  EXPECT_FALSE(filter2.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/media/soundclip.cc b/talk/session/media/soundclip.cc
new file mode 100644
index 0000000..44f457c
--- /dev/null
+++ b/talk/session/media/soundclip.cc
@@ -0,0 +1,82 @@
+/*
+ * 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/media/soundclip.h"
+
+namespace cricket {
+
+enum {
+  MSG_PLAYSOUND = 1,
+};
+
+struct PlaySoundMessageData : talk_base::MessageData {
+  PlaySoundMessageData(const void *c,
+                       int l,
+                       SoundclipMedia::SoundclipFlags f)
+      : clip(c),
+        len(l),
+        flags(f),
+        result(false) {
+  }
+
+  const void *clip;
+  int len;
+  SoundclipMedia::SoundclipFlags flags;
+  bool result;
+};
+
+Soundclip::Soundclip(talk_base::Thread *thread, SoundclipMedia *soundclip_media)
+    : worker_thread_(thread),
+      soundclip_media_(soundclip_media) {
+}
+
+bool Soundclip::PlaySound(const void *clip,
+                          int len,
+                          SoundclipMedia::SoundclipFlags flags) {
+  PlaySoundMessageData data(clip, len, flags);
+  worker_thread_->Send(this, MSG_PLAYSOUND, &data);
+  return data.result;
+}
+
+bool Soundclip::PlaySound_w(const void *clip,
+                            int len,
+                            SoundclipMedia::SoundclipFlags flags) {
+  return soundclip_media_->PlaySound(static_cast<const char *>(clip),
+                                     len,
+                                     flags);
+}
+
+void Soundclip::OnMessage(talk_base::Message *message) {
+  ASSERT(message->message_id == MSG_PLAYSOUND);
+  PlaySoundMessageData *data =
+      static_cast<PlaySoundMessageData *>(message->pdata);
+  data->result = PlaySound_w(data->clip,
+                             data->len,
+                             data->flags);
+}
+
+}  // namespace cricket
diff --git a/talk/session/media/soundclip.h b/talk/session/media/soundclip.h
new file mode 100644
index 0000000..f057d8d
--- /dev/null
+++ b/talk/session/media/soundclip.h
@@ -0,0 +1,70 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_MEDIA_SOUNDCLIP_H_
+#define TALK_SESSION_MEDIA_SOUNDCLIP_H_
+
+#include "talk/base/scoped_ptr.h"
+#include "talk/media/base/mediaengine.h"
+
+namespace talk_base {
+
+class Thread;
+
+}
+
+namespace cricket {
+
+// Soundclip wraps SoundclipMedia to support marshalling calls to the proper
+// thread.
+class Soundclip : private talk_base::MessageHandler {
+ public:
+  Soundclip(talk_base::Thread* thread, SoundclipMedia* soundclip_media);
+
+  // Plays a sound out to the speakers with the given audio stream. The stream
+  // must be 16-bit little-endian 16 kHz PCM. If a stream is already playing
+  // on this Soundclip, it is stopped. If clip is NULL, nothing is played.
+  // Returns whether it was successful.
+  bool PlaySound(const void* clip,
+                 int len,
+                 SoundclipMedia::SoundclipFlags flags);
+
+ private:
+  bool PlaySound_w(const void* clip,
+                   int len,
+                   SoundclipMedia::SoundclipFlags flags);
+
+  // From MessageHandler
+  virtual void OnMessage(talk_base::Message* message);
+
+  talk_base::Thread* worker_thread_;
+  talk_base::scoped_ptr<SoundclipMedia> soundclip_media_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_MEDIA_SOUNDCLIP_H_
diff --git a/talk/session/media/srtpfilter.cc b/talk/session/media/srtpfilter.cc
new file mode 100644
index 0000000..e5104db
--- /dev/null
+++ b/talk/session/media/srtpfilter.cc
@@ -0,0 +1,805 @@
+/*
+ * 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.
+ */
+
+#undef HAVE_CONFIG_H
+
+#include "talk/session/media/srtpfilter.h"
+
+#include <algorithm>
+#include <cstring>
+
+#include "talk/base/base64.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/timeutils.h"
+#include "talk/media/base/rtputils.h"
+
+// Enable this line to turn on SRTP debugging
+// #define SRTP_DEBUG
+
+#ifdef HAVE_SRTP
+#ifdef SRTP_RELATIVE_PATH
+#include "srtp.h"  // NOLINT
+#else
+#include "third_party/libsrtp/include/srtp.h"
+#endif  // SRTP_RELATIVE_PATH
+#ifdef _DEBUG
+extern "C" debug_module_t mod_srtp;
+extern "C" debug_module_t mod_auth;
+extern "C" debug_module_t mod_cipher;
+extern "C" debug_module_t mod_stat;
+extern "C" debug_module_t mod_alloc;
+extern "C" debug_module_t mod_aes_icm;
+extern "C" debug_module_t mod_aes_hmac;
+#endif
+#else
+// SrtpFilter needs that constant.
+#define SRTP_MASTER_KEY_LEN 30
+#endif  // HAVE_SRTP
+
+namespace cricket {
+
+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
+
+// This helper function is used on systems that don't (yet) have SRTP,
+// to log that the functions that require it won't do anything.
+namespace {
+bool SrtpNotAvailable(const char *func) {
+  LOG(LS_ERROR) << func << ": SRTP is not available on your system.";
+  return false;
+}
+}  // anonymous namespace
+
+#endif  // !HAVE_SRTP
+
+void EnableSrtpDebugging() {
+#ifdef HAVE_SRTP
+#ifdef _DEBUG
+  debug_on(mod_srtp);
+  debug_on(mod_auth);
+  debug_on(mod_cipher);
+  debug_on(mod_stat);
+  debug_on(mod_alloc);
+  debug_on(mod_aes_icm);
+  // debug_on(mod_aes_cbc);
+  // debug_on(mod_hmac);
+#endif
+#endif  // HAVE_SRTP
+}
+
+SrtpFilter::SrtpFilter()
+    : state_(ST_INIT),
+      signal_silent_time_in_ms_(0) {
+}
+
+SrtpFilter::~SrtpFilter() {
+}
+
+bool SrtpFilter::IsActive() const {
+  return state_ >= ST_ACTIVE;
+}
+
+bool SrtpFilter::SetOffer(const std::vector<CryptoParams>& offer_params,
+                          ContentSource source) {
+  if (!ExpectOffer(source)) {
+     LOG(LS_ERROR) << "Wrong state to update SRTP offer";
+     return false;
+  }
+  return StoreParams(offer_params, source);
+}
+
+bool SrtpFilter::SetAnswer(const std::vector<CryptoParams>& answer_params,
+                           ContentSource source) {
+  return DoSetAnswer(answer_params, source, true);
+}
+
+bool SrtpFilter::SetProvisionalAnswer(
+    const std::vector<CryptoParams>& answer_params,
+    ContentSource source) {
+  return DoSetAnswer(answer_params, source, false);
+}
+
+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_ || recv_rtcp_session_) {
+    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";
+    return false;
+  }
+  return send_session_->ProtectRtp(p, in_len, max_len, out_len);
+}
+
+bool SrtpFilter::ProtectRtcp(void* p, int in_len, int max_len, int* out_len) {
+  if (!IsActive()) {
+    LOG(LS_WARNING) << "Failed to ProtectRtcp: SRTP not active";
+    return false;
+  }
+  if (send_rtcp_session_) {
+    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) {
+  if (!IsActive()) {
+    LOG(LS_WARNING) << "Failed to UnprotectRtp: SRTP not active";
+    return false;
+  }
+  return recv_session_->UnprotectRtp(p, in_len, out_len);
+}
+
+bool SrtpFilter::UnprotectRtcp(void* p, int in_len, int* out_len) {
+  if (!IsActive()) {
+    LOG(LS_WARNING) << "Failed to UnprotectRtcp: SRTP not active";
+    return false;
+  }
+  if (recv_rtcp_session_) {
+    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) {
+  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_)
+      send_rtcp_session_->set_signal_silent_time(signal_silent_time_in_ms);
+    if (recv_rtcp_session_)
+      recv_rtcp_session_->set_signal_silent_time(signal_silent_time_in_ms);
+  }
+}
+
+bool SrtpFilter::ExpectOffer(ContentSource source) {
+  return ((state_ == ST_INIT) ||
+          (state_ == ST_ACTIVE) ||
+          (state_  == ST_SENTOFFER && source == CS_LOCAL) ||
+          (state_  == ST_SENTUPDATEDOFFER && source == CS_LOCAL) ||
+          (state_ == ST_RECEIVEDOFFER && source == CS_REMOTE) ||
+          (state_ == ST_RECEIVEDUPDATEDOFFER && source == CS_REMOTE));
+}
+
+bool SrtpFilter::StoreParams(const std::vector<CryptoParams>& params,
+                             ContentSource source) {
+  offer_params_ = params;
+  if (state_ == ST_INIT) {
+    state_ = (source == CS_LOCAL) ? ST_SENTOFFER : ST_RECEIVEDOFFER;
+  } else {  // state >= ST_ACTIVE
+    state_ =
+        (source == CS_LOCAL) ? ST_SENTUPDATEDOFFER : ST_RECEIVEDUPDATEDOFFER;
+  }
+  return true;
+}
+
+bool SrtpFilter::ExpectAnswer(ContentSource source) {
+  return ((state_ == ST_SENTOFFER && source == CS_REMOTE) ||
+          (state_ == ST_RECEIVEDOFFER && source == CS_LOCAL) ||
+          (state_ == ST_SENTUPDATEDOFFER && source == CS_REMOTE) ||
+          (state_ == ST_RECEIVEDUPDATEDOFFER && source == CS_LOCAL) ||
+          (state_ == ST_SENTPRANSWER_NO_CRYPTO && source == CS_LOCAL) ||
+          (state_ == ST_SENTPRANSWER && source == CS_LOCAL) ||
+          (state_ == ST_RECEIVEDPRANSWER_NO_CRYPTO && source == CS_REMOTE) ||
+          (state_ == ST_RECEIVEDPRANSWER && source == CS_REMOTE));
+}
+
+bool SrtpFilter::DoSetAnswer(const std::vector<CryptoParams>& answer_params,
+                             ContentSource source,
+                             bool final) {
+  if (!ExpectAnswer(source)) {
+    LOG(LS_ERROR) << "Invalid state for SRTP answer";
+    return false;
+  }
+
+  // If the answer doesn't requests crypto complete the negotiation of an
+  // unencrypted session.
+  // Otherwise, finalize the parameters and apply them.
+  if (answer_params.empty()) {
+    if (final) {
+      return ResetParams();
+    } else {
+      // Need to wait for the final answer to decide if
+      // we should go to Active state.
+      state_ = (source == CS_LOCAL) ? ST_SENTPRANSWER_NO_CRYPTO :
+                                      ST_RECEIVEDPRANSWER_NO_CRYPTO;
+      return true;
+    }
+  }
+  CryptoParams selected_params;
+  if (!NegotiateParams(answer_params, &selected_params))
+    return false;
+  const CryptoParams& send_params =
+      (source == CS_REMOTE) ? selected_params : answer_params[0];
+  const CryptoParams& recv_params =
+      (source == CS_REMOTE) ? answer_params[0] : selected_params;
+  if (!ApplyParams(send_params, recv_params)) {
+    return false;
+  }
+
+  if (final) {
+    offer_params_.clear();
+    state_ = ST_ACTIVE;
+  } else {
+    state_ =
+        (source == CS_LOCAL) ? ST_SENTPRANSWER : ST_RECEIVEDPRANSWER;
+  }
+  return true;
+}
+
+void SrtpFilter::CreateSrtpSessions() {
+  send_session_.reset(new SrtpSession());
+  applied_send_params_ = CryptoParams();
+  recv_session_.reset(new SrtpSession());
+  applied_recv_params_ = CryptoParams();
+
+  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,
+  // unless the offer didn't mention crypto, in which case we shouldn't be here.
+  bool ret = (answer_params.size() == 1U && !offer_params_.empty());
+  if (ret) {
+    // We should find a match between the answer params and the offered params.
+    std::vector<CryptoParams>::const_iterator it;
+    for (it = offer_params_.begin(); it != offer_params_.end(); ++it) {
+      if (answer_params[0].Matches(*it)) {
+        break;
+      }
+    }
+
+    if (it != offer_params_.end()) {
+      *selected_params = *it;
+    } else {
+      ret = false;
+    }
+  }
+
+  if (!ret) {
+    LOG(LS_WARNING) << "Invalid parameters in SRTP answer";
+  }
+  return ret;
+}
+
+bool SrtpFilter::ApplyParams(const CryptoParams& send_params,
+                             const CryptoParams& recv_params) {
+  // TODO(jiayl): Split this method to apply send and receive CryptoParams
+  // independently, so that we can skip one method when either send or receive
+  // CryptoParams is unchanged.
+  if (applied_send_params_.cipher_suite == send_params.cipher_suite &&
+      applied_send_params_.key_params == send_params.key_params &&
+      applied_recv_params_.cipher_suite == recv_params.cipher_suite &&
+      applied_recv_params_.key_params == recv_params.key_params) {
+    LOG(LS_INFO) << "Applying the same SRTP parameters again. No-op.";
+
+    // We do not want to reset the ROC if the keys are the same. So just return.
+    return true;
+  }
+  // TODO(juberti): Zero these buffers after use.
+  bool ret;
+  uint8 send_key[SRTP_MASTER_KEY_LEN], recv_key[SRTP_MASTER_KEY_LEN];
+  ret = (ParseKeyParams(send_params.key_params, send_key, sizeof(send_key)) &&
+         ParseKeyParams(recv_params.key_params, recv_key, sizeof(recv_key)));
+  if (ret) {
+    CreateSrtpSessions();
+    ret = (send_session_->SetSend(send_params.cipher_suite,
+                                  send_key, sizeof(send_key)) &&
+           recv_session_->SetRecv(recv_params.cipher_suite,
+                                  recv_key, sizeof(recv_key)));
+  }
+  if (ret) {
+    LOG(LS_INFO) << "SRTP activated with negotiated parameters:"
+                 << " send cipher_suite " << send_params.cipher_suite
+                 << " recv cipher_suite " << recv_params.cipher_suite;
+    applied_send_params_ = send_params;
+    applied_recv_params_ = recv_params;
+  } else {
+    LOG(LS_WARNING) << "Failed to apply negotiated SRTP parameters";
+  }
+  return ret;
+}
+
+bool SrtpFilter::ResetParams() {
+  offer_params_.clear();
+  state_ = ST_INIT;
+  LOG(LS_INFO) << "SRTP reset to init state";
+  return true;
+}
+
+bool SrtpFilter::ParseKeyParams(const std::string& key_params,
+                                uint8* key, int len) {
+  // example key_params: "inline:YUJDZGVmZ2hpSktMbW9QUXJzVHVWd3l6MTIzNDU2"
+
+  // Fail if key-method is wrong.
+  if (key_params.find("inline:") != 0) {
+    return false;
+  }
+
+  // Fail if base64 decode fails, or the key is the wrong size.
+  std::string key_b64(key_params.substr(7)), key_str;
+  if (!talk_base::Base64::Decode(key_b64, talk_base::Base64::DO_STRICT,
+                                 &key_str, NULL) ||
+      static_cast<int>(key_str.size()) != len) {
+    return false;
+  }
+
+  memcpy(key, key_str.c_str(), len);
+  return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// SrtpSession
+
+#ifdef HAVE_SRTP
+
+bool SrtpSession::inited_ = false;
+
+SrtpSession::SrtpSession()
+    : session_(NULL),
+      rtp_auth_tag_len_(0),
+      rtcp_auth_tag_len_(0),
+      srtp_stat_(new SrtpStat()),
+      last_send_seq_num_(-1) {
+  sessions()->push_back(this);
+  SignalSrtpError.repeat(srtp_stat_->SignalSrtpError);
+}
+
+SrtpSession::~SrtpSession() {
+  sessions()->erase(std::find(sessions()->begin(), sessions()->end(), this));
+  if (session_) {
+    srtp_dealloc(session_);
+  }
+}
+
+bool SrtpSession::SetSend(const std::string& cs, const uint8* key, int len) {
+  return SetKey(ssrc_any_outbound, cs, key, len);
+}
+
+bool SrtpSession::SetRecv(const std::string& cs, const uint8* key, int len) {
+  return SetKey(ssrc_any_inbound, cs, key, len);
+}
+
+bool SrtpSession::ProtectRtp(void* p, int in_len, int max_len, int* out_len) {
+  if (!session_) {
+    LOG(LS_WARNING) << "Failed to protect SRTP packet: no SRTP Session";
+    return false;
+  }
+
+  int need_len = in_len + rtp_auth_tag_len_;  // NOLINT
+  if (max_len < need_len) {
+    LOG(LS_WARNING) << "Failed to protect SRTP packet: The buffer length "
+                    << max_len << " is less than the needed " << need_len;
+    return false;
+  }
+
+  *out_len = in_len;
+  int err = srtp_protect(session_, p, out_len);
+  uint32 ssrc;
+  if (GetRtpSsrc(p, in_len, &ssrc)) {
+    srtp_stat_->AddProtectRtpResult(ssrc, err);
+  }
+  int seq_num;
+  GetRtpSeqNum(p, in_len, &seq_num);
+  if (err != err_status_ok) {
+    LOG(LS_WARNING) << "Failed to protect SRTP packet, seqnum="
+                    << seq_num << ", err=" << err << ", last seqnum="
+                    << last_send_seq_num_;
+    return false;
+  }
+  last_send_seq_num_ = seq_num;
+  return true;
+}
+
+bool SrtpSession::ProtectRtcp(void* p, int in_len, int max_len, int* out_len) {
+  if (!session_) {
+    LOG(LS_WARNING) << "Failed to protect SRTCP packet: no SRTP Session";
+    return false;
+  }
+
+  int need_len = in_len + sizeof(uint32) + rtcp_auth_tag_len_;  // NOLINT
+  if (max_len < need_len) {
+    LOG(LS_WARNING) << "Failed to protect SRTCP packet: The buffer length "
+                    << max_len << " is less than the needed " << need_len;
+    return false;
+  }
+
+  *out_len = in_len;
+  int err = srtp_protect_rtcp(session_, p, out_len);
+  srtp_stat_->AddProtectRtcpResult(err);
+  if (err != err_status_ok) {
+    LOG(LS_WARNING) << "Failed to protect SRTCP packet, err=" << err;
+    return false;
+  }
+  return true;
+}
+
+bool SrtpSession::UnprotectRtp(void* p, int in_len, int* out_len) {
+  if (!session_) {
+    LOG(LS_WARNING) << "Failed to unprotect SRTP packet: no SRTP Session";
+    return false;
+  }
+
+  *out_len = in_len;
+  int err = srtp_unprotect(session_, p, out_len);
+  uint32 ssrc;
+  if (GetRtpSsrc(p, in_len, &ssrc)) {
+    srtp_stat_->AddUnprotectRtpResult(ssrc, err);
+  }
+  if (err != err_status_ok) {
+    LOG(LS_WARNING) << "Failed to unprotect SRTP packet, err=" << err;
+    return false;
+  }
+  return true;
+}
+
+bool SrtpSession::UnprotectRtcp(void* p, int in_len, int* out_len) {
+  if (!session_) {
+    LOG(LS_WARNING) << "Failed to unprotect SRTCP packet: no SRTP Session";
+    return false;
+  }
+
+  *out_len = in_len;
+  int err = srtp_unprotect_rtcp(session_, p, out_len);
+  srtp_stat_->AddUnprotectRtcpResult(err);
+  if (err != err_status_ok) {
+    LOG(LS_WARNING) << "Failed to unprotect SRTCP packet, err=" << err;
+    return false;
+  }
+  return true;
+}
+
+void SrtpSession::set_signal_silent_time(uint32 signal_silent_time_in_ms) {
+  srtp_stat_->set_signal_silent_time(signal_silent_time_in_ms);
+}
+
+bool SrtpSession::SetKey(int type, const std::string& cs,
+                         const uint8* key, int len) {
+  if (session_) {
+    LOG(LS_ERROR) << "Failed to create SRTP session: "
+                  << "SRTP session already created";
+    return false;
+  }
+
+  if (!Init()) {
+    return false;
+  }
+
+  srtp_policy_t policy;
+  memset(&policy, 0, sizeof(policy));
+
+  if (cs == CS_AES_CM_128_HMAC_SHA1_80) {
+    crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtp);
+    crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtcp);
+  } else if (cs == CS_AES_CM_128_HMAC_SHA1_32) {
+    crypto_policy_set_aes_cm_128_hmac_sha1_32(&policy.rtp);   // rtp is 32,
+    crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtcp);  // rtcp still 80
+  } else {
+    LOG(LS_WARNING) << "Failed to create SRTP session: unsupported"
+                    << " cipher_suite " << cs.c_str();
+    return false;
+  }
+
+  if (!key || len != SRTP_MASTER_KEY_LEN) {
+    LOG(LS_WARNING) << "Failed to create SRTP session: invalid key";
+    return false;
+  }
+
+  policy.ssrc.type = static_cast<ssrc_type_t>(type);
+  policy.ssrc.value = 0;
+  policy.key = const_cast<uint8*>(key);
+  // TODO(astor) parse window size from WSH session-param
+  policy.window_size = 1024;
+  policy.allow_repeat_tx = 1;
+  policy.next = NULL;
+
+  int err = srtp_create(&session_, &policy);
+  if (err != err_status_ok) {
+    LOG(LS_ERROR) << "Failed to create SRTP session, err=" << err;
+    return false;
+  }
+
+  rtp_auth_tag_len_ = policy.rtp.auth_tag_len;
+  rtcp_auth_tag_len_ = policy.rtcp.auth_tag_len;
+  return true;
+}
+
+bool SrtpSession::Init() {
+  if (!inited_) {
+    int err;
+    err = srtp_init();
+    if (err != err_status_ok) {
+      LOG(LS_ERROR) << "Failed to init SRTP, err=" << err;
+      return false;
+    }
+
+    err = srtp_install_event_handler(&SrtpSession::HandleEventThunk);
+    if (err != err_status_ok) {
+      LOG(LS_ERROR) << "Failed to install SRTP event handler, err=" << err;
+      return false;
+    }
+
+    inited_ = true;
+  }
+
+  return true;
+}
+
+void SrtpSession::HandleEvent(const srtp_event_data_t* ev) {
+  switch (ev->event) {
+    case event_ssrc_collision:
+      LOG(LS_INFO) << "SRTP event: SSRC collision";
+      break;
+    case event_key_soft_limit:
+      LOG(LS_INFO) << "SRTP event: reached soft key usage limit";
+      break;
+    case event_key_hard_limit:
+      LOG(LS_INFO) << "SRTP event: reached hard key usage limit";
+      break;
+    case event_packet_index_limit:
+      LOG(LS_INFO) << "SRTP event: reached hard packet limit (2^48 packets)";
+      break;
+    default:
+      LOG(LS_INFO) << "SRTP event: unknown " << ev->event;
+      break;
+  }
+}
+
+void SrtpSession::HandleEventThunk(srtp_event_data_t* ev) {
+  for (std::list<SrtpSession*>::iterator it = sessions()->begin();
+       it != sessions()->end(); ++it) {
+    if ((*it)->session_ == ev->session) {
+      (*it)->HandleEvent(ev);
+      break;
+    }
+  }
+}
+
+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.";
+}
+
+SrtpSession::~SrtpSession() {
+}
+
+bool SrtpSession::SetSend(const std::string& cs, const uint8* key, int len) {
+  return SrtpNotAvailable(__FUNCTION__);
+}
+
+bool SrtpSession::SetRecv(const std::string& cs, const uint8* key, int len) {
+  return SrtpNotAvailable(__FUNCTION__);
+}
+
+bool SrtpSession::ProtectRtp(void* data, int in_len, int max_len,
+                             int* out_len) {
+  return SrtpNotAvailable(__FUNCTION__);
+}
+
+bool SrtpSession::ProtectRtcp(void* data, int in_len, int max_len,
+                              int* out_len) {
+  return SrtpNotAvailable(__FUNCTION__);
+}
+
+bool SrtpSession::UnprotectRtp(void* data, int in_len, int* out_len) {
+  return SrtpNotAvailable(__FUNCTION__);
+}
+
+bool SrtpSession::UnprotectRtcp(void* data, int in_len, int* out_len) {
+  return SrtpNotAvailable(__FUNCTION__);
+}
+
+void SrtpSession::set_signal_silent_time(uint32 signal_silent_time) {
+  // Do nothing.
+}
+
+#endif  // HAVE_SRTP
+
+///////////////////////////////////////////////////////////////////////////////
+// SrtpStat
+
+#ifdef HAVE_SRTP
+
+SrtpStat::SrtpStat()
+    : signal_silent_time_(1000) {
+}
+
+void SrtpStat::AddProtectRtpResult(uint32 ssrc, int result) {
+  FailureKey key;
+  key.ssrc = ssrc;
+  key.mode = SrtpFilter::PROTECT;
+  switch (result) {
+    case err_status_ok:
+      key.error = SrtpFilter::ERROR_NONE;
+      break;
+    case err_status_auth_fail:
+      key.error = SrtpFilter::ERROR_AUTH;
+      break;
+    default:
+      key.error = SrtpFilter::ERROR_FAIL;
+  }
+  HandleSrtpResult(key);
+}
+
+void SrtpStat::AddUnprotectRtpResult(uint32 ssrc, int result) {
+  FailureKey key;
+  key.ssrc = ssrc;
+  key.mode = SrtpFilter::UNPROTECT;
+  switch (result) {
+    case err_status_ok:
+      key.error = SrtpFilter::ERROR_NONE;
+      break;
+    case err_status_auth_fail:
+      key.error = SrtpFilter::ERROR_AUTH;
+      break;
+    case err_status_replay_fail:
+    case err_status_replay_old:
+      key.error = SrtpFilter::ERROR_REPLAY;
+      break;
+    default:
+      key.error = SrtpFilter::ERROR_FAIL;
+  }
+  HandleSrtpResult(key);
+}
+
+void SrtpStat::AddProtectRtcpResult(int result) {
+  AddProtectRtpResult(0U, result);
+}
+
+void SrtpStat::AddUnprotectRtcpResult(int result) {
+  AddUnprotectRtpResult(0U, result);
+}
+
+void SrtpStat::HandleSrtpResult(const SrtpStat::FailureKey& key) {
+  // Handle some cases where error should be signalled right away. For other
+  // errors, trigger error for the first time seeing it.  After that, silent
+  // the same error for a certain amount of time (default 1 sec).
+  if (key.error != SrtpFilter::ERROR_NONE) {
+    // For errors, signal first time and wait for 1 sec.
+    FailureStat* stat = &(failures_[key]);
+    uint32 current_time = talk_base::Time();
+    if (stat->last_signal_time == 0 ||
+        talk_base::TimeDiff(current_time, stat->last_signal_time) >
+        static_cast<int>(signal_silent_time_)) {
+      SignalSrtpError(key.ssrc, key.mode, key.error);
+      stat->last_signal_time = current_time;
+    }
+  }
+}
+
+#else   // !HAVE_SRTP
+
+// On some systems, SRTP is not (yet) available.
+
+SrtpStat::SrtpStat()
+    : signal_silent_time_(1000) {
+  LOG(WARNING) << "SRTP implementation is missing.";
+}
+
+void SrtpStat::AddProtectRtpResult(uint32 ssrc, int result) {
+  SrtpNotAvailable(__FUNCTION__);
+}
+
+void SrtpStat::AddUnprotectRtpResult(uint32 ssrc, int result) {
+  SrtpNotAvailable(__FUNCTION__);
+}
+
+void SrtpStat::AddProtectRtcpResult(int result) {
+  SrtpNotAvailable(__FUNCTION__);
+}
+
+void SrtpStat::AddUnprotectRtcpResult(int result) {
+  SrtpNotAvailable(__FUNCTION__);
+}
+
+void SrtpStat::HandleSrtpResult(const SrtpStat::FailureKey& key) {
+  SrtpNotAvailable(__FUNCTION__);
+}
+
+#endif  // HAVE_SRTP
+
+}  // namespace cricket
diff --git a/talk/session/media/srtpfilter.h b/talk/session/media/srtpfilter.h
new file mode 100644
index 0000000..7d97eff
--- /dev/null
+++ b/talk/session/media/srtpfilter.h
@@ -0,0 +1,304 @@
+/*
+ * 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_MEDIA_SRTPFILTER_H_
+#define TALK_SESSION_MEDIA_SRTPFILTER_H_
+
+#include <list>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslotrepeater.h"
+#include "talk/media/base/cryptoparams.h"
+#include "talk/p2p/base/sessiondescription.h"
+
+// Forward declaration to avoid pulling in libsrtp headers here
+struct srtp_event_data_t;
+struct srtp_ctx_t;
+typedef srtp_ctx_t* srtp_t;
+struct srtp_policy_t;
+
+namespace cricket {
+
+// Cipher suite to use for SRTP. Typically a 80-bit HMAC will be used, except
+// in applications (voice) where the additional bandwidth may be significant.
+// A 80-bit HMAC is always used for SRTCP.
+// 128-bit AES with 80-bit SHA-1 HMAC.
+extern const char CS_AES_CM_128_HMAC_SHA1_80[];
+// 128-bit AES with 32-bit SHA-1 HMAC.
+extern const char CS_AES_CM_128_HMAC_SHA1_32[];
+// Key is 128 bits and salt is 112 bits == 30 bytes. B64 bloat => 40 bytes.
+extern const int SRTP_MASTER_KEY_BASE64_LEN;
+
+// Needed for DTLS-SRTP
+extern const int SRTP_MASTER_KEY_KEY_LEN;
+extern const int SRTP_MASTER_KEY_SALT_LEN;
+
+class SrtpSession;
+class SrtpStat;
+
+void EnableSrtpDebugging();
+
+// Class to transform SRTP to/from RTP.
+// Initialize by calling SetSend with the local security params, then call
+// SetRecv once the remote security params are received. At that point
+// Protect/UnprotectRt(c)p can be called to encrypt/decrypt data.
+// TODO: Figure out concurrency policy for SrtpFilter.
+class SrtpFilter {
+ public:
+  enum Mode {
+    PROTECT,
+    UNPROTECT
+  };
+  enum Error {
+    ERROR_NONE,
+    ERROR_FAIL,
+    ERROR_AUTH,
+    ERROR_REPLAY,
+  };
+
+  SrtpFilter();
+  ~SrtpFilter();
+
+  // Whether the filter is active (i.e. crypto has been properly negotiated).
+  bool IsActive() const;
+
+  // Indicates which crypto algorithms and keys were contained in the offer.
+  // offer_params should contain a list of available parameters to use, or none,
+  // if crypto is not desired. This must be called before SetAnswer.
+  bool SetOffer(const std::vector<CryptoParams>& offer_params,
+                ContentSource source);
+  // Same as SetAnwer. But multiple calls are allowed to SetProvisionalAnswer
+  // after a call to SetOffer.
+  bool SetProvisionalAnswer(const std::vector<CryptoParams>& answer_params,
+                            ContentSource source);
+  // Indicates which crypto algorithms and keys were contained in the answer.
+  // answer_params should contain the negotiated parameters, which may be none,
+  // if crypto was not desired or could not be negotiated (and not required).
+  // This must be called after SetOffer. If crypto negotiation completes
+  // successfully, this will advance the filter to the active state.
+  bool SetAnswer(const std::vector<CryptoParams>& answer_params,
+                 ContentSource source);
+
+  // 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);
+  bool ProtectRtcp(void* data, int in_len, int max_len, int* out_len);
+  // Decrypts/verifies an invidiual RTP/RTCP packet.
+  // If an HMAC is used, this will decrease the packet size.
+  bool UnprotectRtp(void* data, int in_len, int* out_len);
+  bool UnprotectRtcp(void* data, int in_len, int* out_len);
+
+  // Update the silent threshold (in ms) for signaling errors.
+  void set_signal_silent_time(uint32 signal_silent_time_in_ms);
+
+  sigslot::repeater3<uint32, Mode, Error> SignalSrtpError;
+
+ protected:
+  bool ExpectOffer(ContentSource source);
+  bool StoreParams(const std::vector<CryptoParams>& params,
+                   ContentSource source);
+  bool ExpectAnswer(ContentSource source);
+  bool DoSetAnswer(const std::vector<CryptoParams>& answer_params,
+                     ContentSource source,
+                     bool final);
+  void CreateSrtpSessions();
+  bool NegotiateParams(const std::vector<CryptoParams>& answer_params,
+                       CryptoParams* selected_params);
+  bool ApplyParams(const CryptoParams& send_params,
+                   const CryptoParams& recv_params);
+  bool ResetParams();
+  static bool ParseKeyParams(const std::string& params, uint8* key, int len);
+
+ private:
+  enum State {
+    ST_INIT,           // SRTP filter unused.
+    ST_SENTOFFER,      // Offer with SRTP parameters sent.
+    ST_RECEIVEDOFFER,  // Offer with SRTP parameters received.
+    ST_SENTPRANSWER_NO_CRYPTO,  // Sent provisional answer without crypto.
+    // Received provisional answer without crypto.
+    ST_RECEIVEDPRANSWER_NO_CRYPTO,
+    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,
+    // SRTP filter is active but the sent answer is only provisional.
+    // When the final answer is set, the state transitions to ST_ACTIVE or
+    // ST_INIT.
+    ST_SENTPRANSWER,
+    // SRTP filter is active but the received answer is only provisional.
+    // When the final answer is set, the state transitions to ST_ACTIVE or
+    // ST_INIT.
+    ST_RECEIVEDPRANSWER
+  };
+  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_;
+  CryptoParams applied_send_params_;
+  CryptoParams applied_recv_params_;
+};
+
+// Class that wraps a libSRTP session.
+class SrtpSession {
+ public:
+  SrtpSession();
+  ~SrtpSession();
+
+  // Configures the session for sending data using the specified
+  // cipher-suite and key. Receiving must be done by a separate session.
+  bool SetSend(const std::string& cs, const uint8* key, int len);
+  // Configures the session for receiving data using the specified
+  // cipher-suite and key. Sending must be done by a separate session.
+  bool SetRecv(const std::string& cs, const uint8* key, int len);
+
+  // Encrypts/signs an individual RTP/RTCP packet, in-place.
+  // If an HMAC is used, this will increase the packet size.
+  bool ProtectRtp(void* data, int in_len, int max_len, int* out_len);
+  bool ProtectRtcp(void* data, int in_len, int max_len, int* out_len);
+  // Decrypts/verifies an invidiual RTP/RTCP packet.
+  // If an HMAC is used, this will decrease the packet size.
+  bool UnprotectRtp(void* data, int in_len, int* out_len);
+  bool UnprotectRtcp(void* data, int in_len, int* out_len);
+
+  // Update the silent threshold (in ms) for signaling errors.
+  void set_signal_silent_time(uint32 signal_silent_time_in_ms);
+
+  sigslot::repeater3<uint32, SrtpFilter::Mode, SrtpFilter::Error>
+      SignalSrtpError;
+
+ private:
+  bool SetKey(int type, const std::string& cs, const uint8* key, int len);
+  static bool Init();
+  void HandleEvent(const srtp_event_data_t* ev);
+  static void HandleEventThunk(srtp_event_data_t* ev);
+  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_;
+  int last_send_seq_num_;
+  DISALLOW_COPY_AND_ASSIGN(SrtpSession);
+};
+
+// Class that collects failures of SRTP.
+class SrtpStat {
+ public:
+  SrtpStat();
+
+  // Report RTP protection results to the handler.
+  void AddProtectRtpResult(uint32 ssrc, int result);
+  // Report RTP unprotection results to the handler.
+  void AddUnprotectRtpResult(uint32 ssrc, int result);
+  // Report RTCP protection results to the handler.
+  void AddProtectRtcpResult(int result);
+  // Report RTCP unprotection results to the handler.
+  void AddUnprotectRtcpResult(int result);
+
+  // Get silent time (in ms) for SRTP statistics handler.
+  uint32 signal_silent_time() const { return signal_silent_time_; }
+  // Set silent time (in ms) for SRTP statistics handler.
+  void set_signal_silent_time(uint32 signal_silent_time) {
+    signal_silent_time_ = signal_silent_time;
+  }
+
+  // Sigslot for reporting errors.
+  sigslot::signal3<uint32, SrtpFilter::Mode, SrtpFilter::Error>
+      SignalSrtpError;
+
+ private:
+  // For each different ssrc and error, we collect statistics separately.
+  struct FailureKey {
+    FailureKey()
+        : ssrc(0),
+          mode(SrtpFilter::PROTECT),
+          error(SrtpFilter::ERROR_NONE) {
+    }
+    FailureKey(uint32 in_ssrc, SrtpFilter::Mode in_mode,
+               SrtpFilter::Error in_error)
+        : ssrc(in_ssrc),
+          mode(in_mode),
+          error(in_error) {
+    }
+    bool operator <(const FailureKey& key) const {
+      return ssrc < key.ssrc || mode < key.mode || error < key.error;
+    }
+    uint32 ssrc;
+    SrtpFilter::Mode mode;
+    SrtpFilter::Error error;
+  };
+  // For tracing conditions for signaling, currently we only use
+  // last_signal_time.  Wrap this as a struct so that later on, if we need any
+  // other improvements, it will be easier.
+  struct FailureStat {
+    FailureStat()
+        : last_signal_time(0) {
+    }
+    explicit FailureStat(uint32 in_last_signal_time)
+        : last_signal_time(in_last_signal_time) {
+    }
+    void Reset() {
+      last_signal_time = 0;
+    }
+    uint32 last_signal_time;
+  };
+
+  // Inspect SRTP result and signal error if needed.
+  void HandleSrtpResult(const FailureKey& key);
+
+  std::map<FailureKey, FailureStat> failures_;
+  // Threshold in ms to silent the signaling errors.
+  uint32 signal_silent_time_;
+
+  DISALLOW_COPY_AND_ASSIGN(SrtpStat);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_MEDIA_SRTPFILTER_H_
diff --git a/talk/session/media/srtpfilter_unittest.cc b/talk/session/media/srtpfilter_unittest.cc
new file mode 100644
index 0000000..1b4aef2
--- /dev/null
+++ b/talk/session/media/srtpfilter_unittest.cc
@@ -0,0 +1,863 @@
+/*
+ * 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/byteorder.h"
+#include "talk/base/gunit.h"
+#include "talk/base/thread.h"
+#include "talk/media/base/cryptoparams.h"
+#include "talk/media/base/fakertp.h"
+#include "talk/p2p/base/sessiondescription.h"
+#include "talk/session/media/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_.SetAnswer(answer, CS_LOCAL));
+  EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we can set offer multiple times from the same source.
+TEST_F(SrtpFilterTest, TestGoodSetupMultipleOffers) {
+  EXPECT_TRUE(f1_.SetOffer(MakeVector(kTestCryptoParams1), CS_LOCAL));
+  EXPECT_TRUE(f1_.SetOffer(MakeVector(kTestCryptoParams2), CS_LOCAL));
+  EXPECT_TRUE(f1_.SetAnswer(MakeVector(kTestCryptoParams2), CS_REMOTE));
+  EXPECT_TRUE(f1_.IsActive());
+  EXPECT_TRUE(f1_.SetOffer(MakeVector(kTestCryptoParams1), CS_LOCAL));
+  EXPECT_TRUE(f1_.SetOffer(MakeVector(kTestCryptoParams2), CS_LOCAL));
+  EXPECT_TRUE(f1_.SetAnswer(MakeVector(kTestCryptoParams2), CS_REMOTE));
+
+  EXPECT_TRUE(f2_.SetOffer(MakeVector(kTestCryptoParams1), CS_REMOTE));
+  EXPECT_TRUE(f2_.SetOffer(MakeVector(kTestCryptoParams2), CS_REMOTE));
+  EXPECT_TRUE(f2_.SetAnswer(MakeVector(kTestCryptoParams2), CS_LOCAL));
+  EXPECT_TRUE(f2_.IsActive());
+  EXPECT_TRUE(f2_.SetOffer(MakeVector(kTestCryptoParams1), CS_REMOTE));
+  EXPECT_TRUE(f2_.SetOffer(MakeVector(kTestCryptoParams2), CS_REMOTE));
+  EXPECT_TRUE(f2_.SetAnswer(MakeVector(kTestCryptoParams2), CS_LOCAL));
+}
+// Test that we can't set offer multiple times from different sources.
+TEST_F(SrtpFilterTest, TestBadSetupMultipleOffers) {
+  EXPECT_TRUE(f1_.SetOffer(MakeVector(kTestCryptoParams1), CS_LOCAL));
+  EXPECT_FALSE(f1_.SetOffer(MakeVector(kTestCryptoParams2), CS_REMOTE));
+  EXPECT_TRUE(f1_.SetAnswer(MakeVector(kTestCryptoParams1), CS_REMOTE));
+  EXPECT_TRUE(f1_.IsActive());
+  EXPECT_TRUE(f1_.SetOffer(MakeVector(kTestCryptoParams2), CS_LOCAL));
+  EXPECT_FALSE(f1_.SetOffer(MakeVector(kTestCryptoParams1), CS_REMOTE));
+  EXPECT_TRUE(f1_.SetAnswer(MakeVector(kTestCryptoParams2), CS_REMOTE));
+
+  EXPECT_TRUE(f2_.SetOffer(MakeVector(kTestCryptoParams2), CS_REMOTE));
+  EXPECT_FALSE(f2_.SetOffer(MakeVector(kTestCryptoParams1), CS_LOCAL));
+  EXPECT_TRUE(f2_.SetAnswer(MakeVector(kTestCryptoParams2), CS_LOCAL));
+  EXPECT_TRUE(f2_.IsActive());
+  EXPECT_TRUE(f2_.SetOffer(MakeVector(kTestCryptoParams2), CS_REMOTE));
+  EXPECT_FALSE(f2_.SetOffer(MakeVector(kTestCryptoParams1), CS_LOCAL));
+  EXPECT_TRUE(f2_.SetAnswer(MakeVector(kTestCryptoParams2), CS_LOCAL));
+}
+
+// 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 setting the same CryptoParams again on
+// one side.
+TEST_F(SrtpFilterTest, TestSettingSameKeyOnOneSide) {
+  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);
+
+  // Re-applying the same keys on one end and it should not reset the ROC.
+  EXPECT_TRUE(f2_.SetOffer(offer, CS_REMOTE));
+  EXPECT_TRUE(f2_.SetAnswer(answer, CS_LOCAL));
+  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_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 send and receive provisional answers with crypto enabled.
+// Also test that we can change the crypto.
+TEST_F(SrtpFilterTest, TestProvisionalAnswer) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  offer.push_back(kTestCryptoParams1);
+  offer[1].tag = 2;
+  offer[1].cipher_suite = CS_AES_CM_128_HMAC_SHA1_32;
+  std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_TRUE(f2_.SetOffer(offer, CS_REMOTE));
+  EXPECT_TRUE(f2_.SetProvisionalAnswer(answer, CS_LOCAL));
+  EXPECT_TRUE(f1_.SetProvisionalAnswer(answer, CS_REMOTE));
+  EXPECT_TRUE(f1_.IsActive());
+  EXPECT_TRUE(f2_.IsActive());
+  TestProtectUnprotect(CS_AES_CM_128_HMAC_SHA1_80, CS_AES_CM_128_HMAC_SHA1_80);
+
+  answer[0].key_params = kTestKeyParams4;
+  answer[0].tag = 2;
+  answer[0].cipher_suite = CS_AES_CM_128_HMAC_SHA1_32;
+  EXPECT_TRUE(f2_.SetAnswer(answer, CS_LOCAL));
+  EXPECT_TRUE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_TRUE(f1_.IsActive());
+  EXPECT_TRUE(f2_.IsActive());
+  TestProtectUnprotect(CS_AES_CM_128_HMAC_SHA1_32, CS_AES_CM_128_HMAC_SHA1_32);
+}
+
+// Test that a provisional answer doesn't need to contain a crypto.
+TEST_F(SrtpFilterTest, TestProvisionalAnswerWithoutCrypto) {
+  std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+  std::vector<CryptoParams> answer;
+
+  EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+  EXPECT_TRUE(f2_.SetOffer(offer, CS_REMOTE));
+  EXPECT_TRUE(f2_.SetProvisionalAnswer(answer, CS_LOCAL));
+  EXPECT_TRUE(f1_.SetProvisionalAnswer(answer, CS_REMOTE));
+  EXPECT_FALSE(f1_.IsActive());
+  EXPECT_FALSE(f2_.IsActive());
+
+  answer.push_back(kTestCryptoParams2);
+  EXPECT_TRUE(f2_.SetAnswer(answer, CS_LOCAL));
+  EXPECT_TRUE(f1_.SetAnswer(answer, CS_REMOTE));
+  EXPECT_TRUE(f1_.IsActive());
+  EXPECT_TRUE(f2_.IsActive());
+  TestProtectUnprotect(CS_AES_CM_128_HMAC_SHA1_80, CS_AES_CM_128_HMAC_SHA1_80);
+}
+
+// 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/media/ssrcmuxfilter.cc b/talk/session/media/ssrcmuxfilter.cc
new file mode 100644
index 0000000..638167d
--- /dev/null
+++ b/talk/session/media/ssrcmuxfilter.cc
@@ -0,0 +1,93 @@
+/*
+ * 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/media/ssrcmuxfilter.h"
+
+#include <algorithm>
+
+#include "talk/base/logging.h"
+#include "talk/media/base/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 {
+  if (ssrc == 0) {
+    return false;
+  }
+  return (GetStreamBySsrc(streams_, ssrc, NULL));
+}
+
+}  // namespace cricket
diff --git a/talk/session/media/ssrcmuxfilter.h b/talk/session/media/ssrcmuxfilter.h
new file mode 100644
index 0000000..9420f54
--- /dev/null
+++ b/talk/session/media/ssrcmuxfilter.h
@@ -0,0 +1,67 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_MEDIA_SSRCMUXFILTER_H_
+#define TALK_SESSION_MEDIA_SSRCMUXFILTER_H_
+
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/media/base/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_MEDIA_SSRCMUXFILTER_H_
diff --git a/talk/session/media/ssrcmuxfilter_unittest.cc b/talk/session/media/ssrcmuxfilter_unittest.cc
new file mode 100644
index 0000000..85a4dbe
--- /dev/null
+++ b/talk/session/media/ssrcmuxfilter_unittest.cc
@@ -0,0 +1,184 @@
+/*
+ * 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/session/media/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/media/typewrapping.h.pump b/talk/session/media/typewrapping.h.pump
new file mode 100644
index 0000000..3b52927
--- /dev/null
+++ b/talk/session/media/typewrapping.h.pump
@@ -0,0 +1,297 @@
+// To generate typewrapping.h from typewrapping.h.pump, execute:
+// /home/build/google3/third_party/gtest/scripts/pump.py typewrapping.h.pump
+
+// Copyright 2009 Google Inc.
+// Author: tschmelcher@google.com (Tristan Schmelcher)
+//
+// A template meta-programming framework for customizable rule-based
+// type-checking of type wrappers and wrapper functions.
+//
+// This framework is useful in a scenario where there are a set of types that
+// you choose to "wrap" by implementing new preferred types such that the new
+// and the old can be converted back and forth in some way, but you already have
+// a library of functions that expect the original types. Example:
+//
+// Type A wraps X
+// Type B wraps Y
+// Type C wraps Z
+//
+// And function X Foo(Y, Z) exists.
+//
+// Since A, B, and C are preferred, you choose to implement a wrapper function
+// with this interface:
+//
+// A Foo2(B, C)
+//
+// However, this can lead to subtle discrepancies, because if the interface to
+// Foo ever changes then Foo2 may become out-of-sync. e.g., Foo might have
+// originally returned void, but later is changed to return an error code. If
+// the programmer forgets to change Foo2, the code will probably still work, but
+// with an implicit cast to void inserted by the compiler, potentially leading
+// to run-time errors or errors in usage.
+//
+// The purpose of this library is to prevent these discrepancies from occurring.
+// You use it as follows:
+//
+// First, declare a new wrapping ruleset:
+//
+// DECLARE_WRAPPING_RULESET(ruleset_name)
+//
+// Then declare rules on what types wrap which other types and how to convert
+// them:
+//
+// DECLARE_WRAPPER(ruleset_name, A, X, variable_name, wrapping_code,
+//     unwrapping_code)
+//
+// Where wrapping_code and unwrapping_code are expressions giving the code to
+// use to wrap and unwrap a variable with the name "variable_name". There are
+// also some helper macros to declare common wrapping schemes.
+//
+// Then implement your wrapped functions like this:
+//
+// A Foo_Wrapped(B b, C c) {
+//   return WRAP_CALL2(ruleset_name, A, Foo, B, b, C, c);
+// }
+//
+// WRAP_CALL2 will unwrap b and c (if B and C are wrapped types) and call Foo,
+// then wrap the result to type A if different from the return type. More
+// importantly, if the types in Foo's interface do not _exactly_ match the
+// unwrapped forms of A, B, and C (after typedef-equivalence), then you will get
+// a compile-time error for a static_cast from the real function type to the
+// expected one (except on Mac where this check is infeasible), and with no icky
+// template instantiation errors either!
+//
+// There are also macros to wrap/unwrap individual values according to whichever
+// rule applies to their types:
+//
+// WRAP(ruleset_name, A, X, value) // Compile-time error if no associated rule.
+//
+// UNWRAP(ruleset_name, A, value) // Infers X. If A is not a wrapper, no change.
+//
+// UNWRAP_TYPE(ruleset_name, A) // Evaluates to X.
+//
+//
+// Essentially, the library works by "storing" the DECLARE_WRAPPER calls in
+// template specializations. When the wrapper or unwrapper is invoked, the
+// normal C++ template system essentially "looks up" the rule for the given
+// type(s).
+//
+// All of the auto-generated code can be inlined to produce zero impact on
+// run-time performance and code size (though some compilers may require
+// gentle encouragement in order for them to do so).
+
+#ifndef TALK_SESSION_PHONE_TYPEWRAPPING_H_
+#define TALK_SESSION_PHONE_TYPEWRAPPING_H_
+
+#include "talk/base/common.h"
+
+#ifdef OSX
+// XCode's GCC doesn't respect typedef-equivalence when casting function pointer
+// types, so we can't enforce that the wrapped function signatures strictly
+// match the expected types. Instead we have to forego the nice user-friendly
+// static_cast check (because it will spuriously fail) and make the Call()
+// function into a member template below.
+#define CAST_FUNCTION_(function, ...) \
+  function
+#else
+#define CAST_FUNCTION_(function, ...) \
+  static_cast<__VA_ARGS__>(function)
+#endif
+
+// Internal helper macros.
+#define SMART_WRAPPER_(wrapper, toType, fromType, from) \
+  (wrapper<toType, fromType>::Wrap(from))
+
+#define SMART_UNWRAPPER_(unwrapper, fromType, from) \
+  (unwrapper<fromType>::Unwrap(from))
+
+#define SMART_UNWRAPPER_TYPE_(unwrapper, fromType) \
+  typename unwrapper<fromType>::ToType
+
+$var n = 27
+$range i 0..n
+
+$for i [[
+$range j 1..i
+
+// The code that follows wraps calls to $i-argument functions, unwrapping the
+// arguments and wrapping the return value as needed.
+
+// The usual case.
+template<
+    template <typename ToType, typename FromType> class Wrapper,
+    template <typename FromType> class Unwrapper,
+    typename ReturnType$for j [[,
+    typename ArgType$j]]>
+class SmartFunctionWrapper$i {
+ public:
+  typedef SMART_UNWRAPPER_TYPE_(Unwrapper, ReturnType) OriginalReturnType;
+
+$for j [[
+  typedef SMART_UNWRAPPER_TYPE_(Unwrapper, ArgType$j) OriginalArgType$j;
+
+]]
+  typedef OriginalReturnType (*OriginalFunctionType)($for j , [[
+
+      OriginalArgType$j]]);
+
+#ifdef OSX
+  template <typename F>
+  static FORCE_INLINE ReturnType Call(F function
+#else
+  static FORCE_INLINE ReturnType Call(OriginalFunctionType function
+#endif
+                                      $for j [[,
+                                      ArgType$j v$j]]) {
+    return SMART_WRAPPER_(Wrapper, ReturnType, OriginalReturnType,
+        (*function)($for j , [[
+
+            SMART_UNWRAPPER_(Unwrapper, ArgType$j, v$j)]]));
+  }
+};
+
+// Special case for functions that return void. (SMART_WRAPPER_ involves
+// passing the unwrapped value in a function call, which is not a legal thing to
+// do with void, so we need a special case here that doesn't call
+// SMART_WRAPPER_()).
+template<
+    template <typename ToType, typename FromType> class Wrapper,
+    template <typename FromType> class Unwrapper$for j [[,
+    typename ArgType$j]]>
+class SmartFunctionWrapper$i<
+    Wrapper,
+    Unwrapper,
+    void$for j [[,
+    ArgType$j]]> {
+ public:
+  typedef void OriginalReturnType;
+
+$for j [[
+  typedef SMART_UNWRAPPER_TYPE_(Unwrapper, ArgType$j) OriginalArgType$j;
+
+]]
+  typedef OriginalReturnType (*OriginalFunctionType)($for j , [[
+
+      OriginalArgType$j]]);
+
+#ifdef OSX
+  template <typename F>
+  static FORCE_INLINE void Call(F function
+#else
+  static FORCE_INLINE void Call(OriginalFunctionType function
+#endif
+                                $for j [[,
+                                ArgType$j v$j]]) {
+    (*function)($for j , [[
+
+        SMART_UNWRAPPER_(Unwrapper, ArgType$j, v$j)]]);
+  }
+};
+
+
+]]
+// Programmer interface follows. Only macros below here should be used outside
+// this file.
+
+#define DECLARE_WRAPPING_RULESET(ruleSet) \
+  namespace ruleSet { \
+\
+  /* SmartWrapper is for wrapping values. */ \
+  template<typename ToType, typename FromType> \
+  class SmartWrapper; \
+\
+  /* Special case where the types are the same. */ \
+  template<typename T1> \
+  class SmartWrapper<T1, T1> { \
+   public: \
+    static FORCE_INLINE T1 Wrap(T1 from) { \
+      return from; \
+    } \
+  }; \
+\
+  /* Class for unwrapping (i.e., going to the original value). This is done
+     function-style rather than predicate-style. The default rule is to leave
+     the type unchanged. */ \
+  template<typename FromType> \
+  class SmartUnwrapper { \
+   public: \
+    typedef FromType ToType; \
+    static FORCE_INLINE ToType Unwrap(FromType from) { \
+      return from; \
+    } \
+  }; \
+\
+  }
+
+// Declares a wrapping rule.
+#define DECLARE_WRAPPER(ruleSet, wrappedType, unwrappedType, var, wrapCode, unwrapCode) \
+  namespace ruleSet { \
+\
+  template<> \
+  class SmartWrapper<wrappedType, unwrappedType> { \
+   public: \
+    static FORCE_INLINE wrappedType Wrap(unwrappedType var) { \
+      return wrapCode; \
+    } \
+  }; \
+\
+  template<> \
+  class SmartUnwrapper<wrappedType> { \
+   public: \
+    typedef unwrappedType ToType; \
+    static FORCE_INLINE unwrappedType Unwrap(wrappedType var) { \
+      return unwrapCode; \
+    } \
+  }; \
+\
+  }
+
+// Helper macro for declaring a wrapper that wraps/unwraps with reinterpret_cast<>.
+#define DECLARE_WRAPPER_BY_REINTERPRET_CAST(ruleSet, wrappedType, unwrappedType) \
+  DECLARE_WRAPPER(ruleSet, wrappedType, unwrappedType, FROM, reinterpret_cast<wrappedType>(FROM), reinterpret_cast<unwrappedType>(FROM))
+
+// Helper macro for declaring a wrapper that wraps/unwraps implicitly.
+#define DECLARE_WRAPPER_BY_IMPLICIT_CAST(ruleSet, wrappedType, unwrappedType) \
+  DECLARE_WRAPPER(ruleSet, wrappedType, unwrappedType, FROM, FROM, FROM)
+
+// Helper macro for declaring that the pointer types for one type wrap the pointer types for another type.
+#define DECLARE_POINTER_WRAPPER(ruleSet, wrappedType, unwrappedType) \
+  DECLARE_WRAPPER_BY_REINTERPRET_CAST(ruleSet, wrappedType*, unwrappedType*) \
+  DECLARE_WRAPPER_BY_REINTERPRET_CAST(ruleSet, const wrappedType*, const unwrappedType*) \
+  DECLARE_WRAPPER_BY_REINTERPRET_CAST(ruleSet, wrappedType* const, unwrappedType* const) \
+  DECLARE_WRAPPER_BY_REINTERPRET_CAST(ruleSet, const wrappedType* const, const unwrappedType* const) \
+
+// Macro to wrap a single value.
+#define WRAP(ruleSet, toType, fromType, from) \
+  SMART_WRAPPER_(ruleSet::SmartWrapper, toType, fromType, from)
+
+// Macro to unwrap a single value.
+#define UNWRAP(ruleSet, fromType, from) \
+  SMART_UNWRAPPER_(ruleSet::SmartUnwrapper, fromType, from)
+
+// Macro to get the unwrapped form of a type.
+#define UNWRAP_TYPE(ruleSet, fromType) \
+  SMART_UNWRAPPER_TYPE_(ruleSet::SmartUnwrapper, from)
+
+// Macros to wrap function calls.
+
+$for i [[
+$range j 1..i
+#define WRAP_CALL$i(ruleSet, toType, function$for j [[, argType$j, arg$j]]) \
+  (SmartFunctionWrapper$i< \
+      ruleSet::SmartWrapper, \
+      ruleSet::SmartUnwrapper, \
+      toType$for j [[, \
+      argType$j]]>::Call( \
+          CAST_FUNCTION_( \
+              &function, \
+              SmartFunctionWrapper$i< \
+                  ruleSet::SmartWrapper, \
+                  ruleSet::SmartUnwrapper, \
+                  toType$for j [[, \
+                  argType$j]]>::OriginalFunctionType)$for j [[, \
+          arg$j]]))
+
+]]
+
+#endif  // TALK_SESSION_PHONE_TYPEWRAPPINGHELPERS_H_
diff --git a/talk/session/media/typingmonitor.cc b/talk/session/media/typingmonitor.cc
new file mode 100644
index 0000000..3c5d387
--- /dev/null
+++ b/talk/session/media/typingmonitor.cc
@@ -0,0 +1,123 @@
+/*
+ * 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/media/typingmonitor.h"
+
+#include "talk/base/logging.h"
+#include "talk/base/thread.h"
+#include "talk/session/media/channel.h"
+
+namespace cricket {
+
+TypingMonitor::TypingMonitor(VoiceChannel* channel,
+                             talk_base::Thread* worker_thread,
+                             const TypingMonitorOptions& settings)
+    : channel_(channel),
+      worker_thread_(worker_thread),
+      mute_period_(settings.mute_period),
+      muted_at_(0),
+      has_pending_unmute_(false) {
+  channel_->media_channel()->SignalMediaError.connect(
+      this, &TypingMonitor::OnVoiceChannelError);
+  channel_->media_channel()->SetTypingDetectionParameters(
+      settings.time_window, settings.cost_per_typing,
+      settings.reporting_threshold, settings.penalty_decay,
+      settings.type_event_delay);
+}
+
+TypingMonitor::~TypingMonitor() {
+  // Shortcut any pending unmutes.
+  if (has_pending_unmute_) {
+    talk_base::MessageList messages;
+    worker_thread_->Clear(this, 0, &messages);
+    ASSERT(messages.size() == 1);
+    channel_->MuteStream(0, false);
+    SignalMuted(channel_, false);
+  }
+}
+
+void TypingMonitor::OnVoiceChannelError(uint32 ssrc,
+                                        VoiceMediaChannel::Error error) {
+  if (error == VoiceMediaChannel::ERROR_REC_TYPING_NOISE_DETECTED &&
+      !channel_->IsStreamMuted(0)) {
+    // Please be careful and cognizant about threading issues when editing this
+    // code.  The MuteStream() call below is a ::Send and is synchronous as well
+    // as the muted signal that comes from this.  This function can be called
+    // from any thread.
+
+    // TODO(perkj): Refactor TypingMonitor and the MediaChannel to handle
+    // multiple sending audio streams. SSRC 0 means the default sending audio
+    // channel.
+    channel_->MuteStream(0, true);
+    SignalMuted(channel_, true);
+    has_pending_unmute_ = true;
+    muted_at_ = talk_base::Time();
+
+    worker_thread_->PostDelayed(mute_period_, this, 0);
+    LOG(LS_INFO) << "Muting for at least " << mute_period_ << "ms.";
+  }
+}
+
+/**
+ * If we mute due to detected typing and the user also mutes during our waiting
+ * period, we don't want to undo their mute.  So, clear our callback.  Should
+ * be called on the worker_thread.
+ */
+void TypingMonitor::OnChannelMuted() {
+  if (has_pending_unmute_) {
+    talk_base::MessageList removed;
+    worker_thread_->Clear(this, 0, &removed);
+    ASSERT(removed.size() == 1);
+    has_pending_unmute_ = false;
+  }
+}
+
+/**
+ * When the specified mute period has elapsed, unmute, or, if the user kept
+ * typing after the initial warning fired, wait for the remainder of time to
+ * elapse since they finished and try to unmute again.  Should be called on the
+ * worker thread.
+ */
+void TypingMonitor::OnMessage(talk_base::Message* msg) {
+  if (!channel_->IsStreamMuted(0) || !has_pending_unmute_) return;
+  int silence_period = channel_->media_channel()->GetTimeSinceLastTyping();
+  int expiry_time = mute_period_ - silence_period;
+  if (silence_period < 0 || expiry_time < 50) {
+    LOG(LS_INFO) << "Mute timeout hit, last typing " << silence_period
+                 << "ms ago, unmuting after " << talk_base::TimeSince(muted_at_)
+                 << "ms total.";
+    has_pending_unmute_ = false;
+    channel_->MuteStream(0, false);
+    SignalMuted(channel_, false);
+  } else {
+    LOG(LS_INFO) << "Mute timeout hit, last typing " << silence_period
+                 << "ms ago, check again in " << expiry_time << "ms.";
+    talk_base::Thread::Current()->PostDelayed(expiry_time, this, 0);
+  }
+}
+
+}  // namespace cricket
diff --git a/talk/session/media/typingmonitor.h b/talk/session/media/typingmonitor.h
new file mode 100644
index 0000000..c9b64e7
--- /dev/null
+++ b/talk/session/media/typingmonitor.h
@@ -0,0 +1,84 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_MEDIA_TYPINGMONITOR_H_
+#define TALK_SESSION_MEDIA_TYPINGMONITOR_H_
+
+#include "talk/base/messagehandler.h"
+#include "talk/media/base/mediachannel.h"
+
+namespace talk_base {
+class Thread;
+}
+
+namespace cricket {
+
+class VoiceChannel;
+class BaseChannel;
+
+struct TypingMonitorOptions {
+  int cost_per_typing;
+  int mute_period;
+  int penalty_decay;
+  int reporting_threshold;
+  int time_window;
+  int type_event_delay;
+  size_t min_participants;
+};
+
+/**
+ * An object that observes a channel and listens for typing detection warnings,
+ * which can be configured to mute audio capture of that channel for some period
+ * of time.  The purpose is to automatically mute someone if they are disturbing
+ * a conference with loud keystroke audio signals.
+ */
+class TypingMonitor
+    : public talk_base::MessageHandler, public sigslot::has_slots<> {
+ public:
+  TypingMonitor(VoiceChannel* channel, talk_base::Thread* worker_thread,
+                const TypingMonitorOptions& params);
+  ~TypingMonitor();
+
+  sigslot::signal2<BaseChannel*, bool> SignalMuted;
+
+  void OnChannelMuted();
+
+ private:
+  void OnVoiceChannelError(uint32 ssrc, VoiceMediaChannel::Error error);
+  void OnMessage(talk_base::Message* msg);
+
+  VoiceChannel* channel_;
+  talk_base::Thread* worker_thread_;
+  int mute_period_;
+  int muted_at_;
+  bool has_pending_unmute_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_MEDIA_TYPINGMONITOR_H_
+
diff --git a/talk/session/media/typingmonitor_unittest.cc b/talk/session/media/typingmonitor_unittest.cc
new file mode 100644
index 0000000..eb8c5bc
--- /dev/null
+++ b/talk/session/media/typingmonitor_unittest.cc
@@ -0,0 +1,92 @@
+/*
+ * 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/media/base/fakemediaengine.h"
+#include "talk/p2p/base/fakesession.h"
+#include "talk/session/media/channel.h"
+#include "talk/session/media/currentspeakermonitor.h"
+#include "talk/session/media/typingmonitor.h"
+
+namespace cricket {
+
+class TypingMonitorTest : public testing::Test {
+ protected:
+  TypingMonitorTest() : session_(true) {
+    vc_.reset(new VoiceChannel(talk_base::Thread::Current(), &engine_,
+                               engine_.CreateChannel(), &session_, "", false));
+    engine_.GetVoiceChannel(0)->set_time_since_last_typing(1000);
+
+    TypingMonitorOptions settings = {10, 20, 30, 40, 50};
+    monitor_.reset(new TypingMonitor(vc_.get(),
+                                     talk_base::Thread::Current(),
+                                     settings));
+  }
+
+  void TearDown() {
+    vc_.reset();
+  }
+
+  talk_base::scoped_ptr<TypingMonitor> monitor_;
+  talk_base::scoped_ptr<VoiceChannel> vc_;
+  FakeMediaEngine engine_;
+  FakeSession session_;
+};
+
+TEST_F(TypingMonitorTest, TestTriggerMute) {
+  EXPECT_FALSE(vc_->IsStreamMuted(0));
+  EXPECT_FALSE(engine_.GetVoiceChannel(0)->IsStreamMuted(0));
+
+  engine_.GetVoiceChannel(0)->TriggerError(0, VoiceMediaChannel::ERROR_OTHER);
+  EXPECT_FALSE(vc_->IsStreamMuted(0));
+  EXPECT_FALSE(engine_.GetVoiceChannel(0)->IsStreamMuted(0));
+
+  engine_.GetVoiceChannel(0)->TriggerError(
+      0, VoiceMediaChannel::ERROR_REC_TYPING_NOISE_DETECTED);
+  EXPECT_TRUE(vc_->IsStreamMuted(0));
+  EXPECT_TRUE(engine_.GetVoiceChannel(0)->IsStreamMuted(0));
+
+  EXPECT_TRUE_WAIT(!vc_->IsStreamMuted(0) &&
+                   !engine_.GetVoiceChannel(0)->IsStreamMuted(0), 100);
+}
+
+TEST_F(TypingMonitorTest, TestResetMonitor) {
+  engine_.GetVoiceChannel(0)->set_time_since_last_typing(1000);
+  EXPECT_FALSE(vc_->IsStreamMuted(0));
+  EXPECT_FALSE(engine_.GetVoiceChannel(0)->IsStreamMuted(0));
+
+  engine_.GetVoiceChannel(0)->TriggerError(
+      0, VoiceMediaChannel::ERROR_REC_TYPING_NOISE_DETECTED);
+  EXPECT_TRUE(vc_->IsStreamMuted(0));
+  EXPECT_TRUE(engine_.GetVoiceChannel(0)->IsStreamMuted(0));
+
+  monitor_.reset();
+  EXPECT_FALSE(vc_->IsStreamMuted(0));
+  EXPECT_FALSE(engine_.GetVoiceChannel(0)->IsStreamMuted(0));
+}
+
+}  // namespace cricket
diff --git a/talk/session/media/voicechannel.h b/talk/session/media/voicechannel.h
new file mode 100644
index 0000000..6c1b6af
--- /dev/null
+++ b/talk/session/media/voicechannel.h
@@ -0,0 +1,33 @@
+/*
+ * 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 _VOICECHANNEL_H_
+#define _VOICECHANNEL_H_
+
+#include "talk/session/media/channel.h"
+
+#endif // _VOICECHANNEL_H_
diff --git a/talk/session/tunnel/pseudotcpchannel.cc b/talk/session/tunnel/pseudotcpchannel.cc
new file mode 100644
index 0000000..8b9a19f
--- /dev/null
+++ b/talk/session/tunnel/pseudotcpchannel.cc
@@ -0,0 +1,600 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+#include "talk/base/basictypes.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stringutils.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/transportchannel.h"
+#include "pseudotcpchannel.h"
+
+using namespace talk_base;
+
+namespace cricket {
+
+extern const talk_base::ConstantLabel SESSION_STATES[];
+
+// MSG_WK_* - worker thread messages
+// MSG_ST_* - stream thread messages
+// MSG_SI_* - signal thread messages
+
+enum {
+  MSG_WK_CLOCK = 1,
+  MSG_WK_PURGE,
+  MSG_ST_EVENT,
+  MSG_SI_DESTROYCHANNEL,
+  MSG_SI_DESTROY,
+};
+
+struct EventData : public MessageData {
+  int event, error;
+  EventData(int ev, int err = 0) : event(ev), error(err) { }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// PseudoTcpChannel::InternalStream
+///////////////////////////////////////////////////////////////////////////////
+
+class PseudoTcpChannel::InternalStream : public StreamInterface {
+public:
+  InternalStream(PseudoTcpChannel* parent);
+  virtual ~InternalStream();
+
+  virtual StreamState GetState() const;
+  virtual StreamResult Read(void* buffer, size_t buffer_len,
+                                       size_t* read, int* error);
+  virtual StreamResult Write(const void* data, size_t data_len,
+                                        size_t* written, int* error);
+  virtual void Close();
+
+private:
+  // parent_ is accessed and modified exclusively on the event thread, to
+  // avoid thread contention.  This means that the PseudoTcpChannel cannot go
+  // away until after it receives a Close() from TunnelStream.
+  PseudoTcpChannel* parent_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// PseudoTcpChannel
+// Member object lifetime summaries:
+//   session_ - passed in constructor, cleared when channel_ goes away.
+//   channel_ - created in Connect, destroyed when session_ or tcp_ goes away.
+//   tcp_ - created in Connect, destroyed when channel_ goes away, or connection
+//     closes.
+//   worker_thread_ - created when channel_ is created, purged when channel_ is
+//     destroyed.
+//   stream_ - created in GetStream, destroyed by owner at arbitrary time.
+//   this - created in constructor, destroyed when worker_thread_ and stream_
+//     are both gone.
+///////////////////////////////////////////////////////////////////////////////
+
+//
+// Signal thread methods
+//
+
+PseudoTcpChannel::PseudoTcpChannel(Thread* stream_thread, Session* session)
+  : signal_thread_(session->session_manager()->signaling_thread()),
+    worker_thread_(NULL),
+    stream_thread_(stream_thread),
+    session_(session), channel_(NULL), tcp_(NULL), stream_(NULL),
+    stream_readable_(false), pending_read_event_(false),
+    ready_to_connect_(false) {
+  ASSERT(signal_thread_->IsCurrent());
+  ASSERT(NULL != session_);
+}
+
+PseudoTcpChannel::~PseudoTcpChannel() {
+  ASSERT(signal_thread_->IsCurrent());
+  ASSERT(worker_thread_ == NULL);
+  ASSERT(session_ == NULL);
+  ASSERT(channel_ == NULL);
+  ASSERT(stream_ == NULL);
+  ASSERT(tcp_ == NULL);
+}
+
+bool PseudoTcpChannel::Connect(const std::string& content_name,
+                               const std::string& channel_name,
+                               int component) {
+  ASSERT(signal_thread_->IsCurrent());
+  CritScope lock(&cs_);
+
+  if (channel_)
+    return false;
+
+  ASSERT(session_ != NULL);
+  worker_thread_ = session_->session_manager()->worker_thread();
+  content_name_ = content_name;
+  channel_ = session_->CreateChannel(
+      content_name, channel_name, component);
+  channel_name_ = channel_name;
+  channel_->SetOption(Socket::OPT_DONTFRAGMENT, 1);
+
+  channel_->SignalDestroyed.connect(this,
+    &PseudoTcpChannel::OnChannelDestroyed);
+  channel_->SignalWritableState.connect(this,
+    &PseudoTcpChannel::OnChannelWritableState);
+  channel_->SignalReadPacket.connect(this,
+    &PseudoTcpChannel::OnChannelRead);
+  channel_->SignalRouteChange.connect(this,
+    &PseudoTcpChannel::OnChannelConnectionChanged);
+
+  ASSERT(tcp_ == NULL);
+  tcp_ = new PseudoTcp(this, 0);
+  if (session_->initiator()) {
+    // Since we may try several protocols and network adapters that won't work,
+    // waiting until we get our first writable notification before initiating
+    // TCP negotiation.
+    ready_to_connect_ = true;
+  }
+
+  return true;
+}
+
+StreamInterface* PseudoTcpChannel::GetStream() {
+  ASSERT(signal_thread_->IsCurrent());
+  CritScope lock(&cs_);
+  ASSERT(NULL != session_);
+  if (!stream_)
+    stream_ = new PseudoTcpChannel::InternalStream(this);
+  //TODO("should we disallow creation of new stream at some point?");
+  return stream_;
+}
+
+void PseudoTcpChannel::OnChannelDestroyed(TransportChannel* channel) {
+  LOG_F(LS_INFO) << "(" << channel->component() << ")";
+  ASSERT(signal_thread_->IsCurrent());
+  CritScope lock(&cs_);
+  ASSERT(channel == channel_);
+  signal_thread_->Clear(this, MSG_SI_DESTROYCHANNEL);
+  // When MSG_WK_PURGE is received, we know there will be no more messages from
+  // the worker thread.
+  worker_thread_->Clear(this, MSG_WK_CLOCK);
+  worker_thread_->Post(this, MSG_WK_PURGE);
+  session_ = NULL;
+  channel_ = NULL;
+  if ((stream_ != NULL)
+      && ((tcp_ == NULL) || (tcp_->State() != PseudoTcp::TCP_CLOSED)))
+    stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_CLOSE, 0));
+  if (tcp_) {
+    tcp_->Close(true);
+    AdjustClock();
+  }
+  SignalChannelClosed(this);
+}
+
+void PseudoTcpChannel::OnSessionTerminate(Session* session) {
+  // When the session terminates before we even connected
+  CritScope lock(&cs_);
+  if (session_ != NULL && channel_ == NULL) {
+    ASSERT(session == session_);
+    ASSERT(worker_thread_ == NULL);
+    ASSERT(tcp_ == NULL);
+    LOG(LS_INFO) << "Destroying unconnected PseudoTcpChannel";
+    session_ = NULL;
+    if (stream_ != NULL)
+      stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_CLOSE, -1));
+  }
+
+  // Even though session_ is being destroyed, we mustn't clear the pointer,
+  // since we'll need it to tear down channel_.
+  //
+  // TODO: Is it always the case that if channel_ != NULL then we'll get
+  // a channel-destroyed notification?
+}
+
+void PseudoTcpChannel::GetOption(PseudoTcp::Option opt, int* value) {
+  ASSERT(signal_thread_->IsCurrent());
+  CritScope lock(&cs_);
+  ASSERT(tcp_ != NULL);
+  tcp_->GetOption(opt, value);
+}
+
+void PseudoTcpChannel::SetOption(PseudoTcp::Option opt, int value) {
+  ASSERT(signal_thread_->IsCurrent());
+  CritScope lock(&cs_);
+  ASSERT(tcp_ != NULL);
+  tcp_->SetOption(opt, value);
+}
+
+//
+// Stream thread methods
+//
+
+StreamState PseudoTcpChannel::GetState() const {
+  ASSERT(stream_ != NULL && stream_thread_->IsCurrent());
+  CritScope lock(&cs_);
+  if (!session_)
+    return SS_CLOSED;
+  if (!tcp_)
+    return SS_OPENING;
+  switch (tcp_->State()) {
+    case PseudoTcp::TCP_LISTEN:
+    case PseudoTcp::TCP_SYN_SENT:
+    case PseudoTcp::TCP_SYN_RECEIVED:
+      return SS_OPENING;
+    case PseudoTcp::TCP_ESTABLISHED:
+      return SS_OPEN;
+    case PseudoTcp::TCP_CLOSED:
+    default:
+      return SS_CLOSED;
+  }
+}
+
+StreamResult PseudoTcpChannel::Read(void* buffer, size_t buffer_len,
+                                    size_t* read, int* error) {
+  ASSERT(stream_ != NULL && stream_thread_->IsCurrent());
+  CritScope lock(&cs_);
+  if (!tcp_)
+    return SR_BLOCK;
+
+  stream_readable_ = false;
+  int result = tcp_->Recv(static_cast<char*>(buffer), buffer_len);
+  //LOG_F(LS_VERBOSE) << "Recv returned: " << result;
+  if (result > 0) {
+    if (read)
+      *read = result;
+    // PseudoTcp doesn't currently support repeated Readable signals.  Simulate
+    // them here.
+    stream_readable_ = true;
+    if (!pending_read_event_) {
+      pending_read_event_ = true;
+      stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_READ), true);
+    }
+    return SR_SUCCESS;
+  } else if (IsBlockingError(tcp_->GetError())) {
+    return SR_BLOCK;
+  } else {
+    if (error)
+      *error = tcp_->GetError();
+    return SR_ERROR;
+  }
+  // This spot is never reached.
+}
+
+StreamResult PseudoTcpChannel::Write(const void* data, size_t data_len,
+                                     size_t* written, int* error) {
+  ASSERT(stream_ != NULL && stream_thread_->IsCurrent());
+  CritScope lock(&cs_);
+  if (!tcp_)
+    return SR_BLOCK;
+  int result = tcp_->Send(static_cast<const char*>(data), data_len);
+  //LOG_F(LS_VERBOSE) << "Send returned: " << result;
+  if (result > 0) {
+    if (written)
+      *written = result;
+    return SR_SUCCESS;
+  } else if (IsBlockingError(tcp_->GetError())) {
+    return SR_BLOCK;
+  } else {
+    if (error)
+      *error = tcp_->GetError();
+    return SR_ERROR;
+  }
+  // This spot is never reached.
+}
+
+void PseudoTcpChannel::Close() {
+  ASSERT(stream_ != NULL && stream_thread_->IsCurrent());
+  CritScope lock(&cs_);
+  stream_ = NULL;
+  // Clear out any pending event notifications
+  stream_thread_->Clear(this, MSG_ST_EVENT);
+  if (tcp_) {
+    tcp_->Close(false);
+    AdjustClock();
+  } else {
+    CheckDestroy();
+  }
+}
+
+//
+// Worker thread methods
+//
+
+void PseudoTcpChannel::OnChannelWritableState(TransportChannel* channel) {
+  LOG_F(LS_VERBOSE) << "[" << channel_name_ << "]";
+  ASSERT(worker_thread_->IsCurrent());
+  CritScope lock(&cs_);
+  if (!channel_) {
+    LOG_F(LS_WARNING) << "NULL channel";
+    return;
+  }
+  ASSERT(channel == channel_);
+  if (!tcp_) {
+    LOG_F(LS_WARNING) << "NULL tcp";
+    return;
+  }
+  if (!ready_to_connect_ || !channel->writable())
+    return;
+
+  ready_to_connect_ = false;
+  tcp_->Connect();
+  AdjustClock();
+}
+
+void PseudoTcpChannel::OnChannelRead(TransportChannel* channel,
+                                     const char* data, size_t size, int flags) {
+  //LOG_F(LS_VERBOSE) << "(" << size << ")";
+  ASSERT(worker_thread_->IsCurrent());
+  CritScope lock(&cs_);
+  if (!channel_) {
+    LOG_F(LS_WARNING) << "NULL channel";
+    return;
+  }
+  ASSERT(channel == channel_);
+  if (!tcp_) {
+    LOG_F(LS_WARNING) << "NULL tcp";
+    return;
+  }
+  tcp_->NotifyPacket(data, size);
+  AdjustClock();
+}
+
+void PseudoTcpChannel::OnChannelConnectionChanged(TransportChannel* channel,
+                                                  const Candidate& candidate) {
+  LOG_F(LS_VERBOSE) << "[" << channel_name_ << "]";
+  ASSERT(worker_thread_->IsCurrent());
+  CritScope lock(&cs_);
+  if (!channel_) {
+    LOG_F(LS_WARNING) << "NULL channel";
+    return;
+  }
+  ASSERT(channel == channel_);
+  if (!tcp_) {
+    LOG_F(LS_WARNING) << "NULL tcp";
+    return;
+  }
+
+  uint16 mtu = 1280;  // safe default
+  int family = candidate.address().family();
+  Socket* socket =
+      worker_thread_->socketserver()->CreateAsyncSocket(family, SOCK_DGRAM);
+  talk_base::scoped_ptr<Socket> mtu_socket(socket);
+  if (socket == NULL) {
+    LOG_F(LS_WARNING) << "Couldn't create socket while estimating MTU.";
+  } else {
+    if (mtu_socket->Connect(candidate.address()) < 0 ||
+        mtu_socket->EstimateMTU(&mtu) < 0) {
+      LOG_F(LS_WARNING) << "Failed to estimate MTU, error="
+                        << mtu_socket->GetError();
+    }
+  }
+
+  LOG_F(LS_VERBOSE) << "Using MTU of " << mtu << " bytes";
+  tcp_->NotifyMTU(mtu);
+  AdjustClock();
+}
+
+void PseudoTcpChannel::OnTcpOpen(PseudoTcp* tcp) {
+  LOG_F(LS_VERBOSE) << "[" << channel_name_ << "]";
+  ASSERT(cs_.CurrentThreadIsOwner());
+  ASSERT(worker_thread_->IsCurrent());
+  ASSERT(tcp == tcp_);
+  if (stream_) {
+    stream_readable_ = true;
+    pending_read_event_ = true;
+    stream_thread_->Post(this, MSG_ST_EVENT,
+                         new EventData(SE_OPEN | SE_READ | SE_WRITE));
+  }
+}
+
+void PseudoTcpChannel::OnTcpReadable(PseudoTcp* tcp) {
+  //LOG_F(LS_VERBOSE);
+  ASSERT(cs_.CurrentThreadIsOwner());
+  ASSERT(worker_thread_->IsCurrent());
+  ASSERT(tcp == tcp_);
+  if (stream_) {
+    stream_readable_ = true;
+    if (!pending_read_event_) {
+      pending_read_event_ = true;
+      stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_READ));
+    }
+  }
+}
+
+void PseudoTcpChannel::OnTcpWriteable(PseudoTcp* tcp) {
+  //LOG_F(LS_VERBOSE);
+  ASSERT(cs_.CurrentThreadIsOwner());
+  ASSERT(worker_thread_->IsCurrent());
+  ASSERT(tcp == tcp_);
+  if (stream_)
+    stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_WRITE));
+}
+
+void PseudoTcpChannel::OnTcpClosed(PseudoTcp* tcp, uint32 nError) {
+  LOG_F(LS_VERBOSE) << "[" << channel_name_ << "]";
+  ASSERT(cs_.CurrentThreadIsOwner());
+  ASSERT(worker_thread_->IsCurrent());
+  ASSERT(tcp == tcp_);
+  if (stream_)
+    stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_CLOSE, nError));
+}
+
+//
+// Multi-thread methods
+//
+
+void PseudoTcpChannel::OnMessage(Message* pmsg) {
+  if (pmsg->message_id == MSG_WK_CLOCK) {
+
+    ASSERT(worker_thread_->IsCurrent());
+    //LOG(LS_INFO) << "PseudoTcpChannel::OnMessage(MSG_WK_CLOCK)";
+    CritScope lock(&cs_);
+    if (tcp_) {
+      tcp_->NotifyClock(PseudoTcp::Now());
+      AdjustClock(false);
+    }
+
+  } else if (pmsg->message_id == MSG_WK_PURGE) {
+
+    ASSERT(worker_thread_->IsCurrent());
+    LOG_F(LS_INFO) << "(MSG_WK_PURGE)";
+    // At this point, we know there are no additional worker thread messages.
+    CritScope lock(&cs_);
+    ASSERT(NULL == session_);
+    ASSERT(NULL == channel_);
+    worker_thread_ = NULL;
+    CheckDestroy();
+
+  } else if (pmsg->message_id == MSG_ST_EVENT) {
+
+    ASSERT(stream_thread_->IsCurrent());
+    //LOG(LS_INFO) << "PseudoTcpChannel::OnMessage(MSG_ST_EVENT, "
+    //             << data->event << ", " << data->error << ")";
+    ASSERT(stream_ != NULL);
+    EventData* data = static_cast<EventData*>(pmsg->pdata);
+    if (data->event & SE_READ) {
+      CritScope lock(&cs_);
+      pending_read_event_ = false;
+    }
+    stream_->SignalEvent(stream_, data->event, data->error);
+    delete data;
+
+  } else if (pmsg->message_id == MSG_SI_DESTROYCHANNEL) {
+
+    ASSERT(signal_thread_->IsCurrent());
+    LOG_F(LS_INFO) << "(MSG_SI_DESTROYCHANNEL)";
+    ASSERT(session_ != NULL);
+    ASSERT(channel_ != NULL);
+    session_->DestroyChannel(content_name_, channel_->component());
+
+  } else if (pmsg->message_id == MSG_SI_DESTROY) {
+
+    ASSERT(signal_thread_->IsCurrent());
+    LOG_F(LS_INFO) << "(MSG_SI_DESTROY)";
+    // The message queue is empty, so it is safe to destroy ourselves.
+    delete this;
+
+  } else {
+    ASSERT(false);
+  }
+}
+
+IPseudoTcpNotify::WriteResult PseudoTcpChannel::TcpWritePacket(
+    PseudoTcp* tcp, const char* buffer, size_t len) {
+  ASSERT(cs_.CurrentThreadIsOwner());
+  ASSERT(tcp == tcp_);
+  ASSERT(NULL != channel_);
+  int sent = channel_->SendPacket(buffer, len);
+  if (sent > 0) {
+    //LOG_F(LS_VERBOSE) << "(" << sent << ") Sent";
+    return IPseudoTcpNotify::WR_SUCCESS;
+  } else if (IsBlockingError(channel_->GetError())) {
+    LOG_F(LS_VERBOSE) << "Blocking";
+    return IPseudoTcpNotify::WR_SUCCESS;
+  } else if (channel_->GetError() == EMSGSIZE) {
+    LOG_F(LS_ERROR) << "EMSGSIZE";
+    return IPseudoTcpNotify::WR_TOO_LARGE;
+  } else {
+    PLOG(LS_ERROR, channel_->GetError()) << "PseudoTcpChannel::TcpWritePacket";
+    ASSERT(false);
+    return IPseudoTcpNotify::WR_FAIL;
+  }
+}
+
+void PseudoTcpChannel::AdjustClock(bool clear) {
+  ASSERT(cs_.CurrentThreadIsOwner());
+  ASSERT(NULL != tcp_);
+
+  long timeout = 0;
+  if (tcp_->GetNextClock(PseudoTcp::Now(), timeout)) {
+    ASSERT(NULL != channel_);
+    // Reset the next clock, by clearing the old and setting a new one.
+    if (clear)
+      worker_thread_->Clear(this, MSG_WK_CLOCK);
+    worker_thread_->PostDelayed(_max(timeout, 0L), this, MSG_WK_CLOCK);
+    return;
+  }
+
+  delete tcp_;
+  tcp_ = NULL;
+  ready_to_connect_ = false;
+
+  if (channel_) {
+    // If TCP has failed, no need for channel_ anymore
+    signal_thread_->Post(this, MSG_SI_DESTROYCHANNEL);
+  }
+}
+
+void PseudoTcpChannel::CheckDestroy() {
+  ASSERT(cs_.CurrentThreadIsOwner());
+  if ((worker_thread_ != NULL) || (stream_ != NULL))
+    return;
+  signal_thread_->Post(this, MSG_SI_DESTROY);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// PseudoTcpChannel::InternalStream
+///////////////////////////////////////////////////////////////////////////////
+
+PseudoTcpChannel::InternalStream::InternalStream(PseudoTcpChannel* parent)
+  : parent_(parent) {
+}
+
+PseudoTcpChannel::InternalStream::~InternalStream() {
+  Close();
+}
+
+StreamState PseudoTcpChannel::InternalStream::GetState() const {
+  if (!parent_)
+    return SS_CLOSED;
+  return parent_->GetState();
+}
+
+StreamResult PseudoTcpChannel::InternalStream::Read(
+    void* buffer, size_t buffer_len, size_t* read, int* error) {
+  if (!parent_) {
+    if (error)
+      *error = ENOTCONN;
+    return SR_ERROR;
+  }
+  return parent_->Read(buffer, buffer_len, read, error);
+}
+
+StreamResult PseudoTcpChannel::InternalStream::Write(
+    const void* data, size_t data_len,  size_t* written, int* error) {
+  if (!parent_) {
+    if (error)
+      *error = ENOTCONN;
+    return SR_ERROR;
+  }
+  return parent_->Write(data, data_len, written, error);
+}
+
+void PseudoTcpChannel::InternalStream::Close() {
+  if (!parent_)
+    return;
+  parent_->Close();
+  parent_ = NULL;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace cricket
diff --git a/talk/session/tunnel/pseudotcpchannel.h b/talk/session/tunnel/pseudotcpchannel.h
new file mode 100644
index 0000000..a540699
--- /dev/null
+++ b/talk/session/tunnel/pseudotcpchannel.h
@@ -0,0 +1,140 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_TUNNEL_PSEUDOTCPCHANNEL_H_
+#define TALK_SESSION_TUNNEL_PSEUDOTCPCHANNEL_H_
+
+#include "talk/base/criticalsection.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/stream.h"
+#include "talk/p2p/base/pseudotcp.h"
+#include "talk/p2p/base/session.h"
+
+namespace talk_base {
+class Thread;
+}
+
+namespace cricket {
+
+class Candidate;
+class TransportChannel;
+
+///////////////////////////////////////////////////////////////////////////////
+// PseudoTcpChannel
+// Note: The PseudoTcpChannel must persist until both of:
+// 1) The StreamInterface provided via GetStream has been closed.
+//    This is tracked via non-null stream_.
+// 2) The PseudoTcp session has completed.
+//    This is tracked via non-null worker_thread_.  When PseudoTcp is done,
+//    the TransportChannel is signalled to tear-down.  Once the channel is
+//    torn down, the worker thread is purged.
+// These indicators are checked by CheckDestroy, invoked whenever one of them
+// changes.
+///////////////////////////////////////////////////////////////////////////////
+// PseudoTcpChannel::GetStream
+// Note: The stream pointer returned by GetStream is owned by the caller.
+// They can close & immediately delete the stream while PseudoTcpChannel still
+// has cleanup work to do.  They can also close the stream but not delete it
+// until long after PseudoTcpChannel has finished.  We must cope with both.
+///////////////////////////////////////////////////////////////////////////////
+
+class PseudoTcpChannel
+    : public IPseudoTcpNotify,
+      public talk_base::MessageHandler,
+      public sigslot::has_slots<> {
+ public:
+  // Signal thread methods
+  PseudoTcpChannel(talk_base::Thread* stream_thread,
+                   Session* session);
+
+  bool Connect(const std::string& content_name,
+               const std::string& channel_name,
+               int component);
+  talk_base::StreamInterface* GetStream();
+
+  sigslot::signal1<PseudoTcpChannel*> SignalChannelClosed;
+
+  // Call this when the Session used to create this channel is being torn
+  // down, to ensure that things get cleaned up properly.
+  void OnSessionTerminate(Session* session);
+
+  // See the PseudoTcp class for available options.
+  void GetOption(PseudoTcp::Option opt, int* value);
+  void SetOption(PseudoTcp::Option opt, int value);
+
+ private:
+  class InternalStream;
+  friend class InternalStream;
+
+  virtual ~PseudoTcpChannel();
+
+  // Stream thread methods
+  talk_base::StreamState GetState() const;
+  talk_base::StreamResult Read(void* buffer, size_t buffer_len,
+                               size_t* read, int* error);
+  talk_base::StreamResult Write(const void* data, size_t data_len,
+                                size_t* written, int* error);
+  void Close();
+
+  // Multi-thread methods
+  void OnMessage(talk_base::Message* pmsg);
+  void AdjustClock(bool clear = true);
+  void CheckDestroy();
+
+  // Signal thread methods
+  void OnChannelDestroyed(TransportChannel* channel);
+
+  // Worker thread methods
+  void OnChannelWritableState(TransportChannel* channel);
+  void OnChannelRead(TransportChannel* channel, const char* data, size_t size,
+                     int flags);
+  void OnChannelConnectionChanged(TransportChannel* channel,
+                                  const Candidate& candidate);
+
+  virtual void OnTcpOpen(PseudoTcp* ptcp);
+  virtual void OnTcpReadable(PseudoTcp* ptcp);
+  virtual void OnTcpWriteable(PseudoTcp* ptcp);
+  virtual void OnTcpClosed(PseudoTcp* ptcp, uint32 nError);
+  virtual IPseudoTcpNotify::WriteResult TcpWritePacket(PseudoTcp* tcp,
+                                                       const char* buffer,
+                                                       size_t len);
+
+  talk_base::Thread* signal_thread_, * worker_thread_, * stream_thread_;
+  Session* session_;
+  TransportChannel* channel_;
+  std::string content_name_;
+  std::string channel_name_;
+  PseudoTcp* tcp_;
+  InternalStream* stream_;
+  bool stream_readable_, pending_read_event_;
+  bool ready_to_connect_;
+  mutable talk_base::CriticalSection cs_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_TUNNEL_PSEUDOTCPCHANNEL_H_
diff --git a/talk/session/tunnel/securetunnelsessionclient.cc b/talk/session/tunnel/securetunnelsessionclient.cc
new file mode 100644
index 0000000..9287d22
--- /dev/null
+++ b/talk/session/tunnel/securetunnelsessionclient.cc
@@ -0,0 +1,387 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// SecureTunnelSessionClient and SecureTunnelSession implementation.
+
+#include "talk/session/tunnel/securetunnelsessionclient.h"
+#include "talk/base/basicdefs.h"
+#include "talk/base/basictypes.h"
+#include "talk/base/common.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/sslidentity.h"
+#include "talk/base/sslstreamadapter.h"
+#include "talk/p2p/base/transportchannel.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/session/tunnel/pseudotcpchannel.h"
+
+namespace cricket {
+
+// XML elements and namespaces for XMPP stanzas used in content exchanges.
+
+const char NS_SECURE_TUNNEL[] = "http://www.google.com/talk/securetunnel";
+const buzz::StaticQName QN_SECURE_TUNNEL_DESCRIPTION =
+    { NS_SECURE_TUNNEL, "description" };
+const buzz::StaticQName QN_SECURE_TUNNEL_TYPE =
+    { NS_SECURE_TUNNEL, "type" };
+const buzz::StaticQName QN_SECURE_TUNNEL_CLIENT_CERT =
+    { NS_SECURE_TUNNEL, "client-cert" };
+const buzz::StaticQName QN_SECURE_TUNNEL_SERVER_CERT =
+    { NS_SECURE_TUNNEL, "server-cert" };
+const char CN_SECURE_TUNNEL[] = "securetunnel";
+
+// SecureTunnelContentDescription
+
+// TunnelContentDescription is extended to hold string forms of the
+// client and server certificate, PEM encoded.
+
+struct SecureTunnelContentDescription : public ContentDescription {
+  std::string description;
+  std::string client_pem_certificate;
+  std::string server_pem_certificate;
+
+  SecureTunnelContentDescription(const std::string& desc,
+                                 const std::string& client_pem_cert,
+                                 const std::string& server_pem_cert)
+      : description(desc),
+        client_pem_certificate(client_pem_cert),
+        server_pem_certificate(server_pem_cert) {
+  }
+  virtual ContentDescription* Copy() const {
+    return new SecureTunnelContentDescription(*this);
+  }
+};
+
+// SecureTunnelSessionClient
+
+SecureTunnelSessionClient::SecureTunnelSessionClient(
+    const buzz::Jid& jid, SessionManager* manager)
+    : TunnelSessionClient(jid, manager, NS_SECURE_TUNNEL) {
+}
+
+void SecureTunnelSessionClient::SetIdentity(talk_base::SSLIdentity* identity) {
+  ASSERT(identity_.get() == NULL);
+  identity_.reset(identity);
+}
+
+bool SecureTunnelSessionClient::GenerateIdentity() {
+  ASSERT(identity_.get() == NULL);
+  identity_.reset(talk_base::SSLIdentity::Generate(
+      // The name on the certificate does not matter: the peer will
+      // make sure the cert it gets during SSL negotiation matches the
+      // one it got from XMPP. It would be neat to put something
+      // recognizable in there such as the JID, except this will show
+      // in clear during the SSL negotiation and so it could be a
+      // privacy issue. Specifying an empty string here causes
+      // it to use a random string.
+#ifdef _DEBUG
+      jid().Str()
+#else
+      ""
+#endif
+      ));
+  if (identity_.get() == NULL) {
+    LOG(LS_ERROR) << "Failed to generate SSL identity";
+    return false;
+  }
+  return true;
+}
+
+talk_base::SSLIdentity& SecureTunnelSessionClient::GetIdentity() const {
+  ASSERT(identity_.get() != NULL);
+  return *identity_;
+}
+
+// Parses a certificate from a PEM encoded string.
+// Returns NULL on failure.
+// The caller is responsible for freeing the returned object.
+static talk_base::SSLCertificate* ParseCertificate(
+    const std::string& pem_cert) {
+  if (pem_cert.empty())
+    return NULL;
+  return talk_base::SSLCertificate::FromPEMString(pem_cert);
+}
+
+TunnelSession* SecureTunnelSessionClient::MakeTunnelSession(
+    Session* session, talk_base::Thread* stream_thread,
+    TunnelSessionRole role) {
+  return new SecureTunnelSession(this, session, stream_thread, role);
+}
+
+bool FindSecureTunnelContent(const cricket::SessionDescription* sdesc,
+                             std::string* name,
+                             const SecureTunnelContentDescription** content) {
+  const ContentInfo* cinfo = sdesc->FirstContentByType(NS_SECURE_TUNNEL);
+  if (cinfo == NULL)
+    return false;
+
+  *name = cinfo->name;
+  *content = static_cast<const SecureTunnelContentDescription*>(
+      cinfo->description);
+  return true;
+}
+
+void SecureTunnelSessionClient::OnIncomingTunnel(const buzz::Jid &jid,
+                                                 Session *session) {
+  std::string content_name;
+  const SecureTunnelContentDescription* content = NULL;
+  if (!FindSecureTunnelContent(session->remote_description(),
+                               &content_name, &content)) {
+    ASSERT(false);
+  }
+
+  // Validate the certificate
+  talk_base::scoped_ptr<talk_base::SSLCertificate> peer_cert(
+      ParseCertificate(content->client_pem_certificate));
+  if (peer_cert.get() == NULL) {
+    LOG(LS_ERROR)
+        << "Rejecting incoming secure tunnel with invalid cetificate";
+    DeclineTunnel(session);
+    return;
+  }
+  // If there were a convenient place we could have cached the
+  // peer_cert so as not to have to parse it a second time when
+  // configuring the tunnel.
+  SignalIncomingTunnel(this, jid, content->description, session);
+}
+
+// The XML representation of a session initiation request (XMPP IQ),
+// containing the initiator's SecureTunnelContentDescription,
+// looks something like this:
+// <iq from="INITIATOR@gmail.com/pcpE101B7F4"
+//       to="RECIPIENT@gmail.com/pcp8B87F0A3"
+//       type="set" id="3">
+//   <session xmlns="http://www.google.com/session"
+//       type="initiate" id="2508605813"
+//       initiator="INITIATOR@gmail.com/pcpE101B7F4">
+//     <description xmlns="http://www.google.com/talk/securetunnel">
+//       <type>send:filename</type>
+//       <client-cert>
+//         -----BEGIN CERTIFICATE-----
+//         INITIATOR'S CERTIFICATE IN PERM FORMAT (ASCII GIBBERISH)
+//         -----END CERTIFICATE-----
+//       </client-cert>
+//     </description>
+//     <transport xmlns="http://www.google.com/transport/p2p"/>
+//   </session>
+// </iq>
+
+// The session accept iq, containing the recipient's certificate and
+// echoing the initiator's certificate, looks something like this:
+// <iq from="RECIPIENT@gmail.com/pcpE101B7F4"
+//     to="INITIATOR@gmail.com/pcpE101B7F4"
+//     type="set" id="5">
+//   <session xmlns="http://www.google.com/session"
+//       type="accept" id="2508605813"
+//       initiator="sdoyon911@gmail.com/pcpE101B7F4">
+//     <description xmlns="http://www.google.com/talk/securetunnel">
+//       <type>send:FILENAME</type>
+//       <client-cert>
+//         -----BEGIN CERTIFICATE-----
+//         INITIATOR'S CERTIFICATE IN PERM FORMAT (ASCII GIBBERISH)
+//         -----END CERTIFICATE-----
+//       </client-cert>
+//       <server-cert>
+//         -----BEGIN CERTIFICATE-----
+//         RECIPIENT'S CERTIFICATE IN PERM FORMAT (ASCII GIBBERISH)
+//         -----END CERTIFICATE-----
+//       </server-cert>
+//     </description>
+//   </session>
+// </iq>
+
+
+bool SecureTunnelSessionClient::ParseContent(SignalingProtocol protocol,
+                                             const buzz::XmlElement* elem,
+                                             ContentDescription** content,
+                                             ParseError* error) {
+  const buzz::XmlElement* type_elem = elem->FirstNamed(QN_SECURE_TUNNEL_TYPE);
+
+  if (type_elem == NULL)
+    // Missing mandatory XML element.
+    return false;
+
+  // Here we consider the certificate components to be optional. In
+  // practice the client certificate is always present, and the server
+  // certificate is initially missing from the session description
+  // sent during session initiation. OnAccept() will enforce that we
+  // have a certificate for our peer.
+  const buzz::XmlElement* client_cert_elem =
+      elem->FirstNamed(QN_SECURE_TUNNEL_CLIENT_CERT);
+  const buzz::XmlElement* server_cert_elem =
+      elem->FirstNamed(QN_SECURE_TUNNEL_SERVER_CERT);
+  *content = new SecureTunnelContentDescription(
+      type_elem->BodyText(),
+      client_cert_elem ? client_cert_elem->BodyText() : "",
+      server_cert_elem ? server_cert_elem->BodyText() : "");
+  return true;
+}
+
+bool SecureTunnelSessionClient::WriteContent(
+    SignalingProtocol protocol, const ContentDescription* untyped_content,
+    buzz::XmlElement** elem, WriteError* error) {
+  const SecureTunnelContentDescription* content =
+      static_cast<const SecureTunnelContentDescription*>(untyped_content);
+
+  buzz::XmlElement* root =
+      new buzz::XmlElement(QN_SECURE_TUNNEL_DESCRIPTION, true);
+  buzz::XmlElement* type_elem = new buzz::XmlElement(QN_SECURE_TUNNEL_TYPE);
+  type_elem->SetBodyText(content->description);
+  root->AddElement(type_elem);
+  if (!content->client_pem_certificate.empty()) {
+    buzz::XmlElement* client_cert_elem =
+        new buzz::XmlElement(QN_SECURE_TUNNEL_CLIENT_CERT);
+    client_cert_elem->SetBodyText(content->client_pem_certificate);
+    root->AddElement(client_cert_elem);
+  }
+  if (!content->server_pem_certificate.empty()) {
+    buzz::XmlElement* server_cert_elem =
+        new buzz::XmlElement(QN_SECURE_TUNNEL_SERVER_CERT);
+    server_cert_elem->SetBodyText(content->server_pem_certificate);
+    root->AddElement(server_cert_elem);
+  }
+  *elem = root;
+  return true;
+}
+
+SessionDescription* NewSecureTunnelSessionDescription(
+    const std::string& content_name, ContentDescription* content) {
+  SessionDescription* sdesc = new SessionDescription();
+  sdesc->AddContent(content_name, NS_SECURE_TUNNEL, content);
+  return sdesc;
+}
+
+SessionDescription* SecureTunnelSessionClient::CreateOffer(
+    const buzz::Jid &jid, const std::string &description) {
+  // We are the initiator so we are the client. Put our cert into the
+  // description.
+  std::string pem_cert = GetIdentity().certificate().ToPEMString();
+  return NewSecureTunnelSessionDescription(
+      CN_SECURE_TUNNEL,
+      new SecureTunnelContentDescription(description, pem_cert, ""));
+}
+
+SessionDescription* SecureTunnelSessionClient::CreateAnswer(
+    const SessionDescription* offer) {
+  std::string content_name;
+  const SecureTunnelContentDescription* offer_tunnel = NULL;
+  if (!FindSecureTunnelContent(offer, &content_name, &offer_tunnel))
+    return NULL;
+
+  // We are accepting a session request. We need to add our cert, the
+  // server cert, into the description. The client cert was validated
+  // in OnIncomingTunnel().
+  ASSERT(!offer_tunnel->client_pem_certificate.empty());
+  return NewSecureTunnelSessionDescription(
+      content_name,
+      new SecureTunnelContentDescription(
+          offer_tunnel->description,
+          offer_tunnel->client_pem_certificate,
+          GetIdentity().certificate().ToPEMString()));
+}
+
+// SecureTunnelSession
+
+SecureTunnelSession::SecureTunnelSession(
+    SecureTunnelSessionClient* client, Session* session,
+    talk_base::Thread* stream_thread, TunnelSessionRole role)
+    : TunnelSession(client, session, stream_thread),
+      role_(role) {
+}
+
+talk_base::StreamInterface* SecureTunnelSession::MakeSecureStream(
+    talk_base::StreamInterface* stream) {
+  talk_base::SSLStreamAdapter* ssl_stream =
+      talk_base::SSLStreamAdapter::Create(stream);
+  talk_base::SSLIdentity* identity =
+      static_cast<SecureTunnelSessionClient*>(client_)->
+      GetIdentity().GetReference();
+  ssl_stream->SetIdentity(identity);
+  if (role_ == RESPONDER)
+    ssl_stream->SetServerRole();
+  ssl_stream->StartSSLWithPeer();
+
+  // SSL negotiation will start on the stream as soon as it
+  // opens. However our SSLStreamAdapter still hasn't been told what
+  // certificate to allow for our peer. If we are the initiator, we do
+  // not have the peer's certificate yet: we will obtain it from the
+  // session accept message which we will receive later (see
+  // OnAccept()). We won't Connect() the PseudoTcpChannel until we get
+  // that, so the stream will stay closed until then.  Keep a handle
+  // on the streem so we can configure the peer certificate later.
+  ssl_stream_reference_.reset(new talk_base::StreamReference(ssl_stream));
+  return ssl_stream_reference_->NewReference();
+}
+
+talk_base::StreamInterface* SecureTunnelSession::GetStream() {
+  ASSERT(channel_ != NULL);
+  ASSERT(ssl_stream_reference_.get() == NULL);
+  return MakeSecureStream(channel_->GetStream());
+}
+
+void SecureTunnelSession::OnAccept() {
+  // We have either sent or received a session accept: it's time to
+  // connect the tunnel. First we must set the peer certificate.
+  ASSERT(channel_ != NULL);
+  ASSERT(session_ != NULL);
+  std::string content_name;
+  const SecureTunnelContentDescription* remote_tunnel = NULL;
+  if (!FindSecureTunnelContent(session_->remote_description(),
+                               &content_name, &remote_tunnel)) {
+    session_->Reject(STR_TERMINATE_INCOMPATIBLE_PARAMETERS);
+    return;
+  }
+
+  const std::string& cert_pem =
+      role_ == INITIATOR ? remote_tunnel->server_pem_certificate :
+                           remote_tunnel->client_pem_certificate;
+  talk_base::SSLCertificate* peer_cert =
+      ParseCertificate(cert_pem);
+  if (peer_cert == NULL) {
+    ASSERT(role_ == INITIATOR);  // when RESPONDER we validated it earlier
+    LOG(LS_ERROR)
+        << "Rejecting secure tunnel accept with invalid cetificate";
+    session_->Reject(STR_TERMINATE_INCOMPATIBLE_PARAMETERS);
+    return;
+  }
+  ASSERT(ssl_stream_reference_.get() != NULL);
+  talk_base::SSLStreamAdapter* ssl_stream =
+      static_cast<talk_base::SSLStreamAdapter*>(
+          ssl_stream_reference_->GetStream());
+  ssl_stream->SetPeerCertificate(peer_cert);  // pass ownership of certificate.
+  // We no longer need our handle to the ssl stream.
+  ssl_stream_reference_.reset();
+  LOG(LS_INFO) << "Connecting tunnel";
+  // This will try to connect the PseudoTcpChannel. If and when that
+  // succeeds, then ssl negotiation will take place, and when that
+  // succeeds, the tunnel stream will finally open.
+  VERIFY(channel_->Connect(
+      content_name, "tcp", ICE_CANDIDATE_COMPONENT_DEFAULT));
+}
+
+}  // namespace cricket
diff --git a/talk/session/tunnel/securetunnelsessionclient.h b/talk/session/tunnel/securetunnelsessionclient.h
new file mode 100644
index 0000000..5c65b98
--- /dev/null
+++ b/talk/session/tunnel/securetunnelsessionclient.h
@@ -0,0 +1,165 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// SecureTunnelSessionClient and SecureTunnelSession.
+// SecureTunnelSessionClient extends TunnelSessionClient to exchange
+// certificates as part of the session description.
+// SecureTunnelSession is a TunnelSession that wraps the underlying
+// tunnel stream into an SSLStreamAdapter.
+
+#ifndef TALK_SESSION_TUNNEL_SECURETUNNELSESSIONCLIENT_H_
+#define TALK_SESSION_TUNNEL_SECURETUNNELSESSIONCLIENT_H_
+
+#include <string>
+
+#include "talk/base/sslidentity.h"
+#include "talk/base/sslstreamadapter.h"
+#include "talk/session/tunnel/tunnelsessionclient.h"
+
+namespace cricket {
+
+class SecureTunnelSession;  // below
+
+// SecureTunnelSessionClient
+
+// This TunnelSessionClient establishes secure tunnels protected by
+// SSL/TLS. The PseudoTcpChannel stream is wrapped with an
+// SSLStreamAdapter. An SSLIdentity must be set or generated.
+//
+// The TunnelContentDescription is extended to include the client and
+// server certificates. The initiator acts as the client. The session
+// initiate stanza carries a description that contains the client's
+// certificate, and the session accept response's description has the
+// server certificate added to it.
+
+class SecureTunnelSessionClient : public TunnelSessionClient {
+ public:
+  // The jid is used as the name for sessions for outgoing tunnels.
+  // manager is the SessionManager to which we register this client
+  // and its sessions.
+  SecureTunnelSessionClient(const buzz::Jid& jid, SessionManager* manager);
+
+  // Configures this client to use a preexisting SSLIdentity.
+  // The client takes ownership of the identity object.
+  // Use either SetIdentity or GenerateIdentity, and only once.
+  void SetIdentity(talk_base::SSLIdentity* identity);
+
+  // Generates an identity from nothing.
+  // Returns true if generation was successful.
+  // Use either SetIdentity or GenerateIdentity, and only once.
+  bool GenerateIdentity();
+
+  // Returns our identity for SSL purposes, as either set by
+  // SetIdentity() or generated by GenerateIdentity(). Call this
+  // method only after our identity has been successfully established
+  // by one of those methods.
+  talk_base::SSLIdentity& GetIdentity() const;
+
+  // Inherited methods
+  virtual void OnIncomingTunnel(const buzz::Jid& jid, Session *session);
+  virtual bool ParseContent(SignalingProtocol protocol,
+                            const buzz::XmlElement* elem,
+                            ContentDescription** content,
+                            ParseError* error);
+  virtual bool WriteContent(SignalingProtocol protocol,
+                            const ContentDescription* content,
+                            buzz::XmlElement** elem,
+                            WriteError* error);
+  virtual SessionDescription* CreateOffer(
+      const buzz::Jid &jid, const std::string &description);
+  virtual SessionDescription* CreateAnswer(
+      const SessionDescription* offer);
+
+ protected:
+  virtual TunnelSession* MakeTunnelSession(
+      Session* session, talk_base::Thread* stream_thread,
+      TunnelSessionRole role);
+
+ private:
+  // Our identity (key and certificate) for SSL purposes. The
+  // certificate part will be communicated within the session
+  // description. The identity will be passed to the SSLStreamAdapter
+  // and used for SSL authentication.
+  talk_base::scoped_ptr<talk_base::SSLIdentity> identity_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(SecureTunnelSessionClient);
+};
+
+// SecureTunnelSession:
+// A TunnelSession represents one session for one client. It
+// provides the actual tunnel stream and handles state changes.
+// A SecureTunnelSession is a TunnelSession that wraps the underlying
+// tunnel stream into an SSLStreamAdapter.
+
+class SecureTunnelSession : public TunnelSession {
+ public:
+  // This TunnelSession will tie together the given client and session.
+  // stream_thread is passed to the PseudoTCPChannel: it's the thread
+  // designated to interact with the tunnel stream.
+  // role is either INITIATOR or RESPONDER, depending on who is
+  // initiating the session.
+  SecureTunnelSession(SecureTunnelSessionClient* client, Session* session,
+                      talk_base::Thread* stream_thread,
+                      TunnelSessionRole role);
+
+  // Returns the stream that implements the actual P2P tunnel.
+  // This may be called only once. Caller is responsible for freeing
+  // the returned object.
+  virtual talk_base::StreamInterface* GetStream();
+
+ protected:
+  // Inherited method: callback on accepting a session.
+  virtual void OnAccept();
+
+  // Helper method for GetStream() that Instantiates the
+  // SSLStreamAdapter to wrap the PseudoTcpChannel's stream, and
+  // configures it with our identity and role.
+  talk_base::StreamInterface* MakeSecureStream(
+      talk_base::StreamInterface* stream);
+
+  // Our role in requesting the tunnel: INITIATOR or
+  // RESPONDER. Translates to our role in SSL negotiation:
+  // respectively client or server. Also indicates which slot of the
+  // SecureTunnelContentDescription our cert goes into: client-cert or
+  // server-cert respectively.
+  TunnelSessionRole role_;
+
+  // This is the stream representing the usable tunnel endpoint.  It's
+  // a StreamReference wrapping the SSLStreamAdapter instance, which
+  // further wraps a PseudoTcpChannel::InternalStream. The
+  // StreamReference is because in the case of CreateTunnel(), the
+  // stream endpoint is returned early, but we need to keep a handle
+  // on it so we can setup the peer certificate when we receive it
+  // later.
+  talk_base::scoped_ptr<talk_base::StreamReference> ssl_stream_reference_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(SecureTunnelSession);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SESSION_TUNNEL_SECURETUNNELSESSIONCLIENT_H_
diff --git a/talk/session/tunnel/tunnelsessionclient.cc b/talk/session/tunnel/tunnelsessionclient.cc
new file mode 100644
index 0000000..71d0ce1
--- /dev/null
+++ b/talk/session/tunnel/tunnelsessionclient.cc
@@ -0,0 +1,432 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/basicdefs.h"
+#include "talk/base/basictypes.h"
+#include "talk/base/common.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/transportchannel.h"
+#include "talk/xmllite/xmlelement.h"
+#include "pseudotcpchannel.h"
+#include "tunnelsessionclient.h"
+
+namespace cricket {
+
+const char NS_TUNNEL[] = "http://www.google.com/talk/tunnel";
+const buzz::StaticQName QN_TUNNEL_DESCRIPTION = { NS_TUNNEL, "description" };
+const buzz::StaticQName QN_TUNNEL_TYPE = { NS_TUNNEL, "type" };
+const char CN_TUNNEL[] = "tunnel";
+
+enum {
+  MSG_CLOCK = 1,
+  MSG_DESTROY,
+  MSG_TERMINATE,
+  MSG_EVENT,
+  MSG_CREATE_TUNNEL,
+};
+
+struct EventData : public talk_base::MessageData {
+  int event, error;
+  EventData(int ev, int err = 0) : event(ev), error(err) { }
+};
+
+struct CreateTunnelData : public talk_base::MessageData {
+  buzz::Jid jid;
+  std::string description;
+  talk_base::Thread* thread;
+  talk_base::StreamInterface* stream;
+};
+
+extern const talk_base::ConstantLabel SESSION_STATES[];
+
+const talk_base::ConstantLabel SESSION_STATES[] = {
+  KLABEL(Session::STATE_INIT),
+  KLABEL(Session::STATE_SENTINITIATE),
+  KLABEL(Session::STATE_RECEIVEDINITIATE),
+  KLABEL(Session::STATE_SENTACCEPT),
+  KLABEL(Session::STATE_RECEIVEDACCEPT),
+  KLABEL(Session::STATE_SENTMODIFY),
+  KLABEL(Session::STATE_RECEIVEDMODIFY),
+  KLABEL(Session::STATE_SENTREJECT),
+  KLABEL(Session::STATE_RECEIVEDREJECT),
+  KLABEL(Session::STATE_SENTREDIRECT),
+  KLABEL(Session::STATE_SENTTERMINATE),
+  KLABEL(Session::STATE_RECEIVEDTERMINATE),
+  KLABEL(Session::STATE_INPROGRESS),
+  KLABEL(Session::STATE_DEINIT),
+  LASTLABEL
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// TunnelContentDescription
+///////////////////////////////////////////////////////////////////////////////
+
+struct TunnelContentDescription : public ContentDescription {
+  std::string description;
+
+  TunnelContentDescription(const std::string& desc) : description(desc) { }
+  virtual ContentDescription* Copy() const {
+    return new TunnelContentDescription(*this);
+  }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// TunnelSessionClientBase
+///////////////////////////////////////////////////////////////////////////////
+
+TunnelSessionClientBase::TunnelSessionClientBase(const buzz::Jid& jid,
+                                SessionManager* manager, const std::string &ns)
+  : jid_(jid), session_manager_(manager), namespace_(ns), shutdown_(false) {
+  session_manager_->AddClient(namespace_, this);
+}
+
+TunnelSessionClientBase::~TunnelSessionClientBase() {
+  shutdown_ = true;
+  for (std::vector<TunnelSession*>::iterator it = sessions_.begin();
+       it != sessions_.end();
+       ++it) {
+     Session* session = (*it)->ReleaseSession(true);
+     session_manager_->DestroySession(session);
+  }
+  session_manager_->RemoveClient(namespace_);
+}
+
+void TunnelSessionClientBase::OnSessionCreate(Session* session, bool received) {
+  LOG(LS_INFO) << "TunnelSessionClientBase::OnSessionCreate: received=" 
+               << received;
+  ASSERT(session_manager_->signaling_thread()->IsCurrent());
+  if (received)
+    sessions_.push_back(
+        MakeTunnelSession(session, talk_base::Thread::Current(), RESPONDER));
+}
+
+void TunnelSessionClientBase::OnSessionDestroy(Session* session) {
+  LOG(LS_INFO) << "TunnelSessionClientBase::OnSessionDestroy";
+  ASSERT(session_manager_->signaling_thread()->IsCurrent());
+  if (shutdown_)
+    return;
+  for (std::vector<TunnelSession*>::iterator it = sessions_.begin();
+       it != sessions_.end();
+       ++it) {
+    if ((*it)->HasSession(session)) {
+      VERIFY((*it)->ReleaseSession(false) == session);
+      sessions_.erase(it);
+      return;
+    }
+  }
+}
+
+talk_base::StreamInterface* TunnelSessionClientBase::CreateTunnel(
+    const buzz::Jid& to, const std::string& description) {
+  // Valid from any thread
+  CreateTunnelData data;
+  data.jid = to;
+  data.description = description;
+  data.thread = talk_base::Thread::Current();
+  data.stream = NULL;
+  session_manager_->signaling_thread()->Send(this, MSG_CREATE_TUNNEL, &data);
+  return data.stream;
+}
+
+talk_base::StreamInterface* TunnelSessionClientBase::AcceptTunnel(
+    Session* session) {
+  ASSERT(session_manager_->signaling_thread()->IsCurrent());
+  TunnelSession* tunnel = NULL;
+  for (std::vector<TunnelSession*>::iterator it = sessions_.begin();
+       it != sessions_.end();
+       ++it) {
+    if ((*it)->HasSession(session)) {
+      tunnel = *it;
+      break;
+    }
+  }
+  ASSERT(tunnel != NULL);
+
+  SessionDescription* answer = CreateAnswer(session->remote_description());
+  if (answer == NULL)
+    return NULL;
+
+  session->Accept(answer);
+  return tunnel->GetStream();
+}
+
+void TunnelSessionClientBase::DeclineTunnel(Session* session) {
+  ASSERT(session_manager_->signaling_thread()->IsCurrent());
+  session->Reject(STR_TERMINATE_DECLINE);
+}
+
+void TunnelSessionClientBase::OnMessage(talk_base::Message* pmsg) {
+  if (pmsg->message_id == MSG_CREATE_TUNNEL) {
+    ASSERT(session_manager_->signaling_thread()->IsCurrent());
+    CreateTunnelData* data = static_cast<CreateTunnelData*>(pmsg->pdata);
+    SessionDescription* offer = CreateOffer(data->jid, data->description);
+    if (offer == NULL) {
+      return;
+    }
+
+    Session* session = session_manager_->CreateSession(jid_.Str(), namespace_);
+    TunnelSession* tunnel = MakeTunnelSession(session, data->thread,
+                                              INITIATOR);
+    sessions_.push_back(tunnel);
+    session->Initiate(data->jid.Str(), offer);
+    data->stream = tunnel->GetStream();
+  }
+}
+
+TunnelSession* TunnelSessionClientBase::MakeTunnelSession(
+    Session* session, talk_base::Thread* stream_thread,
+    TunnelSessionRole /*role*/) {
+  return new TunnelSession(this, session, stream_thread);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TunnelSessionClient
+///////////////////////////////////////////////////////////////////////////////
+
+TunnelSessionClient::TunnelSessionClient(const buzz::Jid& jid,
+                                         SessionManager* manager,
+                                         const std::string &ns)
+    : TunnelSessionClientBase(jid, manager, ns) {
+}
+
+TunnelSessionClient::TunnelSessionClient(const buzz::Jid& jid,
+                                         SessionManager* manager)
+    : TunnelSessionClientBase(jid, manager, NS_TUNNEL) {
+}
+
+TunnelSessionClient::~TunnelSessionClient() {
+}
+
+
+bool TunnelSessionClient::ParseContent(SignalingProtocol protocol,
+                                       const buzz::XmlElement* elem,
+                                       ContentDescription** content,
+                                       ParseError* error) {
+  if (const buzz::XmlElement* type_elem = elem->FirstNamed(QN_TUNNEL_TYPE)) {
+    *content = new TunnelContentDescription(type_elem->BodyText());
+    return true;
+  }
+  return false;
+}
+
+bool TunnelSessionClient::WriteContent(
+    SignalingProtocol protocol,
+    const ContentDescription* untyped_content,
+    buzz::XmlElement** elem, WriteError* error) {
+  const TunnelContentDescription* content =
+      static_cast<const TunnelContentDescription*>(untyped_content);
+
+  buzz::XmlElement* root = new buzz::XmlElement(QN_TUNNEL_DESCRIPTION, true);
+  buzz::XmlElement* type_elem = new buzz::XmlElement(QN_TUNNEL_TYPE);
+  type_elem->SetBodyText(content->description);
+  root->AddElement(type_elem);
+  *elem = root;
+  return true;
+}
+
+SessionDescription* NewTunnelSessionDescription(
+    const std::string& content_name, ContentDescription* content) {
+  SessionDescription* sdesc = new SessionDescription();
+  sdesc->AddContent(content_name, NS_TUNNEL, content);
+  return sdesc;
+}
+
+bool FindTunnelContent(const cricket::SessionDescription* sdesc,
+                       std::string* name,
+                       const TunnelContentDescription** content) {
+  const ContentInfo* cinfo = sdesc->FirstContentByType(NS_TUNNEL);
+  if (cinfo == NULL)
+    return false;
+
+  *name = cinfo->name;
+  *content = static_cast<const TunnelContentDescription*>(
+      cinfo->description);
+  return true;
+}
+
+void TunnelSessionClient::OnIncomingTunnel(const buzz::Jid &jid,
+                                           Session *session) {
+  std::string content_name;
+  const TunnelContentDescription* content = NULL;
+  if (!FindTunnelContent(session->remote_description(),
+                         &content_name, &content)) {
+    session->Reject(STR_TERMINATE_INCOMPATIBLE_PARAMETERS);
+    return;
+  }
+
+  SignalIncomingTunnel(this, jid, content->description, session);
+}
+
+SessionDescription* TunnelSessionClient::CreateOffer(
+    const buzz::Jid &jid, const std::string &description) {
+  SessionDescription* offer = NewTunnelSessionDescription(
+      CN_TUNNEL, new TunnelContentDescription(description));
+  talk_base::scoped_ptr<TransportDescription> tdesc(
+      session_manager_->transport_desc_factory()->CreateOffer(
+          TransportOptions(), NULL));
+  if (tdesc.get()) {
+    offer->AddTransportInfo(TransportInfo(CN_TUNNEL, *tdesc));
+  } else {
+    delete offer;
+    offer = NULL;
+  }
+  return offer;
+}
+
+SessionDescription* TunnelSessionClient::CreateAnswer(
+    const SessionDescription* offer) {
+  std::string content_name;
+  const TunnelContentDescription* offer_tunnel = NULL;
+  if (!FindTunnelContent(offer, &content_name, &offer_tunnel))
+    return NULL;
+
+  SessionDescription* answer = NewTunnelSessionDescription(
+      content_name, new TunnelContentDescription(offer_tunnel->description));
+  const TransportInfo* tinfo = offer->GetTransportInfoByName(content_name);
+  if (tinfo) {
+    const TransportDescription* offer_tdesc = &tinfo->description;
+    ASSERT(offer_tdesc != NULL);
+    talk_base::scoped_ptr<TransportDescription> tdesc(
+      session_manager_->transport_desc_factory()->CreateAnswer(
+          offer_tdesc, TransportOptions(),  NULL));
+    if (tdesc.get()) {
+      answer->AddTransportInfo(TransportInfo(content_name, *tdesc));
+    } else {
+      delete answer;
+      answer = NULL;
+    }
+  }
+  return answer;
+}
+///////////////////////////////////////////////////////////////////////////////
+// TunnelSession
+///////////////////////////////////////////////////////////////////////////////
+
+//
+// Signalling thread methods
+//
+
+TunnelSession::TunnelSession(TunnelSessionClientBase* client, Session* session,
+                             talk_base::Thread* stream_thread)
+    : client_(client), session_(session), channel_(NULL) {
+  ASSERT(client_ != NULL);
+  ASSERT(session_ != NULL);
+  session_->SignalState.connect(this, &TunnelSession::OnSessionState);
+  channel_ = new PseudoTcpChannel(stream_thread, session_);
+  channel_->SignalChannelClosed.connect(this, &TunnelSession::OnChannelClosed);
+}
+
+TunnelSession::~TunnelSession() {
+  ASSERT(client_ != NULL);
+  ASSERT(session_ == NULL);
+  ASSERT(channel_ == NULL);
+}
+
+talk_base::StreamInterface* TunnelSession::GetStream() {
+  ASSERT(channel_ != NULL);
+  return channel_->GetStream();
+}
+
+bool TunnelSession::HasSession(Session* session) {
+  ASSERT(NULL != session_);
+  return (session_ == session);
+}
+
+Session* TunnelSession::ReleaseSession(bool channel_exists) {
+  ASSERT(NULL != session_);
+  ASSERT(NULL != channel_);
+  Session* session = session_;
+  session_->SignalState.disconnect(this);
+  session_ = NULL;
+  if (channel_exists)
+    channel_->SignalChannelClosed.disconnect(this);
+  channel_ = NULL;
+  delete this;
+  return session;
+}
+
+void TunnelSession::OnSessionState(BaseSession* session,
+                                   BaseSession::State state) {
+  LOG(LS_INFO) << "TunnelSession::OnSessionState("
+               << talk_base::nonnull(
+                    talk_base::FindLabel(state, SESSION_STATES), "Unknown")
+               << ")";
+  ASSERT(session == session_);
+
+  switch (state) {
+  case Session::STATE_RECEIVEDINITIATE:
+    OnInitiate();
+    break;
+  case Session::STATE_SENTACCEPT:
+  case Session::STATE_RECEIVEDACCEPT:
+    OnAccept();
+    break;
+  case Session::STATE_SENTTERMINATE:
+  case Session::STATE_RECEIVEDTERMINATE:
+    OnTerminate();
+    break;
+  case Session::STATE_DEINIT:
+    // ReleaseSession should have been called before this.
+    ASSERT(false);
+    break;
+  default:
+    break;
+  }
+}
+
+void TunnelSession::OnInitiate() {
+  ASSERT(client_ != NULL);
+  ASSERT(session_ != NULL);
+  client_->OnIncomingTunnel(buzz::Jid(session_->remote_name()), session_);
+}
+
+void TunnelSession::OnAccept() {
+  ASSERT(channel_ != NULL);
+  const ContentInfo* content =
+      session_->remote_description()->FirstContentByType(NS_TUNNEL);
+  ASSERT(content != NULL);
+  VERIFY(channel_->Connect(
+      content->name, "tcp", ICE_CANDIDATE_COMPONENT_DEFAULT));
+}
+
+void TunnelSession::OnTerminate() {
+  ASSERT(channel_ != NULL);
+  channel_->OnSessionTerminate(session_);
+}
+
+void TunnelSession::OnChannelClosed(PseudoTcpChannel* channel) {
+  ASSERT(channel_ == channel);
+  ASSERT(session_ != NULL);
+  session_->Terminate();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace cricket
diff --git a/talk/session/tunnel/tunnelsessionclient.h b/talk/session/tunnel/tunnelsessionclient.h
new file mode 100644
index 0000000..55ce14a
--- /dev/null
+++ b/talk/session/tunnel/tunnelsessionclient.h
@@ -0,0 +1,182 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __TUNNELSESSIONCLIENT_H__
+#define __TUNNELSESSIONCLIENT_H__
+
+#include <vector>
+
+#include "talk/base/criticalsection.h"
+#include "talk/base/stream.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/pseudotcp.h"
+#include "talk/p2p/base/session.h"
+#include "talk/p2p/base/sessiondescription.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/base/sessionclient.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmpp/constants.h"
+
+namespace cricket {
+
+class TunnelSession;
+class TunnelStream;
+
+enum TunnelSessionRole { INITIATOR, RESPONDER };
+
+///////////////////////////////////////////////////////////////////////////////
+// TunnelSessionClient
+///////////////////////////////////////////////////////////////////////////////
+
+// Base class is still abstract
+class TunnelSessionClientBase
+  : public SessionClient, public talk_base::MessageHandler {
+public:
+  TunnelSessionClientBase(const buzz::Jid& jid, SessionManager* manager,
+                          const std::string &ns);
+  virtual ~TunnelSessionClientBase();
+
+  const buzz::Jid& jid() const { return jid_; }
+  SessionManager* session_manager() const { return session_manager_; }
+
+  void OnSessionCreate(Session* session, bool received);
+  void OnSessionDestroy(Session* session);
+
+  // This can be called on any thread.  The stream interface is
+  // thread-safe, but notifications must be registered on the creating
+  // thread.
+  talk_base::StreamInterface* CreateTunnel(const buzz::Jid& to,
+                                           const std::string& description);
+
+  talk_base::StreamInterface* AcceptTunnel(Session* session);
+  void DeclineTunnel(Session* session);
+
+  // Invoked on an incoming tunnel
+  virtual void OnIncomingTunnel(const buzz::Jid &jid, Session *session) = 0;
+
+  // Invoked on an outgoing session request
+  virtual SessionDescription* CreateOffer(
+      const buzz::Jid &jid, const std::string &description) = 0;
+  // Invoked on a session request accept to create
+  // the local-side session description
+  virtual SessionDescription* CreateAnswer(
+      const SessionDescription* offer) = 0;
+
+protected:
+
+  void OnMessage(talk_base::Message* pmsg);
+
+  // helper method to instantiate TunnelSession. By overriding this,
+  // subclasses of TunnelSessionClient are able to instantiate
+  // subclasses of TunnelSession instead.
+  virtual TunnelSession* MakeTunnelSession(Session* session,
+                                           talk_base::Thread* stream_thread,
+                                           TunnelSessionRole role);
+
+  buzz::Jid jid_;
+  SessionManager* session_manager_;
+  std::vector<TunnelSession*> sessions_;
+  std::string namespace_;
+  bool shutdown_;
+};
+
+class TunnelSessionClient
+  : public TunnelSessionClientBase, public sigslot::has_slots<>  {
+public:
+  TunnelSessionClient(const buzz::Jid& jid, SessionManager* manager);
+  TunnelSessionClient(const buzz::Jid& jid, SessionManager* manager,
+                      const std::string &ns);
+  virtual ~TunnelSessionClient();
+
+  virtual bool ParseContent(SignalingProtocol protocol,
+                            const buzz::XmlElement* elem,
+                            ContentDescription** content,
+                            ParseError* error);
+  virtual bool WriteContent(SignalingProtocol protocol,
+                            const ContentDescription* content,
+                            buzz::XmlElement** elem,
+                            WriteError* error);
+
+  // Signal arguments are this, initiator, description, session
+  sigslot::signal4<TunnelSessionClient*, buzz::Jid, std::string, Session*>
+    SignalIncomingTunnel;
+
+  virtual void OnIncomingTunnel(const buzz::Jid &jid,
+                                Session *session);
+  virtual SessionDescription* CreateOffer(
+      const buzz::Jid &jid, const std::string &description);
+  virtual SessionDescription* CreateAnswer(
+      const SessionDescription* offer);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// TunnelSession
+// Note: The lifetime of TunnelSession is complicated.  It needs to survive
+// until the following three conditions are true:
+// 1) TunnelStream has called Close (tracked via non-null stream_)
+// 2) PseudoTcp has completed (tracked via non-null tcp_)
+// 3) Session has been destroyed (tracked via non-null session_)
+// This is accomplished by calling CheckDestroy after these indicators change.
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// TunnelStream
+// Note: Because TunnelStream provides a stream interface, its lifetime is
+// controlled by the owner of the stream pointer.  As a result, we must support
+// both the TunnelSession disappearing before TunnelStream, and vice versa.
+///////////////////////////////////////////////////////////////////////////////
+
+class PseudoTcpChannel;
+
+class TunnelSession : public sigslot::has_slots<> {
+ public:
+  // Signalling thread methods
+  TunnelSession(TunnelSessionClientBase* client, Session* session,
+                talk_base::Thread* stream_thread);
+
+  virtual talk_base::StreamInterface* GetStream();
+  bool HasSession(Session* session);
+  Session* ReleaseSession(bool channel_exists);
+
+ protected:
+  virtual ~TunnelSession();
+
+  virtual void OnSessionState(BaseSession* session, BaseSession::State state);
+  virtual void OnInitiate();
+  virtual void OnAccept();
+  virtual void OnTerminate();
+  virtual void OnChannelClosed(PseudoTcpChannel* channel);
+
+  TunnelSessionClientBase* client_;
+  Session* session_;
+  PseudoTcpChannel* channel_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace cricket
+
+#endif // __TUNNELSESSIONCLIENT_H__
diff --git a/talk/session/tunnel/tunnelsessionclient_unittest.cc b/talk/session/tunnel/tunnelsessionclient_unittest.cc
new file mode 100644
index 0000000..7370351
--- /dev/null
+++ b/talk/session/tunnel/tunnelsessionclient_unittest.cc
@@ -0,0 +1,226 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+#include "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_.SignalRequestSignaling.connect(this,
+        &TunnelSessionClientTest::OnLocalRequestSignaling);
+    local_sm_.SignalOutgoingMessage.connect(this,
+        &TunnelSessionClientTest::OnOutgoingMessage);
+    remote_sm_.SignalRequestSignaling.connect(this,
+        &TunnelSessionClientTest::OnRemoteRequestSignaling);
+    remote_sm_.SignalOutgoingMessage.connect(this,
+        &TunnelSessionClientTest::OnOutgoingMessage);
+    remote_client_.SignalIncomingTunnel.connect(this,
+        &TunnelSessionClientTest::OnIncomingTunnel);
+  }
+
+  // Transfer the desired amount of data from the local to the remote client.
+  void TestTransfer(int size) {
+    // Create some dummy data to send.
+    send_stream_.ReserveSize(size);
+    for (int i = 0; i < size; ++i) {
+      char ch = static_cast<char>(i);
+      send_stream_.Write(&ch, 1, NULL, NULL);
+    }
+    send_stream_.Rewind();
+    // Prepare the receive stream.
+    recv_stream_.ReserveSize(size);
+    // Create the tunnel and set things in motion.
+    local_tunnel_.reset(local_client_.CreateTunnel(kRemoteJid, "test"));
+    local_tunnel_->SignalEvent.connect(this,
+        &TunnelSessionClientTest::OnStreamEvent);
+    EXPECT_TRUE_WAIT(done_, kTimeoutMs);
+    // Make sure we received the right data.
+    EXPECT_EQ(0, memcmp(send_stream_.GetBuffer(),
+                        recv_stream_.GetBuffer(), size));
+  }
+
+ private:
+  enum { MSG_LSIGNAL, MSG_RSIGNAL };
+
+  // There's no SessionManager* argument in this callback, so we need 2 of them.
+  void OnLocalRequestSignaling() {
+    local_sm_.OnSignalingReady();
+  }
+  void OnRemoteRequestSignaling() {
+    remote_sm_.OnSignalingReady();
+  }
+
+  // Post a message, to avoid problems with directly connecting the callbacks.
+  void OnOutgoingMessage(cricket::SessionManager* manager,
+                         const buzz::XmlElement* stanza) {
+    if (manager == &local_sm_) {
+      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/site_tools/talk_linux.py b/talk/site_scons/site_tools/talk_linux.py
new file mode 100644
index 0000000..1ceb94a
--- /dev/null
+++ b/talk/site_scons/site_tools/talk_linux.py
@@ -0,0 +1,313 @@
+# Copyright 2010 Google Inc.
+# All Rights Reserved.
+# Author: tschmelcher@google.com (Tristan Schmelcher)
+
+"""Tool for helpers used in linux building process."""
+
+import os
+import SCons.Defaults
+import subprocess
+
+
+def _OutputFromShellCommand(command):
+  process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
+  return process.communicate()[0].strip()
+
+
+# This is a pure SCons helper function.
+def _InternalBuildDebianPackage(env, debian_files, package_files,
+    output_dir=None, force_version=None):
+  """Creates build rules to build a Debian package from the specified sources.
+
+  Args:
+    env: SCons Environment.
+    debian_files: Array of the Debian control file sources that should be
+        copied into the package source tree, e.g., changelog, control, rules,
+        etc.
+    package_files: An array of 2-tuples listing the files that should be
+        copied into the package source tree.
+        The first element is the path where the file should be placed for the
+            .install control file to find it, relative to the generated debian
+            package source directory.
+        The second element is the file source.
+    output_dir: An optional directory to place the files in. If omitted, the
+        current output directory is used.
+    force_version: Optional. Forces the version of the package to start with
+        this version string if specified. If the last entry in the changelog
+        is not for a version that starts with this then a dummy entry is
+        generated with this version and a ~prerelease suffix (so that the
+        final version will compare as greater).
+
+  Return:
+    A list of the targets (if any).
+  """
+  if 0 != subprocess.call(['which', 'dpkg-buildpackage']):
+    print ('dpkg-buildpackage not installed on this system; '
+           'skipping DEB build stage')
+    return []
+  # Read the control file and changelog file to determine the package name,
+  # version, and arch that the Debian build tools will use to name the
+  # generated files.
+  control_file = None
+  changelog_file = None
+  for file in debian_files:
+    if os.path.basename(file) == 'control':
+      control_file = env.File(file).srcnode().abspath
+    elif os.path.basename(file) == 'changelog':
+      changelog_file = env.File(file).srcnode().abspath
+  if not control_file:
+    raise Exception('Need to have a control file')
+  if not changelog_file:
+    raise Exception('Need to have a changelog file')
+  source = _OutputFromShellCommand(
+      "awk '/^Source:/ { print $2; }' " + control_file)
+  packages = _OutputFromShellCommand(
+      "awk '/^Package:/ { print $2; }' " + control_file).split('\n')
+  version = _OutputFromShellCommand(
+      "sed -nr '1 { s/.*\\((.*)\\).*/\\1/; p }' " + changelog_file)
+  arch = _OutputFromShellCommand('dpkg --print-architecture')
+  add_dummy_changelog_entry = False
+  if force_version and not version.startswith(force_version):
+    print ('Warning: no entry in ' + changelog_file + ' for version ' +
+        force_version + ' (last is ' + version +'). A dummy entry will be ' +
+        'generated. Remember to add the real changelog entry before ' +
+        'releasing.')
+    version = force_version + '~prerelease'
+    add_dummy_changelog_entry = True
+  source_dir_name = source + '_' + version + '_' + arch
+  target_file_names = [ source_dir_name + '.changes' ]
+  for package in packages:
+    package_file_name = package + '_' + version + '_' + arch + '.deb'
+    target_file_names.append(package_file_name)
+  # The targets
+  if output_dir:
+    targets = [os.path.join(output_dir, s) for s in target_file_names]
+  else:
+    targets = target_file_names
+  # Path to where we will construct the debian build tree.
+  deb_build_tree = os.path.join(source_dir_name, 'deb_build_tree')
+  # First copy the files.
+  for file in package_files:
+    env.Command(os.path.join(deb_build_tree, file[0]), file[1],
+        SCons.Defaults.Copy('$TARGET', '$SOURCE'))
+    env.Depends(targets, os.path.join(deb_build_tree, file[0]))
+  # Now copy the Debian metadata sources. We have to do this all at once so
+  # that we can remove the target directory before copying, because there
+  # can't be any other stale files there or else dpkg-buildpackage may use
+  # them and give incorrect build output.
+  copied_debian_files_paths = []
+  for file in debian_files:
+    copied_debian_files_paths.append(os.path.join(deb_build_tree, 'debian',
+        os.path.basename(file)))
+  copy_commands = [
+      """dir=$$(dirname $TARGET) && \
+          rm -Rf $$dir && \
+          mkdir -p $$dir && \
+          cp $SOURCES $$dir && \
+          chmod -R u+w $$dir"""
+  ]
+  if add_dummy_changelog_entry:
+    copy_commands += [
+        """debchange -c $$(dirname $TARGET)/changelog --newversion %s \
+            --distribution UNRELEASED \
+            'Developer preview build. (This entry was auto-generated.)'""" %
+        version
+    ]
+  env.Command(copied_debian_files_paths, debian_files, copy_commands)
+  env.Depends(targets, copied_debian_files_paths)
+  # Must explicitly specify -a because otherwise cross-builds won't work.
+  # Must explicitly specify -D because -a disables it.
+  # Must explicitly specify fakeroot because old dpkg tools don't assume that.
+  env.Command(targets, None,
+      """dir=%(dir)s && \
+          cd $$dir && \
+          dpkg-buildpackage -b -uc -a%(arch)s -D -rfakeroot && \
+          cd $$OLDPWD && \
+          for file in %(targets)s; do \
+            mv $$dir/../$$file $$(dirname $TARGET) || exit 1; \
+          done""" %
+      {'dir':env.Dir(deb_build_tree).path,
+       'arch':arch,
+       'targets':' '.join(target_file_names)})
+  return targets
+
+
+def BuildDebianPackage(env, debian_files, package_files, force_version=None):
+  """Creates build rules to build a Debian package from the specified sources.
+
+  This is a Hammer-ified version of _InternalBuildDebianPackage that knows to
+  put the packages in the Hammer staging dir.
+
+  Args:
+    env: SCons Environment.
+    debian_files: Array of the Debian control file sources that should be
+        copied into the package source tree, e.g., changelog, control, rules,
+        etc.
+    package_files: An array of 2-tuples listing the files that should be
+        copied into the package source tree.
+        The first element is the path where the file should be placed for the
+            .install control file to find it, relative to the generated debian
+            package source directory.
+        The second element is the file source.
+    force_version: Optional. Forces the version of the package to start with
+        this version string if specified. If the last entry in the changelog
+        is not for a version that starts with this then a dummy entry is
+        generated with this version and a ~prerelease suffix (so that the
+        final version will compare as greater).
+
+  Return:
+    A list of the targets (if any).
+  """
+  if not env.Bit('host_linux'):
+    return []
+  return _InternalBuildDebianPackage(env, debian_files, package_files,
+      output_dir='$STAGING_DIR', force_version=force_version)
+
+
+def _GetPkgConfigCommand():
+  """Return the pkg-config command line to use.
+
+  Returns:
+    A string specifying the pkg-config command line to use.
+  """
+  return os.environ.get('PKG_CONFIG') or 'pkg-config'
+
+
+def _EscapePosixShellArgument(arg):
+  """Escapes a shell command line argument so that it is interpreted literally.
+
+  Args:
+    arg: The shell argument to escape.
+
+  Returns:
+    The escaped string.
+  """
+  return "'%s'" % arg.replace("'", "'\\''")
+
+
+def _HavePackage(package):
+  """Whether the given pkg-config package name is present on the build system.
+
+  Args:
+    package: The name of the package.
+
+  Returns:
+    True if the package is present, else False
+  """
+  return subprocess.call('%s --exists %s' % (
+      _GetPkgConfigCommand(),
+      _EscapePosixShellArgument(package)), shell=True) == 0
+
+
+def _GetPackageFlags(flag_type, packages):
+  """Get the flags needed to compile/link against the given package(s).
+
+  Returns the flags that are needed to compile/link against the given pkg-config
+  package(s).
+
+  Args:
+    flag_type: The option to pkg-config specifying the type of flags to get.
+    packages: The list of package names as strings.
+
+  Returns:
+    The flags of the requested type.
+
+  Raises:
+    subprocess.CalledProcessError: The pkg-config command failed.
+  """
+  pkg_config = _GetPkgConfigCommand()
+  command = ' '.join([pkg_config] +
+                     [_EscapePosixShellArgument(arg) for arg in
+                      [flag_type] + packages])
+  process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
+  output = process.communicate()[0]
+  if process.returncode != 0:
+    raise subprocess.CalledProcessError(process.returncode, pkg_config)
+  return output.strip().split(' ')
+
+
+def GetPackageParams(env, packages):
+  """Get the params needed to compile/link against the given package(s).
+
+  Returns the params that are needed to compile/link against the given
+  pkg-config package(s).
+
+  Args:
+    env: The current SCons environment.
+    packages: The name of the package, or a list of names.
+
+  Returns:
+    A dictionary containing the params.
+
+  Raises:
+    Exception: One or more of the packages is not installed.
+  """
+  if not env.Bit('host_linux'):
+    return {}
+  if not SCons.Util.is_List(packages):
+    packages = [packages]
+  for package in packages:
+    if not _HavePackage(package):
+      raise Exception(('Required package \"%s\" was not found. Please install '
+                       'the package that provides the \"%s.pc\" file.') %
+                      (package, package))
+  package_ccflags = _GetPackageFlags('--cflags', packages)
+  package_libs = _GetPackageFlags('--libs', packages)
+  # Split package_libs into libs, libdirs, and misc. linker flags. (In a perfect
+  # world we could just leave libdirs in link_flags, but some linkers are
+  # somehow confused by the different argument order.)
+  libs = [flag[2:] for flag in package_libs if flag[0:2] == '-l']
+  libdirs = [flag[2:] for flag in package_libs if flag[0:2] == '-L']
+  link_flags = [flag for flag in package_libs if flag[0:2] not in ['-l', '-L']]
+  return {
+      'ccflags': package_ccflags,
+      'libs': libs,
+      'libdirs': libdirs,
+      'link_flags': link_flags,
+      'dependent_target_settings' : {
+          'libs': libs[:],
+          'libdirs': libdirs[:],
+          'link_flags': link_flags[:],
+      },
+  }
+
+
+def EnableFeatureWherePackagePresent(env, bit, cpp_flag, package):
+  """Enable a feature if a required pkg-config package is present.
+
+  Args:
+    env: The current SCons environment.
+    bit: The name of the Bit to enable when the package is present.
+    cpp_flag: The CPP flag to enable when the package is present.
+    package: The name of the package.
+  """
+  if not env.Bit('host_linux'):
+    return
+  if _HavePackage(package):
+    env.SetBits(bit)
+    env.Append(CPPDEFINES=[cpp_flag])
+  else:
+    print ('Warning: Package \"%s\" not found. Feature \"%s\" will not be '
+           'built. To build with this feature, install the package that '
+           'provides the \"%s.pc\" file.') % (package, bit, package)
+
+def GetGccVersion(env):
+  if env.Bit('cross_compile'):
+    gcc_command = env['CXX']
+  else:
+    gcc_command = 'gcc'
+  version_string = _OutputFromShellCommand(
+      '%s --version | head -n 1 |'
+      r'sed "s/.*\([0-9]\+\.[0-9]\+\.[0-9]\+\).*/\1/g"' % gcc_command)
+  return tuple([int(x or '0') for x in version_string.split('.')])
+
+def generate(env):
+  if env.Bit('linux'):
+    env.AddMethod(EnableFeatureWherePackagePresent)
+    env.AddMethod(GetPackageParams)
+    env.AddMethod(BuildDebianPackage)
+    env.AddMethod(GetGccVersion)
+
+
+def exists(env):
+  return 1  # Required by scons
diff --git a/talk/site_scons/site_tools/talk_noops.py b/talk/site_scons/site_tools/talk_noops.py
new file mode 100644
index 0000000..bb8f106
--- /dev/null
+++ b/talk/site_scons/site_tools/talk_noops.py
@@ -0,0 +1,20 @@
+# Copyright 2010 Google Inc.
+# All Rights Reserved.
+# Author: thaloun@google.com (Tim Haloun)
+
+"""Noop tool that defines builder functions for non-default platforms to
+   avoid errors when scanning sconsscripts."""
+
+import SCons.Builder
+
+
+def generate(env):
+  """SCons method."""
+  if not env.Bit('windows'):
+    builder = SCons.Builder.Builder(
+      action=''
+    )
+    env.Append(BUILDERS={'RES': builder, 'Grit': builder})
+
+def exists(env):
+  return 1
diff --git a/talk/site_scons/talk.py b/talk/site_scons/talk.py
new file mode 100644
index 0000000..a274d08
--- /dev/null
+++ b/talk/site_scons/talk.py
@@ -0,0 +1,635 @@
+# Copyright 2010 Google Inc.
+# All Rights Reserved.
+#
+# Author: Tim Haloun (thaloun@google.com)
+#         Daniel Petersson (dape@google.com)
+#
+import os
+import SCons.Util
+
+class LibraryInfo:
+  """Records information on the libraries defined in a build configuration.
+
+  Attributes:
+    lib_targets: Dictionary of library target params for lookups in
+        ExtendComponent().
+    prebuilt_libraries: Set of all prebuilt static libraries.
+    system_libraries: Set of libraries not found in the above (used to detect
+        out-of-order build rules).
+  """
+
+  # Dictionary of LibraryInfo objects keyed by BUILD_TYPE value.
+  __library_info = {}
+
+  @staticmethod
+  def get(env):
+    """Gets the LibraryInfo object for the current build type.
+
+    Args:
+      env: The environment object.
+
+    Returns:
+      The LibraryInfo object.
+    """
+    return LibraryInfo.__library_info.setdefault(env['BUILD_TYPE'],
+                                                 LibraryInfo())
+
+  def __init__(self):
+    self.lib_targets = {}
+    self.prebuilt_libraries = set()
+    self.system_libraries = set()
+
+
+def _GetLibParams(env, lib):
+  """Gets the params for the given library if it is a library target.
+
+  Returns the params that were specified when the given lib target name was
+  created, or None if no such lib target has been defined. In the None case, it
+  additionally records the negative result so as to detect out-of-order
+  dependencies for future targets.
+
+  Args:
+    env: The environment object.
+    lib: The library's name as a string.
+
+  Returns:
+    Its dictionary of params, or None.
+  """
+  info = LibraryInfo.get(env)
+  if lib in info.lib_targets:
+    return info.lib_targets[lib]
+  else:
+    if lib not in info.prebuilt_libraries and lib not in info.system_libraries:
+      info.system_libraries.add(lib)
+    return None
+
+
+def _RecordLibParams(env, lib, params):
+  """Record the params used for a library target.
+
+  Record the params used for a library target while checking for several error
+  conditions.
+
+  Args:
+    env: The environment object.
+    lib: The library target's name as a string.
+    params: Its dictionary of params.
+
+  Raises:
+    Exception: The lib target has already been recorded, or the lib was
+        previously declared to be prebuilt, or the lib target is being defined
+        after a reverse library dependency.
+  """
+  info = LibraryInfo.get(env)
+  if lib in info.lib_targets:
+    raise Exception('Multiple definitions of ' + lib)
+  if lib in info.prebuilt_libraries:
+    raise Exception(lib + ' already declared as a prebuilt library')
+  if lib in info.system_libraries:
+    raise Exception(lib + ' cannot be defined after its reverse library '
+                    'dependencies')
+  info.lib_targets[lib] = params
+
+
+def _IsPrebuiltLibrary(env, lib):
+  """Checks whether or not the given library is a prebuilt static library.
+
+  Returns whether or not the given library name has been declared to be a
+  prebuilt static library. In the False case, it additionally records the
+  negative result so as to detect out-of-order dependencies for future targets.
+
+  Args:
+    env: The environment object.
+    lib: The library's name as a string.
+
+  Returns:
+    True or False
+  """
+  info = LibraryInfo.get(env)
+  if lib in info.prebuilt_libraries:
+    return True
+  else:
+    if lib not in info.lib_targets and lib not in info.system_libraries:
+      info.system_libraries.add(lib)
+    return False
+
+
+def _RecordPrebuiltLibrary(env, lib):
+  """Record that a library is a prebuilt static library.
+
+  Record that the given library name refers to a prebuilt static library while
+  checking for several error conditions.
+
+  Args:
+    env: The environment object.
+    lib: The library's name as a string.
+
+  Raises:
+    Exception: The lib has already been recorded to be prebuilt, or the lib was
+        previously declared as a target, or the lib is being declared as
+        prebuilt after a reverse library dependency.
+  """
+  info = LibraryInfo.get(env)
+  if lib in info.prebuilt_libraries:
+    raise Exception('Multiple prebuilt declarations of ' + lib)
+  if lib in info.lib_targets:
+    raise Exception(lib + ' already defined as a target')
+  if lib in info.system_libraries:
+    raise Exception(lib + ' cannot be declared as prebuilt after its reverse '
+                    'library dependencies')
+  info.prebuilt_libraries.add(lib)
+
+
+def _GenericLibrary(env, static, **kwargs):
+  """Extends ComponentLibrary to support multiplatform builds
+     of dynamic or static libraries.
+
+  Args:
+    env: The environment object.
+    kwargs: The keyword arguments.
+
+  Returns:
+    See swtoolkit ComponentLibrary
+  """
+  params = CombineDicts(kwargs, {'COMPONENT_STATIC': static})
+  return ExtendComponent(env, 'ComponentLibrary', **params)
+
+
+def DeclarePrebuiltLibraries(env, libraries):
+  """Informs the build engine about external static libraries.
+
+  Informs the build engine that the given external library name(s) are prebuilt
+  static libraries, as opposed to shared libraries.
+
+  Args:
+    env: The environment object.
+    libraries: The library or libraries that are being declared as prebuilt
+        static libraries.
+  """
+  if not SCons.Util.is_List(libraries):
+    libraries = [libraries]
+  for library in libraries:
+    _RecordPrebuiltLibrary(env, library)
+
+
+def Library(env, **kwargs):
+  """Extends ComponentLibrary to support multiplatform builds of static
+     libraries.
+
+  Args:
+    env: The current environment.
+    kwargs: The keyword arguments.
+
+  Returns:
+    See swtoolkit ComponentLibrary
+  """
+  return _GenericLibrary(env, True, **kwargs)
+
+
+def DynamicLibrary(env, **kwargs):
+  """Extends ComponentLibrary to support multiplatform builds
+     of dynmic libraries.
+
+  Args:
+    env: The environment object.
+    kwargs: The keyword arguments.
+
+  Returns:
+    See swtoolkit ComponentLibrary
+  """
+  return _GenericLibrary(env, False, **kwargs)
+
+
+def Object(env, **kwargs):
+  return ExtendComponent(env, 'ComponentObject', **kwargs)
+
+
+def Unittest(env, **kwargs):
+  """Extends ComponentTestProgram to support unittest built
+     for multiple platforms.
+
+  Args:
+    env: The current environment.
+    kwargs: The keyword arguments.
+
+  Returns:
+    See swtoolkit ComponentProgram.
+  """
+  kwargs['name'] = kwargs['name'] + '_unittest'
+
+  common_test_params = {
+    'posix_cppdefines': ['GUNIT_NO_GOOGLE3', 'GTEST_HAS_RTTI=0'],
+    'libs': ['unittest_main', 'gunit']
+  }
+  if 'explicit_libs' not in kwargs:
+    common_test_params['win_libs'] = [
+      'advapi32',
+      'crypt32',
+      'iphlpapi',
+      'secur32',
+      'shell32',
+      'shlwapi',
+      'user32',
+      'wininet',
+      'ws2_32'
+    ]
+    common_test_params['lin_libs'] = [
+      'crypto',
+      'pthread',
+      'ssl',
+    ]
+
+  params = CombineDicts(kwargs, common_test_params)
+  return ExtendComponent(env, 'ComponentTestProgram', **params)
+
+
+def App(env, **kwargs):
+  """Extends ComponentProgram to support executables with platform specific
+     options.
+
+  Args:
+    env: The current environment.
+    kwargs: The keyword arguments.
+
+  Returns:
+    See swtoolkit ComponentProgram.
+  """
+  if 'explicit_libs' not in kwargs:
+    common_app_params = {
+      'win_libs': [
+        'advapi32',
+        'crypt32',
+        'iphlpapi',
+        'secur32',
+        'shell32',
+        'shlwapi',
+        'user32',
+        'wininet',
+        'ws2_32'
+      ]}
+    params = CombineDicts(kwargs, common_app_params)
+  else:
+    params = kwargs
+  return ExtendComponent(env, 'ComponentProgram', **params)
+
+def WiX(env, **kwargs):
+  """ Extends the WiX builder
+  Args:
+    env: The current environment.
+    kwargs: The keyword arguments.
+
+  Returns:
+    The node produced by the environment's wix builder
+  """
+  return ExtendComponent(env, 'WiX', **kwargs)
+
+def Repository(env, at, path):
+  """Maps a directory external to $MAIN_DIR to the given path so that sources
+     compiled from it end up in the correct place under $OBJ_DIR.  NOT required
+     when only referring to header files.
+
+  Args:
+    env: The current environment object.
+    at: The 'mount point' within the current directory.
+    path: Path to the actual directory.
+  """
+  env.Dir(at).addRepository(env.Dir(path))
+
+
+def Components(*paths):
+  """Completes the directory paths with the correct file
+     names such that the directory/directory.scons name
+     convention can be used.
+
+  Args:
+    paths: The paths to complete. If it refers to an existing
+           file then it is ignored.
+
+  Returns:
+    The completed lif scons files that are needed to build talk.
+  """
+  files = []
+  for path in paths:
+    if os.path.isfile(path):
+      files.append(path)
+    else:
+      files.append(ExpandSconsPath(path))
+  return files
+
+
+def ExpandSconsPath(path):
+  """Expands a directory path into the path to the
+     scons file that our build uses.
+     Ex: magiflute/plugin/common => magicflute/plugin/common/common.scons
+
+  Args:
+    path: The directory path to expand.
+
+  Returns:
+    The expanded path.
+  """
+  return '%s/%s.scons' % (path, os.path.basename(path))
+
+
+def ReadVersion(filename):
+  """Executes the supplied file and pulls out a version definition from it. """
+  defs = {}
+  execfile(str(filename), defs)
+  if 'version' not in defs:
+    return '0.0.0.0'
+  version = defs['version']
+  parts = version.split(',')
+  build = os.environ.get('GOOGLE_VERSION_BUILDNUMBER')
+  if build:
+    parts[-1] = str(build)
+  return '.'.join(parts)
+
+
+#-------------------------------------------------------------------------------
+# Helper methods for translating talk.Foo() declarations in to manipulations of
+# environmuent construction variables, including parameter parsing and merging,
+#
+def PopEntry(dictionary, key):
+  """Get the value from a dictionary by key. If the key
+     isn't in the dictionary then None is returned. If it is in
+     the dictionary the value is fetched and then is it removed
+     from the dictionary.
+
+  Args:
+    dictionary: The dictionary.
+    key: The key to get the value for.
+  Returns:
+    The value or None if the key is missing.
+  """
+  value = None
+  if key in dictionary:
+    value = dictionary[key]
+    dictionary.pop(key)
+  return value
+
+
+def MergeAndFilterByPlatform(env, params):
+  """Take a dictionary of arguments to lists of values, and, depending on
+     which platform we are targetting, merge the lists of associated keys.
+     Merge by combining value lists like so:
+       {win_foo = [a,b], lin_foo = [c,d], foo = [e], mac_bar = [f], bar = [g] }
+       becomes {foo = [a,b,e], bar = [g]} on windows, and
+       {foo = [e], bar = [f,g]} on mac
+
+  Args:
+    env: The hammer environment which knows which platforms are active
+    params: The keyword argument dictionary.
+  Returns:
+    A new dictionary with the filtered and combined entries of params
+  """
+  platforms = {
+    'linux': 'lin_',
+    'mac': 'mac_',
+    'posix': 'posix_',
+    'windows': 'win_',
+  }
+  active_prefixes = [
+    platforms[x] for x in iter(platforms) if env.Bit(x)
+  ]
+  inactive_prefixes = [
+    platforms[x] for x in iter(platforms) if not env.Bit(x)
+  ]
+
+  merged = {}
+  for arg, values in params.iteritems():
+    inactive_platform = False
+
+    key = arg
+
+    for prefix in active_prefixes:
+      if arg.startswith(prefix):
+        key = arg[len(prefix):]
+
+    for prefix in inactive_prefixes:
+      if arg.startswith(prefix):
+        inactive_platform = True
+
+    if inactive_platform:
+      continue
+
+    AddToDict(merged, key, values)
+
+  return merged
+
+
+def MergeSettingsFromLibraryDependencies(env, params):
+  if 'libs' in params:
+    for lib in params['libs']:
+      libparams = _GetLibParams(env, lib)
+      if libparams:
+        if 'dependent_target_settings' in libparams:
+          params = CombineDicts(
+              params,
+              MergeAndFilterByPlatform(
+                  env,
+                  libparams['dependent_target_settings']))
+  return params
+
+
+def ExtendComponent(env, component, **kwargs):
+  """A wrapper around a scons builder function that preprocesses and post-
+     processes its inputs and outputs.  For example, it merges and filters
+     certain keyword arguments before appending them to the environments
+     construction variables.  It can build signed targets and 64bit copies
+     of targets as well.
+
+  Args:
+    env: The hammer environment with which to build the target
+    component: The environment's builder function, e.g. ComponentProgram
+    kwargs: keyword arguments that are either merged, translated, and passed on
+            to the call to component, or which control execution.
+            TODO(): Document the fields, such as cppdefines->CPPDEFINES,
+            prepend_includedirs, include_talk_media_libs, etc.
+  Returns:
+    The output node returned by the call to component, or a subsequent signed
+    dependant node.
+  """
+  env = env.Clone()
+
+  # prune parameters intended for other platforms, then merge
+  params = MergeAndFilterByPlatform(env, kwargs)
+
+  # get the 'target' field
+  name = PopEntry(params, 'name')
+
+  # get the 'packages' field and process it if present (only used for Linux).
+  packages = PopEntry(params, 'packages')
+  if packages and len(packages):
+    params = CombineDicts(params, env.GetPackageParams(packages))
+
+  # save pristine params of lib targets for future reference
+  if 'ComponentLibrary' == component:
+    _RecordLibParams(env, name, dict(params))
+
+  # add any dependent target settings from library dependencies
+  params = MergeSettingsFromLibraryDependencies(env, params)
+
+  # if this is a signed binary we need to make an unsigned version first
+  signed = env.Bit('windows') and PopEntry(params, 'signed')
+  if signed:
+    name = 'unsigned_' + name
+
+  # potentially exit now
+  srcs = PopEntry(params, 'srcs')
+  if not srcs or not hasattr(env, component):
+    return None
+
+  # apply any explicit dependencies
+  dependencies = PopEntry(params, 'depends')
+  if dependencies is not None:
+    env.Depends(name, dependencies)
+
+  # put the contents of params into the environment
+  # some entries are renamed then appended, others renamed then prepended
+  appends = {
+    'cppdefines' : 'CPPDEFINES',
+    'libdirs' : 'LIBPATH',
+    'link_flags' : 'LINKFLAGS',
+    'libs' : 'LIBS',
+    'FRAMEWORKS' : 'FRAMEWORKS',
+  }
+  prepends = {}
+  if env.Bit('windows'):
+    # MSVC compile flags have precedence at the beginning ...
+    prepends['ccflags'] = 'CCFLAGS'
+  else:
+    # ... while GCC compile flags have precedence at the end
+    appends['ccflags'] = 'CCFLAGS'
+  if PopEntry(params, 'prepend_includedirs'):
+    prepends['includedirs'] = 'CPPPATH'
+  else:
+    appends['includedirs'] = 'CPPPATH'
+
+  for field, var in appends.items():
+    values = PopEntry(params, field)
+    if values is not None:
+      env.Append(**{var : values})
+  for field, var in prepends.items():
+    values = PopEntry(params, field)
+    if values is not None:
+      env.Prepend(**{var : values})
+
+  # any other parameters are replaced without renaming
+  for field, value in params.items():
+    env.Replace(**{field : value})
+
+  if env.Bit('linux') and 'LIBS' in env:
+    libs = env['LIBS']
+    # When using --as-needed + --start/end-group, shared libraries need to come
+    # after --end-group on the command-line because the pruning decision only
+    # considers the preceding modules and --start/end-group may cause the
+    # effective position of early static libraries on the command-line to be
+    # deferred to the point of --end-group. To effect this, we move shared libs
+    # into _LIBFLAGS, which has the --end-group as its first entry. SCons does
+    # not track dependencies on system shared libraries anyway so we lose
+    # nothing by removing them from LIBS.
+    static_libs = [lib for lib in libs if
+                   _GetLibParams(env, lib) or _IsPrebuiltLibrary(env, lib)]
+    shared_libs = ['-l' + lib for lib in libs if not
+                   (_GetLibParams(env, lib) or _IsPrebuiltLibrary(env, lib))]
+    env.Replace(LIBS=static_libs)
+    env.Append(_LIBFLAGS=shared_libs)
+
+  # invoke the builder function
+  builder = getattr(env, component)
+
+  node = builder(name, srcs)
+
+  if env.Bit('mac') and 'ComponentProgram' == component:
+    # Build .dSYM debug packages. This is useful even for non-stripped
+    # binaries, as the dsym utility will fetch symbols from all
+    # statically-linked libraries (the linker doesn't include them in to the
+    # final binary).
+    build_dsym = env.Command(
+        env.Dir('$STAGING_DIR/%s.dSYM' % node[0]),
+        node,
+        'mkdir -p `dirname $TARGET` && dsymutil -o $TARGET $SOURCE')
+    env.Alias('all_dsym', env.Alias('%s.dSYM' % node[0], build_dsym))
+
+  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.
+    target = node[0].path.split('_', 1)[1]
+    # postsignprefix: If defined, postsignprefix is a string that should be
+    # prepended to the target executable.  This is to provide a work around
+    # for EXEs and DLLs with the same name, which thus have PDBs with the
+    # same name.  Setting postsignprefix allows the EXE and its PDB
+    # to be renamed and copied in a previous step; then the desired
+    # name of the EXE (but not PDB) is reconstructed after signing.
+    postsignprefix = PopEntry(params, 'postsignprefix')
+    if postsignprefix is not None:
+        target = postsignprefix + target
+    signed_node = env.SignedBinary(
+      source = node,
+      target = '$STAGING_DIR/' + target,
+    )
+    env.Alias('signed_binaries', signed_node)
+    return signed_node
+
+  return node
+
+
+def AddToDict(dictionary, key, values, append=True):
+  """Merge the given key value(s) pair into a dictionary.  If it contains an
+     entry with that key already, then combine by appending or prepending the
+     values as directed.  Otherwise, assign a new keyvalue pair.
+  """
+  if values is None:
+    return
+
+  if key not in dictionary:
+    dictionary[key] = values
+    return
+
+  cur = dictionary[key]
+  # TODO(dape): Make sure that there are no duplicates
+  # in the list. I can't use python set for this since
+  # the nodes that are returned by the SCONS builders
+  # are not hashable.
+  # dictionary[key] = list(set(cur).union(set(values)))
+  if append:
+    dictionary[key] = cur + values
+  else:
+    dictionary[key] = values + cur
+
+
+def CombineDicts(a, b):
+  """Unions two dictionaries of arrays/dictionaries.
+
+  Unions two dictionaries of arrays/dictionaries by combining the values of keys
+  shared between them. The original dictionaries should not be used again after
+  this call.
+
+  Args:
+    a: First dict.
+    b: Second dict.
+
+  Returns:
+    The union of a and b.
+  """
+  c = {}
+  for key in a:
+    if key in b:
+      aval = a[key]
+      bval = b.pop(key)
+      if isinstance(aval, dict) and isinstance(bval, dict):
+        c[key] = CombineDicts(aval, bval)
+      else:
+        c[key] = aval + bval
+    else:
+      c[key] = a[key]
+
+  for key in b:
+    c[key] = b[key]
+
+  return c
+
+
+def RenameKey(d, old, new, append=True):
+  AddToDict(d, new, PopEntry(d, old), append)
diff --git a/talk/sound/alsasoundsystem.cc b/talk/sound/alsasoundsystem.cc
new file mode 100644
index 0000000..de9e2d6
--- /dev/null
+++ b/talk/sound/alsasoundsystem.cc
@@ -0,0 +1,761 @@
+/*
+ * 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/sound/alsasoundsystem.h"
+
+#include "talk/base/common.h"
+#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"
+#include "talk/sound/soundoutputstreaminterface.h"
+
+namespace cricket {
+
+// Lookup table from the cricket format enum in soundsysteminterface.h to
+// ALSA's enums.
+static const snd_pcm_format_t kCricketFormatToAlsaFormatTable[] = {
+  // The order here must match the order in soundsysteminterface.h
+  SND_PCM_FORMAT_S16_LE,
+};
+
+// Lookup table for the size of a single sample of a given format.
+static const size_t kCricketFormatToSampleSizeTable[] = {
+  // The order here must match the order in soundsysteminterface.h
+  sizeof(int16_t),  // 2
+};
+
+// Minimum latency we allow, in microseconds. This is more or less arbitrary,
+// but it has to be at least large enough to be able to buffer data during a
+// missed context switch, and the typical Linux scheduling quantum is 10ms.
+static const int kMinimumLatencyUsecs = 20 * 1000;
+
+// The latency we'll use for kNoLatencyRequirements (chosen arbitrarily).
+static const int kDefaultLatencyUsecs = kMinimumLatencyUsecs * 2;
+
+// We translate newlines in ALSA device descriptions to hyphens.
+static const char kAlsaDescriptionSearch[] = "\n";
+static const char kAlsaDescriptionReplace[] = " - ";
+
+class AlsaDeviceLocator : public SoundDeviceLocator {
+ public:
+  AlsaDeviceLocator(const std::string &name,
+                    const std::string &device_name)
+      : SoundDeviceLocator(name, device_name) {
+    // The ALSA descriptions have newlines in them, which won't show up in
+    // a drop-down box. Replace them with hyphens.
+    talk_base::replace_substrs(kAlsaDescriptionSearch,
+                               sizeof(kAlsaDescriptionSearch) - 1,
+                               kAlsaDescriptionReplace,
+                               sizeof(kAlsaDescriptionReplace) - 1,
+                               &name_);
+  }
+
+  virtual SoundDeviceLocator *Copy() const {
+    return new AlsaDeviceLocator(*this);
+  }
+};
+
+// Functionality that is common to both AlsaInputStream and AlsaOutputStream.
+class AlsaStream {
+ public:
+  AlsaStream(AlsaSoundSystem *alsa,
+             snd_pcm_t *handle,
+             size_t frame_size,
+             int wait_timeout_ms,
+             int flags,
+             int freq)
+      : alsa_(alsa),
+        handle_(handle),
+        frame_size_(frame_size),
+        wait_timeout_ms_(wait_timeout_ms),
+        flags_(flags),
+        freq_(freq) {
+  }
+
+  ~AlsaStream() {
+    Close();
+  }
+
+  // Waits for the stream to be ready to accept/return more data, and returns
+  // how much can be written/read, or 0 if we need to Wait() again.
+  snd_pcm_uframes_t Wait() {
+    snd_pcm_sframes_t frames;
+    // Ideally we would not use snd_pcm_wait() and instead hook snd_pcm_poll_*
+    // into PhysicalSocketServer, but PhysicalSocketServer is nasty enough
+    // already and the current clients of SoundSystemInterface do not run
+    // anything else on their worker threads, so snd_pcm_wait() is good enough.
+    frames = symbol_table()->snd_pcm_avail_update()(handle_);
+    if (frames < 0) {
+      LOG(LS_ERROR) << "snd_pcm_avail_update(): " << GetError(frames);
+      Recover(frames);
+      return 0;
+    } else if (frames > 0) {
+      // Already ready, so no need to wait.
+      return frames;
+    }
+    // Else no space/data available, so must wait.
+    int ready = symbol_table()->snd_pcm_wait()(handle_, wait_timeout_ms_);
+    if (ready < 0) {
+      LOG(LS_ERROR) << "snd_pcm_wait(): " << GetError(ready);
+      Recover(ready);
+      return 0;
+    } else if (ready == 0) {
+      // Timeout, so nothing can be written/read right now.
+      // We set the timeout to twice the requested latency, so continuous
+      // timeouts are indicative of a problem, so log as a warning.
+      LOG(LS_WARNING) << "Timeout while waiting on stream";
+      return 0;
+    }
+    // Else ready > 0 (i.e., 1), so it's ready. Get count.
+    frames = symbol_table()->snd_pcm_avail_update()(handle_);
+    if (frames < 0) {
+      LOG(LS_ERROR) << "snd_pcm_avail_update(): " << GetError(frames);
+      Recover(frames);
+      return 0;
+    } else if (frames == 0) {
+      // wait() said we were ready, so this ought to have been positive. Has
+      // been observed to happen in practice though.
+      LOG(LS_WARNING) << "Spurious wake-up";
+    }
+    return frames;
+  }
+
+  int CurrentDelayUsecs() {
+    if (!(flags_ & SoundSystemInterface::FLAG_REPORT_LATENCY)) {
+      return 0;
+    }
+
+    snd_pcm_sframes_t delay;
+    int err = symbol_table()->snd_pcm_delay()(handle_, &delay);
+    if (err != 0) {
+      LOG(LS_ERROR) << "snd_pcm_delay(): " << GetError(err);
+      Recover(err);
+      // We'd rather continue playout/capture with an incorrect delay than stop
+      // it altogether, so return a valid value.
+      return 0;
+    }
+    // The delay is in frames. Convert to microseconds.
+    return delay * talk_base::kNumMicrosecsPerSec / freq_;
+  }
+
+  // Used to recover from certain recoverable errors, principally buffer overrun
+  // or underrun (identified as EPIPE). Without calling this the stream stays
+  // in the error state forever.
+  bool Recover(int error) {
+    int err;
+    err = symbol_table()->snd_pcm_recover()(
+        handle_,
+        error,
+        // Silent; i.e., no logging on stderr.
+        1);
+    if (err != 0) {
+      // Docs say snd_pcm_recover returns the original error if it is not one
+      // of the recoverable ones, so this log message will probably contain the
+      // same error twice.
+      LOG(LS_ERROR) << "Unable to recover from \"" << GetError(error) << "\": "
+                    << GetError(err);
+      return false;
+    }
+    if (error == -EPIPE &&  // Buffer underrun/overrun.
+        symbol_table()->snd_pcm_stream()(handle_) == SND_PCM_STREAM_CAPTURE) {
+      // For capture streams we also have to repeat the explicit start() to get
+      // data flowing again.
+      err = symbol_table()->snd_pcm_start()(handle_);
+      if (err != 0) {
+        LOG(LS_ERROR) << "snd_pcm_start(): " << GetError(err);
+        return false;
+      }
+    }
+    return true;
+  }
+
+  bool Close() {
+    if (handle_) {
+      int err;
+      err = symbol_table()->snd_pcm_drop()(handle_);
+      if (err != 0) {
+        LOG(LS_ERROR) << "snd_pcm_drop(): " << GetError(err);
+        // Continue anyways.
+      }
+      err = symbol_table()->snd_pcm_close()(handle_);
+      if (err != 0) {
+        LOG(LS_ERROR) << "snd_pcm_close(): " << GetError(err);
+        // Continue anyways.
+      }
+      handle_ = NULL;
+    }
+    return true;
+  }
+
+  AlsaSymbolTable *symbol_table() {
+    return &alsa_->symbol_table_;
+  }
+
+  snd_pcm_t *handle() {
+    return handle_;
+  }
+
+  const char *GetError(int err) {
+    return alsa_->GetError(err);
+  }
+
+  size_t frame_size() {
+    return frame_size_;
+  }
+
+ private:
+  AlsaSoundSystem *alsa_;
+  snd_pcm_t *handle_;
+  size_t frame_size_;
+  int wait_timeout_ms_;
+  int flags_;
+  int freq_;
+
+  DISALLOW_COPY_AND_ASSIGN(AlsaStream);
+};
+
+// Implementation of an input stream. See soundinputstreaminterface.h regarding
+// thread-safety.
+class AlsaInputStream :
+    public SoundInputStreamInterface,
+    private talk_base::Worker {
+ public:
+  AlsaInputStream(AlsaSoundSystem *alsa,
+                  snd_pcm_t *handle,
+                  size_t frame_size,
+                  int wait_timeout_ms,
+                  int flags,
+                  int freq)
+      : stream_(alsa, handle, frame_size, wait_timeout_ms, flags, freq),
+        buffer_size_(0) {
+  }
+
+  virtual ~AlsaInputStream() {
+    bool success = StopReading();
+    // We need that to live.
+    VERIFY(success);
+  }
+
+  virtual bool StartReading() {
+    return StartWork();
+  }
+
+  virtual bool StopReading() {
+    return StopWork();
+  }
+
+  virtual bool GetVolume(int *volume) {
+    // TODO: Implement this.
+    return false;
+  }
+
+  virtual bool SetVolume(int volume) {
+    // TODO: Implement this.
+    return false;
+  }
+
+  virtual bool Close() {
+    return StopReading() && stream_.Close();
+  }
+
+  virtual int LatencyUsecs() {
+    return stream_.CurrentDelayUsecs();
+  }
+
+ private:
+  // Inherited from Worker.
+  virtual void OnStart() {
+    HaveWork();
+  }
+
+  // Inherited from Worker.
+  virtual void OnHaveWork() {
+    // Block waiting for data.
+    snd_pcm_uframes_t avail = stream_.Wait();
+    if (avail > 0) {
+      // Data is available.
+      size_t size = avail * stream_.frame_size();
+      if (size > buffer_size_) {
+        // Must increase buffer size.
+        buffer_.reset(new char[size]);
+        buffer_size_ = size;
+      }
+      // Read all the data.
+      snd_pcm_sframes_t read = stream_.symbol_table()->snd_pcm_readi()(
+          stream_.handle(),
+          buffer_.get(),
+          avail);
+      if (read < 0) {
+        LOG(LS_ERROR) << "snd_pcm_readi(): " << GetError(read);
+        stream_.Recover(read);
+      } else if (read == 0) {
+        // Docs say this shouldn't happen.
+        ASSERT(false);
+        LOG(LS_ERROR) << "No data?";
+      } else {
+        // Got data. Pass it off to the app.
+        SignalSamplesRead(buffer_.get(),
+                          read * stream_.frame_size(),
+                          this);
+      }
+    }
+    // Check for more data with no delay, after any pending messages are
+    // dispatched.
+    HaveWork();
+  }
+
+  // Inherited from Worker.
+  virtual void OnStop() {
+    // Nothing to do.
+  }
+
+  const char *GetError(int err) {
+    return stream_.GetError(err);
+  }
+
+  AlsaStream stream_;
+  talk_base::scoped_array<char> buffer_;
+  size_t buffer_size_;
+
+  DISALLOW_COPY_AND_ASSIGN(AlsaInputStream);
+};
+
+// Implementation of an output stream. See soundoutputstreaminterface.h
+// regarding thread-safety.
+class AlsaOutputStream :
+    public SoundOutputStreamInterface,
+    private talk_base::Worker {
+ public:
+  AlsaOutputStream(AlsaSoundSystem *alsa,
+                   snd_pcm_t *handle,
+                   size_t frame_size,
+                   int wait_timeout_ms,
+                   int flags,
+                   int freq)
+      : stream_(alsa, handle, frame_size, wait_timeout_ms, flags, freq) {
+  }
+
+  virtual ~AlsaOutputStream() {
+    bool success = DisableBufferMonitoring();
+    // We need that to live.
+    VERIFY(success);
+  }
+
+  virtual bool EnableBufferMonitoring() {
+    return StartWork();
+  }
+
+  virtual bool DisableBufferMonitoring() {
+    return StopWork();
+  }
+
+  virtual bool WriteSamples(const void *sample_data,
+                            size_t size) {
+    if (size % stream_.frame_size() != 0) {
+      // No client of SoundSystemInterface does this, so let's not support it.
+      // (If we wanted to support it, we'd basically just buffer the fractional
+      // frame until we get more data.)
+      ASSERT(false);
+      LOG(LS_ERROR) << "Writes with fractional frames are not supported";
+      return false;
+    }
+    snd_pcm_uframes_t frames = size / stream_.frame_size();
+    snd_pcm_sframes_t written = stream_.symbol_table()->snd_pcm_writei()(
+        stream_.handle(),
+        sample_data,
+        frames);
+    if (written < 0) {
+      LOG(LS_ERROR) << "snd_pcm_writei(): " << GetError(written);
+      stream_.Recover(written);
+      return false;
+    } else if (static_cast<snd_pcm_uframes_t>(written) < frames) {
+      // Shouldn't happen. Drop the rest of the data.
+      LOG(LS_ERROR) << "Stream wrote only " << written << " of " << frames
+                    << " frames!";
+      return false;
+    }
+    return true;
+  }
+
+  virtual bool GetVolume(int *volume) {
+    // TODO: Implement this.
+    return false;
+  }
+
+  virtual bool SetVolume(int volume) {
+    // TODO: Implement this.
+    return false;
+  }
+
+  virtual bool Close() {
+    return DisableBufferMonitoring() && stream_.Close();
+  }
+
+  virtual int LatencyUsecs() {
+    return stream_.CurrentDelayUsecs();
+  }
+
+ private:
+  // Inherited from Worker.
+  virtual void OnStart() {
+    HaveWork();
+  }
+
+  // Inherited from Worker.
+  virtual void OnHaveWork() {
+    snd_pcm_uframes_t avail = stream_.Wait();
+    if (avail > 0) {
+      size_t space = avail * stream_.frame_size();
+      SignalBufferSpace(space, this);
+    }
+    HaveWork();
+  }
+
+  // Inherited from Worker.
+  virtual void OnStop() {
+    // Nothing to do.
+  }
+
+  const char *GetError(int err) {
+    return stream_.GetError(err);
+  }
+
+  AlsaStream stream_;
+
+  DISALLOW_COPY_AND_ASSIGN(AlsaOutputStream);
+};
+
+AlsaSoundSystem::AlsaSoundSystem() : initialized_(false) {}
+
+AlsaSoundSystem::~AlsaSoundSystem() {
+  // Not really necessary, because Terminate() doesn't really do anything.
+  Terminate();
+}
+
+bool AlsaSoundSystem::Init() {
+  if (IsInitialized()) {
+    return true;
+  }
+
+  // Load libasound.
+  if (!symbol_table_.Load()) {
+    // Very odd for a Linux machine to not have a working libasound ...
+    LOG(LS_ERROR) << "Failed to load symbol table";
+    return false;
+  }
+
+  initialized_ = true;
+
+  return true;
+}
+
+void AlsaSoundSystem::Terminate() {
+  if (!IsInitialized()) {
+    return;
+  }
+
+  initialized_ = false;
+
+  // We do not unload the symbol table because we may need it again soon if
+  // Init() is called again.
+}
+
+bool AlsaSoundSystem::EnumeratePlaybackDevices(
+    SoundDeviceLocatorList *devices) {
+  return EnumerateDevices(devices, false);
+}
+
+bool AlsaSoundSystem::EnumerateCaptureDevices(
+    SoundDeviceLocatorList *devices) {
+  return EnumerateDevices(devices, true);
+}
+
+bool AlsaSoundSystem::GetDefaultPlaybackDevice(SoundDeviceLocator **device) {
+  return GetDefaultDevice(device);
+}
+
+bool AlsaSoundSystem::GetDefaultCaptureDevice(SoundDeviceLocator **device) {
+  return GetDefaultDevice(device);
+}
+
+SoundOutputStreamInterface *AlsaSoundSystem::OpenPlaybackDevice(
+    const SoundDeviceLocator *device,
+    const OpenParams &params) {
+  return OpenDevice<SoundOutputStreamInterface>(
+      device,
+      params,
+      SND_PCM_STREAM_PLAYBACK,
+      &AlsaSoundSystem::StartOutputStream);
+}
+
+SoundInputStreamInterface *AlsaSoundSystem::OpenCaptureDevice(
+    const SoundDeviceLocator *device,
+    const OpenParams &params) {
+  return OpenDevice<SoundInputStreamInterface>(
+      device,
+      params,
+      SND_PCM_STREAM_CAPTURE,
+      &AlsaSoundSystem::StartInputStream);
+}
+
+const char *AlsaSoundSystem::GetName() const {
+  return "ALSA";
+}
+
+bool AlsaSoundSystem::EnumerateDevices(
+    SoundDeviceLocatorList *devices,
+    bool capture_not_playback) {
+  ClearSoundDeviceLocatorList(devices);
+
+  if (!IsInitialized()) {
+    return false;
+  }
+
+  const char *type = capture_not_playback ? "Input" : "Output";
+  // dmix and dsnoop are only for playback and capture, respectively, but ALSA
+  // stupidly includes them in both lists.
+  const char *ignore_prefix = capture_not_playback ? "dmix:" : "dsnoop:";
+  // (ALSA lists many more "devices" of questionable interest, but we show them
+  // just in case the weird devices may actually be desirable for some
+  // users/systems.)
+  const char *ignore_default = "default";
+  const char *ignore_null = "null";
+  const char *ignore_pulse = "pulse";
+  // The 'pulse' entry has a habit of mysteriously disappearing when you query
+  // a second time. Remove it from our list. (GIPS lib did the same thing.)
+  int err;
+
+  void **hints;
+  err = symbol_table_.snd_device_name_hint()(-1,     // All cards
+                                             "pcm",  // Only PCM devices
+                                             &hints);
+  if (err != 0) {
+    LOG(LS_ERROR) << "snd_device_name_hint(): " << GetError(err);
+    return false;
+  }
+
+  for (void **list = hints; *list != NULL; ++list) {
+    char *actual_type = symbol_table_.snd_device_name_get_hint()(*list, "IOID");
+    if (actual_type) {  // NULL means it's both.
+      bool wrong_type = (strcmp(actual_type, type) != 0);
+      free(actual_type);
+      if (wrong_type) {
+        // Wrong type of device (i.e., input vs. output).
+        continue;
+      }
+    }
+
+    char *name = symbol_table_.snd_device_name_get_hint()(*list, "NAME");
+    if (!name) {
+      LOG(LS_ERROR) << "Device has no name???";
+      // Skip it.
+      continue;
+    }
+
+    // Now check if we actually want to show this device.
+    if (strcmp(name, ignore_default) != 0 &&
+        strcmp(name, ignore_null) != 0 &&
+        strcmp(name, ignore_pulse) != 0 &&
+        !talk_base::starts_with(name, ignore_prefix)) {
+
+      // Yes, we do.
+      char *desc = symbol_table_.snd_device_name_get_hint()(*list, "DESC");
+      if (!desc) {
+        // Virtual devices don't necessarily have descriptions. Use their names
+        // instead (not pretty!).
+        desc = name;
+      }
+
+      AlsaDeviceLocator *device = new AlsaDeviceLocator(desc, name);
+
+      devices->push_back(device);
+
+      if (desc != name) {
+        free(desc);
+      }
+    }
+
+    free(name);
+  }
+
+  err = symbol_table_.snd_device_name_free_hint()(hints);
+  if (err != 0) {
+    LOG(LS_ERROR) << "snd_device_name_free_hint(): " << GetError(err);
+    // Continue and return true anyways, since we did get the whole list.
+  }
+
+  return true;
+}
+
+bool AlsaSoundSystem::GetDefaultDevice(SoundDeviceLocator **device) {
+  if (!IsInitialized()) {
+    return false;
+  }
+  *device = new AlsaDeviceLocator("Default device", "default");
+  return true;
+}
+
+inline size_t AlsaSoundSystem::FrameSize(const OpenParams &params) {
+  ASSERT(static_cast<int>(params.format) <
+         ARRAY_SIZE(kCricketFormatToSampleSizeTable));
+  return kCricketFormatToSampleSizeTable[params.format] * params.channels;
+}
+
+template <typename StreamInterface>
+StreamInterface *AlsaSoundSystem::OpenDevice(
+    const SoundDeviceLocator *device,
+    const OpenParams &params,
+    snd_pcm_stream_t type,
+    StreamInterface *(AlsaSoundSystem::*start_fn)(
+        snd_pcm_t *handle,
+        size_t frame_size,
+        int wait_timeout_ms,
+        int flags,
+        int freq)) {
+
+  if (!IsInitialized()) {
+    return NULL;
+  }
+
+  StreamInterface *stream;
+  int err;
+
+  const char *dev = static_cast<const AlsaDeviceLocator *>(device)->
+      device_name().c_str();
+
+  snd_pcm_t *handle = NULL;
+  err = symbol_table_.snd_pcm_open()(
+      &handle,
+      dev,
+      type,
+      // No flags.
+      0);
+  if (err != 0) {
+    LOG(LS_ERROR) << "snd_pcm_open(" << dev << "): " << GetError(err);
+    return NULL;
+  }
+  LOG(LS_VERBOSE) << "Opening " << dev;
+  ASSERT(handle);  // If open succeeded, handle ought to be valid
+
+  // Compute requested latency in microseconds.
+  int latency;
+  if (params.latency == kNoLatencyRequirements) {
+    latency = kDefaultLatencyUsecs;
+  } else {
+    // kLowLatency is 0, so we treat it the same as a request for zero latency.
+    // Compute what the user asked for.
+    latency = talk_base::kNumMicrosecsPerSec *
+        params.latency /
+        params.freq /
+        FrameSize(params);
+    // And this is what we'll actually use.
+    latency = talk_base::_max(latency, kMinimumLatencyUsecs);
+  }
+
+  ASSERT(static_cast<int>(params.format) <
+         ARRAY_SIZE(kCricketFormatToAlsaFormatTable));
+
+  err = symbol_table_.snd_pcm_set_params()(
+      handle,
+      kCricketFormatToAlsaFormatTable[params.format],
+      // SoundSystemInterface only supports interleaved audio.
+      SND_PCM_ACCESS_RW_INTERLEAVED,
+      params.channels,
+      params.freq,
+      1,  // Allow ALSA to resample.
+      latency);
+  if (err != 0) {
+    LOG(LS_ERROR) << "snd_pcm_set_params(): " << GetError(err);
+    goto fail;
+  }
+
+  err = symbol_table_.snd_pcm_prepare()(handle);
+  if (err != 0) {
+    LOG(LS_ERROR) << "snd_pcm_prepare(): " << GetError(err);
+    goto fail;
+  }
+
+  stream = (this->*start_fn)(
+      handle,
+      FrameSize(params),
+      // We set the wait time to twice the requested latency, so that wait
+      // timeouts should be rare.
+      2 * latency / talk_base::kNumMicrosecsPerMillisec,
+      params.flags,
+      params.freq);
+  if (stream) {
+    return stream;
+  }
+  // Else fall through.
+
+ fail:
+  err = symbol_table_.snd_pcm_close()(handle);
+  if (err != 0) {
+    LOG(LS_ERROR) << "snd_pcm_close(): " << GetError(err);
+  }
+  return NULL;
+}
+
+SoundOutputStreamInterface *AlsaSoundSystem::StartOutputStream(
+    snd_pcm_t *handle,
+    size_t frame_size,
+    int wait_timeout_ms,
+    int flags,
+    int freq) {
+  // Nothing to do here but instantiate the stream.
+  return new AlsaOutputStream(
+      this, handle, frame_size, wait_timeout_ms, flags, freq);
+}
+
+SoundInputStreamInterface *AlsaSoundSystem::StartInputStream(
+    snd_pcm_t *handle,
+    size_t frame_size,
+    int wait_timeout_ms,
+    int flags,
+    int freq) {
+  // Output streams start automatically once enough data has been written, but
+  // input streams must be started manually or else snd_pcm_wait() will never
+  // return true.
+  int err;
+  err = symbol_table_.snd_pcm_start()(handle);
+  if (err != 0) {
+    LOG(LS_ERROR) << "snd_pcm_start(): " << GetError(err);
+    return NULL;
+  }
+  return new AlsaInputStream(
+      this, handle, frame_size, wait_timeout_ms, flags, freq);
+}
+
+inline const char *AlsaSoundSystem::GetError(int err) {
+  return symbol_table_.snd_strerror()(err);
+}
+
+}  // namespace cricket
diff --git a/talk/sound/alsasoundsystem.h b/talk/sound/alsasoundsystem.h
new file mode 100644
index 0000000..870f25e
--- /dev/null
+++ b/talk/sound/alsasoundsystem.h
@@ -0,0 +1,120 @@
+/*
+ * 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_SOUND_ALSASOUNDSYSTEM_H_
+#define TALK_SOUND_ALSASOUNDSYSTEM_H_
+
+#include "talk/base/constructormagic.h"
+#include "talk/sound/alsasymboltable.h"
+#include "talk/sound/soundsysteminterface.h"
+
+namespace cricket {
+
+class AlsaStream;
+class AlsaInputStream;
+class AlsaOutputStream;
+
+// Sound system implementation for ALSA, the predominant sound device API on
+// Linux (but typically not used directly by applications anymore).
+class AlsaSoundSystem : public SoundSystemInterface {
+  friend class AlsaStream;
+  friend class AlsaInputStream;
+  friend class AlsaOutputStream;
+ public:
+  static SoundSystemInterface *Create() {
+    return new AlsaSoundSystem();
+  }
+
+  AlsaSoundSystem();
+
+  virtual ~AlsaSoundSystem();
+
+  virtual bool Init();
+  virtual void Terminate();
+
+  virtual bool EnumeratePlaybackDevices(SoundDeviceLocatorList *devices);
+  virtual bool EnumerateCaptureDevices(SoundDeviceLocatorList *devices);
+
+  virtual bool GetDefaultPlaybackDevice(SoundDeviceLocator **device);
+  virtual bool GetDefaultCaptureDevice(SoundDeviceLocator **device);
+
+  virtual SoundOutputStreamInterface *OpenPlaybackDevice(
+      const SoundDeviceLocator *device,
+      const OpenParams &params);
+  virtual SoundInputStreamInterface *OpenCaptureDevice(
+      const SoundDeviceLocator *device,
+      const OpenParams &params);
+
+  virtual const char *GetName() const;
+
+ private:
+  bool IsInitialized() { return initialized_; }
+
+  bool EnumerateDevices(SoundDeviceLocatorList *devices,
+                        bool capture_not_playback);
+
+  bool GetDefaultDevice(SoundDeviceLocator **device);
+
+  static size_t FrameSize(const OpenParams &params);
+
+  template <typename StreamInterface>
+  StreamInterface *OpenDevice(
+      const SoundDeviceLocator *device,
+      const OpenParams &params,
+      snd_pcm_stream_t type,
+      StreamInterface *(AlsaSoundSystem::*start_fn)(
+          snd_pcm_t *handle,
+          size_t frame_size,
+          int wait_timeout_ms,
+          int flags,
+          int freq));
+
+  SoundOutputStreamInterface *StartOutputStream(
+      snd_pcm_t *handle,
+      size_t frame_size,
+      int wait_timeout_ms,
+      int flags,
+      int freq);
+
+  SoundInputStreamInterface *StartInputStream(
+      snd_pcm_t *handle,
+      size_t frame_size,
+      int wait_timeout_ms,
+      int flags,
+      int freq);
+
+  const char *GetError(int err);
+
+  bool initialized_;
+  AlsaSymbolTable symbol_table_;
+
+  DISALLOW_COPY_AND_ASSIGN(AlsaSoundSystem);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SOUND_ALSASOUNDSYSTEM_H_
diff --git a/talk/sound/alsasymboltable.cc b/talk/sound/alsasymboltable.cc
new file mode 100644
index 0000000..290c729
--- /dev/null
+++ b/talk/sound/alsasymboltable.cc
@@ -0,0 +1,37 @@
+/*
+ * 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/sound/alsasymboltable.h"
+
+namespace cricket {
+
+#define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME ALSA_SYMBOLS_CLASS_NAME
+#define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST ALSA_SYMBOLS_LIST
+#define LATE_BINDING_SYMBOL_TABLE_DLL_NAME "libasound.so.2"
+#include "talk/base/latebindingsymboltable.cc.def"
+
+}  // namespace cricket
diff --git a/talk/sound/alsasymboltable.h b/talk/sound/alsasymboltable.h
new file mode 100644
index 0000000..cf7803f
--- /dev/null
+++ b/talk/sound/alsasymboltable.h
@@ -0,0 +1,66 @@
+/*
+ * 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_SOUND_ALSASYMBOLTABLE_H_
+#define TALK_SOUND_ALSASYMBOLTABLE_H_
+
+#include <alsa/asoundlib.h>
+
+#include "talk/base/latebindingsymboltable.h"
+
+namespace cricket {
+
+#define ALSA_SYMBOLS_CLASS_NAME AlsaSymbolTable
+// The ALSA symbols we need, as an X-Macro list.
+// This list must contain precisely every libasound function that is used in
+// alsasoundsystem.cc.
+#define ALSA_SYMBOLS_LIST \
+  X(snd_device_name_free_hint) \
+  X(snd_device_name_get_hint) \
+  X(snd_device_name_hint) \
+  X(snd_pcm_avail_update) \
+  X(snd_pcm_close) \
+  X(snd_pcm_delay) \
+  X(snd_pcm_drop) \
+  X(snd_pcm_open) \
+  X(snd_pcm_prepare) \
+  X(snd_pcm_readi) \
+  X(snd_pcm_recover) \
+  X(snd_pcm_set_params) \
+  X(snd_pcm_start) \
+  X(snd_pcm_stream) \
+  X(snd_pcm_wait) \
+  X(snd_pcm_writei) \
+  X(snd_strerror)
+
+#define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME ALSA_SYMBOLS_CLASS_NAME
+#define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST ALSA_SYMBOLS_LIST
+#include "talk/base/latebindingsymboltable.h.def"
+
+}  // namespace cricket
+
+#endif  // TALK_SOUND_ALSASYMBOLTABLE_H_
diff --git a/talk/sound/automaticallychosensoundsystem.h b/talk/sound/automaticallychosensoundsystem.h
new file mode 100644
index 0000000..026c080
--- /dev/null
+++ b/talk/sound/automaticallychosensoundsystem.h
@@ -0,0 +1,105 @@
+/*
+ * 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_SOUND_AUTOMATICALLYCHOSENSOUNDSYSTEM_H_
+#define TALK_SOUND_AUTOMATICALLYCHOSENSOUNDSYSTEM_H_
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/sound/soundsysteminterface.h"
+#include "talk/sound/soundsystemproxy.h"
+
+namespace cricket {
+
+// A function type that creates an instance of a sound system implementation.
+typedef SoundSystemInterface *(*SoundSystemCreator)();
+
+// An AutomaticallyChosenSoundSystem is a sound system proxy that defers to
+// an instance of the first sound system implementation in a list that
+// successfully initializes.
+template <const SoundSystemCreator kSoundSystemCreators[], int kNumSoundSystems>
+class AutomaticallyChosenSoundSystem : public SoundSystemProxy {
+ public:
+  // Chooses and initializes the underlying sound system.
+  virtual bool Init();
+  // Terminates the underlying sound system implementation, but caches it for
+  // future re-use.
+  virtual void Terminate();
+
+  virtual const char *GetName() const;
+
+ private:
+  talk_base::scoped_ptr<SoundSystemInterface> sound_systems_[kNumSoundSystems];
+};
+
+template <const SoundSystemCreator kSoundSystemCreators[], int kNumSoundSystems>
+bool AutomaticallyChosenSoundSystem<kSoundSystemCreators,
+                                    kNumSoundSystems>::Init() {
+  if (wrapped_) {
+    return true;
+  }
+  for (int i = 0; i < kNumSoundSystems; ++i) {
+    if (!sound_systems_[i].get()) {
+      sound_systems_[i].reset((*kSoundSystemCreators[i])());
+    }
+    if (sound_systems_[i]->Init()) {
+      // This is the first sound system in the list to successfully
+      // initialize, so we're done.
+      wrapped_ = sound_systems_[i].get();
+      break;
+    }
+    // Else it failed to initialize, so try the remaining ones.
+  }
+  if (!wrapped_) {
+    LOG(LS_ERROR) << "Failed to find a usable sound system";
+    return false;
+  }
+  LOG(LS_INFO) << "Selected " << wrapped_->GetName() << " sound system";
+  return true;
+}
+
+template <const SoundSystemCreator kSoundSystemCreators[], int kNumSoundSystems>
+void AutomaticallyChosenSoundSystem<kSoundSystemCreators,
+                                    kNumSoundSystems>::Terminate() {
+  if (!wrapped_) {
+    return;
+  }
+  wrapped_->Terminate();
+  wrapped_ = NULL;
+  // We do not free the scoped_ptrs because we may be re-init'ed soon.
+}
+
+template <const SoundSystemCreator kSoundSystemCreators[], int kNumSoundSystems>
+const char *AutomaticallyChosenSoundSystem<kSoundSystemCreators,
+                                           kNumSoundSystems>::GetName() const {
+  return wrapped_ ? wrapped_->GetName() : "automatic";
+}
+
+}  // namespace cricket
+
+#endif  // TALK_SOUND_AUTOMATICALLYCHOSENSOUNDSYSTEM_H_
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/linuxsoundsystem.cc b/talk/sound/linuxsoundsystem.cc
new file mode 100644
index 0000000..7980a15
--- /dev/null
+++ b/talk/sound/linuxsoundsystem.cc
@@ -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.
+ */
+
+#include "talk/sound/linuxsoundsystem.h"
+
+#include "talk/sound/alsasoundsystem.h"
+#include "talk/sound/pulseaudiosoundsystem.h"
+
+namespace cricket {
+
+const SoundSystemCreator kLinuxSoundSystemCreators[] = {
+#ifdef HAVE_LIBPULSE
+  &PulseAudioSoundSystem::Create,
+#endif
+  &AlsaSoundSystem::Create,
+};
+
+}  // namespace cricket
diff --git a/talk/sound/linuxsoundsystem.h b/talk/sound/linuxsoundsystem.h
new file mode 100644
index 0000000..eb48b88
--- /dev/null
+++ b/talk/sound/linuxsoundsystem.h
@@ -0,0 +1,58 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SOUND_LINUXSOUNDSYSTEM_H_
+#define TALK_SOUND_LINUXSOUNDSYSTEM_H_
+
+#include "talk/sound/automaticallychosensoundsystem.h"
+
+namespace cricket {
+
+extern const SoundSystemCreator kLinuxSoundSystemCreators[
+#ifdef HAVE_LIBPULSE
+    2
+#else
+    1
+#endif
+    ];
+
+// The vast majority of Linux systems use ALSA for the device-level sound API,
+// but an increasing number are using PulseAudio for the application API and
+// only using ALSA internally in PulseAudio itself. But like everything on
+// Linux this is user-configurable, so we need to support both and choose the
+// right one at run-time.
+// PulseAudioSoundSystem is designed to only successfully initialize if
+// PulseAudio is installed and running, and if it is running then direct device
+// access using ALSA typically won't work, so if PulseAudioSoundSystem
+// initializes then we choose that. Otherwise we choose ALSA.
+typedef AutomaticallyChosenSoundSystem<
+    kLinuxSoundSystemCreators,
+    ARRAY_SIZE(kLinuxSoundSystemCreators)> LinuxSoundSystem;
+
+}  // namespace cricket
+
+#endif  // TALK_SOUND_LINUXSOUNDSYSTEM_H_
diff --git a/talk/sound/nullsoundsystem.cc b/talk/sound/nullsoundsystem.cc
new file mode 100644
index 0000000..2920008
--- /dev/null
+++ b/talk/sound/nullsoundsystem.cc
@@ -0,0 +1,174 @@
+/*
+ * 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/sound/nullsoundsystem.h"
+
+#include "talk/base/logging.h"
+#include "talk/sound/sounddevicelocator.h"
+#include "talk/sound/soundinputstreaminterface.h"
+#include "talk/sound/soundoutputstreaminterface.h"
+
+namespace talk_base {
+
+class Thread;
+
+}
+
+namespace cricket {
+
+// Name used for the single device and the sound system itself.
+static const char kNullName[] = "null";
+
+class NullSoundDeviceLocator : public SoundDeviceLocator {
+ public:
+  NullSoundDeviceLocator() : SoundDeviceLocator(kNullName, kNullName) {}
+
+  virtual SoundDeviceLocator *Copy() const {
+    return new NullSoundDeviceLocator();
+  }
+};
+
+class NullSoundInputStream : public SoundInputStreamInterface {
+ public:
+  virtual bool StartReading() {
+    return true;
+  }
+
+  virtual bool StopReading() {
+    return true;
+  }
+
+  virtual bool GetVolume(int *volume) {
+    *volume = SoundSystemInterface::kMinVolume;
+    return true;
+  }
+
+  virtual bool SetVolume(int volume) {
+    return false;
+  }
+
+  virtual bool Close() {
+    return true;
+  }
+
+  virtual int LatencyUsecs() {
+    return 0;
+  }
+};
+
+class NullSoundOutputStream : public SoundOutputStreamInterface {
+ public:
+  virtual bool EnableBufferMonitoring() {
+    return true;
+  }
+
+  virtual bool DisableBufferMonitoring() {
+    return true;
+  }
+
+  virtual bool WriteSamples(const void *sample_data,
+                            size_t size) {
+    LOG(LS_VERBOSE) << "Got " << size << " bytes of playback samples";
+    return true;
+  }
+
+  virtual bool GetVolume(int *volume) {
+    *volume = SoundSystemInterface::kMinVolume;
+    return true;
+  }
+
+  virtual bool SetVolume(int volume) {
+    return false;
+  }
+
+  virtual bool Close() {
+    return true;
+  }
+
+  virtual int LatencyUsecs() {
+    return 0;
+  }
+};
+
+NullSoundSystem::~NullSoundSystem() {
+}
+
+bool NullSoundSystem::Init() {
+  return true;
+}
+
+void NullSoundSystem::Terminate() {
+  // Nothing to do.
+}
+
+bool NullSoundSystem::EnumeratePlaybackDevices(
+      SoundSystemInterface::SoundDeviceLocatorList *devices) {
+  ClearSoundDeviceLocatorList(devices);
+  SoundDeviceLocator *device;
+  GetDefaultPlaybackDevice(&device);
+  devices->push_back(device);
+  return true;
+}
+
+bool NullSoundSystem::EnumerateCaptureDevices(
+      SoundSystemInterface::SoundDeviceLocatorList *devices) {
+  ClearSoundDeviceLocatorList(devices);
+  SoundDeviceLocator *device;
+  GetDefaultCaptureDevice(&device);
+  devices->push_back(device);
+  return true;
+}
+
+bool NullSoundSystem::GetDefaultPlaybackDevice(
+    SoundDeviceLocator **device) {
+  *device = new NullSoundDeviceLocator();
+  return true;
+}
+
+bool NullSoundSystem::GetDefaultCaptureDevice(
+    SoundDeviceLocator **device) {
+  *device = new NullSoundDeviceLocator();
+  return true;
+}
+
+SoundOutputStreamInterface *NullSoundSystem::OpenPlaybackDevice(
+      const SoundDeviceLocator *device,
+      const OpenParams &params) {
+  return new NullSoundOutputStream();
+}
+
+SoundInputStreamInterface *NullSoundSystem::OpenCaptureDevice(
+      const SoundDeviceLocator *device,
+      const OpenParams &params) {
+  return new NullSoundInputStream();
+}
+
+const char *NullSoundSystem::GetName() const {
+  return kNullName;
+}
+
+}  // namespace cricket
diff --git a/talk/sound/nullsoundsystem.h b/talk/sound/nullsoundsystem.h
new file mode 100644
index 0000000..3edb4f9
--- /dev/null
+++ b/talk/sound/nullsoundsystem.h
@@ -0,0 +1,70 @@
+/*
+ * 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_SOUND_NULLSOUNDSYSTEM_H_
+#define TALK_SOUND_NULLSOUNDSYSTEM_H_
+
+#include "talk/sound/soundsysteminterface.h"
+
+namespace cricket {
+
+class SoundDeviceLocator;
+class SoundInputStreamInterface;
+class SoundOutputStreamInterface;
+
+// A simple reference sound system that drops output samples and generates
+// no input samples.
+class NullSoundSystem : public SoundSystemInterface {
+ public:
+  static SoundSystemInterface *Create() {
+    return new NullSoundSystem();
+  }
+
+  virtual ~NullSoundSystem();
+
+  virtual bool Init();
+  virtual void Terminate();
+
+  virtual bool EnumeratePlaybackDevices(SoundDeviceLocatorList *devices);
+  virtual bool EnumerateCaptureDevices(SoundDeviceLocatorList *devices);
+
+  virtual SoundOutputStreamInterface *OpenPlaybackDevice(
+      const SoundDeviceLocator *device,
+      const OpenParams &params);
+  virtual SoundInputStreamInterface *OpenCaptureDevice(
+      const SoundDeviceLocator *device,
+      const OpenParams &params);
+
+  virtual bool GetDefaultPlaybackDevice(SoundDeviceLocator **device);
+  virtual bool GetDefaultCaptureDevice(SoundDeviceLocator **device);
+
+  virtual const char *GetName() const;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SOUND_NULLSOUNDSYSTEM_H_
diff --git a/talk/sound/nullsoundsystemfactory.cc b/talk/sound/nullsoundsystemfactory.cc
new file mode 100644
index 0000000..089d51f
--- /dev/null
+++ b/talk/sound/nullsoundsystemfactory.cc
@@ -0,0 +1,49 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/sound/nullsoundsystemfactory.h"
+
+#include "talk/sound/nullsoundsystem.h"
+
+namespace cricket {
+
+NullSoundSystemFactory::NullSoundSystemFactory() {
+}
+
+NullSoundSystemFactory::~NullSoundSystemFactory() {
+}
+
+bool NullSoundSystemFactory::SetupInstance() {
+  instance_.reset(new NullSoundSystem());
+  return true;
+}
+
+void NullSoundSystemFactory::CleanupInstance() {
+  instance_.reset();
+}
+
+}  // namespace cricket
diff --git a/talk/sound/nullsoundsystemfactory.h b/talk/sound/nullsoundsystemfactory.h
new file mode 100644
index 0000000..71ae980
--- /dev/null
+++ b/talk/sound/nullsoundsystemfactory.h
@@ -0,0 +1,50 @@
+/*
+ * 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_SOUND_NULLSOUNDSYSTEMFACTORY_H_
+#define TALK_SOUND_NULLSOUNDSYSTEMFACTORY_H_
+
+#include "talk/sound/soundsystemfactory.h"
+
+namespace cricket {
+
+// A SoundSystemFactory that always returns a NullSoundSystem. Intended for
+// testing.
+class NullSoundSystemFactory : public SoundSystemFactory {
+ public:
+  NullSoundSystemFactory();
+  virtual ~NullSoundSystemFactory();
+
+ protected:
+  // Inherited from SoundSystemFactory.
+  virtual bool SetupInstance();
+  virtual void CleanupInstance();
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SOUND_NULLSOUNDSYSTEMFACTORY_H_
diff --git a/talk/sound/platformsoundsystem.cc b/talk/sound/platformsoundsystem.cc
new file mode 100644
index 0000000..9dff9ae
--- /dev/null
+++ b/talk/sound/platformsoundsystem.cc
@@ -0,0 +1,48 @@
+/*
+ * 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/sound/platformsoundsystem.h"
+
+#include "talk/base/common.h"
+#ifdef LINUX
+#include "talk/sound/linuxsoundsystem.h"
+#else
+#include "talk/sound/nullsoundsystem.h"
+#endif
+
+namespace cricket {
+
+SoundSystemInterface *CreatePlatformSoundSystem() {
+#ifdef LINUX
+  return new LinuxSoundSystem();
+#else
+  ASSERT(false && "Not implemented");
+  return new NullSoundSystem();
+#endif
+}
+
+}  // namespace cricket
diff --git a/talk/sound/platformsoundsystem.h b/talk/sound/platformsoundsystem.h
new file mode 100644
index 0000000..1a8d214
--- /dev/null
+++ b/talk/sound/platformsoundsystem.h
@@ -0,0 +1,40 @@
+/*
+ * 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_SOUND_PLATFORMSOUNDSYSTEM_H_
+#define TALK_SOUND_PLATFORMSOUNDSYSTEM_H_
+
+namespace cricket {
+
+class SoundSystemInterface;
+
+// Creates the sound system implementation for this platform.
+SoundSystemInterface *CreatePlatformSoundSystem();
+
+}  // namespace cricket
+
+#endif  // TALK_SOUND_PLATFORMSOUNDSYSTEM_H_
diff --git a/talk/sound/platformsoundsystemfactory.cc b/talk/sound/platformsoundsystemfactory.cc
new file mode 100644
index 0000000..6c69954
--- /dev/null
+++ b/talk/sound/platformsoundsystemfactory.cc
@@ -0,0 +1,57 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/sound/platformsoundsystemfactory.h"
+
+#include "talk/sound/platformsoundsystem.h"
+#include "talk/sound/soundsysteminterface.h"
+
+namespace cricket {
+
+PlatformSoundSystemFactory::PlatformSoundSystemFactory() {
+}
+
+PlatformSoundSystemFactory::~PlatformSoundSystemFactory() {
+}
+
+bool PlatformSoundSystemFactory::SetupInstance() {
+  if (!instance_.get()) {
+    instance_.reset(CreatePlatformSoundSystem());
+  }
+  if (!instance_->Init()) {
+    LOG(LS_ERROR) << "Can't initialize platform's sound system";
+    return false;
+  }
+  return true;
+}
+
+void PlatformSoundSystemFactory::CleanupInstance() {
+  instance_->Terminate();
+  // We do not delete the sound system because we might be re-initialized soon.
+}
+
+}  // namespace cricket
diff --git a/talk/sound/platformsoundsystemfactory.h b/talk/sound/platformsoundsystemfactory.h
new file mode 100644
index 0000000..63ca863
--- /dev/null
+++ b/talk/sound/platformsoundsystemfactory.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_SOUND_PLATFORMSOUNDSYSTEMFACTORY_H_
+#define TALK_SOUND_PLATFORMSOUNDSYSTEMFACTORY_H_
+
+#include "talk/sound/soundsystemfactory.h"
+
+namespace cricket {
+
+// A SoundSystemFactory that returns the platform's native sound system
+// implementation.
+class PlatformSoundSystemFactory : public SoundSystemFactory {
+ public:
+  PlatformSoundSystemFactory();
+  virtual ~PlatformSoundSystemFactory();
+
+ protected:
+  // Inherited from SoundSystemFactory.
+  virtual bool SetupInstance();
+  virtual void CleanupInstance();
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SOUND_PLATFORMSOUNDSYSTEMFACTORY_H_
+
+
diff --git a/talk/sound/pulseaudiosoundsystem.cc b/talk/sound/pulseaudiosoundsystem.cc
new file mode 100644
index 0000000..7eb690a
--- /dev/null
+++ b/talk/sound/pulseaudiosoundsystem.cc
@@ -0,0 +1,1559 @@
+/*
+ * 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/sound/pulseaudiosoundsystem.h"
+
+#ifdef HAVE_LIBPULSE
+
+#include "talk/base/common.h"
+#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"
+
+namespace cricket {
+
+// First PulseAudio protocol version that supports PA_STREAM_ADJUST_LATENCY.
+static const uint32_t kAdjustLatencyProtocolVersion = 13;
+
+// Lookup table from the cricket format enum in soundsysteminterface.h to
+// Pulse's enums.
+static const pa_sample_format_t kCricketFormatToPulseFormatTable[] = {
+  // The order here must match the order in soundsysteminterface.h
+  PA_SAMPLE_S16LE,
+};
+
+// Some timing constants for optimal operation. See
+// https://tango.0pointer.de/pipermail/pulseaudio-discuss/2008-January/001170.html
+// for a good explanation of some of the factors that go into this.
+
+// Playback.
+
+// For playback, there is a round-trip delay to fill the server-side playback
+// buffer, so setting too low of a latency is a buffer underflow risk. We will
+// automatically increase the latency if a buffer underflow does occur, but we
+// also enforce a sane minimum at start-up time. Anything lower would be
+// virtually guaranteed to underflow at least once, so there's no point in
+// allowing lower latencies.
+static const int kPlaybackLatencyMinimumMsecs = 20;
+// Every time a playback stream underflows, we will reconfigure it with target
+// latency that is greater by this amount.
+static const int kPlaybackLatencyIncrementMsecs = 20;
+// We also need to configure a suitable request size. Too small and we'd burn
+// CPU from the overhead of transfering small amounts of data at once. Too large
+// and the amount of data remaining in the buffer right before refilling it
+// would be a buffer underflow risk. We set it to half of the buffer size.
+static const int kPlaybackRequestFactor = 2;
+
+// Capture.
+
+// For capture, low latency is not a buffer overflow risk, but it makes us burn
+// CPU from the overhead of transfering small amounts of data at once, so we set
+// a recommended value that we use for the kLowLatency constant (but if the user
+// explicitly requests something lower then we will honour it).
+// 1ms takes about 6-7% CPU. 5ms takes about 5%. 10ms takes about 4.x%.
+static const int kLowCaptureLatencyMsecs = 10;
+// There is a round-trip delay to ack the data to the server, so the
+// server-side buffer needs extra space to prevent buffer overflow. 20ms is
+// sufficient, but there is no penalty to making it bigger, so we make it huge.
+// (750ms is libpulse's default value for the _total_ buffer size in the
+// kNoLatencyRequirements case.)
+static const int kCaptureBufferExtraMsecs = 750;
+
+static void FillPlaybackBufferAttr(int latency,
+                                   pa_buffer_attr *attr) {
+  attr->maxlength = latency;
+  attr->tlength = latency;
+  attr->minreq = latency / kPlaybackRequestFactor;
+  attr->prebuf = attr->tlength - attr->minreq;
+  LOG(LS_VERBOSE) << "Configuring latency = " << attr->tlength << ", minreq = "
+                  << attr->minreq << ", minfill = " << attr->prebuf;
+}
+
+static pa_volume_t CricketVolumeToPulseVolume(int volume) {
+  // PA's volume space goes from 0% at PA_VOLUME_MUTED (value 0) to 100% at
+  // PA_VOLUME_NORM (value 0x10000). It can also go beyond 100% up to
+  // PA_VOLUME_MAX (value UINT32_MAX-1), but using that is probably unwise.
+  // We just linearly map the 0-255 scale of SoundSystemInterface onto
+  // PA_VOLUME_MUTED-PA_VOLUME_NORM. If the programmer exceeds kMaxVolume then
+  // they can access the over-100% features of PA.
+  return PA_VOLUME_MUTED + (PA_VOLUME_NORM - PA_VOLUME_MUTED) *
+      volume / SoundSystemInterface::kMaxVolume;
+}
+
+static int PulseVolumeToCricketVolume(pa_volume_t pa_volume) {
+  return SoundSystemInterface::kMinVolume +
+      (SoundSystemInterface::kMaxVolume - SoundSystemInterface::kMinVolume) *
+      pa_volume / PA_VOLUME_NORM;
+}
+
+static pa_volume_t MaxChannelVolume(pa_cvolume *channel_volumes) {
+  pa_volume_t pa_volume = PA_VOLUME_MUTED;  // Minimum possible value.
+  for (int i = 0; i < channel_volumes->channels; ++i) {
+    if (pa_volume < channel_volumes->values[i]) {
+      pa_volume = channel_volumes->values[i];
+    }
+  }
+  return pa_volume;
+}
+
+class PulseAudioDeviceLocator : public SoundDeviceLocator {
+ public:
+  PulseAudioDeviceLocator(const std::string &name,
+                          const std::string &device_name)
+      : SoundDeviceLocator(name, device_name) {
+  }
+
+  virtual SoundDeviceLocator *Copy() const {
+    return new PulseAudioDeviceLocator(*this);
+  }
+};
+
+// Functionality that is common to both PulseAudioInputStream and
+// PulseAudioOutputStream.
+class PulseAudioStream {
+ public:
+  PulseAudioStream(PulseAudioSoundSystem *pulse, pa_stream *stream, int flags)
+      : pulse_(pulse), stream_(stream), flags_(flags) {
+  }
+
+  ~PulseAudioStream() {
+    // Close() should have been called during the containing class's destructor.
+    ASSERT(stream_ == NULL);
+  }
+
+  // Must be called with the lock held.
+  bool Close() {
+    if (!IsClosed()) {
+      // Unset this here so that we don't get a TERMINATED callback.
+      symbol_table()->pa_stream_set_state_callback()(stream_, NULL, NULL);
+      if (symbol_table()->pa_stream_disconnect()(stream_) != 0) {
+        LOG(LS_ERROR) << "Can't disconnect stream";
+        // Continue and return true anyways.
+      }
+      symbol_table()->pa_stream_unref()(stream_);
+      stream_ = NULL;
+    }
+    return true;
+  }
+
+  // Must be called with the lock held.
+  int LatencyUsecs() {
+    if (!(flags_ & SoundSystemInterface::FLAG_REPORT_LATENCY)) {
+      return 0;
+    }
+
+    pa_usec_t latency;
+    int negative;
+    Lock();
+    int re = symbol_table()->pa_stream_get_latency()(stream_, &latency,
+        &negative);
+    Unlock();
+    if (re != 0) {
+      LOG(LS_ERROR) << "Can't query latency";
+      // We'd rather continue playout/capture with an incorrect delay than stop
+      // it altogether, so return a valid value.
+      return 0;
+    }
+    if (negative) {
+      // The delay can be negative for monitoring streams if the captured
+      // samples haven't been played yet. In such a case, "latency" contains the
+      // magnitude, so we must negate it to get the real value.
+      return -latency;
+    } else {
+      return latency;
+    }
+  }
+
+  PulseAudioSoundSystem *pulse() {
+    return pulse_;
+  }
+
+  PulseAudioSymbolTable *symbol_table() {
+    return &pulse()->symbol_table_;
+  }
+
+  pa_stream *stream() {
+    ASSERT(stream_ != NULL);
+    return stream_;
+  }
+
+  bool IsClosed() {
+    return stream_ == NULL;
+  }
+
+  void Lock() {
+    pulse()->Lock();
+  }
+
+  void Unlock() {
+    pulse()->Unlock();
+  }
+
+ private:
+  PulseAudioSoundSystem *pulse_;
+  pa_stream *stream_;
+  int flags_;
+
+  DISALLOW_COPY_AND_ASSIGN(PulseAudioStream);
+};
+
+// Implementation of an input stream. See soundinputstreaminterface.h regarding
+// thread-safety.
+class PulseAudioInputStream :
+    public SoundInputStreamInterface,
+    private talk_base::Worker {
+
+  struct GetVolumeCallbackData {
+    PulseAudioInputStream *instance;
+    pa_cvolume *channel_volumes;
+  };
+
+  struct GetSourceChannelCountCallbackData {
+    PulseAudioInputStream *instance;
+    uint8_t *channels;
+  };
+
+ public:
+  PulseAudioInputStream(PulseAudioSoundSystem *pulse,
+                        pa_stream *stream,
+                        int flags)
+      : stream_(pulse, stream, flags),
+        temp_sample_data_(NULL),
+        temp_sample_data_size_(0) {
+    // This callback seems to never be issued, but let's set it anyways.
+    symbol_table()->pa_stream_set_overflow_callback()(stream, &OverflowCallback,
+        NULL);
+  }
+
+  virtual ~PulseAudioInputStream() {
+    bool success = Close();
+    // We need that to live.
+    VERIFY(success);
+  }
+
+  virtual bool StartReading() {
+    return StartWork();
+  }
+
+  virtual bool StopReading() {
+    return StopWork();
+  }
+
+  virtual bool GetVolume(int *volume) {
+    bool ret = false;
+
+    Lock();
+
+    // Unlike output streams, input streams have no concept of a stream volume,
+    // only a device volume. So we have to retrieve the volume of the device
+    // itself.
+
+    pa_cvolume channel_volumes;
+
+    GetVolumeCallbackData data;
+    data.instance = this;
+    data.channel_volumes = &channel_volumes;
+
+    pa_operation *op = symbol_table()->pa_context_get_source_info_by_index()(
+            stream_.pulse()->context_,
+            symbol_table()->pa_stream_get_device_index()(stream_.stream()),
+            &GetVolumeCallbackThunk,
+            &data);
+    if (!stream_.pulse()->FinishOperation(op)) {
+      goto done;
+    }
+
+    if (data.channel_volumes) {
+      // This pointer was never unset by the callback, so we must have received
+      // an empty list of infos. This probably never happens, but we code for it
+      // anyway.
+      LOG(LS_ERROR) << "Did not receive GetVolumeCallback";
+      goto done;
+    }
+
+    // We now have the volume for each channel. Each channel could have a
+    // different volume if, e.g., the user went and changed the volumes in the
+    // PA UI. To get a single volume for SoundSystemInterface we just take the
+    // maximum. Ideally we'd do so with pa_cvolume_max, but it doesn't exist in
+    // Hardy, so we do it manually.
+    pa_volume_t pa_volume;
+    pa_volume = MaxChannelVolume(&channel_volumes);
+    // Now map onto the SoundSystemInterface range.
+    *volume = PulseVolumeToCricketVolume(pa_volume);
+
+    ret = true;
+   done:
+    Unlock();
+    return ret;
+  }
+
+  virtual bool SetVolume(int volume) {
+    bool ret = false;
+    pa_volume_t pa_volume = CricketVolumeToPulseVolume(volume);
+
+    Lock();
+
+    // Unlike output streams, input streams have no concept of a stream volume,
+    // only a device volume. So we have to change the volume of the device
+    // itself.
+
+    // The device may have a different number of channels than the stream and
+    // their mapping may be different, so we don't want to use the channel count
+    // from our sample spec. We could use PA_CHANNELS_MAX to cover our bases,
+    // and the server allows that even if the device's channel count is lower,
+    // but some buggy PA clients don't like that (the pavucontrol on Hardy dies
+    // in an assert if the channel count is different). So instead we look up
+    // the actual number of channels that the device has.
+
+    uint8_t channels;
+
+    GetSourceChannelCountCallbackData data;
+    data.instance = this;
+    data.channels = &channels;
+
+    uint32_t device_index = symbol_table()->pa_stream_get_device_index()(
+        stream_.stream());
+
+    pa_operation *op = symbol_table()->pa_context_get_source_info_by_index()(
+        stream_.pulse()->context_,
+        device_index,
+        &GetSourceChannelCountCallbackThunk,
+        &data);
+    if (!stream_.pulse()->FinishOperation(op)) {
+      goto done;
+    }
+
+    if (data.channels) {
+      // This pointer was never unset by the callback, so we must have received
+      // an empty list of infos. This probably never happens, but we code for it
+      // anyway.
+      LOG(LS_ERROR) << "Did not receive GetSourceChannelCountCallback";
+      goto done;
+    }
+
+    pa_cvolume channel_volumes;
+    symbol_table()->pa_cvolume_set()(&channel_volumes, channels, pa_volume);
+
+    op = symbol_table()->pa_context_set_source_volume_by_index()(
+        stream_.pulse()->context_,
+        device_index,
+        &channel_volumes,
+        // This callback merely logs errors.
+        &SetVolumeCallback,
+        NULL);
+    if (!op) {
+      LOG(LS_ERROR) << "pa_context_set_source_volume_by_index()";
+      goto done;
+    }
+    // Don't need to wait for this to complete.
+    symbol_table()->pa_operation_unref()(op);
+
+    ret = true;
+   done:
+    Unlock();
+    return ret;
+  }
+
+  virtual bool Close() {
+    if (!StopReading()) {
+      return false;
+    }
+    bool ret = true;
+    if (!stream_.IsClosed()) {
+      Lock();
+      ret = stream_.Close();
+      Unlock();
+    }
+    return ret;
+  }
+
+  virtual int LatencyUsecs() {
+    return stream_.LatencyUsecs();
+  }
+
+ private:
+  void Lock() {
+    stream_.Lock();
+  }
+
+  void Unlock() {
+    stream_.Unlock();
+  }
+
+  PulseAudioSymbolTable *symbol_table() {
+    return stream_.symbol_table();
+  }
+
+  void EnableReadCallback() {
+    symbol_table()->pa_stream_set_read_callback()(
+         stream_.stream(),
+         &ReadCallbackThunk,
+         this);
+  }
+
+  void DisableReadCallback() {
+    symbol_table()->pa_stream_set_read_callback()(
+         stream_.stream(),
+         NULL,
+         NULL);
+  }
+
+  static void ReadCallbackThunk(pa_stream *unused1,
+                                size_t unused2,
+                                void *userdata) {
+    PulseAudioInputStream *instance =
+        static_cast<PulseAudioInputStream *>(userdata);
+    instance->OnReadCallback();
+  }
+
+  void OnReadCallback() {
+    // We get the data pointer and size now in order to save one Lock/Unlock
+    // on OnMessage.
+    if (symbol_table()->pa_stream_peek()(stream_.stream(),
+                                         &temp_sample_data_,
+                                         &temp_sample_data_size_) != 0) {
+      LOG(LS_ERROR) << "Can't read data!";
+      return;
+    }
+    // Since we consume the data asynchronously on a different thread, we have
+    // to temporarily disable the read callback or else Pulse will call it
+    // continuously until we consume the data. We re-enable it below.
+    DisableReadCallback();
+    HaveWork();
+  }
+
+  // Inherited from Worker.
+  virtual void OnStart() {
+    Lock();
+    EnableReadCallback();
+    Unlock();
+  }
+
+  // Inherited from Worker.
+  virtual void OnHaveWork() {
+    ASSERT(temp_sample_data_ && temp_sample_data_size_);
+    SignalSamplesRead(temp_sample_data_,
+                      temp_sample_data_size_,
+                      this);
+    temp_sample_data_ = NULL;
+    temp_sample_data_size_ = 0;
+
+    Lock();
+    for (;;) {
+      // Ack the last thing we read.
+      if (symbol_table()->pa_stream_drop()(stream_.stream()) != 0) {
+        LOG(LS_ERROR) << "Can't ack read data";
+      }
+
+      if (symbol_table()->pa_stream_readable_size()(stream_.stream()) <= 0) {
+        // Then that was all the data.
+        break;
+      }
+
+      // Else more data.
+      const void *sample_data;
+      size_t sample_data_size;
+      if (symbol_table()->pa_stream_peek()(stream_.stream(),
+                                           &sample_data,
+                                           &sample_data_size) != 0) {
+        LOG(LS_ERROR) << "Can't read data!";
+        break;
+      }
+
+      // Drop lock for sigslot dispatch, which could take a while.
+      Unlock();
+      SignalSamplesRead(sample_data, sample_data_size, this);
+      Lock();
+
+      // Return to top of loop for the ack and the check for more data.
+    }
+    EnableReadCallback();
+    Unlock();
+  }
+
+  // Inherited from Worker.
+  virtual void OnStop() {
+    Lock();
+    DisableReadCallback();
+    Unlock();
+  }
+
+  static void OverflowCallback(pa_stream *stream,
+                               void *userdata) {
+    LOG(LS_WARNING) << "Buffer overflow on capture stream " << stream;
+  }
+
+  static void GetVolumeCallbackThunk(pa_context *unused,
+                                     const pa_source_info *info,
+                                     int eol,
+                                     void *userdata) {
+    GetVolumeCallbackData *data =
+        static_cast<GetVolumeCallbackData *>(userdata);
+    data->instance->OnGetVolumeCallback(info, eol, &data->channel_volumes);
+  }
+
+  void OnGetVolumeCallback(const pa_source_info *info,
+                           int eol,
+                           pa_cvolume **channel_volumes) {
+    if (eol) {
+      // List is over. Wake GetVolume().
+      stream_.pulse()->Signal();
+      return;
+    }
+
+    if (*channel_volumes) {
+      **channel_volumes = info->volume;
+      // Unset the pointer so that we know that we have have already copied the
+      // volume.
+      *channel_volumes = NULL;
+    } else {
+      // We have received an additional callback after the first one, which
+      // doesn't make sense for a single source. This probably never happens,
+      // but we code for it anyway.
+      LOG(LS_WARNING) << "Ignoring extra GetVolumeCallback";
+    }
+  }
+
+  static void GetSourceChannelCountCallbackThunk(pa_context *unused,
+                                                 const pa_source_info *info,
+                                                 int eol,
+                                                 void *userdata) {
+    GetSourceChannelCountCallbackData *data =
+        static_cast<GetSourceChannelCountCallbackData *>(userdata);
+    data->instance->OnGetSourceChannelCountCallback(info, eol, &data->channels);
+  }
+
+  void OnGetSourceChannelCountCallback(const pa_source_info *info,
+                                       int eol,
+                                       uint8_t **channels) {
+    if (eol) {
+      // List is over. Wake SetVolume().
+      stream_.pulse()->Signal();
+      return;
+    }
+
+    if (*channels) {
+      **channels = info->channel_map.channels;
+      // Unset the pointer so that we know that we have have already copied the
+      // channel count.
+      *channels = NULL;
+    } else {
+      // We have received an additional callback after the first one, which
+      // doesn't make sense for a single source. This probably never happens,
+      // but we code for it anyway.
+      LOG(LS_WARNING) << "Ignoring extra GetSourceChannelCountCallback";
+    }
+  }
+
+  static void SetVolumeCallback(pa_context *unused1,
+                                int success,
+                                void *unused2) {
+    if (!success) {
+      LOG(LS_ERROR) << "Failed to change capture volume";
+    }
+  }
+
+  PulseAudioStream stream_;
+  // Temporary storage for passing data between threads.
+  const void *temp_sample_data_;
+  size_t temp_sample_data_size_;
+
+  DISALLOW_COPY_AND_ASSIGN(PulseAudioInputStream);
+};
+
+// Implementation of an output stream. See soundoutputstreaminterface.h
+// regarding thread-safety.
+class PulseAudioOutputStream :
+    public SoundOutputStreamInterface,
+    private talk_base::Worker {
+
+  struct GetVolumeCallbackData {
+    PulseAudioOutputStream *instance;
+    pa_cvolume *channel_volumes;
+  };
+
+ public:
+  PulseAudioOutputStream(PulseAudioSoundSystem *pulse,
+                         pa_stream *stream,
+                         int flags,
+                         int latency)
+      : stream_(pulse, stream, flags),
+        configured_latency_(latency),
+        temp_buffer_space_(0) {
+    symbol_table()->pa_stream_set_underflow_callback()(stream,
+                                                       &UnderflowCallbackThunk,
+                                                       this);
+  }
+
+  virtual ~PulseAudioOutputStream() {
+    bool success = Close();
+    // We need that to live.
+    VERIFY(success);
+  }
+
+  virtual bool EnableBufferMonitoring() {
+    return StartWork();
+  }
+
+  virtual bool DisableBufferMonitoring() {
+    return StopWork();
+  }
+
+  virtual bool WriteSamples(const void *sample_data,
+                            size_t size) {
+    bool ret = true;
+    Lock();
+    if (symbol_table()->pa_stream_write()(stream_.stream(),
+                                          sample_data,
+                                          size,
+                                          NULL,
+                                          0,
+                                          PA_SEEK_RELATIVE) != 0) {
+      LOG(LS_ERROR) << "Unable to write";
+      ret = false;
+    }
+    Unlock();
+    return ret;
+  }
+
+  virtual bool GetVolume(int *volume) {
+    bool ret = false;
+
+    Lock();
+
+    pa_cvolume channel_volumes;
+
+    GetVolumeCallbackData data;
+    data.instance = this;
+    data.channel_volumes = &channel_volumes;
+
+    pa_operation *op = symbol_table()->pa_context_get_sink_input_info()(
+            stream_.pulse()->context_,
+            symbol_table()->pa_stream_get_index()(stream_.stream()),
+            &GetVolumeCallbackThunk,
+            &data);
+    if (!stream_.pulse()->FinishOperation(op)) {
+      goto done;
+    }
+
+    if (data.channel_volumes) {
+      // This pointer was never unset by the callback, so we must have received
+      // an empty list of infos. This probably never happens, but we code for it
+      // anyway.
+      LOG(LS_ERROR) << "Did not receive GetVolumeCallback";
+      goto done;
+    }
+
+    // We now have the volume for each channel. Each channel could have a
+    // different volume if, e.g., the user went and changed the volumes in the
+    // PA UI. To get a single volume for SoundSystemInterface we just take the
+    // maximum. Ideally we'd do so with pa_cvolume_max, but it doesn't exist in
+    // Hardy, so we do it manually.
+    pa_volume_t pa_volume;
+    pa_volume = MaxChannelVolume(&channel_volumes);
+    // Now map onto the SoundSystemInterface range.
+    *volume = PulseVolumeToCricketVolume(pa_volume);
+
+    ret = true;
+   done:
+    Unlock();
+    return ret;
+  }
+
+  virtual bool SetVolume(int volume) {
+    bool ret = false;
+    pa_volume_t pa_volume = CricketVolumeToPulseVolume(volume);
+
+    Lock();
+
+    const pa_sample_spec *spec = symbol_table()->pa_stream_get_sample_spec()(
+        stream_.stream());
+    if (!spec) {
+      LOG(LS_ERROR) << "pa_stream_get_sample_spec()";
+      goto done;
+    }
+
+    pa_cvolume channel_volumes;
+    symbol_table()->pa_cvolume_set()(&channel_volumes, spec->channels,
+        pa_volume);
+
+    pa_operation *op;
+    op = symbol_table()->pa_context_set_sink_input_volume()(
+        stream_.pulse()->context_,
+        symbol_table()->pa_stream_get_index()(stream_.stream()),
+        &channel_volumes,
+        // This callback merely logs errors.
+        &SetVolumeCallback,
+        NULL);
+    if (!op) {
+      LOG(LS_ERROR) << "pa_context_set_sink_input_volume()";
+      goto done;
+    }
+    // Don't need to wait for this to complete.
+    symbol_table()->pa_operation_unref()(op);
+
+    ret = true;
+   done:
+    Unlock();
+    return ret;
+  }
+
+  virtual bool Close() {
+    if (!DisableBufferMonitoring()) {
+      return false;
+    }
+    bool ret = true;
+    if (!stream_.IsClosed()) {
+      Lock();
+      symbol_table()->pa_stream_set_underflow_callback()(stream_.stream(),
+                                                         NULL,
+                                                         NULL);
+      ret = stream_.Close();
+      Unlock();
+    }
+    return ret;
+  }
+
+  virtual int LatencyUsecs() {
+    return stream_.LatencyUsecs();
+  }
+
+#if 0
+  // TODO: Versions 0.9.16 and later of Pulse have a new API for
+  // zero-copy writes, but Hardy is not new enough to have that so we can't
+  // rely on it. Perhaps auto-detect if it's present or not and use it if we
+  // can?
+
+  virtual bool GetWriteBuffer(void **buffer, size_t *size) {
+    bool ret = true;
+    Lock();
+    if (symbol_table()->pa_stream_begin_write()(stream_.stream(), buffer, size)
+            != 0) {
+      LOG(LS_ERROR) << "Can't get write buffer";
+      ret = false;
+    }
+    Unlock();
+    return ret;
+  }
+
+  // Releases the caller's hold on the write buffer. "written" must be the
+  // amount of data that was written.
+  virtual bool ReleaseWriteBuffer(void *buffer, size_t written) {
+    bool ret = true;
+    Lock();
+    if (written == 0) {
+      if (symbol_table()->pa_stream_cancel_write()(stream_.stream()) != 0) {
+        LOG(LS_ERROR) << "Can't cancel write";
+        ret = false;
+      }
+    } else {
+      if (symbol_table()->pa_stream_write()(stream_.stream(),
+                                            buffer,
+                                            written,
+                                            NULL,
+                                            0,
+                                            PA_SEEK_RELATIVE) != 0) {
+        LOG(LS_ERROR) << "Unable to write";
+        ret = false;
+      }
+    }
+    Unlock();
+    return ret;
+  }
+#endif
+
+ private:
+  void Lock() {
+    stream_.Lock();
+  }
+
+  void Unlock() {
+    stream_.Unlock();
+  }
+
+  PulseAudioSymbolTable *symbol_table() {
+    return stream_.symbol_table();
+  }
+
+  void EnableWriteCallback() {
+    pa_stream_state_t state = symbol_table()->pa_stream_get_state()(
+        stream_.stream());
+    if (state == PA_STREAM_READY) {
+      // May already have available space. Must check.
+      temp_buffer_space_ = symbol_table()->pa_stream_writable_size()(
+          stream_.stream());
+      if (temp_buffer_space_ > 0) {
+        // Yup, there is already space available, so if we register a write
+        // callback then it will not receive any event. So dispatch one ourself
+        // instead.
+        HaveWork();
+        return;
+      }
+    }
+    symbol_table()->pa_stream_set_write_callback()(
+         stream_.stream(),
+         &WriteCallbackThunk,
+         this);
+  }
+
+  void DisableWriteCallback() {
+    symbol_table()->pa_stream_set_write_callback()(
+         stream_.stream(),
+         NULL,
+         NULL);
+  }
+
+  static void WriteCallbackThunk(pa_stream *unused,
+                                 size_t buffer_space,
+                                 void *userdata) {
+    PulseAudioOutputStream *instance =
+        static_cast<PulseAudioOutputStream *>(userdata);
+    instance->OnWriteCallback(buffer_space);
+  }
+
+  void OnWriteCallback(size_t buffer_space) {
+    temp_buffer_space_ = buffer_space;
+    // Since we write the data asynchronously on a different thread, we have
+    // to temporarily disable the write callback or else Pulse will call it
+    // continuously until we write the data. We re-enable it below.
+    DisableWriteCallback();
+    HaveWork();
+  }
+
+  // Inherited from Worker.
+  virtual void OnStart() {
+    Lock();
+    EnableWriteCallback();
+    Unlock();
+  }
+
+  // Inherited from Worker.
+  virtual void OnHaveWork() {
+    ASSERT(temp_buffer_space_ > 0);
+
+    SignalBufferSpace(temp_buffer_space_, this);
+
+    temp_buffer_space_ = 0;
+    Lock();
+    EnableWriteCallback();
+    Unlock();
+  }
+
+  // Inherited from Worker.
+  virtual void OnStop() {
+    Lock();
+    DisableWriteCallback();
+    Unlock();
+  }
+
+  static void UnderflowCallbackThunk(pa_stream *unused,
+                                     void *userdata) {
+    PulseAudioOutputStream *instance =
+        static_cast<PulseAudioOutputStream *>(userdata);
+    instance->OnUnderflowCallback();
+  }
+
+  void OnUnderflowCallback() {
+    LOG(LS_WARNING) << "Buffer underflow on playback stream "
+                    << stream_.stream();
+
+    if (configured_latency_ == SoundSystemInterface::kNoLatencyRequirements) {
+      // We didn't configure a pa_buffer_attr before, so switching to one now
+      // would be questionable.
+      return;
+    }
+
+    // Otherwise reconfigure the stream with a higher target latency.
+
+    const pa_sample_spec *spec = symbol_table()->pa_stream_get_sample_spec()(
+        stream_.stream());
+    if (!spec) {
+      LOG(LS_ERROR) << "pa_stream_get_sample_spec()";
+      return;
+    }
+
+    size_t bytes_per_sec = symbol_table()->pa_bytes_per_second()(spec);
+
+    int new_latency = configured_latency_ +
+        bytes_per_sec * kPlaybackLatencyIncrementMsecs /
+        talk_base::kNumMicrosecsPerSec;
+
+    pa_buffer_attr new_attr = {0};
+    FillPlaybackBufferAttr(new_latency, &new_attr);
+
+    pa_operation *op = symbol_table()->pa_stream_set_buffer_attr()(
+        stream_.stream(),
+        &new_attr,
+        // No callback.
+        NULL,
+        NULL);
+    if (!op) {
+      LOG(LS_ERROR) << "pa_stream_set_buffer_attr()";
+      return;
+    }
+    // Don't need to wait for this to complete.
+    symbol_table()->pa_operation_unref()(op);
+
+    // Save the new latency in case we underflow again.
+    configured_latency_ = new_latency;
+  }
+
+  static void GetVolumeCallbackThunk(pa_context *unused,
+                                     const pa_sink_input_info *info,
+                                     int eol,
+                                     void *userdata) {
+    GetVolumeCallbackData *data =
+        static_cast<GetVolumeCallbackData *>(userdata);
+    data->instance->OnGetVolumeCallback(info, eol, &data->channel_volumes);
+  }
+
+  void OnGetVolumeCallback(const pa_sink_input_info *info,
+                           int eol,
+                           pa_cvolume **channel_volumes) {
+    if (eol) {
+      // List is over. Wake GetVolume().
+      stream_.pulse()->Signal();
+      return;
+    }
+
+    if (*channel_volumes) {
+      **channel_volumes = info->volume;
+      // Unset the pointer so that we know that we have have already copied the
+      // volume.
+      *channel_volumes = NULL;
+    } else {
+      // We have received an additional callback after the first one, which
+      // doesn't make sense for a single sink input. This probably never
+      // happens, but we code for it anyway.
+      LOG(LS_WARNING) << "Ignoring extra GetVolumeCallback";
+    }
+  }
+
+  static void SetVolumeCallback(pa_context *unused1,
+                                int success,
+                                void *unused2) {
+    if (!success) {
+      LOG(LS_ERROR) << "Failed to change playback volume";
+    }
+  }
+
+  PulseAudioStream stream_;
+  int configured_latency_;
+  // Temporary storage for passing data between threads.
+  size_t temp_buffer_space_;
+
+  DISALLOW_COPY_AND_ASSIGN(PulseAudioOutputStream);
+};
+
+PulseAudioSoundSystem::PulseAudioSoundSystem()
+    : mainloop_(NULL), context_(NULL) {
+}
+
+PulseAudioSoundSystem::~PulseAudioSoundSystem() {
+  Terminate();
+}
+
+bool PulseAudioSoundSystem::Init() {
+  if (IsInitialized()) {
+    return true;
+  }
+
+  // Load libpulse.
+  if (!symbol_table_.Load()) {
+    // Most likely the Pulse library and sound server are not installed on
+    // this system.
+    LOG(LS_WARNING) << "Failed to load symbol table";
+    return false;
+  }
+
+  // Now create and start the Pulse event thread.
+  mainloop_ = symbol_table_.pa_threaded_mainloop_new()();
+  if (!mainloop_) {
+    LOG(LS_ERROR) << "Can't create mainloop";
+    goto fail0;
+  }
+
+  if (symbol_table_.pa_threaded_mainloop_start()(mainloop_) != 0) {
+    LOG(LS_ERROR) << "Can't start mainloop";
+    goto fail1;
+  }
+
+  Lock();
+  context_ = CreateNewConnection();
+  Unlock();
+
+  if (!context_) {
+    goto fail2;
+  }
+
+  // Otherwise we're now ready!
+  return true;
+
+ fail2:
+  symbol_table_.pa_threaded_mainloop_stop()(mainloop_);
+ fail1:
+  symbol_table_.pa_threaded_mainloop_free()(mainloop_);
+  mainloop_ = NULL;
+ fail0:
+  return false;
+}
+
+void PulseAudioSoundSystem::Terminate() {
+  if (!IsInitialized()) {
+    return;
+  }
+
+  Lock();
+  symbol_table_.pa_context_disconnect()(context_);
+  symbol_table_.pa_context_unref()(context_);
+  Unlock();
+  context_ = NULL;
+  symbol_table_.pa_threaded_mainloop_stop()(mainloop_);
+  symbol_table_.pa_threaded_mainloop_free()(mainloop_);
+  mainloop_ = NULL;
+
+  // We do not unload the symbol table because we may need it again soon if
+  // Init() is called again.
+}
+
+bool PulseAudioSoundSystem::EnumeratePlaybackDevices(
+    SoundDeviceLocatorList *devices) {
+  return EnumerateDevices<pa_sink_info>(
+      devices,
+      symbol_table_.pa_context_get_sink_info_list(),
+      &EnumeratePlaybackDevicesCallbackThunk);
+}
+
+bool PulseAudioSoundSystem::EnumerateCaptureDevices(
+    SoundDeviceLocatorList *devices) {
+  return EnumerateDevices<pa_source_info>(
+      devices,
+      symbol_table_.pa_context_get_source_info_list(),
+      &EnumerateCaptureDevicesCallbackThunk);
+}
+
+bool PulseAudioSoundSystem::GetDefaultPlaybackDevice(
+    SoundDeviceLocator **device) {
+  return GetDefaultDevice<&pa_server_info::default_sink_name>(device);
+}
+
+bool PulseAudioSoundSystem::GetDefaultCaptureDevice(
+    SoundDeviceLocator **device) {
+  return GetDefaultDevice<&pa_server_info::default_source_name>(device);
+}
+
+SoundOutputStreamInterface *PulseAudioSoundSystem::OpenPlaybackDevice(
+    const SoundDeviceLocator *device,
+    const OpenParams &params) {
+  return OpenDevice<SoundOutputStreamInterface>(
+      device,
+      params,
+      "Playback",
+      &PulseAudioSoundSystem::ConnectOutputStream);
+}
+
+SoundInputStreamInterface *PulseAudioSoundSystem::OpenCaptureDevice(
+    const SoundDeviceLocator *device,
+    const OpenParams &params) {
+  return OpenDevice<SoundInputStreamInterface>(
+      device,
+      params,
+      "Capture",
+      &PulseAudioSoundSystem::ConnectInputStream);
+}
+
+const char *PulseAudioSoundSystem::GetName() const {
+  return "PulseAudio";
+}
+
+inline bool PulseAudioSoundSystem::IsInitialized() {
+  return mainloop_ != NULL;
+}
+
+struct ConnectToPulseCallbackData {
+  PulseAudioSoundSystem *instance;
+  bool connect_done;
+};
+
+void PulseAudioSoundSystem::ConnectToPulseCallbackThunk(
+    pa_context *context, void *userdata) {
+  ConnectToPulseCallbackData *data =
+      static_cast<ConnectToPulseCallbackData *>(userdata);
+  data->instance->OnConnectToPulseCallback(context, &data->connect_done);
+}
+
+void PulseAudioSoundSystem::OnConnectToPulseCallback(
+    pa_context *context, bool *connect_done) {
+  pa_context_state_t state = symbol_table_.pa_context_get_state()(context);
+  if (state == PA_CONTEXT_READY ||
+      state == PA_CONTEXT_FAILED ||
+      state == PA_CONTEXT_TERMINATED) {
+    // Connection process has reached a terminal state. Wake ConnectToPulse().
+    *connect_done = true;
+    Signal();
+  }
+}
+
+// Must be called with the lock held.
+bool PulseAudioSoundSystem::ConnectToPulse(pa_context *context) {
+  bool ret = true;
+  ConnectToPulseCallbackData data;
+  // Have to put this up here to satisfy the compiler.
+  pa_context_state_t state;
+
+  data.instance = this;
+  data.connect_done = false;
+
+  symbol_table_.pa_context_set_state_callback()(context,
+                                                &ConnectToPulseCallbackThunk,
+                                                &data);
+
+  // Connect to PulseAudio sound server.
+  if (symbol_table_.pa_context_connect()(
+          context,
+          NULL,          // Default server
+          PA_CONTEXT_NOAUTOSPAWN,
+          NULL) != 0) {  // No special fork handling needed
+    LOG(LS_ERROR) << "Can't start connection to PulseAudio sound server";
+    ret = false;
+    goto done;
+  }
+
+  // Wait for the connection state machine to reach a terminal state.
+  do {
+    Wait();
+  } while (!data.connect_done);
+
+  // Now check to see what final state we reached.
+  state = symbol_table_.pa_context_get_state()(context);
+
+  if (state != PA_CONTEXT_READY) {
+    if (state == PA_CONTEXT_FAILED) {
+      LOG(LS_ERROR) << "Failed to connect to PulseAudio sound server";
+    } else if (state == PA_CONTEXT_TERMINATED) {
+      LOG(LS_ERROR) << "PulseAudio connection terminated early";
+    } else {
+      // Shouldn't happen, because we only signal on one of those three states.
+      LOG(LS_ERROR) << "Unknown problem connecting to PulseAudio";
+    }
+    ret = false;
+  }
+
+ done:
+  // We unset our callback for safety just in case the state might somehow
+  // change later, because the pointer to "data" will be invalid after return
+  // from this function.
+  symbol_table_.pa_context_set_state_callback()(context, NULL, NULL);
+  return ret;
+}
+
+// Must be called with the lock held.
+pa_context *PulseAudioSoundSystem::CreateNewConnection() {
+  // Create connection context.
+  std::string app_name;
+  // TODO: Pulse etiquette says this name should be localized. Do
+  // we care?
+  talk_base::Filesystem::GetApplicationName(&app_name);
+  pa_context *context = symbol_table_.pa_context_new()(
+      symbol_table_.pa_threaded_mainloop_get_api()(mainloop_),
+      app_name.c_str());
+  if (!context) {
+    LOG(LS_ERROR) << "Can't create context";
+    goto fail0;
+  }
+
+  // Now connect.
+  if (!ConnectToPulse(context)) {
+    goto fail1;
+  }
+
+  // Otherwise the connection succeeded and is ready.
+  return context;
+
+ fail1:
+  symbol_table_.pa_context_unref()(context);
+ fail0:
+  return NULL;
+}
+
+struct EnumerateDevicesCallbackData {
+  PulseAudioSoundSystem *instance;
+  SoundSystemInterface::SoundDeviceLocatorList *devices;
+};
+
+void PulseAudioSoundSystem::EnumeratePlaybackDevicesCallbackThunk(
+    pa_context *unused,
+    const pa_sink_info *info,
+    int eol,
+    void *userdata) {
+  EnumerateDevicesCallbackData *data =
+      static_cast<EnumerateDevicesCallbackData *>(userdata);
+  data->instance->OnEnumeratePlaybackDevicesCallback(data->devices, info, eol);
+}
+
+void PulseAudioSoundSystem::EnumerateCaptureDevicesCallbackThunk(
+    pa_context *unused,
+    const pa_source_info *info,
+    int eol,
+    void *userdata) {
+  EnumerateDevicesCallbackData *data =
+      static_cast<EnumerateDevicesCallbackData *>(userdata);
+  data->instance->OnEnumerateCaptureDevicesCallback(data->devices, info, eol);
+}
+
+void PulseAudioSoundSystem::OnEnumeratePlaybackDevicesCallback(
+    SoundDeviceLocatorList *devices,
+    const pa_sink_info *info,
+    int eol) {
+  if (eol) {
+    // List is over. Wake EnumerateDevices().
+    Signal();
+    return;
+  }
+
+  // Else this is the next device.
+  devices->push_back(
+      new PulseAudioDeviceLocator(info->description, info->name));
+}
+
+void PulseAudioSoundSystem::OnEnumerateCaptureDevicesCallback(
+    SoundDeviceLocatorList *devices,
+    const pa_source_info *info,
+    int eol) {
+  if (eol) {
+    // List is over. Wake EnumerateDevices().
+    Signal();
+    return;
+  }
+
+  if (info->monitor_of_sink != PA_INVALID_INDEX) {
+    // We don't want to list monitor sources, since they are almost certainly
+    // not what the user wants for voice conferencing.
+    return;
+  }
+
+  // Else this is the next device.
+  devices->push_back(
+      new PulseAudioDeviceLocator(info->description, info->name));
+}
+
+template <typename InfoStruct>
+bool PulseAudioSoundSystem::EnumerateDevices(
+    SoundDeviceLocatorList *devices,
+    pa_operation *(*enumerate_fn)(
+        pa_context *c,
+        void (*callback_fn)(
+            pa_context *c,
+            const InfoStruct *i,
+            int eol,
+            void *userdata),
+        void *userdata),
+    void (*callback_fn)(
+        pa_context *c,
+        const InfoStruct *i,
+        int eol,
+        void *userdata)) {
+  ClearSoundDeviceLocatorList(devices);
+  if (!IsInitialized()) {
+    return false;
+  }
+
+  EnumerateDevicesCallbackData data;
+  data.instance = this;
+  data.devices = devices;
+
+  Lock();
+  pa_operation *op = (*enumerate_fn)(
+      context_,
+      callback_fn,
+      &data);
+  bool ret = FinishOperation(op);
+  Unlock();
+  return ret;
+}
+
+struct GetDefaultDeviceCallbackData {
+  PulseAudioSoundSystem *instance;
+  SoundDeviceLocator **device;
+};
+
+template <const char *(pa_server_info::*field)>
+void PulseAudioSoundSystem::GetDefaultDeviceCallbackThunk(
+    pa_context *unused,
+    const pa_server_info *info,
+    void *userdata) {
+  GetDefaultDeviceCallbackData *data =
+      static_cast<GetDefaultDeviceCallbackData *>(userdata);
+  data->instance->OnGetDefaultDeviceCallback<field>(info, data->device);
+}
+
+template <const char *(pa_server_info::*field)>
+void PulseAudioSoundSystem::OnGetDefaultDeviceCallback(
+    const pa_server_info *info,
+    SoundDeviceLocator **device) {
+  if (info) {
+    const char *dev = info->*field;
+    if (dev) {
+      *device = new PulseAudioDeviceLocator("Default device", dev);
+    }
+  }
+  Signal();
+}
+
+template <const char *(pa_server_info::*field)>
+bool PulseAudioSoundSystem::GetDefaultDevice(SoundDeviceLocator **device) {
+  if (!IsInitialized()) {
+    return false;
+  }
+  bool ret;
+  *device = NULL;
+  GetDefaultDeviceCallbackData data;
+  data.instance = this;
+  data.device = device;
+  Lock();
+  pa_operation *op = symbol_table_.pa_context_get_server_info()(
+      context_,
+      &GetDefaultDeviceCallbackThunk<field>,
+      &data);
+  ret = FinishOperation(op);
+  Unlock();
+  return ret && (*device != NULL);
+}
+
+void PulseAudioSoundSystem::StreamStateChangedCallbackThunk(
+    pa_stream *stream,
+    void *userdata) {
+  PulseAudioSoundSystem *instance =
+      static_cast<PulseAudioSoundSystem *>(userdata);
+  instance->OnStreamStateChangedCallback(stream);
+}
+
+void PulseAudioSoundSystem::OnStreamStateChangedCallback(pa_stream *stream) {
+  pa_stream_state_t state = symbol_table_.pa_stream_get_state()(stream);
+  if (state == PA_STREAM_READY) {
+    LOG(LS_INFO) << "Pulse stream " << stream << " ready";
+  } else if (state == PA_STREAM_FAILED ||
+             state == PA_STREAM_TERMINATED ||
+             state == PA_STREAM_UNCONNECTED) {
+    LOG(LS_ERROR) << "Pulse stream " << stream << " failed to connect: "
+                  << LastError();
+  }
+}
+
+template <typename StreamInterface>
+StreamInterface *PulseAudioSoundSystem::OpenDevice(
+    const SoundDeviceLocator *device,
+    const OpenParams &params,
+    const char *stream_name,
+    StreamInterface *(PulseAudioSoundSystem::*connect_fn)(
+        pa_stream *stream,
+        const char *dev,
+        int flags,
+        pa_stream_flags_t pa_flags,
+        int latency,
+        const pa_sample_spec &spec)) {
+  if (!IsInitialized()) {
+    return NULL;
+  }
+
+  const char *dev = static_cast<const PulseAudioDeviceLocator *>(device)->
+      device_name().c_str();
+
+  StreamInterface *stream_interface = NULL;
+
+  ASSERT(params.format < ARRAY_SIZE(kCricketFormatToPulseFormatTable));
+
+  pa_sample_spec spec;
+  spec.format = kCricketFormatToPulseFormatTable[params.format];
+  spec.rate = params.freq;
+  spec.channels = params.channels;
+
+  int pa_flags = 0;
+  if (params.flags & FLAG_REPORT_LATENCY) {
+    pa_flags |= PA_STREAM_INTERPOLATE_TIMING |
+                PA_STREAM_AUTO_TIMING_UPDATE;
+  }
+
+  if (params.latency != kNoLatencyRequirements) {
+    // If configuring a specific latency then we want to specify
+    // PA_STREAM_ADJUST_LATENCY to make the server adjust parameters
+    // automatically to reach that target latency. However, that flag doesn't
+    // exist in Ubuntu 8.04 and many people still use that, so we have to check
+    // the protocol version of libpulse.
+    if (symbol_table_.pa_context_get_protocol_version()(context_) >=
+        kAdjustLatencyProtocolVersion) {
+      pa_flags |= PA_STREAM_ADJUST_LATENCY;
+    }
+  }
+
+  Lock();
+
+  pa_stream *stream = symbol_table_.pa_stream_new()(context_, stream_name,
+      &spec, NULL);
+  if (!stream) {
+    LOG(LS_ERROR) << "Can't create pa_stream";
+    goto done;
+  }
+
+  // Set a state callback to log errors.
+  symbol_table_.pa_stream_set_state_callback()(stream,
+                                               &StreamStateChangedCallbackThunk,
+                                               this);
+
+  stream_interface = (this->*connect_fn)(
+      stream,
+      dev,
+      params.flags,
+      static_cast<pa_stream_flags_t>(pa_flags),
+      params.latency,
+      spec);
+  if (!stream_interface) {
+    LOG(LS_ERROR) << "Can't connect stream to " << dev;
+    symbol_table_.pa_stream_unref()(stream);
+  }
+
+ done:
+  Unlock();
+  return stream_interface;
+}
+
+// Must be called with the lock held.
+SoundOutputStreamInterface *PulseAudioSoundSystem::ConnectOutputStream(
+    pa_stream *stream,
+    const char *dev,
+    int flags,
+    pa_stream_flags_t pa_flags,
+    int latency,
+    const pa_sample_spec &spec) {
+  pa_buffer_attr attr = {0};
+  pa_buffer_attr *pattr = NULL;
+  if (latency != kNoLatencyRequirements) {
+    // kLowLatency is 0, so we treat it the same as a request for zero latency.
+    ssize_t bytes_per_sec = symbol_table_.pa_bytes_per_second()(&spec);
+    latency = talk_base::_max(
+        latency,
+        static_cast<int>(
+            bytes_per_sec * kPlaybackLatencyMinimumMsecs /
+            talk_base::kNumMicrosecsPerSec));
+    FillPlaybackBufferAttr(latency, &attr);
+    pattr = &attr;
+  }
+  if (symbol_table_.pa_stream_connect_playback()(
+          stream,
+          dev,
+          pattr,
+          pa_flags,
+          // Let server choose volume
+          NULL,
+          // Not synchronized to any other playout
+          NULL) != 0) {
+    return NULL;
+  }
+  return new PulseAudioOutputStream(this, stream, flags, latency);
+}
+
+// Must be called with the lock held.
+SoundInputStreamInterface *PulseAudioSoundSystem::ConnectInputStream(
+    pa_stream *stream,
+    const char *dev,
+    int flags,
+    pa_stream_flags_t pa_flags,
+    int latency,
+    const pa_sample_spec &spec) {
+  pa_buffer_attr attr = {0};
+  pa_buffer_attr *pattr = NULL;
+  if (latency != kNoLatencyRequirements) {
+    size_t bytes_per_sec = symbol_table_.pa_bytes_per_second()(&spec);
+    if (latency == kLowLatency) {
+      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 /
+        talk_base::kNumMicrosecsPerSec;
+    LOG(LS_VERBOSE) << "Configuring latency = " << attr.fragsize
+                    << ", maxlength = " << attr.maxlength;
+    pattr = &attr;
+  }
+  if (symbol_table_.pa_stream_connect_record()(stream,
+                                               dev,
+                                               pattr,
+                                               pa_flags) != 0) {
+    return NULL;
+  }
+  return new PulseAudioInputStream(this, stream, flags);
+}
+
+// Must be called with the lock held.
+bool PulseAudioSoundSystem::FinishOperation(pa_operation *op) {
+  if (!op) {
+    LOG(LS_ERROR) << "Failed to start operation";
+    return false;
+  }
+
+  do {
+    Wait();
+  } while (symbol_table_.pa_operation_get_state()(op) == PA_OPERATION_RUNNING);
+
+  symbol_table_.pa_operation_unref()(op);
+
+  return true;
+}
+
+inline void PulseAudioSoundSystem::Lock() {
+  symbol_table_.pa_threaded_mainloop_lock()(mainloop_);
+}
+
+inline void PulseAudioSoundSystem::Unlock() {
+  symbol_table_.pa_threaded_mainloop_unlock()(mainloop_);
+}
+
+// Must be called with the lock held.
+inline void PulseAudioSoundSystem::Wait() {
+  symbol_table_.pa_threaded_mainloop_wait()(mainloop_);
+}
+
+// Must be called with the lock held.
+inline void PulseAudioSoundSystem::Signal() {
+  symbol_table_.pa_threaded_mainloop_signal()(mainloop_, 0);
+}
+
+// Must be called with the lock held.
+const char *PulseAudioSoundSystem::LastError() {
+  return symbol_table_.pa_strerror()(symbol_table_.pa_context_errno()(
+      context_));
+}
+
+}  // namespace cricket
+
+#endif  // HAVE_LIBPULSE
diff --git a/talk/sound/pulseaudiosoundsystem.h b/talk/sound/pulseaudiosoundsystem.h
new file mode 100644
index 0000000..8a9fe49
--- /dev/null
+++ b/talk/sound/pulseaudiosoundsystem.h
@@ -0,0 +1,194 @@
+/*
+ * 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_SOUND_PULSEAUDIOSOUNDSYSTEM_H_
+#define TALK_SOUND_PULSEAUDIOSOUNDSYSTEM_H_
+
+#ifdef HAVE_LIBPULSE
+
+#include "talk/base/constructormagic.h"
+#include "talk/sound/pulseaudiosymboltable.h"
+#include "talk/sound/soundsysteminterface.h"
+
+namespace cricket {
+
+class PulseAudioInputStream;
+class PulseAudioOutputStream;
+class PulseAudioStream;
+
+// Sound system implementation for PulseAudio, a cross-platform sound server
+// (but commonly used only on Linux, which is the only platform we support
+// it on).
+// Init(), Terminate(), and the destructor should never be invoked concurrently,
+// but all other methods are thread-safe.
+class PulseAudioSoundSystem : public SoundSystemInterface {
+  friend class PulseAudioInputStream;
+  friend class PulseAudioOutputStream;
+  friend class PulseAudioStream;
+ public:
+  static SoundSystemInterface *Create() {
+    return new PulseAudioSoundSystem();
+  }
+
+  PulseAudioSoundSystem();
+
+  virtual ~PulseAudioSoundSystem();
+
+  virtual bool Init();
+  virtual void Terminate();
+
+  virtual bool EnumeratePlaybackDevices(SoundDeviceLocatorList *devices);
+  virtual bool EnumerateCaptureDevices(SoundDeviceLocatorList *devices);
+
+  virtual bool GetDefaultPlaybackDevice(SoundDeviceLocator **device);
+  virtual bool GetDefaultCaptureDevice(SoundDeviceLocator **device);
+
+  virtual SoundOutputStreamInterface *OpenPlaybackDevice(
+      const SoundDeviceLocator *device,
+      const OpenParams &params);
+  virtual SoundInputStreamInterface *OpenCaptureDevice(
+      const SoundDeviceLocator *device,
+      const OpenParams &params);
+
+  virtual const char *GetName() const;
+
+ private:
+  bool IsInitialized();
+
+  static void ConnectToPulseCallbackThunk(pa_context *context, void *userdata);
+
+  void OnConnectToPulseCallback(pa_context *context, bool *connect_done);
+
+  bool ConnectToPulse(pa_context *context);
+
+  pa_context *CreateNewConnection();
+
+  template <typename InfoStruct>
+  bool EnumerateDevices(SoundDeviceLocatorList *devices,
+                        pa_operation *(*enumerate_fn)(
+                            pa_context *c,
+                            void (*callback_fn)(
+                                pa_context *c,
+                                const InfoStruct *i,
+                                int eol,
+                                void *userdata),
+                            void *userdata),
+                        void (*callback_fn)(
+                            pa_context *c,
+                            const InfoStruct *i,
+                            int eol,
+                            void *userdata));
+
+  static void EnumeratePlaybackDevicesCallbackThunk(pa_context *unused,
+                                                    const pa_sink_info *info,
+                                                    int eol,
+                                                    void *userdata);
+
+  static void EnumerateCaptureDevicesCallbackThunk(pa_context *unused,
+                                                   const pa_source_info *info,
+                                                   int eol,
+                                                   void *userdata);
+
+  void OnEnumeratePlaybackDevicesCallback(
+      SoundDeviceLocatorList *devices,
+      const pa_sink_info *info,
+      int eol);
+
+  void OnEnumerateCaptureDevicesCallback(
+      SoundDeviceLocatorList *devices,
+      const pa_source_info *info,
+      int eol);
+
+  template <const char *(pa_server_info::*field)>
+  static void GetDefaultDeviceCallbackThunk(
+      pa_context *unused,
+      const pa_server_info *info,
+      void *userdata);
+
+  template <const char *(pa_server_info::*field)>
+  void OnGetDefaultDeviceCallback(
+      const pa_server_info *info,
+      SoundDeviceLocator **device);
+
+  template <const char *(pa_server_info::*field)>
+  bool GetDefaultDevice(SoundDeviceLocator **device);
+
+  static void StreamStateChangedCallbackThunk(pa_stream *stream,
+                                              void *userdata);
+
+  void OnStreamStateChangedCallback(pa_stream *stream);
+
+  template <typename StreamInterface>
+  StreamInterface *OpenDevice(
+      const SoundDeviceLocator *device,
+      const OpenParams &params,
+      const char *stream_name,
+      StreamInterface *(PulseAudioSoundSystem::*connect_fn)(
+          pa_stream *stream,
+          const char *dev,
+          int flags,
+          pa_stream_flags_t pa_flags,
+          int latency,
+          const pa_sample_spec &spec));
+
+  SoundOutputStreamInterface *ConnectOutputStream(
+      pa_stream *stream,
+      const char *dev,
+      int flags,
+      pa_stream_flags_t pa_flags,
+      int latency,
+      const pa_sample_spec &spec);
+
+  SoundInputStreamInterface *ConnectInputStream(
+      pa_stream *stream,
+      const char *dev,
+      int flags,
+      pa_stream_flags_t pa_flags,
+      int latency,
+      const pa_sample_spec &spec);
+
+  bool FinishOperation(pa_operation *op);
+
+  void Lock();
+  void Unlock();
+  void Wait();
+  void Signal();
+
+  const char *LastError();
+
+  pa_threaded_mainloop *mainloop_;
+  pa_context *context_;
+  PulseAudioSymbolTable symbol_table_;
+
+  DISALLOW_COPY_AND_ASSIGN(PulseAudioSoundSystem);
+};
+
+}  // namespace cricket
+
+#endif  // HAVE_LIBPULSE
+
+#endif  // TALK_SOUND_PULSEAUDIOSOUNDSYSTEM_H_
diff --git a/talk/sound/pulseaudiosymboltable.cc b/talk/sound/pulseaudiosymboltable.cc
new file mode 100644
index 0000000..05213ec
--- /dev/null
+++ b/talk/sound/pulseaudiosymboltable.cc
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+#ifdef HAVE_LIBPULSE
+
+#include "talk/sound/pulseaudiosymboltable.h"
+
+namespace cricket {
+
+#define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME PULSE_AUDIO_SYMBOLS_CLASS_NAME
+#define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST PULSE_AUDIO_SYMBOLS_LIST
+#define LATE_BINDING_SYMBOL_TABLE_DLL_NAME "libpulse.so.0"
+#include "talk/base/latebindingsymboltable.cc.def"
+
+}  // namespace cricket
+
+#endif  // HAVE_LIBPULSE
diff --git a/talk/sound/pulseaudiosymboltable.h b/talk/sound/pulseaudiosymboltable.h
new file mode 100644
index 0000000..ef65157
--- /dev/null
+++ b/talk/sound/pulseaudiosymboltable.h
@@ -0,0 +1,104 @@
+/*
+ * 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_SOUND_PULSEAUDIOSYMBOLTABLE_H_
+#define TALK_SOUND_PULSEAUDIOSYMBOLTABLE_H_
+
+#include <pulse/context.h>
+#include <pulse/def.h>
+#include <pulse/error.h>
+#include <pulse/introspect.h>
+#include <pulse/stream.h>
+#include <pulse/thread-mainloop.h>
+
+#include "talk/base/latebindingsymboltable.h"
+
+namespace cricket {
+
+#define PULSE_AUDIO_SYMBOLS_CLASS_NAME PulseAudioSymbolTable
+// The PulseAudio symbols we need, as an X-Macro list.
+// This list must contain precisely every libpulse function that is used in
+// pulseaudiosoundsystem.cc.
+#define PULSE_AUDIO_SYMBOLS_LIST \
+  X(pa_bytes_per_second) \
+  X(pa_context_connect) \
+  X(pa_context_disconnect) \
+  X(pa_context_errno) \
+  X(pa_context_get_protocol_version) \
+  X(pa_context_get_server_info) \
+  X(pa_context_get_sink_info_list) \
+  X(pa_context_get_sink_input_info) \
+  X(pa_context_get_source_info_by_index) \
+  X(pa_context_get_source_info_list) \
+  X(pa_context_get_state) \
+  X(pa_context_new) \
+  X(pa_context_set_sink_input_volume) \
+  X(pa_context_set_source_volume_by_index) \
+  X(pa_context_set_state_callback) \
+  X(pa_context_unref) \
+  X(pa_cvolume_set) \
+  X(pa_operation_get_state) \
+  X(pa_operation_unref) \
+  X(pa_stream_connect_playback) \
+  X(pa_stream_connect_record) \
+  X(pa_stream_disconnect) \
+  X(pa_stream_drop) \
+  X(pa_stream_get_device_index) \
+  X(pa_stream_get_index) \
+  X(pa_stream_get_latency) \
+  X(pa_stream_get_sample_spec) \
+  X(pa_stream_get_state) \
+  X(pa_stream_new) \
+  X(pa_stream_peek) \
+  X(pa_stream_readable_size) \
+  X(pa_stream_set_buffer_attr) \
+  X(pa_stream_set_overflow_callback) \
+  X(pa_stream_set_read_callback) \
+  X(pa_stream_set_state_callback) \
+  X(pa_stream_set_underflow_callback) \
+  X(pa_stream_set_write_callback) \
+  X(pa_stream_unref) \
+  X(pa_stream_writable_size) \
+  X(pa_stream_write) \
+  X(pa_strerror) \
+  X(pa_threaded_mainloop_free) \
+  X(pa_threaded_mainloop_get_api) \
+  X(pa_threaded_mainloop_lock) \
+  X(pa_threaded_mainloop_new) \
+  X(pa_threaded_mainloop_signal) \
+  X(pa_threaded_mainloop_start) \
+  X(pa_threaded_mainloop_stop) \
+  X(pa_threaded_mainloop_unlock) \
+  X(pa_threaded_mainloop_wait)
+
+#define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME PULSE_AUDIO_SYMBOLS_CLASS_NAME
+#define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST PULSE_AUDIO_SYMBOLS_LIST
+#include "talk/base/latebindingsymboltable.h.def"
+
+}  // namespace cricket
+
+#endif  // TALK_SOUND_PULSEAUDIOSYMBOLTABLE_H_
diff --git a/talk/sound/sounddevicelocator.h b/talk/sound/sounddevicelocator.h
new file mode 100644
index 0000000..e0a8970
--- /dev/null
+++ b/talk/sound/sounddevicelocator.h
@@ -0,0 +1,71 @@
+/*
+ * 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_SOUND_SOUNDDEVICELOCATOR_H_
+#define TALK_SOUND_SOUNDDEVICELOCATOR_H_
+
+#include <string>
+
+#include "talk/base/constructormagic.h"
+
+namespace cricket {
+
+// A simple container for holding the name of a device and any additional id
+// information needed to locate and open it. Implementations of
+// SoundSystemInterface must subclass this to add any id information that they
+// need.
+class SoundDeviceLocator {
+ public:
+  virtual ~SoundDeviceLocator() {}
+
+  // Human-readable name for the device.
+  const std::string &name() const { return name_; }
+
+  // Name sound system uses to locate this device.
+  const std::string &device_name() const { return device_name_; }
+
+  // Makes a duplicate of this locator.
+  virtual SoundDeviceLocator *Copy() const = 0;
+
+ protected:
+  SoundDeviceLocator(const std::string &name,
+                     const std::string &device_name)
+      : name_(name), device_name_(device_name) {}
+
+  explicit SoundDeviceLocator(const SoundDeviceLocator &that)
+      : name_(that.name_), device_name_(that.device_name_) {}
+
+  std::string name_;
+  std::string device_name_;
+
+ private:
+  DISALLOW_ASSIGN(SoundDeviceLocator);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SOUND_SOUNDDEVICELOCATOR_H_
diff --git a/talk/sound/soundinputstreaminterface.h b/talk/sound/soundinputstreaminterface.h
new file mode 100644
index 0000000..de831a6
--- /dev/null
+++ b/talk/sound/soundinputstreaminterface.h
@@ -0,0 +1,85 @@
+/*
+ * 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_SOUND_SOUNDINPUTSTREAMINTERFACE_H_
+#define TALK_SOUND_SOUNDINPUTSTREAMINTERFACE_H_
+
+#include "talk/base/constructormagic.h"
+#include "talk/base/sigslot.h"
+
+namespace cricket {
+
+// Interface for consuming an input stream from a recording device.
+// Semantics and thread-safety of StartReading()/StopReading() are the same as
+// for talk_base::Worker.
+class SoundInputStreamInterface {
+ public:
+  virtual ~SoundInputStreamInterface() {}
+
+  // Starts the reading of samples on the current thread.
+  virtual bool StartReading() = 0;
+  // Stops the reading of samples.
+  virtual bool StopReading() = 0;
+
+  // Retrieves the current input volume for this stream. Nominal range is
+  // defined by SoundSystemInterface::k(Max|Min)Volume, but values exceeding the
+  // max may be possible in some implementations. This call retrieves the actual
+  // volume currently in use by the OS, not a cached value from a previous
+  // (Get|Set)Volume() call.
+  virtual bool GetVolume(int *volume) = 0;
+
+  // Changes the input volume for this stream. Nominal range is defined by
+  // SoundSystemInterface::k(Max|Min)Volume. The effect of exceeding kMaxVolume
+  // is implementation-defined.
+  virtual bool SetVolume(int volume) = 0;
+
+  // Closes this stream object. If currently reading then this may only be
+  // called from the reading thread.
+  virtual bool Close() = 0;
+
+  // Get the latency of the stream.
+  virtual int LatencyUsecs() = 0;
+
+  // Notifies the consumer of new data read from the device.
+  // The first parameter is a pointer to the data read, and is only valid for
+  // the duration of the call.
+  // The second parameter is the amount of data read in bytes (i.e., the valid
+  // length of the memory pointed to).
+  // The 3rd parameter is the stream that is issuing the callback.
+  sigslot::signal3<const void *, size_t,
+      SoundInputStreamInterface *> SignalSamplesRead;
+
+ protected:
+  SoundInputStreamInterface() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SoundInputStreamInterface);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SOUND_SOUNDOUTPUTSTREAMINTERFACE_H_
diff --git a/talk/sound/soundoutputstreaminterface.h b/talk/sound/soundoutputstreaminterface.h
new file mode 100644
index 0000000..d096ba3
--- /dev/null
+++ b/talk/sound/soundoutputstreaminterface.h
@@ -0,0 +1,89 @@
+/*
+ * 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_SOUND_SOUNDOUTPUTSTREAMINTERFACE_H_
+#define TALK_SOUND_SOUNDOUTPUTSTREAMINTERFACE_H_
+
+#include "talk/base/constructormagic.h"
+#include "talk/base/sigslot.h"
+
+namespace cricket {
+
+// Interface for outputting a stream to a playback device.
+// Semantics and thread-safety of EnableBufferMonitoring()/
+// DisableBufferMonitoring() are the same as for talk_base::Worker.
+class SoundOutputStreamInterface {
+ public:
+  virtual ~SoundOutputStreamInterface() {}
+
+  // Enables monitoring the available buffer space on the current thread.
+  virtual bool EnableBufferMonitoring() = 0;
+  // Disables the monitoring.
+  virtual bool DisableBufferMonitoring() = 0;
+
+  // Write the given samples to the devices. If currently monitoring then this
+  // may only be called from the monitoring thread.
+  virtual bool WriteSamples(const void *sample_data,
+                            size_t size) = 0;
+
+  // Retrieves the current output volume for this stream. Nominal range is
+  // defined by SoundSystemInterface::k(Max|Min)Volume, but values exceeding the
+  // max may be possible in some implementations. This call retrieves the actual
+  // volume currently in use by the OS, not a cached value from a previous
+  // (Get|Set)Volume() call.
+  virtual bool GetVolume(int *volume) = 0;
+
+  // Changes the output volume for this stream. Nominal range is defined by
+  // SoundSystemInterface::k(Max|Min)Volume. The effect of exceeding kMaxVolume
+  // is implementation-defined.
+  virtual bool SetVolume(int volume) = 0;
+
+  // Closes this stream object. If currently monitoring then this may only be
+  // called from the monitoring thread.
+  virtual bool Close() = 0;
+
+  // Get the latency of the stream.
+  virtual int LatencyUsecs() = 0;
+
+  // Notifies the producer of the available buffer space for writes.
+  // It fires continuously as long as the space is greater than zero.
+  // The first parameter is the amount of buffer space available for data to
+  // be written (i.e., the maximum amount of data that can be written right now
+  // with WriteSamples() without blocking).
+  // The 2nd parameter is the stream that is issuing the callback.
+  sigslot::signal2<size_t, SoundOutputStreamInterface *> SignalBufferSpace;
+
+ protected:
+  SoundOutputStreamInterface() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SoundOutputStreamInterface);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SOUND_SOUNDOUTPUTSTREAMINTERFACE_H_
diff --git a/talk/sound/soundsystemfactory.h b/talk/sound/soundsystemfactory.h
new file mode 100644
index 0000000..517220b
--- /dev/null
+++ b/talk/sound/soundsystemfactory.h
@@ -0,0 +1,44 @@
+/*
+ * 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_SOUND_SOUNDSYSTEMFACTORY_H_
+#define TALK_SOUND_SOUNDSYSTEMFACTORY_H_
+
+#include "talk/base/referencecountedsingletonfactory.h"
+
+namespace cricket {
+
+class SoundSystemInterface;
+
+typedef talk_base::ReferenceCountedSingletonFactory<SoundSystemInterface>
+    SoundSystemFactory;
+
+typedef talk_base::rcsf_ptr<SoundSystemInterface> SoundSystemHandle;
+
+}  // namespace cricket
+
+#endif  // TALK_SOUND_SOUNDSYSTEMFACTORY_H_
diff --git a/talk/sound/soundsysteminterface.cc b/talk/sound/soundsysteminterface.cc
new file mode 100644
index 0000000..b432262
--- /dev/null
+++ b/talk/sound/soundsysteminterface.cc
@@ -0,0 +1,46 @@
+/*
+ * 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/sound/soundsysteminterface.h"
+
+#include "talk/sound/sounddevicelocator.h"
+
+namespace cricket {
+
+void SoundSystemInterface::ClearSoundDeviceLocatorList(
+    SoundSystemInterface::SoundDeviceLocatorList *devices) {
+  for (SoundDeviceLocatorList::iterator i = devices->begin();
+       i != devices->end();
+       ++i) {
+    if (*i) {
+      delete *i;
+    }
+  }
+  devices->clear();
+}
+
+}  // namespace cricket
diff --git a/talk/sound/soundsysteminterface.h b/talk/sound/soundsysteminterface.h
new file mode 100644
index 0000000..7a059b0
--- /dev/null
+++ b/talk/sound/soundsysteminterface.h
@@ -0,0 +1,129 @@
+/*
+ * 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_SOUND_SOUNDSYSTEMINTERFACE_H_
+#define TALK_SOUND_SOUNDSYSTEMINTERFACE_H_
+
+#include <vector>
+
+#include "talk/base/constructormagic.h"
+
+namespace cricket {
+
+class SoundDeviceLocator;
+class SoundInputStreamInterface;
+class SoundOutputStreamInterface;
+
+// Interface for a platform's sound system.
+// Implementations must guarantee thread-safety for at least the following use
+// cases:
+// 1) Concurrent enumeration and opening of devices from different threads.
+// 2) Concurrent use of different Sound(Input|Output)StreamInterface
+// instances from different threads (but concurrent use of the _same_ one from
+// different threads need not be supported).
+class SoundSystemInterface {
+ public:
+  typedef std::vector<SoundDeviceLocator *> SoundDeviceLocatorList;
+
+  enum SampleFormat {
+    // Only one supported sample format at this time.
+    // The values here may be used in lookup tables, so they shouldn't change.
+    FORMAT_S16LE = 0,
+  };
+
+  enum Flags {
+    // Enable reporting the current stream latency in
+    // Sound(Input|Output)StreamInterface. See those classes for more details.
+    FLAG_REPORT_LATENCY = (1 << 0),
+  };
+
+  struct OpenParams {
+    // Format for the sound stream.
+    SampleFormat format;
+    // Sampling frequency in hertz.
+    unsigned int freq;
+    // Number of channels in the PCM stream.
+    unsigned int channels;
+    // Misc flags. Should be taken from the Flags enum above.
+    int flags;
+    // Desired latency, measured as number of bytes of sample data
+    int latency;
+  };
+
+  // Special values for the "latency" field of OpenParams.
+  // Use this one to say you don't care what the latency is. The sound system
+  // will optimize for other things instead.
+  static const int kNoLatencyRequirements = -1;
+  // Use this one to say that you want the sound system to pick an appropriate
+  // small latency value. The sound system may pick the minimum allowed one, or
+  // a slightly higher one in the event that the true minimum requires an
+  // undesirable trade-off.
+  static const int kLowLatency = 0;
+ 
+  // Max value for the volume parameters for Sound(Input|Output)StreamInterface.
+  static const int kMaxVolume = 255;
+  // Min value for the volume parameters for Sound(Input|Output)StreamInterface.
+  static const int kMinVolume = 0;
+
+  // Helper for clearing a locator list and deleting the entries.
+  static void ClearSoundDeviceLocatorList(SoundDeviceLocatorList *devices);
+
+  virtual ~SoundSystemInterface() {}
+
+  virtual bool Init() = 0;
+  virtual void Terminate() = 0;
+
+  // Enumerates the available devices. (Any pre-existing locators in the lists
+  // are deleted.)
+  virtual bool EnumeratePlaybackDevices(SoundDeviceLocatorList *devices) = 0;
+  virtual bool EnumerateCaptureDevices(SoundDeviceLocatorList *devices) = 0;
+
+  // Gets a special locator for the default device.
+  virtual bool GetDefaultPlaybackDevice(SoundDeviceLocator **device) = 0;
+  virtual bool GetDefaultCaptureDevice(SoundDeviceLocator **device) = 0;
+
+  // Opens the given device, or returns NULL on error.
+  virtual SoundOutputStreamInterface *OpenPlaybackDevice(
+      const SoundDeviceLocator *device,
+      const OpenParams &params) = 0;
+  virtual SoundInputStreamInterface *OpenCaptureDevice(
+      const SoundDeviceLocator *device,
+      const OpenParams &params) = 0;
+
+  // A human-readable name for this sound system.
+  virtual const char *GetName() const = 0;
+
+ protected:
+  SoundSystemInterface() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SoundSystemInterface);
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SOUND_SOUNDSYSTEMINTERFACE_H_
diff --git a/talk/sound/soundsystemproxy.cc b/talk/sound/soundsystemproxy.cc
new file mode 100644
index 0000000..737a6bb
--- /dev/null
+++ b/talk/sound/soundsystemproxy.cc
@@ -0,0 +1,64 @@
+/*
+ * 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/sound/soundsystemproxy.h"
+
+namespace cricket {
+
+bool SoundSystemProxy::EnumeratePlaybackDevices(
+    SoundDeviceLocatorList *devices) {
+  return wrapped_ ? wrapped_->EnumeratePlaybackDevices(devices) : false;
+}
+
+bool SoundSystemProxy::EnumerateCaptureDevices(
+    SoundDeviceLocatorList *devices) {
+  return wrapped_ ? wrapped_->EnumerateCaptureDevices(devices) : false;
+}
+
+bool SoundSystemProxy::GetDefaultPlaybackDevice(
+    SoundDeviceLocator **device) {
+  return wrapped_ ? wrapped_->GetDefaultPlaybackDevice(device) : false;
+}
+
+bool SoundSystemProxy::GetDefaultCaptureDevice(
+    SoundDeviceLocator **device) {
+  return wrapped_ ? wrapped_->GetDefaultCaptureDevice(device) : false;
+}
+
+SoundOutputStreamInterface *SoundSystemProxy::OpenPlaybackDevice(
+    const SoundDeviceLocator *device,
+    const OpenParams &params) {
+  return wrapped_ ? wrapped_->OpenPlaybackDevice(device, params) : NULL;
+}
+
+SoundInputStreamInterface *SoundSystemProxy::OpenCaptureDevice(
+    const SoundDeviceLocator *device,
+    const OpenParams &params) {
+  return wrapped_ ? wrapped_->OpenCaptureDevice(device, params) : NULL;
+}
+
+}  // namespace cricket
diff --git a/talk/sound/soundsystemproxy.h b/talk/sound/soundsystemproxy.h
new file mode 100644
index 0000000..9ccace8
--- /dev/null
+++ b/talk/sound/soundsystemproxy.h
@@ -0,0 +1,64 @@
+/*
+ * 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_SOUND_SOUNDSYSTEMPROXY_H_
+#define TALK_SOUND_SOUNDSYSTEMPROXY_H_
+
+#include "talk/base/basictypes.h"  // for NULL
+#include "talk/sound/soundsysteminterface.h"
+
+namespace cricket {
+
+// A SoundSystemProxy is a sound system that defers to another one.
+// Init(), Terminate(), and GetName() are left as pure virtual, so a sub-class
+// must define them.
+class SoundSystemProxy : public SoundSystemInterface {
+ public:
+  SoundSystemProxy() : wrapped_(NULL) {}
+
+  // Each of these methods simply defers to wrapped_ if non-NULL, else fails.
+
+  virtual bool EnumeratePlaybackDevices(SoundDeviceLocatorList *devices);
+  virtual bool EnumerateCaptureDevices(SoundDeviceLocatorList *devices);
+
+  virtual bool GetDefaultPlaybackDevice(SoundDeviceLocator **device);
+  virtual bool GetDefaultCaptureDevice(SoundDeviceLocator **device);
+
+  virtual SoundOutputStreamInterface *OpenPlaybackDevice(
+      const SoundDeviceLocator *device,
+      const OpenParams &params);
+  virtual SoundInputStreamInterface *OpenCaptureDevice(
+      const SoundDeviceLocator *device,
+      const OpenParams &params);
+
+ protected:
+  SoundSystemInterface *wrapped_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_SOUND_SOUNDSYSTEMPROXY_H_
diff --git a/talk/third_party/libudev/libudev.h b/talk/third_party/libudev/libudev.h
new file mode 100644
index 0000000..5bc42df
--- /dev/null
+++ b/talk/third_party/libudev/libudev.h
@@ -0,0 +1,175 @@
+/*
+ * libudev - interface to udev device information
+ *
+ * Copyright (C) 2008-2010 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ */
+
+#ifndef _LIBUDEV_H_
+#define _LIBUDEV_H_
+
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * udev - library context
+ * 
+ * reads the udev config and system environment
+ * allows custom logging
+ */
+struct udev;
+struct udev *udev_ref(struct udev *udev);
+void udev_unref(struct udev *udev);
+struct udev *udev_new(void);
+void udev_set_log_fn(struct udev *udev,
+			    void (*log_fn)(struct udev *udev,
+					   int priority, const char *file, int line, const char *fn,
+					   const char *format, va_list args));
+int udev_get_log_priority(struct udev *udev);
+void udev_set_log_priority(struct udev *udev, int priority);
+const char *udev_get_sys_path(struct udev *udev);
+const char *udev_get_dev_path(struct udev *udev);
+void *udev_get_userdata(struct udev *udev);
+void udev_set_userdata(struct udev *udev, void *userdata);
+
+/*
+ * udev_list
+ *
+ * access to libudev generated lists
+ */
+struct udev_list_entry;
+struct udev_list_entry *udev_list_entry_get_next(struct udev_list_entry *list_entry);
+struct udev_list_entry *udev_list_entry_get_by_name(struct udev_list_entry *list_entry, const char *name);
+const char *udev_list_entry_get_name(struct udev_list_entry *list_entry);
+const char *udev_list_entry_get_value(struct udev_list_entry *list_entry);
+/**
+ * udev_list_entry_foreach:
+ * @list_entry: entry to store the current position
+ * @first_entry: first entry to start with
+ *
+ * Helper to iterate over all entries of a list.
+ */
+#define udev_list_entry_foreach(list_entry, first_entry) \
+	for (list_entry = first_entry; \
+	     list_entry != NULL; \
+	     list_entry = udev_list_entry_get_next(list_entry))
+
+/*
+ * udev_device
+ *
+ * access to sysfs/kernel devices
+ */
+struct udev_device;
+struct udev_device *udev_device_ref(struct udev_device *udev_device);
+void udev_device_unref(struct udev_device *udev_device);
+struct udev *udev_device_get_udev(struct udev_device *udev_device);
+struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char *syspath);
+struct udev_device *udev_device_new_from_devnum(struct udev *udev, char type, dev_t devnum);
+struct udev_device *udev_device_new_from_subsystem_sysname(struct udev *udev, const char *subsystem, const char *sysname);
+struct udev_device *udev_device_new_from_environment(struct udev *udev);
+/* udev_device_get_parent_*() does not take a reference on the returned device, it is automatically unref'd with the parent */
+struct udev_device *udev_device_get_parent(struct udev_device *udev_device);
+struct udev_device *udev_device_get_parent_with_subsystem_devtype(struct udev_device *udev_device,
+								  const char *subsystem, const char *devtype);
+/* retrieve device properties */
+const char *udev_device_get_devpath(struct udev_device *udev_device);
+const char *udev_device_get_subsystem(struct udev_device *udev_device);
+const char *udev_device_get_devtype(struct udev_device *udev_device);
+const char *udev_device_get_syspath(struct udev_device *udev_device);
+const char *udev_device_get_sysname(struct udev_device *udev_device);
+const char *udev_device_get_sysnum(struct udev_device *udev_device);
+const char *udev_device_get_devnode(struct udev_device *udev_device);
+struct udev_list_entry *udev_device_get_devlinks_list_entry(struct udev_device *udev_device);
+struct udev_list_entry *udev_device_get_properties_list_entry(struct udev_device *udev_device);
+struct udev_list_entry *udev_device_get_tags_list_entry(struct udev_device *udev_device);
+const char *udev_device_get_property_value(struct udev_device *udev_device, const char *key);
+const char *udev_device_get_driver(struct udev_device *udev_device);
+dev_t udev_device_get_devnum(struct udev_device *udev_device);
+const char *udev_device_get_action(struct udev_device *udev_device);
+unsigned long long int udev_device_get_seqnum(struct udev_device *udev_device);
+const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const char *sysattr);
+
+/*
+ * udev_monitor
+ *
+ * access to kernel uevents and udev events
+ */
+struct udev_monitor;
+struct udev_monitor *udev_monitor_ref(struct udev_monitor *udev_monitor);
+void udev_monitor_unref(struct udev_monitor *udev_monitor);
+struct udev *udev_monitor_get_udev(struct udev_monitor *udev_monitor);
+/* kernel and udev generated events over netlink */
+struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name);
+/* custom socket (use netlink and filters instead) */
+struct udev_monitor *udev_monitor_new_from_socket(struct udev *udev, const char *socket_path);
+/* bind socket */
+int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor);
+int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size);
+int udev_monitor_get_fd(struct udev_monitor *udev_monitor);
+struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor);
+/* in-kernel socket filters to select messages that get delivered to a listener */
+int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor,
+						    const char *subsystem, const char *devtype);
+int udev_monitor_filter_add_match_tag(struct udev_monitor *udev_monitor, const char *tag);
+int udev_monitor_filter_update(struct udev_monitor *udev_monitor);
+int udev_monitor_filter_remove(struct udev_monitor *udev_monitor);
+
+/*
+ * udev_enumerate
+ *
+ * search sysfs for specific devices and provide a sorted list
+ */
+struct udev_enumerate;
+struct udev_enumerate *udev_enumerate_ref(struct udev_enumerate *udev_enumerate);
+void udev_enumerate_unref(struct udev_enumerate *udev_enumerate);
+struct udev *udev_enumerate_get_udev(struct udev_enumerate *udev_enumerate);
+struct udev_enumerate *udev_enumerate_new(struct udev *udev);
+/* device properties filter */
+int udev_enumerate_add_match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem);
+int udev_enumerate_add_nomatch_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem);
+int udev_enumerate_add_match_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value);
+int udev_enumerate_add_nomatch_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value);
+int udev_enumerate_add_match_property(struct udev_enumerate *udev_enumerate, const char *property, const char *value);
+int udev_enumerate_add_match_sysname(struct udev_enumerate *udev_enumerate, const char *sysname);
+int udev_enumerate_add_match_tag(struct udev_enumerate *udev_enumerate, const char *tag);
+int udev_enumerate_add_syspath(struct udev_enumerate *udev_enumerate, const char *syspath);
+/* run enumeration with active filters */
+int udev_enumerate_scan_devices(struct udev_enumerate *udev_enumerate);
+int udev_enumerate_scan_subsystems(struct udev_enumerate *udev_enumerate);
+/* return device list */
+struct udev_list_entry *udev_enumerate_get_list_entry(struct udev_enumerate *udev_enumerate);
+
+/*
+ * udev_queue
+ *
+ * access to the currently running udev events
+ */
+struct udev_queue;
+struct udev_queue *udev_queue_ref(struct udev_queue *udev_queue);
+void udev_queue_unref(struct udev_queue *udev_queue);
+struct udev *udev_queue_get_udev(struct udev_queue *udev_queue);
+struct udev_queue *udev_queue_new(struct udev *udev);
+unsigned long long int udev_queue_get_kernel_seqnum(struct udev_queue *udev_queue);
+unsigned long long int udev_queue_get_udev_seqnum(struct udev_queue *udev_queue);
+int udev_queue_get_udev_is_active(struct udev_queue *udev_queue);
+int udev_queue_get_queue_is_empty(struct udev_queue *udev_queue);
+int udev_queue_get_seqnum_is_finished(struct udev_queue *udev_queue, unsigned long long int seqnum);
+int udev_queue_get_seqnum_sequence_is_finished(struct udev_queue *udev_queue,
+					       unsigned long long int start, unsigned long long int end);
+struct udev_list_entry *udev_queue_get_queued_list_entry(struct udev_queue *udev_queue);
+struct udev_list_entry *udev_queue_get_failed_list_entry(struct udev_queue *udev_queue);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif
diff --git a/talk/xmllite/qname.cc b/talk/xmllite/qname.cc
new file mode 100644
index 0000000..0dadb79
--- /dev/null
+++ b/talk/xmllite/qname.cc
@@ -0,0 +1,95 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/qname.h"
+
+namespace buzz {
+
+QName::QName() {
+}
+
+QName::QName(const QName& qname)
+    : namespace_(qname.namespace_),
+      local_part_(qname.local_part_) {
+}
+
+QName::QName(const StaticQName& const_value)
+    : namespace_(const_value.ns),
+      local_part_(const_value.local) {
+}
+
+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() {
+}
+
+std::string QName::Merged() const {
+  if (namespace_[0] == '\0')
+    return local_part_;
+
+  std::string result;
+  result.reserve(namespace_.length() + 1 + local_part_.length());
+  result += namespace_;
+  result += ':';
+  result += local_part_;
+  return result;
+}
+
+bool QName::IsEmpty() const {
+  return namespace_.empty() && local_part_.empty();
+}
+
+int QName::Compare(const StaticQName& other) const {
+  int result = local_part_.compare(other.local);
+  if (result != 0)
+    return result;
+
+  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
new file mode 100644
index 0000000..92e54d0
--- /dev/null
+++ b/talk/xmllite/qname.h
@@ -0,0 +1,100 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_XMLLITE_QNAME_H_
+#define TALK_XMLLITE_QNAME_H_
+
+#include <string>
+
+namespace buzz {
+
+class 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;
+
+  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;
+}
+
+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
new file mode 100644
index 0000000..486b6d5
--- /dev/null
+++ b/talk/xmllite/xmlbuilder.cc
@@ -0,0 +1,147 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/xmllite/xmlbuilder.h"
+
+#include <vector>
+#include <set>
+#include "talk/base/common.h"
+#include "talk/xmllite/xmlconstants.h"
+#include "talk/xmllite/xmlelement.h"
+
+namespace buzz {
+
+XmlBuilder::XmlBuilder() :
+  pelCurrent_(NULL),
+  pelRoot_(NULL),
+  pvParents_(new std::vector<XmlElement *>()) {
+}
+
+void
+XmlBuilder::Reset() {
+  pelRoot_.reset();
+  pelCurrent_ = NULL;
+  pvParents_->clear();
+}
+
+XmlElement *
+XmlBuilder::BuildElement(XmlParseContext * pctx,
+                              const char * name, const char ** atts) {
+  QName tagName(pctx->ResolveQName(name, false));
+  if (tagName.IsEmpty())
+    return NULL;
+
+  XmlElement * pelNew = new XmlElement(tagName);
+
+  if (!*atts)
+    return pelNew;
+
+  std::set<QName> seenNonlocalAtts;
+
+  while (*atts) {
+    QName attName(pctx->ResolveQName(*atts, true));
+    if (attName.IsEmpty()) {
+      delete pelNew;
+      return NULL;
+    }
+
+    // verify that namespaced names are unique
+    if (!attName.Namespace().empty()) {
+      if (seenNonlocalAtts.count(attName)) {
+        delete pelNew;
+        return NULL;
+      }
+      seenNonlocalAtts.insert(attName);
+    }
+
+    pelNew->AddAttr(attName, std::string(*(atts + 1)));
+    atts += 2;
+  }
+
+  return pelNew;
+}
+
+void
+XmlBuilder::StartElement(XmlParseContext * pctx,
+                              const char * name, const char ** atts) {
+  XmlElement * pelNew = BuildElement(pctx, name, atts);
+  if (pelNew == NULL) {
+    pctx->RaiseError(XML_ERROR_SYNTAX);
+    return;
+  }
+
+  if (!pelCurrent_) {
+    pelCurrent_ = pelNew;
+    pelRoot_.reset(pelNew);
+    pvParents_->push_back(NULL);
+  } else {
+    pelCurrent_->AddElement(pelNew);
+    pvParents_->push_back(pelCurrent_);
+    pelCurrent_ = pelNew;
+  }
+}
+
+void
+XmlBuilder::EndElement(XmlParseContext * pctx, const char * name) {
+  UNUSED(pctx);
+  UNUSED(name);
+  pelCurrent_ = pvParents_->back();
+  pvParents_->pop_back();
+}
+
+void
+XmlBuilder::CharacterData(XmlParseContext * pctx,
+                               const char * text, int len) {
+  UNUSED(pctx);
+  if (pelCurrent_) {
+    pelCurrent_->AddParsedText(text, len);
+  }
+}
+
+void
+XmlBuilder::Error(XmlParseContext * pctx, XML_Error err) {
+  UNUSED(pctx);
+  UNUSED(err);
+  pelRoot_.reset(NULL);
+  pelCurrent_ = NULL;
+  pvParents_->clear();
+}
+
+XmlElement *
+XmlBuilder::CreateElement() {
+  return pelRoot_.release();
+}
+
+XmlElement *
+XmlBuilder::BuiltElement() {
+  return pelRoot_.get();
+}
+
+XmlBuilder::~XmlBuilder() {
+}
+
+}  // namespace buzz
diff --git a/talk/xmllite/xmlbuilder.h b/talk/xmllite/xmlbuilder.h
new file mode 100644
index 0000000..984eee2
--- /dev/null
+++ b/talk/xmllite/xmlbuilder.h
@@ -0,0 +1,78 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _xmlbuilder_h_
+#define _xmlbuilder_h_
+
+#include <string>
+#include <vector>
+#include "talk/base/scoped_ptr.h"
+#include "talk/xmllite/xmlparser.h"
+
+#ifdef EXPAT_RELATIVE_PATH
+#include "expat.h"
+#else
+#include "third_party/expat/v2_0_1/Source/lib/expat.h"
+#endif  // EXPAT_RELATIVE_PATH
+
+namespace buzz {
+
+class XmlElement;
+class XmlParseContext;
+
+
+class XmlBuilder : public XmlParseHandler {
+public:
+  XmlBuilder();
+
+  static XmlElement * BuildElement(XmlParseContext * pctx,
+                                  const char * name, const char ** atts);
+  virtual void StartElement(XmlParseContext * pctx,
+                            const char * name, const char ** atts);
+  virtual void EndElement(XmlParseContext * pctx, const char * name);
+  virtual void CharacterData(XmlParseContext * pctx,
+                             const char * text, int len);
+  virtual void Error(XmlParseContext * pctx, XML_Error);
+  virtual ~XmlBuilder();
+
+  void Reset();
+
+  // Take ownership of the built element; second call returns NULL
+  XmlElement * CreateElement();
+
+  // Peek at the built element without taking ownership
+  XmlElement * BuiltElement();
+
+private:
+  XmlElement * pelCurrent_;
+  talk_base::scoped_ptr<XmlElement> pelRoot_;
+  talk_base::scoped_ptr<std::vector<XmlElement*> > pvParents_;
+};
+
+}
+
+#endif
diff --git a/talk/xmllite/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
new file mode 100644
index 0000000..f94d779
--- /dev/null
+++ b/talk/xmllite/xmlconstants.cc
@@ -0,0 +1,42 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/xmlconstants.h"
+
+namespace buzz {
+
+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 StaticQName QN_XMLNS = { STR_EMPTY, STR_XMLNS };
+
+}  // namespace buzz
diff --git a/talk/xmllite/xmlconstants.h b/talk/xmllite/xmlconstants.h
new file mode 100644
index 0000000..3e5da98
--- /dev/null
+++ b/talk/xmllite/xmlconstants.h
@@ -0,0 +1,47 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_XMLLITE_XMLCONSTANTS_H_
+#define TALK_XMLLITE_XMLCONSTANTS_H_
+
+#include "talk/xmllite/qname.h"
+
+namespace buzz {
+
+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[];
+
+extern const StaticQName QN_XMLNS;
+
+}  // namespace buzz
+
+#endif  // TALK_XMLLITE_XMLCONSTANTS_H_
diff --git a/talk/xmllite/xmlelement.cc b/talk/xmllite/xmlelement.cc
new file mode 100644
index 0000000..176ce5c
--- /dev/null
+++ b/talk/xmllite/xmlelement.cc
@@ -0,0 +1,513 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/xmllite/xmlelement.h"
+
+#include <ostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "talk/base/common.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlparser.h"
+#include "talk/xmllite/xmlbuilder.h"
+#include "talk/xmllite/xmlprinter.h"
+#include "talk/xmllite/xmlconstants.h"
+
+namespace buzz {
+
+XmlChild::~XmlChild() {
+}
+
+bool XmlText::IsTextImpl() const {
+  return true;
+}
+
+XmlElement* XmlText::AsElementImpl() const {
+  return NULL;
+}
+
+XmlText* XmlText::AsTextImpl() const {
+  return const_cast<XmlText *>(this);
+}
+
+void XmlText::SetText(const std::string& text) {
+  text_ = text;
+}
+
+void XmlText::AddParsedText(const char* buf, int len) {
+  text_.append(buf, len);
+}
+
+void XmlText::AddText(const std::string& text) {
+  text_ += text;
+}
+
+XmlText::~XmlText() {
+}
+
+XmlElement::XmlElement(const QName& name) :
+    name_(name),
+    first_attr_(NULL),
+    last_attr_(NULL),
+    first_child_(NULL),
+    last_child_(NULL),
+    cdata_(false) {
+}
+
+XmlElement::XmlElement(const XmlElement& elt) :
+    XmlChild(),
+    name_(elt.name_),
+    first_attr_(NULL),
+    last_attr_(NULL),
+    first_child_(NULL),
+    last_child_(NULL),
+    cdata_(false) {
+
+  // copy attributes
+  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_);
+  }
+  last_attr_ = newAttr;
+
+  // copy children
+  XmlChild* pChild;
+  XmlChild ** ppLast = &first_child_;
+  XmlChild* newChild = NULL;
+
+  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->next_child_);
+  }
+  last_child_ = newChild;
+
+  cdata_ = elt.cdata_;
+}
+
+XmlElement::XmlElement(const QName& name, bool useDefaultNs) :
+  name_(name),
+  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 {
+  return false;
+}
+
+XmlElement* XmlElement::AsElementImpl() const {
+  return const_cast<XmlElement *>(this);
+}
+
+XmlText* XmlElement::AsTextImpl() const {
+  return NULL;
+}
+
+const std::string XmlElement::BodyText() const {
+  if (first_child_ && first_child_->IsText() && last_child_ == first_child_) {
+    return first_child_->AsText()->Text();
+  }
+
+  return std::string();
+}
+
+void XmlElement::SetBodyText(const std::string& text) {
+  if (text.empty()) {
+    ClearChildren();
+  } else if (first_child_ == NULL) {
+    AddText(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();
+  if (element == NULL)
+    return QName();
+  return element->Name();
+}
+
+XmlAttr* XmlElement::FirstAttr() {
+  return first_attr_;
+}
+
+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 std::string();
+}
+
+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;
+}
+
+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 (!attr) {
+    attr = new XmlAttr(name, value);
+    if (last_attr_)
+      last_attr_->next_attr_ = attr;
+    else
+      first_attr_ = attr;
+    last_attr_ = attr;
+    return;
+  }
+  attr->value_ = value;
+}
+
+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;
+    last_attr = attr;
+  }
+  if (!attr)
+    return;
+  if (!last_attr)
+    first_attr_ = attr->next_attr_;
+  else
+    last_attr->next_attr_ = attr->next_attr_;
+  if (last_attr_ == attr)
+    last_attr_ = last_attr;
+  delete attr;
+}
+
+XmlChild* XmlElement::FirstChild() {
+  return first_child_;
+}
+
+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 = 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 = first_child_; pChild; pChild = pChild->next_child_) {
+    if (!pChild->IsText() && pChild->AsElement()->Name().Namespace() == ns)
+      return pChild->AsElement();
+  }
+  return NULL;
+}
+
+XmlElement *
+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();
+  }
+  return NULL;
+}
+
+XmlElement *
+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();
+  }
+  return NULL;
+}
+
+XmlElement *
+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();
+  }
+  return NULL;
+}
+
+XmlElement* XmlElement::FindOrAddNamedChild(const QName& name) {
+  XmlElement* child = FirstNamed(name);
+  if (!child) {
+    child = new XmlElement(name);
+    AddElement(child);
+  }
+
+  return child;
+}
+
+const std::string XmlElement::TextNamed(const QName& name) const {
+  XmlChild* pChild;
+  for (pChild = first_child_; pChild; pChild = pChild->next_child_) {
+    if (!pChild->IsText() && pChild->AsElement()->Name() == name)
+      return pChild->AsElement()->BodyText();
+  }
+  return std::string();
+}
+
+void XmlElement::InsertChildAfter(XmlChild* predecessor, XmlChild* next) {
+  if (predecessor == NULL) {
+    next->next_child_ = first_child_;
+    first_child_ = next;
+  }
+  else {
+    next->next_child_ = predecessor->next_child_;
+    predecessor->next_child_ = next;
+  }
+}
+
+void XmlElement::RemoveChildAfter(XmlChild* predecessor) {
+  XmlChild* next;
+
+  if (predecessor == NULL) {
+    next = first_child_;
+    first_child_ = next->next_child_;
+  }
+  else {
+    next = predecessor->next_child_;
+    predecessor->next_child_ = next->next_child_;
+  }
+
+  if (last_child_ == next)
+    last_child_ = predecessor;
+
+  delete next;
+}
+
+void XmlElement::AddAttr(const QName& name, const std::string& value) {
+  ASSERT(!HasAttr(name));
+
+  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,
+                         int depth) {
+  XmlElement* element = this;
+  while (depth--) {
+    element = element->last_child_->AsElement();
+  }
+  element->AddAttr(name, value);
+}
+
+void XmlElement::AddParsedText(const char* cstr, int len) {
+  if (len == 0)
+    return;
+
+  if (last_child_ && last_child_->IsText()) {
+    last_child_->AsText()->AddParsedText(cstr, len);
+    return;
+  }
+  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) {
+  cdata_ = true;
+  AddParsedText(buf, len);
+}
+
+void XmlElement::AddText(const std::string& text) {
+  if (text == STR_EMPTY)
+    return;
+
+  if (last_child_ && last_child_->IsText()) {
+    last_child_->AsText()->AddText(text);
+    return;
+  }
+  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) {
+  // note: the first syntax is ambigious for msvc 6
+  // XmlElement* pel(this);
+  XmlElement* element = this;
+  while (depth--) {
+    element = element->last_child_->AsElement();
+  }
+  element->AddText(text);
+}
+
+void XmlElement::AddElement(XmlElement *child) {
+  if (child == NULL)
+    return;
+
+  XmlChild ** pprev = last_child_ ? &(last_child_->next_child_) : &first_child_;
+  *pprev = child;
+  last_child_ = child;
+  child->next_child_ = NULL;
+}
+
+void XmlElement::AddElement(XmlElement *child, int depth) {
+  XmlElement* element = this;
+  while (depth--) {
+    element = element->last_child_->AsElement();
+  }
+  element->AddElement(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)
+    {
+      RemoveChildAfter(prev_child);
+      continue;
+    }
+    prev_child = child;
+  }
+}
+
+void XmlElement::ClearAttributes() {
+  XmlAttr* attr;
+  for (attr = first_attr_; attr; ) {
+    XmlAttr* to_delete = attr;
+    attr = attr->next_attr_;
+    delete to_delete;
+  }
+  first_attr_ = last_attr_ = NULL;
+}
+
+void XmlElement::ClearChildren() {
+  XmlChild* pchild;
+  for (pchild = first_child_; pchild; ) {
+    XmlChild* to_delete = pchild;
+    pchild = pchild->next_child_;
+    delete to_delete;
+  }
+  first_child_ = last_child_ = NULL;
+}
+
+std::string XmlElement::Str() const {
+  std::stringstream ss;
+  XmlPrinter::PrintXml(&ss, this);
+  return ss.str();
+}
+
+XmlElement* XmlElement::ForStr(const std::string& str) {
+  XmlBuilder builder;
+  XmlParser::ParseXml(&builder, str);
+  return builder.CreateElement();
+}
+
+XmlElement::~XmlElement() {
+  XmlAttr* attr;
+  for (attr = first_attr_; attr; ) {
+    XmlAttr* to_delete = attr;
+    attr = attr->next_attr_;
+    delete to_delete;
+  }
+
+  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
new file mode 100644
index 0000000..ffdc333
--- /dev/null
+++ b/talk/xmllite/xmlelement.h
@@ -0,0 +1,251 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_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 {
+
+class XmlChild;
+class XmlText;
+class XmlElement;
+class XmlAttr;
+
+class XmlChild {
+ 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(); }
+
+  XmlText* AsText() { return AsTextImpl(); }
+  const XmlText* AsText() const { return AsTextImpl(); }
+
+
+ protected:
+  XmlChild() :
+    next_child_(NULL) {
+  }
+
+  virtual bool IsTextImpl() const = 0;
+  virtual XmlElement* AsElementImpl() const = 0;
+  virtual XmlText* AsTextImpl() const = 0;
+
+
+  virtual ~XmlChild();
+
+ private:
+  friend class XmlElement;
+
+  XmlChild(const XmlChild& noimpl);
+
+  XmlChild* next_child_;
+};
+
+class XmlText : public XmlChild {
+ public:
+  explicit XmlText(const std::string& text) :
+    XmlChild(),
+    text_(text) {
+  }
+  explicit XmlText(const XmlText& t) :
+    XmlChild(),
+    text_(t.text_) {
+  }
+  explicit XmlText(const char* cstr, size_t len) :
+    XmlChild(),
+    text_(cstr, len) {
+  }
+  virtual ~XmlText();
+
+  const std::string& Text() const { return text_; }
+  void SetText(const std::string& text);
+  void AddParsedText(const char* buf, int len);
+  void AddText(const std::string& text);
+
+ protected:
+  virtual bool IsTextImpl() const;
+  virtual XmlElement* AsElementImpl() const;
+  virtual XmlText* AsTextImpl() const;
+
+ private:
+  std::string text_;
+};
+
+class XmlAttr {
+ public:
+  XmlAttr* NextAttr() const { return next_attr_; }
+  const QName& Name() const { return name_; }
+  const std::string& Value() const { return value_; }
+
+ private:
+  friend class XmlElement;
+
+  explicit XmlAttr(const QName& name, const std::string& value) :
+    next_attr_(NULL),
+    name_(name),
+    value_(value) {
+  }
+  explicit XmlAttr(const XmlAttr& att) :
+    next_attr_(NULL),
+    name_(att.name_),
+    value_(att.value_) {
+  }
+
+  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);
+
+  virtual ~XmlElement();
+
+  const QName& Name() const { return name_; }
+  void SetName(const QName& name) { name_ = name; }
+
+  const std::string BodyText() const;
+  void SetBodyText(const std::string& text);
+
+  const QName FirstElementName() const;
+
+  XmlAttr* FirstAttr();
+  const XmlAttr* FirstAttr() const
+    { return const_cast<XmlElement *>(this)->FirstAttr(); }
+
+  // Attr will return 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();
+  }
+
+  XmlElement* FirstElement();
+  const XmlElement* FirstElement() const {
+    return const_cast<XmlElement *>(this)->FirstElement();
+  }
+
+  XmlElement* NextElement();
+  const XmlElement* NextElement() const {
+    return const_cast<XmlElement *>(this)->NextElement();
+  }
+
+  XmlElement* FirstWithNamespace(const std::string& ns);
+  const XmlElement* FirstWithNamespace(const std::string& ns) const {
+    return const_cast<XmlElement *>(this)->FirstWithNamespace(ns);
+  }
+
+  XmlElement* NextWithNamespace(const std::string& ns);
+  const XmlElement* NextWithNamespace(const std::string& ns) const {
+    return const_cast<XmlElement *>(this)->NextWithNamespace(ns);
+  }
+
+  XmlElement* FirstNamed(const StaticQName& name);
+  const XmlElement* FirstNamed(const StaticQName& name) const {
+    return const_cast<XmlElement *>(this)->FirstNamed(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;
+
+  void InsertChildAfter(XmlChild* predecessor, XmlChild* new_child);
+  void RemoveChildAfter(XmlChild* predecessor);
+
+  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* 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);
+  std::string Str() const;
+
+  bool IsCDATA() const { return cdata_; }
+
+ protected:
+  virtual bool IsTextImpl() const;
+  virtual XmlElement* AsElementImpl() const;
+  virtual XmlText* AsTextImpl() const;
+
+ private:
+  QName name_;
+  XmlAttr* first_attr_;
+  XmlAttr* last_attr_;
+  XmlChild* first_child_;
+  XmlChild* last_child_;
+  bool cdata_;
+};
+
+}  // 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
new file mode 100644
index 0000000..26e27f8
--- /dev/null
+++ b/talk/xmllite/xmlnsstack.cc
@@ -0,0 +1,195 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 <sstream>
+#include <string>
+#include <vector>
+
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmllite/xmlconstants.h"
+
+namespace buzz {
+
+XmlnsStack::XmlnsStack() :
+  pxmlnsStack_(new std::vector<std::string>),
+  pxmlnsDepthStack_(new std::vector<size_t>) {
+}
+
+XmlnsStack::~XmlnsStack() {}
+
+void XmlnsStack::PushFrame() {
+  pxmlnsDepthStack_->push_back(pxmlnsStack_->size());
+}
+
+void XmlnsStack::PopFrame() {
+  size_t prev_size = pxmlnsDepthStack_->back();
+  pxmlnsDepthStack_->pop_back();
+  if (prev_size < pxmlnsStack_->size()) {
+    pxmlnsStack_->erase(pxmlnsStack_->begin() + prev_size,
+                        pxmlnsStack_->end());
+  }
+}
+
+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 std::make_pair(NS_XML, true);
+    if (prefix == "xmlns")
+      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 std::make_pair(*(pos + 1), true);
+  }
+
+  if (prefix == STR_EMPTY)
+    return std::make_pair(STR_EMPTY, true);  // default namespace
+
+  return std::make_pair(STR_EMPTY, false);  // none found
+}
+
+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) {
+  if (ns == NS_XML)
+    return std::make_pair(std::string("xml"), true);
+  if (ns == NS_XMLNS)
+    return std::make_pair(std::string("xmlns"), true);
+  if (isattr ? ns == STR_EMPTY : PrefixMatchesNs(STR_EMPTY, ns))
+    return std::make_pair(STR_EMPTY, true);
+
+  std::vector<std::string>::iterator pos;
+  for (pos = pxmlnsStack_->end(); pos > pxmlnsStack_->begin(); ) {
+    pos -= 2;
+    if (*(pos + 1) == ns &&
+        (!isattr || !pos->empty()) && PrefixMatchesNs(*pos, ns))
+      return std::make_pair(*pos, true);
+  }
+
+  return std::make_pair(STR_EMPTY, false); // none found
+}
+
+std::string XmlnsStack::FormatQName(const QName& name, bool isAttr) {
+  std::string prefix(PrefixForNs(name.Namespace(), isAttr).first);
+  if (prefix == STR_EMPTY)
+    return name.LocalPart();
+  else
+    return prefix + ':' + name.LocalPart();
+}
+
+void XmlnsStack::AddXmlns(const std::string & prefix, const std::string & ns) {
+  pxmlnsStack_->push_back(prefix);
+  pxmlnsStack_->push_back(ns);
+}
+
+void XmlnsStack::RemoveXmlns() {
+  pxmlnsStack_->pop_back();
+  pxmlnsStack_->pop_back();
+}
+
+static bool IsAsciiLetter(char ch) {
+  return ((ch >= 'a' && ch <= 'z') ||
+          (ch >= 'A' && ch <= 'Z'));
+}
+
+static std::string AsciiLower(const std::string & s) {
+  std::string result(s);
+  size_t i;
+  for (i = 0; i < result.length(); i++) {
+    if (result[i] >= 'A' && result[i] <= 'Z')
+      result[i] += 'a' - 'A';
+  }
+  return result;
+}
+
+static std::string SuggestPrefix(const std::string & ns) {
+  size_t len = ns.length();
+  size_t i = ns.find_last_of('.');
+  if (i != std::string::npos && len - i <= 4 + 1)
+    len = i; // chop off ".html" or ".xsd" or ".?{0,4}"
+  size_t last = len;
+  while (last > 0) {
+    last -= 1;
+    if (IsAsciiLetter(ns[last])) {
+      size_t first = last;
+      last += 1;
+      while (first > 0) {
+        if (!IsAsciiLetter(ns[first - 1]))
+          break;
+        first -= 1;
+      }
+      if (last - first > 4)
+        last = first + 3;
+      std::string candidate(AsciiLower(ns.substr(first, last - first)));
+      if (candidate.find("xml") != 0)
+        return candidate;
+      break;
+    }
+  }
+  return "ns";
+}
+
+std::pair<std::string, bool> XmlnsStack::AddNewPrefix(const std::string& ns,
+                                                      bool isAttr) {
+  if (PrefixForNs(ns, isAttr).second)
+    return std::make_pair(STR_EMPTY, false);
+
+  std::string base(SuggestPrefix(ns));
+  std::string result(base);
+  int i = 2;
+  while (NsForPrefix(result).second) {
+    std::stringstream ss;
+    ss << base;
+    ss << (i++);
+    ss >> result;
+  }
+  AddXmlns(result, ns);
+  return std::make_pair(result, true);
+}
+
+void XmlnsStack::Reset() {
+  pxmlnsStack_->clear();
+  pxmlnsDepthStack_->clear();
+}
+
+}
diff --git a/talk/xmllite/xmlnsstack.h b/talk/xmllite/xmlnsstack.h
new file mode 100644
index 0000000..f6b4b81
--- /dev/null
+++ b/talk/xmllite/xmlnsstack.h
@@ -0,0 +1,62 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_XMLLITE_XMLNSSTACK_H_
+#define TALK_XMLLITE_XMLNSSTACK_H_
+
+#include <string>
+#include <vector>
+#include "talk/base/scoped_ptr.h"
+#include "talk/xmllite/qname.h"
+
+namespace buzz {
+
+class XmlnsStack {
+public:
+  XmlnsStack();
+  ~XmlnsStack();
+
+  void AddXmlns(const std::string& prefix, const std::string& ns);
+  void RemoveXmlns();
+  void PushFrame();
+  void PopFrame();
+  void Reset();
+
+  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::string FormatQName(const QName & name, bool isAttr);
+
+private:
+
+  talk_base::scoped_ptr<std::vector<std::string> > pxmlnsStack_;
+  talk_base::scoped_ptr<std::vector<size_t> > pxmlnsDepthStack_;
+};
+}
+
+#endif  // TALK_XMLLITE_XMLNSSTACK_H_
diff --git a/talk/xmllite/xmlnsstack_unittest.cc b/talk/xmllite/xmlnsstack_unittest.cc
new file mode 100644
index 0000000..20b5972
--- /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_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
new file mode 100644
index 0000000..3e4d733
--- /dev/null
+++ b/talk/xmllite/xmlparser.cc
@@ -0,0 +1,279 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/xmllite/xmlparser.h"
+
+#include <string>
+#include <vector>
+
+#include "talk/base/common.h"
+#include "talk/xmllite/xmlconstants.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmllite/xmlnsstack.h"
+#include "talk/xmllite/xmlnsstack.h"
+
+namespace buzz {
+
+
+static void
+StartElementCallback(void * userData, const char *name, const char **atts) {
+  (static_cast<XmlParser *>(userData))->ExpatStartElement(name, atts);
+}
+
+static void
+EndElementCallback(void * userData, const char *name) {
+  (static_cast<XmlParser *>(userData))->ExpatEndElement(name);
+}
+
+static void
+CharacterDataCallback(void * userData, const char *text, int len) {
+  (static_cast<XmlParser *>(userData))->ExpatCharacterData(text, len);
+}
+
+static void
+XmlDeclCallback(void * userData, const char * ver, const char * enc, int st) {
+  (static_cast<XmlParser *>(userData))->ExpatXmlDecl(ver, enc, st);
+}
+
+XmlParser::XmlParser(XmlParseHandler *pxph) :
+    context_(this), pxph_(pxph), sentError_(false) {
+  expat_ = XML_ParserCreate(NULL);
+  XML_SetUserData(expat_, this);
+  XML_SetElementHandler(expat_, StartElementCallback, EndElementCallback);
+  XML_SetCharacterDataHandler(expat_, CharacterDataCallback);
+  XML_SetXmlDeclHandler(expat_, XmlDeclCallback);
+}
+
+void
+XmlParser::Reset() {
+  if (!XML_ParserReset(expat_, NULL)) {
+    XML_ParserFree(expat_);
+    expat_ = XML_ParserCreate(NULL);
+  }
+  XML_SetUserData(expat_, this);
+  XML_SetElementHandler(expat_, StartElementCallback, EndElementCallback);
+  XML_SetCharacterDataHandler(expat_, CharacterDataCallback);
+  XML_SetXmlDeclHandler(expat_, XmlDeclCallback);
+  context_.Reset();
+  sentError_ = false;
+}
+
+static bool
+XmlParser_StartsWithXmlns(const char *name) {
+  return name[0] == 'x' &&
+         name[1] == 'm' &&
+         name[2] == 'l' &&
+         name[3] == 'n' &&
+         name[4] == 's';
+}
+
+void
+XmlParser::ExpatStartElement(const char *name, const char **atts) {
+  if (context_.RaisedError() != XML_ERROR_NONE)
+    return;
+  const char **att;
+  context_.StartElement();
+  for (att = atts; *att; att += 2) {
+    if (XmlParser_StartsWithXmlns(*att)) {
+      if ((*att)[5] == '\0') {
+        context_.StartNamespace("", *(att + 1));
+      }
+      else if ((*att)[5] == ':') {
+        if (**(att + 1) == '\0') {
+          // In XML 1.0 empty namespace illegal with prefix (not in 1.1)
+          context_.RaiseError(XML_ERROR_SYNTAX);
+          return;
+        }
+        context_.StartNamespace((*att) + 6, *(att + 1));
+      }
+    }
+  }
+  context_.SetPosition(XML_GetCurrentLineNumber(expat_),
+                       XML_GetCurrentColumnNumber(expat_),
+                       XML_GetCurrentByteIndex(expat_));
+  pxph_->StartElement(&context_, name, atts);
+}
+
+void
+XmlParser::ExpatEndElement(const char *name) {
+  if (context_.RaisedError() != XML_ERROR_NONE)
+    return;
+  context_.EndElement();
+  context_.SetPosition(XML_GetCurrentLineNumber(expat_),
+                       XML_GetCurrentColumnNumber(expat_),
+                       XML_GetCurrentByteIndex(expat_));
+  pxph_->EndElement(&context_, name);
+}
+
+void
+XmlParser::ExpatCharacterData(const char *text, int len) {
+  if (context_.RaisedError() != XML_ERROR_NONE)
+    return;
+  context_.SetPosition(XML_GetCurrentLineNumber(expat_),
+                       XML_GetCurrentColumnNumber(expat_),
+                       XML_GetCurrentByteIndex(expat_));
+  pxph_->CharacterData(&context_, text, len);
+}
+
+void
+XmlParser::ExpatXmlDecl(const char * ver, const char * enc, int standalone) {
+  if (context_.RaisedError() != XML_ERROR_NONE)
+    return;
+
+  if (ver && std::string("1.0") != ver) {
+    context_.RaiseError(XML_ERROR_SYNTAX);
+    return;
+  }
+
+  if (standalone == 0) {
+    context_.RaiseError(XML_ERROR_SYNTAX);
+    return;
+  }
+
+  if (enc && !((enc[0] == 'U' || enc[0] == 'u') &&
+               (enc[1] == 'T' || enc[1] == 't') &&
+               (enc[2] == 'F' || enc[2] == 'f') &&
+                enc[3] == '-' && enc[4] =='8')) {
+    context_.RaiseError(XML_ERROR_INCORRECT_ENCODING);
+    return;
+  }
+
+}
+
+bool
+XmlParser::Parse(const char *data, size_t len, bool isFinal) {
+  if (sentError_)
+    return false;
+
+  if (XML_Parse(expat_, data, static_cast<int>(len), isFinal) !=
+      XML_STATUS_OK) {
+    context_.SetPosition(XML_GetCurrentLineNumber(expat_),
+                         XML_GetCurrentColumnNumber(expat_),
+                         XML_GetCurrentByteIndex(expat_));
+    context_.RaiseError(XML_GetErrorCode(expat_));
+  }
+
+  if (context_.RaisedError() != XML_ERROR_NONE) {
+    sentError_ = true;
+    pxph_->Error(&context_, context_.RaisedError());
+    return false;
+  }
+
+  return true;
+}
+
+XmlParser::~XmlParser() {
+  XML_ParserFree(expat_);
+}
+
+void
+XmlParser::ParseXml(XmlParseHandler *pxph, std::string text) {
+  XmlParser parser(pxph);
+  parser.Parse(text.c_str(), text.length(), true);
+}
+
+XmlParser::ParseContext::ParseContext(XmlParser *parser) :
+    parser_(parser),
+    xmlnsstack_(),
+    raised_(XML_ERROR_NONE),
+    line_number_(0),
+    column_number_(0),
+    byte_index_(0) {
+}
+
+void
+XmlParser::ParseContext::StartNamespace(const char *prefix, const char *ns) {
+  xmlnsstack_.AddXmlns(*prefix ? prefix : STR_EMPTY, ns);
+}
+
+void
+XmlParser::ParseContext::StartElement() {
+  xmlnsstack_.PushFrame();
+}
+
+void
+XmlParser::ParseContext::EndElement() {
+  xmlnsstack_.PopFrame();
+}
+
+QName
+XmlParser::ParseContext::ResolveQName(const char* qname, bool isAttr) {
+  const char *c;
+  for (c = qname; *c; ++c) {
+    if (*c == ':') {
+      const std::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)
+    return QName(STR_EMPTY, qname);
+
+  std::pair<std::string, bool> result = xmlnsstack_.NsForPrefix(STR_EMPTY);
+  if (!result.second)
+    return QName();
+
+  return QName(result.first, qname);
+}
+
+void
+XmlParser::ParseContext::Reset() {
+  xmlnsstack_.Reset();
+  raised_ = XML_ERROR_NONE;
+}
+
+void
+XmlParser::ParseContext::SetPosition(int line, int column,
+                                          long byte_index) {
+  line_number_ = line;
+  column_number_ = column;
+  byte_index_ = byte_index;
+}
+
+void
+XmlParser::ParseContext::GetPosition(unsigned long * line,
+                                     unsigned long * column,
+                                     unsigned long * byte_index) {
+  if (line != NULL) {
+    *line = static_cast<unsigned long>(line_number_);
+  }
+
+  if (column != NULL) {
+    *column = static_cast<unsigned long>(column_number_);
+  }
+
+  if (byte_index != NULL) {
+    *byte_index = static_cast<unsigned long>(byte_index_);
+  }
+}
+
+XmlParser::ParseContext::~ParseContext() {
+}
+
+}  // namespace buzz
diff --git a/talk/xmllite/xmlparser.h b/talk/xmllite/xmlparser.h
new file mode 100644
index 0000000..69cde75
--- /dev/null
+++ b/talk/xmllite/xmlparser.h
@@ -0,0 +1,121 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_XMLLITE_XMLPARSER_H_
+#define TALK_XMLLITE_XMLPARSER_H_
+
+#include <string>
+
+#include "talk/xmllite/xmlnsstack.h"
+#ifdef EXPAT_RELATIVE_PATH
+#include "expat.h"
+#else
+#include "third_party/expat/v2_0_1/Source/lib/expat.h"
+#endif  // EXPAT_RELATIVE_PATH
+
+struct XML_ParserStruct;
+typedef struct XML_ParserStruct* XML_Parser;
+
+namespace buzz {
+
+class XmlParseHandler;
+class XmlParseContext;
+class XmlParser;
+
+class XmlParseContext {
+public:
+  virtual ~XmlParseContext() {}
+  virtual QName ResolveQName(const char * qname, bool isAttr) = 0;
+  virtual void RaiseError(XML_Error err) = 0;
+  virtual void GetPosition(unsigned long * line, unsigned long * column,
+                           unsigned long * byte_index) = 0;
+};
+
+class XmlParseHandler {
+public:
+  virtual ~XmlParseHandler() {}
+  virtual void StartElement(XmlParseContext * pctx,
+               const char * name, const char ** atts) = 0;
+  virtual void EndElement(XmlParseContext * pctx,
+               const char * name) = 0;
+  virtual void CharacterData(XmlParseContext * pctx,
+               const char * text, int len) = 0;
+  virtual void Error(XmlParseContext * pctx,
+               XML_Error errorCode) = 0;
+};
+
+class XmlParser {
+public:
+  static void ParseXml(XmlParseHandler * pxph, std::string text);
+
+  explicit XmlParser(XmlParseHandler * pxph);
+  bool Parse(const char * data, size_t len, bool isFinal);
+  void Reset();
+  virtual ~XmlParser();
+
+  // expat callbacks
+  void ExpatStartElement(const char * name, const char ** atts);
+  void ExpatEndElement(const char * name);
+  void ExpatCharacterData(const char * text, int len);
+  void ExpatXmlDecl(const char * ver, const char * enc, int standalone);
+
+private:
+
+  class ParseContext : public XmlParseContext {
+  public:
+    ParseContext(XmlParser * parser);
+    virtual ~ParseContext();
+    virtual QName ResolveQName(const char * qname, bool isAttr);
+    virtual void RaiseError(XML_Error err) { if (!raised_) raised_ = err; }
+    virtual void GetPosition(unsigned long * line, unsigned long * column,
+                             unsigned long * byte_index);
+    XML_Error RaisedError() { return raised_; }
+    void Reset();
+
+    void StartElement();
+    void EndElement();
+    void StartNamespace(const char * prefix, const char * ns);
+    void SetPosition(int line, int column, long byte_index);
+
+  private:
+    const XmlParser * parser_;
+    XmlnsStack xmlnsstack_;
+    XML_Error raised_;
+    XML_Size line_number_;
+    XML_Size column_number_;
+    XML_Index byte_index_;
+  };
+
+  ParseContext context_;
+  XML_Parser expat_;
+  XmlParseHandler * pxph_;
+  bool sentError_;
+};
+
+}  // namespace buzz
+
+#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
new file mode 100644
index 0000000..1350454
--- /dev/null
+++ b/talk/xmllite/xmlprinter.cc
@@ -0,0 +1,191 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/xmllite/xmlprinter.h"
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "talk/xmllite/xmlconstants.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmllite/xmlnsstack.h"
+
+namespace buzz {
+
+class XmlPrinterImpl {
+public:
+  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* ns_stack_;
+};
+
+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,
+                          XmlnsStack* ns_stack) {
+  XmlPrinterImpl printer(pout, ns_stack);
+  printer.PrintElement(element);
+}
+
+XmlPrinterImpl::XmlPrinterImpl(std::ostream* pout, XmlnsStack* ns_stack)
+    : pout_(pout),
+      ns_stack_(ns_stack) {
+}
+
+void XmlPrinterImpl::PrintElement(const XmlElement* element) {
+  ns_stack_->PushFrame();
+
+  // first go through attrs of pel to add xmlns definitions
+  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> new_ns;
+  std::pair<std::string, bool> prefix;
+  prefix = ns_stack_->AddNewPrefix(element->Name().Namespace(), false);
+  if (prefix.second) {
+    new_ns.push_back(prefix.first);
+    new_ns.push_back(element->Name().Namespace());
+  }
+
+  for (attr = element->FirstAttr(); attr; attr = attr->NextAttr()) {
+    prefix = ns_stack_->AddNewPrefix(attr->Name().Namespace(), true);
+    if (prefix.second) {
+      new_ns.push_back(prefix.first);
+      new_ns.push_back(attr->Name().Namespace());
+    }
+  }
+
+  // print the element name
+  *pout_ << '<' << ns_stack_->FormatQName(element->Name(), false);
+
+  // and the attributes
+  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(new_ns.begin());
+  while (i < new_ns.end()) {
+    if (*i == STR_EMPTY) {
+      *pout_ << " xmlns=\"" << *(i + 1) << '"';
+    } else {
+      *pout_ << " xmlns:" << *i << "=\"" << *(i + 1) << '"';
+    }
+    i += 2;
+  }
+
+  // now the children
+  const XmlChild* child = element->FirstChild();
+
+  if (child == NULL)
+    *pout_ << "/>";
+  else {
+    *pout_ << '>';
+    while (child) {
+      if (child->IsText()) {
+        if (element->IsCDATA()) {
+          PrintCDATAText(child->AsText()->Text());
+        } else {
+          PrintBodyText(child->AsText()->Text());
+        }
+      } else {
+        PrintElement(child->AsElement());
+      }
+      child = child->NextChild();
+    }
+    *pout_ << "</" << ns_stack_->FormatQName(element->Name(), false) << '>';
+  }
+
+  ns_stack_->PopFrame();
+}
+
+void XmlPrinterImpl::PrintQuotedValue(const std::string& text) {
+  size_t safe = 0;
+  for (;;) {
+    size_t unsafe = text.find_first_of("<>&\"", safe);
+    if (unsafe == std::string::npos)
+      unsafe = text.length();
+    *pout_ << text.substr(safe, unsafe - safe);
+    if (unsafe == text.length())
+      return;
+    switch (text[unsafe]) {
+      case '<': *pout_ << "&lt;"; break;
+      case '>': *pout_ << "&gt;"; break;
+      case '&': *pout_ << "&amp;"; break;
+      case '"': *pout_ << "&quot;"; break;
+    }
+    safe = unsafe + 1;
+    if (safe == text.length())
+      return;
+  }
+}
+
+void XmlPrinterImpl::PrintBodyText(const std::string& text) {
+  size_t safe = 0;
+  for (;;) {
+    size_t unsafe = text.find_first_of("<>&", safe);
+    if (unsafe == std::string::npos)
+      unsafe = text.length();
+    *pout_ << text.substr(safe, unsafe - safe);
+    if (unsafe == text.length())
+      return;
+    switch (text[unsafe]) {
+      case '<': *pout_ << "&lt;"; break;
+      case '>': *pout_ << "&gt;"; break;
+      case '&': *pout_ << "&amp;"; break;
+    }
+    safe = unsafe + 1;
+    if (safe == text.length())
+      return;
+  }
+}
+
+void XmlPrinterImpl::PrintCDATAText(const std::string& text) {
+  *pout_ << "<![CDATA[" << text << "]]>";
+}
+
+}  // namespace buzz
diff --git a/talk/xmllite/xmlprinter.h b/talk/xmllite/xmlprinter.h
new file mode 100644
index 0000000..90cc255
--- /dev/null
+++ b/talk/xmllite/xmlprinter.h
@@ -0,0 +1,49 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_XMLLITE_XMLPRINTER_H_
+#define TALK_XMLLITE_XMLPRINTER_H_
+
+#include <iosfwd>
+#include <string>
+
+namespace buzz {
+
+class XmlElement;
+class XmlnsStack;
+
+class XmlPrinter {
+ public:
+  static void PrintXml(std::ostream* pout, const XmlElement* pelt);
+
+  static void PrintXml(std::ostream* pout, const XmlElement* pelt,
+                       XmlnsStack* ns_stack);
+};
+
+}  // namespace buzz
+
+#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
new file mode 100644
index 0000000..fb4ef02
--- /dev/null
+++ b/talk/xmpp/asyncsocket.h
@@ -0,0 +1,87 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _ASYNCSOCKET_H_
+#define _ASYNCSOCKET_H_
+
+#include "talk/base/sigslot.h"
+
+namespace talk_base {
+  class SocketAddress;
+}
+
+namespace buzz {
+
+class AsyncSocket {
+public:
+  enum State {
+    STATE_CLOSED = 0,      //!< Socket is not open.
+    STATE_CLOSING,         //!< Socket is closing but can have buffered data
+    STATE_CONNECTING,      //!< In the process of
+    STATE_OPEN,            //!< Socket is connected
+#if defined(FEATURE_ENABLE_SSL)
+    STATE_TLS_CONNECTING,  //!< Establishing TLS connection
+    STATE_TLS_OPEN,        //!< TLS connected
+#endif
+  };
+
+  enum Error {
+    ERROR_NONE = 0,         //!< No error
+    ERROR_WINSOCK,          //!< Winsock error
+    ERROR_DNS,              //!< Couldn't resolve host name
+    ERROR_WRONGSTATE,       //!< Call made while socket is in the wrong state
+#if defined(FEATURE_ENABLE_SSL)
+    ERROR_SSL,              //!< Something went wrong with OpenSSL
+#endif
+  };
+
+  virtual ~AsyncSocket() {}
+  virtual State state() = 0;
+  virtual Error error() = 0;
+  virtual int GetError() = 0;    // winsock error code
+
+  virtual bool Connect(const talk_base::SocketAddress& addr) = 0;
+  virtual bool Read(char * data, size_t len, size_t* len_read) = 0;
+  virtual bool Write(const char * data, size_t len) = 0;
+  virtual bool Close() = 0;
+#if defined(FEATURE_ENABLE_SSL)
+  // We allow matching any passed domain.  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
+
+  sigslot::signal0<> SignalConnected;
+  sigslot::signal0<> SignalSSLConnected;
+  sigslot::signal0<> SignalClosed;
+  sigslot::signal0<> SignalRead;
+  sigslot::signal0<> SignalError;
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/chatroommodule.h b/talk/xmpp/chatroommodule.h
new file mode 100644
index 0000000..47a7106
--- /dev/null
+++ b/talk/xmpp/chatroommodule.h
@@ -0,0 +1,270 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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,
+      const std::string& client_version,
+      const std::string& locale) = 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 RequestConnectionStatusChange(
+      XmppPresenceConnectionStatus connection_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,
+  //! Someone in the room has blocked you
+  XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BLOCKED     = 8,
+  //! You have blocked someone in the room
+  XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BLOCKING    = 9,
+  //! Client is old. User must upgrade to a more recent version for
+  // hangouts to work.
+  XMPP_CHATROOM_ENTERED_FAILURE_OUTDATED_CLIENT    = 10,
+  //! 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:
+  virtual ~XmppChatroomHandler() {}
+
+  //! Indicates the response to RequestEnterChatroom method
+  //! XMPP_CHATROOM_SUCCESS represents success.
+  //! Other status codes are for errors
+  virtual void ChatroomEnteredStatus(XmppChatroomModule* room,
+                                     const XmppPresence* presence,
+                                     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.
+  //! It can be called before ChatroomEnteredStatus.
+  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,
+                             const XmppChatroomMember* changed_member) = 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..a152f60
--- /dev/null
+++ b/talk/xmpp/chatroommodule_unittest.cc
@@ -0,0 +1,297 @@
+/*
+ * 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,
+      const XmppChatroomMember* changed_member) {
+    UNUSED(room);
+    ss_ << "[MemberChanged " << changed_member->member_jid().Str() << "]";
+  }
+
+  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("", XMPP_CONNECTION_STATUS_UNKNOWN, "en");
+    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..eb046d7
--- /dev/null
+++ b/talk/xmpp/chatroommoduleimpl.cc
@@ -0,0 +1,755 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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,
+      const std::string& client_version,
+      const std::string& locale);
+  virtual XmppReturnStatus RequestExitChatroom();
+  virtual XmppReturnStatus RequestConnectionStatusChange(
+      XmppPresenceConnectionStatus connection_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(const XmlElement* presence,
+                         XmppChatroomEnteredStatus status);
+  void FireExitStatus(XmppChatroomExitedStatus status);
+  void FireMessageReceived(const XmlElement& message);
+  void FireMemberEntered(const XmppChatroomMember* entered_member);
+  void FireMemberChanged(const XmppChatroomMember* changed_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;
+}
+
+std::string GetAttrValueFor(XmppPresenceConnectionStatus connection_status) {
+  switch (connection_status) {
+    default:
+    case XMPP_CONNECTION_STATUS_UNKNOWN:
+      return "";
+    case XMPP_CONNECTION_STATUS_CONNECTING:
+      return STR_PSTN_CONFERENCE_STATUS_CONNECTING;
+    case XMPP_CONNECTION_STATUS_CONNECTED:
+      return STR_PSTN_CONFERENCE_STATUS_CONNECTED;
+  }
+}
+
+XmppReturnStatus
+XmppChatroomModuleImpl::RequestEnterChatroom(
+    const std::string& password,
+    const std::string& client_version,
+    const std::string& locale) {
+  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());
+
+  XmlElement* muc_x = new XmlElement(QN_MUC_X);
+  element.AddElement(muc_x);
+
+  if (!client_version.empty()) {
+    XmlElement* client_version_element = new XmlElement(QN_CLIENT_VERSION,
+                                                        false);
+    client_version_element->SetBodyText(client_version);
+    muc_x->AddElement(client_version_element);
+  }
+
+  if (!locale.empty()) {
+    XmlElement* locale_element = new XmlElement(QN_LOCALE, false);
+
+    locale_element->SetBodyText(locale);
+    muc_x->AddElement(locale_element);
+  }
+
+  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::RequestConnectionStatusChange(
+    XmppPresenceConnectionStatus connection_status) {
+  if (!engine())
+    return XMPP_RETURN_BADSTATE;
+
+  if (chatroom_state_ != XMPP_CHATROOM_STATE_IN_ROOM) {
+    // $TODO - this isn't a bad state, it's a bad call,  diff error code?
+    return XMPP_RETURN_BADSTATE;
+  }
+
+  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));
+  if (connection_status != XMPP_CONNECTION_STATUS_UNKNOWN) {
+    XmlElement* con_status_element =
+        new XmlElement(QN_GOOGLE_PSTN_CONFERENCE_STATUS);
+    con_status_element->AddAttr(QN_STATUS, GetAttrValueFor(connection_status));
+    element.AddElement(con_status_element);
+  }
+  XmppReturnStatus status = engine()->SendStanza(&element);
+
+  return status;
+}
+
+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 ""; //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(const XmlElement* presence,
+                                          XmppChatroomEnteredStatus status) {
+  if (chatroom_handler_) {
+    talk_base::scoped_ptr<XmppPresence> xmpp_presence(XmppPresence::Create());
+    xmpp_presence->set_raw_xml(presence);
+    chatroom_handler_->ChatroomEnteredStatus(this, xmpp_presence.get(), 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) {
+  if (chatroom_handler_)
+    chatroom_handler_->MemberEntered(this, entered_member);
+}
+
+void
+XmppChatroomModuleImpl::FireMemberChanged(
+    const XmppChatroomMember* changed_member) {
+  if (chatroom_handler_)
+    chatroom_handler_->MemberChanged(this, changed_member);
+}
+
+void
+XmppChatroomModuleImpl::FireMemberExited(const XmppChatroomMember* exited_member) {
+  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_++;
+      FireMemberChanged(member);
+    }
+    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?
+    // Disable the assert because of b/6133072
+    // ASSERT(transition_desc->is_valid_server_transition);
+    if (!transition_desc->is_valid_server_transition) {
+      return XMPP_RETURN_BADSTATE;
+    }
+  } 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(presence, XMPP_CHATROOM_ENTERED_SUCCESS);
+      break;
+    case TRANSITION_TYPE_ENTER_FAILURE:
+      FireEnteredStatus(presence, 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;
+        if (error->FirstNamed(QN_GOOGLE_SESSION_BLOCKED)) {
+          status = XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BLOCKED;
+        } else if (error->FirstNamed(QN_GOOGLE_SESSION_BLOCKING)) {
+          status = XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BLOCKING;
+        }
+        break;
+      }
+      case 405: status = XMPP_CHATROOM_ENTERED_FAILURE_ROOM_LOCKED; break;
+      case 406: status = XMPP_CHATROOM_ENTERED_FAILURE_OUTDATED_CLIENT; break;
+      case 407: status = XMPP_CHATROOM_ENTERED_FAILURE_NOT_A_MEMBER; break;
+      case 409: status = XMPP_CHATROOM_ENTERED_FAILURE_NICKNAME_CONFLICT; break;
+      // http://xmpp.org/extensions/xep-0045.html#enter-maxusers
+      case 503: status = XMPP_CHATROOM_ENTERED_FAILURE_MAX_USERS; break;
+    }
+  }
+  return status;
+}
+
+XmppChatroomExitedStatus
+XmppChatroomModuleImpl::GetExitFailureFromXml(const XmlElement* presence) {
+  XmppChatroomExitedStatus status = XMPP_CHATROOM_EXITED_UNSPECIFIED;
+  const XmlElement* muc_user = presence->FirstNamed(QN_MUC_USER_X);
+  if (muc_user != NULL) {
+    const XmlElement* user_status = muc_user->FirstNamed(QN_MUC_USER_STATUS);
+    if (user_status != NULL && user_status->HasAttr(QN_CODE)) {
+      int code = atoi(user_status->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
new file mode 100644
index 0000000..193ae2b
--- /dev/null
+++ b/talk/xmpp/constants.cc
@@ -0,0 +1,608 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/xmpp/constants.h"
+
+#include <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"
+
+namespace buzz {
+
+// TODO: Remove static objects of complex types, particularly
+// Jid and QName.
+
+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 char STR_GET[] = "get";
+const char STR_SET[] = "set";
+const char STR_RESULT[] = "result";
+const char STR_ERROR[] = "error";
+
+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 char STR_FROM[] = "from";
+const char STR_TO[] = "to";
+const char STR_BOTH[] = "both";
+const char STR_REMOVE[] = "remove";
+const char STR_TRUE[] = "true";
+
+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 char STR_VOICEMAIL[] = "voicemail";
+const char STR_OUTGOINGVOICEMAIL[] = "outgoingvoicemail";
+#endif
+
+const char STR_UNAVAILABLE[] = "unavailable";
+
+const char NS_PING[] = "urn:xmpp:ping";
+const StaticQName QN_PING = { NS_PING, "ping" };
+
+const char NS_MUC_UNIQUE[] = "http://jabber.org/protocol/muc#unique";
+const StaticQName QN_MUC_UNIQUE_QUERY = { NS_MUC_UNIQUE, "unique" };
+const StaticQName QN_HANGOUT_ID = { STR_EMPTY, "hangout-id" };
+
+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 char STR_MUC_ROOM_FEATURE_HANGOUT[] = "muc_es";
+const char STR_MUC_ROOM_FEATURE_HANGOUT_LITE[] = "muc_lite";
+const char STR_MUC_ROOM_FEATURE_BROADCAST[] = "broadcast";
+const char STR_MUC_ROOM_FEATURE_MULTI_USER_VC[] = "muc_muvc";
+
+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 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 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 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" };
+
+// 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 StaticQName QN_GOOGLE_AUTH_SERVICE =
+    { NS_GOOGLE_AUTH_PROTOCOL, "service" };
+
+const StaticQName QN_DIALBACK_RESULT = { NS_DIALBACK, "result" };
+const StaticQName QN_DIALBACK_VERIFY = { NS_DIALBACK, "verify" };
+
+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 StaticQName QN_BIND_BIND = { NS_BIND, "bind" };
+const StaticQName QN_BIND_RESOURCE = { NS_BIND, "resource" };
+const StaticQName QN_BIND_JID = { NS_BIND, "jid" };
+
+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 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 StaticQName QN_SESSION_SESSION = { NS_SESSION, "session" };
+
+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 StaticQName QN_ROSTER_QUERY = { NS_ROSTER, "query" };
+const StaticQName QN_ROSTER_ITEM = { NS_ROSTER, "item" };
+const StaticQName QN_ROSTER_GROUP = { NS_ROSTER, "group" };
+
+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 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 StaticQName QN_XML_LANG = { NS_XML, "lang" };
+
+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 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 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 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";
+
+const char NS_GOOGLE_PSTN_CONFERENCE[] = "http://www.google.com/pstn-conference";
+const StaticQName QN_GOOGLE_PSTN_CONFERENCE_STATUS = { NS_GOOGLE_PSTN_CONFERENCE, "status" };
+const StaticQName QN_ATTR_STATUS = { STR_EMPTY, "status" };
+
+// Presence connection status
+const char STR_PSTN_CONFERENCE_STATUS_CONNECTING[] = "connecting";
+const char STR_PSTN_CONFERENCE_STATUS_CONNECTED[] = "connected";
+const char STR_PSTN_CONFERENCE_STATUS_HANGUP[] = "hangup";
+
+// Subscription
+const char STR_SUBSCRIBE[] = "subscribe";
+const char STR_SUBSCRIBED[] = "subscribed";
+const char STR_UNSUBSCRIBE[] = "unsubscribe";
+const char STR_UNSUBSCRIBED[] = "unsubscribed";
+
+// Google Invite
+const char NS_GOOGLE_SUBSCRIBE[] = "google:subscribe";
+const StaticQName QN_INVITATION = { NS_GOOGLE_SUBSCRIBE, "invitation" };
+const StaticQName QN_INVITE_NAME = { NS_GOOGLE_SUBSCRIBE, "name" };
+const StaticQName QN_INVITE_SUBJECT = { NS_GOOGLE_SUBSCRIBE, "subject" };
+const StaticQName QN_INVITE_MESSAGE = { NS_GOOGLE_SUBSCRIBE, "body" };
+
+// Kick
+const char NS_GOOGLE_MUC_ADMIN[] = "google:muc#admin";
+const StaticQName QN_GOOGLE_MUC_ADMIN_QUERY = { NS_GOOGLE_MUC_ADMIN, "query" };
+const StaticQName QN_GOOGLE_MUC_ADMIN_QUERY_ITEM =
+    { NS_GOOGLE_MUC_ADMIN, "item" };
+const StaticQName QN_GOOGLE_MUC_ADMIN_QUERY_ITEM_REASON =
+    { NS_GOOGLE_MUC_ADMIN, "reason" };
+
+// PubSub: http://xmpp.org/extensions/xep-0060.html
+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 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 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 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 StaticQName QN_DISCO_ITEMS_QUERY = { NS_DISCO_ITEMS, "query" };
+const StaticQName QN_DISCO_ITEM = { NS_DISCO_ITEMS, "item" };
+
+// JEP 0020
+const char NS_FEATURE[] = "http://jabber.org/protocol/feature-neg";
+const StaticQName QN_FEATURE_FEATURE = { NS_FEATURE, "feature" };
+
+// JEP 0004
+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 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 char NS_GOOGLE_SESSION[] = "http://www.google.com/session";
+const StaticQName QN_GOOGLE_CIRCLE_ID = { STR_EMPTY, "google-circle-id" };
+const StaticQName QN_GOOGLE_USER_ID = { STR_EMPTY, "google-user-id" };
+const StaticQName QN_GOOGLE_SESSION_BLOCKED = { NS_GOOGLE_SESSION, "blocked" };
+const StaticQName QN_GOOGLE_SESSION_BLOCKING =
+    { NS_GOOGLE_SESSION, "blocking" };
+
+const char NS_MUC_OWNER[] = "http://jabber.org/protocol/muc#owner";
+const StaticQName QN_MUC_OWNER_QUERY = { NS_MUC_OWNER, "query" };
+
+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" };
+const StaticQName QN_MUC_USER_REASON = { NS_MUC_USER, "reason" };
+const StaticQName QN_MUC_USER_ABUSE_VIOLATION = { NS_MUC_USER, "abuse-violation" };
+
+// JEP 0055 - Jabber Search
+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" };
+const StaticQName QN_SEARCH_HANGOUT_ID = { NS_SEARCH, "hangout-id" };
+const StaticQName QN_SEARCH_EXTERNAL_ID = { NS_SEARCH, "external-id" };
+
+// JEP 0115
+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 char kNSVCard[] = "vcard-temp:x:update";
+const StaticQName kQnVCardX = { kNSVCard, "x" };
+const StaticQName kQnVCardPhoto = { kNSVCard, "photo" };
+
+// JEP 0172 User Nickname
+const char NS_NICKNAME[] = "http://jabber.org/protocol/nick";
+const StaticQName QN_NICKNAME = { NS_NICKNAME, "nick" };
+
+// JEP 0085 chat state
+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 char kNSDelay[] = "jabber:x:delay";
+const StaticQName kQnDelayX = { kNSDelay, "x" };
+const StaticQName kQnStamp = { STR_EMPTY, "stamp" };
+
+// Google time stamping (higher resolution)
+const char kNSTimestamp[] = "google:timestamp";
+const StaticQName kQnTime = { kNSTimestamp, "time" };
+const StaticQName kQnMilliseconds = { STR_EMPTY, "ms" };
+
+// Jingle Info
+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 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 StaticQName QN_MUC_USER_INVITE = { NS_MUC_USER, "invite" };
+
+// Multiway audio/video.
+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_CLIENT_VERSION = { NS_GOOGLE_MUC_USER, "client-version" };
+const StaticQName QN_LOCALE = { NS_GOOGLE_MUC_USER, "locale" };
+const StaticQName QN_LABEL = { STR_EMPTY, "label" };
+
+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_VIDEO_PAUSE =
+    { NS_GOOGLE_MUC_MEDIA, "video-pause" };
+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" };
+
+const char AUTH_MECHANISM_GOOGLE_COOKIE[] = "X-GOOGLE-COOKIE";
+const char AUTH_MECHANISM_GOOGLE_TOKEN[] = "X-GOOGLE-TOKEN";
+const char AUTH_MECHANISM_OAUTH2[] = "X-OAUTH2";
+const char AUTH_MECHANISM_PLAIN[] = "PLAIN";
+
+}  // namespace buzz
diff --git a/talk/xmpp/constants.h b/talk/xmpp/constants.h
new file mode 100644
index 0000000..e01a798
--- /dev/null
+++ b/talk/xmpp/constants.h
@@ -0,0 +1,551 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_XMPP_CONSTANTS_H_
+#define TALK_XMPP_CONSTANTS_H_
+
+#include <string>
+#include "talk/xmllite/qname.h"
+#include "talk/xmpp/jid.h"
+
+namespace buzz {
+
+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[];
+
+extern const char STR_GET[];
+extern const char STR_SET[];
+extern const char STR_RESULT[];
+extern const char STR_ERROR[];
+
+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 char STR_FROM[];
+extern const char STR_TO[];
+extern const char STR_BOTH[];
+extern const char STR_REMOVE[];
+extern const char STR_TRUE[];
+
+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 char STR_VOICEMAIL[];
+extern const char STR_OUTGOINGVOICEMAIL[];
+#endif
+
+extern const char STR_UNAVAILABLE[];
+
+extern const char NS_PING[];
+extern const StaticQName QN_PING;
+
+extern const char NS_MUC_UNIQUE[];
+extern const StaticQName QN_MUC_UNIQUE_QUERY;
+extern const StaticQName QN_HANGOUT_ID;
+
+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 char STR_MUC_ROOM_FEATURE_HANGOUT[];
+extern const char STR_MUC_ROOM_FEATURE_HANGOUT_LITE[];
+extern const char STR_MUC_ROOM_FEATURE_BROADCAST[];
+extern const char STR_MUC_ROOM_FEATURE_MULTI_USER_VC[];
+
+extern const StaticQName QN_STREAM_STREAM;
+extern const StaticQName QN_STREAM_FEATURES;
+extern const StaticQName QN_STREAM_ERROR;
+
+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 StaticQName QN_TLS_STARTTLS;
+extern const StaticQName QN_TLS_REQUIRED;
+extern const StaticQName QN_TLS_PROCEED;
+extern const StaticQName QN_TLS_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;
+
+// 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 StaticQName QN_GOOGLE_AUTH_SERVICE;
+
+extern const StaticQName QN_DIALBACK_RESULT;
+extern const StaticQName QN_DIALBACK_VERIFY;
+
+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 StaticQName QN_BIND_BIND;
+extern const StaticQName QN_BIND_RESOURCE;
+extern const StaticQName QN_BIND_JID;
+
+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 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 StaticQName QN_SESSION_SESSION;
+
+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 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 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 StaticQName QN_USER_PROFILE_QUERY;
+extern const StaticQName QN_USER_PROFILE_URL;
+
+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 StaticQName QN_XML_LANG;
+
+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 StaticQName QN_XMLNS_CLIENT;
+extern const StaticQName QN_XMLNS_SERVER;
+extern const StaticQName QN_XMLNS_STREAM;
+
+// Presence
+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[];
+
+extern const char NS_GOOGLE_PSTN_CONFERENCE[];
+extern const StaticQName QN_GOOGLE_PSTN_CONFERENCE_STATUS;
+extern const StaticQName QN_ATTR_STATUS;
+
+// Presence connection status
+extern const char STR_PSTN_CONFERENCE_STATUS_CONNECTING[];
+extern const char STR_PSTN_CONFERENCE_STATUS_CONNECTED[];
+extern const char STR_PSTN_CONFERENCE_STATUS_HANGUP[];
+
+// Subscription
+extern const char STR_SUBSCRIBE[];
+extern const char STR_SUBSCRIBED[];
+extern const char STR_UNSUBSCRIBE[];
+extern const char STR_UNSUBSCRIBED[];
+
+// Google Invite
+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;
+
+// Kick
+extern const char NS_GOOGLE_MUC_ADMIN[];
+extern const StaticQName QN_GOOGLE_MUC_ADMIN_QUERY;
+extern const StaticQName QN_GOOGLE_MUC_ADMIN_QUERY_ITEM;
+extern const StaticQName QN_GOOGLE_MUC_ADMIN_QUERY_ITEM_REASON;
+
+// PubSub: http://xmpp.org/extensions/xep-0060.html
+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 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 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 StaticQName QN_CATEGORY;
+extern const StaticQName QN_VAR;
+extern const char NS_DISCO_INFO[];
+extern const char NS_DISCO_ITEMS[];
+
+extern const StaticQName QN_DISCO_INFO_QUERY;
+extern const StaticQName QN_DISCO_IDENTITY;
+extern const StaticQName QN_DISCO_FEATURE;
+
+extern const StaticQName QN_DISCO_ITEMS_QUERY;
+extern const StaticQName QN_DISCO_ITEM;
+
+// JEP 0020
+extern const char NS_FEATURE[];
+extern const StaticQName QN_FEATURE_FEATURE;
+
+// JEP 0004
+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 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 StaticQName QN_CLIENT_VERSION;
+extern const StaticQName QN_LOCALE;
+extern const char STR_AFFILIATION_NONE[];
+extern const char STR_ROLE_PARTICIPANT[];
+
+extern const char NS_GOOGLE_SESSION[];
+extern const StaticQName QN_GOOGLE_USER_ID;
+extern const StaticQName QN_GOOGLE_CIRCLE_ID;
+extern const StaticQName QN_GOOGLE_SESSION_BLOCKED;
+extern const StaticQName QN_GOOGLE_SESSION_BLOCKING;
+
+extern const char NS_MUC_OWNER[];
+extern const StaticQName QN_MUC_OWNER_QUERY;
+
+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;
+extern const StaticQName QN_MUC_USER_REASON;
+extern const StaticQName QN_MUC_USER_ABUSE_VIOLATION;
+
+// JEP 0055 - Jabber Search
+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;
+extern const StaticQName QN_SEARCH_HANGOUT_ID;
+extern const StaticQName QN_SEARCH_EXTERNAL_ID;
+
+// JEP 0115
+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 char kNSVCard[];
+extern const StaticQName kQnVCardX;
+extern const StaticQName kQnVCardPhoto;
+
+// JEP 0172 User Nickname
+extern const char NS_NICKNAME[];
+extern const StaticQName QN_NICKNAME;
+
+// JEP 0085 chat state
+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 char kNSDelay[];
+extern const StaticQName kQnDelayX;
+extern const StaticQName kQnStamp;
+
+// Google time stamping (higher resolution)
+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 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 StaticQName QN_MUC_USER_INVITE;
+
+// Multiway audio/video.
+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 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_VIDEO_PAUSE;
+extern const StaticQName QN_GOOGLE_MUC_RECORDING;
+extern const StaticQName QN_GOOGLE_MUC_MEDIA_BLOCK;
+extern const StaticQName QN_STATE_ATTR;
+
+
+extern const char AUTH_MECHANISM_GOOGLE_COOKIE[];
+extern const char AUTH_MECHANISM_GOOGLE_TOKEN[];
+extern const char AUTH_MECHANISM_OAUTH2[];
+extern const char AUTH_MECHANISM_PLAIN[];
+
+}  // namespace buzz
+
+#endif  // TALK_XMPP_CONSTANTS_H_
diff --git a/talk/xmpp/discoitemsquerytask.cc b/talk/xmpp/discoitemsquerytask.cc
new file mode 100644
index 0000000..7cdee2c
--- /dev/null
+++ b/talk/xmpp/discoitemsquerytask.cc
@@ -0,0 +1,79 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/scoped_ptr.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/discoitemsquerytask.h"
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+
+DiscoItemsQueryTask::DiscoItemsQueryTask(XmppTaskParentInterface* parent,
+                                         const Jid& to,
+                                         const std::string& node)
+    : IqTask(parent, STR_GET, to, MakeRequest(node)) {
+}
+
+XmlElement* DiscoItemsQueryTask::MakeRequest(const std::string& node) {
+  XmlElement* element = new XmlElement(QN_DISCO_ITEMS_QUERY, true);
+  if (!node.empty()) {
+    element->AddAttr(QN_NODE, node);
+  }
+  return element;
+}
+
+void DiscoItemsQueryTask::HandleResult(const XmlElement* stanza) {
+  const XmlElement* query = stanza->FirstNamed(QN_DISCO_ITEMS_QUERY);
+  if (query) {
+    std::vector<DiscoItem> items;
+    for (const buzz::XmlChild* child = query->FirstChild(); child;
+         child = child->NextChild()) {
+      DiscoItem item;
+      const buzz::XmlElement* child_element = child->AsElement();
+      if (ParseItem(child_element, &item)) {
+        items.push_back(item);
+      }
+    }
+    SignalResult(items);
+  } else {
+    SignalError(this, NULL);
+  }
+}
+
+bool DiscoItemsQueryTask::ParseItem(const XmlElement* element,
+                                    DiscoItem* item) {
+  if (element->HasAttr(QN_JID)) {
+    return false;
+  }
+
+  item->jid = element->Attr(QN_JID);
+  item->name = element->Attr(QN_NAME);
+  item->node = element->Attr(QN_NODE);
+  return true;
+}
+
+}  // namespace buzz
diff --git a/talk/xmpp/discoitemsquerytask.h b/talk/xmpp/discoitemsquerytask.h
new file mode 100644
index 0000000..409fc90
--- /dev/null
+++ b/talk/xmpp/discoitemsquerytask.h
@@ -0,0 +1,82 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Fires a disco items query, such as the following example:
+//
+//      <iq type='get'
+//          from='foo@gmail.com/asdf'
+//          to='bar@google.com'
+//          id='1234'>
+//          <query xmlns=' http://jabber.org/protocol/disco#items'
+//                 node='blah '/>
+//      </iq>
+//
+// Sample response:
+//
+//      <iq type='result'
+//          from=' hendriks@google.com'
+//          to='rsturgell@google.com/asdf'
+//          id='1234'>
+//          <query xmlns=' http://jabber.org/protocol/disco#items '
+//                 node='blah'>
+//                 <item something='somethingelse'/>
+//          </query>
+//      </iq>
+
+
+#ifndef TALK_XMPP_DISCOITEMSQUERYTASK_H_
+#define TALK_XMPP_DISCOITEMSQUERYTASK_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/xmpp/iqtask.h"
+
+namespace buzz {
+
+struct DiscoItem {
+  std::string jid;
+  std::string node;
+  std::string name;
+};
+
+class DiscoItemsQueryTask : public IqTask {
+ public:
+  DiscoItemsQueryTask(XmppTaskParentInterface* parent,
+                      const Jid& to, const std::string& node);
+
+  sigslot::signal1<std::vector<DiscoItem> > SignalResult;
+
+ private:
+  static XmlElement* MakeRequest(const std::string& node);
+  virtual void HandleResult(const XmlElement* result);
+  static bool ParseItem(const XmlElement* element, DiscoItem* item);
+};
+
+}  // namespace buzz
+
+#endif  // TALK_XMPP_DISCOITEMSQUERYTASK_H_
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
new file mode 100644
index 0000000..edbf4dd
--- /dev/null
+++ b/talk/xmpp/hangoutpubsubclient.cc
@@ -0,0 +1,643 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/xmpp/hangoutpubsubclient.h"
+
+#include "talk/base/logging.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+
+
+// Gives a high-level API for MUC call PubSub needs such as
+// presenter state, recording state, mute state, and remote mute.
+
+namespace buzz {
+
+namespace {
+const 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
+
+
+// Knows how to handle specific states and XML.
+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> {
+  virtual XmlElement* Write(const QName& state_name, const bool& state) {
+    if (!state) {
+      return NULL;
+    }
+
+    return new XmlElement(state_name, true);
+  }
+
+  virtual bool Parse(const XmlElement* state_elem) {
+    return state_elem != NULL;
+  }
+};
+
+// Adapts PubSubClient to be specifically suited for pub sub call
+// states.  Signals state changes and keeps track of keys, which are
+// normally nicks.
+// TODO: Expose this as a generally useful class, not just
+// private to hangouts.
+template <typename C>
+class PubSubStateClient : public sigslot::has_slots<> {
+ public:
+  // Gets ownership of the serializers, but not the client.
+  PubSubStateClient(const std::string& publisher_nick,
+                    PubSubClient* client,
+                    const QName& state_name,
+                    C default_state,
+                    PubSubStateKeySerializer* key_serializer,
+                    PubSubStateSerializer<C>* state_serializer)
+      : publisher_nick_(publisher_nick),
+        client_(client),
+        state_name_(state_name),
+        default_state_(default_state) {
+    key_serializer_.reset(key_serializer);
+    state_serializer_.reset(state_serializer);
+    client_->SignalItems.connect(
+        this, &PubSubStateClient<C>::OnItems);
+    client_->SignalPublishResult.connect(
+        this, &PubSubStateClient<C>::OnPublishResult);
+    client_->SignalPublishError.connect(
+        this, &PubSubStateClient<C>::OnPublishError);
+    client_->SignalRetractResult.connect(
+        this, &PubSubStateClient<C>::OnRetractResult);
+    client_->SignalRetractError.connect(
+        this, &PubSubStateClient<C>::OnRetractError);
+  }
+
+  virtual ~PubSubStateClient() {}
+
+  virtual void Publish(const std::string& published_nick,
+                       const C& state,
+                       std::string* task_id_out) {
+    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 = state_serializer_->Write(state_name_, state);
+      state_elem->AddAttr(QN_NICK, published_nick);
+      client_->PublishItem(itemid, state_elem, task_id_out);
+    }
+  };
+
+  sigslot::signal1<const PubSubStateChange<C>&> SignalStateChange;
+  // Signal (task_id, item).  item is NULL for retract.
+  sigslot::signal2<const std::string&,
+                   const XmlElement*> SignalPublishResult;
+  // Signal (task_id, item, error stanza).  item is NULL for retract.
+  sigslot::signal3<const std::string&,
+                   const XmlElement*,
+                   const XmlElement*> SignalPublishError;
+
+ protected:
+  // return false if retracted item (no 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;
+    }
+
+    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;
+  };
+
+  virtual bool StatesEqual(C state1, C state2) {
+    return state1 == state2;
+  }
+
+  PubSubClient* client() { return client_; }
+
+ private:
+  void OnItems(PubSubClient* pub_sub_client,
+               const std::vector<PubSubItem>& items) {
+    for (std::vector<PubSubItem>::const_iterator item = items.begin();
+         item != items.end(); ++item) {
+      OnItem(*item);
+    }
+  }
+
+  void OnItem(const PubSubItem& item) {
+    const std::string& itemid = item.itemid;
+    StateItemInfo info;
+    C new_state;
+
+    bool retracted = !ParseStateItem(item, &info, &new_state);
+    if (retracted) {
+      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 {
+        info = info_by_itemid_[itemid];
+        info_by_itemid_.erase(itemid);
+        new_state = default_state_;
+      }
+    } else {
+      // 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());
+    C old_state = has_old_state ? state_by_key_[key] : default_state_;
+    if ((retracted && !has_old_state) || StatesEqual(new_state, old_state)) {
+      // Nothing change, so don't bother signalling.
+      return;
+    }
+
+    if (retracted || StatesEqual(new_state, default_state_)) {
+      // We treat a default state similar to a retract.
+      state_by_key_.erase(key);
+    } else {
+      state_by_key_[key] = new_state;
+    }
+
+    PubSubStateChange<C> change;
+    change.publisher_nick = info.publisher_nick;
+    change.published_nick = info.published_nick;
+    change.old_state = old_state;
+    change.new_state = new_state;
+    SignalStateChange(change);
+ }
+
+  void OnPublishResult(PubSubClient* pub_sub_client,
+                       const std::string& task_id,
+                       const XmlElement* item) {
+    SignalPublishResult(task_id, item);
+  }
+
+  void OnPublishError(PubSubClient* pub_sub_client,
+                      const std::string& task_id,
+                      const buzz::XmlElement* item,
+                      const buzz::XmlElement* stanza) {
+    SignalPublishError(task_id, item, stanza);
+  }
+
+  void OnRetractResult(PubSubClient* pub_sub_client,
+                       const std::string& task_id) {
+    // There's no point in differentiating between publish and retract
+    // errors, so we simplify by making them both signal a publish
+    // result.
+    const XmlElement* item = NULL;
+    SignalPublishResult(task_id, item);
+  }
+
+  void OnRetractError(PubSubClient* pub_sub_client,
+                      const std::string& task_id,
+                      const buzz::XmlElement* stanza) {
+    // There's no point in differentiating between publish and retract
+    // errors, so we simplify by making them both signal a publish
+    // error.
+    const XmlElement* item = NULL;
+    SignalPublishError(task_id, item, stanza);
+  }
+
+  std::string publisher_nick_;
+  PubSubClient* client_;
+  const QName state_name_;
+  C default_state_;
+  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 => StateItemInfo
+  std::map<std::string, StateItemInfo> info_by_itemid_;
+};
+
+class PresenterStateClient : public PubSubStateClient<bool> {
+ public:
+  PresenterStateClient(const std::string& publisher_nick,
+                       PubSubClient* client,
+                       const QName& state_name,
+                       bool default_state)
+      : PubSubStateClient<bool>(
+          publisher_nick, client, state_name, default_state,
+          new PublishedNickKeySerializer(), NULL) {
+  }
+
+  virtual void Publish(const std::string& published_nick,
+                       const bool& state,
+                       std::string* task_id_out) {
+    XmlElement* presenter_elem = new XmlElement(QN_PRESENTER_PRESENTER, true);
+    presenter_elem->AddAttr(QN_NICK, published_nick);
+
+    XmlElement* presentation_item_elem =
+        new XmlElement(QN_PRESENTER_PRESENTATION_ITEM, false);
+    const std::string& presentation_type = state ? kPresenting : kNotPresenting;
+    presentation_item_elem->AddAttr(
+        QN_PRESENTER_PRESENTATION_TYPE, presentation_type);
+
+    // The Presenter state is kind of dumb in that it doesn't always use
+    // retracts.  It relies on setting the "type" to a special value.
+    std::string itemid = published_nick;
+    std::vector<XmlElement*> children;
+    children.push_back(presenter_elem);
+    children.push_back(presentation_item_elem);
+    client()->PublishItem(itemid, children, task_id_out);
+  }
+
+ protected:
+  virtual bool 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 =
+        item.elem->FirstNamed(QN_PRESENTER_PRESENTATION_ITEM);
+    if (presentation_item_elem == NULL || presenter_elem == NULL) {
+      return false;
+    }
+
+    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,
+                                         const Jid& mucjid,
+                                         const std::string& nick)
+    : mucjid_(mucjid),
+      nick_(nick) {
+  presenter_client_.reset(new PubSubClient(parent, mucjid, NS_PRESENTER));
+  presenter_client_->SignalRequestError.connect(
+      this, &HangoutPubSubClient::OnPresenterRequestError);
+
+  media_client_.reset(new PubSubClient(parent, mucjid, NS_GOOGLE_MUC_MEDIA));
+  media_client_->SignalRequestError.connect(
+      this, &HangoutPubSubClient::OnMediaRequestError);
+
+  presenter_state_client_.reset(new PresenterStateClient(
+      nick_, presenter_client_.get(), QN_PRESENTER_PRESENTER, false));
+  presenter_state_client_->SignalStateChange.connect(
+      this, &HangoutPubSubClient::OnPresenterStateChange);
+  presenter_state_client_->SignalPublishResult.connect(
+      this, &HangoutPubSubClient::OnPresenterPublishResult);
+  presenter_state_client_->SignalPublishError.connect(
+      this, &HangoutPubSubClient::OnPresenterPublishError);
+
+  audio_mute_state_client_.reset(new PubSubStateClient<bool>(
+      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);
+  audio_mute_state_client_->SignalPublishResult.connect(
+      this, &HangoutPubSubClient::OnAudioMutePublishResult);
+  audio_mute_state_client_->SignalPublishError.connect(
+      this, &HangoutPubSubClient::OnAudioMutePublishError);
+
+  video_mute_state_client_.reset(new PubSubStateClient<bool>(
+      nick_, media_client_.get(), QN_GOOGLE_MUC_VIDEO_MUTE, false,
+      new PublishedNickKeySerializer(), new BoolStateSerializer()));
+  // Can't just repeat because we need to watch for remote mutes.
+  video_mute_state_client_->SignalStateChange.connect(
+      this, &HangoutPubSubClient::OnVideoMuteStateChange);
+  video_mute_state_client_->SignalPublishResult.connect(
+      this, &HangoutPubSubClient::OnVideoMutePublishResult);
+  video_mute_state_client_->SignalPublishError.connect(
+      this, &HangoutPubSubClient::OnVideoMutePublishError);
+
+  video_pause_state_client_.reset(new PubSubStateClient<bool>(
+      nick_, media_client_.get(), QN_GOOGLE_MUC_VIDEO_PAUSE, false,
+      new PublishedNickKeySerializer(), new BoolStateSerializer()));
+  video_pause_state_client_->SignalStateChange.connect(
+      this, &HangoutPubSubClient::OnVideoPauseStateChange);
+  video_pause_state_client_->SignalPublishResult.connect(
+      this, &HangoutPubSubClient::OnVideoPausePublishResult);
+  video_pause_state_client_->SignalPublishError.connect(
+      this, &HangoutPubSubClient::OnVideoPausePublishError);
+
+  recording_state_client_.reset(new PubSubStateClient<bool>(
+      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() {
+}
+
+void HangoutPubSubClient::RequestAll() {
+  presenter_client_->RequestItems();
+  media_client_->RequestItems();
+}
+
+void HangoutPubSubClient::OnPresenterRequestError(
+    PubSubClient* client, const XmlElement* stanza) {
+  SignalRequestError(client->node(), stanza);
+}
+
+void HangoutPubSubClient::OnMediaRequestError(
+    PubSubClient* client, const XmlElement* stanza) {
+  SignalRequestError(client->node(), stanza);
+}
+
+void HangoutPubSubClient::PublishPresenterState(
+    bool presenting, std::string* task_id_out) {
+  presenter_state_client_->Publish(nick_, presenting, task_id_out);
+}
+
+void HangoutPubSubClient::PublishAudioMuteState(
+    bool muted, std::string* task_id_out) {
+  audio_mute_state_client_->Publish(nick_, muted, task_id_out);
+}
+
+void HangoutPubSubClient::PublishVideoMuteState(
+    bool muted, std::string* task_id_out) {
+  video_mute_state_client_->Publish(nick_, muted, task_id_out);
+}
+
+void HangoutPubSubClient::PublishVideoPauseState(
+    bool paused, std::string* task_id_out) {
+  video_pause_state_client_->Publish(nick_, paused, task_id_out);
+}
+
+void HangoutPubSubClient::PublishRecordingState(
+    bool recording, std::string* task_id_out) {
+  recording_state_client_->Publish(nick_, recording, task_id_out);
+}
+
+// Remote mute is accomplished by setting another client's mute state.
+void HangoutPubSubClient::RemoteMute(
+    const std::string& mutee_nick, std::string* task_id_out) {
+  audio_mute_state_client_->Publish(mutee_nick, true, task_id_out);
+}
+
+// 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) {
+  SignalPresenterStateChange(
+      change.published_nick, change.old_state, change.new_state);
+}
+
+void HangoutPubSubClient::OnPresenterPublishResult(
+    const std::string& task_id, const XmlElement* item) {
+  SignalPublishPresenterResult(task_id);
+}
+
+void HangoutPubSubClient::OnPresenterPublishError(
+    const std::string& task_id, const XmlElement* item,
+    const XmlElement* stanza) {
+  SignalPublishPresenterError(task_id, stanza);
+}
+
+// Since a remote mute is accomplished by another client setting our
+// mute state, if our state changes to muted, we should mute ourselves.
+// Note that remote un-muting is disallowed by the RoomServer.
+void HangoutPubSubClient::OnAudioMuteStateChange(
+    const PubSubStateChange<bool>& change) {
+  bool was_muted = change.old_state;
+  bool is_muted = change.new_state;
+  bool remote_action = (!change.publisher_nick.empty() &&
+                        (change.publisher_nick != change.published_nick));
+  if (remote_action) {
+    const std::string& mutee_nick = change.published_nick;
+    const std::string& muter_nick = change.publisher_nick;
+    if (!is_muted) {
+      // The server should prevent remote un-mute.
+      LOG(LS_WARNING) << muter_nick << " remote unmuted " << mutee_nick;
+      return;
+    }
+    bool should_mute_locally = (mutee_nick == nick_);
+    SignalRemoteMute(mutee_nick, muter_nick, should_mute_locally);
+  } else {
+    SignalAudioMuteStateChange(change.published_nick, was_muted, is_muted);
+  }
+}
+
+const std::string GetAudioMuteNickFromItem(const XmlElement* item) {
+  if (item != NULL) {
+    const XmlElement* audio_mute_state =
+        item->FirstNamed(QN_GOOGLE_MUC_AUDIO_MUTE);
+    if (audio_mute_state != NULL) {
+      return audio_mute_state->Attr(QN_NICK);
+    }
+  }
+  return 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(
+    const std::string& task_id, const XmlElement* item) {
+  const std::string& mutee_nick = GetAudioMuteNickFromItem(item);
+  if (mutee_nick != nick_) {
+    SignalRemoteMuteResult(task_id, mutee_nick);
+  } else {
+    SignalPublishAudioMuteResult(task_id);
+  }
+}
+
+void HangoutPubSubClient::OnAudioMutePublishError(
+    const std::string& task_id, const XmlElement* item,
+    const XmlElement* stanza) {
+  const std::string& mutee_nick = GetAudioMuteNickFromItem(item);
+  if (mutee_nick != nick_) {
+    SignalRemoteMuteError(task_id, mutee_nick, stanza);
+  } else {
+    SignalPublishAudioMuteError(task_id, stanza);
+  }
+}
+
+void HangoutPubSubClient::OnVideoMuteStateChange(
+    const PubSubStateChange<bool>& change) {
+  SignalVideoMuteStateChange(
+      change.published_nick, change.old_state, change.new_state);
+}
+
+void HangoutPubSubClient::OnVideoMutePublishResult(
+    const std::string& task_id, const XmlElement* item) {
+  SignalPublishVideoMuteResult(task_id);
+}
+
+void HangoutPubSubClient::OnVideoMutePublishError(
+    const std::string& task_id, const XmlElement* item,
+    const XmlElement* stanza) {
+  SignalPublishVideoMuteError(task_id, stanza);
+}
+
+void HangoutPubSubClient::OnVideoPauseStateChange(
+    const PubSubStateChange<bool>& change) {
+  SignalVideoPauseStateChange(
+      change.published_nick, change.old_state, change.new_state);
+}
+
+void HangoutPubSubClient::OnVideoPausePublishResult(
+    const std::string& task_id, const XmlElement* item) {
+  SignalPublishVideoPauseResult(task_id);
+}
+
+void HangoutPubSubClient::OnVideoPausePublishError(
+    const std::string& task_id, const XmlElement* item,
+    const XmlElement* stanza) {
+  SignalPublishVideoPauseError(task_id, stanza);
+}
+
+void HangoutPubSubClient::OnRecordingStateChange(
+    const PubSubStateChange<bool>& change) {
+  SignalRecordingStateChange(
+      change.published_nick, change.old_state, change.new_state);
+}
+
+void HangoutPubSubClient::OnRecordingPublishResult(
+    const std::string& task_id, const XmlElement* item) {
+  SignalPublishRecordingResult(task_id);
+}
+
+void HangoutPubSubClient::OnRecordingPublishError(
+    const std::string& task_id, const XmlElement* item,
+    const XmlElement* stanza) {
+  SignalPublishRecordingError(task_id, stanza);
+}
+
+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
new file mode 100644
index 0000000..a9986db
--- /dev/null
+++ b/talk/xmpp/hangoutpubsubclient.h
@@ -0,0 +1,218 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_XMPP_HANGOUTPUBSUBCLIENT_H_
+#define TALK_XMPP_HANGOUTPUBSUBCLIENT_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/sigslotrepeater.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/pubsubclient.h"
+
+// Gives a high-level API for MUC call PubSub needs such as
+// presenter state, recording state, mute state, and remote mute.
+
+namespace buzz {
+
+class Jid;
+class XmlElement;
+class XmppTaskParentInterface;
+
+// 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.  Currently documented
+// at https://docs.google.com/a/google.com/document/d/
+// 1QyHu_ufyVdf0VICdfc_DtJbrOdrdIUm4eM73RZqnivI/edit?hl=en_US
+template <typename C>
+struct PubSubStateChange {
+  // 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;
+};
+
+template <typename C> class PubSubStateClient;
+
+// A client tied to a specific MUC jid and local nick.  Provides ways
+// to get updates and publish state and events.  Must call
+// RequestAll() to start getting updates.
+class HangoutPubSubClient : public sigslot::has_slots<> {
+ public:
+  HangoutPubSubClient(XmppTaskParentInterface* parent,
+                      const Jid& mucjid,
+                      const std::string& nick);
+  ~HangoutPubSubClient();
+  const Jid& mucjid() const { return mucjid_; }
+  const std::string& nick() const { return nick_; }
+
+  // Requests all of the different states and subscribes for updates.
+  // Responses and updates will be signalled via the various signals.
+  void RequestAll();
+  // Signal (nick, was_presenting, is_presenting)
+  sigslot::signal3<const std::string&, bool, bool> SignalPresenterStateChange;
+  // Signal (nick, was_muted, is_muted)
+  sigslot::signal3<const std::string&, bool, bool> SignalAudioMuteStateChange;
+  // Signal (nick, was_muted, is_muted)
+  sigslot::signal3<const std::string&, bool, bool> SignalVideoMuteStateChange;
+  // Signal (nick, was_paused, is_paused)
+  sigslot::signal3<const std::string&, bool, bool> SignalVideoPauseStateChange;
+  // Signal (nick, was_recording, is_recording)
+  sigslot::signal3<const std::string&, bool, bool> SignalRecordingStateChange;
+  // 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;
+
+  // On each of these, provide a task_id_out to get the task_id, which
+  // can be correlated to the error and result signals.
+  void PublishPresenterState(
+      bool presenting, std::string* task_id_out = NULL);
+  void PublishAudioMuteState(
+      bool muted, std::string* task_id_out = NULL);
+  void PublishVideoMuteState(
+      bool muted, std::string* task_id_out = NULL);
+  void PublishVideoPauseState(
+      bool paused, std::string* task_id_out = NULL);
+  void PublishRecordingState(
+      bool recording, std::string* task_id_out = NULL);
+  void RemoteMute(
+      const std::string& mutee_nick, std::string* task_id_out = NULL);
+  void BlockMedia(
+      const std::string& blockee_nick, std::string* task_id_out = NULL);
+
+  // Signal task_id
+  sigslot::signal1<const std::string&> SignalPublishAudioMuteResult;
+  sigslot::signal1<const std::string&> SignalPublishVideoMuteResult;
+  sigslot::signal1<const std::string&> SignalPublishVideoPauseResult;
+  sigslot::signal1<const std::string&> SignalPublishPresenterResult;
+  sigslot::signal1<const std::string&> SignalPublishRecordingResult;
+  // Signal (task_id, mutee_nick)
+  sigslot::signal2<const std::string&,
+                   const std::string&> SignalRemoteMuteResult;
+  // Signal (task_id, blockee_nick)
+  sigslot::signal2<const std::string&,
+                   const std::string&> SignalMediaBlockResult;
+
+  // Signal (task_id, error stanza)
+  sigslot::signal2<const std::string&,
+                   const XmlElement*> SignalPublishAudioMuteError;
+  sigslot::signal2<const std::string&,
+                   const XmlElement*> SignalPublishVideoMuteError;
+  sigslot::signal2<const std::string&,
+                   const XmlElement*> SignalPublishVideoPauseError;
+  sigslot::signal2<const std::string&,
+                   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,
+                               const XmlElement* stanza);
+  void OnMediaRequestError(PubSubClient* client,
+                           const XmlElement* stanza);
+
+  void OnPresenterStateChange(const PubSubStateChange<bool>& change);
+  void OnPresenterPublishResult(const std::string& task_id,
+                               const XmlElement* item);
+  void OnPresenterPublishError(const std::string& task_id,
+                               const XmlElement* item,
+                               const XmlElement* stanza);
+  void OnAudioMuteStateChange(const PubSubStateChange<bool>& change);
+  void OnAudioMutePublishResult(const std::string& task_id,
+                               const XmlElement* item);
+  void OnAudioMutePublishError(const std::string& task_id,
+                               const XmlElement* item,
+                               const XmlElement* stanza);
+  void OnVideoMuteStateChange(const PubSubStateChange<bool>& change);
+  void OnVideoMutePublishResult(const std::string& task_id,
+                               const XmlElement* item);
+  void OnVideoMutePublishError(const std::string& task_id,
+                               const XmlElement* item,
+                               const XmlElement* stanza);
+  void OnVideoPauseStateChange(const PubSubStateChange<bool>& change);
+  void OnVideoPausePublishResult(const std::string& task_id,
+                               const XmlElement* item);
+  void OnVideoPausePublishError(const std::string& task_id,
+                               const XmlElement* item,
+                               const XmlElement* stanza);
+  void OnRecordingStateChange(const PubSubStateChange<bool>& change);
+  void OnRecordingPublishResult(const std::string& task_id,
+                               const XmlElement* item);
+  void OnRecordingPublishError(const std::string& task_id,
+                               const XmlElement* item,
+                               const XmlElement* stanza);
+  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_;
+  talk_base::scoped_ptr<PubSubClient> presenter_client_;
+  talk_base::scoped_ptr<PubSubStateClient<bool> > presenter_state_client_;
+  talk_base::scoped_ptr<PubSubStateClient<bool> > audio_mute_state_client_;
+  talk_base::scoped_ptr<PubSubStateClient<bool> > video_mute_state_client_;
+  talk_base::scoped_ptr<PubSubStateClient<bool> > video_pause_state_client_;
+  talk_base::scoped_ptr<PubSubStateClient<bool> > recording_state_client_;
+  talk_base::scoped_ptr<PubSubStateClient<bool> > media_block_state_client_;
+};
+
+}  // namespace buzz
+
+#endif  // TALK_XMPP_HANGOUTPUBSUBCLIENT_H_
diff --git a/talk/xmpp/hangoutpubsubclient_unittest.cc b/talk/xmpp/hangoutpubsubclient_unittest.cc
new file mode 100644
index 0000000..0ffb248
--- /dev/null
+++ b/talk/xmpp/hangoutpubsubclient_unittest.cc
@@ -0,0 +1,740 @@
+// 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_video_mute_error_count(0),
+      publish_video_pause_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 OnVideoMuteStateChange(
+      const std::string& nick, bool was_muted, bool is_muted) {
+    last_video_muted_nick = nick;
+    last_was_video_muted = was_muted;
+    last_is_video_muted = is_muted;
+  }
+
+  void OnVideoPauseStateChange(
+      const std::string& nick, bool was_paused, bool is_paused) {
+    last_video_paused_nick = nick;
+    last_was_video_paused = was_paused;
+    last_is_video_paused = is_paused;
+  }
+
+  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 OnPublishVideoMuteError(const std::string& task_id,
+                               const buzz::XmlElement* stanza) {
+    ++publish_video_mute_error_count;
+    error_task_id = task_id;
+  }
+
+  void OnPublishVideoPauseError(const std::string& task_id,
+                               const buzz::XmlElement* stanza) {
+    ++publish_video_pause_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_video_muted_nick;
+  bool last_is_video_muted;
+  bool last_was_video_muted;
+  std::string last_video_paused_nick;
+  bool last_is_video_paused;
+  bool last_was_video_paused;
+  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_video_mute_error_count;
+  int publish_video_pause_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->SignalVideoMuteStateChange.connect(
+        listener.get(), &TestHangoutPubSubListener::OnVideoMuteStateChange);
+    client->SignalVideoPauseStateChange.connect(
+        listener.get(), &TestHangoutPubSubListener::OnVideoPauseStateChange);
+    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->SignalPublishVideoMuteError.connect(
+        listener.get(), &TestHangoutPubSubListener::OnPublishVideoMuteError);
+    client->SignalPublishVideoPauseError.connect(
+        listener.get(), &TestHangoutPubSubListener::OnPublishVideoPauseError);
+    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='12344'>"
+      "        <presenter xmlns='google:presenter' nick='presenting-nick2'/>"
+      "        <pre:presentation-item xmlns:pre='google:presenter'"
+      "          pre:presentation-type='s'/>"
+      "      </item>"
+      "      <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>"
+      "    </items>"
+      "  </pubsub>"
+      "</iq>";
+
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(presenter_response));
+  EXPECT_EQ("presenting-nick", 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='video-mute:video-muted-nick'>"
+      "        <video-mute nick='video-muted-nick' xmlns='google:muc#media'/>"
+      "      </item>"
+      "      <item id='video-pause:video-paused-nick'>"
+      "        <video-pause nick='video-paused-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("video-muted-nick", listener->last_video_muted_nick);
+  EXPECT_FALSE(listener->last_was_video_muted);
+  EXPECT_TRUE(listener->last_is_video_muted);
+
+  EXPECT_EQ("video-paused-nick", listener->last_video_paused_nick);
+  EXPECT_FALSE(listener->last_was_video_paused);
+  EXPECT_TRUE(listener->last_is_video_paused);
+
+  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='12344'/>"
+      "    </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='video-mute:video-muted-nick'/>"
+      "      <retract id='video-pause:video-paused-nick'/>"
+      "      <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("video-paused-nick", listener->last_video_paused_nick);
+  EXPECT_TRUE(listener->last_was_video_paused);
+  EXPECT_FALSE(listener->last_is_video_paused);
+
+  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='video-pause:video-paused-nick2'>"
+      "        <video-pause nick='video-paused-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("video-paused-nick2", listener->last_video_paused_nick);
+  EXPECT_FALSE(listener->last_was_video_paused);
+  EXPECT_TRUE(listener->last_is_video_paused);
+
+  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\""
+              " 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->PublishVideoPauseState(true);
+  std::string expected_video_pause_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=\"video-pause:me\">"
+              "<video-pause xmlns=\"google:muc#media\" nick=\"me\"/>"
+            "</item>"
+          "</publish>"
+        "</pubsub>"
+      "</cli:iq>";
+
+  ASSERT_EQ(3U, xmpp_client->sent_stanzas().size());
+  EXPECT_EQ(expected_video_pause_iq, xmpp_client->sent_stanzas()[2]->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(4U, xmpp_client->sent_stanzas().size());
+  EXPECT_EQ(expected_recording_iq, xmpp_client->sent_stanzas()[3]->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(5U, xmpp_client->sent_stanzas().size());
+  EXPECT_EQ(expected_remote_mute_iq, xmpp_client->sent_stanzas()[4]->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\""
+              " nick=\"me\"/>"
+              "<pre:presentation-item"
+              " pre:presentation-type=\"o\" xmlns:pre=\"google:presenter\"/>"
+            "</item>"
+          "</publish>"
+        "</pubsub>"
+      "</cli:iq>";
+
+  ASSERT_EQ(6U, xmpp_client->sent_stanzas().size());
+  EXPECT_EQ(expected_presenter_retract_iq,
+            xmpp_client->sent_stanzas()[5]->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(7U, xmpp_client->sent_stanzas().size());
+  EXPECT_EQ(expected_audio_mute_retract_iq,
+            xmpp_client->sent_stanzas()[6]->Str());
+
+  client->PublishVideoPauseState(false);
+  std::string expected_video_pause_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=\"video-pause:me\"/>"
+          "</retract>"
+        "</pubsub>"
+      "</cli:iq>";
+
+  ASSERT_EQ(8U, xmpp_client->sent_stanzas().size());
+  EXPECT_EQ(expected_video_pause_retract_iq,
+            xmpp_client->sent_stanzas()[7]->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(9U, xmpp_client->sent_stanzas().size());
+  EXPECT_EQ(expected_media_block_iq, xmpp_client->sent_stanzas()[8]->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, TestPublishVideoPauseError) {
+  std::string result_iq =
+      "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'/>";
+
+  client->PublishVideoPauseState(true);
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+  EXPECT_EQ(1, listener->publish_video_pause_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/iqtask.cc b/talk/xmpp/iqtask.cc
new file mode 100644
index 0000000..f54f630
--- /dev/null
+++ b/talk/xmpp/iqtask.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 "talk/xmpp/iqtask.h"
+
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/constants.h"
+
+namespace buzz {
+
+static const int kDefaultIqTimeoutSecs = 15;
+
+IqTask::IqTask(XmppTaskParentInterface* parent,
+               const std::string& verb,
+               const buzz::Jid& to,
+               buzz::XmlElement* el)
+    : buzz::XmppTask(parent, buzz::XmppEngine::HL_SINGLE),
+      to_(to),
+      stanza_(MakeIq(verb, to_, task_id())) {
+  stanza_->AddElement(el);
+  set_timeout_seconds(kDefaultIqTimeoutSecs);
+}
+
+int IqTask::ProcessStart() {
+  buzz::XmppReturnStatus ret = SendStanza(stanza_.get());
+  // TODO: HandleError(NULL) if SendStanza fails?
+  return (ret == buzz::XMPP_RETURN_OK) ? STATE_RESPONSE : STATE_ERROR;
+}
+
+bool IqTask::HandleStanza(const buzz::XmlElement* stanza) {
+  if (!MatchResponseIq(stanza, to_, task_id()))
+    return false;
+
+  if (stanza->Attr(buzz::QN_TYPE) != buzz::STR_RESULT &&
+      stanza->Attr(buzz::QN_TYPE) != buzz::STR_ERROR) {
+    return false;
+  }
+
+  QueueStanza(stanza);
+  return true;
+}
+
+int IqTask::ProcessResponse() {
+  const buzz::XmlElement* stanza = NextStanza();
+  if (stanza == NULL)
+    return STATE_BLOCKED;
+
+  bool success = (stanza->Attr(buzz::QN_TYPE) == buzz::STR_RESULT);
+  if (success) {
+    HandleResult(stanza);
+  } else {
+    SignalError(this, stanza->FirstNamed(QN_ERROR));
+  }
+  return STATE_DONE;
+}
+
+int IqTask::OnTimeout() {
+  SignalError(this, NULL);
+  return XmppTask::OnTimeout();
+}
+
+}  // namespace buzz
diff --git a/talk/xmpp/iqtask.h b/talk/xmpp/iqtask.h
new file mode 100644
index 0000000..2228e6f
--- /dev/null
+++ b/talk/xmpp/iqtask.h
@@ -0,0 +1,65 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_XMPP_IQTASK_H_
+#define TALK_XMPP_IQTASK_H_
+
+#include <string>
+
+#include "talk/xmpp/xmpptask.h"
+#include "talk/xmpp/xmppengine.h"
+
+namespace buzz {
+
+class IqTask : public XmppTask {
+ public:
+  IqTask(XmppTaskParentInterface* parent,
+         const std::string& verb, const Jid& to,
+         XmlElement* el);
+  virtual ~IqTask() {}
+
+  const XmlElement* stanza() const { return stanza_.get(); }
+
+  sigslot::signal2<IqTask*,
+                   const XmlElement*> SignalError;
+
+ protected:
+  virtual void HandleResult(const XmlElement* element) = 0;
+
+ private:
+  virtual int ProcessStart();
+  virtual bool HandleStanza(const XmlElement* stanza);
+  virtual int ProcessResponse();
+  virtual int OnTimeout();
+
+  Jid to_;
+  talk_base::scoped_ptr<XmlElement> stanza_;
+};
+
+}  // namespace buzz
+
+#endif  // TALK_XMPP_IQTASK_H_
diff --git a/talk/xmpp/jid.cc b/talk/xmpp/jid.cc
new file mode 100644
index 0000000..66d5cde
--- /dev/null
+++ b/talk/xmpp/jid.cc
@@ -0,0 +1,396 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/xmpp/jid.h"
+
+#include <ctype.h>
+
+#include <algorithm>
+#include <string>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/xmpp/constants.h"
+
+namespace buzz {
+
+Jid::Jid() {
+}
+
+Jid::Jid(const std::string& jid_string) {
+  if (jid_string.empty())
+    return;
+
+  // First find the slash and slice off that part
+  size_t slash = jid_string.find('/');
+  resource_name_ = (slash == std::string::npos ? STR_EMPTY :
+                    jid_string.substr(slash + 1));
+
+  // Now look for the node
+  size_t at = jid_string.find('@');
+  size_t domain_begin;
+  if (at < slash && at != std::string::npos) {
+    node_name_ = jid_string.substr(0, at);
+    domain_begin = at + 1;
+  } else {
+    domain_begin = 0;
+  }
+
+  // Now take what is left as the domain
+  size_t domain_length = (slash == std::string::npos) ?
+      (jid_string.length() - domain_begin) : (slash - domain_begin);
+  domain_name_ = jid_string.substr(domain_begin, domain_length);
+
+  ValidateOrReset();
+}
+
+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;
+  bool valid_domain;
+  bool 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) {
+    node_name_.clear();
+    domain_name_.clear();
+    resource_name_.clear();
+  }
+}
+
+std::string Jid::Str() const {
+  if (!IsValid())
+    return STR_EMPTY;
+
+  std::string ret;
+
+  if (!node_name_.empty())
+    ret = node_name_ + "@";
+
+  ASSERT(domain_name_ != STR_EMPTY);
+  ret += domain_name_;
+
+  if (!resource_name_.empty())
+    ret += "/" + resource_name_;
+
+  return ret;
+}
+
+Jid::~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() && resource_name_.empty();
+}
+
+bool Jid::IsFull() const {
+  return IsValid() && !resource_name_.empty();
+}
+
+Jid Jid::BareJid() const {
+  if (!IsValid())
+    return Jid();
+  if (!IsFull())
+    return *this;
+  return Jid(node_name_, domain_name_, STR_EMPTY);
+}
+
+bool Jid::BareEquals(const Jid& other) const {
+  return other.node_name_ == node_name_ &&
+      other.domain_name_ == domain_name_;
+}
+
+void Jid::CopyFrom(const Jid& jid) {
+  this->node_name_ = jid.node_name_;
+  this->domain_name_ = jid.domain_name_;
+  this->resource_name_ = jid.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 {
+  int compare_result;
+  compare_result = node_name_.compare(other.node_name_);
+  if (0 != compare_result)
+    return compare_result;
+  compare_result = domain_name_.compare(other.domain_name_);
+  if (0 != compare_result)
+    return compare_result;
+  compare_result = resource_name_.compare(other.resource_name_);
+  return compare_result;
+}
+
+// --- JID parsing code: ---
+
+// Checks and normalizes the node part of a JID.
+std::string Jid::PrepNode(const std::string& node, bool* valid) {
+  *valid = false;
+  std::string result;
+
+  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);
+    }
+    else {
+      // TODO: implement the correct stringprep protocol for these
+      result += tolower(ch);
+    }
+    if (!char_valid) {
+      return STR_EMPTY;
+    }
+  }
+
+  if (result.length() > 1023) {
+    return STR_EMPTY;
+  }
+  *valid = true;
+  return result;
+}
+
+
+// Returns the appropriate mapping for an ASCII character in a node.
+char Jid::PrepNodeAscii(char ch, bool* valid) {
+  *valid = true;
+  switch (ch) {
+    case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
+    case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
+    case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
+    case 'V': case 'W': case 'X': case 'Y': case 'Z':
+      return (char)(ch + ('a' - 'A'));
+
+    case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05:
+    case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B:
+    case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11:
+    case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17:
+    case ' ': case '&': case '/': case ':': case '<': case '>': case '@':
+    case '\"': case '\'':
+    case 0x7F:
+      *valid = false;
+      return 0;
+
+    default:
+      return ch;
+  }
+}
+
+
+// Checks and normalizes the resource part of a JID.
+std::string Jid::PrepResource(const std::string& resource, bool* valid) {
+  *valid = false;
+  std::string result;
+
+  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);
+    }
+    else {
+      // TODO: implement the correct stringprep protocol for these
+      result += ch;
+    }
+  }
+
+  if (result.length() > 1023) {
+    return STR_EMPTY;
+  }
+  *valid = true;
+  return result;
+}
+
+// Returns the appropriate mapping for an ASCII character in a resource.
+char Jid::PrepResourceAscii(char ch, bool* valid) {
+  *valid = true;
+  switch (ch) {
+    case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05:
+    case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B:
+    case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11:
+    case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17:
+    case 0x7F:
+      *valid = false;
+      return 0;
+
+    default:
+      return ch;
+  }
+}
+
+// Checks and normalizes the domain part of a JID.
+std::string Jid::PrepDomain(const std::string& 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(domain, &result, valid);
+  if (!*valid) {
+    return STR_EMPTY;
+  }
+
+  if (result.length() > 1023) {
+    return STR_EMPTY;
+  }
+  *valid = true;
+  return result;
+}
+
+
+// Checks and normalizes an IDNA domain.
+void Jid::PrepDomain(const std::string& domain, std::string* buf, bool* valid) {
+  *valid = false;
+  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) {
+      case 0x002E:
+#if 0 // FIX: This isn't UTF-8-aware.
+      case 0x3002:
+      case 0xFF0E:
+      case 0xFF61:
+#endif
+        PrepDomainLabel(last, i, buf, &label_valid);
+        *buf += '.';
+        last = i + 1;
+        break;
+    }
+    if (!label_valid) {
+      return;
+    }
+  }
+  PrepDomainLabel(last, domain.end(), buf, valid);
+}
+
+// Checks and normalizes a domain label.
+void Jid::PrepDomainLabel(
+    std::string::const_iterator start, std::string::const_iterator end,
+    std::string* buf, bool* valid) {
+  *valid = false;
+
+  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);
+    }
+    else {
+      // TODO: implement ToASCII for these
+      *buf += ch;
+    }
+    if (!char_valid) {
+      return;
+    }
+  }
+
+  int count = buf->length() - start_len;
+  if (count == 0) {
+    return;
+  }
+  else if (count > 63) {
+    return;
+  }
+
+  // Is this check needed? See comment in PrepDomainLabelAscii.
+  if ((*buf)[start_len] == '-') {
+    return;
+  }
+  if ((*buf)[buf->length() - 1] == '-') {
+    return;
+  }
+  *valid = true;
+}
+
+
+// Returns the appropriate mapping for an ASCII character in a domain label.
+char Jid::PrepDomainLabelAscii(char ch, bool* valid) {
+  *valid = true;
+  // TODO: A literal reading of the spec seems to say that we do
+  // not need to check for these illegal characters (an "internationalized
+  // domain label" runs ToASCII with UseSTD3... set to false).  But that
+  // can't be right.  We should at least be checking that there are no '/'
+  // or '@' characters in the domain.  Perhaps we should see what others
+  // do in this case.
+
+  switch (ch) {
+    case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
+    case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
+    case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
+    case 'V': case 'W': case 'X': case 'Y': case 'Z':
+      return (char)(ch + ('a' - 'A'));
+
+    case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05:
+    case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B:
+    case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11:
+    case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17:
+    case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D:
+    case 0x1E: case 0x1F: case 0x20: case 0x21: case 0x22: case 0x23:
+    case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29:
+    case 0x2A: case 0x2B: case 0x2C: case 0x2E: case 0x2F: case 0x3A:
+    case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F: case 0x40:
+    case 0x5B: case 0x5C: case 0x5D: case 0x5E: case 0x5F: case 0x60:
+    case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F:
+      *valid = false;
+      return 0;
+
+    default:
+      return ch;
+  }
+}
+
+}  // namespace buzz
diff --git a/talk/xmpp/jid.h b/talk/xmpp/jid.h
new file mode 100644
index 0000000..dcfc123
--- /dev/null
+++ b/talk/xmpp/jid.h
@@ -0,0 +1,98 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_XMPP_JID_H_
+#define TALK_XMPP_JID_H_
+
+#include <string>
+#include "talk/base/basictypes.h"
+#include "talk/xmllite/xmlconstants.h"
+
+namespace buzz {
+
+// The Jid class encapsulates and provides parsing help for Jids. A Jid
+// consists of three parts: the node, the domain and the resource, 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);
+  ~Jid();
+
+  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;
+  void CopyFrom(const Jid& jid);
+  bool operator==(const Jid& other) const;
+  bool operator!=(const Jid& other) const { return !operator==(other); }
+
+  bool operator<(const Jid& other) const { return Compare(other) < 0; };
+  bool operator>(const Jid& other) const { return Compare(other) > 0; };
+
+  int Compare(const Jid & other) const;
+
+private:
+  void ValidateOrReset();
+
+  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);
+
+  std::string node_name_;
+  std::string domain_name_;
+  std::string resource_name_;
+};
+
+}
+
+#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/jingleinfotask.cc b/talk/xmpp/jingleinfotask.cc
new file mode 100644
index 0000000..cf3eac2
--- /dev/null
+++ b/talk/xmpp/jingleinfotask.cc
@@ -0,0 +1,138 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/xmpp/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/xmpp/jingleinfotask.h b/talk/xmpp/jingleinfotask.h
new file mode 100644
index 0000000..dbc3fb0
--- /dev/null
+++ b/talk/xmpp/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/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.cc b/talk/xmpp/mucroomconfigtask.cc
new file mode 100644
index 0000000..272bd44
--- /dev/null
+++ b/talk/xmpp/mucroomconfigtask.cc
@@ -0,0 +1,91 @@
+/*
+ * 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/xmpp/mucroomconfigtask.h"
+
+#include "talk/base/scoped_ptr.h"
+#include "talk/xmpp/constants.h"
+
+namespace buzz {
+
+MucRoomConfigTask::MucRoomConfigTask(
+    XmppTaskParentInterface* parent,
+    const Jid& room_jid,
+    const std::string& room_name,
+    const std::vector<std::string>& room_features)
+    : IqTask(parent, STR_SET, room_jid,
+             MakeRequest(room_name, room_features)),
+      room_jid_(room_jid) {
+}
+
+XmlElement* MucRoomConfigTask::MakeRequest(
+    const std::string& room_name,
+    const std::vector<std::string>& room_features) {
+  buzz::XmlElement* owner_query = new
+      buzz::XmlElement(buzz::QN_MUC_OWNER_QUERY, true);
+
+  buzz::XmlElement* x_form = new buzz::XmlElement(buzz::QN_XDATA_X, true);
+  x_form->SetAttr(buzz::QN_TYPE, buzz::STR_FORM);
+
+  buzz::XmlElement* roomname_field =
+      new buzz::XmlElement(buzz::QN_XDATA_FIELD, false);
+  roomname_field->SetAttr(buzz::QN_VAR, buzz::STR_MUC_ROOMCONFIG_ROOMNAME);
+  roomname_field->SetAttr(buzz::QN_TYPE, buzz::STR_TEXT_SINGLE);
+
+  buzz::XmlElement* roomname_value =
+      new buzz::XmlElement(buzz::QN_XDATA_VALUE, false);
+  roomname_value->SetBodyText(room_name);
+
+  roomname_field->AddElement(roomname_value);
+  x_form->AddElement(roomname_field);
+
+  buzz::XmlElement* features_field =
+      new buzz::XmlElement(buzz::QN_XDATA_FIELD, false);
+  features_field->SetAttr(buzz::QN_VAR, buzz::STR_MUC_ROOMCONFIG_FEATURES);
+  features_field->SetAttr(buzz::QN_TYPE, buzz::STR_LIST_MULTI);
+
+  for (std::vector<std::string>::const_iterator feature = room_features.begin();
+       feature != room_features.end(); ++feature) {
+    buzz::XmlElement* features_value =
+        new buzz::XmlElement(buzz::QN_XDATA_VALUE, false);
+    features_value->SetBodyText(*feature);
+    features_field->AddElement(features_value);
+  }
+
+  x_form->AddElement(features_field);
+  owner_query->AddElement(x_form);
+  return owner_query;
+}
+
+void MucRoomConfigTask::HandleResult(const XmlElement* element) {
+  SignalResult(this);
+}
+
+}  // namespace buzz
diff --git a/talk/xmpp/mucroomconfigtask.h b/talk/xmpp/mucroomconfigtask.h
new file mode 100644
index 0000000..ba0dbaa
--- /dev/null
+++ b/talk/xmpp/mucroomconfigtask.h
@@ -0,0 +1,64 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_XMPP_MUCROOMCONFIGTASK_H_
+#define TALK_XMPP_MUCROOMCONFIGTASK_H_
+
+#include <string>
+#include "talk/xmpp/iqtask.h"
+
+namespace buzz {
+
+// This task configures the muc room for document sharing and other enterprise
+// specific goodies.
+class MucRoomConfigTask : public IqTask {
+ public:
+  MucRoomConfigTask(XmppTaskParentInterface* parent,
+                    const Jid& room_jid,
+                    const std::string& room_name,
+                    const std::vector<std::string>& room_features);
+
+  // Room configuration does not return any reasonable error
+  // values. The First config request configures the room, subseqent
+  // ones are just ignored by server and server returns empty
+  // response.
+  sigslot::signal1<MucRoomConfigTask*> SignalResult;
+
+  const Jid& room_jid() const { return room_jid_; }
+
+ protected:
+  virtual void HandleResult(const XmlElement* stanza);
+
+ private:
+  static XmlElement* MakeRequest(const std::string& room_name,
+                                 const std::vector<std::string>& room_features);
+  Jid room_jid_;
+};
+
+}  // namespace buzz
+
+#endif  // TALK_XMPP_MUCROOMCONFIGTASK_H_
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/mucroomdiscoverytask.cc b/talk/xmpp/mucroomdiscoverytask.cc
new file mode 100644
index 0000000..e0770fd
--- /dev/null
+++ b/talk/xmpp/mucroomdiscoverytask.cc
@@ -0,0 +1,75 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/mucroomdiscoverytask.h"
+
+#include "talk/xmpp/constants.h"
+
+namespace buzz {
+
+MucRoomDiscoveryTask::MucRoomDiscoveryTask(
+    XmppTaskParentInterface* parent,
+    const Jid& room_jid)
+    : IqTask(parent, STR_GET, room_jid,
+             new buzz::XmlElement(buzz::QN_DISCO_INFO_QUERY)) {
+}
+
+void MucRoomDiscoveryTask::HandleResult(const XmlElement* stanza) {
+  const XmlElement* query = stanza->FirstNamed(QN_DISCO_INFO_QUERY);
+  if (query == NULL) {
+    SignalError(this, NULL);
+    return;
+  }
+
+  std::set<std::string> features;
+  std::map<std::string, std::string> extended_info;
+  const XmlElement* identity = query->FirstNamed(QN_DISCO_IDENTITY);
+  if (identity == NULL || !identity->HasAttr(QN_NAME)) {
+    SignalResult(this, false, "", features, extended_info);
+    return;
+  }
+
+  const std::string name(identity->Attr(QN_NAME));
+
+  for (const XmlElement* feature = query->FirstNamed(QN_DISCO_FEATURE);
+       feature != NULL; feature = feature->NextNamed(QN_DISCO_FEATURE)) {
+    features.insert(feature->Attr(QN_VAR));
+  }
+
+  const XmlElement* data_x = query->FirstNamed(QN_XDATA_X);
+  if (data_x != NULL) {
+    for (const XmlElement* field = data_x->FirstNamed(QN_XDATA_FIELD);
+         field != NULL; field = field->NextNamed(QN_XDATA_FIELD)) {
+      const std::string key(field->Attr(QN_VAR));
+      extended_info[key] = field->Attr(QN_XDATA_VALUE);
+    }
+  }
+
+  SignalResult(this, true, name, features, extended_info);
+}
+
+}  // namespace buzz
diff --git a/talk/xmpp/mucroomdiscoverytask.h b/talk/xmpp/mucroomdiscoverytask.h
new file mode 100644
index 0000000..6e3a21a
--- /dev/null
+++ b/talk/xmpp/mucroomdiscoverytask.h
@@ -0,0 +1,57 @@
+/*
+ * libjingle
+ * Copyright 2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_MUCROOMDISCOVERYTASK_H_
+#define TALK_XMPP_MUCROOMDISCOVERYTASK_H_
+
+#include <map>
+#include <string>
+#include "talk/xmpp/iqtask.h"
+
+namespace buzz {
+
+// This task requests the feature capabilities of the room. It is based on
+// XEP-0030, and extended using XEP-0004.
+class MucRoomDiscoveryTask : public IqTask {
+ public:
+  MucRoomDiscoveryTask(XmppTaskParentInterface* parent,
+                       const Jid& room_jid);
+
+  // Signal (exists, name, features, extended_info)
+  sigslot::signal5<MucRoomDiscoveryTask*,
+                   bool,
+                   const std::string&,
+                   const std::set<std::string>&,
+                   const std::map<std::string, std::string>& > SignalResult;
+
+ protected:
+  virtual void HandleResult(const XmlElement* stanza);
+};
+
+}  // namespace buzz
+
+#endif  // TALK_XMPP_MUCROOMDISCOVERYTASK_H_
diff --git a/talk/xmpp/mucroomdiscoverytask_unittest.cc b/talk/xmpp/mucroomdiscoverytask_unittest.cc
new file mode 100644
index 0000000..b88b6f2
--- /dev/null
+++ b/talk/xmpp/mucroomdiscoverytask_unittest.cc
@@ -0,0 +1,152 @@
+/*
+ * 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/mucroomdiscoverytask.h"
+
+class MucRoomDiscoveryListener : public sigslot::has_slots<> {
+ public:
+  MucRoomDiscoveryListener() : error_count(0) {}
+
+  void OnResult(buzz::MucRoomDiscoveryTask* task,
+                bool exists,
+                const std::string& name,
+                const std::set<std::string>& features,
+                const std::map<std::string, std::string>& extended_info) {
+    last_exists = exists;
+    last_name = name;
+    last_features = features;
+    last_extended_info = extended_info;
+  }
+
+  void OnError(buzz::IqTask* task,
+               const buzz::XmlElement* error) {
+    ++error_count;
+  }
+
+  bool last_exists;
+  std::string last_name;
+  std::set<std::string> last_features;
+  std::map<std::string, std::string> last_extended_info;
+  int error_count;
+};
+
+class MucRoomDiscoveryTaskTest : public testing::Test {
+ public:
+  MucRoomDiscoveryTaskTest() :
+      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 MucRoomDiscoveryListener();
+  }
+
+  virtual void TearDown() {
+    delete listener;
+    // delete xmpp_client;  Deleted by deleting runner.
+    delete runner;
+  }
+
+  talk_base::FakeTaskRunner* runner;
+  buzz::FakeXmppClient* xmpp_client;
+  MucRoomDiscoveryListener* listener;
+  buzz::Jid room_jid;
+  std::string room_name;
+};
+
+TEST_F(MucRoomDiscoveryTaskTest, TestDiscovery) {
+  ASSERT_EQ(0U, xmpp_client->sent_stanzas().size());
+
+  buzz::MucRoomDiscoveryTask* task = new buzz::MucRoomDiscoveryTask(
+      xmpp_client, room_jid);
+  task->SignalResult.connect(listener, &MucRoomDiscoveryListener::OnResult);
+  task->Start();
+
+  std::string expected_iq =
+      "<cli:iq type=\"get\" to=\"muc-jid-ponies@domain.com\" id=\"0\" "
+        "xmlns:cli=\"jabber:client\">"
+        "<info:query xmlns:info=\"http://jabber.org/protocol/disco#info\"/>"
+      "</cli:iq>";
+
+  ASSERT_EQ(1U, xmpp_client->sent_stanzas().size());
+  EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str());
+
+  EXPECT_EQ("", listener->last_name);
+
+  std::string response_iq =
+      "<iq xmlns='jabber:client'"
+      "    from='muc-jid-ponies@domain.com' id='0' type='result'>"
+      "  <info:query xmlns:info='http://jabber.org/protocol/disco#info'>"
+      "    <info:identity name='ponies'/>"
+      "    <info:feature var='feature1'/>"
+      "    <info:feature var='feature2'/>"
+      "    <data:x xmlns:data='jabber:x:data'>"
+      "      <data:field var='var1' data:value='value1' />"
+      "      <data:field var='var2' data:value='value2' />"
+      "    </data:x>"
+      "  </info:query>"
+      "</iq>";
+
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq));
+
+  EXPECT_EQ(true, listener->last_exists);
+  EXPECT_EQ(room_name, listener->last_name);
+  EXPECT_EQ(2U, listener->last_features.size());
+  EXPECT_EQ(1U, listener->last_features.count("feature1"));
+  EXPECT_EQ(2U, listener->last_extended_info.size());
+  EXPECT_EQ("value1", listener->last_extended_info["var1"]);
+  EXPECT_EQ(0, listener->error_count);
+}
+
+TEST_F(MucRoomDiscoveryTaskTest, TestMissingName) {
+  buzz::MucRoomDiscoveryTask* task = new buzz::MucRoomDiscoveryTask(
+      xmpp_client, room_jid);
+  task->SignalError.connect(listener, &MucRoomDiscoveryListener::OnError);
+  task->Start();
+
+  std::string error_iq =
+      "<iq xmlns='jabber:client'"
+      "    from='muc-jid-ponies@domain.com' id='0' type='result'>"
+      "  <info:query xmlns:info='http://jabber.org/protocol/disco#info'>"
+      "    <info:identity />"
+      "  </info:query>"
+      "</iq>";
+  EXPECT_EQ(0, listener->error_count);
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(error_iq));
+  EXPECT_EQ(0, listener->error_count);
+}
diff --git a/talk/xmpp/mucroomlookuptask.cc b/talk/xmpp/mucroomlookuptask.cc
new file mode 100644
index 0000000..b78e5dd
--- /dev/null
+++ b/talk/xmpp/mucroomlookuptask.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 "talk/xmpp/mucroomlookuptask.h"
+
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/xmpp/constants.h"
+
+
+namespace buzz {
+
+MucRoomLookupTask*
+MucRoomLookupTask::CreateLookupTaskForRoomName(XmppTaskParentInterface* parent,
+                                     const Jid& lookup_server_jid,
+                                     const std::string& room_name,
+                                     const std::string& room_domain) {
+  return new MucRoomLookupTask(parent, lookup_server_jid,
+                               MakeNameQuery(room_name, room_domain));
+}
+
+MucRoomLookupTask*
+MucRoomLookupTask::CreateLookupTaskForRoomJid(XmppTaskParentInterface* parent,
+                                              const Jid& lookup_server_jid,
+                                              const Jid& room_jid) {
+  return new MucRoomLookupTask(parent, lookup_server_jid,
+                               MakeJidQuery(room_jid));
+}
+
+MucRoomLookupTask*
+MucRoomLookupTask::CreateLookupTaskForHangoutId(XmppTaskParentInterface* parent,
+                                                const Jid& lookup_server_jid,
+                                                const std::string& hangout_id) {
+  return new MucRoomLookupTask(parent, lookup_server_jid,
+                               MakeHangoutIdQuery(hangout_id));
+}
+
+MucRoomLookupTask*
+MucRoomLookupTask::CreateLookupTaskForExternalId(
+    XmppTaskParentInterface* parent,
+    const Jid& lookup_server_jid,
+    const std::string& external_id,
+    const std::string& type) {
+  return new MucRoomLookupTask(parent, lookup_server_jid,
+                               MakeExternalIdQuery(external_id, type));
+}
+
+MucRoomLookupTask::MucRoomLookupTask(XmppTaskParentInterface* parent,
+                                     const Jid& lookup_server_jid,
+                                     XmlElement* query)
+    : IqTask(parent, STR_SET, lookup_server_jid, query) {
+}
+
+XmlElement* MucRoomLookupTask::MakeNameQuery(
+    const std::string& room_name, const std::string& room_domain) {
+  XmlElement* name_elem = new XmlElement(QN_SEARCH_ROOM_NAME, false);
+  name_elem->SetBodyText(room_name);
+
+  XmlElement* domain_elem = new XmlElement(QN_SEARCH_ROOM_DOMAIN, false);
+  domain_elem->SetBodyText(room_domain);
+
+  XmlElement* query = new XmlElement(QN_SEARCH_QUERY, true);
+  query->AddElement(name_elem);
+  query->AddElement(domain_elem);
+  return query;
+}
+
+XmlElement* MucRoomLookupTask::MakeJidQuery(const Jid& room_jid) {
+  XmlElement* jid_elem = new XmlElement(QN_SEARCH_ROOM_JID);
+  jid_elem->SetBodyText(room_jid.Str());
+
+  XmlElement* query = new XmlElement(QN_SEARCH_QUERY);
+  query->AddElement(jid_elem);
+  return query;
+}
+
+XmlElement* MucRoomLookupTask::MakeExternalIdQuery(
+    const std::string& external_id, const std::string& type) {
+  XmlElement* external_id_elem = new XmlElement(QN_SEARCH_EXTERNAL_ID);
+  external_id_elem->SetAttr(QN_TYPE, type);
+  external_id_elem->SetBodyText(external_id);
+
+  XmlElement* query = new XmlElement(QN_SEARCH_QUERY);
+  query->AddElement(external_id_elem);
+  return query;
+}
+
+// Construct a stanza to lookup the muc jid for a given hangout id. eg:
+//
+// <query xmlns="jabber:iq:search">
+//   <hangout-id>0b48ad092c893a53b7bfc87422caf38e93978798e</hangout-id>
+// </query>
+XmlElement* MucRoomLookupTask::MakeHangoutIdQuery(
+    const std::string& hangout_id) {
+  XmlElement* hangout_id_elem = new XmlElement(QN_SEARCH_HANGOUT_ID, false);
+  hangout_id_elem->SetBodyText(hangout_id);
+
+  XmlElement* query = new XmlElement(QN_SEARCH_QUERY, true);
+  query->AddElement(hangout_id_elem);
+  return query;
+}
+
+// Handle a response like the following:
+//
+// <query xmlns="jabber:iq:search">
+//   <item jid="muvc-private-chat-guid@groupchat.google.com">
+//     <room-name>0b48ad092c893a53b7bfc87422caf38e93978798e</room-name>
+//     <room-domain>hangout.google.com</room-domain>
+//   </item>
+// </query>
+void MucRoomLookupTask::HandleResult(const XmlElement* stanza) {
+  const XmlElement* query_elem = stanza->FirstNamed(QN_SEARCH_QUERY);
+  if (query_elem == NULL) {
+    SignalError(this, stanza);
+    return;
+  }
+
+  const XmlElement* item_elem = query_elem->FirstNamed(QN_SEARCH_ITEM);
+  if (item_elem == NULL) {
+    SignalError(this, stanza);
+    return;
+  }
+
+  MucRoomInfo room;
+  room.jid = Jid(item_elem->Attr(buzz::QN_JID));
+  if (!room.jid.IsValid()) {
+    SignalError(this, stanza);
+    return;
+  }
+
+  const XmlElement* room_name_elem =
+      item_elem->FirstNamed(QN_SEARCH_ROOM_NAME);
+  if (room_name_elem != NULL) {
+    room.name = room_name_elem->BodyText();
+  }
+
+  const XmlElement* room_domain_elem =
+      item_elem->FirstNamed(QN_SEARCH_ROOM_DOMAIN);
+  if (room_domain_elem != NULL) {
+    room.domain = room_domain_elem->BodyText();
+  }
+
+  const XmlElement* hangout_id_elem =
+      item_elem->FirstNamed(QN_SEARCH_HANGOUT_ID);
+  if (hangout_id_elem != NULL) {
+    room.hangout_id = hangout_id_elem->BodyText();
+  }
+
+  SignalResult(this, room);
+}
+
+}  // namespace buzz
diff --git a/talk/xmpp/mucroomlookuptask.h b/talk/xmpp/mucroomlookuptask.h
new file mode 100644
index 0000000..60001ff
--- /dev/null
+++ b/talk/xmpp/mucroomlookuptask.h
@@ -0,0 +1,88 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_XMPP_MUCROOMLOOKUPTASK_H_
+#define TALK_XMPP_MUCROOMLOOKUPTASK_H_
+
+#include <string>
+#include "talk/xmpp/iqtask.h"
+
+namespace buzz {
+
+struct MucRoomInfo {
+  Jid jid;
+  std::string name;
+  std::string domain;
+  std::string hangout_id;
+
+  std::string full_name() const {
+    return name + "@" + domain;
+  }
+};
+
+class MucRoomLookupTask : public IqTask {
+ public:
+  static MucRoomLookupTask*
+      CreateLookupTaskForRoomName(XmppTaskParentInterface* parent,
+                                  const Jid& lookup_server_jid,
+                                  const std::string& room_name,
+                                  const std::string& room_domain);
+  static MucRoomLookupTask*
+      CreateLookupTaskForRoomJid(XmppTaskParentInterface* parent,
+                                 const Jid& lookup_server_jid,
+                                 const Jid& room_jid);
+  static MucRoomLookupTask*
+      CreateLookupTaskForHangoutId(XmppTaskParentInterface* parent,
+                                   const Jid& lookup_server_jid,
+                                   const std::string& hangout_id);
+  static MucRoomLookupTask*
+      CreateLookupTaskForExternalId(XmppTaskParentInterface* parent,
+                                    const Jid& lookup_server_jid,
+                                    const std::string& external_id,
+                                    const std::string& type);
+
+  sigslot::signal2<MucRoomLookupTask*,
+                   const MucRoomInfo&> SignalResult;
+
+ protected:
+  virtual void HandleResult(const XmlElement* element);
+
+ private:
+  MucRoomLookupTask(XmppTaskParentInterface* parent,
+                    const Jid& lookup_server_jid,
+                    XmlElement* query);
+  static XmlElement* MakeNameQuery(const std::string& room_name,
+                                   const std::string& room_domain);
+  static XmlElement* MakeJidQuery(const Jid& room_jid);
+  static XmlElement* MakeHangoutIdQuery(const std::string& hangout_id);
+  static XmlElement* MakeExternalIdQuery(const std::string& external_id,
+                                         const std::string& type);
+};
+
+}  // namespace buzz
+
+#endif  // TALK_XMPP_MUCROOMLOOKUPTASK_H_
diff --git a/talk/xmpp/mucroomlookuptask_unittest.cc b/talk/xmpp/mucroomlookuptask_unittest.cc
new file mode 100644
index 0000000..a662d53
--- /dev/null
+++ b/talk/xmpp/mucroomlookuptask_unittest.cc
@@ -0,0 +1,204 @@
+/*
+ * 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"),
+      hangout_id("some_hangout_id") {
+  }
+
+  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;
+  std::string hangout_id;
+};
+
+TEST_F(MucRoomLookupTaskTest, TestLookupName) {
+  ASSERT_EQ(0U, xmpp_client->sent_stanzas().size());
+
+  buzz::MucRoomLookupTask* task =
+      buzz::MucRoomLookupTask::CreateLookupTaskForRoomName(
+          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, TestLookupHangoutId) {
+  ASSERT_EQ(0U, xmpp_client->sent_stanzas().size());
+
+  buzz::MucRoomLookupTask* task = buzz::MucRoomLookupTask::CreateLookupTaskForHangoutId(
+      xmpp_client, lookup_server_jid, hangout_id);
+  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\">"
+          "<hangout-id>some_hangout_id</hangout-id>"
+        "</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>some_hangout_id</room-name>"
+      "      <room-domain>domain.com</room-domain>"
+      "    </item>"
+      "  </query>"
+      "</iq>";
+
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq));
+
+  EXPECT_EQ(hangout_id, listener->last_room.name);
+  EXPECT_EQ(room_domain, listener->last_room.domain);
+  EXPECT_EQ(room_jid, listener->last_room.jid);
+  EXPECT_EQ(0, listener->error_count);
+}
+
+TEST_F(MucRoomLookupTaskTest, TestError) {
+  buzz::MucRoomLookupTask* task = buzz::MucRoomLookupTask::CreateLookupTaskForRoomName(
+      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 = buzz::MucRoomLookupTask::CreateLookupTaskForRoomName(
+      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/mucroomuniquehangoutidtask.cc b/talk/xmpp/mucroomuniquehangoutidtask.cc
new file mode 100644
index 0000000..78a8edf
--- /dev/null
+++ b/talk/xmpp/mucroomuniquehangoutidtask.cc
@@ -0,0 +1,44 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+
+
+#include "talk/xmpp/mucroomuniquehangoutidtask.h"
+
+#include "talk/xmpp/constants.h"
+
+namespace buzz {
+
+MucRoomUniqueHangoutIdTask::MucRoomUniqueHangoutIdTask(XmppTaskParentInterface* parent,
+                                             const Jid& lookup_server_jid)
+    : IqTask(parent, STR_GET, lookup_server_jid, MakeUniqueRequestXml()) {
+}
+
+// Construct a stanza to request a unique room id. eg:
+//
+// <unique hangout-id="true" xmlns="http://jabber.org/protocol/muc#unique"/>
+XmlElement* MucRoomUniqueHangoutIdTask::MakeUniqueRequestXml() {
+  XmlElement* xml = new XmlElement(QN_MUC_UNIQUE_QUERY, false);
+  xml->SetAttr(QN_HANGOUT_ID, STR_TRUE);
+  return xml;
+}
+
+// Handle a response like the following:
+//
+// <unique hangout-id="hangout_id"
+//    xmlns="http://jabber.org/protocol/muc#unique"/>
+//  muvc-private-chat-guid@groupchat.google.com
+// </unique>
+void MucRoomUniqueHangoutIdTask::HandleResult(const XmlElement* stanza) {
+
+  const XmlElement* unique_elem = stanza->FirstNamed(QN_MUC_UNIQUE_QUERY);
+  if (unique_elem == NULL ||
+      !unique_elem->HasAttr(QN_HANGOUT_ID)) {
+    SignalError(this, stanza);
+    return;
+  }
+
+  std::string hangout_id = unique_elem->Attr(QN_HANGOUT_ID);
+
+  SignalResult(this, hangout_id);
+}
+
+} // namespace buzz
diff --git a/talk/xmpp/mucroomuniquehangoutidtask.h b/talk/xmpp/mucroomuniquehangoutidtask.h
new file mode 100644
index 0000000..d222bac
--- /dev/null
+++ b/talk/xmpp/mucroomuniquehangoutidtask.h
@@ -0,0 +1,31 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+
+
+#ifndef TALK_XMPP_MUCROOMUNIQUEHANGOUTIDTASK_H_
+#define TALK_XMPP_MUCROOMUNIQUEHANGOUTIDTASK_H_
+
+#include "talk/xmpp/iqtask.h"
+
+namespace buzz {
+
+// Task to request a unique hangout id to be used when starting a hangout.
+// The protocol is described in https://docs.google.com/a/google.com/
+// document/d/1EFLT6rCYPDVdqQXSQliXwqB3iUkpZJ9B_MNFeOZgN7g/edit
+class MucRoomUniqueHangoutIdTask : public buzz::IqTask {
+ public:
+  MucRoomUniqueHangoutIdTask(buzz::XmppTaskParentInterface* parent,
+                        const Jid& lookup_server_jid);
+  // signal(task, hangout_id)
+  sigslot::signal2<MucRoomUniqueHangoutIdTask*, const std::string&> SignalResult;
+
+ protected:
+  virtual void HandleResult(const buzz::XmlElement* stanza);
+
+ private:
+  static buzz::XmlElement* MakeUniqueRequestXml();
+
+};
+
+} // namespace buzz
+
+#endif  // TALK_XMPP_MUCROOMUNIQUEHANGOUTIDTASK_H_
diff --git a/talk/xmpp/mucroomuniquehangoutidtask_unittest.cc b/talk/xmpp/mucroomuniquehangoutidtask_unittest.cc
new file mode 100644
index 0000000..128bab3
--- /dev/null
+++ b/talk/xmpp/mucroomuniquehangoutidtask_unittest.cc
@@ -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.
+ */
+
+#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/mucroomuniquehangoutidtask.h"
+
+class MucRoomUniqueHangoutIdListener : public sigslot::has_slots<> {
+ public:
+  MucRoomUniqueHangoutIdListener() : error_count(0) {}
+
+  void OnResult(buzz::MucRoomUniqueHangoutIdTask* task,
+                const std::string& hangout_id) {
+    last_hangout_id = hangout_id;
+  }
+
+  void OnError(buzz::IqTask* task,
+               const buzz::XmlElement* error) {
+    ++error_count;
+  }
+
+  std::string last_hangout_id;
+  int error_count;
+};
+
+class MucRoomUniqueHangoutIdTaskTest : public testing::Test {
+ public:
+  MucRoomUniqueHangoutIdTaskTest() :
+      lookup_server_jid("lookup@domain.com"),
+      hangout_id("some_hangout_id") {
+  }
+
+  virtual void SetUp() {
+    runner = new talk_base::FakeTaskRunner();
+    xmpp_client = new buzz::FakeXmppClient(runner);
+    listener = new MucRoomUniqueHangoutIdListener();
+  }
+
+  virtual void TearDown() {
+    delete listener;
+    // delete xmpp_client;  Deleted by deleting runner.
+    delete runner;
+  }
+
+  talk_base::FakeTaskRunner* runner;
+  buzz::FakeXmppClient* xmpp_client;
+  MucRoomUniqueHangoutIdListener* listener;
+  buzz::Jid lookup_server_jid;
+  std::string hangout_id;
+};
+
+TEST_F(MucRoomUniqueHangoutIdTaskTest, Test) {
+  ASSERT_EQ(0U, xmpp_client->sent_stanzas().size());
+
+  buzz::MucRoomUniqueHangoutIdTask* task = new buzz::MucRoomUniqueHangoutIdTask(
+      xmpp_client, lookup_server_jid);
+  task->SignalResult.connect(listener, &MucRoomUniqueHangoutIdListener::OnResult);
+  task->Start();
+
+  std::string expected_iq =
+      "<cli:iq type=\"get\" to=\"lookup@domain.com\" id=\"0\" "
+          "xmlns:cli=\"jabber:client\">"
+        "<uni:unique hangout-id=\"true\" "
+          "xmlns:uni=\"http://jabber.org/protocol/muc#unique\"/>"
+      "</cli:iq>";
+
+  ASSERT_EQ(1U, xmpp_client->sent_stanzas().size());
+  EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str());
+
+  EXPECT_EQ("", listener->last_hangout_id);
+
+  std::string response_iq =
+      "<iq xmlns='jabber:client' from='lookup@domain.com' id='0' type='result'>"
+        "<unique hangout-id=\"some_hangout_id\" "
+            "xmlns=\"http://jabber.org/protocol/muc#unique\">"
+          "muvc-private-chat-00001234-5678-9abc-def0-123456789abc"
+        "</unique>"
+      "</iq>";
+
+  xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq));
+
+  EXPECT_EQ(hangout_id, listener->last_hangout_id);
+  EXPECT_EQ(0, listener->error_count);
+}
+
diff --git a/talk/xmpp/pingtask.cc b/talk/xmpp/pingtask.cc
new file mode 100644
index 0000000..233062f
--- /dev/null
+++ b/talk/xmpp/pingtask.cc
@@ -0,0 +1,85 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+
+
+#include "talk/xmpp/pingtask.h"
+
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/xmpp/constants.h"
+
+namespace buzz {
+
+PingTask::PingTask(buzz::XmppTaskParentInterface* parent,
+                   talk_base::MessageQueue* message_queue,
+                   uint32 ping_period_millis,
+                   uint32 ping_timeout_millis)
+    : buzz::XmppTask(parent, buzz::XmppEngine::HL_SINGLE),
+      message_queue_(message_queue),
+      ping_period_millis_(ping_period_millis),
+      ping_timeout_millis_(ping_timeout_millis),
+      next_ping_time_(0),
+      ping_response_deadline_(0) {
+  ASSERT(ping_period_millis >= ping_timeout_millis);
+}
+
+bool PingTask::HandleStanza(const buzz::XmlElement* stanza) {
+  if (!MatchResponseIq(stanza, Jid(STR_EMPTY), task_id())) {
+    return false;
+  }
+
+  if (stanza->Attr(buzz::QN_TYPE) != buzz::STR_RESULT &&
+      stanza->Attr(buzz::QN_TYPE) != buzz::STR_ERROR) {
+    return false;
+  }
+
+  QueueStanza(stanza);
+  return true;
+}
+
+// This task runs indefinitely and remains in either the start or blocked
+// states.
+int PingTask::ProcessStart() {
+  if (ping_period_millis_ < ping_timeout_millis_) {
+    LOG(LS_ERROR) << "ping_period_millis should be >= ping_timeout_millis";
+    return STATE_ERROR;
+  }
+  const buzz::XmlElement* stanza = NextStanza();
+  if (stanza != NULL) {
+    // Received a ping response of some sort (don't care what it is).
+    ping_response_deadline_ = 0;
+  }
+
+  uint32 now = talk_base::Time();
+
+  // If the ping timed out, signal.
+  if (ping_response_deadline_ != 0 && now >= ping_response_deadline_) {
+    SignalTimeout();
+    return STATE_ERROR;
+  }
+
+  // Send a ping if it's time.
+  if (now >= next_ping_time_) {
+    talk_base::scoped_ptr<buzz::XmlElement> stanza(
+        MakeIq(buzz::STR_GET, Jid(STR_EMPTY), task_id()));
+    stanza->AddElement(new buzz::XmlElement(QN_PING));
+    SendStanza(stanza.get());
+
+    ping_response_deadline_ = now + ping_timeout_millis_;
+    next_ping_time_ = now + ping_period_millis_;
+
+    // Wake ourselves up when it's time to send another ping or when the ping
+    // times out (so we can fire a signal).
+    message_queue_->PostDelayed(ping_timeout_millis_, this);
+    message_queue_->PostDelayed(ping_period_millis_, this);
+  }
+
+  return STATE_BLOCKED;
+}
+
+void PingTask::OnMessage(talk_base::Message* msg) {
+  // Get the task manager to run this task so we can send a ping or signal or
+  // process a ping response.
+  Wake();
+}
+
+} // namespace buzz
diff --git a/talk/xmpp/pingtask.h b/talk/xmpp/pingtask.h
new file mode 100644
index 0000000..8375241
--- /dev/null
+++ b/talk/xmpp/pingtask.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_XMPP_PINGTASK_H_
+#define TALK_XMPP_PINGTASK_H_
+
+#include "talk/base/messagehandler.h"
+#include "talk/base/messagequeue.h"
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+
+// Task to periodically send pings to the server to ensure that the network
+// connection is valid, implementing XEP-0199.
+//
+// This is especially useful on cellular networks because:
+// 1. It keeps the connections alive through the cellular network's NATs or
+//    proxies.
+// 2. It detects when the server has crashed or any other case in which the
+//    connection has broken without a fin or reset packet being sent to us.
+class PingTask : public buzz::XmppTask, private talk_base::MessageHandler {
+ public:
+  PingTask(buzz::XmppTaskParentInterface* parent,
+      talk_base::MessageQueue* message_queue, uint32 ping_period_millis,
+      uint32 ping_timeout_millis);
+
+  virtual bool HandleStanza(const buzz::XmlElement* stanza);
+  virtual int ProcessStart();
+
+  // Raised if there is no response to a ping within ping_timeout_millis.
+  // The task is automatically aborted after a timeout.
+  sigslot::signal0<> SignalTimeout;
+
+ private:
+  // Implementation of MessageHandler.
+  virtual void OnMessage(talk_base::Message* msg);
+
+  talk_base::MessageQueue* message_queue_;
+  uint32 ping_period_millis_;
+  uint32 ping_timeout_millis_;
+  uint32 next_ping_time_;
+  uint32 ping_response_deadline_; // 0 if the response has been received
+};
+
+} // namespace buzz
+
+#endif  // TALK_XMPP_PINGTASK_H_
diff --git a/talk/xmpp/pingtask_unittest.cc b/talk/xmpp/pingtask_unittest.cc
new file mode 100644
index 0000000..477847d
--- /dev/null
+++ b/talk/xmpp/pingtask_unittest.cc
@@ -0,0 +1,118 @@
+/*
+ * 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/pingtask.h"
+
+class PingTaskTest;
+
+class PingXmppClient : public buzz::FakeXmppClient {
+ public:
+  PingXmppClient(talk_base::TaskParent* parent, PingTaskTest* tst) :
+      FakeXmppClient(parent), test(tst) {
+  }
+
+  buzz::XmppReturnStatus SendStanza(const buzz::XmlElement* stanza);
+
+ private:
+  PingTaskTest* test;
+};
+
+class PingTaskTest : public testing::Test, public sigslot::has_slots<> {
+ public:
+  PingTaskTest() : respond_to_pings(true), timed_out(false) {
+  }
+
+  virtual void SetUp() {
+    runner = new talk_base::FakeTaskRunner();
+    xmpp_client = new PingXmppClient(runner, this);
+  }
+
+  virtual void TearDown() {
+    // delete xmpp_client;  Deleted by deleting runner.
+    delete runner;
+  }
+
+  void ConnectTimeoutSignal(buzz::PingTask* task) {
+    task->SignalTimeout.connect(this, &PingTaskTest::OnPingTimeout);
+  }
+
+  void OnPingTimeout() {
+    timed_out = true;
+  }
+
+  talk_base::FakeTaskRunner* runner;
+  PingXmppClient* xmpp_client;
+  bool respond_to_pings;
+  bool timed_out;
+};
+
+buzz::XmppReturnStatus PingXmppClient::SendStanza(
+    const buzz::XmlElement* stanza) {
+  buzz::XmppReturnStatus result = FakeXmppClient::SendStanza(stanza);
+  if (test->respond_to_pings && (stanza->FirstNamed(buzz::QN_PING) != NULL)) {
+    std::string ping_response =
+        "<iq xmlns=\'jabber:client\' id='0' type='result'/>";
+    HandleStanza(buzz::XmlElement::ForStr(ping_response));
+  }
+  return result;
+}
+
+TEST_F(PingTaskTest, TestSuccess) {
+  uint32 ping_period_millis = 100;
+  buzz::PingTask* task = new buzz::PingTask(xmpp_client,
+      talk_base::Thread::Current(),
+      ping_period_millis, ping_period_millis / 10);
+  ConnectTimeoutSignal(task);
+  task->Start();
+  unsigned int expected_ping_count = 5U;
+  EXPECT_EQ_WAIT(xmpp_client->sent_stanzas().size(), expected_ping_count,
+                 ping_period_millis * (expected_ping_count + 1));
+  EXPECT_FALSE(task->IsDone());
+  EXPECT_FALSE(timed_out);
+}
+
+TEST_F(PingTaskTest, TestTimeout) {
+  respond_to_pings = false;
+  uint32 ping_timeout_millis = 200;
+  buzz::PingTask* task = new buzz::PingTask(xmpp_client,
+      talk_base::Thread::Current(),
+      ping_timeout_millis * 10, ping_timeout_millis);
+  ConnectTimeoutSignal(task);
+  task->Start();
+  WAIT(false, ping_timeout_millis / 2);
+  EXPECT_FALSE(timed_out);
+  EXPECT_TRUE_WAIT(timed_out, ping_timeout_millis * 2);
+}
diff --git a/talk/xmpp/plainsaslhandler.h b/talk/xmpp/plainsaslhandler.h
new file mode 100644
index 0000000..e7d44b9
--- /dev/null
+++ b/talk/xmpp/plainsaslhandler.h
@@ -0,0 +1,80 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _PLAINSASLHANDLER_H_
+#define _PLAINSASLHANDLER_H_
+
+#include "talk/xmpp/saslhandler.h"
+#include <algorithm>
+
+namespace buzz {
+
+class PlainSaslHandler : public SaslHandler {
+public:
+  PlainSaslHandler(const Jid & jid, const talk_base::CryptString & password, 
+      bool allow_plain) : jid_(jid), password_(password), 
+                          allow_plain_(allow_plain) {}
+    
+  virtual ~PlainSaslHandler() {}
+
+  // Should pick the best method according to this handler
+  // returns the empty string if none are suitable
+  virtual std::string ChooseBestSaslMechanism(const std::vector<std::string> & mechanisms, bool encrypted) {
+  
+    if (!encrypted && !allow_plain_) {
+      return "";
+    }
+    
+    std::vector<std::string>::const_iterator it = std::find(mechanisms.begin(), mechanisms.end(), "PLAIN");
+    if (it == mechanisms.end()) {
+      return "";
+    }
+    else {
+      return "PLAIN";
+    }
+  }
+
+  // Creates a SaslMechanism for the given mechanism name (you own it
+  // once you get it).  If not handled, return NULL.
+  virtual SaslMechanism * CreateSaslMechanism(const std::string & mechanism) {
+    if (mechanism == "PLAIN") {
+      return new SaslPlainMechanism(jid_, password_);
+    }
+    return NULL;
+  }
+  
+private:
+  Jid jid_;
+  talk_base::CryptString password_;
+  bool allow_plain_;
+};
+
+
+}
+
+#endif
+
diff --git a/talk/xmpp/presenceouttask.cc b/talk/xmpp/presenceouttask.cc
new file mode 100644
index 0000000..cebd740
--- /dev/null
+++ b/talk/xmpp/presenceouttask.cc
@@ -0,0 +1,157 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <time.h>
+#include <sstream>
+#include "talk/base/stringencode.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/presenceouttask.h"
+#include "talk/xmpp/xmppclient.h"
+
+namespace buzz {
+
+XmppReturnStatus
+PresenceOutTask::Send(const PresenceStatus & s) {
+  if (GetState() != STATE_INIT && GetState() != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+
+  XmlElement * presence = TranslateStatus(s);
+  QueueStanza(presence);
+  delete presence;
+  return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus
+PresenceOutTask::SendDirected(const Jid & j, const PresenceStatus & s) {
+  if (GetState() != STATE_INIT && GetState() != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+
+  XmlElement * presence = TranslateStatus(s);
+  presence->AddAttr(QN_TO, j.Str());
+  QueueStanza(presence);
+  delete presence;
+  return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus PresenceOutTask::SendProbe(const Jid & jid) {
+  if (GetState() != STATE_INIT && GetState() != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+
+  XmlElement * presence = new XmlElement(QN_PRESENCE);
+  presence->AddAttr(QN_TO, jid.Str());
+  presence->AddAttr(QN_TYPE, "probe");
+
+  QueueStanza(presence);
+  delete presence;
+  return XMPP_RETURN_OK;
+}
+
+int
+PresenceOutTask::ProcessStart() {
+  const XmlElement * stanza = NextStanza();
+  if (stanza == NULL)
+    return STATE_BLOCKED;
+
+  if (SendStanza(stanza) != XMPP_RETURN_OK)
+    return STATE_ERROR;
+
+  return STATE_START;
+}
+
+XmlElement *
+PresenceOutTask::TranslateStatus(const PresenceStatus & s) {
+  XmlElement * result = new XmlElement(QN_PRESENCE);
+  if (!s.available()) {
+    result->AddAttr(QN_TYPE, STR_UNAVAILABLE);
+  }
+  else {
+    if (s.show() != PresenceStatus::SHOW_ONLINE && 
+        s.show() != PresenceStatus::SHOW_OFFLINE) {
+      result->AddElement(new XmlElement(QN_SHOW));
+      switch (s.show()) {
+        default:
+          result->AddText(STR_SHOW_AWAY, 1);
+          break;
+        case PresenceStatus::SHOW_XA:
+          result->AddText(STR_SHOW_XA, 1);
+          break;
+        case PresenceStatus::SHOW_DND:
+          result->AddText(STR_SHOW_DND, 1);
+          break;
+        case PresenceStatus::SHOW_CHAT:
+          result->AddText(STR_SHOW_CHAT, 1);
+          break;
+      }
+    }
+
+    result->AddElement(new XmlElement(QN_STATUS));
+    result->AddText(s.status(), 1);
+
+    if (!s.nick().empty()) {
+      result->AddElement(new XmlElement(QN_NICKNAME));
+      result->AddText(s.nick(), 1);
+    }
+
+    std::string pri;
+    talk_base::ToString(s.priority(), &pri);
+
+    result->AddElement(new XmlElement(QN_PRIORITY));
+    result->AddText(pri, 1);
+
+    if (s.know_capabilities()) {
+      result->AddElement(new XmlElement(QN_CAPS_C, true));
+      result->AddAttr(QN_NODE, s.caps_node(), 1);
+      result->AddAttr(QN_VER, s.version(), 1);
+
+      std::string caps;
+      caps.append(s.voice_capability() ? "voice-v1" : "");
+      caps.append(s.pmuc_capability() ? " pmuc-v1" : "");
+      caps.append(s.video_capability() ? " video-v1" : "");
+      caps.append(s.camera_capability() ? " camera-v1" : "");
+
+      result->AddAttr(QN_EXT, caps, 1);
+    }
+
+    // Put the delay mark on the presence according to JEP-0091
+    {
+      result->AddElement(new XmlElement(kQnDelayX, true));
+
+      // This here is why we *love* the C runtime
+      time_t current_time_seconds;
+      time(&current_time_seconds);
+      struct tm* current_time = gmtime(&current_time_seconds);
+      char output[256];
+      strftime(output, ARRAY_SIZE(output), "%Y%m%dT%H:%M:%S", current_time);
+      result->AddAttr(kQnStamp, output, 1);
+    }
+  }
+
+  return result;
+}
+
+
+}
diff --git a/talk/xmpp/presenceouttask.h b/talk/xmpp/presenceouttask.h
new file mode 100644
index 0000000..cea2b56
--- /dev/null
+++ b/talk/xmpp/presenceouttask.h
@@ -0,0 +1,54 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _PRESENCEOUTTASK_H_
+#define _PRESENCEOUTTASK_H_
+
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+#include "talk/xmpp/presencestatus.h"
+
+namespace buzz {
+
+class PresenceOutTask : public XmppTask {
+public:
+  explicit PresenceOutTask(XmppTaskParentInterface* parent)
+      : XmppTask(parent) {}
+  virtual ~PresenceOutTask() {}
+
+  XmppReturnStatus Send(const PresenceStatus & s);
+  XmppReturnStatus SendDirected(const Jid & j, const PresenceStatus & s);
+  XmppReturnStatus SendProbe(const Jid& jid);
+
+  virtual int ProcessStart();
+private:
+  XmlElement * TranslateStatus(const PresenceStatus & s);
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/presencereceivetask.cc b/talk/xmpp/presencereceivetask.cc
new file mode 100644
index 0000000..80121dd
--- /dev/null
+++ b/talk/xmpp/presencereceivetask.cc
@@ -0,0 +1,158 @@
+/*
+ * libjingle
+ * Copyright 2004--2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/presencereceivetask.h"
+
+#include "talk/base/stringencode.h"
+#include "talk/xmpp/constants.h"
+
+namespace buzz {
+
+static bool IsUtf8FirstByte(int c) {
+  return (((c)&0x80)==0) || // is single byte
+    ((unsigned char)((c)-0xc0)<0x3e); // or is lead byte
+}
+
+PresenceReceiveTask::PresenceReceiveTask(XmppTaskParentInterface* parent)
+ : XmppTask(parent, XmppEngine::HL_TYPE) {
+}
+
+PresenceReceiveTask::~PresenceReceiveTask() {
+  Stop();
+}
+
+int PresenceReceiveTask::ProcessStart() {
+  const XmlElement * stanza = NextStanza();
+  if (stanza == NULL) {
+    return STATE_BLOCKED;
+  }
+
+  Jid from(stanza->Attr(QN_FROM));
+  HandlePresence(from, stanza);
+
+  return STATE_START;
+}
+
+bool PresenceReceiveTask::HandleStanza(const XmlElement * stanza) {
+  // Verify that this is a presence stanze
+  if (stanza->Name() != QN_PRESENCE) {
+    return false; // not sure if this ever happens.
+  }
+
+  // Queue it up
+  QueueStanza(stanza);
+
+  return true;
+}
+
+void PresenceReceiveTask::HandlePresence(const Jid& from,
+                                         const XmlElement* stanza) {
+  if (stanza->Attr(QN_TYPE) == STR_ERROR) {
+    return;
+  }
+
+  PresenceStatus status;
+  DecodeStatus(from, stanza, &status);
+  PresenceUpdate(status);
+}
+
+void PresenceReceiveTask::DecodeStatus(const Jid& from,
+                                       const XmlElement* stanza,
+                                       PresenceStatus* presence_status) {
+  presence_status->set_jid(from);
+  if (stanza->Attr(QN_TYPE) == STR_UNAVAILABLE) {
+    presence_status->set_available(false);
+  } else {
+    presence_status->set_available(true);
+    const XmlElement * status_elem = stanza->FirstNamed(QN_STATUS);
+    if (status_elem != NULL) {
+      presence_status->set_status(status_elem->BodyText());
+
+      // Truncate status messages longer than 300 bytes
+      if (presence_status->status().length() > 300) {
+        size_t len = 300;
+
+        // Be careful not to split legal utf-8 chars in half
+        while (!IsUtf8FirstByte(presence_status->status()[len]) && len > 0) {
+          len -= 1;
+        }
+        std::string truncated(presence_status->status(), 0, len);
+        presence_status->set_status(truncated);
+      }
+    }
+
+    const XmlElement * priority = stanza->FirstNamed(QN_PRIORITY);
+    if (priority != NULL) {
+      int pri;
+      if (talk_base::FromString(priority->BodyText(), &pri)) {
+        presence_status->set_priority(pri);
+      }
+    }
+
+    const XmlElement * show = stanza->FirstNamed(QN_SHOW);
+    if (show == NULL || show->FirstChild() == NULL) {
+      presence_status->set_show(PresenceStatus::SHOW_ONLINE);
+    } else if (show->BodyText() == "away") {
+      presence_status->set_show(PresenceStatus::SHOW_AWAY);
+    } else if (show->BodyText() == "xa") {
+      presence_status->set_show(PresenceStatus::SHOW_XA);
+    } else if (show->BodyText() == "dnd") {
+      presence_status->set_show(PresenceStatus::SHOW_DND);
+    } else if (show->BodyText() == "chat") {
+      presence_status->set_show(PresenceStatus::SHOW_CHAT);
+    } else {
+      presence_status->set_show(PresenceStatus::SHOW_ONLINE);
+    }
+
+    const XmlElement * caps = stanza->FirstNamed(QN_CAPS_C);
+    if (caps != NULL) {
+      std::string node = caps->Attr(QN_NODE);
+      std::string ver = caps->Attr(QN_VER);
+      std::string exts = caps->Attr(QN_EXT);
+
+      presence_status->set_know_capabilities(true);
+      presence_status->set_caps_node(node);
+      presence_status->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);
+      presence_status->set_sent_time(stamp);
+    }
+
+    const XmlElement* nick = stanza->FirstNamed(QN_NICKNAME);
+    if (nick) {
+      presence_status->set_nick(nick->BodyText());
+    }
+  }
+}
+
+} // namespace buzz
diff --git a/talk/xmpp/presencereceivetask.h b/talk/xmpp/presencereceivetask.h
new file mode 100644
index 0000000..2bd6494
--- /dev/null
+++ b/talk/xmpp/presencereceivetask.h
@@ -0,0 +1,73 @@
+/*
+ * libjingle
+ * Copyright 2004--2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_LIBJINGLE_FILES_TALK_XMPP_PRESENCERECEIVETASK_H_
+#define THIRD_PARTY_LIBJINGLE_FILES_TALK_XMPP_PRESENCERECEIVETASK_H_
+
+#include "talk/base/sigslot.h"
+
+#include "talk/xmpp/presencestatus.h"
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+
+// A task to receive presence status callbacks from the XMPP server.
+class PresenceReceiveTask : public XmppTask {
+ public:
+  // Arguments:
+  //   parent a reference to task interface associated withe the XMPP client.
+  explicit PresenceReceiveTask(XmppTaskParentInterface* parent);
+
+  // Shuts down the thread associated with this task.
+  virtual ~PresenceReceiveTask();
+
+  // Starts pulling queued status messages and dispatching them to the
+  // PresenceUpdate() callback.
+  virtual int ProcessStart();
+
+  // Slot for presence message callbacks
+  sigslot::signal1<const PresenceStatus&> PresenceUpdate;
+
+ protected:
+  // Called by the XMPP engine when presence stanzas are received from the
+  // server.
+  virtual bool HandleStanza(const XmlElement * stanza);
+
+ private:
+  // Handles presence stanzas by converting the data to PresenceStatus
+  // objects and passing those along to the SignalStatusUpadate() callback.
+  void HandlePresence(const Jid& from, const XmlElement * stanza);
+
+  // Extracts presence information for the presence stanza sent form the
+  // server.
+  static void DecodeStatus(const Jid& from, const XmlElement * stanza,
+                           PresenceStatus* status);
+};
+
+} // namespace buzz
+
+#endif // THIRD_PARTY_LIBJINGLE_FILES_TALK_XMPP_PRESENCERECEIVETASK_H_
diff --git a/talk/xmpp/presencestatus.cc b/talk/xmpp/presencestatus.cc
new file mode 100644
index 0000000..c75b705
--- /dev/null
+++ b/talk/xmpp/presencestatus.cc
@@ -0,0 +1,62 @@
+/*
+ * libjingle
+ * Copyright 2004--2012, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/presencestatus.h"
+
+namespace buzz {
+PresenceStatus::PresenceStatus()
+  : 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) {
+}
+
+void PresenceStatus::UpdateWith(const PresenceStatus& new_value) {
+  if (!new_value.know_capabilities()) {
+    bool k = know_capabilities();
+    bool p = voice_capability();
+    std::string node = caps_node();
+    std::string v = version();
+
+    *this = new_value;
+
+    set_know_capabilities(k);
+    set_caps_node(node);
+    set_voice_capability(p);
+     set_version(v);
+  } else {
+    *this = new_value;
+  }
+}
+
+} // namespace buzz
diff --git a/talk/xmpp/presencestatus.h b/talk/xmpp/presencestatus.h
new file mode 100644
index 0000000..5cf6b61
--- /dev/null
+++ b/talk/xmpp/presencestatus.h
@@ -0,0 +1,205 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_LIBJINGLE_FILES_TALK_XMPP_PRESENCESTATUS_H_
+#define THIRD_PARTY_LIBJINGLE_FILES_TALK_XMPP_PRESENCESTATUS_H_
+
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/constants.h"
+
+namespace buzz {
+
+class PresenceStatus {
+public:
+  PresenceStatus();
+  ~PresenceStatus() {}
+
+  // These are arranged in "priority order", i.e., if we see
+  // two statuses at the same priority but with different Shows,
+  // we will show the one with the highest show in the following
+  // order.
+  enum Show {
+    SHOW_NONE     = 0,
+    SHOW_OFFLINE  = 1,
+    SHOW_XA       = 2,
+    SHOW_AWAY     = 3,
+    SHOW_DND      = 4,
+    SHOW_ONLINE   = 5,
+    SHOW_CHAT     = 6,
+  };
+
+  const Jid& jid() const { return jid_; }
+  int priority() const { return pri_; }
+  Show show() const { return show_; }
+  const std::string& status() const { return status_; }
+  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_; }
+  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_; }
+  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_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_available(bool a) { available_ = a; }
+  void set_error(int e_code, const std::string e_str)
+      { e_code_ = e_code; e_str_ = e_str; }
+  void set_know_capabilities(bool f) { know_capabilities_ = f; }
+  void set_voice_capability(bool f) { voice_capability_ = f; }
+  void set_pmuc_capability(bool f) { pmuc_capability_ = f; }
+  void set_video_capability(bool f) { video_capability_ = f; }
+  void set_camera_capability(bool f) { camera_capability_ = f; }
+  void set_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 PresenceStatus& new_value);
+
+  bool HasQuietStatus() const {
+    if (status_.empty())
+      return false;
+    return !(QuietStatus().empty());
+  }
+
+  // Knowledge of other clients' silly automatic status strings -
+  // Don't show these.
+  std::string QuietStatus() const {
+    if (jid_.resource().find("Psi") != std::string::npos) {
+      if (status_ == "Online" ||
+          status_.find("Auto Status") != std::string::npos)
+        return STR_EMPTY;
+    }
+    if (jid_.resource().find("Gaim") != std::string::npos) {
+      if (status_ == "Sorry, I ran out for a bit!")
+        return STR_EMPTY;
+    }
+    return TrimStatus(status_);
+  }
+
+  std::string ExplicitStatus() const {
+    std::string result = QuietStatus();
+    if (result.empty()) {
+      result = ShowStatus();
+    }
+    return result;
+  }
+
+  std::string ShowStatus() const {
+    std::string result;
+    if (!available()) {
+      result = "Offline";
+    }
+    else {
+      switch (show()) {
+        case SHOW_AWAY:
+        case SHOW_XA:
+          result = "Idle";
+          break;
+        case SHOW_DND:
+          result = "Busy";
+          break;
+        case SHOW_CHAT:
+          result = "Chatty";
+          break;
+        default:
+          result = "Available";
+          break;
+      }
+    }
+    return result;
+  }
+
+  static std::string TrimStatus(const std::string& st) {
+    std::string s(st);
+    int j = 0;
+    bool collapsing = true;
+    for (unsigned int i = 0; i < s.length(); i+= 1) {
+      if (s[i] <= ' ' && s[i] >= 0) {
+        if (collapsing) {
+          continue;
+        }
+        else {
+          s[j] = ' ';
+          j += 1;
+          collapsing = true;
+        }
+      }
+      else {
+        s[j] = s[i];
+        j += 1;
+        collapsing = false;
+      }
+    }
+    if (collapsing && j > 0) {
+      j -= 1;
+    }
+    s.erase(j, s.length());
+    return s;
+  }
+
+private:
+  Jid jid_;
+  int pri_;
+  Show show_;
+  std::string status_;
+  std::string nick_;
+  bool available_;
+  int e_code_;
+  std::string e_str_;
+  bool feedback_probation_;
+
+  // capabilities (valid only if know_capabilities_
+  bool know_capabilities_;
+  bool voice_capability_;
+  bool pmuc_capability_;
+  bool video_capability_;
+  bool camera_capability_;
+  std::string caps_node_;
+  std::string version_;
+
+  std::string sent_time_; // from the jabber:x:delay element
+};
+
+class MucPresenceStatus : public PresenceStatus {
+};
+
+} // namespace buzz
+
+
+#endif // THIRD_PARTY_LIBJINGLE_FILES_TALK_XMPP_PRESENCESTATUS_H_
+
diff --git a/talk/xmpp/prexmppauth.h b/talk/xmpp/prexmppauth.h
new file mode 100644
index 0000000..3bc5ca6
--- /dev/null
+++ b/talk/xmpp/prexmppauth.h
@@ -0,0 +1,88 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_XMPP_PREXMPPAUTH_H_
+#define TALK_XMPP_PREXMPPAUTH_H_
+
+#include "talk/base/cryptstring.h"
+#include "talk/base/sigslot.h"
+#include "talk/xmpp/saslhandler.h"
+
+namespace talk_base {
+  class SocketAddress;
+}
+
+namespace buzz {
+
+class Jid;
+class SaslMechanism;
+
+class CaptchaChallenge {
+ public:
+  CaptchaChallenge() : captcha_needed_(false) {}
+  CaptchaChallenge(const std::string& token, const std::string& url)
+    : captcha_needed_(true), captcha_token_(token), captcha_image_url_(url) {
+  }
+
+  bool captcha_needed() const { return captcha_needed_; }
+  const std::string& captcha_token() const { return captcha_token_; }
+
+  // This url is relative to the gaia server.  Once we have better tools
+  // for cracking URLs, we should probably make this a full URL
+  const std::string& captcha_image_url() const { return captcha_image_url_; }
+
+ private:
+  bool captcha_needed_;
+  std::string captcha_token_;
+  std::string captcha_image_url_;
+};
+
+class PreXmppAuth : public SaslHandler {
+public:
+  virtual ~PreXmppAuth() {}
+
+  virtual void StartPreXmppAuth(
+    const Jid& jid,
+    const talk_base::SocketAddress& server,
+    const talk_base::CryptString& pass,
+    const std::string& auth_mechanism,
+    const std::string& auth_token) = 0;
+
+  sigslot::signal0<> SignalAuthDone;
+
+  virtual bool IsAuthDone() const = 0;
+  virtual bool IsAuthorized() const = 0;
+  virtual bool HadError() const = 0;
+  virtual int GetError() const = 0;
+  virtual CaptchaChallenge GetCaptchaChallenge() const = 0;
+  virtual std::string GetAuthMechanism() const = 0;
+  virtual std::string GetAuthToken() const = 0;
+};
+
+}
+
+#endif  // TALK_XMPP_PREXMPPAUTH_H_
diff --git a/talk/xmpp/pubsub_task.cc b/talk/xmpp/pubsub_task.cc
new file mode 100644
index 0000000..91e2c72
--- /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) {
+    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.cc b/talk/xmpp/pubsubclient.cc
new file mode 100644
index 0000000..8d6d4c4
--- /dev/null
+++ b/talk/xmpp/pubsubclient.cc
@@ -0,0 +1,137 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/xmpp/pubsubclient.h"
+
+#include <string>
+#include <vector>
+
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/pubsubtasks.h"
+
+namespace buzz {
+
+void PubSubClient::RequestItems() {
+  PubSubRequestTask* request_task =
+      new PubSubRequestTask(parent_, pubsubjid_, node_);
+  request_task->SignalResult.connect(this, &PubSubClient::OnRequestResult);
+  request_task->SignalError.connect(this, &PubSubClient::OnRequestError);
+
+  PubSubReceiveTask* receive_task =
+      new PubSubReceiveTask(parent_, pubsubjid_, node_);
+  receive_task->SignalUpdate.connect(this, &PubSubClient::OnReceiveUpdate);
+
+  receive_task->Start();
+  request_task->Start();
+}
+
+void PubSubClient::PublishItem(
+    const std::string& itemid, XmlElement* payload, std::string* task_id_out) {
+  std::vector<XmlElement*> children;
+  children.push_back(payload);
+  PublishItem(itemid, children, task_id_out);
+}
+
+void PubSubClient::PublishItem(
+    const std::string& itemid, const std::vector<XmlElement*>& children,
+    std::string* task_id_out) {
+  PubSubPublishTask* publish_task =
+      new PubSubPublishTask(parent_, pubsubjid_, node_, itemid, children);
+  publish_task->SignalError.connect(this, &PubSubClient::OnPublishError);
+  publish_task->SignalResult.connect(this, &PubSubClient::OnPublishResult);
+  publish_task->Start();
+  if (task_id_out) {
+    *task_id_out = publish_task->task_id();
+  }
+}
+
+void PubSubClient::RetractItem(
+    const std::string& itemid, std::string* task_id_out) {
+  PubSubRetractTask* retract_task =
+      new PubSubRetractTask(parent_, pubsubjid_, node_, itemid);
+  retract_task->SignalError.connect(this, &PubSubClient::OnRetractError);
+  retract_task->SignalResult.connect(this, &PubSubClient::OnRetractResult);
+  retract_task->Start();
+  if (task_id_out) {
+    *task_id_out = retract_task->task_id();
+  }
+}
+
+void PubSubClient::OnRequestResult(PubSubRequestTask* task,
+                                   const std::vector<PubSubItem>& items) {
+  SignalItems(this, items);
+}
+
+void PubSubClient::OnRequestError(IqTask* task,
+                                  const XmlElement* stanza) {
+  SignalRequestError(this, stanza);
+}
+
+void PubSubClient::OnReceiveUpdate(PubSubReceiveTask* task,
+                                   const std::vector<PubSubItem>& items) {
+  SignalItems(this, items);
+}
+
+const XmlElement* GetItemFromStanza(const XmlElement* stanza) {
+  if (stanza != NULL) {
+    const XmlElement* pubsub = stanza->FirstNamed(QN_PUBSUB);
+    if (pubsub != NULL) {
+      const XmlElement* publish = pubsub->FirstNamed(QN_PUBSUB_PUBLISH);
+      if (publish != NULL) {
+        return publish->FirstNamed(QN_PUBSUB_ITEM);
+      }
+    }
+  }
+  return NULL;
+}
+
+void PubSubClient::OnPublishResult(PubSubPublishTask* task) {
+  const XmlElement* item = GetItemFromStanza(task->stanza());
+  SignalPublishResult(this, task->task_id(), item);
+}
+
+void PubSubClient::OnPublishError(IqTask* task,
+                                  const XmlElement* error_stanza) {
+  PubSubPublishTask* publish_task =
+      static_cast<PubSubPublishTask*>(task);
+  const XmlElement* item = GetItemFromStanza(publish_task->stanza());
+  SignalPublishError(this, publish_task->task_id(), item, error_stanza);
+}
+
+void PubSubClient::OnRetractResult(PubSubRetractTask* task) {
+  SignalRetractResult(this, task->task_id());
+}
+
+void PubSubClient::OnRetractError(IqTask* task,
+                                  const XmlElement* stanza) {
+  PubSubRetractTask* retract_task =
+      static_cast<PubSubRetractTask*>(task);
+  SignalRetractError(this, retract_task->task_id(), stanza);
+}
+
+}  // namespace buzz
diff --git a/talk/xmpp/pubsubclient.h b/talk/xmpp/pubsubclient.h
new file mode 100644
index 0000000..099765a
--- /dev/null
+++ b/talk/xmpp/pubsubclient.h
@@ -0,0 +1,125 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_XMPP_PUBSUBCLIENT_H_
+#define TALK_XMPP_PUBSUBCLIENT_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/sigslot.h"
+#include "talk/base/sigslotrepeater.h"
+#include "talk/base/task.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/pubsubtasks.h"
+
+// Easy to use clients built on top of the tasks for XEP-0060
+// (http://xmpp.org/extensions/xep-0060.html).
+
+namespace buzz {
+
+class Jid;
+class XmlElement;
+class XmppTaskParentInterface;
+
+// An easy-to-use pubsub client that handles the three tasks of
+// getting, publishing, and listening for updates.  Tied to a specific
+// pubsub jid and node.  All you have to do is RequestItems, listen
+// for SignalItems and PublishItems.
+class PubSubClient : public sigslot::has_slots<> {
+ public:
+  PubSubClient(XmppTaskParentInterface* parent,
+               const Jid& pubsubjid,
+               const std::string& node)
+    : parent_(parent),
+      pubsubjid_(pubsubjid),
+      node_(node) {}
+
+  const std::string& node() const { return node_; }
+
+  // Requests the <pubsub><items>, which will be returned via
+  // SignalItems, or SignalRequestError if there is a failure.  Should
+  // auto-subscribe.
+  void RequestItems();
+  // Fired when either <pubsub><items> are returned or when
+  // <event><items> are received.
+  sigslot::signal2<PubSubClient*,
+                   const std::vector<PubSubItem>&> SignalItems;
+  // Signal (this, error stanza)
+  sigslot::signal2<PubSubClient*,
+                   const XmlElement*> SignalRequestError;
+  // Signal (this, task_id, item, error stanza)
+  sigslot::signal4<PubSubClient*,
+                   const std::string&,
+                   const XmlElement*,
+                   const XmlElement*> SignalPublishError;
+  // Signal (this, task_id, item)
+  sigslot::signal3<PubSubClient*,
+                   const std::string&,
+                   const XmlElement*> SignalPublishResult;
+  // Signal (this, task_id, error stanza)
+  sigslot::signal3<PubSubClient*,
+                   const std::string&,
+                   const XmlElement*> SignalRetractError;
+  // Signal (this, task_id)
+  sigslot::signal2<PubSubClient*,
+                   const std::string&> SignalRetractResult;
+
+  // Publish an item.  Takes ownership of payload.
+  void PublishItem(const std::string& itemid,
+                   XmlElement* payload,
+                   std::string* task_id_out);
+  // Publish an item.  Takes ownership of children.
+  void PublishItem(const std::string& itemid,
+                   const std::vector<XmlElement*>& children,
+                   std::string* task_id_out);
+  // Retract (delete) an item.
+  void RetractItem(const std::string& itemid,
+                   std::string* task_id_out);
+
+ private:
+  void OnRequestError(IqTask* task,
+                      const XmlElement* stanza);
+  void OnRequestResult(PubSubRequestTask* task,
+                       const std::vector<PubSubItem>& items);
+  void OnReceiveUpdate(PubSubReceiveTask* task,
+                       const std::vector<PubSubItem>& items);
+  void OnPublishResult(PubSubPublishTask* task);
+  void OnPublishError(IqTask* task,
+                      const XmlElement* stanza);
+  void OnRetractResult(PubSubRetractTask* task);
+  void OnRetractError(IqTask* task,
+                      const XmlElement* stanza);
+
+  XmppTaskParentInterface* parent_;
+  Jid pubsubjid_;
+  std::string node_;
+};
+
+}  // namespace buzz
+
+#endif  // TALK_XMPP_PUBSUBCLIENT_H_
diff --git a/talk/xmpp/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.cc b/talk/xmpp/pubsubtasks.cc
new file mode 100644
index 0000000..bbefbe5
--- /dev/null
+++ b/talk/xmpp/pubsubtasks.cc
@@ -0,0 +1,214 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/xmpp/pubsubtasks.h"
+
+#include <string>
+#include <vector>
+
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/receivetask.h"
+
+// An implementation of the tasks for XEP-0060
+// (http://xmpp.org/extensions/xep-0060.html).
+
+namespace buzz {
+
+namespace {
+
+bool IsPubSubEventItemsElem(const XmlElement* stanza,
+                            const std::string& expected_node) {
+  if (stanza->Name() != QN_MESSAGE) {
+    return false;
+  }
+
+  const XmlElement* event_elem = stanza->FirstNamed(QN_PUBSUB_EVENT);
+  if (event_elem == NULL) {
+    return false;
+  }
+
+  const XmlElement* items_elem = event_elem->FirstNamed(QN_PUBSUB_EVENT_ITEMS);
+  if (items_elem == NULL) {
+    return false;
+  }
+
+  const std::string& actual_node = items_elem->Attr(QN_NODE);
+  return (actual_node == expected_node);
+}
+
+
+// Creates <pubsub node="node"><items></pubsub>
+XmlElement* CreatePubSubItemsElem(const std::string& node) {
+  XmlElement* items_elem = new XmlElement(QN_PUBSUB_ITEMS, false);
+  items_elem->AddAttr(QN_NODE, node);
+  XmlElement* pubsub_elem = new XmlElement(QN_PUBSUB, false);
+  pubsub_elem->AddElement(items_elem);
+  return pubsub_elem;
+}
+
+// Creates <pubsub node="node"><publish><item id="itemid">payload</item>...
+// Takes ownership of payload.
+XmlElement* CreatePubSubPublishItemElem(
+    const std::string& node,
+    const std::string& itemid,
+    const std::vector<XmlElement*>& children) {
+  XmlElement* pubsub_elem = new XmlElement(QN_PUBSUB, true);
+  XmlElement* publish_elem = new XmlElement(QN_PUBSUB_PUBLISH, false);
+  publish_elem->AddAttr(QN_NODE, node);
+  XmlElement* item_elem = new XmlElement(QN_PUBSUB_ITEM, false);
+  item_elem->AddAttr(QN_ID, itemid);
+  for (std::vector<XmlElement*>::const_iterator child = children.begin();
+       child != children.end(); ++child) {
+    item_elem->AddElement(*child);
+  }
+  publish_elem->AddElement(item_elem);
+  pubsub_elem->AddElement(publish_elem);
+  return pubsub_elem;
+}
+
+// Creates <pubsub node="node"><publish><item id="itemid">payload</item>...
+// Takes ownership of payload.
+XmlElement* CreatePubSubRetractItemElem(const std::string& node,
+                                        const std::string& itemid) {
+  XmlElement* pubsub_elem = new XmlElement(QN_PUBSUB, true);
+  XmlElement* retract_elem = new XmlElement(QN_PUBSUB_RETRACT, false);
+  retract_elem->AddAttr(QN_NODE, node);
+  retract_elem->AddAttr(QN_NOTIFY, "true");
+  XmlElement* item_elem = new XmlElement(QN_PUBSUB_ITEM, false);
+  item_elem->AddAttr(QN_ID, itemid);
+  retract_elem->AddElement(item_elem);
+  pubsub_elem->AddElement(retract_elem);
+  return pubsub_elem;
+}
+
+void ParseItem(const XmlElement* item_elem,
+               std::vector<PubSubItem>* items) {
+  PubSubItem item;
+  item.itemid = item_elem->Attr(QN_ID);
+  item.elem = item_elem;
+  items->push_back(item);
+}
+
+// Right now, <retract>s are treated the same as items with empty
+// payloads.  We may want to change it in the future, but right now
+// it's sufficient for our needs.
+void ParseRetract(const XmlElement* retract_elem,
+                  std::vector<PubSubItem>* items) {
+  ParseItem(retract_elem, items);
+}
+
+void ParseEventItemsElem(const XmlElement* stanza,
+                         std::vector<PubSubItem>* items) {
+  const XmlElement* event_elem = stanza->FirstNamed(QN_PUBSUB_EVENT);
+  if (event_elem != NULL) {
+    const XmlElement* items_elem =
+        event_elem->FirstNamed(QN_PUBSUB_EVENT_ITEMS);
+    if (items_elem != NULL) {
+      for (const XmlElement* item_elem =
+             items_elem->FirstNamed(QN_PUBSUB_EVENT_ITEM);
+           item_elem != NULL;
+           item_elem = item_elem->NextNamed(QN_PUBSUB_EVENT_ITEM)) {
+        ParseItem(item_elem, items);
+      }
+      for (const XmlElement* retract_elem =
+             items_elem->FirstNamed(QN_PUBSUB_EVENT_RETRACT);
+           retract_elem != NULL;
+           retract_elem = retract_elem->NextNamed(QN_PUBSUB_EVENT_RETRACT)) {
+        ParseRetract(retract_elem, items);
+      }
+    }
+  }
+}
+
+void ParsePubSubItemsElem(const XmlElement* stanza,
+                          std::vector<PubSubItem>* items) {
+  const XmlElement* pubsub_elem = stanza->FirstNamed(QN_PUBSUB);
+  if (pubsub_elem != NULL) {
+    const XmlElement* items_elem = pubsub_elem->FirstNamed(QN_PUBSUB_ITEMS);
+    if (items_elem != NULL) {
+      for (const XmlElement* item_elem = items_elem->FirstNamed(QN_PUBSUB_ITEM);
+           item_elem != NULL;
+           item_elem = item_elem->NextNamed(QN_PUBSUB_ITEM)) {
+        ParseItem(item_elem, items);
+      }
+    }
+  }
+}
+
+}  // namespace
+
+PubSubRequestTask::PubSubRequestTask(XmppTaskParentInterface* parent,
+                                     const Jid& pubsubjid,
+                                     const std::string& node)
+    : IqTask(parent, STR_GET, pubsubjid, CreatePubSubItemsElem(node)) {
+}
+
+void PubSubRequestTask::HandleResult(const XmlElement* stanza) {
+  std::vector<PubSubItem> items;
+  ParsePubSubItemsElem(stanza, &items);
+  SignalResult(this, items);
+}
+
+bool PubSubReceiveTask::WantsStanza(const XmlElement* stanza) {
+  return MatchStanzaFrom(stanza, pubsubjid_) &&
+      IsPubSubEventItemsElem(stanza, node_);
+}
+
+void PubSubReceiveTask::ReceiveStanza(const XmlElement* stanza) {
+  std::vector<PubSubItem> items;
+  ParseEventItemsElem(stanza, &items);
+  SignalUpdate(this, items);
+}
+
+PubSubPublishTask::PubSubPublishTask(XmppTaskParentInterface* parent,
+                                     const Jid& pubsubjid,
+                                     const std::string& node,
+                                     const std::string& itemid,
+                                     const std::vector<XmlElement*>& children)
+    : IqTask(parent, STR_SET, pubsubjid,
+             CreatePubSubPublishItemElem(node, itemid, children)),
+      itemid_(itemid) {
+}
+
+void PubSubPublishTask::HandleResult(const XmlElement* stanza) {
+  SignalResult(this);
+}
+
+PubSubRetractTask::PubSubRetractTask(XmppTaskParentInterface* parent,
+                                     const Jid& pubsubjid,
+                                     const std::string& node,
+                                     const std::string& itemid)
+    : IqTask(parent, STR_SET, pubsubjid,
+             CreatePubSubRetractItemElem(node, itemid)),
+      itemid_(itemid) {
+}
+
+void PubSubRetractTask::HandleResult(const XmlElement* stanza) {
+  SignalResult(this);
+}
+
+}  // namespace buzz
diff --git a/talk/xmpp/pubsubtasks.h b/talk/xmpp/pubsubtasks.h
new file mode 100644
index 0000000..f0a1581
--- /dev/null
+++ b/talk/xmpp/pubsubtasks.h
@@ -0,0 +1,130 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_XMPP_PUBSUBTASKS_H_
+#define TALK_XMPP_PUBSUBTASKS_H_
+
+#include <vector>
+
+#include "talk/base/sigslot.h"
+#include "talk/xmpp/iqtask.h"
+#include "talk/xmpp/receivetask.h"
+
+namespace buzz {
+
+// A PubSub itemid + payload.  Useful for signaling items.
+struct PubSubItem {
+  std::string itemid;
+  // The entire <item>, owned by the stanza handler.  To keep a
+  // reference after handling, make a copy.
+  const XmlElement* elem;
+};
+
+// An IqTask which gets a <pubsub><items> for a particular jid and
+// node, parses the items in the response and signals the items.
+class PubSubRequestTask : public IqTask {
+ public:
+  PubSubRequestTask(XmppTaskParentInterface* parent,
+                    const Jid& pubsubjid,
+                    const std::string& node);
+
+  sigslot::signal2<PubSubRequestTask*,
+                   const std::vector<PubSubItem>&> SignalResult;
+  // SignalError inherited by IqTask.
+ private:
+  virtual void HandleResult(const XmlElement* stanza);
+};
+
+// A ReceiveTask which listens for <event><items> of a particular
+// pubsub JID and node and then signals them items.
+class PubSubReceiveTask : public ReceiveTask {
+ public:
+  PubSubReceiveTask(XmppTaskParentInterface* parent,
+                    const Jid& pubsubjid,
+                    const std::string& node)
+      : ReceiveTask(parent),
+        pubsubjid_(pubsubjid),
+        node_(node) {
+  }
+
+  sigslot::signal2<PubSubReceiveTask*,
+                   const std::vector<PubSubItem>&> SignalUpdate;
+
+ protected:
+  virtual bool WantsStanza(const XmlElement* stanza);
+  virtual void ReceiveStanza(const XmlElement* stanza);
+
+ private:
+  Jid pubsubjid_;
+  std::string node_;
+};
+
+// An IqTask which publishes a <pubsub><publish><item> to a particular
+// pubsub jid and node.
+class PubSubPublishTask : public IqTask {
+ public:
+  // Takes ownership of children
+  PubSubPublishTask(XmppTaskParentInterface* parent,
+                    const Jid& pubsubjid,
+                    const std::string& node,
+                    const std::string& itemid,
+                    const std::vector<XmlElement*>& children);
+
+  const std::string& itemid() const { return itemid_; }
+
+  sigslot::signal1<PubSubPublishTask*> SignalResult;
+
+ private:
+  // SignalError inherited by IqTask.
+  virtual void HandleResult(const XmlElement* stanza);
+
+  std::string itemid_;
+};
+
+// An IqTask which publishes a <pubsub><publish><retract> to a particular
+// pubsub jid and node.
+class PubSubRetractTask : public IqTask {
+ public:
+  PubSubRetractTask(XmppTaskParentInterface* parent,
+                    const Jid& pubsubjid,
+                    const std::string& node,
+                    const std::string& itemid);
+
+  const std::string& itemid() const { return itemid_; }
+
+  sigslot::signal1<PubSubRetractTask*> SignalResult;
+
+ private:
+  // SignalError inherited by IqTask.
+  virtual void HandleResult(const XmlElement* stanza);
+
+  std::string itemid_;
+};
+
+}  // namespace buzz
+
+#endif  // TALK_XMPP_PUBSUBTASKS_H_
diff --git a/talk/xmpp/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/receivetask.cc b/talk/xmpp/receivetask.cc
new file mode 100644
index 0000000..53fac7e
--- /dev/null
+++ b/talk/xmpp/receivetask.cc
@@ -0,0 +1,51 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/xmpp/receivetask.h"
+#include "talk/xmpp/constants.h"
+
+namespace buzz {
+
+bool ReceiveTask::HandleStanza(const XmlElement* stanza) {
+  if (WantsStanza(stanza)) {
+    QueueStanza(stanza);
+    return true;
+  }
+
+  return false;
+}
+
+int ReceiveTask::ProcessStart() {
+  const XmlElement* stanza = NextStanza();
+  if (stanza == NULL)
+    return STATE_BLOCKED;
+
+  ReceiveStanza(stanza);
+  return STATE_START;
+}
+
+}  // namespace buzz
diff --git a/talk/xmpp/receivetask.h b/talk/xmpp/receivetask.h
new file mode 100644
index 0000000..b18e0f0
--- /dev/null
+++ b/talk/xmpp/receivetask.h
@@ -0,0 +1,58 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_XMPP_RECEIVETASK_H_
+#define TALK_XMPP_RECEIVETASK_H_
+
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+
+// A base class for receiving stanzas.  Override WantsStanza to
+// indicate that a stanza should be received and ReceiveStanza to
+// process it.  Once started, ReceiveStanza will be called for all
+// stanzas that return true when passed to WantsStanza. This saves
+// you from having to remember how to setup the queueing and the task
+// states, etc.
+class ReceiveTask : public XmppTask {
+ public:
+  explicit ReceiveTask(XmppTaskParentInterface* parent) :
+      XmppTask(parent, XmppEngine::HL_TYPE) {}
+  virtual int ProcessStart();
+
+ protected:
+  virtual bool HandleStanza(const XmlElement* stanza);
+
+  // Return true if the stanza should be received.
+  virtual bool WantsStanza(const XmlElement* stanza) = 0;
+  // Process the received stanza.
+  virtual void ReceiveStanza(const XmlElement* stanza) = 0;
+};
+
+}  // namespace buzz
+
+#endif  // TALK_XMPP_RECEIVETASK_H_
diff --git a/talk/xmpp/rostermodule.h b/talk/xmpp/rostermodule.h
new file mode 100644
index 0000000..eafd595
--- /dev/null
+++ b/talk/xmpp/rostermodule.h
@@ -0,0 +1,343 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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,
+};
+
+enum XmppPresenceConnectionStatus {
+  XMPP_CONNECTION_STATUS_UNKNOWN    = 0,
+  XMPP_CONNECTION_STATUS_CONNECTING = 1,
+  XMPP_CONNECTION_STATUS_CONNECTED  = 2,
+  XMPP_CONNECTION_STATUS_HANGUP     = 3,
+};
+
+//! 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 connection status
+  virtual XmppPresenceConnectionStatus connection_status() const = 0;
+
+  //! The focus obfuscated GAIA id
+  virtual const std::string google_user_id() const = 0;
+
+  //! The nickname in the presence
+  virtual const std::string nickname() const = 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:
+  virtual ~XmppRosterHandler() {}
+
+  //! 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/rostermodule_unittest.cc b/talk/xmpp/rostermodule_unittest.cc
new file mode 100644
index 0000000..9273eb5
--- /dev/null
+++ b/talk/xmpp/rostermodule_unittest.cc
@@ -0,0 +1,849 @@
+/*
+ * 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/gunit.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/rostermodule.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/util_unittest.h"
+
+#define TEST_OK(x) EXPECT_EQ((x),XMPP_RETURN_OK)
+#define TEST_BADARGUMENT(x) EXPECT_EQ((x),XMPP_RETURN_BADARGUMENT)
+
+
+namespace buzz {
+
+class RosterModuleTest;
+
+static void
+WriteString(std::ostream& os, const std::string& str) {
+  os<<str;
+}
+
+static void
+WriteSubscriptionState(std::ostream& os, XmppSubscriptionState state)
+{
+  switch (state) {
+    case XMPP_SUBSCRIPTION_NONE:
+      os<<"none";
+      break;
+    case XMPP_SUBSCRIPTION_NONE_ASKED:
+      os<<"none_asked";
+      break;
+    case XMPP_SUBSCRIPTION_TO:
+      os<<"to";
+      break;
+    case XMPP_SUBSCRIPTION_FROM:
+      os<<"from";
+      break;
+    case XMPP_SUBSCRIPTION_FROM_ASKED:
+      os<<"from_asked";
+      break;
+    case XMPP_SUBSCRIPTION_BOTH:
+      os<<"both";
+      break;
+    default:
+      os<<"unknown";
+      break;
+  }
+}
+
+static void
+WriteSubscriptionRequestType(std::ostream& os,
+                             XmppSubscriptionRequestType type) {
+  switch(type) {
+    case XMPP_REQUEST_SUBSCRIBE:
+      os<<"subscribe";
+      break;
+    case XMPP_REQUEST_UNSUBSCRIBE:
+      os<<"unsubscribe";
+      break;
+    case XMPP_REQUEST_SUBSCRIBED:
+      os<<"subscribed";
+      break;
+    case XMPP_REQUEST_UNSUBSCRIBED:
+      os<<"unsubscribe";
+      break;
+    default:
+      os<<"unknown";
+      break;
+  }
+}
+
+static void
+WritePresenceShow(std::ostream& os, XmppPresenceShow show) {
+  switch(show) {
+    case XMPP_PRESENCE_AWAY:
+      os<<"away";
+      break;
+    case XMPP_PRESENCE_CHAT:
+      os<<"chat";
+      break;
+    case XMPP_PRESENCE_DND:
+      os<<"dnd";
+      break;
+    case XMPP_PRESENCE_XA:
+      os<<"xa";
+      break;
+    case XMPP_PRESENCE_DEFAULT:
+      os<<"[default]";
+      break;
+    default:
+      os<<"[unknown]";
+      break;
+  }
+}
+
+static void
+WritePresence(std::ostream& os, const XmppPresence* presence) {
+  if (presence == NULL) {
+    os<<"NULL";
+    return;
+  }
+
+  os<<"[Presence jid:";
+  WriteString(os, presence->jid().Str());
+  os<<" available:"<<presence->available();
+  os<<" presence_show:";
+  WritePresenceShow(os, presence->presence_show());
+  os<<" priority:"<<presence->priority();
+  os<<" status:";
+  WriteString(os, presence->status());
+  os<<"]"<<presence->raw_xml()->Str();
+}
+
+static void
+WriteContact(std::ostream& os, const XmppRosterContact* contact) {
+  if (contact == NULL) {
+    os<<"NULL";
+    return;
+  }
+
+  os<<"[Contact jid:";
+  WriteString(os, contact->jid().Str());
+  os<<" name:";
+  WriteString(os, contact->name());
+  os<<" subscription_state:";
+  WriteSubscriptionState(os, contact->subscription_state());
+  os<<" groups:[";
+  for(size_t i=0; i < contact->GetGroupCount(); ++i) {
+    os<<(i==0?"":", ");
+    WriteString(os, contact->GetGroup(i));
+  }
+  os<<"]]"<<contact->raw_xml()->Str();
+}
+
+//! This session handler saves all calls to a string.  These are events and
+//! data delivered form the engine to application code.
+class XmppTestRosterHandler : public XmppRosterHandler {
+public:
+  XmppTestRosterHandler() {}
+  virtual ~XmppTestRosterHandler() {}
+
+  virtual void SubscriptionRequest(XmppRosterModule*,
+                                   const Jid& requesting_jid,
+                                   XmppSubscriptionRequestType type,
+                                   const XmlElement* raw_xml) {
+    ss_<<"[SubscriptionRequest Jid:" << requesting_jid.Str()<<" type:";
+    WriteSubscriptionRequestType(ss_, type);
+    ss_<<"]"<<raw_xml->Str();
+  }
+
+  //! Some type of presence error has occured
+  virtual void SubscriptionError(XmppRosterModule*,
+                                 const Jid& from,
+                                 const XmlElement* raw_xml) {
+    ss_<<"[SubscriptionError from:"<<from.Str()<<"]"<<raw_xml->Str();
+  }
+
+  virtual void RosterError(XmppRosterModule*,
+                           const XmlElement* raw_xml) {
+    ss_<<"[RosterError]"<<raw_xml->Str();
+  }
+
+  //! 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*,
+                                       const XmppPresence* presence) {
+    ss_<<"[IncomingPresenceChanged presence:";
+    WritePresence(ss_, presence);
+    ss_<<"]";
+  }
+
+  //! 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) {
+    ss_<<"[ContactChanged old_contact:";
+    WriteContact(ss_, old_contact);
+    ss_<<" index:"<<index<<" new_contact:";
+    WriteContact(ss_, roster->GetRosterContact(index));
+    ss_<<"]";
+  }
+
+  //! 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) {
+    ss_<<"[ContactsAdded index:"<<index<<" number:"<<number;
+    for (size_t i = 0; i < number; ++i) {
+      ss_<<" "<<(index+i)<<":";
+      WriteContact(ss_, roster->GetRosterContact(index+i));
+    }
+    ss_<<"]";
+  }
+
+  //! A contact has been removed
+  //! This contact has been removed form the list.
+  virtual void ContactRemoved(XmppRosterModule*,
+                              const XmppRosterContact* removed_contact,
+                              size_t index) {
+    ss_<<"[ContactRemoved old_contact:";
+    WriteContact(ss_, removed_contact);
+    ss_<<" index:"<<index<<"]";
+  }
+
+  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 RosterModuleTest : public testing::Test {
+public:
+  RosterModuleTest() {}
+  static void RunLogin(RosterModuleTest* obj, XmppEngine* engine,
+                       XmppTestHandler* handler) {
+    // Needs to be similar to XmppEngineTest::RunLogin
+  }
+};
+
+TEST_F(RosterModuleTest, TestPresence) {
+  XmlElement* status = new XmlElement(QN_GOOGLE_PSTN_CONFERENCE_STATUS);
+  status->AddAttr(QN_STATUS, STR_PSTN_CONFERENCE_STATUS_CONNECTING);
+  XmlElement presence_xml(QN_PRESENCE);
+  presence_xml.AddElement(status);
+  talk_base::scoped_ptr<XmppPresence> presence(XmppPresence::Create());
+  presence->set_raw_xml(&presence_xml);
+  EXPECT_EQ(presence->connection_status(), XMPP_CONNECTION_STATUS_CONNECTING);
+}
+
+TEST_F(RosterModuleTest, TestOutgoingPresence) {
+  std::stringstream dump;
+
+  talk_base::scoped_ptr<XmppEngine> engine(XmppEngine::Create());
+  XmppTestHandler handler(engine.get());
+  XmppTestRosterHandler roster_handler;
+
+  talk_base::scoped_ptr<XmppRosterModule> roster(XmppRosterModule::Create());
+  roster->set_roster_handler(&roster_handler);
+
+  // Configure the roster module
+  roster->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);
+  EXPECT_EQ("", handler.OutputActivity());
+
+  // Set some presence and broadcast it
+  TEST_OK(roster->outgoing_presence()->
+    set_available(XMPP_PRESENCE_AVAILABLE));
+  TEST_OK(roster->outgoing_presence()->set_priority(-37));
+  TEST_OK(roster->outgoing_presence()->set_presence_show(XMPP_PRESENCE_DND));
+  TEST_OK(roster->outgoing_presence()->
+    set_status("I'm off to the races!<>&"));
+  TEST_OK(roster->BroadcastPresence());
+
+  EXPECT_EQ(roster_handler.StrClear(), "");
+  EXPECT_EQ(handler.OutputActivity(),
+    "<presence>"
+      "<priority>-37</priority>"
+      "<show>dnd</show>"
+      "<status>I'm off to the races!&lt;&gt;&amp;</status>"
+    "</presence>");
+  EXPECT_EQ(handler.SessionActivity(), "");
+
+  // Try some more
+  TEST_OK(roster->outgoing_presence()->
+    set_available(XMPP_PRESENCE_UNAVAILABLE));
+  TEST_OK(roster->outgoing_presence()->set_priority(0));
+  TEST_OK(roster->outgoing_presence()->set_presence_show(XMPP_PRESENCE_XA));
+  TEST_OK(roster->outgoing_presence()->set_status("Gone fishin'"));
+  TEST_OK(roster->BroadcastPresence());
+
+  EXPECT_EQ(roster_handler.StrClear(), "");
+  EXPECT_EQ(handler.OutputActivity(),
+    "<presence type=\"unavailable\">"
+      "<show>xa</show>"
+      "<status>Gone fishin'</status>"
+    "</presence>");
+  EXPECT_EQ(handler.SessionActivity(), "");
+
+  // Okay -- we are back on
+  TEST_OK(roster->outgoing_presence()->
+    set_available(XMPP_PRESENCE_AVAILABLE));
+  TEST_BADARGUMENT(roster->outgoing_presence()->set_priority(128));
+  TEST_OK(roster->outgoing_presence()->
+      set_presence_show(XMPP_PRESENCE_DEFAULT));
+  TEST_OK(roster->outgoing_presence()->set_status("Cookin' wit gas"));
+  TEST_OK(roster->BroadcastPresence());
+
+  EXPECT_EQ(roster_handler.StrClear(), "");
+  EXPECT_EQ(handler.OutputActivity(),
+    "<presence>"
+      "<status>Cookin' wit gas</status>"
+    "</presence>");
+  EXPECT_EQ(handler.SessionActivity(), "");
+
+  // Set it via XML
+  XmlElement presence_input(QN_PRESENCE);
+  presence_input.AddAttr(QN_TYPE, "unavailable");
+  presence_input.AddElement(new XmlElement(QN_PRIORITY));
+  presence_input.AddText("42", 1);
+  presence_input.AddElement(new XmlElement(QN_STATUS));
+  presence_input.AddAttr(QN_XML_LANG, "es", 1);
+  presence_input.AddText("Hola Amigos!", 1);
+  presence_input.AddElement(new XmlElement(QN_STATUS));
+  presence_input.AddText("Hey there, friend!", 1);
+  TEST_OK(roster->outgoing_presence()->set_raw_xml(&presence_input));
+  TEST_OK(roster->BroadcastPresence());
+
+  WritePresence(dump, roster->outgoing_presence());
+  EXPECT_EQ(dump.str(),
+    "[Presence jid: available:0 presence_show:[default] "
+              "priority:42 status:Hey there, friend!]"
+    "<cli:presence type=\"unavailable\" xmlns:cli=\"jabber:client\">"
+      "<cli:priority>42</cli:priority>"
+      "<cli:status xml:lang=\"es\">Hola Amigos!</cli:status>"
+      "<cli:status>Hey there, friend!</cli:status>"
+    "</cli:presence>");
+  dump.str("");
+  EXPECT_EQ(roster_handler.StrClear(), "");
+  EXPECT_EQ(handler.OutputActivity(),
+    "<presence type=\"unavailable\">"
+      "<priority>42</priority>"
+      "<status xml:lang=\"es\">Hola Amigos!</status>"
+      "<status>Hey there, friend!</status>"
+    "</presence>");
+  EXPECT_EQ(handler.SessionActivity(), "");
+
+  // Construct a directed presence
+  talk_base::scoped_ptr<XmppPresence> directed_presence(XmppPresence::Create());
+  TEST_OK(directed_presence->set_available(XMPP_PRESENCE_AVAILABLE));
+  TEST_OK(directed_presence->set_priority(120));
+  TEST_OK(directed_presence->set_status("*very* available"));
+  TEST_OK(roster->SendDirectedPresence(directed_presence.get(),
+                                       Jid("myhoney@honey.net")));
+
+  EXPECT_EQ(roster_handler.StrClear(), "");
+  EXPECT_EQ(handler.OutputActivity(),
+    "<presence to=\"myhoney@honey.net\">"
+      "<priority>120</priority>"
+      "<status>*very* available</status>"
+    "</presence>");
+  EXPECT_EQ(handler.SessionActivity(), "");
+}
+
+TEST_F(RosterModuleTest, TestIncomingPresence) {
+  talk_base::scoped_ptr<XmppEngine> engine(XmppEngine::Create());
+  XmppTestHandler handler(engine.get());
+  XmppTestRosterHandler roster_handler;
+
+  talk_base::scoped_ptr<XmppRosterModule> roster(XmppRosterModule::Create());
+  roster->set_roster_handler(&roster_handler);
+
+  // Configure the roster module
+  roster->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);
+  EXPECT_EQ("", handler.OutputActivity());
+
+  // Load up with a bunch of data
+  std::string input;
+  input = "<presence from='maude@example.net/studio' "
+                    "to='david@my-server/test'/>"
+          "<presence from='walter@example.net/home' "
+                    "to='david@my-server/test'>"
+            "<priority>-10</priority>"
+            "<show>xa</show>"
+            "<status>Off bowling</status>"
+          "</presence>"
+          "<presence from='walter@example.net/alley' "
+                    "to='david@my-server/test'>"
+            "<priority>20</priority>"
+            "<status>Looking for toes...</status>"
+          "</presence>"
+          "<presence from='donny@example.net/alley' "
+                    "to='david@my-server/test'>"
+            "<priority>10</priority>"
+            "<status>Throwing rocks</status>"
+          "</presence>";
+  TEST_OK(engine->HandleInput(input.c_str(), input.length()));
+
+  EXPECT_EQ(roster_handler.StrClear(),
+    "[IncomingPresenceChanged "
+      "presence:[Presence jid:maude@example.net/studio available:1 "
+                 "presence_show:[default] priority:0 status:]"
+        "<cli:presence from=\"maude@example.net/studio\" "
+                      "to=\"david@my-server/test\" "
+                      "xmlns:cli=\"jabber:client\"/>]"
+    "[IncomingPresenceChanged "
+      "presence:[Presence jid:walter@example.net/home available:1 "
+                 "presence_show:xa priority:-10 status:Off bowling]"
+        "<cli:presence from=\"walter@example.net/home\" "
+                      "to=\"david@my-server/test\" "
+                      "xmlns:cli=\"jabber:client\">"
+          "<cli:priority>-10</cli:priority>"
+          "<cli:show>xa</cli:show>"
+          "<cli:status>Off bowling</cli:status>"
+        "</cli:presence>]"
+    "[IncomingPresenceChanged "
+      "presence:[Presence jid:walter@example.net/alley available:1 "
+                 "presence_show:[default] "
+                 "priority:20 status:Looking for toes...]"
+        "<cli:presence from=\"walter@example.net/alley\" "
+                       "to=\"david@my-server/test\" "
+                       "xmlns:cli=\"jabber:client\">"
+          "<cli:priority>20</cli:priority>"
+          "<cli:status>Looking for toes...</cli:status>"
+        "</cli:presence>]"
+    "[IncomingPresenceChanged "
+      "presence:[Presence jid:donny@example.net/alley available:1 "
+                 "presence_show:[default] priority:10 status:Throwing rocks]"
+        "<cli:presence from=\"donny@example.net/alley\" "
+                      "to=\"david@my-server/test\" "
+                      "xmlns:cli=\"jabber:client\">"
+          "<cli:priority>10</cli:priority>"
+          "<cli:status>Throwing rocks</cli:status>"
+        "</cli:presence>]");
+  EXPECT_EQ(handler.OutputActivity(), "");
+  handler.SessionActivity(); // Ignore the session output
+
+  // Now look at the data structure we've built
+  EXPECT_EQ(roster->GetIncomingPresenceCount(), static_cast<size_t>(4));
+  EXPECT_EQ(roster->GetIncomingPresenceForJidCount(Jid("maude@example.net")),
+            static_cast<size_t>(1));
+  EXPECT_EQ(roster->GetIncomingPresenceForJidCount(Jid("walter@example.net")),
+            static_cast<size_t>(2));
+
+  const XmppPresence * presence;
+  presence = roster->GetIncomingPresenceForJid(Jid("walter@example.net"), 1);
+
+  std::stringstream dump;
+  WritePresence(dump, presence);
+  EXPECT_EQ(dump.str(),
+          "[Presence jid:walter@example.net/alley available:1 "
+            "presence_show:[default] priority:20 status:Looking for toes...]"
+              "<cli:presence from=\"walter@example.net/alley\" "
+                            "to=\"david@my-server/test\" "
+                            "xmlns:cli=\"jabber:client\">"
+                "<cli:priority>20</cli:priority>"
+                "<cli:status>Looking for toes...</cli:status>"
+              "</cli:presence>");
+  dump.str("");
+
+  // Maude took off...
+  input = "<presence from='maude@example.net/studio' "
+                    "to='david@my-server/test' "
+                    "type='unavailable'>"
+            "<status>Stealing my rug back</status>"
+            "<priority>-10</priority>"
+          "</presence>";
+  TEST_OK(engine->HandleInput(input.c_str(), input.length()));
+
+  EXPECT_EQ(roster_handler.StrClear(),
+    "[IncomingPresenceChanged "
+      "presence:[Presence jid:maude@example.net/studio available:0 "
+                 "presence_show:[default] priority:-10 "
+                 "status:Stealing my rug back]"
+        "<cli:presence from=\"maude@example.net/studio\" "
+                      "to=\"david@my-server/test\" type=\"unavailable\" "
+                      "xmlns:cli=\"jabber:client\">"
+          "<cli:status>Stealing my rug back</cli:status>"
+          "<cli:priority>-10</cli:priority>"
+        "</cli:presence>]");
+  EXPECT_EQ(handler.OutputActivity(), "");
+  handler.SessionActivity(); // Ignore the session output
+}
+
+TEST_F(RosterModuleTest, TestPresenceSubscription) {
+  talk_base::scoped_ptr<XmppEngine> engine(XmppEngine::Create());
+  XmppTestHandler handler(engine.get());
+  XmppTestRosterHandler roster_handler;
+
+  talk_base::scoped_ptr<XmppRosterModule> roster(XmppRosterModule::Create());
+  roster->set_roster_handler(&roster_handler);
+
+  // Configure the roster module
+  roster->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);
+  EXPECT_EQ("", handler.OutputActivity());
+
+  // Test incoming requests
+  std::string input;
+  input =
+    "<presence from='maude@example.net' type='subscribe'/>"
+    "<presence from='maude@example.net' type='unsubscribe'/>"
+    "<presence from='maude@example.net' type='subscribed'/>"
+    "<presence from='maude@example.net' type='unsubscribed'/>";
+  TEST_OK(engine->HandleInput(input.c_str(), input.length()));
+
+  EXPECT_EQ(roster_handler.StrClear(),
+    "[SubscriptionRequest Jid:maude@example.net type:subscribe]"
+      "<cli:presence from=\"maude@example.net\" type=\"subscribe\" "
+                    "xmlns:cli=\"jabber:client\"/>"
+    "[SubscriptionRequest Jid:maude@example.net type:unsubscribe]"
+      "<cli:presence from=\"maude@example.net\" type=\"unsubscribe\" "
+                    "xmlns:cli=\"jabber:client\"/>"
+    "[SubscriptionRequest Jid:maude@example.net type:subscribed]"
+      "<cli:presence from=\"maude@example.net\" type=\"subscribed\" "
+                    "xmlns:cli=\"jabber:client\"/>"
+    "[SubscriptionRequest Jid:maude@example.net type:unsubscribe]"
+      "<cli:presence from=\"maude@example.net\" type=\"unsubscribed\" "
+                    "xmlns:cli=\"jabber:client\"/>");
+  EXPECT_EQ(handler.OutputActivity(), "");
+  handler.SessionActivity(); // Ignore the session output
+
+  TEST_OK(roster->RequestSubscription(Jid("maude@example.net")));
+  TEST_OK(roster->CancelSubscription(Jid("maude@example.net")));
+  TEST_OK(roster->ApproveSubscriber(Jid("maude@example.net")));
+  TEST_OK(roster->CancelSubscriber(Jid("maude@example.net")));
+
+  EXPECT_EQ(roster_handler.StrClear(), "");
+  EXPECT_EQ(handler.OutputActivity(),
+    "<presence to=\"maude@example.net\" type=\"subscribe\"/>"
+    "<presence to=\"maude@example.net\" type=\"unsubscribe\"/>"
+    "<presence to=\"maude@example.net\" type=\"subscribed\"/>"
+    "<presence to=\"maude@example.net\" type=\"unsubscribed\"/>");
+  EXPECT_EQ(handler.SessionActivity(), "");
+}
+
+TEST_F(RosterModuleTest, TestRosterReceive) {
+  talk_base::scoped_ptr<XmppEngine> engine(XmppEngine::Create());
+  XmppTestHandler handler(engine.get());
+  XmppTestRosterHandler roster_handler;
+
+  talk_base::scoped_ptr<XmppRosterModule> roster(XmppRosterModule::Create());
+  roster->set_roster_handler(&roster_handler);
+
+  // Configure the roster module
+  roster->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);
+  EXPECT_EQ("", handler.OutputActivity());
+
+  // Request a roster update
+  TEST_OK(roster->RequestRosterUpdate());
+
+  EXPECT_EQ(roster_handler.StrClear(),"");
+  EXPECT_EQ(handler.OutputActivity(),
+    "<iq type=\"get\" id=\"2\">"
+      "<query xmlns=\"jabber:iq:roster\"/>"
+    "</iq>");
+  EXPECT_EQ(handler.SessionActivity(), "");
+
+  // Prime the roster with a starting set
+  std::string input =
+    "<iq to='david@myserver/test' type='result' id='2'>"
+      "<query xmlns='jabber:iq:roster'>"
+        "<item jid='maude@example.net' "
+              "name='Maude Lebowski' "
+              "subscription='none' "
+              "ask='subscribe'>"
+          "<group>Business Partners</group>"
+        "</item>"
+        "<item jid='walter@example.net' "
+              "name='Walter Sobchak' "
+              "subscription='both'>"
+          "<group>Friends</group>"
+          "<group>Bowling Team</group>"
+          "<group>Bowling League</group>"
+        "</item>"
+        "<item jid='donny@example.net' "
+              "name='Donny' "
+              "subscription='both'>"
+          "<group>Friends</group>"
+          "<group>Bowling Team</group>"
+          "<group>Bowling League</group>"
+        "</item>"
+        "<item jid='jeffrey@example.net' "
+              "name='The Big Lebowski' "
+              "subscription='to'>"
+          "<group>Business Partners</group>"
+        "</item>"
+        "<item jid='jesus@example.net' "
+              "name='Jesus Quintana' "
+              "subscription='from'>"
+          "<group>Bowling League</group>"
+        "</item>"
+      "</query>"
+    "</iq>";
+
+  TEST_OK(engine->HandleInput(input.c_str(), input.length()));
+
+  EXPECT_EQ(roster_handler.StrClear(),
+    "[ContactsAdded index:0 number:5 "
+      "0:[Contact jid:maude@example.net name:Maude Lebowski "
+                 "subscription_state:none_asked "
+                 "groups:[Business Partners]]"
+        "<ros:item jid=\"maude@example.net\" name=\"Maude Lebowski\" "
+                  "subscription=\"none\" ask=\"subscribe\" "
+                  "xmlns:ros=\"jabber:iq:roster\">"
+          "<ros:group>Business Partners</ros:group>"
+        "</ros:item> "
+      "1:[Contact jid:walter@example.net name:Walter Sobchak "
+                 "subscription_state:both "
+                 "groups:[Friends, Bowling Team, Bowling League]]"
+        "<ros:item jid=\"walter@example.net\" name=\"Walter Sobchak\" "
+                  "subscription=\"both\" "
+                  "xmlns:ros=\"jabber:iq:roster\">"
+          "<ros:group>Friends</ros:group>"
+          "<ros:group>Bowling Team</ros:group>"
+          "<ros:group>Bowling League</ros:group>"
+        "</ros:item> "
+      "2:[Contact jid:donny@example.net name:Donny "
+                 "subscription_state:both "
+                 "groups:[Friends, Bowling Team, Bowling League]]"
+        "<ros:item jid=\"donny@example.net\" name=\"Donny\" "
+                  "subscription=\"both\" "
+                  "xmlns:ros=\"jabber:iq:roster\">"
+          "<ros:group>Friends</ros:group>"
+          "<ros:group>Bowling Team</ros:group>"
+          "<ros:group>Bowling League</ros:group>"
+        "</ros:item> "
+      "3:[Contact jid:jeffrey@example.net name:The Big Lebowski "
+                 "subscription_state:to "
+                 "groups:[Business Partners]]"
+        "<ros:item jid=\"jeffrey@example.net\" name=\"The Big Lebowski\" "
+                  "subscription=\"to\" "
+                  "xmlns:ros=\"jabber:iq:roster\">"
+          "<ros:group>Business Partners</ros:group>"
+        "</ros:item> "
+      "4:[Contact jid:jesus@example.net name:Jesus Quintana "
+                 "subscription_state:from groups:[Bowling League]]"
+        "<ros:item jid=\"jesus@example.net\" name=\"Jesus Quintana\" "
+                  "subscription=\"from\" xmlns:ros=\"jabber:iq:roster\">"
+          "<ros:group>Bowling League</ros:group>"
+        "</ros:item>]");
+  EXPECT_EQ(handler.OutputActivity(), "");
+  EXPECT_EQ(handler.SessionActivity(), "");
+
+  // Request that someone be added
+  talk_base::scoped_ptr<XmppRosterContact> contact(XmppRosterContact::Create());
+  TEST_OK(contact->set_jid(Jid("brandt@example.net")));
+  TEST_OK(contact->set_name("Brandt"));
+  TEST_OK(contact->AddGroup("Business Partners"));
+  TEST_OK(contact->AddGroup("Watchers"));
+  TEST_OK(contact->AddGroup("Friends"));
+  TEST_OK(contact->RemoveGroup("Friends")); // Maybe not...
+  TEST_OK(roster->RequestRosterChange(contact.get()));
+
+  EXPECT_EQ(roster_handler.StrClear(), "");
+  EXPECT_EQ(handler.OutputActivity(),
+    "<iq type=\"set\" id=\"3\">"
+      "<query xmlns=\"jabber:iq:roster\">"
+        "<item jid=\"brandt@example.net\" "
+              "name=\"Brandt\">"
+          "<group>Business Partners</group>"
+          "<group>Watchers</group>"
+        "</item>"
+      "</query>"
+    "</iq>");
+  EXPECT_EQ(handler.SessionActivity(), "");
+
+  // Get the push from the server
+  input =
+    "<iq type='result' to='david@my-server/test' id='3'/>"
+    "<iq type='set' id='server_1'>"
+      "<query xmlns='jabber:iq:roster'>"
+        "<item jid='brandt@example.net' "
+              "name='Brandt' "
+              "subscription='none'>"
+          "<group>Business Partners</group>"
+          "<group>Watchers</group>"
+        "</item>"
+      "</query>"
+    "</iq>";
+  TEST_OK(engine->HandleInput(input.c_str(), input.length()));
+
+  EXPECT_EQ(roster_handler.StrClear(),
+    "[ContactsAdded index:5 number:1 "
+      "5:[Contact jid:brandt@example.net name:Brandt "
+                 "subscription_state:none "
+                 "groups:[Business Partners, Watchers]]"
+        "<ros:item jid=\"brandt@example.net\" name=\"Brandt\" "
+                  "subscription=\"none\" "
+                  "xmlns:ros=\"jabber:iq:roster\">"
+          "<ros:group>Business Partners</ros:group>"
+          "<ros:group>Watchers</ros:group>"
+        "</ros:item>]");
+  EXPECT_EQ(handler.OutputActivity(),
+    "<iq type=\"result\" id=\"server_1\"/>");
+  EXPECT_EQ(handler.SessionActivity(), "");
+
+  // Get a contact update
+  input =
+    "<iq type='set' id='server_2'>"
+      "<query xmlns='jabber:iq:roster'>"
+        "<item jid='walter@example.net' "
+              "name='Walter Sobchak' "
+              "subscription='both'>"
+          "<group>Friends</group>"
+          "<group>Bowling Team</group>"
+          "<group>Bowling League</group>"
+          "<group>Not wrong, just an...</group>"
+        "</item>"
+      "</query>"
+    "</iq>";
+
+  TEST_OK(engine->HandleInput(input.c_str(), input.length()));
+
+  EXPECT_EQ(roster_handler.StrClear(),
+    "[ContactChanged "
+      "old_contact:[Contact jid:walter@example.net name:Walter Sobchak "
+                           "subscription_state:both "
+                           "groups:[Friends, Bowling Team, Bowling League]]"
+        "<ros:item jid=\"walter@example.net\" name=\"Walter Sobchak\" "
+                  "subscription=\"both\" xmlns:ros=\"jabber:iq:roster\">"
+          "<ros:group>Friends</ros:group>"
+          "<ros:group>Bowling Team</ros:group>"
+          "<ros:group>Bowling League</ros:group>"
+          "</ros:item> "
+      "index:1 "
+      "new_contact:[Contact jid:walter@example.net name:Walter Sobchak "
+                           "subscription_state:both "
+                           "groups:[Friends, Bowling Team, Bowling League, "
+                                   "Not wrong, just an...]]"
+        "<ros:item jid=\"walter@example.net\" name=\"Walter Sobchak\" "
+                  "subscription=\"both\" xmlns:ros=\"jabber:iq:roster\">"
+          "<ros:group>Friends</ros:group>"
+          "<ros:group>Bowling Team</ros:group>"
+          "<ros:group>Bowling League</ros:group>"
+          "<ros:group>Not wrong, just an...</ros:group>"
+        "</ros:item>]");
+  EXPECT_EQ(handler.OutputActivity(),
+    "<iq type=\"result\" id=\"server_2\"/>");
+  EXPECT_EQ(handler.SessionActivity(), "");
+
+  // Remove a contact
+  TEST_OK(roster->RequestRosterRemove(Jid("jesus@example.net")));
+
+  EXPECT_EQ(roster_handler.StrClear(), "");
+  EXPECT_EQ(handler.OutputActivity(),
+    "<iq type=\"set\" id=\"4\">"
+      "<query xmlns=\"jabber:iq:roster\" jid=\"jesus@example.net\" "
+             "subscription=\"remove\"/>"
+    "</iq>");
+  EXPECT_EQ(handler.SessionActivity(), "");
+
+  // Response from the server
+  input =
+    "<iq type='result' to='david@my-server/test' id='4'/>"
+    "<iq type='set' id='server_3'>"
+      "<query xmlns='jabber:iq:roster'>"
+        "<item jid='jesus@example.net' "
+              "subscription='remove'>"
+        "</item>"
+      "</query>"
+    "</iq>";
+  TEST_OK(engine->HandleInput(input.c_str(), input.length()));
+
+  EXPECT_EQ(roster_handler.StrClear(),
+    "[ContactRemoved "
+      "old_contact:[Contact jid:jesus@example.net name:Jesus Quintana "
+                           "subscription_state:from groups:[Bowling League]]"
+        "<ros:item jid=\"jesus@example.net\" name=\"Jesus Quintana\" "
+                  "subscription=\"from\" "
+                  "xmlns:ros=\"jabber:iq:roster\">"
+          "<ros:group>Bowling League</ros:group>"
+        "</ros:item> index:4]");
+  EXPECT_EQ(handler.OutputActivity(),
+    "<iq type=\"result\" id=\"server_3\"/>");
+  EXPECT_EQ(handler.SessionActivity(), "");
+}
+
+}
diff --git a/talk/xmpp/rostermoduleimpl.cc b/talk/xmpp/rostermoduleimpl.cc
new file mode 100644
index 0000000..2422880
--- /dev/null
+++ b/talk/xmpp/rostermoduleimpl.cc
@@ -0,0 +1,1080 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 char** 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_)
+    return Jid();
+
+  return Jid(raw_xml_->Attr(QN_FROM));
+}
+
+XmppPresenceAvailable
+XmppPresenceImpl::available() const {
+  if (!raw_xml_)
+    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_)
+    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_)
+    return XMPP_PRESENCE_DEFAULT;
+
+  XmppPresenceShow show = XMPP_PRESENCE_DEFAULT;
+  StringToPresenceShow(raw_xml_->TextNamed(QN_SHOW), &show);
+  return show;
+}
+
+XmppReturnStatus
+XmppPresenceImpl::set_presence_show(XmppPresenceShow show) {
+  if (!raw_xml_)
+    CreateRawXmlSkeleton();
+
+  const char* 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_)
+    return 0;
+
+  int raw_priority = 0;
+  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_)
+    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_)
+    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_)
+    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;
+}
+
+XmppPresenceConnectionStatus
+XmppPresenceImpl::connection_status() const {
+  if (!raw_xml_)
+      return XMPP_CONNECTION_STATUS_UNKNOWN;
+
+  XmlElement* con = raw_xml_->FirstNamed(QN_GOOGLE_PSTN_CONFERENCE_STATUS);
+  if (con) {
+    std::string status = con->Attr(QN_ATTR_STATUS);
+    if (status == STR_PSTN_CONFERENCE_STATUS_CONNECTING)
+      return XMPP_CONNECTION_STATUS_CONNECTING;
+    else if (status == STR_PSTN_CONFERENCE_STATUS_CONNECTED)
+      return XMPP_CONNECTION_STATUS_CONNECTED;
+    else if (status == STR_PSTN_CONFERENCE_STATUS_HANGUP)
+        return XMPP_CONNECTION_STATUS_HANGUP;
+  }
+
+  return XMPP_CONNECTION_STATUS_CONNECTED;
+}
+
+const std::string
+XmppPresenceImpl::google_user_id() const {
+  if (!raw_xml_)
+    return std::string();
+
+  XmlElement* muc_user_x = raw_xml_->FirstNamed(QN_MUC_USER_X);
+  if (muc_user_x) {
+    XmlElement* muc_user_item = muc_user_x->FirstNamed(QN_MUC_USER_ITEM);
+    if (muc_user_item) {
+      return muc_user_item->Attr(QN_GOOGLE_USER_ID);
+    }
+  }
+
+  return std::string();
+}
+
+const std::string
+XmppPresenceImpl::nickname() const {
+  if (!raw_xml_)
+    return std::string();
+
+  XmlElement* nickname = raw_xml_->FirstNamed(QN_NICKNAME);
+  if (nickname) {
+    return nickname->BodyText();
+  }
+
+  return std::string();
+}
+
+const XmlElement*
+XmppPresenceImpl::raw_xml() const {
+  if (!raw_xml_)
+    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;
+
+  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_)
+    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_)
+    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_)
+    return XMPP_SUBSCRIPTION_NONE;
+
+  XmppSubscriptionState state = XMPP_SUBSCRIPTION_NONE;
+
+  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_)
+    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_)
+    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_)
+    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_)
+    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..df6b70f
--- /dev/null
+++ b/talk/xmpp/rostermoduleimpl.h
@@ -0,0 +1,302 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 received 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 connection status
+  virtual XmppPresenceConnectionStatus connection_status() const;
+
+  //! The focus obfuscated GAIA id
+  virtual const std::string google_user_id() const;
+
+  //! The nickname in the presence
+  virtual const std::string nickname() const;
+
+  //! 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
new file mode 100644
index 0000000..ded9e97
--- /dev/null
+++ b/talk/xmpp/saslcookiemechanism.h
@@ -0,0 +1,86 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_XMPP_SASLCOOKIEMECHANISM_H_
+#define TALK_XMPP_SASLCOOKIEMECHANISM_H_
+
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/saslmechanism.h"
+#include "talk/xmpp/constants.h"
+
+namespace buzz {
+
+class SaslCookieMechanism : public SaslMechanism {
+
+public:
+  SaslCookieMechanism(const std::string & mechanism,
+                      const std::string & username,
+                      const std::string & cookie,
+                      const std::string & token_service)
+    : mechanism_(mechanism),
+      username_(username),
+      cookie_(cookie),
+      token_service_(token_service) {}
+
+  SaslCookieMechanism(const std::string & mechanism,
+                      const std::string & username,
+                      const std::string & cookie)
+    : mechanism_(mechanism),
+      username_(username),
+      cookie_(cookie),
+      token_service_("") {}
+
+  virtual std::string GetMechanismName() { return mechanism_; }
+
+  virtual XmlElement * StartSaslAuth() {
+    // send initial request
+    XmlElement * el = new XmlElement(QN_SASL_AUTH, true);
+    el->AddAttr(QN_MECHANISM, mechanism_);
+    if (!token_service_.empty()) {
+      el->AddAttr(QN_GOOGLE_AUTH_SERVICE, token_service_);
+    }
+
+    std::string credential;
+    credential.append("\0", 1);
+    credential.append(username_);
+    credential.append("\0", 1);
+    credential.append(cookie_);
+    el->AddText(Base64Encode(credential));
+    return el;
+  }
+
+private:
+  std::string mechanism_;
+  std::string username_;
+  std::string cookie_;
+  std::string token_service_;
+};
+
+}
+
+#endif  // TALK_XMPP_SASLCOOKIEMECHANISM_H_
diff --git a/talk/xmpp/saslhandler.h b/talk/xmpp/saslhandler.h
new file mode 100644
index 0000000..bead8aa
--- /dev/null
+++ b/talk/xmpp/saslhandler.h
@@ -0,0 +1,59 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _SASLHANDLER_H_
+#define _SASLHANDLER_H_
+
+#include <string>
+#include <vector>
+
+namespace buzz {
+
+class XmlElement;
+class SaslMechanism;
+
+// Creates mechanisms to deal with a given mechanism
+class SaslHandler {
+
+public:
+  
+  // Intended to be subclassed
+  virtual ~SaslHandler() {}
+
+  // Should pick the best method according to this handler
+  // returns the empty string if none are suitable
+  virtual std::string ChooseBestSaslMechanism(const std::vector<std::string> & mechanisms, bool encrypted) = 0;
+
+  // Creates a SaslMechanism for the given mechanism name (you own it
+  // once you get it).
+  // If not handled, return NULL.
+  virtual SaslMechanism * CreateSaslMechanism(const std::string & mechanism) = 0;
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/saslmechanism.cc b/talk/xmpp/saslmechanism.cc
new file mode 100644
index 0000000..2645ac0
--- /dev/null
+++ b/talk/xmpp/saslmechanism.cc
@@ -0,0 +1,72 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/base64.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/saslmechanism.h"
+
+using talk_base::Base64;
+
+namespace buzz {
+
+XmlElement *
+SaslMechanism::StartSaslAuth() {
+  return new XmlElement(QN_SASL_AUTH, true);
+}
+
+XmlElement *
+SaslMechanism::HandleSaslChallenge(const XmlElement * challenge) {
+  return new XmlElement(QN_SASL_ABORT, true);
+}
+
+void
+SaslMechanism::HandleSaslSuccess(const XmlElement * success) {
+}
+
+void
+SaslMechanism::HandleSaslFailure(const XmlElement * failure) {
+}
+
+std::string
+SaslMechanism::Base64Encode(const std::string & plain) {
+  return Base64::Encode(plain);
+}
+
+std::string
+SaslMechanism::Base64Decode(const std::string & encoded) {
+  return Base64::Decode(encoded, Base64::DO_LAX);
+}
+
+std::string
+SaslMechanism::Base64EncodeFromArray(const char * plain, size_t length) {
+  std::string result;
+  Base64::EncodeFromArray(plain, length, &result);
+  return result;
+}
+
+}
diff --git a/talk/xmpp/saslmechanism.h b/talk/xmpp/saslmechanism.h
new file mode 100644
index 0000000..f2e5adc
--- /dev/null
+++ b/talk/xmpp/saslmechanism.h
@@ -0,0 +1,74 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _SASLMECHANISM_H_
+#define _SASLMECHANISM_H_
+
+#include <string>
+
+namespace buzz {
+
+class XmlElement;
+
+
+// Defines a mechnanism to do SASL authentication.
+// Subclass instances should have a self-contained way to present
+// credentials.
+class SaslMechanism {
+
+public:
+  
+  // Intended to be subclassed
+  virtual ~SaslMechanism() {}
+
+  // Should return the name of the SASL mechanism, e.g., "PLAIN"
+  virtual std::string GetMechanismName() = 0;
+
+  // Should generate the initial "auth" request.  Default is just <auth/>.
+  virtual XmlElement * StartSaslAuth();
+
+  // Should respond to a SASL "<challenge>" request.  Default is
+  // to abort (for mechanisms that do not do challenge-response)
+  virtual XmlElement * HandleSaslChallenge(const XmlElement * challenge);
+
+  // Notification of a SASL "<success>".  Sometimes information
+  // is passed on success.
+  virtual void HandleSaslSuccess(const XmlElement * success);
+
+  // Notification of a SASL "<failure>".  Sometimes information
+  // for the user is passed on failure.
+  virtual void HandleSaslFailure(const XmlElement * failure);
+
+protected:
+  static std::string Base64Encode(const std::string & plain);
+  static std::string Base64Decode(const std::string & encoded);
+  static std::string Base64EncodeFromArray(const char * plain, size_t length);
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/saslplainmechanism.h b/talk/xmpp/saslplainmechanism.h
new file mode 100644
index 0000000..f0793b4
--- /dev/null
+++ b/talk/xmpp/saslplainmechanism.h
@@ -0,0 +1,65 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_XMPP_SASLPLAINMECHANISM_H_
+#define TALK_XMPP_SASLPLAINMECHANISM_H_
+
+#include "talk/base/cryptstring.h"
+#include "talk/xmpp/saslmechanism.h"
+
+namespace buzz {
+
+class SaslPlainMechanism : public SaslMechanism {
+
+public:
+  SaslPlainMechanism(const buzz::Jid user_jid, const talk_base::CryptString & password) :
+    user_jid_(user_jid), password_(password) {}
+
+  virtual std::string GetMechanismName() { return "PLAIN"; }
+
+  virtual XmlElement * StartSaslAuth() {
+    // send initial request
+    XmlElement * el = new XmlElement(QN_SASL_AUTH, true);
+    el->AddAttr(QN_MECHANISM, "PLAIN");
+
+    talk_base::FormatCryptString credential;
+    credential.Append("\0", 1);
+    credential.Append(user_jid_.node());
+    credential.Append("\0", 1);
+    credential.Append(&password_);
+    el->AddText(Base64EncodeFromArray(credential.GetData(), credential.GetLength()));
+    return el;
+  }
+
+private:
+  Jid user_jid_;
+  talk_base::CryptString password_;
+};
+
+}
+
+#endif  // TALK_XMPP_SASLPLAINMECHANISM_H_
diff --git a/talk/xmpp/util_unittest.cc b/talk/xmpp/util_unittest.cc
new file mode 100644
index 0000000..3d13007
--- /dev/null
+++ b/talk/xmpp/util_unittest.cc
@@ -0,0 +1,102 @@
+// 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;
+    default:
+      break;
+    }
+    break;
+  default:
+    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/xmppauth.cc b/talk/xmpp/xmppauth.cc
new file mode 100644
index 0000000..efda967
--- /dev/null
+++ b/talk/xmpp/xmppauth.cc
@@ -0,0 +1,105 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/xmpp/xmppauth.h"
+
+#include <algorithm>
+
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/saslcookiemechanism.h"
+#include "talk/xmpp/saslplainmechanism.h"
+
+XmppAuth::XmppAuth() : done_(false) {
+}
+
+XmppAuth::~XmppAuth() {
+}
+
+void XmppAuth::StartPreXmppAuth(const buzz::Jid& jid,
+                                const talk_base::SocketAddress& server,
+                                const talk_base::CryptString& pass,
+                                const std::string& auth_mechanism,
+                                const std::string& auth_token) {
+  jid_ = jid;
+  passwd_ = pass;
+  auth_mechanism_ = auth_mechanism;
+  auth_token_ = auth_token;
+  done_ = true;
+
+  SignalAuthDone();
+}
+
+static bool contains(const std::vector<std::string>& strings,
+                     const std::string& string) {
+  return std::find(strings.begin(), strings.end(), string) != strings.end();
+}
+
+std::string XmppAuth::ChooseBestSaslMechanism(
+    const std::vector<std::string>& mechanisms,
+    bool encrypted) {
+  // First try Oauth2.
+  if (GetAuthMechanism() == buzz::AUTH_MECHANISM_OAUTH2 &&
+      contains(mechanisms, buzz::AUTH_MECHANISM_OAUTH2)) {
+    return buzz::AUTH_MECHANISM_OAUTH2;
+  }
+
+  // A token is the weakest auth - 15s, service-limited, so prefer it.
+  if (GetAuthMechanism() == buzz::AUTH_MECHANISM_GOOGLE_TOKEN &&
+      contains(mechanisms, buzz::AUTH_MECHANISM_GOOGLE_TOKEN)) {
+    return buzz::AUTH_MECHANISM_GOOGLE_TOKEN;
+  }
+
+  // A cookie is the next weakest - 14 days.
+  if (GetAuthMechanism() == buzz::AUTH_MECHANISM_GOOGLE_COOKIE &&
+      contains(mechanisms, buzz::AUTH_MECHANISM_GOOGLE_COOKIE)) {
+    return buzz::AUTH_MECHANISM_GOOGLE_COOKIE;
+  }
+
+  // As a last resort, use plain authentication.
+  if (contains(mechanisms, buzz::AUTH_MECHANISM_PLAIN)) {
+    return buzz::AUTH_MECHANISM_PLAIN;
+  }
+
+  // No good mechanism found
+  return "";
+}
+
+buzz::SaslMechanism* XmppAuth::CreateSaslMechanism(
+    const std::string& mechanism) {
+  if (mechanism == buzz::AUTH_MECHANISM_OAUTH2) {
+    return new buzz::SaslCookieMechanism(
+        mechanism, jid_.Str(), auth_token_, "oauth2");
+  } else if (mechanism == buzz::AUTH_MECHANISM_GOOGLE_TOKEN) {
+    return new buzz::SaslCookieMechanism(mechanism, jid_.Str(), auth_token_);
+  // } else if (mechanism == buzz::AUTH_MECHANISM_GOOGLE_COOKIE) {
+  //   return new buzz::SaslCookieMechanism(mechanism, jid.Str(), sid_);
+  } else if (mechanism == buzz::AUTH_MECHANISM_PLAIN) {
+    return new buzz::SaslPlainMechanism(jid_, passwd_);
+  } else {
+    return NULL;
+  }
+}
diff --git a/talk/xmpp/xmppauth.h b/talk/xmpp/xmppauth.h
new file mode 100644
index 0000000..5dd6963
--- /dev/null
+++ b/talk/xmpp/xmppauth.h
@@ -0,0 +1,78 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_XMPP_XMPPAUTH_H_
+#define TALK_XMPP_XMPPAUTH_H_
+
+#include <vector>
+
+#include "talk/base/cryptstring.h"
+#include "talk/base/sigslot.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/saslhandler.h"
+#include "talk/xmpp/prexmppauth.h"
+
+class XmppAuth: public buzz::PreXmppAuth {
+public:
+  XmppAuth();
+  virtual ~XmppAuth();
+
+  // TODO: Just have one "secret" that is either pass or
+  // token?
+  virtual void StartPreXmppAuth(const buzz::Jid& jid,
+                                const talk_base::SocketAddress& server,
+                                const talk_base::CryptString& pass,
+                                const std::string& auth_mechanism,
+                                const std::string& auth_token);
+
+  virtual bool IsAuthDone() const { return done_; }
+  virtual bool IsAuthorized() const { return true; }
+  virtual bool HadError() const { return false; }
+  virtual int  GetError() const { return 0; }
+  virtual buzz::CaptchaChallenge GetCaptchaChallenge() const {
+      return buzz::CaptchaChallenge();
+  }
+  virtual std::string GetAuthMechanism() const { return auth_mechanism_; }
+  virtual std::string GetAuthToken() const { return auth_token_; }
+
+  virtual std::string ChooseBestSaslMechanism(
+      const std::vector<std::string>& mechanisms,
+      bool encrypted);
+
+  virtual buzz::SaslMechanism * CreateSaslMechanism(
+      const std::string& mechanism);
+
+private:
+  buzz::Jid jid_;
+  talk_base::CryptString passwd_;
+  std::string auth_mechanism_;
+  std::string auth_token_;
+  bool done_;
+};
+
+#endif  // TALK_XMPP_XMPPAUTH_H_
+
diff --git a/talk/xmpp/xmppclient.cc b/talk/xmpp/xmppclient.cc
new file mode 100644
index 0000000..f7d7cf2
--- /dev/null
+++ b/talk/xmpp/xmppclient.cc
@@ -0,0 +1,440 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "xmppclient.h"
+#include "xmpptask.h"
+#include "talk/base/logging.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/xmpp/plainsaslhandler.h"
+
+namespace buzz {
+
+class XmppClient::Private :
+    public sigslot::has_slots<>,
+    public XmppSessionHandler,
+    public XmppOutputHandler {
+public:
+
+  explicit Private(XmppClient* client) :
+    client_(client),
+    socket_(NULL),
+    engine_(NULL),
+    proxy_port_(0),
+    pre_engine_error_(XmppEngine::ERROR_NONE),
+    pre_engine_subcode_(0),
+    signal_closed_(false),
+    allow_plain_(false) {}
+
+  virtual ~Private() {
+    // We need to disconnect from socket_ before engine_ is destructed (by
+    // the auto-generated destructor code).
+    ResetSocket();
+  }
+
+  // the owner
+  XmppClient* const client_;
+
+  // the two main objects
+  talk_base::scoped_ptr<AsyncSocket> socket_;
+  talk_base::scoped_ptr<XmppEngine> engine_;
+  talk_base::scoped_ptr<PreXmppAuth> pre_auth_;
+  talk_base::CryptString pass_;
+  std::string auth_mechanism_;
+  std::string auth_token_;
+  talk_base::SocketAddress server_;
+  std::string proxy_host_;
+  int proxy_port_;
+  XmppEngine::Error pre_engine_error_;
+  int pre_engine_subcode_;
+  CaptchaChallenge captcha_challenge_;
+  bool signal_closed_;
+  bool allow_plain_;
+
+  void ResetSocket() {
+    if (socket_) {
+      socket_->SignalConnected.disconnect(this);
+      socket_->SignalRead.disconnect(this);
+      socket_->SignalClosed.disconnect(this);
+      socket_.reset(NULL);
+    }
+  }
+
+  // implementations of interfaces
+  void OnStateChange(int state);
+  void WriteOutput(const char* bytes, size_t len);
+  void StartTls(const std::string& domainname);
+  void CloseConnection();
+
+  // slots for socket signals
+  void OnSocketConnected();
+  void OnSocketRead();
+  void OnSocketClosed();
+};
+
+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) {
+  if (socket == NULL)
+    return XMPP_RETURN_BADARGUMENT;
+  if (d_->socket_)
+    return XMPP_RETURN_BADSTATE;
+
+  d_->socket_.reset(socket);
+
+  d_->socket_->SignalConnected.connect(d_.get(), &Private::OnSocketConnected);
+  d_->socket_->SignalRead.connect(d_.get(), &Private::OnSocketRead);
+  d_->socket_->SignalClosed.connect(d_.get(), &Private::OnSocketClosed);
+
+  d_->engine_.reset(XmppEngine::Create());
+  d_->engine_->SetSessionHandler(d_.get());
+  d_->engine_->SetOutputHandler(d_.get());
+  if (!settings.resource().empty()) {
+    d_->engine_->SetRequestedResource(settings.resource());
+  }
+  d_->engine_->SetTls(settings.use_tls());
+
+  // The talk.google.com server returns a certificate with common-name:
+  //   CN="gmail.com" for @gmail.com accounts,
+  //   CN="googlemail.com" for @googlemail.com accounts,
+  //   CN="talk.google.com" for other accounts (such as @example.com),
+  // so we tweak the tls server setting for those other accounts to match the
+  // returned certificate CN of "talk.google.com".
+  // For other servers, we leave the strings empty, which causes the jid's
+  // domain to be used.  We do the same for gmail.com and googlemail.com as the
+  // returned CN matches the account domain in those cases.
+  std::string server_name = settings.server().HostAsURIString();
+  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 ||
+      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);
+    }
+  }
+
+  // Set language
+  d_->engine_->SetLanguage(lang);
+
+  d_->engine_->SetUser(buzz::Jid(settings.user(), settings.host(), STR_EMPTY));
+
+  d_->pass_ = settings.pass();
+  d_->auth_mechanism_ = settings.auth_mechanism();
+  d_->auth_token_ = settings.auth_token();
+  d_->server_ = settings.server();
+  d_->proxy_host_ = settings.proxy_host();
+  d_->proxy_port_ = settings.proxy_port();
+  d_->allow_plain_ = settings.allow_plain();
+  d_->pre_auth_.reset(pre_auth);
+
+  return XMPP_RETURN_OK;
+}
+
+XmppEngine::State XmppClient::GetState() const {
+  if (!d_->engine_)
+    return XmppEngine::STATE_NONE;
+  return d_->engine_->GetState();
+}
+
+XmppEngine::Error XmppClient::GetError(int* subcode) {
+  if (subcode) {
+    *subcode = 0;
+  }
+  if (!d_->engine_)
+    return XmppEngine::ERROR_NONE;
+  if (d_->pre_engine_error_ != XmppEngine::ERROR_NONE) {
+    if (subcode) {
+      *subcode = d_->pre_engine_subcode_;
+    }
+    return d_->pre_engine_error_;
+  }
+  return d_->engine_->GetError(subcode);
+}
+
+const XmlElement* XmppClient::GetStreamError() {
+  if (!d_->engine_) {
+    return NULL;
+  }
+  return d_->engine_->GetStreamError();
+}
+
+CaptchaChallenge XmppClient::GetCaptchaChallenge() {
+  if (!d_->engine_)
+    return CaptchaChallenge();
+  return d_->captcha_challenge_;
+}
+
+std::string XmppClient::GetAuthMechanism() {
+  if (!d_->engine_)
+    return "";
+  return d_->auth_mechanism_;
+}
+
+std::string XmppClient::GetAuthToken() {
+  if (!d_->engine_)
+    return "";
+  return d_->auth_token_;
+}
+
+int XmppClient::ProcessStart() {
+  // Should not happen, but was observed in crash reports
+  if (!d_->socket_) {
+    LOG(LS_ERROR) << "socket_ already reset";
+    return STATE_DONE;
+  }
+
+  if (d_->pre_auth_) {
+    d_->pre_auth_->SignalAuthDone.connect(this, &XmppClient::OnAuthDone);
+    d_->pre_auth_->StartPreXmppAuth(
+        d_->engine_->GetUser(), d_->server_, d_->pass_,
+        d_->auth_mechanism_, d_->auth_token_);
+    d_->pass_.Clear(); // done with this;
+    return STATE_PRE_XMPP_LOGIN;
+  }
+  else {
+    d_->engine_->SetSaslHandler(new PlainSaslHandler(
+              d_->engine_->GetUser(), d_->pass_, d_->allow_plain_));
+    d_->pass_.Clear(); // done with this;
+    return STATE_START_XMPP_LOGIN;
+  }
+}
+
+void XmppClient::OnAuthDone() {
+  Wake();
+}
+
+int XmppClient::ProcessTokenLogin() {
+  // Should not happen, but was observed in crash reports
+  if (!d_->socket_) {
+    LOG(LS_ERROR) << "socket_ already reset";
+    return STATE_DONE;
+  }
+
+  // Don't know how this could happen, but crash reports show it as NULL
+  if (!d_->pre_auth_) {
+    d_->pre_engine_error_ = XmppEngine::ERROR_AUTH;
+    EnsureClosed();
+    return STATE_ERROR;
+  }
+
+  // Wait until pre authentication is done is done
+  if (!d_->pre_auth_->IsAuthDone())
+    return STATE_BLOCKED;
+
+  if (!d_->pre_auth_->IsAuthorized()) {
+    // maybe split out a case when gaia is down?
+    if (d_->pre_auth_->HadError()) {
+      d_->pre_engine_error_ = XmppEngine::ERROR_AUTH;
+      d_->pre_engine_subcode_ = d_->pre_auth_->GetError();
+    }
+    else {
+      d_->pre_engine_error_ = XmppEngine::ERROR_UNAUTHORIZED;
+      d_->pre_engine_subcode_ = 0;
+      d_->captcha_challenge_ = d_->pre_auth_->GetCaptchaChallenge();
+    }
+    d_->pre_auth_.reset(NULL); // done with this
+    EnsureClosed();
+    return STATE_ERROR;
+  }
+
+  // Save auth token as a result
+
+  d_->auth_mechanism_ = d_->pre_auth_->GetAuthMechanism();
+  d_->auth_token_ = d_->pre_auth_->GetAuthToken();
+
+  // transfer ownership of pre_auth_ to engine
+  d_->engine_->SetSaslHandler(d_->pre_auth_.release());
+  return STATE_START_XMPP_LOGIN;
+}
+
+int XmppClient::ProcessStartXmppLogin() {
+  // Should not happen, but was observed in crash reports
+  if (!d_->socket_) {
+    LOG(LS_ERROR) << "socket_ already reset";
+    return STATE_DONE;
+  }
+
+  // Done with pre-connect tasks - connect!
+  if (!d_->socket_->Connect(d_->server_)) {
+    EnsureClosed();
+    return STATE_ERROR;
+  }
+
+  return STATE_RESPONSE;
+}
+
+int XmppClient::ProcessResponse() {
+  // Hang around while we are connected.
+  if (!delivering_signal_ &&
+      (!d_->engine_ || d_->engine_->GetState() == XmppEngine::STATE_CLOSED))
+    return STATE_DONE;
+  return STATE_BLOCKED;
+}
+
+XmppReturnStatus XmppClient::Disconnect() {
+  if (!d_->socket_)
+    return XMPP_RETURN_BADSTATE;
+  Abort();
+  d_->engine_->Disconnect();
+  d_->ResetSocket();
+  return XMPP_RETURN_OK;
+}
+
+XmppClient::XmppClient(TaskParent* parent)
+    : XmppTaskParentInterface(parent),
+      delivering_signal_(false),
+      valid_(false) {
+  d_.reset(new Private(this));
+  valid_ = true;
+}
+
+XmppClient::~XmppClient() {
+  valid_ = false;
+}
+
+const Jid& XmppClient::jid() const {
+  return d_->engine_->FullJid();
+}
+
+
+std::string XmppClient::NextId() {
+  return d_->engine_->NextId();
+}
+
+XmppReturnStatus XmppClient::SendStanza(const XmlElement* stanza) {
+  return d_->engine_->SendStanza(stanza);
+}
+
+XmppReturnStatus XmppClient::SendStanzaError(
+    const XmlElement* old_stanza, XmppStanzaError xse,
+    const std::string& message) {
+  return d_->engine_->SendStanzaError(old_stanza, xse, message);
+}
+
+XmppReturnStatus XmppClient::SendRaw(const std::string& text) {
+  return d_->engine_->SendRaw(text);
+}
+
+XmppEngine* XmppClient::engine() {
+  return d_->engine_.get();
+}
+
+void XmppClient::Private::OnSocketConnected() {
+  engine_->Connect();
+}
+
+void XmppClient::Private::OnSocketRead() {
+  char bytes[4096];
+  size_t bytes_read;
+  for (;;) {
+    // Should not happen, but was observed in crash reports
+    if (!socket_) {
+      LOG(LS_ERROR) << "socket_ already reset";
+      return;
+    }
+
+    if (!socket_->Read(bytes, sizeof(bytes), &bytes_read)) {
+      // TODO: deal with error information
+      return;
+    }
+
+    if (bytes_read == 0)
+      return;
+
+//#ifdef _DEBUG
+    client_->SignalLogInput(bytes, bytes_read);
+//#endif
+
+    engine_->HandleInput(bytes, bytes_read);
+  }
+}
+
+void XmppClient::Private::OnSocketClosed() {
+  int code = socket_->GetError();
+  engine_->ConnectionClosed(code);
+}
+
+void XmppClient::Private::OnStateChange(int state) {
+  if (state == XmppEngine::STATE_CLOSED) {
+    client_->EnsureClosed();
+  }
+  else {
+    client_->SignalStateChange((XmppEngine::State)state);
+  }
+  client_->Wake();
+}
+
+void XmppClient::Private::WriteOutput(const char* bytes, size_t len) {
+//#ifdef _DEBUG
+  client_->SignalLogOutput(bytes, len);
+//#endif
+
+  socket_->Write(bytes, len);
+  // TODO: deal with error information
+}
+
+void XmppClient::Private::StartTls(const std::string& domain) {
+#if defined(FEATURE_ENABLE_SSL)
+  socket_->StartTls(domain);
+#endif
+}
+
+void XmppClient::Private::CloseConnection() {
+  socket_->Close();
+}
+
+void XmppClient::AddXmppTask(XmppTask* task, XmppEngine::HandlerLevel level) {
+  d_->engine_->AddStanzaHandler(task, level);
+}
+
+void XmppClient::RemoveXmppTask(XmppTask* task) {
+  d_->engine_->RemoveStanzaHandler(task);
+}
+
+void XmppClient::EnsureClosed() {
+  if (!d_->signal_closed_) {
+    d_->signal_closed_ = true;
+    delivering_signal_ = true;
+    SignalStateChange(XmppEngine::STATE_CLOSED);
+    delivering_signal_ = false;
+  }
+}
+
+}  // namespace buzz
diff --git a/talk/xmpp/xmppclient.h b/talk/xmpp/xmppclient.h
new file mode 100644
index 0000000..c8dd91e
--- /dev/null
+++ b/talk/xmpp/xmppclient.h
@@ -0,0 +1,165 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_XMPPCLIENT_H_
+#define TALK_XMPP_XMPPCLIENT_H_
+
+#include <string>
+#include "talk/base/basicdefs.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/task.h"
+#include "talk/xmpp/asyncsocket.h"
+#include "talk/xmpp/xmppclientsettings.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+
+class PreXmppAuth;
+class CaptchaChallenge;
+
+// Just some non-colliding number.  Could have picked "1".
+#define XMPP_CLIENT_TASK_CODE 0x366c1e47
+
+/////////////////////////////////////////////////////////////////////
+//
+// XMPPCLIENT
+//
+/////////////////////////////////////////////////////////////////////
+//
+// See Task first.  XmppClient is a parent task for XmppTasks.
+//
+// XmppClient is a task which is designed to be the parent task for
+// all tasks that depend on a single Xmpp connection.  If you want to,
+// for example, listen for subscription requests forever, then your
+// listener should be a task that is a child of the XmppClient that owns
+// the connection you are using.  XmppClient has all the utility methods
+// that basically drill through to XmppEngine.
+//
+// XmppClient is just a wrapper for XmppEngine, and if I were writing it
+// all over again, I would make XmppClient == XmppEngine.  Why?
+// XmppEngine needs tasks too, for example it has an XmppLoginTask which
+// should just be the same kind of Task instead of an XmppEngine specific
+// thing.  It would help do certain things like GAIA auth cleaner.
+//
+/////////////////////////////////////////////////////////////////////
+
+class XmppClient : public XmppTaskParentInterface,
+                   public XmppClientInterface,
+                   public sigslot::has_slots<>
+{
+public:
+  explicit XmppClient(talk_base::TaskParent * parent);
+  virtual ~XmppClient();
+
+  XmppReturnStatus Connect(const XmppClientSettings & settings,
+                           const std::string & lang,
+                           AsyncSocket * socket,
+                           PreXmppAuth * preauth);
+
+  virtual int ProcessStart();
+  virtual int ProcessResponse();
+  XmppReturnStatus Disconnect();
+
+  sigslot::signal1<XmppEngine::State> SignalStateChange;
+  XmppEngine::Error GetError(int *subcode);
+
+  // When there is a <stream:error> stanza, return the stanza
+  // so that they can be handled.
+  const XmlElement *GetStreamError();
+
+  // When there is an authentication error, we may have captcha info
+  // that the user can use to unlock their account
+  CaptchaChallenge GetCaptchaChallenge();
+
+  // When authentication is successful, this returns the service token
+  // (if we used GAIA authentication)
+  std::string GetAuthMechanism();
+  std::string GetAuthToken();
+
+  XmppReturnStatus SendRaw(const std::string & text);
+
+  XmppEngine* engine();
+
+  sigslot::signal2<const char *, int> SignalLogInput;
+  sigslot::signal2<const char *, int> SignalLogOutput;
+
+  // As XmppTaskParentIntreface
+  virtual XmppClientInterface* GetClient() { return this; }
+
+  // As XmppClientInterface
+  virtual XmppEngine::State GetState() const;
+  virtual const Jid& jid() const;
+  virtual std::string NextId();
+  virtual XmppReturnStatus SendStanza(const XmlElement *stanza);
+  virtual XmppReturnStatus SendStanzaError(const XmlElement * pelOriginal,
+                                           XmppStanzaError code,
+                                           const std::string & text);
+  virtual void AddXmppTask(XmppTask *, XmppEngine::HandlerLevel);
+  virtual void RemoveXmppTask(XmppTask *);
+
+ private:
+  friend class XmppTask;
+
+  void OnAuthDone();
+
+  // Internal state management
+  enum {
+    STATE_PRE_XMPP_LOGIN = STATE_NEXT,
+    STATE_START_XMPP_LOGIN = STATE_NEXT + 1,
+  };
+  int Process(int state) {
+    switch (state) {
+      case STATE_PRE_XMPP_LOGIN: return ProcessTokenLogin();
+      case STATE_START_XMPP_LOGIN: return ProcessStartXmppLogin();
+      default: return Task::Process(state);
+    }
+  }
+
+  std::string GetStateName(int state) const {
+    switch (state) {
+      case STATE_PRE_XMPP_LOGIN:      return "PRE_XMPP_LOGIN";
+      case STATE_START_XMPP_LOGIN:  return "START_XMPP_LOGIN";
+      default: return Task::GetStateName(state);
+    }
+  }
+
+  int ProcessTokenLogin();
+  int ProcessStartXmppLogin();
+  void EnsureClosed();
+
+  class Private;
+  friend class Private;
+  talk_base::scoped_ptr<Private> d_;
+
+  bool delivering_signal_;
+  bool valid_;
+};
+
+}
+
+#endif  // TALK_XMPP_XMPPCLIENT_H_
diff --git a/talk/xmpp/xmppclientsettings.h b/talk/xmpp/xmppclientsettings.h
new file mode 100644
index 0000000..8851f18
--- /dev/null
+++ b/talk/xmpp/xmppclientsettings.h
@@ -0,0 +1,128 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_XMPP_XMPPCLIENTSETTINGS_H_
+#define TALK_XMPP_XMPPCLIENTSETTINGS_H_
+
+#include "talk/p2p/base/port.h"
+#include "talk/base/cryptstring.h"
+#include "talk/xmpp/xmppengine.h"
+
+namespace buzz {
+
+class XmppUserSettings {
+ public:
+  XmppUserSettings()
+    : use_tls_(buzz::TLS_DISABLED),
+      allow_plain_(false) {
+  }
+
+  void set_user(const std::string& user) { user_ = user; }
+  void set_host(const std::string& host) { host_ = host; }
+  void set_pass(const talk_base::CryptString& pass) { pass_ = pass; }
+  void set_auth_token(const std::string& mechanism,
+                      const std::string& token) {
+    auth_mechanism_ = mechanism;
+    auth_token_ = token;
+  }
+  void set_resource(const std::string& resource) { resource_ = resource; }
+  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;
+  }
+
+  const std::string& user() const { return user_; }
+  const std::string& host() const { return host_; }
+  const talk_base::CryptString& pass() const { return pass_; }
+  const std::string& auth_mechanism() const { return auth_mechanism_; }
+  const std::string& auth_token() const { return auth_token_; }
+  const std::string& resource() const { return resource_; }
+  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:
+  std::string user_;
+  std::string host_;
+  talk_base::CryptString pass_;
+  std::string auth_mechanism_;
+  std::string auth_token_;
+  std::string resource_;
+  TlsOptions use_tls_;
+  bool allow_plain_;
+  std::string test_server_domain_;
+  std::string token_service_;
+};
+
+class XmppClientSettings : public XmppUserSettings {
+ public:
+  XmppClientSettings()
+    : protocol_(cricket::PROTO_TCP),
+      proxy_(talk_base::PROXY_NONE),
+      proxy_port_(80),
+      use_proxy_auth_(false) {
+  }
+
+  void set_server(const talk_base::SocketAddress& server) {
+      server_ = server;
+  }
+  void set_protocol(cricket::ProtocolType protocol) { protocol_ = protocol; }
+  void set_proxy(talk_base::ProxyType f) { proxy_ = f; }
+  void set_proxy_host(const std::string& host) { proxy_host_ = host; }
+  void set_proxy_port(int port) { proxy_port_ = port; };
+  void set_use_proxy_auth(bool f) { use_proxy_auth_ = f; }
+  void set_proxy_user(const std::string& user) { proxy_user_ = user; }
+  void set_proxy_pass(const talk_base::CryptString& pass) { proxy_pass_ = pass; }
+
+  const talk_base::SocketAddress& server() const { return server_; }
+  cricket::ProtocolType protocol() const { return protocol_; }
+  talk_base::ProxyType proxy() const { return proxy_; }
+  const std::string& proxy_host() const { return proxy_host_; }
+  int proxy_port() const { return proxy_port_; }
+  bool use_proxy_auth() const { return use_proxy_auth_; }
+  const std::string& proxy_user() const { return proxy_user_; }
+  const talk_base::CryptString& proxy_pass() const { return proxy_pass_; }
+
+ private:
+  talk_base::SocketAddress server_;
+  cricket::ProtocolType protocol_;
+  talk_base::ProxyType proxy_;
+  std::string proxy_host_;
+  int proxy_port_;
+  bool use_proxy_auth_;
+  std::string proxy_user_;
+  talk_base::CryptString proxy_pass_;
+};
+
+}
+
+#endif  // TALK_XMPP_XMPPCLIENT_H_
diff --git a/talk/xmpp/xmppengine.h b/talk/xmpp/xmppengine.h
new file mode 100644
index 0000000..e1b35a3
--- /dev/null
+++ b/talk/xmpp/xmppengine.h
@@ -0,0 +1,349 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _xmppengine_h_
+#define _xmppengine_h_
+
+// also part of the API
+#include "talk/xmpp/jid.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+
+
+namespace buzz {
+
+class XmppEngine;
+class SaslHandler;
+typedef void * XmppIqCookie;
+
+//! XMPP stanza error codes.
+//! Used in XmppEngine.SendStanzaError().
+enum XmppStanzaError {
+  XSE_BAD_REQUEST,
+  XSE_CONFLICT,
+  XSE_FEATURE_NOT_IMPLEMENTED,
+  XSE_FORBIDDEN,
+  XSE_GONE,
+  XSE_INTERNAL_SERVER_ERROR,
+  XSE_ITEM_NOT_FOUND,
+  XSE_JID_MALFORMED,
+  XSE_NOT_ACCEPTABLE,
+  XSE_NOT_ALLOWED,
+  XSE_PAYMENT_REQUIRED,
+  XSE_RECIPIENT_UNAVAILABLE,
+  XSE_REDIRECT,
+  XSE_REGISTRATION_REQUIRED,
+  XSE_SERVER_NOT_FOUND,
+  XSE_SERVER_TIMEOUT,
+  XSE_RESOURCE_CONSTRAINT,
+  XSE_SERVICE_UNAVAILABLE,
+  XSE_SUBSCRIPTION_REQUIRED,
+  XSE_UNDEFINED_CONDITION,
+  XSE_UNEXPECTED_REQUEST,
+};
+
+// XmppReturnStatus
+//    This is used by API functions to synchronously return status.
+enum XmppReturnStatus {
+  XMPP_RETURN_OK,
+  XMPP_RETURN_BADARGUMENT,
+  XMPP_RETURN_BADSTATE,
+  XMPP_RETURN_PENDING,
+  XMPP_RETURN_UNEXPECTED,
+  XMPP_RETURN_NOTYETIMPLEMENTED,
+};
+
+// 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
+//! Connect, SendStanza, SendIq, Disconnect, or HandleInput.
+class XmppOutputHandler {
+public:
+  virtual ~XmppOutputHandler() {}
+
+  //! Deliver the specified bytes to the XMPP socket.
+  virtual void WriteOutput(const char * bytes, size_t len) = 0;
+
+  //! Initiate TLS encryption on the socket.
+  //! The implementation must verify that the SSL
+  //! certificate matches the given domainname.
+  virtual void StartTls(const std::string & domainname) = 0;
+
+  //! Called when engine wants the connecton closed.
+  virtual void CloseConnection() = 0;
+};
+
+//! Callback to deliver engine state change notifications
+//! to the object managing the engine.
+class XmppSessionHandler {
+public:
+  virtual ~XmppSessionHandler() {}
+  //! Called when engine changes state. Argument is new state.
+  virtual void OnStateChange(int state) = 0;
+};
+
+//! Callback to deliver stanzas to an Xmpp application module.
+//! Register via XmppEngine.SetDefaultSessionHandler or via
+//! XmppEngine.AddSessionHAndler.  
+class XmppStanzaHandler {
+public:
+  virtual ~XmppStanzaHandler() {}
+  //! Process the given stanza.
+  //! The handler must return true if it has handled the stanza.
+  //! A false return value causes the stanza to be passed on to
+  //! the next registered handler.
+  virtual bool HandleStanza(const XmlElement * stanza) = 0;
+};
+
+//! Callback to deliver iq responses (results and errors).
+//! Register while sending an iq via XmppEngine.SendIq.
+//! Iq responses are routed to matching XmppIqHandlers in preference
+//! to sending to any registered SessionHandlers.
+class XmppIqHandler {
+public:
+  virtual ~XmppIqHandler() {}
+  //! Called to handle the iq response.
+  //! The response may be either a result or an error, and will have
+  //! an 'id' that matches the request and a 'from' that matches the
+  //! 'to' of the request.  Called no more than once; once this is
+  //! called, the handler is automatically unregistered.
+  virtual void IqResponse(XmppIqCookie cookie, const XmlElement * pelStanza) = 0;
+};
+
+//! The XMPP connection engine.
+//! This engine implements the client side of the 'core' XMPP protocol.
+//! To use it, register an XmppOutputHandler to handle socket output
+//! and pass socket input to HandleInput.  Then application code can
+//! set up the connection with a user, password, and other settings,
+//! and then call Connect() to initiate the connection.
+//! An application can listen for events and receive stanzas by
+//! registering an XmppStanzaHandler via AddStanzaHandler().
+class XmppEngine {
+public:
+  static XmppEngine * Create();
+  virtual ~XmppEngine() {}
+
+  //! Error codes. See GetError().
+  enum Error {
+    ERROR_NONE = 0,         //!< No error
+    ERROR_XML,              //!< Malformed XML or encoding error
+    ERROR_STREAM,           //!< XMPP stream error - see GetStreamError()
+    ERROR_VERSION,          //!< XMPP version error
+    ERROR_UNAUTHORIZED,     //!< User is not authorized (rejected credentials)
+    ERROR_TLS,              //!< TLS could not be negotiated
+    ERROR_AUTH,             //!< Authentication could not be negotiated
+    ERROR_BIND,             //!< Resource or session binding could not be negotiated
+    ERROR_CONNECTION_CLOSED,//!< Connection closed by output handler.
+    ERROR_DOCUMENT_CLOSED,  //!< Closed by </stream:stream>
+    ERROR_SOCKET,           //!< Socket error
+    ERROR_NETWORK_TIMEOUT,  //!< Some sort of timeout (eg., we never got the roster)
+    ERROR_MISSING_USERNAME  //!< User has a Google Account but no nickname
+  };
+
+  //! States.  See GetState().
+  enum State {
+    STATE_NONE = 0,        //!< Nonexistent state
+    STATE_START,           //!< Initial state.
+    STATE_OPENING,         //!< Exchanging stream headers, authenticating and so on.
+    STATE_OPEN,            //!< Authenticated and bound.
+    STATE_CLOSED,          //!< Session closed, possibly due to error.
+  };
+
+  // SOCKET INPUT AND OUTPUT ------------------------------------------------
+
+  //! Registers the handler for socket output
+  virtual XmppReturnStatus SetOutputHandler(XmppOutputHandler *pxoh) = 0;
+
+  //! Provides socket input to the engine
+  virtual XmppReturnStatus HandleInput(const char * bytes, size_t len) = 0;
+
+  //! Advises the engine that the socket has closed
+  virtual XmppReturnStatus ConnectionClosed(int subcode) = 0;
+
+  // SESSION SETUP ---------------------------------------------------------
+
+  //! Indicates the (bare) JID for the user to use.
+  virtual XmppReturnStatus SetUser(const Jid & jid)= 0;
+
+  //! Get the login (bare) JID.
+  virtual const Jid & GetUser() = 0;
+
+  //! Provides different methods for credentials for login.
+  //! Takes ownership of this object; deletes when login is done
+  virtual XmppReturnStatus SetSaslHandler(SaslHandler * h) = 0;
+
+  //! Sets whether TLS will be used within the connection (default true).
+  virtual XmppReturnStatus 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
+  //! serve up its own certificate rather than one owned by the underlying
+  //! domain.
+  virtual XmppReturnStatus SetTlsServer(const std::string & proxy_hostname, 
+                                        const std::string & proxy_domain) = 0;
+
+  //! Gets whether TLS will be used within the connection.
+  virtual TlsOptions GetTls() = 0;
+
+  //! Sets the request resource name, if any (optional).
+  //! Note that the resource name may be overridden by the server; after
+  //! binding, the actual resource name is available as part of FullJid().
+  virtual XmppReturnStatus SetRequestedResource(const std::string& resource) = 0;
+
+  //! Gets the request resource name.
+  virtual const std::string & GetRequestedResource() = 0;
+
+  //! Sets language
+  virtual void SetLanguage(const std::string & lang) = 0;
+
+  // SESSION MANAGEMENT ---------------------------------------------------
+
+  //! Set callback for state changes.
+  virtual XmppReturnStatus SetSessionHandler(XmppSessionHandler* handler) = 0;
+
+  //! Initiates the XMPP connection.
+  //! After supplying connection settings, call this once to initiate,
+  //! (optionally) encrypt, authenticate, and bind the connection.
+  virtual XmppReturnStatus Connect() = 0;
+
+  //! The current engine state.
+  virtual State GetState() = 0;
+
+  //! Returns true if the connection is encrypted (under TLS)
+  virtual bool IsEncrypted() = 0;
+
+  //! The error code.
+  //! Consult this after XmppOutputHandler.OnClose().
+  virtual Error GetError(int *subcode) = 0;
+
+  //! The stream:error stanza, when the error is XmppEngine::ERROR_STREAM.
+  //! Notice the stanza returned is owned by the XmppEngine and
+  //! is deleted when the engine is destroyed.
+  virtual const XmlElement * GetStreamError() = 0;
+
+  //! Closes down the connection.
+  //! Sends CloseConnection to output, and disconnects and registered
+  //! session handlers.  After Disconnect completes, it is guaranteed
+  //! that no further callbacks will be made.
+  virtual XmppReturnStatus Disconnect() = 0;
+
+  // APPLICATION USE -------------------------------------------------------
+
+  enum HandlerLevel {
+    HL_NONE = 0,
+    HL_PEEK,   //!< Sees messages before all other processing; cannot abort
+    HL_SINGLE, //!< Watches for a single message, e.g., by id and sender
+    HL_SENDER, //!< Watches for a type of message from a specific sender
+    HL_TYPE,   //!< Watches a type of message, e.g., all groupchat msgs
+    HL_ALL,    //!< Watches all messages - gets last shot
+    HL_COUNT,  //!< Count of handler levels
+  };
+
+  //! Adds a listener for session events.
+  //! Stanza delivery is chained to session handlers; the first to
+  //! return 'true' is the last to get each stanza.
+  virtual XmppReturnStatus AddStanzaHandler(XmppStanzaHandler* handler, HandlerLevel level = HL_PEEK) = 0;
+
+  //! Removes a listener for session events.
+  virtual XmppReturnStatus RemoveStanzaHandler(XmppStanzaHandler* handler) = 0;
+
+  //! Sends a stanza to the server.
+  virtual XmppReturnStatus SendStanza(const XmlElement * pelStanza) = 0;
+
+  //! Sends raw text to the server
+  virtual XmppReturnStatus SendRaw(const std::string & text) = 0;
+
+  //! Sends an iq to the server, and registers a callback for the result.
+  //! Returns the cookie passed to the result handler.
+  virtual XmppReturnStatus SendIq(const XmlElement* pelStanza,
+                                  XmppIqHandler* iq_handler,
+                                  XmppIqCookie* cookie) = 0;
+
+  //! Unregisters an iq callback handler given its cookie.
+  //! No callback will come to this handler after it's unregistered.
+  virtual XmppReturnStatus RemoveIqHandler(XmppIqCookie cookie,
+                                      XmppIqHandler** iq_handler) = 0;
+
+
+  //! Forms and sends an error in response to the given stanza.
+  //! Swaps to and from, sets type to "error", and adds error information
+  //! based on the passed code.  Text is optional and may be STR_EMPTY.
+  virtual XmppReturnStatus SendStanzaError(const XmlElement * pelOriginal,
+                                           XmppStanzaError code,
+                                           const std::string & text) = 0;
+
+  //! The fullly bound JID.
+  //! This JID is only valid after binding has succeeded.  If the value
+  //! is JID_NULL, the binding has not succeeded.
+  virtual const Jid & FullJid() = 0;
+
+  //! The next unused iq id for this connection.
+  //! Call this when building iq stanzas, to ensure that each iq
+  //! gets its own unique id.
+  virtual std::string NextId() = 0;
+
+};
+
+}
+
+
+// Move these to a better location
+
+#define XMPP_FAILED(x)                      \
+  ( (x) == buzz::XMPP_RETURN_OK ? false : true)   \
+
+
+#define XMPP_SUCCEEDED(x)                   \
+  ( (x) == buzz::XMPP_RETURN_OK ? true : false)   \
+
+#define IFR(x)                        \
+  do {                                \
+    xmpp_status = (x);                \
+    if (XMPP_FAILED(xmpp_status)) {   \
+      return xmpp_status;             \
+    }                                 \
+  } while (false)                     \
+
+
+#define IFC(x)                        \
+  do {                                \
+    xmpp_status = (x);                \
+    if (XMPP_FAILED(xmpp_status)) {   \
+      goto Cleanup;                   \
+    }                                 \
+  } while (false)                     \
+
+
+#endif
diff --git a/talk/xmpp/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
new file mode 100644
index 0000000..8bcea02
--- /dev/null
+++ b/talk/xmpp/xmppengineimpl.cc
@@ -0,0 +1,464 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/xmppengineimpl.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 {
+
+XmppEngine* XmppEngine::Create() {
+  return new XmppEngineImpl();
+}
+
+
+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) {
+  if (state_ != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+
+  output_handler_ = output_handler;
+
+  return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus XmppEngineImpl::SetSessionHandler(
+    XmppSessionHandler* session_handler) {
+  if (state_ != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+
+  session_handler_ = session_handler;
+
+  return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus XmppEngineImpl::HandleInput(
+    const char* bytes, size_t len) {
+  if (state_ < STATE_OPENING || state_ > STATE_OPEN)
+    return XMPP_RETURN_BADSTATE;
+
+  EnterExit ee(this);
+
+  // TODO: The return value of the xml parser is not checked.
+  stanza_parser_.Parse(bytes, len, false);
+
+  return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus XmppEngineImpl::ConnectionClosed(int subcode) {
+  if (state_ != STATE_CLOSED) {
+    EnterExit ee(this);
+    // If told that connection closed and not already closed,
+    // then connection was unpexectedly dropped.
+    if (subcode) {
+      SignalError(ERROR_SOCKET, subcode);
+    } else {
+      SignalError(ERROR_CONNECTION_CLOSED, 0);  // no subcode
+    }
+  }
+  return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus XmppEngineImpl::SetTls(TlsOptions use_tls) {
+  if (state_ != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+  tls_option_ = use_tls;
+  return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus XmppEngineImpl::SetTlsServer(
+    const std::string& tls_server_hostname,
+    const std::string& tls_server_domain) {
+  if (state_ != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+
+  tls_server_hostname_ = tls_server_hostname;
+  tls_server_domain_= tls_server_domain;
+
+  return XMPP_RETURN_OK;
+}
+
+TlsOptions XmppEngineImpl::GetTls() {
+  return tls_option_;
+}
+
+XmppReturnStatus XmppEngineImpl::SetUser(const Jid& jid) {
+  if (state_ != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+
+  user_jid_ = jid;
+
+  return XMPP_RETURN_OK;
+}
+
+const Jid& XmppEngineImpl::GetUser() {
+  return user_jid_;
+}
+
+XmppReturnStatus XmppEngineImpl::SetSaslHandler(SaslHandler* sasl_handler) {
+  if (state_ != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+
+  sasl_handler_.reset(sasl_handler);
+  return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus XmppEngineImpl::SetRequestedResource(
+    const std::string& resource) {
+  if (state_ != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+
+  requested_resource_ = resource;
+
+  return XMPP_RETURN_OK;
+}
+
+const std::string& XmppEngineImpl::GetRequestedResource() {
+  return requested_resource_;
+}
+
+XmppReturnStatus XmppEngineImpl::AddStanzaHandler(
+    XmppStanzaHandler* stanza_handler,
+    XmppEngine::HandlerLevel level) {
+  if (state_ == STATE_CLOSED)
+    return XMPP_RETURN_BADSTATE;
+
+  stanza_handlers_[level]->push_back(stanza_handler);
+
+  return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus XmppEngineImpl::RemoveStanzaHandler(
+    XmppStanzaHandler* stanza_handler) {
+  bool found = false;
+
+  for (int level = 0; level < HL_COUNT; level += 1) {
+    StanzaHandlerVector::iterator new_end =
+      std::remove(stanza_handlers_[level]->begin(),
+      stanza_handlers_[level]->end(),
+      stanza_handler);
+
+    if (new_end != stanza_handlers_[level]->end()) {
+      stanza_handlers_[level]->erase(new_end, stanza_handlers_[level]->end());
+      found = true;
+    }
+  }
+
+  if (!found)
+    return XMPP_RETURN_BADARGUMENT;
+
+  return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus XmppEngineImpl::Connect() {
+  if (state_ != STATE_START)
+    return XMPP_RETURN_BADSTATE;
+
+  EnterExit ee(this);
+
+  // get the login task started
+  state_ = STATE_OPENING;
+  if (login_task_) {
+    login_task_->IncomingStanza(NULL, false);
+    if (login_task_->IsDone())
+      login_task_.reset();
+  }
+
+  return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus XmppEngineImpl::SendStanza(const XmlElement* element) {
+  if (state_ == STATE_CLOSED)
+    return XMPP_RETURN_BADSTATE;
+
+  EnterExit ee(this);
+
+  if (login_task_) {
+    // still handshaking - then outbound stanzas are queued
+    login_task_->OutgoingStanza(element);
+  } else {
+    // handshake done - send straight through
+    InternalSendStanza(element);
+  }
+
+  return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus XmppEngineImpl::SendRaw(const std::string& text) {
+  if (state_ == STATE_CLOSED || login_task_)
+    return XMPP_RETURN_BADSTATE;
+
+  EnterExit ee(this);
+
+  (*output_) << text;
+
+  return XMPP_RETURN_OK;
+}
+
+std::string XmppEngineImpl::NextId() {
+  std::stringstream ss;
+  ss << next_id_++;
+  return ss.str();
+}
+
+XmppReturnStatus XmppEngineImpl::Disconnect() {
+  if (state_ != STATE_CLOSED) {
+    EnterExit ee(this);
+    if (state_ == STATE_OPEN)
+      *output_ << "</stream:stream>";
+    state_ = STATE_CLOSED;
+  }
+
+  return XMPP_RETURN_OK;
+}
+
+void XmppEngineImpl::IncomingStart(const XmlElement* start) {
+  if (HasError() || raised_reset_)
+    return;
+
+  if (login_task_) {
+    // start-stream should go to login task
+    login_task_->IncomingStanza(start, true);
+    if (login_task_->IsDone())
+      login_task_.reset();
+  }
+  else {
+    // if not logging in, it's an error to see a start
+    SignalError(ERROR_XML, 0);
+  }
+}
+
+void XmppEngineImpl::IncomingStanza(const XmlElement* stanza) {
+  if (HasError() || raised_reset_)
+    return;
+
+  if (stanza->Name() == QN_STREAM_ERROR) {
+    // Explicit XMPP stream error
+    SignalStreamError(stanza);
+  } else if (login_task_) {
+    // Handle login handshake
+    login_task_->IncomingStanza(stanza, false);
+    if (login_task_->IsDone())
+      login_task_.reset();
+  } else if (HandleIqResponse(stanza)) {
+    // iq is handled by above call
+  } else {
+    // give every "peek" handler a shot at all stanzas
+    for (size_t i = 0; i < stanza_handlers_[HL_PEEK]->size(); i += 1) {
+      (*stanza_handlers_[HL_PEEK])[i]->HandleStanza(stanza);
+    }
+
+    // give other handlers a shot in precedence order, stopping after handled
+    for (int level = HL_SINGLE; level <= HL_ALL; level += 1) {
+      for (size_t i = 0; i < stanza_handlers_[level]->size(); i += 1) {
+        if ((*stanza_handlers_[level])[i]->HandleStanza(stanza))
+          return;
+      }
+    }
+
+    // If nobody wants to handle a stanza then send back an error.
+    // Only do this for IQ stanzas as messages should probably just be dropped
+    // and presence stanzas should certainly be dropped.
+    std::string type = stanza->Attr(QN_TYPE);
+    if (stanza->Name() == QN_IQ &&
+        !(type == "error" || type == "result")) {
+      SendStanzaError(stanza, XSE_FEATURE_NOT_IMPLEMENTED, STR_EMPTY);
+    }
+  }
+}
+
+void XmppEngineImpl::IncomingEnd(bool isError) {
+  if (HasError() || raised_reset_)
+    return;
+
+  SignalError(isError ? ERROR_XML : ERROR_DOCUMENT_CLOSED, 0);
+}
+
+void XmppEngineImpl::InternalSendStart(const std::string& to) {
+  std::string hostname = tls_server_hostname_;
+  if (hostname.empty())
+    hostname = to;
+
+  // If not language is specified, the spec says use *
+  std::string lang = lang_;
+  if (lang.length() == 0)
+    lang = "*";
+
+  // send stream-beginning
+  // note, we put a \r\n at tne end fo the first line to cause non-XMPP
+  // line-oriented servers (e.g., Apache) to reveal themselves more quickly.
+  *output_ << "<stream:stream to=\"" << hostname << "\" "
+           << "xml:lang=\"" << lang << "\" "
+           << "version=\"1.0\" "
+           << "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+           << "xmlns=\"jabber:client\">\r\n";
+}
+
+void XmppEngineImpl::InternalSendStanza(const XmlElement* element) {
+  // It should really never be necessary to set a FROM attribute on a stanza.
+  // It is implied by the bind on the stream and if you get it wrong
+  // (by flipping from/to on a message?) the server will close the stream.
+  ASSERT(!element->HasAttr(QN_FROM));
+
+  XmlPrinter::PrintXml(output_.get(), element, &xmlns_stack_);
+}
+
+std::string XmppEngineImpl::ChooseBestSaslMechanism(
+    const std::vector<std::string>& mechanisms, bool encrypted) {
+  return sasl_handler_->ChooseBestSaslMechanism(mechanisms, encrypted);
+}
+
+SaslMechanism* XmppEngineImpl::GetSaslMechanism(const std::string& name) {
+  return sasl_handler_->CreateSaslMechanism(name);
+}
+
+void XmppEngineImpl::SignalBound(const Jid& fullJid) {
+  if (state_ == STATE_OPENING) {
+    bound_jid_ = fullJid;
+    state_ = STATE_OPEN;
+  }
+}
+
+void XmppEngineImpl::SignalStreamError(const XmlElement* stream_error) {
+  if (state_ != STATE_CLOSED) {
+    stream_error_.reset(new XmlElement(*stream_error));
+    SignalError(ERROR_STREAM, 0);
+  }
+}
+
+void XmppEngineImpl::SignalError(Error error_code, int sub_code) {
+  if (state_ != STATE_CLOSED) {
+    error_code_ = error_code;
+    subcode_ = sub_code;
+    state_ = STATE_CLOSED;
+  }
+}
+
+bool XmppEngineImpl::HasError() {
+  return error_code_ != ERROR_NONE;
+}
+
+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;
+  }
+}
+
+XmppEngineImpl::EnterExit::EnterExit(XmppEngineImpl* engine)
+    : engine_(engine),
+  state_(engine->state_),
+  error_(engine->error_code_) {
+  engine->engine_entered_ += 1;
+}
+
+XmppEngineImpl::EnterExit::~EnterExit()  {
+ XmppEngineImpl* engine = engine_;
+
+ engine->engine_entered_ -= 1;
+
+ bool closing = (engine->state_ != state_ &&
+       engine->state_ == STATE_CLOSED);
+ bool flushing = closing || (engine->engine_entered_ == 0);
+
+ if (engine->output_handler_ && flushing) {
+   std::string output = engine->output_->str();
+   if (output.length() > 0)
+     engine->output_handler_->WriteOutput(output.c_str(), output.length());
+   engine->output_->str("");
+
+   if (closing) {
+     engine->output_handler_->CloseConnection();
+     engine->output_handler_ = 0;
+   }
+ }
+
+ if (engine->engine_entered_)
+   return;
+
+ if (engine->raised_reset_) {
+   engine->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.
+ }
+}
+
+}  // namespace buzz
diff --git a/talk/xmpp/xmppengineimpl.h b/talk/xmpp/xmppengineimpl.h
new file mode 100644
index 0000000..e292e75
--- /dev/null
+++ b/talk/xmpp/xmppengineimpl.h
@@ -0,0 +1,284 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_XMPPENGINEIMPL_H_
+#define TALK_XMPP_XMPPENGINEIMPL_H_
+
+#include <sstream>
+#include <vector>
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmppstanzaparser.h"
+
+namespace buzz {
+
+class XmppLoginTask;
+class XmppEngine;
+class XmppIqEntry;
+class SaslHandler;
+class SaslMechanism;
+
+//! The XMPP connection engine.
+//! This engine implements the client side of the 'core' XMPP protocol.
+//! To use it, register an XmppOutputHandler to handle socket output
+//! and pass socket input to HandleInput.  Then application code can
+//! set up the connection with a user, password, and other settings,
+//! and then call Connect() to initiate the connection.
+//! An application can listen for events and receive stanzas by
+//! registering an XmppStanzaHandler via AddStanzaHandler().
+class XmppEngineImpl : public XmppEngine {
+ public:
+  XmppEngineImpl();
+  virtual ~XmppEngineImpl();
+
+  // SOCKET INPUT AND OUTPUT ------------------------------------------------
+
+  //! Registers the handler for socket output
+  virtual XmppReturnStatus SetOutputHandler(XmppOutputHandler *pxoh);
+
+  //! Provides socket input to the engine
+  virtual XmppReturnStatus HandleInput(const char* bytes, size_t len);
+
+  //! Advises the engine that the socket has closed
+  virtual XmppReturnStatus ConnectionClosed(int subcode);
+
+  // SESSION SETUP ---------------------------------------------------------
+
+  //! Indicates the (bare) JID for the user to use.
+  virtual XmppReturnStatus SetUser(const Jid& jid);
+
+  //! Get the login (bare) JID.
+  virtual const Jid& GetUser();
+
+  //! Indicates the autentication to use.  Takes ownership of the object.
+  virtual XmppReturnStatus SetSaslHandler(SaslHandler* sasl_handler);
+
+  //! Sets whether TLS will be used within the connection (default true).
+  virtual XmppReturnStatus 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);
+
+  //! Gets whether TLS will be used within the connection.
+  virtual TlsOptions GetTls();
+
+  //! Sets the request resource name, if any (optional).
+  //! Note that the resource name may be overridden by the server; after
+  //! binding, the actual resource name is available as part of FullJid().
+  virtual XmppReturnStatus SetRequestedResource(const std::string& resource);
+
+  //! Gets the request resource name.
+  virtual const std::string& GetRequestedResource();
+
+  //! Sets language
+  virtual void SetLanguage(const std::string& lang) {
+    lang_ = lang;
+  }
+
+  // SESSION MANAGEMENT ---------------------------------------------------
+
+  //! Set callback for state changes.
+  virtual XmppReturnStatus SetSessionHandler(XmppSessionHandler* handler);
+
+  //! Initiates the XMPP connection.
+  //! After supplying connection settings, call this once to initiate,
+  //! (optionally) encrypt, authenticate, and bind the connection.
+  virtual XmppReturnStatus Connect();
+
+  //! The current engine state.
+  virtual State GetState() { return state_; }
+
+  //! Returns true if the connection is encrypted (under TLS)
+  virtual bool IsEncrypted() { return encrypted_; }
+
+  //! The error code.
+  //! Consult this after XmppOutputHandler.OnClose().
+  virtual Error GetError(int *subcode) {
+     if (subcode) {
+       *subcode = subcode_;
+     }
+     return error_code_;
+  }
+
+  //! The stream:error stanza, when the error is XmppEngine::ERROR_STREAM.
+  //! Notice the stanza returned is owned by the XmppEngine and
+  //! is deleted when the engine is destroyed.
+  virtual const XmlElement* GetStreamError() { return stream_error_.get(); }
+
+  //! Closes down the connection.
+  //! Sends CloseConnection to output, and disconnects and registered
+  //! session handlers.  After Disconnect completes, it is guaranteed
+  //! that no further callbacks will be made.
+  virtual XmppReturnStatus Disconnect();
+
+  // APPLICATION USE -------------------------------------------------------
+
+  //! Adds a listener for session events.
+  //! Stanza delivery is chained to session handlers; the first to
+  //! return 'true' is the last to get each stanza.
+  virtual XmppReturnStatus AddStanzaHandler(XmppStanzaHandler* handler,
+                                            XmppEngine::HandlerLevel level);
+
+  //! Removes a listener for session events.
+  virtual XmppReturnStatus RemoveStanzaHandler(XmppStanzaHandler* handler);
+
+  //! Sends a stanza to the server.
+  virtual XmppReturnStatus SendStanza(const XmlElement* stanza);
+
+  //! Sends raw text to the server
+  virtual XmppReturnStatus SendRaw(const std::string& text);
+
+  //! Sends an iq to the server, and registers a callback for the result.
+  //! Returns the cookie passed to the result handler.
+  virtual XmppReturnStatus SendIq(const XmlElement* stanza,
+                                  XmppIqHandler* iq_handler,
+                                  XmppIqCookie* cookie);
+
+  //! Unregisters an iq callback handler given its cookie.
+  //! No callback will come to this handler after it's unregistered.
+  virtual XmppReturnStatus RemoveIqHandler(XmppIqCookie cookie,
+                                      XmppIqHandler** iq_handler);
+
+  //! Forms and sends an error in response to the given stanza.
+  //! Swaps to and from, sets type to "error", and adds error information
+  //! based on the passed code.  Text is optional and may be STR_EMPTY.
+  virtual XmppReturnStatus SendStanzaError(const XmlElement* pelOriginal,
+                                           XmppStanzaError code,
+                                           const std::string& text);
+
+  //! The fullly bound JID.
+  //! This JID is only valid after binding has succeeded.  If the value
+  //! is JID_NULL, the binding has not succeeded.
+  virtual const Jid& FullJid() { return bound_jid_; }
+
+  //! The next unused iq id for this connection.
+  //! Call this when building iq stanzas, to ensure that each iq
+  //! gets its own unique id.
+  virtual std::string NextId();
+
+ private:
+  friend class XmppLoginTask;
+  friend class XmppIqEntry;
+
+  void IncomingStanza(const XmlElement *stanza);
+  void IncomingStart(const XmlElement *stanza);
+  void IncomingEnd(bool isError);
+
+  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);
+  void RaiseReset() { raised_reset_ = true; }
+
+  class StanzaParseHandler : public XmppStanzaParseHandler {
+   public:
+    StanzaParseHandler(XmppEngineImpl* outer) : outer_(outer) {}
+    virtual ~StanzaParseHandler() {}
+
+    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 {
+   public:
+    EnterExit(XmppEngineImpl* engine);
+    ~EnterExit();
+   private:
+    XmppEngineImpl* engine_;
+    State state_;
+    Error error_;
+
+  };
+
+  friend class StanzaParseHandler;
+  friend class EnterExit;
+
+  StanzaParseHandler stanza_parse_handler_;
+  XmppStanzaParser stanza_parser_;
+
+  // state
+  int engine_entered_;
+  Jid user_jid_;
+  std::string password_;
+  std::string requested_resource_;
+  TlsOptions tls_option_;
+  std::string tls_server_hostname_;
+  std::string tls_server_domain_;
+  talk_base::scoped_ptr<XmppLoginTask> login_task_;
+  std::string lang_;
+
+  int next_id_;
+  Jid bound_jid_;
+  State state_;
+  bool encrypted_;
+  Error error_code_;
+  int subcode_;
+  talk_base::scoped_ptr<XmlElement> stream_error_;
+  bool raised_reset_;
+  XmppOutputHandler* output_handler_;
+  XmppSessionHandler* session_handler_;
+
+  XmlnsStack xmlns_stack_;
+
+  typedef std::vector<XmppStanzaHandler*> StanzaHandlerVector;
+  talk_base::scoped_ptr<StanzaHandlerVector> stanza_handlers_[HL_COUNT];
+
+  typedef std::vector<XmppIqEntry*> IqEntryVector;
+  talk_base::scoped_ptr<IqEntryVector> iq_entries_;
+
+  talk_base::scoped_ptr<SaslHandler> sasl_handler_;
+
+  talk_base::scoped_ptr<std::stringstream> output_;
+};
+
+}  // namespace buzz
+
+#endif  // TALK_XMPP_XMPPENGINEIMPL_H_
diff --git a/talk/xmpp/xmppengineimpl_iq.cc b/talk/xmpp/xmppengineimpl_iq.cc
new file mode 100644
index 0000000..5834b90
--- /dev/null
+++ b/talk/xmpp/xmppengineimpl_iq.cc
@@ -0,0 +1,277 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <vector>
+#include <algorithm>
+#include "talk/base/common.h"
+#include "talk/xmpp/xmppengineimpl.h"
+#include "talk/xmpp/constants.h"
+
+namespace buzz {
+
+class XmppIqEntry {
+  XmppIqEntry(const std::string & id, const std::string & to,
+               XmppEngine * pxce, XmppIqHandler * iq_handler) :
+    id_(id),
+    to_(to),
+    engine_(pxce),
+    iq_handler_(iq_handler) {
+  }
+
+private:
+  friend class XmppEngineImpl;
+
+  const std::string id_;
+  const std::string to_;
+  XmppEngine * const engine_;
+  XmppIqHandler * const iq_handler_;
+};
+
+
+XmppReturnStatus
+XmppEngineImpl::SendIq(const XmlElement * element, XmppIqHandler * iq_handler,
+  XmppIqCookie* cookie) {
+  if (state_ == STATE_CLOSED)
+    return XMPP_RETURN_BADSTATE;
+  if (NULL == iq_handler)
+    return XMPP_RETURN_BADARGUMENT;
+  if (!element || element->Name() != QN_IQ)
+    return XMPP_RETURN_BADARGUMENT;
+
+  const std::string& type = element->Attr(QN_TYPE);
+  if (type != "get" && type != "set")
+    return XMPP_RETURN_BADARGUMENT;
+
+  if (!element->HasAttr(QN_ID))
+    return XMPP_RETURN_BADARGUMENT;
+  const std::string& id = element->Attr(QN_ID);
+
+  XmppIqEntry * iq_entry = new XmppIqEntry(id,
+                                              element->Attr(QN_TO),
+                                              this, iq_handler);
+  iq_entries_->push_back(iq_entry);
+  SendStanza(element);
+
+  if (cookie)
+    *cookie = iq_entry;
+
+  return XMPP_RETURN_OK;
+}
+
+
+XmppReturnStatus
+XmppEngineImpl::RemoveIqHandler(XmppIqCookie cookie,
+    XmppIqHandler ** iq_handler) {
+
+  std::vector<XmppIqEntry*, std::allocator<XmppIqEntry*> >::iterator pos;
+
+  pos = std::find(iq_entries_->begin(),
+                  iq_entries_->end(),
+                  reinterpret_cast<XmppIqEntry*>(cookie));
+
+  if (pos == iq_entries_->end())
+    return XMPP_RETURN_BADARGUMENT;
+
+  XmppIqEntry* entry = *pos;
+  iq_entries_->erase(pos);
+  if (iq_handler)
+    *iq_handler = entry->iq_handler_;
+  delete entry;
+
+  return XMPP_RETURN_OK;
+}
+
+void
+XmppEngineImpl::DeleteIqCookies() {
+  for (size_t i = 0; i < iq_entries_->size(); i += 1) {
+    XmppIqEntry * iq_entry_ = (*iq_entries_)[i];
+    (*iq_entries_)[i] = NULL;
+    delete iq_entry_;
+  }
+  iq_entries_->clear();
+}
+
+static void
+AecImpl(XmlElement * error_element, const QName & name,
+        const char * type, const char * code) {
+  error_element->AddElement(new XmlElement(QN_ERROR));
+  error_element->AddAttr(QN_CODE, code, 1);
+  error_element->AddAttr(QN_TYPE, type, 1);
+  error_element->AddElement(new XmlElement(name, true), 1);
+}
+
+
+static void
+AddErrorCode(XmlElement * error_element, XmppStanzaError code) {
+  switch (code) {
+    case XSE_BAD_REQUEST:
+      AecImpl(error_element, QN_STANZA_BAD_REQUEST, "modify", "400");
+      break;
+    case XSE_CONFLICT:
+      AecImpl(error_element, QN_STANZA_CONFLICT, "cancel", "409");
+      break;
+    case XSE_FEATURE_NOT_IMPLEMENTED:
+      AecImpl(error_element, QN_STANZA_FEATURE_NOT_IMPLEMENTED,
+              "cancel", "501");
+      break;
+    case XSE_FORBIDDEN:
+      AecImpl(error_element, QN_STANZA_FORBIDDEN, "auth", "403");
+      break;
+    case XSE_GONE:
+      AecImpl(error_element, QN_STANZA_GONE, "modify", "302");
+      break;
+    case XSE_INTERNAL_SERVER_ERROR:
+      AecImpl(error_element, QN_STANZA_INTERNAL_SERVER_ERROR, "wait", "500");
+      break;
+    case XSE_ITEM_NOT_FOUND:
+      AecImpl(error_element, QN_STANZA_ITEM_NOT_FOUND, "cancel", "404");
+      break;
+    case XSE_JID_MALFORMED:
+      AecImpl(error_element, QN_STANZA_JID_MALFORMED, "modify", "400");
+      break;
+    case XSE_NOT_ACCEPTABLE:
+      AecImpl(error_element, QN_STANZA_NOT_ACCEPTABLE, "cancel", "406");
+      break;
+    case XSE_NOT_ALLOWED:
+      AecImpl(error_element, QN_STANZA_NOT_ALLOWED, "cancel", "405");
+      break;
+    case XSE_PAYMENT_REQUIRED:
+      AecImpl(error_element, QN_STANZA_PAYMENT_REQUIRED, "auth", "402");
+      break;
+    case XSE_RECIPIENT_UNAVAILABLE:
+      AecImpl(error_element, QN_STANZA_RECIPIENT_UNAVAILABLE, "wait", "404");
+      break;
+    case XSE_REDIRECT:
+      AecImpl(error_element, QN_STANZA_REDIRECT, "modify", "302");
+      break;
+    case XSE_REGISTRATION_REQUIRED:
+      AecImpl(error_element, QN_STANZA_REGISTRATION_REQUIRED, "auth", "407");
+      break;
+    case XSE_SERVER_NOT_FOUND:
+      AecImpl(error_element, QN_STANZA_REMOTE_SERVER_NOT_FOUND,
+              "cancel", "404");
+      break;
+    case XSE_SERVER_TIMEOUT:
+      AecImpl(error_element, QN_STANZA_REMOTE_SERVER_TIMEOUT, "wait", "502");
+      break;
+    case XSE_RESOURCE_CONSTRAINT:
+      AecImpl(error_element, QN_STANZA_RESOURCE_CONSTRAINT, "wait", "500");
+      break;
+    case XSE_SERVICE_UNAVAILABLE:
+      AecImpl(error_element, QN_STANZA_SERVICE_UNAVAILABLE, "cancel", "503");
+      break;
+    case XSE_SUBSCRIPTION_REQUIRED:
+      AecImpl(error_element, QN_STANZA_SUBSCRIPTION_REQUIRED, "auth", "407");
+      break;
+    case XSE_UNDEFINED_CONDITION:
+      AecImpl(error_element, QN_STANZA_UNDEFINED_CONDITION, "wait", "500");
+      break;
+    case XSE_UNEXPECTED_REQUEST:
+      AecImpl(error_element, QN_STANZA_UNEXPECTED_REQUEST, "wait", "400");
+      break;
+  }
+}
+
+
+XmppReturnStatus
+XmppEngineImpl::SendStanzaError(const XmlElement * element_original,
+                                XmppStanzaError code,
+                                const std::string & text) {
+
+  if (state_ == STATE_CLOSED)
+    return XMPP_RETURN_BADSTATE;
+
+  XmlElement error_element(element_original->Name());
+  error_element.AddAttr(QN_TYPE, "error");
+
+  // copy attrs, copy 'from' to 'to' and strip 'from'
+  for (const XmlAttr * attribute = element_original->FirstAttr();
+       attribute; attribute = attribute->NextAttr()) {
+    QName name = attribute->Name();
+    if (name == QN_TO)
+      continue; // no need to put a from attr.  Server will stamp stanza
+    else if (name == QN_FROM)
+      name = QN_TO;
+    else if (name == QN_TYPE)
+      continue;
+    error_element.AddAttr(name, attribute->Value());
+  }
+
+  // copy children
+  for (const XmlChild * child = element_original->FirstChild();
+       child;
+       child = child->NextChild()) {
+    if (child->IsText()) {
+      error_element.AddText(child->AsText()->Text());
+    } else {
+      error_element.AddElement(new XmlElement(*(child->AsElement())));
+    }
+  }
+
+  // add error information
+  AddErrorCode(&error_element, code);
+  if (text != STR_EMPTY) {
+    XmlElement * text_element = new XmlElement(QN_STANZA_TEXT, true);
+    text_element->AddText(text);
+    error_element.AddElement(text_element);
+  }
+
+  SendStanza(&error_element);
+
+  return XMPP_RETURN_OK;
+}
+
+
+bool
+XmppEngineImpl::HandleIqResponse(const XmlElement * element) {
+  if (iq_entries_->empty())
+    return false;
+  if (element->Name() != QN_IQ)
+    return false;
+  std::string type = element->Attr(QN_TYPE);
+  if (type != "result" && type != "error")
+    return false;
+  if (!element->HasAttr(QN_ID))
+    return false;
+  std::string id = element->Attr(QN_ID);
+  std::string from = element->Attr(QN_FROM);
+
+  for (std::vector<XmppIqEntry *>::iterator it = iq_entries_->begin();
+       it != iq_entries_->end(); it += 1) {
+    XmppIqEntry * iq_entry = *it;
+    if (iq_entry->id_ == id && iq_entry->to_ == from) {
+      iq_entries_->erase(it);
+      iq_entry->iq_handler_->IqResponse(iq_entry, element);
+      delete iq_entry;
+      return true;
+    }
+  }
+
+  return false;
+}
+
+}
diff --git a/talk/xmpp/xmpplogintask.cc b/talk/xmpp/xmpplogintask.cc
new file mode 100644
index 0000000..eec943b
--- /dev/null
+++ b/talk/xmpp/xmpplogintask.cc
@@ -0,0 +1,397 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/xmpplogintask.h"
+
+#include <string>
+#include <vector>
+
+#include "talk/base/base64.h"
+#include "talk/base/common.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/saslmechanism.h"
+#include "talk/xmpp/xmppengineimpl.h"
+
+using talk_base::ConstantLabel;
+
+namespace buzz {
+
+#ifdef _DEBUG
+const ConstantLabel XmppLoginTask::LOGINTASK_STATES[] = {
+  KLABEL(LOGINSTATE_INIT),
+  KLABEL(LOGINSTATE_STREAMSTART_SENT),
+  KLABEL(LOGINSTATE_STARTED_XMPP),
+  KLABEL(LOGINSTATE_TLS_INIT),
+  KLABEL(LOGINSTATE_AUTH_INIT),
+  KLABEL(LOGINSTATE_BIND_INIT),
+  KLABEL(LOGINSTATE_TLS_REQUESTED),
+  KLABEL(LOGINSTATE_SASL_RUNNING),
+  KLABEL(LOGINSTATE_BIND_REQUESTED),
+  KLABEL(LOGINSTATE_SESSION_REQUESTED),
+  KLABEL(LOGINSTATE_DONE),
+  LASTLABEL
+};
+#endif  // _DEBUG
+XmppLoginTask::XmppLoginTask(XmppEngineImpl * pctx) :
+  pctx_(pctx),
+  authNeeded_(true),
+  allowNonGoogleLogin_(true),
+  state_(LOGINSTATE_INIT),
+  pelStanza_(NULL),
+  isStart_(false),
+  iqId_(STR_EMPTY),
+  pelFeatures_(NULL),
+  fullJid_(STR_EMPTY),
+  streamId_(STR_EMPTY),
+  pvecQueuedStanzas_(new std::vector<XmlElement *>()),
+  sasl_mech_(NULL) {
+}
+
+XmppLoginTask::~XmppLoginTask() {
+  for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1)
+    delete (*pvecQueuedStanzas_)[i];
+}
+
+void
+XmppLoginTask::IncomingStanza(const XmlElement *element, bool isStart) {
+  pelStanza_ = element;
+  isStart_ = isStart;
+  Advance();
+  pelStanza_ = NULL;
+  isStart_ = false;
+}
+
+const XmlElement *
+XmppLoginTask::NextStanza() {
+  const XmlElement * result = pelStanza_;
+  pelStanza_ = NULL;
+  return result;
+}
+
+bool
+XmppLoginTask::Advance() {
+
+  for (;;) {
+
+    const XmlElement * element = NULL;
+
+#if _DEBUG
+    LOG(LS_VERBOSE) << "XmppLoginTask::Advance - "
+      << talk_base::ErrorName(state_, LOGINTASK_STATES);
+#endif  // _DEBUG
+
+    switch (state_) {
+
+      case LOGINSTATE_INIT: {
+        pctx_->RaiseReset();
+        pelFeatures_.reset(NULL);
+
+        // The proper domain to verify against is the real underlying
+        // domain - i.e., the domain that owns the JID.  Our XmppEngineImpl
+        // also allows matching against a proxy domain instead, if it is told
+        // to do so - see the implementation of XmppEngineImpl::StartTls and
+        // XmppEngine::SetTlsServerDomain to see how you can use that feature
+        pctx_->InternalSendStart(pctx_->user_jid_.domain());
+        state_ = LOGINSTATE_STREAMSTART_SENT;
+        break;
+      }
+
+      case LOGINSTATE_STREAMSTART_SENT: {
+        if (NULL == (element = NextStanza()))
+          return true;
+
+        if (!isStart_ || !HandleStartStream(element))
+          return Failure(XmppEngine::ERROR_VERSION);
+
+        state_ = LOGINSTATE_STARTED_XMPP;
+        return true;
+      }
+
+      case LOGINSTATE_STARTED_XMPP: {
+        if (NULL == (element = NextStanza()))
+          return true;
+
+        if (!HandleFeatures(element))
+          return Failure(XmppEngine::ERROR_VERSION);
+
+        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;
+        }
+
+        if (authNeeded_) {
+          state_ = LOGINSTATE_AUTH_INIT;
+          continue;
+        }
+
+        state_ = LOGINSTATE_BIND_INIT;
+        continue;
+      }
+
+      case LOGINSTATE_TLS_INIT: {
+        const XmlElement * pelTls = GetFeature(QN_TLS_STARTTLS);
+        if (!pelTls)
+          return Failure(XmppEngine::ERROR_TLS);
+
+        XmlElement el(QN_TLS_STARTTLS, true);
+        pctx_->InternalSendStanza(&el);
+        state_ = LOGINSTATE_TLS_REQUESTED;
+        continue;
+      }
+
+      case LOGINSTATE_TLS_REQUESTED: {
+        if (NULL == (element = NextStanza()))
+          return true;
+        if (element->Name() != QN_TLS_PROCEED)
+          return Failure(XmppEngine::ERROR_TLS);
+
+        // The proper domain to verify against is the real underlying
+        // domain - i.e., the domain that owns the JID.  Our XmppEngineImpl
+        // also allows matching against a proxy domain instead, if it is told
+        // to do so - see the implementation of XmppEngineImpl::StartTls and
+        // XmppEngine::SetTlsServerDomain to see how you can use that feature
+        pctx_->StartTls(pctx_->user_jid_.domain());
+        pctx_->tls_option_ = buzz::TLS_ENABLED;
+        state_ = LOGINSTATE_INIT;
+        continue;
+      }
+
+      case LOGINSTATE_AUTH_INIT: {
+        const XmlElement * pelSaslAuth = GetFeature(QN_SASL_MECHANISMS);
+        if (!pelSaslAuth) {
+          return Failure(XmppEngine::ERROR_AUTH);
+        }
+
+        // Collect together the SASL auth mechanisms presented by the server
+        std::vector<std::string> mechanisms;
+        for (const XmlElement * pelMech =
+             pelSaslAuth->FirstNamed(QN_SASL_MECHANISM);
+             pelMech;
+             pelMech = pelMech->NextNamed(QN_SASL_MECHANISM)) {
+
+          mechanisms.push_back(pelMech->BodyText());
+        }
+
+        // Given all the mechanisms, choose the best
+        std::string choice(pctx_->ChooseBestSaslMechanism(mechanisms, pctx_->IsEncrypted()));
+        if (choice.empty()) {
+          return Failure(XmppEngine::ERROR_AUTH);
+        }
+
+        // No recognized auth mechanism - that's an error
+        sasl_mech_.reset(pctx_->GetSaslMechanism(choice));
+        if (!sasl_mech_) {
+          return Failure(XmppEngine::ERROR_AUTH);
+        }
+
+        // OK, let's start it.
+        XmlElement * auth = sasl_mech_->StartSaslAuth();
+        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;
+        state_ = LOGINSTATE_SASL_RUNNING;
+        continue;
+      }
+
+      case LOGINSTATE_SASL_RUNNING: {
+        if (NULL == (element = NextStanza()))
+          return true;
+        if (element->Name().Namespace() != NS_SASL)
+          return Failure(XmppEngine::ERROR_AUTH);
+        if (element->Name() == QN_SASL_CHALLENGE) {
+          XmlElement * response = sasl_mech_->HandleSaslChallenge(element);
+          if (response == NULL) {
+            return Failure(XmppEngine::ERROR_AUTH);
+          }
+          pctx_->InternalSendStanza(response);
+          delete response;
+          state_ = LOGINSTATE_SASL_RUNNING;
+          continue;
+        }
+        if (element->Name() != QN_SASL_SUCCESS) {
+          return Failure(XmppEngine::ERROR_UNAUTHORIZED);
+        }
+
+        // Authenticated!
+        authNeeded_ = false;
+        state_ = LOGINSTATE_INIT;
+        continue;
+      }
+
+      case LOGINSTATE_BIND_INIT: {
+        const XmlElement * pelBindFeature = GetFeature(QN_BIND_BIND);
+        const XmlElement * pelSessionFeature = GetFeature(QN_SESSION_SESSION);
+        if (!pelBindFeature || !pelSessionFeature)
+          return Failure(XmppEngine::ERROR_BIND);
+
+        XmlElement iq(QN_IQ);
+        iq.AddAttr(QN_TYPE, "set");
+
+        iqId_ = pctx_->NextId();
+        iq.AddAttr(QN_ID, iqId_);
+        iq.AddElement(new XmlElement(QN_BIND_BIND, true));
+
+        if (pctx_->requested_resource_ != STR_EMPTY) {
+          iq.AddElement(new XmlElement(QN_BIND_RESOURCE), 1);
+          iq.AddText(pctx_->requested_resource_, 2);
+        }
+        pctx_->InternalSendStanza(&iq);
+        state_ = LOGINSTATE_BIND_REQUESTED;
+        continue;
+      }
+
+      case LOGINSTATE_BIND_REQUESTED: {
+        if (NULL == (element = NextStanza()))
+          return true;
+
+        if (element->Name() != QN_IQ || element->Attr(QN_ID) != iqId_ ||
+            element->Attr(QN_TYPE) == "get" || element->Attr(QN_TYPE) == "set")
+          return true;
+
+        if (element->Attr(QN_TYPE) != "result" || element->FirstElement() == NULL ||
+            element->FirstElement()->Name() != QN_BIND_BIND)
+          return Failure(XmppEngine::ERROR_BIND);
+
+        fullJid_ = Jid(element->FirstElement()->TextNamed(QN_BIND_JID));
+        if (!fullJid_.IsFull()) {
+          return Failure(XmppEngine::ERROR_BIND);
+        }
+
+        // now request session
+        XmlElement iq(QN_IQ);
+        iq.AddAttr(QN_TYPE, "set");
+
+        iqId_ = pctx_->NextId();
+        iq.AddAttr(QN_ID, iqId_);
+        iq.AddElement(new XmlElement(QN_SESSION_SESSION, true));
+        pctx_->InternalSendStanza(&iq);
+
+        state_ = LOGINSTATE_SESSION_REQUESTED;
+        continue;
+      }
+
+      case LOGINSTATE_SESSION_REQUESTED: {
+        if (NULL == (element = NextStanza()))
+          return true;
+        if (element->Name() != QN_IQ || element->Attr(QN_ID) != iqId_ ||
+            element->Attr(QN_TYPE) == "get" || element->Attr(QN_TYPE) == "set")
+          return false;
+
+        if (element->Attr(QN_TYPE) != "result")
+          return Failure(XmppEngine::ERROR_BIND);
+
+        pctx_->SignalBound(fullJid_);
+        FlushQueuedStanzas();
+        state_ = LOGINSTATE_DONE;
+        return true;
+      }
+
+      case LOGINSTATE_DONE:
+        return false;
+    }
+  }
+}
+
+bool
+XmppLoginTask::HandleStartStream(const XmlElement *element) {
+
+  if (element->Name() != QN_STREAM_STREAM)
+    return false;
+
+  if (element->Attr(QN_XMLNS) != "jabber:client")
+    return false;
+
+  if (element->Attr(QN_VERSION) != "1.0")
+    return false;
+
+  if (!element->HasAttr(QN_ID))
+    return false;
+
+  streamId_ = element->Attr(QN_ID);
+
+  return true;
+}
+
+bool
+XmppLoginTask::HandleFeatures(const XmlElement *element) {
+  if (element->Name() != QN_STREAM_FEATURES)
+    return false;
+
+  pelFeatures_.reset(new XmlElement(*element));
+  return true;
+}
+
+const XmlElement *
+XmppLoginTask::GetFeature(const QName & name) {
+  return pelFeatures_->FirstNamed(name);
+}
+
+bool
+XmppLoginTask::Failure(XmppEngine::Error reason) {
+  state_ = LOGINSTATE_DONE;
+  pctx_->SignalError(reason, 0);
+  return false;
+}
+
+void
+XmppLoginTask::OutgoingStanza(const XmlElement * element) {
+  XmlElement * pelCopy = new XmlElement(*element);
+  pvecQueuedStanzas_->push_back(pelCopy);
+}
+
+void
+XmppLoginTask::FlushQueuedStanzas() {
+  for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1) {
+    pctx_->InternalSendStanza((*pvecQueuedStanzas_)[i]);
+    delete (*pvecQueuedStanzas_)[i];
+  }
+  pvecQueuedStanzas_->clear();
+}
+
+}
diff --git a/talk/xmpp/xmpplogintask.h b/talk/xmpp/xmpplogintask.h
new file mode 100644
index 0000000..9b3f5ae
--- /dev/null
+++ b/talk/xmpp/xmpplogintask.h
@@ -0,0 +1,104 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_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"
+#include "talk/xmpp/xmppengine.h"
+
+namespace buzz {
+
+class XmlElement;
+class XmppEngineImpl;
+class SaslMechanism;
+
+
+// TODO: Rename to LoginTask.
+class XmppLoginTask {
+
+public:
+  XmppLoginTask(XmppEngineImpl *pctx);
+  ~XmppLoginTask();
+
+  bool IsDone()
+    { return state_ == LOGINSTATE_DONE; }
+  void IncomingStanza(const XmlElement * element, bool isStart);
+  void OutgoingStanza(const XmlElement *element);
+  void set_allow_non_google_login(bool b)
+    { allowNonGoogleLogin_ = b; }
+
+private:
+  enum LoginTaskState {
+    LOGINSTATE_INIT = 0,
+    LOGINSTATE_STREAMSTART_SENT,
+    LOGINSTATE_STARTED_XMPP,
+    LOGINSTATE_TLS_INIT,
+    LOGINSTATE_AUTH_INIT,
+    LOGINSTATE_BIND_INIT,
+    LOGINSTATE_TLS_REQUESTED,
+    LOGINSTATE_SASL_RUNNING,
+    LOGINSTATE_BIND_REQUESTED,
+    LOGINSTATE_SESSION_REQUESTED,
+    LOGINSTATE_DONE,
+  };
+
+  const XmlElement * NextStanza();
+  bool Advance();
+  bool HandleStartStream(const XmlElement * element);
+  bool HandleFeatures(const XmlElement * element);
+  const XmlElement * GetFeature(const QName & name);
+  bool Failure(XmppEngine::Error reason);
+  void FlushQueuedStanzas();
+
+  XmppEngineImpl * pctx_;
+  bool authNeeded_;
+  bool allowNonGoogleLogin_;
+  LoginTaskState state_;
+  const XmlElement * pelStanza_;
+  bool isStart_;
+  std::string iqId_;
+  talk_base::scoped_ptr<XmlElement> pelFeatures_;
+  Jid fullJid_;
+  std::string streamId_;
+  talk_base::scoped_ptr<std::vector<XmlElement *> > pvecQueuedStanzas_;
+
+  talk_base::scoped_ptr<SaslMechanism> sasl_mech_;
+
+#ifdef _DEBUG
+  static const talk_base::ConstantLabel LOGINTASK_STATES[];
+#endif  // _DEBUG
+};
+
+}
+
+#endif  //  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/xmpppump.cc b/talk/xmpp/xmpppump.cc
new file mode 100644
index 0000000..5732986
--- /dev/null
+++ b/talk/xmpp/xmpppump.cc
@@ -0,0 +1,84 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/xmpppump.h"
+
+#include "talk/xmpp/xmppauth.h"
+
+namespace buzz {
+
+XmppPump::XmppPump(XmppPumpNotify * notify) {
+  state_ = buzz::XmppEngine::STATE_NONE;
+  notify_ = notify;
+  client_ = new buzz::XmppClient(this);  // NOTE: deleted by TaskRunner
+}
+
+void XmppPump::DoLogin(const buzz::XmppClientSettings & xcs,
+                       buzz::AsyncSocket* socket,
+                       buzz::PreXmppAuth* auth) {
+  OnStateChange(buzz::XmppEngine::STATE_START);
+  if (!AllChildrenDone()) {
+    client_->SignalStateChange.connect(this, &XmppPump::OnStateChange);
+    client_->Connect(xcs, "", socket, auth);
+    client_->Start();
+  }
+}
+
+void XmppPump::DoDisconnect() {
+  if (!AllChildrenDone())
+    client_->Disconnect();
+  OnStateChange(buzz::XmppEngine::STATE_CLOSED);
+}
+
+void XmppPump::OnStateChange(buzz::XmppEngine::State state) {
+  if (state_ == state)
+    return;
+  state_ = state;
+  if (notify_ != NULL)
+    notify_->OnStateChange(state);
+}
+
+void XmppPump::WakeTasks() {
+  talk_base::Thread::Current()->Post(this);
+}
+
+int64 XmppPump::CurrentTime() {
+  return (int64)talk_base::Time();
+}
+
+void XmppPump::OnMessage(talk_base::Message *pmsg) {
+  RunTasks();
+}
+
+buzz::XmppReturnStatus XmppPump::SendStanza(const buzz::XmlElement *stanza) {
+  if (!AllChildrenDone())
+    return client_->SendStanza(stanza);
+  return buzz::XMPP_RETURN_BADSTATE;
+}
+
+}  // namespace buzz
+
diff --git a/talk/xmpp/xmpppump.h b/talk/xmpp/xmpppump.h
new file mode 100644
index 0000000..7a374cc
--- /dev/null
+++ b/talk/xmpp/xmpppump.h
@@ -0,0 +1,79 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_XMPP_XMPPPUMP_H_
+#define TALK_XMPP_XMPPPUMP_H_
+
+#include "talk/base/messagequeue.h"
+#include "talk/base/taskrunner.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+
+// Simple xmpp pump
+
+class XmppPumpNotify {
+public:
+  virtual ~XmppPumpNotify() {}
+  virtual void OnStateChange(buzz::XmppEngine::State state) = 0;
+};
+
+class XmppPump : public talk_base::MessageHandler, public talk_base::TaskRunner {
+public:
+  XmppPump(buzz::XmppPumpNotify * notify = NULL);
+
+  buzz::XmppClient *client() { return client_; }
+
+  void DoLogin(const buzz::XmppClientSettings & xcs,
+               buzz::AsyncSocket* socket,
+               buzz::PreXmppAuth* auth);
+  void DoDisconnect();
+
+  void OnStateChange(buzz::XmppEngine::State state);
+
+  void WakeTasks();
+
+  int64 CurrentTime();
+
+  void OnMessage(talk_base::Message *pmsg);
+
+  buzz::XmppReturnStatus SendStanza(const buzz::XmlElement *stanza);
+
+private:
+  buzz::XmppClient *client_;
+  buzz::XmppEngine::State state_;
+  buzz::XmppPumpNotify *notify_;
+};
+
+}  // namespace buzz
+
+#endif // TALK_XMPP_XMPPPUMP_H_
+
diff --git a/talk/xmpp/xmppsocket.cc b/talk/xmpp/xmppsocket.cc
new file mode 100644
index 0000000..31d1b69
--- /dev/null
+++ b/talk/xmpp/xmppsocket.cc
@@ -0,0 +1,262 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "xmppsocket.h"
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include "talk/base/basicdefs.h"
+#include "talk/base/logging.h"
+#include "talk/base/thread.h"
+#ifdef FEATURE_ENABLE_SSL
+#include "talk/base/ssladapter.h"
+#endif
+
+#ifdef USE_SSLSTREAM
+#include "talk/base/socketstream.h"
+#ifdef FEATURE_ENABLE_SSL
+#include "talk/base/sslstreamadapter.h"
+#endif  // FEATURE_ENABLE_SSL
+#endif  // USE_SSLSTREAM
+
+namespace buzz {
+
+XmppSocket::XmppSocket(buzz::TlsOptions tls) : cricket_socket_(NULL),
+                                               tls_(tls) {
+  state_ = buzz::AsyncSocket::STATE_CLOSED;
+}
+
+void XmppSocket::CreateCricketSocket(int family) {
+  talk_base::Thread* pth = talk_base::Thread::Current();
+  if (family == AF_UNSPEC) {
+    family = AF_INET;
+  }
+  talk_base::AsyncSocket* socket =
+      pth->socketserver()->CreateAsyncSocket(family, SOCK_STREAM);
+#ifndef USE_SSLSTREAM
+#ifdef FEATURE_ENABLE_SSL
+  if (tls_ != buzz::TLS_DISABLED) {
+    socket = talk_base::SSLAdapter::Create(socket);
+  }
+#endif  // FEATURE_ENABLE_SSL
+  cricket_socket_ = socket;
+  cricket_socket_->SignalReadEvent.connect(this, &XmppSocket::OnReadEvent);
+  cricket_socket_->SignalWriteEvent.connect(this, &XmppSocket::OnWriteEvent);
+  cricket_socket_->SignalConnectEvent.connect(this,
+                                              &XmppSocket::OnConnectEvent);
+  cricket_socket_->SignalCloseEvent.connect(this, &XmppSocket::OnCloseEvent);
+#else  // USE_SSLSTREAM
+  cricket_socket_ = socket;
+  stream_ = new talk_base::SocketStream(cricket_socket_);
+#ifdef FEATURE_ENABLE_SSL
+  if (tls_ != buzz::TLS_DISABLED)
+    stream_ = talk_base::SSLStreamAdapter::Create(stream_);
+#endif  // FEATURE_ENABLE_SSL
+  stream_->SignalEvent.connect(this, &XmppSocket::OnEvent);
+#endif  // USE_SSLSTREAM
+}
+
+XmppSocket::~XmppSocket() {
+  Close();
+#ifndef USE_SSLSTREAM
+  delete cricket_socket_;
+#else  // USE_SSLSTREAM
+  delete stream_;
+#endif  // USE_SSLSTREAM
+}
+
+#ifndef USE_SSLSTREAM
+void XmppSocket::OnReadEvent(talk_base::AsyncSocket * socket) {
+  SignalRead();
+}
+
+void XmppSocket::OnWriteEvent(talk_base::AsyncSocket * socket) {
+  // Write bytes if there are any
+  while (buffer_.Length() != 0) {
+    int written = cricket_socket_->Send(buffer_.Data(), buffer_.Length());
+    if (written > 0) {
+      buffer_.Consume(written);
+      continue;
+    }
+    if (!cricket_socket_->IsBlocking())
+      LOG(LS_ERROR) << "Send error: " << cricket_socket_->GetError();
+    return;
+  }
+}
+
+void XmppSocket::OnConnectEvent(talk_base::AsyncSocket * socket) {
+#if defined(FEATURE_ENABLE_SSL)
+  if (state_ == buzz::AsyncSocket::STATE_TLS_CONNECTING) {
+    state_ = buzz::AsyncSocket::STATE_TLS_OPEN;
+    SignalSSLConnected();
+    OnWriteEvent(cricket_socket_);
+    return;
+  }
+#endif  // !defined(FEATURE_ENABLE_SSL)
+  state_ = buzz::AsyncSocket::STATE_OPEN;
+  SignalConnected();
+}
+
+void XmppSocket::OnCloseEvent(talk_base::AsyncSocket * socket, int error) {
+  SignalCloseEvent(error);
+}
+
+#else  // USE_SSLSTREAM
+
+void XmppSocket::OnEvent(talk_base::StreamInterface* stream,
+                         int events, int err) {
+  if ((events & talk_base::SE_OPEN)) {
+#if defined(FEATURE_ENABLE_SSL)
+    if (state_ == buzz::AsyncSocket::STATE_TLS_CONNECTING) {
+      state_ = buzz::AsyncSocket::STATE_TLS_OPEN;
+      SignalSSLConnected();
+      events |= talk_base::SE_WRITE;
+    } else
+#endif
+    {
+      state_ = buzz::AsyncSocket::STATE_OPEN;
+      SignalConnected();
+    }
+  }
+  if ((events & talk_base::SE_READ))
+    SignalRead();
+  if ((events & talk_base::SE_WRITE)) {
+    // Write bytes if there are any
+    while (buffer_.Length() != 0) {
+      talk_base::StreamResult result;
+      size_t written;
+      int error;
+      result = stream_->Write(buffer_.Data(), buffer_.Length(),
+                              &written, &error);
+      if (result == talk_base::SR_ERROR) {
+        LOG(LS_ERROR) << "Send error: " << error;
+        return;
+      }
+      if (result == talk_base::SR_BLOCK)
+        return;
+      ASSERT(result == talk_base::SR_SUCCESS);
+      ASSERT(written > 0);
+      buffer_.Shift(written);
+    }
+  }
+  if ((events & talk_base::SE_CLOSE))
+    SignalCloseEvent(err);
+}
+#endif  // USE_SSLSTREAM
+
+buzz::AsyncSocket::State XmppSocket::state() {
+  return state_;
+}
+
+buzz::AsyncSocket::Error XmppSocket::error() {
+  return buzz::AsyncSocket::ERROR_NONE;
+}
+
+int XmppSocket::GetError() {
+  return 0;
+}
+
+bool XmppSocket::Connect(const talk_base::SocketAddress& addr) {
+  if (cricket_socket_ == NULL) {
+    CreateCricketSocket(addr.family());
+  }
+  if (cricket_socket_->Connect(addr) < 0) {
+    return cricket_socket_->IsBlocking();
+  }
+  return true;
+}
+
+bool XmppSocket::Read(char * data, size_t len, size_t* len_read) {
+#ifndef USE_SSLSTREAM
+  int read = cricket_socket_->Recv(data, len);
+  if (read > 0) {
+    *len_read = (size_t)read;
+    return true;
+  }
+#else  // USE_SSLSTREAM
+  talk_base::StreamResult result = stream_->Read(data, len, len_read, NULL);
+  if (result == talk_base::SR_SUCCESS)
+    return true;
+#endif  // USE_SSLSTREAM
+  return false;
+}
+
+bool XmppSocket::Write(const char * data, size_t len) {
+  buffer_.WriteBytes(data, len);
+#ifndef USE_SSLSTREAM
+  OnWriteEvent(cricket_socket_);
+#else  // USE_SSLSTREAM
+  OnEvent(stream_, talk_base::SE_WRITE, 0);
+#endif  // USE_SSLSTREAM
+  return true;
+}
+
+bool XmppSocket::Close() {
+  if (state_ != buzz::AsyncSocket::STATE_OPEN)
+    return false;
+#ifndef USE_SSLSTREAM
+  if (cricket_socket_->Close() == 0) {
+    state_ = buzz::AsyncSocket::STATE_CLOSED;
+    SignalClosed();
+    return true;
+  }
+  return false;
+#else  // USE_SSLSTREAM
+  state_ = buzz::AsyncSocket::STATE_CLOSED;
+  stream_->Close();
+  SignalClosed();
+  return true;
+#endif  // USE_SSLSTREAM
+}
+
+bool XmppSocket::StartTls(const std::string & domainname) {
+#if defined(FEATURE_ENABLE_SSL)
+  if (tls_ == buzz::TLS_DISABLED)
+    return false;
+#ifndef USE_SSLSTREAM
+  talk_base::SSLAdapter* ssl_adapter =
+    static_cast<talk_base::SSLAdapter *>(cricket_socket_);
+  if (ssl_adapter->StartSSL(domainname.c_str(), false) != 0)
+    return false;
+#else  // USE_SSLSTREAM
+  talk_base::SSLStreamAdapter* ssl_stream =
+    static_cast<talk_base::SSLStreamAdapter *>(stream_);
+  if (ssl_stream->StartSSLWithServer(domainname.c_str()) != 0)
+    return false;
+#endif  // USE_SSLSTREAM
+  state_ = buzz::AsyncSocket::STATE_TLS_CONNECTING;
+  return true;
+#else  // !defined(FEATURE_ENABLE_SSL)
+  return false;
+#endif  // !defined(FEATURE_ENABLE_SSL)
+}
+
+}  // namespace buzz
+
diff --git a/talk/xmpp/xmppsocket.h b/talk/xmpp/xmppsocket.h
new file mode 100644
index 0000000..f89333f
--- /dev/null
+++ b/talk/xmpp/xmppsocket.h
@@ -0,0 +1,89 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_XMPPSOCKET_H_
+#define TALK_XMPP_XMPPSOCKET_H_
+
+#include "talk/base/asyncsocket.h"
+#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.
+// #define USE_SSLSTREAM 
+
+namespace talk_base {
+  class StreamInterface;
+  class SocketAddress;
+};
+extern talk_base::AsyncSocket* cricket_socket_;
+
+namespace buzz {
+
+class XmppSocket : public buzz::AsyncSocket, public sigslot::has_slots<> {
+public:
+  XmppSocket(buzz::TlsOptions tls);
+  ~XmppSocket();
+
+  virtual buzz::AsyncSocket::State state();
+  virtual buzz::AsyncSocket::Error error();
+  virtual int GetError();
+
+  virtual bool Connect(const talk_base::SocketAddress& addr);
+  virtual bool Read(char * data, size_t len, size_t* len_read);
+  virtual bool Write(const char * data, size_t len);
+  virtual bool Close();
+  virtual bool StartTls(const std::string & domainname);
+
+  sigslot::signal1<int> SignalCloseEvent;
+
+private:
+  void CreateCricketSocket(int family);
+#ifndef USE_SSLSTREAM
+  void OnReadEvent(talk_base::AsyncSocket * socket);
+  void OnWriteEvent(talk_base::AsyncSocket * socket);
+  void OnConnectEvent(talk_base::AsyncSocket * socket);
+  void OnCloseEvent(talk_base::AsyncSocket * socket, int error);
+#else  // USE_SSLSTREAM
+  void OnEvent(talk_base::StreamInterface* stream, int events, int err);
+#endif  // USE_SSLSTREAM
+
+  talk_base::AsyncSocket * cricket_socket_;
+#ifdef USE_SSLSTREAM
+  talk_base::StreamInterface *stream_;
+#endif  // USE_SSLSTREAM
+  buzz::AsyncSocket::State state_;
+  talk_base::ByteBuffer buffer_;
+  buzz::TlsOptions tls_;
+};
+
+}  // namespace buzz
+
+#endif // TALK_XMPP_XMPPSOCKET_H_
+
diff --git a/talk/xmpp/xmppstanzaparser.cc b/talk/xmpp/xmppstanzaparser.cc
new file mode 100644
index 0000000..6c3ef5f
--- /dev/null
+++ b/talk/xmpp/xmppstanzaparser.cc
@@ -0,0 +1,106 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/xmppstanzaparser.h"
+
+#include "talk/xmllite/xmlelement.h"
+#include "talk/base/common.h"
+#include "talk/xmpp/constants.h"
+#ifdef EXPAT_RELATIVE_PATH
+#include "expat.h"
+#else
+#include "third_party/expat/v2_0_1/Source/lib/expat.h"
+#endif
+
+namespace buzz {
+
+XmppStanzaParser::XmppStanzaParser(XmppStanzaParseHandler *psph) :
+  psph_(psph),
+  innerHandler_(this),
+  parser_(&innerHandler_),
+  depth_(0),
+  builder_() {
+}
+
+void
+XmppStanzaParser::Reset() {
+  parser_.Reset();
+  depth_ = 0;
+  builder_.Reset();
+}
+
+void
+XmppStanzaParser::IncomingStartElement(
+    XmlParseContext * pctx, const char * name, const char ** atts) {
+  if (depth_++ == 0) {
+    XmlElement * pelStream = XmlBuilder::BuildElement(pctx, name, atts);
+    if (pelStream == NULL) {
+      pctx->RaiseError(XML_ERROR_SYNTAX);
+      return;
+    }
+    psph_->StartStream(pelStream);
+    delete pelStream;
+    return;
+  }
+
+  builder_.StartElement(pctx, name, atts);
+}
+
+void
+XmppStanzaParser::IncomingCharacterData(
+    XmlParseContext * pctx, const char * text, int len) {
+  if (depth_ > 1) {
+    builder_.CharacterData(pctx, text, len);
+  }
+}
+
+void
+XmppStanzaParser::IncomingEndElement(
+    XmlParseContext * pctx, const char * name) {
+  if (--depth_ == 0) {
+    psph_->EndStream();
+    return;
+  }
+
+  builder_.EndElement(pctx, name);
+
+  if (depth_ == 1) {
+    XmlElement *element = builder_.CreateElement();
+    psph_->Stanza(element);
+    delete element;
+  }
+}
+
+void
+XmppStanzaParser::IncomingError(
+    XmlParseContext * pctx, XML_Error errCode) {
+  UNUSED(pctx);
+  UNUSED(errCode);
+  psph_->XmlError();
+}
+
+}
diff --git a/talk/xmpp/xmppstanzaparser.h b/talk/xmpp/xmppstanzaparser.h
new file mode 100644
index 0000000..c6f8b08
--- /dev/null
+++ b/talk/xmpp/xmppstanzaparser.h
@@ -0,0 +1,97 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _xmppstanzaparser_h_
+#define _xmppstanzaparser_h_
+
+#include "talk/xmllite/xmlparser.h"
+#include "talk/xmllite/xmlbuilder.h"
+
+
+namespace buzz {
+
+class XmlElement;
+
+class XmppStanzaParseHandler {
+public:
+  virtual ~XmppStanzaParseHandler() {}
+  virtual void StartStream(const XmlElement * pelStream) = 0;
+  virtual void Stanza(const XmlElement * pelStanza) = 0;
+  virtual void EndStream() = 0;
+  virtual void XmlError() = 0;
+};
+
+class XmppStanzaParser {
+public:
+  XmppStanzaParser(XmppStanzaParseHandler *psph);
+  bool Parse(const char * data, size_t len, bool isFinal)
+    { return parser_.Parse(data, len, isFinal); }
+  void Reset();
+
+private:
+  class ParseHandler : public XmlParseHandler {
+  public:
+    ParseHandler(XmppStanzaParser * outer) : outer_(outer) {}
+    virtual void StartElement(XmlParseContext * pctx,
+               const char * name, const char ** atts)
+      { outer_->IncomingStartElement(pctx, name, atts); }
+    virtual void EndElement(XmlParseContext * pctx,
+               const char * name)
+      { outer_->IncomingEndElement(pctx, name); }
+    virtual void CharacterData(XmlParseContext * pctx,
+               const char * text, int len)
+      { outer_->IncomingCharacterData(pctx, text, len); }
+    virtual void Error(XmlParseContext * pctx,
+               XML_Error errCode)
+      { outer_->IncomingError(pctx, errCode); }
+  private:
+    XmppStanzaParser * const outer_;
+  };
+
+  friend class ParseHandler;
+
+  void IncomingStartElement(XmlParseContext * pctx,
+               const char * name, const char ** atts);
+  void IncomingEndElement(XmlParseContext * pctx,
+               const char * name);
+  void IncomingCharacterData(XmlParseContext * pctx,
+               const char * text, int len);
+  void IncomingError(XmlParseContext * pctx,
+               XML_Error errCode);
+
+  XmppStanzaParseHandler * psph_;
+  ParseHandler innerHandler_;
+  XmlParser parser_;
+  int depth_;
+  XmlBuilder builder_;
+
+ };
+
+
+}
+
+#endif
diff --git a/talk/xmpp/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
new file mode 100644
index 0000000..046f7a1
--- /dev/null
+++ b/talk/xmpp/xmpptask.cc
@@ -0,0 +1,175 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/xmpp/xmpptask.h"
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/constants.h"
+
+namespace buzz {
+
+XmppClientInterface::XmppClientInterface() {
+}
+
+XmppClientInterface::~XmppClientInterface() {
+}
+
+XmppTask::XmppTask(XmppTaskParentInterface* parent,
+                   XmppEngine::HandlerLevel level)
+    : XmppTaskBase(parent), stopped_(false) {
+#ifdef _DEBUG
+  debug_force_timeout_ = false;
+#endif
+
+  id_ = GetClient()->NextId();
+  GetClient()->AddXmppTask(this, level);
+  GetClient()->SignalDisconnected.connect(this, &XmppTask::OnDisconnect);
+}
+
+XmppTask::~XmppTask() {
+  StopImpl();
+}
+
+void XmppTask::StopImpl() {
+  while (NextStanza() != NULL) {}
+  if (!stopped_) {
+    GetClient()->RemoveXmppTask(this);
+    GetClient()->SignalDisconnected.disconnect(this);
+    stopped_ = true;
+  }
+}
+
+XmppReturnStatus XmppTask::SendStanza(const XmlElement* stanza) {
+  if (stopped_)
+    return XMPP_RETURN_BADSTATE;
+  return GetClient()->SendStanza(stanza);
+}
+
+XmppReturnStatus XmppTask::SendStanzaError(const XmlElement* element_original,
+                                           XmppStanzaError code,
+                                           const std::string& text) {
+  if (stopped_)
+    return XMPP_RETURN_BADSTATE;
+  return GetClient()->SendStanzaError(element_original, code, text);
+}
+
+void XmppTask::Stop() {
+  StopImpl();
+  Task::Stop();
+}
+
+void XmppTask::OnDisconnect() {
+  Error();
+}
+
+void XmppTask::QueueStanza(const XmlElement* stanza) {
+#ifdef _DEBUG
+  if (debug_force_timeout_)
+    return;
+#endif
+
+  stanza_queue_.push_back(new XmlElement(*stanza));
+  Wake();
+}
+
+const XmlElement* XmppTask::NextStanza() {
+  XmlElement* result = NULL;
+  if (!stanza_queue_.empty()) {
+    result = stanza_queue_.front();
+    stanza_queue_.pop_front();
+  }
+  next_stanza_.reset(result);
+  return result;
+}
+
+XmlElement* XmppTask::MakeIq(const std::string& type,
+                             const buzz::Jid& to,
+                             const std::string& id) {
+  XmlElement* result = new XmlElement(QN_IQ);
+  if (!type.empty())
+    result->AddAttr(QN_TYPE, type);
+  if (!to.IsEmpty())
+    result->AddAttr(QN_TO, to.Str());
+  if (!id.empty())
+    result->AddAttr(QN_ID, id);
+  return result;
+}
+
+XmlElement* XmppTask::MakeIqResult(const XmlElement * query) {
+  XmlElement* result = new XmlElement(QN_IQ);
+  result->AddAttr(QN_TYPE, STR_RESULT);
+  if (query->HasAttr(QN_FROM)) {
+    result->AddAttr(QN_TO, query->Attr(QN_FROM));
+  }
+  result->AddAttr(QN_ID, query->Attr(QN_ID));
+  return result;
+}
+
+bool XmppTask::MatchResponseIq(const XmlElement* stanza,
+                               const Jid& to,
+                               const std::string& id) {
+  if (stanza->Name() != QN_IQ)
+    return false;
+
+  if (stanza->Attr(QN_ID) != id)
+    return false;
+
+  return MatchStanzaFrom(stanza, to);
+}
+
+bool XmppTask::MatchStanzaFrom(const XmlElement* stanza,
+                               const Jid& to) {
+  Jid from(stanza->Attr(QN_FROM));
+  if (from == to)
+    return true;
+
+  // We address the server as "", check if we are doing so here.
+  if (!to.IsEmpty())
+    return false;
+
+  // It is legal for the server to identify itself with "domain" or
+  // "myself@domain"
+  Jid me = GetClient()->jid();
+  return (from == Jid(me.domain())) || (from == me.BareJid());
+}
+
+bool XmppTask::MatchRequestIq(const XmlElement* stanza,
+                              const std::string& type,
+                              const QName& qn) {
+  if (stanza->Name() != QN_IQ)
+    return false;
+
+  if (stanza->Attr(QN_TYPE) != type)
+    return false;
+
+  if (stanza->FirstNamed(qn) == NULL)
+    return false;
+
+  return true;
+}
+
+}
diff --git a/talk/xmpp/xmpptask.h b/talk/xmpp/xmpptask.h
new file mode 100644
index 0000000..6a88f98
--- /dev/null
+++ b/talk/xmpp/xmpptask.h
@@ -0,0 +1,189 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_XMPP_XMPPTASK_H_
+#define TALK_XMPP_XMPPTASK_H_
+
+#include <string>
+#include <deque>
+#include "talk/base/sigslot.h"
+#include "talk/base/task.h"
+#include "talk/base/taskparent.h"
+#include "talk/xmpp/xmppengine.h"
+
+namespace buzz {
+
+/////////////////////////////////////////////////////////////////////
+//
+// XMPPTASK
+//
+/////////////////////////////////////////////////////////////////////
+//
+// See Task and XmppClient first.
+//
+// XmppTask is a task that is designed to go underneath XmppClient and be
+// useful there.  It has a way of finding its XmppClient parent so you
+// can have it nested arbitrarily deep under an XmppClient and it can
+// still find the XMPP services.
+//
+// Tasks register themselves to listen to particular kinds of stanzas
+// that are sent out by the client.  Rather than processing stanzas
+// right away, they should decide if they own the sent stanza,
+// and if so, queue it and Wake() the task, or if a stanza does not belong
+// to you, return false right away so the next XmppTask can take a crack.
+// This technique (synchronous recognize, but asynchronous processing)
+// allows you to have arbitrary logic for recognizing stanzas yet still,
+// for example, disconnect a client while processing a stanza -
+// without reentrancy problems.
+//
+/////////////////////////////////////////////////////////////////////
+
+class XmppTask;
+
+// XmppClientInterface is an abstract interface for sending and
+// handling stanzas.  It can be implemented for unit tests or
+// different network environments.  It will usually be implemented by
+// XmppClient.
+class XmppClientInterface {
+ public:
+  XmppClientInterface();
+  virtual ~XmppClientInterface();
+
+  virtual XmppEngine::State GetState() const = 0;
+  virtual const Jid& jid() const = 0;
+  virtual std::string NextId() = 0;
+  virtual XmppReturnStatus SendStanza(const XmlElement* stanza) = 0;
+  virtual XmppReturnStatus SendStanzaError(const XmlElement* original_stanza,
+                                           XmppStanzaError error_code,
+                                           const std::string& message) = 0;
+  virtual void AddXmppTask(XmppTask* task, XmppEngine::HandlerLevel level) = 0;
+  virtual void RemoveXmppTask(XmppTask* task) = 0;
+  sigslot::signal0<> SignalDisconnected;
+
+  DISALLOW_EVIL_CONSTRUCTORS(XmppClientInterface);
+};
+
+// XmppTaskParentInterface is the interface require for any parent of
+// an XmppTask.  It needs, for example, a way to get an
+// XmppClientInterface.
+
+// We really ought to inherit from a TaskParentInterface, but we tried
+// that and it's way too complicated to change
+// Task/TaskParent/TaskRunner.  For now, this works.
+class XmppTaskParentInterface : public talk_base::Task {
+ public:
+  explicit XmppTaskParentInterface(talk_base::TaskParent* parent)
+      : Task(parent) {
+  }
+  virtual ~XmppTaskParentInterface() {}
+
+  virtual XmppClientInterface* GetClient() = 0;
+
+  DISALLOW_EVIL_CONSTRUCTORS(XmppTaskParentInterface);
+};
+
+class XmppTaskBase : public XmppTaskParentInterface {
+ public:
+  explicit XmppTaskBase(XmppTaskParentInterface* parent)
+      : XmppTaskParentInterface(parent),
+        parent_(parent) {
+  }
+  virtual ~XmppTaskBase() {}
+
+  virtual XmppClientInterface* GetClient() {
+    return parent_->GetClient();
+  }
+
+ protected:
+  XmppTaskParentInterface* parent_;
+
+  DISALLOW_EVIL_CONSTRUCTORS(XmppTaskBase);
+};
+
+class XmppTask : public XmppTaskBase,
+                 public XmppStanzaHandler,
+                 public sigslot::has_slots<>
+{
+ public:
+  XmppTask(XmppTaskParentInterface* parent,
+           XmppEngine::HandlerLevel level = XmppEngine::HL_NONE);
+  virtual ~XmppTask();
+
+  std::string task_id() const { return id_; }
+  void set_task_id(std::string id) { id_ = id; }
+
+#ifdef _DEBUG
+  void set_debug_force_timeout(const bool f) { debug_force_timeout_ = f; }
+#endif
+
+  virtual bool HandleStanza(const XmlElement* stanza) { return false; }
+
+ protected:
+  XmppReturnStatus SendStanza(const XmlElement* stanza);
+  XmppReturnStatus SetResult(const std::string& code);
+  XmppReturnStatus SendStanzaError(const XmlElement* element_original,
+                                   XmppStanzaError code,
+                                   const std::string& text);
+
+  virtual void Stop();
+  virtual void OnDisconnect();
+
+  virtual void QueueStanza(const XmlElement* stanza);
+  const XmlElement* NextStanza();
+
+  bool MatchStanzaFrom(const XmlElement* stanza, const Jid& match_jid);
+
+  bool MatchResponseIq(const XmlElement* stanza, const Jid& to,
+                       const std::string& task_id);
+
+  static bool MatchRequestIq(const XmlElement* stanza, const std::string& type,
+                             const QName& qn);
+  static XmlElement *MakeIqResult(const XmlElement* query);
+  static XmlElement *MakeIq(const std::string& type,
+                            const Jid& to, const std::string& task_id);
+
+  // Returns true if the task is under the specified rate limit and updates the
+  // rate limit accordingly
+  bool VerifyTaskRateLimit(const std::string task_name, int max_count,
+                           int per_x_seconds);
+
+private:
+  void StopImpl();
+
+  bool stopped_;
+  std::deque<XmlElement*> stanza_queue_;
+  talk_base::scoped_ptr<XmlElement> next_stanza_;
+  std::string id_;
+
+#ifdef _DEBUG
+  bool debug_force_timeout_;
+#endif
+};
+
+}  // namespace buzz
+
+#endif // TALK_XMPP_XMPPTASK_H_
diff --git a/talk/xmpp/xmppthread.cc b/talk/xmpp/xmppthread.cc
new file mode 100644
index 0000000..43dded8
--- /dev/null
+++ b/talk/xmpp/xmppthread.cc
@@ -0,0 +1,85 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/xmpp/xmppthread.h"
+
+#include "talk/xmpp/xmppauth.h"
+#include "talk/xmpp/xmppclientsettings.h"
+
+namespace buzz {
+namespace {
+
+const uint32 MSG_LOGIN = 1;
+const uint32 MSG_DISCONNECT = 2;
+
+struct LoginData: public talk_base::MessageData {
+  LoginData(const buzz::XmppClientSettings& s) : xcs(s) {}
+  virtual ~LoginData() {}
+
+  buzz::XmppClientSettings xcs;
+};
+
+} // namespace
+
+XmppThread::XmppThread() {
+  pump_ = new buzz::XmppPump(this);
+}
+
+XmppThread::~XmppThread() {
+  delete pump_;
+}
+
+void XmppThread::ProcessMessages(int cms) {
+  talk_base::Thread::ProcessMessages(cms);
+}
+
+void XmppThread::Login(const buzz::XmppClientSettings& xcs) {
+  Post(this, MSG_LOGIN, new LoginData(xcs));
+}
+
+void XmppThread::Disconnect() {
+  Post(this, MSG_DISCONNECT);
+}
+
+void XmppThread::OnStateChange(buzz::XmppEngine::State state) {
+}
+
+void XmppThread::OnMessage(talk_base::Message* pmsg) {
+  if (pmsg->message_id == MSG_LOGIN) {
+    ASSERT(pmsg->pdata != NULL);
+    LoginData* data = reinterpret_cast<LoginData*>(pmsg->pdata);
+    pump_->DoLogin(data->xcs, new XmppSocket(buzz::TLS_DISABLED),
+        new XmppAuth());
+    delete data;
+  } else if (pmsg->message_id == MSG_DISCONNECT) {
+    pump_->DoDisconnect();
+  } else {
+    ASSERT(false);
+  }
+}
+
+}  // namespace buzz
diff --git a/talk/xmpp/xmppthread.h b/talk/xmpp/xmppthread.h
new file mode 100644
index 0000000..62a5ce6
--- /dev/null
+++ b/talk/xmpp/xmppthread.h
@@ -0,0 +1,62 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice, 
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products 
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_XMPP_XMPPTHREAD_H_
+#define TALK_XMPP_XMPPTHREAD_H_
+
+#include "talk/base/thread.h"
+#include "talk/xmpp/xmppclientsettings.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpppump.h"
+#include "talk/xmpp/xmppsocket.h"
+
+namespace buzz {
+
+class XmppThread:
+    public talk_base::Thread, buzz::XmppPumpNotify, talk_base::MessageHandler {
+public:
+  XmppThread();
+  ~XmppThread();
+
+  buzz::XmppClient* client() { return pump_->client(); }
+
+  void ProcessMessages(int cms);
+
+  void Login(const buzz::XmppClientSettings & xcs);
+  void Disconnect();
+
+private:
+  buzz::XmppPump* pump_;
+
+  void OnStateChange(buzz::XmppEngine::State state);
+  void OnMessage(talk_base::Message* pmsg);
+};
+
+}  // namespace buzz
+
+#endif  // TALK_XMPP_XMPPTHREAD_H_
+