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, ¤t_pos, desc, candidates)) {
+ if (!ParseMediaDescription(message, ¤t_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;