diff --git a/tools-webrtc/valgrind/gtest_exclude/peerconnection_unittests.gtest-memcheck.txt b/tools-webrtc/valgrind/gtest_exclude/peerconnection_unittests.gtest-memcheck.txt index 464a822615..f1aec4dca6 100644 --- a/tools-webrtc/valgrind/gtest_exclude/peerconnection_unittests.gtest-memcheck.txt +++ b/tools-webrtc/valgrind/gtest_exclude/peerconnection_unittests.gtest-memcheck.txt @@ -1,7 +1,7 @@ # Tests that are failing when run under memcheck. # https://code.google.com/p/webrtc/issues/detail?id=4387 DtmfSenderTest.* -P2PTestConductor.* +PeerConnectionIntegrationTest.* PeerConnectionEndToEndTest.* PeerConnectionInterfaceTest.* RTCStatsIntegrationTest.* diff --git a/webrtc/api/peerconnectioninterface.h b/webrtc/api/peerconnectioninterface.h index b346783bb3..e965457362 100644 --- a/webrtc/api/peerconnectioninterface.h +++ b/webrtc/api/peerconnectioninterface.h @@ -648,12 +648,14 @@ class PeerConnectionInterface : public rtc::RefCountInterface { const MediaConstraintsInterface* constraints) {} // Sets the local session description. - // JsepInterface takes the ownership of |desc| even if it fails. + // The PeerConnection takes the ownership of |desc| even if it fails. // The |observer| callback will be called when done. + // TODO(deadbeef): Change |desc| to be a unique_ptr, to make it clear + // that this method always takes ownership of it. virtual void SetLocalDescription(SetSessionDescriptionObserver* observer, SessionDescriptionInterface* desc) = 0; // Sets the remote session description. - // JsepInterface takes the ownership of |desc| even if it fails. + // The PeerConnection takes the ownership of |desc| even if it fails. // The |observer| callback will be called when done. virtual void SetRemoteDescription(SetSessionDescriptionObserver* observer, SessionDescriptionInterface* desc) = 0; diff --git a/webrtc/media/base/fakevideorenderer.h b/webrtc/media/base/fakevideorenderer.h index 7255c05f83..3d2cc67532 100644 --- a/webrtc/media/base/fakevideorenderer.h +++ b/webrtc/media/base/fakevideorenderer.h @@ -35,7 +35,7 @@ class FakeVideoRenderer : public rtc::VideoSinkInterface { // tolerance on Y values. Some unit tests produce Y values close // to 16 rather than close to zero, for supposedly black frames. // Largest value observed is 34, e.g., running - // P2PTestConductor.LocalP2PTest16To9 (peerconnection_unittests). + // PeerConnectionIntegrationTest.SendAndReceive16To9AspectRatio. black_frame_ = CheckFrameColorYuv(0, 48, 128, 128, 128, 128, &frame); // Treat unexpected frame size as error. ++num_rendered_frames_; diff --git a/webrtc/pc/BUILD.gn b/webrtc/pc/BUILD.gn index 5e38c6b9dd..75a644c1ed 100644 --- a/webrtc/pc/BUILD.gn +++ b/webrtc/pc/BUILD.gn @@ -88,6 +88,8 @@ rtc_static_library("libjingle_peerconnection") { "datachannel.h", "dtmfsender.cc", "dtmfsender.h", + "iceserverparsing.cc", + "iceserverparsing.h", "jsepicecandidate.cc", "jsepsessiondescription.cc", "localaudiosource.cc", @@ -278,11 +280,12 @@ if (rtc_include_tests) { "datachannel_unittest.cc", "dtmfsender_unittest.cc", "fakemediacontroller.h", + "iceserverparsing_unittest.cc", "jsepsessiondescription_unittest.cc", "localaudiosource_unittest.cc", "mediaconstraintsinterface_unittest.cc", "mediastream_unittest.cc", - "peerconnection_unittest.cc", + "peerconnection_integrationtest.cc", "peerconnectionendtoend_unittest.cc", "peerconnectionfactory_unittest.cc", "peerconnectioninterface_unittest.cc", diff --git a/webrtc/pc/iceserverparsing.cc b/webrtc/pc/iceserverparsing.cc new file mode 100644 index 0000000000..5769cee94e --- /dev/null +++ b/webrtc/pc/iceserverparsing.cc @@ -0,0 +1,294 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/pc/iceserverparsing.h" + +#include // For std::isdigit. +#include + +#include "webrtc/base/arraysize.h" + +namespace webrtc { + +// The min number of tokens must present in Turn host uri. +// e.g. user@turn.example.org +static const size_t kTurnHostTokensNum = 2; +// Number of tokens must be preset when TURN uri has transport param. +static const size_t kTurnTransportTokensNum = 2; +// The default stun port. +static const int kDefaultStunPort = 3478; +static const int kDefaultStunTlsPort = 5349; +static const char kTransport[] = "transport"; + +// NOTE: Must be in the same order as the ServiceType enum. +static const char* kValidIceServiceTypes[] = {"stun", "stuns", "turn", "turns"}; + +// NOTE: A loop below assumes that the first value of this enum is 0 and all +// other values are incremental. +enum ServiceType { + STUN = 0, // Indicates a STUN server. + STUNS, // Indicates a STUN server used with a TLS session. + TURN, // Indicates a TURN server + TURNS, // Indicates a TURN server used with a TLS session. + INVALID, // Unknown. +}; +static_assert(INVALID == arraysize(kValidIceServiceTypes), + "kValidIceServiceTypes must have as many strings as ServiceType " + "has values."); + +// |in_str| should be of format +// stunURI = scheme ":" stun-host [ ":" stun-port ] +// scheme = "stun" / "stuns" +// stun-host = IP-literal / IPv4address / reg-name +// stun-port = *DIGIT +// +// draft-petithuguenin-behave-turn-uris-01 +// turnURI = scheme ":" turn-host [ ":" turn-port ] +// turn-host = username@IP-literal / IPv4address / reg-name +static bool GetServiceTypeAndHostnameFromUri(const std::string& in_str, + ServiceType* service_type, + std::string* hostname) { + const std::string::size_type colonpos = in_str.find(':'); + if (colonpos == std::string::npos) { + LOG(LS_WARNING) << "Missing ':' in ICE URI: " << in_str; + return false; + } + if ((colonpos + 1) == in_str.length()) { + LOG(LS_WARNING) << "Empty hostname in ICE URI: " << in_str; + return false; + } + *service_type = INVALID; + for (size_t i = 0; i < arraysize(kValidIceServiceTypes); ++i) { + if (in_str.compare(0, colonpos, kValidIceServiceTypes[i]) == 0) { + *service_type = static_cast(i); + break; + } + } + if (*service_type == INVALID) { + return false; + } + *hostname = in_str.substr(colonpos + 1, std::string::npos); + return true; +} + +static bool ParsePort(const std::string& in_str, int* port) { + // Make sure port only contains digits. FromString doesn't check this. + for (const char& c : in_str) { + if (!std::isdigit(c)) { + return false; + } + } + return rtc::FromString(in_str, port); +} + +// This method parses IPv6 and IPv4 literal strings, along with hostnames in +// standard hostname:port format. +// Consider following formats as correct. +// |hostname:port|, |[IPV6 address]:port|, |IPv4 address|:port, +// |hostname|, |[IPv6 address]|, |IPv4 address|. +static bool ParseHostnameAndPortFromString(const std::string& in_str, + std::string* host, + int* port) { + RTC_DCHECK(host->empty()); + if (in_str.at(0) == '[') { + std::string::size_type closebracket = in_str.rfind(']'); + if (closebracket != std::string::npos) { + std::string::size_type colonpos = in_str.find(':', closebracket); + if (std::string::npos != colonpos) { + if (!ParsePort(in_str.substr(closebracket + 2, std::string::npos), + port)) { + return false; + } + } + *host = in_str.substr(1, closebracket - 1); + } else { + return false; + } + } else { + std::string::size_type colonpos = in_str.find(':'); + if (std::string::npos != colonpos) { + if (!ParsePort(in_str.substr(colonpos + 1, std::string::npos), port)) { + return false; + } + *host = in_str.substr(0, colonpos); + } else { + *host = in_str; + } + } + return !host->empty(); +} + +// Adds a STUN or TURN server to the appropriate list, +// by parsing |url| and using the username/password in |server|. +static RTCErrorType ParseIceServerUrl( + const PeerConnectionInterface::IceServer& server, + const std::string& url, + cricket::ServerAddresses* stun_servers, + std::vector* turn_servers) { + // draft-nandakumar-rtcweb-stun-uri-01 + // stunURI = scheme ":" stun-host [ ":" stun-port ] + // scheme = "stun" / "stuns" + // stun-host = IP-literal / IPv4address / reg-name + // stun-port = *DIGIT + + // draft-petithuguenin-behave-turn-uris-01 + // turnURI = scheme ":" turn-host [ ":" turn-port ] + // [ "?transport=" transport ] + // scheme = "turn" / "turns" + // transport = "udp" / "tcp" / transport-ext + // transport-ext = 1*unreserved + // turn-host = IP-literal / IPv4address / reg-name + // turn-port = *DIGIT + RTC_DCHECK(stun_servers != nullptr); + RTC_DCHECK(turn_servers != nullptr); + std::vector tokens; + cricket::ProtocolType turn_transport_type = cricket::PROTO_UDP; + RTC_DCHECK(!url.empty()); + rtc::tokenize_with_empty_tokens(url, '?', &tokens); + std::string uri_without_transport = tokens[0]; + // Let's look into transport= param, if it exists. + if (tokens.size() == kTurnTransportTokensNum) { // ?transport= is present. + std::string uri_transport_param = tokens[1]; + rtc::tokenize_with_empty_tokens(uri_transport_param, '=', &tokens); + if (tokens[0] != kTransport) { + LOG(LS_WARNING) << "Invalid transport parameter key."; + return RTCErrorType::SYNTAX_ERROR; + } + if (tokens.size() < 2) { + LOG(LS_WARNING) << "Transport parameter missing value."; + return RTCErrorType::SYNTAX_ERROR; + } + if (!cricket::StringToProto(tokens[1].c_str(), &turn_transport_type) || + (turn_transport_type != cricket::PROTO_UDP && + turn_transport_type != cricket::PROTO_TCP)) { + LOG(LS_WARNING) << "Transport parameter should always be udp or tcp."; + return RTCErrorType::SYNTAX_ERROR; + } + } + + std::string hoststring; + ServiceType service_type; + if (!GetServiceTypeAndHostnameFromUri(uri_without_transport, &service_type, + &hoststring)) { + LOG(LS_WARNING) << "Invalid transport parameter in ICE URI: " << url; + return RTCErrorType::SYNTAX_ERROR; + } + + // GetServiceTypeAndHostnameFromUri should never give an empty hoststring + RTC_DCHECK(!hoststring.empty()); + + // Let's break hostname. + tokens.clear(); + rtc::tokenize_with_empty_tokens(hoststring, '@', &tokens); + + std::string username(server.username); + if (tokens.size() > kTurnHostTokensNum) { + LOG(LS_WARNING) << "Invalid user@hostname format: " << hoststring; + return RTCErrorType::SYNTAX_ERROR; + } + if (tokens.size() == kTurnHostTokensNum) { + if (tokens[0].empty() || tokens[1].empty()) { + LOG(LS_WARNING) << "Invalid user@hostname format: " << hoststring; + return RTCErrorType::SYNTAX_ERROR; + } + username.assign(rtc::s_url_decode(tokens[0])); + hoststring = tokens[1]; + } else { + hoststring = tokens[0]; + } + + int port = kDefaultStunPort; + if (service_type == TURNS) { + port = kDefaultStunTlsPort; + turn_transport_type = cricket::PROTO_TLS; + } + + std::string address; + if (!ParseHostnameAndPortFromString(hoststring, &address, &port)) { + LOG(WARNING) << "Invalid hostname format: " << uri_without_transport; + return RTCErrorType::SYNTAX_ERROR; + } + + if (port <= 0 || port > 0xffff) { + LOG(WARNING) << "Invalid port: " << port; + return RTCErrorType::SYNTAX_ERROR; + } + + switch (service_type) { + case STUN: + case STUNS: + stun_servers->insert(rtc::SocketAddress(address, port)); + break; + case TURN: + case TURNS: { + if (username.empty() || server.password.empty()) { + // The WebRTC spec requires throwing an InvalidAccessError when username + // or credential are ommitted; this is the native equivalent. + return RTCErrorType::INVALID_PARAMETER; + } + cricket::RelayServerConfig config = cricket::RelayServerConfig( + address, port, username, server.password, turn_transport_type); + if (server.tls_cert_policy == + PeerConnectionInterface::kTlsCertPolicyInsecureNoCheck) { + config.tls_cert_policy = + cricket::TlsCertPolicy::TLS_CERT_POLICY_INSECURE_NO_CHECK; + } + turn_servers->push_back(config); + break; + } + default: + // We shouldn't get to this point with an invalid service_type, we should + // have returned an error already. + RTC_NOTREACHED() << "Unexpected service type"; + return RTCErrorType::INTERNAL_ERROR; + } + return RTCErrorType::NONE; +} + +RTCErrorType ParseIceServers( + const PeerConnectionInterface::IceServers& servers, + cricket::ServerAddresses* stun_servers, + std::vector* turn_servers) { + for (const PeerConnectionInterface::IceServer& server : servers) { + if (!server.urls.empty()) { + for (const std::string& url : server.urls) { + if (url.empty()) { + LOG(LS_ERROR) << "Empty uri."; + return RTCErrorType::SYNTAX_ERROR; + } + RTCErrorType err = + ParseIceServerUrl(server, url, stun_servers, turn_servers); + if (err != RTCErrorType::NONE) { + return err; + } + } + } else if (!server.uri.empty()) { + // Fallback to old .uri if new .urls isn't present. + RTCErrorType err = + ParseIceServerUrl(server, server.uri, stun_servers, turn_servers); + if (err != RTCErrorType::NONE) { + return err; + } + } else { + LOG(LS_ERROR) << "Empty uri."; + return RTCErrorType::SYNTAX_ERROR; + } + } + // Candidates must have unique priorities, so that connectivity checks + // are performed in a well-defined order. + int priority = static_cast(turn_servers->size() - 1); + for (cricket::RelayServerConfig& turn_server : *turn_servers) { + // First in the list gets highest priority. + turn_server.priority = priority--; + } + return RTCErrorType::NONE; +} + +} // namespace webrtc diff --git a/webrtc/pc/iceserverparsing.h b/webrtc/pc/iceserverparsing.h new file mode 100644 index 0000000000..c8feefd090 --- /dev/null +++ b/webrtc/pc/iceserverparsing.h @@ -0,0 +1,34 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_PC_ICESERVERPARSING_H_ +#define WEBRTC_PC_ICESERVERPARSING_H_ + +#include + +#include "webrtc/api/peerconnectioninterface.h" +#include "webrtc/api/rtcerror.h" + +namespace webrtc { + +// Parses the URLs for each server in |servers| to build |stun_servers| and +// |turn_servers|. Can return SYNTAX_ERROR if the URL is malformed, or +// INVALID_PARAMETER if a TURN server is missing |username| or |password|. +// +// Intended to be used to convert/validate the servers passed into a +// PeerConnection through RTCConfiguration. +RTCErrorType ParseIceServers( + const PeerConnectionInterface::IceServers& servers, + cricket::ServerAddresses* stun_servers, + std::vector* turn_servers); + +} // namespace webrtc + +#endif // WEBRTC_PC_ICESERVERPARSING_H_ diff --git a/webrtc/pc/iceserverparsing_unittest.cc b/webrtc/pc/iceserverparsing_unittest.cc new file mode 100644 index 0000000000..045018918a --- /dev/null +++ b/webrtc/pc/iceserverparsing_unittest.cc @@ -0,0 +1,231 @@ +/* + * Copyright 2012 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include + +#include "webrtc/base/gunit.h" +#include "webrtc/pc/iceserverparsing.h" + +namespace webrtc { + +class IceServerParsingTest : public testing::Test { + public: + // Convenience functions for parsing a single URL. Result is stored in + // |stun_servers_| and |turn_servers_|. + bool ParseUrl(const std::string& url) { + return ParseUrl(url, std::string(), std::string()); + } + + bool ParseTurnUrl(const std::string& url) { + return ParseUrl(url, "username", "password"); + } + + bool ParseUrl(const std::string& url, + const std::string& username, + const std::string& password) { + return ParseUrl( + url, username, password, + PeerConnectionInterface::TlsCertPolicy::kTlsCertPolicySecure); + } + + bool ParseUrl(const std::string& url, + const std::string& username, + const std::string& password, + PeerConnectionInterface::TlsCertPolicy tls_certificate_policy) { + stun_servers_.clear(); + turn_servers_.clear(); + PeerConnectionInterface::IceServers servers; + PeerConnectionInterface::IceServer server; + server.urls.push_back(url); + server.username = username; + server.password = password; + server.tls_cert_policy = tls_certificate_policy; + servers.push_back(server); + return webrtc::ParseIceServers(servers, &stun_servers_, &turn_servers_) == + webrtc::RTCErrorType::NONE; + } + + protected: + cricket::ServerAddresses stun_servers_; + std::vector turn_servers_; +}; + +// Make sure all STUN/TURN prefixes are parsed correctly. +TEST_F(IceServerParsingTest, ParseStunPrefixes) { + EXPECT_TRUE(ParseUrl("stun:hostname")); + EXPECT_EQ(1U, stun_servers_.size()); + EXPECT_EQ(0U, turn_servers_.size()); + + EXPECT_TRUE(ParseUrl("stuns:hostname")); + EXPECT_EQ(1U, stun_servers_.size()); + EXPECT_EQ(0U, turn_servers_.size()); + + EXPECT_TRUE(ParseTurnUrl("turn:hostname")); + EXPECT_EQ(0U, stun_servers_.size()); + EXPECT_EQ(1U, turn_servers_.size()); + EXPECT_EQ(cricket::PROTO_UDP, turn_servers_[0].ports[0].proto); + + EXPECT_TRUE(ParseTurnUrl("turns:hostname")); + EXPECT_EQ(0U, stun_servers_.size()); + EXPECT_EQ(1U, turn_servers_.size()); + EXPECT_EQ(cricket::PROTO_TLS, turn_servers_[0].ports[0].proto); + EXPECT_TRUE(turn_servers_[0].tls_cert_policy == + cricket::TlsCertPolicy::TLS_CERT_POLICY_SECURE); + + EXPECT_TRUE(ParseUrl( + "turns:hostname", "username", "password", + PeerConnectionInterface::TlsCertPolicy::kTlsCertPolicyInsecureNoCheck)); + EXPECT_EQ(0U, stun_servers_.size()); + EXPECT_EQ(1U, turn_servers_.size()); + EXPECT_TRUE(turn_servers_[0].tls_cert_policy == + cricket::TlsCertPolicy::TLS_CERT_POLICY_INSECURE_NO_CHECK); + EXPECT_EQ(cricket::PROTO_TLS, turn_servers_[0].ports[0].proto); + + // invalid prefixes + EXPECT_FALSE(ParseUrl("stunn:hostname")); + EXPECT_FALSE(ParseUrl(":hostname")); + EXPECT_FALSE(ParseUrl(":")); + EXPECT_FALSE(ParseUrl("")); +} + +TEST_F(IceServerParsingTest, VerifyDefaults) { + // TURNS defaults + EXPECT_TRUE(ParseTurnUrl("turns:hostname")); + EXPECT_EQ(1U, turn_servers_.size()); + EXPECT_EQ(5349, turn_servers_[0].ports[0].address.port()); + EXPECT_EQ(cricket::PROTO_TLS, turn_servers_[0].ports[0].proto); + + // TURN defaults + EXPECT_TRUE(ParseTurnUrl("turn:hostname")); + EXPECT_EQ(1U, turn_servers_.size()); + EXPECT_EQ(3478, turn_servers_[0].ports[0].address.port()); + EXPECT_EQ(cricket::PROTO_UDP, turn_servers_[0].ports[0].proto); + + // STUN defaults + EXPECT_TRUE(ParseUrl("stun:hostname")); + EXPECT_EQ(1U, stun_servers_.size()); + EXPECT_EQ(3478, stun_servers_.begin()->port()); +} + +// Check that the 6 combinations of IPv4/IPv6/hostname and with/without port +// can be parsed correctly. +TEST_F(IceServerParsingTest, ParseHostnameAndPort) { + EXPECT_TRUE(ParseUrl("stun:1.2.3.4:1234")); + EXPECT_EQ(1U, stun_servers_.size()); + EXPECT_EQ("1.2.3.4", stun_servers_.begin()->hostname()); + EXPECT_EQ(1234, stun_servers_.begin()->port()); + + EXPECT_TRUE(ParseUrl("stun:[1:2:3:4:5:6:7:8]:4321")); + EXPECT_EQ(1U, stun_servers_.size()); + EXPECT_EQ("1:2:3:4:5:6:7:8", stun_servers_.begin()->hostname()); + EXPECT_EQ(4321, stun_servers_.begin()->port()); + + EXPECT_TRUE(ParseUrl("stun:hostname:9999")); + EXPECT_EQ(1U, stun_servers_.size()); + EXPECT_EQ("hostname", stun_servers_.begin()->hostname()); + EXPECT_EQ(9999, stun_servers_.begin()->port()); + + EXPECT_TRUE(ParseUrl("stun:1.2.3.4")); + EXPECT_EQ(1U, stun_servers_.size()); + EXPECT_EQ("1.2.3.4", stun_servers_.begin()->hostname()); + EXPECT_EQ(3478, stun_servers_.begin()->port()); + + EXPECT_TRUE(ParseUrl("stun:[1:2:3:4:5:6:7:8]")); + EXPECT_EQ(1U, stun_servers_.size()); + EXPECT_EQ("1:2:3:4:5:6:7:8", stun_servers_.begin()->hostname()); + EXPECT_EQ(3478, stun_servers_.begin()->port()); + + EXPECT_TRUE(ParseUrl("stun:hostname")); + EXPECT_EQ(1U, stun_servers_.size()); + EXPECT_EQ("hostname", stun_servers_.begin()->hostname()); + EXPECT_EQ(3478, stun_servers_.begin()->port()); + + // Try some invalid hostname:port strings. + EXPECT_FALSE(ParseUrl("stun:hostname:99a99")); + EXPECT_FALSE(ParseUrl("stun:hostname:-1")); + EXPECT_FALSE(ParseUrl("stun:hostname:port:more")); + EXPECT_FALSE(ParseUrl("stun:hostname:port more")); + EXPECT_FALSE(ParseUrl("stun:hostname:")); + EXPECT_FALSE(ParseUrl("stun:[1:2:3:4:5:6:7:8]junk:1000")); + EXPECT_FALSE(ParseUrl("stun::5555")); + EXPECT_FALSE(ParseUrl("stun:")); +} + +// Test parsing the "?transport=xxx" part of the URL. +TEST_F(IceServerParsingTest, ParseTransport) { + EXPECT_TRUE(ParseTurnUrl("turn:hostname:1234?transport=tcp")); + EXPECT_EQ(1U, turn_servers_.size()); + EXPECT_EQ(cricket::PROTO_TCP, turn_servers_[0].ports[0].proto); + + EXPECT_TRUE(ParseTurnUrl("turn:hostname?transport=udp")); + EXPECT_EQ(1U, turn_servers_.size()); + EXPECT_EQ(cricket::PROTO_UDP, turn_servers_[0].ports[0].proto); + + EXPECT_FALSE(ParseTurnUrl("turn:hostname?transport=invalid")); + EXPECT_FALSE(ParseTurnUrl("turn:hostname?transport=")); + EXPECT_FALSE(ParseTurnUrl("turn:hostname?=")); + EXPECT_FALSE(ParseTurnUrl("turn:hostname?")); + EXPECT_FALSE(ParseTurnUrl("?")); +} + +// Test parsing ICE username contained in URL. +TEST_F(IceServerParsingTest, ParseUsername) { + EXPECT_TRUE(ParseTurnUrl("turn:user@hostname")); + EXPECT_EQ(1U, turn_servers_.size()); + EXPECT_EQ("user", turn_servers_[0].credentials.username); + + EXPECT_FALSE(ParseTurnUrl("turn:@hostname")); + EXPECT_FALSE(ParseTurnUrl("turn:username@")); + EXPECT_FALSE(ParseTurnUrl("turn:@")); + EXPECT_FALSE(ParseTurnUrl("turn:user@name@hostname")); +} + +// Test that username and password from IceServer is copied into the resulting +// RelayServerConfig. +TEST_F(IceServerParsingTest, CopyUsernameAndPasswordFromIceServer) { + EXPECT_TRUE(ParseUrl("turn:hostname", "username", "password")); + EXPECT_EQ(1U, turn_servers_.size()); + EXPECT_EQ("username", turn_servers_[0].credentials.username); + EXPECT_EQ("password", turn_servers_[0].credentials.password); +} + +// Ensure that if a server has multiple URLs, each one is parsed. +TEST_F(IceServerParsingTest, ParseMultipleUrls) { + PeerConnectionInterface::IceServers servers; + PeerConnectionInterface::IceServer server; + server.urls.push_back("stun:hostname"); + server.urls.push_back("turn:hostname"); + server.username = "foo"; + server.password = "bar"; + servers.push_back(server); + EXPECT_EQ(webrtc::RTCErrorType::NONE, + webrtc::ParseIceServers(servers, &stun_servers_, &turn_servers_)); + EXPECT_EQ(1U, stun_servers_.size()); + EXPECT_EQ(1U, turn_servers_.size()); +} + +// Ensure that TURN servers are given unique priorities, +// so that their resulting candidates have unique priorities. +TEST_F(IceServerParsingTest, TurnServerPrioritiesUnique) { + PeerConnectionInterface::IceServers servers; + PeerConnectionInterface::IceServer server; + server.urls.push_back("turn:hostname"); + server.urls.push_back("turn:hostname2"); + server.username = "foo"; + server.password = "bar"; + servers.push_back(server); + EXPECT_EQ(webrtc::RTCErrorType::NONE, + webrtc::ParseIceServers(servers, &stun_servers_, &turn_servers_)); + EXPECT_EQ(2U, turn_servers_.size()); + EXPECT_NE(turn_servers_[0].priority, turn_servers_[1].priority); +} + +} // namespace webrtc diff --git a/webrtc/pc/peerconnection.cc b/webrtc/pc/peerconnection.cc index 7abbb44b0b..a3981039e6 100644 --- a/webrtc/pc/peerconnection.cc +++ b/webrtc/pc/peerconnection.cc @@ -11,7 +11,6 @@ #include "webrtc/pc/peerconnection.h" #include -#include // for isdigit #include #include @@ -20,7 +19,6 @@ #include "webrtc/api/mediaconstraintsinterface.h" #include "webrtc/api/mediastreamproxy.h" #include "webrtc/api/mediastreamtrackproxy.h" -#include "webrtc/base/arraysize.h" #include "webrtc/base/bind.h" #include "webrtc/base/checks.h" #include "webrtc/base/logging.h" @@ -62,35 +60,9 @@ static const char kDefaultStreamLabel[] = "default"; static const char kDefaultAudioTrackLabel[] = "defaulta0"; static const char kDefaultVideoTrackLabel[] = "defaultv0"; -// The min number of tokens must present in Turn host uri. -// e.g. user@turn.example.org -static const size_t kTurnHostTokensNum = 2; -// Number of tokens must be preset when TURN uri has transport param. -static const size_t kTurnTransportTokensNum = 2; -// The default stun port. -static const int kDefaultStunPort = 3478; -static const int kDefaultStunTlsPort = 5349; -static const char kTransport[] = "transport"; - -// NOTE: Must be in the same order as the ServiceType enum. -static const char* kValidIceServiceTypes[] = {"stun", "stuns", "turn", "turns"}; - // The length of RTCP CNAMEs. static const int kRtcpCnameLength = 16; -// NOTE: A loop below assumes that the first value of this enum is 0 and all -// other values are incremental. -enum ServiceType { - STUN = 0, // Indicates a STUN server. - STUNS, // Indicates a STUN server used with a TLS session. - TURN, // Indicates a TURN server - TURNS, // Indicates a TURN server used with a TLS session. - INVALID, // Unknown. -}; -static_assert(INVALID == arraysize(kValidIceServiceTypes), - "kValidIceServiceTypes must have as many strings as ServiceType " - "has values."); - enum { MSG_SET_SESSIONDESCRIPTION_SUCCESS = 0, MSG_SET_SESSIONDESCRIPTION_FAILED, @@ -127,216 +99,6 @@ struct GetStatsMsg : public rtc::MessageData { rtc::scoped_refptr track; }; -// |in_str| should be of format -// stunURI = scheme ":" stun-host [ ":" stun-port ] -// scheme = "stun" / "stuns" -// stun-host = IP-literal / IPv4address / reg-name -// stun-port = *DIGIT -// -// draft-petithuguenin-behave-turn-uris-01 -// turnURI = scheme ":" turn-host [ ":" turn-port ] -// turn-host = username@IP-literal / IPv4address / reg-name -bool GetServiceTypeAndHostnameFromUri(const std::string& in_str, - ServiceType* service_type, - std::string* hostname) { - const std::string::size_type colonpos = in_str.find(':'); - if (colonpos == std::string::npos) { - LOG(LS_WARNING) << "Missing ':' in ICE URI: " << in_str; - return false; - } - if ((colonpos + 1) == in_str.length()) { - LOG(LS_WARNING) << "Empty hostname in ICE URI: " << in_str; - return false; - } - *service_type = INVALID; - for (size_t i = 0; i < arraysize(kValidIceServiceTypes); ++i) { - if (in_str.compare(0, colonpos, kValidIceServiceTypes[i]) == 0) { - *service_type = static_cast(i); - break; - } - } - if (*service_type == INVALID) { - return false; - } - *hostname = in_str.substr(colonpos + 1, std::string::npos); - return true; -} - -bool ParsePort(const std::string& in_str, int* port) { - // Make sure port only contains digits. FromString doesn't check this. - for (const char& c : in_str) { - if (!std::isdigit(c)) { - return false; - } - } - return rtc::FromString(in_str, port); -} - -// This method parses IPv6 and IPv4 literal strings, along with hostnames in -// standard hostname:port format. -// Consider following formats as correct. -// |hostname:port|, |[IPV6 address]:port|, |IPv4 address|:port, -// |hostname|, |[IPv6 address]|, |IPv4 address|. -bool ParseHostnameAndPortFromString(const std::string& in_str, - std::string* host, - int* port) { - RTC_DCHECK(host->empty()); - if (in_str.at(0) == '[') { - std::string::size_type closebracket = in_str.rfind(']'); - if (closebracket != std::string::npos) { - std::string::size_type colonpos = in_str.find(':', closebracket); - if (std::string::npos != colonpos) { - if (!ParsePort(in_str.substr(closebracket + 2, std::string::npos), - port)) { - return false; - } - } - *host = in_str.substr(1, closebracket - 1); - } else { - return false; - } - } else { - std::string::size_type colonpos = in_str.find(':'); - if (std::string::npos != colonpos) { - if (!ParsePort(in_str.substr(colonpos + 1, std::string::npos), port)) { - return false; - } - *host = in_str.substr(0, colonpos); - } else { - *host = in_str; - } - } - return !host->empty(); -} - -// Adds a STUN or TURN server to the appropriate list, -// by parsing |url| and using the username/password in |server|. -RTCErrorType ParseIceServerUrl( - const PeerConnectionInterface::IceServer& server, - const std::string& url, - cricket::ServerAddresses* stun_servers, - std::vector* turn_servers) { - // draft-nandakumar-rtcweb-stun-uri-01 - // stunURI = scheme ":" stun-host [ ":" stun-port ] - // scheme = "stun" / "stuns" - // stun-host = IP-literal / IPv4address / reg-name - // stun-port = *DIGIT - - // draft-petithuguenin-behave-turn-uris-01 - // turnURI = scheme ":" turn-host [ ":" turn-port ] - // [ "?transport=" transport ] - // scheme = "turn" / "turns" - // transport = "udp" / "tcp" / transport-ext - // transport-ext = 1*unreserved - // turn-host = IP-literal / IPv4address / reg-name - // turn-port = *DIGIT - RTC_DCHECK(stun_servers != nullptr); - RTC_DCHECK(turn_servers != nullptr); - std::vector tokens; - cricket::ProtocolType turn_transport_type = cricket::PROTO_UDP; - RTC_DCHECK(!url.empty()); - rtc::tokenize_with_empty_tokens(url, '?', &tokens); - std::string uri_without_transport = tokens[0]; - // Let's look into transport= param, if it exists. - if (tokens.size() == kTurnTransportTokensNum) { // ?transport= is present. - std::string uri_transport_param = tokens[1]; - rtc::tokenize_with_empty_tokens(uri_transport_param, '=', &tokens); - if (tokens[0] != kTransport) { - LOG(LS_WARNING) << "Invalid transport parameter key."; - return RTCErrorType::SYNTAX_ERROR; - } - if (tokens.size() < 2) { - LOG(LS_WARNING) << "Transport parameter missing value."; - return RTCErrorType::SYNTAX_ERROR; - } - if (!cricket::StringToProto(tokens[1].c_str(), &turn_transport_type) || - (turn_transport_type != cricket::PROTO_UDP && - turn_transport_type != cricket::PROTO_TCP)) { - LOG(LS_WARNING) << "Transport parameter should always be udp or tcp."; - return RTCErrorType::SYNTAX_ERROR; - } - } - - std::string hoststring; - ServiceType service_type; - if (!GetServiceTypeAndHostnameFromUri(uri_without_transport, - &service_type, - &hoststring)) { - LOG(LS_WARNING) << "Invalid transport parameter in ICE URI: " << url; - return RTCErrorType::SYNTAX_ERROR; - } - - // GetServiceTypeAndHostnameFromUri should never give an empty hoststring - RTC_DCHECK(!hoststring.empty()); - - // Let's break hostname. - tokens.clear(); - rtc::tokenize_with_empty_tokens(hoststring, '@', &tokens); - - std::string username(server.username); - if (tokens.size() > kTurnHostTokensNum) { - LOG(LS_WARNING) << "Invalid user@hostname format: " << hoststring; - return RTCErrorType::SYNTAX_ERROR; - } - if (tokens.size() == kTurnHostTokensNum) { - if (tokens[0].empty() || tokens[1].empty()) { - LOG(LS_WARNING) << "Invalid user@hostname format: " << hoststring; - return RTCErrorType::SYNTAX_ERROR; - } - username.assign(rtc::s_url_decode(tokens[0])); - hoststring = tokens[1]; - } else { - hoststring = tokens[0]; - } - - int port = kDefaultStunPort; - if (service_type == TURNS) { - port = kDefaultStunTlsPort; - turn_transport_type = cricket::PROTO_TLS; - } - - std::string address; - if (!ParseHostnameAndPortFromString(hoststring, &address, &port)) { - LOG(WARNING) << "Invalid hostname format: " << uri_without_transport; - return RTCErrorType::SYNTAX_ERROR; - } - - if (port <= 0 || port > 0xffff) { - LOG(WARNING) << "Invalid port: " << port; - return RTCErrorType::SYNTAX_ERROR; - } - - switch (service_type) { - case STUN: - case STUNS: - stun_servers->insert(rtc::SocketAddress(address, port)); - break; - case TURN: - case TURNS: { - if (username.empty() || server.password.empty()) { - // The WebRTC spec requires throwing an InvalidAccessError when username - // or credential are ommitted; this is the native equivalent. - return RTCErrorType::INVALID_PARAMETER; - } - cricket::RelayServerConfig config = cricket::RelayServerConfig( - address, port, username, server.password, turn_transport_type); - if (server.tls_cert_policy == - PeerConnectionInterface::kTlsCertPolicyInsecureNoCheck) { - config.tls_cert_policy = - cricket::TlsCertPolicy::TLS_CERT_POLICY_INSECURE_NO_CHECK; - } - turn_servers->push_back(config); - break; - } - default: - // We shouldn't get to this point with an invalid service_type, we should - // have returned an error already. - RTC_NOTREACHED() << "Unexpected service type"; - return RTCErrorType::INTERNAL_ERROR; - } - return RTCErrorType::NONE; -} - // Check if we can send |new_stream| on a PeerConnection. bool CanAddLocalMediaStream(webrtc::StreamCollectionInterface* current_streams, webrtc::MediaStreamInterface* new_stream) { @@ -629,45 +391,6 @@ bool ParseConstraintsForAnswer(const MediaConstraintsInterface* constraints, return mandatory_constraints_satisfied == constraints->GetMandatory().size(); } -RTCErrorType ParseIceServers( - const PeerConnectionInterface::IceServers& servers, - cricket::ServerAddresses* stun_servers, - std::vector* turn_servers) { - for (const webrtc::PeerConnectionInterface::IceServer& server : servers) { - if (!server.urls.empty()) { - for (const std::string& url : server.urls) { - if (url.empty()) { - LOG(LS_ERROR) << "Empty uri."; - return RTCErrorType::SYNTAX_ERROR; - } - RTCErrorType err = - ParseIceServerUrl(server, url, stun_servers, turn_servers); - if (err != RTCErrorType::NONE) { - return err; - } - } - } else if (!server.uri.empty()) { - // Fallback to old .uri if new .urls isn't present. - RTCErrorType err = - ParseIceServerUrl(server, server.uri, stun_servers, turn_servers); - if (err != RTCErrorType::NONE) { - return err; - } - } else { - LOG(LS_ERROR) << "Empty uri."; - return RTCErrorType::SYNTAX_ERROR; - } - } - // Candidates must have unique priorities, so that connectivity checks - // are performed in a well-defined order. - int priority = static_cast(turn_servers->size() - 1); - for (cricket::RelayServerConfig& turn_server : *turn_servers) { - // First in the list gets highest priority. - turn_server.priority = priority--; - } - return RTCErrorType::NONE; -} - PeerConnection::PeerConnection(PeerConnectionFactory* factory) : factory_(factory), observer_(NULL), diff --git a/webrtc/pc/peerconnection.h b/webrtc/pc/peerconnection.h index 2aa634c700..02f56f6c68 100644 --- a/webrtc/pc/peerconnection.h +++ b/webrtc/pc/peerconnection.h @@ -17,6 +17,7 @@ #include #include "webrtc/api/peerconnectioninterface.h" +#include "webrtc/pc/iceserverparsing.h" #include "webrtc/pc/peerconnectionfactory.h" #include "webrtc/pc/rtcstatscollector.h" #include "webrtc/pc/rtpreceiver.h" @@ -53,14 +54,6 @@ bool ExtractMediaSessionOptions( bool ParseConstraintsForAnswer(const MediaConstraintsInterface* constraints, cricket::MediaSessionOptions* session_options); -// Parses the URLs for each server in |servers| to build |stun_servers| and -// |turn_servers|. Can return SYNTAX_ERROR if the URL is malformed, or -// INVALID_PARAMETER if a TURN server is missing |username| or |password|. -RTCErrorType ParseIceServers( - const PeerConnectionInterface::IceServers& servers, - cricket::ServerAddresses* stun_servers, - std::vector* turn_servers); - // PeerConnection implements the PeerConnectionInterface interface. // It uses WebRtcSession to implement the PeerConnection functionality. class PeerConnection : public PeerConnectionInterface, diff --git a/webrtc/pc/peerconnection_integrationtest.cc b/webrtc/pc/peerconnection_integrationtest.cc new file mode 100644 index 0000000000..8a4df9b189 --- /dev/null +++ b/webrtc/pc/peerconnection_integrationtest.cc @@ -0,0 +1,2706 @@ +/* + * Copyright 2012 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Disable for TSan v2, see +// https://code.google.com/p/webrtc/issues/detail?id=1205 for details. +#if !defined(THREAD_SANITIZER) + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "webrtc/api/fakemetricsobserver.h" +#include "webrtc/api/mediastreaminterface.h" +#include "webrtc/api/peerconnectioninterface.h" +#include "webrtc/api/test/fakeconstraints.h" +#include "webrtc/base/asyncinvoker.h" +#include "webrtc/base/fakenetwork.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/ssladapter.h" +#include "webrtc/base/sslstreamadapter.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/virtualsocketserver.h" +#include "webrtc/media/engine/fakewebrtcvideoengine.h" +#include "webrtc/p2p/base/p2pconstants.h" +#include "webrtc/p2p/base/portinterface.h" +#include "webrtc/p2p/base/sessiondescription.h" +#include "webrtc/p2p/base/testturnserver.h" +#include "webrtc/p2p/client/basicportallocator.h" +#include "webrtc/pc/dtmfsender.h" +#include "webrtc/pc/localaudiosource.h" +#include "webrtc/pc/mediasession.h" +#include "webrtc/pc/peerconnection.h" +#include "webrtc/pc/peerconnectionfactory.h" +#include "webrtc/pc/test/fakeaudiocapturemodule.h" +#include "webrtc/pc/test/fakeperiodicvideocapturer.h" +#include "webrtc/pc/test/fakertccertificategenerator.h" +#include "webrtc/pc/test/fakevideotrackrenderer.h" +#include "webrtc/pc/test/mockpeerconnectionobservers.h" + +using cricket::ContentInfo; +using cricket::FakeWebRtcVideoDecoder; +using cricket::FakeWebRtcVideoDecoderFactory; +using cricket::FakeWebRtcVideoEncoder; +using cricket::FakeWebRtcVideoEncoderFactory; +using cricket::MediaContentDescription; +using webrtc::DataBuffer; +using webrtc::DataChannelInterface; +using webrtc::DtmfSender; +using webrtc::DtmfSenderInterface; +using webrtc::DtmfSenderObserverInterface; +using webrtc::FakeConstraints; +using webrtc::MediaConstraintsInterface; +using webrtc::MediaStreamInterface; +using webrtc::MediaStreamTrackInterface; +using webrtc::MockCreateSessionDescriptionObserver; +using webrtc::MockDataChannelObserver; +using webrtc::MockSetSessionDescriptionObserver; +using webrtc::MockStatsObserver; +using webrtc::ObserverInterface; +using webrtc::PeerConnectionInterface; +using webrtc::PeerConnectionFactory; +using webrtc::SessionDescriptionInterface; +using webrtc::StreamCollectionInterface; + +namespace { + +static const int kDefaultTimeout = 10000; +static const int kMaxWaitForStatsMs = 3000; +static const int kMaxWaitForActivationMs = 5000; +static const int kMaxWaitForFramesMs = 10000; +// Default number of audio/video frames to wait for before considering a test +// successful. +static const int kDefaultExpectedAudioFrameCount = 3; +static const int kDefaultExpectedVideoFrameCount = 3; + +static const char kDefaultStreamLabel[] = "stream_label"; +static const char kDefaultVideoTrackId[] = "video_track"; +static const char kDefaultAudioTrackId[] = "audio_track"; +static const char kDataChannelLabel[] = "data_channel"; + +// SRTP cipher name negotiated by the tests. This must be updated if the +// default changes. +static const int kDefaultSrtpCryptoSuite = rtc::SRTP_AES128_CM_SHA1_32; +static const int kDefaultSrtpCryptoSuiteGcm = rtc::SRTP_AEAD_AES_256_GCM; + +// Helper function for constructing offer/answer options to initiate an ICE +// restart. +PeerConnectionInterface::RTCOfferAnswerOptions IceRestartOfferAnswerOptions() { + PeerConnectionInterface::RTCOfferAnswerOptions options; + options.ice_restart = true; + return options; +} + +class SignalingMessageReceiver { + public: + virtual void ReceiveSdpMessage(const std::string& type, + const std::string& msg) = 0; + virtual void ReceiveIceMessage(const std::string& sdp_mid, + int sdp_mline_index, + const std::string& msg) = 0; + + protected: + SignalingMessageReceiver() {} + virtual ~SignalingMessageReceiver() {} +}; + +class MockRtpReceiverObserver : public webrtc::RtpReceiverObserverInterface { + public: + explicit MockRtpReceiverObserver(cricket::MediaType media_type) + : expected_media_type_(media_type) {} + + void OnFirstPacketReceived(cricket::MediaType media_type) override { + ASSERT_EQ(expected_media_type_, media_type); + first_packet_received_ = true; + } + + bool first_packet_received() const { return first_packet_received_; } + + virtual ~MockRtpReceiverObserver() {} + + private: + bool first_packet_received_ = false; + cricket::MediaType expected_media_type_; +}; + +// Helper class that wraps a peer connection, observes it, and can accept +// signaling messages from another wrapper. +// +// Uses a fake network, fake A/V capture, and optionally fake +// encoders/decoders, though they aren't used by default since they don't +// advertise support of any codecs. +class PeerConnectionWrapper : public webrtc::PeerConnectionObserver, + public SignalingMessageReceiver, + public ObserverInterface { + public: + // Different factory methods for convenience. + // TODO(deadbeef): Could use the pattern of: + // + // PeerConnectionWrapper = + // WrapperBuilder.WithConfig(...).WithOptions(...).build(); + // + // To reduce some code duplication. + static PeerConnectionWrapper* CreateWithDtlsIdentityStore( + const std::string& debug_name, + std::unique_ptr cert_generator, + rtc::Thread* network_thread, + rtc::Thread* worker_thread) { + PeerConnectionWrapper* client(new PeerConnectionWrapper(debug_name)); + if (!client->Init(nullptr, nullptr, nullptr, std::move(cert_generator), + network_thread, worker_thread)) { + delete client; + return nullptr; + } + return client; + } + + static PeerConnectionWrapper* CreateWithConfig( + const std::string& debug_name, + const PeerConnectionInterface::RTCConfiguration& config, + rtc::Thread* network_thread, + rtc::Thread* worker_thread) { + std::unique_ptr cert_generator( + new FakeRTCCertificateGenerator()); + PeerConnectionWrapper* client(new PeerConnectionWrapper(debug_name)); + if (!client->Init(nullptr, nullptr, &config, std::move(cert_generator), + network_thread, worker_thread)) { + delete client; + return nullptr; + } + return client; + } + + static PeerConnectionWrapper* CreateWithOptions( + const std::string& debug_name, + const PeerConnectionFactory::Options& options, + rtc::Thread* network_thread, + rtc::Thread* worker_thread) { + std::unique_ptr cert_generator( + new FakeRTCCertificateGenerator()); + PeerConnectionWrapper* client(new PeerConnectionWrapper(debug_name)); + if (!client->Init(nullptr, &options, nullptr, std::move(cert_generator), + network_thread, worker_thread)) { + delete client; + return nullptr; + } + return client; + } + + static PeerConnectionWrapper* CreateWithConstraints( + const std::string& debug_name, + const MediaConstraintsInterface* constraints, + rtc::Thread* network_thread, + rtc::Thread* worker_thread) { + std::unique_ptr cert_generator( + new FakeRTCCertificateGenerator()); + PeerConnectionWrapper* client(new PeerConnectionWrapper(debug_name)); + if (!client->Init(constraints, nullptr, nullptr, std::move(cert_generator), + network_thread, worker_thread)) { + delete client; + return nullptr; + } + return client; + } + + webrtc::PeerConnectionInterface* pc() const { return peer_connection_.get(); } + + // If a signaling message receiver is set (via ConnectFakeSignaling), this + // will set the whole offer/answer exchange in motion. Just need to wait for + // the signaling state to reach "stable". + void CreateAndSetAndSignalOffer() { + auto offer = CreateOffer(); + ASSERT_NE(nullptr, offer); + EXPECT_TRUE(SetLocalDescriptionAndSendSdpMessage(std::move(offer))); + } + + // Sets the options to be used when CreateAndSetAndSignalOffer is called, or + // when a remote offer is received (via fake signaling) and an answer is + // generated. By default, uses default options. + void SetOfferAnswerOptions( + const PeerConnectionInterface::RTCOfferAnswerOptions& options) { + offer_answer_options_ = options; + } + + // Set a callback to be invoked when SDP is received via the fake signaling + // channel, which provides an opportunity to munge (modify) the SDP. This is + // used to test SDP being applied that a PeerConnection would normally not + // generate, but a non-JSEP endpoint might. + void SetReceivedSdpMunger( + std::function munger) { + received_sdp_munger_ = munger; + } + + // Siimlar to the above, but this is run on SDP immediately after it's + // generated. + void SetGeneratedSdpMunger( + std::function munger) { + generated_sdp_munger_ = munger; + } + + // Number of times the gathering state has transitioned to "gathering". + // Useful for telling if an ICE restart occurred as expected. + int transitions_to_gathering_state() const { + return transitions_to_gathering_state_; + } + + // TODO(deadbeef): Switch the majority of these tests to use AddTrack instead + // of AddStream since AddStream is deprecated. + void AddAudioVideoMediaStream() { + AddMediaStreamFromTracks(CreateLocalAudioTrack(), CreateLocalVideoTrack()); + } + + void AddAudioOnlyMediaStream() { + AddMediaStreamFromTracks(CreateLocalAudioTrack(), nullptr); + } + + void AddVideoOnlyMediaStream() { + AddMediaStreamFromTracks(nullptr, CreateLocalVideoTrack()); + } + + rtc::scoped_refptr CreateLocalAudioTrack() { + FakeConstraints constraints; + // Disable highpass filter so that we can get all the test audio frames. + constraints.AddMandatory(MediaConstraintsInterface::kHighpassFilter, false); + rtc::scoped_refptr source = + peer_connection_factory_->CreateAudioSource(&constraints); + // TODO(perkj): Test audio source when it is implemented. Currently audio + // always use the default input. + return peer_connection_factory_->CreateAudioTrack(kDefaultAudioTrackId, + source); + } + + rtc::scoped_refptr CreateLocalVideoTrack() { + return CreateLocalVideoTrackInternal( + kDefaultVideoTrackId, FakeConstraints(), webrtc::kVideoRotation_0); + } + + rtc::scoped_refptr + CreateLocalVideoTrackWithConstraints(const FakeConstraints& constraints) { + return CreateLocalVideoTrackInternal(kDefaultVideoTrackId, constraints, + webrtc::kVideoRotation_0); + } + + rtc::scoped_refptr + CreateLocalVideoTrackWithRotation(webrtc::VideoRotation rotation) { + return CreateLocalVideoTrackInternal(kDefaultVideoTrackId, + FakeConstraints(), rotation); + } + + rtc::scoped_refptr CreateLocalVideoTrackWithId( + const std::string& id) { + return CreateLocalVideoTrackInternal(id, FakeConstraints(), + webrtc::kVideoRotation_0); + } + + void AddMediaStreamFromTracks( + rtc::scoped_refptr audio, + rtc::scoped_refptr video) { + AddMediaStreamFromTracksWithLabel(audio, video, kDefaultStreamLabel); + } + + void AddMediaStreamFromTracksWithLabel( + rtc::scoped_refptr audio, + rtc::scoped_refptr video, + const std::string& stream_label) { + rtc::scoped_refptr stream = + peer_connection_factory_->CreateLocalMediaStream(stream_label); + if (audio) { + stream->AddTrack(audio); + } + if (video) { + stream->AddTrack(video); + } + EXPECT_TRUE(pc()->AddStream(stream)); + } + + bool SignalingStateStable() { + return pc()->signaling_state() == webrtc::PeerConnectionInterface::kStable; + } + + void CreateDataChannel() { CreateDataChannel(nullptr); } + + void CreateDataChannel(const webrtc::DataChannelInit* init) { + data_channel_ = pc()->CreateDataChannel(kDataChannelLabel, init); + ASSERT_TRUE(data_channel_.get() != nullptr); + data_observer_.reset(new MockDataChannelObserver(data_channel_)); + } + + DataChannelInterface* data_channel() { return data_channel_; } + const MockDataChannelObserver* data_observer() const { + return data_observer_.get(); + } + + int audio_frames_received() const { + return fake_audio_capture_module_->frames_received(); + } + + // Takes minimum of video frames received for each track. + // + // Can be used like: + // EXPECT_GE(expected_frames, min_video_frames_received_per_track()); + // + // To ensure that all video tracks received at least a certain number of + // frames. + int min_video_frames_received_per_track() const { + int min_frames = INT_MAX; + if (video_decoder_factory_enabled_) { + const std::vector& decoders = + fake_video_decoder_factory_->decoders(); + if (decoders.empty()) { + return 0; + } + for (FakeWebRtcVideoDecoder* decoder : decoders) { + min_frames = std::min(min_frames, decoder->GetNumFramesReceived()); + } + return min_frames; + } else { + if (fake_video_renderers_.empty()) { + return 0; + } + + for (const auto& pair : fake_video_renderers_) { + min_frames = std::min(min_frames, pair.second->num_rendered_frames()); + } + return min_frames; + } + } + + // In contrast to the above, sums the video frames received for all tracks. + // Can be used to verify that no video frames were received, or that the + // counts didn't increase. + int total_video_frames_received() const { + int total = 0; + if (video_decoder_factory_enabled_) { + const std::vector& decoders = + fake_video_decoder_factory_->decoders(); + for (const FakeWebRtcVideoDecoder* decoder : decoders) { + total += decoder->GetNumFramesReceived(); + } + } else { + for (const auto& pair : fake_video_renderers_) { + total += pair.second->num_rendered_frames(); + } + for (const auto& renderer : removed_fake_video_renderers_) { + total += renderer->num_rendered_frames(); + } + } + return total; + } + + // Returns a MockStatsObserver in a state after stats gathering finished, + // which can be used to access the gathered stats. + rtc::scoped_refptr GetStatsForTrack( + webrtc::MediaStreamTrackInterface* track) { + rtc::scoped_refptr observer( + new rtc::RefCountedObject()); + EXPECT_TRUE(peer_connection_->GetStats( + observer, nullptr, PeerConnectionInterface::kStatsOutputLevelStandard)); + EXPECT_TRUE_WAIT(observer->called(), kDefaultTimeout); + return observer; + } + + // Version that doesn't take a track "filter", and gathers all stats. + rtc::scoped_refptr GetStats() { + return GetStatsForTrack(nullptr); + } + + int rendered_width() { + EXPECT_FALSE(fake_video_renderers_.empty()); + return fake_video_renderers_.empty() + ? 0 + : fake_video_renderers_.begin()->second->width(); + } + + int rendered_height() { + EXPECT_FALSE(fake_video_renderers_.empty()); + return fake_video_renderers_.empty() + ? 0 + : fake_video_renderers_.begin()->second->height(); + } + + double rendered_aspect_ratio() { + if (rendered_height() == 0) { + return 0.0; + } + return static_cast(rendered_width()) / rendered_height(); + } + + webrtc::VideoRotation rendered_rotation() { + EXPECT_FALSE(fake_video_renderers_.empty()); + return fake_video_renderers_.empty() + ? webrtc::kVideoRotation_0 + : fake_video_renderers_.begin()->second->rotation(); + } + + int local_rendered_width() { + return local_video_renderer_ ? local_video_renderer_->width() : 0; + } + + int local_rendered_height() { + return local_video_renderer_ ? local_video_renderer_->height() : 0; + } + + double local_rendered_aspect_ratio() { + if (local_rendered_height() == 0) { + return 0.0; + } + return static_cast(local_rendered_width()) / + local_rendered_height(); + } + + size_t number_of_remote_streams() { + if (!pc()) { + return 0; + } + return pc()->remote_streams()->count(); + } + + StreamCollectionInterface* remote_streams() const { + if (!pc()) { + ADD_FAILURE(); + return nullptr; + } + return pc()->remote_streams(); + } + + StreamCollectionInterface* local_streams() { + if (!pc()) { + ADD_FAILURE(); + return nullptr; + } + return pc()->local_streams(); + } + + webrtc::PeerConnectionInterface::SignalingState signaling_state() { + return pc()->signaling_state(); + } + + webrtc::PeerConnectionInterface::IceConnectionState ice_connection_state() { + return pc()->ice_connection_state(); + } + + webrtc::PeerConnectionInterface::IceGatheringState ice_gathering_state() { + return pc()->ice_gathering_state(); + } + + // Returns a MockRtpReceiverObserver for each RtpReceiver returned by + // GetReceivers. They're updated automatically when a remote offer/answer + // from the fake signaling channel is applied, or when + // ResetRtpReceiverObservers below is called. + const std::vector>& + rtp_receiver_observers() { + return rtp_receiver_observers_; + } + + void ResetRtpReceiverObservers() { + rtp_receiver_observers_.clear(); + for (auto receiver : pc()->GetReceivers()) { + std::unique_ptr observer( + new MockRtpReceiverObserver(receiver->media_type())); + receiver->SetObserver(observer.get()); + rtp_receiver_observers_.push_back(std::move(observer)); + } + } + + private: + explicit PeerConnectionWrapper(const std::string& debug_name) + : debug_name_(debug_name) {} + + bool Init( + const MediaConstraintsInterface* constraints, + const PeerConnectionFactory::Options* options, + const PeerConnectionInterface::RTCConfiguration* config, + std::unique_ptr cert_generator, + rtc::Thread* network_thread, + rtc::Thread* worker_thread) { + // There's an error in this test code if Init ends up being called twice. + RTC_DCHECK(!peer_connection_); + RTC_DCHECK(!peer_connection_factory_); + + fake_network_manager_.reset(new rtc::FakeNetworkManager()); + fake_network_manager_->AddInterface(rtc::SocketAddress("192.168.1.1", 0)); + + std::unique_ptr port_allocator( + new cricket::BasicPortAllocator(fake_network_manager_.get())); + fake_audio_capture_module_ = FakeAudioCaptureModule::Create(); + if (!fake_audio_capture_module_) { + return false; + } + // Note that these factories don't end up getting used unless supported + // codecs are added to them. + fake_video_decoder_factory_ = new FakeWebRtcVideoDecoderFactory(); + fake_video_encoder_factory_ = new FakeWebRtcVideoEncoderFactory(); + rtc::Thread* const signaling_thread = rtc::Thread::Current(); + peer_connection_factory_ = webrtc::CreatePeerConnectionFactory( + network_thread, worker_thread, signaling_thread, + fake_audio_capture_module_, fake_video_encoder_factory_, + fake_video_decoder_factory_); + if (!peer_connection_factory_) { + return false; + } + if (options) { + peer_connection_factory_->SetOptions(*options); + } + peer_connection_ = + CreatePeerConnection(std::move(port_allocator), constraints, config, + std::move(cert_generator)); + return peer_connection_.get() != nullptr; + } + + rtc::scoped_refptr CreatePeerConnection( + std::unique_ptr port_allocator, + const MediaConstraintsInterface* constraints, + const PeerConnectionInterface::RTCConfiguration* config, + std::unique_ptr cert_generator) { + PeerConnectionInterface::RTCConfiguration modified_config; + // If |config| is null, this will result in a default configuration being + // used. + if (config) { + modified_config = *config; + } + // Disable resolution adaptation; we don't want it interfering with the + // test results. + // TODO(deadbeef): Do something more robust. Since we're testing for aspect + // ratios and not specific resolutions, is this even necessary? + modified_config.set_cpu_adaptation(false); + + return peer_connection_factory_->CreatePeerConnection( + modified_config, constraints, std::move(port_allocator), + std::move(cert_generator), this); + } + + void set_signaling_message_receiver( + SignalingMessageReceiver* signaling_message_receiver) { + signaling_message_receiver_ = signaling_message_receiver; + } + + void set_signaling_delay_ms(int delay_ms) { signaling_delay_ms_ = delay_ms; } + + void EnableVideoDecoderFactory() { + video_decoder_factory_enabled_ = true; + fake_video_decoder_factory_->AddSupportedVideoCodecType( + webrtc::kVideoCodecVP8); + } + + rtc::scoped_refptr CreateLocalVideoTrackInternal( + const std::string& track_id, + const FakeConstraints& constraints, + webrtc::VideoRotation rotation) { + // Set max frame rate to 10fps to reduce the risk of test flakiness. + // TODO(deadbeef): Do something more robust. + FakeConstraints source_constraints = constraints; + source_constraints.SetMandatoryMaxFrameRate(10); + + cricket::FakeVideoCapturer* fake_capturer = + new webrtc::FakePeriodicVideoCapturer(); + fake_capturer->SetRotation(rotation); + video_capturers_.push_back(fake_capturer); + rtc::scoped_refptr source = + peer_connection_factory_->CreateVideoSource(fake_capturer, + &source_constraints); + rtc::scoped_refptr track( + peer_connection_factory_->CreateVideoTrack(track_id, source)); + if (!local_video_renderer_) { + local_video_renderer_.reset(new webrtc::FakeVideoTrackRenderer(track)); + } + return track; + } + + void HandleIncomingOffer(const std::string& msg) { + LOG(LS_INFO) << debug_name_ << ": HandleIncomingOffer"; + std::unique_ptr desc( + webrtc::CreateSessionDescription("offer", msg, nullptr)); + if (received_sdp_munger_) { + received_sdp_munger_(desc->description()); + } + + EXPECT_TRUE(SetRemoteDescription(std::move(desc))); + // Setting a remote description may have changed the number of receivers, + // so reset the receiver observers. + ResetRtpReceiverObservers(); + auto answer = CreateAnswer(); + ASSERT_NE(nullptr, answer); + EXPECT_TRUE(SetLocalDescriptionAndSendSdpMessage(std::move(answer))); + } + + void HandleIncomingAnswer(const std::string& msg) { + LOG(LS_INFO) << debug_name_ << ": HandleIncomingAnswer"; + std::unique_ptr desc( + webrtc::CreateSessionDescription("answer", msg, nullptr)); + if (received_sdp_munger_) { + received_sdp_munger_(desc->description()); + } + + EXPECT_TRUE(SetRemoteDescription(std::move(desc))); + // Set the RtpReceiverObserver after receivers are created. + ResetRtpReceiverObservers(); + } + + // Returns null on failure. + std::unique_ptr CreateOffer() { + rtc::scoped_refptr observer( + new rtc::RefCountedObject()); + pc()->CreateOffer(observer, offer_answer_options_); + return WaitForDescriptionFromObserver(observer); + } + + // Returns null on failure. + std::unique_ptr CreateAnswer() { + rtc::scoped_refptr observer( + new rtc::RefCountedObject()); + pc()->CreateAnswer(observer, offer_answer_options_); + return WaitForDescriptionFromObserver(observer); + } + + std::unique_ptr WaitForDescriptionFromObserver( + rtc::scoped_refptr observer) { + EXPECT_EQ_WAIT(true, observer->called(), kDefaultTimeout); + if (!observer->result()) { + return nullptr; + } + auto description = observer->MoveDescription(); + if (generated_sdp_munger_) { + generated_sdp_munger_(description->description()); + } + return description; + } + + // Setting the local description and sending the SDP message over the fake + // signaling channel are combined into the same method because the SDP + // message needs to be sent as soon as SetLocalDescription finishes, without + // waiting for the observer to be called. This ensures that ICE candidates + // don't outrace the description. + bool SetLocalDescriptionAndSendSdpMessage( + std::unique_ptr desc) { + rtc::scoped_refptr observer( + new rtc::RefCountedObject()); + LOG(LS_INFO) << debug_name_ << ": SetLocalDescriptionAndSendSdpMessage"; + std::string type = desc->type(); + std::string sdp; + EXPECT_TRUE(desc->ToString(&sdp)); + pc()->SetLocalDescription(observer, desc.release()); + // As mentioned above, we need to send the message immediately after + // SetLocalDescription. + SendSdpMessage(type, sdp); + EXPECT_TRUE_WAIT(observer->called(), kDefaultTimeout); + return true; + } + + bool SetRemoteDescription(std::unique_ptr desc) { + rtc::scoped_refptr observer( + new rtc::RefCountedObject()); + LOG(LS_INFO) << debug_name_ << ": SetRemoteDescription"; + pc()->SetRemoteDescription(observer, desc.release()); + EXPECT_TRUE_WAIT(observer->called(), kDefaultTimeout); + return observer->result(); + } + + // Simulate sending a blob of SDP with delay |signaling_delay_ms_| (0 by + // default). + void SendSdpMessage(const std::string& type, const std::string& msg) { + if (signaling_delay_ms_ == 0) { + RelaySdpMessageIfReceiverExists(type, msg); + } else { + invoker_.AsyncInvokeDelayed( + RTC_FROM_HERE, rtc::Thread::Current(), + rtc::Bind(&PeerConnectionWrapper::RelaySdpMessageIfReceiverExists, + this, type, msg), + signaling_delay_ms_); + } + } + + void RelaySdpMessageIfReceiverExists(const std::string& type, + const std::string& msg) { + if (signaling_message_receiver_) { + signaling_message_receiver_->ReceiveSdpMessage(type, msg); + } + } + + // Simulate trickling an ICE candidate with delay |signaling_delay_ms_| (0 by + // default). + void SendIceMessage(const std::string& sdp_mid, + int sdp_mline_index, + const std::string& msg) { + if (signaling_delay_ms_ == 0) { + RelayIceMessageIfReceiverExists(sdp_mid, sdp_mline_index, msg); + } else { + invoker_.AsyncInvokeDelayed( + RTC_FROM_HERE, rtc::Thread::Current(), + rtc::Bind(&PeerConnectionWrapper::RelayIceMessageIfReceiverExists, + this, sdp_mid, sdp_mline_index, msg), + signaling_delay_ms_); + } + } + + void RelayIceMessageIfReceiverExists(const std::string& sdp_mid, + int sdp_mline_index, + const std::string& msg) { + if (signaling_message_receiver_) { + signaling_message_receiver_->ReceiveIceMessage(sdp_mid, sdp_mline_index, + msg); + } + } + + // SignalingMessageReceiver callbacks. + void ReceiveSdpMessage(const std::string& type, + const std::string& msg) override { + if (type == webrtc::SessionDescriptionInterface::kOffer) { + HandleIncomingOffer(msg); + } else { + HandleIncomingAnswer(msg); + } + } + + void ReceiveIceMessage(const std::string& sdp_mid, + int sdp_mline_index, + const std::string& msg) override { + LOG(LS_INFO) << debug_name_ << ": ReceiveIceMessage"; + std::unique_ptr candidate( + webrtc::CreateIceCandidate(sdp_mid, sdp_mline_index, msg, nullptr)); + EXPECT_TRUE(pc()->AddIceCandidate(candidate.get())); + } + + // PeerConnectionObserver callbacks. + void OnSignalingChange( + webrtc::PeerConnectionInterface::SignalingState new_state) override { + EXPECT_EQ(pc()->signaling_state(), new_state); + } + void OnAddStream( + rtc::scoped_refptr media_stream) override { + media_stream->RegisterObserver(this); + for (size_t i = 0; i < media_stream->GetVideoTracks().size(); ++i) { + const std::string id = media_stream->GetVideoTracks()[i]->id(); + ASSERT_TRUE(fake_video_renderers_.find(id) == + fake_video_renderers_.end()); + fake_video_renderers_[id].reset(new webrtc::FakeVideoTrackRenderer( + media_stream->GetVideoTracks()[i])); + } + } + void OnRemoveStream( + rtc::scoped_refptr media_stream) override {} + void OnRenegotiationNeeded() override {} + void OnIceConnectionChange( + webrtc::PeerConnectionInterface::IceConnectionState new_state) override { + EXPECT_EQ(pc()->ice_connection_state(), new_state); + } + void OnIceGatheringChange( + webrtc::PeerConnectionInterface::IceGatheringState new_state) override { + if (new_state == PeerConnectionInterface::kIceGatheringGathering) { + ++transitions_to_gathering_state_; + } + EXPECT_EQ(pc()->ice_gathering_state(), new_state); + } + void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override { + LOG(LS_INFO) << debug_name_ << ": OnIceCandidate"; + + std::string ice_sdp; + EXPECT_TRUE(candidate->ToString(&ice_sdp)); + if (signaling_message_receiver_ == nullptr) { + // Remote party may be deleted. + return; + } + SendIceMessage(candidate->sdp_mid(), candidate->sdp_mline_index(), ice_sdp); + } + void OnDataChannel( + rtc::scoped_refptr data_channel) override { + LOG(LS_INFO) << debug_name_ << ": OnDataChannel"; + data_channel_ = data_channel; + data_observer_.reset(new MockDataChannelObserver(data_channel)); + } + + // MediaStreamInterface callback + void OnChanged() override { + // Track added or removed from MediaStream, so update our renderers. + rtc::scoped_refptr remote_streams = + pc()->remote_streams(); + // Remove renderers for tracks that were removed. + for (auto it = fake_video_renderers_.begin(); + it != fake_video_renderers_.end();) { + if (remote_streams->FindVideoTrack(it->first) == nullptr) { + auto to_remove = it++; + removed_fake_video_renderers_.push_back(std::move(to_remove->second)); + fake_video_renderers_.erase(to_remove); + } else { + ++it; + } + } + // Create renderers for new video tracks. + for (size_t stream_index = 0; stream_index < remote_streams->count(); + ++stream_index) { + MediaStreamInterface* remote_stream = remote_streams->at(stream_index); + for (size_t track_index = 0; + track_index < remote_stream->GetVideoTracks().size(); + ++track_index) { + const std::string id = + remote_stream->GetVideoTracks()[track_index]->id(); + if (fake_video_renderers_.find(id) != fake_video_renderers_.end()) { + continue; + } + fake_video_renderers_[id].reset(new webrtc::FakeVideoTrackRenderer( + remote_stream->GetVideoTracks()[track_index])); + } + } + } + + std::string debug_name_; + + std::unique_ptr fake_network_manager_; + + rtc::scoped_refptr peer_connection_; + rtc::scoped_refptr + peer_connection_factory_; + + // Needed to keep track of number of frames sent. + rtc::scoped_refptr fake_audio_capture_module_; + // Needed to keep track of number of frames received. + std::map> + fake_video_renderers_; + // Needed to ensure frames aren't received for removed tracks. + std::vector> + removed_fake_video_renderers_; + // Needed to keep track of number of frames received when external decoder + // used. + FakeWebRtcVideoDecoderFactory* fake_video_decoder_factory_ = nullptr; + FakeWebRtcVideoEncoderFactory* fake_video_encoder_factory_ = nullptr; + bool video_decoder_factory_enabled_ = false; + + // For remote peer communication. + SignalingMessageReceiver* signaling_message_receiver_ = nullptr; + int signaling_delay_ms_ = 0; + + // Store references to the video capturers we've created, so that we can stop + // them, if required. + std::vector video_capturers_; + // |local_video_renderer_| attached to the first created local video track. + std::unique_ptr local_video_renderer_; + + PeerConnectionInterface::RTCOfferAnswerOptions offer_answer_options_; + std::function received_sdp_munger_; + std::function generated_sdp_munger_; + + rtc::scoped_refptr data_channel_; + std::unique_ptr data_observer_; + + std::vector> rtp_receiver_observers_; + + int transitions_to_gathering_state_ = 0; + + rtc::AsyncInvoker invoker_; + + friend class PeerConnectionIntegrationTest; +}; + +// Tests two PeerConnections connecting to each other end-to-end, using a +// virtual network, fake A/V capture and fake encoder/decoders. The +// PeerConnections share the threads/socket servers, but use separate versions +// of everything else (including "PeerConnectionFactory"s). +class PeerConnectionIntegrationTest : public testing::Test { + public: + PeerConnectionIntegrationTest() + : pss_(new rtc::PhysicalSocketServer), + ss_(new rtc::VirtualSocketServer(pss_.get())), + network_thread_(new rtc::Thread(ss_.get())), + worker_thread_(rtc::Thread::Create()) { + RTC_CHECK(network_thread_->Start()); + RTC_CHECK(worker_thread_->Start()); + } + + ~PeerConnectionIntegrationTest() { + if (caller_) { + caller_->set_signaling_message_receiver(nullptr); + } + if (callee_) { + callee_->set_signaling_message_receiver(nullptr); + } + } + + bool SignalingStateStable() { + return caller_->SignalingStateStable() && callee_->SignalingStateStable(); + } + + bool CreatePeerConnectionWrappers() { + return CreatePeerConnectionWrappersWithConfig( + PeerConnectionInterface::RTCConfiguration(), + PeerConnectionInterface::RTCConfiguration()); + } + + bool CreatePeerConnectionWrappersWithConstraints( + MediaConstraintsInterface* caller_constraints, + MediaConstraintsInterface* callee_constraints) { + caller_.reset(PeerConnectionWrapper::CreateWithConstraints( + "Caller", caller_constraints, network_thread_.get(), + worker_thread_.get())); + callee_.reset(PeerConnectionWrapper::CreateWithConstraints( + "Callee", callee_constraints, network_thread_.get(), + worker_thread_.get())); + return caller_ && callee_; + } + + bool CreatePeerConnectionWrappersWithConfig( + const PeerConnectionInterface::RTCConfiguration& caller_config, + const PeerConnectionInterface::RTCConfiguration& callee_config) { + caller_.reset(PeerConnectionWrapper::CreateWithConfig( + "Caller", caller_config, network_thread_.get(), worker_thread_.get())); + callee_.reset(PeerConnectionWrapper::CreateWithConfig( + "Callee", callee_config, network_thread_.get(), worker_thread_.get())); + return caller_ && callee_; + } + + bool CreatePeerConnectionWrappersWithOptions( + const PeerConnectionFactory::Options& caller_options, + const PeerConnectionFactory::Options& callee_options) { + caller_.reset(PeerConnectionWrapper::CreateWithOptions( + "Caller", caller_options, network_thread_.get(), worker_thread_.get())); + callee_.reset(PeerConnectionWrapper::CreateWithOptions( + "Callee", callee_options, network_thread_.get(), worker_thread_.get())); + return caller_ && callee_; + } + + PeerConnectionWrapper* CreatePeerConnectionWrapperWithAlternateKey() { + std::unique_ptr cert_generator( + new FakeRTCCertificateGenerator()); + cert_generator->use_alternate_key(); + + // Make sure the new client is using a different certificate. + return PeerConnectionWrapper::CreateWithDtlsIdentityStore( + "New Peer", std::move(cert_generator), network_thread_.get(), + worker_thread_.get()); + } + + // Once called, SDP blobs and ICE candidates will be automatically signaled + // between PeerConnections. + void ConnectFakeSignaling() { + caller_->set_signaling_message_receiver(callee_.get()); + callee_->set_signaling_message_receiver(caller_.get()); + } + + void SetSignalingDelayMs(int delay_ms) { + caller_->set_signaling_delay_ms(delay_ms); + callee_->set_signaling_delay_ms(delay_ms); + } + + void EnableVideoDecoderFactory() { + caller_->EnableVideoDecoderFactory(); + callee_->EnableVideoDecoderFactory(); + } + + // Messages may get lost on the unreliable DataChannel, so we send multiple + // times to avoid test flakiness. + void SendRtpDataWithRetries(webrtc::DataChannelInterface* dc, + const std::string& data, + int retries) { + for (int i = 0; i < retries; ++i) { + dc->Send(DataBuffer(data)); + } + } + + rtc::Thread* network_thread() { return network_thread_.get(); } + + rtc::VirtualSocketServer* virtual_socket_server() { return ss_.get(); } + + PeerConnectionWrapper* caller() { return caller_.get(); } + + // Set the |caller_| to the |wrapper| passed in and return the + // original |caller_|. + PeerConnectionWrapper* SetCallerPcWrapperAndReturnCurrent( + PeerConnectionWrapper* wrapper) { + PeerConnectionWrapper* old = caller_.release(); + caller_.reset(wrapper); + return old; + } + + PeerConnectionWrapper* callee() { return callee_.get(); } + + // Set the |callee_| to the |wrapper| passed in and return the + // original |callee_|. + PeerConnectionWrapper* SetCalleePcWrapperAndReturnCurrent( + PeerConnectionWrapper* wrapper) { + PeerConnectionWrapper* old = callee_.release(); + callee_.reset(wrapper); + return old; + } + + // Expects the provided number of new frames to be received within |wait_ms|. + // "New frames" meaning that it waits for the current frame counts to + // *increase* by the provided values. For video, uses + // RecievedVideoFramesForEachTrack for the case of multiple video tracks + // being received. + void ExpectNewFramesReceivedWithWait( + int expected_caller_received_audio_frames, + int expected_caller_received_video_frames, + int expected_callee_received_audio_frames, + int expected_callee_received_video_frames, + int wait_ms) { + // Add current frame counts to the provided values, in order to wait for + // the frame count to increase. + expected_caller_received_audio_frames += caller()->audio_frames_received(); + expected_caller_received_video_frames += + caller()->min_video_frames_received_per_track(); + expected_callee_received_audio_frames += callee()->audio_frames_received(); + expected_callee_received_video_frames += + callee()->min_video_frames_received_per_track(); + + EXPECT_TRUE_WAIT(caller()->audio_frames_received() >= + expected_caller_received_audio_frames && + caller()->min_video_frames_received_per_track() >= + expected_caller_received_video_frames && + callee()->audio_frames_received() >= + expected_callee_received_audio_frames && + callee()->min_video_frames_received_per_track() >= + expected_callee_received_video_frames, + wait_ms); + + // After the combined wait, do an "expect" for each individual count, to + // print out a more detailed message upon failure. + EXPECT_GE(caller()->audio_frames_received(), + expected_caller_received_audio_frames); + EXPECT_GE(caller()->min_video_frames_received_per_track(), + expected_caller_received_video_frames); + EXPECT_GE(callee()->audio_frames_received(), + expected_callee_received_audio_frames); + EXPECT_GE(callee()->min_video_frames_received_per_track(), + expected_callee_received_video_frames); + } + + void TestGcmNegotiationUsesCipherSuite(bool local_gcm_enabled, + bool remote_gcm_enabled, + int expected_cipher_suite) { + PeerConnectionFactory::Options caller_options; + caller_options.crypto_options.enable_gcm_crypto_suites = local_gcm_enabled; + PeerConnectionFactory::Options callee_options; + callee_options.crypto_options.enable_gcm_crypto_suites = remote_gcm_enabled; + ASSERT_TRUE(CreatePeerConnectionWrappersWithOptions(caller_options, + callee_options)); + rtc::scoped_refptr caller_observer = + new rtc::RefCountedObject(); + caller()->pc()->RegisterUMAObserver(caller_observer); + ConnectFakeSignaling(); + caller()->AddAudioVideoMediaStream(); + callee()->AddAudioVideoMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + EXPECT_EQ_WAIT(rtc::SrtpCryptoSuiteToName(expected_cipher_suite), + caller()->GetStats()->SrtpCipher(), kDefaultTimeout); + EXPECT_EQ( + 1, caller_observer->GetEnumCounter(webrtc::kEnumCounterAudioSrtpCipher, + expected_cipher_suite)); + caller()->pc()->RegisterUMAObserver(nullptr); + } + + private: + // |ss_| is used by |network_thread_| so it must be destroyed later. + std::unique_ptr pss_; + std::unique_ptr ss_; + // |network_thread_| and |worker_thread_| are used by both + // |caller_| and |callee_| so they must be destroyed + // later. + std::unique_ptr network_thread_; + std::unique_ptr worker_thread_; + std::unique_ptr caller_; + std::unique_ptr callee_; +}; + +// Test the OnFirstPacketReceived callback from audio/video RtpReceivers. This +// includes testing that the callback is invoked if an observer is connected +// after the first packet has already been received. +TEST_F(PeerConnectionIntegrationTest, + RtpReceiverObserverOnFirstPacketReceived) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + caller()->AddAudioVideoMediaStream(); + callee()->AddAudioVideoMediaStream(); + // Start offer/answer exchange and wait for it to complete. + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + // Should be one receiver each for audio/video. + EXPECT_EQ(2, caller()->rtp_receiver_observers().size()); + EXPECT_EQ(2, callee()->rtp_receiver_observers().size()); + // Wait for all "first packet received" callbacks to be fired. + EXPECT_TRUE_WAIT( + std::all_of(caller()->rtp_receiver_observers().begin(), + caller()->rtp_receiver_observers().end(), + [](const std::unique_ptr& o) { + return o->first_packet_received(); + }), + kMaxWaitForFramesMs); + EXPECT_TRUE_WAIT( + std::all_of(callee()->rtp_receiver_observers().begin(), + callee()->rtp_receiver_observers().end(), + [](const std::unique_ptr& o) { + return o->first_packet_received(); + }), + kMaxWaitForFramesMs); + // If new observers are set after the first packet was already received, the + // callback should still be invoked. + caller()->ResetRtpReceiverObservers(); + callee()->ResetRtpReceiverObservers(); + EXPECT_EQ(2, caller()->rtp_receiver_observers().size()); + EXPECT_EQ(2, callee()->rtp_receiver_observers().size()); + EXPECT_TRUE( + std::all_of(caller()->rtp_receiver_observers().begin(), + caller()->rtp_receiver_observers().end(), + [](const std::unique_ptr& o) { + return o->first_packet_received(); + })); + EXPECT_TRUE( + std::all_of(callee()->rtp_receiver_observers().begin(), + callee()->rtp_receiver_observers().end(), + [](const std::unique_ptr& o) { + return o->first_packet_received(); + })); +} + +class DummyDtmfObserver : public DtmfSenderObserverInterface { + public: + DummyDtmfObserver() : completed_(false) {} + + // Implements DtmfSenderObserverInterface. + void OnToneChange(const std::string& tone) override { + tones_.push_back(tone); + if (tone.empty()) { + completed_ = true; + } + } + + const std::vector& tones() const { return tones_; } + bool completed() const { return completed_; } + + private: + bool completed_; + std::vector tones_; +}; + +// Assumes |sender| already has an audio track added and the offer/answer +// exchange is done. +void TestDtmfFromSenderToReceiver(PeerConnectionWrapper* sender, + PeerConnectionWrapper* receiver) { + DummyDtmfObserver observer; + rtc::scoped_refptr dtmf_sender; + + // We should be able to create a DTMF sender from a local track. + webrtc::AudioTrackInterface* localtrack = + sender->local_streams()->at(0)->GetAudioTracks()[0]; + dtmf_sender = sender->pc()->CreateDtmfSender(localtrack); + ASSERT_NE(nullptr, dtmf_sender.get()); + dtmf_sender->RegisterObserver(&observer); + + // Test the DtmfSender object just created. + EXPECT_TRUE(dtmf_sender->CanInsertDtmf()); + EXPECT_TRUE(dtmf_sender->InsertDtmf("1a", 100, 50)); + + EXPECT_TRUE_WAIT(observer.completed(), kDefaultTimeout); + std::vector tones = {"1", "a", ""}; + EXPECT_EQ(tones, observer.tones()); + dtmf_sender->UnregisterObserver(); + // TODO(deadbeef): Verify the tones were actually received end-to-end. +} + +// Verifies the DtmfSenderObserver callbacks for a DtmfSender (one in each +// direction). +TEST_F(PeerConnectionIntegrationTest, DtmfSenderObserver) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + // Only need audio for DTMF. + caller()->AddAudioOnlyMediaStream(); + callee()->AddAudioOnlyMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + TestDtmfFromSenderToReceiver(caller(), callee()); + TestDtmfFromSenderToReceiver(callee(), caller()); +} + +// Basic end-to-end test, verifying media can be encoded/transmitted/decoded +// between two connections, using DTLS-SRTP. +TEST_F(PeerConnectionIntegrationTest, EndToEndCallWithDtls) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + // Do normal offer/answer and wait for some frames to be received in each + // direction. + caller()->AddAudioVideoMediaStream(); + callee()->AddAudioVideoMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + ExpectNewFramesReceivedWithWait( + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kMaxWaitForFramesMs); +} + +// Uses SDES instead of DTLS for key agreement. +TEST_F(PeerConnectionIntegrationTest, EndToEndCallWithSdes) { + PeerConnectionInterface::RTCConfiguration sdes_config; + sdes_config.enable_dtls_srtp.emplace(false); + ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(sdes_config, sdes_config)); + ConnectFakeSignaling(); + + // Do normal offer/answer and wait for some frames to be received in each + // direction. + caller()->AddAudioVideoMediaStream(); + callee()->AddAudioVideoMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + ExpectNewFramesReceivedWithWait( + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kMaxWaitForFramesMs); +} + +// This test sets up a call between two parties (using DTLS) and tests that we +// can get a video aspect ratio of 16:9. +TEST_F(PeerConnectionIntegrationTest, SendAndReceive16To9AspectRatio) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + + // Add video tracks with 16:9 constraint. + FakeConstraints constraints; + double requested_ratio = 16.0 / 9; + constraints.SetMandatoryMinAspectRatio(requested_ratio); + caller()->AddMediaStreamFromTracks( + nullptr, caller()->CreateLocalVideoTrackWithConstraints(constraints)); + callee()->AddMediaStreamFromTracks( + nullptr, callee()->CreateLocalVideoTrackWithConstraints(constraints)); + + // Do normal offer/answer and wait for at least one frame to be received in + // each direction. + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(caller()->min_video_frames_received_per_track() > 0 && + callee()->min_video_frames_received_per_track() > 0, + kMaxWaitForFramesMs); + + // Check rendered aspect ratio. + EXPECT_EQ(requested_ratio, caller()->local_rendered_aspect_ratio()); + EXPECT_EQ(requested_ratio, caller()->rendered_aspect_ratio()); + EXPECT_EQ(requested_ratio, callee()->local_rendered_aspect_ratio()); + EXPECT_EQ(requested_ratio, callee()->rendered_aspect_ratio()); +} + +// This test sets up a call between two parties with a source resolution of +// 1280x720 and verifies that a 16:9 aspect ratio is received. +TEST_F(PeerConnectionIntegrationTest, + Send1280By720ResolutionAndReceive16To9AspectRatio) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + + // Similar to above test, but uses MandatoryMin[Width/Height] constraint + // instead of aspect ratio constraint. + FakeConstraints constraints; + constraints.SetMandatoryMinWidth(1280); + constraints.SetMandatoryMinHeight(720); + caller()->AddMediaStreamFromTracks( + nullptr, caller()->CreateLocalVideoTrackWithConstraints(constraints)); + callee()->AddMediaStreamFromTracks( + nullptr, callee()->CreateLocalVideoTrackWithConstraints(constraints)); + + // Do normal offer/answer and wait for at least one frame to be received in + // each direction. + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(caller()->min_video_frames_received_per_track() > 0 && + callee()->min_video_frames_received_per_track() > 0, + kMaxWaitForFramesMs); + + // Check rendered aspect ratio. + EXPECT_EQ(16.0 / 9, caller()->local_rendered_aspect_ratio()); + EXPECT_EQ(16.0 / 9, caller()->rendered_aspect_ratio()); + EXPECT_EQ(16.0 / 9, callee()->local_rendered_aspect_ratio()); + EXPECT_EQ(16.0 / 9, callee()->rendered_aspect_ratio()); +} + +// This test sets up an one-way call, with media only from caller to +// callee. +TEST_F(PeerConnectionIntegrationTest, OneWayMediaCall) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + caller()->AddAudioVideoMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + int caller_received_frames = 0; + ExpectNewFramesReceivedWithWait( + caller_received_frames, caller_received_frames, + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kMaxWaitForFramesMs); +} + +// This test sets up a audio call initially, with the callee rejecting video +// initially. Then later the callee decides to upgrade to audio/video, and +// initiates a new offer/answer exchange. +TEST_F(PeerConnectionIntegrationTest, AudioToVideoUpgrade) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + // Initially, offer an audio/video stream from the caller, but refuse to + // send/receive video on the callee side. + caller()->AddAudioVideoMediaStream(); + callee()->AddMediaStreamFromTracks(callee()->CreateLocalAudioTrack(), + nullptr); + PeerConnectionInterface::RTCOfferAnswerOptions options; + options.offer_to_receive_video = 0; + callee()->SetOfferAnswerOptions(options); + // Do offer/answer and make sure audio is still received end-to-end. + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + ExpectNewFramesReceivedWithWait(kDefaultExpectedAudioFrameCount, 0, + kDefaultExpectedAudioFrameCount, 0, + kMaxWaitForFramesMs); + // Sanity check that the callee's description has a rejected video section. + ASSERT_NE(nullptr, callee()->pc()->local_description()); + const ContentInfo* callee_video_content = + GetFirstVideoContent(callee()->pc()->local_description()->description()); + ASSERT_NE(nullptr, callee_video_content); + EXPECT_TRUE(callee_video_content->rejected); + // Now negotiate with video and ensure negotiation succeeds, with video + // frames and additional audio frames being received. + callee()->AddMediaStreamFromTracksWithLabel( + nullptr, callee()->CreateLocalVideoTrack(), "video_only_stream"); + options.offer_to_receive_video = 1; + callee()->SetOfferAnswerOptions(options); + callee()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + // Expect additional audio frames to be received after the upgrade. + ExpectNewFramesReceivedWithWait( + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kMaxWaitForFramesMs); +} + +// This test sets up a call that's transferred to a new caller with a different +// DTLS fingerprint. +TEST_F(PeerConnectionIntegrationTest, CallTransferredForCallee) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + caller()->AddAudioVideoMediaStream(); + callee()->AddAudioVideoMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + + // Keep the original peer around which will still send packets to the + // receiving client. These SRTP packets will be dropped. + std::unique_ptr original_peer( + SetCallerPcWrapperAndReturnCurrent( + CreatePeerConnectionWrapperWithAlternateKey())); + // TODO(deadbeef): Why do we call Close here? That goes against the comment + // directly above. + original_peer->pc()->Close(); + + ConnectFakeSignaling(); + caller()->AddAudioVideoMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + // Wait for some additional frames to be transmitted end-to-end. + ExpectNewFramesReceivedWithWait( + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kMaxWaitForFramesMs); +} + +// This test sets up a call that's transferred to a new callee with a different +// DTLS fingerprint. +TEST_F(PeerConnectionIntegrationTest, CallTransferredForCaller) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + caller()->AddAudioVideoMediaStream(); + callee()->AddAudioVideoMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + + // Keep the original peer around which will still send packets to the + // receiving client. These SRTP packets will be dropped. + std::unique_ptr original_peer( + SetCalleePcWrapperAndReturnCurrent( + CreatePeerConnectionWrapperWithAlternateKey())); + // TODO(deadbeef): Why do we call Close here? That goes against the comment + // directly above. + original_peer->pc()->Close(); + + ConnectFakeSignaling(); + callee()->AddAudioVideoMediaStream(); + caller()->SetOfferAnswerOptions(IceRestartOfferAnswerOptions()); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + // Wait for some additional frames to be transmitted end-to-end. + ExpectNewFramesReceivedWithWait( + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kMaxWaitForFramesMs); +} + +// This test sets up a non-bundled call and negotiates bundling at the same +// time as starting an ICE restart. When bundling is in effect in the restart, +// the DTLS-SRTP context should be successfully reset. +TEST_F(PeerConnectionIntegrationTest, BundlingEnabledWhileIceRestartOccurs) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + + caller()->AddAudioVideoMediaStream(); + callee()->AddAudioVideoMediaStream(); + // Remove the bundle group from the SDP received by the callee. + callee()->SetReceivedSdpMunger([](cricket::SessionDescription* desc) { + desc->RemoveGroupByName("BUNDLE"); + }); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + ExpectNewFramesReceivedWithWait( + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kMaxWaitForFramesMs); + + // Now stop removing the BUNDLE group, and trigger an ICE restart. + callee()->SetReceivedSdpMunger(nullptr); + caller()->SetOfferAnswerOptions(IceRestartOfferAnswerOptions()); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + + // Expect additional frames to be received after the ICE restart. + ExpectNewFramesReceivedWithWait( + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kMaxWaitForFramesMs); +} + +// Test CVO (Coordination of Video Orientation). If a video source is rotated +// and both peers support the CVO RTP header extension, the actual video frames +// don't need to be encoded in different resolutions, since the rotation is +// communicated through the RTP header extension. +TEST_F(PeerConnectionIntegrationTest, RotatedVideoWithCVOExtension) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + // Add rotated video tracks. + caller()->AddMediaStreamFromTracks( + nullptr, + caller()->CreateLocalVideoTrackWithRotation(webrtc::kVideoRotation_90)); + callee()->AddMediaStreamFromTracks( + nullptr, + callee()->CreateLocalVideoTrackWithRotation(webrtc::kVideoRotation_270)); + + // Wait for video frames to be received by both sides. + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + ASSERT_TRUE_WAIT(caller()->min_video_frames_received_per_track() > 0 && + callee()->min_video_frames_received_per_track() > 0, + kMaxWaitForFramesMs); + + // Ensure that the aspect ratio is unmodified. + // TODO(deadbeef): Where does 4:3 come from? Should be explicit in the test, + // not just assumed. + EXPECT_EQ(4.0 / 3, caller()->local_rendered_aspect_ratio()); + EXPECT_EQ(4.0 / 3, caller()->rendered_aspect_ratio()); + EXPECT_EQ(4.0 / 3, callee()->local_rendered_aspect_ratio()); + EXPECT_EQ(4.0 / 3, callee()->rendered_aspect_ratio()); + // Ensure that the CVO bits were surfaced to the renderer. + EXPECT_EQ(webrtc::kVideoRotation_270, caller()->rendered_rotation()); + EXPECT_EQ(webrtc::kVideoRotation_90, callee()->rendered_rotation()); +} + +// Test that when the CVO extension isn't supported, video is rotated the +// old-fashioned way, by encoding rotated frames. +TEST_F(PeerConnectionIntegrationTest, RotatedVideoWithoutCVOExtension) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + // Add rotated video tracks. + caller()->AddMediaStreamFromTracks( + nullptr, + caller()->CreateLocalVideoTrackWithRotation(webrtc::kVideoRotation_90)); + callee()->AddMediaStreamFromTracks( + nullptr, + callee()->CreateLocalVideoTrackWithRotation(webrtc::kVideoRotation_270)); + + // Remove the CVO extension from the offered SDP. + callee()->SetReceivedSdpMunger([](cricket::SessionDescription* desc) { + cricket::VideoContentDescription* video = + GetFirstVideoContentDescription(desc); + video->ClearRtpHeaderExtensions(); + }); + // Wait for video frames to be received by both sides. + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + ASSERT_TRUE_WAIT(caller()->min_video_frames_received_per_track() > 0 && + callee()->min_video_frames_received_per_track() > 0, + kMaxWaitForFramesMs); + + // Expect that the aspect ratio is inversed to account for the 90/270 degree + // rotation. + // TODO(deadbeef): Where does 4:3 come from? Should be explicit in the test, + // not just assumed. + EXPECT_EQ(3.0 / 4, caller()->local_rendered_aspect_ratio()); + EXPECT_EQ(3.0 / 4, caller()->rendered_aspect_ratio()); + EXPECT_EQ(3.0 / 4, callee()->local_rendered_aspect_ratio()); + EXPECT_EQ(3.0 / 4, callee()->rendered_aspect_ratio()); + // Expect that each endpoint is unaware of the rotation of the other endpoint. + EXPECT_EQ(webrtc::kVideoRotation_0, caller()->rendered_rotation()); + EXPECT_EQ(webrtc::kVideoRotation_0, callee()->rendered_rotation()); +} + +// TODO(deadbeef): The tests below rely on RTCOfferAnswerOptions to reject an +// m= section. When we implement Unified Plan SDP, the right way to do this +// would be by stopping an RtpTransceiver. + +// Test that if the answerer rejects the audio m= section, no audio is sent or +// received, but video still can be. +TEST_F(PeerConnectionIntegrationTest, AnswererRejectsAudioSection) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + caller()->AddAudioVideoMediaStream(); + // Only add video track for callee, and set offer_to_receive_audio to 0, so + // it will reject the audio m= section completely. + PeerConnectionInterface::RTCOfferAnswerOptions options; + options.offer_to_receive_audio = 0; + callee()->SetOfferAnswerOptions(options); + callee()->AddMediaStreamFromTracks(nullptr, + callee()->CreateLocalVideoTrack()); + // Do offer/answer and wait for successful end-to-end video frames. + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + ExpectNewFramesReceivedWithWait(0, kDefaultExpectedVideoFrameCount, 0, + kDefaultExpectedVideoFrameCount, + kMaxWaitForFramesMs); + // Shouldn't have received audio frames at any point. + EXPECT_EQ(0, caller()->audio_frames_received()); + EXPECT_EQ(0, callee()->audio_frames_received()); + // Sanity check that the callee's description has a rejected audio section. + ASSERT_NE(nullptr, callee()->pc()->local_description()); + const ContentInfo* callee_audio_content = + GetFirstAudioContent(callee()->pc()->local_description()->description()); + ASSERT_NE(nullptr, callee_audio_content); + EXPECT_TRUE(callee_audio_content->rejected); +} + +// Test that if the answerer rejects the video m= section, no video is sent or +// received, but audio still can be. +TEST_F(PeerConnectionIntegrationTest, AnswererRejectsVideoSection) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + caller()->AddAudioVideoMediaStream(); + // Only add audio track for callee, and set offer_to_receive_video to 0, so + // it will reject the video m= section completely. + PeerConnectionInterface::RTCOfferAnswerOptions options; + options.offer_to_receive_video = 0; + callee()->SetOfferAnswerOptions(options); + callee()->AddMediaStreamFromTracks(callee()->CreateLocalAudioTrack(), + nullptr); + // Do offer/answer and wait for successful end-to-end audio frames. + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + ExpectNewFramesReceivedWithWait(kDefaultExpectedAudioFrameCount, 0, + kDefaultExpectedAudioFrameCount, 0, + kMaxWaitForFramesMs); + // Shouldn't have received video frames at any point. + EXPECT_EQ(0, caller()->total_video_frames_received()); + EXPECT_EQ(0, callee()->total_video_frames_received()); + // Sanity check that the callee's description has a rejected video section. + ASSERT_NE(nullptr, callee()->pc()->local_description()); + const ContentInfo* callee_video_content = + GetFirstVideoContent(callee()->pc()->local_description()->description()); + ASSERT_NE(nullptr, callee_video_content); + EXPECT_TRUE(callee_video_content->rejected); +} + +// Test that if the answerer rejects both audio and video m= sections, nothing +// bad happens. +// TODO(deadbeef): Test that a data channel still works. Currently this doesn't +// test anything but the fact that negotiation succeeds, which doesn't mean +// much. +TEST_F(PeerConnectionIntegrationTest, AnswererRejectsAudioAndVideoSections) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + caller()->AddAudioVideoMediaStream(); + // Don't give the callee any tracks, and set offer_to_receive_X to 0, so it + // will reject both audio and video m= sections. + PeerConnectionInterface::RTCOfferAnswerOptions options; + options.offer_to_receive_audio = 0; + options.offer_to_receive_video = 0; + callee()->SetOfferAnswerOptions(options); + // Do offer/answer and wait for stable signaling state. + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + // Sanity check that the callee's description has rejected m= sections. + ASSERT_NE(nullptr, callee()->pc()->local_description()); + const ContentInfo* callee_audio_content = + GetFirstAudioContent(callee()->pc()->local_description()->description()); + ASSERT_NE(nullptr, callee_audio_content); + EXPECT_TRUE(callee_audio_content->rejected); + const ContentInfo* callee_video_content = + GetFirstVideoContent(callee()->pc()->local_description()->description()); + ASSERT_NE(nullptr, callee_video_content); + EXPECT_TRUE(callee_video_content->rejected); +} + +// This test sets up an audio and video call between two parties. After the +// call runs for a while, the caller sends an updated offer with video being +// rejected. Once the re-negotiation is done, the video flow should stop and +// the audio flow should continue. +TEST_F(PeerConnectionIntegrationTest, VideoRejectedInSubsequentOffer) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + caller()->AddAudioVideoMediaStream(); + callee()->AddAudioVideoMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + ExpectNewFramesReceivedWithWait( + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kMaxWaitForFramesMs); + + // Renegotiate, rejecting the video m= section. + // TODO(deadbeef): When an RtpTransceiver API is available, use that to + // reject the video m= section. + caller()->SetGeneratedSdpMunger([](cricket::SessionDescription* description) { + for (cricket::ContentInfo& content : description->contents()) { + if (cricket::IsVideoContent(&content)) { + content.rejected = true; + } + } + }); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kMaxWaitForActivationMs); + + // Sanity check that the caller's description has a rejected video section. + ASSERT_NE(nullptr, caller()->pc()->local_description()); + const ContentInfo* caller_video_content = + GetFirstVideoContent(caller()->pc()->local_description()->description()); + ASSERT_NE(nullptr, caller_video_content); + EXPECT_TRUE(caller_video_content->rejected); + + int caller_video_received = caller()->total_video_frames_received(); + int callee_video_received = callee()->total_video_frames_received(); + + // Wait for some additional audio frames to be received. + ExpectNewFramesReceivedWithWait(kDefaultExpectedAudioFrameCount, 0, + kDefaultExpectedAudioFrameCount, 0, + kMaxWaitForFramesMs); + + // During this time, we shouldn't have received any additional video frames + // for the rejected video tracks. + EXPECT_EQ(caller_video_received, caller()->total_video_frames_received()); + EXPECT_EQ(callee_video_received, callee()->total_video_frames_received()); +} + +// Basic end-to-end test, but without SSRC/MSID signaling. This functionality +// is needed to support legacy endpoints. +// TODO(deadbeef): When we support the MID extension and demuxing on MID, also +// add a test for an end-to-end test without MID signaling either (basically, +// the minimum acceptable SDP). +TEST_F(PeerConnectionIntegrationTest, EndToEndCallWithoutSsrcOrMsidSignaling) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + // Add audio and video, testing that packets can be demuxed on payload type. + caller()->AddAudioVideoMediaStream(); + callee()->AddAudioVideoMediaStream(); + // Remove all stream information (SSRCs, track IDs, etc.) and "msid-semantic" + // attribute from received SDP, simulating a legacy endpoint. + callee()->SetReceivedSdpMunger([](cricket::SessionDescription* desc) { + for (ContentInfo& content : desc->contents()) { + MediaContentDescription* media_desc = + static_cast(content.description); + media_desc->mutable_streams().clear(); + } + desc->set_msid_supported(false); + }); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + ExpectNewFramesReceivedWithWait( + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kMaxWaitForFramesMs); +} + +// Test that if two video tracks are sent (from caller to callee, in this test), +// they're transmitted correctly end-to-end. +TEST_F(PeerConnectionIntegrationTest, EndToEndCallWithTwoVideoTracks) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + // Add one audio/video stream, and one video-only stream. + caller()->AddAudioVideoMediaStream(); + caller()->AddMediaStreamFromTracksWithLabel( + nullptr, caller()->CreateLocalVideoTrackWithId("extra_track"), + "extra_stream"); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + ASSERT_EQ(2u, callee()->number_of_remote_streams()); + int expected_callee_received_frames = kDefaultExpectedVideoFrameCount; + ExpectNewFramesReceivedWithWait(0, 0, 0, expected_callee_received_frames, + kMaxWaitForFramesMs); +} + +static void MakeSpecCompliantMaxBundleOffer(cricket::SessionDescription* desc) { + bool first = true; + for (cricket::ContentInfo& content : desc->contents()) { + if (first) { + first = false; + continue; + } + content.bundle_only = true; + } + first = true; + for (cricket::TransportInfo& transport : desc->transport_infos()) { + if (first) { + first = false; + continue; + } + transport.description.ice_ufrag.clear(); + transport.description.ice_pwd.clear(); + transport.description.connection_role = cricket::CONNECTIONROLE_NONE; + transport.description.identity_fingerprint.reset(nullptr); + } +} + +// Test that if applying a true "max bundle" offer, which uses ports of 0, +// "a=bundle-only", omitting "a=fingerprint", "a=setup", "a=ice-ufrag" and +// "a=ice-pwd" for all but the audio "m=" section, negotiation still completes +// successfully and media flows. +// TODO(deadbeef): Update this test to also omit "a=rtcp-mux", once that works. +// TODO(deadbeef): Won't need this test once we start generating actual +// standards-compliant SDP. +TEST_F(PeerConnectionIntegrationTest, + EndToEndCallWithSpecCompliantMaxBundleOffer) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + caller()->AddAudioVideoMediaStream(); + callee()->AddAudioVideoMediaStream(); + // Do the equivalent of setting the port to 0, adding a=bundle-only, and + // removing a=ice-ufrag, a=ice-pwd, a=fingerprint and a=setup from all + // but the first m= section. + callee()->SetReceivedSdpMunger(MakeSpecCompliantMaxBundleOffer); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + ExpectNewFramesReceivedWithWait( + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kMaxWaitForFramesMs); +} + +// Test that we can receive the audio output level from a remote audio track. +// TODO(deadbeef): Use a fake audio source and verify that the output level is +// exactly what the source on the other side was configured with. +TEST_F(PeerConnectionIntegrationTest, GetAudioOutputLevelStats) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + // Just add an audio track. + caller()->AddMediaStreamFromTracks(caller()->CreateLocalAudioTrack(), + nullptr); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + + // Get the audio output level stats. Note that the level is not available + // until an RTCP packet has been received. + EXPECT_TRUE_WAIT(callee()->GetStats()->AudioOutputLevel() > 0, + kMaxWaitForFramesMs); +} + +// Test that an audio input level is reported. +// TODO(deadbeef): Use a fake audio source and verify that the input level is +// exactly what the source was configured with. +TEST_F(PeerConnectionIntegrationTest, GetAudioInputLevelStats) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + // Just add an audio track. + caller()->AddMediaStreamFromTracks(caller()->CreateLocalAudioTrack(), + nullptr); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + + // Get the audio input level stats. The level should be available very + // soon after the test starts. + EXPECT_TRUE_WAIT(caller()->GetStats()->AudioInputLevel() > 0, + kMaxWaitForStatsMs); +} + +// Test that we can get incoming byte counts from both audio and video tracks. +TEST_F(PeerConnectionIntegrationTest, GetBytesReceivedStats) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + caller()->AddAudioVideoMediaStream(); + // Do offer/answer, wait for the callee to receive some frames. + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + int expected_caller_received_frames = 0; + ExpectNewFramesReceivedWithWait( + expected_caller_received_frames, expected_caller_received_frames, + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kMaxWaitForFramesMs); + + // Get a handle to the remote tracks created, so they can be used as GetStats + // filters. + StreamCollectionInterface* remote_streams = callee()->remote_streams(); + ASSERT_EQ(1u, remote_streams->count()); + ASSERT_EQ(1u, remote_streams->at(0)->GetAudioTracks().size()); + ASSERT_EQ(1u, remote_streams->at(0)->GetVideoTracks().size()); + MediaStreamTrackInterface* remote_audio_track = + remote_streams->at(0)->GetAudioTracks()[0]; + MediaStreamTrackInterface* remote_video_track = + remote_streams->at(0)->GetVideoTracks()[0]; + + // We received frames, so we definitely should have nonzero "received bytes" + // stats at this point. + EXPECT_GT(callee()->GetStatsForTrack(remote_audio_track)->BytesReceived(), 0); + EXPECT_GT(callee()->GetStatsForTrack(remote_video_track)->BytesReceived(), 0); +} + +// Test that we can get outgoing byte counts from both audio and video tracks. +TEST_F(PeerConnectionIntegrationTest, GetBytesSentStats) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + auto audio_track = caller()->CreateLocalAudioTrack(); + auto video_track = caller()->CreateLocalVideoTrack(); + caller()->AddMediaStreamFromTracks(audio_track, video_track); + // Do offer/answer, wait for the callee to receive some frames. + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + int expected_caller_received_frames = 0; + ExpectNewFramesReceivedWithWait( + expected_caller_received_frames, expected_caller_received_frames, + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kMaxWaitForFramesMs); + + // The callee received frames, so we definitely should have nonzero "sent + // bytes" stats at this point. + EXPECT_GT(caller()->GetStatsForTrack(audio_track)->BytesSent(), 0); + EXPECT_GT(caller()->GetStatsForTrack(video_track)->BytesSent(), 0); +} + +// Test that DTLS 1.0 is used if both sides only support DTLS 1.0. +TEST_F(PeerConnectionIntegrationTest, EndToEndCallWithDtls10) { + PeerConnectionFactory::Options dtls_10_options; + dtls_10_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_10; + ASSERT_TRUE(CreatePeerConnectionWrappersWithOptions(dtls_10_options, + dtls_10_options)); + ConnectFakeSignaling(); + // Do normal offer/answer and wait for some frames to be received in each + // direction. + caller()->AddAudioVideoMediaStream(); + callee()->AddAudioVideoMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + ExpectNewFramesReceivedWithWait( + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kMaxWaitForFramesMs); +} + +// Test getting cipher stats and UMA metrics when DTLS 1.0 is negotiated. +TEST_F(PeerConnectionIntegrationTest, Dtls10CipherStatsAndUmaMetrics) { + PeerConnectionFactory::Options dtls_10_options; + dtls_10_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_10; + ASSERT_TRUE(CreatePeerConnectionWrappersWithOptions(dtls_10_options, + dtls_10_options)); + ConnectFakeSignaling(); + // Register UMA observer before signaling begins. + rtc::scoped_refptr caller_observer = + new rtc::RefCountedObject(); + caller()->pc()->RegisterUMAObserver(caller_observer); + caller()->AddAudioVideoMediaStream(); + callee()->AddAudioVideoMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + EXPECT_TRUE_WAIT(rtc::SSLStreamAdapter::IsAcceptableCipher( + caller()->GetStats()->DtlsCipher(), rtc::KT_DEFAULT), + kDefaultTimeout); + EXPECT_EQ_WAIT(rtc::SrtpCryptoSuiteToName(kDefaultSrtpCryptoSuite), + caller()->GetStats()->SrtpCipher(), kDefaultTimeout); + EXPECT_EQ(1, + caller_observer->GetEnumCounter(webrtc::kEnumCounterAudioSrtpCipher, + kDefaultSrtpCryptoSuite)); +} + +// Test getting cipher stats and UMA metrics when DTLS 1.2 is negotiated. +TEST_F(PeerConnectionIntegrationTest, Dtls12CipherStatsAndUmaMetrics) { + PeerConnectionFactory::Options dtls_12_options; + dtls_12_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_12; + ASSERT_TRUE(CreatePeerConnectionWrappersWithOptions(dtls_12_options, + dtls_12_options)); + ConnectFakeSignaling(); + // Register UMA observer before signaling begins. + rtc::scoped_refptr caller_observer = + new rtc::RefCountedObject(); + caller()->pc()->RegisterUMAObserver(caller_observer); + caller()->AddAudioVideoMediaStream(); + callee()->AddAudioVideoMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + EXPECT_TRUE_WAIT(rtc::SSLStreamAdapter::IsAcceptableCipher( + caller()->GetStats()->DtlsCipher(), rtc::KT_DEFAULT), + kDefaultTimeout); + EXPECT_EQ_WAIT(rtc::SrtpCryptoSuiteToName(kDefaultSrtpCryptoSuite), + caller()->GetStats()->SrtpCipher(), kDefaultTimeout); + EXPECT_EQ(1, + caller_observer->GetEnumCounter(webrtc::kEnumCounterAudioSrtpCipher, + kDefaultSrtpCryptoSuite)); +} + +// Test that DTLS 1.0 can be used if the caller supports DTLS 1.2 and the +// callee only supports 1.0. +TEST_F(PeerConnectionIntegrationTest, CallerDtls12ToCalleeDtls10) { + PeerConnectionFactory::Options caller_options; + caller_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_12; + PeerConnectionFactory::Options callee_options; + callee_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_10; + ASSERT_TRUE( + CreatePeerConnectionWrappersWithOptions(caller_options, callee_options)); + ConnectFakeSignaling(); + // Do normal offer/answer and wait for some frames to be received in each + // direction. + caller()->AddAudioVideoMediaStream(); + callee()->AddAudioVideoMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + ExpectNewFramesReceivedWithWait( + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kMaxWaitForFramesMs); +} + +// Test that DTLS 1.0 can be used if the caller only supports DTLS 1.0 and the +// callee supports 1.2. +TEST_F(PeerConnectionIntegrationTest, CallerDtls10ToCalleeDtls12) { + PeerConnectionFactory::Options caller_options; + caller_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_10; + PeerConnectionFactory::Options callee_options; + callee_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_12; + ASSERT_TRUE( + CreatePeerConnectionWrappersWithOptions(caller_options, callee_options)); + ConnectFakeSignaling(); + // Do normal offer/answer and wait for some frames to be received in each + // direction. + caller()->AddAudioVideoMediaStream(); + callee()->AddAudioVideoMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + ExpectNewFramesReceivedWithWait( + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kMaxWaitForFramesMs); +} + +// Test that a non-GCM cipher is used if both sides only support non-GCM. +TEST_F(PeerConnectionIntegrationTest, NonGcmCipherUsedWhenGcmNotSupported) { + bool local_gcm_enabled = false; + bool remote_gcm_enabled = false; + int expected_cipher_suite = kDefaultSrtpCryptoSuite; + TestGcmNegotiationUsesCipherSuite(local_gcm_enabled, remote_gcm_enabled, + expected_cipher_suite); +} + +// Test that a GCM cipher is used if both ends support it. +TEST_F(PeerConnectionIntegrationTest, GcmCipherUsedWhenGcmSupported) { + bool local_gcm_enabled = true; + bool remote_gcm_enabled = true; + int expected_cipher_suite = kDefaultSrtpCryptoSuiteGcm; + TestGcmNegotiationUsesCipherSuite(local_gcm_enabled, remote_gcm_enabled, + expected_cipher_suite); +} + +// Test that GCM isn't used if only the offerer supports it. +TEST_F(PeerConnectionIntegrationTest, + NonGcmCipherUsedWhenOnlyCallerSupportsGcm) { + bool local_gcm_enabled = true; + bool remote_gcm_enabled = false; + int expected_cipher_suite = kDefaultSrtpCryptoSuite; + TestGcmNegotiationUsesCipherSuite(local_gcm_enabled, remote_gcm_enabled, + expected_cipher_suite); +} + +// Test that GCM isn't used if only the answerer supports it. +TEST_F(PeerConnectionIntegrationTest, + NonGcmCipherUsedWhenOnlyCalleeSupportsGcm) { + bool local_gcm_enabled = false; + bool remote_gcm_enabled = true; + int expected_cipher_suite = kDefaultSrtpCryptoSuite; + TestGcmNegotiationUsesCipherSuite(local_gcm_enabled, remote_gcm_enabled, + expected_cipher_suite); +} + +// This test sets up a call between two parties with audio, video and an RTP +// data channel. +TEST_F(PeerConnectionIntegrationTest, EndToEndCallWithRtpDataChannel) { + FakeConstraints setup_constraints; + setup_constraints.SetAllowRtpDataChannels(); + ASSERT_TRUE(CreatePeerConnectionWrappersWithConstraints(&setup_constraints, + &setup_constraints)); + ConnectFakeSignaling(); + // Expect that data channel created on caller side will show up for callee as + // well. + caller()->CreateDataChannel(); + caller()->AddAudioVideoMediaStream(); + callee()->AddAudioVideoMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + // Ensure the existence of the RTP data channel didn't impede audio/video. + ExpectNewFramesReceivedWithWait( + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kMaxWaitForFramesMs); + ASSERT_NE(nullptr, caller()->data_channel()); + ASSERT_NE(nullptr, callee()->data_channel()); + EXPECT_TRUE_WAIT(caller()->data_observer()->IsOpen(), kDefaultTimeout); + EXPECT_TRUE_WAIT(callee()->data_observer()->IsOpen(), kDefaultTimeout); + + // Ensure data can be sent in both directions. + std::string data = "hello world"; + SendRtpDataWithRetries(caller()->data_channel(), data, 5); + EXPECT_EQ_WAIT(data, callee()->data_observer()->last_message(), + kDefaultTimeout); + SendRtpDataWithRetries(callee()->data_channel(), data, 5); + EXPECT_EQ_WAIT(data, caller()->data_observer()->last_message(), + kDefaultTimeout); +} + +// Ensure that an RTP data channel is signaled as closed for the caller when +// the callee rejects it in a subsequent offer. +TEST_F(PeerConnectionIntegrationTest, + RtpDataChannelSignaledClosedInCalleeOffer) { + // Same procedure as above test. + FakeConstraints setup_constraints; + setup_constraints.SetAllowRtpDataChannels(); + ASSERT_TRUE(CreatePeerConnectionWrappersWithConstraints(&setup_constraints, + &setup_constraints)); + ConnectFakeSignaling(); + caller()->CreateDataChannel(); + caller()->AddAudioVideoMediaStream(); + callee()->AddAudioVideoMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + ASSERT_NE(nullptr, caller()->data_channel()); + ASSERT_NE(nullptr, callee()->data_channel()); + ASSERT_TRUE_WAIT(caller()->data_observer()->IsOpen(), kDefaultTimeout); + ASSERT_TRUE_WAIT(callee()->data_observer()->IsOpen(), kDefaultTimeout); + + // Close the data channel on the callee, and do an updated offer/answer. + callee()->data_channel()->Close(); + callee()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + EXPECT_FALSE(caller()->data_observer()->IsOpen()); + EXPECT_FALSE(callee()->data_observer()->IsOpen()); +} + +// Tests that data is buffered in an RTP data channel until an observer is +// registered for it. +// +// NOTE: RTP data channels can receive data before the underlying +// transport has detected that a channel is writable and thus data can be +// received before the data channel state changes to open. That is hard to test +// but the same buffering is expected to be used in that case. +TEST_F(PeerConnectionIntegrationTest, + DataBufferedUntilRtpDataChannelObserverRegistered) { + // Use fake clock and simulated network delay so that we predictably can wait + // until an SCTP message has been delivered without "sleep()"ing. + rtc::ScopedFakeClock fake_clock; + // Some things use a time of "0" as a special value, so we need to start out + // the fake clock at a nonzero time. + // TODO(deadbeef): Fix this. + fake_clock.AdvanceTime(rtc::TimeDelta::FromSeconds(1)); + virtual_socket_server()->set_delay_mean(5); // 5 ms per hop. + virtual_socket_server()->UpdateDelayDistribution(); + + FakeConstraints constraints; + constraints.SetAllowRtpDataChannels(); + ASSERT_TRUE( + CreatePeerConnectionWrappersWithConstraints(&constraints, &constraints)); + ConnectFakeSignaling(); + caller()->CreateDataChannel(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE(caller()->data_channel() != nullptr); + ASSERT_TRUE_SIMULATED_WAIT(callee()->data_channel() != nullptr, + kDefaultTimeout, fake_clock); + ASSERT_TRUE_SIMULATED_WAIT(caller()->data_observer()->IsOpen(), + kDefaultTimeout, fake_clock); + ASSERT_EQ_SIMULATED_WAIT(DataChannelInterface::kOpen, + callee()->data_channel()->state(), kDefaultTimeout, + fake_clock); + + // Unregister the observer which is normally automatically registered. + callee()->data_channel()->UnregisterObserver(); + // Send data and advance fake clock until it should have been received. + std::string data = "hello world"; + caller()->data_channel()->Send(DataBuffer(data)); + SIMULATED_WAIT(false, 50, fake_clock); + + // Attach data channel and expect data to be received immediately. Note that + // EXPECT_EQ_WAIT is used, such that the simulated clock is not advanced any + // further, but data can be received even if the callback is asynchronous. + MockDataChannelObserver new_observer(callee()->data_channel()); + EXPECT_EQ_SIMULATED_WAIT(data, new_observer.last_message(), kDefaultTimeout, + fake_clock); +} + +// This test sets up a call between two parties with audio, video and but only +// the caller client supports RTP data channels. +TEST_F(PeerConnectionIntegrationTest, RtpDataChannelsRejectedByCallee) { + FakeConstraints setup_constraints_1; + setup_constraints_1.SetAllowRtpDataChannels(); + // Must disable DTLS to make negotiation succeed. + setup_constraints_1.SetMandatory(MediaConstraintsInterface::kEnableDtlsSrtp, + false); + FakeConstraints setup_constraints_2; + setup_constraints_2.SetMandatory(MediaConstraintsInterface::kEnableDtlsSrtp, + false); + ASSERT_TRUE(CreatePeerConnectionWrappersWithConstraints( + &setup_constraints_1, &setup_constraints_2)); + ConnectFakeSignaling(); + caller()->CreateDataChannel(); + caller()->AddAudioVideoMediaStream(); + callee()->AddAudioVideoMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + // The caller should still have a data channel, but it should be closed, and + // one should ever have been created for the callee. + EXPECT_TRUE(caller()->data_channel() != nullptr); + EXPECT_FALSE(caller()->data_observer()->IsOpen()); + EXPECT_EQ(nullptr, callee()->data_channel()); +} + +// This test sets up a call between two parties with audio, and video. When +// audio and video is setup and flowing, an RTP data channel is negotiated. +TEST_F(PeerConnectionIntegrationTest, AddRtpDataChannelInSubsequentOffer) { + FakeConstraints setup_constraints; + setup_constraints.SetAllowRtpDataChannels(); + ASSERT_TRUE(CreatePeerConnectionWrappersWithConstraints(&setup_constraints, + &setup_constraints)); + ConnectFakeSignaling(); + // Do initial offer/answer with audio/video. + caller()->AddAudioVideoMediaStream(); + callee()->AddAudioVideoMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + // Create data channel and do new offer and answer. + caller()->CreateDataChannel(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + ASSERT_NE(nullptr, caller()->data_channel()); + ASSERT_NE(nullptr, callee()->data_channel()); + EXPECT_TRUE_WAIT(caller()->data_observer()->IsOpen(), kDefaultTimeout); + EXPECT_TRUE_WAIT(callee()->data_observer()->IsOpen(), kDefaultTimeout); + // Ensure data can be sent in both directions. + std::string data = "hello world"; + SendRtpDataWithRetries(caller()->data_channel(), data, 5); + EXPECT_EQ_WAIT(data, callee()->data_observer()->last_message(), + kDefaultTimeout); + SendRtpDataWithRetries(callee()->data_channel(), data, 5); + EXPECT_EQ_WAIT(data, caller()->data_observer()->last_message(), + kDefaultTimeout); +} + +#ifdef HAVE_SCTP + +// This test sets up a call between two parties with audio, video and an SCTP +// data channel. +TEST_F(PeerConnectionIntegrationTest, EndToEndCallWithSctpDataChannel) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + // Expect that data channel created on caller side will show up for callee as + // well. + caller()->CreateDataChannel(); + caller()->AddAudioVideoMediaStream(); + callee()->AddAudioVideoMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + // Ensure the existence of the SCTP data channel didn't impede audio/video. + ExpectNewFramesReceivedWithWait( + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kMaxWaitForFramesMs); + // Caller data channel should already exist (it created one). Callee data + // channel may not exist yet, since negotiation happens in-band, not in SDP. + ASSERT_NE(nullptr, caller()->data_channel()); + ASSERT_TRUE_WAIT(callee()->data_channel() != nullptr, kDefaultTimeout); + EXPECT_TRUE_WAIT(caller()->data_observer()->IsOpen(), kDefaultTimeout); + EXPECT_TRUE_WAIT(callee()->data_observer()->IsOpen(), kDefaultTimeout); + + // Ensure data can be sent in both directions. + std::string data = "hello world"; + caller()->data_channel()->Send(DataBuffer(data)); + EXPECT_EQ_WAIT(data, callee()->data_observer()->last_message(), + kDefaultTimeout); + callee()->data_channel()->Send(DataBuffer(data)); + EXPECT_EQ_WAIT(data, caller()->data_observer()->last_message(), + kDefaultTimeout); +} + +// Ensure that when the callee closes an SCTP data channel, the closing +// procedure results in the data channel being closed for the caller as well. +TEST_F(PeerConnectionIntegrationTest, CalleeClosesSctpDataChannel) { + // Same procedure as above test. + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + caller()->CreateDataChannel(); + caller()->AddAudioVideoMediaStream(); + callee()->AddAudioVideoMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + ASSERT_NE(nullptr, caller()->data_channel()); + ASSERT_TRUE_WAIT(callee()->data_channel() != nullptr, kDefaultTimeout); + ASSERT_TRUE_WAIT(caller()->data_observer()->IsOpen(), kDefaultTimeout); + ASSERT_TRUE_WAIT(callee()->data_observer()->IsOpen(), kDefaultTimeout); + + // Close the data channel on the callee side, and wait for it to reach the + // "closed" state on both sides. + callee()->data_channel()->Close(); + EXPECT_TRUE_WAIT(!caller()->data_observer()->IsOpen(), kDefaultTimeout); + EXPECT_TRUE_WAIT(!callee()->data_observer()->IsOpen(), kDefaultTimeout); +} + +// Test usrsctp's ability to process unordered data stream, where data actually +// arrives out of order using simulated delays. Previously there have been some +// bugs in this area. +TEST_F(PeerConnectionIntegrationTest, StressTestUnorderedSctpDataChannel) { + // Introduce random network delays. + // Otherwise it's not a true "unordered" test. + virtual_socket_server()->set_delay_mean(20); + virtual_socket_server()->set_delay_stddev(5); + virtual_socket_server()->UpdateDelayDistribution(); + // Normal procedure, but with unordered data channel config. + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + webrtc::DataChannelInit init; + init.ordered = false; + caller()->CreateDataChannel(&init); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + ASSERT_NE(nullptr, caller()->data_channel()); + ASSERT_TRUE_WAIT(callee()->data_channel() != nullptr, kDefaultTimeout); + ASSERT_TRUE_WAIT(caller()->data_observer()->IsOpen(), kDefaultTimeout); + ASSERT_TRUE_WAIT(callee()->data_observer()->IsOpen(), kDefaultTimeout); + + static constexpr int kNumMessages = 100; + // Deliberately chosen to be larger than the MTU so messages get fragmented. + static constexpr size_t kMaxMessageSize = 4096; + // Create and send random messages. + std::vector sent_messages; + for (int i = 0; i < kNumMessages; ++i) { + size_t length = + (rand() % kMaxMessageSize) + 1; // NOLINT (rand_r instead of rand) + std::string message; + ASSERT_TRUE(rtc::CreateRandomString(length, &message)); + caller()->data_channel()->Send(DataBuffer(message)); + callee()->data_channel()->Send(DataBuffer(message)); + sent_messages.push_back(message); + } + + // Wait for all messages to be received. + EXPECT_EQ_WAIT(kNumMessages, + caller()->data_observer()->received_message_count(), + kDefaultTimeout); + EXPECT_EQ_WAIT(kNumMessages, + callee()->data_observer()->received_message_count(), + kDefaultTimeout); + + // Sort and compare to make sure none of the messages were corrupted. + std::vector caller_received_messages = + caller()->data_observer()->messages(); + std::vector callee_received_messages = + callee()->data_observer()->messages(); + std::sort(sent_messages.begin(), sent_messages.end()); + std::sort(caller_received_messages.begin(), caller_received_messages.end()); + std::sort(callee_received_messages.begin(), callee_received_messages.end()); + EXPECT_EQ(sent_messages, caller_received_messages); + EXPECT_EQ(sent_messages, callee_received_messages); +} + +// This test sets up a call between two parties with audio, and video. When +// audio and video are setup and flowing, an SCTP data channel is negotiated. +TEST_F(PeerConnectionIntegrationTest, AddSctpDataChannelInSubsequentOffer) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + // Do initial offer/answer with audio/video. + caller()->AddAudioVideoMediaStream(); + callee()->AddAudioVideoMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + // Create data channel and do new offer and answer. + caller()->CreateDataChannel(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + // Caller data channel should already exist (it created one). Callee data + // channel may not exist yet, since negotiation happens in-band, not in SDP. + ASSERT_NE(nullptr, caller()->data_channel()); + ASSERT_TRUE_WAIT(callee()->data_channel() != nullptr, kDefaultTimeout); + EXPECT_TRUE_WAIT(caller()->data_observer()->IsOpen(), kDefaultTimeout); + EXPECT_TRUE_WAIT(callee()->data_observer()->IsOpen(), kDefaultTimeout); + // Ensure data can be sent in both directions. + std::string data = "hello world"; + caller()->data_channel()->Send(DataBuffer(data)); + EXPECT_EQ_WAIT(data, callee()->data_observer()->last_message(), + kDefaultTimeout); + callee()->data_channel()->Send(DataBuffer(data)); + EXPECT_EQ_WAIT(data, caller()->data_observer()->last_message(), + kDefaultTimeout); +} + +#endif // HAVE_SCTP + +// Test that the ICE connection and gathering states eventually reach +// "complete". +TEST_F(PeerConnectionIntegrationTest, IceStatesReachCompletion) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + // Do normal offer/answer. + caller()->AddAudioVideoMediaStream(); + callee()->AddAudioVideoMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceGatheringComplete, + caller()->ice_gathering_state(), kMaxWaitForFramesMs); + EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceGatheringComplete, + callee()->ice_gathering_state(), kMaxWaitForFramesMs); + // After the best candidate pair is selected and all candidates are signaled, + // the ICE connection state should reach "complete". + // TODO(deadbeef): Currently, the ICE "controlled" agent (the + // answerer/"callee" by default) only reaches "connected". When this is + // fixed, this test should be updated. + EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceConnectionCompleted, + caller()->ice_connection_state(), kDefaultTimeout); + EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceConnectionConnected, + callee()->ice_connection_state(), kDefaultTimeout); +} + +// This test sets up a call between two parties with audio and video. +// During the call, the caller restarts ICE and the test verifies that +// new ICE candidates are generated and audio and video still can flow, and the +// ICE state reaches completed again. +TEST_F(PeerConnectionIntegrationTest, MediaContinuesFlowingAfterIceRestart) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + // Do normal offer/answer and wait for ICE to complete. + caller()->AddAudioVideoMediaStream(); + callee()->AddAudioVideoMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceConnectionCompleted, + caller()->ice_connection_state(), kMaxWaitForFramesMs); + EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceConnectionConnected, + callee()->ice_connection_state(), kMaxWaitForFramesMs); + + // To verify that the ICE restart actually occurs, get + // ufrag/password/candidates before and after restart. + // Create an SDP string of the first audio candidate for both clients. + const webrtc::IceCandidateCollection* audio_candidates_caller = + caller()->pc()->local_description()->candidates(0); + const webrtc::IceCandidateCollection* audio_candidates_callee = + callee()->pc()->local_description()->candidates(0); + ASSERT_GT(audio_candidates_caller->count(), 0u); + ASSERT_GT(audio_candidates_callee->count(), 0u); + std::string caller_candidate_pre_restart; + ASSERT_TRUE( + audio_candidates_caller->at(0)->ToString(&caller_candidate_pre_restart)); + std::string callee_candidate_pre_restart; + ASSERT_TRUE( + audio_candidates_callee->at(0)->ToString(&callee_candidate_pre_restart)); + const cricket::SessionDescription* desc = + caller()->pc()->local_description()->description(); + std::string caller_ufrag_pre_restart = + desc->transport_infos()[0].description.ice_ufrag; + desc = callee()->pc()->local_description()->description(); + std::string callee_ufrag_pre_restart = + desc->transport_infos()[0].description.ice_ufrag; + + // Have the caller initiate an ICE restart. + caller()->SetOfferAnswerOptions(IceRestartOfferAnswerOptions()); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceConnectionCompleted, + caller()->ice_connection_state(), kMaxWaitForFramesMs); + EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceConnectionConnected, + callee()->ice_connection_state(), kMaxWaitForFramesMs); + + // Grab the ufrags/candidates again. + audio_candidates_caller = caller()->pc()->local_description()->candidates(0); + audio_candidates_callee = callee()->pc()->local_description()->candidates(0); + ASSERT_GT(audio_candidates_caller->count(), 0u); + ASSERT_GT(audio_candidates_callee->count(), 0u); + std::string caller_candidate_post_restart; + ASSERT_TRUE( + audio_candidates_caller->at(0)->ToString(&caller_candidate_post_restart)); + std::string callee_candidate_post_restart; + ASSERT_TRUE( + audio_candidates_callee->at(0)->ToString(&callee_candidate_post_restart)); + desc = caller()->pc()->local_description()->description(); + std::string caller_ufrag_post_restart = + desc->transport_infos()[0].description.ice_ufrag; + desc = callee()->pc()->local_description()->description(); + std::string callee_ufrag_post_restart = + desc->transport_infos()[0].description.ice_ufrag; + // Sanity check that an ICE restart was actually negotiated in SDP. + ASSERT_NE(caller_candidate_pre_restart, caller_candidate_post_restart); + ASSERT_NE(callee_candidate_pre_restart, callee_candidate_post_restart); + ASSERT_NE(caller_ufrag_pre_restart, caller_ufrag_post_restart); + ASSERT_NE(callee_ufrag_pre_restart, callee_ufrag_post_restart); + + // Ensure that additional frames are received after the ICE restart. + ExpectNewFramesReceivedWithWait( + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kMaxWaitForFramesMs); +} + +// Verify that audio/video can be received end-to-end when ICE renomination is +// enabled. +TEST_F(PeerConnectionIntegrationTest, EndToEndCallWithIceRenomination) { + PeerConnectionInterface::RTCConfiguration config; + config.enable_ice_renomination = true; + ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config)); + ConnectFakeSignaling(); + // Do normal offer/answer and wait for some frames to be received in each + // direction. + caller()->AddAudioVideoMediaStream(); + callee()->AddAudioVideoMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + // Sanity check that ICE renomination was actually negotiated. + const cricket::SessionDescription* desc = + caller()->pc()->local_description()->description(); + for (const cricket::TransportInfo& info : desc->transport_infos()) { + ASSERT_NE(info.description.transport_options.end(), + std::find(info.description.transport_options.begin(), + info.description.transport_options.end(), + cricket::ICE_RENOMINATION_STR)); + } + desc = callee()->pc()->local_description()->description(); + for (const cricket::TransportInfo& info : desc->transport_infos()) { + ASSERT_NE(info.description.transport_options.end(), + std::find(info.description.transport_options.begin(), + info.description.transport_options.end(), + cricket::ICE_RENOMINATION_STR)); + } + ExpectNewFramesReceivedWithWait( + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kMaxWaitForFramesMs); +} + +// This test sets up a call between two parties with audio and video. It then +// renegotiates setting the video m-line to "port 0", then later renegotiates +// again, enabling video. +TEST_F(PeerConnectionIntegrationTest, + VideoFlowsAfterMediaSectionIsRejectedAndRecycled) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + + // Do initial negotiation, only sending media from the caller. Will result in + // video and audio recvonly "m=" sections. + caller()->AddAudioVideoMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + + // Negotiate again, disabling the video "m=" section (the callee will set the + // port to 0 due to offer_to_receive_video = 0). + PeerConnectionInterface::RTCOfferAnswerOptions options; + options.offer_to_receive_video = 0; + callee()->SetOfferAnswerOptions(options); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + // Sanity check that video "m=" section was actually rejected. + const ContentInfo* answer_video_content = cricket::GetFirstVideoContent( + callee()->pc()->local_description()->description()); + ASSERT_NE(nullptr, answer_video_content); + ASSERT_TRUE(answer_video_content->rejected); + + // Enable video and do negotiation again, making sure video is received + // end-to-end, also adding media stream to callee. + options.offer_to_receive_video = 1; + callee()->SetOfferAnswerOptions(options); + callee()->AddAudioVideoMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + // Verify the caller receives frames from the newly added stream, and the + // callee receives additional frames from the re-enabled video m= section. + ExpectNewFramesReceivedWithWait( + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kMaxWaitForFramesMs); +} + +// This test sets up a Jsep call between two parties with external +// VideoDecoderFactory. +// TODO(holmer): Disabled due to sometimes crashing on buildbots. +// See issue webrtc/2378. +TEST_F(PeerConnectionIntegrationTest, + DISABLED_EndToEndCallWithVideoDecoderFactory) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + EnableVideoDecoderFactory(); + ConnectFakeSignaling(); + caller()->AddAudioVideoMediaStream(); + callee()->AddAudioVideoMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + ExpectNewFramesReceivedWithWait( + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kMaxWaitForFramesMs); +} + +// This tests that if we negotiate after calling CreateSender but before we +// have a track, then set a track later, frames from the newly-set track are +// received end-to-end. +// TODO(deadbeef): Change this test to use AddTransceiver, once that's +// implemented. +TEST_F(PeerConnectionIntegrationTest, + MediaFlowsAfterEarlyWarmupWithCreateSender) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + auto caller_audio_sender = + caller()->pc()->CreateSender("audio", "caller_stream"); + auto caller_video_sender = + caller()->pc()->CreateSender("video", "caller_stream"); + auto callee_audio_sender = + callee()->pc()->CreateSender("audio", "callee_stream"); + auto callee_video_sender = + callee()->pc()->CreateSender("video", "callee_stream"); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kMaxWaitForActivationMs); + // Wait for ICE to complete, without any tracks being set. + EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceConnectionCompleted, + caller()->ice_connection_state(), kMaxWaitForFramesMs); + EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceConnectionConnected, + callee()->ice_connection_state(), kMaxWaitForFramesMs); + // Now set the tracks, and expect frames to immediately start flowing. + EXPECT_TRUE(caller_audio_sender->SetTrack(caller()->CreateLocalAudioTrack())); + EXPECT_TRUE(caller_video_sender->SetTrack(caller()->CreateLocalVideoTrack())); + EXPECT_TRUE(callee_audio_sender->SetTrack(callee()->CreateLocalAudioTrack())); + EXPECT_TRUE(callee_video_sender->SetTrack(callee()->CreateLocalVideoTrack())); + ExpectNewFramesReceivedWithWait( + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount, + kMaxWaitForFramesMs); +} + +// This test verifies that a remote video track can be added via AddStream, +// and sent end-to-end. For this particular test, it's simply echoed back +// from the caller to the callee, rather than being forwarded to a third +// PeerConnection. +TEST_F(PeerConnectionIntegrationTest, CanSendRemoteVideoTrack) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + // Just send a video track from the caller. + caller()->AddMediaStreamFromTracks(nullptr, + caller()->CreateLocalVideoTrack()); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kMaxWaitForActivationMs); + ASSERT_EQ(1, callee()->remote_streams()->count()); + + // Echo the stream back, and do a new offer/anwer (initiated by callee this + // time). + callee()->pc()->AddStream(callee()->remote_streams()->at(0)); + callee()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kMaxWaitForActivationMs); + + int expected_caller_received_video_frames = kDefaultExpectedVideoFrameCount; + ExpectNewFramesReceivedWithWait(0, expected_caller_received_video_frames, 0, + 0, kMaxWaitForFramesMs); +} + +// Test that we achieve the expected end-to-end connection time, using a +// fake clock and simulated latency on the media and signaling paths. +// We use a TURN<->TURN connection because this is usually the quickest to +// set up initially, especially when we're confident the connection will work +// and can start sending media before we get a STUN response. +// +// With various optimizations enabled, here are the network delays we expect to +// be on the critical path: +// 1. 2 signaling trips: Signaling offer and offerer's TURN candidate, then +// signaling answer (with DTLS fingerprint). +// 2. 9 media hops: Rest of the DTLS handshake. 3 hops in each direction when +// using TURN<->TURN pair, and DTLS exchange is 4 packets, +// the first of which should have arrived before the answer. +TEST_F(PeerConnectionIntegrationTest, EndToEndConnectionTimeWithTurnTurnPair) { + rtc::ScopedFakeClock fake_clock; + // Some things use a time of "0" as a special value, so we need to start out + // the fake clock at a nonzero time. + // TODO(deadbeef): Fix this. + fake_clock.AdvanceTime(rtc::TimeDelta::FromSeconds(1)); + + static constexpr int media_hop_delay_ms = 50; + static constexpr int signaling_trip_delay_ms = 500; + // For explanation of these values, see comment above. + static constexpr int required_media_hops = 9; + static constexpr int required_signaling_trips = 2; + // For internal delays (such as posting an event asychronously). + static constexpr int allowed_internal_delay_ms = 20; + static constexpr int total_connection_time_ms = + media_hop_delay_ms * required_media_hops + + signaling_trip_delay_ms * required_signaling_trips + + allowed_internal_delay_ms; + + static const rtc::SocketAddress turn_server_1_internal_address{"88.88.88.0", + 3478}; + static const rtc::SocketAddress turn_server_1_external_address{"88.88.88.1", + 0}; + static const rtc::SocketAddress turn_server_2_internal_address{"99.99.99.0", + 3478}; + static const rtc::SocketAddress turn_server_2_external_address{"99.99.99.1", + 0}; + cricket::TestTurnServer turn_server_1(network_thread(), + turn_server_1_internal_address, + turn_server_1_external_address); + cricket::TestTurnServer turn_server_2(network_thread(), + turn_server_2_internal_address, + turn_server_2_external_address); + // Bypass permission check on received packets so media can be sent before + // the candidate is signaled. + turn_server_1.set_enable_permission_checks(false); + turn_server_2.set_enable_permission_checks(false); + + PeerConnectionInterface::RTCConfiguration client_1_config; + webrtc::PeerConnectionInterface::IceServer ice_server_1; + ice_server_1.urls.push_back("turn:88.88.88.0:3478"); + ice_server_1.username = "test"; + ice_server_1.password = "test"; + client_1_config.servers.push_back(ice_server_1); + client_1_config.type = webrtc::PeerConnectionInterface::kRelay; + client_1_config.presume_writable_when_fully_relayed = true; + + PeerConnectionInterface::RTCConfiguration client_2_config; + webrtc::PeerConnectionInterface::IceServer ice_server_2; + ice_server_2.urls.push_back("turn:99.99.99.0:3478"); + ice_server_2.username = "test"; + ice_server_2.password = "test"; + client_2_config.servers.push_back(ice_server_2); + client_2_config.type = webrtc::PeerConnectionInterface::kRelay; + client_2_config.presume_writable_when_fully_relayed = true; + + ASSERT_TRUE( + CreatePeerConnectionWrappersWithConfig(client_1_config, client_2_config)); + // Set up the simulated delays. + SetSignalingDelayMs(signaling_trip_delay_ms); + ConnectFakeSignaling(); + virtual_socket_server()->set_delay_mean(media_hop_delay_ms); + virtual_socket_server()->UpdateDelayDistribution(); + + // Set "offer to receive audio/video" without adding any tracks, so we just + // set up ICE/DTLS with no media. + PeerConnectionInterface::RTCOfferAnswerOptions options; + options.offer_to_receive_audio = 1; + options.offer_to_receive_video = 1; + caller()->SetOfferAnswerOptions(options); + caller()->CreateAndSetAndSignalOffer(); + // TODO(deadbeef): kIceConnectionConnected currently means both ICE and DTLS + // are connected. This is an important distinction. Once we have separate ICE + // and DTLS state, this check needs to use the DTLS state. + EXPECT_TRUE_SIMULATED_WAIT( + (callee()->ice_connection_state() == + webrtc::PeerConnectionInterface::kIceConnectionConnected || + callee()->ice_connection_state() == + webrtc::PeerConnectionInterface::kIceConnectionCompleted) && + (caller()->ice_connection_state() == + webrtc::PeerConnectionInterface::kIceConnectionConnected || + caller()->ice_connection_state() == + webrtc::PeerConnectionInterface::kIceConnectionCompleted), + total_connection_time_ms, fake_clock); + // Need to free the clients here since they're using things we created on + // the stack. + delete SetCallerPcWrapperAndReturnCurrent(nullptr); + delete SetCalleePcWrapperAndReturnCurrent(nullptr); +} + +} // namespace + +#endif // if !defined(THREAD_SANITIZER) diff --git a/webrtc/pc/peerconnection_unittest.cc b/webrtc/pc/peerconnection_unittest.cc deleted file mode 100644 index e5e310ea0f..0000000000 --- a/webrtc/pc/peerconnection_unittest.cc +++ /dev/null @@ -1,2869 +0,0 @@ -/* - * Copyright 2012 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include - -#include -#include -#include -#include -#include -#include - -#include "webrtc/api/fakemetricsobserver.h" -#include "webrtc/api/mediastreaminterface.h" -#include "webrtc/api/peerconnectioninterface.h" -#include "webrtc/api/test/fakeconstraints.h" -#include "webrtc/base/fakenetwork.h" -#include "webrtc/base/gunit.h" -#include "webrtc/base/helpers.h" -#include "webrtc/base/physicalsocketserver.h" -#include "webrtc/base/ssladapter.h" -#include "webrtc/base/sslstreamadapter.h" -#include "webrtc/base/thread.h" -#include "webrtc/base/virtualsocketserver.h" -#include "webrtc/media/engine/fakewebrtcvideoengine.h" -#include "webrtc/p2p/base/p2pconstants.h" -#include "webrtc/p2p/base/portinterface.h" -#include "webrtc/p2p/base/sessiondescription.h" -#include "webrtc/p2p/base/testturnserver.h" -#include "webrtc/p2p/client/basicportallocator.h" -#include "webrtc/pc/dtmfsender.h" -#include "webrtc/pc/localaudiosource.h" -#include "webrtc/pc/mediasession.h" -#include "webrtc/pc/peerconnection.h" -#include "webrtc/pc/peerconnectionfactory.h" -#include "webrtc/pc/test/fakeaudiocapturemodule.h" -#include "webrtc/pc/test/fakeperiodicvideocapturer.h" -#include "webrtc/pc/test/fakertccertificategenerator.h" -#include "webrtc/pc/test/fakevideotrackrenderer.h" -#include "webrtc/pc/test/mockpeerconnectionobservers.h" - -using cricket::ContentInfo; -using cricket::FakeWebRtcVideoDecoder; -using cricket::FakeWebRtcVideoDecoderFactory; -using cricket::FakeWebRtcVideoEncoder; -using cricket::FakeWebRtcVideoEncoderFactory; -using cricket::MediaContentDescription; -using webrtc::DataBuffer; -using webrtc::DataChannelInterface; -using webrtc::DtmfSender; -using webrtc::DtmfSenderInterface; -using webrtc::DtmfSenderObserverInterface; -using webrtc::FakeConstraints; -using webrtc::MediaConstraintsInterface; -using webrtc::MediaStreamInterface; -using webrtc::MediaStreamTrackInterface; -using webrtc::MockCreateSessionDescriptionObserver; -using webrtc::MockDataChannelObserver; -using webrtc::MockSetSessionDescriptionObserver; -using webrtc::MockStatsObserver; -using webrtc::ObserverInterface; -using webrtc::PeerConnectionInterface; -using webrtc::PeerConnectionFactory; -using webrtc::SessionDescriptionInterface; -using webrtc::StreamCollectionInterface; - -namespace { - -static const int kMaxWaitMs = 10000; -// Disable for TSan v2, see -// https://code.google.com/p/webrtc/issues/detail?id=1205 for details. -// This declaration is also #ifdef'd as it causes uninitialized-variable -// warnings. -#if !defined(THREAD_SANITIZER) -static const int kMaxWaitForStatsMs = 3000; -#endif -static const int kMaxWaitForActivationMs = 5000; -static const int kMaxWaitForFramesMs = 10000; -static const int kEndAudioFrameCount = 3; -static const int kEndVideoFrameCount = 3; - -static const char kStreamLabelBase[] = "stream_label"; -static const char kVideoTrackLabelBase[] = "video_track"; -static const char kAudioTrackLabelBase[] = "audio_track"; -static const char kDataChannelLabel[] = "data_channel"; - -// Disable for TSan v2, see -// https://code.google.com/p/webrtc/issues/detail?id=1205 for details. -// This declaration is also #ifdef'd as it causes unused-variable errors. -#if !defined(THREAD_SANITIZER) -// SRTP cipher name negotiated by the tests. This must be updated if the -// default changes. -static const int kDefaultSrtpCryptoSuite = rtc::SRTP_AES128_CM_SHA1_32; -static const int kDefaultSrtpCryptoSuiteGcm = rtc::SRTP_AEAD_AES_256_GCM; -#endif - -// Used to simulate signaling ICE/SDP between two PeerConnections. -enum Message { MSG_SDP_MESSAGE, MSG_ICE_MESSAGE }; - -struct SdpMessage { - std::string type; - std::string msg; -}; - -struct IceMessage { - std::string sdp_mid; - int sdp_mline_index; - std::string msg; -}; - -static void RemoveLinesFromSdp(const std::string& line_start, - std::string* sdp) { - const char kSdpLineEnd[] = "\r\n"; - size_t ssrc_pos = 0; - while ((ssrc_pos = sdp->find(line_start, ssrc_pos)) != - std::string::npos) { - size_t end_ssrc = sdp->find(kSdpLineEnd, ssrc_pos); - sdp->erase(ssrc_pos, end_ssrc - ssrc_pos + strlen(kSdpLineEnd)); - } -} - -bool StreamsHaveAudioTrack(StreamCollectionInterface* streams) { - for (size_t idx = 0; idx < streams->count(); idx++) { - auto stream = streams->at(idx); - if (stream->GetAudioTracks().size() > 0) { - return true; - } - } - return false; -} - -bool StreamsHaveVideoTrack(StreamCollectionInterface* streams) { - for (size_t idx = 0; idx < streams->count(); idx++) { - auto stream = streams->at(idx); - if (stream->GetVideoTracks().size() > 0) { - return true; - } - } - return false; -} - -class SignalingMessageReceiver { - public: - virtual void ReceiveSdpMessage(const std::string& type, - std::string& msg) = 0; - virtual void ReceiveIceMessage(const std::string& sdp_mid, - int sdp_mline_index, - const std::string& msg) = 0; - - protected: - SignalingMessageReceiver() {} - virtual ~SignalingMessageReceiver() {} -}; - -class MockRtpReceiverObserver : public webrtc::RtpReceiverObserverInterface { - public: - MockRtpReceiverObserver(cricket::MediaType media_type) - : expected_media_type_(media_type) {} - - void OnFirstPacketReceived(cricket::MediaType media_type) override { - ASSERT_EQ(expected_media_type_, media_type); - first_packet_received_ = true; - } - - bool first_packet_received() { return first_packet_received_; } - - virtual ~MockRtpReceiverObserver() {} - - private: - bool first_packet_received_ = false; - cricket::MediaType expected_media_type_; -}; - -class PeerConnectionTestClient : public webrtc::PeerConnectionObserver, - public SignalingMessageReceiver, - public ObserverInterface, - public rtc::MessageHandler { - public: - // If |config| is not provided, uses a default constructed RTCConfiguration. - static PeerConnectionTestClient* CreateClientWithDtlsIdentityStore( - const std::string& id, - const MediaConstraintsInterface* constraints, - const PeerConnectionFactory::Options* options, - const PeerConnectionInterface::RTCConfiguration* config, - std::unique_ptr cert_generator, - bool prefer_constraint_apis, - rtc::Thread* network_thread, - rtc::Thread* worker_thread) { - PeerConnectionTestClient* client(new PeerConnectionTestClient(id)); - if (!client->Init(constraints, options, config, std::move(cert_generator), - prefer_constraint_apis, network_thread, worker_thread)) { - delete client; - return nullptr; - } - return client; - } - - static PeerConnectionTestClient* CreateClient( - const std::string& id, - const MediaConstraintsInterface* constraints, - const PeerConnectionFactory::Options* options, - const PeerConnectionInterface::RTCConfiguration* config, - rtc::Thread* network_thread, - rtc::Thread* worker_thread) { - std::unique_ptr cert_generator( - new FakeRTCCertificateGenerator()); - - return CreateClientWithDtlsIdentityStore(id, constraints, options, config, - std::move(cert_generator), true, - network_thread, worker_thread); - } - - static PeerConnectionTestClient* CreateClientPreferNoConstraints( - const std::string& id, - const PeerConnectionFactory::Options* options, - rtc::Thread* network_thread, - rtc::Thread* worker_thread) { - std::unique_ptr cert_generator( - new FakeRTCCertificateGenerator()); - - return CreateClientWithDtlsIdentityStore(id, nullptr, options, nullptr, - std::move(cert_generator), false, - network_thread, worker_thread); - } - - ~PeerConnectionTestClient() { - } - - void Negotiate() { Negotiate(true, true); } - - void Negotiate(bool audio, bool video) { - std::unique_ptr offer; - ASSERT_TRUE(DoCreateOffer(&offer)); - - if (offer->description()->GetContentByName("audio")) { - offer->description()->GetContentByName("audio")->rejected = !audio; - } - if (offer->description()->GetContentByName("video")) { - offer->description()->GetContentByName("video")->rejected = !video; - } - - std::string sdp; - EXPECT_TRUE(offer->ToString(&sdp)); - EXPECT_TRUE(DoSetLocalDescription(offer.release())); - SendSdpMessage(webrtc::SessionDescriptionInterface::kOffer, sdp); - } - - void SendSdpMessage(const std::string& type, std::string& msg) { - if (signaling_delay_ms_ == 0) { - if (signaling_message_receiver_) { - signaling_message_receiver_->ReceiveSdpMessage(type, msg); - } - } else { - rtc::Thread::Current()->PostDelayed( - RTC_FROM_HERE, signaling_delay_ms_, this, MSG_SDP_MESSAGE, - new rtc::TypedMessageData({type, msg})); - } - } - - void SendIceMessage(const std::string& sdp_mid, - int sdp_mline_index, - const std::string& msg) { - if (signaling_delay_ms_ == 0) { - if (signaling_message_receiver_) { - signaling_message_receiver_->ReceiveIceMessage(sdp_mid, sdp_mline_index, - msg); - } - } else { - rtc::Thread::Current()->PostDelayed(RTC_FROM_HERE, signaling_delay_ms_, - this, MSG_ICE_MESSAGE, - new rtc::TypedMessageData( - {sdp_mid, sdp_mline_index, msg})); - } - } - - // MessageHandler callback. - void OnMessage(rtc::Message* msg) override { - switch (msg->message_id) { - case MSG_SDP_MESSAGE: { - auto sdp_message = - static_cast*>(msg->pdata); - if (signaling_message_receiver_) { - signaling_message_receiver_->ReceiveSdpMessage( - sdp_message->data().type, sdp_message->data().msg); - } - delete sdp_message; - break; - } - case MSG_ICE_MESSAGE: { - auto ice_message = - static_cast*>(msg->pdata); - if (signaling_message_receiver_) { - signaling_message_receiver_->ReceiveIceMessage( - ice_message->data().sdp_mid, ice_message->data().sdp_mline_index, - ice_message->data().msg); - } - delete ice_message; - break; - } - default: - RTC_CHECK(false); - } - } - - // SignalingMessageReceiver callback. - void ReceiveSdpMessage(const std::string& type, std::string& msg) override { - FilterIncomingSdpMessage(&msg); - if (type == webrtc::SessionDescriptionInterface::kOffer) { - HandleIncomingOffer(msg); - } else { - HandleIncomingAnswer(msg); - } - } - - // SignalingMessageReceiver callback. - void ReceiveIceMessage(const std::string& sdp_mid, - int sdp_mline_index, - const std::string& msg) override { - LOG(INFO) << id_ << "ReceiveIceMessage"; - std::unique_ptr candidate( - webrtc::CreateIceCandidate(sdp_mid, sdp_mline_index, msg, nullptr)); - EXPECT_TRUE(pc()->AddIceCandidate(candidate.get())); - } - - // PeerConnectionObserver callbacks. - void OnSignalingChange( - webrtc::PeerConnectionInterface::SignalingState new_state) override { - EXPECT_EQ(pc()->signaling_state(), new_state); - } - void OnAddStream( - rtc::scoped_refptr media_stream) override { - media_stream->RegisterObserver(this); - for (size_t i = 0; i < media_stream->GetVideoTracks().size(); ++i) { - const std::string id = media_stream->GetVideoTracks()[i]->id(); - ASSERT_TRUE(fake_video_renderers_.find(id) == - fake_video_renderers_.end()); - fake_video_renderers_[id].reset(new webrtc::FakeVideoTrackRenderer( - media_stream->GetVideoTracks()[i])); - } - } - void OnRemoveStream( - rtc::scoped_refptr media_stream) override {} - void OnRenegotiationNeeded() override {} - void OnIceConnectionChange( - webrtc::PeerConnectionInterface::IceConnectionState new_state) override { - EXPECT_EQ(pc()->ice_connection_state(), new_state); - } - void OnIceGatheringChange( - webrtc::PeerConnectionInterface::IceGatheringState new_state) override { - EXPECT_EQ(pc()->ice_gathering_state(), new_state); - } - void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override { - LOG(INFO) << id_ << "OnIceCandidate"; - - std::string ice_sdp; - EXPECT_TRUE(candidate->ToString(&ice_sdp)); - if (signaling_message_receiver_ == nullptr) { - // Remote party may be deleted. - return; - } - SendIceMessage(candidate->sdp_mid(), candidate->sdp_mline_index(), ice_sdp); - } - - // MediaStreamInterface callback - void OnChanged() override { - // Track added or removed from MediaStream, so update our renderers. - rtc::scoped_refptr remote_streams = - pc()->remote_streams(); - // Remove renderers for tracks that were removed. - for (auto it = fake_video_renderers_.begin(); - it != fake_video_renderers_.end();) { - if (remote_streams->FindVideoTrack(it->first) == nullptr) { - auto to_remove = it++; - removed_fake_video_renderers_.push_back(std::move(to_remove->second)); - fake_video_renderers_.erase(to_remove); - } else { - ++it; - } - } - // Create renderers for new video tracks. - for (size_t stream_index = 0; stream_index < remote_streams->count(); - ++stream_index) { - MediaStreamInterface* remote_stream = remote_streams->at(stream_index); - for (size_t track_index = 0; - track_index < remote_stream->GetVideoTracks().size(); - ++track_index) { - const std::string id = - remote_stream->GetVideoTracks()[track_index]->id(); - if (fake_video_renderers_.find(id) != fake_video_renderers_.end()) { - continue; - } - fake_video_renderers_[id].reset(new webrtc::FakeVideoTrackRenderer( - remote_stream->GetVideoTracks()[track_index])); - } - } - } - - void SetVideoConstraints(const webrtc::FakeConstraints& video_constraint) { - video_constraints_ = video_constraint; - } - - void AddMediaStream(bool audio, bool video) { - std::string stream_label = - kStreamLabelBase + - rtc::ToString(static_cast(pc()->local_streams()->count())); - rtc::scoped_refptr stream = - peer_connection_factory_->CreateLocalMediaStream(stream_label); - - if (audio && can_receive_audio()) { - stream->AddTrack(CreateLocalAudioTrack(stream_label)); - } - if (video && can_receive_video()) { - stream->AddTrack(CreateLocalVideoTrack(stream_label)); - } - - EXPECT_TRUE(pc()->AddStream(stream)); - } - - size_t NumberOfLocalMediaStreams() { return pc()->local_streams()->count(); } - - bool SessionActive() { - return pc()->signaling_state() == webrtc::PeerConnectionInterface::kStable; - } - - // Automatically add a stream when receiving an offer, if we don't have one. - // Defaults to true. - void set_auto_add_stream(bool auto_add_stream) { - auto_add_stream_ = auto_add_stream; - } - - void set_signaling_message_receiver( - SignalingMessageReceiver* signaling_message_receiver) { - signaling_message_receiver_ = signaling_message_receiver; - } - - void set_signaling_delay_ms(int delay_ms) { signaling_delay_ms_ = delay_ms; } - - void EnableVideoDecoderFactory() { - video_decoder_factory_enabled_ = true; - fake_video_decoder_factory_->AddSupportedVideoCodecType( - webrtc::kVideoCodecVP8); - } - - void IceRestart() { - offer_answer_constraints_.SetMandatoryIceRestart(true); - offer_answer_options_.ice_restart = true; - SetExpectIceRestart(true); - } - - void SetExpectIceRestart(bool expect_restart) { - expect_ice_restart_ = expect_restart; - } - - bool ExpectIceRestart() const { return expect_ice_restart_; } - - void SetExpectIceRenomination(bool expect_renomination) { - expect_ice_renomination_ = expect_renomination; - } - void SetExpectRemoteIceRenomination(bool expect_renomination) { - expect_remote_ice_renomination_ = expect_renomination; - } - bool ExpectIceRenomination() { return expect_ice_renomination_; } - bool ExpectRemoteIceRenomination() { return expect_remote_ice_renomination_; } - - // The below 3 methods assume streams will be offered. - // Thus they'll only set the "offer to receive" flag to true if it's - // currently false, not if it's just unset. - void SetReceiveAudioVideo(bool audio, bool video) { - SetReceiveAudio(audio); - SetReceiveVideo(video); - ASSERT_EQ(audio, can_receive_audio()); - ASSERT_EQ(video, can_receive_video()); - } - - void SetReceiveAudio(bool audio) { - if (audio && can_receive_audio()) { - return; - } - offer_answer_constraints_.SetMandatoryReceiveAudio(audio); - offer_answer_options_.offer_to_receive_audio = audio ? 1 : 0; - } - - void SetReceiveVideo(bool video) { - if (video && can_receive_video()) { - return; - } - offer_answer_constraints_.SetMandatoryReceiveVideo(video); - offer_answer_options_.offer_to_receive_video = video ? 1 : 0; - } - - void SetOfferToReceiveAudioVideo(bool audio, bool video) { - offer_answer_constraints_.SetMandatoryReceiveAudio(audio); - offer_answer_options_.offer_to_receive_audio = audio ? 1 : 0; - offer_answer_constraints_.SetMandatoryReceiveVideo(video); - offer_answer_options_.offer_to_receive_video = video ? 1 : 0; - } - - void RemoveMsidFromReceivedSdp(bool remove) { remove_msid_ = remove; } - - void RemoveSdesCryptoFromReceivedSdp(bool remove) { remove_sdes_ = remove; } - - void RemoveBundleFromReceivedSdp(bool remove) { remove_bundle_ = remove; } - - void RemoveCvoFromReceivedSdp(bool remove) { remove_cvo_ = remove; } - - void MakeSpecCompliantMaxBundleOfferFromReceivedSdp(bool real) { - make_spec_compliant_max_bundle_offer_ = real; - } - - bool can_receive_audio() { - bool value; - if (prefer_constraint_apis_) { - if (webrtc::FindConstraint( - &offer_answer_constraints_, - MediaConstraintsInterface::kOfferToReceiveAudio, &value, - nullptr)) { - return value; - } - return true; - } - return offer_answer_options_.offer_to_receive_audio > 0 || - offer_answer_options_.offer_to_receive_audio == - PeerConnectionInterface::RTCOfferAnswerOptions::kUndefined; - } - - bool can_receive_video() { - bool value; - if (prefer_constraint_apis_) { - if (webrtc::FindConstraint( - &offer_answer_constraints_, - MediaConstraintsInterface::kOfferToReceiveVideo, &value, - nullptr)) { - return value; - } - return true; - } - return offer_answer_options_.offer_to_receive_video > 0 || - offer_answer_options_.offer_to_receive_video == - PeerConnectionInterface::RTCOfferAnswerOptions::kUndefined; - } - - void OnDataChannel( - rtc::scoped_refptr data_channel) override { - LOG(INFO) << id_ << "OnDataChannel"; - data_channel_ = data_channel; - data_observer_.reset(new MockDataChannelObserver(data_channel)); - } - - void CreateDataChannel() { CreateDataChannel(nullptr); } - - void CreateDataChannel(const webrtc::DataChannelInit* init) { - data_channel_ = pc()->CreateDataChannel(kDataChannelLabel, init); - ASSERT_TRUE(data_channel_.get() != nullptr); - data_observer_.reset(new MockDataChannelObserver(data_channel_)); - } - - rtc::scoped_refptr CreateLocalAudioTrack( - const std::string& stream_label) { - FakeConstraints constraints; - // Disable highpass filter so that we can get all the test audio frames. - constraints.AddMandatory(MediaConstraintsInterface::kHighpassFilter, false); - rtc::scoped_refptr source = - peer_connection_factory_->CreateAudioSource(&constraints); - // TODO(perkj): Test audio source when it is implemented. Currently audio - // always use the default input. - std::string label = stream_label + kAudioTrackLabelBase; - return peer_connection_factory_->CreateAudioTrack(label, source); - } - - rtc::scoped_refptr CreateLocalVideoTrack( - const std::string& stream_label) { - // Set max frame rate to 10fps to reduce the risk of the tests to be flaky. - FakeConstraints source_constraints = video_constraints_; - source_constraints.SetMandatoryMaxFrameRate(10); - - cricket::FakeVideoCapturer* fake_capturer = - new webrtc::FakePeriodicVideoCapturer(); - fake_capturer->SetRotation(capture_rotation_); - video_capturers_.push_back(fake_capturer); - rtc::scoped_refptr source = - peer_connection_factory_->CreateVideoSource( - std::unique_ptr(fake_capturer), - &source_constraints); - std::string label = stream_label + kVideoTrackLabelBase; - - rtc::scoped_refptr track( - peer_connection_factory_->CreateVideoTrack(label, source)); - if (!local_video_renderer_) { - local_video_renderer_.reset(new webrtc::FakeVideoTrackRenderer(track)); - } - return track; - } - - DataChannelInterface* data_channel() { return data_channel_; } - const MockDataChannelObserver* data_observer() const { - return data_observer_.get(); - } - - webrtc::PeerConnectionInterface* pc() const { return peer_connection_.get(); } - - void StopVideoCapturers() { - for (auto* capturer : video_capturers_) { - capturer->Stop(); - } - } - - void SetCaptureRotation(webrtc::VideoRotation rotation) { - ASSERT_TRUE(video_capturers_.empty()); - capture_rotation_ = rotation; - } - - bool AudioFramesReceivedCheck(int number_of_frames) const { - return number_of_frames <= fake_audio_capture_module_->frames_received(); - } - - int audio_frames_received() const { - return fake_audio_capture_module_->frames_received(); - } - - bool VideoFramesReceivedCheck(int number_of_frames) { - if (video_decoder_factory_enabled_) { - const std::vector& decoders - = fake_video_decoder_factory_->decoders(); - if (decoders.empty()) { - return number_of_frames <= 0; - } - // Note - this checks that EACH decoder has the requisite number - // of frames. The video_frames_received() function sums them. - for (FakeWebRtcVideoDecoder* decoder : decoders) { - if (number_of_frames > decoder->GetNumFramesReceived()) { - return false; - } - } - return true; - } else { - if (fake_video_renderers_.empty()) { - return number_of_frames <= 0; - } - - for (const auto& pair : fake_video_renderers_) { - if (number_of_frames > pair.second->num_rendered_frames()) { - return false; - } - } - return true; - } - } - - int video_frames_received() const { - int total = 0; - if (video_decoder_factory_enabled_) { - const std::vector& decoders = - fake_video_decoder_factory_->decoders(); - for (const FakeWebRtcVideoDecoder* decoder : decoders) { - total += decoder->GetNumFramesReceived(); - } - } else { - for (const auto& pair : fake_video_renderers_) { - total += pair.second->num_rendered_frames(); - } - for (const auto& renderer : removed_fake_video_renderers_) { - total += renderer->num_rendered_frames(); - } - } - return total; - } - - // Verify the CreateDtmfSender interface - void VerifyDtmf() { - std::unique_ptr observer(new DummyDtmfObserver()); - rtc::scoped_refptr dtmf_sender; - - // We can't create a DTMF sender with an invalid audio track or a non local - // track. - EXPECT_TRUE(peer_connection_->CreateDtmfSender(nullptr) == nullptr); - rtc::scoped_refptr non_localtrack( - peer_connection_factory_->CreateAudioTrack("dummy_track", nullptr)); - EXPECT_TRUE(peer_connection_->CreateDtmfSender(non_localtrack) == nullptr); - - // We should be able to create a DTMF sender from a local track. - webrtc::AudioTrackInterface* localtrack = - peer_connection_->local_streams()->at(0)->GetAudioTracks()[0]; - dtmf_sender = peer_connection_->CreateDtmfSender(localtrack); - EXPECT_TRUE(dtmf_sender.get() != nullptr); - dtmf_sender->RegisterObserver(observer.get()); - - // Test the DtmfSender object just created. - EXPECT_TRUE(dtmf_sender->CanInsertDtmf()); - EXPECT_TRUE(dtmf_sender->InsertDtmf("1a", 100, 50)); - - // We don't need to verify that the DTMF tones are actually sent out because - // that is already covered by the tests of the lower level components. - - EXPECT_TRUE_WAIT(observer->completed(), kMaxWaitMs); - std::vector tones; - tones.push_back("1"); - tones.push_back("a"); - tones.push_back(""); - observer->Verify(tones); - - dtmf_sender->UnregisterObserver(); - } - - // Verifies that the SessionDescription have rejected the appropriate media - // content. - void VerifyRejectedMediaInSessionDescription() { - ASSERT_TRUE(peer_connection_->remote_description() != nullptr); - ASSERT_TRUE(peer_connection_->local_description() != nullptr); - const cricket::SessionDescription* remote_desc = - peer_connection_->remote_description()->description(); - const cricket::SessionDescription* local_desc = - peer_connection_->local_description()->description(); - - const ContentInfo* remote_audio_content = GetFirstAudioContent(remote_desc); - if (remote_audio_content) { - const ContentInfo* audio_content = - GetFirstAudioContent(local_desc); - EXPECT_EQ(can_receive_audio(), !audio_content->rejected); - } - - const ContentInfo* remote_video_content = GetFirstVideoContent(remote_desc); - if (remote_video_content) { - const ContentInfo* video_content = - GetFirstVideoContent(local_desc); - EXPECT_EQ(can_receive_video(), !video_content->rejected); - } - } - - void VerifyLocalIceUfragAndPassword() { - ASSERT_TRUE(peer_connection_->local_description() != nullptr); - const cricket::SessionDescription* desc = - peer_connection_->local_description()->description(); - const cricket::ContentInfos& contents = desc->contents(); - - for (size_t index = 0; index < contents.size(); ++index) { - if (contents[index].rejected) - continue; - const cricket::TransportDescription* transport_desc = - desc->GetTransportDescriptionByName(contents[index].name); - - std::map::const_iterator ufragpair_it = - ice_ufrag_pwd_.find(static_cast(index)); - if (ufragpair_it == ice_ufrag_pwd_.end()) { - ASSERT_FALSE(ExpectIceRestart()); - ice_ufrag_pwd_[static_cast(index)] = - IceUfragPwdPair(transport_desc->ice_ufrag, transport_desc->ice_pwd); - } else if (ExpectIceRestart()) { - const IceUfragPwdPair& ufrag_pwd = ufragpair_it->second; - EXPECT_NE(ufrag_pwd.first, transport_desc->ice_ufrag); - EXPECT_NE(ufrag_pwd.second, transport_desc->ice_pwd); - } else { - const IceUfragPwdPair& ufrag_pwd = ufragpair_it->second; - EXPECT_EQ(ufrag_pwd.first, transport_desc->ice_ufrag); - EXPECT_EQ(ufrag_pwd.second, transport_desc->ice_pwd); - } - } - } - - void VerifyLocalIceRenomination() { - ASSERT_TRUE(peer_connection_->local_description() != nullptr); - const cricket::SessionDescription* desc = - peer_connection_->local_description()->description(); - const cricket::ContentInfos& contents = desc->contents(); - - for (auto content : contents) { - if (content.rejected) - continue; - const cricket::TransportDescription* transport_desc = - desc->GetTransportDescriptionByName(content.name); - const auto& options = transport_desc->transport_options; - auto iter = std::find(options.begin(), options.end(), - cricket::ICE_RENOMINATION_STR); - EXPECT_EQ(ExpectIceRenomination(), iter != options.end()); - } - } - - void VerifyRemoteIceRenomination() { - ASSERT_TRUE(peer_connection_->remote_description() != nullptr); - const cricket::SessionDescription* desc = - peer_connection_->remote_description()->description(); - const cricket::ContentInfos& contents = desc->contents(); - - for (auto content : contents) { - if (content.rejected) - continue; - const cricket::TransportDescription* transport_desc = - desc->GetTransportDescriptionByName(content.name); - const auto& options = transport_desc->transport_options; - auto iter = std::find(options.begin(), options.end(), - cricket::ICE_RENOMINATION_STR); - EXPECT_EQ(ExpectRemoteIceRenomination(), iter != options.end()); - } - } - - int GetAudioOutputLevelStats(webrtc::MediaStreamTrackInterface* track) { - rtc::scoped_refptr - observer(new rtc::RefCountedObject()); - EXPECT_TRUE(peer_connection_->GetStats( - observer, track, PeerConnectionInterface::kStatsOutputLevelStandard)); - EXPECT_TRUE_WAIT(observer->called(), kMaxWaitMs); - EXPECT_NE(0, observer->timestamp()); - return observer->AudioOutputLevel(); - } - - int GetAudioInputLevelStats() { - rtc::scoped_refptr - observer(new rtc::RefCountedObject()); - EXPECT_TRUE(peer_connection_->GetStats( - observer, nullptr, PeerConnectionInterface::kStatsOutputLevelStandard)); - EXPECT_TRUE_WAIT(observer->called(), kMaxWaitMs); - EXPECT_NE(0, observer->timestamp()); - return observer->AudioInputLevel(); - } - - int GetBytesReceivedStats(webrtc::MediaStreamTrackInterface* track) { - rtc::scoped_refptr - observer(new rtc::RefCountedObject()); - EXPECT_TRUE(peer_connection_->GetStats( - observer, track, PeerConnectionInterface::kStatsOutputLevelStandard)); - EXPECT_TRUE_WAIT(observer->called(), kMaxWaitMs); - EXPECT_NE(0, observer->timestamp()); - return observer->BytesReceived(); - } - - int GetBytesSentStats(webrtc::MediaStreamTrackInterface* track) { - rtc::scoped_refptr - observer(new rtc::RefCountedObject()); - EXPECT_TRUE(peer_connection_->GetStats( - observer, track, PeerConnectionInterface::kStatsOutputLevelStandard)); - EXPECT_TRUE_WAIT(observer->called(), kMaxWaitMs); - EXPECT_NE(0, observer->timestamp()); - return observer->BytesSent(); - } - - int GetAvailableReceivedBandwidthStats() { - rtc::scoped_refptr - observer(new rtc::RefCountedObject()); - EXPECT_TRUE(peer_connection_->GetStats( - observer, nullptr, PeerConnectionInterface::kStatsOutputLevelStandard)); - EXPECT_TRUE_WAIT(observer->called(), kMaxWaitMs); - EXPECT_NE(0, observer->timestamp()); - int bw = observer->AvailableReceiveBandwidth(); - return bw; - } - - std::string GetDtlsCipherStats() { - rtc::scoped_refptr - observer(new rtc::RefCountedObject()); - EXPECT_TRUE(peer_connection_->GetStats( - observer, nullptr, PeerConnectionInterface::kStatsOutputLevelStandard)); - EXPECT_TRUE_WAIT(observer->called(), kMaxWaitMs); - EXPECT_NE(0, observer->timestamp()); - return observer->DtlsCipher(); - } - - std::string GetSrtpCipherStats() { - rtc::scoped_refptr - observer(new rtc::RefCountedObject()); - EXPECT_TRUE(peer_connection_->GetStats( - observer, nullptr, PeerConnectionInterface::kStatsOutputLevelStandard)); - EXPECT_TRUE_WAIT(observer->called(), kMaxWaitMs); - EXPECT_NE(0, observer->timestamp()); - return observer->SrtpCipher(); - } - - int rendered_width() { - EXPECT_FALSE(fake_video_renderers_.empty()); - return fake_video_renderers_.empty() ? 1 : - fake_video_renderers_.begin()->second->width(); - } - - int rendered_height() { - EXPECT_FALSE(fake_video_renderers_.empty()); - return fake_video_renderers_.empty() ? 1 : - fake_video_renderers_.begin()->second->height(); - } - - webrtc::VideoRotation rendered_rotation() { - EXPECT_FALSE(fake_video_renderers_.empty()); - return fake_video_renderers_.empty() - ? webrtc::kVideoRotation_0 - : fake_video_renderers_.begin()->second->rotation(); - } - - int local_rendered_width() { - return local_video_renderer_ ? local_video_renderer_->width() : 1; - } - - int local_rendered_height() { - return local_video_renderer_ ? local_video_renderer_->height() : 1; - } - - size_t number_of_remote_streams() { - if (!pc()) - return 0; - return pc()->remote_streams()->count(); - } - - StreamCollectionInterface* remote_streams() const { - if (!pc()) { - ADD_FAILURE(); - return nullptr; - } - return pc()->remote_streams(); - } - - StreamCollectionInterface* local_streams() { - if (!pc()) { - ADD_FAILURE(); - return nullptr; - } - return pc()->local_streams(); - } - - bool HasLocalAudioTrack() { return StreamsHaveAudioTrack(local_streams()); } - - bool HasLocalVideoTrack() { return StreamsHaveVideoTrack(local_streams()); } - - webrtc::PeerConnectionInterface::SignalingState signaling_state() { - return pc()->signaling_state(); - } - - webrtc::PeerConnectionInterface::IceConnectionState ice_connection_state() { - return pc()->ice_connection_state(); - } - - webrtc::PeerConnectionInterface::IceGatheringState ice_gathering_state() { - return pc()->ice_gathering_state(); - } - - std::vector> const& - rtp_receiver_observers() { - return rtp_receiver_observers_; - } - - void SetRtpReceiverObservers() { - rtp_receiver_observers_.clear(); - for (auto receiver : pc()->GetReceivers()) { - std::unique_ptr observer( - new MockRtpReceiverObserver(receiver->media_type())); - receiver->SetObserver(observer.get()); - rtp_receiver_observers_.push_back(std::move(observer)); - } - } - - private: - class DummyDtmfObserver : public DtmfSenderObserverInterface { - public: - DummyDtmfObserver() : completed_(false) {} - - // Implements DtmfSenderObserverInterface. - void OnToneChange(const std::string& tone) override { - tones_.push_back(tone); - if (tone.empty()) { - completed_ = true; - } - } - - void Verify(const std::vector& tones) const { - ASSERT_TRUE(tones_.size() == tones.size()); - EXPECT_TRUE(std::equal(tones.begin(), tones.end(), tones_.begin())); - } - - bool completed() const { return completed_; } - - private: - bool completed_; - std::vector tones_; - }; - - explicit PeerConnectionTestClient(const std::string& id) : id_(id) {} - - bool Init( - const MediaConstraintsInterface* constraints, - const PeerConnectionFactory::Options* options, - const PeerConnectionInterface::RTCConfiguration* config, - std::unique_ptr cert_generator, - bool prefer_constraint_apis, - rtc::Thread* network_thread, - rtc::Thread* worker_thread) { - EXPECT_TRUE(!peer_connection_); - EXPECT_TRUE(!peer_connection_factory_); - if (!prefer_constraint_apis) { - EXPECT_TRUE(!constraints); - } - prefer_constraint_apis_ = prefer_constraint_apis; - - fake_network_manager_.reset(new rtc::FakeNetworkManager()); - fake_network_manager_->AddInterface(rtc::SocketAddress("192.168.1.1", 0)); - - std::unique_ptr port_allocator( - new cricket::BasicPortAllocator(fake_network_manager_.get())); - fake_audio_capture_module_ = FakeAudioCaptureModule::Create(); - - if (fake_audio_capture_module_ == nullptr) { - return false; - } - fake_video_decoder_factory_ = new FakeWebRtcVideoDecoderFactory(); - fake_video_encoder_factory_ = new FakeWebRtcVideoEncoderFactory(); - rtc::Thread* const signaling_thread = rtc::Thread::Current(); - peer_connection_factory_ = webrtc::CreatePeerConnectionFactory( - network_thread, worker_thread, signaling_thread, - fake_audio_capture_module_, fake_video_encoder_factory_, - fake_video_decoder_factory_); - if (!peer_connection_factory_) { - return false; - } - if (options) { - peer_connection_factory_->SetOptions(*options); - } - peer_connection_ = - CreatePeerConnection(std::move(port_allocator), constraints, config, - std::move(cert_generator)); - return peer_connection_.get() != nullptr; - } - - rtc::scoped_refptr CreatePeerConnection( - std::unique_ptr port_allocator, - const MediaConstraintsInterface* constraints, - const PeerConnectionInterface::RTCConfiguration* config, - std::unique_ptr cert_generator) { - // CreatePeerConnection with RTCConfiguration. - PeerConnectionInterface::RTCConfiguration default_config; - - if (!config) { - config = &default_config; - } - - return peer_connection_factory_->CreatePeerConnection( - *config, constraints, std::move(port_allocator), - std::move(cert_generator), this); - } - - void HandleIncomingOffer(const std::string& msg) { - LOG(INFO) << id_ << "HandleIncomingOffer "; - if (NumberOfLocalMediaStreams() == 0 && auto_add_stream_) { - // If we are not sending any streams ourselves it is time to add some. - AddMediaStream(true, true); - } - std::unique_ptr desc( - webrtc::CreateSessionDescription("offer", msg, nullptr)); - - // Do the equivalent of setting the port to 0, adding a=bundle-only, and - // removing a=ice-ufrag, a=ice-pwd, a=fingerprint and a=setup from all but - // the first m= section. - if (make_spec_compliant_max_bundle_offer_) { - bool first = true; - for (cricket::ContentInfo& content : desc->description()->contents()) { - if (first) { - first = false; - continue; - } - content.bundle_only = true; - } - first = true; - for (cricket::TransportInfo& transport : - desc->description()->transport_infos()) { - if (first) { - first = false; - continue; - } - transport.description.ice_ufrag.clear(); - transport.description.ice_pwd.clear(); - transport.description.connection_role = cricket::CONNECTIONROLE_NONE; - transport.description.identity_fingerprint.reset(nullptr); - } - } - - EXPECT_TRUE(DoSetRemoteDescription(desc.release())); - // Set the RtpReceiverObserver after receivers are created. - SetRtpReceiverObservers(); - std::unique_ptr answer; - EXPECT_TRUE(DoCreateAnswer(&answer)); - std::string sdp; - EXPECT_TRUE(answer->ToString(&sdp)); - EXPECT_TRUE(DoSetLocalDescription(answer.release())); - SendSdpMessage(webrtc::SessionDescriptionInterface::kAnswer, sdp); - } - - void HandleIncomingAnswer(const std::string& msg) { - LOG(INFO) << id_ << "HandleIncomingAnswer"; - std::unique_ptr desc( - webrtc::CreateSessionDescription("answer", msg, nullptr)); - EXPECT_TRUE(DoSetRemoteDescription(desc.release())); - // Set the RtpReceiverObserver after receivers are created. - SetRtpReceiverObservers(); - } - - bool DoCreateOfferAnswer(std::unique_ptr* desc, - bool offer) { - rtc::scoped_refptr - observer(new rtc::RefCountedObject< - MockCreateSessionDescriptionObserver>()); - if (prefer_constraint_apis_) { - if (offer) { - pc()->CreateOffer(observer, &offer_answer_constraints_); - } else { - pc()->CreateAnswer(observer, &offer_answer_constraints_); - } - } else { - if (offer) { - pc()->CreateOffer(observer, offer_answer_options_); - } else { - pc()->CreateAnswer(observer, offer_answer_options_); - } - } - EXPECT_EQ_WAIT(true, observer->called(), kMaxWaitMs); - desc->reset(observer->release_desc()); - if (observer->result() && ExpectIceRestart()) { - EXPECT_EQ(0u, (*desc)->candidates(0)->count()); - } - return observer->result(); - } - - bool DoCreateOffer(std::unique_ptr* desc) { - return DoCreateOfferAnswer(desc, true); - } - - bool DoCreateAnswer(std::unique_ptr* desc) { - return DoCreateOfferAnswer(desc, false); - } - - bool DoSetLocalDescription(SessionDescriptionInterface* desc) { - rtc::scoped_refptr - observer(new rtc::RefCountedObject< - MockSetSessionDescriptionObserver>()); - LOG(INFO) << id_ << "SetLocalDescription "; - pc()->SetLocalDescription(observer, desc); - // Ignore the observer result. If we wait for the result with - // EXPECT_TRUE_WAIT, local ice candidates might be sent to the remote peer - // before the offer which is an error. - // The reason is that EXPECT_TRUE_WAIT uses - // rtc::Thread::Current()->ProcessMessages(1); - // ProcessMessages waits at least 1ms but processes all messages before - // returning. Since this test is synchronous and send messages to the remote - // peer whenever a callback is invoked, this can lead to messages being - // sent to the remote peer in the wrong order. - // TODO(perkj): Find a way to check the result without risking that the - // order of sent messages are changed. Ex- by posting all messages that are - // sent to the remote peer. - return true; - } - - bool DoSetRemoteDescription(SessionDescriptionInterface* desc) { - rtc::scoped_refptr - observer(new rtc::RefCountedObject< - MockSetSessionDescriptionObserver>()); - LOG(INFO) << id_ << "SetRemoteDescription "; - pc()->SetRemoteDescription(observer, desc); - EXPECT_TRUE_WAIT(observer->called(), kMaxWaitMs); - return observer->result(); - } - - // This modifies all received SDP messages before they are processed. - void FilterIncomingSdpMessage(std::string* sdp) { - if (remove_msid_) { - const char kSdpSsrcAttribute[] = "a=ssrc:"; - RemoveLinesFromSdp(kSdpSsrcAttribute, sdp); - const char kSdpMsidSupportedAttribute[] = "a=msid-semantic:"; - RemoveLinesFromSdp(kSdpMsidSupportedAttribute, sdp); - } - if (remove_bundle_) { - const char kSdpBundleAttribute[] = "a=group:BUNDLE"; - RemoveLinesFromSdp(kSdpBundleAttribute, sdp); - } - if (remove_sdes_) { - const char kSdpSdesCryptoAttribute[] = "a=crypto"; - RemoveLinesFromSdp(kSdpSdesCryptoAttribute, sdp); - } - if (remove_cvo_) { - const char kSdpCvoExtenstion[] = "urn:3gpp:video-orientation"; - RemoveLinesFromSdp(kSdpCvoExtenstion, sdp); - } - } - - std::string id_; - - std::unique_ptr fake_network_manager_; - - rtc::scoped_refptr peer_connection_; - rtc::scoped_refptr - peer_connection_factory_; - - bool prefer_constraint_apis_ = true; - bool auto_add_stream_ = true; - - typedef std::pair IceUfragPwdPair; - std::map ice_ufrag_pwd_; - bool expect_ice_restart_ = false; - bool expect_ice_renomination_ = false; - bool expect_remote_ice_renomination_ = false; - - // Needed to keep track of number of frames sent. - rtc::scoped_refptr fake_audio_capture_module_; - // Needed to keep track of number of frames received. - std::map> - fake_video_renderers_; - // Needed to ensure frames aren't received for removed tracks. - std::vector> - removed_fake_video_renderers_; - // Needed to keep track of number of frames received when external decoder - // used. - FakeWebRtcVideoDecoderFactory* fake_video_decoder_factory_ = nullptr; - FakeWebRtcVideoEncoderFactory* fake_video_encoder_factory_ = nullptr; - bool video_decoder_factory_enabled_ = false; - webrtc::FakeConstraints video_constraints_; - - // For remote peer communication. - SignalingMessageReceiver* signaling_message_receiver_ = nullptr; - int signaling_delay_ms_ = 0; - - // Store references to the video capturers we've created, so that we can stop - // them, if required. - std::vector video_capturers_; - webrtc::VideoRotation capture_rotation_ = webrtc::kVideoRotation_0; - // |local_video_renderer_| attached to the first created local video track. - std::unique_ptr local_video_renderer_; - - webrtc::FakeConstraints offer_answer_constraints_; - PeerConnectionInterface::RTCOfferAnswerOptions offer_answer_options_; - bool remove_msid_ = false; // True if MSID should be removed in received SDP. - bool remove_bundle_ = - false; // True if bundle should be removed in received SDP. - bool remove_sdes_ = - false; // True if a=crypto should be removed in received SDP. - // |remove_cvo_| is true if extension urn:3gpp:video-orientation should be - // removed in the received SDP. - bool remove_cvo_ = false; - // See LocalP2PTestWithSpecCompliantMaxBundleOffer. - bool make_spec_compliant_max_bundle_offer_ = false; - - rtc::scoped_refptr data_channel_; - std::unique_ptr data_observer_; - - std::vector> rtp_receiver_observers_; -}; - -class P2PTestConductor : public testing::Test { - public: - P2PTestConductor() - : pss_(new rtc::PhysicalSocketServer), - ss_(new rtc::VirtualSocketServer(pss_.get())), - network_thread_(new rtc::Thread(ss_.get())), - worker_thread_(rtc::Thread::Create()) { - RTC_CHECK(network_thread_->Start()); - RTC_CHECK(worker_thread_->Start()); - } - - bool SessionActive() { - return initiating_client_->SessionActive() && - receiving_client_->SessionActive(); - } - - // Return true if the number of frames provided have been received - // on the video and audio tracks provided. - bool FramesHaveArrived(int audio_frames_to_receive, - int video_frames_to_receive) { - bool all_good = true; - if (initiating_client_->HasLocalAudioTrack() && - receiving_client_->can_receive_audio()) { - all_good &= - receiving_client_->AudioFramesReceivedCheck(audio_frames_to_receive); - } - if (initiating_client_->HasLocalVideoTrack() && - receiving_client_->can_receive_video()) { - all_good &= - receiving_client_->VideoFramesReceivedCheck(video_frames_to_receive); - } - if (receiving_client_->HasLocalAudioTrack() && - initiating_client_->can_receive_audio()) { - all_good &= - initiating_client_->AudioFramesReceivedCheck(audio_frames_to_receive); - } - if (receiving_client_->HasLocalVideoTrack() && - initiating_client_->can_receive_video()) { - all_good &= - initiating_client_->VideoFramesReceivedCheck(video_frames_to_receive); - } - return all_good; - } - - void VerifyDtmf() { - initiating_client_->VerifyDtmf(); - receiving_client_->VerifyDtmf(); - } - - void TestUpdateOfferWithRejectedContent() { - // Renegotiate, rejecting the video m-line. - initiating_client_->Negotiate(true, false); - ASSERT_TRUE_WAIT(SessionActive(), kMaxWaitForActivationMs); - - int pc1_audio_received = initiating_client_->audio_frames_received(); - int pc1_video_received = initiating_client_->video_frames_received(); - int pc2_audio_received = receiving_client_->audio_frames_received(); - int pc2_video_received = receiving_client_->video_frames_received(); - - // Wait for some additional audio frames to be received. - EXPECT_TRUE_WAIT(initiating_client_->AudioFramesReceivedCheck( - pc1_audio_received + kEndAudioFrameCount) && - receiving_client_->AudioFramesReceivedCheck( - pc2_audio_received + kEndAudioFrameCount), - kMaxWaitForFramesMs); - - // During this time, we shouldn't have received any additional video frames - // for the rejected video tracks. - EXPECT_EQ(pc1_video_received, initiating_client_->video_frames_received()); - EXPECT_EQ(pc2_video_received, receiving_client_->video_frames_received()); - } - - void VerifyRenderedAspectRatio(int width, int height) { - VerifyRenderedAspectRatio(width, height, webrtc::kVideoRotation_0); - } - - void VerifyRenderedAspectRatio(int width, - int height, - webrtc::VideoRotation rotation) { - double expected_aspect_ratio = static_cast(width) / height; - double receiving_client_rendered_aspect_ratio = - static_cast(receiving_client()->rendered_width()) / - receiving_client()->rendered_height(); - double initializing_client_rendered_aspect_ratio = - static_cast(initializing_client()->rendered_width()) / - initializing_client()->rendered_height(); - double initializing_client_local_rendered_aspect_ratio = - static_cast(initializing_client()->local_rendered_width()) / - initializing_client()->local_rendered_height(); - // Verify end-to-end rendered aspect ratio. - EXPECT_EQ(expected_aspect_ratio, receiving_client_rendered_aspect_ratio); - EXPECT_EQ(expected_aspect_ratio, initializing_client_rendered_aspect_ratio); - // Verify aspect ratio of the local preview. - EXPECT_EQ(expected_aspect_ratio, - initializing_client_local_rendered_aspect_ratio); - - // Verify rotation. - EXPECT_EQ(rotation, receiving_client()->rendered_rotation()); - EXPECT_EQ(rotation, initializing_client()->rendered_rotation()); - } - - void VerifySessionDescriptions() { - initiating_client_->VerifyRejectedMediaInSessionDescription(); - receiving_client_->VerifyRejectedMediaInSessionDescription(); - initiating_client_->VerifyLocalIceUfragAndPassword(); - receiving_client_->VerifyLocalIceUfragAndPassword(); - } - - ~P2PTestConductor() { - if (initiating_client_) { - initiating_client_->set_signaling_message_receiver(nullptr); - } - if (receiving_client_) { - receiving_client_->set_signaling_message_receiver(nullptr); - } - } - - bool CreateTestClients() { return CreateTestClients(nullptr, nullptr); } - - bool CreateTestClients(MediaConstraintsInterface* init_constraints, - MediaConstraintsInterface* recv_constraints) { - return CreateTestClients(init_constraints, nullptr, nullptr, - recv_constraints, nullptr, nullptr); - } - - bool CreateTestClients( - const PeerConnectionInterface::RTCConfiguration& init_config, - const PeerConnectionInterface::RTCConfiguration& recv_config) { - return CreateTestClients(nullptr, nullptr, &init_config, nullptr, nullptr, - &recv_config); - } - - bool CreateTestClientsThatPreferNoConstraints() { - initiating_client_.reset( - PeerConnectionTestClient::CreateClientPreferNoConstraints( - "Caller: ", nullptr, network_thread_.get(), worker_thread_.get())); - receiving_client_.reset( - PeerConnectionTestClient::CreateClientPreferNoConstraints( - "Callee: ", nullptr, network_thread_.get(), worker_thread_.get())); - if (!initiating_client_ || !receiving_client_) { - return false; - } - // Remember the choice for possible later resets of the clients. - prefer_constraint_apis_ = false; - SetSignalingReceivers(); - return true; - } - - bool CreateTestClients( - MediaConstraintsInterface* init_constraints, - PeerConnectionFactory::Options* init_options, - const PeerConnectionInterface::RTCConfiguration* init_config, - MediaConstraintsInterface* recv_constraints, - PeerConnectionFactory::Options* recv_options, - const PeerConnectionInterface::RTCConfiguration* recv_config) { - initiating_client_.reset(PeerConnectionTestClient::CreateClient( - "Caller: ", init_constraints, init_options, init_config, - network_thread_.get(), worker_thread_.get())); - receiving_client_.reset(PeerConnectionTestClient::CreateClient( - "Callee: ", recv_constraints, recv_options, recv_config, - network_thread_.get(), worker_thread_.get())); - if (!initiating_client_ || !receiving_client_) { - return false; - } - SetSignalingReceivers(); - return true; - } - - void SetSignalingReceivers() { - initiating_client_->set_signaling_message_receiver(receiving_client_.get()); - receiving_client_->set_signaling_message_receiver(initiating_client_.get()); - } - - void SetSignalingDelayMs(int delay_ms) { - initiating_client_->set_signaling_delay_ms(delay_ms); - receiving_client_->set_signaling_delay_ms(delay_ms); - } - - void SetVideoConstraints(const webrtc::FakeConstraints& init_constraints, - const webrtc::FakeConstraints& recv_constraints) { - initiating_client_->SetVideoConstraints(init_constraints); - receiving_client_->SetVideoConstraints(recv_constraints); - } - - void SetCaptureRotation(webrtc::VideoRotation rotation) { - initiating_client_->SetCaptureRotation(rotation); - receiving_client_->SetCaptureRotation(rotation); - } - - void EnableVideoDecoderFactory() { - initiating_client_->EnableVideoDecoderFactory(); - receiving_client_->EnableVideoDecoderFactory(); - } - - // This test sets up a call between two parties. Both parties send static - // frames to each other. Once the test is finished the number of sent frames - // is compared to the number of received frames. - void LocalP2PTest() { - if (initiating_client_->NumberOfLocalMediaStreams() == 0) { - initiating_client_->AddMediaStream(true, true); - } - initiating_client_->Negotiate(); - // Assert true is used here since next tests are guaranteed to fail and - // would eat up 5 seconds. - ASSERT_TRUE_WAIT(SessionActive(), kMaxWaitForActivationMs); - VerifySessionDescriptions(); - - int audio_frame_count = kEndAudioFrameCount; - int video_frame_count = kEndVideoFrameCount; - // TODO(ronghuawu): Add test to cover the case of sendonly and recvonly. - - if ((!initiating_client_->can_receive_audio() && - !initiating_client_->can_receive_video()) || - (!receiving_client_->can_receive_audio() && - !receiving_client_->can_receive_video())) { - // Neither audio nor video will flow, so connections won't be - // established. There's nothing more to check. - // TODO(hta): Check connection if there's a data channel. - return; - } - - // Audio or video is expected to flow, so both clients should reach the - // Connected state, and the offerer (ICE controller) should proceed to - // Completed. - // Note: These tests have been observed to fail under heavy load at - // shorter timeouts, so they may be flaky. - EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceConnectionCompleted, - initiating_client_->ice_connection_state(), - kMaxWaitForFramesMs); - EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceConnectionConnected, - receiving_client_->ice_connection_state(), - kMaxWaitForFramesMs); - - // The ICE gathering state should end up in kIceGatheringComplete, - // but there's a bug that prevents this at the moment, and the state - // machine is being updated by the WEBRTC WG. - // TODO(hta): Update this check when spec revisions finish. - EXPECT_NE(webrtc::PeerConnectionInterface::kIceGatheringNew, - initiating_client_->ice_gathering_state()); - EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceGatheringComplete, - receiving_client_->ice_gathering_state(), - kMaxWaitForFramesMs); - - // Check that the expected number of frames have arrived. - EXPECT_TRUE_WAIT(FramesHaveArrived(audio_frame_count, video_frame_count), - kMaxWaitForFramesMs); - } - - void SetupAndVerifyDtlsCall() { - FakeConstraints setup_constraints; - setup_constraints.AddMandatory(MediaConstraintsInterface::kEnableDtlsSrtp, - true); - // Disable resolution adaptation, we don't want it interfering with the - // test results. - webrtc::PeerConnectionInterface::RTCConfiguration rtc_config; - rtc_config.set_cpu_adaptation(false); - - ASSERT_TRUE(CreateTestClients(&setup_constraints, nullptr, &rtc_config, - &setup_constraints, nullptr, &rtc_config)); - LocalP2PTest(); - VerifyRenderedAspectRatio(640, 480); - } - - PeerConnectionTestClient* CreateDtlsClientWithAlternateKey() { - FakeConstraints setup_constraints; - setup_constraints.AddMandatory(MediaConstraintsInterface::kEnableDtlsSrtp, - true); - // Disable resolution adaptation, we don't want it interfering with the - // test results. - webrtc::PeerConnectionInterface::RTCConfiguration rtc_config; - rtc_config.set_cpu_adaptation(false); - - std::unique_ptr cert_generator( - new FakeRTCCertificateGenerator()); - cert_generator->use_alternate_key(); - - // Make sure the new client is using a different certificate. - return PeerConnectionTestClient::CreateClientWithDtlsIdentityStore( - "New Peer: ", &setup_constraints, nullptr, &rtc_config, - std::move(cert_generator), prefer_constraint_apis_, - network_thread_.get(), worker_thread_.get()); - } - - void SendRtpData(webrtc::DataChannelInterface* dc, const std::string& data) { - // Messages may get lost on the unreliable DataChannel, so we send multiple - // times to avoid test flakiness. - static const size_t kSendAttempts = 5; - - for (size_t i = 0; i < kSendAttempts; ++i) { - dc->Send(DataBuffer(data)); - } - } - - rtc::Thread* network_thread() { return network_thread_.get(); } - - rtc::VirtualSocketServer* virtual_socket_server() { return ss_.get(); } - - PeerConnectionTestClient* initializing_client() { - return initiating_client_.get(); - } - - // Set the |initiating_client_| to the |client| passed in and return the - // original |initiating_client_|. - PeerConnectionTestClient* set_initializing_client( - PeerConnectionTestClient* client) { - PeerConnectionTestClient* old = initiating_client_.release(); - initiating_client_.reset(client); - return old; - } - - PeerConnectionTestClient* receiving_client() { - return receiving_client_.get(); - } - - // Set the |receiving_client_| to the |client| passed in and return the - // original |receiving_client_|. - PeerConnectionTestClient* set_receiving_client( - PeerConnectionTestClient* client) { - PeerConnectionTestClient* old = receiving_client_.release(); - receiving_client_.reset(client); - return old; - } - - bool AllObserversReceived( - const std::vector>& observers) { - for (auto& observer : observers) { - if (!observer->first_packet_received()) { - return false; - } - } - return true; - } - - void TestGcmNegotiation(bool local_gcm_enabled, bool remote_gcm_enabled, - int expected_cipher_suite) { - PeerConnectionFactory::Options init_options; - init_options.crypto_options.enable_gcm_crypto_suites = local_gcm_enabled; - PeerConnectionFactory::Options recv_options; - recv_options.crypto_options.enable_gcm_crypto_suites = remote_gcm_enabled; - ASSERT_TRUE(CreateTestClients(nullptr, &init_options, nullptr, nullptr, - &recv_options, nullptr)); - rtc::scoped_refptr - init_observer = - new rtc::RefCountedObject(); - initializing_client()->pc()->RegisterUMAObserver(init_observer); - LocalP2PTest(); - - EXPECT_EQ_WAIT(rtc::SrtpCryptoSuiteToName(expected_cipher_suite), - initializing_client()->GetSrtpCipherStats(), - kMaxWaitMs); - EXPECT_EQ(1, - init_observer->GetEnumCounter(webrtc::kEnumCounterAudioSrtpCipher, - expected_cipher_suite)); - } - - private: - // |ss_| is used by |network_thread_| so it must be destroyed later. - std::unique_ptr pss_; - std::unique_ptr ss_; - // |network_thread_| and |worker_thread_| are used by both - // |initiating_client_| and |receiving_client_| so they must be destroyed - // later. - std::unique_ptr network_thread_; - std::unique_ptr worker_thread_; - std::unique_ptr initiating_client_; - std::unique_ptr receiving_client_; - bool prefer_constraint_apis_ = true; -}; - -// Disable for TSan v2, see -// https://code.google.com/p/webrtc/issues/detail?id=1205 for details. -#if !defined(THREAD_SANITIZER) - -TEST_F(P2PTestConductor, TestRtpReceiverObserverCallbackFunction) { - ASSERT_TRUE(CreateTestClients()); - LocalP2PTest(); - EXPECT_TRUE_WAIT( - AllObserversReceived(initializing_client()->rtp_receiver_observers()), - kMaxWaitForFramesMs); - EXPECT_TRUE_WAIT( - AllObserversReceived(receiving_client()->rtp_receiver_observers()), - kMaxWaitForFramesMs); -} - -// The observers are expected to fire the signal even if they are set after the -// first packet is received. -TEST_F(P2PTestConductor, TestSetRtpReceiverObserverAfterFirstPacketIsReceived) { - ASSERT_TRUE(CreateTestClients()); - LocalP2PTest(); - // Reset the RtpReceiverObservers. - initializing_client()->SetRtpReceiverObservers(); - receiving_client()->SetRtpReceiverObservers(); - EXPECT_TRUE_WAIT( - AllObserversReceived(initializing_client()->rtp_receiver_observers()), - kMaxWaitForFramesMs); - EXPECT_TRUE_WAIT( - AllObserversReceived(receiving_client()->rtp_receiver_observers()), - kMaxWaitForFramesMs); -} - -// This test sets up a Jsep call between two parties and test Dtmf. -// TODO(holmer): Disabled due to sometimes crashing on buildbots. -// See issue webrtc/2378. -TEST_F(P2PTestConductor, DISABLED_LocalP2PTestDtmf) { - ASSERT_TRUE(CreateTestClients()); - LocalP2PTest(); - VerifyDtmf(); -} - -// This test sets up a Jsep call between two parties and test that we can get a -// video aspect ratio of 16:9. -TEST_F(P2PTestConductor, LocalP2PTest16To9) { - ASSERT_TRUE(CreateTestClients()); - FakeConstraints constraint; - double requested_ratio = 640.0/360; - constraint.SetMandatoryMinAspectRatio(requested_ratio); - SetVideoConstraints(constraint, constraint); - LocalP2PTest(); - - ASSERT_LE(0, initializing_client()->rendered_height()); - double initiating_video_ratio = - static_cast(initializing_client()->rendered_width()) / - initializing_client()->rendered_height(); - EXPECT_LE(requested_ratio, initiating_video_ratio); - - ASSERT_LE(0, receiving_client()->rendered_height()); - double receiving_video_ratio = - static_cast(receiving_client()->rendered_width()) / - receiving_client()->rendered_height(); - EXPECT_LE(requested_ratio, receiving_video_ratio); -} - -// This test sets up a Jsep call between two parties and test that the -// received video has a resolution of 1280*720. -// TODO(mallinath): Enable when -// http://code.google.com/p/webrtc/issues/detail?id=981 is fixed. -TEST_F(P2PTestConductor, DISABLED_LocalP2PTest1280By720) { - ASSERT_TRUE(CreateTestClients()); - FakeConstraints constraint; - constraint.SetMandatoryMinWidth(1280); - constraint.SetMandatoryMinHeight(720); - SetVideoConstraints(constraint, constraint); - LocalP2PTest(); - VerifyRenderedAspectRatio(1280, 720); -} - -// This test sets up a call between two endpoints that are configured to use -// DTLS key agreement. As a result, DTLS is negotiated and used for transport. -TEST_F(P2PTestConductor, LocalP2PTestDtls) { - SetupAndVerifyDtlsCall(); -} - -// This test sets up an one-way call, with media only from initiator to -// responder. -TEST_F(P2PTestConductor, OneWayMediaCall) { - ASSERT_TRUE(CreateTestClients()); - receiving_client()->set_auto_add_stream(false); - LocalP2PTest(); -} - -TEST_F(P2PTestConductor, OneWayMediaCallWithoutConstraints) { - ASSERT_TRUE(CreateTestClientsThatPreferNoConstraints()); - receiving_client()->set_auto_add_stream(false); - LocalP2PTest(); -} - -// This test sets up a audio call initially and then upgrades to audio/video, -// using DTLS. -TEST_F(P2PTestConductor, LocalP2PTestDtlsRenegotiate) { - FakeConstraints setup_constraints; - setup_constraints.AddMandatory(MediaConstraintsInterface::kEnableDtlsSrtp, - true); - ASSERT_TRUE(CreateTestClients(&setup_constraints, &setup_constraints)); - receiving_client()->SetReceiveAudioVideo(true, false); - LocalP2PTest(); - receiving_client()->SetReceiveAudioVideo(true, true); - receiving_client()->Negotiate(); -} - -// This test sets up a call transfer to a new caller with a different DTLS -// fingerprint. -TEST_F(P2PTestConductor, LocalP2PTestDtlsTransferCallee) { - SetupAndVerifyDtlsCall(); - - // Keeping the original peer around which will still send packets to the - // receiving client. These SRTP packets will be dropped. - std::unique_ptr original_peer( - set_initializing_client(CreateDtlsClientWithAlternateKey())); - original_peer->pc()->Close(); - - SetSignalingReceivers(); - receiving_client()->SetExpectIceRestart(true); - LocalP2PTest(); - VerifyRenderedAspectRatio(640, 480); -} - -// This test sets up a non-bundle call and apply bundle during ICE restart. When -// bundle is in effect in the restart, the channel can successfully reset its -// DTLS-SRTP context. -TEST_F(P2PTestConductor, LocalP2PTestDtlsBundleInIceRestart) { - FakeConstraints setup_constraints; - setup_constraints.AddMandatory(MediaConstraintsInterface::kEnableDtlsSrtp, - true); - ASSERT_TRUE(CreateTestClients(&setup_constraints, &setup_constraints)); - receiving_client()->RemoveBundleFromReceivedSdp(true); - LocalP2PTest(); - VerifyRenderedAspectRatio(640, 480); - - initializing_client()->IceRestart(); - receiving_client()->SetExpectIceRestart(true); - receiving_client()->RemoveBundleFromReceivedSdp(false); - LocalP2PTest(); - VerifyRenderedAspectRatio(640, 480); -} - -// This test sets up a call transfer to a new callee with a different DTLS -// fingerprint. -TEST_F(P2PTestConductor, LocalP2PTestDtlsTransferCaller) { - SetupAndVerifyDtlsCall(); - - // Keeping the original peer around which will still send packets to the - // receiving client. These SRTP packets will be dropped. - std::unique_ptr original_peer( - set_receiving_client(CreateDtlsClientWithAlternateKey())); - original_peer->pc()->Close(); - - SetSignalingReceivers(); - initializing_client()->IceRestart(); - LocalP2PTest(); - VerifyRenderedAspectRatio(640, 480); -} - -TEST_F(P2PTestConductor, LocalP2PTestCVO) { - ASSERT_TRUE(CreateTestClients()); - SetCaptureRotation(webrtc::kVideoRotation_90); - LocalP2PTest(); - VerifyRenderedAspectRatio(640, 480, webrtc::kVideoRotation_90); -} - -TEST_F(P2PTestConductor, LocalP2PTestReceiverDoesntSupportCVO) { - ASSERT_TRUE(CreateTestClients()); - SetCaptureRotation(webrtc::kVideoRotation_90); - receiving_client()->RemoveCvoFromReceivedSdp(true); - LocalP2PTest(); - VerifyRenderedAspectRatio(480, 640, webrtc::kVideoRotation_0); -} - -// This test sets up a call between two endpoints that are configured to use -// DTLS key agreement. The offerer don't support SDES. As a result, DTLS is -// negotiated and used for transport. -TEST_F(P2PTestConductor, LocalP2PTestOfferDtlsButNotSdes) { - FakeConstraints setup_constraints; - setup_constraints.AddMandatory(MediaConstraintsInterface::kEnableDtlsSrtp, - true); - ASSERT_TRUE(CreateTestClients(&setup_constraints, &setup_constraints)); - receiving_client()->RemoveSdesCryptoFromReceivedSdp(true); - LocalP2PTest(); - VerifyRenderedAspectRatio(640, 480); -} - -#ifdef HAVE_SCTP -// This test verifies that the negotiation will succeed with data channel only -// in max-bundle mode. -TEST_F(P2PTestConductor, LocalP2PTestOfferDataChannelOnly) { - webrtc::PeerConnectionInterface::RTCConfiguration rtc_config; - rtc_config.bundle_policy = - webrtc::PeerConnectionInterface::kBundlePolicyMaxBundle; - ASSERT_TRUE(CreateTestClients(rtc_config, rtc_config)); - initializing_client()->CreateDataChannel(); - initializing_client()->Negotiate(); -} -#endif - -// This test sets up a Jsep call between two parties, and the callee only -// accept to receive video. -TEST_F(P2PTestConductor, LocalP2PTestAnswerVideo) { - ASSERT_TRUE(CreateTestClients()); - receiving_client()->SetReceiveAudioVideo(false, true); - LocalP2PTest(); -} - -// This test sets up a Jsep call between two parties, and the callee only -// accept to receive audio. -TEST_F(P2PTestConductor, LocalP2PTestAnswerAudio) { - ASSERT_TRUE(CreateTestClients()); - receiving_client()->SetReceiveAudioVideo(true, false); - LocalP2PTest(); -} - -// This test sets up a Jsep call between two parties, and the callee reject both -// audio and video. -TEST_F(P2PTestConductor, LocalP2PTestAnswerNone) { - ASSERT_TRUE(CreateTestClients()); - receiving_client()->SetReceiveAudioVideo(false, false); - LocalP2PTest(); -} - -// This test sets up an audio and video call between two parties. After the call -// runs for a while (10 frames), the caller sends an update offer with video -// being rejected. Once the re-negotiation is done, the video flow should stop -// and the audio flow should continue. -TEST_F(P2PTestConductor, UpdateOfferWithRejectedContent) { - ASSERT_TRUE(CreateTestClients()); - LocalP2PTest(); - TestUpdateOfferWithRejectedContent(); -} - -// This test sets up a Jsep call between two parties. The MSID is removed from -// the SDP strings from the caller. -TEST_F(P2PTestConductor, LocalP2PTestWithoutMsid) { - ASSERT_TRUE(CreateTestClients()); - receiving_client()->RemoveMsidFromReceivedSdp(true); - // TODO(perkj): Currently there is a bug that cause audio to stop playing if - // audio and video is muxed when MSID is disabled. Remove - // SetRemoveBundleFromSdp once - // https://code.google.com/p/webrtc/issues/detail?id=1193 is fixed. - receiving_client()->RemoveBundleFromReceivedSdp(true); - LocalP2PTest(); -} - -TEST_F(P2PTestConductor, LocalP2PTestTwoStreams) { - ASSERT_TRUE(CreateTestClients()); - // Set optional video constraint to max 320pixels to decrease CPU usage. - FakeConstraints constraint; - constraint.SetOptionalMaxWidth(320); - SetVideoConstraints(constraint, constraint); - initializing_client()->AddMediaStream(true, true); - initializing_client()->AddMediaStream(false, true); - ASSERT_EQ(2u, initializing_client()->NumberOfLocalMediaStreams()); - LocalP2PTest(); - EXPECT_EQ(2u, receiving_client()->number_of_remote_streams()); -} - -// Test that if applying a true "max bundle" offer, which uses ports of 0, -// "a=bundle-only", omitting "a=fingerprint", "a=setup", "a=ice-ufrag" and -// "a=ice-pwd" for all but the audio "m=" section, negotiation still completes -// successfully and media flows. -// TODO(deadbeef): Update this test to also omit "a=rtcp-mux", once that works. -// TODO(deadbeef): Won't need this test once we start generating actual -// standards-compliant SDP. -TEST_F(P2PTestConductor, LocalP2PTestWithSpecCompliantMaxBundleOffer) { - ASSERT_TRUE(CreateTestClients()); - receiving_client()->MakeSpecCompliantMaxBundleOfferFromReceivedSdp(true); - LocalP2PTest(); -} - -// Test that we can receive the audio output level from a remote audio track. -TEST_F(P2PTestConductor, GetAudioOutputLevelStats) { - ASSERT_TRUE(CreateTestClients()); - LocalP2PTest(); - - StreamCollectionInterface* remote_streams = - initializing_client()->remote_streams(); - ASSERT_GT(remote_streams->count(), 0u); - ASSERT_GT(remote_streams->at(0)->GetAudioTracks().size(), 0u); - MediaStreamTrackInterface* remote_audio_track = - remote_streams->at(0)->GetAudioTracks()[0]; - - // Get the audio output level stats. Note that the level is not available - // until a RTCP packet has been received. - EXPECT_TRUE_WAIT( - initializing_client()->GetAudioOutputLevelStats(remote_audio_track) > 0, - kMaxWaitForStatsMs); -} - -// Test that an audio input level is reported. -TEST_F(P2PTestConductor, GetAudioInputLevelStats) { - ASSERT_TRUE(CreateTestClients()); - LocalP2PTest(); - - // Get the audio input level stats. The level should be available very - // soon after the test starts. - EXPECT_TRUE_WAIT(initializing_client()->GetAudioInputLevelStats() > 0, - kMaxWaitForStatsMs); -} - -// Test that we can get incoming byte counts from both audio and video tracks. -TEST_F(P2PTestConductor, GetBytesReceivedStats) { - ASSERT_TRUE(CreateTestClients()); - LocalP2PTest(); - - StreamCollectionInterface* remote_streams = - initializing_client()->remote_streams(); - ASSERT_GT(remote_streams->count(), 0u); - ASSERT_GT(remote_streams->at(0)->GetAudioTracks().size(), 0u); - MediaStreamTrackInterface* remote_audio_track = - remote_streams->at(0)->GetAudioTracks()[0]; - EXPECT_TRUE_WAIT( - initializing_client()->GetBytesReceivedStats(remote_audio_track) > 0, - kMaxWaitForStatsMs); - - MediaStreamTrackInterface* remote_video_track = - remote_streams->at(0)->GetVideoTracks()[0]; - EXPECT_TRUE_WAIT( - initializing_client()->GetBytesReceivedStats(remote_video_track) > 0, - kMaxWaitForStatsMs); -} - -// Test that we can get outgoing byte counts from both audio and video tracks. -TEST_F(P2PTestConductor, GetBytesSentStats) { - ASSERT_TRUE(CreateTestClients()); - LocalP2PTest(); - - StreamCollectionInterface* local_streams = - initializing_client()->local_streams(); - ASSERT_GT(local_streams->count(), 0u); - ASSERT_GT(local_streams->at(0)->GetAudioTracks().size(), 0u); - MediaStreamTrackInterface* local_audio_track = - local_streams->at(0)->GetAudioTracks()[0]; - EXPECT_TRUE_WAIT( - initializing_client()->GetBytesSentStats(local_audio_track) > 0, - kMaxWaitForStatsMs); - - MediaStreamTrackInterface* local_video_track = - local_streams->at(0)->GetVideoTracks()[0]; - EXPECT_TRUE_WAIT( - initializing_client()->GetBytesSentStats(local_video_track) > 0, - kMaxWaitForStatsMs); -} - -// Test that DTLS 1.0 is used if both sides only support DTLS 1.0. -TEST_F(P2PTestConductor, GetDtls12None) { - PeerConnectionFactory::Options init_options; - init_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_10; - PeerConnectionFactory::Options recv_options; - recv_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_10; - ASSERT_TRUE(CreateTestClients(nullptr, &init_options, nullptr, nullptr, - &recv_options, nullptr)); - rtc::scoped_refptr - init_observer = new rtc::RefCountedObject(); - initializing_client()->pc()->RegisterUMAObserver(init_observer); - LocalP2PTest(); - - EXPECT_TRUE_WAIT( - rtc::SSLStreamAdapter::IsAcceptableCipher( - initializing_client()->GetDtlsCipherStats(), rtc::KT_DEFAULT), - kMaxWaitForStatsMs); - EXPECT_EQ_WAIT(rtc::SrtpCryptoSuiteToName(kDefaultSrtpCryptoSuite), - initializing_client()->GetSrtpCipherStats(), - kMaxWaitForStatsMs); - EXPECT_EQ(1, - init_observer->GetEnumCounter(webrtc::kEnumCounterAudioSrtpCipher, - kDefaultSrtpCryptoSuite)); -} - -// Test that DTLS 1.2 is used if both ends support it. -TEST_F(P2PTestConductor, GetDtls12Both) { - PeerConnectionFactory::Options init_options; - init_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_12; - PeerConnectionFactory::Options recv_options; - recv_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_12; - ASSERT_TRUE(CreateTestClients(nullptr, &init_options, nullptr, nullptr, - &recv_options, nullptr)); - rtc::scoped_refptr - init_observer = new rtc::RefCountedObject(); - initializing_client()->pc()->RegisterUMAObserver(init_observer); - LocalP2PTest(); - - EXPECT_TRUE_WAIT( - rtc::SSLStreamAdapter::IsAcceptableCipher( - initializing_client()->GetDtlsCipherStats(), rtc::KT_DEFAULT), - kMaxWaitForStatsMs); - EXPECT_EQ_WAIT(rtc::SrtpCryptoSuiteToName(kDefaultSrtpCryptoSuite), - initializing_client()->GetSrtpCipherStats(), - kMaxWaitForStatsMs); - EXPECT_EQ(1, - init_observer->GetEnumCounter(webrtc::kEnumCounterAudioSrtpCipher, - kDefaultSrtpCryptoSuite)); -} - -// Test that DTLS 1.0 is used if the initator supports DTLS 1.2 and the -// received supports 1.0. -TEST_F(P2PTestConductor, GetDtls12Init) { - PeerConnectionFactory::Options init_options; - init_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_12; - PeerConnectionFactory::Options recv_options; - recv_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_10; - ASSERT_TRUE(CreateTestClients(nullptr, &init_options, nullptr, nullptr, - &recv_options, nullptr)); - rtc::scoped_refptr - init_observer = new rtc::RefCountedObject(); - initializing_client()->pc()->RegisterUMAObserver(init_observer); - LocalP2PTest(); - - EXPECT_TRUE_WAIT( - rtc::SSLStreamAdapter::IsAcceptableCipher( - initializing_client()->GetDtlsCipherStats(), rtc::KT_DEFAULT), - kMaxWaitForStatsMs); - EXPECT_EQ_WAIT(rtc::SrtpCryptoSuiteToName(kDefaultSrtpCryptoSuite), - initializing_client()->GetSrtpCipherStats(), - kMaxWaitForStatsMs); - EXPECT_EQ(1, - init_observer->GetEnumCounter(webrtc::kEnumCounterAudioSrtpCipher, - kDefaultSrtpCryptoSuite)); -} - -// Test that DTLS 1.0 is used if the initator supports DTLS 1.0 and the -// received supports 1.2. -TEST_F(P2PTestConductor, GetDtls12Recv) { - PeerConnectionFactory::Options init_options; - init_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_10; - PeerConnectionFactory::Options recv_options; - recv_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_12; - ASSERT_TRUE(CreateTestClients(nullptr, &init_options, nullptr, nullptr, - &recv_options, nullptr)); - rtc::scoped_refptr - init_observer = new rtc::RefCountedObject(); - initializing_client()->pc()->RegisterUMAObserver(init_observer); - LocalP2PTest(); - - EXPECT_TRUE_WAIT( - rtc::SSLStreamAdapter::IsAcceptableCipher( - initializing_client()->GetDtlsCipherStats(), rtc::KT_DEFAULT), - kMaxWaitForStatsMs); - EXPECT_EQ_WAIT(rtc::SrtpCryptoSuiteToName(kDefaultSrtpCryptoSuite), - initializing_client()->GetSrtpCipherStats(), - kMaxWaitForStatsMs); - EXPECT_EQ(1, - init_observer->GetEnumCounter(webrtc::kEnumCounterAudioSrtpCipher, - kDefaultSrtpCryptoSuite)); -} - -// Test that a non-GCM cipher is used if both sides only support non-GCM. -TEST_F(P2PTestConductor, GetGcmNone) { - TestGcmNegotiation(false, false, kDefaultSrtpCryptoSuite); -} - -// Test that a GCM cipher is used if both ends support it. -TEST_F(P2PTestConductor, GetGcmBoth) { - TestGcmNegotiation(true, true, kDefaultSrtpCryptoSuiteGcm); -} - -// Test that GCM isn't used if only the initiator supports it. -TEST_F(P2PTestConductor, GetGcmInit) { - TestGcmNegotiation(true, false, kDefaultSrtpCryptoSuite); -} - -// Test that GCM isn't used if only the receiver supports it. -TEST_F(P2PTestConductor, GetGcmRecv) { - TestGcmNegotiation(false, true, kDefaultSrtpCryptoSuite); -} - -// This test sets up a call between two parties with audio, video and an RTP -// data channel. -TEST_F(P2PTestConductor, LocalP2PTestRtpDataChannel) { - FakeConstraints setup_constraints; - setup_constraints.SetAllowRtpDataChannels(); - ASSERT_TRUE(CreateTestClients(&setup_constraints, &setup_constraints)); - initializing_client()->CreateDataChannel(); - LocalP2PTest(); - ASSERT_TRUE(initializing_client()->data_channel() != nullptr); - ASSERT_TRUE(receiving_client()->data_channel() != nullptr); - EXPECT_TRUE_WAIT(initializing_client()->data_observer()->IsOpen(), - kMaxWaitMs); - EXPECT_TRUE_WAIT(receiving_client()->data_observer()->IsOpen(), - kMaxWaitMs); - - std::string data = "hello world"; - - SendRtpData(initializing_client()->data_channel(), data); - EXPECT_EQ_WAIT(data, receiving_client()->data_observer()->last_message(), - kMaxWaitMs); - - SendRtpData(receiving_client()->data_channel(), data); - EXPECT_EQ_WAIT(data, initializing_client()->data_observer()->last_message(), - kMaxWaitMs); - - receiving_client()->data_channel()->Close(); - // Send new offer and answer. - receiving_client()->Negotiate(); - EXPECT_FALSE(initializing_client()->data_observer()->IsOpen()); - EXPECT_FALSE(receiving_client()->data_observer()->IsOpen()); -} - -#ifdef HAVE_SCTP -// This test sets up a call between two parties with audio, video and an SCTP -// data channel. -TEST_F(P2PTestConductor, LocalP2PTestSctpDataChannel) { - ASSERT_TRUE(CreateTestClients()); - initializing_client()->CreateDataChannel(); - LocalP2PTest(); - ASSERT_TRUE(initializing_client()->data_channel() != nullptr); - EXPECT_TRUE_WAIT(receiving_client()->data_channel() != nullptr, kMaxWaitMs); - EXPECT_TRUE_WAIT(initializing_client()->data_observer()->IsOpen(), - kMaxWaitMs); - EXPECT_TRUE_WAIT(receiving_client()->data_observer()->IsOpen(), kMaxWaitMs); - - std::string data = "hello world"; - - initializing_client()->data_channel()->Send(DataBuffer(data)); - EXPECT_EQ_WAIT(data, receiving_client()->data_observer()->last_message(), - kMaxWaitMs); - - receiving_client()->data_channel()->Send(DataBuffer(data)); - EXPECT_EQ_WAIT(data, initializing_client()->data_observer()->last_message(), - kMaxWaitMs); - - receiving_client()->data_channel()->Close(); - EXPECT_TRUE_WAIT(!initializing_client()->data_observer()->IsOpen(), - kMaxWaitMs); - EXPECT_TRUE_WAIT(!receiving_client()->data_observer()->IsOpen(), kMaxWaitMs); -} - -TEST_F(P2PTestConductor, UnorderedSctpDataChannel) { - ASSERT_TRUE(CreateTestClients()); - webrtc::DataChannelInit init; - init.ordered = false; - initializing_client()->CreateDataChannel(&init); - - // Introduce random network delays. - // Otherwise it's not a true "unordered" test. - virtual_socket_server()->set_delay_mean(20); - virtual_socket_server()->set_delay_stddev(5); - virtual_socket_server()->UpdateDelayDistribution(); - - initializing_client()->Negotiate(); - ASSERT_TRUE(initializing_client()->data_channel() != nullptr); - EXPECT_TRUE_WAIT(receiving_client()->data_channel() != nullptr, kMaxWaitMs); - EXPECT_TRUE_WAIT(initializing_client()->data_observer()->IsOpen(), - kMaxWaitMs); - EXPECT_TRUE_WAIT(receiving_client()->data_observer()->IsOpen(), kMaxWaitMs); - - static constexpr int kNumMessages = 100; - // Deliberately chosen to be larger than the MTU so messages get fragmented. - static constexpr size_t kMaxMessageSize = 4096; - // Create and send random messages. - std::vector sent_messages; - for (int i = 0; i < kNumMessages; ++i) { - size_t length = (rand() % kMaxMessageSize) + 1; - std::string message; - ASSERT_TRUE(rtc::CreateRandomString(length, &message)); - initializing_client()->data_channel()->Send(DataBuffer(message)); - receiving_client()->data_channel()->Send(DataBuffer(message)); - sent_messages.push_back(message); - } - - EXPECT_EQ_WAIT( - kNumMessages, - initializing_client()->data_observer()->received_message_count(), - kMaxWaitMs); - EXPECT_EQ_WAIT(kNumMessages, - receiving_client()->data_observer()->received_message_count(), - kMaxWaitMs); - - // Sort and compare to make sure none of the messages were corrupted. - std::vector initializing_client_received_messages = - initializing_client()->data_observer()->messages(); - std::vector receiving_client_received_messages = - receiving_client()->data_observer()->messages(); - std::sort(sent_messages.begin(), sent_messages.end()); - std::sort(initializing_client_received_messages.begin(), - initializing_client_received_messages.end()); - std::sort(receiving_client_received_messages.begin(), - receiving_client_received_messages.end()); - EXPECT_EQ(sent_messages, initializing_client_received_messages); - EXPECT_EQ(sent_messages, receiving_client_received_messages); - - receiving_client()->data_channel()->Close(); - EXPECT_TRUE_WAIT(!initializing_client()->data_observer()->IsOpen(), - kMaxWaitMs); - EXPECT_TRUE_WAIT(!receiving_client()->data_observer()->IsOpen(), kMaxWaitMs); -} -#endif // HAVE_SCTP - -// This test sets up a call between two parties and creates a data channel. -// The test tests that received data is buffered unless an observer has been -// registered. -// Rtp data channels can receive data before the underlying -// transport has detected that a channel is writable and thus data can be -// received before the data channel state changes to open. That is hard to test -// but the same buffering is used in that case. -TEST_F(P2PTestConductor, RegisterDataChannelObserver) { - FakeConstraints setup_constraints; - setup_constraints.SetAllowRtpDataChannels(); - ASSERT_TRUE(CreateTestClients(&setup_constraints, &setup_constraints)); - initializing_client()->CreateDataChannel(); - initializing_client()->Negotiate(); - - ASSERT_TRUE(initializing_client()->data_channel() != nullptr); - ASSERT_TRUE(receiving_client()->data_channel() != nullptr); - EXPECT_TRUE_WAIT(initializing_client()->data_observer()->IsOpen(), - kMaxWaitMs); - EXPECT_EQ_WAIT(DataChannelInterface::kOpen, - receiving_client()->data_channel()->state(), kMaxWaitMs); - - // Unregister the existing observer. - receiving_client()->data_channel()->UnregisterObserver(); - - std::string data = "hello world"; - SendRtpData(initializing_client()->data_channel(), data); - - // Wait a while to allow the sent data to arrive before an observer is - // registered.. - rtc::Thread::Current()->ProcessMessages(100); - - MockDataChannelObserver new_observer(receiving_client()->data_channel()); - EXPECT_EQ_WAIT(data, new_observer.last_message(), kMaxWaitMs); -} - -// This test sets up a call between two parties with audio, video and but only -// the initiating client support data. -TEST_F(P2PTestConductor, LocalP2PTestReceiverDoesntSupportData) { - FakeConstraints setup_constraints_1; - setup_constraints_1.SetAllowRtpDataChannels(); - // Must disable DTLS to make negotiation succeed. - setup_constraints_1.SetMandatory( - MediaConstraintsInterface::kEnableDtlsSrtp, false); - FakeConstraints setup_constraints_2; - setup_constraints_2.SetMandatory( - MediaConstraintsInterface::kEnableDtlsSrtp, false); - ASSERT_TRUE(CreateTestClients(&setup_constraints_1, &setup_constraints_2)); - initializing_client()->CreateDataChannel(); - LocalP2PTest(); - EXPECT_TRUE(initializing_client()->data_channel() != nullptr); - EXPECT_FALSE(receiving_client()->data_channel()); - EXPECT_FALSE(initializing_client()->data_observer()->IsOpen()); -} - -// This test sets up a call between two parties with audio, video. When audio -// and video is setup and flowing and data channel is negotiated. -TEST_F(P2PTestConductor, AddDataChannelAfterRenegotiation) { - FakeConstraints setup_constraints; - setup_constraints.SetAllowRtpDataChannels(); - ASSERT_TRUE(CreateTestClients(&setup_constraints, &setup_constraints)); - LocalP2PTest(); - initializing_client()->CreateDataChannel(); - // Send new offer and answer. - initializing_client()->Negotiate(); - ASSERT_TRUE(initializing_client()->data_channel() != nullptr); - ASSERT_TRUE(receiving_client()->data_channel() != nullptr); - EXPECT_TRUE_WAIT(initializing_client()->data_observer()->IsOpen(), - kMaxWaitMs); - EXPECT_TRUE_WAIT(receiving_client()->data_observer()->IsOpen(), - kMaxWaitMs); -} - -// This test sets up a Jsep call with SCTP DataChannel and verifies the -// negotiation is completed without error. -#ifdef HAVE_SCTP -TEST_F(P2PTestConductor, CreateOfferWithSctpDataChannel) { - FakeConstraints constraints; - constraints.SetMandatory( - MediaConstraintsInterface::kEnableDtlsSrtp, true); - ASSERT_TRUE(CreateTestClients(&constraints, &constraints)); - initializing_client()->CreateDataChannel(); - initializing_client()->Negotiate(false, false); -} -#endif - -// This test sets up a call between two parties with audio, and video. -// During the call, the initializing side restart ice and the test verifies that -// new ice candidates are generated and audio and video still can flow. -TEST_F(P2PTestConductor, IceRestart) { - ASSERT_TRUE(CreateTestClients()); - - // Negotiate and wait for ice completion and make sure audio and video plays. - LocalP2PTest(); - - // Create a SDP string of the first audio candidate for both clients. - const webrtc::IceCandidateCollection* audio_candidates_initiator = - initializing_client()->pc()->local_description()->candidates(0); - const webrtc::IceCandidateCollection* audio_candidates_receiver = - receiving_client()->pc()->local_description()->candidates(0); - ASSERT_GT(audio_candidates_initiator->count(), 0u); - ASSERT_GT(audio_candidates_receiver->count(), 0u); - std::string initiator_candidate; - EXPECT_TRUE( - audio_candidates_initiator->at(0)->ToString(&initiator_candidate)); - std::string receiver_candidate; - EXPECT_TRUE(audio_candidates_receiver->at(0)->ToString(&receiver_candidate)); - - // Restart ice on the initializing client. - receiving_client()->SetExpectIceRestart(true); - initializing_client()->IceRestart(); - - // Negotiate and wait for ice completion again and make sure audio and video - // plays. - LocalP2PTest(); - - // Create a SDP string of the first audio candidate for both clients again. - const webrtc::IceCandidateCollection* audio_candidates_initiator_restart = - initializing_client()->pc()->local_description()->candidates(0); - const webrtc::IceCandidateCollection* audio_candidates_reciever_restart = - receiving_client()->pc()->local_description()->candidates(0); - ASSERT_GT(audio_candidates_initiator_restart->count(), 0u); - ASSERT_GT(audio_candidates_reciever_restart->count(), 0u); - std::string initiator_candidate_restart; - EXPECT_TRUE(audio_candidates_initiator_restart->at(0)->ToString( - &initiator_candidate_restart)); - std::string receiver_candidate_restart; - EXPECT_TRUE(audio_candidates_reciever_restart->at(0)->ToString( - &receiver_candidate_restart)); - - // Verify that the first candidates in the local session descriptions has - // changed. - EXPECT_NE(initiator_candidate, initiator_candidate_restart); - EXPECT_NE(receiver_candidate, receiver_candidate_restart); -} - -TEST_F(P2PTestConductor, IceRenominationDisabled) { - PeerConnectionInterface::RTCConfiguration config; - config.enable_ice_renomination = false; - ASSERT_TRUE(CreateTestClients(config, config)); - LocalP2PTest(); - - initializing_client()->VerifyLocalIceRenomination(); - receiving_client()->VerifyLocalIceRenomination(); - initializing_client()->VerifyRemoteIceRenomination(); - receiving_client()->VerifyRemoteIceRenomination(); -} - -TEST_F(P2PTestConductor, IceRenominationEnabled) { - PeerConnectionInterface::RTCConfiguration config; - config.enable_ice_renomination = true; - ASSERT_TRUE(CreateTestClients(config, config)); - initializing_client()->SetExpectIceRenomination(true); - initializing_client()->SetExpectRemoteIceRenomination(true); - receiving_client()->SetExpectIceRenomination(true); - receiving_client()->SetExpectRemoteIceRenomination(true); - LocalP2PTest(); - - initializing_client()->VerifyLocalIceRenomination(); - receiving_client()->VerifyLocalIceRenomination(); - initializing_client()->VerifyRemoteIceRenomination(); - receiving_client()->VerifyRemoteIceRenomination(); -} - -// This test sets up a call between two parties with audio, and video. -// It then renegotiates setting the video m-line to "port 0", then later -// renegotiates again, enabling video. -TEST_F(P2PTestConductor, LocalP2PTestVideoDisableEnable) { - ASSERT_TRUE(CreateTestClients()); - - // Do initial negotiation. Will result in video and audio sendonly m-lines. - receiving_client()->set_auto_add_stream(false); - initializing_client()->AddMediaStream(true, true); - initializing_client()->Negotiate(); - - // Negotiate again, disabling the video m-line (receiving client will - // set port to 0 due to mandatory "OfferToReceiveVideo: false" constraint). - receiving_client()->SetReceiveVideo(false); - initializing_client()->Negotiate(); - - // Enable video and do negotiation again, making sure video is received - // end-to-end. - receiving_client()->SetReceiveVideo(true); - receiving_client()->AddMediaStream(true, true); - LocalP2PTest(); -} - -// This test sets up a Jsep call between two parties with external -// VideoDecoderFactory. -// TODO(holmer): Disabled due to sometimes crashing on buildbots. -// See issue webrtc/2378. -TEST_F(P2PTestConductor, DISABLED_LocalP2PTestWithVideoDecoderFactory) { - ASSERT_TRUE(CreateTestClients()); - EnableVideoDecoderFactory(); - LocalP2PTest(); -} - -// This tests that if we negotiate after calling CreateSender but before we -// have a track, then set a track later, frames from the newly-set track are -// received end-to-end. -TEST_F(P2PTestConductor, EarlyWarmupTest) { - ASSERT_TRUE(CreateTestClients()); - auto audio_sender = - initializing_client()->pc()->CreateSender("audio", "stream_id"); - auto video_sender = - initializing_client()->pc()->CreateSender("video", "stream_id"); - initializing_client()->Negotiate(); - // Wait for ICE connection to complete, without any tracks. - // Note that the receiving client WILL (in HandleIncomingOffer) create - // tracks, so it's only the initiator here that's doing early warmup. - ASSERT_TRUE_WAIT(SessionActive(), kMaxWaitForActivationMs); - VerifySessionDescriptions(); - EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceConnectionCompleted, - initializing_client()->ice_connection_state(), - kMaxWaitForFramesMs); - EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceConnectionConnected, - receiving_client()->ice_connection_state(), - kMaxWaitForFramesMs); - // Now set the tracks, and expect frames to immediately start flowing. - EXPECT_TRUE( - audio_sender->SetTrack(initializing_client()->CreateLocalAudioTrack(""))); - EXPECT_TRUE( - video_sender->SetTrack(initializing_client()->CreateLocalVideoTrack(""))); - EXPECT_TRUE_WAIT(FramesHaveArrived(kEndAudioFrameCount, kEndVideoFrameCount), - kMaxWaitForFramesMs); -} - -#ifdef HAVE_QUIC -// This test sets up a call between two parties using QUIC instead of DTLS for -// audio and video, and a QUIC data channel. -TEST_F(P2PTestConductor, LocalP2PTestQuicDataChannel) { - PeerConnectionInterface::RTCConfiguration quic_config; - quic_config.enable_quic = true; - ASSERT_TRUE(CreateTestClients(quic_config, quic_config)); - webrtc::DataChannelInit init; - init.ordered = false; - init.reliable = true; - init.id = 1; - initializing_client()->CreateDataChannel(&init); - receiving_client()->CreateDataChannel(&init); - LocalP2PTest(); - ASSERT_NE(nullptr, initializing_client()->data_channel()); - ASSERT_NE(nullptr, receiving_client()->data_channel()); - EXPECT_TRUE_WAIT(initializing_client()->data_observer()->IsOpen(), - kMaxWaitMs); - EXPECT_TRUE_WAIT(receiving_client()->data_observer()->IsOpen(), kMaxWaitMs); - - std::string data = "hello world"; - - initializing_client()->data_channel()->Send(DataBuffer(data)); - EXPECT_EQ_WAIT(data, receiving_client()->data_observer()->last_message(), - kMaxWaitMs); - - receiving_client()->data_channel()->Send(DataBuffer(data)); - EXPECT_EQ_WAIT(data, initializing_client()->data_observer()->last_message(), - kMaxWaitMs); -} - -// Tests that negotiation of QUIC data channels is completed without error. -TEST_F(P2PTestConductor, NegotiateQuicDataChannel) { - PeerConnectionInterface::RTCConfiguration quic_config; - quic_config.enable_quic = true; - ASSERT_TRUE(CreateTestClients(quic_config, quic_config)); - FakeConstraints constraints; - constraints.SetMandatory(MediaConstraintsInterface::kEnableDtlsSrtp, true); - ASSERT_TRUE(CreateTestClients(&constraints, &constraints)); - webrtc::DataChannelInit init; - init.ordered = false; - init.reliable = true; - init.id = 1; - initializing_client()->CreateDataChannel(&init); - initializing_client()->Negotiate(false, false); -} - -// This test sets up a JSEP call using QUIC. The callee only receives video. -TEST_F(P2PTestConductor, LocalP2PTestVideoOnlyWithQuic) { - PeerConnectionInterface::RTCConfiguration quic_config; - quic_config.enable_quic = true; - ASSERT_TRUE(CreateTestClients(quic_config, quic_config)); - receiving_client()->SetReceiveAudioVideo(false, true); - LocalP2PTest(); -} - -// This test sets up a JSEP call using QUIC. The callee only receives audio. -TEST_F(P2PTestConductor, LocalP2PTestAudioOnlyWithQuic) { - PeerConnectionInterface::RTCConfiguration quic_config; - quic_config.enable_quic = true; - ASSERT_TRUE(CreateTestClients(quic_config, quic_config)); - receiving_client()->SetReceiveAudioVideo(true, false); - LocalP2PTest(); -} - -// This test sets up a JSEP call using QUIC. The callee rejects both audio and -// video. -TEST_F(P2PTestConductor, LocalP2PTestNoVideoAudioWithQuic) { - PeerConnectionInterface::RTCConfiguration quic_config; - quic_config.enable_quic = true; - ASSERT_TRUE(CreateTestClients(quic_config, quic_config)); - receiving_client()->SetReceiveAudioVideo(false, false); - LocalP2PTest(); -} - -#endif // HAVE_QUIC - -TEST_F(P2PTestConductor, ForwardVideoOnlyStream) { - ASSERT_TRUE(CreateTestClients()); - // One-way stream - receiving_client()->set_auto_add_stream(false); - // Video only, audio forwarding not expected to work. - initializing_client()->AddMediaStream(false, true); - initializing_client()->Negotiate(); - - ASSERT_TRUE_WAIT(SessionActive(), kMaxWaitForActivationMs); - VerifySessionDescriptions(); - - ASSERT_TRUE(initializing_client()->can_receive_video()); - ASSERT_TRUE(receiving_client()->can_receive_video()); - - EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceConnectionCompleted, - initializing_client()->ice_connection_state(), - kMaxWaitForFramesMs); - EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceConnectionConnected, - receiving_client()->ice_connection_state(), - kMaxWaitForFramesMs); - - ASSERT_TRUE(receiving_client()->remote_streams()->count() == 1); - - // Echo the stream back. - receiving_client()->pc()->AddStream( - receiving_client()->remote_streams()->at(0)); - receiving_client()->Negotiate(); - - EXPECT_TRUE_WAIT( - initializing_client()->VideoFramesReceivedCheck(kEndVideoFrameCount), - kMaxWaitForFramesMs); -} - -// Test that we achieve the expected end-to-end connection time, using a -// fake clock and simulated latency on the media and signaling paths. -// We use a TURN<->TURN connection because this is usually the quickest to -// set up initially, especially when we're confident the connection will work -// and can start sending media before we get a STUN response. -// -// With various optimizations enabled, here are the network delays we expect to -// be on the critical path: -// 1. 2 signaling trips: Signaling offer and offerer's TURN candidate, then -// signaling answer (with DTLS fingerprint). -// 2. 9 media hops: Rest of the DTLS handshake. 3 hops in each direction when -// using TURN<->TURN pair, and DTLS exchange is 4 packets, -// the first of which should have arrived before the answer. -TEST_F(P2PTestConductor, EndToEndConnectionTimeWithTurnTurnPair) { - rtc::ScopedFakeClock fake_clock; - // Some things use a time of "0" as a special value, so we need to start out - // the fake clock at a nonzero time. - // TODO(deadbeef): Fix this. - fake_clock.AdvanceTime(rtc::TimeDelta::FromSeconds(1)); - - static constexpr int media_hop_delay_ms = 50; - static constexpr int signaling_trip_delay_ms = 500; - // For explanation of these values, see comment above. - static constexpr int required_media_hops = 9; - static constexpr int required_signaling_trips = 2; - // For internal delays (such as posting an event asychronously). - static constexpr int allowed_internal_delay_ms = 20; - static constexpr int total_connection_time_ms = - media_hop_delay_ms * required_media_hops + - signaling_trip_delay_ms * required_signaling_trips + - allowed_internal_delay_ms; - - static const rtc::SocketAddress turn_server_1_internal_address{"88.88.88.0", - 3478}; - static const rtc::SocketAddress turn_server_1_external_address{"88.88.88.1", - 0}; - static const rtc::SocketAddress turn_server_2_internal_address{"99.99.99.0", - 3478}; - static const rtc::SocketAddress turn_server_2_external_address{"99.99.99.1", - 0}; - cricket::TestTurnServer turn_server_1(network_thread(), - turn_server_1_internal_address, - turn_server_1_external_address); - cricket::TestTurnServer turn_server_2(network_thread(), - turn_server_2_internal_address, - turn_server_2_external_address); - // Bypass permission check on received packets so media can be sent before - // the candidate is signaled. - turn_server_1.set_enable_permission_checks(false); - turn_server_2.set_enable_permission_checks(false); - - PeerConnectionInterface::RTCConfiguration client_1_config; - webrtc::PeerConnectionInterface::IceServer ice_server_1; - ice_server_1.urls.push_back("turn:88.88.88.0:3478"); - ice_server_1.username = "test"; - ice_server_1.password = "test"; - client_1_config.servers.push_back(ice_server_1); - client_1_config.type = webrtc::PeerConnectionInterface::kRelay; - client_1_config.presume_writable_when_fully_relayed = true; - - PeerConnectionInterface::RTCConfiguration client_2_config; - webrtc::PeerConnectionInterface::IceServer ice_server_2; - ice_server_2.urls.push_back("turn:99.99.99.0:3478"); - ice_server_2.username = "test"; - ice_server_2.password = "test"; - client_2_config.servers.push_back(ice_server_2); - client_2_config.type = webrtc::PeerConnectionInterface::kRelay; - client_2_config.presume_writable_when_fully_relayed = true; - - ASSERT_TRUE(CreateTestClients(client_1_config, client_2_config)); - // Set up the simulated delays. - SetSignalingDelayMs(signaling_trip_delay_ms); - virtual_socket_server()->set_delay_mean(media_hop_delay_ms); - virtual_socket_server()->UpdateDelayDistribution(); - - initializing_client()->SetOfferToReceiveAudioVideo(true, true); - initializing_client()->Negotiate(); - // TODO(deadbeef): kIceConnectionConnected currently means both ICE and DTLS - // are connected. This is an important distinction. Once we have separate ICE - // and DTLS state, this check needs to use the DTLS state. - EXPECT_TRUE_SIMULATED_WAIT( - (receiving_client()->ice_connection_state() == - webrtc::PeerConnectionInterface::kIceConnectionConnected || - receiving_client()->ice_connection_state() == - webrtc::PeerConnectionInterface::kIceConnectionCompleted) && - (initializing_client()->ice_connection_state() == - webrtc::PeerConnectionInterface::kIceConnectionConnected || - initializing_client()->ice_connection_state() == - webrtc::PeerConnectionInterface::kIceConnectionCompleted), - total_connection_time_ms, fake_clock); - // Need to free the clients here since they're using things we created on - // the stack. - delete set_initializing_client(nullptr); - delete set_receiving_client(nullptr); -} - -class IceServerParsingTest : public testing::Test { - public: - // Convenience for parsing a single URL. - bool ParseUrl(const std::string& url) { - return ParseUrl(url, std::string(), std::string()); - } - - bool ParseTurnUrl(const std::string& url) { - return ParseUrl(url, "username", "password"); - } - - bool ParseUrl(const std::string& url, - const std::string& username, - const std::string& password) { - return ParseUrl( - url, username, password, - PeerConnectionInterface::TlsCertPolicy::kTlsCertPolicySecure); - } - - bool ParseUrl(const std::string& url, - const std::string& username, - const std::string& password, - PeerConnectionInterface::TlsCertPolicy tls_certificate_policy) { - PeerConnectionInterface::IceServers servers; - PeerConnectionInterface::IceServer server; - server.urls.push_back(url); - server.username = username; - server.password = password; - server.tls_cert_policy = tls_certificate_policy; - servers.push_back(server); - return webrtc::ParseIceServers(servers, &stun_servers_, &turn_servers_) == - webrtc::RTCErrorType::NONE; - } - - protected: - cricket::ServerAddresses stun_servers_; - std::vector turn_servers_; -}; - -// Make sure all STUN/TURN prefixes are parsed correctly. -TEST_F(IceServerParsingTest, ParseStunPrefixes) { - EXPECT_TRUE(ParseUrl("stun:hostname")); - EXPECT_EQ(1U, stun_servers_.size()); - EXPECT_EQ(0U, turn_servers_.size()); - stun_servers_.clear(); - - EXPECT_TRUE(ParseUrl("stuns:hostname")); - EXPECT_EQ(1U, stun_servers_.size()); - EXPECT_EQ(0U, turn_servers_.size()); - stun_servers_.clear(); - - EXPECT_TRUE(ParseTurnUrl("turn:hostname")); - EXPECT_EQ(0U, stun_servers_.size()); - EXPECT_EQ(1U, turn_servers_.size()); - EXPECT_EQ(cricket::PROTO_UDP, turn_servers_[0].ports[0].proto); - turn_servers_.clear(); - - EXPECT_TRUE(ParseTurnUrl("turns:hostname")); - EXPECT_EQ(0U, stun_servers_.size()); - EXPECT_EQ(1U, turn_servers_.size()); - EXPECT_EQ(cricket::PROTO_TLS, turn_servers_[0].ports[0].proto); - EXPECT_TRUE(turn_servers_[0].tls_cert_policy == - cricket::TlsCertPolicy::TLS_CERT_POLICY_SECURE); - turn_servers_.clear(); - - EXPECT_TRUE(ParseUrl( - "turns:hostname", "username", "password", - PeerConnectionInterface::TlsCertPolicy::kTlsCertPolicyInsecureNoCheck)); - EXPECT_EQ(0U, stun_servers_.size()); - EXPECT_EQ(1U, turn_servers_.size()); - EXPECT_TRUE(turn_servers_[0].tls_cert_policy == - cricket::TlsCertPolicy::TLS_CERT_POLICY_INSECURE_NO_CHECK); - EXPECT_EQ(cricket::PROTO_TLS, turn_servers_[0].ports[0].proto); - turn_servers_.clear(); - - // invalid prefixes - EXPECT_FALSE(ParseUrl("stunn:hostname")); - EXPECT_FALSE(ParseUrl(":hostname")); - EXPECT_FALSE(ParseUrl(":")); - EXPECT_FALSE(ParseUrl("")); -} - -TEST_F(IceServerParsingTest, VerifyDefaults) { - // TURNS defaults - EXPECT_TRUE(ParseTurnUrl("turns:hostname")); - EXPECT_EQ(1U, turn_servers_.size()); - EXPECT_EQ(5349, turn_servers_[0].ports[0].address.port()); - EXPECT_EQ(cricket::PROTO_TLS, turn_servers_[0].ports[0].proto); - turn_servers_.clear(); - - // TURN defaults - EXPECT_TRUE(ParseTurnUrl("turn:hostname")); - EXPECT_EQ(1U, turn_servers_.size()); - EXPECT_EQ(3478, turn_servers_[0].ports[0].address.port()); - EXPECT_EQ(cricket::PROTO_UDP, turn_servers_[0].ports[0].proto); - turn_servers_.clear(); - - // STUN defaults - EXPECT_TRUE(ParseUrl("stun:hostname")); - EXPECT_EQ(1U, stun_servers_.size()); - EXPECT_EQ(3478, stun_servers_.begin()->port()); - stun_servers_.clear(); -} - -// Check that the 6 combinations of IPv4/IPv6/hostname and with/without port -// can be parsed correctly. -TEST_F(IceServerParsingTest, ParseHostnameAndPort) { - EXPECT_TRUE(ParseUrl("stun:1.2.3.4:1234")); - EXPECT_EQ(1U, stun_servers_.size()); - EXPECT_EQ("1.2.3.4", stun_servers_.begin()->hostname()); - EXPECT_EQ(1234, stun_servers_.begin()->port()); - stun_servers_.clear(); - - EXPECT_TRUE(ParseUrl("stun:[1:2:3:4:5:6:7:8]:4321")); - EXPECT_EQ(1U, stun_servers_.size()); - EXPECT_EQ("1:2:3:4:5:6:7:8", stun_servers_.begin()->hostname()); - EXPECT_EQ(4321, stun_servers_.begin()->port()); - stun_servers_.clear(); - - EXPECT_TRUE(ParseUrl("stun:hostname:9999")); - EXPECT_EQ(1U, stun_servers_.size()); - EXPECT_EQ("hostname", stun_servers_.begin()->hostname()); - EXPECT_EQ(9999, stun_servers_.begin()->port()); - stun_servers_.clear(); - - EXPECT_TRUE(ParseUrl("stun:1.2.3.4")); - EXPECT_EQ(1U, stun_servers_.size()); - EXPECT_EQ("1.2.3.4", stun_servers_.begin()->hostname()); - EXPECT_EQ(3478, stun_servers_.begin()->port()); - stun_servers_.clear(); - - EXPECT_TRUE(ParseUrl("stun:[1:2:3:4:5:6:7:8]")); - EXPECT_EQ(1U, stun_servers_.size()); - EXPECT_EQ("1:2:3:4:5:6:7:8", stun_servers_.begin()->hostname()); - EXPECT_EQ(3478, stun_servers_.begin()->port()); - stun_servers_.clear(); - - EXPECT_TRUE(ParseUrl("stun:hostname")); - EXPECT_EQ(1U, stun_servers_.size()); - EXPECT_EQ("hostname", stun_servers_.begin()->hostname()); - EXPECT_EQ(3478, stun_servers_.begin()->port()); - stun_servers_.clear(); - - // Try some invalid hostname:port strings. - EXPECT_FALSE(ParseUrl("stun:hostname:99a99")); - EXPECT_FALSE(ParseUrl("stun:hostname:-1")); - EXPECT_FALSE(ParseUrl("stun:hostname:port:more")); - EXPECT_FALSE(ParseUrl("stun:hostname:port more")); - EXPECT_FALSE(ParseUrl("stun:hostname:")); - EXPECT_FALSE(ParseUrl("stun:[1:2:3:4:5:6:7:8]junk:1000")); - EXPECT_FALSE(ParseUrl("stun::5555")); - EXPECT_FALSE(ParseUrl("stun:")); -} - -// Test parsing the "?transport=xxx" part of the URL. -TEST_F(IceServerParsingTest, ParseTransport) { - EXPECT_TRUE(ParseTurnUrl("turn:hostname:1234?transport=tcp")); - EXPECT_EQ(1U, turn_servers_.size()); - EXPECT_EQ(cricket::PROTO_TCP, turn_servers_[0].ports[0].proto); - turn_servers_.clear(); - - EXPECT_TRUE(ParseTurnUrl("turn:hostname?transport=udp")); - EXPECT_EQ(1U, turn_servers_.size()); - EXPECT_EQ(cricket::PROTO_UDP, turn_servers_[0].ports[0].proto); - turn_servers_.clear(); - - EXPECT_FALSE(ParseTurnUrl("turn:hostname?transport=invalid")); - EXPECT_FALSE(ParseTurnUrl("turn:hostname?transport=")); - EXPECT_FALSE(ParseTurnUrl("turn:hostname?=")); - EXPECT_FALSE(ParseTurnUrl("turn:hostname?")); - EXPECT_FALSE(ParseTurnUrl("?")); -} - -// Test parsing ICE username contained in URL. -TEST_F(IceServerParsingTest, ParseUsername) { - EXPECT_TRUE(ParseTurnUrl("turn:user@hostname")); - EXPECT_EQ(1U, turn_servers_.size()); - EXPECT_EQ("user", turn_servers_[0].credentials.username); - turn_servers_.clear(); - - EXPECT_FALSE(ParseTurnUrl("turn:@hostname")); - EXPECT_FALSE(ParseTurnUrl("turn:username@")); - EXPECT_FALSE(ParseTurnUrl("turn:@")); - EXPECT_FALSE(ParseTurnUrl("turn:user@name@hostname")); -} - -// Test that username and password from IceServer is copied into the resulting -// RelayServerConfig. -TEST_F(IceServerParsingTest, CopyUsernameAndPasswordFromIceServer) { - EXPECT_TRUE(ParseUrl("turn:hostname", "username", "password")); - EXPECT_EQ(1U, turn_servers_.size()); - EXPECT_EQ("username", turn_servers_[0].credentials.username); - EXPECT_EQ("password", turn_servers_[0].credentials.password); -} - -// Ensure that if a server has multiple URLs, each one is parsed. -TEST_F(IceServerParsingTest, ParseMultipleUrls) { - PeerConnectionInterface::IceServers servers; - PeerConnectionInterface::IceServer server; - server.urls.push_back("stun:hostname"); - server.urls.push_back("turn:hostname"); - server.username = "foo"; - server.password = "bar"; - servers.push_back(server); - EXPECT_EQ(webrtc::RTCErrorType::NONE, - webrtc::ParseIceServers(servers, &stun_servers_, &turn_servers_)); - EXPECT_EQ(1U, stun_servers_.size()); - EXPECT_EQ(1U, turn_servers_.size()); -} - -// Ensure that TURN servers are given unique priorities, -// so that their resulting candidates have unique priorities. -TEST_F(IceServerParsingTest, TurnServerPrioritiesUnique) { - PeerConnectionInterface::IceServers servers; - PeerConnectionInterface::IceServer server; - server.urls.push_back("turn:hostname"); - server.urls.push_back("turn:hostname2"); - server.username = "foo"; - server.password = "bar"; - servers.push_back(server); - EXPECT_EQ(webrtc::RTCErrorType::NONE, - webrtc::ParseIceServers(servers, &stun_servers_, &turn_servers_)); - EXPECT_EQ(2U, turn_servers_.size()); - EXPECT_NE(turn_servers_[0].priority, turn_servers_[1].priority); -} - -#endif // if !defined(THREAD_SANITIZER) - -} // namespace diff --git a/webrtc/pc/peerconnectioninterface_unittest.cc b/webrtc/pc/peerconnectioninterface_unittest.cc index ccf1a5e121..05d53ad1a1 100644 --- a/webrtc/pc/peerconnectioninterface_unittest.cc +++ b/webrtc/pc/peerconnectioninterface_unittest.cc @@ -732,6 +732,8 @@ class PeerConnectionInterfaceTest : public testing::Test { new cricket::FakePortAllocator(rtc::Thread::Current(), nullptr)); port_allocator_ = port_allocator.get(); + // Create certificate generator unless DTLS constraint is explicitly set to + // false. std::unique_ptr cert_generator; bool dtls; if (FindConstraint(constraints, @@ -850,7 +852,7 @@ class PeerConnectionInterfaceTest : public testing::Test { pc_->CreateAnswer(observer, constraints); } EXPECT_EQ_WAIT(true, observer->called(), kTimeout); - desc->reset(observer->release_desc()); + *desc = observer->MoveDescription(); return observer->result(); } @@ -1649,6 +1651,18 @@ TEST_F(PeerConnectionInterfaceTest, RemoveTrackAfterAddStream) { EXPECT_TRUE(video_desc == nullptr); } +// Verify that CreateDtmfSender only succeeds if called with a valid local +// track. Other aspects of DtmfSenders are tested in +// peerconnection_integrationtest.cc. +TEST_F(PeerConnectionInterfaceTest, CreateDtmfSenderWithInvalidParams) { + CreatePeerConnection(); + AddAudioVideoStream(kStreamLabel1, "audio_label", "video_label"); + EXPECT_EQ(nullptr, pc_->CreateDtmfSender(nullptr)); + rtc::scoped_refptr non_localtrack( + pc_factory_->CreateAudioTrack("dummy_track", nullptr)); + EXPECT_EQ(nullptr, pc_->CreateDtmfSender(non_localtrack)); +} + // Test creating a sender with a stream ID, and ensure the ID is populated // in the offer. TEST_F(PeerConnectionInterfaceTest, CreateSenderWithStream) { @@ -3150,6 +3164,128 @@ TEST_F(PeerConnectionInterfaceTest, pc_->StopRtcEventLog(); } +// Test that ICE renomination isn't offered if it's not enabled in the PC's +// RTCConfiguration. +TEST_F(PeerConnectionInterfaceTest, IceRenominationNotOffered) { + PeerConnectionInterface::RTCConfiguration config; + config.enable_ice_renomination = false; + CreatePeerConnection(config, nullptr); + AddVoiceStream("foo"); + + std::unique_ptr offer; + ASSERT_TRUE(DoCreateOffer(&offer, nullptr)); + cricket::SessionDescription* desc = offer->description(); + EXPECT_EQ(1u, desc->transport_infos().size()); + EXPECT_FALSE( + desc->transport_infos()[0].description.GetIceParameters().renomination); +} + +// Test that the ICE renomination option is present in generated offers/answers +// if it's enabled in the PC's RTCConfiguration. +TEST_F(PeerConnectionInterfaceTest, IceRenominationOptionInOfferAndAnswer) { + PeerConnectionInterface::RTCConfiguration config; + config.enable_ice_renomination = true; + CreatePeerConnection(config, nullptr); + AddVoiceStream("foo"); + + std::unique_ptr offer; + ASSERT_TRUE(DoCreateOffer(&offer, nullptr)); + cricket::SessionDescription* desc = offer->description(); + EXPECT_EQ(1u, desc->transport_infos().size()); + EXPECT_TRUE( + desc->transport_infos()[0].description.GetIceParameters().renomination); + + // Set the offer as a remote description, then create an answer and ensure it + // has the renomination flag too. + EXPECT_TRUE(DoSetRemoteDescription(offer.release())); + std::unique_ptr answer; + ASSERT_TRUE(DoCreateAnswer(&answer, nullptr)); + desc = answer->description(); + EXPECT_EQ(1u, desc->transport_infos().size()); + EXPECT_TRUE( + desc->transport_infos()[0].description.GetIceParameters().renomination); +} + +// Test that if CreateOffer is called with the deprecated "offer to receive +// audio/video" constraints, they're processed and result in an offer with +// audio/video sections just as if RTCOfferAnswerOptions had been used. +TEST_F(PeerConnectionInterfaceTest, CreateOfferWithOfferToReceiveConstraints) { + CreatePeerConnection(); + + FakeConstraints constraints; + constraints.SetMandatoryReceiveAudio(true); + constraints.SetMandatoryReceiveVideo(true); + std::unique_ptr offer; + ASSERT_TRUE(DoCreateOffer(&offer, &constraints)); + + cricket::SessionDescription* desc = offer->description(); + const cricket::ContentInfo* audio = cricket::GetFirstAudioContent(desc); + const cricket::ContentInfo* video = cricket::GetFirstVideoContent(desc); + ASSERT_NE(nullptr, audio); + ASSERT_NE(nullptr, video); + EXPECT_FALSE(audio->rejected); + EXPECT_FALSE(video->rejected); +} + +// Test that if CreateAnswer is called with the deprecated "offer to receive +// audio/video" constraints, they're processed and can be used to reject an +// offered m= section just as can be done with RTCOfferAnswerOptions; +TEST_F(PeerConnectionInterfaceTest, CreateAnswerWithOfferToReceiveConstraints) { + CreatePeerConnection(); + + // First, create an offer with audio/video and apply it as a remote + // description. + FakeConstraints constraints; + constraints.SetMandatoryReceiveAudio(true); + constraints.SetMandatoryReceiveVideo(true); + std::unique_ptr offer; + ASSERT_TRUE(DoCreateOffer(&offer, &constraints)); + EXPECT_TRUE(DoSetRemoteDescription(offer.release())); + + // Now create answer that rejects audio/video. + constraints.SetMandatoryReceiveAudio(false); + constraints.SetMandatoryReceiveVideo(false); + std::unique_ptr answer; + ASSERT_TRUE(DoCreateAnswer(&answer, &constraints)); + + cricket::SessionDescription* desc = answer->description(); + const cricket::ContentInfo* audio = cricket::GetFirstAudioContent(desc); + const cricket::ContentInfo* video = cricket::GetFirstVideoContent(desc); + ASSERT_NE(nullptr, audio); + ASSERT_NE(nullptr, video); + EXPECT_TRUE(audio->rejected); + EXPECT_TRUE(video->rejected); +} + +#ifdef HAVE_SCTP +#define MAYBE_DataChannelOnlyOfferWithMaxBundlePolicy \ + DataChannelOnlyOfferWithMaxBundlePolicy +#else +#define MAYBE_DataChannelOnlyOfferWithMaxBundlePolicy \ + DISABLED_DataChannelOnlyOfferWithMaxBundlePolicy +#endif + +// Test that negotiation can succeed with a data channel only, and with the max +// bundle policy. Previously there was a bug that prevented this. +TEST_F(PeerConnectionInterfaceTest, + MAYBE_DataChannelOnlyOfferWithMaxBundlePolicy) { + PeerConnectionInterface::RTCConfiguration config; + config.bundle_policy = PeerConnectionInterface::kBundlePolicyMaxBundle; + CreatePeerConnection(config, nullptr); + + // First, create an offer with only a data channel and apply it as a remote + // description. + pc_->CreateDataChannel("test", nullptr); + std::unique_ptr offer; + ASSERT_TRUE(DoCreateOffer(&offer, nullptr)); + EXPECT_TRUE(DoSetRemoteDescription(offer.release())); + + // Create and set answer as well. + std::unique_ptr answer; + ASSERT_TRUE(DoCreateAnswer(&answer, nullptr)); + EXPECT_TRUE(DoSetLocalDescription(answer.release())); +} + class PeerConnectionMediaConfigTest : public testing::Test { protected: void SetUp() override { diff --git a/webrtc/pc/test/mockpeerconnectionobservers.h b/webrtc/pc/test/mockpeerconnectionobservers.h index bde59a8d3f..94996d004f 100644 --- a/webrtc/pc/test/mockpeerconnectionobservers.h +++ b/webrtc/pc/test/mockpeerconnectionobservers.h @@ -39,8 +39,8 @@ class MockCreateSessionDescriptionObserver } bool called() const { return called_; } bool result() const { return result_; } - SessionDescriptionInterface* release_desc() { - return desc_.release(); + std::unique_ptr MoveDescription() { + return std::move(desc_); } private: