Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <iostream>
#include <map>
#include "nss.h"
#include "ssl.h"
#include "mozilla/Preferences.h"
#include "mozilla/RefPtr.h"
#define GTEST_HAS_RTTI 0
#include "gtest/gtest.h"
#include "CodecConfig.h"
#include "PeerConnectionImpl.h"
#include "sdp/SdpMediaSection.h"
#include "sdp/SipccSdpParser.h"
#include "jsep/JsepCodecDescription.h"
#include "jsep/JsepTrack.h"
#include "jsep/JsepSession.h"
#include "jsep/JsepSessionImpl.h"
namespace mozilla {
static std::string kAEqualsCandidate("a=candidate:");
const static size_t kNumCandidatesPerComponent = 3;
class JsepSessionTestBase : public ::testing::Test {
public:
static void SetUpTestCase() {
NSS_NoDB_Init(nullptr);
NSS_SetDomesticPolicy();
}
};
class FakeUuidGenerator : public mozilla::JsepUuidGenerator {
public:
bool Generate(std::string* str) {
std::ostringstream os;
os << "FAKE_UUID_" << ++ctr;
*str = os.str();
return true;
}
mozilla::JsepUuidGenerator* Clone() const {
return new FakeUuidGenerator(*this);
}
private:
static uint64_t ctr;
};
uint64_t FakeUuidGenerator::ctr = 1000;
class JsepSessionTest : public JsepSessionTestBase,
public ::testing::WithParamInterface<std::string> {
public:
JsepSessionTest() : mSdpHelper(&mLastError) {
Preferences::SetCString("media.peerconnection.sdp.parser", "legacy");
Preferences::SetCString("media.peerconnection.sdp.alternate_parse_mode",
"never");
Preferences::SetBool("media.peerconnection.video.use_rtx", true);
Preferences::SetBool("media.navigator.video.use_transport_cc", true);
Preferences::SetBool("media.navigator.video.disable_h264_baseline", false);
mSessionOff =
MakeUnique<JsepSessionImpl>("Offerer", MakeUnique<FakeUuidGenerator>());
mSessionAns = MakeUnique<JsepSessionImpl>("Answerer",
MakeUnique<FakeUuidGenerator>());
EXPECT_EQ(NS_OK, mSessionOff->Init());
EXPECT_EQ(NS_OK, mSessionAns->Init());
std::vector<UniquePtr<JsepCodecDescription>> preferredCodecs;
PeerConnectionImpl::SetupPreferredCodecs(preferredCodecs);
mSessionOff->SetDefaultCodecs(preferredCodecs);
mSessionAns->SetDefaultCodecs(preferredCodecs);
std::vector<PeerConnectionImpl::RtpExtensionHeader> preferredHeaders;
PeerConnectionImpl::SetupPreferredRtpExtensions(preferredHeaders);
for (const auto& header : preferredHeaders) {
mSessionOff->AddRtpExtension(header.mMediaType, header.extensionname,
header.direction);
mSessionAns->AddRtpExtension(header.mMediaType, header.extensionname,
header.direction);
}
mOffererTransport = MakeUnique<TransportData>();
mAnswererTransport = MakeUnique<TransportData>();
AddTransportData(*mSessionOff, *mOffererTransport);
AddTransportData(*mSessionAns, *mAnswererTransport);
mOffCandidates = MakeUnique<CandidateSet>();
mAnsCandidates = MakeUnique<CandidateSet>();
}
static std::vector<JsepTransceiver>& GetTransceivers(JsepSession& aSession) {
return aSession.GetTransceivers();
}
static const std::vector<JsepTransceiver>& GetTransceivers(
const JsepSession& aSession) {
return aSession.GetTransceivers();
}
protected:
struct TransportData {
std::map<nsCString, std::vector<uint8_t>> mFingerprints;
};
void AddDtlsFingerprint(const nsCString& alg, JsepSessionImpl& session,
TransportData& tdata) {
std::vector<uint8_t> fp;
fp.assign((alg == "sha-1") ? 20 : 32,
(session.GetName() == "Offerer") ? 0x4f : 0x41);
session.AddDtlsFingerprint(alg, fp);
tdata.mFingerprints[alg] = fp;
}
void AddTransportData(JsepSessionImpl& session, TransportData& tdata) {
AddDtlsFingerprint("sha-1"_ns, session, tdata);
AddDtlsFingerprint("sha-256"_ns, session, tdata);
}
void CheckTransceiverInvariants(
const std::vector<JsepTransceiver>& oldTransceivers,
const std::vector<JsepTransceiver>& newTransceivers) {
ASSERT_LE(oldTransceivers.size(), newTransceivers.size());
std::set<size_t> levels;
for (const auto& newTransceiver : newTransceivers) {
if (newTransceiver.HasLevel()) {
ASSERT_FALSE(levels.count(newTransceiver.GetLevel()))
<< "Two new transceivers are mapped to level "
<< newTransceiver.GetLevel();
levels.insert(newTransceiver.GetLevel());
}
}
auto last = levels.rbegin();
if (last != levels.rend()) {
ASSERT_LE(*last, levels.size())
<< "Max level observed in transceivers was " << *last
<< ", but there are only " << levels.size()
<< " levels in the "
"transceivers.";
}
for (const auto& oldTransceiver : oldTransceivers) {
if (oldTransceiver.HasLevel()) {
ASSERT_TRUE(levels.count(oldTransceiver.GetLevel()))
<< "Level " << oldTransceiver.GetLevel()
<< " had a transceiver in the old, but not the new (or, "
"perhaps this level had more than one transceiver in the "
"old)";
levels.erase(oldTransceiver.GetLevel());
}
}
}
std::string CreateOffer(const Maybe<JsepOfferOptions>& options = Nothing()) {
std::vector<JsepTransceiver> transceiversBefore =
GetTransceivers(*mSessionOff);
JsepOfferOptions defaultOptions;
const JsepOfferOptions& optionsRef = options ? *options : defaultOptions;
std::string offer;
JsepSession::Result result = mSessionOff->CreateOffer(optionsRef, &offer);
EXPECT_FALSE(result.mError.isSome()) << mSessionOff->GetLastError();
std::cerr << "OFFER: " << offer << std::endl;
ValidateTransport(*mOffererTransport, offer, sdp::kOffer);
if (transceiversBefore.size() != GetTransceivers(*mSessionOff).size()) {
EXPECT_TRUE(false) << "CreateOffer changed number of transceivers!";
return offer;
}
CheckTransceiverInvariants(transceiversBefore,
GetTransceivers(*mSessionOff));
for (size_t i = 0; i < transceiversBefore.size(); ++i) {
JsepTransceiver oldTransceiver = transceiversBefore[i];
JsepTransceiver newTransceiver = GetTransceivers(*mSessionOff)[i];
EXPECT_EQ(oldTransceiver.IsStopped(), newTransceiver.IsStopped());
if (oldTransceiver.IsStopped()) {
if (!newTransceiver.HasLevel()) {
// Tolerate unmapping of stopped transceivers by removing this
// difference.
oldTransceiver.ClearLevel();
}
} else if (!oldTransceiver.HasLevel()) {
EXPECT_TRUE(newTransceiver.HasLevel());
// Tolerate new mappings.
oldTransceiver.SetLevel(newTransceiver.GetLevel());
}
EXPECT_TRUE(Equals(oldTransceiver, newTransceiver));
}
return offer;
}
typedef enum { NO_ADDTRACK_MAGIC, ADDTRACK_MAGIC } AddTrackMagic;
void AddTracks(JsepSessionImpl& side, AddTrackMagic magic = ADDTRACK_MAGIC) {
// Add tracks.
if (types.empty()) {
types = BuildTypes(GetParam());
}
AddTracks(side, types, magic);
// Now, we move datachannel to the end
auto it =
std::find(types.begin(), types.end(), SdpMediaSection::kApplication);
if (it != types.end()) {
types.erase(it);
types.push_back(SdpMediaSection::kApplication);
}
}
void AddTracks(JsepSessionImpl& side, const std::string& mediatypes,
AddTrackMagic magic = ADDTRACK_MAGIC) {
AddTracks(side, BuildTypes(mediatypes), magic);
}
JsepTrack RemoveTrack(JsepSession& side, size_t index) {
if (GetTransceivers(side).size() <= index) {
EXPECT_TRUE(false) << "Index " << index << " out of bounds!";
return JsepTrack(SdpMediaSection::kAudio, sdp::kSend);
}
JsepTransceiver transceiver(GetTransceivers(side)[index]);
JsepTrack& track = transceiver.mSendTrack;
EXPECT_FALSE(track.GetStreamIds().empty()) << "No track at index " << index;
JsepTrack original(track);
track.ClearStreamIds();
transceiver.mJsDirection &= SdpDirectionAttribute::Direction::kRecvonly;
side.SetTransceiver(transceiver);
return original;
}
void SetDirection(JsepSession& side, size_t index,
SdpDirectionAttribute::Direction direction) {
ASSERT_LT(index, GetTransceivers(side).size())
<< "Index " << index << " out of bounds!";
auto transceiver = GetTransceivers(side)[index];
transceiver.mJsDirection = direction;
side.SetTransceiver(transceiver);
}
std::vector<SdpMediaSection::MediaType> BuildTypes(
const std::string& mediatypes) {
std::vector<SdpMediaSection::MediaType> result;
size_t ptr = 0;
for (;;) {
size_t comma = mediatypes.find(',', ptr);
std::string chunk = mediatypes.substr(ptr, comma - ptr);
if (chunk == "audio") {
result.push_back(SdpMediaSection::kAudio);
} else if (chunk == "video") {
result.push_back(SdpMediaSection::kVideo);
} else if (chunk == "datachannel") {
result.push_back(SdpMediaSection::kApplication);
} else {
MOZ_CRASH();
}
if (comma == std::string::npos) break;
ptr = comma + 1;
}
return result;
}
void AddTracks(JsepSessionImpl& side,
const std::vector<SdpMediaSection::MediaType>& mediatypes,
AddTrackMagic magic = ADDTRACK_MAGIC) {
std::string stream_id;
std::string track_id;
ASSERT_TRUE(mUuidGen.Generate(&stream_id));
AddTracksToStream(side, stream_id, mediatypes, magic);
}
void AddTracksToStream(JsepSessionImpl& side, const std::string stream_id,
const std::string& mediatypes,
AddTrackMagic magic = ADDTRACK_MAGIC) {
AddTracksToStream(side, stream_id, BuildTypes(mediatypes), magic);
}
// A bit of a hack. JsepSessionImpl populates the track-id automatically, just
// in case, because the w3c spec requires msid to be set even when there's no
// send track.
bool IsNull(const JsepTrack& track) const {
return track.GetStreamIds().empty() &&
(track.GetMediaType() != SdpMediaSection::MediaType::kApplication);
}
void AddTracksToStream(
JsepSessionImpl& side, const std::string stream_id,
const std::vector<SdpMediaSection::MediaType>& mediatypes,
AddTrackMagic magic = ADDTRACK_MAGIC)
{
std::string track_id;
for (auto type : mediatypes) {
ASSERT_TRUE(mUuidGen.Generate(&track_id));
Maybe<JsepTransceiver> suitableTransceiver;
size_t i;
if (magic == ADDTRACK_MAGIC) {
// We're simulating addTrack.
for (i = 0; i < GetTransceivers(side).size(); ++i) {
auto transceiver = GetTransceivers(side)[i];
if (transceiver.mSendTrack.GetMediaType() != type) {
continue;
}
if (IsNull(transceiver.mSendTrack) ||
transceiver.GetMediaType() == SdpMediaSection::kApplication) {
suitableTransceiver = Some(transceiver);
break;
}
}
}
if (!suitableTransceiver) {
i = GetTransceivers(side).size();
side.AddTransceiver(JsepTransceiver(type, mUuidGen));
suitableTransceiver = Some(GetTransceivers(side).back());
if (magic == ADDTRACK_MAGIC) {
suitableTransceiver->SetAddTrackMagic();
}
}
std::cerr << "Updating send track for transceiver " << i << std::endl;
suitableTransceiver->SetOnlyExistsBecauseOfSetRemote(false);
suitableTransceiver->mJsDirection |=
SdpDirectionAttribute::Direction::kSendonly;
suitableTransceiver->mSendTrack.UpdateStreamIds(
std::vector<std::string>(1, stream_id));
side.SetTransceiver(*suitableTransceiver);
}
}
bool HasMediaStream(const std::vector<JsepTrack>& tracks) const {
for (const auto& track : tracks) {
if (track.GetMediaType() != SdpMediaSection::kApplication) {
return true;
}
}
return false;
}
const std::string GetFirstLocalStreamId(JsepSessionImpl& side) const {
auto tracks = GetLocalTracks(side);
return tracks.begin()->GetStreamIds()[0];
}
std::vector<JsepTrack> GetLocalTracks(const JsepSession& session) const {
std::vector<JsepTrack> result;
for (const auto& transceiver : GetTransceivers(session)) {
if (!IsNull(transceiver.mSendTrack)) {
result.push_back(transceiver.mSendTrack);
}
}
return result;
}
std::vector<JsepTrack> GetRemoteTracks(const JsepSession& session) const {
std::vector<JsepTrack> result;
for (const auto& transceiver : GetTransceivers(session)) {
if (!IsNull(transceiver.mRecvTrack)) {
result.push_back(transceiver.mRecvTrack);
}
}
return result;
}
JsepTransceiver* GetDatachannelTransceiver(JsepSession& side) {
for (auto& transceiver : GetTransceivers(side)) {
if (transceiver.mSendTrack.GetMediaType() ==
SdpMediaSection::MediaType::kApplication) {
return &transceiver;
}
}
return nullptr;
}
JsepTransceiver* GetNegotiatedTransceiver(JsepSession& side, size_t index) {
for (auto& transceiver : GetTransceivers(side)) {
if (transceiver.mSendTrack.GetNegotiatedDetails() ||
transceiver.mRecvTrack.GetNegotiatedDetails()) {
if (index) {
--index;
continue;
}
return &transceiver;
}
}
return nullptr;
}
Maybe<JsepTransceiver> GetTransceiverByLevel(
const std::vector<JsepTransceiver>& transceivers, size_t level) {
for (auto& transceiver : transceivers) {
if (transceiver.HasLevel() && transceiver.GetLevel() == level) {
return Some(transceiver);
}
}
return Nothing();
}
Maybe<JsepTransceiver> GetTransceiverByLevel(JsepSession& side,
size_t level) {
return GetTransceiverByLevel(GetTransceivers(side), level);
}
std::vector<std::string> GetMediaStreamIds(
const std::vector<JsepTrack>& tracks) const {
std::vector<std::string> ids;
for (const auto& track : tracks) {
// data channels don't have msid's
if (track.GetMediaType() == SdpMediaSection::kApplication) {
continue;
}
ids.insert(ids.end(), track.GetStreamIds().begin(),
track.GetStreamIds().end());
}
return ids;
}
std::vector<std::string> GetLocalMediaStreamIds(JsepSessionImpl& side) const {
return GetMediaStreamIds(GetLocalTracks(side));
}
std::vector<std::string> GetRemoteMediaStreamIds(
JsepSessionImpl& side) const {
return GetMediaStreamIds(GetRemoteTracks(side));
}
std::vector<std::string> sortUniqueStrVector(
std::vector<std::string> in) const {
std::sort(in.begin(), in.end());
auto it = std::unique(in.begin(), in.end());
in.resize(std::distance(in.begin(), it));
return in;
}
std::vector<std::string> GetLocalUniqueStreamIds(
JsepSessionImpl& side) const {
return sortUniqueStrVector(GetLocalMediaStreamIds(side));
}
std::vector<std::string> GetRemoteUniqueStreamIds(
JsepSessionImpl& side) const {
return sortUniqueStrVector(GetRemoteMediaStreamIds(side));
}
JsepTrack GetTrack(JsepSessionImpl& side, SdpMediaSection::MediaType type,
size_t index) const {
for (const auto& transceiver : GetTransceivers(side)) {
if (IsNull(transceiver.mSendTrack) ||
transceiver.mSendTrack.GetMediaType() != type) {
continue;
}
if (index != 0) {
--index;
continue;
}
return transceiver.mSendTrack;
}
return JsepTrack(type, sdp::kSend);
}
JsepTrack GetTrackOff(size_t index, SdpMediaSection::MediaType type) {
return GetTrack(*mSessionOff, type, index);
}
JsepTrack GetTrackAns(size_t index, SdpMediaSection::MediaType type) {
return GetTrack(*mSessionAns, type, index);
}
bool Equals(const SdpFingerprintAttributeList::Fingerprint& f1,
const SdpFingerprintAttributeList::Fingerprint& f2) const {
if (f1.hashFunc != f2.hashFunc) {
return false;
}
if (f1.fingerprint != f2.fingerprint) {
return false;
}
return true;
}
bool Equals(const SdpFingerprintAttributeList& f1,
const SdpFingerprintAttributeList& f2) const {
if (f1.mFingerprints.size() != f2.mFingerprints.size()) {
return false;
}
for (size_t i = 0; i < f1.mFingerprints.size(); ++i) {
if (!Equals(f1.mFingerprints[i], f2.mFingerprints[i])) {
return false;
}
}
return true;
}
bool Equals(const UniquePtr<JsepDtlsTransport>& t1,
const UniquePtr<JsepDtlsTransport>& t2) const {
if (!t1 && !t2) {
return true;
}
if (!t1 || !t2) {
return false;
}
if (!Equals(t1->GetFingerprints(), t2->GetFingerprints())) {
return false;
}
if (t1->GetRole() != t2->GetRole()) {
return false;
}
return true;
}
bool Equals(const UniquePtr<JsepIceTransport>& t1,
const UniquePtr<JsepIceTransport>& t2) const {
if (!t1 && !t2) {
return true;
}
if (!t1 || !t2) {
return false;
}
if (t1->GetUfrag() != t2->GetUfrag()) {
return false;
}
if (t1->GetPassword() != t2->GetPassword()) {
return false;
}
return true;
}
bool Equals(const JsepTransport& t1, const JsepTransport& t2) const {
if (t1.mTransportId != t2.mTransportId) {
std::cerr << "Transport id differs: " << t1.mTransportId << " vs "
<< t2.mTransportId << std::endl;
return false;
}
if (t1.mComponents != t2.mComponents) {
std::cerr << "Component count differs" << std::endl;
return false;
}
if (!Equals(t1.mIce, t2.mIce)) {
std::cerr << "ICE differs" << std::endl;
return false;
}
return true;
}
bool Equals(const JsepTrack& t1, const JsepTrack& t2) const {
if (t1.GetMediaType() != t2.GetMediaType()) {
return false;
}
if (t1.GetDirection() != t2.GetDirection()) {
return false;
}
if (t1.GetStreamIds() != t2.GetStreamIds()) {
return false;
}
if (t1.GetActive() != t2.GetActive()) {
return false;
}
if (t1.GetCNAME() != t2.GetCNAME()) {
return false;
}
if (t1.GetSsrcs() != t2.GetSsrcs()) {
return false;
}
return true;
}
bool Equals(const JsepTransceiver& p1, const JsepTransceiver& p2) const {
if (p1.HasLevel() != p2.HasLevel()) {
std::cerr << "One transceiver has a level, the other doesn't"
<< std::endl;
return false;
}
if (p1.HasLevel() && (p1.GetLevel() != p2.GetLevel())) {
std::cerr << "Level differs: " << p1.GetLevel() << " vs " << p2.GetLevel()
<< std::endl;
return false;
}
// We don't check things like BundleLevel(), since that can change without
// any changes to the transport, which is what we're really interested in.
if (p1.IsStopped() != p2.IsStopped()) {
std::cerr << "One transceiver is stopped, the other is not" << std::endl;
return false;
}
if (p1.IsAssociated() != p2.IsAssociated()) {
std::cerr << "One transceiver has a mid, the other doesn't" << std::endl;
return false;
}
if (p1.IsAssociated() && (p1.GetMid() != p2.GetMid())) {
std::cerr << "mid differs: " << p1.GetMid() << " vs " << p2.GetMid()
<< std::endl;
return false;
}
if (!Equals(p1.mSendTrack, p2.mSendTrack)) {
std::cerr << "Send track differs" << std::endl;
return false;
}
if (!Equals(p1.mRecvTrack, p2.mRecvTrack)) {
std::cerr << "Receive track differs" << std::endl;
return false;
}
if (!Equals(p1.mTransport, p2.mTransport)) {
std::cerr << "Transport differs" << std::endl;
return false;
}
return true;
}
bool Equals(const std::vector<JsepTransceiver>& t1,
const std::vector<JsepTransceiver>& t2) const {
if (t1.size() != t2.size()) {
std::cerr << "Size differs: t1.size = " << t1.size()
<< ", t2.size = " << t2.size() << std::endl;
return false;
}
for (size_t i = 0; i < t1.size(); ++i) {
if (!Equals(t1[i], t2[i])) {
return false;
}
}
return true;
}
size_t GetTrackCount(JsepSessionImpl& side,
SdpMediaSection::MediaType type) const {
size_t result = 0;
for (const auto& track : GetLocalTracks(side)) {
if (track.GetMediaType() == type) {
++result;
}
}
return result;
}
UniquePtr<Sdp> GetParsedLocalDescription(const JsepSessionImpl& side) const {
return Parse(side.GetLocalDescription(kJsepDescriptionCurrent));
}
SdpMediaSection* GetMsection(Sdp& sdp, SdpMediaSection::MediaType type,
size_t index) const {
for (size_t i = 0; i < sdp.GetMediaSectionCount(); ++i) {
auto& msection = sdp.GetMediaSection(i);
if (msection.GetMediaType() != type) {
continue;
}
if (index) {
--index;
continue;
}
return &msection;
}
return nullptr;
}
void SetPayloadTypeNumber(JsepSession& session, const std::string& codecName,
const std::string& payloadType) {
for (auto& codec : session.Codecs()) {
if (codec->mName == codecName) {
codec->mDefaultPt = payloadType;
}
}
}
void SetCodecEnabled(JsepSession& session, const std::string& codecName,
bool enabled) {
for (auto& codec : session.Codecs()) {
if (codec->mName == codecName) {
codec->mEnabled = enabled;
}
}
}
void EnsureNegotiationFailure(SdpMediaSection::MediaType type,
const std::string& codecName) {
for (auto& codec : mSessionOff->Codecs()) {
if (codec->Type() == type && codec->mName != codecName) {
codec->mEnabled = false;
}
}
for (auto& codec : mSessionAns->Codecs()) {
if (codec->Type() == type && codec->mName == codecName) {
codec->mEnabled = false;
}
}
}
std::string CreateAnswer() {
std::vector<JsepTransceiver> transceiversBefore =
GetTransceivers(*mSessionAns);
JsepAnswerOptions options;
std::string answer;
JsepSession::Result result = mSessionAns->CreateAnswer(options, &answer);
EXPECT_FALSE(result.mError.isSome());
std::cerr << "ANSWER: " << answer << std::endl;
ValidateTransport(*mAnswererTransport, answer, sdp::kAnswer);
CheckTransceiverInvariants(transceiversBefore,
GetTransceivers(*mSessionAns));
return answer;
}
static const uint32_t NO_CHECKS = 0;
static const uint32_t CHECK_SUCCESS = 1;
static const uint32_t CHECK_TRACKS = 1 << 2;
static const uint32_t ALL_CHECKS = CHECK_SUCCESS | CHECK_TRACKS;
void OfferAnswer(uint32_t checkFlags = ALL_CHECKS,
const Maybe<JsepOfferOptions>& options = Nothing()) {
std::string offer = CreateOffer(options);
SetLocalOffer(offer, checkFlags);
SetRemoteOffer(offer, checkFlags);
std::string answer = CreateAnswer();
SetLocalAnswer(answer, checkFlags);
SetRemoteAnswer(answer, checkFlags);
}
void SetLocalOffer(const std::string& offer,
uint32_t checkFlags = ALL_CHECKS) {
std::vector<JsepTransceiver> transceiversBefore =
GetTransceivers(*mSessionOff);
JsepSession::Result result =
mSessionOff->SetLocalDescription(kJsepSdpOffer, offer);
CheckTransceiverInvariants(transceiversBefore,
GetTransceivers(*mSessionOff));
if (checkFlags & CHECK_SUCCESS) {
ASSERT_FALSE(result.mError.isSome());
}
if (checkFlags & CHECK_TRACKS) {
// This assumes no recvonly or inactive transceivers.
ASSERT_EQ(types.size(), GetTransceivers(*mSessionOff).size());
for (const auto& transceiver : GetTransceivers(*mSessionOff)) {
if (!transceiver.HasLevel()) {
continue;
}
const auto& track(transceiver.mSendTrack);
size_t level = transceiver.GetLevel();
ASSERT_FALSE(IsNull(track));
ASSERT_EQ(types[level], track.GetMediaType());
if (track.GetMediaType() != SdpMediaSection::kApplication) {
std::string msidAttr("a=msid:");
msidAttr += track.GetStreamIds()[0];
ASSERT_NE(std::string::npos, offer.find(msidAttr))
<< "Did not find " << msidAttr << " in offer";
}
}
if (types.size() == 1 && types[0] == SdpMediaSection::kApplication) {
ASSERT_EQ(std::string::npos, offer.find("a=ssrc"))
<< "Data channel should not contain SSRC";
}
}
}
void SetRemoteOffer(const std::string& offer,
uint32_t checkFlags = ALL_CHECKS) {
std::vector<JsepTransceiver> transceiversBefore =
GetTransceivers(*mSessionAns);
JsepSession::Result result =
mSessionAns->SetRemoteDescription(kJsepSdpOffer, offer);
CheckTransceiverInvariants(transceiversBefore,
GetTransceivers(*mSessionAns));
if (checkFlags & CHECK_SUCCESS) {
ASSERT_FALSE(result.mError.isSome());
}
if (checkFlags & CHECK_TRACKS) {
// This assumes no recvonly or inactive transceivers.
ASSERT_EQ(types.size(), GetTransceivers(*mSessionAns).size());
for (const auto& transceiver : GetTransceivers(*mSessionAns)) {
if (!transceiver.HasLevel()) {
continue;
}
const auto& track(transceiver.mRecvTrack);
size_t level = transceiver.GetLevel();
ASSERT_FALSE(IsNull(track));
ASSERT_EQ(types[level], track.GetMediaType());
if (track.GetMediaType() != SdpMediaSection::kApplication) {
std::string msidAttr("a=msid:");
msidAttr += track.GetStreamIds()[0];
// Track id will not match, and will eventually not be present at all
ASSERT_NE(std::string::npos, offer.find(msidAttr))
<< "Did not find " << msidAttr << " in offer";
}
}
}
}
void SetLocalAnswer(const std::string& answer,
uint32_t checkFlags = ALL_CHECKS) {
std::vector<JsepTransceiver> transceiversBefore =
GetTransceivers(*mSessionAns);
JsepSession::Result result =
mSessionAns->SetLocalDescription(kJsepSdpAnswer, answer);
if (checkFlags & CHECK_SUCCESS) {
ASSERT_FALSE(result.mError.isSome());
}
CheckTransceiverInvariants(transceiversBefore,
GetTransceivers(*mSessionAns));
if (checkFlags & CHECK_TRACKS) {
// Verify that the right stuff is in the tracks.
ASSERT_EQ(types.size(), GetTransceivers(*mSessionAns).size());
for (const auto& transceiver : GetTransceivers(*mSessionAns)) {
if (!transceiver.HasLevel()) {
continue;
}
const auto& sendTrack(transceiver.mSendTrack);
const auto& recvTrack(transceiver.mRecvTrack);
size_t level = transceiver.GetLevel();
ASSERT_FALSE(IsNull(sendTrack));
ASSERT_EQ(types[level], sendTrack.GetMediaType());
// These might have been in the SDP, or might have been randomly
// chosen by JsepSessionImpl
ASSERT_FALSE(IsNull(recvTrack));
ASSERT_EQ(types[level], recvTrack.GetMediaType());
if (recvTrack.GetMediaType() != SdpMediaSection::kApplication) {
std::string msidAttr("a=msid:");
msidAttr += sendTrack.GetStreamIds()[0];
ASSERT_NE(std::string::npos, answer.find(msidAttr))
<< "Did not find " << msidAttr << " in answer";
}
}
if (types.size() == 1 && types[0] == SdpMediaSection::kApplication) {
ASSERT_EQ(std::string::npos, answer.find("a=ssrc"))
<< "Data channel should not contain SSRC";
}
}
std::cerr << "Answerer transceivers:" << std::endl;
DumpTransceivers(*mSessionAns);
}
void SetRemoteAnswer(const std::string& answer,
uint32_t checkFlags = ALL_CHECKS) {
std::vector<JsepTransceiver> transceiversBefore =
GetTransceivers(*mSessionOff);
JsepSession::Result result =
mSessionOff->SetRemoteDescription(kJsepSdpAnswer, answer);
if (checkFlags & CHECK_SUCCESS) {
ASSERT_FALSE(result.mError.isSome());
}
CheckTransceiverInvariants(transceiversBefore,
GetTransceivers(*mSessionOff));
if (checkFlags & CHECK_TRACKS) {
// Verify that the right stuff is in the tracks.
ASSERT_EQ(types.size(), GetTransceivers(*mSessionOff).size());
for (const auto& transceiver : GetTransceivers(*mSessionOff)) {
if (!transceiver.HasLevel()) {
continue;
}
const auto& sendTrack(transceiver.mSendTrack);
const auto& recvTrack(transceiver.mRecvTrack);
size_t level = transceiver.GetLevel();
ASSERT_FALSE(IsNull(sendTrack));
ASSERT_EQ(types[level], sendTrack.GetMediaType());
// These might have been in the SDP, or might have been randomly
// chosen by JsepSessionImpl
ASSERT_FALSE(IsNull(recvTrack));
ASSERT_EQ(types[level], recvTrack.GetMediaType());
if (recvTrack.GetMediaType() != SdpMediaSection::kApplication) {
std::string msidAttr("a=msid:");
msidAttr += recvTrack.GetStreamIds()[0];
// Track id will not match, and will eventually not be present at all
ASSERT_NE(std::string::npos, answer.find(msidAttr))
<< "Did not find " << msidAttr << " in answer";
}
}
}
std::cerr << "Offerer transceivers:" << std::endl;
DumpTransceivers(*mSessionOff);
}
std::string GetTransportId(const JsepSession& session, size_t level) {
for (const auto& transceiver : GetTransceivers(session)) {
if (transceiver.HasLevel() && transceiver.GetLevel() == level) {
return transceiver.mTransport.mTransportId;
}
}
return std::string();
}
typedef enum { RTP = 1, RTCP = 2 } ComponentType;
class CandidateSet {
public:
CandidateSet() {}
void Gather(JsepSession& session, ComponentType maxComponent = RTCP) {
for (const auto& transceiver : GetTransceivers(session)) {
if (transceiver.HasOwnTransport()) {
Gather(session, transceiver.mTransport.mTransportId, RTP);
if (transceiver.mTransport.mComponents > 1) {
Gather(session, transceiver.mTransport.mTransportId, RTCP);
}
}
}
FinishGathering(session);
}
void Gather(JsepSession& session, const std::string& transportId,
ComponentType component) {
static uint16_t port = 1000;
std::vector<std::string> candidates;
for (size_t i = 0; i < kNumCandidatesPerComponent; ++i) {
++port;
std::ostringstream candidate;
candidate << "0 " << static_cast<uint16_t>(component)
<< " UDP 9999 192.168.0.1 " << port << " typ host";
std::string mid;
uint16_t level = 0;
bool skipped;
session.AddLocalIceCandidate(kAEqualsCandidate + candidate.str(),
transportId, "", &level, &mid, &skipped);
if (!skipped) {
mCandidatesToTrickle.push_back(std::tuple<Level, Mid, Candidate>(
level, mid, kAEqualsCandidate + candidate.str()));
candidates.push_back(candidate.str());
}
}
// Stomp existing candidates
mCandidates[transportId][component] = candidates;
// Stomp existing defaults
mDefaultCandidates[transportId][component] =
std::make_pair("192.168.0.1", port);
session.UpdateDefaultCandidate(
mDefaultCandidates[transportId][RTP].first,
mDefaultCandidates[transportId][RTP].second,
// Will be empty string if not present, which is how we indicate
// that there is no default for RTCP
mDefaultCandidates[transportId][RTCP].first,
mDefaultCandidates[transportId][RTCP].second, transportId);
}
void FinishGathering(JsepSession& session) const {
// Copy so we can be terse and use []
for (auto idAndCandidates : mDefaultCandidates) {
ASSERT_EQ(1U, idAndCandidates.second.count(RTP));
// do a final UpdateDefaultCandidate here in case candidates were
// cleared during renegotiation.
session.UpdateDefaultCandidate(
idAndCandidates.second[RTP].first,
idAndCandidates.second[RTP].second,
// Will be empty string if not present, which is how we indicate
// that there is no default for RTCP
idAndCandidates.second[RTCP].first,
idAndCandidates.second[RTCP].second, idAndCandidates.first);
std::string mid;
uint16_t level = 0;
bool skipped;
session.AddLocalIceCandidate("", idAndCandidates.first, "", &level,
&mid, &skipped);
}
}
void Trickle(JsepSession& session) {
std::string transportId;
for (const auto& levelMidAndCandidate : mCandidatesToTrickle) {
auto [level, mid, candidate] = levelMidAndCandidate;
std::cerr << "trickling candidate: " << candidate << " level: " << level
<< " mid: " << mid << std::endl;
Maybe<unsigned long> lev = Some(level);
session.AddRemoteIceCandidate(candidate, mid, lev, "", &transportId);
}
session.AddRemoteIceCandidate("", "", Maybe<uint16_t>(), "",
&transportId);
mCandidatesToTrickle.clear();
}
void CheckRtpCandidates(bool expectRtpCandidates,
const SdpMediaSection& msection,
const std::string& transportId,
const std::string& context) const {
auto& attrs = msection.GetAttributeList();
ASSERT_EQ(expectRtpCandidates,
attrs.HasAttribute(SdpAttribute::kCandidateAttribute))
<< context << " (level " << msection.GetLevel() << ")";
if (expectRtpCandidates) {
// Copy so we can be terse and use []
auto expectedCandidates = mCandidates;
ASSERT_LE(kNumCandidatesPerComponent,
expectedCandidates[transportId][RTP].size());
auto& candidates = attrs.GetCandidate();
ASSERT_LE(kNumCandidatesPerComponent, candidates.size())
<< context << " (level " << msection.GetLevel() << ")";
for (size_t i = 0; i < kNumCandidatesPerComponent; ++i) {
ASSERT_EQ(expectedCandidates[transportId][RTP][i], candidates[i])
<< context << " (level " << msection.GetLevel() << ")";
}
}
}
void CheckRtcpCandidates(bool expectRtcpCandidates,
const SdpMediaSection& msection,
const std::string& transportId,
const std::string& context) const {
auto& attrs = msection.GetAttributeList();
if (expectRtcpCandidates) {
// Copy so we can be terse and use []
auto expectedCandidates = mCandidates;
ASSERT_LE(kNumCandidatesPerComponent,
expectedCandidates[transportId][RTCP].size());
ASSERT_TRUE(attrs.HasAttribute(SdpAttribute::kCandidateAttribute))
<< context << " (level " << msection.GetLevel() << ")";
auto& candidates = attrs.GetCandidate();
ASSERT_EQ(kNumCandidatesPerComponent * 2, candidates.size())
<< context << " (level " << msection.GetLevel() << ")";
for (size_t i = 0; i < kNumCandidatesPerComponent; ++i) {
ASSERT_EQ(expectedCandidates[transportId][RTCP][i],
candidates[i + kNumCandidatesPerComponent])
<< context << " (level " << msection.GetLevel() << ")";
}
}
}
void CheckDefaultRtpCandidate(bool expectDefault,
const SdpMediaSection& msection,
const std::string& transportId,
const std::string& context) const {
Address expectedAddress = "0.0.0.0";
Port expectedPort = 9U;
if (expectDefault) {
// Copy so we can be terse and use []
auto defaultCandidates = mDefaultCandidates;
expectedAddress = defaultCandidates[transportId][RTP].first;
expectedPort = defaultCandidates[transportId][RTP].second;
}
// if bundle-only attribute is present, expect port 0
const SdpAttributeList& attrs = msection.GetAttributeList();
if (attrs.HasAttribute(SdpAttribute::kBundleOnlyAttribute)) {
expectedPort = 0U;
}
ASSERT_EQ(expectedAddress, msection.GetConnection().GetAddress())
<< context << " (level " << msection.GetLevel() << ")";
ASSERT_EQ(expectedPort, msection.GetPort())
<< context << " (level " << msection.GetLevel() << ")";
}
void CheckDefaultRtcpCandidate(bool expectDefault,
const SdpMediaSection& msection,
const std::string& transportId,
const std::string& context) const {
if (expectDefault) {
// Copy so we can be terse and use []
auto defaultCandidates = mDefaultCandidates;
ASSERT_TRUE(msection.GetAttributeList().HasAttribute(
SdpAttribute::kRtcpAttribute))
<< context << " (level " << msection.GetLevel() << ")";
auto& rtcpAttr = msection.GetAttributeList().GetRtcp();
ASSERT_EQ(defaultCandidates[transportId][RTCP].second, rtcpAttr.mPort)
<< context << " (level " << msection.GetLevel() << ")";
ASSERT_EQ(sdp::kInternet, rtcpAttr.mNetType)
<< context << " (level " << msection.GetLevel() << ")";
ASSERT_EQ(sdp::kIPv4, rtcpAttr.mAddrType)
<< context << " (level " << msection.GetLevel() << ")";
ASSERT_EQ(defaultCandidates[transportId][RTCP].first, rtcpAttr.mAddress)
<< context << " (level " << msection.GetLevel() << ")";
} else {
ASSERT_FALSE(msection.GetAttributeList().HasAttribute(
SdpAttribute::kRtcpAttribute))
<< context << " (level " << msection.GetLevel() << ")";
}
}
private:
typedef size_t Level;
typedef std::string TransportId;
typedef std::string Mid;
typedef std::string Candidate;
typedef std::string Address;
typedef uint16_t Port;
// Default candidates are put into the m-line, c-line, and rtcp
// attribute for endpoints that don't support ICE.
std::map<TransportId, std::map<ComponentType, std::pair<Address, Port>>>
mDefaultCandidates;
std::map<TransportId, std::map<ComponentType, std::vector<Candidate>>>
mCandidates;
// Level/mid/candidate tuples that need to be trickled
std::vector<std::tuple<Level, Mid, Candidate>> mCandidatesToTrickle;
};
// For streaming parse errors
std::string GetParseErrors(
const UniquePtr<SdpParser::Results>& results) const {
std::stringstream output;
auto errors = std::move(results->Errors());
for (auto error : errors) {
output << error.first << ": " << error.second << std::endl;
}
return output.str();
}
void CheckEndOfCandidates(bool expectEoc, const SdpMediaSection& msection,
const std::string& context) {
if (expectEoc) {
ASSERT_TRUE(msection.GetAttributeList().HasAttribute(
SdpAttribute::kEndOfCandidatesAttribute))
<< context << " (level " << msection.GetLevel() << ")";
} else {
ASSERT_FALSE(msection.GetAttributeList().HasAttribute(
SdpAttribute::kEndOfCandidatesAttribute))
<< context << " (level " << msection.GetLevel() << ")";
}
}
void CheckTransceiversAreBundled(const JsepSession& session,
const std::string& context) {
for (const auto& transceiver : GetTransceivers(session)) {
ASSERT_TRUE(transceiver.HasBundleLevel())
<< context;
ASSERT_EQ(0U, transceiver.BundleLevel()) << context;
ASSERT_NE("", transceiver.mTransport.mTransportId);
}
}
void DisableMsid(std::string* sdp) const {
while (true) {
size_t pos = sdp->find("a=msid");
if (pos == std::string::npos) {
break;
}
(*sdp)[pos + 2] = 'X'; // garble, a=Xsid
}
}
void DisableBundle(std::string* sdp) const {
size_t pos = sdp->find("a=group:BUNDLE");
ASSERT_NE(std::string::npos, pos);
(*sdp)[pos + 11] = 'G'; // garble, a=group:BUNGLE
}
void DisableMsection(std::string* sdp, size_t level) const {
UniquePtr<Sdp> parsed(Parse(*sdp));
ASSERT_TRUE(parsed.get());
ASSERT_LT(level, parsed->GetMediaSectionCount());
SdpHelper::DisableMsection(parsed.get(), &parsed->GetMediaSection(level));
(*sdp) = parsed->ToString();
}
void CopyTransportAttributes(std::string* sdp, size_t src_level,
size_t dst_level) {
UniquePtr<Sdp> parsed(Parse(*sdp));
ASSERT_TRUE(parsed.get());
ASSERT_LT(src_level, parsed->GetMediaSectionCount());
ASSERT_LT(dst_level, parsed->GetMediaSectionCount());
nsresult rv =
mSdpHelper.CopyTransportParams(2, parsed->GetMediaSection(src_level),
&parsed->GetMediaSection(dst_level));
ASSERT_EQ(NS_OK, rv);
(*sdp) = parsed->ToString();
}
void ReplaceInSdp(std::string* sdp, const char* searchStr,
const char* replaceStr) const {
if (searchStr[0] == '\0') return;
size_t pos = 0;
while ((pos = sdp->find(searchStr, pos)) != std::string::npos) {
sdp->replace(pos, strlen(searchStr), replaceStr);
pos += strlen(replaceStr);
}
}
void ValidateDisabledMSection(const SdpMediaSection* msection) {
ASSERT_EQ(1U, msection->GetFormats().size());
auto& attrs = msection->GetAttributeList();
ASSERT_TRUE(attrs.HasAttribute(SdpAttribute::kMidAttribute));
ASSERT_TRUE(attrs.HasAttribute(SdpAttribute::kDirectionAttribute));
ASSERT_FALSE(attrs.HasAttribute(SdpAttribute::kBundleOnlyAttribute));
ASSERT_EQ(SdpDirectionAttribute::kInactive,
msection->GetDirectionAttribute().mValue);
ASSERT_EQ(3U, attrs.Count());
if (msection->GetMediaType() == SdpMediaSection::kAudio) {
ASSERT_EQ("0", msection->GetFormats()[0]);
const SdpRtpmapAttributeList::Rtpmap* rtpmap(msection->FindRtpmap("0"));
ASSERT_TRUE(rtpmap);
ASSERT_EQ("0", rtpmap->pt);
ASSERT_EQ("PCMU", rtpmap->name);
} else if (msection->GetMediaType() == SdpMediaSection::kVideo) {
ASSERT_EQ("120", msection->GetFormats()[0]);
const SdpRtpmapAttributeList::Rtpmap* rtpmap(msection->FindRtpmap("120"));
ASSERT_TRUE(rtpmap);
ASSERT_EQ("120", rtpmap->pt);
ASSERT_EQ("VP8", rtpmap->name);
} else if (msection->GetMediaType() == SdpMediaSection::kApplication) {
if (msection->GetProtocol() == SdpMediaSection::kUdpDtlsSctp ||
msection->GetProtocol() == SdpMediaSection::kTcpDtlsSctp) {
// draft 21 format
ASSERT_EQ("webrtc-datachannel", msection->GetFormats()[0]);
ASSERT_FALSE(msection->GetSctpmap());
ASSERT_EQ(0U, msection->GetSctpPort());
} else {
// old draft 05 format
ASSERT_EQ("0", msection->GetFormats()[0]);
const SdpSctpmapAttributeList::Sctpmap* sctpmap(msection->GetSctpmap());
ASSERT_TRUE(sctpmap);
ASSERT_EQ("0", sctpmap->pt);
ASSERT_EQ("rejected", sctpmap->name);
ASSERT_EQ(0U, sctpmap->streams);
}
} else {
// Not that we would have any test which tests this...
ASSERT_EQ("19", msection->GetFormats()[0]);
const SdpRtpmapAttributeList::Rtpmap* rtpmap(msection->FindRtpmap("19"));
ASSERT_TRUE(rtpmap);
ASSERT_EQ("19", rtpmap->pt);
ASSERT_EQ("reserved", rtpmap->name);
}
ASSERT_FALSE(msection->GetAttributeList().HasAttribute(
SdpAttribute::kMsidAttribute));
}
void ValidateSetupAttribute(const JsepSessionImpl& side,
const SdpSetupAttribute::Role expectedRole) {
auto sdp = GetParsedLocalDescription(side);
for (size_t i = 0; sdp && i < sdp->GetMediaSectionCount(); ++i) {
if (sdp->GetMediaSection(i).GetAttributeList().HasAttribute(
SdpAttribute::kSetupAttribute)) {
auto role = sdp->GetMediaSection(i).GetAttributeList().GetSetup().mRole;
ASSERT_EQ(expectedRole, role);
}
}
}
void DumpTrack(const JsepTrack& track) {
const JsepTrackNegotiatedDetails* details = track.GetNegotiatedDetails();
std::cerr << " type=" << track.GetMediaType() << std::endl;
if (!details) {
std::cerr << " not negotiated" << std::endl;
return;
}
std::cerr << " encodings=" << std::endl;
for (size_t i = 0; i < details->GetEncodingCount(); ++i) {
const JsepTrackEncoding& encoding = details->GetEncoding(i);
std::cerr << " id=" << encoding.mRid << std::endl;
for (const auto& codec : encoding.GetCodecs()) {
std::cerr << " " << codec->mName << " enabled("
<< (codec->mEnabled ? "yes" : "no") << ")";
if (track.GetMediaType() == SdpMediaSection::kAudio) {
const JsepAudioCodecDescription* audioCodec =
static_cast<const JsepAudioCodecDescription*>(codec.get());
std::cerr << " dtmf(" << (audioCodec->mDtmfEnabled ? "yes" : "no")
<< ")";
}
if (track.GetMediaType() == SdpMediaSection::kVideo) {
const JsepVideoCodecDescription* videoCodec =
static_cast<const JsepVideoCodecDescription*>(codec.get());
std::cerr << " rtx("
<< (videoCodec->mRtxEnabled ? videoCodec->mRtxPayloadType
: "no")
<< ")";
}
std::cerr << std::endl;
}
}
}
void DumpTransport(const JsepTransport& transport) {
std::cerr << " id=" << transport.mTransportId << std::endl;
std::cerr << " components=" << transport.mComponents << std::endl;
}
void DumpTransceivers(const JsepSessionImpl& session) {
for (const auto& transceiver : GetTransceivers(session)) {
std::cerr << "Transceiver ";
if (transceiver.HasLevel()) {
std::cerr << transceiver.GetLevel() << std::endl;
} else {
std::cerr << "<NO LEVEL>" << std::endl;
}
if (transceiver.HasBundleLevel()) {
std::cerr << "(bundle level is " << transceiver.BundleLevel() << ")"
<< std::endl;
}
if (!IsNull(transceiver.mSendTrack)) {
std::cerr << "Sending-->" << std::endl;
DumpTrack(transceiver.mSendTrack);
}
if (!IsNull(transceiver.mRecvTrack)) {
std::cerr << "Receiving-->" << std::endl;
DumpTrack(transceiver.mRecvTrack);
}
std::cerr << "Transport-->" << std::endl;
DumpTransport(transceiver.mTransport);
}
}
UniquePtr<Sdp> Parse(const std::string& sdp) const {
SipccSdpParser parser;
auto results = parser.Parse(sdp);
UniquePtr<Sdp> parsed = std::move(results->Sdp());
EXPECT_TRUE(parsed.get()) << "Should have valid SDP" << std::endl
<< "Errors were: " << GetParseErrors(results);
return parsed;
}
std::string SetExtmap(const std::string& aSdp, const std::string& aUri,
uint16_t aId, uint16_t* aOldId = nullptr) {
UniquePtr<Sdp> munge(Parse(aSdp));
for (size_t i = 0; i < munge->GetMediaSectionCount(); ++i) {
auto& attrs = munge->GetMediaSection(i).GetAttributeList();
if (attrs.HasAttribute(SdpAttribute::kExtmapAttribute)) {
auto extmap = attrs.GetExtmap();
for (auto it = extmap.mExtmaps.begin(); it != extmap.mExtmaps.end();
++it) {
if (it->extensionname == aUri) {
if (aOldId) {
*aOldId = it->entry;
}
if (aId) {
it->entry = aId;
} else {
extmap.mExtmaps.erase(it);
}
break;
}
}
attrs.SetAttribute(extmap.Clone());
}
}
return munge->ToString();
}
uint16_t GetExtmap(const std::string& aSdp, const std::string& aUri) {
UniquePtr<Sdp> parsed(Parse(aSdp));
for (size_t i = 0; i < parsed->GetMediaSectionCount(); ++i) {
auto& attrs = parsed->GetMediaSection(i).GetAttributeList();
if (attrs.HasAttribute(SdpAttribute::kExtmapAttribute)) {
auto extmap = attrs.GetExtmap();
for (auto& ext : extmap.mExtmaps) {
if (ext.extensionname == aUri) {
return ext.entry;
}
}
}
}
return 0;
}
void SwapOfferAnswerRoles() {
mSessionOff.swap(mSessionAns);
mOffCandidates.swap(mAnsCandidates);
mOffererTransport.swap(mAnswererTransport);
}
UniquePtr<JsepSessionImpl> mSessionOff;
UniquePtr<CandidateSet> mOffCandidates;
UniquePtr<JsepSessionImpl> mSessionAns;
UniquePtr<CandidateSet> mAnsCandidates;
std::vector<SdpMediaSection::MediaType> types;
std::vector<std::pair<std::string, uint16_t>> mGatheredCandidates;
FakeUuidGenerator mUuidGen;
private:
void ValidateTransport(TransportData& source, const std::string& sdp_str,
sdp::SdpType type) {
UniquePtr<Sdp> sdp(Parse(sdp_str));
ASSERT_TRUE(!!sdp);
size_t num_m_sections = sdp->GetMediaSectionCount();
for (size_t i = 0; i < num_m_sections; ++i) {
auto& msection = sdp->GetMediaSection(i);
if (msection.GetMediaType() == SdpMediaSection::kApplication) {
if (!(msection.GetProtocol() == SdpMediaSection::kUdpDtlsSctp ||
msection.GetProtocol() == SdpMediaSection::kTcpDtlsSctp)) {
// old draft 05 format
ASSERT_EQ(SdpMediaSection::kDtlsSctp, msection.GetProtocol());
}
} else {
ASSERT_EQ(SdpMediaSection::kUdpTlsRtpSavpf, msection.GetProtocol());
}
const SdpAttributeList& attrs = msection.GetAttributeList();
bool bundle_only = attrs.HasAttribute(SdpAttribute::kBundleOnlyAttribute);
// port 0 only means disabled when the bundle-only attribute is missing
if (!bundle_only && msection.GetPort() == 0) {
ValidateDisabledMSection(&msection);
continue;
}
if (mSdpHelper.OwnsTransport(*sdp, i, type)) {
const SdpAttributeList& attrs = msection.GetAttributeList();
ASSERT_FALSE(attrs.GetIceUfrag().empty());
ASSERT_FALSE(attrs.GetIcePwd().empty());
const SdpFingerprintAttributeList& fps = attrs.GetFingerprint();
for (auto fp = fps.mFingerprints.begin(); fp != fps.mFingerprints.end();
++fp) {
nsCString alg_str = "None"_ns;
if (fp->hashFunc == SdpFingerprintAttributeList::kSha1) {
alg_str = "sha-1"_ns;
} else if (fp->hashFunc == SdpFingerprintAttributeList::kSha256) {
alg_str = "sha-256"_ns;
}
ASSERT_EQ(source.mFingerprints[alg_str], fp->fingerprint);
}
ASSERT_EQ(source.mFingerprints.size(), fps.mFingerprints.size());
}
}
}
std::string mLastError;
SdpHelper mSdpHelper;
UniquePtr<TransportData> mOffererTransport;
UniquePtr<TransportData> mAnswererTransport;
};
TEST_F(JsepSessionTestBase, CreateDestroy) {}
TEST_P(JsepSessionTest, CreateOffer) {
AddTracks(*mSessionOff);
CreateOffer();
}
TEST_P(JsepSessionTest, CreateOfferSetLocal) {
AddTracks(*mSessionOff);
std::string offer = CreateOffer();
SetLocalOffer(offer);
}
TEST_P(JsepSessionTest, CreateOfferSetLocalSetRemote) {
AddTracks(*mSessionOff);
std::string offer = CreateOffer();
SetLocalOffer(offer);
SetRemoteOffer(offer);
}
TEST_P(JsepSessionTest, CreateOfferSetLocalSetRemoteCreateAnswer) {
AddTracks(*mSessionOff);
std::string offer = CreateOffer();
SetLocalOffer(offer);
SetRemoteOffer(offer);
AddTracks(*mSessionAns);
std::string answer = CreateAnswer();
}
TEST_P(JsepSessionTest, CreateOfferSetLocalSetRemoteCreateAnswerSetLocal) {
AddTracks(*mSessionOff);
std::string offer = CreateOffer();
SetLocalOffer(offer);
SetRemoteOffer(offer);
AddTracks(*mSessionAns);
std::string answer = CreateAnswer();