Dactyloidae/media/webrtc/signaling/test/jsep_session_unittest.cpp
Moonchild 8c395520d9 Issue #1656 - Part 1: Nuke most vim config lines in the tree.
Since these are just interpreted comments, there's 0 impact on actual code.
This removes all lines that match /* vim: set(.*)tw=80: */ with S&R -- there are
a few others scattered around which will be removed manually in a second part.
2020-09-25 22:04:12 +08:00

4234 lines
144 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "nspr.h"
#include "nss.h"
#include "ssl.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Tuple.h"
#define GTEST_HAS_RTTI 0
#include "gtest/gtest.h"
#include "gtest_utils.h"
#include "FakeMediaStreams.h"
#include "FakeMediaStreamsImpl.h"
#include "FakeLogging.h"
#include "signaling/src/sdp/SdpMediaSection.h"
#include "signaling/src/sdp/SipccSdpParser.h"
#include "signaling/src/jsep/JsepCodecDescription.h"
#include "signaling/src/jsep/JsepTrack.h"
#include "signaling/src/jsep/JsepSession.h"
#include "signaling/src/jsep/JsepSessionImpl.h"
#include "signaling/src/jsep/JsepTrack.h"
#include "mtransport_test_utils.h"
#include "FakeIPC.h"
#include "FakeIPC.cpp"
#include "TestHarness.h"
namespace mozilla {
static std::string kAEqualsCandidate("a=candidate:");
const static size_t kNumCandidatesPerComponent = 3;
class JsepSessionTestBase : public ::testing::Test
{
};
class FakeUuidGenerator : public mozilla::JsepUuidGenerator
{
public:
bool
Generate(std::string* str)
{
std::ostringstream os;
os << "FAKE_UUID_" << ++ctr;
*str = os.str();
return true;
}
private:
static uint64_t ctr;
};
uint64_t FakeUuidGenerator::ctr = 1000;
class JsepSessionTest : public JsepSessionTestBase,
public ::testing::WithParamInterface<std::string>
{
public:
JsepSessionTest()
: mSessionOff("Offerer", MakeUnique<FakeUuidGenerator>()),
mSessionAns("Answerer", MakeUnique<FakeUuidGenerator>())
{
EXPECT_EQ(NS_OK, mSessionOff.Init());
EXPECT_EQ(NS_OK, mSessionAns.Init());
AddTransportData(&mSessionOff, &mOffererTransport);
AddTransportData(&mSessionAns, &mAnswererTransport);
}
protected:
struct TransportData {
std::string mIceUfrag;
std::string mIcePwd;
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)
{
// Values here semi-borrowed from JSEP draft.
tdata->mIceUfrag = session->GetName() + "-ufrag";
tdata->mIcePwd = session->GetName() + "-1234567890";
session->SetIceCredentials(tdata->mIceUfrag, tdata->mIcePwd);
AddDtlsFingerprint("sha-1", session, tdata);
AddDtlsFingerprint("sha-256", session, tdata);
}
std::string
CreateOffer(const Maybe<JsepOfferOptions> options = Nothing())
{
JsepOfferOptions defaultOptions;
const JsepOfferOptions& optionsRef = options ? *options : defaultOptions;
std::string offer;
nsresult rv = mSessionOff.CreateOffer(optionsRef, &offer);
EXPECT_EQ(NS_OK, rv) << mSessionOff.GetLastError();
std::cerr << "OFFER: " << offer << std::endl;
ValidateTransport(mOffererTransport, offer);
return offer;
}
void
AddTracks(JsepSessionImpl& side)
{
// Add tracks.
if (types.empty()) {
types = BuildTypes(GetParam());
}
AddTracks(side, types);
// Now that we have added streams, we expect audio, then video, then
// application in the SDP, regardless of the order in which the streams were
// added.
std::sort(types.begin(), types.end());
}
void
AddTracks(JsepSessionImpl& side, const std::string& mediatypes)
{
AddTracks(side, BuildTypes(mediatypes));
}
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);
SdpMediaSection::MediaType type;
if (chunk == "audio") {
type = SdpMediaSection::kAudio;
} else if (chunk == "video") {
type = SdpMediaSection::kVideo;
} else if (chunk == "datachannel") {
type = SdpMediaSection::kApplication;
} else {
MOZ_CRASH();
}
result.push_back(type);
if (comma == std::string::npos)
break;
ptr = comma + 1;
}
return result;
}
void
AddTracks(JsepSessionImpl& side,
const std::vector<SdpMediaSection::MediaType>& mediatypes)
{
FakeUuidGenerator uuid_gen;
std::string stream_id;
std::string track_id;
ASSERT_TRUE(uuid_gen.Generate(&stream_id));
AddTracksToStream(side, stream_id, mediatypes);
}
void
AddTracksToStream(JsepSessionImpl& side,
const std::string stream_id,
const std::string& mediatypes)
{
AddTracksToStream(side, stream_id, BuildTypes(mediatypes));
}
void
AddTracksToStream(JsepSessionImpl& side,
const std::string stream_id,
const std::vector<SdpMediaSection::MediaType>& mediatypes)
{
FakeUuidGenerator uuid_gen;
std::string track_id;
for (auto track = mediatypes.begin(); track != mediatypes.end(); ++track) {
ASSERT_TRUE(uuid_gen.Generate(&track_id));
RefPtr<JsepTrack> mst(new JsepTrack(*track, stream_id, track_id));
side.AddTrack(mst);
}
}
bool HasMediaStream(std::vector<RefPtr<JsepTrack>> tracks) const {
for (auto i = tracks.begin(); i != tracks.end(); ++i) {
if ((*i)->GetMediaType() != SdpMediaSection::kApplication) {
return 1;
}
}
return 0;
}
const std::string GetFirstLocalStreamId(JsepSessionImpl& side) const {
auto tracks = side.GetLocalTracks();
return (*tracks.begin())->GetStreamId();
}
std::vector<std::string>
GetMediaStreamIds(std::vector<RefPtr<JsepTrack>> tracks) const {
std::vector<std::string> ids;
for (auto i = tracks.begin(); i != tracks.end(); ++i) {
// data channels don't have msid's
if ((*i)->GetMediaType() == SdpMediaSection::kApplication) {
continue;
}
ids.push_back((*i)->GetStreamId());
}
return ids;
}
std::vector<std::string>
GetLocalMediaStreamIds(JsepSessionImpl& side) const {
return GetMediaStreamIds(side.GetLocalTracks());
}
std::vector<std::string>
GetRemoteMediaStreamIds(JsepSessionImpl& side) const {
return GetMediaStreamIds(side.GetRemoteTracks());
}
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));
}
RefPtr<JsepTrack> GetTrack(JsepSessionImpl& side,
SdpMediaSection::MediaType type,
size_t index) const {
auto tracks = side.GetLocalTracks();
for (auto i = tracks.begin(); i != tracks.end(); ++i) {
if ((*i)->GetMediaType() != type) {
continue;
}
if (index != 0) {
--index;
continue;
}
return *i;
}
return RefPtr<JsepTrack>(nullptr);
}
RefPtr<JsepTrack> GetTrackOff(size_t index,
SdpMediaSection::MediaType type) {
return GetTrack(mSessionOff, type, index);
}
RefPtr<JsepTrack> GetTrackAns(size_t index,
SdpMediaSection::MediaType type) {
return GetTrack(mSessionAns, type, index);
}
class ComparePairsByLevel {
public:
bool operator()(const JsepTrackPair& lhs,
const JsepTrackPair& rhs) const {
return lhs.mLevel < rhs.mLevel;
}
};
std::vector<JsepTrackPair> GetTrackPairsByLevel(JsepSessionImpl& side) const {
auto pairs = side.GetNegotiatedTrackPairs();
std::sort(pairs.begin(), pairs.end(), ComparePairsByLevel());
return pairs;
}
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 RefPtr<JsepTransport>& t1,
const RefPtr<JsepTransport>& t2) const {
if (!t1 && !t2) {
return true;
}
if (!t1 || !t2) {
return false;
}
if (t1->mTransportId != t2->mTransportId) {
return false;
}
if (t1->mComponents != t2->mComponents) {
return false;
}
if (!Equals(t1->mIce, t2->mIce)) {
return false;
}
return true;
}
bool Equals(const JsepTrackPair& p1,
const JsepTrackPair& p2) const {
if (p1.mLevel != p2.mLevel) {
return false;
}
// We don't check things like mBundleLevel, since that can change without
// any changes to the transport, which is what we're really interested in.
if (p1.mSending.get() != p2.mSending.get()) {
return false;
}
if (p1.mReceiving.get() != p2.mReceiving.get()) {
return false;
}
if (!Equals(p1.mRtpTransport, p2.mRtpTransport)) {
return false;
}
if (!Equals(p1.mRtcpTransport, p2.mRtcpTransport)) {
return false;
}
return true;
}
size_t GetTrackCount(JsepSessionImpl& side,
SdpMediaSection::MediaType type) const {
auto tracks = side.GetLocalTracks();
size_t result = 0;
for (auto i = tracks.begin(); i != tracks.end(); ++i) {
if ((*i)->GetMediaType() == type) {
++result;
}
}
return result;
}
UniquePtr<Sdp> GetParsedLocalDescription(const JsepSessionImpl& side) const {
return Parse(side.GetLocalDescription());
}
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 i = mSessionOff.Codecs().begin(); i != mSessionOff.Codecs().end();
++i) {
auto* codec = *i;
if (codec->mType == type && codec->mName != codecName) {
codec->mEnabled = false;
}
}
for (auto i = mSessionAns.Codecs().begin(); i != mSessionAns.Codecs().end();
++i) {
auto* codec = *i;
if (codec->mType == type && codec->mName == codecName) {
codec->mEnabled = false;
}
}
}
std::string
CreateAnswer()
{
JsepAnswerOptions options;
std::string answer;
nsresult rv = mSessionAns.CreateAnswer(options, &answer);
EXPECT_EQ(NS_OK, rv);
std::cerr << "ANSWER: " << answer << std::endl;
ValidateTransport(mAnswererTransport, answer);
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)
{
nsresult rv = mSessionOff.SetLocalDescription(kJsepSdpOffer, offer);
if (checkFlags & CHECK_SUCCESS) {
ASSERT_EQ(NS_OK, rv);
}
if (checkFlags & CHECK_TRACKS) {
// Check that the transports exist.
ASSERT_EQ(types.size(), mSessionOff.GetTransports().size());
auto tracks = mSessionOff.GetLocalTracks();
for (size_t i = 0; i < types.size(); ++i) {
ASSERT_NE("", tracks[i]->GetStreamId());
ASSERT_NE("", tracks[i]->GetTrackId());
if (tracks[i]->GetMediaType() != SdpMediaSection::kApplication) {
std::string msidAttr("a=msid:");
msidAttr += tracks[i]->GetStreamId();
msidAttr += " ";
msidAttr += tracks[i]->GetTrackId();
ASSERT_NE(std::string::npos, offer.find(msidAttr))
<< "Did not find " << msidAttr << " in offer";
}
}
}
}
void
SetRemoteOffer(const std::string& offer, uint32_t checkFlags = ALL_CHECKS)
{
nsresult rv = mSessionAns.SetRemoteDescription(kJsepSdpOffer, offer);
if (checkFlags & CHECK_SUCCESS) {
ASSERT_EQ(NS_OK, rv);
}
if (checkFlags & CHECK_TRACKS) {
auto tracks = mSessionAns.GetRemoteTracks();
// Now verify that the right stuff is in the tracks.
ASSERT_EQ(types.size(), tracks.size());
for (size_t i = 0; i < tracks.size(); ++i) {
ASSERT_EQ(types[i], tracks[i]->GetMediaType());
ASSERT_NE("", tracks[i]->GetStreamId());
ASSERT_NE("", tracks[i]->GetTrackId());
if (tracks[i]->GetMediaType() != SdpMediaSection::kApplication) {
std::string msidAttr("a=msid:");
msidAttr += tracks[i]->GetStreamId();
msidAttr += " ";
msidAttr += tracks[i]->GetTrackId();
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)
{
nsresult rv = mSessionAns.SetLocalDescription(kJsepSdpAnswer, answer);
if (checkFlags & CHECK_SUCCESS) {
ASSERT_EQ(NS_OK, rv);
}
if (checkFlags & CHECK_TRACKS) {
// Verify that the right stuff is in the tracks.
auto pairs = mSessionAns.GetNegotiatedTrackPairs();
ASSERT_EQ(types.size(), pairs.size());
for (size_t i = 0; i < types.size(); ++i) {
ASSERT_TRUE(pairs[i].mSending);
ASSERT_EQ(types[i], pairs[i].mSending->GetMediaType());
ASSERT_TRUE(pairs[i].mReceiving);
ASSERT_EQ(types[i], pairs[i].mReceiving->GetMediaType());
ASSERT_NE("", pairs[i].mSending->GetStreamId());
ASSERT_NE("", pairs[i].mSending->GetTrackId());
// These might have been in the SDP, or might have been randomly
// chosen by JsepSessionImpl
ASSERT_NE("", pairs[i].mReceiving->GetStreamId());
ASSERT_NE("", pairs[i].mReceiving->GetTrackId());
if (pairs[i].mReceiving->GetMediaType() != SdpMediaSection::kApplication) {
std::string msidAttr("a=msid:");
msidAttr += pairs[i].mSending->GetStreamId();
msidAttr += " ";
msidAttr += pairs[i].mSending->GetTrackId();
ASSERT_NE(std::string::npos, answer.find(msidAttr))
<< "Did not find " << msidAttr << " in offer";
}
}
}
std::cerr << "OFFER pairs:" << std::endl;
DumpTrackPairs(mSessionOff);
}
void
SetRemoteAnswer(const std::string& answer, uint32_t checkFlags = ALL_CHECKS)
{
nsresult rv = mSessionOff.SetRemoteDescription(kJsepSdpAnswer, answer);
if (checkFlags & CHECK_SUCCESS) {
ASSERT_EQ(NS_OK, rv);
}
if (checkFlags & CHECK_TRACKS) {
// Verify that the right stuff is in the tracks.
auto pairs = mSessionOff.GetNegotiatedTrackPairs();
ASSERT_EQ(types.size(), pairs.size());
for (size_t i = 0; i < types.size(); ++i) {
ASSERT_TRUE(pairs[i].mSending);
ASSERT_EQ(types[i], pairs[i].mSending->GetMediaType());
ASSERT_TRUE(pairs[i].mReceiving);
ASSERT_EQ(types[i], pairs[i].mReceiving->GetMediaType());
ASSERT_NE("", pairs[i].mSending->GetStreamId());
ASSERT_NE("", pairs[i].mSending->GetTrackId());
// These might have been in the SDP, or might have been randomly
// chosen by JsepSessionImpl
ASSERT_NE("", pairs[i].mReceiving->GetStreamId());
ASSERT_NE("", pairs[i].mReceiving->GetTrackId());
if (pairs[i].mReceiving->GetMediaType() != SdpMediaSection::kApplication) {
std::string msidAttr("a=msid:");
msidAttr += pairs[i].mReceiving->GetStreamId();
msidAttr += " ";
msidAttr += pairs[i].mReceiving->GetTrackId();
ASSERT_NE(std::string::npos, answer.find(msidAttr))
<< "Did not find " << msidAttr << " in answer";
}
}
}
std::cerr << "ANSWER pairs:" << std::endl;
DumpTrackPairs(mSessionAns);
}
typedef enum {
RTP = 1,
RTCP = 2
} ComponentType;
class CandidateSet {
public:
CandidateSet() {}
void Gather(JsepSession& session,
const std::vector<SdpMediaSection::MediaType>& types,
ComponentType maxComponent = RTCP)
{
for (size_t level = 0; level < types.size(); ++level) {
Gather(session, level, RTP);
if (types[level] != SdpMediaSection::kApplication &&
maxComponent == RTCP) {
Gather(session, level, RTCP);
}
}
FinishGathering(session);
}
void Gather(JsepSession& session, size_t level, 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;
bool skipped;
session.AddLocalIceCandidate(kAEqualsCandidate + candidate.str(),
level, &mid, &skipped);
if (!skipped) {
mCandidatesToTrickle.push_back(
Tuple<Level, Mid, Candidate>(
level, mid, kAEqualsCandidate + candidate.str()));
candidates.push_back(candidate.str());
}
}
// Stomp existing candidates
mCandidates[level][component] = candidates;
// Stomp existing defaults
mDefaultCandidates[level][component] =
std::make_pair("192.168.0.1", port);
session.UpdateDefaultCandidate(
mDefaultCandidates[level][RTP].first,
mDefaultCandidates[level][RTP].second,
// Will be empty string if not present, which is how we indicate
// that there is no default for RTCP
mDefaultCandidates[level][RTCP].first,
mDefaultCandidates[level][RTCP].second,
level);
}
void FinishGathering(JsepSession& session) const
{
// Copy so we can be terse and use []
for (auto levelAndCandidates : mDefaultCandidates) {
ASSERT_EQ(1U, levelAndCandidates.second.count(RTP));
// do a final UpdateDefaultCandidate here in case candidates were
// cleared during renegotiation.
session.UpdateDefaultCandidate(
levelAndCandidates.second[RTP].first,
levelAndCandidates.second[RTP].second,
// Will be empty string if not present, which is how we indicate
// that there is no default for RTCP
levelAndCandidates.second[RTCP].first,
levelAndCandidates.second[RTCP].second,
levelAndCandidates.first);
session.EndOfLocalCandidates(levelAndCandidates.first);
}
}
void Trickle(JsepSession& session)
{
for (const auto& levelMidAndCandidate : mCandidatesToTrickle) {
Level level;
Mid mid;
Candidate candidate;
Tie(level, mid, candidate) = levelMidAndCandidate;
session.AddRemoteIceCandidate(candidate, mid, level);
}
mCandidatesToTrickle.clear();
}
void CheckRtpCandidates(bool expectRtpCandidates,
const SdpMediaSection& msection,
size_t transportLevel,
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[transportLevel][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[transportLevel][RTP][i], candidates[i])
<< context << " (level " << msection.GetLevel() << ")";
}
}
}
void CheckRtcpCandidates(bool expectRtcpCandidates,
const SdpMediaSection& msection,
size_t transportLevel,
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[transportLevel][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[transportLevel][RTCP][i],
candidates[i + kNumCandidatesPerComponent])
<< context << " (level " << msection.GetLevel() << ")";
}
}
}
void CheckDefaultRtpCandidate(bool expectDefault,
const SdpMediaSection& msection,
size_t transportLevel,
const std::string& context) const
{
if (expectDefault) {
// Copy so we can be terse and use []
auto defaultCandidates = mDefaultCandidates;
ASSERT_EQ(defaultCandidates[transportLevel][RTP].first,
msection.GetConnection().GetAddress())
<< context << " (level " << msection.GetLevel() << ")";
ASSERT_EQ(defaultCandidates[transportLevel][RTP].second,
msection.GetPort())
<< context << " (level " << msection.GetLevel() << ")";
} else {
ASSERT_EQ("0.0.0.0", msection.GetConnection().GetAddress())
<< context << " (level " << msection.GetLevel() << ")";
ASSERT_EQ(9U, msection.GetPort())
<< context << " (level " << msection.GetLevel() << ")";
}
}
void CheckDefaultRtcpCandidate(bool expectDefault,
const SdpMediaSection& msection,
size_t transportLevel,
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[transportLevel][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[transportLevel][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 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<Level,
std::map<ComponentType,
std::pair<Address, Port>>> mDefaultCandidates;
std::map<Level,
std::map<ComponentType,
std::vector<Candidate>>> mCandidates;
// Level/mid/candidate tuples that need to be trickled
std::vector<Tuple<Level, Mid, Candidate>> mCandidatesToTrickle;
};
// For streaming parse errors
std::string
GetParseErrors(const SipccSdpParser& parser) const
{
std::stringstream output;
for (auto e = parser.GetParseErrors().begin();
e != parser.GetParseErrors().end();
++e) {
output << e->first << ": " << e->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 CheckPairs(const JsepSession& session, const std::string& context)
{
auto pairs = session.GetNegotiatedTrackPairs();
for (JsepTrackPair& pair : pairs) {
if (types.size() == 1) {
ASSERT_FALSE(pair.mBundleLevel.isSome()) << context;
} else {
ASSERT_TRUE(pair.mBundleLevel.isSome()) << context;
ASSERT_EQ(0U, *pair.mBundleLevel) << context;
}
}
}
void
DisableMsid(std::string* sdp) const {
size_t pos = sdp->find("a=msid-semantic");
ASSERT_NE(std::string::npos, pos);
(*sdp)[pos + 2] = 'X'; // garble, a=Xsid-semantic
}
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
ReplaceInSdp(std::string* sdp,
const char* searchStr,
const char* replaceStr) const
{
if (searchStr[0] == '\0') return;
size_t pos;
while ((pos = sdp->find(searchStr)) != std::string::npos) {
sdp->replace(pos, strlen(searchStr), replaceStr);
}
}
void
ValidateDisabledMSection(const SdpMediaSection* msection)
{
ASSERT_EQ(1U, msection->GetFormats().size());
// Maybe validate that no attributes are present except rtpmap and
// inactive? How?
ASSERT_EQ(SdpDirectionAttribute::kInactive,
msection->GetDirectionAttribute().mValue);
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) {
ASSERT_EQ("5000", msection->GetFormats()[0]);
const SdpSctpmapAttributeList::Sctpmap* sctpmap(msection->FindSctpmap("5000"));
ASSERT_TRUE(sctpmap);
ASSERT_EQ("5000", 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);
}
}
void
DumpTrack(const JsepTrack& track)
{
const JsepTrackNegotiatedDetails* details = track.GetNegotiatedDetails();
std::cerr << " type=" << track.GetMediaType() << std::endl;
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 JsepCodecDescription* 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);
std::cerr << " dtmf(" << (audioCodec->mDtmfEnabled?"yes":"no") << ")";
}
std::cerr << std::endl;
}
}
}
void
DumpTrackPairs(const JsepSessionImpl& session)
{
auto pairs = mSessionAns.GetNegotiatedTrackPairs();
for (auto i = pairs.begin(); i != pairs.end(); ++i) {
std::cerr << "Track pair " << i->mLevel << std::endl;
if (i->mSending) {
std::cerr << "Sending-->" << std::endl;
DumpTrack(*i->mSending);
}
if (i->mReceiving) {
std::cerr << "Receiving-->" << std::endl;
DumpTrack(*i->mReceiving);
}
}
}
UniquePtr<Sdp>
Parse(const std::string& sdp) const
{
SipccSdpParser parser;
UniquePtr<Sdp> parsed = parser.Parse(sdp);
EXPECT_TRUE(parsed.get()) << "Should have valid SDP" << std::endl
<< "Errors were: " << GetParseErrors(parser);
return parsed;
}
JsepSessionImpl mSessionOff;
CandidateSet mOffCandidates;
JsepSessionImpl mSessionAns;
CandidateSet mAnsCandidates;
std::vector<SdpMediaSection::MediaType> types;
std::vector<std::pair<std::string, uint16_t>> mGatheredCandidates;
private:
void
ValidateTransport(TransportData& source, const std::string& sdp_str)
{
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) {
ASSERT_EQ(SdpMediaSection::kDtlsSctp, msection.GetProtocol());
} else {
ASSERT_EQ(SdpMediaSection::kUdpTlsRtpSavpf, msection.GetProtocol());
}
if (msection.GetPort() == 0) {
ValidateDisabledMSection(&msection);
continue;
}
const SdpAttributeList& attrs = msection.GetAttributeList();
ASSERT_EQ(source.mIceUfrag, attrs.GetIceUfrag());
ASSERT_EQ(source.mIcePwd, attrs.GetIcePwd());
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());
}
}
TransportData mOffererTransport;
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, RenegotiationNoChange)
{
AddTracks(mSessionOff);
std::string offer = CreateOffer();
SetLocalOffer(offer);
SetRemoteOffer(offer);
auto added = mSessionAns.GetRemoteTracksAdded();
auto removed = mSessionAns.GetRemoteTracksRemoved();
ASSERT_EQ(types.size(), added.size());
ASSERT_EQ(0U, removed.size());
AddTracks(mSessionAns);
std::string answer = CreateAnswer();
SetLocalAnswer(answer);
SetRemoteAnswer(answer);
added = mSessionOff.GetRemoteTracksAdded();
removed = mSessionOff.GetRemoteTracksRemoved();
ASSERT_EQ(types.size(), added.size());
ASSERT_EQ(0U, removed.size());
auto offererPairs = GetTrackPairsByLevel(mSessionOff);
auto answererPairs = GetTrackPairsByLevel(mSessionAns);
std::string reoffer = CreateOffer();
SetLocalOffer(reoffer);
SetRemoteOffer(reoffer);
added = mSessionAns.GetRemoteTracksAdded();
removed = mSessionAns.GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
ASSERT_EQ(0U, removed.size());
std::string reanswer = CreateAnswer();
SetLocalAnswer(reanswer);
SetRemoteAnswer(reanswer);
added = mSessionOff.GetRemoteTracksAdded();
removed = mSessionOff.GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
ASSERT_EQ(0U, removed.size());
auto newOffererPairs = GetTrackPairsByLevel(mSessionOff);
auto newAnswererPairs = GetTrackPairsByLevel(mSessionAns);
ASSERT_EQ(offererPairs.size(), newOffererPairs.size());
for (size_t i = 0; i < offererPairs.size(); ++i) {
ASSERT_TRUE(Equals(offererPairs[i], newOffererPairs[i]));
}
ASSERT_EQ(answererPairs.size(), newAnswererPairs.size());
for (size_t i = 0; i < answererPairs.size(); ++i) {
ASSERT_TRUE(Equals(answererPairs[i], newAnswererPairs[i]));
}
}
TEST_P(JsepSessionTest, RenegotiationOffererAddsTrack)
{
AddTracks(mSessionOff);
AddTracks(mSessionAns);
OfferAnswer();
auto offererPairs = GetTrackPairsByLevel(mSessionOff);
auto answererPairs = GetTrackPairsByLevel(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);
auto added = mSessionAns.GetRemoteTracksAdded();
auto removed = mSessionAns.GetRemoteTracksRemoved();
ASSERT_EQ(2U, added.size());
ASSERT_EQ(0U, removed.size());
ASSERT_EQ(SdpMediaSection::kAudio, added[0]->GetMediaType());
ASSERT_EQ(SdpMediaSection::kVideo, added[1]->GetMediaType());
added = mSessionOff.GetRemoteTracksAdded();
removed = mSessionOff.GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
ASSERT_EQ(0U, removed.size());
auto newOffererPairs = GetTrackPairsByLevel(mSessionOff);
auto newAnswererPairs = GetTrackPairsByLevel(mSessionAns);
ASSERT_EQ(offererPairs.size() + 2, newOffererPairs.size());
for (size_t i = 0; i < offererPairs.size(); ++i) {
ASSERT_TRUE(Equals(offererPairs[i], newOffererPairs[i]));
}
ASSERT_EQ(answererPairs.size() + 2, newAnswererPairs.size());
for (size_t i = 0; i < answererPairs.size(); ++i) {
ASSERT_TRUE(Equals(answererPairs[i], newAnswererPairs[i]));
}
}
TEST_P(JsepSessionTest, RenegotiationAnswererAddsTrack)
{
AddTracks(mSessionOff);
AddTracks(mSessionAns);
OfferAnswer();
auto offererPairs = GetTrackPairsByLevel(mSessionOff);
auto answererPairs = GetTrackPairsByLevel(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
JsepOfferOptions options;
options.mOfferToReceiveAudio =
Some(GetTrackCount(mSessionOff, SdpMediaSection::kAudio) + 1);
options.mOfferToReceiveVideo =
Some(GetTrackCount(mSessionOff, SdpMediaSection::kVideo) + 1);
std::string offer = CreateOffer(Some(options));
SetLocalOffer(offer, CHECK_SUCCESS);
SetRemoteOffer(offer, CHECK_SUCCESS);
std::string answer = CreateAnswer();
SetLocalAnswer(answer, CHECK_SUCCESS);
SetRemoteAnswer(answer, CHECK_SUCCESS);
auto added = mSessionAns.GetRemoteTracksAdded();
auto removed = mSessionAns.GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
ASSERT_EQ(0U, removed.size());
added = mSessionOff.GetRemoteTracksAdded();
removed = mSessionOff.GetRemoteTracksRemoved();
ASSERT_EQ(2U, added.size());
ASSERT_EQ(0U, removed.size());
ASSERT_EQ(SdpMediaSection::kAudio, added[0]->GetMediaType());
ASSERT_EQ(SdpMediaSection::kVideo, added[1]->GetMediaType());
auto newOffererPairs = GetTrackPairsByLevel(mSessionOff);
auto newAnswererPairs = GetTrackPairsByLevel(mSessionAns);
ASSERT_EQ(offererPairs.size() + 2, newOffererPairs.size());
for (size_t i = 0; i < offererPairs.size(); ++i) {
ASSERT_TRUE(Equals(offererPairs[i], newOffererPairs[i]));
}
ASSERT_EQ(answererPairs.size() + 2, newAnswererPairs.size());
for (size_t i = 0; i < answererPairs.size(); ++i) {
ASSERT_TRUE(Equals(answererPairs[i], newAnswererPairs[i]));
}
}
TEST_P(JsepSessionTest, RenegotiationBothAddTrack)
{
AddTracks(mSessionOff);
AddTracks(mSessionAns);
OfferAnswer();
auto offererPairs = GetTrackPairsByLevel(mSessionOff);
auto answererPairs = GetTrackPairsByLevel(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);
auto added = mSessionAns.GetRemoteTracksAdded();
auto removed = mSessionAns.GetRemoteTracksRemoved();
ASSERT_EQ(2U, added.size());
ASSERT_EQ(0U, removed.size());
ASSERT_EQ(SdpMediaSection::kAudio, added[0]->GetMediaType());
ASSERT_EQ(SdpMediaSection::kVideo, added[1]->GetMediaType());
added = mSessionOff.GetRemoteTracksAdded();
removed = mSessionOff.GetRemoteTracksRemoved();
ASSERT_EQ(2U, added.size());
ASSERT_EQ(0U, removed.size());
ASSERT_EQ(SdpMediaSection::kAudio, added[0]->GetMediaType());
ASSERT_EQ(SdpMediaSection::kVideo, added[1]->GetMediaType());
auto newOffererPairs = GetTrackPairsByLevel(mSessionOff);
auto newAnswererPairs = GetTrackPairsByLevel(mSessionAns);
ASSERT_EQ(offererPairs.size() + 2, newOffererPairs.size());
for (size_t i = 0; i < offererPairs.size(); ++i) {
ASSERT_TRUE(Equals(offererPairs[i], newOffererPairs[i]));
}
ASSERT_EQ(answererPairs.size() + 2, newAnswererPairs.size());
for (size_t i = 0; i < answererPairs.size(); ++i) {
ASSERT_TRUE(Equals(answererPairs[i], newAnswererPairs[i]));
}
}
TEST_P(JsepSessionTest, RenegotiationBothAddTracksToExistingStream)
{
AddTracks(mSessionOff);
AddTracks(mSessionAns);
if (GetParam() == "datachannel") {
return;
}
OfferAnswer();
auto oHasStream = HasMediaStream(mSessionOff.GetLocalTracks());
auto aHasStream = HasMediaStream(mSessionAns.GetLocalTracks());
ASSERT_EQ(oHasStream, GetLocalUniqueStreamIds(mSessionOff).size());
ASSERT_EQ(aHasStream, GetLocalUniqueStreamIds(mSessionAns).size());
ASSERT_EQ(aHasStream, GetRemoteUniqueStreamIds(mSessionOff).size());
ASSERT_EQ(oHasStream, GetRemoteUniqueStreamIds(mSessionAns).size());
auto firstOffId = GetFirstLocalStreamId(mSessionOff);
auto firstAnsId = GetFirstLocalStreamId(mSessionAns);
auto offererPairs = GetTrackPairsByLevel(mSessionOff);
auto answererPairs = GetTrackPairsByLevel(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(mSessionOff.GetLocalTracks());
aHasStream = HasMediaStream(mSessionAns.GetLocalTracks());
ASSERT_EQ(oHasStream, GetLocalUniqueStreamIds(mSessionOff).size());
ASSERT_EQ(aHasStream, GetLocalUniqueStreamIds(mSessionAns).size());
ASSERT_EQ(aHasStream, GetRemoteUniqueStreamIds(mSessionOff).size());
ASSERT_EQ(oHasStream, GetRemoteUniqueStreamIds(mSessionAns).size());
if (oHasStream) {
ASSERT_STREQ(firstOffId.c_str(),
GetFirstLocalStreamId(mSessionOff).c_str());
}
if (aHasStream) {
ASSERT_STREQ(firstAnsId.c_str(),
GetFirstLocalStreamId(mSessionAns).c_str());
}
}
TEST_P(JsepSessionTest, RenegotiationOffererRemovesTrack)
{
AddTracks(mSessionOff);
AddTracks(mSessionAns);
if (types.front() == SdpMediaSection::kApplication) {
return;
}
OfferAnswer();
auto offererPairs = GetTrackPairsByLevel(mSessionOff);
auto answererPairs = GetTrackPairsByLevel(mSessionAns);
RefPtr<JsepTrack> removedTrack = GetTrackOff(0, types.front());
ASSERT_TRUE(removedTrack);
ASSERT_EQ(NS_OK, mSessionOff.RemoveTrack(removedTrack->GetStreamId(),
removedTrack->GetTrackId()));
OfferAnswer(CHECK_SUCCESS);
auto added = mSessionAns.GetRemoteTracksAdded();
auto removed = mSessionAns.GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
ASSERT_EQ(1U, removed.size());
ASSERT_EQ(removedTrack->GetMediaType(), removed[0]->GetMediaType());
ASSERT_EQ(removedTrack->GetStreamId(), removed[0]->GetStreamId());
ASSERT_EQ(removedTrack->GetTrackId(), removed[0]->GetTrackId());
added = mSessionOff.GetRemoteTracksAdded();
removed = mSessionOff.GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
ASSERT_EQ(0U, removed.size());
// First m-section should be recvonly
auto offer = GetParsedLocalDescription(mSessionOff);
auto* msection = GetMsection(*offer, types.front(), 0);
ASSERT_TRUE(msection);
ASSERT_TRUE(msection->IsReceiving());
ASSERT_FALSE(msection->IsSending());
// First audio m-section should be sendonly
auto answer = GetParsedLocalDescription(mSessionAns);
msection = GetMsection(*answer, types.front(), 0);
ASSERT_TRUE(msection);
ASSERT_FALSE(msection->IsReceiving());
ASSERT_TRUE(msection->IsSending());
auto newOffererPairs = GetTrackPairsByLevel(mSessionOff);
auto newAnswererPairs = GetTrackPairsByLevel(mSessionAns);
// Will be the same size since we still have a track on one side.
ASSERT_EQ(offererPairs.size(), newOffererPairs.size());
// This should be the only difference.
ASSERT_TRUE(offererPairs[0].mSending);
ASSERT_FALSE(newOffererPairs[0].mSending);
// Remove this difference, let loop below take care of the rest
offererPairs[0].mSending = nullptr;
for (size_t i = 0; i < offererPairs.size(); ++i) {
ASSERT_TRUE(Equals(offererPairs[i], newOffererPairs[i]));
}
// Will be the same size since we still have a track on one side.
ASSERT_EQ(answererPairs.size(), newAnswererPairs.size());
// This should be the only difference.
ASSERT_TRUE(answererPairs[0].mReceiving);
ASSERT_FALSE(newAnswererPairs[0].mReceiving);
// Remove this difference, let loop below take care of the rest
answererPairs[0].mReceiving = nullptr;
for (size_t i = 0; i < answererPairs.size(); ++i) {
ASSERT_TRUE(Equals(answererPairs[i], newAnswererPairs[i]));
}
}
TEST_P(JsepSessionTest, RenegotiationAnswererRemovesTrack)
{
AddTracks(mSessionOff);
AddTracks(mSessionAns);
if (types.front() == SdpMediaSection::kApplication) {
return;
}
OfferAnswer();
auto offererPairs = GetTrackPairsByLevel(mSessionOff);
auto answererPairs = GetTrackPairsByLevel(mSessionAns);
RefPtr<JsepTrack> removedTrack = GetTrackAns(0, types.front());
ASSERT_TRUE(removedTrack);
ASSERT_EQ(NS_OK, mSessionAns.RemoveTrack(removedTrack->GetStreamId(),
removedTrack->GetTrackId()));
OfferAnswer(CHECK_SUCCESS);
auto added = mSessionAns.GetRemoteTracksAdded();
auto removed = mSessionAns.GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
ASSERT_EQ(0U, removed.size());
added = mSessionOff.GetRemoteTracksAdded();
removed = mSessionOff.GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
ASSERT_EQ(1U, removed.size());
ASSERT_EQ(removedTrack->GetMediaType(), removed[0]->GetMediaType());
ASSERT_EQ(removedTrack->GetStreamId(), removed[0]->GetStreamId());
ASSERT_EQ(removedTrack->GetTrackId(), removed[0]->GetTrackId());
// First m-section should be sendrecv
auto offer = GetParsedLocalDescription(mSessionOff);
auto* msection = GetMsection(*offer, types.front(), 0);
ASSERT_TRUE(msection);
ASSERT_TRUE(msection->IsReceiving());
ASSERT_TRUE(msection->IsSending());
// First audio m-section should be recvonly
auto answer = GetParsedLocalDescription(mSessionAns);
msection = GetMsection(*answer, types.front(), 0);
ASSERT_TRUE(msection);
ASSERT_TRUE(msection->IsReceiving());
ASSERT_FALSE(msection->IsSending());
auto newOffererPairs = GetTrackPairsByLevel(mSessionOff);
auto newAnswererPairs = GetTrackPairsByLevel(mSessionAns);
// Will be the same size since we still have a track on one side.
ASSERT_EQ(offererPairs.size(), newOffererPairs.size());
// This should be the only difference.
ASSERT_TRUE(offererPairs[0].mReceiving);
ASSERT_FALSE(newOffererPairs[0].mReceiving);
// Remove this difference, let loop below take care of the rest
offererPairs[0].mReceiving = nullptr;
for (size_t i = 0; i < offererPairs.size(); ++i) {
ASSERT_TRUE(Equals(offererPairs[i], newOffererPairs[i]));
}
// Will be the same size since we still have a track on one side.
ASSERT_EQ(answererPairs.size(), newAnswererPairs.size());
// This should be the only difference.
ASSERT_TRUE(answererPairs[0].mSending);
ASSERT_FALSE(newAnswererPairs[0].mSending);
// Remove this difference, let loop below take care of the rest
answererPairs[0].mSending = nullptr;
for (size_t i = 0; i < answererPairs.size(); ++i) {
ASSERT_TRUE(Equals(answererPairs[i], newAnswererPairs[i]));
}
}
TEST_P(JsepSessionTest, RenegotiationBothRemoveTrack)
{
AddTracks(mSessionOff);
AddTracks(mSessionAns);
if (types.front() == SdpMediaSection::kApplication) {
return;
}
OfferAnswer();
auto offererPairs = GetTrackPairsByLevel(mSessionOff);
auto answererPairs = GetTrackPairsByLevel(mSessionAns);
RefPtr<JsepTrack> removedTrackAnswer = GetTrackAns(0, types.front());
ASSERT_TRUE(removedTrackAnswer);
ASSERT_EQ(NS_OK, mSessionAns.RemoveTrack(removedTrackAnswer->GetStreamId(),
removedTrackAnswer->GetTrackId()));
RefPtr<JsepTrack> removedTrackOffer = GetTrackOff(0, types.front());
ASSERT_TRUE(removedTrackOffer);
ASSERT_EQ(NS_OK, mSessionOff.RemoveTrack(removedTrackOffer->GetStreamId(),
removedTrackOffer->GetTrackId()));
OfferAnswer(CHECK_SUCCESS);
auto added = mSessionAns.GetRemoteTracksAdded();
auto removed = mSessionAns.GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
ASSERT_EQ(1U, removed.size());
ASSERT_EQ(removedTrackOffer->GetMediaType(), removed[0]->GetMediaType());
ASSERT_EQ(removedTrackOffer->GetStreamId(), removed[0]->GetStreamId());
ASSERT_EQ(removedTrackOffer->GetTrackId(), removed[0]->GetTrackId());
added = mSessionOff.GetRemoteTracksAdded();
removed = mSessionOff.GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
ASSERT_EQ(1U, removed.size());
ASSERT_EQ(removedTrackAnswer->GetMediaType(), removed[0]->GetMediaType());
ASSERT_EQ(removedTrackAnswer->GetStreamId(), removed[0]->GetStreamId());
ASSERT_EQ(removedTrackAnswer->GetTrackId(), removed[0]->GetTrackId());
// First m-section should be recvonly
auto offer = GetParsedLocalDescription(mSessionOff);
auto* msection = GetMsection(*offer, types.front(), 0);
ASSERT_TRUE(msection);
ASSERT_TRUE(msection->IsReceiving());
ASSERT_FALSE(msection->IsSending());
// First m-section should be inactive, and rejected
auto answer = GetParsedLocalDescription(mSessionAns);
msection = GetMsection(*answer, types.front(), 0);
ASSERT_TRUE(msection);
ASSERT_FALSE(msection->IsReceiving());
ASSERT_FALSE(msection->IsSending());
ASSERT_FALSE(msection->GetPort());
auto newOffererPairs = GetTrackPairsByLevel(mSessionOff);
auto newAnswererPairs = GetTrackPairsByLevel(mSessionAns);
ASSERT_EQ(offererPairs.size(), newOffererPairs.size() + 1);
for (size_t i = 0; i < newOffererPairs.size(); ++i) {
JsepTrackPair oldPair(offererPairs[i + 1]);
JsepTrackPair newPair(newOffererPairs[i]);
ASSERT_EQ(oldPair.mLevel, newPair.mLevel);
ASSERT_EQ(oldPair.mSending.get(), newPair.mSending.get());
ASSERT_EQ(oldPair.mReceiving.get(), newPair.mReceiving.get());
ASSERT_TRUE(oldPair.mBundleLevel.isSome());
ASSERT_TRUE(newPair.mBundleLevel.isSome());
ASSERT_EQ(0U, *oldPair.mBundleLevel);
ASSERT_EQ(1U, *newPair.mBundleLevel);
}
ASSERT_EQ(answererPairs.size(), newAnswererPairs.size() + 1);
for (size_t i = 0; i < newAnswererPairs.size(); ++i) {
JsepTrackPair oldPair(answererPairs[i + 1]);
JsepTrackPair newPair(newAnswererPairs[i]);
ASSERT_EQ(oldPair.mLevel, newPair.mLevel);
ASSERT_EQ(oldPair.mSending.get(), newPair.mSending.get());
ASSERT_EQ(oldPair.mReceiving.get(), newPair.mReceiving.get());
ASSERT_TRUE(oldPair.mBundleLevel.isSome());
ASSERT_TRUE(newPair.mBundleLevel.isSome());
ASSERT_EQ(0U, *oldPair.mBundleLevel);
ASSERT_EQ(1U, *newPair.mBundleLevel);
}
}
TEST_P(JsepSessionTest, RenegotiationBothRemoveThenAddTrack)
{
AddTracks(mSessionOff);
AddTracks(mSessionAns);
if (types.front() == SdpMediaSection::kApplication) {
return;
}
SdpMediaSection::MediaType removedType = types.front();
OfferAnswer();
RefPtr<JsepTrack> removedTrackAnswer = GetTrackAns(0, removedType);
ASSERT_TRUE(removedTrackAnswer);
ASSERT_EQ(NS_OK, mSessionAns.RemoveTrack(removedTrackAnswer->GetStreamId(),
removedTrackAnswer->GetTrackId()));
RefPtr<JsepTrack> removedTrackOffer = GetTrackOff(0, removedType);
ASSERT_TRUE(removedTrackOffer);
ASSERT_EQ(NS_OK, mSessionOff.RemoveTrack(removedTrackOffer->GetStreamId(),
removedTrackOffer->GetTrackId()));
OfferAnswer(CHECK_SUCCESS);
auto offererPairs = GetTrackPairsByLevel(mSessionOff);
auto answererPairs = GetTrackPairsByLevel(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 added = mSessionAns.GetRemoteTracksAdded();
auto removed = mSessionAns.GetRemoteTracksRemoved();
ASSERT_EQ(1U, added.size());
ASSERT_EQ(0U, removed.size());
ASSERT_EQ(removedType, added[0]->GetMediaType());
added = mSessionOff.GetRemoteTracksAdded();
removed = mSessionOff.GetRemoteTracksRemoved();
ASSERT_EQ(1U, added.size());
ASSERT_EQ(0U, removed.size());
ASSERT_EQ(removedType, added[0]->GetMediaType());
auto newOffererPairs = GetTrackPairsByLevel(mSessionOff);
auto newAnswererPairs = GetTrackPairsByLevel(mSessionAns);
ASSERT_EQ(offererPairs.size() + 1, newOffererPairs.size());
ASSERT_EQ(answererPairs.size() + 1, newAnswererPairs.size());
// Ensure that the m-section was re-used; no gaps
for (size_t i = 0; i < newOffererPairs.size(); ++i) {
ASSERT_EQ(i, newOffererPairs[i].mLevel);
}
for (size_t i = 0; i < newAnswererPairs.size(); ++i) {
ASSERT_EQ(i, newAnswererPairs[i].mLevel);
}
}
TEST_P(JsepSessionTest, RenegotiationBothRemoveTrackDifferentMsection)
{
AddTracks(mSessionOff);
AddTracks(mSessionAns);
if (types.front() == SdpMediaSection::kApplication) {
return;
}
if (types.size() < 2 || types[0] != types[1]) {
// For simplicity, just run in cases where we have two of the same type
return;
}
OfferAnswer();
auto offererPairs = GetTrackPairsByLevel(mSessionOff);
auto answererPairs = GetTrackPairsByLevel(mSessionAns);
RefPtr<JsepTrack> removedTrackAnswer = GetTrackAns(0, types.front());
ASSERT_TRUE(removedTrackAnswer);
ASSERT_EQ(NS_OK, mSessionAns.RemoveTrack(removedTrackAnswer->GetStreamId(),
removedTrackAnswer->GetTrackId()));
// Second instance of the same type
RefPtr<JsepTrack> removedTrackOffer = GetTrackOff(1, types.front());
ASSERT_TRUE(removedTrackOffer);
ASSERT_EQ(NS_OK, mSessionOff.RemoveTrack(removedTrackOffer->GetStreamId(),
removedTrackOffer->GetTrackId()));
OfferAnswer(CHECK_SUCCESS);
auto added = mSessionAns.GetRemoteTracksAdded();
auto removed = mSessionAns.GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
ASSERT_EQ(1U, removed.size());
ASSERT_EQ(removedTrackOffer->GetMediaType(), removed[0]->GetMediaType());
ASSERT_EQ(removedTrackOffer->GetStreamId(), removed[0]->GetStreamId());
ASSERT_EQ(removedTrackOffer->GetTrackId(), removed[0]->GetTrackId());
added = mSessionOff.GetRemoteTracksAdded();
removed = mSessionOff.GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
ASSERT_EQ(1U, removed.size());
ASSERT_EQ(removedTrackAnswer->GetMediaType(), removed[0]->GetMediaType());
ASSERT_EQ(removedTrackAnswer->GetStreamId(), removed[0]->GetStreamId());
ASSERT_EQ(removedTrackAnswer->GetTrackId(), removed[0]->GetTrackId());
// Second m-section should be recvonly
auto offer = GetParsedLocalDescription(mSessionOff);
auto* msection = GetMsection(*offer, types.front(), 1);
ASSERT_TRUE(msection);
ASSERT_TRUE(msection->IsReceiving());
ASSERT_FALSE(msection->IsSending());
// First m-section should be recvonly
auto answer = GetParsedLocalDescription(mSessionAns);
msection = GetMsection(*answer, types.front(), 0);
ASSERT_TRUE(msection);
ASSERT_TRUE(msection->IsReceiving());
ASSERT_FALSE(msection->IsSending());
auto newOffererPairs = GetTrackPairsByLevel(mSessionOff);
auto newAnswererPairs = GetTrackPairsByLevel(mSessionAns);
ASSERT_EQ(offererPairs.size(), newOffererPairs.size());
// This should be the only difference.
ASSERT_TRUE(offererPairs[0].mReceiving);
ASSERT_FALSE(newOffererPairs[0].mReceiving);
// Remove this difference, let loop below take care of the rest
offererPairs[0].mReceiving = nullptr;
// This should be the only difference.
ASSERT_TRUE(offererPairs[1].mSending);
ASSERT_FALSE(newOffererPairs[1].mSending);
// Remove this difference, let loop below take care of the rest
offererPairs[1].mSending = nullptr;
for (size_t i = 0; i < newOffererPairs.size(); ++i) {
ASSERT_TRUE(Equals(offererPairs[i], newOffererPairs[i]));
}
ASSERT_EQ(answererPairs.size(), newAnswererPairs.size());
// This should be the only difference.
ASSERT_TRUE(answererPairs[0].mSending);
ASSERT_FALSE(newAnswererPairs[0].mSending);
// Remove this difference, let loop below take care of the rest
answererPairs[0].mSending = nullptr;
// This should be the only difference.
ASSERT_TRUE(answererPairs[1].mReceiving);
ASSERT_FALSE(newAnswererPairs[1].mReceiving);
// Remove this difference, let loop below take care of the rest
answererPairs[1].mReceiving = nullptr;
for (size_t i = 0; i < newAnswererPairs.size(); ++i) {
ASSERT_TRUE(Equals(answererPairs[i], newAnswererPairs[i]));
}
}
TEST_P(JsepSessionTest, RenegotiationOffererReplacesTrack)
{
AddTracks(mSessionOff);
AddTracks(mSessionAns);
if (types.front() == SdpMediaSection::kApplication) {
return;
}
OfferAnswer();
auto offererPairs = GetTrackPairsByLevel(mSessionOff);
auto answererPairs = GetTrackPairsByLevel(mSessionAns);
RefPtr<JsepTrack> removedTrack = GetTrackOff(0, types.front());
ASSERT_TRUE(removedTrack);
ASSERT_EQ(NS_OK, mSessionOff.RemoveTrack(removedTrack->GetStreamId(),
removedTrack->GetTrackId()));
RefPtr<JsepTrack> addedTrack(
new JsepTrack(types.front(), "newstream", "newtrack"));
ASSERT_EQ(NS_OK, mSessionOff.AddTrack(addedTrack));
OfferAnswer(CHECK_SUCCESS);
auto added = mSessionAns.GetRemoteTracksAdded();
auto removed = mSessionAns.GetRemoteTracksRemoved();
ASSERT_EQ(1U, added.size());
ASSERT_EQ(1U, removed.size());
ASSERT_EQ(removedTrack->GetMediaType(), removed[0]->GetMediaType());
ASSERT_EQ(removedTrack->GetStreamId(), removed[0]->GetStreamId());
ASSERT_EQ(removedTrack->GetTrackId(), removed[0]->GetTrackId());
ASSERT_EQ(addedTrack->GetMediaType(), added[0]->GetMediaType());
ASSERT_EQ(addedTrack->GetStreamId(), added[0]->GetStreamId());
ASSERT_EQ(addedTrack->GetTrackId(), added[0]->GetTrackId());
added = mSessionOff.GetRemoteTracksAdded();
removed = mSessionOff.GetRemoteTracksRemoved();
ASSERT_EQ(0U, added.size());
ASSERT_EQ(0U, removed.size());
// First audio m-section should be sendrecv
auto offer = GetParsedLocalDescription(mSessionOff);
auto* msection = GetMsection(*offer, types.front(), 0);
ASSERT_TRUE(msection);
ASSERT_TRUE(msection->IsReceiving());
ASSERT_TRUE(msection->IsSending());
// First audio m-section should be sendrecv
auto answer = GetParsedLocalDescription(mSessionAns);
msection = GetMsection(*answer, types.front(), 0);
ASSERT_TRUE(msection);
ASSERT_TRUE(msection->IsReceiving());
ASSERT_TRUE(msection->IsSending());
auto newOffererPairs = GetTrackPairsByLevel(mSessionOff);
auto newAnswererPairs = GetTrackPairsByLevel(mSessionAns);
ASSERT_EQ(offererPairs.size(), newOffererPairs.size());
ASSERT_NE(offererPairs[0].mSending->GetStreamId(),
newOffererPairs[0].mSending->GetStreamId());
ASSERT_NE(offererPairs[0].mSending->GetTrackId(),
newOffererPairs[0].mSending->GetTrackId());
// Skip first pair
for (size_t i = 1; i < offererPairs.size(); ++i) {
ASSERT_TRUE(Equals(offererPairs[i], newOffererPairs[i]));
}
ASSERT_EQ(answererPairs.size(), newAnswererPairs.size());
ASSERT_NE(answererPairs[0].mReceiving->GetStreamId(),
newAnswererPairs[0].mReceiving->GetStreamId());
ASSERT_NE(answererPairs[0].mReceiving->GetTrackId(),
newAnswererPairs[0].mReceiving->GetTrackId());
// Skip first pair
for (size_t i = 1; i < newAnswererPairs.size(); ++i) {
ASSERT_TRUE(Equals(answererPairs[i], newAnswererPairs[i]));
}
}
// 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);
auto offererPairs = GetTrackPairsByLevel(mSessionOff);
// Make sure that DisableMsid actually worked, since it is kinda hacky
auto answererPairs = GetTrackPairsByLevel(mSessionAns);
ASSERT_EQ(offererPairs.size(), answererPairs.size());
for (size_t i = 0; i < offererPairs.size(); ++i) {
ASSERT_TRUE(offererPairs[i].mReceiving);
ASSERT_TRUE(answererPairs[i].mSending);
// These should not match since we've monkeyed with the msid
ASSERT_NE(offererPairs[i].mReceiving->GetStreamId(),
answererPairs[i].mSending->GetStreamId());
ASSERT_NE(offererPairs[i].mReceiving->GetTrackId(),
answererPairs[i].mSending->GetTrackId());
}
offer = CreateOffer();
SetLocalOffer(offer);
SetRemoteOffer(offer);
AddTracks(mSessionAns);
answer = CreateAnswer();
SetLocalAnswer(answer);
DisableMsid(&answer);
SetRemoteAnswer(answer, CHECK_SUCCESS);
auto newOffererPairs = mSessionOff.GetNegotiatedTrackPairs();
ASSERT_EQ(offererPairs.size(), newOffererPairs.size());
for (size_t i = 0; i < offererPairs.size(); ++i) {
ASSERT_TRUE(Equals(offererPairs[i], newOffererPairs[i]));
}
}
TEST_P(JsepSessionTest, RenegotiationOffererDisablesTelephoneEvent)
{
AddTracks(mSessionOff);
AddTracks(mSessionAns);
OfferAnswer();
auto offererPairs = GetTrackPairsByLevel(mSessionOff);
// check all the audio tracks to make sure they have 2 codecs (109 and 101),
// and dtmf is enabled on all audio tracks
for (size_t i = 0; i < offererPairs.size(); ++i) {
std::vector<JsepTrack*> tracks;
tracks.push_back(offererPairs[i].mSending.get());
tracks.push_back(offererPairs[i].mReceiving.get());
for (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(2U, encoding.GetCodecs().size());
ASSERT_TRUE(encoding.HasFormat("109"));
ASSERT_TRUE(encoding.HasFormat("101"));
for (JsepCodecDescription* codec: encoding.GetCodecs()) {
ASSERT_TRUE(codec);
// we can cast here because we've already checked for audio track
JsepAudioCodecDescription *audioCodec =
static_cast<JsepAudioCodecDescription*>(codec);
ASSERT_TRUE(audioCodec->mDtmfEnabled);
}
}
}
std::string offer = CreateOffer();
ReplaceInSdp(&offer, " 109 101 ", " 109 ");
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);
AddTracks(mSessionAns);
std::string answer = CreateAnswer();
SetLocalAnswer(answer);
SetRemoteAnswer(answer);
auto newOffererPairs = GetTrackPairsByLevel(mSessionOff);
// check all the audio tracks to make sure they have 1 codec (109),
// and dtmf is disabled on all audio tracks
for (size_t i = 0; i < newOffererPairs.size(); ++i) {
std::vector<JsepTrack*> tracks;
tracks.push_back(newOffererPairs[i].mSending.get());
tracks.push_back(newOffererPairs[i].mReceiving.get());
for (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(1U, encoding.GetCodecs().size());
ASSERT_TRUE(encoding.HasFormat("109"));
// we can cast here because we've already checked for audio track
JsepAudioCodecDescription *audioCodec =
static_cast<JsepAudioCodecDescription*>(encoding.GetCodecs()[0]);
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);
auto offererPairs = GetTrackPairsByLevel(mSessionOff);
offer = CreateOffer();
SetLocalOffer(offer);
SetRemoteOffer(offer);
AddTracks(mSessionAns);
answer = CreateAnswer();
SetLocalAnswer(answer);
SetRemoteAnswer(answer, CHECK_SUCCESS);
auto newOffererPairs = mSessionOff.GetNegotiatedTrackPairs();
ASSERT_EQ(offererPairs.size(), newOffererPairs.size());
for (size_t i = 0; i < offererPairs.size(); ++i) {
ASSERT_EQ(offererPairs[i].mReceiving->GetMediaType(),
newOffererPairs[i].mReceiving->GetMediaType());
ASSERT_EQ(offererPairs[i].mSending, newOffererPairs[i].mSending);
ASSERT_TRUE(Equals(offererPairs[i].mRtpTransport,
newOffererPairs[i].mRtpTransport));
ASSERT_TRUE(Equals(offererPairs[i].mRtcpTransport,
newOffererPairs[i].mRtcpTransport));
if (offererPairs[i].mReceiving->GetMediaType() ==
SdpMediaSection::kApplication) {
ASSERT_EQ(offererPairs[i].mReceiving, newOffererPairs[i].mReceiving);
} else {
// This should be the only difference
ASSERT_NE(offererPairs[i].mReceiving, newOffererPairs[i].mReceiving);
}
}
}
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);
auto offererPairs = GetTrackPairsByLevel(mSessionOff);
offer = CreateOffer();
SetLocalOffer(offer);
SetRemoteOffer(offer);
AddTracks(mSessionAns);
answer = CreateAnswer();
SetLocalAnswer(answer);
DisableMsid(&answer);
SetRemoteAnswer(answer, CHECK_SUCCESS);
auto newOffererPairs = mSessionOff.GetNegotiatedTrackPairs();
ASSERT_EQ(offererPairs.size(), newOffererPairs.size());
for (size_t i = 0; i < offererPairs.size(); ++i) {
ASSERT_EQ(offererPairs[i].mReceiving->GetMediaType(),
newOffererPairs[i].mReceiving->GetMediaType());
ASSERT_EQ(offererPairs[i].mSending, newOffererPairs[i].mSending);
ASSERT_TRUE(Equals(offererPairs[i].mRtpTransport,
newOffererPairs[i].mRtpTransport));
ASSERT_TRUE(Equals(offererPairs[i].mRtcpTransport,
newOffererPairs[i].mRtcpTransport));
if (offererPairs[i].mReceiving->GetMediaType() ==
SdpMediaSection::kApplication) {
ASSERT_EQ(offererPairs[i].mReceiving, newOffererPairs[i].mReceiving);
} else {
// This should be the only difference
ASSERT_NE(offererPairs[i].mReceiving, newOffererPairs[i].mReceiving);
}
}
}
// 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);
auto offererPairs = GetTrackPairsByLevel(mSessionOff);
auto answererPairs = GetTrackPairsByLevel(mSessionAns);
OfferAnswer();
auto newOffererPairs = GetTrackPairsByLevel(mSessionOff);
auto newAnswererPairs = GetTrackPairsByLevel(mSessionAns);
ASSERT_EQ(newOffererPairs.size(), newAnswererPairs.size());
ASSERT_EQ(offererPairs.size(), newOffererPairs.size());
ASSERT_EQ(answererPairs.size(), newAnswererPairs.size());
for (size_t i = 0; i < newOffererPairs.size(); ++i) {
// No bundle initially
ASSERT_FALSE(offererPairs[i].mBundleLevel.isSome());
ASSERT_FALSE(answererPairs[i].mBundleLevel.isSome());
if (i != 0) {
ASSERT_NE(offererPairs[0].mRtpTransport.get(),
offererPairs[i].mRtpTransport.get());
if (offererPairs[0].mRtcpTransport) {
ASSERT_NE(offererPairs[0].mRtcpTransport.get(),
offererPairs[i].mRtcpTransport.get());
}
ASSERT_NE(answererPairs[0].mRtpTransport.get(),
answererPairs[i].mRtpTransport.get());
if (answererPairs[0].mRtcpTransport) {
ASSERT_NE(answererPairs[0].mRtcpTransport.get(),
answererPairs[i].mRtcpTransport.get());
}
}
// Verify that bundle worked after renegotiation
ASSERT_TRUE(newOffererPairs[i].mBundleLevel.isSome());
ASSERT_TRUE(newAnswererPairs[i].mBundleLevel.isSome());
ASSERT_EQ(newOffererPairs[0].mRtpTransport.get(),
newOffererPairs[i].mRtpTransport.get());
ASSERT_EQ(newOffererPairs[0].mRtcpTransport.get(),
newOffererPairs[i].mRtcpTransport.get());
ASSERT_EQ(newAnswererPairs[0].mRtpTransport.get(),
newAnswererPairs[i].mRtpTransport.get());
ASSERT_EQ(newAnswererPairs[0].mRtcpTransport.get(),
newAnswererPairs[i].mRtcpTransport.get());
}
}
TEST_P(JsepSessionTest, RenegotiationOffererDisablesBundleTransport)
{
AddTracks(mSessionOff);
AddTracks(mSessionAns);
if (types.size() < 2) {
return;
}
OfferAnswer();
auto offererPairs = GetTrackPairsByLevel(mSessionOff);
auto answererPairs = GetTrackPairsByLevel(mSessionAns);
std::string reoffer = CreateOffer();
DisableMsection(&reoffer, 0);
SetLocalOffer(reoffer, CHECK_SUCCESS);
SetRemoteOffer(reoffer, CHECK_SUCCESS);
std::string reanswer = CreateAnswer();
SetLocalAnswer(reanswer, CHECK_SUCCESS);
SetRemoteAnswer(reanswer, CHECK_SUCCESS);
auto newOffererPairs = GetTrackPairsByLevel(mSessionOff);
auto newAnswererPairs = GetTrackPairsByLevel(mSessionAns);
ASSERT_EQ(newOffererPairs.size(), newAnswererPairs.size());
ASSERT_EQ(offererPairs.size(), newOffererPairs.size() + 1);
ASSERT_EQ(answererPairs.size(), newAnswererPairs.size() + 1);
for (size_t i = 0; i < newOffererPairs.size(); ++i) {
ASSERT_TRUE(newOffererPairs[i].mBundleLevel.isSome());
ASSERT_TRUE(newAnswererPairs[i].mBundleLevel.isSome());
ASSERT_EQ(1U, *newOffererPairs[i].mBundleLevel);
ASSERT_EQ(1U, *newAnswererPairs[i].mBundleLevel);
ASSERT_EQ(newOffererPairs[0].mRtpTransport.get(),
newOffererPairs[i].mRtpTransport.get());
ASSERT_EQ(newOffererPairs[0].mRtcpTransport.get(),
newOffererPairs[i].mRtcpTransport.get());
ASSERT_EQ(newAnswererPairs[0].mRtpTransport.get(),
newAnswererPairs[i].mRtpTransport.get());
ASSERT_EQ(newAnswererPairs[0].mRtcpTransport.get(),
newAnswererPairs[i].mRtcpTransport.get());
}
ASSERT_NE(newOffererPairs[0].mRtpTransport.get(),
offererPairs[0].mRtpTransport.get());
ASSERT_NE(newAnswererPairs[0].mRtpTransport.get(),
answererPairs[0].mRtpTransport.get());
ASSERT_LE(1U, mSessionOff.GetTransports().size());
ASSERT_LE(1U, mSessionAns.GetTransports().size());
ASSERT_EQ(0U, mSessionOff.GetTransports()[0]->mComponents);
ASSERT_EQ(0U, mSessionAns.GetTransports()[0]->mComponents);
}
TEST_P(JsepSessionTest, RenegotiationAnswererDisablesBundleTransport)
{
AddTracks(mSessionOff);
AddTracks(mSessionAns);
if (types.size() < 2) {
return;
}
OfferAnswer();
auto offererPairs = GetTrackPairsByLevel(mSessionOff);
auto answererPairs = GetTrackPairsByLevel(mSessionAns);
std::string reoffer = CreateOffer();
SetLocalOffer(reoffer, CHECK_SUCCESS);
SetRemoteOffer(reoffer, CHECK_SUCCESS);
std::string reanswer = CreateAnswer();
DisableMsection(&reanswer, 0);
SetLocalAnswer(reanswer, CHECK_SUCCESS);
SetRemoteAnswer(reanswer, CHECK_SUCCESS);
auto newOffererPairs = GetTrackPairsByLevel(mSessionOff);
auto newAnswererPairs = GetTrackPairsByLevel(mSessionAns);
ASSERT_EQ(newOffererPairs.size(), newAnswererPairs.size());
ASSERT_EQ(offererPairs.size(), newOffererPairs.size() + 1);
ASSERT_EQ(answererPairs.size(), newAnswererPairs.size() + 1);
for (size_t i = 0; i < newOffererPairs.size(); ++i) {
ASSERT_TRUE(newOffererPairs[i].mBundleLevel.isSome());
ASSERT_TRUE(newAnswererPairs[i].mBundleLevel.isSome());
ASSERT_EQ(1U, *newOffererPairs[i].mBundleLevel);
ASSERT_EQ(1U, *newAnswererPairs[i].mBundleLevel);
ASSERT_EQ(newOffererPairs[0].mRtpTransport.get(),
newOffererPairs[i].mRtpTransport.get());
ASSERT_EQ(newOffererPairs[0].mRtcpTransport.get(),
newOffererPairs[i].mRtcpTransport.get());
ASSERT_EQ(newAnswererPairs[0].mRtpTransport.get(),
newAnswererPairs[i].mRtpTransport.get());
ASSERT_EQ(newAnswererPairs[0].mRtcpTransport.get(),
newAnswererPairs[i].mRtcpTransport.get());
}
ASSERT_NE(newOffererPairs[0].mRtpTransport.get(),
offererPairs[0].mRtpTransport.get());
ASSERT_NE(newAnswererPairs[0].mRtpTransport.get(),
answererPairs[0].mRtpTransport.get());
}
TEST_P(JsepSessionTest, ParseRejectsBadMediaFormat)
{
if (GetParam() == "datachannel") {
return;
}
AddTracks(mSessionOff);
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();
nsresult rv = mSessionOff.SetLocalDescription(kJsepSdpOffer, sdpString);
ASSERT_EQ(NS_ERROR_INVALID_ARG, rv);
}
TEST_P(JsepSessionTest, FullCallWithCandidates)
{
AddTracks(mSessionOff);
std::string offer = CreateOffer();
SetLocalOffer(offer);
mOffCandidates.Gather(mSessionOff, types);
UniquePtr<Sdp> localOffer(Parse(mSessionOff.GetLocalDescription()));
for (size_t i = 0; i < localOffer->GetMediaSectionCount(); ++i) {
mOffCandidates.CheckRtpCandidates(
true, localOffer->GetMediaSection(i), i,
"Local offer after gathering should have RTP candidates.");
mOffCandidates.CheckDefaultRtpCandidate(
true, localOffer->GetMediaSection(i), i,
"Local offer after gathering should have a default RTP candidate.");
mOffCandidates.CheckRtcpCandidates(
types[i] != SdpMediaSection::kApplication,
localOffer->GetMediaSection(i), i,
"Local offer after gathering should have RTCP candidates "
"(unless m=application)");
mOffCandidates.CheckDefaultRtcpCandidate(
types[i] != SdpMediaSection::kApplication,
localOffer->GetMediaSection(i), i,
"Local offer after gathering should have a default RTCP candidate "
"(unless m=application)");
CheckEndOfCandidates(true, localOffer->GetMediaSection(i),
"Local offer after gathering should have an end-of-candidates.");
}
SetRemoteOffer(offer);
mOffCandidates.Trickle(mSessionAns);
UniquePtr<Sdp> remoteOffer(Parse(mSessionAns.GetRemoteDescription()));
for (size_t i = 0; i < remoteOffer->GetMediaSectionCount(); ++i) {
mOffCandidates.CheckRtpCandidates(
true, remoteOffer->GetMediaSection(i), i,
"Remote offer after trickle should have RTP candidates.");
mOffCandidates.CheckDefaultRtpCandidate(
false, remoteOffer->GetMediaSection(i), i,
"Initial remote offer should not have a default RTP candidate.");
mOffCandidates.CheckRtcpCandidates(
types[i] != SdpMediaSection::kApplication,
remoteOffer->GetMediaSection(i), i,
"Remote offer after trickle should have RTCP candidates "
"(unless m=application)");
mOffCandidates.CheckDefaultRtcpCandidate(
false, remoteOffer->GetMediaSection(i), i,
"Initial remote offer should not have a default RTCP candidate.");
CheckEndOfCandidates(false, remoteOffer->GetMediaSection(i),
"Initial remote offer should not 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, types);
UniquePtr<Sdp> localAnswer(Parse(mSessionAns.GetLocalDescription()));
for (size_t i = 0; i < localAnswer->GetMediaSectionCount(); ++i) {
mAnsCandidates.CheckRtpCandidates(
i == 0, localAnswer->GetMediaSection(i), i,
"Local answer after gathering should have RTP candidates on level 0.");
mAnsCandidates.CheckDefaultRtpCandidate(
true, localAnswer->GetMediaSection(i), 0,
"Local answer after gathering should have a default RTP candidate "
"on all levels that matches transport level 0.");
mAnsCandidates.CheckRtcpCandidates(
false, localAnswer->GetMediaSection(i), i,
"Local answer after gathering should not have RTCP candidates "
"(because we're answering with rtcp-mux)");
mAnsCandidates.CheckDefaultRtcpCandidate(
false, localAnswer->GetMediaSection(i), i,
"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()));
for (size_t i = 0; i < remoteAnswer->GetMediaSectionCount(); ++i) {
mAnsCandidates.CheckRtpCandidates(
i == 0, remoteAnswer->GetMediaSection(i), i,
"Remote answer after trickle should have RTP candidates on level 0.");
mAnsCandidates.CheckDefaultRtpCandidate(
false, remoteAnswer->GetMediaSection(i), i,
"Remote answer after trickle should not have a default RTP candidate.");
mAnsCandidates.CheckRtcpCandidates(
false, remoteAnswer->GetMediaSection(i), i,
"Remote answer after trickle should not have RTCP candidates "
"(because we're answering with rtcp-mux)");
mAnsCandidates.CheckDefaultRtcpCandidate(
false, remoteAnswer->GetMediaSection(i), i,
"Remote answer after trickle should not have a default RTCP "
"candidate.");
CheckEndOfCandidates(false, remoteAnswer->GetMediaSection(i),
"Remote answer after trickle should not have an end-of-candidates.");
}
}
TEST_P(JsepSessionTest, RenegotiationWithCandidates)
{
AddTracks(mSessionOff);
std::string offer = CreateOffer();
SetLocalOffer(offer);
mOffCandidates.Gather(mSessionOff, types);
SetRemoteOffer(offer);
mOffCandidates.Trickle(mSessionAns);
AddTracks(mSessionAns);
std::string answer = CreateAnswer();
SetLocalAnswer(answer);
mAnsCandidates.Gather(mSessionAns, types);
SetRemoteAnswer(answer);
mAnsCandidates.Trickle(mSessionOff);
offer = CreateOffer();
SetLocalOffer(offer);
UniquePtr<Sdp> parsedOffer(Parse(offer));
for (size_t i = 0; i < parsedOffer->GetMediaSectionCount(); ++i) {
mOffCandidates.CheckRtpCandidates(
i == 0, parsedOffer->GetMediaSection(i), i,
"Local reoffer before gathering should have RTP candidates on level 0"
" only.");
mOffCandidates.CheckDefaultRtpCandidate(
i == 0, parsedOffer->GetMediaSection(i), 0,
"Local reoffer before gathering should have a default RTP candidate "
"on level 0 only.");
mOffCandidates.CheckRtcpCandidates(
false, parsedOffer->GetMediaSection(i), i,
"Local reoffer before gathering should not have RTCP candidates.");
mOffCandidates.CheckDefaultRtcpCandidate(
false, parsedOffer->GetMediaSection(i), i,
"Local reoffer before gathering should not have a default RTCP "
"candidate.");
CheckEndOfCandidates(false, parsedOffer->GetMediaSection(i),
"Local reoffer before gathering should not have an end-of-candidates.");
}
// mSessionAns should generate a reoffer that is similar
std::string otherOffer;
JsepOfferOptions defaultOptions;
nsresult rv = mSessionAns.CreateOffer(defaultOptions, &otherOffer);
ASSERT_EQ(NS_OK, rv);
parsedOffer = Parse(otherOffer);
for (size_t i = 0; i < parsedOffer->GetMediaSectionCount(); ++i) {
mAnsCandidates.CheckRtpCandidates(
i == 0, parsedOffer->GetMediaSection(i), i,
"Local reoffer before gathering should have RTP candidates on level 0"
" only. (previous answerer)");
mAnsCandidates.CheckDefaultRtpCandidate(
i == 0, parsedOffer->GetMediaSection(i), 0,
"Local reoffer before gathering should have a default RTP candidate "
"on level 0 only. (previous answerer)");
mAnsCandidates.CheckRtcpCandidates(
false, parsedOffer->GetMediaSection(i), i,
"Local reoffer before gathering should not have RTCP candidates."
" (previous answerer)");
mAnsCandidates.CheckDefaultRtcpCandidate(
false, parsedOffer->GetMediaSection(i), i,
"Local reoffer before gathering should not have a default RTCP "
"candidate. (previous answerer)");
CheckEndOfCandidates(false, parsedOffer->GetMediaSection(i),
"Local reoffer before gathering should not have an end-of-candidates. "
"(previous answerer)");
}
// 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, 0, RTCP);
}
// Since the remaining levels were bundled, PeerConnection will re-gather for
// both RTP and RTCP, in case the answerer rejects bundle.
for (size_t level = 1; level < types.size(); ++level) {
mOffCandidates.Gather(mSessionOff, level, RTP);
if (types[level] != SdpMediaSection::kApplication) {
mOffCandidates.Gather(mSessionOff, level, RTCP);
}
}
mOffCandidates.FinishGathering(mSessionOff);
mOffCandidates.Trickle(mSessionAns);
UniquePtr<Sdp> localOffer(Parse(mSessionOff.GetLocalDescription()));
for (size_t i = 0; i < localOffer->GetMediaSectionCount(); ++i) {
mOffCandidates.CheckRtpCandidates(
true, localOffer->GetMediaSection(i), i,
"Local reoffer after gathering should have RTP candidates.");
mOffCandidates.CheckDefaultRtpCandidate(
true, localOffer->GetMediaSection(i), i,
"Local reoffer after gathering should have a default RTP candidate.");
mOffCandidates.CheckRtcpCandidates(
types[i] != SdpMediaSection::kApplication,
localOffer->GetMediaSection(i), i,
"Local reoffer after gathering should have RTCP candidates "
"(unless m=application)");
mOffCandidates.CheckDefaultRtcpCandidate(
types[i] != SdpMediaSection::kApplication,
localOffer->GetMediaSection(i), i,
"Local reoffer after gathering should have a default RTCP candidate "
"(unless m=application)");
CheckEndOfCandidates(true, localOffer->GetMediaSection(i),
"Local reoffer after gathering should have an end-of-candidates.");
}
UniquePtr<Sdp> remoteOffer(Parse(mSessionAns.GetRemoteDescription()));
for (size_t i = 0; i < remoteOffer->GetMediaSectionCount(); ++i) {
mOffCandidates.CheckRtpCandidates(
true, remoteOffer->GetMediaSection(i), i,
"Remote reoffer after trickle should have RTP candidates.");
mOffCandidates.CheckDefaultRtpCandidate(
i == 0, remoteOffer->GetMediaSection(i), i,
"Remote reoffer should have a default RTP candidate on level 0 "
"(because it was gathered last offer/answer).");
mOffCandidates.CheckRtcpCandidates(
types[i] != SdpMediaSection::kApplication,
remoteOffer->GetMediaSection(i), i,
"Remote reoffer after trickle should have RTCP candidates.");
mOffCandidates.CheckDefaultRtcpCandidate(
false, remoteOffer->GetMediaSection(i), i,
"Remote reoffer should not have a default RTCP candidate.");
CheckEndOfCandidates(false, remoteOffer->GetMediaSection(i),
"Remote reoffer should not 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()));
for (size_t i = 0; i < localAnswer->GetMediaSectionCount(); ++i) {
mAnsCandidates.CheckRtpCandidates(
i == 0, localAnswer->GetMediaSection(i), i,
"Local reanswer after gathering should have RTP candidates on level "
"0.");
mAnsCandidates.CheckDefaultRtpCandidate(
true, localAnswer->GetMediaSection(i), 0,
"Local reanswer after gathering should have a default RTP candidate "
"on all levels that matches transport level 0.");
mAnsCandidates.CheckRtcpCandidates(
false, localAnswer->GetMediaSection(i), i,
"Local reanswer after gathering should not have RTCP candidates "
"(because we're reanswering with rtcp-mux)");
mAnsCandidates.CheckDefaultRtcpCandidate(
false, localAnswer->GetMediaSection(i), i,
"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()));
for (size_t i = 0; i < localAnswer->GetMediaSectionCount(); ++i) {
mAnsCandidates.CheckRtpCandidates(
i == 0, remoteAnswer->GetMediaSection(i), i,
"Remote reanswer after trickle should have RTP candidates on level 0.");
mAnsCandidates.CheckDefaultRtpCandidate(
i == 0, remoteAnswer->GetMediaSection(i), i,
"Remote reanswer should have a default RTP candidate on level 0 "
"(because it was gathered last offer/answer).");
mAnsCandidates.CheckRtcpCandidates(
false, remoteAnswer->GetMediaSection(i), i,
"Remote reanswer after trickle should not have RTCP candidates "
"(because we're reanswering with rtcp-mux)");
mAnsCandidates.CheckDefaultRtcpCandidate(
false, remoteAnswer->GetMediaSection(i), i,
"Remote reanswer after trickle should not have a default RTCP "
"candidate.");
CheckEndOfCandidates(false, remoteAnswer->GetMediaSection(i),
"Remote reanswer after trickle should not have an end-of-candidates.");
}
}
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 RefPtr<JsepTrack>& track : mSessionOff.GetLocalTracks()) {
if (track->GetMediaType() != SdpMediaSection::kApplication) {
ASSERT_FALSE(track->GetActive());
}
}
ASSERT_EQ(types.size(), mSessionOff.GetNegotiatedTrackPairs().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 RefPtr<JsepTrack>& track : mSessionOff.GetLocalTracks()) {
if (track->GetMediaType() != SdpMediaSection::kApplication) {
ASSERT_FALSE(track->GetActive());
}
}
ASSERT_EQ(types.size(), mSessionOff.GetNegotiatedTrackPairs().size());
}
INSTANTIATE_TEST_CASE_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)
{
JsepOfferOptions options;
options.mOfferToReceiveAudio = Some(static_cast<size_t>(1U));
options.mOfferToReceiveVideo = Some(static_cast<size_t>(2U));
options.mDontOfferDataChannel = Some(true);
std::string offer = CreateOffer(Some(options));
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<JsepTrackPair> trackPairs(mSessionOff.GetNegotiatedTrackPairs());
ASSERT_EQ(2U, trackPairs.size());
for (auto pair : trackPairs) {
auto ssrcs = parsedOffer->GetMediaSection(pair.mLevel).GetAttributeList()
.GetSsrc().mSsrcs;
ASSERT_EQ(1U, ssrcs.size());
ASSERT_EQ(pair.mRecvonlySsrc, ssrcs.front().ssrc);
}
}
TEST_F(JsepSessionTest, OfferAnswerSendOnlyLines)
{
AddTracks(mSessionOff, "audio,video,video");
JsepOfferOptions options;
options.mOfferToReceiveAudio = Some(static_cast<size_t>(0U));
options.mOfferToReceiveVideo = Some(static_cast<size_t>(1U));
options.mDontOfferDataChannel = Some(true);
std::string offer = CreateOffer(Some(options));
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)
{
JsepOfferOptions options;
options.mOfferToReceiveAudio = Some<size_t>(1);
OfferAnswer(CHECK_SUCCESS, Some(options));
UniquePtr<Sdp> offer(Parse(mSessionOff.GetLocalDescription()));
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()));
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)
{
JsepOfferOptions options;
options.mOfferToReceiveVideo = Some<size_t>(1);
OfferAnswer(CHECK_SUCCESS, Some(options));
UniquePtr<Sdp> offer(Parse(mSessionOff.GetLocalDescription()));
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()));
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)
{
RefPtr<JsepTrack> msta(
new JsepTrack(SdpMediaSection::kAudio, "offerer_stream", "a1"));
mSessionOff.AddTrack(msta);
RefPtr<JsepTrack> mstv1(
new JsepTrack(SdpMediaSection::kVideo, "offerer_stream", "v1"));
mSessionOff.AddTrack(mstv1);
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);
RefPtr<JsepTrack> msta(
new JsepTrack(SdpMediaSection::kAudio, "offerer_stream", "a1"));
mSessionOff.AddTrack(msta);
RefPtr<JsepTrack> mstv1(
new JsepTrack(SdpMediaSection::kVideo, "offerer_stream", "v2"));
mSessionOff.AddTrack(mstv1);
std::string offer = CreateOffer();
UniquePtr<Sdp> outputSdp(Parse(offer));
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(6U, video_section.GetFormats().size());
ASSERT_EQ("120", video_section.GetFormats()[0]);
ASSERT_EQ("121", video_section.GetFormats()[1]);
ASSERT_EQ("126", video_section.GetFormats()[2]);
ASSERT_EQ("97", video_section.GetFormats()[3]);
ASSERT_EQ("122", video_section.GetFormats()[4]);
ASSERT_EQ("123", video_section.GetFormats()[5]);
// Validate rtpmap
ASSERT_TRUE(video_attrs.HasAttribute(SdpAttribute::kRtpmapAttribute));
auto& rtpmaps = video_attrs.GetRtpmap();
ASSERT_TRUE(rtpmaps.HasEntry("120"));
ASSERT_TRUE(rtpmaps.HasEntry("121"));
ASSERT_TRUE(rtpmaps.HasEntry("126"));
ASSERT_TRUE(rtpmaps.HasEntry("97"));
ASSERT_TRUE(rtpmaps.HasEntry("122"));
ASSERT_TRUE(rtpmaps.HasEntry("123"));
auto& vp8_entry = rtpmaps.GetEntry("120");
auto& vp9_entry = rtpmaps.GetEntry("121");
auto& h264_1_entry = rtpmaps.GetEntry("126");
auto& h264_0_entry = rtpmaps.GetEntry("97");
auto& red_0_entry = rtpmaps.GetEntry("122");
auto& ulpfec_0_entry = rtpmaps.GetEntry("123");
ASSERT_EQ("VP8", vp8_entry.name);
ASSERT_EQ("VP9", vp9_entry.name);
ASSERT_EQ("H264", h264_1_entry.name);
ASSERT_EQ("H264", h264_0_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(5U, 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);
// VP9
const SdpFmtpAttributeList::Parameters* vp9_params =
video_section.FindFmtp("121");
ASSERT_TRUE(vp9_params);
ASSERT_EQ(SdpRtpmapAttributeList::kVP9, vp9_params->codec_type);
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);
// 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);
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 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);
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);
// red
const SdpFmtpAttributeList::Parameters* red_params =
video_section.FindFmtp("122");
ASSERT_TRUE(red_params);
ASSERT_EQ(SdpRtpmapAttributeList::kRed, red_params->codec_type);
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);
RefPtr<JsepTrack> msta(
new JsepTrack(SdpMediaSection::kAudio, "offerer_stream", "a1"));
mSessionOff.AddTrack(msta);
RefPtr<JsepTrack> mstv1(
new JsepTrack(SdpMediaSection::kVideo, "offerer_stream", "v2"));
mSessionOff.AddTrack(mstv1);
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);
// 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, ValidateAnsweredCodecParams)
{
// 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 i = mSessionAns.Codecs().begin(); i != mSessionAns.Codecs().end();
++i) {
auto* codec = *i;
if (codec->mName == "H264") {
JsepVideoCodecDescription* h264 =
static_cast<JsepVideoCodecDescription*>(codec);
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);
RefPtr<JsepTrack> msta(
new JsepTrack(SdpMediaSection::kAudio, "offerer_stream", "a1"));
mSessionOff.AddTrack(msta);
RefPtr<JsepTrack> mstv1(
new JsepTrack(SdpMediaSection::kVideo, "offerer_stream", "v1"));
mSessionOff.AddTrack(mstv1);
std::string offer = CreateOffer();
SetLocalOffer(offer);
SetRemoteOffer(offer);
RefPtr<JsepTrack> msta_ans(
new JsepTrack(SdpMediaSection::kAudio, "answerer_stream", "a1"));
mSessionAns.AddTrack(msta);
RefPtr<JsepTrack> mstv1_ans(
new JsepTrack(SdpMediaSection::kVideo, "answerer_stream", "v1"));
mSessionAns.AddTrack(mstv1);
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());
// TODO(bug 1099351): Once fixed, this stuff will need to be updated.
ASSERT_EQ(1U, video_section.GetFormats().size());
// ASSERT_EQ(3U, video_section.GetFormats().size());
ASSERT_EQ("120", video_section.GetFormats()[0]);
// ASSERT_EQ("121", video_section.GetFormats()[1]);
// ASSERT_EQ("126", video_section.GetFormats()[2]);
// ASSERT_EQ("97", 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"));
// ASSERT_TRUE(rtpmaps.HasEntry("126"));
// ASSERT_TRUE(rtpmaps.HasEntry("97"));
auto& vp8_entry = rtpmaps.GetEntry("120");
//auto& vp9_entry = rtpmaps.GetEntry("121");
// auto& h264_1_entry = rtpmaps.GetEntry("126");
// auto& h264_0_entry = rtpmaps.GetEntry("97");
ASSERT_EQ("VP8", vp8_entry.name);
//ASSERT_EQ("VP9", vp9_entry.name);
// ASSERT_EQ("H264", h264_1_entry.name);
// ASSERT_EQ("H264", h264_0_entry.name);
// Validate fmtps
ASSERT_TRUE(video_attrs.HasAttribute(SdpAttribute::kFmtpAttribute));
auto& fmtps = video_attrs.GetFmtp().mFmtps;
ASSERT_EQ(1U, fmtps.size());
// ASSERT_EQ(3U, 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);
SetLocalAnswer(answer);
SetRemoteAnswer(answer);
auto offerPairs = mSessionOff.GetNegotiatedTrackPairs();
ASSERT_EQ(2U, offerPairs.size());
ASSERT_TRUE(offerPairs[1].mSending);
ASSERT_TRUE(offerPairs[1].mReceiving);
ASSERT_TRUE(offerPairs[1].mSending->GetNegotiatedDetails());
ASSERT_TRUE(offerPairs[1].mReceiving->GetNegotiatedDetails());
ASSERT_EQ(1U,
offerPairs[1].mSending->GetNegotiatedDetails()->GetEncoding(0)
.GetCodecs().size());
ASSERT_EQ(1U,
offerPairs[1].mReceiving->GetNegotiatedDetails()->GetEncoding(0)
.GetCodecs().size());
auto answerPairs = mSessionAns.GetNegotiatedTrackPairs();
ASSERT_EQ(2U, answerPairs.size());
ASSERT_TRUE(answerPairs[1].mSending);
ASSERT_TRUE(answerPairs[1].mReceiving);
ASSERT_TRUE(answerPairs[1].mSending->GetNegotiatedDetails());
ASSERT_TRUE(answerPairs[1].mReceiving->GetNegotiatedDetails());
ASSERT_EQ(1U,
answerPairs[1].mSending->GetNegotiatedDetails()->GetEncoding(0)
.GetCodecs().size());
ASSERT_EQ(1U,
answerPairs[1].mReceiving->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
}
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 pairIndex,
sdp::Direction direction,
size_t encodingIndex,
size_t codecIndex,
const JsepCodecDescription** codecOut)
{
*codecOut = nullptr;
ASSERT_LT(pairIndex, session.GetNegotiatedTrackPairs().size());
JsepTrackPair pair(session.GetNegotiatedTrackPairs().front());
RefPtr<JsepTrack> track(
(direction == sdp::kSend) ? pair.mSending : pair.mReceiving);
ASSERT_TRUE(track);
ASSERT_TRUE(track->GetNegotiatedDetails());
ASSERT_LT(encodingIndex, track->GetNegotiatedDetails()->GetEncodingCount());
ASSERT_LT(codecIndex,
track->GetNegotiatedDetails()->GetEncoding(encodingIndex)
.GetCodecs().size());
*codecOut =
track->GetNegotiatedDetails()->GetEncoding(encodingIndex)
.GetCodecs()[codecIndex];
}
static void
ForceH264(JsepSession& session, uint32_t profileLevelId)
{
for (JsepCodecDescription* codec : session.Codecs()) {
if (codec->mName == "H264") {
JsepVideoCodecDescription* h264 =
static_cast<JsepVideoCodecDescription*>(codec);
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);
const 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));
ASSERT_EQ((uint32_t)0x42e00d, offererVideoSendCodec->mProfileLevelId);
const JsepCodecDescription* offererRecvCodec;
GetCodec(mSessionOff, 0, sdp::kRecv, 0, 0, &offererRecvCodec);
ASSERT_EQ("H264", offererRecvCodec->mName);
const JsepVideoCodecDescription* offererVideoRecvCodec(
static_cast<const JsepVideoCodecDescription*>(offererRecvCodec));
ASSERT_EQ((uint32_t)0x42e00b, offererVideoRecvCodec->mProfileLevelId);
const 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));
ASSERT_EQ((uint32_t)0x42e00b, answererVideoSendCodec->mProfileLevelId);
const JsepCodecDescription* answererRecvCodec;
GetCodec(mSessionAns, 0, sdp::kRecv, 0, 0, &answererRecvCodec);
ASSERT_EQ("H264", answererRecvCodec->mName);
const JsepVideoCodecDescription* answererVideoRecvCodec(
static_cast<const JsepVideoCodecDescription*>(answererRecvCodec));
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(0U, mSessionOff.GetNegotiatedTrackPairs().size());
ASSERT_EQ(0U, mSessionAns.GetNegotiatedTrackPairs().size());
}
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);
const 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));
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);
const 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));
ASSERT_EQ((uint32_t)0x420010, answererVideoSendCodec->mProfileLevelId);
const JsepCodecDescription* answererRecvCodec;
GetCodec(mSessionAns, 0, sdp::kRecv, 0, 0, &answererRecvCodec);
ASSERT_EQ("H264", answererRecvCodec->mName);
const JsepVideoCodecDescription* answererVideoRecvCodec(
static_cast<const JsepVideoCodecDescription*>(answererRecvCodec));
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.
const 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));
ASSERT_EQ((uint32_t)0x42e00b, answererVideoSendCodec->mProfileLevelId);
const JsepCodecDescription* answererRecvCodec;
GetCodec(mSessionAns, 0, sdp::kRecv, 0, 0, &answererRecvCodec);
ASSERT_EQ("H264", answererRecvCodec->mName);
const JsepVideoCodecDescription* answererVideoRecvCodec(
static_cast<const JsepVideoCodecDescription*>(answererRecvCodec));
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.
const 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));
ASSERT_EQ((uint32_t)0x42e00b, answererVideoSendCodec->mProfileLevelId);
const JsepCodecDescription* answererRecvCodec;
GetCodec(mSessionAns, 0, sdp::kRecv, 0, 0, &answererRecvCodec);
ASSERT_EQ("H264", answererRecvCodec->mName);
const JsepVideoCodecDescription* answererVideoRecvCodec(
static_cast<const JsepVideoCodecDescription*>(answererRecvCodec));
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);
const 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));
ASSERT_EQ((uint32_t)0x42e00b, offererVideoSendCodec->mProfileLevelId);
const JsepCodecDescription* offererRecvCodec;
GetCodec(mSessionOff, 0, sdp::kRecv, 0, 0, &offererRecvCodec);
ASSERT_EQ("H264", offererRecvCodec->mName);
const JsepVideoCodecDescription* offererVideoRecvCodec(
static_cast<const JsepVideoCodecDescription*>(offererRecvCodec));
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);
const 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));
ASSERT_EQ((uint32_t)0x42e00b, offererVideoSendCodec->mProfileLevelId);
const JsepCodecDescription* offererRecvCodec;
GetCodec(mSessionOff, 0, sdp::kRecv, 0, 0, &offererRecvCodec);
ASSERT_EQ("H264", offererRecvCodec->mName);
const JsepVideoCodecDescription* offererVideoRecvCodec(
static_cast<const JsepVideoCodecDescription*>(offererRecvCodec));
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());
std::sort(types.begin(), types.end());
switch (types.front()) {
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() == types.front()) {
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(), types.front());
size_t numAccepted = types.size() - numRejected;
ASSERT_EQ(numAccepted, mSessionOff.GetNegotiatedTrackPairs().size());
ASSERT_EQ(numAccepted, mSessionAns.GetNegotiatedTrackPairs().size());
ASSERT_EQ(types.size(), mSessionOff.GetTransports().size());
ASSERT_EQ(types.size(), mSessionOff.GetLocalTracks().size());
ASSERT_EQ(numAccepted, mSessionOff.GetRemoteTracks().size());
ASSERT_EQ(types.size(), mSessionAns.GetTransports().size());
ASSERT_EQ(types.size(), mSessionAns.GetLocalTracks().size());
ASSERT_EQ(types.size(), mSessionAns.GetRemoteTracks().size());
}
TEST_F(JsepSessionTest, CreateOfferNoMlines)
{
JsepOfferOptions options;
std::string offer;
nsresult rv = mSessionOff.CreateOffer(options, &offer);
ASSERT_NE(NS_OK, rv);
ASSERT_NE("", mSessionOff.GetLastError());
}
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, TestExtmap)
{
AddTracks(mSessionOff, "audio");
AddTracks(mSessionAns, "audio");
// ssrc-audio-level will be extmap 1 for both
mSessionOff.AddAudioRtpExtension("foo"); // Default mapping of 2
mSessionOff.AddAudioRtpExtension("bar"); // Default mapping of 3
mSessionAns.AddAudioRtpExtension("bar"); // Default mapping of 2
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(3U, offerExtmap.size());
ASSERT_EQ("urn:ietf:params:rtp-hdrext:ssrc-audio-level",
offerExtmap[0].extensionname);
ASSERT_EQ(1U, offerExtmap[0].entry);
ASSERT_EQ("foo", offerExtmap[1].extensionname);
ASSERT_EQ(2U, offerExtmap[1].entry);
ASSERT_EQ("bar", offerExtmap[2].extensionname);
ASSERT_EQ(3U, offerExtmap[2].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(1U, answerExtmap.size());
// We ensure that the entry for "bar" matches what was in the offer
ASSERT_EQ("bar", answerExtmap[0].extensionname);
ASSERT_EQ(3U, answerExtmap[0].entry);
}
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, mSessionAns.GetRemoteTracks().size());
RefPtr<JsepTrack> track = mSessionAns.GetRemoteTracks()[0];
ASSERT_TRUE(track->GetNegotiatedDetails());
auto* details = track->GetNegotiatedDetails();
for (const JsepCodecDescription* codec :
details->GetEncoding(0).GetCodecs()) {
const JsepVideoCodecDescription* videoCodec =
static_cast<const JsepVideoCodecDescription*>(codec);
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 offerPairs = mSessionOff.GetNegotiatedTrackPairs();
auto answerPairs = mSessionAns.GetNegotiatedTrackPairs();
ASSERT_EQ(3U, offerPairs.size());
ASSERT_EQ(3U, answerPairs.size());
ASSERT_TRUE(offerPairs[0].mReceiving);
ASSERT_TRUE(offerPairs[0].mReceiving->GetNegotiatedDetails());
ASSERT_EQ(0U,
offerPairs[0].mReceiving->GetNegotiatedDetails()->
GetUniquePayloadTypes().size());
ASSERT_TRUE(offerPairs[1].mReceiving);
ASSERT_TRUE(offerPairs[1].mReceiving->GetNegotiatedDetails());
ASSERT_EQ(0U,
offerPairs[1].mReceiving->GetNegotiatedDetails()->
GetUniquePayloadTypes().size());
ASSERT_TRUE(offerPairs[2].mReceiving);
ASSERT_TRUE(offerPairs[2].mReceiving->GetNegotiatedDetails());
ASSERT_NE(0U,
offerPairs[2].mReceiving->GetNegotiatedDetails()->
GetUniquePayloadTypes().size());
ASSERT_TRUE(answerPairs[0].mReceiving);
ASSERT_TRUE(answerPairs[0].mReceiving->GetNegotiatedDetails());
ASSERT_EQ(0U,
answerPairs[0].mReceiving->GetNegotiatedDetails()->
GetUniquePayloadTypes().size());
ASSERT_TRUE(answerPairs[1].mReceiving);
ASSERT_TRUE(answerPairs[1].mReceiving->GetNegotiatedDetails());
ASSERT_EQ(0U,
answerPairs[1].mReceiving->GetNegotiatedDetails()->
GetUniquePayloadTypes().size());
ASSERT_TRUE(answerPairs[2].mReceiving);
ASSERT_TRUE(answerPairs[2].mReceiving->GetNegotiatedDetails());
ASSERT_NE(0U,
answerPairs[2].mReceiving->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);
nsresult rv = mSessionAns.SetRemoteDescription(kJsepSdpOffer, offer);
ASSERT_NE(NS_OK, rv);
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 (JsepCodecDescription* codec : mSessionAns.Codecs()) {
if (codec->mName == "H264") {
codec->mStronglyPreferred = true;
}
}
types.push_back(SdpMediaSection::kVideo);
AddTracks(mSessionOff, "video");
AddTracks(mSessionAns, "video");
OfferAnswer();
const 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();
const 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, PayloadTypeClash)
{
// Disable this so mSessionOff doesn't have a duplicate
SetCodecEnabled(mSessionOff, "PCMU", false);
SetPayloadTypeNumber(mSessionOff, "opus", "0");
SetPayloadTypeNumber(mSessionAns, "PCMU", "0");
types.push_back(SdpMediaSection::kAudio);
AddTracks(mSessionOff, "audio");
AddTracks(mSessionAns, "audio");
OfferAnswer();
const JsepCodecDescription* codec;
GetCodec(mSessionAns, 0, sdp::kSend, 0, 0, &codec);
ASSERT_TRUE(codec);
ASSERT_EQ("opus", codec->mName);
ASSERT_EQ("0", codec->mDefaultPt);
GetCodec(mSessionAns, 0, sdp::kRecv, 0, 0, &codec);
ASSERT_TRUE(codec);
ASSERT_EQ("opus", codec->mName);
ASSERT_EQ("0", codec->mDefaultPt);
// Now, make sure that mSessionAns does not put a=rtpmap:0 PCMU in a reoffer,
// since pt 0 is taken for opus (the answerer still supports PCMU, and will
// reoffer it, but it should choose a new payload type for it)
JsepOfferOptions options;
std::string reoffer;
nsresult rv = mSessionAns.CreateOffer(options, &reoffer);
ASSERT_EQ(NS_OK, rv);
ASSERT_EQ(std::string::npos, reoffer.find("a=rtpmap:0 PCMU")) << reoffer;
}
TEST_P(JsepSessionTest, TestGlareRollback)
{
AddTracks(mSessionOff);
AddTracks(mSessionAns);
JsepOfferOptions options;
std::string offer;
ASSERT_EQ(NS_OK, mSessionAns.CreateOffer(options, &offer));
ASSERT_EQ(NS_OK,
mSessionAns.SetLocalDescription(kJsepSdpOffer, offer));
ASSERT_EQ(kJsepStateHaveLocalOffer, mSessionAns.GetState());
ASSERT_EQ(NS_OK, mSessionOff.CreateOffer(options, &offer));
ASSERT_EQ(NS_OK,
mSessionOff.SetLocalDescription(kJsepSdpOffer, offer));
ASSERT_EQ(kJsepStateHaveLocalOffer, mSessionOff.GetState());
ASSERT_EQ(NS_ERROR_UNEXPECTED,
mSessionAns.SetRemoteDescription(kJsepSdpOffer, offer));
ASSERT_EQ(NS_OK,
mSessionAns.SetLocalDescription(kJsepSdpRollback, ""));
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_EQ(NS_OK,
mSessionAns.SetRemoteDescription(kJsepSdpRollback, ""));
ASSERT_EQ(kJsepStateStable, mSessionAns.GetState());
ASSERT_EQ(types.size(), mSessionAns.GetRemoteTracksRemoved().size());
ASSERT_EQ(NS_OK,
mSessionOff.SetLocalDescription(kJsepSdpRollback, ""));
ASSERT_EQ(kJsepStateStable, mSessionOff.GetState());
OfferAnswer();
}
TEST_P(JsepSessionTest, TestInvalidRollback)
{
AddTracks(mSessionOff);
AddTracks(mSessionAns);
ASSERT_EQ(NS_ERROR_UNEXPECTED,
mSessionOff.SetLocalDescription(kJsepSdpRollback, ""));
ASSERT_EQ(NS_ERROR_UNEXPECTED,
mSessionOff.SetRemoteDescription(kJsepSdpRollback, ""));
std::string offer = CreateOffer();
ASSERT_EQ(NS_ERROR_UNEXPECTED,
mSessionOff.SetLocalDescription(kJsepSdpRollback, ""));
ASSERT_EQ(NS_ERROR_UNEXPECTED,
mSessionOff.SetRemoteDescription(kJsepSdpRollback, ""));
SetLocalOffer(offer);
ASSERT_EQ(NS_ERROR_UNEXPECTED,
mSessionOff.SetRemoteDescription(kJsepSdpRollback, ""));
SetRemoteOffer(offer);
ASSERT_EQ(NS_ERROR_UNEXPECTED,
mSessionAns.SetLocalDescription(kJsepSdpRollback, ""));
std::string answer = CreateAnswer();
ASSERT_EQ(NS_ERROR_UNEXPECTED,
mSessionAns.SetLocalDescription(kJsepSdpRollback, ""));
SetLocalAnswer(answer);
ASSERT_EQ(NS_ERROR_UNEXPECTED,
mSessionAns.SetLocalDescription(kJsepSdpRollback, ""));
ASSERT_EQ(NS_ERROR_UNEXPECTED,
mSessionAns.SetRemoteDescription(kJsepSdpRollback, ""));
SetRemoteAnswer(answer);
ASSERT_EQ(NS_ERROR_UNEXPECTED,
mSessionOff.SetLocalDescription(kJsepSdpRollback, ""));
ASSERT_EQ(NS_ERROR_UNEXPECTED,
mSessionOff.SetRemoteDescription(kJsepSdpRollback, ""));
}
size_t GetActiveTransportCount(const JsepSession& session)
{
auto transports = session.GetTransports();
size_t activeTransportCount = 0;
for (RefPtr<JsepTransport>& transport : transports) {
activeTransportCount += transport->mComponents;
}
return activeTransportCount;
}
TEST_P(JsepSessionTest, TestBalancedBundle)
{
AddTracks(mSessionOff);
AddTracks(mSessionAns);
mSessionOff.SetBundlePolicy(kBundleBalanced);
std::string offer = CreateOffer();
SipccSdpParser parser;
UniquePtr<Sdp> parsedOffer = parser.Parse(offer);
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);
CheckPairs(mSessionOff, "Offerer pairs");
CheckPairs(mSessionAns, "Answerer pairs");
EXPECT_EQ(1U, GetActiveTransportCount(mSessionOff));
EXPECT_EQ(1U, GetActiveTransportCount(mSessionAns));
}
TEST_P(JsepSessionTest, TestMaxBundle)
{
AddTracks(mSessionOff);
AddTracks(mSessionAns);
mSessionOff.SetBundlePolicy(kBundleMaxBundle);
OfferAnswer();
std::string offer = mSessionOff.GetLocalDescription();
SipccSdpParser parser;
UniquePtr<Sdp> parsedOffer = parser.Parse(offer);
ASSERT_TRUE(parsedOffer.get());
ASSERT_FALSE(
parsedOffer->GetMediaSection(0).GetAttributeList().HasAttribute(
SdpAttribute::kBundleOnlyAttribute));
for (size_t i = 1; i < parsedOffer->GetMediaSectionCount(); ++i) {
ASSERT_TRUE(
parsedOffer->GetMediaSection(i).GetAttributeList().HasAttribute(
SdpAttribute::kBundleOnlyAttribute));
}
CheckPairs(mSessionOff, "Offerer pairs");
CheckPairs(mSessionAns, "Answerer pairs");
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_EQ(NS_OK, mSessionOff.CreateOffer(JsepOfferOptions(), &offer));
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());
}
} // namespace mozilla
int
main(int argc, char** argv)
{
// Prevents some log spew
ScopedXPCOM xpcom("jsep_session_unittest");
NSS_NoDB_Init(nullptr);
NSS_SetDomesticPolicy();
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}