Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 14:04:28 +01:00
parent 81b91f4139
commit f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions

View file

@ -0,0 +1,11 @@
hbos@webrtc.org
hta@webrtc.org
perkj@webrtc.org
tommi@webrtc.org
deadbeef@webrtc.org
orphis@webrtc.org
# Adding features via SDP munging requires approval from SDP owners
per-file webrtc_sdp.cc = set noparent
per-file webrtc_sdp.cc = hta@webrtc.org
per-file webrtc_sdp.cc = hbos@webrtc.org

View file

@ -0,0 +1,347 @@
/*
* Copyright 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/audio_rtp_receiver.h"
#include <stddef.h>
#include <string>
#include <utility>
#include <vector>
#include "api/sequence_checker.h"
#include "pc/audio_track.h"
#include "pc/media_stream_track_proxy.h"
#include "rtc_base/checks.h"
namespace webrtc {
AudioRtpReceiver::AudioRtpReceiver(
rtc::Thread* worker_thread,
std::string receiver_id,
std::vector<std::string> stream_ids,
bool is_unified_plan,
cricket::VoiceMediaReceiveChannelInterface* voice_channel /*= nullptr*/)
: AudioRtpReceiver(worker_thread,
receiver_id,
CreateStreamsFromIds(std::move(stream_ids)),
is_unified_plan,
voice_channel) {}
AudioRtpReceiver::AudioRtpReceiver(
rtc::Thread* worker_thread,
const std::string& receiver_id,
const std::vector<rtc::scoped_refptr<MediaStreamInterface>>& streams,
bool is_unified_plan,
cricket::VoiceMediaReceiveChannelInterface* voice_channel /*= nullptr*/)
: worker_thread_(worker_thread),
id_(receiver_id),
source_(rtc::make_ref_counted<RemoteAudioSource>(
worker_thread,
is_unified_plan
? RemoteAudioSource::OnAudioChannelGoneAction::kSurvive
: RemoteAudioSource::OnAudioChannelGoneAction::kEnd)),
track_(AudioTrackProxyWithInternal<AudioTrack>::Create(
rtc::Thread::Current(),
AudioTrack::Create(receiver_id, source_))),
media_channel_(voice_channel),
cached_track_enabled_(track_->internal()->enabled()),
attachment_id_(GenerateUniqueId()),
worker_thread_safety_(PendingTaskSafetyFlag::CreateDetachedInactive()) {
RTC_DCHECK(worker_thread_);
RTC_DCHECK(track_->GetSource()->remote());
track_->RegisterObserver(this);
track_->GetSource()->RegisterAudioObserver(this);
SetStreams(streams);
}
AudioRtpReceiver::~AudioRtpReceiver() {
RTC_DCHECK_RUN_ON(&signaling_thread_checker_);
RTC_DCHECK(!media_channel_);
track_->GetSource()->UnregisterAudioObserver(this);
track_->UnregisterObserver(this);
}
void AudioRtpReceiver::OnChanged() {
RTC_DCHECK_RUN_ON(&signaling_thread_checker_);
const bool enabled = track_->internal()->enabled();
if (cached_track_enabled_ == enabled)
return;
cached_track_enabled_ = enabled;
worker_thread_->PostTask(SafeTask(worker_thread_safety_, [this, enabled]() {
RTC_DCHECK_RUN_ON(worker_thread_);
Reconfigure(enabled);
}));
}
void AudioRtpReceiver::SetOutputVolume_w(double volume) {
RTC_DCHECK_RUN_ON(worker_thread_);
RTC_DCHECK_GE(volume, 0.0);
RTC_DCHECK_LE(volume, 10.0);
if (!media_channel_)
return;
signaled_ssrc_ ? media_channel_->SetOutputVolume(*signaled_ssrc_, volume)
: media_channel_->SetDefaultOutputVolume(volume);
}
void AudioRtpReceiver::OnSetVolume(double volume) {
RTC_DCHECK_RUN_ON(&signaling_thread_checker_);
RTC_DCHECK_GE(volume, 0);
RTC_DCHECK_LE(volume, 10);
bool track_enabled = track_->internal()->enabled();
worker_thread_->BlockingCall([&]() {
RTC_DCHECK_RUN_ON(worker_thread_);
// Update the cached_volume_ even when stopped, to allow clients to set
// the volume before starting/restarting, eg see crbug.com/1272566.
cached_volume_ = volume;
// When the track is disabled, the volume of the source, which is the
// corresponding WebRtc Voice Engine channel will be 0. So we do not
// allow setting the volume to the source when the track is disabled.
if (track_enabled)
SetOutputVolume_w(volume);
});
}
rtc::scoped_refptr<DtlsTransportInterface> AudioRtpReceiver::dtls_transport()
const {
RTC_DCHECK_RUN_ON(&signaling_thread_checker_);
return dtls_transport_;
}
std::vector<std::string> AudioRtpReceiver::stream_ids() const {
RTC_DCHECK_RUN_ON(&signaling_thread_checker_);
std::vector<std::string> stream_ids(streams_.size());
for (size_t i = 0; i < streams_.size(); ++i)
stream_ids[i] = streams_[i]->id();
return stream_ids;
}
std::vector<rtc::scoped_refptr<MediaStreamInterface>>
AudioRtpReceiver::streams() const {
RTC_DCHECK_RUN_ON(&signaling_thread_checker_);
return streams_;
}
RtpParameters AudioRtpReceiver::GetParameters() const {
RTC_DCHECK_RUN_ON(worker_thread_);
if (!media_channel_)
return RtpParameters();
auto current_ssrc = ssrc();
return current_ssrc.has_value()
? media_channel_->GetRtpReceiverParameters(current_ssrc.value())
: media_channel_->GetDefaultRtpReceiveParameters();
}
void AudioRtpReceiver::SetFrameDecryptor(
rtc::scoped_refptr<FrameDecryptorInterface> frame_decryptor) {
RTC_DCHECK_RUN_ON(worker_thread_);
frame_decryptor_ = std::move(frame_decryptor);
// Special Case: Set the frame decryptor to any value on any existing channel.
if (media_channel_ && signaled_ssrc_) {
media_channel_->SetFrameDecryptor(*signaled_ssrc_, frame_decryptor_);
}
}
rtc::scoped_refptr<FrameDecryptorInterface>
AudioRtpReceiver::GetFrameDecryptor() const {
RTC_DCHECK_RUN_ON(worker_thread_);
return frame_decryptor_;
}
void AudioRtpReceiver::Stop() {
RTC_DCHECK_RUN_ON(&signaling_thread_checker_);
source_->SetState(MediaSourceInterface::kEnded);
track_->internal()->set_ended();
}
void AudioRtpReceiver::RestartMediaChannel(absl::optional<uint32_t> ssrc) {
RTC_DCHECK_RUN_ON(&signaling_thread_checker_);
bool enabled = track_->internal()->enabled();
MediaSourceInterface::SourceState state = source_->state();
worker_thread_->BlockingCall([&]() {
RTC_DCHECK_RUN_ON(worker_thread_);
RestartMediaChannel_w(std::move(ssrc), enabled, state);
});
source_->SetState(MediaSourceInterface::kLive);
}
void AudioRtpReceiver::RestartMediaChannel_w(
absl::optional<uint32_t> ssrc,
bool track_enabled,
MediaSourceInterface::SourceState state) {
RTC_DCHECK_RUN_ON(worker_thread_);
if (!media_channel_)
return; // Can't restart.
// Make sure the safety flag is marked as `alive` for cases where the media
// channel was provided via the ctor and not an explicit call to
// SetMediaChannel.
worker_thread_safety_->SetAlive();
if (state != MediaSourceInterface::kInitializing) {
if (signaled_ssrc_ == ssrc)
return;
source_->Stop(media_channel_, signaled_ssrc_);
}
signaled_ssrc_ = std::move(ssrc);
source_->Start(media_channel_, signaled_ssrc_);
if (signaled_ssrc_) {
media_channel_->SetBaseMinimumPlayoutDelayMs(*signaled_ssrc_,
delay_.GetMs());
}
Reconfigure(track_enabled);
}
void AudioRtpReceiver::SetupMediaChannel(uint32_t ssrc) {
RTC_DCHECK_RUN_ON(&signaling_thread_checker_);
RestartMediaChannel(ssrc);
}
void AudioRtpReceiver::SetupUnsignaledMediaChannel() {
RTC_DCHECK_RUN_ON(&signaling_thread_checker_);
RestartMediaChannel(absl::nullopt);
}
absl::optional<uint32_t> AudioRtpReceiver::ssrc() const {
RTC_DCHECK_RUN_ON(worker_thread_);
if (!signaled_ssrc_.has_value() && media_channel_) {
return media_channel_->GetUnsignaledSsrc();
}
return signaled_ssrc_;
}
void AudioRtpReceiver::set_stream_ids(std::vector<std::string> stream_ids) {
RTC_DCHECK_RUN_ON(&signaling_thread_checker_);
SetStreams(CreateStreamsFromIds(std::move(stream_ids)));
}
void AudioRtpReceiver::set_transport(
rtc::scoped_refptr<DtlsTransportInterface> dtls_transport) {
RTC_DCHECK_RUN_ON(&signaling_thread_checker_);
dtls_transport_ = std::move(dtls_transport);
}
void AudioRtpReceiver::SetStreams(
const std::vector<rtc::scoped_refptr<MediaStreamInterface>>& streams) {
RTC_DCHECK_RUN_ON(&signaling_thread_checker_);
// Remove remote track from any streams that are going away.
for (const auto& existing_stream : streams_) {
bool removed = true;
for (const auto& stream : streams) {
if (existing_stream->id() == stream->id()) {
RTC_DCHECK_EQ(existing_stream.get(), stream.get());
removed = false;
break;
}
}
if (removed) {
existing_stream->RemoveTrack(audio_track());
}
}
// Add remote track to any streams that are new.
for (const auto& stream : streams) {
bool added = true;
for (const auto& existing_stream : streams_) {
if (stream->id() == existing_stream->id()) {
RTC_DCHECK_EQ(stream.get(), existing_stream.get());
added = false;
break;
}
}
if (added) {
stream->AddTrack(audio_track());
}
}
streams_ = streams;
}
std::vector<RtpSource> AudioRtpReceiver::GetSources() const {
RTC_DCHECK_RUN_ON(worker_thread_);
auto current_ssrc = ssrc();
if (!media_channel_ || !current_ssrc.has_value()) {
return {};
}
return media_channel_->GetSources(current_ssrc.value());
}
void AudioRtpReceiver::SetDepacketizerToDecoderFrameTransformer(
rtc::scoped_refptr<FrameTransformerInterface> frame_transformer) {
RTC_DCHECK_RUN_ON(worker_thread_);
if (media_channel_) {
media_channel_->SetDepacketizerToDecoderFrameTransformer(
signaled_ssrc_.value_or(0), frame_transformer);
}
frame_transformer_ = std::move(frame_transformer);
}
void AudioRtpReceiver::Reconfigure(bool track_enabled) {
RTC_DCHECK_RUN_ON(worker_thread_);
RTC_DCHECK(media_channel_);
SetOutputVolume_w(track_enabled ? cached_volume_ : 0);
if (signaled_ssrc_ && frame_decryptor_) {
// Reattach the frame decryptor if we were reconfigured.
media_channel_->SetFrameDecryptor(*signaled_ssrc_, frame_decryptor_);
}
if (frame_transformer_) {
media_channel_->SetDepacketizerToDecoderFrameTransformer(
signaled_ssrc_.value_or(0), frame_transformer_);
}
}
void AudioRtpReceiver::SetObserver(RtpReceiverObserverInterface* observer) {
RTC_DCHECK_RUN_ON(&signaling_thread_checker_);
observer_ = observer;
// Deliver any notifications the observer may have missed by being set late.
if (received_first_packet_ && observer_) {
observer_->OnFirstPacketReceived(media_type());
}
}
void AudioRtpReceiver::SetJitterBufferMinimumDelay(
absl::optional<double> delay_seconds) {
RTC_DCHECK_RUN_ON(worker_thread_);
delay_.Set(delay_seconds);
if (media_channel_ && signaled_ssrc_)
media_channel_->SetBaseMinimumPlayoutDelayMs(*signaled_ssrc_,
delay_.GetMs());
}
void AudioRtpReceiver::SetMediaChannel(
cricket::MediaReceiveChannelInterface* media_channel) {
RTC_DCHECK_RUN_ON(worker_thread_);
RTC_DCHECK(media_channel == nullptr ||
media_channel->media_type() == media_type());
if (!media_channel && media_channel_)
SetOutputVolume_w(0.0);
media_channel ? worker_thread_safety_->SetAlive()
: worker_thread_safety_->SetNotAlive();
media_channel_ =
static_cast<cricket::VoiceMediaReceiveChannelInterface*>(media_channel);
}
void AudioRtpReceiver::NotifyFirstPacketReceived() {
RTC_DCHECK_RUN_ON(&signaling_thread_checker_);
if (observer_) {
observer_->OnFirstPacketReceived(media_type());
}
received_first_packet_ = true;
}
} // namespace webrtc

View file

@ -0,0 +1,164 @@
/*
* Copyright 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef PC_AUDIO_RTP_RECEIVER_H_
#define PC_AUDIO_RTP_RECEIVER_H_
#include <stdint.h>
#include <string>
#include <vector>
#include "absl/types/optional.h"
#include "api/crypto/frame_decryptor_interface.h"
#include "api/dtls_transport_interface.h"
#include "api/frame_transformer_interface.h"
#include "api/media_stream_interface.h"
#include "api/media_types.h"
#include "api/rtp_parameters.h"
#include "api/rtp_receiver_interface.h"
#include "api/scoped_refptr.h"
#include "api/sequence_checker.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "api/transport/rtp/rtp_source.h"
#include "media/base/media_channel.h"
#include "pc/audio_track.h"
#include "pc/jitter_buffer_delay.h"
#include "pc/media_stream_track_proxy.h"
#include "pc/remote_audio_source.h"
#include "pc/rtp_receiver.h"
#include "rtc_base/system/no_unique_address.h"
#include "rtc_base/thread.h"
#include "rtc_base/thread_annotations.h"
namespace webrtc {
class AudioRtpReceiver : public ObserverInterface,
public AudioSourceInterface::AudioObserver,
public RtpReceiverInternal {
public:
// The constructor supports optionally passing the voice channel to the
// instance at construction time without having to call `SetMediaChannel()`
// on the worker thread straight after construction.
// However, when using that, the assumption is that right after construction,
// a call to either `SetupUnsignaledMediaChannel` or `SetupMediaChannel`
// will be made, which will internally start the source on the worker thread.
AudioRtpReceiver(
rtc::Thread* worker_thread,
std::string receiver_id,
std::vector<std::string> stream_ids,
bool is_unified_plan,
cricket::VoiceMediaReceiveChannelInterface* voice_channel = nullptr);
// TODO(https://crbug.com/webrtc/9480): Remove this when streams() is removed.
AudioRtpReceiver(
rtc::Thread* worker_thread,
const std::string& receiver_id,
const std::vector<rtc::scoped_refptr<MediaStreamInterface>>& streams,
bool is_unified_plan,
cricket::VoiceMediaReceiveChannelInterface* media_channel = nullptr);
virtual ~AudioRtpReceiver();
// ObserverInterface implementation
void OnChanged() override;
// AudioSourceInterface::AudioObserver implementation
void OnSetVolume(double volume) override;
rtc::scoped_refptr<AudioTrackInterface> audio_track() const { return track_; }
// RtpReceiverInterface implementation
rtc::scoped_refptr<MediaStreamTrackInterface> track() const override {
return track_;
}
rtc::scoped_refptr<DtlsTransportInterface> dtls_transport() const override;
std::vector<std::string> stream_ids() const override;
std::vector<rtc::scoped_refptr<MediaStreamInterface>> streams()
const override;
cricket::MediaType media_type() const override {
return cricket::MEDIA_TYPE_AUDIO;
}
std::string id() const override { return id_; }
RtpParameters GetParameters() const override;
void SetFrameDecryptor(
rtc::scoped_refptr<FrameDecryptorInterface> frame_decryptor) override;
rtc::scoped_refptr<FrameDecryptorInterface> GetFrameDecryptor()
const override;
// RtpReceiverInternal implementation.
void Stop() override;
void SetupMediaChannel(uint32_t ssrc) override;
void SetupUnsignaledMediaChannel() override;
absl::optional<uint32_t> ssrc() const override;
void NotifyFirstPacketReceived() override;
void set_stream_ids(std::vector<std::string> stream_ids) override;
void set_transport(
rtc::scoped_refptr<DtlsTransportInterface> dtls_transport) override;
void SetStreams(const std::vector<rtc::scoped_refptr<MediaStreamInterface>>&
streams) override;
void SetObserver(RtpReceiverObserverInterface* observer) override;
void SetJitterBufferMinimumDelay(
absl::optional<double> delay_seconds) override;
void SetMediaChannel(
cricket::MediaReceiveChannelInterface* media_channel) override;
std::vector<RtpSource> GetSources() const override;
int AttachmentId() const override { return attachment_id_; }
void SetDepacketizerToDecoderFrameTransformer(
rtc::scoped_refptr<FrameTransformerInterface> frame_transformer) override;
private:
void RestartMediaChannel(absl::optional<uint32_t> ssrc)
RTC_RUN_ON(&signaling_thread_checker_);
void RestartMediaChannel_w(absl::optional<uint32_t> ssrc,
bool track_enabled,
MediaSourceInterface::SourceState state)
RTC_RUN_ON(worker_thread_);
void Reconfigure(bool track_enabled) RTC_RUN_ON(worker_thread_);
void SetOutputVolume_w(double volume) RTC_RUN_ON(worker_thread_);
RTC_NO_UNIQUE_ADDRESS SequenceChecker signaling_thread_checker_;
rtc::Thread* const worker_thread_;
const std::string id_;
const rtc::scoped_refptr<RemoteAudioSource> source_;
const rtc::scoped_refptr<AudioTrackProxyWithInternal<AudioTrack>> track_;
cricket::VoiceMediaReceiveChannelInterface* media_channel_
RTC_GUARDED_BY(worker_thread_) = nullptr;
absl::optional<uint32_t> signaled_ssrc_ RTC_GUARDED_BY(worker_thread_);
std::vector<rtc::scoped_refptr<MediaStreamInterface>> streams_
RTC_GUARDED_BY(&signaling_thread_checker_);
bool cached_track_enabled_ RTC_GUARDED_BY(&signaling_thread_checker_);
double cached_volume_ RTC_GUARDED_BY(worker_thread_) = 1.0;
RtpReceiverObserverInterface* observer_
RTC_GUARDED_BY(&signaling_thread_checker_) = nullptr;
bool received_first_packet_ RTC_GUARDED_BY(&signaling_thread_checker_) =
false;
const int attachment_id_;
rtc::scoped_refptr<FrameDecryptorInterface> frame_decryptor_
RTC_GUARDED_BY(worker_thread_);
rtc::scoped_refptr<DtlsTransportInterface> dtls_transport_
RTC_GUARDED_BY(&signaling_thread_checker_);
// Stores and updates the playout delay. Handles caching cases if
// `SetJitterBufferMinimumDelay` is called before start.
JitterBufferDelay delay_ RTC_GUARDED_BY(worker_thread_);
rtc::scoped_refptr<FrameTransformerInterface> frame_transformer_
RTC_GUARDED_BY(worker_thread_);
const rtc::scoped_refptr<PendingTaskSafetyFlag> worker_thread_safety_;
};
} // namespace webrtc
#endif // PC_AUDIO_RTP_RECEIVER_H_

View file

@ -0,0 +1,127 @@
/*
* Copyright 2021 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/audio_rtp_receiver.h"
#include <atomic>
#include "pc/test/mock_voice_media_receive_channel_interface.h"
#include "rtc_base/gunit.h"
#include "rtc_base/thread.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/run_loop.h"
using ::testing::_;
using ::testing::InvokeWithoutArgs;
using ::testing::Mock;
static const int kTimeOut = 100;
static const double kDefaultVolume = 1;
static const double kVolume = 3.7;
static const double kVolumeMuted = 0.0;
static const uint32_t kSsrc = 3;
namespace webrtc {
class AudioRtpReceiverTest : public ::testing::Test {
protected:
AudioRtpReceiverTest()
: worker_(rtc::Thread::Current()),
receiver_(
rtc::make_ref_counted<AudioRtpReceiver>(worker_,
std::string(),
std::vector<std::string>(),
false)) {
EXPECT_CALL(receive_channel_, SetRawAudioSink(kSsrc, _));
EXPECT_CALL(receive_channel_, SetBaseMinimumPlayoutDelayMs(kSsrc, _));
}
~AudioRtpReceiverTest() {
EXPECT_CALL(receive_channel_, SetOutputVolume(kSsrc, kVolumeMuted));
receiver_->SetMediaChannel(nullptr);
}
rtc::AutoThread main_thread_;
rtc::Thread* worker_;
rtc::scoped_refptr<AudioRtpReceiver> receiver_;
cricket::MockVoiceMediaReceiveChannelInterface receive_channel_;
};
TEST_F(AudioRtpReceiverTest, SetOutputVolumeIsCalled) {
std::atomic_int set_volume_calls(0);
EXPECT_CALL(receive_channel_, SetOutputVolume(kSsrc, kDefaultVolume))
.WillOnce(InvokeWithoutArgs([&] {
set_volume_calls++;
return true;
}));
receiver_->track();
receiver_->track()->set_enabled(true);
receiver_->SetMediaChannel(&receive_channel_);
EXPECT_CALL(receive_channel_, SetDefaultRawAudioSink(_)).Times(0);
receiver_->SetupMediaChannel(kSsrc);
EXPECT_CALL(receive_channel_, SetOutputVolume(kSsrc, kVolume))
.WillOnce(InvokeWithoutArgs([&] {
set_volume_calls++;
return true;
}));
receiver_->OnSetVolume(kVolume);
EXPECT_TRUE_WAIT(set_volume_calls == 2, kTimeOut);
}
TEST_F(AudioRtpReceiverTest, VolumesSetBeforeStartingAreRespected) {
// Set the volume before setting the media channel. It should still be used
// as the initial volume.
receiver_->OnSetVolume(kVolume);
receiver_->track()->set_enabled(true);
receiver_->SetMediaChannel(&receive_channel_);
// The previosly set initial volume should be propagated to the provided
// media_channel_ as soon as SetupMediaChannel is called.
EXPECT_CALL(receive_channel_, SetOutputVolume(kSsrc, kVolume));
receiver_->SetupMediaChannel(kSsrc);
}
// Tests that OnChanged notifications are processed correctly on the worker
// thread when a media channel pointer is passed to the receiver via the
// constructor.
TEST(AudioRtpReceiver, OnChangedNotificationsAfterConstruction) {
test::RunLoop loop;
auto* thread = rtc::Thread::Current(); // Points to loop's thread.
cricket::MockVoiceMediaReceiveChannelInterface receive_channel;
auto receiver = rtc::make_ref_counted<AudioRtpReceiver>(
thread, std::string(), std::vector<std::string>(), true,
&receive_channel);
EXPECT_CALL(receive_channel, SetDefaultRawAudioSink(_)).Times(1);
EXPECT_CALL(receive_channel, SetDefaultOutputVolume(kDefaultVolume)).Times(1);
receiver->SetupUnsignaledMediaChannel();
loop.Flush();
// Mark the track as disabled.
receiver->track()->set_enabled(false);
// When the track was marked as disabled, an async notification was queued
// for the worker thread. This notification should trigger the volume
// of the media channel to be set to kVolumeMuted.
// Flush the worker thread, but set the expectation first for the call.
EXPECT_CALL(receive_channel, SetDefaultOutputVolume(kVolumeMuted)).Times(1);
loop.Flush();
EXPECT_CALL(receive_channel, SetDefaultOutputVolume(kVolumeMuted)).Times(1);
receiver->SetMediaChannel(nullptr);
}
} // namespace webrtc

View file

@ -0,0 +1,70 @@
/*
* Copyright 2011 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/audio_track.h"
#include "rtc_base/checks.h"
namespace webrtc {
// static
rtc::scoped_refptr<AudioTrack> AudioTrack::Create(
absl::string_view id,
const rtc::scoped_refptr<AudioSourceInterface>& source) {
return rtc::make_ref_counted<AudioTrack>(id, source);
}
AudioTrack::AudioTrack(absl::string_view label,
const rtc::scoped_refptr<AudioSourceInterface>& source)
: MediaStreamTrack<AudioTrackInterface>(label), audio_source_(source) {
if (audio_source_) {
audio_source_->RegisterObserver(this);
OnChanged();
}
}
AudioTrack::~AudioTrack() {
RTC_DCHECK_RUN_ON(&signaling_thread_checker_);
set_state(MediaStreamTrackInterface::kEnded);
if (audio_source_)
audio_source_->UnregisterObserver(this);
}
std::string AudioTrack::kind() const {
return kAudioKind;
}
AudioSourceInterface* AudioTrack::GetSource() const {
// Callable from any thread.
return audio_source_.get();
}
void AudioTrack::AddSink(AudioTrackSinkInterface* sink) {
RTC_DCHECK_RUN_ON(&signaling_thread_checker_);
if (audio_source_)
audio_source_->AddSink(sink);
}
void AudioTrack::RemoveSink(AudioTrackSinkInterface* sink) {
RTC_DCHECK_RUN_ON(&signaling_thread_checker_);
if (audio_source_)
audio_source_->RemoveSink(sink);
}
void AudioTrack::OnChanged() {
RTC_DCHECK_RUN_ON(&signaling_thread_checker_);
if (audio_source_->state() == MediaSourceInterface::kEnded) {
set_state(kEnded);
} else {
set_state(kLive);
}
}
} // namespace webrtc

View file

@ -0,0 +1,66 @@
/*
* Copyright 2011 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef PC_AUDIO_TRACK_H_
#define PC_AUDIO_TRACK_H_
#include <string>
#include "api/media_stream_interface.h"
#include "api/media_stream_track.h"
#include "api/scoped_refptr.h"
#include "api/sequence_checker.h"
#include "rtc_base/system/no_unique_address.h"
namespace webrtc {
// TODO(tommi): Instead of inheriting from `MediaStreamTrack<>`, implement the
// properties directly in this class. `MediaStreamTrack` doesn't guard against
// conflicting access, so we'd need to override those methods anyway in this
// class in order to make sure things are correctly checked.
class AudioTrack : public MediaStreamTrack<AudioTrackInterface>,
public ObserverInterface {
protected:
// Protected ctor to force use of factory method.
AudioTrack(absl::string_view label,
const rtc::scoped_refptr<AudioSourceInterface>& source);
AudioTrack() = delete;
AudioTrack(const AudioTrack&) = delete;
AudioTrack& operator=(const AudioTrack&) = delete;
~AudioTrack() override;
public:
static rtc::scoped_refptr<AudioTrack> Create(
absl::string_view id,
const rtc::scoped_refptr<AudioSourceInterface>& source);
// MediaStreamTrack implementation.
std::string kind() const override;
// AudioTrackInterface implementation.
AudioSourceInterface* GetSource() const override;
void AddSink(AudioTrackSinkInterface* sink) override;
void RemoveSink(AudioTrackSinkInterface* sink) override;
private:
// ObserverInterface implementation.
void OnChanged() override;
private:
const rtc::scoped_refptr<AudioSourceInterface> audio_source_;
RTC_NO_UNIQUE_ADDRESS SequenceChecker signaling_thread_checker_;
};
} // namespace webrtc
#endif // PC_AUDIO_TRACK_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,507 @@
/*
* Copyright 2004 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef PC_CHANNEL_H_
#define PC_CHANNEL_H_
#include <stdint.h>
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "api/crypto/crypto_options.h"
#include "api/jsep.h"
#include "api/media_types.h"
#include "api/rtp_parameters.h"
#include "api/rtp_transceiver_direction.h"
#include "api/scoped_refptr.h"
#include "api/sequence_checker.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "call/rtp_demuxer.h"
#include "call/rtp_packet_sink_interface.h"
#include "media/base/media_channel.h"
#include "media/base/media_channel_impl.h"
#include "media/base/stream_params.h"
#include "modules/rtp_rtcp/source/rtp_packet_received.h"
#include "pc/channel_interface.h"
#include "pc/rtp_transport_internal.h"
#include "pc/session_description.h"
#include "rtc_base/async_packet_socket.h"
#include "rtc_base/checks.h"
#include "rtc_base/containers/flat_set.h"
#include "rtc_base/copy_on_write_buffer.h"
#include "rtc_base/network/sent_packet.h"
#include "rtc_base/network_route.h"
#include "rtc_base/socket.h"
#include "rtc_base/thread.h"
#include "rtc_base/thread_annotations.h"
#include "rtc_base/unique_id_generator.h"
namespace cricket {
// BaseChannel contains logic common to voice and video, including enable,
// marshaling calls to a worker and network threads, and connection and media
// monitors.
//
// BaseChannel assumes signaling and other threads are allowed to make
// synchronous calls to the worker thread, the worker thread makes synchronous
// calls only to the network thread, and the network thread can't be blocked by
// other threads.
// All methods with _n suffix must be called on network thread,
// methods with _w suffix on worker thread
// and methods with _s suffix on signaling thread.
// Network and worker threads may be the same thread.
//
class VideoChannel;
class VoiceChannel;
class BaseChannel : public ChannelInterface,
// TODO(tommi): Consider implementing these interfaces
// via composition.
public MediaChannelNetworkInterface,
public webrtc::RtpPacketSinkInterface {
public:
// If `srtp_required` is true, the channel will not send or receive any
// RTP/RTCP packets without using SRTP (either using SDES or DTLS-SRTP).
// The BaseChannel does not own the UniqueRandomIdGenerator so it is the
// responsibility of the user to ensure it outlives this object.
// TODO(zhihuang:) Create a BaseChannel::Config struct for the parameter lists
// which will make it easier to change the constructor.
// Constructor for use when the MediaChannels are split
BaseChannel(
webrtc::TaskQueueBase* worker_thread,
rtc::Thread* network_thread,
webrtc::TaskQueueBase* signaling_thread,
std::unique_ptr<MediaSendChannelInterface> media_send_channel,
std::unique_ptr<MediaReceiveChannelInterface> media_receive_channel,
absl::string_view mid,
bool srtp_required,
webrtc::CryptoOptions crypto_options,
rtc::UniqueRandomIdGenerator* ssrc_generator);
virtual ~BaseChannel();
webrtc::TaskQueueBase* worker_thread() const { return worker_thread_; }
rtc::Thread* network_thread() const { return network_thread_; }
const std::string& mid() const override { return demuxer_criteria_.mid(); }
// TODO(deadbeef): This is redundant; remove this.
absl::string_view transport_name() const override {
RTC_DCHECK_RUN_ON(network_thread());
if (rtp_transport_)
return rtp_transport_->transport_name();
return "";
}
// This function returns true if using SRTP (DTLS-based keying or SDES).
bool srtp_active() const {
RTC_DCHECK_RUN_ON(network_thread());
return rtp_transport_ && rtp_transport_->IsSrtpActive();
}
// Set an RTP level transport which could be an RtpTransport without
// encryption, an SrtpTransport for SDES or a DtlsSrtpTransport for DTLS-SRTP.
// This can be called from any thread and it hops to the network thread
// internally. It would replace the `SetTransports` and its variants.
bool SetRtpTransport(webrtc::RtpTransportInternal* rtp_transport) override;
webrtc::RtpTransportInternal* rtp_transport() const {
RTC_DCHECK_RUN_ON(network_thread());
return rtp_transport_;
}
// Channel control
bool SetLocalContent(const MediaContentDescription* content,
webrtc::SdpType type,
std::string& error_desc) override;
bool SetRemoteContent(const MediaContentDescription* content,
webrtc::SdpType type,
std::string& error_desc) override;
// Controls whether this channel will receive packets on the basis of
// matching payload type alone. This is needed for legacy endpoints that
// don't signal SSRCs or use MID/RID, but doesn't make sense if there is
// more than channel of specific media type, As that creates an ambiguity.
//
// This method will also remove any existing streams that were bound to this
// channel on the basis of payload type, since one of these streams might
// actually belong to a new channel. See: crbug.com/webrtc/11477
bool SetPayloadTypeDemuxingEnabled(bool enabled) override;
void Enable(bool enable) override;
const std::vector<StreamParams>& local_streams() const override {
return local_streams_;
}
const std::vector<StreamParams>& remote_streams() const override {
return remote_streams_;
}
// Used for latency measurements.
void SetFirstPacketReceivedCallback(std::function<void()> callback) override;
// From RtpTransport - public for testing only
void OnTransportReadyToSend(bool ready);
// Only public for unit tests. Otherwise, consider protected.
int SetOption(SocketType type, rtc::Socket::Option o, int val) override;
// RtpPacketSinkInterface overrides.
void OnRtpPacket(const webrtc::RtpPacketReceived& packet) override;
VideoMediaSendChannelInterface* video_media_send_channel() override {
RTC_CHECK(false) << "Attempt to fetch video channel from non-video";
return nullptr;
}
VoiceMediaSendChannelInterface* voice_media_send_channel() override {
RTC_CHECK(false) << "Attempt to fetch voice channel from non-voice";
return nullptr;
}
VideoMediaReceiveChannelInterface* video_media_receive_channel() override {
RTC_CHECK(false) << "Attempt to fetch video channel from non-video";
return nullptr;
}
VoiceMediaReceiveChannelInterface* voice_media_receive_channel() override {
RTC_CHECK(false) << "Attempt to fetch voice channel from non-voice";
return nullptr;
}
protected:
void set_local_content_direction(webrtc::RtpTransceiverDirection direction)
RTC_RUN_ON(worker_thread()) {
local_content_direction_ = direction;
}
webrtc::RtpTransceiverDirection local_content_direction() const
RTC_RUN_ON(worker_thread()) {
return local_content_direction_;
}
void set_remote_content_direction(webrtc::RtpTransceiverDirection direction)
RTC_RUN_ON(worker_thread()) {
remote_content_direction_ = direction;
}
webrtc::RtpTransceiverDirection remote_content_direction() const
RTC_RUN_ON(worker_thread()) {
return remote_content_direction_;
}
webrtc::RtpExtension::Filter extensions_filter() const {
return extensions_filter_;
}
bool network_initialized() RTC_RUN_ON(network_thread()) {
return media_send_channel()->HasNetworkInterface();
}
bool enabled() const RTC_RUN_ON(worker_thread()) { return enabled_; }
webrtc::TaskQueueBase* signaling_thread() const { return signaling_thread_; }
// Call to verify that:
// * The required content description directions have been set.
// * The channel is enabled.
// * The SRTP filter is active if it's needed.
// * The transport has been writable before, meaning it should be at least
// possible to succeed in sending a packet.
//
// When any of these properties change, UpdateMediaSendRecvState_w should be
// called.
bool IsReadyToSendMedia_w() const RTC_RUN_ON(worker_thread());
// NetworkInterface implementation, called by MediaEngine
bool SendPacket(rtc::CopyOnWriteBuffer* packet,
const rtc::PacketOptions& options) override;
bool SendRtcp(rtc::CopyOnWriteBuffer* packet,
const rtc::PacketOptions& options) override;
// From RtpTransportInternal
void OnWritableState(bool writable);
void OnNetworkRouteChanged(absl::optional<rtc::NetworkRoute> network_route);
bool SendPacket(bool rtcp,
rtc::CopyOnWriteBuffer* packet,
const rtc::PacketOptions& options);
void EnableMedia_w() RTC_RUN_ON(worker_thread());
void DisableMedia_w() RTC_RUN_ON(worker_thread());
// Performs actions if the RTP/RTCP writable state changed. This should
// be called whenever a channel's writable state changes or when RTCP muxing
// becomes active/inactive.
void UpdateWritableState_n() RTC_RUN_ON(network_thread());
void ChannelWritable_n() RTC_RUN_ON(network_thread());
void ChannelNotWritable_n() RTC_RUN_ON(network_thread());
bool SetPayloadTypeDemuxingEnabled_w(bool enabled)
RTC_RUN_ON(worker_thread());
// Should be called whenever the conditions for
// IsReadyToReceiveMedia/IsReadyToSendMedia are satisfied (or unsatisfied).
// Updates the send/recv state of the media channel.
virtual void UpdateMediaSendRecvState_w() RTC_RUN_ON(worker_thread()) = 0;
bool UpdateLocalStreams_w(const std::vector<StreamParams>& streams,
webrtc::SdpType type,
std::string& error_desc)
RTC_RUN_ON(worker_thread());
bool UpdateRemoteStreams_w(const MediaContentDescription* content,
webrtc::SdpType type,
std::string& error_desc)
RTC_RUN_ON(worker_thread());
virtual bool SetLocalContent_w(const MediaContentDescription* content,
webrtc::SdpType type,
std::string& error_desc)
RTC_RUN_ON(worker_thread()) = 0;
virtual bool SetRemoteContent_w(const MediaContentDescription* content,
webrtc::SdpType type,
std::string& error_desc)
RTC_RUN_ON(worker_thread()) = 0;
// Returns a list of RTP header extensions where any extension URI is unique.
// Encrypted extensions will be either preferred or discarded, depending on
// the current crypto_options_.
RtpHeaderExtensions GetDeduplicatedRtpHeaderExtensions(
const RtpHeaderExtensions& extensions);
// Add `payload_type` to `demuxer_criteria_` if payload type demuxing is
// enabled.
// Returns true if the demuxer payload type changed and a re-registration
// is needed.
bool MaybeAddHandledPayloadType(int payload_type) RTC_RUN_ON(worker_thread());
// Returns true if the demuxer payload type criteria was non-empty before
// clearing.
bool ClearHandledPayloadTypes() RTC_RUN_ON(worker_thread());
// Hops to the network thread to update the transport if an update is
// requested. If `update_demuxer` is false and `extensions` is not set, the
// function simply returns. If either of these is set, the function updates
// the transport with either or both of the demuxer criteria and the supplied
// rtp header extensions.
// Returns `true` if either an update wasn't needed or one was successfully
// applied. If the return value is `false`, then updating the demuxer criteria
// failed, which needs to be treated as an error.
bool MaybeUpdateDemuxerAndRtpExtensions_w(
bool update_demuxer,
absl::optional<RtpHeaderExtensions> extensions,
std::string& error_desc) RTC_RUN_ON(worker_thread());
bool RegisterRtpDemuxerSink_w() RTC_RUN_ON(worker_thread());
// Return description of media channel to facilitate logging
std::string ToString() const;
const std::unique_ptr<MediaSendChannelInterface> media_send_channel_;
const std::unique_ptr<MediaReceiveChannelInterface> media_receive_channel_;
private:
bool ConnectToRtpTransport_n() RTC_RUN_ON(network_thread());
void DisconnectFromRtpTransport_n() RTC_RUN_ON(network_thread());
void SignalSentPacket_n(const rtc::SentPacket& sent_packet);
webrtc::TaskQueueBase* const worker_thread_;
rtc::Thread* const network_thread_;
webrtc::TaskQueueBase* const signaling_thread_;
rtc::scoped_refptr<webrtc::PendingTaskSafetyFlag> alive_;
std::function<void()> on_first_packet_received_
RTC_GUARDED_BY(network_thread());
webrtc::RtpTransportInternal* rtp_transport_
RTC_GUARDED_BY(network_thread()) = nullptr;
std::vector<std::pair<rtc::Socket::Option, int> > socket_options_
RTC_GUARDED_BY(network_thread());
std::vector<std::pair<rtc::Socket::Option, int> > rtcp_socket_options_
RTC_GUARDED_BY(network_thread());
bool writable_ RTC_GUARDED_BY(network_thread()) = false;
bool was_ever_writable_n_ RTC_GUARDED_BY(network_thread()) = false;
bool was_ever_writable_ RTC_GUARDED_BY(worker_thread()) = false;
const bool srtp_required_ = true;
// Set to either kPreferEncryptedExtension or kDiscardEncryptedExtension
// based on the supplied CryptoOptions.
const webrtc::RtpExtension::Filter extensions_filter_;
// Currently the `enabled_` flag is accessed from the signaling thread as
// well, but it can be changed only when signaling thread does a synchronous
// call to the worker thread, so it should be safe.
bool enabled_ RTC_GUARDED_BY(worker_thread()) = false;
bool enabled_s_ RTC_GUARDED_BY(signaling_thread()) = false;
bool payload_type_demuxing_enabled_ RTC_GUARDED_BY(worker_thread()) = true;
std::vector<StreamParams> local_streams_ RTC_GUARDED_BY(worker_thread());
std::vector<StreamParams> remote_streams_ RTC_GUARDED_BY(worker_thread());
webrtc::RtpTransceiverDirection local_content_direction_ RTC_GUARDED_BY(
worker_thread()) = webrtc::RtpTransceiverDirection::kInactive;
webrtc::RtpTransceiverDirection remote_content_direction_ RTC_GUARDED_BY(
worker_thread()) = webrtc::RtpTransceiverDirection::kInactive;
// Cached list of payload types, used if payload type demuxing is re-enabled.
webrtc::flat_set<uint8_t> payload_types_ RTC_GUARDED_BY(worker_thread());
// A stored copy of the rtp header extensions as applied to the transport.
RtpHeaderExtensions rtp_header_extensions_ RTC_GUARDED_BY(worker_thread());
// TODO(bugs.webrtc.org/12239): Modified on worker thread, accessed
// on network thread in RegisterRtpDemuxerSink_n (called from Init_w)
webrtc::RtpDemuxerCriteria demuxer_criteria_;
// This generator is used to generate SSRCs for local streams.
// This is needed in cases where SSRCs are not negotiated or set explicitly
// like in Simulcast.
// This object is not owned by the channel so it must outlive it.
rtc::UniqueRandomIdGenerator* const ssrc_generator_;
};
// VoiceChannel is a specialization that adds support for early media, DTMF,
// and input/output level monitoring.
class VoiceChannel : public BaseChannel {
public:
VoiceChannel(
webrtc::TaskQueueBase* worker_thread,
rtc::Thread* network_thread,
webrtc::TaskQueueBase* signaling_thread,
std::unique_ptr<VoiceMediaSendChannelInterface> send_channel_impl,
std::unique_ptr<VoiceMediaReceiveChannelInterface> receive_channel_impl,
absl::string_view mid,
bool srtp_required,
webrtc::CryptoOptions crypto_options,
rtc::UniqueRandomIdGenerator* ssrc_generator);
~VoiceChannel();
VideoChannel* AsVideoChannel() override {
RTC_CHECK_NOTREACHED();
return nullptr;
}
VoiceChannel* AsVoiceChannel() override { return this; }
VoiceMediaSendChannelInterface* send_channel() {
return media_send_channel_->AsVoiceSendChannel();
}
VoiceMediaReceiveChannelInterface* receive_channel() {
return media_receive_channel_->AsVoiceReceiveChannel();
}
VoiceMediaSendChannelInterface* media_send_channel() override {
return send_channel();
}
VoiceMediaSendChannelInterface* voice_media_send_channel() override {
return send_channel();
}
VoiceMediaReceiveChannelInterface* media_receive_channel() override {
return receive_channel();
}
VoiceMediaReceiveChannelInterface* voice_media_receive_channel() override {
return receive_channel();
}
cricket::MediaType media_type() const override {
return cricket::MEDIA_TYPE_AUDIO;
}
private:
// overrides from BaseChannel
void UpdateMediaSendRecvState_w() RTC_RUN_ON(worker_thread()) override;
bool SetLocalContent_w(const MediaContentDescription* content,
webrtc::SdpType type,
std::string& error_desc)
RTC_RUN_ON(worker_thread()) override;
bool SetRemoteContent_w(const MediaContentDescription* content,
webrtc::SdpType type,
std::string& error_desc)
RTC_RUN_ON(worker_thread()) override;
// Last AudioSenderParameter sent down to the media_channel() via
// SetSenderParameters.
AudioSenderParameter last_send_params_ RTC_GUARDED_BY(worker_thread());
// Last AudioReceiverParameters sent down to the media_channel() via
// SetReceiverParameters.
AudioReceiverParameters last_recv_params_ RTC_GUARDED_BY(worker_thread());
};
// VideoChannel is a specialization for video.
class VideoChannel : public BaseChannel {
public:
VideoChannel(
webrtc::TaskQueueBase* worker_thread,
rtc::Thread* network_thread,
webrtc::TaskQueueBase* signaling_thread,
std::unique_ptr<VideoMediaSendChannelInterface> media_send_channel,
std::unique_ptr<VideoMediaReceiveChannelInterface> media_receive_channel,
absl::string_view mid,
bool srtp_required,
webrtc::CryptoOptions crypto_options,
rtc::UniqueRandomIdGenerator* ssrc_generator);
~VideoChannel();
VideoChannel* AsVideoChannel() override { return this; }
VoiceChannel* AsVoiceChannel() override {
RTC_CHECK_NOTREACHED();
return nullptr;
}
VideoMediaSendChannelInterface* send_channel() {
return media_send_channel_->AsVideoSendChannel();
}
VideoMediaReceiveChannelInterface* receive_channel() {
return media_receive_channel_->AsVideoReceiveChannel();
}
VideoMediaSendChannelInterface* media_send_channel() override {
return send_channel();
}
VideoMediaSendChannelInterface* video_media_send_channel() override {
return send_channel();
}
VideoMediaReceiveChannelInterface* media_receive_channel() override {
return receive_channel();
}
VideoMediaReceiveChannelInterface* video_media_receive_channel() override {
return receive_channel();
}
cricket::MediaType media_type() const override {
return cricket::MEDIA_TYPE_VIDEO;
}
private:
// overrides from BaseChannel
void UpdateMediaSendRecvState_w() RTC_RUN_ON(worker_thread()) override;
bool SetLocalContent_w(const MediaContentDescription* content,
webrtc::SdpType type,
std::string& error_desc)
RTC_RUN_ON(worker_thread()) override;
bool SetRemoteContent_w(const MediaContentDescription* content,
webrtc::SdpType type,
std::string& error_desc)
RTC_RUN_ON(worker_thread()) override;
// Last VideoSenderParameters sent down to the media_channel() via
// SetSenderParameters.
VideoSenderParameters last_send_params_ RTC_GUARDED_BY(worker_thread());
// Last VideoReceiverParameters sent down to the media_channel() via
// SetReceiverParameters.
VideoReceiverParameters last_recv_params_ RTC_GUARDED_BY(worker_thread());
};
} // namespace cricket
#endif // PC_CHANNEL_H_

View file

@ -0,0 +1,105 @@
/*
* Copyright 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef PC_CHANNEL_INTERFACE_H_
#define PC_CHANNEL_INTERFACE_H_
#include <memory>
#include <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "api/jsep.h"
#include "api/media_types.h"
#include "media/base/media_channel.h"
#include "pc/rtp_transport_internal.h"
namespace webrtc {
class Call;
class VideoBitrateAllocatorFactory;
} // namespace webrtc
namespace cricket {
class VoiceChannel;
class VideoChannel;
class MediaContentDescription;
struct MediaConfig;
// A Channel is a construct that groups media streams of the same type
// (audio or video), both outgoing and incoming.
// When the PeerConnection API is used, a Channel corresponds one to one
// to an RtpTransceiver.
// When Unified Plan is used, there can only be at most one outgoing and
// one incoming stream. With Plan B, there can be more than one.
// ChannelInterface contains methods common to voice and video channels.
// As more methods are added to BaseChannel, they should be included in the
// interface as well.
// TODO(bugs.webrtc.org/13931): Merge this class into RtpTransceiver.
class ChannelInterface {
public:
virtual ~ChannelInterface() = default;
virtual cricket::MediaType media_type() const = 0;
virtual VideoChannel* AsVideoChannel() = 0;
virtual VoiceChannel* AsVoiceChannel() = 0;
virtual MediaSendChannelInterface* media_send_channel() = 0;
// Typecasts of media_channel(). Will cause an exception if the
// channel is of the wrong type.
virtual VideoMediaSendChannelInterface* video_media_send_channel() = 0;
virtual VoiceMediaSendChannelInterface* voice_media_send_channel() = 0;
virtual MediaReceiveChannelInterface* media_receive_channel() = 0;
// Typecasts of media_channel(). Will cause an exception if the
// channel is of the wrong type.
virtual VideoMediaReceiveChannelInterface* video_media_receive_channel() = 0;
virtual VoiceMediaReceiveChannelInterface* voice_media_receive_channel() = 0;
// Returns a string view for the transport name. Fetching the transport name
// must be done on the network thread only and note that the lifetime of
// the returned object should be assumed to only be the calling scope.
// TODO(deadbeef): This is redundant; remove this.
virtual absl::string_view transport_name() const = 0;
// TODO(tommi): Change return type to string_view.
virtual const std::string& mid() const = 0;
// Enables or disables this channel
virtual void Enable(bool enable) = 0;
// Used for latency measurements.
virtual void SetFirstPacketReceivedCallback(
std::function<void()> callback) = 0;
// Channel control
virtual bool SetLocalContent(const MediaContentDescription* content,
webrtc::SdpType type,
std::string& error_desc) = 0;
virtual bool SetRemoteContent(const MediaContentDescription* content,
webrtc::SdpType type,
std::string& error_desc) = 0;
virtual bool SetPayloadTypeDemuxingEnabled(bool enabled) = 0;
// Access to the local and remote streams that were set on the channel.
virtual const std::vector<StreamParams>& local_streams() const = 0;
virtual const std::vector<StreamParams>& remote_streams() const = 0;
// Set an RTP level transport.
// Some examples:
// * An RtpTransport without encryption.
// * An SrtpTransport for SDES.
// * A DtlsSrtpTransport for DTLS-SRTP.
virtual bool SetRtpTransport(webrtc::RtpTransportInternal* rtp_transport) = 0;
};
} // namespace cricket
#endif // PC_CHANNEL_INTERFACE_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,193 @@
/*
* Copyright 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/connection_context.h"
#include <type_traits>
#include <utility>
#include <vector>
#include "api/environment/environment.h"
#include "media/base/media_engine.h"
#include "media/sctp/sctp_transport_factory.h"
#include "pc/media_factory.h"
#include "rtc_base/helpers.h"
#include "rtc_base/internal/default_socket_server.h"
#include "rtc_base/socket_server.h"
#include "rtc_base/time_utils.h"
namespace webrtc {
namespace {
rtc::Thread* MaybeStartNetworkThread(
rtc::Thread* old_thread,
std::unique_ptr<rtc::SocketFactory>& socket_factory_holder,
std::unique_ptr<rtc::Thread>& thread_holder) {
if (old_thread) {
return old_thread;
}
std::unique_ptr<rtc::SocketServer> socket_server =
rtc::CreateDefaultSocketServer();
thread_holder = std::make_unique<rtc::Thread>(socket_server.get());
socket_factory_holder = std::move(socket_server);
thread_holder->SetName("pc_network_thread", nullptr);
thread_holder->Start();
return thread_holder.get();
}
rtc::Thread* MaybeWrapThread(rtc::Thread* signaling_thread,
bool& wraps_current_thread) {
wraps_current_thread = false;
if (signaling_thread) {
return signaling_thread;
}
auto this_thread = rtc::Thread::Current();
if (!this_thread) {
// If this thread isn't already wrapped by an rtc::Thread, create a
// wrapper and own it in this class.
this_thread = rtc::ThreadManager::Instance()->WrapCurrentThread();
wraps_current_thread = true;
}
return this_thread;
}
std::unique_ptr<SctpTransportFactoryInterface> MaybeCreateSctpFactory(
std::unique_ptr<SctpTransportFactoryInterface> factory,
rtc::Thread* network_thread) {
if (factory) {
return factory;
}
#ifdef WEBRTC_HAVE_SCTP
return std::make_unique<cricket::SctpTransportFactory>(network_thread);
#else
return nullptr;
#endif
}
} // namespace
// Static
rtc::scoped_refptr<ConnectionContext> ConnectionContext::Create(
const Environment& env,
PeerConnectionFactoryDependencies* dependencies) {
return rtc::scoped_refptr<ConnectionContext>(
new ConnectionContext(env, dependencies));
}
ConnectionContext::ConnectionContext(
const Environment& env,
PeerConnectionFactoryDependencies* dependencies)
: network_thread_(MaybeStartNetworkThread(dependencies->network_thread,
owned_socket_factory_,
owned_network_thread_)),
worker_thread_(dependencies->worker_thread,
[]() {
auto thread_holder = rtc::Thread::Create();
thread_holder->SetName("pc_worker_thread", nullptr);
thread_holder->Start();
return thread_holder;
}),
signaling_thread_(MaybeWrapThread(dependencies->signaling_thread,
wraps_current_thread_)),
env_(env),
media_engine_(
dependencies->media_factory != nullptr
? dependencies->media_factory->CreateMediaEngine(env_,
*dependencies)
: nullptr),
network_monitor_factory_(
std::move(dependencies->network_monitor_factory)),
default_network_manager_(std::move(dependencies->network_manager)),
call_factory_(std::move(dependencies->media_factory)),
default_socket_factory_(std::move(dependencies->packet_socket_factory)),
sctp_factory_(
MaybeCreateSctpFactory(std::move(dependencies->sctp_factory),
network_thread())),
use_rtx_(true) {
RTC_DCHECK_RUN_ON(signaling_thread_);
RTC_DCHECK(!(default_network_manager_ && network_monitor_factory_))
<< "You can't set both network_manager and network_monitor_factory.";
signaling_thread_->AllowInvokesToThread(worker_thread());
signaling_thread_->AllowInvokesToThread(network_thread_);
worker_thread_->AllowInvokesToThread(network_thread_);
if (!network_thread_->IsCurrent()) {
// network_thread_->IsCurrent() == true means signaling_thread_ is
// network_thread_. In this case, no further action is required as
// signaling_thread_ can already invoke network_thread_.
network_thread_->PostTask(
[thread = network_thread_, worker_thread = worker_thread_.get()] {
thread->DisallowBlockingCalls();
thread->DisallowAllInvokes();
if (worker_thread == thread) {
// In this case, worker_thread_ == network_thread_
thread->AllowInvokesToThread(thread);
}
});
}
rtc::InitRandom(rtc::Time32());
rtc::SocketFactory* socket_factory = dependencies->socket_factory;
if (socket_factory == nullptr) {
if (owned_socket_factory_) {
socket_factory = owned_socket_factory_.get();
} else {
// TODO(bugs.webrtc.org/13145): This case should be deleted. Either
// require that a PacketSocketFactory and NetworkManager always are
// injected (with no need to construct these default objects), or require
// that if a network_thread is injected, an approprite rtc::SocketServer
// should be injected too.
socket_factory = network_thread()->socketserver();
}
}
if (!default_network_manager_) {
// If network_monitor_factory_ is non-null, it will be used to create a
// network monitor while on the network thread.
default_network_manager_ = std::make_unique<rtc::BasicNetworkManager>(
network_monitor_factory_.get(), socket_factory, &env_.field_trials());
}
if (!default_socket_factory_) {
default_socket_factory_ =
std::make_unique<rtc::BasicPacketSocketFactory>(socket_factory);
}
// Set warning levels on the threads, to give warnings when response
// may be slower than is expected of the thread.
// Since some of the threads may be the same, start with the least
// restrictive limits and end with the least permissive ones.
// This will give warnings for all cases.
signaling_thread_->SetDispatchWarningMs(100);
worker_thread_->SetDispatchWarningMs(30);
network_thread_->SetDispatchWarningMs(10);
if (media_engine_) {
// TODO(tommi): Change VoiceEngine to do ctor time initialization so that
// this isn't necessary.
worker_thread_->BlockingCall([&] { media_engine_->Init(); });
}
}
ConnectionContext::~ConnectionContext() {
RTC_DCHECK_RUN_ON(signaling_thread_);
// `media_engine_` requires destruction to happen on the worker thread.
worker_thread_->PostTask([media_engine = std::move(media_engine_)] {});
// Make sure `worker_thread()` and `signaling_thread()` outlive
// `default_socket_factory_` and `default_network_manager_`.
default_socket_factory_ = nullptr;
default_network_manager_ = nullptr;
if (wraps_current_thread_)
rtc::ThreadManager::Instance()->UnwrapCurrentThread();
}
} // namespace webrtc

View file

@ -0,0 +1,152 @@
/*
* Copyright 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef PC_CONNECTION_CONTEXT_H_
#define PC_CONNECTION_CONTEXT_H_
#include <memory>
#include <string>
#include "api/environment/environment.h"
#include "api/media_stream_interface.h"
#include "api/peer_connection_interface.h"
#include "api/ref_counted_base.h"
#include "api/scoped_refptr.h"
#include "api/sequence_checker.h"
#include "api/transport/sctp_transport_factory_interface.h"
#include "media/base/media_engine.h"
#include "p2p/base/basic_packet_socket_factory.h"
#include "rtc_base/checks.h"
#include "rtc_base/network.h"
#include "rtc_base/network_monitor_factory.h"
#include "rtc_base/rtc_certificate_generator.h"
#include "rtc_base/socket_factory.h"
#include "rtc_base/thread.h"
#include "rtc_base/thread_annotations.h"
namespace rtc {
class BasicPacketSocketFactory;
class UniqueRandomIdGenerator;
} // namespace rtc
namespace webrtc {
// This class contains resources needed by PeerConnection and associated
// objects. A reference to this object is passed to each PeerConnection. The
// methods on this object are assumed not to change the state in any way that
// interferes with the operation of other PeerConnections.
//
// This class must be created and destroyed on the signaling thread.
class ConnectionContext final
: public rtc::RefCountedNonVirtual<ConnectionContext> {
public:
// Creates a ConnectionContext. May return null if initialization fails.
// The Dependencies class allows simple management of all new dependencies
// being added to the ConnectionContext.
static rtc::scoped_refptr<ConnectionContext> Create(
const Environment& env,
PeerConnectionFactoryDependencies* dependencies);
// This class is not copyable or movable.
ConnectionContext(const ConnectionContext&) = delete;
ConnectionContext& operator=(const ConnectionContext&) = delete;
// Functions called from PeerConnection and friends
SctpTransportFactoryInterface* sctp_transport_factory() const {
return sctp_factory_.get();
}
cricket::MediaEngineInterface* media_engine() const {
return media_engine_.get();
}
rtc::Thread* signaling_thread() { return signaling_thread_; }
const rtc::Thread* signaling_thread() const { return signaling_thread_; }
rtc::Thread* worker_thread() { return worker_thread_.get(); }
const rtc::Thread* worker_thread() const { return worker_thread_.get(); }
rtc::Thread* network_thread() { return network_thread_; }
const rtc::Thread* network_thread() const { return network_thread_; }
// Environment associated with the PeerConnectionFactory.
// Note: environments are different for different PeerConnections,
// but they are not supposed to change after creating the PeerConnection.
const Environment& env() const { return env_; }
// Accessors only used from the PeerConnectionFactory class
rtc::NetworkManager* default_network_manager() {
RTC_DCHECK_RUN_ON(signaling_thread_);
return default_network_manager_.get();
}
rtc::PacketSocketFactory* default_socket_factory() {
RTC_DCHECK_RUN_ON(signaling_thread_);
return default_socket_factory_.get();
}
MediaFactory* call_factory() {
RTC_DCHECK_RUN_ON(worker_thread());
return call_factory_.get();
}
rtc::UniqueRandomIdGenerator* ssrc_generator() { return &ssrc_generator_; }
// Note: There is lots of code that wants to know whether or not we
// use RTX, but so far, no code has been found that sets it to false.
// Kept in the API in order to ease introduction if we want to resurrect
// the functionality.
bool use_rtx() { return use_rtx_; }
// For use by tests.
void set_use_rtx(bool use_rtx) { use_rtx_ = use_rtx; }
protected:
ConnectionContext(const Environment& env,
PeerConnectionFactoryDependencies* dependencies);
friend class rtc::RefCountedNonVirtual<ConnectionContext>;
~ConnectionContext();
private:
// The following three variables are used to communicate between the
// constructor and the destructor, and are never exposed externally.
bool wraps_current_thread_;
std::unique_ptr<rtc::SocketFactory> owned_socket_factory_;
std::unique_ptr<rtc::Thread> owned_network_thread_
RTC_GUARDED_BY(signaling_thread_);
rtc::Thread* const network_thread_;
AlwaysValidPointer<rtc::Thread> const worker_thread_;
rtc::Thread* const signaling_thread_;
const Environment env_;
// This object is const over the lifetime of the ConnectionContext, and is
// only altered in the destructor.
std::unique_ptr<cricket::MediaEngineInterface> media_engine_;
// This object should be used to generate any SSRC that is not explicitly
// specified by the user (or by the remote party).
// TODO(bugs.webrtc.org/12666): This variable is used from both the signaling
// and worker threads. See if we can't restrict usage to a single thread.
rtc::UniqueRandomIdGenerator ssrc_generator_;
std::unique_ptr<rtc::NetworkMonitorFactory> const network_monitor_factory_
RTC_GUARDED_BY(signaling_thread_);
std::unique_ptr<rtc::NetworkManager> default_network_manager_
RTC_GUARDED_BY(signaling_thread_);
std::unique_ptr<MediaFactory> const call_factory_
RTC_GUARDED_BY(worker_thread());
std::unique_ptr<rtc::PacketSocketFactory> default_socket_factory_
RTC_GUARDED_BY(signaling_thread_);
std::unique_ptr<SctpTransportFactoryInterface> const sctp_factory_;
// Controls whether to announce support for the the rfc4588 payload format
// for retransmitted video packets.
bool use_rtx_;
};
} // namespace webrtc
#endif // PC_CONNECTION_CONTEXT_H_

View file

@ -0,0 +1,440 @@
/*
* Copyright 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/data_channel_controller.h"
#include <utility>
#include "absl/algorithm/container.h"
#include "api/peer_connection_interface.h"
#include "api/rtc_error.h"
#include "pc/peer_connection_internal.h"
#include "pc/sctp_utils.h"
#include "rtc_base/logging.h"
namespace webrtc {
DataChannelController::~DataChannelController() {
RTC_DCHECK(sctp_data_channels_n_.empty())
<< "Missing call to TeardownDataChannelTransport_n?";
RTC_DCHECK(!signaling_safety_.flag()->alive())
<< "Missing call to PrepareForShutdown?";
}
bool DataChannelController::HasDataChannels() const {
RTC_DCHECK_RUN_ON(signaling_thread());
return channel_usage_ == DataChannelUsage::kInUse;
}
bool DataChannelController::HasUsedDataChannels() const {
RTC_DCHECK_RUN_ON(signaling_thread());
return channel_usage_ != DataChannelUsage::kNeverUsed;
}
RTCError DataChannelController::SendData(
StreamId sid,
const SendDataParams& params,
const rtc::CopyOnWriteBuffer& payload) {
RTC_DCHECK_RUN_ON(network_thread());
if (!data_channel_transport_) {
RTC_LOG(LS_ERROR) << "SendData called before transport is ready";
return RTCError(RTCErrorType::INVALID_STATE);
}
return data_channel_transport_->SendData(sid.stream_id_int(), params,
payload);
}
void DataChannelController::AddSctpDataStream(StreamId sid) {
RTC_DCHECK_RUN_ON(network_thread());
RTC_DCHECK(sid.HasValue());
if (data_channel_transport_) {
data_channel_transport_->OpenChannel(sid.stream_id_int());
}
}
void DataChannelController::RemoveSctpDataStream(StreamId sid) {
RTC_DCHECK_RUN_ON(network_thread());
if (data_channel_transport_) {
data_channel_transport_->CloseChannel(sid.stream_id_int());
}
}
void DataChannelController::OnChannelStateChanged(
SctpDataChannel* channel,
DataChannelInterface::DataState state) {
RTC_DCHECK_RUN_ON(network_thread());
// Stash away the internal id here in case `OnSctpDataChannelClosed` ends up
// releasing the last reference to the channel.
const int channel_id = channel->internal_id();
if (state == DataChannelInterface::DataState::kClosed)
OnSctpDataChannelClosed(channel);
DataChannelUsage channel_usage = sctp_data_channels_n_.empty()
? DataChannelUsage::kHaveBeenUsed
: DataChannelUsage::kInUse;
signaling_thread()->PostTask(SafeTask(
signaling_safety_.flag(), [this, channel_id, state, channel_usage] {
RTC_DCHECK_RUN_ON(signaling_thread());
channel_usage_ = channel_usage;
pc_->OnSctpDataChannelStateChanged(channel_id, state);
}));
}
void DataChannelController::OnDataReceived(
int channel_id,
DataMessageType type,
const rtc::CopyOnWriteBuffer& buffer) {
RTC_DCHECK_RUN_ON(network_thread());
if (HandleOpenMessage_n(channel_id, type, buffer))
return;
auto it = absl::c_find_if(sctp_data_channels_n_, [&](const auto& c) {
return c->sid_n().stream_id_int() == channel_id;
});
if (it != sctp_data_channels_n_.end())
(*it)->OnDataReceived(type, buffer);
}
void DataChannelController::OnChannelClosing(int channel_id) {
RTC_DCHECK_RUN_ON(network_thread());
auto it = absl::c_find_if(sctp_data_channels_n_, [&](const auto& c) {
return c->sid_n().stream_id_int() == channel_id;
});
if (it != sctp_data_channels_n_.end())
(*it)->OnClosingProcedureStartedRemotely();
}
void DataChannelController::OnChannelClosed(int channel_id) {
RTC_DCHECK_RUN_ON(network_thread());
StreamId sid(channel_id);
sid_allocator_.ReleaseSid(sid);
auto it = absl::c_find_if(sctp_data_channels_n_,
[&](const auto& c) { return c->sid_n() == sid; });
if (it != sctp_data_channels_n_.end()) {
rtc::scoped_refptr<SctpDataChannel> channel = std::move(*it);
sctp_data_channels_n_.erase(it);
channel->OnClosingProcedureComplete();
}
}
void DataChannelController::OnReadyToSend() {
RTC_DCHECK_RUN_ON(network_thread());
auto copy = sctp_data_channels_n_;
for (const auto& channel : copy) {
if (channel->sid_n().HasValue()) {
channel->OnTransportReady();
} else {
// This happens for role==SSL_SERVER channels when we get notified by
// the transport *before* the SDP code calls `AllocateSctpSids` to
// trigger assignment of sids. In this case OnTransportReady() will be
// called from within `AllocateSctpSids` below.
RTC_LOG(LS_INFO) << "OnReadyToSend: Still waiting for an id for channel.";
}
}
}
void DataChannelController::OnTransportClosed(RTCError error) {
RTC_DCHECK_RUN_ON(network_thread());
// This loop will close all data channels and trigger a callback to
// `OnSctpDataChannelClosed`. We'll empty `sctp_data_channels_n_`, first
// and `OnSctpDataChannelClosed` will become a noop but we'll release the
// StreamId here.
std::vector<rtc::scoped_refptr<SctpDataChannel>> temp_sctp_dcs;
temp_sctp_dcs.swap(sctp_data_channels_n_);
for (const auto& channel : temp_sctp_dcs) {
channel->OnTransportChannelClosed(error);
sid_allocator_.ReleaseSid(channel->sid_n());
}
}
void DataChannelController::SetupDataChannelTransport_n(
DataChannelTransportInterface* transport) {
RTC_DCHECK_RUN_ON(network_thread());
RTC_DCHECK(transport);
set_data_channel_transport(transport);
}
void DataChannelController::PrepareForShutdown() {
RTC_DCHECK_RUN_ON(signaling_thread());
signaling_safety_.reset(PendingTaskSafetyFlag::CreateDetachedInactive());
if (channel_usage_ != DataChannelUsage::kNeverUsed)
channel_usage_ = DataChannelUsage::kHaveBeenUsed;
}
void DataChannelController::TeardownDataChannelTransport_n(RTCError error) {
RTC_DCHECK_RUN_ON(network_thread());
OnTransportClosed(error);
set_data_channel_transport(nullptr);
RTC_DCHECK(sctp_data_channels_n_.empty());
weak_factory_.InvalidateWeakPtrs();
}
void DataChannelController::OnTransportChanged(
DataChannelTransportInterface* new_data_channel_transport) {
RTC_DCHECK_RUN_ON(network_thread());
if (data_channel_transport_ &&
data_channel_transport_ != new_data_channel_transport) {
// Changed which data channel transport is used for `sctp_mid_` (eg. now
// it's bundled).
set_data_channel_transport(new_data_channel_transport);
}
}
std::vector<DataChannelStats> DataChannelController::GetDataChannelStats()
const {
RTC_DCHECK_RUN_ON(network_thread());
std::vector<DataChannelStats> stats;
stats.reserve(sctp_data_channels_n_.size());
for (const auto& channel : sctp_data_channels_n_)
stats.push_back(channel->GetStats());
return stats;
}
bool DataChannelController::HandleOpenMessage_n(
int channel_id,
DataMessageType type,
const rtc::CopyOnWriteBuffer& buffer) {
if (type != DataMessageType::kControl || !IsOpenMessage(buffer))
return false;
// Received OPEN message; parse and signal that a new data channel should
// be created.
std::string label;
InternalDataChannelInit config;
config.id = channel_id;
if (!ParseDataChannelOpenMessage(buffer, &label, &config)) {
RTC_LOG(LS_WARNING) << "Failed to parse the OPEN message for sid "
<< channel_id;
} else {
config.open_handshake_role = InternalDataChannelInit::kAcker;
auto channel_or_error = CreateDataChannel(label, config);
if (channel_or_error.ok()) {
signaling_thread()->PostTask(SafeTask(
signaling_safety_.flag(),
[this, channel = channel_or_error.MoveValue(),
ready_to_send = data_channel_transport_->IsReadyToSend()] {
RTC_DCHECK_RUN_ON(signaling_thread());
OnDataChannelOpenMessage(std::move(channel), ready_to_send);
}));
} else {
RTC_LOG(LS_ERROR) << "Failed to create DataChannel from the OPEN message."
<< ToString(channel_or_error.error().type());
}
}
return true;
}
void DataChannelController::OnDataChannelOpenMessage(
rtc::scoped_refptr<SctpDataChannel> channel,
bool ready_to_send) {
channel_usage_ = DataChannelUsage::kInUse;
auto proxy = SctpDataChannel::CreateProxy(channel, signaling_safety_.flag());
pc_->Observer()->OnDataChannel(proxy);
pc_->NoteDataAddedEvent();
if (ready_to_send) {
network_thread()->PostTask([channel = std::move(channel)] {
if (channel->state() != DataChannelInterface::DataState::kClosed)
channel->OnTransportReady();
});
}
}
// RTC_RUN_ON(network_thread())
RTCError DataChannelController::ReserveOrAllocateSid(
StreamId& sid,
absl::optional<rtc::SSLRole> fallback_ssl_role) {
if (sid.HasValue()) {
return sid_allocator_.ReserveSid(sid)
? RTCError::OK()
: RTCError(RTCErrorType::INVALID_RANGE,
"StreamId out of range or reserved.");
}
// Attempt to allocate an ID based on the negotiated role.
absl::optional<rtc::SSLRole> role = pc_->GetSctpSslRole_n();
if (!role)
role = fallback_ssl_role;
if (role) {
sid = sid_allocator_.AllocateSid(*role);
if (!sid.HasValue())
return RTCError(RTCErrorType::RESOURCE_EXHAUSTED);
}
// When we get here, we may still not have an ID, but that's a supported case
// whereby an id will be assigned later.
RTC_DCHECK(sid.HasValue() || !role);
return RTCError::OK();
}
// RTC_RUN_ON(network_thread())
RTCErrorOr<rtc::scoped_refptr<SctpDataChannel>>
DataChannelController::CreateDataChannel(const std::string& label,
InternalDataChannelInit& config) {
StreamId sid(config.id);
RTCError err = ReserveOrAllocateSid(sid, config.fallback_ssl_role);
if (!err.ok())
return err;
// In case `sid` has changed. Update `config` accordingly.
config.id = sid.stream_id_int();
rtc::scoped_refptr<SctpDataChannel> channel = SctpDataChannel::Create(
weak_factory_.GetWeakPtr(), label, data_channel_transport_ != nullptr,
config, signaling_thread(), network_thread());
RTC_DCHECK(channel);
sctp_data_channels_n_.push_back(channel);
// If we have an id already, notify the transport.
if (sid.HasValue())
AddSctpDataStream(sid);
return channel;
}
RTCErrorOr<rtc::scoped_refptr<DataChannelInterface>>
DataChannelController::InternalCreateDataChannelWithProxy(
const std::string& label,
const InternalDataChannelInit& config) {
RTC_DCHECK_RUN_ON(signaling_thread());
RTC_DCHECK(!pc_->IsClosed());
if (!config.IsValid()) {
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
"Invalid DataChannelInit");
}
bool ready_to_send = false;
InternalDataChannelInit new_config = config;
StreamId sid(new_config.id);
auto ret = network_thread()->BlockingCall(
[&]() -> RTCErrorOr<rtc::scoped_refptr<SctpDataChannel>> {
RTC_DCHECK_RUN_ON(network_thread());
auto channel = CreateDataChannel(label, new_config);
if (!channel.ok())
return channel;
ready_to_send =
data_channel_transport_ && data_channel_transport_->IsReadyToSend();
if (ready_to_send) {
// If the transport is ready to send because the initial channel
// ready signal may have been sent before the DataChannel creation.
// This has to be done async because the upper layer objects (e.g.
// Chrome glue and WebKit) are not wired up properly until after
// `InternalCreateDataChannelWithProxy` returns.
network_thread()->PostTask([channel = channel.value()] {
if (channel->state() != DataChannelInterface::DataState::kClosed)
channel->OnTransportReady();
});
}
return channel;
});
if (!ret.ok())
return ret.MoveError();
channel_usage_ = DataChannelUsage::kInUse;
return SctpDataChannel::CreateProxy(ret.MoveValue(),
signaling_safety_.flag());
}
void DataChannelController::AllocateSctpSids(rtc::SSLRole role) {
RTC_DCHECK_RUN_ON(network_thread());
const bool ready_to_send =
data_channel_transport_ && data_channel_transport_->IsReadyToSend();
std::vector<std::pair<SctpDataChannel*, StreamId>> channels_to_update;
std::vector<rtc::scoped_refptr<SctpDataChannel>> channels_to_close;
for (auto it = sctp_data_channels_n_.begin();
it != sctp_data_channels_n_.end();) {
if (!(*it)->sid_n().HasValue()) {
StreamId sid = sid_allocator_.AllocateSid(role);
if (sid.HasValue()) {
(*it)->SetSctpSid_n(sid);
AddSctpDataStream(sid);
if (ready_to_send) {
RTC_LOG(LS_INFO) << "AllocateSctpSids: Id assigned, ready to send.";
(*it)->OnTransportReady();
}
channels_to_update.push_back(std::make_pair((*it).get(), sid));
} else {
channels_to_close.push_back(std::move(*it));
it = sctp_data_channels_n_.erase(it);
continue;
}
}
++it;
}
// Since closing modifies the list of channels, we have to do the actual
// closing outside the loop.
for (const auto& channel : channels_to_close) {
channel->CloseAbruptlyWithDataChannelFailure("Failed to allocate SCTP SID");
}
}
void DataChannelController::OnSctpDataChannelClosed(SctpDataChannel* channel) {
RTC_DCHECK_RUN_ON(network_thread());
// After the closing procedure is done, it's safe to use this ID for
// another data channel.
if (channel->sid_n().HasValue()) {
sid_allocator_.ReleaseSid(channel->sid_n());
}
auto it = absl::c_find_if(sctp_data_channels_n_,
[&](const auto& c) { return c.get() == channel; });
if (it != sctp_data_channels_n_.end())
sctp_data_channels_n_.erase(it);
}
void DataChannelController::set_data_channel_transport(
DataChannelTransportInterface* transport) {
RTC_DCHECK_RUN_ON(network_thread());
if (data_channel_transport_)
data_channel_transport_->SetDataSink(nullptr);
data_channel_transport_ = transport;
if (data_channel_transport_) {
// There's a new data channel transport. This needs to be signaled to the
// `sctp_data_channels_n_` so that they can reopen and reconnect. This is
// necessary when bundling is applied.
NotifyDataChannelsOfTransportCreated();
data_channel_transport_->SetDataSink(this);
}
}
void DataChannelController::NotifyDataChannelsOfTransportCreated() {
RTC_DCHECK_RUN_ON(network_thread());
RTC_DCHECK(data_channel_transport_);
for (const auto& channel : sctp_data_channels_n_) {
if (channel->sid_n().HasValue())
AddSctpDataStream(channel->sid_n());
channel->OnTransportChannelCreated();
}
}
rtc::Thread* DataChannelController::network_thread() const {
return pc_->network_thread();
}
rtc::Thread* DataChannelController::signaling_thread() const {
return pc_->signaling_thread();
}
} // namespace webrtc

View file

@ -0,0 +1,165 @@
/*
* Copyright 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef PC_DATA_CHANNEL_CONTROLLER_H_
#define PC_DATA_CHANNEL_CONTROLLER_H_
#include <string>
#include <vector>
#include "api/data_channel_interface.h"
#include "api/rtc_error.h"
#include "api/scoped_refptr.h"
#include "api/sequence_checker.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "api/transport/data_channel_transport_interface.h"
#include "pc/data_channel_utils.h"
#include "pc/sctp_data_channel.h"
#include "rtc_base/checks.h"
#include "rtc_base/copy_on_write_buffer.h"
#include "rtc_base/ssl_stream_adapter.h"
#include "rtc_base/thread.h"
#include "rtc_base/thread_annotations.h"
#include "rtc_base/weak_ptr.h"
namespace webrtc {
class PeerConnectionInternal;
class DataChannelController : public SctpDataChannelControllerInterface,
public DataChannelSink {
public:
explicit DataChannelController(PeerConnectionInternal* pc) : pc_(pc) {}
~DataChannelController();
// Not copyable or movable.
DataChannelController(DataChannelController&) = delete;
DataChannelController& operator=(const DataChannelController& other) = delete;
DataChannelController(DataChannelController&&) = delete;
DataChannelController& operator=(DataChannelController&& other) = delete;
// Implements
// SctpDataChannelProviderInterface.
RTCError SendData(StreamId sid,
const SendDataParams& params,
const rtc::CopyOnWriteBuffer& payload) override;
void AddSctpDataStream(StreamId sid) override;
void RemoveSctpDataStream(StreamId sid) override;
void OnChannelStateChanged(SctpDataChannel* channel,
DataChannelInterface::DataState state) override;
// Implements DataChannelSink.
void OnDataReceived(int channel_id,
DataMessageType type,
const rtc::CopyOnWriteBuffer& buffer) override;
void OnChannelClosing(int channel_id) override;
void OnChannelClosed(int channel_id) override;
void OnReadyToSend() override;
void OnTransportClosed(RTCError error) override;
// Called as part of destroying the owning PeerConnection.
void PrepareForShutdown();
// Called from PeerConnection::SetupDataChannelTransport_n
void SetupDataChannelTransport_n(DataChannelTransportInterface* transport);
// Called from PeerConnection::TeardownDataChannelTransport_n
void TeardownDataChannelTransport_n(RTCError error);
// Called from PeerConnection::OnTransportChanged
// to make required changes to datachannels' transports.
void OnTransportChanged(
DataChannelTransportInterface* data_channel_transport);
// Called from PeerConnection::GetDataChannelStats on the signaling thread.
std::vector<DataChannelStats> GetDataChannelStats() const;
// Creates channel and adds it to the collection of DataChannels that will
// be offered in a SessionDescription, and wraps it in a proxy object.
RTCErrorOr<rtc::scoped_refptr<DataChannelInterface>>
InternalCreateDataChannelWithProxy(const std::string& label,
const InternalDataChannelInit& config);
void AllocateSctpSids(rtc::SSLRole role);
// Check if data channels are currently tracked. Used to decide whether a
// rejected m=application section should be reoffered.
bool HasDataChannels() const;
// At some point in time, a data channel has existed.
bool HasUsedDataChannels() const;
protected:
rtc::Thread* network_thread() const;
rtc::Thread* signaling_thread() const;
private:
void OnSctpDataChannelClosed(SctpDataChannel* channel);
// Creates a new SctpDataChannel object on the network thread.
RTCErrorOr<rtc::scoped_refptr<SctpDataChannel>> CreateDataChannel(
const std::string& label,
InternalDataChannelInit& config) RTC_RUN_ON(network_thread());
// Parses and handles open messages. Returns true if the message is an open
// message and should be considered to be handled, false otherwise.
bool HandleOpenMessage_n(int channel_id,
DataMessageType type,
const rtc::CopyOnWriteBuffer& buffer)
RTC_RUN_ON(network_thread());
// Called when a valid data channel OPEN message is received.
void OnDataChannelOpenMessage(rtc::scoped_refptr<SctpDataChannel> channel,
bool ready_to_send)
RTC_RUN_ON(signaling_thread());
// Accepts a `StreamId` which may be pre-negotiated or unassigned. For
// pre-negotiated sids, attempts to reserve the sid in the allocation pool,
// for unassigned sids attempts to generate a new sid if possible. Returns
// RTCError::OK() if the sid is reserved (and may have been generated) or
// if not enough information exists to generate a sid, in which case the sid
// will still be unassigned upon return, but will be assigned later.
// If the pool has been exhausted or a sid has already been reserved, an
// error will be returned.
RTCError ReserveOrAllocateSid(StreamId& sid,
absl::optional<rtc::SSLRole> fallback_ssl_role)
RTC_RUN_ON(network_thread());
// Called when all data channels need to be notified of a transport channel
// (calls OnTransportChannelCreated on the signaling thread).
void NotifyDataChannelsOfTransportCreated();
void set_data_channel_transport(DataChannelTransportInterface* transport);
// Plugin transport used for data channels. Pointer may be accessed and
// checked from any thread, but the object may only be touched on the
// network thread.
DataChannelTransportInterface* data_channel_transport_
RTC_GUARDED_BY(network_thread()) = nullptr;
SctpSidAllocator sid_allocator_ RTC_GUARDED_BY(network_thread());
std::vector<rtc::scoped_refptr<SctpDataChannel>> sctp_data_channels_n_
RTC_GUARDED_BY(network_thread());
enum class DataChannelUsage : uint8_t {
kNeverUsed = 0,
kHaveBeenUsed,
kInUse
};
DataChannelUsage channel_usage_ RTC_GUARDED_BY(signaling_thread()) =
DataChannelUsage::kNeverUsed;
// Owning PeerConnection.
PeerConnectionInternal* const pc_;
// The weak pointers must be dereferenced and invalidated on the network
// thread only.
rtc::WeakPtrFactory<DataChannelController> weak_factory_
RTC_GUARDED_BY(network_thread()){this};
ScopedTaskSafety signaling_safety_;
};
} // namespace webrtc
#endif // PC_DATA_CHANNEL_CONTROLLER_H_

View file

@ -0,0 +1,214 @@
/*
* Copyright 2022 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/data_channel_controller.h"
#include <memory>
#include "pc/peer_connection_internal.h"
#include "pc/sctp_data_channel.h"
#include "pc/test/mock_peer_connection_internal.h"
#include "rtc_base/null_socket_server.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/run_loop.h"
namespace webrtc {
namespace {
using ::testing::NiceMock;
using ::testing::Return;
class MockDataChannelTransport : public DataChannelTransportInterface {
public:
~MockDataChannelTransport() override {}
MOCK_METHOD(RTCError, OpenChannel, (int channel_id), (override));
MOCK_METHOD(RTCError,
SendData,
(int channel_id,
const SendDataParams& params,
const rtc::CopyOnWriteBuffer& buffer),
(override));
MOCK_METHOD(RTCError, CloseChannel, (int channel_id), (override));
MOCK_METHOD(void, SetDataSink, (DataChannelSink * sink), (override));
MOCK_METHOD(bool, IsReadyToSend, (), (const, override));
};
// Convenience class for tests to ensure that shutdown methods for DCC
// are consistently called. In practice SdpOfferAnswerHandler will call
// TeardownDataChannelTransport_n on the network thread when destroying the
// data channel transport and PeerConnection calls PrepareForShutdown() from
// within PeerConnection::Close(). The DataChannelControllerForTest class mimics
// behavior by calling those methods from within its destructor.
class DataChannelControllerForTest : public DataChannelController {
public:
explicit DataChannelControllerForTest(
PeerConnectionInternal* pc,
DataChannelTransportInterface* transport = nullptr)
: DataChannelController(pc) {
if (transport) {
network_thread()->BlockingCall(
[&] { SetupDataChannelTransport_n(transport); });
}
}
~DataChannelControllerForTest() override {
network_thread()->BlockingCall(
[&] { TeardownDataChannelTransport_n(RTCError::OK()); });
PrepareForShutdown();
}
};
class DataChannelControllerTest : public ::testing::Test {
protected:
DataChannelControllerTest()
: network_thread_(std::make_unique<rtc::NullSocketServer>()) {
network_thread_.Start();
pc_ = rtc::make_ref_counted<NiceMock<MockPeerConnectionInternal>>();
ON_CALL(*pc_, signaling_thread)
.WillByDefault(Return(rtc::Thread::Current()));
ON_CALL(*pc_, network_thread).WillByDefault(Return(&network_thread_));
}
~DataChannelControllerTest() override {
run_loop_.Flush();
network_thread_.Stop();
}
test::RunLoop run_loop_;
rtc::Thread network_thread_;
rtc::scoped_refptr<NiceMock<MockPeerConnectionInternal>> pc_;
};
TEST_F(DataChannelControllerTest, CreateAndDestroy) {
DataChannelControllerForTest dcc(pc_.get());
}
TEST_F(DataChannelControllerTest, CreateDataChannelEarlyRelease) {
DataChannelControllerForTest dcc(pc_.get());
auto ret = dcc.InternalCreateDataChannelWithProxy(
"label", InternalDataChannelInit(DataChannelInit()));
ASSERT_TRUE(ret.ok());
auto channel = ret.MoveValue();
// DCC still holds a reference to the channel. Release this reference early.
channel = nullptr;
}
TEST_F(DataChannelControllerTest, CreateDataChannelEarlyClose) {
DataChannelControllerForTest dcc(pc_.get());
EXPECT_FALSE(dcc.HasDataChannels());
EXPECT_FALSE(dcc.HasUsedDataChannels());
auto ret = dcc.InternalCreateDataChannelWithProxy(
"label", InternalDataChannelInit(DataChannelInit()));
ASSERT_TRUE(ret.ok());
auto channel = ret.MoveValue();
EXPECT_TRUE(dcc.HasDataChannels());
EXPECT_TRUE(dcc.HasUsedDataChannels());
channel->Close();
run_loop_.Flush();
EXPECT_FALSE(dcc.HasDataChannels());
EXPECT_TRUE(dcc.HasUsedDataChannels());
}
TEST_F(DataChannelControllerTest, CreateDataChannelLateRelease) {
auto dcc = std::make_unique<DataChannelControllerForTest>(pc_.get());
auto ret = dcc->InternalCreateDataChannelWithProxy(
"label", InternalDataChannelInit(DataChannelInit()));
ASSERT_TRUE(ret.ok());
auto channel = ret.MoveValue();
dcc.reset();
channel = nullptr;
}
TEST_F(DataChannelControllerTest, CloseAfterControllerDestroyed) {
auto dcc = std::make_unique<DataChannelControllerForTest>(pc_.get());
auto ret = dcc->InternalCreateDataChannelWithProxy(
"label", InternalDataChannelInit(DataChannelInit()));
ASSERT_TRUE(ret.ok());
auto channel = ret.MoveValue();
dcc.reset();
channel->Close();
}
// Allocate the maximum number of data channels and then one more.
// The last allocation should fail.
TEST_F(DataChannelControllerTest, MaxChannels) {
NiceMock<MockDataChannelTransport> transport;
int channel_id = 0;
ON_CALL(*pc_, GetSctpSslRole_n).WillByDefault([&]() {
return absl::optional<rtc::SSLRole>((channel_id & 1) ? rtc::SSL_SERVER
: rtc::SSL_CLIENT);
});
DataChannelControllerForTest dcc(pc_.get(), &transport);
// Allocate the maximum number of channels + 1. Inside the loop, the creation
// process will allocate a stream id for each channel.
for (channel_id = 0; channel_id <= cricket::kMaxSctpStreams; ++channel_id) {
auto ret = dcc.InternalCreateDataChannelWithProxy(
"label", InternalDataChannelInit(DataChannelInit()));
if (channel_id == cricket::kMaxSctpStreams) {
// We've reached the maximum and the previous call should have failed.
EXPECT_FALSE(ret.ok());
} else {
// We're still working on saturating the pool. Things should be working.
EXPECT_TRUE(ret.ok());
}
}
}
// Test that while a data channel is in the `kClosing` state, its StreamId does
// not get re-used for new channels. Only once the state reaches `kClosed`
// should a StreamId be available again for allocation.
TEST_F(DataChannelControllerTest, NoStreamIdReuseWhileClosing) {
ON_CALL(*pc_, GetSctpSslRole_n).WillByDefault([&]() {
return rtc::SSL_CLIENT;
});
NiceMock<MockDataChannelTransport> transport; // Wider scope than `dcc`.
DataChannelControllerForTest dcc(pc_.get(), &transport);
// Create the first channel and check that we got the expected, first sid.
auto channel1 = dcc.InternalCreateDataChannelWithProxy(
"label", InternalDataChannelInit(DataChannelInit()))
.MoveValue();
ASSERT_EQ(channel1->id(), 0);
// Start closing the channel and make sure its state is `kClosing`
channel1->Close();
ASSERT_EQ(channel1->state(), DataChannelInterface::DataState::kClosing);
// Create a second channel and make sure we get a new StreamId, not the same
// as that of channel1.
auto channel2 = dcc.InternalCreateDataChannelWithProxy(
"label2", InternalDataChannelInit(DataChannelInit()))
.MoveValue();
ASSERT_NE(channel2->id(), channel1->id()); // In practice the id will be 2.
// Simulate the acknowledgement of the channel closing from the transport.
// This completes the closing operation of channel1.
pc_->network_thread()->BlockingCall([&] { dcc.OnChannelClosed(0); });
run_loop_.Flush();
ASSERT_EQ(channel1->state(), DataChannelInterface::DataState::kClosed);
// Now create a third channel. This time, the id of the first channel should
// be available again and therefore the ids of the first and third channels
// should be the same.
auto channel3 = dcc.InternalCreateDataChannelWithProxy(
"label3", InternalDataChannelInit(DataChannelInit()))
.MoveValue();
EXPECT_EQ(channel3->id(), channel1->id());
}
} // namespace
} // namespace webrtc

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,54 @@
/*
* Copyright 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/data_channel_utils.h"
#include <utility>
#include "rtc_base/checks.h"
namespace webrtc {
bool PacketQueue::Empty() const {
return packets_.empty();
}
std::unique_ptr<DataBuffer> PacketQueue::PopFront() {
RTC_DCHECK(!packets_.empty());
byte_count_ -= packets_.front()->size();
std::unique_ptr<DataBuffer> packet = std::move(packets_.front());
packets_.pop_front();
return packet;
}
void PacketQueue::PushFront(std::unique_ptr<DataBuffer> packet) {
byte_count_ += packet->size();
packets_.push_front(std::move(packet));
}
void PacketQueue::PushBack(std::unique_ptr<DataBuffer> packet) {
byte_count_ += packet->size();
packets_.push_back(std::move(packet));
}
void PacketQueue::Clear() {
packets_.clear();
byte_count_ = 0;
}
void PacketQueue::Swap(PacketQueue* other) {
size_t other_byte_count = other->byte_count_;
other->byte_count_ = byte_count_;
byte_count_ = other_byte_count;
other->packets_.swap(packets_);
}
} // namespace webrtc

View file

@ -0,0 +1,63 @@
/*
* Copyright 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef PC_DATA_CHANNEL_UTILS_H_
#define PC_DATA_CHANNEL_UTILS_H_
#include <stddef.h>
#include <stdint.h>
#include <deque>
#include <memory>
#include <string>
#include <utility>
#include "api/data_channel_interface.h"
#include "media/base/media_engine.h"
namespace webrtc {
// A packet queue which tracks the total queued bytes. Queued packets are
// owned by this class.
class PacketQueue final {
public:
size_t byte_count() const { return byte_count_; }
bool Empty() const;
std::unique_ptr<DataBuffer> PopFront();
void PushFront(std::unique_ptr<DataBuffer> packet);
void PushBack(std::unique_ptr<DataBuffer> packet);
void Clear();
void Swap(PacketQueue* other);
private:
std::deque<std::unique_ptr<DataBuffer>> packets_;
size_t byte_count_ = 0;
};
struct DataChannelStats {
int internal_id;
int id;
std::string label;
std::string protocol;
DataChannelInterface::DataState state;
uint32_t messages_sent;
uint32_t messages_received;
uint64_t bytes_sent;
uint64_t bytes_received;
};
} // namespace webrtc
#endif // PC_DATA_CHANNEL_UTILS_H_

View file

@ -0,0 +1,330 @@
/*
* Copyright 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/dtls_srtp_transport.h"
#include <string.h>
#include <string>
#include <utility>
#include "api/dtls_transport_interface.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/ssl_stream_adapter.h"
namespace {
// Value specified in RFC 5764.
static const char kDtlsSrtpExporterLabel[] = "EXTRACTOR-dtls_srtp";
} // namespace
namespace webrtc {
DtlsSrtpTransport::DtlsSrtpTransport(bool rtcp_mux_enabled,
const FieldTrialsView& field_trials)
: SrtpTransport(rtcp_mux_enabled, field_trials) {}
void DtlsSrtpTransport::SetDtlsTransports(
cricket::DtlsTransportInternal* rtp_dtls_transport,
cricket::DtlsTransportInternal* rtcp_dtls_transport) {
// Transport names should be the same.
if (rtp_dtls_transport && rtcp_dtls_transport) {
RTC_DCHECK(rtp_dtls_transport->transport_name() ==
rtcp_dtls_transport->transport_name());
}
// When using DTLS-SRTP, we must reset the SrtpTransport every time the
// DtlsTransport changes and wait until the DTLS handshake is complete to set
// the newly negotiated parameters.
// If `active_reset_srtp_params_` is true, intentionally reset the SRTP
// parameter even though the DtlsTransport may not change.
if (IsSrtpActive() && (rtp_dtls_transport != rtp_dtls_transport_ ||
active_reset_srtp_params_)) {
ResetParams();
}
const std::string transport_name =
rtp_dtls_transport ? rtp_dtls_transport->transport_name() : "null";
if (rtcp_dtls_transport && rtcp_dtls_transport != rtcp_dtls_transport_) {
// This would only be possible if using BUNDLE but not rtcp-mux, which isn't
// allowed according to the BUNDLE spec.
RTC_CHECK(!(IsSrtpActive()))
<< "Setting RTCP for DTLS/SRTP after the DTLS is active "
"should never happen.";
}
if (rtcp_dtls_transport) {
RTC_LOG(LS_INFO) << "Setting RTCP Transport on " << transport_name
<< " transport " << rtcp_dtls_transport;
}
SetRtcpDtlsTransport(rtcp_dtls_transport);
SetRtcpPacketTransport(rtcp_dtls_transport);
RTC_LOG(LS_INFO) << "Setting RTP Transport on " << transport_name
<< " transport " << rtp_dtls_transport;
SetRtpDtlsTransport(rtp_dtls_transport);
SetRtpPacketTransport(rtp_dtls_transport);
MaybeSetupDtlsSrtp();
}
void DtlsSrtpTransport::SetRtcpMuxEnabled(bool enable) {
SrtpTransport::SetRtcpMuxEnabled(enable);
if (enable) {
MaybeSetupDtlsSrtp();
}
}
void DtlsSrtpTransport::UpdateSendEncryptedHeaderExtensionIds(
const std::vector<int>& send_extension_ids) {
if (send_extension_ids_ == send_extension_ids) {
return;
}
send_extension_ids_.emplace(send_extension_ids);
if (DtlsHandshakeCompleted()) {
// Reset the crypto parameters to update the send extension IDs.
SetupRtpDtlsSrtp();
}
}
void DtlsSrtpTransport::UpdateRecvEncryptedHeaderExtensionIds(
const std::vector<int>& recv_extension_ids) {
if (recv_extension_ids_ == recv_extension_ids) {
return;
}
recv_extension_ids_.emplace(recv_extension_ids);
if (DtlsHandshakeCompleted()) {
// Reset the crypto parameters to update the receive extension IDs.
SetupRtpDtlsSrtp();
}
}
bool DtlsSrtpTransport::IsDtlsActive() {
auto rtcp_dtls_transport =
rtcp_mux_enabled() ? nullptr : rtcp_dtls_transport_;
return (rtp_dtls_transport_ && rtp_dtls_transport_->IsDtlsActive() &&
(!rtcp_dtls_transport || rtcp_dtls_transport->IsDtlsActive()));
}
bool DtlsSrtpTransport::IsDtlsConnected() {
auto rtcp_dtls_transport =
rtcp_mux_enabled() ? nullptr : rtcp_dtls_transport_;
return (rtp_dtls_transport_ &&
rtp_dtls_transport_->dtls_state() == DtlsTransportState::kConnected &&
(!rtcp_dtls_transport || rtcp_dtls_transport->dtls_state() ==
DtlsTransportState::kConnected));
}
bool DtlsSrtpTransport::IsDtlsWritable() {
auto rtcp_packet_transport =
rtcp_mux_enabled() ? nullptr : rtcp_dtls_transport_;
return rtp_dtls_transport_ && rtp_dtls_transport_->writable() &&
(!rtcp_packet_transport || rtcp_packet_transport->writable());
}
bool DtlsSrtpTransport::DtlsHandshakeCompleted() {
return IsDtlsActive() && IsDtlsConnected();
}
void DtlsSrtpTransport::MaybeSetupDtlsSrtp() {
if (IsSrtpActive() || !IsDtlsWritable()) {
return;
}
SetupRtpDtlsSrtp();
if (!rtcp_mux_enabled() && rtcp_dtls_transport_) {
SetupRtcpDtlsSrtp();
}
}
void DtlsSrtpTransport::SetupRtpDtlsSrtp() {
// Use an empty encrypted header extension ID vector if not set. This could
// happen when the DTLS handshake is completed before processing the
// Offer/Answer which contains the encrypted header extension IDs.
std::vector<int> send_extension_ids;
std::vector<int> recv_extension_ids;
if (send_extension_ids_) {
send_extension_ids = *send_extension_ids_;
}
if (recv_extension_ids_) {
recv_extension_ids = *recv_extension_ids_;
}
int selected_crypto_suite;
rtc::ZeroOnFreeBuffer<unsigned char> send_key;
rtc::ZeroOnFreeBuffer<unsigned char> recv_key;
if (!ExtractParams(rtp_dtls_transport_, &selected_crypto_suite, &send_key,
&recv_key) ||
!SetRtpParams(selected_crypto_suite, &send_key[0],
static_cast<int>(send_key.size()), send_extension_ids,
selected_crypto_suite, &recv_key[0],
static_cast<int>(recv_key.size()), recv_extension_ids)) {
RTC_LOG(LS_WARNING) << "DTLS-SRTP key installation for RTP failed";
}
}
void DtlsSrtpTransport::SetupRtcpDtlsSrtp() {
// Return if the DTLS-SRTP is active because the encrypted header extension
// IDs don't need to be updated for RTCP and the crypto params don't need to
// be reset.
if (IsSrtpActive()) {
return;
}
std::vector<int> send_extension_ids;
std::vector<int> recv_extension_ids;
if (send_extension_ids_) {
send_extension_ids = *send_extension_ids_;
}
if (recv_extension_ids_) {
recv_extension_ids = *recv_extension_ids_;
}
int selected_crypto_suite;
rtc::ZeroOnFreeBuffer<unsigned char> rtcp_send_key;
rtc::ZeroOnFreeBuffer<unsigned char> rtcp_recv_key;
if (!ExtractParams(rtcp_dtls_transport_, &selected_crypto_suite,
&rtcp_send_key, &rtcp_recv_key) ||
!SetRtcpParams(selected_crypto_suite, &rtcp_send_key[0],
static_cast<int>(rtcp_send_key.size()), send_extension_ids,
selected_crypto_suite, &rtcp_recv_key[0],
static_cast<int>(rtcp_recv_key.size()),
recv_extension_ids)) {
RTC_LOG(LS_WARNING) << "DTLS-SRTP key installation for RTCP failed";
}
}
bool DtlsSrtpTransport::ExtractParams(
cricket::DtlsTransportInternal* dtls_transport,
int* selected_crypto_suite,
rtc::ZeroOnFreeBuffer<unsigned char>* send_key,
rtc::ZeroOnFreeBuffer<unsigned char>* recv_key) {
if (!dtls_transport || !dtls_transport->IsDtlsActive()) {
return false;
}
if (!dtls_transport->GetSrtpCryptoSuite(selected_crypto_suite)) {
RTC_LOG(LS_ERROR) << "No DTLS-SRTP selected crypto suite";
return false;
}
RTC_LOG(LS_INFO) << "Extracting keys from transport: "
<< dtls_transport->transport_name();
int key_len;
int salt_len;
if (!rtc::GetSrtpKeyAndSaltLengths((*selected_crypto_suite), &key_len,
&salt_len)) {
RTC_LOG(LS_ERROR) << "Unknown DTLS-SRTP crypto suite"
<< selected_crypto_suite;
return false;
}
// OK, we're now doing DTLS (RFC 5764)
rtc::ZeroOnFreeBuffer<unsigned char> dtls_buffer(key_len * 2 + salt_len * 2);
// RFC 5705 exporter using the RFC 5764 parameters
if (!dtls_transport->ExportKeyingMaterial(kDtlsSrtpExporterLabel, NULL, 0,
false, &dtls_buffer[0],
dtls_buffer.size())) {
RTC_LOG(LS_WARNING) << "DTLS-SRTP key export failed";
RTC_DCHECK_NOTREACHED(); // This should never happen
return false;
}
// Sync up the keys with the DTLS-SRTP interface
rtc::ZeroOnFreeBuffer<unsigned char> client_write_key(key_len + salt_len);
rtc::ZeroOnFreeBuffer<unsigned char> server_write_key(key_len + salt_len);
size_t offset = 0;
memcpy(&client_write_key[0], &dtls_buffer[offset], key_len);
offset += key_len;
memcpy(&server_write_key[0], &dtls_buffer[offset], key_len);
offset += key_len;
memcpy(&client_write_key[key_len], &dtls_buffer[offset], salt_len);
offset += salt_len;
memcpy(&server_write_key[key_len], &dtls_buffer[offset], salt_len);
rtc::SSLRole role;
if (!dtls_transport->GetDtlsRole(&role)) {
RTC_LOG(LS_WARNING) << "Failed to get the DTLS role.";
return false;
}
if (role == rtc::SSL_SERVER) {
*send_key = std::move(server_write_key);
*recv_key = std::move(client_write_key);
} else {
*send_key = std::move(client_write_key);
*recv_key = std::move(server_write_key);
}
return true;
}
void DtlsSrtpTransport::SetDtlsTransport(
cricket::DtlsTransportInternal* new_dtls_transport,
cricket::DtlsTransportInternal** old_dtls_transport) {
if (*old_dtls_transport == new_dtls_transport) {
return;
}
if (*old_dtls_transport) {
(*old_dtls_transport)->UnsubscribeDtlsTransportState(this);
}
*old_dtls_transport = new_dtls_transport;
if (new_dtls_transport) {
new_dtls_transport->SubscribeDtlsTransportState(
this,
[this](cricket::DtlsTransportInternal* transport,
DtlsTransportState state) { OnDtlsState(transport, state); });
}
}
void DtlsSrtpTransport::SetRtpDtlsTransport(
cricket::DtlsTransportInternal* rtp_dtls_transport) {
SetDtlsTransport(rtp_dtls_transport, &rtp_dtls_transport_);
}
void DtlsSrtpTransport::SetRtcpDtlsTransport(
cricket::DtlsTransportInternal* rtcp_dtls_transport) {
SetDtlsTransport(rtcp_dtls_transport, &rtcp_dtls_transport_);
}
void DtlsSrtpTransport::OnDtlsState(cricket::DtlsTransportInternal* transport,
DtlsTransportState state) {
RTC_DCHECK(transport == rtp_dtls_transport_ ||
transport == rtcp_dtls_transport_);
if (on_dtls_state_change_) {
on_dtls_state_change_();
}
if (state != DtlsTransportState::kConnected) {
ResetParams();
return;
}
MaybeSetupDtlsSrtp();
}
void DtlsSrtpTransport::OnWritableState(
rtc::PacketTransportInternal* packet_transport) {
MaybeSetupDtlsSrtp();
}
void DtlsSrtpTransport::SetOnDtlsStateChange(
std::function<void(void)> callback) {
on_dtls_state_change_ = std::move(callback);
}
} // namespace webrtc

View file

@ -0,0 +1,95 @@
/*
* Copyright 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef PC_DTLS_SRTP_TRANSPORT_H_
#define PC_DTLS_SRTP_TRANSPORT_H_
#include <functional>
#include <string>
#include <vector>
#include "absl/types/optional.h"
#include "api/dtls_transport_interface.h"
#include "api/rtc_error.h"
#include "p2p/base/dtls_transport_internal.h"
#include "p2p/base/packet_transport_internal.h"
#include "pc/srtp_transport.h"
#include "rtc_base/buffer.h"
namespace webrtc {
// The subclass of SrtpTransport is used for DTLS-SRTP. When the DTLS handshake
// is finished, it extracts the keying materials from DtlsTransport and
// configures the SrtpSessions in the base class.
class DtlsSrtpTransport : public SrtpTransport {
public:
DtlsSrtpTransport(bool rtcp_mux_enabled, const FieldTrialsView& field_trials);
// Set P2P layer RTP/RTCP DtlsTransports. When using RTCP-muxing,
// `rtcp_dtls_transport` is null.
void SetDtlsTransports(cricket::DtlsTransportInternal* rtp_dtls_transport,
cricket::DtlsTransportInternal* rtcp_dtls_transport);
void SetRtcpMuxEnabled(bool enable) override;
// Set the header extension ids that should be encrypted.
void UpdateSendEncryptedHeaderExtensionIds(
const std::vector<int>& send_extension_ids);
void UpdateRecvEncryptedHeaderExtensionIds(
const std::vector<int>& recv_extension_ids);
void SetOnDtlsStateChange(std::function<void(void)> callback);
// If `active_reset_srtp_params_` is set to be true, the SRTP parameters will
// be reset whenever the DtlsTransports are reset.
void SetActiveResetSrtpParams(bool active_reset_srtp_params) {
active_reset_srtp_params_ = active_reset_srtp_params;
}
private:
bool IsDtlsActive();
bool IsDtlsConnected();
bool IsDtlsWritable();
bool DtlsHandshakeCompleted();
void MaybeSetupDtlsSrtp();
void SetupRtpDtlsSrtp();
void SetupRtcpDtlsSrtp();
bool ExtractParams(cricket::DtlsTransportInternal* dtls_transport,
int* selected_crypto_suite,
rtc::ZeroOnFreeBuffer<unsigned char>* send_key,
rtc::ZeroOnFreeBuffer<unsigned char>* recv_key);
void SetDtlsTransport(cricket::DtlsTransportInternal* new_dtls_transport,
cricket::DtlsTransportInternal** old_dtls_transport);
void SetRtpDtlsTransport(cricket::DtlsTransportInternal* rtp_dtls_transport);
void SetRtcpDtlsTransport(
cricket::DtlsTransportInternal* rtcp_dtls_transport);
void OnDtlsState(cricket::DtlsTransportInternal* dtls_transport,
DtlsTransportState state);
// Override the SrtpTransport::OnWritableState.
void OnWritableState(rtc::PacketTransportInternal* packet_transport) override;
// Owned by the TransportController.
cricket::DtlsTransportInternal* rtp_dtls_transport_ = nullptr;
cricket::DtlsTransportInternal* rtcp_dtls_transport_ = nullptr;
// The encrypted header extension IDs.
absl::optional<std::vector<int>> send_extension_ids_;
absl::optional<std::vector<int>> recv_extension_ids_;
bool active_reset_srtp_params_ = false;
std::function<void(void)> on_dtls_state_change_;
};
} // namespace webrtc
#endif // PC_DTLS_SRTP_TRANSPORT_H_

View file

@ -0,0 +1,576 @@
/*
* Copyright 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/dtls_srtp_transport.h"
#include <string.h>
#include <cstdint>
#include <memory>
#include "call/rtp_demuxer.h"
#include "media/base/fake_rtp.h"
#include "p2p/base/dtls_transport_internal.h"
#include "p2p/base/fake_dtls_transport.h"
#include "p2p/base/fake_ice_transport.h"
#include "p2p/base/p2p_constants.h"
#include "pc/rtp_transport.h"
#include "pc/test/rtp_transport_test_util.h"
#include "rtc_base/async_packet_socket.h"
#include "rtc_base/byte_order.h"
#include "rtc_base/containers/flat_set.h"
#include "rtc_base/copy_on_write_buffer.h"
#include "rtc_base/rtc_certificate.h"
#include "rtc_base/ssl_identity.h"
#include "rtc_base/third_party/sigslot/sigslot.h"
#include "test/gtest.h"
#include "test/scoped_key_value_config.h"
using cricket::FakeDtlsTransport;
using cricket::FakeIceTransport;
using webrtc::DtlsSrtpTransport;
using webrtc::RtpTransport;
using webrtc::SrtpTransport;
const int kRtpAuthTagLen = 10;
class DtlsSrtpTransportTest : public ::testing::Test,
public sigslot::has_slots<> {
protected:
DtlsSrtpTransportTest() {}
~DtlsSrtpTransportTest() {
if (dtls_srtp_transport1_) {
dtls_srtp_transport1_->UnregisterRtpDemuxerSink(&transport_observer1_);
}
if (dtls_srtp_transport2_) {
dtls_srtp_transport2_->UnregisterRtpDemuxerSink(&transport_observer2_);
}
}
std::unique_ptr<DtlsSrtpTransport> MakeDtlsSrtpTransport(
FakeDtlsTransport* rtp_dtls,
FakeDtlsTransport* rtcp_dtls,
bool rtcp_mux_enabled) {
auto dtls_srtp_transport =
std::make_unique<DtlsSrtpTransport>(rtcp_mux_enabled, field_trials_);
dtls_srtp_transport->SetDtlsTransports(rtp_dtls, rtcp_dtls);
return dtls_srtp_transport;
}
void MakeDtlsSrtpTransports(FakeDtlsTransport* rtp_dtls1,
FakeDtlsTransport* rtcp_dtls1,
FakeDtlsTransport* rtp_dtls2,
FakeDtlsTransport* rtcp_dtls2,
bool rtcp_mux_enabled) {
dtls_srtp_transport1_ =
MakeDtlsSrtpTransport(rtp_dtls1, rtcp_dtls1, rtcp_mux_enabled);
dtls_srtp_transport2_ =
MakeDtlsSrtpTransport(rtp_dtls2, rtcp_dtls2, rtcp_mux_enabled);
dtls_srtp_transport1_->SubscribeRtcpPacketReceived(
&transport_observer1_,
[this](rtc::CopyOnWriteBuffer* buffer, int64_t packet_time_ms) {
transport_observer1_.OnRtcpPacketReceived(buffer, packet_time_ms);
});
dtls_srtp_transport1_->SubscribeReadyToSend(
&transport_observer1_,
[this](bool ready) { transport_observer1_.OnReadyToSend(ready); });
dtls_srtp_transport2_->SubscribeRtcpPacketReceived(
&transport_observer2_,
[this](rtc::CopyOnWriteBuffer* buffer, int64_t packet_time_ms) {
transport_observer2_.OnRtcpPacketReceived(buffer, packet_time_ms);
});
dtls_srtp_transport2_->SubscribeReadyToSend(
&transport_observer2_,
[this](bool ready) { transport_observer2_.OnReadyToSend(ready); });
webrtc::RtpDemuxerCriteria demuxer_criteria;
// 0x00 is the payload type used in kPcmuFrame.
demuxer_criteria.payload_types() = {0x00};
dtls_srtp_transport1_->RegisterRtpDemuxerSink(demuxer_criteria,
&transport_observer1_);
dtls_srtp_transport2_->RegisterRtpDemuxerSink(demuxer_criteria,
&transport_observer2_);
}
void CompleteDtlsHandshake(FakeDtlsTransport* fake_dtls1,
FakeDtlsTransport* fake_dtls2) {
auto cert1 = rtc::RTCCertificate::Create(
rtc::SSLIdentity::Create("session1", rtc::KT_DEFAULT));
fake_dtls1->SetLocalCertificate(cert1);
auto cert2 = rtc::RTCCertificate::Create(
rtc::SSLIdentity::Create("session1", rtc::KT_DEFAULT));
fake_dtls2->SetLocalCertificate(cert2);
fake_dtls1->SetDestination(fake_dtls2);
}
void SendRecvRtpPackets() {
ASSERT_TRUE(dtls_srtp_transport1_);
ASSERT_TRUE(dtls_srtp_transport2_);
ASSERT_TRUE(dtls_srtp_transport1_->IsSrtpActive());
ASSERT_TRUE(dtls_srtp_transport2_->IsSrtpActive());
size_t rtp_len = sizeof(kPcmuFrame);
size_t packet_size = rtp_len + kRtpAuthTagLen;
rtc::Buffer rtp_packet_buffer(packet_size);
char* rtp_packet_data = rtp_packet_buffer.data<char>();
memcpy(rtp_packet_data, kPcmuFrame, rtp_len);
// In order to be able to run this test function multiple times we can not
// use the same sequence number twice. Increase the sequence number by one.
rtc::SetBE16(reinterpret_cast<uint8_t*>(rtp_packet_data) + 2,
++sequence_number_);
rtc::CopyOnWriteBuffer rtp_packet1to2(rtp_packet_data, rtp_len,
packet_size);
rtc::CopyOnWriteBuffer rtp_packet2to1(rtp_packet_data, rtp_len,
packet_size);
rtc::PacketOptions options;
// Send a packet from `srtp_transport1_` to `srtp_transport2_` and verify
// that the packet can be successfully received and decrypted.
int prev_received_packets = transport_observer2_.rtp_count();
ASSERT_TRUE(dtls_srtp_transport1_->SendRtpPacket(&rtp_packet1to2, options,
cricket::PF_SRTP_BYPASS));
ASSERT_TRUE(transport_observer2_.last_recv_rtp_packet().data());
EXPECT_EQ(0, memcmp(transport_observer2_.last_recv_rtp_packet().data(),
kPcmuFrame, rtp_len));
EXPECT_EQ(prev_received_packets + 1, transport_observer2_.rtp_count());
prev_received_packets = transport_observer1_.rtp_count();
ASSERT_TRUE(dtls_srtp_transport2_->SendRtpPacket(&rtp_packet2to1, options,
cricket::PF_SRTP_BYPASS));
ASSERT_TRUE(transport_observer1_.last_recv_rtp_packet().data());
EXPECT_EQ(0, memcmp(transport_observer1_.last_recv_rtp_packet().data(),
kPcmuFrame, rtp_len));
EXPECT_EQ(prev_received_packets + 1, transport_observer1_.rtp_count());
}
void SendRecvRtcpPackets() {
size_t rtcp_len = sizeof(kRtcpReport);
size_t packet_size = rtcp_len + 4 + kRtpAuthTagLen;
rtc::Buffer rtcp_packet_buffer(packet_size);
// TODO(zhihuang): Remove the extra copy when the SendRtpPacket method
// doesn't take the CopyOnWriteBuffer by pointer.
rtc::CopyOnWriteBuffer rtcp_packet1to2(kRtcpReport, rtcp_len, packet_size);
rtc::CopyOnWriteBuffer rtcp_packet2to1(kRtcpReport, rtcp_len, packet_size);
rtc::PacketOptions options;
// Send a packet from `srtp_transport1_` to `srtp_transport2_` and verify
// that the packet can be successfully received and decrypted.
int prev_received_packets = transport_observer2_.rtcp_count();
ASSERT_TRUE(dtls_srtp_transport1_->SendRtcpPacket(&rtcp_packet1to2, options,
cricket::PF_SRTP_BYPASS));
ASSERT_TRUE(transport_observer2_.last_recv_rtcp_packet().data());
EXPECT_EQ(0, memcmp(transport_observer2_.last_recv_rtcp_packet().data(),
kRtcpReport, rtcp_len));
EXPECT_EQ(prev_received_packets + 1, transport_observer2_.rtcp_count());
// Do the same thing in the opposite direction;
prev_received_packets = transport_observer1_.rtcp_count();
ASSERT_TRUE(dtls_srtp_transport2_->SendRtcpPacket(&rtcp_packet2to1, options,
cricket::PF_SRTP_BYPASS));
ASSERT_TRUE(transport_observer1_.last_recv_rtcp_packet().data());
EXPECT_EQ(0, memcmp(transport_observer1_.last_recv_rtcp_packet().data(),
kRtcpReport, rtcp_len));
EXPECT_EQ(prev_received_packets + 1, transport_observer1_.rtcp_count());
}
void SendRecvRtpPacketsWithHeaderExtension(
const std::vector<int>& encrypted_header_ids) {
ASSERT_TRUE(dtls_srtp_transport1_);
ASSERT_TRUE(dtls_srtp_transport2_);
ASSERT_TRUE(dtls_srtp_transport1_->IsSrtpActive());
ASSERT_TRUE(dtls_srtp_transport2_->IsSrtpActive());
size_t rtp_len = sizeof(kPcmuFrameWithExtensions);
size_t packet_size = rtp_len + kRtpAuthTagLen;
rtc::Buffer rtp_packet_buffer(packet_size);
char* rtp_packet_data = rtp_packet_buffer.data<char>();
memcpy(rtp_packet_data, kPcmuFrameWithExtensions, rtp_len);
// In order to be able to run this test function multiple times we can not
// use the same sequence number twice. Increase the sequence number by one.
rtc::SetBE16(reinterpret_cast<uint8_t*>(rtp_packet_data) + 2,
++sequence_number_);
rtc::CopyOnWriteBuffer rtp_packet1to2(rtp_packet_data, rtp_len,
packet_size);
rtc::CopyOnWriteBuffer rtp_packet2to1(rtp_packet_data, rtp_len,
packet_size);
char original_rtp_data[sizeof(kPcmuFrameWithExtensions)];
memcpy(original_rtp_data, rtp_packet_data, rtp_len);
rtc::PacketOptions options;
// Send a packet from `srtp_transport1_` to `srtp_transport2_` and verify
// that the packet can be successfully received and decrypted.
ASSERT_TRUE(dtls_srtp_transport1_->SendRtpPacket(&rtp_packet1to2, options,
cricket::PF_SRTP_BYPASS));
ASSERT_TRUE(transport_observer2_.last_recv_rtp_packet().data());
EXPECT_EQ(0, memcmp(transport_observer2_.last_recv_rtp_packet().data(),
original_rtp_data, rtp_len));
// Get the encrypted packet from underneath packet transport and verify the
// data and header extension are actually encrypted.
auto fake_dtls_transport = static_cast<FakeDtlsTransport*>(
dtls_srtp_transport1_->rtp_packet_transport());
auto fake_ice_transport =
static_cast<FakeIceTransport*>(fake_dtls_transport->ice_transport());
EXPECT_NE(0, memcmp(fake_ice_transport->last_sent_packet().data(),
original_rtp_data, rtp_len));
CompareHeaderExtensions(reinterpret_cast<const char*>(
fake_ice_transport->last_sent_packet().data()),
fake_ice_transport->last_sent_packet().size(),
original_rtp_data, rtp_len, encrypted_header_ids,
false);
// Do the same thing in the opposite direction.
ASSERT_TRUE(dtls_srtp_transport2_->SendRtpPacket(&rtp_packet2to1, options,
cricket::PF_SRTP_BYPASS));
ASSERT_TRUE(transport_observer1_.last_recv_rtp_packet().data());
EXPECT_EQ(0, memcmp(transport_observer1_.last_recv_rtp_packet().data(),
original_rtp_data, rtp_len));
// Get the encrypted packet from underneath packet transport and verify the
// data and header extension are actually encrypted.
fake_dtls_transport = static_cast<FakeDtlsTransport*>(
dtls_srtp_transport2_->rtp_packet_transport());
fake_ice_transport =
static_cast<FakeIceTransport*>(fake_dtls_transport->ice_transport());
EXPECT_NE(0, memcmp(fake_ice_transport->last_sent_packet().data(),
original_rtp_data, rtp_len));
CompareHeaderExtensions(reinterpret_cast<const char*>(
fake_ice_transport->last_sent_packet().data()),
fake_ice_transport->last_sent_packet().size(),
original_rtp_data, rtp_len, encrypted_header_ids,
false);
}
void SendRecvPackets() {
SendRecvRtpPackets();
SendRecvRtcpPackets();
}
rtc::AutoThread main_thread_;
std::unique_ptr<DtlsSrtpTransport> dtls_srtp_transport1_;
std::unique_ptr<DtlsSrtpTransport> dtls_srtp_transport2_;
webrtc::TransportObserver transport_observer1_;
webrtc::TransportObserver transport_observer2_;
int sequence_number_ = 0;
webrtc::test::ScopedKeyValueConfig field_trials_;
};
// Tests that if RTCP muxing is enabled and transports are set after RTP
// transport finished the handshake, SRTP is set up.
TEST_F(DtlsSrtpTransportTest, SetTransportsAfterHandshakeCompleteWithRtcpMux) {
auto rtp_dtls1 = std::make_unique<FakeDtlsTransport>(
"video", cricket::ICE_CANDIDATE_COMPONENT_RTP);
auto rtp_dtls2 = std::make_unique<FakeDtlsTransport>(
"video", cricket::ICE_CANDIDATE_COMPONENT_RTP);
MakeDtlsSrtpTransports(rtp_dtls1.get(), nullptr, rtp_dtls2.get(), nullptr,
/*rtcp_mux_enabled=*/true);
auto rtp_dtls3 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
auto rtp_dtls4 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
CompleteDtlsHandshake(rtp_dtls3.get(), rtp_dtls4.get());
dtls_srtp_transport1_->SetDtlsTransports(rtp_dtls3.get(), nullptr);
dtls_srtp_transport2_->SetDtlsTransports(rtp_dtls4.get(), nullptr);
SendRecvPackets();
}
// Tests that if RTCP muxing is not enabled and transports are set after both
// RTP and RTCP transports finished the handshake, SRTP is set up.
TEST_F(DtlsSrtpTransportTest,
SetTransportsAfterHandshakeCompleteWithoutRtcpMux) {
auto rtp_dtls1 = std::make_unique<FakeDtlsTransport>(
"video", cricket::ICE_CANDIDATE_COMPONENT_RTP);
auto rtcp_dtls1 = std::make_unique<FakeDtlsTransport>(
"video", cricket::ICE_CANDIDATE_COMPONENT_RTCP);
auto rtp_dtls2 = std::make_unique<FakeDtlsTransport>(
"video", cricket::ICE_CANDIDATE_COMPONENT_RTP);
auto rtcp_dtls2 = std::make_unique<FakeDtlsTransport>(
"video", cricket::ICE_CANDIDATE_COMPONENT_RTCP);
MakeDtlsSrtpTransports(rtp_dtls1.get(), rtcp_dtls1.get(), rtp_dtls2.get(),
rtcp_dtls2.get(), /*rtcp_mux_enabled=*/false);
auto rtp_dtls3 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
auto rtcp_dtls3 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTCP);
auto rtp_dtls4 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
auto rtcp_dtls4 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTCP);
CompleteDtlsHandshake(rtp_dtls3.get(), rtp_dtls4.get());
CompleteDtlsHandshake(rtcp_dtls3.get(), rtcp_dtls4.get());
dtls_srtp_transport1_->SetDtlsTransports(rtp_dtls3.get(), rtcp_dtls3.get());
dtls_srtp_transport2_->SetDtlsTransports(rtp_dtls4.get(), rtcp_dtls4.get());
SendRecvPackets();
}
// Tests if RTCP muxing is enabled, SRTP is set up as soon as the RTP DTLS
// handshake is finished.
TEST_F(DtlsSrtpTransportTest, SetTransportsBeforeHandshakeCompleteWithRtcpMux) {
auto rtp_dtls1 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
auto rtcp_dtls1 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTCP);
auto rtp_dtls2 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
auto rtcp_dtls2 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTCP);
MakeDtlsSrtpTransports(rtp_dtls1.get(), rtcp_dtls1.get(), rtp_dtls2.get(),
rtcp_dtls2.get(),
/*rtcp_mux_enabled=*/false);
dtls_srtp_transport1_->SetRtcpMuxEnabled(true);
dtls_srtp_transport2_->SetRtcpMuxEnabled(true);
CompleteDtlsHandshake(rtp_dtls1.get(), rtp_dtls2.get());
SendRecvPackets();
}
// Tests if RTCP muxing is not enabled, SRTP is set up when both the RTP and
// RTCP DTLS handshake are finished.
TEST_F(DtlsSrtpTransportTest,
SetTransportsBeforeHandshakeCompleteWithoutRtcpMux) {
auto rtp_dtls1 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
auto rtcp_dtls1 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTCP);
auto rtp_dtls2 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
auto rtcp_dtls2 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTCP);
MakeDtlsSrtpTransports(rtp_dtls1.get(), rtcp_dtls1.get(), rtp_dtls2.get(),
rtcp_dtls2.get(), /*rtcp_mux_enabled=*/false);
CompleteDtlsHandshake(rtp_dtls1.get(), rtp_dtls2.get());
EXPECT_FALSE(dtls_srtp_transport1_->IsSrtpActive());
EXPECT_FALSE(dtls_srtp_transport2_->IsSrtpActive());
CompleteDtlsHandshake(rtcp_dtls1.get(), rtcp_dtls2.get());
SendRecvPackets();
}
// Tests that if the DtlsTransport underneath is changed, the previous DTLS-SRTP
// context will be reset and will be re-setup once the new transports' handshake
// complete.
TEST_F(DtlsSrtpTransportTest, DtlsSrtpResetAfterDtlsTransportChange) {
auto rtp_dtls1 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
auto rtp_dtls2 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
MakeDtlsSrtpTransports(rtp_dtls1.get(), nullptr, rtp_dtls2.get(), nullptr,
/*rtcp_mux_enabled=*/true);
CompleteDtlsHandshake(rtp_dtls1.get(), rtp_dtls2.get());
EXPECT_TRUE(dtls_srtp_transport1_->IsSrtpActive());
EXPECT_TRUE(dtls_srtp_transport2_->IsSrtpActive());
auto rtp_dtls3 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
auto rtp_dtls4 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
// The previous context is reset.
dtls_srtp_transport1_->SetDtlsTransports(rtp_dtls3.get(), nullptr);
dtls_srtp_transport2_->SetDtlsTransports(rtp_dtls4.get(), nullptr);
EXPECT_FALSE(dtls_srtp_transport1_->IsSrtpActive());
EXPECT_FALSE(dtls_srtp_transport2_->IsSrtpActive());
// Re-setup.
CompleteDtlsHandshake(rtp_dtls3.get(), rtp_dtls4.get());
SendRecvPackets();
}
// Tests if only the RTP DTLS handshake complete, and then RTCP muxing is
// enabled, SRTP is set up.
TEST_F(DtlsSrtpTransportTest,
RtcpMuxEnabledAfterRtpTransportHandshakeComplete) {
auto rtp_dtls1 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
auto rtcp_dtls1 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTCP);
auto rtp_dtls2 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
auto rtcp_dtls2 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTCP);
MakeDtlsSrtpTransports(rtp_dtls1.get(), rtcp_dtls1.get(), rtp_dtls2.get(),
rtcp_dtls2.get(), /*rtcp_mux_enabled=*/false);
CompleteDtlsHandshake(rtp_dtls1.get(), rtp_dtls2.get());
// Inactive because the RTCP transport handshake didn't complete.
EXPECT_FALSE(dtls_srtp_transport1_->IsSrtpActive());
EXPECT_FALSE(dtls_srtp_transport2_->IsSrtpActive());
dtls_srtp_transport1_->SetRtcpMuxEnabled(true);
dtls_srtp_transport2_->SetRtcpMuxEnabled(true);
// The transports should be active and be able to send packets when the
// RTCP muxing is enabled.
SendRecvPackets();
}
// Tests that when SetSend/RecvEncryptedHeaderExtensionIds is called, the SRTP
// sessions are updated with new encryped header extension IDs immediately.
TEST_F(DtlsSrtpTransportTest, EncryptedHeaderExtensionIdUpdated) {
auto rtp_dtls1 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
auto rtp_dtls2 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
MakeDtlsSrtpTransports(rtp_dtls1.get(), nullptr, rtp_dtls2.get(), nullptr,
/*rtcp_mux_enabled=*/true);
CompleteDtlsHandshake(rtp_dtls1.get(), rtp_dtls2.get());
std::vector<int> encrypted_headers;
encrypted_headers.push_back(kHeaderExtensionIDs[0]);
encrypted_headers.push_back(kHeaderExtensionIDs[1]);
dtls_srtp_transport1_->UpdateSendEncryptedHeaderExtensionIds(
encrypted_headers);
dtls_srtp_transport1_->UpdateRecvEncryptedHeaderExtensionIds(
encrypted_headers);
dtls_srtp_transport2_->UpdateSendEncryptedHeaderExtensionIds(
encrypted_headers);
dtls_srtp_transport2_->UpdateRecvEncryptedHeaderExtensionIds(
encrypted_headers);
}
// Tests if RTCP muxing is enabled. DtlsSrtpTransport is ready to send once the
// RTP DtlsTransport is ready.
TEST_F(DtlsSrtpTransportTest, SignalReadyToSendFiredWithRtcpMux) {
auto rtp_dtls1 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
auto rtp_dtls2 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
MakeDtlsSrtpTransports(rtp_dtls1.get(), nullptr, rtp_dtls2.get(), nullptr,
/*rtcp_mux_enabled=*/true);
rtp_dtls1->SetDestination(rtp_dtls2.get());
EXPECT_TRUE(transport_observer1_.ready_to_send());
EXPECT_TRUE(transport_observer2_.ready_to_send());
}
// Tests if RTCP muxing is not enabled. DtlsSrtpTransport is ready to send once
// both the RTP and RTCP DtlsTransport are ready.
TEST_F(DtlsSrtpTransportTest, SignalReadyToSendFiredWithoutRtcpMux) {
auto rtp_dtls1 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
auto rtcp_dtls1 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTCP);
auto rtp_dtls2 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
auto rtcp_dtls2 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTCP);
MakeDtlsSrtpTransports(rtp_dtls1.get(), rtcp_dtls1.get(), rtp_dtls2.get(),
rtcp_dtls2.get(), /*rtcp_mux_enabled=*/false);
rtp_dtls1->SetDestination(rtp_dtls2.get());
EXPECT_FALSE(transport_observer1_.ready_to_send());
EXPECT_FALSE(transport_observer2_.ready_to_send());
rtcp_dtls1->SetDestination(rtcp_dtls2.get());
EXPECT_TRUE(transport_observer1_.ready_to_send());
EXPECT_TRUE(transport_observer2_.ready_to_send());
}
// Test that if an endpoint "fully" enables RTCP mux, setting the RTCP
// transport to null, it *doesn't* reset its SRTP context. That would cause the
// ROC and SRTCP index to be reset, causing replay detection and other errors
// when attempting to unprotect packets.
// Regression test for bugs.webrtc.org/8996
TEST_F(DtlsSrtpTransportTest, SrtpSessionNotResetWhenRtcpTransportRemoved) {
auto rtp_dtls1 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
auto rtcp_dtls1 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTCP);
auto rtp_dtls2 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
auto rtcp_dtls2 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTCP);
MakeDtlsSrtpTransports(rtp_dtls1.get(), rtcp_dtls1.get(), rtp_dtls2.get(),
rtcp_dtls2.get(), /*rtcp_mux_enabled=*/true);
CompleteDtlsHandshake(rtp_dtls1.get(), rtp_dtls2.get());
CompleteDtlsHandshake(rtcp_dtls1.get(), rtcp_dtls2.get());
// Send some RTCP packets, causing the SRTCP index to be incremented.
SendRecvRtcpPackets();
// Set RTCP transport to null, which previously would trigger this problem.
dtls_srtp_transport1_->SetDtlsTransports(rtp_dtls1.get(), nullptr);
// Attempt to send more RTCP packets. If the issue occurred, one side would
// reset its context while the other would not, causing replay detection
// errors when a packet with a duplicate SRTCP index is received.
SendRecvRtcpPackets();
}
// Tests that RTCP packets can be sent and received if both sides actively reset
// the SRTP parameters with the `active_reset_srtp_params_` flag.
TEST_F(DtlsSrtpTransportTest, ActivelyResetSrtpParams) {
auto rtp_dtls1 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
auto rtcp_dtls1 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTCP);
auto rtp_dtls2 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
auto rtcp_dtls2 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTCP);
MakeDtlsSrtpTransports(rtp_dtls1.get(), rtcp_dtls1.get(), rtp_dtls2.get(),
rtcp_dtls2.get(), /*rtcp_mux_enabled=*/true);
CompleteDtlsHandshake(rtp_dtls1.get(), rtp_dtls2.get());
CompleteDtlsHandshake(rtcp_dtls1.get(), rtcp_dtls2.get());
// Send some RTCP packets, causing the SRTCP index to be incremented.
SendRecvRtcpPackets();
// Only set the `active_reset_srtp_params_` flag to be true one side.
dtls_srtp_transport1_->SetActiveResetSrtpParams(true);
// Set RTCP transport to null to trigger the SRTP parameters update.
dtls_srtp_transport1_->SetDtlsTransports(rtp_dtls1.get(), nullptr);
dtls_srtp_transport2_->SetDtlsTransports(rtp_dtls2.get(), nullptr);
// Sending some RTCP packets.
size_t rtcp_len = sizeof(kRtcpReport);
size_t packet_size = rtcp_len + 4 + kRtpAuthTagLen;
rtc::Buffer rtcp_packet_buffer(packet_size);
rtc::CopyOnWriteBuffer rtcp_packet(kRtcpReport, rtcp_len, packet_size);
int prev_received_packets = transport_observer2_.rtcp_count();
ASSERT_TRUE(dtls_srtp_transport1_->SendRtcpPacket(
&rtcp_packet, rtc::PacketOptions(), cricket::PF_SRTP_BYPASS));
// The RTCP packet is not exepected to be received because the SRTP parameters
// are only reset on one side and the SRTCP index is out of sync.
EXPECT_EQ(prev_received_packets, transport_observer2_.rtcp_count());
// Set the flag to be true on the other side.
dtls_srtp_transport2_->SetActiveResetSrtpParams(true);
// Set RTCP transport to null to trigger the SRTP parameters update.
dtls_srtp_transport1_->SetDtlsTransports(rtp_dtls1.get(), nullptr);
dtls_srtp_transport2_->SetDtlsTransports(rtp_dtls2.get(), nullptr);
// RTCP packets flow is expected to work just fine.
SendRecvRtcpPackets();
}

View file

@ -0,0 +1,152 @@
/*
* Copyright 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/dtls_transport.h"
#include <utility>
#include "absl/types/optional.h"
#include "api/dtls_transport_interface.h"
#include "api/make_ref_counted.h"
#include "api/sequence_checker.h"
#include "pc/ice_transport.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/ssl_stream_adapter.h"
namespace webrtc {
// Implementation of DtlsTransportInterface
DtlsTransport::DtlsTransport(
std::unique_ptr<cricket::DtlsTransportInternal> internal)
: owner_thread_(rtc::Thread::Current()),
info_(DtlsTransportState::kNew),
internal_dtls_transport_(std::move(internal)),
ice_transport_(rtc::make_ref_counted<IceTransportWithPointer>(
internal_dtls_transport_->ice_transport())) {
RTC_DCHECK(internal_dtls_transport_.get());
internal_dtls_transport_->SubscribeDtlsTransportState(
[this](cricket::DtlsTransportInternal* transport,
DtlsTransportState state) {
OnInternalDtlsState(transport, state);
});
UpdateInformation();
}
DtlsTransport::~DtlsTransport() {
// TODO(tommi): Due to a reference being held by the RtpSenderBase
// implementation, the last reference to the `DtlsTransport` instance can
// be released on the signaling thread.
// RTC_DCHECK_RUN_ON(owner_thread_);
// We depend on the signaling thread to call Clear() before dropping
// its last reference to this object.
// If there are non `owner_thread_` references outstanding, and those
// references are the last ones released, we depend on Clear() having been
// called from the owner_thread before the last reference is deleted.
// `Clear()` is currently called from `JsepTransport::~JsepTransport`.
RTC_DCHECK(owner_thread_->IsCurrent() || !internal_dtls_transport_);
}
DtlsTransportInformation DtlsTransport::Information() {
MutexLock lock(&lock_);
return info_;
}
void DtlsTransport::RegisterObserver(DtlsTransportObserverInterface* observer) {
RTC_DCHECK_RUN_ON(owner_thread_);
RTC_DCHECK(observer);
observer_ = observer;
}
void DtlsTransport::UnregisterObserver() {
RTC_DCHECK_RUN_ON(owner_thread_);
observer_ = nullptr;
}
rtc::scoped_refptr<IceTransportInterface> DtlsTransport::ice_transport() {
return ice_transport_;
}
// Internal functions
void DtlsTransport::Clear() {
RTC_DCHECK_RUN_ON(owner_thread_);
RTC_DCHECK(internal());
bool must_send_event =
(internal()->dtls_state() != DtlsTransportState::kClosed);
internal_dtls_transport_.reset();
ice_transport_->Clear();
UpdateInformation();
if (observer_ && must_send_event) {
observer_->OnStateChange(Information());
}
}
void DtlsTransport::OnInternalDtlsState(
cricket::DtlsTransportInternal* transport,
DtlsTransportState state) {
RTC_DCHECK_RUN_ON(owner_thread_);
RTC_DCHECK(transport == internal());
RTC_DCHECK(state == internal()->dtls_state());
UpdateInformation();
if (observer_) {
observer_->OnStateChange(Information());
}
}
void DtlsTransport::UpdateInformation() {
RTC_DCHECK_RUN_ON(owner_thread_);
if (internal_dtls_transport_) {
if (internal_dtls_transport_->dtls_state() ==
DtlsTransportState::kConnected) {
bool success = true;
rtc::SSLRole internal_role;
absl::optional<DtlsTransportTlsRole> role;
int ssl_cipher_suite;
int tls_version;
int srtp_cipher;
success &= internal_dtls_transport_->GetDtlsRole(&internal_role);
if (success) {
switch (internal_role) {
case rtc::SSL_CLIENT:
role = DtlsTransportTlsRole::kClient;
break;
case rtc::SSL_SERVER:
role = DtlsTransportTlsRole::kServer;
break;
}
}
success &= internal_dtls_transport_->GetSslVersionBytes(&tls_version);
success &= internal_dtls_transport_->GetSslCipherSuite(&ssl_cipher_suite);
success &= internal_dtls_transport_->GetSrtpCryptoSuite(&srtp_cipher);
if (success) {
set_info(DtlsTransportInformation(
internal_dtls_transport_->dtls_state(), role, tls_version,
ssl_cipher_suite, srtp_cipher,
internal_dtls_transport_->GetRemoteSSLCertChain()));
} else {
RTC_LOG(LS_ERROR) << "DtlsTransport in connected state has incomplete "
"TLS information";
set_info(DtlsTransportInformation(
internal_dtls_transport_->dtls_state(), role, absl::nullopt,
absl::nullopt, absl::nullopt,
internal_dtls_transport_->GetRemoteSSLCertChain()));
}
} else {
set_info(
DtlsTransportInformation(internal_dtls_transport_->dtls_state()));
}
} else {
set_info(DtlsTransportInformation(DtlsTransportState::kClosed));
}
}
} // namespace webrtc

View file

@ -0,0 +1,88 @@
/*
* Copyright 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef PC_DTLS_TRANSPORT_H_
#define PC_DTLS_TRANSPORT_H_
#include <memory>
#include <utility>
#include "api/dtls_transport_interface.h"
#include "api/ice_transport_interface.h"
#include "api/scoped_refptr.h"
#include "p2p/base/dtls_transport.h"
#include "p2p/base/dtls_transport_internal.h"
#include "pc/ice_transport.h"
#include "rtc_base/synchronization/mutex.h"
#include "rtc_base/thread.h"
#include "rtc_base/thread_annotations.h"
namespace webrtc {
class IceTransportWithPointer;
// This implementation wraps a cricket::DtlsTransport, and takes
// ownership of it.
class DtlsTransport : public DtlsTransportInterface {
public:
// This object must be constructed and updated on a consistent thread,
// the same thread as the one the cricket::DtlsTransportInternal object
// lives on.
// The Information() function can be called from a different thread,
// such as the signalling thread.
explicit DtlsTransport(
std::unique_ptr<cricket::DtlsTransportInternal> internal);
rtc::scoped_refptr<IceTransportInterface> ice_transport() override;
// Currently called from the signaling thread and potentially Chromium's
// JS thread.
DtlsTransportInformation Information() override;
void RegisterObserver(DtlsTransportObserverInterface* observer) override;
void UnregisterObserver() override;
void Clear();
cricket::DtlsTransportInternal* internal() {
RTC_DCHECK_RUN_ON(owner_thread_);
return internal_dtls_transport_.get();
}
const cricket::DtlsTransportInternal* internal() const {
RTC_DCHECK_RUN_ON(owner_thread_);
return internal_dtls_transport_.get();
}
protected:
~DtlsTransport();
private:
void OnInternalDtlsState(cricket::DtlsTransportInternal* transport,
DtlsTransportState state);
void UpdateInformation();
// Called when changing `info_`. We only change the values from the
// `owner_thread_` (a.k.a. the network thread).
void set_info(DtlsTransportInformation&& info) RTC_RUN_ON(owner_thread_) {
MutexLock lock(&lock_);
info_ = std::move(info);
}
DtlsTransportObserverInterface* observer_ = nullptr;
rtc::Thread* owner_thread_;
mutable Mutex lock_;
DtlsTransportInformation info_ RTC_GUARDED_BY(lock_);
std::unique_ptr<cricket::DtlsTransportInternal> internal_dtls_transport_
RTC_GUARDED_BY(owner_thread_);
const rtc::scoped_refptr<IceTransportWithPointer> ice_transport_;
};
} // namespace webrtc
#endif // PC_DTLS_TRANSPORT_H_

View file

@ -0,0 +1,181 @@
/*
* Copyright 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/dtls_transport.h"
#include <utility>
#include <vector>
#include "absl/types/optional.h"
#include "api/make_ref_counted.h"
#include "api/rtc_error.h"
#include "p2p/base/fake_dtls_transport.h"
#include "p2p/base/p2p_constants.h"
#include "rtc_base/fake_ssl_identity.h"
#include "rtc_base/gunit.h"
#include "rtc_base/rtc_certificate.h"
#include "rtc_base/ssl_identity.h"
#include "test/gmock.h"
#include "test/gtest.h"
constexpr int kDefaultTimeout = 1000; // milliseconds
constexpr int kNonsenseCipherSuite = 1234;
using cricket::FakeDtlsTransport;
using ::testing::ElementsAre;
namespace webrtc {
class TestDtlsTransportObserver : public DtlsTransportObserverInterface {
public:
void OnStateChange(DtlsTransportInformation info) override {
state_change_called_ = true;
states_.push_back(info.state());
info_ = info;
}
void OnError(RTCError error) override {}
DtlsTransportState state() {
if (states_.size() > 0) {
return states_[states_.size() - 1];
} else {
return DtlsTransportState::kNew;
}
}
bool state_change_called_ = false;
DtlsTransportInformation info_;
std::vector<DtlsTransportState> states_;
};
class DtlsTransportTest : public ::testing::Test {
public:
DtlsTransport* transport() { return transport_.get(); }
DtlsTransportObserverInterface* observer() { return &observer_; }
void CreateTransport(rtc::FakeSSLCertificate* certificate = nullptr) {
auto cricket_transport = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
if (certificate) {
cricket_transport->SetRemoteSSLCertificate(certificate);
}
cricket_transport->SetSslCipherSuite(kNonsenseCipherSuite);
transport_ =
rtc::make_ref_counted<DtlsTransport>(std::move(cricket_transport));
}
void CompleteDtlsHandshake() {
auto fake_dtls1 = static_cast<FakeDtlsTransport*>(transport_->internal());
auto fake_dtls2 = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
auto cert1 = rtc::RTCCertificate::Create(
rtc::SSLIdentity::Create("session1", rtc::KT_DEFAULT));
fake_dtls1->SetLocalCertificate(cert1);
auto cert2 = rtc::RTCCertificate::Create(
rtc::SSLIdentity::Create("session1", rtc::KT_DEFAULT));
fake_dtls2->SetLocalCertificate(cert2);
fake_dtls1->SetDestination(fake_dtls2.get());
}
rtc::AutoThread main_thread_;
rtc::scoped_refptr<DtlsTransport> transport_;
TestDtlsTransportObserver observer_;
};
TEST_F(DtlsTransportTest, CreateClearDelete) {
auto cricket_transport = std::make_unique<FakeDtlsTransport>(
"audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
auto webrtc_transport =
rtc::make_ref_counted<DtlsTransport>(std::move(cricket_transport));
ASSERT_TRUE(webrtc_transport->internal());
ASSERT_EQ(DtlsTransportState::kNew, webrtc_transport->Information().state());
webrtc_transport->Clear();
ASSERT_FALSE(webrtc_transport->internal());
ASSERT_EQ(DtlsTransportState::kClosed,
webrtc_transport->Information().state());
}
TEST_F(DtlsTransportTest, EventsObservedWhenConnecting) {
CreateTransport();
transport()->RegisterObserver(observer());
CompleteDtlsHandshake();
ASSERT_TRUE_WAIT(observer_.state_change_called_, kDefaultTimeout);
EXPECT_THAT(
observer_.states_,
ElementsAre( // FakeDtlsTransport doesn't signal the "connecting" state.
// TODO(hta): fix FakeDtlsTransport or file bug on it.
// DtlsTransportState::kConnecting,
DtlsTransportState::kConnected));
}
TEST_F(DtlsTransportTest, CloseWhenClearing) {
CreateTransport();
transport()->RegisterObserver(observer());
CompleteDtlsHandshake();
ASSERT_TRUE_WAIT(observer_.state() == DtlsTransportState::kConnected,
kDefaultTimeout);
transport()->Clear();
ASSERT_TRUE_WAIT(observer_.state() == DtlsTransportState::kClosed,
kDefaultTimeout);
}
TEST_F(DtlsTransportTest, RoleAppearsOnConnect) {
rtc::FakeSSLCertificate fake_certificate("fake data");
CreateTransport(&fake_certificate);
transport()->RegisterObserver(observer());
EXPECT_FALSE(transport()->Information().role());
CompleteDtlsHandshake();
ASSERT_TRUE_WAIT(observer_.state() == DtlsTransportState::kConnected,
kDefaultTimeout);
EXPECT_TRUE(observer_.info_.role());
EXPECT_TRUE(transport()->Information().role());
EXPECT_EQ(transport()->Information().role(), DtlsTransportTlsRole::kClient);
}
TEST_F(DtlsTransportTest, CertificateAppearsOnConnect) {
rtc::FakeSSLCertificate fake_certificate("fake data");
CreateTransport(&fake_certificate);
transport()->RegisterObserver(observer());
CompleteDtlsHandshake();
ASSERT_TRUE_WAIT(observer_.state() == DtlsTransportState::kConnected,
kDefaultTimeout);
EXPECT_TRUE(observer_.info_.remote_ssl_certificates() != nullptr);
}
TEST_F(DtlsTransportTest, CertificateDisappearsOnClose) {
rtc::FakeSSLCertificate fake_certificate("fake data");
CreateTransport(&fake_certificate);
transport()->RegisterObserver(observer());
CompleteDtlsHandshake();
ASSERT_TRUE_WAIT(observer_.state() == DtlsTransportState::kConnected,
kDefaultTimeout);
EXPECT_TRUE(observer_.info_.remote_ssl_certificates() != nullptr);
transport()->Clear();
ASSERT_TRUE_WAIT(observer_.state() == DtlsTransportState::kClosed,
kDefaultTimeout);
EXPECT_FALSE(observer_.info_.remote_ssl_certificates());
}
TEST_F(DtlsTransportTest, CipherSuiteVisibleWhenConnected) {
CreateTransport();
transport()->RegisterObserver(observer());
CompleteDtlsHandshake();
ASSERT_TRUE_WAIT(observer_.state() == DtlsTransportState::kConnected,
kDefaultTimeout);
ASSERT_TRUE(observer_.info_.ssl_cipher_suite());
EXPECT_EQ(kNonsenseCipherSuite, *observer_.info_.ssl_cipher_suite());
transport()->Clear();
ASSERT_TRUE_WAIT(observer_.state() == DtlsTransportState::kClosed,
kDefaultTimeout);
EXPECT_FALSE(observer_.info_.ssl_cipher_suite());
}
} // namespace webrtc

View file

@ -0,0 +1,243 @@
/*
* Copyright 2012 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/dtmf_sender.h"
#include <ctype.h>
#include <string.h>
#include "api/task_queue/pending_task_safety_flag.h"
#include "api/task_queue/task_queue_base.h"
#include "api/units/time_delta.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
namespace webrtc {
// RFC4733
// +-------+--------+------+---------+
// | Event | Code | Type | Volume? |
// +-------+--------+------+---------+
// | 0--9 | 0--9 | tone | yes |
// | * | 10 | tone | yes |
// | # | 11 | tone | yes |
// | A--D | 12--15 | tone | yes |
// +-------+--------+------+---------+
// The "," is a special event defined by the WebRTC spec. It means to delay for
// 2 seconds before processing the next tone. We use -1 as its code.
static const int kDtmfCommaDelay = -1;
static const char kDtmfValidTones[] = ",0123456789*#ABCDabcd";
static const char kDtmfTonesTable[] = ",0123456789*#ABCD";
// The duration cannot be more than 6000ms or less than 40ms. The gap between
// tones must be at least 50 ms.
// Source for values: W3C WEBRTC specification.
// https://w3c.github.io/webrtc-pc/#dom-rtcdtmfsender-insertdtmf
static const int kDtmfDefaultDurationMs = 100;
static const int kDtmfMinDurationMs = 40;
static const int kDtmfMaxDurationMs = 6000;
static const int kDtmfDefaultGapMs = 50;
static const int kDtmfMinGapMs = 30;
// Get DTMF code from the DTMF event character.
bool GetDtmfCode(char tone, int* code) {
// Convert a-d to A-D.
char event = toupper(tone);
const char* p = strchr(kDtmfTonesTable, event);
if (!p) {
return false;
}
*code = p - kDtmfTonesTable - 1;
return true;
}
rtc::scoped_refptr<DtmfSender> DtmfSender::Create(
TaskQueueBase* signaling_thread,
DtmfProviderInterface* provider) {
if (!signaling_thread) {
return nullptr;
}
return rtc::make_ref_counted<DtmfSender>(signaling_thread, provider);
}
DtmfSender::DtmfSender(TaskQueueBase* signaling_thread,
DtmfProviderInterface* provider)
: observer_(nullptr),
signaling_thread_(signaling_thread),
provider_(provider),
duration_(kDtmfDefaultDurationMs),
inter_tone_gap_(kDtmfDefaultGapMs),
comma_delay_(kDtmfDefaultCommaDelayMs) {
RTC_DCHECK(signaling_thread_);
RTC_DCHECK(provider_);
}
void DtmfSender::OnDtmfProviderDestroyed() {
RTC_DCHECK_RUN_ON(signaling_thread_);
RTC_DLOG(LS_INFO) << "The Dtmf provider is deleted. Clear the sending queue.";
StopSending();
provider_ = nullptr;
}
DtmfSender::~DtmfSender() {
RTC_DCHECK_RUN_ON(signaling_thread_);
StopSending();
}
void DtmfSender::RegisterObserver(DtmfSenderObserverInterface* observer) {
RTC_DCHECK_RUN_ON(signaling_thread_);
observer_ = observer;
}
void DtmfSender::UnregisterObserver() {
RTC_DCHECK_RUN_ON(signaling_thread_);
observer_ = nullptr;
}
bool DtmfSender::CanInsertDtmf() {
RTC_DCHECK_RUN_ON(signaling_thread_);
if (!provider_) {
return false;
}
return provider_->CanInsertDtmf();
}
bool DtmfSender::InsertDtmf(const std::string& tones,
int duration,
int inter_tone_gap,
int comma_delay) {
RTC_DCHECK_RUN_ON(signaling_thread_);
if (duration > kDtmfMaxDurationMs || duration < kDtmfMinDurationMs ||
inter_tone_gap < kDtmfMinGapMs || comma_delay < kDtmfMinGapMs) {
RTC_LOG(LS_ERROR)
<< "InsertDtmf is called with invalid duration or tones gap. "
"The duration cannot be more than "
<< kDtmfMaxDurationMs << "ms or less than " << kDtmfMinDurationMs
<< "ms. The gap between tones must be at least " << kDtmfMinGapMs
<< "ms.";
return false;
}
if (!CanInsertDtmf()) {
RTC_LOG(LS_ERROR)
<< "InsertDtmf is called on DtmfSender that can't send DTMF.";
return false;
}
tones_ = tones;
duration_ = duration;
inter_tone_gap_ = inter_tone_gap;
comma_delay_ = comma_delay;
// Cancel any remaining tasks for previous tones.
if (safety_flag_) {
safety_flag_->SetNotAlive();
}
safety_flag_ = PendingTaskSafetyFlag::Create();
// Kick off a new DTMF task.
QueueInsertDtmf(1 /*ms*/);
return true;
}
std::string DtmfSender::tones() const {
RTC_DCHECK_RUN_ON(signaling_thread_);
return tones_;
}
int DtmfSender::duration() const {
RTC_DCHECK_RUN_ON(signaling_thread_);
return duration_;
}
int DtmfSender::inter_tone_gap() const {
RTC_DCHECK_RUN_ON(signaling_thread_);
return inter_tone_gap_;
}
int DtmfSender::comma_delay() const {
RTC_DCHECK_RUN_ON(signaling_thread_);
return comma_delay_;
}
void DtmfSender::QueueInsertDtmf(uint32_t delay_ms) {
signaling_thread_->PostDelayedHighPrecisionTask(
SafeTask(safety_flag_,
[this] {
RTC_DCHECK_RUN_ON(signaling_thread_);
DoInsertDtmf();
}),
TimeDelta::Millis(delay_ms));
}
void DtmfSender::DoInsertDtmf() {
// Get the first DTMF tone from the tone buffer. Unrecognized characters will
// be ignored and skipped.
size_t first_tone_pos = tones_.find_first_of(kDtmfValidTones);
int code = 0;
if (first_tone_pos == std::string::npos) {
tones_.clear();
// Fire a “OnToneChange” event with an empty string and stop.
if (observer_) {
observer_->OnToneChange(std::string(), tones_);
observer_->OnToneChange(std::string());
}
return;
} else {
char tone = tones_[first_tone_pos];
if (!GetDtmfCode(tone, &code)) {
// The find_first_of(kDtmfValidTones) should have guarantee `tone` is
// a valid DTMF tone.
RTC_DCHECK_NOTREACHED();
}
}
int tone_gap = inter_tone_gap_;
if (code == kDtmfCommaDelay) {
// Special case defined by WebRTC - By default, the character ',' indicates
// a delay of 2 seconds before processing the next character in the tones
// parameter. The comma delay can be set to a non default value via
// InsertDtmf to comply with legacy WebRTC clients.
tone_gap = comma_delay_;
} else {
if (!provider_) {
RTC_LOG(LS_ERROR) << "The DtmfProvider has been destroyed.";
return;
}
// The provider starts playout of the given tone on the
// associated RTP media stream, using the appropriate codec.
if (!provider_->InsertDtmf(code, duration_)) {
RTC_LOG(LS_ERROR) << "The DtmfProvider can no longer send DTMF.";
return;
}
// Wait for the number of milliseconds specified by `duration_`.
tone_gap += duration_;
}
// Fire a “OnToneChange” event with the tone that's just processed.
if (observer_) {
observer_->OnToneChange(tones_.substr(first_tone_pos, 1),
tones_.substr(first_tone_pos + 1));
observer_->OnToneChange(tones_.substr(first_tone_pos, 1));
}
// Erase the unrecognized characters plus the tone that's just processed.
tones_.erase(0, first_tone_pos + 1);
// Continue with the next tone.
QueueInsertDtmf(tone_gap);
}
void DtmfSender::StopSending() {
if (safety_flag_) {
safety_flag_->SetNotAlive();
}
}
} // namespace webrtc

View file

@ -0,0 +1,118 @@
/*
* 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.
*/
#ifndef PC_DTMF_SENDER_H_
#define PC_DTMF_SENDER_H_
#include <stdint.h>
#include <string>
#include "api/dtmf_sender_interface.h"
#include "api/scoped_refptr.h"
#include "api/sequence_checker.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "api/task_queue/task_queue_base.h"
#include "pc/proxy.h"
#include "rtc_base/ref_count.h"
#include "rtc_base/thread_annotations.h"
// DtmfSender is the native implementation of the RTCDTMFSender defined by
// the WebRTC W3C Editor's Draft.
// https://w3c.github.io/webrtc-pc/#rtcdtmfsender
namespace webrtc {
// This interface is called by DtmfSender to talk to the actual audio channel
// to send DTMF.
class DtmfProviderInterface {
public:
// Returns true if the audio sender is capable of sending DTMF. Otherwise
// returns false.
virtual bool CanInsertDtmf() = 0;
// Sends DTMF `code`.
// The `duration` indicates the length of the DTMF tone in ms.
// Returns true on success and false on failure.
virtual bool InsertDtmf(int code, int duration) = 0;
protected:
virtual ~DtmfProviderInterface() {}
};
class DtmfSender : public DtmfSenderInterface {
public:
static rtc::scoped_refptr<DtmfSender> Create(TaskQueueBase* signaling_thread,
DtmfProviderInterface* provider);
void OnDtmfProviderDestroyed();
// Implements DtmfSenderInterface.
void RegisterObserver(DtmfSenderObserverInterface* observer) override;
void UnregisterObserver() override;
bool CanInsertDtmf() override;
bool InsertDtmf(const std::string& tones,
int duration,
int inter_tone_gap,
int comma_delay = kDtmfDefaultCommaDelayMs) override;
std::string tones() const override;
int duration() const override;
int inter_tone_gap() const override;
int comma_delay() const override;
protected:
DtmfSender(TaskQueueBase* signaling_thread, DtmfProviderInterface* provider);
virtual ~DtmfSender();
DtmfSender(const DtmfSender&) = delete;
DtmfSender& operator=(const DtmfSender&) = delete;
private:
DtmfSender();
void QueueInsertDtmf(uint32_t delay_ms) RTC_RUN_ON(signaling_thread_);
// The DTMF sending task.
void DoInsertDtmf() RTC_RUN_ON(signaling_thread_);
void StopSending() RTC_RUN_ON(signaling_thread_);
DtmfSenderObserverInterface* observer_ RTC_GUARDED_BY(signaling_thread_);
TaskQueueBase* const signaling_thread_;
DtmfProviderInterface* provider_ RTC_GUARDED_BY(signaling_thread_);
std::string tones_ RTC_GUARDED_BY(signaling_thread_);
int duration_ RTC_GUARDED_BY(signaling_thread_);
int inter_tone_gap_ RTC_GUARDED_BY(signaling_thread_);
int comma_delay_ RTC_GUARDED_BY(signaling_thread_);
// For cancelling the tasks which feed the DTMF provider one tone at a time.
rtc::scoped_refptr<PendingTaskSafetyFlag> safety_flag_ RTC_GUARDED_BY(
signaling_thread_) RTC_PT_GUARDED_BY(signaling_thread_) = nullptr;
};
// Define proxy for DtmfSenderInterface.
BEGIN_PRIMARY_PROXY_MAP(DtmfSender)
PROXY_PRIMARY_THREAD_DESTRUCTOR()
PROXY_METHOD1(void, RegisterObserver, DtmfSenderObserverInterface*)
PROXY_METHOD0(void, UnregisterObserver)
PROXY_METHOD0(bool, CanInsertDtmf)
PROXY_METHOD4(bool, InsertDtmf, const std::string&, int, int, int)
PROXY_CONSTMETHOD0(std::string, tones)
PROXY_CONSTMETHOD0(int, duration)
PROXY_CONSTMETHOD0(int, inter_tone_gap)
PROXY_CONSTMETHOD0(int, comma_delay)
END_PROXY_MAP(DtmfSender)
// Get DTMF code from the DTMF event character.
bool GetDtmfCode(char tone, int* code);
} // namespace webrtc
#endif // PC_DTMF_SENDER_H_

View file

@ -0,0 +1,371 @@
/*
* Copyright 2012 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/dtmf_sender.h"
#include <stddef.h>
#include <memory>
#include <string>
#include <vector>
#include "rtc_base/fake_clock.h"
#include "rtc_base/gunit.h"
#include "rtc_base/time_utils.h"
#include "test/gtest.h"
using webrtc::DtmfProviderInterface;
using webrtc::DtmfSender;
using webrtc::DtmfSenderObserverInterface;
// TODO(deadbeef): Even though this test now uses a fake clock, it has a
// generous 3-second timeout for every test case. The timeout could be tuned
// to each test based on the tones sent, instead.
static const int kMaxWaitMs = 3000;
class FakeDtmfObserver : public DtmfSenderObserverInterface {
public:
FakeDtmfObserver() : completed_(false) {}
// Implements DtmfSenderObserverInterface.
void OnToneChange(const std::string& tone) override {
tones_from_single_argument_callback_.push_back(tone);
if (tone.empty()) {
completed_ = true;
}
}
void OnToneChange(const std::string& tone,
const std::string& tone_buffer) override {
tones_.push_back(tone);
tones_remaining_ = tone_buffer;
if (tone.empty()) {
completed_ = true;
}
}
// getters
const std::vector<std::string>& tones() const { return tones_; }
const std::vector<std::string>& tones_from_single_argument_callback() const {
return tones_from_single_argument_callback_;
}
const std::string tones_remaining() { return tones_remaining_; }
bool completed() const { return completed_; }
private:
std::vector<std::string> tones_;
std::vector<std::string> tones_from_single_argument_callback_;
std::string tones_remaining_;
bool completed_;
};
class FakeDtmfProvider : public DtmfProviderInterface {
public:
struct DtmfInfo {
DtmfInfo(int code, int duration, int gap)
: code(code), duration(duration), gap(gap) {}
int code;
int duration;
int gap;
};
FakeDtmfProvider() : last_insert_dtmf_call_(0) {}
// Implements DtmfProviderInterface.
bool CanInsertDtmf() override { return can_insert_; }
bool InsertDtmf(int code, int duration) override {
int gap = 0;
// TODO(ronghuawu): Make the timer (basically the rtc::TimeNanos)
// mockable and use a fake timer in the unit tests.
if (last_insert_dtmf_call_ > 0) {
gap = static_cast<int>(rtc::TimeMillis() - last_insert_dtmf_call_);
}
last_insert_dtmf_call_ = rtc::TimeMillis();
dtmf_info_queue_.push_back(DtmfInfo(code, duration, gap));
return true;
}
// getter and setter
const std::vector<DtmfInfo>& dtmf_info_queue() const {
return dtmf_info_queue_;
}
// helper functions
void SetCanInsertDtmf(bool can_insert) { can_insert_ = can_insert; }
private:
bool can_insert_ = false;
std::vector<DtmfInfo> dtmf_info_queue_;
int64_t last_insert_dtmf_call_;
};
class DtmfSenderTest : public ::testing::Test {
protected:
DtmfSenderTest()
: observer_(new FakeDtmfObserver()), provider_(new FakeDtmfProvider()) {
provider_->SetCanInsertDtmf(true);
dtmf_ = DtmfSender::Create(rtc::Thread::Current(), provider_.get());
dtmf_->RegisterObserver(observer_.get());
}
~DtmfSenderTest() {
if (dtmf_.get()) {
dtmf_->UnregisterObserver();
}
}
// Constructs a list of DtmfInfo from `tones`, `duration` and
// `inter_tone_gap`.
void GetDtmfInfoFromString(
const std::string& tones,
int duration,
int inter_tone_gap,
std::vector<FakeDtmfProvider::DtmfInfo>* dtmfs,
int comma_delay = webrtc::DtmfSender::kDtmfDefaultCommaDelayMs) {
// Init extra_delay as -inter_tone_gap - duration to ensure the first
// DtmfInfo's gap field will be 0.
int extra_delay = -1 * (inter_tone_gap + duration);
std::string::const_iterator it = tones.begin();
for (; it != tones.end(); ++it) {
char tone = *it;
int code = 0;
webrtc::GetDtmfCode(tone, &code);
if (tone == ',') {
extra_delay = comma_delay;
} else {
dtmfs->push_back(FakeDtmfProvider::DtmfInfo(
code, duration, duration + inter_tone_gap + extra_delay));
extra_delay = 0;
}
}
}
void VerifyExpectedState(const std::string& tones,
int duration,
int inter_tone_gap) {
EXPECT_EQ(tones, dtmf_->tones());
EXPECT_EQ(duration, dtmf_->duration());
EXPECT_EQ(inter_tone_gap, dtmf_->inter_tone_gap());
}
// Verify the provider got all the expected calls.
void VerifyOnProvider(
const std::string& tones,
int duration,
int inter_tone_gap,
int comma_delay = webrtc::DtmfSender::kDtmfDefaultCommaDelayMs) {
std::vector<FakeDtmfProvider::DtmfInfo> dtmf_queue_ref;
GetDtmfInfoFromString(tones, duration, inter_tone_gap, &dtmf_queue_ref,
comma_delay);
VerifyOnProvider(dtmf_queue_ref);
}
void VerifyOnProvider(
const std::vector<FakeDtmfProvider::DtmfInfo>& dtmf_queue_ref) {
const std::vector<FakeDtmfProvider::DtmfInfo>& dtmf_queue =
provider_->dtmf_info_queue();
ASSERT_EQ(dtmf_queue_ref.size(), dtmf_queue.size());
std::vector<FakeDtmfProvider::DtmfInfo>::const_iterator it_ref =
dtmf_queue_ref.begin();
std::vector<FakeDtmfProvider::DtmfInfo>::const_iterator it =
dtmf_queue.begin();
while (it_ref != dtmf_queue_ref.end() && it != dtmf_queue.end()) {
EXPECT_EQ(it_ref->code, it->code);
EXPECT_EQ(it_ref->duration, it->duration);
// Allow ~10ms error (can be small since we're using a fake clock).
EXPECT_GE(it_ref->gap, it->gap - 10);
EXPECT_LE(it_ref->gap, it->gap + 10);
++it_ref;
++it;
}
}
// Verify the observer got all the expected callbacks.
void VerifyOnObserver(const std::string& tones_ref) {
const std::vector<std::string>& tones = observer_->tones();
// The observer will get an empty string at the end.
EXPECT_EQ(tones_ref.size() + 1, tones.size());
EXPECT_EQ(observer_->tones(),
observer_->tones_from_single_argument_callback());
EXPECT_TRUE(tones.back().empty());
EXPECT_TRUE(observer_->tones_remaining().empty());
std::string::const_iterator it_ref = tones_ref.begin();
std::vector<std::string>::const_iterator it = tones.begin();
while (it_ref != tones_ref.end() && it != tones.end()) {
EXPECT_EQ(*it_ref, it->at(0));
++it_ref;
++it;
}
}
rtc::AutoThread main_thread_;
std::unique_ptr<FakeDtmfObserver> observer_;
std::unique_ptr<FakeDtmfProvider> provider_;
rtc::scoped_refptr<DtmfSender> dtmf_;
rtc::ScopedFakeClock fake_clock_;
};
TEST_F(DtmfSenderTest, CanInsertDtmf) {
EXPECT_TRUE(dtmf_->CanInsertDtmf());
provider_->SetCanInsertDtmf(false);
EXPECT_FALSE(dtmf_->CanInsertDtmf());
}
TEST_F(DtmfSenderTest, InsertDtmf) {
std::string tones = "@1%a&*$";
int duration = 100;
int inter_tone_gap = 50;
EXPECT_TRUE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap));
EXPECT_TRUE_SIMULATED_WAIT(observer_->completed(), kMaxWaitMs, fake_clock_);
// The unrecognized characters should be ignored.
std::string known_tones = "1a*";
VerifyOnProvider(known_tones, duration, inter_tone_gap);
VerifyOnObserver(known_tones);
}
TEST_F(DtmfSenderTest, InsertDtmfTwice) {
std::string tones1 = "12";
std::string tones2 = "ab";
int duration = 100;
int inter_tone_gap = 50;
EXPECT_TRUE(dtmf_->InsertDtmf(tones1, duration, inter_tone_gap));
VerifyExpectedState(tones1, duration, inter_tone_gap);
// Wait until the first tone got sent.
EXPECT_TRUE_SIMULATED_WAIT(observer_->tones().size() == 1, kMaxWaitMs,
fake_clock_);
VerifyExpectedState("2", duration, inter_tone_gap);
// Insert with another tone buffer.
EXPECT_TRUE(dtmf_->InsertDtmf(tones2, duration, inter_tone_gap));
VerifyExpectedState(tones2, duration, inter_tone_gap);
// Wait until it's completed.
EXPECT_TRUE_SIMULATED_WAIT(observer_->completed(), kMaxWaitMs, fake_clock_);
std::vector<FakeDtmfProvider::DtmfInfo> dtmf_queue_ref;
GetDtmfInfoFromString("1", duration, inter_tone_gap, &dtmf_queue_ref);
GetDtmfInfoFromString("ab", duration, inter_tone_gap, &dtmf_queue_ref);
VerifyOnProvider(dtmf_queue_ref);
VerifyOnObserver("1ab");
}
TEST_F(DtmfSenderTest, InsertDtmfWhileProviderIsDeleted) {
std::string tones = "@1%a&*$";
int duration = 100;
int inter_tone_gap = 50;
EXPECT_TRUE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap));
// Wait until the first tone got sent.
EXPECT_TRUE_SIMULATED_WAIT(observer_->tones().size() == 1, kMaxWaitMs,
fake_clock_);
// Delete provider.
dtmf_->OnDtmfProviderDestroyed();
provider_.reset();
// The queue should be discontinued so no more tone callbacks.
SIMULATED_WAIT(false, 200, fake_clock_);
EXPECT_EQ(1U, observer_->tones().size());
}
TEST_F(DtmfSenderTest, InsertDtmfWhileSenderIsDeleted) {
std::string tones = "@1%a&*$";
int duration = 100;
int inter_tone_gap = 50;
EXPECT_TRUE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap));
// Wait until the first tone got sent.
EXPECT_TRUE_SIMULATED_WAIT(observer_->tones().size() == 1, kMaxWaitMs,
fake_clock_);
// Delete the sender.
dtmf_ = NULL;
// The queue should be discontinued so no more tone callbacks.
SIMULATED_WAIT(false, 200, fake_clock_);
EXPECT_EQ(1U, observer_->tones().size());
}
TEST_F(DtmfSenderTest, InsertEmptyTonesToCancelPreviousTask) {
std::string tones1 = "12";
std::string tones2 = "";
int duration = 100;
int inter_tone_gap = 50;
EXPECT_TRUE(dtmf_->InsertDtmf(tones1, duration, inter_tone_gap));
// Wait until the first tone got sent.
EXPECT_TRUE_SIMULATED_WAIT(observer_->tones().size() == 1, kMaxWaitMs,
fake_clock_);
// Insert with another tone buffer.
EXPECT_TRUE(dtmf_->InsertDtmf(tones2, duration, inter_tone_gap));
// Wait until it's completed.
EXPECT_TRUE_SIMULATED_WAIT(observer_->completed(), kMaxWaitMs, fake_clock_);
std::vector<FakeDtmfProvider::DtmfInfo> dtmf_queue_ref;
GetDtmfInfoFromString("1", duration, inter_tone_gap, &dtmf_queue_ref);
VerifyOnProvider(dtmf_queue_ref);
VerifyOnObserver("1");
}
TEST_F(DtmfSenderTest, InsertDtmfWithDefaultCommaDelay) {
std::string tones = "3,4";
int duration = 100;
int inter_tone_gap = 50;
int default_comma_delay = webrtc::DtmfSender::kDtmfDefaultCommaDelayMs;
EXPECT_EQ(dtmf_->comma_delay(), default_comma_delay);
EXPECT_TRUE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap));
EXPECT_TRUE_SIMULATED_WAIT(observer_->completed(), kMaxWaitMs, fake_clock_);
VerifyOnProvider(tones, duration, inter_tone_gap);
VerifyOnObserver(tones);
EXPECT_EQ(dtmf_->comma_delay(), default_comma_delay);
}
TEST_F(DtmfSenderTest, InsertDtmfWithNonDefaultCommaDelay) {
std::string tones = "3,4";
int duration = 100;
int inter_tone_gap = 50;
int default_comma_delay = webrtc::DtmfSender::kDtmfDefaultCommaDelayMs;
int comma_delay = 500;
EXPECT_EQ(dtmf_->comma_delay(), default_comma_delay);
EXPECT_TRUE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap, comma_delay));
EXPECT_TRUE_SIMULATED_WAIT(observer_->completed(), kMaxWaitMs, fake_clock_);
VerifyOnProvider(tones, duration, inter_tone_gap, comma_delay);
VerifyOnObserver(tones);
EXPECT_EQ(dtmf_->comma_delay(), comma_delay);
}
TEST_F(DtmfSenderTest, TryInsertDtmfWhenItDoesNotWork) {
std::string tones = "3,4";
int duration = 100;
int inter_tone_gap = 50;
provider_->SetCanInsertDtmf(false);
EXPECT_FALSE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap));
}
TEST_F(DtmfSenderTest, InsertDtmfWithInvalidDurationOrGap) {
std::string tones = "3,4";
int duration = 40;
int inter_tone_gap = 50;
EXPECT_FALSE(dtmf_->InsertDtmf(tones, 6001, inter_tone_gap));
EXPECT_FALSE(dtmf_->InsertDtmf(tones, 39, inter_tone_gap));
EXPECT_FALSE(dtmf_->InsertDtmf(tones, duration, 29));
EXPECT_FALSE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap, 29));
EXPECT_TRUE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap));
}
TEST_F(DtmfSenderTest, InsertDtmfSendsAfterWait) {
std::string tones = "ABC";
int duration = 100;
int inter_tone_gap = 50;
EXPECT_TRUE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap));
VerifyExpectedState("ABC", duration, inter_tone_gap);
// Wait until the first tone got sent.
EXPECT_TRUE_SIMULATED_WAIT(observer_->tones().size() == 1, kMaxWaitMs,
fake_clock_);
VerifyExpectedState("BC", duration, inter_tone_gap);
}

View file

@ -0,0 +1,143 @@
/*
* Copyright 2014 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/external_hmac.h"
#include <stdlib.h> // For malloc/free.
#include <string.h>
#include "rtc_base/logging.h"
#include "rtc_base/zero_memory.h"
#include "third_party/libsrtp/include/srtp.h"
// Begin test case 0 */
static const uint8_t kExternalHmacTestCase0Key[20] = {
0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b};
static const uint8_t kExternalHmacTestCase0Data[8] = {
0x48, 0x69, 0x20, 0x54, 0x68, 0x65, 0x72, 0x65 // "Hi There"
};
static const uint8_t kExternalHmacFakeTag[10] = {0xba, 0xdd, 0xba, 0xdd, 0xba,
0xdd, 0xba, 0xdd, 0xba, 0xdd};
static const srtp_auth_test_case_t kExternalHmacTestCase0 = {
20, // Octets in key
const_cast<uint8_t*>(kExternalHmacTestCase0Key), // Key
8, // Octets in data
const_cast<uint8_t*>(kExternalHmacTestCase0Data), // Data
10, // Octets in tag
const_cast<uint8_t*>(kExternalHmacFakeTag), // Tag
NULL // Pointer to next
// testcase
};
static const char kExternalHmacDescription[] =
"external hmac sha-1 authentication";
// srtp_auth_type_t external_hmac is the hmac metaobject
static const srtp_auth_type_t external_hmac = {
external_hmac_alloc,
external_hmac_dealloc,
external_hmac_init,
external_hmac_compute,
external_hmac_update,
external_hmac_start,
const_cast<char*>(kExternalHmacDescription),
const_cast<srtp_auth_test_case_t*>(&kExternalHmacTestCase0),
EXTERNAL_HMAC_SHA1};
srtp_err_status_t external_hmac_alloc(srtp_auth_t** a,
int key_len,
int out_len) {
uint8_t* pointer;
// Check key length - note that we don't support keys larger
// than 20 bytes yet
if (key_len > 20)
return srtp_err_status_bad_param;
// Check output length - should be less than 20 bytes/
if (out_len > 20)
return srtp_err_status_bad_param;
// Allocate memory for auth and hmac_ctx_t structures.
pointer = new uint8_t[(sizeof(ExternalHmacContext) + sizeof(srtp_auth_t))];
if (pointer == NULL)
return srtp_err_status_alloc_fail;
// Set pointers
*a = reinterpret_cast<srtp_auth_t*>(pointer);
// `external_hmac` is const and libsrtp expects `type` to be non-const.
// const conversion is required. `external_hmac` is constant because we don't
// want to increase global count in Chrome.
(*a)->type = const_cast<srtp_auth_type_t*>(&external_hmac);
(*a)->state = pointer + sizeof(srtp_auth_t);
(*a)->out_len = out_len;
(*a)->key_len = key_len;
(*a)->prefix_len = 0;
return srtp_err_status_ok;
}
srtp_err_status_t external_hmac_dealloc(srtp_auth_t* a) {
rtc::ExplicitZeroMemory(a, sizeof(ExternalHmacContext) + sizeof(srtp_auth_t));
// Free memory
delete[] a;
return srtp_err_status_ok;
}
srtp_err_status_t external_hmac_init(void* state,
const uint8_t* key,
int key_len) {
if (key_len > HMAC_KEY_LENGTH)
return srtp_err_status_bad_param;
ExternalHmacContext* context = static_cast<ExternalHmacContext*>(state);
memcpy(context->key, key, key_len);
context->key_length = key_len;
return srtp_err_status_ok;
}
srtp_err_status_t external_hmac_start(void* /*state*/) {
return srtp_err_status_ok;
}
srtp_err_status_t external_hmac_update(void* /*state*/,
const uint8_t* /*message*/,
int /*msg_octets*/) {
return srtp_err_status_ok;
}
srtp_err_status_t external_hmac_compute(void* /*state*/,
const uint8_t* /*message*/,
int /*msg_octets*/,
int tag_len,
uint8_t* result) {
memcpy(result, kExternalHmacFakeTag, tag_len);
return srtp_err_status_ok;
}
srtp_err_status_t external_crypto_init() {
// `external_hmac` is const. const_cast is required as libsrtp expects
// non-const.
srtp_err_status_t status = srtp_replace_auth_type(
const_cast<srtp_auth_type_t*>(&external_hmac), EXTERNAL_HMAC_SHA1);
if (status) {
RTC_LOG(LS_ERROR) << "Error in replacing default auth module, error: "
<< status;
return srtp_err_status_fail;
}
return srtp_err_status_ok;
}

View file

@ -0,0 +1,72 @@
/*
* Copyright 2014 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef PC_EXTERNAL_HMAC_H_
#define PC_EXTERNAL_HMAC_H_
// External libsrtp HMAC auth module which implements methods defined in
// auth_type_t.
// The default auth module will be replaced only when the ENABLE_EXTERNAL_AUTH
// flag is enabled. This allows us to access to authentication keys,
// as the default auth implementation doesn't provide access and avoids
// hashing each packet twice.
// How will libsrtp select this module?
// Libsrtp defines authentication function types identified by an unsigned
// integer, e.g. SRTP_HMAC_SHA1 is 3. Using authentication ids, the
// application can plug any desired authentication modules into libsrtp.
// libsrtp also provides a mechanism to select different auth functions for
// individual streams. This can be done by setting the right value in
// the auth_type of srtp_policy_t. The application must first register auth
// functions and the corresponding authentication id using
// crypto_kernel_replace_auth_type function.
#include <stdint.h>
#include "third_party/libsrtp/crypto/include/crypto_types.h"
#include "third_party/libsrtp/include/srtp.h"
#include "third_party/libsrtp/include/srtp_priv.h"
#define EXTERNAL_HMAC_SHA1 SRTP_HMAC_SHA1 + 1
#define HMAC_KEY_LENGTH 20
// The HMAC context structure used to store authentication keys.
// The pointer to the key will be allocated in the external_hmac_init function.
// This pointer is owned by srtp_t in a template context.
typedef struct {
uint8_t key[HMAC_KEY_LENGTH];
int key_length;
} ExternalHmacContext;
srtp_err_status_t external_hmac_alloc(srtp_auth_t** a,
int key_len,
int out_len);
srtp_err_status_t external_hmac_dealloc(srtp_auth_t* a);
srtp_err_status_t external_hmac_init(void* state,
const uint8_t* key,
int key_len);
srtp_err_status_t external_hmac_start(void* state);
srtp_err_status_t external_hmac_update(void* state,
const uint8_t* message,
int msg_octets);
srtp_err_status_t external_hmac_compute(void* state,
const uint8_t* message,
int msg_octets,
int tag_len,
uint8_t* result);
srtp_err_status_t external_crypto_init();
#endif // PC_EXTERNAL_HMAC_H_

View file

@ -0,0 +1,360 @@
/*
* Copyright 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/ice_server_parsing.h"
#include <stddef.h>
#include <cctype> // For std::isdigit.
#include <string>
#include <tuple>
#include "p2p/base/port_interface.h"
#include "rtc_base/arraysize.h"
#include "rtc_base/checks.h"
#include "rtc_base/ip_address.h"
#include "rtc_base/logging.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/string_encode.h"
#include "rtc_base/string_to_number.h"
namespace webrtc {
namespace {
// Number of tokens must be preset when TURN uri has transport param.
const size_t kTurnTransportTokensNum = 2;
// The default stun port.
const int kDefaultStunPort = 3478;
const int kDefaultStunTlsPort = 5349;
const char kTransport[] = "transport";
// Allowed characters in hostname per RFC 3986 Appendix A "reg-name"
const char kRegNameCharacters[] =
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789"
"-._~" // unreserved
"%" // pct-encoded
"!$&'()*+,;="; // sub-delims
// NOTE: Must be in the same order as the ServiceType enum.
const char* kValidIceServiceTypes[] = {"stun", "stuns", "turn", "turns"};
// NOTE: A loop below assumes that the first value of this enum is 0 and all
// other values are incremental.
enum class ServiceType {
STUN = 0, // Indicates a STUN server.
STUNS, // Indicates a STUN server used with a TLS session.
TURN, // Indicates a TURN server
TURNS, // Indicates a TURN server used with a TLS session.
INVALID, // Unknown.
};
static_assert(static_cast<size_t>(ServiceType::INVALID) ==
arraysize(kValidIceServiceTypes),
"kValidIceServiceTypes must have as many strings as ServiceType "
"has values.");
// `in_str` should follow of RFC 7064/7065 syntax, but with an optional
// "?transport=" already stripped. I.e.,
// stunURI = scheme ":" host [ ":" port ]
// scheme = "stun" / "stuns" / "turn" / "turns"
// host = IP-literal / IPv4address / reg-name
// port = *DIGIT
// Return tuple is service_type, host, with service_type == ServiceType::INVALID
// on failure.
std::tuple<ServiceType, absl::string_view> GetServiceTypeAndHostnameFromUri(
absl::string_view in_str) {
const auto colonpos = in_str.find(':');
if (colonpos == absl::string_view::npos) {
RTC_LOG(LS_WARNING) << "Missing ':' in ICE URI: " << in_str;
return {ServiceType::INVALID, ""};
}
if ((colonpos + 1) == in_str.length()) {
RTC_LOG(LS_WARNING) << "Empty hostname in ICE URI: " << in_str;
return {ServiceType::INVALID, ""};
}
for (size_t i = 0; i < arraysize(kValidIceServiceTypes); ++i) {
if (in_str.compare(0, colonpos, kValidIceServiceTypes[i]) == 0) {
return {static_cast<ServiceType>(i), in_str.substr(colonpos + 1)};
}
}
return {ServiceType::INVALID, ""};
}
absl::optional<int> ParsePort(absl::string_view in_str) {
// Make sure port only contains digits. StringToNumber doesn't check this.
for (const char& c : in_str) {
if (!std::isdigit(static_cast<unsigned char>(c))) {
return false;
}
}
return rtc::StringToNumber<int>(in_str);
}
// This method parses IPv6 and IPv4 literal strings, along with hostnames in
// standard hostname:port format.
// Consider following formats as correct.
// `hostname:port`, |[IPV6 address]:port|, |IPv4 address|:port,
// `hostname`, |[IPv6 address]|, |IPv4 address|.
// Return tuple is success, host, port.
std::tuple<bool, absl::string_view, int> ParseHostnameAndPortFromString(
absl::string_view in_str,
int default_port) {
if (in_str.empty()) {
return {false, "", 0};
}
absl::string_view host;
int port = default_port;
if (in_str.at(0) == '[') {
// IP_literal syntax
auto closebracket = in_str.rfind(']');
if (closebracket == absl::string_view::npos) {
return {false, "", 0};
}
auto colonpos = in_str.find(':', closebracket);
if (absl::string_view::npos != colonpos) {
if (absl::optional<int> opt_port =
ParsePort(in_str.substr(closebracket + 2))) {
port = *opt_port;
} else {
return {false, "", 0};
}
}
host = in_str.substr(1, closebracket - 1);
} else {
// IPv4address or reg-name syntax
auto colonpos = in_str.find(':');
if (absl::string_view::npos != colonpos) {
if (absl::optional<int> opt_port =
ParsePort(in_str.substr(colonpos + 1))) {
port = *opt_port;
} else {
return {false, "", 0};
}
host = in_str.substr(0, colonpos);
} else {
host = in_str;
}
// RFC 3986 section 3.2.2 and Appendix A - "reg-name" syntax
if (host.find_first_not_of(kRegNameCharacters) != absl::string_view::npos) {
return {false, "", 0};
}
}
return {!host.empty(), host, port};
}
// Adds a STUN or TURN server to the appropriate list,
// by parsing `url` and using the username/password in `server`.
RTCError ParseIceServerUrl(
const PeerConnectionInterface::IceServer& server,
absl::string_view url,
cricket::ServerAddresses* stun_servers,
std::vector<cricket::RelayServerConfig>* turn_servers) {
// RFC 7064
// stunURI = scheme ":" host [ ":" port ]
// scheme = "stun" / "stuns"
// RFC 7065
// turnURI = scheme ":" host [ ":" port ]
// [ "?transport=" transport ]
// scheme = "turn" / "turns"
// transport = "udp" / "tcp" / transport-ext
// transport-ext = 1*unreserved
// RFC 3986
// host = IP-literal / IPv4address / reg-name
// port = *DIGIT
RTC_DCHECK(stun_servers != nullptr);
RTC_DCHECK(turn_servers != nullptr);
cricket::ProtocolType turn_transport_type = cricket::PROTO_UDP;
RTC_DCHECK(!url.empty());
std::vector<absl::string_view> tokens = rtc::split(url, '?');
absl::string_view uri_without_transport = tokens[0];
// Let's look into transport= param, if it exists.
if (tokens.size() == kTurnTransportTokensNum) { // ?transport= is present.
std::vector<absl::string_view> transport_tokens =
rtc::split(tokens[1], '=');
if (transport_tokens[0] != kTransport) {
LOG_AND_RETURN_ERROR(
RTCErrorType::SYNTAX_ERROR,
"ICE server parsing failed: Invalid transport parameter key.");
}
if (transport_tokens.size() < 2) {
LOG_AND_RETURN_ERROR(
RTCErrorType::SYNTAX_ERROR,
"ICE server parsing failed: Transport parameter missing value.");
}
absl::optional<cricket::ProtocolType> proto =
cricket::StringToProto(transport_tokens[1]);
if (!proto ||
(*proto != cricket::PROTO_UDP && *proto != cricket::PROTO_TCP)) {
LOG_AND_RETURN_ERROR(
RTCErrorType::SYNTAX_ERROR,
"ICE server parsing failed: Transport parameter should "
"always be udp or tcp.");
}
turn_transport_type = *proto;
}
auto [service_type, hoststring] =
GetServiceTypeAndHostnameFromUri(uri_without_transport);
if (service_type == ServiceType::INVALID) {
RTC_LOG(LS_ERROR) << "Invalid transport parameter in ICE URI: " << url;
LOG_AND_RETURN_ERROR(
RTCErrorType::SYNTAX_ERROR,
"ICE server parsing failed: Invalid transport parameter in ICE URI");
}
// GetServiceTypeAndHostnameFromUri should never give an empty hoststring
RTC_DCHECK(!hoststring.empty());
// stun with ?transport (or any ?) is not valid.
if ((service_type == ServiceType::STUN ||
service_type == ServiceType::STUNS) &&
tokens.size() > 1) {
LOG_AND_RETURN_ERROR(
RTCErrorType::SYNTAX_ERROR,
"ICE server parsing failed: Invalid stun url with query parameters");
}
int default_port = kDefaultStunPort;
if (service_type == ServiceType::TURNS) {
default_port = kDefaultStunTlsPort;
turn_transport_type = cricket::PROTO_TLS;
}
if (hoststring.find('@') != absl::string_view::npos) {
RTC_LOG(LS_ERROR) << "Invalid url with long deprecated user@host syntax: "
<< uri_without_transport;
LOG_AND_RETURN_ERROR(RTCErrorType::SYNTAX_ERROR,
"ICE server parsing failed: Invalid url with long "
"deprecated user@host syntax");
}
auto [success, address, port] =
ParseHostnameAndPortFromString(hoststring, default_port);
if (!success) {
RTC_LOG(LS_ERROR) << "Invalid hostname format: " << uri_without_transport;
LOG_AND_RETURN_ERROR(RTCErrorType::SYNTAX_ERROR,
"ICE server parsing failed: Invalid hostname format");
}
if (port <= 0 || port > 0xffff) {
RTC_LOG(LS_ERROR) << "Invalid port: " << port;
LOG_AND_RETURN_ERROR(RTCErrorType::SYNTAX_ERROR,
"ICE server parsing failed: Invalid port");
}
switch (service_type) {
case ServiceType::STUN:
case ServiceType::STUNS:
stun_servers->insert(rtc::SocketAddress(address, port));
break;
case ServiceType::TURN:
case ServiceType::TURNS: {
if (server.username.empty() || server.password.empty()) {
// The WebRTC spec requires throwing an InvalidAccessError when username
// or credential are ommitted; this is the native equivalent.
LOG_AND_RETURN_ERROR(
RTCErrorType::INVALID_PARAMETER,
"ICE server parsing failed: TURN server with empty "
"username or password");
}
// If the hostname field is not empty, then the server address must be
// the resolved IP for that host, the hostname is needed later for TLS
// handshake (SNI and Certificate verification).
absl::string_view hostname =
server.hostname.empty() ? address : server.hostname;
rtc::SocketAddress socket_address(hostname, port);
if (!server.hostname.empty()) {
rtc::IPAddress ip;
if (!IPFromString(address, &ip)) {
// When hostname is set, the server address must be a
// resolved ip address.
LOG_AND_RETURN_ERROR(
RTCErrorType::INVALID_PARAMETER,
"ICE server parsing failed: "
"IceServer has hostname field set, but URI does not "
"contain an IP address.");
}
socket_address.SetResolvedIP(ip);
}
cricket::RelayServerConfig config =
cricket::RelayServerConfig(socket_address, server.username,
server.password, turn_transport_type);
if (server.tls_cert_policy ==
PeerConnectionInterface::kTlsCertPolicyInsecureNoCheck) {
config.tls_cert_policy =
cricket::TlsCertPolicy::TLS_CERT_POLICY_INSECURE_NO_CHECK;
}
config.tls_alpn_protocols = server.tls_alpn_protocols;
config.tls_elliptic_curves = server.tls_elliptic_curves;
turn_servers->push_back(config);
break;
}
default:
// We shouldn't get to this point with an invalid service_type, we should
// have returned an error already.
LOG_AND_RETURN_ERROR(
RTCErrorType::INTERNAL_ERROR,
"ICE server parsing failed: Unexpected service type");
}
return RTCError::OK();
}
} // namespace
RTCError ParseIceServersOrError(
const PeerConnectionInterface::IceServers& servers,
cricket::ServerAddresses* stun_servers,
std::vector<cricket::RelayServerConfig>* turn_servers) {
for (const PeerConnectionInterface::IceServer& server : servers) {
if (!server.urls.empty()) {
for (const std::string& url : server.urls) {
if (url.empty()) {
LOG_AND_RETURN_ERROR(RTCErrorType::SYNTAX_ERROR,
"ICE server parsing failed: Empty uri.");
}
RTCError err =
ParseIceServerUrl(server, url, stun_servers, turn_servers);
if (!err.ok()) {
return err;
}
}
} else if (!server.uri.empty()) {
// Fallback to old .uri if new .urls isn't present.
RTCError err =
ParseIceServerUrl(server, server.uri, stun_servers, turn_servers);
if (!err.ok()) {
return err;
}
} else {
LOG_AND_RETURN_ERROR(RTCErrorType::SYNTAX_ERROR,
"ICE server parsing failed: Empty uri.");
}
}
return RTCError::OK();
}
RTCErrorType ParseIceServers(
const PeerConnectionInterface::IceServers& servers,
cricket::ServerAddresses* stun_servers,
std::vector<cricket::RelayServerConfig>* turn_servers) {
return ParseIceServersOrError(servers, stun_servers, turn_servers).type();
}
} // namespace webrtc

View file

@ -0,0 +1,42 @@
/*
* Copyright 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef PC_ICE_SERVER_PARSING_H_
#define PC_ICE_SERVER_PARSING_H_
#include <vector>
#include "api/peer_connection_interface.h"
#include "api/rtc_error.h"
#include "p2p/base/port.h"
#include "p2p/base/port_allocator.h"
#include "rtc_base/system/rtc_export.h"
namespace webrtc {
// Parses the URLs for each server in `servers` to build `stun_servers` and
// `turn_servers`. Can return SYNTAX_ERROR if the URL is malformed, or
// INVALID_PARAMETER if a TURN server is missing `username` or `password`.
//
// Intended to be used to convert/validate the servers passed into a
// PeerConnection through RTCConfiguration.
RTC_EXPORT RTCError
ParseIceServersOrError(const PeerConnectionInterface::IceServers& servers,
cricket::ServerAddresses* stun_servers,
std::vector<cricket::RelayServerConfig>* turn_servers);
[[deprecated("use ParseIceServersOrError")]] RTC_EXPORT RTCErrorType
ParseIceServers(const PeerConnectionInterface::IceServers& servers,
cricket::ServerAddresses* stun_servers,
std::vector<cricket::RelayServerConfig>* turn_servers);
} // namespace webrtc
#endif // PC_ICE_SERVER_PARSING_H_

View file

@ -0,0 +1,239 @@
/*
* Copyright 2012 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/ice_server_parsing.h"
#include <string>
#include <vector>
#include "p2p/base/port_interface.h"
#include "rtc_base/ip_address.h"
#include "rtc_base/socket_address.h"
#include "test/gtest.h"
namespace webrtc {
class IceServerParsingTest : public ::testing::Test {
public:
// Convenience functions for parsing a single URL. Result is stored in
// `stun_servers_` and `turn_servers_`.
bool ParseUrl(const std::string& url) {
return ParseUrl(url, std::string(), std::string());
}
bool ParseTurnUrl(const std::string& url) {
return ParseUrl(url, "username", "password");
}
bool ParseUrl(const std::string& url,
const std::string& username,
const std::string& password) {
return ParseUrl(
url, username, password,
PeerConnectionInterface::TlsCertPolicy::kTlsCertPolicySecure);
}
bool ParseUrl(const std::string& url,
const std::string& username,
const std::string& password,
PeerConnectionInterface::TlsCertPolicy tls_certificate_policy) {
return ParseUrl(url, username, password, tls_certificate_policy, "");
}
bool ParseUrl(const std::string& url,
const std::string& username,
const std::string& password,
PeerConnectionInterface::TlsCertPolicy tls_certificate_policy,
const std::string& hostname) {
stun_servers_.clear();
turn_servers_.clear();
PeerConnectionInterface::IceServers servers;
PeerConnectionInterface::IceServer server;
server.urls.push_back(url);
server.username = username;
server.password = password;
server.tls_cert_policy = tls_certificate_policy;
server.hostname = hostname;
servers.push_back(server);
return ParseIceServersOrError(servers, &stun_servers_, &turn_servers_).ok();
}
protected:
cricket::ServerAddresses stun_servers_;
std::vector<cricket::RelayServerConfig> turn_servers_;
};
// Make sure all STUN/TURN prefixes are parsed correctly.
TEST_F(IceServerParsingTest, ParseStunPrefixes) {
EXPECT_TRUE(ParseUrl("stun:hostname"));
EXPECT_EQ(1U, stun_servers_.size());
EXPECT_EQ(0U, turn_servers_.size());
EXPECT_TRUE(ParseUrl("stuns:hostname"));
EXPECT_EQ(1U, stun_servers_.size());
EXPECT_EQ(0U, turn_servers_.size());
EXPECT_TRUE(ParseTurnUrl("turn:hostname"));
EXPECT_EQ(0U, stun_servers_.size());
EXPECT_EQ(1U, turn_servers_.size());
EXPECT_EQ(cricket::PROTO_UDP, turn_servers_[0].ports[0].proto);
EXPECT_TRUE(ParseTurnUrl("turns:hostname"));
EXPECT_EQ(0U, stun_servers_.size());
EXPECT_EQ(1U, turn_servers_.size());
EXPECT_EQ(cricket::PROTO_TLS, turn_servers_[0].ports[0].proto);
EXPECT_TRUE(turn_servers_[0].tls_cert_policy ==
cricket::TlsCertPolicy::TLS_CERT_POLICY_SECURE);
EXPECT_TRUE(ParseUrl(
"turns:hostname", "username", "password",
PeerConnectionInterface::TlsCertPolicy::kTlsCertPolicyInsecureNoCheck));
EXPECT_EQ(0U, stun_servers_.size());
EXPECT_EQ(1U, turn_servers_.size());
EXPECT_TRUE(turn_servers_[0].tls_cert_policy ==
cricket::TlsCertPolicy::TLS_CERT_POLICY_INSECURE_NO_CHECK);
EXPECT_EQ(cricket::PROTO_TLS, turn_servers_[0].ports[0].proto);
// invalid prefixes
EXPECT_FALSE(ParseUrl("stunn:hostname"));
EXPECT_FALSE(ParseUrl(":hostname"));
EXPECT_FALSE(ParseUrl(":"));
EXPECT_FALSE(ParseUrl(""));
}
TEST_F(IceServerParsingTest, VerifyDefaults) {
// TURNS defaults
EXPECT_TRUE(ParseTurnUrl("turns:hostname"));
EXPECT_EQ(1U, turn_servers_.size());
EXPECT_EQ(5349, turn_servers_[0].ports[0].address.port());
EXPECT_EQ(cricket::PROTO_TLS, turn_servers_[0].ports[0].proto);
// TURN defaults
EXPECT_TRUE(ParseTurnUrl("turn:hostname"));
EXPECT_EQ(1U, turn_servers_.size());
EXPECT_EQ(3478, turn_servers_[0].ports[0].address.port());
EXPECT_EQ(cricket::PROTO_UDP, turn_servers_[0].ports[0].proto);
// STUN defaults
EXPECT_TRUE(ParseUrl("stun:hostname"));
EXPECT_EQ(1U, stun_servers_.size());
EXPECT_EQ(3478, stun_servers_.begin()->port());
}
// Check that the 6 combinations of IPv4/IPv6/hostname and with/without port
// can be parsed correctly.
TEST_F(IceServerParsingTest, ParseHostnameAndPort) {
EXPECT_TRUE(ParseUrl("stun:1.2.3.4:1234"));
EXPECT_EQ(1U, stun_servers_.size());
EXPECT_EQ("1.2.3.4", stun_servers_.begin()->hostname());
EXPECT_EQ(1234, stun_servers_.begin()->port());
EXPECT_TRUE(ParseUrl("stun:[1:2:3:4:5:6:7:8]:4321"));
EXPECT_EQ(1U, stun_servers_.size());
EXPECT_EQ("1:2:3:4:5:6:7:8", stun_servers_.begin()->hostname());
EXPECT_EQ(4321, stun_servers_.begin()->port());
EXPECT_TRUE(ParseUrl("stun:hostname:9999"));
EXPECT_EQ(1U, stun_servers_.size());
EXPECT_EQ("hostname", stun_servers_.begin()->hostname());
EXPECT_EQ(9999, stun_servers_.begin()->port());
EXPECT_TRUE(ParseUrl("stun:1.2.3.4"));
EXPECT_EQ(1U, stun_servers_.size());
EXPECT_EQ("1.2.3.4", stun_servers_.begin()->hostname());
EXPECT_EQ(3478, stun_servers_.begin()->port());
EXPECT_TRUE(ParseUrl("stun:[1:2:3:4:5:6:7:8]"));
EXPECT_EQ(1U, stun_servers_.size());
EXPECT_EQ("1:2:3:4:5:6:7:8", stun_servers_.begin()->hostname());
EXPECT_EQ(3478, stun_servers_.begin()->port());
EXPECT_TRUE(ParseUrl("stun:hostname"));
EXPECT_EQ(1U, stun_servers_.size());
EXPECT_EQ("hostname", stun_servers_.begin()->hostname());
EXPECT_EQ(3478, stun_servers_.begin()->port());
// Both TURN IP and host exist
EXPECT_TRUE(
ParseUrl("turn:1.2.3.4:1234", "username", "password",
PeerConnectionInterface::TlsCertPolicy::kTlsCertPolicySecure,
"hostname"));
EXPECT_EQ(1U, turn_servers_.size());
rtc::SocketAddress address = turn_servers_[0].ports[0].address;
EXPECT_EQ("hostname", address.hostname());
EXPECT_EQ(1234, address.port());
EXPECT_FALSE(address.IsUnresolvedIP());
EXPECT_EQ("1.2.3.4", address.ipaddr().ToString());
// Try some invalid hostname:port strings.
EXPECT_FALSE(ParseUrl("stun:hostname:99a99"));
EXPECT_FALSE(ParseUrl("stun:hostname:-1"));
EXPECT_FALSE(ParseUrl("stun:hostname:port:more"));
EXPECT_FALSE(ParseUrl("stun:hostname:port more"));
EXPECT_FALSE(ParseUrl("stun:hostname:"));
EXPECT_FALSE(ParseUrl("stun:[1:2:3:4:5:6:7:8]junk:1000"));
EXPECT_FALSE(ParseUrl("stun::5555"));
EXPECT_FALSE(ParseUrl("stun:"));
// Test illegal URLs according to RFC 3986 (URI generic syntax)
// and RFC 7064 (URI schemes for STUN and TURN)
EXPECT_FALSE(ParseUrl("stun:/hostname")); // / is not allowed
EXPECT_FALSE(ParseUrl("stun:?hostname")); // ? is not allowed
EXPECT_FALSE(ParseUrl("stun:#hostname")); // # is not allowed
// STUN explicitly forbids query parameters.
EXPECT_FALSE(ParseUrl("stun:hostname?transport=udp"));
}
// Test parsing the "?transport=xxx" part of the URL.
TEST_F(IceServerParsingTest, ParseTransport) {
EXPECT_TRUE(ParseTurnUrl("turn:hostname:1234?transport=tcp"));
EXPECT_EQ(1U, turn_servers_.size());
EXPECT_EQ(cricket::PROTO_TCP, turn_servers_[0].ports[0].proto);
EXPECT_TRUE(ParseTurnUrl("turn:hostname?transport=udp"));
EXPECT_EQ(1U, turn_servers_.size());
EXPECT_EQ(cricket::PROTO_UDP, turn_servers_[0].ports[0].proto);
EXPECT_FALSE(ParseTurnUrl("turn:hostname?transport=invalid"));
EXPECT_FALSE(ParseTurnUrl("turn:hostname?transport="));
EXPECT_FALSE(ParseTurnUrl("turn:hostname?="));
EXPECT_FALSE(ParseTurnUrl("turn:hostname?"));
EXPECT_FALSE(ParseTurnUrl("?"));
}
// Reject pre-RFC 7065 syntax with ICE username contained in URL.
TEST_F(IceServerParsingTest, ParseRejectsUsername) {
EXPECT_FALSE(ParseTurnUrl("turn:user@hostname"));
}
// Test that username and password from IceServer is copied into the resulting
// RelayServerConfig.
TEST_F(IceServerParsingTest, CopyUsernameAndPasswordFromIceServer) {
EXPECT_TRUE(ParseUrl("turn:hostname", "username", "password"));
EXPECT_EQ(1U, turn_servers_.size());
EXPECT_EQ("username", turn_servers_[0].credentials.username);
EXPECT_EQ("password", turn_servers_[0].credentials.password);
}
// Ensure that if a server has multiple URLs, each one is parsed.
TEST_F(IceServerParsingTest, ParseMultipleUrls) {
PeerConnectionInterface::IceServers servers;
PeerConnectionInterface::IceServer server;
server.urls.push_back("stun:hostname");
server.urls.push_back("turn:hostname");
server.username = "foo";
server.password = "bar";
servers.push_back(server);
EXPECT_TRUE(
ParseIceServersOrError(servers, &stun_servers_, &turn_servers_).ok());
EXPECT_EQ(1U, stun_servers_.size());
EXPECT_EQ(1U, turn_servers_.size());
}
} // namespace webrtc

View file

@ -0,0 +1,36 @@
/*
* Copyright 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/ice_transport.h"
#include "api/sequence_checker.h"
namespace webrtc {
IceTransportWithPointer::~IceTransportWithPointer() {
// We depend on the networking thread to call Clear() before dropping
// its last reference to this object; if the destructor is called
// on the networking thread, it's OK to not have called Clear().
if (internal_) {
RTC_DCHECK_RUN_ON(creator_thread_);
}
}
cricket::IceTransportInternal* IceTransportWithPointer::internal() {
RTC_DCHECK_RUN_ON(creator_thread_);
return internal_;
}
void IceTransportWithPointer::Clear() {
RTC_DCHECK_RUN_ON(creator_thread_);
internal_ = nullptr;
}
} // namespace webrtc

View file

@ -0,0 +1,52 @@
/*
* Copyright 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef PC_ICE_TRANSPORT_H_
#define PC_ICE_TRANSPORT_H_
#include "api/ice_transport_interface.h"
#include "rtc_base/checks.h"
#include "rtc_base/thread.h"
#include "rtc_base/thread_annotations.h"
namespace webrtc {
// Implementation of IceTransportInterface that does not take ownership
// of its underlying IceTransport. It depends on its creator class to
// ensure that Clear() is called before the underlying IceTransport
// is deallocated.
class IceTransportWithPointer : public IceTransportInterface {
public:
explicit IceTransportWithPointer(cricket::IceTransportInternal* internal)
: creator_thread_(rtc::Thread::Current()), internal_(internal) {
RTC_DCHECK(internal_);
}
IceTransportWithPointer() = delete;
IceTransportWithPointer(const IceTransportWithPointer&) = delete;
IceTransportWithPointer& operator=(const IceTransportWithPointer&) = delete;
cricket::IceTransportInternal* internal() override;
// This call will ensure that the pointer passed at construction is
// no longer in use by this object. Later calls to internal() will return
// null.
void Clear();
protected:
~IceTransportWithPointer() override;
private:
const rtc::Thread* creator_thread_;
cricket::IceTransportInternal* internal_ RTC_GUARDED_BY(creator_thread_);
};
} // namespace webrtc
#endif // PC_ICE_TRANSPORT_H_

View file

@ -0,0 +1,64 @@
/*
* Copyright 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/ice_transport.h"
#include <memory>
#include <utility>
#include "api/ice_transport_factory.h"
#include "api/make_ref_counted.h"
#include "api/scoped_refptr.h"
#include "p2p/base/fake_ice_transport.h"
#include "p2p/base/fake_port_allocator.h"
#include "rtc_base/internal/default_socket_server.h"
#include "test/gtest.h"
#include "test/scoped_key_value_config.h"
namespace webrtc {
class IceTransportTest : public ::testing::Test {
protected:
IceTransportTest()
: socket_server_(rtc::CreateDefaultSocketServer()),
main_thread_(socket_server_.get()) {}
rtc::SocketServer* socket_server() const { return socket_server_.get(); }
test::ScopedKeyValueConfig field_trials_;
private:
std::unique_ptr<rtc::SocketServer> socket_server_;
rtc::AutoSocketServerThread main_thread_;
};
TEST_F(IceTransportTest, CreateNonSelfDeletingTransport) {
auto cricket_transport =
std::make_unique<cricket::FakeIceTransport>("name", 0, nullptr);
auto ice_transport =
rtc::make_ref_counted<IceTransportWithPointer>(cricket_transport.get());
EXPECT_EQ(ice_transport->internal(), cricket_transport.get());
ice_transport->Clear();
EXPECT_NE(ice_transport->internal(), cricket_transport.get());
}
TEST_F(IceTransportTest, CreateSelfDeletingTransport) {
std::unique_ptr<cricket::FakePortAllocator> port_allocator(
std::make_unique<cricket::FakePortAllocator>(
nullptr,
std::make_unique<rtc::BasicPacketSocketFactory>(socket_server()),
&field_trials_));
IceTransportInit init;
init.set_port_allocator(port_allocator.get());
auto ice_transport = CreateIceTransport(std::move(init));
EXPECT_NE(nullptr, ice_transport->internal());
}
} // namespace webrtc

View file

@ -0,0 +1,38 @@
/*
* Copyright 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/jitter_buffer_delay.h"
#include "api/sequence_checker.h"
#include "rtc_base/checks.h"
#include "rtc_base/numerics/safe_conversions.h"
#include "rtc_base/numerics/safe_minmax.h"
namespace {
constexpr int kDefaultDelay = 0;
constexpr int kMaximumDelayMs = 10000;
} // namespace
namespace webrtc {
void JitterBufferDelay::Set(absl::optional<double> delay_seconds) {
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
cached_delay_seconds_ = delay_seconds;
}
int JitterBufferDelay::GetMs() const {
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
return rtc::SafeClamp(
rtc::saturated_cast<int>(cached_delay_seconds_.value_or(kDefaultDelay) *
1000),
0, kMaximumDelayMs);
}
} // namespace webrtc

View file

@ -0,0 +1,42 @@
/*
* Copyright 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef PC_JITTER_BUFFER_DELAY_H_
#define PC_JITTER_BUFFER_DELAY_H_
#include <stdint.h>
#include "absl/types/optional.h"
#include "api/sequence_checker.h"
#include "rtc_base/system/no_unique_address.h"
#include "rtc_base/thread_annotations.h"
namespace webrtc {
// JitterBufferDelay converts delay from seconds to milliseconds for the
// underlying media channel. It also handles cases when user sets delay before
// the start of media_channel by caching its request.
class JitterBufferDelay {
public:
JitterBufferDelay() = default;
void Set(absl::optional<double> delay_seconds);
int GetMs() const;
private:
RTC_NO_UNIQUE_ADDRESS SequenceChecker worker_thread_checker_{
SequenceChecker::kDetached};
absl::optional<double> cached_delay_seconds_
RTC_GUARDED_BY(&worker_thread_checker_);
};
} // namespace webrtc
#endif // PC_JITTER_BUFFER_DELAY_H_

View file

@ -0,0 +1,56 @@
/*
* Copyright 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/jitter_buffer_delay.h"
#include "test/gtest.h"
namespace webrtc {
class JitterBufferDelayTest : public ::testing::Test {
public:
JitterBufferDelayTest() {}
protected:
JitterBufferDelay delay_;
};
TEST_F(JitterBufferDelayTest, Set) {
// Delay in seconds.
delay_.Set(3.0);
EXPECT_EQ(delay_.GetMs(), 3000);
}
TEST_F(JitterBufferDelayTest, DefaultValue) {
EXPECT_EQ(delay_.GetMs(), 0); // Default value is 0ms.
}
TEST_F(JitterBufferDelayTest, Clamping) {
// In current Jitter Buffer implementation (Audio or Video) maximum supported
// value is 10000 milliseconds.
delay_.Set(10.5);
EXPECT_EQ(delay_.GetMs(), 10000);
// Test int overflow.
delay_.Set(21474836470.0);
EXPECT_EQ(delay_.GetMs(), 10000);
delay_.Set(-21474836470.0);
EXPECT_EQ(delay_.GetMs(), 0);
// Boundary value in seconds to milliseconds conversion.
delay_.Set(0.0009);
EXPECT_EQ(delay_.GetMs(), 0);
delay_.Set(-2.0);
EXPECT_EQ(delay_.GetMs(), 0);
}
} // namespace webrtc

View file

@ -0,0 +1,76 @@
/*
* Copyright 2012 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "api/jsep_ice_candidate.h"
#include "pc/webrtc_sdp.h"
// This file contains JsepIceCandidate-related functions that are not
// included in api/jsep_ice_candidate.cc. Some of these link to SDP
// parsing/serializing functions, which some users may not want.
// TODO(bugs.webrtc.org/12330): Merge the two .cc files somehow.
namespace webrtc {
IceCandidateInterface* CreateIceCandidate(const std::string& sdp_mid,
int sdp_mline_index,
const std::string& sdp,
SdpParseError* error) {
JsepIceCandidate* jsep_ice = new JsepIceCandidate(sdp_mid, sdp_mline_index);
if (!jsep_ice->Initialize(sdp, error)) {
delete jsep_ice;
return NULL;
}
return jsep_ice;
}
std::unique_ptr<IceCandidateInterface> CreateIceCandidate(
const std::string& sdp_mid,
int sdp_mline_index,
const cricket::Candidate& candidate) {
return std::make_unique<JsepIceCandidate>(sdp_mid, sdp_mline_index,
candidate);
}
JsepIceCandidate::JsepIceCandidate(const std::string& sdp_mid,
int sdp_mline_index)
: sdp_mid_(sdp_mid), sdp_mline_index_(sdp_mline_index) {}
JsepIceCandidate::JsepIceCandidate(const std::string& sdp_mid,
int sdp_mline_index,
const cricket::Candidate& candidate)
: sdp_mid_(sdp_mid),
sdp_mline_index_(sdp_mline_index),
candidate_(candidate) {}
JsepIceCandidate::~JsepIceCandidate() {}
JsepCandidateCollection JsepCandidateCollection::Clone() const {
JsepCandidateCollection new_collection;
for (const auto& candidate : candidates_) {
new_collection.candidates_.push_back(std::make_unique<JsepIceCandidate>(
candidate->sdp_mid(), candidate->sdp_mline_index(),
candidate->candidate()));
}
return new_collection;
}
bool JsepIceCandidate::Initialize(const std::string& sdp, SdpParseError* err) {
return SdpDeserializeCandidate(sdp, this, err);
}
bool JsepIceCandidate::ToString(std::string* out) const {
if (!out)
return false;
*out = SdpSerializeCandidate(*this);
return !out->empty();
}
} // namespace webrtc

View file

@ -0,0 +1,343 @@
/*
* Copyright 2012 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "api/jsep_session_description.h"
#include <memory>
#include <utility>
#include "absl/types/optional.h"
#include "p2p/base/p2p_constants.h"
#include "p2p/base/port.h"
#include "p2p/base/transport_description.h"
#include "p2p/base/transport_info.h"
#include "pc/media_session.h" // IWYU pragma: keep
#include "pc/webrtc_sdp.h"
#include "rtc_base/checks.h"
#include "rtc_base/ip_address.h"
#include "rtc_base/logging.h"
#include "rtc_base/net_helper.h"
#include "rtc_base/socket_address.h"
using cricket::Candidate;
using cricket::SessionDescription;
namespace webrtc {
namespace {
constexpr char kDummyAddress[] = "0.0.0.0";
constexpr int kDummyPort = 9;
// Update the connection address for the MediaContentDescription based on the
// candidates.
void UpdateConnectionAddress(
const JsepCandidateCollection& candidate_collection,
cricket::MediaContentDescription* media_desc) {
int port = kDummyPort;
std::string ip = kDummyAddress;
std::string hostname;
int current_preference = 0; // Start with lowest preference.
int current_family = AF_UNSPEC;
for (size_t i = 0; i < candidate_collection.count(); ++i) {
const IceCandidateInterface* jsep_candidate = candidate_collection.at(i);
if (jsep_candidate->candidate().component() !=
cricket::ICE_CANDIDATE_COMPONENT_RTP) {
continue;
}
// Default destination should be UDP only.
if (jsep_candidate->candidate().protocol() != cricket::UDP_PROTOCOL_NAME) {
continue;
}
const int preference = jsep_candidate->candidate().type_preference();
const int family = jsep_candidate->candidate().address().ipaddr().family();
// See if this candidate is more preferable then the current one if it's the
// same family. Or if the current family is IPv4 already so we could safely
// ignore all IPv6 ones. WebRTC bug 4269.
// http://code.google.com/p/webrtc/issues/detail?id=4269
if ((preference <= current_preference && current_family == family) ||
(current_family == AF_INET && family == AF_INET6)) {
continue;
}
current_preference = preference;
current_family = family;
const rtc::SocketAddress& candidate_addr =
jsep_candidate->candidate().address();
port = candidate_addr.port();
ip = candidate_addr.ipaddr().ToString();
hostname = candidate_addr.hostname();
}
rtc::SocketAddress connection_addr(ip, port);
if (rtc::IPIsUnspec(connection_addr.ipaddr()) && !hostname.empty()) {
// When a hostname candidate becomes the (default) connection address,
// we use the dummy address 0.0.0.0 and port 9 in the c= and the m= lines.
//
// We have observed in deployment that with a FQDN in a c= line, SDP parsing
// could fail in other JSEP implementations. We note that the wildcard
// addresses (0.0.0.0 or ::) with port 9 are given the exception as the
// connection address that will not result in an ICE mismatch
// (draft-ietf-mmusic-ice-sip-sdp). Also, 0.0.0.0 or :: can be used as the
// connection address in the initial offer or answer with trickle ICE
// if the offerer or answerer does not want to include the host IP address
// (draft-ietf-mmusic-trickle-ice-sip), and in particular 0.0.0.0 has been
// widely deployed for this use without outstanding compatibility issues.
// Combining the above considerations, we use 0.0.0.0 with port 9 to
// populate the c= and the m= lines. See `BuildMediaDescription` in
// webrtc_sdp.cc for the SDP generation with
// `media_desc->connection_address()`.
connection_addr = rtc::SocketAddress(kDummyAddress, kDummyPort);
}
media_desc->set_connection_address(connection_addr);
}
} // namespace
// TODO(steveanton): Remove this default implementation once Chromium has been
// updated.
SdpType SessionDescriptionInterface::GetType() const {
absl::optional<SdpType> maybe_type = SdpTypeFromString(type());
if (maybe_type) {
return *maybe_type;
} else {
RTC_LOG(LS_WARNING) << "Default implementation of "
"SessionDescriptionInterface::GetType does not "
"recognize the result from type(), returning "
"kOffer.";
return SdpType::kOffer;
}
}
SessionDescriptionInterface* CreateSessionDescription(const std::string& type,
const std::string& sdp,
SdpParseError* error) {
absl::optional<SdpType> maybe_type = SdpTypeFromString(type);
if (!maybe_type) {
return nullptr;
}
return CreateSessionDescription(*maybe_type, sdp, error).release();
}
std::unique_ptr<SessionDescriptionInterface> CreateSessionDescription(
SdpType type,
const std::string& sdp) {
return CreateSessionDescription(type, sdp, nullptr);
}
std::unique_ptr<SessionDescriptionInterface> CreateSessionDescription(
SdpType type,
const std::string& sdp,
SdpParseError* error_out) {
auto jsep_desc = std::make_unique<JsepSessionDescription>(type);
if (type != SdpType::kRollback) {
if (!SdpDeserialize(sdp, jsep_desc.get(), error_out)) {
return nullptr;
}
}
return std::move(jsep_desc);
}
std::unique_ptr<SessionDescriptionInterface> CreateSessionDescription(
SdpType type,
const std::string& session_id,
const std::string& session_version,
std::unique_ptr<cricket::SessionDescription> description) {
auto jsep_description = std::make_unique<JsepSessionDescription>(type);
bool initialize_success = jsep_description->Initialize(
std::move(description), session_id, session_version);
RTC_DCHECK(initialize_success);
return std::move(jsep_description);
}
JsepSessionDescription::JsepSessionDescription(SdpType type) : type_(type) {}
JsepSessionDescription::JsepSessionDescription(const std::string& type) {
absl::optional<SdpType> maybe_type = SdpTypeFromString(type);
if (maybe_type) {
type_ = *maybe_type;
} else {
RTC_LOG(LS_WARNING)
<< "JsepSessionDescription constructed with invalid type string: "
<< type << ". Assuming it is an offer.";
type_ = SdpType::kOffer;
}
}
JsepSessionDescription::JsepSessionDescription(
SdpType type,
std::unique_ptr<cricket::SessionDescription> description,
absl::string_view session_id,
absl::string_view session_version)
: description_(std::move(description)),
session_id_(session_id),
session_version_(session_version),
type_(type) {
RTC_DCHECK(description_);
candidate_collection_.resize(number_of_mediasections());
}
JsepSessionDescription::~JsepSessionDescription() {}
bool JsepSessionDescription::Initialize(
std::unique_ptr<cricket::SessionDescription> description,
const std::string& session_id,
const std::string& session_version) {
if (!description)
return false;
session_id_ = session_id;
session_version_ = session_version;
description_ = std::move(description);
candidate_collection_.resize(number_of_mediasections());
return true;
}
std::unique_ptr<SessionDescriptionInterface> JsepSessionDescription::Clone()
const {
auto new_description = std::make_unique<JsepSessionDescription>(type_);
new_description->session_id_ = session_id_;
new_description->session_version_ = session_version_;
if (description_) {
new_description->description_ = description_->Clone();
}
for (const auto& collection : candidate_collection_) {
new_description->candidate_collection_.push_back(collection.Clone());
}
return new_description;
}
bool JsepSessionDescription::AddCandidate(
const IceCandidateInterface* candidate) {
if (!candidate)
return false;
size_t mediasection_index = 0;
if (!GetMediasectionIndex(candidate, &mediasection_index)) {
return false;
}
if (mediasection_index >= number_of_mediasections())
return false;
const std::string& content_name =
description_->contents()[mediasection_index].name;
const cricket::TransportInfo* transport_info =
description_->GetTransportInfoByName(content_name);
if (!transport_info) {
return false;
}
Candidate updated_candidate = candidate->candidate();
if (updated_candidate.username().empty()) {
updated_candidate.set_username(transport_info->description.ice_ufrag);
}
if (updated_candidate.password().empty()) {
updated_candidate.set_password(transport_info->description.ice_pwd);
}
std::unique_ptr<JsepIceCandidate> updated_candidate_wrapper(
new JsepIceCandidate(candidate->sdp_mid(),
static_cast<int>(mediasection_index),
updated_candidate));
if (!candidate_collection_[mediasection_index].HasCandidate(
updated_candidate_wrapper.get())) {
candidate_collection_[mediasection_index].add(
updated_candidate_wrapper.release());
UpdateConnectionAddress(
candidate_collection_[mediasection_index],
description_->contents()[mediasection_index].media_description());
}
return true;
}
size_t JsepSessionDescription::RemoveCandidates(
const std::vector<Candidate>& candidates) {
size_t num_removed = 0;
for (auto& candidate : candidates) {
int mediasection_index = GetMediasectionIndex(candidate);
if (mediasection_index < 0) {
// Not found.
continue;
}
num_removed += candidate_collection_[mediasection_index].remove(candidate);
UpdateConnectionAddress(
candidate_collection_[mediasection_index],
description_->contents()[mediasection_index].media_description());
}
return num_removed;
}
size_t JsepSessionDescription::number_of_mediasections() const {
if (!description_)
return 0;
return description_->contents().size();
}
const IceCandidateCollection* JsepSessionDescription::candidates(
size_t mediasection_index) const {
if (mediasection_index >= candidate_collection_.size())
return NULL;
return &candidate_collection_[mediasection_index];
}
bool JsepSessionDescription::ToString(std::string* out) const {
if (!description_ || !out) {
return false;
}
*out = SdpSerialize(*this);
return !out->empty();
}
bool JsepSessionDescription::GetMediasectionIndex(
const IceCandidateInterface* candidate,
size_t* index) {
if (!candidate || !index) {
return false;
}
// If the candidate has no valid mline index or sdp_mid, it is impossible
// to find a match.
if (candidate->sdp_mid().empty() &&
(candidate->sdp_mline_index() < 0 ||
static_cast<size_t>(candidate->sdp_mline_index()) >=
description_->contents().size())) {
return false;
}
if (candidate->sdp_mline_index() >= 0)
*index = static_cast<size_t>(candidate->sdp_mline_index());
if (description_ && !candidate->sdp_mid().empty()) {
bool found = false;
// Try to match the sdp_mid with content name.
for (size_t i = 0; i < description_->contents().size(); ++i) {
if (candidate->sdp_mid() == description_->contents().at(i).name) {
*index = i;
found = true;
break;
}
}
if (!found) {
// If the sdp_mid is presented but we can't find a match, we consider
// this as an error.
return false;
}
}
return true;
}
int JsepSessionDescription::GetMediasectionIndex(const Candidate& candidate) {
// Find the description with a matching transport name of the candidate.
const std::string& transport_name = candidate.transport_name();
for (size_t i = 0; i < description_->contents().size(); ++i) {
if (transport_name == description_->contents().at(i).name) {
return static_cast<int>(i);
}
}
return -1;
}
} // namespace webrtc

View file

@ -0,0 +1,530 @@
/*
* Copyright 2012 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "api/jsep_session_description.h"
#include <stddef.h>
#include <stdint.h>
#include <utility>
#include <vector>
#include "api/candidate.h"
#include "api/jsep.h"
#include "api/jsep_ice_candidate.h"
#include "media/base/codec.h"
#include "p2p/base/p2p_constants.h"
#include "p2p/base/port.h"
#include "p2p/base/transport_description.h"
#include "p2p/base/transport_info.h"
#include "pc/session_description.h"
#include "pc/webrtc_sdp.h"
#include "rtc_base/helpers.h"
#include "rtc_base/net_helper.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/string_encode.h"
#include "test/gtest.h"
using cricket::MediaProtocolType;
using ::testing::Values;
using webrtc::IceCandidateCollection;
using webrtc::IceCandidateInterface;
using webrtc::JsepIceCandidate;
using webrtc::JsepSessionDescription;
using webrtc::SdpType;
using webrtc::SessionDescriptionInterface;
static const char kCandidateUfrag[] = "ufrag";
static const char kCandidatePwd[] = "pwd";
static const char kCandidateUfragVoice[] = "ufrag_voice";
static const char kCandidatePwdVoice[] = "pwd_voice";
static const char kCandidateUfragVideo[] = "ufrag_video";
static const char kCandidatePwdVideo[] = "pwd_video";
static const char kCandidateFoundation[] = "a0+B/1";
static const uint32_t kCandidatePriority = 2130706432U; // pref = 1.0
static const uint32_t kCandidateGeneration = 2;
// This creates a session description with both audio and video media contents.
// In SDP this is described by two m lines, one audio and one video.
static std::unique_ptr<cricket::SessionDescription>
CreateCricketSessionDescription() {
auto desc = std::make_unique<cricket::SessionDescription>();
// AudioContentDescription
auto audio = std::make_unique<cricket::AudioContentDescription>();
// VideoContentDescription
auto video = std::make_unique<cricket::VideoContentDescription>();
audio->AddCodec(cricket::CreateAudioCodec(103, "ISAC", 16000, 0));
desc->AddContent(cricket::CN_AUDIO, MediaProtocolType::kRtp,
std::move(audio));
video->AddCodec(cricket::CreateVideoCodec(120, "VP8"));
desc->AddContent(cricket::CN_VIDEO, MediaProtocolType::kRtp,
std::move(video));
desc->AddTransportInfo(cricket::TransportInfo(
cricket::CN_AUDIO,
cricket::TransportDescription(
std::vector<std::string>(), kCandidateUfragVoice, kCandidatePwdVoice,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_NONE, NULL)));
desc->AddTransportInfo(cricket::TransportInfo(
cricket::CN_VIDEO,
cricket::TransportDescription(
std::vector<std::string>(), kCandidateUfragVideo, kCandidatePwdVideo,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_NONE, NULL)));
return desc;
}
class JsepSessionDescriptionTest : public ::testing::Test {
protected:
virtual void SetUp() {
int port = 1234;
rtc::SocketAddress address("127.0.0.1", port++);
cricket::Candidate candidate(cricket::ICE_CANDIDATE_COMPONENT_RTP, "udp",
address, 1, "", "", "local", 0, "1");
candidate_ = candidate;
const std::string session_id = rtc::ToString(rtc::CreateRandomId64());
const std::string session_version = rtc::ToString(rtc::CreateRandomId());
jsep_desc_ = std::make_unique<JsepSessionDescription>(SdpType::kOffer);
ASSERT_TRUE(jsep_desc_->Initialize(CreateCricketSessionDescription(),
session_id, session_version));
}
std::string Serialize(const SessionDescriptionInterface* desc) {
std::string sdp;
EXPECT_TRUE(desc->ToString(&sdp));
EXPECT_FALSE(sdp.empty());
return sdp;
}
std::unique_ptr<SessionDescriptionInterface> DeSerialize(
const std::string& sdp) {
auto jsep_desc = std::make_unique<JsepSessionDescription>(SdpType::kOffer);
EXPECT_TRUE(webrtc::SdpDeserialize(sdp, jsep_desc.get(), nullptr));
return std::move(jsep_desc);
}
cricket::Candidate candidate_;
std::unique_ptr<JsepSessionDescription> jsep_desc_;
};
TEST_F(JsepSessionDescriptionTest, CloneDefault) {
auto new_desc = jsep_desc_->Clone();
EXPECT_EQ(jsep_desc_->type(), new_desc->type());
std::string old_desc_string;
std::string new_desc_string;
EXPECT_TRUE(jsep_desc_->ToString(&old_desc_string));
EXPECT_TRUE(new_desc->ToString(&new_desc_string));
EXPECT_EQ(old_desc_string, new_desc_string);
EXPECT_EQ(jsep_desc_->session_id(), new_desc->session_id());
EXPECT_EQ(jsep_desc_->session_version(), new_desc->session_version());
}
TEST_F(JsepSessionDescriptionTest, CloneRollback) {
auto jsep_desc = std::make_unique<JsepSessionDescription>(SdpType::kRollback);
auto new_desc = jsep_desc->Clone();
EXPECT_EQ(jsep_desc->type(), new_desc->type());
}
TEST_F(JsepSessionDescriptionTest, CloneWithCandidates) {
cricket::Candidate candidate_v4(
cricket::ICE_CANDIDATE_COMPONENT_RTP, "udp",
rtc::SocketAddress("192.168.1.5", 1234), kCandidatePriority, "", "",
cricket::STUN_PORT_TYPE, kCandidateGeneration, kCandidateFoundation);
cricket::Candidate candidate_v6(
cricket::ICE_CANDIDATE_COMPONENT_RTP, "udp",
rtc::SocketAddress("::1", 1234), kCandidatePriority, "", "",
cricket::LOCAL_PORT_TYPE, kCandidateGeneration, kCandidateFoundation);
JsepIceCandidate jice_v4("audio", 0, candidate_v4);
JsepIceCandidate jice_v6("audio", 0, candidate_v6);
JsepIceCandidate jice_v4_video("video", 0, candidate_v4);
JsepIceCandidate jice_v6_video("video", 0, candidate_v6);
ASSERT_TRUE(jsep_desc_->AddCandidate(&jice_v4));
ASSERT_TRUE(jsep_desc_->AddCandidate(&jice_v6));
ASSERT_TRUE(jsep_desc_->AddCandidate(&jice_v4_video));
ASSERT_TRUE(jsep_desc_->AddCandidate(&jice_v6_video));
auto new_desc = jsep_desc_->Clone();
EXPECT_EQ(jsep_desc_->type(), new_desc->type());
std::string old_desc_string;
std::string new_desc_string;
EXPECT_TRUE(jsep_desc_->ToString(&old_desc_string));
EXPECT_TRUE(new_desc->ToString(&new_desc_string));
EXPECT_EQ(old_desc_string, new_desc_string);
}
// Test that number_of_mediasections() returns the number of media contents in
// a session description.
TEST_F(JsepSessionDescriptionTest, CheckSessionDescription) {
EXPECT_EQ(2u, jsep_desc_->number_of_mediasections());
}
// Test that we can add a candidate to a session description without MID.
TEST_F(JsepSessionDescriptionTest, AddCandidateWithoutMid) {
JsepIceCandidate jsep_candidate("", 0, candidate_);
EXPECT_TRUE(jsep_desc_->AddCandidate(&jsep_candidate));
const IceCandidateCollection* ice_candidates = jsep_desc_->candidates(0);
ASSERT_TRUE(ice_candidates != NULL);
EXPECT_EQ(1u, ice_candidates->count());
const IceCandidateInterface* ice_candidate = ice_candidates->at(0);
ASSERT_TRUE(ice_candidate != NULL);
candidate_.set_username(kCandidateUfragVoice);
candidate_.set_password(kCandidatePwdVoice);
EXPECT_TRUE(ice_candidate->candidate().IsEquivalent(candidate_));
EXPECT_EQ(0, ice_candidate->sdp_mline_index());
EXPECT_EQ(0u, jsep_desc_->candidates(1)->count());
}
// Test that we can add and remove candidates to a session description with
// MID. Removing candidates requires MID (transport_name).
TEST_F(JsepSessionDescriptionTest, AddAndRemoveCandidatesWithMid) {
// mid and m-line index don't match, in this case mid is preferred.
std::string mid = "video";
JsepIceCandidate jsep_candidate(mid, 0, candidate_);
EXPECT_TRUE(jsep_desc_->AddCandidate(&jsep_candidate));
EXPECT_EQ(0u, jsep_desc_->candidates(0)->count());
const IceCandidateCollection* ice_candidates = jsep_desc_->candidates(1);
ASSERT_TRUE(ice_candidates != NULL);
EXPECT_EQ(1u, ice_candidates->count());
const IceCandidateInterface* ice_candidate = ice_candidates->at(0);
ASSERT_TRUE(ice_candidate != NULL);
candidate_.set_username(kCandidateUfragVideo);
candidate_.set_password(kCandidatePwdVideo);
EXPECT_TRUE(ice_candidate->candidate().IsEquivalent(candidate_));
// The mline index should have been updated according to mid.
EXPECT_EQ(1, ice_candidate->sdp_mline_index());
std::vector<cricket::Candidate> candidates(1, candidate_);
candidates[0].set_transport_name(mid);
EXPECT_EQ(1u, jsep_desc_->RemoveCandidates(candidates));
EXPECT_EQ(0u, jsep_desc_->candidates(0)->count());
EXPECT_EQ(0u, jsep_desc_->candidates(1)->count());
}
TEST_F(JsepSessionDescriptionTest, AddCandidateAlreadyHasUfrag) {
candidate_.set_username(kCandidateUfrag);
candidate_.set_password(kCandidatePwd);
JsepIceCandidate jsep_candidate("audio", 0, candidate_);
EXPECT_TRUE(jsep_desc_->AddCandidate(&jsep_candidate));
const IceCandidateCollection* ice_candidates = jsep_desc_->candidates(0);
ASSERT_TRUE(ice_candidates != NULL);
EXPECT_EQ(1u, ice_candidates->count());
const IceCandidateInterface* ice_candidate = ice_candidates->at(0);
ASSERT_TRUE(ice_candidate != NULL);
candidate_.set_username(kCandidateUfrag);
candidate_.set_password(kCandidatePwd);
EXPECT_TRUE(ice_candidate->candidate().IsEquivalent(candidate_));
EXPECT_EQ(0u, jsep_desc_->candidates(1)->count());
}
// Test that we can not add a candidate if there is no corresponding media
// content in the session description.
TEST_F(JsepSessionDescriptionTest, AddBadCandidate) {
JsepIceCandidate bad_candidate1("", 55, candidate_);
EXPECT_FALSE(jsep_desc_->AddCandidate(&bad_candidate1));
JsepIceCandidate bad_candidate2("some weird mid", 0, candidate_);
EXPECT_FALSE(jsep_desc_->AddCandidate(&bad_candidate2));
}
// Tests that repeatedly adding the same candidate, with or without credentials,
// does not increase the number of candidates in the description.
TEST_F(JsepSessionDescriptionTest, AddCandidateDuplicates) {
JsepIceCandidate jsep_candidate("", 0, candidate_);
EXPECT_TRUE(jsep_desc_->AddCandidate(&jsep_candidate));
EXPECT_EQ(1u, jsep_desc_->candidates(0)->count());
// Add the same candidate again. It should be ignored.
EXPECT_TRUE(jsep_desc_->AddCandidate(&jsep_candidate));
EXPECT_EQ(1u, jsep_desc_->candidates(0)->count());
// Create a new candidate, identical except that the ufrag and pwd are now
// populated.
candidate_.set_username(kCandidateUfragVoice);
candidate_.set_password(kCandidatePwdVoice);
JsepIceCandidate jsep_candidate_with_credentials("", 0, candidate_);
// This should also be identified as redundant and ignored.
EXPECT_TRUE(jsep_desc_->AddCandidate(&jsep_candidate_with_credentials));
EXPECT_EQ(1u, jsep_desc_->candidates(0)->count());
}
// Test that the connection address is set to a hostname address after adding a
// hostname candidate.
TEST_F(JsepSessionDescriptionTest, AddHostnameCandidate) {
cricket::Candidate c;
c.set_component(cricket::ICE_CANDIDATE_COMPONENT_RTP);
c.set_protocol(cricket::UDP_PROTOCOL_NAME);
c.set_address(rtc::SocketAddress("example.local", 1234));
c.set_type(cricket::LOCAL_PORT_TYPE);
const size_t audio_index = 0;
JsepIceCandidate hostname_candidate("audio", audio_index, c);
EXPECT_TRUE(jsep_desc_->AddCandidate(&hostname_candidate));
ASSERT_NE(nullptr, jsep_desc_->description());
ASSERT_EQ(2u, jsep_desc_->description()->contents().size());
const auto& content = jsep_desc_->description()->contents()[audio_index];
EXPECT_EQ("0.0.0.0:9",
content.media_description()->connection_address().ToString());
}
// Test that we can serialize a JsepSessionDescription and deserialize it again.
TEST_F(JsepSessionDescriptionTest, SerializeDeserialize) {
std::string sdp = Serialize(jsep_desc_.get());
auto parsed_jsep_desc = DeSerialize(sdp);
EXPECT_EQ(2u, parsed_jsep_desc->number_of_mediasections());
std::string parsed_sdp = Serialize(parsed_jsep_desc.get());
EXPECT_EQ(sdp, parsed_sdp);
}
// Test that we can serialize a JsepSessionDescription when a hostname candidate
// is the default destination and deserialize it again. The connection address
// in the deserialized description should be the dummy address 0.0.0.0:9.
TEST_F(JsepSessionDescriptionTest, SerializeDeserializeWithHostnameCandidate) {
cricket::Candidate c;
c.set_component(cricket::ICE_CANDIDATE_COMPONENT_RTP);
c.set_protocol(cricket::UDP_PROTOCOL_NAME);
c.set_address(rtc::SocketAddress("example.local", 1234));
c.set_type(cricket::LOCAL_PORT_TYPE);
const size_t audio_index = 0;
const size_t video_index = 1;
JsepIceCandidate hostname_candidate_audio("audio", audio_index, c);
JsepIceCandidate hostname_candidate_video("video", video_index, c);
EXPECT_TRUE(jsep_desc_->AddCandidate(&hostname_candidate_audio));
EXPECT_TRUE(jsep_desc_->AddCandidate(&hostname_candidate_video));
std::string sdp = Serialize(jsep_desc_.get());
auto parsed_jsep_desc = DeSerialize(sdp);
EXPECT_EQ(2u, parsed_jsep_desc->number_of_mediasections());
ASSERT_NE(nullptr, parsed_jsep_desc->description());
ASSERT_EQ(2u, parsed_jsep_desc->description()->contents().size());
const auto& audio_content =
parsed_jsep_desc->description()->contents()[audio_index];
const auto& video_content =
parsed_jsep_desc->description()->contents()[video_index];
EXPECT_EQ("0.0.0.0:9",
audio_content.media_description()->connection_address().ToString());
EXPECT_EQ("0.0.0.0:9",
video_content.media_description()->connection_address().ToString());
}
// Tests that we can serialize and deserialize a JsepSesssionDescription
// with candidates.
TEST_F(JsepSessionDescriptionTest, SerializeDeserializeWithCandidates) {
std::string sdp = Serialize(jsep_desc_.get());
// Add a candidate and check that the serialized result is different.
JsepIceCandidate jsep_candidate("audio", 0, candidate_);
EXPECT_TRUE(jsep_desc_->AddCandidate(&jsep_candidate));
std::string sdp_with_candidate = Serialize(jsep_desc_.get());
EXPECT_NE(sdp, sdp_with_candidate);
auto parsed_jsep_desc = DeSerialize(sdp_with_candidate);
std::string parsed_sdp_with_candidate = Serialize(parsed_jsep_desc.get());
EXPECT_EQ(sdp_with_candidate, parsed_sdp_with_candidate);
}
// TODO(zhihuang): Modify these tests. These are used to verify that after
// adding the candidates, the connection_address field is set correctly. Modify
// those so that the "connection address" is tested directly.
// Tests serialization of SDP with only IPv6 candidates and verifies that IPv6
// is used as default address in c line according to preference.
TEST_F(JsepSessionDescriptionTest, SerializeSessionDescriptionWithIPv6Only) {
// Stun has a high preference than local host.
cricket::Candidate candidate1(
cricket::ICE_CANDIDATE_COMPONENT_RTP, "udp",
rtc::SocketAddress("::1", 1234), kCandidatePriority, "", "",
cricket::STUN_PORT_TYPE, kCandidateGeneration, kCandidateFoundation);
cricket::Candidate candidate2(
cricket::ICE_CANDIDATE_COMPONENT_RTP, "udp",
rtc::SocketAddress("::2", 1235), kCandidatePriority, "", "",
cricket::LOCAL_PORT_TYPE, kCandidateGeneration, kCandidateFoundation);
JsepIceCandidate jice1("audio", 0, candidate1);
JsepIceCandidate jice2("audio", 0, candidate2);
JsepIceCandidate jice3("video", 0, candidate1);
JsepIceCandidate jice4("video", 0, candidate2);
ASSERT_TRUE(jsep_desc_->AddCandidate(&jice1));
ASSERT_TRUE(jsep_desc_->AddCandidate(&jice2));
ASSERT_TRUE(jsep_desc_->AddCandidate(&jice3));
ASSERT_TRUE(jsep_desc_->AddCandidate(&jice4));
std::string message = Serialize(jsep_desc_.get());
// Should have a c line like this one.
EXPECT_NE(message.find("c=IN IP6 ::1"), std::string::npos);
// Shouldn't have a IP4 c line.
EXPECT_EQ(message.find("c=IN IP4"), std::string::npos);
}
// Tests serialization of SDP with both IPv4 and IPv6 candidates and
// verifies that IPv4 is used as default address in c line even if the
// preference of IPv4 is lower.
TEST_F(JsepSessionDescriptionTest,
SerializeSessionDescriptionWithBothIPFamilies) {
cricket::Candidate candidate_v4(
cricket::ICE_CANDIDATE_COMPONENT_RTP, "udp",
rtc::SocketAddress("192.168.1.5", 1234), kCandidatePriority, "", "",
cricket::STUN_PORT_TYPE, kCandidateGeneration, kCandidateFoundation);
cricket::Candidate candidate_v6(
cricket::ICE_CANDIDATE_COMPONENT_RTP, "udp",
rtc::SocketAddress("::1", 1234), kCandidatePriority, "", "",
cricket::LOCAL_PORT_TYPE, kCandidateGeneration, kCandidateFoundation);
JsepIceCandidate jice_v4("audio", 0, candidate_v4);
JsepIceCandidate jice_v6("audio", 0, candidate_v6);
JsepIceCandidate jice_v4_video("video", 0, candidate_v4);
JsepIceCandidate jice_v6_video("video", 0, candidate_v6);
ASSERT_TRUE(jsep_desc_->AddCandidate(&jice_v4));
ASSERT_TRUE(jsep_desc_->AddCandidate(&jice_v6));
ASSERT_TRUE(jsep_desc_->AddCandidate(&jice_v4_video));
ASSERT_TRUE(jsep_desc_->AddCandidate(&jice_v6_video));
std::string message = Serialize(jsep_desc_.get());
// Should have a c line like this one.
EXPECT_NE(message.find("c=IN IP4 192.168.1.5"), std::string::npos);
// Shouldn't have a IP6 c line.
EXPECT_EQ(message.find("c=IN IP6"), std::string::npos);
}
// Tests serialization of SDP with both UDP and TCP candidates and
// verifies that UDP is used as default address in c line even if the
// preference of UDP is lower.
TEST_F(JsepSessionDescriptionTest,
SerializeSessionDescriptionWithBothProtocols) {
// Stun has a high preference than local host.
cricket::Candidate candidate1(
cricket::ICE_CANDIDATE_COMPONENT_RTP, "tcp",
rtc::SocketAddress("::1", 1234), kCandidatePriority, "", "",
cricket::STUN_PORT_TYPE, kCandidateGeneration, kCandidateFoundation);
cricket::Candidate candidate2(
cricket::ICE_CANDIDATE_COMPONENT_RTP, "udp",
rtc::SocketAddress("fe80::1234:5678:abcd:ef12", 1235), kCandidatePriority,
"", "", cricket::LOCAL_PORT_TYPE, kCandidateGeneration,
kCandidateFoundation);
JsepIceCandidate jice1("audio", 0, candidate1);
JsepIceCandidate jice2("audio", 0, candidate2);
JsepIceCandidate jice3("video", 0, candidate1);
JsepIceCandidate jice4("video", 0, candidate2);
ASSERT_TRUE(jsep_desc_->AddCandidate(&jice1));
ASSERT_TRUE(jsep_desc_->AddCandidate(&jice2));
ASSERT_TRUE(jsep_desc_->AddCandidate(&jice3));
ASSERT_TRUE(jsep_desc_->AddCandidate(&jice4));
std::string message = Serialize(jsep_desc_.get());
// Should have a c line like this one.
EXPECT_NE(message.find("c=IN IP6 fe80::1234:5678:abcd:ef12"),
std::string::npos);
// Shouldn't have a IP4 c line.
EXPECT_EQ(message.find("c=IN IP4"), std::string::npos);
}
// Tests serialization of SDP with only TCP candidates and verifies that
// null IPv4 is used as default address in c line.
TEST_F(JsepSessionDescriptionTest, SerializeSessionDescriptionWithTCPOnly) {
// Stun has a high preference than local host.
cricket::Candidate candidate1(
cricket::ICE_CANDIDATE_COMPONENT_RTP, "tcp",
rtc::SocketAddress("::1", 1234), kCandidatePriority, "", "",
cricket::STUN_PORT_TYPE, kCandidateGeneration, kCandidateFoundation);
cricket::Candidate candidate2(
cricket::ICE_CANDIDATE_COMPONENT_RTP, "tcp",
rtc::SocketAddress("::2", 1235), kCandidatePriority, "", "",
cricket::LOCAL_PORT_TYPE, kCandidateGeneration, kCandidateFoundation);
JsepIceCandidate jice1("audio", 0, candidate1);
JsepIceCandidate jice2("audio", 0, candidate2);
JsepIceCandidate jice3("video", 0, candidate1);
JsepIceCandidate jice4("video", 0, candidate2);
ASSERT_TRUE(jsep_desc_->AddCandidate(&jice1));
ASSERT_TRUE(jsep_desc_->AddCandidate(&jice2));
ASSERT_TRUE(jsep_desc_->AddCandidate(&jice3));
ASSERT_TRUE(jsep_desc_->AddCandidate(&jice4));
std::string message = Serialize(jsep_desc_.get());
EXPECT_EQ(message.find("c=IN IP6 ::3"), std::string::npos);
// Should have a c line like this one when no any default exists.
EXPECT_NE(message.find("c=IN IP4 0.0.0.0"), std::string::npos);
}
// Tests that the connection address will be correctly set when the Candidate is
// removed.
TEST_F(JsepSessionDescriptionTest, RemoveCandidateAndSetConnectionAddress) {
cricket::Candidate candidate1(
cricket::ICE_CANDIDATE_COMPONENT_RTP, "udp",
rtc::SocketAddress("::1", 1234), kCandidatePriority, "", "",
cricket::LOCAL_PORT_TYPE, kCandidateGeneration, kCandidateFoundation);
candidate1.set_transport_name("audio");
cricket::Candidate candidate2(
cricket::ICE_CANDIDATE_COMPONENT_RTP, "tcp",
rtc::SocketAddress("::2", 1235), kCandidatePriority, "", "",
cricket::LOCAL_PORT_TYPE, kCandidateGeneration, kCandidateFoundation);
candidate2.set_transport_name("audio");
cricket::Candidate candidate3(
cricket::ICE_CANDIDATE_COMPONENT_RTP, "udp",
rtc::SocketAddress("192.168.1.1", 1236), kCandidatePriority, "", "",
cricket::LOCAL_PORT_TYPE, kCandidateGeneration, kCandidateFoundation);
candidate3.set_transport_name("audio");
JsepIceCandidate jice1("audio", 0, candidate1);
JsepIceCandidate jice2("audio", 0, candidate2);
JsepIceCandidate jice3("audio", 0, candidate3);
size_t audio_index = 0;
auto media_desc =
jsep_desc_->description()->contents()[audio_index].media_description();
ASSERT_TRUE(jsep_desc_->AddCandidate(&jice1));
ASSERT_TRUE(jsep_desc_->AddCandidate(&jice2));
ASSERT_TRUE(jsep_desc_->AddCandidate(&jice3));
std::vector<cricket::Candidate> candidates;
EXPECT_EQ("192.168.1.1:1236", media_desc->connection_address().ToString());
candidates.push_back(candidate3);
ASSERT_TRUE(jsep_desc_->RemoveCandidates(candidates));
EXPECT_EQ("[::1]:1234", media_desc->connection_address().ToString());
candidates.clear();
candidates.push_back(candidate2);
ASSERT_TRUE(jsep_desc_->RemoveCandidates(candidates));
EXPECT_EQ("[::1]:1234", media_desc->connection_address().ToString());
candidates.clear();
candidates.push_back(candidate1);
ASSERT_TRUE(jsep_desc_->RemoveCandidates(candidates));
EXPECT_EQ("0.0.0.0:9", media_desc->connection_address().ToString());
}
class EnumerateAllSdpTypesTest : public ::testing::Test,
public ::testing::WithParamInterface<SdpType> {
};
TEST_P(EnumerateAllSdpTypesTest, TestIdentity) {
SdpType type = GetParam();
const char* str = webrtc::SdpTypeToString(type);
EXPECT_EQ(type, webrtc::SdpTypeFromString(str));
}
INSTANTIATE_TEST_SUITE_P(JsepSessionDescriptionTest,
EnumerateAllSdpTypesTest,
Values(SdpType::kOffer,
SdpType::kPrAnswer,
SdpType::kAnswer));

View file

@ -0,0 +1,642 @@
/*
* Copyright 2018 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/jsep_transport.h"
#include <stddef.h>
#include <stdint.h>
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include "api/array_view.h"
#include "api/candidate.h"
#include "p2p/base/p2p_constants.h"
#include "p2p/base/p2p_transport_channel.h"
#include "rtc_base/checks.h"
#include "rtc_base/copy_on_write_buffer.h"
#include "rtc_base/logging.h"
#include "rtc_base/strings/string_builder.h"
#include "rtc_base/trace_event.h"
using webrtc::SdpType;
namespace cricket {
JsepTransportDescription::JsepTransportDescription() {}
JsepTransportDescription::JsepTransportDescription(
bool rtcp_mux_enabled,
const std::vector<int>& encrypted_header_extension_ids,
int rtp_abs_sendtime_extn_id,
const TransportDescription& transport_desc)
: rtcp_mux_enabled(rtcp_mux_enabled),
encrypted_header_extension_ids(encrypted_header_extension_ids),
rtp_abs_sendtime_extn_id(rtp_abs_sendtime_extn_id),
transport_desc(transport_desc) {}
JsepTransportDescription::JsepTransportDescription(
const JsepTransportDescription& from)
: rtcp_mux_enabled(from.rtcp_mux_enabled),
encrypted_header_extension_ids(from.encrypted_header_extension_ids),
rtp_abs_sendtime_extn_id(from.rtp_abs_sendtime_extn_id),
transport_desc(from.transport_desc) {}
JsepTransportDescription::~JsepTransportDescription() = default;
JsepTransportDescription& JsepTransportDescription::operator=(
const JsepTransportDescription& from) {
if (this == &from) {
return *this;
}
rtcp_mux_enabled = from.rtcp_mux_enabled;
encrypted_header_extension_ids = from.encrypted_header_extension_ids;
rtp_abs_sendtime_extn_id = from.rtp_abs_sendtime_extn_id;
transport_desc = from.transport_desc;
return *this;
}
JsepTransport::JsepTransport(
const std::string& mid,
const rtc::scoped_refptr<rtc::RTCCertificate>& local_certificate,
rtc::scoped_refptr<webrtc::IceTransportInterface> ice_transport,
rtc::scoped_refptr<webrtc::IceTransportInterface> rtcp_ice_transport,
std::unique_ptr<webrtc::RtpTransport> unencrypted_rtp_transport,
std::unique_ptr<webrtc::SrtpTransport> sdes_transport,
std::unique_ptr<webrtc::DtlsSrtpTransport> dtls_srtp_transport,
std::unique_ptr<DtlsTransportInternal> rtp_dtls_transport,
std::unique_ptr<DtlsTransportInternal> rtcp_dtls_transport,
std::unique_ptr<SctpTransportInternal> sctp_transport,
std::function<void()> rtcp_mux_active_callback)
: network_thread_(rtc::Thread::Current()),
mid_(mid),
local_certificate_(local_certificate),
ice_transport_(std::move(ice_transport)),
rtcp_ice_transport_(std::move(rtcp_ice_transport)),
unencrypted_rtp_transport_(std::move(unencrypted_rtp_transport)),
sdes_transport_(std::move(sdes_transport)),
dtls_srtp_transport_(std::move(dtls_srtp_transport)),
rtp_dtls_transport_(rtp_dtls_transport
? rtc::make_ref_counted<webrtc::DtlsTransport>(
std::move(rtp_dtls_transport))
: nullptr),
rtcp_dtls_transport_(rtcp_dtls_transport
? rtc::make_ref_counted<webrtc::DtlsTransport>(
std::move(rtcp_dtls_transport))
: nullptr),
sctp_transport_(sctp_transport
? rtc::make_ref_counted<webrtc::SctpTransport>(
std::move(sctp_transport))
: nullptr),
rtcp_mux_active_callback_(std::move(rtcp_mux_active_callback)) {
TRACE_EVENT0("webrtc", "JsepTransport::JsepTransport");
RTC_DCHECK(ice_transport_);
RTC_DCHECK(rtp_dtls_transport_);
// `rtcp_ice_transport_` must be present iff `rtcp_dtls_transport_` is
// present.
RTC_DCHECK_EQ((rtcp_ice_transport_ != nullptr),
(rtcp_dtls_transport_ != nullptr));
// Verify the "only one out of these three can be set" invariant.
if (unencrypted_rtp_transport_) {
RTC_DCHECK(!sdes_transport);
RTC_DCHECK(!dtls_srtp_transport);
} else if (sdes_transport_) {
RTC_DCHECK(!unencrypted_rtp_transport);
RTC_DCHECK(!dtls_srtp_transport);
} else {
RTC_DCHECK(dtls_srtp_transport_);
RTC_DCHECK(!unencrypted_rtp_transport);
RTC_DCHECK(!sdes_transport);
}
if (sctp_transport_) {
sctp_transport_->SetDtlsTransport(rtp_dtls_transport_);
}
}
JsepTransport::~JsepTransport() {
TRACE_EVENT0("webrtc", "JsepTransport::~JsepTransport");
if (sctp_transport_) {
sctp_transport_->Clear();
}
// Clear all DtlsTransports. There may be pointers to these from
// other places, so we can't assume they'll be deleted by the destructor.
rtp_dtls_transport_->Clear();
if (rtcp_dtls_transport_) {
rtcp_dtls_transport_->Clear();
}
// ICE will be the last transport to be deleted.
}
webrtc::RTCError JsepTransport::SetLocalJsepTransportDescription(
const JsepTransportDescription& jsep_description,
SdpType type) {
webrtc::RTCError error;
TRACE_EVENT0("webrtc", "JsepTransport::SetLocalJsepTransportDescription");
RTC_DCHECK_RUN_ON(network_thread_);
IceParameters ice_parameters =
jsep_description.transport_desc.GetIceParameters();
webrtc::RTCError ice_parameters_result = ice_parameters.Validate();
if (!ice_parameters_result.ok()) {
rtc::StringBuilder sb;
sb << "Invalid ICE parameters: " << ice_parameters_result.message();
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
sb.Release());
}
if (!SetRtcpMux(jsep_description.rtcp_mux_enabled, type,
ContentSource::CS_LOCAL)) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Failed to setup RTCP mux.");
}
if (dtls_srtp_transport_) {
RTC_DCHECK(!unencrypted_rtp_transport_);
RTC_DCHECK(!sdes_transport_);
dtls_srtp_transport_->UpdateRecvEncryptedHeaderExtensionIds(
jsep_description.encrypted_header_extension_ids);
}
bool ice_restarting =
local_description_ != nullptr &&
IceCredentialsChanged(local_description_->transport_desc.ice_ufrag,
local_description_->transport_desc.ice_pwd,
ice_parameters.ufrag, ice_parameters.pwd);
local_description_.reset(new JsepTransportDescription(jsep_description));
rtc::SSLFingerprint* local_fp =
local_description_->transport_desc.identity_fingerprint.get();
if (!local_fp) {
local_certificate_ = nullptr;
} else {
error = VerifyCertificateFingerprint(local_certificate_.get(), local_fp);
if (!error.ok()) {
local_description_.reset();
return error;
}
}
RTC_DCHECK(rtp_dtls_transport_->internal());
rtp_dtls_transport_->internal()->ice_transport()->SetIceParameters(
ice_parameters);
if (rtcp_dtls_transport_) {
RTC_DCHECK(rtcp_dtls_transport_->internal());
rtcp_dtls_transport_->internal()->ice_transport()->SetIceParameters(
ice_parameters);
}
// If PRANSWER/ANSWER is set, we should decide transport protocol type.
if (type == SdpType::kPrAnswer || type == SdpType::kAnswer) {
error = NegotiateAndSetDtlsParameters(type);
}
if (!error.ok()) {
local_description_.reset();
return error;
}
if (needs_ice_restart_ && ice_restarting) {
needs_ice_restart_ = false;
RTC_LOG(LS_VERBOSE) << "needs-ice-restart flag cleared for transport "
<< mid();
}
return webrtc::RTCError::OK();
}
webrtc::RTCError JsepTransport::SetRemoteJsepTransportDescription(
const JsepTransportDescription& jsep_description,
webrtc::SdpType type) {
TRACE_EVENT0("webrtc", "JsepTransport::SetLocalJsepTransportDescription");
webrtc::RTCError error;
RTC_DCHECK_RUN_ON(network_thread_);
IceParameters ice_parameters =
jsep_description.transport_desc.GetIceParameters();
webrtc::RTCError ice_parameters_result = ice_parameters.Validate();
if (!ice_parameters_result.ok()) {
remote_description_.reset();
rtc::StringBuilder sb;
sb << "Invalid ICE parameters: " << ice_parameters_result.message();
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
sb.Release());
}
if (!SetRtcpMux(jsep_description.rtcp_mux_enabled, type,
ContentSource::CS_REMOTE)) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Failed to setup RTCP mux.");
}
if (dtls_srtp_transport_) {
RTC_DCHECK(!unencrypted_rtp_transport_);
RTC_DCHECK(!sdes_transport_);
dtls_srtp_transport_->UpdateSendEncryptedHeaderExtensionIds(
jsep_description.encrypted_header_extension_ids);
dtls_srtp_transport_->CacheRtpAbsSendTimeHeaderExtension(
jsep_description.rtp_abs_sendtime_extn_id);
}
remote_description_.reset(new JsepTransportDescription(jsep_description));
RTC_DCHECK(rtp_dtls_transport());
SetRemoteIceParameters(ice_parameters, rtp_dtls_transport()->ice_transport());
if (rtcp_dtls_transport()) {
SetRemoteIceParameters(ice_parameters,
rtcp_dtls_transport()->ice_transport());
}
// If PRANSWER/ANSWER is set, we should decide transport protocol type.
if (type == SdpType::kPrAnswer || type == SdpType::kAnswer) {
error = NegotiateAndSetDtlsParameters(SdpType::kOffer);
}
if (!error.ok()) {
remote_description_.reset();
return error;
}
return webrtc::RTCError::OK();
}
webrtc::RTCError JsepTransport::AddRemoteCandidates(
const Candidates& candidates) {
RTC_DCHECK_RUN_ON(network_thread_);
if (!local_description_ || !remote_description_) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_STATE,
mid() +
" is not ready to use the remote candidate "
"because the local or remote description is "
"not set.");
}
for (const cricket::Candidate& candidate : candidates) {
auto transport =
candidate.component() == cricket::ICE_CANDIDATE_COMPONENT_RTP
? rtp_dtls_transport_
: rtcp_dtls_transport_;
if (!transport) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Candidate has an unknown component: " +
candidate.ToSensitiveString() + " for mid " +
mid());
}
RTC_DCHECK(transport->internal() && transport->internal()->ice_transport());
transport->internal()->ice_transport()->AddRemoteCandidate(candidate);
}
return webrtc::RTCError::OK();
}
void JsepTransport::SetNeedsIceRestartFlag() {
RTC_DCHECK_RUN_ON(network_thread_);
if (!needs_ice_restart_) {
needs_ice_restart_ = true;
RTC_LOG(LS_VERBOSE) << "needs-ice-restart flag set for transport " << mid();
}
}
absl::optional<rtc::SSLRole> JsepTransport::GetDtlsRole() const {
RTC_DCHECK_RUN_ON(network_thread_);
RTC_DCHECK(rtp_dtls_transport_);
RTC_DCHECK(rtp_dtls_transport_->internal());
rtc::SSLRole dtls_role;
if (!rtp_dtls_transport_->internal()->GetDtlsRole(&dtls_role)) {
return absl::optional<rtc::SSLRole>();
}
return absl::optional<rtc::SSLRole>(dtls_role);
}
bool JsepTransport::GetStats(TransportStats* stats) {
TRACE_EVENT0("webrtc", "JsepTransport::GetStats");
RTC_DCHECK_RUN_ON(network_thread_);
stats->transport_name = mid();
stats->channel_stats.clear();
RTC_DCHECK(rtp_dtls_transport_->internal());
bool ret = GetTransportStats(rtp_dtls_transport_->internal(),
ICE_CANDIDATE_COMPONENT_RTP, stats);
if (rtcp_dtls_transport_) {
RTC_DCHECK(rtcp_dtls_transport_->internal());
ret &= GetTransportStats(rtcp_dtls_transport_->internal(),
ICE_CANDIDATE_COMPONENT_RTCP, stats);
}
return ret;
}
webrtc::RTCError JsepTransport::VerifyCertificateFingerprint(
const rtc::RTCCertificate* certificate,
const rtc::SSLFingerprint* fingerprint) const {
TRACE_EVENT0("webrtc", "JsepTransport::VerifyCertificateFingerprint");
RTC_DCHECK_RUN_ON(network_thread_);
if (!fingerprint) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"No fingerprint");
}
if (!certificate) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
"Fingerprint provided but no identity available.");
}
std::unique_ptr<rtc::SSLFingerprint> fp_tmp =
rtc::SSLFingerprint::CreateUnique(fingerprint->algorithm,
*certificate->identity());
RTC_DCHECK(fp_tmp.get() != NULL);
if (*fp_tmp == *fingerprint) {
return webrtc::RTCError::OK();
}
char ss_buf[1024];
rtc::SimpleStringBuilder desc(ss_buf);
desc << "Local fingerprint does not match identity. Expected: ";
desc << fp_tmp->ToString();
desc << " Got: " << fingerprint->ToString();
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER,
std::string(desc.str()));
}
void JsepTransport::SetActiveResetSrtpParams(bool active_reset_srtp_params) {
RTC_DCHECK_RUN_ON(network_thread_);
if (dtls_srtp_transport_) {
RTC_LOG(LS_INFO)
<< "Setting active_reset_srtp_params of DtlsSrtpTransport to: "
<< active_reset_srtp_params;
dtls_srtp_transport_->SetActiveResetSrtpParams(active_reset_srtp_params);
}
}
void JsepTransport::SetRemoteIceParameters(
const IceParameters& ice_parameters,
IceTransportInternal* ice_transport) {
TRACE_EVENT0("webrtc", "JsepTransport::SetRemoteIceParameters");
RTC_DCHECK_RUN_ON(network_thread_);
RTC_DCHECK(ice_transport);
RTC_DCHECK(remote_description_);
ice_transport->SetRemoteIceParameters(ice_parameters);
ice_transport->SetRemoteIceMode(remote_description_->transport_desc.ice_mode);
}
webrtc::RTCError JsepTransport::SetNegotiatedDtlsParameters(
DtlsTransportInternal* dtls_transport,
absl::optional<rtc::SSLRole> dtls_role,
rtc::SSLFingerprint* remote_fingerprint) {
RTC_DCHECK(dtls_transport);
return dtls_transport->SetRemoteParameters(
remote_fingerprint->algorithm, remote_fingerprint->digest.cdata(),
remote_fingerprint->digest.size(), dtls_role);
}
bool JsepTransport::SetRtcpMux(bool enable,
webrtc::SdpType type,
ContentSource source) {
RTC_DCHECK_RUN_ON(network_thread_);
bool ret = false;
switch (type) {
case SdpType::kOffer:
ret = rtcp_mux_negotiator_.SetOffer(enable, source);
break;
case SdpType::kPrAnswer:
// This may activate RTCP muxing, but we don't yet destroy the transport
// because the final answer may deactivate it.
ret = rtcp_mux_negotiator_.SetProvisionalAnswer(enable, source);
break;
case SdpType::kAnswer:
ret = rtcp_mux_negotiator_.SetAnswer(enable, source);
if (ret && rtcp_mux_negotiator_.IsActive()) {
ActivateRtcpMux();
}
break;
default:
RTC_DCHECK_NOTREACHED();
}
if (!ret) {
return false;
}
auto transport = rtp_transport();
transport->SetRtcpMuxEnabled(rtcp_mux_negotiator_.IsActive());
return ret;
}
void JsepTransport::ActivateRtcpMux() {
if (unencrypted_rtp_transport_) {
RTC_DCHECK(!sdes_transport_);
RTC_DCHECK(!dtls_srtp_transport_);
unencrypted_rtp_transport_->SetRtcpPacketTransport(nullptr);
} else if (sdes_transport_) {
RTC_DCHECK(!unencrypted_rtp_transport_);
RTC_DCHECK(!dtls_srtp_transport_);
sdes_transport_->SetRtcpPacketTransport(nullptr);
} else if (dtls_srtp_transport_) {
RTC_DCHECK(dtls_srtp_transport_);
RTC_DCHECK(!unencrypted_rtp_transport_);
RTC_DCHECK(!sdes_transport_);
dtls_srtp_transport_->SetDtlsTransports(rtp_dtls_transport(),
/*rtcp_dtls_transport=*/nullptr);
}
rtcp_dtls_transport_ = nullptr; // Destroy this reference.
// Notify the JsepTransportController to update the aggregate states.
rtcp_mux_active_callback_();
}
webrtc::RTCError JsepTransport::NegotiateAndSetDtlsParameters(
SdpType local_description_type) {
RTC_DCHECK_RUN_ON(network_thread_);
if (!local_description_ || !remote_description_) {
return webrtc::RTCError(webrtc::RTCErrorType::INVALID_STATE,
"Applying an answer transport description "
"without applying any offer.");
}
std::unique_ptr<rtc::SSLFingerprint> remote_fingerprint;
absl::optional<rtc::SSLRole> negotiated_dtls_role;
rtc::SSLFingerprint* local_fp =
local_description_->transport_desc.identity_fingerprint.get();
rtc::SSLFingerprint* remote_fp =
remote_description_->transport_desc.identity_fingerprint.get();
if (remote_fp && local_fp) {
remote_fingerprint = std::make_unique<rtc::SSLFingerprint>(*remote_fp);
webrtc::RTCError error =
NegotiateDtlsRole(local_description_type,
local_description_->transport_desc.connection_role,
remote_description_->transport_desc.connection_role,
&negotiated_dtls_role);
if (!error.ok()) {
return error;
}
} else if (local_fp && (local_description_type == SdpType::kAnswer)) {
return webrtc::RTCError(
webrtc::RTCErrorType::INVALID_PARAMETER,
"Local fingerprint supplied when caller didn't offer DTLS.");
} else {
// We are not doing DTLS
remote_fingerprint = std::make_unique<rtc::SSLFingerprint>(
"", rtc::ArrayView<const uint8_t>());
}
// Now that we have negotiated everything, push it downward.
// Note that we cache the result so that if we have race conditions
// between future SetRemote/SetLocal invocations and new transport
// creation, we have the negotiation state saved until a new
// negotiation happens.
RTC_DCHECK(rtp_dtls_transport());
webrtc::RTCError error = SetNegotiatedDtlsParameters(
rtp_dtls_transport(), negotiated_dtls_role, remote_fingerprint.get());
if (!error.ok()) {
return error;
}
if (rtcp_dtls_transport()) {
error = SetNegotiatedDtlsParameters(
rtcp_dtls_transport(), negotiated_dtls_role, remote_fingerprint.get());
}
return error;
}
webrtc::RTCError JsepTransport::NegotiateDtlsRole(
SdpType local_description_type,
ConnectionRole local_connection_role,
ConnectionRole remote_connection_role,
absl::optional<rtc::SSLRole>* negotiated_dtls_role) {
// From RFC 4145, section-4.1, The following are the values that the
// 'setup' attribute can take in an offer/answer exchange:
// Offer Answer
// ________________
// active passive / holdconn
// passive active / holdconn
// actpass active / passive / holdconn
// holdconn holdconn
//
// Set the role that is most conformant with RFC 5763, Section 5, bullet 1
// The endpoint MUST use the setup attribute defined in [RFC4145].
// The endpoint that is the offerer MUST use the setup attribute
// value of setup:actpass and be prepared to receive a client_hello
// before it receives the answer. The answerer MUST use either a
// setup attribute value of setup:active or setup:passive. Note that
// if the answerer uses setup:passive, then the DTLS handshake will
// not begin until the answerer is received, which adds additional
// latency. setup:active allows the answer and the DTLS handshake to
// occur in parallel. Thus, setup:active is RECOMMENDED. Whichever
// party is active MUST initiate a DTLS handshake by sending a
// ClientHello over each flow (host/port quartet).
// IOW - actpass and passive modes should be treated as server and
// active as client.
// RFC 8842 section 5.3 updates this text, so that it is mandated
// for the responder to handle offers with "active" and "passive"
// as well as "actpass"
bool is_remote_server = false;
if (local_description_type == SdpType::kOffer) {
if (local_connection_role != CONNECTIONROLE_ACTPASS) {
return webrtc::RTCError(
webrtc::RTCErrorType::INVALID_PARAMETER,
"Offerer must use actpass value for setup attribute.");
}
if (remote_connection_role == CONNECTIONROLE_ACTIVE ||
remote_connection_role == CONNECTIONROLE_PASSIVE ||
remote_connection_role == CONNECTIONROLE_NONE) {
is_remote_server = (remote_connection_role == CONNECTIONROLE_PASSIVE);
} else {
return webrtc::RTCError(
webrtc::RTCErrorType::INVALID_PARAMETER,
"Answerer must use either active or passive value "
"for setup attribute.");
}
// If remote is NONE or ACTIVE it will act as client.
} else {
if (remote_connection_role != CONNECTIONROLE_ACTPASS &&
remote_connection_role != CONNECTIONROLE_NONE) {
// Accept a remote role attribute that's not "actpass", but matches the
// current negotiated role. This is allowed by dtls-sdp, though our
// implementation will never generate such an offer as it's not
// recommended.
//
// See https://datatracker.ietf.org/doc/html/draft-ietf-mmusic-dtls-sdp,
// section 5.5.
auto current_dtls_role = GetDtlsRole();
if (!current_dtls_role) {
// Role not assigned yet. Verify that local role fits with remote role.
switch (remote_connection_role) {
case CONNECTIONROLE_ACTIVE:
if (local_connection_role != CONNECTIONROLE_PASSIVE) {
return webrtc::RTCError(
webrtc::RTCErrorType::INVALID_PARAMETER,
"Answerer must be passive when offerer is active");
}
break;
case CONNECTIONROLE_PASSIVE:
if (local_connection_role != CONNECTIONROLE_ACTIVE) {
return webrtc::RTCError(
webrtc::RTCErrorType::INVALID_PARAMETER,
"Answerer must be active when offerer is passive");
}
break;
default:
RTC_DCHECK_NOTREACHED();
break;
}
} else {
if ((*current_dtls_role == rtc::SSL_CLIENT &&
remote_connection_role == CONNECTIONROLE_ACTIVE) ||
(*current_dtls_role == rtc::SSL_SERVER &&
remote_connection_role == CONNECTIONROLE_PASSIVE)) {
return webrtc::RTCError(
webrtc::RTCErrorType::INVALID_PARAMETER,
"Offerer must use current negotiated role for "
"setup attribute.");
}
}
}
if (local_connection_role == CONNECTIONROLE_ACTIVE ||
local_connection_role == CONNECTIONROLE_PASSIVE) {
is_remote_server = (local_connection_role == CONNECTIONROLE_ACTIVE);
} else {
return webrtc::RTCError(
webrtc::RTCErrorType::INVALID_PARAMETER,
"Answerer must use either active or passive value "
"for setup attribute.");
}
// If local is passive, local will act as server.
}
*negotiated_dtls_role =
(is_remote_server ? rtc::SSL_CLIENT : rtc::SSL_SERVER);
return webrtc::RTCError::OK();
}
bool JsepTransport::GetTransportStats(DtlsTransportInternal* dtls_transport,
int component,
TransportStats* stats) {
RTC_DCHECK_RUN_ON(network_thread_);
RTC_DCHECK(dtls_transport);
TransportChannelStats substats;
substats.component = component;
dtls_transport->GetSslVersionBytes(&substats.ssl_version_bytes);
dtls_transport->GetSrtpCryptoSuite(&substats.srtp_crypto_suite);
dtls_transport->GetSslCipherSuite(&substats.ssl_cipher_suite);
substats.dtls_state = dtls_transport->dtls_state();
rtc::SSLRole dtls_role;
if (dtls_transport->GetDtlsRole(&dtls_role)) {
substats.dtls_role = dtls_role;
}
if (!dtls_transport->ice_transport()->GetStats(
&substats.ice_transport_stats)) {
return false;
}
substats.ssl_peer_signature_algorithm =
dtls_transport->GetSslPeerSignatureAlgorithm();
stats->channel_stats.push_back(substats);
return true;
}
} // namespace cricket

View file

@ -0,0 +1,320 @@
/*
* Copyright 2018 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef PC_JSEP_TRANSPORT_H_
#define PC_JSEP_TRANSPORT_H_
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "absl/types/optional.h"
#include "api/candidate.h"
#include "api/ice_transport_interface.h"
#include "api/jsep.h"
#include "api/rtc_error.h"
#include "api/scoped_refptr.h"
#include "api/sequence_checker.h"
#include "api/transport/data_channel_transport_interface.h"
#include "media/sctp/sctp_transport_internal.h"
#include "p2p/base/dtls_transport.h"
#include "p2p/base/dtls_transport_internal.h"
#include "p2p/base/ice_transport_internal.h"
#include "p2p/base/p2p_constants.h"
#include "p2p/base/transport_description.h"
#include "p2p/base/transport_info.h"
#include "pc/dtls_srtp_transport.h"
#include "pc/dtls_transport.h"
#include "pc/rtcp_mux_filter.h"
#include "pc/rtp_transport.h"
#include "pc/rtp_transport_internal.h"
#include "pc/sctp_transport.h"
#include "pc/session_description.h"
#include "pc/srtp_transport.h"
#include "pc/transport_stats.h"
#include "rtc_base/checks.h"
#include "rtc_base/rtc_certificate.h"
#include "rtc_base/ssl_fingerprint.h"
#include "rtc_base/ssl_stream_adapter.h"
#include "rtc_base/thread.h"
#include "rtc_base/thread_annotations.h"
namespace cricket {
class DtlsTransportInternal;
struct JsepTransportDescription {
public:
JsepTransportDescription();
JsepTransportDescription(
bool rtcp_mux_enabled,
const std::vector<int>& encrypted_header_extension_ids,
int rtp_abs_sendtime_extn_id,
const TransportDescription& transport_description);
JsepTransportDescription(const JsepTransportDescription& from);
~JsepTransportDescription();
JsepTransportDescription& operator=(const JsepTransportDescription& from);
bool rtcp_mux_enabled = true;
std::vector<int> encrypted_header_extension_ids;
int rtp_abs_sendtime_extn_id = -1;
// TODO(zhihuang): Add the ICE and DTLS related variables and methods from
// TransportDescription and remove this extra layer of abstraction.
TransportDescription transport_desc;
};
// Helper class used by JsepTransportController that processes
// TransportDescriptions. A TransportDescription represents the
// transport-specific properties of an SDP m= section, processed according to
// JSEP. Each transport consists of DTLS and ICE transport channels for RTP
// (and possibly RTCP, if rtcp-mux isn't used).
//
// On Threading: JsepTransport performs work solely on the network thread, and
// so its methods should only be called on the network thread.
class JsepTransport {
public:
// `mid` is just used for log statements in order to identify the Transport.
// Note that `local_certificate` is allowed to be null since a remote
// description may be set before a local certificate is generated.
JsepTransport(
const std::string& mid,
const rtc::scoped_refptr<rtc::RTCCertificate>& local_certificate,
rtc::scoped_refptr<webrtc::IceTransportInterface> ice_transport,
rtc::scoped_refptr<webrtc::IceTransportInterface> rtcp_ice_transport,
std::unique_ptr<webrtc::RtpTransport> unencrypted_rtp_transport,
std::unique_ptr<webrtc::SrtpTransport> sdes_transport,
std::unique_ptr<webrtc::DtlsSrtpTransport> dtls_srtp_transport,
std::unique_ptr<DtlsTransportInternal> rtp_dtls_transport,
std::unique_ptr<DtlsTransportInternal> rtcp_dtls_transport,
std::unique_ptr<SctpTransportInternal> sctp_transport,
std::function<void()> rtcp_mux_active_callback);
~JsepTransport();
JsepTransport(const JsepTransport&) = delete;
JsepTransport& operator=(const JsepTransport&) = delete;
// Returns the MID of this transport. This is only used for logging.
const std::string& mid() const { return mid_; }
// Must be called before applying local session description.
// Needed in order to verify the local fingerprint.
void SetLocalCertificate(
const rtc::scoped_refptr<rtc::RTCCertificate>& local_certificate) {
RTC_DCHECK_RUN_ON(network_thread_);
local_certificate_ = local_certificate;
}
// Return the local certificate provided by SetLocalCertificate.
rtc::scoped_refptr<rtc::RTCCertificate> GetLocalCertificate() const {
RTC_DCHECK_RUN_ON(network_thread_);
return local_certificate_;
}
webrtc::RTCError SetLocalJsepTransportDescription(
const JsepTransportDescription& jsep_description,
webrtc::SdpType type);
// Set the remote TransportDescription to be used by DTLS and ICE channels
// that are part of this Transport.
webrtc::RTCError SetRemoteJsepTransportDescription(
const JsepTransportDescription& jsep_description,
webrtc::SdpType type);
webrtc::RTCError AddRemoteCandidates(const Candidates& candidates);
// Set the "needs-ice-restart" flag as described in JSEP. After the flag is
// set, offers should generate new ufrags/passwords until an ICE restart
// occurs.
//
// This and `needs_ice_restart()` must be called on the network thread.
void SetNeedsIceRestartFlag();
// Returns true if the ICE restart flag above was set, and no ICE restart has
// occurred yet for this transport (by applying a local description with
// changed ufrag/password).
bool needs_ice_restart() const {
RTC_DCHECK_RUN_ON(network_thread_);
return needs_ice_restart_;
}
// Returns role if negotiated, or empty absl::optional if it hasn't been
// negotiated yet.
absl::optional<rtc::SSLRole> GetDtlsRole() const;
// TODO(deadbeef): Make this const. See comment in transportcontroller.h.
bool GetStats(TransportStats* stats);
const JsepTransportDescription* local_description() const {
RTC_DCHECK_RUN_ON(network_thread_);
return local_description_.get();
}
const JsepTransportDescription* remote_description() const {
RTC_DCHECK_RUN_ON(network_thread_);
return remote_description_.get();
}
// Returns the rtp transport, if any.
webrtc::RtpTransportInternal* rtp_transport() const {
if (dtls_srtp_transport_) {
return dtls_srtp_transport_.get();
}
if (sdes_transport_) {
return sdes_transport_.get();
}
if (unencrypted_rtp_transport_) {
return unencrypted_rtp_transport_.get();
}
return nullptr;
}
const DtlsTransportInternal* rtp_dtls_transport() const {
if (rtp_dtls_transport_) {
return rtp_dtls_transport_->internal();
}
return nullptr;
}
DtlsTransportInternal* rtp_dtls_transport() {
if (rtp_dtls_transport_) {
return rtp_dtls_transport_->internal();
}
return nullptr;
}
const DtlsTransportInternal* rtcp_dtls_transport() const {
RTC_DCHECK_RUN_ON(network_thread_);
if (rtcp_dtls_transport_) {
return rtcp_dtls_transport_->internal();
}
return nullptr;
}
DtlsTransportInternal* rtcp_dtls_transport() {
RTC_DCHECK_RUN_ON(network_thread_);
if (rtcp_dtls_transport_) {
return rtcp_dtls_transport_->internal();
}
return nullptr;
}
rtc::scoped_refptr<webrtc::DtlsTransport> RtpDtlsTransport() {
return rtp_dtls_transport_;
}
rtc::scoped_refptr<webrtc::SctpTransport> SctpTransport() const {
return sctp_transport_;
}
// TODO(bugs.webrtc.org/9719): Delete method, update callers to use
// SctpTransport() instead.
webrtc::DataChannelTransportInterface* data_channel_transport() const {
return sctp_transport_.get();
}
// TODO(deadbeef): The methods below are only public for testing. Should make
// them utility functions or objects so they can be tested independently from
// this class.
// Returns an error if the certificate's identity does not match the
// fingerprint, or either is NULL.
webrtc::RTCError VerifyCertificateFingerprint(
const rtc::RTCCertificate* certificate,
const rtc::SSLFingerprint* fingerprint) const;
void SetActiveResetSrtpParams(bool active_reset_srtp_params);
private:
bool SetRtcpMux(bool enable, webrtc::SdpType type, ContentSource source);
void ActivateRtcpMux() RTC_RUN_ON(network_thread_);
// Negotiates and sets the DTLS parameters based on the current local and
// remote transport description, such as the DTLS role to use, and whether
// DTLS should be activated.
//
// Called when an answer TransportDescription is applied.
webrtc::RTCError NegotiateAndSetDtlsParameters(
webrtc::SdpType local_description_type);
// Negotiates the DTLS role based off the offer and answer as specified by
// RFC 4145, section-4.1. Returns an RTCError if role cannot be determined
// from the local description and remote description.
webrtc::RTCError NegotiateDtlsRole(
webrtc::SdpType local_description_type,
ConnectionRole local_connection_role,
ConnectionRole remote_connection_role,
absl::optional<rtc::SSLRole>* negotiated_dtls_role);
// Pushes down the ICE parameters from the remote description.
void SetRemoteIceParameters(const IceParameters& ice_parameters,
IceTransportInternal* ice);
// Pushes down the DTLS parameters obtained via negotiation.
static webrtc::RTCError SetNegotiatedDtlsParameters(
DtlsTransportInternal* dtls_transport,
absl::optional<rtc::SSLRole> dtls_role,
rtc::SSLFingerprint* remote_fingerprint);
bool GetTransportStats(DtlsTransportInternal* dtls_transport,
int component,
TransportStats* stats);
// Owning thread, for safety checks
const rtc::Thread* const network_thread_;
const std::string mid_;
// needs-ice-restart bit as described in JSEP.
bool needs_ice_restart_ RTC_GUARDED_BY(network_thread_) = false;
rtc::scoped_refptr<rtc::RTCCertificate> local_certificate_
RTC_GUARDED_BY(network_thread_);
std::unique_ptr<JsepTransportDescription> local_description_
RTC_GUARDED_BY(network_thread_);
std::unique_ptr<JsepTransportDescription> remote_description_
RTC_GUARDED_BY(network_thread_);
// Ice transport which may be used by any of upper-layer transports (below).
// Owned by JsepTransport and guaranteed to outlive the transports below.
const rtc::scoped_refptr<webrtc::IceTransportInterface> ice_transport_;
const rtc::scoped_refptr<webrtc::IceTransportInterface> rtcp_ice_transport_;
// To avoid downcasting and make it type safe, keep three unique pointers for
// different SRTP mode and only one of these is non-nullptr.
const std::unique_ptr<webrtc::RtpTransport> unencrypted_rtp_transport_;
const std::unique_ptr<webrtc::SrtpTransport> sdes_transport_;
const std::unique_ptr<webrtc::DtlsSrtpTransport> dtls_srtp_transport_;
const rtc::scoped_refptr<webrtc::DtlsTransport> rtp_dtls_transport_;
// The RTCP transport is const for all usages, except that it is cleared
// when RTCP multiplexing is turned on; this happens on the network thread.
rtc::scoped_refptr<webrtc::DtlsTransport> rtcp_dtls_transport_
RTC_GUARDED_BY(network_thread_);
const rtc::scoped_refptr<webrtc::SctpTransport> sctp_transport_;
RtcpMuxFilter rtcp_mux_negotiator_ RTC_GUARDED_BY(network_thread_);
// Cache the encrypted header extension IDs for SDES negoitation.
absl::optional<std::vector<int>> send_extension_ids_
RTC_GUARDED_BY(network_thread_);
absl::optional<std::vector<int>> recv_extension_ids_
RTC_GUARDED_BY(network_thread_);
// This is invoked when RTCP-mux becomes active and
// `rtcp_dtls_transport_` is destroyed. The JsepTransportController will
// receive the callback and update the aggregate transport states.
std::function<void()> rtcp_mux_active_callback_;
};
} // namespace cricket
#endif // PC_JSEP_TRANSPORT_H_

View file

@ -0,0 +1,390 @@
/*
* Copyright 2021 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/jsep_transport_collection.h"
#include <algorithm>
#include <map>
#include <set>
#include <type_traits>
#include <utility>
#include "p2p/base/p2p_constants.h"
#include "rtc_base/logging.h"
namespace webrtc {
void BundleManager::Update(const cricket::SessionDescription* description,
SdpType type) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
// Rollbacks should call Rollback, not Update.
RTC_DCHECK(type != SdpType::kRollback);
bool bundle_groups_changed = false;
// TODO(bugs.webrtc.org/3349): Do this for kPrAnswer as well. To make this
// work, we also need to make sure PRANSWERs don't call
// MaybeDestroyJsepTransport, because the final answer may need the destroyed
// transport if it changes the BUNDLE group.
if (bundle_policy_ == PeerConnectionInterface::kBundlePolicyMaxBundle ||
type == SdpType::kAnswer) {
// If our policy is "max-bundle" or this is an answer, update all bundle
// groups.
bundle_groups_changed = true;
bundle_groups_.clear();
for (const cricket::ContentGroup* new_bundle_group :
description->GetGroupsByName(cricket::GROUP_TYPE_BUNDLE)) {
bundle_groups_.push_back(
std::make_unique<cricket::ContentGroup>(*new_bundle_group));
RTC_DLOG(LS_VERBOSE) << "Establishing bundle group "
<< new_bundle_group->ToString();
}
} else if (type == SdpType::kOffer) {
// If this is an offer, update existing bundle groups.
// We do this because as per RFC 8843, section 7.3.2, the answerer cannot
// remove an m= section from an existing BUNDLE group without rejecting it.
// Thus any m= sections added to a BUNDLE group in this offer can
// preemptively start using the bundled transport, as there is no possible
// non-bundled fallback.
for (const cricket::ContentGroup* new_bundle_group :
description->GetGroupsByName(cricket::GROUP_TYPE_BUNDLE)) {
// Attempt to find a matching existing group.
for (const std::string& mid : new_bundle_group->content_names()) {
auto it = established_bundle_groups_by_mid_.find(mid);
if (it != established_bundle_groups_by_mid_.end()) {
*it->second = *new_bundle_group;
bundle_groups_changed = true;
RTC_DLOG(LS_VERBOSE)
<< "Establishing bundle group " << new_bundle_group->ToString();
break;
}
}
}
}
if (bundle_groups_changed) {
RefreshEstablishedBundleGroupsByMid();
}
}
const cricket::ContentGroup* BundleManager::LookupGroupByMid(
const std::string& mid) const {
auto it = established_bundle_groups_by_mid_.find(mid);
return it != established_bundle_groups_by_mid_.end() ? it->second : nullptr;
}
bool BundleManager::IsFirstMidInGroup(const std::string& mid) const {
auto group = LookupGroupByMid(mid);
if (!group) {
return true; // Unbundled MIDs are considered group leaders
}
return mid == *(group->FirstContentName());
}
cricket::ContentGroup* BundleManager::LookupGroupByMid(const std::string& mid) {
auto it = established_bundle_groups_by_mid_.find(mid);
return it != established_bundle_groups_by_mid_.end() ? it->second : nullptr;
}
void BundleManager::DeleteMid(const cricket::ContentGroup* bundle_group,
const std::string& mid) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
RTC_LOG(LS_VERBOSE) << "Deleting mid " << mid << " from bundle group "
<< bundle_group->ToString();
// Remove the rejected content from the `bundle_group`.
// The const pointer arg is used to identify the group, we verify
// it before we use it to make a modification.
auto bundle_group_it = std::find_if(
bundle_groups_.begin(), bundle_groups_.end(),
[bundle_group](std::unique_ptr<cricket::ContentGroup>& group) {
return bundle_group == group.get();
});
RTC_DCHECK(bundle_group_it != bundle_groups_.end());
(*bundle_group_it)->RemoveContentName(mid);
established_bundle_groups_by_mid_.erase(
established_bundle_groups_by_mid_.find(mid));
}
void BundleManager::DeleteGroup(const cricket::ContentGroup* bundle_group) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
RTC_DLOG(LS_VERBOSE) << "Deleting bundle group " << bundle_group->ToString();
auto bundle_group_it = std::find_if(
bundle_groups_.begin(), bundle_groups_.end(),
[bundle_group](std::unique_ptr<cricket::ContentGroup>& group) {
return bundle_group == group.get();
});
RTC_DCHECK(bundle_group_it != bundle_groups_.end());
auto mid_list = (*bundle_group_it)->content_names();
for (const auto& content_name : mid_list) {
DeleteMid(bundle_group, content_name);
}
bundle_groups_.erase(bundle_group_it);
}
void BundleManager::Rollback() {
RTC_DCHECK_RUN_ON(&sequence_checker_);
bundle_groups_.clear();
for (const auto& bundle_group : stable_bundle_groups_) {
bundle_groups_.push_back(
std::make_unique<cricket::ContentGroup>(*bundle_group));
}
RefreshEstablishedBundleGroupsByMid();
}
void BundleManager::Commit() {
RTC_DCHECK_RUN_ON(&sequence_checker_);
stable_bundle_groups_.clear();
for (const auto& bundle_group : bundle_groups_) {
stable_bundle_groups_.push_back(
std::make_unique<cricket::ContentGroup>(*bundle_group));
}
}
void BundleManager::RefreshEstablishedBundleGroupsByMid() {
established_bundle_groups_by_mid_.clear();
for (const auto& bundle_group : bundle_groups_) {
for (const std::string& content_name : bundle_group->content_names()) {
established_bundle_groups_by_mid_[content_name] = bundle_group.get();
}
}
}
void JsepTransportCollection::RegisterTransport(
const std::string& mid,
std::unique_ptr<cricket::JsepTransport> transport) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
SetTransportForMid(mid, transport.get());
jsep_transports_by_name_[mid] = std::move(transport);
RTC_DCHECK(IsConsistent());
}
std::vector<cricket::JsepTransport*> JsepTransportCollection::Transports() {
RTC_DCHECK_RUN_ON(&sequence_checker_);
std::vector<cricket::JsepTransport*> result;
for (auto& kv : jsep_transports_by_name_) {
result.push_back(kv.second.get());
}
return result;
}
std::vector<cricket::JsepTransport*>
JsepTransportCollection::ActiveTransports() {
RTC_DCHECK_RUN_ON(&sequence_checker_);
std::set<cricket::JsepTransport*> transports;
for (const auto& kv : mid_to_transport_) {
transports.insert(kv.second);
}
return std::vector<cricket::JsepTransport*>(transports.begin(),
transports.end());
}
void JsepTransportCollection::DestroyAllTransports() {
RTC_DCHECK_RUN_ON(&sequence_checker_);
for (const auto& jsep_transport : jsep_transports_by_name_) {
map_change_callback_(jsep_transport.first, nullptr);
}
jsep_transports_by_name_.clear();
RTC_DCHECK(IsConsistent());
}
const cricket::JsepTransport* JsepTransportCollection::GetTransportByName(
const std::string& transport_name) const {
RTC_DCHECK_RUN_ON(&sequence_checker_);
auto it = jsep_transports_by_name_.find(transport_name);
return (it == jsep_transports_by_name_.end()) ? nullptr : it->second.get();
}
cricket::JsepTransport* JsepTransportCollection::GetTransportByName(
const std::string& transport_name) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
auto it = jsep_transports_by_name_.find(transport_name);
return (it == jsep_transports_by_name_.end()) ? nullptr : it->second.get();
}
cricket::JsepTransport* JsepTransportCollection::GetTransportForMid(
const std::string& mid) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
auto it = mid_to_transport_.find(mid);
return it == mid_to_transport_.end() ? nullptr : it->second;
}
const cricket::JsepTransport* JsepTransportCollection::GetTransportForMid(
const std::string& mid) const {
RTC_DCHECK_RUN_ON(&sequence_checker_);
auto it = mid_to_transport_.find(mid);
return it == mid_to_transport_.end() ? nullptr : it->second;
}
cricket::JsepTransport* JsepTransportCollection::GetTransportForMid(
absl::string_view mid) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
// TODO(hta): should be a better way.
auto it = mid_to_transport_.find(std::string(mid));
return it == mid_to_transport_.end() ? nullptr : it->second;
}
const cricket::JsepTransport* JsepTransportCollection::GetTransportForMid(
absl::string_view mid) const {
RTC_DCHECK_RUN_ON(&sequence_checker_);
// TODO(hta): Should be a better way
auto it = mid_to_transport_.find(std::string(mid));
return it == mid_to_transport_.end() ? nullptr : it->second;
}
bool JsepTransportCollection::SetTransportForMid(
const std::string& mid,
cricket::JsepTransport* jsep_transport) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
RTC_DCHECK(jsep_transport);
auto it = mid_to_transport_.find(mid);
if (it != mid_to_transport_.end() && it->second == jsep_transport)
return true;
// The map_change_callback must be called before destroying the
// transport, because it removes references to the transport
// in the RTP demuxer.
bool result = map_change_callback_(mid, jsep_transport);
if (it == mid_to_transport_.end()) {
mid_to_transport_.insert(std::make_pair(mid, jsep_transport));
} else {
auto old_transport = it->second;
it->second = jsep_transport;
MaybeDestroyJsepTransport(old_transport);
}
RTC_DCHECK(IsConsistent());
return result;
}
void JsepTransportCollection::RemoveTransportForMid(const std::string& mid) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
RTC_DCHECK(IsConsistent());
bool ret = map_change_callback_(mid, nullptr);
// Calling OnTransportChanged with nullptr should always succeed, since it is
// only expected to fail when adding media to a transport (not removing).
RTC_DCHECK(ret);
auto old_transport = GetTransportForMid(mid);
if (old_transport) {
mid_to_transport_.erase(mid);
MaybeDestroyJsepTransport(old_transport);
}
RTC_DCHECK(IsConsistent());
}
bool JsepTransportCollection::RollbackTransports() {
RTC_DCHECK_RUN_ON(&sequence_checker_);
bool ret = true;
// First, remove any new mid->transport mappings.
for (const auto& kv : mid_to_transport_) {
if (stable_mid_to_transport_.count(kv.first) == 0) {
ret = ret && map_change_callback_(kv.first, nullptr);
}
}
// Next, restore old mappings.
for (const auto& kv : stable_mid_to_transport_) {
auto it = mid_to_transport_.find(kv.first);
if (it == mid_to_transport_.end() || it->second != kv.second) {
ret = ret && map_change_callback_(kv.first, kv.second);
}
}
mid_to_transport_ = stable_mid_to_transport_;
// Moving a transport back to mid_to_transport_ means it's now included in
// the aggregate state if it wasn't previously.
state_change_callback_();
DestroyUnusedTransports();
RTC_DCHECK(IsConsistent());
return ret;
}
void JsepTransportCollection::CommitTransports() {
RTC_DCHECK_RUN_ON(&sequence_checker_);
stable_mid_to_transport_ = mid_to_transport_;
DestroyUnusedTransports();
RTC_DCHECK(IsConsistent());
}
bool JsepTransportCollection::TransportInUse(
cricket::JsepTransport* jsep_transport) const {
RTC_DCHECK_RUN_ON(&sequence_checker_);
for (const auto& kv : mid_to_transport_) {
if (kv.second == jsep_transport) {
return true;
}
}
return false;
}
bool JsepTransportCollection::TransportNeededForRollback(
cricket::JsepTransport* jsep_transport) const {
RTC_DCHECK_RUN_ON(&sequence_checker_);
for (const auto& kv : stable_mid_to_transport_) {
if (kv.second == jsep_transport) {
return true;
}
}
return false;
}
void JsepTransportCollection::MaybeDestroyJsepTransport(
cricket::JsepTransport* transport) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
// Don't destroy the JsepTransport if there are still media sections referring
// to it, or if it will be needed in case of rollback.
if (TransportInUse(transport)) {
return;
}
// If this transport is needed for rollback, don't destroy it yet, but make
// sure the aggregate state is updated since this transport is no longer
// included in it.
if (TransportNeededForRollback(transport)) {
state_change_callback_();
return;
}
for (const auto& it : jsep_transports_by_name_) {
if (it.second.get() == transport) {
jsep_transports_by_name_.erase(it.first);
state_change_callback_();
break;
}
}
RTC_DCHECK(IsConsistent());
}
void JsepTransportCollection::DestroyUnusedTransports() {
RTC_DCHECK_RUN_ON(&sequence_checker_);
bool need_state_change_callback = false;
auto it = jsep_transports_by_name_.begin();
while (it != jsep_transports_by_name_.end()) {
if (TransportInUse(it->second.get()) ||
TransportNeededForRollback(it->second.get())) {
++it;
} else {
it = jsep_transports_by_name_.erase(it);
need_state_change_callback = true;
}
}
if (need_state_change_callback) {
state_change_callback_();
}
}
bool JsepTransportCollection::IsConsistent() {
RTC_DCHECK_RUN_ON(&sequence_checker_);
for (const auto& it : jsep_transports_by_name_) {
if (!TransportInUse(it.second.get()) &&
!TransportNeededForRollback(it.second.get())) {
RTC_LOG(LS_ERROR) << "Transport registered with mid " << it.first
<< " is not in use, transport " << it.second.get();
return false;
}
}
return true;
}
} // namespace webrtc

View file

@ -0,0 +1,173 @@
/*
* Copyright 2021 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef PC_JSEP_TRANSPORT_COLLECTION_H_
#define PC_JSEP_TRANSPORT_COLLECTION_H_
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "api/jsep.h"
#include "api/peer_connection_interface.h"
#include "api/sequence_checker.h"
#include "pc/jsep_transport.h"
#include "pc/session_description.h"
#include "rtc_base/checks.h"
#include "rtc_base/system/no_unique_address.h"
#include "rtc_base/thread_annotations.h"
namespace webrtc {
// This class manages information about RFC 8843 BUNDLE bundles
// in SDP descriptions.
// This is a work-in-progress. Planned steps:
// 1) Move all Bundle-related data structures from JsepTransport
// into this class.
// 2) Move all Bundle-related functions into this class.
// 3) Move remaining Bundle-related logic into this class.
// Make data members private.
// 4) Refine interface to have comprehensible semantics.
// 5) Add unit tests.
// 6) Change the logic to do what's right.
class BundleManager {
public:
explicit BundleManager(PeerConnectionInterface::BundlePolicy bundle_policy)
: bundle_policy_(bundle_policy) {}
const std::vector<std::unique_ptr<cricket::ContentGroup>>& bundle_groups()
const {
RTC_DCHECK_RUN_ON(&sequence_checker_);
return bundle_groups_;
}
// Lookup a bundle group by a member mid name.
const cricket::ContentGroup* LookupGroupByMid(const std::string& mid) const;
cricket::ContentGroup* LookupGroupByMid(const std::string& mid);
// Returns true if the MID is the first item of a group, or if
// the MID is not a member of a group.
bool IsFirstMidInGroup(const std::string& mid) const;
// Update the groups description. This completely replaces the group
// description with the one from the SessionDescription.
void Update(const cricket::SessionDescription* description, SdpType type);
// Delete a MID from the group that contains it.
void DeleteMid(const cricket::ContentGroup* bundle_group,
const std::string& mid);
// Delete a group.
void DeleteGroup(const cricket::ContentGroup* bundle_group);
// Roll back to previous stable state.
void Rollback();
// Commit current bundle groups.
void Commit();
private:
// Recalculate established_bundle_groups_by_mid_ from bundle_groups_.
void RefreshEstablishedBundleGroupsByMid() RTC_RUN_ON(sequence_checker_);
RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_{
SequenceChecker::kDetached};
PeerConnectionInterface::BundlePolicy bundle_policy_;
std::vector<std::unique_ptr<cricket::ContentGroup>> bundle_groups_
RTC_GUARDED_BY(sequence_checker_);
std::vector<std::unique_ptr<cricket::ContentGroup>> stable_bundle_groups_
RTC_GUARDED_BY(sequence_checker_);
std::map<std::string, cricket::ContentGroup*>
established_bundle_groups_by_mid_;
};
// This class keeps the mapping of MIDs to transports.
// It is pulled out here because a lot of the code that deals with
// bundles end up modifying this map, and the two need to be consistent;
// the managers may merge.
class JsepTransportCollection {
public:
JsepTransportCollection(std::function<bool(const std::string& mid,
cricket::JsepTransport* transport)>
map_change_callback,
std::function<void()> state_change_callback)
: map_change_callback_(map_change_callback),
state_change_callback_(state_change_callback) {}
void RegisterTransport(const std::string& mid,
std::unique_ptr<cricket::JsepTransport> transport);
// Returns all transports, including those not currently mapped to any MID
// because they're being kept alive in case of rollback.
std::vector<cricket::JsepTransport*> Transports();
// Only returns transports currently mapped to a MID.
std::vector<cricket::JsepTransport*> ActiveTransports();
void DestroyAllTransports();
// Lookup a JsepTransport by the MID that was used to register it.
cricket::JsepTransport* GetTransportByName(const std::string& mid);
const cricket::JsepTransport* GetTransportByName(
const std::string& mid) const;
// Lookup a JsepTransport by any MID that refers to it.
cricket::JsepTransport* GetTransportForMid(const std::string& mid);
const cricket::JsepTransport* GetTransportForMid(
const std::string& mid) const;
cricket::JsepTransport* GetTransportForMid(absl::string_view mid);
const cricket::JsepTransport* GetTransportForMid(absl::string_view mid) const;
// Set transport for a MID. This may destroy a transport if it is no
// longer in use.
bool SetTransportForMid(const std::string& mid,
cricket::JsepTransport* jsep_transport);
// Remove a transport for a MID. This may destroy a transport if it is
// no longer in use.
void RemoveTransportForMid(const std::string& mid);
// Roll back to previous stable mid-to-transport mappings.
bool RollbackTransports();
// Commit pending mid-transport mappings (rollback is no longer possible),
// and destroy unused transports because we know now we'll never need them
// again.
void CommitTransports();
private:
// Returns true if any mid currently maps to this transport.
bool TransportInUse(cricket::JsepTransport* jsep_transport) const;
// Returns true if any mid in the last stable mapping maps to this transport,
// meaning it should be kept alive in case of rollback.
bool TransportNeededForRollback(cricket::JsepTransport* jsep_transport) const;
// Destroy a transport if it's no longer in use. This includes whether it
// will be needed in case of rollback.
void MaybeDestroyJsepTransport(cricket::JsepTransport* transport);
// Destroys all transports that are no longer in use.
void DestroyUnusedTransports();
bool IsConsistent(); // For testing only: Verify internal structure.
RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_{
SequenceChecker::kDetached};
// This member owns the JSEP transports.
std::map<std::string, std::unique_ptr<cricket::JsepTransport>>
jsep_transports_by_name_ RTC_GUARDED_BY(sequence_checker_);
// This keeps track of the mapping between media section
// (BaseChannel/SctpTransport) and the JsepTransport underneath.
std::map<std::string, cricket::JsepTransport*> mid_to_transport_
RTC_GUARDED_BY(sequence_checker_);
// A snapshot of mid_to_transport_ at the last stable state. Used for
// rollback.
std::map<std::string, cricket::JsepTransport*> stable_mid_to_transport_
RTC_GUARDED_BY(sequence_checker_);
// Callback used to inform subscribers of altered transports.
const std::function<bool(const std::string& mid,
cricket::JsepTransport* transport)>
map_change_callback_;
// Callback used to inform subscribers of possibly altered state.
const std::function<void()> state_change_callback_;
};
} // namespace webrtc
#endif // PC_JSEP_TRANSPORT_COLLECTION_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,518 @@
/*
* Copyright 2017 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef PC_JSEP_TRANSPORT_CONTROLLER_H_
#define PC_JSEP_TRANSPORT_CONTROLLER_H_
#include <stdint.h>
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "absl/functional/any_invocable.h"
#include "absl/types/optional.h"
#include "api/async_dns_resolver.h"
#include "api/candidate.h"
#include "api/crypto/crypto_options.h"
#include "api/ice_transport_factory.h"
#include "api/ice_transport_interface.h"
#include "api/jsep.h"
#include "api/peer_connection_interface.h"
#include "api/rtc_error.h"
#include "api/rtc_event_log/rtc_event_log.h"
#include "api/scoped_refptr.h"
#include "api/sequence_checker.h"
#include "api/transport/data_channel_transport_interface.h"
#include "api/transport/sctp_transport_factory_interface.h"
#include "media/sctp/sctp_transport_internal.h"
#include "p2p/base/dtls_transport.h"
#include "p2p/base/dtls_transport_factory.h"
#include "p2p/base/dtls_transport_internal.h"
#include "p2p/base/ice_transport_internal.h"
#include "p2p/base/p2p_transport_channel.h"
#include "p2p/base/packet_transport_internal.h"
#include "p2p/base/port.h"
#include "p2p/base/port_allocator.h"
#include "p2p/base/transport_description.h"
#include "p2p/base/transport_info.h"
#include "pc/dtls_srtp_transport.h"
#include "pc/dtls_transport.h"
#include "pc/jsep_transport.h"
#include "pc/jsep_transport_collection.h"
#include "pc/rtp_transport.h"
#include "pc/rtp_transport_internal.h"
#include "pc/sctp_transport.h"
#include "pc/session_description.h"
#include "pc/srtp_transport.h"
#include "pc/transport_stats.h"
#include "rtc_base/callback_list.h"
#include "rtc_base/checks.h"
#include "rtc_base/copy_on_write_buffer.h"
#include "rtc_base/helpers.h"
#include "rtc_base/rtc_certificate.h"
#include "rtc_base/ssl_certificate.h"
#include "rtc_base/ssl_stream_adapter.h"
#include "rtc_base/third_party/sigslot/sigslot.h"
#include "rtc_base/thread.h"
#include "rtc_base/thread_annotations.h"
namespace rtc {
class Thread;
class PacketTransportInternal;
} // namespace rtc
namespace webrtc {
class JsepTransportController : public sigslot::has_slots<> {
public:
// Used when the RtpTransport/DtlsTransport of the m= section is changed
// because the section is rejected or BUNDLE is enabled.
class Observer {
public:
virtual ~Observer() {}
// Returns true if media associated with `mid` was successfully set up to be
// demultiplexed on `rtp_transport`. Could return false if two bundled m=
// sections use the same SSRC, for example.
//
// If a data channel transport must be negotiated, `data_channel_transport`
// and `negotiation_state` indicate negotiation status. If
// `data_channel_transport` is null, the data channel transport should not
// be used. Otherwise, the value is a pointer to the transport to be used
// for data channels on `mid`, if any.
//
// The observer should not send data on `data_channel_transport` until
// `negotiation_state` is provisional or final. It should not delete
// `data_channel_transport` or any fallback transport until
// `negotiation_state` is final.
virtual bool OnTransportChanged(
const std::string& mid,
RtpTransportInternal* rtp_transport,
rtc::scoped_refptr<DtlsTransport> dtls_transport,
DataChannelTransportInterface* data_channel_transport) = 0;
};
struct Config {
// If `redetermine_role_on_ice_restart` is true, ICE role is redetermined
// upon setting a local transport description that indicates an ICE
// restart.
bool redetermine_role_on_ice_restart = true;
rtc::SSLProtocolVersion ssl_max_version = rtc::SSL_PROTOCOL_DTLS_12;
// `crypto_options` is used to determine if created DTLS transports
// negotiate GCM crypto suites or not.
CryptoOptions crypto_options;
PeerConnectionInterface::BundlePolicy bundle_policy =
PeerConnectionInterface::kBundlePolicyBalanced;
PeerConnectionInterface::RtcpMuxPolicy rtcp_mux_policy =
PeerConnectionInterface::kRtcpMuxPolicyRequire;
bool disable_encryption = false;
bool enable_external_auth = false;
// Used to inject the ICE/DTLS transports created externally.
IceTransportFactory* ice_transport_factory = nullptr;
cricket::DtlsTransportFactory* dtls_transport_factory = nullptr;
Observer* transport_observer = nullptr;
// Must be provided and valid for the lifetime of the
// JsepTransportController instance.
absl::AnyInvocable<void(const rtc::CopyOnWriteBuffer& packet,
int64_t packet_time_us) const>
rtcp_handler;
absl::AnyInvocable<void(const RtpPacketReceived& parsed_packet) const>
un_demuxable_packet_handler;
// Initial value for whether DtlsTransport reset causes a reset
// of SRTP parameters.
bool active_reset_srtp_params = false;
RtcEventLog* event_log = nullptr;
// Factory for SCTP transports.
SctpTransportFactoryInterface* sctp_factory = nullptr;
std::function<void(rtc::SSLHandshakeError)> on_dtls_handshake_error_;
// Field trials.
const FieldTrialsView* field_trials;
};
// The ICE related events are fired on the `network_thread`.
// All the transport related methods are called on the `network_thread`
// and destruction of the JsepTransportController must occur on the
// `network_thread`.
JsepTransportController(
rtc::Thread* network_thread,
cricket::PortAllocator* port_allocator,
AsyncDnsResolverFactoryInterface* async_dns_resolver_factory,
Config config);
virtual ~JsepTransportController();
JsepTransportController(const JsepTransportController&) = delete;
JsepTransportController& operator=(const JsepTransportController&) = delete;
// The main method to be called; applies a description at the transport
// level, creating/destroying transport objects as needed and updating their
// properties. This includes RTP, DTLS, and ICE (but not SCTP). At least not
// yet? May make sense to in the future.
//
// `local_desc` must always be valid. If a remote description has previously
// been set via a call to `SetRemoteDescription()` then `remote_desc` should
// point to that description object in order to keep the current local and
// remote session descriptions in sync.
RTCError SetLocalDescription(SdpType type,
const cricket::SessionDescription* local_desc,
const cricket::SessionDescription* remote_desc);
// Call to apply a remote description (See `SetLocalDescription()` for local).
//
// `remote_desc` must always be valid. If a local description has previously
// been set via a call to `SetLocalDescription()` then `local_desc` should
// point to that description object in order to keep the current local and
// remote session descriptions in sync.
RTCError SetRemoteDescription(SdpType type,
const cricket::SessionDescription* local_desc,
const cricket::SessionDescription* remote_desc);
// Get transports to be used for the provided `mid`. If bundling is enabled,
// calling GetRtpTransport for multiple MIDs may yield the same object.
RtpTransportInternal* GetRtpTransport(absl::string_view mid) const;
cricket::DtlsTransportInternal* GetDtlsTransport(const std::string& mid);
const cricket::DtlsTransportInternal* GetRtcpDtlsTransport(
const std::string& mid) const;
// Gets the externally sharable version of the DtlsTransport.
rtc::scoped_refptr<DtlsTransport> LookupDtlsTransportByMid(
const std::string& mid);
rtc::scoped_refptr<SctpTransport> GetSctpTransport(
const std::string& mid) const;
DataChannelTransportInterface* GetDataChannelTransport(
const std::string& mid) const;
/*********************
* ICE-related methods
********************/
// This method is public to allow PeerConnection to update it from
// SetConfiguration.
void SetIceConfig(const cricket::IceConfig& config);
// Set the "needs-ice-restart" flag as described in JSEP. After the flag is
// set, offers should generate new ufrags/passwords until an ICE restart
// occurs.
void SetNeedsIceRestartFlag();
// Returns true if the ICE restart flag above was set, and no ICE restart has
// occurred yet for this transport (by applying a local description with
// changed ufrag/password). If the transport has been deleted as a result of
// bundling, returns false.
bool NeedsIceRestart(const std::string& mid) const;
// Start gathering candidates for any new transports, or transports doing an
// ICE restart.
void MaybeStartGathering();
RTCError AddRemoteCandidates(
const std::string& mid,
const std::vector<cricket::Candidate>& candidates);
RTCError RemoveRemoteCandidates(
const std::vector<cricket::Candidate>& candidates);
/**********************
* DTLS-related methods
*********************/
// Specifies the identity to use in this session.
// Can only be called once.
bool SetLocalCertificate(
const rtc::scoped_refptr<rtc::RTCCertificate>& certificate);
rtc::scoped_refptr<rtc::RTCCertificate> GetLocalCertificate(
const std::string& mid) const;
// Caller owns returned certificate chain. This method mainly exists for
// stats reporting.
std::unique_ptr<rtc::SSLCertChain> GetRemoteSSLCertChain(
const std::string& mid) const;
// Get negotiated role, if one has been negotiated.
absl::optional<rtc::SSLRole> GetDtlsRole(const std::string& mid) const;
// TODO(deadbeef): GetStats isn't const because all the way down to
// OpenSSLStreamAdapter, GetSslCipherSuite and GetDtlsSrtpCryptoSuite are not
// const. Fix this.
bool GetStats(const std::string& mid, cricket::TransportStats* stats);
bool initial_offerer() const { return initial_offerer_ && *initial_offerer_; }
void SetActiveResetSrtpParams(bool active_reset_srtp_params);
RTCError RollbackTransports();
// F: void(const std::string&, const std::vector<cricket::Candidate>&)
template <typename F>
void SubscribeIceCandidateGathered(F&& callback) {
RTC_DCHECK_RUN_ON(network_thread_);
signal_ice_candidates_gathered_.AddReceiver(std::forward<F>(callback));
}
// F: void(cricket::IceConnectionState)
template <typename F>
void SubscribeIceConnectionState(F&& callback) {
RTC_DCHECK_RUN_ON(network_thread_);
signal_ice_connection_state_.AddReceiver(std::forward<F>(callback));
}
// F: void(PeerConnectionInterface::PeerConnectionState)
template <typename F>
void SubscribeConnectionState(F&& callback) {
RTC_DCHECK_RUN_ON(network_thread_);
signal_connection_state_.AddReceiver(std::forward<F>(callback));
}
// F: void(PeerConnectionInterface::IceConnectionState)
template <typename F>
void SubscribeStandardizedIceConnectionState(F&& callback) {
RTC_DCHECK_RUN_ON(network_thread_);
signal_standardized_ice_connection_state_.AddReceiver(
std::forward<F>(callback));
}
// F: void(cricket::IceGatheringState)
template <typename F>
void SubscribeIceGatheringState(F&& callback) {
RTC_DCHECK_RUN_ON(network_thread_);
signal_ice_gathering_state_.AddReceiver(std::forward<F>(callback));
}
// F: void(const cricket::IceCandidateErrorEvent&)
template <typename F>
void SubscribeIceCandidateError(F&& callback) {
RTC_DCHECK_RUN_ON(network_thread_);
signal_ice_candidate_error_.AddReceiver(std::forward<F>(callback));
}
// F: void(const std::vector<cricket::Candidate>&)
template <typename F>
void SubscribeIceCandidatesRemoved(F&& callback) {
RTC_DCHECK_RUN_ON(network_thread_);
signal_ice_candidates_removed_.AddReceiver(std::forward<F>(callback));
}
// F: void(const cricket::CandidatePairChangeEvent&)
template <typename F>
void SubscribeIceCandidatePairChanged(F&& callback) {
RTC_DCHECK_RUN_ON(network_thread_);
signal_ice_candidate_pair_changed_.AddReceiver(std::forward<F>(callback));
}
private:
// All of these callbacks are fired on the network thread.
// If any transport failed => failed,
// Else if all completed => completed,
// Else if all connected => connected,
// Else => connecting
CallbackList<cricket::IceConnectionState> signal_ice_connection_state_
RTC_GUARDED_BY(network_thread_);
CallbackList<PeerConnectionInterface::PeerConnectionState>
signal_connection_state_ RTC_GUARDED_BY(network_thread_);
CallbackList<PeerConnectionInterface::IceConnectionState>
signal_standardized_ice_connection_state_ RTC_GUARDED_BY(network_thread_);
// If all transports done gathering => complete,
// Else if any are gathering => gathering,
// Else => new
CallbackList<cricket::IceGatheringState> signal_ice_gathering_state_
RTC_GUARDED_BY(network_thread_);
// [mid, candidates]
CallbackList<const std::string&, const std::vector<cricket::Candidate>&>
signal_ice_candidates_gathered_ RTC_GUARDED_BY(network_thread_);
CallbackList<const cricket::IceCandidateErrorEvent&>
signal_ice_candidate_error_ RTC_GUARDED_BY(network_thread_);
CallbackList<const std::vector<cricket::Candidate>&>
signal_ice_candidates_removed_ RTC_GUARDED_BY(network_thread_);
CallbackList<const cricket::CandidatePairChangeEvent&>
signal_ice_candidate_pair_changed_ RTC_GUARDED_BY(network_thread_);
// Called from SetLocalDescription and SetRemoteDescription.
// When `local` is true, local_desc must be valid. Similarly when
// `local` is false, remote_desc must be valid. The description counterpart
// to the one that's being applied, may be nullptr but when it's supplied
// the counterpart description's content groups will be kept up to date for
// `type == SdpType::kAnswer`.
RTCError ApplyDescription_n(bool local,
SdpType type,
const cricket::SessionDescription* local_desc,
const cricket::SessionDescription* remote_desc)
RTC_RUN_ON(network_thread_);
RTCError ValidateAndMaybeUpdateBundleGroups(
bool local,
SdpType type,
const cricket::SessionDescription* local_desc,
const cricket::SessionDescription* remote_desc)
RTC_RUN_ON(network_thread_);
RTCError ValidateContent(const cricket::ContentInfo& content_info);
void HandleRejectedContent(const cricket::ContentInfo& content_info)
RTC_RUN_ON(network_thread_);
bool HandleBundledContent(const cricket::ContentInfo& content_info,
const cricket::ContentGroup& bundle_group)
RTC_RUN_ON(network_thread_);
cricket::JsepTransportDescription CreateJsepTransportDescription(
const cricket::ContentInfo& content_info,
const cricket::TransportInfo& transport_info,
const std::vector<int>& encrypted_extension_ids,
int rtp_abs_sendtime_extn_id);
std::map<const cricket::ContentGroup*, std::vector<int>>
MergeEncryptedHeaderExtensionIdsForBundles(
const cricket::SessionDescription* description);
std::vector<int> GetEncryptedHeaderExtensionIds(
const cricket::ContentInfo& content_info);
int GetRtpAbsSendTimeHeaderExtensionId(
const cricket::ContentInfo& content_info);
// This method takes the BUNDLE group into account. If the JsepTransport is
// destroyed because of BUNDLE, it would return the transport which other
// transports are bundled on (In current implementation, it is the first
// content in the BUNDLE group).
const cricket::JsepTransport* GetJsepTransportForMid(
const std::string& mid) const RTC_RUN_ON(network_thread_);
cricket::JsepTransport* GetJsepTransportForMid(const std::string& mid)
RTC_RUN_ON(network_thread_);
const cricket::JsepTransport* GetJsepTransportForMid(
absl::string_view mid) const RTC_RUN_ON(network_thread_);
cricket::JsepTransport* GetJsepTransportForMid(absl::string_view mid)
RTC_RUN_ON(network_thread_);
// Get the JsepTransport without considering the BUNDLE group. Return nullptr
// if the JsepTransport is destroyed.
const cricket::JsepTransport* GetJsepTransportByName(
const std::string& transport_name) const RTC_RUN_ON(network_thread_);
cricket::JsepTransport* GetJsepTransportByName(
const std::string& transport_name) RTC_RUN_ON(network_thread_);
// Creates jsep transport. Noop if transport is already created.
// Transport is created either during SetLocalDescription (`local` == true) or
// during SetRemoteDescription (`local` == false). Passing `local` helps to
// differentiate initiator (caller) from answerer (callee).
RTCError MaybeCreateJsepTransport(
bool local,
const cricket::ContentInfo& content_info,
const cricket::SessionDescription& description)
RTC_RUN_ON(network_thread_);
void DestroyAllJsepTransports_n() RTC_RUN_ON(network_thread_);
void SetIceRole_n(cricket::IceRole ice_role) RTC_RUN_ON(network_thread_);
cricket::IceRole DetermineIceRole(
cricket::JsepTransport* jsep_transport,
const cricket::TransportInfo& transport_info,
SdpType type,
bool local);
std::unique_ptr<cricket::DtlsTransportInternal> CreateDtlsTransport(
const cricket::ContentInfo& content_info,
cricket::IceTransportInternal* ice);
rtc::scoped_refptr<IceTransportInterface> CreateIceTransport(
const std::string& transport_name,
bool rtcp);
std::unique_ptr<RtpTransport> CreateUnencryptedRtpTransport(
const std::string& transport_name,
rtc::PacketTransportInternal* rtp_packet_transport,
rtc::PacketTransportInternal* rtcp_packet_transport);
std::unique_ptr<SrtpTransport> CreateSdesTransport(
const std::string& transport_name,
cricket::DtlsTransportInternal* rtp_dtls_transport,
cricket::DtlsTransportInternal* rtcp_dtls_transport);
std::unique_ptr<DtlsSrtpTransport> CreateDtlsSrtpTransport(
const std::string& transport_name,
cricket::DtlsTransportInternal* rtp_dtls_transport,
cricket::DtlsTransportInternal* rtcp_dtls_transport);
// Collect all the DtlsTransports, including RTP and RTCP, from the
// JsepTransports, including those not mapped to a MID because they are being
// kept alive in case of rollback.
std::vector<cricket::DtlsTransportInternal*> GetDtlsTransports();
// Same as the above, but doesn't include rollback transports.
// JsepTransportController can iterate all the DtlsTransports and update the
// aggregate states.
std::vector<cricket::DtlsTransportInternal*> GetActiveDtlsTransports();
// Handlers for signals from Transport.
void OnTransportWritableState_n(rtc::PacketTransportInternal* transport)
RTC_RUN_ON(network_thread_);
void OnTransportReceivingState_n(rtc::PacketTransportInternal* transport)
RTC_RUN_ON(network_thread_);
void OnTransportGatheringState_n(cricket::IceTransportInternal* transport)
RTC_RUN_ON(network_thread_);
void OnTransportCandidateGathered_n(cricket::IceTransportInternal* transport,
const cricket::Candidate& candidate)
RTC_RUN_ON(network_thread_);
void OnTransportCandidateError_n(cricket::IceTransportInternal* transport,
const cricket::IceCandidateErrorEvent& event)
RTC_RUN_ON(network_thread_);
void OnTransportCandidatesRemoved_n(cricket::IceTransportInternal* transport,
const cricket::Candidates& candidates)
RTC_RUN_ON(network_thread_);
void OnTransportRoleConflict_n(cricket::IceTransportInternal* transport)
RTC_RUN_ON(network_thread_);
void OnTransportStateChanged_n(cricket::IceTransportInternal* transport)
RTC_RUN_ON(network_thread_);
void OnTransportCandidatePairChanged_n(
const cricket::CandidatePairChangeEvent& event)
RTC_RUN_ON(network_thread_);
void UpdateAggregateStates_n() RTC_RUN_ON(network_thread_);
void OnRtcpPacketReceived_n(rtc::CopyOnWriteBuffer* packet,
int64_t packet_time_us)
RTC_RUN_ON(network_thread_);
void OnUnDemuxableRtpPacketReceived_n(const RtpPacketReceived& packet)
RTC_RUN_ON(network_thread_);
void OnDtlsHandshakeError(rtc::SSLHandshakeError error);
bool OnTransportChanged(const std::string& mid,
cricket::JsepTransport* transport);
rtc::Thread* const network_thread_ = nullptr;
cricket::PortAllocator* const port_allocator_ = nullptr;
AsyncDnsResolverFactoryInterface* const async_dns_resolver_factory_ = nullptr;
JsepTransportCollection transports_ RTC_GUARDED_BY(network_thread_);
// Aggregate states for Transports.
// standardized_ice_connection_state_ is intended to replace
// ice_connection_state, see bugs.webrtc.org/9308
cricket::IceConnectionState ice_connection_state_ =
cricket::kIceConnectionConnecting;
PeerConnectionInterface::IceConnectionState
standardized_ice_connection_state_ =
PeerConnectionInterface::kIceConnectionNew;
PeerConnectionInterface::PeerConnectionState combined_connection_state_ =
PeerConnectionInterface::PeerConnectionState::kNew;
cricket::IceGatheringState ice_gathering_state_ = cricket::kIceGatheringNew;
const Config config_;
bool active_reset_srtp_params_ RTC_GUARDED_BY(network_thread_);
absl::optional<bool> initial_offerer_;
cricket::IceConfig ice_config_;
cricket::IceRole ice_role_ = cricket::ICEROLE_CONTROLLING;
uint64_t ice_tiebreaker_;
rtc::scoped_refptr<rtc::RTCCertificate> certificate_;
BundleManager bundles_;
};
} // namespace webrtc
#endif // PC_JSEP_TRANSPORT_CONTROLLER_H_

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,219 @@
/*
* 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.
*/
// This file contains a class used for gathering statistics from an ongoing
// libjingle PeerConnection.
#ifndef PC_LEGACY_STATS_COLLECTOR_H_
#define PC_LEGACY_STATS_COLLECTOR_H_
#include <stdint.h>
#include <algorithm>
#include <cstdint>
#include <map>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "absl/types/optional.h"
#include "api/candidate.h"
#include "api/field_trials_view.h"
#include "api/legacy_stats_types.h"
#include "api/media_stream_interface.h"
#include "api/peer_connection_interface.h"
#include "api/scoped_refptr.h"
#include "p2p/base/connection_info.h"
#include "p2p/base/port.h"
#include "pc/legacy_stats_collector_interface.h"
#include "pc/peer_connection_internal.h"
#include "pc/rtp_transceiver.h"
#include "pc/transport_stats.h"
#include "rtc_base/network_constants.h"
#include "rtc_base/ssl_certificate.h"
#include "rtc_base/thread_annotations.h"
namespace webrtc {
// Conversion function to convert candidate type string to the corresponding one
// from enum RTCStatsIceCandidateType.
const char* IceCandidateTypeToStatsType(const cricket::Candidate& candidate);
// Conversion function to convert adapter type to report string which are more
// fitting to the general style of http://w3c.github.io/webrtc-stats. This is
// only used by stats collector.
const char* AdapterTypeToStatsType(rtc::AdapterType type);
// A mapping between track ids and their StatsReport.
typedef std::map<std::string, StatsReport*> TrackIdMap;
class LegacyStatsCollector : public LegacyStatsCollectorInterface {
public:
// The caller is responsible for ensuring that the pc outlives the
// LegacyStatsCollector instance.
explicit LegacyStatsCollector(PeerConnectionInternal* pc);
virtual ~LegacyStatsCollector();
// Adds a MediaStream with tracks that can be used as a `selector` in a call
// to GetStats.
void AddStream(MediaStreamInterface* stream);
void AddTrack(MediaStreamTrackInterface* track);
// Adds a local audio track that is used for getting some voice statistics.
void AddLocalAudioTrack(AudioTrackInterface* audio_track,
uint32_t ssrc) override;
// Removes a local audio tracks that is used for getting some voice
// statistics.
void RemoveLocalAudioTrack(AudioTrackInterface* audio_track,
uint32_t ssrc) override;
// Gather statistics from the session and store them for future use.
void UpdateStats(PeerConnectionInterface::StatsOutputLevel level);
// Gets a StatsReports of the last collected stats. Note that UpdateStats must
// be called before this function to get the most recent stats. `selector` is
// a track label or empty string. The most recent reports are stored in
// `reports`.
// TODO(tommi): Change this contract to accept a callback object instead
// of filling in `reports`. As is, there's a requirement that the caller
// uses `reports` immediately without allowing any async activity on
// the thread (message handling etc) and then discard the results.
void GetStats(MediaStreamTrackInterface* track,
StatsReports* reports) override;
// Prepare a local or remote SSRC report for the given ssrc. Used internally
// in the ExtractStatsFromList template.
StatsReport* PrepareReport(bool local,
uint32_t ssrc,
const std::string& track_id,
const StatsReport::Id& transport_id,
StatsReport::Direction direction);
StatsReport* PrepareADMReport();
// A track is invalid if there is no report data for it.
bool IsValidTrack(const std::string& track_id);
// Reset the internal cache timestamp to force an update of the stats next
// time UpdateStats() is called. This call needs to be made on the signaling
// thread and should be made every time configuration changes that affect
// stats have been made.
void InvalidateCache();
bool UseStandardBytesStats() const { return use_standard_bytes_stats_; }
private:
friend class LegacyStatsCollectorTest;
// Struct that's populated on the network thread and carries the values to
// the signaling thread where the stats are added to the stats reports.
struct TransportStats {
TransportStats() = default;
TransportStats(std::string transport_name,
cricket::TransportStats transport_stats)
: name(std::move(transport_name)), stats(std::move(transport_stats)) {}
TransportStats(TransportStats&&) = default;
TransportStats(const TransportStats&) = delete;
std::string name;
cricket::TransportStats stats;
std::unique_ptr<rtc::SSLCertificateStats> local_cert_stats;
std::unique_ptr<rtc::SSLCertificateStats> remote_cert_stats;
};
struct SessionStats {
SessionStats() = default;
SessionStats(SessionStats&&) = default;
SessionStats(const SessionStats&) = delete;
SessionStats& operator=(SessionStats&&) = default;
SessionStats& operator=(SessionStats&) = delete;
cricket::CandidateStatsList candidate_stats;
std::vector<TransportStats> transport_stats;
std::map<std::string, std::string> transport_names_by_mid;
};
// Overridden in unit tests to fake timing.
virtual double GetTimeNow();
bool CopySelectedReports(const std::string& selector, StatsReports* reports);
// Helper method for creating IceCandidate report. `is_local` indicates
// whether this candidate is local or remote.
StatsReport* AddCandidateReport(
const cricket::CandidateStats& candidate_stats,
bool local);
// Adds a report for this certificate and every certificate in its chain, and
// returns the leaf certificate's report (`cert_stats`'s report).
StatsReport* AddCertificateReports(
std::unique_ptr<rtc::SSLCertificateStats> cert_stats);
StatsReport* AddConnectionInfoReport(const std::string& content_name,
int component,
int connection_id,
const StatsReport::Id& channel_report_id,
const cricket::ConnectionInfo& info);
void ExtractDataInfo_n(StatsCollection* reports);
// Returns the `transport_names_by_mid` member from the SessionStats as
// gathered and used to populate the stats. Contains one synchronous hop
// to the network thread to get this information along with querying data
// channel stats at the same time and populating `reports_`.
std::map<std::string, std::string> ExtractSessionAndDataInfo();
void ExtractBweInfo();
void ExtractMediaInfo(
const std::map<std::string, std::string>& transport_names_by_mid);
void ExtractSenderInfo();
StatsReport* GetReport(const StatsReport::StatsType& type,
const std::string& id,
StatsReport::Direction direction);
// Helper method to get stats from the local audio tracks.
void UpdateStatsFromExistingLocalAudioTracks(bool has_remote_tracks);
void UpdateReportFromAudioTrack(AudioTrackInterface* track,
StatsReport* report,
bool has_remote_tracks);
// Helper method to update the timestamp of track records.
void UpdateTrackReports();
SessionStats ExtractSessionInfo_n(
const std::vector<rtc::scoped_refptr<
RtpTransceiverProxyWithInternal<RtpTransceiver>>>& transceivers,
absl::optional<std::string> sctp_transport_name,
absl::optional<std::string> sctp_mid);
void ExtractSessionInfo_s(SessionStats& session_stats);
// A collection for all of our stats reports.
StatsCollection reports_;
TrackIdMap track_ids_;
// Raw pointer to the peer connection the statistics are gathered from.
PeerConnectionInternal* const pc_;
int64_t cache_timestamp_ms_ RTC_GUARDED_BY(pc_->signaling_thread()) = 0;
double stats_gathering_started_;
const bool use_standard_bytes_stats_;
// TODO(tommi): We appear to be holding on to raw pointers to reference
// counted objects? We should be using scoped_refptr here.
typedef std::vector<std::pair<AudioTrackInterface*, uint32_t>>
LocalAudioTrackVector;
LocalAudioTrackVector local_audio_tracks_;
};
} // namespace webrtc
#endif // PC_LEGACY_STATS_COLLECTOR_H_

View file

@ -0,0 +1,43 @@
/*
* Copyright 2020 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.
*/
// This file contains an interface for the (obsolete) StatsCollector class that
// is used by compilation units that do not wish to depend on the StatsCollector
// implementation.
#ifndef PC_LEGACY_STATS_COLLECTOR_INTERFACE_H_
#define PC_LEGACY_STATS_COLLECTOR_INTERFACE_H_
#include <stdint.h>
#include "api/legacy_stats_types.h"
#include "api/media_stream_interface.h"
namespace webrtc {
class LegacyStatsCollectorInterface {
public:
virtual ~LegacyStatsCollectorInterface() {}
// Adds a local audio track that is used for getting some voice statistics.
virtual void AddLocalAudioTrack(AudioTrackInterface* audio_track,
uint32_t ssrc) = 0;
// Removes a local audio tracks that is used for getting some voice
// statistics.
virtual void RemoveLocalAudioTrack(AudioTrackInterface* audio_track,
uint32_t ssrc) = 0;
virtual void GetStats(MediaStreamTrackInterface* track,
StatsReports* reports) = 0;
};
} // namespace webrtc
#endif // PC_LEGACY_STATS_COLLECTOR_INTERFACE_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,31 @@
/*
* Copyright 2013 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/local_audio_source.h"
using webrtc::MediaSourceInterface;
namespace webrtc {
rtc::scoped_refptr<LocalAudioSource> LocalAudioSource::Create(
const cricket::AudioOptions* audio_options) {
auto source = rtc::make_ref_counted<LocalAudioSource>();
source->Initialize(audio_options);
return source;
}
void LocalAudioSource::Initialize(const cricket::AudioOptions* audio_options) {
if (!audio_options)
return;
options_ = *audio_options;
}
} // namespace webrtc

View file

@ -0,0 +1,50 @@
/*
* 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.
*/
#ifndef PC_LOCAL_AUDIO_SOURCE_H_
#define PC_LOCAL_AUDIO_SOURCE_H_
#include "api/audio_options.h"
#include "api/media_stream_interface.h"
#include "api/notifier.h"
#include "api/scoped_refptr.h"
// LocalAudioSource implements AudioSourceInterface.
// This contains settings for switching audio processing on and off.
namespace webrtc {
class LocalAudioSource : public Notifier<AudioSourceInterface> {
public:
// Creates an instance of LocalAudioSource.
static rtc::scoped_refptr<LocalAudioSource> Create(
const cricket::AudioOptions* audio_options);
SourceState state() const override { return kLive; }
bool remote() const override { return false; }
const cricket::AudioOptions options() const override { return options_; }
void AddSink(AudioTrackSinkInterface* sink) override {}
void RemoveSink(AudioTrackSinkInterface* sink) override {}
protected:
LocalAudioSource() {}
~LocalAudioSource() override {}
private:
void Initialize(const cricket::AudioOptions* audio_options);
cricket::AudioOptions options_;
};
} // namespace webrtc
#endif // PC_LOCAL_AUDIO_SOURCE_H_

View file

@ -0,0 +1,30 @@
/*
* Copyright 2013 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/local_audio_source.h"
#include "absl/types/optional.h"
#include "test/gtest.h"
using webrtc::LocalAudioSource;
TEST(LocalAudioSourceTest, InitWithAudioOptions) {
cricket::AudioOptions audio_options;
audio_options.highpass_filter = true;
rtc::scoped_refptr<LocalAudioSource> source =
LocalAudioSource::Create(&audio_options);
EXPECT_EQ(true, source->options().highpass_filter);
}
TEST(LocalAudioSourceTest, InitWithNoOptions) {
rtc::scoped_refptr<LocalAudioSource> source =
LocalAudioSource::Create(nullptr);
EXPECT_EQ(absl::nullopt, source->options().highpass_filter);
}

View file

@ -0,0 +1,45 @@
/*
* Copyright 2023 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef PC_MEDIA_FACTORY_H_
#define PC_MEDIA_FACTORY_H_
#include <memory>
#include "api/environment/environment.h"
#include "call/call.h"
#include "call/call_config.h"
#include "media/base/media_engine.h"
namespace webrtc {
// PeerConnectionFactoryDependencies is forward declared because of circular
// dependency between MediaFactory and PeerConnectionFactoryDependencies:
// PeerConnectionFactoryDependencies keeps an instance of MediaFactory and thus
// needs to know how to destroy it.
// MediaFactory mentions PeerConnectionFactoryDependencies in api, but does not
// need its full definition.
struct PeerConnectionFactoryDependencies;
// Interface repsponsible for constructing media specific classes for
// PeerConnectionFactory and PeerConnection.
class MediaFactory {
public:
virtual ~MediaFactory() = default;
virtual std::unique_ptr<Call> CreateCall(const CallConfig& config) = 0;
virtual std::unique_ptr<cricket::MediaEngineInterface> CreateMediaEngine(
const Environment& env,
PeerConnectionFactoryDependencies& dependencies) = 0;
};
} // namespace webrtc
#endif // PC_MEDIA_FACTORY_H_

View file

@ -0,0 +1,105 @@
/*
* Copyright 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/media_protocol_names.h"
#include <ctype.h>
#include <stddef.h>
#include <string>
namespace cricket {
// The official registry of RTP parameters is at
// http://www.iana.org/assignments/rtp-parameters/rtp-parameters.xml
// The UDP/DTLS and TCP/DTLS prefixes are not registered there.
// There are multiple variants of the RTP protocol stack, including
// UDP/TLS/RTP/SAVPF (WebRTC default), RTP/AVP, RTP/AVPF, RTP/SAVPF,
// TCP/DTLS/RTP/SAVPF and so on. We accept anything that has RTP/
// embedded in it somewhere as being an RTP protocol.
const char kMediaProtocolRtpPrefix[] = "RTP/";
// Protocol names generated by WebRTC
const char kMediaProtocolSctp[] = "SCTP";
const char kMediaProtocolUdpDtlsSctp[] = "UDP/DTLS/SCTP";
const char kMediaProtocolDtlsSctp[] = "DTLS/SCTP";
const char kMediaProtocolTcpDtlsSctp[] = "TCP/DTLS/SCTP";
// RFC5124
const char kMediaProtocolDtlsSavpf[] = "UDP/TLS/RTP/SAVPF";
const char kMediaProtocolSavpf[] = "RTP/SAVPF";
const char kMediaProtocolAvpf[] = "RTP/AVPF";
namespace {
// Protocol names that we tolerate, but do not generate.
// We always generate offers with "UDP/TLS/RTP/SAVPF" when using DTLS-SRTP,
// but we tolerate "RTP/SAVPF" and "RTP/SAVP" and the "UDP/TLS" and "TCP/TLS"
// prefixes in offers we receive, for compatibility.
// RFC4585
const char kMediaProtocolSavp[] = "RTP/SAVP";
const char kMediaProtocolAvp[] = "RTP/AVP";
const char kMediaProtocolTcpTlsSavpf[] = "TCP/TLS/RTP/SAVPF";
const char kMediaProtocolUdpTlsSavpf[] = "UDP/TLS/RTP/SAVPF";
const char kMediaProtocolTcpTlsSavp[] = "TCP/TLS/RTP/SAVP";
const char kMediaProtocolUdpTlsSavp[] = "UDP/TLS/RTP/SAVP";
} // namespace
bool IsDtlsSctp(absl::string_view protocol) {
return protocol == kMediaProtocolDtlsSctp ||
protocol == kMediaProtocolUdpDtlsSctp ||
protocol == kMediaProtocolTcpDtlsSctp;
}
bool IsPlainSctp(absl::string_view protocol) {
return protocol == kMediaProtocolSctp;
}
bool IsSctpProtocol(absl::string_view protocol) {
return IsPlainSctp(protocol) || IsDtlsSctp(protocol);
}
bool IsRtpProtocol(absl::string_view protocol) {
if (protocol.empty()) {
return true;
}
size_t pos = protocol.find(cricket::kMediaProtocolRtpPrefix);
if (pos == std::string::npos) {
return false;
}
// RTP must be at the beginning of a string or not preceded by alpha
if (pos == 0 || !isalpha(static_cast<unsigned char>(protocol[pos - 1]))) {
return true;
}
return false;
}
// Note that the below functions support some protocol strings purely for
// legacy compatibility, as required by JSEP in Section 5.1.2, Profile Names
// and Interoperability.
bool IsDtlsRtp(absl::string_view protocol) {
// Most-likely values first.
return protocol == kMediaProtocolDtlsSavpf ||
protocol == kMediaProtocolTcpTlsSavpf ||
protocol == kMediaProtocolUdpTlsSavpf ||
protocol == kMediaProtocolUdpTlsSavp ||
protocol == kMediaProtocolTcpTlsSavp;
}
bool IsPlainRtp(absl::string_view protocol) {
// Most-likely values first.
return protocol == kMediaProtocolSavpf || protocol == kMediaProtocolAvpf ||
protocol == kMediaProtocolSavp || protocol == kMediaProtocolAvp;
}
} // namespace cricket

View file

@ -0,0 +1,47 @@
/*
* Copyright 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef PC_MEDIA_PROTOCOL_NAMES_H_
#define PC_MEDIA_PROTOCOL_NAMES_H_
#include "absl/strings/string_view.h"
namespace cricket {
// Names or name prefixes of protocols as defined by SDP specifications,
// and generated in SDP produced by WebRTC.
extern const char kMediaProtocolSctp[];
extern const char kMediaProtocolUdpDtlsSctp[];
extern const char kMediaProtocolDtlsSavpf[];
extern const char kMediaProtocolSavpf[];
extern const char kMediaProtocolAvpf[];
// Exported for testing only
extern const char kMediaProtocolTcpDtlsSctp[];
extern const char kMediaProtocolDtlsSctp[];
// Returns true if the given media section protocol indicates use of RTP.
bool IsRtpProtocol(absl::string_view protocol);
// Returns true if the given media section protocol indicates use of SCTP.
bool IsSctpProtocol(absl::string_view protocol);
// Returns true if the given media protocol is unencrypted SCTP
bool IsPlainSctp(absl::string_view protocol);
// Returns true if the given media protocol is encrypted SCTP
bool IsDtlsSctp(absl::string_view protocol);
// Returns true if the given media protocol is unencrypted RTP
bool IsPlainRtp(absl::string_view protocol);
// Returns true if the given media protocol is encrypted RTP
bool IsDtlsRtp(absl::string_view protocol);
} // namespace cricket
#endif // PC_MEDIA_PROTOCOL_NAMES_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,366 @@
/*
* Copyright 2004 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.
*/
// Types and classes used in media session descriptions.
#ifndef PC_MEDIA_SESSION_H_
#define PC_MEDIA_SESSION_H_
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "api/crypto/crypto_options.h"
#include "api/media_types.h"
#include "api/rtp_parameters.h"
#include "api/rtp_transceiver_direction.h"
#include "media/base/media_constants.h"
#include "media/base/rid_description.h"
#include "media/base/stream_params.h"
#include "p2p/base/ice_credentials_iterator.h"
#include "p2p/base/transport_description.h"
#include "p2p/base/transport_description_factory.h"
#include "p2p/base/transport_info.h"
#include "pc/jsep_transport.h"
#include "pc/media_protocol_names.h"
#include "pc/session_description.h"
#include "pc/simulcast_description.h"
#include "rtc_base/memory/always_valid_pointer.h"
#include "rtc_base/unique_id_generator.h"
namespace webrtc {
// Forward declaration due to circular dependecy.
class ConnectionContext;
} // namespace webrtc
namespace cricket {
class MediaEngineInterface;
// Default RTCP CNAME for unit tests.
const char kDefaultRtcpCname[] = "DefaultRtcpCname";
// Options for an RtpSender contained with an media description/"m=" section.
// Note: Spec-compliant Simulcast and legacy simulcast are mutually exclusive.
struct SenderOptions {
std::string track_id;
std::vector<std::string> stream_ids;
// Use RIDs and Simulcast Layers to indicate spec-compliant Simulcast.
std::vector<RidDescription> rids;
SimulcastLayerList simulcast_layers;
// Use `num_sim_layers` to indicate legacy simulcast.
int num_sim_layers;
};
// Options for an individual media description/"m=" section.
struct MediaDescriptionOptions {
MediaDescriptionOptions(MediaType type,
const std::string& mid,
webrtc::RtpTransceiverDirection direction,
bool stopped)
: type(type), mid(mid), direction(direction), stopped(stopped) {}
// TODO(deadbeef): When we don't support Plan B, there will only be one
// sender per media description and this can be simplified.
void AddAudioSender(const std::string& track_id,
const std::vector<std::string>& stream_ids);
void AddVideoSender(const std::string& track_id,
const std::vector<std::string>& stream_ids,
const std::vector<RidDescription>& rids,
const SimulcastLayerList& simulcast_layers,
int num_sim_layers);
MediaType type;
std::string mid;
webrtc::RtpTransceiverDirection direction;
bool stopped;
TransportOptions transport_options;
// Note: There's no equivalent "RtpReceiverOptions" because only send
// stream information goes in the local descriptions.
std::vector<SenderOptions> sender_options;
std::vector<webrtc::RtpCodecCapability> codec_preferences;
std::vector<webrtc::RtpHeaderExtensionCapability> header_extensions;
private:
// Doesn't DCHECK on `type`.
void AddSenderInternal(const std::string& track_id,
const std::vector<std::string>& stream_ids,
const std::vector<RidDescription>& rids,
const SimulcastLayerList& simulcast_layers,
int num_sim_layers);
};
// Provides a mechanism for describing how m= sections should be generated.
// The m= section with index X will use media_description_options[X]. There
// must be an option for each existing section if creating an answer, or a
// subsequent offer.
struct MediaSessionOptions {
MediaSessionOptions() {}
bool has_audio() const { return HasMediaDescription(MEDIA_TYPE_AUDIO); }
bool has_video() const { return HasMediaDescription(MEDIA_TYPE_VIDEO); }
bool has_data() const { return HasMediaDescription(MEDIA_TYPE_DATA); }
bool HasMediaDescription(MediaType type) const;
bool vad_enabled = true; // When disabled, removes all CN codecs from SDP.
bool rtcp_mux_enabled = true;
bool bundle_enabled = false;
bool offer_extmap_allow_mixed = false;
bool raw_packetization_for_video = false;
std::string rtcp_cname = kDefaultRtcpCname;
webrtc::CryptoOptions crypto_options;
// List of media description options in the same order that the media
// descriptions will be generated.
std::vector<MediaDescriptionOptions> media_description_options;
std::vector<IceParameters> pooled_ice_credentials;
// Use the draft-ietf-mmusic-sctp-sdp-03 obsolete syntax for SCTP
// datachannels.
// Default is true for backwards compatibility with clients that use
// this internal interface.
bool use_obsolete_sctp_sdp = true;
};
// Creates media session descriptions according to the supplied codecs and
// other fields, as well as the supplied per-call options.
// When creating answers, performs the appropriate negotiation
// of the various fields to determine the proper result.
class MediaSessionDescriptionFactory {
public:
// This constructor automatically sets up the factory to get its configuration
// from the specified MediaEngine (when provided).
// The TransportDescriptionFactory and the UniqueRandomIdGenerator are not
// owned by MediaSessionDescriptionFactory, so they must be kept alive by the
// user of this class.
MediaSessionDescriptionFactory(cricket::MediaEngineInterface* media_engine,
bool rtx_enabled,
rtc::UniqueRandomIdGenerator* ssrc_generator,
const TransportDescriptionFactory* factory);
const AudioCodecs& audio_sendrecv_codecs() const;
const AudioCodecs& audio_send_codecs() const;
const AudioCodecs& audio_recv_codecs() const;
void set_audio_codecs(const AudioCodecs& send_codecs,
const AudioCodecs& recv_codecs);
const VideoCodecs& video_sendrecv_codecs() const;
const VideoCodecs& video_send_codecs() const;
const VideoCodecs& video_recv_codecs() const;
void set_video_codecs(const VideoCodecs& send_codecs,
const VideoCodecs& recv_codecs);
RtpHeaderExtensions filtered_rtp_header_extensions(
RtpHeaderExtensions extensions) const;
void set_enable_encrypted_rtp_header_extensions(bool enable) {
enable_encrypted_rtp_header_extensions_ = enable;
}
void set_is_unified_plan(bool is_unified_plan) {
is_unified_plan_ = is_unified_plan;
}
webrtc::RTCErrorOr<std::unique_ptr<SessionDescription>> CreateOfferOrError(
const MediaSessionOptions& options,
const SessionDescription* current_description) const;
webrtc::RTCErrorOr<std::unique_ptr<SessionDescription>> CreateAnswerOrError(
const SessionDescription* offer,
const MediaSessionOptions& options,
const SessionDescription* current_description) const;
private:
struct AudioVideoRtpHeaderExtensions {
RtpHeaderExtensions audio;
RtpHeaderExtensions video;
};
const AudioCodecs& GetAudioCodecsForOffer(
const webrtc::RtpTransceiverDirection& direction) const;
const AudioCodecs& GetAudioCodecsForAnswer(
const webrtc::RtpTransceiverDirection& offer,
const webrtc::RtpTransceiverDirection& answer) const;
const VideoCodecs& GetVideoCodecsForOffer(
const webrtc::RtpTransceiverDirection& direction) const;
const VideoCodecs& GetVideoCodecsForAnswer(
const webrtc::RtpTransceiverDirection& offer,
const webrtc::RtpTransceiverDirection& answer) const;
void GetCodecsForOffer(
const std::vector<const ContentInfo*>& current_active_contents,
AudioCodecs* audio_codecs,
VideoCodecs* video_codecs) const;
void GetCodecsForAnswer(
const std::vector<const ContentInfo*>& current_active_contents,
const SessionDescription& remote_offer,
AudioCodecs* audio_codecs,
VideoCodecs* video_codecs) const;
AudioVideoRtpHeaderExtensions GetOfferedRtpHeaderExtensionsWithIds(
const std::vector<const ContentInfo*>& current_active_contents,
bool extmap_allow_mixed,
const std::vector<MediaDescriptionOptions>& media_description_options)
const;
webrtc::RTCError AddTransportOffer(
const std::string& content_name,
const TransportOptions& transport_options,
const SessionDescription* current_desc,
SessionDescription* offer,
IceCredentialsIterator* ice_credentials) const;
std::unique_ptr<TransportDescription> CreateTransportAnswer(
const std::string& content_name,
const SessionDescription* offer_desc,
const TransportOptions& transport_options,
const SessionDescription* current_desc,
bool require_transport_attributes,
IceCredentialsIterator* ice_credentials) const;
webrtc::RTCError AddTransportAnswer(
const std::string& content_name,
const TransportDescription& transport_desc,
SessionDescription* answer_desc) const;
// Helpers for adding media contents to the SessionDescription.
webrtc::RTCError AddRtpContentForOffer(
const MediaDescriptionOptions& media_description_options,
const MediaSessionOptions& session_options,
const ContentInfo* current_content,
const SessionDescription* current_description,
const RtpHeaderExtensions& header_extensions,
const std::vector<Codec>& codecs,
StreamParamsVec* current_streams,
SessionDescription* desc,
IceCredentialsIterator* ice_credentials) const;
webrtc::RTCError AddDataContentForOffer(
const MediaDescriptionOptions& media_description_options,
const MediaSessionOptions& session_options,
const ContentInfo* current_content,
const SessionDescription* current_description,
StreamParamsVec* current_streams,
SessionDescription* desc,
IceCredentialsIterator* ice_credentials) const;
webrtc::RTCError AddUnsupportedContentForOffer(
const MediaDescriptionOptions& media_description_options,
const MediaSessionOptions& session_options,
const ContentInfo* current_content,
const SessionDescription* current_description,
SessionDescription* desc,
IceCredentialsIterator* ice_credentials) const;
webrtc::RTCError AddRtpContentForAnswer(
const MediaDescriptionOptions& media_description_options,
const MediaSessionOptions& session_options,
const ContentInfo* offer_content,
const SessionDescription* offer_description,
const ContentInfo* current_content,
const SessionDescription* current_description,
const TransportInfo* bundle_transport,
const std::vector<Codec>& codecs,
const RtpHeaderExtensions& header_extensions,
StreamParamsVec* current_streams,
SessionDescription* answer,
IceCredentialsIterator* ice_credentials) const;
webrtc::RTCError AddDataContentForAnswer(
const MediaDescriptionOptions& media_description_options,
const MediaSessionOptions& session_options,
const ContentInfo* offer_content,
const SessionDescription* offer_description,
const ContentInfo* current_content,
const SessionDescription* current_description,
const TransportInfo* bundle_transport,
StreamParamsVec* current_streams,
SessionDescription* answer,
IceCredentialsIterator* ice_credentials) const;
webrtc::RTCError AddUnsupportedContentForAnswer(
const MediaDescriptionOptions& media_description_options,
const MediaSessionOptions& session_options,
const ContentInfo* offer_content,
const SessionDescription* offer_description,
const ContentInfo* current_content,
const SessionDescription* current_description,
const TransportInfo* bundle_transport,
SessionDescription* answer,
IceCredentialsIterator* ice_credentials) const;
void ComputeAudioCodecsIntersectionAndUnion();
void ComputeVideoCodecsIntersectionAndUnion();
rtc::UniqueRandomIdGenerator* ssrc_generator() const {
return ssrc_generator_.get();
}
bool is_unified_plan_ = false;
AudioCodecs audio_send_codecs_;
AudioCodecs audio_recv_codecs_;
// Intersection of send and recv.
AudioCodecs audio_sendrecv_codecs_;
// Union of send and recv.
AudioCodecs all_audio_codecs_;
VideoCodecs video_send_codecs_;
VideoCodecs video_recv_codecs_;
// Intersection of send and recv.
VideoCodecs video_sendrecv_codecs_;
// Union of send and recv.
VideoCodecs all_video_codecs_;
// This object may or may not be owned by this class.
webrtc::AlwaysValidPointer<rtc::UniqueRandomIdGenerator> const
ssrc_generator_;
bool enable_encrypted_rtp_header_extensions_ = false;
const TransportDescriptionFactory* transport_desc_factory_;
};
// Convenience functions.
bool IsMediaContent(const ContentInfo* content);
bool IsAudioContent(const ContentInfo* content);
bool IsVideoContent(const ContentInfo* content);
bool IsDataContent(const ContentInfo* content);
bool IsUnsupportedContent(const ContentInfo* content);
const ContentInfo* GetFirstMediaContent(const ContentInfos& contents,
MediaType media_type);
const ContentInfo* GetFirstAudioContent(const ContentInfos& contents);
const ContentInfo* GetFirstVideoContent(const ContentInfos& contents);
const ContentInfo* GetFirstDataContent(const ContentInfos& contents);
const ContentInfo* GetFirstMediaContent(const SessionDescription* sdesc,
MediaType media_type);
const ContentInfo* GetFirstAudioContent(const SessionDescription* sdesc);
const ContentInfo* GetFirstVideoContent(const SessionDescription* sdesc);
const ContentInfo* GetFirstDataContent(const SessionDescription* sdesc);
const AudioContentDescription* GetFirstAudioContentDescription(
const SessionDescription* sdesc);
const VideoContentDescription* GetFirstVideoContentDescription(
const SessionDescription* sdesc);
const SctpDataContentDescription* GetFirstSctpDataContentDescription(
const SessionDescription* sdesc);
// Non-const versions of the above functions.
// Useful when modifying an existing description.
ContentInfo* GetFirstMediaContent(ContentInfos* contents, MediaType media_type);
ContentInfo* GetFirstAudioContent(ContentInfos* contents);
ContentInfo* GetFirstVideoContent(ContentInfos* contents);
ContentInfo* GetFirstDataContent(ContentInfos* contents);
ContentInfo* GetFirstMediaContent(SessionDescription* sdesc,
MediaType media_type);
ContentInfo* GetFirstAudioContent(SessionDescription* sdesc);
ContentInfo* GetFirstVideoContent(SessionDescription* sdesc);
ContentInfo* GetFirstDataContent(SessionDescription* sdesc);
AudioContentDescription* GetFirstAudioContentDescription(
SessionDescription* sdesc);
VideoContentDescription* GetFirstVideoContentDescription(
SessionDescription* sdesc);
SctpDataContentDescription* GetFirstSctpDataContentDescription(
SessionDescription* sdesc);
} // namespace cricket
#endif // PC_MEDIA_SESSION_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,96 @@
/*
* Copyright 2011 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/media_stream.h"
#include <stddef.h>
#include <utility>
#include "rtc_base/checks.h"
namespace webrtc {
template <class V>
static typename V::iterator FindTrack(V* vector, const std::string& track_id) {
typename V::iterator it = vector->begin();
for (; it != vector->end(); ++it) {
if ((*it)->id() == track_id) {
break;
}
}
return it;
}
rtc::scoped_refptr<MediaStream> MediaStream::Create(const std::string& id) {
return rtc::make_ref_counted<MediaStream>(id);
}
MediaStream::MediaStream(const std::string& id) : id_(id) {}
bool MediaStream::AddTrack(rtc::scoped_refptr<AudioTrackInterface> track) {
return AddTrack<AudioTrackVector, AudioTrackInterface>(&audio_tracks_, track);
}
bool MediaStream::AddTrack(rtc::scoped_refptr<VideoTrackInterface> track) {
return AddTrack<VideoTrackVector, VideoTrackInterface>(&video_tracks_, track);
}
bool MediaStream::RemoveTrack(rtc::scoped_refptr<AudioTrackInterface> track) {
return RemoveTrack<AudioTrackVector>(&audio_tracks_, track);
}
bool MediaStream::RemoveTrack(rtc::scoped_refptr<VideoTrackInterface> track) {
return RemoveTrack<VideoTrackVector>(&video_tracks_, track);
}
rtc::scoped_refptr<AudioTrackInterface> MediaStream::FindAudioTrack(
const std::string& track_id) {
AudioTrackVector::iterator it = FindTrack(&audio_tracks_, track_id);
if (it == audio_tracks_.end())
return nullptr;
return *it;
}
rtc::scoped_refptr<VideoTrackInterface> MediaStream::FindVideoTrack(
const std::string& track_id) {
VideoTrackVector::iterator it = FindTrack(&video_tracks_, track_id);
if (it == video_tracks_.end())
return nullptr;
return *it;
}
template <typename TrackVector, typename Track>
bool MediaStream::AddTrack(TrackVector* tracks,
rtc::scoped_refptr<Track> track) {
typename TrackVector::iterator it = FindTrack(tracks, track->id());
if (it != tracks->end())
return false;
tracks->emplace_back(std::move((track)));
FireOnChanged();
return true;
}
template <typename TrackVector>
bool MediaStream::RemoveTrack(
TrackVector* tracks,
rtc::scoped_refptr<MediaStreamTrackInterface> track) {
RTC_DCHECK(tracks != NULL);
if (!track)
return false;
typename TrackVector::iterator it = FindTrack(tracks, track->id());
if (it == tracks->end())
return false;
tracks->erase(it);
FireOnChanged();
return true;
}
} // namespace webrtc

View file

@ -0,0 +1,59 @@
/*
* Copyright 2011 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.
*/
// This file contains the implementation of MediaStreamInterface interface.
#ifndef PC_MEDIA_STREAM_H_
#define PC_MEDIA_STREAM_H_
#include <string>
#include "api/media_stream_interface.h"
#include "api/notifier.h"
#include "api/scoped_refptr.h"
namespace webrtc {
class MediaStream : public Notifier<MediaStreamInterface> {
public:
static rtc::scoped_refptr<MediaStream> Create(const std::string& id);
std::string id() const override { return id_; }
bool AddTrack(rtc::scoped_refptr<AudioTrackInterface> track) override;
bool AddTrack(rtc::scoped_refptr<VideoTrackInterface> track) override;
bool RemoveTrack(rtc::scoped_refptr<AudioTrackInterface> track) override;
bool RemoveTrack(rtc::scoped_refptr<VideoTrackInterface> track) override;
rtc::scoped_refptr<AudioTrackInterface> FindAudioTrack(
const std::string& track_id) override;
rtc::scoped_refptr<VideoTrackInterface> FindVideoTrack(
const std::string& track_id) override;
AudioTrackVector GetAudioTracks() override { return audio_tracks_; }
VideoTrackVector GetVideoTracks() override { return video_tracks_; }
protected:
explicit MediaStream(const std::string& id);
private:
template <typename TrackVector, typename Track>
bool AddTrack(TrackVector* Tracks, rtc::scoped_refptr<Track> track);
template <typename TrackVector>
bool RemoveTrack(TrackVector* Tracks,
rtc::scoped_refptr<MediaStreamTrackInterface> track);
const std::string id_;
AudioTrackVector audio_tracks_;
VideoTrackVector video_tracks_;
};
} // namespace webrtc
#endif // PC_MEDIA_STREAM_H_

View file

@ -0,0 +1,98 @@
/*
* Copyright 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/media_stream_observer.h"
#include <functional>
#include <string>
#include <utility>
#include <vector>
#include "absl/algorithm/container.h"
namespace webrtc {
MediaStreamObserver::MediaStreamObserver(
MediaStreamInterface* stream,
std::function<void(AudioTrackInterface*, MediaStreamInterface*)>
audio_track_added_callback,
std::function<void(AudioTrackInterface*, MediaStreamInterface*)>
audio_track_removed_callback,
std::function<void(VideoTrackInterface*, MediaStreamInterface*)>
video_track_added_callback,
std::function<void(VideoTrackInterface*, MediaStreamInterface*)>
video_track_removed_callback)
: stream_(stream),
cached_audio_tracks_(stream->GetAudioTracks()),
cached_video_tracks_(stream->GetVideoTracks()),
audio_track_added_callback_(std::move(audio_track_added_callback)),
audio_track_removed_callback_(std::move(audio_track_removed_callback)),
video_track_added_callback_(std::move(video_track_added_callback)),
video_track_removed_callback_(std::move(video_track_removed_callback)) {
stream_->RegisterObserver(this);
}
MediaStreamObserver::~MediaStreamObserver() {
stream_->UnregisterObserver(this);
}
void MediaStreamObserver::OnChanged() {
AudioTrackVector new_audio_tracks = stream_->GetAudioTracks();
VideoTrackVector new_video_tracks = stream_->GetVideoTracks();
// Find removed audio tracks.
for (const auto& cached_track : cached_audio_tracks_) {
if (absl::c_none_of(
new_audio_tracks,
[cached_track](const AudioTrackVector::value_type& new_track) {
return new_track->id() == cached_track->id();
})) {
audio_track_removed_callback_(cached_track.get(), stream_.get());
}
}
// Find added audio tracks.
for (const auto& new_track : new_audio_tracks) {
if (absl::c_none_of(
cached_audio_tracks_,
[new_track](const AudioTrackVector::value_type& cached_track) {
return new_track->id() == cached_track->id();
})) {
audio_track_added_callback_(new_track.get(), stream_.get());
}
}
// Find removed video tracks.
for (const auto& cached_track : cached_video_tracks_) {
if (absl::c_none_of(
new_video_tracks,
[cached_track](const VideoTrackVector::value_type& new_track) {
return new_track->id() == cached_track->id();
})) {
video_track_removed_callback_(cached_track.get(), stream_.get());
}
}
// Find added video tracks.
for (const auto& new_track : new_video_tracks) {
if (absl::c_none_of(
cached_video_tracks_,
[new_track](const VideoTrackVector::value_type& cached_track) {
return new_track->id() == cached_track->id();
})) {
video_track_added_callback_(new_track.get(), stream_.get());
}
}
cached_audio_tracks_ = new_audio_tracks;
cached_video_tracks_ = new_video_tracks;
}
} // namespace webrtc

View file

@ -0,0 +1,57 @@
/*
* Copyright 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef PC_MEDIA_STREAM_OBSERVER_H_
#define PC_MEDIA_STREAM_OBSERVER_H_
#include <functional>
#include "api/media_stream_interface.h"
#include "api/scoped_refptr.h"
namespace webrtc {
// Helper class which will listen for changes to a stream and emit the
// corresponding signals.
class MediaStreamObserver : public ObserverInterface {
public:
explicit MediaStreamObserver(
MediaStreamInterface* stream,
std::function<void(AudioTrackInterface*, MediaStreamInterface*)>
audio_track_added_callback,
std::function<void(AudioTrackInterface*, MediaStreamInterface*)>
audio_track_removed_callback,
std::function<void(VideoTrackInterface*, MediaStreamInterface*)>
video_track_added_callback,
std::function<void(VideoTrackInterface*, MediaStreamInterface*)>
video_track_removed_callback);
~MediaStreamObserver() override;
const MediaStreamInterface* stream() const { return stream_.get(); }
void OnChanged() override;
private:
rtc::scoped_refptr<MediaStreamInterface> stream_;
AudioTrackVector cached_audio_tracks_;
VideoTrackVector cached_video_tracks_;
const std::function<void(AudioTrackInterface*, MediaStreamInterface*)>
audio_track_added_callback_;
const std::function<void(AudioTrackInterface*, MediaStreamInterface*)>
audio_track_removed_callback_;
const std::function<void(VideoTrackInterface*, MediaStreamInterface*)>
video_track_added_callback_;
const std::function<void(VideoTrackInterface*, MediaStreamInterface*)>
video_track_removed_callback_;
};
} // namespace webrtc
#endif // PC_MEDIA_STREAM_OBSERVER_H_

View file

@ -0,0 +1,44 @@
/*
* Copyright 2011 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef PC_MEDIA_STREAM_PROXY_H_
#define PC_MEDIA_STREAM_PROXY_H_
#include <string>
#include "api/media_stream_interface.h"
#include "pc/proxy.h"
namespace webrtc {
// TODO(deadbeef): Move this to a .cc file. What threads methods are called on
// is an implementation detail.
BEGIN_PRIMARY_PROXY_MAP(MediaStream)
PROXY_PRIMARY_THREAD_DESTRUCTOR()
BYPASS_PROXY_CONSTMETHOD0(std::string, id)
PROXY_METHOD0(AudioTrackVector, GetAudioTracks)
PROXY_METHOD0(VideoTrackVector, GetVideoTracks)
PROXY_METHOD1(rtc::scoped_refptr<AudioTrackInterface>,
FindAudioTrack,
const std::string&)
PROXY_METHOD1(rtc::scoped_refptr<VideoTrackInterface>,
FindVideoTrack,
const std::string&)
PROXY_METHOD1(bool, AddTrack, rtc::scoped_refptr<AudioTrackInterface>)
PROXY_METHOD1(bool, AddTrack, rtc::scoped_refptr<VideoTrackInterface>)
PROXY_METHOD1(bool, RemoveTrack, rtc::scoped_refptr<AudioTrackInterface>)
PROXY_METHOD1(bool, RemoveTrack, rtc::scoped_refptr<VideoTrackInterface>)
PROXY_METHOD1(void, RegisterObserver, ObserverInterface*)
PROXY_METHOD1(void, UnregisterObserver, ObserverInterface*)
END_PROXY_MAP(MediaStream)
} // namespace webrtc
#endif // PC_MEDIA_STREAM_PROXY_H_

View file

@ -0,0 +1,65 @@
/*
* Copyright 2011 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.
*/
// This file includes proxy classes for tracks. The purpose is
// to make sure tracks are only accessed from the signaling thread.
#ifndef PC_MEDIA_STREAM_TRACK_PROXY_H_
#define PC_MEDIA_STREAM_TRACK_PROXY_H_
#include <string>
#include "api/media_stream_interface.h"
#include "pc/proxy.h"
namespace webrtc {
// TODO(deadbeef): Move this to .cc file. What threads methods are called on is
// an implementation detail.
BEGIN_PRIMARY_PROXY_MAP(AudioTrack)
PROXY_PRIMARY_THREAD_DESTRUCTOR()
BYPASS_PROXY_CONSTMETHOD0(std::string, kind)
BYPASS_PROXY_CONSTMETHOD0(std::string, id)
PROXY_CONSTMETHOD0(TrackState, state)
PROXY_CONSTMETHOD0(bool, enabled)
BYPASS_PROXY_CONSTMETHOD0(AudioSourceInterface*, GetSource)
PROXY_METHOD1(void, AddSink, AudioTrackSinkInterface*)
PROXY_METHOD1(void, RemoveSink, AudioTrackSinkInterface*)
PROXY_METHOD1(bool, GetSignalLevel, int*)
PROXY_METHOD0(rtc::scoped_refptr<AudioProcessorInterface>, GetAudioProcessor)
PROXY_METHOD1(bool, set_enabled, bool)
PROXY_METHOD1(void, RegisterObserver, ObserverInterface*)
PROXY_METHOD1(void, UnregisterObserver, ObserverInterface*)
END_PROXY_MAP(AudioTrack)
BEGIN_PROXY_MAP(VideoTrack)
PROXY_PRIMARY_THREAD_DESTRUCTOR()
BYPASS_PROXY_CONSTMETHOD0(std::string, kind)
BYPASS_PROXY_CONSTMETHOD0(std::string, id)
PROXY_SECONDARY_CONSTMETHOD0(TrackState, state)
PROXY_CONSTMETHOD0(bool, enabled)
PROXY_METHOD1(bool, set_enabled, bool)
PROXY_CONSTMETHOD0(ContentHint, content_hint)
PROXY_METHOD1(void, set_content_hint, ContentHint)
PROXY_SECONDARY_METHOD2(void,
AddOrUpdateSink,
rtc::VideoSinkInterface<VideoFrame>*,
const rtc::VideoSinkWants&)
PROXY_SECONDARY_METHOD1(void, RemoveSink, rtc::VideoSinkInterface<VideoFrame>*)
PROXY_SECONDARY_METHOD0(void, RequestRefreshFrame)
BYPASS_PROXY_CONSTMETHOD0(VideoTrackSourceInterface*, GetSource)
PROXY_METHOD1(void, RegisterObserver, ObserverInterface*)
PROXY_METHOD1(void, UnregisterObserver, ObserverInterface*)
END_PROXY_MAP(VideoTrack)
} // namespace webrtc
#endif // PC_MEDIA_STREAM_TRACK_PROXY_H_

View file

@ -0,0 +1,151 @@
/*
* Copyright 2011 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/media_stream.h"
#include <stddef.h>
#include "pc/audio_track.h"
#include "pc/test/fake_video_track_source.h"
#include "pc/video_track.h"
#include "rtc_base/thread.h"
#include "test/gmock.h"
#include "test/gtest.h"
static const char kStreamId1[] = "local_stream_1";
static const char kVideoTrackId[] = "dummy_video_cam_1";
static const char kAudioTrackId[] = "dummy_microphone_1";
using rtc::scoped_refptr;
using ::testing::Exactly;
namespace webrtc {
// Helper class to test Observer.
class MockObserver : public ObserverInterface {
public:
explicit MockObserver(NotifierInterface* notifier) : notifier_(notifier) {
notifier_->RegisterObserver(this);
}
~MockObserver() { Unregister(); }
void Unregister() {
if (notifier_) {
notifier_->UnregisterObserver(this);
notifier_ = nullptr;
}
}
MOCK_METHOD(void, OnChanged, (), (override));
private:
NotifierInterface* notifier_;
};
class MediaStreamTest : public ::testing::Test {
protected:
virtual void SetUp() {
stream_ = MediaStream::Create(kStreamId1);
ASSERT_TRUE(stream_.get() != NULL);
video_track_ = VideoTrack::Create(
kVideoTrackId, FakeVideoTrackSource::Create(), rtc::Thread::Current());
ASSERT_TRUE(video_track_.get() != NULL);
EXPECT_EQ(MediaStreamTrackInterface::kLive, video_track_->state());
audio_track_ = AudioTrack::Create(kAudioTrackId, nullptr);
ASSERT_TRUE(audio_track_.get() != NULL);
EXPECT_EQ(MediaStreamTrackInterface::kLive, audio_track_->state());
EXPECT_TRUE(stream_->AddTrack(video_track_));
EXPECT_FALSE(stream_->AddTrack(video_track_));
EXPECT_TRUE(stream_->AddTrack(audio_track_));
EXPECT_FALSE(stream_->AddTrack(audio_track_));
}
void ChangeTrack(MediaStreamTrackInterface* track) {
MockObserver observer(track);
EXPECT_CALL(observer, OnChanged()).Times(Exactly(1));
track->set_enabled(false);
EXPECT_FALSE(track->enabled());
}
rtc::AutoThread main_thread_;
scoped_refptr<MediaStreamInterface> stream_;
scoped_refptr<AudioTrackInterface> audio_track_;
scoped_refptr<VideoTrackInterface> video_track_;
};
TEST_F(MediaStreamTest, GetTrackInfo) {
ASSERT_EQ(1u, stream_->GetVideoTracks().size());
ASSERT_EQ(1u, stream_->GetAudioTracks().size());
// Verify the video track.
scoped_refptr<MediaStreamTrackInterface> video_track(
stream_->GetVideoTracks()[0]);
EXPECT_EQ(0, video_track->id().compare(kVideoTrackId));
EXPECT_TRUE(video_track->enabled());
ASSERT_EQ(1u, stream_->GetVideoTracks().size());
EXPECT_TRUE(stream_->GetVideoTracks()[0].get() == video_track.get());
EXPECT_TRUE(stream_->FindVideoTrack(video_track->id()).get() ==
video_track.get());
video_track = stream_->GetVideoTracks()[0];
EXPECT_EQ(0, video_track->id().compare(kVideoTrackId));
EXPECT_TRUE(video_track->enabled());
// Verify the audio track.
scoped_refptr<MediaStreamTrackInterface> audio_track(
stream_->GetAudioTracks()[0]);
EXPECT_EQ(0, audio_track->id().compare(kAudioTrackId));
EXPECT_TRUE(audio_track->enabled());
ASSERT_EQ(1u, stream_->GetAudioTracks().size());
EXPECT_TRUE(stream_->GetAudioTracks()[0].get() == audio_track.get());
EXPECT_TRUE(stream_->FindAudioTrack(audio_track->id()).get() ==
audio_track.get());
audio_track = stream_->GetAudioTracks()[0];
EXPECT_EQ(0, audio_track->id().compare(kAudioTrackId));
EXPECT_TRUE(audio_track->enabled());
}
TEST_F(MediaStreamTest, RemoveTrack) {
MockObserver observer(stream_.get());
EXPECT_CALL(observer, OnChanged()).Times(Exactly(2));
EXPECT_TRUE(stream_->RemoveTrack(audio_track_));
EXPECT_FALSE(stream_->RemoveTrack(audio_track_));
EXPECT_EQ(0u, stream_->GetAudioTracks().size());
EXPECT_EQ(0u, stream_->GetAudioTracks().size());
EXPECT_TRUE(stream_->RemoveTrack(video_track_));
EXPECT_FALSE(stream_->RemoveTrack(video_track_));
EXPECT_EQ(0u, stream_->GetVideoTracks().size());
EXPECT_EQ(0u, stream_->GetVideoTracks().size());
EXPECT_FALSE(stream_->RemoveTrack(rtc::scoped_refptr<AudioTrackInterface>()));
EXPECT_FALSE(stream_->RemoveTrack(rtc::scoped_refptr<VideoTrackInterface>()));
}
TEST_F(MediaStreamTest, ChangeVideoTrack) {
scoped_refptr<VideoTrackInterface> video_track(stream_->GetVideoTracks()[0]);
ChangeTrack(video_track.get());
}
TEST_F(MediaStreamTest, ChangeAudioTrack) {
scoped_refptr<AudioTrackInterface> audio_track(stream_->GetAudioTracks()[0]);
ChangeTrack(audio_track.get());
}
} // namespace webrtc

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,706 @@
/*
* 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.
*/
#ifndef PC_PEER_CONNECTION_H_
#define PC_PEER_CONNECTION_H_
#include <stdint.h>
#include <functional>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <vector>
#include "absl/types/optional.h"
#include "api/adaptation/resource.h"
#include "api/async_dns_resolver.h"
#include "api/candidate.h"
#include "api/crypto/crypto_options.h"
#include "api/data_channel_interface.h"
#include "api/dtls_transport_interface.h"
#include "api/environment/environment.h"
#include "api/field_trials_view.h"
#include "api/ice_transport_interface.h"
#include "api/jsep.h"
#include "api/media_stream_interface.h"
#include "api/media_types.h"
#include "api/peer_connection_interface.h"
#include "api/rtc_error.h"
#include "api/rtc_event_log/rtc_event_log.h"
#include "api/rtc_event_log_output.h"
#include "api/rtp_receiver_interface.h"
#include "api/rtp_sender_interface.h"
#include "api/rtp_transceiver_interface.h"
#include "api/scoped_refptr.h"
#include "api/sctp_transport_interface.h"
#include "api/sequence_checker.h"
#include "api/set_local_description_observer_interface.h"
#include "api/set_remote_description_observer_interface.h"
#include "api/stats/rtc_stats_collector_callback.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "api/transport/bitrate_settings.h"
#include "api/transport/data_channel_transport_interface.h"
#include "api/transport/enums.h"
#include "api/turn_customizer.h"
#include "call/call.h"
#include "modules/rtp_rtcp/source/rtp_packet_received.h"
#include "p2p/base/ice_transport_internal.h"
#include "p2p/base/port.h"
#include "p2p/base/port_allocator.h"
#include "p2p/base/transport_description.h"
#include "pc/channel_interface.h"
#include "pc/connection_context.h"
#include "pc/data_channel_controller.h"
#include "pc/data_channel_utils.h"
#include "pc/dtls_transport.h"
#include "pc/jsep_transport_controller.h"
#include "pc/legacy_stats_collector.h"
#include "pc/peer_connection_internal.h"
#include "pc/peer_connection_message_handler.h"
#include "pc/rtc_stats_collector.h"
#include "pc/rtp_transceiver.h"
#include "pc/rtp_transmission_manager.h"
#include "pc/rtp_transport_internal.h"
#include "pc/sctp_data_channel.h"
#include "pc/sdp_offer_answer.h"
#include "pc/session_description.h"
#include "pc/transceiver_list.h"
#include "pc/transport_stats.h"
#include "pc/usage_pattern.h"
#include "rtc_base/checks.h"
#include "rtc_base/copy_on_write_buffer.h"
#include "rtc_base/rtc_certificate.h"
#include "rtc_base/ssl_certificate.h"
#include "rtc_base/ssl_stream_adapter.h"
#include "rtc_base/thread.h"
#include "rtc_base/thread_annotations.h"
#include "rtc_base/weak_ptr.h"
namespace webrtc {
// PeerConnection is the implementation of the PeerConnection object as defined
// by the PeerConnectionInterface API surface.
// The class currently is solely responsible for the following:
// - Managing the session state machine (signaling state).
// - Creating and initializing lower-level objects, like PortAllocator and
// BaseChannels.
// - Owning and managing the life cycle of the RtpSender/RtpReceiver and track
// objects.
// - Tracking the current and pending local/remote session descriptions.
// The class currently is jointly responsible for the following:
// - Parsing and interpreting SDP.
// - Generating offers and answers based on the current state.
// - The ICE state machine.
// - Generating stats.
class PeerConnection : public PeerConnectionInternal,
public JsepTransportController::Observer {
public:
// Creates a PeerConnection and initializes it with the given values.
// If the initialization fails, the function releases the PeerConnection
// and returns nullptr.
//
// Note that the function takes ownership of dependencies, and will
// either use them or release them, whether it succeeds or fails.
static RTCErrorOr<rtc::scoped_refptr<PeerConnection>> Create(
const Environment& env,
rtc::scoped_refptr<ConnectionContext> context,
const PeerConnectionFactoryInterface::Options& options,
std::unique_ptr<Call> call,
const PeerConnectionInterface::RTCConfiguration& configuration,
PeerConnectionDependencies dependencies);
rtc::scoped_refptr<StreamCollectionInterface> local_streams() override;
rtc::scoped_refptr<StreamCollectionInterface> remote_streams() override;
bool AddStream(MediaStreamInterface* local_stream) override;
void RemoveStream(MediaStreamInterface* local_stream) override;
RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> AddTrack(
rtc::scoped_refptr<MediaStreamTrackInterface> track,
const std::vector<std::string>& stream_ids) override;
RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> AddTrack(
rtc::scoped_refptr<MediaStreamTrackInterface> track,
const std::vector<std::string>& stream_ids,
const std::vector<RtpEncodingParameters>& init_send_encodings) override;
RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> AddTrack(
rtc::scoped_refptr<MediaStreamTrackInterface> track,
const std::vector<std::string>& stream_ids,
const std::vector<RtpEncodingParameters>* init_send_encodings);
RTCError RemoveTrackOrError(
rtc::scoped_refptr<RtpSenderInterface> sender) override;
RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> AddTransceiver(
rtc::scoped_refptr<MediaStreamTrackInterface> track) override;
RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> AddTransceiver(
rtc::scoped_refptr<MediaStreamTrackInterface> track,
const RtpTransceiverInit& init) override;
RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> AddTransceiver(
cricket::MediaType media_type) override;
RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> AddTransceiver(
cricket::MediaType media_type,
const RtpTransceiverInit& init) override;
rtc::scoped_refptr<RtpSenderInterface> CreateSender(
const std::string& kind,
const std::string& stream_id) override;
std::vector<rtc::scoped_refptr<RtpSenderInterface>> GetSenders()
const override;
std::vector<rtc::scoped_refptr<RtpReceiverInterface>> GetReceivers()
const override;
std::vector<rtc::scoped_refptr<RtpTransceiverInterface>> GetTransceivers()
const override;
RTCErrorOr<rtc::scoped_refptr<DataChannelInterface>> CreateDataChannelOrError(
const std::string& label,
const DataChannelInit* config) override;
// WARNING: LEGACY. See peerconnectioninterface.h
bool GetStats(StatsObserver* observer,
MediaStreamTrackInterface* track,
StatsOutputLevel level) override;
// Spec-complaint GetStats(). See peerconnectioninterface.h
void GetStats(RTCStatsCollectorCallback* callback) override;
void GetStats(
rtc::scoped_refptr<RtpSenderInterface> selector,
rtc::scoped_refptr<RTCStatsCollectorCallback> callback) override;
void GetStats(
rtc::scoped_refptr<RtpReceiverInterface> selector,
rtc::scoped_refptr<RTCStatsCollectorCallback> callback) override;
void ClearStatsCache() override;
SignalingState signaling_state() override;
IceConnectionState ice_connection_state() override;
IceConnectionState ice_connection_state_internal() override {
return ice_connection_state();
}
IceConnectionState standardized_ice_connection_state() override;
PeerConnectionState peer_connection_state() override;
IceGatheringState ice_gathering_state() override;
absl::optional<bool> can_trickle_ice_candidates() override;
const SessionDescriptionInterface* local_description() const override;
const SessionDescriptionInterface* remote_description() const override;
const SessionDescriptionInterface* current_local_description() const override;
const SessionDescriptionInterface* current_remote_description()
const override;
const SessionDescriptionInterface* pending_local_description() const override;
const SessionDescriptionInterface* pending_remote_description()
const override;
void RestartIce() override;
// JSEP01
void CreateOffer(CreateSessionDescriptionObserver* observer,
const RTCOfferAnswerOptions& options) override;
void CreateAnswer(CreateSessionDescriptionObserver* observer,
const RTCOfferAnswerOptions& options) override;
void SetLocalDescription(
std::unique_ptr<SessionDescriptionInterface> desc,
rtc::scoped_refptr<SetLocalDescriptionObserverInterface> observer)
override;
void SetLocalDescription(
rtc::scoped_refptr<SetLocalDescriptionObserverInterface> observer)
override;
// TODO(https://crbug.com/webrtc/11798): Delete these methods in favor of the
// ones taking SetLocalDescriptionObserverInterface as argument.
void SetLocalDescription(SetSessionDescriptionObserver* observer,
SessionDescriptionInterface* desc) override;
void SetLocalDescription(SetSessionDescriptionObserver* observer) override;
void SetRemoteDescription(
std::unique_ptr<SessionDescriptionInterface> desc,
rtc::scoped_refptr<SetRemoteDescriptionObserverInterface> observer)
override;
// TODO(https://crbug.com/webrtc/11798): Delete this methods in favor of the
// ones taking SetRemoteDescriptionObserverInterface as argument.
void SetRemoteDescription(SetSessionDescriptionObserver* observer,
SessionDescriptionInterface* desc) override;
PeerConnectionInterface::RTCConfiguration GetConfiguration() override;
RTCError SetConfiguration(
const PeerConnectionInterface::RTCConfiguration& configuration) override;
bool AddIceCandidate(const IceCandidateInterface* candidate) override;
void AddIceCandidate(std::unique_ptr<IceCandidateInterface> candidate,
std::function<void(RTCError)> callback) override;
bool RemoveIceCandidates(
const std::vector<cricket::Candidate>& candidates) override;
RTCError SetBitrate(const BitrateSettings& bitrate) override;
void ReconfigureBandwidthEstimation(
const BandwidthEstimationSettings& settings) override;
void SetAudioPlayout(bool playout) override;
void SetAudioRecording(bool recording) override;
rtc::scoped_refptr<DtlsTransportInterface> LookupDtlsTransportByMid(
const std::string& mid) override;
rtc::scoped_refptr<DtlsTransport> LookupDtlsTransportByMidInternal(
const std::string& mid);
rtc::scoped_refptr<SctpTransportInterface> GetSctpTransport() const override;
void AddAdaptationResource(rtc::scoped_refptr<Resource> resource) override;
bool StartRtcEventLog(std::unique_ptr<RtcEventLogOutput> output,
int64_t output_period_ms) override;
bool StartRtcEventLog(std::unique_ptr<RtcEventLogOutput> output) override;
void StopRtcEventLog() override;
void Close() override;
rtc::Thread* signaling_thread() const final {
return context_->signaling_thread();
}
rtc::Thread* network_thread() const final {
return context_->network_thread();
}
rtc::Thread* worker_thread() const final { return context_->worker_thread(); }
std::string session_id() const override { return session_id_; }
bool initial_offerer() const override {
RTC_DCHECK_RUN_ON(signaling_thread());
return sdp_handler_->initial_offerer();
}
std::vector<
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>>
GetTransceiversInternal() const override {
RTC_DCHECK_RUN_ON(signaling_thread());
if (!ConfiguredForMedia()) {
return {};
}
return rtp_manager()->transceivers()->List();
}
std::vector<DataChannelStats> GetDataChannelStats() const override;
absl::optional<std::string> sctp_transport_name() const override;
absl::optional<std::string> sctp_mid() const override;
cricket::CandidateStatsList GetPooledCandidateStats() const override;
std::map<std::string, cricket::TransportStats> GetTransportStatsByNames(
const std::set<std::string>& transport_names) override;
Call::Stats GetCallStats() override;
absl::optional<AudioDeviceModule::Stats> GetAudioDeviceStats() override;
bool GetLocalCertificate(
const std::string& transport_name,
rtc::scoped_refptr<rtc::RTCCertificate>* certificate) override;
std::unique_ptr<rtc::SSLCertChain> GetRemoteSSLCertChain(
const std::string& transport_name) override;
bool IceRestartPending(const std::string& content_name) const override;
bool NeedsIceRestart(const std::string& content_name) const override;
bool GetSslRole(const std::string& content_name, rtc::SSLRole* role) override;
// Functions needed by DataChannelController
void NoteDataAddedEvent() override { NoteUsageEvent(UsageEvent::DATA_ADDED); }
// Returns the observer. Will crash on CHECK if the observer is removed.
PeerConnectionObserver* Observer() const override;
bool IsClosed() const override {
RTC_DCHECK_RUN_ON(signaling_thread());
return !sdp_handler_ ||
sdp_handler_->signaling_state() == PeerConnectionInterface::kClosed;
}
// Get current SSL role used by SCTP's underlying transport.
absl::optional<rtc::SSLRole> GetSctpSslRole_n() override;
void OnSctpDataChannelStateChanged(
int channel_id,
DataChannelInterface::DataState state) override;
bool ShouldFireNegotiationNeededEvent(uint32_t event_id) override;
// Functions needed by SdpOfferAnswerHandler
LegacyStatsCollector* legacy_stats() override {
RTC_DCHECK_RUN_ON(signaling_thread());
return legacy_stats_.get();
}
DataChannelController* data_channel_controller() override {
RTC_DCHECK_RUN_ON(signaling_thread());
return &data_channel_controller_;
}
bool dtls_enabled() const override {
RTC_DCHECK_RUN_ON(signaling_thread());
return dtls_enabled_;
}
const PeerConnectionInterface::RTCConfiguration* configuration()
const override {
RTC_DCHECK_RUN_ON(signaling_thread());
return &configuration_;
}
PeerConnectionMessageHandler* message_handler() override {
RTC_DCHECK_RUN_ON(signaling_thread());
return &message_handler_;
}
RtpTransmissionManager* rtp_manager() override { return rtp_manager_.get(); }
const RtpTransmissionManager* rtp_manager() const override {
return rtp_manager_.get();
}
JsepTransportController* transport_controller_s() override {
RTC_DCHECK_RUN_ON(signaling_thread());
return transport_controller_copy_;
}
JsepTransportController* transport_controller_n() override {
RTC_DCHECK_RUN_ON(network_thread());
return transport_controller_.get();
}
cricket::PortAllocator* port_allocator() override {
return port_allocator_.get();
}
Call* call_ptr() override { return call_ptr_; }
ConnectionContext* context() { return context_.get(); }
const PeerConnectionFactoryInterface::Options* options() const override {
return &options_;
}
void SetIceConnectionState(IceConnectionState new_state) override;
void NoteUsageEvent(UsageEvent event) override;
// Asynchronously adds a remote candidate on the network thread.
void AddRemoteCandidate(const std::string& mid,
const cricket::Candidate& candidate) override;
// Report the UMA metric BundleUsage for the given remote description.
void ReportSdpBundleUsage(
const SessionDescriptionInterface& remote_description) override;
// Report several UMA metrics on establishing the connection.
void ReportFirstConnectUsageMetrics() RTC_RUN_ON(signaling_thread());
// Returns true if the PeerConnection is configured to use Unified Plan
// semantics for creating offers/answers and setting local/remote
// descriptions. If this is true the RtpTransceiver API will also be available
// to the user. If this is false, Plan B semantics are assumed.
// TODO(bugs.webrtc.org/8530): Flip the default to be Unified Plan once
// sufficient time has passed.
bool IsUnifiedPlan() const override {
RTC_DCHECK_RUN_ON(signaling_thread());
return is_unified_plan_;
}
bool ValidateBundleSettings(
const cricket::SessionDescription* desc,
const std::map<std::string, const cricket::ContentGroup*>&
bundle_groups_by_mid) override;
bool CreateDataChannelTransport(absl::string_view mid) override;
void DestroyDataChannelTransport(RTCError error) override;
// Asynchronously calls SctpTransport::Start() on the network thread for
// `sctp_mid()` if set. Called as part of setting the local description.
void StartSctpTransport(int local_port,
int remote_port,
int max_message_size) override;
// Returns the CryptoOptions for this PeerConnection. This will always
// return the RTCConfiguration.crypto_options if set and will only default
// back to the PeerConnectionFactory settings if nothing was set.
CryptoOptions GetCryptoOptions() override;
// Internal implementation for AddTransceiver family of methods. If
// `fire_callback` is set, fires OnRenegotiationNeeded callback if successful.
RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> AddTransceiver(
cricket::MediaType media_type,
rtc::scoped_refptr<MediaStreamTrackInterface> track,
const RtpTransceiverInit& init,
bool fire_callback = true) override;
// Returns true if SRTP (either using DTLS-SRTP or SDES) is required by
// this session.
bool SrtpRequired() const override;
absl::optional<std::string> SetupDataChannelTransport_n(absl::string_view mid)
RTC_RUN_ON(network_thread());
void TeardownDataChannelTransport_n(RTCError error)
RTC_RUN_ON(network_thread());
const FieldTrialsView& trials() const override { return env_.field_trials(); }
bool ConfiguredForMedia() const;
// Functions made public for testing.
void ReturnHistogramVeryQuicklyForTesting() {
RTC_DCHECK_RUN_ON(signaling_thread());
return_histogram_very_quickly_ = true;
}
void RequestUsagePatternReportForTesting();
protected:
// Available for rtc::scoped_refptr creation
PeerConnection(const Environment& env,
rtc::scoped_refptr<ConnectionContext> context,
const PeerConnectionFactoryInterface::Options& options,
bool is_unified_plan,
std::unique_ptr<Call> call,
PeerConnectionDependencies& dependencies,
bool dtls_enabled);
~PeerConnection() override;
private:
RTCError Initialize(
const PeerConnectionInterface::RTCConfiguration& configuration,
PeerConnectionDependencies dependencies);
JsepTransportController* InitializeTransportController_n(
const RTCConfiguration& configuration,
const PeerConnectionDependencies& dependencies)
RTC_RUN_ON(network_thread());
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
FindTransceiverBySender(rtc::scoped_refptr<RtpSenderInterface> sender)
RTC_RUN_ON(signaling_thread());
void SetStandardizedIceConnectionState(
PeerConnectionInterface::IceConnectionState new_state)
RTC_RUN_ON(signaling_thread());
void SetConnectionState(
PeerConnectionInterface::PeerConnectionState new_state)
RTC_RUN_ON(signaling_thread());
// Called any time the IceGatheringState changes.
void OnIceGatheringChange(IceGatheringState new_state)
RTC_RUN_ON(signaling_thread());
// New ICE candidate has been gathered.
void OnIceCandidate(std::unique_ptr<IceCandidateInterface> candidate)
RTC_RUN_ON(signaling_thread());
// Gathering of an ICE candidate failed.
void OnIceCandidateError(const std::string& address,
int port,
const std::string& url,
int error_code,
const std::string& error_text)
RTC_RUN_ON(signaling_thread());
// Some local ICE candidates have been removed.
void OnIceCandidatesRemoved(const std::vector<cricket::Candidate>& candidates)
RTC_RUN_ON(signaling_thread());
void OnSelectedCandidatePairChanged(
const cricket::CandidatePairChangeEvent& event)
RTC_RUN_ON(signaling_thread());
void OnNegotiationNeeded();
// Called when first configuring the port allocator.
struct InitializePortAllocatorResult {
bool enable_ipv6;
};
InitializePortAllocatorResult InitializePortAllocator_n(
const cricket::ServerAddresses& stun_servers,
const std::vector<cricket::RelayServerConfig>& turn_servers,
const RTCConfiguration& configuration);
// Called when SetConfiguration is called to apply the supported subset
// of the configuration on the network thread.
bool ReconfigurePortAllocator_n(
const cricket::ServerAddresses& stun_servers,
const std::vector<cricket::RelayServerConfig>& turn_servers,
IceTransportsType type,
int candidate_pool_size,
PortPrunePolicy turn_port_prune_policy,
TurnCustomizer* turn_customizer,
absl::optional<int> stun_candidate_keepalive_interval,
bool have_local_description);
// Starts output of an RTC event log to the given output object.
// This function should only be called from the worker thread.
bool StartRtcEventLog_w(std::unique_ptr<RtcEventLogOutput> output,
int64_t output_period_ms);
// Stops recording an RTC event log.
// This function should only be called from the worker thread.
void StopRtcEventLog_w();
// Returns true and the TransportInfo of the given `content_name`
// from `description`. Returns false if it's not available.
static bool GetTransportDescription(
const cricket::SessionDescription* description,
const std::string& content_name,
cricket::TransportDescription* info);
// Returns the media index for a local ice candidate given the content name.
// Returns false if the local session description does not have a media
// content called `content_name`.
bool GetLocalCandidateMediaIndex(const std::string& content_name,
int* sdp_mline_index)
RTC_RUN_ON(signaling_thread());
// JsepTransportController signal handlers.
void OnTransportControllerConnectionState(cricket::IceConnectionState state)
RTC_RUN_ON(signaling_thread());
void OnTransportControllerGatheringState(cricket::IceGatheringState state)
RTC_RUN_ON(signaling_thread());
void OnTransportControllerCandidatesGathered(
const std::string& transport_name,
const std::vector<cricket::Candidate>& candidates)
RTC_RUN_ON(signaling_thread());
void OnTransportControllerCandidateError(
const cricket::IceCandidateErrorEvent& event)
RTC_RUN_ON(signaling_thread());
void OnTransportControllerCandidatesRemoved(
const std::vector<cricket::Candidate>& candidates)
RTC_RUN_ON(signaling_thread());
void OnTransportControllerCandidateChanged(
const cricket::CandidatePairChangeEvent& event)
RTC_RUN_ON(signaling_thread());
void OnTransportControllerDtlsHandshakeError(rtc::SSLHandshakeError error);
// Invoked when TransportController connection completion is signaled.
// Reports stats for all transports in use.
void ReportTransportStats(std::vector<RtpTransceiverProxyRefPtr> transceivers)
RTC_RUN_ON(network_thread());
// Gather the usage of IPv4/IPv6 as best connection.
static void ReportBestConnectionState(const cricket::TransportStats& stats);
static void ReportNegotiatedCiphers(
bool dtls_enabled,
const cricket::TransportStats& stats,
const std::set<cricket::MediaType>& media_types);
void ReportIceCandidateCollected(const cricket::Candidate& candidate)
RTC_RUN_ON(signaling_thread());
void ReportUsagePattern() const RTC_RUN_ON(signaling_thread());
void ReportRemoteIceCandidateAdded(const cricket::Candidate& candidate);
// JsepTransportController::Observer override.
//
// Called by `transport_controller_` when processing transport information
// from a session description, and the mapping from m= sections to transports
// changed (as a result of BUNDLE negotiation, or m= sections being
// rejected).
bool OnTransportChanged(
const std::string& mid,
RtpTransportInternal* rtp_transport,
rtc::scoped_refptr<DtlsTransport> dtls_transport,
DataChannelTransportInterface* data_channel_transport) override;
void SetSctpTransportName(std::string sctp_transport_name);
std::function<void(const rtc::CopyOnWriteBuffer& packet,
int64_t packet_time_us)>
InitializeRtcpCallback();
std::function<void(const RtpPacketReceived& parsed_packet)>
InitializeUnDemuxablePacketHandler();
const Environment env_;
const rtc::scoped_refptr<ConnectionContext> context_;
const PeerConnectionFactoryInterface::Options options_;
PeerConnectionObserver* observer_ RTC_GUARDED_BY(signaling_thread()) =
nullptr;
const bool is_unified_plan_;
IceConnectionState ice_connection_state_ RTC_GUARDED_BY(signaling_thread()) =
kIceConnectionNew;
PeerConnectionInterface::IceConnectionState standardized_ice_connection_state_
RTC_GUARDED_BY(signaling_thread()) = kIceConnectionNew;
PeerConnectionInterface::PeerConnectionState connection_state_
RTC_GUARDED_BY(signaling_thread()) = PeerConnectionState::kNew;
IceGatheringState ice_gathering_state_ RTC_GUARDED_BY(signaling_thread()) =
kIceGatheringNew;
PeerConnectionInterface::RTCConfiguration configuration_
RTC_GUARDED_BY(signaling_thread());
const std::unique_ptr<AsyncDnsResolverFactoryInterface>
async_dns_resolver_factory_;
std::unique_ptr<cricket::PortAllocator>
port_allocator_; // TODO(bugs.webrtc.org/9987): Accessed on both
// signaling and network thread.
const std::unique_ptr<IceTransportFactory>
ice_transport_factory_; // TODO(bugs.webrtc.org/9987): Accessed on the
// signaling thread but the underlying raw
// pointer is given to
// `jsep_transport_controller_` and used on the
// network thread.
const std::unique_ptr<rtc::SSLCertificateVerifier> tls_cert_verifier_
RTC_GUARDED_BY(network_thread());
// The unique_ptr belongs to the worker thread, but the Call object manages
// its own thread safety.
std::unique_ptr<Call> call_ RTC_GUARDED_BY(worker_thread());
ScopedTaskSafety signaling_thread_safety_;
rtc::scoped_refptr<PendingTaskSafetyFlag> network_thread_safety_;
rtc::scoped_refptr<PendingTaskSafetyFlag> worker_thread_safety_;
// Points to the same thing as `call_`. Since it's const, we may read the
// pointer from any thread.
// TODO(bugs.webrtc.org/11992): Remove this workaround (and potential dangling
// pointer).
Call* const call_ptr_;
std::unique_ptr<LegacyStatsCollector> legacy_stats_
RTC_GUARDED_BY(signaling_thread()); // A pointer is passed to senders_
rtc::scoped_refptr<RTCStatsCollector> stats_collector_
RTC_GUARDED_BY(signaling_thread());
const std::string session_id_;
// The transport controller is set and used on the network thread.
// Some functions pass the value of the transport_controller_ pointer
// around as arguments while running on the signaling thread; these
// use the transport_controller_copy.
std::unique_ptr<JsepTransportController> transport_controller_
RTC_GUARDED_BY(network_thread());
JsepTransportController* transport_controller_copy_
RTC_GUARDED_BY(signaling_thread()) = nullptr;
// `sctp_mid_` is the content name (MID) in SDP.
// Note: this is used as the data channel MID by both SCTP and data channel
// transports. It is set when either transport is initialized and unset when
// both transports are deleted.
// There is one copy on the signaling thread and another copy on the
// networking thread. Changes are always initiated from the signaling
// thread, but applied first on the networking thread via an invoke().
absl::optional<std::string> sctp_mid_s_ RTC_GUARDED_BY(signaling_thread());
absl::optional<std::string> sctp_mid_n_ RTC_GUARDED_BY(network_thread());
std::string sctp_transport_name_s_ RTC_GUARDED_BY(signaling_thread());
// The machinery for handling offers and answers. Const after initialization.
std::unique_ptr<SdpOfferAnswerHandler> sdp_handler_
RTC_GUARDED_BY(signaling_thread()) RTC_PT_GUARDED_BY(signaling_thread());
const bool dtls_enabled_;
UsagePattern usage_pattern_ RTC_GUARDED_BY(signaling_thread());
bool return_histogram_very_quickly_ RTC_GUARDED_BY(signaling_thread()) =
false;
// The DataChannelController is accessed from both the signaling thread
// and networking thread. It is a thread-aware object.
DataChannelController data_channel_controller_;
// Machinery for handling messages posted to oneself
PeerConnectionMessageHandler message_handler_
RTC_GUARDED_BY(signaling_thread());
// Administration of senders, receivers and transceivers
// Accessed on both signaling and network thread. Const after Initialize().
std::unique_ptr<RtpTransmissionManager> rtp_manager_;
// Did the connectionState ever change to `connected`?
// Used to gather metrics only the first such state change.
bool was_ever_connected_ RTC_GUARDED_BY(signaling_thread()) = false;
// This variable needs to be the last one in the class.
rtc::WeakPtrFactory<PeerConnection> weak_factory_;
};
} // namespace webrtc
#endif // PC_PEER_CONNECTION_H_

View file

@ -0,0 +1,172 @@
/*
* Copyright 2020 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <stdint.h>
#include <memory>
#include <string>
#include "absl/types/optional.h"
#include "api/adaptation/resource.h"
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
#include "api/media_stream_interface.h"
#include "api/peer_connection_interface.h"
#include "api/rtc_error.h"
#include "api/rtp_parameters.h"
#include "api/rtp_sender_interface.h"
#include "api/scoped_refptr.h"
#include "api/video/video_source_interface.h"
#include "call/adaptation/test/fake_resource.h"
#include "pc/test/fake_periodic_video_source.h"
#include "pc/test/fake_periodic_video_track_source.h"
#include "pc/test/peer_connection_test_wrapper.h"
#include "rtc_base/checks.h"
#include "rtc_base/gunit.h"
#include "rtc_base/thread.h"
#include "rtc_base/time_utils.h"
#include "rtc_base/virtual_socket_server.h"
#include "test/gtest.h"
namespace webrtc {
const int64_t kDefaultTimeoutMs = 5000;
struct TrackWithPeriodicSource {
rtc::scoped_refptr<VideoTrackInterface> track;
rtc::scoped_refptr<FakePeriodicVideoTrackSource> periodic_track_source;
};
// Performs an O/A exchange and waits until the signaling state is stable again.
void Negotiate(rtc::scoped_refptr<PeerConnectionTestWrapper> caller,
rtc::scoped_refptr<PeerConnectionTestWrapper> callee) {
// Wire up callbacks and listeners such that a full O/A is performed in
// response to CreateOffer().
PeerConnectionTestWrapper::Connect(caller.get(), callee.get());
caller->CreateOffer(PeerConnectionInterface::RTCOfferAnswerOptions());
caller->WaitForNegotiation();
}
TrackWithPeriodicSource CreateTrackWithPeriodicSource(
rtc::scoped_refptr<PeerConnectionFactoryInterface> factory) {
FakePeriodicVideoSource::Config periodic_track_source_config;
periodic_track_source_config.frame_interval_ms = 100;
periodic_track_source_config.timestamp_offset_ms = rtc::TimeMillis();
rtc::scoped_refptr<FakePeriodicVideoTrackSource> periodic_track_source =
rtc::make_ref_counted<FakePeriodicVideoTrackSource>(
periodic_track_source_config, /* remote */ false);
TrackWithPeriodicSource track_with_source;
track_with_source.track =
factory->CreateVideoTrack(periodic_track_source, "PeriodicTrack");
track_with_source.periodic_track_source = periodic_track_source;
return track_with_source;
}
// Triggers overuse and obtains VideoSinkWants. Adaptation processing happens in
// parallel and this function makes no guarantee that the returnd VideoSinkWants
// have yet to reflect the overuse signal. Used together with EXPECT_TRUE_WAIT
// to "spam overuse until a change is observed".
rtc::VideoSinkWants TriggerOveruseAndGetSinkWants(
rtc::scoped_refptr<FakeResource> fake_resource,
const FakePeriodicVideoSource& source) {
fake_resource->SetUsageState(ResourceUsageState::kOveruse);
return source.wants();
}
class PeerConnectionAdaptationIntegrationTest : public ::testing::Test {
public:
PeerConnectionAdaptationIntegrationTest()
: virtual_socket_server_(),
network_thread_(new rtc::Thread(&virtual_socket_server_)),
worker_thread_(rtc::Thread::Create()) {
RTC_CHECK(network_thread_->Start());
RTC_CHECK(worker_thread_->Start());
}
rtc::scoped_refptr<PeerConnectionTestWrapper> CreatePcWrapper(
const char* name) {
rtc::scoped_refptr<PeerConnectionTestWrapper> pc_wrapper =
rtc::make_ref_counted<PeerConnectionTestWrapper>(
name, &virtual_socket_server_, network_thread_.get(),
worker_thread_.get());
PeerConnectionInterface::RTCConfiguration config;
config.sdp_semantics = SdpSemantics::kUnifiedPlan;
EXPECT_TRUE(pc_wrapper->CreatePc(config, CreateBuiltinAudioEncoderFactory(),
CreateBuiltinAudioDecoderFactory()));
return pc_wrapper;
}
protected:
rtc::VirtualSocketServer virtual_socket_server_;
std::unique_ptr<rtc::Thread> network_thread_;
std::unique_ptr<rtc::Thread> worker_thread_;
};
TEST_F(PeerConnectionAdaptationIntegrationTest,
ResouceInjectedAfterNegotiationCausesReductionInResolution) {
auto caller_wrapper = CreatePcWrapper("caller");
auto caller = caller_wrapper->pc();
auto callee_wrapper = CreatePcWrapper("callee");
// Adding a track and negotiating ensures that a VideoSendStream exists.
TrackWithPeriodicSource track_with_source =
CreateTrackWithPeriodicSource(caller_wrapper->pc_factory());
auto sender = caller->AddTrack(track_with_source.track, {}).value();
Negotiate(caller_wrapper, callee_wrapper);
// Prefer degrading resolution.
auto parameters = sender->GetParameters();
parameters.degradation_preference = DegradationPreference::MAINTAIN_FRAMERATE;
sender->SetParameters(parameters);
const auto& source =
track_with_source.periodic_track_source->fake_periodic_source();
int pixel_count_before_overuse = source.wants().max_pixel_count;
// Inject a fake resource and spam kOveruse until resolution becomes limited.
auto fake_resource = FakeResource::Create("FakeResource");
caller->AddAdaptationResource(fake_resource);
EXPECT_TRUE_WAIT(
TriggerOveruseAndGetSinkWants(fake_resource, source).max_pixel_count <
pixel_count_before_overuse,
kDefaultTimeoutMs);
}
TEST_F(PeerConnectionAdaptationIntegrationTest,
ResouceInjectedBeforeNegotiationCausesReductionInResolution) {
auto caller_wrapper = CreatePcWrapper("caller");
auto caller = caller_wrapper->pc();
auto callee_wrapper = CreatePcWrapper("callee");
// Inject a fake resource before adding any tracks or negotiating.
auto fake_resource = FakeResource::Create("FakeResource");
caller->AddAdaptationResource(fake_resource);
// Adding a track and negotiating ensures that a VideoSendStream exists.
TrackWithPeriodicSource track_with_source =
CreateTrackWithPeriodicSource(caller_wrapper->pc_factory());
auto sender = caller->AddTrack(track_with_source.track, {}).value();
Negotiate(caller_wrapper, callee_wrapper);
// Prefer degrading resolution.
auto parameters = sender->GetParameters();
parameters.degradation_preference = DegradationPreference::MAINTAIN_FRAMERATE;
sender->SetParameters(parameters);
const auto& source =
track_with_source.periodic_track_source->fake_periodic_source();
int pixel_count_before_overuse = source.wants().max_pixel_count;
// Spam kOveruse until resolution becomes limited.
EXPECT_TRUE_WAIT(
TriggerOveruseAndGetSinkWants(fake_resource, source).max_pixel_count <
pixel_count_before_overuse,
kDefaultTimeoutMs);
}
} // namespace webrtc

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,526 @@
/*
* Copyright 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <stddef.h>
#include <memory>
#include <ostream>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
#include "absl/types/optional.h"
#include "api/audio/audio_mixer.h"
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
#include "api/create_peerconnection_factory.h"
#include "api/crypto/crypto_options.h"
#include "api/jsep.h"
#include "api/peer_connection_interface.h"
#include "api/scoped_refptr.h"
#include "api/video_codecs/video_decoder_factory_template.h"
#include "api/video_codecs/video_decoder_factory_template_dav1d_adapter.h"
#include "api/video_codecs/video_decoder_factory_template_libvpx_vp8_adapter.h"
#include "api/video_codecs/video_decoder_factory_template_libvpx_vp9_adapter.h"
#include "api/video_codecs/video_decoder_factory_template_open_h264_adapter.h"
#include "api/video_codecs/video_encoder_factory_template.h"
#include "api/video_codecs/video_encoder_factory_template_libaom_av1_adapter.h"
#include "api/video_codecs/video_encoder_factory_template_libvpx_vp8_adapter.h"
#include "api/video_codecs/video_encoder_factory_template_libvpx_vp9_adapter.h"
#include "api/video_codecs/video_encoder_factory_template_open_h264_adapter.h"
#include "modules/audio_device/include/audio_device.h"
#include "modules/audio_processing/include/audio_processing.h"
#include "p2p/base/fake_port_allocator.h"
#include "p2p/base/port_allocator.h"
#include "p2p/base/transport_description.h"
#include "p2p/base/transport_info.h"
#include "pc/media_protocol_names.h"
#include "pc/media_session.h"
#include "pc/peer_connection_wrapper.h"
#include "pc/sdp_utils.h"
#include "pc/session_description.h"
#include "pc/test/mock_peer_connection_observers.h"
#include "rtc_base/checks.h"
#include "rtc_base/rtc_certificate.h"
#include "rtc_base/rtc_certificate_generator.h"
#include "rtc_base/ssl_fingerprint.h"
#include "rtc_base/thread.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/scoped_key_value_config.h"
#ifdef WEBRTC_ANDROID
#include "pc/test/android_test_initializer.h"
#endif
#include "pc/test/fake_audio_capture_module.h"
#include "pc/test/fake_rtc_certificate_generator.h"
#include "rtc_base/gunit.h"
#include "rtc_base/virtual_socket_server.h"
namespace webrtc {
using RTCConfiguration = PeerConnectionInterface::RTCConfiguration;
using RTCOfferAnswerOptions = PeerConnectionInterface::RTCOfferAnswerOptions;
using ::testing::Combine;
using ::testing::HasSubstr;
using ::testing::Values;
constexpr int kGenerateCertTimeout = 1000;
class PeerConnectionCryptoBaseTest : public ::testing::Test {
protected:
typedef std::unique_ptr<PeerConnectionWrapper> WrapperPtr;
explicit PeerConnectionCryptoBaseTest(SdpSemantics sdp_semantics)
: vss_(new rtc::VirtualSocketServer()),
main_(vss_.get()),
sdp_semantics_(sdp_semantics) {
#ifdef WEBRTC_ANDROID
InitializeAndroidObjects();
#endif
pc_factory_ = CreatePeerConnectionFactory(
rtc::Thread::Current(), rtc::Thread::Current(), rtc::Thread::Current(),
FakeAudioCaptureModule::Create(), CreateBuiltinAudioEncoderFactory(),
CreateBuiltinAudioDecoderFactory(),
std::make_unique<VideoEncoderFactoryTemplate<
LibvpxVp8EncoderTemplateAdapter, LibvpxVp9EncoderTemplateAdapter,
OpenH264EncoderTemplateAdapter, LibaomAv1EncoderTemplateAdapter>>(),
std::make_unique<VideoDecoderFactoryTemplate<
LibvpxVp8DecoderTemplateAdapter, LibvpxVp9DecoderTemplateAdapter,
OpenH264DecoderTemplateAdapter, Dav1dDecoderTemplateAdapter>>(),
nullptr /* audio_mixer */, nullptr /* audio_processing */);
}
WrapperPtr CreatePeerConnection() {
return CreatePeerConnection(RTCConfiguration());
}
WrapperPtr CreatePeerConnection(const RTCConfiguration& config) {
return CreatePeerConnection(config, nullptr);
}
WrapperPtr CreatePeerConnection(
const RTCConfiguration& config,
std::unique_ptr<rtc::RTCCertificateGeneratorInterface> cert_gen) {
auto fake_port_allocator = std::make_unique<cricket::FakePortAllocator>(
rtc::Thread::Current(),
std::make_unique<rtc::BasicPacketSocketFactory>(vss_.get()),
&field_trials_);
auto observer = std::make_unique<MockPeerConnectionObserver>();
RTCConfiguration modified_config = config;
modified_config.sdp_semantics = sdp_semantics_;
PeerConnectionDependencies pc_dependencies(observer.get());
pc_dependencies.allocator = std::move(fake_port_allocator);
pc_dependencies.cert_generator = std::move(cert_gen);
auto result = pc_factory_->CreatePeerConnectionOrError(
modified_config, std::move(pc_dependencies));
if (!result.ok()) {
return nullptr;
}
observer->SetPeerConnectionInterface(result.value().get());
return std::make_unique<PeerConnectionWrapper>(
pc_factory_, result.MoveValue(), std::move(observer));
}
// Accepts the same arguments as CreatePeerConnection and adds default audio
// and video tracks.
template <typename... Args>
WrapperPtr CreatePeerConnectionWithAudioVideo(Args&&... args) {
auto wrapper = CreatePeerConnection(std::forward<Args>(args)...);
if (!wrapper) {
return nullptr;
}
wrapper->AddAudioTrack("a");
wrapper->AddVideoTrack("v");
return wrapper;
}
cricket::ConnectionRole& AudioConnectionRole(
cricket::SessionDescription* desc) {
return ConnectionRoleFromContent(desc, cricket::GetFirstAudioContent(desc));
}
cricket::ConnectionRole& VideoConnectionRole(
cricket::SessionDescription* desc) {
return ConnectionRoleFromContent(desc, cricket::GetFirstVideoContent(desc));
}
cricket::ConnectionRole& ConnectionRoleFromContent(
cricket::SessionDescription* desc,
cricket::ContentInfo* content) {
RTC_DCHECK(content);
auto* transport_info = desc->GetTransportInfoByName(content->name);
RTC_DCHECK(transport_info);
return transport_info->description.connection_role;
}
test::ScopedKeyValueConfig field_trials_;
std::unique_ptr<rtc::VirtualSocketServer> vss_;
rtc::AutoSocketServerThread main_;
rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory_;
const SdpSemantics sdp_semantics_;
};
SdpContentPredicate HaveDtlsFingerprint() {
return [](const cricket::ContentInfo* content,
const cricket::TransportInfo* transport) {
return transport->description.identity_fingerprint != nullptr;
};
}
SdpContentPredicate HaveProtocol(const std::string& protocol) {
return [protocol](const cricket::ContentInfo* content,
const cricket::TransportInfo* transport) {
return content->media_description()->protocol() == protocol;
};
}
class PeerConnectionCryptoTest
: public PeerConnectionCryptoBaseTest,
public ::testing::WithParamInterface<SdpSemantics> {
protected:
PeerConnectionCryptoTest() : PeerConnectionCryptoBaseTest(GetParam()) {}
};
SdpContentMutator RemoveDtlsFingerprint() {
return [](cricket::ContentInfo* content, cricket::TransportInfo* transport) {
transport->description.identity_fingerprint.reset();
};
}
// When DTLS is enabled, the SDP offer/answer should have a DTLS fingerprint
TEST_P(PeerConnectionCryptoTest, CorrectCryptoInOfferWhenDtlsEnabled) {
RTCConfiguration config;
auto caller = CreatePeerConnectionWithAudioVideo(config);
auto offer = caller->CreateOffer();
ASSERT_TRUE(offer);
ASSERT_FALSE(offer->description()->contents().empty());
EXPECT_TRUE(SdpContentsAll(HaveDtlsFingerprint(), offer->description()));
EXPECT_TRUE(SdpContentsAll(HaveProtocol(cricket::kMediaProtocolDtlsSavpf),
offer->description()));
}
TEST_P(PeerConnectionCryptoTest, CorrectCryptoInAnswerWhenDtlsEnabled) {
RTCConfiguration config;
auto caller = CreatePeerConnectionWithAudioVideo(config);
auto callee = CreatePeerConnectionWithAudioVideo(config);
callee->SetRemoteDescription(caller->CreateOffer());
auto answer = callee->CreateAnswer();
ASSERT_TRUE(answer);
ASSERT_FALSE(answer->description()->contents().empty());
EXPECT_TRUE(SdpContentsAll(HaveDtlsFingerprint(), answer->description()));
EXPECT_TRUE(SdpContentsAll(HaveProtocol(cricket::kMediaProtocolDtlsSavpf),
answer->description()));
}
// The following group tests that two PeerConnections can successfully exchange
// an offer/answer when DTLS is on and that they will refuse any offer/answer
// applied locally/remotely if it does not include a DTLS fingerprint.
TEST_P(PeerConnectionCryptoTest, ExchangeOfferAnswerWhenDtlsOn) {
RTCConfiguration config;
auto caller = CreatePeerConnectionWithAudioVideo(config);
auto callee = CreatePeerConnectionWithAudioVideo(config);
auto offer = caller->CreateOfferAndSetAsLocal();
ASSERT_TRUE(offer);
ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
auto answer = callee->CreateAnswerAndSetAsLocal();
ASSERT_TRUE(answer);
ASSERT_TRUE(caller->SetRemoteDescription(std::move(answer)));
}
TEST_P(PeerConnectionCryptoTest,
FailToSetLocalOfferWithNoFingerprintWhenDtlsOn) {
RTCConfiguration config;
auto caller = CreatePeerConnectionWithAudioVideo(config);
auto offer = caller->CreateOffer();
SdpContentsForEach(RemoveDtlsFingerprint(), offer->description());
EXPECT_FALSE(caller->SetLocalDescription(std::move(offer)));
}
TEST_P(PeerConnectionCryptoTest,
FailToSetRemoteOfferWithNoFingerprintWhenDtlsOn) {
RTCConfiguration config;
auto caller = CreatePeerConnectionWithAudioVideo(config);
auto callee = CreatePeerConnectionWithAudioVideo(config);
auto offer = caller->CreateOffer();
SdpContentsForEach(RemoveDtlsFingerprint(), offer->description());
EXPECT_FALSE(callee->SetRemoteDescription(std::move(offer)));
}
TEST_P(PeerConnectionCryptoTest,
FailToSetLocalAnswerWithNoFingerprintWhenDtlsOn) {
RTCConfiguration config;
auto caller = CreatePeerConnectionWithAudioVideo(config);
auto callee = CreatePeerConnectionWithAudioVideo(config);
callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal());
auto answer = callee->CreateAnswer();
SdpContentsForEach(RemoveDtlsFingerprint(), answer->description());
}
TEST_P(PeerConnectionCryptoTest,
FailToSetRemoteAnswerWithNoFingerprintWhenDtlsOn) {
RTCConfiguration config;
auto caller = CreatePeerConnectionWithAudioVideo(config);
auto callee = CreatePeerConnectionWithAudioVideo(config);
callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal());
auto answer = callee->CreateAnswerAndSetAsLocal();
SdpContentsForEach(RemoveDtlsFingerprint(), answer->description());
EXPECT_FALSE(caller->SetRemoteDescription(std::move(answer)));
}
// Tests that a DTLS call can be established when the certificate is specified
// in the PeerConnection config and no certificate generator is specified.
TEST_P(PeerConnectionCryptoTest,
ExchangeOfferAnswerWhenDtlsCertificateInConfig) {
RTCConfiguration caller_config;
caller_config.certificates.push_back(
FakeRTCCertificateGenerator::GenerateCertificate());
auto caller = CreatePeerConnectionWithAudioVideo(caller_config);
RTCConfiguration callee_config;
callee_config.certificates.push_back(
FakeRTCCertificateGenerator::GenerateCertificate());
auto callee = CreatePeerConnectionWithAudioVideo(callee_config);
auto offer = caller->CreateOfferAndSetAsLocal();
ASSERT_TRUE(offer);
ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
auto answer = callee->CreateAnswerAndSetAsLocal();
ASSERT_TRUE(answer);
ASSERT_TRUE(caller->SetRemoteDescription(std::move(answer)));
}
// The following parameterized test verifies that CreateOffer/CreateAnswer
// returns successfully (or with failure if the underlying certificate generator
// fails) no matter when the DTLS certificate is generated. If multiple
// CreateOffer/CreateAnswer calls are made while waiting for the certificate,
// they all finish after the certificate is generated.
// Whether the certificate will be generated before calling CreateOffer or
// while CreateOffer is executing.
enum class CertGenTime { kBefore, kDuring };
std::ostream& operator<<(std::ostream& out, CertGenTime value) {
switch (value) {
case CertGenTime::kBefore:
return out << "before";
case CertGenTime::kDuring:
return out << "during";
default:
return out << "unknown";
}
}
// Whether the fake certificate generator will produce a certificate or fail.
enum class CertGenResult { kSucceed, kFail };
std::ostream& operator<<(std::ostream& out, CertGenResult value) {
switch (value) {
case CertGenResult::kSucceed:
return out << "succeed";
case CertGenResult::kFail:
return out << "fail";
default:
return out << "unknown";
}
}
class PeerConnectionCryptoDtlsCertGenTest
: public PeerConnectionCryptoBaseTest,
public ::testing::WithParamInterface<std::tuple<SdpSemantics,
SdpType,
CertGenTime,
CertGenResult,
size_t>> {
protected:
PeerConnectionCryptoDtlsCertGenTest()
: PeerConnectionCryptoBaseTest(std::get<0>(GetParam())) {
sdp_type_ = std::get<1>(GetParam());
cert_gen_time_ = std::get<2>(GetParam());
cert_gen_result_ = std::get<3>(GetParam());
concurrent_calls_ = std::get<4>(GetParam());
}
SdpType sdp_type_;
CertGenTime cert_gen_time_;
CertGenResult cert_gen_result_;
size_t concurrent_calls_;
};
TEST_P(PeerConnectionCryptoDtlsCertGenTest, TestCertificateGeneration) {
RTCConfiguration config;
auto owned_fake_certificate_generator =
std::make_unique<FakeRTCCertificateGenerator>();
auto* fake_certificate_generator = owned_fake_certificate_generator.get();
fake_certificate_generator->set_should_fail(cert_gen_result_ ==
CertGenResult::kFail);
fake_certificate_generator->set_should_wait(cert_gen_time_ ==
CertGenTime::kDuring);
WrapperPtr pc;
if (sdp_type_ == SdpType::kOffer) {
pc = CreatePeerConnectionWithAudioVideo(
config, std::move(owned_fake_certificate_generator));
} else {
auto caller = CreatePeerConnectionWithAudioVideo(config);
pc = CreatePeerConnectionWithAudioVideo(
config, std::move(owned_fake_certificate_generator));
pc->SetRemoteDescription(caller->CreateOfferAndSetAsLocal());
}
if (cert_gen_time_ == CertGenTime::kBefore) {
ASSERT_TRUE_WAIT(fake_certificate_generator->generated_certificates() +
fake_certificate_generator->generated_failures() >
0,
kGenerateCertTimeout);
} else {
ASSERT_EQ(fake_certificate_generator->generated_certificates(), 0);
fake_certificate_generator->set_should_wait(false);
}
std::vector<rtc::scoped_refptr<MockCreateSessionDescriptionObserver>>
observers;
for (size_t i = 0; i < concurrent_calls_; i++) {
rtc::scoped_refptr<MockCreateSessionDescriptionObserver> observer =
rtc::make_ref_counted<MockCreateSessionDescriptionObserver>();
observers.push_back(observer);
if (sdp_type_ == SdpType::kOffer) {
pc->pc()->CreateOffer(observer.get(),
PeerConnectionInterface::RTCOfferAnswerOptions());
} else {
pc->pc()->CreateAnswer(observer.get(),
PeerConnectionInterface::RTCOfferAnswerOptions());
}
}
for (auto& observer : observers) {
EXPECT_TRUE_WAIT(observer->called(), 1000);
if (cert_gen_result_ == CertGenResult::kSucceed) {
EXPECT_TRUE(observer->result());
} else {
EXPECT_FALSE(observer->result());
}
}
}
INSTANTIATE_TEST_SUITE_P(
PeerConnectionCryptoTest,
PeerConnectionCryptoDtlsCertGenTest,
Combine(Values(SdpSemantics::kPlanB_DEPRECATED, SdpSemantics::kUnifiedPlan),
Values(SdpType::kOffer, SdpType::kAnswer),
Values(CertGenTime::kBefore, CertGenTime::kDuring),
Values(CertGenResult::kSucceed, CertGenResult::kFail),
Values(1, 3)));
// Test that we can create and set an answer correctly when different
// SSL roles have been negotiated for different transports.
// See: https://bugs.chromium.org/p/webrtc/issues/detail?id=4525
TEST_P(PeerConnectionCryptoTest, CreateAnswerWithDifferentSslRoles) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnectionWithAudioVideo();
RTCOfferAnswerOptions options_no_bundle;
options_no_bundle.use_rtp_mux = false;
// First, negotiate different SSL roles for audio and video.
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
auto answer = callee->CreateAnswer(options_no_bundle);
AudioConnectionRole(answer->description()) = cricket::CONNECTIONROLE_ACTIVE;
VideoConnectionRole(answer->description()) = cricket::CONNECTIONROLE_PASSIVE;
ASSERT_TRUE(
callee->SetLocalDescription(CloneSessionDescription(answer.get())));
ASSERT_TRUE(caller->SetRemoteDescription(std::move(answer)));
// Now create an offer in the reverse direction, and ensure the initial
// offerer responds with an answer with the correct SSL roles.
ASSERT_TRUE(caller->SetRemoteDescription(callee->CreateOfferAndSetAsLocal()));
answer = caller->CreateAnswer(options_no_bundle);
EXPECT_EQ(cricket::CONNECTIONROLE_PASSIVE,
AudioConnectionRole(answer->description()));
EXPECT_EQ(cricket::CONNECTIONROLE_ACTIVE,
VideoConnectionRole(answer->description()));
ASSERT_TRUE(
caller->SetLocalDescription(CloneSessionDescription(answer.get())));
ASSERT_TRUE(callee->SetRemoteDescription(std::move(answer)));
// Lastly, start BUNDLE-ing on "audio", expecting that the "passive" role of
// audio is transferred over to video in the answer that completes the BUNDLE
// negotiation.
RTCOfferAnswerOptions options_bundle;
options_bundle.use_rtp_mux = true;
ASSERT_TRUE(caller->SetRemoteDescription(callee->CreateOfferAndSetAsLocal()));
answer = caller->CreateAnswer(options_bundle);
EXPECT_EQ(cricket::CONNECTIONROLE_PASSIVE,
AudioConnectionRole(answer->description()));
EXPECT_EQ(cricket::CONNECTIONROLE_PASSIVE,
VideoConnectionRole(answer->description()));
ASSERT_TRUE(
caller->SetLocalDescription(CloneSessionDescription(answer.get())));
ASSERT_TRUE(callee->SetRemoteDescription(std::move(answer)));
}
// Tests that if the DTLS fingerprint is invalid then all future calls to
// SetLocalDescription and SetRemoteDescription will fail due to a session
// error.
// This is a regression test for crbug.com/800775
TEST_P(PeerConnectionCryptoTest, SessionErrorIfFingerprintInvalid) {
auto callee_certificate = rtc::RTCCertificate::FromPEM(kRsaPems[0]);
auto other_certificate = rtc::RTCCertificate::FromPEM(kRsaPems[1]);
auto caller = CreatePeerConnectionWithAudioVideo();
RTCConfiguration callee_config;
callee_config.certificates.push_back(callee_certificate);
auto callee = CreatePeerConnectionWithAudioVideo(callee_config);
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
// Create an invalid answer with the other certificate's fingerprint.
auto valid_answer = callee->CreateAnswer();
auto invalid_answer = CloneSessionDescription(valid_answer.get());
auto* audio_content =
cricket::GetFirstAudioContent(invalid_answer->description());
ASSERT_TRUE(audio_content);
auto* audio_transport_info =
invalid_answer->description()->GetTransportInfoByName(
audio_content->name);
ASSERT_TRUE(audio_transport_info);
audio_transport_info->description.identity_fingerprint =
rtc::SSLFingerprint::CreateFromCertificate(*other_certificate);
// Set the invalid answer and expect a fingerprint error.
std::string error;
ASSERT_FALSE(callee->SetLocalDescription(std::move(invalid_answer), &error));
EXPECT_THAT(error, HasSubstr("Local fingerprint does not match identity."));
// Make sure that setting a valid remote offer or local answer also fails now.
ASSERT_FALSE(callee->SetRemoteDescription(caller->CreateOffer(), &error));
EXPECT_THAT(error, HasSubstr("Session error code: ERROR_CONTENT."));
ASSERT_FALSE(callee->SetLocalDescription(std::move(valid_answer), &error));
EXPECT_THAT(error, HasSubstr("Session error code: ERROR_CONTENT."));
}
INSTANTIATE_TEST_SUITE_P(PeerConnectionCryptoTest,
PeerConnectionCryptoTest,
Values(SdpSemantics::kPlanB_DEPRECATED,
SdpSemantics::kUnifiedPlan));
} // namespace webrtc

View file

@ -0,0 +1,334 @@
/*
* Copyright 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "absl/types/optional.h"
#include "api/jsep.h"
#include "api/media_types.h"
#include "api/peer_connection_interface.h"
#include "api/scoped_refptr.h"
#include "api/sctp_transport_interface.h"
#include "api/task_queue/default_task_queue_factory.h"
#include "api/task_queue/task_queue_factory.h"
#include "api/transport/sctp_transport_factory_interface.h"
#include "p2p/base/p2p_constants.h"
#include "p2p/base/port_allocator.h"
#include "pc/media_session.h"
#include "pc/peer_connection.h"
#include "pc/peer_connection_proxy.h"
#include "pc/peer_connection_wrapper.h"
#include "pc/sctp_transport.h"
#include "pc/sdp_utils.h"
#include "pc/session_description.h"
#include "pc/test/enable_fake_media.h"
#include "pc/test/mock_peer_connection_observers.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/rtc_certificate_generator.h"
#include "rtc_base/thread.h"
#include "test/gmock.h"
#include "test/gtest.h"
#ifdef WEBRTC_ANDROID
#include "pc/test/android_test_initializer.h"
#endif
#include "rtc_base/virtual_socket_server.h"
#include "test/pc/sctp/fake_sctp_transport.h"
namespace webrtc {
using RTCConfiguration = PeerConnectionInterface::RTCConfiguration;
using RTCOfferAnswerOptions = PeerConnectionInterface::RTCOfferAnswerOptions;
using ::testing::HasSubstr;
using ::testing::Not;
using ::testing::Values;
namespace {
PeerConnectionFactoryDependencies CreatePeerConnectionFactoryDependencies() {
PeerConnectionFactoryDependencies deps;
deps.network_thread = rtc::Thread::Current();
deps.worker_thread = rtc::Thread::Current();
deps.signaling_thread = rtc::Thread::Current();
deps.task_queue_factory = CreateDefaultTaskQueueFactory();
EnableFakeMedia(deps);
deps.sctp_factory = std::make_unique<FakeSctpTransportFactory>();
return deps;
}
} // namespace
class PeerConnectionWrapperForDataChannelTest : public PeerConnectionWrapper {
public:
using PeerConnectionWrapper::PeerConnectionWrapper;
FakeSctpTransportFactory* sctp_transport_factory() {
return sctp_transport_factory_;
}
void set_sctp_transport_factory(
FakeSctpTransportFactory* sctp_transport_factory) {
sctp_transport_factory_ = sctp_transport_factory;
}
absl::optional<std::string> sctp_mid() {
return GetInternalPeerConnection()->sctp_mid();
}
absl::optional<std::string> sctp_transport_name() {
return GetInternalPeerConnection()->sctp_transport_name();
}
PeerConnection* GetInternalPeerConnection() {
auto* pci =
static_cast<PeerConnectionProxyWithInternal<PeerConnectionInterface>*>(
pc());
return static_cast<PeerConnection*>(pci->internal());
}
private:
FakeSctpTransportFactory* sctp_transport_factory_ = nullptr;
};
class PeerConnectionDataChannelBaseTest : public ::testing::Test {
protected:
typedef std::unique_ptr<PeerConnectionWrapperForDataChannelTest> WrapperPtr;
explicit PeerConnectionDataChannelBaseTest(SdpSemantics sdp_semantics)
: vss_(new rtc::VirtualSocketServer()),
main_(vss_.get()),
sdp_semantics_(sdp_semantics) {
#ifdef WEBRTC_ANDROID
InitializeAndroidObjects();
#endif
}
WrapperPtr CreatePeerConnection() {
return CreatePeerConnection(RTCConfiguration());
}
WrapperPtr CreatePeerConnection(const RTCConfiguration& config) {
return CreatePeerConnection(config,
PeerConnectionFactoryInterface::Options());
}
WrapperPtr CreatePeerConnection(
const RTCConfiguration& config,
const PeerConnectionFactoryInterface::Options factory_options) {
auto factory_deps = CreatePeerConnectionFactoryDependencies();
FakeSctpTransportFactory* fake_sctp_transport_factory =
static_cast<FakeSctpTransportFactory*>(factory_deps.sctp_factory.get());
rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory =
CreateModularPeerConnectionFactory(std::move(factory_deps));
pc_factory->SetOptions(factory_options);
auto observer = std::make_unique<MockPeerConnectionObserver>();
RTCConfiguration modified_config = config;
modified_config.sdp_semantics = sdp_semantics_;
auto result = pc_factory->CreatePeerConnectionOrError(
modified_config, PeerConnectionDependencies(observer.get()));
if (!result.ok()) {
return nullptr;
}
observer->SetPeerConnectionInterface(result.value().get());
auto wrapper = std::make_unique<PeerConnectionWrapperForDataChannelTest>(
pc_factory, result.MoveValue(), std::move(observer));
wrapper->set_sctp_transport_factory(fake_sctp_transport_factory);
return wrapper;
}
// Accepts the same arguments as CreatePeerConnection and adds a default data
// channel.
template <typename... Args>
WrapperPtr CreatePeerConnectionWithDataChannel(Args&&... args) {
auto wrapper = CreatePeerConnection(std::forward<Args>(args)...);
if (!wrapper) {
return nullptr;
}
EXPECT_TRUE(wrapper->pc()->CreateDataChannelOrError("dc", nullptr).ok());
return wrapper;
}
// Changes the SCTP data channel port on the given session description.
void ChangeSctpPortOnDescription(cricket::SessionDescription* desc,
int port) {
auto* data_content = cricket::GetFirstDataContent(desc);
RTC_DCHECK(data_content);
auto* data_desc = data_content->media_description()->as_sctp();
RTC_DCHECK(data_desc);
data_desc->set_port(port);
}
std::unique_ptr<rtc::VirtualSocketServer> vss_;
rtc::AutoSocketServerThread main_;
const SdpSemantics sdp_semantics_;
};
class PeerConnectionDataChannelTest
: public PeerConnectionDataChannelBaseTest,
public ::testing::WithParamInterface<SdpSemantics> {
protected:
PeerConnectionDataChannelTest()
: PeerConnectionDataChannelBaseTest(GetParam()) {}
};
class PeerConnectionDataChannelUnifiedPlanTest
: public PeerConnectionDataChannelBaseTest {
protected:
PeerConnectionDataChannelUnifiedPlanTest()
: PeerConnectionDataChannelBaseTest(SdpSemantics::kUnifiedPlan) {}
};
TEST_P(PeerConnectionDataChannelTest, InternalSctpTransportDeletedOnTeardown) {
auto caller = CreatePeerConnectionWithDataChannel();
ASSERT_TRUE(caller->SetLocalDescription(caller->CreateOffer()));
EXPECT_TRUE(caller->sctp_transport_factory()->last_fake_sctp_transport());
rtc::scoped_refptr<SctpTransportInterface> sctp_transport =
caller->GetInternalPeerConnection()->GetSctpTransport();
caller.reset();
EXPECT_EQ(static_cast<SctpTransport*>(sctp_transport.get())->internal(),
nullptr);
}
// Test that sctp_mid/sctp_transport_name (used for stats) are correct
// before and after BUNDLE is negotiated.
TEST_P(PeerConnectionDataChannelTest, SctpContentAndTransportNameSetCorrectly) {
auto caller = CreatePeerConnection();
auto callee = CreatePeerConnection();
// Initially these fields should be empty.
EXPECT_FALSE(caller->sctp_mid());
EXPECT_FALSE(caller->sctp_transport_name());
// Create offer with audio/video/data.
// Default bundle policy is "balanced", so data should be using its own
// transport.
caller->AddAudioTrack("a");
caller->AddVideoTrack("v");
caller->pc()->CreateDataChannelOrError("dc", nullptr);
auto offer = caller->CreateOffer();
const auto& offer_contents = offer->description()->contents();
ASSERT_EQ(cricket::MEDIA_TYPE_AUDIO,
offer_contents[0].media_description()->type());
std::string audio_mid = offer_contents[0].name;
ASSERT_EQ(cricket::MEDIA_TYPE_DATA,
offer_contents[2].media_description()->type());
std::string data_mid = offer_contents[2].name;
ASSERT_TRUE(
caller->SetLocalDescription(CloneSessionDescription(offer.get())));
ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
ASSERT_TRUE(caller->sctp_mid());
EXPECT_EQ(data_mid, *caller->sctp_mid());
ASSERT_TRUE(caller->sctp_transport_name());
EXPECT_EQ(data_mid, *caller->sctp_transport_name());
// Create answer that finishes BUNDLE negotiation, which means everything
// should be bundled on the first transport (audio).
RTCOfferAnswerOptions options;
options.use_rtp_mux = true;
ASSERT_TRUE(
caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
ASSERT_TRUE(caller->sctp_mid());
EXPECT_EQ(data_mid, *caller->sctp_mid());
ASSERT_TRUE(caller->sctp_transport_name());
EXPECT_EQ(audio_mid, *caller->sctp_transport_name());
}
TEST_P(PeerConnectionDataChannelTest,
CreateOfferWithNoDataChannelsGivesNoDataSection) {
auto caller = CreatePeerConnection();
auto offer = caller->CreateOffer();
EXPECT_FALSE(offer->description()->GetContentByName(cricket::CN_DATA));
EXPECT_FALSE(offer->description()->GetTransportInfoByName(cricket::CN_DATA));
}
TEST_P(PeerConnectionDataChannelTest,
CreateAnswerWithRemoteSctpDataChannelIncludesDataSection) {
auto caller = CreatePeerConnectionWithDataChannel();
auto callee = CreatePeerConnection();
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
auto answer = callee->CreateAnswer();
ASSERT_TRUE(answer);
auto* data_content = cricket::GetFirstDataContent(answer->description());
ASSERT_TRUE(data_content);
EXPECT_FALSE(data_content->rejected);
EXPECT_TRUE(
answer->description()->GetTransportInfoByName(data_content->name));
}
TEST_P(PeerConnectionDataChannelTest, SctpPortPropagatedFromSdpToTransport) {
constexpr int kNewSendPort = 9998;
constexpr int kNewRecvPort = 7775;
auto caller = CreatePeerConnectionWithDataChannel();
auto callee = CreatePeerConnectionWithDataChannel();
auto offer = caller->CreateOffer();
ChangeSctpPortOnDescription(offer->description(), kNewSendPort);
ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
auto answer = callee->CreateAnswer();
ChangeSctpPortOnDescription(answer->description(), kNewRecvPort);
std::string sdp;
answer->ToString(&sdp);
ASSERT_TRUE(callee->SetLocalDescription(std::move(answer)));
auto* callee_transport =
callee->sctp_transport_factory()->last_fake_sctp_transport();
ASSERT_TRUE(callee_transport);
EXPECT_EQ(kNewSendPort, callee_transport->remote_port());
EXPECT_EQ(kNewRecvPort, callee_transport->local_port());
}
TEST_P(PeerConnectionDataChannelTest, ModernSdpSyntaxByDefault) {
PeerConnectionInterface::RTCOfferAnswerOptions options;
auto caller = CreatePeerConnectionWithDataChannel();
auto offer = caller->CreateOffer(options);
EXPECT_FALSE(cricket::GetFirstSctpDataContentDescription(offer->description())
->use_sctpmap());
std::string sdp;
offer->ToString(&sdp);
RTC_LOG(LS_ERROR) << sdp;
EXPECT_THAT(sdp, HasSubstr(" UDP/DTLS/SCTP webrtc-datachannel"));
EXPECT_THAT(sdp, Not(HasSubstr("a=sctpmap:")));
}
TEST_P(PeerConnectionDataChannelTest, ObsoleteSdpSyntaxIfSet) {
PeerConnectionInterface::RTCOfferAnswerOptions options;
options.use_obsolete_sctp_sdp = true;
auto caller = CreatePeerConnectionWithDataChannel();
auto offer = caller->CreateOffer(options);
EXPECT_TRUE(cricket::GetFirstSctpDataContentDescription(offer->description())
->use_sctpmap());
std::string sdp;
offer->ToString(&sdp);
EXPECT_THAT(sdp, Not(HasSubstr(" UDP/DTLS/SCTP webrtc-datachannel")));
EXPECT_THAT(sdp, HasSubstr("a=sctpmap:"));
}
INSTANTIATE_TEST_SUITE_P(PeerConnectionDataChannelTest,
PeerConnectionDataChannelTest,
Values(SdpSemantics::kPlanB_DEPRECATED,
SdpSemantics::kUnifiedPlan));
} // namespace webrtc

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,765 @@
/*
* Copyright 2013 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <stdint.h>
#include <cstddef>
#include <limits>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "absl/strings/match.h"
#include "absl/types/optional.h"
#include "api/audio_codecs/L16/audio_decoder_L16.h"
#include "api/audio_codecs/L16/audio_encoder_L16.h"
#include "api/audio_codecs/audio_codec_pair_id.h"
#include "api/audio_codecs/audio_decoder.h"
#include "api/audio_codecs/audio_decoder_factory.h"
#include "api/audio_codecs/audio_decoder_factory_template.h"
#include "api/audio_codecs/audio_encoder.h"
#include "api/audio_codecs/audio_encoder_factory.h"
#include "api/audio_codecs/audio_encoder_factory_template.h"
#include "api/audio_codecs/audio_format.h"
#include "api/audio_codecs/opus_audio_decoder_factory.h"
#include "api/audio_codecs/opus_audio_encoder_factory.h"
#include "api/audio_options.h"
#include "api/data_channel_interface.h"
#include "api/media_stream_interface.h"
#include "api/peer_connection_interface.h"
#include "api/rtc_error.h"
#include "api/scoped_refptr.h"
#include "media/sctp/sctp_transport_internal.h"
#include "rtc_base/checks.h"
#include "rtc_base/copy_on_write_buffer.h"
#include "rtc_base/gunit.h"
#include "rtc_base/physical_socket_server.h"
#include "rtc_base/third_party/sigslot/sigslot.h"
#include "rtc_base/thread.h"
#include "test/gmock.h"
#include "test/gtest.h"
#ifdef WEBRTC_ANDROID
#include "pc/test/android_test_initializer.h"
#endif
#include "pc/test/peer_connection_test_wrapper.h"
// Notice that mockpeerconnectionobservers.h must be included after the above!
#include "pc/test/mock_peer_connection_observers.h"
#include "test/mock_audio_decoder.h"
#include "test/mock_audio_decoder_factory.h"
#include "test/mock_audio_encoder_factory.h"
using ::testing::_;
using ::testing::AtLeast;
using ::testing::Invoke;
using ::testing::StrictMock;
using ::testing::Values;
using webrtc::DataChannelInterface;
using webrtc::MediaStreamInterface;
using webrtc::PeerConnectionInterface;
using webrtc::SdpSemantics;
namespace {
const int kMaxWait = 25000;
} // namespace
class PeerConnectionEndToEndBaseTest : public sigslot::has_slots<>,
public ::testing::Test {
public:
typedef std::vector<rtc::scoped_refptr<DataChannelInterface>> DataChannelList;
explicit PeerConnectionEndToEndBaseTest(SdpSemantics sdp_semantics)
: network_thread_(std::make_unique<rtc::Thread>(&pss_)),
worker_thread_(rtc::Thread::Create()) {
RTC_CHECK(network_thread_->Start());
RTC_CHECK(worker_thread_->Start());
caller_ = rtc::make_ref_counted<PeerConnectionTestWrapper>(
"caller", &pss_, network_thread_.get(), worker_thread_.get());
callee_ = rtc::make_ref_counted<PeerConnectionTestWrapper>(
"callee", &pss_, network_thread_.get(), worker_thread_.get());
webrtc::PeerConnectionInterface::IceServer ice_server;
ice_server.uri = "stun:stun.l.google.com:19302";
config_.servers.push_back(ice_server);
config_.sdp_semantics = sdp_semantics;
#ifdef WEBRTC_ANDROID
webrtc::InitializeAndroidObjects();
#endif
}
void CreatePcs(
rtc::scoped_refptr<webrtc::AudioEncoderFactory> audio_encoder_factory1,
rtc::scoped_refptr<webrtc::AudioDecoderFactory> audio_decoder_factory1,
rtc::scoped_refptr<webrtc::AudioEncoderFactory> audio_encoder_factory2,
rtc::scoped_refptr<webrtc::AudioDecoderFactory> audio_decoder_factory2) {
EXPECT_TRUE(caller_->CreatePc(config_, audio_encoder_factory1,
audio_decoder_factory1));
EXPECT_TRUE(callee_->CreatePc(config_, audio_encoder_factory2,
audio_decoder_factory2));
PeerConnectionTestWrapper::Connect(caller_.get(), callee_.get());
caller_->SignalOnDataChannel.connect(
this, &PeerConnectionEndToEndBaseTest::OnCallerAddedDataChanel);
callee_->SignalOnDataChannel.connect(
this, &PeerConnectionEndToEndBaseTest::OnCalleeAddedDataChannel);
}
void CreatePcs(
rtc::scoped_refptr<webrtc::AudioEncoderFactory> audio_encoder_factory,
rtc::scoped_refptr<webrtc::AudioDecoderFactory> audio_decoder_factory) {
CreatePcs(audio_encoder_factory, audio_decoder_factory,
audio_encoder_factory, audio_decoder_factory);
}
void GetAndAddUserMedia() {
cricket::AudioOptions audio_options;
GetAndAddUserMedia(true, audio_options, true);
}
void GetAndAddUserMedia(bool audio,
const cricket::AudioOptions& audio_options,
bool video) {
caller_->GetAndAddUserMedia(audio, audio_options, video);
callee_->GetAndAddUserMedia(audio, audio_options, video);
}
void Negotiate() {
caller_->CreateOffer(
webrtc::PeerConnectionInterface::RTCOfferAnswerOptions());
}
void WaitForCallEstablished() {
caller_->WaitForCallEstablished();
callee_->WaitForCallEstablished();
}
void WaitForConnection() {
caller_->WaitForConnection();
callee_->WaitForConnection();
}
void OnCallerAddedDataChanel(DataChannelInterface* dc) {
caller_signaled_data_channels_.push_back(
rtc::scoped_refptr<DataChannelInterface>(dc));
}
void OnCalleeAddedDataChannel(DataChannelInterface* dc) {
callee_signaled_data_channels_.push_back(
rtc::scoped_refptr<DataChannelInterface>(dc));
}
// Tests that `dc1` and `dc2` can send to and receive from each other.
void TestDataChannelSendAndReceive(DataChannelInterface* dc1,
DataChannelInterface* dc2,
size_t size = 6) {
std::unique_ptr<webrtc::MockDataChannelObserver> dc1_observer(
new webrtc::MockDataChannelObserver(dc1));
std::unique_ptr<webrtc::MockDataChannelObserver> dc2_observer(
new webrtc::MockDataChannelObserver(dc2));
static const std::string kDummyData =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
webrtc::DataBuffer buffer("");
size_t sizeLeft = size;
while (sizeLeft > 0) {
size_t chunkSize =
sizeLeft > kDummyData.length() ? kDummyData.length() : sizeLeft;
buffer.data.AppendData(kDummyData.data(), chunkSize);
sizeLeft -= chunkSize;
}
EXPECT_TRUE(dc1->Send(buffer));
EXPECT_EQ_WAIT(buffer.data,
rtc::CopyOnWriteBuffer(dc2_observer->last_message()),
kMaxWait);
EXPECT_TRUE(dc2->Send(buffer));
EXPECT_EQ_WAIT(buffer.data,
rtc::CopyOnWriteBuffer(dc1_observer->last_message()),
kMaxWait);
EXPECT_EQ(1U, dc1_observer->received_message_count());
EXPECT_EQ(size, dc1_observer->last_message().length());
EXPECT_EQ(1U, dc2_observer->received_message_count());
EXPECT_EQ(size, dc2_observer->last_message().length());
}
void WaitForDataChannelsToOpen(DataChannelInterface* local_dc,
const DataChannelList& remote_dc_list,
size_t remote_dc_index) {
EXPECT_EQ_WAIT(DataChannelInterface::kOpen, local_dc->state(), kMaxWait);
ASSERT_TRUE_WAIT(remote_dc_list.size() > remote_dc_index, kMaxWait);
EXPECT_EQ_WAIT(DataChannelInterface::kOpen,
remote_dc_list[remote_dc_index]->state(), kMaxWait);
EXPECT_EQ(local_dc->id(), remote_dc_list[remote_dc_index]->id());
}
void CloseDataChannels(DataChannelInterface* local_dc,
const DataChannelList& remote_dc_list,
size_t remote_dc_index) {
local_dc->Close();
EXPECT_EQ_WAIT(DataChannelInterface::kClosed, local_dc->state(), kMaxWait);
EXPECT_EQ_WAIT(DataChannelInterface::kClosed,
remote_dc_list[remote_dc_index]->state(), kMaxWait);
}
protected:
rtc::AutoThread main_thread_;
rtc::PhysicalSocketServer pss_;
std::unique_ptr<rtc::Thread> network_thread_;
std::unique_ptr<rtc::Thread> worker_thread_;
rtc::scoped_refptr<PeerConnectionTestWrapper> caller_;
rtc::scoped_refptr<PeerConnectionTestWrapper> callee_;
DataChannelList caller_signaled_data_channels_;
DataChannelList callee_signaled_data_channels_;
webrtc::PeerConnectionInterface::RTCConfiguration config_;
};
class PeerConnectionEndToEndTest
: public PeerConnectionEndToEndBaseTest,
public ::testing::WithParamInterface<SdpSemantics> {
protected:
PeerConnectionEndToEndTest() : PeerConnectionEndToEndBaseTest(GetParam()) {}
};
namespace {
std::unique_ptr<webrtc::AudioDecoder> CreateForwardingMockDecoder(
std::unique_ptr<webrtc::AudioDecoder> real_decoder) {
class ForwardingMockDecoder : public StrictMock<webrtc::MockAudioDecoder> {
public:
explicit ForwardingMockDecoder(std::unique_ptr<AudioDecoder> decoder)
: decoder_(std::move(decoder)) {}
private:
std::unique_ptr<AudioDecoder> decoder_;
};
const auto dec = real_decoder.get(); // For lambda capturing.
auto mock_decoder =
std::make_unique<ForwardingMockDecoder>(std::move(real_decoder));
EXPECT_CALL(*mock_decoder, Channels())
.Times(AtLeast(1))
.WillRepeatedly(Invoke([dec] { return dec->Channels(); }));
EXPECT_CALL(*mock_decoder, DecodeInternal(_, _, _, _, _))
.Times(AtLeast(1))
.WillRepeatedly(
Invoke([dec](const uint8_t* encoded, size_t encoded_len,
int sample_rate_hz, int16_t* decoded,
webrtc::AudioDecoder::SpeechType* speech_type) {
return dec->Decode(encoded, encoded_len, sample_rate_hz,
std::numeric_limits<size_t>::max(), decoded,
speech_type);
}));
EXPECT_CALL(*mock_decoder, Die());
EXPECT_CALL(*mock_decoder, HasDecodePlc()).WillRepeatedly(Invoke([dec] {
return dec->HasDecodePlc();
}));
EXPECT_CALL(*mock_decoder, PacketDuration(_, _))
.Times(AtLeast(1))
.WillRepeatedly(Invoke([dec](const uint8_t* encoded, size_t encoded_len) {
return dec->PacketDuration(encoded, encoded_len);
}));
EXPECT_CALL(*mock_decoder, SampleRateHz())
.Times(AtLeast(1))
.WillRepeatedly(Invoke([dec] { return dec->SampleRateHz(); }));
return std::move(mock_decoder);
}
rtc::scoped_refptr<webrtc::AudioDecoderFactory>
CreateForwardingMockDecoderFactory(
webrtc::AudioDecoderFactory* real_decoder_factory) {
rtc::scoped_refptr<webrtc::MockAudioDecoderFactory> mock_decoder_factory =
rtc::make_ref_counted<StrictMock<webrtc::MockAudioDecoderFactory>>();
EXPECT_CALL(*mock_decoder_factory, GetSupportedDecoders())
.Times(AtLeast(1))
.WillRepeatedly(Invoke([real_decoder_factory] {
return real_decoder_factory->GetSupportedDecoders();
}));
EXPECT_CALL(*mock_decoder_factory, IsSupportedDecoder(_))
.Times(AtLeast(1))
.WillRepeatedly(
Invoke([real_decoder_factory](const webrtc::SdpAudioFormat& format) {
return real_decoder_factory->IsSupportedDecoder(format);
}));
EXPECT_CALL(*mock_decoder_factory, MakeAudioDecoderMock(_, _, _))
.Times(AtLeast(2))
.WillRepeatedly(
Invoke([real_decoder_factory](
const webrtc::SdpAudioFormat& format,
absl::optional<webrtc::AudioCodecPairId> codec_pair_id,
std::unique_ptr<webrtc::AudioDecoder>* return_value) {
auto real_decoder =
real_decoder_factory->MakeAudioDecoder(format, codec_pair_id);
*return_value =
real_decoder
? CreateForwardingMockDecoder(std::move(real_decoder))
: nullptr;
}));
return mock_decoder_factory;
}
struct AudioEncoderUnicornSparklesRainbow {
using Config = webrtc::AudioEncoderL16::Config;
static absl::optional<Config> SdpToConfig(webrtc::SdpAudioFormat format) {
if (absl::EqualsIgnoreCase(format.name, "UnicornSparklesRainbow")) {
const webrtc::CodecParameterMap expected_params = {{"num_horns", "1"}};
EXPECT_EQ(expected_params, format.parameters);
format.parameters.clear();
format.name = "L16";
return webrtc::AudioEncoderL16::SdpToConfig(format);
} else {
return absl::nullopt;
}
}
static void AppendSupportedEncoders(
std::vector<webrtc::AudioCodecSpec>* specs) {
std::vector<webrtc::AudioCodecSpec> new_specs;
webrtc::AudioEncoderL16::AppendSupportedEncoders(&new_specs);
for (auto& spec : new_specs) {
spec.format.name = "UnicornSparklesRainbow";
EXPECT_TRUE(spec.format.parameters.empty());
spec.format.parameters.emplace("num_horns", "1");
specs->push_back(spec);
}
}
static webrtc::AudioCodecInfo QueryAudioEncoder(const Config& config) {
return webrtc::AudioEncoderL16::QueryAudioEncoder(config);
}
static std::unique_ptr<webrtc::AudioEncoder> MakeAudioEncoder(
const Config& config,
int payload_type,
absl::optional<webrtc::AudioCodecPairId> codec_pair_id = absl::nullopt) {
return webrtc::AudioEncoderL16::MakeAudioEncoder(config, payload_type,
codec_pair_id);
}
};
struct AudioDecoderUnicornSparklesRainbow {
using Config = webrtc::AudioDecoderL16::Config;
static absl::optional<Config> SdpToConfig(webrtc::SdpAudioFormat format) {
if (absl::EqualsIgnoreCase(format.name, "UnicornSparklesRainbow")) {
const webrtc::CodecParameterMap expected_params = {{"num_horns", "1"}};
EXPECT_EQ(expected_params, format.parameters);
format.parameters.clear();
format.name = "L16";
return webrtc::AudioDecoderL16::SdpToConfig(format);
} else {
return absl::nullopt;
}
}
static void AppendSupportedDecoders(
std::vector<webrtc::AudioCodecSpec>* specs) {
std::vector<webrtc::AudioCodecSpec> new_specs;
webrtc::AudioDecoderL16::AppendSupportedDecoders(&new_specs);
for (auto& spec : new_specs) {
spec.format.name = "UnicornSparklesRainbow";
EXPECT_TRUE(spec.format.parameters.empty());
spec.format.parameters.emplace("num_horns", "1");
specs->push_back(spec);
}
}
static std::unique_ptr<webrtc::AudioDecoder> MakeAudioDecoder(
const Config& config,
absl::optional<webrtc::AudioCodecPairId> codec_pair_id = absl::nullopt) {
return webrtc::AudioDecoderL16::MakeAudioDecoder(config, codec_pair_id);
}
};
} // namespace
TEST_P(PeerConnectionEndToEndTest, Call) {
rtc::scoped_refptr<webrtc::AudioDecoderFactory> real_decoder_factory =
webrtc::CreateOpusAudioDecoderFactory();
CreatePcs(webrtc::CreateOpusAudioEncoderFactory(),
CreateForwardingMockDecoderFactory(real_decoder_factory.get()));
GetAndAddUserMedia();
Negotiate();
WaitForCallEstablished();
}
#if defined(IS_FUCHSIA)
TEST_P(PeerConnectionEndToEndTest, CallWithSdesKeyNegotiation) {
config_.enable_dtls_srtp = false;
CreatePcs(webrtc::CreateOpusAudioEncoderFactory(),
webrtc::CreateOpusAudioDecoderFactory());
GetAndAddUserMedia();
Negotiate();
WaitForCallEstablished();
}
#endif
TEST_P(PeerConnectionEndToEndTest, CallWithCustomCodec) {
class IdLoggingAudioEncoderFactory : public webrtc::AudioEncoderFactory {
public:
IdLoggingAudioEncoderFactory(
rtc::scoped_refptr<AudioEncoderFactory> real_factory,
std::vector<webrtc::AudioCodecPairId>* const codec_ids)
: fact_(real_factory), codec_ids_(codec_ids) {}
std::vector<webrtc::AudioCodecSpec> GetSupportedEncoders() override {
return fact_->GetSupportedEncoders();
}
absl::optional<webrtc::AudioCodecInfo> QueryAudioEncoder(
const webrtc::SdpAudioFormat& format) override {
return fact_->QueryAudioEncoder(format);
}
std::unique_ptr<webrtc::AudioEncoder> MakeAudioEncoder(
int payload_type,
const webrtc::SdpAudioFormat& format,
absl::optional<webrtc::AudioCodecPairId> codec_pair_id) override {
EXPECT_TRUE(codec_pair_id.has_value());
codec_ids_->push_back(*codec_pair_id);
return fact_->MakeAudioEncoder(payload_type, format, codec_pair_id);
}
private:
const rtc::scoped_refptr<webrtc::AudioEncoderFactory> fact_;
std::vector<webrtc::AudioCodecPairId>* const codec_ids_;
};
class IdLoggingAudioDecoderFactory : public webrtc::AudioDecoderFactory {
public:
IdLoggingAudioDecoderFactory(
rtc::scoped_refptr<AudioDecoderFactory> real_factory,
std::vector<webrtc::AudioCodecPairId>* const codec_ids)
: fact_(real_factory), codec_ids_(codec_ids) {}
std::vector<webrtc::AudioCodecSpec> GetSupportedDecoders() override {
return fact_->GetSupportedDecoders();
}
bool IsSupportedDecoder(const webrtc::SdpAudioFormat& format) override {
return fact_->IsSupportedDecoder(format);
}
std::unique_ptr<webrtc::AudioDecoder> MakeAudioDecoder(
const webrtc::SdpAudioFormat& format,
absl::optional<webrtc::AudioCodecPairId> codec_pair_id) override {
EXPECT_TRUE(codec_pair_id.has_value());
codec_ids_->push_back(*codec_pair_id);
return fact_->MakeAudioDecoder(format, codec_pair_id);
}
private:
const rtc::scoped_refptr<webrtc::AudioDecoderFactory> fact_;
std::vector<webrtc::AudioCodecPairId>* const codec_ids_;
};
std::vector<webrtc::AudioCodecPairId> encoder_id1, encoder_id2, decoder_id1,
decoder_id2;
CreatePcs(rtc::make_ref_counted<IdLoggingAudioEncoderFactory>(
webrtc::CreateAudioEncoderFactory<
AudioEncoderUnicornSparklesRainbow>(),
&encoder_id1),
rtc::make_ref_counted<IdLoggingAudioDecoderFactory>(
webrtc::CreateAudioDecoderFactory<
AudioDecoderUnicornSparklesRainbow>(),
&decoder_id1),
rtc::make_ref_counted<IdLoggingAudioEncoderFactory>(
webrtc::CreateAudioEncoderFactory<
AudioEncoderUnicornSparklesRainbow>(),
&encoder_id2),
rtc::make_ref_counted<IdLoggingAudioDecoderFactory>(
webrtc::CreateAudioDecoderFactory<
AudioDecoderUnicornSparklesRainbow>(),
&decoder_id2));
GetAndAddUserMedia();
Negotiate();
WaitForCallEstablished();
// Each codec factory has been used to create one codec. The first pair got
// the same ID because they were passed to the same PeerConnectionFactory,
// and the second pair got the same ID---but these two IDs are not equal,
// because each PeerConnectionFactory has its own ID.
EXPECT_EQ(1U, encoder_id1.size());
EXPECT_EQ(1U, encoder_id2.size());
EXPECT_EQ(encoder_id1, decoder_id1);
EXPECT_EQ(encoder_id2, decoder_id2);
EXPECT_NE(encoder_id1, encoder_id2);
}
#ifdef WEBRTC_HAVE_SCTP
// Verifies that a DataChannel created before the negotiation can transition to
// "OPEN" and transfer data.
TEST_P(PeerConnectionEndToEndTest, CreateDataChannelBeforeNegotiate) {
CreatePcs(webrtc::MockAudioEncoderFactory::CreateEmptyFactory(),
webrtc::MockAudioDecoderFactory::CreateEmptyFactory());
webrtc::DataChannelInit init;
rtc::scoped_refptr<DataChannelInterface> caller_dc(
caller_->CreateDataChannel("data", init));
rtc::scoped_refptr<DataChannelInterface> callee_dc(
callee_->CreateDataChannel("data", init));
Negotiate();
WaitForConnection();
WaitForDataChannelsToOpen(caller_dc.get(), callee_signaled_data_channels_, 0);
WaitForDataChannelsToOpen(callee_dc.get(), caller_signaled_data_channels_, 0);
TestDataChannelSendAndReceive(caller_dc.get(),
callee_signaled_data_channels_[0].get());
TestDataChannelSendAndReceive(callee_dc.get(),
caller_signaled_data_channels_[0].get());
CloseDataChannels(caller_dc.get(), callee_signaled_data_channels_, 0);
CloseDataChannels(callee_dc.get(), caller_signaled_data_channels_, 0);
}
// Verifies that a DataChannel created after the negotiation can transition to
// "OPEN" and transfer data.
TEST_P(PeerConnectionEndToEndTest, CreateDataChannelAfterNegotiate) {
CreatePcs(webrtc::MockAudioEncoderFactory::CreateEmptyFactory(),
webrtc::MockAudioDecoderFactory::CreateEmptyFactory());
webrtc::DataChannelInit init;
// This DataChannel is for creating the data content in the negotiation.
rtc::scoped_refptr<DataChannelInterface> dummy(
caller_->CreateDataChannel("data", init));
Negotiate();
WaitForConnection();
// Wait for the data channel created pre-negotiation to be opened.
WaitForDataChannelsToOpen(dummy.get(), callee_signaled_data_channels_, 0);
// Create new DataChannels after the negotiation and verify their states.
rtc::scoped_refptr<DataChannelInterface> caller_dc(
caller_->CreateDataChannel("hello", init));
rtc::scoped_refptr<DataChannelInterface> callee_dc(
callee_->CreateDataChannel("hello", init));
WaitForDataChannelsToOpen(caller_dc.get(), callee_signaled_data_channels_, 1);
WaitForDataChannelsToOpen(callee_dc.get(), caller_signaled_data_channels_, 0);
TestDataChannelSendAndReceive(caller_dc.get(),
callee_signaled_data_channels_[1].get());
TestDataChannelSendAndReceive(callee_dc.get(),
caller_signaled_data_channels_[0].get());
CloseDataChannels(caller_dc.get(), callee_signaled_data_channels_, 1);
CloseDataChannels(callee_dc.get(), caller_signaled_data_channels_, 0);
}
// Verifies that a DataChannel created can transfer large messages.
TEST_P(PeerConnectionEndToEndTest, CreateDataChannelLargeTransfer) {
CreatePcs(webrtc::MockAudioEncoderFactory::CreateEmptyFactory(),
webrtc::MockAudioDecoderFactory::CreateEmptyFactory());
webrtc::DataChannelInit init;
// This DataChannel is for creating the data content in the negotiation.
rtc::scoped_refptr<DataChannelInterface> dummy(
caller_->CreateDataChannel("data", init));
Negotiate();
WaitForConnection();
// Wait for the data channel created pre-negotiation to be opened.
WaitForDataChannelsToOpen(dummy.get(), callee_signaled_data_channels_, 0);
// Create new DataChannels after the negotiation and verify their states.
rtc::scoped_refptr<DataChannelInterface> caller_dc(
caller_->CreateDataChannel("hello", init));
rtc::scoped_refptr<DataChannelInterface> callee_dc(
callee_->CreateDataChannel("hello", init));
WaitForDataChannelsToOpen(caller_dc.get(), callee_signaled_data_channels_, 1);
WaitForDataChannelsToOpen(callee_dc.get(), caller_signaled_data_channels_, 0);
TestDataChannelSendAndReceive(
caller_dc.get(), callee_signaled_data_channels_[1].get(), 256 * 1024);
TestDataChannelSendAndReceive(
callee_dc.get(), caller_signaled_data_channels_[0].get(), 256 * 1024);
CloseDataChannels(caller_dc.get(), callee_signaled_data_channels_, 1);
CloseDataChannels(callee_dc.get(), caller_signaled_data_channels_, 0);
}
// Verifies that DataChannel IDs are even/odd based on the DTLS roles.
TEST_P(PeerConnectionEndToEndTest, DataChannelIdAssignment) {
CreatePcs(webrtc::MockAudioEncoderFactory::CreateEmptyFactory(),
webrtc::MockAudioDecoderFactory::CreateEmptyFactory());
webrtc::DataChannelInit init;
rtc::scoped_refptr<DataChannelInterface> caller_dc_1(
caller_->CreateDataChannel("data", init));
rtc::scoped_refptr<DataChannelInterface> callee_dc_1(
callee_->CreateDataChannel("data", init));
Negotiate();
WaitForConnection();
EXPECT_EQ(1, caller_dc_1->id() % 2);
EXPECT_EQ(0, callee_dc_1->id() % 2);
rtc::scoped_refptr<DataChannelInterface> caller_dc_2(
caller_->CreateDataChannel("data", init));
rtc::scoped_refptr<DataChannelInterface> callee_dc_2(
callee_->CreateDataChannel("data", init));
EXPECT_EQ(1, caller_dc_2->id() % 2);
EXPECT_EQ(0, callee_dc_2->id() % 2);
}
// Verifies that the message is received by the right remote DataChannel when
// there are multiple DataChannels.
TEST_P(PeerConnectionEndToEndTest,
MessageTransferBetweenTwoPairsOfDataChannels) {
CreatePcs(webrtc::MockAudioEncoderFactory::CreateEmptyFactory(),
webrtc::MockAudioDecoderFactory::CreateEmptyFactory());
webrtc::DataChannelInit init;
rtc::scoped_refptr<DataChannelInterface> caller_dc_1(
caller_->CreateDataChannel("data", init));
rtc::scoped_refptr<DataChannelInterface> caller_dc_2(
caller_->CreateDataChannel("data", init));
Negotiate();
WaitForConnection();
WaitForDataChannelsToOpen(caller_dc_1.get(), callee_signaled_data_channels_,
0);
WaitForDataChannelsToOpen(caller_dc_2.get(), callee_signaled_data_channels_,
1);
std::unique_ptr<webrtc::MockDataChannelObserver> dc_1_observer(
new webrtc::MockDataChannelObserver(
callee_signaled_data_channels_[0].get()));
std::unique_ptr<webrtc::MockDataChannelObserver> dc_2_observer(
new webrtc::MockDataChannelObserver(
callee_signaled_data_channels_[1].get()));
const std::string message_1 = "hello 1";
const std::string message_2 = "hello 2";
caller_dc_1->Send(webrtc::DataBuffer(message_1));
EXPECT_EQ_WAIT(message_1, dc_1_observer->last_message(), kMaxWait);
caller_dc_2->Send(webrtc::DataBuffer(message_2));
EXPECT_EQ_WAIT(message_2, dc_2_observer->last_message(), kMaxWait);
EXPECT_EQ(1U, dc_1_observer->received_message_count());
EXPECT_EQ(1U, dc_2_observer->received_message_count());
}
// Verifies that a DataChannel added from an OPEN message functions after
// a channel has been previously closed (webrtc issue 3778).
// This previously failed because the new channel re-used the ID of the closed
// channel, and the closed channel was incorrectly still assigned to the ID.
TEST_P(PeerConnectionEndToEndTest,
DataChannelFromOpenWorksAfterPreviousChannelClosed) {
CreatePcs(webrtc::MockAudioEncoderFactory::CreateEmptyFactory(),
webrtc::MockAudioDecoderFactory::CreateEmptyFactory());
webrtc::DataChannelInit init;
rtc::scoped_refptr<DataChannelInterface> caller_dc(
caller_->CreateDataChannel("data", init));
Negotiate();
WaitForConnection();
WaitForDataChannelsToOpen(caller_dc.get(), callee_signaled_data_channels_, 0);
int first_channel_id = caller_dc->id();
// Wait for the local side to say it's closed, but not the remote side.
// Previously, the channel on which Close is called reported being closed
// prematurely, and this caused issues; see bugs.webrtc.org/4453.
caller_dc->Close();
EXPECT_EQ_WAIT(DataChannelInterface::kClosed, caller_dc->state(), kMaxWait);
// Create a new channel and ensure it works after closing the previous one.
caller_dc = caller_->CreateDataChannel("data2", init);
WaitForDataChannelsToOpen(caller_dc.get(), callee_signaled_data_channels_, 1);
// Since the second channel was created after the first finished closing, it
// should be able to re-use the first one's ID.
EXPECT_EQ(first_channel_id, caller_dc->id());
TestDataChannelSendAndReceive(caller_dc.get(),
callee_signaled_data_channels_[1].get());
CloseDataChannels(caller_dc.get(), callee_signaled_data_channels_, 1);
}
// This tests that if a data channel is closed remotely while not referenced
// by the application (meaning only the PeerConnection contributes to its
// reference count), no memory access violation will occur.
// See: https://code.google.com/p/chromium/issues/detail?id=565048
TEST_P(PeerConnectionEndToEndTest, CloseDataChannelRemotelyWhileNotReferenced) {
CreatePcs(webrtc::MockAudioEncoderFactory::CreateEmptyFactory(),
webrtc::MockAudioDecoderFactory::CreateEmptyFactory());
webrtc::DataChannelInit init;
rtc::scoped_refptr<DataChannelInterface> caller_dc(
caller_->CreateDataChannel("data", init));
Negotiate();
WaitForConnection();
WaitForDataChannelsToOpen(caller_dc.get(), callee_signaled_data_channels_, 0);
// This removes the reference to the remote data channel that we hold.
callee_signaled_data_channels_.clear();
caller_dc->Close();
EXPECT_EQ_WAIT(DataChannelInterface::kClosed, caller_dc->state(), kMaxWait);
// Wait for a bit longer so the remote data channel will receive the
// close message and be destroyed.
rtc::Thread::Current()->ProcessMessages(100);
}
// Test behavior of creating too many datachannels.
TEST_P(PeerConnectionEndToEndTest, TooManyDataChannelsOpenedBeforeConnecting) {
CreatePcs(webrtc::MockAudioEncoderFactory::CreateEmptyFactory(),
webrtc::MockAudioDecoderFactory::CreateEmptyFactory());
webrtc::DataChannelInit init;
std::vector<rtc::scoped_refptr<DataChannelInterface>> channels;
for (int i = 0; i <= cricket::kMaxSctpStreams / 2; i++) {
rtc::scoped_refptr<DataChannelInterface> caller_dc(
caller_->CreateDataChannel("data", init));
channels.push_back(std::move(caller_dc));
}
Negotiate();
WaitForConnection();
EXPECT_EQ_WAIT(callee_signaled_data_channels_.size(),
static_cast<size_t>(cricket::kMaxSctpStreams / 2), kMaxWait);
EXPECT_EQ(DataChannelInterface::kOpen,
channels[(cricket::kMaxSctpStreams / 2) - 1]->state());
EXPECT_EQ(DataChannelInterface::kClosed,
channels[cricket::kMaxSctpStreams / 2]->state());
}
#endif // WEBRTC_HAVE_SCTP
TEST_P(PeerConnectionEndToEndTest, CanRestartIce) {
rtc::scoped_refptr<webrtc::AudioDecoderFactory> real_decoder_factory =
webrtc::CreateOpusAudioDecoderFactory();
CreatePcs(webrtc::CreateOpusAudioEncoderFactory(),
CreateForwardingMockDecoderFactory(real_decoder_factory.get()));
GetAndAddUserMedia();
Negotiate();
WaitForCallEstablished();
// Cause ICE restart to be requested.
auto config = caller_->pc()->GetConfiguration();
ASSERT_NE(PeerConnectionInterface::kRelay, config.type);
config.type = PeerConnectionInterface::kRelay;
ASSERT_TRUE(caller_->pc()->SetConfiguration(config).ok());
// When solving https://crbug.com/webrtc/10504, all we need to check
// is that we do not crash. We should also be testing that restart happens.
}
INSTANTIATE_TEST_SUITE_P(PeerConnectionEndToEndTest,
PeerConnectionEndToEndTest,
Values(SdpSemantics::kPlanB_DEPRECATED,
SdpSemantics::kUnifiedPlan));

View file

@ -0,0 +1,358 @@
/*
* Copyright 2004 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/peer_connection_factory.h"
#include <type_traits>
#include <utility>
#include "absl/strings/match.h"
#include "api/environment/environment.h"
#include "api/environment/environment_factory.h"
#include "api/fec_controller.h"
#include "api/ice_transport_interface.h"
#include "api/network_state_predictor.h"
#include "api/packet_socket_factory.h"
#include "api/rtc_event_log/rtc_event_log.h"
#include "api/sequence_checker.h"
#include "api/transport/bitrate_settings.h"
#include "api/units/data_rate.h"
#include "call/audio_state.h"
#include "call/rtp_transport_controller_send_factory.h"
#include "media/base/media_engine.h"
#include "p2p/base/basic_packet_socket_factory.h"
#include "p2p/base/default_ice_transport_factory.h"
#include "p2p/base/port_allocator.h"
#include "p2p/client/basic_port_allocator.h"
#include "pc/audio_track.h"
#include "pc/local_audio_source.h"
#include "pc/media_factory.h"
#include "pc/media_stream.h"
#include "pc/media_stream_proxy.h"
#include "pc/media_stream_track_proxy.h"
#include "pc/peer_connection.h"
#include "pc/peer_connection_factory_proxy.h"
#include "pc/peer_connection_proxy.h"
#include "pc/rtp_parameters_conversion.h"
#include "pc/session_description.h"
#include "pc/video_track.h"
#include "rtc_base/checks.h"
#include "rtc_base/experiments/field_trial_parser.h"
#include "rtc_base/experiments/field_trial_units.h"
#include "rtc_base/logging.h"
#include "rtc_base/numerics/safe_conversions.h"
#include "rtc_base/rtc_certificate_generator.h"
#include "rtc_base/system/file_wrapper.h"
namespace webrtc {
rtc::scoped_refptr<PeerConnectionFactoryInterface>
CreateModularPeerConnectionFactory(
PeerConnectionFactoryDependencies dependencies) {
// The PeerConnectionFactory must be created on the signaling thread.
if (dependencies.signaling_thread &&
!dependencies.signaling_thread->IsCurrent()) {
return dependencies.signaling_thread->BlockingCall([&dependencies] {
return CreateModularPeerConnectionFactory(std::move(dependencies));
});
}
auto pc_factory = PeerConnectionFactory::Create(std::move(dependencies));
if (!pc_factory) {
return nullptr;
}
// Verify that the invocation and the initialization ended up agreeing on the
// thread.
RTC_DCHECK_RUN_ON(pc_factory->signaling_thread());
return PeerConnectionFactoryProxy::Create(
pc_factory->signaling_thread(), pc_factory->worker_thread(), pc_factory);
}
// Static
rtc::scoped_refptr<PeerConnectionFactory> PeerConnectionFactory::Create(
PeerConnectionFactoryDependencies dependencies) {
auto context = ConnectionContext::Create(
CreateEnvironment(std::move(dependencies.trials),
std::move(dependencies.task_queue_factory)),
&dependencies);
if (!context) {
return nullptr;
}
return rtc::make_ref_counted<PeerConnectionFactory>(context, &dependencies);
}
PeerConnectionFactory::PeerConnectionFactory(
rtc::scoped_refptr<ConnectionContext> context,
PeerConnectionFactoryDependencies* dependencies)
: context_(context),
event_log_factory_(std::move(dependencies->event_log_factory)),
fec_controller_factory_(std::move(dependencies->fec_controller_factory)),
network_state_predictor_factory_(
std::move(dependencies->network_state_predictor_factory)),
injected_network_controller_factory_(
std::move(dependencies->network_controller_factory)),
neteq_factory_(std::move(dependencies->neteq_factory)),
transport_controller_send_factory_(
(dependencies->transport_controller_send_factory)
? std::move(dependencies->transport_controller_send_factory)
: std::make_unique<RtpTransportControllerSendFactory>()),
decode_metronome_(std::move(dependencies->decode_metronome)),
encode_metronome_(std::move(dependencies->encode_metronome)) {}
PeerConnectionFactory::PeerConnectionFactory(
PeerConnectionFactoryDependencies dependencies)
: PeerConnectionFactory(
ConnectionContext::Create(
CreateEnvironment(std::move(dependencies.trials),
std::move(dependencies.task_queue_factory)),
&dependencies),
&dependencies) {}
PeerConnectionFactory::~PeerConnectionFactory() {
RTC_DCHECK_RUN_ON(signaling_thread());
worker_thread()->BlockingCall([this] {
RTC_DCHECK_RUN_ON(worker_thread());
decode_metronome_ = nullptr;
encode_metronome_ = nullptr;
});
}
void PeerConnectionFactory::SetOptions(const Options& options) {
RTC_DCHECK_RUN_ON(signaling_thread());
options_ = options;
}
RtpCapabilities PeerConnectionFactory::GetRtpSenderCapabilities(
cricket::MediaType kind) const {
RTC_DCHECK_RUN_ON(signaling_thread());
switch (kind) {
case cricket::MEDIA_TYPE_AUDIO: {
cricket::AudioCodecs cricket_codecs;
cricket_codecs = media_engine()->voice().send_codecs();
auto extensions =
GetDefaultEnabledRtpHeaderExtensions(media_engine()->voice());
return ToRtpCapabilities(cricket_codecs, extensions);
}
case cricket::MEDIA_TYPE_VIDEO: {
cricket::VideoCodecs cricket_codecs;
cricket_codecs = media_engine()->video().send_codecs(context_->use_rtx());
auto extensions =
GetDefaultEnabledRtpHeaderExtensions(media_engine()->video());
return ToRtpCapabilities(cricket_codecs, extensions);
}
case cricket::MEDIA_TYPE_DATA:
return RtpCapabilities();
case cricket::MEDIA_TYPE_UNSUPPORTED:
return RtpCapabilities();
}
RTC_DLOG(LS_ERROR) << "Got unexpected MediaType " << kind;
RTC_CHECK_NOTREACHED();
}
RtpCapabilities PeerConnectionFactory::GetRtpReceiverCapabilities(
cricket::MediaType kind) const {
RTC_DCHECK_RUN_ON(signaling_thread());
switch (kind) {
case cricket::MEDIA_TYPE_AUDIO: {
cricket::AudioCodecs cricket_codecs;
cricket_codecs = media_engine()->voice().recv_codecs();
auto extensions =
GetDefaultEnabledRtpHeaderExtensions(media_engine()->voice());
return ToRtpCapabilities(cricket_codecs, extensions);
}
case cricket::MEDIA_TYPE_VIDEO: {
cricket::VideoCodecs cricket_codecs =
media_engine()->video().recv_codecs(context_->use_rtx());
auto extensions =
GetDefaultEnabledRtpHeaderExtensions(media_engine()->video());
return ToRtpCapabilities(cricket_codecs, extensions);
}
case cricket::MEDIA_TYPE_DATA:
return RtpCapabilities();
case cricket::MEDIA_TYPE_UNSUPPORTED:
return RtpCapabilities();
}
RTC_DLOG(LS_ERROR) << "Got unexpected MediaType " << kind;
RTC_CHECK_NOTREACHED();
}
rtc::scoped_refptr<AudioSourceInterface>
PeerConnectionFactory::CreateAudioSource(const cricket::AudioOptions& options) {
RTC_DCHECK(signaling_thread()->IsCurrent());
rtc::scoped_refptr<LocalAudioSource> source(
LocalAudioSource::Create(&options));
return source;
}
bool PeerConnectionFactory::StartAecDump(FILE* file, int64_t max_size_bytes) {
RTC_DCHECK_RUN_ON(worker_thread());
return media_engine()->voice().StartAecDump(FileWrapper(file),
max_size_bytes);
}
void PeerConnectionFactory::StopAecDump() {
RTC_DCHECK_RUN_ON(worker_thread());
media_engine()->voice().StopAecDump();
}
cricket::MediaEngineInterface* PeerConnectionFactory::media_engine() const {
RTC_DCHECK(context_);
return context_->media_engine();
}
RTCErrorOr<rtc::scoped_refptr<PeerConnectionInterface>>
PeerConnectionFactory::CreatePeerConnectionOrError(
const PeerConnectionInterface::RTCConfiguration& configuration,
PeerConnectionDependencies dependencies) {
RTC_DCHECK_RUN_ON(signaling_thread());
EnvironmentFactory env_factory(context_->env());
// Field trials active for this PeerConnection is the first of:
// a) Specified in the PeerConnectionDependencies
// b) Specified in the PeerConnectionFactoryDependencies
// c) Created as default by the EnvironmentFactory.
env_factory.Set(std::move(dependencies.trials));
if (event_log_factory_ != nullptr) {
worker_thread()->BlockingCall([&] {
Environment env_for_rtc_event_log = env_factory.Create();
env_factory.Set(event_log_factory_->Create(env_for_rtc_event_log));
});
}
const Environment env = env_factory.Create();
// Set internal defaults if optional dependencies are not set.
if (!dependencies.cert_generator) {
dependencies.cert_generator =
std::make_unique<rtc::RTCCertificateGenerator>(signaling_thread(),
network_thread());
}
if (!dependencies.allocator) {
dependencies.allocator = std::make_unique<cricket::BasicPortAllocator>(
context_->default_network_manager(), context_->default_socket_factory(),
configuration.turn_customizer, /*relay_port_factory=*/nullptr,
&env.field_trials());
dependencies.allocator->SetPortRange(
configuration.port_allocator_config.min_port,
configuration.port_allocator_config.max_port);
dependencies.allocator->set_flags(
configuration.port_allocator_config.flags);
}
if (!dependencies.ice_transport_factory) {
dependencies.ice_transport_factory =
std::make_unique<DefaultIceTransportFactory>();
}
dependencies.allocator->SetNetworkIgnoreMask(options().network_ignore_mask);
dependencies.allocator->SetVpnList(configuration.vpn_list);
std::unique_ptr<Call> call =
worker_thread()->BlockingCall([this, &env, &configuration] {
return CreateCall_w(env, configuration);
});
auto result = PeerConnection::Create(env, context_, options_, std::move(call),
configuration, std::move(dependencies));
if (!result.ok()) {
return result.MoveError();
}
// We configure the proxy with a pointer to the network thread for methods
// that need to be invoked there rather than on the signaling thread.
// Internally, the proxy object has a member variable named `worker_thread_`
// which will point to the network thread (and not the factory's
// worker_thread()). All such methods have thread checks though, so the code
// should still be clear (outside of macro expansion).
rtc::scoped_refptr<PeerConnectionInterface> result_proxy =
PeerConnectionProxy::Create(signaling_thread(), network_thread(),
result.MoveValue());
return result_proxy;
}
rtc::scoped_refptr<MediaStreamInterface>
PeerConnectionFactory::CreateLocalMediaStream(const std::string& stream_id) {
RTC_DCHECK(signaling_thread()->IsCurrent());
return MediaStreamProxy::Create(signaling_thread(),
MediaStream::Create(stream_id));
}
rtc::scoped_refptr<VideoTrackInterface> PeerConnectionFactory::CreateVideoTrack(
rtc::scoped_refptr<VideoTrackSourceInterface> source,
absl::string_view id) {
RTC_DCHECK(signaling_thread()->IsCurrent());
rtc::scoped_refptr<VideoTrackInterface> track =
VideoTrack::Create(id, source, worker_thread());
return VideoTrackProxy::Create(signaling_thread(), worker_thread(), track);
}
rtc::scoped_refptr<AudioTrackInterface> PeerConnectionFactory::CreateAudioTrack(
const std::string& id,
AudioSourceInterface* source) {
RTC_DCHECK(signaling_thread()->IsCurrent());
rtc::scoped_refptr<AudioTrackInterface> track =
AudioTrack::Create(id, rtc::scoped_refptr<AudioSourceInterface>(source));
return AudioTrackProxy::Create(signaling_thread(), track);
}
std::unique_ptr<Call> PeerConnectionFactory::CreateCall_w(
const Environment& env,
const PeerConnectionInterface::RTCConfiguration& configuration) {
RTC_DCHECK_RUN_ON(worker_thread());
CallConfig call_config(env, network_thread());
if (!media_engine() || !context_->call_factory()) {
return nullptr;
}
call_config.audio_state = media_engine()->voice().GetAudioState();
FieldTrialParameter<DataRate> min_bandwidth("min",
DataRate::KilobitsPerSec(30));
FieldTrialParameter<DataRate> start_bandwidth("start",
DataRate::KilobitsPerSec(300));
FieldTrialParameter<DataRate> max_bandwidth("max",
DataRate::KilobitsPerSec(2000));
ParseFieldTrial({&min_bandwidth, &start_bandwidth, &max_bandwidth},
env.field_trials().Lookup("WebRTC-PcFactoryDefaultBitrates"));
call_config.bitrate_config.min_bitrate_bps =
rtc::saturated_cast<int>(min_bandwidth->bps());
call_config.bitrate_config.start_bitrate_bps =
rtc::saturated_cast<int>(start_bandwidth->bps());
call_config.bitrate_config.max_bitrate_bps =
rtc::saturated_cast<int>(max_bandwidth->bps());
call_config.fec_controller_factory = fec_controller_factory_.get();
call_config.network_state_predictor_factory =
network_state_predictor_factory_.get();
call_config.neteq_factory = neteq_factory_.get();
if (IsTrialEnabled("WebRTC-Bwe-InjectedCongestionController")) {
RTC_LOG(LS_INFO) << "Using injected network controller factory";
call_config.network_controller_factory =
injected_network_controller_factory_.get();
} else {
RTC_LOG(LS_INFO) << "Using default network controller factory";
}
call_config.rtp_transport_controller_send_factory =
transport_controller_send_factory_.get();
call_config.decode_metronome = decode_metronome_.get();
call_config.encode_metronome = encode_metronome_.get();
call_config.pacer_burst_interval = configuration.pacer_burst_interval;
return context_->call_factory()->CreateCall(call_config);
}
bool PeerConnectionFactory::IsTrialEnabled(absl::string_view key) const {
return absl::StartsWith(field_trials().Lookup(key), "Enabled");
}
} // namespace webrtc

View file

@ -0,0 +1,156 @@
/*
* Copyright 2011 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef PC_PEER_CONNECTION_FACTORY_H_
#define PC_PEER_CONNECTION_FACTORY_H_
#include <stdint.h>
#include <stdio.h>
#include <memory>
#include <string>
#include "absl/strings/string_view.h"
#include "api/audio_options.h"
#include "api/fec_controller.h"
#include "api/field_trials_view.h"
#include "api/media_stream_interface.h"
#include "api/media_types.h"
#include "api/metronome/metronome.h"
#include "api/neteq/neteq_factory.h"
#include "api/network_state_predictor.h"
#include "api/peer_connection_interface.h"
#include "api/rtc_error.h"
#include "api/rtc_event_log/rtc_event_log_factory_interface.h"
#include "api/rtp_parameters.h"
#include "api/scoped_refptr.h"
#include "api/sequence_checker.h"
#include "api/transport/network_control.h"
#include "api/transport/sctp_transport_factory_interface.h"
#include "call/call.h"
#include "call/rtp_transport_controller_send_factory_interface.h"
#include "p2p/base/port_allocator.h"
#include "pc/connection_context.h"
#include "rtc_base/checks.h"
#include "rtc_base/rtc_certificate_generator.h"
#include "rtc_base/thread.h"
#include "rtc_base/thread_annotations.h"
namespace rtc {
class BasicNetworkManager;
class BasicPacketSocketFactory;
} // namespace rtc
namespace webrtc {
class PeerConnectionFactory : public PeerConnectionFactoryInterface {
public:
// Creates a PeerConnectionFactory. It returns nullptr on initialization
// error.
//
// The Dependencies structure allows simple management of all new
// dependencies being added to the PeerConnectionFactory.
static rtc::scoped_refptr<PeerConnectionFactory> Create(
PeerConnectionFactoryDependencies dependencies);
void SetOptions(const Options& options) override;
RTCErrorOr<rtc::scoped_refptr<PeerConnectionInterface>>
CreatePeerConnectionOrError(
const PeerConnectionInterface::RTCConfiguration& configuration,
PeerConnectionDependencies dependencies) override;
RtpCapabilities GetRtpSenderCapabilities(
cricket::MediaType kind) const override;
RtpCapabilities GetRtpReceiverCapabilities(
cricket::MediaType kind) const override;
rtc::scoped_refptr<MediaStreamInterface> CreateLocalMediaStream(
const std::string& stream_id) override;
rtc::scoped_refptr<AudioSourceInterface> CreateAudioSource(
const cricket::AudioOptions& options) override;
rtc::scoped_refptr<VideoTrackInterface> CreateVideoTrack(
rtc::scoped_refptr<VideoTrackSourceInterface> video_source,
absl::string_view id) override;
rtc::scoped_refptr<AudioTrackInterface> CreateAudioTrack(
const std::string& id,
AudioSourceInterface* audio_source) override;
bool StartAecDump(FILE* file, int64_t max_size_bytes) override;
void StopAecDump() override;
SctpTransportFactoryInterface* sctp_transport_factory() {
return context_->sctp_transport_factory();
}
rtc::Thread* signaling_thread() const {
// This method can be called on a different thread when the factory is
// created in CreatePeerConnectionFactory().
return context_->signaling_thread();
}
rtc::Thread* worker_thread() const { return context_->worker_thread(); }
const Options& options() const {
RTC_DCHECK_RUN_ON(signaling_thread());
return options_;
}
const FieldTrialsView& field_trials() const {
return context_->env().field_trials();
}
cricket::MediaEngineInterface* media_engine() const;
protected:
// Constructor used by the static Create() method. Modifies the dependencies.
PeerConnectionFactory(rtc::scoped_refptr<ConnectionContext> context,
PeerConnectionFactoryDependencies* dependencies);
// Constructor for use in testing. Ignores the possibility of initialization
// failure. The dependencies are passed in by std::move().
explicit PeerConnectionFactory(
PeerConnectionFactoryDependencies dependencies);
virtual ~PeerConnectionFactory();
private:
rtc::Thread* network_thread() const { return context_->network_thread(); }
bool IsTrialEnabled(absl::string_view key) const;
std::unique_ptr<Call> CreateCall_w(
const Environment& env,
const PeerConnectionInterface::RTCConfiguration& configuration);
rtc::scoped_refptr<ConnectionContext> context_;
PeerConnectionFactoryInterface::Options options_
RTC_GUARDED_BY(signaling_thread());
std::unique_ptr<RtcEventLogFactoryInterface> event_log_factory_;
std::unique_ptr<FecControllerFactoryInterface> fec_controller_factory_;
std::unique_ptr<NetworkStatePredictorFactoryInterface>
network_state_predictor_factory_;
std::unique_ptr<NetworkControllerFactoryInterface>
injected_network_controller_factory_;
std::unique_ptr<NetEqFactory> neteq_factory_;
const std::unique_ptr<RtpTransportControllerSendFactoryInterface>
transport_controller_send_factory_;
std::unique_ptr<Metronome> decode_metronome_ RTC_GUARDED_BY(worker_thread());
std::unique_ptr<Metronome> encode_metronome_ RTC_GUARDED_BY(worker_thread());
};
} // namespace webrtc
#endif // PC_PEER_CONNECTION_FACTORY_H_

View file

@ -0,0 +1,58 @@
/*
* Copyright 2014 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef PC_PEER_CONNECTION_FACTORY_PROXY_H_
#define PC_PEER_CONNECTION_FACTORY_PROXY_H_
#include <memory>
#include <string>
#include <utility>
#include "api/peer_connection_interface.h"
#include "pc/proxy.h"
namespace webrtc {
// TODO(deadbeef): Move this to .cc file. What threads methods are called on is
// an implementation detail.
BEGIN_PROXY_MAP(PeerConnectionFactory)
PROXY_PRIMARY_THREAD_DESTRUCTOR()
PROXY_METHOD1(void, SetOptions, const Options&)
PROXY_METHOD2(RTCErrorOr<rtc::scoped_refptr<PeerConnectionInterface>>,
CreatePeerConnectionOrError,
const PeerConnectionInterface::RTCConfiguration&,
PeerConnectionDependencies)
PROXY_CONSTMETHOD1(RtpCapabilities,
GetRtpSenderCapabilities,
cricket::MediaType)
PROXY_CONSTMETHOD1(RtpCapabilities,
GetRtpReceiverCapabilities,
cricket::MediaType)
PROXY_METHOD1(rtc::scoped_refptr<MediaStreamInterface>,
CreateLocalMediaStream,
const std::string&)
PROXY_METHOD1(rtc::scoped_refptr<AudioSourceInterface>,
CreateAudioSource,
const cricket::AudioOptions&)
PROXY_METHOD2(rtc::scoped_refptr<VideoTrackInterface>,
CreateVideoTrack,
rtc::scoped_refptr<VideoTrackSourceInterface>,
absl::string_view)
PROXY_METHOD2(rtc::scoped_refptr<AudioTrackInterface>,
CreateAudioTrack,
const std::string&,
AudioSourceInterface*)
PROXY_SECONDARY_METHOD2(bool, StartAecDump, FILE*, int64_t)
PROXY_SECONDARY_METHOD0(void, StopAecDump)
END_PROXY_MAP(PeerConnectionFactory)
} // namespace webrtc
#endif // PC_PEER_CONNECTION_FACTORY_PROXY_H_

View file

@ -0,0 +1,724 @@
/*
* Copyright 2012 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/peer_connection_factory.h"
#include <algorithm>
#include <memory>
#include <utility>
#include <vector>
#include "api/audio/audio_mixer.h"
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
#include "api/create_peerconnection_factory.h"
#include "api/data_channel_interface.h"
#include "api/enable_media.h"
#include "api/environment/environment_factory.h"
#include "api/jsep.h"
#include "api/media_stream_interface.h"
#include "api/task_queue/default_task_queue_factory.h"
#include "api/test/mock_packet_socket_factory.h"
#include "api/video_codecs/video_decoder_factory_template.h"
#include "api/video_codecs/video_decoder_factory_template_dav1d_adapter.h"
#include "api/video_codecs/video_decoder_factory_template_libvpx_vp8_adapter.h"
#include "api/video_codecs/video_decoder_factory_template_libvpx_vp9_adapter.h"
#include "api/video_codecs/video_decoder_factory_template_open_h264_adapter.h"
#include "api/video_codecs/video_encoder_factory_template.h"
#include "api/video_codecs/video_encoder_factory_template_libaom_av1_adapter.h"
#include "api/video_codecs/video_encoder_factory_template_libvpx_vp8_adapter.h"
#include "api/video_codecs/video_encoder_factory_template_libvpx_vp9_adapter.h"
#include "api/video_codecs/video_encoder_factory_template_open_h264_adapter.h"
#include "media/base/fake_frame_source.h"
#include "modules/audio_device/include/audio_device.h"
#include "modules/audio_processing/include/audio_processing.h"
#include "p2p/base/fake_port_allocator.h"
#include "p2p/base/port.h"
#include "p2p/base/port_allocator.h"
#include "p2p/base/port_interface.h"
#include "pc/test/fake_audio_capture_module.h"
#include "pc/test/fake_video_track_source.h"
#include "pc/test/mock_peer_connection_observers.h"
#include "rtc_base/gunit.h"
#include "rtc_base/internal/default_socket_server.h"
#include "rtc_base/rtc_certificate_generator.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/time_utils.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/scoped_key_value_config.h"
#ifdef WEBRTC_ANDROID
#include "pc/test/android_test_initializer.h"
#endif
#include "pc/test/fake_rtc_certificate_generator.h"
#include "pc/test/fake_video_track_renderer.h"
namespace webrtc {
namespace {
using ::testing::_;
using ::testing::AtLeast;
using ::testing::InvokeWithoutArgs;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::UnorderedElementsAre;
static const char kStunIceServer[] = "stun:stun.l.google.com:19302";
static const char kTurnIceServer[] = "turn:test.com:1234";
static const char kTurnIceServerWithTransport[] =
"turn:hello.com?transport=tcp";
static const char kSecureTurnIceServer[] = "turns:hello.com?transport=tcp";
static const char kSecureTurnIceServerWithoutTransportParam[] =
"turns:hello.com:443";
static const char kSecureTurnIceServerWithoutTransportAndPortParam[] =
"turns:hello.com";
static const char kTurnIceServerWithNoUsernameInUri[] = "turn:test.com:1234";
static const char kTurnPassword[] = "turnpassword";
static const int kDefaultStunPort = 3478;
static const int kDefaultStunTlsPort = 5349;
static const char kTurnUsername[] = "test";
static const char kStunIceServerWithIPv4Address[] = "stun:1.2.3.4:1234";
static const char kStunIceServerWithIPv4AddressWithoutPort[] = "stun:1.2.3.4";
static const char kStunIceServerWithIPv6Address[] = "stun:[2401:fa00:4::]:1234";
static const char kStunIceServerWithIPv6AddressWithoutPort[] =
"stun:[2401:fa00:4::]";
static const char kTurnIceServerWithIPv6Address[] = "turn:[2401:fa00:4::]:1234";
class NullPeerConnectionObserver : public PeerConnectionObserver {
public:
virtual ~NullPeerConnectionObserver() = default;
void OnSignalingChange(
PeerConnectionInterface::SignalingState new_state) override {}
void OnAddStream(rtc::scoped_refptr<MediaStreamInterface> stream) override {}
void OnRemoveStream(
rtc::scoped_refptr<MediaStreamInterface> stream) override {}
void OnDataChannel(
rtc::scoped_refptr<DataChannelInterface> data_channel) override {}
void OnRenegotiationNeeded() override {}
void OnIceConnectionChange(
PeerConnectionInterface::IceConnectionState new_state) override {}
void OnIceGatheringChange(
PeerConnectionInterface::IceGatheringState new_state) override {}
void OnIceCandidate(const IceCandidateInterface* candidate) override {}
};
class MockNetworkManager : public rtc::NetworkManager {
public:
MOCK_METHOD(void, StartUpdating, (), (override));
MOCK_METHOD(void, StopUpdating, (), (override));
MOCK_METHOD(std::vector<const rtc::Network*>,
GetNetworks,
(),
(const override));
MOCK_METHOD(std::vector<const rtc::Network*>,
GetAnyAddressNetworks,
(),
(override));
};
class PeerConnectionFactoryTest : public ::testing::Test {
public:
PeerConnectionFactoryTest()
: socket_server_(rtc::CreateDefaultSocketServer()),
main_thread_(socket_server_.get()) {}
private:
void SetUp() {
#ifdef WEBRTC_ANDROID
InitializeAndroidObjects();
#endif
// Use fake audio device module since we're only testing the interface
// level, and using a real one could make tests flaky e.g. when run in
// parallel.
factory_ = CreatePeerConnectionFactory(
rtc::Thread::Current(), rtc::Thread::Current(), rtc::Thread::Current(),
rtc::scoped_refptr<AudioDeviceModule>(FakeAudioCaptureModule::Create()),
CreateBuiltinAudioEncoderFactory(), CreateBuiltinAudioDecoderFactory(),
std::make_unique<VideoEncoderFactoryTemplate<
LibvpxVp8EncoderTemplateAdapter, LibvpxVp9EncoderTemplateAdapter,
OpenH264EncoderTemplateAdapter, LibaomAv1EncoderTemplateAdapter>>(),
std::make_unique<VideoDecoderFactoryTemplate<
LibvpxVp8DecoderTemplateAdapter, LibvpxVp9DecoderTemplateAdapter,
OpenH264DecoderTemplateAdapter, Dav1dDecoderTemplateAdapter>>(),
nullptr /* audio_mixer */, nullptr /* audio_processing */);
ASSERT_TRUE(factory_.get() != NULL);
packet_socket_factory_.reset(
new rtc::BasicPacketSocketFactory(socket_server_.get()));
port_allocator_.reset(new cricket::FakePortAllocator(
rtc::Thread::Current(), packet_socket_factory_.get(), &field_trials_));
raw_port_allocator_ = port_allocator_.get();
}
protected:
void VerifyStunServers(cricket::ServerAddresses stun_servers) {
EXPECT_EQ(stun_servers, raw_port_allocator_->stun_servers());
}
void VerifyTurnServers(std::vector<cricket::RelayServerConfig> turn_servers) {
EXPECT_EQ(turn_servers.size(), raw_port_allocator_->turn_servers().size());
for (size_t i = 0; i < turn_servers.size(); ++i) {
ASSERT_EQ(1u, turn_servers[i].ports.size());
EXPECT_EQ(1u, raw_port_allocator_->turn_servers()[i].ports.size());
EXPECT_EQ(
turn_servers[i].ports[0].address.ToString(),
raw_port_allocator_->turn_servers()[i].ports[0].address.ToString());
EXPECT_EQ(turn_servers[i].ports[0].proto,
raw_port_allocator_->turn_servers()[i].ports[0].proto);
EXPECT_EQ(turn_servers[i].credentials.username,
raw_port_allocator_->turn_servers()[i].credentials.username);
EXPECT_EQ(turn_servers[i].credentials.password,
raw_port_allocator_->turn_servers()[i].credentials.password);
}
}
void VerifyAudioCodecCapability(const RtpCodecCapability& codec) {
EXPECT_EQ(codec.kind, cricket::MEDIA_TYPE_AUDIO);
EXPECT_FALSE(codec.name.empty());
EXPECT_GT(codec.clock_rate, 0);
EXPECT_GT(codec.num_channels, 0);
}
void VerifyVideoCodecCapability(const RtpCodecCapability& codec,
bool sender) {
EXPECT_EQ(codec.kind, cricket::MEDIA_TYPE_VIDEO);
EXPECT_FALSE(codec.name.empty());
EXPECT_GT(codec.clock_rate, 0);
if (sender) {
if (codec.name == "VP8" || codec.name == "H264") {
EXPECT_THAT(
codec.scalability_modes,
UnorderedElementsAre(ScalabilityMode::kL1T1, ScalabilityMode::kL1T2,
ScalabilityMode::kL1T3))
<< "Codec: " << codec.name;
} else if (codec.name == "VP9" || codec.name == "AV1") {
EXPECT_THAT(
codec.scalability_modes,
UnorderedElementsAre(
// clang-format off
ScalabilityMode::kL1T1,
ScalabilityMode::kL1T2,
ScalabilityMode::kL1T3,
ScalabilityMode::kL2T1,
ScalabilityMode::kL2T1h,
ScalabilityMode::kL2T1_KEY,
ScalabilityMode::kL2T2,
ScalabilityMode::kL2T2h,
ScalabilityMode::kL2T2_KEY,
ScalabilityMode::kL2T2_KEY_SHIFT,
ScalabilityMode::kL2T3,
ScalabilityMode::kL2T3h,
ScalabilityMode::kL2T3_KEY,
ScalabilityMode::kL3T1,
ScalabilityMode::kL3T1h,
ScalabilityMode::kL3T1_KEY,
ScalabilityMode::kL3T2,
ScalabilityMode::kL3T2h,
ScalabilityMode::kL3T2_KEY,
ScalabilityMode::kL3T3,
ScalabilityMode::kL3T3h,
ScalabilityMode::kL3T3_KEY,
ScalabilityMode::kS2T1,
ScalabilityMode::kS2T1h,
ScalabilityMode::kS2T2,
ScalabilityMode::kS2T2h,
ScalabilityMode::kS2T3,
ScalabilityMode::kS2T3h,
ScalabilityMode::kS3T1,
ScalabilityMode::kS3T1h,
ScalabilityMode::kS3T2,
ScalabilityMode::kS3T2h,
ScalabilityMode::kS3T3,
ScalabilityMode::kS3T3h)
// clang-format on
)
<< "Codec: " << codec.name;
} else {
EXPECT_TRUE(codec.scalability_modes.empty());
}
} else {
EXPECT_TRUE(codec.scalability_modes.empty());
}
}
test::ScopedKeyValueConfig field_trials_;
std::unique_ptr<rtc::SocketServer> socket_server_;
rtc::AutoSocketServerThread main_thread_;
rtc::scoped_refptr<PeerConnectionFactoryInterface> factory_;
NullPeerConnectionObserver observer_;
std::unique_ptr<rtc::PacketSocketFactory> packet_socket_factory_;
std::unique_ptr<cricket::FakePortAllocator> port_allocator_;
// Since the PC owns the port allocator after it's been initialized,
// this should only be used when known to be safe.
cricket::FakePortAllocator* raw_port_allocator_;
};
// Since there is no public PeerConnectionFactory API to control RTX usage, need
// to reconstruct factory with our own ConnectionContext.
rtc::scoped_refptr<PeerConnectionFactoryInterface>
CreatePeerConnectionFactoryWithRtxDisabled() {
PeerConnectionFactoryDependencies pcf_dependencies;
pcf_dependencies.signaling_thread = rtc::Thread::Current();
pcf_dependencies.worker_thread = rtc::Thread::Current();
pcf_dependencies.network_thread = rtc::Thread::Current();
pcf_dependencies.task_queue_factory = CreateDefaultTaskQueueFactory();
pcf_dependencies.adm = FakeAudioCaptureModule::Create();
pcf_dependencies.audio_encoder_factory = CreateBuiltinAudioEncoderFactory();
pcf_dependencies.audio_decoder_factory = CreateBuiltinAudioDecoderFactory();
pcf_dependencies.video_encoder_factory =
std::make_unique<VideoEncoderFactoryTemplate<
LibvpxVp8EncoderTemplateAdapter, LibvpxVp9EncoderTemplateAdapter,
OpenH264EncoderTemplateAdapter, LibaomAv1EncoderTemplateAdapter>>();
pcf_dependencies.video_decoder_factory =
std::make_unique<VideoDecoderFactoryTemplate<
LibvpxVp8DecoderTemplateAdapter, LibvpxVp9DecoderTemplateAdapter,
OpenH264DecoderTemplateAdapter, Dav1dDecoderTemplateAdapter>>(),
EnableMedia(pcf_dependencies);
rtc::scoped_refptr<ConnectionContext> context =
ConnectionContext::Create(CreateEnvironment(), &pcf_dependencies);
context->set_use_rtx(false);
return rtc::make_ref_counted<PeerConnectionFactory>(context,
&pcf_dependencies);
}
// Verify creation of PeerConnection using internal ADM, video factory and
// internal libjingle threads.
// TODO(henrika): disabling this test since relying on real audio can result in
// flaky tests and focus on details that are out of scope for you might expect
// for a PeerConnectionFactory unit test.
// See https://bugs.chromium.org/p/webrtc/issues/detail?id=7806 for details.
TEST(PeerConnectionFactoryTestInternal, DISABLED_CreatePCUsingInternalModules) {
#ifdef WEBRTC_ANDROID
InitializeAndroidObjects();
#endif
rtc::scoped_refptr<PeerConnectionFactoryInterface> factory(
CreatePeerConnectionFactory(
nullptr /* network_thread */, nullptr /* worker_thread */,
nullptr /* signaling_thread */, nullptr /* default_adm */,
CreateBuiltinAudioEncoderFactory(),
CreateBuiltinAudioDecoderFactory(),
nullptr /* video_encoder_factory */,
nullptr /* video_decoder_factory */, nullptr /* audio_mixer */,
nullptr /* audio_processing */));
NullPeerConnectionObserver observer;
PeerConnectionInterface::RTCConfiguration config;
config.sdp_semantics = SdpSemantics::kUnifiedPlan;
std::unique_ptr<FakeRTCCertificateGenerator> cert_generator(
new FakeRTCCertificateGenerator());
PeerConnectionDependencies pc_dependencies(&observer);
pc_dependencies.cert_generator = std::move(cert_generator);
auto result =
factory->CreatePeerConnectionOrError(config, std::move(pc_dependencies));
EXPECT_TRUE(result.ok());
}
TEST_F(PeerConnectionFactoryTest, CheckRtpSenderAudioCapabilities) {
RtpCapabilities audio_capabilities =
factory_->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_AUDIO);
EXPECT_FALSE(audio_capabilities.codecs.empty());
for (const auto& codec : audio_capabilities.codecs) {
VerifyAudioCodecCapability(codec);
}
EXPECT_FALSE(audio_capabilities.header_extensions.empty());
for (const auto& header_extension : audio_capabilities.header_extensions) {
EXPECT_FALSE(header_extension.uri.empty());
}
}
TEST_F(PeerConnectionFactoryTest, CheckRtpSenderVideoCapabilities) {
RtpCapabilities video_capabilities =
factory_->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO);
EXPECT_FALSE(video_capabilities.codecs.empty());
for (const auto& codec : video_capabilities.codecs) {
VerifyVideoCodecCapability(codec, true);
}
EXPECT_FALSE(video_capabilities.header_extensions.empty());
for (const auto& header_extension : video_capabilities.header_extensions) {
EXPECT_FALSE(header_extension.uri.empty());
}
}
TEST_F(PeerConnectionFactoryTest, CheckRtpSenderRtxEnabledCapabilities) {
RtpCapabilities video_capabilities =
factory_->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO);
const auto it = std::find_if(
video_capabilities.codecs.begin(), video_capabilities.codecs.end(),
[](const auto& c) { return c.name == cricket::kRtxCodecName; });
EXPECT_TRUE(it != video_capabilities.codecs.end());
}
TEST(PeerConnectionFactoryTestInternal, CheckRtpSenderRtxDisabledCapabilities) {
auto factory = CreatePeerConnectionFactoryWithRtxDisabled();
RtpCapabilities video_capabilities =
factory->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO);
const auto it = std::find_if(
video_capabilities.codecs.begin(), video_capabilities.codecs.end(),
[](const auto& c) { return c.name == cricket::kRtxCodecName; });
EXPECT_TRUE(it == video_capabilities.codecs.end());
}
TEST_F(PeerConnectionFactoryTest, CheckRtpSenderDataCapabilities) {
RtpCapabilities data_capabilities =
factory_->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_DATA);
EXPECT_TRUE(data_capabilities.codecs.empty());
EXPECT_TRUE(data_capabilities.header_extensions.empty());
}
TEST_F(PeerConnectionFactoryTest, CheckRtpReceiverAudioCapabilities) {
RtpCapabilities audio_capabilities =
factory_->GetRtpReceiverCapabilities(cricket::MEDIA_TYPE_AUDIO);
EXPECT_FALSE(audio_capabilities.codecs.empty());
for (const auto& codec : audio_capabilities.codecs) {
VerifyAudioCodecCapability(codec);
}
EXPECT_FALSE(audio_capabilities.header_extensions.empty());
for (const auto& header_extension : audio_capabilities.header_extensions) {
EXPECT_FALSE(header_extension.uri.empty());
}
}
TEST_F(PeerConnectionFactoryTest, CheckRtpReceiverVideoCapabilities) {
RtpCapabilities video_capabilities =
factory_->GetRtpReceiverCapabilities(cricket::MEDIA_TYPE_VIDEO);
EXPECT_FALSE(video_capabilities.codecs.empty());
for (const auto& codec : video_capabilities.codecs) {
VerifyVideoCodecCapability(codec, false);
}
EXPECT_FALSE(video_capabilities.header_extensions.empty());
for (const auto& header_extension : video_capabilities.header_extensions) {
EXPECT_FALSE(header_extension.uri.empty());
}
}
TEST_F(PeerConnectionFactoryTest, CheckRtpReceiverRtxEnabledCapabilities) {
RtpCapabilities video_capabilities =
factory_->GetRtpReceiverCapabilities(cricket::MEDIA_TYPE_VIDEO);
const auto it = std::find_if(
video_capabilities.codecs.begin(), video_capabilities.codecs.end(),
[](const auto& c) { return c.name == cricket::kRtxCodecName; });
EXPECT_TRUE(it != video_capabilities.codecs.end());
}
TEST(PeerConnectionFactoryTestInternal,
CheckRtpReceiverRtxDisabledCapabilities) {
auto factory = CreatePeerConnectionFactoryWithRtxDisabled();
RtpCapabilities video_capabilities =
factory->GetRtpReceiverCapabilities(cricket::MEDIA_TYPE_VIDEO);
const auto it = std::find_if(
video_capabilities.codecs.begin(), video_capabilities.codecs.end(),
[](const auto& c) { return c.name == cricket::kRtxCodecName; });
EXPECT_TRUE(it == video_capabilities.codecs.end());
}
TEST_F(PeerConnectionFactoryTest, CheckRtpReceiverDataCapabilities) {
RtpCapabilities data_capabilities =
factory_->GetRtpReceiverCapabilities(cricket::MEDIA_TYPE_DATA);
EXPECT_TRUE(data_capabilities.codecs.empty());
EXPECT_TRUE(data_capabilities.header_extensions.empty());
}
// This test verifies creation of PeerConnection with valid STUN and TURN
// configuration. Also verifies the URL's parsed correctly as expected.
TEST_F(PeerConnectionFactoryTest, CreatePCUsingIceServers) {
PeerConnectionInterface::RTCConfiguration config;
config.sdp_semantics = SdpSemantics::kUnifiedPlan;
PeerConnectionInterface::IceServer ice_server;
ice_server.uri = kStunIceServer;
config.servers.push_back(ice_server);
ice_server.uri = kTurnIceServer;
ice_server.username = kTurnUsername;
ice_server.password = kTurnPassword;
config.servers.push_back(ice_server);
ice_server.uri = kTurnIceServerWithTransport;
ice_server.username = kTurnUsername;
ice_server.password = kTurnPassword;
config.servers.push_back(ice_server);
PeerConnectionDependencies pc_dependencies(&observer_);
pc_dependencies.cert_generator =
std::make_unique<FakeRTCCertificateGenerator>();
pc_dependencies.allocator = std::move(port_allocator_);
auto result =
factory_->CreatePeerConnectionOrError(config, std::move(pc_dependencies));
ASSERT_TRUE(result.ok());
cricket::ServerAddresses stun_servers;
rtc::SocketAddress stun1("stun.l.google.com", 19302);
stun_servers.insert(stun1);
VerifyStunServers(stun_servers);
std::vector<cricket::RelayServerConfig> turn_servers;
cricket::RelayServerConfig turn1("test.com", 1234, kTurnUsername,
kTurnPassword, cricket::PROTO_UDP);
turn_servers.push_back(turn1);
cricket::RelayServerConfig turn2("hello.com", kDefaultStunPort, kTurnUsername,
kTurnPassword, cricket::PROTO_TCP);
turn_servers.push_back(turn2);
VerifyTurnServers(turn_servers);
}
// This test verifies creation of PeerConnection with valid STUN and TURN
// configuration. Also verifies the list of URL's parsed correctly as expected.
TEST_F(PeerConnectionFactoryTest, CreatePCUsingIceServersUrls) {
PeerConnectionInterface::RTCConfiguration config;
config.sdp_semantics = SdpSemantics::kUnifiedPlan;
PeerConnectionInterface::IceServer ice_server;
ice_server.urls.push_back(kStunIceServer);
ice_server.urls.push_back(kTurnIceServer);
ice_server.urls.push_back(kTurnIceServerWithTransport);
ice_server.username = kTurnUsername;
ice_server.password = kTurnPassword;
config.servers.push_back(ice_server);
PeerConnectionDependencies pc_dependencies(&observer_);
pc_dependencies.cert_generator =
std::make_unique<FakeRTCCertificateGenerator>();
pc_dependencies.allocator = std::move(port_allocator_);
auto result =
factory_->CreatePeerConnectionOrError(config, std::move(pc_dependencies));
ASSERT_TRUE(result.ok());
cricket::ServerAddresses stun_servers;
rtc::SocketAddress stun1("stun.l.google.com", 19302);
stun_servers.insert(stun1);
VerifyStunServers(stun_servers);
std::vector<cricket::RelayServerConfig> turn_servers;
cricket::RelayServerConfig turn1("test.com", 1234, kTurnUsername,
kTurnPassword, cricket::PROTO_UDP);
turn_servers.push_back(turn1);
cricket::RelayServerConfig turn2("hello.com", kDefaultStunPort, kTurnUsername,
kTurnPassword, cricket::PROTO_TCP);
turn_servers.push_back(turn2);
VerifyTurnServers(turn_servers);
}
TEST_F(PeerConnectionFactoryTest, CreatePCUsingNoUsernameInUri) {
PeerConnectionInterface::RTCConfiguration config;
config.sdp_semantics = SdpSemantics::kUnifiedPlan;
PeerConnectionInterface::IceServer ice_server;
ice_server.uri = kStunIceServer;
config.servers.push_back(ice_server);
ice_server.uri = kTurnIceServerWithNoUsernameInUri;
ice_server.username = kTurnUsername;
ice_server.password = kTurnPassword;
config.servers.push_back(ice_server);
PeerConnectionDependencies pc_dependencies(&observer_);
pc_dependencies.cert_generator =
std::make_unique<FakeRTCCertificateGenerator>();
pc_dependencies.allocator = std::move(port_allocator_);
auto result =
factory_->CreatePeerConnectionOrError(config, std::move(pc_dependencies));
ASSERT_TRUE(result.ok());
std::vector<cricket::RelayServerConfig> turn_servers;
cricket::RelayServerConfig turn("test.com", 1234, kTurnUsername,
kTurnPassword, cricket::PROTO_UDP);
turn_servers.push_back(turn);
VerifyTurnServers(turn_servers);
}
// This test verifies the PeerConnection created properly with TURN url which
// has transport parameter in it.
TEST_F(PeerConnectionFactoryTest, CreatePCUsingTurnUrlWithTransportParam) {
PeerConnectionInterface::RTCConfiguration config;
config.sdp_semantics = SdpSemantics::kUnifiedPlan;
PeerConnectionInterface::IceServer ice_server;
ice_server.uri = kTurnIceServerWithTransport;
ice_server.username = kTurnUsername;
ice_server.password = kTurnPassword;
config.servers.push_back(ice_server);
PeerConnectionDependencies pc_dependencies(&observer_);
pc_dependencies.cert_generator =
std::make_unique<FakeRTCCertificateGenerator>();
pc_dependencies.allocator = std::move(port_allocator_);
auto result =
factory_->CreatePeerConnectionOrError(config, std::move(pc_dependencies));
ASSERT_TRUE(result.ok());
std::vector<cricket::RelayServerConfig> turn_servers;
cricket::RelayServerConfig turn("hello.com", kDefaultStunPort, kTurnUsername,
kTurnPassword, cricket::PROTO_TCP);
turn_servers.push_back(turn);
VerifyTurnServers(turn_servers);
}
TEST_F(PeerConnectionFactoryTest, CreatePCUsingSecureTurnUrl) {
PeerConnectionInterface::RTCConfiguration config;
config.sdp_semantics = SdpSemantics::kUnifiedPlan;
PeerConnectionInterface::IceServer ice_server;
ice_server.uri = kSecureTurnIceServer;
ice_server.username = kTurnUsername;
ice_server.password = kTurnPassword;
config.servers.push_back(ice_server);
ice_server.uri = kSecureTurnIceServerWithoutTransportParam;
ice_server.username = kTurnUsername;
ice_server.password = kTurnPassword;
config.servers.push_back(ice_server);
ice_server.uri = kSecureTurnIceServerWithoutTransportAndPortParam;
ice_server.username = kTurnUsername;
ice_server.password = kTurnPassword;
config.servers.push_back(ice_server);
PeerConnectionDependencies pc_dependencies(&observer_);
pc_dependencies.cert_generator =
std::make_unique<FakeRTCCertificateGenerator>();
pc_dependencies.allocator = std::move(port_allocator_);
auto result =
factory_->CreatePeerConnectionOrError(config, std::move(pc_dependencies));
ASSERT_TRUE(result.ok());
std::vector<cricket::RelayServerConfig> turn_servers;
cricket::RelayServerConfig turn1("hello.com", kDefaultStunTlsPort,
kTurnUsername, kTurnPassword,
cricket::PROTO_TLS);
turn_servers.push_back(turn1);
// TURNS with transport param should be default to tcp.
cricket::RelayServerConfig turn2("hello.com", 443, kTurnUsername,
kTurnPassword, cricket::PROTO_TLS);
turn_servers.push_back(turn2);
cricket::RelayServerConfig turn3("hello.com", kDefaultStunTlsPort,
kTurnUsername, kTurnPassword,
cricket::PROTO_TLS);
turn_servers.push_back(turn3);
VerifyTurnServers(turn_servers);
}
TEST_F(PeerConnectionFactoryTest, CreatePCUsingIPLiteralAddress) {
PeerConnectionInterface::RTCConfiguration config;
config.sdp_semantics = SdpSemantics::kUnifiedPlan;
PeerConnectionInterface::IceServer ice_server;
ice_server.uri = kStunIceServerWithIPv4Address;
config.servers.push_back(ice_server);
ice_server.uri = kStunIceServerWithIPv4AddressWithoutPort;
config.servers.push_back(ice_server);
ice_server.uri = kStunIceServerWithIPv6Address;
config.servers.push_back(ice_server);
ice_server.uri = kStunIceServerWithIPv6AddressWithoutPort;
config.servers.push_back(ice_server);
ice_server.uri = kTurnIceServerWithIPv6Address;
ice_server.username = kTurnUsername;
ice_server.password = kTurnPassword;
config.servers.push_back(ice_server);
PeerConnectionDependencies pc_dependencies(&observer_);
pc_dependencies.cert_generator =
std::make_unique<FakeRTCCertificateGenerator>();
pc_dependencies.allocator = std::move(port_allocator_);
auto result =
factory_->CreatePeerConnectionOrError(config, std::move(pc_dependencies));
ASSERT_TRUE(result.ok());
cricket::ServerAddresses stun_servers;
rtc::SocketAddress stun1("1.2.3.4", 1234);
stun_servers.insert(stun1);
rtc::SocketAddress stun2("1.2.3.4", 3478);
stun_servers.insert(stun2); // Default port
rtc::SocketAddress stun3("2401:fa00:4::", 1234);
stun_servers.insert(stun3);
rtc::SocketAddress stun4("2401:fa00:4::", 3478);
stun_servers.insert(stun4); // Default port
VerifyStunServers(stun_servers);
std::vector<cricket::RelayServerConfig> turn_servers;
cricket::RelayServerConfig turn1("2401:fa00:4::", 1234, kTurnUsername,
kTurnPassword, cricket::PROTO_UDP);
turn_servers.push_back(turn1);
VerifyTurnServers(turn_servers);
}
// This test verifies the captured stream is rendered locally using a
// local video track.
TEST_F(PeerConnectionFactoryTest, LocalRendering) {
rtc::scoped_refptr<FakeVideoTrackSource> source =
FakeVideoTrackSource::Create(/*is_screencast=*/false);
cricket::FakeFrameSource frame_source(1280, 720,
rtc::kNumMicrosecsPerSec / 30);
ASSERT_TRUE(source.get() != NULL);
rtc::scoped_refptr<VideoTrackInterface> track(
factory_->CreateVideoTrack(source, "testlabel"));
ASSERT_TRUE(track.get() != NULL);
FakeVideoTrackRenderer local_renderer(track.get());
EXPECT_EQ(0, local_renderer.num_rendered_frames());
source->InjectFrame(frame_source.GetFrame());
EXPECT_EQ(1, local_renderer.num_rendered_frames());
EXPECT_FALSE(local_renderer.black_frame());
track->set_enabled(false);
source->InjectFrame(frame_source.GetFrame());
EXPECT_EQ(2, local_renderer.num_rendered_frames());
EXPECT_TRUE(local_renderer.black_frame());
track->set_enabled(true);
source->InjectFrame(frame_source.GetFrame());
EXPECT_EQ(3, local_renderer.num_rendered_frames());
EXPECT_FALSE(local_renderer.black_frame());
}
TEST(PeerConnectionFactoryDependenciesTest, UsesNetworkManager) {
constexpr TimeDelta kWaitTimeout = TimeDelta::Seconds(10);
auto mock_network_manager = std::make_unique<NiceMock<MockNetworkManager>>();
rtc::Event called;
EXPECT_CALL(*mock_network_manager, StartUpdating())
.Times(AtLeast(1))
.WillRepeatedly(InvokeWithoutArgs([&] { called.Set(); }));
PeerConnectionFactoryDependencies pcf_dependencies;
pcf_dependencies.network_manager = std::move(mock_network_manager);
rtc::scoped_refptr<PeerConnectionFactoryInterface> pcf =
CreateModularPeerConnectionFactory(std::move(pcf_dependencies));
PeerConnectionInterface::RTCConfiguration config;
config.ice_candidate_pool_size = 2;
NullPeerConnectionObserver observer;
auto pc = pcf->CreatePeerConnectionOrError(
config, PeerConnectionDependencies(&observer));
ASSERT_TRUE(pc.ok());
called.Wait(kWaitTimeout);
}
TEST(PeerConnectionFactoryDependenciesTest, UsesPacketSocketFactory) {
constexpr TimeDelta kWaitTimeout = TimeDelta::Seconds(10);
auto mock_socket_factory =
std::make_unique<NiceMock<rtc::MockPacketSocketFactory>>();
rtc::Event called;
EXPECT_CALL(*mock_socket_factory, CreateUdpSocket(_, _, _))
.WillOnce(InvokeWithoutArgs([&] {
called.Set();
return nullptr;
}))
.WillRepeatedly(Return(nullptr));
PeerConnectionFactoryDependencies pcf_dependencies;
pcf_dependencies.packet_socket_factory = std::move(mock_socket_factory);
rtc::scoped_refptr<PeerConnectionFactoryInterface> pcf =
CreateModularPeerConnectionFactory(std::move(pcf_dependencies));
// By default, localhost addresses are ignored, which makes tests fail if test
// machine is offline.
PeerConnectionFactoryInterface::Options options;
options.network_ignore_mask = 0;
pcf->SetOptions(options);
PeerConnectionInterface::RTCConfiguration config;
config.ice_candidate_pool_size = 2;
NullPeerConnectionObserver observer;
auto pc = pcf->CreatePeerConnectionOrError(
config, PeerConnectionDependencies(&observer));
ASSERT_TRUE(pc.ok());
called.Wait(kWaitTimeout);
}
} // namespace
} // namespace webrtc

View file

@ -0,0 +1,272 @@
/*
* Copyright (c) 2022 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.
*/
// This file contains tests that verify that field trials do what they're
// supposed to do.
#include <set>
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
#include "api/create_peerconnection_factory.h"
#include "api/enable_media_with_defaults.h"
#include "api/peer_connection_interface.h"
#include "api/stats/rtcstats_objects.h"
#include "api/task_queue/default_task_queue_factory.h"
#include "api/video_codecs/builtin_video_decoder_factory.h"
#include "api/video_codecs/builtin_video_encoder_factory.h"
#include "media/engine/webrtc_media_engine.h"
#include "pc/peer_connection_wrapper.h"
#include "pc/session_description.h"
#include "pc/test/fake_audio_capture_module.h"
#include "pc/test/frame_generator_capturer_video_track_source.h"
#include "pc/test/peer_connection_test_wrapper.h"
#include "rtc_base/gunit.h"
#include "rtc_base/internal/default_socket_server.h"
#include "rtc_base/physical_socket_server.h"
#include "rtc_base/thread.h"
#include "test/gtest.h"
#include "test/scoped_key_value_config.h"
#ifdef WEBRTC_ANDROID
#include "pc/test/android_test_initializer.h"
#endif
namespace webrtc {
namespace {
static const int kDefaultTimeoutMs = 5000;
bool AddIceCandidates(PeerConnectionWrapper* peer,
std::vector<const IceCandidateInterface*> candidates) {
for (const auto candidate : candidates) {
if (!peer->pc()->AddIceCandidate(candidate)) {
return false;
}
}
return true;
}
} // namespace
using RTCConfiguration = PeerConnectionInterface::RTCConfiguration;
class PeerConnectionFieldTrialTest : public ::testing::Test {
protected:
typedef std::unique_ptr<PeerConnectionWrapper> WrapperPtr;
PeerConnectionFieldTrialTest()
: clock_(Clock::GetRealTimeClock()),
socket_server_(rtc::CreateDefaultSocketServer()),
main_thread_(socket_server_.get()) {
#ifdef WEBRTC_ANDROID
InitializeAndroidObjects();
#endif
PeerConnectionInterface::IceServer ice_server;
ice_server.uri = "stun:stun.l.google.com:19302";
config_.servers.push_back(ice_server);
config_.sdp_semantics = SdpSemantics::kUnifiedPlan;
}
void TearDown() override { pc_factory_ = nullptr; }
void CreatePCFactory(std::unique_ptr<FieldTrialsView> field_trials) {
PeerConnectionFactoryDependencies pcf_deps;
pcf_deps.signaling_thread = rtc::Thread::Current();
pcf_deps.trials = std::move(field_trials);
pcf_deps.task_queue_factory = CreateDefaultTaskQueueFactory();
pcf_deps.adm = FakeAudioCaptureModule::Create();
EnableMediaWithDefaults(pcf_deps);
pc_factory_ = CreateModularPeerConnectionFactory(std::move(pcf_deps));
// Allow ADAPTER_TYPE_LOOPBACK to create PeerConnections with loopback in
// this test.
RTC_DCHECK(pc_factory_);
PeerConnectionFactoryInterface::Options options;
options.network_ignore_mask = 0;
pc_factory_->SetOptions(options);
}
WrapperPtr CreatePeerConnection() {
auto observer = std::make_unique<MockPeerConnectionObserver>();
auto result = pc_factory_->CreatePeerConnectionOrError(
config_, PeerConnectionDependencies(observer.get()));
RTC_CHECK(result.ok());
observer->SetPeerConnectionInterface(result.value().get());
return std::make_unique<PeerConnectionWrapper>(
pc_factory_, result.MoveValue(), std::move(observer));
}
Clock* const clock_;
std::unique_ptr<rtc::SocketServer> socket_server_;
rtc::AutoSocketServerThread main_thread_;
rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory_ = nullptr;
PeerConnectionInterface::RTCConfiguration config_;
};
// Tests for the dependency descriptor field trial. The dependency descriptor
// field trial is implemented in media/engine/webrtc_video_engine.cc.
TEST_F(PeerConnectionFieldTrialTest, EnableDependencyDescriptorAdvertised) {
std::unique_ptr<test::ScopedKeyValueConfig> field_trials =
std::make_unique<test::ScopedKeyValueConfig>(
"WebRTC-DependencyDescriptorAdvertised/Enabled/");
CreatePCFactory(std::move(field_trials));
WrapperPtr caller = CreatePeerConnection();
caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO);
auto offer = caller->CreateOffer();
auto contents1 = offer->description()->contents();
ASSERT_EQ(1u, contents1.size());
const cricket::MediaContentDescription* media_description1 =
contents1[0].media_description();
EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, media_description1->type());
const cricket::RtpHeaderExtensions& rtp_header_extensions1 =
media_description1->rtp_header_extensions();
bool found = absl::c_find_if(rtp_header_extensions1,
[](const RtpExtension& rtp_extension) {
return rtp_extension.uri ==
RtpExtension::kDependencyDescriptorUri;
}) != rtp_header_extensions1.end();
EXPECT_TRUE(found);
}
// Tests that dependency descriptor RTP header extensions can be exchanged
// via SDP munging, even if dependency descriptor field trial is disabled.
TEST_F(PeerConnectionFieldTrialTest, InjectDependencyDescriptor) {
std::unique_ptr<test::ScopedKeyValueConfig> field_trials =
std::make_unique<test::ScopedKeyValueConfig>(
"WebRTC-DependencyDescriptorAdvertised/Disabled/");
CreatePCFactory(std::move(field_trials));
WrapperPtr caller = CreatePeerConnection();
WrapperPtr callee = CreatePeerConnection();
caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO);
auto offer = caller->CreateOffer();
cricket::ContentInfos& contents1 = offer->description()->contents();
ASSERT_EQ(1u, contents1.size());
cricket::MediaContentDescription* media_description1 =
contents1[0].media_description();
EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, media_description1->type());
cricket::RtpHeaderExtensions rtp_header_extensions1 =
media_description1->rtp_header_extensions();
bool found1 = absl::c_find_if(rtp_header_extensions1,
[](const RtpExtension& rtp_extension) {
return rtp_extension.uri ==
RtpExtension::kDependencyDescriptorUri;
}) != rtp_header_extensions1.end();
EXPECT_FALSE(found1);
std::set<int> existing_ids;
for (const RtpExtension& rtp_extension : rtp_header_extensions1) {
existing_ids.insert(rtp_extension.id);
}
// Find the currently unused RTP header extension ID.
int insert_id = 1;
std::set<int>::const_iterator iter = existing_ids.begin();
while (true) {
if (iter == existing_ids.end()) {
break;
}
if (*iter != insert_id) {
break;
}
insert_id++;
iter++;
}
rtp_header_extensions1.emplace_back(RtpExtension::kDependencyDescriptorUri,
insert_id);
media_description1->set_rtp_header_extensions(rtp_header_extensions1);
caller->SetLocalDescription(offer->Clone());
ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
auto answer = callee->CreateAnswer();
cricket::ContentInfos& contents2 = answer->description()->contents();
ASSERT_EQ(1u, contents2.size());
cricket::MediaContentDescription* media_description2 =
contents2[0].media_description();
EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, media_description2->type());
cricket::RtpHeaderExtensions rtp_header_extensions2 =
media_description2->rtp_header_extensions();
bool found2 = absl::c_find_if(rtp_header_extensions2,
[](const RtpExtension& rtp_extension) {
return rtp_extension.uri ==
RtpExtension::kDependencyDescriptorUri;
}) != rtp_header_extensions2.end();
EXPECT_TRUE(found2);
}
// Test that the ability to emulate degraded networks works without crashing.
TEST_F(PeerConnectionFieldTrialTest, ApplyFakeNetworkConfig) {
std::unique_ptr<test::ScopedKeyValueConfig> field_trials =
std::make_unique<test::ScopedKeyValueConfig>(
"WebRTC-FakeNetworkSendConfig/link_capacity_kbps:500/"
"WebRTC-FakeNetworkReceiveConfig/loss_percent:1/");
CreatePCFactory(std::move(field_trials));
WrapperPtr caller = CreatePeerConnection();
BitrateSettings bitrate_settings;
bitrate_settings.start_bitrate_bps = 1'000'000;
bitrate_settings.max_bitrate_bps = 1'000'000;
caller->pc()->SetBitrate(bitrate_settings);
FrameGeneratorCapturerVideoTrackSource::Config config;
auto video_track_source =
rtc::make_ref_counted<FrameGeneratorCapturerVideoTrackSource>(
config, clock_, /*is_screencast=*/false);
video_track_source->Start();
caller->AddTrack(pc_factory_->CreateVideoTrack(video_track_source, "v"));
WrapperPtr callee = CreatePeerConnection();
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
ASSERT_TRUE(
caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
// Do the SDP negotiation, and also exchange ice candidates.
ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
ASSERT_TRUE_WAIT(
caller->signaling_state() == PeerConnectionInterface::kStable,
kDefaultTimeoutMs);
ASSERT_TRUE_WAIT(caller->IsIceGatheringDone(), kDefaultTimeoutMs);
ASSERT_TRUE_WAIT(callee->IsIceGatheringDone(), kDefaultTimeoutMs);
// Connect an ICE candidate pairs.
ASSERT_TRUE(
AddIceCandidates(callee.get(), caller->observer()->GetAllCandidates()));
ASSERT_TRUE(
AddIceCandidates(caller.get(), callee->observer()->GetAllCandidates()));
// This means that ICE and DTLS are connected.
ASSERT_TRUE_WAIT(callee->IsIceConnected(), kDefaultTimeoutMs);
ASSERT_TRUE_WAIT(caller->IsIceConnected(), kDefaultTimeoutMs);
// Send packets for kDefaultTimeoutMs
WAIT(false, kDefaultTimeoutMs);
std::vector<const RTCOutboundRtpStreamStats*> outbound_rtp_stats =
caller->GetStats()->GetStatsOfType<RTCOutboundRtpStreamStats>();
ASSERT_GE(outbound_rtp_stats.size(), 1u);
ASSERT_TRUE(outbound_rtp_stats[0]->target_bitrate.has_value());
// Link capacity is limited to 500k, so BWE is expected to be close to 500k.
ASSERT_LE(*outbound_rtp_stats[0]->target_bitrate, 500'000 * 1.1);
}
} // namespace webrtc

View file

@ -0,0 +1,585 @@
/*
* Copyright 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <memory>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "api/jsep.h"
#include "api/media_types.h"
#include "api/peer_connection_interface.h"
#include "api/rtc_error.h"
#include "api/rtc_event_log/rtc_event_log_factory.h"
#include "api/rtc_event_log/rtc_event_log_factory_interface.h"
#include "api/rtp_parameters.h"
#include "api/rtp_transceiver_direction.h"
#include "api/rtp_transceiver_interface.h"
#include "api/scoped_refptr.h"
#include "api/task_queue/default_task_queue_factory.h"
#include "api/task_queue/task_queue_factory.h"
#include "media/base/fake_media_engine.h"
#include "media/base/media_engine.h"
#include "p2p/base/fake_port_allocator.h"
#include "p2p/base/port_allocator.h"
#include "pc/peer_connection_wrapper.h"
#include "pc/session_description.h"
#include "pc/test/enable_fake_media.h"
#include "pc/test/mock_peer_connection_observers.h"
#include "rtc_base/internal/default_socket_server.h"
#include "rtc_base/rtc_certificate_generator.h"
#include "rtc_base/strings/string_builder.h"
#include "rtc_base/thread.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/scoped_key_value_config.h"
namespace webrtc {
using ::testing::Combine;
using ::testing::ElementsAre;
using ::testing::Field;
using ::testing::Return;
using ::testing::Values;
class PeerConnectionHeaderExtensionTest
: public ::testing::TestWithParam<
std::tuple<cricket::MediaType, SdpSemantics>> {
protected:
PeerConnectionHeaderExtensionTest()
: socket_server_(rtc::CreateDefaultSocketServer()),
main_thread_(socket_server_.get()),
extensions_(
{RtpHeaderExtensionCapability("uri1",
1,
RtpTransceiverDirection::kStopped),
RtpHeaderExtensionCapability("uri2",
2,
RtpTransceiverDirection::kSendOnly),
RtpHeaderExtensionCapability("uri3",
3,
RtpTransceiverDirection::kRecvOnly),
RtpHeaderExtensionCapability(
"uri4",
4,
RtpTransceiverDirection::kSendRecv)}) {}
std::unique_ptr<PeerConnectionWrapper> CreatePeerConnection(
cricket::MediaType media_type,
absl::optional<SdpSemantics> semantics) {
auto media_engine = std::make_unique<cricket::FakeMediaEngine>();
if (media_type == cricket::MediaType::MEDIA_TYPE_AUDIO)
media_engine->fake_voice_engine()->SetRtpHeaderExtensions(extensions_);
else
media_engine->fake_video_engine()->SetRtpHeaderExtensions(extensions_);
PeerConnectionFactoryDependencies factory_dependencies;
factory_dependencies.network_thread = rtc::Thread::Current();
factory_dependencies.worker_thread = rtc::Thread::Current();
factory_dependencies.signaling_thread = rtc::Thread::Current();
factory_dependencies.task_queue_factory = CreateDefaultTaskQueueFactory();
EnableFakeMedia(factory_dependencies, std::move(media_engine));
factory_dependencies.event_log_factory =
std::make_unique<RtcEventLogFactory>();
auto pc_factory =
CreateModularPeerConnectionFactory(std::move(factory_dependencies));
auto fake_port_allocator = std::make_unique<cricket::FakePortAllocator>(
rtc::Thread::Current(),
std::make_unique<rtc::BasicPacketSocketFactory>(socket_server_.get()),
&field_trials_);
auto observer = std::make_unique<MockPeerConnectionObserver>();
PeerConnectionInterface::RTCConfiguration config;
if (semantics)
config.sdp_semantics = *semantics;
PeerConnectionDependencies pc_dependencies(observer.get());
pc_dependencies.allocator = std::move(fake_port_allocator);
auto result = pc_factory->CreatePeerConnectionOrError(
config, std::move(pc_dependencies));
EXPECT_TRUE(result.ok());
observer->SetPeerConnectionInterface(result.value().get());
return std::make_unique<PeerConnectionWrapper>(
pc_factory, result.MoveValue(), std::move(observer));
}
test::ScopedKeyValueConfig field_trials_;
std::unique_ptr<rtc::SocketServer> socket_server_;
rtc::AutoSocketServerThread main_thread_;
std::vector<RtpHeaderExtensionCapability> extensions_;
};
TEST_P(PeerConnectionHeaderExtensionTest, TransceiverOffersHeaderExtensions) {
cricket::MediaType media_type;
SdpSemantics semantics;
std::tie(media_type, semantics) = GetParam();
if (semantics != SdpSemantics::kUnifiedPlan)
return;
std::unique_ptr<PeerConnectionWrapper> wrapper =
CreatePeerConnection(media_type, semantics);
auto transceiver = wrapper->AddTransceiver(media_type);
EXPECT_EQ(transceiver->GetHeaderExtensionsToNegotiate(), extensions_);
}
TEST_P(PeerConnectionHeaderExtensionTest,
SenderReceiverCapabilitiesReturnNotStoppedExtensions) {
cricket::MediaType media_type;
SdpSemantics semantics;
std::tie(media_type, semantics) = GetParam();
std::unique_ptr<PeerConnectionWrapper> wrapper =
CreatePeerConnection(media_type, semantics);
EXPECT_THAT(wrapper->pc_factory()
->GetRtpSenderCapabilities(media_type)
.header_extensions,
ElementsAre(Field(&RtpHeaderExtensionCapability::uri, "uri2"),
Field(&RtpHeaderExtensionCapability::uri, "uri3"),
Field(&RtpHeaderExtensionCapability::uri, "uri4")));
EXPECT_EQ(wrapper->pc_factory()
->GetRtpReceiverCapabilities(media_type)
.header_extensions,
wrapper->pc_factory()
->GetRtpSenderCapabilities(media_type)
.header_extensions);
}
TEST_P(PeerConnectionHeaderExtensionTest, OffersUnstoppedDefaultExtensions) {
cricket::MediaType media_type;
SdpSemantics semantics;
std::tie(media_type, semantics) = GetParam();
if (semantics != SdpSemantics::kUnifiedPlan)
return;
std::unique_ptr<PeerConnectionWrapper> wrapper =
CreatePeerConnection(media_type, semantics);
auto transceiver = wrapper->AddTransceiver(media_type);
auto session_description = wrapper->CreateOffer();
EXPECT_THAT(session_description->description()
->contents()[0]
.media_description()
->rtp_header_extensions(),
ElementsAre(Field(&RtpExtension::uri, "uri2"),
Field(&RtpExtension::uri, "uri3"),
Field(&RtpExtension::uri, "uri4")));
}
TEST_P(PeerConnectionHeaderExtensionTest, OffersUnstoppedModifiedExtensions) {
cricket::MediaType media_type;
SdpSemantics semantics;
std::tie(media_type, semantics) = GetParam();
if (semantics != SdpSemantics::kUnifiedPlan)
return;
std::unique_ptr<PeerConnectionWrapper> wrapper =
CreatePeerConnection(media_type, semantics);
auto transceiver = wrapper->AddTransceiver(media_type);
auto modified_extensions = transceiver->GetHeaderExtensionsToNegotiate();
modified_extensions[0].direction = RtpTransceiverDirection::kSendRecv;
modified_extensions[3].direction = RtpTransceiverDirection::kStopped;
EXPECT_TRUE(
transceiver->SetHeaderExtensionsToNegotiate(modified_extensions).ok());
auto session_description = wrapper->CreateOffer();
EXPECT_THAT(session_description->description()
->contents()[0]
.media_description()
->rtp_header_extensions(),
ElementsAre(Field(&RtpExtension::uri, "uri1"),
Field(&RtpExtension::uri, "uri2"),
Field(&RtpExtension::uri, "uri3")));
}
TEST_P(PeerConnectionHeaderExtensionTest, AnswersUnstoppedModifiedExtensions) {
cricket::MediaType media_type;
SdpSemantics semantics;
std::tie(media_type, semantics) = GetParam();
if (semantics != SdpSemantics::kUnifiedPlan)
return;
std::unique_ptr<PeerConnectionWrapper> pc1 =
CreatePeerConnection(media_type, semantics);
std::unique_ptr<PeerConnectionWrapper> pc2 =
CreatePeerConnection(media_type, semantics);
auto transceiver1 = pc1->AddTransceiver(media_type);
auto offer = pc1->CreateOfferAndSetAsLocal(
PeerConnectionInterface::RTCOfferAnswerOptions());
pc2->SetRemoteDescription(std::move(offer));
ASSERT_EQ(pc2->pc()->GetTransceivers().size(), 1u);
auto transceiver2 = pc2->pc()->GetTransceivers()[0];
auto modified_extensions = transceiver2->GetHeaderExtensionsToNegotiate();
// Don't offer uri4.
modified_extensions[3].direction = RtpTransceiverDirection::kStopped;
transceiver2->SetHeaderExtensionsToNegotiate(modified_extensions);
auto answer = pc2->CreateAnswerAndSetAsLocal(
PeerConnectionInterface::RTCOfferAnswerOptions());
EXPECT_THAT(answer->description()
->contents()[0]
.media_description()
->rtp_header_extensions(),
ElementsAre(Field(&RtpExtension::uri, "uri2"),
Field(&RtpExtension::uri, "uri3")));
}
TEST_P(PeerConnectionHeaderExtensionTest, NegotiatedExtensionsAreAccessible) {
cricket::MediaType media_type;
SdpSemantics semantics;
std::tie(media_type, semantics) = GetParam();
if (semantics != SdpSemantics::kUnifiedPlan)
return;
std::unique_ptr<PeerConnectionWrapper> pc1 =
CreatePeerConnection(media_type, semantics);
auto transceiver1 = pc1->AddTransceiver(media_type);
auto modified_extensions = transceiver1->GetHeaderExtensionsToNegotiate();
modified_extensions[3].direction = RtpTransceiverDirection::kStopped;
transceiver1->SetHeaderExtensionsToNegotiate(modified_extensions);
auto offer = pc1->CreateOfferAndSetAsLocal(
PeerConnectionInterface::RTCOfferAnswerOptions());
std::unique_ptr<PeerConnectionWrapper> pc2 =
CreatePeerConnection(media_type, semantics);
auto transceiver2 = pc2->AddTransceiver(media_type);
pc2->SetRemoteDescription(std::move(offer));
auto answer = pc2->CreateAnswerAndSetAsLocal(
PeerConnectionInterface::RTCOfferAnswerOptions());
pc1->SetRemoteDescription(std::move(answer));
// PC1 has exts 2-4 unstopped and PC2 has exts 1-3 unstopped -> ext 2, 3
// survives.
EXPECT_THAT(transceiver1->GetNegotiatedHeaderExtensions(),
ElementsAre(Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kStopped),
Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kSendRecv),
Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kSendRecv),
Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kStopped)));
}
TEST_P(PeerConnectionHeaderExtensionTest, OfferedExtensionsArePerTransceiver) {
cricket::MediaType media_type;
SdpSemantics semantics;
std::tie(media_type, semantics) = GetParam();
if (semantics != SdpSemantics::kUnifiedPlan)
return;
std::unique_ptr<PeerConnectionWrapper> pc1 =
CreatePeerConnection(media_type, semantics);
auto transceiver1 = pc1->AddTransceiver(media_type);
auto modified_extensions = transceiver1->GetHeaderExtensionsToNegotiate();
modified_extensions[3].direction = RtpTransceiverDirection::kStopped;
transceiver1->SetHeaderExtensionsToNegotiate(modified_extensions);
auto transceiver2 = pc1->AddTransceiver(media_type);
auto session_description = pc1->CreateOffer();
EXPECT_THAT(session_description->description()
->contents()[0]
.media_description()
->rtp_header_extensions(),
ElementsAre(Field(&RtpExtension::uri, "uri2"),
Field(&RtpExtension::uri, "uri3")));
EXPECT_THAT(session_description->description()
->contents()[1]
.media_description()
->rtp_header_extensions(),
ElementsAre(Field(&RtpExtension::uri, "uri2"),
Field(&RtpExtension::uri, "uri3"),
Field(&RtpExtension::uri, "uri4")));
}
TEST_P(PeerConnectionHeaderExtensionTest, RemovalAfterRenegotiation) {
cricket::MediaType media_type;
SdpSemantics semantics;
std::tie(media_type, semantics) = GetParam();
if (semantics != SdpSemantics::kUnifiedPlan)
return;
std::unique_ptr<PeerConnectionWrapper> pc1 =
CreatePeerConnection(media_type, semantics);
std::unique_ptr<PeerConnectionWrapper> pc2 =
CreatePeerConnection(media_type, semantics);
auto transceiver1 = pc1->AddTransceiver(media_type);
auto offer = pc1->CreateOfferAndSetAsLocal(
PeerConnectionInterface::RTCOfferAnswerOptions());
pc2->SetRemoteDescription(std::move(offer));
auto answer = pc2->CreateAnswerAndSetAsLocal(
PeerConnectionInterface::RTCOfferAnswerOptions());
pc1->SetRemoteDescription(std::move(answer));
auto modified_extensions = transceiver1->GetHeaderExtensionsToNegotiate();
modified_extensions[3].direction = RtpTransceiverDirection::kStopped;
transceiver1->SetHeaderExtensionsToNegotiate(modified_extensions);
auto session_description = pc1->CreateOffer();
EXPECT_THAT(session_description->description()
->contents()[0]
.media_description()
->rtp_header_extensions(),
ElementsAre(Field(&RtpExtension::uri, "uri2"),
Field(&RtpExtension::uri, "uri3")));
}
TEST_P(PeerConnectionHeaderExtensionTest,
StoppedByDefaultExtensionCanBeActivatedByRemoteSdp) {
cricket::MediaType media_type;
SdpSemantics semantics;
std::tie(media_type, semantics) = GetParam();
if (semantics != SdpSemantics::kUnifiedPlan)
return;
std::unique_ptr<PeerConnectionWrapper> pc1 =
CreatePeerConnection(media_type, semantics);
std::unique_ptr<PeerConnectionWrapper> pc2 =
CreatePeerConnection(media_type, semantics);
auto transceiver1 = pc1->AddTransceiver(media_type);
auto offer = pc1->CreateOfferAndSetAsLocal(
PeerConnectionInterface::RTCOfferAnswerOptions());
pc2->SetRemoteDescription(std::move(offer));
auto answer = pc2->CreateAnswerAndSetAsLocal(
PeerConnectionInterface::RTCOfferAnswerOptions());
std::string sdp;
ASSERT_TRUE(answer->ToString(&sdp));
// We support uri1 but it is stopped by default. Let the remote reactivate it.
sdp += "a=extmap:15 uri1\r\n";
auto modified_answer = CreateSessionDescription(SdpType::kAnswer, sdp);
pc1->SetRemoteDescription(std::move(modified_answer));
EXPECT_THAT(transceiver1->GetNegotiatedHeaderExtensions(),
ElementsAre(Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kSendRecv),
Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kSendRecv),
Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kSendRecv),
Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kSendRecv)));
}
TEST_P(PeerConnectionHeaderExtensionTest,
UnknownExtensionInRemoteOfferDoesNotShowUp) {
cricket::MediaType media_type;
SdpSemantics semantics;
std::tie(media_type, semantics) = GetParam();
if (semantics != SdpSemantics::kUnifiedPlan)
return;
std::unique_ptr<PeerConnectionWrapper> pc =
CreatePeerConnection(media_type, semantics);
std::string sdp =
"v=0\r\n"
"o=- 0 3 IN IP4 127.0.0.1\r\n"
"s=-\r\n"
"t=0 0\r\n"
"a=fingerprint:sha-256 "
"A7:24:72:CA:6E:02:55:39:BA:66:DF:6E:CC:4C:D8:B0:1A:BF:1A:56:65:7D:F4:03:"
"AD:7E:77:43:2A:29:EC:93\r\n"
"a=ice-ufrag:6HHHdzzeIhkE0CKj\r\n"
"a=ice-pwd:XYDGVpfvklQIEnZ6YnyLsAew\r\n";
if (media_type == cricket::MEDIA_TYPE_AUDIO) {
sdp +=
"m=audio 9 RTP/AVPF 111\r\n"
"a=rtpmap:111 fake_audio_codec/8000\r\n";
} else {
sdp +=
"m=video 9 RTP/AVPF 111\r\n"
"a=rtpmap:111 fake_video_codec/90000\r\n";
}
sdp +=
"c=IN IP4 0.0.0.0\r\n"
"a=rtcp-mux\r\n"
"a=sendonly\r\n"
"a=mid:audio\r\n"
"a=setup:actpass\r\n"
"a=extmap:1 urn:bogus\r\n";
auto offer = CreateSessionDescription(SdpType::kOffer, sdp);
pc->SetRemoteDescription(std::move(offer));
pc->CreateAnswerAndSetAsLocal(
PeerConnectionInterface::RTCOfferAnswerOptions());
ASSERT_GT(pc->pc()->GetTransceivers().size(), 0u);
auto transceiver = pc->pc()->GetTransceivers()[0];
auto negotiated = transceiver->GetNegotiatedHeaderExtensions();
EXPECT_EQ(negotiated.size(),
transceiver->GetHeaderExtensionsToNegotiate().size());
// All extensions are stopped, the "bogus" one does not show up.
for (const auto& extension : negotiated) {
EXPECT_EQ(extension.direction, RtpTransceiverDirection::kStopped);
EXPECT_NE(extension.uri, "urn:bogus");
}
}
// These tests are regression tests for behavior that the API
// enables in a proper way. It conflicts with the behavior
// of the API to only offer non-stopped extensions.
TEST_P(PeerConnectionHeaderExtensionTest,
SdpMungingAnswerWithoutApiUsageEnablesExtensions) {
cricket::MediaType media_type;
SdpSemantics semantics;
std::tie(media_type, semantics) = GetParam();
if (semantics != SdpSemantics::kUnifiedPlan)
return;
std::unique_ptr<PeerConnectionWrapper> pc =
CreatePeerConnection(media_type, semantics);
std::string sdp =
"v=0\r\n"
"o=- 0 3 IN IP4 127.0.0.1\r\n"
"s=-\r\n"
"t=0 0\r\n"
"a=fingerprint:sha-256 "
"A7:24:72:CA:6E:02:55:39:BA:66:DF:6E:CC:4C:D8:B0:1A:BF:1A:56:65:7D:F4:03:"
"AD:7E:77:43:2A:29:EC:93\r\n"
"a=ice-ufrag:6HHHdzzeIhkE0CKj\r\n"
"a=ice-pwd:XYDGVpfvklQIEnZ6YnyLsAew\r\n";
if (media_type == cricket::MEDIA_TYPE_AUDIO) {
sdp +=
"m=audio 9 RTP/AVPF 111\r\n"
"a=rtpmap:111 fake_audio_codec/8000\r\n";
} else {
sdp +=
"m=video 9 RTP/AVPF 111\r\n"
"a=rtpmap:111 fake_video_codec/90000\r\n";
}
sdp +=
"c=IN IP4 0.0.0.0\r\n"
"a=rtcp-mux\r\n"
"a=sendrecv\r\n"
"a=mid:audio\r\n"
"a=setup:actpass\r\n"
"a=extmap:1 uri1\r\n";
auto offer = CreateSessionDescription(SdpType::kOffer, sdp);
pc->SetRemoteDescription(std::move(offer));
auto answer =
pc->CreateAnswer(PeerConnectionInterface::RTCOfferAnswerOptions());
std::string modified_sdp;
ASSERT_TRUE(answer->ToString(&modified_sdp));
modified_sdp += "a=extmap:1 uri1\r\n";
auto modified_answer =
CreateSessionDescription(SdpType::kAnswer, modified_sdp);
ASSERT_TRUE(pc->SetLocalDescription(std::move(modified_answer)));
auto session_description = pc->CreateOffer();
EXPECT_THAT(session_description->description()
->contents()[0]
.media_description()
->rtp_header_extensions(),
ElementsAre(Field(&RtpExtension::uri, "uri1"),
Field(&RtpExtension::uri, "uri2"),
Field(&RtpExtension::uri, "uri3"),
Field(&RtpExtension::uri, "uri4")));
}
TEST_P(PeerConnectionHeaderExtensionTest,
SdpMungingOfferWithoutApiUsageEnablesExtensions) {
cricket::MediaType media_type;
SdpSemantics semantics;
std::tie(media_type, semantics) = GetParam();
if (semantics != SdpSemantics::kUnifiedPlan)
return;
std::unique_ptr<PeerConnectionWrapper> pc =
CreatePeerConnection(media_type, semantics);
pc->AddTransceiver(media_type);
auto offer =
pc->CreateOffer(PeerConnectionInterface::RTCOfferAnswerOptions());
std::string modified_sdp;
ASSERT_TRUE(offer->ToString(&modified_sdp));
modified_sdp += "a=extmap:1 uri1\r\n";
auto modified_offer = CreateSessionDescription(SdpType::kOffer, modified_sdp);
ASSERT_TRUE(pc->SetLocalDescription(std::move(modified_offer)));
auto offer2 =
pc->CreateOffer(PeerConnectionInterface::RTCOfferAnswerOptions());
EXPECT_THAT(offer2->description()
->contents()[0]
.media_description()
->rtp_header_extensions(),
ElementsAre(Field(&RtpExtension::uri, "uri2"),
Field(&RtpExtension::uri, "uri3"),
Field(&RtpExtension::uri, "uri4"),
Field(&RtpExtension::uri, "uri1")));
}
TEST_P(PeerConnectionHeaderExtensionTest, EnablingExtensionsAfterRemoteOffer) {
cricket::MediaType media_type;
SdpSemantics semantics;
std::tie(media_type, semantics) = GetParam();
if (semantics != SdpSemantics::kUnifiedPlan)
return;
std::unique_ptr<PeerConnectionWrapper> pc =
CreatePeerConnection(media_type, semantics);
std::string sdp =
"v=0\r\n"
"o=- 0 3 IN IP4 127.0.0.1\r\n"
"s=-\r\n"
"t=0 0\r\n"
"a=fingerprint:sha-256 "
"A7:24:72:CA:6E:02:55:39:BA:66:DF:6E:CC:4C:D8:B0:1A:BF:1A:56:65:7D:F4:03:"
"AD:7E:77:43:2A:29:EC:93\r\n"
"a=ice-ufrag:6HHHdzzeIhkE0CKj\r\n"
"a=ice-pwd:XYDGVpfvklQIEnZ6YnyLsAew\r\n";
if (media_type == cricket::MEDIA_TYPE_AUDIO) {
sdp +=
"m=audio 9 RTP/AVPF 111\r\n"
"a=rtpmap:111 fake_audio_codec/8000\r\n";
} else {
sdp +=
"m=video 9 RTP/AVPF 111\r\n"
"a=rtpmap:111 fake_video_codec/90000\r\n";
}
sdp +=
"c=IN IP4 0.0.0.0\r\n"
"a=rtcp-mux\r\n"
"a=sendrecv\r\n"
"a=mid:audio\r\n"
"a=setup:actpass\r\n"
"a=extmap:5 uri1\r\n";
auto offer = CreateSessionDescription(SdpType::kOffer, sdp);
pc->SetRemoteDescription(std::move(offer));
ASSERT_GT(pc->pc()->GetTransceivers().size(), 0u);
auto transceiver = pc->pc()->GetTransceivers()[0];
auto modified_extensions = transceiver->GetHeaderExtensionsToNegotiate();
modified_extensions[0].direction = RtpTransceiverDirection::kSendRecv;
transceiver->SetHeaderExtensionsToNegotiate(modified_extensions);
pc->CreateAnswerAndSetAsLocal(
PeerConnectionInterface::RTCOfferAnswerOptions());
auto session_description = pc->CreateOffer();
auto extensions = session_description->description()
->contents()[0]
.media_description()
->rtp_header_extensions();
EXPECT_THAT(extensions, ElementsAre(Field(&RtpExtension::uri, "uri1"),
Field(&RtpExtension::uri, "uri2"),
Field(&RtpExtension::uri, "uri3"),
Field(&RtpExtension::uri, "uri4")));
// Check uri1's id still matches the remote id.
EXPECT_EQ(extensions[0].id, 5);
}
INSTANTIATE_TEST_SUITE_P(
,
PeerConnectionHeaderExtensionTest,
Combine(Values(SdpSemantics::kPlanB_DEPRECATED, SdpSemantics::kUnifiedPlan),
Values(cricket::MediaType::MEDIA_TYPE_AUDIO,
cricket::MediaType::MEDIA_TYPE_VIDEO)),
[](const testing::TestParamInfo<
PeerConnectionHeaderExtensionTest::ParamType>& info) {
cricket::MediaType media_type;
SdpSemantics semantics;
std::tie(media_type, semantics) = info.param;
return (rtc::StringBuilder("With")
<< (semantics == SdpSemantics::kPlanB_DEPRECATED ? "PlanB"
: "UnifiedPlan")
<< "And"
<< (media_type == cricket::MediaType::MEDIA_TYPE_AUDIO ? "Voice"
: "Video")
<< "Engine")
.str();
});
} // namespace webrtc

View file

@ -0,0 +1,781 @@
/*
* Copyright 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "absl/types/optional.h"
#include "api/jsep.h"
#include "api/jsep_session_description.h"
#include "api/peer_connection_interface.h"
#include "api/rtc_error.h"
#include "api/scoped_refptr.h"
#include "api/task_queue/default_task_queue_factory.h"
#include "api/task_queue/task_queue_factory.h"
#include "api/test/mock_async_dns_resolver.h"
#include "media/base/media_engine.h"
#include "p2p/base/port_allocator.h"
#include "p2p/client/basic_port_allocator.h"
#include "pc/peer_connection.h"
#include "pc/peer_connection_factory.h"
#include "pc/peer_connection_proxy.h"
#include "pc/peer_connection_wrapper.h"
#include "pc/sdp_utils.h"
#include "pc/test/enable_fake_media.h"
#include "pc/test/mock_peer_connection_observers.h"
#include "pc/usage_pattern.h"
#include "pc/webrtc_sdp.h"
#include "rtc_base/arraysize.h"
#include "rtc_base/checks.h"
#include "rtc_base/fake_mdns_responder.h"
#include "rtc_base/fake_network.h"
#include "rtc_base/gunit.h"
#include "rtc_base/mdns_responder_interface.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/thread.h"
#include "rtc_base/virtual_socket_server.h"
#include "system_wrappers/include/metrics.h"
#include "test/gmock.h"
#include "test/gtest.h"
namespace webrtc {
using RTCConfiguration = PeerConnectionInterface::RTCConfiguration;
using RTCOfferAnswerOptions = PeerConnectionInterface::RTCOfferAnswerOptions;
using ::testing::NiceMock;
using ::testing::Values;
static const char kUsagePatternMetric[] = "WebRTC.PeerConnection.UsagePattern";
static constexpr int kDefaultTimeout = 10000;
static const rtc::SocketAddress kLocalAddrs[2] = {
rtc::SocketAddress("1.1.1.1", 0), rtc::SocketAddress("2.2.2.2", 0)};
static const rtc::SocketAddress kPrivateLocalAddress("10.1.1.1", 0);
static const rtc::SocketAddress kPrivateIpv6LocalAddress("fd12:3456:789a:1::1",
0);
int MakeUsageFingerprint(std::set<UsageEvent> events) {
int signature = 0;
for (const auto it : events) {
signature |= static_cast<int>(it);
}
return signature;
}
class PeerConnectionFactoryForUsageHistogramTest
: public PeerConnectionFactory {
public:
PeerConnectionFactoryForUsageHistogramTest()
: PeerConnectionFactory([] {
PeerConnectionFactoryDependencies dependencies;
dependencies.network_thread = rtc::Thread::Current();
dependencies.worker_thread = rtc::Thread::Current();
dependencies.signaling_thread = rtc::Thread::Current();
dependencies.task_queue_factory = CreateDefaultTaskQueueFactory();
EnableFakeMedia(dependencies);
return dependencies;
}()) {}
};
class PeerConnectionWrapperForUsageHistogramTest;
typedef PeerConnectionWrapperForUsageHistogramTest* RawWrapperPtr;
class ObserverForUsageHistogramTest : public MockPeerConnectionObserver {
public:
void OnIceCandidate(const IceCandidateInterface* candidate) override;
void OnInterestingUsage(int usage_pattern) override {
interesting_usage_detected_ = usage_pattern;
}
void PrepareToExchangeCandidates(RawWrapperPtr other) {
candidate_target_ = other;
}
bool HaveDataChannel() { return last_datachannel_ != nullptr; }
absl::optional<int> interesting_usage_detected() {
return interesting_usage_detected_;
}
void ClearInterestingUsageDetector() {
interesting_usage_detected_ = absl::optional<int>();
}
bool candidate_gathered() const { return candidate_gathered_; }
private:
absl::optional<int> interesting_usage_detected_;
bool candidate_gathered_ = false;
RawWrapperPtr candidate_target_; // Note: Not thread-safe against deletions.
};
class PeerConnectionWrapperForUsageHistogramTest
: public PeerConnectionWrapper {
public:
using PeerConnectionWrapper::PeerConnectionWrapper;
PeerConnection* GetInternalPeerConnection() {
auto* pci =
static_cast<PeerConnectionProxyWithInternal<PeerConnectionInterface>*>(
pc());
return static_cast<PeerConnection*>(pci->internal());
}
// Override with different return type
ObserverForUsageHistogramTest* observer() {
return static_cast<ObserverForUsageHistogramTest*>(
PeerConnectionWrapper::observer());
}
void PrepareToExchangeCandidates(
PeerConnectionWrapperForUsageHistogramTest* other) {
observer()->PrepareToExchangeCandidates(other);
other->observer()->PrepareToExchangeCandidates(this);
}
bool IsConnected() {
return pc()->ice_connection_state() ==
PeerConnectionInterface::kIceConnectionConnected ||
pc()->ice_connection_state() ==
PeerConnectionInterface::kIceConnectionCompleted;
}
bool HaveDataChannel() {
return static_cast<ObserverForUsageHistogramTest*>(observer())
->HaveDataChannel();
}
void BufferIceCandidate(const IceCandidateInterface* candidate) {
std::string sdp;
EXPECT_TRUE(candidate->ToString(&sdp));
std::unique_ptr<IceCandidateInterface> candidate_copy(CreateIceCandidate(
candidate->sdp_mid(), candidate->sdp_mline_index(), sdp, nullptr));
buffered_candidates_.push_back(std::move(candidate_copy));
}
void AddBufferedIceCandidates() {
for (const auto& candidate : buffered_candidates_) {
EXPECT_TRUE(pc()->AddIceCandidate(candidate.get()));
}
buffered_candidates_.clear();
}
// This method performs the following actions in sequence:
// 1. Exchange Offer and Answer.
// 2. Exchange ICE candidates after both caller and callee complete
// gathering.
// 3. Wait for ICE to connect.
//
// This guarantees a deterministic sequence of events and also rules out the
// occurrence of prflx candidates if the offer/answer signaling and the
// candidate trickling race in order. In case prflx candidates need to be
// simulated, see the approach used by tests below for that.
bool ConnectTo(PeerConnectionWrapperForUsageHistogramTest* callee) {
PrepareToExchangeCandidates(callee);
if (!ExchangeOfferAnswerWith(callee)) {
return false;
}
// Wait until the gathering completes before we signal the candidate.
WAIT(observer()->ice_gathering_complete_, kDefaultTimeout);
WAIT(callee->observer()->ice_gathering_complete_, kDefaultTimeout);
AddBufferedIceCandidates();
callee->AddBufferedIceCandidates();
WAIT(IsConnected(), kDefaultTimeout);
WAIT(callee->IsConnected(), kDefaultTimeout);
return IsConnected() && callee->IsConnected();
}
bool GenerateOfferAndCollectCandidates() {
auto offer = CreateOffer(RTCOfferAnswerOptions());
if (!offer) {
return false;
}
bool set_local_offer =
SetLocalDescription(CloneSessionDescription(offer.get()));
EXPECT_TRUE(set_local_offer);
if (!set_local_offer) {
return false;
}
EXPECT_TRUE_WAIT(observer()->ice_gathering_complete_, kDefaultTimeout);
return true;
}
PeerConnectionInterface::IceGatheringState ice_gathering_state() {
return pc()->ice_gathering_state();
}
private:
// Candidates that have been sent but not yet configured
std::vector<std::unique_ptr<IceCandidateInterface>> buffered_candidates_;
};
// Buffers candidates until we add them via AddBufferedIceCandidates.
void ObserverForUsageHistogramTest::OnIceCandidate(
const IceCandidateInterface* candidate) {
// If target is not set, ignore. This happens in one-ended unit tests.
if (candidate_target_) {
this->candidate_target_->BufferIceCandidate(candidate);
}
candidate_gathered_ = true;
}
class PeerConnectionUsageHistogramTest : public ::testing::Test {
protected:
typedef std::unique_ptr<PeerConnectionWrapperForUsageHistogramTest>
WrapperPtr;
PeerConnectionUsageHistogramTest()
: vss_(new rtc::VirtualSocketServer()),
socket_factory_(new rtc::BasicPacketSocketFactory(vss_.get())),
main_(vss_.get()) {
metrics::Reset();
}
WrapperPtr CreatePeerConnection() {
RTCConfiguration config;
config.sdp_semantics = SdpSemantics::kUnifiedPlan;
return CreatePeerConnection(
config, PeerConnectionFactoryInterface::Options(), nullptr);
}
WrapperPtr CreatePeerConnection(const RTCConfiguration& config) {
return CreatePeerConnection(
config, PeerConnectionFactoryInterface::Options(), nullptr);
}
WrapperPtr CreatePeerConnectionWithMdns(const RTCConfiguration& config) {
auto resolver_factory =
std::make_unique<NiceMock<MockAsyncDnsResolverFactory>>();
PeerConnectionDependencies deps(nullptr /* observer_in */);
auto fake_network = NewFakeNetwork();
fake_network->set_mdns_responder(
std::make_unique<FakeMdnsResponder>(rtc::Thread::Current()));
fake_network->AddInterface(NextLocalAddress());
std::unique_ptr<cricket::BasicPortAllocator> port_allocator(
new cricket::BasicPortAllocator(fake_network, socket_factory_.get()));
deps.async_dns_resolver_factory = std::move(resolver_factory);
deps.allocator = std::move(port_allocator);
return CreatePeerConnection(
config, PeerConnectionFactoryInterface::Options(), std::move(deps));
}
WrapperPtr CreatePeerConnectionWithImmediateReport() {
RTCConfiguration configuration;
configuration.sdp_semantics = SdpSemantics::kUnifiedPlan;
configuration.report_usage_pattern_delay_ms = 0;
return CreatePeerConnection(
configuration, PeerConnectionFactoryInterface::Options(), nullptr);
}
WrapperPtr CreatePeerConnectionWithPrivateLocalAddresses() {
auto* fake_network = NewFakeNetwork();
fake_network->AddInterface(NextLocalAddress());
fake_network->AddInterface(kPrivateLocalAddress);
auto port_allocator = std::make_unique<cricket::BasicPortAllocator>(
fake_network, socket_factory_.get());
RTCConfiguration config;
config.sdp_semantics = SdpSemantics::kUnifiedPlan;
return CreatePeerConnection(config,
PeerConnectionFactoryInterface::Options(),
std::move(port_allocator));
}
WrapperPtr CreatePeerConnectionWithPrivateIpv6LocalAddresses() {
auto* fake_network = NewFakeNetwork();
fake_network->AddInterface(NextLocalAddress());
fake_network->AddInterface(kPrivateIpv6LocalAddress);
auto port_allocator = std::make_unique<cricket::BasicPortAllocator>(
fake_network, socket_factory_.get());
RTCConfiguration config;
config.sdp_semantics = SdpSemantics::kUnifiedPlan;
return CreatePeerConnection(config,
PeerConnectionFactoryInterface::Options(),
std::move(port_allocator));
}
WrapperPtr CreatePeerConnection(
const RTCConfiguration& config,
const PeerConnectionFactoryInterface::Options factory_options,
std::unique_ptr<cricket::PortAllocator> allocator) {
PeerConnectionDependencies deps(nullptr);
deps.allocator = std::move(allocator);
return CreatePeerConnection(config, factory_options, std::move(deps));
}
WrapperPtr CreatePeerConnection(
const RTCConfiguration& config,
const PeerConnectionFactoryInterface::Options factory_options,
PeerConnectionDependencies deps) {
auto pc_factory =
rtc::make_ref_counted<PeerConnectionFactoryForUsageHistogramTest>();
pc_factory->SetOptions(factory_options);
// If no allocator is provided, one will be created using a network manager
// that uses the host network. This doesn't work on all trybots.
if (!deps.allocator) {
auto fake_network = NewFakeNetwork();
fake_network->AddInterface(NextLocalAddress());
deps.allocator = std::make_unique<cricket::BasicPortAllocator>(
fake_network, socket_factory_.get());
}
auto observer = std::make_unique<ObserverForUsageHistogramTest>();
deps.observer = observer.get();
auto result =
pc_factory->CreatePeerConnectionOrError(config, std::move(deps));
if (!result.ok()) {
return nullptr;
}
observer->SetPeerConnectionInterface(result.value().get());
auto wrapper = std::make_unique<PeerConnectionWrapperForUsageHistogramTest>(
pc_factory, result.MoveValue(), std::move(observer));
return wrapper;
}
int ObservedFingerprint() {
// This works correctly only if there is only one sample value
// that has been counted.
// Returns -1 for "not found".
return metrics::MinSample(kUsagePatternMetric);
}
// The PeerConnection's port allocator is tied to the PeerConnection's
// lifetime and expects the underlying NetworkManager to outlive it. That
// prevents us from having the PeerConnectionWrapper own the fake network.
// Therefore, the test fixture will own all the fake networks even though
// tests should access the fake network through the PeerConnectionWrapper.
rtc::FakeNetworkManager* NewFakeNetwork() {
fake_networks_.emplace_back(std::make_unique<rtc::FakeNetworkManager>());
return fake_networks_.back().get();
}
rtc::SocketAddress NextLocalAddress() {
RTC_DCHECK(next_local_address_ < (int)arraysize(kLocalAddrs));
return kLocalAddrs[next_local_address_++];
}
std::vector<std::unique_ptr<rtc::FakeNetworkManager>> fake_networks_;
int next_local_address_ = 0;
std::unique_ptr<rtc::VirtualSocketServer> vss_;
std::unique_ptr<rtc::BasicPacketSocketFactory> socket_factory_;
rtc::AutoSocketServerThread main_;
};
TEST_F(PeerConnectionUsageHistogramTest, UsageFingerprintHistogramFromTimeout) {
auto pc = CreatePeerConnectionWithImmediateReport();
int expected_fingerprint = MakeUsageFingerprint({});
EXPECT_METRIC_EQ_WAIT(1, metrics::NumSamples(kUsagePatternMetric),
kDefaultTimeout);
EXPECT_METRIC_EQ(
1, metrics::NumEvents(kUsagePatternMetric, expected_fingerprint));
}
#ifndef WEBRTC_ANDROID
// These tests do not work on Android. Why is unclear.
// https://bugs.webrtc.org/9461
// Test getting the usage fingerprint for an audio/video connection.
TEST_F(PeerConnectionUsageHistogramTest, FingerprintAudioVideo) {
auto caller = CreatePeerConnection();
auto callee = CreatePeerConnection();
caller->AddAudioTrack("audio");
caller->AddVideoTrack("video");
ASSERT_TRUE(caller->ConnectTo(callee.get()));
caller->pc()->Close();
callee->pc()->Close();
int expected_fingerprint = MakeUsageFingerprint(
{UsageEvent::AUDIO_ADDED, UsageEvent::VIDEO_ADDED,
UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
UsageEvent::CANDIDATE_COLLECTED, UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED,
UsageEvent::ICE_STATE_CONNECTED, UsageEvent::REMOTE_CANDIDATE_ADDED,
UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED});
// In this case, we may or may not have PRIVATE_CANDIDATE_COLLECTED,
// depending on the machine configuration.
EXPECT_METRIC_EQ(2, metrics::NumSamples(kUsagePatternMetric));
EXPECT_METRIC_TRUE(
metrics::NumEvents(kUsagePatternMetric, expected_fingerprint) == 2 ||
metrics::NumEvents(
kUsagePatternMetric,
expected_fingerprint |
static_cast<int>(UsageEvent::PRIVATE_CANDIDATE_COLLECTED)) == 2);
}
// Test getting the usage fingerprint when the caller collects an mDNS
// candidate.
TEST_F(PeerConnectionUsageHistogramTest, FingerprintWithMdnsCaller) {
RTCConfiguration config;
config.sdp_semantics = SdpSemantics::kUnifiedPlan;
// Enable hostname candidates with mDNS names.
auto caller = CreatePeerConnectionWithMdns(config);
auto callee = CreatePeerConnection(config);
caller->AddAudioTrack("audio");
caller->AddVideoTrack("video");
ASSERT_TRUE(caller->ConnectTo(callee.get()));
caller->pc()->Close();
callee->pc()->Close();
int expected_fingerprint_caller = MakeUsageFingerprint(
{UsageEvent::AUDIO_ADDED, UsageEvent::VIDEO_ADDED,
UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
UsageEvent::CANDIDATE_COLLECTED, UsageEvent::MDNS_CANDIDATE_COLLECTED,
UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED, UsageEvent::ICE_STATE_CONNECTED,
UsageEvent::REMOTE_CANDIDATE_ADDED,
UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED});
// Without a resolver, the callee cannot resolve the received mDNS candidate
// but can still connect with the caller via a prflx candidate. As a result,
// the bit for the direct connection should not be logged.
int expected_fingerprint_callee = MakeUsageFingerprint(
{UsageEvent::AUDIO_ADDED, UsageEvent::VIDEO_ADDED,
UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
UsageEvent::CANDIDATE_COLLECTED, UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED,
UsageEvent::REMOTE_MDNS_CANDIDATE_ADDED, UsageEvent::ICE_STATE_CONNECTED,
UsageEvent::REMOTE_CANDIDATE_ADDED, UsageEvent::CLOSE_CALLED});
EXPECT_METRIC_EQ(2, metrics::NumSamples(kUsagePatternMetric));
EXPECT_METRIC_EQ(
1, metrics::NumEvents(kUsagePatternMetric, expected_fingerprint_caller));
EXPECT_METRIC_EQ(
1, metrics::NumEvents(kUsagePatternMetric, expected_fingerprint_callee));
}
// Test getting the usage fingerprint when the callee collects an mDNS
// candidate.
TEST_F(PeerConnectionUsageHistogramTest, FingerprintWithMdnsCallee) {
RTCConfiguration config;
config.sdp_semantics = SdpSemantics::kUnifiedPlan;
// Enable hostname candidates with mDNS names.
auto caller = CreatePeerConnection(config);
auto callee = CreatePeerConnectionWithMdns(config);
caller->AddAudioTrack("audio");
caller->AddVideoTrack("video");
ASSERT_TRUE(caller->ConnectTo(callee.get()));
caller->pc()->Close();
callee->pc()->Close();
// Similar to the test above, the caller connects with the callee via a prflx
// candidate.
int expected_fingerprint_caller = MakeUsageFingerprint(
{UsageEvent::AUDIO_ADDED, UsageEvent::VIDEO_ADDED,
UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
UsageEvent::CANDIDATE_COLLECTED, UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED,
UsageEvent::REMOTE_MDNS_CANDIDATE_ADDED, UsageEvent::ICE_STATE_CONNECTED,
UsageEvent::REMOTE_CANDIDATE_ADDED, UsageEvent::CLOSE_CALLED});
int expected_fingerprint_callee = MakeUsageFingerprint(
{UsageEvent::AUDIO_ADDED, UsageEvent::VIDEO_ADDED,
UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
UsageEvent::CANDIDATE_COLLECTED, UsageEvent::MDNS_CANDIDATE_COLLECTED,
UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED, UsageEvent::ICE_STATE_CONNECTED,
UsageEvent::REMOTE_CANDIDATE_ADDED,
UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED});
EXPECT_METRIC_EQ(2, metrics::NumSamples(kUsagePatternMetric));
EXPECT_METRIC_EQ(
1, metrics::NumEvents(kUsagePatternMetric, expected_fingerprint_caller));
EXPECT_METRIC_EQ(
1, metrics::NumEvents(kUsagePatternMetric, expected_fingerprint_callee));
}
#ifdef WEBRTC_HAVE_SCTP
TEST_F(PeerConnectionUsageHistogramTest, FingerprintDataOnly) {
auto caller = CreatePeerConnection();
auto callee = CreatePeerConnection();
caller->CreateDataChannel("foodata");
ASSERT_TRUE(caller->ConnectTo(callee.get()));
ASSERT_TRUE_WAIT(callee->HaveDataChannel(), kDefaultTimeout);
caller->pc()->Close();
callee->pc()->Close();
int expected_fingerprint = MakeUsageFingerprint(
{UsageEvent::DATA_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
UsageEvent::CANDIDATE_COLLECTED, UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED,
UsageEvent::ICE_STATE_CONNECTED, UsageEvent::REMOTE_CANDIDATE_ADDED,
UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED});
EXPECT_METRIC_EQ(2, metrics::NumSamples(kUsagePatternMetric));
EXPECT_METRIC_TRUE(
metrics::NumEvents(kUsagePatternMetric, expected_fingerprint) == 2 ||
metrics::NumEvents(
kUsagePatternMetric,
expected_fingerprint |
static_cast<int>(UsageEvent::PRIVATE_CANDIDATE_COLLECTED)) == 2);
}
#endif // WEBRTC_HAVE_SCTP
#endif // WEBRTC_ANDROID
TEST_F(PeerConnectionUsageHistogramTest, FingerprintStunTurn) {
RTCConfiguration configuration;
configuration.sdp_semantics = SdpSemantics::kUnifiedPlan;
PeerConnection::IceServer server;
server.urls = {"stun:dummy.stun.server"};
configuration.servers.push_back(server);
server.urls = {"turn:dummy.turn.server"};
server.username = "username";
server.password = "password";
configuration.servers.push_back(server);
auto caller = CreatePeerConnection(configuration);
ASSERT_TRUE(caller);
caller->pc()->Close();
int expected_fingerprint = MakeUsageFingerprint(
{UsageEvent::STUN_SERVER_ADDED, UsageEvent::TURN_SERVER_ADDED,
UsageEvent::CLOSE_CALLED});
EXPECT_METRIC_EQ(1, metrics::NumSamples(kUsagePatternMetric));
EXPECT_METRIC_EQ(
1, metrics::NumEvents(kUsagePatternMetric, expected_fingerprint));
}
TEST_F(PeerConnectionUsageHistogramTest, FingerprintStunTurnInReconfiguration) {
RTCConfiguration configuration;
configuration.sdp_semantics = SdpSemantics::kUnifiedPlan;
PeerConnection::IceServer server;
server.urls = {"stun:dummy.stun.server"};
configuration.servers.push_back(server);
server.urls = {"turn:dummy.turn.server"};
server.username = "username";
server.password = "password";
configuration.servers.push_back(server);
auto caller = CreatePeerConnection();
ASSERT_TRUE(caller);
ASSERT_TRUE(caller->pc()->SetConfiguration(configuration).ok());
caller->pc()->Close();
int expected_fingerprint = MakeUsageFingerprint(
{UsageEvent::STUN_SERVER_ADDED, UsageEvent::TURN_SERVER_ADDED,
UsageEvent::CLOSE_CALLED});
EXPECT_METRIC_EQ(1, metrics::NumSamples(kUsagePatternMetric));
EXPECT_METRIC_EQ(
1, metrics::NumEvents(kUsagePatternMetric, expected_fingerprint));
}
TEST_F(PeerConnectionUsageHistogramTest, FingerprintWithPrivateIPCaller) {
auto caller = CreatePeerConnectionWithPrivateLocalAddresses();
auto callee = CreatePeerConnection();
caller->AddAudioTrack("audio");
ASSERT_TRUE(caller->ConnectTo(callee.get()));
caller->pc()->Close();
callee->pc()->Close();
int expected_fingerprint_caller = MakeUsageFingerprint(
{UsageEvent::AUDIO_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
UsageEvent::CANDIDATE_COLLECTED, UsageEvent::PRIVATE_CANDIDATE_COLLECTED,
UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED, UsageEvent::ICE_STATE_CONNECTED,
UsageEvent::REMOTE_CANDIDATE_ADDED,
UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED});
int expected_fingerprint_callee = MakeUsageFingerprint(
{UsageEvent::AUDIO_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
UsageEvent::CANDIDATE_COLLECTED, UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED,
UsageEvent::REMOTE_PRIVATE_CANDIDATE_ADDED,
UsageEvent::ICE_STATE_CONNECTED, UsageEvent::REMOTE_CANDIDATE_ADDED,
UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED});
EXPECT_METRIC_EQ(2, metrics::NumSamples(kUsagePatternMetric));
EXPECT_METRIC_EQ(
1, metrics::NumEvents(kUsagePatternMetric, expected_fingerprint_caller));
EXPECT_METRIC_EQ(
1, metrics::NumEvents(kUsagePatternMetric, expected_fingerprint_callee));
}
TEST_F(PeerConnectionUsageHistogramTest, FingerprintWithPrivateIpv6Callee) {
auto caller = CreatePeerConnection();
auto callee = CreatePeerConnectionWithPrivateIpv6LocalAddresses();
caller->AddAudioTrack("audio");
ASSERT_TRUE(caller->ConnectTo(callee.get()));
caller->pc()->Close();
callee->pc()->Close();
int expected_fingerprint_caller = MakeUsageFingerprint(
{UsageEvent::AUDIO_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
UsageEvent::CANDIDATE_COLLECTED, UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED,
UsageEvent::REMOTE_PRIVATE_CANDIDATE_ADDED,
UsageEvent::ICE_STATE_CONNECTED, UsageEvent::REMOTE_CANDIDATE_ADDED,
UsageEvent::REMOTE_IPV6_CANDIDATE_ADDED,
UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED});
int expected_fingerprint_callee = MakeUsageFingerprint(
{UsageEvent::AUDIO_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
UsageEvent::CANDIDATE_COLLECTED, UsageEvent::PRIVATE_CANDIDATE_COLLECTED,
UsageEvent::IPV6_CANDIDATE_COLLECTED,
UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED,
UsageEvent::REMOTE_CANDIDATE_ADDED, UsageEvent::ICE_STATE_CONNECTED,
UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED});
EXPECT_METRIC_EQ(2, metrics::NumSamples(kUsagePatternMetric));
EXPECT_METRIC_EQ(
1, metrics::NumEvents(kUsagePatternMetric, expected_fingerprint_caller));
EXPECT_METRIC_EQ(
1, metrics::NumEvents(kUsagePatternMetric, expected_fingerprint_callee));
}
#ifndef WEBRTC_ANDROID
#ifdef WEBRTC_HAVE_SCTP
// Test that the usage pattern bits for adding remote (private IPv6) candidates
// are set when the remote candidates are retrieved from the Offer SDP instead
// of trickled ICE messages.
TEST_F(PeerConnectionUsageHistogramTest,
AddRemoteCandidatesFromRemoteDescription) {
// We construct the following data-channel-only scenario. The caller collects
// IPv6 private local candidates and appends them in the Offer as in
// non-trickled sessions. The callee collects mDNS candidates that are not
// contained in the Answer as in Trickle ICE. Only the Offer and Answer are
// signaled and we expect a connection with prflx remote candidates at the
// caller side.
auto caller = CreatePeerConnectionWithPrivateIpv6LocalAddresses();
RTCConfiguration config;
config.sdp_semantics = SdpSemantics::kUnifiedPlan;
auto callee = CreatePeerConnectionWithMdns(config);
caller->CreateDataChannel("test_channel");
ASSERT_TRUE(caller->SetLocalDescription(caller->CreateOffer()));
// Wait until the gathering completes so that the session description would
// have contained ICE candidates.
EXPECT_EQ_WAIT(PeerConnectionInterface::kIceGatheringComplete,
caller->ice_gathering_state(), kDefaultTimeout);
EXPECT_TRUE(caller->observer()->candidate_gathered());
// Get the current offer that contains candidates and pass it to the callee.
//
// Note that we cannot use CloneSessionDescription on `cur_offer` to obtain an
// SDP with candidates. The method above does not strictly copy everything, in
// particular, not copying the ICE candidates.
// TODO(qingsi): Technically, this is a bug. Fix it.
auto cur_offer = caller->pc()->local_description();
ASSERT_TRUE(cur_offer);
std::string sdp_with_candidates_str;
cur_offer->ToString(&sdp_with_candidates_str);
auto offer = std::make_unique<JsepSessionDescription>(SdpType::kOffer);
ASSERT_TRUE(SdpDeserialize(sdp_with_candidates_str, offer.get(),
nullptr /* error */));
ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
// By default, the Answer created does not contain ICE candidates.
auto answer = callee->CreateAnswer();
callee->SetLocalDescription(CloneSessionDescription(answer.get()));
caller->SetRemoteDescription(std::move(answer));
EXPECT_TRUE_WAIT(caller->IsConnected(), kDefaultTimeout);
EXPECT_TRUE_WAIT(callee->IsConnected(), kDefaultTimeout);
// The callee needs to process the open message to have the data channel open.
EXPECT_TRUE_WAIT(callee->observer()->last_datachannel_ != nullptr,
kDefaultTimeout);
caller->pc()->Close();
callee->pc()->Close();
// The caller should not have added any remote candidate either via
// AddIceCandidate or from the remote description. Also, the caller connects
// with the callee via a prflx candidate and hence no direct connection bit
// should be set.
int expected_fingerprint_caller = MakeUsageFingerprint(
{UsageEvent::DATA_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
UsageEvent::CANDIDATE_COLLECTED, UsageEvent::PRIVATE_CANDIDATE_COLLECTED,
UsageEvent::IPV6_CANDIDATE_COLLECTED, UsageEvent::ICE_STATE_CONNECTED,
UsageEvent::CLOSE_CALLED});
int expected_fingerprint_callee = MakeUsageFingerprint(
{UsageEvent::DATA_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED,
UsageEvent::CANDIDATE_COLLECTED, UsageEvent::MDNS_CANDIDATE_COLLECTED,
UsageEvent::REMOTE_CANDIDATE_ADDED,
UsageEvent::REMOTE_PRIVATE_CANDIDATE_ADDED,
UsageEvent::REMOTE_IPV6_CANDIDATE_ADDED, UsageEvent::ICE_STATE_CONNECTED,
UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED});
EXPECT_METRIC_EQ(2, metrics::NumSamples(kUsagePatternMetric));
EXPECT_METRIC_EQ(
1, metrics::NumEvents(kUsagePatternMetric, expected_fingerprint_caller));
EXPECT_METRIC_EQ(
1, metrics::NumEvents(kUsagePatternMetric, expected_fingerprint_callee));
}
TEST_F(PeerConnectionUsageHistogramTest, NotableUsageNoted) {
auto caller = CreatePeerConnection();
caller->CreateDataChannel("foo");
caller->GenerateOfferAndCollectCandidates();
caller->pc()->Close();
int expected_fingerprint = MakeUsageFingerprint(
{UsageEvent::DATA_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
UsageEvent::CANDIDATE_COLLECTED, UsageEvent::CLOSE_CALLED});
EXPECT_METRIC_EQ(1, metrics::NumSamples(kUsagePatternMetric));
EXPECT_METRIC_TRUE(
expected_fingerprint == ObservedFingerprint() ||
(expected_fingerprint |
static_cast<int>(UsageEvent::PRIVATE_CANDIDATE_COLLECTED)) ==
ObservedFingerprint());
EXPECT_METRIC_EQ(absl::make_optional(ObservedFingerprint()),
caller->observer()->interesting_usage_detected());
}
TEST_F(PeerConnectionUsageHistogramTest, NotableUsageOnEventFiring) {
auto caller = CreatePeerConnection();
caller->CreateDataChannel("foo");
caller->GenerateOfferAndCollectCandidates();
int expected_fingerprint = MakeUsageFingerprint(
{UsageEvent::DATA_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
UsageEvent::CANDIDATE_COLLECTED});
EXPECT_METRIC_EQ(0, metrics::NumSamples(kUsagePatternMetric));
caller->GetInternalPeerConnection()->RequestUsagePatternReportForTesting();
EXPECT_METRIC_EQ_WAIT(1, metrics::NumSamples(kUsagePatternMetric),
kDefaultTimeout);
EXPECT_METRIC_TRUE(
expected_fingerprint == ObservedFingerprint() ||
(expected_fingerprint |
static_cast<int>(UsageEvent::PRIVATE_CANDIDATE_COLLECTED)) ==
ObservedFingerprint());
EXPECT_METRIC_EQ(absl::make_optional(ObservedFingerprint()),
caller->observer()->interesting_usage_detected());
}
TEST_F(PeerConnectionUsageHistogramTest,
NoNotableUsageOnEventFiringAfterClose) {
auto caller = CreatePeerConnection();
caller->CreateDataChannel("foo");
caller->GenerateOfferAndCollectCandidates();
int expected_fingerprint = MakeUsageFingerprint(
{UsageEvent::DATA_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED,
UsageEvent::CANDIDATE_COLLECTED, UsageEvent::CLOSE_CALLED});
EXPECT_METRIC_EQ(0, metrics::NumSamples(kUsagePatternMetric));
caller->pc()->Close();
EXPECT_METRIC_EQ(1, metrics::NumSamples(kUsagePatternMetric));
caller->GetInternalPeerConnection()->RequestUsagePatternReportForTesting();
caller->observer()->ClearInterestingUsageDetector();
EXPECT_METRIC_EQ_WAIT(2, metrics::NumSamples(kUsagePatternMetric),
kDefaultTimeout);
EXPECT_METRIC_TRUE(
expected_fingerprint == ObservedFingerprint() ||
(expected_fingerprint |
static_cast<int>(UsageEvent::PRIVATE_CANDIDATE_COLLECTED)) ==
ObservedFingerprint());
// After close, the usage-detection callback should NOT have been called.
EXPECT_METRIC_FALSE(caller->observer()->interesting_usage_detected());
}
#endif
#endif
} // namespace webrtc

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,192 @@
/*
* Copyright 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef PC_PEER_CONNECTION_INTERNAL_H_
#define PC_PEER_CONNECTION_INTERNAL_H_
#include <map>
#include <memory>
#include <set>
#include <string>
#include <vector>
#include "absl/types/optional.h"
#include "api/peer_connection_interface.h"
#include "call/call.h"
#include "modules/audio_device/include/audio_device.h"
#include "pc/jsep_transport_controller.h"
#include "pc/peer_connection_message_handler.h"
#include "pc/rtp_transceiver.h"
#include "pc/rtp_transmission_manager.h"
#include "pc/sctp_data_channel.h"
namespace webrtc {
class DataChannelController;
class LegacyStatsCollector;
// This interface defines the functions that are needed for
// SdpOfferAnswerHandler to access PeerConnection internal state.
class PeerConnectionSdpMethods {
public:
virtual ~PeerConnectionSdpMethods() = default;
// The SDP session ID as defined by RFC 3264.
virtual std::string session_id() const = 0;
// Returns true if the ICE restart flag above was set, and no ICE restart has
// occurred yet for this transport (by applying a local description with
// changed ufrag/password). If the transport has been deleted as a result of
// bundling, returns false.
virtual bool NeedsIceRestart(const std::string& content_name) const = 0;
virtual absl::optional<std::string> sctp_mid() const = 0;
// Functions below this comment are known to only be accessed
// from SdpOfferAnswerHandler.
// Return a pointer to the active configuration.
virtual const PeerConnectionInterface::RTCConfiguration* configuration()
const = 0;
// Report the UMA metric BundleUsage for the given remote description.
virtual void ReportSdpBundleUsage(
const SessionDescriptionInterface& remote_description) = 0;
virtual PeerConnectionMessageHandler* message_handler() = 0;
virtual RtpTransmissionManager* rtp_manager() = 0;
virtual const RtpTransmissionManager* rtp_manager() const = 0;
virtual bool dtls_enabled() const = 0;
virtual const PeerConnectionFactoryInterface::Options* options() const = 0;
// Returns the CryptoOptions for this PeerConnection. This will always
// return the RTCConfiguration.crypto_options if set and will only default
// back to the PeerConnectionFactory settings if nothing was set.
virtual CryptoOptions GetCryptoOptions() = 0;
virtual JsepTransportController* transport_controller_s() = 0;
virtual JsepTransportController* transport_controller_n() = 0;
virtual DataChannelController* data_channel_controller() = 0;
virtual cricket::PortAllocator* port_allocator() = 0;
virtual LegacyStatsCollector* legacy_stats() = 0;
// Returns the observer. Will crash on CHECK if the observer is removed.
virtual PeerConnectionObserver* Observer() const = 0;
virtual absl::optional<rtc::SSLRole> GetSctpSslRole_n() = 0;
virtual PeerConnectionInterface::IceConnectionState
ice_connection_state_internal() = 0;
virtual void SetIceConnectionState(
PeerConnectionInterface::IceConnectionState new_state) = 0;
virtual void NoteUsageEvent(UsageEvent event) = 0;
virtual bool IsClosed() const = 0;
// Returns true if the PeerConnection is configured to use Unified Plan
// semantics for creating offers/answers and setting local/remote
// descriptions. If this is true the RtpTransceiver API will also be available
// to the user. If this is false, Plan B semantics are assumed.
// TODO(bugs.webrtc.org/8530): Flip the default to be Unified Plan once
// sufficient time has passed.
virtual bool IsUnifiedPlan() const = 0;
virtual bool ValidateBundleSettings(
const cricket::SessionDescription* desc,
const std::map<std::string, const cricket::ContentGroup*>&
bundle_groups_by_mid) = 0;
// Internal implementation for AddTransceiver family of methods. If
// `fire_callback` is set, fires OnRenegotiationNeeded callback if successful.
virtual RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>>
AddTransceiver(cricket::MediaType media_type,
rtc::scoped_refptr<MediaStreamTrackInterface> track,
const RtpTransceiverInit& init,
bool fire_callback = true) = 0;
// Asynchronously calls SctpTransport::Start() on the network thread for
// `sctp_mid()` if set. Called as part of setting the local description.
virtual void StartSctpTransport(int local_port,
int remote_port,
int max_message_size) = 0;
// Asynchronously adds a remote candidate on the network thread.
virtual void AddRemoteCandidate(const std::string& mid,
const cricket::Candidate& candidate) = 0;
virtual Call* call_ptr() = 0;
// Returns true if SRTP (either using DTLS-SRTP or SDES) is required by
// this session.
virtual bool SrtpRequired() const = 0;
// Initializes the data channel transport for the peerconnection instance.
// This will have the effect that `sctp_mid()` and `sctp_transport_name()`
// will return a set value (even though it might be an empty string) and the
// dc transport will be initialized on the network thread.
virtual bool CreateDataChannelTransport(absl::string_view mid) = 0;
// Tears down the data channel transport state and clears the `sctp_mid()` and
// `sctp_transport_name()` properties.
virtual void DestroyDataChannelTransport(RTCError error) = 0;
virtual const FieldTrialsView& trials() const = 0;
virtual void ClearStatsCache() = 0;
};
// Functions defined in this class are called by other objects,
// but not by SdpOfferAnswerHandler.
class PeerConnectionInternal : public PeerConnectionInterface,
public PeerConnectionSdpMethods {
public:
virtual rtc::Thread* network_thread() const = 0;
virtual rtc::Thread* worker_thread() const = 0;
// Returns true if we were the initial offerer.
virtual bool initial_offerer() const = 0;
virtual std::vector<
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>>
GetTransceiversInternal() const = 0;
// Call on the network thread to fetch stats for all the data channels.
// TODO(tommi): Make pure virtual after downstream updates.
virtual std::vector<DataChannelStats> GetDataChannelStats() const {
return {};
}
virtual absl::optional<std::string> sctp_transport_name() const = 0;
virtual cricket::CandidateStatsList GetPooledCandidateStats() const = 0;
// Returns a map from transport name to transport stats for all given
// transport names.
// Must be called on the network thread.
virtual std::map<std::string, cricket::TransportStats>
GetTransportStatsByNames(const std::set<std::string>& transport_names) = 0;
virtual Call::Stats GetCallStats() = 0;
virtual absl::optional<AudioDeviceModule::Stats> GetAudioDeviceStats() = 0;
virtual bool GetLocalCertificate(
const std::string& transport_name,
rtc::scoped_refptr<rtc::RTCCertificate>* certificate) = 0;
virtual std::unique_ptr<rtc::SSLCertChain> GetRemoteSSLCertChain(
const std::string& transport_name) = 0;
// Returns true if there was an ICE restart initiated by the remote offer.
virtual bool IceRestartPending(const std::string& content_name) const = 0;
// Get SSL role for an arbitrary m= section (handles bundling correctly).
virtual bool GetSslRole(const std::string& content_name,
rtc::SSLRole* role) = 0;
// Functions needed by DataChannelController
virtual void NoteDataAddedEvent() {}
// Handler for sctp data channel state changes.
// The `channel_id` is the same unique identifier as used in
// `DataChannelStats::internal_id and
// `RTCDataChannelStats::data_channel_identifier`.
virtual void OnSctpDataChannelStateChanged(
int channel_id,
DataChannelInterface::DataState state) {}
};
} // namespace webrtc
#endif // PC_PEER_CONNECTION_INTERNAL_H_

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,85 @@
/*
* Copyright 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/peer_connection_message_handler.h"
#include <utility>
#include "api/jsep.h"
#include "api/legacy_stats_types.h"
#include "api/media_stream_interface.h"
#include "api/peer_connection_interface.h"
#include "api/scoped_refptr.h"
#include "api/sequence_checker.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "pc/legacy_stats_collector_interface.h"
#include "rtc_base/checks.h"
namespace webrtc {
namespace {
template <typename T>
rtc::scoped_refptr<T> WrapScoped(T* ptr) {
return rtc::scoped_refptr<T>(ptr);
}
} // namespace
void PeerConnectionMessageHandler::PostSetSessionDescriptionSuccess(
SetSessionDescriptionObserver* observer) {
signaling_thread_->PostTask(
SafeTask(safety_.flag(),
[observer = WrapScoped(observer)] { observer->OnSuccess(); }));
}
void PeerConnectionMessageHandler::PostSetSessionDescriptionFailure(
SetSessionDescriptionObserver* observer,
RTCError&& error) {
RTC_DCHECK(!error.ok());
signaling_thread_->PostTask(SafeTask(
safety_.flag(),
[observer = WrapScoped(observer), error = std::move(error)]() mutable {
observer->OnFailure(std::move(error));
}));
}
void PeerConnectionMessageHandler::PostCreateSessionDescriptionFailure(
CreateSessionDescriptionObserver* observer,
RTCError error) {
RTC_DCHECK(!error.ok());
// Do not protect this task with the safety_.flag() to ensure
// observer is invoked even if the PeerConnection is destroyed early.
signaling_thread_->PostTask(
[observer = WrapScoped(observer), error = std::move(error)]() mutable {
observer->OnFailure(std::move(error));
});
}
void PeerConnectionMessageHandler::PostGetStats(
StatsObserver* observer,
LegacyStatsCollectorInterface* legacy_stats,
MediaStreamTrackInterface* track) {
signaling_thread_->PostTask(
SafeTask(safety_.flag(), [observer = WrapScoped(observer), legacy_stats,
track = WrapScoped(track)] {
StatsReports reports;
legacy_stats->GetStats(track.get(), &reports);
observer->OnComplete(reports);
}));
}
void PeerConnectionMessageHandler::RequestUsagePatternReport(
std::function<void()> func,
int delay_ms) {
signaling_thread_->PostDelayedTask(SafeTask(safety_.flag(), std::move(func)),
TimeDelta::Millis(delay_ms));
}
} // namespace webrtc

View file

@ -0,0 +1,52 @@
/*
* Copyright 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef PC_PEER_CONNECTION_MESSAGE_HANDLER_H_
#define PC_PEER_CONNECTION_MESSAGE_HANDLER_H_
#include <functional>
#include "api/jsep.h"
#include "api/legacy_stats_types.h"
#include "api/media_stream_interface.h"
#include "api/peer_connection_interface.h"
#include "api/rtc_error.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "api/task_queue/task_queue_base.h"
#include "pc/legacy_stats_collector_interface.h"
namespace webrtc {
class PeerConnectionMessageHandler {
public:
explicit PeerConnectionMessageHandler(rtc::Thread* signaling_thread)
: signaling_thread_(signaling_thread) {}
~PeerConnectionMessageHandler() = default;
void PostSetSessionDescriptionSuccess(
SetSessionDescriptionObserver* observer);
void PostSetSessionDescriptionFailure(SetSessionDescriptionObserver* observer,
RTCError&& error);
void PostCreateSessionDescriptionFailure(
CreateSessionDescriptionObserver* observer,
RTCError error);
void PostGetStats(StatsObserver* observer,
LegacyStatsCollectorInterface* legacy_stats,
MediaStreamTrackInterface* track);
void RequestUsagePatternReport(std::function<void()>, int delay_ms);
private:
ScopedTaskSafety safety_;
TaskQueueBase* const signaling_thread_;
};
} // namespace webrtc
#endif // PC_PEER_CONNECTION_MESSAGE_HANDLER_H_

View file

@ -0,0 +1,174 @@
/*
* 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.
*/
#ifndef PC_PEER_CONNECTION_PROXY_H_
#define PC_PEER_CONNECTION_PROXY_H_
#include <memory>
#include <string>
#include <vector>
#include "api/peer_connection_interface.h"
#include "api/transport/bandwidth_estimation_settings.h"
#include "pc/proxy.h"
namespace webrtc {
// PeerConnection proxy objects will be constructed with two thread pointers,
// signaling and network. The proxy macros don't have 'network' specific macros
// and support for a secondary thread is provided via 'SECONDARY' macros.
// TODO(deadbeef): Move this to .cc file. What threads methods are called on is
// an implementation detail.
BEGIN_PROXY_MAP(PeerConnection)
PROXY_PRIMARY_THREAD_DESTRUCTOR()
PROXY_METHOD0(rtc::scoped_refptr<StreamCollectionInterface>, local_streams)
PROXY_METHOD0(rtc::scoped_refptr<StreamCollectionInterface>, remote_streams)
PROXY_METHOD1(bool, AddStream, MediaStreamInterface*)
PROXY_METHOD1(void, RemoveStream, MediaStreamInterface*)
PROXY_METHOD2(RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>>,
AddTrack,
rtc::scoped_refptr<MediaStreamTrackInterface>,
const std::vector<std::string>&)
PROXY_METHOD3(RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>>,
AddTrack,
rtc::scoped_refptr<MediaStreamTrackInterface>,
const std::vector<std::string>&,
const std::vector<RtpEncodingParameters>&)
PROXY_METHOD1(RTCError,
RemoveTrackOrError,
rtc::scoped_refptr<RtpSenderInterface>)
PROXY_METHOD1(RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>>,
AddTransceiver,
rtc::scoped_refptr<MediaStreamTrackInterface>)
PROXY_METHOD2(RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>>,
AddTransceiver,
rtc::scoped_refptr<MediaStreamTrackInterface>,
const RtpTransceiverInit&)
PROXY_METHOD1(RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>>,
AddTransceiver,
cricket::MediaType)
PROXY_METHOD2(RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>>,
AddTransceiver,
cricket::MediaType,
const RtpTransceiverInit&)
PROXY_METHOD2(rtc::scoped_refptr<RtpSenderInterface>,
CreateSender,
const std::string&,
const std::string&)
PROXY_CONSTMETHOD0(std::vector<rtc::scoped_refptr<RtpSenderInterface>>,
GetSenders)
PROXY_CONSTMETHOD0(std::vector<rtc::scoped_refptr<RtpReceiverInterface>>,
GetReceivers)
PROXY_CONSTMETHOD0(std::vector<rtc::scoped_refptr<RtpTransceiverInterface>>,
GetTransceivers)
PROXY_METHOD3(bool,
GetStats,
StatsObserver*,
MediaStreamTrackInterface*,
StatsOutputLevel)
PROXY_METHOD1(void, GetStats, RTCStatsCollectorCallback*)
PROXY_METHOD2(void,
GetStats,
rtc::scoped_refptr<RtpSenderInterface>,
rtc::scoped_refptr<RTCStatsCollectorCallback>)
PROXY_METHOD2(void,
GetStats,
rtc::scoped_refptr<RtpReceiverInterface>,
rtc::scoped_refptr<RTCStatsCollectorCallback>)
PROXY_METHOD0(void, ClearStatsCache)
PROXY_METHOD2(RTCErrorOr<rtc::scoped_refptr<DataChannelInterface>>,
CreateDataChannelOrError,
const std::string&,
const DataChannelInit*)
PROXY_CONSTMETHOD0(const SessionDescriptionInterface*, local_description)
PROXY_CONSTMETHOD0(const SessionDescriptionInterface*, remote_description)
PROXY_CONSTMETHOD0(const SessionDescriptionInterface*,
current_local_description)
PROXY_CONSTMETHOD0(const SessionDescriptionInterface*,
current_remote_description)
PROXY_CONSTMETHOD0(const SessionDescriptionInterface*,
pending_local_description)
PROXY_CONSTMETHOD0(const SessionDescriptionInterface*,
pending_remote_description)
PROXY_METHOD0(void, RestartIce)
PROXY_METHOD2(void,
CreateOffer,
CreateSessionDescriptionObserver*,
const RTCOfferAnswerOptions&)
PROXY_METHOD2(void,
CreateAnswer,
CreateSessionDescriptionObserver*,
const RTCOfferAnswerOptions&)
PROXY_METHOD2(void,
SetLocalDescription,
std::unique_ptr<SessionDescriptionInterface>,
rtc::scoped_refptr<SetLocalDescriptionObserverInterface>)
PROXY_METHOD1(void,
SetLocalDescription,
rtc::scoped_refptr<SetLocalDescriptionObserverInterface>)
PROXY_METHOD2(void,
SetLocalDescription,
SetSessionDescriptionObserver*,
SessionDescriptionInterface*)
PROXY_METHOD1(void, SetLocalDescription, SetSessionDescriptionObserver*)
PROXY_METHOD2(void,
SetRemoteDescription,
std::unique_ptr<SessionDescriptionInterface>,
rtc::scoped_refptr<SetRemoteDescriptionObserverInterface>)
PROXY_METHOD2(void,
SetRemoteDescription,
SetSessionDescriptionObserver*,
SessionDescriptionInterface*)
PROXY_METHOD1(bool, ShouldFireNegotiationNeededEvent, uint32_t)
PROXY_METHOD0(PeerConnectionInterface::RTCConfiguration, GetConfiguration)
PROXY_METHOD1(RTCError,
SetConfiguration,
const PeerConnectionInterface::RTCConfiguration&)
PROXY_METHOD1(bool, AddIceCandidate, const IceCandidateInterface*)
PROXY_METHOD2(void,
AddIceCandidate,
std::unique_ptr<IceCandidateInterface>,
std::function<void(RTCError)>)
PROXY_METHOD1(bool, RemoveIceCandidates, const std::vector<cricket::Candidate>&)
PROXY_METHOD1(RTCError, SetBitrate, const BitrateSettings&)
PROXY_METHOD1(void,
ReconfigureBandwidthEstimation,
const BandwidthEstimationSettings&)
PROXY_METHOD1(void, SetAudioPlayout, bool)
PROXY_METHOD1(void, SetAudioRecording, bool)
// This method will be invoked on the network thread. See
// PeerConnectionFactory::CreatePeerConnectionOrError for more details.
PROXY_SECONDARY_METHOD1(rtc::scoped_refptr<DtlsTransportInterface>,
LookupDtlsTransportByMid,
const std::string&)
// This method will be invoked on the network thread. See
// PeerConnectionFactory::CreatePeerConnectionOrError for more details.
PROXY_SECONDARY_CONSTMETHOD0(rtc::scoped_refptr<SctpTransportInterface>,
GetSctpTransport)
PROXY_METHOD0(SignalingState, signaling_state)
PROXY_METHOD0(IceConnectionState, ice_connection_state)
PROXY_METHOD0(IceConnectionState, standardized_ice_connection_state)
PROXY_METHOD0(PeerConnectionState, peer_connection_state)
PROXY_METHOD0(IceGatheringState, ice_gathering_state)
PROXY_METHOD0(absl::optional<bool>, can_trickle_ice_candidates)
PROXY_METHOD1(void, AddAdaptationResource, rtc::scoped_refptr<Resource>)
PROXY_METHOD2(bool,
StartRtcEventLog,
std::unique_ptr<RtcEventLogOutput>,
int64_t)
PROXY_METHOD1(bool, StartRtcEventLog, std::unique_ptr<RtcEventLogOutput>)
PROXY_METHOD0(void, StopRtcEventLog)
PROXY_METHOD0(void, Close)
BYPASS_PROXY_CONSTMETHOD0(rtc::Thread*, signaling_thread)
END_PROXY_MAP(PeerConnection)
} // namespace webrtc
#endif // PC_PEER_CONNECTION_PROXY_H_

View file

@ -0,0 +1,457 @@
/*
* Copyright 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/types/optional.h"
#include "api/audio/audio_mixer.h"
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
#include "api/audio_options.h"
#include "api/create_peerconnection_factory.h"
#include "api/jsep.h"
#include "api/media_stream_interface.h"
#include "api/peer_connection_interface.h"
#include "api/rtc_error.h"
#include "api/scoped_refptr.h"
#include "api/stats/rtc_stats.h"
#include "api/stats/rtc_stats_report.h"
#include "api/stats/rtcstats_objects.h"
#include "api/test/metrics/global_metrics_logger_and_exporter.h"
#include "api/test/metrics/metric.h"
#include "api/video_codecs/video_decoder_factory_template.h"
#include "api/video_codecs/video_decoder_factory_template_dav1d_adapter.h"
#include "api/video_codecs/video_decoder_factory_template_libvpx_vp8_adapter.h"
#include "api/video_codecs/video_decoder_factory_template_libvpx_vp9_adapter.h"
#include "api/video_codecs/video_decoder_factory_template_open_h264_adapter.h"
#include "api/video_codecs/video_encoder_factory_template.h"
#include "api/video_codecs/video_encoder_factory_template_libaom_av1_adapter.h"
#include "api/video_codecs/video_encoder_factory_template_libvpx_vp8_adapter.h"
#include "api/video_codecs/video_encoder_factory_template_libvpx_vp9_adapter.h"
#include "api/video_codecs/video_encoder_factory_template_open_h264_adapter.h"
#include "modules/audio_device/include/audio_device.h"
#include "modules/audio_processing/include/audio_processing.h"
#include "p2p/base/port_allocator.h"
#include "p2p/base/port_interface.h"
#include "p2p/base/test_turn_server.h"
#include "p2p/client/basic_port_allocator.h"
#include "pc/peer_connection.h"
#include "pc/peer_connection_wrapper.h"
#include "pc/test/fake_audio_capture_module.h"
#include "pc/test/frame_generator_capturer_video_track_source.h"
#include "pc/test/mock_peer_connection_observers.h"
#include "rtc_base/checks.h"
#include "rtc_base/fake_network.h"
#include "rtc_base/firewall_socket_server.h"
#include "rtc_base/gunit.h"
#include "rtc_base/helpers.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/socket_factory.h"
#include "rtc_base/ssl_certificate.h"
#include "rtc_base/task_queue_for_test.h"
#include "rtc_base/test_certificate_verifier.h"
#include "rtc_base/thread.h"
#include "rtc_base/virtual_socket_server.h"
#include "system_wrappers/include/clock.h"
#include "test/gtest.h"
namespace webrtc {
namespace {
using ::webrtc::test::GetGlobalMetricsLogger;
using ::webrtc::test::ImprovementDirection;
using ::webrtc::test::Unit;
static const int kDefaultTestTimeMs = 15000;
static const int kRampUpTimeMs = 5000;
static const int kPollIntervalTimeMs = 50;
static const int kDefaultTimeoutMs = 10000;
static const rtc::SocketAddress kDefaultLocalAddress("1.1.1.1", 0);
static const char kTurnInternalAddress[] = "88.88.88.0";
static const char kTurnExternalAddress[] = "88.88.88.1";
static const int kTurnInternalPort = 3478;
static const int kTurnExternalPort = 0;
// The video's configured max bitrate in webrtcvideoengine.cc is 1.7 Mbps.
// Setting the network bandwidth to 1 Mbps allows the video's bitrate to push
// the network's limitations.
static const int kNetworkBandwidth = 1000000;
} // namespace
using RTCConfiguration = PeerConnectionInterface::RTCConfiguration;
// This is an end to end test to verify that BWE is functioning when setting
// up a one to one call at the PeerConnection level. The intention of the test
// is to catch potential regressions for different ICE path configurations. The
// test uses a VirtualSocketServer for it's underlying simulated network and
// fake audio and video sources. The test is based upon rampup_tests.cc, but
// instead is at the PeerConnection level and uses a different fake network
// (rampup_tests.cc uses SimulatedNetwork). In the future, this test could
// potentially test different network conditions and test video quality as well
// (video_quality_test.cc does this, but at the call level).
//
// The perf test results are printed using the perf test support. If the
// isolated_script_test_perf_output flag is specified in test_main.cc, then
// the results are written to a JSON formatted file for the Chrome perf
// dashboard. Since this test is a webrtc_perf_test, it will be run in the perf
// console every webrtc commit.
class PeerConnectionWrapperForRampUpTest : public PeerConnectionWrapper {
public:
using PeerConnectionWrapper::PeerConnectionWrapper;
PeerConnectionWrapperForRampUpTest(
rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory,
rtc::scoped_refptr<PeerConnectionInterface> pc,
std::unique_ptr<MockPeerConnectionObserver> observer)
: PeerConnectionWrapper::PeerConnectionWrapper(pc_factory,
pc,
std::move(observer)) {}
bool AddIceCandidates(std::vector<const IceCandidateInterface*> candidates) {
bool success = true;
for (const auto candidate : candidates) {
if (!pc()->AddIceCandidate(candidate)) {
success = false;
}
}
return success;
}
rtc::scoped_refptr<VideoTrackInterface> CreateLocalVideoTrack(
FrameGeneratorCapturerVideoTrackSource::Config config,
Clock* clock) {
video_track_sources_.emplace_back(
rtc::make_ref_counted<FrameGeneratorCapturerVideoTrackSource>(
config, clock, /*is_screencast=*/false));
video_track_sources_.back()->Start();
return rtc::scoped_refptr<VideoTrackInterface>(
pc_factory()->CreateVideoTrack(video_track_sources_.back(),
rtc::CreateRandomUuid()));
}
rtc::scoped_refptr<AudioTrackInterface> CreateLocalAudioTrack(
const cricket::AudioOptions options) {
rtc::scoped_refptr<AudioSourceInterface> source =
pc_factory()->CreateAudioSource(options);
return pc_factory()->CreateAudioTrack(rtc::CreateRandomUuid(),
source.get());
}
private:
std::vector<rtc::scoped_refptr<FrameGeneratorCapturerVideoTrackSource>>
video_track_sources_;
};
// TODO(shampson): Paramaterize the test to run for both Plan B & Unified Plan.
class PeerConnectionRampUpTest : public ::testing::Test {
public:
PeerConnectionRampUpTest()
: clock_(Clock::GetRealTimeClock()),
virtual_socket_server_(new rtc::VirtualSocketServer()),
firewall_socket_server_(
new rtc::FirewallSocketServer(virtual_socket_server_.get())),
firewall_socket_factory_(
new rtc::BasicPacketSocketFactory(firewall_socket_server_.get())),
network_thread_(new rtc::Thread(firewall_socket_server_.get())),
worker_thread_(rtc::Thread::Create()) {
network_thread_->SetName("PCNetworkThread", this);
worker_thread_->SetName("PCWorkerThread", this);
RTC_CHECK(network_thread_->Start());
RTC_CHECK(worker_thread_->Start());
virtual_socket_server_->set_bandwidth(kNetworkBandwidth / 8);
pc_factory_ = CreatePeerConnectionFactory(
network_thread_.get(), worker_thread_.get(), rtc::Thread::Current(),
rtc::scoped_refptr<AudioDeviceModule>(FakeAudioCaptureModule::Create()),
CreateBuiltinAudioEncoderFactory(), CreateBuiltinAudioDecoderFactory(),
std::make_unique<VideoEncoderFactoryTemplate<
LibvpxVp8EncoderTemplateAdapter, LibvpxVp9EncoderTemplateAdapter,
OpenH264EncoderTemplateAdapter, LibaomAv1EncoderTemplateAdapter>>(),
std::make_unique<VideoDecoderFactoryTemplate<
LibvpxVp8DecoderTemplateAdapter, LibvpxVp9DecoderTemplateAdapter,
OpenH264DecoderTemplateAdapter, Dav1dDecoderTemplateAdapter>>(),
nullptr /* audio_mixer */, nullptr /* audio_processing */);
}
virtual ~PeerConnectionRampUpTest() {
SendTask(network_thread(), [this] { turn_servers_.clear(); });
}
bool CreatePeerConnectionWrappers(const RTCConfiguration& caller_config,
const RTCConfiguration& callee_config) {
caller_ = CreatePeerConnectionWrapper(caller_config);
callee_ = CreatePeerConnectionWrapper(callee_config);
return caller_ && callee_;
}
std::unique_ptr<PeerConnectionWrapperForRampUpTest>
CreatePeerConnectionWrapper(const RTCConfiguration& config) {
auto* fake_network_manager = new rtc::FakeNetworkManager();
fake_network_manager->AddInterface(kDefaultLocalAddress);
fake_network_managers_.emplace_back(fake_network_manager);
auto observer = std::make_unique<MockPeerConnectionObserver>();
PeerConnectionDependencies dependencies(observer.get());
cricket::BasicPortAllocator* port_allocator =
new cricket::BasicPortAllocator(fake_network_manager,
firewall_socket_factory_.get());
port_allocator->set_step_delay(cricket::kDefaultStepDelay);
dependencies.allocator =
std::unique_ptr<cricket::BasicPortAllocator>(port_allocator);
dependencies.tls_cert_verifier =
std::make_unique<rtc::TestCertificateVerifier>();
auto result = pc_factory_->CreatePeerConnectionOrError(
config, std::move(dependencies));
if (!result.ok()) {
return nullptr;
}
return std::make_unique<PeerConnectionWrapperForRampUpTest>(
pc_factory_, result.MoveValue(), std::move(observer));
}
void SetupOneWayCall() {
ASSERT_TRUE(caller_);
ASSERT_TRUE(callee_);
FrameGeneratorCapturerVideoTrackSource::Config config;
caller_->AddTrack(caller_->CreateLocalVideoTrack(config, clock_));
// Disable highpass filter so that we can get all the test audio frames.
cricket::AudioOptions options;
options.highpass_filter = false;
caller_->AddTrack(caller_->CreateLocalAudioTrack(options));
// Do the SDP negotiation, and also exchange ice candidates.
ASSERT_TRUE(caller_->ExchangeOfferAnswerWith(callee_.get()));
ASSERT_TRUE_WAIT(
caller_->signaling_state() == PeerConnectionInterface::kStable,
kDefaultTimeoutMs);
ASSERT_TRUE_WAIT(caller_->IsIceGatheringDone(), kDefaultTimeoutMs);
ASSERT_TRUE_WAIT(callee_->IsIceGatheringDone(), kDefaultTimeoutMs);
// Connect an ICE candidate pairs.
ASSERT_TRUE(
callee_->AddIceCandidates(caller_->observer()->GetAllCandidates()));
ASSERT_TRUE(
caller_->AddIceCandidates(callee_->observer()->GetAllCandidates()));
// This means that ICE and DTLS are connected.
ASSERT_TRUE_WAIT(callee_->IsIceConnected(), kDefaultTimeoutMs);
ASSERT_TRUE_WAIT(caller_->IsIceConnected(), kDefaultTimeoutMs);
}
void CreateTurnServer(cricket::ProtocolType type,
const std::string& common_name = "test turn server") {
rtc::Thread* thread = network_thread();
rtc::SocketFactory* factory = firewall_socket_server_.get();
std::unique_ptr<cricket::TestTurnServer> turn_server;
SendTask(network_thread_.get(), [&] {
static const rtc::SocketAddress turn_server_internal_address{
kTurnInternalAddress, kTurnInternalPort};
static const rtc::SocketAddress turn_server_external_address{
kTurnExternalAddress, kTurnExternalPort};
turn_server = std::make_unique<cricket::TestTurnServer>(
thread, factory, turn_server_internal_address,
turn_server_external_address, type, true /*ignore_bad_certs=*/,
common_name);
});
turn_servers_.push_back(std::move(turn_server));
}
// First runs the call for kRampUpTimeMs to ramp up the bandwidth estimate.
// Then runs the test for the remaining test time, grabbing the bandwidth
// estimation stat, every kPollIntervalTimeMs. When finished, averages the
// bandwidth estimations and prints the bandwidth estimation result as a perf
// metric.
void RunTest(const std::string& test_string) {
rtc::Thread::Current()->ProcessMessages(kRampUpTimeMs);
int number_of_polls =
(kDefaultTestTimeMs - kRampUpTimeMs) / kPollIntervalTimeMs;
int total_bwe = 0;
for (int i = 0; i < number_of_polls; ++i) {
rtc::Thread::Current()->ProcessMessages(kPollIntervalTimeMs);
total_bwe += static_cast<int>(GetCallerAvailableBitrateEstimate());
}
double average_bandwidth_estimate = total_bwe / number_of_polls;
std::string value_description =
"bwe_after_" + std::to_string(kDefaultTestTimeMs / 1000) + "_seconds";
GetGlobalMetricsLogger()->LogSingleValueMetric(
"peerconnection_ramp_up_" + test_string, value_description,
average_bandwidth_estimate, Unit::kUnitless,
ImprovementDirection::kNeitherIsBetter);
}
rtc::Thread* network_thread() { return network_thread_.get(); }
rtc::FirewallSocketServer* firewall_socket_server() {
return firewall_socket_server_.get();
}
PeerConnectionWrapperForRampUpTest* caller() { return caller_.get(); }
PeerConnectionWrapperForRampUpTest* callee() { return callee_.get(); }
private:
// Gets the caller's outgoing available bitrate from the stats. Returns 0 if
// something went wrong. It takes the outgoing bitrate from the current
// selected ICE candidate pair's stats.
double GetCallerAvailableBitrateEstimate() {
auto stats = caller_->GetStats();
auto transport_stats = stats->GetStatsOfType<RTCTransportStats>();
if (transport_stats.size() == 0u ||
!transport_stats[0]->selected_candidate_pair_id.has_value()) {
return 0;
}
std::string selected_ice_id =
transport_stats[0]
->GetAttribute(transport_stats[0]->selected_candidate_pair_id)
.ToString();
// Use the selected ICE candidate pair ID to get the appropriate ICE stats.
const RTCIceCandidatePairStats ice_candidate_pair_stats =
stats->Get(selected_ice_id)->cast_to<const RTCIceCandidatePairStats>();
if (ice_candidate_pair_stats.available_outgoing_bitrate.has_value()) {
return *ice_candidate_pair_stats.available_outgoing_bitrate;
}
// We couldn't get the `available_outgoing_bitrate` for the active candidate
// pair.
return 0;
}
Clock* const clock_;
// The turn servers should be accessed & deleted on the network thread to
// avoid a race with the socket read/write which occurs on the network thread.
std::vector<std::unique_ptr<cricket::TestTurnServer>> turn_servers_;
// `virtual_socket_server_` is used by `network_thread_` so it must be
// destroyed later.
// TODO(bugs.webrtc.org/7668): We would like to update the virtual network we
// use for this test. VirtualSocketServer isn't ideal because:
// 1) It uses the same queue & network capacity for both directions.
// 2) VirtualSocketServer implements how the network bandwidth affects the
// send delay differently than the SimulatedNetwork, used by the
// FakeNetworkPipe. It would be ideal if all of levels of virtual
// networks used in testing were consistent.
// We would also like to update this test to record the time to ramp up,
// down, and back up (similar to in rampup_tests.cc). This is problematic with
// the VirtualSocketServer. The first ramp down time is very noisy and the
// second ramp up time can take up to 300 seconds, most likely due to a built
// up queue.
std::unique_ptr<rtc::VirtualSocketServer> virtual_socket_server_;
std::unique_ptr<rtc::FirewallSocketServer> firewall_socket_server_;
std::unique_ptr<rtc::BasicPacketSocketFactory> firewall_socket_factory_;
std::unique_ptr<rtc::Thread> network_thread_;
std::unique_ptr<rtc::Thread> worker_thread_;
// The `pc_factory` uses `network_thread_` & `worker_thread_`, so it must be
// destroyed first.
std::vector<std::unique_ptr<rtc::FakeNetworkManager>> fake_network_managers_;
rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory_;
std::unique_ptr<PeerConnectionWrapperForRampUpTest> caller_;
std::unique_ptr<PeerConnectionWrapperForRampUpTest> callee_;
};
TEST_F(PeerConnectionRampUpTest, Bwe_After_TurnOverTCP) {
CreateTurnServer(cricket::ProtocolType::PROTO_TCP);
PeerConnectionInterface::IceServer ice_server;
std::string ice_server_url = "turn:" + std::string(kTurnInternalAddress) +
":" + std::to_string(kTurnInternalPort) +
"?transport=tcp";
ice_server.urls.push_back(ice_server_url);
ice_server.username = "test";
ice_server.password = "test";
PeerConnectionInterface::RTCConfiguration client_1_config;
client_1_config.sdp_semantics = SdpSemantics::kUnifiedPlan;
client_1_config.servers.push_back(ice_server);
client_1_config.type = PeerConnectionInterface::kRelay;
PeerConnectionInterface::RTCConfiguration client_2_config;
client_2_config.sdp_semantics = SdpSemantics::kUnifiedPlan;
client_2_config.servers.push_back(ice_server);
client_2_config.type = PeerConnectionInterface::kRelay;
ASSERT_TRUE(CreatePeerConnectionWrappers(client_1_config, client_2_config));
SetupOneWayCall();
RunTest("turn_over_tcp");
}
TEST_F(PeerConnectionRampUpTest, Bwe_After_TurnOverUDP) {
CreateTurnServer(cricket::ProtocolType::PROTO_UDP);
PeerConnectionInterface::IceServer ice_server;
std::string ice_server_url = "turn:" + std::string(kTurnInternalAddress) +
":" + std::to_string(kTurnInternalPort);
ice_server.urls.push_back(ice_server_url);
ice_server.username = "test";
ice_server.password = "test";
PeerConnectionInterface::RTCConfiguration client_1_config;
client_1_config.sdp_semantics = SdpSemantics::kUnifiedPlan;
client_1_config.servers.push_back(ice_server);
client_1_config.type = PeerConnectionInterface::kRelay;
PeerConnectionInterface::RTCConfiguration client_2_config;
client_2_config.sdp_semantics = SdpSemantics::kUnifiedPlan;
client_2_config.servers.push_back(ice_server);
client_2_config.type = PeerConnectionInterface::kRelay;
ASSERT_TRUE(CreatePeerConnectionWrappers(client_1_config, client_2_config));
SetupOneWayCall();
RunTest("turn_over_udp");
}
TEST_F(PeerConnectionRampUpTest, Bwe_After_TurnOverTLS) {
CreateTurnServer(cricket::ProtocolType::PROTO_TLS, kTurnInternalAddress);
PeerConnectionInterface::IceServer ice_server;
std::string ice_server_url = "turns:" + std::string(kTurnInternalAddress) +
":" + std::to_string(kTurnInternalPort) +
"?transport=tcp";
ice_server.urls.push_back(ice_server_url);
ice_server.username = "test";
ice_server.password = "test";
PeerConnectionInterface::RTCConfiguration client_1_config;
client_1_config.sdp_semantics = SdpSemantics::kUnifiedPlan;
client_1_config.servers.push_back(ice_server);
client_1_config.type = PeerConnectionInterface::kRelay;
PeerConnectionInterface::RTCConfiguration client_2_config;
client_2_config.sdp_semantics = SdpSemantics::kUnifiedPlan;
client_2_config.servers.push_back(ice_server);
client_2_config.type = PeerConnectionInterface::kRelay;
ASSERT_TRUE(CreatePeerConnectionWrappers(client_1_config, client_2_config));
SetupOneWayCall();
RunTest("turn_over_tls");
}
TEST_F(PeerConnectionRampUpTest, Bwe_After_UDPPeerToPeer) {
PeerConnectionInterface::RTCConfiguration client_1_config;
client_1_config.sdp_semantics = SdpSemantics::kUnifiedPlan;
client_1_config.tcp_candidate_policy =
PeerConnection::kTcpCandidatePolicyDisabled;
PeerConnectionInterface::RTCConfiguration client_2_config;
client_2_config.sdp_semantics = SdpSemantics::kUnifiedPlan;
client_2_config.tcp_candidate_policy =
PeerConnection::kTcpCandidatePolicyDisabled;
ASSERT_TRUE(CreatePeerConnectionWrappers(client_1_config, client_2_config));
SetupOneWayCall();
RunTest("udp_peer_to_peer");
}
TEST_F(PeerConnectionRampUpTest, Bwe_After_TCPPeerToPeer) {
firewall_socket_server()->set_udp_sockets_enabled(false);
PeerConnectionInterface::RTCConfiguration config;
config.sdp_semantics = SdpSemantics::kUnifiedPlan;
ASSERT_TRUE(CreatePeerConnectionWrappers(config, config));
SetupOneWayCall();
RunTest("tcp_peer_to_peer");
}
} // namespace webrtc

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,581 @@
/*
* Copyright 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <algorithm>
#include <iterator>
#include <map>
#include <memory>
#include <ostream> // no-presubmit-check TODO(webrtc:8982)
#include <string>
#include <utility>
#include <vector>
#include "absl/algorithm/container.h"
#include "absl/strings/match.h"
#include "absl/strings/string_view.h"
#include "api/audio/audio_mixer.h"
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
#include "api/audio_codecs/opus_audio_decoder_factory.h"
#include "api/audio_codecs/opus_audio_encoder_factory.h"
#include "api/create_peerconnection_factory.h"
#include "api/jsep.h"
#include "api/media_types.h"
#include "api/peer_connection_interface.h"
#include "api/rtc_error.h"
#include "api/rtp_parameters.h"
#include "api/rtp_sender_interface.h"
#include "api/rtp_transceiver_direction.h"
#include "api/rtp_transceiver_interface.h"
#include "api/scoped_refptr.h"
#include "api/uma_metrics.h"
#include "api/video/video_codec_constants.h"
#include "api/video_codecs/video_decoder_factory_template.h"
#include "api/video_codecs/video_decoder_factory_template_dav1d_adapter.h"
#include "api/video_codecs/video_decoder_factory_template_libvpx_vp8_adapter.h"
#include "api/video_codecs/video_decoder_factory_template_libvpx_vp9_adapter.h"
#include "api/video_codecs/video_decoder_factory_template_open_h264_adapter.h"
#include "api/video_codecs/video_encoder_factory_template.h"
#include "api/video_codecs/video_encoder_factory_template_libaom_av1_adapter.h"
#include "api/video_codecs/video_encoder_factory_template_libvpx_vp8_adapter.h"
#include "api/video_codecs/video_encoder_factory_template_libvpx_vp9_adapter.h"
#include "api/video_codecs/video_encoder_factory_template_open_h264_adapter.h"
#include "media/base/media_constants.h"
#include "media/base/rid_description.h"
#include "media/base/stream_params.h"
#include "modules/audio_device/include/audio_device.h"
#include "modules/audio_processing/include/audio_processing.h"
#include "pc/channel_interface.h"
#include "pc/peer_connection_wrapper.h"
#include "pc/sdp_utils.h"
#include "pc/session_description.h"
#include "pc/simulcast_description.h"
#include "pc/test/fake_audio_capture_module.h"
#include "pc/test/mock_peer_connection_observers.h"
#include "pc/test/simulcast_layer_util.h"
#include "rtc_base/checks.h"
#include "rtc_base/gunit.h"
#include "rtc_base/strings/string_builder.h"
#include "rtc_base/thread.h"
#include "rtc_base/unique_id_generator.h"
#include "system_wrappers/include/metrics.h"
#include "test/gmock.h"
#include "test/gtest.h"
using ::testing::Contains;
using ::testing::Each;
using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
using ::testing::Eq;
using ::testing::Field;
using ::testing::IsEmpty;
using ::testing::Le;
using ::testing::Ne;
using ::testing::Pair;
using ::testing::Property;
using ::testing::SizeIs;
using ::testing::StartsWith;
using cricket::MediaContentDescription;
using cricket::RidDescription;
using cricket::SimulcastDescription;
using cricket::SimulcastLayer;
using cricket::StreamParams;
namespace cricket {
std::ostream& operator<<( // no-presubmit-check TODO(webrtc:8982)
std::ostream& os, // no-presubmit-check TODO(webrtc:8982)
const SimulcastLayer& layer) {
if (layer.is_paused) {
os << "~";
}
return os << layer.rid;
}
} // namespace cricket
namespace webrtc {
class PeerConnectionSimulcastTests : public ::testing::Test {
public:
PeerConnectionSimulcastTests()
: pc_factory_(CreatePeerConnectionFactory(
rtc::Thread::Current(),
rtc::Thread::Current(),
rtc::Thread::Current(),
FakeAudioCaptureModule::Create(),
CreateBuiltinAudioEncoderFactory(),
CreateBuiltinAudioDecoderFactory(),
std::make_unique<
VideoEncoderFactoryTemplate<LibvpxVp8EncoderTemplateAdapter,
LibvpxVp9EncoderTemplateAdapter,
OpenH264EncoderTemplateAdapter,
LibaomAv1EncoderTemplateAdapter>>(),
std::make_unique<
VideoDecoderFactoryTemplate<LibvpxVp8DecoderTemplateAdapter,
LibvpxVp9DecoderTemplateAdapter,
OpenH264DecoderTemplateAdapter,
Dav1dDecoderTemplateAdapter>>(),
nullptr,
nullptr)) {}
rtc::scoped_refptr<PeerConnectionInterface> CreatePeerConnection(
MockPeerConnectionObserver* observer) {
PeerConnectionInterface::RTCConfiguration config;
config.sdp_semantics = SdpSemantics::kUnifiedPlan;
PeerConnectionDependencies pcd(observer);
auto result =
pc_factory_->CreatePeerConnectionOrError(config, std::move(pcd));
EXPECT_TRUE(result.ok());
observer->SetPeerConnectionInterface(result.value().get());
return result.MoveValue();
}
std::unique_ptr<PeerConnectionWrapper> CreatePeerConnectionWrapper() {
auto observer = std::make_unique<MockPeerConnectionObserver>();
auto pc = CreatePeerConnection(observer.get());
return std::make_unique<PeerConnectionWrapper>(pc_factory_, pc,
std::move(observer));
}
void ExchangeOfferAnswer(PeerConnectionWrapper* local,
PeerConnectionWrapper* remote,
const std::vector<SimulcastLayer>& answer_layers) {
auto offer = local->CreateOfferAndSetAsLocal();
// Remove simulcast as the second peer connection won't support it.
RemoveSimulcast(offer.get());
std::string err;
EXPECT_TRUE(remote->SetRemoteDescription(std::move(offer), &err)) << err;
auto answer = remote->CreateAnswerAndSetAsLocal();
// Setup the answer to look like a server response.
auto mcd_answer = answer->description()->contents()[0].media_description();
auto& receive_layers = mcd_answer->simulcast_description().receive_layers();
for (const SimulcastLayer& layer : answer_layers) {
receive_layers.AddLayer(layer);
}
EXPECT_TRUE(local->SetRemoteDescription(std::move(answer), &err)) << err;
}
rtc::scoped_refptr<RtpTransceiverInterface> AddTransceiver(
PeerConnectionWrapper* pc,
const std::vector<SimulcastLayer>& layers,
cricket::MediaType media_type = cricket::MEDIA_TYPE_VIDEO) {
auto init = CreateTransceiverInit(layers);
return pc->AddTransceiver(media_type, init);
}
void AddRequestToReceiveSimulcast(const std::vector<SimulcastLayer>& layers,
SessionDescriptionInterface* sd) {
auto mcd = sd->description()->contents()[0].media_description();
SimulcastDescription simulcast;
auto& receive_layers = simulcast.receive_layers();
for (const SimulcastLayer& layer : layers) {
receive_layers.AddLayer(layer);
}
mcd->set_simulcast_description(simulcast);
}
void ValidateTransceiverParameters(
rtc::scoped_refptr<RtpTransceiverInterface> transceiver,
const std::vector<SimulcastLayer>& layers) {
auto parameters = transceiver->sender()->GetParameters();
std::vector<SimulcastLayer> result_layers;
absl::c_transform(parameters.encodings, std::back_inserter(result_layers),
[](const RtpEncodingParameters& encoding) {
return SimulcastLayer(encoding.rid, !encoding.active);
});
EXPECT_THAT(result_layers, ElementsAreArray(layers));
}
private:
rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory_;
};
// Validates that RIDs are supported arguments when adding a transceiver.
TEST_F(PeerConnectionSimulcastTests, CanCreateTransceiverWithRid) {
auto pc = CreatePeerConnectionWrapper();
auto layers = CreateLayers({"f"}, true);
auto transceiver = AddTransceiver(pc.get(), layers);
ASSERT_TRUE(transceiver);
auto parameters = transceiver->sender()->GetParameters();
// Single RID should be removed.
EXPECT_THAT(parameters.encodings,
ElementsAre(Field("rid", &RtpEncodingParameters::rid, Eq(""))));
}
TEST_F(PeerConnectionSimulcastTests, CanCreateTransceiverWithSimulcast) {
auto pc = CreatePeerConnectionWrapper();
auto layers = CreateLayers({"f", "h", "q"}, true);
auto transceiver = AddTransceiver(pc.get(), layers);
ASSERT_TRUE(transceiver);
ValidateTransceiverParameters(transceiver, layers);
}
TEST_F(PeerConnectionSimulcastTests, RidsAreAutogeneratedIfNotProvided) {
auto pc = CreatePeerConnectionWrapper();
auto init = CreateTransceiverInit(CreateLayers({"f", "h", "q"}, true));
for (RtpEncodingParameters& parameters : init.send_encodings) {
parameters.rid = "";
}
auto transceiver = pc->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init);
auto parameters = transceiver->sender()->GetParameters();
ASSERT_EQ(3u, parameters.encodings.size());
EXPECT_THAT(parameters.encodings,
Each(Field("rid", &RtpEncodingParameters::rid, Ne(""))));
}
// Validates that an error is returned when there is a mix of supplied and not
// supplied RIDs in a call to AddTransceiver.
TEST_F(PeerConnectionSimulcastTests, MustSupplyAllOrNoRidsInSimulcast) {
auto pc_wrapper = CreatePeerConnectionWrapper();
auto pc = pc_wrapper->pc();
// Cannot create a layer with empty RID. Remove the RID after init is created.
auto layers = CreateLayers({"f", "h", "remove"}, true);
auto init = CreateTransceiverInit(layers);
init.send_encodings[2].rid = "";
auto error = pc->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init);
EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, error.error().type());
}
// Validates that an error is returned when illegal RIDs are supplied.
TEST_F(PeerConnectionSimulcastTests, ChecksForIllegalRidValues) {
auto pc_wrapper = CreatePeerConnectionWrapper();
auto pc = pc_wrapper->pc();
auto layers = CreateLayers({"f", "h", "~q"}, true);
auto init = CreateTransceiverInit(layers);
auto error = pc->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init);
EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, error.error().type());
}
// Validates that a single RID is removed from the encoding layer.
TEST_F(PeerConnectionSimulcastTests, SingleRidIsRemovedFromSessionDescription) {
auto pc = CreatePeerConnectionWrapper();
auto transceiver = AddTransceiver(pc.get(), CreateLayers({"1"}, true));
auto offer = pc->CreateOfferAndSetAsLocal();
ASSERT_TRUE(offer);
auto contents = offer->description()->contents();
ASSERT_EQ(1u, contents.size());
EXPECT_THAT(contents[0].media_description()->streams(),
ElementsAre(Property(&StreamParams::has_rids, false)));
}
TEST_F(PeerConnectionSimulcastTests, SimulcastLayersRemovedFromTail) {
static_assert(
kMaxSimulcastStreams < 8,
"Test assumes that the platform does not allow 8 simulcast layers");
auto pc = CreatePeerConnectionWrapper();
auto layers = CreateLayers({"1", "2", "3", "4", "5", "6", "7", "8"}, true);
std::vector<SimulcastLayer> expected_layers;
std::copy_n(layers.begin(), kMaxSimulcastStreams,
std::back_inserter(expected_layers));
auto transceiver = AddTransceiver(pc.get(), layers);
ValidateTransceiverParameters(transceiver, expected_layers);
}
// Checks that an offfer to send simulcast contains a SimulcastDescription.
TEST_F(PeerConnectionSimulcastTests, SimulcastAppearsInSessionDescription) {
auto pc = CreatePeerConnectionWrapper();
std::vector<std::string> rids({"f", "h", "q"});
auto layers = CreateLayers(rids, true);
auto transceiver = AddTransceiver(pc.get(), layers);
auto offer = pc->CreateOffer();
ASSERT_TRUE(offer);
auto contents = offer->description()->contents();
ASSERT_EQ(1u, contents.size());
auto content = contents[0];
auto mcd = content.media_description();
ASSERT_TRUE(mcd->HasSimulcast());
auto simulcast = mcd->simulcast_description();
EXPECT_THAT(simulcast.receive_layers(), IsEmpty());
// The size is validated separately because GetAllLayers() flattens the list.
EXPECT_THAT(simulcast.send_layers(), SizeIs(3));
std::vector<SimulcastLayer> result = simulcast.send_layers().GetAllLayers();
EXPECT_THAT(result, ElementsAreArray(layers));
auto streams = mcd->streams();
ASSERT_EQ(1u, streams.size());
auto stream = streams[0];
EXPECT_FALSE(stream.has_ssrcs());
EXPECT_TRUE(stream.has_rids());
std::vector<std::string> result_rids;
absl::c_transform(stream.rids(), std::back_inserter(result_rids),
[](const RidDescription& rid) { return rid.rid; });
EXPECT_THAT(result_rids, ElementsAreArray(rids));
}
// Checks that Simulcast layers propagate to the sender parameters.
TEST_F(PeerConnectionSimulcastTests, SimulcastLayersAreSetInSender) {
auto local = CreatePeerConnectionWrapper();
auto remote = CreatePeerConnectionWrapper();
auto layers = CreateLayers({"f", "h", "q"}, true);
auto transceiver = AddTransceiver(local.get(), layers);
auto offer = local->CreateOfferAndSetAsLocal();
{
SCOPED_TRACE("after create offer");
ValidateTransceiverParameters(transceiver, layers);
}
// Remove simulcast as the second peer connection won't support it.
auto simulcast = RemoveSimulcast(offer.get());
std::string error;
EXPECT_TRUE(remote->SetRemoteDescription(std::move(offer), &error)) << error;
auto answer = remote->CreateAnswerAndSetAsLocal();
// Setup an answer that mimics a server accepting simulcast.
auto mcd_answer = answer->description()->contents()[0].media_description();
mcd_answer->mutable_streams().clear();
auto simulcast_layers = simulcast.send_layers().GetAllLayers();
auto& receive_layers = mcd_answer->simulcast_description().receive_layers();
for (const auto& layer : simulcast_layers) {
receive_layers.AddLayer(layer);
}
EXPECT_TRUE(local->SetRemoteDescription(std::move(answer), &error)) << error;
{
SCOPED_TRACE("after set remote");
ValidateTransceiverParameters(transceiver, layers);
}
}
// Checks that paused Simulcast layers propagate to the sender parameters.
TEST_F(PeerConnectionSimulcastTests, PausedSimulcastLayersAreDisabledInSender) {
auto local = CreatePeerConnectionWrapper();
auto remote = CreatePeerConnectionWrapper();
auto layers = CreateLayers({"f", "h", "q"}, {true, false, true});
auto server_layers = CreateLayers({"f", "h", "q"}, {true, false, false});
RTC_DCHECK_EQ(layers.size(), server_layers.size());
auto transceiver = AddTransceiver(local.get(), layers);
auto offer = local->CreateOfferAndSetAsLocal();
{
SCOPED_TRACE("after create offer");
ValidateTransceiverParameters(transceiver, layers);
}
// Remove simulcast as the second peer connection won't support it.
RemoveSimulcast(offer.get());
std::string error;
EXPECT_TRUE(remote->SetRemoteDescription(std::move(offer), &error)) << error;
auto answer = remote->CreateAnswerAndSetAsLocal();
// Setup an answer that mimics a server accepting simulcast.
auto mcd_answer = answer->description()->contents()[0].media_description();
mcd_answer->mutable_streams().clear();
auto& receive_layers = mcd_answer->simulcast_description().receive_layers();
for (const SimulcastLayer& layer : server_layers) {
receive_layers.AddLayer(layer);
}
EXPECT_TRUE(local->SetRemoteDescription(std::move(answer), &error)) << error;
{
SCOPED_TRACE("after set remote");
ValidateTransceiverParameters(transceiver, server_layers);
}
}
// Checks that when Simulcast is not supported by the remote party, then all
// the layers (except the first) are removed.
TEST_F(PeerConnectionSimulcastTests, SimulcastRejectedRemovesExtraLayers) {
auto local = CreatePeerConnectionWrapper();
auto remote = CreatePeerConnectionWrapper();
auto layers = CreateLayers({"1", "2", "3", "4"}, true);
auto transceiver = AddTransceiver(local.get(), layers);
ExchangeOfferAnswer(local.get(), remote.get(), {});
auto parameters = transceiver->sender()->GetParameters();
// Should only have the first layer.
EXPECT_THAT(parameters.encodings,
ElementsAre(Field("rid", &RtpEncodingParameters::rid, Eq("1"))));
}
// Checks that if Simulcast is supported by remote party, but some layers are
// rejected, then only rejected layers are removed from the sender.
TEST_F(PeerConnectionSimulcastTests, RejectedSimulcastLayersAreDeactivated) {
auto local = CreatePeerConnectionWrapper();
auto remote = CreatePeerConnectionWrapper();
auto layers = CreateLayers({"1", "2", "3"}, true);
auto expected_layers = CreateLayers({"2", "3"}, true);
auto transceiver = AddTransceiver(local.get(), layers);
auto offer = local->CreateOfferAndSetAsLocal();
{
SCOPED_TRACE("after create offer");
ValidateTransceiverParameters(transceiver, layers);
}
// Remove simulcast as the second peer connection won't support it.
auto removed_simulcast = RemoveSimulcast(offer.get());
std::string error;
EXPECT_TRUE(remote->SetRemoteDescription(std::move(offer), &error)) << error;
auto answer = remote->CreateAnswerAndSetAsLocal();
auto mcd_answer = answer->description()->contents()[0].media_description();
// Setup the answer to look like a server response.
// Remove one of the layers to reject it in the answer.
auto simulcast_layers = removed_simulcast.send_layers().GetAllLayers();
simulcast_layers.erase(simulcast_layers.begin());
auto& receive_layers = mcd_answer->simulcast_description().receive_layers();
for (const auto& layer : simulcast_layers) {
receive_layers.AddLayer(layer);
}
ASSERT_TRUE(mcd_answer->HasSimulcast());
EXPECT_TRUE(local->SetRemoteDescription(std::move(answer), &error)) << error;
{
SCOPED_TRACE("after set remote");
ValidateTransceiverParameters(transceiver, expected_layers);
}
}
// Checks that simulcast is set up correctly when the server sends an offer
// requesting to receive simulcast.
TEST_F(PeerConnectionSimulcastTests, ServerSendsOfferToReceiveSimulcast) {
auto local = CreatePeerConnectionWrapper();
auto remote = CreatePeerConnectionWrapper();
auto layers = CreateLayers({"f", "h", "q"}, true);
AddTransceiver(local.get(), layers);
auto offer = local->CreateOfferAndSetAsLocal();
// Remove simulcast as a sender and set it up as a receiver.
RemoveSimulcast(offer.get());
AddRequestToReceiveSimulcast(layers, offer.get());
std::string error;
EXPECT_TRUE(remote->SetRemoteDescription(std::move(offer), &error)) << error;
auto transceiver = remote->pc()->GetTransceivers()[0];
transceiver->SetDirectionWithError(RtpTransceiverDirection::kSendRecv);
EXPECT_TRUE(remote->CreateAnswerAndSetAsLocal());
ValidateTransceiverParameters(transceiver, layers);
}
// Checks that SetRemoteDescription doesn't attempt to associate a transceiver
// when simulcast is requested by the server.
TEST_F(PeerConnectionSimulcastTests, TransceiverIsNotRecycledWithSimulcast) {
auto local = CreatePeerConnectionWrapper();
auto remote = CreatePeerConnectionWrapper();
auto layers = CreateLayers({"f", "h", "q"}, true);
AddTransceiver(local.get(), layers);
auto offer = local->CreateOfferAndSetAsLocal();
// Remove simulcast as a sender and set it up as a receiver.
RemoveSimulcast(offer.get());
AddRequestToReceiveSimulcast(layers, offer.get());
// Call AddTrack so that a transceiver is created.
remote->AddVideoTrack("fake_track");
std::string error;
EXPECT_TRUE(remote->SetRemoteDescription(std::move(offer), &error)) << error;
auto transceivers = remote->pc()->GetTransceivers();
ASSERT_EQ(2u, transceivers.size());
auto transceiver = transceivers[1];
transceiver->SetDirectionWithError(RtpTransceiverDirection::kSendRecv);
EXPECT_TRUE(remote->CreateAnswerAndSetAsLocal());
ValidateTransceiverParameters(transceiver, layers);
}
// Checks that if the number of layers changes during negotiation, then any
// outstanding get/set parameters transaction is invalidated.
TEST_F(PeerConnectionSimulcastTests, ParametersAreInvalidatedWhenLayersChange) {
auto local = CreatePeerConnectionWrapper();
auto remote = CreatePeerConnectionWrapper();
auto layers = CreateLayers({"1", "2", "3"}, true);
auto transceiver = AddTransceiver(local.get(), layers);
auto parameters = transceiver->sender()->GetParameters();
ASSERT_EQ(3u, parameters.encodings.size());
// Response will reject simulcast altogether.
ExchangeOfferAnswer(local.get(), remote.get(), {});
auto result = transceiver->sender()->SetParameters(parameters);
EXPECT_EQ(RTCErrorType::INVALID_STATE, result.type());
}
// Checks that even though negotiation modifies the sender's parameters, an
// outstanding get/set parameters transaction is not invalidated.
// This test negotiates twice because initial parameters before negotiation
// is missing critical information and cannot be set on the sender.
TEST_F(PeerConnectionSimulcastTests,
NegotiationDoesNotInvalidateParameterTransactions) {
auto local = CreatePeerConnectionWrapper();
auto remote = CreatePeerConnectionWrapper();
auto layers = CreateLayers({"1", "2", "3"}, true);
auto expected_layers = CreateLayers({"1", "2", "3"}, false);
auto transceiver = AddTransceiver(local.get(), layers);
ExchangeOfferAnswer(local.get(), remote.get(), expected_layers);
// Verify that negotiation does not invalidate the parameters.
auto parameters = transceiver->sender()->GetParameters();
ExchangeOfferAnswer(local.get(), remote.get(), expected_layers);
auto result = transceiver->sender()->SetParameters(parameters);
EXPECT_TRUE(result.ok());
ValidateTransceiverParameters(transceiver, expected_layers);
}
// Tests that a simulcast answer is rejected if the RID extension is not
// negotiated.
TEST_F(PeerConnectionSimulcastTests, NegotiationDoesNotHaveRidExtensionFails) {
auto local = CreatePeerConnectionWrapper();
auto remote = CreatePeerConnectionWrapper();
auto layers = CreateLayers({"1", "2", "3"}, true);
auto expected_layers = CreateLayers({"1"}, true);
auto transceiver = AddTransceiver(local.get(), layers);
auto offer = local->CreateOfferAndSetAsLocal();
// Remove simulcast as the second peer connection won't support it.
RemoveSimulcast(offer.get());
std::string err;
EXPECT_TRUE(remote->SetRemoteDescription(std::move(offer), &err)) << err;
auto answer = remote->CreateAnswerAndSetAsLocal();
// Setup the answer to look like a server response.
// Drop the RID header extension.
auto mcd_answer = answer->description()->contents()[0].media_description();
auto& receive_layers = mcd_answer->simulcast_description().receive_layers();
for (const SimulcastLayer& layer : layers) {
receive_layers.AddLayer(layer);
}
cricket::RtpHeaderExtensions extensions;
for (auto extension : mcd_answer->rtp_header_extensions()) {
if (extension.uri != RtpExtension::kRidUri) {
extensions.push_back(extension);
}
}
mcd_answer->set_rtp_header_extensions(extensions);
EXPECT_EQ(layers.size(), mcd_answer->simulcast_description()
.receive_layers()
.GetAllLayers()
.size());
EXPECT_FALSE(local->SetRemoteDescription(std::move(answer), &err)) << err;
}
TEST_F(PeerConnectionSimulcastTests, SimulcastAudioRejected) {
auto local = CreatePeerConnectionWrapper();
auto remote = CreatePeerConnectionWrapper();
auto layers = CreateLayers({"1", "2", "3", "4"}, true);
auto transceiver =
AddTransceiver(local.get(), layers, cricket::MEDIA_TYPE_AUDIO);
// Should only have the first layer.
auto parameters = transceiver->sender()->GetParameters();
EXPECT_EQ(1u, parameters.encodings.size());
EXPECT_THAT(parameters.encodings,
ElementsAre(Field("rid", &RtpEncodingParameters::rid, Eq(""))));
ExchangeOfferAnswer(local.get(), remote.get(), {});
// Still have a single layer after negotiation
parameters = transceiver->sender()->GetParameters();
EXPECT_EQ(1u, parameters.encodings.size());
EXPECT_THAT(parameters.encodings,
ElementsAre(Field("rid", &RtpEncodingParameters::rid, Eq(""))));
}
// Check that modifying the offer to remove simulcast and at the same
// time leaving in a RID line does not cause an exception.
TEST_F(PeerConnectionSimulcastTests, SimulcastSldModificationRejected) {
auto local = CreatePeerConnectionWrapper();
auto remote = CreatePeerConnectionWrapper();
auto layers = CreateLayers({"1", "2", "3"}, true);
AddTransceiver(local.get(), layers);
auto offer = local->CreateOffer();
std::string as_string;
EXPECT_TRUE(offer->ToString(&as_string));
auto simulcast_marker = "a=rid:3 send\r\na=simulcast:send 1;2;3\r\n";
auto pos = as_string.find(simulcast_marker);
EXPECT_NE(pos, std::string::npos);
as_string.erase(pos, strlen(simulcast_marker));
SdpParseError parse_error;
auto modified_offer =
CreateSessionDescription(SdpType::kOffer, as_string, &parse_error);
EXPECT_TRUE(modified_offer);
EXPECT_TRUE(local->SetLocalDescription(std::move(modified_offer)));
}
} // namespace webrtc

View file

@ -0,0 +1,305 @@
/*
* Copyright 2022 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.
*/
// Integration tests for PeerConnection.
// These tests exercise a full stack for the SVC extension.
#include <stdint.h>
#include <functional>
#include <vector>
#include "absl/strings/match.h"
#include "api/rtc_error.h"
#include "api/rtp_parameters.h"
#include "api/rtp_transceiver_interface.h"
#include "api/scoped_refptr.h"
#include "pc/test/integration_test_helpers.h"
#include "rtc_base/gunit.h"
#include "rtc_base/helpers.h"
#include "test/gtest.h"
namespace webrtc {
namespace {
class PeerConnectionSVCIntegrationTest
: public PeerConnectionIntegrationBaseTest {
protected:
PeerConnectionSVCIntegrationTest()
: PeerConnectionIntegrationBaseTest(SdpSemantics::kUnifiedPlan) {}
RTCError SetCodecPreferences(
rtc::scoped_refptr<RtpTransceiverInterface> transceiver,
absl::string_view codec_name) {
RtpCapabilities capabilities =
caller()->pc_factory()->GetRtpSenderCapabilities(
cricket::MEDIA_TYPE_VIDEO);
std::vector<RtpCodecCapability> codecs;
for (const RtpCodecCapability& codec_capability : capabilities.codecs) {
if (codec_capability.name == codec_name)
codecs.push_back(codec_capability);
}
return transceiver->SetCodecPreferences(codecs);
}
};
TEST_F(PeerConnectionSVCIntegrationTest, AddTransceiverAcceptsL1T1) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
RtpTransceiverInit init;
RtpEncodingParameters encoding_parameters;
encoding_parameters.scalability_mode = "L1T1";
init.send_encodings.push_back(encoding_parameters);
auto transceiver_or_error =
caller()->pc()->AddTransceiver(caller()->CreateLocalVideoTrack(), init);
EXPECT_TRUE(transceiver_or_error.ok());
}
TEST_F(PeerConnectionSVCIntegrationTest, AddTransceiverAcceptsL3T3) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
RtpTransceiverInit init;
RtpEncodingParameters encoding_parameters;
encoding_parameters.scalability_mode = "L3T3";
init.send_encodings.push_back(encoding_parameters);
auto transceiver_or_error =
caller()->pc()->AddTransceiver(caller()->CreateLocalVideoTrack(), init);
EXPECT_TRUE(transceiver_or_error.ok());
}
TEST_F(PeerConnectionSVCIntegrationTest,
AddTransceiverRejectsUnknownScalabilityMode) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
RtpTransceiverInit init;
RtpEncodingParameters encoding_parameters;
encoding_parameters.scalability_mode = "FOOBAR";
init.send_encodings.push_back(encoding_parameters);
auto transceiver_or_error =
caller()->pc()->AddTransceiver(caller()->CreateLocalVideoTrack(), init);
EXPECT_FALSE(transceiver_or_error.ok());
EXPECT_EQ(transceiver_or_error.error().type(),
RTCErrorType::UNSUPPORTED_OPERATION);
}
TEST_F(PeerConnectionSVCIntegrationTest, SetParametersAcceptsL1T3WithVP8) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
RtpCapabilities capabilities =
caller()->pc_factory()->GetRtpSenderCapabilities(
cricket::MEDIA_TYPE_VIDEO);
std::vector<RtpCodecCapability> vp8_codec;
for (const RtpCodecCapability& codec_capability : capabilities.codecs) {
if (codec_capability.name == cricket::kVp8CodecName)
vp8_codec.push_back(codec_capability);
}
RtpTransceiverInit init;
RtpEncodingParameters encoding_parameters;
init.send_encodings.push_back(encoding_parameters);
auto transceiver_or_error =
caller()->pc()->AddTransceiver(caller()->CreateLocalVideoTrack(), init);
ASSERT_TRUE(transceiver_or_error.ok());
auto transceiver = transceiver_or_error.MoveValue();
EXPECT_TRUE(transceiver->SetCodecPreferences(vp8_codec).ok());
RtpParameters parameters = transceiver->sender()->GetParameters();
ASSERT_EQ(parameters.encodings.size(), 1u);
parameters.encodings[0].scalability_mode = "L1T3";
auto result = transceiver->sender()->SetParameters(parameters);
EXPECT_TRUE(result.ok());
}
TEST_F(PeerConnectionSVCIntegrationTest, SetParametersRejectsL3T3WithVP8) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
RtpTransceiverInit init;
RtpEncodingParameters encoding_parameters;
init.send_encodings.push_back(encoding_parameters);
auto transceiver_or_error =
caller()->pc()->AddTransceiver(caller()->CreateLocalVideoTrack(), init);
ASSERT_TRUE(transceiver_or_error.ok());
auto transceiver = transceiver_or_error.MoveValue();
EXPECT_TRUE(SetCodecPreferences(transceiver, cricket::kVp8CodecName).ok());
RtpParameters parameters = transceiver->sender()->GetParameters();
ASSERT_EQ(parameters.encodings.size(), 1u);
parameters.encodings[0].scalability_mode = "L3T3";
auto result = transceiver->sender()->SetParameters(parameters);
EXPECT_FALSE(result.ok());
EXPECT_EQ(result.type(), RTCErrorType::INVALID_MODIFICATION);
}
TEST_F(PeerConnectionSVCIntegrationTest,
SetParametersAcceptsL1T3WithVP8AfterNegotiation) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
RtpTransceiverInit init;
RtpEncodingParameters encoding_parameters;
init.send_encodings.push_back(encoding_parameters);
auto transceiver_or_error =
caller()->pc()->AddTransceiver(caller()->CreateLocalVideoTrack(), init);
ASSERT_TRUE(transceiver_or_error.ok());
auto transceiver = transceiver_or_error.MoveValue();
EXPECT_TRUE(SetCodecPreferences(transceiver, cricket::kVp8CodecName).ok());
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
RtpParameters parameters = transceiver->sender()->GetParameters();
ASSERT_EQ(parameters.encodings.size(), 1u);
parameters.encodings[0].scalability_mode = "L1T3";
auto result = transceiver->sender()->SetParameters(parameters);
EXPECT_TRUE(result.ok());
}
TEST_F(PeerConnectionSVCIntegrationTest,
SetParametersAcceptsL3T3WithVP9AfterNegotiation) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
RtpTransceiverInit init;
RtpEncodingParameters encoding_parameters;
init.send_encodings.push_back(encoding_parameters);
auto transceiver_or_error =
caller()->pc()->AddTransceiver(caller()->CreateLocalVideoTrack(), init);
ASSERT_TRUE(transceiver_or_error.ok());
auto transceiver = transceiver_or_error.MoveValue();
EXPECT_TRUE(SetCodecPreferences(transceiver, cricket::kVp9CodecName).ok());
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
RtpParameters parameters = transceiver->sender()->GetParameters();
ASSERT_EQ(parameters.encodings.size(), 1u);
parameters.encodings[0].scalability_mode = "L3T3";
auto result = transceiver->sender()->SetParameters(parameters);
EXPECT_TRUE(result.ok());
}
TEST_F(PeerConnectionSVCIntegrationTest,
SetParametersRejectsL3T3WithVP8AfterNegotiation) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
RtpTransceiverInit init;
RtpEncodingParameters encoding_parameters;
init.send_encodings.push_back(encoding_parameters);
auto transceiver_or_error =
caller()->pc()->AddTransceiver(caller()->CreateLocalVideoTrack(), init);
ASSERT_TRUE(transceiver_or_error.ok());
auto transceiver = transceiver_or_error.MoveValue();
EXPECT_TRUE(SetCodecPreferences(transceiver, cricket::kVp8CodecName).ok());
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
RtpParameters parameters = transceiver->sender()->GetParameters();
ASSERT_EQ(parameters.encodings.size(), 1u);
parameters.encodings[0].scalability_mode = "L3T3";
auto result = transceiver->sender()->SetParameters(parameters);
EXPECT_FALSE(result.ok());
EXPECT_EQ(result.type(), RTCErrorType::INVALID_MODIFICATION);
}
TEST_F(PeerConnectionSVCIntegrationTest,
SetParametersRejectsInvalidModeWithVP9AfterNegotiation) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
RtpTransceiverInit init;
RtpEncodingParameters encoding_parameters;
init.send_encodings.push_back(encoding_parameters);
auto transceiver_or_error =
caller()->pc()->AddTransceiver(caller()->CreateLocalVideoTrack(), init);
ASSERT_TRUE(transceiver_or_error.ok());
auto transceiver = transceiver_or_error.MoveValue();
EXPECT_TRUE(SetCodecPreferences(transceiver, cricket::kVp9CodecName).ok());
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
RtpParameters parameters = transceiver->sender()->GetParameters();
ASSERT_EQ(parameters.encodings.size(), 1u);
parameters.encodings[0].scalability_mode = "FOOBAR";
auto result = transceiver->sender()->SetParameters(parameters);
EXPECT_FALSE(result.ok());
EXPECT_EQ(result.type(), RTCErrorType::INVALID_MODIFICATION);
}
TEST_F(PeerConnectionSVCIntegrationTest, FallbackToL1Tx) {
ASSERT_TRUE(CreatePeerConnectionWrappers());
ConnectFakeSignaling();
RtpTransceiverInit init;
RtpEncodingParameters encoding_parameters;
init.send_encodings.push_back(encoding_parameters);
auto transceiver_or_error =
caller()->pc()->AddTransceiver(caller()->CreateLocalVideoTrack(), init);
ASSERT_TRUE(transceiver_or_error.ok());
auto caller_transceiver = transceiver_or_error.MoveValue();
RtpCapabilities capabilities =
caller()->pc_factory()->GetRtpSenderCapabilities(
cricket::MEDIA_TYPE_VIDEO);
std::vector<RtpCodecCapability> send_codecs = capabilities.codecs;
// Only keep VP9 in the caller
send_codecs.erase(std::partition(send_codecs.begin(), send_codecs.end(),
[](const auto& codec) -> bool {
return codec.name ==
cricket::kVp9CodecName;
}),
send_codecs.end());
ASSERT_FALSE(send_codecs.empty());
caller_transceiver->SetCodecPreferences(send_codecs);
// L3T3 should be supported by VP9
RtpParameters parameters = caller_transceiver->sender()->GetParameters();
ASSERT_EQ(parameters.encodings.size(), 1u);
parameters.encodings[0].scalability_mode = "L3T3";
auto result = caller_transceiver->sender()->SetParameters(parameters);
EXPECT_TRUE(result.ok());
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
parameters = caller_transceiver->sender()->GetParameters();
ASSERT_TRUE(parameters.encodings[0].scalability_mode.has_value());
EXPECT_TRUE(
absl::StartsWith(*parameters.encodings[0].scalability_mode, "L3T3"));
// Keep only VP8 in the caller
send_codecs = capabilities.codecs;
send_codecs.erase(std::partition(send_codecs.begin(), send_codecs.end(),
[](const auto& codec) -> bool {
return codec.name ==
cricket::kVp8CodecName;
}),
send_codecs.end());
ASSERT_FALSE(send_codecs.empty());
caller_transceiver->SetCodecPreferences(send_codecs);
// Renegotiate to force the new codec list to be used
caller()->CreateAndSetAndSignalOffer();
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
// Fallback should happen and L3T3 is not used anymore
parameters = caller_transceiver->sender()->GetParameters();
ASSERT_TRUE(parameters.encodings[0].scalability_mode.has_value());
EXPECT_TRUE(
absl::StartsWith(*parameters.encodings[0].scalability_mode, "L1T"));
}
} // namespace
} // namespace webrtc

View file

@ -0,0 +1,349 @@
/*
* Copyright 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/peer_connection_wrapper.h"
#include <stdint.h>
#include <utility>
#include <vector>
#include "absl/types/optional.h"
#include "api/function_view.h"
#include "api/set_remote_description_observer_interface.h"
#include "pc/sdp_utils.h"
#include "pc/test/fake_video_track_source.h"
#include "rtc_base/checks.h"
#include "rtc_base/gunit.h"
#include "rtc_base/logging.h"
#include "test/gtest.h"
namespace webrtc {
using RTCOfferAnswerOptions = PeerConnectionInterface::RTCOfferAnswerOptions;
namespace {
const uint32_t kDefaultTimeout = 10000U;
}
PeerConnectionWrapper::PeerConnectionWrapper(
rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory,
rtc::scoped_refptr<PeerConnectionInterface> pc,
std::unique_ptr<MockPeerConnectionObserver> observer)
: pc_factory_(std::move(pc_factory)),
observer_(std::move(observer)),
pc_(std::move(pc)) {
RTC_DCHECK(pc_factory_);
RTC_DCHECK(pc_);
RTC_DCHECK(observer_);
observer_->SetPeerConnectionInterface(pc_.get());
}
PeerConnectionWrapper::~PeerConnectionWrapper() {
if (pc_)
pc_->Close();
}
PeerConnectionFactoryInterface* PeerConnectionWrapper::pc_factory() {
return pc_factory_.get();
}
PeerConnectionInterface* PeerConnectionWrapper::pc() {
return pc_.get();
}
MockPeerConnectionObserver* PeerConnectionWrapper::observer() {
return observer_.get();
}
std::unique_ptr<SessionDescriptionInterface>
PeerConnectionWrapper::CreateOffer() {
return CreateOffer(RTCOfferAnswerOptions());
}
std::unique_ptr<SessionDescriptionInterface> PeerConnectionWrapper::CreateOffer(
const PeerConnectionInterface::RTCOfferAnswerOptions& options,
std::string* error_out) {
return CreateSdp(
[this, options](CreateSessionDescriptionObserver* observer) {
pc()->CreateOffer(observer, options);
},
error_out);
}
std::unique_ptr<SessionDescriptionInterface>
PeerConnectionWrapper::CreateOfferAndSetAsLocal() {
return CreateOfferAndSetAsLocal(RTCOfferAnswerOptions());
}
std::unique_ptr<SessionDescriptionInterface>
PeerConnectionWrapper::CreateOfferAndSetAsLocal(
const PeerConnectionInterface::RTCOfferAnswerOptions& options) {
auto offer = CreateOffer(options);
if (!offer) {
return nullptr;
}
EXPECT_TRUE(SetLocalDescription(CloneSessionDescription(offer.get())));
return offer;
}
std::unique_ptr<SessionDescriptionInterface>
PeerConnectionWrapper::CreateAnswer() {
return CreateAnswer(RTCOfferAnswerOptions());
}
std::unique_ptr<SessionDescriptionInterface>
PeerConnectionWrapper::CreateAnswer(
const PeerConnectionInterface::RTCOfferAnswerOptions& options,
std::string* error_out) {
return CreateSdp(
[this, options](CreateSessionDescriptionObserver* observer) {
pc()->CreateAnswer(observer, options);
},
error_out);
}
std::unique_ptr<SessionDescriptionInterface>
PeerConnectionWrapper::CreateAnswerAndSetAsLocal() {
return CreateAnswerAndSetAsLocal(RTCOfferAnswerOptions());
}
std::unique_ptr<SessionDescriptionInterface>
PeerConnectionWrapper::CreateAnswerAndSetAsLocal(
const PeerConnectionInterface::RTCOfferAnswerOptions& options) {
auto answer = CreateAnswer(options);
if (!answer) {
return nullptr;
}
EXPECT_TRUE(SetLocalDescription(CloneSessionDescription(answer.get())));
return answer;
}
std::unique_ptr<SessionDescriptionInterface>
PeerConnectionWrapper::CreateRollback() {
return CreateSessionDescription(SdpType::kRollback, "");
}
std::unique_ptr<SessionDescriptionInterface> PeerConnectionWrapper::CreateSdp(
rtc::FunctionView<void(CreateSessionDescriptionObserver*)> fn,
std::string* error_out) {
auto observer = rtc::make_ref_counted<MockCreateSessionDescriptionObserver>();
fn(observer.get());
EXPECT_EQ_WAIT(true, observer->called(), kDefaultTimeout);
if (error_out && !observer->result()) {
*error_out = observer->error();
}
return observer->MoveDescription();
}
bool PeerConnectionWrapper::SetLocalDescription(
std::unique_ptr<SessionDescriptionInterface> desc,
std::string* error_out) {
return SetSdp(
[this, &desc](SetSessionDescriptionObserver* observer) {
pc()->SetLocalDescription(observer, desc.release());
},
error_out);
}
bool PeerConnectionWrapper::SetRemoteDescription(
std::unique_ptr<SessionDescriptionInterface> desc,
std::string* error_out) {
return SetSdp(
[this, &desc](SetSessionDescriptionObserver* observer) {
pc()->SetRemoteDescription(observer, desc.release());
},
error_out);
}
bool PeerConnectionWrapper::SetRemoteDescription(
std::unique_ptr<SessionDescriptionInterface> desc,
RTCError* error_out) {
auto observer = rtc::make_ref_counted<FakeSetRemoteDescriptionObserver>();
pc()->SetRemoteDescription(std::move(desc), observer);
EXPECT_EQ_WAIT(true, observer->called(), kDefaultTimeout);
bool ok = observer->error().ok();
if (error_out)
*error_out = std::move(observer->error());
return ok;
}
bool PeerConnectionWrapper::SetSdp(
rtc::FunctionView<void(SetSessionDescriptionObserver*)> fn,
std::string* error_out) {
auto observer = rtc::make_ref_counted<MockSetSessionDescriptionObserver>();
fn(observer.get());
EXPECT_EQ_WAIT(true, observer->called(), kDefaultTimeout);
if (error_out && !observer->result()) {
*error_out = observer->error();
}
return observer->result();
}
bool PeerConnectionWrapper::ExchangeOfferAnswerWith(
PeerConnectionWrapper* answerer) {
return ExchangeOfferAnswerWith(answerer, RTCOfferAnswerOptions(),
RTCOfferAnswerOptions());
}
bool PeerConnectionWrapper::ExchangeOfferAnswerWith(
PeerConnectionWrapper* answerer,
const PeerConnectionInterface::RTCOfferAnswerOptions& offer_options,
const PeerConnectionInterface::RTCOfferAnswerOptions& answer_options) {
RTC_DCHECK(answerer);
if (answerer == this) {
RTC_LOG(LS_ERROR) << "Cannot exchange offer/answer with ourself!";
return false;
}
auto offer = CreateOffer(offer_options);
EXPECT_TRUE(offer);
if (!offer) {
return false;
}
bool set_local_offer =
SetLocalDescription(CloneSessionDescription(offer.get()));
EXPECT_TRUE(set_local_offer);
if (!set_local_offer) {
return false;
}
bool set_remote_offer = answerer->SetRemoteDescription(std::move(offer));
EXPECT_TRUE(set_remote_offer);
if (!set_remote_offer) {
return false;
}
auto answer = answerer->CreateAnswer(answer_options);
EXPECT_TRUE(answer);
if (!answer) {
return false;
}
bool set_local_answer =
answerer->SetLocalDescription(CloneSessionDescription(answer.get()));
EXPECT_TRUE(set_local_answer);
if (!set_local_answer) {
return false;
}
bool set_remote_answer = SetRemoteDescription(std::move(answer));
EXPECT_TRUE(set_remote_answer);
return set_remote_answer;
}
rtc::scoped_refptr<RtpTransceiverInterface>
PeerConnectionWrapper::AddTransceiver(cricket::MediaType media_type) {
RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> result =
pc()->AddTransceiver(media_type);
EXPECT_EQ(RTCErrorType::NONE, result.error().type());
return result.MoveValue();
}
rtc::scoped_refptr<RtpTransceiverInterface>
PeerConnectionWrapper::AddTransceiver(cricket::MediaType media_type,
const RtpTransceiverInit& init) {
RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> result =
pc()->AddTransceiver(media_type, init);
EXPECT_EQ(RTCErrorType::NONE, result.error().type());
return result.MoveValue();
}
rtc::scoped_refptr<RtpTransceiverInterface>
PeerConnectionWrapper::AddTransceiver(
rtc::scoped_refptr<MediaStreamTrackInterface> track) {
RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> result =
pc()->AddTransceiver(track);
EXPECT_EQ(RTCErrorType::NONE, result.error().type());
return result.MoveValue();
}
rtc::scoped_refptr<RtpTransceiverInterface>
PeerConnectionWrapper::AddTransceiver(
rtc::scoped_refptr<MediaStreamTrackInterface> track,
const RtpTransceiverInit& init) {
RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> result =
pc()->AddTransceiver(track, init);
EXPECT_EQ(RTCErrorType::NONE, result.error().type());
return result.MoveValue();
}
rtc::scoped_refptr<AudioTrackInterface> PeerConnectionWrapper::CreateAudioTrack(
const std::string& label) {
return pc_factory()->CreateAudioTrack(label, nullptr);
}
rtc::scoped_refptr<VideoTrackInterface> PeerConnectionWrapper::CreateVideoTrack(
const std::string& label) {
return pc_factory()->CreateVideoTrack(FakeVideoTrackSource::Create(), label);
}
rtc::scoped_refptr<RtpSenderInterface> PeerConnectionWrapper::AddTrack(
rtc::scoped_refptr<MediaStreamTrackInterface> track,
const std::vector<std::string>& stream_ids) {
RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> result =
pc()->AddTrack(track, stream_ids);
EXPECT_EQ(RTCErrorType::NONE, result.error().type());
return result.MoveValue();
}
rtc::scoped_refptr<RtpSenderInterface> PeerConnectionWrapper::AddTrack(
rtc::scoped_refptr<MediaStreamTrackInterface> track,
const std::vector<std::string>& stream_ids,
const std::vector<RtpEncodingParameters>& init_send_encodings) {
RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> result =
pc()->AddTrack(track, stream_ids, init_send_encodings);
EXPECT_EQ(RTCErrorType::NONE, result.error().type());
return result.MoveValue();
}
rtc::scoped_refptr<RtpSenderInterface> PeerConnectionWrapper::AddAudioTrack(
const std::string& track_label,
const std::vector<std::string>& stream_ids) {
return AddTrack(CreateAudioTrack(track_label), stream_ids);
}
rtc::scoped_refptr<RtpSenderInterface> PeerConnectionWrapper::AddVideoTrack(
const std::string& track_label,
const std::vector<std::string>& stream_ids) {
return AddTrack(CreateVideoTrack(track_label), stream_ids);
}
rtc::scoped_refptr<DataChannelInterface>
PeerConnectionWrapper::CreateDataChannel(
const std::string& label,
const absl::optional<DataChannelInit>& config) {
const DataChannelInit* config_ptr = config.has_value() ? &(*config) : nullptr;
auto result = pc()->CreateDataChannelOrError(label, config_ptr);
if (!result.ok()) {
RTC_LOG(LS_ERROR) << "CreateDataChannel failed: "
<< ToString(result.error().type()) << " "
<< result.error().message();
return nullptr;
}
return result.MoveValue();
}
PeerConnectionInterface::SignalingState
PeerConnectionWrapper::signaling_state() {
return pc()->signaling_state();
}
bool PeerConnectionWrapper::IsIceGatheringDone() {
return observer()->ice_gathering_complete_;
}
bool PeerConnectionWrapper::IsIceConnected() {
return observer()->ice_connected_;
}
rtc::scoped_refptr<const RTCStatsReport> PeerConnectionWrapper::GetStats() {
auto callback = rtc::make_ref_counted<MockRTCStatsCollectorCallback>();
pc()->GetStats(callback.get());
EXPECT_TRUE_WAIT(callback->called(), kDefaultTimeout);
return callback->report();
}
} // namespace webrtc

Some files were not shown because too many files have changed in this diff Show more