mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-25 18:07:31 +09:00
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.
4234 lines
144 KiB
C++
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();
|
|
}
|