| /* |
| * libjingle |
| * Copyright 2011, Google Inc. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * 3. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO |
| * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
| * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
| * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include <string> |
| |
| #include "talk/app/webrtc/webrtcsdp.h" |
| #include "talk/base/gunit.h" |
| #include "talk/base/logging.h" |
| #include "talk/base/scoped_ptr.h" |
| #include "talk/base/stringutils.h" |
| #include "talk/p2p/base/constants.h" |
| #include "talk/session/phone/mediasession.h" |
| |
| typedef std::vector<cricket::Candidate> Candidates; |
| using cricket::AudioCodec; |
| using cricket::AudioContentDescription; |
| using cricket::ContentInfo; |
| using cricket::CryptoParams; |
| using cricket::SessionDescription; |
| using cricket::StreamParams; |
| using cricket::VideoCodec; |
| using cricket::VideoContentDescription; |
| |
| // Reference sdp string |
| static const char kSdpString[] = |
| "v=0\r\n" |
| "o=- 0 0 IN IP4 127.0.0.1\r\n" |
| "s=\r\n" |
| "t=0 0\r\n" |
| "a=group:BUNDLE audio video\r\n" |
| "m=audio 0 RTP/AVPF 103 104\r\n" |
| "a=mid:audio\r\n" |
| "a=rtcp-mux\r\n" |
| "a=crypto:1 AES_CM_128_HMAC_SHA1_32 " |
| "inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 \r\n" |
| "a=rtpmap:103 ISAC/16000\r\n" |
| "a=rtpmap:104 ISAC/32000\r\n" |
| "a=candidate:1 1 udp 1 127.0.0.1 1234 typ host name rtp network_name " |
| "eth0 username user_rtp password password_rtp generation 0\r\n" |
| "a=candidate:1 1 udp 1 127.0.0.1 1235 typ host name rtcp network_name " |
| "eth0 username user_rtcp password password_rtcp generation 0\r\n" |
| "a=ssrc:1 cname:stream_1_cname mslabel:local_stream_1 " |
| "label:local_audio_1\r\n" |
| "a=ssrc:4 cname:stream_2_cname mslabel:local_stream_2 " |
| "label:local_audio_2\r\n" |
| "m=video 0 RTP/AVPF 120\r\n" |
| "a=mid:video\r\n" |
| "a=crypto:1 AES_CM_128_HMAC_SHA1_80 " |
| "inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32 \r\n" |
| "a=rtpmap:120 VP8/0\r\n" |
| "a=candidate:1 1 udp 1 127.0.0.1 1236 typ host name video_rtcp " |
| "network_name eth0 username user_video_rtcp password password_video_rtcp " |
| "generation 0\r\n" |
| "a=candidate:1 1 udp 1 127.0.0.1 1237 typ host name video_rtp " |
| "network_name eth0 username user_video_rtp password password_video_rtp " |
| "generation 0\r\n" |
| "a=ssrc:2 cname:stream_1_cname mslabel:local_stream_1 " |
| "label:local_video_1\r\n" |
| "a=ssrc:3 cname:stream_1_cname mslabel:local_stream_1 " |
| "label:local_video_2\r\n" |
| "a=ssrc:5 cname:stream_2_cname mslabel:local_stream_2 " |
| "label:local_video_3\r\n"; |
| |
| static const char kSdpDestroyer[] = "!@#$%^&"; |
| |
| // MediaStream 1 |
| static const char kStreamLabel1[] = "local_stream_1"; |
| static const char kStream1Cname[] = "stream_1_cname"; |
| static const char kAudioTrackLabel1[] = "local_audio_1"; |
| static const uint32 kAudioTrack1Ssrc = 1; |
| static const char kVideoTrackLabel1[] = "local_video_1"; |
| static const uint32 kVideoTrack1Ssrc = 2; |
| static const char kVideoTrackLabel2[] = "local_video_2"; |
| static const uint32 kVideoTrack2Ssrc = 3; |
| |
| // MediaStream 2 |
| static const char kStreamLabel2[] = "local_stream_2"; |
| static const char kStream2Cname[] = "stream_2_cname"; |
| static const char kAudioTrackLabel2[] = "local_audio_2"; |
| static const uint32 kAudioTrack2Ssrc = 4; |
| static const char kVideoTrackLabel3[] = "local_video_3"; |
| static const uint32 kVideoTrack3Ssrc = 5; |
| |
| class WebRtcSdpTest : public testing::Test { |
| public: |
| WebRtcSdpTest() { |
| // AudioContentDescription |
| talk_base::scoped_ptr<AudioContentDescription> audio( |
| new AudioContentDescription()); |
| audio->set_rtcp_mux(true); |
| StreamParams audio_stream1; |
| audio_stream1.name = kAudioTrackLabel1; |
| audio_stream1.cname = kStream1Cname; |
| audio_stream1.sync_label = kStreamLabel1; |
| audio_stream1.ssrcs.push_back(kAudioTrack1Ssrc); |
| audio->AddStream(audio_stream1); |
| StreamParams audio_stream2; |
| audio_stream2.name = kAudioTrackLabel2; |
| audio_stream2.cname = kStream2Cname; |
| audio_stream2.sync_label = kStreamLabel2; |
| audio_stream2.ssrcs.push_back(kAudioTrack2Ssrc); |
| audio->AddStream(audio_stream2); |
| audio->AddCrypto(CryptoParams(1, "AES_CM_128_HMAC_SHA1_32", |
| "inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32", "")); |
| audio->AddCodec(AudioCodec(103, "ISAC", 16000, 0, 0, 0)); |
| audio->AddCodec(AudioCodec(104, "ISAC", 32000, 0, 0, 0)); |
| desc_.AddContent(cricket::CN_AUDIO, cricket::NS_JINGLE_RTP, |
| audio.release()); |
| |
| // VideoContentDescription |
| talk_base::scoped_ptr<VideoContentDescription> video( |
| new VideoContentDescription()); |
| StreamParams video_stream1; |
| video_stream1.name = kVideoTrackLabel1; |
| video_stream1.cname = kStream1Cname; |
| video_stream1.sync_label = kStreamLabel1; |
| video_stream1.ssrcs.push_back(kVideoTrack1Ssrc); |
| video->AddStream(video_stream1); |
| StreamParams video_stream2; |
| video_stream2.name = kVideoTrackLabel2; |
| video_stream2.cname = kStream1Cname; |
| video_stream2.sync_label = kStreamLabel1; |
| video_stream2.ssrcs.push_back(kVideoTrack2Ssrc); |
| video->AddStream(video_stream2); |
| StreamParams video_stream3; |
| video_stream3.name = kVideoTrackLabel3; |
| video_stream3.cname = kStream2Cname; |
| video_stream3.sync_label = kStreamLabel2; |
| video_stream3.ssrcs.push_back(kVideoTrack3Ssrc); |
| video->AddStream(video_stream3); |
| video->AddCrypto(CryptoParams(1, "AES_CM_128_HMAC_SHA1_80", |
| "inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32", "")); |
| video->AddCodec(VideoCodec(120, "VP8", 352, 288, 30, 0)); |
| desc_.AddContent(cricket::CN_VIDEO, cricket::NS_JINGLE_RTP, |
| video.release()); |
| |
| int port = 1234; |
| talk_base::SocketAddress address("127.0.0.1", port++); |
| cricket::Candidate candidate1("rtp", "udp", address, 1, |
| "user_rtp", "password_rtp", "local", "eth0", 0); |
| address.SetPort(port++); |
| cricket::Candidate candidate2("rtcp", "udp", address, 1, |
| "user_rtcp", "password_rtcp", "local", "eth0", 0); |
| address.SetPort(port++); |
| cricket::Candidate candidate3("video_rtcp", "udp", address, 1, |
| "user_video_rtcp", "password_video_rtcp", "local", "eth0", 0); |
| address.SetPort(port++); |
| cricket::Candidate candidate4("video_rtp", "udp", address, 1, |
| "user_video_rtp", "password_video_rtp", "local", "eth0", 0); |
| |
| candidates_.push_back(candidate1); |
| candidates_.push_back(candidate2); |
| candidates_.push_back(candidate3); |
| candidates_.push_back(candidate4); |
| } |
| |
| bool CompareSessionDescription(const SessionDescription& desc1, |
| const SessionDescription& desc2) { |
| const ContentInfo* ac1 = desc1.GetContentByName("audio"); |
| const AudioContentDescription* acd1 = |
| static_cast<const AudioContentDescription*>(ac1->description); |
| const ContentInfo* vc1 = desc1.GetContentByName("video"); |
| const VideoContentDescription* vcd1 = |
| static_cast<const VideoContentDescription*>(vc1->description); |
| |
| const ContentInfo* ac2 = desc2.GetContentByName("audio"); |
| const AudioContentDescription* acd2 = |
| static_cast<const AudioContentDescription*>(ac2->description); |
| const ContentInfo* vc2 = desc2.GetContentByName("video"); |
| const VideoContentDescription* vcd2 = |
| static_cast<const VideoContentDescription*>(vc2->description); |
| |
| // rtcp_mux |
| EXPECT_EQ(acd1->rtcp_mux(), acd2->rtcp_mux()); |
| EXPECT_EQ(vcd1->rtcp_mux(), vcd2->rtcp_mux()); |
| |
| // cryptos |
| EXPECT_EQ(acd1->cryptos().size(), acd2->cryptos().size()); |
| EXPECT_EQ(vcd1->cryptos().size(), vcd2->cryptos().size()); |
| for (size_t i = 0; i< acd1->cryptos().size(); ++i) { |
| const CryptoParams c1 = acd1->cryptos().at(i); |
| const CryptoParams c2 = acd2->cryptos().at(i); |
| EXPECT_TRUE(c1.Matches(c2)); |
| } |
| for (size_t i = 0; i< vcd1->cryptos().size(); ++i) { |
| const CryptoParams c1 = vcd1->cryptos().at(i); |
| const CryptoParams c2 = vcd2->cryptos().at(i); |
| EXPECT_TRUE(c1.Matches(c2)); |
| } |
| |
| // codecs |
| EXPECT_EQ(acd1->codecs().size(), acd2->codecs().size()); |
| EXPECT_EQ(vcd1->codecs().size(), vcd2->codecs().size()); |
| for (size_t i = 0; i< acd1->codecs().size(); ++i) { |
| const AudioCodec c1 = acd1->codecs().at(i); |
| const AudioCodec c2 = acd2->codecs().at(i); |
| EXPECT_TRUE(c1.Matches(c2)); |
| } |
| for (size_t i = 0; i< vcd1->codecs().size(); ++i) { |
| const VideoCodec c1 = vcd1->codecs().at(i); |
| const VideoCodec c2 = vcd2->codecs().at(i); |
| EXPECT_TRUE(c1.Matches(c2)); |
| } |
| |
| // streams |
| EXPECT_EQ(acd1->streams(), acd2->streams()); |
| EXPECT_EQ(vcd1->streams(), vcd2->streams()); |
| |
| return true; |
| } |
| |
| bool CompareCandidates(const Candidates& cs1, const Candidates& cs2) { |
| EXPECT_EQ(cs1.size(), cs2.size()); |
| |
| for (size_t i = 0; i< cs1.size(); ++i) { |
| const cricket::Candidate c1 = cs1.at(i); |
| const cricket::Candidate c2 = cs2.at(i); |
| EXPECT_TRUE(c1.IsEquivalent(c2)); |
| } |
| return true; |
| } |
| |
| bool ReplaceAndTryToParse(const char* search, const char* replace) { |
| SessionDescription desc; |
| std::vector<cricket::Candidate> candidates; |
| std::string sdp = kSdpString; |
| talk_base::replace_substrs(search, strlen(search), replace, |
| strlen(replace), &sdp); |
| return webrtc::SdpDeserialize(sdp, &desc, &candidates); |
| } |
| |
| protected: |
| SessionDescription desc_; |
| Candidates candidates_; |
| }; |
| |
| TEST_F(WebRtcSdpTest, Serialize) { |
| std::string message = webrtc::SdpSerialize(desc_, candidates_); |
| LOG(LS_INFO) << "SDP: " << message; |
| EXPECT_EQ(std::string(kSdpString), message); |
| } |
| |
| TEST_F(WebRtcSdpTest, Deserialize) { |
| SessionDescription desc; |
| std::vector<cricket::Candidate> candidates; |
| // Deserialize |
| EXPECT_TRUE(webrtc::SdpDeserialize(kSdpString, &desc, &candidates)); |
| // Verify |
| LOG(LS_INFO) << "SDP: " << webrtc::SdpSerialize(desc, candidates); |
| EXPECT_TRUE(CompareSessionDescription(desc_, desc)); |
| EXPECT_TRUE(CompareCandidates(candidates_, candidates)); |
| } |
| |
| TEST_F(WebRtcSdpTest, DeserializeBrokenSdp) { |
| // Broken session description |
| EXPECT_EQ(false, ReplaceAndTryToParse("v=", kSdpDestroyer)); |
| EXPECT_EQ(false, ReplaceAndTryToParse("o=", kSdpDestroyer)); |
| EXPECT_EQ(false, ReplaceAndTryToParse("s=", kSdpDestroyer)); |
| // Broken time description |
| EXPECT_EQ(false, ReplaceAndTryToParse("t=", kSdpDestroyer)); |
| |
| // No group line |
| EXPECT_EQ(true, ReplaceAndTryToParse("a=group:BUNDLE audio video\r\n", "")); |
| EXPECT_EQ(true, ReplaceAndTryToParse("a=mid:audio\r\n", "")); |
| EXPECT_EQ(true, ReplaceAndTryToParse("a=mid:video\r\n", "")); |
| |
| // Broken media description |
| EXPECT_EQ(true, ReplaceAndTryToParse("video 0 RTP/AVPF", kSdpDestroyer)); |
| } |