| /* |
| * Copyright 2017 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/strings/str_replace.h" |
| #include "api/audio/audio_device.h" |
| #include "api/audio/audio_mixer.h" |
| #include "api/audio/audio_processing.h" |
| #include "api/audio_codecs/builtin_audio_decoder_factory.h" |
| #include "api/audio_codecs/builtin_audio_encoder_factory.h" |
| #include "api/create_peerconnection_factory.h" |
| #include "api/media_types.h" |
| #include "api/peer_connection_interface.h" |
| #include "api/rtp_transceiver_interface.h" |
| #include "api/scoped_refptr.h" |
| #include "api/video_codecs/video_decoder_factory_template.h" |
| #include "api/video_codecs/video_decoder_factory_template_dav1d_adapter.h" |
| #include "api/video_codecs/video_decoder_factory_template_libvpx_vp8_adapter.h" |
| #include "api/video_codecs/video_decoder_factory_template_libvpx_vp9_adapter.h" |
| #include "api/video_codecs/video_decoder_factory_template_open_h264_adapter.h" |
| #include "api/video_codecs/video_encoder_factory_template.h" |
| #include "api/video_codecs/video_encoder_factory_template_libaom_av1_adapter.h" |
| #include "api/video_codecs/video_encoder_factory_template_libvpx_vp8_adapter.h" |
| #include "api/video_codecs/video_encoder_factory_template_libvpx_vp9_adapter.h" |
| #include "api/video_codecs/video_encoder_factory_template_open_h264_adapter.h" |
| #include "p2p/base/port_allocator.h" |
| #include "pc/peer_connection_wrapper.h" |
| #include "pc/session_description.h" |
| #include "pc/test/fake_audio_capture_module.h" |
| #include "pc/test/mock_peer_connection_observers.h" |
| #include "rtc_base/rtc_certificate_generator.h" |
| #include "rtc_base/thread.h" |
| #include "system_wrappers/include/metrics.h" |
| #include "test/gtest.h" |
| |
| // This file contains unit tests that relate to the behavior of the |
| // SdpOfferAnswer module. |
| // Tests are writen as integration tests with PeerConnection, since the |
| // behaviors are still linked so closely that it is hard to test them in |
| // isolation. |
| |
| namespace webrtc { |
| |
| using RTCConfiguration = PeerConnectionInterface::RTCConfiguration; |
| |
| namespace { |
| |
| std::unique_ptr<rtc::Thread> CreateAndStartThread() { |
| auto thread = rtc::Thread::Create(); |
| thread->Start(); |
| return thread; |
| } |
| |
| } // namespace |
| |
| class SdpOfferAnswerTest : public ::testing::Test { |
| public: |
| SdpOfferAnswerTest() |
| // Note: We use a PeerConnectionFactory with a distinct |
| // signaling thread, so that thread handling can be tested. |
| : signaling_thread_(CreateAndStartThread()), |
| pc_factory_(CreatePeerConnectionFactory( |
| nullptr, |
| nullptr, |
| signaling_thread_.get(), |
| FakeAudioCaptureModule::Create(), |
| CreateBuiltinAudioEncoderFactory(), |
| CreateBuiltinAudioDecoderFactory(), |
| std::make_unique< |
| VideoEncoderFactoryTemplate<LibvpxVp8EncoderTemplateAdapter, |
| LibvpxVp9EncoderTemplateAdapter, |
| OpenH264EncoderTemplateAdapter, |
| LibaomAv1EncoderTemplateAdapter>>(), |
| std::make_unique< |
| VideoDecoderFactoryTemplate<LibvpxVp8DecoderTemplateAdapter, |
| LibvpxVp9DecoderTemplateAdapter, |
| OpenH264DecoderTemplateAdapter, |
| Dav1dDecoderTemplateAdapter>>(), |
| nullptr /* audio_mixer */, |
| nullptr /* audio_processing */)) { |
| metrics::Reset(); |
| } |
| |
| std::unique_ptr<PeerConnectionWrapper> CreatePeerConnection() { |
| RTCConfiguration config; |
| config.sdp_semantics = SdpSemantics::kUnifiedPlan; |
| return CreatePeerConnection(config); |
| } |
| |
| std::unique_ptr<PeerConnectionWrapper> CreatePeerConnection( |
| const RTCConfiguration& config) { |
| auto observer = std::make_unique<MockPeerConnectionObserver>(); |
| auto result = pc_factory_->CreatePeerConnectionOrError( |
| config, PeerConnectionDependencies(observer.get())); |
| EXPECT_TRUE(result.ok()); |
| observer->SetPeerConnectionInterface(result.value().get()); |
| return std::make_unique<PeerConnectionWrapper>( |
| pc_factory_, result.MoveValue(), std::move(observer)); |
| } |
| |
| protected: |
| std::unique_ptr<rtc::Thread> signaling_thread_; |
| rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory_; |
| |
| private: |
| rtc::AutoThread main_thread_; |
| }; |
| |
| TEST_F(SdpOfferAnswerTest, OnTrackReturnsProxiedObject) { |
| auto caller = CreatePeerConnection(); |
| auto callee = CreatePeerConnection(); |
| |
| auto audio_transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); |
| |
| ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); |
| // Verify that caller->observer->OnTrack() has been called with a |
| // proxied transceiver object. |
| ASSERT_EQ(callee->observer()->on_track_transceivers_.size(), 1u); |
| auto transceiver = callee->observer()->on_track_transceivers_[0]; |
| // Since the signaling thread is not the current thread, |
| // this will DCHECK if the transceiver is not proxied. |
| transceiver->stopped(); |
| } |
| |
| TEST_F(SdpOfferAnswerTest, BundleRejectsCodecCollisionsAudioVideo) { |
| auto pc = CreatePeerConnection(); |
| std::string sdp = |
| "v=0\r\n" |
| "o=- 0 3 IN IP4 127.0.0.1\r\n" |
| "s=-\r\n" |
| "t=0 0\r\n" |
| "a=group:BUNDLE 0 1\r\n" |
| "a=fingerprint:sha-1 " |
| "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB\r\n" |
| "a=setup:actpass\r\n" |
| "a=ice-ufrag:ETEn\r\n" |
| "a=ice-pwd:OtSK0WpNtpUjkY4+86js7Z/l\r\n" |
| "m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=rtcp-mux\r\n" |
| "a=sendonly\r\n" |
| "a=mid:0\r\n" |
| "a=rtpmap:111 opus/48000/2\r\n" |
| "m=video 9 UDP/TLS/RTP/SAVPF 111\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=rtcp-mux\r\n" |
| "a=sendonly\r\n" |
| "a=mid:1\r\n" |
| "a=rtpmap:111 H264/90000\r\n" |
| "a=fmtp:111 " |
| "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=" |
| "42e01f\r\n"; |
| |
| auto desc = CreateSessionDescription(SdpType::kOffer, sdp); |
| ASSERT_NE(desc, nullptr); |
| RTCError error; |
| pc->SetRemoteDescription(std::move(desc), &error); |
| // There is no error yet but the metrics counter will increase. |
| EXPECT_TRUE(error.ok()); |
| EXPECT_METRIC_EQ( |
| 1, metrics::NumEvents("WebRTC.PeerConnection.ValidBundledPayloadTypes", |
| false)); |
| |
| // Tolerate codec collisions in rejected m-lines. |
| pc = CreatePeerConnection(); |
| auto rejected_offer = CreateSessionDescription( |
| SdpType::kOffer, |
| absl::StrReplaceAll(sdp, {{"m=video 9 ", "m=video 0 "}})); |
| pc->SetRemoteDescription(std::move(rejected_offer), &error); |
| EXPECT_TRUE(error.ok()); |
| EXPECT_METRIC_EQ( |
| 1, metrics::NumEvents("WebRTC.PeerConnection.ValidBundledPayloadTypes", |
| true)); |
| } |
| |
| TEST_F(SdpOfferAnswerTest, BundleRejectsCodecCollisionsVideoFmtp) { |
| auto pc = CreatePeerConnection(); |
| std::string sdp = |
| "v=0\r\n" |
| "o=- 0 3 IN IP4 127.0.0.1\r\n" |
| "s=-\r\n" |
| "t=0 0\r\n" |
| "a=group:BUNDLE 0 1\r\n" |
| "a=fingerprint:sha-1 " |
| "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB\r\n" |
| "a=setup:actpass\r\n" |
| "a=ice-ufrag:ETEn\r\n" |
| "a=ice-pwd:OtSK0WpNtpUjkY4+86js7Z/l\r\n" |
| "m=video 9 UDP/TLS/RTP/SAVPF 111\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=rtcp-mux\r\n" |
| "a=sendonly\r\n" |
| "a=mid:0\r\n" |
| "a=rtpmap:111 H264/90000\r\n" |
| "a=fmtp:111 " |
| "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=" |
| "42e01f\r\n" |
| "m=video 9 UDP/TLS/RTP/SAVPF 111\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=rtcp-mux\r\n" |
| "a=sendonly\r\n" |
| "a=mid:1\r\n" |
| "a=rtpmap:111 H264/90000\r\n" |
| "a=fmtp:111 " |
| "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=" |
| "42e01f\r\n"; |
| |
| auto desc = CreateSessionDescription(SdpType::kOffer, sdp); |
| ASSERT_NE(desc, nullptr); |
| RTCError error; |
| pc->SetRemoteDescription(std::move(desc), &error); |
| EXPECT_TRUE(error.ok()); |
| EXPECT_METRIC_EQ( |
| 1, metrics::NumEvents("WebRTC.PeerConnection.ValidBundledPayloadTypes", |
| false)); |
| } |
| |
| TEST_F(SdpOfferAnswerTest, BundleCodecCollisionInDifferentBundlesAllowed) { |
| auto pc = CreatePeerConnection(); |
| std::string sdp = |
| "v=0\r\n" |
| "o=- 0 3 IN IP4 127.0.0.1\r\n" |
| "s=-\r\n" |
| "t=0 0\r\n" |
| "a=group:BUNDLE 0\r\n" |
| "a=group:BUNDLE 1\r\n" |
| "a=fingerprint:sha-1 " |
| "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB\r\n" |
| "a=setup:actpass\r\n" |
| "a=ice-ufrag:ETEn\r\n" |
| "a=ice-pwd:OtSK0WpNtpUjkY4+86js7Z/l\r\n" |
| "m=video 9 UDP/TLS/RTP/SAVPF 111\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=rtcp-mux\r\n" |
| "a=sendonly\r\n" |
| "a=mid:0\r\n" |
| "a=rtpmap:111 H264/90000\r\n" |
| "a=fmtp:111 " |
| "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=" |
| "42e01f\r\n" |
| "m=video 9 UDP/TLS/RTP/SAVPF 111\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=rtcp-mux\r\n" |
| "a=sendonly\r\n" |
| "a=mid:1\r\n" |
| "a=rtpmap:111 H264/90000\r\n" |
| "a=fmtp:111 " |
| "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=" |
| "42e01f\r\n"; |
| |
| auto desc = CreateSessionDescription(SdpType::kOffer, sdp); |
| ASSERT_NE(desc, nullptr); |
| RTCError error; |
| pc->SetRemoteDescription(std::move(desc), &error); |
| EXPECT_TRUE(error.ok()); |
| EXPECT_METRIC_EQ( |
| 0, metrics::NumEvents("WebRTC.PeerConnection.ValidBundledPayloadTypes", |
| false)); |
| } |
| |
| TEST_F(SdpOfferAnswerTest, BundleMeasuresHeaderExtensionIdCollision) { |
| auto pc = CreatePeerConnection(); |
| std::string sdp = |
| "v=0\r\n" |
| "o=- 0 3 IN IP4 127.0.0.1\r\n" |
| "s=-\r\n" |
| "t=0 0\r\n" |
| "a=group:BUNDLE 0 1\r\n" |
| "a=fingerprint:sha-1 " |
| "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB\r\n" |
| "a=setup:actpass\r\n" |
| "a=ice-ufrag:ETEn\r\n" |
| "a=ice-pwd:OtSK0WpNtpUjkY4+86js7Z/l\r\n" |
| "m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=rtcp-mux\r\n" |
| "a=sendonly\r\n" |
| "a=mid:0\r\n" |
| "a=rtpmap:111 opus/48000/2\r\n" |
| "a=extmap:3 " |
| "http://www.ietf.org/id/" |
| "draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n" |
| "m=video 9 UDP/TLS/RTP/SAVPF 112\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=rtcp-mux\r\n" |
| "a=sendonly\r\n" |
| "a=mid:1\r\n" |
| "a=rtpmap:112 VP8/90000\r\n" |
| "a=extmap:3 " |
| "http://www.ietf.org/id/" |
| "draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n"; |
| auto desc = CreateSessionDescription(SdpType::kOffer, sdp); |
| ASSERT_NE(desc, nullptr); |
| RTCError error; |
| pc->SetRemoteDescription(std::move(desc), &error); |
| EXPECT_TRUE(error.ok()); |
| } |
| |
| // extmap:3 is used with two different URIs which is not allowed. |
| TEST_F(SdpOfferAnswerTest, BundleRejectsHeaderExtensionIdCollision) { |
| auto pc = CreatePeerConnection(); |
| std::string sdp = |
| "v=0\r\n" |
| "o=- 0 3 IN IP4 127.0.0.1\r\n" |
| "s=-\r\n" |
| "t=0 0\r\n" |
| "a=group:BUNDLE 0 1\r\n" |
| "a=fingerprint:sha-1 " |
| "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB\r\n" |
| "a=setup:actpass\r\n" |
| "a=ice-ufrag:ETEn\r\n" |
| "a=ice-pwd:OtSK0WpNtpUjkY4+86js7Z/l\r\n" |
| "m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=rtcp-mux\r\n" |
| "a=sendonly\r\n" |
| "a=mid:0\r\n" |
| "a=rtpmap:111 opus/48000/2\r\n" |
| "a=extmap:3 " |
| "http://www.ietf.org/id/" |
| "draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n" |
| "m=video 9 UDP/TLS/RTP/SAVPF 112\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=rtcp-mux\r\n" |
| "a=sendonly\r\n" |
| "a=mid:1\r\n" |
| "a=rtpmap:112 VP8/90000\r\n" |
| "a=extmap:3 urn:3gpp:video-orientation\r\n"; |
| auto desc = CreateSessionDescription(SdpType::kOffer, sdp); |
| ASSERT_NE(desc, nullptr); |
| RTCError error; |
| pc->SetRemoteDescription(std::move(desc), &error); |
| EXPECT_FALSE(error.ok()); |
| EXPECT_EQ(error.type(), RTCErrorType::INVALID_PARAMETER); |
| } |
| |
| // transport-wide cc is negotiated with two different ids 3 and 4. |
| // This is not a good idea but tolerable. |
| TEST_F(SdpOfferAnswerTest, BundleAcceptsDifferentIdsForSameExtension) { |
| auto pc = CreatePeerConnection(); |
| std::string sdp = |
| "v=0\r\n" |
| "o=- 0 3 IN IP4 127.0.0.1\r\n" |
| "s=-\r\n" |
| "t=0 0\r\n" |
| "a=group:BUNDLE 0 1\r\n" |
| "a=fingerprint:sha-1 " |
| "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB\r\n" |
| "a=setup:actpass\r\n" |
| "a=ice-ufrag:ETEn\r\n" |
| "a=ice-pwd:OtSK0WpNtpUjkY4+86js7Z/l\r\n" |
| "m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=rtcp-mux\r\n" |
| "a=sendonly\r\n" |
| "a=mid:0\r\n" |
| "a=rtpmap:111 opus/48000/2\r\n" |
| "a=extmap:3 " |
| "http://www.ietf.org/id/" |
| "draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n" |
| "m=video 9 UDP/TLS/RTP/SAVPF 112\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=rtcp-mux\r\n" |
| "a=sendonly\r\n" |
| "a=mid:1\r\n" |
| "a=rtpmap:112 VP8/90000\r\n" |
| "a=extmap:4 " |
| "http://www.ietf.org/id/" |
| "draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n"; |
| auto desc = CreateSessionDescription(SdpType::kOffer, sdp); |
| ASSERT_NE(desc, nullptr); |
| RTCError error; |
| pc->SetRemoteDescription(std::move(desc), &error); |
| EXPECT_TRUE(error.ok()); |
| } |
| |
| TEST_F(SdpOfferAnswerTest, LargeMidsAreRejected) { |
| auto pc = CreatePeerConnection(); |
| std::string sdp = |
| "v=0\r\n" |
| "o=- 0 3 IN IP4 127.0.0.1\r\n" |
| "s=-\r\n" |
| "t=0 0\r\n" |
| "a=fingerprint:sha-1 " |
| "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB\r\n" |
| "a=setup:actpass\r\n" |
| "a=ice-ufrag:ETEn\r\n" |
| "a=ice-pwd:OtSK0WpNtpUjkY4+86js7Z/l\r\n" |
| "m=video 9 UDP/TLS/RTP/SAVPF 111\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=rtcp-mux\r\n" |
| "a=sendonly\r\n" |
| "a=rtpmap:111 VP8/90000\r\n" |
| "a=mid:01234567890123456\r\n"; |
| auto desc = CreateSessionDescription(SdpType::kOffer, sdp); |
| ASSERT_NE(desc, nullptr); |
| RTCError error; |
| pc->SetRemoteDescription(std::move(desc), &error); |
| EXPECT_FALSE(error.ok()); |
| EXPECT_EQ(error.type(), RTCErrorType::INVALID_PARAMETER); |
| } |
| |
| TEST_F(SdpOfferAnswerTest, RollbackPreservesAddTrackMid) { |
| std::string sdp = |
| "v=0\r\n" |
| "o=- 4131505339648218884 3 IN IP4 **-----**\r\n" |
| "s=-\r\n" |
| "t=0 0\r\n" |
| "a=ice-lite\r\n" |
| "a=msid-semantic: WMS 100030878598094:4Qs1PjbLM32RK5u3\r\n" |
| "a=ice-ufrag:zGWFZ+fVXDeN6UoI/136\r\n" |
| "a=ice-pwd:9AUNgUqRNI5LSIrC1qFD2iTR\r\n" |
| "a=fingerprint:sha-256 " |
| "AD:52:52:E0:B1:37:34:21:0E:15:8E:B7:56:56:7B:B4:39:0E:6D:1C:F5:84:A7:EE:" |
| "B5:27:3E:30:B1:7D:69:42\r\n" |
| "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n" |
| "a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n" |
| "a=group:BUNDLE 0 1\r\n" |
| "m=audio 40005 UDP/TLS/RTP/SAVPF 111\r\n" |
| "a=rtpmap:111 opus/48000/2\r\n" |
| "a=fmtp:111 " |
| "maxaveragebitrate=20000;maxplaybackrate=16000;minptime=10;usedtx=1;" |
| "useinbandfec=1;stereo=0\r\n" |
| "a=rtcp-fb:111 nack\r\n" |
| "a=setup:passive\r\n" |
| "a=mid:0\r\n" |
| "a=msid:- 75156ebd-e705-4da1-920e-2dac39794dfd\r\n" |
| "a=ptime:60\r\n" |
| "a=recvonly\r\n" |
| "a=rtcp-mux\r\n" |
| "m=audio 40005 UDP/TLS/RTP/SAVPF 111\r\n" |
| "a=rtpmap:111 opus/48000/2\r\n" |
| "a=fmtp:111 " |
| "maxaveragebitrate=20000;maxplaybackrate=16000;minptime=10;usedtx=1;" |
| "useinbandfec=1;stereo=0\r\n" |
| "a=rtcp-fb:111 nack\r\n" |
| "a=setup:passive\r\n" |
| "a=mid:1\r\n" |
| "a=msid:100030878598094:4Qs1PjbLM32RK5u3 9695447562408476674\r\n" |
| "a=ptime:60\r\n" |
| "a=sendonly\r\n" |
| "a=ssrc:2565730539 cname:100030878598094:4Qs1PjbLM32RK5u3\r\n" |
| "a=rtcp-mux\r\n"; |
| auto pc = CreatePeerConnection(); |
| auto audio_track = pc->AddAudioTrack("audio_track", {}); |
| auto first_transceiver = pc->pc()->GetTransceivers()[0]; |
| EXPECT_FALSE(first_transceiver->mid().has_value()); |
| auto desc = CreateSessionDescription(SdpType::kOffer, sdp); |
| ASSERT_NE(desc, nullptr); |
| RTCError error; |
| ASSERT_TRUE(pc->SetRemoteDescription(std::move(desc))); |
| pc->CreateAnswerAndSetAsLocal(); |
| auto saved_mid = first_transceiver->mid(); |
| EXPECT_TRUE(saved_mid.has_value()); |
| auto offer_before_rollback = pc->CreateOfferAndSetAsLocal(); |
| EXPECT_EQ(saved_mid, first_transceiver->mid()); |
| auto rollback = pc->CreateRollback(); |
| ASSERT_NE(rollback, nullptr); |
| ASSERT_TRUE(pc->SetLocalDescription(std::move(rollback))); |
| EXPECT_EQ(saved_mid, first_transceiver->mid()); |
| auto offer2 = pc->CreateOfferAndSetAsLocal(); |
| ASSERT_NE(offer2, nullptr); |
| EXPECT_EQ(saved_mid, first_transceiver->mid()); |
| } |
| |
| #ifdef WEBRTC_HAVE_SCTP |
| |
| TEST_F(SdpOfferAnswerTest, RejectedDataChannelsDoNotGetReoffered) { |
| auto pc = CreatePeerConnection(); |
| EXPECT_TRUE(pc->pc()->CreateDataChannelOrError("dc", nullptr).ok()); |
| EXPECT_TRUE(pc->CreateOfferAndSetAsLocal()); |
| auto mid = pc->pc()->local_description()->description()->contents()[0].mid(); |
| |
| // An answer that rejects the datachannel content. |
| std::string sdp = |
| "v=0\r\n" |
| "o=- 4131505339648218884 3 IN IP4 **-----**\r\n" |
| "s=-\r\n" |
| "t=0 0\r\n" |
| "a=ice-ufrag:zGWFZ+fVXDeN6UoI/136\r\n" |
| "a=ice-pwd:9AUNgUqRNI5LSIrC1qFD2iTR\r\n" |
| "a=fingerprint:sha-256 " |
| "AD:52:52:E0:B1:37:34:21:0E:15:8E:B7:56:56:7B:B4:39:0E:6D:1C:F5:84:A7:EE:" |
| "B5:27:3E:30:B1:7D:69:42\r\n" |
| "a=setup:passive\r\n" |
| "m=application 0 UDP/DTLS/SCTP webrtc-datachannel\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=sctp-port:5000\r\n" |
| "a=max-message-size:262144\r\n" |
| "a=mid:" + |
| mid + "\r\n"; |
| auto answer = CreateSessionDescription(SdpType::kAnswer, sdp); |
| ASSERT_TRUE(pc->SetRemoteDescription(std::move(answer))); |
| // The subsequent offer should not recycle the m-line since the existing data |
| // channel is closed. |
| auto offer = pc->CreateOffer(); |
| const auto& offer_contents = offer->description()->contents(); |
| ASSERT_EQ(offer_contents.size(), 1u); |
| EXPECT_EQ(offer_contents[0].mid(), mid); |
| EXPECT_EQ(offer_contents[0].rejected, true); |
| } |
| |
| TEST_F(SdpOfferAnswerTest, RejectedDataChannelsDoGetReofferedWhenActive) { |
| auto pc = CreatePeerConnection(); |
| EXPECT_TRUE(pc->pc()->CreateDataChannelOrError("dc", nullptr).ok()); |
| EXPECT_TRUE(pc->CreateOfferAndSetAsLocal()); |
| auto mid = pc->pc()->local_description()->description()->contents()[0].mid(); |
| |
| // An answer that rejects the datachannel content. |
| std::string sdp = |
| "v=0\r\n" |
| "o=- 4131505339648218884 3 IN IP4 **-----**\r\n" |
| "s=-\r\n" |
| "t=0 0\r\n" |
| "a=ice-ufrag:zGWFZ+fVXDeN6UoI/136\r\n" |
| "a=ice-pwd:9AUNgUqRNI5LSIrC1qFD2iTR\r\n" |
| "a=fingerprint:sha-256 " |
| "AD:52:52:E0:B1:37:34:21:0E:15:8E:B7:56:56:7B:B4:39:0E:6D:1C:F5:84:A7:EE:" |
| "B5:27:3E:30:B1:7D:69:42\r\n" |
| "a=setup:passive\r\n" |
| "m=application 0 UDP/DTLS/SCTP webrtc-datachannel\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=sctp-port:5000\r\n" |
| "a=max-message-size:262144\r\n" |
| "a=mid:" + |
| mid + "\r\n"; |
| auto answer = CreateSessionDescription(SdpType::kAnswer, sdp); |
| ASSERT_TRUE(pc->SetRemoteDescription(std::move(answer))); |
| |
| // The subsequent offer should recycle the m-line when there is a new data |
| // channel. |
| EXPECT_TRUE(pc->pc()->CreateDataChannelOrError("dc2", nullptr).ok()); |
| EXPECT_TRUE(pc->pc()->ShouldFireNegotiationNeededEvent( |
| pc->observer()->latest_negotiation_needed_event())); |
| |
| auto offer = pc->CreateOffer(); |
| const auto& offer_contents = offer->description()->contents(); |
| ASSERT_EQ(offer_contents.size(), 1u); |
| EXPECT_EQ(offer_contents[0].mid(), mid); |
| EXPECT_EQ(offer_contents[0].rejected, false); |
| } |
| |
| #endif // WEBRTC_HAVE_SCTP |
| |
| TEST_F(SdpOfferAnswerTest, SimulcastAnswerWithNoRidsIsRejected) { |
| auto pc = CreatePeerConnection(); |
| |
| RtpTransceiverInit init; |
| RtpEncodingParameters rid1; |
| rid1.rid = "1"; |
| init.send_encodings.push_back(rid1); |
| RtpEncodingParameters rid2; |
| rid2.rid = "2"; |
| init.send_encodings.push_back(rid2); |
| |
| auto transceiver = pc->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init); |
| EXPECT_TRUE(pc->CreateOfferAndSetAsLocal()); |
| auto mid = pc->pc()->local_description()->description()->contents()[0].mid(); |
| |
| // A SDP answer with simulcast but without mid/rid extensions. |
| std::string sdp = |
| "v=0\r\n" |
| "o=- 4131505339648218884 3 IN IP4 **-----**\r\n" |
| "s=-\r\n" |
| "t=0 0\r\n" |
| "a=ice-ufrag:zGWFZ+fVXDeN6UoI/136\r\n" |
| "a=ice-pwd:9AUNgUqRNI5LSIrC1qFD2iTR\r\n" |
| "a=fingerprint:sha-256 " |
| "AD:52:52:E0:B1:37:34:21:0E:15:8E:B7:56:56:7B:B4:39:0E:6D:1C:F5:84:A7:EE:" |
| "B5:27:3E:30:B1:7D:69:42\r\n" |
| "a=setup:passive\r\n" |
| "m=video 9 UDP/TLS/RTP/SAVPF 96\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=rtcp:9 IN IP4 0.0.0.0\r\n" |
| "a=mid:" + |
| mid + |
| "\r\n" |
| "a=recvonly\r\n" |
| "a=rtcp-mux\r\n" |
| "a=rtcp-rsize\r\n" |
| "a=rtpmap:96 VP8/90000\r\n" |
| "a=rid:1 recv\r\n" |
| "a=rid:2 recv\r\n" |
| "a=simulcast:recv 1;2\r\n"; |
| std::string extensions = |
| "a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid\r\n" |
| "a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n"; |
| auto answer = CreateSessionDescription(SdpType::kAnswer, sdp); |
| EXPECT_FALSE(pc->SetRemoteDescription(std::move(answer))); |
| |
| auto answer_with_extensions = |
| CreateSessionDescription(SdpType::kAnswer, sdp + extensions); |
| EXPECT_TRUE(pc->SetRemoteDescription(std::move(answer_with_extensions))); |
| |
| // Tolerate the lack of mid/rid extensions in rejected m-lines. |
| EXPECT_TRUE(pc->CreateOfferAndSetAsLocal()); |
| auto rejected_answer = CreateSessionDescription( |
| SdpType::kAnswer, |
| absl::StrReplaceAll(sdp, {{"m=video 9 ", "m=video 0 "}})); |
| EXPECT_TRUE(pc->SetRemoteDescription(std::move(rejected_answer))); |
| } |
| |
| TEST_F(SdpOfferAnswerTest, ExpectAllSsrcsSpecifiedInSsrcGroupFid) { |
| auto pc = CreatePeerConnection(); |
| std::string sdp = |
| "v=0\r\n" |
| "o=- 0 3 IN IP4 127.0.0.1\r\n" |
| "s=-\r\n" |
| "t=0 0\r\n" |
| "a=group:BUNDLE 0\r\n" |
| "a=fingerprint:sha-1 " |
| "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB\r\n" |
| "a=setup:actpass\r\n" |
| "a=ice-ufrag:ETEn\r\n" |
| "a=ice-pwd:OtSK0WpNtpUjkY4+86js7Z/l\r\n" |
| "m=video 9 UDP/TLS/RTP/SAVPF 96 97\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=rtcp-mux\r\n" |
| "a=sendonly\r\n" |
| "a=mid:0\r\n" |
| "a=rtpmap:96 H264/90000\r\n" |
| "a=fmtp:96 " |
| "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=" |
| "42e01f\r\n" |
| "a=rtpmap:97 rtx/90000\r\n" |
| "a=fmtp:97 apt=96\r\n" |
| "a=ssrc-group:FID 1 2\r\n" |
| "a=ssrc:1 cname:test\r\n"; |
| auto offer = CreateSessionDescription(SdpType::kOffer, sdp); |
| RTCError error; |
| pc->SetRemoteDescription(std::move(offer), &error); |
| EXPECT_FALSE(error.ok()); |
| EXPECT_EQ(error.type(), RTCErrorType::INVALID_PARAMETER); |
| } |
| |
| TEST_F(SdpOfferAnswerTest, ExpectAllSsrcsSpecifiedInSsrcGroupFecFr) { |
| auto pc = CreatePeerConnection(); |
| std::string sdp = |
| "v=0\r\n" |
| "o=- 0 3 IN IP4 127.0.0.1\r\n" |
| "s=-\r\n" |
| "t=0 0\r\n" |
| "a=group:BUNDLE 0\r\n" |
| "a=fingerprint:sha-1 " |
| "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB\r\n" |
| "a=setup:actpass\r\n" |
| "a=ice-ufrag:ETEn\r\n" |
| "a=ice-pwd:OtSK0WpNtpUjkY4+86js7Z/l\r\n" |
| "m=video 9 UDP/TLS/RTP/SAVPF 96 98\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=rtcp-mux\r\n" |
| "a=sendonly\r\n" |
| "a=mid:0\r\n" |
| "a=rtpmap:96 H264/90000\r\n" |
| "a=fmtp:96 " |
| "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=" |
| "42e01f\r\n" |
| "a=rtpmap:98 flexfec-03/90000\r\n" |
| "a=fmtp:98 repair-window=10000000\r\n" |
| "a=ssrc-group:FEC-FR 1 2\r\n" |
| "a=ssrc:1 cname:test\r\n"; |
| auto offer = CreateSessionDescription(SdpType::kOffer, sdp); |
| RTCError error; |
| pc->SetRemoteDescription(std::move(offer), &error); |
| EXPECT_FALSE(error.ok()); |
| EXPECT_EQ(error.type(), RTCErrorType::INVALID_PARAMETER); |
| } |
| |
| TEST_F(SdpOfferAnswerTest, ExpectTwoSsrcsInSsrcGroupFid) { |
| auto pc = CreatePeerConnection(); |
| std::string sdp = |
| "v=0\r\n" |
| "o=- 0 3 IN IP4 127.0.0.1\r\n" |
| "s=-\r\n" |
| "t=0 0\r\n" |
| "a=group:BUNDLE 0\r\n" |
| "a=fingerprint:sha-1 " |
| "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB\r\n" |
| "a=setup:actpass\r\n" |
| "a=ice-ufrag:ETEn\r\n" |
| "a=ice-pwd:OtSK0WpNtpUjkY4+86js7Z/l\r\n" |
| "m=video 9 UDP/TLS/RTP/SAVPF 96 97\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=rtcp-mux\r\n" |
| "a=sendonly\r\n" |
| "a=mid:0\r\n" |
| "a=rtpmap:96 H264/90000\r\n" |
| "a=fmtp:96 " |
| "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=" |
| "42e01f\r\n" |
| "a=rtpmap:97 rtx/90000\r\n" |
| "a=fmtp:97 apt=96\r\n" |
| "a=ssrc-group:FID 1 2 3\r\n" |
| "a=ssrc:1 cname:test\r\n" |
| "a=ssrc:2 cname:test\r\n" |
| "a=ssrc:3 cname:test\r\n"; |
| auto offer = CreateSessionDescription(SdpType::kOffer, sdp); |
| RTCError error; |
| pc->SetRemoteDescription(std::move(offer), &error); |
| EXPECT_FALSE(error.ok()); |
| EXPECT_EQ(error.type(), RTCErrorType::INVALID_PARAMETER); |
| } |
| |
| TEST_F(SdpOfferAnswerTest, ExpectTwoSsrcsInSsrcGroupFecFr) { |
| auto pc = CreatePeerConnection(); |
| std::string sdp = |
| "v=0\r\n" |
| "o=- 0 3 IN IP4 127.0.0.1\r\n" |
| "s=-\r\n" |
| "t=0 0\r\n" |
| "a=group:BUNDLE 0\r\n" |
| "a=fingerprint:sha-1 " |
| "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB\r\n" |
| "a=setup:actpass\r\n" |
| "a=ice-ufrag:ETEn\r\n" |
| "a=ice-pwd:OtSK0WpNtpUjkY4+86js7Z/l\r\n" |
| "m=video 9 UDP/TLS/RTP/SAVPF 96 98\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=rtcp-mux\r\n" |
| "a=sendonly\r\n" |
| "a=mid:0\r\n" |
| "a=rtpmap:96 H264/90000\r\n" |
| "a=fmtp:96 " |
| "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=" |
| "42e01f\r\n" |
| "a=rtpmap:98 flexfec-03/90000\r\n" |
| "a=fmtp:98 repair-window=10000000\r\n" |
| "a=ssrc-group:FEC-FR 1 2 3\r\n" |
| "a=ssrc:1 cname:test\r\n" |
| "a=ssrc:2 cname:test\r\n" |
| "a=ssrc:3 cname:test\r\n"; |
| auto offer = CreateSessionDescription(SdpType::kOffer, sdp); |
| RTCError error; |
| pc->SetRemoteDescription(std::move(offer), &error); |
| EXPECT_FALSE(error.ok()); |
| EXPECT_EQ(error.type(), RTCErrorType::INVALID_PARAMETER); |
| } |
| |
| TEST_F(SdpOfferAnswerTest, ExpectAtMostFourSsrcsInSsrcGroupSIM) { |
| auto pc = CreatePeerConnection(); |
| std::string sdp = |
| "v=0\r\n" |
| "o=- 0 3 IN IP4 127.0.0.1\r\n" |
| "s=-\r\n" |
| "t=0 0\r\n" |
| "a=group:BUNDLE 0\r\n" |
| "a=fingerprint:sha-1 " |
| "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB\r\n" |
| "a=setup:actpass\r\n" |
| "a=ice-ufrag:ETEn\r\n" |
| "a=ice-pwd:OtSK0WpNtpUjkY4+86js7Z/l\r\n" |
| "m=video 9 UDP/TLS/RTP/SAVPF 96 97\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=rtcp-mux\r\n" |
| "a=sendonly\r\n" |
| "a=mid:0\r\n" |
| "a=rtpmap:96 H264/90000\r\n" |
| "a=fmtp:96 " |
| "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=" |
| "42e01f\r\n" |
| "a=rtpmap:97 rtx/90000\r\n" |
| "a=fmtp:97 apt=96\r\n" |
| "a=ssrc-group:SIM 1 2 3 4\r\n" |
| "a=ssrc:1 cname:test\r\n" |
| "a=ssrc:2 cname:test\r\n" |
| "a=ssrc:3 cname:test\r\n" |
| "a=ssrc:4 cname:test\r\n"; |
| auto offer = CreateSessionDescription(SdpType::kOffer, sdp); |
| RTCError error; |
| pc->SetRemoteDescription(std::move(offer), &error); |
| EXPECT_FALSE(error.ok()); |
| EXPECT_EQ(error.type(), RTCErrorType::INVALID_PARAMETER); |
| } |
| |
| TEST_F(SdpOfferAnswerTest, DuplicateSsrcsDisallowedInLocalDescription) { |
| auto pc = CreatePeerConnection(); |
| pc->AddAudioTrack("audio_track", {}); |
| pc->AddVideoTrack("video_track", {}); |
| auto offer = pc->CreateOffer(); |
| auto& offer_contents = offer->description()->contents(); |
| ASSERT_EQ(offer_contents.size(), 2u); |
| uint32_t second_ssrc = offer_contents[1].media_description()->first_ssrc(); |
| |
| offer->description() |
| ->contents()[0] |
| .media_description() |
| ->mutable_streams()[0] |
| .ssrcs[0] = second_ssrc; |
| EXPECT_FALSE(pc->SetLocalDescription(std::move(offer))); |
| } |
| |
| TEST_F(SdpOfferAnswerTest, |
| DuplicateSsrcsAcrossMlinesDisallowedInLocalDescriptionTwoSsrc) { |
| auto pc = CreatePeerConnection(); |
| |
| pc->AddAudioTrack("audio_track", {}); |
| pc->AddVideoTrack("video_track", {}); |
| auto offer = pc->CreateOffer(); |
| auto& offer_contents = offer->description()->contents(); |
| ASSERT_EQ(offer_contents.size(), 2u); |
| uint32_t audio_ssrc = offer_contents[0].media_description()->first_ssrc(); |
| ASSERT_EQ(offer_contents[1].media_description()->streams().size(), 1u); |
| auto& video_stream = offer->description() |
| ->contents()[1] |
| .media_description() |
| ->mutable_streams()[0]; |
| ASSERT_EQ(video_stream.ssrcs.size(), 2u); |
| ASSERT_EQ(video_stream.ssrc_groups.size(), 1u); |
| video_stream.ssrcs[1] = audio_ssrc; |
| video_stream.ssrc_groups[0].ssrcs[1] = audio_ssrc; |
| video_stream.ssrc_groups[0].semantics = cricket::kSimSsrcGroupSemantics; |
| std::string sdp; |
| offer->ToString(&sdp); |
| |
| // Trim the last two lines which contain ssrc-specific attributes |
| // that we change/munge above. Guarded with expectation about what |
| // should be removed in case the SDP generation changes. |
| size_t end = sdp.rfind("\r\n"); |
| end = sdp.rfind("\r\n", end - 2); |
| end = sdp.rfind("\r\n", end - 2); |
| EXPECT_EQ(sdp.substr(end + 2), "a=ssrc:" + rtc::ToString(audio_ssrc) + |
| " cname:" + video_stream.cname + |
| "\r\n" |
| "a=ssrc:" + |
| rtc::ToString(audio_ssrc) + |
| " msid:- video_track\r\n"); |
| |
| auto modified_offer = |
| CreateSessionDescription(SdpType::kOffer, sdp.substr(0, end + 2)); |
| EXPECT_FALSE(pc->SetLocalDescription(std::move(modified_offer))); |
| } |
| |
| TEST_F(SdpOfferAnswerTest, |
| DuplicateSsrcsAcrossMlinesDisallowedInLocalDescriptionThreeSsrcs) { |
| auto pc = CreatePeerConnection(); |
| |
| pc->AddAudioTrack("audio_track", {}); |
| pc->AddVideoTrack("video_track", {}); |
| auto offer = pc->CreateOffer(); |
| auto& offer_contents = offer->description()->contents(); |
| ASSERT_EQ(offer_contents.size(), 2u); |
| uint32_t audio_ssrc = offer_contents[0].media_description()->first_ssrc(); |
| ASSERT_EQ(offer_contents[1].media_description()->streams().size(), 1u); |
| auto& video_stream = offer->description() |
| ->contents()[1] |
| .media_description() |
| ->mutable_streams()[0]; |
| ASSERT_EQ(video_stream.ssrcs.size(), 2u); |
| ASSERT_EQ(video_stream.ssrc_groups.size(), 1u); |
| video_stream.ssrcs.push_back(audio_ssrc); |
| video_stream.ssrc_groups[0].ssrcs.push_back(audio_ssrc); |
| video_stream.ssrc_groups[0].semantics = cricket::kSimSsrcGroupSemantics; |
| std::string sdp; |
| offer->ToString(&sdp); |
| |
| // Trim the last two lines which contain ssrc-specific attributes |
| // that we change/munge above. Guarded with expectation about what |
| // should be removed in case the SDP generation changes. |
| size_t end = sdp.rfind("\r\n"); |
| end = sdp.rfind("\r\n", end - 2); |
| end = sdp.rfind("\r\n", end - 2); |
| EXPECT_EQ(sdp.substr(end + 2), "a=ssrc:" + rtc::ToString(audio_ssrc) + |
| " cname:" + video_stream.cname + |
| "\r\n" |
| "a=ssrc:" + |
| rtc::ToString(audio_ssrc) + |
| " msid:- video_track\r\n"); |
| |
| auto modified_offer = |
| CreateSessionDescription(SdpType::kOffer, sdp.substr(0, end + 2)); |
| EXPECT_FALSE(pc->SetLocalDescription(std::move(modified_offer))); |
| } |
| |
| TEST_F(SdpOfferAnswerTest, AllowOnlyOneSsrcGroupPerSemanticAndPrimarySsrc) { |
| auto pc = CreatePeerConnection(); |
| |
| pc->AddAudioTrack("audio_track", {}); |
| pc->AddVideoTrack("video_track", {}); |
| auto offer = pc->CreateOffer(); |
| auto& offer_contents = offer->description()->contents(); |
| ASSERT_EQ(offer_contents.size(), 2u); |
| uint32_t audio_ssrc = offer_contents[0].media_description()->first_ssrc(); |
| ASSERT_EQ(offer_contents[1].media_description()->streams().size(), 1u); |
| auto& video_stream = offer->description() |
| ->contents()[1] |
| .media_description() |
| ->mutable_streams()[0]; |
| ASSERT_EQ(video_stream.ssrcs.size(), 2u); |
| ASSERT_EQ(video_stream.ssrc_groups.size(), 1u); |
| video_stream.ssrcs.push_back(audio_ssrc); |
| video_stream.ssrc_groups.push_back( |
| {cricket::kFidSsrcGroupSemantics, {video_stream.ssrcs[0], audio_ssrc}}); |
| std::string sdp; |
| offer->ToString(&sdp); |
| |
| // Trim the last two lines which contain ssrc-specific attributes |
| // that we change/munge above. Guarded with expectation about what |
| // should be removed in case the SDP generation changes. |
| size_t end = sdp.rfind("\r\n"); |
| end = sdp.rfind("\r\n", end - 2); |
| end = sdp.rfind("\r\n", end - 2); |
| EXPECT_EQ(sdp.substr(end + 2), "a=ssrc:" + rtc::ToString(audio_ssrc) + |
| " cname:" + video_stream.cname + |
| "\r\n" |
| "a=ssrc:" + |
| rtc::ToString(audio_ssrc) + |
| " msid:- video_track\r\n"); |
| |
| auto modified_offer = |
| CreateSessionDescription(SdpType::kOffer, sdp.substr(0, end + 2)); |
| EXPECT_FALSE(pc->SetLocalDescription(std::move(modified_offer))); |
| } |
| |
| TEST_F(SdpOfferAnswerTest, OfferWithRtxAndNoMsidIsNotRejected) { |
| auto pc = CreatePeerConnection(); |
| std::string sdp = |
| "v=0\r\n" |
| "o=- 0 3 IN IP4 127.0.0.1\r\n" |
| "s=-\r\n" |
| "t=0 0\r\n" |
| "a=group:BUNDLE 0\r\n" |
| "a=fingerprint:sha-1 " |
| "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB\r\n" |
| "a=setup:actpass\r\n" |
| "a=ice-ufrag:ETEn\r\n" |
| "a=ice-pwd:OtSK0WpNtpUjkY4+86js7Z/l\r\n" |
| "m=video 9 UDP/TLS/RTP/SAVPF 96 97\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=rtcp-mux\r\n" |
| "a=sendonly\r\n" |
| "a=mid:0\r\n" |
| // "a=msid:stream obsoletetrack\r\n" |
| "a=rtpmap:96 VP8/90000\r\n" |
| "a=rtpmap:97 rtx/90000\r\n" |
| "a=fmtp:97 apt=96\r\n" |
| "a=ssrc-group:FID 1 2\r\n" |
| "a=ssrc:1 cname:test\r\n" |
| "a=ssrc:2 cname:test\r\n"; |
| auto offer = CreateSessionDescription(SdpType::kOffer, sdp); |
| EXPECT_TRUE(pc->SetRemoteDescription(std::move(offer))); |
| } |
| |
| TEST_F(SdpOfferAnswerTest, RejectsAnswerWithInvalidTransport) { |
| auto pc1 = CreatePeerConnection(); |
| pc1->AddAudioTrack("audio_track", {}); |
| auto pc2 = CreatePeerConnection(); |
| pc2->AddAudioTrack("anotheraudio_track", {}); |
| |
| auto initial_offer = pc1->CreateOfferAndSetAsLocal(); |
| ASSERT_EQ(initial_offer->description()->contents().size(), 1u); |
| auto mid = initial_offer->description()->contents()[0].mid(); |
| |
| EXPECT_TRUE(pc2->SetRemoteDescription(std::move(initial_offer))); |
| auto initial_answer = pc2->CreateAnswerAndSetAsLocal(); |
| |
| std::string sdp; |
| initial_answer->ToString(&sdp); |
| EXPECT_TRUE(pc1->SetRemoteDescription(std::move(initial_answer))); |
| |
| auto transceivers = pc1->pc()->GetTransceivers(); |
| ASSERT_EQ(transceivers.size(), 1u); |
| // This stops the only transport. |
| transceivers[0]->StopStandard(); |
| |
| auto subsequent_offer = pc1->CreateOfferAndSetAsLocal(); |
| // But the remote answers with a non-rejected m-line which is not valid. |
| auto bad_answer = CreateSessionDescription( |
| SdpType::kAnswer, |
| absl::StrReplaceAll(sdp, {{"a=group:BUNDLE " + mid + "\r\n", ""}})); |
| |
| RTCError error; |
| pc1->SetRemoteDescription(std::move(bad_answer), &error); |
| EXPECT_FALSE(error.ok()); |
| EXPECT_EQ(error.type(), RTCErrorType::INVALID_PARAMETER); |
| } |
| |
| TEST_F(SdpOfferAnswerTest, SdpMungingWithInvalidPayloadTypeIsRejected) { |
| auto pc = CreatePeerConnection(); |
| pc->AddAudioTrack("audio_track", {}); |
| |
| auto offer = pc->CreateOffer(); |
| ASSERT_EQ(offer->description()->contents().size(), 1u); |
| auto* audio = offer->description()->contents()[0].media_description(); |
| ASSERT_GT(audio->codecs().size(), 0u); |
| EXPECT_TRUE(audio->rtcp_mux()); |
| auto codecs = audio->codecs(); |
| for (int invalid_payload_type = 64; invalid_payload_type < 96; |
| invalid_payload_type++) { |
| codecs[0].id = |
| invalid_payload_type; // The range [64-95] is disallowed with rtcp_mux. |
| audio->set_codecs(codecs); |
| // ASSERT to avoid getting into a bad state. |
| ASSERT_FALSE(pc->SetLocalDescription(offer->Clone())); |
| ASSERT_FALSE(pc->SetRemoteDescription(offer->Clone())); |
| } |
| } |
| |
| TEST_F(SdpOfferAnswerTest, MsidSignalingInSubsequentOfferAnswer) { |
| auto pc = CreatePeerConnection(); |
| pc->AddAudioTrack("audio_track", {}); |
| |
| std::string sdp = |
| "v=0\r\n" |
| "o=- 0 3 IN IP4 127.0.0.1\r\n" |
| "s=-\r\n" |
| "t=0 0\r\n" |
| "a=msid-semantic: WMS\r\n" |
| "a=fingerprint:sha-1 " |
| "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB\r\n" |
| "a=setup:actpass\r\n" |
| "a=ice-ufrag:ETEn\r\n" |
| "a=ice-pwd:OtSK0WpNtpUjkY4+86js7Z/l\r\n" |
| "m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=rtcp:9 IN IP4 0.0.0.0\r\n" |
| "a=recvonly\r\n" |
| "a=rtcp-mux\r\n" |
| "a=rtpmap:111 opus/48000/2\r\n"; |
| |
| auto offer = CreateSessionDescription(SdpType::kOffer, sdp); |
| EXPECT_TRUE(pc->SetRemoteDescription(std::move(offer))); |
| |
| // Check the generated SDP. |
| auto answer = pc->CreateAnswer(); |
| answer->ToString(&sdp); |
| EXPECT_NE(std::string::npos, sdp.find("a=msid:- audio_track\r\n")); |
| |
| EXPECT_TRUE(pc->SetLocalDescription(std::move(answer))); |
| |
| // Check the local description object. |
| auto local_description = pc->pc()->local_description(); |
| ASSERT_EQ(local_description->description()->contents().size(), 1u); |
| auto streams = local_description->description() |
| ->contents()[0] |
| .media_description() |
| ->streams(); |
| ASSERT_EQ(streams.size(), 1u); |
| EXPECT_EQ(streams[0].id, "audio_track"); |
| |
| // Check the serialization of the local description. |
| local_description->ToString(&sdp); |
| EXPECT_NE(std::string::npos, sdp.find("a=msid:- audio_track\r\n")); |
| } |
| |
| // Regression test for crbug.com/328522463 |
| // where the stream parameters got recreated which changed the ssrc. |
| TEST_F(SdpOfferAnswerTest, MsidSignalingUnknownRespondsWithMsidAndKeepsSsrc) { |
| auto pc = CreatePeerConnection(); |
| pc->AddAudioTrack("audio_track", {"default"}); |
| std::string sdp = |
| "v=0\r\n" |
| "o=- 0 3 IN IP4 127.0.0.1\r\n" |
| "s=-\r\n" |
| "t=0 0\r\n" |
| "a=group:BUNDLE 0\r\n" |
| // "a=msid-semantic: WMS *\r\n" |
| "a=ice-ufrag:ETEn\r\n" |
| "a=ice-pwd:OtSK0WpNtpUjkY4+86js7Z/l\r\n" |
| "a=fingerprint:sha-1 " |
| "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB\r\n" |
| "a=setup:actpass\r\n" |
| "m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=rtcp:9 IN IP4 0.0.0.0\r\n" |
| "a=recvonly\r\n" |
| "a=rtcp-mux\r\n" |
| "a=mid:0\r\n" |
| "a=rtpmap:111 opus/48000/2\r\n"; |
| |
| auto offer = CreateSessionDescription(SdpType::kOffer, sdp); |
| EXPECT_TRUE(pc->SetRemoteDescription(std::move(offer))); |
| auto first_transceiver = pc->pc()->GetTransceivers()[0]; |
| EXPECT_TRUE(first_transceiver |
| ->SetDirectionWithError(RtpTransceiverDirection::kSendOnly) |
| .ok()); |
| // Check the generated *serialized* SDP. |
| auto answer = pc->CreateAnswer(); |
| const auto& answer_contents = answer->description()->contents(); |
| ASSERT_EQ(answer_contents.size(), 1u); |
| auto answer_streams = answer_contents[0].media_description()->streams(); |
| ASSERT_EQ(answer_streams.size(), 1u); |
| std::string first_stream_serialized = answer_streams[0].ToString(); |
| uint32_t first_ssrc = answer_contents[0].media_description()->first_ssrc(); |
| |
| answer->ToString(&sdp); |
| EXPECT_TRUE( |
| pc->SetLocalDescription(CreateSessionDescription(SdpType::kAnswer, sdp))); |
| |
| auto reoffer = pc->CreateOffer(); |
| const auto& offer_contents = reoffer->description()->contents(); |
| ASSERT_EQ(offer_contents.size(), 1u); |
| |
| auto offer_streams = offer_contents[0].media_description()->streams(); |
| ASSERT_EQ(offer_streams.size(), 1u); |
| std::string second_stream_serialized = offer_streams[0].ToString(); |
| uint32_t second_ssrc = offer_contents[0].media_description()->first_ssrc(); |
| |
| EXPECT_EQ(first_ssrc, second_ssrc); |
| EXPECT_EQ(first_stream_serialized, second_stream_serialized); |
| EXPECT_TRUE(pc->SetLocalDescription(std::move(reoffer))); |
| } |
| |
| // Test variant with boolean order for audio-video and video-audio. |
| class SdpOfferAnswerShuffleMediaTypes |
| : public SdpOfferAnswerTest, |
| public testing::WithParamInterface<bool> { |
| public: |
| SdpOfferAnswerShuffleMediaTypes() : SdpOfferAnswerTest() {} |
| }; |
| |
| TEST_P(SdpOfferAnswerShuffleMediaTypes, |
| RecyclingWithDifferentKindAndSameMidFailsAnswer) { |
| bool audio_first = GetParam(); |
| auto pc1 = CreatePeerConnection(); |
| auto pc2 = CreatePeerConnection(); |
| if (audio_first) { |
| pc1->AddAudioTrack("audio_track", {}); |
| pc2->AddVideoTrack("video_track", {}); |
| } else { |
| pc2->AddAudioTrack("audio_track", {}); |
| pc1->AddVideoTrack("video_track", {}); |
| } |
| |
| auto initial_offer = pc1->CreateOfferAndSetAsLocal(); |
| ASSERT_EQ(initial_offer->description()->contents().size(), 1u); |
| auto mid1 = initial_offer->description()->contents()[0].mid(); |
| std::string rejected_answer_sdp = |
| "v=0\r\n" |
| "o=- 8621259572628890423 2 IN IP4 127.0.0.1\r\n" |
| "s=-\r\n" |
| "t=0 0\r\n" |
| "m=" + |
| std::string(audio_first ? "audio" : "video") + |
| " 0 UDP/TLS/RTP/SAVPF 111\r\n" |
| "c=IN IP4 0.0.0.0\r\n"; |
| auto rejected_answer = |
| CreateSessionDescription(SdpType::kAnswer, rejected_answer_sdp); |
| EXPECT_TRUE(pc1->SetRemoteDescription(std::move(rejected_answer))); |
| |
| auto offer = |
| pc2->CreateOfferAndSetAsLocal(); // This will generate a mid=0 too |
| ASSERT_EQ(offer->description()->contents().size(), 1u); |
| auto mid2 = offer->description()->contents()[0].mid(); |
| EXPECT_EQ(mid1, mid2); // Check that the mids collided. |
| EXPECT_TRUE(pc1->SetRemoteDescription(std::move(offer))); |
| auto answer = pc1->CreateAnswer(); |
| EXPECT_FALSE(pc1->SetLocalDescription(std::move(answer))); |
| } |
| |
| // Similar to the previous test but with implicit rollback and creating |
| // an offer, triggering a different codepath. |
| TEST_P(SdpOfferAnswerShuffleMediaTypes, |
| RecyclingWithDifferentKindAndSameMidFailsOffer) { |
| bool audio_first = GetParam(); |
| auto pc1 = CreatePeerConnection(); |
| auto pc2 = CreatePeerConnection(); |
| if (audio_first) { |
| pc1->AddAudioTrack("audio_track", {}); |
| pc2->AddVideoTrack("video_track", {}); |
| } else { |
| pc2->AddAudioTrack("audio_track", {}); |
| pc1->AddVideoTrack("video_track", {}); |
| } |
| |
| auto initial_offer = pc1->CreateOfferAndSetAsLocal(); |
| ASSERT_EQ(initial_offer->description()->contents().size(), 1u); |
| auto mid1 = initial_offer->description()->contents()[0].mid(); |
| std::string rejected_answer_sdp = |
| "v=0\r\n" |
| "o=- 8621259572628890423 2 IN IP4 127.0.0.1\r\n" |
| "s=-\r\n" |
| "t=0 0\r\n" |
| "m=" + |
| std::string(audio_first ? "audio" : "video") + |
| " 0 UDP/TLS/RTP/SAVPF 111\r\n" |
| "c=IN IP4 0.0.0.0\r\n"; |
| auto rejected_answer = |
| CreateSessionDescription(SdpType::kAnswer, rejected_answer_sdp); |
| EXPECT_TRUE(pc1->SetRemoteDescription(std::move(rejected_answer))); |
| |
| auto offer = |
| pc2->CreateOfferAndSetAsLocal(); // This will generate a mid=0 too |
| ASSERT_EQ(offer->description()->contents().size(), 1u); |
| auto mid2 = offer->description()->contents()[0].mid(); |
| EXPECT_EQ(mid1, mid2); // Check that the mids collided. |
| EXPECT_TRUE(pc1->SetRemoteDescription(std::move(offer))); |
| EXPECT_FALSE(pc1->CreateOffer()); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(SdpOfferAnswerShuffleMediaTypes, |
| SdpOfferAnswerShuffleMediaTypes, |
| ::testing::Values(true, false)); |
| |
| TEST_F(SdpOfferAnswerTest, OfferWithNoCompatibleCodecsIsRejectedInAnswer) { |
| auto pc = CreatePeerConnection(); |
| // An offer with no common codecs. This should reject both contents |
| // in the answer without throwing an error. |
| std::string sdp = |
| "v=0\r\n" |
| "o=- 0 3 IN IP4 127.0.0.1\r\n" |
| "s=-\r\n" |
| "t=0 0\r\n" |
| "a=fingerprint:sha-1 " |
| "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB\r\n" |
| "a=setup:actpass\r\n" |
| "a=ice-ufrag:ETEn\r\n" |
| "a=ice-pwd:OtSK0WpNtpUjkY4+86js7Z/l\r\n" |
| "m=audio 9 RTP/SAVPF 97\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=sendrecv\r\n" |
| "a=rtpmap:97 x-unknown/90000\r\n" |
| "a=rtcp-mux\r\n" |
| "m=video 9 RTP/SAVPF 98\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=sendrecv\r\n" |
| "a=rtpmap:98 H263-1998/90000\r\n" |
| "a=fmtp:98 CIF=1;QCIF=1\r\n" |
| "a=rtcp-mux\r\n"; |
| |
| auto desc = CreateSessionDescription(SdpType::kOffer, sdp); |
| ASSERT_NE(desc, nullptr); |
| RTCError error; |
| pc->SetRemoteDescription(std::move(desc), &error); |
| EXPECT_TRUE(error.ok()); |
| |
| auto answer = pc->CreateAnswer(); |
| auto answer_contents = answer->description()->contents(); |
| ASSERT_EQ(answer_contents.size(), 2u); |
| EXPECT_EQ(answer_contents[0].rejected, true); |
| EXPECT_EQ(answer_contents[1].rejected, true); |
| } |
| |
| TEST_F(SdpOfferAnswerTest, OfferWithRejectedMlineWithoutFingerprintIsAccepted) { |
| auto pc = CreatePeerConnection(); |
| // A rejected m-line without fingerprint. |
| // The answer does not require one. |
| std::string sdp = |
| "v=0\r\n" |
| "o=- 0 3 IN IP4 127.0.0.1\r\n" |
| "s=-\r\n" |
| "t=0 0\r\n" |
| "a=setup:actpass\r\n" |
| "a=ice-ufrag:ETEn\r\n" |
| "a=ice-pwd:OtSK0WpNtpUjkY4+86js7Z/l\r\n" |
| "m=audio 0 RTP/SAVPF 111\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=sendrecv\r\n" |
| "a=rtpmap:111 opus/48000/2\r\n" |
| "a=rtcp-mux\r\n"; |
| auto desc = CreateSessionDescription(SdpType::kOffer, sdp); |
| ASSERT_NE(desc, nullptr); |
| RTCError error; |
| pc->SetRemoteDescription(std::move(desc), &error); |
| EXPECT_TRUE(error.ok()); |
| |
| auto answer = pc->CreateAnswer(); |
| EXPECT_TRUE(pc->SetLocalDescription(std::move(answer))); |
| } |
| |
| TEST_F(SdpOfferAnswerTest, MidBackfillAnswer) { |
| auto pc = CreatePeerConnection(); |
| // An offer without a mid backfills the mid. This is currently |
| // done with a per-peerconnection counter that starts from 0. |
| // JSEP says to only include the mid in the answer if it was in the offer |
| // but due to backfill it is always present. |
| // TODO: https://issues.webrtc.org/issues/338529222 - don't respond with mid. |
| std::string sdp = |
| "v=0\r\n" |
| "o=- 0 3 IN IP4 127.0.0.1\r\n" |
| "s=-\r\n" |
| "t=0 0\r\n" |
| "a=setup:actpass\r\n" |
| "a=ice-ufrag:ETEn\r\n" |
| "a=ice-pwd:OtSK0WpNtpUjkY4+86js7Z/l\r\n" |
| "a=fingerprint:sha-1 " |
| "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB\r\n" |
| "a=setup:actpass\r\n" |
| "m=audio 9 RTP/SAVPF 111\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=sendrecv\r\n" |
| "a=rtpmap:111 opus/48000/2\r\n" |
| // "a=mid:0\r\n" |
| "a=rtcp-mux\r\n"; |
| auto desc = CreateSessionDescription(SdpType::kOffer, sdp); |
| ASSERT_NE(desc, nullptr); |
| RTCError error; |
| pc->SetRemoteDescription(std::move(desc), &error); |
| EXPECT_TRUE(error.ok()); |
| auto offer_contents = |
| pc->pc()->remote_description()->description()->contents(); |
| ASSERT_EQ(offer_contents.size(), 1u); |
| EXPECT_EQ(offer_contents[0].mid(), "0"); |
| auto answer = pc->CreateAnswerAndSetAsLocal(); |
| auto answer_contents = answer->description()->contents(); |
| ASSERT_EQ(answer_contents.size(), 1u); |
| EXPECT_EQ(answer_contents[0].mid(), offer_contents[0].mid()); |
| } |
| |
| TEST_F(SdpOfferAnswerTest, MidBackfillDoesNotCheckAgainstBundleGroup) { |
| auto pc = CreatePeerConnection(); |
| // An offer with a BUNDLE group specifying a mid that is not present |
| // in the offer. This is not rejected due to the mid being backfilled |
| // starting at 0. |
| // TODO: https://issues.webrtc.org/issues/338528603 - reject this. |
| std::string sdp = |
| "v=0\r\n" |
| "o=- 0 3 IN IP4 127.0.0.1\r\n" |
| "s=-\r\n" |
| "t=0 0\r\n" |
| "a=setup:actpass\r\n" |
| "a=ice-ufrag:ETEn\r\n" |
| "a=ice-pwd:OtSK0WpNtpUjkY4+86js7Z/l\r\n" |
| "a=fingerprint:sha-1 " |
| "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB\r\n" |
| "a=setup:actpass\r\n" |
| "a=group:BUNDLE 0\r\n" |
| "m=audio 9 RTP/SAVPF 111\r\n" |
| "c=IN IP4 0.0.0.0\r\n" |
| "a=sendrecv\r\n" |
| "a=rtpmap:111 opus/48000/2\r\n" |
| // "a=mid:0\r\n" |
| "a=rtcp-mux\r\n"; |
| auto desc = CreateSessionDescription(SdpType::kOffer, sdp); |
| ASSERT_NE(desc, nullptr); |
| RTCError error; |
| pc->SetRemoteDescription(std::move(desc), &error); |
| EXPECT_TRUE(error.ok()); |
| EXPECT_TRUE(pc->CreateAnswerAndSetAsLocal()); |
| } |
| |
| } // namespace webrtc |