summaryrefslogtreecommitdiffstats
path: root/media/webrtc/signaling
diff options
context:
space:
mode:
Diffstat (limited to 'media/webrtc/signaling')
-rw-r--r--media/webrtc/signaling/gtest/Canonicals.h153
-rw-r--r--media/webrtc/signaling/gtest/MockCall.cpp53
-rw-r--r--media/webrtc/signaling/gtest/MockCall.h370
-rw-r--r--media/webrtc/signaling/gtest/MockConduit.h65
-rw-r--r--media/webrtc/signaling/gtest/audioconduit_unittests.cpp781
-rw-r--r--media/webrtc/signaling/gtest/jsep_session_unittest.cpp7609
-rw-r--r--media/webrtc/signaling/gtest/jsep_track_unittest.cpp1748
-rw-r--r--media/webrtc/signaling/gtest/mediapipeline_unittest.cpp697
-rw-r--r--media/webrtc/signaling/gtest/moz.build54
-rw-r--r--media/webrtc/signaling/gtest/sdp_unittests.cpp5915
-rw-r--r--media/webrtc/signaling/gtest/videoconduit_unittests.cpp2336
11 files changed, 19781 insertions, 0 deletions
diff --git a/media/webrtc/signaling/gtest/Canonicals.h b/media/webrtc/signaling/gtest/Canonicals.h
new file mode 100644
index 0000000000..2d865a5ef2
--- /dev/null
+++ b/media/webrtc/signaling/gtest/Canonicals.h
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MEDIA_WEBRTC_SIGNALING_GTEST_CANONICALS_H_
+#define MEDIA_WEBRTC_SIGNALING_GTEST_CANONICALS_H_
+
+#include "MediaConduitControl.h"
+#include "MediaPipeline.h"
+#include "WaitFor.h"
+
+namespace mozilla {
+
+#define INIT_CANONICAL(name, val) \
+ name(AbstractThread::MainThread(), val, "ConcreteCanonicals::" #name)
+class ConcreteCanonicals {
+ public:
+ ConcreteCanonicals()
+ : INIT_CANONICAL(mReceiving, false),
+ INIT_CANONICAL(mTransmitting, false),
+ INIT_CANONICAL(mLocalSsrcs, Ssrcs()),
+ INIT_CANONICAL(mLocalVideoRtxSsrcs, Ssrcs()),
+ INIT_CANONICAL(mLocalCname, std::string()),
+ INIT_CANONICAL(mMid, std::string()),
+ INIT_CANONICAL(mSyncGroup, std::string()),
+ INIT_CANONICAL(mLocalSendRtpExtensions, RtpExtList()),
+ INIT_CANONICAL(mLocalRecvRtpExtensions, RtpExtList()),
+ INIT_CANONICAL(mRemoteSsrc, 0),
+ INIT_CANONICAL(mRemoteVideoRtxSsrc, 0),
+ INIT_CANONICAL(mAudioRecvCodecs, std::vector<AudioCodecConfig>()),
+ INIT_CANONICAL(mAudioSendCodec, Nothing()),
+ INIT_CANONICAL(mVideoRecvCodecs, std::vector<VideoCodecConfig>()),
+ INIT_CANONICAL(mVideoSendCodec, Nothing()),
+ INIT_CANONICAL(mVideoRecvRtpRtcpConfig, Nothing()),
+ INIT_CANONICAL(mVideoSendRtpRtcpConfig, Nothing()),
+ INIT_CANONICAL(mVideoCodecMode,
+ webrtc::VideoCodecMode::kRealtimeVideo) {}
+
+ Canonical<bool> mReceiving;
+ Canonical<bool> mTransmitting;
+ Canonical<Ssrcs> mLocalSsrcs;
+ Canonical<Ssrcs> mLocalVideoRtxSsrcs;
+ Canonical<std::string> mLocalCname;
+ Canonical<std::string> mMid;
+ Canonical<std::string> mSyncGroup;
+ Canonical<RtpExtList> mLocalSendRtpExtensions;
+ Canonical<RtpExtList> mLocalRecvRtpExtensions;
+ Canonical<Ssrc> mRemoteSsrc;
+ Canonical<Ssrc> mRemoteVideoRtxSsrc;
+
+ Canonical<std::vector<AudioCodecConfig>> mAudioRecvCodecs;
+ Canonical<Maybe<AudioCodecConfig>> mAudioSendCodec;
+ MediaEventProducer<DtmfEvent> mDtmfEvent;
+
+ Canonical<std::vector<VideoCodecConfig>> mVideoRecvCodecs;
+ Canonical<Maybe<VideoCodecConfig>> mVideoSendCodec;
+ Canonical<Maybe<RtpRtcpConfig>> mVideoRecvRtpRtcpConfig;
+ Canonical<Maybe<RtpRtcpConfig>> mVideoSendRtpRtcpConfig;
+ Canonical<webrtc::VideoCodecMode> mVideoCodecMode;
+};
+#undef INIT_CANONICAL
+
+class ConcreteControl : public AudioConduitControlInterface,
+ public VideoConduitControlInterface,
+ public MediaPipelineReceiveControlInterface,
+ public MediaPipelineTransmitControlInterface,
+ private ConcreteCanonicals {
+ private:
+ RefPtr<nsISerialEventTarget> mTarget;
+
+ public:
+ explicit ConcreteControl(RefPtr<nsISerialEventTarget> aTarget)
+ : mTarget(std::move(aTarget)) {}
+
+ template <typename Function>
+ void Update(Function aFunction) {
+ mTarget->Dispatch(NS_NewRunnableFunction(
+ __func__, [&] { aFunction(*static_cast<ConcreteCanonicals*>(this)); }));
+ WaitForMirrors(mTarget);
+ }
+
+ // MediaConduitControlInterface
+ // -- MediaPipelineReceiveControlInterface
+ AbstractCanonical<bool>* CanonicalReceiving() override { return &mReceiving; }
+ // -- MediaPipelineTransmitControlInterface
+ AbstractCanonical<bool>* CanonicalTransmitting() override {
+ return &mTransmitting;
+ }
+ AbstractCanonical<Ssrcs>* CanonicalLocalSsrcs() override {
+ return &mLocalSsrcs;
+ }
+ AbstractCanonical<Ssrcs>* CanonicalLocalVideoRtxSsrcs() override {
+ return &mLocalVideoRtxSsrcs;
+ }
+ AbstractCanonical<std::string>* CanonicalLocalCname() override {
+ return &mLocalCname;
+ }
+ AbstractCanonical<std::string>* CanonicalMid() override { return &mMid; }
+ AbstractCanonical<Ssrc>* CanonicalRemoteSsrc() override {
+ return &mRemoteSsrc;
+ }
+ AbstractCanonical<Ssrc>* CanonicalRemoteVideoRtxSsrc() override {
+ return &mRemoteVideoRtxSsrc;
+ }
+ AbstractCanonical<std::string>* CanonicalSyncGroup() override {
+ return &mSyncGroup;
+ }
+ AbstractCanonical<RtpExtList>* CanonicalLocalRecvRtpExtensions() override {
+ return &mLocalRecvRtpExtensions;
+ }
+ AbstractCanonical<RtpExtList>* CanonicalLocalSendRtpExtensions() override {
+ return &mLocalSendRtpExtensions;
+ }
+
+ // AudioConduitControlInterface
+ AbstractCanonical<Maybe<AudioCodecConfig>>* CanonicalAudioSendCodec()
+ override {
+ return &mAudioSendCodec;
+ }
+ AbstractCanonical<std::vector<AudioCodecConfig>>* CanonicalAudioRecvCodecs()
+ override {
+ return &mAudioRecvCodecs;
+ }
+ MediaEventSource<DtmfEvent>& OnDtmfEvent() override { return mDtmfEvent; }
+
+ // VideoConduitControlInterface
+ AbstractCanonical<Maybe<VideoCodecConfig>>* CanonicalVideoSendCodec()
+ override {
+ return &mVideoSendCodec;
+ }
+ AbstractCanonical<Maybe<RtpRtcpConfig>>* CanonicalVideoSendRtpRtcpConfig()
+ override {
+ return &mVideoSendRtpRtcpConfig;
+ }
+ AbstractCanonical<std::vector<VideoCodecConfig>>* CanonicalVideoRecvCodecs()
+ override {
+ return &mVideoRecvCodecs;
+ }
+ AbstractCanonical<Maybe<RtpRtcpConfig>>* CanonicalVideoRecvRtpRtcpConfig()
+ override {
+ return &mVideoRecvRtpRtcpConfig;
+ }
+ AbstractCanonical<webrtc::VideoCodecMode>* CanonicalVideoCodecMode()
+ override {
+ return &mVideoCodecMode;
+ }
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/media/webrtc/signaling/gtest/MockCall.cpp b/media/webrtc/signaling/gtest/MockCall.cpp
new file mode 100644
index 0000000000..8f70565a8f
--- /dev/null
+++ b/media/webrtc/signaling/gtest/MockCall.cpp
@@ -0,0 +1,53 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MockCall.h"
+
+namespace test {
+
+const webrtc::AudioSendStream::Config& MockAudioSendStream::GetConfig() const {
+ return *mCallWrapper->GetMockCall()->mAudioSendConfig;
+}
+
+void MockAudioSendStream::Reconfigure(const Config& config,
+ webrtc::SetParametersCallback callback) {
+ mCallWrapper->GetMockCall()->mAudioSendConfig = mozilla::Some(config);
+}
+
+void MockAudioReceiveStream::SetDecoderMap(
+ std::map<int, webrtc::SdpAudioFormat> decoder_map) {
+ MOZ_ASSERT(mCallWrapper->GetMockCall()->mAudioReceiveConfig.isSome());
+ mCallWrapper->GetMockCall()->mAudioReceiveConfig->decoder_map =
+ std::move(decoder_map);
+}
+
+void MockAudioReceiveStream::SetRtpExtensions(
+ std::vector<webrtc::RtpExtension> extensions) {
+ MOZ_ASSERT(mCallWrapper->GetMockCall()->mAudioReceiveConfig.isSome());
+ mCallWrapper->GetMockCall()->mAudioReceiveConfig->rtp.extensions =
+ std::move(extensions);
+}
+
+webrtc::RtpHeaderExtensionMap MockAudioReceiveStream::GetRtpExtensionMap()
+ const {
+ return webrtc::RtpHeaderExtensionMap();
+}
+
+void MockVideoSendStream::ReconfigureVideoEncoder(
+ webrtc::VideoEncoderConfig config) {
+ mCallWrapper->GetMockCall()->mVideoSendEncoderConfig =
+ mozilla::Some(config.Copy());
+}
+
+void MockVideoSendStream::ReconfigureVideoEncoder(
+ webrtc::VideoEncoderConfig config, webrtc::SetParametersCallback callback) {
+ ReconfigureVideoEncoder(std::move(config));
+}
+
+webrtc::RtpHeaderExtensionMap MockVideoReceiveStream::GetRtpExtensionMap()
+ const {
+ return webrtc::RtpHeaderExtensionMap();
+}
+
+} // namespace test
diff --git a/media/webrtc/signaling/gtest/MockCall.h b/media/webrtc/signaling/gtest/MockCall.h
new file mode 100644
index 0000000000..09b7779150
--- /dev/null
+++ b/media/webrtc/signaling/gtest/MockCall.h
@@ -0,0 +1,370 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOCK_CALL_H_
+#define MOCK_CALL_H_
+
+#include "gmock/gmock.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/media/MediaUtils.h"
+#include "WebrtcCallWrapper.h"
+#include "PeerConnectionCtx.h"
+
+// libwebrtc
+#include "api/call/audio_sink.h"
+#include "call/call.h"
+
+namespace test {
+class MockCallWrapper;
+
+class MockAudioSendStream : public webrtc::AudioSendStream {
+ public:
+ explicit MockAudioSendStream(RefPtr<MockCallWrapper> aCallWrapper)
+ : mCallWrapper(std::move(aCallWrapper)) {}
+
+ const webrtc::AudioSendStream::Config& GetConfig() const override;
+
+ void Reconfigure(const Config& config,
+ webrtc::SetParametersCallback callback) override;
+
+ void Start() override {}
+
+ void Stop() override {}
+
+ void SendAudioData(std::unique_ptr<webrtc::AudioFrame> audio_frame) override {
+ }
+
+ bool SendTelephoneEvent(int payload_type, int payload_frequency, int event,
+ int duration_ms) override {
+ return true;
+ }
+
+ void SetMuted(bool muted) override {}
+
+ Stats GetStats() const override { return mStats; }
+
+ Stats GetStats(bool has_remote_tracks) const override { return mStats; }
+
+ virtual ~MockAudioSendStream() {}
+
+ const RefPtr<MockCallWrapper> mCallWrapper;
+ webrtc::AudioSendStream::Stats mStats;
+};
+
+class MockAudioReceiveStream : public webrtc::AudioReceiveStreamInterface {
+ public:
+ explicit MockAudioReceiveStream(RefPtr<MockCallWrapper> aCallWrapper)
+ : mCallWrapper(std::move(aCallWrapper)) {}
+
+ void Start() override {}
+
+ void Stop() override {}
+
+ bool IsRunning() const override { return true; }
+
+ Stats GetStats(bool get_and_clear_legacy_stats) const override {
+ return mStats;
+ }
+
+ void SetSink(webrtc::AudioSinkInterface* sink) override {}
+
+ void SetGain(float gain) override {}
+
+ std::vector<webrtc::RtpSource> GetSources() const override {
+ return mRtpSources;
+ }
+
+ virtual void SetDepacketizerToDecoderFrameTransformer(
+ rtc::scoped_refptr<webrtc::FrameTransformerInterface> frame_transformer)
+ override {
+ // Unimplemented after webrtc.org e2561e17e2 removed the Reconfigure
+ // method.
+ MOZ_ASSERT(false);
+ }
+ virtual void SetDecoderMap(
+ std::map<int, webrtc::SdpAudioFormat> decoder_map) override;
+ virtual void SetNackHistory(int history_ms) override {
+ // Unimplemented after webrtc.org e2561e17e2 removed the Reconfigure
+ // method.
+ MOZ_ASSERT(false);
+ }
+ virtual void SetNonSenderRttMeasurement(bool enabled) override {}
+ void SetFrameDecryptor(rtc::scoped_refptr<webrtc::FrameDecryptorInterface>
+ frame_decryptor) override {}
+ void SetRtpExtensions(std::vector<webrtc::RtpExtension> extensions) override;
+ webrtc::RtpHeaderExtensionMap GetRtpExtensionMap() const override;
+ bool SetBaseMinimumPlayoutDelayMs(int delay_ms) override { return false; }
+ int GetBaseMinimumPlayoutDelayMs() const override { return 0; }
+ uint32_t remote_ssrc() const override { return 0; }
+
+ virtual ~MockAudioReceiveStream() {}
+
+ const RefPtr<MockCallWrapper> mCallWrapper;
+ webrtc::AudioReceiveStreamInterface::Stats mStats;
+ std::vector<webrtc::RtpSource> mRtpSources;
+};
+
+class MockVideoSendStream : public webrtc::VideoSendStream {
+ public:
+ explicit MockVideoSendStream(RefPtr<MockCallWrapper> aCallWrapper)
+ : mCallWrapper(std::move(aCallWrapper)) {}
+
+ void Start() override {}
+
+ void Stop() override {}
+
+ bool started() override { return false; }
+
+ void SetSource(
+ rtc::VideoSourceInterface<webrtc::VideoFrame>* source,
+ const webrtc::DegradationPreference& degradation_preference) override {}
+
+ void ReconfigureVideoEncoder(webrtc::VideoEncoderConfig config) override;
+
+ void ReconfigureVideoEncoder(webrtc::VideoEncoderConfig config,
+ webrtc::SetParametersCallback callback) override;
+
+ Stats GetStats() override { return mStats; }
+
+ void StartPerRtpStream(const std::vector<bool> active_layers) override {}
+
+ void AddAdaptationResource(
+ rtc::scoped_refptr<webrtc::Resource> resource) override {}
+
+ std::vector<rtc::scoped_refptr<webrtc::Resource>> GetAdaptationResources()
+ override {
+ return std::vector<rtc::scoped_refptr<webrtc::Resource>>();
+ }
+
+ void GenerateKeyFrame(const std::vector<std::string>& rids) override {}
+
+ virtual ~MockVideoSendStream() {}
+
+ const RefPtr<MockCallWrapper> mCallWrapper;
+ webrtc::VideoSendStream::Stats mStats;
+};
+
+class MockVideoReceiveStream : public webrtc::VideoReceiveStreamInterface {
+ public:
+ explicit MockVideoReceiveStream(RefPtr<MockCallWrapper> aCallWrapper)
+ : mCallWrapper(std::move(aCallWrapper)) {}
+
+ void Start() override {}
+
+ void Stop() override {}
+
+ Stats GetStats() const override { return mStats; }
+
+ std::vector<webrtc::RtpSource> GetSources() const override {
+ return std::vector<webrtc::RtpSource>();
+ }
+
+ bool SetBaseMinimumPlayoutDelayMs(int delay_ms) override { return false; }
+
+ int GetBaseMinimumPlayoutDelayMs() const override { return 0; }
+
+ void SetFrameDecryptor(rtc::scoped_refptr<webrtc::FrameDecryptorInterface>
+ frame_decryptor) override {}
+
+ void SetDepacketizerToDecoderFrameTransformer(
+ rtc::scoped_refptr<webrtc::FrameTransformerInterface> frame_transformer)
+ override {}
+
+ RecordingState SetAndGetRecordingState(RecordingState state,
+ bool generate_key_frame) override {
+ return {};
+ }
+
+ void GenerateKeyFrame() override {}
+
+ void SetRtcpMode(webrtc::RtcpMode mode) override {}
+
+ void SetFlexFecProtection(
+ webrtc::RtpPacketSinkInterface* flexfec_sink) override {}
+
+ void SetLossNotificationEnabled(bool enabled) override {}
+
+ void SetNackHistory(webrtc::TimeDelta history) override {}
+
+ void SetProtectionPayloadTypes(int red_payload_type,
+ int ulpfec_payload_type) override {}
+
+ void SetRtcpXr(Config::Rtp::RtcpXr rtcp_xr) override {}
+
+ virtual void SetAssociatedPayloadTypes(
+ std::map<int, int> associated_payload_types) override {}
+
+ void SetRtpExtensions(std::vector<webrtc::RtpExtension> extensions) override {
+ }
+ webrtc::RtpHeaderExtensionMap GetRtpExtensionMap() const override;
+
+ virtual ~MockVideoReceiveStream() {}
+
+ const RefPtr<MockCallWrapper> mCallWrapper;
+ webrtc::VideoReceiveStreamInterface::Stats mStats;
+};
+
+class MockCall : public webrtc::Call {
+ public:
+ explicit MockCall(RefPtr<MockCallWrapper> aCallWrapper)
+ : mCallWrapper(std::move(aCallWrapper)) {}
+
+ webrtc::AudioSendStream* CreateAudioSendStream(
+ const webrtc::AudioSendStream::Config& config) override {
+ MOZ_RELEASE_ASSERT(!mAudioSendConfig);
+ mAudioSendConfig = mozilla::Some(config);
+ return new MockAudioSendStream(mCallWrapper);
+ }
+
+ void DestroyAudioSendStream(webrtc::AudioSendStream* send_stream) override {
+ mAudioSendConfig = mozilla::Nothing();
+ delete static_cast<MockAudioSendStream*>(send_stream);
+ }
+
+ webrtc::AudioReceiveStreamInterface* CreateAudioReceiveStream(
+ const webrtc::AudioReceiveStreamInterface::Config& config) override {
+ MOZ_RELEASE_ASSERT(!mAudioReceiveConfig);
+ mAudioReceiveConfig = mozilla::Some(config);
+ return new MockAudioReceiveStream(mCallWrapper);
+ }
+ void DestroyAudioReceiveStream(
+ webrtc::AudioReceiveStreamInterface* receive_stream) override {
+ mAudioReceiveConfig = mozilla::Nothing();
+ delete static_cast<MockAudioReceiveStream*>(receive_stream);
+ }
+
+ webrtc::VideoSendStream* CreateVideoSendStream(
+ webrtc::VideoSendStream::Config config,
+ webrtc::VideoEncoderConfig encoder_config) override {
+ MOZ_RELEASE_ASSERT(!mVideoSendConfig);
+ MOZ_RELEASE_ASSERT(!mVideoSendEncoderConfig);
+ mVideoSendConfig = mozilla::Some(std::move(config));
+ mVideoSendEncoderConfig = mozilla::Some(std::move(encoder_config));
+ return new MockVideoSendStream(mCallWrapper);
+ }
+
+ void DestroyVideoSendStream(webrtc::VideoSendStream* send_stream) override {
+ mVideoSendConfig = mozilla::Nothing();
+ mVideoSendEncoderConfig = mozilla::Nothing();
+ delete static_cast<MockVideoSendStream*>(send_stream);
+ }
+
+ webrtc::VideoReceiveStreamInterface* CreateVideoReceiveStream(
+ webrtc::VideoReceiveStreamInterface::Config configuration) override {
+ MOZ_RELEASE_ASSERT(!mVideoReceiveConfig);
+ mVideoReceiveConfig = mozilla::Some(std::move(configuration));
+ return new MockVideoReceiveStream(mCallWrapper);
+ }
+
+ void DestroyVideoReceiveStream(
+ webrtc::VideoReceiveStreamInterface* receive_stream) override {
+ mVideoReceiveConfig = mozilla::Nothing();
+ delete static_cast<MockVideoReceiveStream*>(receive_stream);
+ }
+
+ webrtc::FlexfecReceiveStream* CreateFlexfecReceiveStream(
+ const webrtc::FlexfecReceiveStream::Config config) override {
+ return nullptr;
+ }
+
+ void DestroyFlexfecReceiveStream(
+ webrtc::FlexfecReceiveStream* receive_stream) override {}
+
+ void AddAdaptationResource(
+ rtc::scoped_refptr<webrtc::Resource> resource) override {}
+
+ webrtc::PacketReceiver* Receiver() override { return nullptr; }
+
+ webrtc::RtpTransportControllerSendInterface* GetTransportControllerSend()
+ override {
+ return nullptr;
+ }
+
+ Stats GetStats() const override { return mStats; }
+
+ void SignalChannelNetworkState(webrtc::MediaType media,
+ webrtc::NetworkState state) override {}
+
+ void OnAudioTransportOverheadChanged(
+ int transport_overhead_per_packet) override {}
+
+ void OnLocalSsrcUpdated(webrtc::AudioReceiveStreamInterface& stream,
+ uint32_t local_ssrc) override {}
+ void OnLocalSsrcUpdated(webrtc::VideoReceiveStreamInterface& stream,
+ uint32_t local_ssrc) override {}
+ void OnLocalSsrcUpdated(webrtc::FlexfecReceiveStream& stream,
+ uint32_t local_ssrc) override {}
+
+ void OnUpdateSyncGroup(webrtc::AudioReceiveStreamInterface& stream,
+ absl::string_view sync_group) override {}
+
+ void OnSentPacket(const rtc::SentPacket& sent_packet) override {}
+
+ void SetClientBitratePreferences(
+ const webrtc::BitrateSettings& preferences) override {}
+
+ std::vector<webrtc::VideoStream> CreateEncoderStreams(int width, int height) {
+ return mVideoSendEncoderConfig->video_stream_factory->CreateEncoderStreams(
+ width, height, *mVideoSendEncoderConfig);
+ }
+
+ virtual const webrtc::WebRtcKeyValueConfig& trials() const override {
+ return mUnusedConfig;
+ }
+
+ virtual webrtc::TaskQueueBase* network_thread() const override {
+ return nullptr;
+ }
+
+ virtual webrtc::TaskQueueBase* worker_thread() const override {
+ return nullptr;
+ }
+
+ virtual ~MockCall(){};
+
+ const RefPtr<MockCallWrapper> mCallWrapper;
+ mozilla::Maybe<webrtc::AudioReceiveStreamInterface::Config>
+ mAudioReceiveConfig;
+ mozilla::Maybe<webrtc::AudioSendStream::Config> mAudioSendConfig;
+ mozilla::Maybe<webrtc::VideoReceiveStreamInterface::Config>
+ mVideoReceiveConfig;
+ mozilla::Maybe<webrtc::VideoSendStream::Config> mVideoSendConfig;
+ mozilla::Maybe<webrtc::VideoEncoderConfig> mVideoSendEncoderConfig;
+ webrtc::Call::Stats mStats;
+ webrtc::NoTrialsConfig mUnusedConfig;
+};
+
+class MockCallWrapper : public mozilla::WebrtcCallWrapper {
+ public:
+ MockCallWrapper(
+ RefPtr<mozilla::SharedWebrtcState> aSharedState,
+ mozilla::UniquePtr<webrtc::VideoBitrateAllocatorFactory>
+ aVideoBitrateAllocatorFactory,
+ mozilla::UniquePtr<webrtc::RtcEventLog> aEventLog,
+ mozilla::UniquePtr<webrtc::TaskQueueFactory> aTaskQueueFactory,
+ const mozilla::dom::RTCStatsTimestampMaker& aTimestampMaker,
+ mozilla::UniquePtr<mozilla::media::ShutdownBlockingTicket>
+ aShutdownTicket)
+ : mozilla::WebrtcCallWrapper(
+ std::move(aSharedState), std::move(aVideoBitrateAllocatorFactory),
+ std::move(aEventLog), std::move(aTaskQueueFactory), aTimestampMaker,
+ std::move(aShutdownTicket)) {}
+
+ static RefPtr<MockCallWrapper> Create() {
+ auto state = mozilla::MakeRefPtr<mozilla::SharedWebrtcState>(
+ mozilla::AbstractThread::GetCurrent(), webrtc::AudioState::Config(),
+ nullptr, nullptr);
+ auto wrapper = mozilla::MakeRefPtr<MockCallWrapper>(
+ state, nullptr, nullptr, nullptr,
+ mozilla::dom::RTCStatsTimestampMaker::Create(), nullptr);
+ wrapper->SetCall(mozilla::WrapUnique(new MockCall(wrapper)));
+ return wrapper;
+ }
+
+ MockCall* GetMockCall() { return static_cast<MockCall*>(Call()); }
+};
+
+} // namespace test
+#endif
diff --git a/media/webrtc/signaling/gtest/MockConduit.h b/media/webrtc/signaling/gtest/MockConduit.h
new file mode 100644
index 0000000000..d0c43f7c45
--- /dev/null
+++ b/media/webrtc/signaling/gtest/MockConduit.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MEDIA_WEBRTC_SIGNALING_GTEST_MOCKCONDUIT_H_
+#define MEDIA_WEBRTC_SIGNALING_GTEST_MOCKCONDUIT_H_
+
+#include "gmock/gmock.h"
+#include "MediaConduitInterface.h"
+
+namespace webrtc {
+std::ostream& operator<<(std::ostream& aStream,
+ const webrtc::Call::Stats& aObj) {
+ aStream << aObj.ToString(0);
+ return aStream;
+}
+} // namespace webrtc
+
+namespace mozilla {
+class MockConduit : public MediaSessionConduit {
+ public:
+ MockConduit() = default;
+
+ MOCK_CONST_METHOD0(type, Type());
+ MOCK_CONST_METHOD0(ActiveSendPayloadType, Maybe<int>());
+ MOCK_CONST_METHOD0(ActiveRecvPayloadType, Maybe<int>());
+ MOCK_METHOD1(SetTransportActive, void(bool));
+ MOCK_METHOD0(SenderRtpSendEvent, MediaEventSourceExc<MediaPacket>&());
+ MOCK_METHOD0(SenderRtcpSendEvent, MediaEventSourceExc<MediaPacket>&());
+ MOCK_METHOD0(ReceiverRtcpSendEvent, MediaEventSourceExc<MediaPacket>&());
+ MOCK_METHOD1(
+ ConnectReceiverRtpEvent,
+ void(MediaEventSourceExc<webrtc::RtpPacketReceived, webrtc::RTPHeader>&));
+ MOCK_METHOD1(ConnectReceiverRtcpEvent,
+ void(MediaEventSourceExc<MediaPacket>&));
+ MOCK_METHOD1(ConnectSenderRtcpEvent, void(MediaEventSourceExc<MediaPacket>&));
+ MOCK_CONST_METHOD0(LastRtcpReceived, Maybe<DOMHighResTimeStamp>());
+ MOCK_CONST_METHOD1(RtpSendBaseSeqFor, Maybe<uint16_t>(uint32_t));
+ MOCK_CONST_METHOD0(GetNow, DOMHighResTimeStamp());
+ MOCK_CONST_METHOD0(GetTimestampMaker, dom::RTCStatsTimestampMaker&());
+ MOCK_CONST_METHOD0(GetLocalSSRCs, Ssrcs());
+ MOCK_CONST_METHOD0(GetRemoteSSRC, Maybe<Ssrc>());
+ MOCK_METHOD1(UnsetRemoteSSRC, void(Ssrc));
+ MOCK_METHOD0(DisableSsrcChanges, void());
+ MOCK_CONST_METHOD1(HasCodecPluginID, bool(uint64_t));
+ MOCK_METHOD0(RtcpByeEvent, MediaEventSource<void>&());
+ MOCK_METHOD0(RtcpTimeoutEvent, MediaEventSource<void>&());
+ MOCK_METHOD0(RtpPacketEvent, MediaEventSource<void>&());
+ MOCK_METHOD3(SendRtp,
+ bool(const uint8_t*, size_t, const webrtc::PacketOptions&));
+ MOCK_METHOD2(SendSenderRtcp, bool(const uint8_t*, size_t));
+ MOCK_METHOD2(SendReceiverRtcp, bool(const uint8_t*, size_t));
+ MOCK_METHOD2(DeliverPacket, void(rtc::CopyOnWriteBuffer, PacketType));
+ MOCK_METHOD0(Shutdown, RefPtr<GenericPromise>());
+ MOCK_METHOD0(AsAudioSessionConduit, Maybe<RefPtr<AudioSessionConduit>>());
+ MOCK_METHOD0(AsVideoSessionConduit, Maybe<RefPtr<VideoSessionConduit>>());
+ MOCK_CONST_METHOD0(GetCallStats, Maybe<webrtc::Call::Stats>());
+ MOCK_METHOD1(SetJitterBufferTarget, void(DOMHighResTimeStamp));
+ MOCK_CONST_METHOD0(GetUpstreamRtpSources, std::vector<webrtc::RtpSource>());
+};
+} // namespace mozilla
+
+#endif
diff --git a/media/webrtc/signaling/gtest/audioconduit_unittests.cpp b/media/webrtc/signaling/gtest/audioconduit_unittests.cpp
new file mode 100644
index 0000000000..12f35f344f
--- /dev/null
+++ b/media/webrtc/signaling/gtest/audioconduit_unittests.cpp
@@ -0,0 +1,781 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+
+#include "AudioConduit.h"
+#include "Canonicals.h"
+#include "WaitFor.h"
+
+#include "MockCall.h"
+
+using namespace mozilla;
+using namespace testing;
+using namespace webrtc;
+
+namespace test {
+
+class AudioConduitTest : public ::testing::Test {
+ public:
+ AudioConduitTest()
+ : mCallWrapper(MockCallWrapper::Create()),
+ mAudioConduit(MakeRefPtr<WebrtcAudioConduit>(
+ mCallWrapper, GetCurrentSerialEventTarget())),
+ mControl(GetCurrentSerialEventTarget()) {
+ mAudioConduit->InitControl(&mControl);
+ }
+
+ ~AudioConduitTest() override {
+ mozilla::Unused << WaitFor(mAudioConduit->Shutdown());
+ mCallWrapper->Destroy();
+ }
+
+ MockCall* Call() { return mCallWrapper->GetMockCall(); }
+
+ const RefPtr<MockCallWrapper> mCallWrapper;
+ const RefPtr<WebrtcAudioConduit> mAudioConduit;
+ ConcreteControl mControl;
+};
+
+TEST_F(AudioConduitTest, TestConfigureSendMediaCodec) {
+ mControl.Update([&](auto& aControl) {
+ // defaults
+ aControl.mAudioSendCodec =
+ Some(AudioCodecConfig(114, "opus", 48000, 2, false));
+ aControl.mTransmitting = true;
+ });
+
+ ASSERT_TRUE(Call()->mAudioSendConfig);
+ {
+ const webrtc::SdpAudioFormat& f =
+ Call()->mAudioSendConfig->send_codec_spec->format;
+ ASSERT_EQ(f.name, "opus");
+ ASSERT_EQ(f.clockrate_hz, 48000);
+ ASSERT_EQ(f.num_channels, 2UL);
+ ASSERT_NE(f.parameters.find("stereo"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("stereo"), "1");
+ ASSERT_EQ(f.parameters.find("maxplaybackrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("useinbandfec"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxaveragebitrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("usedtx"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("cbr"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("ptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("minptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxptime"), f.parameters.end());
+ }
+
+ mControl.Update([&](auto& aControl) {
+ // empty codec name
+ aControl.mAudioSendCodec = Some(AudioCodecConfig(114, "", 48000, 2, false));
+ });
+
+ ASSERT_TRUE(Call()->mAudioSendConfig);
+ {
+ // Invalid codec was ignored.
+ const webrtc::SdpAudioFormat& f =
+ Call()->mAudioSendConfig->send_codec_spec->format;
+ ASSERT_EQ(f.name, "opus");
+ }
+}
+
+TEST_F(AudioConduitTest, TestConfigureSendOpusMono) {
+ mControl.Update([&](auto& aControl) {
+ // opus mono
+ aControl.mAudioSendCodec =
+ Some(AudioCodecConfig(114, "opus", 48000, 1, false));
+ aControl.mTransmitting = true;
+ });
+
+ ASSERT_TRUE(Call()->mAudioSendConfig);
+ {
+ const webrtc::SdpAudioFormat& f =
+ Call()->mAudioSendConfig->send_codec_spec->format;
+ ASSERT_EQ(f.name, "opus");
+ ASSERT_EQ(f.clockrate_hz, 48000);
+ ASSERT_EQ(f.num_channels, 1UL);
+ ASSERT_EQ(f.parameters.find("stereo"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxplaybackrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("useinbandfec"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxaveragebitrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("usedtx"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("cbr"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("ptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("minptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxptime"), f.parameters.end());
+ }
+}
+
+TEST_F(AudioConduitTest, TestConfigureSendOpusFEC) {
+ mControl.Update([&](auto& aControl) {
+ // opus with inband Forward Error Correction
+ AudioCodecConfig codecConfig =
+ AudioCodecConfig(114, "opus", 48000, 2, true);
+ aControl.mAudioSendCodec = Some(codecConfig);
+ aControl.mTransmitting = true;
+ });
+
+ ASSERT_TRUE(Call()->mAudioSendConfig);
+ {
+ const webrtc::SdpAudioFormat& f =
+ Call()->mAudioSendConfig->send_codec_spec->format;
+ ASSERT_EQ(f.name, "opus");
+ ASSERT_EQ(f.clockrate_hz, 48000);
+ ASSERT_EQ(f.num_channels, 2UL);
+ ASSERT_NE(f.parameters.find("stereo"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("stereo"), "1");
+ ASSERT_NE(f.parameters.find("useinbandfec"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("useinbandfec"), "1");
+ ASSERT_EQ(f.parameters.find("maxplaybackrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxaveragebitrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("usedtx"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("cbr"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("ptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("minptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxptime"), f.parameters.end());
+ }
+}
+
+TEST_F(AudioConduitTest, TestConfigureSendOpusMaxPlaybackRate) {
+ mControl.Update([&](auto& aControl) {
+ AudioCodecConfig codecConfig =
+ AudioCodecConfig(114, "opus", 48000, 2, false);
+ codecConfig.mMaxPlaybackRate = 1234;
+ aControl.mAudioSendCodec = Some(codecConfig);
+ aControl.mTransmitting = true;
+ });
+
+ ASSERT_TRUE(Call()->mAudioSendConfig);
+ {
+ const webrtc::SdpAudioFormat& f =
+ Call()->mAudioSendConfig->send_codec_spec->format;
+ ASSERT_EQ(f.name, "opus");
+ ASSERT_EQ(f.clockrate_hz, 48000);
+ ASSERT_EQ(f.num_channels, 2UL);
+ ASSERT_NE(f.parameters.find("stereo"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("stereo"), "1");
+ ASSERT_EQ(f.parameters.find("useinbandfec"), f.parameters.end());
+ ASSERT_NE(f.parameters.find("maxplaybackrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("maxplaybackrate"), "1234");
+ ASSERT_EQ(f.parameters.find("maxaveragebitrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("usedtx"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("cbr"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("ptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("minptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxptime"), f.parameters.end());
+ }
+}
+
+TEST_F(AudioConduitTest, TestConfigureSendOpusMaxAverageBitrate) {
+ mControl.Update([&](auto& aControl) {
+ AudioCodecConfig codecConfig =
+ AudioCodecConfig(114, "opus", 48000, 2, false);
+ codecConfig.mMaxAverageBitrate = 12345;
+ aControl.mAudioSendCodec = Some(codecConfig);
+ aControl.mTransmitting = true;
+ });
+
+ ASSERT_TRUE(Call()->mAudioSendConfig);
+ {
+ const webrtc::SdpAudioFormat& f =
+ Call()->mAudioSendConfig->send_codec_spec->format;
+ ASSERT_EQ(f.name, "opus");
+ ASSERT_EQ(f.clockrate_hz, 48000);
+ ASSERT_EQ(f.num_channels, 2UL);
+ ASSERT_NE(f.parameters.find("stereo"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("stereo"), "1");
+ ASSERT_EQ(f.parameters.find("useinbandfec"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxplaybackrate"), f.parameters.end());
+ ASSERT_NE(f.parameters.find("maxaveragebitrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("maxaveragebitrate"), "12345");
+ ASSERT_EQ(f.parameters.find("usedtx"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("cbr"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("ptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("minptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxptime"), f.parameters.end());
+ }
+}
+
+TEST_F(AudioConduitTest, TestConfigureSendOpusDtx) {
+ mControl.Update([&](auto& aControl) {
+ AudioCodecConfig codecConfig =
+ AudioCodecConfig(114, "opus", 48000, 2, false);
+ codecConfig.mDTXEnabled = true;
+ aControl.mAudioSendCodec = Some(codecConfig);
+ aControl.mTransmitting = true;
+ });
+
+ ASSERT_TRUE(Call()->mAudioSendConfig);
+ {
+ const webrtc::SdpAudioFormat& f =
+ Call()->mAudioSendConfig->send_codec_spec->format;
+ ASSERT_EQ(f.name, "opus");
+ ASSERT_EQ(f.clockrate_hz, 48000);
+ ASSERT_EQ(f.num_channels, 2UL);
+ ASSERT_NE(f.parameters.find("stereo"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("stereo"), "1");
+ ASSERT_EQ(f.parameters.find("useinbandfec"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxplaybackrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxaveragebitrate"), f.parameters.end());
+ ASSERT_NE(f.parameters.find("usedtx"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("usedtx"), "1");
+ ASSERT_EQ(f.parameters.find("cbr"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("ptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("minptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxptime"), f.parameters.end());
+ }
+}
+
+TEST_F(AudioConduitTest, TestConfigureSendOpusCbr) {
+ mControl.Update([&](auto& aControl) {
+ AudioCodecConfig codecConfig =
+ AudioCodecConfig(114, "opus", 48000, 2, false);
+ codecConfig.mCbrEnabled = true;
+ aControl.mAudioSendCodec = Some(codecConfig);
+ aControl.mTransmitting = true;
+ });
+
+ ASSERT_TRUE(Call()->mAudioSendConfig);
+ {
+ const webrtc::SdpAudioFormat& f =
+ Call()->mAudioSendConfig->send_codec_spec->format;
+ ASSERT_EQ(f.name, "opus");
+ ASSERT_EQ(f.clockrate_hz, 48000);
+ ASSERT_EQ(f.num_channels, 2UL);
+ ASSERT_NE(f.parameters.find("stereo"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("stereo"), "1");
+ ASSERT_EQ(f.parameters.find("useinbandfec"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxplaybackrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxaveragebitrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("usedtx"), f.parameters.end());
+ ASSERT_NE(f.parameters.find("cbr"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("cbr"), "1");
+ ASSERT_EQ(f.parameters.find("ptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("minptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxptime"), f.parameters.end());
+ }
+}
+
+TEST_F(AudioConduitTest, TestConfigureSendOpusPtime) {
+ mControl.Update([&](auto& aControl) {
+ AudioCodecConfig codecConfig =
+ AudioCodecConfig(114, "opus", 48000, 2, false);
+ codecConfig.mFrameSizeMs = 100;
+ aControl.mAudioSendCodec = Some(codecConfig);
+ aControl.mTransmitting = true;
+ });
+
+ ASSERT_TRUE(Call()->mAudioSendConfig);
+ {
+ const webrtc::SdpAudioFormat& f =
+ Call()->mAudioSendConfig->send_codec_spec->format;
+ ASSERT_EQ(f.name, "opus");
+ ASSERT_EQ(f.clockrate_hz, 48000);
+ ASSERT_EQ(f.num_channels, 2UL);
+ ASSERT_NE(f.parameters.find("stereo"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("stereo"), "1");
+ ASSERT_EQ(f.parameters.find("useinbandfec"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxplaybackrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxaveragebitrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("usedtx"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("cbr"), f.parameters.end());
+ ASSERT_NE(f.parameters.find("ptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("ptime"), "100");
+ ASSERT_EQ(f.parameters.find("minptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxptime"), f.parameters.end());
+ }
+}
+
+TEST_F(AudioConduitTest, TestConfigureSendOpusMinPtime) {
+ mControl.Update([&](auto& aControl) {
+ AudioCodecConfig codecConfig =
+ AudioCodecConfig(114, "opus", 48000, 2, false);
+ codecConfig.mMinFrameSizeMs = 201;
+ aControl.mAudioSendCodec = Some(codecConfig);
+ aControl.mTransmitting = true;
+ });
+
+ ASSERT_TRUE(Call()->mAudioSendConfig);
+ {
+ const webrtc::SdpAudioFormat& f =
+ Call()->mAudioSendConfig->send_codec_spec->format;
+ ASSERT_EQ(f.name, "opus");
+ ASSERT_EQ(f.clockrate_hz, 48000);
+ ASSERT_EQ(f.num_channels, 2UL);
+ ASSERT_NE(f.parameters.find("stereo"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("stereo"), "1");
+ ASSERT_EQ(f.parameters.find("useinbandfec"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxplaybackrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxaveragebitrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("usedtx"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("cbr"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("ptime"), f.parameters.end());
+ ASSERT_NE(f.parameters.find("minptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("minptime"), "201");
+ ASSERT_EQ(f.parameters.find("maxptime"), f.parameters.end());
+ }
+}
+
+TEST_F(AudioConduitTest, TestConfigureSendOpusMaxPtime) {
+ mControl.Update([&](auto& aControl) {
+ AudioCodecConfig codecConfig =
+ AudioCodecConfig(114, "opus", 48000, 2, false);
+ codecConfig.mMaxFrameSizeMs = 321;
+ aControl.mAudioSendCodec = Some(codecConfig);
+ aControl.mTransmitting = true;
+ });
+
+ ASSERT_TRUE(Call()->mAudioSendConfig);
+ {
+ const webrtc::SdpAudioFormat& f =
+ Call()->mAudioSendConfig->send_codec_spec->format;
+ ASSERT_EQ(f.name, "opus");
+ ASSERT_EQ(f.clockrate_hz, 48000);
+ ASSERT_EQ(f.num_channels, 2UL);
+ ASSERT_NE(f.parameters.find("stereo"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("stereo"), "1");
+ ASSERT_EQ(f.parameters.find("useinbandfec"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxplaybackrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxaveragebitrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("usedtx"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("cbr"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("ptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("minptime"), f.parameters.end());
+ ASSERT_NE(f.parameters.find("maxptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("maxptime"), "321");
+ }
+}
+
+TEST_F(AudioConduitTest, TestConfigureSendOpusAllParams) {
+ mControl.Update([&](auto& aControl) {
+ AudioCodecConfig codecConfig =
+ AudioCodecConfig(114, "opus", 48000, 2, true);
+ codecConfig.mMaxPlaybackRate = 5432;
+ codecConfig.mMaxAverageBitrate = 54321;
+ codecConfig.mDTXEnabled = true;
+ codecConfig.mCbrEnabled = true;
+ codecConfig.mFrameSizeMs = 999;
+ codecConfig.mMinFrameSizeMs = 123;
+ codecConfig.mMaxFrameSizeMs = 789;
+ aControl.mAudioSendCodec = Some(codecConfig);
+ aControl.mTransmitting = true;
+ });
+
+ ASSERT_TRUE(Call()->mAudioSendConfig);
+ {
+ const webrtc::SdpAudioFormat& f =
+ Call()->mAudioSendConfig->send_codec_spec->format;
+ ASSERT_EQ(f.name, "opus");
+ ASSERT_EQ(f.clockrate_hz, 48000);
+ ASSERT_EQ(f.num_channels, 2UL);
+ ASSERT_NE(f.parameters.find("stereo"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("stereo"), "1");
+ ASSERT_NE(f.parameters.find("useinbandfec"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("useinbandfec"), "1");
+ ASSERT_NE(f.parameters.find("maxplaybackrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("maxplaybackrate"), "5432");
+ ASSERT_NE(f.parameters.find("maxaveragebitrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("maxaveragebitrate"), "54321");
+ ASSERT_NE(f.parameters.find("usedtx"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("usedtx"), "1");
+ ASSERT_NE(f.parameters.find("cbr"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("cbr"), "1");
+ ASSERT_NE(f.parameters.find("ptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("ptime"), "999");
+ ASSERT_NE(f.parameters.find("minptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("minptime"), "123");
+ ASSERT_NE(f.parameters.find("maxptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("maxptime"), "789");
+ }
+}
+
+TEST_F(AudioConduitTest, TestConfigureReceiveMediaCodecs) {
+ mControl.Update([&](auto& aControl) {
+ // just default opus stereo
+ std::vector<mozilla::AudioCodecConfig> codecs;
+ codecs.emplace_back(AudioCodecConfig(114, "opus", 48000, 2, false));
+ aControl.mAudioRecvCodecs = codecs;
+ aControl.mReceiving = true;
+ });
+ ASSERT_TRUE(Call()->mAudioReceiveConfig);
+ ASSERT_EQ(Call()->mAudioReceiveConfig->sync_group, "");
+ ASSERT_EQ(Call()->mAudioReceiveConfig->decoder_map.size(), 1U);
+ {
+ const webrtc::SdpAudioFormat& f =
+ Call()->mAudioReceiveConfig->decoder_map.at(114);
+ ASSERT_EQ(f.name, "opus");
+ ASSERT_EQ(f.clockrate_hz, 48000);
+ ASSERT_EQ(f.num_channels, 2UL);
+ ASSERT_EQ(f.parameters.at("stereo"), "1");
+ ASSERT_EQ(f.parameters.find("maxplaybackrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("useinbandfec"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxaveragebitrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("usedtx"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("cbr"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("ptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("minptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxptime"), f.parameters.end());
+ }
+
+ mControl.Update([&](auto& aControl) {
+ // multiple codecs
+ std::vector<mozilla::AudioCodecConfig> codecs;
+ codecs.emplace_back(AudioCodecConfig(9, "g722", 16000, 2, false));
+ codecs.emplace_back(AudioCodecConfig(114, "opus", 48000, 2, false));
+ aControl.mAudioRecvCodecs = codecs;
+ aControl.mReceiving = true;
+ });
+ ASSERT_TRUE(Call()->mAudioReceiveConfig);
+ ASSERT_EQ(Call()->mAudioReceiveConfig->sync_group, "");
+ ASSERT_EQ(Call()->mAudioReceiveConfig->decoder_map.size(), 2U);
+ {
+ const webrtc::SdpAudioFormat& f =
+ Call()->mAudioReceiveConfig->decoder_map.at(9);
+ ASSERT_EQ(f.name, "g722");
+ ASSERT_EQ(f.clockrate_hz, 16000);
+ ASSERT_EQ(f.num_channels, 2U);
+ ASSERT_EQ(f.parameters.size(), 0U);
+ }
+ {
+ const webrtc::SdpAudioFormat& f =
+ Call()->mAudioReceiveConfig->decoder_map.at(114);
+ ASSERT_EQ(f.name, "opus");
+ ASSERT_EQ(f.clockrate_hz, 48000);
+ ASSERT_EQ(f.num_channels, 2U);
+ ASSERT_EQ(f.parameters.at("stereo"), "1");
+ }
+
+ mControl.Update([&](auto& aControl) {
+ // no codecs
+ std::vector<mozilla::AudioCodecConfig> codecs;
+ aControl.mAudioRecvCodecs = codecs;
+ });
+ ASSERT_TRUE(Call()->mAudioReceiveConfig);
+ ASSERT_EQ(Call()->mAudioReceiveConfig->decoder_map.size(), 0U);
+
+ mControl.Update([&](auto& aControl) {
+ // invalid codec name
+ std::vector<mozilla::AudioCodecConfig> codecs;
+ codecs.emplace_back(AudioCodecConfig(114, "", 48000, 2, false));
+ aControl.mAudioRecvCodecs = codecs;
+ });
+ ASSERT_TRUE(Call()->mAudioReceiveConfig);
+ ASSERT_EQ(Call()->mAudioReceiveConfig->decoder_map.size(), 0U);
+
+ mControl.Update([&](auto& aControl) {
+ // invalid number of channels
+ std::vector<mozilla::AudioCodecConfig> codecs;
+ codecs.emplace_back(AudioCodecConfig(114, "opus", 48000, 42, false));
+ aControl.mAudioRecvCodecs = codecs;
+ });
+ ASSERT_TRUE(Call()->mAudioReceiveConfig);
+ ASSERT_EQ(Call()->mAudioReceiveConfig->decoder_map.size(), 0U);
+}
+
+TEST_F(AudioConduitTest, TestConfigureReceiveOpusMono) {
+ mControl.Update([&](auto& aControl) {
+ // opus mono
+ std::vector<mozilla::AudioCodecConfig> codecs;
+ codecs.emplace_back(AudioCodecConfig(114, "opus", 48000, 1, false));
+ aControl.mAudioRecvCodecs = codecs;
+ aControl.mReceiving = true;
+ });
+ ASSERT_TRUE(Call()->mAudioReceiveConfig);
+ ASSERT_EQ(Call()->mAudioReceiveConfig->sync_group, "");
+ ASSERT_EQ(Call()->mAudioReceiveConfig->decoder_map.size(), 1U);
+ {
+ const webrtc::SdpAudioFormat& f =
+ Call()->mAudioReceiveConfig->decoder_map.at(114);
+ ASSERT_EQ(f.name, "opus");
+ ASSERT_EQ(f.clockrate_hz, 48000);
+ ASSERT_EQ(f.num_channels, 1UL);
+ ASSERT_EQ(f.parameters.find("stereo"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxplaybackrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("useinbandfec"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxaveragebitrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("usedtx"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("cbr"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("ptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("minptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxptime"), f.parameters.end());
+ }
+}
+
+TEST_F(AudioConduitTest, TestConfigureReceiveOpusDtx) {
+ mControl.Update([&](auto& aControl) {
+ // opus mono
+ std::vector<mozilla::AudioCodecConfig> codecs;
+ codecs.emplace_back(AudioCodecConfig(114, "opus", 48000, 2, false));
+ codecs[0].mDTXEnabled = true;
+ aControl.mAudioRecvCodecs = codecs;
+ aControl.mReceiving = true;
+ });
+ ASSERT_TRUE(Call()->mAudioReceiveConfig);
+ ASSERT_EQ(Call()->mAudioReceiveConfig->sync_group, "");
+ ASSERT_EQ(Call()->mAudioReceiveConfig->decoder_map.size(), 1U);
+ {
+ const webrtc::SdpAudioFormat& f =
+ Call()->mAudioReceiveConfig->decoder_map.at(114);
+ ASSERT_EQ(f.name, "opus");
+ ASSERT_EQ(f.clockrate_hz, 48000);
+ ASSERT_EQ(f.num_channels, 2UL);
+ ASSERT_NE(f.parameters.find("stereo"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("stereo"), "1");
+ ASSERT_EQ(f.parameters.find("maxplaybackrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("useinbandfec"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxaveragebitrate"), f.parameters.end());
+ ASSERT_NE(f.parameters.find("usedtx"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("usedtx"), "1");
+ ASSERT_EQ(f.parameters.find("cbr"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("ptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("minptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxptime"), f.parameters.end());
+ }
+}
+
+TEST_F(AudioConduitTest, TestConfigureReceiveOpusFEC) {
+ mControl.Update([&](auto& aControl) {
+ // opus with inband Forward Error Correction
+ std::vector<mozilla::AudioCodecConfig> codecs;
+ codecs.emplace_back(AudioCodecConfig(114, "opus", 48000, 2, true));
+ aControl.mAudioRecvCodecs = codecs;
+ aControl.mReceiving = true;
+ });
+ ASSERT_TRUE(Call()->mAudioReceiveConfig);
+ ASSERT_EQ(Call()->mAudioReceiveConfig->sync_group, "");
+ ASSERT_EQ(Call()->mAudioReceiveConfig->decoder_map.size(), 1U);
+ {
+ const webrtc::SdpAudioFormat& f =
+ Call()->mAudioReceiveConfig->decoder_map.at(114);
+ ASSERT_EQ(f.name, "opus");
+ ASSERT_EQ(f.clockrate_hz, 48000);
+ ASSERT_EQ(f.num_channels, 2UL);
+ ASSERT_NE(f.parameters.find("stereo"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("stereo"), "1");
+ ASSERT_NE(f.parameters.find("useinbandfec"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("useinbandfec"), "1");
+ ASSERT_EQ(f.parameters.find("maxplaybackrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxaveragebitrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("usedtx"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("cbr"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("ptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("minptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxptime"), f.parameters.end());
+ }
+}
+
+TEST_F(AudioConduitTest, TestConfigureReceiveOpusMaxPlaybackRate) {
+ std::vector<mozilla::AudioCodecConfig> codecs;
+ codecs.emplace_back(AudioCodecConfig(114, "opus", 48000, 2, false));
+
+ mControl.Update([&](auto& aControl) {
+ codecs[0].mMaxPlaybackRate = 0;
+ aControl.mAudioRecvCodecs = codecs;
+ aControl.mReceiving = true;
+ });
+ ASSERT_TRUE(Call()->mAudioReceiveConfig);
+ ASSERT_EQ(Call()->mAudioReceiveConfig->decoder_map.size(), 1U);
+ {
+ const webrtc::SdpAudioFormat& f =
+ Call()->mAudioReceiveConfig->decoder_map.at(114);
+ ASSERT_EQ(f.name, "opus");
+ ASSERT_EQ(f.clockrate_hz, 48000);
+ ASSERT_EQ(f.num_channels, 2UL);
+ ASSERT_EQ(f.parameters.at("stereo"), "1");
+ ASSERT_EQ(f.parameters.find("useinbandfec"), f.parameters.end());
+ ASSERT_EQ(f.parameters.count("maxplaybackrate"), 0U);
+ ASSERT_EQ(f.parameters.find("maxaveragebitrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("usedtx"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("cbr"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("ptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("minptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxptime"), f.parameters.end());
+ }
+
+ mControl.Update([&](auto& aControl) {
+ codecs[0].mMaxPlaybackRate = 8000;
+ aControl.mAudioRecvCodecs = codecs;
+ });
+ ASSERT_TRUE(Call()->mAudioReceiveConfig);
+ ASSERT_EQ(Call()->mAudioReceiveConfig->decoder_map.size(), 1U);
+ {
+ const webrtc::SdpAudioFormat& f =
+ Call()->mAudioReceiveConfig->decoder_map.at(114);
+ ASSERT_EQ(f.name, "opus");
+ ASSERT_EQ(f.clockrate_hz, 48000);
+ ASSERT_EQ(f.num_channels, 2UL);
+ ASSERT_EQ(f.parameters.at("stereo"), "1");
+ ASSERT_EQ(f.parameters.at("maxplaybackrate"), "8000");
+ ASSERT_EQ(f.parameters.find("useinbandfec"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxaveragebitrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("usedtx"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("cbr"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("ptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("minptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxptime"), f.parameters.end());
+ }
+}
+
+TEST_F(AudioConduitTest, TestConfigureReceiveOpusMaxAverageBitrate) {
+ std::vector<mozilla::AudioCodecConfig> codecs;
+ codecs.emplace_back(AudioCodecConfig(114, "opus", 48000, 2, false));
+ mControl.Update([&](auto& aControl) {
+ codecs[0].mMaxAverageBitrate = 0;
+ aControl.mAudioRecvCodecs = codecs;
+ aControl.mReceiving = true;
+ });
+ ASSERT_TRUE(Call()->mAudioReceiveConfig);
+ ASSERT_EQ(Call()->mAudioReceiveConfig->decoder_map.size(), 1U);
+ {
+ const webrtc::SdpAudioFormat& f =
+ Call()->mAudioReceiveConfig->decoder_map.at(114);
+ ASSERT_EQ(f.name, "opus");
+ ASSERT_EQ(f.clockrate_hz, 48000);
+ ASSERT_EQ(f.num_channels, 2UL);
+ ASSERT_EQ(f.parameters.at("stereo"), "1");
+ ASSERT_EQ(f.parameters.find("useinbandfec"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxplaybackrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.count("maxaveragebitrate"), 0U);
+ ASSERT_EQ(f.parameters.find("usedtx"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("cbr"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("ptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("minptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxptime"), f.parameters.end());
+ }
+
+ mControl.Update([&](auto& aControl) {
+ codecs[0].mMaxAverageBitrate = 8000;
+ aControl.mAudioRecvCodecs = codecs;
+ });
+ ASSERT_TRUE(Call()->mAudioReceiveConfig);
+ ASSERT_EQ(Call()->mAudioReceiveConfig->decoder_map.size(), 1U);
+ {
+ const webrtc::SdpAudioFormat& f =
+ Call()->mAudioReceiveConfig->decoder_map.at(114);
+ ASSERT_EQ(f.name, "opus");
+ ASSERT_EQ(f.clockrate_hz, 48000);
+ ASSERT_EQ(f.num_channels, 2UL);
+ ASSERT_EQ(f.parameters.at("stereo"), "1");
+ ASSERT_EQ(f.parameters.find("useinbandfec"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxplaybackrate"), f.parameters.end());
+ ASSERT_EQ(f.parameters.at("maxaveragebitrate"), "8000");
+ ASSERT_EQ(f.parameters.find("usedtx"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("cbr"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("ptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("minptime"), f.parameters.end());
+ ASSERT_EQ(f.parameters.find("maxptime"), f.parameters.end());
+ }
+}
+
+TEST_F(AudioConduitTest, TestConfigureReceiveOpusAllParameters) {
+ std::vector<mozilla::AudioCodecConfig> codecs;
+ codecs.emplace_back(AudioCodecConfig(114, "opus", 48000, 2, true));
+
+ mControl.Update([&](auto& aControl) {
+ codecs[0].mMaxPlaybackRate = 8000;
+ codecs[0].mMaxAverageBitrate = 9000;
+ codecs[0].mDTXEnabled = true;
+ codecs[0].mCbrEnabled = true;
+ codecs[0].mFrameSizeMs = 10;
+ codecs[0].mMinFrameSizeMs = 20;
+ codecs[0].mMaxFrameSizeMs = 30;
+
+ aControl.mAudioRecvCodecs = codecs;
+ aControl.mReceiving = true;
+ });
+ ASSERT_TRUE(Call()->mAudioReceiveConfig);
+ ASSERT_EQ(Call()->mAudioReceiveConfig->decoder_map.size(), 1U);
+ {
+ const webrtc::SdpAudioFormat& f =
+ Call()->mAudioReceiveConfig->decoder_map.at(114);
+ ASSERT_EQ(f.name, "opus");
+ ASSERT_EQ(f.clockrate_hz, 48000);
+ ASSERT_EQ(f.num_channels, 2UL);
+ ASSERT_EQ(f.parameters.at("stereo"), "1");
+ ASSERT_EQ(f.parameters.at("useinbandfec"), "1");
+ ASSERT_EQ(f.parameters.at("maxplaybackrate"), "8000");
+ ASSERT_EQ(f.parameters.at("maxaveragebitrate"), "9000");
+ ASSERT_EQ(f.parameters.at("usedtx"), "1");
+ ASSERT_EQ(f.parameters.at("cbr"), "1");
+ ASSERT_EQ(f.parameters.at("ptime"), "10");
+ ASSERT_EQ(f.parameters.at("minptime"), "20");
+ ASSERT_EQ(f.parameters.at("maxptime"), "30");
+ }
+}
+
+TEST_F(AudioConduitTest, TestSetLocalRTPExtensions) {
+ // Empty extensions
+ mControl.Update([&](auto& aControl) {
+ RtpExtList extensions;
+ aControl.mLocalRecvRtpExtensions = extensions;
+ aControl.mReceiving = true;
+ aControl.mLocalSendRtpExtensions = extensions;
+ aControl.mTransmitting = true;
+ });
+ ASSERT_TRUE(Call()->mAudioReceiveConfig);
+ ASSERT_TRUE(Call()->mAudioReceiveConfig->rtp.extensions.empty());
+ ASSERT_TRUE(Call()->mAudioSendConfig);
+ ASSERT_TRUE(Call()->mAudioSendConfig->rtp.extensions.empty());
+
+ // Audio level
+ mControl.Update([&](auto& aControl) {
+ RtpExtList extensions;
+ webrtc::RtpExtension extension;
+ extension.uri = webrtc::RtpExtension::kAudioLevelUri;
+ extensions.emplace_back(extension);
+ aControl.mLocalRecvRtpExtensions = extensions;
+ aControl.mLocalSendRtpExtensions = extensions;
+ });
+ ASSERT_TRUE(Call()->mAudioReceiveConfig);
+ ASSERT_EQ(Call()->mAudioReceiveConfig->rtp.extensions.back().uri,
+ webrtc::RtpExtension::kAudioLevelUri);
+ ASSERT_TRUE(Call()->mAudioSendConfig);
+ ASSERT_EQ(Call()->mAudioSendConfig->rtp.extensions.back().uri,
+ webrtc::RtpExtension::kAudioLevelUri);
+
+ // Contributing sources audio level
+ mControl.Update([&](auto& aControl) {
+ // We do not support configuring sending csrc-audio-level. It will be
+ // ignored.
+ RtpExtList extensions;
+ webrtc::RtpExtension extension;
+ extension.uri = webrtc::RtpExtension::kCsrcAudioLevelsUri;
+ extensions.emplace_back(extension);
+ aControl.mLocalRecvRtpExtensions = extensions;
+ aControl.mLocalSendRtpExtensions = extensions;
+ });
+ ASSERT_TRUE(Call()->mAudioReceiveConfig);
+ ASSERT_EQ(Call()->mAudioReceiveConfig->rtp.extensions.back().uri,
+ webrtc::RtpExtension::kCsrcAudioLevelsUri);
+ ASSERT_TRUE(Call()->mAudioSendConfig);
+ ASSERT_TRUE(Call()->mAudioSendConfig->rtp.extensions.empty());
+
+ // Mid
+ mControl.Update([&](auto& aControl) {
+ // We do not support configuring receiving MId. It will be ignored.
+ RtpExtList extensions;
+ webrtc::RtpExtension extension;
+ extension.uri = webrtc::RtpExtension::kMidUri;
+ extensions.emplace_back(extension);
+ aControl.mLocalRecvRtpExtensions = extensions;
+ aControl.mLocalSendRtpExtensions = extensions;
+ });
+ ASSERT_TRUE(Call()->mAudioReceiveConfig);
+ ASSERT_TRUE(Call()->mAudioReceiveConfig->rtp.extensions.empty());
+ ASSERT_EQ(Call()->mAudioSendConfig->rtp.extensions.back().uri,
+ webrtc::RtpExtension::kMidUri);
+}
+
+TEST_F(AudioConduitTest, TestSyncGroup) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mSyncGroup = "test";
+ aControl.mReceiving = true;
+ });
+ ASSERT_TRUE(Call()->mAudioReceiveConfig);
+ ASSERT_EQ(Call()->mAudioReceiveConfig->sync_group, "test");
+}
+
+} // End namespace test.
diff --git a/media/webrtc/signaling/gtest/jsep_session_unittest.cpp b/media/webrtc/signaling/gtest/jsep_session_unittest.cpp
new file mode 100644
index 0000000000..e9062ad971
--- /dev/null
+++ b/media/webrtc/signaling/gtest/jsep_session_unittest.cpp
@@ -0,0 +1,7609 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <iostream>
+#include <map>
+
+#include "nss.h"
+#include "ssl.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/RefPtr.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+
+#include "PeerConnectionImpl.h"
+#include "sdp/SdpMediaSection.h"
+#include "sdp/SipccSdpParser.h"
+#include "jsep/JsepCodecDescription.h"
+#include "jsep/JsepTrack.h"
+#include "jsep/JsepSession.h"
+#include "jsep/JsepSessionImpl.h"
+
+namespace mozilla {
+static std::string kAEqualsCandidate("a=candidate:");
+const static size_t kNumCandidatesPerComponent = 3;
+
+class JsepSessionTestBase : public ::testing::Test {
+ public:
+ static void SetUpTestCase() {
+ NSS_NoDB_Init(nullptr);
+ NSS_SetDomesticPolicy();
+ }
+};
+
+class FakeUuidGenerator : public mozilla::JsepUuidGenerator {
+ public:
+ bool Generate(std::string* str) {
+ std::ostringstream os;
+ os << "FAKE_UUID_" << ++ctr;
+ *str = os.str();
+
+ return true;
+ }
+
+ mozilla::JsepUuidGenerator* Clone() const {
+ return new FakeUuidGenerator(*this);
+ }
+
+ private:
+ static uint64_t ctr;
+};
+
+uint64_t FakeUuidGenerator::ctr = 1000;
+
+class JsepSessionTest : public JsepSessionTestBase,
+ public ::testing::WithParamInterface<std::string> {
+ public:
+ JsepSessionTest() : mSdpHelper(&mLastError) {
+ Preferences::SetCString("media.peerconnection.sdp.parser", "legacy");
+ Preferences::SetCString("media.peerconnection.sdp.alternate_parse_mode",
+ "never");
+ Preferences::SetBool("media.peerconnection.video.use_rtx", true);
+ Preferences::SetBool("media.navigator.video.use_transport_cc", true);
+
+ mSessionOff =
+ MakeUnique<JsepSessionImpl>("Offerer", MakeUnique<FakeUuidGenerator>());
+ mSessionAns = MakeUnique<JsepSessionImpl>("Answerer",
+ MakeUnique<FakeUuidGenerator>());
+
+ EXPECT_EQ(NS_OK, mSessionOff->Init());
+ EXPECT_EQ(NS_OK, mSessionAns->Init());
+
+ std::vector<UniquePtr<JsepCodecDescription>> preferredCodecs;
+ PeerConnectionImpl::SetupPreferredCodecs(preferredCodecs);
+ mSessionOff->SetDefaultCodecs(preferredCodecs);
+ mSessionAns->SetDefaultCodecs(preferredCodecs);
+
+ std::vector<PeerConnectionImpl::RtpExtensionHeader> preferredHeaders;
+ PeerConnectionImpl::SetupPreferredRtpExtensions(preferredHeaders);
+
+ for (const auto& header : preferredHeaders) {
+ mSessionOff->AddRtpExtension(header.mMediaType, header.extensionname,
+ header.direction);
+ mSessionAns->AddRtpExtension(header.mMediaType, header.extensionname,
+ header.direction);
+ }
+
+ mOffererTransport = MakeUnique<TransportData>();
+ mAnswererTransport = MakeUnique<TransportData>();
+
+ AddTransportData(*mSessionOff, *mOffererTransport);
+ AddTransportData(*mSessionAns, *mAnswererTransport);
+
+ mOffCandidates = MakeUnique<CandidateSet>();
+ mAnsCandidates = MakeUnique<CandidateSet>();
+ }
+
+ static std::vector<JsepTransceiver>& GetTransceivers(JsepSession& aSession) {
+ return aSession.GetTransceivers();
+ }
+
+ static const std::vector<JsepTransceiver>& GetTransceivers(
+ const JsepSession& aSession) {
+ return aSession.GetTransceivers();
+ }
+
+ protected:
+ struct TransportData {
+ std::map<std::string, std::vector<uint8_t>> mFingerprints;
+ };
+
+ void AddDtlsFingerprint(const std::string& alg, JsepSessionImpl& session,
+ TransportData& tdata) {
+ std::vector<uint8_t> fp;
+ fp.assign((alg == "sha-1") ? 20 : 32,
+ (session.GetName() == "Offerer") ? 0x4f : 0x41);
+ session.AddDtlsFingerprint(alg, fp);
+ tdata.mFingerprints[alg] = fp;
+ }
+
+ void AddTransportData(JsepSessionImpl& session, TransportData& tdata) {
+ AddDtlsFingerprint("sha-1", session, tdata);
+ AddDtlsFingerprint("sha-256", session, tdata);
+ }
+
+ void CheckTransceiverInvariants(
+ const std::vector<JsepTransceiver>& oldTransceivers,
+ const std::vector<JsepTransceiver>& newTransceivers) {
+ ASSERT_LE(oldTransceivers.size(), newTransceivers.size());
+ std::set<size_t> levels;
+
+ for (const auto& newTransceiver : newTransceivers) {
+ if (newTransceiver.HasLevel()) {
+ ASSERT_FALSE(levels.count(newTransceiver.GetLevel()))
+ << "Two new transceivers are mapped to level "
+ << newTransceiver.GetLevel();
+ levels.insert(newTransceiver.GetLevel());
+ }
+ }
+
+ auto last = levels.rbegin();
+ if (last != levels.rend()) {
+ ASSERT_LE(*last, levels.size())
+ << "Max level observed in transceivers was " << *last
+ << ", but there are only " << levels.size()
+ << " levels in the "
+ "transceivers.";
+ }
+
+ for (const auto& oldTransceiver : oldTransceivers) {
+ if (oldTransceiver.HasLevel()) {
+ ASSERT_TRUE(levels.count(oldTransceiver.GetLevel()))
+ << "Level " << oldTransceiver.GetLevel()
+ << " had a transceiver in the old, but not the new (or, "
+ "perhaps this level had more than one transceiver in the "
+ "old)";
+ levels.erase(oldTransceiver.GetLevel());
+ }
+ }
+ }
+
+ std::string CreateOffer(const Maybe<JsepOfferOptions>& options = Nothing()) {
+ std::vector<JsepTransceiver> transceiversBefore =
+ GetTransceivers(*mSessionOff);
+ JsepOfferOptions defaultOptions;
+ const JsepOfferOptions& optionsRef = options ? *options : defaultOptions;
+ std::string offer;
+ JsepSession::Result result = mSessionOff->CreateOffer(optionsRef, &offer);
+ EXPECT_FALSE(result.mError.isSome()) << mSessionOff->GetLastError();
+
+ std::cerr << "OFFER: " << offer << std::endl;
+
+ ValidateTransport(*mOffererTransport, offer, sdp::kOffer);
+
+ if (transceiversBefore.size() != GetTransceivers(*mSessionOff).size()) {
+ EXPECT_TRUE(false) << "CreateOffer changed number of transceivers!";
+ return offer;
+ }
+
+ CheckTransceiverInvariants(transceiversBefore,
+ GetTransceivers(*mSessionOff));
+
+ for (size_t i = 0; i < transceiversBefore.size(); ++i) {
+ JsepTransceiver oldTransceiver = transceiversBefore[i];
+ JsepTransceiver newTransceiver = GetTransceivers(*mSessionOff)[i];
+ EXPECT_EQ(oldTransceiver.IsStopped(), newTransceiver.IsStopped());
+
+ if (oldTransceiver.IsStopped()) {
+ if (!newTransceiver.HasLevel()) {
+ // Tolerate unmapping of stopped transceivers by removing this
+ // difference.
+ oldTransceiver.ClearLevel();
+ }
+ } else if (!oldTransceiver.HasLevel()) {
+ EXPECT_TRUE(newTransceiver.HasLevel());
+ // Tolerate new mappings.
+ oldTransceiver.SetLevel(newTransceiver.GetLevel());
+ }
+
+ EXPECT_TRUE(Equals(oldTransceiver, newTransceiver));
+ }
+
+ return offer;
+ }
+
+ typedef enum { NO_ADDTRACK_MAGIC, ADDTRACK_MAGIC } AddTrackMagic;
+
+ void AddTracks(JsepSessionImpl& side, AddTrackMagic magic = ADDTRACK_MAGIC) {
+ // Add tracks.
+ if (types.empty()) {
+ types = BuildTypes(GetParam());
+ }
+ AddTracks(side, types, magic);
+
+ // Now, we move datachannel to the end
+ auto it =
+ std::find(types.begin(), types.end(), SdpMediaSection::kApplication);
+ if (it != types.end()) {
+ types.erase(it);
+ types.push_back(SdpMediaSection::kApplication);
+ }
+ }
+
+ void AddTracks(JsepSessionImpl& side, const std::string& mediatypes,
+ AddTrackMagic magic = ADDTRACK_MAGIC) {
+ AddTracks(side, BuildTypes(mediatypes), magic);
+ }
+
+ JsepTrack RemoveTrack(JsepSession& side, size_t index) {
+ if (GetTransceivers(side).size() <= index) {
+ EXPECT_TRUE(false) << "Index " << index << " out of bounds!";
+ return JsepTrack(SdpMediaSection::kAudio, sdp::kSend);
+ }
+
+ JsepTransceiver transceiver(GetTransceivers(side)[index]);
+ JsepTrack& track = transceiver.mSendTrack;
+ EXPECT_FALSE(track.GetStreamIds().empty()) << "No track at index " << index;
+
+ JsepTrack original(track);
+ track.ClearStreamIds();
+ transceiver.mJsDirection &= SdpDirectionAttribute::Direction::kRecvonly;
+ side.SetTransceiver(transceiver);
+ return original;
+ }
+
+ void SetDirection(JsepSession& side, size_t index,
+ SdpDirectionAttribute::Direction direction) {
+ ASSERT_LT(index, GetTransceivers(side).size())
+ << "Index " << index << " out of bounds!";
+
+ auto transceiver = GetTransceivers(side)[index];
+ transceiver.mJsDirection = direction;
+ side.SetTransceiver(transceiver);
+ }
+
+ std::vector<SdpMediaSection::MediaType> BuildTypes(
+ const std::string& mediatypes) {
+ std::vector<SdpMediaSection::MediaType> result;
+ size_t ptr = 0;
+
+ for (;;) {
+ size_t comma = mediatypes.find(',', ptr);
+ std::string chunk = mediatypes.substr(ptr, comma - ptr);
+
+ if (chunk == "audio") {
+ result.push_back(SdpMediaSection::kAudio);
+ } else if (chunk == "video") {
+ result.push_back(SdpMediaSection::kVideo);
+ } else if (chunk == "datachannel") {
+ result.push_back(SdpMediaSection::kApplication);
+ } else {
+ MOZ_CRASH();
+ }
+
+ if (comma == std::string::npos) break;
+ ptr = comma + 1;
+ }
+
+ return result;
+ }
+
+ void AddTracks(JsepSessionImpl& side,
+ const std::vector<SdpMediaSection::MediaType>& mediatypes,
+ AddTrackMagic magic = ADDTRACK_MAGIC) {
+ std::string stream_id;
+ std::string track_id;
+
+ ASSERT_TRUE(mUuidGen.Generate(&stream_id));
+
+ AddTracksToStream(side, stream_id, mediatypes, magic);
+ }
+
+ void AddTracksToStream(JsepSessionImpl& side, const std::string stream_id,
+ const std::string& mediatypes,
+ AddTrackMagic magic = ADDTRACK_MAGIC) {
+ AddTracksToStream(side, stream_id, BuildTypes(mediatypes), magic);
+ }
+
+ // A bit of a hack. JsepSessionImpl populates the track-id automatically, just
+ // in case, because the w3c spec requires msid to be set even when there's no
+ // send track.
+ bool IsNull(const JsepTrack& track) const {
+ return track.GetStreamIds().empty() &&
+ (track.GetMediaType() != SdpMediaSection::MediaType::kApplication);
+ }
+
+ void AddTracksToStream(
+ JsepSessionImpl& side, const std::string stream_id,
+ const std::vector<SdpMediaSection::MediaType>& mediatypes,
+ AddTrackMagic magic = ADDTRACK_MAGIC)
+
+ {
+ std::string track_id;
+
+ for (auto type : mediatypes) {
+ ASSERT_TRUE(mUuidGen.Generate(&track_id));
+
+ Maybe<JsepTransceiver> suitableTransceiver;
+ size_t i;
+ if (magic == ADDTRACK_MAGIC) {
+ // We're simulating addTrack.
+ for (i = 0; i < GetTransceivers(side).size(); ++i) {
+ auto transceiver = GetTransceivers(side)[i];
+ if (transceiver.mSendTrack.GetMediaType() != type) {
+ continue;
+ }
+
+ if (IsNull(transceiver.mSendTrack) ||
+ transceiver.GetMediaType() == SdpMediaSection::kApplication) {
+ suitableTransceiver = Some(transceiver);
+ break;
+ }
+ }
+ }
+
+ if (!suitableTransceiver) {
+ i = GetTransceivers(side).size();
+ side.AddTransceiver(JsepTransceiver(type, mUuidGen));
+ suitableTransceiver = Some(GetTransceivers(side).back());
+ if (magic == ADDTRACK_MAGIC) {
+ suitableTransceiver->SetAddTrackMagic();
+ }
+ }
+
+ std::cerr << "Updating send track for transceiver " << i << std::endl;
+ suitableTransceiver->SetOnlyExistsBecauseOfSetRemote(false);
+ suitableTransceiver->mJsDirection |=
+ SdpDirectionAttribute::Direction::kSendonly;
+ suitableTransceiver->mSendTrack.UpdateStreamIds(
+ std::vector<std::string>(1, stream_id));
+ side.SetTransceiver(*suitableTransceiver);
+ }
+ }
+
+ bool HasMediaStream(const std::vector<JsepTrack>& tracks) const {
+ for (const auto& track : tracks) {
+ if (track.GetMediaType() != SdpMediaSection::kApplication) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ const std::string GetFirstLocalStreamId(JsepSessionImpl& side) const {
+ auto tracks = GetLocalTracks(side);
+ return tracks.begin()->GetStreamIds()[0];
+ }
+
+ std::vector<JsepTrack> GetLocalTracks(const JsepSession& session) const {
+ std::vector<JsepTrack> result;
+ for (const auto& transceiver : GetTransceivers(session)) {
+ if (!IsNull(transceiver.mSendTrack)) {
+ result.push_back(transceiver.mSendTrack);
+ }
+ }
+ return result;
+ }
+
+ std::vector<JsepTrack> GetRemoteTracks(const JsepSession& session) const {
+ std::vector<JsepTrack> result;
+ for (const auto& transceiver : GetTransceivers(session)) {
+ if (!IsNull(transceiver.mRecvTrack)) {
+ result.push_back(transceiver.mRecvTrack);
+ }
+ }
+ return result;
+ }
+
+ JsepTransceiver* GetDatachannelTransceiver(JsepSession& side) {
+ for (auto& transceiver : GetTransceivers(side)) {
+ if (transceiver.mSendTrack.GetMediaType() ==
+ SdpMediaSection::MediaType::kApplication) {
+ return &transceiver;
+ }
+ }
+
+ return nullptr;
+ }
+
+ JsepTransceiver* GetNegotiatedTransceiver(JsepSession& side, size_t index) {
+ for (auto& transceiver : GetTransceivers(side)) {
+ if (transceiver.mSendTrack.GetNegotiatedDetails() ||
+ transceiver.mRecvTrack.GetNegotiatedDetails()) {
+ if (index) {
+ --index;
+ continue;
+ }
+
+ return &transceiver;
+ }
+ }
+
+ return nullptr;
+ }
+
+ Maybe<JsepTransceiver> GetTransceiverByLevel(
+ const std::vector<JsepTransceiver>& transceivers, size_t level) {
+ for (auto& transceiver : transceivers) {
+ if (transceiver.HasLevel() && transceiver.GetLevel() == level) {
+ return Some(transceiver);
+ }
+ }
+
+ return Nothing();
+ }
+
+ Maybe<JsepTransceiver> GetTransceiverByLevel(JsepSession& side,
+ size_t level) {
+ return GetTransceiverByLevel(GetTransceivers(side), level);
+ }
+
+ std::vector<std::string> GetMediaStreamIds(
+ const std::vector<JsepTrack>& tracks) const {
+ std::vector<std::string> ids;
+ for (const auto& track : tracks) {
+ // data channels don't have msid's
+ if (track.GetMediaType() == SdpMediaSection::kApplication) {
+ continue;
+ }
+ ids.insert(ids.end(), track.GetStreamIds().begin(),
+ track.GetStreamIds().end());
+ }
+ return ids;
+ }
+
+ std::vector<std::string> GetLocalMediaStreamIds(JsepSessionImpl& side) const {
+ return GetMediaStreamIds(GetLocalTracks(side));
+ }
+
+ std::vector<std::string> GetRemoteMediaStreamIds(
+ JsepSessionImpl& side) const {
+ return GetMediaStreamIds(GetRemoteTracks(side));
+ }
+
+ std::vector<std::string> sortUniqueStrVector(
+ std::vector<std::string> in) const {
+ std::sort(in.begin(), in.end());
+ auto it = std::unique(in.begin(), in.end());
+ in.resize(std::distance(in.begin(), it));
+ return in;
+ }
+
+ std::vector<std::string> GetLocalUniqueStreamIds(
+ JsepSessionImpl& side) const {
+ return sortUniqueStrVector(GetLocalMediaStreamIds(side));
+ }
+
+ std::vector<std::string> GetRemoteUniqueStreamIds(
+ JsepSessionImpl& side) const {
+ return sortUniqueStrVector(GetRemoteMediaStreamIds(side));
+ }
+
+ JsepTrack GetTrack(JsepSessionImpl& side, SdpMediaSection::MediaType type,
+ size_t index) const {
+ for (const auto& transceiver : GetTransceivers(side)) {
+ if (IsNull(transceiver.mSendTrack) ||
+ transceiver.mSendTrack.GetMediaType() != type) {
+ continue;
+ }
+
+ if (index != 0) {
+ --index;
+ continue;
+ }
+
+ return transceiver.mSendTrack;
+ }
+
+ return JsepTrack(type, sdp::kSend);
+ }
+
+ JsepTrack GetTrackOff(size_t index, SdpMediaSection::MediaType type) {
+ return GetTrack(*mSessionOff, type, index);
+ }
+
+ JsepTrack GetTrackAns(size_t index, SdpMediaSection::MediaType type) {
+ return GetTrack(*mSessionAns, type, index);
+ }
+
+ bool Equals(const SdpFingerprintAttributeList::Fingerprint& f1,
+ const SdpFingerprintAttributeList::Fingerprint& f2) const {
+ if (f1.hashFunc != f2.hashFunc) {
+ return false;
+ }
+
+ if (f1.fingerprint != f2.fingerprint) {
+ return false;
+ }
+
+ return true;
+ }
+
+ bool Equals(const SdpFingerprintAttributeList& f1,
+ const SdpFingerprintAttributeList& f2) const {
+ if (f1.mFingerprints.size() != f2.mFingerprints.size()) {
+ return false;
+ }
+
+ for (size_t i = 0; i < f1.mFingerprints.size(); ++i) {
+ if (!Equals(f1.mFingerprints[i], f2.mFingerprints[i])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ bool Equals(const UniquePtr<JsepDtlsTransport>& t1,
+ const UniquePtr<JsepDtlsTransport>& t2) const {
+ if (!t1 && !t2) {
+ return true;
+ }
+
+ if (!t1 || !t2) {
+ return false;
+ }
+
+ if (!Equals(t1->GetFingerprints(), t2->GetFingerprints())) {
+ return false;
+ }
+
+ if (t1->GetRole() != t2->GetRole()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ bool Equals(const UniquePtr<JsepIceTransport>& t1,
+ const UniquePtr<JsepIceTransport>& t2) const {
+ if (!t1 && !t2) {
+ return true;
+ }
+
+ if (!t1 || !t2) {
+ return false;
+ }
+
+ if (t1->GetUfrag() != t2->GetUfrag()) {
+ return false;
+ }
+
+ if (t1->GetPassword() != t2->GetPassword()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ bool Equals(const JsepTransport& t1, const JsepTransport& t2) const {
+ if (t1.mTransportId != t2.mTransportId) {
+ std::cerr << "Transport id differs: " << t1.mTransportId << " vs "
+ << t2.mTransportId << std::endl;
+ return false;
+ }
+
+ if (t1.mComponents != t2.mComponents) {
+ std::cerr << "Component count differs" << std::endl;
+ return false;
+ }
+
+ if (!Equals(t1.mIce, t2.mIce)) {
+ std::cerr << "ICE differs" << std::endl;
+ return false;
+ }
+
+ return true;
+ }
+
+ bool Equals(const JsepTrack& t1, const JsepTrack& t2) const {
+ if (t1.GetMediaType() != t2.GetMediaType()) {
+ return false;
+ }
+
+ if (t1.GetDirection() != t2.GetDirection()) {
+ return false;
+ }
+
+ if (t1.GetStreamIds() != t2.GetStreamIds()) {
+ return false;
+ }
+
+ if (t1.GetActive() != t2.GetActive()) {
+ return false;
+ }
+
+ if (t1.GetCNAME() != t2.GetCNAME()) {
+ return false;
+ }
+
+ if (t1.GetSsrcs() != t2.GetSsrcs()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ bool Equals(const JsepTransceiver& p1, const JsepTransceiver& p2) const {
+ if (p1.HasLevel() != p2.HasLevel()) {
+ std::cerr << "One transceiver has a level, the other doesn't"
+ << std::endl;
+ return false;
+ }
+
+ if (p1.HasLevel() && (p1.GetLevel() != p2.GetLevel())) {
+ std::cerr << "Level differs: " << p1.GetLevel() << " vs " << p2.GetLevel()
+ << std::endl;
+ return false;
+ }
+
+ // We don't check things like BundleLevel(), since that can change without
+ // any changes to the transport, which is what we're really interested in.
+
+ if (p1.IsStopped() != p2.IsStopped()) {
+ std::cerr << "One transceiver is stopped, the other is not" << std::endl;
+ return false;
+ }
+
+ if (p1.IsAssociated() != p2.IsAssociated()) {
+ std::cerr << "One transceiver has a mid, the other doesn't" << std::endl;
+ return false;
+ }
+
+ if (p1.IsAssociated() && (p1.GetMid() != p2.GetMid())) {
+ std::cerr << "mid differs: " << p1.GetMid() << " vs " << p2.GetMid()
+ << std::endl;
+ return false;
+ }
+
+ if (!Equals(p1.mSendTrack, p2.mSendTrack)) {
+ std::cerr << "Send track differs" << std::endl;
+ return false;
+ }
+
+ if (!Equals(p1.mRecvTrack, p2.mRecvTrack)) {
+ std::cerr << "Receive track differs" << std::endl;
+ return false;
+ }
+
+ if (!Equals(p1.mTransport, p2.mTransport)) {
+ std::cerr << "Transport differs" << std::endl;
+ return false;
+ }
+
+ return true;
+ }
+
+ bool Equals(const std::vector<JsepTransceiver>& t1,
+ const std::vector<JsepTransceiver>& t2) const {
+ if (t1.size() != t2.size()) {
+ std::cerr << "Size differs: t1.size = " << t1.size()
+ << ", t2.size = " << t2.size() << std::endl;
+ return false;
+ }
+
+ for (size_t i = 0; i < t1.size(); ++i) {
+ if (!Equals(t1[i], t2[i])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ size_t GetTrackCount(JsepSessionImpl& side,
+ SdpMediaSection::MediaType type) const {
+ size_t result = 0;
+ for (const auto& track : GetLocalTracks(side)) {
+ if (track.GetMediaType() == type) {
+ ++result;
+ }
+ }
+ return result;
+ }
+
+ UniquePtr<Sdp> GetParsedLocalDescription(const JsepSessionImpl& side) const {
+ return Parse(side.GetLocalDescription(kJsepDescriptionCurrent));
+ }
+
+ SdpMediaSection* GetMsection(Sdp& sdp, SdpMediaSection::MediaType type,
+ size_t index) const {
+ for (size_t i = 0; i < sdp.GetMediaSectionCount(); ++i) {
+ auto& msection = sdp.GetMediaSection(i);
+ if (msection.GetMediaType() != type) {
+ continue;
+ }
+
+ if (index) {
+ --index;
+ continue;
+ }
+
+ return &msection;
+ }
+
+ return nullptr;
+ }
+
+ void SetPayloadTypeNumber(JsepSession& session, const std::string& codecName,
+ const std::string& payloadType) {
+ for (auto& codec : session.Codecs()) {
+ if (codec->mName == codecName) {
+ codec->mDefaultPt = payloadType;
+ }
+ }
+ }
+
+ void SetCodecEnabled(JsepSession& session, const std::string& codecName,
+ bool enabled) {
+ for (auto& codec : session.Codecs()) {
+ if (codec->mName == codecName) {
+ codec->mEnabled = enabled;
+ }
+ }
+ }
+
+ void EnsureNegotiationFailure(SdpMediaSection::MediaType type,
+ const std::string& codecName) {
+ for (auto& codec : mSessionOff->Codecs()) {
+ if (codec->Type() == type && codec->mName != codecName) {
+ codec->mEnabled = false;
+ }
+ }
+
+ for (auto& codec : mSessionAns->Codecs()) {
+ if (codec->Type() == type && codec->mName == codecName) {
+ codec->mEnabled = false;
+ }
+ }
+ }
+
+ std::string CreateAnswer() {
+ std::vector<JsepTransceiver> transceiversBefore =
+ GetTransceivers(*mSessionAns);
+
+ JsepAnswerOptions options;
+ std::string answer;
+
+ JsepSession::Result result = mSessionAns->CreateAnswer(options, &answer);
+ EXPECT_FALSE(result.mError.isSome());
+
+ std::cerr << "ANSWER: " << answer << std::endl;
+
+ ValidateTransport(*mAnswererTransport, answer, sdp::kAnswer);
+ CheckTransceiverInvariants(transceiversBefore,
+ GetTransceivers(*mSessionAns));
+
+ return answer;
+ }
+
+ static const uint32_t NO_CHECKS = 0;
+ static const uint32_t CHECK_SUCCESS = 1;
+ static const uint32_t CHECK_TRACKS = 1 << 2;
+ static const uint32_t ALL_CHECKS = CHECK_SUCCESS | CHECK_TRACKS;
+
+ void OfferAnswer(uint32_t checkFlags = ALL_CHECKS,
+ const Maybe<JsepOfferOptions>& options = Nothing()) {
+ std::string offer = CreateOffer(options);
+ SetLocalOffer(offer, checkFlags);
+ SetRemoteOffer(offer, checkFlags);
+
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer, checkFlags);
+ SetRemoteAnswer(answer, checkFlags);
+ }
+
+ void SetLocalOffer(const std::string& offer,
+ uint32_t checkFlags = ALL_CHECKS) {
+ std::vector<JsepTransceiver> transceiversBefore =
+ GetTransceivers(*mSessionOff);
+
+ JsepSession::Result result =
+ mSessionOff->SetLocalDescription(kJsepSdpOffer, offer);
+
+ CheckTransceiverInvariants(transceiversBefore,
+ GetTransceivers(*mSessionOff));
+
+ if (checkFlags & CHECK_SUCCESS) {
+ ASSERT_FALSE(result.mError.isSome());
+ }
+
+ if (checkFlags & CHECK_TRACKS) {
+ // This assumes no recvonly or inactive transceivers.
+ ASSERT_EQ(types.size(), GetTransceivers(*mSessionOff).size());
+ for (const auto& transceiver : GetTransceivers(*mSessionOff)) {
+ if (!transceiver.HasLevel()) {
+ continue;
+ }
+ const auto& track(transceiver.mSendTrack);
+ size_t level = transceiver.GetLevel();
+ ASSERT_FALSE(IsNull(track));
+ ASSERT_EQ(types[level], track.GetMediaType());
+ if (track.GetMediaType() != SdpMediaSection::kApplication) {
+ std::string msidAttr("a=msid:");
+ msidAttr += track.GetStreamIds()[0];
+ ASSERT_NE(std::string::npos, offer.find(msidAttr))
+ << "Did not find " << msidAttr << " in offer";
+ }
+ }
+ if (types.size() == 1 && types[0] == SdpMediaSection::kApplication) {
+ ASSERT_EQ(std::string::npos, offer.find("a=ssrc"))
+ << "Data channel should not contain SSRC";
+ }
+ }
+ }
+
+ void SetRemoteOffer(const std::string& offer,
+ uint32_t checkFlags = ALL_CHECKS) {
+ std::vector<JsepTransceiver> transceiversBefore =
+ GetTransceivers(*mSessionAns);
+
+ JsepSession::Result result =
+ mSessionAns->SetRemoteDescription(kJsepSdpOffer, offer);
+
+ CheckTransceiverInvariants(transceiversBefore,
+ GetTransceivers(*mSessionAns));
+
+ if (checkFlags & CHECK_SUCCESS) {
+ ASSERT_FALSE(result.mError.isSome());
+ }
+
+ if (checkFlags & CHECK_TRACKS) {
+ // This assumes no recvonly or inactive transceivers.
+ ASSERT_EQ(types.size(), GetTransceivers(*mSessionAns).size());
+ for (const auto& transceiver : GetTransceivers(*mSessionAns)) {
+ if (!transceiver.HasLevel()) {
+ continue;
+ }
+ const auto& track(transceiver.mRecvTrack);
+ size_t level = transceiver.GetLevel();
+ ASSERT_FALSE(IsNull(track));
+ ASSERT_EQ(types[level], track.GetMediaType());
+ if (track.GetMediaType() != SdpMediaSection::kApplication) {
+ std::string msidAttr("a=msid:");
+ msidAttr += track.GetStreamIds()[0];
+ // Track id will not match, and will eventually not be present at all
+ ASSERT_NE(std::string::npos, offer.find(msidAttr))
+ << "Did not find " << msidAttr << " in offer";
+ }
+ }
+ }
+ }
+
+ void SetLocalAnswer(const std::string& answer,
+ uint32_t checkFlags = ALL_CHECKS) {
+ std::vector<JsepTransceiver> transceiversBefore =
+ GetTransceivers(*mSessionAns);
+
+ JsepSession::Result result =
+ mSessionAns->SetLocalDescription(kJsepSdpAnswer, answer);
+ if (checkFlags & CHECK_SUCCESS) {
+ ASSERT_FALSE(result.mError.isSome());
+ }
+
+ CheckTransceiverInvariants(transceiversBefore,
+ GetTransceivers(*mSessionAns));
+
+ if (checkFlags & CHECK_TRACKS) {
+ // Verify that the right stuff is in the tracks.
+ ASSERT_EQ(types.size(), GetTransceivers(*mSessionAns).size());
+ for (const auto& transceiver : GetTransceivers(*mSessionAns)) {
+ if (!transceiver.HasLevel()) {
+ continue;
+ }
+ const auto& sendTrack(transceiver.mSendTrack);
+ const auto& recvTrack(transceiver.mRecvTrack);
+ size_t level = transceiver.GetLevel();
+ ASSERT_FALSE(IsNull(sendTrack));
+ ASSERT_EQ(types[level], sendTrack.GetMediaType());
+ // These might have been in the SDP, or might have been randomly
+ // chosen by JsepSessionImpl
+ ASSERT_FALSE(IsNull(recvTrack));
+ ASSERT_EQ(types[level], recvTrack.GetMediaType());
+
+ if (recvTrack.GetMediaType() != SdpMediaSection::kApplication) {
+ std::string msidAttr("a=msid:");
+ msidAttr += sendTrack.GetStreamIds()[0];
+ ASSERT_NE(std::string::npos, answer.find(msidAttr))
+ << "Did not find " << msidAttr << " in answer";
+ }
+ }
+ if (types.size() == 1 && types[0] == SdpMediaSection::kApplication) {
+ ASSERT_EQ(std::string::npos, answer.find("a=ssrc"))
+ << "Data channel should not contain SSRC";
+ }
+ }
+ std::cerr << "Answerer transceivers:" << std::endl;
+ DumpTransceivers(*mSessionAns);
+ }
+
+ void SetRemoteAnswer(const std::string& answer,
+ uint32_t checkFlags = ALL_CHECKS) {
+ std::vector<JsepTransceiver> transceiversBefore =
+ GetTransceivers(*mSessionOff);
+
+ JsepSession::Result result =
+ mSessionOff->SetRemoteDescription(kJsepSdpAnswer, answer);
+ if (checkFlags & CHECK_SUCCESS) {
+ ASSERT_FALSE(result.mError.isSome());
+ }
+
+ CheckTransceiverInvariants(transceiversBefore,
+ GetTransceivers(*mSessionOff));
+
+ if (checkFlags & CHECK_TRACKS) {
+ // Verify that the right stuff is in the tracks.
+ ASSERT_EQ(types.size(), GetTransceivers(*mSessionOff).size());
+ for (const auto& transceiver : GetTransceivers(*mSessionOff)) {
+ if (!transceiver.HasLevel()) {
+ continue;
+ }
+ const auto& sendTrack(transceiver.mSendTrack);
+ const auto& recvTrack(transceiver.mRecvTrack);
+ size_t level = transceiver.GetLevel();
+ ASSERT_FALSE(IsNull(sendTrack));
+ ASSERT_EQ(types[level], sendTrack.GetMediaType());
+ // These might have been in the SDP, or might have been randomly
+ // chosen by JsepSessionImpl
+ ASSERT_FALSE(IsNull(recvTrack));
+ ASSERT_EQ(types[level], recvTrack.GetMediaType());
+
+ if (recvTrack.GetMediaType() != SdpMediaSection::kApplication) {
+ std::string msidAttr("a=msid:");
+ msidAttr += recvTrack.GetStreamIds()[0];
+ // Track id will not match, and will eventually not be present at all
+ ASSERT_NE(std::string::npos, answer.find(msidAttr))
+ << "Did not find " << msidAttr << " in answer";
+ }
+ }
+ }
+ std::cerr << "Offerer transceivers:" << std::endl;
+ DumpTransceivers(*mSessionOff);
+ }
+
+ std::string GetTransportId(const JsepSession& session, size_t level) {
+ for (const auto& transceiver : GetTransceivers(session)) {
+ if (transceiver.HasLevel() && transceiver.GetLevel() == level) {
+ return transceiver.mTransport.mTransportId;
+ }
+ }
+ return std::string();
+ }
+
+ typedef enum { RTP = 1, RTCP = 2 } ComponentType;
+
+ class CandidateSet {
+ public:
+ CandidateSet() {}
+
+ void Gather(JsepSession& session, ComponentType maxComponent = RTCP) {
+ for (const auto& transceiver : GetTransceivers(session)) {
+ if (transceiver.HasOwnTransport()) {
+ Gather(session, transceiver.mTransport.mTransportId, RTP);
+ if (transceiver.mTransport.mComponents > 1) {
+ Gather(session, transceiver.mTransport.mTransportId, RTCP);
+ }
+ }
+ }
+ FinishGathering(session);
+ }
+
+ void Gather(JsepSession& session, const std::string& transportId,
+ ComponentType component) {
+ static uint16_t port = 1000;
+ std::vector<std::string> candidates;
+
+ for (size_t i = 0; i < kNumCandidatesPerComponent; ++i) {
+ ++port;
+ std::ostringstream candidate;
+ candidate << "0 " << static_cast<uint16_t>(component)
+ << " UDP 9999 192.168.0.1 " << port << " typ host";
+ std::string mid;
+ uint16_t level = 0;
+ bool skipped;
+ session.AddLocalIceCandidate(kAEqualsCandidate + candidate.str(),
+ transportId, "", &level, &mid, &skipped);
+ if (!skipped) {
+ mCandidatesToTrickle.push_back(std::tuple<Level, Mid, Candidate>(
+ level, mid, kAEqualsCandidate + candidate.str()));
+ candidates.push_back(candidate.str());
+ }
+ }
+
+ // Stomp existing candidates
+ mCandidates[transportId][component] = candidates;
+
+ // Stomp existing defaults
+ mDefaultCandidates[transportId][component] =
+ std::make_pair("192.168.0.1", port);
+ session.UpdateDefaultCandidate(
+ mDefaultCandidates[transportId][RTP].first,
+ mDefaultCandidates[transportId][RTP].second,
+ // Will be empty string if not present, which is how we indicate
+ // that there is no default for RTCP
+ mDefaultCandidates[transportId][RTCP].first,
+ mDefaultCandidates[transportId][RTCP].second, transportId);
+ }
+
+ void FinishGathering(JsepSession& session) const {
+ // Copy so we can be terse and use []
+ for (auto idAndCandidates : mDefaultCandidates) {
+ ASSERT_EQ(1U, idAndCandidates.second.count(RTP));
+ // do a final UpdateDefaultCandidate here in case candidates were
+ // cleared during renegotiation.
+ session.UpdateDefaultCandidate(
+ idAndCandidates.second[RTP].first,
+ idAndCandidates.second[RTP].second,
+ // Will be empty string if not present, which is how we indicate
+ // that there is no default for RTCP
+ idAndCandidates.second[RTCP].first,
+ idAndCandidates.second[RTCP].second, idAndCandidates.first);
+ std::string mid;
+ uint16_t level = 0;
+ bool skipped;
+ session.AddLocalIceCandidate("", idAndCandidates.first, "", &level,
+ &mid, &skipped);
+ }
+ }
+
+ void Trickle(JsepSession& session) {
+ std::string transportId;
+ for (const auto& levelMidAndCandidate : mCandidatesToTrickle) {
+ auto [level, mid, candidate] = levelMidAndCandidate;
+ std::cerr << "trickling candidate: " << candidate << " level: " << level
+ << " mid: " << mid << std::endl;
+ Maybe<unsigned long> lev = Some(level);
+ session.AddRemoteIceCandidate(candidate, mid, lev, "", &transportId);
+ }
+ session.AddRemoteIceCandidate("", "", Maybe<uint16_t>(), "",
+ &transportId);
+ mCandidatesToTrickle.clear();
+ }
+
+ void CheckRtpCandidates(bool expectRtpCandidates,
+ const SdpMediaSection& msection,
+ const std::string& transportId,
+ const std::string& context) const {
+ auto& attrs = msection.GetAttributeList();
+
+ ASSERT_EQ(expectRtpCandidates,
+ attrs.HasAttribute(SdpAttribute::kCandidateAttribute))
+ << context << " (level " << msection.GetLevel() << ")";
+
+ if (expectRtpCandidates) {
+ // Copy so we can be terse and use []
+ auto expectedCandidates = mCandidates;
+ ASSERT_LE(kNumCandidatesPerComponent,
+ expectedCandidates[transportId][RTP].size());
+
+ auto& candidates = attrs.GetCandidate();
+ ASSERT_LE(kNumCandidatesPerComponent, candidates.size())
+ << context << " (level " << msection.GetLevel() << ")";
+ for (size_t i = 0; i < kNumCandidatesPerComponent; ++i) {
+ ASSERT_EQ(expectedCandidates[transportId][RTP][i], candidates[i])
+ << context << " (level " << msection.GetLevel() << ")";
+ }
+ }
+ }
+
+ void CheckRtcpCandidates(bool expectRtcpCandidates,
+ const SdpMediaSection& msection,
+ const std::string& transportId,
+ const std::string& context) const {
+ auto& attrs = msection.GetAttributeList();
+
+ if (expectRtcpCandidates) {
+ // Copy so we can be terse and use []
+ auto expectedCandidates = mCandidates;
+ ASSERT_LE(kNumCandidatesPerComponent,
+ expectedCandidates[transportId][RTCP].size());
+
+ ASSERT_TRUE(attrs.HasAttribute(SdpAttribute::kCandidateAttribute))
+ << context << " (level " << msection.GetLevel() << ")";
+ auto& candidates = attrs.GetCandidate();
+ ASSERT_EQ(kNumCandidatesPerComponent * 2, candidates.size())
+ << context << " (level " << msection.GetLevel() << ")";
+ for (size_t i = 0; i < kNumCandidatesPerComponent; ++i) {
+ ASSERT_EQ(expectedCandidates[transportId][RTCP][i],
+ candidates[i + kNumCandidatesPerComponent])
+ << context << " (level " << msection.GetLevel() << ")";
+ }
+ }
+ }
+
+ void CheckDefaultRtpCandidate(bool expectDefault,
+ const SdpMediaSection& msection,
+ const std::string& transportId,
+ const std::string& context) const {
+ Address expectedAddress = "0.0.0.0";
+ Port expectedPort = 9U;
+
+ if (expectDefault) {
+ // Copy so we can be terse and use []
+ auto defaultCandidates = mDefaultCandidates;
+ expectedAddress = defaultCandidates[transportId][RTP].first;
+ expectedPort = defaultCandidates[transportId][RTP].second;
+ }
+
+ // if bundle-only attribute is present, expect port 0
+ const SdpAttributeList& attrs = msection.GetAttributeList();
+ if (attrs.HasAttribute(SdpAttribute::kBundleOnlyAttribute)) {
+ expectedPort = 0U;
+ }
+
+ ASSERT_EQ(expectedAddress, msection.GetConnection().GetAddress())
+ << context << " (level " << msection.GetLevel() << ")";
+ ASSERT_EQ(expectedPort, msection.GetPort())
+ << context << " (level " << msection.GetLevel() << ")";
+ }
+
+ void CheckDefaultRtcpCandidate(bool expectDefault,
+ const SdpMediaSection& msection,
+ const std::string& transportId,
+ const std::string& context) const {
+ if (expectDefault) {
+ // Copy so we can be terse and use []
+ auto defaultCandidates = mDefaultCandidates;
+ ASSERT_TRUE(msection.GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpAttribute))
+ << context << " (level " << msection.GetLevel() << ")";
+ auto& rtcpAttr = msection.GetAttributeList().GetRtcp();
+ ASSERT_EQ(defaultCandidates[transportId][RTCP].second, rtcpAttr.mPort)
+ << context << " (level " << msection.GetLevel() << ")";
+ ASSERT_EQ(sdp::kInternet, rtcpAttr.mNetType)
+ << context << " (level " << msection.GetLevel() << ")";
+ ASSERT_EQ(sdp::kIPv4, rtcpAttr.mAddrType)
+ << context << " (level " << msection.GetLevel() << ")";
+ ASSERT_EQ(defaultCandidates[transportId][RTCP].first, rtcpAttr.mAddress)
+ << context << " (level " << msection.GetLevel() << ")";
+ } else {
+ ASSERT_FALSE(msection.GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpAttribute))
+ << context << " (level " << msection.GetLevel() << ")";
+ }
+ }
+
+ private:
+ typedef size_t Level;
+ typedef std::string TransportId;
+ typedef std::string Mid;
+ typedef std::string Candidate;
+ typedef std::string Address;
+ typedef uint16_t Port;
+ // Default candidates are put into the m-line, c-line, and rtcp
+ // attribute for endpoints that don't support ICE.
+ std::map<TransportId, std::map<ComponentType, std::pair<Address, Port>>>
+ mDefaultCandidates;
+ std::map<TransportId, std::map<ComponentType, std::vector<Candidate>>>
+ mCandidates;
+ // Level/mid/candidate tuples that need to be trickled
+ std::vector<std::tuple<Level, Mid, Candidate>> mCandidatesToTrickle;
+ };
+
+ // For streaming parse errors
+ std::string GetParseErrors(
+ const UniquePtr<SdpParser::Results>& results) const {
+ std::stringstream output;
+ auto errors = std::move(results->Errors());
+ for (auto error : errors) {
+ output << error.first << ": " << error.second << std::endl;
+ }
+ return output.str();
+ }
+
+ void CheckEndOfCandidates(bool expectEoc, const SdpMediaSection& msection,
+ const std::string& context) {
+ if (expectEoc) {
+ ASSERT_TRUE(msection.GetAttributeList().HasAttribute(
+ SdpAttribute::kEndOfCandidatesAttribute))
+ << context << " (level " << msection.GetLevel() << ")";
+ } else {
+ ASSERT_FALSE(msection.GetAttributeList().HasAttribute(
+ SdpAttribute::kEndOfCandidatesAttribute))
+ << context << " (level " << msection.GetLevel() << ")";
+ }
+ }
+
+ void CheckTransceiversAreBundled(const JsepSession& session,
+ const std::string& context) {
+ for (const auto& transceiver : GetTransceivers(session)) {
+ ASSERT_TRUE(transceiver.HasBundleLevel())
+ << context;
+ ASSERT_EQ(0U, transceiver.BundleLevel()) << context;
+ ASSERT_NE("", transceiver.mTransport.mTransportId);
+ }
+ }
+
+ void DisableMsid(std::string* sdp) const {
+ while (true) {
+ size_t pos = sdp->find("a=msid");
+ if (pos == std::string::npos) {
+ break;
+ }
+ (*sdp)[pos + 2] = 'X'; // garble, a=Xsid
+ }
+ }
+
+ void DisableBundle(std::string* sdp) const {
+ size_t pos = sdp->find("a=group:BUNDLE");
+ ASSERT_NE(std::string::npos, pos);
+ (*sdp)[pos + 11] = 'G'; // garble, a=group:BUNGLE
+ }
+
+ void DisableMsection(std::string* sdp, size_t level) const {
+ UniquePtr<Sdp> parsed(Parse(*sdp));
+ ASSERT_TRUE(parsed.get());
+ ASSERT_LT(level, parsed->GetMediaSectionCount());
+ SdpHelper::DisableMsection(parsed.get(), &parsed->GetMediaSection(level));
+ (*sdp) = parsed->ToString();
+ }
+
+ void CopyTransportAttributes(std::string* sdp, size_t src_level,
+ size_t dst_level) {
+ UniquePtr<Sdp> parsed(Parse(*sdp));
+ ASSERT_TRUE(parsed.get());
+ ASSERT_LT(src_level, parsed->GetMediaSectionCount());
+ ASSERT_LT(dst_level, parsed->GetMediaSectionCount());
+ nsresult rv =
+ mSdpHelper.CopyTransportParams(2, parsed->GetMediaSection(src_level),
+ &parsed->GetMediaSection(dst_level));
+ ASSERT_EQ(NS_OK, rv);
+ (*sdp) = parsed->ToString();
+ }
+
+ void ReplaceInSdp(std::string* sdp, const char* searchStr,
+ const char* replaceStr) const {
+ if (searchStr[0] == '\0') return;
+ size_t pos = 0;
+ while ((pos = sdp->find(searchStr, pos)) != std::string::npos) {
+ sdp->replace(pos, strlen(searchStr), replaceStr);
+ pos += strlen(replaceStr);
+ }
+ }
+
+ void ValidateDisabledMSection(const SdpMediaSection* msection) {
+ ASSERT_EQ(1U, msection->GetFormats().size());
+
+ auto& attrs = msection->GetAttributeList();
+ ASSERT_TRUE(attrs.HasAttribute(SdpAttribute::kMidAttribute));
+ ASSERT_TRUE(attrs.HasAttribute(SdpAttribute::kDirectionAttribute));
+ ASSERT_FALSE(attrs.HasAttribute(SdpAttribute::kBundleOnlyAttribute));
+ ASSERT_EQ(SdpDirectionAttribute::kInactive,
+ msection->GetDirectionAttribute().mValue);
+ ASSERT_EQ(3U, attrs.Count());
+ if (msection->GetMediaType() == SdpMediaSection::kAudio) {
+ ASSERT_EQ("0", msection->GetFormats()[0]);
+ const SdpRtpmapAttributeList::Rtpmap* rtpmap(msection->FindRtpmap("0"));
+ ASSERT_TRUE(rtpmap);
+ ASSERT_EQ("0", rtpmap->pt);
+ ASSERT_EQ("PCMU", rtpmap->name);
+ } else if (msection->GetMediaType() == SdpMediaSection::kVideo) {
+ ASSERT_EQ("120", msection->GetFormats()[0]);
+ const SdpRtpmapAttributeList::Rtpmap* rtpmap(msection->FindRtpmap("120"));
+ ASSERT_TRUE(rtpmap);
+ ASSERT_EQ("120", rtpmap->pt);
+ ASSERT_EQ("VP8", rtpmap->name);
+ } else if (msection->GetMediaType() == SdpMediaSection::kApplication) {
+ if (msection->GetProtocol() == SdpMediaSection::kUdpDtlsSctp ||
+ msection->GetProtocol() == SdpMediaSection::kTcpDtlsSctp) {
+ // draft 21 format
+ ASSERT_EQ("webrtc-datachannel", msection->GetFormats()[0]);
+ ASSERT_FALSE(msection->GetSctpmap());
+ ASSERT_EQ(0U, msection->GetSctpPort());
+ } else {
+ // old draft 05 format
+ ASSERT_EQ("0", msection->GetFormats()[0]);
+ const SdpSctpmapAttributeList::Sctpmap* sctpmap(msection->GetSctpmap());
+ ASSERT_TRUE(sctpmap);
+ ASSERT_EQ("0", sctpmap->pt);
+ ASSERT_EQ("rejected", sctpmap->name);
+ ASSERT_EQ(0U, sctpmap->streams);
+ }
+ } else {
+ // Not that we would have any test which tests this...
+ ASSERT_EQ("19", msection->GetFormats()[0]);
+ const SdpRtpmapAttributeList::Rtpmap* rtpmap(msection->FindRtpmap("19"));
+ ASSERT_TRUE(rtpmap);
+ ASSERT_EQ("19", rtpmap->pt);
+ ASSERT_EQ("reserved", rtpmap->name);
+ }
+
+ ASSERT_FALSE(msection->GetAttributeList().HasAttribute(
+ SdpAttribute::kMsidAttribute));
+ }
+
+ void ValidateSetupAttribute(const JsepSessionImpl& side,
+ const SdpSetupAttribute::Role expectedRole) {
+ auto sdp = GetParsedLocalDescription(side);
+ for (size_t i = 0; sdp && i < sdp->GetMediaSectionCount(); ++i) {
+ if (sdp->GetMediaSection(i).GetAttributeList().HasAttribute(
+ SdpAttribute::kSetupAttribute)) {
+ auto role = sdp->GetMediaSection(i).GetAttributeList().GetSetup().mRole;
+ ASSERT_EQ(expectedRole, role);
+ }
+ }
+ }
+
+ void DumpTrack(const JsepTrack& track) {
+ const JsepTrackNegotiatedDetails* details = track.GetNegotiatedDetails();
+ std::cerr << " type=" << track.GetMediaType() << std::endl;
+ if (!details) {
+ std::cerr << " not negotiated" << std::endl;
+ return;
+ }
+ std::cerr << " encodings=" << std::endl;
+ for (size_t i = 0; i < details->GetEncodingCount(); ++i) {
+ const JsepTrackEncoding& encoding = details->GetEncoding(i);
+ std::cerr << " id=" << encoding.mRid << std::endl;
+ for (const auto& codec : encoding.GetCodecs()) {
+ std::cerr << " " << codec->mName << " enabled("
+ << (codec->mEnabled ? "yes" : "no") << ")";
+ if (track.GetMediaType() == SdpMediaSection::kAudio) {
+ const JsepAudioCodecDescription* audioCodec =
+ static_cast<const JsepAudioCodecDescription*>(codec.get());
+ std::cerr << " dtmf(" << (audioCodec->mDtmfEnabled ? "yes" : "no")
+ << ")";
+ }
+ if (track.GetMediaType() == SdpMediaSection::kVideo) {
+ const JsepVideoCodecDescription* videoCodec =
+ static_cast<const JsepVideoCodecDescription*>(codec.get());
+ std::cerr << " rtx("
+ << (videoCodec->mRtxEnabled ? videoCodec->mRtxPayloadType
+ : "no")
+ << ")";
+ }
+ std::cerr << std::endl;
+ }
+ }
+ }
+
+ void DumpTransport(const JsepTransport& transport) {
+ std::cerr << " id=" << transport.mTransportId << std::endl;
+ std::cerr << " components=" << transport.mComponents << std::endl;
+ }
+
+ void DumpTransceivers(const JsepSessionImpl& session) {
+ for (const auto& transceiver : GetTransceivers(session)) {
+ std::cerr << "Transceiver ";
+ if (transceiver.HasLevel()) {
+ std::cerr << transceiver.GetLevel() << std::endl;
+ } else {
+ std::cerr << "<NO LEVEL>" << std::endl;
+ }
+ if (transceiver.HasBundleLevel()) {
+ std::cerr << "(bundle level is " << transceiver.BundleLevel() << ")"
+ << std::endl;
+ }
+ if (!IsNull(transceiver.mSendTrack)) {
+ std::cerr << "Sending-->" << std::endl;
+ DumpTrack(transceiver.mSendTrack);
+ }
+ if (!IsNull(transceiver.mRecvTrack)) {
+ std::cerr << "Receiving-->" << std::endl;
+ DumpTrack(transceiver.mRecvTrack);
+ }
+ std::cerr << "Transport-->" << std::endl;
+ DumpTransport(transceiver.mTransport);
+ }
+ }
+
+ UniquePtr<Sdp> Parse(const std::string& sdp) const {
+ SipccSdpParser parser;
+ auto results = parser.Parse(sdp);
+ UniquePtr<Sdp> parsed = std::move(results->Sdp());
+ EXPECT_TRUE(parsed.get()) << "Should have valid SDP" << std::endl
+ << "Errors were: " << GetParseErrors(results);
+ return parsed;
+ }
+
+ std::string SetExtmap(const std::string& aSdp, const std::string& aUri,
+ uint16_t aId, uint16_t* aOldId = nullptr) {
+ UniquePtr<Sdp> munge(Parse(aSdp));
+ for (size_t i = 0; i < munge->GetMediaSectionCount(); ++i) {
+ auto& attrs = munge->GetMediaSection(i).GetAttributeList();
+ if (attrs.HasAttribute(SdpAttribute::kExtmapAttribute)) {
+ auto extmap = attrs.GetExtmap();
+ for (auto it = extmap.mExtmaps.begin(); it != extmap.mExtmaps.end();
+ ++it) {
+ if (it->extensionname == aUri) {
+ if (aOldId) {
+ *aOldId = it->entry;
+ }
+
+ if (aId) {
+ it->entry = aId;
+ } else {
+ extmap.mExtmaps.erase(it);
+ }
+ break;
+ }
+ }
+ attrs.SetAttribute(extmap.Clone());
+ }
+ }
+ return munge->ToString();
+ }
+
+ uint16_t GetExtmap(const std::string& aSdp, const std::string& aUri) {
+ UniquePtr<Sdp> parsed(Parse(aSdp));
+ for (size_t i = 0; i < parsed->GetMediaSectionCount(); ++i) {
+ auto& attrs = parsed->GetMediaSection(i).GetAttributeList();
+ if (attrs.HasAttribute(SdpAttribute::kExtmapAttribute)) {
+ auto extmap = attrs.GetExtmap();
+ for (auto& ext : extmap.mExtmaps) {
+ if (ext.extensionname == aUri) {
+ return ext.entry;
+ }
+ }
+ }
+ }
+ return 0;
+ }
+
+ void SwapOfferAnswerRoles() {
+ mSessionOff.swap(mSessionAns);
+ mOffCandidates.swap(mAnsCandidates);
+ mOffererTransport.swap(mAnswererTransport);
+ }
+
+ UniquePtr<JsepSessionImpl> mSessionOff;
+ UniquePtr<CandidateSet> mOffCandidates;
+ UniquePtr<JsepSessionImpl> mSessionAns;
+ UniquePtr<CandidateSet> mAnsCandidates;
+
+ std::vector<SdpMediaSection::MediaType> types;
+ std::vector<std::pair<std::string, uint16_t>> mGatheredCandidates;
+
+ FakeUuidGenerator mUuidGen;
+
+ private:
+ void ValidateTransport(TransportData& source, const std::string& sdp_str,
+ sdp::SdpType type) {
+ UniquePtr<Sdp> sdp(Parse(sdp_str));
+ ASSERT_TRUE(!!sdp);
+ size_t num_m_sections = sdp->GetMediaSectionCount();
+ for (size_t i = 0; i < num_m_sections; ++i) {
+ auto& msection = sdp->GetMediaSection(i);
+
+ if (msection.GetMediaType() == SdpMediaSection::kApplication) {
+ if (!(msection.GetProtocol() == SdpMediaSection::kUdpDtlsSctp ||
+ msection.GetProtocol() == SdpMediaSection::kTcpDtlsSctp)) {
+ // old draft 05 format
+ ASSERT_EQ(SdpMediaSection::kDtlsSctp, msection.GetProtocol());
+ }
+ } else {
+ ASSERT_EQ(SdpMediaSection::kUdpTlsRtpSavpf, msection.GetProtocol());
+ }
+
+ const SdpAttributeList& attrs = msection.GetAttributeList();
+ bool bundle_only = attrs.HasAttribute(SdpAttribute::kBundleOnlyAttribute);
+
+ // port 0 only means disabled when the bundle-only attribute is missing
+ if (!bundle_only && msection.GetPort() == 0) {
+ ValidateDisabledMSection(&msection);
+ continue;
+ }
+ if (mSdpHelper.OwnsTransport(*sdp, i, type)) {
+ const SdpAttributeList& attrs = msection.GetAttributeList();
+
+ ASSERT_FALSE(attrs.GetIceUfrag().empty());
+ ASSERT_FALSE(attrs.GetIcePwd().empty());
+ const SdpFingerprintAttributeList& fps = attrs.GetFingerprint();
+ for (auto fp = fps.mFingerprints.begin(); fp != fps.mFingerprints.end();
+ ++fp) {
+ std::string alg_str = "None";
+
+ if (fp->hashFunc == SdpFingerprintAttributeList::kSha1) {
+ alg_str = "sha-1";
+ } else if (fp->hashFunc == SdpFingerprintAttributeList::kSha256) {
+ alg_str = "sha-256";
+ }
+ ASSERT_EQ(source.mFingerprints[alg_str], fp->fingerprint);
+ }
+
+ ASSERT_EQ(source.mFingerprints.size(), fps.mFingerprints.size());
+ }
+ }
+ }
+
+ std::string mLastError;
+ SdpHelper mSdpHelper;
+
+ UniquePtr<TransportData> mOffererTransport;
+ UniquePtr<TransportData> mAnswererTransport;
+};
+
+TEST_F(JsepSessionTestBase, CreateDestroy) {}
+
+TEST_P(JsepSessionTest, CreateOffer) {
+ AddTracks(*mSessionOff);
+ CreateOffer();
+}
+
+TEST_P(JsepSessionTest, CreateOfferSetLocal) {
+ AddTracks(*mSessionOff);
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+}
+
+TEST_P(JsepSessionTest, CreateOfferSetLocalSetRemote) {
+ AddTracks(*mSessionOff);
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+}
+
+TEST_P(JsepSessionTest, CreateOfferSetLocalSetRemoteCreateAnswer) {
+ AddTracks(*mSessionOff);
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+ AddTracks(*mSessionAns);
+ std::string answer = CreateAnswer();
+}
+
+TEST_P(JsepSessionTest, CreateOfferSetLocalSetRemoteCreateAnswerSetLocal) {
+ AddTracks(*mSessionOff);
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+ AddTracks(*mSessionAns);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+}
+
+TEST_P(JsepSessionTest, FullCall) {
+ AddTracks(*mSessionOff);
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+ AddTracks(*mSessionAns);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+ SetRemoteAnswer(answer);
+}
+
+TEST_P(JsepSessionTest, GetDescriptions) {
+ AddTracks(*mSessionOff);
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+ std::string desc = mSessionOff->GetLocalDescription(kJsepDescriptionCurrent);
+ ASSERT_EQ(0U, desc.size());
+ desc = mSessionOff->GetLocalDescription(kJsepDescriptionPending);
+ ASSERT_NE(0U, desc.size());
+ desc = mSessionOff->GetLocalDescription(kJsepDescriptionPendingOrCurrent);
+ ASSERT_NE(0U, desc.size());
+ desc = mSessionOff->GetRemoteDescription(kJsepDescriptionPendingOrCurrent);
+ ASSERT_EQ(0U, desc.size());
+ desc = mSessionAns->GetLocalDescription(kJsepDescriptionPendingOrCurrent);
+ ASSERT_EQ(0U, desc.size());
+ desc = mSessionAns->GetRemoteDescription(kJsepDescriptionPendingOrCurrent);
+ ASSERT_EQ(0U, desc.size());
+
+ SetRemoteOffer(offer);
+ desc = mSessionAns->GetRemoteDescription(kJsepDescriptionCurrent);
+ ASSERT_EQ(0U, desc.size());
+ desc = mSessionAns->GetRemoteDescription(kJsepDescriptionPending);
+ ASSERT_NE(0U, desc.size());
+ desc = mSessionAns->GetRemoteDescription(kJsepDescriptionPendingOrCurrent);
+ ASSERT_NE(0U, desc.size());
+ desc = mSessionAns->GetLocalDescription(kJsepDescriptionPendingOrCurrent);
+ ASSERT_EQ(0U, desc.size());
+ desc = mSessionOff->GetLocalDescription(kJsepDescriptionPendingOrCurrent);
+ ASSERT_NE(0U, desc.size());
+ desc = mSessionOff->GetRemoteDescription(kJsepDescriptionPendingOrCurrent);
+ ASSERT_EQ(0U, desc.size());
+
+ AddTracks(*mSessionAns);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+ desc = mSessionAns->GetLocalDescription(kJsepDescriptionCurrent);
+ ASSERT_NE(0U, desc.size());
+ desc = mSessionAns->GetLocalDescription(kJsepDescriptionPending);
+ ASSERT_EQ(0U, desc.size());
+ desc = mSessionAns->GetLocalDescription(kJsepDescriptionPendingOrCurrent);
+ ASSERT_NE(0U, desc.size());
+ desc = mSessionAns->GetRemoteDescription(kJsepDescriptionCurrent);
+ ASSERT_NE(0U, desc.size());
+ desc = mSessionAns->GetRemoteDescription(kJsepDescriptionPending);
+ ASSERT_EQ(0U, desc.size());
+ desc = mSessionAns->GetRemoteDescription(kJsepDescriptionPendingOrCurrent);
+ ASSERT_NE(0U, desc.size());
+ desc = mSessionOff->GetLocalDescription(kJsepDescriptionPendingOrCurrent);
+ ASSERT_NE(0U, desc.size());
+ desc = mSessionOff->GetRemoteDescription(kJsepDescriptionPendingOrCurrent);
+ ASSERT_EQ(0U, desc.size());
+
+ SetRemoteAnswer(answer);
+ desc = mSessionOff->GetLocalDescription(kJsepDescriptionCurrent);
+ ASSERT_NE(0U, desc.size());
+ desc = mSessionOff->GetLocalDescription(kJsepDescriptionPending);
+ ASSERT_EQ(0U, desc.size());
+ desc = mSessionOff->GetLocalDescription(kJsepDescriptionPendingOrCurrent);
+ ASSERT_NE(0U, desc.size());
+ desc = mSessionOff->GetRemoteDescription(kJsepDescriptionCurrent);
+ ASSERT_NE(0U, desc.size());
+ desc = mSessionOff->GetRemoteDescription(kJsepDescriptionPending);
+ ASSERT_EQ(0U, desc.size());
+ desc = mSessionOff->GetRemoteDescription(kJsepDescriptionPendingOrCurrent);
+ ASSERT_NE(0U, desc.size());
+ desc = mSessionAns->GetLocalDescription(kJsepDescriptionPendingOrCurrent);
+ ASSERT_NE(0U, desc.size());
+ desc = mSessionAns->GetRemoteDescription(kJsepDescriptionPendingOrCurrent);
+ ASSERT_NE(0U, desc.size());
+}
+
+TEST_P(JsepSessionTest, RenegotiationNoChange) {
+ AddTracks(*mSessionOff);
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+
+ AddTracks(*mSessionAns);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+ SetRemoteAnswer(answer);
+
+ ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
+ ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);
+
+ std::vector<JsepTransceiver> origOffererTransceivers =
+ GetTransceivers(*mSessionOff);
+ std::vector<JsepTransceiver> origAnswererTransceivers =
+ GetTransceivers(*mSessionAns);
+
+ std::string reoffer = CreateOffer();
+ SetLocalOffer(reoffer);
+ SetRemoteOffer(reoffer);
+
+ std::string reanswer = CreateAnswer();
+ SetLocalAnswer(reanswer);
+ SetRemoteAnswer(reanswer);
+
+ ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
+ ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);
+
+ auto newOffererTransceivers = GetTransceivers(*mSessionOff);
+ auto newAnswererTransceivers = GetTransceivers(*mSessionAns);
+
+ ASSERT_TRUE(Equals(origOffererTransceivers, newOffererTransceivers));
+ ASSERT_TRUE(Equals(origAnswererTransceivers, newAnswererTransceivers));
+}
+
+// Disabled: See Bug 1329028
+TEST_P(JsepSessionTest, DISABLED_RenegotiationSwappedRolesNoChange) {
+ AddTracks(*mSessionOff);
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+
+ AddTracks(*mSessionAns);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+ SetRemoteAnswer(answer);
+
+ ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
+ ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);
+
+ auto offererTransceivers = GetTransceivers(*mSessionOff);
+ auto answererTransceivers = GetTransceivers(*mSessionAns);
+
+ SwapOfferAnswerRoles();
+
+ std::string reoffer = CreateOffer();
+ SetLocalOffer(reoffer);
+ SetRemoteOffer(reoffer);
+
+ std::string reanswer = CreateAnswer();
+ SetLocalAnswer(reanswer);
+ SetRemoteAnswer(reanswer);
+
+ ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
+ ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kPassive);
+
+ auto newOffererTransceivers = GetTransceivers(*mSessionOff);
+ auto newAnswererTransceivers = GetTransceivers(*mSessionAns);
+
+ ASSERT_TRUE(Equals(offererTransceivers, newAnswererTransceivers));
+ ASSERT_TRUE(Equals(answererTransceivers, newOffererTransceivers));
+}
+
+static void RemoveLastN(std::vector<JsepTransceiver>& aTransceivers,
+ size_t aNum) {
+ while (aNum--) {
+ // erase doesn't take reverse_iterator :(
+ aTransceivers.erase(--aTransceivers.end());
+ }
+}
+
+TEST_P(JsepSessionTest, RenegotiationOffererAddsTrack) {
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ OfferAnswer();
+
+ ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
+ ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);
+
+ std::vector<JsepTransceiver> origOffererTransceivers =
+ GetTransceivers(*mSessionOff);
+ std::vector<JsepTransceiver> origAnswererTransceivers =
+ GetTransceivers(*mSessionAns);
+
+ std::vector<SdpMediaSection::MediaType> extraTypes;
+ extraTypes.push_back(SdpMediaSection::kAudio);
+ extraTypes.push_back(SdpMediaSection::kVideo);
+ AddTracks(*mSessionOff, extraTypes);
+ types.insert(types.end(), extraTypes.begin(), extraTypes.end());
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
+ ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);
+
+ auto newOffererTransceivers = GetTransceivers(*mSessionOff);
+ auto newAnswererTransceivers = GetTransceivers(*mSessionAns);
+
+ ASSERT_LE(2U, newOffererTransceivers.size());
+ RemoveLastN(newOffererTransceivers, 2);
+ ASSERT_TRUE(Equals(origOffererTransceivers, newOffererTransceivers));
+
+ ASSERT_LE(2U, newAnswererTransceivers.size());
+ RemoveLastN(newAnswererTransceivers, 2);
+ ASSERT_TRUE(Equals(origAnswererTransceivers, newAnswererTransceivers));
+}
+
+TEST_P(JsepSessionTest, RenegotiationAnswererAddsTrack) {
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ OfferAnswer();
+
+ ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
+ ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);
+
+ std::vector<JsepTransceiver> origOffererTransceivers =
+ GetTransceivers(*mSessionOff);
+ std::vector<JsepTransceiver> origAnswererTransceivers =
+ GetTransceivers(*mSessionAns);
+
+ std::vector<SdpMediaSection::MediaType> extraTypes;
+ extraTypes.push_back(SdpMediaSection::kAudio);
+ extraTypes.push_back(SdpMediaSection::kVideo);
+ AddTracks(*mSessionAns, extraTypes);
+ types.insert(types.end(), extraTypes.begin(), extraTypes.end());
+
+ // We need to add a recvonly m-section to the offer for this to work
+ mSessionOff->AddTransceiver(
+ JsepTransceiver(SdpMediaSection::kAudio, mUuidGen,
+ SdpDirectionAttribute::Direction::kRecvonly));
+ mSessionOff->AddTransceiver(
+ JsepTransceiver(SdpMediaSection::kVideo, mUuidGen,
+ SdpDirectionAttribute::Direction::kRecvonly));
+
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer, CHECK_SUCCESS);
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer, CHECK_SUCCESS);
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+
+ ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
+ ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);
+
+ auto newOffererTransceivers = GetTransceivers(*mSessionOff);
+ auto newAnswererTransceivers = GetTransceivers(*mSessionAns);
+
+ ASSERT_LE(2U, newOffererTransceivers.size());
+ RemoveLastN(newOffererTransceivers, 2);
+ ASSERT_TRUE(Equals(origOffererTransceivers, newOffererTransceivers));
+
+ ASSERT_LE(2U, newAnswererTransceivers.size());
+ RemoveLastN(newAnswererTransceivers, 2);
+ ASSERT_TRUE(Equals(origAnswererTransceivers, newAnswererTransceivers));
+}
+
+TEST_P(JsepSessionTest, RenegotiationBothAddTrack) {
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ OfferAnswer();
+
+ ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
+ ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);
+
+ std::vector<JsepTransceiver> origOffererTransceivers =
+ GetTransceivers(*mSessionOff);
+ std::vector<JsepTransceiver> origAnswererTransceivers =
+ GetTransceivers(*mSessionAns);
+
+ std::vector<SdpMediaSection::MediaType> extraTypes;
+ extraTypes.push_back(SdpMediaSection::kAudio);
+ extraTypes.push_back(SdpMediaSection::kVideo);
+ AddTracks(*mSessionAns, extraTypes);
+ AddTracks(*mSessionOff, extraTypes);
+ types.insert(types.end(), extraTypes.begin(), extraTypes.end());
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
+ ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);
+
+ auto newOffererTransceivers = GetTransceivers(*mSessionOff);
+ auto newAnswererTransceivers = GetTransceivers(*mSessionAns);
+
+ ASSERT_LE(2U, newOffererTransceivers.size());
+ RemoveLastN(newOffererTransceivers, 2);
+ ASSERT_TRUE(Equals(origOffererTransceivers, newOffererTransceivers));
+
+ ASSERT_LE(2U, newAnswererTransceivers.size());
+ RemoveLastN(newAnswererTransceivers, 2);
+ ASSERT_TRUE(Equals(origAnswererTransceivers, newAnswererTransceivers));
+}
+
+TEST_P(JsepSessionTest, RenegotiationBothAddTracksToExistingStream) {
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+ if (GetParam() == "datachannel") {
+ return;
+ }
+
+ OfferAnswer();
+
+ auto oHasStream = HasMediaStream(GetLocalTracks(*mSessionOff));
+ auto aHasStream = HasMediaStream(GetLocalTracks(*mSessionAns));
+ ASSERT_EQ(oHasStream, !GetLocalUniqueStreamIds(*mSessionOff).empty());
+ ASSERT_EQ(aHasStream, !GetLocalUniqueStreamIds(*mSessionAns).empty());
+ ASSERT_EQ(aHasStream, !GetRemoteUniqueStreamIds(*mSessionOff).empty());
+ ASSERT_EQ(oHasStream, !GetRemoteUniqueStreamIds(*mSessionAns).empty());
+
+ auto firstOffId = GetFirstLocalStreamId(*mSessionOff);
+ auto firstAnsId = GetFirstLocalStreamId(*mSessionAns);
+
+ auto offererTransceivers = GetTransceivers(*mSessionOff);
+ auto answererTransceivers = GetTransceivers(*mSessionAns);
+
+ std::vector<SdpMediaSection::MediaType> extraTypes;
+ extraTypes.push_back(SdpMediaSection::kAudio);
+ extraTypes.push_back(SdpMediaSection::kVideo);
+ AddTracksToStream(*mSessionOff, firstOffId, extraTypes);
+ AddTracksToStream(*mSessionAns, firstAnsId, extraTypes);
+ types.insert(types.end(), extraTypes.begin(), extraTypes.end());
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ oHasStream = HasMediaStream(GetLocalTracks(*mSessionOff));
+ aHasStream = HasMediaStream(GetLocalTracks(*mSessionAns));
+
+ ASSERT_EQ(oHasStream, !GetLocalUniqueStreamIds(*mSessionOff).empty());
+ ASSERT_EQ(aHasStream, !GetLocalUniqueStreamIds(*mSessionAns).empty());
+ ASSERT_EQ(aHasStream, !GetRemoteUniqueStreamIds(*mSessionOff).empty());
+ ASSERT_EQ(oHasStream, !GetRemoteUniqueStreamIds(*mSessionAns).empty());
+ if (oHasStream) {
+ ASSERT_STREQ(firstOffId.c_str(),
+ GetFirstLocalStreamId(*mSessionOff).c_str());
+ }
+ if (aHasStream) {
+ ASSERT_STREQ(firstAnsId.c_str(),
+ GetFirstLocalStreamId(*mSessionAns).c_str());
+
+ auto oHasStream = HasMediaStream(GetLocalTracks(*mSessionOff));
+ auto aHasStream = HasMediaStream(GetLocalTracks(*mSessionAns));
+ ASSERT_EQ(oHasStream, !GetLocalUniqueStreamIds(*mSessionOff).empty());
+ ASSERT_EQ(aHasStream, !GetLocalUniqueStreamIds(*mSessionAns).empty());
+ }
+}
+
+// The JSEP draft explicitly forbids changing the msid on an m-section, but
+// that is a bug.
+TEST_P(JsepSessionTest, RenegotiationOffererChangesMsid) {
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ OfferAnswer();
+
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+
+ JsepTransceiver* transceiver = GetNegotiatedTransceiver(*mSessionOff, 0);
+ ASSERT_TRUE(transceiver);
+ if (transceiver->GetMediaType() == SdpMediaSection::kApplication) {
+ return;
+ }
+ std::string streamId = transceiver->mSendTrack.GetStreamIds()[0];
+ std::string msidToReplace("a=msid:");
+ msidToReplace += streamId;
+ size_t msidOffset = offer.find(msidToReplace);
+ ASSERT_NE(std::string::npos, msidOffset);
+ offer.replace(msidOffset, msidToReplace.size(), "a=msid:foo");
+
+ SetRemoteOffer(offer);
+ transceiver = GetNegotiatedTransceiver(*mSessionAns, 0);
+ ASSERT_EQ("foo", transceiver->mRecvTrack.GetStreamIds()[0]);
+
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+ SetRemoteAnswer(answer);
+}
+
+// The JSEP draft explicitly forbids changing the msid on an m-section, but
+// that is a bug.
+TEST_P(JsepSessionTest, RenegotiationAnswererChangesMsid) {
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ OfferAnswer();
+
+ JsepTransceiver* transceiver = GetNegotiatedTransceiver(*mSessionOff, 0);
+ ASSERT_TRUE(transceiver);
+ if (transceiver->GetMediaType() == SdpMediaSection::kApplication) {
+ return;
+ }
+
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+
+ transceiver = GetNegotiatedTransceiver(*mSessionAns, 0);
+ ASSERT_TRUE(transceiver);
+ if (transceiver->GetMediaType() == SdpMediaSection::kApplication) {
+ return;
+ }
+ std::string streamId = transceiver->mSendTrack.GetStreamIds()[0];
+ std::string msidToReplace("a=msid:");
+ msidToReplace += streamId;
+ size_t msidOffset = answer.find(msidToReplace);
+ ASSERT_NE(std::string::npos, msidOffset);
+ answer.replace(msidOffset, msidToReplace.size(), "a=msid:foo");
+
+ SetRemoteAnswer(answer);
+
+ transceiver = GetNegotiatedTransceiver(*mSessionOff, 0);
+ ASSERT_EQ("foo", transceiver->mRecvTrack.GetStreamIds()[0]);
+}
+
+TEST_P(JsepSessionTest, RenegotiationOffererStopsTransceiver) {
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+ if (types.back() == SdpMediaSection::kApplication) {
+ return;
+ }
+
+ OfferAnswer();
+
+ std::vector<JsepTransceiver> origOffererTransceivers =
+ GetTransceivers(*mSessionOff);
+ std::vector<JsepTransceiver> origAnswererTransceivers =
+ GetTransceivers(*mSessionAns);
+
+ JsepTransceiver lastTransceiver = GetTransceivers(*mSessionOff).back();
+ // Avoid bundle transport side effects; don't stop the BUNDLE-tag!
+ lastTransceiver.Stop();
+ mSessionOff->SetTransceiver(lastTransceiver);
+ JsepTrack removedTrack(lastTransceiver.mSendTrack);
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ // Last m-section should be disabled
+ auto offer = GetParsedLocalDescription(*mSessionOff);
+ const SdpMediaSection* msection =
+ &offer->GetMediaSection(offer->GetMediaSectionCount() - 1);
+ ASSERT_TRUE(msection);
+ ValidateDisabledMSection(msection);
+
+ // Last m-section should be disabled
+ auto answer = GetParsedLocalDescription(*mSessionAns);
+ msection = &answer->GetMediaSection(answer->GetMediaSectionCount() - 1);
+ ASSERT_TRUE(msection);
+ ValidateDisabledMSection(msection);
+
+ auto newOffererTransceivers = GetTransceivers(*mSessionOff);
+ auto newAnswererTransceivers = GetTransceivers(*mSessionAns);
+
+ ASSERT_EQ(origOffererTransceivers.size(), newOffererTransceivers.size());
+
+ ASSERT_FALSE(origOffererTransceivers.back().IsStopped());
+ ASSERT_TRUE(newOffererTransceivers.back().IsStopped());
+
+ ASSERT_FALSE(origAnswererTransceivers.back().IsStopped());
+ ASSERT_TRUE(newAnswererTransceivers.back().IsStopped());
+ RemoveLastN(origOffererTransceivers, 1); // Ignore this one
+ RemoveLastN(newOffererTransceivers, 1); // Ignore this one
+ RemoveLastN(origAnswererTransceivers, 1); // Ignore this one
+ RemoveLastN(newAnswererTransceivers, 1); // Ignore this one
+
+ ASSERT_TRUE(Equals(origOffererTransceivers, newOffererTransceivers));
+ ASSERT_TRUE(Equals(origAnswererTransceivers, newAnswererTransceivers));
+}
+
+TEST_P(JsepSessionTest, RenegotiationAnswererStopsTransceiver) {
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+ if (types.back() == SdpMediaSection::kApplication) {
+ return;
+ }
+
+ OfferAnswer();
+
+ std::vector<JsepTransceiver> origOffererTransceivers =
+ GetTransceivers(*mSessionOff);
+ std::vector<JsepTransceiver> origAnswererTransceivers =
+ GetTransceivers(*mSessionAns);
+
+ // Avoid bundle transport side effects; don't stop the BUNDLE-tag!
+ GetTransceivers(*mSessionAns).back().Stop();
+ JsepTrack removedTrack(GetTransceivers(*mSessionAns).back().mSendTrack);
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ // Last m-section should be sendrecv
+ auto offer = GetParsedLocalDescription(*mSessionOff);
+ const SdpMediaSection* msection =
+ &offer->GetMediaSection(offer->GetMediaSectionCount() - 1);
+ ASSERT_TRUE(msection);
+ ASSERT_TRUE(msection->IsReceiving());
+ ASSERT_TRUE(msection->IsSending());
+
+ // Last m-section should be disabled
+ auto answer = GetParsedLocalDescription(*mSessionAns);
+ msection = &answer->GetMediaSection(answer->GetMediaSectionCount() - 1);
+ ASSERT_TRUE(msection);
+ ValidateDisabledMSection(msection);
+
+ auto newOffererTransceivers = GetTransceivers(*mSessionOff);
+ auto newAnswererTransceivers = GetTransceivers(*mSessionAns);
+
+ ASSERT_EQ(origOffererTransceivers.size(), newOffererTransceivers.size());
+
+ ASSERT_FALSE(origOffererTransceivers.back().IsStopped());
+ ASSERT_TRUE(newOffererTransceivers.back().IsStopped());
+ ASSERT_FALSE(origAnswererTransceivers.back().IsStopped());
+ ASSERT_TRUE(newAnswererTransceivers.back().IsStopped());
+ RemoveLastN(origOffererTransceivers, 1); // Ignore this one
+ RemoveLastN(newOffererTransceivers, 1); // Ignore this one
+ RemoveLastN(origAnswererTransceivers, 1); // Ignore this one
+ RemoveLastN(newAnswererTransceivers, 1); // Ignore this one
+
+ ASSERT_TRUE(Equals(origOffererTransceivers, newOffererTransceivers));
+ ASSERT_TRUE(Equals(origAnswererTransceivers, newAnswererTransceivers));
+}
+
+TEST_P(JsepSessionTest, RenegotiationBothStopSameTransceiver) {
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+ if (types.back() == SdpMediaSection::kApplication) {
+ return;
+ }
+
+ OfferAnswer();
+
+ std::vector<JsepTransceiver> origOffererTransceivers =
+ GetTransceivers(*mSessionOff);
+ std::vector<JsepTransceiver> origAnswererTransceivers =
+ GetTransceivers(*mSessionAns);
+
+ // Avoid bundle transport side effects; don't stop the BUNDLE-tag!
+ GetTransceivers(*mSessionOff).back().Stop();
+ JsepTrack removedTrackOffer(GetTransceivers(*mSessionOff).back().mSendTrack);
+ GetTransceivers(*mSessionAns).back().Stop();
+ JsepTrack removedTrackAnswer(GetTransceivers(*mSessionAns).back().mSendTrack);
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ // Last m-section should be disabled
+ auto offer = GetParsedLocalDescription(*mSessionOff);
+ const SdpMediaSection* msection =
+ &offer->GetMediaSection(offer->GetMediaSectionCount() - 1);
+ ASSERT_TRUE(msection);
+ ValidateDisabledMSection(msection);
+
+ // Last m-section should be disabled
+ auto answer = GetParsedLocalDescription(*mSessionAns);
+ msection = &answer->GetMediaSection(answer->GetMediaSectionCount() - 1);
+ ASSERT_TRUE(msection);
+ ValidateDisabledMSection(msection);
+
+ auto newOffererTransceivers = GetTransceivers(*mSessionOff);
+ auto newAnswererTransceivers = GetTransceivers(*mSessionAns);
+
+ ASSERT_EQ(origOffererTransceivers.size(), newOffererTransceivers.size());
+
+ ASSERT_FALSE(origOffererTransceivers.back().IsStopped());
+ ASSERT_TRUE(newOffererTransceivers.back().IsStopped());
+ ASSERT_FALSE(origAnswererTransceivers.back().IsStopped());
+ ASSERT_TRUE(newAnswererTransceivers.back().IsStopped());
+ RemoveLastN(origOffererTransceivers, 1); // Ignore this one
+ RemoveLastN(newOffererTransceivers, 1); // Ignore this one
+ RemoveLastN(origAnswererTransceivers, 1); // Ignore this one
+ RemoveLastN(newAnswererTransceivers, 1); // Ignore this one
+
+ ASSERT_TRUE(Equals(origOffererTransceivers, newOffererTransceivers));
+ ASSERT_TRUE(Equals(origAnswererTransceivers, newAnswererTransceivers));
+}
+
+TEST_P(JsepSessionTest, RenegotiationBothStopTransceiverThenAddTrack) {
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+ if (types.back() == SdpMediaSection::kApplication) {
+ return;
+ }
+
+ SdpMediaSection::MediaType removedType = types.back();
+
+ OfferAnswer();
+
+ // Avoid bundle transport side effects; don't stop the BUNDLE-tag!
+ GetTransceivers(*mSessionOff).back().Stop();
+ JsepTrack removedTrackOffer(GetTransceivers(*mSessionOff).back().mSendTrack);
+ GetTransceivers(*mSessionOff).back().Stop();
+ JsepTrack removedTrackAnswer(GetTransceivers(*mSessionOff).back().mSendTrack);
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ std::vector<JsepTransceiver> origOffererTransceivers =
+ GetTransceivers(*mSessionOff);
+ std::vector<JsepTransceiver> origAnswererTransceivers =
+ GetTransceivers(*mSessionAns);
+
+ std::vector<SdpMediaSection::MediaType> extraTypes;
+ extraTypes.push_back(removedType);
+ AddTracks(*mSessionAns, extraTypes);
+ AddTracks(*mSessionOff, extraTypes);
+ types.insert(types.end(), extraTypes.begin(), extraTypes.end());
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ auto newOffererTransceivers = GetTransceivers(*mSessionOff);
+ auto newAnswererTransceivers = GetTransceivers(*mSessionAns);
+
+ ASSERT_EQ(origOffererTransceivers.size() + 1, newOffererTransceivers.size());
+ ASSERT_EQ(origAnswererTransceivers.size() + 1,
+ newAnswererTransceivers.size());
+
+ // Ensure that the m-section was re-used; no gaps
+ ASSERT_EQ(origOffererTransceivers.back().GetLevel(),
+ newOffererTransceivers.back().GetLevel());
+
+ ASSERT_EQ(origAnswererTransceivers.back().GetLevel(),
+ newAnswererTransceivers.back().GetLevel());
+}
+
+TEST_P(JsepSessionTest, RenegotiationBothStopTransceiverDifferentMsection) {
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ if (types.size() < 2) {
+ return;
+ }
+
+ if (GetTransceivers(*mSessionOff)[0].GetMediaType() ==
+ SdpMediaSection::kApplication ||
+ GetTransceivers(*mSessionOff)[1].GetMediaType() ==
+ SdpMediaSection::kApplication) {
+ return;
+ }
+
+ OfferAnswer();
+
+ GetTransceivers(*mSessionOff)[0].Stop();
+ GetTransceivers(*mSessionOff)[1].Stop();
+
+ OfferAnswer(CHECK_SUCCESS);
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[0].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[1].IsStopped());
+}
+
+TEST_P(JsepSessionTest, RenegotiationOffererChangesStreamId) {
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ if (GetTransceivers(*mSessionOff)[0].GetMediaType() ==
+ SdpMediaSection::kApplication) {
+ return;
+ }
+
+ OfferAnswer();
+
+ GetTransceivers(*mSessionOff)[0].mSendTrack.UpdateStreamIds(
+ std::vector<std::string>(1, "newstream"));
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ ASSERT_EQ("newstream",
+ GetTransceivers(*mSessionAns)[0].mRecvTrack.GetStreamIds()[0]);
+}
+
+TEST_P(JsepSessionTest, RenegotiationAnswererChangesStreamId) {
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ if (GetTransceivers(*mSessionOff)[0].GetMediaType() ==
+ SdpMediaSection::kApplication) {
+ return;
+ }
+
+ OfferAnswer();
+
+ GetTransceivers(*mSessionAns)[0].mSendTrack.UpdateStreamIds(
+ std::vector<std::string>(1, "newstream"));
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ ASSERT_EQ("newstream",
+ GetTransceivers(*mSessionOff)[0].mRecvTrack.GetStreamIds()[0]);
+}
+
+// Tests whether auto-assigned remote msids (ie; what happens when the other
+// side doesn't use msid attributes) are stable across renegotiation.
+TEST_P(JsepSessionTest, RenegotiationAutoAssignedMsidIsStable) {
+ AddTracks(*mSessionOff);
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+ AddTracks(*mSessionAns);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+
+ DisableMsid(&answer);
+
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+
+ std::vector<JsepTransceiver> origOffererTransceivers =
+ GetTransceivers(*mSessionOff);
+ std::vector<JsepTransceiver> origAnswererTransceivers =
+ GetTransceivers(*mSessionAns);
+
+ ASSERT_EQ(origOffererTransceivers.size(), origAnswererTransceivers.size());
+ for (size_t i = 0; i < origOffererTransceivers.size(); ++i) {
+ ASSERT_FALSE(IsNull(origOffererTransceivers[i].mRecvTrack));
+ ASSERT_FALSE(IsNull(origAnswererTransceivers[i].mSendTrack));
+ // These should not match since we've monkeyed with the msid
+ ASSERT_NE(origOffererTransceivers[i].mRecvTrack.GetStreamIds(),
+ origAnswererTransceivers[i].mSendTrack.GetStreamIds());
+ }
+
+ offer = CreateOffer();
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+ answer = CreateAnswer();
+ SetLocalAnswer(answer);
+
+ DisableMsid(&answer);
+
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+
+ auto newOffererTransceivers = GetTransceivers(*mSessionOff);
+
+ ASSERT_TRUE(Equals(origOffererTransceivers, newOffererTransceivers));
+}
+
+TEST_P(JsepSessionTest, RenegotiationOffererDisablesTelephoneEvent) {
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+ OfferAnswer();
+
+ // check all the audio tracks to make sure they have 2 codecs (109 and 101),
+ // and dtmf is enabled on all audio tracks
+ std::vector<JsepTrack> tracks;
+ for (const auto& transceiver : GetTransceivers(*mSessionOff)) {
+ tracks.push_back(transceiver.mSendTrack);
+ tracks.push_back(transceiver.mRecvTrack);
+ }
+
+ for (const JsepTrack& track : tracks) {
+ if (track.GetMediaType() != SdpMediaSection::kAudio) {
+ continue;
+ }
+ const JsepTrackNegotiatedDetails* details = track.GetNegotiatedDetails();
+ ASSERT_EQ(1U, details->GetEncodingCount());
+ const JsepTrackEncoding& encoding = details->GetEncoding(0);
+ ASSERT_EQ(5U, encoding.GetCodecs().size());
+ ASSERT_TRUE(encoding.HasFormat("109"));
+ ASSERT_TRUE(encoding.HasFormat("101"));
+ for (const auto& codec : encoding.GetCodecs()) {
+ ASSERT_TRUE(codec);
+ // we can cast here because we've already checked for audio track
+ const JsepAudioCodecDescription* audioCodec =
+ static_cast<const JsepAudioCodecDescription*>(codec.get());
+ ASSERT_TRUE(audioCodec->mDtmfEnabled);
+ }
+ }
+
+ std::string offer = CreateOffer();
+ ReplaceInSdp(&offer, "8 101", "8");
+ ReplaceInSdp(&offer, "a=fmtp:101 0-15\r\n", "");
+ ReplaceInSdp(&offer, "a=rtpmap:101 telephone-event/8000/1\r\n", "");
+ std::cerr << "modified OFFER: " << offer << std::endl;
+
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+ SetRemoteAnswer(answer);
+
+ // check all the audio tracks to make sure they have 1 codec (109),
+ // and dtmf is disabled on all audio tracks
+ tracks.clear();
+ for (const auto& transceiver : GetTransceivers(*mSessionOff)) {
+ tracks.push_back(transceiver.mSendTrack);
+ tracks.push_back(transceiver.mRecvTrack);
+ }
+
+ for (const JsepTrack& track : tracks) {
+ if (track.GetMediaType() != SdpMediaSection::kAudio) {
+ continue;
+ }
+ const JsepTrackNegotiatedDetails* details = track.GetNegotiatedDetails();
+ ASSERT_EQ(1U, details->GetEncodingCount());
+ const JsepTrackEncoding& encoding = details->GetEncoding(0);
+ ASSERT_EQ(4U, encoding.GetCodecs().size());
+ ASSERT_TRUE(encoding.HasFormat("109"));
+ // we can cast here because we've already checked for audio track
+ const JsepAudioCodecDescription* audioCodec =
+ static_cast<const JsepAudioCodecDescription*>(
+ encoding.GetCodecs()[0].get());
+ ASSERT_TRUE(audioCodec);
+ ASSERT_FALSE(audioCodec->mDtmfEnabled);
+ }
+}
+
+// Tests behavior when the answerer does not use msid in the initial exchange,
+// but does on renegotiation.
+TEST_P(JsepSessionTest, RenegotiationAnswererEnablesMsid) {
+ AddTracks(*mSessionOff);
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+ AddTracks(*mSessionAns);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+
+ DisableMsid(&answer);
+
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+
+ std::vector<JsepTransceiver> origOffererTransceivers =
+ GetTransceivers(*mSessionOff);
+ std::vector<JsepTransceiver> origAnswererTransceivers =
+ GetTransceivers(*mSessionAns);
+
+ offer = CreateOffer();
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+ answer = CreateAnswer();
+ SetLocalAnswer(answer);
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+
+ auto newOffererTransceivers = GetTransceivers(*mSessionOff);
+
+ ASSERT_EQ(origOffererTransceivers.size(), newOffererTransceivers.size());
+ for (size_t i = 0; i < origOffererTransceivers.size(); ++i) {
+ ASSERT_EQ(origOffererTransceivers[i].mRecvTrack.GetMediaType(),
+ newOffererTransceivers[i].mRecvTrack.GetMediaType());
+
+ ASSERT_TRUE(Equals(origOffererTransceivers[i].mSendTrack,
+ newOffererTransceivers[i].mSendTrack));
+ ASSERT_TRUE(Equals(origOffererTransceivers[i].mTransport,
+ newOffererTransceivers[i].mTransport));
+
+ if (origOffererTransceivers[i].mRecvTrack.GetMediaType() ==
+ SdpMediaSection::kApplication) {
+ ASSERT_TRUE(Equals(origOffererTransceivers[i].mRecvTrack,
+ newOffererTransceivers[i].mRecvTrack));
+ } else {
+ // This should be the only difference
+ ASSERT_FALSE(Equals(origOffererTransceivers[i].mRecvTrack,
+ newOffererTransceivers[i].mRecvTrack));
+ }
+ }
+}
+
+TEST_P(JsepSessionTest, RenegotiationAnswererDisablesMsid) {
+ AddTracks(*mSessionOff);
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+ AddTracks(*mSessionAns);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+
+ std::vector<JsepTransceiver> origOffererTransceivers =
+ GetTransceivers(*mSessionOff);
+ std::vector<JsepTransceiver> origAnswererTransceivers =
+ GetTransceivers(*mSessionAns);
+
+ offer = CreateOffer();
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+ answer = CreateAnswer();
+ SetLocalAnswer(answer);
+
+ DisableMsid(&answer);
+
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+
+ auto newOffererTransceivers = GetTransceivers(*mSessionOff);
+
+ ASSERT_EQ(origOffererTransceivers.size(), newOffererTransceivers.size());
+ for (size_t i = 0; i < origOffererTransceivers.size(); ++i) {
+ ASSERT_EQ(origOffererTransceivers[i].mRecvTrack.GetMediaType(),
+ newOffererTransceivers[i].mRecvTrack.GetMediaType());
+
+ ASSERT_TRUE(Equals(origOffererTransceivers[i].mSendTrack,
+ newOffererTransceivers[i].mSendTrack));
+ ASSERT_TRUE(Equals(origOffererTransceivers[i].mTransport,
+ newOffererTransceivers[i].mTransport));
+
+ if (origOffererTransceivers[i].mRecvTrack.GetMediaType() ==
+ SdpMediaSection::kApplication) {
+ ASSERT_TRUE(Equals(origOffererTransceivers[i].mRecvTrack,
+ newOffererTransceivers[i].mRecvTrack));
+ } else {
+ // This should be the only difference
+ ASSERT_FALSE(Equals(origOffererTransceivers[i].mRecvTrack,
+ newOffererTransceivers[i].mRecvTrack));
+ }
+ }
+}
+
+// Tests behavior when offerer does not use bundle on the initial offer/answer,
+// but does on renegotiation.
+TEST_P(JsepSessionTest, RenegotiationOffererEnablesBundle) {
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ if (types.size() < 2) {
+ // No bundle will happen here.
+ return;
+ }
+
+ std::string offer = CreateOffer();
+
+ DisableBundle(&offer);
+
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+ SetRemoteAnswer(answer);
+
+ std::vector<JsepTransceiver> origOffererTransceivers =
+ GetTransceivers(*mSessionOff);
+ std::vector<JsepTransceiver> origAnswererTransceivers =
+ GetTransceivers(*mSessionAns);
+
+ OfferAnswer();
+
+ auto newOffererTransceivers = GetTransceivers(*mSessionOff);
+ auto newAnswererTransceivers = GetTransceivers(*mSessionAns);
+
+ ASSERT_EQ(newOffererTransceivers.size(), newAnswererTransceivers.size());
+ ASSERT_EQ(origOffererTransceivers.size(), newOffererTransceivers.size());
+ ASSERT_EQ(origAnswererTransceivers.size(), newAnswererTransceivers.size());
+
+ for (size_t i = 0; i < newOffererTransceivers.size(); ++i) {
+ // No bundle initially
+ ASSERT_FALSE(origOffererTransceivers[i].HasBundleLevel());
+ ASSERT_FALSE(origAnswererTransceivers[i].HasBundleLevel());
+ if (i != 0) {
+ ASSERT_FALSE(Equals(origOffererTransceivers[0].mTransport,
+ origOffererTransceivers[i].mTransport));
+ ASSERT_FALSE(Equals(origAnswererTransceivers[0].mTransport,
+ origAnswererTransceivers[i].mTransport));
+ }
+
+ // Verify that bundle worked after renegotiation
+ ASSERT_TRUE(newOffererTransceivers[i].HasBundleLevel());
+ ASSERT_TRUE(newAnswererTransceivers[i].HasBundleLevel());
+ ASSERT_TRUE(Equals(newOffererTransceivers[0].mTransport,
+ newOffererTransceivers[i].mTransport));
+ ASSERT_TRUE(Equals(newAnswererTransceivers[0].mTransport,
+ newAnswererTransceivers[i].mTransport));
+ }
+}
+
+TEST_P(JsepSessionTest, RenegotiationOffererDisablesBundleTransport) {
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ if (types.size() < 2) {
+ return;
+ }
+
+ OfferAnswer();
+
+ auto stopped = GetTransceiverByLevel(*mSessionOff, 0);
+ stopped->Stop();
+ mSessionOff->SetTransceiver(*stopped);
+
+ std::vector<JsepTransceiver> origOffererTransceivers =
+ GetTransceivers(*mSessionOff);
+ std::vector<JsepTransceiver> origAnswererTransceivers =
+ GetTransceivers(*mSessionAns);
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ auto newOffererTransceivers = GetTransceivers(*mSessionOff);
+ auto newAnswererTransceivers = GetTransceivers(*mSessionAns);
+
+ ASSERT_EQ(newOffererTransceivers.size(), newAnswererTransceivers.size());
+ ASSERT_EQ(origOffererTransceivers.size(), newOffererTransceivers.size());
+ ASSERT_EQ(origAnswererTransceivers.size(), newAnswererTransceivers.size());
+
+ Maybe<JsepTransceiver> ot0 = GetTransceiverByLevel(newOffererTransceivers, 0);
+ Maybe<JsepTransceiver> at0 =
+ GetTransceiverByLevel(newAnswererTransceivers, 0);
+ ASSERT_FALSE(ot0->HasBundleLevel());
+ ASSERT_FALSE(at0->HasBundleLevel());
+
+ ASSERT_FALSE(
+ Equals(ot0->mTransport,
+ GetTransceiverByLevel(origOffererTransceivers, 0)->mTransport));
+ ASSERT_FALSE(
+ Equals(at0->mTransport,
+ GetTransceiverByLevel(origAnswererTransceivers, 0)->mTransport));
+
+ ASSERT_EQ(0U, ot0->mTransport.mComponents);
+ ASSERT_EQ(0U, at0->mTransport.mComponents);
+
+ for (size_t i = 1; i < types.size() - 1; ++i) {
+ Maybe<JsepTransceiver> ot =
+ GetTransceiverByLevel(newOffererTransceivers, i);
+ Maybe<JsepTransceiver> at =
+ GetTransceiverByLevel(newAnswererTransceivers, i);
+ ASSERT_TRUE(ot->HasBundleLevel());
+ ASSERT_TRUE(at->HasBundleLevel());
+ ASSERT_EQ(1U, ot->BundleLevel());
+ ASSERT_EQ(1U, at->BundleLevel());
+ ASSERT_FALSE(Equals(ot0->mTransport, ot->mTransport));
+ ASSERT_FALSE(Equals(at0->mTransport, at->mTransport));
+ }
+}
+
+TEST_P(JsepSessionTest, RenegotiationAnswererDisablesBundleTransport) {
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ if (types.size() < 2) {
+ return;
+ }
+
+ OfferAnswer();
+
+ std::vector<JsepTransceiver> origOffererTransceivers =
+ GetTransceivers(*mSessionOff);
+ std::vector<JsepTransceiver> origAnswererTransceivers =
+ GetTransceivers(*mSessionAns);
+
+ auto stopped = GetTransceiverByLevel(*mSessionAns, 0);
+ stopped->Stop();
+ mSessionAns->SetTransceiver(*stopped);
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ auto newOffererTransceivers = GetTransceivers(*mSessionOff);
+ auto newAnswererTransceivers = GetTransceivers(*mSessionAns);
+
+ ASSERT_EQ(newOffererTransceivers.size(), newAnswererTransceivers.size());
+ ASSERT_EQ(origOffererTransceivers.size(), newOffererTransceivers.size());
+ ASSERT_EQ(origAnswererTransceivers.size(), newAnswererTransceivers.size());
+
+ Maybe<JsepTransceiver> ot0 = GetTransceiverByLevel(newOffererTransceivers, 0);
+ Maybe<JsepTransceiver> at0 =
+ GetTransceiverByLevel(newAnswererTransceivers, 0);
+ ASSERT_FALSE(ot0->HasBundleLevel());
+ ASSERT_FALSE(at0->HasBundleLevel());
+
+ ASSERT_FALSE(
+ Equals(ot0->mTransport,
+ GetTransceiverByLevel(origOffererTransceivers, 0)->mTransport));
+ ASSERT_FALSE(
+ Equals(at0->mTransport,
+ GetTransceiverByLevel(origAnswererTransceivers, 0)->mTransport));
+
+ ASSERT_EQ(0U, ot0->mTransport.mComponents);
+ ASSERT_EQ(0U, at0->mTransport.mComponents);
+
+ // With bundle-policy "balanced", this ends up being somewhat complicated.
+ // The first m-section of each type is _not_ marked bundle-only,
+ // but subsequent m-sections of that type are. This means that if there is
+ // more than one type, there is more than one transport, which means there's
+ // a fallback when the bundle-tag is disabled (the first such transport). We
+ // should be using that fallback when it exists.
+ Maybe<size_t> fallbackTransport;
+ for (size_t level = 1; level != types.size(); ++level) {
+ if (types[level] != types[0]) {
+ fallbackTransport = Some(level);
+ break;
+ }
+ }
+
+ for (size_t i = 1; i < newOffererTransceivers.size(); ++i) {
+ auto ot = GetTransceiverByLevel(newOffererTransceivers, i);
+ auto at = GetTransceiverByLevel(newAnswererTransceivers, i);
+ // If there is no fallback, the bundle level will be left pointing at the
+ // dead transport at index 0.
+ size_t expectedBundleLevel = fallbackTransport.valueOr(0);
+ auto otWithTransport =
+ GetTransceiverByLevel(newOffererTransceivers, expectedBundleLevel);
+ auto atWithTransport =
+ GetTransceiverByLevel(newAnswererTransceivers, expectedBundleLevel);
+ ASSERT_TRUE(ot->HasBundleLevel());
+ ASSERT_TRUE(at->HasBundleLevel());
+ ASSERT_EQ(expectedBundleLevel, ot->BundleLevel());
+ ASSERT_EQ(expectedBundleLevel, at->BundleLevel());
+ // TODO: When creating an answer where we have rejected the bundle
+ // transport, we do not do a good job of creating a sensible SDP. Mainly,
+ // when we remove the rejected mid from the bundle group, we can leave a
+ // bundle-only mid as the first one when others are available.
+ ASSERT_TRUE(Equals(otWithTransport->mTransport, ot->mTransport));
+ ASSERT_TRUE(Equals(atWithTransport->mTransport, at->mTransport));
+ }
+}
+
+TEST_P(JsepSessionTest, ParseRejectsBadMediaFormat) {
+ AddTracks(*mSessionOff);
+ if (types.front() == SdpMediaSection::MediaType::kApplication) {
+ return;
+ }
+ std::string offer = CreateOffer();
+ UniquePtr<Sdp> munge(Parse(offer));
+ SdpMediaSection& mediaSection = munge->GetMediaSection(0);
+ mediaSection.AddCodec("75", "DummyFormatVal", 8000, 1);
+ std::string sdpString = munge->ToString();
+ JsepSession::Result result =
+ mSessionOff->SetLocalDescription(kJsepSdpOffer, sdpString);
+ ASSERT_EQ(dom::PCError::OperationError, *result.mError);
+}
+
+TEST_P(JsepSessionTest, FullCallWithCandidates) {
+ AddTracks(*mSessionOff);
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+ mOffCandidates->Gather(*mSessionOff);
+
+ UniquePtr<Sdp> localOffer(
+ Parse(mSessionOff->GetLocalDescription(kJsepDescriptionPending)));
+ for (size_t i = 0; i < localOffer->GetMediaSectionCount(); ++i) {
+ std::string id = GetTransportId(*mSessionOff, i);
+ bool bundleOnly =
+ localOffer->GetMediaSection(i).GetAttributeList().HasAttribute(
+ SdpAttribute::kBundleOnlyAttribute);
+ mOffCandidates->CheckRtpCandidates(
+ !bundleOnly, localOffer->GetMediaSection(i), id,
+ "Local offer after gathering should have RTP candidates "
+ "(unless bundle-only)");
+ mOffCandidates->CheckDefaultRtpCandidate(
+ !bundleOnly, localOffer->GetMediaSection(i), id,
+ "Local offer after gathering should have a default RTP candidate "
+ "(unless bundle-only)");
+ mOffCandidates->CheckRtcpCandidates(
+ !bundleOnly && types[i] != SdpMediaSection::kApplication,
+ localOffer->GetMediaSection(i), id,
+ "Local offer after gathering should have RTCP candidates "
+ "(unless m=application or bundle-only)");
+ mOffCandidates->CheckDefaultRtcpCandidate(
+ !bundleOnly && types[i] != SdpMediaSection::kApplication,
+ localOffer->GetMediaSection(i), id,
+ "Local offer after gathering should have a default RTCP candidate "
+ "(unless m=application or bundle-only)");
+ CheckEndOfCandidates(
+ !bundleOnly, localOffer->GetMediaSection(i),
+ "Local offer after gathering should have an end-of-candidates "
+ "(unless bundle-only)");
+ }
+
+ SetRemoteOffer(offer);
+ mOffCandidates->Trickle(*mSessionAns);
+
+ UniquePtr<Sdp> remoteOffer(
+ Parse(mSessionAns->GetRemoteDescription(kJsepDescriptionPending)));
+ for (size_t i = 0; i < remoteOffer->GetMediaSectionCount(); ++i) {
+ std::string id = GetTransportId(*mSessionOff, i);
+ bool bundleOnly =
+ remoteOffer->GetMediaSection(i).GetAttributeList().HasAttribute(
+ SdpAttribute::kBundleOnlyAttribute);
+ mOffCandidates->CheckRtpCandidates(
+ !bundleOnly, remoteOffer->GetMediaSection(i), id,
+ "Remote offer after trickle should have RTP candidates "
+ "(unless bundle-only)");
+ mOffCandidates->CheckDefaultRtpCandidate(
+ false, remoteOffer->GetMediaSection(i), id,
+ "Remote offer after trickle should not have a default RTP candidate.");
+ mOffCandidates->CheckRtcpCandidates(
+ !bundleOnly && types[i] != SdpMediaSection::kApplication,
+ remoteOffer->GetMediaSection(i), id,
+ "Remote offer after trickle should have RTCP candidates "
+ "(unless m=application or bundle-only)");
+ mOffCandidates->CheckDefaultRtcpCandidate(
+ false, remoteOffer->GetMediaSection(i), id,
+ "Remote offer after trickle should not have a default RTCP candidate.");
+ CheckEndOfCandidates(
+ true, remoteOffer->GetMediaSection(i),
+ "Remote offer after trickle should have an end-of-candidates.");
+ }
+
+ AddTracks(*mSessionAns);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+ // This will gather candidates that mSessionAns knows it doesn't need.
+ // They should not be present in the SDP.
+ mAnsCandidates->Gather(*mSessionAns);
+
+ UniquePtr<Sdp> localAnswer(
+ Parse(mSessionAns->GetLocalDescription(kJsepDescriptionCurrent)));
+ std::string id0 = GetTransportId(*mSessionAns, 0);
+ for (size_t i = 0; i < localAnswer->GetMediaSectionCount(); ++i) {
+ std::string id = GetTransportId(*mSessionAns, i);
+ mAnsCandidates->CheckRtpCandidates(
+ i == 0, localAnswer->GetMediaSection(i), id,
+ "Local answer after gathering should have RTP candidates on level 0.");
+ mAnsCandidates->CheckDefaultRtpCandidate(
+ true, localAnswer->GetMediaSection(i), id0,
+ "Local answer after gathering should have a default RTP candidate "
+ "on all levels that matches transport level 0.");
+ mAnsCandidates->CheckRtcpCandidates(
+ false, localAnswer->GetMediaSection(i), id,
+ "Local answer after gathering should not have RTCP candidates "
+ "(because we're answering with rtcp-mux)");
+ mAnsCandidates->CheckDefaultRtcpCandidate(
+ false, localAnswer->GetMediaSection(i), id,
+ "Local answer after gathering should not have a default RTCP candidate "
+ "(because we're answering with rtcp-mux)");
+ CheckEndOfCandidates(
+ i == 0, localAnswer->GetMediaSection(i),
+ "Local answer after gathering should have an end-of-candidates only for"
+ " level 0.");
+ }
+
+ SetRemoteAnswer(answer);
+ mAnsCandidates->Trickle(*mSessionOff);
+
+ UniquePtr<Sdp> remoteAnswer(
+ Parse(mSessionOff->GetRemoteDescription(kJsepDescriptionCurrent)));
+ for (size_t i = 0; i < remoteAnswer->GetMediaSectionCount(); ++i) {
+ std::string id = GetTransportId(*mSessionAns, i);
+ mAnsCandidates->CheckRtpCandidates(
+ i == 0, remoteAnswer->GetMediaSection(i), id,
+ "Remote answer after trickle should have RTP candidates on level 0.");
+ mAnsCandidates->CheckDefaultRtpCandidate(
+ false, remoteAnswer->GetMediaSection(i), id,
+ "Remote answer after trickle should not have a default RTP candidate.");
+ mAnsCandidates->CheckRtcpCandidates(
+ false, remoteAnswer->GetMediaSection(i), id,
+ "Remote answer after trickle should not have RTCP candidates "
+ "(because we're answering with rtcp-mux)");
+ mAnsCandidates->CheckDefaultRtcpCandidate(
+ false, remoteAnswer->GetMediaSection(i), id,
+ "Remote answer after trickle should not have a default RTCP "
+ "candidate.");
+ CheckEndOfCandidates(
+ true, remoteAnswer->GetMediaSection(i),
+ "Remote answer after trickle should have an end-of-candidates.");
+ }
+}
+
+TEST_P(JsepSessionTest, RenegotiationWithCandidates) {
+ AddTracks(*mSessionOff);
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+ mOffCandidates->Gather(*mSessionOff);
+ SetRemoteOffer(offer);
+ mOffCandidates->Trickle(*mSessionAns);
+ AddTracks(*mSessionAns);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+ mAnsCandidates->Gather(*mSessionAns);
+ SetRemoteAnswer(answer);
+ mAnsCandidates->Trickle(*mSessionOff);
+
+ offer = CreateOffer();
+ SetLocalOffer(offer);
+
+ UniquePtr<Sdp> parsedOffer(Parse(offer));
+ std::string id0 = GetTransportId(*mSessionOff, 0);
+ for (size_t i = 0; i < parsedOffer->GetMediaSectionCount(); ++i) {
+ std::string id = GetTransportId(*mSessionOff, i);
+ mOffCandidates->CheckRtpCandidates(
+ i == 0, parsedOffer->GetMediaSection(i), id,
+ "Local reoffer before gathering should have RTP candidates on level 0"
+ " only.");
+ mOffCandidates->CheckDefaultRtpCandidate(
+ i == 0, parsedOffer->GetMediaSection(i), id0,
+ "Local reoffer before gathering should have a default RTP candidate "
+ "on level 0 only.");
+ mOffCandidates->CheckRtcpCandidates(
+ false, parsedOffer->GetMediaSection(i), id,
+ "Local reoffer before gathering should not have RTCP candidates.");
+ mOffCandidates->CheckDefaultRtcpCandidate(
+ false, parsedOffer->GetMediaSection(i), id,
+ "Local reoffer before gathering should not have a default RTCP "
+ "candidate.");
+ CheckEndOfCandidates(
+ i == 0, parsedOffer->GetMediaSection(i),
+ "Local reoffer before gathering should have an end-of-candidates "
+ "(level 0 only)");
+ }
+
+ // mSessionAns should generate a reoffer that is similar
+ std::string otherOffer;
+ JsepOfferOptions defaultOptions;
+ JsepSession::Result result =
+ mSessionAns->CreateOffer(defaultOptions, &otherOffer);
+ ASSERT_FALSE(result.mError.isSome());
+ parsedOffer = Parse(otherOffer);
+ id0 = GetTransportId(*mSessionAns, 0);
+ for (size_t i = 0; i < parsedOffer->GetMediaSectionCount(); ++i) {
+ std::string id = GetTransportId(*mSessionAns, i);
+ mAnsCandidates->CheckRtpCandidates(
+ i == 0, parsedOffer->GetMediaSection(i), id,
+ "Local reoffer before gathering should have RTP candidates on level 0"
+ " only. (previous answerer)");
+ mAnsCandidates->CheckDefaultRtpCandidate(
+ i == 0, parsedOffer->GetMediaSection(i), id0,
+ "Local reoffer before gathering should have a default RTP candidate "
+ "on level 0 only. (previous answerer)");
+ mAnsCandidates->CheckRtcpCandidates(
+ false, parsedOffer->GetMediaSection(i), id,
+ "Local reoffer before gathering should not have RTCP candidates."
+ " (previous answerer)");
+ mAnsCandidates->CheckDefaultRtcpCandidate(
+ false, parsedOffer->GetMediaSection(i), id,
+ "Local reoffer before gathering should not have a default RTCP "
+ "candidate. (previous answerer)");
+ CheckEndOfCandidates(
+ i == 0, parsedOffer->GetMediaSection(i),
+ "Local reoffer before gathering should have an end-of-candidates "
+ "(level 0 only)");
+ }
+
+ // Ok, let's continue with the renegotiation
+ SetRemoteOffer(offer);
+
+ // PeerConnection will not re-gather for RTP, but it will for RTCP in case
+ // the answerer decides to turn off rtcp-mux.
+ if (types[0] != SdpMediaSection::kApplication) {
+ mOffCandidates->Gather(*mSessionOff, GetTransportId(*mSessionOff, 0), RTCP);
+ }
+
+ // Since the remaining levels were bundled, PeerConnection will re-gather for
+ // both RTP and RTCP, in case the answerer rejects bundle, provided
+ // bundle-only isn't being used.
+ UniquePtr<Sdp> localOffer(
+ Parse(mSessionOff->GetLocalDescription(kJsepDescriptionPending)));
+ for (size_t level = 1; level < types.size(); ++level) {
+ std::string id = GetTransportId(*mSessionOff, level);
+ bool bundleOnly =
+ localOffer->GetMediaSection(level).GetAttributeList().HasAttribute(
+ SdpAttribute::kBundleOnlyAttribute);
+ if (!id.empty() && !bundleOnly) {
+ mOffCandidates->Gather(*mSessionOff, id, RTP);
+ if (types[level] != SdpMediaSection::kApplication) {
+ mOffCandidates->Gather(*mSessionOff, id, RTCP);
+ }
+ }
+ }
+ mOffCandidates->FinishGathering(*mSessionOff);
+ localOffer = Parse(mSessionOff->GetLocalDescription(kJsepDescriptionPending));
+
+ mOffCandidates->Trickle(*mSessionAns);
+
+ for (size_t i = 0; i < localOffer->GetMediaSectionCount(); ++i) {
+ std::string id = GetTransportId(*mSessionOff, i);
+ bool bundleOnly =
+ localOffer->GetMediaSection(i).GetAttributeList().HasAttribute(
+ SdpAttribute::kBundleOnlyAttribute);
+ mOffCandidates->CheckRtpCandidates(
+ !bundleOnly, localOffer->GetMediaSection(i), id,
+ "Local reoffer after gathering should have RTP candidates "
+ "(unless bundle-only)");
+ mOffCandidates->CheckDefaultRtpCandidate(
+ !bundleOnly, localOffer->GetMediaSection(i), id,
+ "Local reoffer after gathering should have a default RTP candidate "
+ "(unless bundle-only)");
+ mOffCandidates->CheckRtcpCandidates(
+ !bundleOnly && (types[i] != SdpMediaSection::kApplication),
+ localOffer->GetMediaSection(i), id,
+ "Local reoffer after gathering should have RTCP candidates "
+ "(unless m=application or bundle-only)");
+ mOffCandidates->CheckDefaultRtcpCandidate(
+ !bundleOnly && (types[i] != SdpMediaSection::kApplication),
+ localOffer->GetMediaSection(i), id,
+ "Local reoffer after gathering should have a default RTCP candidate "
+ "(unless m=application or bundle-only)");
+ CheckEndOfCandidates(
+ !bundleOnly, localOffer->GetMediaSection(i),
+ "Local reoffer after gathering should have an end-of-candidates "
+ "(unless bundle-only)");
+ }
+
+ UniquePtr<Sdp> remoteOffer(
+ Parse(mSessionAns->GetRemoteDescription(kJsepDescriptionPending)));
+ for (size_t i = 0; i < remoteOffer->GetMediaSectionCount(); ++i) {
+ bool bundleOnly =
+ remoteOffer->GetMediaSection(i).GetAttributeList().HasAttribute(
+ SdpAttribute::kBundleOnlyAttribute);
+ std::string id = GetTransportId(*mSessionOff, i);
+ mOffCandidates->CheckRtpCandidates(
+ !bundleOnly, remoteOffer->GetMediaSection(i), id,
+ "Remote reoffer after trickle should have RTP candidates "
+ "(unless bundle-only)");
+ mOffCandidates->CheckDefaultRtpCandidate(
+ i == 0, remoteOffer->GetMediaSection(i), id,
+ "Remote reoffer should have a default RTP candidate on level 0 "
+ "(because it was gathered last offer/answer).");
+ mOffCandidates->CheckRtcpCandidates(
+ !bundleOnly && types[i] != SdpMediaSection::kApplication,
+ remoteOffer->GetMediaSection(i), id,
+ "Remote reoffer after trickle should have RTCP candidates "
+ "(unless m=application or bundle-only)");
+ mOffCandidates->CheckDefaultRtcpCandidate(
+ false, remoteOffer->GetMediaSection(i), id,
+ "Remote reoffer should not have a default RTCP candidate.");
+ CheckEndOfCandidates(true, remoteOffer->GetMediaSection(i),
+ "Remote reoffer should have an end-of-candidates.");
+ }
+
+ answer = CreateAnswer();
+ SetLocalAnswer(answer);
+ SetRemoteAnswer(answer);
+ // No candidates should be gathered at the answerer, but default candidates
+ // should be set.
+ mAnsCandidates->FinishGathering(*mSessionAns);
+
+ UniquePtr<Sdp> localAnswer(
+ Parse(mSessionAns->GetLocalDescription(kJsepDescriptionCurrent)));
+ id0 = GetTransportId(*mSessionAns, 0);
+ for (size_t i = 0; i < localAnswer->GetMediaSectionCount(); ++i) {
+ std::string id = GetTransportId(*mSessionAns, 0);
+ mAnsCandidates->CheckRtpCandidates(
+ i == 0, localAnswer->GetMediaSection(i), id,
+ "Local reanswer after gathering should have RTP candidates on level "
+ "0.");
+ mAnsCandidates->CheckDefaultRtpCandidate(
+ true, localAnswer->GetMediaSection(i), id0,
+ "Local reanswer after gathering should have a default RTP candidate "
+ "on all levels that matches transport level 0.");
+ mAnsCandidates->CheckRtcpCandidates(
+ false, localAnswer->GetMediaSection(i), id,
+ "Local reanswer after gathering should not have RTCP candidates "
+ "(because we're reanswering with rtcp-mux)");
+ mAnsCandidates->CheckDefaultRtcpCandidate(
+ false, localAnswer->GetMediaSection(i), id,
+ "Local reanswer after gathering should not have a default RTCP "
+ "candidate (because we're reanswering with rtcp-mux)");
+ CheckEndOfCandidates(
+ i == 0, localAnswer->GetMediaSection(i),
+ "Local reanswer after gathering should have an end-of-candidates only "
+ "for level 0.");
+ }
+
+ UniquePtr<Sdp> remoteAnswer(
+ Parse(mSessionOff->GetRemoteDescription(kJsepDescriptionCurrent)));
+ for (size_t i = 0; i < localAnswer->GetMediaSectionCount(); ++i) {
+ std::string id = GetTransportId(*mSessionAns, 0);
+ mAnsCandidates->CheckRtpCandidates(
+ i == 0, remoteAnswer->GetMediaSection(i), id,
+ "Remote reanswer after trickle should have RTP candidates on level 0.");
+ mAnsCandidates->CheckDefaultRtpCandidate(
+ i == 0, remoteAnswer->GetMediaSection(i), id,
+ "Remote reanswer should have a default RTP candidate on level 0 "
+ "(because it was gathered last offer/answer).");
+ mAnsCandidates->CheckRtcpCandidates(
+ false, remoteAnswer->GetMediaSection(i), id,
+ "Remote reanswer after trickle should not have RTCP candidates "
+ "(because we're reanswering with rtcp-mux)");
+ mAnsCandidates->CheckDefaultRtcpCandidate(
+ false, remoteAnswer->GetMediaSection(i), id,
+ "Remote reanswer after trickle should not have a default RTCP "
+ "candidate.");
+ CheckEndOfCandidates(i == 0, remoteAnswer->GetMediaSection(i),
+ "Remote reanswer after trickle should have an "
+ "end-of-candidates on level 0 only.");
+ }
+}
+
+TEST_P(JsepSessionTest, RenegotiationAnswererSendonly) {
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+ OfferAnswer();
+
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+
+ UniquePtr<Sdp> parsedAnswer(Parse(answer));
+ for (size_t i = 0; i < parsedAnswer->GetMediaSectionCount(); ++i) {
+ SdpMediaSection& msection = parsedAnswer->GetMediaSection(i);
+ if (msection.GetMediaType() != SdpMediaSection::kApplication) {
+ msection.SetReceiving(false);
+ }
+ }
+
+ answer = parsedAnswer->ToString();
+
+ SetRemoteAnswer(answer);
+
+ for (const JsepTrack& track : GetLocalTracks(*mSessionOff)) {
+ if (track.GetMediaType() != SdpMediaSection::kApplication) {
+ ASSERT_FALSE(track.GetActive());
+ }
+ }
+
+ ASSERT_EQ(types.size(), GetTransceivers(*mSessionOff).size());
+}
+
+TEST_P(JsepSessionTest, RenegotiationAnswererInactive) {
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+ OfferAnswer();
+
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+
+ UniquePtr<Sdp> parsedAnswer(Parse(answer));
+ for (size_t i = 0; i < parsedAnswer->GetMediaSectionCount(); ++i) {
+ SdpMediaSection& msection = parsedAnswer->GetMediaSection(i);
+ if (msection.GetMediaType() != SdpMediaSection::kApplication) {
+ msection.SetReceiving(false);
+ msection.SetSending(false);
+ }
+ }
+
+ answer = parsedAnswer->ToString();
+
+ SetRemoteAnswer(answer, CHECK_SUCCESS); // Won't have answerer tracks
+
+ for (const JsepTrack& track : GetLocalTracks(*mSessionOff)) {
+ if (track.GetMediaType() != SdpMediaSection::kApplication) {
+ ASSERT_FALSE(track.GetActive());
+ }
+ }
+
+ ASSERT_EQ(types.size(), GetTransceivers(*mSessionOff).size());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ Variants, JsepSessionTest,
+ ::testing::Values("audio", "video", "datachannel", "audio,video",
+ "video,audio", "audio,datachannel", "video,datachannel",
+ "video,audio,datachannel", "audio,video,datachannel",
+ "datachannel,audio", "datachannel,video",
+ "datachannel,audio,video", "datachannel,video,audio",
+ "audio,datachannel,video", "video,datachannel,audio",
+ "audio,audio", "video,video", "audio,audio,video",
+ "audio,video,video", "audio,audio,video,video",
+ "audio,audio,video,video,datachannel"));
+
+// offerToReceiveXxx variants
+
+TEST_F(JsepSessionTest, OfferAnswerRecvOnlyLines) {
+ mSessionOff->AddTransceiver(JsepTransceiver(
+ SdpMediaSection::kAudio, mUuidGen, SdpDirectionAttribute::kRecvonly));
+ mSessionOff->AddTransceiver(JsepTransceiver(
+ SdpMediaSection::kVideo, mUuidGen, SdpDirectionAttribute::kRecvonly));
+ mSessionOff->AddTransceiver(JsepTransceiver(
+ SdpMediaSection::kVideo, mUuidGen, SdpDirectionAttribute::kRecvonly));
+ std::string offer = CreateOffer();
+
+ UniquePtr<Sdp> parsedOffer(Parse(offer));
+ ASSERT_TRUE(!!parsedOffer);
+
+ ASSERT_EQ(3U, parsedOffer->GetMediaSectionCount());
+ ASSERT_EQ(SdpMediaSection::kAudio,
+ parsedOffer->GetMediaSection(0).GetMediaType());
+ ASSERT_EQ(SdpDirectionAttribute::kRecvonly,
+ parsedOffer->GetMediaSection(0).GetAttributeList().GetDirection());
+ ASSERT_TRUE(parsedOffer->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kSsrcAttribute));
+
+ ASSERT_EQ(SdpMediaSection::kVideo,
+ parsedOffer->GetMediaSection(1).GetMediaType());
+ ASSERT_EQ(SdpDirectionAttribute::kRecvonly,
+ parsedOffer->GetMediaSection(1).GetAttributeList().GetDirection());
+ ASSERT_TRUE(parsedOffer->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kSsrcAttribute));
+
+ ASSERT_EQ(SdpMediaSection::kVideo,
+ parsedOffer->GetMediaSection(2).GetMediaType());
+ ASSERT_EQ(SdpDirectionAttribute::kRecvonly,
+ parsedOffer->GetMediaSection(2).GetAttributeList().GetDirection());
+ ASSERT_TRUE(parsedOffer->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kSsrcAttribute));
+
+ ASSERT_TRUE(parsedOffer->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpMuxAttribute));
+ ASSERT_TRUE(parsedOffer->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpMuxAttribute));
+ ASSERT_TRUE(parsedOffer->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpMuxAttribute));
+
+ SetLocalOffer(offer, CHECK_SUCCESS);
+
+ AddTracks(*mSessionAns, "audio,video");
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+
+ std::string answer = CreateAnswer();
+ UniquePtr<Sdp> parsedAnswer(Parse(answer));
+
+ ASSERT_EQ(3U, parsedAnswer->GetMediaSectionCount());
+ ASSERT_EQ(SdpMediaSection::kAudio,
+ parsedAnswer->GetMediaSection(0).GetMediaType());
+ ASSERT_EQ(SdpDirectionAttribute::kSendonly,
+ parsedAnswer->GetMediaSection(0).GetAttributeList().GetDirection());
+ ASSERT_EQ(SdpMediaSection::kVideo,
+ parsedAnswer->GetMediaSection(1).GetMediaType());
+ ASSERT_EQ(SdpDirectionAttribute::kSendonly,
+ parsedAnswer->GetMediaSection(1).GetAttributeList().GetDirection());
+ ASSERT_EQ(SdpMediaSection::kVideo,
+ parsedAnswer->GetMediaSection(2).GetMediaType());
+ ASSERT_EQ(SdpDirectionAttribute::kInactive,
+ parsedAnswer->GetMediaSection(2).GetAttributeList().GetDirection());
+
+ SetLocalAnswer(answer, CHECK_SUCCESS);
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+
+ std::vector<JsepTransceiver> transceivers(GetTransceivers(*mSessionOff));
+ ASSERT_EQ(3U, transceivers.size());
+ for (const auto& transceiver : transceivers) {
+ const auto& msection = parsedOffer->GetMediaSection(transceiver.GetLevel());
+ const auto& ssrcs = msection.GetAttributeList().GetSsrc().mSsrcs;
+ ASSERT_EQ(1U, ssrcs.size());
+ }
+}
+
+TEST_F(JsepSessionTest, OfferAnswerSendOnlyLines) {
+ AddTracks(*mSessionOff, "audio,video,video");
+
+ SetDirection(*mSessionOff, 0, SdpDirectionAttribute::kSendonly);
+ SetDirection(*mSessionOff, 2, SdpDirectionAttribute::kSendonly);
+ std::string offer = CreateOffer();
+
+ UniquePtr<Sdp> outputSdp(Parse(offer));
+ ASSERT_TRUE(!!outputSdp);
+
+ ASSERT_EQ(3U, outputSdp->GetMediaSectionCount());
+ ASSERT_EQ(SdpMediaSection::kAudio,
+ outputSdp->GetMediaSection(0).GetMediaType());
+ ASSERT_EQ(SdpDirectionAttribute::kSendonly,
+ outputSdp->GetMediaSection(0).GetAttributeList().GetDirection());
+ ASSERT_EQ(SdpMediaSection::kVideo,
+ outputSdp->GetMediaSection(1).GetMediaType());
+ ASSERT_EQ(SdpDirectionAttribute::kSendrecv,
+ outputSdp->GetMediaSection(1).GetAttributeList().GetDirection());
+ ASSERT_EQ(SdpMediaSection::kVideo,
+ outputSdp->GetMediaSection(2).GetMediaType());
+ ASSERT_EQ(SdpDirectionAttribute::kSendonly,
+ outputSdp->GetMediaSection(2).GetAttributeList().GetDirection());
+
+ ASSERT_TRUE(outputSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpMuxAttribute));
+ ASSERT_TRUE(outputSdp->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpMuxAttribute));
+ ASSERT_TRUE(outputSdp->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpMuxAttribute));
+
+ SetLocalOffer(offer, CHECK_SUCCESS);
+
+ AddTracks(*mSessionAns, "audio,video");
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+
+ std::string answer = CreateAnswer();
+ outputSdp = Parse(answer);
+
+ ASSERT_EQ(3U, outputSdp->GetMediaSectionCount());
+ ASSERT_EQ(SdpMediaSection::kAudio,
+ outputSdp->GetMediaSection(0).GetMediaType());
+ ASSERT_EQ(SdpDirectionAttribute::kRecvonly,
+ outputSdp->GetMediaSection(0).GetAttributeList().GetDirection());
+ ASSERT_EQ(SdpMediaSection::kVideo,
+ outputSdp->GetMediaSection(1).GetMediaType());
+ ASSERT_EQ(SdpDirectionAttribute::kSendrecv,
+ outputSdp->GetMediaSection(1).GetAttributeList().GetDirection());
+ ASSERT_EQ(SdpMediaSection::kVideo,
+ outputSdp->GetMediaSection(2).GetMediaType());
+ ASSERT_EQ(SdpDirectionAttribute::kRecvonly,
+ outputSdp->GetMediaSection(2).GetAttributeList().GetDirection());
+}
+
+TEST_F(JsepSessionTest, OfferToReceiveAudioNotUsed) {
+ mSessionOff->AddTransceiver(JsepTransceiver(
+ SdpMediaSection::kAudio, mUuidGen, SdpDirectionAttribute::kRecvonly));
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ UniquePtr<Sdp> offer(
+ Parse(mSessionOff->GetLocalDescription(kJsepDescriptionCurrent)));
+ ASSERT_TRUE(offer.get());
+ ASSERT_EQ(1U, offer->GetMediaSectionCount());
+ ASSERT_EQ(SdpMediaSection::kAudio, offer->GetMediaSection(0).GetMediaType());
+ ASSERT_EQ(SdpDirectionAttribute::kRecvonly,
+ offer->GetMediaSection(0).GetAttributeList().GetDirection());
+
+ UniquePtr<Sdp> answer(
+ Parse(mSessionAns->GetLocalDescription(kJsepDescriptionCurrent)));
+ ASSERT_TRUE(answer.get());
+ ASSERT_EQ(1U, answer->GetMediaSectionCount());
+ ASSERT_EQ(SdpMediaSection::kAudio, answer->GetMediaSection(0).GetMediaType());
+ ASSERT_EQ(SdpDirectionAttribute::kInactive,
+ answer->GetMediaSection(0).GetAttributeList().GetDirection());
+}
+
+TEST_F(JsepSessionTest, OfferToReceiveVideoNotUsed) {
+ mSessionOff->AddTransceiver(JsepTransceiver(
+ SdpMediaSection::kVideo, mUuidGen, SdpDirectionAttribute::kRecvonly));
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ UniquePtr<Sdp> offer(
+ Parse(mSessionOff->GetLocalDescription(kJsepDescriptionCurrent)));
+ ASSERT_TRUE(offer.get());
+ ASSERT_EQ(1U, offer->GetMediaSectionCount());
+ ASSERT_EQ(SdpMediaSection::kVideo, offer->GetMediaSection(0).GetMediaType());
+ ASSERT_EQ(SdpDirectionAttribute::kRecvonly,
+ offer->GetMediaSection(0).GetAttributeList().GetDirection());
+
+ UniquePtr<Sdp> answer(
+ Parse(mSessionAns->GetLocalDescription(kJsepDescriptionCurrent)));
+ ASSERT_TRUE(answer.get());
+ ASSERT_EQ(1U, answer->GetMediaSectionCount());
+ ASSERT_EQ(SdpMediaSection::kVideo, answer->GetMediaSection(0).GetMediaType());
+ ASSERT_EQ(SdpDirectionAttribute::kInactive,
+ answer->GetMediaSection(0).GetAttributeList().GetDirection());
+}
+
+TEST_F(JsepSessionTest, CreateOfferNoDatachannelDefault) {
+ JsepTransceiver audio(SdpMediaSection::kAudio, mUuidGen);
+ audio.mSendTrack.UpdateStreamIds(
+ std::vector<std::string>(1, "offerer_stream"));
+ mSessionOff->AddTransceiver(audio);
+
+ JsepTransceiver video(SdpMediaSection::kVideo, mUuidGen);
+ video.mSendTrack.UpdateStreamIds(
+ std::vector<std::string>(1, "offerer_stream"));
+ mSessionOff->AddTransceiver(video);
+
+ std::string offer = CreateOffer();
+
+ UniquePtr<Sdp> outputSdp(Parse(offer));
+ ASSERT_TRUE(!!outputSdp);
+
+ ASSERT_EQ(2U, outputSdp->GetMediaSectionCount());
+ ASSERT_EQ(SdpMediaSection::kAudio,
+ outputSdp->GetMediaSection(0).GetMediaType());
+ ASSERT_EQ(SdpMediaSection::kVideo,
+ outputSdp->GetMediaSection(1).GetMediaType());
+}
+
+TEST_F(JsepSessionTest, ValidateOfferedVideoCodecParams) {
+ types.push_back(SdpMediaSection::kAudio);
+ types.push_back(SdpMediaSection::kVideo);
+
+ JsepTransceiver audio(SdpMediaSection::kAudio, mUuidGen);
+ audio.mSendTrack.UpdateStreamIds(
+ std::vector<std::string>(1, "offerer_stream"));
+ mSessionOff->AddTransceiver(audio);
+
+ JsepTransceiver video(SdpMediaSection::kVideo, mUuidGen);
+ video.mSendTrack.UpdateStreamIds(
+ std::vector<std::string>(1, "offerer_stream"));
+ mSessionOff->AddTransceiver(video);
+
+ std::string offer = CreateOffer();
+
+ UniquePtr<Sdp> outputSdp(Parse(offer));
+ ASSERT_TRUE(!!outputSdp);
+
+ ASSERT_EQ(2U, outputSdp->GetMediaSectionCount());
+ const auto& video_section = outputSdp->GetMediaSection(1);
+ ASSERT_EQ(SdpMediaSection::kVideo, video_section.GetMediaType());
+ const auto& video_attrs = video_section.GetAttributeList();
+ ASSERT_EQ(SdpDirectionAttribute::kSendrecv, video_attrs.GetDirection());
+
+ ASSERT_EQ(10U, video_section.GetFormats().size());
+ ASSERT_EQ("120", video_section.GetFormats()[0]);
+ ASSERT_EQ("124", video_section.GetFormats()[1]);
+ ASSERT_EQ("121", video_section.GetFormats()[2]);
+ ASSERT_EQ("125", video_section.GetFormats()[3]);
+ ASSERT_EQ("126", video_section.GetFormats()[4]);
+ ASSERT_EQ("127", video_section.GetFormats()[5]);
+ ASSERT_EQ("97", video_section.GetFormats()[6]);
+ ASSERT_EQ("98", video_section.GetFormats()[7]);
+ ASSERT_EQ("123", video_section.GetFormats()[8]);
+ ASSERT_EQ("122", video_section.GetFormats()[9]);
+
+ // Validate rtpmap
+ ASSERT_TRUE(video_attrs.HasAttribute(SdpAttribute::kRtpmapAttribute));
+ auto& rtpmaps = video_attrs.GetRtpmap();
+ ASSERT_TRUE(rtpmaps.HasEntry("120"));
+ ASSERT_TRUE(rtpmaps.HasEntry("124"));
+ ASSERT_TRUE(rtpmaps.HasEntry("121"));
+ ASSERT_TRUE(rtpmaps.HasEntry("125"));
+ ASSERT_TRUE(rtpmaps.HasEntry("126"));
+ ASSERT_TRUE(rtpmaps.HasEntry("127"));
+ ASSERT_TRUE(rtpmaps.HasEntry("97"));
+ ASSERT_TRUE(rtpmaps.HasEntry("98"));
+ ASSERT_TRUE(rtpmaps.HasEntry("123"));
+ ASSERT_TRUE(rtpmaps.HasEntry("122"));
+
+ const auto& vp8_entry = rtpmaps.GetEntry("120");
+ const auto& vp8_rtx_entry = rtpmaps.GetEntry("124");
+ const auto& vp9_entry = rtpmaps.GetEntry("121");
+ const auto& vp9_rtx_entry = rtpmaps.GetEntry("125");
+ const auto& h264_1_entry = rtpmaps.GetEntry("126");
+ const auto& h264_1_rtx_entry = rtpmaps.GetEntry("127");
+ const auto& h264_0_entry = rtpmaps.GetEntry("97");
+ const auto& h264_0_rtx_entry = rtpmaps.GetEntry("98");
+ const auto& ulpfec_0_entry = rtpmaps.GetEntry("123");
+ const auto& red_0_entry = rtpmaps.GetEntry("122");
+
+ ASSERT_EQ("VP8", vp8_entry.name);
+ ASSERT_EQ("rtx", vp8_rtx_entry.name);
+ ASSERT_EQ("VP9", vp9_entry.name);
+ ASSERT_EQ("rtx", vp9_rtx_entry.name);
+ ASSERT_EQ("H264", h264_1_entry.name);
+ ASSERT_EQ("rtx", h264_1_rtx_entry.name);
+ ASSERT_EQ("H264", h264_0_entry.name);
+ ASSERT_EQ("rtx", h264_0_rtx_entry.name);
+ ASSERT_EQ("red", red_0_entry.name);
+ ASSERT_EQ("ulpfec", ulpfec_0_entry.name);
+
+ // Validate fmtps
+ ASSERT_TRUE(video_attrs.HasAttribute(SdpAttribute::kFmtpAttribute));
+ auto& fmtps = video_attrs.GetFmtp().mFmtps;
+
+ ASSERT_EQ(9U, fmtps.size());
+
+ // VP8
+ const SdpFmtpAttributeList::Parameters* vp8_params =
+ video_section.FindFmtp("120");
+ ASSERT_TRUE(vp8_params);
+ ASSERT_EQ(SdpRtpmapAttributeList::kVP8, vp8_params->codec_type);
+
+ auto& parsed_vp8_params =
+ *static_cast<const SdpFmtpAttributeList::VP8Parameters*>(vp8_params);
+
+ ASSERT_EQ((uint32_t)12288, parsed_vp8_params.max_fs);
+ ASSERT_EQ((uint32_t)60, parsed_vp8_params.max_fr);
+
+ // VP8 RTX
+ const SdpFmtpAttributeList::Parameters* vp8_rtx_params =
+ video_section.FindFmtp("124");
+ ASSERT_TRUE(vp8_rtx_params);
+ ASSERT_EQ(SdpRtpmapAttributeList::kRtx, vp8_rtx_params->codec_type);
+
+ const auto& parsed_vp8_rtx_params =
+ *static_cast<const SdpFmtpAttributeList::RtxParameters*>(vp8_rtx_params);
+
+ ASSERT_EQ((uint32_t)120, parsed_vp8_rtx_params.apt);
+
+ // VP9
+ const SdpFmtpAttributeList::Parameters* vp9_params =
+ video_section.FindFmtp("121");
+ ASSERT_TRUE(vp9_params);
+ ASSERT_EQ(SdpRtpmapAttributeList::kVP9, vp9_params->codec_type);
+
+ const auto& parsed_vp9_params =
+ *static_cast<const SdpFmtpAttributeList::VP8Parameters*>(vp9_params);
+
+ ASSERT_EQ((uint32_t)12288, parsed_vp9_params.max_fs);
+ ASSERT_EQ((uint32_t)60, parsed_vp9_params.max_fr);
+
+ // VP9 RTX
+ const SdpFmtpAttributeList::Parameters* vp9_rtx_params =
+ video_section.FindFmtp("125");
+ ASSERT_TRUE(vp9_rtx_params);
+ ASSERT_EQ(SdpRtpmapAttributeList::kRtx, vp9_rtx_params->codec_type);
+
+ const auto& parsed_vp9_rtx_params =
+ *static_cast<const SdpFmtpAttributeList::RtxParameters*>(vp9_rtx_params);
+ ASSERT_EQ((uint32_t)121, parsed_vp9_rtx_params.apt);
+
+ // H264 packetization mode 1
+ const SdpFmtpAttributeList::Parameters* h264_1_params =
+ video_section.FindFmtp("126");
+ ASSERT_TRUE(h264_1_params);
+ ASSERT_EQ(SdpRtpmapAttributeList::kH264, h264_1_params->codec_type);
+
+ const auto& parsed_h264_1_params =
+ *static_cast<const SdpFmtpAttributeList::H264Parameters*>(h264_1_params);
+
+ ASSERT_EQ((uint32_t)0x42e00d, parsed_h264_1_params.profile_level_id);
+ ASSERT_TRUE(parsed_h264_1_params.level_asymmetry_allowed);
+ ASSERT_EQ(1U, parsed_h264_1_params.packetization_mode);
+
+ // H264 packetization mode 1 RTX
+ const SdpFmtpAttributeList::Parameters* h264_1_rtx_params =
+ video_section.FindFmtp("127");
+ ASSERT_TRUE(h264_1_rtx_params);
+ ASSERT_EQ(SdpRtpmapAttributeList::kRtx, h264_1_rtx_params->codec_type);
+
+ const auto& parsed_h264_1_rtx_params =
+ *static_cast<const SdpFmtpAttributeList::RtxParameters*>(
+ h264_1_rtx_params);
+
+ ASSERT_EQ((uint32_t)126, parsed_h264_1_rtx_params.apt);
+
+ // H264 packetization mode 0
+ const SdpFmtpAttributeList::Parameters* h264_0_params =
+ video_section.FindFmtp("97");
+ ASSERT_TRUE(h264_0_params);
+ ASSERT_EQ(SdpRtpmapAttributeList::kH264, h264_0_params->codec_type);
+
+ const auto& parsed_h264_0_params =
+ *static_cast<const SdpFmtpAttributeList::H264Parameters*>(h264_0_params);
+
+ ASSERT_EQ((uint32_t)0x42e00d, parsed_h264_0_params.profile_level_id);
+ ASSERT_TRUE(parsed_h264_0_params.level_asymmetry_allowed);
+ ASSERT_EQ(0U, parsed_h264_0_params.packetization_mode);
+
+ // H264 packetization mode 0 RTX
+ const SdpFmtpAttributeList::Parameters* h264_0_rtx_params =
+ video_section.FindFmtp("98");
+ ASSERT_TRUE(h264_0_rtx_params);
+ ASSERT_EQ(SdpRtpmapAttributeList::kRtx, h264_0_rtx_params->codec_type);
+
+ const auto& parsed_h264_0_rtx_params =
+ *static_cast<const SdpFmtpAttributeList::RtxParameters*>(
+ h264_0_rtx_params);
+
+ ASSERT_EQ((uint32_t)97, parsed_h264_0_rtx_params.apt);
+
+ // red
+ const SdpFmtpAttributeList::Parameters* red_params =
+ video_section.FindFmtp("122");
+ ASSERT_TRUE(red_params);
+ ASSERT_EQ(SdpRtpmapAttributeList::kRed, red_params->codec_type);
+
+ const auto& parsed_red_params =
+ *static_cast<const SdpFmtpAttributeList::RedParameters*>(red_params);
+ ASSERT_EQ(5U, parsed_red_params.encodings.size());
+ ASSERT_EQ(120, parsed_red_params.encodings[0]);
+ ASSERT_EQ(121, parsed_red_params.encodings[1]);
+ ASSERT_EQ(126, parsed_red_params.encodings[2]);
+ ASSERT_EQ(97, parsed_red_params.encodings[3]);
+ ASSERT_EQ(123, parsed_red_params.encodings[4]);
+}
+
+TEST_F(JsepSessionTest, ValidateOfferedAudioCodecParams) {
+ types.push_back(SdpMediaSection::kAudio);
+ types.push_back(SdpMediaSection::kVideo);
+
+ JsepTransceiver audio(SdpMediaSection::kAudio, mUuidGen);
+ audio.mSendTrack.UpdateStreamIds(
+ std::vector<std::string>(1, "offerer_stream"));
+ mSessionOff->AddTransceiver(audio);
+
+ JsepTransceiver video(SdpMediaSection::kVideo, mUuidGen);
+ video.mSendTrack.UpdateStreamIds(
+ std::vector<std::string>(1, "offerer_stream"));
+ mSessionOff->AddTransceiver(video);
+
+ std::string offer = CreateOffer();
+
+ UniquePtr<Sdp> outputSdp(Parse(offer));
+ ASSERT_TRUE(!!outputSdp);
+
+ ASSERT_EQ(2U, outputSdp->GetMediaSectionCount());
+ auto& audio_section = outputSdp->GetMediaSection(0);
+ ASSERT_EQ(SdpMediaSection::kAudio, audio_section.GetMediaType());
+ auto& audio_attrs = audio_section.GetAttributeList();
+ ASSERT_EQ(SdpDirectionAttribute::kSendrecv, audio_attrs.GetDirection());
+ ASSERT_EQ(5U, audio_section.GetFormats().size());
+ ASSERT_EQ("109", audio_section.GetFormats()[0]);
+ ASSERT_EQ("9", audio_section.GetFormats()[1]);
+ ASSERT_EQ("0", audio_section.GetFormats()[2]);
+ ASSERT_EQ("8", audio_section.GetFormats()[3]);
+ ASSERT_EQ("101", audio_section.GetFormats()[4]);
+
+ // Validate rtpmap
+ ASSERT_TRUE(audio_attrs.HasAttribute(SdpAttribute::kRtpmapAttribute));
+ auto& rtpmaps = audio_attrs.GetRtpmap();
+ ASSERT_TRUE(rtpmaps.HasEntry("109"));
+ ASSERT_TRUE(rtpmaps.HasEntry("9"));
+ ASSERT_TRUE(rtpmaps.HasEntry("0"));
+ ASSERT_TRUE(rtpmaps.HasEntry("8"));
+ ASSERT_TRUE(rtpmaps.HasEntry("101"));
+
+ auto& opus_entry = rtpmaps.GetEntry("109");
+ auto& g722_entry = rtpmaps.GetEntry("9");
+ auto& pcmu_entry = rtpmaps.GetEntry("0");
+ auto& pcma_entry = rtpmaps.GetEntry("8");
+ auto& telephone_event_entry = rtpmaps.GetEntry("101");
+
+ ASSERT_EQ("opus", opus_entry.name);
+ ASSERT_EQ("G722", g722_entry.name);
+ ASSERT_EQ("PCMU", pcmu_entry.name);
+ ASSERT_EQ("PCMA", pcma_entry.name);
+ ASSERT_EQ("telephone-event", telephone_event_entry.name);
+
+ // Validate fmtps
+ ASSERT_TRUE(audio_attrs.HasAttribute(SdpAttribute::kFmtpAttribute));
+ auto& fmtps = audio_attrs.GetFmtp().mFmtps;
+
+ ASSERT_EQ(2U, fmtps.size());
+
+ // opus
+ const SdpFmtpAttributeList::Parameters* opus_params =
+ audio_section.FindFmtp("109");
+ ASSERT_TRUE(opus_params);
+ ASSERT_EQ(SdpRtpmapAttributeList::kOpus, opus_params->codec_type);
+
+ auto& parsed_opus_params =
+ *static_cast<const SdpFmtpAttributeList::OpusParameters*>(opus_params);
+
+ ASSERT_EQ((uint32_t)48000, parsed_opus_params.maxplaybackrate);
+ ASSERT_EQ((uint32_t)1, parsed_opus_params.stereo);
+ ASSERT_EQ((uint32_t)0, parsed_opus_params.useInBandFec);
+ ASSERT_EQ((uint32_t)0, parsed_opus_params.maxAverageBitrate);
+ ASSERT_EQ((uint32_t)0, parsed_opus_params.useDTX);
+ ASSERT_EQ((uint32_t)0, parsed_opus_params.useCbr);
+ ASSERT_EQ((uint32_t)0, parsed_opus_params.frameSizeMs);
+ ASSERT_EQ((uint32_t)0, parsed_opus_params.minFrameSizeMs);
+ ASSERT_EQ((uint32_t)0, parsed_opus_params.maxFrameSizeMs);
+
+ // dtmf
+ const SdpFmtpAttributeList::Parameters* dtmf_params =
+ audio_section.FindFmtp("101");
+ ASSERT_TRUE(dtmf_params);
+ ASSERT_EQ(SdpRtpmapAttributeList::kTelephoneEvent, dtmf_params->codec_type);
+
+ auto& parsed_dtmf_params =
+ *static_cast<const SdpFmtpAttributeList::TelephoneEventParameters*>(
+ dtmf_params);
+
+ ASSERT_EQ("0-15", parsed_dtmf_params.dtmfTones);
+}
+
+TEST_F(JsepSessionTest, ValidateNoFmtpLineForRedInOfferAndAnswer) {
+ types.push_back(SdpMediaSection::kAudio);
+ types.push_back(SdpMediaSection::kVideo);
+
+ AddTracksToStream(*mSessionOff, "offerer_stream", "audio,video");
+
+ std::string offer = CreateOffer();
+
+ // look for line with fmtp:122 and remove it
+ size_t start = offer.find("a=fmtp:122");
+ size_t end = offer.find("\r\n", start);
+ offer.replace(start, end + 2 - start, "");
+
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+
+ AddTracksToStream(*mSessionAns, "answerer_stream", "audio,video");
+
+ std::string answer = CreateAnswer();
+ // because parsing will throw out the malformed fmtp, make sure it is not
+ // in the answer sdp string
+ ASSERT_EQ(std::string::npos, answer.find("a=fmtp:122"));
+
+ UniquePtr<Sdp> outputSdp(Parse(answer));
+ ASSERT_TRUE(!!outputSdp);
+
+ ASSERT_EQ(2U, outputSdp->GetMediaSectionCount());
+ auto& video_section = outputSdp->GetMediaSection(1);
+ ASSERT_EQ(SdpMediaSection::kVideo, video_section.GetMediaType());
+ auto& video_attrs = video_section.GetAttributeList();
+ ASSERT_EQ(SdpDirectionAttribute::kSendrecv, video_attrs.GetDirection());
+
+ ASSERT_EQ(10U, video_section.GetFormats().size());
+ ASSERT_EQ("120", video_section.GetFormats()[0]);
+ ASSERT_EQ("124", video_section.GetFormats()[1]);
+ ASSERT_EQ("121", video_section.GetFormats()[2]);
+ ASSERT_EQ("125", video_section.GetFormats()[3]);
+ ASSERT_EQ("126", video_section.GetFormats()[4]);
+ ASSERT_EQ("127", video_section.GetFormats()[5]);
+ ASSERT_EQ("97", video_section.GetFormats()[6]);
+ ASSERT_EQ("98", video_section.GetFormats()[7]);
+ ASSERT_EQ("123", video_section.GetFormats()[8]);
+ ASSERT_EQ("122", video_section.GetFormats()[9]);
+
+ // Validate rtpmap
+ ASSERT_TRUE(video_attrs.HasAttribute(SdpAttribute::kRtpmapAttribute));
+ auto& rtpmaps = video_attrs.GetRtpmap();
+ ASSERT_TRUE(rtpmaps.HasEntry("120"));
+ ASSERT_TRUE(rtpmaps.HasEntry("124"));
+ ASSERT_TRUE(rtpmaps.HasEntry("121"));
+ ASSERT_TRUE(rtpmaps.HasEntry("125"));
+ ASSERT_TRUE(rtpmaps.HasEntry("126"));
+ ASSERT_TRUE(rtpmaps.HasEntry("127"));
+ ASSERT_TRUE(rtpmaps.HasEntry("97"));
+ ASSERT_TRUE(rtpmaps.HasEntry("98"));
+ ASSERT_TRUE(rtpmaps.HasEntry("123"));
+ ASSERT_TRUE(rtpmaps.HasEntry("122"));
+
+ // Validate fmtps
+ ASSERT_TRUE(video_attrs.HasAttribute(SdpAttribute::kFmtpAttribute));
+ auto& fmtps = video_attrs.GetFmtp().mFmtps;
+
+ ASSERT_EQ(8U, fmtps.size());
+ ASSERT_EQ("126", fmtps[0].format);
+ ASSERT_EQ("97", fmtps[1].format);
+ ASSERT_EQ("120", fmtps[2].format);
+ ASSERT_EQ("124", fmtps[3].format);
+ ASSERT_EQ("121", fmtps[4].format);
+ ASSERT_EQ("125", fmtps[5].format);
+ ASSERT_EQ("127", fmtps[6].format);
+ ASSERT_EQ("98", fmtps[7].format);
+
+ SetLocalAnswer(answer);
+ SetRemoteAnswer(answer);
+
+ auto offerTransceivers = GetTransceivers(*mSessionOff);
+ ASSERT_EQ(2U, offerTransceivers.size());
+ ASSERT_FALSE(IsNull(offerTransceivers[1].mSendTrack));
+ ASSERT_FALSE(IsNull(offerTransceivers[1].mRecvTrack));
+ ASSERT_TRUE(offerTransceivers[1].mSendTrack.GetNegotiatedDetails());
+ ASSERT_TRUE(offerTransceivers[1].mRecvTrack.GetNegotiatedDetails());
+ ASSERT_EQ(6U, offerTransceivers[1]
+ .mSendTrack.GetNegotiatedDetails()
+ ->GetEncoding(0)
+ .GetCodecs()
+ .size());
+ ASSERT_EQ(6U, offerTransceivers[1]
+ .mRecvTrack.GetNegotiatedDetails()
+ ->GetEncoding(0)
+ .GetCodecs()
+ .size());
+
+ auto answerTransceivers = GetTransceivers(*mSessionAns);
+ ASSERT_EQ(2U, answerTransceivers.size());
+ ASSERT_FALSE(IsNull(answerTransceivers[1].mSendTrack));
+ ASSERT_FALSE(IsNull(answerTransceivers[1].mRecvTrack));
+ ASSERT_TRUE(answerTransceivers[1].mSendTrack.GetNegotiatedDetails());
+ ASSERT_TRUE(answerTransceivers[1].mRecvTrack.GetNegotiatedDetails());
+ ASSERT_EQ(6U, answerTransceivers[1]
+ .mSendTrack.GetNegotiatedDetails()
+ ->GetEncoding(0)
+ .GetCodecs()
+ .size());
+ ASSERT_EQ(6U, answerTransceivers[1]
+ .mRecvTrack.GetNegotiatedDetails()
+ ->GetEncoding(0)
+ .GetCodecs()
+ .size());
+}
+
+TEST_F(JsepSessionTest, ValidateAnsweredCodecParamsNoRed) {
+ // TODO(bug 1099351): Once fixed, we can allow red in this offer,
+ // which will also cause multiple codecs in answer. For now,
+ // red/ulpfec for video are behind a pref to mitigate potential for
+ // errors.
+ SetCodecEnabled(*mSessionOff, "red", false);
+ for (auto& codec : mSessionAns->Codecs()) {
+ if (codec->mName == "H264") {
+ JsepVideoCodecDescription* h264 =
+ static_cast<JsepVideoCodecDescription*>(codec.get());
+ h264->mProfileLevelId = 0x42a00d;
+ // Switch up the pts
+ if (h264->mDefaultPt == "126") {
+ h264->mDefaultPt = "97";
+ } else {
+ h264->mDefaultPt = "126";
+ }
+ }
+ }
+
+ types.push_back(SdpMediaSection::kAudio);
+ types.push_back(SdpMediaSection::kVideo);
+
+ AddTracksToStream(*mSessionOff, "offerer_stream", "audio,video");
+
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+
+ AddTracksToStream(*mSessionAns, "answerer_stream", "audio,video");
+
+ std::string answer = CreateAnswer();
+
+ UniquePtr<Sdp> outputSdp(Parse(answer));
+ ASSERT_TRUE(!!outputSdp);
+
+ ASSERT_EQ(2U, outputSdp->GetMediaSectionCount());
+ auto& video_section = outputSdp->GetMediaSection(1);
+ ASSERT_EQ(SdpMediaSection::kVideo, video_section.GetMediaType());
+ auto& video_attrs = video_section.GetAttributeList();
+ ASSERT_EQ(SdpDirectionAttribute::kSendrecv, video_attrs.GetDirection());
+
+ ASSERT_EQ(4U, video_section.GetFormats().size());
+ ASSERT_EQ("120", video_section.GetFormats()[0]);
+ ASSERT_EQ("124", video_section.GetFormats()[1]);
+ ASSERT_EQ("121", video_section.GetFormats()[2]);
+ ASSERT_EQ("125", video_section.GetFormats()[3]);
+
+ // Validate rtpmap
+ ASSERT_TRUE(video_attrs.HasAttribute(SdpAttribute::kRtpmapAttribute));
+ auto& rtpmaps = video_attrs.GetRtpmap();
+ ASSERT_TRUE(rtpmaps.HasEntry("120"));
+ ASSERT_TRUE(rtpmaps.HasEntry("121"));
+
+ auto& vp8_entry = rtpmaps.GetEntry("120");
+ auto& vp9_entry = rtpmaps.GetEntry("121");
+
+ ASSERT_EQ("VP8", vp8_entry.name);
+ ASSERT_EQ("VP9", vp9_entry.name);
+
+ // Validate fmtps
+ ASSERT_TRUE(video_attrs.HasAttribute(SdpAttribute::kFmtpAttribute));
+ auto& fmtps = video_attrs.GetFmtp().mFmtps;
+
+ ASSERT_EQ(4U, fmtps.size());
+
+ // VP8
+ ASSERT_EQ("120", fmtps[0].format);
+ ASSERT_TRUE(!!fmtps[0].parameters);
+ ASSERT_EQ(SdpRtpmapAttributeList::kVP8, fmtps[0].parameters->codec_type);
+
+ auto& parsed_vp8_params =
+ *static_cast<const SdpFmtpAttributeList::VP8Parameters*>(
+ fmtps[0].parameters.get());
+
+ ASSERT_EQ((uint32_t)12288, parsed_vp8_params.max_fs);
+ ASSERT_EQ((uint32_t)60, parsed_vp8_params.max_fr);
+
+ // VP9
+ ASSERT_EQ("121", fmtps[2].format);
+ ASSERT_TRUE(!!fmtps[2].parameters);
+ ASSERT_EQ(SdpRtpmapAttributeList::kVP9, fmtps[2].parameters->codec_type);
+
+ auto& parsed_vp9_params =
+ *static_cast<const SdpFmtpAttributeList::VP8Parameters*>(
+ fmtps[2].parameters.get());
+
+ ASSERT_EQ((uint32_t)12288, parsed_vp9_params.max_fs);
+ ASSERT_EQ((uint32_t)60, parsed_vp9_params.max_fr);
+
+ SetLocalAnswer(answer);
+ SetRemoteAnswer(answer);
+
+ auto offerTransceivers = GetTransceivers(*mSessionOff);
+ ASSERT_EQ(2U, offerTransceivers.size());
+ ASSERT_FALSE(IsNull(offerTransceivers[1].mSendTrack));
+ ASSERT_FALSE(IsNull(offerTransceivers[1].mRecvTrack));
+ ASSERT_TRUE(offerTransceivers[1].mSendTrack.GetNegotiatedDetails());
+ ASSERT_TRUE(offerTransceivers[1].mRecvTrack.GetNegotiatedDetails());
+ ASSERT_EQ(2U, offerTransceivers[1]
+ .mSendTrack.GetNegotiatedDetails()
+ ->GetEncoding(0)
+ .GetCodecs()
+ .size());
+ ASSERT_EQ(2U, offerTransceivers[1]
+ .mRecvTrack.GetNegotiatedDetails()
+ ->GetEncoding(0)
+ .GetCodecs()
+ .size());
+
+ auto answerTransceivers = GetTransceivers(*mSessionAns);
+ ASSERT_EQ(2U, answerTransceivers.size());
+ ASSERT_FALSE(IsNull(answerTransceivers[1].mSendTrack));
+ ASSERT_FALSE(IsNull(answerTransceivers[1].mRecvTrack));
+ ASSERT_TRUE(answerTransceivers[1].mSendTrack.GetNegotiatedDetails());
+ ASSERT_TRUE(answerTransceivers[1].mRecvTrack.GetNegotiatedDetails());
+ ASSERT_EQ(2U, answerTransceivers[1]
+ .mSendTrack.GetNegotiatedDetails()
+ ->GetEncoding(0)
+ .GetCodecs()
+ .size());
+ ASSERT_EQ(2U, answerTransceivers[1]
+ .mRecvTrack.GetNegotiatedDetails()
+ ->GetEncoding(0)
+ .GetCodecs()
+ .size());
+
+#if 0
+ // H264 packetization mode 1
+ ASSERT_EQ("126", fmtps[1].format);
+ ASSERT_TRUE(fmtps[1].parameters);
+ ASSERT_EQ(SdpRtpmapAttributeList::kH264, fmtps[1].parameters->codec_type);
+
+ auto& parsed_h264_1_params =
+ *static_cast<const SdpFmtpAttributeList::H264Parameters*>(
+ fmtps[1].parameters.get());
+
+ ASSERT_EQ((uint32_t)0x42a00d, parsed_h264_1_params.profile_level_id);
+ ASSERT_TRUE(parsed_h264_1_params.level_asymmetry_allowed);
+ ASSERT_EQ(1U, parsed_h264_1_params.packetization_mode);
+
+ // H264 packetization mode 0
+ ASSERT_EQ("97", fmtps[2].format);
+ ASSERT_TRUE(fmtps[2].parameters);
+ ASSERT_EQ(SdpRtpmapAttributeList::kH264, fmtps[2].parameters->codec_type);
+
+ auto& parsed_h264_0_params =
+ *static_cast<const SdpFmtpAttributeList::H264Parameters*>(
+ fmtps[2].parameters.get());
+
+ ASSERT_EQ((uint32_t)0x42a00d, parsed_h264_0_params.profile_level_id);
+ ASSERT_TRUE(parsed_h264_0_params.level_asymmetry_allowed);
+ ASSERT_EQ(0U, parsed_h264_0_params.packetization_mode);
+#endif
+}
+
+TEST_F(JsepSessionTest, OfferWithBundleGroupNoTags) {
+ AddTracks(*mSessionOff, "audio,video");
+ AddTracks(*mSessionAns, "audio,video");
+
+ std::string offer = CreateOffer();
+ size_t i = offer.find("a=group:BUNDLE");
+ offer.insert(i, "a=group:BUNDLE\r\n");
+
+ SetLocalOffer(offer, CHECK_SUCCESS);
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+ std::string answer(CreateAnswer());
+}
+
+static void Replace(const std::string& toReplace, const std::string& with,
+ std::string* in) {
+ size_t pos = in->find(toReplace);
+ ASSERT_NE(std::string::npos, pos);
+ in->replace(pos, toReplace.size(), with);
+}
+
+static void ReplaceAll(const std::string& toReplace, const std::string& with,
+ std::string* in) {
+ while (in->find(toReplace) != std::string::npos) {
+ Replace(toReplace, with, in);
+ }
+}
+
+static void GetCodec(JsepSession& session, size_t transceiverIndex,
+ sdp::Direction direction, size_t encodingIndex,
+ size_t codecIndex,
+ UniquePtr<JsepCodecDescription>* codecOut) {
+ codecOut->reset();
+ ASSERT_LT(transceiverIndex, JsepSessionTest::GetTransceivers(session).size());
+ JsepTransceiver transceiver(
+ JsepSessionTest::GetTransceivers(session)[transceiverIndex]);
+ JsepTrack& track = (direction == sdp::kSend) ? transceiver.mSendTrack
+ : transceiver.mRecvTrack;
+ ASSERT_TRUE(track.GetNegotiatedDetails());
+ ASSERT_LT(encodingIndex, track.GetNegotiatedDetails()->GetEncodingCount());
+ ASSERT_LT(codecIndex, track.GetNegotiatedDetails()
+ ->GetEncoding(encodingIndex)
+ .GetCodecs()
+ .size());
+ codecOut->reset(track.GetNegotiatedDetails()
+ ->GetEncoding(encodingIndex)
+ .GetCodecs()[codecIndex]
+ ->Clone());
+}
+
+static void ForceH264(JsepSession& session, uint32_t profileLevelId) {
+ for (auto& codec : session.Codecs()) {
+ if (codec->mName == "H264") {
+ JsepVideoCodecDescription* h264 =
+ static_cast<JsepVideoCodecDescription*>(codec.get());
+ h264->mProfileLevelId = profileLevelId;
+ } else {
+ codec->mEnabled = false;
+ }
+ }
+}
+
+TEST_F(JsepSessionTest, TestH264Negotiation) {
+ ForceH264(*mSessionOff, 0x42e00b);
+ ForceH264(*mSessionAns, 0x42e00d);
+
+ AddTracks(*mSessionOff, "video");
+ AddTracks(*mSessionAns, "video");
+
+ std::string offer(CreateOffer());
+ SetLocalOffer(offer, CHECK_SUCCESS);
+
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+ std::string answer(CreateAnswer());
+
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+ SetLocalAnswer(answer, CHECK_SUCCESS);
+
+ UniquePtr<JsepCodecDescription> offererSendCodec;
+ GetCodec(*mSessionOff, 0, sdp::kSend, 0, 0, &offererSendCodec);
+ ASSERT_TRUE(offererSendCodec);
+ ASSERT_EQ("H264", offererSendCodec->mName);
+ const JsepVideoCodecDescription* offererVideoSendCodec(
+ static_cast<const JsepVideoCodecDescription*>(offererSendCodec.get()));
+ ASSERT_EQ((uint32_t)0x42e00d, offererVideoSendCodec->mProfileLevelId);
+
+ UniquePtr<JsepCodecDescription> offererRecvCodec;
+ GetCodec(*mSessionOff, 0, sdp::kRecv, 0, 0, &offererRecvCodec);
+ ASSERT_EQ("H264", offererRecvCodec->mName);
+ const JsepVideoCodecDescription* offererVideoRecvCodec(
+ static_cast<const JsepVideoCodecDescription*>(offererRecvCodec.get()));
+ ASSERT_EQ((uint32_t)0x42e00b, offererVideoRecvCodec->mProfileLevelId);
+
+ UniquePtr<JsepCodecDescription> answererSendCodec;
+ GetCodec(*mSessionAns, 0, sdp::kSend, 0, 0, &answererSendCodec);
+ ASSERT_TRUE(answererSendCodec);
+ ASSERT_EQ("H264", answererSendCodec->mName);
+ const JsepVideoCodecDescription* answererVideoSendCodec(
+ static_cast<const JsepVideoCodecDescription*>(answererSendCodec.get()));
+ ASSERT_EQ((uint32_t)0x42e00b, answererVideoSendCodec->mProfileLevelId);
+
+ UniquePtr<JsepCodecDescription> answererRecvCodec;
+ GetCodec(*mSessionAns, 0, sdp::kRecv, 0, 0, &answererRecvCodec);
+ ASSERT_EQ("H264", answererRecvCodec->mName);
+ const JsepVideoCodecDescription* answererVideoRecvCodec(
+ static_cast<const JsepVideoCodecDescription*>(answererRecvCodec.get()));
+ ASSERT_EQ((uint32_t)0x42e00d, answererVideoRecvCodec->mProfileLevelId);
+}
+
+TEST_F(JsepSessionTest, TestH264NegotiationFails) {
+ ForceH264(*mSessionOff, 0x42000b);
+ ForceH264(*mSessionAns, 0x42e00d);
+
+ AddTracks(*mSessionOff, "video");
+ AddTracks(*mSessionAns, "video");
+
+ std::string offer(CreateOffer());
+ SetLocalOffer(offer, CHECK_SUCCESS);
+
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+ std::string answer(CreateAnswer());
+
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+ SetLocalAnswer(answer, CHECK_SUCCESS);
+
+ ASSERT_EQ(nullptr, GetNegotiatedTransceiver(*mSessionOff, 0));
+ ASSERT_EQ(nullptr, GetNegotiatedTransceiver(*mSessionAns, 0));
+}
+
+TEST_F(JsepSessionTest, TestH264NegotiationOffererDefault) {
+ ForceH264(*mSessionOff, 0x42000d);
+ ForceH264(*mSessionAns, 0x42000d);
+
+ AddTracks(*mSessionOff, "video");
+ AddTracks(*mSessionAns, "video");
+
+ std::string offer(CreateOffer());
+ SetLocalOffer(offer, CHECK_SUCCESS);
+
+ Replace("profile-level-id=42000d", "some-unknown-param=0", &offer);
+
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+ std::string answer(CreateAnswer());
+
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+ SetLocalAnswer(answer, CHECK_SUCCESS);
+
+ UniquePtr<JsepCodecDescription> answererSendCodec;
+ GetCodec(*mSessionAns, 0, sdp::kSend, 0, 0, &answererSendCodec);
+ ASSERT_TRUE(answererSendCodec);
+ ASSERT_EQ("H264", answererSendCodec->mName);
+ const JsepVideoCodecDescription* answererVideoSendCodec(
+ static_cast<const JsepVideoCodecDescription*>(answererSendCodec.get()));
+ ASSERT_EQ((uint32_t)0x420010, answererVideoSendCodec->mProfileLevelId);
+}
+
+TEST_F(JsepSessionTest, TestH264NegotiationOffererNoFmtp) {
+ ForceH264(*mSessionOff, 0x42000d);
+ ForceH264(*mSessionAns, 0x42001e);
+
+ AddTracks(*mSessionOff, "video");
+ AddTracks(*mSessionAns, "video");
+
+ std::string offer(CreateOffer());
+ SetLocalOffer(offer, CHECK_SUCCESS);
+
+ Replace("a=fmtp", "a=oops", &offer);
+
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+ std::string answer(CreateAnswer());
+
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+ SetLocalAnswer(answer, CHECK_SUCCESS);
+
+ UniquePtr<JsepCodecDescription> answererSendCodec;
+ GetCodec(*mSessionAns, 0, sdp::kSend, 0, 0, &answererSendCodec);
+ ASSERT_TRUE(answererSendCodec);
+ ASSERT_EQ("H264", answererSendCodec->mName);
+ const JsepVideoCodecDescription* answererVideoSendCodec(
+ static_cast<const JsepVideoCodecDescription*>(answererSendCodec.get()));
+ ASSERT_EQ((uint32_t)0x420010, answererVideoSendCodec->mProfileLevelId);
+
+ UniquePtr<JsepCodecDescription> answererRecvCodec;
+ GetCodec(*mSessionAns, 0, sdp::kRecv, 0, 0, &answererRecvCodec);
+ ASSERT_EQ("H264", answererRecvCodec->mName);
+ const JsepVideoCodecDescription* answererVideoRecvCodec(
+ static_cast<const JsepVideoCodecDescription*>(answererRecvCodec.get()));
+ ASSERT_EQ((uint32_t)0x420010, answererVideoRecvCodec->mProfileLevelId);
+}
+
+TEST_F(JsepSessionTest, TestH264LevelAsymmetryDisallowedByOffererWithLowLevel) {
+ ForceH264(*mSessionOff, 0x42e00b);
+ ForceH264(*mSessionAns, 0x42e00d);
+
+ AddTracks(*mSessionOff, "video");
+ AddTracks(*mSessionAns, "video");
+
+ std::string offer(CreateOffer());
+ SetLocalOffer(offer, CHECK_SUCCESS);
+
+ Replace("level-asymmetry-allowed=1", "level-asymmetry-allowed=0", &offer);
+
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+ std::string answer(CreateAnswer());
+
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+ SetLocalAnswer(answer, CHECK_SUCCESS);
+
+ // Offerer doesn't know about the shenanigans we've pulled here, so will
+ // behave normally, and we test the normal behavior elsewhere.
+
+ UniquePtr<JsepCodecDescription> answererSendCodec;
+ GetCodec(*mSessionAns, 0, sdp::kSend, 0, 0, &answererSendCodec);
+ ASSERT_TRUE(answererSendCodec);
+ ASSERT_EQ("H264", answererSendCodec->mName);
+ const JsepVideoCodecDescription* answererVideoSendCodec(
+ static_cast<const JsepVideoCodecDescription*>(answererSendCodec.get()));
+ ASSERT_EQ((uint32_t)0x42e00b, answererVideoSendCodec->mProfileLevelId);
+
+ UniquePtr<JsepCodecDescription> answererRecvCodec;
+ GetCodec(*mSessionAns, 0, sdp::kRecv, 0, 0, &answererRecvCodec);
+ ASSERT_EQ("H264", answererRecvCodec->mName);
+ const JsepVideoCodecDescription* answererVideoRecvCodec(
+ static_cast<const JsepVideoCodecDescription*>(answererRecvCodec.get()));
+ ASSERT_EQ((uint32_t)0x42e00b, answererVideoRecvCodec->mProfileLevelId);
+}
+
+TEST_F(JsepSessionTest,
+ TestH264LevelAsymmetryDisallowedByOffererWithHighLevel) {
+ ForceH264(*mSessionOff, 0x42e00d);
+ ForceH264(*mSessionAns, 0x42e00b);
+
+ AddTracks(*mSessionOff, "video");
+ AddTracks(*mSessionAns, "video");
+
+ std::string offer(CreateOffer());
+ SetLocalOffer(offer, CHECK_SUCCESS);
+
+ Replace("level-asymmetry-allowed=1", "level-asymmetry-allowed=0", &offer);
+
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+ std::string answer(CreateAnswer());
+
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+ SetLocalAnswer(answer, CHECK_SUCCESS);
+
+ // Offerer doesn't know about the shenanigans we've pulled here, so will
+ // behave normally, and we test the normal behavior elsewhere.
+
+ UniquePtr<JsepCodecDescription> answererSendCodec;
+ GetCodec(*mSessionAns, 0, sdp::kSend, 0, 0, &answererSendCodec);
+ ASSERT_TRUE(answererSendCodec);
+ ASSERT_EQ("H264", answererSendCodec->mName);
+ const JsepVideoCodecDescription* answererVideoSendCodec(
+ static_cast<const JsepVideoCodecDescription*>(answererSendCodec.get()));
+ ASSERT_EQ((uint32_t)0x42e00b, answererVideoSendCodec->mProfileLevelId);
+
+ UniquePtr<JsepCodecDescription> answererRecvCodec;
+ GetCodec(*mSessionAns, 0, sdp::kRecv, 0, 0, &answererRecvCodec);
+ ASSERT_EQ("H264", answererRecvCodec->mName);
+ const JsepVideoCodecDescription* answererVideoRecvCodec(
+ static_cast<const JsepVideoCodecDescription*>(answererRecvCodec.get()));
+ ASSERT_EQ((uint32_t)0x42e00b, answererVideoRecvCodec->mProfileLevelId);
+}
+
+TEST_F(JsepSessionTest,
+ TestH264LevelAsymmetryDisallowedByAnswererWithLowLevel) {
+ ForceH264(*mSessionOff, 0x42e00d);
+ ForceH264(*mSessionAns, 0x42e00b);
+
+ AddTracks(*mSessionOff, "video");
+ AddTracks(*mSessionAns, "video");
+
+ std::string offer(CreateOffer());
+ SetLocalOffer(offer, CHECK_SUCCESS);
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+ std::string answer(CreateAnswer());
+
+ Replace("level-asymmetry-allowed=1", "level-asymmetry-allowed=0", &answer);
+
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+ SetLocalAnswer(answer, CHECK_SUCCESS);
+
+ UniquePtr<JsepCodecDescription> offererSendCodec;
+ GetCodec(*mSessionOff, 0, sdp::kSend, 0, 0, &offererSendCodec);
+ ASSERT_TRUE(offererSendCodec);
+ ASSERT_EQ("H264", offererSendCodec->mName);
+ const JsepVideoCodecDescription* offererVideoSendCodec(
+ static_cast<const JsepVideoCodecDescription*>(offererSendCodec.get()));
+ ASSERT_EQ((uint32_t)0x42e00b, offererVideoSendCodec->mProfileLevelId);
+
+ UniquePtr<JsepCodecDescription> offererRecvCodec;
+ GetCodec(*mSessionOff, 0, sdp::kRecv, 0, 0, &offererRecvCodec);
+ ASSERT_EQ("H264", offererRecvCodec->mName);
+ const JsepVideoCodecDescription* offererVideoRecvCodec(
+ static_cast<const JsepVideoCodecDescription*>(offererRecvCodec.get()));
+ ASSERT_EQ((uint32_t)0x42e00b, offererVideoRecvCodec->mProfileLevelId);
+
+ // Answerer doesn't know we've pulled these shenanigans, it should act as if
+ // it did not set level-asymmetry-required, and we already check that
+ // elsewhere
+}
+
+TEST_F(JsepSessionTest,
+ TestH264LevelAsymmetryDisallowedByAnswererWithHighLevel) {
+ ForceH264(*mSessionOff, 0x42e00b);
+ ForceH264(*mSessionAns, 0x42e00d);
+
+ AddTracks(*mSessionOff, "video");
+ AddTracks(*mSessionAns, "video");
+
+ std::string offer(CreateOffer());
+ SetLocalOffer(offer, CHECK_SUCCESS);
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+ std::string answer(CreateAnswer());
+
+ Replace("level-asymmetry-allowed=1", "level-asymmetry-allowed=0", &answer);
+
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+ SetLocalAnswer(answer, CHECK_SUCCESS);
+
+ UniquePtr<JsepCodecDescription> offererSendCodec;
+ GetCodec(*mSessionOff, 0, sdp::kSend, 0, 0, &offererSendCodec);
+ ASSERT_TRUE(offererSendCodec);
+ ASSERT_EQ("H264", offererSendCodec->mName);
+ const JsepVideoCodecDescription* offererVideoSendCodec(
+ static_cast<const JsepVideoCodecDescription*>(offererSendCodec.get()));
+ ASSERT_EQ((uint32_t)0x42e00b, offererVideoSendCodec->mProfileLevelId);
+
+ UniquePtr<JsepCodecDescription> offererRecvCodec;
+ GetCodec(*mSessionOff, 0, sdp::kRecv, 0, 0, &offererRecvCodec);
+ ASSERT_EQ("H264", offererRecvCodec->mName);
+ const JsepVideoCodecDescription* offererVideoRecvCodec(
+ static_cast<const JsepVideoCodecDescription*>(offererRecvCodec.get()));
+ ASSERT_EQ((uint32_t)0x42e00b, offererVideoRecvCodec->mProfileLevelId);
+
+ // Answerer doesn't know we've pulled these shenanigans, it should act as if
+ // it did not set level-asymmetry-required, and we already check that
+ // elsewhere
+}
+
+TEST_P(JsepSessionTest, TestRejectMline) {
+ // We need to do this before adding tracks
+ types = BuildTypes(GetParam());
+
+ SdpMediaSection::MediaType type = types.front();
+
+ switch (type) {
+ case SdpMediaSection::kAudio:
+ // Sabotage audio
+ EnsureNegotiationFailure(types.front(), "opus");
+ break;
+ case SdpMediaSection::kVideo:
+ // Sabotage video
+ EnsureNegotiationFailure(types.front(), "H264");
+ break;
+ case SdpMediaSection::kApplication:
+ // Sabotage datachannel
+ EnsureNegotiationFailure(types.front(), "webrtc-datachannel");
+ break;
+ default:
+ ASSERT_TRUE(false)
+ << "Unknown media type";
+ }
+
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ std::string offer = CreateOffer();
+ mSessionOff->SetLocalDescription(kJsepSdpOffer, offer);
+ mSessionAns->SetRemoteDescription(kJsepSdpOffer, offer);
+
+ std::string answer = CreateAnswer();
+
+ UniquePtr<Sdp> outputSdp(Parse(answer));
+ ASSERT_TRUE(!!outputSdp);
+
+ ASSERT_NE(0U, outputSdp->GetMediaSectionCount());
+ SdpMediaSection* failed_section = nullptr;
+
+ for (size_t i = 0; i < outputSdp->GetMediaSectionCount(); ++i) {
+ if (outputSdp->GetMediaSection(i).GetMediaType() == type) {
+ failed_section = &outputSdp->GetMediaSection(i);
+ }
+ }
+
+ ASSERT_TRUE(failed_section)
+ << "Failed type was entirely absent from SDP";
+ auto& failed_attrs = failed_section->GetAttributeList();
+ ASSERT_EQ(SdpDirectionAttribute::kInactive, failed_attrs.GetDirection());
+ ASSERT_EQ(0U, failed_section->GetPort());
+
+ mSessionAns->SetLocalDescription(kJsepSdpAnswer, answer);
+ mSessionOff->SetRemoteDescription(kJsepSdpAnswer, answer);
+
+ size_t numRejected = std::count(types.begin(), types.end(), type);
+ size_t numAccepted = types.size() - numRejected;
+
+ if (type == SdpMediaSection::MediaType::kApplication) {
+ ASSERT_TRUE(GetDatachannelTransceiver(*mSessionOff));
+ ASSERT_FALSE(
+ GetDatachannelTransceiver(*mSessionOff)->mRecvTrack.GetActive());
+ ASSERT_TRUE(GetDatachannelTransceiver(*mSessionAns));
+ ASSERT_FALSE(
+ GetDatachannelTransceiver(*mSessionAns)->mRecvTrack.GetActive());
+ } else {
+ ASSERT_EQ(types.size(), GetLocalTracks(*mSessionOff).size());
+ ASSERT_EQ(numAccepted, GetRemoteTracks(*mSessionOff).size());
+
+ ASSERT_EQ(types.size(), GetLocalTracks(*mSessionAns).size());
+ ASSERT_EQ(types.size(), GetRemoteTracks(*mSessionAns).size());
+ }
+}
+
+TEST_F(JsepSessionTest, NegotiationNoMlines) { OfferAnswer(); }
+
+TEST_F(JsepSessionTest, TestIceLite) {
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer, CHECK_SUCCESS);
+
+ UniquePtr<Sdp> parsedOffer(Parse(offer));
+ parsedOffer->GetAttributeList().SetAttribute(
+ new SdpFlagAttribute(SdpAttribute::kIceLiteAttribute));
+
+ std::ostringstream os;
+ parsedOffer->Serialize(os);
+ SetRemoteOffer(os.str(), CHECK_SUCCESS);
+
+ ASSERT_TRUE(mSessionAns->RemoteIsIceLite());
+ ASSERT_FALSE(mSessionOff->RemoteIsIceLite());
+}
+
+TEST_F(JsepSessionTest, TestIceOptions) {
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer, CHECK_SUCCESS);
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer, CHECK_SUCCESS);
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+
+ ASSERT_EQ(1U, mSessionOff->GetIceOptions().size());
+ ASSERT_EQ("trickle", mSessionOff->GetIceOptions()[0]);
+
+ ASSERT_EQ(1U, mSessionAns->GetIceOptions().size());
+ ASSERT_EQ("trickle", mSessionAns->GetIceOptions()[0]);
+}
+
+TEST_F(JsepSessionTest, TestIceRestart) {
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer, CHECK_SUCCESS);
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer, CHECK_SUCCESS);
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+
+ JsepOfferOptions options;
+ options.mIceRestart = Some(true);
+
+ std::string reoffer = CreateOffer(Some(options));
+ SetLocalOffer(reoffer, CHECK_SUCCESS);
+ SetRemoteOffer(reoffer, CHECK_SUCCESS);
+ std::string reanswer = CreateAnswer();
+ SetLocalAnswer(reanswer, CHECK_SUCCESS);
+ SetRemoteAnswer(reanswer, CHECK_SUCCESS);
+
+ UniquePtr<Sdp> parsedOffer(Parse(offer));
+ ASSERT_EQ(1U, parsedOffer->GetMediaSectionCount());
+ UniquePtr<Sdp> parsedReoffer(Parse(reoffer));
+ ASSERT_EQ(1U, parsedReoffer->GetMediaSectionCount());
+
+ UniquePtr<Sdp> parsedAnswer(Parse(answer));
+ ASSERT_EQ(1U, parsedAnswer->GetMediaSectionCount());
+ UniquePtr<Sdp> parsedReanswer(Parse(reanswer));
+ ASSERT_EQ(1U, parsedReanswer->GetMediaSectionCount());
+
+ // verify ice pwd/ufrag are present in offer/answer and reoffer/reanswer
+ auto& offerMediaAttrs = parsedOffer->GetMediaSection(0).GetAttributeList();
+ ASSERT_TRUE(offerMediaAttrs.HasAttribute(SdpAttribute::kIcePwdAttribute));
+ ASSERT_TRUE(offerMediaAttrs.HasAttribute(SdpAttribute::kIceUfragAttribute));
+
+ auto& reofferMediaAttrs =
+ parsedReoffer->GetMediaSection(0).GetAttributeList();
+ ASSERT_TRUE(reofferMediaAttrs.HasAttribute(SdpAttribute::kIcePwdAttribute));
+ ASSERT_TRUE(reofferMediaAttrs.HasAttribute(SdpAttribute::kIceUfragAttribute));
+
+ auto& answerMediaAttrs = parsedAnswer->GetMediaSection(0).GetAttributeList();
+ ASSERT_TRUE(answerMediaAttrs.HasAttribute(SdpAttribute::kIcePwdAttribute));
+ ASSERT_TRUE(answerMediaAttrs.HasAttribute(SdpAttribute::kIceUfragAttribute));
+
+ auto& reanswerMediaAttrs =
+ parsedReanswer->GetMediaSection(0).GetAttributeList();
+ ASSERT_TRUE(reanswerMediaAttrs.HasAttribute(SdpAttribute::kIcePwdAttribute));
+ ASSERT_TRUE(
+ reanswerMediaAttrs.HasAttribute(SdpAttribute::kIceUfragAttribute));
+
+ // make sure offer/reoffer ice pwd/ufrag changed on ice restart
+ ASSERT_NE(offerMediaAttrs.GetIcePwd().c_str(),
+ reofferMediaAttrs.GetIcePwd().c_str());
+ ASSERT_NE(offerMediaAttrs.GetIceUfrag().c_str(),
+ reofferMediaAttrs.GetIceUfrag().c_str());
+
+ // make sure answer/reanswer ice pwd/ufrag changed on ice restart
+ ASSERT_NE(answerMediaAttrs.GetIcePwd().c_str(),
+ reanswerMediaAttrs.GetIcePwd().c_str());
+ ASSERT_NE(answerMediaAttrs.GetIceUfrag().c_str(),
+ reanswerMediaAttrs.GetIceUfrag().c_str());
+
+ auto offererTransceivers = GetTransceivers(*mSessionOff);
+ auto answererTransceivers = GetTransceivers(*mSessionAns);
+ ASSERT_EQ(reofferMediaAttrs.GetIceUfrag(),
+ offererTransceivers[0].mTransport.mLocalUfrag);
+ ASSERT_EQ(reofferMediaAttrs.GetIceUfrag(),
+ answererTransceivers[0].mTransport.mIce->GetUfrag());
+ ASSERT_EQ(reofferMediaAttrs.GetIcePwd(),
+ offererTransceivers[0].mTransport.mLocalPwd);
+ ASSERT_EQ(reofferMediaAttrs.GetIcePwd(),
+ answererTransceivers[0].mTransport.mIce->GetPassword());
+
+ ASSERT_EQ(reanswerMediaAttrs.GetIceUfrag(),
+ answererTransceivers[0].mTransport.mLocalUfrag);
+ ASSERT_EQ(reanswerMediaAttrs.GetIceUfrag(),
+ offererTransceivers[0].mTransport.mIce->GetUfrag());
+ ASSERT_EQ(reanswerMediaAttrs.GetIcePwd(),
+ answererTransceivers[0].mTransport.mLocalPwd);
+ ASSERT_EQ(reanswerMediaAttrs.GetIcePwd(),
+ offererTransceivers[0].mTransport.mIce->GetPassword());
+}
+
+TEST_F(JsepSessionTest, TestAnswererIndicatingIceRestart) {
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer, CHECK_SUCCESS);
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer, CHECK_SUCCESS);
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+
+ // reoffer, but we'll improperly indicate an ice restart in the answer by
+ // modifying the ice pwd and ufrag
+ std::string reoffer = CreateOffer();
+ SetLocalOffer(reoffer, CHECK_SUCCESS);
+ SetRemoteOffer(reoffer, CHECK_SUCCESS);
+ std::string reanswer = CreateAnswer();
+
+ // change the ice pwd and ufrag
+ ReplaceInSdp(&reanswer, "a=ice-ufrag:", "a=ice-ufrag:bad-");
+ ReplaceInSdp(&reanswer, "a=ice-pwd:", "a=ice-pwd:bad-");
+ SetLocalAnswer(reanswer, CHECK_SUCCESS);
+ JsepSession::Result result =
+ mSessionOff->SetRemoteDescription(kJsepSdpAnswer, reanswer);
+ ASSERT_EQ(dom::PCError::InvalidAccessError, *result.mError);
+}
+
+TEST_F(JsepSessionTest, TestExtmap) {
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+ // ssrc-audio-level will be extmap 1 for both
+ // csrc-audio-level will be 2 for both
+ // mid will be 3 for both
+ // video related extensions take 4 - 7
+ mSessionOff->AddAudioRtpExtension("foo"); // Default mapping of 8
+ mSessionOff->AddAudioRtpExtension("bar"); // Default mapping of 9
+ mSessionAns->AddAudioRtpExtension("bar"); // Default mapping of 8
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer, CHECK_SUCCESS);
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer, CHECK_SUCCESS);
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+
+ UniquePtr<Sdp> parsedOffer(Parse(offer));
+ ASSERT_EQ(1U, parsedOffer->GetMediaSectionCount());
+
+ auto& offerMediaAttrs = parsedOffer->GetMediaSection(0).GetAttributeList();
+ ASSERT_TRUE(offerMediaAttrs.HasAttribute(SdpAttribute::kExtmapAttribute));
+ auto& offerExtmap = offerMediaAttrs.GetExtmap().mExtmaps;
+ ASSERT_EQ(5U, offerExtmap.size());
+ ASSERT_EQ("urn:ietf:params:rtp-hdrext:ssrc-audio-level",
+ offerExtmap[0].extensionname);
+ ASSERT_EQ(1U, offerExtmap[0].entry);
+ ASSERT_EQ("urn:ietf:params:rtp-hdrext:csrc-audio-level",
+ offerExtmap[1].extensionname);
+ ASSERT_EQ(2U, offerExtmap[1].entry);
+ ASSERT_EQ("urn:ietf:params:rtp-hdrext:sdes:mid",
+ offerExtmap[2].extensionname);
+ ASSERT_EQ(3U, offerExtmap[2].entry);
+ ASSERT_EQ("foo", offerExtmap[3].extensionname);
+ ASSERT_EQ(8U, offerExtmap[3].entry);
+ ASSERT_EQ("bar", offerExtmap[4].extensionname);
+ ASSERT_EQ(9U, offerExtmap[4].entry);
+
+ UniquePtr<Sdp> parsedAnswer(Parse(answer));
+ ASSERT_EQ(1U, parsedAnswer->GetMediaSectionCount());
+
+ auto& answerMediaAttrs = parsedAnswer->GetMediaSection(0).GetAttributeList();
+ ASSERT_TRUE(answerMediaAttrs.HasAttribute(SdpAttribute::kExtmapAttribute));
+ auto& answerExtmap = answerMediaAttrs.GetExtmap().mExtmaps;
+ ASSERT_EQ(3U, answerExtmap.size());
+ ASSERT_EQ("urn:ietf:params:rtp-hdrext:ssrc-audio-level",
+ answerExtmap[0].extensionname);
+ ASSERT_EQ(1U, answerExtmap[0].entry);
+ ASSERT_EQ("urn:ietf:params:rtp-hdrext:sdes:mid",
+ answerExtmap[1].extensionname);
+ ASSERT_EQ(3U, answerExtmap[1].entry);
+ // We ensure that the entry for "bar" matches what was in the offer
+ ASSERT_EQ("bar", answerExtmap[2].extensionname);
+ ASSERT_EQ(9U, answerExtmap[2].entry);
+}
+
+TEST_F(JsepSessionTest, TestExtmapDefaults) {
+ types.push_back(SdpMediaSection::kAudio);
+ types.push_back(SdpMediaSection::kVideo);
+ AddTracks(*mSessionOff, "audio,video");
+
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer, CHECK_SUCCESS);
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer, CHECK_SUCCESS);
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+
+ UniquePtr<Sdp> parsedOffer(Parse(offer));
+ ASSERT_EQ(2U, parsedOffer->GetMediaSectionCount());
+
+ auto& offerAudioMediaAttrs =
+ parsedOffer->GetMediaSection(0).GetAttributeList();
+ ASSERT_TRUE(
+ offerAudioMediaAttrs.HasAttribute(SdpAttribute::kExtmapAttribute));
+ auto& offerAudioExtmap = offerAudioMediaAttrs.GetExtmap().mExtmaps;
+ ASSERT_EQ(3U, offerAudioExtmap.size());
+
+ ASSERT_EQ("urn:ietf:params:rtp-hdrext:ssrc-audio-level",
+ offerAudioExtmap[0].extensionname);
+ ASSERT_EQ(1U, offerAudioExtmap[0].entry);
+ ASSERT_EQ("urn:ietf:params:rtp-hdrext:csrc-audio-level",
+ offerAudioExtmap[1].extensionname);
+ ASSERT_EQ(2U, offerAudioExtmap[1].entry);
+ ASSERT_EQ("urn:ietf:params:rtp-hdrext:sdes:mid",
+ offerAudioExtmap[2].extensionname);
+
+ auto& offerVideoMediaAttrs =
+ parsedOffer->GetMediaSection(1).GetAttributeList();
+ ASSERT_TRUE(
+ offerVideoMediaAttrs.HasAttribute(SdpAttribute::kExtmapAttribute));
+ auto& offerVideoExtmap = offerVideoMediaAttrs.GetExtmap().mExtmaps;
+ ASSERT_EQ(5U, offerVideoExtmap.size());
+
+ ASSERT_EQ(3U, offerVideoExtmap[0].entry);
+ ASSERT_EQ("urn:ietf:params:rtp-hdrext:sdes:mid",
+ offerVideoExtmap[0].extensionname);
+ ASSERT_EQ("http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time",
+ offerVideoExtmap[1].extensionname);
+ ASSERT_EQ(4U, offerVideoExtmap[1].entry);
+ ASSERT_EQ("urn:ietf:params:rtp-hdrext:toffset",
+ offerVideoExtmap[2].extensionname);
+ ASSERT_EQ(5U, offerVideoExtmap[2].entry);
+ ASSERT_EQ("http://www.webrtc.org/experiments/rtp-hdrext/playout-delay",
+ offerVideoExtmap[3].extensionname);
+ ASSERT_EQ(6U, offerVideoExtmap[3].entry);
+ ASSERT_EQ(
+ "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-"
+ "extensions-01",
+ offerVideoExtmap[4].extensionname);
+ ASSERT_EQ(7U, offerVideoExtmap[4].entry);
+
+ UniquePtr<Sdp> parsedAnswer(Parse(answer));
+ ASSERT_EQ(2U, parsedAnswer->GetMediaSectionCount());
+
+ auto& answerAudioMediaAttrs =
+ parsedAnswer->GetMediaSection(0).GetAttributeList();
+ ASSERT_TRUE(
+ answerAudioMediaAttrs.HasAttribute(SdpAttribute::kExtmapAttribute));
+ auto& answerAudioExtmap = answerAudioMediaAttrs.GetExtmap().mExtmaps;
+ ASSERT_EQ(2U, answerAudioExtmap.size());
+
+ ASSERT_EQ("urn:ietf:params:rtp-hdrext:ssrc-audio-level",
+ answerAudioExtmap[0].extensionname);
+ ASSERT_EQ(1U, answerAudioExtmap[0].entry);
+ ASSERT_EQ("urn:ietf:params:rtp-hdrext:sdes:mid",
+ answerAudioExtmap[1].extensionname);
+ ASSERT_EQ(3U, answerAudioExtmap[1].entry);
+
+ auto& answerVideoMediaAttrs =
+ parsedAnswer->GetMediaSection(1).GetAttributeList();
+ ASSERT_TRUE(
+ answerVideoMediaAttrs.HasAttribute(SdpAttribute::kExtmapAttribute));
+ auto& answerVideoExtmap = answerVideoMediaAttrs.GetExtmap().mExtmaps;
+ ASSERT_EQ(4U, answerVideoExtmap.size());
+
+ ASSERT_EQ(3U, answerVideoExtmap[0].entry);
+ ASSERT_EQ("urn:ietf:params:rtp-hdrext:sdes:mid",
+ answerVideoExtmap[0].extensionname);
+ ASSERT_EQ("http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time",
+ answerVideoExtmap[1].extensionname);
+ ASSERT_EQ(4U, answerVideoExtmap[1].entry);
+ ASSERT_EQ("urn:ietf:params:rtp-hdrext:toffset",
+ answerVideoExtmap[2].extensionname);
+ ASSERT_EQ(5U, answerVideoExtmap[2].entry);
+ ASSERT_EQ(
+ "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-"
+ "extensions-01",
+ answerVideoExtmap[3].extensionname);
+ ASSERT_EQ(7U, answerVideoExtmap[3].entry);
+}
+
+TEST_F(JsepSessionTest, TestExtmapWithDuplicates) {
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+ // ssrc-audio-level will be extmap 1 for both
+ // csrc-audio-level will be 2 for both
+ // mid will be 3 for both
+ // video related extensions take 4 - 7
+ mSessionOff->AddAudioRtpExtension("foo"); // Default mapping of 8
+ mSessionOff->AddAudioRtpExtension("bar"); // Default mapping of 9
+ mSessionOff->AddAudioRtpExtension("bar"); // Should be ignored
+ mSessionOff->AddAudioRtpExtension("bar"); // Should be ignored
+ mSessionOff->AddAudioRtpExtension("baz"); // Default mapping of 10
+ mSessionOff->AddAudioRtpExtension("bar"); // Should be ignored
+
+ std::string offer = CreateOffer();
+ UniquePtr<Sdp> parsedOffer(Parse(offer));
+ ASSERT_EQ(1U, parsedOffer->GetMediaSectionCount());
+
+ auto& offerMediaAttrs = parsedOffer->GetMediaSection(0).GetAttributeList();
+ ASSERT_TRUE(offerMediaAttrs.HasAttribute(SdpAttribute::kExtmapAttribute));
+ auto& offerExtmap = offerMediaAttrs.GetExtmap().mExtmaps;
+ ASSERT_EQ(6U, offerExtmap.size());
+ ASSERT_EQ("urn:ietf:params:rtp-hdrext:ssrc-audio-level",
+ offerExtmap[0].extensionname);
+ ASSERT_EQ(1U, offerExtmap[0].entry);
+ ASSERT_EQ("urn:ietf:params:rtp-hdrext:csrc-audio-level",
+ offerExtmap[1].extensionname);
+ ASSERT_EQ(2U, offerExtmap[1].entry);
+ ASSERT_EQ("urn:ietf:params:rtp-hdrext:sdes:mid",
+ offerExtmap[2].extensionname);
+ ASSERT_EQ(3U, offerExtmap[2].entry);
+ ASSERT_EQ("foo", offerExtmap[3].extensionname);
+ ASSERT_EQ(8U, offerExtmap[3].entry);
+ ASSERT_EQ("bar", offerExtmap[4].extensionname);
+ ASSERT_EQ(9U, offerExtmap[4].entry);
+ ASSERT_EQ("baz", offerExtmap[5].extensionname);
+ ASSERT_EQ(10U, offerExtmap[5].entry);
+}
+
+TEST_F(JsepSessionTest, TestExtmapZeroId) {
+ AddTracks(*mSessionOff, "video");
+ AddTracks(*mSessionAns, "video");
+
+ std::string sdp =
+ "v=0\r\n"
+ "o=- 6 2 IN IP4 1r\r\n"
+ "t=0 0a\r\n"
+ "a=ice-ufrag:Xp\r\n"
+ "a=ice-pwd:he\r\n"
+ "a=setup:actpass\r\n"
+ "a=fingerprint:sha-256 "
+ "DC:FC:25:56:2B:88:77:2F:E4:FA:97:4E:2E:F1:D6:34:A6:A0:11:E2:E4:38:B3:98:"
+ "08:D2:F7:9D:F5:E2:C1:15\r\n"
+ "m=video 9 UDP/TLS/RTP/SAVPF 100\r\n"
+ "c=IN IP4 0\r\n"
+ "a=rtpmap:100 VP8/90000\r\n"
+ "a=extmap:0 urn:ietf:params:rtp-hdrext:toffset\r\n";
+ auto result = mSessionAns->SetRemoteDescription(kJsepSdpOffer, sdp);
+ ASSERT_TRUE(result.mError == Some(dom::PCError::OperationError));
+ ASSERT_EQ(
+ "Description contains invalid extension id 0 on level 0 which is"
+ " unsupported until 2-byte rtp header extensions are supported in"
+ " webrtc.org",
+ mSessionAns->GetLastError());
+}
+
+TEST_F(JsepSessionTest, TestExtmapInvalidId) {
+ AddTracks(*mSessionAns, "video");
+
+ std::string sdp =
+ "v=0\r\n"
+ "o=- 6 2 IN IP4 1r\r\n"
+ "t=0 0a\r\n"
+ "a=ice-ufrag:Xp\r\n"
+ "a=ice-pwd:he\r\n"
+ "a=setup:actpass\r\n"
+ "a=fingerprint:sha-256 "
+ "DC:FC:25:56:2B:88:77:2F:E4:FA:97:4E:2E:F1:D6:34:A6:A0:11:E2:E4:38:B3:98:"
+ "08:D2:F7:9D:F5:E2:C1:15\r\n"
+ "m=video 9 UDP/TLS/RTP/SAVPF 100\r\n"
+ "c=IN IP4 0\r\n"
+ "a=rtpmap:100 VP8/90000\r\n"
+ "a=extmap:15 urn:ietf:params:rtp-hdrext:toffset\r\n";
+ auto result = mSessionAns->SetRemoteDescription(kJsepSdpOffer, sdp);
+ ASSERT_TRUE(result.mError == Some(dom::PCError::OperationError));
+ ASSERT_EQ(
+ "Description contains invalid extension id 15 on level 0 which is"
+ " unsupported until 2-byte rtp header extensions are supported in"
+ " webrtc.org",
+ mSessionAns->GetLastError());
+}
+
+TEST_F(JsepSessionTest, TestExtmapDuplicateId) {
+ AddTracks(*mSessionAns, "video");
+
+ std::string sdp =
+ "v=0\r\n"
+ "o=- 6 2 IN IP4 1r\r\n"
+ "t=0 0a\r\n"
+ "a=ice-ufrag:Xp\r\n"
+ "a=ice-pwd:he\r\n"
+ "a=setup:actpass\r\n"
+ "a=fingerprint:sha-256 "
+ "DC:FC:25:56:2B:88:77:2F:E4:FA:97:4E:2E:F1:D6:34:A6:A0:11:E2:E4:38:B3:98:"
+ "08:D2:F7:9D:F5:E2:C1:15\r\n"
+ "m=video 9 UDP/TLS/RTP/SAVPF 100\r\n"
+ "c=IN IP4 0\r\n"
+ "a=rtpmap:100 VP8/90000\r\n"
+ "a=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r\n"
+ "a=extmap:2 "
+ "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n";
+ auto result = mSessionAns->SetRemoteDescription(kJsepSdpOffer, sdp);
+ ASSERT_TRUE(result.mError == Some(dom::PCError::OperationError));
+ ASSERT_EQ("Description contains duplicate extension id 2 on level 0",
+ mSessionAns->GetLastError());
+}
+
+TEST_F(JsepSessionTest, TestNegotiatedExtmapStability) {
+ AddTracks(*mSessionAns, "audio,video");
+
+ std::string sdp =
+ "v=0\r\n"
+ "o=- 6 2 IN IP4 1r\r\n"
+ "t=0 0a\r\n"
+ "a=ice-ufrag:Xp\r\n"
+ "a=ice-pwd:he\r\n"
+ "a=setup:actpass\r\n"
+ "a=fingerprint:sha-256 "
+ "DC:FC:25:56:2B:88:77:2F:E4:FA:97:4E:2E:F1:D6:34:A6:A0:11:E2:E4:38:B3:98:"
+ "08:D2:F7:9D:F5:E2:C1:15\r\n"
+ "m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n"
+ "c=IN IP4 51.81.107.13\r\n"
+ "a=sendrecv\r\n"
+ "a=extmap:11 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n"
+ "a=fmtp:111 maxplaybackrate=48000;stereo=1;useinbandfec=1\r\n"
+ "a=mid:audio\r\n"
+ "a=rtcp-mux\r\n"
+ "a=rtpmap:111 opus/48000/2\r\n"
+ "a=setup:active\r\n"
+ "a=ssrc:3463672643 cname:{ec9a356a-8d2c-504e-9977-99070a51f929}\r\n"
+ "m=video 9 UDP/TLS/RTP/SAVPF 100\r\n"
+ "c=IN IP4 0\r\n"
+ "a=sendrecv\r\n"
+ "a=rtpmap:100 VP8/90000\r\n"
+ "a=extmap:12 urn:ietf:params:rtp-hdrext:toffset\r\n"
+ "a=extmap:13 "
+ "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n";
+ auto result = mSessionAns->SetRemoteDescription(kJsepSdpOffer, sdp);
+ ASSERT_FALSE(result.mError.isSome());
+ auto answer = CreateAnswer();
+ result = mSessionAns->SetLocalDescription(kJsepSdpAnswer, answer);
+ ASSERT_FALSE(result.mError.isSome());
+
+ // Verify that we've negotiated the right extmap based on the unusual values
+ // in the offer.
+ auto transceivers = GetTransceivers(*mSessionAns);
+ ASSERT_EQ(2U, transceivers.size());
+ auto* audioSend = transceivers[0].mSendTrack.GetNegotiatedDetails();
+ auto* audioRecv = transceivers[0].mRecvTrack.GetNegotiatedDetails();
+ auto* videoSend = transceivers[1].mSendTrack.GetNegotiatedDetails();
+ auto* videoRecv = transceivers[1].mRecvTrack.GetNegotiatedDetails();
+ ASSERT_TRUE(audioSend);
+ ASSERT_TRUE(audioRecv);
+ ASSERT_TRUE(videoSend);
+ ASSERT_TRUE(videoRecv);
+ ASSERT_EQ(
+ 11U,
+ audioSend->GetExt("urn:ietf:params:rtp-hdrext:ssrc-audio-level")->entry);
+ ASSERT_EQ(
+ 11U,
+ audioRecv->GetExt("urn:ietf:params:rtp-hdrext:ssrc-audio-level")->entry);
+ ASSERT_EQ(12U,
+ videoSend->GetExt("urn:ietf:params:rtp-hdrext:toffset")->entry);
+ ASSERT_EQ(12U,
+ videoRecv->GetExt("urn:ietf:params:rtp-hdrext:toffset")->entry);
+ ASSERT_EQ(
+ 13U,
+ videoSend
+ ->GetExt("http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time")
+ ->entry);
+ ASSERT_EQ(
+ 13U,
+ videoRecv
+ ->GetExt("http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time")
+ ->entry);
+
+ SwapOfferAnswerRoles();
+
+ // Make sure a reoffer uses the negotiated extmap
+ auto reoffer = CreateOffer();
+ ASSERT_NE(std::string::npos, reoffer.find("a=extmap:11"));
+ ASSERT_NE(std::string::npos, reoffer.find("a=extmap:12"));
+ ASSERT_NE(std::string::npos, reoffer.find("a=extmap:13"));
+}
+
+TEST_F(JsepSessionTest, TestNegotiatedExtmapCollision) {
+ AddTracks(*mSessionAns, "audio");
+ // ssrc-audio-level will be extmap 1 for both
+ // csrc-audio-level will be 2 for both
+ // mid will be 3 for both
+ mSessionAns->AddAudioRtpExtension("foo");
+ mSessionAns->AddAudioRtpExtension("bar");
+ mSessionAns->AddAudioRtpExtension("baz");
+
+ // Set up an offer that uses the same extmap entries, but for different
+ // things, causing collisions.
+ std::string sdp =
+ "v=0\r\n"
+ "o=- 6 2 IN IP4 1r\r\n"
+ "t=0 0a\r\n"
+ "a=ice-ufrag:Xp\r\n"
+ "a=ice-pwd:he\r\n"
+ "a=setup:actpass\r\n"
+ "a=fingerprint:sha-256 "
+ "DC:FC:25:56:2B:88:77:2F:E4:FA:97:4E:2E:F1:D6:34:A6:A0:11:E2:E4:38:B3:98:"
+ "08:D2:F7:9D:F5:E2:C1:15\r\n"
+ "m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n"
+ "c=IN IP4 51.81.107.13\r\n"
+ "a=sendrecv\r\n"
+ "a=extmap:1 foo\r\n"
+ "a=extmap:2 bar\r\n"
+ "a=extmap:3 baz\r\n"
+ "a=extmap:11 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n"
+ "a=fmtp:111 maxplaybackrate=48000;stereo=1;useinbandfec=1\r\n"
+ "a=mid:audio\r\n"
+ "a=rtcp-mux\r\n"
+ "a=rtpmap:111 opus/48000/2\r\n"
+ "a=setup:active\r\n"
+ "a=ssrc:3463672643 cname:{ec9a356a-8d2c-504e-9977-99070a51f929}\r\n";
+ auto result = mSessionAns->SetRemoteDescription(kJsepSdpOffer, sdp);
+ ASSERT_FALSE(result.mError.isSome());
+ auto answer = CreateAnswer();
+ result = mSessionAns->SetLocalDescription(kJsepSdpAnswer, answer);
+ ASSERT_FALSE(result.mError.isSome());
+
+ // Verify that we've negotiated the right extmap based on the unusual values
+ // in the offer.
+ auto transceivers = GetTransceivers(*mSessionAns);
+ ASSERT_EQ(1U, transceivers.size());
+ auto* audioSend = transceivers[0].mSendTrack.GetNegotiatedDetails();
+ auto* audioRecv = transceivers[0].mRecvTrack.GetNegotiatedDetails();
+ ASSERT_TRUE(audioSend);
+ ASSERT_TRUE(audioRecv);
+ ASSERT_EQ(1U, audioSend->GetExt("foo")->entry);
+ ASSERT_EQ(1U, audioRecv->GetExt("foo")->entry);
+ ASSERT_EQ(2U, audioSend->GetExt("bar")->entry);
+ ASSERT_EQ(2U, audioRecv->GetExt("bar")->entry);
+ ASSERT_EQ(3U, audioSend->GetExt("baz")->entry);
+ ASSERT_EQ(3U, audioRecv->GetExt("baz")->entry);
+ ASSERT_EQ(
+ 11U,
+ audioSend->GetExt("urn:ietf:params:rtp-hdrext:ssrc-audio-level")->entry);
+ ASSERT_EQ(
+ 11U,
+ audioRecv->GetExt("urn:ietf:params:rtp-hdrext:ssrc-audio-level")->entry);
+ SwapOfferAnswerRoles();
+
+ // Make sure a reoffer uses the negotiated extmap
+ auto reoffer = CreateOffer();
+ ASSERT_NE(std::string::npos, reoffer.find("a=extmap:1 foo"));
+ ASSERT_NE(std::string::npos, reoffer.find("a=extmap:2 bar"));
+ ASSERT_NE(std::string::npos, reoffer.find("a=extmap:3 baz"));
+ ASSERT_NE(
+ std::string::npos,
+ reoffer.find("a=extmap:11 urn:ietf:params:rtp-hdrext:ssrc-audio-level"));
+
+ // Ensure no duplicates
+ ASSERT_EQ(std::string::npos,
+ reoffer.find("a=extmap:1 ", reoffer.find("a=extmap:1 ") + 1));
+ ASSERT_EQ(std::string::npos,
+ reoffer.find("a=extmap:2 ", reoffer.find("a=extmap:2 ") + 1));
+ ASSERT_EQ(std::string::npos,
+ reoffer.find("a=extmap:3 ", reoffer.find("a=extmap:3 ") + 1));
+ ASSERT_EQ(std::string::npos,
+ reoffer.find("a=extmap:11 ", reoffer.find("a=extmap:11 ") + 1));
+}
+
+TEST_F(JsepSessionTest, TestExtmapAnswerChangesId) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer, ALL_CHECKS);
+ SetRemoteOffer(offer, ALL_CHECKS);
+
+ std::string answer = CreateAnswer();
+ std::string mungedAnswer =
+ SetExtmap(answer, "urn:ietf:params:rtp-hdrext:sdes:mid", 14);
+ JsepSession::Result result =
+ mSessionOff->SetRemoteDescription(kJsepSdpAnswer, mungedAnswer);
+ ASSERT_TRUE(result.mError.isSome());
+ ASSERT_EQ(dom::PCError::InvalidAccessError, *result.mError);
+ ASSERT_NE(mSessionOff->GetLastError().find(
+ "Answer changed id for extmap attribute at level 0"),
+ std::string::npos);
+}
+
+TEST_F(JsepSessionTest, TestExtmapChangeId) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+
+ OfferAnswer();
+
+ // RFC 5285 does not seem to forbid giving a pre-existing extension a new id,
+ // as long as that id has never been used before
+ {
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer, ALL_CHECKS);
+ uint16_t oldId = 0;
+ std::string mungedOffer =
+ SetExtmap(offer, "urn:ietf:params:rtp-hdrext:sdes:mid", 14, &oldId);
+ ASSERT_NE(oldId, 0);
+ SetRemoteOffer(mungedOffer, ALL_CHECKS);
+
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer, ALL_CHECKS);
+
+ std::string mungedAnswer =
+ SetExtmap(answer, "urn:ietf:params:rtp-hdrext:sdes:mid", oldId);
+ SetRemoteAnswer(mungedAnswer, ALL_CHECKS);
+ }
+
+ // Make sure going back to the previous id works
+ OfferAnswer();
+}
+
+TEST_F(JsepSessionTest, TestExtmapSwap) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+
+ OfferAnswer();
+
+ std::string offer = CreateOffer();
+ uint16_t midId = GetExtmap(offer, "urn:ietf:params:rtp-hdrext:sdes:mid");
+ uint16_t ssrcLevelId = 0;
+ std::string mungedOffer =
+ SetExtmap(offer, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", midId,
+ &ssrcLevelId);
+ mungedOffer = SetExtmap(mungedOffer, "urn:ietf:params:rtp-hdrext:sdes:mid",
+ ssrcLevelId);
+
+ JsepSession::Result result =
+ mSessionAns->SetRemoteDescription(kJsepSdpOffer, mungedOffer);
+ ASSERT_TRUE(result.mError.isSome());
+ ASSERT_EQ(dom::PCError::InvalidAccessError, *result.mError);
+ ASSERT_NE(mSessionAns->GetLastError().find(
+ "Remote description attempted to remap RTP extension id"),
+ std::string::npos);
+}
+
+TEST_F(JsepSessionTest, TestExtmapReuse) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+
+ OfferAnswer();
+
+ std::string offer = CreateOffer();
+ UniquePtr<Sdp> munge(Parse(offer));
+ ASSERT_EQ(1U, munge->GetMediaSectionCount());
+
+ auto& offerMediaAttrs = munge->GetMediaSection(0).GetAttributeList();
+ ASSERT_TRUE(offerMediaAttrs.HasAttribute(SdpAttribute::kExtmapAttribute));
+ auto offerExtmap = offerMediaAttrs.GetExtmap();
+ for (auto& ext : offerExtmap.mExtmaps) {
+ if (ext.extensionname == "urn:ietf:params:rtp-hdrext:ssrc-audio-level") {
+ ext.extensionname = "foo";
+ }
+ }
+
+ offerMediaAttrs.SetAttribute(offerExtmap.Clone());
+
+ std::string sdpString = munge->ToString();
+ JsepSession::Result result =
+ mSessionAns->SetRemoteDescription(kJsepSdpOffer, sdpString);
+ ASSERT_TRUE(result.mError.isSome());
+ ASSERT_EQ(dom::PCError::InvalidAccessError, *result.mError);
+ ASSERT_NE(mSessionAns->GetLastError().find(
+ "Remote description attempted to remap RTP extension id"),
+ std::string::npos);
+}
+
+TEST_F(JsepSessionTest, TestExtmapReuseAfterRenegotiation) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+
+ OfferAnswer();
+
+ // Renegotiate without ssrc-audio-level
+ {
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer, ALL_CHECKS);
+ // Passing 0 removes urn:ietf:params:rtp-hdrext:ssrc-audio-level
+ std::string mungedOffer =
+ SetExtmap(offer, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", 0);
+ SetRemoteOffer(mungedOffer, ALL_CHECKS);
+
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer, ALL_CHECKS);
+ SetRemoteAnswer(answer, ALL_CHECKS);
+ }
+
+ // Make sure trying to reuse the id for ssrc-audio-level fails, even though we
+ // did not use it last round.
+ {
+ std::string offer = CreateOffer();
+ UniquePtr<Sdp> munge(Parse(offer));
+ ASSERT_EQ(1U, munge->GetMediaSectionCount());
+
+ auto& offerMediaAttrs = munge->GetMediaSection(0).GetAttributeList();
+ ASSERT_TRUE(offerMediaAttrs.HasAttribute(SdpAttribute::kExtmapAttribute));
+ auto offerExtmap = offerMediaAttrs.GetExtmap();
+ for (auto& ext : offerExtmap.mExtmaps) {
+ if (ext.extensionname == "urn:ietf:params:rtp-hdrext:ssrc-audio-level") {
+ ext.extensionname = "foo";
+ }
+ }
+
+ offerMediaAttrs.SetAttribute(offerExtmap.Clone());
+
+ std::string sdpString = munge->ToString();
+ JsepSession::Result result =
+ mSessionAns->SetRemoteDescription(kJsepSdpOffer, sdpString);
+ ASSERT_TRUE(result.mError.isSome());
+ ASSERT_EQ(dom::PCError::InvalidAccessError, *result.mError);
+ ASSERT_NE(mSessionAns->GetLastError().find(
+ "Remote description attempted to remap RTP extension id"),
+ std::string::npos);
+ }
+}
+
+TEST_F(JsepSessionTest, TestRtcpFbStar) {
+ AddTracks(*mSessionOff, "video");
+ AddTracks(*mSessionAns, "video");
+
+ std::string offer = CreateOffer();
+
+ UniquePtr<Sdp> parsedOffer(Parse(offer));
+ auto* rtcpfbs = new SdpRtcpFbAttributeList;
+ rtcpfbs->PushEntry("*", SdpRtcpFbAttributeList::kNack);
+ parsedOffer->GetMediaSection(0).GetAttributeList().SetAttribute(rtcpfbs);
+ offer = parsedOffer->ToString();
+
+ SetLocalOffer(offer, CHECK_SUCCESS);
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer, CHECK_SUCCESS);
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+
+ ASSERT_EQ(1U, GetRemoteTracks(*mSessionAns).size());
+ JsepTrack track = GetRemoteTracks(*mSessionAns)[0];
+ ASSERT_TRUE(track.GetNegotiatedDetails());
+ auto* details = track.GetNegotiatedDetails();
+ for (const auto& codec : details->GetEncoding(0).GetCodecs()) {
+ const JsepVideoCodecDescription* videoCodec =
+ static_cast<const JsepVideoCodecDescription*>(codec.get());
+ ASSERT_EQ(1U, videoCodec->mNackFbTypes.size());
+ ASSERT_EQ("", videoCodec->mNackFbTypes[0]);
+ }
+}
+
+TEST_F(JsepSessionTest, TestUniquePayloadTypes) {
+ // The audio payload types will all appear more than once, but the video
+ // payload types will be unique.
+ AddTracks(*mSessionOff, "audio,audio,video");
+ AddTracks(*mSessionAns, "audio,audio,video");
+
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer, CHECK_SUCCESS);
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer, CHECK_SUCCESS);
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+
+ auto offerTransceivers = GetTransceivers(*mSessionOff);
+ auto answerTransceivers = GetTransceivers(*mSessionAns);
+ ASSERT_EQ(3U, offerTransceivers.size());
+ ASSERT_EQ(3U, answerTransceivers.size());
+
+ ASSERT_FALSE(IsNull(offerTransceivers[0].mRecvTrack));
+ ASSERT_TRUE(offerTransceivers[0].mRecvTrack.GetNegotiatedDetails());
+ ASSERT_EQ(0U, offerTransceivers[0]
+ .mRecvTrack.GetNegotiatedDetails()
+ ->GetUniquePayloadTypes()
+ .size());
+
+ ASSERT_FALSE(IsNull(offerTransceivers[1].mRecvTrack));
+ ASSERT_TRUE(offerTransceivers[1].mRecvTrack.GetNegotiatedDetails());
+ ASSERT_EQ(0U, offerTransceivers[1]
+ .mRecvTrack.GetNegotiatedDetails()
+ ->GetUniquePayloadTypes()
+ .size());
+
+ ASSERT_FALSE(IsNull(offerTransceivers[2].mRecvTrack));
+ ASSERT_TRUE(offerTransceivers[2].mRecvTrack.GetNegotiatedDetails());
+ ASSERT_NE(0U, offerTransceivers[2]
+ .mRecvTrack.GetNegotiatedDetails()
+ ->GetUniquePayloadTypes()
+ .size());
+
+ ASSERT_FALSE(IsNull(answerTransceivers[0].mRecvTrack));
+ ASSERT_TRUE(answerTransceivers[0].mRecvTrack.GetNegotiatedDetails());
+ ASSERT_EQ(0U, answerTransceivers[0]
+ .mRecvTrack.GetNegotiatedDetails()
+ ->GetUniquePayloadTypes()
+ .size());
+
+ ASSERT_FALSE(IsNull(answerTransceivers[1].mRecvTrack));
+ ASSERT_TRUE(answerTransceivers[1].mRecvTrack.GetNegotiatedDetails());
+ ASSERT_EQ(0U, answerTransceivers[1]
+ .mRecvTrack.GetNegotiatedDetails()
+ ->GetUniquePayloadTypes()
+ .size());
+
+ ASSERT_FALSE(IsNull(answerTransceivers[2].mRecvTrack));
+ ASSERT_TRUE(answerTransceivers[2].mRecvTrack.GetNegotiatedDetails());
+ ASSERT_NE(0U, answerTransceivers[2]
+ .mRecvTrack.GetNegotiatedDetails()
+ ->GetUniquePayloadTypes()
+ .size());
+}
+
+TEST_F(JsepSessionTest, UnknownFingerprintAlgorithm) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+
+ std::string offer(CreateOffer());
+ SetLocalOffer(offer);
+ ReplaceAll("fingerprint:sha", "fingerprint:foo", &offer);
+ JsepSession::Result result =
+ mSessionAns->SetRemoteDescription(kJsepSdpOffer, offer);
+ ASSERT_EQ(dom::PCError::InvalidAccessError, *result.mError);
+ ASSERT_NE("", mSessionAns->GetLastError());
+}
+
+TEST(H264ProfileLevelIdTest, TestLevelComparisons)
+{
+ ASSERT_LT(JsepVideoCodecDescription::GetSaneH264Level(0x421D0B), // 1b
+ JsepVideoCodecDescription::GetSaneH264Level(0x420D0B)); // 1.1
+ ASSERT_LT(JsepVideoCodecDescription::GetSaneH264Level(0x420D0A), // 1.0
+ JsepVideoCodecDescription::GetSaneH264Level(0x421D0B)); // 1b
+ ASSERT_LT(JsepVideoCodecDescription::GetSaneH264Level(0x420D0A), // 1.0
+ JsepVideoCodecDescription::GetSaneH264Level(0x420D0B)); // 1.1
+
+ ASSERT_LT(JsepVideoCodecDescription::GetSaneH264Level(0x640009), // 1b
+ JsepVideoCodecDescription::GetSaneH264Level(0x64000B)); // 1.1
+ ASSERT_LT(JsepVideoCodecDescription::GetSaneH264Level(0x64000A), // 1.0
+ JsepVideoCodecDescription::GetSaneH264Level(0x640009)); // 1b
+ ASSERT_LT(JsepVideoCodecDescription::GetSaneH264Level(0x64000A), // 1.0
+ JsepVideoCodecDescription::GetSaneH264Level(0x64000B)); // 1.1
+}
+
+TEST(H264ProfileLevelIdTest, TestLevelSetting)
+{
+ uint32_t profileLevelId = 0x420D0A;
+ JsepVideoCodecDescription::SetSaneH264Level(
+ JsepVideoCodecDescription::GetSaneH264Level(0x42100B), &profileLevelId);
+ ASSERT_EQ((uint32_t)0x421D0B, profileLevelId);
+
+ JsepVideoCodecDescription::SetSaneH264Level(
+ JsepVideoCodecDescription::GetSaneH264Level(0x42000A), &profileLevelId);
+ ASSERT_EQ((uint32_t)0x420D0A, profileLevelId);
+
+ profileLevelId = 0x6E100A;
+ JsepVideoCodecDescription::SetSaneH264Level(
+ JsepVideoCodecDescription::GetSaneH264Level(0x640009), &profileLevelId);
+ ASSERT_EQ((uint32_t)0x6E1009, profileLevelId);
+
+ JsepVideoCodecDescription::SetSaneH264Level(
+ JsepVideoCodecDescription::GetSaneH264Level(0x64000B), &profileLevelId);
+ ASSERT_EQ((uint32_t)0x6E100B, profileLevelId);
+}
+
+TEST_F(JsepSessionTest, StronglyPreferredCodec) {
+ for (auto& codec : mSessionAns->Codecs()) {
+ if (codec->mName == "H264") {
+ codec->mStronglyPreferred = true;
+ }
+ }
+
+ types.push_back(SdpMediaSection::kVideo);
+ AddTracks(*mSessionOff, "video");
+ AddTracks(*mSessionAns, "video");
+
+ OfferAnswer();
+
+ UniquePtr<JsepCodecDescription> codec;
+ GetCodec(*mSessionAns, 0, sdp::kSend, 0, 0, &codec);
+ ASSERT_TRUE(codec);
+ ASSERT_EQ("H264", codec->mName);
+ GetCodec(*mSessionAns, 0, sdp::kRecv, 0, 0, &codec);
+ ASSERT_TRUE(codec);
+ ASSERT_EQ("H264", codec->mName);
+}
+
+TEST_F(JsepSessionTest, LowDynamicPayloadType) {
+ SetPayloadTypeNumber(*mSessionOff, "opus", "12");
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+
+ OfferAnswer();
+ UniquePtr<JsepCodecDescription> codec;
+ GetCodec(*mSessionAns, 0, sdp::kSend, 0, 0, &codec);
+ ASSERT_TRUE(codec);
+ ASSERT_EQ("opus", codec->mName);
+ ASSERT_EQ("12", codec->mDefaultPt);
+ GetCodec(*mSessionAns, 0, sdp::kRecv, 0, 0, &codec);
+ ASSERT_TRUE(codec);
+ ASSERT_EQ("opus", codec->mName);
+ ASSERT_EQ("12", codec->mDefaultPt);
+}
+
+TEST_F(JsepSessionTest, TestOfferPTAsymmetry) {
+ SetPayloadTypeNumber(*mSessionAns, "opus", "105");
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+ JsepOfferOptions options;
+
+ // Ensure that mSessionAns is appropriately configured. Also ensure that
+ // creating an offer with 105 does not prompt mSessionAns to ignore the
+ // PT in the offer.
+ std::string offer;
+ JsepSession::Result result = mSessionAns->CreateOffer(options, &offer);
+ ASSERT_FALSE(result.mError.isSome());
+ ASSERT_NE(std::string::npos, offer.find("a=rtpmap:105 opus")) << offer;
+
+ OfferAnswer();
+
+ // Answerer should use what the offerer suggested
+ UniquePtr<JsepCodecDescription> codec;
+ GetCodec(*mSessionAns, 0, sdp::kSend, 0, 0, &codec);
+ ASSERT_TRUE(codec);
+ ASSERT_EQ("opus", codec->mName);
+ ASSERT_EQ("109", codec->mDefaultPt);
+ GetCodec(*mSessionAns, 0, sdp::kRecv, 0, 0, &codec);
+ ASSERT_TRUE(codec);
+ ASSERT_EQ("opus", codec->mName);
+ ASSERT_EQ("109", codec->mDefaultPt);
+
+ // Answerer should not change when it reoffers
+ result = mSessionAns->CreateOffer(options, &offer);
+ ASSERT_FALSE(result.mError.isSome());
+ ASSERT_NE(std::string::npos, offer.find("a=rtpmap:109 opus")) << offer;
+}
+
+TEST_F(JsepSessionTest, TestAnswerPTAsymmetry) {
+ // JsepSessionImpl will never answer with an asymmetric payload type
+ // (tested in TestOfferPTAsymmetry), so we have to rewrite SDP a little.
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+
+ Replace("a=rtpmap:109 opus", "a=rtpmap:105 opus", &offer);
+ Replace("m=audio 9 UDP/TLS/RTP/SAVPF 109", "m=audio 9 UDP/TLS/RTP/SAVPF 105",
+ &offer);
+ ReplaceAll("a=fmtp:109", "a=fmtp:105", &offer);
+ SetRemoteOffer(offer);
+
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+ SetRemoteAnswer(answer);
+
+ UniquePtr<JsepCodecDescription> codec;
+ GetCodec(*mSessionOff, 0, sdp::kSend, 0, 0, &codec);
+ ASSERT_TRUE(codec);
+ ASSERT_EQ("opus", codec->mName);
+ ASSERT_EQ("105", codec->mDefaultPt);
+ GetCodec(*mSessionOff, 0, sdp::kRecv, 0, 0, &codec);
+ ASSERT_TRUE(codec);
+ ASSERT_EQ("opus", codec->mName);
+ ASSERT_EQ("109", codec->mDefaultPt);
+
+ GetCodec(*mSessionAns, 0, sdp::kSend, 0, 0, &codec);
+ ASSERT_TRUE(codec);
+ ASSERT_EQ("opus", codec->mName);
+ ASSERT_EQ("105", codec->mDefaultPt);
+ GetCodec(*mSessionAns, 0, sdp::kRecv, 0, 0, &codec);
+ ASSERT_TRUE(codec);
+ ASSERT_EQ("opus", codec->mName);
+ ASSERT_EQ("105", codec->mDefaultPt);
+
+ // Offerer should use 105 for reoffers
+ offer = CreateOffer();
+ ASSERT_NE(std::string::npos, offer.find("a=rtpmap:105 opus")) << offer;
+ ASSERT_EQ(std::string::npos, offer.find("a=rtpmap:109 opus")) << offer;
+ ASSERT_NE(std::string::npos, offer.find("a=fmtp:105")) << offer;
+ ASSERT_EQ(std::string::npos, offer.find("a=fmtp:109")) << offer;
+}
+
+TEST_F(JsepSessionTest, PayloadTypeClash) {
+ // Set up a scenario where mSessionAns' favorite codec (opus) is unsupported
+ // by mSessionOff, and mSessionOff uses that payload type for something else.
+ SetCodecEnabled(*mSessionOff, "opus", false);
+ SetPayloadTypeNumber(*mSessionOff, "opus", "0");
+ SetPayloadTypeNumber(*mSessionOff, "G722", "109");
+ SetPayloadTypeNumber(*mSessionAns, "opus", "109");
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+
+ OfferAnswer();
+ UniquePtr<JsepCodecDescription> codec;
+ GetCodec(*mSessionAns, 0, sdp::kSend, 0, 0, &codec);
+ ASSERT_TRUE(codec);
+ ASSERT_EQ("G722", codec->mName);
+ ASSERT_EQ("109", codec->mDefaultPt);
+ GetCodec(*mSessionAns, 0, sdp::kRecv, 0, 0, &codec);
+ ASSERT_TRUE(codec);
+ ASSERT_EQ("G722", codec->mName);
+ ASSERT_EQ("109", codec->mDefaultPt);
+
+ // Now, make sure that mSessionAns does not put a=rtpmap:109 opus in a
+ // reoffer, since pt 109 is taken for PCMU (the answerer still supports opus,
+ // and will reoffer it, but it should choose a new payload type for it)
+ JsepOfferOptions options;
+ std::string reoffer;
+ JsepSession::Result result = mSessionAns->CreateOffer(options, &reoffer);
+ ASSERT_FALSE(result.mError.isSome());
+ ASSERT_EQ(std::string::npos, reoffer.find("a=rtpmap:109 opus")) << reoffer;
+ ASSERT_NE(std::string::npos, reoffer.find(" opus")) << reoffer;
+}
+
+TEST_P(JsepSessionTest, TestGlareRollback) {
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+ JsepOfferOptions options;
+
+ std::string offer;
+ ASSERT_FALSE(mSessionAns->CreateOffer(options, &offer).mError.isSome());
+ ASSERT_FALSE(
+ mSessionAns->SetLocalDescription(kJsepSdpOffer, offer).mError.isSome());
+ ASSERT_EQ(kJsepStateHaveLocalOffer, mSessionAns->GetState());
+
+ ASSERT_FALSE(mSessionOff->CreateOffer(options, &offer).mError.isSome());
+ ASSERT_FALSE(
+ mSessionOff->SetLocalDescription(kJsepSdpOffer, offer).mError.isSome());
+ ASSERT_EQ(kJsepStateHaveLocalOffer, mSessionOff->GetState());
+
+ ASSERT_EQ(dom::PCError::InvalidStateError,
+ *mSessionAns->SetRemoteDescription(kJsepSdpOffer, offer).mError);
+ ASSERT_FALSE(
+ mSessionAns->SetLocalDescription(kJsepSdpRollback, "").mError.isSome());
+ ASSERT_EQ(kJsepStateStable, mSessionAns->GetState());
+
+ SetRemoteOffer(offer);
+
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+ SetRemoteAnswer(answer);
+}
+
+TEST_P(JsepSessionTest, TestRejectOfferRollback) {
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+
+ ASSERT_FALSE(
+ mSessionAns->SetRemoteDescription(kJsepSdpRollback, "").mError.isSome());
+ ASSERT_EQ(kJsepStateStable, mSessionAns->GetState());
+ for (const auto& transceiver : GetTransceivers(*mSessionAns)) {
+ ASSERT_EQ(0U, transceiver.mRecvTrack.GetStreamIds().size());
+ }
+
+ ASSERT_FALSE(
+ mSessionOff->SetLocalDescription(kJsepSdpRollback, "").mError.isSome());
+ ASSERT_EQ(kJsepStateStable, mSessionOff->GetState());
+
+ OfferAnswer();
+}
+
+TEST_P(JsepSessionTest, TestInvalidRollback) {
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ ASSERT_EQ(dom::PCError::InvalidStateError,
+ *mSessionOff->SetLocalDescription(kJsepSdpRollback, "").mError);
+ ASSERT_EQ(dom::PCError::InvalidStateError,
+ *mSessionOff->SetRemoteDescription(kJsepSdpRollback, "").mError);
+
+ std::string offer = CreateOffer();
+ ASSERT_EQ(dom::PCError::InvalidStateError,
+ *mSessionOff->SetLocalDescription(kJsepSdpRollback, "").mError);
+ ASSERT_EQ(dom::PCError::InvalidStateError,
+ *mSessionOff->SetRemoteDescription(kJsepSdpRollback, "").mError);
+
+ SetLocalOffer(offer);
+ ASSERT_EQ(dom::PCError::InvalidStateError,
+ *mSessionOff->SetRemoteDescription(kJsepSdpRollback, "").mError);
+
+ SetRemoteOffer(offer);
+ ASSERT_EQ(dom::PCError::InvalidStateError,
+ *mSessionAns->SetLocalDescription(kJsepSdpRollback, "").mError);
+
+ std::string answer = CreateAnswer();
+ ASSERT_EQ(dom::PCError::InvalidStateError,
+ *mSessionAns->SetLocalDescription(kJsepSdpRollback, "").mError);
+
+ SetLocalAnswer(answer);
+ ASSERT_EQ(dom::PCError::InvalidStateError,
+ *mSessionAns->SetLocalDescription(kJsepSdpRollback, "").mError);
+ ASSERT_EQ(dom::PCError::InvalidStateError,
+ *mSessionAns->SetRemoteDescription(kJsepSdpRollback, "").mError);
+
+ SetRemoteAnswer(answer);
+ ASSERT_EQ(dom::PCError::InvalidStateError,
+ *mSessionOff->SetLocalDescription(kJsepSdpRollback, "").mError);
+ ASSERT_EQ(dom::PCError::InvalidStateError,
+ *mSessionOff->SetRemoteDescription(kJsepSdpRollback, "").mError);
+}
+
+size_t GetActiveTransportCount(const JsepSession& session) {
+ size_t activeTransportCount = 0;
+ for (const auto& transceiver : JsepSessionTest::GetTransceivers(session)) {
+ if (!transceiver.HasBundleLevel() ||
+ (transceiver.BundleLevel() == transceiver.GetLevel())) {
+ activeTransportCount += transceiver.mTransport.mComponents;
+ }
+ }
+ return activeTransportCount;
+}
+
+TEST_P(JsepSessionTest, TestBalancedBundle) {
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ mSessionOff->SetBundlePolicy(kBundleBalanced);
+
+ std::string offer = CreateOffer();
+ UniquePtr<Sdp> parsedOffer = std::move(SipccSdpParser().Parse(offer)->Sdp());
+
+ ASSERT_TRUE(parsedOffer.get());
+
+ std::map<SdpMediaSection::MediaType, SdpMediaSection*> firstByType;
+
+ for (size_t i = 0; i < parsedOffer->GetMediaSectionCount(); ++i) {
+ SdpMediaSection& msection(parsedOffer->GetMediaSection(i));
+ bool firstOfType = !firstByType.count(msection.GetMediaType());
+ if (firstOfType) {
+ firstByType[msection.GetMediaType()] = &msection;
+ }
+ ASSERT_EQ(!firstOfType, msection.GetAttributeList().HasAttribute(
+ SdpAttribute::kBundleOnlyAttribute));
+ }
+
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+ SetRemoteAnswer(answer);
+
+ CheckTransceiversAreBundled(*mSessionOff, "Offerer transceivers");
+ CheckTransceiversAreBundled(*mSessionAns, "Answerer transceivers");
+ EXPECT_EQ(1U, GetActiveTransportCount(*mSessionOff));
+ EXPECT_EQ(1U, GetActiveTransportCount(*mSessionAns));
+}
+
+TEST_P(JsepSessionTest, TestMaxBundle) {
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ mSessionOff->SetBundlePolicy(kBundleMaxBundle);
+ std::string offer = CreateOffer();
+ UniquePtr<Sdp> parsedOffer = std::move(SipccSdpParser().Parse(offer)->Sdp());
+
+ ASSERT_TRUE(parsedOffer.get());
+
+ ASSERT_FALSE(parsedOffer->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kBundleOnlyAttribute));
+ ASSERT_NE(0U, parsedOffer->GetMediaSection(0).GetPort());
+ for (size_t i = 1; i < parsedOffer->GetMediaSectionCount(); ++i) {
+ ASSERT_TRUE(parsedOffer->GetMediaSection(i).GetAttributeList().HasAttribute(
+ SdpAttribute::kBundleOnlyAttribute));
+ ASSERT_EQ(0U, parsedOffer->GetMediaSection(i).GetPort());
+ }
+
+ SetLocalOffer(offer);
+ for (const auto& transceiver : GetTransceivers(*mSessionOff)) {
+ if (transceiver.GetLevel() == 0) {
+ // We do not set the bundle-level in have-local-offer unless the
+ // m-section is bundle-only.
+ ASSERT_FALSE(transceiver.HasBundleLevel());
+ } else {
+ ASSERT_TRUE(transceiver.HasBundleLevel());
+ ASSERT_EQ(0U, transceiver.BundleLevel());
+ }
+ ASSERT_NE("", transceiver.mTransport.mTransportId);
+ }
+
+ SetRemoteOffer(offer);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+ SetRemoteAnswer(answer);
+
+ CheckTransceiversAreBundled(*mSessionOff, "Offerer transceivers");
+ CheckTransceiversAreBundled(*mSessionAns, "Answerer transceivers");
+ EXPECT_EQ(1U, GetActiveTransportCount(*mSessionOff));
+ EXPECT_EQ(1U, GetActiveTransportCount(*mSessionAns));
+}
+
+TEST_F(JsepSessionTest, TestNonDefaultProtocol) {
+ AddTracks(*mSessionOff, "audio,video,datachannel");
+ AddTracks(*mSessionAns, "audio,video,datachannel");
+
+ std::string offer;
+ ASSERT_FALSE(
+ mSessionOff->CreateOffer(JsepOfferOptions(), &offer).mError.isSome());
+ offer.replace(offer.find("UDP/TLS/RTP/SAVPF"), strlen("UDP/TLS/RTP/SAVPF"),
+ "RTP/SAVPF");
+ offer.replace(offer.find("UDP/TLS/RTP/SAVPF"), strlen("UDP/TLS/RTP/SAVPF"),
+ "RTP/SAVPF");
+ mSessionOff->SetLocalDescription(kJsepSdpOffer, offer);
+ mSessionAns->SetRemoteDescription(kJsepSdpOffer, offer);
+
+ std::string answer;
+ mSessionAns->CreateAnswer(JsepAnswerOptions(), &answer);
+ UniquePtr<Sdp> parsedAnswer = Parse(answer);
+ ASSERT_EQ(3U, parsedAnswer->GetMediaSectionCount());
+ ASSERT_EQ(SdpMediaSection::kRtpSavpf,
+ parsedAnswer->GetMediaSection(0).GetProtocol());
+ ASSERT_EQ(SdpMediaSection::kRtpSavpf,
+ parsedAnswer->GetMediaSection(1).GetProtocol());
+
+ mSessionAns->SetLocalDescription(kJsepSdpAnswer, answer);
+ mSessionOff->SetRemoteDescription(kJsepSdpAnswer, answer);
+
+ // Make sure reoffer uses the same protocol as before
+ mSessionOff->CreateOffer(JsepOfferOptions(), &offer);
+ UniquePtr<Sdp> parsedOffer = Parse(offer);
+ ASSERT_EQ(3U, parsedOffer->GetMediaSectionCount());
+ ASSERT_EQ(SdpMediaSection::kRtpSavpf,
+ parsedOffer->GetMediaSection(0).GetProtocol());
+ ASSERT_EQ(SdpMediaSection::kRtpSavpf,
+ parsedOffer->GetMediaSection(1).GetProtocol());
+
+ // Make sure reoffer from other side uses the same protocol as before
+ mSessionAns->CreateOffer(JsepOfferOptions(), &offer);
+ parsedOffer = Parse(offer);
+ ASSERT_EQ(3U, parsedOffer->GetMediaSectionCount());
+ ASSERT_EQ(SdpMediaSection::kRtpSavpf,
+ parsedOffer->GetMediaSection(0).GetProtocol());
+ ASSERT_EQ(SdpMediaSection::kRtpSavpf,
+ parsedOffer->GetMediaSection(1).GetProtocol());
+}
+
+TEST_F(JsepSessionTest, CreateOfferNoVideoStreamRecvVideo) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+
+ JsepOfferOptions options;
+ options.mOfferToReceiveAudio = Some(static_cast<size_t>(1U));
+ options.mOfferToReceiveVideo = Some(static_cast<size_t>(1U));
+
+ CreateOffer(Some(options));
+}
+
+TEST_F(JsepSessionTest, CreateOfferNoAudioStreamRecvAudio) {
+ types.push_back(SdpMediaSection::kVideo);
+ AddTracks(*mSessionOff, "video");
+
+ JsepOfferOptions options;
+ options.mOfferToReceiveAudio = Some(static_cast<size_t>(1U));
+ options.mOfferToReceiveVideo = Some(static_cast<size_t>(1U));
+
+ CreateOffer(Some(options));
+}
+
+TEST_F(JsepSessionTest, CreateOfferNoVideoStream) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+
+ JsepOfferOptions options;
+ options.mOfferToReceiveAudio = Some(static_cast<size_t>(1U));
+ options.mOfferToReceiveVideo = Some(static_cast<size_t>(0U));
+
+ CreateOffer(Some(options));
+}
+
+TEST_F(JsepSessionTest, CreateOfferNoAudioStream) {
+ types.push_back(SdpMediaSection::kVideo);
+ AddTracks(*mSessionOff, "video");
+
+ JsepOfferOptions options;
+ options.mOfferToReceiveAudio = Some(static_cast<size_t>(0U));
+ options.mOfferToReceiveVideo = Some(static_cast<size_t>(1U));
+
+ CreateOffer(Some(options));
+}
+
+TEST_F(JsepSessionTest, CreateOfferDontReceiveAudio) {
+ types.push_back(SdpMediaSection::kAudio);
+ types.push_back(SdpMediaSection::kVideo);
+ AddTracks(*mSessionOff, "audio,video");
+
+ JsepOfferOptions options;
+ options.mOfferToReceiveAudio = Some(static_cast<size_t>(0U));
+ options.mOfferToReceiveVideo = Some(static_cast<size_t>(1U));
+
+ CreateOffer(Some(options));
+}
+
+TEST_F(JsepSessionTest, CreateOfferDontReceiveVideo) {
+ types.push_back(SdpMediaSection::kAudio);
+ types.push_back(SdpMediaSection::kVideo);
+ AddTracks(*mSessionOff, "audio,video");
+
+ JsepOfferOptions options;
+ options.mOfferToReceiveAudio = Some(static_cast<size_t>(1U));
+ options.mOfferToReceiveVideo = Some(static_cast<size_t>(0U));
+
+ CreateOffer(Some(options));
+}
+
+TEST_F(JsepSessionTest, CreateOfferRemoveAudioTrack) {
+ types.push_back(SdpMediaSection::kAudio);
+ types.push_back(SdpMediaSection::kVideo);
+ AddTracks(*mSessionOff, "audio,video");
+
+ SetDirection(*mSessionOff, 1, SdpDirectionAttribute::kSendonly);
+ JsepTrack removedTrack = RemoveTrack(*mSessionOff, 0);
+ ASSERT_FALSE(IsNull(removedTrack));
+
+ CreateOffer();
+}
+
+TEST_F(JsepSessionTest, CreateOfferDontReceiveAudioRemoveAudioTrack) {
+ types.push_back(SdpMediaSection::kAudio);
+ types.push_back(SdpMediaSection::kVideo);
+ AddTracks(*mSessionOff, "audio,video");
+
+ SetDirection(*mSessionOff, 0, SdpDirectionAttribute::kSendonly);
+ JsepTrack removedTrack = RemoveTrack(*mSessionOff, 0);
+ ASSERT_FALSE(IsNull(removedTrack));
+
+ CreateOffer();
+}
+
+TEST_F(JsepSessionTest, CreateOfferDontReceiveVideoRemoveVideoTrack) {
+ types.push_back(SdpMediaSection::kAudio);
+ types.push_back(SdpMediaSection::kVideo);
+ AddTracks(*mSessionOff, "audio,video");
+
+ JsepOfferOptions options;
+ options.mOfferToReceiveAudio = Some(static_cast<size_t>(1U));
+ options.mOfferToReceiveVideo = Some(static_cast<size_t>(0U));
+
+ JsepTrack removedTrack = RemoveTrack(*mSessionOff, 0);
+ ASSERT_FALSE(IsNull(removedTrack));
+
+ CreateOffer(Some(options));
+}
+
+static const std::string strSampleCandidate =
+ "a=candidate:1 1 UDP 2130706431 192.168.2.1 50005 typ host\r\n";
+
+static const unsigned short nSamplelevel = 2;
+
+TEST_F(JsepSessionTest, CreateOfferAddCandidate) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+
+ uint16_t level;
+ std::string mid;
+ bool skipped;
+ nsresult rv;
+ rv = mSessionOff->AddLocalIceCandidate(strSampleCandidate,
+ GetTransportId(*mSessionOff, 0), "",
+ &level, &mid, &skipped);
+ ASSERT_EQ(NS_OK, rv);
+}
+
+TEST_F(JsepSessionTest, AddIceCandidateEarly) {
+ uint16_t level;
+ std::string mid;
+ bool skipped;
+ nsresult rv;
+ rv = mSessionOff->AddLocalIceCandidate(strSampleCandidate,
+ GetTransportId(*mSessionOff, 0), "",
+ &level, &mid, &skipped);
+
+ // This can't succeed without a local description
+ ASSERT_NE(NS_OK, rv);
+}
+
+TEST_F(JsepSessionTest, OfferAnswerDontAddAudioStreamOnAnswerNoOptions) {
+ types.push_back(SdpMediaSection::kAudio);
+ types.push_back(SdpMediaSection::kVideo);
+ AddTracks(*mSessionOff, "audio,video");
+ AddTracks(*mSessionAns, "video");
+
+ JsepOfferOptions options;
+ options.mOfferToReceiveAudio = Some(static_cast<size_t>(1U));
+ options.mOfferToReceiveVideo = Some(static_cast<size_t>(1U));
+
+ CreateOffer(Some(options));
+ std::string offer = CreateOffer(Some(options));
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer, CHECK_SUCCESS);
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+}
+
+TEST_F(JsepSessionTest, OfferAnswerDontAddVideoStreamOnAnswerNoOptions) {
+ types.push_back(SdpMediaSection::kAudio);
+ types.push_back(SdpMediaSection::kVideo);
+ AddTracks(*mSessionOff, "audio,video");
+ AddTracks(*mSessionAns, "audio");
+
+ JsepOfferOptions options;
+ options.mOfferToReceiveAudio = Some(static_cast<size_t>(1U));
+ options.mOfferToReceiveVideo = Some(static_cast<size_t>(1U));
+
+ CreateOffer(Some(options));
+ std::string offer = CreateOffer(Some(options));
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer, CHECK_SUCCESS);
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+}
+
+TEST_F(JsepSessionTest, OfferAnswerDontAddAudioVideoStreamsOnAnswerNoOptions) {
+ types.push_back(SdpMediaSection::kAudio);
+ types.push_back(SdpMediaSection::kVideo);
+ AddTracks(*mSessionOff, "audio,video");
+ AddTracks(*mSessionAns);
+
+ JsepOfferOptions options;
+ options.mOfferToReceiveAudio = Some(static_cast<size_t>(1U));
+ options.mOfferToReceiveVideo = Some(static_cast<size_t>(1U));
+
+ OfferAnswer();
+}
+
+TEST_F(JsepSessionTest, OfferAndAnswerWithExtraCodec) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+ std::string answer = CreateAnswer();
+
+ UniquePtr<Sdp> munge = Parse(answer);
+ SdpMediaSection& mediaSection = munge->GetMediaSection(0);
+ mediaSection.AddCodec("8", "PCMA", 8000, 1);
+ std::string sdpString = munge->ToString();
+
+ SetLocalAnswer(sdpString);
+ SetRemoteAnswer(answer);
+}
+
+TEST_F(JsepSessionTest, AddCandidateInHaveLocalOffer) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+
+ std::string mid;
+ std::string transportId;
+ JsepSession::Result result = mSessionOff->AddRemoteIceCandidate(
+ strSampleCandidate, mid, Some(nSamplelevel), "", &transportId);
+ ASSERT_EQ(dom::PCError::InvalidStateError, *result.mError);
+}
+
+TEST_F(JsepSessionTest, SetLocalWithoutCreateOffer) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+
+ std::string offer = CreateOffer();
+ JsepSession::Result result =
+ mSessionAns->SetLocalDescription(kJsepSdpOffer, offer);
+ ASSERT_EQ(dom::PCError::InvalidModificationError, *result.mError);
+}
+
+TEST_F(JsepSessionTest, SetLocalWithoutCreateAnswer) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+
+ std::string offer = CreateOffer();
+ SetRemoteOffer(offer);
+ JsepSession::Result result =
+ mSessionAns->SetLocalDescription(kJsepSdpAnswer, offer);
+ ASSERT_EQ(dom::PCError::InvalidModificationError, *result.mError);
+}
+
+// Test for Bug 843595
+TEST_F(JsepSessionTest, missingUfrag) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+ std::string offer = CreateOffer();
+ std::string ufrag = "ice-ufrag";
+ std::size_t pos = offer.find(ufrag);
+ ASSERT_NE(pos, std::string::npos);
+ offer.replace(pos, ufrag.length(), "ice-ufrog");
+ JsepSession::Result result =
+ mSessionAns->SetRemoteDescription(kJsepSdpOffer, offer);
+ ASSERT_EQ(dom::PCError::InvalidAccessError, *result.mError);
+}
+
+TEST_F(JsepSessionTest, AudioOnlyCalleeNoRtcpMux) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+ std::string offer = CreateOffer();
+ std::string rtcp_mux = "a=rtcp-mux\r\n";
+ std::size_t pos = offer.find(rtcp_mux);
+ ASSERT_NE(pos, std::string::npos);
+ offer.replace(pos, rtcp_mux.length(), "");
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+ std::string answer = CreateAnswer();
+ pos = answer.find(rtcp_mux);
+ ASSERT_EQ(pos, std::string::npos);
+}
+
+// This test comes from Bug 810220
+TEST_F(JsepSessionTest, AudioOnlyG711Call) {
+ std::string offer =
+ "v=0\r\n"
+ "o=- 1 1 IN IP4 148.147.200.251\r\n"
+ "s=-\r\n"
+ "b=AS:64\r\n"
+ "t=0 0\r\n"
+ "a=fingerprint:sha-256 F3:FA:20:C0:CD:48:C4:5F:02:5F:A5:D3:21:D0:2D:48:"
+ "7B:31:60:5C:5A:D8:0D:CD:78:78:6C:6D:CE:CC:0C:67\r\n"
+ "m=audio 9000 UDP/TLS/RTP/SAVPF 0 8 126\r\n"
+ "c=IN IP4 148.147.200.251\r\n"
+ "b=TIAS:64000\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=rtpmap:8 PCMA/8000\r\n"
+ "a=rtpmap:126 telephone-event/8000\r\n"
+ "a=candidate:0 1 udp 2130706432 148.147.200.251 9000 typ host\r\n"
+ "a=candidate:0 2 udp 2130706432 148.147.200.251 9005 typ host\r\n"
+ "a=ice-ufrag:cYuakxkEKH+RApYE\r\n"
+ "a=ice-pwd:bwtpzLZD+3jbu8vQHvEa6Xuq\r\n"
+ "a=setup:active\r\n"
+ "a=sendrecv\r\n";
+
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionAns, "audio");
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+ std::string answer = CreateAnswer();
+
+ // They didn't offer opus, so our answer shouldn't include it.
+ ASSERT_EQ(answer.find(" opus/"), std::string::npos);
+
+ // They also didn't offer video or application
+ ASSERT_EQ(answer.find("video"), std::string::npos);
+ ASSERT_EQ(answer.find("application"), std::string::npos);
+
+ // We should answer with PCMU and telephone-event
+ ASSERT_NE(answer.find(" PCMU/8000"), std::string::npos);
+
+ // Double-check the directionality
+ ASSERT_NE(answer.find("\r\na=sendrecv"), std::string::npos);
+}
+
+TEST_F(JsepSessionTest, AudioOnlyG722Only) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+
+ std::string audio = "m=audio 9 UDP/TLS/RTP/SAVPF 109 9 0 8 101\r\n";
+ std::size_t pos = offer.find(audio);
+ ASSERT_NE(pos, std::string::npos);
+ offer.replace(pos, audio.length(), "m=audio 65375 UDP/TLS/RTP/SAVPF 9\r\n");
+ SetRemoteOffer(offer);
+
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+ ASSERT_NE(mSessionAns->GetLocalDescription(kJsepDescriptionCurrent)
+ .find("UDP/TLS/RTP/SAVPF 9\r"),
+ std::string::npos);
+ ASSERT_NE(mSessionAns->GetLocalDescription(kJsepDescriptionCurrent)
+ .find("a=rtpmap:9 G722/8000"),
+ std::string::npos);
+}
+
+TEST_F(JsepSessionTest, AudioOnlyG722Rejected) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+
+ std::string audio = "m=audio 9 UDP/TLS/RTP/SAVPF 109 9 0 8 101\r\n";
+ std::size_t pos = offer.find(audio);
+ ASSERT_NE(pos, std::string::npos);
+ offer.replace(pos, audio.length(), "m=audio 65375 UDP/TLS/RTP/SAVPF 0 8\r\n");
+ SetRemoteOffer(offer);
+
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+ SetRemoteAnswer(answer);
+
+ ASSERT_NE(mSessionAns->GetLocalDescription(kJsepDescriptionCurrent)
+ .find("UDP/TLS/RTP/SAVPF 0 8\r"),
+ std::string::npos);
+ ASSERT_NE(mSessionAns->GetLocalDescription(kJsepDescriptionCurrent)
+ .find("a=rtpmap:0 PCMU/8000"),
+ std::string::npos);
+ ASSERT_EQ(mSessionAns->GetLocalDescription(kJsepDescriptionCurrent)
+ .find("a=rtpmap:109 opus/48000/2"),
+ std::string::npos);
+ ASSERT_EQ(mSessionAns->GetLocalDescription(kJsepDescriptionCurrent)
+ .find("a=rtpmap:9 G722/8000"),
+ std::string::npos);
+}
+
+// This test doesn't make sense for bundle
+TEST_F(JsepSessionTest, DISABLED_FullCallAudioNoMuxVideoMux) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio,video");
+ AddTracks(*mSessionAns, "audio,video");
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+ std::string rtcp_mux = "a=rtcp-mux\r\n";
+ std::size_t pos = offer.find(rtcp_mux);
+ ASSERT_NE(pos, std::string::npos);
+ offer.replace(pos, rtcp_mux.length(), "");
+ SetRemoteOffer(offer);
+ std::string answer = CreateAnswer();
+
+ size_t match = mSessionAns->GetLocalDescription(kJsepDescriptionCurrent)
+ .find("\r\na=rtcp-mux");
+ ASSERT_NE(match, std::string::npos);
+ match = mSessionAns->GetLocalDescription(kJsepDescriptionCurrent)
+ .find("\r\na=rtcp-mux", match + 1);
+ ASSERT_EQ(match, std::string::npos);
+}
+
+// Disabled pending resolution of bug 818640.
+// Actually, this test is completely broken; you can't just call
+// SetRemote/CreateAnswer over and over again.
+TEST_F(JsepSessionTest, DISABLED_OfferAllDynamicTypes) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionAns, "audio");
+
+ std::string offer;
+ for (int i = 96; i < 128; i++) {
+ std::stringstream ss;
+ ss << i;
+ std::cout << "Trying dynamic pt = " << i << std::endl;
+ offer =
+ "v=0\r\n"
+ "o=- 1 1 IN IP4 148.147.200.251\r\n"
+ "s=-\r\n"
+ "b=AS:64\r\n"
+ "t=0 0\r\n"
+ "a=fingerprint:sha-256 F3:FA:20:C0:CD:48:C4:5F:02:5F:A5:D3:21:D0:2D:48:"
+ "7B:31:60:5C:5A:D8:0D:CD:78:78:6C:6D:CE:CC:0C:67\r\n"
+ "m=audio 9000 RTP/AVP " +
+ ss.str() +
+ "\r\n"
+ "c=IN IP4 148.147.200.251\r\n"
+ "b=TIAS:64000\r\n"
+ "a=rtpmap:" +
+ ss.str() +
+ " opus/48000/2\r\n"
+ "a=candidate:0 1 udp 2130706432 148.147.200.251 9000 typ host\r\n"
+ "a=candidate:0 2 udp 2130706432 148.147.200.251 9005 typ host\r\n"
+ "a=ice-ufrag:cYuakxkEKH+RApYE\r\n"
+ "a=ice-pwd:bwtpzLZD+3jbu8vQHvEa6Xuq\r\n"
+ "a=sendrecv\r\n";
+
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+ std::string answer = CreateAnswer();
+ ASSERT_NE(answer.find(ss.str() + " opus/"), std::string::npos);
+ }
+}
+
+TEST_F(JsepSessionTest, ipAddrAnyOffer) {
+ std::string offer =
+ "v=0\r\n"
+ "o=- 1 1 IN IP4 127.0.0.1\r\n"
+ "s=-\r\n"
+ "b=AS:64\r\n"
+ "t=0 0\r\n"
+ "a=fingerprint:sha-256 F3:FA:20:C0:CD:48:C4:5F:02:5F:A5:D3:21:D0:2D:48:"
+ "7B:31:60:5C:5A:D8:0D:CD:78:78:6C:6D:CE:CC:0C:67\r\n"
+ "m=audio 9000 UDP/TLS/RTP/SAVPF 99\r\n"
+ "c=IN IP4 0.0.0.0\r\n"
+ "a=rtpmap:99 opus/48000/2\r\n"
+ "a=ice-ufrag:cYuakxkEKH+RApYE\r\n"
+ "a=ice-pwd:bwtpzLZD+3jbu8vQHvEa6Xuq\r\n"
+ "a=setup:active\r\n"
+ "a=sendrecv\r\n";
+
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionAns, "audio");
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+ std::string answer = CreateAnswer();
+
+ ASSERT_NE(answer.find("a=sendrecv"), std::string::npos);
+}
+
+static void CreateSDPForBigOTests(std::string& offer,
+ const std::string& number) {
+ offer =
+ "v=0\r\n"
+ "o=- ";
+ offer += number;
+ offer += " ";
+ offer += number;
+ offer +=
+ " IN IP4 127.0.0.1\r\n"
+ "s=-\r\n"
+ "b=AS:64\r\n"
+ "t=0 0\r\n"
+ "a=fingerprint:sha-256 F3:FA:20:C0:CD:48:C4:5F:02:5F:A5:D3:21:D0:2D:48:"
+ "7B:31:60:5C:5A:D8:0D:CD:78:78:6C:6D:CE:CC:0C:67\r\n"
+ "m=audio 9000 RTP/AVP 99\r\n"
+ "c=IN IP4 0.0.0.0\r\n"
+ "a=rtpmap:99 opus/48000/2\r\n"
+ "a=ice-ufrag:cYuakxkEKH+RApYE\r\n"
+ "a=ice-pwd:bwtpzLZD+3jbu8vQHvEa6Xuq\r\n"
+ "a=setup:active\r\n"
+ "a=sendrecv\r\n";
+}
+
+TEST_F(JsepSessionTest, BigOValues) {
+ std::string offer;
+
+ CreateSDPForBigOTests(offer, "12345678901234567");
+
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionAns, "audio");
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+}
+
+TEST_F(JsepSessionTest, BigOValuesExtraChars) {
+ std::string offer;
+
+ CreateSDPForBigOTests(offer, "12345678901234567FOOBAR");
+
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionAns, "audio");
+ // The signaling state will remain "stable" because the unparsable
+ // SDP leads to a failure in SetRemoteDescription.
+ SetRemoteOffer(offer, NO_CHECKS);
+ ASSERT_EQ(kJsepStateStable, mSessionAns->GetState());
+}
+
+TEST_F(JsepSessionTest, BigOValuesTooBig) {
+ std::string offer;
+
+ CreateSDPForBigOTests(offer, "18446744073709551615");
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionAns, "audio");
+
+ // The signaling state will remain "stable" because the unparsable
+ // SDP leads to a failure in SetRemoteDescription.
+ SetRemoteOffer(offer, NO_CHECKS);
+ ASSERT_EQ(kJsepStateStable, mSessionAns->GetState());
+}
+
+TEST_F(JsepSessionTest, SetLocalAnswerInStable) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ std::string offer = CreateOffer();
+
+ // The signaling state will remain "stable" because the
+ // SetLocalDescription call fails.
+ SetLocalAnswer(offer, NO_CHECKS);
+ ASSERT_EQ(kJsepStateStable, mSessionOff->GetState());
+}
+
+TEST_F(JsepSessionTest, SetRemoteAnswerInStable) {
+ const std::string answer =
+ "v=0\r\n"
+ "o=Mozilla-SIPUA 4949 0 IN IP4 10.86.255.143\r\n"
+ "s=SIP Call\r\n"
+ "t=0 0\r\n"
+ "a=ice-ufrag:qkEP\r\n"
+ "a=ice-pwd:ed6f9GuHjLcoCN6sC/Eh7fVl\r\n"
+ "m=audio 16384 RTP/AVP 0 8 9 101\r\n"
+ "c=IN IP4 10.86.255.143\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=rtpmap:8 PCMA/8000\r\n"
+ "a=rtpmap:9 G722/8000\r\n"
+ "a=rtpmap:101 telephone-event/8000\r\n"
+ "a=fmtp:101 0-15\r\n"
+ "a=sendrecv\r\n"
+ "a=candidate:1 1 UDP 2130706431 192.168.2.1 50005 typ host\r\n"
+ "a=candidate:2 2 UDP 2130706431 192.168.2.2 50006 typ host\r\n"
+ "m=video 1024 RTP/AVP 97\r\n"
+ "c=IN IP4 10.86.255.143\r\n"
+ "a=rtpmap:120 VP8/90000\r\n"
+ "a=fmtp:97 profile-level-id=42E00C\r\n"
+ "a=sendrecv\r\n"
+ "a=candidate:1 1 UDP 2130706431 192.168.2.3 50007 typ host\r\n"
+ "a=candidate:2 2 UDP 2130706431 192.168.2.4 50008 typ host\r\n";
+
+ // The signaling state will remain "stable" because the
+ // SetRemoteDescription call fails.
+ JsepSession::Result result =
+ mSessionOff->SetRemoteDescription(kJsepSdpAnswer, answer);
+ ASSERT_EQ(dom::PCError::InvalidStateError, *result.mError);
+ ASSERT_EQ(kJsepStateStable, mSessionOff->GetState());
+}
+
+TEST_F(JsepSessionTest, SetLocalAnswerInHaveLocalOffer) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ std::string offer = CreateOffer();
+
+ SetLocalOffer(offer);
+ ASSERT_EQ(kJsepStateHaveLocalOffer, mSessionOff->GetState());
+
+ // The signaling state will remain "have-local-offer" because the
+ // SetLocalDescription call fails.
+ JsepSession::Result result =
+ mSessionOff->SetLocalDescription(kJsepSdpAnswer, offer);
+ ASSERT_EQ(dom::PCError::InvalidModificationError, *result.mError);
+ ASSERT_EQ(kJsepStateHaveLocalOffer, mSessionOff->GetState());
+}
+
+TEST_F(JsepSessionTest, SetRemoteOfferInHaveLocalOffer) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ std::string offer = CreateOffer();
+
+ SetLocalOffer(offer);
+ ASSERT_EQ(kJsepStateHaveLocalOffer, mSessionOff->GetState());
+
+ // The signaling state will remain "have-local-offer" because the
+ // SetRemoteDescription call fails.
+ JsepSession::Result result =
+ mSessionOff->SetRemoteDescription(kJsepSdpOffer, offer);
+ ASSERT_EQ(dom::PCError::InvalidStateError, *result.mError);
+ ASSERT_EQ(kJsepStateHaveLocalOffer, mSessionOff->GetState());
+}
+
+TEST_F(JsepSessionTest, SetLocalOfferInHaveRemoteOffer) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ std::string offer = CreateOffer();
+
+ SetRemoteOffer(offer);
+ ASSERT_EQ(kJsepStateHaveRemoteOffer, mSessionAns->GetState());
+
+ // The signaling state will remain "have-remote-offer" because the
+ // SetLocalDescription call fails.
+ JsepSession::Result result =
+ mSessionAns->SetLocalDescription(kJsepSdpOffer, offer);
+ ASSERT_EQ(dom::PCError::InvalidModificationError, *result.mError);
+ ASSERT_EQ(kJsepStateHaveRemoteOffer, mSessionAns->GetState());
+}
+
+TEST_F(JsepSessionTest, SetRemoteAnswerInHaveRemoteOffer) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ std::string offer = CreateOffer();
+
+ SetRemoteOffer(offer);
+ ASSERT_EQ(kJsepStateHaveRemoteOffer, mSessionAns->GetState());
+
+ // The signaling state will remain "have-remote-offer" because the
+ // SetRemoteDescription call fails.
+ JsepSession::Result result =
+ mSessionAns->SetRemoteDescription(kJsepSdpAnswer, offer);
+ ASSERT_EQ(dom::PCError::InvalidStateError, *result.mError);
+
+ ASSERT_EQ(kJsepStateHaveRemoteOffer, mSessionAns->GetState());
+}
+
+TEST_F(JsepSessionTest, RtcpFbInOffer) {
+ types.push_back(SdpMediaSection::kAudio);
+ types.push_back(SdpMediaSection::kVideo);
+ AddTracks(*mSessionOff, "audio,video");
+ std::string offer = CreateOffer();
+
+ std::map<std::string, bool> expected;
+ expected["nack"] = false;
+ expected["nack pli"] = false;
+ expected["ccm fir"] = false;
+
+ size_t prev = 0;
+ size_t found = 0;
+ for (;;) {
+ found = offer.find('\n', found + 1);
+ if (found == std::string::npos) break;
+
+ std::string line = offer.substr(prev, (found - prev));
+
+ // ensure no other rtcp-fb values are present
+ if (line.find("a=rtcp-fb:") != std::string::npos) {
+ size_t space = line.find(' ');
+ // strip trailing \r\n
+ std::string value = line.substr(space + 1, line.length() - space - 2);
+ std::map<std::string, bool>::iterator entry = expected.find(value);
+ ASSERT_NE(entry, expected.end());
+ entry->second = true;
+ }
+
+ prev = found + 1;
+ }
+
+ // ensure all values are present
+ for (std::map<std::string, bool>::iterator it = expected.begin();
+ it != expected.end(); ++it) {
+ ASSERT_EQ(it->second, true);
+ }
+}
+
+// In this test we will change the offer SDP's a=setup value
+// from actpass to passive. This will force the answer to do active.
+TEST_F(JsepSessionTest, AudioCallForceDtlsRoles) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+ std::string offer = CreateOffer();
+
+ std::string actpass = "\r\na=setup:actpass";
+ size_t match = offer.find(actpass);
+ ASSERT_NE(match, std::string::npos);
+ offer.replace(match, actpass.length(), "\r\na=setup:passive");
+
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+ ASSERT_EQ(kJsepStateHaveRemoteOffer, mSessionAns->GetState());
+ std::string answer = CreateAnswer();
+ match = answer.find("\r\na=setup:active");
+ ASSERT_NE(match, std::string::npos);
+
+ SetLocalAnswer(answer);
+ SetRemoteAnswer(answer);
+ ASSERT_EQ(kJsepStateStable, mSessionAns->GetState());
+}
+
+// In this test we will change the offer SDP's a=setup value
+// from actpass to active. This will force the answer to do passive.
+TEST_F(JsepSessionTest, AudioCallReverseDtlsRoles) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+ std::string offer = CreateOffer();
+
+ std::string actpass = "\r\na=setup:actpass";
+ size_t match = offer.find(actpass);
+ ASSERT_NE(match, std::string::npos);
+ offer.replace(match, actpass.length(), "\r\na=setup:active");
+
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+ ASSERT_EQ(kJsepStateHaveRemoteOffer, mSessionAns->GetState());
+ std::string answer = CreateAnswer();
+ match = answer.find("\r\na=setup:passive");
+ ASSERT_NE(match, std::string::npos);
+
+ SetLocalAnswer(answer);
+ SetRemoteAnswer(answer);
+ ASSERT_EQ(kJsepStateStable, mSessionAns->GetState());
+}
+
+// In this test we will change the answer SDP's a=setup value
+// from active to passive. This will make both sides do
+// active and should not connect.
+TEST_F(JsepSessionTest, AudioCallMismatchDtlsRoles) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+ std::string offer = CreateOffer();
+
+ std::string actpass = "\r\na=setup:actpass";
+ size_t match = offer.find(actpass);
+ ASSERT_NE(match, std::string::npos);
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+ ASSERT_EQ(kJsepStateHaveRemoteOffer, mSessionAns->GetState());
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+
+ std::string active = "\r\na=setup:active";
+ match = answer.find(active);
+ ASSERT_NE(match, std::string::npos);
+ answer.replace(match, active.length(), "\r\na=setup:passive");
+ SetRemoteAnswer(answer);
+
+ // This is as good as it gets in a JSEP test (w/o starting DTLS)
+ ASSERT_EQ(JsepDtlsTransport::kJsepDtlsClient,
+ GetTransceivers(*mSessionOff)[0].mTransport.mDtls->GetRole());
+ ASSERT_EQ(JsepDtlsTransport::kJsepDtlsClient,
+ GetTransceivers(*mSessionAns)[0].mTransport.mDtls->GetRole());
+}
+
+// Verify that missing a=setup in offer gets rejected
+TEST_F(JsepSessionTest, AudioCallOffererNoSetup) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+
+ std::string actpass = "\r\na=setup:actpass";
+ size_t match = offer.find(actpass);
+ ASSERT_NE(match, std::string::npos);
+ offer.replace(match, actpass.length(), "");
+
+ // The signaling state will remain "stable" because the unparsable
+ // SDP leads to a failure in SetRemoteDescription.
+ SetRemoteOffer(offer, NO_CHECKS);
+ ASSERT_EQ(kJsepStateStable, mSessionAns->GetState());
+ ASSERT_EQ(kJsepStateHaveLocalOffer, mSessionOff->GetState());
+}
+
+// In this test we will change the answer SDP to remove the
+// a=setup line, which results in active being assumed.
+TEST_F(JsepSessionTest, AudioCallAnswerNoSetup) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+ std::string offer = CreateOffer();
+ size_t match = offer.find("\r\na=setup:actpass");
+ ASSERT_NE(match, std::string::npos);
+
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+ ASSERT_EQ(kJsepStateHaveRemoteOffer, mSessionAns->GetState());
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+
+ std::string active = "\r\na=setup:active";
+ match = answer.find(active);
+ ASSERT_NE(match, std::string::npos);
+ answer.replace(match, active.length(), "");
+ SetRemoteAnswer(answer);
+ ASSERT_EQ(kJsepStateStable, mSessionAns->GetState());
+
+ // This is as good as it gets in a JSEP test (w/o starting DTLS)
+ ASSERT_EQ(JsepDtlsTransport::kJsepDtlsServer,
+ GetTransceivers(*mSessionOff)[0].mTransport.mDtls->GetRole());
+ ASSERT_EQ(JsepDtlsTransport::kJsepDtlsClient,
+ GetTransceivers(*mSessionAns)[0].mTransport.mDtls->GetRole());
+}
+
+// Verify that 'holdconn' gets rejected
+TEST_F(JsepSessionTest, AudioCallDtlsRoleHoldconn) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+
+ std::string actpass = "\r\na=setup:actpass";
+ size_t match = offer.find(actpass);
+ ASSERT_NE(match, std::string::npos);
+ offer.replace(match, actpass.length(), "\r\na=setup:holdconn");
+
+ // The signaling state will remain "stable" because the unparsable
+ // SDP leads to a failure in SetRemoteDescription.
+ SetRemoteOffer(offer, NO_CHECKS);
+ ASSERT_EQ(kJsepStateStable, mSessionAns->GetState());
+ ASSERT_EQ(kJsepStateHaveLocalOffer, mSessionOff->GetState());
+}
+
+// Verify that 'actpass' in answer gets rejected
+TEST_F(JsepSessionTest, AudioCallAnswererUsesActpass) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+
+ std::string active = "\r\na=setup:active";
+ size_t match = answer.find(active);
+ ASSERT_NE(match, std::string::npos);
+ answer.replace(match, active.length(), "\r\na=setup:actpass");
+
+ // The signaling state will remain "stable" because the unparsable
+ // SDP leads to a failure in SetRemoteDescription.
+ SetRemoteAnswer(answer, NO_CHECKS);
+ ASSERT_EQ(kJsepStateStable, mSessionAns->GetState());
+ ASSERT_EQ(kJsepStateHaveLocalOffer, mSessionOff->GetState());
+}
+
+// Verify that 'actpass' in reoffer from previous answerer doesn't result
+// in a role switch.
+TEST_F(JsepSessionTest, AudioCallPreviousAnswererUsesActpassInReoffer) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+
+ OfferAnswer();
+
+ ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
+ ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);
+
+ SwapOfferAnswerRoles();
+
+ OfferAnswer();
+
+ ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
+ ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kPassive);
+}
+
+// Disabled: See Bug 1329028
+TEST_F(JsepSessionTest, DISABLED_AudioCallOffererAttemptsSetupRoleSwitch) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+
+ OfferAnswer();
+
+ ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
+ ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);
+
+ std::string reoffer = CreateOffer();
+ SetLocalOffer(reoffer);
+
+ std::string actpass = "\r\na=setup:actpass";
+ size_t match = reoffer.find(actpass);
+ ASSERT_NE(match, std::string::npos);
+ reoffer.replace(match, actpass.length(), "\r\na=setup:active");
+
+ // This is expected to fail.
+ SetRemoteOffer(reoffer, NO_CHECKS);
+ ASSERT_EQ(kJsepStateHaveLocalOffer, mSessionOff->GetState());
+ ASSERT_EQ(kJsepStateStable, mSessionAns->GetState());
+}
+
+// Disabled: See Bug 1329028
+TEST_F(JsepSessionTest, DISABLED_AudioCallAnswererAttemptsSetupRoleSwitch) {
+ types.push_back(SdpMediaSection::kAudio);
+ AddTracks(*mSessionOff, "audio");
+ AddTracks(*mSessionAns, "audio");
+
+ OfferAnswer();
+
+ ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
+ ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);
+
+ std::string reoffer = CreateOffer();
+ SetLocalOffer(reoffer);
+ SetRemoteOffer(reoffer);
+
+ std::string reanswer = CreateAnswer();
+ SetLocalAnswer(reanswer);
+
+ std::string actpass = "\r\na=setup:active";
+ size_t match = reanswer.find(actpass);
+ ASSERT_NE(match, std::string::npos);
+ reanswer.replace(match, actpass.length(), "\r\na=setup:passive");
+
+ // This is expected to fail.
+ SetRemoteAnswer(reanswer, NO_CHECKS);
+ ASSERT_EQ(kJsepStateHaveLocalOffer, mSessionOff->GetState());
+ ASSERT_EQ(kJsepStateStable, mSessionAns->GetState());
+}
+
+// Remove H.264 P1 and VP8 from offer, check answer negotiates H.264 P0
+TEST_F(JsepSessionTest, OfferWithOnlyH264P0) {
+ for (auto& codec : mSessionOff->Codecs()) {
+ if (codec->mName != "H264" || codec->mDefaultPt == "126") {
+ codec->mEnabled = false;
+ }
+ }
+
+ types.push_back(SdpMediaSection::kAudio);
+ types.push_back(SdpMediaSection::kVideo);
+ AddTracks(*mSessionOff, "audio,video");
+ AddTracks(*mSessionAns, "audio,video");
+ std::string offer = CreateOffer();
+
+ ASSERT_EQ(offer.find("a=rtpmap:126 H264/90000"), std::string::npos);
+ ASSERT_EQ(offer.find("a=rtpmap:120 VP8/90000"), std::string::npos);
+
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+ std::string answer = CreateAnswer();
+ size_t match = answer.find("\r\na=setup:active");
+ ASSERT_NE(match, std::string::npos);
+
+ // validate answer SDP
+ ASSERT_NE(answer.find("a=rtpmap:97 H264/90000"), std::string::npos);
+ ASSERT_NE(answer.find("a=rtcp-fb:97 nack"), std::string::npos);
+ ASSERT_NE(answer.find("a=rtcp-fb:97 nack pli"), std::string::npos);
+ ASSERT_NE(answer.find("a=rtcp-fb:97 ccm fir"), std::string::npos);
+ // Ensure VP8 and P1 removed
+ ASSERT_EQ(answer.find("a=rtpmap:126 H264/90000"), std::string::npos);
+ ASSERT_EQ(answer.find("a=rtpmap:120 VP8/90000"), std::string::npos);
+ ASSERT_EQ(answer.find("a=rtcp-fb:120"), std::string::npos);
+ ASSERT_EQ(answer.find("a=rtcp-fb:126"), std::string::npos);
+}
+
+// Test negotiating an answer which has only H.264 P1
+// Which means replace VP8 with H.264 P1 in answer
+TEST_F(JsepSessionTest, AnswerWithoutVP8) {
+ types.push_back(SdpMediaSection::kAudio);
+ types.push_back(SdpMediaSection::kVideo);
+ AddTracks(*mSessionOff, "audio,video");
+ AddTracks(*mSessionAns, "audio,video");
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+ SetRemoteOffer(offer);
+
+ for (auto& codec : mSessionOff->Codecs()) {
+ if (codec->mName != "H264" || codec->mDefaultPt == "126") {
+ codec->mEnabled = false;
+ }
+ }
+
+ std::string answer = CreateAnswer();
+
+ SetLocalAnswer(answer);
+ SetRemoteAnswer(answer);
+}
+
+// Ok. Hear me out.
+// The JSEP spec specifies very different behavior for the following two cases:
+// 1. AddTrack caused a transceiver to be created.
+// 2. The transceiver was not created as a side-effect of AddTrack.
+//
+// All together now...
+//
+// SADFACE :(
+//
+// Ok, enough of that. The upshot is we need to test two different codepaths for
+// the same thing here. Most of this unit-test suite tests the "magic" case
+// (case 1 above). Case 2 (the non-magic case) is simpler, so we have just a
+// handful of tests.
+TEST_F(JsepSessionTest, OffererNoAddTrackMagic) {
+ types = BuildTypes("audio,video");
+ AddTracks(*mSessionOff, NO_ADDTRACK_MAGIC);
+ AddTracks(*mSessionAns);
+
+ // Offerer's transceivers aren't "magic"; they will not associate with the
+ // remote side's m-sections automatically. But, since they went into the
+ // offer, everything works normally.
+ OfferAnswer();
+
+ ASSERT_EQ(2U, GetTransceivers(*mSessionOff).size());
+ ASSERT_EQ(2U, GetTransceivers(*mSessionAns).size());
+}
+
+TEST_F(JsepSessionTest, AnswererNoAddTrackMagic) {
+ types = BuildTypes("audio,video");
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns, NO_ADDTRACK_MAGIC);
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ ASSERT_EQ(2U, GetTransceivers(*mSessionOff).size());
+ // Since answerer's transceivers aren't "magic", they cannot automatically be
+ // attached to the offerer's m-sections.
+ ASSERT_EQ(4U, GetTransceivers(*mSessionAns).size());
+
+ SwapOfferAnswerRoles();
+
+ OfferAnswer(CHECK_SUCCESS);
+ ASSERT_EQ(4U, GetTransceivers(*mSessionOff).size());
+ ASSERT_EQ(4U, GetTransceivers(*mSessionAns).size());
+}
+
+// JSEP has rules about when a disabled m-section can be reused; the gist is
+// that the m-section has to be negotiated disabled, then it becomes a candidate
+// for reuse on the next renegotiation. Stopping a transceiver does not allow
+// you to reuse on the next negotiation.
+TEST_F(JsepSessionTest, OffererRecycle) {
+ types = BuildTypes("audio,video");
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ OfferAnswer();
+
+ ASSERT_EQ(2U, GetTransceivers(*mSessionOff).size());
+ ASSERT_EQ(2U, GetTransceivers(*mSessionAns).size());
+ GetTransceivers(*mSessionOff)[0].Stop();
+ AddTracks(*mSessionOff, "audio");
+ ASSERT_EQ(3U, GetTransceivers(*mSessionOff).size());
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ // It is too soon to recycle msection 0, so the new track should have been
+ // given a new msection.
+ ASSERT_EQ(3U, GetTransceivers(*mSessionOff).size());
+ ASSERT_EQ(0U, GetTransceivers(*mSessionOff)[0].GetLevel());
+ ASSERT_TRUE(GetTransceivers(*mSessionOff)[0].IsStopped());
+ ASSERT_EQ(1U, GetTransceivers(*mSessionOff)[1].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[1].IsStopped());
+ ASSERT_EQ(2U, GetTransceivers(*mSessionOff)[2].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[2].IsStopped());
+
+ ASSERT_EQ(3U, GetTransceivers(*mSessionAns).size());
+ ASSERT_EQ(0U, GetTransceivers(*mSessionAns)[0].GetLevel());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[0].IsStopped());
+ ASSERT_EQ(1U, GetTransceivers(*mSessionAns)[1].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[1].IsStopped());
+ ASSERT_EQ(2U, GetTransceivers(*mSessionAns)[2].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].IsStopped());
+
+ UniquePtr<Sdp> offer = GetParsedLocalDescription(*mSessionOff);
+ ASSERT_EQ(3U, offer->GetMediaSectionCount());
+ ValidateDisabledMSection(&offer->GetMediaSection(0));
+
+ UniquePtr<Sdp> answer = GetParsedLocalDescription(*mSessionAns);
+ ASSERT_EQ(3U, answer->GetMediaSectionCount());
+ ValidateDisabledMSection(&answer->GetMediaSection(0));
+
+ // Ok. Now renegotiating should recycle m-section 0.
+ AddTracks(*mSessionOff, "audio");
+ ASSERT_EQ(4U, GetTransceivers(*mSessionOff).size());
+ OfferAnswer(CHECK_SUCCESS);
+
+ // Transceiver 3 should now be attached to m-section 0
+ ASSERT_EQ(4U, GetTransceivers(*mSessionOff).size());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[0].HasLevel());
+ ASSERT_TRUE(GetTransceivers(*mSessionOff)[0].IsStopped());
+ ASSERT_EQ(1U, GetTransceivers(*mSessionOff)[1].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[1].IsStopped());
+ ASSERT_EQ(2U, GetTransceivers(*mSessionOff)[2].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[2].IsStopped());
+ ASSERT_EQ(0U, GetTransceivers(*mSessionOff)[3].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[3].IsStopped());
+
+ ASSERT_EQ(4U, GetTransceivers(*mSessionAns).size());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[0].HasLevel());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[0].IsStopped());
+ ASSERT_EQ(1U, GetTransceivers(*mSessionAns)[1].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[1].IsStopped());
+ ASSERT_EQ(2U, GetTransceivers(*mSessionAns)[2].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].IsStopped());
+ ASSERT_EQ(0U, GetTransceivers(*mSessionAns)[3].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[3].IsStopped());
+}
+
+TEST_F(JsepSessionTest, RecycleAnswererStopsTransceiver) {
+ types = BuildTypes("audio,video");
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ OfferAnswer();
+
+ ASSERT_EQ(2U, GetTransceivers(*mSessionOff).size());
+ ASSERT_EQ(2U, GetTransceivers(*mSessionAns).size());
+ GetTransceivers(*mSessionAns)[0].Stop();
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ ASSERT_EQ(2U, GetTransceivers(*mSessionOff).size());
+ ASSERT_EQ(0U, GetTransceivers(*mSessionOff)[0].GetLevel());
+ ASSERT_TRUE(GetTransceivers(*mSessionOff)[0].IsStopped());
+ ASSERT_EQ(1U, GetTransceivers(*mSessionOff)[1].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[1].IsStopped());
+
+ ASSERT_EQ(2U, GetTransceivers(*mSessionAns).size());
+ ASSERT_EQ(0U, GetTransceivers(*mSessionAns)[0].GetLevel());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[0].IsStopped());
+ ASSERT_EQ(1U, GetTransceivers(*mSessionAns)[1].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[1].IsStopped());
+
+ UniquePtr<Sdp> offer = GetParsedLocalDescription(*mSessionOff);
+ ASSERT_EQ(2U, offer->GetMediaSectionCount());
+
+ UniquePtr<Sdp> answer = GetParsedLocalDescription(*mSessionAns);
+ ASSERT_EQ(2U, answer->GetMediaSectionCount());
+ ValidateDisabledMSection(&answer->GetMediaSection(0));
+
+ // Renegotiating should recycle m-section 0.
+ AddTracks(*mSessionOff, "audio");
+ ASSERT_EQ(3U, GetTransceivers(*mSessionOff).size());
+ OfferAnswer(CHECK_SUCCESS);
+
+ // Transceiver 3 should now be attached to m-section 0
+ ASSERT_EQ(3U, GetTransceivers(*mSessionOff).size());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[0].HasLevel());
+ ASSERT_TRUE(GetTransceivers(*mSessionOff)[0].IsStopped());
+ ASSERT_EQ(1U, GetTransceivers(*mSessionOff)[1].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[1].IsStopped());
+ ASSERT_EQ(0U, GetTransceivers(*mSessionOff)[2].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[2].IsStopped());
+
+ ASSERT_EQ(3U, GetTransceivers(*mSessionAns).size());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[0].HasLevel());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[0].IsStopped());
+ ASSERT_EQ(1U, GetTransceivers(*mSessionAns)[1].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[1].IsStopped());
+ ASSERT_EQ(0U, GetTransceivers(*mSessionAns)[2].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].IsStopped());
+}
+
+// TODO: Have a test where offerer stops, and answerer adds a track and reoffers
+// once Nils' role swap code lands.
+
+// TODO: Have a test where answerer stops and adds a track.
+
+TEST_F(JsepSessionTest, OffererRecycleNoMagic) {
+ types = BuildTypes("audio,video");
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ OfferAnswer();
+
+ ASSERT_EQ(2U, GetTransceivers(*mSessionOff).size());
+ ASSERT_EQ(2U, GetTransceivers(*mSessionAns).size());
+ GetTransceivers(*mSessionOff)[0].Stop();
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ // Ok. Now renegotiating should recycle m-section 0.
+ AddTracks(*mSessionOff, "audio", NO_ADDTRACK_MAGIC);
+ ASSERT_EQ(3U, GetTransceivers(*mSessionOff).size());
+ OfferAnswer(CHECK_SUCCESS);
+
+ // Transceiver 2 should now be attached to m-section 0
+ ASSERT_EQ(3U, GetTransceivers(*mSessionOff).size());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[0].HasLevel());
+ ASSERT_TRUE(GetTransceivers(*mSessionOff)[0].IsStopped());
+ ASSERT_EQ(1U, GetTransceivers(*mSessionOff)[1].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[1].IsStopped());
+ ASSERT_EQ(0U, GetTransceivers(*mSessionOff)[2].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[2].IsStopped());
+
+ ASSERT_EQ(3U, GetTransceivers(*mSessionAns).size());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[0].HasLevel());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[0].IsStopped());
+ ASSERT_EQ(1U, GetTransceivers(*mSessionAns)[1].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[1].IsStopped());
+ ASSERT_EQ(0U, GetTransceivers(*mSessionAns)[2].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].IsStopped());
+}
+
+TEST_F(JsepSessionTest, OffererRecycleNoMagicAnswererStopsTransceiver) {
+ types = BuildTypes("audio,video");
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ OfferAnswer();
+
+ ASSERT_EQ(2U, GetTransceivers(*mSessionOff).size());
+ ASSERT_EQ(2U, GetTransceivers(*mSessionAns).size());
+ GetTransceivers(*mSessionAns)[0].Stop();
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ // Ok. Now renegotiating should recycle m-section 0.
+ AddTracks(*mSessionOff, "audio", NO_ADDTRACK_MAGIC);
+ ASSERT_EQ(3U, GetTransceivers(*mSessionOff).size());
+ OfferAnswer(CHECK_SUCCESS);
+
+ // Transceiver 2 should now be attached to m-section 0
+ ASSERT_EQ(3U, GetTransceivers(*mSessionOff).size());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[0].HasLevel());
+ ASSERT_TRUE(GetTransceivers(*mSessionOff)[0].IsStopped());
+ ASSERT_EQ(1U, GetTransceivers(*mSessionOff)[1].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[1].IsStopped());
+ ASSERT_EQ(0U, GetTransceivers(*mSessionOff)[2].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[2].IsStopped());
+
+ ASSERT_EQ(3U, GetTransceivers(*mSessionAns).size());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[0].HasLevel());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[0].IsStopped());
+ ASSERT_EQ(1U, GetTransceivers(*mSessionAns)[1].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[1].IsStopped());
+ ASSERT_EQ(0U, GetTransceivers(*mSessionAns)[2].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].IsStopped());
+}
+
+TEST_F(JsepSessionTest, RecycleRollback) {
+ types = BuildTypes("audio,video");
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ OfferAnswer();
+
+ ASSERT_EQ(2U, GetTransceivers(*mSessionOff).size());
+ ASSERT_EQ(2U, GetTransceivers(*mSessionAns).size());
+ GetTransceivers(*mSessionOff)[0].Stop();
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ AddTracks(*mSessionOff, "audio");
+
+ ASSERT_EQ(3U, GetTransceivers(*mSessionOff).size());
+ ASSERT_EQ(0U, GetTransceivers(*mSessionOff)[0].GetLevel());
+ ASSERT_TRUE(GetTransceivers(*mSessionOff)[0].IsStopped());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[0].IsAssociated());
+ ASSERT_EQ(1U, GetTransceivers(*mSessionOff)[1].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[1].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionOff)[1].IsAssociated());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[2].HasLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[2].IsStopped());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[2].IsAssociated());
+
+ std::string offer = CreateOffer();
+ ASSERT_EQ(3U, GetTransceivers(*mSessionOff).size());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[0].HasLevel());
+ ASSERT_TRUE(GetTransceivers(*mSessionOff)[0].IsStopped());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[0].IsAssociated());
+ ASSERT_EQ(1U, GetTransceivers(*mSessionOff)[1].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[1].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionOff)[1].IsAssociated());
+ ASSERT_EQ(0U, GetTransceivers(*mSessionOff)[2].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[2].IsStopped());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[2].IsAssociated());
+
+ SetLocalOffer(offer, CHECK_SUCCESS);
+
+ ASSERT_EQ(3U, GetTransceivers(*mSessionOff).size());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[0].HasLevel());
+ ASSERT_TRUE(GetTransceivers(*mSessionOff)[0].IsStopped());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[0].IsAssociated());
+ ASSERT_EQ(1U, GetTransceivers(*mSessionOff)[1].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[1].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionOff)[1].IsAssociated());
+ ASSERT_EQ(0U, GetTransceivers(*mSessionOff)[2].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[2].IsStopped());
+ // This should now be associated
+ ASSERT_TRUE(GetTransceivers(*mSessionOff)[2].IsAssociated());
+
+ ASSERT_FALSE(
+ mSessionOff->SetLocalDescription(kJsepSdpRollback, "").mError.isSome());
+
+ // Rollback should not change the levels of any of these, since those are set
+ // in CreateOffer.
+ ASSERT_EQ(3U, GetTransceivers(*mSessionOff).size());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[0].HasLevel());
+ ASSERT_TRUE(GetTransceivers(*mSessionOff)[0].IsStopped());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[0].IsAssociated());
+ ASSERT_EQ(1U, GetTransceivers(*mSessionOff)[1].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[1].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionOff)[1].IsAssociated());
+ ASSERT_EQ(0U, GetTransceivers(*mSessionOff)[2].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[2].IsStopped());
+ // This should no longer be associated
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[2].IsAssociated());
+}
+
+TEST_F(JsepSessionTest, AddTrackMagicWithNullReplaceTrack) {
+ types = BuildTypes("audio,video");
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ OfferAnswer();
+
+ ASSERT_EQ(2U, GetTransceivers(*mSessionOff).size());
+ ASSERT_EQ(2U, GetTransceivers(*mSessionAns).size());
+
+ AddTracks(*mSessionAns, "audio");
+ AddTracks(*mSessionOff, "audio");
+
+ ASSERT_EQ(3U, GetTransceivers(*mSessionAns).size());
+ ASSERT_EQ(0U, GetTransceivers(*mSessionAns)[0].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[0].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[0].IsAssociated());
+ ASSERT_EQ(1U, GetTransceivers(*mSessionAns)[1].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[1].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[1].IsAssociated());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].HasLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].IsStopped());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].IsAssociated());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[2].HasAddTrackMagic());
+
+ // Ok, transceiver 2 is "magical". Ensure it still has this "magical"
+ // auto-matching property even if we null it out with replaceTrack.
+ GetTransceivers(*mSessionAns)[2].mSendTrack.ClearStreamIds();
+ GetTransceivers(*mSessionAns)[2].mJsDirection =
+ SdpDirectionAttribute::Direction::kRecvonly;
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ ASSERT_EQ(3U, GetTransceivers(*mSessionAns).size());
+ ASSERT_EQ(0U, GetTransceivers(*mSessionAns)[0].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[0].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[0].IsAssociated());
+ ASSERT_EQ(1U, GetTransceivers(*mSessionAns)[1].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[1].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[1].IsAssociated());
+ ASSERT_EQ(2U, GetTransceivers(*mSessionAns)[2].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[2].IsAssociated());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[2].HasAddTrackMagic());
+
+ ASSERT_EQ(3U, GetTransceivers(*mSessionOff).size());
+ ASSERT_EQ(0U, GetTransceivers(*mSessionOff)[0].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[0].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionOff)[0].IsAssociated());
+ ASSERT_EQ(1U, GetTransceivers(*mSessionOff)[1].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[1].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionOff)[1].IsAssociated());
+ ASSERT_EQ(2U, GetTransceivers(*mSessionOff)[2].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[2].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionOff)[2].IsAssociated());
+ ASSERT_TRUE(GetTransceivers(*mSessionOff)[2].HasAddTrackMagic());
+}
+
+// Flipside of AddTrackMagicWithNullReplaceTrack; we want to check that
+// auto-matching does not work for transceivers that were created without a
+// track, but were later given a track with replaceTrack.
+TEST_F(JsepSessionTest, NoAddTrackMagicReplaceTrack) {
+ types = BuildTypes("audio,video");
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ OfferAnswer();
+
+ ASSERT_EQ(2U, GetTransceivers(*mSessionOff).size());
+ ASSERT_EQ(2U, GetTransceivers(*mSessionAns).size());
+ AddTracks(*mSessionOff, "audio");
+ mSessionAns->AddTransceiver(
+ JsepTransceiver(SdpMediaSection::MediaType::kAudio, mUuidGen));
+
+ GetTransceivers(*mSessionAns)[2].mSendTrack.UpdateStreamIds({"newstream"});
+
+ ASSERT_EQ(3U, GetTransceivers(*mSessionAns).size());
+ ASSERT_EQ(0U, GetTransceivers(*mSessionAns)[0].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[0].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[0].IsAssociated());
+ ASSERT_EQ(1U, GetTransceivers(*mSessionAns)[1].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[1].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[1].IsAssociated());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].HasLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].IsStopped());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].IsAssociated());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].HasAddTrackMagic());
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ ASSERT_EQ(4U, GetTransceivers(*mSessionAns).size());
+ ASSERT_EQ(0U, GetTransceivers(*mSessionAns)[0].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[0].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[0].IsAssociated());
+ ASSERT_EQ(1U, GetTransceivers(*mSessionAns)[1].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[1].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[1].IsAssociated());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].HasLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].IsStopped());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].IsAssociated());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].HasAddTrackMagic());
+ ASSERT_EQ(2U, GetTransceivers(*mSessionAns)[3].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[3].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[3].IsAssociated());
+}
+
+TEST_F(JsepSessionTest, AddTrackDoesNotMakeTransceiverMagical) {
+ types = BuildTypes("audio,video");
+ AddTracks(*mSessionOff);
+ AddTracks(*mSessionAns);
+
+ OfferAnswer();
+
+ ASSERT_EQ(2U, GetTransceivers(*mSessionOff).size());
+ ASSERT_EQ(2U, GetTransceivers(*mSessionAns).size());
+ AddTracks(*mSessionOff, "audio");
+ mSessionAns->AddTransceiver(
+ JsepTransceiver(SdpMediaSection::MediaType::kAudio, mUuidGen));
+
+ ASSERT_EQ(3U, GetTransceivers(*mSessionAns).size());
+ ASSERT_EQ(0U, GetTransceivers(*mSessionAns)[0].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[0].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[0].IsAssociated());
+ ASSERT_EQ(1U, GetTransceivers(*mSessionAns)[1].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[1].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[1].IsAssociated());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].HasLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].IsStopped());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].IsAssociated());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].HasAddTrackMagic());
+
+ // This attaches a track to the third transceiver, but does _not_ set the
+ // addTrack magic bit, meaning it will not auto-pair with the track added
+ // to the offerer.
+ AddTracks(*mSessionAns, "audio");
+
+ ASSERT_EQ(3U, GetTransceivers(*mSessionAns).size());
+ ASSERT_EQ(0U, GetTransceivers(*mSessionAns)[0].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[0].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[0].IsAssociated());
+ ASSERT_EQ(1U, GetTransceivers(*mSessionAns)[1].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[1].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[1].IsAssociated());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].HasLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].IsStopped());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].IsAssociated());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].HasAddTrackMagic());
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ // The offer's new transceiver does not pair up with the transceiver we added
+ ASSERT_EQ(4U, GetTransceivers(*mSessionAns).size());
+ ASSERT_EQ(0U, GetTransceivers(*mSessionAns)[0].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[0].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[0].IsAssociated());
+ ASSERT_EQ(1U, GetTransceivers(*mSessionAns)[1].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[1].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[1].IsAssociated());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].HasLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].IsStopped());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].IsAssociated());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].HasAddTrackMagic());
+ ASSERT_EQ(2U, GetTransceivers(*mSessionAns)[3].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[3].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[3].IsAssociated());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[3].HasAddTrackMagic());
+}
+
+TEST_F(JsepSessionTest, ComplicatedRemoteRollback) {
+ AddTracks(*mSessionOff, "audio,audio,audio,video");
+ AddTracks(*mSessionAns, "video,video");
+
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer, CHECK_SUCCESS);
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+
+ // Three recvonly for audio, one sendrecv for video, and one (unmapped) for
+ // the second video track.
+ ASSERT_EQ(5U, GetTransceivers(*mSessionAns).size());
+ // First video transceiver; auto matched with offer
+ ASSERT_EQ(3U, GetTransceivers(*mSessionAns)[0].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[0].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[0].IsAssociated());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[0].HasAddTrackMagic());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[0].OnlyExistsBecauseOfSetRemote());
+
+ // Second video transceiver, not matched with offer
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[1].HasLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[1].IsStopped());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[1].IsAssociated());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[1].HasAddTrackMagic());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[1].OnlyExistsBecauseOfSetRemote());
+
+ // Audio transceiver, created due to application of SetRemote
+ ASSERT_EQ(0U, GetTransceivers(*mSessionAns)[2].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[2].IsAssociated());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].HasAddTrackMagic());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[2].OnlyExistsBecauseOfSetRemote());
+
+ // Audio transceiver, created due to application of SetRemote
+ ASSERT_EQ(1U, GetTransceivers(*mSessionAns)[3].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[3].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[3].IsAssociated());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[3].HasAddTrackMagic());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[3].OnlyExistsBecauseOfSetRemote());
+
+ // Audio transceiver, created due to application of SetRemote
+ ASSERT_EQ(2U, GetTransceivers(*mSessionAns)[4].GetLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[4].IsStopped());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[4].IsAssociated());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[4].HasAddTrackMagic());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[4].OnlyExistsBecauseOfSetRemote());
+
+ // This will prevent rollback from eating this transceiver, even though we
+ // call replaceTrack(null) on it.
+ AddTracks(*mSessionAns, "audio");
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].HasAddTrackMagic());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].OnlyExistsBecauseOfSetRemote());
+ GetTransceivers(*mSessionAns)[2].mSendTrack.ClearStreamIds();
+ GetTransceivers(*mSessionAns)[2].mJsDirection =
+ SdpDirectionAttribute::Direction::kRecvonly;
+
+ // We do nothing with the second audio transceiver; when we rollback, it will
+ // be marked as removed.
+
+ // This will not cause the third audio transceiver to stick around; having a
+ // track is _not_ enough to preserve it. It must have addTrack "magic"!
+ GetTransceivers(*mSessionAns)[4].mSendTrack.UpdateStreamIds({"newstream"});
+
+ // Create a fourth audio transceiver. Rollback will leave it alone, since we
+ // created it.
+ mSessionAns->AddTransceiver(
+ JsepTransceiver(SdpMediaSection::MediaType::kAudio, mUuidGen,
+ SdpDirectionAttribute::Direction::kRecvonly));
+
+ ASSERT_FALSE(
+ mSessionAns->SetRemoteDescription(kJsepSdpRollback, "").mError.isSome());
+
+ // Two of these (3 and 4) will be marked removed, if this all worked
+ ASSERT_EQ(6U, GetTransceivers(*mSessionAns).size());
+
+ // First video transceiver
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[0].HasLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[0].IsStopped());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[0].IsAssociated());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[0].HasAddTrackMagic());
+ ASSERT_FALSE(IsNull(GetTransceivers(*mSessionAns)[0].mSendTrack));
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[0].IsRemoved());
+
+ // Second video transceiver
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[1].HasLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[1].IsStopped());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[1].IsAssociated());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[1].HasAddTrackMagic());
+ ASSERT_FALSE(IsNull(GetTransceivers(*mSessionAns)[1].mSendTrack));
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[1].IsRemoved());
+
+ // First audio transceiver, kept because AddTrack touched it, even though we
+ // removed the send track after.
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].HasLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].IsStopped());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].IsAssociated());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].HasAddTrackMagic());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].OnlyExistsBecauseOfSetRemote());
+ ASSERT_TRUE(IsNull(GetTransceivers(*mSessionAns)[2].mSendTrack));
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[2].IsRemoved());
+
+ // Second audio transceiver should be gone.
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[3].HasLevel());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[3].IsStopped());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[3].IsAssociated());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[3].HasAddTrackMagic());
+ ASSERT_TRUE(IsNull(GetTransceivers(*mSessionAns)[3].mSendTrack));
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[3].IsRemoved());
+
+ // Third audio transceiver should also be gone.
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[4].HasLevel());
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[4].IsStopped());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[4].IsAssociated());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[4].HasAddTrackMagic());
+ ASSERT_TRUE(IsNull(GetTransceivers(*mSessionAns)[4].mSendTrack));
+ ASSERT_TRUE(GetTransceivers(*mSessionAns)[4].IsRemoved());
+
+ // Fourth audio transceiver, created after SetRemote
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[5].HasLevel());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[5].IsStopped());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[5].IsAssociated());
+ ASSERT_FALSE(GetTransceivers(*mSessionAns)[5].HasAddTrackMagic());
+ ASSERT_TRUE(
+ GetTransceivers(*mSessionAns)[5].mSendTrack.GetStreamIds().empty());
+}
+
+TEST_F(JsepSessionTest, LocalRollback) {
+ AddTracks(*mSessionOff, "audio,video");
+ AddTracks(*mSessionAns, "audio,video");
+
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer, CHECK_SUCCESS);
+
+ ASSERT_TRUE(GetTransceivers(*mSessionOff)[0].IsAssociated());
+ ASSERT_TRUE(GetTransceivers(*mSessionOff)[1].IsAssociated());
+ ASSERT_FALSE(
+ mSessionOff->SetLocalDescription(kJsepSdpRollback, "").mError.isSome());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[0].IsAssociated());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[1].IsAssociated());
+}
+
+TEST_F(JsepSessionTest, JsStopsTransceiverBeforeAnswer) {
+ AddTracks(*mSessionOff, "audio,video");
+ AddTracks(*mSessionAns, "audio,video");
+
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer, CHECK_SUCCESS);
+ SetRemoteOffer(offer, CHECK_SUCCESS);
+
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer, CHECK_SUCCESS);
+
+ // Now JS decides to stop a transceiver. Make sure transport stuff is still
+ // ready to go when the answer is set. This should only prevent the flow of
+ // media for that transceiver.
+
+ GetTransceivers(*mSessionOff)[0].Stop();
+ SetRemoteAnswer(answer, CHECK_SUCCESS);
+
+ ASSERT_TRUE(GetTransceivers(*mSessionOff)[0].IsStopped());
+ ASSERT_EQ(1U, GetTransceivers(*mSessionOff)[0].mTransport.mComponents);
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[0].mSendTrack.GetActive());
+ ASSERT_FALSE(GetTransceivers(*mSessionOff)[0].mRecvTrack.GetActive());
+}
+
+TEST_F(JsepSessionTest, TestOfferPTAsymmetryRtxApt) {
+ for (auto& codec : mSessionAns->Codecs()) {
+ if (codec->mName == "VP8") {
+ JsepVideoCodecDescription* vp8 =
+ static_cast<JsepVideoCodecDescription*>(codec.get());
+ vp8->EnableRtx("42");
+ break;
+ }
+ }
+
+ types.push_back(SdpMediaSection::kVideo);
+ AddTracks(*mSessionOff, "video");
+ AddTracks(*mSessionAns, "video");
+ JsepOfferOptions options;
+
+ // Ensure that mSessionAns is appropriately configured.
+ std::string offer;
+ JsepSession::Result result = mSessionAns->CreateOffer(options, &offer);
+ ASSERT_FALSE(result.mError.isSome());
+ ASSERT_NE(std::string::npos, offer.find("a=rtpmap:42 rtx")) << offer;
+
+ OfferAnswer();
+
+ // Answerer should use what the offerer suggested
+ UniquePtr<JsepCodecDescription> codec;
+ GetCodec(*mSessionAns, 0, sdp::kSend, 0, 0, &codec);
+ ASSERT_TRUE(codec);
+ ASSERT_EQ("VP8", codec->mName);
+ JsepVideoCodecDescription* vp8 =
+ static_cast<JsepVideoCodecDescription*>(codec.get());
+ ASSERT_EQ("120", vp8->mDefaultPt);
+ ASSERT_EQ("124", vp8->mRtxPayloadType);
+ GetCodec(*mSessionAns, 0, sdp::kRecv, 0, 0, &codec);
+ ASSERT_TRUE(codec);
+ ASSERT_EQ("VP8", codec->mName);
+ vp8 = static_cast<JsepVideoCodecDescription*>(codec.get());
+ ASSERT_EQ("120", vp8->mDefaultPt);
+ ASSERT_EQ("124", vp8->mRtxPayloadType);
+
+ // Answerer should not change back when it reoffers
+ result = mSessionAns->CreateOffer(options, &offer);
+ ASSERT_FALSE(result.mError.isSome());
+ ASSERT_NE(std::string::npos, offer.find("a=rtpmap:124 rtx")) << offer;
+}
+
+TEST_F(JsepSessionTest, TestAnswerPTAsymmetryRtx) {
+ // JsepSessionImpl will never answer with an asymmetric payload type
+ // (tested in TestOfferPTAsymmetry), so we have to rewrite SDP a little.
+ types.push_back(SdpMediaSection::kVideo);
+ AddTracks(*mSessionOff, "video");
+ AddTracks(*mSessionAns, "video");
+
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+
+ Replace("a=rtpmap:120 VP8", "a=rtpmap:119 VP8", &offer);
+ Replace("m=video 9 UDP/TLS/RTP/SAVPF 120", "m=video 9 UDP/TLS/RTP/SAVPF 119",
+ &offer);
+ ReplaceAll("a=fmtp:120", "a=fmtp:119", &offer);
+ ReplaceAll("a=fmtp:122 120", "a=fmtp:122 119", &offer);
+ ReplaceAll("a=fmtp:124 apt=120", "a=fmtp:124 apt=119", &offer);
+ ReplaceAll("a=rtcp-fb:120", "a=rtcp-fb:119", &offer);
+
+ SetRemoteOffer(offer);
+
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+ SetRemoteAnswer(answer);
+
+ UniquePtr<JsepCodecDescription> codec;
+ GetCodec(*mSessionOff, 0, sdp::kSend, 0, 0, &codec);
+ ASSERT_TRUE(codec);
+ ASSERT_EQ("VP8", codec->mName);
+ ASSERT_EQ("119", codec->mDefaultPt);
+ JsepVideoCodecDescription* vp8 =
+ static_cast<JsepVideoCodecDescription*>(codec.get());
+ ASSERT_EQ("124", vp8->mRtxPayloadType);
+ GetCodec(*mSessionOff, 0, sdp::kRecv, 0, 0, &codec);
+ ASSERT_TRUE(codec);
+ ASSERT_EQ("VP8", codec->mName);
+ ASSERT_EQ("120", codec->mDefaultPt);
+ vp8 = static_cast<JsepVideoCodecDescription*>(codec.get());
+ ASSERT_EQ("124", vp8->mRtxPayloadType);
+
+ GetCodec(*mSessionAns, 0, sdp::kSend, 0, 0, &codec);
+ ASSERT_TRUE(codec);
+ ASSERT_EQ("VP8", codec->mName);
+ ASSERT_EQ("119", codec->mDefaultPt);
+ vp8 = static_cast<JsepVideoCodecDescription*>(codec.get());
+ ASSERT_EQ("124", vp8->mRtxPayloadType);
+ GetCodec(*mSessionAns, 0, sdp::kRecv, 0, 0, &codec);
+ ASSERT_TRUE(codec);
+ ASSERT_EQ("VP8", codec->mName);
+ ASSERT_EQ("119", codec->mDefaultPt);
+ vp8 = static_cast<JsepVideoCodecDescription*>(codec.get());
+ ASSERT_EQ("124", vp8->mRtxPayloadType);
+}
+
+TEST_F(JsepSessionTest, TestAnswerPTAsymmetryRtxApt) {
+ // JsepSessionImpl will never answer with an asymmetric payload type
+ // so we have to rewrite SDP a little.
+ types.push_back(SdpMediaSection::kVideo);
+ AddTracks(*mSessionOff, "video");
+ AddTracks(*mSessionAns, "video");
+
+ std::string offer = CreateOffer();
+ SetLocalOffer(offer);
+
+ Replace("a=rtpmap:124 rtx", "a=rtpmap:42 rtx", &offer);
+ Replace("m=video 9 UDP/TLS/RTP/SAVPF 120 124",
+ "m=video 9 UDP/TLS/RTP/SAVPF 120 42", &offer);
+ ReplaceAll("a=fmtp:124", "a=fmtp:42", &offer);
+
+ SetRemoteOffer(offer);
+
+ std::string answer = CreateAnswer();
+ SetLocalAnswer(answer);
+ SetRemoteAnswer(answer);
+
+ UniquePtr<JsepCodecDescription> codec;
+ GetCodec(*mSessionOff, 0, sdp::kSend, 0, 0, &codec);
+ ASSERT_TRUE(codec);
+ ASSERT_EQ("VP8", codec->mName);
+ ASSERT_EQ("120", codec->mDefaultPt);
+ JsepVideoCodecDescription* vp8 =
+ static_cast<JsepVideoCodecDescription*>(codec.get());
+ ASSERT_EQ("42", vp8->mRtxPayloadType);
+ GetCodec(*mSessionOff, 0, sdp::kRecv, 0, 0, &codec);
+ ASSERT_TRUE(codec);
+ ASSERT_EQ("VP8", codec->mName);
+ ASSERT_EQ("120", codec->mDefaultPt);
+ vp8 = static_cast<JsepVideoCodecDescription*>(codec.get());
+ ASSERT_EQ("124", vp8->mRtxPayloadType);
+
+ GetCodec(*mSessionAns, 0, sdp::kSend, 0, 0, &codec);
+ ASSERT_TRUE(codec);
+ ASSERT_EQ("VP8", codec->mName);
+ vp8 = static_cast<JsepVideoCodecDescription*>(codec.get());
+ ASSERT_EQ("120", vp8->mDefaultPt);
+ ASSERT_EQ("42", vp8->mRtxPayloadType);
+ GetCodec(*mSessionAns, 0, sdp::kRecv, 0, 0, &codec);
+ ASSERT_TRUE(codec);
+ ASSERT_EQ("VP8", codec->mName);
+ vp8 = static_cast<JsepVideoCodecDescription*>(codec.get());
+ ASSERT_EQ("120", vp8->mDefaultPt);
+ ASSERT_EQ("42", vp8->mRtxPayloadType);
+}
+
+TEST_F(JsepSessionTest, TestOfferNoRtx) {
+ for (auto& codec : mSessionOff->Codecs()) {
+ if (codec->Type() == SdpMediaSection::kVideo) {
+ JsepVideoCodecDescription* videoCodec =
+ static_cast<JsepVideoCodecDescription*>(codec.get());
+ videoCodec->mRtxEnabled = false;
+ }
+ }
+
+ types.push_back(SdpMediaSection::kVideo);
+ AddTracks(*mSessionOff, "video");
+ AddTracks(*mSessionAns, "video");
+ JsepOfferOptions options;
+
+ std::string offer;
+ JsepSession::Result result = mSessionOff->CreateOffer(options, &offer);
+ ASSERT_FALSE(result.mError.isSome());
+ ASSERT_EQ(std::string::npos, offer.find("rtx")) << offer;
+
+ OfferAnswer();
+
+ // Answerer should use what the offerer suggested
+ UniquePtr<JsepCodecDescription> codec;
+ for (size_t i = 0; i < 4; ++i) {
+ GetCodec(*mSessionAns, 0, sdp::kSend, 0, i, &codec);
+ ASSERT_TRUE(codec);
+ JsepVideoCodecDescription* videoCodec =
+ static_cast<JsepVideoCodecDescription*>(codec.get());
+ ASSERT_FALSE(videoCodec->mRtxEnabled);
+ GetCodec(*mSessionAns, 0, sdp::kRecv, 0, i, &codec);
+ ASSERT_TRUE(codec);
+ videoCodec = static_cast<JsepVideoCodecDescription*>(codec.get());
+ ASSERT_FALSE(videoCodec->mRtxEnabled);
+ }
+}
+
+TEST_F(JsepSessionTest, TestOneWayRtx) {
+ for (auto& codec : mSessionAns->Codecs()) {
+ if (codec->Type() == SdpMediaSection::kVideo) {
+ JsepVideoCodecDescription* videoCodec =
+ static_cast<JsepVideoCodecDescription*>(codec.get());
+ videoCodec->mRtxEnabled = false;
+ }
+ }
+
+ types.push_back(SdpMediaSection::kVideo);
+ AddTracks(*mSessionOff, "video");
+ AddTracks(*mSessionAns, "video");
+ JsepOfferOptions options;
+
+ std::string offer;
+ JsepSession::Result result = mSessionAns->CreateOffer(options, &offer);
+ ASSERT_FALSE(result.mError.isSome());
+ ASSERT_EQ(std::string::npos, offer.find("rtx")) << offer;
+
+ OfferAnswer();
+
+ // If the answerer does not support rtx, the offerer should not send it,
+ // but it is too late to turn off recv on the offerer side.
+ UniquePtr<JsepCodecDescription> codec;
+ for (size_t i = 0; i < 4; ++i) {
+ GetCodec(*mSessionOff, 0, sdp::kSend, 0, i, &codec);
+ ASSERT_TRUE(codec);
+ JsepVideoCodecDescription* videoCodec =
+ static_cast<JsepVideoCodecDescription*>(codec.get());
+ ASSERT_FALSE(videoCodec->mRtxEnabled);
+ GetCodec(*mSessionOff, 0, sdp::kRecv, 0, i, &codec);
+ ASSERT_TRUE(codec);
+ videoCodec = static_cast<JsepVideoCodecDescription*>(codec.get());
+ ASSERT_TRUE(videoCodec->mRtxEnabled);
+ }
+}
+
+TEST_F(JsepSessionTest, TestRtxNoSsrcGroup) {
+ mSessionOff->AddTransceiver(JsepTransceiver(
+ SdpMediaSection::kVideo, mUuidGen, SdpDirectionAttribute::kRecvonly));
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ std::string offer = mSessionOff->GetLocalDescription(kJsepDescriptionCurrent);
+ ASSERT_EQ(std::string::npos, offer.find("FID")) << offer;
+
+ std::string answer =
+ mSessionOff->GetRemoteDescription(kJsepDescriptionCurrent);
+ ASSERT_EQ(std::string::npos, answer.find("FID")) << answer;
+}
+
+TEST_F(JsepSessionTest, TestRtxSsrcGroupOnlyOffered) {
+ mSessionOff->AddTransceiver(JsepTransceiver(
+ SdpMediaSection::kVideo, mUuidGen, SdpDirectionAttribute::kSendonly));
+
+ OfferAnswer(CHECK_SUCCESS);
+
+ std::string offer = mSessionOff->GetLocalDescription(kJsepDescriptionCurrent);
+ ASSERT_NE(std::string::npos, offer.find("FID")) << offer;
+
+ std::string answer =
+ mSessionOff->GetRemoteDescription(kJsepDescriptionCurrent);
+ ASSERT_EQ(std::string::npos, answer.find("FID")) << answer;
+}
+
+TEST_F(JsepSessionTest, TestOfferRtxNoMsid) {
+ for (auto& codec : mSessionOff->Codecs()) {
+ if (codec->mName == "VP8") {
+ JsepVideoCodecDescription* vp8 =
+ static_cast<JsepVideoCodecDescription*>(codec.get());
+ vp8->EnableRtx("42");
+ break;
+ }
+ }
+
+ types.push_back(SdpMediaSection::kVideo);
+ AddTracks(*mSessionOff, "video");
+
+ mSessionOff->ForEachTransceiver([this](JsepTransceiver& aTransceiver) {
+ if (!IsNull(aTransceiver.mSendTrack)) {
+ std::vector<std::string> empty;
+ aTransceiver.mSendTrack.UpdateStreamIds(empty);
+ }
+ });
+
+ // MSID stream absence should not influence FID ssrc-group
+ JsepOfferOptions options;
+ std::string offer;
+ JsepSession::Result result = mSessionOff->CreateOffer(options, &offer);
+ ASSERT_FALSE(result.mError.isSome());
+ ASSERT_NE(std::string::npos, offer.find("FID")) << offer;
+}
+
+TEST_F(JsepSessionTest, TestDuplicatePayloadTypes) {
+ for (auto& codec : mSessionOff->Codecs()) {
+ if (codec->Type() == SdpMediaSection::kVideo) {
+ JsepVideoCodecDescription* videoCodec =
+ static_cast<JsepVideoCodecDescription*>(codec.get());
+ videoCodec->mRtxPayloadType = "97";
+ videoCodec->EnableFec("97", "97");
+ }
+ }
+
+ types.push_back(SdpMediaSection::kVideo);
+ AddTracks(*mSessionOff, "video");
+ AddTracks(*mSessionAns, "video");
+
+ OfferAnswer();
+
+ std::vector<sdp::Direction> directions = {sdp::kSend, sdp::kRecv};
+ for (auto direction : directions) {
+ UniquePtr<JsepCodecDescription> codec;
+ std::set<std::string> payloadTypes;
+ std::string redPt, ulpfecPt;
+ for (size_t i = 0; i < 4; ++i) {
+ GetCodec(*mSessionOff, 0, direction, 0, i, &codec);
+ ASSERT_TRUE(codec);
+ JsepVideoCodecDescription* videoCodec =
+ static_cast<JsepVideoCodecDescription*>(codec.get());
+ ASSERT_TRUE(payloadTypes.insert(videoCodec->mDefaultPt).second);
+ ASSERT_TRUE(payloadTypes.insert(videoCodec->mRtxPayloadType).second);
+ // ULPFEC and RED payload types are the same for each codec, so we only
+ // check them for the first one.
+ if (i == 0) {
+ ASSERT_TRUE(payloadTypes.insert(videoCodec->mREDPayloadType).second);
+ ASSERT_TRUE(payloadTypes.insert(videoCodec->mULPFECPayloadType).second);
+ redPt = videoCodec->mREDPayloadType;
+ ulpfecPt = videoCodec->mULPFECPayloadType;
+ } else {
+ ASSERT_TRUE(redPt == videoCodec->mREDPayloadType);
+ ASSERT_TRUE(ulpfecPt == videoCodec->mULPFECPayloadType);
+ }
+ }
+ }
+}
+
+TEST_F(JsepSessionTest, TestTransportAttributeValidation) {
+ const std::string sdpTemplate =
+ "v=0\r\n"
+ "o=- 6 2 IN IP4 1r\r\n"
+ "t=0 0a\r\n"
+ "a=group:BUNDLE audio video\r\n"
+ "m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n"
+ "c=IN IP4 51.81.107.13\r\n"
+ "a=ice-ufrag:Xp\r\n"
+ "a=ice-pwd:he\r\n"
+ "a=setup:actpass\r\n"
+ "a=fingerprint:sha-256 "
+ "DC:FC:25:56:2B:88:77:2F:E4:FA:97:4E:2E:F1:D6:34:A6:A0:11:E2:E4:38:B3:98:"
+ "08:D2:F7:9D:F5:E2:C1:15\r\n"
+ "a=sendrecv\r\n"
+ "a=extmap:11 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n"
+ "a=fmtp:111 maxplaybackrate=48000;stereo=1;useinbandfec=1\r\n"
+ "a=mid:audio\r\n"
+ "a=rtcp-mux\r\n"
+ "a=rtpmap:111 opus/48000/2\r\n"
+ "a=ssrc:3463672643 cname:{ec9a356a-8d2c-504e-9977-99070a51f929}\r\n"
+ "m=video 9 UDP/TLS/RTP/SAVPF 100\r\n"
+ "c=IN IP4 51.81.107.13\r\n"
+ "a=rtpmap:100 VP8/90000\r\n"
+ "a=extmap:1 urn:ietf:params:rtp-hdrext:toffset\r\n"
+ "a=mid:video\r\n"
+ "a=ice-ufrag:Xp\r\n"
+ "a=ice-pwd:he\r\n"
+ "a=setup:actpass\r\n"
+ "a=fingerprint:sha-256 "
+ "DC:FC:25:56:2B:88:77:2F:E4:FA:97:4E:2E:F1:D6:34:A6:A0:11:E2:E4:38:B3:98:"
+ "08:D2:F7:9D:F5:E2:C1:15\r\n";
+
+ // Control case
+ {
+ auto result = mSessionOff->SetRemoteDescription(kJsepSdpOffer, sdpTemplate);
+ ASSERT_FALSE(result.mError.isSome());
+ }
+
+ // Missing ufrag
+ {
+ UniquePtr<Sdp> parsed = Parse(sdpTemplate);
+ parsed->GetMediaSection(0).GetAttributeList().RemoveAttribute(
+ SdpAttribute::kIceUfragAttribute);
+ auto sdp = parsed->ToString();
+ auto result = mSessionOff->SetRemoteDescription(kJsepSdpOffer, sdp);
+ ASSERT_TRUE(result.mError.isSome());
+ }
+
+ // Missing pwd, bundle tag
+ {
+ UniquePtr<Sdp> parsed = Parse(sdpTemplate);
+ parsed->GetMediaSection(0).GetAttributeList().RemoveAttribute(
+ SdpAttribute::kIcePwdAttribute);
+ auto sdp = parsed->ToString();
+ auto result = mSessionOff->SetRemoteDescription(kJsepSdpOffer, sdp);
+ ASSERT_TRUE(result.mError.isSome());
+ }
+
+ // Missing setup, bundle tag
+ {
+ UniquePtr<Sdp> parsed = Parse(sdpTemplate);
+ parsed->GetMediaSection(0).GetAttributeList().RemoveAttribute(
+ SdpAttribute::kSetupAttribute);
+ auto sdp = parsed->ToString();
+ auto result = mSessionOff->SetRemoteDescription(kJsepSdpOffer, sdp);
+ ASSERT_TRUE(result.mError.isSome());
+ }
+
+ // Invalid setup attribute (holdconn), bundle tag
+ {
+ UniquePtr<Sdp> parsed = Parse(sdpTemplate);
+ parsed->GetMediaSection(0).GetAttributeList().SetAttribute(
+ new SdpSetupAttribute(SdpSetupAttribute::kHoldconn));
+ auto sdp = parsed->ToString();
+ auto result = mSessionOff->SetRemoteDescription(kJsepSdpOffer, sdp);
+ ASSERT_TRUE(result.mError.isSome());
+ }
+
+ // Missing fingerprint, bundle tag
+ {
+ UniquePtr<Sdp> parsed = Parse(sdpTemplate);
+ parsed->GetMediaSection(0).GetAttributeList().RemoveAttribute(
+ SdpAttribute::kFingerprintAttribute);
+ auto sdp = parsed->ToString();
+ auto result = mSessionOff->SetRemoteDescription(kJsepSdpOffer, sdp);
+ ASSERT_TRUE(result.mError.isSome());
+ }
+
+ // Unknown fingerprint algorithm
+ {
+ std::string mungedSdp = sdpTemplate;
+ ReplaceAll("fingerprint:sha", "fingerprint:foo", &mungedSdp);
+ UniquePtr<Sdp> parsed = Parse(mungedSdp);
+ auto sdp = parsed->ToString();
+ auto result = mSessionOff->SetRemoteDescription(kJsepSdpOffer, sdp);
+ ASSERT_TRUE(result.mError.isSome());
+ }
+
+ // Missing pwd, bundled msection without bundle-only
+ {
+ UniquePtr<Sdp> parsed = Parse(sdpTemplate);
+ parsed->GetMediaSection(1).GetAttributeList().RemoveAttribute(
+ SdpAttribute::kIcePwdAttribute);
+ auto sdp = parsed->ToString();
+ auto result = mSessionOff->SetRemoteDescription(kJsepSdpOffer, sdp);
+ ASSERT_TRUE(result.mError.isSome());
+ }
+
+ // Missing setup, bundled msection without bundle-only
+ {
+ UniquePtr<Sdp> parsed = Parse(sdpTemplate);
+ parsed->GetMediaSection(1).GetAttributeList().RemoveAttribute(
+ SdpAttribute::kSetupAttribute);
+ auto sdp = parsed->ToString();
+ auto result = mSessionOff->SetRemoteDescription(kJsepSdpOffer, sdp);
+ ASSERT_TRUE(result.mError.isSome());
+ }
+
+ // Missing fingerprint, bundled msection without bundle-only
+ {
+ UniquePtr<Sdp> parsed = Parse(sdpTemplate);
+ parsed->GetMediaSection(1).GetAttributeList().RemoveAttribute(
+ SdpAttribute::kFingerprintAttribute);
+ auto sdp = parsed->ToString();
+ auto result = mSessionOff->SetRemoteDescription(kJsepSdpOffer, sdp);
+ ASSERT_TRUE(result.mError.isSome());
+ }
+
+ // Missing ufrag attribute, bundle-only msection
+ {
+ UniquePtr<Sdp> parsed = Parse(sdpTemplate);
+ parsed->GetMediaSection(1).GetAttributeList().RemoveAttribute(
+ SdpAttribute::kIceUfragAttribute);
+ parsed->GetMediaSection(1).GetAttributeList().SetAttribute(
+ new SdpFlagAttribute(SdpAttribute::kBundleOnlyAttribute));
+ auto sdp = parsed->ToString();
+ auto result = mSessionOff->SetRemoteDescription(kJsepSdpOffer, sdp);
+ ASSERT_FALSE(result.mError.isSome());
+ }
+
+ // Missing pwd attribute, bundle-only msection
+ {
+ UniquePtr<Sdp> parsed = Parse(sdpTemplate);
+ parsed->GetMediaSection(1).GetAttributeList().RemoveAttribute(
+ SdpAttribute::kIcePwdAttribute);
+ parsed->GetMediaSection(1).GetAttributeList().SetAttribute(
+ new SdpFlagAttribute(SdpAttribute::kBundleOnlyAttribute));
+ auto sdp = parsed->ToString();
+ auto result = mSessionOff->SetRemoteDescription(kJsepSdpOffer, sdp);
+ ASSERT_FALSE(result.mError.isSome());
+ }
+
+ // Missing fingerprint attribute, bundle-only msection
+ {
+ UniquePtr<Sdp> parsed = Parse(sdpTemplate);
+ parsed->GetMediaSection(1).GetAttributeList().RemoveAttribute(
+ SdpAttribute::kFingerprintAttribute);
+ parsed->GetMediaSection(1).GetAttributeList().SetAttribute(
+ new SdpFlagAttribute(SdpAttribute::kBundleOnlyAttribute));
+ auto sdp = parsed->ToString();
+ auto result = mSessionOff->SetRemoteDescription(kJsepSdpOffer, sdp);
+ ASSERT_FALSE(result.mError.isSome());
+ }
+}
+} // namespace mozilla
diff --git a/media/webrtc/signaling/gtest/jsep_track_unittest.cpp b/media/webrtc/signaling/gtest/jsep_track_unittest.cpp
new file mode 100644
index 0000000000..778b87102e
--- /dev/null
+++ b/media/webrtc/signaling/gtest/jsep_track_unittest.cpp
@@ -0,0 +1,1748 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nss.h"
+#include "ssl.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+
+#include "jsep/JsepTrack.h"
+#include "sdp/SipccSdp.h"
+#include "sdp/SipccSdpParser.h"
+#include "sdp/SdpHelper.h"
+
+namespace mozilla {
+
+class JsepTrackTestBase : public ::testing::Test {
+ public:
+ static void SetUpTestCase() {
+ NSS_NoDB_Init(nullptr);
+ NSS_SetDomesticPolicy();
+ }
+};
+
+class JsepTrackTest : public JsepTrackTestBase {
+ public:
+ JsepTrackTest()
+ : mSendOff(SdpMediaSection::kAudio, sdp::kSend),
+ mRecvOff(SdpMediaSection::kAudio, sdp::kRecv),
+ mSendAns(SdpMediaSection::kAudio, sdp::kSend),
+ mRecvAns(SdpMediaSection::kAudio, sdp::kRecv) {}
+
+ void TearDown() override {
+ if (::testing::UnitTest::GetInstance()
+ ->current_test_info()
+ ->result()
+ ->Failed()) {
+ if (mOffer) {
+ std::cerr << "Offer SDP: " << std::endl;
+ mOffer->Serialize(std::cerr);
+ }
+
+ if (mAnswer) {
+ std::cerr << "Answer SDP: " << std::endl;
+ mAnswer->Serialize(std::cerr);
+ }
+ }
+ }
+
+ std::vector<UniquePtr<JsepCodecDescription>> MakeCodecs(
+ bool addFecCodecs = false, bool preferRed = false,
+ bool addDtmfCodec = false) const {
+ std::vector<UniquePtr<JsepCodecDescription>> results;
+ results.emplace_back(JsepAudioCodecDescription::CreateDefaultOpus());
+ results.emplace_back(JsepAudioCodecDescription::CreateDefaultG722());
+ if (addDtmfCodec) {
+ results.emplace_back(
+ JsepAudioCodecDescription::CreateDefaultTelephoneEvent());
+ }
+
+ if (addFecCodecs && preferRed) {
+ results.emplace_back(JsepVideoCodecDescription::CreateDefaultRed());
+ }
+
+ results.emplace_back(JsepVideoCodecDescription::CreateDefaultVP8(false));
+ results.emplace_back(JsepVideoCodecDescription::CreateDefaultH264_1(false));
+
+ if (addFecCodecs) {
+ if (!preferRed) {
+ results.emplace_back(JsepVideoCodecDescription::CreateDefaultRed());
+ }
+ results.emplace_back(JsepVideoCodecDescription::CreateDefaultUlpFec());
+ }
+
+ results.emplace_back(new JsepApplicationCodecDescription(
+ "webrtc-datachannel", 256, 5999, 499));
+
+ // if we're doing something with red, it needs
+ // to update the redundant encodings list
+ for (auto& codec : results) {
+ if (codec->mName == "red") {
+ JsepVideoCodecDescription& red =
+ static_cast<JsepVideoCodecDescription&>(*codec);
+ red.UpdateRedundantEncodings(results);
+ }
+ }
+
+ return results;
+ }
+
+ void Init(SdpMediaSection::MediaType type) {
+ InitCodecs();
+ InitTracks(type);
+ InitSdp(type);
+ }
+
+ void InitCodecs() {
+ mOffCodecs = MakeCodecs();
+ mAnsCodecs = MakeCodecs();
+ }
+
+ void InitTracks(SdpMediaSection::MediaType type) {
+ mSendOff = JsepTrack(type, sdp::kSend);
+ if (type != SdpMediaSection::MediaType::kApplication) {
+ mSendOff.UpdateStreamIds(std::vector<std::string>(1, "stream_id"));
+ }
+ mRecvOff = JsepTrack(type, sdp::kRecv);
+ mSendOff.PopulateCodecs(mOffCodecs);
+ mRecvOff.PopulateCodecs(mOffCodecs);
+
+ mSendAns = JsepTrack(type, sdp::kSend);
+ if (type != SdpMediaSection::MediaType::kApplication) {
+ mSendAns.UpdateStreamIds(std::vector<std::string>(1, "stream_id"));
+ }
+ mRecvAns = JsepTrack(type, sdp::kRecv);
+ mSendAns.PopulateCodecs(mAnsCodecs);
+ mRecvAns.PopulateCodecs(mAnsCodecs);
+ }
+
+ void InitSdp(SdpMediaSection::MediaType type) {
+ std::vector<std::string> msids(1, "*");
+ std::string error;
+ SdpHelper helper(&error);
+
+ mOffer.reset(new SipccSdp(SdpOrigin("", 0, 0, sdp::kIPv4, "")));
+ mOffer->AddMediaSection(type, SdpDirectionAttribute::kSendrecv, 0,
+ SdpHelper::GetProtocolForMediaType(type),
+ sdp::kIPv4, "0.0.0.0");
+ // JsepTrack doesn't set msid-semantic
+ helper.SetupMsidSemantic(msids, mOffer.get());
+
+ mAnswer.reset(new SipccSdp(SdpOrigin("", 0, 0, sdp::kIPv4, "")));
+ mAnswer->AddMediaSection(type, SdpDirectionAttribute::kSendrecv, 0,
+ SdpHelper::GetProtocolForMediaType(type),
+ sdp::kIPv4, "0.0.0.0");
+ // JsepTrack doesn't set msid-semantic
+ helper.SetupMsidSemantic(msids, mAnswer.get());
+ }
+
+ SdpMediaSection& GetOffer() { return mOffer->GetMediaSection(0); }
+
+ SdpMediaSection& GetAnswer() { return mAnswer->GetMediaSection(0); }
+
+ void CreateOffer() {
+ mSendOff.AddToOffer(mSsrcGenerator, &GetOffer());
+ mRecvOff.AddToOffer(mSsrcGenerator, &GetOffer());
+ }
+
+ void CreateAnswer() {
+ if (mRecvAns.GetMediaType() != SdpMediaSection::MediaType::kApplication) {
+ mRecvAns.RecvTrackSetRemote(*mOffer, GetOffer());
+ mSendAns.SendTrackSetRemote(mSsrcGenerator, GetOffer());
+ }
+
+ mSendAns.AddToAnswer(GetOffer(), mSsrcGenerator, &GetAnswer());
+ mRecvAns.AddToAnswer(GetOffer(), mSsrcGenerator, &GetAnswer());
+ }
+
+ void Negotiate() {
+ if (mRecvOff.GetMediaType() != SdpMediaSection::MediaType::kApplication) {
+ mRecvOff.RecvTrackSetRemote(*mAnswer, GetAnswer());
+ mSendOff.SendTrackSetRemote(mSsrcGenerator, GetAnswer());
+ }
+
+ if (GetAnswer().IsSending()) {
+ mSendAns.Negotiate(GetAnswer(), GetOffer(), GetAnswer());
+ mRecvOff.Negotiate(GetAnswer(), GetAnswer(), GetOffer());
+ }
+
+ if (GetAnswer().IsReceiving()) {
+ mRecvAns.Negotiate(GetAnswer(), GetOffer(), GetAnswer());
+ mSendOff.Negotiate(GetAnswer(), GetAnswer(), GetOffer());
+ }
+ }
+
+ void OfferAnswer() {
+ CreateOffer();
+ CreateAnswer();
+ Negotiate();
+ SanityCheck();
+ }
+
+ // TODO: Look into writing a macro that wraps an ASSERT_ and returns false
+ // if it fails (probably requires writing a bool-returning function that
+ // takes a void-returning lambda with a bool outparam, which will in turn
+ // invokes the ASSERT_)
+ static void CheckEncodingCount(size_t expected, const JsepTrack& send,
+ const JsepTrack& recv) {
+ if (expected) {
+ ASSERT_TRUE(send.GetNegotiatedDetails());
+ ASSERT_TRUE(recv.GetNegotiatedDetails());
+ }
+
+ if (!send.GetStreamIds().empty() && send.GetNegotiatedDetails()) {
+ ASSERT_EQ(expected, send.GetNegotiatedDetails()->GetEncodingCount());
+ }
+
+ if (!recv.GetStreamIds().empty() && recv.GetNegotiatedDetails()) {
+ ASSERT_EQ(expected, recv.GetNegotiatedDetails()->GetEncodingCount());
+ }
+ }
+
+ void CheckOffEncodingCount(size_t expected) const {
+ CheckEncodingCount(expected, mSendOff, mRecvAns);
+ }
+
+ void CheckAnsEncodingCount(size_t expected) const {
+ CheckEncodingCount(expected, mSendAns, mRecvOff);
+ }
+
+ UniquePtr<JsepCodecDescription> GetCodec(const JsepTrack& track,
+ SdpMediaSection::MediaType type,
+ size_t expectedSize,
+ size_t codecIndex) const {
+ if (!track.GetNegotiatedDetails() ||
+ track.GetNegotiatedDetails()->GetEncodingCount() != 1U ||
+ track.GetMediaType() != type) {
+ return nullptr;
+ }
+ const auto& codecs =
+ track.GetNegotiatedDetails()->GetEncoding(0).GetCodecs();
+ // it should not be possible for codecs to have a different type
+ // than the track, but we'll check the codec here just in case.
+ if (codecs.size() != expectedSize || codecIndex >= expectedSize ||
+ codecs[codecIndex]->Type() != type) {
+ return nullptr;
+ }
+ return UniquePtr<JsepCodecDescription>(codecs[codecIndex]->Clone());
+ }
+
+ UniquePtr<JsepVideoCodecDescription> GetVideoCodec(
+ const JsepTrack& track, size_t expectedSize = 1,
+ size_t codecIndex = 0) const {
+ auto codec =
+ GetCodec(track, SdpMediaSection::kVideo, expectedSize, codecIndex);
+ return UniquePtr<JsepVideoCodecDescription>(
+ static_cast<JsepVideoCodecDescription*>(codec.release()));
+ }
+
+ UniquePtr<JsepAudioCodecDescription> GetAudioCodec(
+ const JsepTrack& track, size_t expectedSize = 1,
+ size_t codecIndex = 0) const {
+ auto codec =
+ GetCodec(track, SdpMediaSection::kAudio, expectedSize, codecIndex);
+ return UniquePtr<JsepAudioCodecDescription>(
+ static_cast<JsepAudioCodecDescription*>(codec.release()));
+ }
+
+ void CheckOtherFbExists(const JsepVideoCodecDescription& videoCodec,
+ SdpRtcpFbAttributeList::Type type) const {
+ for (const auto& fb : videoCodec.mOtherFbTypes) {
+ if (fb.type == type) {
+ return; // found the RtcpFb type, so stop looking
+ }
+ }
+ FAIL(); // RtcpFb type not found
+ }
+
+ void SanityCheckRtcpFbs(const JsepVideoCodecDescription& a,
+ const JsepVideoCodecDescription& b) const {
+ ASSERT_EQ(a.mNackFbTypes.size(), b.mNackFbTypes.size());
+ ASSERT_EQ(a.mAckFbTypes.size(), b.mAckFbTypes.size());
+ ASSERT_EQ(a.mCcmFbTypes.size(), b.mCcmFbTypes.size());
+ ASSERT_EQ(a.mOtherFbTypes.size(), b.mOtherFbTypes.size());
+ }
+
+ void SanityCheckCodecs(const JsepCodecDescription& a,
+ const JsepCodecDescription& b) const {
+#define MSG \
+ "For codecs " << a.mName << " (" << a.mDirection << ") and " << b.mName \
+ << " (" << b.mDirection << ")"
+ ASSERT_EQ(a.Type(), b.Type()) << MSG;
+ if (a.Type() != SdpMediaSection::kApplication) {
+ ASSERT_EQ(a.mDefaultPt, b.mDefaultPt) << MSG;
+ }
+ ASSERT_EQ(a.mName, b.mName);
+ if (!mExpectDifferingFmtp) {
+ ASSERT_EQ(a.mSdpFmtpLine, b.mSdpFmtpLine) << MSG;
+ }
+ ASSERT_EQ(a.mClock, b.mClock) << MSG;
+ ASSERT_EQ(a.mChannels, b.mChannels) << MSG;
+ ASSERT_NE(a.mDirection, b.mDirection) << MSG;
+ // These constraints are for fmtp and rid, which _are_ signaled
+ ASSERT_EQ(a.mConstraints, b.mConstraints) << MSG;
+#undef MSG
+
+ if (a.Type() == SdpMediaSection::kVideo) {
+ SanityCheckRtcpFbs(static_cast<const JsepVideoCodecDescription&>(a),
+ static_cast<const JsepVideoCodecDescription&>(b));
+ }
+ }
+
+ void SanityCheckEncodings(const JsepTrackEncoding& a,
+ const JsepTrackEncoding& b) const {
+ ASSERT_EQ(a.GetCodecs().size(), b.GetCodecs().size());
+ for (size_t i = 0; i < a.GetCodecs().size(); ++i) {
+ SanityCheckCodecs(*a.GetCodecs()[i], *b.GetCodecs()[i]);
+ }
+
+ ASSERT_EQ(a.mRid, b.mRid);
+ // mConstraints will probably differ, since they are not signaled to the
+ // other side.
+ }
+
+ void SanityCheckNegotiatedDetails(const JsepTrackNegotiatedDetails& a,
+ const JsepTrackNegotiatedDetails& b) const {
+ ASSERT_EQ(a.GetEncodingCount(), b.GetEncodingCount());
+ for (size_t i = 0; i < a.GetEncodingCount(); ++i) {
+ SanityCheckEncodings(a.GetEncoding(i), b.GetEncoding(i));
+ }
+
+ ASSERT_EQ(a.GetUniquePayloadTypes().size(),
+ b.GetUniquePayloadTypes().size());
+ for (size_t i = 0; i < a.GetUniquePayloadTypes().size(); ++i) {
+ ASSERT_EQ(a.GetUniquePayloadTypes()[i], b.GetUniquePayloadTypes()[i]);
+ }
+ }
+
+ void SanityCheckTracks(const JsepTrack& a, const JsepTrack& b) const {
+ if (!a.GetNegotiatedDetails()) {
+ ASSERT_FALSE(!!b.GetNegotiatedDetails());
+ return;
+ }
+
+ ASSERT_TRUE(!!a.GetNegotiatedDetails());
+ ASSERT_TRUE(!!b.GetNegotiatedDetails());
+ ASSERT_EQ(a.GetMediaType(), b.GetMediaType());
+ ASSERT_EQ(a.GetStreamIds(), b.GetStreamIds());
+ ASSERT_EQ(a.GetCNAME(), b.GetCNAME());
+ ASSERT_NE(a.GetDirection(), b.GetDirection());
+ ASSERT_EQ(a.GetSsrcs().size(), b.GetSsrcs().size());
+ for (size_t i = 0; i < a.GetSsrcs().size(); ++i) {
+ ASSERT_EQ(a.GetSsrcs()[i], b.GetSsrcs()[i]);
+ }
+
+ SanityCheckNegotiatedDetails(*a.GetNegotiatedDetails(),
+ *b.GetNegotiatedDetails());
+ }
+
+ void SanityCheck() const {
+ SanityCheckTracks(mSendOff, mRecvAns);
+ SanityCheckTracks(mRecvOff, mSendAns);
+ }
+
+ protected:
+ JsepTrack mSendOff;
+ JsepTrack mRecvOff;
+ JsepTrack mSendAns;
+ JsepTrack mRecvAns;
+ std::vector<UniquePtr<JsepCodecDescription>> mOffCodecs;
+ std::vector<UniquePtr<JsepCodecDescription>> mAnsCodecs;
+ UniquePtr<Sdp> mOffer;
+ UniquePtr<Sdp> mAnswer;
+ SsrcGenerator mSsrcGenerator;
+ bool mExpectDifferingFmtp = false;
+};
+
+TEST_F(JsepTrackTestBase, CreateDestroy) {}
+
+TEST_F(JsepTrackTest, CreateDestroy) { Init(SdpMediaSection::kAudio); }
+
+TEST_F(JsepTrackTest, AudioNegotiation) {
+ Init(SdpMediaSection::kAudio);
+ OfferAnswer();
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+}
+
+TEST_F(JsepTrackTest, VideoNegotiation) {
+ Init(SdpMediaSection::kVideo);
+ OfferAnswer();
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+}
+
+class CheckForCodecType {
+ public:
+ explicit CheckForCodecType(SdpMediaSection::MediaType type, bool* result)
+ : mResult(result), mType(type) {}
+
+ void operator()(const UniquePtr<JsepCodecDescription>& codec) {
+ if (codec->Type() == mType) {
+ *mResult = true;
+ }
+ }
+
+ private:
+ bool* mResult;
+ SdpMediaSection::MediaType mType;
+};
+
+TEST_F(JsepTrackTest, CheckForMismatchedAudioCodecAndVideoTrack) {
+ std::vector<UniquePtr<JsepCodecDescription>> offerCodecs;
+
+ // make codecs including telephone-event (an audio codec)
+ offerCodecs = MakeCodecs(false, false, true);
+ JsepTrack videoTrack(SdpMediaSection::kVideo, sdp::kSend);
+ videoTrack.UpdateStreamIds(std::vector<std::string>(1, "stream_id"));
+ // populate codecs and then make sure we don't have any audio codecs
+ // in the video track
+ videoTrack.PopulateCodecs(offerCodecs);
+
+ bool found = false;
+ videoTrack.ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
+ ASSERT_FALSE(found);
+
+ found = false;
+ videoTrack.ForEachCodec(CheckForCodecType(SdpMediaSection::kVideo, &found));
+ ASSERT_TRUE(found); // for sanity, make sure we did find video codecs
+}
+
+TEST_F(JsepTrackTest, CheckVideoTrackWithHackedDtmfSdp) {
+ Init(SdpMediaSection::kVideo);
+ CreateOffer();
+ // make sure we don't find sdp containing telephone-event in video track
+ ASSERT_EQ(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+ // force audio codec telephone-event into video m= section of offer
+ GetOffer().AddCodec("101", "telephone-event", 8000, 1);
+ // make sure we _do_ find sdp containing telephone-event in video track
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+
+ CreateAnswer();
+ // make sure we don't find sdp containing telephone-event in video track
+ ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+ // force audio codec telephone-event into video m= section of answer
+ GetAnswer().AddCodec("101", "telephone-event", 8000, 1);
+ // make sure we _do_ find sdp containing telephone-event in video track
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+
+ Negotiate();
+ SanityCheck();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ // make sure we still don't find any audio codecs in the video track after
+ // hacking the sdp
+ bool found = false;
+ mSendOff.ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
+ ASSERT_FALSE(found);
+ mRecvOff.ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
+ ASSERT_FALSE(found);
+ mSendAns.ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
+ ASSERT_FALSE(found);
+ mRecvAns.ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
+ ASSERT_FALSE(found);
+}
+
+TEST_F(JsepTrackTest, AudioNegotiationOffererDtmf) {
+ mOffCodecs = MakeCodecs(false, false, true);
+ mAnsCodecs = MakeCodecs(false, false, false);
+
+ InitTracks(SdpMediaSection::kAudio);
+ InitSdp(SdpMediaSection::kAudio);
+ OfferAnswer();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+
+ ASSERT_NE(mOffer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=fmtp:101"), std::string::npos);
+
+ UniquePtr<JsepAudioCodecDescription> track;
+ ASSERT_TRUE((track = GetAudioCodec(mSendOff, 2, 0)));
+ ASSERT_EQ("109", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 2, 0)));
+ ASSERT_EQ("109", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mSendAns, 2, 0)));
+ ASSERT_EQ("109", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 2, 0)));
+ ASSERT_EQ("109", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mSendOff, 2, 1)));
+ ASSERT_EQ("9", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 2, 1)));
+ ASSERT_EQ("9", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mSendAns, 2, 1)));
+ ASSERT_EQ("9", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 2, 1)));
+ ASSERT_EQ("9", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, AudioNegotiationAnswererDtmf) {
+ mOffCodecs = MakeCodecs(false, false, false);
+ mAnsCodecs = MakeCodecs(false, false, true);
+
+ InitTracks(SdpMediaSection::kAudio);
+ InitSdp(SdpMediaSection::kAudio);
+ OfferAnswer();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_EQ(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+
+ ASSERT_EQ(mOffer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=fmtp:101"), std::string::npos);
+
+ UniquePtr<JsepAudioCodecDescription> track;
+ ASSERT_TRUE((track = GetAudioCodec(mSendOff, 2, 0)));
+ ASSERT_EQ("109", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 2, 0)));
+ ASSERT_EQ("109", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mSendAns, 2, 0)));
+ ASSERT_EQ("109", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 2, 0)));
+ ASSERT_EQ("109", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mSendOff, 2, 1)));
+ ASSERT_EQ("9", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 2, 1)));
+ ASSERT_EQ("9", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mSendAns, 2, 1)));
+ ASSERT_EQ("9", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 2, 1)));
+ ASSERT_EQ("9", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, AudioNegotiationOffererAnswererDtmf) {
+ mOffCodecs = MakeCodecs(false, false, true);
+ mAnsCodecs = MakeCodecs(false, false, true);
+
+ InitTracks(SdpMediaSection::kAudio);
+ InitSdp(SdpMediaSection::kAudio);
+ OfferAnswer();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+
+ ASSERT_NE(mOffer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
+
+ UniquePtr<JsepAudioCodecDescription> track;
+ ASSERT_TRUE((track = GetAudioCodec(mSendOff, 3, 0)));
+ ASSERT_EQ("109", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 3, 0)));
+ ASSERT_EQ("109", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mSendAns, 3, 0)));
+ ASSERT_EQ("109", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 3, 0)));
+ ASSERT_EQ("109", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mSendOff, 3, 1)));
+ ASSERT_EQ("9", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 3, 1)));
+ ASSERT_EQ("9", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mSendAns, 3, 1)));
+ ASSERT_EQ("9", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 3, 1)));
+ ASSERT_EQ("9", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mSendOff, 3, 2)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 3, 2)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mSendAns, 3, 2)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 3, 2)));
+ ASSERT_EQ("101", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, AudioNegotiationDtmfOffererNoFmtpAnswererFmtp) {
+ mOffCodecs = MakeCodecs(false, false, true);
+ mAnsCodecs = MakeCodecs(false, false, true);
+
+ mExpectDifferingFmtp = true;
+
+ InitTracks(SdpMediaSection::kAudio);
+ InitSdp(SdpMediaSection::kAudio);
+
+ CreateOffer();
+ GetOffer().RemoveFmtp("101");
+
+ CreateAnswer();
+
+ Negotiate();
+ SanityCheck();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+
+ ASSERT_EQ(mOffer->ToString().find("a=fmtp:101"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
+
+ UniquePtr<JsepAudioCodecDescription> track;
+ ASSERT_TRUE((track = GetAudioCodec(mSendOff, 3, 0)));
+ ASSERT_EQ("109", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 3, 0)));
+ ASSERT_EQ("109", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mSendAns, 3, 0)));
+ ASSERT_EQ("109", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 3, 0)));
+ ASSERT_EQ("109", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mSendOff, 3, 1)));
+ ASSERT_EQ("9", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 3, 1)));
+ ASSERT_EQ("9", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mSendAns, 3, 1)));
+ ASSERT_EQ("9", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 3, 1)));
+ ASSERT_EQ("9", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mSendOff, 3, 2)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_EQ("0-15", track->mSdpFmtpLine.valueOr("nothing"));
+ ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 3, 2)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_EQ("nothing", track->mSdpFmtpLine.valueOr("nothing"));
+ ASSERT_TRUE((track = GetAudioCodec(mSendAns, 3, 2)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_EQ("nothing", track->mSdpFmtpLine.valueOr("nothing"));
+ ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 3, 2)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_EQ("0-15", track->mSdpFmtpLine.valueOr("nothing"));
+}
+
+TEST_F(JsepTrackTest, AudioNegotiationDtmfOffererFmtpAnswererNoFmtp) {
+ mOffCodecs = MakeCodecs(false, false, true);
+ mAnsCodecs = MakeCodecs(false, false, true);
+
+ mExpectDifferingFmtp = true;
+
+ InitTracks(SdpMediaSection::kAudio);
+ InitSdp(SdpMediaSection::kAudio);
+
+ CreateOffer();
+
+ CreateAnswer();
+ GetAnswer().RemoveFmtp("101");
+
+ Negotiate();
+ SanityCheck();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+
+ ASSERT_NE(mOffer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=fmtp:101"), std::string::npos);
+
+ UniquePtr<JsepAudioCodecDescription> track;
+ ASSERT_TRUE((track = GetAudioCodec(mSendOff, 3, 0)));
+ ASSERT_EQ("109", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 3, 0)));
+ ASSERT_EQ("109", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mSendAns, 3, 0)));
+ ASSERT_EQ("109", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 3, 0)));
+ ASSERT_EQ("109", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mSendOff, 3, 1)));
+ ASSERT_EQ("9", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 3, 1)));
+ ASSERT_EQ("9", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mSendAns, 3, 1)));
+ ASSERT_EQ("9", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 3, 1)));
+ ASSERT_EQ("9", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mSendOff, 3, 2)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_EQ("nothing", track->mSdpFmtpLine.valueOr("nothing"));
+ ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 3, 2)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_EQ("0-15", track->mSdpFmtpLine.valueOr("nothing"));
+ ASSERT_TRUE((track = GetAudioCodec(mSendAns, 3, 2)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_EQ("0-15", track->mSdpFmtpLine.valueOr("nothing"));
+ ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 3, 2)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_EQ("nothing", track->mSdpFmtpLine.valueOr("nothing"));
+}
+
+TEST_F(JsepTrackTest, AudioNegotiationDtmfOffererNoFmtpAnswererNoFmtp) {
+ mOffCodecs = MakeCodecs(false, false, true);
+ mAnsCodecs = MakeCodecs(false, false, true);
+
+ mExpectDifferingFmtp = true;
+
+ InitTracks(SdpMediaSection::kAudio);
+ InitSdp(SdpMediaSection::kAudio);
+
+ CreateOffer();
+ GetOffer().RemoveFmtp("101");
+
+ CreateAnswer();
+ GetAnswer().RemoveFmtp("101");
+
+ Negotiate();
+ SanityCheck();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+
+ ASSERT_EQ(mOffer->ToString().find("a=fmtp:101"), std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=fmtp:101"), std::string::npos);
+
+ UniquePtr<JsepAudioCodecDescription> track;
+ ASSERT_TRUE((track = GetAudioCodec(mSendOff, 3, 0)));
+ ASSERT_EQ("109", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 3, 0)));
+ ASSERT_EQ("109", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mSendAns, 3, 0)));
+ ASSERT_EQ("109", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 3, 0)));
+ ASSERT_EQ("109", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mSendOff, 3, 1)));
+ ASSERT_EQ("9", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 3, 1)));
+ ASSERT_EQ("9", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mSendAns, 3, 1)));
+ ASSERT_EQ("9", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 3, 1)));
+ ASSERT_EQ("9", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(mSendOff, 3, 2)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_EQ("nothing", track->mSdpFmtpLine.valueOr("nothing"));
+ ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 3, 2)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_EQ("nothing", track->mSdpFmtpLine.valueOr("nothing"));
+ ASSERT_TRUE((track = GetAudioCodec(mSendAns, 3, 2)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_EQ("nothing", track->mSdpFmtpLine.valueOr("nothing"));
+ ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 3, 2)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_EQ("nothing", track->mSdpFmtpLine.valueOr("nothing"));
+}
+
+TEST_F(JsepTrackTest, VideoNegotationOffererFEC) {
+ mOffCodecs = MakeCodecs(true);
+ mAnsCodecs = MakeCodecs(false);
+
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+
+ ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123"),
+ std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=fmtp:122"), std::string::npos);
+
+ UniquePtr<JsepVideoCodecDescription> track;
+ ASSERT_TRUE((track = GetVideoCodec(mSendOff, 2, 0)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 2, 0)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(mSendAns, 2, 0)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 2, 0)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(mSendOff, 2, 1)));
+ ASSERT_EQ("126", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 2, 1)));
+ ASSERT_EQ("126", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(mSendAns, 2, 1)));
+ ASSERT_EQ("126", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 2, 1)));
+ ASSERT_EQ("126", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, VideoNegotationAnswererFEC) {
+ mOffCodecs = MakeCodecs(false);
+ mAnsCodecs = MakeCodecs(true);
+
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_EQ(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_EQ(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+
+ ASSERT_EQ(mOffer->ToString().find("a=fmtp:122"), std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=fmtp:122"), std::string::npos);
+
+ UniquePtr<JsepVideoCodecDescription> track;
+ ASSERT_TRUE((track = GetVideoCodec(mSendOff, 2, 0)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 2, 0)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(mSendAns, 2, 0)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 2, 0)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(mSendOff, 2, 1)));
+ ASSERT_EQ("126", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 2, 1)));
+ ASSERT_EQ("126", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(mSendAns, 2, 1)));
+ ASSERT_EQ("126", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 2, 1)));
+ ASSERT_EQ("126", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFEC) {
+ mOffCodecs = MakeCodecs(true);
+ mAnsCodecs = MakeCodecs(true);
+
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+
+ ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123"),
+ std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=fmtp:122 120/126/123"),
+ std::string::npos);
+
+ UniquePtr<JsepVideoCodecDescription> track;
+ ASSERT_TRUE((track = GetVideoCodec(mSendOff, 4)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 4)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(mSendAns, 4)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 4)));
+ ASSERT_EQ("120", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFECPreferred) {
+ mOffCodecs = MakeCodecs(true, true);
+ mAnsCodecs = MakeCodecs(true);
+
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+
+ ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123"),
+ std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=fmtp:122 120/126/123"),
+ std::string::npos);
+
+ UniquePtr<JsepVideoCodecDescription> track;
+ ASSERT_TRUE((track = GetVideoCodec(mSendOff, 4)));
+ ASSERT_EQ("122", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 4)));
+ ASSERT_EQ("122", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(mSendAns, 4)));
+ ASSERT_EQ("122", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 4)));
+ ASSERT_EQ("122", track->mDefaultPt);
+}
+
+// Make sure we only put the right things in the fmtp:122 120/.... line
+TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFECMismatch) {
+ mOffCodecs = MakeCodecs(true, true);
+ mAnsCodecs = MakeCodecs(true);
+ // remove h264 from answer codecs
+ ASSERT_EQ("H264", mAnsCodecs[3]->mName);
+ mAnsCodecs.erase(mAnsCodecs.begin() + 3);
+
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+
+ ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123"),
+ std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=fmtp:122 120/123"), std::string::npos);
+
+ UniquePtr<JsepVideoCodecDescription> track;
+ ASSERT_TRUE((track = GetVideoCodec(mSendOff, 3)));
+ ASSERT_EQ("122", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 3)));
+ ASSERT_EQ("122", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(mSendAns, 3)));
+ ASSERT_EQ("122", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 3)));
+ ASSERT_EQ("122", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFECZeroVP9Codec) {
+ mOffCodecs = MakeCodecs(true);
+ auto vp9 = JsepVideoCodecDescription::CreateDefaultVP9(false);
+ vp9->mDefaultPt = "0";
+ mOffCodecs.push_back(std::move(vp9));
+
+ ASSERT_EQ(8U, mOffCodecs.size());
+ JsepVideoCodecDescription& red =
+ static_cast<JsepVideoCodecDescription&>(*mOffCodecs[4]);
+ ASSERT_EQ("red", red.mName);
+ // rebuild the redundant encodings with our newly added "wacky" VP9
+ red.mRedundantEncodings.clear();
+ red.UpdateRedundantEncodings(mOffCodecs);
+
+ mAnsCodecs = MakeCodecs(true);
+
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+
+ ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123/0"),
+ std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=fmtp:122 120/126/123\r\n"),
+ std::string::npos);
+}
+
+TEST_F(JsepTrackTest, VideoNegotiationOfferRemb) {
+ InitCodecs();
+ // enable remb on the offer codecs
+ ((JsepVideoCodecDescription&)*mOffCodecs[2]).EnableRemb();
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ // make sure REMB is on offer and not on answer
+ ASSERT_NE(mOffer->ToString().find("a=rtcp-fb:120 goog-remb"),
+ std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=rtcp-fb:120 goog-remb"),
+ std::string::npos);
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ UniquePtr<JsepVideoCodecDescription> codec;
+ ASSERT_TRUE((codec = GetVideoCodec(mSendOff, 2, 0)));
+ ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
+ ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 2, 0)));
+ ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
+ ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 2, 0)));
+ ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
+ ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 2, 0)));
+ ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
+}
+
+TEST_F(JsepTrackTest, VideoNegotiationAnswerRemb) {
+ InitCodecs();
+ // enable remb on the answer codecs
+ ((JsepVideoCodecDescription&)*mAnsCodecs[2]).EnableRemb();
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ // make sure REMB is not on offer and not on answer
+ ASSERT_EQ(mOffer->ToString().find("a=rtcp-fb:120 goog-remb"),
+ std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=rtcp-fb:120 goog-remb"),
+ std::string::npos);
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ UniquePtr<JsepVideoCodecDescription> codec;
+ ASSERT_TRUE((codec = GetVideoCodec(mSendOff, 2, 0)));
+ ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
+ ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 2, 0)));
+ ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
+ ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 2, 0)));
+ ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
+ ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 2, 0)));
+ ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
+}
+
+TEST_F(JsepTrackTest, VideoNegotiationOfferAnswerRemb) {
+ InitCodecs();
+ // enable remb on the offer and answer codecs
+ ((JsepVideoCodecDescription&)*mOffCodecs[2]).EnableRemb();
+ ((JsepVideoCodecDescription&)*mAnsCodecs[2]).EnableRemb();
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ // make sure REMB is on offer and on answer
+ ASSERT_NE(mOffer->ToString().find("a=rtcp-fb:120 goog-remb"),
+ std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtcp-fb:120 goog-remb"),
+ std::string::npos);
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ UniquePtr<JsepVideoCodecDescription> codec;
+ ASSERT_TRUE((codec = GetVideoCodec(mSendOff, 2, 0)));
+ ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
+ CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kRemb);
+ ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 2, 0)));
+ ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
+ CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kRemb);
+ ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 2, 0)));
+ ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
+ CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kRemb);
+ ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 2, 0)));
+ ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
+ CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kRemb);
+}
+
+TEST_F(JsepTrackTest, VideoNegotiationOfferTransportCC) {
+ InitCodecs();
+ // enable TransportCC on the offer codecs
+ ((JsepVideoCodecDescription&)*mOffCodecs[2]).EnableTransportCC();
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ // make sure TransportCC is on offer and not on answer
+ ASSERT_NE(mOffer->ToString().find("a=rtcp-fb:120 transport-cc"),
+ std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=rtcp-fb:120 transport-cc"),
+ std::string::npos);
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ UniquePtr<JsepVideoCodecDescription> codec;
+ ASSERT_TRUE((codec = GetVideoCodec(mSendOff, 2, 0)));
+ ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
+ ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 2, 0)));
+ ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
+ ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 2, 0)));
+ ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
+ ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 2, 0)));
+ ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
+}
+
+TEST_F(JsepTrackTest, VideoNegotiationAnswerTransportCC) {
+ InitCodecs();
+ // enable TransportCC on the answer codecs
+ ((JsepVideoCodecDescription&)*mAnsCodecs[2]).EnableTransportCC();
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ // make sure TransportCC is not on offer and not on answer
+ ASSERT_EQ(mOffer->ToString().find("a=rtcp-fb:120 transport-cc"),
+ std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=rtcp-fb:120 transport-cc"),
+ std::string::npos);
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ UniquePtr<JsepVideoCodecDescription> codec;
+ ASSERT_TRUE((codec = GetVideoCodec(mSendOff, 2, 0)));
+ ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
+ ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 2, 0)));
+ ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
+ ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 2, 0)));
+ ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
+ ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 2, 0)));
+ ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
+}
+
+TEST_F(JsepTrackTest, VideoNegotiationOfferAnswerTransportCC) {
+ InitCodecs();
+ // enable TransportCC on the offer and answer codecs
+ ((JsepVideoCodecDescription&)*mOffCodecs[2]).EnableTransportCC();
+ ((JsepVideoCodecDescription&)*mAnsCodecs[2]).EnableTransportCC();
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ // make sure TransportCC is on offer and on answer
+ ASSERT_NE(mOffer->ToString().find("a=rtcp-fb:120 transport-cc"),
+ std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtcp-fb:120 transport-cc"),
+ std::string::npos);
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ UniquePtr<JsepVideoCodecDescription> codec;
+ ASSERT_TRUE((codec = GetVideoCodec(mSendOff, 2, 0)));
+ ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
+ CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kTransportCC);
+ ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 2, 0)));
+ ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
+ CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kTransportCC);
+ ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 2, 0)));
+ ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
+ CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kTransportCC);
+ ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 2, 0)));
+ ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
+ CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kTransportCC);
+}
+
+TEST_F(JsepTrackTest, AudioOffSendonlyAnsRecvonly) {
+ Init(SdpMediaSection::kAudio);
+ GetOffer().SetDirection(SdpDirectionAttribute::kSendonly);
+ GetAnswer().SetDirection(SdpDirectionAttribute::kRecvonly);
+ OfferAnswer();
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(0);
+}
+
+TEST_F(JsepTrackTest, VideoOffSendonlyAnsRecvonly) {
+ Init(SdpMediaSection::kVideo);
+ GetOffer().SetDirection(SdpDirectionAttribute::kSendonly);
+ GetAnswer().SetDirection(SdpDirectionAttribute::kRecvonly);
+ OfferAnswer();
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(0);
+}
+
+TEST_F(JsepTrackTest, AudioOffSendrecvAnsRecvonly) {
+ Init(SdpMediaSection::kAudio);
+ GetAnswer().SetDirection(SdpDirectionAttribute::kRecvonly);
+ OfferAnswer();
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(0);
+}
+
+TEST_F(JsepTrackTest, VideoOffSendrecvAnsRecvonly) {
+ Init(SdpMediaSection::kVideo);
+ GetAnswer().SetDirection(SdpDirectionAttribute::kRecvonly);
+ OfferAnswer();
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(0);
+}
+
+TEST_F(JsepTrackTest, AudioOffRecvonlyAnsSendonly) {
+ Init(SdpMediaSection::kAudio);
+ GetOffer().SetDirection(SdpDirectionAttribute::kRecvonly);
+ GetAnswer().SetDirection(SdpDirectionAttribute::kSendonly);
+ OfferAnswer();
+ CheckOffEncodingCount(0);
+ CheckAnsEncodingCount(1);
+}
+
+TEST_F(JsepTrackTest, VideoOffRecvonlyAnsSendonly) {
+ Init(SdpMediaSection::kVideo);
+ GetOffer().SetDirection(SdpDirectionAttribute::kRecvonly);
+ GetAnswer().SetDirection(SdpDirectionAttribute::kSendonly);
+ OfferAnswer();
+ CheckOffEncodingCount(0);
+ CheckAnsEncodingCount(1);
+}
+
+TEST_F(JsepTrackTest, AudioOffSendrecvAnsSendonly) {
+ Init(SdpMediaSection::kAudio);
+ GetAnswer().SetDirection(SdpDirectionAttribute::kSendonly);
+ OfferAnswer();
+ CheckOffEncodingCount(0);
+ CheckAnsEncodingCount(1);
+}
+
+TEST_F(JsepTrackTest, VideoOffSendrecvAnsSendonly) {
+ Init(SdpMediaSection::kVideo);
+ GetAnswer().SetDirection(SdpDirectionAttribute::kSendonly);
+ OfferAnswer();
+ CheckOffEncodingCount(0);
+ CheckAnsEncodingCount(1);
+}
+
+TEST_F(JsepTrackTest, DataChannelDraft05) {
+ mOffCodecs = MakeCodecs(false, false, false);
+ mAnsCodecs = MakeCodecs(false, false, false);
+ InitTracks(SdpMediaSection::kApplication);
+
+ mOffer.reset(new SipccSdp(SdpOrigin("", 0, 0, sdp::kIPv4, "")));
+ mOffer->AddMediaSection(SdpMediaSection::kApplication,
+ SdpDirectionAttribute::kSendrecv, 0,
+ SdpMediaSection::kDtlsSctp, sdp::kIPv4, "0.0.0.0");
+ mAnswer.reset(new SipccSdp(SdpOrigin("", 0, 0, sdp::kIPv4, "")));
+ mAnswer->AddMediaSection(SdpMediaSection::kApplication,
+ SdpDirectionAttribute::kSendrecv, 0,
+ SdpMediaSection::kDtlsSctp, sdp::kIPv4, "0.0.0.0");
+
+ OfferAnswer();
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(std::string::npos,
+ mOffer->ToString().find("a=sctpmap:5999 webrtc-datachannel 256"));
+ ASSERT_NE(std::string::npos,
+ mAnswer->ToString().find("a=sctpmap:5999 webrtc-datachannel 256"));
+ // Note: this is testing for a workaround, see bug 1335262 for details
+ ASSERT_NE(std::string::npos,
+ mOffer->ToString().find("a=max-message-size:499"));
+ ASSERT_NE(std::string::npos,
+ mAnswer->ToString().find("a=max-message-size:499"));
+ ASSERT_EQ(std::string::npos, mOffer->ToString().find("a=sctp-port"));
+ ASSERT_EQ(std::string::npos, mAnswer->ToString().find("a=sctp-port"));
+}
+
+TEST_F(JsepTrackTest, DataChannelDraft21) {
+ Init(SdpMediaSection::kApplication);
+ OfferAnswer();
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(std::string::npos, mOffer->ToString().find("a=sctp-port:5999"));
+ ASSERT_NE(std::string::npos, mAnswer->ToString().find("a=sctp-port:5999"));
+ ASSERT_NE(std::string::npos,
+ mOffer->ToString().find("a=max-message-size:499"));
+ ASSERT_NE(std::string::npos,
+ mAnswer->ToString().find("a=max-message-size:499"));
+ ASSERT_EQ(std::string::npos, mOffer->ToString().find("a=sctpmap"));
+ ASSERT_EQ(std::string::npos, mAnswer->ToString().find("a=sctpmap"));
+}
+
+TEST_F(JsepTrackTest, DataChannelDraft21AnswerWithDifferentPort) {
+ mOffCodecs = MakeCodecs(false, false, false);
+ mAnsCodecs = MakeCodecs(false, false, false);
+
+ mOffCodecs.pop_back();
+ mOffCodecs.emplace_back(new JsepApplicationCodecDescription(
+ "webrtc-datachannel", 256, 4555, 10544));
+
+ InitTracks(SdpMediaSection::kApplication);
+ InitSdp(SdpMediaSection::kApplication);
+
+ OfferAnswer();
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(std::string::npos, mOffer->ToString().find("a=sctp-port:4555"));
+ ASSERT_NE(std::string::npos, mAnswer->ToString().find("a=sctp-port:5999"));
+ ASSERT_NE(std::string::npos,
+ mOffer->ToString().find("a=max-message-size:10544"));
+ ASSERT_NE(std::string::npos,
+ mAnswer->ToString().find("a=max-message-size:499"));
+ ASSERT_EQ(std::string::npos, mOffer->ToString().find("a=sctpmap"));
+ ASSERT_EQ(std::string::npos, mAnswer->ToString().find("a=sctpmap"));
+}
+
+TEST_F(JsepTrackTest, SimulcastRejected) {
+ Init(SdpMediaSection::kVideo);
+ std::vector<std::string> rids;
+ rids.push_back("foo");
+ rids.push_back("bar");
+ mSendOff.SetRids(rids);
+ OfferAnswer();
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+}
+
+TEST_F(JsepTrackTest, SimulcastPrevented) {
+ Init(SdpMediaSection::kVideo);
+ std::vector<std::string> rids;
+ rids.push_back("foo");
+ rids.push_back("bar");
+ mSendAns.SetRids(rids);
+ OfferAnswer();
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+}
+
+TEST_F(JsepTrackTest, SimulcastOfferer) {
+ Init(SdpMediaSection::kVideo);
+ std::vector<std::string> rids;
+ rids.push_back("foo");
+ rids.push_back("bar");
+ mSendOff.SetRids(rids);
+ CreateOffer();
+ CreateAnswer();
+ // Add simulcast/rid to answer
+ mRecvAns.AddToMsection(rids, sdp::kRecv, mSsrcGenerator, false, &GetAnswer());
+ Negotiate();
+ ASSERT_TRUE(mSendOff.GetNegotiatedDetails());
+ ASSERT_EQ(2U, mSendOff.GetNegotiatedDetails()->GetEncodingCount());
+ ASSERT_EQ("foo", mSendOff.GetNegotiatedDetails()->GetEncoding(0).mRid);
+ ASSERT_EQ("bar", mSendOff.GetNegotiatedDetails()->GetEncoding(1).mRid);
+ ASSERT_NE(std::string::npos,
+ mOffer->ToString().find("a=simulcast:send foo;bar"));
+ ASSERT_NE(std::string::npos,
+ mAnswer->ToString().find("a=simulcast:recv foo;bar"));
+ ASSERT_NE(std::string::npos, mOffer->ToString().find("a=rid:foo send"));
+ ASSERT_NE(std::string::npos, mOffer->ToString().find("a=rid:bar send"));
+ ASSERT_NE(std::string::npos, mAnswer->ToString().find("a=rid:foo recv"));
+ ASSERT_NE(std::string::npos, mAnswer->ToString().find("a=rid:bar recv"));
+}
+
+TEST_F(JsepTrackTest, SimulcastOffererWithRtx) {
+ Init(SdpMediaSection::kVideo);
+ std::vector<std::string> rids;
+ rids.push_back("foo");
+ rids.push_back("bar");
+ rids.push_back("pop");
+ mSendOff.SetRids(rids);
+ mSendOff.AddToMsection(rids, sdp::kSend, mSsrcGenerator, true, &GetOffer());
+ mRecvOff.AddToMsection(rids, sdp::kSend, mSsrcGenerator, true, &GetOffer());
+ CreateAnswer();
+ // Add simulcast/rid to answer
+ mRecvAns.AddToMsection(rids, sdp::kRecv, mSsrcGenerator, false, &GetAnswer());
+ Negotiate();
+
+ ASSERT_EQ(3U, mSendOff.GetSsrcs().size());
+ const auto posSsrc0 =
+ mOffer->ToString().find(std::to_string(mSendOff.GetSsrcs()[0]));
+ const auto posSsrc1 =
+ mOffer->ToString().find(std::to_string(mSendOff.GetSsrcs()[1]));
+ const auto posSsrc2 =
+ mOffer->ToString().find(std::to_string(mSendOff.GetSsrcs()[2]));
+ ASSERT_NE(std::string::npos, posSsrc0);
+ ASSERT_NE(std::string::npos, posSsrc1);
+ ASSERT_NE(std::string::npos, posSsrc2);
+ ASSERT_GT(posSsrc1, posSsrc0);
+ ASSERT_GT(posSsrc2, posSsrc0);
+ ASSERT_GT(posSsrc2, posSsrc1);
+}
+
+TEST_F(JsepTrackTest, SimulcastAnswerer) {
+ Init(SdpMediaSection::kVideo);
+ std::vector<std::string> rids;
+ rids.push_back("foo");
+ rids.push_back("bar");
+ mSendAns.SetRids(rids);
+ CreateOffer();
+ // Add simulcast/rid to offer
+ mRecvOff.AddToMsection(rids, sdp::kRecv, mSsrcGenerator, false, &GetOffer());
+ CreateAnswer();
+ Negotiate();
+ ASSERT_TRUE(mSendAns.GetNegotiatedDetails());
+ ASSERT_EQ(2U, mSendAns.GetNegotiatedDetails()->GetEncodingCount());
+ ASSERT_EQ("foo", mSendAns.GetNegotiatedDetails()->GetEncoding(0).mRid);
+ ASSERT_EQ("bar", mSendAns.GetNegotiatedDetails()->GetEncoding(1).mRid);
+ ASSERT_NE(std::string::npos,
+ mOffer->ToString().find("a=simulcast:recv foo;bar"));
+ ASSERT_NE(std::string::npos,
+ mAnswer->ToString().find("a=simulcast:send foo;bar"));
+ ASSERT_NE(std::string::npos, mOffer->ToString().find("a=rid:foo recv"));
+ ASSERT_NE(std::string::npos, mOffer->ToString().find("a=rid:bar recv"));
+ ASSERT_NE(std::string::npos, mAnswer->ToString().find("a=rid:foo send"));
+ ASSERT_NE(std::string::npos, mAnswer->ToString().find("a=rid:bar send"));
+}
+
+#define VERIFY_OPUS_MAX_PLAYBACK_RATE(track, expectedRate) \
+ { \
+ JsepTrack& copy(track); \
+ ASSERT_TRUE(copy.GetNegotiatedDetails()); \
+ ASSERT_TRUE(copy.GetNegotiatedDetails()->GetEncodingCount()); \
+ for (const auto& codec : \
+ copy.GetNegotiatedDetails()->GetEncoding(0).GetCodecs()) { \
+ if (codec->mName == "opus") { \
+ JsepAudioCodecDescription& audioCodec = \
+ static_cast<JsepAudioCodecDescription&>(*codec); \
+ ASSERT_EQ((expectedRate), audioCodec.mMaxPlaybackRate); \
+ } \
+ }; \
+ }
+
+#define VERIFY_OPUS_FORCE_MONO(track, expected) \
+ { \
+ JsepTrack& copy(track); \
+ ASSERT_TRUE(copy.GetNegotiatedDetails()); \
+ ASSERT_TRUE(copy.GetNegotiatedDetails()->GetEncodingCount()); \
+ for (const auto& codec : \
+ copy.GetNegotiatedDetails()->GetEncoding(0).GetCodecs()) { \
+ if (codec->mName == "opus") { \
+ JsepAudioCodecDescription& audioCodec = \
+ static_cast<JsepAudioCodecDescription&>(*codec); \
+ /* gtest has some compiler warnings when using ASSERT_EQ with \
+ * booleans. */ \
+ ASSERT_EQ((int)(expected), (int)audioCodec.mForceMono); \
+ } \
+ }; \
+ }
+
+TEST_F(JsepTrackTest, DefaultOpusParameters) {
+ Init(SdpMediaSection::kAudio);
+ OfferAnswer();
+
+ VERIFY_OPUS_MAX_PLAYBACK_RATE(
+ mSendOff, SdpFmtpAttributeList::OpusParameters::kDefaultMaxPlaybackRate);
+ VERIFY_OPUS_MAX_PLAYBACK_RATE(
+ mSendAns, SdpFmtpAttributeList::OpusParameters::kDefaultMaxPlaybackRate);
+ VERIFY_OPUS_MAX_PLAYBACK_RATE(mRecvOff, 0U);
+ VERIFY_OPUS_FORCE_MONO(mRecvOff, false);
+ VERIFY_OPUS_MAX_PLAYBACK_RATE(mRecvAns, 0U);
+ VERIFY_OPUS_FORCE_MONO(mRecvAns, false);
+}
+
+TEST_F(JsepTrackTest, NonDefaultOpusParameters) {
+ InitCodecs();
+ for (auto& codec : mAnsCodecs) {
+ if (codec->mName == "opus") {
+ JsepAudioCodecDescription* audioCodec =
+ static_cast<JsepAudioCodecDescription*>(codec.get());
+ audioCodec->mMaxPlaybackRate = 16000;
+ audioCodec->mForceMono = true;
+ }
+ }
+ InitTracks(SdpMediaSection::kAudio);
+ InitSdp(SdpMediaSection::kAudio);
+ OfferAnswer();
+
+ VERIFY_OPUS_MAX_PLAYBACK_RATE(mSendOff, 16000U);
+ VERIFY_OPUS_FORCE_MONO(mSendOff, true);
+ VERIFY_OPUS_MAX_PLAYBACK_RATE(
+ mSendAns, SdpFmtpAttributeList::OpusParameters::kDefaultMaxPlaybackRate);
+ VERIFY_OPUS_FORCE_MONO(mSendAns, false);
+ VERIFY_OPUS_MAX_PLAYBACK_RATE(mRecvOff, 0U);
+ VERIFY_OPUS_FORCE_MONO(mRecvOff, false);
+ VERIFY_OPUS_MAX_PLAYBACK_RATE(mRecvAns, 16000U);
+ VERIFY_OPUS_FORCE_MONO(mRecvAns, true);
+}
+
+TEST_F(JsepTrackTest, RtcpFbWithPayloadTypeAsymmetry) {
+ std::vector<std::string> expectedAckFbTypes;
+ std::vector<std::string> expectedNackFbTypes{"", "pli"};
+ std::vector<std::string> expectedCcmFbTypes{"fir"};
+ std::vector<SdpRtcpFbAttributeList::Feedback> expectedOtherFbTypes{
+ {"", SdpRtcpFbAttributeList::kRemb, "", ""},
+ {"", SdpRtcpFbAttributeList::kTransportCC, "", ""}};
+
+ InitCodecs();
+
+ // On offerer, configure to support remb and transport-cc on video codecs
+ for (auto& codec : mOffCodecs) {
+ if (codec->Type() == SdpMediaSection::kVideo) {
+ auto& videoCodec = static_cast<JsepVideoCodecDescription&>(*codec);
+ videoCodec.EnableRemb();
+ videoCodec.EnableTransportCC();
+ }
+ }
+
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+
+ CreateOffer();
+ // We do not bother trying to bamboozle the answerer into doing asymmetric
+ // payload types, we just use a raw SDP.
+ const std::string answer =
+ "v=0\r\n"
+ "o=- 0 0 IN IP4 127.0.0.1\r\n"
+ "s=-\r\n"
+ "t=0 0\r\n"
+ "a=msid-semantic:WMS *\r\n"
+ "m=video 0 UDP/TLS/RTP/SAVPF 136\r\n"
+ "c=IN IP4 0.0.0.0\r\n"
+ "a=sendrecv\r\n"
+ "a=fmtp:136 "
+ "profile-level-id=42e00d;level-asymmetry-allowed=1;packetization-mode="
+ "1\r\n"
+ "a=msid:stream_id\r\n"
+ "a=rtcp-fb:136 nack\r\n"
+ "a=rtcp-fb:136 nack pli\r\n"
+ "a=rtcp-fb:136 ccm fir\r\n"
+ "a=rtcp-fb:136 goog-remb\r\n"
+ "a=rtcp-fb:136 transport-cc\r\n"
+ "a=rtpmap:136 H264/90000\r\n"
+ "a=ssrc:2025549043 cname:\r\n";
+
+ UniquePtr<SdpParser> parser(new SipccSdpParser);
+ mAnswer = std::move(parser->Parse(answer)->Sdp());
+ ASSERT_TRUE(mAnswer);
+
+ mRecvOff.RecvTrackSetRemote(*mAnswer, GetAnswer());
+ mRecvOff.Negotiate(GetAnswer(), GetAnswer(), GetOffer());
+ mSendOff.Negotiate(GetAnswer(), GetAnswer(), GetOffer());
+
+ ASSERT_TRUE(mSendOff.GetNegotiatedDetails());
+ ASSERT_TRUE(mRecvOff.GetNegotiatedDetails());
+
+ UniquePtr<JsepVideoCodecDescription> codec;
+ ASSERT_TRUE((codec = GetVideoCodec(mSendOff)));
+ ASSERT_EQ("136", codec->mDefaultPt)
+ << "Offerer should have seen answer asymmetry!";
+ ASSERT_TRUE((codec = GetVideoCodec(mRecvOff)));
+ ASSERT_EQ("126", codec->mDefaultPt);
+ ASSERT_EQ(expectedAckFbTypes, codec->mAckFbTypes);
+ ASSERT_EQ(expectedNackFbTypes, codec->mNackFbTypes);
+ ASSERT_EQ(expectedCcmFbTypes, codec->mCcmFbTypes);
+ ASSERT_EQ(expectedOtherFbTypes, codec->mOtherFbTypes);
+}
+
+TEST_F(JsepTrackTest, AudioSdpFmtpLine) {
+ mOffCodecs = MakeCodecs(true, true, true);
+ mAnsCodecs = MakeCodecs(true, true, true);
+ InitTracks(SdpMediaSection::kAudio);
+ InitSdp(SdpMediaSection::kAudio);
+ OfferAnswer();
+
+ // SanityCheck checks that the sdpFmtpLine for a local codec matches that of
+ // the corresponding remote codec.
+ UniquePtr<JsepAudioCodecDescription> codec;
+ EXPECT_TRUE((codec = GetAudioCodec(mSendOff, 3, 0)));
+ EXPECT_EQ("opus", codec->mName);
+ EXPECT_EQ("maxplaybackrate=48000;stereo=1;useinbandfec=0",
+ codec->mSdpFmtpLine.valueOr("nothing"));
+ EXPECT_TRUE((codec = GetAudioCodec(mSendAns, 3, 0)));
+ EXPECT_EQ("opus", codec->mName);
+ EXPECT_EQ("maxplaybackrate=48000;stereo=1;useinbandfec=0",
+ codec->mSdpFmtpLine.valueOr("nothing"));
+
+ EXPECT_TRUE((codec = GetAudioCodec(mSendOff, 3, 1)));
+ EXPECT_EQ("G722", codec->mName);
+ EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing"));
+ EXPECT_TRUE((codec = GetAudioCodec(mSendAns, 3, 1)));
+ EXPECT_EQ("G722", codec->mName);
+ EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing"));
+
+ EXPECT_TRUE((codec = GetAudioCodec(mSendOff, 3, 2)));
+ EXPECT_EQ("telephone-event", codec->mName);
+ EXPECT_EQ("0-15", codec->mSdpFmtpLine.valueOr("nothing"));
+ EXPECT_TRUE((codec = GetAudioCodec(mSendAns, 3, 2)));
+ EXPECT_EQ("telephone-event", codec->mName);
+ EXPECT_EQ("0-15", codec->mSdpFmtpLine.valueOr("nothing"));
+}
+
+TEST_F(JsepTrackTest, NonDefaultAudioSdpFmtpLine) {
+ mOffCodecs = MakeCodecs(true, true, true);
+ mAnsCodecs = MakeCodecs(true, true, true);
+
+ for (auto& codec : mOffCodecs) {
+ if (codec->mName == "opus") {
+ auto* audio = static_cast<JsepAudioCodecDescription*>(codec.get());
+ audio->mForceMono = true;
+ audio->mMaxPlaybackRate = 32000;
+ }
+ }
+
+ for (auto& codec : mAnsCodecs) {
+ if (codec->mName == "opus") {
+ auto* audio = static_cast<JsepAudioCodecDescription*>(codec.get());
+ audio->mFECEnabled = true;
+ audio->mCbrEnabled = true;
+ audio->mDTXEnabled = true;
+ audio->mFrameSizeMs = 10;
+ audio->mMinFrameSizeMs = 5;
+ audio->mMaxFrameSizeMs = 20;
+ }
+ }
+
+ InitTracks(SdpMediaSection::kAudio);
+ InitSdp(SdpMediaSection::kAudio);
+
+ {
+ // telephone-event doesn't store any params in JsepAudioCodecDescription.
+ // Set them directly in the offer sdp instead.
+ auto params = MakeUnique<SdpFmtpAttributeList::TelephoneEventParameters>();
+ params->dtmfTones = "2-9";
+ GetOffer().SetFmtp({"101", *params});
+ }
+
+ {
+ // telephone-event doesn't store any params in JsepAudioCodecDescription.
+ // Set them directly in the answer sdp instead.
+ auto params = MakeUnique<SdpFmtpAttributeList::TelephoneEventParameters>();
+ params->dtmfTones = "0-3,10";
+ GetAnswer().SetFmtp({"101", *params});
+ }
+
+ OfferAnswer();
+
+ // SanityCheck checks that the sdpFmtpLine for a local codec matches that of
+ // the corresponding remote codec.
+ UniquePtr<JsepAudioCodecDescription> codec;
+ EXPECT_TRUE((codec = GetAudioCodec(mSendOff, 3, 0)));
+ EXPECT_EQ("opus", codec->mName);
+ EXPECT_EQ(
+ "maxplaybackrate=48000;stereo=1;useinbandfec=1;usedtx=1;ptime=10;"
+ "minptime=5;maxptime=20;cbr=1",
+ codec->mSdpFmtpLine.valueOr("nothing"));
+ EXPECT_TRUE((codec = GetAudioCodec(mSendAns, 3, 0)));
+ EXPECT_EQ("opus", codec->mName);
+ EXPECT_EQ("maxplaybackrate=32000;stereo=0;useinbandfec=0",
+ codec->mSdpFmtpLine.valueOr("nothing"));
+
+ EXPECT_TRUE((codec = GetAudioCodec(mSendOff, 3, 1)));
+ EXPECT_EQ("G722", codec->mName);
+ EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing"));
+ EXPECT_TRUE((codec = GetAudioCodec(mSendAns, 3, 1)));
+ EXPECT_EQ("G722", codec->mName);
+ EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing"));
+
+ EXPECT_TRUE((codec = GetAudioCodec(mSendOff, 3, 2)));
+ EXPECT_EQ("telephone-event", codec->mName);
+ EXPECT_EQ("0-3,10", codec->mSdpFmtpLine.valueOr("nothing"));
+ EXPECT_TRUE((codec = GetAudioCodec(mSendAns, 3, 2)));
+ EXPECT_EQ("telephone-event", codec->mName);
+ EXPECT_EQ("2-9", codec->mSdpFmtpLine.valueOr("nothing"));
+}
+
+TEST_F(JsepTrackTest, VideoSdpFmtpLine) {
+ mOffCodecs = MakeCodecs(true, true, true);
+ mAnsCodecs = MakeCodecs(true, true, true);
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ // SanityCheck checks that the sdpFmtpLine for a local codec matches that of
+ // the corresponding remote codec.
+ UniquePtr<JsepVideoCodecDescription> codec;
+ EXPECT_TRUE((codec = GetVideoCodec(mSendOff, 4, 0)));
+ EXPECT_EQ("red", codec->mName);
+ EXPECT_EQ("120/126/123", codec->mSdpFmtpLine.valueOr("nothing"));
+ EXPECT_TRUE((codec = GetVideoCodec(mSendAns, 4, 0)));
+ EXPECT_EQ("red", codec->mName);
+ EXPECT_EQ("120/126/123", codec->mSdpFmtpLine.valueOr("nothing"));
+
+ EXPECT_TRUE((codec = GetVideoCodec(mSendOff, 4, 1)));
+ EXPECT_EQ("VP8", codec->mName);
+ EXPECT_EQ("max-fs=12288;max-fr=60", codec->mSdpFmtpLine.valueOr("nothing"));
+ EXPECT_TRUE((codec = GetVideoCodec(mSendAns, 4, 1)));
+ EXPECT_EQ("VP8", codec->mName);
+ EXPECT_EQ("max-fs=12288;max-fr=60", codec->mSdpFmtpLine.valueOr("nothing"));
+
+ EXPECT_TRUE((codec = GetVideoCodec(mSendOff, 4, 2)));
+ EXPECT_EQ("H264", codec->mName);
+ EXPECT_EQ(
+ "profile-level-id=42e00d;level-asymmetry-allowed=1;packetization-mode=1",
+ codec->mSdpFmtpLine.valueOr("nothing"));
+ EXPECT_TRUE((codec = GetVideoCodec(mSendAns, 4, 2)));
+ EXPECT_EQ("H264", codec->mName);
+ EXPECT_EQ(
+ "profile-level-id=42e00d;level-asymmetry-allowed=1;packetization-mode=1",
+ codec->mSdpFmtpLine.valueOr("nothing"));
+
+ EXPECT_TRUE((codec = GetVideoCodec(mSendOff, 4, 3)));
+ EXPECT_EQ("ulpfec", codec->mName);
+ EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing"));
+ EXPECT_TRUE((codec = GetVideoCodec(mSendAns, 4, 3)));
+ EXPECT_EQ("ulpfec", codec->mName);
+ EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing"));
+}
+
+TEST_F(JsepTrackTest, NonDefaultVideoSdpFmtpLine) {
+ mOffCodecs = MakeCodecs(true, true, true);
+ mAnsCodecs = MakeCodecs(true, true, true);
+
+ for (auto& codec : mOffCodecs) {
+ if (codec->mName == "VP8" || codec->mName == "H264") {
+ auto* video = static_cast<JsepVideoCodecDescription*>(codec.get());
+ video->mConstraints.maxFs = 1200;
+ if (codec->mName == "VP8") {
+ video->mConstraints.maxFps = Some(15);
+ } else {
+ video->mConstraints.maxDpb = 6400;
+ video->mConstraints.maxBr = 1000;
+ JsepVideoCodecDescription::SetSaneH264Level(0x1F0,
+ &video->mProfileLevelId);
+ }
+ }
+ }
+
+ for (auto& codec : mAnsCodecs) {
+ if (codec->mName == "VP8" || codec->mName == "H264") {
+ auto* video = static_cast<JsepVideoCodecDescription*>(codec.get());
+ video->mConstraints.maxFs = 32400;
+ if (codec->mName == "VP8") {
+ video->mConstraints.maxFps = Some(60);
+ } else {
+ video->mConstraints.maxMbps = 1944000;
+ video->mConstraints.maxCpb = 800000;
+ video->mConstraints.maxDpb = 128000;
+ JsepVideoCodecDescription::SetSaneH264Level(0xAB,
+ &video->mProfileLevelId);
+ video->mPacketizationMode = 1;
+ }
+ }
+ }
+
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ // SanityCheck checks that the sdpFmtpLine for a local codec matches that of
+ // the corresponding remote codec.
+ UniquePtr<JsepVideoCodecDescription> codec;
+ EXPECT_TRUE((codec = GetVideoCodec(mSendOff, 4, 0)));
+ EXPECT_EQ("red", codec->mName);
+ EXPECT_EQ("120/126/123", codec->mSdpFmtpLine.valueOr("nothing"));
+ EXPECT_TRUE((codec = GetVideoCodec(mSendAns, 4, 0)));
+ EXPECT_EQ("red", codec->mName);
+ EXPECT_EQ("120/126/123", codec->mSdpFmtpLine.valueOr("nothing"));
+
+ EXPECT_TRUE((codec = GetVideoCodec(mSendOff, 4, 1)));
+ EXPECT_EQ("VP8", codec->mName);
+ EXPECT_EQ("max-fs=32400;max-fr=60", codec->mSdpFmtpLine.valueOr("nothing"));
+ EXPECT_TRUE((codec = GetVideoCodec(mSendAns, 4, 1)));
+ EXPECT_EQ("VP8", codec->mName);
+ EXPECT_EQ("max-fs=1200;max-fr=15", codec->mSdpFmtpLine.valueOr("nothing"));
+
+ EXPECT_TRUE((codec = GetVideoCodec(mSendOff, 4, 2)));
+ EXPECT_EQ("H264", codec->mName);
+ EXPECT_EQ(
+ "profile-level-id=42f00b;level-asymmetry-allowed=1;packetization-mode=1;"
+ "max-mbps=1944000;max-fs=32400;max-cpb=800000;max-dpb=128000",
+ codec->mSdpFmtpLine.valueOr("nothing"));
+ EXPECT_TRUE((codec = GetVideoCodec(mSendAns, 4, 2)));
+ EXPECT_EQ("H264", codec->mName);
+ EXPECT_EQ(
+ "profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1;"
+ "max-fs=1200;max-dpb=6400;max-br=1000",
+ codec->mSdpFmtpLine.valueOr("nothing"));
+
+ EXPECT_TRUE((codec = GetVideoCodec(mSendOff, 4, 3)));
+ EXPECT_EQ("ulpfec", codec->mName);
+ EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing"));
+ EXPECT_TRUE((codec = GetVideoCodec(mSendAns, 4, 3)));
+ EXPECT_EQ("ulpfec", codec->mName);
+ EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing"));
+}
+
+} // namespace mozilla
diff --git a/media/webrtc/signaling/gtest/mediapipeline_unittest.cpp b/media/webrtc/signaling/gtest/mediapipeline_unittest.cpp
new file mode 100644
index 0000000000..2ee6e96ef4
--- /dev/null
+++ b/media/webrtc/signaling/gtest/mediapipeline_unittest.cpp
@@ -0,0 +1,697 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+#include "logging.h"
+#include "nss.h"
+#include "ssl.h"
+
+#include "api/audio_codecs/builtin_audio_decoder_factory.h"
+#include "api/scoped_refptr.h"
+#include "AudioSegment.h"
+#include "Canonicals.h"
+#include "modules/audio_device/include/fake_audio_device.h"
+#include "modules/audio_mixer/audio_mixer_impl.h"
+#include "modules/audio_processing/include/audio_processing.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "MediaConduitInterface.h"
+#include "MediaPipeline.h"
+#include "MediaPipelineFilter.h"
+#include "MediaTrackGraph.h"
+#include "MediaTrackListener.h"
+#include "TaskQueueWrapper.h"
+#include "mtransport_test_utils.h"
+#include "SharedBuffer.h"
+#include "MediaTransportHandler.h"
+#include "WebrtcCallWrapper.h"
+#include "PeerConnectionCtx.h"
+#include "WaitFor.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+MOZ_MTLOG_MODULE("transportbridge")
+
+static MtransportTestUtils* test_utils;
+
+namespace {
+class MainAsCurrent : public TaskQueueWrapper<DeletionPolicy::NonBlocking> {
+ public:
+ MainAsCurrent()
+ : TaskQueueWrapper(
+ TaskQueue::Create(do_AddRef(GetMainThreadSerialEventTarget()),
+ "MainAsCurrentTaskQueue"),
+ "MainAsCurrent"_ns),
+ mSetter(this) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ }
+
+ ~MainAsCurrent() = default;
+
+ private:
+ CurrentTaskQueueSetter mSetter;
+};
+
+class FakeAudioTrack : public ProcessedMediaTrack {
+ public:
+ FakeAudioTrack()
+ : ProcessedMediaTrack(44100, MediaSegment::AUDIO, nullptr),
+ mMutex("Fake AudioTrack") {
+ NS_NewTimerWithFuncCallback(
+ getter_AddRefs(mTimer), FakeAudioTrackGenerateData, this, 20,
+ nsITimer::TYPE_REPEATING_SLACK,
+ "FakeAudioTrack::FakeAudioTrackGenerateData", test_utils->sts_target());
+ }
+
+ void Destroy() override {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(!mMainThreadDestroyed);
+ mMainThreadDestroyed = true;
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ void QueueSetAutoend(bool) override {}
+
+ void AddListener(MediaTrackListener* aListener) override {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(!mListener);
+ mListener = aListener;
+ }
+
+ RefPtr<GenericPromise> RemoveListener(
+ MediaTrackListener* aListener) override {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mListener == aListener);
+ mListener = nullptr;
+ return GenericPromise::CreateAndResolve(true, __func__);
+ }
+
+ void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) override {}
+
+ uint32_t NumberOfChannels() const override { return NUM_CHANNELS; }
+
+ private:
+ Mutex mMutex MOZ_UNANNOTATED;
+ MediaTrackListener* mListener = nullptr;
+ nsCOMPtr<nsITimer> mTimer;
+ int mCount = 0;
+
+ static const int AUDIO_BUFFER_SIZE = 1600;
+ static const int NUM_CHANNELS = 2;
+ static void FakeAudioTrackGenerateData(nsITimer* timer, void* closure) {
+ auto t = static_cast<FakeAudioTrack*>(closure);
+ MutexAutoLock lock(t->mMutex);
+ if (t->mMainThreadDestroyed) {
+ return;
+ }
+ CheckedInt<size_t> bufferSize(sizeof(int16_t));
+ bufferSize *= NUM_CHANNELS;
+ bufferSize *= AUDIO_BUFFER_SIZE;
+ RefPtr<SharedBuffer> samples = SharedBuffer::Create(bufferSize);
+ int16_t* data = reinterpret_cast<int16_t*>(samples->Data());
+ for (int i = 0; i < (AUDIO_BUFFER_SIZE * NUM_CHANNELS); i++) {
+ // saw tooth audio sample
+ data[i] = ((t->mCount % 8) * 4000) - (7 * 4000) / 2;
+ t->mCount++;
+ }
+
+ AudioSegment segment;
+ AutoTArray<const int16_t*, 1> channels;
+ channels.AppendElement(data);
+ segment.AppendFrames(samples.forget(), channels, AUDIO_BUFFER_SIZE,
+ PRINCIPAL_HANDLE_NONE);
+
+ if (t->mListener) {
+ t->mListener->NotifyQueuedChanges(nullptr, 0, segment);
+ }
+ }
+};
+
+template <typename Function>
+void RunOnSts(Function&& aFunction) {
+ MOZ_ALWAYS_SUCCEEDS(test_utils->SyncDispatchToSTS(
+ NS_NewRunnableFunction(__func__, [&] { aFunction(); })));
+}
+
+class LoopbackTransport : public MediaTransportHandler {
+ public:
+ LoopbackTransport() : MediaTransportHandler(nullptr) {
+ RunOnSts([&] {
+ SetState("mux", TransportLayer::TS_INIT);
+ SetRtcpState("mux", TransportLayer::TS_INIT);
+ SetState("non-mux", TransportLayer::TS_INIT);
+ SetRtcpState("non-mux", TransportLayer::TS_INIT);
+ });
+ }
+
+ static void InitAndConnect(LoopbackTransport& client,
+ LoopbackTransport& server) {
+ client.Connect(&server);
+ server.Connect(&client);
+ }
+
+ void Connect(LoopbackTransport* peer) { peer_ = peer; }
+
+ void Shutdown() { peer_ = nullptr; }
+
+ RefPtr<IceLogPromise> GetIceLog(const nsCString& aPattern) override {
+ return nullptr;
+ }
+
+ void ClearIceLog() override {}
+ void EnterPrivateMode() override {}
+ void ExitPrivateMode() override {}
+
+ void CreateIceCtx(const std::string& aName) override {}
+
+ nsresult SetIceConfig(const nsTArray<dom::RTCIceServer>& aIceServers,
+ dom::RTCIceTransportPolicy aIcePolicy) override {
+ return NS_OK;
+ }
+
+ void Destroy() override {}
+
+ // We will probably be able to move the proxy lookup stuff into
+ // this class once we move mtransport to its own process.
+ void SetProxyConfig(NrSocketProxyConfig&& aProxyConfig) override {}
+
+ void EnsureProvisionalTransport(const std::string& aTransportId,
+ const std::string& aLocalUfrag,
+ const std::string& aLocalPwd,
+ int aComponentCount) override {}
+
+ void SetTargetForDefaultLocalAddressLookup(const std::string& aTargetIp,
+ uint16_t aTargetPort) override {}
+
+ // We set default-route-only as late as possible because it depends on what
+ // capture permissions have been granted on the window, which could easily
+ // change between Init (ie; when the PC is created) and StartIceGathering
+ // (ie; when we set the local description).
+ void StartIceGathering(bool aDefaultRouteOnly, bool aObfuscateAddresses,
+ // TODO: It probably makes sense to look
+ // this up internally
+ const nsTArray<NrIceStunAddr>& aStunAddrs) override {}
+
+ void ActivateTransport(
+ const std::string& aTransportId, const std::string& aLocalUfrag,
+ const std::string& aLocalPwd, size_t aComponentCount,
+ const std::string& aUfrag, const std::string& aPassword,
+ const nsTArray<uint8_t>& aKeyDer, const nsTArray<uint8_t>& aCertDer,
+ SSLKEAType aAuthType, bool aDtlsClient, const DtlsDigestList& aDigests,
+ bool aPrivacyRequested) override {}
+
+ void RemoveTransportsExcept(
+ const std::set<std::string>& aTransportIds) override {}
+
+ void StartIceChecks(bool aIsControlling,
+ const std::vector<std::string>& aIceOptions) override {}
+
+ void AddIceCandidate(const std::string& aTransportId,
+ const std::string& aCandidate, const std::string& aUfrag,
+ const std::string& aObfuscatedAddress) override {}
+
+ void UpdateNetworkState(bool aOnline) override {}
+
+ RefPtr<dom::RTCStatsPromise> GetIceStats(const std::string& aTransportId,
+ DOMHighResTimeStamp aNow) override {
+ return nullptr;
+ }
+
+ void SendPacket(const std::string& aTransportId,
+ MediaPacket&& aPacket) override {
+ peer_->SignalPacketReceived(aTransportId, aPacket);
+ }
+
+ void SetState(const std::string& aTransportId, TransportLayer::State aState) {
+ MediaTransportHandler::OnStateChange(aTransportId, aState);
+ }
+
+ void SetRtcpState(const std::string& aTransportId,
+ TransportLayer::State aState) {
+ MediaTransportHandler::OnRtcpStateChange(aTransportId, aState);
+ }
+
+ private:
+ RefPtr<MediaTransportHandler> peer_;
+};
+
+class TestAgent {
+ public:
+ explicit TestAgent(const RefPtr<SharedWebrtcState>& aSharedState)
+ : control_(aSharedState->mCallWorkerThread),
+ audio_config_(109, "opus", 48000, 2, false),
+ call_(WebrtcCallWrapper::Create(
+ mozilla::dom::RTCStatsTimestampMaker::Create(), nullptr,
+ aSharedState)),
+ audio_conduit_(
+ AudioSessionConduit::Create(call_, test_utils->sts_target())),
+ audio_pipeline_(),
+ transport_(new LoopbackTransport) {
+ Unused << WaitFor(InvokeAsync(call_->mCallThread, __func__, [&] {
+ audio_conduit_->InitControl(&control_);
+ return GenericPromise::CreateAndResolve(true, "TestAgent()");
+ }));
+ }
+
+ static void Connect(TestAgent* client, TestAgent* server) {
+ LoopbackTransport::InitAndConnect(*client->transport_, *server->transport_);
+ }
+
+ virtual void CreatePipeline(const std::string& aTransportId) = 0;
+
+ void SetState_s(const std::string& aTransportId,
+ TransportLayer::State aState) {
+ transport_->SetState(aTransportId, aState);
+ }
+
+ void SetRtcpState_s(const std::string& aTransportId,
+ TransportLayer::State aState) {
+ transport_->SetRtcpState(aTransportId, aState);
+ }
+
+ void UpdateTransport_s(const std::string& aTransportId,
+ UniquePtr<MediaPipelineFilter>&& aFilter) {
+ audio_pipeline_->UpdateTransport_s(aTransportId, std::move(aFilter));
+ }
+
+ void Stop() {
+ MOZ_MTLOG(ML_DEBUG, "Stopping");
+
+ control_.Update([](auto& aControl) {
+ aControl.mTransmitting = false;
+ aControl.mReceiving = false;
+ });
+ }
+
+ void Shutdown_s() { transport_->Shutdown(); }
+
+ void Shutdown() {
+ if (audio_pipeline_) {
+ audio_pipeline_->Shutdown();
+ }
+ if (audio_conduit_) {
+ Unused << WaitFor(audio_conduit_->Shutdown());
+ }
+ if (call_) {
+ call_->Destroy();
+ }
+ if (audio_track_) {
+ audio_track_->Destroy();
+ audio_track_ = nullptr;
+ }
+
+ test_utils->SyncDispatchToSTS(WrapRunnable(this, &TestAgent::Shutdown_s));
+ }
+
+ uint32_t GetRemoteSSRC() {
+ return audio_conduit_->GetRemoteSSRC().valueOr(0);
+ }
+
+ uint32_t GetLocalSSRC() {
+ std::vector<uint32_t> res;
+ res = audio_conduit_->GetLocalSSRCs();
+ return res.empty() ? 0 : res[0];
+ }
+
+ int GetAudioRtpCountSent() { return audio_pipeline_->RtpPacketsSent(); }
+
+ int GetAudioRtpCountReceived() {
+ return audio_pipeline_->RtpPacketsReceived();
+ }
+
+ int GetAudioRtcpCountSent() { return audio_pipeline_->RtcpPacketsSent(); }
+
+ int GetAudioRtcpCountReceived() {
+ return audio_pipeline_->RtcpPacketsReceived();
+ }
+
+ protected:
+ ConcreteControl control_;
+ AudioCodecConfig audio_config_;
+ RefPtr<WebrtcCallWrapper> call_;
+ RefPtr<AudioSessionConduit> audio_conduit_;
+ RefPtr<FakeAudioTrack> audio_track_;
+ // TODO(bcampen@mozilla.com): Right now this does not let us test RTCP in
+ // both directions; only the sender's RTCP is sent, but the receiver should
+ // be sending it too.
+ RefPtr<MediaPipeline> audio_pipeline_;
+ RefPtr<LoopbackTransport> transport_;
+};
+
+class TestAgentSend : public TestAgent {
+ public:
+ explicit TestAgentSend(const RefPtr<SharedWebrtcState>& aSharedState)
+ : TestAgent(aSharedState) {
+ control_.Update([&](auto& aControl) {
+ aControl.mAudioSendCodec = Some(audio_config_);
+ });
+ audio_track_ = new FakeAudioTrack();
+ }
+
+ virtual void CreatePipeline(const std::string& aTransportId) {
+ std::string test_pc;
+
+ auto audio_pipeline = MakeRefPtr<MediaPipelineTransmit>(
+ test_pc, transport_, AbstractThread::MainThread(),
+ test_utils->sts_target(), false, audio_conduit_);
+ Unused << WaitFor(InvokeAsync(call_->mCallThread, __func__, [&] {
+ audio_pipeline->InitControl(&control_);
+ return GenericPromise::CreateAndResolve(true, __func__);
+ }));
+
+ audio_pipeline->SetSendTrackOverride(audio_track_);
+ control_.Update([](auto& aControl) { aControl.mTransmitting = true; });
+ audio_pipeline->UpdateTransport_m(aTransportId, nullptr);
+ audio_pipeline_ = audio_pipeline;
+ }
+};
+
+class TestAgentReceive : public TestAgent {
+ public:
+ explicit TestAgentReceive(const RefPtr<SharedWebrtcState>& aSharedState)
+ : TestAgent(aSharedState) {
+ control_.Update([&](auto& aControl) {
+ std::vector<AudioCodecConfig> codecs;
+ codecs.push_back(audio_config_);
+ aControl.mAudioRecvCodecs = codecs;
+ });
+ }
+
+ virtual void CreatePipeline(const std::string& aTransportId) {
+ std::string test_pc;
+
+ auto audio_pipeline = MakeRefPtr<MediaPipelineReceiveAudio>(
+ test_pc, transport_, AbstractThread::MainThread(),
+ test_utils->sts_target(),
+ static_cast<AudioSessionConduit*>(audio_conduit_.get()), nullptr,
+ TrackingId(), PRINCIPAL_HANDLE_NONE, PrincipalPrivacy::NonPrivate);
+ Unused << WaitFor(InvokeAsync(call_->mCallThread, __func__, [&] {
+ audio_pipeline->InitControl(&control_);
+ return GenericPromise::CreateAndResolve(true, __func__);
+ }));
+
+ control_.Update([](auto& aControl) { aControl.mReceiving = true; });
+ audio_pipeline->UpdateTransport_m(aTransportId, std::move(bundle_filter_));
+ audio_pipeline_ = audio_pipeline;
+ }
+
+ void SetBundleFilter(UniquePtr<MediaPipelineFilter>&& filter) {
+ bundle_filter_ = std::move(filter);
+ }
+
+ void UpdateTransport_s(const std::string& aTransportId,
+ UniquePtr<MediaPipelineFilter>&& filter) {
+ audio_pipeline_->UpdateTransport_s(aTransportId, std::move(filter));
+ }
+
+ private:
+ UniquePtr<MediaPipelineFilter> bundle_filter_;
+};
+
+void WaitFor(TimeDuration aDuration) {
+ bool done = false;
+ NS_DelayedDispatchToCurrentThread(
+ NS_NewRunnableFunction(__func__, [&] { done = true; }),
+ aDuration.ToMilliseconds());
+ SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
+ "WaitFor(TimeDuration aDuration)"_ns, [&] { return done; });
+}
+
+webrtc::AudioState::Config CreateAudioStateConfig() {
+ webrtc::AudioState::Config audio_state_config;
+ audio_state_config.audio_mixer = webrtc::AudioMixerImpl::Create();
+ webrtc::AudioProcessingBuilder audio_processing_builder;
+ audio_state_config.audio_processing = audio_processing_builder.Create();
+ audio_state_config.audio_device_module = new webrtc::FakeAudioDeviceModule();
+ return audio_state_config;
+}
+
+class MediaPipelineTest : public ::testing::Test {
+ public:
+ MediaPipelineTest()
+ : main_task_queue_(
+ WrapUnique<TaskQueueWrapper<DeletionPolicy::NonBlocking>>(
+ new MainAsCurrent())),
+ shared_state_(MakeAndAddRef<SharedWebrtcState>(
+ AbstractThread::MainThread(), CreateAudioStateConfig(),
+ already_AddRefed(
+ webrtc::CreateBuiltinAudioDecoderFactory().release()),
+ WrapUnique(new webrtc::NoTrialsConfig()))),
+ p1_(shared_state_),
+ p2_(shared_state_) {}
+
+ ~MediaPipelineTest() {
+ p1_.Shutdown();
+ p2_.Shutdown();
+ }
+
+ static void SetUpTestCase() {
+ test_utils = new MtransportTestUtils();
+ NSS_NoDB_Init(nullptr);
+ NSS_SetDomesticPolicy();
+ }
+
+ // Setup transport.
+ void InitTransports() {
+ test_utils->SyncDispatchToSTS(
+ WrapRunnableNM(&TestAgent::Connect, &p2_, &p1_));
+ }
+
+ // Verify RTP and RTCP
+ void TestAudioSend(bool aIsRtcpMux,
+ UniquePtr<MediaPipelineFilter>&& initialFilter = nullptr,
+ UniquePtr<MediaPipelineFilter>&& refinedFilter = nullptr,
+ unsigned int ms_until_filter_update = 500,
+ unsigned int ms_of_traffic_after_answer = 10000) {
+ bool bundle = !!(initialFilter);
+ // We do not support testing bundle without rtcp mux, since that doesn't
+ // make any sense.
+ ASSERT_FALSE(!aIsRtcpMux && bundle);
+
+ p2_.SetBundleFilter(std::move(initialFilter));
+
+ // Setup transport flows
+ InitTransports();
+
+ std::string transportId = aIsRtcpMux ? "mux" : "non-mux";
+ p1_.CreatePipeline(transportId);
+ p2_.CreatePipeline(transportId);
+
+ // Set state of transports to CONNECTING. MediaPipeline doesn't really care
+ // about this transition, but we're trying to simluate what happens in a
+ // real case.
+ RunOnSts([&] {
+ p1_.SetState_s(transportId, TransportLayer::TS_CONNECTING);
+ p1_.SetRtcpState_s(transportId, TransportLayer::TS_CONNECTING);
+ p2_.SetState_s(transportId, TransportLayer::TS_CONNECTING);
+ p2_.SetRtcpState_s(transportId, TransportLayer::TS_CONNECTING);
+ });
+
+ WaitFor(TimeDuration::FromMilliseconds(10));
+
+ // Set state of transports to OPEN (ie; connected). This should result in
+ // media flowing.
+ RunOnSts([&] {
+ p1_.SetState_s(transportId, TransportLayer::TS_OPEN);
+ p1_.SetRtcpState_s(transportId, TransportLayer::TS_OPEN);
+ p2_.SetState_s(transportId, TransportLayer::TS_OPEN);
+ p2_.SetRtcpState_s(transportId, TransportLayer::TS_OPEN);
+ });
+
+ if (bundle) {
+ WaitFor(TimeDuration::FromMilliseconds(ms_until_filter_update));
+
+ // Leaving refinedFilter not set implies we want to just update with
+ // the other side's SSRC
+ if (!refinedFilter) {
+ refinedFilter = MakeUnique<MediaPipelineFilter>();
+ // Might not be safe, strictly speaking.
+ refinedFilter->AddRemoteSSRC(p1_.GetLocalSSRC());
+ }
+
+ RunOnSts([&] {
+ p2_.UpdateTransport_s(transportId, std::move(refinedFilter));
+ });
+ }
+
+ // wait for some RTP/RTCP tx and rx to happen
+ WaitFor(TimeDuration::FromMilliseconds(ms_of_traffic_after_answer));
+
+ p1_.Stop();
+ p2_.Stop();
+
+ // wait for any packets in flight to arrive
+ WaitFor(TimeDuration::FromMilliseconds(200));
+
+ p1_.Shutdown();
+ p2_.Shutdown();
+
+ if (!bundle) {
+ // If we are filtering, allow the test-case to do this checking.
+ ASSERT_GE(p1_.GetAudioRtpCountSent(), 40);
+ ASSERT_EQ(p1_.GetAudioRtpCountReceived(), p2_.GetAudioRtpCountSent());
+ ASSERT_EQ(p1_.GetAudioRtpCountSent(), p2_.GetAudioRtpCountReceived());
+ }
+
+ // No RTCP packets should have been dropped, because we do not filter them.
+ // Calling ShutdownMedia_m on both pipelines does not stop the flow of
+ // RTCP. So, we might be off by one here.
+ ASSERT_LE(p2_.GetAudioRtcpCountReceived(), p1_.GetAudioRtcpCountSent());
+ ASSERT_GE(p2_.GetAudioRtcpCountReceived() + 1, p1_.GetAudioRtcpCountSent());
+ }
+
+ void TestAudioReceiverBundle(
+ bool bundle_accepted, UniquePtr<MediaPipelineFilter>&& initialFilter,
+ UniquePtr<MediaPipelineFilter>&& refinedFilter = nullptr,
+ unsigned int ms_until_answer = 500,
+ unsigned int ms_of_traffic_after_answer = 10000) {
+ TestAudioSend(true, std::move(initialFilter), std::move(refinedFilter),
+ ms_until_answer, ms_of_traffic_after_answer);
+ }
+
+ protected:
+ // main_task_queue_ has this type to make sure it goes through Delete() when
+ // we're destroyed.
+ UniquePtr<TaskQueueWrapper<DeletionPolicy::NonBlocking>> main_task_queue_;
+ const RefPtr<SharedWebrtcState> shared_state_;
+ TestAgentSend p1_;
+ TestAgentReceive p2_;
+};
+
+class MediaPipelineFilterTest : public ::testing::Test {
+ public:
+ bool Filter(MediaPipelineFilter& filter, uint32_t ssrc, uint8_t payload_type,
+ const Maybe<std::string>& mid = Nothing()) {
+ webrtc::RTPHeader header;
+ header.ssrc = ssrc;
+ header.payloadType = payload_type;
+ mid.apply([&](const auto& mid) { header.extension.mid = mid; });
+ return filter.Filter(header);
+ }
+};
+
+TEST_F(MediaPipelineFilterTest, TestConstruct) { MediaPipelineFilter filter; }
+
+TEST_F(MediaPipelineFilterTest, TestDefault) {
+ MediaPipelineFilter filter;
+ EXPECT_FALSE(Filter(filter, 233, 110));
+}
+
+TEST_F(MediaPipelineFilterTest, TestSSRCFilter) {
+ MediaPipelineFilter filter;
+ filter.AddRemoteSSRC(555);
+ EXPECT_TRUE(Filter(filter, 555, 110));
+ EXPECT_FALSE(Filter(filter, 556, 110));
+}
+
+#define SSRC(ssrc) \
+ ((ssrc >> 24) & 0xFF), ((ssrc >> 16) & 0xFF), ((ssrc >> 8) & 0xFF), \
+ (ssrc & 0xFF)
+#define REPORT_FRAGMENT(ssrc) \
+ SSRC(ssrc), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+
+#define RTCP_TYPEINFO(num_rrs, type, size) 0x80 + num_rrs, type, 0, size
+
+TEST_F(MediaPipelineFilterTest, TestMidFilter) {
+ MediaPipelineFilter filter;
+ const auto mid = Some(std::string("mid0"));
+ filter.SetRemoteMediaStreamId(mid);
+
+ EXPECT_FALSE(Filter(filter, 16, 110));
+ EXPECT_TRUE(Filter(filter, 16, 110, mid));
+ EXPECT_TRUE(Filter(filter, 16, 110));
+ EXPECT_FALSE(Filter(filter, 17, 110));
+
+ // The mid filter maintains a set of SSRCs. Adding a new SSRC should work
+ // and still allow previous SSRCs to work. Unrecognized SSRCs should still be
+ // filtered out.
+ EXPECT_TRUE(Filter(filter, 18, 111, mid));
+ EXPECT_TRUE(Filter(filter, 18, 111));
+ EXPECT_TRUE(Filter(filter, 16, 110));
+ EXPECT_FALSE(Filter(filter, 17, 110));
+}
+
+TEST_F(MediaPipelineFilterTest, TestPayloadTypeFilter) {
+ MediaPipelineFilter filter;
+ filter.AddUniquePT(110);
+ EXPECT_TRUE(Filter(filter, 555, 110));
+ EXPECT_FALSE(Filter(filter, 556, 111));
+}
+
+TEST_F(MediaPipelineFilterTest, TestSSRCMovedWithMid) {
+ MediaPipelineFilter filter;
+ const auto mid0 = Some(std::string("mid0"));
+ const auto mid1 = Some(std::string("mid1"));
+ filter.SetRemoteMediaStreamId(mid0);
+ ASSERT_TRUE(Filter(filter, 555, 110, mid0));
+ ASSERT_TRUE(Filter(filter, 555, 110));
+ // Present a new MID binding
+ ASSERT_FALSE(Filter(filter, 555, 110, mid1));
+ ASSERT_FALSE(Filter(filter, 555, 110));
+}
+
+TEST_F(MediaPipelineFilterTest, TestRemoteSDPNoSSRCs) {
+ // If the remote SDP doesn't have SSRCs, right now this is a no-op and
+ // there is no point of even incorporating a filter, but we make the
+ // behavior consistent to avoid confusion.
+ MediaPipelineFilter filter;
+ const auto mid = Some(std::string("mid0"));
+ filter.SetRemoteMediaStreamId(mid);
+ filter.AddUniquePT(111);
+ EXPECT_TRUE(Filter(filter, 555, 110, mid));
+ EXPECT_TRUE(Filter(filter, 555, 110));
+
+ // Update but remember binding./
+ MediaPipelineFilter filter2;
+
+ filter.Update(filter2);
+
+ // Ensure that the old SSRC still works.
+ EXPECT_TRUE(Filter(filter, 555, 110));
+
+ // Forget the previous binding
+ MediaPipelineFilter filter3;
+ filter3.SetRemoteMediaStreamId(Some(std::string("mid1")));
+ filter.Update(filter3);
+
+ ASSERT_FALSE(Filter(filter, 555, 110));
+}
+
+TEST_F(MediaPipelineTest, TestAudioSendNoMux) { TestAudioSend(false); }
+
+TEST_F(MediaPipelineTest, TestAudioSendMux) { TestAudioSend(true); }
+
+TEST_F(MediaPipelineTest, TestAudioSendBundle) {
+ auto filter = MakeUnique<MediaPipelineFilter>();
+ // These durations have to be _extremely_ long to have any assurance that
+ // some RTCP will be sent at all. This is because the first RTCP packet
+ // is sometimes sent before the transports are ready, which causes it to
+ // be dropped.
+ TestAudioReceiverBundle(
+ true, std::move(filter),
+ // We do not specify the filter for the remote description, so it will be
+ // set to something sane after a short time.
+ nullptr, 10000, 10000);
+
+ // Some packets should have been dropped, but not all
+ ASSERT_GT(p1_.GetAudioRtpCountSent(), p2_.GetAudioRtpCountReceived());
+ ASSERT_GT(p2_.GetAudioRtpCountReceived(), 40);
+ ASSERT_GT(p1_.GetAudioRtcpCountSent(), 1);
+}
+
+TEST_F(MediaPipelineTest, TestAudioSendEmptyBundleFilter) {
+ auto filter = MakeUnique<MediaPipelineFilter>();
+ auto bad_answer_filter = MakeUnique<MediaPipelineFilter>();
+ TestAudioReceiverBundle(true, std::move(filter),
+ std::move(bad_answer_filter));
+ // Filter is empty, so should drop everything.
+ ASSERT_EQ(0, p2_.GetAudioRtpCountReceived());
+}
+
+} // end namespace
diff --git a/media/webrtc/signaling/gtest/moz.build b/media/webrtc/signaling/gtest/moz.build
new file mode 100644
index 0000000000..d975e81422
--- /dev/null
+++ b/media/webrtc/signaling/gtest/moz.build
@@ -0,0 +1,54 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+include("/dom/media/webrtc/third_party_build/webrtc.mozbuild")
+
+if CONFIG["OS_TARGET"] == "WINNT":
+ DEFINES["SIP_OS_WINDOWS"] = True
+elif CONFIG["OS_TARGET"] == "Darwin":
+ DEFINES["SIP_OS_OSX"] = True
+else:
+ DEFINES["SIP_OS_LINUX"] = True
+
+# TODO: bug 1172551 - get these tests working on iOS
+# Also, these lengthy tests are prone to OOM on Windows ASan.
+if (
+ CONFIG["MOZ_WIDGET_TOOLKIT"] != "uikit"
+ and CONFIG["OS_TARGET"] != "Android"
+ and not (CONFIG["OS_TARGET"] == "WINNT" and CONFIG["MOZ_ASAN"])
+):
+ LOCAL_INCLUDES += [
+ "!/ipc/ipdl/_ipdlheaders",
+ "/dom/media",
+ "/dom/media/gtest",
+ "/dom/media/systemservices",
+ "/dom/media/webrtc",
+ "/dom/media/webrtc/common",
+ "/dom/media/webrtc/common/time_profiling",
+ "/dom/media/webrtc/jsapi",
+ "/dom/media/webrtc/libwebrtcglue",
+ "/dom/media/webrtc/transport",
+ "/dom/media/webrtc/transport/test",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/registry",
+ "/dom/media/webrtc/transportbridge",
+ "/ipc/chromium/src",
+ "/media/webrtc/",
+ "/third_party/libsrtp/src/include",
+ "/third_party/libwebrtc",
+ "/third_party/libwebrtc/third_party/abseil-cpp",
+ "/third_party/sipcc",
+ ]
+
+ SOURCES += [
+ "audioconduit_unittests.cpp",
+ "jsep_session_unittest.cpp",
+ "jsep_track_unittest.cpp",
+ "mediapipeline_unittest.cpp",
+ "MockCall.cpp",
+ "sdp_unittests.cpp",
+ "videoconduit_unittests.cpp",
+ ]
+
+ FINAL_LIBRARY = "xul-gtest"
diff --git a/media/webrtc/signaling/gtest/sdp_unittests.cpp b/media/webrtc/signaling/gtest/sdp_unittests.cpp
new file mode 100644
index 0000000000..df0b85f1dc
--- /dev/null
+++ b/media/webrtc/signaling/gtest/sdp_unittests.cpp
@@ -0,0 +1,5915 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+
+#include "nss.h"
+#include "ssl.h"
+
+#include "sdp/RsdparsaSdpParser.h"
+#include "sdp/SipccSdpParser.h"
+#include "sdp/SdpMediaSection.h"
+#include "sdp/SdpAttribute.h"
+#include "sdp/ParsingResultComparer.h"
+
+extern "C" {
+#include "sipcc_sdp.h"
+#include "sdp_private.h"
+}
+
+#ifdef CRLF
+# undef CRLF
+#endif
+#define CRLF "\r\n"
+
+#define SKIP_TEST_WITH_RUST_PARSER \
+ if (!::testing::get<1>(GetParam())) { \
+ return; \
+ }
+#define SKIP_TEST_WITH_SIPCC_PARSER \
+ if (ResultsAreFromSipcc()) { \
+ return; \
+ }
+
+// If you want to see the SDP as it is parsed
+// #define DEBUG_DISPLAY_SDP
+
+using namespace mozilla;
+
+namespace test {
+
+class SdpTest : public ::testing::Test {
+ public:
+ SdpTest() : final_level_(0), sdp_ptr_(nullptr) {}
+
+ ~SdpTest() { sdp_free_description(sdp_ptr_); }
+
+ static void SetUpTestCase() {
+ NSS_NoDB_Init(nullptr);
+ NSS_SetDomesticPolicy();
+ }
+
+ void SetUp() {
+ final_level_ = 0;
+ sdp_ptr_ = nullptr;
+ }
+
+ static void TearDownTestCase() {}
+
+ void ResetSdp() {
+ if (!sdp_ptr_) {
+ sdp_free_description(sdp_ptr_);
+ }
+
+ sdp_media_e supported_media[] = {
+ SDP_MEDIA_AUDIO, SDP_MEDIA_VIDEO, SDP_MEDIA_APPLICATION,
+ SDP_MEDIA_DATA, SDP_MEDIA_CONTROL, SDP_MEDIA_NAS_RADIUS,
+ SDP_MEDIA_NAS_TACACS, SDP_MEDIA_NAS_DIAMETER, SDP_MEDIA_NAS_L2TP,
+ SDP_MEDIA_NAS_LOGIN, SDP_MEDIA_NAS_NONE, SDP_MEDIA_IMAGE,
+ };
+
+ sdp_conf_options_t* config_p = sdp_init_config();
+ unsigned int i;
+ for (i = 0; i < sizeof(supported_media) / sizeof(sdp_media_e); i++) {
+ sdp_media_supported(config_p, supported_media[i], true);
+ }
+ sdp_nettype_supported(config_p, SDP_NT_INTERNET, true);
+ sdp_addrtype_supported(config_p, SDP_AT_IP4, true);
+ sdp_addrtype_supported(config_p, SDP_AT_IP6, true);
+ sdp_transport_supported(config_p, SDP_TRANSPORT_RTPSAVPF, true);
+ sdp_transport_supported(config_p, SDP_TRANSPORT_UDPTL, true);
+ sdp_require_session_name(config_p, false);
+
+ sdp_ptr_ = sdp_init_description(config_p);
+ if (!sdp_ptr_) {
+ sdp_free_config(config_p);
+ }
+ }
+
+ void ParseSdp(const std::string& sdp_str) {
+ const char* buf = sdp_str.data();
+ ResetSdp();
+ ASSERT_EQ(sdp_parse(sdp_ptr_, buf, sdp_str.size()), SDP_SUCCESS);
+ }
+
+ void InitLocalSdp() {
+ ResetSdp();
+ ASSERT_EQ(sdp_set_version(sdp_ptr_, 0), SDP_SUCCESS);
+ ASSERT_EQ(sdp_set_owner_username(sdp_ptr_, "-"), SDP_SUCCESS);
+ ASSERT_EQ(sdp_set_owner_sessionid(sdp_ptr_, "132954853"), SDP_SUCCESS);
+ ASSERT_EQ(sdp_set_owner_version(sdp_ptr_, "0"), SDP_SUCCESS);
+ ASSERT_EQ(sdp_set_owner_network_type(sdp_ptr_, SDP_NT_INTERNET),
+ SDP_SUCCESS);
+ ASSERT_EQ(sdp_set_owner_address_type(sdp_ptr_, SDP_AT_IP4), SDP_SUCCESS);
+ ASSERT_EQ(sdp_set_owner_address(sdp_ptr_, "198.51.100.7"), SDP_SUCCESS);
+ ASSERT_EQ(sdp_set_session_name(sdp_ptr_, "SDP Unit Test"), SDP_SUCCESS);
+ ASSERT_EQ(sdp_set_time_start(sdp_ptr_, "0"), SDP_SUCCESS);
+ ASSERT_EQ(sdp_set_time_stop(sdp_ptr_, "0"), SDP_SUCCESS);
+ }
+
+ std::string SerializeSdp() {
+ flex_string fs;
+ flex_string_init(&fs);
+ EXPECT_EQ(sdp_build(sdp_ptr_, &fs), SDP_SUCCESS);
+ std::string body(fs.buffer);
+ flex_string_free(&fs);
+ return body;
+ }
+
+ // Returns "level" for new media section
+ int AddNewMedia(sdp_media_e type) {
+ final_level_++;
+ EXPECT_EQ(sdp_insert_media_line(sdp_ptr_, final_level_), SDP_SUCCESS);
+ EXPECT_EQ(sdp_set_conn_nettype(sdp_ptr_, final_level_, SDP_NT_INTERNET),
+ SDP_SUCCESS);
+ EXPECT_EQ(sdp_set_conn_addrtype(sdp_ptr_, final_level_, SDP_AT_IP4),
+ SDP_SUCCESS);
+ EXPECT_EQ(sdp_set_conn_address(sdp_ptr_, final_level_, "198.51.100.7"),
+ SDP_SUCCESS);
+ EXPECT_EQ(sdp_set_media_type(sdp_ptr_, final_level_, SDP_MEDIA_VIDEO),
+ SDP_SUCCESS);
+ EXPECT_EQ(
+ sdp_set_media_transport(sdp_ptr_, final_level_, SDP_TRANSPORT_RTPAVP),
+ SDP_SUCCESS);
+ EXPECT_EQ(sdp_set_media_portnum(sdp_ptr_, final_level_, 12345, 0),
+ SDP_SUCCESS);
+ EXPECT_EQ(sdp_add_media_payload_type(sdp_ptr_, final_level_, 120,
+ SDP_PAYLOAD_NUMERIC),
+ SDP_SUCCESS);
+ return final_level_;
+ }
+
+ uint16_t AddNewRtcpFbAck(int level, sdp_rtcp_fb_ack_type_e type,
+ uint16_t payload = SDP_ALL_PAYLOADS) {
+ uint16_t inst_num = 0;
+ EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_RTCP_FB, &inst_num),
+ SDP_SUCCESS);
+ EXPECT_EQ(
+ sdp_attr_set_rtcp_fb_ack(sdp_ptr_, level, payload, inst_num, type),
+ SDP_SUCCESS);
+ return inst_num;
+ }
+
+ uint16_t AddNewRtcpFbNack(int level, sdp_rtcp_fb_nack_type_e type,
+ uint16_t payload = SDP_ALL_PAYLOADS) {
+ uint16_t inst_num = 0;
+ EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_RTCP_FB, &inst_num),
+ SDP_SUCCESS);
+ EXPECT_EQ(
+ sdp_attr_set_rtcp_fb_nack(sdp_ptr_, level, payload, inst_num, type),
+ SDP_SUCCESS);
+ return inst_num;
+ }
+
+ uint16_t AddNewRtcpFbTrrInt(int level, uint32_t interval,
+ uint16_t payload = SDP_ALL_PAYLOADS) {
+ uint16_t inst_num = 0;
+ EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_RTCP_FB, &inst_num),
+ SDP_SUCCESS);
+ EXPECT_EQ(sdp_attr_set_rtcp_fb_trr_int(sdp_ptr_, level, payload, inst_num,
+ interval),
+ SDP_SUCCESS);
+ return inst_num;
+ }
+
+ uint16_t AddNewRtcpFbRemb(int level, uint16_t payload = SDP_ALL_PAYLOADS) {
+ uint16_t inst_num = 0;
+ EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_RTCP_FB, &inst_num),
+ SDP_SUCCESS);
+ EXPECT_EQ(sdp_attr_set_rtcp_fb_remb(sdp_ptr_, level, payload, inst_num),
+ SDP_SUCCESS);
+ return inst_num;
+ }
+
+ uint16_t AddNewRtcpFbCcm(int level, sdp_rtcp_fb_ccm_type_e type,
+ uint16_t payload = SDP_ALL_PAYLOADS) {
+ uint16_t inst_num = 0;
+ EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_RTCP_FB, &inst_num),
+ SDP_SUCCESS);
+ EXPECT_EQ(
+ sdp_attr_set_rtcp_fb_ccm(sdp_ptr_, level, payload, inst_num, type),
+ SDP_SUCCESS);
+ return inst_num;
+ }
+ uint16_t AddNewExtMap(int level, const char* uri) {
+ uint16_t inst_num = 0;
+ EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_EXTMAP, &inst_num),
+ SDP_SUCCESS);
+ EXPECT_EQ(sdp_attr_set_extmap(sdp_ptr_, level, inst_num, uri, inst_num),
+ SDP_SUCCESS);
+ return inst_num;
+ }
+
+ uint16_t AddNewFmtpMaxFs(int level, uint32_t max_fs) {
+ uint16_t inst_num = 0;
+ EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_FMTP, &inst_num),
+ SDP_SUCCESS);
+ EXPECT_EQ(sdp_attr_set_fmtp_payload_type(sdp_ptr_, level, 0, inst_num, 120),
+ SDP_SUCCESS);
+ EXPECT_EQ(sdp_attr_set_fmtp_max_fs(sdp_ptr_, level, 0, inst_num, max_fs),
+ SDP_SUCCESS);
+ return inst_num;
+ }
+
+ uint16_t AddNewFmtpMaxFr(int level, uint32_t max_fr) {
+ uint16_t inst_num = 0;
+ EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_FMTP, &inst_num),
+ SDP_SUCCESS);
+ EXPECT_EQ(sdp_attr_set_fmtp_payload_type(sdp_ptr_, level, 0, inst_num, 120),
+ SDP_SUCCESS);
+ EXPECT_EQ(sdp_attr_set_fmtp_max_fr(sdp_ptr_, level, 0, inst_num, max_fr),
+ SDP_SUCCESS);
+ return inst_num;
+ }
+
+ uint16_t AddNewFmtpMaxFsFr(int level, uint32_t max_fs, uint32_t max_fr) {
+ uint16_t inst_num = 0;
+ EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_FMTP, &inst_num),
+ SDP_SUCCESS);
+ EXPECT_EQ(sdp_attr_set_fmtp_payload_type(sdp_ptr_, level, 0, inst_num, 120),
+ SDP_SUCCESS);
+ EXPECT_EQ(sdp_attr_set_fmtp_max_fs(sdp_ptr_, level, 0, inst_num, max_fs),
+ SDP_SUCCESS);
+ EXPECT_EQ(sdp_attr_set_fmtp_max_fr(sdp_ptr_, level, 0, inst_num, max_fr),
+ SDP_SUCCESS);
+ return inst_num;
+ }
+
+ protected:
+ int final_level_;
+ sdp_t* sdp_ptr_;
+};
+
+static const std::string kVideoSdp =
+ "v=0\r\n"
+ "o=- 4294967296 2 IN IP4 127.0.0.1\r\n"
+ "s=SIP Call\r\n"
+ "c=IN IP4 198.51.100.7\r\n"
+ "t=0 0\r\n"
+ "m=video 56436 RTP/SAVPF 120\r\n"
+ "a=rtpmap:120 VP8/90000\r\n";
+
+TEST_F(SdpTest, parseRtcpFbAckRpsi) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 ack rpsi\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ack(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_ACK_RPSI);
+}
+
+TEST_F(SdpTest, parseRtcpFbAckApp) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 ack app\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ack(sdp_ptr_, 1, 120, 1), SDP_RTCP_FB_ACK_APP);
+}
+
+TEST_F(SdpTest, parseRtcpFbAckAppFoo) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 ack app foo\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ack(sdp_ptr_, 1, 120, 1), SDP_RTCP_FB_ACK_APP);
+}
+
+TEST_F(SdpTest, parseRtcpFbAckFooBar) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 ack foo bar\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ack(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_ACK_UNKNOWN);
+}
+
+TEST_F(SdpTest, parseRtcpFbAckFooBarBaz) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 ack foo bar baz\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ack(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_ACK_UNKNOWN);
+}
+
+TEST_F(SdpTest, parseRtcpFbNack) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 nack\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_NACK_BASIC);
+}
+
+TEST_F(SdpTest, parseRtcpFbNackPli) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 nack pli\r\n");
+}
+
+TEST_F(SdpTest, parseRtcpFbNackSli) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 nack sli\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_NACK_SLI);
+}
+
+TEST_F(SdpTest, parseRtcpFbNackRpsi) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 nack rpsi\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_NACK_RPSI);
+}
+
+TEST_F(SdpTest, parseRtcpFbNackApp) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 nack app\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_NACK_APP);
+}
+
+TEST_F(SdpTest, parseRtcpFbNackAppFoo) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 nack app foo\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_NACK_APP);
+}
+
+TEST_F(SdpTest, parseRtcpFbNackAppFooBar) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 nack app foo bar\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_NACK_APP);
+}
+
+TEST_F(SdpTest, parseRtcpFbNackFooBarBaz) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 nack foo bar baz\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_NACK_UNKNOWN);
+}
+
+TEST_F(SdpTest, parseRtcpFbRemb) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 goog-remb\r\n");
+ ASSERT_EQ((bool)sdp_attr_get_rtcp_fb_remb_enabled(sdp_ptr_, 1, 120), true);
+}
+
+TEST_F(SdpTest, parseRtcpRbRembAllPt) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:* goog-remb\r\n");
+ ASSERT_EQ(
+ (bool)sdp_attr_get_rtcp_fb_remb_enabled(sdp_ptr_, 1, SDP_ALL_PAYLOADS),
+ true);
+}
+
+TEST_F(SdpTest, parseRtcpFbTrrInt0) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 trr-int 0\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_trr_int(sdp_ptr_, 1, 120, 1), 0U);
+}
+
+TEST_F(SdpTest, parseRtcpFbTrrInt123) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 trr-int 123\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_trr_int(sdp_ptr_, 1, 120, 1), 123U);
+}
+
+TEST_F(SdpTest, parseRtcpFbCcmFir) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 ccm fir\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 1), SDP_RTCP_FB_CCM_FIR);
+}
+
+TEST_F(SdpTest, parseRtcpFbCcmTmmbr) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 ccm tmmbr\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_CCM_TMMBR);
+}
+
+TEST_F(SdpTest, parseRtcpFbCcmTmmbrSmaxpr) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 ccm tmmbr smaxpr=456\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_CCM_TMMBR);
+}
+
+TEST_F(SdpTest, parseRtcpFbCcmTstr) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 ccm tstr\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_CCM_TSTR);
+}
+
+TEST_F(SdpTest, parseRtcpFbCcmVbcm) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 ccm vbcm 123 456 789\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_CCM_VBCM);
+ // We don't currently parse out VBCM submessage types, since we don't have
+ // any use for them.
+}
+
+TEST_F(SdpTest, parseRtcpFbCcmFoo) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 ccm foo\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_CCM_UNKNOWN);
+}
+
+TEST_F(SdpTest, parseRtcpFbCcmFooBarBaz) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 ccm foo bar baz\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_CCM_UNKNOWN);
+}
+
+TEST_F(SdpTest, parseRtcpFbFoo) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 foo\r\n");
+}
+
+TEST_F(SdpTest, parseRtcpFbFooBar) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 foo bar\r\n");
+}
+
+TEST_F(SdpTest, parseRtcpFbFooBarBaz) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 foo bar baz\r\n");
+}
+
+static const std::string kVideoSdpWithUnknonwBrokenFtmp =
+ "v=0\r\n"
+ "o=- 4294967296 2 IN IP4 127.0.0.1\r\n"
+ "s=SIP Call\r\n"
+ "c=IN IP4 198.51.100.7\r\n"
+ "t=0 0\r\n"
+ "m=video 56436 RTP/SAVPF 120\r\n"
+ "a=rtpmap:120 VP8/90000\r\n"
+ "a=fmtp:122 unknown=10\n"
+ "a=rtpmap:122 red/90000\r\n";
+
+TEST_F(SdpTest, parseUnknownBrokenFtmp) {
+ ParseSdp(kVideoSdpWithUnknonwBrokenFtmp);
+}
+
+TEST_F(SdpTest, parseRtcpFbKitchenSink) {
+ ParseSdp(kVideoSdp +
+ "a=rtcp-fb:120 ack rpsi\r\n"
+ "a=rtcp-fb:120 ack app\r\n"
+ "a=rtcp-fb:120 ack app foo\r\n"
+ "a=rtcp-fb:120 ack foo bar\r\n"
+ "a=rtcp-fb:120 ack foo bar baz\r\n"
+ "a=rtcp-fb:120 nack\r\n"
+ "a=rtcp-fb:120 nack pli\r\n"
+ "a=rtcp-fb:120 nack sli\r\n"
+ "a=rtcp-fb:120 nack rpsi\r\n"
+ "a=rtcp-fb:120 nack app\r\n"
+ "a=rtcp-fb:120 nack app foo\r\n"
+ "a=rtcp-fb:120 nack app foo bar\r\n"
+ "a=rtcp-fb:120 nack foo bar baz\r\n"
+ "a=rtcp-fb:120 trr-int 0\r\n"
+ "a=rtcp-fb:120 trr-int 123\r\n"
+ "a=rtcp-fb:120 goog-remb\r\n"
+ "a=rtcp-fb:120 ccm fir\r\n"
+ "a=rtcp-fb:120 ccm tmmbr\r\n"
+ "a=rtcp-fb:120 ccm tmmbr smaxpr=456\r\n"
+ "a=rtcp-fb:120 ccm tstr\r\n"
+ "a=rtcp-fb:120 ccm vbcm 123 456 789\r\n"
+ "a=rtcp-fb:120 ccm foo\r\n"
+ "a=rtcp-fb:120 ccm foo bar baz\r\n"
+ "a=rtcp-fb:120 foo\r\n"
+ "a=rtcp-fb:120 foo bar\r\n"
+ "a=rtcp-fb:120 foo bar baz\r\n");
+
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ack(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_ACK_RPSI);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ack(sdp_ptr_, 1, 120, 2), SDP_RTCP_FB_ACK_APP);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ack(sdp_ptr_, 1, 120, 3), SDP_RTCP_FB_ACK_APP);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ack(sdp_ptr_, 1, 120, 4),
+ SDP_RTCP_FB_ACK_UNKNOWN);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ack(sdp_ptr_, 1, 120, 5),
+ SDP_RTCP_FB_ACK_UNKNOWN);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ack(sdp_ptr_, 1, 120, 6),
+ SDP_RTCP_FB_ACK_NOT_FOUND);
+
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_NACK_BASIC);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 2),
+ SDP_RTCP_FB_NACK_PLI);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 3),
+ SDP_RTCP_FB_NACK_SLI);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 4),
+ SDP_RTCP_FB_NACK_RPSI);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 5),
+ SDP_RTCP_FB_NACK_APP);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 6),
+ SDP_RTCP_FB_NACK_APP);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 7),
+ SDP_RTCP_FB_NACK_APP);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 8),
+ SDP_RTCP_FB_NACK_UNKNOWN);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 9),
+ SDP_RTCP_FB_NACK_NOT_FOUND);
+
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_trr_int(sdp_ptr_, 1, 120, 1), 0U);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_trr_int(sdp_ptr_, 1, 120, 2), 123U);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_trr_int(sdp_ptr_, 1, 120, 3), 0xFFFFFFFF);
+
+ ASSERT_EQ((bool)sdp_attr_get_rtcp_fb_remb_enabled(sdp_ptr_, 1, 120), true);
+ ASSERT_EQ((bool)sdp_attr_get_rtcp_fb_remb_enabled(sdp_ptr_, 2, 120), false);
+
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 1), SDP_RTCP_FB_CCM_FIR);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 2),
+ SDP_RTCP_FB_CCM_TMMBR);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 3),
+ SDP_RTCP_FB_CCM_TMMBR);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 4),
+ SDP_RTCP_FB_CCM_TSTR);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 5),
+ SDP_RTCP_FB_CCM_VBCM);
+ // We don't currently parse out VBCM submessage types, since we don't have
+ // any use for them.
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 6),
+ SDP_RTCP_FB_CCM_UNKNOWN);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 7),
+ SDP_RTCP_FB_CCM_UNKNOWN);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 8),
+ SDP_RTCP_FB_CCM_NOT_FOUND);
+}
+
+TEST_F(SdpTest, addRtcpFbAckRpsi) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbAck(level, SDP_RTCP_FB_ACK_RPSI, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 ack rpsi\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbAckRpsiAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbAck(level, SDP_RTCP_FB_ACK_RPSI);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* ack rpsi\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbAckApp) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbAck(level, SDP_RTCP_FB_ACK_APP, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 ack app\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbAckAppAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbAck(level, SDP_RTCP_FB_ACK_APP);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* ack app\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNack) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_BASIC, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 nack\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_BASIC);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* nack\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackSli) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_SLI, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 nack sli\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackSliAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_SLI);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* nack sli\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackPli) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_PLI, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 nack pli\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackPliAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_PLI);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* nack pli\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackRpsi) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_RPSI, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 nack rpsi\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackRpsiAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_RPSI);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* nack rpsi\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackApp) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_APP, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 nack app\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackAppAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_APP);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* nack app\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackRai) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_RAI, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 nack rai\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackRaiAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_RAI);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* nack rai\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackTllei) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_TLLEI, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 nack tllei\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackTlleiAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_TLLEI);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* nack tllei\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackPslei) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_PSLEI, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 nack pslei\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackPsleiAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_PSLEI);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* nack pslei\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackEcn) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_ECN, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 nack ecn\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackEcnAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_ECN);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* nack ecn\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbRemb) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbRemb(level, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 goog-remb\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbRembAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbRemb(level);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* goog-remb\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbTrrInt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbTrrInt(level, 12345, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 trr-int 12345\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackTrrIntAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbTrrInt(level, 0);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* trr-int 0\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbCcmFir) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbCcm(level, SDP_RTCP_FB_CCM_FIR, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 ccm fir\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbCcmFirAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbCcm(level, SDP_RTCP_FB_CCM_FIR);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* ccm fir\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbCcmTmmbr) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbCcm(level, SDP_RTCP_FB_CCM_TMMBR, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 ccm tmmbr\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbCcmTmmbrAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbCcm(level, SDP_RTCP_FB_CCM_TMMBR);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* ccm tmmbr\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbCcmTstr) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbCcm(level, SDP_RTCP_FB_CCM_TSTR, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 ccm tstr\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbCcmTstrAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbCcm(level, SDP_RTCP_FB_CCM_TSTR);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* ccm tstr\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbCcmVbcm) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbCcm(level, SDP_RTCP_FB_CCM_VBCM, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 ccm vbcm\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbCcmVbcmAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbCcm(level, SDP_RTCP_FB_CCM_VBCM);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* ccm vbcm\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, parseRtcpFbAllPayloads) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:* ack rpsi\r\n");
+ for (int i = 0; i < 128; i++) {
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ack(sdp_ptr_, 1, i, 1),
+ SDP_RTCP_FB_ACK_RPSI);
+ }
+}
+TEST_F(SdpTest, addExtMap) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewExtMap(level, SDP_EXTMAP_AUDIO_LEVEL);
+ std::string body = SerializeSdp();
+ ASSERT_NE(
+ body.find("a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n"),
+ std::string::npos);
+}
+
+TEST_F(SdpTest, parseExtMap) {
+ ParseSdp(kVideoSdp +
+ "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n");
+ ASSERT_STREQ(sdp_attr_get_extmap_uri(sdp_ptr_, 1, 1), SDP_EXTMAP_AUDIO_LEVEL);
+ ASSERT_EQ(sdp_attr_get_extmap_id(sdp_ptr_, 1, 1), 1);
+}
+
+TEST_F(SdpTest, parseFmtpBitrate) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 bitrate=400\r\n");
+ ASSERT_EQ(400, sdp_attr_get_fmtp_bitrate_type(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpBitrateWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 bitrate=0\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE,
+ sdp_attr_get_fmtp_bitrate_type(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpBitrateWith32001) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 bitrate=32001\r\n");
+ ASSERT_EQ(32001, sdp_attr_get_fmtp_bitrate_type(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpBitrateWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 bitrate=4294967296\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE,
+ sdp_attr_get_fmtp_bitrate_type(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpMode) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 mode=200\r\n");
+ ASSERT_EQ(200U, sdp_attr_get_fmtp_mode_for_payload_type(sdp_ptr_, 1, 0, 120));
+}
+
+TEST_F(SdpTest, parseFmtpModeWith4294967295) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 mode=4294967295\r\n");
+ ASSERT_EQ(4294967295,
+ sdp_attr_get_fmtp_mode_for_payload_type(sdp_ptr_, 1, 0, 120));
+}
+
+TEST_F(SdpTest, parseFmtpModeWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 mode=4294967296\r\n");
+ // returns 0 if not found
+ ASSERT_EQ(0U, sdp_attr_get_fmtp_mode_for_payload_type(sdp_ptr_, 1, 0, 120));
+}
+
+TEST_F(SdpTest, parseFmtpQcif) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 qcif=20\r\n");
+ ASSERT_EQ(20, sdp_attr_get_fmtp_qcif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpQcifWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 qcif=0\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_qcif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpQcifWith33) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 qcif=33\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_qcif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCif) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 cif=11\r\n");
+ ASSERT_EQ(11, sdp_attr_get_fmtp_cif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCifWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 cif=0\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_cif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCifWith33) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 cif=33\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_cif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpMaxbr) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 maxbr=21\r\n");
+ ASSERT_EQ(21, sdp_attr_get_fmtp_maxbr(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpMaxbrWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 maxbr=0\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_maxbr(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpMaxbrWith65536) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 maxbr=65536\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_maxbr(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpSqcif) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sqcif=6\r\n");
+ ASSERT_EQ(6, sdp_attr_get_fmtp_sqcif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpSqcifWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sqcif=0\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_sqcif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpSqcifWith33) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sqcif=33\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_sqcif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCif4) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 cif4=11\r\n");
+ ASSERT_EQ(11, sdp_attr_get_fmtp_cif4(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCif4With0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 cif4=0\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_cif4(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCif4With33) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 cif4=33\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_cif4(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCif16) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 cif16=11\r\n");
+ ASSERT_EQ(11, sdp_attr_get_fmtp_cif16(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCif16With0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 cif16=0\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_cif16(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCif16With33) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 cif16=33\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_cif16(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpBpp) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 bpp=7\r\n");
+ ASSERT_EQ(7, sdp_attr_get_fmtp_bpp(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpBppWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 bpp=0\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_bpp(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpBppWith65536) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 bpp=65536\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_bpp(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpHrd) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 hrd=800\r\n");
+ ASSERT_EQ(800, sdp_attr_get_fmtp_hrd(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpHrdWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 hrd=0\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_hrd(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpHrdWith65536) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 hrd=65536\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_hrd(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpProfile) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 profile=4\r\n");
+ ASSERT_EQ(4, sdp_attr_get_fmtp_profile(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpProfileWith11) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 profile=11\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_profile(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpLevel) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 level=56\r\n");
+ ASSERT_EQ(56, sdp_attr_get_fmtp_level(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpLevelWith101) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 level=101\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_level(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpPacketizationMode) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 packetization-mode=1\r\n");
+ uint16_t packetizationMode;
+ ASSERT_EQ(SDP_SUCCESS,
+ sdp_attr_get_fmtp_pack_mode(sdp_ptr_, 1, 0, 1, &packetizationMode));
+ ASSERT_EQ(1, packetizationMode);
+}
+
+TEST_F(SdpTest, parseFmtpPacketizationModeWith3) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 packetization-mode=3\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_pack_mode(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpInterleavingDepth) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sprop-interleaving-depth=566\r\n");
+ uint16_t interleavingDepth;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_interleaving_depth(
+ sdp_ptr_, 1, 0, 1, &interleavingDepth));
+ ASSERT_EQ(566, interleavingDepth);
+}
+
+TEST_F(SdpTest, parseFmtpInterleavingDepthWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sprop-interleaving-depth=0\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_interleaving_depth(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpInterleavingDepthWith65536) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sprop-interleaving-depth=65536\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_interleaving_depth(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpDeintBuf) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sprop-deint-buf-req=4294967295\r\n");
+ uint32_t deintBuf;
+ ASSERT_EQ(SDP_SUCCESS,
+ sdp_attr_get_fmtp_deint_buf_req(sdp_ptr_, 1, 0, 1, &deintBuf));
+ ASSERT_EQ(4294967295, deintBuf);
+}
+
+TEST_F(SdpTest, parseFmtpDeintBufWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sprop-deint-buf-req=0\r\n");
+ uint32_t deintBuf;
+ ASSERT_EQ(SDP_SUCCESS,
+ sdp_attr_get_fmtp_deint_buf_req(sdp_ptr_, 1, 0, 1, &deintBuf));
+ ASSERT_EQ(0U, deintBuf);
+}
+
+TEST_F(SdpTest, parseFmtpDeintBufWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sprop-deint-buf-req=4294967296\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_deint_buf_req(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxDonDiff) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sprop-max-don-diff=5678\r\n");
+ uint32_t maxDonDiff;
+ ASSERT_EQ(SDP_SUCCESS,
+ sdp_attr_get_fmtp_max_don_diff(sdp_ptr_, 1, 0, 1, &maxDonDiff));
+ ASSERT_EQ(5678U, maxDonDiff);
+}
+
+TEST_F(SdpTest, parseFmtpMaxDonDiffWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sprop-max-don-diff=0\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_max_don_diff(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxDonDiffWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sprop-max-don-diff=4294967296\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_max_don_diff(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpInitBufTime) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sprop-init-buf-time=4294967295\r\n");
+ uint32_t initBufTime;
+ ASSERT_EQ(SDP_SUCCESS,
+ sdp_attr_get_fmtp_init_buf_time(sdp_ptr_, 1, 0, 1, &initBufTime));
+ ASSERT_EQ(4294967295, initBufTime);
+}
+
+TEST_F(SdpTest, parseFmtpInitBufTimeWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sprop-init-buf-time=0\r\n");
+ uint32_t initBufTime;
+ ASSERT_EQ(SDP_SUCCESS,
+ sdp_attr_get_fmtp_init_buf_time(sdp_ptr_, 1, 0, 1, &initBufTime));
+ ASSERT_EQ(0U, initBufTime);
+}
+
+TEST_F(SdpTest, parseFmtpInitBufTimeWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sprop-init-buf-time=4294967296\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_init_buf_time(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxMbps) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-mbps=46789\r\n");
+ uint32_t maxMpbs;
+ ASSERT_EQ(SDP_SUCCESS,
+ sdp_attr_get_fmtp_max_mbps(sdp_ptr_, 1, 0, 1, &maxMpbs));
+ ASSERT_EQ(46789U, maxMpbs);
+}
+
+TEST_F(SdpTest, parseFmtpMaxMbpsWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-mbps=0\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_max_mbps(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxMbpsWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-mbps=4294967296\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_max_mbps(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxCpb) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-cpb=47891\r\n");
+ uint32_t maxCpb;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_max_cpb(sdp_ptr_, 1, 0, 1, &maxCpb));
+ ASSERT_EQ(47891U, maxCpb);
+}
+
+TEST_F(SdpTest, parseFmtpMaxCpbWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-cpb=0\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_max_cpb(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxCpbWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-cpb=4294967296\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_max_cpb(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxDpb) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-dpb=47892\r\n");
+ uint32_t maxDpb;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_max_dpb(sdp_ptr_, 1, 0, 1, &maxDpb));
+ ASSERT_EQ(47892U, maxDpb);
+}
+
+TEST_F(SdpTest, parseFmtpMaxDpbWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-dpb=0\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_max_dpb(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxDpbWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-dpb=4294967296\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_max_dpb(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxBr) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-br=47893\r\n");
+ uint32_t maxBr;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_max_br(sdp_ptr_, 1, 0, 1, &maxBr));
+ ASSERT_EQ(47893U, maxBr);
+}
+
+TEST_F(SdpTest, parseFmtpMaxBrWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-br=0\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_max_br(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxBrWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-br=4294967296\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_max_br(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpRedundantPicCap) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 redundant-pic-cap=1\r\n");
+ ASSERT_EQ(1, sdp_attr_fmtp_is_redundant_pic_cap(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpRedundantPicCapWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 redundant-pic-cap=0\r\n");
+ ASSERT_EQ(0, sdp_attr_fmtp_is_redundant_pic_cap(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpRedundantPicCapWith2) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 redundant-pic-cap=2\r\n");
+ ASSERT_EQ(0, sdp_attr_fmtp_is_redundant_pic_cap(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpDeintBufCap) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 deint-buf-cap=4294967295\r\n");
+ uint32_t deintBufCap;
+ ASSERT_EQ(SDP_SUCCESS,
+ sdp_attr_get_fmtp_deint_buf_cap(sdp_ptr_, 1, 0, 1, &deintBufCap));
+ ASSERT_EQ(4294967295, deintBufCap);
+}
+
+TEST_F(SdpTest, parseFmtpDeintBufCapWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 deint-buf-cap=0\r\n");
+ uint32_t deintBufCap;
+ ASSERT_EQ(SDP_SUCCESS,
+ sdp_attr_get_fmtp_deint_buf_cap(sdp_ptr_, 1, 0, 1, &deintBufCap));
+ ASSERT_EQ(0U, deintBufCap);
+}
+
+TEST_F(SdpTest, parseFmtpDeintBufCapWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 deint-buf-cap=4294967296\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_deint_buf_cap(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxRcmdNaluSize) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-rcmd-nalu-size=4294967295\r\n");
+ uint32_t maxRcmdNaluSize;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_max_rcmd_nalu_size(
+ sdp_ptr_, 1, 0, 1, &maxRcmdNaluSize));
+ ASSERT_EQ(4294967295, maxRcmdNaluSize);
+}
+
+TEST_F(SdpTest, parseFmtpMaxRcmdNaluSizeWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-rcmd-nalu-size=0\r\n");
+ uint32_t maxRcmdNaluSize;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_max_rcmd_nalu_size(
+ sdp_ptr_, 1, 0, 1, &maxRcmdNaluSize));
+ ASSERT_EQ(0U, maxRcmdNaluSize);
+}
+
+TEST_F(SdpTest, parseFmtpMaxRcmdNaluSizeWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-rcmd-nalu-size=4294967296\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_max_rcmd_nalu_size(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpParameterAdd) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 parameter-add=1\r\n");
+ ASSERT_EQ(1, sdp_attr_fmtp_is_parameter_add(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpParameterAddWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 parameter-add=0\r\n");
+ ASSERT_EQ(0, sdp_attr_fmtp_is_parameter_add(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpParameterAddWith2) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 parameter-add=2\r\n");
+ ASSERT_EQ(0, sdp_attr_fmtp_is_parameter_add(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexK) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 K=566\r\n");
+ ASSERT_EQ(566, sdp_attr_get_fmtp_annex_k_val(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexKWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 K=0\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE,
+ sdp_attr_get_fmtp_annex_k_val(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexKWith65536) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 K=65536\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE,
+ sdp_attr_get_fmtp_annex_k_val(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexN) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 N=4567\r\n");
+ ASSERT_EQ(4567, sdp_attr_get_fmtp_annex_n_val(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexNWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 N=0\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE,
+ sdp_attr_get_fmtp_annex_n_val(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexNWith65536) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 N=65536\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE,
+ sdp_attr_get_fmtp_annex_n_val(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexP) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 P=5678,2\r\n");
+ ASSERT_EQ(5678, sdp_attr_get_fmtp_annex_p_picture_resize(sdp_ptr_, 1, 0, 1));
+ ASSERT_EQ(2, sdp_attr_get_fmtp_annex_p_warp(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexPWithResize0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 P=0,3\r\n");
+ ASSERT_EQ(0, sdp_attr_get_fmtp_annex_p_picture_resize(sdp_ptr_, 1, 0, 1));
+ ASSERT_EQ(3, sdp_attr_get_fmtp_annex_p_warp(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexPWithResize65536) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 P=65536,4\r\n");
+ ASSERT_EQ(0, sdp_attr_get_fmtp_annex_p_picture_resize(sdp_ptr_, 1, 0, 1));
+ // if the first fails, the second will too. Both default to 0 on failure.
+ ASSERT_EQ(0, sdp_attr_get_fmtp_annex_p_warp(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexPWithWarp65536) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 P=346,65536\r\n");
+ ASSERT_EQ(346, sdp_attr_get_fmtp_annex_p_picture_resize(sdp_ptr_, 1, 0, 1));
+ ASSERT_EQ(0, sdp_attr_get_fmtp_annex_p_warp(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpLevelAsymmetryAllowed) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 level-asymmetry-allowed=1\r\n");
+
+ uint16_t levelAsymmetryAllowed;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_level_asymmetry_allowed(
+ sdp_ptr_, 1, 0, 1, &levelAsymmetryAllowed));
+ ASSERT_EQ(1U, levelAsymmetryAllowed);
+}
+
+TEST_F(SdpTest, parseFmtpLevelAsymmetryAllowedWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 level-asymmetry-allowed=0\r\n");
+ uint16_t levelAsymmetryAllowed;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_level_asymmetry_allowed(
+ sdp_ptr_, 1, 0, 1, &levelAsymmetryAllowed));
+ ASSERT_EQ(0U, levelAsymmetryAllowed);
+}
+
+TEST_F(SdpTest, parseFmtpLevelAsymmetryAllowedWith2) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 level-asymmetry-allowed=2\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_level_asymmetry_allowed(
+ sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxAverageBitrate) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 maxaveragebitrate=47893\r\n");
+ uint32_t maxAverageBitrate;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_max_average_bitrate(
+ sdp_ptr_, 1, 0, 1, &maxAverageBitrate));
+ ASSERT_EQ(47893U, maxAverageBitrate);
+}
+
+TEST_F(SdpTest, parseFmtpMaxAverageBitrateWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 maxaveragebitrate=0\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_max_average_bitrate(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxAverageBitrateWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 maxaveragebitrate=4294967296\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_max_average_bitrate(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpUsedTx) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 usedtx=1\r\n");
+ tinybool usedTx;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_usedtx(sdp_ptr_, 1, 0, 1, &usedTx));
+ ASSERT_EQ(1, usedTx);
+}
+
+TEST_F(SdpTest, parseFmtpUsedTxWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 usedtx=0\r\n");
+ tinybool usedTx;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_usedtx(sdp_ptr_, 1, 0, 1, &usedTx));
+ ASSERT_EQ(0, usedTx);
+}
+
+TEST_F(SdpTest, parseFmtpUsedTxWith2) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 usedtx=2\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_usedtx(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpStereo) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 stereo=1\r\n");
+ tinybool stereo;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_stereo(sdp_ptr_, 1, 0, 1, &stereo));
+ ASSERT_EQ(1, stereo);
+}
+
+TEST_F(SdpTest, parseFmtpStereoWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 stereo=0\r\n");
+ tinybool stereo;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_stereo(sdp_ptr_, 1, 0, 1, &stereo));
+ ASSERT_EQ(0, stereo);
+}
+
+TEST_F(SdpTest, parseFmtpStereoWith2) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 stereo=2\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_stereo(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpUseInBandFec) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 useinbandfec=1\r\n");
+ tinybool useInbandFec;
+ ASSERT_EQ(SDP_SUCCESS,
+ sdp_attr_get_fmtp_useinbandfec(sdp_ptr_, 1, 0, 1, &useInbandFec));
+ ASSERT_EQ(1, useInbandFec);
+}
+
+TEST_F(SdpTest, parseFmtpUseInBandWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 useinbandfec=0\r\n");
+ tinybool useInbandFec;
+ ASSERT_EQ(SDP_SUCCESS,
+ sdp_attr_get_fmtp_useinbandfec(sdp_ptr_, 1, 0, 1, &useInbandFec));
+ ASSERT_EQ(0, useInbandFec);
+}
+
+TEST_F(SdpTest, parseFmtpUseInBandWith2) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 useinbandfec=2\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_useinbandfec(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxCodedAudioBandwidth) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 maxcodedaudiobandwidth=abcdefg\r\n");
+ char* maxCodedAudioBandwith =
+ sdp_attr_get_fmtp_maxcodedaudiobandwidth(sdp_ptr_, 1, 0, 1);
+ ASSERT_EQ(0, strcmp("abcdefg", maxCodedAudioBandwith));
+}
+
+TEST_F(SdpTest, parseFmtpMaxCodedAudioBandwidthBad) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 maxcodedaudiobandwidth=\r\n");
+ char* maxCodedAudioBandwith =
+ sdp_attr_get_fmtp_maxcodedaudiobandwidth(sdp_ptr_, 1, 0, 1);
+ ASSERT_EQ(0, *maxCodedAudioBandwith);
+}
+
+TEST_F(SdpTest, parseFmtpCbr) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 cbr=1\r\n");
+ tinybool cbr;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_cbr(sdp_ptr_, 1, 0, 1, &cbr));
+ ASSERT_EQ(1, cbr);
+}
+
+TEST_F(SdpTest, parseFmtpCbrWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 cbr=0\r\n");
+ tinybool cbr;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_cbr(sdp_ptr_, 1, 0, 1, &cbr));
+ ASSERT_EQ(0, cbr);
+}
+
+TEST_F(SdpTest, parseFmtpCbrWith2) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 cbr=2\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_cbr(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxPlaybackRate) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 maxplaybackrate=47900\r\n");
+ sdp_attr_t* attr_p = sdp_find_attr(sdp_ptr_, 1, 0, SDP_ATTR_FMTP, 1);
+ ASSERT_NE(nullptr, attr_p);
+ ASSERT_EQ(47900U, attr_p->attr.fmtp.maxplaybackrate);
+}
+
+TEST_F(SdpTest, parseFmtpMaxPlaybackRateWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 maxplaybackrate=0\r\n");
+ sdp_attr_t* attr_p = sdp_find_attr(sdp_ptr_, 1, 0, SDP_ATTR_FMTP, 1);
+ ASSERT_EQ(NULL, attr_p);
+}
+
+TEST_F(SdpTest, parseFmtpMaxPlaybackRateWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 maxplaybackrate=4294967296\r\n");
+ sdp_attr_t* attr_p = sdp_find_attr(sdp_ptr_, 1, 0, SDP_ATTR_FMTP, 1);
+ ASSERT_EQ(NULL, attr_p);
+}
+
+TEST_F(SdpTest, parseFmtpMaxFs) {
+ uint32_t val = 0;
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-fs=300;max-fr=30\r\n");
+ ASSERT_EQ(sdp_attr_get_fmtp_max_fs(sdp_ptr_, 1, 0, 1, &val), SDP_SUCCESS);
+ ASSERT_EQ(val, 300U);
+}
+TEST_F(SdpTest, parseFmtpMaxFsWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-fs=0\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_max_fs(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxFsWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-fs=4294967296\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_max_fs(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxFr) {
+ uint32_t val = 0;
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-fs=300;max-fr=30\r\n");
+ ASSERT_EQ(sdp_attr_get_fmtp_max_fr(sdp_ptr_, 1, 0, 1, &val), SDP_SUCCESS);
+ ASSERT_EQ(val, 30U);
+}
+
+TEST_F(SdpTest, parseFmtpMaxFrWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-fr=0\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_max_fr(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxFrWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-fr=4294967296\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER,
+ sdp_attr_get_fmtp_max_fr(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, addFmtpMaxFs) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewFmtpMaxFs(level, 300);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=fmtp:120 max-fs=300\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addFmtpMaxFr) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewFmtpMaxFr(level, 30);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=fmtp:120 max-fr=30\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addFmtpMaxFsFr) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewFmtpMaxFsFr(level, 300, 30);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=fmtp:120 max-fs=300;max-fr=30\r\n"),
+ std::string::npos);
+}
+
+static const std::string kBrokenFmtp =
+ "v=0\r\n"
+ "o=- 4294967296 2 IN IP4 127.0.0.1\r\n"
+ "s=SIP Call\r\n"
+ "t=0 0\r\n"
+ "m=video 56436 RTP/SAVPF 120\r\n"
+ "c=IN IP4 198.51.100.7\r\n"
+ "a=rtpmap:120 VP8/90000\r\n"
+ /* Note: the \0 in this string triggered bz://1089207
+ */
+ "a=fmtp:120 max-fs=300;max\0fr=30";
+
+TEST_F(SdpTest, parseBrokenFmtp) {
+ uint32_t val = 0;
+ const char* buf = kBrokenFmtp.data();
+ ResetSdp();
+ /* We need to manually invoke the parser here to be able to specify the length
+ * of the string beyond the \0 in last line of the string.
+ */
+ ASSERT_EQ(sdp_parse(sdp_ptr_, buf, 165), SDP_SUCCESS);
+ ASSERT_EQ(sdp_attr_get_fmtp_max_fs(sdp_ptr_, 1, 0, 1, &val),
+ SDP_INVALID_PARAMETER);
+}
+
+TEST_F(SdpTest, addIceLite) {
+ InitLocalSdp();
+ uint16_t inst_num = 0;
+ EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, SDP_SESSION_LEVEL, 0, SDP_ATTR_ICE_LITE,
+ &inst_num),
+ SDP_SUCCESS);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=ice-lite\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, parseIceLite) {
+ std::string sdp =
+ "v=0\r\n"
+ "o=- 4294967296 2 IN IP4 127.0.0.1\r\n"
+ "s=SIP Call\r\n"
+ "t=0 0\r\n"
+ "a=ice-lite\r\n";
+ ParseSdp(sdp);
+ ASSERT_TRUE(
+ sdp_attr_is_present(sdp_ptr_, SDP_ATTR_ICE_LITE, SDP_SESSION_LEVEL, 0));
+}
+
+class NewSdpTest
+ : public ::testing::Test,
+ public ::testing::WithParamInterface< ::testing::tuple<bool, bool> > {
+ public:
+ NewSdpTest() = default;
+
+ void ParseSdp(const std::string& sdp, bool expectSuccess = true,
+ bool expectEqual = true) {
+ UniquePtr<SdpParser> firstParser(new RsdparsaSdpParser());
+ UniquePtr<SdpParser> secondParser(new SipccSdpParser());
+ if (::testing::get<1>(GetParam())) {
+ firstParser.swap(secondParser);
+ }
+ mResults = firstParser->Parse(sdp);
+#ifdef DEBUG_DISPLAY_SDP
+ std::cout << firstParser->Name() << " Parsing SDP:" << std::endl;
+ std::stringstream sdpStream(sdp);
+ std::string line;
+ size_t lineNumber = 0;
+ while (std::getline(sdpStream, line, '\n')) {
+ if (line.length() && line.back() == '\r') {
+ line.pop_back();
+ }
+ lineNumber++;
+ std::cout << std::setw(4) << lineNumber << " " << line << std::endl;
+ }
+#endif
+ // Are we configured to do a parse and serialize before actually
+ // running the test?
+ if (::testing::get<0>(GetParam())) {
+ if (expectSuccess) {
+ ASSERT_TRUE(!!mResults->Sdp())
+ << "Parse failed on first pass: " << SerializeParseErrors();
+ }
+
+ if (mResults->Sdp()) {
+ std::stringstream os, os2;
+ // Serialize and re-parse
+ mResults->Sdp()->Serialize(os);
+ const auto secondResults = secondParser->Parse(os.str());
+ // Whether we expected the parse to work or not, it should
+ // succeed the second time if it succeeded the first.
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed on second pass, SDP was: " << std::endl
+ << os.str() << std::endl
+ << "Errors were: " << IntSerializeParseErrors(secondResults);
+ // Serialize again and compare
+ secondResults->Sdp()->Serialize(os2);
+ if (expectEqual) {
+ ASSERT_EQ(os.str(), os2.str())
+ << "FIRST IS " << mResults->ParserName() << ", SECOND IS "
+ << secondResults->ParserName();
+ } else {
+ ASSERT_NE(os.str(), os2.str())
+ << "FIRST IS " << mResults->ParserName() << ", SECOND IS "
+ << secondResults->ParserName();
+ }
+ }
+ }
+
+ if (expectSuccess) {
+ ASSERT_TRUE(!!mResults->Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(0U, ParseErrorCount())
+ << "Got unexpected parse errors/warnings: " << SerializeParseErrors();
+ }
+ }
+ const UniquePtr<class Sdp>& Sdp() const {
+ static const UniquePtr<class Sdp> NO_SDP(nullptr);
+ return mResults ? mResults->Sdp() : NO_SDP;
+ }
+
+ size_t ParseErrorCount() const { return mResults->Errors().size(); }
+ size_t ParseWarningCount() const { return mResults->Warnings().size(); }
+
+ std::string IntSerializeParseErrors(
+ const UniquePtr<SdpParser::Results>& aResults) const {
+ std::stringstream output;
+ for (const auto& e : aResults->Errors()) {
+ output << e.first << ": " << e.second << std::endl;
+ }
+ return output.str();
+ }
+ // For streaming parse errors
+ std::string SerializeParseErrors() const {
+ return IntSerializeParseErrors(mResults);
+ }
+
+ std::string IntSerializeParseWarnings(
+ const UniquePtr<SdpParser::Results>& aResults) const {
+ std::stringstream output;
+ for (const auto& e : aResults->Warnings()) {
+ output << e.first << ": " << e.second << std::endl;
+ }
+ return output.str();
+ }
+
+ std::string SerializeParseWarnings() const {
+ return IntSerializeParseWarnings(mResults);
+ }
+
+ void CheckRtpmap(const std::string& expected_pt,
+ SdpRtpmapAttributeList::CodecType codec,
+ const std::string& name, uint32_t clock, uint16_t channels,
+ const std::string& search_pt,
+ const SdpRtpmapAttributeList& rtpmaps) const {
+ ASSERT_TRUE(rtpmaps.HasEntry(search_pt));
+ auto attr = rtpmaps.GetEntry(search_pt);
+ ASSERT_EQ(expected_pt, attr.pt);
+ ASSERT_EQ(codec, attr.codec);
+ std::cout << "Codec = " << name << std::endl;
+ ASSERT_EQ(name, attr.name);
+ ASSERT_EQ(clock, attr.clock);
+ ASSERT_EQ(channels, attr.channels);
+ }
+
+ void CheckSctpmap(const std::string& expected_pt, const std::string& name,
+ uint16_t streams, const std::string& search_pt,
+ const SdpSctpmapAttributeList& sctpmaps) const {
+ ASSERT_TRUE(sctpmaps.HasEntry(search_pt));
+ auto attr = sctpmaps.GetFirstEntry();
+ ASSERT_EQ(expected_pt, search_pt);
+ ASSERT_EQ(expected_pt, attr.pt);
+ ASSERT_EQ(name, attr.name);
+ ASSERT_EQ(streams, attr.streams);
+ }
+
+ void CheckRtcpFb(const SdpRtcpFbAttributeList::Feedback& feedback,
+ const std::string& pt, SdpRtcpFbAttributeList::Type type,
+ const std::string& first_parameter,
+ const std::string& extra = "") const {
+ ASSERT_EQ(pt, feedback.pt);
+ ASSERT_EQ(type, feedback.type);
+ ASSERT_EQ(first_parameter, feedback.parameter);
+ ASSERT_EQ(extra, feedback.extra);
+ }
+
+ void CheckDtmfFmtp(const std::string& expectedDtmfTones) const {
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kFmtpAttribute));
+ auto audio_format_params =
+ Sdp()->GetMediaSection(0).GetAttributeList().GetFmtp().mFmtps;
+ ASSERT_EQ(2U, audio_format_params.size());
+
+ ASSERT_EQ("101", audio_format_params[1].format);
+ ASSERT_TRUE(!!audio_format_params[1].parameters);
+ const SdpFmtpAttributeList::TelephoneEventParameters* te_parameters =
+ static_cast<SdpFmtpAttributeList::TelephoneEventParameters*>(
+ audio_format_params[1].parameters.get());
+ ASSERT_NE(0U, te_parameters->dtmfTones.size());
+ ASSERT_EQ(expectedDtmfTones, te_parameters->dtmfTones);
+ }
+
+ void CheckSerialize(const std::string& expected,
+ const SdpAttribute& attr) const {
+ std::stringstream str;
+ attr.Serialize(str);
+ ASSERT_EQ(expected, str.str());
+ }
+
+ bool ResultsAreFromSipcc() const {
+ return mResults && SipccSdpParser::IsNamed(mResults->ParserName());
+ }
+
+ mozilla::UniquePtr<SdpParser::Results> mResults;
+}; // class NewSdpTest
+
+TEST_P(NewSdpTest, CreateDestroy) {}
+
+TEST_P(NewSdpTest, ParseEmpty) {
+ ParseSdp("", false);
+ ASSERT_FALSE(Sdp());
+ ASSERT_NE(0U, ParseErrorCount()) << "Expected at least one parse error.";
+}
+
+const std::string kBadSdp = "This is SDPARTA!!!!";
+
+TEST_P(NewSdpTest, ParseGarbage) {
+ ParseSdp(kBadSdp, false);
+ ASSERT_FALSE(Sdp());
+ ASSERT_NE(0U, ParseErrorCount()) << "Expected at least one parse error.";
+}
+
+TEST_P(NewSdpTest, ParseGarbageTwice) {
+ ParseSdp(kBadSdp, false);
+ ASSERT_FALSE(Sdp());
+ size_t errorCount = ParseErrorCount();
+ ASSERT_NE(0U, errorCount) << "Expected at least one parse error.";
+ ParseSdp(kBadSdp, false);
+ ASSERT_FALSE(Sdp());
+ ASSERT_EQ(errorCount, ParseErrorCount())
+ << "Expected same error count for same SDP.";
+}
+
+TEST_P(NewSdpTest, ParseMinimal) {
+ ParseSdp(kVideoSdp);
+ ASSERT_EQ(0U, ParseErrorCount())
+ << "Got parse errors: " << SerializeParseErrors();
+}
+
+TEST_P(NewSdpTest, CheckOriginGetUsername) {
+ ParseSdp(kVideoSdp);
+ ASSERT_EQ("-", Sdp()->GetOrigin().GetUsername())
+ << "Wrong username in origin";
+}
+
+TEST_P(NewSdpTest, CheckOriginGetSessionId) {
+ ParseSdp(kVideoSdp);
+ ASSERT_EQ(4294967296U, Sdp()->GetOrigin().GetSessionId())
+ << "Wrong session id in origin";
+}
+
+TEST_P(NewSdpTest, CheckOriginGetSessionVersion) {
+ ParseSdp(kVideoSdp);
+ ASSERT_EQ(2U, Sdp()->GetOrigin().GetSessionVersion())
+ << "Wrong version in origin";
+}
+
+TEST_P(NewSdpTest, CheckOriginGetAddrType) {
+ ParseSdp(kVideoSdp);
+ ASSERT_EQ(sdp::kIPv4, Sdp()->GetOrigin().GetAddrType())
+ << "Wrong address type in origin";
+}
+
+TEST_P(NewSdpTest, CheckOriginGetAddress) {
+ ParseSdp(kVideoSdp);
+ ASSERT_EQ("127.0.0.1", Sdp()->GetOrigin().GetAddress())
+ << "Wrong address in origin";
+}
+
+TEST_P(NewSdpTest, CheckGetMissingBandwidth) {
+ ParseSdp(kVideoSdp);
+ ASSERT_EQ(0U, Sdp()->GetBandwidth("CT")) << "Wrong bandwidth in session";
+}
+
+TEST_P(NewSdpTest, CheckGetBandwidth) {
+ ParseSdp("v=0" CRLF "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF "s=SIP Call" CRLF
+ "c=IN IP4 198.51.100.7" CRLF "b=CT:5000" CRLF "b=FOOBAR:10" CRLF
+ "b=AS:4" CRLF "t=0 0" CRLF "m=video 56436 RTP/SAVPF 120" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF,
+ true, ::testing::get<1>(GetParam()));
+ ASSERT_EQ(5000U, Sdp()->GetBandwidth("CT"))
+ << "Wrong CT bandwidth in session";
+ ASSERT_EQ(0U, Sdp()->GetBandwidth("FOOBAR"))
+ << "Wrong FOOBAR bandwidth in session";
+ ASSERT_EQ(4U, Sdp()->GetBandwidth("AS")) << "Wrong AS bandwidth in session";
+}
+
+TEST_P(NewSdpTest, CheckGetMediaSectionsCount) {
+ ParseSdp(kVideoSdp);
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+}
+
+TEST_P(NewSdpTest, CheckMediaSectionGetMediaType) {
+ ParseSdp(kVideoSdp);
+ ASSERT_EQ(SdpMediaSection::kVideo, Sdp()->GetMediaSection(0).GetMediaType())
+ << "Wrong type for first media section";
+}
+
+TEST_P(NewSdpTest, CheckMediaSectionGetProtocol) {
+ ParseSdp(kVideoSdp);
+ ASSERT_EQ(SdpMediaSection::kRtpSavpf, Sdp()->GetMediaSection(0).GetProtocol())
+ << "Wrong protocol for video";
+}
+
+TEST_P(NewSdpTest, CheckMediaSectionGetFormats) {
+ ParseSdp(kVideoSdp);
+ auto video_formats = Sdp()->GetMediaSection(0).GetFormats();
+ ASSERT_EQ(1U, video_formats.size()) << "Wrong number of formats for video";
+ ASSERT_EQ("120", video_formats[0]);
+}
+
+TEST_P(NewSdpTest, CheckMediaSectionGetPort) {
+ ParseSdp(kVideoSdp);
+ ASSERT_EQ(56436U, Sdp()->GetMediaSection(0).GetPort())
+ << "Wrong port number in media section";
+}
+
+TEST_P(NewSdpTest, CheckMediaSectionGetMissingPortCount) {
+ ParseSdp(kVideoSdp);
+ ASSERT_EQ(0U, Sdp()->GetMediaSection(0).GetPortCount())
+ << "Wrong port count in media section";
+}
+
+TEST_P(NewSdpTest, CheckMediaSectionGetPortCount) {
+ ParseSdp(kVideoSdp + "m=audio 12345/2 RTP/SAVPF 0" CRLF
+ "a=rtpmap:0 PCMU/8000" CRLF);
+ ASSERT_EQ(2U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+ ASSERT_EQ(2U, Sdp()->GetMediaSection(1).GetPortCount())
+ << "Wrong port count in media section";
+}
+
+TEST_P(NewSdpTest, CheckMediaSectionGetMissingBandwidth) {
+ ParseSdp(kVideoSdp);
+ ASSERT_EQ(0U, Sdp()->GetMediaSection(0).GetBandwidth("CT"))
+ << "Wrong bandwidth in media section";
+}
+
+TEST_P(NewSdpTest, CheckMediaSectionGetBandwidth) {
+ ParseSdp(
+ "v=0\r\n"
+ "o=- 4294967296 2 IN IP4 127.0.0.1\r\n"
+ "s=SIP Call\r\n"
+ "c=IN IP4 198.51.100.7\r\n"
+ "t=0 0\r\n"
+ "m=video 56436 RTP/SAVPF 120\r\n"
+ "b=CT:1000\r\n"
+ "a=rtpmap:120 VP8/90000\r\n");
+ ASSERT_EQ(1000U, Sdp()->GetMediaSection(0).GetBandwidth("CT"))
+ << "Wrong bandwidth in media section";
+}
+
+// Define a string that is 258 characters long. We use a long string here so
+// that we can test that we are able to parse and handle a string longer than
+// the default maximum length of 256 in sipcc.
+#define ID_A "1234567890abcdef"
+#define ID_B ID_A ID_A ID_A ID_A
+#define LONG_IDENTITY ID_B ID_B ID_B ID_B "xx"
+
+#define BASE64_DTLS_HELLO \
+ "FgEAAAAAAAAAAAAAagEAAF4AAAAAAAAAXgEARI11KHx3QB6Ky" \
+ "CKgoBj/kwjKrApkL8kiZLwIqBaJGT8AAAA2ADkAOAA1ABYAEwAKADMAMgAvAAcAZgAFAAQAYw" \
+ "BiAGEAFQASAAkAZQBkAGAAFAARAAgABgADAQA="
+
+// SDP from a basic A/V apprtc call FFX/FFX
+const std::string kBasicAudioVideoOffer =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "a=dtls-message:client " BASE64_DTLS_HELLO CRLF "a=ice-ufrag:4a799b2e" CRLF
+ "a=ice-pwd:e4cc12a910f106a0a744719425510e17" CRLF "a=ice-lite" CRLF
+ "a=ice-options:trickle foo" CRLF "a=msid-semantic:WMS stream streama" CRLF
+ "a=msid-semantic:foo stream" CRLF
+ "a=fingerprint:sha-256 "
+ "DF:2E:AC:8A:FD:0A:8E:99:BF:5D:E8:3C:E7:FA:FB:08:3B:3C:54:1D:D7:D4:05:77:"
+ "A0:72:9B:14:08:6D:0F:4C" CRLF "a=identity:" LONG_IDENTITY CRLF
+ "a=group:BUNDLE first second" CRLF "a=group:BUNDLE third" CRLF
+ "a=group:LS first third" CRLF "m=audio 9 RTP/SAVPF 109 9 0 8 101" CRLF
+ "c=IN IP4 0.0.0.0" CRLF "a=mid:first" CRLF "a=rtpmap:109 opus/48000/2" CRLF
+ "a=fmtp:109 maxplaybackrate=32000;stereo=1" CRLF "a=ptime:20" CRLF
+ "a=maxptime:20" CRLF "a=rtpmap:9 G722/8000" CRLF "a=rtpmap:0 PCMU/8000" CRLF
+ "a=rtpmap:8 PCMA/8000" CRLF "a=rtpmap:101 telephone-event/8000" CRLF
+ "a=fmtp:101 0-15,66,32-34,67" CRLF "a=ice-ufrag:00000000" CRLF
+ "a=ice-pwd:0000000000000000000000000000000" CRLF "a=sendonly" CRLF
+ "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level" CRLF
+ "a=setup:actpass" CRLF "a=rtcp-mux" CRLF "a=msid:stream track" CRLF
+ "a=candidate:0 1 UDP 2130379007 10.0.0.36 62453 typ host" CRLF
+ "a=candidate:2 1 UDP 1694236671 24.6.134.204 62453 typ srflx raddr "
+ "10.0.0.36 rport 62453" CRLF
+ "a=candidate:3 1 UDP 100401151 162.222.183.171 49761 typ relay raddr "
+ "162.222.183.171 rport 49761" CRLF
+ "a=candidate:6 1 UDP 16515071 162.222.183.171 51858 typ relay raddr "
+ "162.222.183.171 rport 51858" CRLF
+ "a=candidate:3 2 UDP 100401150 162.222.183.171 62454 typ relay raddr "
+ "162.222.183.171 rport 62454" CRLF
+ "a=candidate:2 2 UDP 1694236670 24.6.134.204 55428 typ srflx raddr "
+ "10.0.0.36 rport 55428" CRLF
+ "a=candidate:6 2 UDP 16515070 162.222.183.171 50340 typ relay raddr "
+ "162.222.183.171 rport 50340" CRLF
+ "a=candidate:0 2 UDP 2130379006 10.0.0.36 55428 typ host" CRLF
+ "a=rtcp:62454 IN IP4 162.222.183.171" CRLF "a=end-of-candidates" CRLF
+ "a=ssrc:5150" CRLF "m=video 9 RTP/SAVPF 120 121 122 123" CRLF
+ "c=IN IP6 ::1" CRLF
+ "a=fingerprint:sha-1 "
+ "DF:FA:FB:08:3B:3C:54:1D:D7:D4:05:77:A0:72:9B:14:08:6D:0F:4C" CRLF
+ "a=mid:second" CRLF "a=rtpmap:120 VP8/90000" CRLF
+ "a=fmtp:120 max-fs=3600;max-fr=30" CRLF "a=rtpmap:121 VP9/90000" CRLF
+ "a=fmtp:121 max-fs=3600;max-fr=30" CRLF "a=rtpmap:122 red/90000" CRLF
+ "a=rtpmap:123 ulpfec/90000" CRLF "a=recvonly" CRLF "a=rtcp-fb:120 nack" CRLF
+ "a=rtcp-fb:120 nack pli" CRLF "a=rtcp-fb:120 ccm fir" CRLF
+ "a=rtcp-fb:121 nack" CRLF "a=rtcp-fb:121 nack pli" CRLF
+ "a=rtcp-fb:121 ccm fir" CRLF "a=setup:active" CRLF "a=rtcp-mux" CRLF
+ "a=msid:streama tracka" CRLF "a=msid:streamb trackb" CRLF
+ "a=candidate:0 1 UDP 2130379007 10.0.0.36 59530 typ host" CRLF
+ "a=candidate:0 2 UDP 2130379006 10.0.0.36 64378 typ host" CRLF
+ "a=candidate:2 2 UDP 1694236670 24.6.134.204 64378 typ srflx raddr "
+ "10.0.0.36 rport 64378" CRLF
+ "a=candidate:6 2 UDP 16515070 162.222.183.171 64941 typ relay raddr "
+ "162.222.183.171 rport 64941" CRLF
+ "a=candidate:6 1 UDP 16515071 162.222.183.171 64800 typ relay raddr "
+ "162.222.183.171 rport 64800" CRLF
+ "a=candidate:2 1 UDP 1694236671 24.6.134.204 59530 typ srflx raddr "
+ "10.0.0.36 rport 59530" CRLF
+ "a=candidate:3 1 UDP 100401151 162.222.183.171 62935 typ relay raddr "
+ "162.222.183.171 rport 62935" CRLF
+ "a=candidate:3 2 UDP 100401150 162.222.183.171 61026 typ relay raddr "
+ "162.222.183.171 rport 61026" CRLF "a=rtcp:61026" CRLF
+ "a=end-of-candidates" CRLF "a=ssrc:1111 foo" CRLF "a=ssrc:1111 foo:bar" CRLF
+ "a=ssrc:1111 msid:1d0cdb4e-5934-4f0f-9f88-40392cb60d31 "
+ "315b086a-5cb6-4221-89de-caf0b038c79d" CRLF
+ "a=imageattr:120 send * recv *" CRLF
+ "a=imageattr:121 send [x=640,y=480] recv [x=640,y=480]" CRLF
+ "a=rid:bar recv pt=120;max-width=800;max-height=600" CRLF
+ "a=rid:bar123 recv max-width=1920;max-height=1080" CRLF
+ "a=simulcast:recv bar;bar123" CRLF "m=audio 9 RTP/SAVPF 0" CRLF
+ "a=mid:third" CRLF "a=rtpmap:0 PCMU/8000" CRLF "a=ice-options:foo bar" CRLF
+ "a=msid:noappdata" CRLF "a=bundle-only" CRLF;
+
+TEST_P(NewSdpTest, BasicAudioVideoSdpParse) { ParseSdp(kBasicAudioVideoOffer); }
+
+TEST_P(NewSdpTest, CheckRemoveFmtp) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(3U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ SdpAttributeList& audioAttrList =
+ Sdp()->GetMediaSection(0).GetAttributeList();
+
+ ASSERT_TRUE(audioAttrList.HasAttribute(SdpAttribute::kFmtpAttribute));
+ ASSERT_EQ(2U, audioAttrList.GetFmtp().mFmtps.size());
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).FindFmtp("109"));
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).FindFmtp("101"));
+
+ Sdp()->GetMediaSection(0).RemoveFmtp("101");
+
+ ASSERT_TRUE(audioAttrList.HasAttribute(SdpAttribute::kFmtpAttribute));
+ ASSERT_EQ(1U, audioAttrList.GetFmtp().mFmtps.size());
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).FindFmtp("109"));
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).FindFmtp("101"));
+
+ Sdp()->GetMediaSection(0).RemoveFmtp("109");
+
+ ASSERT_TRUE(audioAttrList.HasAttribute(SdpAttribute::kFmtpAttribute));
+ ASSERT_EQ(0U, audioAttrList.GetFmtp().mFmtps.size());
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).FindFmtp("109"));
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).FindFmtp("101"));
+
+ // make sure we haven't disturbed the video fmtps
+ SdpAttributeList& videoAttrList =
+ Sdp()->GetMediaSection(1).GetAttributeList();
+ ASSERT_TRUE(videoAttrList.HasAttribute(SdpAttribute::kFmtpAttribute));
+ ASSERT_EQ(2U, videoAttrList.GetFmtp().mFmtps.size());
+ ASSERT_TRUE(Sdp()->GetMediaSection(1).FindFmtp("120"));
+ ASSERT_TRUE(Sdp()->GetMediaSection(1).FindFmtp("121"));
+}
+
+TEST_P(NewSdpTest, CheckIceUfrag) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_TRUE(
+ Sdp()->GetAttributeList().HasAttribute(SdpAttribute::kIceUfragAttribute));
+ auto ice_ufrag = Sdp()->GetAttributeList().GetIceUfrag();
+ ASSERT_EQ("4a799b2e", ice_ufrag) << "Wrong ice-ufrag value";
+
+ ice_ufrag = Sdp()->GetMediaSection(0).GetAttributeList().GetIceUfrag();
+ ASSERT_EQ("00000000", ice_ufrag) << "ice-ufrag isn't overridden";
+
+ ice_ufrag = Sdp()->GetMediaSection(1).GetAttributeList().GetIceUfrag();
+ ASSERT_EQ("4a799b2e", ice_ufrag) << "ice-ufrag isn't carried to m-section";
+}
+
+TEST_P(NewSdpTest, CheckIcePwd) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_TRUE(
+ Sdp()->GetAttributeList().HasAttribute(SdpAttribute::kIcePwdAttribute));
+ auto ice_pwd = Sdp()->GetAttributeList().GetIcePwd();
+ ASSERT_EQ("e4cc12a910f106a0a744719425510e17", ice_pwd)
+ << "Wrong ice-pwd value";
+
+ ice_pwd = Sdp()->GetMediaSection(0).GetAttributeList().GetIcePwd();
+ ASSERT_EQ("0000000000000000000000000000000", ice_pwd)
+ << "ice-pwd isn't overridden";
+
+ ice_pwd = Sdp()->GetMediaSection(1).GetAttributeList().GetIcePwd();
+ ASSERT_EQ("e4cc12a910f106a0a744719425510e17", ice_pwd)
+ << "ice-pwd isn't carried to m-section";
+}
+
+TEST_P(NewSdpTest, CheckIceOptions) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_TRUE(Sdp()->GetAttributeList().HasAttribute(
+ SdpAttribute::kIceOptionsAttribute));
+ auto ice_options = Sdp()->GetAttributeList().GetIceOptions();
+ ASSERT_EQ(2U, ice_options.mValues.size()) << "Wrong ice-options size";
+ ASSERT_EQ("trickle", ice_options.mValues[0]) << "Wrong ice-options value";
+ ASSERT_EQ("foo", ice_options.mValues[1]) << "Wrong ice-options value";
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kIceOptionsAttribute));
+ auto ice_options_media_level =
+ Sdp()->GetMediaSection(2).GetAttributeList().GetIceOptions();
+ ASSERT_EQ(2U, ice_options_media_level.mValues.size())
+ << "Wrong ice-options size";
+ ASSERT_EQ("foo", ice_options_media_level.mValues[0])
+ << "Wrong ice-options value";
+ ASSERT_EQ("bar", ice_options_media_level.mValues[1])
+ << "Wrong ice-options value";
+}
+
+TEST_P(NewSdpTest, CheckFingerprint) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_TRUE(Sdp()->GetAttributeList().HasAttribute(
+ SdpAttribute::kFingerprintAttribute));
+ auto fingerprints = Sdp()->GetAttributeList().GetFingerprint();
+ ASSERT_EQ(1U, fingerprints.mFingerprints.size());
+ ASSERT_EQ(SdpFingerprintAttributeList::kSha256,
+ fingerprints.mFingerprints[0].hashFunc)
+ << "Wrong hash function";
+ ASSERT_EQ(
+ "DF:2E:AC:8A:FD:0A:8E:99:BF:5D:E8:3C:E7:FA:FB:08:"
+ "3B:3C:54:1D:D7:D4:05:77:A0:72:9B:14:08:6D:0F:4C",
+ SdpFingerprintAttributeList::FormatFingerprint(
+ fingerprints.mFingerprints[0].fingerprint))
+ << "Wrong fingerprint";
+ ASSERT_EQ(0xdfU, fingerprints.mFingerprints[0].fingerprint[0])
+ << "first fingerprint element is iffy";
+
+ ASSERT_EQ(3U, Sdp()->GetMediaSectionCount());
+
+ // Fallback to session level
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kFingerprintAttribute));
+ fingerprints = Sdp()->GetMediaSection(0).GetAttributeList().GetFingerprint();
+ ASSERT_EQ(1U, fingerprints.mFingerprints.size());
+ ASSERT_EQ(SdpFingerprintAttributeList::kSha256,
+ fingerprints.mFingerprints[0].hashFunc)
+ << "Wrong hash function";
+ ASSERT_EQ(
+ "DF:2E:AC:8A:FD:0A:8E:99:BF:5D:E8:3C:E7:FA:FB:08:"
+ "3B:3C:54:1D:D7:D4:05:77:A0:72:9B:14:08:6D:0F:4C",
+ SdpFingerprintAttributeList::FormatFingerprint(
+ fingerprints.mFingerprints[0].fingerprint))
+ << "Wrong fingerprint";
+ ASSERT_EQ(0xdfU, fingerprints.mFingerprints[0].fingerprint[0])
+ << "first fingerprint element is iffy";
+
+ // Media level
+ ASSERT_TRUE(Sdp()->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kFingerprintAttribute));
+ fingerprints = Sdp()->GetMediaSection(1).GetAttributeList().GetFingerprint();
+ ASSERT_EQ(1U, fingerprints.mFingerprints.size());
+ ASSERT_EQ(SdpFingerprintAttributeList::kSha1,
+ fingerprints.mFingerprints[0].hashFunc)
+ << "Wrong hash function";
+ ASSERT_EQ(
+ "DF:FA:FB:08:3B:3C:54:1D:D7:D4:05:77:A0:72:9B:14:"
+ "08:6D:0F:4C",
+ SdpFingerprintAttributeList::FormatFingerprint(
+ fingerprints.mFingerprints[0].fingerprint))
+ << "Wrong fingerprint";
+ ASSERT_EQ(0xdfU, fingerprints.mFingerprints[0].fingerprint[0])
+ << "first fingerprint element is iffy";
+}
+
+TEST_P(NewSdpTest, CheckIdentity) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_TRUE(
+ Sdp()->GetAttributeList().HasAttribute(SdpAttribute::kIdentityAttribute));
+ auto identity = Sdp()->GetAttributeList().GetIdentity();
+ ASSERT_EQ(LONG_IDENTITY, identity) << "Wrong identity assertion";
+}
+
+TEST_P(NewSdpTest, CheckDtlsMessage) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_TRUE(Sdp()->GetAttributeList().HasAttribute(
+ SdpAttribute::kDtlsMessageAttribute));
+ auto dtls_message = Sdp()->GetAttributeList().GetDtlsMessage();
+ ASSERT_EQ(SdpDtlsMessageAttribute::kClient, dtls_message.mRole)
+ << "Wrong dtls-message role";
+ ASSERT_EQ(BASE64_DTLS_HELLO, dtls_message.mValue)
+ << "Wrong dtls-message value";
+}
+
+TEST_P(NewSdpTest, CheckNumberOfMediaSections) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(3U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+}
+
+TEST_P(NewSdpTest, CheckMlines) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(3U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+ ASSERT_EQ(SdpMediaSection::kAudio, Sdp()->GetMediaSection(0).GetMediaType())
+ << "Wrong type for first media section";
+ ASSERT_EQ(SdpMediaSection::kRtpSavpf, Sdp()->GetMediaSection(0).GetProtocol())
+ << "Wrong protocol for audio";
+ auto audio_formats = Sdp()->GetMediaSection(0).GetFormats();
+ ASSERT_EQ(5U, audio_formats.size()) << "Wrong number of formats for audio";
+ ASSERT_EQ("109", audio_formats[0]);
+ ASSERT_EQ("9", audio_formats[1]);
+ ASSERT_EQ("0", audio_formats[2]);
+ ASSERT_EQ("8", audio_formats[3]);
+ ASSERT_EQ("101", audio_formats[4]);
+
+ ASSERT_EQ(SdpMediaSection::kVideo, Sdp()->GetMediaSection(1).GetMediaType())
+ << "Wrong type for second media section";
+ ASSERT_EQ(SdpMediaSection::kRtpSavpf, Sdp()->GetMediaSection(1).GetProtocol())
+ << "Wrong protocol for video";
+ auto video_formats = Sdp()->GetMediaSection(1).GetFormats();
+ ASSERT_EQ(4U, video_formats.size()) << "Wrong number of formats for video";
+ ASSERT_EQ("120", video_formats[0]);
+ ASSERT_EQ("121", video_formats[1]);
+ ASSERT_EQ("122", video_formats[2]);
+ ASSERT_EQ("123", video_formats[3]);
+
+ ASSERT_EQ(SdpMediaSection::kAudio, Sdp()->GetMediaSection(2).GetMediaType())
+ << "Wrong type for third media section";
+}
+
+TEST_P(NewSdpTest, CheckSetup) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(3U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kSetupAttribute));
+ ASSERT_EQ(SdpSetupAttribute::kActpass,
+ Sdp()->GetMediaSection(0).GetAttributeList().GetSetup().mRole);
+ ASSERT_TRUE(Sdp()->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kSetupAttribute));
+ ASSERT_EQ(SdpSetupAttribute::kActive,
+ Sdp()->GetMediaSection(1).GetAttributeList().GetSetup().mRole);
+ ASSERT_FALSE(Sdp()->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kSetupAttribute));
+}
+
+TEST_P(NewSdpTest, CheckSsrc) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(3U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kSsrcAttribute));
+ auto ssrcs = Sdp()->GetMediaSection(0).GetAttributeList().GetSsrc().mSsrcs;
+ ASSERT_EQ(1U, ssrcs.size());
+ ASSERT_EQ(5150U, ssrcs[0].ssrc);
+ ASSERT_EQ("", ssrcs[0].attribute);
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kSsrcAttribute));
+ ssrcs = Sdp()->GetMediaSection(1).GetAttributeList().GetSsrc().mSsrcs;
+ ASSERT_EQ(3U, ssrcs.size());
+ ASSERT_EQ(1111U, ssrcs[0].ssrc);
+ ASSERT_EQ("foo", ssrcs[0].attribute);
+ ASSERT_EQ(1111U, ssrcs[1].ssrc);
+ ASSERT_EQ("foo:bar", ssrcs[1].attribute);
+ ASSERT_EQ(1111U, ssrcs[2].ssrc);
+ ASSERT_EQ(
+ "msid:1d0cdb4e-5934-4f0f-9f88-40392cb60d31 "
+ "315b086a-5cb6-4221-89de-caf0b038c79d",
+ ssrcs[2].attribute);
+}
+
+TEST_P(NewSdpTest, CheckRtpmap) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(3U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ const SdpMediaSection& audiosec = Sdp()->GetMediaSection(0);
+ const SdpRtpmapAttributeList& rtpmap =
+ audiosec.GetAttributeList().GetRtpmap();
+ ASSERT_EQ(5U, rtpmap.mRtpmaps.size())
+ << "Wrong number of rtpmap attributes for audio";
+
+ // Need to know name of type
+ CheckRtpmap("109", SdpRtpmapAttributeList::kOpus, "opus", 48000, 2,
+ audiosec.GetFormats()[0], rtpmap);
+
+ CheckRtpmap("9", SdpRtpmapAttributeList::kG722, "G722", 8000, 1,
+ audiosec.GetFormats()[1], rtpmap);
+
+ CheckRtpmap("0", SdpRtpmapAttributeList::kPCMU, "PCMU", 8000, 1,
+ audiosec.GetFormats()[2], rtpmap);
+
+ CheckRtpmap("8", SdpRtpmapAttributeList::kPCMA, "PCMA", 8000, 1,
+ audiosec.GetFormats()[3], rtpmap);
+
+ CheckRtpmap("101", SdpRtpmapAttributeList::kTelephoneEvent, "telephone-event",
+ 8000, 1, audiosec.GetFormats()[4], rtpmap);
+
+ const SdpMediaSection& videosec = Sdp()->GetMediaSection(1);
+ const SdpRtpmapAttributeList videoRtpmap =
+ videosec.GetAttributeList().GetRtpmap();
+ ASSERT_EQ(4U, videoRtpmap.mRtpmaps.size())
+ << "Wrong number of rtpmap attributes for video";
+
+ CheckRtpmap("120", SdpRtpmapAttributeList::kVP8, "VP8", 90000, 0,
+ videosec.GetFormats()[0], videoRtpmap);
+
+ CheckRtpmap("121", SdpRtpmapAttributeList::kVP9, "VP9", 90000, 0,
+ videosec.GetFormats()[1], videoRtpmap);
+
+ CheckRtpmap("122", SdpRtpmapAttributeList::kRed, "red", 90000, 0,
+ videosec.GetFormats()[2], videoRtpmap);
+
+ CheckRtpmap("123", SdpRtpmapAttributeList::kUlpfec, "ulpfec", 90000, 0,
+ videosec.GetFormats()[3], videoRtpmap);
+}
+
+static const std::string kAudioWithTelephoneEvent =
+ "v=0" CRLF "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF "s=SIP Call" CRLF
+ "c=IN IP4 198.51.100.7" CRLF "t=0 0" CRLF
+ "m=audio 9 RTP/SAVPF 109 9 0 8 101" CRLF "c=IN IP4 0.0.0.0" CRLF
+ "a=mid:first" CRLF "a=rtpmap:109 opus/48000/2" CRLF
+ "a=fmtp:109 maxplaybackrate=32000;stereo=1" CRLF "a=ptime:20" CRLF
+ "a=maxptime:20" CRLF "a=rtpmap:9 G722/8000" CRLF "a=rtpmap:0 PCMU/8000" CRLF
+ "a=rtpmap:8 PCMA/8000" CRLF "a=rtpmap:101 telephone-event/8000" CRLF;
+
+TEST_P(NewSdpTest, CheckTelephoneEventNoFmtp) {
+ ParseSdp(kAudioWithTelephoneEvent);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kFmtpAttribute));
+ auto audio_format_params =
+ Sdp()->GetMediaSection(0).GetAttributeList().GetFmtp().mFmtps;
+ ASSERT_EQ(1U, audio_format_params.size());
+
+ // make sure we don't get a fmtp for codec 101
+ for (size_t i = 0; i < audio_format_params.size(); ++i) {
+ ASSERT_NE("101", audio_format_params[i].format);
+ }
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventWithDefaultEvents) {
+ ParseSdp(kAudioWithTelephoneEvent + "a=fmtp:101 0-15" CRLF);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventWithBadCharacter) {
+ ParseSdp(kAudioWithTelephoneEvent + "a=fmtp:101 0-5." CRLF);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventIncludingCommas) {
+ ParseSdp(kAudioWithTelephoneEvent + "a=fmtp:101 0-15,66,67" CRLF);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ CheckDtmfFmtp("0-15,66,67");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventComplexEvents) {
+ ParseSdp(kAudioWithTelephoneEvent + "a=fmtp:101 0,1,2-4,5-15,66,67" CRLF);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ CheckDtmfFmtp("0,1,2-4,5-15,66,67");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventNoHyphen) {
+ ParseSdp(kAudioWithTelephoneEvent + "a=fmtp:101 5,6,7" CRLF);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ CheckDtmfFmtp("5,6,7");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventOnlyZero) {
+ ParseSdp(kAudioWithTelephoneEvent + "a=fmtp:101 0" CRLF);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ CheckDtmfFmtp("0");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventOnlyOne) {
+ ParseSdp(kAudioWithTelephoneEvent + "a=fmtp:101 1" CRLF);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ CheckDtmfFmtp("1");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadThreeDigit) {
+ ParseSdp(kAudioWithTelephoneEvent + "a=fmtp:101 123" CRLF);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ // check for the default dtmf tones
+ CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadThreeDigitWithHyphen) {
+ ParseSdp(kAudioWithTelephoneEvent + "a=fmtp:101 0-123" CRLF);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ // check for the default dtmf tones
+ CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadLeadingHyphen) {
+ ParseSdp(kAudioWithTelephoneEvent + "a=fmtp:101 -12" CRLF);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ // check for the default dtmf tones
+ CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadTrailingHyphen) {
+ ParseSdp(kAudioWithTelephoneEvent + "a=fmtp:101 12-" CRLF, false);
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadTrailingHyphenInMiddle) {
+ ParseSdp(kAudioWithTelephoneEvent + "a=fmtp:101 1,12-,4" CRLF, false);
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadLeadingComma) {
+ ParseSdp(kAudioWithTelephoneEvent + "a=fmtp:101 ,2,3" CRLF);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ // check for the default dtmf tones
+ CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadMultipleLeadingComma) {
+ ParseSdp(kAudioWithTelephoneEvent + "a=fmtp:101 ,,,2,3" CRLF);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ // check for the default dtmf tones
+ CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadConsecutiveCommas) {
+ ParseSdp(kAudioWithTelephoneEvent + "a=fmtp:101 1,,,,,,,,3" CRLF);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ // check for the default dtmf tones
+ CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadTrailingComma) {
+ ParseSdp(kAudioWithTelephoneEvent + "a=fmtp:101 1,2,3," CRLF);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ // check for the default dtmf tones
+ CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadTwoHyphens) {
+ ParseSdp(kAudioWithTelephoneEvent + "a=fmtp:101 1-2-3" CRLF);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ // check for the default dtmf tones
+ CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadSixDigit) {
+ ParseSdp(kAudioWithTelephoneEvent + "a=fmtp:101 112233" CRLF);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ // check for the default dtmf tones
+ CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadRangeReversed) {
+ ParseSdp(kAudioWithTelephoneEvent + "a=fmtp:101 33-2" CRLF);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ // check for the default dtmf tones
+ CheckDtmfFmtp("0-15");
+}
+
+static const std::string kVideoWithRedAndUlpfecSdp =
+ "v=0" CRLF "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF "s=SIP Call" CRLF
+ "c=IN IP4 198.51.100.7" CRLF "t=0 0" CRLF
+ "m=video 9 RTP/SAVPF 97 120 121 122 123" CRLF "c=IN IP6 ::1" CRLF
+ "a=fingerprint:sha-1 "
+ "DF:FA:FB:08:3B:3C:54:1D:D7:D4:05:77:A0:72:9B:14:08:6D:0F:4C" CRLF
+ "a=rtpmap:97 H264/90000" CRLF "a=fmtp:97 profile-level-id=42a01e" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF "a=fmtp:120 max-fs=3600;max-fr=30" CRLF
+ "a=rtpmap:121 VP9/90000" CRLF "a=fmtp:121 max-fs=3600;max-fr=30" CRLF
+ "a=rtpmap:122 red/90000" CRLF "a=rtpmap:123 ulpfec/90000" CRLF;
+
+TEST_P(NewSdpTest, CheckRedNoFmtp) {
+ ParseSdp(kVideoWithRedAndUlpfecSdp);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kFmtpAttribute));
+ auto video_format_params =
+ Sdp()->GetMediaSection(0).GetAttributeList().GetFmtp().mFmtps;
+ ASSERT_EQ(3U, video_format_params.size());
+
+ // make sure we don't get a fmtp for codec 122
+ for (size_t i = 0; i < video_format_params.size(); ++i) {
+ ASSERT_NE("122", video_format_params[i].format);
+ }
+}
+
+TEST_P(NewSdpTest, CheckRedEmptyFmtp) {
+ // if serializing and re-parsing, we expect errors
+ if (::testing::get<0>(GetParam())) {
+ ParseSdp(kVideoWithRedAndUlpfecSdp + "a=fmtp:122" CRLF, false);
+ } else {
+ ParseSdp(kVideoWithRedAndUlpfecSdp + "a=fmtp:122" CRLF, false);
+
+ if (ResultsAreFromSipcc()) {
+ ASSERT_NE(0U, ParseErrorCount());
+ } else {
+ // This is the branch for the rust parser to check for warnings, as the
+ // rust parser will give a warning at that point instead of an error.
+ ASSERT_NE(0U, ParseWarningCount());
+ }
+ }
+
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kFmtpAttribute));
+ auto video_format_params =
+ Sdp()->GetMediaSection(0).GetAttributeList().GetFmtp().mFmtps;
+ ASSERT_EQ(3U, video_format_params.size());
+
+ // make sure we don't get a fmtp for codec 122
+ for (size_t i = 0; i < video_format_params.size(); ++i) {
+ ASSERT_NE("122", video_format_params[i].format);
+ }
+}
+
+TEST_P(NewSdpTest, CheckRedFmtpWith2Codecs) {
+ ParseSdp(kVideoWithRedAndUlpfecSdp + "a=fmtp:122 120/121" CRLF);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kFmtpAttribute));
+ auto video_format_params =
+ Sdp()->GetMediaSection(0).GetAttributeList().GetFmtp().mFmtps;
+ ASSERT_EQ(4U, video_format_params.size());
+
+ ASSERT_EQ("122", video_format_params[3].format);
+ ASSERT_TRUE(!!video_format_params[3].parameters);
+ ASSERT_EQ(SdpRtpmapAttributeList::kRed,
+ video_format_params[3].parameters->codec_type);
+ const SdpFmtpAttributeList::RedParameters* red_parameters(
+ static_cast<SdpFmtpAttributeList::RedParameters*>(
+ video_format_params[3].parameters.get()));
+ ASSERT_EQ(2U, red_parameters->encodings.size());
+ ASSERT_EQ(120U, red_parameters->encodings[0]);
+ ASSERT_EQ(121U, red_parameters->encodings[1]);
+}
+
+TEST_P(NewSdpTest, CheckRedFmtpWith3Codecs) {
+ ParseSdp(kVideoWithRedAndUlpfecSdp + "a=fmtp:122 120/121/123" CRLF);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kFmtpAttribute));
+ auto video_format_params =
+ Sdp()->GetMediaSection(0).GetAttributeList().GetFmtp().mFmtps;
+ ASSERT_EQ(4U, video_format_params.size());
+
+ ASSERT_EQ("122", video_format_params[3].format);
+ ASSERT_TRUE(!!video_format_params[3].parameters);
+ ASSERT_EQ(SdpRtpmapAttributeList::kRed,
+ video_format_params[3].parameters->codec_type);
+ const SdpFmtpAttributeList::RedParameters* red_parameters(
+ static_cast<SdpFmtpAttributeList::RedParameters*>(
+ video_format_params[3].parameters.get()));
+ ASSERT_EQ(3U, red_parameters->encodings.size());
+ ASSERT_EQ(120U, red_parameters->encodings[0]);
+ ASSERT_EQ(121U, red_parameters->encodings[1]);
+ ASSERT_EQ(123U, red_parameters->encodings[2]);
+}
+
+const std::string kH264AudioVideoOffer =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "a=ice-ufrag:4a799b2e" CRLF
+ "a=ice-pwd:e4cc12a910f106a0a744719425510e17" CRLF "a=ice-lite" CRLF
+ "a=msid-semantic:WMS stream streama" CRLF
+ "a=fingerprint:sha-256 "
+ "DF:2E:AC:8A:FD:0A:8E:99:BF:5D:E8:3C:E7:FA:FB:08:3B:3C:54:1D:D7:D4:05:77:"
+ "A0:72:9B:14:08:6D:0F:4C" CRLF "a=group:BUNDLE first second" CRLF
+ "a=group:BUNDLE third" CRLF "a=group:LS first third" CRLF
+ "m=audio 9 RTP/SAVPF 109 9 0 8 101" CRLF "c=IN IP4 0.0.0.0" CRLF
+ "a=mid:first" CRLF "a=rtpmap:109 opus/48000/2" CRLF "a=ptime:20" CRLF
+ "a=maxptime:20" CRLF "a=rtpmap:9 G722/8000" CRLF "a=rtpmap:0 PCMU/8000" CRLF
+ "a=rtpmap:8 PCMA/8000" CRLF "a=rtpmap:101 telephone-event/8000" CRLF
+ "a=fmtp:109 maxplaybackrate=32000;stereo=1;useinbandfec=1" CRLF
+ "a=fmtp:101 0-15,66,32-34,67" CRLF "a=ice-ufrag:00000000" CRLF
+ "a=ice-pwd:0000000000000000000000000000000" CRLF "a=sendonly" CRLF
+ "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level" CRLF
+ "a=setup:actpass" CRLF "a=rtcp-mux" CRLF "a=msid:stream track" CRLF
+ "a=candidate:0 1 UDP 2130379007 10.0.0.36 62453 typ host" CRLF
+ "a=candidate:2 1 UDP 1694236671 24.6.134.204 62453 typ srflx raddr "
+ "10.0.0.36 rport 62453" CRLF
+ "a=candidate:3 1 UDP 100401151 162.222.183.171 49761 typ relay raddr "
+ "162.222.183.171 rport 49761" CRLF
+ "a=candidate:6 1 UDP 16515071 162.222.183.171 51858 typ relay raddr "
+ "162.222.183.171 rport 51858" CRLF
+ "a=candidate:3 2 UDP 100401150 162.222.183.171 62454 typ relay raddr "
+ "162.222.183.171 rport 62454" CRLF
+ "a=candidate:2 2 UDP 1694236670 24.6.134.204 55428 typ srflx raddr "
+ "10.0.0.36 rport 55428" CRLF
+ "a=candidate:6 2 UDP 16515070 162.222.183.171 50340 typ relay raddr "
+ "162.222.183.171 rport 50340" CRLF
+ "a=candidate:0 2 UDP 2130379006 10.0.0.36 55428 typ host" CRLF
+ "m=video 9 RTP/SAVPF 97 98 120" CRLF "c=IN IP6 ::1" CRLF "a=mid:second" CRLF
+ "a=rtpmap:97 H264/90000" CRLF "a=fmtp:97 profile-level-id=42a01e" CRLF
+ "a=rtpmap:98 H264/90000" CRLF
+ "a=fmtp:98 "
+ "PROFILE=0;LEVEL=0;parameter-add=1;profile-level-id=42a00d;packetization-"
+ "mode=1;level-asymmetry-allowed=1;max-mbps=42000;max-fs=1400;max-cpb=1000;"
+ "max-dpb=1000;max-br=180000;usedtx=0;stereo=0;useinbandfec=0;cbr=0" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF "a=fmtp:120 max-fs=3601;max-fr=31" CRLF
+ "a=recvonly" CRLF "a=setup:active" CRLF "a=rtcp-mux" CRLF
+ "a=msid:streama tracka" CRLF "a=msid:streamb trackb" CRLF
+ "a=candidate:0 1 UDP 2130379007 10.0.0.36 59530 typ host" CRLF
+ "a=candidate:0 2 UDP 2130379006 10.0.0.36 64378 typ host" CRLF
+ "a=candidate:2 2 UDP 1694236670 24.6.134.204 64378 typ srflx raddr "
+ "10.0.0.36 rport 64378" CRLF
+ "a=candidate:6 2 UDP 16515070 162.222.183.171 64941 typ relay raddr "
+ "162.222.183.171 rport 64941" CRLF
+ "a=candidate:6 1 UDP 16515071 162.222.183.171 64800 typ relay raddr "
+ "162.222.183.171 rport 64800" CRLF
+ "a=candidate:2 1 UDP 1694236671 24.6.134.204 59530 typ srflx raddr "
+ "10.0.0.36 rport 59530" CRLF
+ "a=candidate:3 1 UDP 100401151 162.222.183.171 62935 typ relay raddr "
+ "162.222.183.171 rport 62935" CRLF
+ "a=candidate:3 2 UDP 100401150 162.222.183.171 61026 typ relay raddr "
+ "162.222.183.171 rport 61026" CRLF "m=audio 9 RTP/SAVPF 0" CRLF
+ "a=mid:third" CRLF "a=rtpmap:0 PCMU/8000" CRLF "a=msid:noappdata" CRLF;
+
+TEST_P(NewSdpTest, CheckFormatParameters) {
+ ParseSdp(kH264AudioVideoOffer);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(3U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kFmtpAttribute));
+ auto audio_format_params =
+ Sdp()->GetMediaSection(0).GetAttributeList().GetFmtp().mFmtps;
+ ASSERT_EQ(2U, audio_format_params.size());
+ ASSERT_EQ("109", audio_format_params[0].format);
+ ASSERT_TRUE(!!audio_format_params[0].parameters);
+ const SdpFmtpAttributeList::OpusParameters* opus_parameters =
+ static_cast<SdpFmtpAttributeList::OpusParameters*>(
+ audio_format_params[0].parameters.get());
+ ASSERT_EQ(32000U, opus_parameters->maxplaybackrate);
+ ASSERT_EQ(1U, opus_parameters->stereo);
+ ASSERT_EQ(1U, opus_parameters->useInBandFec);
+ ASSERT_EQ(0U, opus_parameters->maxAverageBitrate);
+ ASSERT_EQ(0U, opus_parameters->useDTX);
+ ASSERT_EQ(0U, opus_parameters->useCbr);
+ ASSERT_EQ(0U, opus_parameters->frameSizeMs);
+ ASSERT_EQ(0U, opus_parameters->minFrameSizeMs);
+ ASSERT_EQ(0U, opus_parameters->maxFrameSizeMs);
+ ASSERT_EQ("101", audio_format_params[1].format);
+ ASSERT_TRUE(!!audio_format_params[1].parameters);
+ const SdpFmtpAttributeList::TelephoneEventParameters* te_parameters =
+ static_cast<SdpFmtpAttributeList::TelephoneEventParameters*>(
+ audio_format_params[1].parameters.get());
+ ASSERT_NE(0U, te_parameters->dtmfTones.size());
+ ASSERT_EQ("0-15,66,32-34,67", te_parameters->dtmfTones);
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kFmtpAttribute));
+ auto video_format_params =
+ Sdp()->GetMediaSection(1).GetAttributeList().GetFmtp().mFmtps;
+ ASSERT_EQ(3U, video_format_params.size());
+ ASSERT_EQ("97", video_format_params[0].format);
+ ASSERT_TRUE(!!video_format_params[0].parameters);
+ ASSERT_EQ(SdpRtpmapAttributeList::kH264,
+ video_format_params[0].parameters->codec_type);
+ const SdpFmtpAttributeList::H264Parameters* h264_parameters(
+ static_cast<SdpFmtpAttributeList::H264Parameters*>(
+ video_format_params[0].parameters.get()));
+ ASSERT_EQ((uint32_t)0x42a01e, h264_parameters->profile_level_id);
+ ASSERT_EQ(0U, h264_parameters->packetization_mode);
+ ASSERT_FALSE(static_cast<bool>(h264_parameters->level_asymmetry_allowed));
+ ASSERT_EQ(0U, h264_parameters->max_mbps);
+ ASSERT_EQ(0U, h264_parameters->max_fs);
+ ASSERT_EQ(0U, h264_parameters->max_cpb);
+ ASSERT_EQ(0U, h264_parameters->max_dpb);
+ ASSERT_EQ(0U, h264_parameters->max_br);
+
+ ASSERT_EQ("98", video_format_params[1].format);
+ ASSERT_TRUE(!!video_format_params[1].parameters);
+ ASSERT_EQ(SdpRtpmapAttributeList::kH264,
+ video_format_params[1].parameters->codec_type);
+ h264_parameters = static_cast<SdpFmtpAttributeList::H264Parameters*>(
+ video_format_params[1].parameters.get());
+ ASSERT_EQ((uint32_t)0x42a00d, h264_parameters->profile_level_id);
+ ASSERT_EQ(1U, h264_parameters->packetization_mode);
+ ASSERT_TRUE(static_cast<bool>(h264_parameters->level_asymmetry_allowed));
+ ASSERT_EQ(42000U, h264_parameters->max_mbps);
+ ASSERT_EQ(1400U, h264_parameters->max_fs);
+ ASSERT_EQ(1000U, h264_parameters->max_cpb);
+ ASSERT_EQ(1000U, h264_parameters->max_dpb);
+ ASSERT_EQ(180000U, h264_parameters->max_br);
+
+ ASSERT_EQ("120", video_format_params[2].format);
+ ASSERT_TRUE(!!video_format_params[2].parameters);
+ ASSERT_EQ(SdpRtpmapAttributeList::kVP8,
+ video_format_params[2].parameters->codec_type);
+ const SdpFmtpAttributeList::VP8Parameters* vp8_parameters =
+ static_cast<SdpFmtpAttributeList::VP8Parameters*>(
+ video_format_params[2].parameters.get());
+ ASSERT_EQ(3601U, vp8_parameters->max_fs);
+ ASSERT_EQ(31U, vp8_parameters->max_fr);
+
+ ASSERT_FALSE(Sdp()->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kFmtpAttribute));
+}
+
+TEST_P(NewSdpTest, CheckPtime) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_EQ(20U, Sdp()->GetMediaSection(0).GetAttributeList().GetPtime());
+ ASSERT_FALSE(Sdp()->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kPtimeAttribute));
+}
+
+TEST_P(NewSdpTest, CheckFlags) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(
+ Sdp()->GetAttributeList().HasAttribute(SdpAttribute::kIceLiteAttribute));
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kIceLiteAttribute));
+ ASSERT_FALSE(Sdp()->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kIceLiteAttribute));
+ ASSERT_FALSE(Sdp()->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kIceLiteAttribute));
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpMuxAttribute));
+ ASSERT_FALSE(Sdp()->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpMuxAttribute));
+
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kBundleOnlyAttribute));
+ ASSERT_TRUE(Sdp()->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kBundleOnlyAttribute));
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kEndOfCandidatesAttribute));
+ ASSERT_TRUE(Sdp()->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kEndOfCandidatesAttribute));
+ ASSERT_FALSE(Sdp()->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kEndOfCandidatesAttribute));
+}
+
+TEST_P(NewSdpTest, CheckConnectionLines) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(3U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ const SdpConnection& conn1 = Sdp()->GetMediaSection(0).GetConnection();
+ ASSERT_EQ(sdp::kIPv4, conn1.GetAddrType());
+ ASSERT_EQ("0.0.0.0", conn1.GetAddress());
+ ASSERT_EQ(0U, conn1.GetTtl());
+ ASSERT_EQ(0U, conn1.GetCount());
+
+ const SdpConnection& conn2 = Sdp()->GetMediaSection(1).GetConnection();
+ ASSERT_EQ(sdp::kIPv6, conn2.GetAddrType());
+ ASSERT_EQ("::1", conn2.GetAddress());
+ ASSERT_EQ(0U, conn2.GetTtl());
+ ASSERT_EQ(0U, conn2.GetCount());
+
+ // tests that we can fall through to session level as appropriate
+ const SdpConnection& conn3 = Sdp()->GetMediaSection(2).GetConnection();
+ ASSERT_EQ(sdp::kIPv4, conn3.GetAddrType());
+ ASSERT_EQ("224.0.0.1", conn3.GetAddress());
+ ASSERT_EQ(100U, conn3.GetTtl());
+ ASSERT_EQ(12U, conn3.GetCount());
+}
+
+TEST_P(NewSdpTest, CheckDirections) {
+ ParseSdp(kBasicAudioVideoOffer);
+
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(SdpDirectionAttribute::kSendonly,
+ Sdp()->GetMediaSection(0).GetAttributeList().GetDirection());
+ ASSERT_EQ(SdpDirectionAttribute::kRecvonly,
+ Sdp()->GetMediaSection(1).GetAttributeList().GetDirection());
+ ASSERT_EQ(SdpDirectionAttribute::kSendrecv,
+ Sdp()->GetMediaSection(2).GetAttributeList().GetDirection());
+}
+
+TEST_P(NewSdpTest, CheckCandidates) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(3U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kCandidateAttribute));
+ auto audio_candidates =
+ Sdp()->GetMediaSection(0).GetAttributeList().GetCandidate();
+ ASSERT_EQ(8U, audio_candidates.size());
+ ASSERT_EQ("0 1 UDP 2130379007 10.0.0.36 62453 typ host", audio_candidates[0]);
+ ASSERT_EQ(
+ "2 1 UDP 1694236671 24.6.134.204 62453 typ srflx raddr 10.0.0.36 rport "
+ "62453",
+ audio_candidates[1]);
+ ASSERT_EQ(
+ "3 1 UDP 100401151 162.222.183.171 49761 typ relay raddr 162.222.183.171 "
+ "rport 49761",
+ audio_candidates[2]);
+ ASSERT_EQ(
+ "6 1 UDP 16515071 162.222.183.171 51858 typ relay raddr 162.222.183.171 "
+ "rport 51858",
+ audio_candidates[3]);
+ ASSERT_EQ(
+ "3 2 UDP 100401150 162.222.183.171 62454 typ relay raddr 162.222.183.171 "
+ "rport 62454",
+ audio_candidates[4]);
+ ASSERT_EQ(
+ "2 2 UDP 1694236670 24.6.134.204 55428 typ srflx raddr 10.0.0.36 rport "
+ "55428",
+ audio_candidates[5]);
+ ASSERT_EQ(
+ "6 2 UDP 16515070 162.222.183.171 50340 typ relay raddr 162.222.183.171 "
+ "rport 50340",
+ audio_candidates[6]);
+ ASSERT_EQ("0 2 UDP 2130379006 10.0.0.36 55428 typ host", audio_candidates[7]);
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kCandidateAttribute));
+ auto video_candidates =
+ Sdp()->GetMediaSection(1).GetAttributeList().GetCandidate();
+ ASSERT_EQ(8U, video_candidates.size());
+ ASSERT_EQ("0 1 UDP 2130379007 10.0.0.36 59530 typ host", video_candidates[0]);
+ ASSERT_EQ("0 2 UDP 2130379006 10.0.0.36 64378 typ host", video_candidates[1]);
+ ASSERT_EQ(
+ "2 2 UDP 1694236670 24.6.134.204 64378 typ srflx raddr 10.0.0.36 rport "
+ "64378",
+ video_candidates[2]);
+ ASSERT_EQ(
+ "6 2 UDP 16515070 162.222.183.171 64941 typ relay raddr 162.222.183.171 "
+ "rport 64941",
+ video_candidates[3]);
+ ASSERT_EQ(
+ "6 1 UDP 16515071 162.222.183.171 64800 typ relay raddr 162.222.183.171 "
+ "rport 64800",
+ video_candidates[4]);
+ ASSERT_EQ(
+ "2 1 UDP 1694236671 24.6.134.204 59530 typ srflx raddr 10.0.0.36 rport "
+ "59530",
+ video_candidates[5]);
+ ASSERT_EQ(
+ "3 1 UDP 100401151 162.222.183.171 62935 typ relay raddr 162.222.183.171 "
+ "rport 62935",
+ video_candidates[6]);
+ ASSERT_EQ(
+ "3 2 UDP 100401150 162.222.183.171 61026 typ relay raddr 162.222.183.171 "
+ "rport 61026",
+ video_candidates[7]);
+
+ ASSERT_FALSE(Sdp()->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kCandidateAttribute));
+}
+
+TEST_P(NewSdpTest, CheckMid) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_EQ("first", Sdp()->GetMediaSection(0).GetAttributeList().GetMid());
+ ASSERT_EQ("second", Sdp()->GetMediaSection(1).GetAttributeList().GetMid());
+ ASSERT_EQ("third", Sdp()->GetMediaSection(2).GetAttributeList().GetMid());
+}
+
+TEST_P(NewSdpTest, CheckMsid) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(Sdp()->GetAttributeList().HasAttribute(
+ SdpAttribute::kMsidSemanticAttribute));
+ auto semantics = Sdp()->GetAttributeList().GetMsidSemantic().mMsidSemantics;
+ ASSERT_EQ(2U, semantics.size());
+ ASSERT_EQ("WMS", semantics[0].semantic);
+ ASSERT_EQ(2U, semantics[0].msids.size());
+ ASSERT_EQ("stream", semantics[0].msids[0]);
+ ASSERT_EQ("streama", semantics[0].msids[1]);
+ ASSERT_EQ("foo", semantics[1].semantic);
+ ASSERT_EQ(1U, semantics[1].msids.size());
+ ASSERT_EQ("stream", semantics[1].msids[0]);
+
+ const SdpMsidAttributeList& msids1 =
+ Sdp()->GetMediaSection(0).GetAttributeList().GetMsid();
+ ASSERT_EQ(1U, msids1.mMsids.size());
+ ASSERT_EQ("stream", msids1.mMsids[0].identifier);
+ ASSERT_EQ("track", msids1.mMsids[0].appdata);
+ const SdpMsidAttributeList& msids2 =
+ Sdp()->GetMediaSection(1).GetAttributeList().GetMsid();
+ ASSERT_EQ(2U, msids2.mMsids.size());
+ ASSERT_EQ("streama", msids2.mMsids[0].identifier);
+ ASSERT_EQ("tracka", msids2.mMsids[0].appdata);
+ ASSERT_EQ("streamb", msids2.mMsids[1].identifier);
+ ASSERT_EQ("trackb", msids2.mMsids[1].appdata);
+ const SdpMsidAttributeList& msids3 =
+ Sdp()->GetMediaSection(2).GetAttributeList().GetMsid();
+ ASSERT_EQ(1U, msids3.mMsids.size());
+ ASSERT_EQ("noappdata", msids3.mMsids[0].identifier);
+ ASSERT_EQ("", msids3.mMsids[0].appdata);
+}
+
+TEST_P(NewSdpTest, CheckManyMsidSemantics) {
+ if (::testing::get<0>(GetParam())) {
+ return;
+ }
+ std::ostringstream msid;
+ msid << "a=msid-semantic: foo";
+ for (size_t i = 0; i < SDP_MAX_MEDIA_STREAMS + 1; ++i) {
+ msid << " " << i;
+ }
+ msid << CRLF;
+
+ std::string offer =
+ "v=0" CRLF "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF "s=SIP Call" CRLF
+ "c=IN IP4 198.51.100.7" CRLF "t=0 0" CRLF;
+ offer += msid.str();
+ offer += "m=video 56436 RTP/SAVPF 120" CRLF "b=CT:1000" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF;
+
+ ParseSdp(offer);
+ auto semantics = Sdp()->GetAttributeList().GetMsidSemantic().mMsidSemantics;
+ ASSERT_EQ(1U, semantics.size());
+
+ // Only sipcc is limited by SDP_MAX_MEDIA_STREAMS, the Rust parser
+ // will parse all of the msids.
+ ASSERT_GE(semantics[0].msids.size(),
+ static_cast<size_t>(SDP_MAX_MEDIA_STREAMS));
+}
+
+TEST_P(NewSdpTest, CheckRid) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!Sdp());
+ ASSERT_EQ(3U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ ASSERT_FALSE(
+ Sdp()->GetAttributeList().HasAttribute(SdpAttribute::kRidAttribute));
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kRidAttribute));
+ ASSERT_TRUE(Sdp()->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kRidAttribute));
+ ASSERT_FALSE(Sdp()->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kRidAttribute));
+
+ const SdpRidAttributeList& rids =
+ Sdp()->GetMediaSection(1).GetAttributeList().GetRid();
+
+ ASSERT_EQ(2U, rids.mRids.size());
+
+ ASSERT_EQ("bar", rids.mRids[0].id);
+ ASSERT_EQ(sdp::kRecv, rids.mRids[0].direction);
+ ASSERT_EQ(1U, rids.mRids[0].formats.size());
+ ASSERT_EQ(120U, rids.mRids[0].formats[0]);
+ ASSERT_EQ(800U, rids.mRids[0].constraints.maxWidth);
+ ASSERT_EQ(600U, rids.mRids[0].constraints.maxHeight);
+
+ ASSERT_EQ("bar123", rids.mRids[1].id);
+ ASSERT_EQ(sdp::kRecv, rids.mRids[1].direction);
+ ASSERT_EQ(0U, rids.mRids[1].formats.size());
+ ASSERT_EQ(1920U, rids.mRids[1].constraints.maxWidth);
+ ASSERT_EQ(1080U, rids.mRids[1].constraints.maxHeight);
+}
+
+TEST_P(NewSdpTest, CheckMediaLevelIceUfrag) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(3U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kIceUfragAttribute, true));
+ ASSERT_EQ("00000000",
+ Sdp()->GetMediaSection(0).GetAttributeList().GetIceUfrag());
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kIceUfragAttribute, false));
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kIceUfragAttribute, true));
+ ASSERT_EQ("4a799b2e",
+ Sdp()->GetMediaSection(1).GetAttributeList().GetIceUfrag());
+}
+
+TEST_P(NewSdpTest, CheckMediaLevelIcePwd) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(3U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kIcePwdAttribute));
+ ASSERT_EQ("0000000000000000000000000000000",
+ Sdp()->GetMediaSection(0).GetAttributeList().GetIcePwd());
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kIcePwdAttribute));
+ ASSERT_EQ("e4cc12a910f106a0a744719425510e17",
+ Sdp()->GetMediaSection(1).GetAttributeList().GetIcePwd());
+}
+
+TEST_P(NewSdpTest, CheckGroups) {
+ ParseSdp(kBasicAudioVideoOffer);
+ const SdpGroupAttributeList& group = Sdp()->GetAttributeList().GetGroup();
+ const SdpGroupAttributeList::Group& group1 = group.mGroups[0];
+ ASSERT_EQ(SdpGroupAttributeList::kBundle, group1.semantics);
+ ASSERT_EQ(2U, group1.tags.size());
+ ASSERT_EQ("first", group1.tags[0]);
+ ASSERT_EQ("second", group1.tags[1]);
+
+ const SdpGroupAttributeList::Group& group2 = group.mGroups[1];
+ ASSERT_EQ(SdpGroupAttributeList::kBundle, group2.semantics);
+ ASSERT_EQ(1U, group2.tags.size());
+ ASSERT_EQ("third", group2.tags[0]);
+
+ const SdpGroupAttributeList::Group& group3 = group.mGroups[2];
+ ASSERT_EQ(SdpGroupAttributeList::kLs, group3.semantics);
+ ASSERT_EQ(2U, group3.tags.size());
+ ASSERT_EQ("first", group3.tags[0]);
+ ASSERT_EQ("third", group3.tags[1]);
+}
+
+TEST_P(NewSdpTest, CheckManyGroups) {
+ std::ostringstream bundle;
+ bundle << "a=group:BUNDLE";
+ for (size_t i = 0; i < SDP_MAX_MEDIA_STREAMS + 1; ++i) {
+ bundle << " " << i;
+ }
+ bundle << CRLF;
+
+ std::string offer =
+ "v=0" CRLF "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF "s=SIP Call" CRLF
+ "c=IN IP4 198.51.100.7" CRLF "t=0 0" CRLF;
+ offer += bundle.str();
+ offer += "m=video 56436 RTP/SAVPF 120" CRLF "b=CT:1000" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF;
+ // Rust will reserialize what Sipcc serializes to, but otherwise they will
+ // differ.
+ ParseSdp(offer, true,
+ ::testing::get<0>(GetParam()) && ::testing::get<1>(GetParam()));
+ const SdpGroupAttributeList& group = Sdp()->GetAttributeList().GetGroup();
+ const SdpGroupAttributeList::Group& group1 = group.mGroups[0];
+ ASSERT_EQ(SdpGroupAttributeList::kBundle, group1.semantics);
+
+ // Only sipcc is limited by SDP_MAX_MEDIA_STREAMS, the Rust parser
+ // will parse all of the groups.
+ ASSERT_GE(group1.tags.size(), static_cast<size_t>(SDP_MAX_MEDIA_STREAMS));
+}
+
+// SDP from a basic A/V call with data channel FFX/FFX
+const std::string kBasicAudioVideoDataOffer =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 27987 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "t=0 0" CRLF "a=ice-ufrag:8a39d2ae" CRLF
+ "a=ice-pwd:601d53aba51a318351b3ecf5ee00048f" CRLF
+ "a=fingerprint:sha-256 "
+ "30:FF:8E:2B:AC:9D:ED:70:18:10:67:C8:AE:9E:68:F3:86:53:51:B0:AC:31:B7:BE:"
+ "6D:CF:A4:2E:D3:6E:B4:28" CRLF "m=audio 9 RTP/SAVPF 109 9 0 8 101" CRLF
+ "c=IN IP4 0.0.0.0" CRLF "a=rtpmap:109 opus/48000/2" CRLF "a=ptime:20" CRLF
+ "a=rtpmap:9 G722/8000" CRLF "a=rtpmap:0 PCMU/8000" CRLF
+ "a=rtpmap:8 PCMA/8000" CRLF "a=rtpmap:101 telephone-event/8000" CRLF
+ "a=fmtp:101 0-15" CRLF "a=sendrecv" CRLF
+ "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level" CRLF
+ "a=extmap:2/sendonly some_extension" CRLF
+ "a=extmap:3 some_other_extension some_params some more params" CRLF
+ "a=setup:actpass" CRLF "a=rtcp-mux" CRLF
+ "m=video 9 RTP/SAVPF 120 126 97" CRLF "c=IN IP4 0.0.0.0" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF "a=rtpmap:126 H264/90000" CRLF
+ "a=fmtp:126 profile-level-id=42e01f;packetization-mode=1" CRLF
+ "a=rtpmap:97 H264/90000" CRLF "a=fmtp:97 profile-level-id=42e01f" CRLF
+ "a=sendrecv" CRLF
+ // sipcc barfs on this, despite that it is valid syntax
+ // Do we care about fixing?
+ //"a=rtcp-fb:120 ack" CRLF // Should be ignored by sipcc
+ "a=rtcp-fb:120 ack rpsi" CRLF "a=rtcp-fb:120 ack app foo" CRLF
+ "a=rtcp-fb:120 ack foo" CRLF // Should be ignored
+ "a=rtcp-fb:120 nack" CRLF "a=rtcp-fb:120 nack sli" CRLF
+ "a=rtcp-fb:120 nack pli" CRLF "a=rtcp-fb:120 nack rpsi" CRLF
+ "a=rtcp-fb:120 nack app foo" CRLF
+ "a=rtcp-fb:120 nack foo" CRLF // Should be ignored
+ "a=rtcp-fb:120 ccm fir" CRLF "a=rtcp-fb:120 ccm tmmbr" CRLF
+ "a=rtcp-fb:120 ccm tstr" CRLF "a=rtcp-fb:120 ccm vbcm" CRLF
+ "a=rtcp-fb:120 ccm foo" CRLF // Should be ignored
+ "a=rtcp-fb:120 trr-int 10" CRLF "a=rtcp-fb:120 goog-remb" CRLF
+ "a=rtcp-fb:120 foo" CRLF // Should be ignored
+ "a=rtcp-fb:126 nack" CRLF "a=rtcp-fb:126 nack pli" CRLF
+ "a=rtcp-fb:126 ccm fir" CRLF "a=rtcp-fb:97 nack" CRLF
+ "a=rtcp-fb:97 nack pli" CRLF "a=rtcp-fb:97 ccm fir" CRLF
+ "a=rtcp-fb:* ccm tmmbr" CRLF "a=rtcp-fb:120 transport-cc" CRLF
+ "a=setup:actpass" CRLF "a=rtcp-mux" CRLF
+ "m=application 9 DTLS/SCTP 5000" CRLF "c=IN IP4 0.0.0.0" CRLF
+ "a=sctpmap:5000 webrtc-datachannel 16" CRLF "a=setup:actpass" CRLF;
+
+TEST_P(NewSdpTest, BasicAudioVideoDataSdpParse) {
+ ParseSdp(kBasicAudioVideoDataOffer, true);
+ ASSERT_EQ(0U, ParseErrorCount())
+ << "Got parse errors: " << SerializeParseErrors();
+}
+
+TEST_P(NewSdpTest, CheckApplicationParameters) {
+ ParseSdp(kBasicAudioVideoDataOffer, true);
+ ASSERT_TRUE(!!Sdp());
+ ASSERT_EQ(3U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+ ASSERT_EQ(SdpMediaSection::kAudio, Sdp()->GetMediaSection(0).GetMediaType())
+ << "Wrong type for first media section";
+ ASSERT_EQ(SdpMediaSection::kVideo, Sdp()->GetMediaSection(1).GetMediaType())
+ << "Wrong type for second media section";
+ ASSERT_EQ(SdpMediaSection::kApplication,
+ Sdp()->GetMediaSection(2).GetMediaType())
+ << "Wrong type for third media section";
+
+ ASSERT_EQ(SdpMediaSection::kDtlsSctp, Sdp()->GetMediaSection(2).GetProtocol())
+ << "Wrong protocol for application";
+ auto app_formats = Sdp()->GetMediaSection(2).GetFormats();
+ ASSERT_EQ(1U, app_formats.size()) << "Wrong number of formats for audio";
+ ASSERT_EQ("5000", app_formats[0]);
+
+ const SdpConnection& conn3 = Sdp()->GetMediaSection(2).GetConnection();
+ ASSERT_EQ(sdp::kIPv4, conn3.GetAddrType());
+ ASSERT_EQ("0.0.0.0", conn3.GetAddress());
+ ASSERT_EQ(0U, conn3.GetTtl());
+ ASSERT_EQ(0U, conn3.GetCount());
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kSetupAttribute));
+ ASSERT_EQ(SdpSetupAttribute::kActpass,
+ Sdp()->GetMediaSection(2).GetAttributeList().GetSetup().mRole);
+}
+
+TEST_P(NewSdpTest, CheckExtmap) {
+ ParseSdp(kBasicAudioVideoDataOffer, true);
+ ASSERT_TRUE(!!Sdp());
+ ASSERT_EQ(3U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kExtmapAttribute));
+
+ auto extmaps =
+ Sdp()->GetMediaSection(0).GetAttributeList().GetExtmap().mExtmaps;
+ ASSERT_EQ(3U, extmaps.size());
+
+ ASSERT_EQ(1U, extmaps[0].entry);
+ ASSERT_FALSE(extmaps[0].direction_specified);
+ ASSERT_EQ("urn:ietf:params:rtp-hdrext:ssrc-audio-level",
+ extmaps[0].extensionname);
+ ASSERT_EQ("", extmaps[0].extensionattributes);
+
+ ASSERT_EQ(2U, extmaps[1].entry);
+ ASSERT_TRUE(extmaps[1].direction_specified);
+ ASSERT_EQ(SdpDirectionAttribute::kSendonly, extmaps[1].direction);
+ ASSERT_EQ("some_extension", extmaps[1].extensionname);
+ ASSERT_EQ("", extmaps[1].extensionattributes);
+
+ ASSERT_EQ(3U, extmaps[2].entry);
+ ASSERT_FALSE(extmaps[2].direction_specified);
+ ASSERT_EQ("some_other_extension", extmaps[2].extensionname);
+ ASSERT_EQ("some_params some more params", extmaps[2].extensionattributes);
+}
+
+TEST_P(NewSdpTest, CheckRtcpFb) {
+ ParseSdp(kBasicAudioVideoDataOffer, true);
+ ASSERT_TRUE(!!Sdp());
+ ASSERT_EQ(3U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ auto& video_attrs = Sdp()->GetMediaSection(1).GetAttributeList();
+ ASSERT_TRUE(video_attrs.HasAttribute(SdpAttribute::kRtcpFbAttribute));
+ auto& rtcpfbs = video_attrs.GetRtcpFb().mFeedbacks;
+
+ if (ResultsAreFromSipcc()) {
+ ASSERT_EQ(21U, rtcpfbs.size());
+ } else {
+ ASSERT_EQ("WEBRTCSDP", mResults->ParserName());
+ std::stringstream os;
+ Sdp()->Serialize(os);
+ // Reserialized results should be the same
+ if (::testing::get<1>(GetParam())) {
+ ASSERT_EQ(21U, rtcpfbs.size());
+ } else {
+ ASSERT_EQ(21U, rtcpfbs.size());
+ }
+ }
+
+ CheckRtcpFb(rtcpfbs[0], "120", SdpRtcpFbAttributeList::kAck, "rpsi");
+ CheckRtcpFb(rtcpfbs[1], "120", SdpRtcpFbAttributeList::kAck, "app", "foo");
+ CheckRtcpFb(rtcpfbs[2], "120", SdpRtcpFbAttributeList::kNack, "");
+ CheckRtcpFb(rtcpfbs[3], "120", SdpRtcpFbAttributeList::kNack, "sli");
+ CheckRtcpFb(rtcpfbs[4], "120", SdpRtcpFbAttributeList::kNack, "pli");
+ CheckRtcpFb(rtcpfbs[5], "120", SdpRtcpFbAttributeList::kNack, "rpsi");
+ CheckRtcpFb(rtcpfbs[6], "120", SdpRtcpFbAttributeList::kNack, "app", "foo");
+ CheckRtcpFb(rtcpfbs[7], "120", SdpRtcpFbAttributeList::kCcm, "fir");
+ CheckRtcpFb(rtcpfbs[8], "120", SdpRtcpFbAttributeList::kCcm, "tmmbr");
+ CheckRtcpFb(rtcpfbs[9], "120", SdpRtcpFbAttributeList::kCcm, "tstr");
+ CheckRtcpFb(rtcpfbs[10], "120", SdpRtcpFbAttributeList::kCcm, "vbcm");
+ CheckRtcpFb(rtcpfbs[11], "120", SdpRtcpFbAttributeList::kTrrInt, "10");
+ CheckRtcpFb(rtcpfbs[12], "120", SdpRtcpFbAttributeList::kRemb, "");
+ CheckRtcpFb(rtcpfbs[13], "126", SdpRtcpFbAttributeList::kNack, "");
+ CheckRtcpFb(rtcpfbs[14], "126", SdpRtcpFbAttributeList::kNack, "pli");
+ CheckRtcpFb(rtcpfbs[15], "126", SdpRtcpFbAttributeList::kCcm, "fir");
+ CheckRtcpFb(rtcpfbs[16], "97", SdpRtcpFbAttributeList::kNack, "");
+ CheckRtcpFb(rtcpfbs[17], "97", SdpRtcpFbAttributeList::kNack, "pli");
+ CheckRtcpFb(rtcpfbs[18], "97", SdpRtcpFbAttributeList::kCcm, "fir");
+ CheckRtcpFb(rtcpfbs[19], "*", SdpRtcpFbAttributeList::kCcm, "tmmbr");
+ CheckRtcpFb(rtcpfbs[20], "120", SdpRtcpFbAttributeList::kTransportCC, "");
+}
+
+TEST_P(NewSdpTest, CheckRtcp) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!Sdp());
+ ASSERT_EQ(3U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ ASSERT_FALSE(
+ Sdp()->GetAttributeList().HasAttribute(SdpAttribute::kRtcpAttribute));
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpAttribute));
+ ASSERT_TRUE(Sdp()->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpAttribute));
+ ASSERT_FALSE(Sdp()->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpAttribute));
+
+ auto& rtcpAttr_0 = Sdp()->GetMediaSection(0).GetAttributeList().GetRtcp();
+ ASSERT_EQ(62454U, rtcpAttr_0.mPort);
+ ASSERT_EQ(sdp::kInternet, rtcpAttr_0.mNetType);
+ ASSERT_EQ(sdp::kIPv4, rtcpAttr_0.mAddrType);
+ ASSERT_EQ("162.222.183.171", rtcpAttr_0.mAddress);
+
+ auto& rtcpAttr_1 = Sdp()->GetMediaSection(1).GetAttributeList().GetRtcp();
+ ASSERT_EQ(61026U, rtcpAttr_1.mPort);
+ ASSERT_EQ("", rtcpAttr_1.mAddress);
+}
+
+TEST_P(NewSdpTest, CheckImageattr) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!Sdp());
+ ASSERT_EQ(3U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ ASSERT_FALSE(Sdp()->GetAttributeList().HasAttribute(
+ SdpAttribute::kImageattrAttribute));
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kImageattrAttribute));
+ ASSERT_TRUE(Sdp()->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kImageattrAttribute));
+ ASSERT_FALSE(Sdp()->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kImageattrAttribute));
+
+ const SdpImageattrAttributeList& imageattrs =
+ Sdp()->GetMediaSection(1).GetAttributeList().GetImageattr();
+
+ ASSERT_EQ(2U, imageattrs.mImageattrs.size());
+ const SdpImageattrAttributeList::Imageattr& imageattr_0(
+ imageattrs.mImageattrs[0]);
+ ASSERT_TRUE(imageattr_0.pt.isSome());
+ ASSERT_EQ(120U, *imageattr_0.pt);
+ ASSERT_TRUE(imageattr_0.sendAll);
+ ASSERT_TRUE(imageattr_0.recvAll);
+
+ const SdpImageattrAttributeList::Imageattr& imageattr_1(
+ imageattrs.mImageattrs[1]);
+ ASSERT_TRUE(imageattr_1.pt.isSome());
+ ASSERT_EQ(121U, *imageattr_1.pt);
+ ASSERT_FALSE(imageattr_1.sendAll);
+ ASSERT_FALSE(imageattr_1.recvAll);
+ ASSERT_EQ(1U, imageattr_1.sendSets.size());
+ ASSERT_EQ(1U, imageattr_1.sendSets[0].xRange.discreteValues.size());
+ ASSERT_EQ(640U, imageattr_1.sendSets[0].xRange.discreteValues.front());
+ ASSERT_EQ(1U, imageattr_1.sendSets[0].yRange.discreteValues.size());
+ ASSERT_EQ(480U, imageattr_1.sendSets[0].yRange.discreteValues.front());
+ ASSERT_EQ(1U, imageattr_1.recvSets.size());
+ ASSERT_EQ(1U, imageattr_1.recvSets[0].xRange.discreteValues.size());
+ ASSERT_EQ(640U, imageattr_1.recvSets[0].xRange.discreteValues.front());
+ ASSERT_EQ(1U, imageattr_1.recvSets[0].yRange.discreteValues.size());
+ ASSERT_EQ(480U, imageattr_1.recvSets[0].yRange.discreteValues.front());
+}
+
+TEST_P(NewSdpTest, CheckSimulcast) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!Sdp());
+ ASSERT_EQ(3U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ ASSERT_FALSE(Sdp()->GetAttributeList().HasAttribute(
+ SdpAttribute::kSimulcastAttribute));
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kSimulcastAttribute));
+ ASSERT_TRUE(Sdp()->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kSimulcastAttribute));
+ ASSERT_FALSE(Sdp()->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kSimulcastAttribute));
+
+ const SdpSimulcastAttribute& simulcast =
+ Sdp()->GetMediaSection(1).GetAttributeList().GetSimulcast();
+
+ ASSERT_EQ(2U, simulcast.recvVersions.size());
+ ASSERT_EQ(0U, simulcast.sendVersions.size());
+ ASSERT_EQ(1U, simulcast.recvVersions[0].choices.size());
+ ASSERT_EQ("bar", simulcast.recvVersions[0].choices[0].rid);
+ ASSERT_EQ(1U, simulcast.recvVersions[1].choices.size());
+ ASSERT_EQ("bar123", simulcast.recvVersions[1].choices[0].rid);
+}
+
+TEST_P(NewSdpTest, CheckSctpmap) {
+ ParseSdp(kBasicAudioVideoDataOffer);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(3U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ const SdpMediaSection& appsec = Sdp()->GetMediaSection(2);
+ ASSERT_TRUE(
+ appsec.GetAttributeList().HasAttribute(SdpAttribute::kSctpmapAttribute));
+ const SdpSctpmapAttributeList& sctpmap =
+ appsec.GetAttributeList().GetSctpmap();
+
+ ASSERT_EQ(1U, sctpmap.mSctpmaps.size())
+ << "Wrong number of sctpmap attributes";
+ ASSERT_EQ(1U, appsec.GetFormats().size());
+
+ // Need to know name of type
+ CheckSctpmap("5000", "webrtc-datachannel", 16, appsec.GetFormats()[0],
+ sctpmap);
+}
+
+TEST_P(NewSdpTest, CheckMaxPtime) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(3U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kMaxptimeAttribute));
+ ASSERT_EQ(Sdp()->GetMediaSection(0).GetAttributeList().GetMaxptime(), 20U);
+}
+
+const std::string kNewSctpportOfferDraft21 =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 27987 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "t=0 0" CRLF "a=ice-ufrag:8a39d2ae" CRLF
+ "a=ice-pwd:601d53aba51a318351b3ecf5ee00048f" CRLF
+ "a=fingerprint:sha-256 "
+ "30:FF:8E:2B:AC:9D:ED:70:18:10:67:C8:AE:9E:68:F3:86:53:51:B0:AC:31:B7:BE:"
+ "6D:CF:A4:2E:D3:6E:B4:28" CRLF
+ "m=application 9 UDP/DTLS/SCTP webrtc-datachannel" CRLF
+ "c=IN IP4 0.0.0.0" CRLF "a=sctp-port:5000" CRLF
+ "a=max-message-size:10000" CRLF "a=setup:actpass" CRLF;
+
+TEST_P(NewSdpTest, NewSctpportSdpParse) {
+ ParseSdp(kNewSctpportOfferDraft21, false);
+}
+
+INSTANTIATE_TEST_SUITE_P(RoundTripSerialize, NewSdpTest,
+ ::testing::Combine(::testing::Bool(),
+ ::testing::Bool()));
+
+const std::string kCandidateInSessionSDP =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "a=candidate:0 1 UDP 2130379007 10.0.0.36 62453 typ host" CRLF
+ "m=audio 9 RTP/SAVPF 109 9 0 8 101" CRLF "c=IN IP4 0.0.0.0" CRLF
+ "a=rtpmap:109 opus/48000/2" CRLF;
+
+// This may or may not parse, but if it does, the errant candidate attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckCandidateInSessionLevel) {
+ ParseSdp(kCandidateInSessionSDP, false);
+ if (Sdp()) {
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kCandidateAttribute));
+ ASSERT_FALSE(Sdp()->GetAttributeList().HasAttribute(
+ SdpAttribute::kCandidateAttribute));
+ }
+}
+
+const std::string kBundleOnlyInSessionSDP =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "a=bundle-only" CRLF "m=audio 9 RTP/SAVPF 109 9 0 8 101" CRLF
+ "c=IN IP4 0.0.0.0" CRLF "a=rtpmap:109 opus/48000/2" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckBundleOnlyInSessionLevel) {
+ ParseSdp(kBundleOnlyInSessionSDP, false);
+ if (Sdp()) {
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kBundleOnlyAttribute));
+ ASSERT_FALSE(Sdp()->GetAttributeList().HasAttribute(
+ SdpAttribute::kBundleOnlyAttribute));
+ }
+}
+
+const std::string kFmtpInSessionSDP =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "a=fmtp:109 0-15" CRLF "m=audio 9 RTP/SAVPF 109 9 0 8 101" CRLF
+ "c=IN IP4 0.0.0.0" CRLF "a=rtpmap:109 opus/48000/2" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckFmtpInSessionLevel) {
+ ParseSdp(kFmtpInSessionSDP, false);
+ if (Sdp()) {
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kFmtpAttribute));
+ ASSERT_FALSE(
+ Sdp()->GetAttributeList().HasAttribute(SdpAttribute::kFmtpAttribute));
+ }
+}
+
+const std::string kIceMismatchInSessionSDP =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "a=ice-mismatch" CRLF "m=audio 9 RTP/SAVPF 109 9 0 8 101" CRLF
+ "c=IN IP4 0.0.0.0" CRLF "a=rtpmap:109 opus/48000/2" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckIceMismatchInSessionLevel) {
+ ParseSdp(kIceMismatchInSessionSDP, false);
+ if (Sdp()) {
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kIceMismatchAttribute));
+ ASSERT_FALSE(Sdp()->GetAttributeList().HasAttribute(
+ SdpAttribute::kIceMismatchAttribute));
+ }
+}
+
+const std::string kImageattrInSessionSDP =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "a=imageattr:120 send * recv *" CRLF "m=video 9 RTP/SAVPF 120" CRLF
+ "c=IN IP4 0.0.0.0" CRLF "a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckImageattrInSessionLevel) {
+ ParseSdp(kImageattrInSessionSDP, false);
+ if (Sdp()) {
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kImageattrAttribute));
+ ASSERT_FALSE(Sdp()->GetAttributeList().HasAttribute(
+ SdpAttribute::kImageattrAttribute));
+ }
+}
+
+const std::string kLabelInSessionSDP =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "a=label:foobar" CRLF "m=video 9 RTP/SAVPF 120" CRLF "c=IN IP4 0.0.0.0" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckLabelInSessionLevel) {
+ ParseSdp(kLabelInSessionSDP, false);
+ if (Sdp()) {
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kLabelAttribute));
+ ASSERT_FALSE(
+ Sdp()->GetAttributeList().HasAttribute(SdpAttribute::kLabelAttribute));
+ }
+}
+
+const std::string kMaxptimeInSessionSDP =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "a=maxptime:100" CRLF "m=video 9 RTP/SAVPF 120" CRLF "c=IN IP4 0.0.0.0" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckMaxptimeInSessionLevel) {
+ ParseSdp(kMaxptimeInSessionSDP, false);
+ if (Sdp()) {
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kMaxptimeAttribute));
+ ASSERT_FALSE(Sdp()->GetAttributeList().HasAttribute(
+ SdpAttribute::kMaxptimeAttribute));
+ }
+}
+
+const std::string kMidInSessionSDP =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "a=mid:foobar" CRLF "m=video 9 RTP/SAVPF 120" CRLF "c=IN IP4 0.0.0.0" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckMidInSessionLevel) {
+ ParseSdp(kMidInSessionSDP, false);
+ if (Sdp()) {
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kMidAttribute));
+ ASSERT_FALSE(
+ Sdp()->GetAttributeList().HasAttribute(SdpAttribute::kMidAttribute));
+ }
+}
+
+const std::string kMsidInSessionSDP =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "a=msid:foobar" CRLF "m=video 9 RTP/SAVPF 120" CRLF "c=IN IP4 0.0.0.0" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckMsidInSessionLevel) {
+ ParseSdp(kMsidInSessionSDP, false);
+ if (Sdp()) {
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kMsidAttribute));
+ ASSERT_FALSE(
+ Sdp()->GetAttributeList().HasAttribute(SdpAttribute::kMsidAttribute));
+ }
+}
+
+const std::string kPtimeInSessionSDP =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "a=ptime:50" CRLF "m=video 9 RTP/SAVPF 120" CRLF "c=IN IP4 0.0.0.0" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckPtimeInSessionLevel) {
+ ParseSdp(kPtimeInSessionSDP, false);
+ if (Sdp()) {
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kPtimeAttribute));
+ ASSERT_FALSE(
+ Sdp()->GetAttributeList().HasAttribute(SdpAttribute::kPtimeAttribute));
+ }
+}
+
+const std::string kRemoteCandidatesInSessionSDP =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "a=remote-candidates:0 10.0.0.1 5555" CRLF "m=video 9 RTP/SAVPF 120" CRLF
+ "c=IN IP4 0.0.0.0" CRLF "a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckRemoteCandidatesInSessionLevel) {
+ ParseSdp(kRemoteCandidatesInSessionSDP, false);
+ if (Sdp()) {
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kRemoteCandidatesAttribute));
+ ASSERT_FALSE(Sdp()->GetAttributeList().HasAttribute(
+ SdpAttribute::kRemoteCandidatesAttribute));
+ }
+}
+
+const std::string kRtcpInSessionSDP =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "a=rtcp:5555" CRLF "m=video 9 RTP/SAVPF 120" CRLF "c=IN IP4 0.0.0.0" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckRtcpInSessionLevel) {
+ ParseSdp(kRtcpInSessionSDP, false);
+ if (Sdp()) {
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpAttribute));
+ ASSERT_FALSE(
+ Sdp()->GetAttributeList().HasAttribute(SdpAttribute::kRtcpAttribute));
+ }
+}
+
+const std::string kRtcpFbInSessionSDP =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "a=rtcp-fb:120 nack" CRLF "m=video 9 RTP/SAVPF 120" CRLF
+ "c=IN IP4 0.0.0.0" CRLF "a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckRtcpFbInSessionLevel) {
+ ParseSdp(kRtcpFbInSessionSDP, false, true);
+ if (Sdp()) {
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpFbAttribute));
+ ASSERT_FALSE(
+ Sdp()->GetAttributeList().HasAttribute(SdpAttribute::kRtcpFbAttribute));
+ }
+}
+
+const std::string kRtcpMuxInSessionSDP =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "a=rtcp-mux" CRLF "m=video 9 RTP/SAVPF 120" CRLF "c=IN IP4 0.0.0.0" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckRtcpMuxInSessionLevel) {
+ ParseSdp(kRtcpMuxInSessionSDP, false);
+ if (Sdp()) {
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpMuxAttribute));
+ ASSERT_FALSE(Sdp()->GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpMuxAttribute));
+ }
+}
+
+const std::string kRtcpRsizeInSessionSDP =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "a=rtcp-rsize" CRLF "m=video 9 RTP/SAVPF 120" CRLF "c=IN IP4 0.0.0.0" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckRtcpRsizeInSessionLevel) {
+ ParseSdp(kRtcpRsizeInSessionSDP, false);
+ if (Sdp()) {
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpRsizeAttribute));
+ ASSERT_FALSE(Sdp()->GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpRsizeAttribute));
+ }
+}
+
+const std::string kRtpmapInSessionSDP =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF "m=video 9 RTP/SAVPF 120" CRLF
+ "c=IN IP4 0.0.0.0" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckRtpmapInSessionLevel) {
+ ParseSdp(kRtpmapInSessionSDP, false);
+ if (Sdp()) {
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtpmapAttribute));
+ ASSERT_FALSE(
+ Sdp()->GetAttributeList().HasAttribute(SdpAttribute::kRtpmapAttribute));
+ }
+}
+
+const std::string kSctpmapInSessionSDP =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "a=sctpmap:5000" CRLF "m=video 9 RTP/SAVPF 120" CRLF "c=IN IP4 0.0.0.0" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckSctpmapInSessionLevel) {
+ ParseSdp(kSctpmapInSessionSDP, false);
+ if (Sdp()) {
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kSctpmapAttribute));
+ ASSERT_FALSE(Sdp()->GetAttributeList().HasAttribute(
+ SdpAttribute::kSctpmapAttribute));
+ }
+}
+
+const std::string kSsrcInSessionSDP =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "a=ssrc:5000" CRLF "m=video 9 RTP/SAVPF 120" CRLF "c=IN IP4 0.0.0.0" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckSsrcInSessionLevel) {
+ ParseSdp(kSsrcInSessionSDP, false);
+ if (Sdp()) {
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kSsrcAttribute));
+ ASSERT_FALSE(
+ Sdp()->GetAttributeList().HasAttribute(SdpAttribute::kSsrcAttribute));
+ }
+}
+
+const std::string kMalformedImageattr =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "m=video 9 RTP/SAVPF 120" CRLF "c=IN IP4 0.0.0.0" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF "a=imageattr:flob" CRLF;
+
+TEST_P(NewSdpTest, CheckMalformedImageattr) {
+ if (::testing::get<0>(GetParam())) {
+ // Don't do a parse/serialize before running this test
+ return;
+ }
+
+ ParseSdp(kMalformedImageattr, false);
+ ASSERT_NE(0U, ParseErrorCount());
+}
+
+TEST_P(NewSdpTest, ParseInvalidSimulcastNoSuchSendRid) {
+ ParseSdp("v=0" CRLF "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF "s=SIP Call" CRLF
+ "c=IN IP4 198.51.100.7" CRLF "b=CT:5000" CRLF "t=0 0" CRLF
+ "m=video 56436 RTP/SAVPF 120" CRLF "a=rtpmap:120 VP8/90000" CRLF
+ "a=sendrecv" CRLF "a=simulcast:send 9" CRLF
+ "a=rid:9 recv max-width=800;max-height=600" CRLF,
+ false);
+ ASSERT_NE(0U, ParseErrorCount());
+}
+
+TEST_P(NewSdpTest, ParseInvalidSimulcastNoSuchRecvRid) {
+ ParseSdp("v=0" CRLF "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF "s=SIP Call" CRLF
+ "c=IN IP4 198.51.100.7" CRLF "b=CT:5000" CRLF "t=0 0" CRLF
+ "m=video 56436 RTP/SAVPF 120" CRLF "a=rtpmap:120 VP8/90000" CRLF
+ "a=sendrecv" CRLF "a=simulcast:recv 9" CRLF
+ "a=rid:9 send max-width=800;max-height=600" CRLF,
+ false);
+ ASSERT_NE(0U, ParseErrorCount());
+}
+
+TEST_P(NewSdpTest, ParseSimulcastNotSending) {
+ ParseSdp("v=0" CRLF "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF "s=SIP Call" CRLF
+ "c=IN IP4 198.51.100.7" CRLF "b=CT:5000" CRLF "t=0 0" CRLF
+ "m=video 56436 RTP/SAVPF 120" CRLF "a=rtpmap:120 VP8/90000" CRLF
+ "a=recvonly" CRLF "a=simulcast:send 120" CRLF "a=rid:120 send" CRLF,
+ false);
+ ASSERT_EQ(0U, ParseErrorCount());
+}
+
+TEST_P(NewSdpTest, ParseSimulcastNotReceiving) {
+ ParseSdp("v=0" CRLF "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF "s=SIP Call" CRLF
+ "c=IN IP4 198.51.100.7" CRLF "b=CT:5000" CRLF "t=0 0" CRLF
+ "m=video 56436 RTP/SAVPF 120" CRLF "a=rtpmap:120 VP8/90000" CRLF
+ "a=sendonly" CRLF "a=simulcast:recv 120" CRLF "a=rid:120 recv" CRLF,
+ false);
+ ASSERT_EQ(0U, ParseErrorCount());
+}
+
+TEST_P(NewSdpTest, ParseInvalidRidNoSuchPt) {
+ SKIP_TEST_WITH_SIPCC_PARSER
+ ParseSdp("v=0" CRLF "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF "s=SIP Call" CRLF
+ "c=IN IP4 198.51.100.7" CRLF "b=CT:5000" CRLF "t=0 0" CRLF
+ "m=video 56436 RTP/SAVPF 120" CRLF "a=rtpmap:120 VP8/90000" CRLF
+ "a=sendrecv" CRLF "a=simulcast:recv rid=9" CRLF
+ "a=rid:9 recv pt=101;max-width=800;max-height=600" CRLF,
+ false);
+ ASSERT_NE(0U, ParseErrorCount());
+}
+
+const std::string kNoAttributes =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "m=video 9 RTP/SAVPF 120" CRLF "c=IN IP4 0.0.0.0" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF;
+
+TEST_P(NewSdpTest, CheckNoAttributes) {
+ ParseSdp(kNoAttributes);
+
+ for (auto a = static_cast<size_t>(SdpAttribute::kFirstAttribute);
+ a <= static_cast<size_t>(SdpAttribute::kLastAttribute); ++a) {
+ SdpAttribute::AttributeType type =
+ static_cast<SdpAttribute::AttributeType>(a);
+
+ // rtpmap is a special case right now, we throw parse errors if it is
+ // missing, and then insert one.
+ // direction is another special case that gets a default if not present
+ if (type != SdpAttribute::kRtpmapAttribute &&
+ type != SdpAttribute::kDirectionAttribute) {
+ ASSERT_FALSE(
+ Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(type))
+ << "Attribute " << a << " should not have been present at media level";
+ ASSERT_FALSE(Sdp()->GetAttributeList().HasAttribute(type))
+ << "Attribute " << a << " should not have been present at session level";
+ }
+ }
+
+ ASSERT_FALSE(
+ Sdp()->GetAttributeList().HasAttribute(SdpAttribute::kRtpmapAttribute));
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kDirectionAttribute));
+ ASSERT_EQ(SdpDirectionAttribute::kSendrecv,
+ Sdp()->GetMediaSection(0).GetAttributeList().GetDirection());
+ ASSERT_TRUE(Sdp()->GetAttributeList().HasAttribute(
+ SdpAttribute::kDirectionAttribute));
+ ASSERT_EQ(SdpDirectionAttribute::kSendrecv,
+ Sdp()->GetAttributeList().GetDirection());
+}
+
+const std::string kMediaLevelDtlsMessage =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "m=video 9 RTP/SAVPF 120" CRLF "c=IN IP4 0.0.0.0" CRLF
+ "a=dtls-message:client " BASE64_DTLS_HELLO CRLF
+ "a=rtpmap:120 VP8/90000" CRLF;
+
+TEST_P(NewSdpTest, CheckMediaLevelDtlsMessage) {
+ ParseSdp(kMediaLevelDtlsMessage);
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+
+ // dtls-message is not defined for use at the media level; we don't
+ // parse it
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kDtlsMessageAttribute));
+}
+
+TEST_P(NewSdpTest, CheckSetPort) {
+ // Parse any valid sdp with a media section
+ ParseSdp("v=0" CRLF "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF "s=SIP Call" CRLF
+ "c=IN IP4 198.51.100.7" CRLF "b=CT:5000" CRLF "t=0 0" CRLF
+ "m=video 56436 RTP/SAVPF 120" CRLF "a=rtpmap:120 VP8/90000" CRLF
+ "a=sendonly" CRLF,
+ false);
+
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+
+ constexpr unsigned int expectedParesPort = 56436;
+ unsigned int currentPort = Sdp()->GetMediaSection(0).GetPort();
+ ASSERT_EQ(expectedParesPort, currentPort);
+
+ Sdp()->GetMediaSection(0).SetPort(currentPort + 1);
+ ASSERT_EQ(currentPort + 1, Sdp()->GetMediaSection(0).GetPort());
+}
+
+TEST_P(NewSdpTest, CheckAddCodec) {
+ // Parse any valid sdp with a media section
+ ParseSdp("v=0" CRLF "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF "s=SIP Call" CRLF
+ "c=IN IP4 198.51.100.7" CRLF "b=CT:5000" CRLF "t=0 0" CRLF
+ "m=video 56436 RTP/SAVPF 120" CRLF "a=rtpmap:120 VP8/90000" CRLF
+ "a=sendonly" CRLF);
+
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount());
+
+ ASSERT_EQ(1U, Sdp()->GetMediaSection(0).GetFormats().size());
+ ASSERT_EQ(
+ 1U,
+ Sdp()->GetMediaSection(0).GetAttributeList().GetRtpmap().mRtpmaps.size());
+
+ Sdp()->GetMediaSection(0).AddCodec("110", "opus", 48000, 2);
+
+ ASSERT_EQ(2U, Sdp()->GetMediaSection(0).GetFormats().size());
+ const auto& rtpmaps =
+ Sdp()->GetMediaSection(0).GetAttributeList().GetRtpmap();
+ ASSERT_EQ(2U, rtpmaps.mRtpmaps.size());
+
+ ASSERT_TRUE(rtpmaps.HasEntry("120"));
+ ASSERT_TRUE(rtpmaps.HasEntry("110"));
+ const auto aRtpmap = rtpmaps.GetEntry("110");
+ ASSERT_EQ(aRtpmap.pt, "110");
+ ASSERT_EQ(aRtpmap.codec, SdpRtpmapAttributeList::kOpus);
+ ASSERT_EQ(aRtpmap.name, "opus");
+ ASSERT_EQ(aRtpmap.clock, 48000U);
+ ASSERT_EQ(aRtpmap.channels, 2U);
+}
+
+TEST_P(NewSdpTest, CheckClearCodecs) {
+ // Parse any valid sdp with a media section
+ ParseSdp("v=0" CRLF "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF "s=SIP Call" CRLF
+ "c=IN IP4 198.51.100.7" CRLF "b=CT:5000" CRLF "t=0 0" CRLF
+ "m=video 56436 RTP/SAVPF 120 110" CRLF "a=rtpmap:120 VP8/90000" CRLF
+ "a=sendonly" CRLF "a=rtpmap:110 opus/48000" CRLF);
+ // SIPCC strips the channels on opus so the /2 was omitted
+
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount());
+
+ ASSERT_EQ(2U, Sdp()->GetMediaSection(0).GetFormats().size());
+ ASSERT_EQ(
+ 2U,
+ Sdp()->GetMediaSection(0).GetAttributeList().GetRtpmap().mRtpmaps.size());
+
+ Sdp()->GetMediaSection(0).ClearCodecs();
+
+ ASSERT_EQ(0U, Sdp()->GetMediaSection(0).GetFormats().size());
+ ASSERT_FALSE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtpmapAttribute));
+}
+
+TEST_P(NewSdpTest, CheckAddMediaSection) {
+ ParseSdp(kBasicAudioVideoOffer);
+
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(3U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ Sdp()->AddMediaSection(
+ SdpMediaSection::kVideo, SdpDirectionAttribute::Direction::kSendrecv,
+ 58000, SdpMediaSection::kUdpDtlsSctp, sdp::kIPv4, "127.0.0.1");
+
+ ASSERT_EQ(4U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections after adding media section";
+
+ const SdpMediaSection& newMediaSection = Sdp()->GetMediaSection(3);
+
+ ASSERT_EQ(SdpMediaSection::kVideo, newMediaSection.GetMediaType());
+ ASSERT_EQ(SdpDirectionAttribute::Direction::kSendrecv,
+ newMediaSection.GetDirectionAttribute().mValue);
+ ASSERT_EQ(58000U, newMediaSection.GetPort());
+ ASSERT_EQ(SdpMediaSection::kUdpDtlsSctp, newMediaSection.GetProtocol());
+ ASSERT_EQ(sdp::kIPv4, newMediaSection.GetConnection().GetAddrType());
+ ASSERT_EQ("127.0.0.1", newMediaSection.GetConnection().GetAddress());
+
+ Sdp()->AddMediaSection(SdpMediaSection::kAudio,
+ SdpDirectionAttribute::Direction::kSendonly, 14006,
+ SdpMediaSection::kTcpDtlsRtpSavpf, sdp::kIPv6,
+ "2607:f8b0:4004:801::2013");
+
+ ASSERT_EQ(5U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections after adding media section";
+
+ const SdpMediaSection& nextNewMediaSection = Sdp()->GetMediaSection(4);
+
+ ASSERT_EQ(SdpMediaSection::kAudio, nextNewMediaSection.GetMediaType());
+ ASSERT_EQ(SdpDirectionAttribute::Direction::kSendonly,
+ nextNewMediaSection.GetDirectionAttribute().mValue);
+ ASSERT_EQ(14006U, nextNewMediaSection.GetPort());
+ ASSERT_EQ(SdpMediaSection::kTcpDtlsRtpSavpf,
+ nextNewMediaSection.GetProtocol());
+ ASSERT_EQ(sdp::kIPv6, nextNewMediaSection.GetConnection().GetAddrType());
+ ASSERT_EQ("2607:f8b0:4004:801::2013",
+ nextNewMediaSection.GetConnection().GetAddress());
+
+ if (!ResultsAreFromSipcc()) {
+ // All following AddMediaSection calls are expected to fail
+ // SdpMediaSection::kDccpRtpAvp is expected to cause a failure
+ Sdp()->AddMediaSection(SdpMediaSection::kAudio,
+ SdpDirectionAttribute::Direction::kSendonly, 14006,
+ SdpMediaSection::kDccpRtpAvp, sdp::kIPv6,
+ "2607:f8b0:4004:801::2013");
+ ASSERT_EQ(5U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections after adding media section";
+
+ // sdp::kAddrTypeNone is expected to cause a failure
+ Sdp()->AddMediaSection(SdpMediaSection::kAudio,
+ SdpDirectionAttribute::Direction::kSendonly, 14006,
+ SdpMediaSection::kDtlsSctp, sdp::kAddrTypeNone,
+ "2607:f8b0:4004:801::2013");
+ ASSERT_EQ(5U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections after adding media section";
+
+ // "NOT:AN.IP.ADDRESS" is expected to cause a failure
+ Sdp()->AddMediaSection(SdpMediaSection::kAudio,
+ SdpDirectionAttribute::Direction::kSendonly, 14006,
+ SdpMediaSection::kTcpDtlsRtpSavpf, sdp::kIPv6,
+ "NOT:AN.IP.ADDRESS");
+ ASSERT_EQ(5U, Sdp()->GetMediaSectionCount())
+ << "Wrong number of media sections after adding media section";
+ }
+}
+
+TEST_P(NewSdpTest, CheckAddDataChannel_Draft05) {
+ // Parse any valid sdp with a media section
+ ParseSdp("v=0" CRLF "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF "s=SIP Call" CRLF
+ "c=IN IP4 198.51.100.7" CRLF "b=CT:5000" CRLF "t=0 0" CRLF
+ "m=application 56436 DTLS/SCTP 5000" CRLF);
+
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount());
+
+ auto& mediaSection = Sdp()->GetMediaSection(0);
+ mediaSection.AddDataChannel("webrtc-datachannel", 6000, 16, 0);
+
+ ASSERT_FALSE(mediaSection.GetAttributeList().HasAttribute(
+ SdpAttribute::kMaxMessageSizeAttribute));
+ ASSERT_TRUE(mediaSection.GetAttributeList().HasAttribute(
+ SdpAttribute::kSctpmapAttribute));
+ ASSERT_TRUE(mediaSection.GetAttributeList().GetSctpmap().HasEntry("6000"));
+ ASSERT_EQ(
+ 16U,
+ mediaSection.GetAttributeList().GetSctpmap().GetFirstEntry().streams);
+ ASSERT_EQ("webrtc-datachannel",
+ mediaSection.GetAttributeList().GetSctpmap().GetFirstEntry().name);
+
+ mediaSection.AddDataChannel("webrtc-datachannel", 15000, 8, 1800);
+
+ ASSERT_TRUE(mediaSection.GetAttributeList().HasAttribute(
+ SdpAttribute::kMaxMessageSizeAttribute));
+ ASSERT_EQ(1800U, mediaSection.GetAttributeList().GetMaxMessageSize());
+ ASSERT_TRUE(mediaSection.GetAttributeList().HasAttribute(
+ SdpAttribute::kSctpmapAttribute));
+ ASSERT_TRUE(mediaSection.GetAttributeList().GetSctpmap().HasEntry("15000"));
+ ASSERT_EQ(
+ 8U, mediaSection.GetAttributeList().GetSctpmap().GetFirstEntry().streams);
+}
+
+TEST_P(NewSdpTest, CheckAddDataChannel) {
+ ParseSdp("v=0" CRLF "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF "s=SIP Call" CRLF
+ "c=IN IP4 198.51.100.7" CRLF "b=CT:5000" CRLF "t=0 0" CRLF
+ "m=application 56436 UDP/DTLS/SCTP webrtc-datachannel" CRLF);
+
+ ASSERT_TRUE(!!Sdp())
+ << "Parse failed: " << SerializeParseErrors();
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount());
+
+ auto& mediaSection = Sdp()->GetMediaSection(0);
+ mediaSection.AddDataChannel("webrtc-datachannel", 6000, 16, 0);
+
+ ASSERT_FALSE(mediaSection.GetAttributeList().HasAttribute(
+ SdpAttribute::kMaxMessageSizeAttribute));
+ ASSERT_TRUE(mediaSection.GetAttributeList().HasAttribute(
+ SdpAttribute::kSctpPortAttribute));
+ ASSERT_EQ(6000U, mediaSection.GetAttributeList().GetSctpPort());
+
+ mediaSection.AddDataChannel("webrtc-datachannel", 15000, 8, 1800);
+
+ ASSERT_TRUE(mediaSection.GetAttributeList().HasAttribute(
+ SdpAttribute::kMaxMessageSizeAttribute));
+ ASSERT_EQ(1800U, mediaSection.GetAttributeList().GetMaxMessageSize());
+ ASSERT_TRUE(mediaSection.GetAttributeList().HasAttribute(
+ SdpAttribute::kSctpPortAttribute));
+ ASSERT_EQ(15000U, mediaSection.GetAttributeList().GetSctpPort());
+}
+
+TEST(NewSdpTestNoFixture, CheckParsingResultComparer)
+{
+ auto check_comparison = [](const std::string sdp_string) {
+ SipccSdpParser sipccParser;
+ RsdparsaSdpParser rustParser;
+ auto print_errors =
+ [](const mozilla::UniquePtr<SdpParser::Results>& results,
+ const char* name) {
+ for (const auto& e : results->Errors()) {
+ std::cerr << name << " Line " << e.first << ": " << e.second;
+ }
+ };
+ auto sipccResults = sipccParser.Parse(sdp_string);
+ auto sipccSdp = std::move(sipccResults->Sdp());
+ print_errors(sipccResults, "sipcc");
+
+ auto rustResults = rustParser.Parse(sdp_string);
+ auto rustSdp = std::move(rustResults->Sdp());
+ print_errors(rustResults, "webrtc-sdp");
+
+ ParsingResultComparer comparer;
+ return sipccSdp && rustSdp
+ ? comparer.Compare(*rustSdp, *sipccSdp, sdp_string)
+ : false;
+ };
+
+ ASSERT_TRUE(check_comparison(kBasicAudioVideoOffer));
+ ASSERT_TRUE(check_comparison(kH264AudioVideoOffer));
+
+ // Check the Fmtp comprison
+ const std::string kBasicOpusFmtp1 =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "m=video 9 RTP/SAVPF 120" CRLF "a=rtpmap:120 opus/48000/1" CRLF
+ "a=fmtp:120 stereo=1;useinbandfec=1" CRLF;
+ ASSERT_TRUE(check_comparison(kBasicOpusFmtp1));
+
+ const std::string kBasicOpusFmtp2 =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "m=video 9 RTP/SAVPF 120" CRLF "a=rtpmap:120 opus/48000" CRLF
+ "a=fmtp:120 useinbandfec=1;stereo=1;maxplaybackrate=32000" CRLF;
+ ASSERT_TRUE(check_comparison(kBasicOpusFmtp2));
+
+ const std::string kBasicVP8Fmtp1 =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "m=video 9 RTP/SAVPF 120" CRLF "a=rtpmap:120 VP8/90000" CRLF
+ "a=fmtp:120 max-fs=3600;max-fr=60" CRLF;
+ ASSERT_TRUE(check_comparison(kBasicVP8Fmtp1));
+ //
+ const std::string kBasicVP8Fmtp2 =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "m=video 9 RTP/SAVPF 120" CRLF "a=rtpmap:120 VP8/90000" CRLF
+ "a=fmtp:120 max-fr=60;max-fs=3600" CRLF;
+ ASSERT_TRUE(check_comparison(kBasicVP8Fmtp2));
+
+ const std::string kBasicH264Fmtp1 =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "m=video 9 RTP/SAVPF 120" CRLF "a=rtpmap:120 H264/90000" CRLF
+ "a=fmtp:120 profile-level-id=42a01e;level_asymmetry_allowed=1" CRLF;
+ ASSERT_TRUE(check_comparison(kBasicH264Fmtp1));
+
+ const std::string kBasicH264Fmtp2 =
+ "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+ "s=SIP Call" CRLF "c=IN IP4 224.0.0.1/100/12" CRLF "t=0 0" CRLF
+ "m=video 9 RTP/SAVPF 120" CRLF "a=rtpmap:120 H264/90000" CRLF
+ "a=fmtp:120 "
+ "level_asymmetry_allowed=1;profile-level-id=42a01e;max_fs=3600" CRLF;
+ ASSERT_TRUE(check_comparison(kBasicH264Fmtp2));
+}
+
+TEST(NewSdpTestNoFixture, CheckAttributeTypeSerialize)
+{
+ for (auto a = static_cast<size_t>(SdpAttribute::kFirstAttribute);
+ a <= static_cast<size_t>(SdpAttribute::kLastAttribute); ++a) {
+ SdpAttribute::AttributeType type =
+ static_cast<SdpAttribute::AttributeType>(a);
+
+ // Direction attributes are handled a little differently
+ if (type != SdpAttribute::kDirectionAttribute) {
+ std::ostringstream os;
+ os << type;
+ ASSERT_NE("", os.str());
+ }
+ }
+}
+
+static SdpImageattrAttributeList::XYRange ParseXYRange(
+ const std::string& input) {
+ std::istringstream is(input + ",");
+ std::string error;
+ SdpImageattrAttributeList::XYRange range;
+ EXPECT_TRUE(range.Parse(is, &error)) << error;
+ EXPECT_EQ(',', is.get());
+ EXPECT_EQ(EOF, is.get());
+ return range;
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrXYRangeParseValid)
+{
+ {
+ SdpImageattrAttributeList::XYRange range(ParseXYRange("640"));
+ ASSERT_EQ(1U, range.discreteValues.size());
+ ASSERT_EQ(640U, range.discreteValues[0]);
+ }
+
+ {
+ SdpImageattrAttributeList::XYRange range(ParseXYRange("[320,640]"));
+ ASSERT_EQ(2U, range.discreteValues.size());
+ ASSERT_EQ(320U, range.discreteValues[0]);
+ ASSERT_EQ(640U, range.discreteValues[1]);
+ }
+
+ {
+ SdpImageattrAttributeList::XYRange range(ParseXYRange("[320,640,1024]"));
+ ASSERT_EQ(3U, range.discreteValues.size());
+ ASSERT_EQ(320U, range.discreteValues[0]);
+ ASSERT_EQ(640U, range.discreteValues[1]);
+ ASSERT_EQ(1024U, range.discreteValues[2]);
+ }
+
+ {
+ SdpImageattrAttributeList::XYRange range(ParseXYRange("[320:640]"));
+ ASSERT_EQ(0U, range.discreteValues.size());
+ ASSERT_EQ(320U, range.min);
+ ASSERT_EQ(1U, range.step);
+ ASSERT_EQ(640U, range.max);
+ }
+
+ {
+ SdpImageattrAttributeList::XYRange range(ParseXYRange("[320:16:640]"));
+ ASSERT_EQ(0U, range.discreteValues.size());
+ ASSERT_EQ(320U, range.min);
+ ASSERT_EQ(16U, range.step);
+ ASSERT_EQ(640U, range.max);
+ }
+}
+
+template <typename T>
+void ParseInvalid(const std::string& input, size_t last) {
+ std::istringstream is(input);
+ T parsed;
+ std::string error;
+ ASSERT_FALSE(parsed.Parse(is, &error))
+ << "\'" << input << "\' should not have parsed successfully";
+ is.clear();
+ ASSERT_EQ(last, static_cast<size_t>(is.tellg()))
+ << "Parse failed at unexpected location:" << std::endl
+ << input << std::endl
+ << std::string(is.tellg(), ' ') << "^" << std::endl;
+ // For a human to eyeball to make sure the error strings look sane
+ std::cout << "\"" << input << "\" - " << error << std::endl;
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrXYRangeParseInvalid)
+{
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[-1", 1);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[-", 1);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[-v", 1);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640:-1", 5);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640:16:-1", 8);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640,-1", 5);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640,-]", 5);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("-v", 0);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("-1", 0);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("", 0);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[", 1);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[v", 1);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[", 1);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[ 640", 1);
+ // It looks like the overflow detection only happens once the whole number
+ // is scanned...
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[99999999999999999:", 18);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640", 4);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640:", 5);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640:v", 5);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640:16", 7);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640:16:", 8);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640:16:v", 8);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640:16:320]", 11);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640:16:320", 11);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640:16:320v", 11);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640:1024", 9);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640:320]", 8);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640:1024v", 9);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640,", 5);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640,v", 5);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640]", 4);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640x", 4);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640,]", 5);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>(" ", 0);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("v", 0);
+}
+
+static SdpImageattrAttributeList::SRange ParseSRange(const std::string& input) {
+ std::istringstream is(input + ",");
+ std::string error;
+ SdpImageattrAttributeList::SRange range;
+ EXPECT_TRUE(range.Parse(is, &error)) << error;
+ EXPECT_EQ(',', is.get());
+ EXPECT_EQ(EOF, is.get());
+ return range;
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrSRangeParseValid)
+{
+ {
+ SdpImageattrAttributeList::SRange range(ParseSRange("0.1"));
+ ASSERT_EQ(1U, range.discreteValues.size());
+ ASSERT_FLOAT_EQ(0.1f, range.discreteValues[0]);
+ }
+
+ {
+ SdpImageattrAttributeList::SRange range(ParseSRange("[0.1,0.2]"));
+ ASSERT_EQ(2U, range.discreteValues.size());
+ ASSERT_FLOAT_EQ(0.1f, range.discreteValues[0]);
+ ASSERT_FLOAT_EQ(0.2f, range.discreteValues[1]);
+ }
+
+ {
+ SdpImageattrAttributeList::SRange range(ParseSRange("[0.1,0.2,0.3]"));
+ ASSERT_EQ(3U, range.discreteValues.size());
+ ASSERT_FLOAT_EQ(0.1f, range.discreteValues[0]);
+ ASSERT_FLOAT_EQ(0.2f, range.discreteValues[1]);
+ ASSERT_FLOAT_EQ(0.3f, range.discreteValues[2]);
+ }
+
+ {
+ SdpImageattrAttributeList::SRange range(ParseSRange("[0.1-0.2]"));
+ ASSERT_EQ(0U, range.discreteValues.size());
+ ASSERT_FLOAT_EQ(0.1f, range.min);
+ ASSERT_FLOAT_EQ(0.2f, range.max);
+ }
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrSRangeParseInvalid)
+{
+ ParseInvalid<SdpImageattrAttributeList::SRange>("", 0);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[", 1);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[v", 1);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[-1", 1);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[", 1);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[-", 1);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[v", 1);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[ 0.2", 1);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[10.1-", 5);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.08-", 5);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2", 4);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2-", 5);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2-v", 5);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2--1", 5);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2-0.3", 8);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2-0.1]", 8);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2-0.3v", 8);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2,", 5);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2,v", 5);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2,-1", 5);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2]", 4);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2v", 4);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2,]", 5);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2,-]", 5);
+ ParseInvalid<SdpImageattrAttributeList::SRange>(" ", 0);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("v", 0);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("-v", 0);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("-1", 0);
+}
+
+static SdpImageattrAttributeList::PRange ParsePRange(const std::string& input) {
+ std::istringstream is(input + ",");
+ std::string error;
+ SdpImageattrAttributeList::PRange range;
+ EXPECT_TRUE(range.Parse(is, &error)) << error;
+ EXPECT_EQ(',', is.get());
+ EXPECT_EQ(EOF, is.get());
+ return range;
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrPRangeParseValid)
+{
+ SdpImageattrAttributeList::PRange range(ParsePRange("[0.1000-9.9999]"));
+ ASSERT_FLOAT_EQ(0.1f, range.min);
+ ASSERT_FLOAT_EQ(9.9999f, range.max);
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrPRangeParseInvalid)
+{
+ ParseInvalid<SdpImageattrAttributeList::PRange>("", 0);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[", 1);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[v", 1);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[-1", 1);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[", 1);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[-", 1);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[v", 1);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[ 0.2", 1);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[10.1-", 5);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[0.08-", 5);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[0.2", 4);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[0.2-", 5);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[0.2-v", 5);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[0.2--1", 5);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[0.2-0.3", 8);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[0.2-0.1]", 8);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[0.2-0.3v", 8);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[0.2,", 4);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[0.2:", 4);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[0.2]", 4);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[0.2v", 4);
+ ParseInvalid<SdpImageattrAttributeList::PRange>(" ", 0);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("v", 0);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("-x", 0);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("-1", 0);
+}
+
+static SdpImageattrAttributeList::Set ParseSet(const std::string& input) {
+ std::istringstream is(input + " ");
+ std::string error;
+ SdpImageattrAttributeList::Set set;
+ EXPECT_TRUE(set.Parse(is, &error)) << error;
+ EXPECT_EQ(' ', is.get());
+ EXPECT_EQ(EOF, is.get());
+ return set;
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrSetParseValid)
+{
+ {
+ SdpImageattrAttributeList::Set set(ParseSet("[x=320,y=240]"));
+ ASSERT_EQ(1U, set.xRange.discreteValues.size());
+ ASSERT_EQ(320U, set.xRange.discreteValues[0]);
+ ASSERT_EQ(1U, set.yRange.discreteValues.size());
+ ASSERT_EQ(240U, set.yRange.discreteValues[0]);
+ ASSERT_FALSE(set.sRange.IsSet());
+ ASSERT_FALSE(set.pRange.IsSet());
+ ASSERT_FLOAT_EQ(0.5f, set.qValue);
+ }
+
+ {
+ SdpImageattrAttributeList::Set set(ParseSet("[X=320,Y=240]"));
+ ASSERT_EQ(1U, set.xRange.discreteValues.size());
+ ASSERT_EQ(320U, set.xRange.discreteValues[0]);
+ ASSERT_EQ(1U, set.yRange.discreteValues.size());
+ ASSERT_EQ(240U, set.yRange.discreteValues[0]);
+ ASSERT_FALSE(set.sRange.IsSet());
+ ASSERT_FALSE(set.pRange.IsSet());
+ ASSERT_FLOAT_EQ(0.5f, set.qValue);
+ }
+
+ {
+ SdpImageattrAttributeList::Set set(ParseSet("[x=320,y=240,par=[0.1-0.2]]"));
+ ASSERT_EQ(1U, set.xRange.discreteValues.size());
+ ASSERT_EQ(320U, set.xRange.discreteValues[0]);
+ ASSERT_EQ(1U, set.yRange.discreteValues.size());
+ ASSERT_EQ(240U, set.yRange.discreteValues[0]);
+ ASSERT_FALSE(set.sRange.IsSet());
+ ASSERT_TRUE(set.pRange.IsSet());
+ ASSERT_FLOAT_EQ(0.1f, set.pRange.min);
+ ASSERT_FLOAT_EQ(0.2f, set.pRange.max);
+ ASSERT_FLOAT_EQ(0.5f, set.qValue);
+ }
+
+ {
+ SdpImageattrAttributeList::Set set(ParseSet("[x=320,y=240,sar=[0.1-0.2]]"));
+ ASSERT_EQ(1U, set.xRange.discreteValues.size());
+ ASSERT_EQ(320U, set.xRange.discreteValues[0]);
+ ASSERT_EQ(1U, set.yRange.discreteValues.size());
+ ASSERT_EQ(240U, set.yRange.discreteValues[0]);
+ ASSERT_TRUE(set.sRange.IsSet());
+ ASSERT_FLOAT_EQ(0.1f, set.sRange.min);
+ ASSERT_FLOAT_EQ(0.2f, set.sRange.max);
+ ASSERT_FALSE(set.pRange.IsSet());
+ ASSERT_FLOAT_EQ(0.5f, set.qValue);
+ }
+
+ {
+ SdpImageattrAttributeList::Set set(ParseSet("[x=320,y=240,q=0.1]"));
+ ASSERT_EQ(1U, set.xRange.discreteValues.size());
+ ASSERT_EQ(320U, set.xRange.discreteValues[0]);
+ ASSERT_EQ(1U, set.yRange.discreteValues.size());
+ ASSERT_EQ(240U, set.yRange.discreteValues[0]);
+ ASSERT_FALSE(set.sRange.IsSet());
+ ASSERT_FALSE(set.pRange.IsSet());
+ ASSERT_FLOAT_EQ(0.1f, set.qValue);
+ }
+
+ {
+ SdpImageattrAttributeList::Set set(
+ ParseSet("[x=320,y=240,par=[0.1-0.2],sar=[0.3-0.4],q=0.6]"));
+ ASSERT_EQ(1U, set.xRange.discreteValues.size());
+ ASSERT_EQ(320U, set.xRange.discreteValues[0]);
+ ASSERT_EQ(1U, set.yRange.discreteValues.size());
+ ASSERT_EQ(240U, set.yRange.discreteValues[0]);
+ ASSERT_TRUE(set.sRange.IsSet());
+ ASSERT_FLOAT_EQ(0.3f, set.sRange.min);
+ ASSERT_FLOAT_EQ(0.4f, set.sRange.max);
+ ASSERT_TRUE(set.pRange.IsSet());
+ ASSERT_FLOAT_EQ(0.1f, set.pRange.min);
+ ASSERT_FLOAT_EQ(0.2f, set.pRange.max);
+ ASSERT_FLOAT_EQ(0.6f, set.qValue);
+ }
+
+ {
+ SdpImageattrAttributeList::Set set(ParseSet("[x=320,y=240,foo=bar,q=0.1]"));
+ ASSERT_EQ(1U, set.xRange.discreteValues.size());
+ ASSERT_EQ(320U, set.xRange.discreteValues[0]);
+ ASSERT_EQ(1U, set.yRange.discreteValues.size());
+ ASSERT_EQ(240U, set.yRange.discreteValues[0]);
+ ASSERT_FALSE(set.sRange.IsSet());
+ ASSERT_FALSE(set.pRange.IsSet());
+ ASSERT_FLOAT_EQ(0.1f, set.qValue);
+ }
+
+ {
+ SdpImageattrAttributeList::Set set(
+ ParseSet("[x=320,y=240,foo=bar,q=0.1,bar=baz]"));
+ ASSERT_EQ(1U, set.xRange.discreteValues.size());
+ ASSERT_EQ(320U, set.xRange.discreteValues[0]);
+ ASSERT_EQ(1U, set.yRange.discreteValues.size());
+ ASSERT_EQ(240U, set.yRange.discreteValues[0]);
+ ASSERT_FALSE(set.sRange.IsSet());
+ ASSERT_FALSE(set.pRange.IsSet());
+ ASSERT_FLOAT_EQ(0.1f, set.qValue);
+ }
+
+ {
+ SdpImageattrAttributeList::Set set(
+ ParseSet("[x=320,y=240,foo=[bar],q=0.1,bar=[baz]]"));
+ ASSERT_EQ(1U, set.xRange.discreteValues.size());
+ ASSERT_EQ(320U, set.xRange.discreteValues[0]);
+ ASSERT_EQ(1U, set.yRange.discreteValues.size());
+ ASSERT_EQ(240U, set.yRange.discreteValues[0]);
+ ASSERT_FALSE(set.sRange.IsSet());
+ ASSERT_FALSE(set.pRange.IsSet());
+ ASSERT_FLOAT_EQ(0.1f, set.qValue);
+ }
+
+ {
+ SdpImageattrAttributeList::Set set(
+ ParseSet("[x=320,y=240,foo=[par=foo,sar=bar],q=0.1,bar=[baz]]"));
+ ASSERT_EQ(1U, set.xRange.discreteValues.size());
+ ASSERT_EQ(320U, set.xRange.discreteValues[0]);
+ ASSERT_EQ(1U, set.yRange.discreteValues.size());
+ ASSERT_EQ(240U, set.yRange.discreteValues[0]);
+ ASSERT_FALSE(set.sRange.IsSet());
+ ASSERT_FALSE(set.pRange.IsSet());
+ ASSERT_FLOAT_EQ(0.1f, set.qValue);
+ }
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrSetParseInvalid)
+{
+ ParseInvalid<SdpImageattrAttributeList::Set>("", 0);
+ ParseInvalid<SdpImageattrAttributeList::Set>("x", 0);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[", 1);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[=", 2);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x", 2);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[y=", 3);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=[", 4);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320", 6);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320v", 6);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,", 7);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,=", 8);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,x", 8);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,x=", 9);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=[", 10);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240", 12);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240x", 12);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,", 13);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,q=", 15);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,q=v", 15);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,q=0.5", 18);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,q=0.5,", 19);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,q=0.5,]", 20);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,q=0.5,=]", 20);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,q=0.5,sar=v]", 23);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,q=0.5,q=0.4", 21);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,sar=", 17);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,sar=v", 17);
+ ParseInvalid<SdpImageattrAttributeList::Set>(
+ "[x=320,y=240,sar=[0.5-0.6],sar=[0.7-0.8]", 31);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,par=", 17);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,par=x", 17);
+ ParseInvalid<SdpImageattrAttributeList::Set>(
+ "[x=320,y=240,par=[0.5-0.6],par=[0.7-0.8]", 31);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,foo=", 17);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,foo=x", 18);
+}
+
+static SdpImageattrAttributeList::Imageattr ParseImageattr(
+ const std::string& input) {
+ std::istringstream is(input);
+ std::string error;
+ SdpImageattrAttributeList::Imageattr imageattr;
+ EXPECT_TRUE(imageattr.Parse(is, &error)) << error;
+ EXPECT_TRUE(is.eof());
+ return imageattr;
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrParseValid)
+{
+ {
+ SdpImageattrAttributeList::Imageattr imageattr(ParseImageattr("* send *"));
+ ASSERT_FALSE(imageattr.pt.isSome());
+ ASSERT_TRUE(imageattr.sendAll);
+ ASSERT_TRUE(imageattr.sendSets.empty());
+ ASSERT_FALSE(imageattr.recvAll);
+ ASSERT_TRUE(imageattr.recvSets.empty());
+ }
+
+ {
+ SdpImageattrAttributeList::Imageattr imageattr(ParseImageattr("* SEND *"));
+ ASSERT_FALSE(imageattr.pt.isSome());
+ ASSERT_TRUE(imageattr.sendAll);
+ ASSERT_TRUE(imageattr.sendSets.empty());
+ ASSERT_FALSE(imageattr.recvAll);
+ ASSERT_TRUE(imageattr.recvSets.empty());
+ }
+
+ {
+ SdpImageattrAttributeList::Imageattr imageattr(ParseImageattr("* recv *"));
+ ASSERT_FALSE(imageattr.pt.isSome());
+ ASSERT_FALSE(imageattr.sendAll);
+ ASSERT_TRUE(imageattr.sendSets.empty());
+ ASSERT_TRUE(imageattr.recvAll);
+ ASSERT_TRUE(imageattr.recvSets.empty());
+ }
+
+ {
+ SdpImageattrAttributeList::Imageattr imageattr(ParseImageattr("* RECV *"));
+ ASSERT_FALSE(imageattr.pt.isSome());
+ ASSERT_FALSE(imageattr.sendAll);
+ ASSERT_TRUE(imageattr.sendSets.empty());
+ ASSERT_TRUE(imageattr.recvAll);
+ ASSERT_TRUE(imageattr.recvSets.empty());
+ }
+
+ {
+ SdpImageattrAttributeList::Imageattr imageattr(
+ ParseImageattr("* recv * send *"));
+ ASSERT_FALSE(imageattr.pt.isSome());
+ ASSERT_TRUE(imageattr.sendAll);
+ ASSERT_TRUE(imageattr.sendSets.empty());
+ ASSERT_TRUE(imageattr.recvAll);
+ ASSERT_TRUE(imageattr.recvSets.empty());
+ }
+
+ {
+ SdpImageattrAttributeList::Imageattr imageattr(
+ ParseImageattr("* send * recv *"));
+ ASSERT_FALSE(imageattr.pt.isSome());
+ ASSERT_TRUE(imageattr.sendAll);
+ ASSERT_TRUE(imageattr.sendSets.empty());
+ ASSERT_TRUE(imageattr.recvAll);
+ ASSERT_TRUE(imageattr.recvSets.empty());
+ }
+
+ {
+ SdpImageattrAttributeList::Imageattr imageattr(
+ ParseImageattr("8 send * recv *"));
+ ASSERT_EQ(8U, *imageattr.pt);
+ ASSERT_TRUE(imageattr.sendAll);
+ ASSERT_TRUE(imageattr.sendSets.empty());
+ ASSERT_TRUE(imageattr.recvAll);
+ ASSERT_TRUE(imageattr.recvSets.empty());
+ }
+
+ {
+ SdpImageattrAttributeList::Imageattr imageattr(
+ ParseImageattr("8 send [x=320,y=240] recv *"));
+ ASSERT_EQ(8U, *imageattr.pt);
+ ASSERT_FALSE(imageattr.sendAll);
+ ASSERT_EQ(1U, imageattr.sendSets.size());
+ ASSERT_EQ(1U, imageattr.sendSets[0].xRange.discreteValues.size());
+ ASSERT_EQ(320U, imageattr.sendSets[0].xRange.discreteValues[0]);
+ ASSERT_EQ(1U, imageattr.sendSets[0].yRange.discreteValues.size());
+ ASSERT_EQ(240U, imageattr.sendSets[0].yRange.discreteValues[0]);
+ ASSERT_TRUE(imageattr.recvAll);
+ ASSERT_TRUE(imageattr.recvSets.empty());
+ }
+
+ {
+ SdpImageattrAttributeList::Imageattr imageattr(
+ ParseImageattr("8 send [x=320,y=240] [x=640,y=480] recv *"));
+ ASSERT_EQ(8U, *imageattr.pt);
+ ASSERT_FALSE(imageattr.sendAll);
+ ASSERT_EQ(2U, imageattr.sendSets.size());
+ ASSERT_EQ(1U, imageattr.sendSets[0].xRange.discreteValues.size());
+ ASSERT_EQ(320U, imageattr.sendSets[0].xRange.discreteValues[0]);
+ ASSERT_EQ(1U, imageattr.sendSets[0].yRange.discreteValues.size());
+ ASSERT_EQ(240U, imageattr.sendSets[0].yRange.discreteValues[0]);
+ ASSERT_EQ(1U, imageattr.sendSets[1].xRange.discreteValues.size());
+ ASSERT_EQ(640U, imageattr.sendSets[1].xRange.discreteValues[0]);
+ ASSERT_EQ(1U, imageattr.sendSets[1].yRange.discreteValues.size());
+ ASSERT_EQ(480U, imageattr.sendSets[1].yRange.discreteValues[0]);
+ ASSERT_TRUE(imageattr.recvAll);
+ ASSERT_TRUE(imageattr.recvSets.empty());
+ }
+
+ {
+ SdpImageattrAttributeList::Imageattr imageattr(
+ ParseImageattr("8 send * recv [x=320,y=240]"));
+ ASSERT_EQ(8U, *imageattr.pt);
+ ASSERT_FALSE(imageattr.recvAll);
+ ASSERT_EQ(1U, imageattr.recvSets.size());
+ ASSERT_EQ(1U, imageattr.recvSets[0].xRange.discreteValues.size());
+ ASSERT_EQ(320U, imageattr.recvSets[0].xRange.discreteValues[0]);
+ ASSERT_EQ(1U, imageattr.recvSets[0].yRange.discreteValues.size());
+ ASSERT_EQ(240U, imageattr.recvSets[0].yRange.discreteValues[0]);
+ ASSERT_TRUE(imageattr.sendAll);
+ ASSERT_TRUE(imageattr.sendSets.empty());
+ }
+
+ {
+ SdpImageattrAttributeList::Imageattr imageattr(
+ ParseImageattr("8 send * recv [x=320,y=240] [x=640,y=480]"));
+ ASSERT_EQ(8U, *imageattr.pt);
+ ASSERT_FALSE(imageattr.recvAll);
+ ASSERT_EQ(2U, imageattr.recvSets.size());
+ ASSERT_EQ(1U, imageattr.recvSets[0].xRange.discreteValues.size());
+ ASSERT_EQ(320U, imageattr.recvSets[0].xRange.discreteValues[0]);
+ ASSERT_EQ(1U, imageattr.recvSets[0].yRange.discreteValues.size());
+ ASSERT_EQ(240U, imageattr.recvSets[0].yRange.discreteValues[0]);
+ ASSERT_EQ(1U, imageattr.recvSets[1].xRange.discreteValues.size());
+ ASSERT_EQ(640U, imageattr.recvSets[1].xRange.discreteValues[0]);
+ ASSERT_EQ(1U, imageattr.recvSets[1].yRange.discreteValues.size());
+ ASSERT_EQ(480U, imageattr.recvSets[1].yRange.discreteValues[0]);
+ ASSERT_TRUE(imageattr.sendAll);
+ ASSERT_TRUE(imageattr.sendSets.empty());
+ }
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrParseInvalid)
+{
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>("", 0);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>(" ", 0);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>("-1", 0);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>("99999 ", 5);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>("*", 1);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>("* sen", 5);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>("* vcer *", 6);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>("* send x", 7);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>("* send [x=640,y=480] [",
+ 22);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>("* send * sen", 12);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>("* send * vcer *", 13);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>("* send * send *", 13);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>("* recv * recv *", 13);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>("* send * recv x", 14);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>(
+ "* send * recv [x=640,y=480] [", 29);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>(
+ "* send * recv [x=640,y=480] *", 28);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>(
+ "* send * recv [x=640,y=480] foobajooba", 28);
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrXYRangeSerialization)
+{
+ SdpImageattrAttributeList::XYRange range;
+ std::stringstream os;
+
+ range.min = 320;
+ range.max = 640;
+ range.Serialize(os);
+ ASSERT_EQ("[320:640]", os.str());
+ os.str(""); // clear
+
+ range.step = 16;
+ range.Serialize(os);
+ ASSERT_EQ("[320:16:640]", os.str());
+ os.str(""); // clear
+
+ range.min = 0;
+ range.max = 0;
+ range.discreteValues.push_back(320);
+ range.Serialize(os);
+ ASSERT_EQ("320", os.str());
+ os.str("");
+
+ range.discreteValues.push_back(640);
+ range.Serialize(os);
+ ASSERT_EQ("[320,640]", os.str());
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrSRangeSerialization)
+{
+ SdpImageattrAttributeList::SRange range;
+ std::ostringstream os;
+
+ range.min = 0.1f;
+ range.max = 0.9999f;
+ range.Serialize(os);
+ ASSERT_EQ("[0.1000-0.9999]", os.str());
+ os.str("");
+
+ range.min = 0.0f;
+ range.max = 0.0f;
+ range.discreteValues.push_back(0.1f);
+ range.Serialize(os);
+ ASSERT_EQ("0.1000", os.str());
+ os.str("");
+
+ range.discreteValues.push_back(0.5f);
+ range.Serialize(os);
+ ASSERT_EQ("[0.1000,0.5000]", os.str());
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrPRangeSerialization)
+{
+ SdpImageattrAttributeList::PRange range;
+ std::ostringstream os;
+
+ range.min = 0.1f;
+ range.max = 0.9999f;
+ range.Serialize(os);
+ ASSERT_EQ("[0.1000-0.9999]", os.str());
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrSetSerialization)
+{
+ SdpImageattrAttributeList::Set set;
+ std::ostringstream os;
+
+ set.xRange.discreteValues.push_back(640);
+ set.yRange.discreteValues.push_back(480);
+ set.Serialize(os);
+ ASSERT_EQ("[x=640,y=480]", os.str());
+ os.str("");
+
+ set.qValue = 0.00f;
+ set.Serialize(os);
+ ASSERT_EQ("[x=640,y=480,q=0.00]", os.str());
+ os.str("");
+
+ set.qValue = 0.10f;
+ set.Serialize(os);
+ ASSERT_EQ("[x=640,y=480,q=0.10]", os.str());
+ os.str("");
+
+ set.qValue = 1.00f;
+ set.Serialize(os);
+ ASSERT_EQ("[x=640,y=480,q=1.00]", os.str());
+ os.str("");
+
+ set.sRange.discreteValues.push_back(1.1f);
+ set.Serialize(os);
+ ASSERT_EQ("[x=640,y=480,sar=1.1000,q=1.00]", os.str());
+ os.str("");
+
+ set.pRange.min = 0.9f;
+ set.pRange.max = 1.1f;
+ set.Serialize(os);
+ ASSERT_EQ("[x=640,y=480,sar=1.1000,par=[0.9000-1.1000],q=1.00]", os.str());
+ os.str("");
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrSerialization)
+{
+ SdpImageattrAttributeList::Imageattr imageattr;
+ std::ostringstream os;
+
+ imageattr.sendAll = true;
+ imageattr.pt = Some<uint16_t>(8U);
+ imageattr.Serialize(os);
+ ASSERT_EQ("8 send *", os.str());
+ os.str("");
+
+ imageattr.pt.reset();
+ ;
+ imageattr.Serialize(os);
+ ASSERT_EQ("* send *", os.str());
+ os.str("");
+
+ imageattr.sendAll = false;
+ imageattr.recvAll = true;
+ imageattr.Serialize(os);
+ ASSERT_EQ("* recv *", os.str());
+ os.str("");
+
+ imageattr.sendAll = true;
+ imageattr.Serialize(os);
+ ASSERT_EQ("* send * recv *", os.str());
+ os.str("");
+
+ imageattr.sendAll = false;
+ imageattr.sendSets.push_back(SdpImageattrAttributeList::Set());
+ imageattr.sendSets.back().xRange.discreteValues.push_back(320);
+ imageattr.sendSets.back().yRange.discreteValues.push_back(240);
+ imageattr.Serialize(os);
+ ASSERT_EQ("* send [x=320,y=240] recv *", os.str());
+ os.str("");
+
+ imageattr.sendSets.push_back(SdpImageattrAttributeList::Set());
+ imageattr.sendSets.back().xRange.discreteValues.push_back(640);
+ imageattr.sendSets.back().yRange.discreteValues.push_back(480);
+ imageattr.Serialize(os);
+ ASSERT_EQ("* send [x=320,y=240] [x=640,y=480] recv *", os.str());
+ os.str("");
+
+ imageattr.recvAll = false;
+ imageattr.recvSets.push_back(SdpImageattrAttributeList::Set());
+ imageattr.recvSets.back().xRange.discreteValues.push_back(320);
+ imageattr.recvSets.back().yRange.discreteValues.push_back(240);
+ imageattr.Serialize(os);
+ ASSERT_EQ("* send [x=320,y=240] [x=640,y=480] recv [x=320,y=240]", os.str());
+ os.str("");
+
+ imageattr.recvSets.push_back(SdpImageattrAttributeList::Set());
+ imageattr.recvSets.back().xRange.discreteValues.push_back(640);
+ imageattr.recvSets.back().yRange.discreteValues.push_back(480);
+ imageattr.Serialize(os);
+ ASSERT_EQ(
+ "* send [x=320,y=240] [x=640,y=480] recv [x=320,y=240] [x=640,y=480]",
+ os.str());
+ os.str("");
+}
+
+TEST(NewSdpTestNoFixture, CheckSimulcastVersionSerialize)
+{
+ std::ostringstream os;
+
+ SdpSimulcastAttribute::Version version;
+ version.choices.push_back(SdpSimulcastAttribute::Encoding("8", false));
+ version.Serialize(os);
+ ASSERT_EQ("8", os.str());
+ os.str("");
+
+ version.choices.push_back(SdpSimulcastAttribute::Encoding("9", true));
+ version.Serialize(os);
+ ASSERT_EQ("8,~9", os.str());
+ os.str("");
+
+ version.choices.push_back(SdpSimulcastAttribute::Encoding("0", false));
+ version.Serialize(os);
+ ASSERT_EQ("8,~9,0", os.str());
+ os.str("");
+}
+
+static SdpSimulcastAttribute::Version ParseSimulcastVersion(
+ const std::string& input) {
+ std::istringstream is(input + ";");
+ std::string error;
+ SdpSimulcastAttribute::Version version;
+ EXPECT_TRUE(version.Parse(is, &error)) << error;
+ EXPECT_EQ(';', is.get());
+ EXPECT_EQ(EOF, is.get());
+ return version;
+}
+
+TEST(NewSdpTestNoFixture, CheckSimulcastVersionValidParse)
+{
+ {
+ SdpSimulcastAttribute::Version version(ParseSimulcastVersion("1"));
+ ASSERT_EQ(1U, version.choices.size());
+ ASSERT_EQ("1", version.choices[0].rid);
+ ASSERT_FALSE(version.choices[0].paused);
+ }
+
+ {
+ SdpSimulcastAttribute::Version version(ParseSimulcastVersion("1,~2"));
+ ASSERT_EQ(2U, version.choices.size());
+ ASSERT_EQ("1", version.choices[0].rid);
+ ASSERT_EQ("2", version.choices[1].rid);
+ ASSERT_FALSE(version.choices[0].paused);
+ ASSERT_TRUE(version.choices[1].paused);
+ }
+}
+
+TEST(NewSdpTestNoFixture, CheckSimulcastVersionInvalidParse)
+{
+ ParseInvalid<SdpSimulcastAttribute::Version>("", 0);
+ ParseInvalid<SdpSimulcastAttribute::Version>(",", 0);
+ ParseInvalid<SdpSimulcastAttribute::Version>(";", 0);
+ ParseInvalid<SdpSimulcastAttribute::Version>(" ", 0);
+ ParseInvalid<SdpSimulcastAttribute::Version>("8,", 2);
+ ParseInvalid<SdpSimulcastAttribute::Version>("8, ", 2);
+ ParseInvalid<SdpSimulcastAttribute::Version>("8,,", 2);
+ ParseInvalid<SdpSimulcastAttribute::Version>("8,;", 2);
+}
+
+TEST(NewSdpTestNoFixture, CheckSimulcastVersionsSerialize)
+{
+ std::ostringstream os;
+
+ SdpSimulcastAttribute::Versions versions;
+ versions.push_back(SdpSimulcastAttribute::Version());
+ versions.back().choices.push_back(
+ SdpSimulcastAttribute::Encoding("8", false));
+ versions.Serialize(os);
+ ASSERT_EQ("8", os.str());
+ os.str("");
+
+ versions.push_back(SdpSimulcastAttribute::Version());
+ versions.Serialize(os);
+ ASSERT_EQ("8", os.str());
+ os.str("");
+
+ versions.back().choices.push_back(SdpSimulcastAttribute::Encoding("9", true));
+ versions.Serialize(os);
+ ASSERT_EQ("8;~9", os.str());
+ os.str("");
+
+ versions.push_back(SdpSimulcastAttribute::Version());
+ versions.back().choices.push_back(
+ SdpSimulcastAttribute::Encoding("0", false));
+ versions.Serialize(os);
+ ASSERT_EQ("8;~9;0", os.str());
+ os.str("");
+}
+
+static SdpSimulcastAttribute::Versions ParseSimulcastVersions(
+ const std::string& input) {
+ std::istringstream is(input + " ");
+ std::string error;
+ SdpSimulcastAttribute::Versions list;
+ EXPECT_TRUE(list.Parse(is, &error)) << error;
+ EXPECT_EQ(' ', is.get());
+ EXPECT_EQ(EOF, is.get());
+ return list;
+}
+
+TEST(NewSdpTestNoFixture, CheckSimulcastVersionsValidParse)
+{
+ {
+ SdpSimulcastAttribute::Versions versions(ParseSimulcastVersions("8"));
+ ASSERT_EQ(1U, versions.size());
+ ASSERT_EQ(1U, versions[0].choices.size());
+ ASSERT_EQ("8", versions[0].choices[0].rid);
+ ASSERT_FALSE(versions[0].choices[0].paused);
+ }
+
+ {
+ SdpSimulcastAttribute::Versions versions(ParseSimulcastVersions("~8,9"));
+ ASSERT_EQ(1U, versions.size());
+ ASSERT_EQ(2U, versions[0].choices.size());
+ ASSERT_EQ("8", versions[0].choices[0].rid);
+ ASSERT_EQ("9", versions[0].choices[1].rid);
+ ASSERT_TRUE(versions[0].choices[0].paused);
+ ASSERT_FALSE(versions[0].choices[1].paused);
+ }
+
+ {
+ SdpSimulcastAttribute::Versions versions(ParseSimulcastVersions("8,9;~10"));
+ ASSERT_EQ(2U, versions.size());
+ ASSERT_EQ(2U, versions[0].choices.size());
+ ASSERT_EQ("8", versions[0].choices[0].rid);
+ ASSERT_EQ("9", versions[0].choices[1].rid);
+ ASSERT_FALSE(versions[0].choices[0].paused);
+ ASSERT_FALSE(versions[0].choices[1].paused);
+ ASSERT_EQ(1U, versions[1].choices.size());
+ ASSERT_EQ("10", versions[1].choices[0].rid);
+ ASSERT_TRUE(versions[1].choices[0].paused);
+ }
+}
+
+TEST(NewSdpTestNoFixture, CheckSimulcastVersionsInvalidParse)
+{
+ ParseInvalid<SdpSimulcastAttribute::Versions>("", 0);
+ ParseInvalid<SdpSimulcastAttribute::Versions>(";", 0);
+ ParseInvalid<SdpSimulcastAttribute::Versions>("8;", 2);
+ ParseInvalid<SdpSimulcastAttribute::Versions>("8;;", 2);
+}
+
+TEST(NewSdpTestNoFixture, CheckSimulcastSerialize)
+{
+ std::ostringstream os;
+
+ SdpSimulcastAttribute simulcast;
+ simulcast.recvVersions.push_back(SdpSimulcastAttribute::Version());
+ simulcast.recvVersions.back().choices.push_back(
+ SdpSimulcastAttribute::Encoding("8", false));
+ simulcast.Serialize(os);
+ ASSERT_EQ("a=simulcast:recv 8" CRLF, os.str());
+ os.str("");
+
+ simulcast.sendVersions.push_back(SdpSimulcastAttribute::Version());
+ simulcast.sendVersions.back().choices.push_back(
+ SdpSimulcastAttribute::Encoding("9", true));
+ simulcast.Serialize(os);
+ ASSERT_EQ("a=simulcast:send ~9 recv 8" CRLF, os.str());
+}
+
+static SdpSimulcastAttribute ParseSimulcast(const std::string& input) {
+ std::istringstream is(input);
+ std::string error;
+ SdpSimulcastAttribute simulcast;
+ EXPECT_TRUE(simulcast.Parse(is, &error)) << error;
+ EXPECT_TRUE(is.eof());
+ return simulcast;
+}
+
+TEST(NewSdpTestNoFixture, CheckSimulcastValidParse)
+{
+ {
+ SdpSimulcastAttribute simulcast(ParseSimulcast("send 8"));
+ ASSERT_EQ(1U, simulcast.sendVersions.size());
+ ASSERT_EQ(1U, simulcast.sendVersions[0].choices.size());
+ ASSERT_EQ("8", simulcast.sendVersions[0].choices[0].rid);
+ ASSERT_FALSE(simulcast.sendVersions[0].choices[0].paused);
+ ASSERT_EQ(0U, simulcast.recvVersions.size());
+ }
+
+ {
+ SdpSimulcastAttribute simulcast(ParseSimulcast(" SEND 8"));
+ ASSERT_EQ(1U, simulcast.sendVersions.size());
+ ASSERT_EQ(1U, simulcast.sendVersions[0].choices.size());
+ ASSERT_EQ("8", simulcast.sendVersions[0].choices[0].rid);
+ ASSERT_FALSE(simulcast.sendVersions[0].choices[0].paused);
+ ASSERT_EQ(0U, simulcast.recvVersions.size());
+ }
+
+ {
+ SdpSimulcastAttribute simulcast(ParseSimulcast("recv 8"));
+ ASSERT_EQ(1U, simulcast.recvVersions.size());
+ ASSERT_EQ(1U, simulcast.recvVersions[0].choices.size());
+ ASSERT_EQ("8", simulcast.recvVersions[0].choices[0].rid);
+ ASSERT_FALSE(simulcast.recvVersions[0].choices[0].paused);
+ ASSERT_EQ(0U, simulcast.sendVersions.size());
+ }
+
+ {
+ SdpSimulcastAttribute simulcast(
+ ParseSimulcast("send 8,9;~101;97,~98 recv 101,120;97"));
+ ASSERT_EQ(3U, simulcast.sendVersions.size());
+ ASSERT_EQ(2U, simulcast.sendVersions[0].choices.size());
+ ASSERT_EQ("8", simulcast.sendVersions[0].choices[0].rid);
+ ASSERT_EQ("9", simulcast.sendVersions[0].choices[1].rid);
+ ASSERT_FALSE(simulcast.sendVersions[0].choices[0].paused);
+ ASSERT_FALSE(simulcast.sendVersions[0].choices[1].paused);
+ ASSERT_EQ(1U, simulcast.sendVersions[1].choices.size());
+ ASSERT_EQ("101", simulcast.sendVersions[1].choices[0].rid);
+ ASSERT_TRUE(simulcast.sendVersions[1].choices[0].paused);
+ ASSERT_EQ(2U, simulcast.sendVersions[2].choices.size());
+ ASSERT_EQ("97", simulcast.sendVersions[2].choices[0].rid);
+ ASSERT_EQ("98", simulcast.sendVersions[2].choices[1].rid);
+ ASSERT_FALSE(simulcast.sendVersions[2].choices[0].paused);
+ ASSERT_TRUE(simulcast.sendVersions[2].choices[1].paused);
+
+ ASSERT_EQ(2U, simulcast.recvVersions.size());
+ ASSERT_EQ(2U, simulcast.recvVersions[0].choices.size());
+ ASSERT_EQ("101", simulcast.recvVersions[0].choices[0].rid);
+ ASSERT_EQ("120", simulcast.recvVersions[0].choices[1].rid);
+ ASSERT_EQ(1U, simulcast.recvVersions[1].choices.size());
+ ASSERT_EQ("97", simulcast.recvVersions[1].choices[0].rid);
+ }
+}
+
+TEST(NewSdpTestNoFixture, CheckSimulcastInvalidParse)
+{
+ ParseInvalid<SdpSimulcastAttribute>("", 0);
+ ParseInvalid<SdpSimulcastAttribute>(" ", 1);
+ ParseInvalid<SdpSimulcastAttribute>("vcer ", 4);
+ ParseInvalid<SdpSimulcastAttribute>(" send 8 send ", 12);
+ ParseInvalid<SdpSimulcastAttribute>(" recv 8 recv ", 12);
+}
+
+static SdpRidAttributeList::Rid ParseRid(const std::string& input) {
+ std::istringstream is(input);
+ std::string error;
+ SdpRidAttributeList::Rid rid;
+ EXPECT_TRUE(rid.Parse(is, &error)) << error;
+ EXPECT_TRUE(is.eof());
+ return rid;
+}
+
+TEST(NewSdpTestNoFixture, CheckRidValidParse)
+{
+ {
+ SdpRidAttributeList::Rid rid(ParseRid("1 send"));
+ ASSERT_EQ("1", rid.id);
+ ASSERT_EQ(sdp::kSend, rid.direction);
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(0U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_FALSE(rid.constraints.maxFps.isSome());
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(ParseRid("1 send pt=96;max-width=800"));
+ ASSERT_EQ("1", rid.id);
+ ASSERT_EQ(sdp::kSend, rid.direction);
+ ASSERT_EQ(1U, rid.formats.size());
+ ASSERT_EQ(96U, rid.formats[0]);
+ ASSERT_EQ(800U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_FALSE(rid.constraints.maxFps.isSome());
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(ParseRid("1 send pt=96,97,98;max-width=800"));
+ ASSERT_EQ("1", rid.id);
+ ASSERT_EQ(sdp::kSend, rid.direction);
+ ASSERT_EQ(3U, rid.formats.size());
+ ASSERT_EQ(96U, rid.formats[0]);
+ ASSERT_EQ(97U, rid.formats[1]);
+ ASSERT_EQ(98U, rid.formats[2]);
+ ASSERT_EQ(800U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_FALSE(rid.constraints.maxFps.isSome());
+ ASSERT_FALSE(rid.constraints.maxFps.isSome());
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(ParseRid("foo send"));
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(0U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_FALSE(rid.constraints.maxFps.isSome());
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(ParseRid("foo send pt=96"));
+ ASSERT_EQ(1U, rid.formats.size());
+ ASSERT_EQ(96U, rid.formats[0]);
+ ASSERT_EQ(0U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_FALSE(rid.constraints.maxFps.isSome());
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ // This is not technically permitted by the BNF, but the parse code is simpler
+ // if we allow it. If we decide to stop allowing this, this will need to be
+ // converted to an invalid parse test-case.
+ {
+ SdpRidAttributeList::Rid rid(ParseRid("foo send max-br=30000;pt=96"));
+ ASSERT_EQ(1U, rid.formats.size());
+ ASSERT_EQ(96U, rid.formats[0]);
+ ASSERT_EQ(0U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_FALSE(rid.constraints.maxFps.isSome());
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(30000U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(ParseRid("foo send pt=96,97,98"));
+ ASSERT_EQ(3U, rid.formats.size());
+ ASSERT_EQ(96U, rid.formats[0]);
+ ASSERT_EQ(97U, rid.formats[1]);
+ ASSERT_EQ(98U, rid.formats[2]);
+ ASSERT_EQ(0U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_FALSE(rid.constraints.maxFps.isSome());
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(ParseRid("foo send max-width=800"));
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(800U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_FALSE(rid.constraints.maxFps.isSome());
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(ParseRid("foo send max-height=640"));
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(0U, rid.constraints.maxWidth);
+ ASSERT_EQ(640U, rid.constraints.maxHeight);
+ ASSERT_FALSE(rid.constraints.maxFps.isSome());
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(ParseRid("foo send max-fps=30"));
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(0U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_EQ(30.0, *rid.constraints.maxFps);
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(ParseRid("foo send max-fs=3600"));
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(0U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_FALSE(rid.constraints.maxFps.isSome());
+ ASSERT_EQ(3600U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(ParseRid("foo send max-br=30000"));
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(0U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_FALSE(rid.constraints.maxFps.isSome());
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(30000U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(ParseRid("foo send max-pps=9216000"));
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(0U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_FALSE(rid.constraints.maxFps.isSome());
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(9216000U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(ParseRid("foo send depend=foo"));
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(0U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_FALSE(rid.constraints.maxFps.isSome());
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(1U, rid.dependIds.size());
+ ASSERT_EQ("foo", rid.dependIds[0]);
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(ParseRid("foo send max-foo=20"));
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(0U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_FALSE(rid.constraints.maxFps.isSome());
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(ParseRid("foo send depend=foo,bar"));
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(0U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_FALSE(rid.constraints.maxFps.isSome());
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(2U, rid.dependIds.size());
+ ASSERT_EQ("foo", rid.dependIds[0]);
+ ASSERT_EQ("bar", rid.dependIds[1]);
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(
+ ParseRid("foo send max-width=800;max-height=600"));
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(800U, rid.constraints.maxWidth);
+ ASSERT_EQ(600U, rid.constraints.maxHeight);
+ ASSERT_FALSE(rid.constraints.maxFps.isSome());
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(
+ ParseRid("foo send pt=96,97;max-width=800;max-height=600"));
+ ASSERT_EQ(2U, rid.formats.size());
+ ASSERT_EQ(96U, rid.formats[0]);
+ ASSERT_EQ(97U, rid.formats[1]);
+ ASSERT_EQ(800U, rid.constraints.maxWidth);
+ ASSERT_EQ(600U, rid.constraints.maxHeight);
+ ASSERT_FALSE(rid.constraints.maxFps.isSome());
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(
+ ParseRid("foo send depend=foo,bar;max-width=800;max-height=600"));
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(800U, rid.constraints.maxWidth);
+ ASSERT_EQ(600U, rid.constraints.maxHeight);
+ ASSERT_FALSE(rid.constraints.maxFps.isSome());
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(2U, rid.dependIds.size());
+ ASSERT_EQ("foo", rid.dependIds[0]);
+ ASSERT_EQ("bar", rid.dependIds[1]);
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(
+ ParseRid("foo send max-foo=20;max-width=800;max-height=600"));
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(800U, rid.constraints.maxWidth);
+ ASSERT_EQ(600U, rid.constraints.maxHeight);
+ ASSERT_FALSE(rid.constraints.maxFps.isSome());
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+}
+
+TEST(NewSdpTestNoFixture, CheckRidInvalidParse)
+{
+ ParseInvalid<SdpRidAttributeList::Rid>("", 0);
+ ParseInvalid<SdpRidAttributeList::Rid>(" ", 0);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo", 3);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo ", 4);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo ", 5);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo bar", 7);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo recv ", 9);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo recv pt=", 12);
+ ParseInvalid<SdpRidAttributeList::Rid>(" ", 0);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send pt", 11);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send pt=", 12);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send pt=x", 12);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send pt=-1", 12);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send pt=96,", 15);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send pt=196", 15);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send max-width", 18);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send max-width=", 19);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send max-width=x", 19);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send max-width=-1", 19);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send max-width=800;", 23);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send max-width=800; ", 24);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send depend=", 16);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send depend=,", 16);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send depend=1,", 18);
+ ParseInvalid<SdpRidAttributeList::Rid>("0123456789az-_", 14);
+}
+
+TEST(NewSdpTestNoFixture, CheckRidSerialize)
+{
+ {
+ SdpRidAttributeList::Rid rid;
+ rid.id = "foo";
+ rid.direction = sdp::kSend;
+ std::ostringstream os;
+ rid.Serialize(os);
+ ASSERT_EQ("foo send", os.str());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid;
+ rid.id = "foo";
+ rid.direction = sdp::kSend;
+ std::ostringstream os;
+ rid.Serialize(os);
+ ASSERT_EQ("foo send", os.str());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid;
+ rid.id = "foo";
+ rid.direction = sdp::kSend;
+ rid.formats.push_back(96);
+ std::ostringstream os;
+ rid.Serialize(os);
+ ASSERT_EQ("foo send pt=96", os.str());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid;
+ rid.id = "foo";
+ rid.direction = sdp::kSend;
+ rid.formats.push_back(96);
+ rid.formats.push_back(97);
+ std::ostringstream os;
+ rid.Serialize(os);
+ ASSERT_EQ("foo send pt=96,97", os.str());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid;
+ rid.id = "foo";
+ rid.direction = sdp::kSend;
+ rid.constraints.maxWidth = 800;
+ std::ostringstream os;
+ rid.Serialize(os);
+ ASSERT_EQ("foo send max-width=800", os.str());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid;
+ rid.id = "foo";
+ rid.direction = sdp::kSend;
+ rid.constraints.maxHeight = 600;
+ std::ostringstream os;
+ rid.Serialize(os);
+ ASSERT_EQ("foo send max-height=600", os.str());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid;
+ rid.id = "foo";
+ rid.direction = sdp::kSend;
+ rid.constraints.maxFps = Some(30);
+ std::ostringstream os;
+ rid.Serialize(os);
+ ASSERT_EQ("foo send max-fps=30", os.str());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid;
+ rid.id = "foo";
+ rid.direction = sdp::kSend;
+ rid.constraints.maxFs = 3600;
+ std::ostringstream os;
+ rid.Serialize(os);
+ ASSERT_EQ("foo send max-fs=3600", os.str());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid;
+ rid.id = "foo";
+ rid.direction = sdp::kSend;
+ rid.constraints.maxBr = 30000;
+ std::ostringstream os;
+ rid.Serialize(os);
+ ASSERT_EQ("foo send max-br=30000", os.str());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid;
+ rid.id = "foo";
+ rid.direction = sdp::kSend;
+ rid.constraints.maxPps = 9216000;
+ std::ostringstream os;
+ rid.Serialize(os);
+ ASSERT_EQ("foo send max-pps=9216000", os.str());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid;
+ rid.id = "foo";
+ rid.direction = sdp::kSend;
+ rid.dependIds.push_back("foo");
+ std::ostringstream os;
+ rid.Serialize(os);
+ ASSERT_EQ("foo send depend=foo", os.str());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid;
+ rid.id = "foo";
+ rid.direction = sdp::kSend;
+ rid.dependIds.push_back("foo");
+ rid.dependIds.push_back("bar");
+ std::ostringstream os;
+ rid.Serialize(os);
+ ASSERT_EQ("foo send depend=foo,bar", os.str());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid;
+ rid.id = "foo";
+ rid.direction = sdp::kSend;
+ rid.formats.push_back(96);
+ rid.constraints.maxBr = 30000;
+ std::ostringstream os;
+ rid.Serialize(os);
+ ASSERT_EQ("foo send pt=96;max-br=30000", os.str());
+ }
+}
+
+TEST_F(SdpTest, hugeSdp) {
+ std::string offer =
+ "v=0\r\n"
+ "o=- 1109973417102828257 2 IN IP4 127.0.0.1\r\n"
+ "s=-\r\n"
+ "t=0 0\r\n"
+ "a=group:BUNDLE audio video\r\n"
+ "a=msid-semantic: WMS 1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIP\r\n"
+ "m=audio 32952 UDP/TLS/RTP/SAVPF 111 103 104 0 8 107 106 105 13 126\r\n"
+ "c=IN IP4 128.64.32.16\r\n"
+ "a=rtcp:32952 IN IP4 128.64.32.16\r\n"
+ "a=candidate:77142221 1 udp 2113937151 192.168.137.1 54081 typ host "
+ "generation 0\r\n"
+ "a=candidate:77142221 2 udp 2113937151 192.168.137.1 54081 typ host "
+ "generation 0\r\n"
+ "a=candidate:983072742 1 udp 2113937151 172.22.0.56 54082 typ host "
+ "generation 0\r\n"
+ "a=candidate:983072742 2 udp 2113937151 172.22.0.56 54082 typ host "
+ "generation 0\r\n"
+ "a=candidate:2245074553 1 udp 1845501695 32.64.128.1 62397 typ srflx "
+ "raddr 192.168.137.1 rport 54081 generation 0\r\n"
+ "a=candidate:2245074553 2 udp 1845501695 32.64.128.1 62397 typ srflx "
+ "raddr 192.168.137.1 rport 54081 generation 0\r\n"
+ "a=candidate:2479353907 1 udp 1845501695 32.64.128.1 54082 typ srflx "
+ "raddr 172.22.0.56 rport 54082 generation 0\r\n"
+ "a=candidate:2479353907 2 udp 1845501695 32.64.128.1 54082 typ srflx "
+ "raddr 172.22.0.56 rport 54082 generation 0\r\n"
+ "a=candidate:1243276349 1 tcp 1509957375 192.168.137.1 0 typ host "
+ "generation 0\r\n"
+ "a=candidate:1243276349 2 tcp 1509957375 192.168.137.1 0 typ host "
+ "generation 0\r\n"
+ "a=candidate:1947960086 1 tcp 1509957375 172.22.0.56 0 typ host "
+ "generation 0\r\n"
+ "a=candidate:1947960086 2 tcp 1509957375 172.22.0.56 0 typ host "
+ "generation 0\r\n"
+ "a=candidate:1808221584 1 udp 33562367 128.64.32.16 32952 typ relay "
+ "raddr 32.64.128.1 rport 62398 generation 0\r\n"
+ "a=candidate:1808221584 2 udp 33562367 128.64.32.16 32952 typ relay "
+ "raddr 32.64.128.1 rport 62398 generation 0\r\n"
+ "a=candidate:507872740 1 udp 33562367 128.64.32.16 40975 typ relay raddr "
+ "32.64.128.1 rport 54085 generation 0\r\n"
+ "a=candidate:507872740 2 udp 33562367 128.64.32.16 40975 typ relay raddr "
+ "32.64.128.1 rport 54085 generation 0\r\n"
+ "a=ice-ufrag:xQuJwjX3V3eMA81k\r\n"
+ "a=ice-pwd:ZUiRmjS2GDhG140p73dAsSVP\r\n"
+ "a=ice-options:google-ice\r\n"
+ "a=fingerprint:sha-256 "
+ "59:4A:8B:73:A7:73:53:71:88:D7:4D:58:28:0C:79:72:31:29:9B:05:37:DD:58:43:"
+ "C2:D4:85:A2:B3:66:38:7A\r\n"
+ "a=setup:active\r\n"
+ "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n"
+ "a=sendrecv\r\n"
+ "a=mid:audio\r\n"
+ "a=rtcp-mux\r\n"
+ "a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
+ "inline:/U44g3ULdtapeiSg+T3n6dDLBKIjpOhb/NXAL/2b\r\n"
+ "a=rtpmap:111 opus/48000/2\r\n"
+ "a=fmtp:111 minptime=10\r\n"
+ "a=rtpmap:103 ISAC/16000\r\n"
+ "a=rtpmap:104 ISAC/32000\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=rtpmap:8 PCMA/8000\r\n"
+ "a=rtpmap:107 CN/48000\r\n"
+ "a=rtpmap:106 CN/32000\r\n"
+ "a=rtpmap:105 CN/16000\r\n"
+ "a=rtpmap:13 CN/8000\r\n"
+ "a=rtpmap:126 telephone-event/8000\r\n"
+ "a=maxptime:60\r\n"
+ "a=ssrc:2271517329 cname:mKDNt7SQf6pwDlIn\r\n"
+ "a=ssrc:2271517329 msid:1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIP "
+ "1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIPa0\r\n"
+ "a=ssrc:2271517329 mslabel:1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIP\r\n"
+ "a=ssrc:2271517329 label:1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIPa0\r\n"
+ "m=video 32952 UDP/TLS/RTP/SAVPF 100 116 117\r\n"
+ "c=IN IP4 128.64.32.16\r\n"
+ "a=rtcp:32952 IN IP4 128.64.32.16\r\n"
+ "a=candidate:77142221 1 udp 2113937151 192.168.137.1 54081 typ host "
+ "generation 0\r\n"
+ "a=candidate:77142221 2 udp 2113937151 192.168.137.1 54081 typ host "
+ "generation 0\r\n"
+ "a=candidate:983072742 1 udp 2113937151 172.22.0.56 54082 typ host "
+ "generation 0\r\n"
+ "a=candidate:983072742 2 udp 2113937151 172.22.0.56 54082 typ host "
+ "generation 0\r\n"
+ "a=candidate:2245074553 1 udp 1845501695 32.64.128.1 62397 typ srflx "
+ "raddr 192.168.137.1 rport 54081 generation 0\r\n"
+ "a=candidate:2245074553 2 udp 1845501695 32.64.128.1 62397 typ srflx "
+ "raddr 192.168.137.1 rport 54081 generation 0\r\n"
+ "a=candidate:2479353907 1 udp 1845501695 32.64.128.1 54082 typ srflx "
+ "raddr 172.22.0.56 rport 54082 generation 0\r\n"
+ "a=candidate:2479353907 2 udp 1845501695 32.64.128.1 54082 typ srflx "
+ "raddr 172.22.0.56 rport 54082 generation 0\r\n"
+ "a=candidate:1243276349 1 tcp 1509957375 192.168.137.1 0 typ host "
+ "generation 0\r\n"
+ "a=candidate:1243276349 2 tcp 1509957375 192.168.137.1 0 typ host "
+ "generation 0\r\n"
+ "a=candidate:1947960086 1 tcp 1509957375 172.22.0.56 0 typ host "
+ "generation 0\r\n"
+ "a=candidate:1947960086 2 tcp 1509957375 172.22.0.56 0 typ host "
+ "generation 0\r\n"
+ "a=candidate:1808221584 1 udp 33562367 128.64.32.16 32952 typ relay "
+ "raddr 32.64.128.1 rport 62398 generation 0\r\n"
+ "a=candidate:1808221584 2 udp 33562367 128.64.32.16 32952 typ relay "
+ "raddr 32.64.128.1 rport 62398 generation 0\r\n"
+ "a=candidate:507872740 1 udp 33562367 128.64.32.16 40975 typ relay raddr "
+ "32.64.128.1 rport 54085 generation 0\r\n"
+ "a=candidate:507872740 2 udp 33562367 128.64.32.16 40975 typ relay raddr "
+ "32.64.128.1 rport 54085 generation 0\r\n"
+ "a=ice-ufrag:xQuJwjX3V3eMA81k\r\n"
+ "a=ice-pwd:ZUiRmjS2GDhG140p73dAsSVP\r\n"
+ "a=ice-options:google-ice\r\n"
+ "a=fingerprint:sha-256 "
+ "59:4A:8B:73:A7:73:53:71:88:D7:4D:58:28:0C:79:72:31:29:9B:05:37:DD:58:43:"
+ "C2:D4:85:A2:B3:66:38:7A\r\n"
+ "a=setup:active\r\n"
+ "a=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r\n"
+ "a=extmap:3 "
+ "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n"
+ "a=extmap:6 "
+ "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\n"
+ "a=sendrecv\r\n"
+ "a=mid:video\r\n"
+ "a=rtcp-mux\r\n"
+ "a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
+ "inline:/U44g3ULdtapeiSg+T3n6dDLBKIjpOhb/NXAL/2b\r\n"
+ "a=rtpmap:100 VP8/90000\r\n"
+ "a=rtcp-fb:100 ccm fir\r\n"
+ "a=rtcp-fb:100 nack\r\n"
+ "a=rtcp-fb:100 goog-remb\r\n"
+ "a=rtpmap:116 red/90000\r\n"
+ "a=rtpmap:117 ulpfec/90000\r\n"
+ "a=ssrc:54724160 cname:mKDNt7SQf6pwDlIn\r\n"
+ "a=ssrc:54724160 msid:1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIP "
+ "1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIPv0\r\n"
+ "a=ssrc:54724160 mslabel:1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIP\r\n"
+ "a=ssrc:54724160 label:1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIPv0\r\n";
+
+ ParseSdp(offer);
+}
+
+TEST_P(NewSdpTest, CheckSsrcGroup) {
+ ParseSdp(kVideoSdp + "a=ssrc-group:\r\n", false);
+ ParseSdp(kVideoSdp + "a=ssrc-group:BLAH\r\n", false);
+ ParseSdp(kVideoSdp + "a=ssrc-group:FID\r\n", false);
+ ParseSdp(kVideoSdp + "a=ssrc-group:FID 1 2 3 four\r\n", false);
+ ParseSdp(kVideoSdp + "a=ssrc-group:FID 1\r\n");
+
+ std::array<char, 8192> gigantic = {{0}};
+ gigantic.fill('A');
+ gigantic[gigantic.size() - 1] = 0;
+ ParseSdp(kVideoSdp + "a=ssrc-group:" + gigantic.data() + "\r\n", false);
+
+ ParseSdp(kVideoSdp + "a=ssrc-group:fid 1\r\n");
+ ParseSdp(kVideoSdp + "a=ssrc-group:FID \r\n", false);
+ ParseSdp(kVideoSdp + "a=ssrc-group:FID 0\r\n");
+ ParseSdp(kVideoSdp + "a=ssrc-group:FID 9999999999\r\n", false);
+ ParseSdp(kVideoSdp + "a=ssrc-group:FID 99999999999999999999\r\n", false);
+ ParseSdp(kVideoSdp + "a=ssrc-group:FID 1twothree\r\n", false);
+ ParseSdp(kVideoSdp + "a=ssrc-group:FID -1\r\n", false);
+
+ ParseSdp(kVideoSdp + "a=ssrc-group:FID 3156517279 2673335628\r\n");
+ ASSERT_EQ(1U, Sdp()->GetMediaSectionCount());
+
+ const SdpSsrcGroupAttributeList& group =
+ Sdp()->GetMediaSection(0).GetAttributeList().GetSsrcGroup();
+
+ ASSERT_EQ(1U, group.mSsrcGroups.size());
+ ASSERT_EQ(SdpSsrcGroupAttributeList::Semantics::kFid,
+ group.mSsrcGroups[0].semantics);
+ ASSERT_EQ(2U, group.mSsrcGroups[0].ssrcs.size());
+ ASSERT_EQ(3156517279U, group.mSsrcGroups[0].ssrcs[0]);
+ ASSERT_EQ(2673335628U, group.mSsrcGroups[0].ssrcs[1]);
+}
+
+TEST_P(NewSdpTest, CheckSsrcGroupSerialization) {
+ std::vector ssrcs = {3156517279U, 2673335628U};
+ {
+ SdpSsrcGroupAttributeList list;
+ list.PushEntry(SdpSsrcGroupAttributeList::Semantics::kFec, ssrcs);
+ CheckSerialize("a=ssrc-group:FEC 3156517279 2673335628\r\n", list);
+ }
+ {
+ SdpSsrcGroupAttributeList list;
+ list.PushEntry(SdpSsrcGroupAttributeList::Semantics::kFid, ssrcs);
+ CheckSerialize("a=ssrc-group:FID 3156517279 2673335628\r\n", list);
+ }
+ {
+ SdpSsrcGroupAttributeList list;
+ list.PushEntry(SdpSsrcGroupAttributeList::Semantics::kFecFr, ssrcs);
+ CheckSerialize("a=ssrc-group:FEC-FR 3156517279 2673335628\r\n", list);
+ }
+ {
+ SdpSsrcGroupAttributeList list;
+ list.PushEntry(SdpSsrcGroupAttributeList::Semantics::kDup, ssrcs);
+ CheckSerialize("a=ssrc-group:DUP 3156517279 2673335628\r\n", list);
+ }
+ {
+ SdpSsrcGroupAttributeList list;
+ list.PushEntry(SdpSsrcGroupAttributeList::Semantics::kSim, ssrcs);
+ CheckSerialize("a=ssrc-group:SIM 3156517279 2673335628\r\n", list);
+ }
+}
+
+TEST_P(NewSdpTest, CheckRtxApt) {
+ ParseSdp(kVideoSdp + "a=rtpmap:96 rtx/90000\r\na=fmtp:96", false);
+ ParseSdp(kVideoSdp +
+ "a=rtpmap:96 rtx/90000\r\na=fmtp:96 apt=120; apt=124\r\n");
+ ParseSdp(kVideoSdp + "a=rtpmap:96 rtx/90000\r\na=fmtp:96 apt=", false);
+ ParseSdp(kVideoSdp + "a=rtpmap:96 rtx/90000\r\na=fmtp:96 apt=blah", false);
+ ParseSdp(kVideoSdp +
+ "a=rtpmap:96 rtx/90000\r\na=fmtp:96 "
+ "apt=999999999999999999999999999999",
+ false);
+ ParseSdp(kVideoSdp + "a=rtpmap:96 rtx/90000\r\na=fmtp:96 apt=-120\r\n",
+ false);
+ ParseSdp(kVideoSdp + "a=rtpmap:96 rtx/90000\r\na=fmtp:96 apt=1twenty\r\n",
+ false);
+ ParseSdp(kVideoSdp + "a=rtpmap:96 rtx/90000\r\na=fmtp:96 APT=120\r\n");
+
+ ParseSdp(kVideoSdp + "a=rtpmap:96 rtx/90000\r\na=fmtp:96 apt=120\r\n");
+
+ const SdpMediaSection& msec = Sdp()->GetMediaSection(0);
+ ASSERT_EQ(1U, msec.GetFormats().size());
+ ASSERT_EQ(2U, msec.GetAttributeList().GetRtpmap().mRtpmaps.size());
+ const SdpRtpmapAttributeList::Rtpmap& rtpmap =
+ msec.GetAttributeList().GetRtpmap().GetEntry("96");
+ ASSERT_EQ(rtpmap.pt, "96");
+ ASSERT_EQ(rtpmap.codec, SdpRtpmapAttributeList::CodecType::kRtx);
+ ASSERT_EQ(rtpmap.name, "rtx");
+ ASSERT_EQ(rtpmap.clock, 90000U);
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kFmtpAttribute));
+ const auto& format_params = msec.GetAttributeList().GetFmtp().mFmtps;
+ ASSERT_EQ(1U, format_params.size());
+ ASSERT_EQ("96", format_params[0].format);
+ ASSERT_EQ(SdpRtpmapAttributeList::CodecType::kRtx,
+ format_params[0].parameters->codec_type);
+ ASSERT_EQ(static_cast<uint8_t>(120),
+ static_cast<SdpFmtpAttributeList::RtxParameters*>(
+ format_params[0].parameters.get())
+ ->apt);
+ ASSERT_EQ(Nothing(), static_cast<SdpFmtpAttributeList::RtxParameters*>(
+ format_params[0].parameters.get())
+ ->rtx_time);
+}
+
+TEST_P(NewSdpTest, CheckRtxAptRtxTime) {
+ ParseSdp(kVideoSdp + "a=rtpmap:96 rtx/90000\r\na=fmtp:96 apt=120;rtx-time=",
+ false);
+ ParseSdp(
+ kVideoSdp + "a=rtpmap:96 rtx/90000\r\na=fmtp:96 apt=120;rtx-time=blah",
+ false);
+ ParseSdp(kVideoSdp +
+ "a=rtpmap:96 rtx/90000\r\na=fmtp:96 "
+ "apt=120;rtx-time=9999999999999999999999999999999",
+ false);
+ ParseSdp(
+ kVideoSdp + "a=rtpmap:96 rtx/90000\r\na=fmtp:96 apt=120;rtx-time=-3000",
+ false);
+ ParseSdp(kVideoSdp +
+ "a=rtpmap:96 rtx/90000\r\na=fmtp:96 apt=120;rtx-time=3thousand",
+ false);
+
+ {
+ ParseSdp(kVideoSdp +
+ "a=rtpmap:96 rtx/90000\r\na=fmtp:96 apt=120;RTX-TIME=3000\r\n");
+
+ const SdpMediaSection& msec = Sdp()->GetMediaSection(0);
+ ASSERT_EQ(1U, msec.GetFormats().size());
+ ASSERT_EQ(2U, msec.GetAttributeList().GetRtpmap().mRtpmaps.size());
+ const SdpRtpmapAttributeList::Rtpmap& rtpmap =
+ msec.GetAttributeList().GetRtpmap().GetEntry("96");
+ ASSERT_EQ(rtpmap.pt, "96");
+ ASSERT_EQ(rtpmap.codec, SdpRtpmapAttributeList::CodecType::kRtx);
+ ASSERT_EQ(rtpmap.name, "rtx");
+ ASSERT_EQ(rtpmap.clock, 90000U);
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kFmtpAttribute));
+ const auto& format_params = msec.GetAttributeList().GetFmtp().mFmtps;
+ ASSERT_EQ(1U, format_params.size());
+ ASSERT_EQ("96", format_params[0].format);
+ ASSERT_EQ(SdpRtpmapAttributeList::CodecType::kRtx,
+ format_params[0].parameters->codec_type);
+ ASSERT_EQ(static_cast<uint8_t>(120),
+ static_cast<SdpFmtpAttributeList::RtxParameters*>(
+ format_params[0].parameters.get())
+ ->apt);
+ ASSERT_EQ(Some(3000U), static_cast<SdpFmtpAttributeList::RtxParameters*>(
+ format_params[0].parameters.get())
+ ->rtx_time);
+ }
+
+ {
+ ParseSdp(kVideoSdp +
+ "a=rtpmap:96 rtx/90000\r\na=fmtp:96 "
+ "apt=120;rtx-time=3000;rtx-time=3300\r\n");
+
+ const SdpMediaSection& msec = Sdp()->GetMediaSection(0);
+ ASSERT_EQ(1U, msec.GetFormats().size());
+ ASSERT_EQ(2U, msec.GetAttributeList().GetRtpmap().mRtpmaps.size());
+ const SdpRtpmapAttributeList::Rtpmap& rtpmap =
+ msec.GetAttributeList().GetRtpmap().GetEntry("96");
+ ASSERT_EQ(rtpmap.pt, "96");
+ ASSERT_EQ(rtpmap.codec, SdpRtpmapAttributeList::CodecType::kRtx);
+ ASSERT_EQ(rtpmap.name, "rtx");
+ ASSERT_EQ(rtpmap.clock, 90000U);
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kFmtpAttribute));
+ const auto& format_params = msec.GetAttributeList().GetFmtp().mFmtps;
+ ASSERT_EQ(1U, format_params.size());
+ ASSERT_EQ("96", format_params[0].format);
+ ASSERT_EQ(SdpRtpmapAttributeList::CodecType::kRtx,
+ format_params[0].parameters->codec_type);
+ ASSERT_EQ(static_cast<uint8_t>(120),
+ static_cast<SdpFmtpAttributeList::RtxParameters*>(
+ format_params[0].parameters.get())
+ ->apt);
+
+ // Last rtx-time wins. This parameter is unused by our implementation
+ // anyway.
+ ASSERT_EQ(Some(3300U), static_cast<SdpFmtpAttributeList::RtxParameters*>(
+ format_params[0].parameters.get())
+ ->rtx_time);
+ }
+
+ {
+ ParseSdp(kVideoSdp +
+ "a=rtpmap:96 rtx/90000\r\na=fmtp:96 apt=120;rtx-time=3000\r\n");
+
+ const SdpMediaSection& msec = Sdp()->GetMediaSection(0);
+ ASSERT_EQ(1U, msec.GetFormats().size());
+ ASSERT_EQ(2U, msec.GetAttributeList().GetRtpmap().mRtpmaps.size());
+ const SdpRtpmapAttributeList::Rtpmap& rtpmap =
+ msec.GetAttributeList().GetRtpmap().GetEntry("96");
+ ASSERT_EQ(rtpmap.pt, "96");
+ ASSERT_EQ(rtpmap.codec, SdpRtpmapAttributeList::CodecType::kRtx);
+ ASSERT_EQ(rtpmap.name, "rtx");
+ ASSERT_EQ(rtpmap.clock, 90000U);
+
+ ASSERT_TRUE(Sdp()->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kFmtpAttribute));
+ const auto& format_params = msec.GetAttributeList().GetFmtp().mFmtps;
+ ASSERT_EQ(1U, format_params.size());
+ ASSERT_EQ("96", format_params[0].format);
+ ASSERT_EQ(SdpRtpmapAttributeList::CodecType::kRtx,
+ format_params[0].parameters->codec_type);
+ ASSERT_EQ(static_cast<uint8_t>(120),
+ static_cast<SdpFmtpAttributeList::RtxParameters*>(
+ format_params[0].parameters.get())
+ ->apt);
+ ASSERT_EQ(Some(3000U), static_cast<SdpFmtpAttributeList::RtxParameters*>(
+ format_params[0].parameters.get())
+ ->rtx_time);
+ }
+}
+
+} // End namespace test.
diff --git a/media/webrtc/signaling/gtest/videoconduit_unittests.cpp b/media/webrtc/signaling/gtest/videoconduit_unittests.cpp
new file mode 100644
index 0000000000..040d8d9ee0
--- /dev/null
+++ b/media/webrtc/signaling/gtest/videoconduit_unittests.cpp
@@ -0,0 +1,2336 @@
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+
+#include "nspr.h"
+#include "nss.h"
+#include "ssl.h"
+
+#include "Canonicals.h"
+#include "VideoConduit.h"
+#include "RtpRtcpConfig.h"
+#include "WebrtcCallWrapper.h"
+#include "WebrtcGmpVideoCodec.h"
+
+#include "api/video/video_sink_interface.h"
+#include "media/base/media_constants.h"
+#include "media/base/video_adapter.h"
+
+#include "MockCall.h"
+#include "MockConduit.h"
+
+using namespace mozilla;
+using namespace testing;
+using namespace webrtc;
+
+namespace test {
+
+class MockVideoSink : public rtc::VideoSinkInterface<webrtc::VideoFrame> {
+ public:
+ MockVideoSink() : mVideoFrame(nullptr, kVideoRotation_0, 0) {}
+
+ ~MockVideoSink() override = default;
+
+ void OnFrame(const webrtc::VideoFrame& frame) override {
+ mVideoFrame = frame;
+ ++mOnFrameCount;
+ }
+
+ size_t mOnFrameCount = 0;
+ webrtc::VideoFrame mVideoFrame;
+};
+
+class VideoConduitTest : public Test {
+ public:
+ VideoConduitTest(
+ VideoSessionConduit::Options aOptions = VideoSessionConduit::Options())
+ : mCallWrapper(MockCallWrapper::Create()),
+ mVideoConduit(MakeRefPtr<WebrtcVideoConduit>(
+ mCallWrapper, GetCurrentSerialEventTarget(), std::move(aOptions),
+ "", TrackingId(TrackingId::Source::Unimplemented, 0))),
+ mControl(GetCurrentSerialEventTarget()) {
+ NSS_NoDB_Init(nullptr);
+
+ EXPECT_EQ(mVideoConduit->Init(), kMediaConduitNoError);
+ mVideoConduit->InitControl(&mControl);
+ mControl.Update([](auto& aControl) {
+ aControl.mLocalSsrcs = {42};
+ aControl.mLocalVideoRtxSsrcs = {43};
+ });
+ }
+
+ ~VideoConduitTest() override {
+ mozilla::Unused << WaitFor(mVideoConduit->Shutdown());
+ mCallWrapper->Destroy();
+ }
+
+ MockCall* Call() { return mCallWrapper->GetMockCall(); }
+
+ MediaConduitErrorCode SendVideoFrame(unsigned short width,
+ unsigned short height,
+ uint64_t capture_time_ms) {
+ rtc::scoped_refptr<webrtc::I420Buffer> buffer =
+ webrtc::I420Buffer::Create(width, height);
+ memset(buffer->MutableDataY(), 0x10, buffer->StrideY() * buffer->height());
+ memset(buffer->MutableDataU(), 0x80,
+ buffer->StrideU() * ((buffer->height() + 1) / 2));
+ memset(buffer->MutableDataV(), 0x80,
+ buffer->StrideV() * ((buffer->height() + 1) / 2));
+
+ webrtc::VideoFrame frame(buffer, capture_time_ms, capture_time_ms,
+ webrtc::kVideoRotation_0);
+ return mVideoConduit->SendVideoFrame(frame);
+ }
+
+ const RefPtr<MockCallWrapper> mCallWrapper;
+ const RefPtr<mozilla::WebrtcVideoConduit> mVideoConduit;
+ ConcreteControl mControl;
+};
+
+TEST_F(VideoConduitTest, TestConfigureReceiveMediaCodecs) {
+ // No codecs
+ mControl.Update([&](auto& aControl) {
+ aControl.mReceiving = true;
+ aControl.mVideoRecvCodecs = {};
+ });
+ ASSERT_TRUE(Call()->mVideoReceiveConfig);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 0U);
+
+ // empty codec name
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codec(120, "", EncodingConstraints());
+ aControl.mVideoRecvCodecs = {codec};
+ });
+ ASSERT_TRUE(Call()->mVideoReceiveConfig);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 0U);
+
+ // Defaults
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codec(120, "VP8", EncodingConstraints());
+ aControl.mVideoRecvCodecs = {codec};
+ aControl.mVideoRecvRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ ASSERT_TRUE(Call()->mVideoReceiveConfig);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].payload_type, 120);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].video_format.name, "VP8");
+ ASSERT_NE(Call()->mVideoReceiveConfig->rtp.local_ssrc, 0U);
+ ASSERT_NE(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 0U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.rtcp_mode,
+ webrtc::RtcpMode::kCompound);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.nack.rtp_history_ms, 0);
+ ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.remb);
+ ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.tmmbr);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method,
+ webrtc::KeyFrameReqMethod::kNone);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.ulpfec_payload_type, -1);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.red_payload_type, -1);
+ ASSERT_EQ(
+ Call()->mVideoReceiveConfig->rtp.rtx_associated_payload_types.size(), 0U);
+}
+
+TEST_F(VideoConduitTest, TestConfigureReceiveMediaCodecsFEC) {
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mFECFbSet = true;
+ aControl.mVideoRecvCodecs = {
+ codecConfig, VideoCodecConfig(1, "ulpfec", EncodingConstraints()),
+ VideoCodecConfig(2, "red", EncodingConstraints())};
+ aControl.mVideoRecvRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ aControl.mReceiving = true;
+ });
+ ASSERT_TRUE(Call()->mVideoReceiveConfig);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].payload_type, 120);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].video_format.name, "VP8");
+ ASSERT_NE(Call()->mVideoReceiveConfig->rtp.local_ssrc, 0U);
+ ASSERT_NE(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 0U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.rtcp_mode,
+ webrtc::RtcpMode::kCompound);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.nack.rtp_history_ms, 0);
+ ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.remb);
+ ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.tmmbr);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method,
+ webrtc::KeyFrameReqMethod::kNone);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.ulpfec_payload_type, 1);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.red_payload_type, 2);
+ ASSERT_EQ(
+ Call()->mVideoReceiveConfig->rtp.rtx_associated_payload_types.size(), 0U);
+}
+
+TEST_F(VideoConduitTest, TestConfigureReceiveMediaCodecsH264) {
+ mControl.Update([&](auto& aControl) {
+ // Insert twice to test that only one H264 codec is used at a time
+ aControl.mReceiving = true;
+ aControl.mVideoRecvCodecs = {
+ VideoCodecConfig(120, "H264", EncodingConstraints()),
+ VideoCodecConfig(120, "H264", EncodingConstraints())};
+ aControl.mVideoRecvRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ ASSERT_TRUE(Call()->mVideoReceiveConfig);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].payload_type, 120);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].video_format.name, "H264");
+ ASSERT_NE(Call()->mVideoReceiveConfig->rtp.local_ssrc, 0U);
+ ASSERT_NE(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 0U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.rtcp_mode,
+ webrtc::RtcpMode::kCompound);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.nack.rtp_history_ms, 0);
+ ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.remb);
+ ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.tmmbr);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method,
+ webrtc::KeyFrameReqMethod::kNone);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.ulpfec_payload_type, -1);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.red_payload_type, -1);
+ ASSERT_EQ(
+ Call()->mVideoReceiveConfig->rtp.rtx_associated_payload_types.size(), 0U);
+}
+
+TEST_F(VideoConduitTest, TestConfigureReceiveMediaCodecsKeyframeRequestType) {
+ // PLI should be preferred to FIR, same codec.
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mNackFbTypes.push_back("pli");
+ codecConfig.mCcmFbTypes.push_back("fir");
+ aControl.mReceiving = true;
+ aControl.mVideoRecvCodecs = {codecConfig};
+ aControl.mVideoRecvRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ ASSERT_TRUE(Call()->mVideoReceiveConfig);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method,
+ webrtc::KeyFrameReqMethod::kPliRtcp);
+
+ // Just FIR
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mCcmFbTypes.push_back("fir");
+ aControl.mVideoRecvCodecs = {codecConfig};
+ });
+ ASSERT_TRUE(Call()->mVideoReceiveConfig);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method,
+ webrtc::KeyFrameReqMethod::kFirRtcp);
+
+ // PLI should be preferred to FIR, multiple codecs.
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig pliCodec(120, "VP8", EncodingConstraints());
+ pliCodec.mNackFbTypes.push_back("pli");
+ VideoCodecConfig firCodec(120, "VP8", EncodingConstraints());
+ firCodec.mCcmFbTypes.push_back("fir");
+ aControl.mVideoRecvCodecs = {pliCodec, firCodec};
+ });
+ ASSERT_TRUE(Call()->mVideoReceiveConfig);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 2U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method,
+ webrtc::KeyFrameReqMethod::kPliRtcp);
+}
+
+TEST_F(VideoConduitTest, TestConfigureReceiveMediaCodecsNack) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mReceiving = true;
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mNackFbTypes.push_back("");
+ aControl.mVideoRecvCodecs = {codecConfig};
+ aControl.mVideoRecvRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ ASSERT_TRUE(Call()->mVideoReceiveConfig);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].payload_type, 120);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].video_format.name, "VP8");
+ ASSERT_NE(Call()->mVideoReceiveConfig->rtp.local_ssrc, 0U);
+ ASSERT_NE(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 0U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.rtcp_mode,
+ webrtc::RtcpMode::kCompound);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.nack.rtp_history_ms, 1000);
+ ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.remb);
+ ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.tmmbr);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method,
+ webrtc::KeyFrameReqMethod::kNone);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.ulpfec_payload_type, -1);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.red_payload_type, -1);
+ ASSERT_EQ(
+ Call()->mVideoReceiveConfig->rtp.rtx_associated_payload_types.size(), 0U);
+}
+
+TEST_F(VideoConduitTest, TestConfigureReceiveMediaCodecsRemb) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mReceiving = true;
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mRembFbSet = true;
+ aControl.mVideoRecvCodecs = {codecConfig};
+ aControl.mVideoRecvRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ ASSERT_TRUE(Call()->mVideoReceiveConfig);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].payload_type, 120);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].video_format.name, "VP8");
+ ASSERT_NE(Call()->mVideoReceiveConfig->rtp.local_ssrc, 0U);
+ ASSERT_NE(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 0U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.rtcp_mode,
+ webrtc::RtcpMode::kCompound);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.nack.rtp_history_ms, 0);
+ ASSERT_TRUE(Call()->mVideoReceiveConfig->rtp.remb);
+ ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.tmmbr);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method,
+ webrtc::KeyFrameReqMethod::kNone);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.ulpfec_payload_type, -1);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.red_payload_type, -1);
+ ASSERT_EQ(
+ Call()->mVideoReceiveConfig->rtp.rtx_associated_payload_types.size(), 0U);
+}
+
+TEST_F(VideoConduitTest, TestConfigureReceiveMediaCodecsTmmbr) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mReceiving = true;
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mCcmFbTypes.push_back("tmmbr");
+ aControl.mVideoRecvCodecs = {codecConfig};
+ aControl.mVideoRecvRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ ASSERT_TRUE(Call()->mVideoReceiveConfig);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].payload_type, 120);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].video_format.name, "VP8");
+ ASSERT_NE(Call()->mVideoReceiveConfig->rtp.local_ssrc, 0U);
+ ASSERT_NE(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 0U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.rtcp_mode,
+ webrtc::RtcpMode::kCompound);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.nack.rtp_history_ms, 0);
+ ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.remb);
+ ASSERT_TRUE(Call()->mVideoReceiveConfig->rtp.tmmbr);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method,
+ webrtc::KeyFrameReqMethod::kNone);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.ulpfec_payload_type, -1);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.red_payload_type, -1);
+ ASSERT_EQ(
+ Call()->mVideoReceiveConfig->rtp.rtx_associated_payload_types.size(), 0U);
+}
+
+TEST_F(VideoConduitTest, TestConfigureSendMediaCodec) {
+ // defaults
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mEncodings.emplace_back();
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ ASSERT_TRUE(Call()->mVideoSendConfig);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.payload_name, "VP8");
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.payload_type, 120);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.rtcp_mode,
+ webrtc::RtcpMode::kCompound);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.max_packet_size, kVideoMtu);
+ ASSERT_EQ(Call()->mVideoSendEncoderConfig->content_type,
+ VideoEncoderConfig::ContentType::kRealtimeVideo);
+ ASSERT_EQ(Call()->mVideoSendEncoderConfig->min_transmit_bitrate_bps, 0);
+ ASSERT_EQ(Call()->mVideoSendEncoderConfig->max_bitrate_bps, KBPS(10000));
+ ASSERT_EQ(Call()->mVideoSendEncoderConfig->number_of_streams, 1U);
+
+ // empty codec name
+ mControl.Update([&](auto& aControl) {
+ aControl.mVideoSendCodec =
+ Some(VideoCodecConfig(120, "", EncodingConstraints()));
+ });
+ // Bad codec gets ignored
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.payload_name, "VP8");
+}
+
+TEST_F(VideoConduitTest, TestConfigureSendMediaCodecMaxFps) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ EncodingConstraints constraints;
+ VideoCodecConfig codecConfig(120, "VP8", constraints);
+ codecConfig.mEncodings.emplace_back();
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+ std::vector<webrtc::VideoStream> videoStreams;
+ videoStreams = Call()->CreateEncoderStreams(640, 480);
+ ASSERT_EQ(videoStreams.size(), 1U);
+ ASSERT_EQ(videoStreams[0].max_framerate, 30); // DEFAULT_VIDEO_MAX_FRAMERATE
+
+ mControl.Update([&](auto& aControl) {
+ EncodingConstraints constraints;
+ constraints.maxFps = Some(42);
+ VideoCodecConfig codecConfig(120, "VP8", constraints);
+ codecConfig.mEncodings.emplace_back();
+ aControl.mVideoSendCodec = Some(codecConfig);
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+ videoStreams = Call()->CreateEncoderStreams(640, 480);
+ ASSERT_EQ(videoStreams.size(), 1U);
+ ASSERT_EQ(videoStreams[0].max_framerate, 42);
+}
+
+TEST_F(VideoConduitTest, TestConfigureSendMediaCodecMaxMbps) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ EncodingConstraints constraints;
+ constraints.maxMbps = 0;
+ VideoCodecConfig codecConfig(120, "VP8", constraints);
+ codecConfig.mEncodings.emplace_back();
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+ SendVideoFrame(640, 480, 1);
+ std::vector<webrtc::VideoStream> videoStreams;
+ videoStreams = Call()->CreateEncoderStreams(640, 480);
+ ASSERT_EQ(videoStreams.size(), 1U);
+ ASSERT_EQ(videoStreams[0].max_framerate, 30); // DEFAULT_VIDEO_MAX_FRAMERATE
+
+ mControl.Update([&](auto& aControl) {
+ EncodingConstraints constraints;
+ constraints.maxMbps = 10000;
+ VideoCodecConfig codecConfig(120, "VP8", constraints);
+ codecConfig.mEncodings.emplace_back();
+ aControl.mVideoSendCodec = Some(codecConfig);
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+ SendVideoFrame(640, 480, 1);
+ videoStreams = Call()->CreateEncoderStreams(640, 480);
+ ASSERT_EQ(videoStreams.size(), 1U);
+ ASSERT_EQ(videoStreams[0].max_framerate, 8);
+}
+
+TEST_F(VideoConduitTest, TestConfigureSendMediaCodecDefaults) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mEncodings.emplace_back();
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+
+ {
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(640, 480);
+ EXPECT_EQ(videoStreams.size(), 1U);
+ EXPECT_EQ(videoStreams[0].min_bitrate_bps, 150000);
+ EXPECT_EQ(videoStreams[0].target_bitrate_bps, 500000);
+ EXPECT_EQ(videoStreams[0].max_bitrate_bps, 2000000);
+ }
+
+ {
+ // SelectBitrates not called until we send a frame
+ SendVideoFrame(1280, 720, 1);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(1280, 720);
+ EXPECT_EQ(videoStreams.size(), 1U);
+ EXPECT_EQ(videoStreams[0].min_bitrate_bps, 200000);
+ EXPECT_EQ(videoStreams[0].target_bitrate_bps, 800000);
+ EXPECT_EQ(videoStreams[0].max_bitrate_bps, 2500000);
+ }
+}
+
+TEST_F(VideoConduitTest, TestConfigureSendMediaCodecTias) {
+ // TIAS
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfigTias(120, "VP8", EncodingConstraints());
+ codecConfigTias.mEncodings.emplace_back();
+ codecConfigTias.mTias = 1000000;
+ aControl.mVideoSendCodec = Some(codecConfigTias);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ {
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+ SendVideoFrame(1280, 720, 1);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(1280, 720);
+ ASSERT_EQ(videoStreams.size(), 1U);
+ ASSERT_EQ(videoStreams[0].min_bitrate_bps, 200000);
+ ASSERT_EQ(videoStreams[0].target_bitrate_bps, 800000);
+ ASSERT_EQ(videoStreams[0].max_bitrate_bps, 1000000);
+ }
+
+ // TIAS (too low)
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfigTiasLow(120, "VP8", EncodingConstraints());
+ codecConfigTiasLow.mEncodings.emplace_back();
+ codecConfigTiasLow.mTias = 1000;
+ aControl.mVideoSendCodec = Some(codecConfigTiasLow);
+ });
+ {
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+ SendVideoFrame(1280, 720, 1);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(1280, 720);
+ ASSERT_EQ(videoStreams.size(), 1U);
+ ASSERT_EQ(videoStreams[0].min_bitrate_bps, 30000);
+ ASSERT_EQ(videoStreams[0].target_bitrate_bps, 30000);
+ ASSERT_EQ(videoStreams[0].max_bitrate_bps, 30000);
+ }
+}
+
+TEST_F(VideoConduitTest, TestConfigureSendMediaCodecMaxBr) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ auto& encoding = codecConfig.mEncodings.emplace_back();
+ encoding.constraints.maxBr = 50000;
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+ SendVideoFrame(1280, 720, 1);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(1280, 720);
+ ASSERT_EQ(videoStreams.size(), 1U);
+ ASSERT_LE(videoStreams[0].min_bitrate_bps, 50000);
+ ASSERT_LE(videoStreams[0].target_bitrate_bps, 50000);
+ ASSERT_EQ(videoStreams[0].max_bitrate_bps, 50000);
+}
+
+TEST_F(VideoConduitTest, TestConfigureSendMediaCodecScaleResolutionBy) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ {
+ auto& encoding = codecConfig.mEncodings.emplace_back();
+ encoding.constraints.scaleDownBy = 2;
+ }
+ {
+ auto& encoding = codecConfig.mEncodings.emplace_back();
+ encoding.constraints.scaleDownBy = 4;
+ }
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ aControl.mLocalSsrcs = {42, 1729};
+ aControl.mLocalVideoRtxSsrcs = {43, 1730};
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+
+ UniquePtr<MockVideoSink> sink(new MockVideoSink());
+ rtc::VideoSinkWants wants;
+ mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+
+ SendVideoFrame(640, 360, 1);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams.size(), 2U);
+ ASSERT_EQ(videoStreams[0].width, 160U);
+ ASSERT_EQ(videoStreams[0].height, 90U);
+ ASSERT_EQ(videoStreams[1].width, 320U);
+ ASSERT_EQ(videoStreams[1].height, 180U);
+}
+
+TEST_F(VideoConduitTest, TestConfigureSendMediaCodecCodecMode) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mEncodings.emplace_back();
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ aControl.mVideoCodecMode = webrtc::VideoCodecMode::kScreensharing;
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+ ASSERT_EQ(Call()->mVideoSendEncoderConfig->content_type,
+ VideoEncoderConfig::ContentType::kScreen);
+}
+
+TEST_F(VideoConduitTest, TestConfigureSendMediaCodecFEC) {
+ {
+ // H264 + FEC
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfig(120, "H264", EncodingConstraints());
+ codecConfig.mEncodings.emplace_back();
+ codecConfig.mFECFbSet = true;
+ codecConfig.mULPFECPayloadType = 1;
+ codecConfig.mREDPayloadType = 2;
+ codecConfig.mREDRTXPayloadType = 3;
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ ASSERT_TRUE(Call()->mVideoSendConfig);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.ulpfec.ulpfec_payload_type, 1);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.ulpfec.red_payload_type, 2);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.ulpfec.red_rtx_payload_type, 3);
+ }
+
+ {
+ // H264 + FEC + Nack
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfig(120, "H264", EncodingConstraints());
+ codecConfig.mEncodings.emplace_back();
+ codecConfig.mFECFbSet = true;
+ codecConfig.mNackFbTypes.push_back("");
+ codecConfig.mULPFECPayloadType = 1;
+ codecConfig.mREDPayloadType = 2;
+ codecConfig.mREDRTXPayloadType = 3;
+ aControl.mVideoSendCodec = Some(codecConfig);
+ });
+ ASSERT_TRUE(Call()->mVideoSendConfig);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.ulpfec.ulpfec_payload_type, -1);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.ulpfec.red_payload_type, -1);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.ulpfec.red_rtx_payload_type, -1);
+ }
+
+ {
+ // VP8 + FEC + Nack
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mEncodings.emplace_back();
+ codecConfig.mFECFbSet = true;
+ codecConfig.mNackFbTypes.push_back("");
+ codecConfig.mULPFECPayloadType = 1;
+ codecConfig.mREDPayloadType = 2;
+ codecConfig.mREDRTXPayloadType = 3;
+ aControl.mVideoSendCodec = Some(codecConfig);
+ });
+ ASSERT_TRUE(Call()->mVideoSendConfig);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.ulpfec.ulpfec_payload_type, 1);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.ulpfec.red_payload_type, 2);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.ulpfec.red_rtx_payload_type, 3);
+ }
+}
+
+TEST_F(VideoConduitTest, TestConfigureSendMediaCodecNack) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mEncodings.emplace_back();
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ ASSERT_TRUE(Call()->mVideoSendConfig);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.nack.rtp_history_ms, 0);
+
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfig = *aControl.mVideoSendCodec.Ref();
+ codecConfig.mNackFbTypes.push_back("");
+ aControl.mVideoSendCodec = Some(codecConfig);
+ });
+ ASSERT_TRUE(Call()->mVideoSendConfig);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.nack.rtp_history_ms, 1000);
+}
+
+TEST_F(VideoConduitTest, TestConfigureSendMediaCodecRids) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mEncodings.emplace_back();
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ ASSERT_TRUE(Call()->mVideoSendConfig);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.rids.size(), 0U);
+
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ {
+ auto& encoding = codecConfig.mEncodings.emplace_back();
+ encoding.rid = "1";
+ }
+ {
+ auto& encoding = codecConfig.mEncodings.emplace_back();
+ encoding.rid = "2";
+ }
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mLocalSsrcs = {42, 1729};
+ aControl.mLocalVideoRtxSsrcs = {43, 1730};
+ });
+ ASSERT_TRUE(Call()->mVideoSendConfig);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.rids.size(), 2U);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.rids[0], "2");
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.rids[1], "1");
+}
+
+TEST_F(VideoConduitTest, TestOnSinkWantsChanged) {
+ UniquePtr<MockVideoSink> sink(new MockVideoSink());
+ rtc::VideoSinkWants wants;
+ mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mEncodings.emplace_back();
+ codecConfig.mEncodingConstraints.maxFs = 0;
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+ wants.max_pixel_count = 256000;
+ mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+ SendVideoFrame(1920, 1080, 1);
+ {
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(1920, 1080);
+ EXPECT_LE(videoStreams[0].width * videoStreams[0].height, 256000U);
+ ASSERT_EQ(videoStreams.size(), 1U);
+ EXPECT_EQ(videoStreams[0].width, 673U);
+ EXPECT_EQ(videoStreams[0].height, 379U);
+ }
+
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfig = *aControl.mVideoSendCodec.Ref();
+ codecConfig.mEncodingConstraints.maxFs = 500;
+ aControl.mVideoSendCodec = Some(codecConfig);
+ });
+ mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+ SendVideoFrame(1920, 1080, 2);
+ {
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ EXPECT_LE(videoStreams[0].width * videoStreams[0].height, 500U * 16U * 16U);
+ ASSERT_EQ(videoStreams.size(), 1U);
+ EXPECT_EQ(videoStreams[0].width, 476U);
+ EXPECT_EQ(videoStreams[0].height, 268U);
+ }
+
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfig = *aControl.mVideoSendCodec.Ref();
+ codecConfig.mEncodingConstraints.maxFs = 1000;
+ aControl.mVideoSendCodec = Some(codecConfig);
+ });
+ mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+ SendVideoFrame(1920, 1080, 3);
+ {
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ EXPECT_LE(videoStreams[0].width * videoStreams[0].height,
+ 1000U * 16U * 16U);
+ ASSERT_EQ(videoStreams.size(), 1U);
+ EXPECT_EQ(videoStreams[0].width, 673U);
+ EXPECT_EQ(videoStreams[0].height, 379U);
+ }
+
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfig = *aControl.mVideoSendCodec.Ref();
+ codecConfig.mEncodingConstraints.maxFs = 500;
+ aControl.mVideoSendCodec = Some(codecConfig);
+ });
+ wants.max_pixel_count = 64000;
+ mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+ SendVideoFrame(1920, 1080, 4);
+ {
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ EXPECT_LE(videoStreams[0].width * videoStreams[0].height, 64000U);
+ ASSERT_EQ(videoStreams.size(), 1U);
+ EXPECT_EQ(videoStreams[0].width, 336U);
+ EXPECT_EQ(videoStreams[0].height, 189U);
+ }
+}
+
+class VideoConduitTestScalingLocked : public VideoConduitTest {
+ public:
+ static VideoSessionConduit::Options CreateOptions() {
+ VideoSessionConduit::Options options;
+ options.mLockScaling = true;
+ return options;
+ }
+ VideoConduitTestScalingLocked() : VideoConduitTest(CreateOptions()) {}
+};
+
+TEST_F(VideoConduitTestScalingLocked, TestOnSinkWantsChanged) {
+ UniquePtr<MockVideoSink> sink(new MockVideoSink());
+ rtc::VideoSinkWants wants;
+ mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mEncodingConstraints.maxFs = 0;
+ codecConfig.mEncodings.emplace_back();
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+ wants.max_pixel_count = 256000;
+ mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+ SendVideoFrame(1920, 1080, 1);
+ EXPECT_EQ(sink->mVideoFrame.width(), 1920);
+ EXPECT_EQ(sink->mVideoFrame.height(), 1080);
+ {
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams.size(), 1U);
+ EXPECT_EQ(videoStreams[0].width, 1920U);
+ EXPECT_EQ(videoStreams[0].height, 1080U);
+ }
+
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfig = *aControl.mVideoSendCodec.Ref();
+ codecConfig.mEncodingConstraints.maxFs = 500;
+ aControl.mVideoSendCodec = Some(codecConfig);
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+ SendVideoFrame(1920, 1080, 2);
+ {
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ EXPECT_LE(videoStreams[0].width * videoStreams[0].height, 500U * 16U * 16U);
+ ASSERT_EQ(videoStreams.size(), 1U);
+ EXPECT_EQ(videoStreams[0].width, 476U);
+ EXPECT_EQ(videoStreams[0].height, 268U);
+ }
+}
+
+TEST_F(VideoConduitTest, TestConfigureSendMediaCodecSimulcastOddScreen) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ {
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mEncodings.emplace_back();
+ {
+ auto& encoding = codecConfig.mEncodings.emplace_back();
+ encoding.constraints.scaleDownBy = 2;
+ }
+ {
+ auto& encoding = codecConfig.mEncodings.emplace_back();
+ encoding.constraints.scaleDownBy = 4;
+ }
+ aControl.mVideoSendCodec = Some(codecConfig);
+ }
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ aControl.mLocalSsrcs = {42, 43, 44};
+ aControl.mLocalVideoRtxSsrcs = {45, 46, 47};
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+
+ UniquePtr<MockVideoSink> sink(new MockVideoSink());
+ rtc::VideoSinkWants wants;
+ mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+
+ SendVideoFrame(26, 24, 1);
+ {
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams.size(), 3U);
+ EXPECT_EQ(videoStreams[2].width, 26U);
+ EXPECT_EQ(videoStreams[2].height, 24U);
+ EXPECT_EQ(videoStreams[1].width, 13U);
+ EXPECT_EQ(videoStreams[1].height, 12U);
+ EXPECT_EQ(videoStreams[0].width, 6U);
+ EXPECT_EQ(videoStreams[0].height, 6U);
+ }
+
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfig = *aControl.mVideoSendCodec.Ref();
+ codecConfig.mEncodings.clear();
+ codecConfig.mEncodings.emplace_back();
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mLocalSsrcs = {42};
+ aControl.mLocalVideoRtxSsrcs = {43};
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+ SendVideoFrame(26, 24, 2);
+ {
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams.size(), 1U);
+ EXPECT_EQ(videoStreams[0].width, 26U);
+ EXPECT_EQ(videoStreams[0].height, 24U);
+ }
+}
+
+TEST_F(VideoConduitTest, TestConfigureSendMediaCodecSimulcastAllScaling) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ {
+ auto& encoding = codecConfig.mEncodings.emplace_back();
+ encoding.constraints.scaleDownBy = 2;
+ }
+ {
+ auto& encoding = codecConfig.mEncodings.emplace_back();
+ encoding.constraints.scaleDownBy = 4;
+ }
+ {
+ auto& encoding = codecConfig.mEncodings.emplace_back();
+ encoding.constraints.scaleDownBy = 6;
+ }
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ aControl.mLocalSsrcs = {42, 43, 44};
+ aControl.mLocalVideoRtxSsrcs = {45, 46, 47};
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+
+ UniquePtr<MockVideoSink> sink(new MockVideoSink());
+ rtc::VideoSinkWants wants;
+ mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+
+ SendVideoFrame(1281, 721, 1);
+ {
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams.size(), 3U);
+ EXPECT_EQ(videoStreams[2].width, 640U);
+ EXPECT_EQ(videoStreams[2].height, 360U);
+ EXPECT_EQ(videoStreams[1].width, 320U);
+ EXPECT_EQ(videoStreams[1].height, 180U);
+ EXPECT_EQ(videoStreams[0].width, 213U);
+ EXPECT_EQ(videoStreams[0].height, 120U);
+ }
+
+ SendVideoFrame(1281, 721, 2);
+ {
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams.size(), 3U);
+ EXPECT_EQ(videoStreams[2].width, 640U);
+ EXPECT_EQ(videoStreams[2].height, 360U);
+ EXPECT_EQ(videoStreams[1].width, 320U);
+ EXPECT_EQ(videoStreams[1].height, 180U);
+ EXPECT_EQ(videoStreams[0].width, 213U);
+ EXPECT_EQ(videoStreams[0].height, 120U);
+ }
+
+ SendVideoFrame(1280, 720, 3);
+ {
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams.size(), 3U);
+ EXPECT_EQ(videoStreams[2].width, 640U);
+ EXPECT_EQ(videoStreams[2].height, 360U);
+ EXPECT_EQ(videoStreams[1].width, 320U);
+ EXPECT_EQ(videoStreams[1].height, 180U);
+ EXPECT_EQ(videoStreams[0].width, 213U);
+ EXPECT_EQ(videoStreams[0].height, 120U);
+ }
+
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfig = *aControl.mVideoSendCodec.Ref();
+ codecConfig.mEncodings[0].constraints.scaleDownBy = 1;
+ codecConfig.mEncodings[1].constraints.scaleDownBy = 2;
+ codecConfig.mEncodings[2].constraints.scaleDownBy = 4;
+ aControl.mVideoSendCodec = Some(codecConfig);
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+ SendVideoFrame(1280, 720, 4);
+ {
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams.size(), 3U);
+ EXPECT_EQ(videoStreams[2].width, 1280U);
+ EXPECT_EQ(videoStreams[2].height, 720U);
+ EXPECT_EQ(videoStreams[1].width, 640U);
+ EXPECT_EQ(videoStreams[1].height, 360U);
+ EXPECT_EQ(videoStreams[0].width, 320U);
+ EXPECT_EQ(videoStreams[0].height, 180U);
+ }
+}
+
+TEST_F(VideoConduitTest, TestConfigureSendMediaCodecSimulcastScreenshare) {
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mEncodings.emplace_back();
+ {
+ auto& encoding = codecConfig.mEncodings.emplace_back();
+ encoding.constraints.scaleDownBy = 2;
+ }
+ {
+ auto& encoding = codecConfig.mEncodings.emplace_back();
+ encoding.constraints.scaleDownBy = 4;
+ }
+
+ aControl.mTransmitting = true;
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ aControl.mLocalSsrcs = {42, 43, 44};
+ aControl.mLocalVideoRtxSsrcs = {45, 46, 47};
+ aControl.mVideoCodecMode = webrtc::VideoCodecMode::kScreensharing;
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(640, 480);
+ ASSERT_EQ(videoStreams.size(), 1U);
+}
+
+TEST_F(VideoConduitTest, TestReconfigureReceiveMediaCodecs) {
+ // Defaults
+ mControl.Update([&](auto& aControl) {
+ aControl.mReceiving = true;
+ aControl.mVideoRecvCodecs = {
+ VideoCodecConfig(120, "VP8", EncodingConstraints())};
+ aControl.mVideoRecvRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ ASSERT_TRUE(Call()->mVideoReceiveConfig);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].payload_type, 120);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].video_format.name, "VP8");
+ ASSERT_NE(Call()->mVideoReceiveConfig->rtp.local_ssrc, 0U);
+ ASSERT_NE(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 0U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.rtcp_mode,
+ webrtc::RtcpMode::kCompound);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.nack.rtp_history_ms, 0);
+ ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.remb);
+ ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.tmmbr);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method,
+ webrtc::KeyFrameReqMethod::kNone);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.ulpfec_payload_type, -1);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.red_payload_type, -1);
+ ASSERT_EQ(
+ Call()->mVideoReceiveConfig->rtp.rtx_associated_payload_types.size(), 0U);
+
+ // FEC
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfigFecFb(120, "VP8", EncodingConstraints());
+ codecConfigFecFb.mFECFbSet = true;
+ VideoCodecConfig codecConfigFEC(1, "ulpfec", EncodingConstraints());
+ VideoCodecConfig codecConfigRED(2, "red", EncodingConstraints());
+ aControl.mVideoRecvCodecs = {codecConfigFecFb, codecConfigFEC,
+ codecConfigRED};
+ });
+ ASSERT_TRUE(Call()->mVideoReceiveConfig);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].payload_type, 120);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].video_format.name, "VP8");
+ ASSERT_NE(Call()->mVideoReceiveConfig->rtp.local_ssrc, 0U);
+ ASSERT_NE(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 0U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.rtcp_mode,
+ webrtc::RtcpMode::kCompound);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.nack.rtp_history_ms, 0);
+ ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.remb);
+ ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.tmmbr);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method,
+ webrtc::KeyFrameReqMethod::kNone);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.ulpfec_payload_type, 1);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.red_payload_type, 2);
+ ASSERT_EQ(
+ Call()->mVideoReceiveConfig->rtp.rtx_associated_payload_types.size(), 0U);
+
+ // H264
+ mControl.Update([&](auto& aControl) {
+ aControl.mVideoRecvCodecs = {
+ VideoCodecConfig(120, "H264", EncodingConstraints())};
+ });
+ ASSERT_TRUE(Call()->mVideoReceiveConfig);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].payload_type, 120);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].video_format.name, "H264");
+ ASSERT_NE(Call()->mVideoReceiveConfig->rtp.local_ssrc, 0U);
+ ASSERT_NE(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 0U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.rtcp_mode,
+ webrtc::RtcpMode::kCompound);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.nack.rtp_history_ms, 0);
+ ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.remb);
+ ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.tmmbr);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method,
+ webrtc::KeyFrameReqMethod::kNone);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.ulpfec_payload_type, -1);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.red_payload_type, -1);
+ ASSERT_EQ(
+ Call()->mVideoReceiveConfig->rtp.rtx_associated_payload_types.size(), 0U);
+
+ // Nack
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfigNack(120, "VP8", EncodingConstraints());
+ codecConfigNack.mNackFbTypes.push_back("");
+ aControl.mVideoRecvCodecs = {codecConfigNack};
+ });
+ ASSERT_TRUE(Call()->mVideoReceiveConfig);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].payload_type, 120);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].video_format.name, "VP8");
+ ASSERT_NE(Call()->mVideoReceiveConfig->rtp.local_ssrc, 0U);
+ ASSERT_NE(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 0U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.rtcp_mode,
+ webrtc::RtcpMode::kCompound);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.nack.rtp_history_ms, 1000);
+ ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.remb);
+ ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.tmmbr);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method,
+ webrtc::KeyFrameReqMethod::kNone);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.ulpfec_payload_type, -1);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.red_payload_type, -1);
+ ASSERT_EQ(
+ Call()->mVideoReceiveConfig->rtp.rtx_associated_payload_types.size(), 0U);
+
+ // Remb
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfigRemb(120, "VP8", EncodingConstraints());
+ codecConfigRemb.mRembFbSet = true;
+ aControl.mVideoRecvCodecs = {codecConfigRemb};
+ });
+ ASSERT_TRUE(Call()->mVideoReceiveConfig);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].payload_type, 120);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].video_format.name, "VP8");
+ ASSERT_NE(Call()->mVideoReceiveConfig->rtp.local_ssrc, 0U);
+ ASSERT_NE(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 0U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.rtcp_mode,
+ webrtc::RtcpMode::kCompound);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.nack.rtp_history_ms, 0);
+ ASSERT_TRUE(Call()->mVideoReceiveConfig->rtp.remb);
+ ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.tmmbr);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method,
+ webrtc::KeyFrameReqMethod::kNone);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.ulpfec_payload_type, -1);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.red_payload_type, -1);
+ ASSERT_EQ(
+ Call()->mVideoReceiveConfig->rtp.rtx_associated_payload_types.size(), 0U);
+
+ // Tmmbr
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfigTmmbr(120, "VP8", EncodingConstraints());
+ codecConfigTmmbr.mCcmFbTypes.push_back("tmmbr");
+ aControl.mVideoRecvCodecs = {codecConfigTmmbr};
+ });
+ ASSERT_TRUE(Call()->mVideoReceiveConfig);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].payload_type, 120);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].video_format.name, "VP8");
+ ASSERT_NE(Call()->mVideoReceiveConfig->rtp.local_ssrc, 0U);
+ ASSERT_NE(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 0U);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.rtcp_mode,
+ webrtc::RtcpMode::kCompound);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.nack.rtp_history_ms, 0);
+ ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.remb);
+ ASSERT_TRUE(Call()->mVideoReceiveConfig->rtp.tmmbr);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method,
+ webrtc::KeyFrameReqMethod::kNone);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.ulpfec_payload_type, -1);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.red_payload_type, -1);
+ ASSERT_EQ(
+ Call()->mVideoReceiveConfig->rtp.rtx_associated_payload_types.size(), 0U);
+}
+
+TEST_F(VideoConduitTest, TestReconfigureSendMediaCodec) {
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mEncodings.emplace_back();
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ ASSERT_FALSE(Call()->mVideoSendConfig);
+
+ // Defaults
+ mControl.Update([&](auto& aControl) { aControl.mTransmitting = true; });
+ ASSERT_TRUE(Call()->mVideoSendConfig);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.payload_name, "VP8");
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.payload_type, 120);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.rtcp_mode,
+ webrtc::RtcpMode::kCompound);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.max_packet_size, kVideoMtu);
+ ASSERT_EQ(Call()->mVideoSendEncoderConfig->content_type,
+ VideoEncoderConfig::ContentType::kRealtimeVideo);
+ ASSERT_EQ(Call()->mVideoSendEncoderConfig->min_transmit_bitrate_bps, 0);
+ ASSERT_EQ(Call()->mVideoSendEncoderConfig->max_bitrate_bps, KBPS(10000));
+ ASSERT_EQ(Call()->mVideoSendEncoderConfig->number_of_streams, 1U);
+ mControl.Update([&](auto& aControl) { aControl.mTransmitting = false; });
+
+ // FEC
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfigFEC(120, "VP8", EncodingConstraints());
+ codecConfigFEC.mEncodings.emplace_back();
+ codecConfigFEC.mFECFbSet = true;
+ codecConfigFEC.mNackFbTypes.push_back("");
+ codecConfigFEC.mULPFECPayloadType = 1;
+ codecConfigFEC.mREDPayloadType = 2;
+ codecConfigFEC.mREDRTXPayloadType = 3;
+ aControl.mVideoSendCodec = Some(codecConfigFEC);
+ });
+ ASSERT_TRUE(Call()->mVideoSendConfig);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.ulpfec.ulpfec_payload_type, 1);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.ulpfec.red_payload_type, 2);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.ulpfec.red_rtx_payload_type, 3);
+ mControl.Update([&](auto& aControl) { aControl.mTransmitting = false; });
+
+ // H264
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfigH264(120, "H264", EncodingConstraints());
+ codecConfigH264.mEncodings.emplace_back();
+ aControl.mVideoSendCodec = Some(codecConfigH264);
+ });
+ ASSERT_TRUE(Call()->mVideoSendConfig);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.payload_name, "H264");
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.payload_type, 120);
+ mControl.Update([&](auto& aControl) { aControl.mTransmitting = false; });
+
+ // TIAS
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfigTias(120, "VP8", EncodingConstraints());
+ codecConfigTias.mEncodings.emplace_back();
+ codecConfigTias.mTias = 1000000;
+ aControl.mVideoSendCodec = Some(codecConfigTias);
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+ SendVideoFrame(1280, 720, 1);
+
+ {
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(1280, 720);
+ ASSERT_EQ(videoStreams.size(), 1U);
+ ASSERT_EQ(videoStreams[0].min_bitrate_bps, 200000);
+ ASSERT_EQ(videoStreams[0].target_bitrate_bps, 800000);
+ ASSERT_EQ(videoStreams[0].max_bitrate_bps, 1000000);
+ }
+ mControl.Update([&](auto& aControl) { aControl.mTransmitting = false; });
+
+ // MaxBr
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ VideoCodecConfig::Encoding encoding;
+ {
+ auto& encoding = codecConfig.mEncodings.emplace_back();
+ encoding.constraints.maxBr = 50000;
+ }
+ aControl.mVideoSendCodec = Some(codecConfig);
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+ SendVideoFrame(1280, 720, 1);
+ {
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(1280, 720);
+ ASSERT_EQ(videoStreams.size(), 1U);
+ ASSERT_LE(videoStreams[0].min_bitrate_bps, 50000);
+ ASSERT_LE(videoStreams[0].target_bitrate_bps, 50000);
+ ASSERT_EQ(videoStreams[0].max_bitrate_bps, 50000);
+ }
+ mControl.Update([&](auto& aControl) { aControl.mTransmitting = false; });
+
+ // MaxFs
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfigMaxFs(120, "VP8", EncodingConstraints());
+ codecConfigMaxFs.mEncodingConstraints.maxFs = 3600;
+ VideoCodecConfig::Encoding encoding;
+ encoding.constraints.maxBr = 0;
+ codecConfigMaxFs.mEncodings.push_back(encoding);
+ aControl.mVideoSendCodec = Some(codecConfigMaxFs);
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+
+ UniquePtr<MockVideoSink> sink(new MockVideoSink());
+ rtc::VideoSinkWants wants;
+ mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+
+ {
+ SendVideoFrame(1280, 720, 1);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams[0].width, 1280U);
+ ASSERT_EQ(videoStreams[0].height, 720U);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 1000U);
+ ASSERT_EQ(sink->mOnFrameCount, 1U);
+ }
+
+ {
+ SendVideoFrame(640, 360, 2);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams[0].width, 640U);
+ ASSERT_EQ(videoStreams[0].height, 360U);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 2000U);
+ ASSERT_EQ(sink->mOnFrameCount, 2U);
+ }
+
+ {
+ SendVideoFrame(1920, 1280, 3);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams[0].width, 1174U);
+ ASSERT_EQ(videoStreams[0].height, 783U);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 3000U);
+ ASSERT_EQ(sink->mOnFrameCount, 3U);
+ }
+}
+
+TEST_F(VideoConduitTest, TestReconfigureSendMediaCodecWhileTransmitting) {
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mEncodings.emplace_back();
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ ASSERT_FALSE(Call()->mVideoSendConfig);
+
+ // Defaults
+ mControl.Update([&](auto& aControl) { aControl.mTransmitting = true; });
+ ASSERT_TRUE(Call()->mVideoSendConfig);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.payload_name, "VP8");
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.payload_type, 120);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.rtcp_mode,
+ webrtc::RtcpMode::kCompound);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.max_packet_size, kVideoMtu);
+ ASSERT_EQ(Call()->mVideoSendEncoderConfig->content_type,
+ VideoEncoderConfig::ContentType::kRealtimeVideo);
+ ASSERT_EQ(Call()->mVideoSendEncoderConfig->min_transmit_bitrate_bps, 0);
+ ASSERT_EQ(Call()->mVideoSendEncoderConfig->max_bitrate_bps, KBPS(10000));
+ ASSERT_EQ(Call()->mVideoSendEncoderConfig->number_of_streams, 1U);
+
+ // Changing these parameters should not require flipping mTransmitting for the
+ // changes to take effect.
+
+ // TIAS
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfigTias(120, "VP8", EncodingConstraints());
+ codecConfigTias.mEncodings.emplace_back();
+ codecConfigTias.mTias = 1000000;
+ aControl.mVideoSendCodec = Some(codecConfigTias);
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+ SendVideoFrame(1280, 720, 1);
+
+ {
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(1280, 720);
+ ASSERT_EQ(videoStreams.size(), 1U);
+ ASSERT_EQ(videoStreams[0].min_bitrate_bps, 200000);
+ ASSERT_EQ(videoStreams[0].target_bitrate_bps, 800000);
+ ASSERT_EQ(videoStreams[0].max_bitrate_bps, 1000000);
+ }
+
+ // MaxBr
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ {
+ auto& encoding = codecConfig.mEncodings.emplace_back();
+ encoding.constraints.maxBr = 50000;
+ }
+ aControl.mVideoSendCodec = Some(codecConfig);
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+ SendVideoFrame(1280, 720, 1);
+ {
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(1280, 720);
+ ASSERT_EQ(videoStreams.size(), 1U);
+ ASSERT_LE(videoStreams[0].min_bitrate_bps, 50000);
+ ASSERT_LE(videoStreams[0].target_bitrate_bps, 50000);
+ ASSERT_EQ(videoStreams[0].max_bitrate_bps, 50000);
+ }
+
+ // MaxFs
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mEncodingConstraints.maxFs = 3600;
+ {
+ auto& encoding = codecConfig.mEncodings.emplace_back();
+ encoding.constraints.maxBr = 0;
+ }
+ aControl.mVideoSendCodec = Some(codecConfig);
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+
+ UniquePtr<MockVideoSink> sink(new MockVideoSink());
+ rtc::VideoSinkWants wants;
+ mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+
+ {
+ SendVideoFrame(1280, 720, 1);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams[0].width, 1280U);
+ ASSERT_EQ(videoStreams[0].height, 720U);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 1000U);
+ ASSERT_EQ(sink->mOnFrameCount, 1U);
+ }
+
+ {
+ SendVideoFrame(640, 360, 2);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams[0].width, 640U);
+ ASSERT_EQ(videoStreams[0].height, 360U);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 2000U);
+ ASSERT_EQ(sink->mOnFrameCount, 2U);
+ }
+
+ {
+ SendVideoFrame(1920, 1280, 3);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams[0].width, 1174U);
+ ASSERT_EQ(videoStreams[0].height, 783U);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 3000U);
+ ASSERT_EQ(sink->mOnFrameCount, 3U);
+ }
+
+ // ScaleResolutionDownBy
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ {
+ auto& encoding = codecConfig.mEncodings.emplace_back();
+ encoding.constraints.maxFs = 0;
+ encoding.constraints.scaleDownBy = 3.7;
+ }
+ aControl.mVideoSendCodec = Some(codecConfig);
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+
+ {
+ SendVideoFrame(1280, 720, 4);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams[0].width, 345U);
+ ASSERT_EQ(videoStreams[0].height, 194U);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 4000U);
+ ASSERT_EQ(sink->mOnFrameCount, 4U);
+ }
+
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfigScaleDownBy = *aControl.mVideoSendCodec.Ref();
+ codecConfigScaleDownBy.mEncodings[0].constraints.scaleDownBy = 1.3;
+ aControl.mVideoSendCodec = Some(codecConfigScaleDownBy);
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+
+ {
+ SendVideoFrame(641, 359, 5);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams[0].width, 493U);
+ ASSERT_EQ(videoStreams[0].height, 276U);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 5000U);
+ ASSERT_EQ(sink->mOnFrameCount, 5U);
+ }
+}
+
+TEST_F(VideoConduitTest, TestVideoEncode) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mEncodings.emplace_back();
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+
+ UniquePtr<MockVideoSink> sink(new MockVideoSink());
+ rtc::VideoSinkWants wants;
+ mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+
+ SendVideoFrame(1280, 720, 1);
+ ASSERT_EQ(sink->mVideoFrame.width(), 1280);
+ ASSERT_EQ(sink->mVideoFrame.height(), 720);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 1000U);
+ ASSERT_EQ(sink->mOnFrameCount, 1U);
+
+ SendVideoFrame(640, 360, 2);
+ ASSERT_EQ(sink->mVideoFrame.width(), 640);
+ ASSERT_EQ(sink->mVideoFrame.height(), 360);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 2000U);
+ ASSERT_EQ(sink->mOnFrameCount, 2U);
+
+ SendVideoFrame(1920, 1280, 3);
+ ASSERT_EQ(sink->mVideoFrame.width(), 1920);
+ ASSERT_EQ(sink->mVideoFrame.height(), 1280);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 3000U);
+ ASSERT_EQ(sink->mOnFrameCount, 3U);
+
+ mVideoConduit->RemoveSink(sink.get());
+}
+
+TEST_F(VideoConduitTest, TestVideoEncodeMaxFs) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mEncodingConstraints.maxFs = 3600;
+ codecConfig.mEncodings.emplace_back();
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+
+ UniquePtr<MockVideoSink> sink(new MockVideoSink());
+ rtc::VideoSinkWants wants;
+ mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+
+ {
+ SendVideoFrame(1280, 720, 1);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams[0].width, 1280U);
+ ASSERT_EQ(videoStreams[0].height, 720U);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 1000U);
+ ASSERT_EQ(sink->mOnFrameCount, 1U);
+ }
+
+ {
+ SendVideoFrame(640, 360, 2);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams[0].width, 640U);
+ ASSERT_EQ(videoStreams[0].height, 360U);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 2000U);
+ ASSERT_EQ(sink->mOnFrameCount, 2U);
+ }
+
+ {
+ SendVideoFrame(1920, 1280, 3);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams[0].width, 1174U);
+ ASSERT_EQ(videoStreams[0].height, 783U);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 3000U);
+ ASSERT_EQ(sink->mOnFrameCount, 3U);
+ }
+
+ // maxFs should not force pixel count above what a sink has requested.
+ // We set 3600 macroblocks (16x16 pixels), so we request 3500 here.
+ wants.max_pixel_count = 3500 * 16 * 16;
+ mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+
+ {
+ SendVideoFrame(1280, 720, 4);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams[0].width, 1260U);
+ ASSERT_EQ(videoStreams[0].height, 709U);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 4000U);
+ ASSERT_EQ(sink->mOnFrameCount, 4U);
+ }
+
+ {
+ SendVideoFrame(640, 360, 5);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams[0].width, 640U);
+ ASSERT_EQ(videoStreams[0].height, 360U);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 5000U);
+ ASSERT_EQ(sink->mOnFrameCount, 5U);
+ }
+
+ {
+ SendVideoFrame(1920, 1280, 6);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams[0].width, 1158U);
+ ASSERT_EQ(videoStreams[0].height, 772U);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 6000U);
+ ASSERT_EQ(sink->mOnFrameCount, 6U);
+ }
+
+ mVideoConduit->RemoveSink(sink.get());
+}
+
+TEST_F(VideoConduitTest, TestVideoEncodeMaxFsNegotiatedThenSinkWants) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mEncodings.emplace_back();
+ codecConfig.mEncodingConstraints.maxFs = 3500;
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+
+ UniquePtr<MockVideoSink> sink(new MockVideoSink());
+ rtc::VideoSinkWants wants;
+ mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+
+ unsigned int frame = 0;
+
+ {
+ SendVideoFrame(1280, 720, frame++);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams[0].width, 1260U);
+ ASSERT_EQ(videoStreams[0].height, 709U);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), (frame - 1) * 1000);
+ ASSERT_EQ(sink->mOnFrameCount, frame);
+ }
+
+ wants.max_pixel_count = 3600 * 16 * 16;
+ mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+
+ {
+ SendVideoFrame(1280, 720, frame++);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams[0].width, 1260U);
+ ASSERT_EQ(videoStreams[0].height, 709U);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), (frame - 1) * 1000);
+ ASSERT_EQ(sink->mOnFrameCount, frame);
+ }
+
+ mVideoConduit->RemoveSink(sink.get());
+}
+
+TEST_F(VideoConduitTest, TestVideoEncodeMaxFsCodecChange) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mEncodings.emplace_back();
+ codecConfig.mEncodingConstraints.maxFs = 3500;
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+
+ UniquePtr<MockVideoSink> sink(new MockVideoSink());
+ rtc::VideoSinkWants wants;
+ mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+
+ unsigned int frame = 0;
+
+ {
+ SendVideoFrame(1280, 720, frame++);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams[0].width, 1260U);
+ ASSERT_EQ(videoStreams[0].height, 709U);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), (frame - 1) * 1000);
+ ASSERT_EQ(sink->mOnFrameCount, frame);
+ }
+
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfig(121, "VP9", EncodingConstraints());
+ codecConfig.mEncodings.emplace_back();
+ codecConfig.mEncodingConstraints.maxFs = 3500;
+ aControl.mVideoSendCodec = Some(codecConfig);
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+
+ {
+ SendVideoFrame(1280, 720, frame++);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams[0].width, 1260U);
+ ASSERT_EQ(videoStreams[0].height, 709U);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), (frame - 1) * 1000);
+ ASSERT_EQ(sink->mOnFrameCount, frame);
+ }
+
+ mVideoConduit->RemoveSink(sink.get());
+}
+
+TEST_F(VideoConduitTest, TestVideoEncodeMaxFsSinkWantsThenCodecChange) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mEncodings.emplace_back();
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+
+ UniquePtr<MockVideoSink> sink(new MockVideoSink());
+ rtc::VideoSinkWants wants;
+ wants.max_pixel_count = 3500 * 16 * 16;
+ mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+
+ unsigned int frame = 0;
+
+ SendVideoFrame(1280, 720, frame++);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams[0].width, 1260U);
+ ASSERT_EQ(videoStreams[0].height, 709U);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), (frame - 1) * 1000);
+ ASSERT_EQ(sink->mOnFrameCount, frame);
+
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfig(121, "VP9", EncodingConstraints());
+ codecConfig.mEncodings.emplace_back();
+ aControl.mVideoSendCodec = Some(codecConfig);
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+
+ {
+ SendVideoFrame(1280, 720, frame++);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams[0].width, 1260U);
+ ASSERT_EQ(videoStreams[0].height, 709U);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), (frame - 1) * 1000);
+ ASSERT_EQ(sink->mOnFrameCount, frame);
+ }
+
+ mVideoConduit->RemoveSink(sink.get());
+}
+
+TEST_F(VideoConduitTest, TestVideoEncodeMaxFsNegotiated) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mEncodings.emplace_back();
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+
+ UniquePtr<MockVideoSink> sink(new MockVideoSink());
+ rtc::VideoSinkWants wants;
+ mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+
+ unsigned int frame = 0;
+ SendVideoFrame(1280, 720, frame++);
+ ASSERT_EQ(sink->mVideoFrame.width(), 1280);
+ ASSERT_EQ(sink->mVideoFrame.height(), 720);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), (frame - 1) * 1000);
+ ASSERT_EQ(sink->mOnFrameCount, frame);
+
+ // Ensure that negotiating a new max-fs works
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfig = *aControl.mVideoSendCodec.Ref();
+ codecConfig.mEncodingConstraints.maxFs = 3500;
+ aControl.mVideoSendCodec = Some(codecConfig);
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+
+ {
+ SendVideoFrame(1280, 720, frame++);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams[0].width, 1260U);
+ ASSERT_EQ(videoStreams[0].height, 709U);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), (frame - 1) * 1000);
+ ASSERT_EQ(sink->mOnFrameCount, frame);
+ }
+
+ // Ensure that negotiating max-fs away works
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfig = *aControl.mVideoSendCodec.Ref();
+ codecConfig.mEncodingConstraints.maxFs = 0;
+ aControl.mVideoSendCodec = Some(codecConfig);
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+
+ SendVideoFrame(1280, 720, frame++);
+ ASSERT_EQ(sink->mVideoFrame.width(), 1280);
+ ASSERT_EQ(sink->mVideoFrame.height(), 720);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), (frame - 1) * 1000);
+ ASSERT_EQ(sink->mOnFrameCount, frame);
+
+ mVideoConduit->RemoveSink(sink.get());
+}
+
+TEST_F(VideoConduitTest, TestVideoEncodeMaxWidthAndHeight) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mEncodingConstraints.maxWidth = 1280;
+ codecConfig.mEncodingConstraints.maxHeight = 720;
+ codecConfig.mEncodings.emplace_back();
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+
+ UniquePtr<MockVideoSink> sink(new MockVideoSink());
+ rtc::VideoSinkWants wants;
+ mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+
+ SendVideoFrame(1280, 720, 1);
+ ASSERT_EQ(sink->mVideoFrame.width(), 1280);
+ ASSERT_EQ(sink->mVideoFrame.height(), 720);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 1000U);
+ ASSERT_EQ(sink->mOnFrameCount, 1U);
+
+ SendVideoFrame(640, 360, 2);
+ ASSERT_EQ(sink->mVideoFrame.width(), 640);
+ ASSERT_EQ(sink->mVideoFrame.height(), 360);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 2000U);
+ ASSERT_EQ(sink->mOnFrameCount, 2U);
+
+ {
+ SendVideoFrame(1920, 1280, 3);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams[0].width, 1080U);
+ ASSERT_EQ(videoStreams[0].height, 720U);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 3000U);
+ ASSERT_EQ(sink->mOnFrameCount, 3U);
+ }
+
+ mVideoConduit->RemoveSink(sink.get());
+}
+
+TEST_F(VideoConduitTest, TestVideoEncodeScaleResolutionBy) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mEncodingConstraints.maxFs = 3600;
+ auto& encoding = codecConfig.mEncodings.emplace_back();
+ encoding.constraints.scaleDownBy = 2;
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+
+ UniquePtr<MockVideoSink> sink(new MockVideoSink());
+ rtc::VideoSinkWants wants;
+ mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+
+ {
+ SendVideoFrame(1280, 720, 1);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams[0].width, 640U);
+ ASSERT_EQ(videoStreams[0].height, 360U);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 1000U);
+ ASSERT_EQ(sink->mOnFrameCount, 1U);
+ }
+
+ {
+ SendVideoFrame(640, 360, 2);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams[0].width, 320U);
+ ASSERT_EQ(videoStreams[0].height, 180U);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 2000U);
+ ASSERT_EQ(sink->mOnFrameCount, 2U);
+ mVideoConduit->RemoveSink(sink.get());
+ }
+}
+
+TEST_F(VideoConduitTest, TestVideoEncodeSimulcastScaleResolutionBy) {
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ {
+ auto& encoding = codecConfig.mEncodings.emplace_back();
+ encoding.constraints.scaleDownBy = 2;
+ }
+ {
+ auto& encoding = codecConfig.mEncodings.emplace_back();
+ encoding.constraints.scaleDownBy = 3;
+ }
+ {
+ auto& encoding = codecConfig.mEncodings.emplace_back();
+ encoding.constraints.scaleDownBy = 4;
+ }
+
+ aControl.mTransmitting = true;
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ aControl.mLocalSsrcs = {42, 43, 44};
+ aControl.mLocalVideoRtxSsrcs = {45, 46, 47};
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+
+ UniquePtr<MockVideoSink> sink(new MockVideoSink());
+ rtc::VideoSinkWants wants;
+ mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+
+ {
+ SendVideoFrame(640, 480, 1);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams[2].width, 320U);
+ ASSERT_EQ(videoStreams[2].height, 240U);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 1000U);
+ ASSERT_EQ(sink->mOnFrameCount, 1U);
+ }
+
+ {
+ SendVideoFrame(1280, 720, 2);
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(sink->mVideoFrame.width(),
+ sink->mVideoFrame.height());
+ ASSERT_EQ(videoStreams[2].width, 640U);
+ ASSERT_EQ(videoStreams[2].height, 360U);
+ ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 2000U);
+ ASSERT_EQ(sink->mOnFrameCount, 2U);
+ mVideoConduit->RemoveSink(sink.get());
+ }
+}
+
+TEST_F(VideoConduitTest, TestVideoEncodeLargeScaleResolutionByFrameDropping) {
+ for (const auto& scales :
+ {std::vector{200U}, std::vector{200U, 300U}, std::vector{300U, 200U}}) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ for (const auto& scale : scales) {
+ auto& encoding = codecConfig.mEncodings.emplace_back();
+ encoding.constraints.scaleDownBy = scale;
+ }
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ aControl.mLocalSsrcs = scales;
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+
+ UniquePtr<MockVideoSink> sink(new MockVideoSink());
+ rtc::VideoSinkWants wants;
+ mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+
+ {
+ // If all layers' scaleDownBy is larger than any input dimension, that
+ // dimension becomes zero and we drop it.
+ // NB: libwebrtc doesn't CreateEncoderStreams() unless there's a real
+ // frame, so no reason to call it here.
+ SendVideoFrame(199, 199, 1);
+ EXPECT_EQ(sink->mOnFrameCount, 0U);
+ }
+
+ {
+ // If only width becomes zero, we drop.
+ SendVideoFrame(199, 200, 2);
+ EXPECT_EQ(sink->mOnFrameCount, 0U);
+ }
+
+ {
+ // If only height becomes zero, we drop.
+ SendVideoFrame(200, 199, 3);
+ EXPECT_EQ(sink->mOnFrameCount, 0U);
+ }
+
+ {
+ // If dimensions are non-zero, we pass through.
+ SendVideoFrame(200, 200, 4);
+ EXPECT_EQ(sink->mOnFrameCount, 1U);
+ }
+
+ mVideoConduit->RemoveSink(sink.get());
+ }
+}
+
+TEST_F(VideoConduitTest, TestVideoEncodeLargeScaleResolutionByStreamCreation) {
+ for (const auto& scales :
+ {std::vector{200U}, std::vector{200U, 300U}, std::vector{300U, 200U}}) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ for (const auto& scale : scales) {
+ auto& encoding = codecConfig.mEncodings.emplace_back();
+ encoding.constraints.scaleDownBy = scale;
+ }
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ aControl.mLocalSsrcs = scales;
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+
+ {
+ // If dimensions scale to <1, we create a 1x1 stream.
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(199, 199);
+ ASSERT_EQ(videoStreams.size(), scales.size());
+ for (const auto& stream : videoStreams) {
+ EXPECT_EQ(stream.width, 1U);
+ EXPECT_EQ(stream.height, 1U);
+ }
+ }
+
+ {
+ // If width scales to <1, we create a 1x1 stream.
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(199, 200);
+ ASSERT_EQ(videoStreams.size(), scales.size());
+ for (const auto& stream : videoStreams) {
+ EXPECT_EQ(stream.width, 1U);
+ EXPECT_EQ(stream.height, 1U);
+ }
+ }
+
+ {
+ // If height scales to <1, we create a 1x1 stream.
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(200, 199);
+ ASSERT_EQ(videoStreams.size(), scales.size());
+ for (const auto& stream : videoStreams) {
+ EXPECT_EQ(stream.width, 1U);
+ EXPECT_EQ(stream.height, 1U);
+ }
+ }
+
+ {
+ // If dimensions scale to 1, we create a 1x1 stream.
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(200, 200);
+ ASSERT_EQ(videoStreams.size(), scales.size());
+ for (const auto& stream : videoStreams) {
+ EXPECT_EQ(stream.width, 1U);
+ EXPECT_EQ(stream.height, 1U);
+ }
+ }
+
+ {
+ // If one dimension scales to 0 and the other >1, we create a 1x1 stream.
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(400, 199);
+ ASSERT_EQ(videoStreams.size(), scales.size());
+ for (const auto& stream : videoStreams) {
+ EXPECT_EQ(stream.width, 1U);
+ EXPECT_EQ(stream.height, 1U);
+ }
+ }
+
+ {
+ // Legit case scaling down to more than 1x1.
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(600, 400);
+ ASSERT_EQ(videoStreams.size(), scales.size());
+ for (size_t i = 0; i < scales.size(); ++i) {
+ // Streams are backwards for some reason
+ const auto& stream = videoStreams[scales.size() - i - 1];
+ const auto& scale = scales[i];
+ if (scale == 200U) {
+ EXPECT_EQ(stream.width, 3U);
+ EXPECT_EQ(stream.height, 2U);
+ } else {
+ EXPECT_EQ(stream.width, 2U);
+ EXPECT_EQ(stream.height, 1U);
+ }
+ }
+ }
+ }
+}
+
+TEST_F(VideoConduitTest, TestVideoEncodeResolutionAlignment) {
+ UniquePtr<MockVideoSink> sink(new MockVideoSink());
+
+ for (const auto& scales : {std::vector{1U}, std::vector{1U, 9U}}) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ for (const auto& scale : scales) {
+ auto& encoding = codecConfig.mEncodings.emplace_back();
+ encoding.constraints.scaleDownBy = scale;
+ }
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ aControl.mLocalSsrcs = scales;
+ });
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+
+ for (const auto& alignment : {2, 16, 39, 400, 1000}) {
+ // Test that requesting specific alignment always results in the expected
+ // number of layers and valid alignment.
+ rtc::VideoSinkWants wants;
+ wants.resolution_alignment = alignment;
+ mVideoConduit->AddOrUpdateSink(sink.get(), wants);
+
+ const std::vector<webrtc::VideoStream> videoStreams =
+ Call()->CreateEncoderStreams(640, 480);
+ ASSERT_EQ(videoStreams.size(), scales.size());
+ for (size_t i = 0; i < videoStreams.size(); ++i) {
+ // videoStreams is backwards
+ const auto& stream = videoStreams[videoStreams.size() - 1 - i];
+ const auto& scale = scales[i];
+ uint32_t expectation =
+ 480 / scale < static_cast<uint32_t>(alignment) ? 1 : 0;
+ EXPECT_EQ(stream.width % alignment, expectation)
+ << " for scale " << scale << " and alignment " << alignment;
+ EXPECT_EQ(stream.height % alignment, expectation);
+ }
+ }
+ }
+
+ mVideoConduit->RemoveSink(sink.get());
+}
+
+TEST_F(VideoConduitTest, TestSettingRtpRtcpRsize) {
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mEncodings.emplace_back();
+ RtpRtcpConfig rtcpConf(webrtc::RtcpMode::kReducedSize);
+
+ aControl.mReceiving = true;
+ aControl.mVideoRecvCodecs = {codecConfig};
+ aControl.mVideoRecvRtpRtcpConfig = Some(rtcpConf);
+ aControl.mTransmitting = true;
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig = Some(rtcpConf);
+ });
+ ASSERT_TRUE(Call()->mVideoReceiveConfig);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.rtcp_mode,
+ webrtc::RtcpMode::kReducedSize);
+ ASSERT_TRUE(Call()->mVideoSendConfig);
+ ASSERT_EQ(Call()->mVideoSendConfig->rtp.rtcp_mode,
+ webrtc::RtcpMode::kReducedSize);
+}
+
+TEST_F(VideoConduitTest, TestRemoteSsrcDefault) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mRemoteSsrc = 0;
+ aControl.mLocalSsrcs = {1};
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mEncodings.emplace_back();
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ aControl.mReceiving = true;
+ aControl.mTransmitting = true;
+ });
+ ASSERT_TRUE(Call()->mVideoReceiveConfig);
+ ASSERT_THAT(Call()->mVideoReceiveConfig->rtp.remote_ssrc,
+ Not(testing::AnyOf(0U, 1U)));
+ ASSERT_TRUE(Call()->mVideoSendConfig);
+ ASSERT_THAT(Call()->mVideoSendConfig->rtp.ssrcs, ElementsAre(1U));
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.local_ssrc,
+ Call()->mVideoSendConfig->rtp.ssrcs[0]);
+}
+
+TEST_F(VideoConduitTest, TestRemoteSsrcCollision) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mRemoteSsrc = 1;
+ aControl.mLocalSsrcs = {1};
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mEncodings.emplace_back();
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ aControl.mReceiving = true;
+ aControl.mTransmitting = true;
+ });
+ EXPECT_TRUE(Call()->mVideoReceiveConfig);
+ EXPECT_EQ(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 1U);
+ EXPECT_TRUE(Call()->mVideoSendConfig);
+ EXPECT_THAT(Call()->mVideoSendConfig->rtp.ssrcs,
+ ElementsAre(Not(testing::AnyOf(0U, 1U))));
+ EXPECT_EQ(Call()->mVideoReceiveConfig->rtp.local_ssrc,
+ Call()->mVideoSendConfig->rtp.ssrcs[0]);
+}
+
+TEST_F(VideoConduitTest, TestLocalSsrcDefault) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mRemoteSsrc = 1;
+ aControl.mLocalSsrcs = {};
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mEncodings.emplace_back();
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ aControl.mReceiving = true;
+ aControl.mTransmitting = true;
+ });
+ ASSERT_TRUE(Call()->mVideoReceiveConfig);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 1U);
+ ASSERT_TRUE(Call()->mVideoSendConfig);
+ ASSERT_THAT(Call()->mVideoSendConfig->rtp.ssrcs,
+ ElementsAre(Not(testing::AnyOf(0U, 1U))));
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.local_ssrc,
+ Call()->mVideoSendConfig->rtp.ssrcs[0]);
+}
+
+TEST_F(VideoConduitTest, TestLocalSsrcCollision) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mRemoteSsrc = 1;
+ aControl.mLocalSsrcs = {2, 2};
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ codecConfig.mEncodings.emplace_back();
+ codecConfig.mEncodings.emplace_back();
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ aControl.mReceiving = true;
+ aControl.mTransmitting = true;
+ });
+ ASSERT_TRUE(Call()->mVideoReceiveConfig);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 1U);
+ ASSERT_TRUE(Call()->mVideoSendConfig);
+ ASSERT_THAT(Call()->mVideoSendConfig->rtp.ssrcs,
+ ElementsAre(2U, Not(testing::AnyOf(0U, 2U))));
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.local_ssrc,
+ Call()->mVideoSendConfig->rtp.ssrcs[0]);
+}
+
+TEST_F(VideoConduitTest, TestLocalSsrcUnorderedCollision) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mRemoteSsrc = 1;
+ aControl.mLocalSsrcs = {2, 3, 2};
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ for (int i = 0; i < 3; ++i) {
+ codecConfig.mEncodings.emplace_back();
+ }
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ aControl.mReceiving = true;
+ aControl.mTransmitting = true;
+ });
+ ASSERT_TRUE(Call()->mVideoReceiveConfig);
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 1U);
+ ASSERT_TRUE(Call()->mVideoSendConfig);
+ ASSERT_THAT(Call()->mVideoSendConfig->rtp.ssrcs,
+ ElementsAre(2U, 3U, Not(testing::AnyOf(0U, 2U))));
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.local_ssrc,
+ Call()->mVideoSendConfig->rtp.ssrcs[0]);
+}
+
+TEST_F(VideoConduitTest, TestLocalAndRemoteSsrcCollision) {
+ mControl.Update([&](auto& aControl) {
+ aControl.mRemoteSsrc = 1;
+ aControl.mLocalSsrcs = {1, 2, 2};
+ VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints());
+ for (int i = 0; i < 3; ++i) {
+ codecConfig.mEncodings.emplace_back();
+ }
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ aControl.mReceiving = true;
+ aControl.mTransmitting = true;
+ });
+ ASSERT_TRUE(Call()->mVideoReceiveConfig);
+ ASSERT_THAT(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 1U);
+ ASSERT_TRUE(Call()->mVideoSendConfig);
+ ASSERT_THAT(Call()->mVideoSendConfig->rtp.ssrcs,
+ ElementsAre(Not(testing::AnyOf(0U, 1U, 2U)), 2U,
+ Not(testing::AnyOf(
+ 0U, 1U, 2U,
+ Call()->mVideoReceiveConfig->rtp.remote_ssrc))));
+ ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.local_ssrc,
+ Call()->mVideoSendConfig->rtp.ssrcs[0]);
+}
+
+TEST_F(VideoConduitTest, TestExternalRemoteSsrcCollision) {
+ auto other = MakeRefPtr<MockConduit>();
+ mCallWrapper->RegisterConduit(other);
+
+ // First the mControl update should trigger an UnsetRemoteSSRC(1) from us.
+ // Then we simulate another conduit using that same ssrc, which should trigger
+ // us to generate a fresh ssrc that is not 0 and not 1.
+ {
+ InSequence s;
+ EXPECT_CALL(*other, UnsetRemoteSSRC(1U)).Times(2);
+ EXPECT_CALL(*other, UnsetRemoteSSRC(Not(testing::AnyOf(0U, 1U))));
+ }
+
+ mControl.Update([&](auto& aControl) {
+ aControl.mRemoteSsrc = 1;
+ aControl.mReceiving = true;
+ });
+ EXPECT_TRUE(Call()->mVideoReceiveConfig);
+ EXPECT_EQ(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 1U);
+
+ mozilla::Unused << WaitFor(InvokeAsync(
+ GetCurrentSerialEventTarget(), __func__, [wrapper = mCallWrapper] {
+ wrapper->UnsetRemoteSSRC(1);
+ return GenericPromise::CreateAndResolve(true, __func__);
+ }));
+
+ EXPECT_TRUE(Call()->mVideoReceiveConfig);
+ EXPECT_THAT(Call()->mVideoReceiveConfig->rtp.remote_ssrc,
+ Not(testing::AnyOf(0U, 1U)));
+}
+
+TEST_F(VideoConduitTest, TestVideoConfigurationH264) {
+ const int profileLevelId1 = 0x42E00D;
+ const int profileLevelId2 = 0x64000C;
+ const char* sprop1 = "foo bar";
+ const char* sprop2 = "baz";
+
+ // Test that VideoConduit propagates H264 configuration data properly.
+ // We do two tests:
+ // - Test valid data in packetization mode 0 (SingleNALU)
+ // - Test different valid data in packetization mode 1 (NonInterleaved)
+
+ {
+ mControl.Update([&](auto& aControl) {
+ aControl.mTransmitting = true;
+ VideoCodecConfigH264 h264{};
+ h264.packetization_mode = 0;
+ h264.profile_level_id = profileLevelId1;
+ strncpy(h264.sprop_parameter_sets, sprop1,
+ sizeof(h264.sprop_parameter_sets) - 1);
+ VideoCodecConfig codecConfig(97, "H264", EncodingConstraints(), &h264);
+ codecConfig.mEncodings.emplace_back();
+ aControl.mVideoSendCodec = Some(codecConfig);
+ aControl.mVideoSendRtpRtcpConfig =
+ Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound));
+ });
+
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+ auto& params = Call()->mVideoSendEncoderConfig->video_format.parameters;
+ EXPECT_EQ(params[cricket::kH264FmtpPacketizationMode], "0");
+ EXPECT_EQ(params[cricket::kH264FmtpProfileLevelId], "42e00d");
+ EXPECT_EQ(params[cricket::kH264FmtpSpropParameterSets], sprop1);
+ }
+
+ {
+ mControl.Update([&](auto& aControl) {
+ VideoCodecConfigH264 h264{};
+ h264.packetization_mode = 1;
+ h264.profile_level_id = profileLevelId2;
+ strncpy(h264.sprop_parameter_sets, sprop2,
+ sizeof(h264.sprop_parameter_sets) - 1);
+ VideoCodecConfig codecConfig(126, "H264", EncodingConstraints(), &h264);
+ codecConfig.mEncodings.emplace_back();
+ aControl.mVideoSendCodec = Some(codecConfig);
+ });
+
+ ASSERT_TRUE(Call()->mVideoSendEncoderConfig);
+ auto& params = Call()->mVideoSendEncoderConfig->video_format.parameters;
+ EXPECT_EQ(params[cricket::kH264FmtpPacketizationMode], "1");
+ EXPECT_EQ(params[cricket::kH264FmtpProfileLevelId], "64000c");
+ EXPECT_EQ(params[cricket::kH264FmtpSpropParameterSets], sprop2);
+ }
+}
+
+} // End namespace test.