blob: a7f7aad0f54b31caed3ea7fd45f63f47c94a9ee0 [file] [log] [blame]
/*
* 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 <stdio.h>
#include <algorithm>
#include <functional>
#include <list>
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
#include "api/mediastreaminterface.h"
#include "api/peerconnectioninterface.h"
#include "api/peerconnectionproxy.h"
#include "api/rtpreceiverinterface.h"
#include "api/test/loopback_media_transport.h"
#include "api/umametrics.h"
#include "api/video_codecs/builtin_video_decoder_factory.h"
#include "api/video_codecs/builtin_video_encoder_factory.h"
#include "api/video_codecs/sdp_video_format.h"
#include "call/call.h"
#include "logging/rtc_event_log/fake_rtc_event_log_factory.h"
#include "logging/rtc_event_log/rtc_event_log_factory_interface.h"
#include "media/engine/fakewebrtcvideoengine.h"
#include "media/engine/webrtcmediaengine.h"
#include "modules/audio_processing/include/audio_processing.h"
#include "p2p/base/mockasyncresolver.h"
#include "p2p/base/p2pconstants.h"
#include "p2p/base/portinterface.h"
#include "p2p/base/teststunserver.h"
#include "p2p/base/testturncustomizer.h"
#include "p2p/base/testturnserver.h"
#include "p2p/client/basicportallocator.h"
#include "pc/dtmfsender.h"
#include "pc/localaudiosource.h"
#include "pc/mediasession.h"
#include "pc/peerconnection.h"
#include "pc/peerconnectionfactory.h"
#include "pc/rtpmediautils.h"
#include "pc/sessiondescription.h"
#include "pc/test/fakeaudiocapturemodule.h"
#include "pc/test/fakeperiodicvideotracksource.h"
#include "pc/test/fakertccertificategenerator.h"
#include "pc/test/fakevideotrackrenderer.h"
#include "pc/test/mockpeerconnectionobservers.h"
#include "rtc_base/fakenetwork.h"
#include "rtc_base/firewallsocketserver.h"
#include "rtc_base/gunit.h"
#include "rtc_base/numerics/safe_conversions.h"
#include "rtc_base/testcertificateverifier.h"
#include "rtc_base/timeutils.h"
#include "rtc_base/virtualsocketserver.h"
#include "system_wrappers/include/metrics.h"
#include "test/gmock.h"
using cricket::ContentInfo;
using cricket::FakeWebRtcVideoDecoder;
using cricket::FakeWebRtcVideoDecoderFactory;
using cricket::FakeWebRtcVideoEncoder;
using cricket::FakeWebRtcVideoEncoderFactory;
using cricket::MediaContentDescription;
using cricket::StreamParams;
using rtc::SocketAddress;
using ::testing::Combine;
using ::testing::ElementsAre;
using ::testing::Return;
using ::testing::SetArgPointee;
using ::testing::Values;
using ::testing::_;
using webrtc::DataBuffer;
using webrtc::DataChannelInterface;
using webrtc::DtmfSender;
using webrtc::DtmfSenderInterface;
using webrtc::DtmfSenderObserverInterface;
using webrtc::FakeVideoTrackRenderer;
using webrtc::MediaStreamInterface;
using webrtc::MediaStreamTrackInterface;
using webrtc::MockCreateSessionDescriptionObserver;
using webrtc::MockDataChannelObserver;
using webrtc::MockSetSessionDescriptionObserver;
using webrtc::MockStatsObserver;
using webrtc::ObserverInterface;
using webrtc::PeerConnection;
using webrtc::PeerConnectionInterface;
using RTCConfiguration = PeerConnectionInterface::RTCConfiguration;
using webrtc::PeerConnectionFactory;
using webrtc::PeerConnectionProxy;
using webrtc::RTCErrorType;
using webrtc::RTCTransportStats;
using webrtc::RtpSenderInterface;
using webrtc::RtpReceiverInterface;
using webrtc::RtpSenderInterface;
using webrtc::RtpTransceiverDirection;
using webrtc::RtpTransceiverInit;
using webrtc::RtpTransceiverInterface;
using webrtc::SdpSemantics;
using webrtc::SdpType;
using webrtc::SessionDescriptionInterface;
using webrtc::StreamCollectionInterface;
using webrtc::VideoTrackInterface;
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 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_80;
static const int kDefaultSrtpCryptoSuiteGcm = rtc::SRTP_AEAD_AES_256_GCM;
static const SocketAddress kDefaultLocalAddress("192.168.1.1", 0);
// Helper function for constructing offer/answer options to initiate an ICE
// restart.
PeerConnectionInterface::RTCOfferAnswerOptions IceRestartOfferAnswerOptions() {
PeerConnectionInterface::RTCOfferAnswerOptions options;
options.ice_restart = true;
return options;
}
// Remove all stream information (SSRCs, track IDs, etc.) and "msid-semantic"
// attribute from received SDP, simulating a legacy endpoint.
void RemoveSsrcsAndMsids(cricket::SessionDescription* desc) {
for (ContentInfo& content : desc->contents()) {
content.media_description()->mutable_streams().clear();
}
desc->set_msid_supported(false);
}
// Removes all stream information besides the stream ids, simulating an
// endpoint that only signals a=msid lines to convey stream_ids.
void RemoveSsrcsAndKeepMsids(cricket::SessionDescription* desc) {
for (ContentInfo& content : desc->contents()) {
std::string track_id;
std::vector<std::string> stream_ids;
if (!content.media_description()->streams().empty()) {
const StreamParams& first_stream =
content.media_description()->streams()[0];
track_id = first_stream.id;
stream_ids = first_stream.stream_ids();
}
content.media_description()->mutable_streams().clear();
StreamParams new_stream;
new_stream.id = track_id;
new_stream.set_stream_ids(stream_ids);
content.media_description()->AddStream(new_stream);
}
}
int FindFirstMediaStatsIndexByKind(
const std::string& kind,
const std::vector<const webrtc::RTCMediaStreamTrackStats*>&
media_stats_vec) {
for (size_t i = 0; i < media_stats_vec.size(); i++) {
if (media_stats_vec[i]->kind.ValueToString() == kind) {
return i;
}
}
return -1;
}
class SignalingMessageReceiver {
public:
virtual void ReceiveSdpMessage(SdpType 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_;
};
// Used by PeerConnectionWrapper::OnIceCandidate to allow a test to modify an
// ICE candidate before it is signaled.
class IceCandidateReplacerInterface {
public:
virtual ~IceCandidateReplacerInterface() = default;
// Return nullptr to drop the candidate (it won't be signaled to the other
// side).
virtual std::unique_ptr<webrtc::IceCandidateInterface> ReplaceCandidate(
const webrtc::IceCandidateInterface*) = 0;
};
// 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.
// TODO(steveanton): See how this could become a subclass of
// PeerConnectionWrapper defined in peerconnectionwrapper.h.
class PeerConnectionWrapper : public webrtc::PeerConnectionObserver,
public SignalingMessageReceiver {
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<rtc::RTCCertificateGeneratorInterface> cert_generator,
rtc::Thread* network_thread,
rtc::Thread* worker_thread) {
PeerConnectionWrapper* client(new PeerConnectionWrapper(debug_name));
webrtc::PeerConnectionDependencies dependencies(nullptr);
dependencies.cert_generator = std::move(cert_generator);
if (!client->Init(nullptr, nullptr, std::move(dependencies), network_thread,
worker_thread, nullptr,
/*media_transport_factory=*/nullptr)) {
delete client;
return nullptr;
}
return client;
}
webrtc::PeerConnectionFactoryInterface* pc_factory() const {
return peer_connection_factory_.get();
}
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<void(cricket::SessionDescription*)> munger) {
received_sdp_munger_ = std::move(munger);
}
// Similar to the above, but this is run on SDP immediately after it's
// generated.
void SetGeneratedSdpMunger(
std::function<void(cricket::SessionDescription*)> munger) {
generated_sdp_munger_ = std::move(munger);
}
// Set a callback to be invoked when a remote offer is received via the fake
// signaling channel. This provides an opportunity to change the
// PeerConnection state before an answer is created and sent to the caller.
void SetRemoteOfferHandler(std::function<void()> handler) {
remote_offer_handler_ = std::move(handler);
}
void SetLocalIceCandidateReplacer(
std::unique_ptr<IceCandidateReplacerInterface> replacer) {
local_ice_candidate_replacer_ = std::move(replacer);
}
// Every ICE connection state in order that has been seen by the observer.
std::vector<PeerConnectionInterface::IceConnectionState>
ice_connection_state_history() const {
return ice_connection_state_history_;
}
void clear_ice_connection_state_history() {
ice_connection_state_history_.clear();
}
// Every PeerConnection state in order that has been seen by the observer.
std::vector<PeerConnectionInterface::PeerConnectionState>
peer_connection_state_history() const {
return peer_connection_state_history_;
}
// Every ICE gathering state in order that has been seen by the observer.
std::vector<PeerConnectionInterface::IceGatheringState>
ice_gathering_state_history() const {
return ice_gathering_state_history_;
}
void AddAudioVideoTracks() {
AddAudioTrack();
AddVideoTrack();
}
rtc::scoped_refptr<RtpSenderInterface> AddAudioTrack() {
return AddTrack(CreateLocalAudioTrack());
}
rtc::scoped_refptr<RtpSenderInterface> AddVideoTrack() {
return AddTrack(CreateLocalVideoTrack());
}
rtc::scoped_refptr<webrtc::AudioTrackInterface> CreateLocalAudioTrack() {
cricket::AudioOptions options;
// Disable highpass filter so that we can get all the test audio frames.
options.highpass_filter = false;
rtc::scoped_refptr<webrtc::AudioSourceInterface> source =
peer_connection_factory_->CreateAudioSource(options);
// TODO(perkj): Test audio source when it is implemented. Currently audio
// always use the default input.
return peer_connection_factory_->CreateAudioTrack(rtc::CreateRandomUuid(),
source);
}
rtc::scoped_refptr<webrtc::VideoTrackInterface> CreateLocalVideoTrack() {
webrtc::FakePeriodicVideoSource::Config config;
config.timestamp_offset_ms = rtc::TimeMillis();
return CreateLocalVideoTrackInternal(config);
}
rtc::scoped_refptr<webrtc::VideoTrackInterface>
CreateLocalVideoTrackWithConfig(
webrtc::FakePeriodicVideoSource::Config config) {
return CreateLocalVideoTrackInternal(config);
}
rtc::scoped_refptr<webrtc::VideoTrackInterface>
CreateLocalVideoTrackWithRotation(webrtc::VideoRotation rotation) {
webrtc::FakePeriodicVideoSource::Config config;
config.rotation = rotation;
config.timestamp_offset_ms = rtc::TimeMillis();
return CreateLocalVideoTrackInternal(config);
}
rtc::scoped_refptr<RtpSenderInterface> AddTrack(
rtc::scoped_refptr<MediaStreamTrackInterface> track,
const std::vector<std::string>& stream_ids = {}) {
auto result = pc()->AddTrack(track, stream_ids);
EXPECT_EQ(RTCErrorType::NONE, result.error().type());
return result.MoveValue();
}
std::vector<rtc::scoped_refptr<RtpReceiverInterface>> GetReceiversOfType(
cricket::MediaType media_type) {
std::vector<rtc::scoped_refptr<RtpReceiverInterface>> receivers;
for (auto receiver : pc()->GetReceivers()) {
if (receiver->media_type() == media_type) {
receivers.push_back(receiver);
}
}
return receivers;
}
rtc::scoped_refptr<RtpTransceiverInterface> GetFirstTransceiverOfType(
cricket::MediaType media_type) {
for (auto transceiver : pc()->GetTransceivers()) {
if (transceiver->receiver()->media_type() == media_type) {
return transceiver;
}
}
return nullptr;
}
bool SignalingStateStable() {
return pc()->signaling_state() == webrtc::PeerConnectionInterface::kStable;
}
void CreateDataChannel() { CreateDataChannel(nullptr); }
void CreateDataChannel(const webrtc::DataChannelInit* init) {
CreateDataChannel(kDataChannelLabel, init);
}
void CreateDataChannel(const std::string& label,
const webrtc::DataChannelInit* init) {
data_channel_ = pc()->CreateDataChannel(label, 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 (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;
}
// Returns a MockStatsObserver in a state after stats gathering finished,
// which can be used to access the gathered stats.
rtc::scoped_refptr<MockStatsObserver> OldGetStatsForTrack(
webrtc::MediaStreamTrackInterface* track) {
rtc::scoped_refptr<MockStatsObserver> observer(
new rtc::RefCountedObject<MockStatsObserver>());
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<MockStatsObserver> OldGetStats() {
return OldGetStatsForTrack(nullptr);
}
// Synchronously gets stats and returns them. If it times out, fails the test
// and returns null.
rtc::scoped_refptr<const webrtc::RTCStatsReport> NewGetStats() {
rtc::scoped_refptr<webrtc::MockRTCStatsCollectorCallback> callback(
new rtc::RefCountedObject<webrtc::MockRTCStatsCollectorCallback>());
peer_connection_->GetStats(callback);
EXPECT_TRUE_WAIT(callback->called(), kDefaultTimeout);
return callback->report();
}
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<double>(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<double>(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<std::unique_ptr<MockRtpReceiverObserver>>&
rtp_receiver_observers() {
return rtp_receiver_observers_;
}
void ResetRtpReceiverObservers() {
rtp_receiver_observers_.clear();
for (const rtc::scoped_refptr<RtpReceiverInterface>& receiver :
pc()->GetReceivers()) {
std::unique_ptr<MockRtpReceiverObserver> observer(
new MockRtpReceiverObserver(receiver->media_type()));
receiver->SetObserver(observer.get());
rtp_receiver_observers_.push_back(std::move(observer));
}
}
rtc::FakeNetworkManager* network() const {
return fake_network_manager_.get();
}
cricket::PortAllocator* port_allocator() const { return port_allocator_; }
webrtc::FakeRtcEventLogFactory* event_log_factory() const {
return event_log_factory_;
}
private:
explicit PeerConnectionWrapper(const std::string& debug_name)
: debug_name_(debug_name) {}
bool Init(
const PeerConnectionFactory::Options* options,
const PeerConnectionInterface::RTCConfiguration* config,
webrtc::PeerConnectionDependencies dependencies,
rtc::Thread* network_thread,
rtc::Thread* worker_thread,
std::unique_ptr<webrtc::FakeRtcEventLogFactory> event_log_factory,
std::unique_ptr<webrtc::MediaTransportFactory> media_transport_factory) {
// 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(kDefaultLocalAddress);
std::unique_ptr<cricket::PortAllocator> port_allocator(
new cricket::BasicPortAllocator(fake_network_manager_.get()));
port_allocator_ = port_allocator.get();
fake_audio_capture_module_ = FakeAudioCaptureModule::Create();
if (!fake_audio_capture_module_) {
return false;
}
rtc::Thread* const signaling_thread = rtc::Thread::Current();
webrtc::PeerConnectionFactoryDependencies pc_factory_dependencies;
pc_factory_dependencies.network_thread = network_thread;
pc_factory_dependencies.worker_thread = worker_thread;
pc_factory_dependencies.signaling_thread = signaling_thread;
pc_factory_dependencies.media_engine =
cricket::WebRtcMediaEngineFactory::Create(
rtc::scoped_refptr<webrtc::AudioDeviceModule>(
fake_audio_capture_module_),
webrtc::CreateBuiltinAudioEncoderFactory(),
webrtc::CreateBuiltinAudioDecoderFactory(),
webrtc::CreateBuiltinVideoEncoderFactory(),
webrtc::CreateBuiltinVideoDecoderFactory(), nullptr,
webrtc::AudioProcessingBuilder().Create());
pc_factory_dependencies.call_factory = webrtc::CreateCallFactory();
if (event_log_factory) {
event_log_factory_ = event_log_factory.get();
pc_factory_dependencies.event_log_factory = std::move(event_log_factory);
} else {
pc_factory_dependencies.event_log_factory =
webrtc::CreateRtcEventLogFactory();
}
if (media_transport_factory) {
pc_factory_dependencies.media_transport_factory =
std::move(media_transport_factory);
}
peer_connection_factory_ = webrtc::CreateModularPeerConnectionFactory(
std::move(pc_factory_dependencies));
if (!peer_connection_factory_) {
return false;
}
if (options) {
peer_connection_factory_->SetOptions(*options);
}
if (config) {
sdp_semantics_ = config->sdp_semantics;
}
dependencies.allocator = std::move(port_allocator);
peer_connection_ = CreatePeerConnection(config, std::move(dependencies));
return peer_connection_.get() != nullptr;
}
rtc::scoped_refptr<webrtc::PeerConnectionInterface> CreatePeerConnection(
const PeerConnectionInterface::RTCConfiguration* config,
webrtc::PeerConnectionDependencies dependencies) {
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);
dependencies.observer = this;
return peer_connection_factory_->CreatePeerConnection(
modified_config, std::move(dependencies));
}
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 set_signal_ice_candidates(bool signal) {
signal_ice_candidates_ = signal;
}
rtc::scoped_refptr<webrtc::VideoTrackInterface> CreateLocalVideoTrackInternal(
webrtc::FakePeriodicVideoSource::Config config) {
// Set max frame rate to 10fps to reduce the risk of test flakiness.
// TODO(deadbeef): Do something more robust.
config.frame_interval_ms = 100;
video_track_sources_.emplace_back(
new rtc::RefCountedObject<webrtc::FakePeriodicVideoTrackSource>(
config, false /* remote */));
rtc::scoped_refptr<webrtc::VideoTrackInterface> track(
peer_connection_factory_->CreateVideoTrack(
rtc::CreateRandomUuid(), video_track_sources_.back()));
if (!local_video_renderer_) {
local_video_renderer_.reset(new webrtc::FakeVideoTrackRenderer(track));
}
return track;
}
void HandleIncomingOffer(const std::string& msg) {
RTC_LOG(LS_INFO) << debug_name_ << ": HandleIncomingOffer";
std::unique_ptr<SessionDescriptionInterface> desc =
webrtc::CreateSessionDescription(SdpType::kOffer, msg);
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();
if (remote_offer_handler_) {
remote_offer_handler_();
}
auto answer = CreateAnswer();
ASSERT_NE(nullptr, answer);
EXPECT_TRUE(SetLocalDescriptionAndSendSdpMessage(std::move(answer)));
}
void HandleIncomingAnswer(const std::string& msg) {
RTC_LOG(LS_INFO) << debug_name_ << ": HandleIncomingAnswer";
std::unique_ptr<SessionDescriptionInterface> desc =
webrtc::CreateSessionDescription(SdpType::kAnswer, msg);
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<SessionDescriptionInterface> CreateOffer() {
rtc::scoped_refptr<MockCreateSessionDescriptionObserver> observer(
new rtc::RefCountedObject<MockCreateSessionDescriptionObserver>());
pc()->CreateOffer(observer, offer_answer_options_);
return WaitForDescriptionFromObserver(observer);
}
// Returns null on failure.
std::unique_ptr<SessionDescriptionInterface> CreateAnswer() {
rtc::scoped_refptr<MockCreateSessionDescriptionObserver> observer(
new rtc::RefCountedObject<MockCreateSessionDescriptionObserver>());
pc()->CreateAnswer(observer, offer_answer_options_);
return WaitForDescriptionFromObserver(observer);
}
std::unique_ptr<SessionDescriptionInterface> WaitForDescriptionFromObserver(
MockCreateSessionDescriptionObserver* 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<SessionDescriptionInterface> desc) {
rtc::scoped_refptr<MockSetSessionDescriptionObserver> observer(
new rtc::RefCountedObject<MockSetSessionDescriptionObserver>());
RTC_LOG(LS_INFO) << debug_name_ << ": SetLocalDescriptionAndSendSdpMessage";
SdpType type = desc->GetType();
std::string sdp;
EXPECT_TRUE(desc->ToString(&sdp));
pc()->SetLocalDescription(observer, desc.release());
if (sdp_semantics_ == SdpSemantics::kUnifiedPlan) {
RemoveUnusedVideoRenderers();
}
// 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<SessionDescriptionInterface> desc) {
rtc::scoped_refptr<MockSetSessionDescriptionObserver> observer(
new rtc::RefCountedObject<MockSetSessionDescriptionObserver>());
RTC_LOG(LS_INFO) << debug_name_ << ": SetRemoteDescription";
pc()->SetRemoteDescription(observer, desc.release());
if (sdp_semantics_ == SdpSemantics::kUnifiedPlan) {
RemoveUnusedVideoRenderers();
}
EXPECT_TRUE_WAIT(observer->called(), kDefaultTimeout);
return observer->result();
}
// This is a work around to remove unused fake_video_renderers from
// transceivers that have either stopped or are no longer receiving.
void RemoveUnusedVideoRenderers() {
auto transceivers = pc()->GetTransceivers();
for (auto& transceiver : transceivers) {
if (transceiver->receiver()->media_type() != cricket::MEDIA_TYPE_VIDEO) {
continue;
}
// Remove fake video renderers from any stopped transceivers.
if (transceiver->stopped()) {
auto it =
fake_video_renderers_.find(transceiver->receiver()->track()->id());
if (it != fake_video_renderers_.end()) {
fake_video_renderers_.erase(it);
}
}
// Remove fake video renderers from any transceivers that are no longer
// receiving.
if ((transceiver->current_direction() &&
!webrtc::RtpTransceiverDirectionHasRecv(
*transceiver->current_direction()))) {
auto it =
fake_video_renderers_.find(transceiver->receiver()->track()->id());
if (it != fake_video_renderers_.end()) {
fake_video_renderers_.erase(it);
}
}
}
}
// Simulate sending a blob of SDP with delay |signaling_delay_ms_| (0 by
// default).
void SendSdpMessage(SdpType type, const std::string& msg) {
if (signaling_delay_ms_ == 0) {
RelaySdpMessageIfReceiverExists(type, msg);
} else {
invoker_.AsyncInvokeDelayed<void>(
RTC_FROM_HERE, rtc::Thread::Current(),
rtc::Bind(&PeerConnectionWrapper::RelaySdpMessageIfReceiverExists,
this, type, msg),
signaling_delay_ms_);
}
}
void RelaySdpMessageIfReceiverExists(SdpType 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<void>(
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(SdpType type, const std::string& msg) override {
if (type == SdpType::kOffer) {
HandleIncomingOffer(msg);
} else {
HandleIncomingAnswer(msg);
}
}
void ReceiveIceMessage(const std::string& sdp_mid,
int sdp_mline_index,
const std::string& msg) override {
RTC_LOG(LS_INFO) << debug_name_ << ": ReceiveIceMessage";
std::unique_ptr<webrtc::IceCandidateInterface> 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 OnAddTrack(rtc::scoped_refptr<RtpReceiverInterface> receiver,
const std::vector<rtc::scoped_refptr<MediaStreamInterface>>&
streams) override {
if (receiver->media_type() == cricket::MEDIA_TYPE_VIDEO) {
rtc::scoped_refptr<VideoTrackInterface> video_track(
static_cast<VideoTrackInterface*>(receiver->track().get()));
ASSERT_TRUE(fake_video_renderers_.find(video_track->id()) ==
fake_video_renderers_.end());
fake_video_renderers_[video_track->id()] =
absl::make_unique<FakeVideoTrackRenderer>(video_track);
}
}
void OnRemoveTrack(
rtc::scoped_refptr<RtpReceiverInterface> receiver) override {
if (receiver->media_type() == cricket::MEDIA_TYPE_VIDEO) {
auto it = fake_video_renderers_.find(receiver->track()->id());
RTC_DCHECK(it != fake_video_renderers_.end());
fake_video_renderers_.erase(it);
}
}
void OnRenegotiationNeeded() override {}
void OnIceConnectionChange(
webrtc::PeerConnectionInterface::IceConnectionState new_state) override {
EXPECT_EQ(pc()->ice_connection_state(), new_state);
ice_connection_state_history_.push_back(new_state);
}
void OnConnectionChange(
webrtc::PeerConnectionInterface::PeerConnectionState new_state) override {
peer_connection_state_history_.push_back(new_state);
}
void OnIceGatheringChange(
webrtc::PeerConnectionInterface::IceGatheringState new_state) override {
EXPECT_EQ(pc()->ice_gathering_state(), new_state);
ice_gathering_state_history_.push_back(new_state);
}
std::unique_ptr<webrtc::IceCandidateInterface> ReplaceIceCandidate(
const webrtc::IceCandidateInterface* candidate) {
std::string candidate_string;
candidate->ToString(&candidate_string);
auto owned_candidate =
local_ice_candidate_replacer_->ReplaceCandidate(candidate);
if (!owned_candidate) {
RTC_LOG(LS_INFO) << "LocalIceCandidateReplacer dropped \""
<< candidate_string << "\"";
return nullptr;
}
std::string owned_candidate_string;
owned_candidate->ToString(&owned_candidate_string);
RTC_LOG(LS_INFO) << "LocalIceCandidateReplacer changed \""
<< candidate_string << "\" to \"" << owned_candidate_string
<< "\"";
return owned_candidate;
}
void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override {
RTC_LOG(LS_INFO) << debug_name_ << ": OnIceCandidate";
const webrtc::IceCandidateInterface* new_candidate = candidate;
std::unique_ptr<webrtc::IceCandidateInterface> owned_candidate;
if (local_ice_candidate_replacer_) {
owned_candidate = ReplaceIceCandidate(candidate);
if (!owned_candidate) {
return; // The candidate was dropped.
}
new_candidate = owned_candidate.get();
}
std::string ice_sdp;
EXPECT_TRUE(new_candidate->ToString(&ice_sdp));
if (signaling_message_receiver_ == nullptr || !signal_ice_candidates_) {
// Remote party may be deleted.
return;
}
SendIceMessage(new_candidate->sdp_mid(), new_candidate->sdp_mline_index(),
ice_sdp);
}
void OnDataChannel(
rtc::scoped_refptr<DataChannelInterface> data_channel) override {
RTC_LOG(LS_INFO) << debug_name_ << ": OnDataChannel";
data_channel_ = data_channel;
data_observer_.reset(new MockDataChannelObserver(data_channel));
}
std::string debug_name_;
std::unique_ptr<rtc::FakeNetworkManager> fake_network_manager_;
rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection_;
rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface>
peer_connection_factory_;
cricket::PortAllocator* port_allocator_;
// Needed to keep track of number of frames sent.
rtc::scoped_refptr<FakeAudioCaptureModule> fake_audio_capture_module_;
// Needed to keep track of number of frames received.
std::map<std::string, std::unique_ptr<webrtc::FakeVideoTrackRenderer>>
fake_video_renderers_;
// Needed to ensure frames aren't received for removed tracks.
std::vector<std::unique_ptr<webrtc::FakeVideoTrackRenderer>>
removed_fake_video_renderers_;
// For remote peer communication.
SignalingMessageReceiver* signaling_message_receiver_ = nullptr;
int signaling_delay_ms_ = 0;
bool signal_ice_candidates_ = true;
// Store references to the video sources we've created, so that we can stop
// them, if required.
std::vector<rtc::scoped_refptr<webrtc::VideoTrackSource>>
video_track_sources_;
// |local_video_renderer_| attached to the first created local video track.
std::unique_ptr<webrtc::FakeVideoTrackRenderer> local_video_renderer_;
SdpSemantics sdp_semantics_;
PeerConnectionInterface::RTCOfferAnswerOptions offer_answer_options_;
std::function<void(cricket::SessionDescription*)> received_sdp_munger_;
std::function<void(cricket::SessionDescription*)> generated_sdp_munger_;
std::function<void()> remote_offer_handler_;
std::unique_ptr<IceCandidateReplacerInterface> local_ice_candidate_replacer_;
rtc::scoped_refptr<DataChannelInterface> data_channel_;
std::unique_ptr<MockDataChannelObserver> data_observer_;
std::vector<std::unique_ptr<MockRtpReceiverObserver>> rtp_receiver_observers_;
std::vector<PeerConnectionInterface::IceConnectionState>
ice_connection_state_history_;
std::vector<PeerConnectionInterface::PeerConnectionState>
peer_connection_state_history_;
std::vector<PeerConnectionInterface::IceGatheringState>
ice_gathering_state_history_;
webrtc::FakeRtcEventLogFactory* event_log_factory_;
rtc::AsyncInvoker invoker_;
friend class PeerConnectionIntegrationBaseTest;
};
class MockRtcEventLogOutput : public webrtc::RtcEventLogOutput {
public:
virtual ~MockRtcEventLogOutput() = default;
MOCK_CONST_METHOD0(IsActive, bool());
MOCK_METHOD1(Write, bool(const std::string&));
};
// This helper object is used for both specifying how many audio/video frames
// are expected to be received for a caller/callee. It provides helper functions
// to specify these expectations. The object initially starts in a state of no
// expectations.
class MediaExpectations {
public:
enum ExpectFrames {
kExpectSomeFrames,
kExpectNoFrames,
kNoExpectation,
};
void ExpectBidirectionalAudioAndVideo() {
ExpectBidirectionalAudio();
ExpectBidirectionalVideo();
}
void ExpectBidirectionalAudio() {
CallerExpectsSomeAudio();
CalleeExpectsSomeAudio();
}
void ExpectNoAudio() {
CallerExpectsNoAudio();
CalleeExpectsNoAudio();
}
void ExpectBidirectionalVideo() {
CallerExpectsSomeVideo();
CalleeExpectsSomeVideo();
}
void ExpectNoVideo() {
CallerExpectsNoVideo();
CalleeExpectsNoVideo();
}
void CallerExpectsSomeAudioAndVideo() {
CallerExpectsSomeAudio();
CallerExpectsSomeVideo();
}
void CalleeExpectsSomeAudioAndVideo() {
CalleeExpectsSomeAudio();
CalleeExpectsSomeVideo();
}
// Caller's audio functions.
void CallerExpectsSomeAudio(
int expected_audio_frames = kDefaultExpectedAudioFrameCount) {
caller_audio_expectation_ = kExpectSomeFrames;
caller_audio_frames_expected_ = expected_audio_frames;
}
void CallerExpectsNoAudio() {
caller_audio_expectation_ = kExpectNoFrames;
caller_audio_frames_expected_ = 0;
}
// Caller's video functions.
void CallerExpectsSomeVideo(
int expected_video_frames = kDefaultExpectedVideoFrameCount) {
caller_video_expectation_ = kExpectSomeFrames;
caller_video_frames_expected_ = expected_video_frames;
}
void CallerExpectsNoVideo() {
caller_video_expectation_ = kExpectNoFrames;
caller_video_frames_expected_ = 0;
}
// Callee's audio functions.
void CalleeExpectsSomeAudio(
int expected_audio_frames = kDefaultExpectedAudioFrameCount) {
callee_audio_expectation_ = kExpectSomeFrames;
callee_audio_frames_expected_ = expected_audio_frames;
}
void CalleeExpectsNoAudio() {
callee_audio_expectation_ = kExpectNoFrames;
callee_audio_frames_expected_ = 0;
}
// Callee's video functions.
void CalleeExpectsSomeVideo(
int expected_video_frames = kDefaultExpectedVideoFrameCount) {
callee_video_expectation_ = kExpectSomeFrames;
callee_video_frames_expected_ = expected_video_frames;
}
void CalleeExpectsNoVideo() {
callee_video_expectation_ = kExpectNoFrames;
callee_video_frames_expected_ = 0;
}
ExpectFrames caller_audio_expectation_ = kNoExpectation;
ExpectFrames caller_video_expectation_ = kNoExpectation;
ExpectFrames callee_audio_expectation_ = kNoExpectation;
ExpectFrames callee_video_expectation_ = kNoExpectation;
int caller_audio_frames_expected_ = 0;
int caller_video_frames_expected_ = 0;
int callee_audio_frames_expected_ = 0;
int callee_video_frames_expected_ = 0;
};
// 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 PeerConnectionIntegrationBaseTest : public testing::Test {
public:
explicit PeerConnectionIntegrationBaseTest(SdpSemantics sdp_semantics)
: sdp_semantics_(sdp_semantics),
ss_(new rtc::VirtualSocketServer()),
fss_(new rtc::FirewallSocketServer(ss_.get())),
network_thread_(new rtc::Thread(fss_.get())),
worker_thread_(rtc::Thread::Create()),
loopback_media_transports_(network_thread_.get()) {
network_thread_->SetName("PCNetworkThread", this);
worker_thread_->SetName("PCWorkerThread", this);
RTC_CHECK(network_thread_->Start());
RTC_CHECK(worker_thread_->Start());
webrtc::metrics::Reset();
}
~PeerConnectionIntegrationBaseTest() {
// The PeerConnections should deleted before the TurnCustomizers.
// A TurnPort is created with a raw pointer to a TurnCustomizer. The
// TurnPort has the same lifetime as the PeerConnection, so it's expected
// that the TurnCustomizer outlives the life of the PeerConnection or else
// when Send() is called it will hit a seg fault.
if (caller_) {
caller_->set_signaling_message_receiver(nullptr);
delete SetCallerPcWrapperAndReturnCurrent(nullptr);
}
if (callee_) {
callee_->set_signaling_message_receiver(nullptr);
delete SetCalleePcWrapperAndReturnCurrent(nullptr);
}
// If turn servers were created for the test they need to be destroyed on
// the network thread.
network_thread()->Invoke<void>(RTC_FROM_HERE, [this] {
turn_servers_.clear();
turn_customizers_.clear();
});
}
bool SignalingStateStable() {
return caller_->SignalingStateStable() && callee_->SignalingStateStable();
}
bool DtlsConnected() {
// 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.
return (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);
}
// When |event_log_factory| is null, the default implementation of the event
// log factory will be used.
std::unique_ptr<PeerConnectionWrapper> CreatePeerConnectionWrapper(
const std::string& debug_name,
const PeerConnectionFactory::Options* options,
const RTCConfiguration* config,
webrtc::PeerConnectionDependencies dependencies,
std::unique_ptr<webrtc::FakeRtcEventLogFactory> event_log_factory,
std::unique_ptr<webrtc::MediaTransportFactory> media_transport_factory) {
RTCConfiguration modified_config;
if (config) {
modified_config = *config;
}
modified_config.sdp_semantics = sdp_semantics_;
if (!dependencies.cert_generator) {
dependencies.cert_generator =
absl::make_unique<FakeRTCCertificateGenerator>();
}
std::unique_ptr<PeerConnectionWrapper> client(
new PeerConnectionWrapper(debug_name));
if (!client->Init(options, &modified_config, std::move(dependencies),
network_thread_.get(), worker_thread_.get(),
std::move(event_log_factory),
std::move(media_transport_factory))) {
return nullptr;
}
return client;
}
std::unique_ptr<PeerConnectionWrapper>
CreatePeerConnectionWrapperWithFakeRtcEventLog(
const std::string& debug_name,
const PeerConnectionFactory::Options* options,
const RTCConfiguration* config,
webrtc::PeerConnectionDependencies dependencies) {
std::unique_ptr<webrtc::FakeRtcEventLogFactory> event_log_factory(
new webrtc::FakeRtcEventLogFactory(rtc::Thread::Current()));
return CreatePeerConnectionWrapper(debug_name, options, config,
std::move(dependencies),
std::move(event_log_factory),
/*media_transport_factory=*/nullptr);
}
bool CreatePeerConnectionWrappers() {
return CreatePeerConnectionWrappersWithConfig(
PeerConnectionInterface::RTCConfiguration(),
PeerConnectionInterface::RTCConfiguration());
}
bool CreatePeerConnectionWrappersWithSdpSemantics(
SdpSemantics caller_semantics,
SdpSemantics callee_semantics) {
// Can't specify the sdp_semantics in the passed-in configuration since it
// will be overwritten by CreatePeerConnectionWrapper with whatever is
// stored in sdp_semantics_. So get around this by modifying the instance
// variable before calling CreatePeerConnectionWrapper for the caller and
// callee PeerConnections.
SdpSemantics original_semantics = sdp_semantics_;
sdp_semantics_ = caller_semantics;
caller_ = CreatePeerConnectionWrapper(
"Caller", nullptr, nullptr, webrtc::PeerConnectionDependencies(nullptr),
nullptr, /*media_transport_factory=*/nullptr);
sdp_semantics_ = callee_semantics;
callee_ = CreatePeerConnectionWrapper(
"Callee", nullptr, nullptr, webrtc::PeerConnectionDependencies(nullptr),
nullptr, /*media_transport_factory=*/nullptr);
sdp_semantics_ = original_semantics;
return caller_ && callee_;
}
bool CreatePeerConnectionWrappersWithConfig(
const PeerConnectionInterface::RTCConfiguration& caller_config,
const PeerConnectionInterface::RTCConfiguration& callee_config) {
caller_ = CreatePeerConnectionWrapper(
"Caller", nullptr, &caller_config,
webrtc::PeerConnectionDependencies(nullptr), nullptr,
/*media_transport_factory=*/nullptr);
callee_ = CreatePeerConnectionWrapper(
"Callee", nullptr, &callee_config,
webrtc::PeerConnectionDependencies(nullptr), nullptr,
/*media_transport_factory=*/nullptr);
return caller_ && callee_;
}
bool CreatePeerConnectionWrappersWithConfigAndMediaTransportFactory(
const PeerConnectionInterface::RTCConfiguration& caller_config,
const PeerConnectionInterface::RTCConfiguration& callee_config,
std::unique_ptr<webrtc::MediaTransportFactory> caller_factory,
std::unique_ptr<webrtc::MediaTransportFactory> callee_factory) {
caller_ =
CreatePeerConnectionWrapper("Caller", nullptr, &caller_config,
webrtc::PeerConnectionDependencies(nullptr),
nullptr, std::move(caller_factory));
callee_ =
CreatePeerConnectionWrapper("Callee", nullptr, &callee_config,
webrtc::PeerConnectionDependencies(nullptr),
nullptr, std::move(callee_factory));
return caller_ && callee_;
}
bool CreatePeerConnectionWrappersWithConfigAndDeps(
const PeerConnectionInterface::RTCConfiguration& caller_config,
webrtc::PeerConnectionDependencies caller_dependencies,
const PeerConnectionInterface::RTCConfiguration& callee_config,
webrtc::PeerConnectionDependencies callee_dependencies) {
caller_ =
CreatePeerConnectionWrapper("Caller", nullptr, &caller_config,
std::move(caller_dependencies), nullptr,
/*media_transport_factory=*/nullptr);
callee_ =
CreatePeerConnectionWrapper("Callee", nullptr, &callee_config,
std::move(callee_dependencies), nullptr,
/*media_transport_factory=*/nullptr);
return caller_ && callee_;
}
bool CreatePeerConnectionWrappersWithOptions(
const PeerConnectionFactory::Options& caller_options,
const PeerConnectionFactory::Options& callee_options) {
caller_ = CreatePeerConnectionWrapper(
"Caller", &caller_options, nullptr,
webrtc::PeerConnectionDependencies(nullptr), nullptr,
/*media_transport_factory=*/nullptr);
callee_ = CreatePeerConnectionWrapper(
"Callee", &callee_options, nullptr,
webrtc::PeerConnectionDependencies(nullptr), nullptr,
/*media_transport_factory=*/nullptr);
return caller_ && callee_;
}
bool CreatePeerConnectionWrappersWithFakeRtcEventLog() {
PeerConnectionInterface::RTCConfiguration default_config;
caller_ = CreatePeerConnectionWrapperWithFakeRtcEventLog(
"Caller", nullptr, &default_config,
webrtc::PeerConnectionDependencies(nullptr));
callee_ = CreatePeerConnectionWrapperWithFakeRtcEventLog(
"Callee", nullptr, &default_config,
webrtc::PeerConnectionDependencies(nullptr));
return caller_ && callee_;
}
std::unique_ptr<PeerConnectionWrapper>
CreatePeerConnectionWrapperWithAlternateKey() {
std::unique_ptr<FakeRTCCertificateGenerator> cert_generator(
new FakeRTCCertificateGenerator());
cert_generator->use_alternate_key();
webrtc::PeerConnectionDependencies dependencies(nullptr);
dependencies.cert_generator = std::move(cert_generator);
return CreatePeerConnectionWrapper("New Peer", nullptr, nullptr,
std::move(dependencies), nullptr,
/*media_transport_factory=*/nullptr);
}
cricket::TestTurnServer* CreateTurnServer(
rtc::SocketAddress internal_address,
rtc::SocketAddress external_address,
cricket::ProtocolType type = cricket::ProtocolType::PROTO_UDP,
const std::string& common_name = "test turn server") {
rtc::Thread* thread = network_thread();
std::unique_ptr<cricket::TestTurnServer> turn_server =
network_thread()->Invoke<std::unique_ptr<cricket::TestTurnServer>>(
RTC_FROM_HERE,
[thread, internal_address, external_address, type, common_name] {
return absl::make_unique<cricket::TestTurnServer>(
thread, internal_address, external_address, type,
/*ignore_bad_certs=*/true, common_name);
});
turn_servers_.push_back(std::move(turn_server));
// Interactions with the turn server should be done on the network thread.
return turn_servers_.back().get();
}
cricket::TestTurnCustomizer* CreateTurnCustomizer() {
std::unique_ptr<cricket::TestTurnCustomizer> turn_customizer =
network_thread()->Invoke<std::unique_ptr<cricket::TestTurnCustomizer>>(
RTC_FROM_HERE,
[] { return absl::make_unique<cricket::TestTurnCustomizer>(); });
turn_customizers_.push_back(std::move(turn_customizer));
// Interactions with the turn customizer should be done on the network
// thread.
return turn_customizers_.back().get();
}
// Checks that the function counters for a TestTurnCustomizer are greater than
// 0.
void ExpectTurnCustomizerCountersIncremented(
cricket::TestTurnCustomizer* turn_customizer) {
unsigned int allow_channel_data_counter =
network_thread()->Invoke<unsigned int>(
RTC_FROM_HERE, [turn_customizer] {
return turn_customizer->allow_channel_data_cnt_;
});
EXPECT_GT(allow_channel_data_counter, 0u);
unsigned int modify_counter = network_thread()->Invoke<unsigned int>(
RTC_FROM_HERE,
[turn_customizer] { return turn_customizer->modify_cnt_; });
EXPECT_GT(modify_counter, 0u);
}
// 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());
}
// Once called, SDP blobs will be automatically signaled between
// PeerConnections. Note that ICE candidates will not be signaled unless they
// are in the exchanged SDP blobs.
void ConnectFakeSignalingForSdpOnly() {
ConnectFakeSignaling();
SetSignalIceCandidates(false);
}
void SetSignalingDelayMs(int delay_ms) {
caller_->set_signaling_delay_ms(delay_ms);
callee_->set_signaling_delay_ms(delay_ms);
}
void SetSignalIceCandidates(bool signal) {
caller_->set_signal_ice_candidates(signal);
callee_->set_signal_ice_candidates(signal);
}
// 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(); }
webrtc::MediaTransportPair* loopback_media_transports() {
return &loopback_media_transports_;
}
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;
}
rtc::FirewallSocketServer* firewall() const { return fss_.get(); }
// Expects the provided number of new frames to be received within
// kMaxWaitForFramesMs. The new expected frames are specified in
// |media_expectations|. Returns false if any of the expectations were
// not met.
bool ExpectNewFrames(const MediaExpectations& media_expectations) {
// First initialize the expected frame counts based upon the current
// frame count.
int total_caller_audio_frames_expected = caller()->audio_frames_received();
if (media_expectations.caller_audio_expectation_ ==
MediaExpectations::kExpectSomeFrames) {
total_caller_audio_frames_expected +=
media_expectations.caller_audio_frames_expected_;
}
int total_caller_video_frames_expected =
caller()->min_video_frames_received_per_track();
if (media_expectations.caller_video_expectation_ ==
MediaExpectations::kExpectSomeFrames) {
total_caller_video_frames_expected +=
media_expectations.caller_video_frames_expected_;
}
int total_callee_audio_frames_expected = callee()->audio_frames_received();
if (media_expectations.callee_audio_expectation_ ==
MediaExpectations::kExpectSomeFrames) {
total_callee_audio_frames_expected +=
media_expectations.callee_audio_frames_expected_;
}
int total_callee_video_frames_expected =
callee()->min_video_frames_received_per_track();
if (media_expectations.callee_video_expectation_ ==
MediaExpectations::kExpectSomeFrames) {
total_callee_video_frames_expected +=
media_expectations.callee_video_frames_expected_;
}
// Wait for the expected frames.
EXPECT_TRUE_WAIT(caller()->audio_frames_received() >=
total_caller_audio_frames_expected &&
caller()->min_video_frames_received_per_track() >=
total_caller_video_frames_expected &&
callee()->audio_frames_received() >=
total_callee_audio_frames_expected &&
callee()->min_video_frames_received_per_track() >=
total_callee_video_frames_expected,
kMaxWaitForFramesMs);
bool expectations_correct =
caller()->audio_frames_received() >=
total_caller_audio_frames_expected &&
caller()->min_video_frames_received_per_track() >=
total_caller_video_frames_expected &&
callee()->audio_frames_received() >=
total_callee_audio_frames_expected &&
callee()->min_video_frames_received_per_track() >=
total_callee_video_frames_expected;
// After the combined wait, print out a more detailed message upon
// failure.
EXPECT_GE(caller()->audio_frames_received(),
total_caller_audio_frames_expected);
EXPECT_GE(caller()->min_video_frames_received_per_track(),
total_caller_video_frames_expected);
EXPECT_GE(callee()->audio_frames_received(),
total_callee_audio_frames_expected);
EXPECT_GE(callee()->min_video_frames_received_per_track(),
total_callee_video_frames_expected);
// We want to make sure nothing unexpected was received.
if (media_expectations.caller_audio_expectation_ ==
MediaExpectations::kExpectNoFrames) {
EXPECT_EQ(caller()->audio_frames_received(),
total_caller_audio_frames_expected);
if (caller()->audio_frames_received() !=
total_caller_audio_frames_expected) {
expectations_correct = false;
}
}
if (media_expectations.caller_video_expectation_ ==
MediaExpectations::kExpectNoFrames) {
EXPECT_EQ(caller()->min_video_frames_received_per_track(),
total_caller_video_frames_expected);
if (caller()->min_video_frames_received_per_track() !=
total_caller_video_frames_expected) {
expectations_correct = false;
}
}
if (media_expectations.callee_audio_expectation_ ==
MediaExpectations::kExpectNoFrames) {
EXPECT_EQ(callee()->audio_frames_received(),
total_callee_audio_frames_expected);
if (callee()->audio_frames_received() !=
total_callee_audio_frames_expected) {
expectations_correct = false;
}
}
if (media_expectations.callee_video_expectation_ ==
MediaExpectations::kExpectNoFrames) {
EXPECT_EQ(callee()->min_video_frames_received_per_track(),
total_callee_video_frames_expected);
if (callee()->min_video_frames_received_per_track() !=
total_callee_video_frames_expected) {
expectations_correct = false;
}
}
return expectations_correct;
}
void TestNegotiatedCipherSuite(
const PeerConnectionFactory::Options& caller_options,
const PeerConnectionFactory::Options& callee_options,
int expected_cipher_suite) {
ASSERT_TRUE(CreatePeerConnectionWrappersWithOptions(caller_options,
callee_options));
ConnectFakeSignaling();
caller()->AddAudioVideoTracks();
callee()->AddAudioVideoTracks();
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(DtlsConnected(), kDefaultTimeout);
EXPECT_EQ_WAIT(rtc::SrtpCryptoSuiteToName(expected_cipher_suite),
caller()->OldGetStats()->SrtpCipher(), kDefaultTimeout);
// TODO(bugs.webrtc.org/9456): Fix it.
EXPECT_EQ(1, webrtc::metrics::NumEvents(
"WebRTC.PeerConnection.SrtpCryptoSuite.Audio",
expected_cipher_suite));
}
void TestGcmNegotiationUsesCipherSuite(bool local_gcm_enabled,
bool remote_gcm_enabled,
int expected_cipher_suite) {
PeerConnectionFactory::Options caller_options;
caller_options.crypto_options.srtp.enable_gcm_crypto_suites =
local_gcm_enabled;
PeerConnectionFactory::Options callee_options;
callee_options.crypto_options.srtp.enable_gcm_crypto_suites =
remote_gcm_enabled;
TestNegotiatedCipherSuite(caller_options, callee_options,
expected_cipher_suite);
}
protected:
SdpSemantics sdp_semantics_;
private:
// |ss_| is used by |network_thread_| so it must be destroyed later.
std::unique_ptr<rtc::VirtualSocketServer> ss_;
std::unique_ptr<rtc::FirewallSocketServer> fss_;
// |network_thread_| and |worker_thread_| are used by both
// |caller_| and |callee_| so they must be destroyed
// later.
std::unique_ptr<rtc::Thread> network_thread_;
std::unique_ptr<rtc::Thread> worker_thread_;
// The turn servers and turn customizers should be accessed & deleted on the
// network thread to avoid a race with the socket read/write that occurs
// on the network thread.
std::vector<std::unique_ptr<cricket::TestTurnServer>> turn_servers_;
std::vector<std::unique_ptr<cricket::TestTurnCustomizer>> turn_customizers_;
webrtc::MediaTransportPair loopback_media_transports_;
std::unique_ptr<PeerConnectionWrapper> caller_;
std::unique_ptr<PeerConnectionWrapper> callee_;
};
class PeerConnectionIntegrationTest
: public PeerConnectionIntegrationBaseTest,
public ::testing::WithParamInterface<SdpSemantics> {
protected:
PeerConnectionIntegrationTest()
: PeerConnectionIntegrationBaseTest(GetParam()) {}
};
class PeerConnectionIntegrationTestPlanB
: public PeerConnectionIntegrationBaseTest {
protected:
PeerConnectionIntegrationTestPlanB()
: PeerConnectionIntegrationBaseTest(SdpSemantics::kPlanB) {}
};
class PeerConnectionIntegrationTestUnifiedPlan
: public PeerConnectionIntegrationBaseTest {
protected:
PeerConnectionIntegrationTestUnifiedPlan()
: PeerConnectionIntegrationBaseTest(SdpSemantics::kUnifiedPlan) {}
};
// 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_P(PeerConnectionIntegrationTest,
RtpReceiverObserverOnFirstPacketReceived) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
caller()->AddAudioVideoTracks();
callee()->AddAudioVideoTracks();
// 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(2U, caller()->rtp_receiver_observers().size());
EXPECT_EQ(2U, 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<MockRtpReceiverObserver>& 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<MockRtpReceiverObserver>& 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(2U, caller()->rtp_receiver_observers().size());
EXPECT_EQ(2U, callee()->rtp_receiver_observers().size());
EXPECT_TRUE(
std::all_of(caller()->rtp_receiver_observers().begin(),
caller()->rtp_receiver_observers().end(),
[](const std::unique_ptr<MockRtpReceiverObserver>& 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<MockRtpReceiverObserver>& 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<std::string>& tones() const { return tones_; }
bool completed() const { return completed_; }
private:
bool completed_;
std::vector<std::string> tones_;
};
// Assumes |sender| already has an audio track added and the offer/answer
// exchange is done.
void TestDtmfFromSenderToReceiver(PeerConnectionWrapper* sender,
PeerConnectionWrapper* receiver) {
// We should be able to get a DTMF sender from the local sender.
rtc::scoped_refptr<DtmfSenderInterface> dtmf_sender =
sender->pc()->GetSenders().at(0)->GetDtmfSender();
ASSERT_TRUE(dtmf_sender);
DummyDtmfObserver observer;
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<std::string> 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_P(PeerConnectionIntegrationTest, DtmfSenderObserver) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
// Only need audio for DTMF.
caller()->AddAudioTrack();
callee()->AddAudioTrack();
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
// DTLS must finish before the DTMF sender can be used reliably.
ASSERT_TRUE_WAIT(DtlsConnected(), 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_P(PeerConnectionIntegrationTest, EndToEndCallWithDtls) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
// Do normal offer/answer and wait for some frames to be received in each
// direction.
caller()->AddAudioVideoTracks();
callee()->AddAudioVideoTracks();
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
MediaExpectations media_expectations;
media_expectations.ExpectBidirectionalAudioAndVideo();
ASSERT_TRUE(ExpectNewFrames(media_expectations));
EXPECT_LE(2, webrtc::metrics::NumEvents("WebRTC.PeerConnection.KeyProtocol",
webrtc::kEnumCounterKeyProtocolDtls));
EXPECT_EQ(0, webrtc::metrics::NumEvents("WebRTC.PeerConnection.KeyProtocol",
webrtc::kEnumCounterKeyProtocolSdes));
}
// Uses SDES instead of DTLS for key agreement.
TEST_P(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()->AddAudioVideoTracks();
callee()->AddAudioVideoTracks();
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
MediaExpectations media_expectations;
media_expectations.ExpectBidirectionalAudioAndVideo();
ASSERT_TRUE(ExpectNewFrames(media_expectations));
EXPECT_LE(2, webrtc::metrics::NumEvents("WebRTC.PeerConnection.KeyProtocol",
webrtc::kEnumCounterKeyProtocolSdes));
EXPECT_EQ(0, webrtc::metrics::NumEvents("WebRTC.PeerConnection.KeyProtocol",
webrtc::kEnumCounterKeyProtocolDtls));
}
// Tests that the GetRemoteAudioSSLCertificate method returns the remote DTLS
// certificate once the DTLS handshake has finished.
TEST_P(PeerConnectionIntegrationTest,
GetRemoteAudioSSLCertificateReturnsExchangedCertificate) {
auto GetRemoteAudioSSLCertificate = [](PeerConnectionWrapper* wrapper) {
auto pci = reinterpret_cast<PeerConnectionProxy*>(wrapper->pc());
auto pc = reinterpret_cast<PeerConnection*>(pci->internal());
return pc->GetRemoteAudioSSLCertificate();
};
auto GetRemoteAudioSSLCertChain = [](PeerConnectionWrapper* wrapper) {
auto pci = reinterpret_cast<PeerConnectionProxy*>(wrapper->pc());
auto pc = reinterpret_cast<PeerConnection*>(pci->internal());
return pc->GetRemoteAudioSSLCertChain();
};
auto caller_cert = rtc::RTCCertificate::FromPEM(kRsaPems[0]);
auto callee_cert = rtc::RTCCertificate::FromPEM(kRsaPems[1]);
// Configure each side with a known certificate so they can be compared later.
PeerConnectionInterface::RTCConfiguration caller_config;
caller_config.enable_dtls_srtp.emplace(true);
caller_config.certificates.push_back(caller_cert);
PeerConnectionInterface::RTCConfiguration callee_config;
callee_config.enable_dtls_srtp.emplace(true);
callee_config.certificates.push_back(callee_cert);
ASSERT_TRUE(
CreatePeerConnectionWrappersWithConfig(caller_config, callee_config));
ConnectFakeSignaling();
// When first initialized, there should not be a remote SSL certificate (and
// calling this method should not crash).
EXPECT_EQ(nullptr, GetRemoteAudioSSLCertificate(caller()));
EXPECT_EQ(nullptr, GetRemoteAudioSSLCertificate(callee()));
EXPECT_EQ(nullptr, GetRemoteAudioSSLCertChain(caller()));
EXPECT_EQ(nullptr, GetRemoteAudioSSLCertChain(callee()));
caller()->AddAudioTrack();
callee()->AddAudioTrack();
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
ASSERT_TRUE_WAIT(DtlsConnected(), kDefaultTimeout);
// Once DTLS has been connected, each side should return the other's SSL
// certificate when calling GetRemoteAudioSSLCertificate.
auto caller_remote_cert = GetRemoteAudioSSLCertificate(caller());
ASSERT_TRUE(caller_remote_cert);
EXPECT_EQ(callee_cert->GetSSLCertificate().ToPEMString(),
caller_remote_cert->ToPEMString());
auto callee_remote_cert = GetRemoteAudioSSLCertificate(callee());
ASSERT_TRUE(callee_remote_cert);
EXPECT_EQ(caller_cert->GetSSLCertificate().ToPEMString(),
callee_remote_cert->ToPEMString());
auto caller_remote_cert_chain = GetRemoteAudioSSLCertChain(caller());
ASSERT_TRUE(caller_remote_cert_chain);
ASSERT_EQ(1U, caller_remote_cert_chain->GetSize());
auto remote_cert = &caller_remote_cert_chain->Get(0);
EXPECT_EQ(callee_cert->GetSSLCertificate().ToPEMString(),
remote_cert->ToPEMString());
auto callee_remote_cert_chain = GetRemoteAudioSSLCertChain(callee());
ASSERT_TRUE(callee_remote_cert_chain);
ASSERT_EQ(1U, callee_remote_cert_chain->GetSize());
remote_cert = &callee_remote_cert_chain->Get(0);
EXPECT_EQ(caller_cert->GetSSLCertificate().ToPEMString(),
remote_cert->ToPEMString());
}
// 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_P(PeerConnectionIntegrationTest,
Send1280By720ResolutionAndReceive16To9AspectRatio) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
// Add video tracks with 16:9 aspect ratio, size 1280 x 720.
webrtc::FakePeriodicVideoSource::Config config;
config.width = 1280;
config.height = 720;
config.timestamp_offset_ms = rtc::TimeMillis();
caller()->AddTrack(caller()->CreateLocalVideoTrackWithConfig(config));
callee()->AddTrack(callee()->CreateLocalVideoTrackWithConfig(config));
// 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_P(PeerConnectionIntegrationTest, OneWayMediaCall) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
caller()->AddAudioVideoTracks();
caller()->CreateAndSetAndSignalOffer();
MediaExpectations media_expectations;
media_expectations.CalleeExpectsSomeAudioAndVideo();
media_expectations.CallerExpectsNoAudio();
media_expectations.CallerExpectsNoVideo();
ASSERT_TRUE(ExpectNewFrames(media_expectations));
}
// 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_P(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()->AddAudioVideoTracks();
callee()->AddAudioTrack();
if (sdp_semantics_ == SdpSemantics::kPlanB) {
PeerConnectionInterface::RTCOfferAnswerOptions options;
options.offer_to_receive_video = 0;
callee()->SetOfferAnswerOptions(options);
} else {
callee()->SetRemoteOfferHandler([this] {
callee()->GetFirstTransceiverOfType(cricket::MEDIA_TYPE_VIDEO)->Stop();
});
}
// Do offer/answer and make sure audio is still received end-to-end.
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
{
MediaExpectations media_expectations;
media_expectations.ExpectBidirectionalAudio();
media_expectations.ExpectNoVideo();
ASSERT_TRUE(ExpectNewFrames(media_expectations));
}
// 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()->AddVideoTrack();
if (sdp_semantics_ == SdpSemantics::kPlanB) {
PeerConnectionInterface::RTCOfferAnswerOptions options;
options.offer_to_receive_video = 1;
callee()->SetOfferAnswerOptions(options);
} else {
callee()->SetRemoteOfferHandler(nullptr);
caller()->SetRemoteOfferHandler([this] {
// The caller creates a new transceiver to receive video on when receiving
// the offer, but by default it is send only.
auto transceivers = caller()->pc()->GetTransceivers();
ASSERT_EQ(3U, transceivers.size());
ASSERT_EQ(cricket::MEDIA_TYPE_VIDEO,
transceivers[2]->receiver()->media_type());
transceivers[2]->sender()->SetTrack(caller()->CreateLocalVideoTrack());
transceivers[2]->SetDirection(RtpTransceiverDirection::kSendRecv);
});
}
callee()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
{
// Expect additional audio frames to be received after the upgrade.
MediaExpectations media_expectations;
media_expectations.ExpectBidirectionalAudioAndVideo();
ASSERT_TRUE(ExpectNewFrames(media_expectations));
}
}
// Simpler than the above test; just add an audio track to an established
// video-only connection.
TEST_P(PeerConnectionIntegrationTest, AddAudioToVideoOnlyCall) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
// Do initial offer/answer with just a video track.
caller()->AddVideoTrack();
callee()->AddVideoTrack();
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
// Now add an audio track and do another offer/answer.
caller()->AddAudioTrack();
callee()->AddAudioTrack();
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
// Ensure both audio and video frames are received end-to-end.
MediaExpectations media_expectations;
media_expectations.ExpectBidirectionalAudioAndVideo();
ASSERT_TRUE(ExpectNewFrames(media_expectations));
}
// This test sets up a call that's transferred to a new caller with a different
// DTLS fingerprint.
TEST_P(PeerConnectionIntegrationTest, CallTransferredForCallee) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
caller()->AddAudioVideoTracks();
callee()->AddAudioVideoTracks();
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<PeerConnectionWrapper> original_peer(
SetCallerPcWrapperAndReturnCurrent(
CreatePeerConnectionWrapperWithAlternateKey().release()));
// TODO(deadbeef): Why do we call Close here? That goes against the comment
// directly above.
original_peer->pc()->Close();
ConnectFakeSignaling();
caller()->AddAudioVideoTracks();
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
// Wait for some additional frames to be transmitted end-to-end.
MediaExpectations media_expectations;
media_expectations.ExpectBidirectionalAudioAndVideo();
ASSERT_TRUE(ExpectNewFrames(media_expectations));
}
// This test sets up a call that's transferred to a new callee with a different
// DTLS fingerprint.
TEST_P(PeerConnectionIntegrationTest, CallTransferredForCaller) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
caller()->AddAudioVideoTracks();
callee()->AddAudioVideoTracks();
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<PeerConnectionWrapper> original_peer(
SetCalleePcWrapperAndReturnCurrent(
CreatePeerConnectionWrapperWithAlternateKey().release()));
// TODO(deadbeef): Why do we call Close here? That goes against the comment
// directly above.
original_peer->pc()->Close();
ConnectFakeSignaling();
callee()->AddAudioVideoTracks();
caller()->SetOfferAnswerOptions(IceRestartOfferAnswerOptions());
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
// Wait for some additional frames to be transmitted end-to-end.
MediaExpectations media_expectations;
media_expectations.ExpectBidirectionalAudioAndVideo();
ASSERT_TRUE(ExpectNewFrames(media_expectations));
}
// 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_P(PeerConnectionIntegrationTest, BundlingEnabledWhileIceRestartOccurs) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
caller()->AddAudioVideoTracks();
callee()->AddAudioVideoTracks();
// 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);
{
MediaExpectations media_expectations;
media_expectations.ExpectBidirectionalAudioAndVideo();
ASSERT_TRUE(ExpectNewFrames(media_expectations));
}
// 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.
{
MediaExpectations media_expectations;
media_expectations.ExpectBidirectionalAudioAndVideo();
ASSERT_TRUE(ExpectNewFrames(media_expectations));
}
}
// 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_P(PeerConnectionIntegrationTest, RotatedVideoWithCVOExtension) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
// Add rotated video tracks.
caller()->AddTrack(
caller()->CreateLocalVideoTrackWithRotation(webrtc::kVideoRotation_90));
callee()->AddTrack(
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_P(PeerConnectionIntegrationTest, RotatedVideoWithoutCVOExtension) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
// Add rotated video tracks.
caller()->AddTrack(
caller()->CreateLocalVideoTrackWithRotation(webrtc::kVideoRotation_90));
callee()->AddTrack(
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());
}
// Test that if the answerer rejects the audio m= section, no audio is sent or
// received, but video still can be.
TEST_P(PeerConnectionIntegrationTest, AnswererRejectsAudioSection) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
caller()->AddAudioVideoTracks();
if (sdp_semantics_ == SdpSemantics::kPlanB) {
// 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);
} else {
// Stopping the audio RtpTransceiver will cause the media section to be
// rejected in the answer.
callee()->SetRemoteOfferHandler([this] {
callee()->GetFirstTransceiverOfType(cricket::MEDIA_TYPE_AUDIO)->Stop();
});
}
callee()->AddTrack(callee()->CreateLocalVideoTrack());
// Do offer/answer and wait for successful end-to-end video frames.
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
MediaExpectations media_expectations;
media_expectations.ExpectBidirectionalVideo();
media_expectations.ExpectNoAudio();
ASSERT_TRUE(ExpectNewFrames(media_expectations));
// 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);
if (sdp_semantics_ == SdpSemantics::kUnifiedPlan) {
// The caller's transceiver should have stopped after receiving the answer.
EXPECT_TRUE(caller()
->GetFirstTransceiverOfType(cricket::MEDIA_TYPE_AUDIO)
->stopped());
}
}
// Test that if the answerer rejects the video m= section, no video is sent or
// received, but audio still can be.
TEST_P(PeerConnectionIntegrationTest, AnswererRejectsVideoSection) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
caller()->AddAudioVideoTracks();
if (sdp_semantics_ == SdpSemantics::kPlanB) {
// 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);
} else {
// Stopping the video RtpTransceiver will cause the media section to be
// rejected in the answer.
callee()->SetRemoteOfferHandler([this] {
callee()->GetFirstTransceiverOfType(cricket::MEDIA_TYPE_VIDEO)->Stop();
});
}
callee()->AddTrack(callee()->CreateLocalAudioTrack());
// Do offer/answer and wait for successful end-to-end audio frames.
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
MediaExpectations media_expectations;
media_expectations.ExpectBidirectionalAudio();
media_expectations.ExpectNoVideo();
ASSERT_TRUE(ExpectNewFrames(media_expectations));
// 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);
if (sdp_semantics_ == SdpSemantics::kUnifiedPlan) {
// The caller's transceiver should have stopped after receiving the answer.
EXPECT_TRUE(caller()
->GetFirstTransceiverOfType(cricket::MEDIA_TYPE_VIDEO)
->stopped());
}
}
// 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_P(PeerConnectionIntegrationTest, AnswererRejectsAudioAndVideoSections) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
caller()->AddAudioVideoTracks();
if (sdp_semantics_ == SdpSemantics::kPlanB) {
// 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);
} else {
callee()->SetRemoteOfferHandler([this] {
// Stopping all transceivers will cause all media sections to be rejected.
for (auto transceiver : callee()->pc()->GetTransceivers()) {
transceiver->Stop();
}
});
}
// 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_P(PeerConnectionIntegrationTest, VideoRejectedInSubsequentOffer) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
caller()->AddAudioVideoTracks();
callee()->AddAudioVideoTracks();
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
{
MediaExpectations media_expectations;
media_expectations.ExpectBidirectionalAudioAndVideo();
ASSERT_TRUE(ExpectNewFrames(media_expectations));
}
// Renegotiate, rejecting the video m= section.
if (sdp_semantics_ == SdpSemantics::kPlanB) {
caller()->SetGeneratedSdpMunger(
[](cricket::SessionDescription* description) {
for (cricket::ContentInfo& content : description->contents()) {
if (cricket::IsVideoContent(&content)) {
content.rejected = true;
}
}
});
} else {
caller()->GetFirstTransceiverOfType(cricket::MEDIA_TYPE_VIDEO)->Stop();
}
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);
// Wait for some additional audio frames to be received.
{
MediaExpectations media_expectations;
media_expectations.ExpectBidirectionalAudio();
media_expectations.ExpectNoVideo();
ASSERT_TRUE(ExpectNewFrames(media_expectations));
}
}
// Do one offer/answer with audio, another that disables it (rejecting the m=
// section), and another that re-enables it. Regression test for:
// bugs.webrtc.org/6023
TEST_F(PeerConnectionIntegrationTestPlanB, EnableAudioAfterRejecting) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
// Add audio track, do normal offer/answer.
rtc::scoped_refptr<webrtc::AudioTrackInterface> track =
caller()->CreateLocalAudioTrack();
rtc::scoped_refptr<webrtc::RtpSenderInterface> sender =
caller()->pc()->AddTrack(track, {"stream"}).MoveValue();
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
// Remove audio track, and set offer_to_receive_audio to false to cause the
// m= section to be completely disabled, not just "recvonly".
caller()->pc()->RemoveTrack(sender);
PeerConnectionInterface::RTCOfferAnswerOptions options;
options.offer_to_receive_audio = 0;
caller()->SetOfferAnswerOptions(options);
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
// Add the audio track again, expecting negotiation to succeed and frames to
// flow.
sender = caller()->pc()->AddTrack(track, {"stream"}).MoveValue();
options.offer_to_receive_audio = 1;
caller()->SetOfferAnswerOptions(options);
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
MediaExpectations media_expectations;
media_expectations.CalleeExpectsSomeAudio();
EXPECT_TRUE(ExpectNewFrames(media_expectations));
}
// 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_P(PeerConnectionIntegrationTest, EndToEndCallWithoutSsrcOrMsidSignaling) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
// Add audio and video, testing that packets can be demuxed on payload type.
caller()->AddAudioVideoTracks();
callee()->AddAudioVideoTracks();
// Remove SSRCs and MSIDs from the received offer SDP.
callee()->SetReceivedSdpMunger(RemoveSsrcsAndMsids);
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
MediaExpectations media_expectations;
media_expectations.ExpectBidirectionalAudioAndVideo();
ASSERT_TRUE(ExpectNewFrames(media_expectations));
}
// Basic end-to-end test, without SSRC signaling. This means that the track
// was created properly and frames are delivered when the MSIDs are communicated
// with a=msid lines and no a=ssrc lines.
TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
EndToEndCallWithoutSsrcSignaling) {
const char kStreamId[] = "streamId";
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
// Add just audio tracks.
caller()->AddTrack(caller()->CreateLocalAudioTrack(), {kStreamId});
callee()->AddAudioTrack();
// Remove SSRCs from the received offer SDP.
callee()->SetReceivedSdpMunger(RemoveSsrcsAndKeepMsids);
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
MediaExpectations media_expectations;
media_expectations.ExpectBidirectionalAudio();
ASSERT_TRUE(ExpectNewFrames(media_expectations));
}
// Tests that video flows between multiple video tracks when SSRCs are not
// signaled. This exercises the MID RTP header extension which is needed to
// demux the incoming video tracks.
TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
EndToEndCallWithTwoVideoTracksAndNoSignaledSsrc) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
caller()->AddVideoTrack();
caller()->AddVideoTrack();
callee()->AddVideoTrack();
callee()->AddVideoTrack();
caller()->SetReceivedSdpMunger(&RemoveSsrcsAndKeepMsids);
callee()->SetReceivedSdpMunger(&RemoveSsrcsAndKeepMsids);
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
ASSERT_EQ(2u, caller()->pc()->GetReceivers().size());
ASSERT_EQ(2u, callee()->pc()->GetReceivers().size());
// Expect video to be received in both directions on both tracks.
MediaExpectations media_expectations;
media_expectations.ExpectBidirectionalVideo();
EXPECT_TRUE(ExpectNewFrames(media_expectations));
}
// Test that if two video tracks are sent (from caller to callee, in this test),
// they're transmitted correctly end-to-end.
TEST_P(PeerConnectionIntegrationTest, EndToEndCallWithTwoVideoTracks) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
// Add one audio/video stream, and one video-only stream.
caller()->AddAudioVideoTracks();
caller()->AddVideoTrack();
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
ASSERT_EQ(3u, callee()->pc()->GetReceivers().size());
MediaExpectations media_expectations;
media_expectations.CalleeExpectsSomeAudioAndVideo();
ASSERT_TRUE(ExpectNewFrames(media_expectations));
}
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_P(PeerConnectionIntegrationTest,
EndToEndCallWithSpecCompliantMaxBundleOffer) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
caller()->AddAudioVideoTracks();
callee()->AddAudioVideoTracks();
// 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);
MediaExpectations media_expectations;
media_expectations.ExpectBidirectionalAudioAndVideo();
ASSERT_TRUE(ExpectNewFrames(media_expectations));
}
// 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_P(PeerConnectionIntegrationTest, GetAudioOutputLevelStatsWithOldStatsApi) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
// Just add an audio track.
caller()->AddAudioTrack();
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()->OldGetStats()->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_P(PeerConnectionIntegrationTest, GetAudioInputLevelStatsWithOldStatsApi) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
// Just add an audio track.
caller()->AddAudioTrack();
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()->OldGetStats()->AudioInputLevel() > 0,
kMaxWaitForStatsMs);
}
// Test that we can get incoming byte counts from both audio and video tracks.
TEST_P(PeerConnectionIntegrationTest, GetBytesReceivedStatsWithOldStatsApi) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
caller()->AddAudioVideoTracks();
// Do offer/answer, wait for the callee to receive some frames.
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
MediaExpectations media_expectations;
media_expectations.CalleeExpectsSomeAudioAndVideo();
ASSERT_TRUE(ExpectNewFrames(media_expectations));
// Get a handle to the remote tracks created, so they can be used as GetStats
// filters.
for (auto receiver : callee()->pc()->GetReceivers()) {
// We received frames, so we definitely should have nonzero "received bytes"
// stats at this point.
EXPECT_GT(callee()->OldGetStatsForTrack(receiver->track())->BytesReceived(),
0);
}
}
// Test that we can get outgoing byte counts from both audio and video tracks.
TEST_P(PeerConnectionIntegrationTest, GetBytesSentStatsWithOldStatsApi) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
auto audio_track = caller()->CreateLocalAudioTrack();
auto video_track = caller()->CreateLocalVideoTrack();
caller()->AddTrack(audio_track);
caller()->AddTrack(video_track);
// Do offer/answer, wait for the callee to receive some frames.
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
MediaExpectations media_expectations;
media_expectations.CalleeExpectsSomeAudioAndVideo();
ASSERT_TRUE(ExpectNewFrames(media_expectations));
// The callee received frames, so we definitely should have nonzero "sent
// bytes" stats at this point.
EXPECT_GT(caller()->OldGetStatsForTrack(audio_track)->BytesSent(), 0);
EXPECT_GT(caller()->OldGetStatsForTrack(video_track)->BytesSent(), 0);
}
// Test that we can get capture start ntp time.
TEST_P(PeerConnectionIntegrationTest, GetCaptureStartNtpTimeWithOldStatsApi) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
caller()->AddAudioTrack();
callee()->AddAudioTrack();
// Do offer/answer, wait for the callee to receive some frames.
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
// Get the remote audio track created on the receiver, so they can be used as
// GetStats filters.
auto receivers = callee()->pc()->GetReceivers();
ASSERT_EQ(1u, receivers.size());
auto remote_audio_track = receivers[0]->track();
// Get the audio output level stats. Note that the level is not available
// until an RTCP packet has been received.
EXPECT_TRUE_WAIT(
callee()->OldGetStatsForTrack(remote_audio_track)->CaptureStartNtpTime() >
0,
2 * kMaxWaitForFramesMs);
}
// Test that we can get stats (using the new stats implemnetation) for
// unsignaled streams. Meaning when SSRCs/MSIDs aren't signaled explicitly in
// SDP.
TEST_P(PeerConnectionIntegrationTest,
GetStatsForUnsignaledStreamWithNewStatsApi) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
caller()->AddAudioTrack();
// Remove SSRCs and MSIDs from the received offer SDP.
callee()->SetReceivedSdpMunger(RemoveSsrcsAndMsids);
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
MediaExpectations media_expectations;
media_expectations.CalleeExpectsSomeAudio(1);
ASSERT_TRUE(ExpectNewFrames(media_expectations));
// We received a frame, so we should have nonzero "bytes received" stats for
// the unsignaled stream, if stats are working for it.
rtc::scoped_refptr<const webrtc::RTCStatsReport> report =
callee()->NewGetStats();
ASSERT_NE(nullptr, report);
auto inbound_stream_stats =
report->GetStatsOfType<webrtc::RTCInboundRTPStreamStats>();
ASSERT_EQ(1U, inbound_stream_stats.size());
ASSERT_TRUE(inbound_stream_stats[0]->bytes_received.is_defined());
ASSERT_GT(*inbound_stream_stats[0]->bytes_received, 0U);
ASSERT_TRUE(inbound_stream_stats[0]->track_id.is_defined());
}
// Same as above but for the legacy stats implementation.
TEST_P(PeerConnectionIntegrationTest,
GetStatsForUnsignaledStreamWithOldStatsApi) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
caller()->AddAudioTrack();
// Remove SSRCs and MSIDs from the received offer SDP.
callee()->SetReceivedSdpMunger(RemoveSsrcsAndMsids);
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
// Note that, since the old stats implementation associates SSRCs with tracks
// using SDP, when SSRCs aren't signaled in SDP these stats won't have an
// associated track ID. So we can't use the track "selector" argument.
//
// Also, we use "EXPECT_TRUE_WAIT" because the stats collector may decide to
// return cached stats if not enough time has passed since the last update.
EXPECT_TRUE_WAIT(callee()->OldGetStats()->BytesReceived() > 0,
kDefaultTimeout);
}
// Test that we can successfully get the media related stats (audio level
// etc.) for the unsignaled stream.
TEST_P(PeerConnectionIntegrationTest,
GetMediaStatsForUnsignaledStreamWithNewStatsApi) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
caller()->AddAudioVideoTracks();
// Remove SSRCs and MSIDs from the received offer SDP.
callee()->SetReceivedSdpMunger(RemoveSsrcsAndMsids);
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
MediaExpectations media_expectations;
media_expectations.CalleeExpectsSomeAudio(1);
media_expectations.CalleeExpectsSomeVideo(1);
ASSERT_TRUE(ExpectNewFrames(media_expectations));
rtc::scoped_refptr<const webrtc::RTCStatsReport> report =
callee()->NewGetStats();
ASSERT_NE(nullptr, report);
auto media_stats = report->GetStatsOfType<webrtc::RTCMediaStreamTrackStats>();
auto audio_index = FindFirstMediaStatsIndexByKind("audio", media_stats);
ASSERT_GE(audio_index, 0);
EXPECT_TRUE(media_stats[audio_index]->audio_level.is_defined());
}
// Helper for test below.
void ModifySsrcs(cricket::SessionDescription* desc) {
for (ContentInfo& content : desc->contents()) {
for (StreamParams& stream :
content.media_description()->mutable_streams()) {
for (uint32_t& ssrc : stream.ssrcs) {
ssrc = rtc::CreateRandomId();
}
}
}
}
// Test that the "RTCMediaSteamTrackStats" object is updated correctly when
// SSRCs are unsignaled, and the SSRC of the received (audio) stream changes.
// This should result in two "RTCInboundRTPStreamStats", but only one
// "RTCMediaStreamTrackStats", whose counters go up continuously rather than
// being reset to 0 once the SSRC change occurs.
//
// Regression test for this bug:
// https://bugs.chromium.org/p/webrtc/issues/detail?id=8158
//
// The bug causes the track stats to only represent one of the two streams:
// whichever one has the higher SSRC. So with this bug, there was a 50% chance
// that the track stat counters would reset to 0 when the new stream is
// received, and a 50% chance that they'll stop updating (while
// "concealed_samples" continues increasing, due to silence being generated for
// the inactive stream).
TEST_P(PeerConnectionIntegrationTest,
TrackStatsUpdatedCorrectlyWhenUnsignaledSsrcChanges) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
caller()->AddAudioTrack();
// Remove SSRCs and MSIDs from the received offer SDP, simulating an endpoint
// that doesn't signal SSRCs (from the callee's perspective).
callee()->SetReceivedSdpMunger(RemoveSsrcsAndMsids);
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
// Wait for 50 audio frames (500ms of audio) to be received by the callee.
{
MediaExpectations media_expectations;
media_expectations.CalleeExpectsSomeAudio(50);
ASSERT_TRUE(ExpectNewFrames(media_expectations));
}
// Some audio frames were received, so we should have nonzero "samples
// received" for the track.
rtc::scoped_refptr<const webrtc::RTCStatsReport> report =
callee()->NewGetStats();
ASSERT_NE(nullptr, report);
auto track_stats = report->GetStatsOfType<webrtc::RTCMediaStreamTrackStats>();
ASSERT_EQ(1U, track_stats.size());
ASSERT_TRUE(track_stats[0]->total_samples_received.is_defined());
ASSERT_GT(*track_stats[0]->total_samples_received, 0U);
// uint64_t prev_samples_received = *track_stats[0]->total_samples_received;
// Create a new offer and munge it to cause the caller to use a new SSRC.
caller()->SetGeneratedSdpMunger(ModifySsrcs);
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
// Wait for 25 more audio frames (250ms of audio) to be received, from the new
// SSRC.
{
MediaExpectations media_expectations;
media_expectations.CalleeExpectsSomeAudio(25);
ASSERT_TRUE(ExpectNewFrames(media_expectations));
}
report = callee()->NewGetStats();
ASSERT_NE(nullptr, report);
track_stats = report->GetStatsOfType<webrtc::RTCMediaStreamTrackStats>();
ASSERT_EQ(1U, track_stats.size());
ASSERT_TRUE(track_stats[0]->total_samples_received.is_defined());
// The "total samples received" stat should only be greater than it was
// before.
// TODO(deadbeef): Uncomment this assertion once the bug is completely fixed.
// Right now, the new SSRC will cause the counters to reset to 0.
// EXPECT_GT(*track_stats[0]->total_samples_received, prev_samples_received);
// Additionally, the percentage of concealed samples (samples generated to
// conceal packet loss) should be less than 50%. If it's greater, that's a
// good sign that we're seeing stats from the old stream that's no longer
// receiving packets, and is generating concealed samples of silence.
constexpr double kAcceptableConcealedSamplesPercentage = 0.50;
ASSERT_TRUE(track_stats[0]->concealed_samples.is_defined());
EXPECT_LT(*track_stats[0]->concealed_samples,
*track_stats[0]->total_samples_received *
kAcceptableConcealedSamplesPercentage);
// Also ensure that we have two "RTCInboundRTPStreamStats" as expected, as a
// sanity check that the SSRC really changed.
// TODO(deadbeef): This isn't working right now, because we're not returning
// *any* stats for the inactive stream. Uncomment when the bug is completely
// fixed.
// auto inbound_stream_stats =
// report->GetStatsOfType<webrtc::RTCInboundRTPStreamStats>();
// ASSERT_EQ(2U, inbound_stream_stats.size());
}
// Test that DTLS 1.0 is used if both sides only support DTLS 1.0.
TEST_P(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()->AddAudioVideoTracks();
callee()->AddAudioVideoTracks();
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
MediaExpectations media_expectations;
media_expectations.ExpectBidirectionalAudioAndVideo();
ASSERT_TRUE(ExpectNewFrames(media_expectations));
}
// Test getting cipher stats and UMA metrics when DTLS 1.0 is negotiated.
TEST_P(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();
caller()->AddAudioVideoTracks();
callee()->AddAudioVideoTracks();
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(DtlsConnected(), kDefaultTimeout);
EXPECT_TRUE_WAIT(rtc::SSLStreamAdapter::IsAcceptableCipher(
caller()->OldGetStats()->DtlsCipher(), rtc::KT_DEFAULT),
kDefaultTimeout);
EXPECT_EQ_WAIT(rtc::SrtpCryptoSuiteToName(kDefaultSrtpCryptoSuite),
caller()->OldGetStats()->SrtpCipher(), kDefaultTimeout);
// TODO(bugs.webrtc.org/9456): Fix it.
EXPECT_EQ(1, webrtc::metrics::NumEvents(
"WebRTC.PeerConnection.SrtpCryptoSuite.Audio",
kDefaultSrtpCryptoSuite));
}
// Test getting cipher stats and UMA metrics when DTLS 1.2 is negotiated.
TEST_P(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();
caller()->AddAudioVideoTracks();
callee()->AddAudioVideoTracks();
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(DtlsConnected(), kDefaultTimeout);
EXPECT_TRUE_WAIT(rtc::SSLStreamAdapter::IsAcceptableCipher(
caller()->OldGetStats()->DtlsCipher(), rtc::KT_DEFAULT),
kDefaultTimeout);
EXPECT_EQ_WAIT(rtc::SrtpCryptoSuiteToName(kDefaultSrtpCryptoSuite),
caller()->OldGetStats()->SrtpCipher(), kDefaultTimeout);
// TODO(bugs.webrtc.org/9456): Fix it.
EXPECT_EQ(1, webrtc::metrics::NumEvents(
"WebRTC.PeerConnection.SrtpCryptoSuite.Audio",
kDefaultSrtpCryptoSuite));
}
// Test that DTLS 1.0 can be used if the caller supports DTLS 1.2 and the
// callee only supports 1.0.
TEST_P(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()->AddAudioVideoTracks();
callee()->AddAudioVideoTracks();
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
MediaExpectations media_expectations;
media_expectations.ExpectBidirectionalAudioAndVideo();
ASSERT_TRUE(ExpectNewFrames(media_expectations));
}
// Test that DTLS 1.0 can be used if the caller only supports DTLS 1.0 and the
// callee supports 1.2.
TEST_P(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()->AddAudioVideoTracks();
callee()->AddAudioVideoTracks();
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
MediaExpectations media_expectations;
media_expectations.ExpectBidirectionalAudioAndVideo();
ASSERT_TRUE(ExpectNewFrames(media_expectations));
}
// The three tests below verify that "enable_aes128_sha1_32_crypto_cipher"
// works as expected; the cipher should only be used if enabled by both sides.
TEST_P(PeerConnectionIntegrationTest,
Aes128Sha1_32_CipherNotUsedWhenOnlyCallerSupported) {
PeerConnectionFactory::Options caller_options;
caller_options.crypto_options.srtp.enable_aes128_sha1_32_crypto_cipher = true;
PeerConnectionFactory::Options callee_options;
callee_options.crypto_options.srtp.enable_aes128_sha1_32_crypto_cipher =
false;
int expected_cipher_suite = rtc::SRTP_AES128_CM_SHA1_80;
TestNegotiatedCipherSuite(caller_options, callee_options,
expected_cipher_suite);
}
TEST_P(PeerConnectionIntegrationTest,
Aes128Sha1_32_CipherNotUsedWhenOnlyCalleeSupported) {
PeerConnectionFactory::Options caller_options;
caller_options.crypto_options.srtp.enable_aes128_sha1_32_crypto_cipher =
false;
PeerConnectionFactory::Options callee_options;
callee_options.crypto_options.srtp.enable_aes128_sha1_32_crypto_cipher = true;
int expected_cipher_suite = rtc::SRTP_AES128_CM_SHA1_80;
TestNegotiatedCipherSuite(caller_options, callee_options,
expected_cipher_suite);
}
TEST_P(PeerConnectionIntegrationTest, Aes128Sha1_32_CipherUsedWhenSupported) {
PeerConnectionFactory::Options caller_options;
caller_options.crypto_options.srtp.enable_aes128_sha1_32_crypto_cipher = true;
PeerConnectionFactory::Options callee_options;
callee_options.crypto_options.srtp.enable_aes128_sha1_32_crypto_cipher = true;
int expected_cipher_suite = rtc::SRTP_AES128_CM_SHA1_32;
TestNegotiatedCipherSuite(caller_options, callee_options,
expected_cipher_suite);
}
// Test that a non-GCM cipher is used if both sides only support non-GCM.
TEST_P(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_P(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_P(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_P(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);
}
// Verify that media can be transmitted end-to-end when GCM crypto suites are
// enabled. Note that the above tests, such as GcmCipherUsedWhenGcmSupported,
// only verify that a GCM cipher is negotiated, and not necessarily that SRTP
// works with it.
TEST_P(PeerConnectionIntegrationTest, EndToEndCallWithGcmCipher) {
PeerConnectionFactory::Options gcm_options;
gcm_options.crypto_options.srtp.enable_gcm_crypto_suites = true;
ASSERT_TRUE(
CreatePeerConnectionWrappersWithOptions(gcm_options, gcm_options));
ConnectFakeSignaling();
// Do normal offer/answer and wait for some frames to be received in each
// direction.
caller()->AddAudioVideoTracks();
callee()->AddAudioVideoTracks();
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
MediaExpectations media_expectations;