Update libjingle to 0.6.12.
  - PeerConnection client for windows.
  - Bug fixes.
Review URL: https://webrtc-codereview.appspot.com/390002

git-svn-id: http://libjingle.googlecode.com/svn/trunk@115 dd674b97-3498-5ee5-1854-bdd07cd0ff33
diff --git a/CHANGELOG b/CHANGELOG
index 1d52975..ecc343f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,9 @@
 Libjingle
 
+0.6.12 - Feb 07, 2012
+  - PeerConnection client for windows.
+  - Bug fixes.
+
 0.6.11 - Jan 24, 2012
   - Improved ipv6 support.
   - Initial DTLS support.
diff --git a/talk/app/webrtc/peerconnectionfactory_unittest.cc b/talk/app/webrtc/peerconnectionfactory_unittest.cc
index 7a3b15f..5056e17 100644
--- a/talk/app/webrtc/peerconnectionfactory_unittest.cc
+++ b/talk/app/webrtc/peerconnectionfactory_unittest.cc
@@ -49,18 +49,15 @@
 };
 
 TEST(PeerConnectionFactory, CreatePCUsingInternalModules) {
-  NullPeerConnectionObserver observer;
   talk_base::scoped_refptr<PeerConnectionFactoryInterface> factory(
       CreatePeerConnectionFactory());
   ASSERT_TRUE(factory.get() != NULL);
-  talk_base::scoped_refptr<PeerConnectionInterface> pc1(
-      factory->CreatePeerConnection("", &observer));
-  EXPECT_TRUE(pc1.get() == NULL);
 
-  talk_base::scoped_refptr<PeerConnectionInterface> pc2(
+  NullPeerConnectionObserver observer;
+  talk_base::scoped_refptr<PeerConnectionInterface> pc(
       factory->CreatePeerConnection(kStunConfiguration, &observer));
 
-  EXPECT_TRUE(pc2.get() != NULL);
+  EXPECT_TRUE(pc.get() != NULL);
 }
 
 TEST(PeerConnectionFactory, CreatePCUsingExternalModules) {
@@ -75,14 +72,9 @@
   ASSERT_TRUE(factory.get() != NULL);
 
   NullPeerConnectionObserver observer;
-  talk_base::scoped_refptr<webrtc::PeerConnectionInterface> pc1(
-      factory->CreatePeerConnection("", &observer));
-
-  EXPECT_TRUE(pc1.get() == NULL);
-
-  talk_base::scoped_refptr<PeerConnectionInterface> pc2(
+  talk_base::scoped_refptr<PeerConnectionInterface> pc(
       factory->CreatePeerConnection(kStunConfiguration, &observer));
-  EXPECT_TRUE(pc2.get() != NULL);
+  EXPECT_TRUE(pc.get() != NULL);
 }
 
 }  // namespace webrtc
diff --git a/talk/app/webrtc/peerconnectionimpl.cc b/talk/app/webrtc/peerconnectionimpl.cc
index fdc2659..7f990f1 100644
--- a/talk/app/webrtc/peerconnectionimpl.cc
+++ b/talk/app/webrtc/peerconnectionimpl.cc
@@ -124,7 +124,7 @@
     case STUNS:
     case INVALID:
     default:
-      ASSERT(!"Configuration not supported");
+      LOG(WARNING) << "Configuration not supported";
       return false;
   }
   return true;
@@ -199,8 +199,7 @@
   std::vector<PortAllocatorFactoryInterface::StunConfiguration> stun_config;
   std::vector<PortAllocatorFactoryInterface::TurnConfiguration> turn_config;
 
-  if (!ParseConfigString(configuration, &stun_config, &turn_config))
-    return false;
+  ParseConfigString(configuration, &stun_config, &turn_config);
 
   port_allocator_.reset(factory_->port_allocator_factory()->CreatePortAllocator(
       stun_config, turn_config));
diff --git a/talk/app/webrtc/peerconnectionimpl_unittest.cc b/talk/app/webrtc/peerconnectionimpl_unittest.cc
index 0dae92c..5363af6 100644
--- a/talk/app/webrtc/peerconnectionimpl_unittest.cc
+++ b/talk/app/webrtc/peerconnectionimpl_unittest.cc
@@ -39,6 +39,7 @@
 static const char kStreamLabel1[] = "local_stream_1";
 static const char kStreamLabel2[] = "local_stream_2";
 static const char kStunConfiguration[] = "STUN stun.l.google.com:19302";
+static const char kInvalidConfiguration[] = "a13151913541234:19302";
 static const uint32 kTimeout = 5000U;
 
 using talk_base::scoped_ptr;
@@ -159,12 +160,22 @@
         talk_base::Thread::Current(), talk_base::Thread::Current(),
         port_allocator_factory_.get(), NULL);
     ASSERT_TRUE(pc_factory_.get() != NULL);
+  }
+
+  void CreatePeerConnection() {
     pc_ = pc_factory_->CreatePeerConnection(kStunConfiguration, &observer_);
     ASSERT_TRUE(pc_.get() != NULL);
     observer_.SetPeerConnectionInterface(pc_.get());
     EXPECT_EQ(PeerConnectionInterface::kNegotiating, observer_.state_);
   }
 
+  void CreatePeerConnectionWithInvalidConfiguration() {
+    pc_ = pc_factory_->CreatePeerConnection(kInvalidConfiguration, &observer_);
+    ASSERT_TRUE(pc_.get() != NULL);
+    observer_.SetPeerConnectionInterface(pc_.get());
+    EXPECT_EQ(PeerConnectionInterface::kNegotiating, observer_.state_);
+  }
+
   void AddStream(const std::string& label) {
     // Create a local stream.
     scoped_refptr<LocalMediaStreamInterface> stream(
@@ -188,7 +199,13 @@
   MockPeerConnectionObserver observer_;
 };
 
+TEST_F(PeerConnectionImplTest, CreatePeerConnectionWithInvalidConfiguration) {
+  CreatePeerConnectionWithInvalidConfiguration();
+  AddStream(kStreamLabel1);
+}
+
 TEST_F(PeerConnectionImplTest, AddStream) {
+  CreatePeerConnection();
   AddStream(kStreamLabel1);
   ASSERT_EQ(1u, pc_->local_streams()->count());
   EXPECT_EQ(kStreamLabel1, pc_->local_streams()->at(0)->label());
@@ -206,7 +223,8 @@
   EXPECT_EQ(kStreamLabel1, pc_->remote_streams()->at(0)->label());
 }
 
-TEST_F(PeerConnectionImplTest, UpdateStream) {
+TEST_F(PeerConnectionImplTest, DISABLED_UpdateStream) {
+  CreatePeerConnection();
   AddStream(kStreamLabel1);
   WAIT(PeerConnectionInterface::kNegotiating == observer_.state_, kTimeout);
   pc_->ProcessSignalingMessage(CreateAnswerMessage(observer_.last_message_));
@@ -241,6 +259,7 @@
 }
 
 TEST_F(PeerConnectionImplTest, SendClose) {
+  CreatePeerConnection();
   pc_->Close();
   EXPECT_EQ(RoapMessageBase::kShutdown, observer_.last_message_.type());
   EXPECT_EQ(PeerConnectionInterface::kClosing, observer_.state_);
@@ -249,6 +268,7 @@
 }
 
 TEST_F(PeerConnectionImplTest, ReceiveClose) {
+  CreatePeerConnection();
   pc_->ProcessSignalingMessage(CreateShutdownMessage());
   EXPECT_EQ_WAIT(RoapMessageBase::kOk, observer_.last_message_.type(),
                  kTimeout);
@@ -256,6 +276,7 @@
 }
 
 TEST_F(PeerConnectionImplTest, ReceiveCloseWhileExpectingAnswer) {
+  CreatePeerConnection();
   AddStream(kStreamLabel1);
 
   // Receive the shutdown message.
diff --git a/talk/app/webrtc/webrtcsdp.cc b/talk/app/webrtc/webrtcsdp.cc
index 6986a8b..bdf4d00 100644
--- a/talk/app/webrtc/webrtcsdp.cc
+++ b/talk/app/webrtc/webrtcsdp.cc
@@ -118,7 +118,6 @@
 static const int kDefaultVideoPreference = 0;
 
 static void BuildMediaDescription(const cricket::ContentInfo& content_info,
-                                  const std::vector<Candidate>& candidates,
                                   const MediaType media_type,
                                   std::string* message);
 static void BuildRtpMap(const MediaContentDescription* media_desc,
@@ -131,13 +130,13 @@
 static bool ParseSessionDescription(const std::string& message, size_t* pos);
 static bool ParseTimeDescription(const std::string& message, size_t* pos);
 static bool ParseMediaDescription(const std::string& message, size_t* pos,
-                                  cricket::SessionDescription* desc,
-                                  std::vector<Candidate>* candidates);
+                                  cricket::SessionDescription* desc);
 static bool ParseContent(const std::string& message,
                          const MediaType media_type,
                          size_t* pos,
-                         ContentDescription* content,
-                         std::vector<Candidate>* candidates);
+                         ContentDescription* content);
+static bool ParseCandidates(const std::string& message,
+                            std::vector<Candidate>* candidates);
 
 // Helper functions
 #define LOG_PREFIX_PARSING_ERROR(line_prefix) LOG(LS_ERROR) \
@@ -202,6 +201,12 @@
 
 std::string SdpSerialize(const cricket::SessionDescription& desc,
                          const std::vector<Candidate>& candidates) {
+  return SdpFormat(SdpSerializeSessionDescription(desc),
+                   SdpSerializeCandidates(candidates));
+}
+
+std::string SdpSerializeSessionDescription(
+    const cricket::SessionDescription& desc) {
   std::string message;
 
   // Session Description.
@@ -221,21 +226,61 @@
 
   // Media Description
   if (audio_content) {
-    BuildMediaDescription(*audio_content, candidates,
-        cricket::MEDIA_TYPE_AUDIO, &message);
+    BuildMediaDescription(*audio_content, cricket::MEDIA_TYPE_AUDIO, &message);
   }
 
   if (video_content) {
-    BuildMediaDescription(*video_content, candidates,
-        cricket::MEDIA_TYPE_VIDEO, &message);
+    BuildMediaDescription(*video_content, cricket::MEDIA_TYPE_VIDEO, &message);
   }
 
   return message;
 }
 
+std::string SdpSerializeCandidates(const std::vector<Candidate>& candidates) {
+  std::string message;
+  // rfc5245
+  // a=candidate:<foundation> <component-id> <transport> <priority>
+  // <connection-address> <port> typ <candidate-types>
+  // [raddr <connection-address>] [rport <port>]
+  BuildCandidate(candidates, cricket::MEDIA_TYPE_AUDIO, &message);
+  BuildCandidate(candidates, cricket::MEDIA_TYPE_VIDEO, &message);
+  return message;
+}
+
+std::string SdpFormat(const std::string& desc, const std::string& candidates) {
+  std::string sdp;  // New sdp message.
+
+  std::vector<Candidate> candidates_vector;
+  if (!ParseCandidates(candidates, &candidates_vector))
+    return sdp;
+
+  size_t pos = 0;
+  std::string line;
+  while (GetLine(desc, &pos, &line)) {
+    AddLine(line, &sdp);  // Copy old line to new sdp.
+    if (!HasPrefix(line, kLinePrefixMedia)) {
+      continue;  // Loop until the next m line.
+    }
+    if (HasAttribute(line, kMediaTypeVideo)) {
+      BuildCandidate(candidates_vector, cricket::MEDIA_TYPE_VIDEO, &sdp);
+    } else if (HasAttribute(line, kMediaTypeAudio)) {
+      BuildCandidate(candidates_vector, cricket::MEDIA_TYPE_AUDIO, &sdp);
+    }
+  }
+
+  return sdp;
+}
+
+
 bool SdpDeserialize(const std::string& message,
                     cricket::SessionDescription* desc,
                     std::vector<Candidate>* candidates) {
+  return SdpDeserializeSessionDescription(message, desc) &&
+         SdpDeserializeCandidates(message, candidates);
+}
+
+bool SdpDeserializeSessionDescription(const std::string& message,
+                                      cricket::SessionDescription* desc) {
   size_t current_pos = 0;
 
   // Session Description
@@ -249,15 +294,19 @@
   }
 
   // Media Description
-  if (!ParseMediaDescription(message, &current_pos, desc, candidates)) {
+  if (!ParseMediaDescription(message, &current_pos, desc)) {
     return false;
   }
 
   return true;
 }
 
+bool SdpDeserializeCandidates(const std::string& message,
+                              std::vector<Candidate>* candidates) {
+  return ParseCandidates(message, candidates);
+}
+
 void BuildMediaDescription(const cricket::ContentInfo& content_info,
-                           const std::vector<Candidate>& candidates,
                            const MediaType media_type,
                            std::string* message) {
   ASSERT(message != NULL);
@@ -335,12 +384,6 @@
   // [/<encodingparameters>]
   BuildRtpMap(media_desc, media_type, message);
 
-  // rfc5245
-  // a=candidate:<foundation> <component-id> <transport> <priority>
-  // <connection-address> <port> typ <candidate-types>
-  // [raddr <connection-address>] [rport <port>]
-  BuildCandidate(candidates, media_type, message);
-
   // draft - Mechanisms for Media Source Selection in SDP
   // a=ssrc:<ssrc-id> <attribute>:<value>
   // a=ssrc:<ssrc-id> cname:<value> mslabel:<value> label:<value>
@@ -521,10 +564,8 @@
 }
 
 bool ParseMediaDescription(const std::string& message, size_t* pos,
-                           cricket::SessionDescription* desc,
-                           std::vector<Candidate>* candidates) {
+                           cricket::SessionDescription* desc) {
   ASSERT(desc != NULL);
-  ASSERT(candidates != NULL);
 
   std::string line;
 
@@ -545,7 +586,7 @@
       LOG(LS_WARNING) << "Unsupported media type: " << line;
     }
 
-    if (!ParseContent(message, media_type, pos, content, candidates))
+    if (!ParseContent(message, media_type, pos, content))
       return false;
   }
   return true;
@@ -554,9 +595,7 @@
 bool ParseContent(const std::string& message,
                   const MediaType media_type,
                   size_t* pos,
-                  ContentDescription* content,
-                  std::vector<Candidate>* candidates) {
-  ASSERT(candidates != NULL);
+                  ContentDescription* content) {
   std::string line;
   // Loop until the next m line
   while (!HasPrefix(message, kLinePrefixMedia, *pos)) {
@@ -625,57 +664,7 @@
       const std::string key_params = fields[2];
       media_desc->AddCrypto(CryptoParams(tag, crypto_suite, key_params, ""));
     } else if (HasAttribute(line, kAttributeCandidate)) {
-      // a=candidate:<foundation> <component-id> <transport> <priority>
-      // <connection-address> <port> typ <candidate-types>
-      // [raddr <connection-address>] [rport <port>]
-      // *(SP extension-att-name SP extension-att-value)
-      // 8 mandatory fields
-      if (fields.size() < 8 || (fields[6] != kAttributeCandidateTyp)) {
-        LOG_LINE_PARSING_ERROR(line);
-        return false;
-      }
-      const std::string transport = fields[2];
-      const float priority = talk_base::FromString<float>(fields[3]);
-      const std::string connection_address = fields[4];
-      const int port = talk_base::FromString<int>(fields[5]);
-      std::string candidate_type;
-      const std::string type = fields[7];
-      if (type == kCandidateHost) {
-        candidate_type = cricket::LOCAL_PORT_TYPE;
-      } else if (type == kCandidateSrflx) {
-        candidate_type = cricket::STUN_PORT_TYPE;
-      } else if (type == kCandidateRelay) {
-        candidate_type = cricket::RELAY_PORT_TYPE;
-      } else {
-        LOG(LS_ERROR) << "Unsupported candidate type from line: " << line;
-        return false;
-      }
-
-      // extension
-      std::string name;
-      std::string network_name;
-      std::string username;
-      std::string password;
-      uint32 generation = 0;
-      for (size_t i = 8; i < (fields.size() - 1); ++i) {
-        const std::string field = fields.at(i);
-        if (field == kAttributeCandidateName) {
-          name = fields.at(++i);
-        } else if (field == kAttributeCandidateNetworkName) {
-          network_name = fields.at(++i);
-        } else if (field == kAttributeCandidateUsername) {
-          username = fields.at(++i);
-        } else if (field == kAttributeCandidatePassword) {
-          password = fields.at(++i);
-        } else if (field == kAttributeCandidateGeneration) {
-          generation = talk_base::FromString<uint32>(fields.at(++i));
-        }
-      }
-
-      SocketAddress address(connection_address, port);
-      Candidate candidate(name, transport, address, priority, username,
-          password, candidate_type, network_name, generation);
-      candidates->push_back(candidate);
+      continue;  // Parse candidates separately.
     } else if (HasAttribute(line, kAttributeRtpmap)) {
       // a=rtpmap:<payload type> <encoding name>/<clock rate>
       // [/<encodingparameters>]
@@ -715,4 +704,73 @@
   return true;
 }
 
+bool ParseCandidates(const std::string& message,
+                     std::vector<Candidate>* candidates) {
+  ASSERT(candidates != NULL);
+  std::string line;
+  size_t pos = 0;
+
+  // Loop until the next attribute line.
+  while (GetLine(message, &pos, &line)) {
+    if (!HasPrefix(line, kLinePrefixAttributes) ||
+        !HasAttribute(line, kAttributeCandidate)) {
+      continue;  // Only parse candidates
+    }
+    std::vector<std::string> fields;
+    talk_base::split(line.substr(kLinePrefixLength), kSdpDelimiter, &fields);
+    // a=candidate:<foundation> <component-id> <transport> <priority>
+    // <connection-address> <port> typ <candidate-types>
+    // [raddr <connection-address>] [rport <port>]
+    // *(SP extension-att-name SP extension-att-value)
+    // 8 mandatory fields
+    if (fields.size() < 8 || (fields[6] != kAttributeCandidateTyp)) {
+      LOG_LINE_PARSING_ERROR(line);
+      return false;
+    }
+    const std::string transport = fields[2];
+    const float priority = talk_base::FromString<float>(fields[3]);
+    const std::string connection_address = fields[4];
+    const int port = talk_base::FromString<int>(fields[5]);
+    std::string candidate_type;
+    const std::string type = fields[7];
+    if (type == kCandidateHost) {
+      candidate_type = cricket::LOCAL_PORT_TYPE;
+    } else if (type == kCandidateSrflx) {
+      candidate_type = cricket::STUN_PORT_TYPE;
+    } else if (type == kCandidateRelay) {
+      candidate_type = cricket::RELAY_PORT_TYPE;
+    } else {
+      LOG(LS_ERROR) << "Unsupported candidate type from line: " << line;
+      return false;
+    }
+
+    // extension
+    std::string name;
+    std::string network_name;
+    std::string username;
+    std::string password;
+    uint32 generation = 0;
+    for (size_t i = 8; i < (fields.size() - 1); ++i) {
+      const std::string field = fields.at(i);
+      if (field == kAttributeCandidateName) {
+        name = fields.at(++i);
+      } else if (field == kAttributeCandidateNetworkName) {
+        network_name = fields.at(++i);
+      } else if (field == kAttributeCandidateUsername) {
+        username = fields.at(++i);
+      } else if (field == kAttributeCandidatePassword) {
+        password = fields.at(++i);
+      } else if (field == kAttributeCandidateGeneration) {
+        generation = talk_base::FromString<uint32>(fields.at(++i));
+      }
+    }
+
+    SocketAddress address(connection_address, port);
+    Candidate candidate(name, transport, address, priority, username,
+        password, candidate_type, network_name, generation);
+    candidates->push_back(candidate);
+  }
+  return true;
+}
+
 }  // namespace webrtc
diff --git a/talk/app/webrtc/webrtcsdp.h b/talk/app/webrtc/webrtcsdp.h
index 32131b1..e5c1b12 100644
--- a/talk/app/webrtc/webrtcsdp.h
+++ b/talk/app/webrtc/webrtcsdp.h
@@ -48,13 +48,23 @@
 
 namespace webrtc {
 
-// Serializes the passed in SessionDescription and Candidates to an SDP string.
+// Serializes the passed in SessionDescription and Candidates to a SDP string.
 // desc - The SessionDescription object to be serialized.
 // candidates - The Set of Candidate objects to be serialized.
 // return - SDP string serialized from the arguments.
 std::string SdpSerialize(const cricket::SessionDescription& desc,
                          const std::vector<cricket::Candidate>& candidates);
 
+// Serializes the passed in SessionDescription to a SDP string.
+// desc - The SessionDescription object to be serialized.
+std::string SdpSerializeSessionDescription(
+    const cricket::SessionDescription& desc);
+
+// Serializes the passed in Candidates to a SDP string.
+// candidates - The Set of Candidate objects to be serialized.
+std::string SdpSerializeCandidates(
+    const std::vector<cricket::Candidate>& candidates);
+
 // Deserializes the passed in SDP string to a SessionDescription and Candidates.
 // message - SDP string to be Deserialized.
 // desc - The SessionDescription object deserialized from the SDP string.
@@ -64,6 +74,26 @@
                     cricket::SessionDescription* desc,
                     std::vector<cricket::Candidate>* candidates);
 
+// Deserializes the passed in SDP string to a SessionDescription.
+// Candidates are ignored.
+// message - SDP string to be Deserialized.
+// desc - The SessionDescription object deserialized from the SDP string.
+// return - true on success, false on failure.
+bool SdpDeserializeSessionDescription(const std::string& message,
+                                      cricket::SessionDescription* desc);
+
+// Deserializes the passed in SDP string to Candidates.
+// Only the candidates are parsed from the SDP string.
+// message - SDP string to be Deserialized.
+// candidates - The set of Candidate deserialized from the SDP string.
+// return - true on success, false on failure.
+bool SdpDeserializeCandidates(const std::string& message,
+                              std::vector<cricket::Candidate>* candidates);
+
+// Formats a correct SDP string by reformatting a session description and
+// candidates.
+std::string SdpFormat(const std::string& desc, const std::string& candidates);
+
 }  // namespace webrtc
 
 #endif  // TALK_APP_WEBRTC_WEBRTCSDP_H_
diff --git a/talk/app/webrtc/webrtcsdp_unittest.cc b/talk/app/webrtc/webrtcsdp_unittest.cc
index 7e34d2c..dbc7ccd 100644
--- a/talk/app/webrtc/webrtcsdp_unittest.cc
+++ b/talk/app/webrtc/webrtcsdp_unittest.cc
@@ -46,6 +46,46 @@
 using cricket::VideoContentDescription;
 
 // Reference sdp string
+static const char kSdpFullString[] =
+    "v=0\r\n"
+    "o=- 0 0 IN IP4 127.0.0.1\r\n"
+    "s=\r\n"
+    "t=0 0\r\n"
+    "a=group:BUNDLE audio video\r\n"
+    "m=audio 0 RTP/AVPF 103 104\r\n"
+    "a=candidate:1 1 udp 1 127.0.0.1 1234 typ host name rtp network_name "
+    "eth0 username user_rtp password password_rtp generation 0\r\n"
+    "a=candidate:1 1 udp 1 127.0.0.1 1235 typ host name rtcp network_name "
+    "eth0 username user_rtcp password password_rtcp generation 0\r\n"
+    "a=mid:audio\r\n"
+    "a=rtcp-mux\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_32 "
+    "inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 \r\n"
+    "a=rtpmap:103 ISAC/16000\r\n"
+    "a=rtpmap:104 ISAC/32000\r\n"
+    "a=ssrc:1 cname:stream_1_cname mslabel:local_stream_1 "
+    "label:local_audio_1\r\n"
+    "a=ssrc:4 cname:stream_2_cname mslabel:local_stream_2 "
+    "label:local_audio_2\r\n"
+    "m=video 0 RTP/AVPF 120\r\n"
+    "a=candidate:1 1 udp 1 127.0.0.1 1236 typ host name video_rtcp "
+    "network_name eth0 username user_video_rtcp password password_video_rtcp "
+    "generation 0\r\n"
+    "a=candidate:1 1 udp 1 127.0.0.1 1237 typ host name video_rtp "
+    "network_name eth0 username user_video_rtp password password_video_rtp "
+    "generation 0\r\n"
+    "a=mid:video\r\n"
+    "a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
+    "inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32 \r\n"
+    "a=rtpmap:120 VP8/0\r\n"
+    "a=ssrc:2 cname:stream_1_cname mslabel:local_stream_1 "
+    "label:local_video_1\r\n"
+    "a=ssrc:3 cname:stream_1_cname mslabel:local_stream_1 "
+    "label:local_video_2\r\n"
+    "a=ssrc:5 cname:stream_2_cname mslabel:local_stream_2 "
+    "label:local_video_3\r\n";
+
+// SDP reference string without the candidates.
 static const char kSdpString[] =
     "v=0\r\n"
     "o=- 0 0 IN IP4 127.0.0.1\r\n"
@@ -59,10 +99,6 @@
     "inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 \r\n"
     "a=rtpmap:103 ISAC/16000\r\n"
     "a=rtpmap:104 ISAC/32000\r\n"
-    "a=candidate:1 1 udp 1 127.0.0.1 1234 typ host name rtp network_name "
-    "eth0 username user_rtp password password_rtp generation 0\r\n"
-    "a=candidate:1 1 udp 1 127.0.0.1 1235 typ host name rtcp network_name "
-    "eth0 username user_rtcp password password_rtcp generation 0\r\n"
     "a=ssrc:1 cname:stream_1_cname mslabel:local_stream_1 "
     "label:local_audio_1\r\n"
     "a=ssrc:4 cname:stream_2_cname mslabel:local_stream_2 "
@@ -72,12 +108,6 @@
     "a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
     "inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32 \r\n"
     "a=rtpmap:120 VP8/0\r\n"
-    "a=candidate:1 1 udp 1 127.0.0.1 1236 typ host name video_rtcp "
-    "network_name eth0 username user_video_rtcp password password_video_rtcp "
-    "generation 0\r\n"
-    "a=candidate:1 1 udp 1 127.0.0.1 1237 typ host name video_rtp "
-    "network_name eth0 username user_video_rtp password password_video_rtp "
-    "generation 0\r\n"
     "a=ssrc:2 cname:stream_1_cname mslabel:local_stream_1 "
     "label:local_video_1\r\n"
     "a=ssrc:3 cname:stream_1_cname mslabel:local_stream_1 "
@@ -85,6 +115,19 @@
     "a=ssrc:5 cname:stream_2_cname mslabel:local_stream_2 "
     "label:local_video_3\r\n";
 
+// Candidates reference string.
+static const char kSdpCandidates[] =
+    "a=candidate:1 1 udp 1 127.0.0.1 1234 typ host name rtp network_name "
+    "eth0 username user_rtp password password_rtp generation 0\r\n"
+    "a=candidate:1 1 udp 1 127.0.0.1 1235 typ host name rtcp network_name "
+    "eth0 username user_rtcp password password_rtcp generation 0\r\n"
+    "a=candidate:1 1 udp 1 127.0.0.1 1236 typ host name video_rtcp "
+    "network_name eth0 username user_video_rtcp password password_video_rtcp "
+    "generation 0\r\n"
+    "a=candidate:1 1 udp 1 127.0.0.1 1237 typ host name video_rtp "
+    "network_name eth0 username user_video_rtp password password_video_rtp "
+    "generation 0\r\n";
+
 static const char kSdpDestroyer[] = "!@#$%^&";
 
 // MediaStream 1
@@ -201,6 +244,10 @@
     // cryptos
     EXPECT_EQ(acd1->cryptos().size(), acd2->cryptos().size());
     EXPECT_EQ(vcd1->cryptos().size(), vcd2->cryptos().size());
+    if (acd1->cryptos().size() != acd2->cryptos().size() ||
+        vcd1->cryptos().size() != vcd2->cryptos().size()) {
+      return false;
+    }
     for (size_t i = 0; i< acd1->cryptos().size(); ++i) {
       const CryptoParams c1 = acd1->cryptos().at(i);
       const CryptoParams c2 = acd2->cryptos().at(i);
@@ -214,7 +261,11 @@
 
     // codecs
     EXPECT_EQ(acd1->codecs().size(), acd2->codecs().size());
+    if (acd1->codecs().size() != acd2->codecs().size())
+      return false;
     EXPECT_EQ(vcd1->codecs().size(), vcd2->codecs().size());
+    if (vcd1->codecs().size() != vcd2->codecs().size())
+      return false;
     for (size_t i = 0; i< acd1->codecs().size(); ++i) {
       const AudioCodec c1 = acd1->codecs().at(i);
       const AudioCodec c2 = acd2->codecs().at(i);
@@ -238,7 +289,8 @@
 
   bool CompareCandidates(const Candidates& cs1, const Candidates& cs2) {
     EXPECT_EQ(cs1.size(), cs2.size());
-
+    if (cs1.size() != cs2.size())
+      return false;
     for (size_t i = 0; i< cs1.size(); ++i) {
       const cricket::Candidate c1 = cs1.at(i);
       const cricket::Candidate c2 = cs2.at(i);
@@ -250,7 +302,7 @@
   bool ReplaceAndTryToParse(const char* search, const char* replace) {
     SessionDescription desc;
     std::vector<cricket::Candidate> candidates;
-    std::string sdp = kSdpString;
+    std::string sdp = kSdpFullString;
     talk_base::replace_substrs(search, strlen(search), replace,
         strlen(replace), &sdp);
     return webrtc::SdpDeserialize(sdp, &desc, &candidates);
@@ -264,17 +316,38 @@
 TEST_F(WebRtcSdpTest, Serialize) {
   std::string message = webrtc::SdpSerialize(desc_, candidates_);
   LOG(LS_INFO) << "SDP: " << message;
+  EXPECT_EQ(std::string(kSdpFullString), message);
+}
+
+TEST_F(WebRtcSdpTest, SerializeSessionDescription) {
+  std::string message = webrtc::SdpSerializeSessionDescription(desc_);
   EXPECT_EQ(std::string(kSdpString), message);
 }
 
+TEST_F(WebRtcSdpTest, SerializeCandidates) {
+  std::string message = webrtc::SdpSerializeCandidates(candidates_);
+  EXPECT_EQ(std::string(kSdpCandidates), message);
+}
+
 TEST_F(WebRtcSdpTest, Deserialize) {
   SessionDescription desc;
   std::vector<cricket::Candidate> candidates;
   // Deserialize
-  EXPECT_TRUE(webrtc::SdpDeserialize(kSdpString, &desc, &candidates));
+  EXPECT_TRUE(webrtc::SdpDeserialize(kSdpFullString, &desc, &candidates));
   // Verify
   LOG(LS_INFO) << "SDP: " << webrtc::SdpSerialize(desc, candidates);
   EXPECT_TRUE(CompareSessionDescription(desc_, desc));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSessionDescription) {
+  SessionDescription desc;
+  EXPECT_TRUE(webrtc::SdpDeserializeSessionDescription(kSdpString, &desc));
+  EXPECT_TRUE(CompareSessionDescription(desc_, desc));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeCandidates) {
+  std::vector<cricket::Candidate> candidates;
+  EXPECT_TRUE(webrtc::SdpDeserializeCandidates(kSdpCandidates, &candidates));
   EXPECT_TRUE(CompareCandidates(candidates_, candidates));
 }
 
@@ -294,3 +367,8 @@
   // Broken media description
   EXPECT_EQ(true, ReplaceAndTryToParse("video 0 RTP/AVPF", kSdpDestroyer));
 }
+
+TEST_F(WebRtcSdpTest, FormatSdp) {
+  std::string full_sdp = webrtc::SdpFormat(kSdpString, kSdpCandidates);
+  EXPECT_EQ(kSdpFullString, full_sdp);
+}
diff --git a/talk/base/nat_unittest.cc b/talk/base/nat_unittest.cc
index a7fc3b1..8f87b3a 100644
--- a/talk/base/nat_unittest.cc
+++ b/talk/base/nat_unittest.cc
@@ -218,7 +218,7 @@
   explicit TestVirtualSocketServer(SocketServer* ss)
       : VirtualSocketServer(ss) {}
   // Expose this publicly
-  uint32 GetNextIP() { return VirtualSocketServer::GetNextIP(); }
+  IPAddress GetNextIP(int af) { return VirtualSocketServer::GetNextIP(af); }
 };
 
 TEST(NatTest, TestVirtual) {
@@ -227,10 +227,11 @@
   TestVirtualSocketServer* ext_vss = new TestVirtualSocketServer(
       new PhysicalSocketServer());
 
+  // TODO: IPv6ize this test when the NAT stuff is v6ed.
   SocketAddress int_addr, ext_addrs[4];
-  int_addr.SetIP(IPAddress(int_vss->GetNextIP()));
-  ext_addrs[0].SetIP(IPAddress(ext_vss->GetNextIP()));
-  ext_addrs[1].SetIP(IPAddress(ext_vss->GetNextIP()));
+  int_addr.SetIP(int_vss->GetNextIP(int_addr.ipaddr().family()));
+  ext_addrs[0].SetIP(ext_vss->GetNextIP(int_addr.ipaddr().family()));
+  ext_addrs[1].SetIP(ext_vss->GetNextIP(int_addr.ipaddr().family()));
   ext_addrs[2].SetIP(ext_addrs[0].ipaddr());
   ext_addrs[3].SetIP(ext_addrs[1].ipaddr());
 
diff --git a/talk/base/natserver.cc b/talk/base/natserver.cc
index 7107f77..d287059 100644
--- a/talk/base/natserver.cc
+++ b/talk/base/natserver.cc
@@ -2,26 +2,26 @@
  * libjingle
  * Copyright 2004--2005, Google Inc.
  *
- * Redistribution and use in source and binary forms, with or without 
+ * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
  *
- *  1. Redistributions of source code must retain the above copyright notice, 
+ *  1. Redistributions of source code must retain the above copyright notice,
  *     this list of conditions and the following disclaimer.
  *  2. Redistributions in binary form must reproduce the above copyright notice,
  *     this list of conditions and the following disclaimer in the documentation
  *     and/or other materials provided with the distribution.
- *  3. The name of the author may not be used to endorse or promote products 
+ *  3. The name of the author may not be used to endorse or promote products
  *     derived from this software without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
- * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
diff --git a/talk/base/socket.h b/talk/base/socket.h
index a55b3dc..ad8eee6 100644
--- a/talk/base/socket.h
+++ b/talk/base/socket.h
@@ -180,9 +180,10 @@
 
   enum Option {
     OPT_DONTFRAGMENT,
-    OPT_RCVBUF,  // receive buffer size
-    OPT_SNDBUF,  // send buffer size
-    OPT_NODELAY  // whether Nagle algorithm is enabled
+    OPT_RCVBUF,      // receive buffer size
+    OPT_SNDBUF,      // send buffer size
+    OPT_NODELAY,     // whether Nagle algorithm is enabled
+    OPT_IPV6_V6ONLY  // Whether the socket is IPv6 only.
   };
   virtual int GetOption(Option opt, int* value) = 0;
   virtual int SetOption(Option opt, int value) = 0;
diff --git a/talk/base/virtualsocket_unittest.cc b/talk/base/virtualsocket_unittest.cc
index 1c44fe7..93fb5ef 100644
--- a/talk/base/virtualsocket_unittest.cc
+++ b/talk/base/virtualsocket_unittest.cc
@@ -26,7 +26,9 @@
  */
 
 #include <time.h>
-
+#ifdef POSIX
+#include <netinet/in.h>
+#endif
 #include <cmath>
 
 #include "talk/base/logging.h"
@@ -39,487 +41,6 @@
 
 using namespace talk_base;
 
-class VirtualSocketServerTest : public testing::Test {
- public:
-  VirtualSocketServerTest() : ss_(new VirtualSocketServer(NULL)) {
-  }
-
- protected:
-  virtual void SetUp() {
-    Thread::Current()->set_socketserver(ss_);
-  }
-  virtual void TearDown() {
-    Thread::Current()->set_socketserver(NULL);
-  }
-
-  VirtualSocketServer* ss_;
-};
-
-TEST_F(VirtualSocketServerTest, basic) {
-  SocketAddress addr1(IPAddress(INADDR_ANY), 5000);
-  AsyncSocket* socket = ss_->CreateAsyncSocket(SOCK_DGRAM);
-  socket->Bind(addr1);
-  addr1 = socket->GetLocalAddress();
-
-  TestClient* client1 = new TestClient(new AsyncUDPSocket(socket));
-  AsyncSocket* socket2 = ss_->CreateAsyncSocket(SOCK_DGRAM);
-  TestClient* client2 = new TestClient(new AsyncUDPSocket(socket2));
-
-  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);
-
-  for (int i = 0; i < 10; i++) {
-    client2 = new TestClient(AsyncUDPSocket::Create(ss_, SocketAddress()));
-
-    SocketAddress addr4;
-    EXPECT_EQ(3, client2->SendTo("foo", 3, addr1));
-    EXPECT_TRUE(client1->CheckNextPacket("foo", 3, &addr4));
-    EXPECT_EQ(addr4.ipaddr().v4AddressAsHostOrderInteger(),
-              addr2.ipaddr().v4AddressAsHostOrderInteger() + 1);
-    EXPECT_EQ(addr4.port(), addr2.port() + 1);
-
-    SocketAddress addr5;
-    EXPECT_EQ(6, client1->SendTo("bizbaz", 6, addr4));
-    EXPECT_TRUE(client2->CheckNextPacket("bizbaz", 6, &addr5));
-    EXPECT_EQ(addr5, addr1);
-
-    addr2 = addr4;
-  }
-}
-
-TEST_F(VirtualSocketServerTest, connect) {
-  testing::StreamSink sink;
-  SocketAddress accept_addr;
-  const SocketAddress kEmptyAddr;
-
-  // Create client
-  AsyncSocket* client = ss_->CreateAsyncSocket(SOCK_STREAM);
-  sink.Monitor(client);
-  EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED);
-  EXPECT_EQ(client->GetLocalAddress(), kEmptyAddr);
-
-  // Create server
-  AsyncSocket* server = ss_->CreateAsyncSocket(SOCK_STREAM);
-  sink.Monitor(server);
-  EXPECT_NE(0, server->Listen(5));  // Bind required
-  EXPECT_EQ(0, server->Bind(kEmptyAddr));
-  EXPECT_EQ(0, server->Listen(5));
-  EXPECT_EQ(server->GetState(), AsyncSocket::CS_CONNECTING);
-
-  // No pending server connections
-  EXPECT_FALSE(sink.Check(server, testing::SSE_READ));
-  EXPECT_TRUE(NULL == server->Accept(&accept_addr));
-  EXPECT_EQ(accept_addr, kEmptyAddr);
-
-  // Attempt connect to listening socket
-  EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
-  EXPECT_NE(client->GetLocalAddress(), kEmptyAddr);  // Implicit Bind
-  EXPECT_NE(client->GetLocalAddress(), server->GetLocalAddress());
-
-  // Client is connecting
-  EXPECT_EQ(client->GetState(), AsyncSocket::CS_CONNECTING);
-  EXPECT_FALSE(sink.Check(client, testing::SSE_OPEN));
-  EXPECT_FALSE(sink.Check(client, testing::SSE_CLOSE));
-
-  ss_->ProcessMessagesUntilIdle();
-
-  // Client still connecting
-  EXPECT_EQ(client->GetState(), AsyncSocket::CS_CONNECTING);
-  EXPECT_FALSE(sink.Check(client, testing::SSE_OPEN));
-  EXPECT_FALSE(sink.Check(client, testing::SSE_CLOSE));
-
-  // Server has pending connection
-  EXPECT_TRUE(sink.Check(server, testing::SSE_READ));
-  Socket* accepted = server->Accept(&accept_addr);
-  EXPECT_TRUE(NULL != accepted);
-  EXPECT_NE(accept_addr, kEmptyAddr);
-  EXPECT_EQ(accepted->GetRemoteAddress(), accept_addr);
-
-  EXPECT_EQ(accepted->GetState(), AsyncSocket::CS_CONNECTED);
-  EXPECT_EQ(accepted->GetLocalAddress(), server->GetLocalAddress());
-  EXPECT_EQ(accepted->GetRemoteAddress(), client->GetLocalAddress());
-
-  ss_->ProcessMessagesUntilIdle();
-
-  // Client has connected
-  EXPECT_EQ(client->GetState(), AsyncSocket::CS_CONNECTED);
-  EXPECT_TRUE(sink.Check(client, testing::SSE_OPEN));
-  EXPECT_FALSE(sink.Check(client, testing::SSE_CLOSE));
-  EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress());
-  EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress());
-}
-
-TEST_F(VirtualSocketServerTest, connect_to_non_listener) {
-  testing::StreamSink sink;
-  SocketAddress accept_addr;
-  const SocketAddress kEmptyAddr;
-
-  // Create client
-  AsyncSocket* client = ss_->CreateAsyncSocket(SOCK_STREAM);
-  sink.Monitor(client);
-
-  // Create server
-  AsyncSocket* server = ss_->CreateAsyncSocket(SOCK_STREAM);
-  sink.Monitor(server);
-  EXPECT_EQ(0, server->Bind(kEmptyAddr));
-
-  // Attempt connect to non-listening socket
-  EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
-
-  ss_->ProcessMessagesUntilIdle();
-
-  // No pending server connections
-  EXPECT_FALSE(sink.Check(server, testing::SSE_READ));
-  EXPECT_TRUE(NULL == server->Accept(&accept_addr));
-  EXPECT_EQ(accept_addr, kEmptyAddr);
-
-  // Connection failed
-  EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED);
-  EXPECT_FALSE(sink.Check(client, testing::SSE_OPEN));
-  EXPECT_TRUE(sink.Check(client, testing::SSE_ERROR));
-  EXPECT_EQ(client->GetRemoteAddress(), kEmptyAddr);
-}
-
-TEST_F(VirtualSocketServerTest, close_during_connect) {
-  testing::StreamSink sink;
-  SocketAddress accept_addr;
-  const SocketAddress kEmptyAddr;
-
-  // Create client and server
-  AsyncSocket* client = ss_->CreateAsyncSocket(SOCK_STREAM);
-  sink.Monitor(client);
-  AsyncSocket* server = ss_->CreateAsyncSocket(SOCK_STREAM);
-  sink.Monitor(server);
-
-  // Initiate connect
-  EXPECT_EQ(0, server->Bind(kEmptyAddr));
-  EXPECT_EQ(0, server->Listen(5));
-  EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
-
-  // Server close before socket enters accept queue
-  EXPECT_FALSE(sink.Check(server, testing::SSE_READ));
-  server->Close();
-
-  ss_->ProcessMessagesUntilIdle();
-
-  // Result: connection failed
-  EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED);
-  EXPECT_TRUE(sink.Check(client, testing::SSE_ERROR));
-
-  // New server
-  delete server;
-  server = ss_->CreateAsyncSocket(SOCK_STREAM);
-  sink.Monitor(server);
-
-  // Initiate connect
-  EXPECT_EQ(0, server->Bind(kEmptyAddr));
-  EXPECT_EQ(0, server->Listen(5));
-  EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
-
-  ss_->ProcessMessagesUntilIdle();
-
-  // Server close while socket is in accept queue
-  EXPECT_TRUE(sink.Check(server, testing::SSE_READ));
-  server->Close();
-
-  ss_->ProcessMessagesUntilIdle();
-
-  // Result: connection failed
-  EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED);
-  EXPECT_TRUE(sink.Check(client, testing::SSE_ERROR));
-
-  // New server
-  delete server;
-  server = ss_->CreateAsyncSocket(SOCK_STREAM);
-  sink.Monitor(server);
-
-  // Initiate connect
-  EXPECT_EQ(0, server->Bind(kEmptyAddr));
-  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));
-}
-
-TEST_F(VirtualSocketServerTest, close) {
-  testing::StreamSink sink;
-  const SocketAddress kEmptyAddr;
-
-  // Create clients
-  AsyncSocket* a = ss_->CreateAsyncSocket(SOCK_STREAM);
-  sink.Monitor(a);
-  a->Bind(kEmptyAddr);
-
-  AsyncSocket* b = ss_->CreateAsyncSocket(SOCK_STREAM);
-  sink.Monitor(b);
-  b->Bind(kEmptyAddr);
-
-  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);
-}
-
-TEST_F(VirtualSocketServerTest, tcp_send) {
-  testing::StreamSink sink;
-  const SocketAddress kEmptyAddr;
-
-  // Connect two sockets
-  AsyncSocket* a = ss_->CreateAsyncSocket(SOCK_STREAM);
-  sink.Monitor(a);
-  a->Bind(kEmptyAddr);
-
-  AsyncSocket* b = ss_->CreateAsyncSocket(SOCK_STREAM);
-  sink.Monitor(b);
-  b->Bind(kEmptyAddr);
-
-  EXPECT_EQ(0, a->Connect(b->GetLocalAddress()));
-  EXPECT_EQ(0, b->Connect(a->GetLocalAddress()));
-
-  ss_->ProcessMessagesUntilIdle();
-
-  const size_t kBufferSize = 2000;
-  ss_->set_send_buffer_capacity(kBufferSize);
-  ss_->set_recv_buffer_capacity(kBufferSize);
-
-  const size_t kDataSize = 5000;
-  char send_buffer[kDataSize], recv_buffer[kDataSize];
-  for (size_t i = 0; i < kDataSize; ++i) send_buffer[i] = i;
-  memset(recv_buffer, 0, sizeof(recv_buffer));
-  size_t send_pos = 0, recv_pos = 0;
-
-  // Can't send more than send buffer in one write
-  int result = a->Send(send_buffer + send_pos, kDataSize - send_pos);
-  EXPECT_EQ(static_cast<int>(kBufferSize), result);
-  send_pos += result;
-
-  ss_->ProcessMessagesUntilIdle();
-  EXPECT_FALSE(sink.Check(a, testing::SSE_WRITE));
-  EXPECT_TRUE(sink.Check(b, testing::SSE_READ));
-
-  // Receive buffer is already filled, fill send buffer again
-  result = a->Send(send_buffer + send_pos, kDataSize - send_pos);
-  EXPECT_EQ(static_cast<int>(kBufferSize), result);
-  send_pos += result;
-
-  ss_->ProcessMessagesUntilIdle();
-  EXPECT_FALSE(sink.Check(a, testing::SSE_WRITE));
-  EXPECT_FALSE(sink.Check(b, testing::SSE_READ));
-
-  // No more room in send or receive buffer
-  result = a->Send(send_buffer + send_pos, kDataSize - send_pos);
-  EXPECT_EQ(-1, result);
-  EXPECT_TRUE(a->IsBlocking());
-
-  // Read a subset of the data
-  result = b->Recv(recv_buffer + recv_pos, 500);
-  EXPECT_EQ(500, result);
-  recv_pos += result;
-
-  ss_->ProcessMessagesUntilIdle();
-  EXPECT_TRUE(sink.Check(a, testing::SSE_WRITE));
-  EXPECT_TRUE(sink.Check(b, testing::SSE_READ));
-
-  // Room for more on the sending side
-  result = a->Send(send_buffer + send_pos, kDataSize - send_pos);
-  EXPECT_EQ(500, result);
-  send_pos += result;
-
-  // Empty the recv buffer
-  while (true) {
-    result = b->Recv(recv_buffer + recv_pos, kDataSize - recv_pos);
-    if (result < 0) {
-      EXPECT_EQ(-1, result);
-      EXPECT_TRUE(b->IsBlocking());
-      break;
-    }
-    recv_pos += result;
-  }
-
-  ss_->ProcessMessagesUntilIdle();
-  EXPECT_TRUE(sink.Check(b, testing::SSE_READ));
-
-  // Continue to empty the recv buffer
-  while (true) {
-    result = b->Recv(recv_buffer + recv_pos, kDataSize - recv_pos);
-    if (result < 0) {
-      EXPECT_EQ(-1, result);
-      EXPECT_TRUE(b->IsBlocking());
-      break;
-    }
-    recv_pos += result;
-  }
-
-  // Send last of the data
-  result = a->Send(send_buffer + send_pos, kDataSize - send_pos);
-  EXPECT_EQ(500, result);
-  send_pos += result;
-
-  ss_->ProcessMessagesUntilIdle();
-  EXPECT_TRUE(sink.Check(b, testing::SSE_READ));
-
-  // Receive the last of the data
-  while (true) {
-    result = b->Recv(recv_buffer + recv_pos, kDataSize - recv_pos);
-    if (result < 0) {
-      EXPECT_EQ(-1, result);
-      EXPECT_TRUE(b->IsBlocking());
-      break;
-    }
-    recv_pos += result;
-  }
-
-  ss_->ProcessMessagesUntilIdle();
-  EXPECT_FALSE(sink.Check(b, testing::SSE_READ));
-
-  // The received data matches the sent data
-  EXPECT_EQ(kDataSize, send_pos);
-  EXPECT_EQ(kDataSize, recv_pos);
-  EXPECT_EQ(0, memcmp(recv_buffer, send_buffer, kDataSize));
-}
-
-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;
-      }
-    }
-  }
-}
-
-TEST_F(VirtualSocketServerTest, TcpSendsPacketsInOrder) {
-  const SocketAddress kEmptyAddr;
-
-  // Connect two sockets
-  AsyncSocket* a = ss_->CreateAsyncSocket(SOCK_STREAM);
-  AsyncSocket* b = ss_->CreateAsyncSocket(SOCK_STREAM);
-  a->Bind(kEmptyAddr);
-  b->Bind(kEmptyAddr);
-  EXPECT_EQ(0, a->Connect(b->GetLocalAddress()));
-  EXPECT_EQ(0, b->Connect(a->GetLocalAddress()));
-  ss_->ProcessMessagesUntilIdle();
-
-  // First, deliver all packets in 0 ms.
-  char buffer[2] = { 0, 0 };
-  const size_t cNumPackets = 10;
-  for (size_t i = 0; i < cNumPackets; ++i) {
-    buffer[0] = '0' + i;
-    EXPECT_EQ(1, a->Send(buffer, 1));
-  }
-
-  ss_->ProcessMessagesUntilIdle();
-
-  for (size_t i = 0; i < cNumPackets; ++i) {
-    EXPECT_EQ(1, b->Recv(buffer, sizeof(buffer)));
-    EXPECT_EQ(static_cast<char>('0' + i), buffer[0]);
-  }
-
-  // Next, deliver packets at random intervals
-  const uint32 mean = 50;
-  const uint32 stddev = 50;
-
-  ss_->set_delay_mean(mean);
-  ss_->set_delay_stddev(stddev);
-  ss_->UpdateDelayDistribution();
-
-  for (size_t i = 0; i < cNumPackets; ++i) {
-    buffer[0] = 'A' + i;
-    EXPECT_EQ(1, a->Send(buffer, 1));
-  }
-
-  ss_->ProcessMessagesUntilIdle();
-
-  for (size_t i = 0; i < cNumPackets; ++i) {
-    EXPECT_EQ(1, b->Recv(buffer, sizeof(buffer)));
-    EXPECT_EQ(static_cast<char>('A' + i), buffer[0]);
-  }
-}
-
 // Sends at a constant rate but with random packet sizes.
 struct Sender : public MessageHandler {
   Sender(Thread* th, AsyncSocket* s, uint32 rt)
@@ -616,71 +137,873 @@
   uint32 samples;
 };
 
-TEST_F(VirtualSocketServerTest, bandwidth) {
-  AsyncSocket* send_socket = ss_->CreateAsyncSocket(SOCK_DGRAM);
-  AsyncSocket* recv_socket = ss_->CreateAsyncSocket(SOCK_DGRAM);
-  ASSERT_EQ(0, send_socket->Bind(SocketAddress(IPAddress(INADDR_ANY), 1000)));
-  ASSERT_EQ(0, recv_socket->Bind(SocketAddress(IPAddress(INADDR_ANY), 1000)));
-  ASSERT_EQ(0, send_socket->Connect(recv_socket->GetLocalAddress()));
+class VirtualSocketServerTest : public testing::Test {
+ public:
+  VirtualSocketServerTest() : ss_(new VirtualSocketServer(NULL)),
+                              kIPv4AnyAddress(IPAddress(INADDR_ANY), 0),
+                              kIPv6AnyAddress(IPAddress(in6addr_any), 0) {
+  }
 
-  uint32 bandwidth = 64 * 1024;
-  ss_->set_bandwidth(bandwidth);
+  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);
+    }
+  }
 
-  Thread* pthMain = Thread::Current();
-  Sender sender(pthMain, send_socket, 80 * 1024);
-  Receiver receiver(pthMain, recv_socket, bandwidth);
+  void BasicTest(const SocketAddress& initial_addr) {
+    AsyncSocket* socket = ss_->CreateAsyncSocket(SOCK_DGRAM);
+    socket->Bind(initial_addr);
+    SocketAddress server_addr = socket->GetLocalAddress();
+    // Make sure VSS didn't switch families on us.
+    EXPECT_EQ(server_addr.ipaddr().family(),
+              initial_addr.ipaddr().family());
 
-  pthMain->ProcessMessages(5000);
-  sender.done = true;
-  pthMain->ProcessMessages(5000);
+    TestClient* client1 = new TestClient(new AsyncUDPSocket(socket));
+    AsyncSocket* socket2 = ss_->CreateAsyncSocket(SOCK_DGRAM);
+    TestClient* client2 = new TestClient(new AsyncUDPSocket(socket2));
 
-  ASSERT_TRUE(receiver.count >= 5 * 3 * bandwidth / 4);
-  ASSERT_TRUE(receiver.count <= 6 * bandwidth);  // queue could drain for 1 sec
+    SocketAddress client2_addr;
+    EXPECT_EQ(3, client2->SendTo("foo", 3, server_addr));
+    EXPECT_TRUE(client1->CheckNextPacket("foo", 3, &client2_addr));
 
-  ss_->set_bandwidth(0);
+    SocketAddress client1_addr;
+    EXPECT_EQ(6, client1->SendTo("bizbaz", 6, client2_addr));
+    EXPECT_TRUE(client2->CheckNextPacket("bizbaz", 6, &client1_addr));
+    EXPECT_EQ(client1_addr, server_addr);
+
+    for (int i = 0; i < 10; i++) {
+      client2 = new TestClient(AsyncUDPSocket::Create(ss_, SocketAddress()));
+
+      SocketAddress next_client2_addr;
+      EXPECT_EQ(3, client2->SendTo("foo", 3, server_addr));
+      EXPECT_TRUE(client1->CheckNextPacket("foo", 3, &next_client2_addr));
+      CheckAddressIncrementalization(next_client2_addr, client2_addr);
+      // EXPECT_EQ(next_client2_addr.port(), client2_addr.port() + 1);
+
+      SocketAddress server_addr2;
+      EXPECT_EQ(6, client1->SendTo("bizbaz", 6, next_client2_addr));
+      EXPECT_TRUE(client2->CheckNextPacket("bizbaz", 6, &server_addr2));
+      EXPECT_EQ(server_addr2, server_addr);
+
+      client2_addr = next_client2_addr;
+    }
+  }
+
+  // initial_addr should be made from either INADDR_ANY or in6addr_any.
+  void ConnectTest(const SocketAddress& initial_addr) {
+    testing::StreamSink sink;
+    SocketAddress accept_addr;
+    const SocketAddress kEmptyAddr;
+
+    // Create client
+    AsyncSocket* client = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(client);
+    EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED);
+    EXPECT_EQ(client->GetLocalAddress(), kEmptyAddr);
+
+    // Create server
+    AsyncSocket* server = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(server);
+    EXPECT_NE(0, server->Listen(5));  // Bind required
+    EXPECT_EQ(0, server->Bind(initial_addr));
+    EXPECT_EQ(server->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+    EXPECT_EQ(0, server->Listen(5));
+    EXPECT_EQ(server->GetState(), AsyncSocket::CS_CONNECTING);
+
+    // No pending server connections
+    EXPECT_FALSE(sink.Check(server, testing::SSE_READ));
+    EXPECT_TRUE(NULL == server->Accept(&accept_addr));
+    EXPECT_EQ(accept_addr, kEmptyAddr);
+
+    // Attempt connect to listening socket
+    EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+    EXPECT_NE(client->GetLocalAddress(), kEmptyAddr);  // Implicit Bind
+    EXPECT_NE(client->GetLocalAddress(), server->GetLocalAddress());
+
+    // Client is connecting
+    EXPECT_EQ(client->GetState(), AsyncSocket::CS_CONNECTING);
+    EXPECT_FALSE(sink.Check(client, testing::SSE_OPEN));
+    EXPECT_FALSE(sink.Check(client, testing::SSE_CLOSE));
+
+    ss_->ProcessMessagesUntilIdle();
+
+    // Client still connecting
+    EXPECT_EQ(client->GetState(), AsyncSocket::CS_CONNECTING);
+    EXPECT_FALSE(sink.Check(client, testing::SSE_OPEN));
+    EXPECT_FALSE(sink.Check(client, testing::SSE_CLOSE));
+
+    // Server has pending connection
+    EXPECT_TRUE(sink.Check(server, testing::SSE_READ));
+    Socket* accepted = server->Accept(&accept_addr);
+    EXPECT_TRUE(NULL != accepted);
+    EXPECT_NE(accept_addr, kEmptyAddr);
+    EXPECT_EQ(accepted->GetRemoteAddress(), accept_addr);
+
+    EXPECT_EQ(accepted->GetState(), AsyncSocket::CS_CONNECTED);
+    EXPECT_EQ(accepted->GetLocalAddress(), server->GetLocalAddress());
+    EXPECT_EQ(accepted->GetRemoteAddress(), client->GetLocalAddress());
+
+    ss_->ProcessMessagesUntilIdle();
+
+    // Client has connected
+    EXPECT_EQ(client->GetState(), AsyncSocket::CS_CONNECTED);
+    EXPECT_TRUE(sink.Check(client, testing::SSE_OPEN));
+    EXPECT_FALSE(sink.Check(client, testing::SSE_CLOSE));
+    EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress());
+    EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress());
+  }
+
+  void ConnectToNonListenerTest(const SocketAddress& initial_addr) {
+    testing::StreamSink sink;
+    SocketAddress accept_addr;
+    const SocketAddress kEmptyAddr;
+
+    // Create client
+    AsyncSocket* client = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(client);
+
+    // Create server
+    AsyncSocket* server = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(server);
+    EXPECT_EQ(0, server->Bind(initial_addr));
+    EXPECT_EQ(server->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+    // Attempt connect to non-listening socket
+    EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+
+    ss_->ProcessMessagesUntilIdle();
+
+    // No pending server connections
+    EXPECT_FALSE(sink.Check(server, testing::SSE_READ));
+    EXPECT_TRUE(NULL == server->Accept(&accept_addr));
+    EXPECT_EQ(accept_addr, kEmptyAddr);
+
+    // Connection failed
+    EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED);
+    EXPECT_FALSE(sink.Check(client, testing::SSE_OPEN));
+    EXPECT_TRUE(sink.Check(client, testing::SSE_ERROR));
+    EXPECT_EQ(client->GetRemoteAddress(), kEmptyAddr);
+  }
+
+  void CloseDuringConnectTest(const SocketAddress& initial_addr) {
+    testing::StreamSink sink;
+    SocketAddress accept_addr;
+    const SocketAddress kEmptyAddr;
+
+    // Create client and server
+    AsyncSocket* client = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(client);
+    AsyncSocket* server = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(server);
+
+    // Initiate connect
+    EXPECT_EQ(0, server->Bind(initial_addr));
+    EXPECT_EQ(server->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+
+    EXPECT_EQ(0, server->Listen(5));
+    EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+
+    // Server close before socket enters accept queue
+    EXPECT_FALSE(sink.Check(server, testing::SSE_READ));
+    server->Close();
+
+    ss_->ProcessMessagesUntilIdle();
+
+    // Result: connection failed
+    EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED);
+    EXPECT_TRUE(sink.Check(client, testing::SSE_ERROR));
+
+    // New server
+    delete server;
+    server = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(server);
+
+    // Initiate connect
+    EXPECT_EQ(0, server->Bind(initial_addr));
+    EXPECT_EQ(server->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+
+    EXPECT_EQ(0, server->Listen(5));
+    EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+
+    ss_->ProcessMessagesUntilIdle();
+
+    // Server close while socket is in accept queue
+    EXPECT_TRUE(sink.Check(server, testing::SSE_READ));
+    server->Close();
+
+    ss_->ProcessMessagesUntilIdle();
+
+    // Result: connection failed
+    EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED);
+    EXPECT_TRUE(sink.Check(client, testing::SSE_ERROR));
+
+    // New server
+    delete server;
+    server = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(server);
+
+    // Initiate connect
+    EXPECT_EQ(0, server->Bind(initial_addr));
+    EXPECT_EQ(server->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+
+    EXPECT_EQ(0, server->Listen(5));
+    EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+
+    ss_->ProcessMessagesUntilIdle();
+
+    // Server accepts connection
+    EXPECT_TRUE(sink.Check(server, testing::SSE_READ));
+    AsyncSocket* accepted = server->Accept(&accept_addr);
+    ASSERT_TRUE(NULL != accepted);
+    sink.Monitor(accepted);
+
+    // Client closes before connection complets
+    EXPECT_EQ(accepted->GetState(), AsyncSocket::CS_CONNECTED);
+
+    // Connected message has not been processed yet.
+    EXPECT_EQ(client->GetState(), AsyncSocket::CS_CONNECTING);
+    client->Close();
+
+    ss_->ProcessMessagesUntilIdle();
+
+    // Result: accepted socket closes
+    EXPECT_EQ(accepted->GetState(), AsyncSocket::CS_CLOSED);
+    EXPECT_TRUE(sink.Check(accepted, testing::SSE_CLOSE));
+    EXPECT_FALSE(sink.Check(client, testing::SSE_CLOSE));
+  }
+
+  void CloseTest(const SocketAddress& initial_addr) {
+    testing::StreamSink sink;
+    const SocketAddress kEmptyAddr;
+
+    // Create clients
+    AsyncSocket* a = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(a);
+    a->Bind(initial_addr);
+    EXPECT_EQ(a->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+
+
+    AsyncSocket* b = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(b);
+    b->Bind(initial_addr);
+    EXPECT_EQ(b->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+
+    EXPECT_EQ(0, a->Connect(b->GetLocalAddress()));
+    EXPECT_EQ(0, b->Connect(a->GetLocalAddress()));
+
+    ss_->ProcessMessagesUntilIdle();
+
+    EXPECT_TRUE(sink.Check(a, testing::SSE_OPEN));
+    EXPECT_EQ(a->GetState(), AsyncSocket::CS_CONNECTED);
+    EXPECT_EQ(a->GetRemoteAddress(), b->GetLocalAddress());
+
+    EXPECT_TRUE(sink.Check(b, testing::SSE_OPEN));
+    EXPECT_EQ(b->GetState(), AsyncSocket::CS_CONNECTED);
+    EXPECT_EQ(b->GetRemoteAddress(), a->GetLocalAddress());
+
+    EXPECT_EQ(1, a->Send("a", 1));
+    b->Close();
+    EXPECT_EQ(1, a->Send("b", 1));
+
+    ss_->ProcessMessagesUntilIdle();
+
+    char buffer[10];
+    EXPECT_FALSE(sink.Check(b, testing::SSE_READ));
+    EXPECT_EQ(-1, b->Recv(buffer, 10));
+
+    EXPECT_TRUE(sink.Check(a, testing::SSE_CLOSE));
+    EXPECT_EQ(a->GetState(), AsyncSocket::CS_CLOSED);
+    EXPECT_EQ(a->GetRemoteAddress(), kEmptyAddr);
+
+    EXPECT_FALSE(sink.Check(b, testing::SSE_CLOSE));  // No signal for Closer
+    EXPECT_EQ(b->GetState(), AsyncSocket::CS_CLOSED);
+    EXPECT_EQ(b->GetRemoteAddress(), kEmptyAddr);
+  }
+
+  void TcpSendTest(const SocketAddress& initial_addr) {
+    testing::StreamSink sink;
+    const SocketAddress kEmptyAddr;
+
+    // Connect two sockets
+    AsyncSocket* a = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(a);
+    a->Bind(initial_addr);
+    EXPECT_EQ(a->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+
+    AsyncSocket* b = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(b);
+    b->Bind(initial_addr);
+    EXPECT_EQ(b->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+
+    EXPECT_EQ(0, a->Connect(b->GetLocalAddress()));
+    EXPECT_EQ(0, b->Connect(a->GetLocalAddress()));
+
+    ss_->ProcessMessagesUntilIdle();
+
+    const size_t kBufferSize = 2000;
+    ss_->set_send_buffer_capacity(kBufferSize);
+    ss_->set_recv_buffer_capacity(kBufferSize);
+
+    const size_t kDataSize = 5000;
+    char send_buffer[kDataSize], recv_buffer[kDataSize];
+    for (size_t i = 0; i < kDataSize; ++i) send_buffer[i] = i;
+    memset(recv_buffer, 0, sizeof(recv_buffer));
+    size_t send_pos = 0, recv_pos = 0;
+
+    // Can't send more than send buffer in one write
+    int result = a->Send(send_buffer + send_pos, kDataSize - send_pos);
+    EXPECT_EQ(static_cast<int>(kBufferSize), result);
+    send_pos += result;
+
+    ss_->ProcessMessagesUntilIdle();
+    EXPECT_FALSE(sink.Check(a, testing::SSE_WRITE));
+    EXPECT_TRUE(sink.Check(b, testing::SSE_READ));
+
+    // Receive buffer is already filled, fill send buffer again
+    result = a->Send(send_buffer + send_pos, kDataSize - send_pos);
+    EXPECT_EQ(static_cast<int>(kBufferSize), result);
+    send_pos += result;
+
+    ss_->ProcessMessagesUntilIdle();
+    EXPECT_FALSE(sink.Check(a, testing::SSE_WRITE));
+    EXPECT_FALSE(sink.Check(b, testing::SSE_READ));
+
+    // No more room in send or receive buffer
+    result = a->Send(send_buffer + send_pos, kDataSize - send_pos);
+    EXPECT_EQ(-1, result);
+    EXPECT_TRUE(a->IsBlocking());
+
+    // Read a subset of the data
+    result = b->Recv(recv_buffer + recv_pos, 500);
+    EXPECT_EQ(500, result);
+    recv_pos += result;
+
+    ss_->ProcessMessagesUntilIdle();
+    EXPECT_TRUE(sink.Check(a, testing::SSE_WRITE));
+    EXPECT_TRUE(sink.Check(b, testing::SSE_READ));
+
+    // Room for more on the sending side
+    result = a->Send(send_buffer + send_pos, kDataSize - send_pos);
+    EXPECT_EQ(500, result);
+    send_pos += result;
+
+    // Empty the recv buffer
+    while (true) {
+      result = b->Recv(recv_buffer + recv_pos, kDataSize - recv_pos);
+      if (result < 0) {
+        EXPECT_EQ(-1, result);
+        EXPECT_TRUE(b->IsBlocking());
+        break;
+      }
+      recv_pos += result;
+    }
+
+    ss_->ProcessMessagesUntilIdle();
+    EXPECT_TRUE(sink.Check(b, testing::SSE_READ));
+
+    // Continue to empty the recv buffer
+    while (true) {
+      result = b->Recv(recv_buffer + recv_pos, kDataSize - recv_pos);
+      if (result < 0) {
+        EXPECT_EQ(-1, result);
+        EXPECT_TRUE(b->IsBlocking());
+        break;
+      }
+      recv_pos += result;
+    }
+
+    // Send last of the data
+    result = a->Send(send_buffer + send_pos, kDataSize - send_pos);
+    EXPECT_EQ(500, result);
+    send_pos += result;
+
+    ss_->ProcessMessagesUntilIdle();
+    EXPECT_TRUE(sink.Check(b, testing::SSE_READ));
+
+    // Receive the last of the data
+    while (true) {
+      result = b->Recv(recv_buffer + recv_pos, kDataSize - recv_pos);
+      if (result < 0) {
+        EXPECT_EQ(-1, result);
+        EXPECT_TRUE(b->IsBlocking());
+        break;
+      }
+      recv_pos += result;
+    }
+
+    ss_->ProcessMessagesUntilIdle();
+    EXPECT_FALSE(sink.Check(b, testing::SSE_READ));
+
+    // The received data matches the sent data
+    EXPECT_EQ(kDataSize, send_pos);
+    EXPECT_EQ(kDataSize, recv_pos);
+    EXPECT_EQ(0, memcmp(recv_buffer, send_buffer, kDataSize));
+  }
+
+  void TcpSendsPacketsInOrderTest(const SocketAddress& initial_addr) {
+    const SocketAddress kEmptyAddr;
+
+    // Connect two sockets
+    AsyncSocket* a = ss_->CreateAsyncSocket(SOCK_STREAM);
+    AsyncSocket* b = ss_->CreateAsyncSocket(SOCK_STREAM);
+    a->Bind(initial_addr);
+    EXPECT_EQ(a->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+
+    b->Bind(initial_addr);
+    EXPECT_EQ(b->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+
+    EXPECT_EQ(0, a->Connect(b->GetLocalAddress()));
+    EXPECT_EQ(0, b->Connect(a->GetLocalAddress()));
+    ss_->ProcessMessagesUntilIdle();
+
+    // First, deliver all packets in 0 ms.
+    char buffer[2] = { 0, 0 };
+    const size_t cNumPackets = 10;
+    for (size_t i = 0; i < cNumPackets; ++i) {
+      buffer[0] = '0' + i;
+      EXPECT_EQ(1, a->Send(buffer, 1));
+    }
+
+    ss_->ProcessMessagesUntilIdle();
+
+    for (size_t i = 0; i < cNumPackets; ++i) {
+      EXPECT_EQ(1, b->Recv(buffer, sizeof(buffer)));
+      EXPECT_EQ(static_cast<char>('0' + i), buffer[0]);
+    }
+
+    // Next, deliver packets at random intervals
+    const uint32 mean = 50;
+    const uint32 stddev = 50;
+
+    ss_->set_delay_mean(mean);
+    ss_->set_delay_stddev(stddev);
+    ss_->UpdateDelayDistribution();
+
+    for (size_t i = 0; i < cNumPackets; ++i) {
+      buffer[0] = 'A' + i;
+      EXPECT_EQ(1, a->Send(buffer, 1));
+    }
+
+    ss_->ProcessMessagesUntilIdle();
+
+    for (size_t i = 0; i < cNumPackets; ++i) {
+      EXPECT_EQ(1, b->Recv(buffer, sizeof(buffer)));
+      EXPECT_EQ(static_cast<char>('A' + i), buffer[0]);
+    }
+  }
+
+  void BandwidthTest(const SocketAddress& initial_addr) {
+    AsyncSocket* send_socket = ss_->CreateAsyncSocket(SOCK_DGRAM);
+    AsyncSocket* recv_socket = ss_->CreateAsyncSocket(SOCK_DGRAM);
+    ASSERT_EQ(0, send_socket->Bind(initial_addr));
+    ASSERT_EQ(0, recv_socket->Bind(initial_addr));
+    EXPECT_EQ(send_socket->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+    EXPECT_EQ(recv_socket->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+    ASSERT_EQ(0, send_socket->Connect(recv_socket->GetLocalAddress()));
+
+    uint32 bandwidth = 64 * 1024;
+    ss_->set_bandwidth(bandwidth);
+
+    Thread* pthMain = Thread::Current();
+    Sender sender(pthMain, send_socket, 80 * 1024);
+    Receiver receiver(pthMain, recv_socket, bandwidth);
+
+    pthMain->ProcessMessages(5000);
+    sender.done = true;
+    pthMain->ProcessMessages(5000);
+
+    ASSERT_TRUE(receiver.count >= 5 * 3 * bandwidth / 4);
+    ASSERT_TRUE(receiver.count <= 6 * bandwidth);  // queue could drain for 1s
+
+    ss_->set_bandwidth(0);
+  }
+
+  void DelayTest(const SocketAddress& initial_addr) {
+    time_t seed = ::time(NULL);
+    LOG(LS_VERBOSE) << "seed = " << seed;
+    srand(seed);
+
+    const uint32 mean = 2000;
+    const uint32 stddev = 500;
+
+    ss_->set_delay_mean(mean);
+    ss_->set_delay_stddev(stddev);
+    ss_->UpdateDelayDistribution();
+
+    AsyncSocket* send_socket = ss_->CreateAsyncSocket(SOCK_DGRAM);
+    AsyncSocket* recv_socket = ss_->CreateAsyncSocket(SOCK_DGRAM);
+    ASSERT_EQ(0, send_socket->Bind(initial_addr));
+    ASSERT_EQ(0, recv_socket->Bind(initial_addr));
+    EXPECT_EQ(send_socket->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+    EXPECT_EQ(recv_socket->GetLocalAddress().ipaddr().family(),
+              initial_addr.ipaddr().family());
+    ASSERT_EQ(0, send_socket->Connect(recv_socket->GetLocalAddress()));
+
+    Thread* pthMain = Thread::Current();
+    // Avg packet size is 2K, so at 200KB/s for 10s, we should see about
+    // 1000 packets, which is necessary to get a good distribution.
+    Sender sender(pthMain, send_socket, 100 * 2 * 1024);
+    Receiver receiver(pthMain, recv_socket, 0);
+
+    pthMain->ProcessMessages(10000);
+    sender.done = receiver.done = true;
+    ss_->ProcessMessagesUntilIdle();
+
+    const double sample_mean = receiver.sum / receiver.samples;
+    double num =
+        receiver.samples * receiver.sum_sq - receiver.sum * receiver.sum;
+    double den = receiver.samples * (receiver.samples - 1);
+    const double sample_stddev = std::sqrt(num / den);
+    LOG(LS_VERBOSE) << "mean=" << sample_mean << " stddev=" << sample_stddev;
+
+    EXPECT_LE(500u, receiver.samples);
+    // We initially used a 0.1 fudge factor, but on the build machine, we
+    // have seen the value differ by as much as 0.13.
+    EXPECT_NEAR(mean, sample_mean, 0.15 * mean);
+    EXPECT_NEAR(stddev, sample_stddev, 0.15 * stddev);
+
+    ss_->set_delay_mean(0);
+    ss_->set_delay_stddev(0);
+    ss_->UpdateDelayDistribution();
+  }
+
+  // Test cross-family communication between a client bound to client_addr and a
+  // server bound to server_addr. shouldSucceed indicates if communication is
+  // expected to work or not.
+  void CrossFamilyConnectionTest(const SocketAddress& client_addr,
+                                 const SocketAddress& server_addr,
+                                 bool shouldSucceed) {
+    testing::StreamSink sink;
+    SocketAddress accept_address;
+    const SocketAddress kEmptyAddr;
+
+    // Client gets a IPv4 address
+    AsyncSocket* client = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(client);
+    EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED);
+    EXPECT_EQ(client->GetLocalAddress(), kEmptyAddr);
+    client->Bind(client_addr);
+
+    // Server gets a non-mapped non-any IPv6 address.
+    // IPv4 sockets should not be able to connect to this.
+    AsyncSocket* server = ss_->CreateAsyncSocket(SOCK_STREAM);
+    sink.Monitor(server);
+    server->Bind(server_addr);
+    server->Listen(5);
+
+    if (shouldSucceed) {
+      EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+      ss_->ProcessMessagesUntilIdle();
+      EXPECT_TRUE(sink.Check(server, testing::SSE_READ));
+      Socket* accepted = server->Accept(&accept_address);
+      EXPECT_TRUE(NULL != accepted);
+      EXPECT_NE(kEmptyAddr, accept_address);
+      ss_->ProcessMessagesUntilIdle();
+      EXPECT_TRUE(sink.Check(client, testing::SSE_OPEN));
+      EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress());
+    } else {
+      // Check that the connection failed.
+      EXPECT_EQ(-1, client->Connect(server->GetLocalAddress()));
+      ss_->ProcessMessagesUntilIdle();
+
+      EXPECT_FALSE(sink.Check(server, testing::SSE_READ));
+      EXPECT_TRUE(NULL == server->Accept(&accept_address));
+      EXPECT_EQ(accept_address, kEmptyAddr);
+      EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED);
+      EXPECT_FALSE(sink.Check(client, testing::SSE_OPEN));
+      EXPECT_EQ(client->GetRemoteAddress(), kEmptyAddr);
+    }
+  }
+
+  // Test cross-family datagram sending between a client bound to client_addr
+  // and a server bound to server_addr. shouldSucceed indicates if sending is
+  // expected to succed or not.
+  void CrossFamilyDatagramTest(const SocketAddress& client_addr,
+                               const SocketAddress& server_addr,
+                               bool shouldSucceed) {
+    AsyncSocket* socket = ss_->CreateAsyncSocket(SOCK_DGRAM);
+    socket->Bind(server_addr);
+    SocketAddress bound_server_addr = socket->GetLocalAddress();
+    TestClient* client1 = new TestClient(new AsyncUDPSocket(socket));
+
+    AsyncSocket* socket2 = ss_->CreateAsyncSocket(SOCK_DGRAM);
+    socket2->Bind(client_addr);
+    TestClient* client2 = new TestClient(new AsyncUDPSocket(socket2));
+    SocketAddress client2_addr;
+
+    if (shouldSucceed) {
+      EXPECT_EQ(3, client2->SendTo("foo", 3, bound_server_addr));
+      EXPECT_TRUE(client1->CheckNextPacket("foo", 3, &client2_addr));
+      SocketAddress client1_addr;
+      EXPECT_EQ(6, client1->SendTo("bizbaz", 6, client2_addr));
+      EXPECT_TRUE(client2->CheckNextPacket("bizbaz", 6, &client1_addr));
+      EXPECT_EQ(client1_addr, bound_server_addr);
+    } else {
+      EXPECT_EQ(-1, client2->SendTo("foo", 3, bound_server_addr));
+      EXPECT_FALSE(client1->CheckNextPacket("foo", 3, 0));
+    }
+  }
+
+ protected:
+  virtual void SetUp() {
+    Thread::Current()->set_socketserver(ss_);
+  }
+  virtual void TearDown() {
+    Thread::Current()->set_socketserver(NULL);
+  }
+
+  VirtualSocketServer* ss_;
+  const SocketAddress kIPv4AnyAddress;
+  const SocketAddress kIPv6AnyAddress;
+};
+
+TEST_F(VirtualSocketServerTest, basic_v4) {
+  SocketAddress ipv4_test_addr(IPAddress(INADDR_ANY), 5000);
+  BasicTest(ipv4_test_addr);
 }
 
-TEST_F(VirtualSocketServerTest, delay) {
-  time_t seed = ::time(NULL);
-  LOG(LS_VERBOSE) << "seed = " << seed;
-  srand(seed);
+TEST_F(VirtualSocketServerTest, basic_v6) {
+  SocketAddress ipv6_test_addr(IPAddress(in6addr_any), 5000);
+  BasicTest(ipv6_test_addr);
+}
 
-  const uint32 mean = 2000;
-  const uint32 stddev = 500;
+TEST_F(VirtualSocketServerTest, connect_v4) {
+  ConnectTest(kIPv4AnyAddress);
+}
 
-  ss_->set_delay_mean(mean);
-  ss_->set_delay_stddev(stddev);
-  ss_->UpdateDelayDistribution();
+TEST_F(VirtualSocketServerTest, connect_v6) {
+  ConnectTest(kIPv6AnyAddress);
+}
 
-  AsyncSocket* send_socket = ss_->CreateAsyncSocket(SOCK_DGRAM);
-  AsyncSocket* recv_socket = ss_->CreateAsyncSocket(SOCK_DGRAM);
-  ASSERT_EQ(0, send_socket->Bind(SocketAddress(IPAddress(INADDR_ANY), 1000)));
-  ASSERT_EQ(0, recv_socket->Bind(SocketAddress(IPAddress(INADDR_ANY), 1000)));
-  ASSERT_EQ(0, send_socket->Connect(recv_socket->GetLocalAddress()));
+TEST_F(VirtualSocketServerTest, connect_to_non_listener_v4) {
+  ConnectToNonListenerTest(kIPv4AnyAddress);
+}
 
-  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);
+TEST_F(VirtualSocketServerTest, connect_to_non_listener_v6) {
+  ConnectToNonListenerTest(kIPv6AnyAddress);
+}
 
-  pthMain->ProcessMessages(10000);
-  sender.done = receiver.done = true;
-  ss_->ProcessMessagesUntilIdle();
+TEST_F(VirtualSocketServerTest, close_during_connect_v4) {
+  CloseDuringConnectTest(kIPv4AnyAddress);
+}
 
-  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;
+TEST_F(VirtualSocketServerTest, close_during_connect_v6) {
+  CloseDuringConnectTest(kIPv6AnyAddress);
+}
 
-  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);
+TEST_F(VirtualSocketServerTest, close_v4) {
+  CloseTest(kIPv4AnyAddress);
+}
 
-  ss_->set_delay_mean(0);
-  ss_->set_delay_stddev(0);
-  ss_->UpdateDelayDistribution();
+TEST_F(VirtualSocketServerTest, close_v6) {
+  CloseTest(kIPv6AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, tcp_send_v4) {
+  TcpSendTest(kIPv4AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, tcp_send_v6) {
+  TcpSendTest(kIPv6AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, TcpSendsPacketsInOrder_v4) {
+  TcpSendsPacketsInOrderTest(kIPv4AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, TcpSendsPacketsInOrder_v6) {
+  TcpSendsPacketsInOrderTest(kIPv6AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, bandwidth_v4) {
+  SocketAddress ipv4_test_addr(IPAddress(INADDR_ANY), 1000);
+  BandwidthTest(ipv4_test_addr);
+}
+
+TEST_F(VirtualSocketServerTest, bandwidth_v6) {
+  SocketAddress ipv6_test_addr(IPAddress(in6addr_any), 1000);
+  BandwidthTest(ipv6_test_addr);
+}
+
+TEST_F(VirtualSocketServerTest, delay_v4) {
+  SocketAddress ipv4_test_addr(IPAddress(INADDR_ANY), 1000);
+  DelayTest(ipv4_test_addr);
+}
+
+TEST_F(VirtualSocketServerTest, delay_v6) {
+  SocketAddress ipv6_test_addr(IPAddress(in6addr_any), 1000);
+  DelayTest(ipv6_test_addr);
+}
+
+// Works, receiving socket sees 127.0.0.2.
+TEST_F(VirtualSocketServerTest, CanConnectFromMappedIPv6ToIPv4Any) {
+  CrossFamilyConnectionTest(SocketAddress("::ffff:127.0.0.2", 0),
+                            SocketAddress("0.0.0.0", 5000),
+                            true);
+}
+
+// Fails.
+TEST_F(VirtualSocketServerTest, CantConnectFromUnMappedIPv6ToIPv4Any) {
+  CrossFamilyConnectionTest(SocketAddress("::2", 0),
+                            SocketAddress("0.0.0.0", 5000),
+                            false);
+}
+
+// Fails.
+TEST_F(VirtualSocketServerTest, CantConnectFromUnMappedIPv6ToMappedIPv6) {
+  CrossFamilyConnectionTest(SocketAddress("::2", 0),
+                            SocketAddress("::ffff:127.0.0.1", 5000),
+                            false);
+}
+
+// Works. receiving socket sees ::ffff:127.0.0.2.
+TEST_F(VirtualSocketServerTest, CanConnectFromIPv4ToIPv6Any) {
+  CrossFamilyConnectionTest(SocketAddress("127.0.0.2", 0),
+                            SocketAddress("::", 5000),
+                            true);
+}
+
+// Fails.
+TEST_F(VirtualSocketServerTest, CantConnectFromIPv4ToUnMappedIPv6) {
+  CrossFamilyConnectionTest(SocketAddress("127.0.0.2", 0),
+                            SocketAddress("::1", 5000),
+                            false);
+}
+
+// Works. Receiving socket sees ::ffff:127.0.0.1.
+TEST_F(VirtualSocketServerTest, CanConnectFromIPv4ToMappedIPv6) {
+  CrossFamilyConnectionTest(SocketAddress("127.0.0.1", 0),
+                            SocketAddress("::ffff:127.0.0.2", 5000),
+                            true);
+}
+
+// Works, receiving socket sees a result from GetNextIP.
+TEST_F(VirtualSocketServerTest, CanConnectFromUnboundIPv6ToIPv4Any) {
+  CrossFamilyConnectionTest(SocketAddress("::", 0),
+                            SocketAddress("0.0.0.0", 5000),
+                            true);
+}
+
+// Works, receiving socket sees whatever GetNextIP gave the client.
+TEST_F(VirtualSocketServerTest, CanConnectFromUnboundIPv4ToIPv6Any) {
+  CrossFamilyConnectionTest(SocketAddress(),
+                            SocketAddress("::", 5000),
+                            true);
+}
+
+TEST_F(VirtualSocketServerTest, CanSendDatagramFromUnboundIPv4ToIPv6Any) {
+  CrossFamilyDatagramTest(SocketAddress(),
+                          SocketAddress("::", 5000),
+                          true);
+}
+
+TEST_F(VirtualSocketServerTest, CanSendDatagramFromMappedIPv6ToIPv4Any) {
+  CrossFamilyDatagramTest(SocketAddress("::ffff:127.0.0.1", 0),
+                          SocketAddress("0.0.0.0", 5000),
+                          true);
+}
+
+TEST_F(VirtualSocketServerTest, CantSendDatagramFromUnMappedIPv6ToIPv4Any) {
+  CrossFamilyDatagramTest(SocketAddress("::2", 0),
+                          SocketAddress("0.0.0.0", 5000),
+                          false);
+}
+
+TEST_F(VirtualSocketServerTest, CantSendDatagramFromUnMappedIPv6ToMappedIPv6) {
+  CrossFamilyDatagramTest(SocketAddress("::2", 0),
+                          SocketAddress("::ffff:127.0.0.1", 5000),
+                          false);
+}
+
+TEST_F(VirtualSocketServerTest, CanSendDatagramFromIPv4ToIPv6Any) {
+  CrossFamilyDatagramTest(SocketAddress("127.0.0.2", 0),
+                          SocketAddress("::", 5000),
+                          true);
+}
+
+TEST_F(VirtualSocketServerTest, CantSendDatagramFromIPv4ToUnMappedIPv6) {
+  CrossFamilyDatagramTest(SocketAddress("127.0.0.2", 0),
+                          SocketAddress("::1", 5000),
+                          false);
+}
+
+TEST_F(VirtualSocketServerTest, CanSendDatagramFromIPv4ToMappedIPv6) {
+  CrossFamilyDatagramTest(SocketAddress("127.0.0.1", 0),
+                          SocketAddress("::ffff:127.0.0.2", 5000),
+                          true);
+}
+
+TEST_F(VirtualSocketServerTest, CanSendDatagramFromUnboundIPv6ToIPv4Any) {
+  CrossFamilyDatagramTest(SocketAddress("::", 0),
+                          SocketAddress("0.0.0.0", 5000),
+                          true);
+}
+
+TEST_F(VirtualSocketServerTest, CreatesStandardDistribution) {
+  const uint32 kTestMean[] = { 10, 100, 333, 1000 };
+  const double kTestDev[] = { 0.25, 0.1, 0.01 };
+  // TODO: The current code only works for 1000 data points or more.
+  const uint32 kTestSamples[] = { /*10, 100,*/ 1000 };
+  for (size_t midx = 0; midx < ARRAY_SIZE(kTestMean); ++midx) {
+    for (size_t didx = 0; didx < ARRAY_SIZE(kTestDev); ++didx) {
+      for (size_t sidx = 0; sidx < ARRAY_SIZE(kTestSamples); ++sidx) {
+        ASSERT_LT(0u, kTestSamples[sidx]);
+        const uint32 kStdDev =
+            static_cast<uint32>(kTestDev[didx] * kTestMean[midx]);
+        VirtualSocketServer::Function* f =
+            VirtualSocketServer::CreateDistribution(kTestMean[midx],
+                                                    kStdDev,
+                                                    kTestSamples[sidx]);
+        ASSERT_TRUE(NULL != f);
+        ASSERT_EQ(kTestSamples[sidx], f->size());
+        double sum = 0;
+        for (uint32 i = 0; i < f->size(); ++i) {
+          sum += (*f)[i].second;
+        }
+        const double mean = sum / f->size();
+        double sum_sq_dev = 0;
+        for (uint32 i = 0; i < f->size(); ++i) {
+          double dev = (*f)[i].second - mean;
+          sum_sq_dev += dev * dev;
+        }
+        const double stddev = std::sqrt(sum_sq_dev / f->size());
+        EXPECT_NEAR(kTestMean[midx], mean, 0.1 * kTestMean[midx])
+          << "M=" << kTestMean[midx]
+          << " SD=" << kStdDev
+          << " N=" << kTestSamples[sidx];
+        EXPECT_NEAR(kStdDev, stddev, 0.1 * kStdDev)
+          << "M=" << kTestMean[midx]
+          << " SD=" << kStdDev
+          << " N=" << kTestSamples[sidx];
+        delete f;
+      }
+    }
+  }
 }
diff --git a/talk/base/virtualsocketserver.cc b/talk/base/virtualsocketserver.cc
index c9c9f0b..c18ffb6 100644
--- a/talk/base/virtualsocketserver.cc
+++ b/talk/base/virtualsocketserver.cc
@@ -42,6 +42,16 @@
 #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;
@@ -105,7 +115,7 @@
   VirtualSocket(VirtualSocketServer* server, int type, bool async)
       : server_(server), type_(type), async_(async), state_(CS_CLOSED),
         listen_queue_(NULL), write_enabled_(false), network_size_(0),
-        recv_buffer_size_(0), bound_(false) {
+        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
   }
@@ -144,6 +154,7 @@
       error_ = EADDRINUSE;
     } else {
       bound_ = true;
+      was_any_ = addr.IsAnyIP();
     }
     return result;
   }
@@ -317,6 +328,8 @@
 
       // 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();
@@ -408,6 +421,9 @@
     }
   }
 
+  bool was_any() { return was_any_; }
+  void set_was_any(bool was_any) { was_any_ = was_any; }
+
  private:
   struct NetworkEntry {
     uint32 size;
@@ -426,7 +442,13 @@
       return -1;
     }
     if (local_addr_.IsAny()) {
-      int result = Bind(SocketAddress());
+      // 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;
       }
@@ -514,6 +536,12 @@
   // 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_;
 
@@ -522,7 +550,8 @@
 
 VirtualSocketServer::VirtualSocketServer(SocketServer* ss)
     : server_(ss), server_owned_(false), msg_queue_(NULL), stop_on_idle_(false),
-      network_delay_(Time()), next_ip_(1), next_port_(kFirstEphemeralPort),
+      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),
@@ -545,8 +574,18 @@
   }
 }
 
-uint32 VirtualSocketServer::GetNextIP() {
-  return next_ip_++;
+IPAddress VirtualSocketServer::GetNextIP(int family) {
+  if (family == AF_INET) {
+    IPAddress next_ip(next_ipv4_);
+    next_ipv4_.s_addr = htonl(ntohl(next_ipv4_.s_addr) + 1);
+    return next_ip;
+  } else if (family == AF_INET6) {
+    IPAddress next_ip(next_ipv6_);
+    uint32* as_ints = reinterpret_cast<uint32*>(&next_ipv6_.s6_addr);
+    as_ints[3] += 1;
+    return next_ip;
+  }
+  return IPAddress();
 }
 
 uint16 VirtualSocketServer::GetNextPort() {
@@ -611,7 +650,10 @@
   ASSERT(!IPIsAny(addr.ipaddr()));
   ASSERT(addr.port() != 0);
 
-  AddressMap::value_type entry(addr, socket);
+  // 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;
 }
 
@@ -619,8 +661,9 @@
   ASSERT(NULL != socket);
 
   if (IPIsAny(addr->ipaddr())) {
-    // TODO: An IPv6-ish version of this?
-    addr->SetIP(IPAddress(GetNextIP()));
+    addr->SetIP(GetNextIP(addr->ipaddr().family()));
+  } else {
+    addr->SetIP(addr->ipaddr().Normalized());
   }
 
   if (addr->port() == 0) {
@@ -636,14 +679,18 @@
 }
 
 VirtualSocket* VirtualSocketServer::LookupBinding(const SocketAddress& addr) {
-  AddressMap::iterator it = bindings_->find(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) {
-  ASSERT((*bindings_)[addr] == socket);
-  bindings_->erase(bindings_->find(addr));
+  SocketAddress normalized(addr.ipaddr().Normalized(),
+                           addr.port());
+  ASSERT((*bindings_)[normalized] == socket);
+  bindings_->erase(bindings_->find(normalized));
   return 0;
 }
 
@@ -652,7 +699,11 @@
                                         VirtualSocket* remote_socket) {
   // Add this socket pair to our routing table. This will allow
   // multiple clients to connect to the same server address.
-  SocketAddressPair address_pair(local, 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_->insert(std::pair<SocketAddressPair,
                        VirtualSocket*>(address_pair, remote_socket));
 }
@@ -660,14 +711,22 @@
 VirtualSocket* VirtualSocketServer::LookupConnection(
     const SocketAddress& local,
     const SocketAddress& remote) {
-  SocketAddressPair address_pair(local, 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) {
-  SocketAddressPair address_pair(local, 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);
 }
 
@@ -679,7 +738,13 @@
                                  const SocketAddress& remote_addr,
                                  bool use_delay) {
   uint32 delay = use_delay ? GetRandomTransitDelay() : 0;
-  if (VirtualSocket* remote = LookupBinding(remote_addr)) {
+  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));
@@ -710,10 +775,24 @@
 
   VirtualSocket* recipient = LookupBinding(remote_addr);
   if (!recipient) {
+    // Make a fake recipient for address family checking.
+    scoped_ptr<VirtualSocket> dummy_socket(CreateSocketInternal(SOCK_DGRAM));
+    dummy_socket->SetLocalAddress(remote_addr);
+    if (!CanInteractWith(socket, dummy_socket.get())) {
+      LOG(LS_VERBOSE) << "Incompatible address families: "
+                      << socket->GetLocalAddress() << " and " << remote_addr;
+      return -1;
+    }
     LOG(LS_VERBOSE) << "No one listening at " << remote_addr;
     return static_cast<int>(data_size);
   }
 
+  if (!CanInteractWith(socket, recipient)) {
+    LOG(LS_VERBOSE) << "Incompatible address families: "
+                    << socket->GetLocalAddress() << " and " << remote_addr;
+    return -1;
+  }
+
   CritScope cs(&socket->crit_);
 
   uint32 cur_time = Time();
@@ -979,4 +1058,45 @@
   }
 }
 
+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
index 239ea9b..d35d586 100644
--- a/talk/base/virtualsocketserver.h
+++ b/talk/base/virtualsocketserver.h
@@ -42,7 +42,8 @@
 
 // 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.
+// 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.
@@ -124,7 +125,7 @@
 
  protected:
   // Returns a new IP not used before in this network.
-  uint32 GetNextIP();
+  IPAddress GetNextIP(int family);
   uint16 GetNextPort();
 
   VirtualSocket* CreateSocketInternal(int type);
@@ -192,6 +193,24 @@
   // 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;
 
@@ -203,7 +222,8 @@
   MessageQueue* msg_queue_;
   bool stop_on_idle_;
   uint32 network_delay_;
-  uint32 next_ip_;
+  in_addr next_ipv4_;
+  in6_addr next_ipv6_;
   uint16 next_port_;
   AddressMap* bindings_;
   ConnectionMap* connections_;
diff --git a/talk/examples/call/callclient.cc b/talk/examples/call/callclient.cc
index 9b8fb1a..89f964b 100644
--- a/talk/examples/call/callclient.cc
+++ b/talk/examples/call/callclient.cc
@@ -1116,22 +1116,24 @@
                                       cricket::Session* session,
                                       const cricket::MediaStreams& added,
                                       const cricket::MediaStreams& removed) {
-  for (std::vector<cricket::StreamParams>::const_iterator
-       it = removed.video().begin(); it != removed.video().end(); ++it) {
-    RemoveStaticRenderedView(it->first_ssrc());
-  }
-
-  if (render_) {
+  if (call->video()) {
     for (std::vector<cricket::StreamParams>::const_iterator
-         it = added.video().begin(); it != added.video().end(); ++it) {
-      // TODO: Make dimensions and positions more configurable.
-      int offset = (50 * static_views_accumulated_count_) % 300;
-      AddStaticRenderedView(session, it->first_ssrc(), 640, 400, 30,
-                            offset, offset);
+         it = removed.video().begin(); it != removed.video().end(); ++it) {
+      RemoveStaticRenderedView(it->first_ssrc());
     }
-  }
 
-  SendViewRequest(session);
+    if (render_) {
+      for (std::vector<cricket::StreamParams>::const_iterator
+           it = added.video().begin(); it != added.video().end(); ++it) {
+        // TODO: Make dimensions and positions more configurable.
+        int offset = (50 * static_views_accumulated_count_) % 300;
+        AddStaticRenderedView(session, it->first_ssrc(), 640, 400, 30,
+                              offset, offset);
+      }
+    }
+
+    SendViewRequest(session);
+  }
 }
 
 // TODO: Would these methods to add and remove views make
diff --git a/talk/examples/peerconnection/client/main.cc b/talk/examples/peerconnection/client/main.cc
index 4ae0da0..8d2bdc9 100644
--- a/talk/examples/peerconnection/client/main.cc
+++ b/talk/examples/peerconnection/client/main.cc
@@ -25,12 +25,9 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include <windows.h>
-
 #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 "system_wrappers/source/trace_impl.h"
 #include "talk/base/win32socketinit.h"
 
 
@@ -38,10 +35,6 @@
                     wchar_t* cmd_line, int cmd_show) {
   talk_base::EnsureWinsockInit();
 
-  webrtc::Trace::CreateTrace();
-  webrtc::Trace::SetTraceFile("peerconnection_client.log");
-  webrtc::Trace::SetLevelFilter(webrtc::kTraceWarning);
-
   MainWnd wnd;
   if (!wnd.Create()) {
     ASSERT(false);
@@ -54,7 +47,7 @@
   // Main loop.
   MSG msg;
   BOOL gm;
-  while ((gm = ::GetMessage(&msg, NULL, 0, 0)) && gm != -1) {
+  while ((gm = ::GetMessage(&msg, NULL, 0, 0)) != 0 && gm != -1) {
     if (!wnd.PreTranslateMessage(&msg)) {
       ::TranslateMessage(&msg);
       ::DispatchMessage(&msg);
@@ -63,7 +56,7 @@
 
   if (conductor.connection_active() || client.is_connected()) {
     while ((conductor.connection_active() || client.is_connected()) &&
-           (gm = ::GetMessage(&msg, NULL, 0, 0)) && gm != -1) {
+           (gm = ::GetMessage(&msg, NULL, 0, 0)) != 0 && gm != -1) {
       if (!wnd.PreTranslateMessage(&msg)) {
         ::TranslateMessage(&msg);
         ::DispatchMessage(&msg);
diff --git a/talk/examples/peerconnection/client/main_wnd.cc b/talk/examples/peerconnection/client/main_wnd.cc
index e4e0539..367922e 100644
--- a/talk/examples/peerconnection/client/main_wnd.cc
+++ b/talk/examples/peerconnection/client/main_wnd.cc
@@ -31,6 +31,7 @@
 
 #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";
@@ -160,8 +161,9 @@
 }
 
 void MainWnd::SwitchToPeerList(const Peers& peers) {
-  remote_video_.reset();
-  local_video_.reset();
+  // Clean up buffers from a potential previous session.
+  local_renderer_wrapper_ = NULL;
+  remote_renderer_wrapper_ = NULL;
 
   LayoutConnectUI(false);
 
@@ -191,16 +193,18 @@
   ::MessageBoxA(handle(), text, caption, flags);
 }
 
-cricket::VideoRenderer* MainWnd::local_renderer() {
-  if (!local_video_.get())
-    local_video_.reset(new VideoRenderer(handle(), 1, 1));
-  return local_video_.get();
+webrtc::VideoRendererWrapperInterface* MainWnd::local_renderer() {
+  if (!local_renderer_wrapper_.get())
+    local_renderer_wrapper_  =
+        webrtc::CreateVideoRenderer(new VideoRenderer(handle(), 1, 1));
+  return local_renderer_wrapper_.get();
 }
 
-cricket::VideoRenderer* MainWnd::remote_renderer() {
-  if (!remote_video_.get())
-    remote_video_.reset(new VideoRenderer(handle(), 1, 1));
-  return remote_video_.get();
+webrtc::VideoRendererWrapperInterface* MainWnd::remote_renderer() {
+  if (!remote_renderer_wrapper_.get())
+    remote_renderer_wrapper_ =
+        webrtc::CreateVideoRenderer(new VideoRenderer(handle(), 1, 1));
+  return remote_renderer_wrapper_.get();
 }
 
 void MainWnd::QueueUIThreadCallback(int msg_id, void* data) {
@@ -215,15 +219,21 @@
   RECT rc;
   ::GetClientRect(handle(), &rc);
 
-  if (ui_ == STREAMING && remote_video_.get() && local_video_.get()) {
-    AutoLock<VideoRenderer> local_lock(local_video_.get());
-    AutoLock<VideoRenderer> remote_lock(remote_video_.get());
+  webrtc::VideoRendererWrapperInterface* renderer_wrapper = local_renderer();
+  VideoRenderer* local_renderer = renderer_wrapper ?
+      static_cast<VideoRenderer*>(renderer_wrapper->renderer()) : NULL;
+  renderer_wrapper = remote_renderer();
+  VideoRenderer* remote_renderer = renderer_wrapper ?
+      static_cast<VideoRenderer*>(renderer_wrapper->renderer()) : NULL;
+  if (ui_ == STREAMING && remote_renderer && local_renderer) {
+    AutoLock<VideoRenderer> local_lock(local_renderer);
+    AutoLock<VideoRenderer> remote_lock(remote_renderer);
 
-    const BITMAPINFO& bmi = remote_video_->bmi();
+    const BITMAPINFO& bmi = remote_renderer->bmi();
     int height = abs(bmi.bmiHeader.biHeight);
     int width = bmi.bmiHeader.biWidth;
 
-    const uint8* image = remote_video_->image();
+    const uint8* image = remote_renderer->image();
     if (image != NULL) {
       HDC dc_mem = ::CreateCompatibleDC(ps.hdc);
       ::SetStretchBltMode(dc_mem, HALFTONE);
@@ -247,7 +257,7 @@
       ::FillRect(dc_mem, &logical_rect, brush);
       ::DeleteObject(brush);
 
-      int max_unit = std::max(width, height);
+      int max_unit = (std::max)(width, height);
       int x = (logical_area.x / 2) - (width / 2);
       int y = (logical_area.y / 2) - (height / 2);
 
@@ -255,8 +265,8 @@
                     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_video_->bmi();
-        image = local_video_->image();
+        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,
@@ -285,7 +295,7 @@
       ::SetBkMode(ps.hdc, TRANSPARENT);
 
       std::string text(kConnecting);
-      if (!local_video_->image()) {
+      if (!local_renderer->image()) {
         text += kNoVideoStreams;
       } else {
         text += kNoIncomingStream;
diff --git a/talk/examples/peerconnection/client/main_wnd.h b/talk/examples/peerconnection/client/main_wnd.h
index 1807f4e..17243b3 100644
--- a/talk/examples/peerconnection/client/main_wnd.h
+++ b/talk/examples/peerconnection/client/main_wnd.h
@@ -107,8 +107,8 @@
                           bool is_error);
   virtual UI current_ui() { return ui_; }
 
-  virtual cricket::VideoRenderer* local_renderer();
-  virtual cricket::VideoRenderer* remote_renderer();
+  virtual webrtc::VideoRendererWrapperInterface* local_renderer();
+  virtual webrtc::VideoRendererWrapperInterface* remote_renderer();
 
   virtual void QueueUIThreadCallback(int msg_id, void* data);
 
@@ -187,8 +187,10 @@
   void HandleTabbing();
 
  private:
-  talk_base::scoped_ptr<VideoRenderer> remote_video_;
-  talk_base::scoped_ptr<VideoRenderer> local_video_;
+  talk_base::scoped_refptr<webrtc::VideoRendererWrapperInterface>
+      local_renderer_wrapper_;
+  talk_base::scoped_refptr<webrtc::VideoRendererWrapperInterface>
+      remote_renderer_wrapper_;
   UI ui_;
   HWND wnd_;
   DWORD ui_thread_id_;
diff --git a/talk/examples/peerconnection/server/server_test.html b/talk/examples/peerconnection/server/server_test.html
index be702d8..d7c2266 100644
--- a/talk/examples/peerconnection/server/server_test.html
+++ b/talk/examples/peerconnection/server/server_test.html
@@ -35,6 +35,26 @@
     if (data.search("OFFER") != -1) {
       // In loopback mode, replace the ROAP OFFER with ROAP ANSWER.
       data = data.replace("OFFER", "ANSWER");
+      // Keep only the first crypto line for each m line in the answer.
+      var mlineBegin = data.indexOf("m=", 0);
+      while (mlineBegin != -1) {
+        var cryptoBegin = data.indexOf("a=crypto:", mlineBegin);
+        if (cryptoBegin == -1) {
+          // No more crypto lines.
+          break;
+        }
+        // Skip the first crypto line.
+        cryptoBegin = data.indexOf("a=crypto:", cryptoBegin + 1);
+        // Update the mlineBegin to the the next m line.
+        mlineBegin = data.indexOf("m=", mlineBegin + 1);
+        while (cryptoBegin != -1 && cryptoBegin < mlineBegin) {
+          var cryptoEnd = data.indexOf("\\n", cryptoBegin);
+          var crypto = data.substring(cryptoBegin, cryptoEnd + 2);
+          data = data.replace(crypto, "");
+          // Search for the the next crypto line.
+          cryptoBegin = data.indexOf("a=crypto:", cryptoBegin + 1);
+        }
+      }
       var lines = data.split("\n");
       for (var i = 1; i < lines.length; ++i) {
         // Look for the offererSessionId and use it as answererSessionId
diff --git a/talk/main.scons b/talk/main.scons
index a58e115..15cbc63 100644
--- a/talk/main.scons
+++ b/talk/main.scons
@@ -100,6 +100,8 @@
     'no', 'or', 'pl', 'pt-BR', 'pt-PT', 'ro', 'ru', 'sk', 'sl', 'sr', 'sv',
     'ta', 'te', 'th', 'tl', 'tr', 'uk', 'ur', 'vi', 'zh-CN', 'zh-TW'])
 
+AddTargetGroup('all_breakpads', 'breakpad files can be built')
+
 #-------------------------------------------------------------------------------
 # W I N D O W S
 #
diff --git a/talk/p2p/base/session.cc b/talk/p2p/base/session.cc
index 2439682..aba5ac4 100644
--- a/talk/p2p/base/session.cc
+++ b/talk/p2p/base/session.cc
@@ -960,14 +960,18 @@
   }
 
   ContentInfos updated_contents = description_info.ClearContents();
-  ContentInfos::iterator it;
 
+  // TODO: Currently, reflector sends back
+  // video stream updates even for an audio-only call, which causes
+  // this to fail.  Put this back once reflector is fixed.
+  //
+  // ContentInfos::iterator it;
   // First, ensure all updates are valid before modifying remote_description_.
-  for (it = updated_contents.begin(); it != updated_contents.end(); ++it) {
-    if (remote_description()->GetContentByName(it->name) == NULL) {
-      return false;
-    }
-  }
+  // for (it = updated_contents.begin(); it != updated_contents.end(); ++it) {
+  //   if (remote_description()->GetContentByName(it->name) == NULL) {
+  //     return false;
+  //   }
+  // }
 
   // TODO: We used to replace contents from an update, but
   // that no longer works with partial updates.  We need to figure out
diff --git a/talk/session/phone/call.cc b/talk/session/phone/call.cc
index 1157519..0273353 100644
--- a/talk/session/phone/call.cc
+++ b/talk/session/phone/call.cc
@@ -607,53 +607,15 @@
     return;
   }
 
-  if (!SetSendResolutions(session, view_request)) {
-    LOG(LS_WARNING) << "Failed to set send resolutions.";
-  }
-}
-
-bool Call::SetSendResolutions(
-    Session *session, const ViewRequest& view_request) {
-  // TODO: Change resolution depending on ssrc.  For now,
-  // assume we have the same resolution for all, and change by setting
-  // the remote content to change the codecs, which changes the
-  // resolution.
-  //
-  // Eventually it should look something like this:
-  // VideoChannel *video_channel = GetVideoChannel(session);
-  // for (StaticVideoViews::const_iterator view =
-  //          view_request.static_video_views.begin();
-  //      view != view_request.static_video_views.end(); ++view) {
-  //   if (!video_channel->SetResolution(
-  //           view->ssrc, view->width, view->height, view->framerate)) {
-  //     LOG(LS_WARNING) <<
-  //         "Failed to set view request resolution of ssrc " << view->ssrc;
-  //   }
-  // }
-
   VideoChannel *video_channel = GetVideoChannel(session);
   if (video_channel == NULL) {
-    return false;
+    LOG(LS_WARNING) << "Ignore view request since we have no video channel.";
+    return;
   }
 
-  // If there are no views, set resolution to 0x0x0.
-  StaticVideoView target_view(0U, 0, 0, 0);
-  if (!view_request.static_video_views.empty()) {
-    target_view = view_request.static_video_views[0];
+  if (!video_channel->ApplyViewRequest(view_request)) {
+    LOG(LS_WARNING) << "Failed to ApplyViewRequest.";
   }
-  VideoContentDescription* target_video = new VideoContentDescription();
-  const VideoContentDescription* current_video =
-      GetFirstVideoContentDescription(session->remote_description());
-  for (VideoCodecs::const_iterator current_codec =
-           current_video->codecs().begin();
-       current_codec != current_video->codecs().end(); ++current_codec) {
-    VideoCodec target_codec = *current_codec;
-    target_codec.width = target_view.width;
-    target_codec.height = target_view.height;
-    target_codec.framerate = target_view.framerate;
-    target_video->AddCodec(target_codec);
-  }
-  return video_channel->SetRemoteContent(target_video, CA_UPDATE);
 }
 
 void FindStreamChanges(const std::vector<StreamParams>& streams,
@@ -668,7 +630,13 @@
         removed_streams->push_back(stream);
       }
     } else {
-      added_streams->push_back(*update);
+      // 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);
+      }
     }
   }
 }
diff --git a/talk/session/phone/call.h b/talk/session/phone/call.h
index 69d2941..e081c55 100644
--- a/talk/session/phone/call.h
+++ b/talk/session/phone/call.h
@@ -147,7 +147,6 @@
   void OnMediaMonitor(VideoChannel *channel, const VideoMediaInfo& info);
   VoiceChannel* GetVoiceChannel(Session *session);
   VideoChannel* GetVideoChannel(Session *session);
-  bool SetSendResolutions(Session *session, const ViewRequest& view_request);
   bool UpdateVoiceChannelRemoteContent(Session *session,
                                        const AudioContentDescription* audio);
   bool UpdateVideoChannelRemoteContent(Session *session,
diff --git a/talk/session/phone/channel.cc b/talk/session/phone/channel.cc
index 9e39088..1481887 100644
--- a/talk/session/phone/channel.cc
+++ b/talk/session/phone/channel.cc
@@ -33,16 +33,143 @@
 #include "talk/base/logging.h"
 #include "talk/p2p/base/transportchannel.h"
 #include "talk/session/phone/channelmanager.h"
+#include "talk/session/phone/mediamessages.h"
 #include "talk/session/phone/mediasessionclient.h"
 #include "talk/session/phone/rtcpmuxfilter.h"
 #include "talk/session/phone/rtputils.h"
 
 namespace cricket {
 
+enum {
+  MSG_ENABLE = 1,
+  MSG_DISABLE = 2,
+  MSG_MUTE = 3,
+  MSG_UNMUTE = 4,
+  MSG_SETREMOTECONTENT = 5,
+  MSG_SETLOCALCONTENT = 6,
+  MSG_EARLYMEDIATIMEOUT = 8,
+  MSG_PRESSDTMF = 9,
+  MSG_SETRENDERER = 10,
+  MSG_ADDRECVSTREAM = 11,
+  MSG_REMOVERECVSTREAM = 12,
+  MSG_SETRINGBACKTONE = 13,
+  MSG_PLAYRINGBACKTONE = 14,
+  MSG_SETMAXSENDBANDWIDTH = 15,
+  MSG_ADDSCREENCAST = 16,
+  MSG_REMOVESCREENCAST = 17,
+  // Removed MSG_SETRTCPCNAME = 18. It is no longer used.
+  MSG_SENDINTRAFRAME = 19,
+  MSG_REQUESTINTRAFRAME = 20,
+  MSG_SCREENCASTWINDOWEVENT = 21,
+  MSG_RTPPACKET = 22,
+  MSG_RTCPPACKET = 23,
+  MSG_CHANNEL_ERROR = 24,
+  MSG_ENABLECPUADAPTATION = 25,
+  MSG_DISABLECPUADAPTATION = 26,
+  MSG_SCALEVOLUME = 27,
+  MSG_HANDLEVIEWREQUEST = 28
+};
+
+struct SetContentData : public talk_base::MessageData {
+  SetContentData(const MediaContentDescription* content, ContentAction action)
+      : content(content),
+        action(action),
+        result(false) {
+  }
+  const MediaContentDescription* content;
+  ContentAction action;
+  bool result;
+};
+
+struct SetBandwidthData : public talk_base::MessageData {
+  explicit SetBandwidthData(int value) : value(value), result(false) {}
+  int value;
+  bool result;
+};
+
+struct SetRingbackToneMessageData : public talk_base::MessageData {
+  SetRingbackToneMessageData(const void* b, int l)
+      : buf(b),
+        len(l),
+        result(false) {
+  }
+  const void* buf;
+  int len;
+  bool result;
+};
+
+struct PlayRingbackToneMessageData : public talk_base::MessageData {
+  PlayRingbackToneMessageData(uint32 s, bool p, bool l)
+      : ssrc(s),
+        play(p),
+        loop(l),
+        result(false) {
+  }
+  uint32 ssrc;
+  bool play;
+  bool loop;
+  bool result;
+};
+struct DtmfMessageData : public talk_base::MessageData {
+  DtmfMessageData(int d, bool p)
+      : digit(d),
+        playout(p),
+        result(false) {
+  }
+  int digit;
+  bool playout;
+  bool result;
+};
+struct ScaleVolumeMessageData : public talk_base::MessageData {
+  ScaleVolumeMessageData(uint32 s, double l, double r)
+      : ssrc(s),
+        left(l),
+        right(r),
+        result(false) {
+  }
+  uint32 ssrc;
+  double left;
+  double right;
+  bool result;
+};
+
 struct PacketMessageData : public talk_base::MessageData {
   talk_base::Buffer packet;
 };
 
+struct RenderMessageData : public talk_base::MessageData {
+  RenderMessageData(uint32 s, VideoRenderer* r) : ssrc(s), renderer(r) {}
+  uint32 ssrc;
+  VideoRenderer* renderer;
+};
+
+struct ScreencastMessageData : public talk_base::MessageData {
+  ScreencastMessageData(uint32 s, const ScreencastId& id)
+      : ssrc(s),
+        window_id(id) {
+  }
+  uint32 ssrc;
+  ScreencastId window_id;
+};
+
+struct ScreencastEventData : public talk_base::MessageData {
+  ScreencastEventData(uint32 s, talk_base::WindowEvent we)
+      : ssrc(s),
+        event(we) {
+  }
+  uint32 ssrc;
+  talk_base::WindowEvent event;
+};
+
+struct ViewRequestMessageData : public talk_base::MessageData {
+  explicit ViewRequestMessageData(const ViewRequest& r)
+      : request(r),
+        result(false) {
+  }
+  ViewRequest request;
+  bool result;
+};
+
 struct VoiceChannelErrorMessageData : public talk_base::MessageData {
   VoiceChannelErrorMessageData(uint32 in_ssrc,
                                VoiceMediaChannel::Error in_error)
@@ -589,7 +716,7 @@
                        << it->first_ssrc();
           return false;
         }
-      } else if (stream_exist) {
+      } 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() << ".";
@@ -597,8 +724,7 @@
         }
         RemoveStreamBySsrc(&local_streams_, existing_stream.first_ssrc());
       } else {
-        LOG(LS_ERROR) << "Unknown send stream update.";
-        return false;
+        LOG(LS_WARNING) << "Ignore unsupported stream update";
       }
     }
     return true;
@@ -652,7 +778,7 @@
                        << it->first_ssrc();
           return false;
         }
-      } else if (stream_exists) {
+      } 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() << ".";
@@ -660,8 +786,7 @@
         }
         RemoveStreamBySsrc(&remote_streams_, existing_stream.first_ssrc());
       } else {
-        LOG(LS_ERROR) << "Unknown remote stream update.";
-        return false;
+        LOG(LS_WARNING) << "Ignore unsupported stream update";
       }
     }
     return true;
@@ -1212,6 +1337,12 @@
   return true;
 }
 
+bool VideoChannel::ApplyViewRequest(const ViewRequest& request) {
+  ViewRequestMessageData data(request);
+  Send(MSG_HANDLEVIEWREQUEST, &data);
+  return data.result;
+}
+
 bool VideoChannel::AddScreencast(uint32 ssrc, const ScreencastId& id) {
   ScreencastMessageData data(ssrc, id);
   Send(MSG_ADDSCREENCAST, &data);
@@ -1352,6 +1483,43 @@
   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) {
+      // Sender view request from Reflector has SSRC 0 (b/5977302). Here we hack
+      // the client to apply the view request with SSRC 0. TODO: Remove
+      // 0 == view->SSRC once Reflector uses the correct SSRC in view request.
+      if (it->has_ssrc(view->ssrc) || 0 == view->ssrc) {
+        format.width = view->width;
+        format.height = view->height;
+        format.interval = cricket::VideoFormat::FpsToInterval(view->framerate);
+        break;
+      }
+    }
+
+    ret &= media_channel()->SetSendStreamFormat(it->first_ssrc(), format);
+  }
+
+  // Check if the view request has invalid streams.
+  for (StaticVideoViews::const_iterator it = request.static_video_views.begin();
+      it != request.static_video_views.end(); ++it) {
+    if (!GetStreamBySsrc(local_streams(), it->ssrc, NULL)) {
+      LOG(LS_WARNING) << "View request's SSRC " << it->ssrc
+                      << " is not in the local streams.";
+    }
+  }
+
+  return ret;
+}
+
 void VideoChannel::SetRenderer_w(uint32 ssrc, VideoRenderer* renderer) {
   media_channel()->SetRenderer(ssrc, renderer);
 }
@@ -1415,6 +1583,12 @@
       delete data;
       break;
     }
+    case MSG_HANDLEVIEWREQUEST: {
+      ViewRequestMessageData* data =
+          static_cast<ViewRequestMessageData*>(pmsg->pdata);
+      data->result = ApplyViewRequest_w(data->request);
+      break;
+    }
     default:
       BaseChannel::OnMessage(pmsg);
       break;
diff --git a/talk/session/phone/channel.h b/talk/session/phone/channel.h
index 42f3969..df0124f 100644
--- a/talk/session/phone/channel.h
+++ b/talk/session/phone/channel.h
@@ -52,35 +52,7 @@
 
 class MediaContentDescription;
 struct CryptoParams;
-
-enum {
-  MSG_ENABLE = 1,
-  MSG_DISABLE = 2,
-  MSG_MUTE = 3,
-  MSG_UNMUTE = 4,
-  MSG_SETREMOTECONTENT = 5,
-  MSG_SETLOCALCONTENT = 6,
-  MSG_EARLYMEDIATIMEOUT = 8,
-  MSG_PRESSDTMF = 9,
-  MSG_SETRENDERER = 10,
-  MSG_ADDRECVSTREAM = 11,
-  MSG_REMOVERECVSTREAM = 12,
-  MSG_SETRINGBACKTONE = 13,
-  MSG_PLAYRINGBACKTONE = 14,
-  MSG_SETMAXSENDBANDWIDTH = 15,
-  MSG_ADDSCREENCAST = 16,
-  MSG_REMOVESCREENCAST = 17,
-  // Removed MSG_SETRTCPCNAME = 18. It is no longer used.
-  MSG_SENDINTRAFRAME = 19,
-  MSG_REQUESTINTRAFRAME = 20,
-  MSG_SCREENCASTWINDOWEVENT = 21,
-  MSG_RTPPACKET = 22,
-  MSG_RTCPPACKET = 23,
-  MSG_CHANNEL_ERROR = 24,
-  MSG_ENABLECPUADAPTATION = 25,
-  MSG_DISABLECPUADAPTATION = 26,
-  MSG_SCALEVOLUME = 27
-};
+struct ViewRequest;
 
 // BaseChannel contains logic common to voice and video, including
 // enable/mute, marshaling calls to a worker thread, and
@@ -226,14 +198,6 @@
   bool RemoveRecvStream_w(uint32 ssrc);
 
   virtual void ChangeState() = 0;
-  struct SetContentData : public talk_base::MessageData {
-    SetContentData(const MediaContentDescription* content,
-                   ContentAction action)
-        : content(content), action(action), result(false) {}
-    const MediaContentDescription* content;
-    ContentAction action;
-    bool result;
-  };
 
   // Gets the content appropriate to the channel (audio or video).
   virtual const MediaContentDescription* GetFirstContent(
@@ -255,11 +219,6 @@
                  ContentSource src);
   bool SetRtcpMux_w(bool enable, ContentAction action, ContentSource src);
 
-  struct SetBandwidthData : public talk_base::MessageData {
-    explicit SetBandwidthData(int value) : value(value), result(false) {}
-    int value;
-    bool result;
-  };
   bool SetMaxSendBandwidth_w(int max_bandwidth);
 
   // From MessageHandler
@@ -353,51 +312,6 @@
   static const int kTypingBlackoutPeriod = 1500;
 
  private:
-  struct SetRingbackToneMessageData : public talk_base::MessageData {
-    SetRingbackToneMessageData(const void* b, int l)
-        : buf(b),
-          len(l),
-          result(false) {
-    }
-    const void* buf;
-    int len;
-    bool result;
-  };
-  struct PlayRingbackToneMessageData : public talk_base::MessageData {
-    PlayRingbackToneMessageData(uint32 s, bool p, bool l)
-        : ssrc(s),
-          play(p),
-          loop(l),
-          result(false) {
-    }
-    uint32 ssrc;
-    bool play;
-    bool loop;
-    bool result;
-  };
-  struct DtmfMessageData : public talk_base::MessageData {
-    DtmfMessageData(int d, bool p)
-        : digit(d),
-          playout(p),
-          result(false) {
-    }
-    int digit;
-    bool playout;
-    bool result;
-  };
-  struct ScaleVolumeMessageData : public talk_base::MessageData {
-    ScaleVolumeMessageData(uint32 s, double l, double r)
-        : ssrc(s),
-          left(l),
-          right(r),
-          result(false) {
-    }
-    uint32 ssrc;
-    double left;
-    double right;
-    bool result;
-  };
-
   // overrides from BaseChannel
   virtual void OnChannelRead(TransportChannel* channel,
                              const char *data, size_t len);
@@ -449,6 +363,7 @@
   }
 
   bool SetRenderer(uint32 ssrc, VideoRenderer* renderer);
+  bool ApplyViewRequest(const ViewRequest& request);
 
   bool AddScreencast(uint32 ssrc, const ScreencastId& id);
   bool RemoveScreencast(uint32 ssrc);
@@ -491,26 +406,7 @@
     media_channel()->SetOptions(enable ? OPT_CPU_ADAPTATION : 0);
   }
 
-  struct RenderMessageData : public talk_base::MessageData {
-    RenderMessageData(uint32 s, VideoRenderer* r) : ssrc(s), renderer(r) {}
-    uint32 ssrc;
-    VideoRenderer* renderer;
-  };
-
-  struct ScreencastMessageData : public talk_base::MessageData {
-    ScreencastMessageData(uint32 s, const ScreencastId& id)
-        : ssrc(s), window_id(id) {}
-    uint32 ssrc;
-    ScreencastId window_id;
-  };
-
-  struct ScreencastEventData : public talk_base::MessageData {
-    ScreencastEventData(uint32 s, talk_base::WindowEvent we)
-        : ssrc(s), event(we) {}
-    uint32 ssrc;
-    talk_base::WindowEvent event;
-  };
-
+  bool ApplyViewRequest_w(const ViewRequest& request);
   void SetRenderer_w(uint32 ssrc, VideoRenderer* renderer);
 
   void AddScreencast_w(uint32 ssrc, const ScreencastId&);
diff --git a/talk/session/phone/channel_unittest.cc b/talk/session/phone/channel_unittest.cc
index a134f45..fa6c938 100644
--- a/talk/session/phone/channel_unittest.cc
+++ b/talk/session/phone/channel_unittest.cc
@@ -33,6 +33,7 @@
 #include "talk/session/phone/channel.h"
 #include "talk/session/phone/fakemediaengine.h"
 #include "talk/session/phone/fakertp.h"
+#include "talk/session/phone/mediamessages.h"
 #include "talk/session/phone/mediasessionclient.h"
 #include "talk/session/phone/mediarecorder.h"
 #include "talk/session/phone/rtpdump.h"
@@ -540,6 +541,16 @@
     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
@@ -598,6 +609,16 @@
     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
@@ -1855,3 +1876,41 @@
 TEST_F(VideoChannelTest, TestSrtpError) {
   Base::TestSrtpError();
 }
+
+TEST_F(VideoChannelTest, TestApplyViewRequest) {
+  CreateChannels(0, 0);
+  EXPECT_TRUE(SendInitiate());
+  EXPECT_TRUE(SendAccept());
+
+  cricket::VideoFormat send_format;
+  EXPECT_TRUE(media_channel1_->GetSendStreamFormat(kSsrc1, &send_format));
+  EXPECT_EQ(640, send_format.width);
+  EXPECT_EQ(400, send_format.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), send_format.interval);
+
+  cricket::ViewRequest request;
+  request.static_video_views.push_back(
+      cricket::StaticVideoView(kSsrc1, 320, 200, 15));
+  EXPECT_TRUE(channel1_->ApplyViewRequest(request));
+  EXPECT_TRUE(media_channel1_->GetSendStreamFormat(kSsrc1, &send_format));
+  EXPECT_EQ(320, send_format.width);
+  EXPECT_EQ(200, send_format.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(15), send_format.interval);
+
+  // Short term hack for view request with SSRC 0. TODO: Remove this
+  // once Reflector uses the correct SSRC in view request (b/5977302).
+  request.static_video_views.clear();
+  request.static_video_views.push_back(
+      cricket::StaticVideoView(0, 160, 100, 8));
+  EXPECT_TRUE(channel1_->ApplyViewRequest(request));
+  EXPECT_TRUE(media_channel1_->GetSendStreamFormat(kSsrc1, &send_format));
+  EXPECT_EQ(160, send_format.width);
+  EXPECT_EQ(100, send_format.height);
+  EXPECT_EQ(cricket::VideoFormat::FpsToInterval(8), send_format.interval);
+
+  request.static_video_views.clear();
+  EXPECT_TRUE(channel1_->ApplyViewRequest(request));
+  EXPECT_TRUE(media_channel1_->GetSendStreamFormat(kSsrc1, &send_format));
+  EXPECT_EQ(0, send_format.width);
+  EXPECT_EQ(0, send_format.height);
+}
diff --git a/talk/session/phone/fakemediaengine.h b/talk/session/phone/fakemediaengine.h
index 4f83549..b772c9c 100644
--- a/talk/session/phone/fakemediaengine.h
+++ b/talk/session/phone/fakemediaengine.h
@@ -474,8 +474,6 @@
   void set_requested_intra_frame(bool v) { requested_intra_frame_ = v; }
   bool requested_intra_frame() const { return requested_intra_frame_; }
   bool screen_casting() const { return screen_casting_; }
-  // TODO: remove this with FMS CL
-  bool IsScreencasting() const { return screen_casting_; }
 
  private:
   // Be default, each send stream uses the first send codec format.
diff --git a/talk/session/phone/fakenetworkinterface.h b/talk/session/phone/fakenetworkinterface.h
index 09c3bf7..58b24a2 100644
--- a/talk/session/phone/fakenetworkinterface.h
+++ b/talk/session/phone/fakenetworkinterface.h
@@ -76,9 +76,13 @@
     return rtp_packets_.size();
   }
 
+  // Note: callers are responsible for deleting the returned buffer.
   const talk_base::Buffer* GetRtpPacket(int index) {
     talk_base::CritScope cs(&crit_);
-    return (index < NumRtpPackets()) ? &rtp_packets_[index] : NULL;
+    if (index >= NumRtpPackets()) {
+      return NULL;
+    }
+    return new talk_base::Buffer(rtp_packets_[index]);
   }
 
   int NumRtcpPackets() {
@@ -86,9 +90,13 @@
     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_);
-    return (index < NumRtcpPackets()) ? &rtcp_packets_[index] : NULL;
+    if (index >= NumRtcpPackets()) {
+      return NULL;
+    }
+    return new talk_base::Buffer(rtcp_packets_[index]);
   }
 
   int sendbuf_size() const { return sendbuf_size_; }
diff --git a/talk/session/phone/mediasession.cc b/talk/session/phone/mediasession.cc
index 67ac082..ed4eaa7 100644
--- a/talk/session/phone/mediasession.cc
+++ b/talk/session/phone/mediasession.cc
@@ -254,12 +254,14 @@
 }
 
 MediaSessionDescriptionFactory::MediaSessionDescriptionFactory()
-    : secure_(SEC_DISABLED) {
+    : secure_(SEC_DISABLED),
+      add_legacy_(true) {
 }
 
 MediaSessionDescriptionFactory::MediaSessionDescriptionFactory(
     ChannelManager* channel_manager)
-    : secure_(SEC_DISABLED) {
+    : secure_(SEC_DISABLED),
+      add_legacy_(true) {
   channel_manager->GetSupportedAudioCodecs(&audio_codecs_);
   channel_manager->GetSupportedVideoCodecs(&video_codecs_);
 }
@@ -284,7 +286,7 @@
       return NULL;  // Abort, something went seriously wrong.
     }
 
-    if (options.streams.empty()) {
+    if (options.streams.empty() && add_legacy_) {
       // TODO: Remove this legacy stream when all apps use StreamParams.
       audio->AddLegacyStream(talk_base::CreateRandomNonZeroId());
     }
@@ -337,7 +339,7 @@
       return NULL;  // Abort, something went seriously wrong.
     }
 
-    if (options.streams.empty()) {
+    if (options.streams.empty() && add_legacy_) {
       // TODO: Remove this legacy stream when all apps use StreamParams.
       video->AddLegacyStream(talk_base::CreateRandomNonZeroId());
     }
@@ -410,7 +412,7 @@
       return NULL;  // Abort, something went seriously wrong.
     }
 
-    if (options.streams.empty()) {
+    if (options.streams.empty() && add_legacy_) {
       // TODO: Remove this legacy stream when all apps use StreamParams.
       audio_accept->AddLegacyStream(talk_base::CreateRandomNonZeroId());
     }
@@ -475,7 +477,7 @@
       return NULL;  // Abort, something went seriously wrong.
     }
 
-    if (options.streams.empty()) {
+    if (options.streams.empty() && add_legacy_) {
       // TODO: Remove this legacy stream when all apps use StreamParams.
       video_accept->AddLegacyStream(talk_base::CreateRandomNonZeroId());
     }
diff --git a/talk/session/phone/mediasession.h b/talk/session/phone/mediasession.h
index c2798c0..718f013 100644
--- a/talk/session/phone/mediasession.h
+++ b/talk/session/phone/mediasession.h
@@ -285,6 +285,11 @@
   void set_video_codecs(const VideoCodecs& codecs) { video_codecs_ = codecs; }
   SecureMediaPolicy secure() const { return secure_; }
   void set_secure(SecureMediaPolicy s) { secure_ = s; }
+  // Decides if a StreamParams shall be added to the audio and video media
+  // content in SessionDescription when CreateOffer and CreateAnswer is called
+  // even if |options| don't include a Stream. This is needed to support legacy
+  // applications. |add_legacy_| is true per default.
+  void set_add_legacy_streams(bool add_legacy) { add_legacy_ = add_legacy; }
 
   SessionDescription* CreateOffer(
       const MediaSessionOptions& options,
@@ -299,6 +304,7 @@
   AudioCodecs audio_codecs_;
   VideoCodecs video_codecs_;
   SecureMediaPolicy secure_;
+  bool add_legacy_;
   std::string lang_;
 };
 
diff --git a/talk/session/phone/mediasession_unittest.cc b/talk/session/phone/mediasession_unittest.cc
index 9bb2db2..974531e 100644
--- a/talk/session/phone/mediasession_unittest.cc
+++ b/talk/session/phone/mediasession_unittest.cc
@@ -221,6 +221,28 @@
   ASSERT_CRYPTO(vcd, false, 1U, CS_AES_CM_128_HMAC_SHA1_80);
 }
 
+// Create an audio, video offer without legacy StreamParams.
+TEST_F(MediaSessionDescriptionFactoryTest,
+       TestCreateOfferWithoutLegacyStreams) {
+  MediaSessionOptions opts;
+  opts.has_video = true;
+  f1_.set_add_legacy_streams(false);
+  talk_base::scoped_ptr<SessionDescription>
+      offer(f1_.CreateOffer(opts, NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+  const ContentInfo* ac = offer->GetContentByName("audio");
+  const ContentInfo* vc = offer->GetContentByName("video");
+  ASSERT_TRUE(ac != NULL);
+  ASSERT_TRUE(vc != NULL);
+  const AudioContentDescription* acd =
+      static_cast<const AudioContentDescription*>(ac->description);
+  const VideoContentDescription* vcd =
+      static_cast<const VideoContentDescription*>(vc->description);
+
+  EXPECT_FALSE(vcd->has_ssrcs());             // No StreamParams.
+  EXPECT_FALSE(acd->has_ssrcs());             // No StreamParams.
+}
+
 // Create a typical audio answer, and ensure it matches what we expect.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswer) {
   f1_.set_secure(SEC_ENABLED);
@@ -278,6 +300,30 @@
   ASSERT_CRYPTO(vcd, false, 1U, CS_AES_CM_128_HMAC_SHA1_80);
 }
 
+// Create an audio, video answer without legacy StreamParams.
+TEST_F(MediaSessionDescriptionFactoryTest,
+       TestCreateAnswerWithoutLegacyStreams) {
+  MediaSessionOptions opts;
+  opts.has_video = true;
+  f1_.set_add_legacy_streams(false);
+  f2_.set_add_legacy_streams(false);
+  talk_base::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
+  ASSERT_TRUE(offer.get() != NULL);
+  talk_base::scoped_ptr<SessionDescription> answer(
+      f2_.CreateAnswer(offer.get(), opts, NULL));
+  const ContentInfo* ac = answer->GetContentByName("audio");
+  const ContentInfo* vc = answer->GetContentByName("video");
+  ASSERT_TRUE(ac != NULL);
+  ASSERT_TRUE(vc != NULL);
+  const AudioContentDescription* acd =
+      static_cast<const AudioContentDescription*>(ac->description);
+  const VideoContentDescription* vcd =
+      static_cast<const VideoContentDescription*>(vc->description);
+
+  EXPECT_FALSE(acd->has_ssrcs());  // No StreamParams.
+  EXPECT_FALSE(vcd->has_ssrcs());  // No StreamParams.
+}
+
 TEST_F(MediaSessionDescriptionFactoryTest, TestPartial) {
   MediaSessionOptions opts;
   opts.has_video = true;
diff --git a/talk/session/phone/mediasessionclient_unittest.cc b/talk/session/phone/mediasessionclient_unittest.cc
index c698e78..8232d3d 100644
--- a/talk/session/phone/mediasessionclient_unittest.cc
+++ b/talk/session/phone/mediasessionclient_unittest.cc
@@ -883,6 +883,38 @@
       "</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) {
@@ -2126,6 +2158,20 @@
     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());
diff --git a/talk/session/phone/videoadapter.cc b/talk/session/phone/videoadapter.cc
index 1cb1cc4..4313fc7 100644
--- a/talk/session/phone/videoadapter.cc
+++ b/talk/session/phone/videoadapter.cc
@@ -118,7 +118,8 @@
 ///////////////////////////////////////////////////////////////////////
 // Implementation of VideoAdapter
 VideoAdapter::VideoAdapter()
-    : black_output_(false),
+    : output_num_pixels_(0),
+      black_output_(false),
       is_black_(false),
       drop_frame_count_(0) {
 }
diff --git a/talk/session/phone/videocapturer.cc b/talk/session/phone/videocapturer.cc
index ac544eb..7f86293 100644
--- a/talk/session/phone/videocapturer.cc
+++ b/talk/session/phone/videocapturer.cc
@@ -105,6 +105,7 @@
     }
   }
   if (supported_formats_->end() == best) {
+    LOG(LS_ERROR) << " No acceptable camera format found";
     return false;
   }
 
@@ -207,10 +208,11 @@
   if (delta_fps < 0) {
     // For same resolution, prefer higher framerate but accept lower.
     // Otherwise prefer higher resolution.
-    distance |= static_cast<int64>(1) << 15;
     delta_fps = -delta_fps;
-    if (supported_fps <  kMinDesirableFps) {
-      distance |= kMaxDistance;
+    if (supported_fps < kMinDesirableFps) {
+      distance |= static_cast<int64>(1) << 62;
+    } else {
+      distance |= static_cast<int64>(1) << 15;
     }
   }
 
diff --git a/talk/session/phone/videocapturer_unittest.cc b/talk/session/phone/videocapturer_unittest.cc
index 1b73b15..5bd2419 100644
--- a/talk/session/phone/videocapturer_unittest.cc
+++ b/talk/session/phone/videocapturer_unittest.cc
@@ -248,6 +248,54 @@
   }
 }
 
+// Some cameras only have very low fps. Verify we choose something sensible.
+TEST(VideoCapturerTest, TestPoorFpsFormats) {
+  FakeVideoCapturer capturer;
+  // all formats are low framerate
+  std::vector<cricket::VideoFormat> supported_formats;
+  supported_formats.push_back(cricket::VideoFormat(320, 240,
+      cricket::VideoFormat::FpsToInterval(10), cricket::FOURCC_I420));
+  supported_formats.push_back(cricket::VideoFormat(640, 480,
+      cricket::VideoFormat::FpsToInterval(7), cricket::FOURCC_I420));
+  supported_formats.push_back(cricket::VideoFormat(1280, 720,
+      cricket::VideoFormat::FpsToInterval(2), cricket::FOURCC_I420));
+  capturer.ResetSupportedFormats(supported_formats);
+
+  std::vector<cricket::VideoFormat> required_formats;
+  required_formats.push_back(cricket::VideoFormat(320, 240,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  required_formats.push_back(cricket::VideoFormat(640, 480,
+      cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+  cricket::VideoFormat best;
+  for (size_t i = 0; i < required_formats.size(); ++i) {
+    EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[i], &best));
+#ifdef OSX
+    EXPECT_EQ(640, best.width);
+    EXPECT_EQ(480, best.height);
+#else
+    EXPECT_EQ(required_formats[i].width, best.width);
+    EXPECT_EQ(required_formats[i].height, best.height);
+#endif
+  }
+
+  // Increase framerate of 320x240.  Expect low fps VGA avoided.
+  // Except on Mac, where QVGA is avoid due to aspect ratio.
+  supported_formats.clear();
+  supported_formats.push_back(cricket::VideoFormat(320, 240,
+      cricket::VideoFormat::FpsToInterval(15), cricket::FOURCC_I420));
+  supported_formats.push_back(cricket::VideoFormat(640, 480,
+      cricket::VideoFormat::FpsToInterval(7), cricket::FOURCC_I420));
+  supported_formats.push_back(cricket::VideoFormat(1280, 720,
+      cricket::VideoFormat::FpsToInterval(2), cricket::FOURCC_I420));
+  capturer.ResetSupportedFormats(supported_formats);
+
+  for (size_t i = 0; i < required_formats.size(); ++i) {
+    EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[i], &best));
+    EXPECT_EQ(320, best.width);
+    EXPECT_EQ(240, best.height);
+  }
+}
+
 // Some cameras support same size with different frame rates. Verify we choose
 // the frame rate properly.
 TEST(VideoCapturerTest, TestSameSizeDifferentFpsFormats) {
diff --git a/talk/session/phone/webrtcvideoengine.cc b/talk/session/phone/webrtcvideoengine.cc
index 88c8a94..07ab5e6 100644
--- a/talk/session/phone/webrtcvideoengine.cc
+++ b/talk/session/phone/webrtcvideoengine.cc
@@ -108,8 +108,11 @@
       return 0;
     }
     WebRtcVideoFrame video_frame;
+    // Convert 90K timestamp to ns timestamp.
+    int64 time_stamp_in_ns = (time_stamp / 90) *
+       talk_base::kNumNanosecsPerMillisec;
     video_frame.Attach(buffer, buffer_size, width_, height_,
-                       1, 1, 0, time_stamp, 0);
+                       1, 1, 0, time_stamp_in_ns, 0);
 
 
     // Sanity check on decoded frame size.
@@ -249,20 +252,20 @@
  public:
   explicit WebRtcVideoChannelInfo(int channel_id)
       : channel_id_(channel_id),
-        renderer_(NULL),
+        render_adapter_(NULL),
         decoder_observer_(channel_id) {
   }
   int channel_id() { return channel_id_; }
   void SetRenderer(VideoRenderer* renderer) {
-    renderer_.SetRenderer(renderer);
+    render_adapter_.SetRenderer(renderer);
   }
-  WebRtcRenderAdapter* render_adapter() { return &renderer_; }
+  WebRtcRenderAdapter* render_adapter() { return &render_adapter_; }
   WebRtcDecoderObserver* decoder_observer() { return &decoder_observer_; }
 
  private:
   int channel_id_;  // Webrtc video channel number.
   // Renderer for this channel.
-  WebRtcRenderAdapter renderer_;
+  WebRtcRenderAdapter render_adapter_;
   WebRtcDecoderObserver decoder_observer_;
 };
 
@@ -1019,6 +1022,7 @@
       render_started_(false),
       muted_(false),
       local_ssrc_(0),
+      first_receive_ssrc_(0),
       send_min_bitrate_(kMinVideoBitrate),
       send_start_bitrate_(kStartVideoBitrate),
       send_max_bitrate_(kMaxVideoBitrate),
@@ -1276,7 +1280,7 @@
     }
   }
   sending_ = send;
-
+  
   return true;
 }
 
@@ -1337,6 +1341,17 @@
 }
 
 bool WebRtcVideoMediaChannel::AddRecvStream(const StreamParams& sp) {
+  // TODO Remove this once BWE works properly across different send
+  // and receive channels.
+  // Reuse default channel for recv stream in 1:1 call.
+  if ((channel_options_ & OPT_CONFERENCE) == 0 && first_receive_ssrc_ == 0) {
+    LOG(LS_INFO) << "Recv stream " << sp.first_ssrc()
+                 << " reuse default channel #"
+                 << vie_channel_;
+    first_receive_ssrc_ = sp.first_ssrc();
+    return true;
+  }
+
   if (mux_channels_.find(sp.first_ssrc()) != mux_channels_.end()) {
     LOG(LS_ERROR) << "Stream already exists";
     return false;
@@ -1358,12 +1373,28 @@
     LOG_RTCERR1(CreateChannel, channel_id);
     return false;
   }
-  if (!ConfigureChannel(channel_id) || !ConfigureReceiving(channel_id,
-                                                           sp.first_ssrc())) {
+
+  // Get the default renderer.
+  VideoRenderer* default_renderer = NULL;
+  if ((channel_options_ & OPT_CONFERENCE) != 0) {
+    if (mux_channels_.size() == 1 &&
+        mux_channels_.find(0) != mux_channels_.end()) {
+      GetRenderer(0, &default_renderer);
+    }
+  }
+
+  if (!ConfigureChannel(channel_id) ||
+      !ConfigureReceiving(channel_id, sp.first_ssrc())) {
     engine_->vie()->base()->DeleteChannel(channel_id);
     return false;
   }
 
+  // The first recv stream reuses the default renderer (if a default renderer
+  // has been set).
+  if (default_renderer) {
+    SetRenderer(sp.first_ssrc(), default_renderer);
+  }
+
   LOG(LS_INFO) << "New video stream " << sp.first_ssrc()
                << " registered to VideoEngine channel #"
                << channel_id;
@@ -1375,6 +1406,13 @@
   ChannelMap::iterator it = mux_channels_.find(ssrc);
 
   if (it == mux_channels_.end()) {
+    // TODO: Remove this once BWE works properly across different send
+    // and receive channels.
+    // The default channel is reused for recv stream in 1:1 call.
+    if (first_receive_ssrc_ == ssrc) {
+      first_receive_ssrc_ = 0;
+      return true;
+    }
     return false;
   }
   WebRtcVideoChannelInfo* info = it->second;
@@ -1413,6 +1451,11 @@
     LOG_RTCERR1(StartSend, vie_channel_);
     return false;
   }
+
+  // TODO Change this once REMB supporting multiple sending channels.
+  // Send remb (2nd param) and use remb for BWE (3rd param).
+  engine_->vie()->rtp()->SetRembStatus(vie_channel_, true, true);
+
   return true;
 }
 
@@ -1421,13 +1464,30 @@
     LOG_RTCERR1(StopSend, vie_channel_);
     return false;
   }
+
+  // TODO Change this once REMB supporting multiple sending channels.
+  // Don't send remb (2nd param) but use remb for BWE (3rd param).
+  engine_->vie()->rtp()->SetRembStatus(vie_channel_, false, true);
+
   return true;
 }
 
 bool WebRtcVideoMediaChannel::SetRenderer(uint32 ssrc,
                                           VideoRenderer* renderer) {
-  if (mux_channels_.find(ssrc) == mux_channels_.end())
+  if (mux_channels_.find(ssrc) == mux_channels_.end()) {
+    // TODO: Remove this once BWE works properly across different send
+    // and receive channels.
+    // The default channel is reused for recv stream in 1:1 call.
+    if (first_receive_ssrc_ == ssrc &&
+        mux_channels_.find(0) != mux_channels_.end()) {
+      LOG(LS_INFO) << "SetRenderer " << ssrc
+                   << " reuse default channel #"
+                   << vie_channel_;
+      mux_channels_[0]->SetRenderer(renderer);
+      return true;
+    }
     return false;
+  }
 
   mux_channels_[ssrc]->SetRenderer(renderer);
   return true;
@@ -1705,8 +1765,18 @@
 bool WebRtcVideoMediaChannel::GetRenderer(uint32 ssrc,
                                           VideoRenderer** renderer) {
   ChannelMap::const_iterator it = mux_channels_.find(ssrc);
-  if (it == mux_channels_.end())
+  if (it == mux_channels_.end()) {
+    if (first_receive_ssrc_ == ssrc &&
+        mux_channels_.find(0) != mux_channels_.end()) {
+      LOG(LS_INFO) << " GetRenderer " << ssrc
+                   << " reuse default renderer #"
+                   << vie_channel_;
+      *renderer = mux_channels_[0]->render_adapter()->renderer();
+      return true;
+    }
     return false;
+  }
+
   *renderer = it->second->render_adapter()->renderer();
   return true;
 }
@@ -1802,9 +1872,11 @@
       // Not a fatal error.
     }
   }
-  // Install a render adapter.
+
   talk_base::scoped_ptr<WebRtcVideoChannelInfo> channel_info(
       new WebRtcVideoChannelInfo(channel_id));
+
+  // Install a render adapter.
   if (engine_->vie()->render()->AddRenderer(channel_id,
       webrtc::kVideoI420, channel_info->render_adapter()) != 0) {
     LOG_RTCERR3(AddRenderer, channel_id, webrtc::kVideoI420,
@@ -1812,15 +1884,11 @@
     return false;
   }
 
-  // Turn on REMB-based BWE reporting.
-  // First parameter is channel id, second is if this channel should send REMB
-  // packets, last parameter is if it should report BWE using REMB.
-  // TODO: |send_remb| should be channel id so we can have several
-  // REMB groups.
-  bool send_remb = (remote_ssrc == 0);  // SSRC 0 is our default channel.
-  // TODO - Add return value check for SetRembStatus once webrtc
-  // revisions are synced in Chrome and Plugin.
-  engine_->vie()->rtp()->SetRembStatus(channel_id, send_remb, true);
+  // TODO Change this once REMB supporting multiple sending channels.
+  // Turn off remb sending (2nd param) and turn on remb reporting (3rd param)
+  // here.
+  // For sending channel, remb sending will be turned on after StartSending.
+  engine_->vie()->rtp()->SetRembStatus(channel_id, false, true);
 
   if (remote_ssrc != 0) {
     // Use the same SSRC as our default channel
diff --git a/talk/session/phone/webrtcvideoengine.h b/talk/session/phone/webrtcvideoengine.h
index 7085dbe..8eb56da 100644
--- a/talk/session/phone/webrtcvideoengine.h
+++ b/talk/session/phone/webrtcvideoengine.h
@@ -298,6 +298,7 @@
   bool muted_;  // Flag to tell if we need to mute video.
   // Our local SSRC. Currently only one send stream is supported.
   uint32 local_ssrc_;
+  uint32 first_receive_ssrc_;
   int send_min_bitrate_;
   int send_start_bitrate_;
   int send_max_bitrate_;
diff --git a/talk/session/phone/webrtcvideoengine_unittest.cc b/talk/session/phone/webrtcvideoengine_unittest.cc
index a051989..b0bf813 100644
--- a/talk/session/phone/webrtcvideoengine_unittest.cc
+++ b/talk/session/phone/webrtcvideoengine_unittest.cc
@@ -354,6 +354,12 @@
 TEST_F(WebRtcVideoEngineTestFake, RembEnabled) {
   EXPECT_TRUE(SetupEngine());
   int channel_num = vie_.GetLastChannel();
+  EXPECT_TRUE(channel_->AddSendStream(
+      cricket::StreamParams::CreateLegacy(1)));
+  EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+  EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+  EXPECT_FALSE(vie_.GetRembStatusSend(channel_num));
+  EXPECT_TRUE(channel_->SetSend(true));
   EXPECT_TRUE(vie_.GetRembStatus(channel_num));
   EXPECT_TRUE(vie_.GetRembStatusSend(channel_num));
 }
@@ -362,8 +368,66 @@
 // channel for sending remb packets.
 TEST_F(WebRtcVideoEngineTestFake, RembEnabledOnReceiveChannels) {
   EXPECT_TRUE(SetupEngine());
-  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
   int channel_num = vie_.GetLastChannel();
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+  EXPECT_TRUE(channel_->AddSendStream(
+      cricket::StreamParams::CreateLegacy(1)));
+  EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+  EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+  EXPECT_FALSE(vie_.GetRembStatusSend(channel_num));
+  EXPECT_TRUE(channel_->SetSend(true));
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+  int new_channel_num = vie_.GetLastChannel();
+  EXPECT_NE(channel_num, new_channel_num);
+
+  EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+  EXPECT_TRUE(vie_.GetRembStatusSend(channel_num));
+  EXPECT_TRUE(vie_.GetRembStatus(new_channel_num));
+  EXPECT_FALSE(vie_.GetRembStatusSend(new_channel_num));
+}
+
+// Test that AddRecvStream doesn't create new channel for 1:1 call.
+TEST_F(WebRtcVideoEngineTestFake, AddRecvStream1On1) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+  EXPECT_EQ(channel_num, vie_.GetLastChannel());
+}
+
+// Test that AddRecvStream doesn't change remb for 1:1 call.
+TEST_F(WebRtcVideoEngineTestFake, NoRembChangeAfterAddRecvStream) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  EXPECT_TRUE(channel_->AddSendStream(
+      cricket::StreamParams::CreateLegacy(1)));
+  EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+  EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+  EXPECT_FALSE(vie_.GetRembStatusSend(channel_num));
+  EXPECT_TRUE(channel_->SetSend(true));
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+  EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+  EXPECT_TRUE(vie_.GetRembStatusSend(channel_num));
+}
+
+// Test remb sending is on after StartSending and off after StopSending.
+TEST_F(WebRtcVideoEngineTestFake, RembOnOff) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+
+  // Verify remb sending is off before StartSending.
+  EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+  EXPECT_FALSE(vie_.GetRembStatusSend(channel_num));
+
+  // Verify remb sending is on after StartSending.
+  EXPECT_TRUE(channel_->AddSendStream(
+      cricket::StreamParams::CreateLegacy(1)));
+  EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+  EXPECT_TRUE(channel_->SetSend(true));
+  EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+  EXPECT_TRUE(vie_.GetRembStatusSend(channel_num));
+
+  // Verify remb sending is off after StopSending.
+  EXPECT_TRUE(channel_->SetSend(false));
   EXPECT_TRUE(vie_.GetRembStatus(channel_num));
   EXPECT_FALSE(vie_.GetRembStatusSend(channel_num));
 }
@@ -829,6 +893,10 @@
   Base::AddRemoveRecvStreams();
 }
 
+TEST_F(WebRtcVideoMediaChannelTest, AddRemoveRecvStreamsNoConference) {
+  Base::AddRemoveRecvStreamsNoConference();
+}
+
 TEST_F(WebRtcVideoMediaChannelTest, AddRemoveSendStreams) {
   Base::AddRemoveSendStreams();
 }
@@ -880,4 +948,4 @@
 // TODO: Understand why we receive a not-quite-black frame.
 TEST_F(WebRtcVideoMediaChannelTest, DISABLED_Mute) {
   Base::Mute();
-}
\ No newline at end of file
+}
diff --git a/talk/session/phone/webrtcvoiceengine.cc b/talk/session/phone/webrtcvoiceengine.cc
index 7f4326b..98a1b85 100644
--- a/talk/session/phone/webrtcvoiceengine.cc
+++ b/talk/session/phone/webrtcvoiceengine.cc
@@ -856,7 +856,7 @@
     "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",
+    "webrtc::RTCPReceiver::SenderInfoReceived No received SR",
     "StatisticsRTP() no statisitics availble",
     NULL
   };
@@ -1639,6 +1639,13 @@
 bool WebRtcVoiceMediaChannel::AddRecvStream(const StreamParams& sp) {
   talk_base::CritScope lock(&mux_channels_cs_);
 
+  // Reuse default channel for recv stream in 1:1 call.
+  if ((channel_options_ & OPT_CONFERENCE) == 0) {
+    LOG(LS_INFO) << "Recv stream " << sp.first_ssrc()
+                 << " reuse default channel";
+    return true;
+  }
+
   if (!VERIFY(sp.ssrcs.size() == 1))
     return false;
   uint32 ssrc = sp.first_ssrc();
diff --git a/talk/session/phone/webrtcvoiceengine_unittest.cc b/talk/session/phone/webrtcvoiceengine_unittest.cc
index 4da2812..f971106 100644
--- a/talk/session/phone/webrtcvoiceengine_unittest.cc
+++ b/talk/session/phone/webrtcvoiceengine_unittest.cc
@@ -253,6 +253,7 @@
 // Test that changes to recv codecs are applied to all streams.
 TEST_F(WebRtcVoiceEngineTestFake, SetRecvCodecsWithMultipleStreams) {
   EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
   std::vector<cricket::AudioCodec> codecs;
   codecs.push_back(kIsacCodec);
   codecs.push_back(kPcmuCodec);
@@ -279,6 +280,7 @@
 
 TEST_F(WebRtcVoiceEngineTestFake, SetRecvCodecsAfterAddingStreams) {
   EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
   std::vector<cricket::AudioCodec> codecs;
   codecs.push_back(kIsacCodec);
   codecs[0].id = 106;  // collide with existing telephone-event
@@ -643,6 +645,7 @@
   int channel_num1 = voe_.GetLastChannel();
 
   // Start playout on the default channel.
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
   EXPECT_TRUE(channel_->SetPlayout(true));
   EXPECT_TRUE(voe_.GetPlayout(channel_num1));
 
@@ -872,6 +875,7 @@
 // SSRC is set in SetupEngine by calling AddSendStream.
 TEST_F(WebRtcVoiceEngineTestFake, SetSendSsrcWithMultipleStreams) {
   EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
   int channel_num1 = voe_.GetLastChannel();
   unsigned int send_ssrc;
   EXPECT_EQ(0, voe_.GetLocalSSRC(channel_num1, send_ssrc));
@@ -888,6 +892,7 @@
 TEST_F(WebRtcVoiceEngineTestFake, SetSendSsrcAfterCreatingReceiveChannel) {
   EXPECT_TRUE(engine_.Init());
   channel_ = engine_.CreateChannel();
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
 
   EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
   int receive_channel_num = voe_.GetLastChannel();
@@ -915,6 +920,7 @@
 // Test that we can properly receive packets on multiple streams.
 TEST_F(WebRtcVoiceEngineTestFake, RecvWithMultipleStreams) {
   EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
   EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
   int channel_num1 = voe_.GetLastChannel();
   EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
@@ -958,13 +964,27 @@
 TEST_F(WebRtcVoiceEngineTestFake, AddStreamFail) {
   EXPECT_TRUE(SetupEngine());
   voe_.set_fail_create_channel(true);
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
   EXPECT_FALSE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
+
+  // In 1:1 call, we should not try to create a new channel.
+  EXPECT_TRUE(channel_->SetOptions(0));
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
+}
+
+// Test that AddRecvStream doesn't create new channel for 1:1 call.
+TEST_F(WebRtcVoiceEngineTestFake, AddRecvStream1On1) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = voe_.GetLastChannel();
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+  EXPECT_EQ(channel_num, voe_.GetLastChannel());
 }
 
 // Test that we properly clean up any streams that were added, even if
 // not explicitly removed.
 TEST_F(WebRtcVoiceEngineTestFake, StreamCleanup) {
   EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
   EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
   EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
   EXPECT_EQ(3, voe_.GetNumChannels());  // default channel + 2 added
@@ -1012,6 +1032,7 @@
 // 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(cricket::OPT_CONFERENCE));
   EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
   EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
   int channel_num = voe_.GetLastChannel();
@@ -1070,6 +1091,7 @@
   unsigned int ssrc = 0;
 
   EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
   EXPECT_TRUE(channel_->SetSend(cricket::SEND_MICROPHONE));
 
   media_channel = reinterpret_cast<cricket::WebRtcVoiceMediaChannel*>(channel_);
@@ -1109,6 +1131,7 @@
 
 TEST_F(WebRtcVoiceEngineTestFake, TestSetPlayoutError) {
   EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
   std::vector<cricket::AudioCodec> codecs;
   codecs.push_back(kPcmuCodec);
   EXPECT_TRUE(channel_->SetSendCodecs(codecs));
@@ -1125,6 +1148,7 @@
 // webrtcvoiceengine works as expected
 TEST_F(WebRtcVoiceEngineTestFake, RegisterVoiceProcessor) {
   EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
   EXPECT_TRUE(channel_->AddRecvStream(
       cricket::StreamParams::CreateLegacy(kSsrc1)));
   uint32 ssrc = 0;